(SOLVED)Problèmes de compatibilités [scripts savegames]

Started by Jerelink, March 13, 2017, 09:40:01 PM

Previous topic - Next topic
Bonjour a tous !

  Je me présente, Jerelink, membre de ZS depuis un long moment déjà, je m'intéresse depuis peu au moteur Solarus et souhaite par conséquent obtenir de l'aide sur ce forum  :).

  Tout d'abord, je tiens à m'excuser de ne pas poster ce message en anglais, mes connaissances sont faibles, de ce fait je trouve qu'il est plus judicieux de procéder ainsi plutôt qu'utiliser Google traduction (sauf si vous préférez ce dernier évidement) .

Voici mon problème:
  Débutant que je suis, j'essaie de créer un menu des sauvegardes en me basant sur les tutos de Christopho ( E46 et E47), seulement, il y a quelques petits problèmes de compatibilités entre les versions 1.1 et l'actuelle. Pour commencer, game_manager:create(file_name) ne fonctionne pas sous 1.5. Je l'ai donc remplacé par start_game même si je doute que ça soit son équivalent, mais n'ayant rien trouvé d'autre dans l'api...

  Ensuite, le hearts_builder pour afficher les cœurs sur la/les parties existantes. Ma version du HUD est la même que dans le tuto HUD en 1.5 (EP 33) et dans le script "hearts.lua", la function "hearts_builder" fait appelle a un deuxième paramètre hearts_builder:new(game, config) et c'est là que vient le plus gros du problème.  A l'exécution, tout ce passe correctement, mais dès l'ajout de cette ligne draw_hearts(game, surface) dans la fonction "read_savegames()", j'ai ce message d'erreur qui s'affiche:
QuoteError: In on_started: [string "scripts/hud/hearts.lua"]:12: attempt to index local 'config' (a nil value)

J'ai essayé pas mal de choses pour contourner ce bug mais rien y fait.

Voici mon code :

local savegames_menu = {}

local game_manager = require("scripts/game_manager")
local gui_designer = require("scripts/lib/gui_designer")
local hearts_builder = require("scripts/hud/hearts")

local cursor_img = sol.surface.create("menus/link_cursor.png")
local cursor_position
local savegames_surfaces = {}
local games = {}

local layout

local function build_layout()

  layout = gui_designer:create(320, 240)
  layout:make_background()
  layout:make_big_wooden_frame(16, 8, 160, 32)
  layout:make_text(sol.language.get_string("savegames_menu.title"), 96, 16, "center")
  layout:make_wooden_frame(16, 48, 288, 32)
  layout:make_wooden_frame(16, 96, 288, 32)
  layout:make_wooden_frame(16, 144, 288, 32)
end

-- Places the cursor on the savegame 1, 2 or 3.
local function set_cursor_position(index)

  cursor_position = index
  cursor_img:set_xy(26, 2 + index * 48)
end

local function get_savegame_file_name(index)
  return "save" .. index .. ".dat"
end

-- Draws the hearts of a game on a savegame surface.
local function draw_hearts(game, surface)

  local hearts = hearts_builder:new(game)
  hearts:on_started()
  hearts:on_draw(surface)
end

-- Reads the existing savegames and creates the savegames surfaces.
local function read_savegames()

  for i = 1, 3 do
    local file_name = get_savegame_file_name(i)
    local surface = sol.surface.create(240, 16)
    surface:set_xy(56, 8 + i * 48)
    savegames_surfaces[i] = surface
   
    if not sol.game.exists(file_name) then
      games[i] = nil
    else
      -- Existing file.
      local game = game_manager:start_game(file_name)
      games[i] = game
     
      --draw_hearts(game, surface)
    end
  end
end

function savegames_menu:on_started()

  build_layout()
  read_savegames()
  sol.audio.play_music("game_over")
  set_cursor_position(1)
end

