Menu

Show posts

This section allows you to view all posts made by this member. Note that you can only see posts made in areas you currently have access to.

Show posts Menu

Messages - Christopho

#1096
I fixed the crash. You are right, there was a crash because the default context was not correctly chosen by the engine.
The default context of a timer is the map if a game is running. and sol.main otherwise.
In game:on_started(), the game is started but not really running yet: the first map is not loaded. The bug was here.
#1097
You're not doing things wrong. There seems to be a bug in the C++ code with timers indeed. I will investigate this today if I find time.

About require vs load_file, here is how to define your function game:on_started from your required file:

-- in the module play :

local init = {}

function init:create(game)   
  hud_initialize(game)   

  function game:on_started()
    -- stuff
  end

end   

return init

-- in main.lua :

init = require("play")
init:create(game)


Remember that a function definition is nothing more than an assignment in Lua. Assigning a value (of type function) to a variable.
You can even write it as an assignment if you find it more clear:


-- in the module play :

local init = {}

function init:create(game)   
  hud_initialize(game)   

  game.on_started = function(game)
    -- stuff
  end

end   

return init

-- in main.lua :

init = require("play")
init:create(game)
#1098
Thanks for the detailed bug report. I will try to reproduce the crash and fix it.

About require not accepting arguments, this is because the file included is executed only once (the first time it is required) and without arguments. Next times, require() does not execute the code again, is only returns whatever the file returned the first time. The usual way is to return a table that contains functions.

If you need arguments (and we often do!), here is the trick: wrap all the content of your file in a big function called for example create() or build(). This big function takes the necessary arguments. And your file returns a table that contains this big function.
This is what I do all the time, like in the rupee counter code I posted in the other topic. Or also here: https://github.com/christopho/zelda_mercuris_chest/blob/master/data/scripts/dialog_box.lua
The sol.main.load_file() calls in ZSDX are only there for historical reasons but I don't like them. I don't like them not only because the same file is parsed and executed several times, but also because sol.main.load_file("rupee_counter")(game) is an ulgy line :)
#1099
3) Good remark. The idea with entities is that they are normally created from the editor with some properties, and then controlled by the map script. From a map script, or from any script, you can always call code from external Lua scripts using require.
However, enemies always need models (the "breed") from separate files, so the engine supports enemy scripts natively.
Similarly, custom entities often need models from separte files, so the engine also supports custom entity scripts natively.

For other entities, like I said you can still can require("some_script") and call a function that initializes your entity like you want. Okay, this is a bit repetitive if you have lots of maps with entities that should have the same behavior. The ultimate solution is metatables (since Solarus 1.2). With metatables, you won't even have to call require() for each map script that contains an entity. For example, you can set a behavior to all sensors whose name has a specific pattern. Give the appropriate name from the editor in a map, and the sensor will have the behavior without touching the map script. See nice examples in the tutorials (chapters 54 and 55) and here: https://github.com/christopho/zelda_mercuris_chest/blob/master/data/scripts/quest_manager.lua
#1100
Quote from: linky on August 04, 2014, 03:24:40 PM
But is the menu itself an object created automaticaly by the engine when you call some of its function (start, stop...) and that includes the table that called the menu or does it just add some functions to the table that has called him ?
A menu is a table created by the user (so, there is no "menu" type). But the engine does not add anything in that table. It just calls functions if they exist in the table. So I understand why it seems confusing. This is very different from the rest of the API because there is no dedicated type. Maybe this is a mistake and maybe it will change one day. A proper type for something displayed on the screen with some size and a position might be more natural.

Anyway, the callbacks (on_draw, on_key_pressed...) are called while the menu is active, that is, after sol.menu.start(your_menu) and before sol.menu.stop(your_menu).

Without the menu API, you can still display things on the screen like a HUD during the game, but the liftetime is more difficult to control. You have to call your_hud_element:on_draw() yourself at each frame (probably from game:on_draw()) and stop doing that when the HUD is disabled. Same thing for on_key_pressed() and its friends, by taking care of the return value. Actually, you would have to forward yourself each callback of game to the HUD elements. This requires a lot of boilerplate code. It would work, but using menus is less painful.