function savegames_menu:on_draw(dst_surface)

  layout:draw(dst_surface)
 
  for i = 1, 3 do
    savegames_surfaces[i]:draw(dst_surface)
  end
  cursor_img:draw(dst_surface)
end

function savegames_menu:on_key_pressed(key)

  local handled = false

  if key == "space" then
    local file_name = get_savegame_file_name(cursor_position)
    game_manager:start_game(file_name)
    sol.menu.stop(savegames_menu)
    handled = true

  elseif key == "down" then
    if cursor_position < 3 then
      set_cursor_position(cursor_position + 1)
    else
      set_cursor_position(1)
    end
    sol.audio.play_sound("cursor")
    handled = true

  elseif key == "up" then
    if cursor_position > 1 then
      set_cursor_position(cursor_position - 1)
    else
      set_cursor_position(3)
    end 
    sol.audio.play_sound("cursor")
    handled = true

  end

  return handled
end

return savegames_menu


Je pense avoir tout dit, je vous remercie de m'avoir lu, bonne journée a tous  ;).

Salut,

Je ne vois pas de problèmes de compatibilité par rapport à Solarus 1.1 dans les messages d'erreur que tu reportes. Par contre ce sont les scripts que ton menu de sauvegarde appelle qui ont changé. Les scripts plus récents sont plus indépendants, plus modulaires, plus réutilisables. Le tuto sur le menu des sauvegarde est vieux.
Tu n'as rien trouvé dans l'API car game_manager n'est pas dans l'API, c'est un script Lua que je mets dans mes jeux et dans les tutos. Même chose pour l'afficheur de cœurs. Il faut donc que tu regardes ces scripts pour voir ce qui a changé.

Le code que tu as posté est celui du menu des sauvegardes du tuto, si j'ai bien compris. Il faut que tu nous postes aussi game_manager.lua et hearts.lua pour savoir ce qui ne va pas.

Salut Christopho,

Effectivement, il s'agit là d'un problème de compatibilité entre scripts et non entre les versions. J'ai eu ce doute a cause du ":create: sans faire le lien avec les fonctions programmé dans le game_manager.
C'est bien le scripts du tuto oui, je l'ai juste adapté pour fonctionner avec le plus récent game_manager.
Voici le game_manager, il est vraiment vide, c'est une nouvelle quête :) :
local game_manager = {}

local initial_game = require("scripts/initial_game")

-- Starts the game from the given savegame file,
-- initializing it if necessary.
function game_manager:start_game(file_name)

  local exists = sol.game.exists(file_name)
  local game = sol.game.load(file_name)
  if not exists then
    -- Initialize a new savegame.
    initial_game:initialize_new_savegame(game)
  end
  game:start()

end

return game_manager


Puis l'afficheur des cœurs, le scripts qui doit poser souci au menus des sauvegarde ^^:

-- Hearts view used in game screen and in the savegames selection screen.

local hearts_builder = {}

local hearts_img = sol.surface.create("hud/hearts.png")

function hearts_builder:new(game, config)

  local hearts = {}

  hearts.surface = sol.surface.create(80, 16)
  hearts.dst_x = config.x
  hearts.dst_y = config.y
  hearts.max_life_displayed = game:get_max_life()
  hearts.current_life_displayed = game:get_life()

  function hearts:repeat_danger_sound()

    if game:get_life() <= game:get_max_life() / 4 then

      sol.audio.play_sound("danger")
      hearts.danger_sound_timer = sol.timer.start(hearts, 750, function()
        hearts:repeat_danger_sound()
      end)
      hearts.danger_sound_timer:set_suspended_with_map(true)
    else
      hearts.danger_sound_timer = nil
    end
  end

  function hearts:rebuild_surface()

    hearts.surface:clear()

    local life = hearts.current_life_displayed
    local max_life = hearts.max_life_displayed
    for j = 1, max_life do
      if j % 2 == 0 then
        local x, y
        if j <= 20 then
          x = 4 * (j - 2)
          y = 0
        else
          x = 4 * (j - 22)
          y = 8
        end
        if life >= j then
          hearts_img:draw_region(0, 0, 8, 8, hearts.surface, x, y)
        else
          hearts_img:draw_region(16, 0, 8, 8, hearts.surface, x, y)
        end
      end
    end
    if life % 2 == 1 then
      local x, y
      if life <= 20 then
        x = 4 * (life - 1)
        y = 0
      else
        x = 4 * (life - 21)
        y = 8
      end
      hearts_img:draw_region(8, 0, 8, 8, hearts.surface, x, y)
    end
  end

  function hearts:on_draw(dst_surface)

    local x, y = hearts.dst_x, hearts.dst_y
    local width, height = dst_surface:get_size()
    if x < 0 then
      x = width + x
    end
    if y < 0 then
      y = height + y
    end

    hearts.surface:draw(dst_surface, x, y + 18)
  end

  -- Checks whether the view displays correct information
  -- and updates it if necessary.
  local function check()

    local need_rebuild = false

    -- Maximum life.
    local max_life = game:get_max_life()
    if max_life ~= hearts.max_life_displayed then
      need_rebuild = true
      hearts.max_life_displayed = max_life
    end

    -- Current life.
    local current_life = game:get_life()
    if current_life == hearts.current_life_displayed then
      hearts.sound_remainder = nil
    else

      need_rebuild = true
      if current_life < hearts.current_life_displayed then
        hearts.current_life_displayed = hearts.current_life_displayed - 1
      else
        hearts.current_life_displayed = hearts.current_life_displayed + 1
        if game:is_started() then
          if hearts.sound_remainder == nil then
            hearts.sound_remainder = hearts.current_life_displayed % 4
          end
          if hearts.current_life_displayed % 4 == hearts.sound_remainder then
            sol.audio.play_sound("heart")
          end
        end
      end
    end

    -- If we are in-game, play an animation and a sound if the life is low.
    if game:is_started() then

      if game:get_life() <= game:get_max_life() / 4
          and not game:is_suspended() then
        need_rebuild = true
        if hearts.danger_sound_timer == nil then
          hearts.danger_sound_timer = sol.timer.start(hearts, 250, function()
            hearts:repeat_danger_sound()
          end)
          hearts.danger_sound_timer:set_suspended_with_map(true)
        end
      end
    end

    -- Redraw the surface only if something has changed.
    if need_rebuild then
      hearts:rebuild_surface()
    end

    return true  -- Repeat the timer.
  end

  function hearts:on_started()

    -- This function is called when the HUD starts or
    -- was disabled and gets enabled again.
    -- Unlike other HUD elements, the timers were canceled because they
    -- are attached to the menu and not to the game
    -- (this is because the hearts may also be used in the savegame menu).
    hearts.danger_sound_timer = nil
    -- Periodically check.
    check()
    sol.timer.start(hearts, 50, check)
    hearts:rebuild_surface()
  end

  return hearts
end

return hearts_builder



Le moteur custom de ROTH SE est un peu compliqué à comprendre aux premiers abords, je conseille celui de MOSDX, surtout pour ceux qui débutent.

Peux tu nous montrer quand hearts_builder:new(game, config) est appellé dans ton script, la ou ca cause l'erreur, pour moi, c'est une erreure basique, vu que quelques lignes plus loins

  hearts.dst_x = config.x
  hearts.dst_y = config.y

config doit avoir une valeure, une table par conséquent, sinon le code ne trouvera jamais config.x car elle n'a pas été définie alors que hearts.dst_x en a besoin

Je n'utilise aucun élément du moteur custom de Zelda ROTH a ma connaissance, d'ailleurs j'ai était voir les scripts du moteur en question afin de m'inspirer, mais je préfère rester sur ceux-ci.

Heart_builder est appelé dans le scripts savegames, dans cette fonction :