There is also the problem of timers. When you create a timer specific to a menu, for example the title screen that shows some text after 5 seconds, you have to cancel the timer if the title screen is skipped before the delay. The lifetime of a timer can be a menu, and this way you don't have to stop the timer explicitly yourself.
Menus also have a lifetime for the same reason: when you exit the game and go back to the title screen, all menus that belong to the game are stopped. Otherwise they would still be displayed, and worse, they would react to user inputs.
These bugs are a nightmare, I had lots of them. Then I made this menu API.

Also, yes, drawings have to be done again at each frame. The map is redrawn on the screen, so everything that was drawn before is overwritten.
#1101
Hi!
First of all, you have a good understanding of all this, there is no doubt.
I am going to answer in 3 posts for more clarity.

1) You are absolutely right in your analysis. The setmetatable and __index magic is only useful to make several instances of the class and when inheritance is needed. This is the usual idiom to mimic object-oriented programming in Lua, so I did that way at first, and the code is still there in ZSDX for historical reasons. But I find it very hard to understand. When there is only one instance, this is too complex for no reason. Keep it simple! There are at least two other approaches:
- The one you mention: simply do a unique table and it will work with only one instance.
- Another approach to allow multiple instances is what I made in the tutorial:

-- The money counter shown in the game screen.

local rupees_builder = {}

function rupees_builder:new(game)

  local rupees = {}

  function rupees:initialize()

    rupees.surface = sol.surface.create(48, 12)
    rupees.surface:clear()
    rupees.digits_text = sol.text_surface.create({
      font = "white_digits",
    })
    rupees.digits_text:set_text(game:get_money())
    rupees.rupee_icons_img = sol.surface.create("hud/rupee_icon.png")
    rupees.rupee_bag_displayed = game:get_item("rupee_bag"):get_variant()
    rupees.money_displayed = game:get_money()

    rupees:check()
    rupees:rebuild_surface()
  end

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

    local need_rebuild = false
    local rupee_bag = game:get_item("rupee_bag"):get_variant()
    local money = game:get_money()

    -- Max money.
    if rupee_bag ~= rupees.rupee_bag_displayed then
      need_rebuild = true
      rupees.rupee_bag_displayed = rupee_bag
    end

    -- Current money.
    if money ~= rupees.money_displayed then
      need_rebuild = true

      if rupees.money_displayed < money then
        rupees.money_displayed = rupees.money_displayed + 1
      else
        rupees.money_displayed = rupees.money_displayed - 1
      end

      if rupees.money_displayed == money  -- The final value was just reached.
          or rupees.money_displayed % 3 == 0 then  -- Otherwise, play sound "rupee_counter_end" every 3 values.
        sol.audio.play_sound("rupee_counter_end")
      end
    end

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

    -- Schedule the next check.
    sol.timer.start(game, 40, function()
      rupees:check()
    end)
  end

  function rupees:rebuild_surface()

    rupees.surface:clear()

    -- Max money (icon).
    rupees.rupee_icons_img:draw_region((rupees.rupee_bag_displayed - 1) * 12, 0, 12, 12, rupees.surface)

    -- Current rupee (counter).
    -- TODO show in green if the maximum is reached.
    if rupees.money_displayed == game:get_max_money() then
      rupees.digits_text:set_font("green_digits")
    else
      rupees.digits_text:set_font("white_digits")
    end
    rupees.digits_text:set_text(rupees.money_displayed)
    rupees.digits_text:draw(rupees.surface, 16, 5)
  end

  function rupees:set_dst_position(x, y)
    rupees.dst_x = x
    rupees.dst_y = y
  end

  function rupees:on_draw(dst_surface)

    local x, y = rupees.dst_x, rupees.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

    rupees.surface:draw(dst_surface, x, y)
  end

  rupees:initialize()

  return rupees
end

return rupees_builder

This last approach looks much better, I use it everywhere now for the HUD, the pause menus and the title screen. However, note that if you have a lot of instances, each instance duplicates the functions it contains. This is not the best choice for memory. One way to avoid that is to declare the functions outside the enclosing function rupee_builder:new().

Also note that in ZSDX, we do have multiple instances of the hearts counter in the savegame selection menu.
#1102
Your projects / Re: My first video
August 02, 2014, 08:59:24 AM
Great video!
A few remarks:
- Sometimes it is hard to distinguish between walls, holes and the floor.
- Zelda A Link to the Past graphics don't fit so well with your dungeon graphics. Maybe you intend to change them later? Or at least, you could change their colors.
- Your moving platforms seems to work great. Which is quite surprising because I did not really test such platforms. Did you use dynamic tiles? Did you have any problem?

To record the sound, I'm sure this is possible with VLC but I don't know how to do. I use Linux and ffmpeg for my videos.
#1103
Not yet, this is missing. I just opened an issue to implement that for 1.3: https://github.com/christopho/solarus/issues/572

In the meantime, you should be able to implement your own functions:

function hero:my_save_solid_ground(x, y, layer)
  hero:save_solid_ground(x, y, layer)
  hero.solid_ground_position = { x = x, y = y, layer = layer}
end

function hero;my_get_solid_ground_position()
  if hero.solid_ground_position == nil then
    return nil
  end
  return hero.solid_ground_position.x, hero.solid_ground_position.y, hero.solid_ground_position.layer
end
#1104
Development / Re: Notepad++ autocompletion for Solarus
August 02, 2014, 08:15:01 AM
Nice!
I didn't even know there was completion in Notepad++.
Too bad it does not work well with functions in tables and with functions having the same name but in different tables.
Anyway, this looks very promising :)
#1105
Bugs & Feature requests / Re: Little doc error
August 01, 2014, 05:09:19 PM
Fixed in the git version, thanks again.
#1106
The hero is translated to the solid-ground-position.
To teleport him directly, you can override the behavior of the "back to solid ground" state like this (untested):

function hero:on_state_changed(state)
  if state == "back to solid ground" then
    hero:set_position(x, y)
    hero:unfreeze()  -- Stop the "back to solid ground" built-in state, restore control to the player.
  end
end
#1107
Development / Re: Collision detection
July 29, 2014, 06:26:26 PM
Hi,
Thanks for the detailed answer and the screenshot, now I better understand what you are trying to do :)

- For the entrance of cavern: you can easily fix this by changing the tileset. Make proper obstacle tiles for the north part of the entrance, including diagonal walls. I guess you re-used the tileset I made for ZSDX. In ZSDX, the player cannot go to the north of the entrance, so I did not bother making these tiles.

- And for your « ugly » stone: yes, exactly, ugly is the appropriate word hehe :) There are no such irregular tiles in old-school games. Or their obstacles are implemented with regular tiles aligned on an 8x8 grid. Diagonal walls can help you here too. But this is hard if the stone rotates on itself.
When the hero gets stucked into a wall, or is close to get stuck, there is no mechanism to move him back.

If you really want a pixel-precise wall, you should let the stone always traversable, but use add_collision_test() to move the hero back, sending him a few pixels just to be sure.
Here is the idead (untested code):

stone:add_collision_test("sprite", function(entity)
  if not entity:get_type() == "hero" then
    return
  end

  local hero = entity
  local angle = stone:get_angle(hero)  -- angle from the stone to the hero
  local movement = sol.movement.create("straight")
  movement:set_speed(128)
  movement:set_max_distance(16)
  hero:freeze()
  movement:start(hero, function()
    hero:unfreeze()
  end)
end)


- custom_entity:is_traversable() does not exist because I was not sure how to design it. It is either true, false or a function. What should I return if the traversable property is a function and not a boolean? (Now that I think about it, a solution could be to always return a function.)
#1108
Bugs & Feature requests / Re: Little doc error
July 29, 2014, 06:09:43 PM
Fixed, thanks!
#1109
Bugs & Feature requests / Re: strange bug.
July 28, 2014, 10:51:28 AM
According to http://forum.solarus-games.org/index.php/topic,21.msg239.html#msg239, they are working on the ZSDX 1.8 port for GCW-Zero and it runs much faster than before.
This is thanks to SDL2 and LuaJIT.
#1110
Shin-NIL: is everything working? Did you make the updated package? I would like to upload it to the website. Thanks