local function draw_hearts(game, surface)

  local hearts = hearts_builder:new(game)
  hearts:on_started()
  hearts:on_draw(surface)
end


Ici il manque le paramètre "Config", mais même avec celui-ci ça ne fonctionne pas. Quant à config.x/config.y je me suis posé la même question sans trop y accorder d'importance étant donné que le scripts "Hearts" fonctionne parfaitement IG.


Le seul truc que je vois c'est le config.x, config.y, une table, qui, si n'est pas définie, retournera nil, quelqu'en soit la raison, et donc le script retournera une erreur

@Jerelink: post both the error and all the code of the script that appears in the error. But display the script code as in Lua, so that we can see the lines of the script and find the good one. (To do that, write "code=Lua" in the first bracket, instead of just "code". Always do that when you post Lua code here; that will make our lifes easier.)
"If you make people think they're thinking, they'll love you. But if you really make them think, they'll hate you."

I agree with MetalZelda: this has to be a missing parameter "config" when calling the function
Code (Lua) Select
function hearts_builder:new(game, config)
from some script. And this may be due to some scripts of different versions mixed, so you will need to adapt the code if that is the case.
"If you make people think they're thinking, they'll love you. But if you really make them think, they'll hate you."

Ce paramètre config a été ajouté relativement récemment à mes scripts de HUD pour faciliter l'ajout/suppression/déplacement d'éléments.
Tu dois pouvoir juste passer ce paramètre depuis le script du menu de sauvegardes. Il faut passer un tableau avec les champs x et y correspondant à la position où tu veux afficher les cœurs. Cette position était auparavant indiquée en appelant la fonction set_dst_position().

(Mais ton script heart_builder vient d'où ? Parce que normalement, je croyais avoir justement fait en sorte que le config soit optionnel pour que ça continue à marcher avec l'ancienne façon)

Quote from: MetalZelda on April 02, 2017, 03:37:58 PM
Le seul truc que je vois c'est le config.x, config.y, une table, qui, si n'est pas définie, retournera nil, quelqu'en soit la raison, et donc le script retournera une erreur

Ce que je n'arrive pas a comprendre, c'est que ce script fonctionne très bien dans la partie, les cœurs s'affichent. J'ai était voir du coté du script (script/hud/hud) étant donné que celui-ci gère intégralement le HUD, mais là aussi aucune table.

Quote from: Diarandor on April 02, 2017, 06:26:51 PM
@Jerelink: post both the error and all the code of the script that appears in the error. But display the script code as in Lua, so that we can see the lines of the script and find the good one. (To do that, write "code=Lua" in the first bracket, instead of just "code". Always do that when you post Lua code here; that will make our lifes easier.)

Merci du conseil ;). La seule erreur affiché lors de l'exécution est celle-ci :
Code (lua) Select
Error: In on_started: [string "scripts/hud/hearts.lua"]:12: attempt to index local 'config' (a nil value)

Puis celle-ci qui est selon moi la conséquence de la première erreur ^^ :
Code (lua) Select
Error: In on_draw: [string "scripts/menus/savegames.lua"]:79: attempt to index a nil value

Chose anormale du fait que le script "hearts" n'a aucune erreur sauf lorsque je l'appel dans le script "savegames" :/.

Je reposte ça ici au cas ou:
Code (lua) Select
local savegames_menu = {}

local game_manager = require("scripts/game_manager")
local gui_designer = require("scripts/menus/lib/gui_designer")
local hearts_builder = require("scripts/hud/hearts")
local cursor_img = sol.surface.create("menus/link_cursor.png")
local cursor_position
local savegames_surfaces = {}
local games = {}

local layout

local function build_layout()

  layout = gui_designer:create(320, 240)
  layout:make_green_tiled_background()
  layout:make_big_wooden_frame(16, 8, 160, 32)
  layout:make_text(sol.language.get_string("savegames_menu.title"), 96, 16, "center")
  layout:make_wooden_frame(16, 48, 288, 32)
  layout:make_wooden_frame(16, 96, 288, 32)
  layout:make_wooden_frame(16, 144, 288, 32)
end

-- Places the cursor on the savegame 1, 2 or 3.
local function set_cursor_position(index)

  cursor_position = index
  cursor_img:set_xy(26, 2 + index * 48)
end

local function get_savegame_file_name(index)
  return "save" .. index .. ".dat"
end

-- Draws the hearts of a game on a savegame surface.
local function draw_hearts(game, surface)


  local hearts = hearts_builder:new(game)

  hearts:on_started()
  hearts:on_draw(surface)
end

-- Reads the existing savegames and creates the savegames surfaces.
local function read_savegames()

  for i = 1, 3 do
    local file_name = get_savegame_file_name(i)
    local surface = sol.surface.create(240, 16)
    surface:set_xy(56, 8 + i * 48)
    savegames_surfaces[i] = surface
   
    if not sol.game.exists(file_name) then
      games[i] = nil
    else
      -- Existing file.
      local game = game_manager:start_game(file_name)
      games[i] = game
     
      draw_hearts(game, surface)
    end
  end
end

function savegames_menu:on_started()

  build_layout()
  read_savegames()
  sol.audio.play_music("game_over")
  set_cursor_position(1)
end

function savegames_menu:on_draw(dst_surface)

  layout:draw(dst_surface)
 
  for i = 1, 3 do
    savegames_surfaces[i]:draw(dst_surface)
  end
  cursor_img:draw(dst_surface)
end

function savegames_menu:on_key_pressed(key)

  local handled = false

  if key == "space" then
    local file_name = get_savegame_file_name(cursor_position)
    game_manager:start_game(file_name)
    sol.menu.stop(savegames_menu)
    handled = true

  elseif key == "down" then
    if cursor_position < 3 then
      set_cursor_position(cursor_position + 1)
    else
      set_cursor_position(1)
    end
    sol.audio.play_sound("cursor")
    handled = true

  elseif key == "up" then
    if cursor_position > 1 then
      set_cursor_position(cursor_position - 1)
    else
      set_cursor_position(3)
    end 
    sol.audio.play_sound("cursor")
    handled = true

  end

  return handled
end

return savegames_menu


Quote from: Christopho on April 02, 2017, 09:22:34 PM
Ce paramètre config a été ajouté relativement récemment à mes scripts de HUD pour faciliter l'ajout/suppression/déplacement d'éléments.
Tu dois pouvoir juste passer ce paramètre depuis le script du menu de sauvegardes. Il faut passer un tableau avec les champs x et y correspondant à la position où tu veux afficher les cœurs. Cette position était auparavant indiquée en appelant la fonction set_dst_position().

(Mais ton script heart_builder vient d'où ? Parce que normalement, je croyais avoir justement fait en sorte que le config soit optionnel pour que ça continue à marcher avec l'ancienne façon)

Ce script provient directement de tes tutos les plus récents et n'a pas été modifié.
Je n'ai pas tenté l'ajout d'un tableau dans ce script car honnêtement, je ne sais pas comment configurer ça du a mon manque d'expérience :/.


Ligne 39 de savegames.lua, remplace
Code (lua) Select

hearts_builder:new(game)

par
Code (lua) Select

hearts_builder:new(game, { x = 0, y = 0 })

Fait, maintenant c'est au tour de "game" de poser problème.

Code (lua) Select

Error: In on_started: [string "scripts/hud/hearts.lua"]:14: attempt to index local 'game' (a nil value)

Comme l'indique le message d'erreur, vérifie que le game que tu passes n'est pas nil.

J'ai changé la valeur par "file_name" voir si ça changé quelque chose. Mais il semblerai que le paramètre game pose souci dans le script "hearts" d'après le message d'erreur. Désolé mais je doit vraiment être bête  ;D