[SOLVED]Saving state for a single Custom Entity possible ?

Started by MetalZelda, October 16, 2015, 03:48:13 PM

Previous topic - Next topic
Hi
I was wondering if it was possible to save a single custom entity state on the map when there are copies of the same entity  ?

This is my alternate chest script (issue bellow)

Code (lua) Select
local entity = ...
local game = entity:get_game()
local map = entity:get_game():get_map()
local hero = entity:get_map():get_entity("hero")

-- Chest example : Small Key (code from : Big chest example : Master Key)

-- Hud notification : check hero & entity direction and tell by the way of a hud info if it can be opened
entity:add_collision_test("touching", function()
if not open and hero:get_direction() == entity:get_direction() then
game:set_custom_command_effect("action", "open")
else
game:set_custom_command_effect("action", nil)
end
end)

function entity:on_created()
  self:set_drawn_in_y_order(true)
  self:set_can_traverse("hero", false)
  self:set_traversable_by("hero", false)

-- check if open
if open then
self:get_sprite():set_animation("open")
end

end

function entity:on_interaction()

local x,y = entity:get_position()
local hero = entity:get_map():get_entity("hero")

if not open then
open = false
end

  if hero:get_direction() == entity:get_direction() and not open then
       if entity:get_direction() == 0 then --right
           hero:set_position(x-16, y)
       elseif entity:get_direction() == 1 then --up
           hero:set_position(x, y+16)
       elseif entity:get_direction() == 2 then --left
           hero:set_position(x+16, y)
       elseif entity:get_direction() == 3 then --down
           hero:set_position(x, y-16)
       end

    hero:freeze()
    game:set_pause_allowed(false)

    sol.timer.start(1,function()
            hero:set_animation("drop")
    end)

    sol.timer.start(200,function()
           if hero:get_direction() == 3 or hero:get_direction() == 1 then
            hero:set_animation("stopped")
           else
            hero:set_animation("grabbing")
           end
    end)

    sol.timer.start(300,function()
           if hero:get_direction() == 0 or hero:get_direction() == 2 then
            hero:set_animation("stopped")
           end
    self:get_sprite():set_animation("open")
    sol.audio.play_sound("/common/chest_open")
    end)
     
    sol.timer.start(600,function()
    hero:set_animation("stopped")
    if hero:get_direction() == entity:get_direction() then
       if entity:get_direction() == 0 then --right
           hero:set_direction(3)
       elseif entity:get_direction() == 1 then --up
           hero:set_direction(2)
       elseif entity:get_direction() == 2 then --left
           hero:set_direction(3)
       end
      end
     end)

    sol.timer.start(750,function()
    hero:set_animation("chest_holding_before_brandish")
    end)

    sol.timer.start(1500, function()
    hero:unfreeze()
    hero:start_treasure("small_key")
    hero:set_animation("brandish_alternate") -- overwrite the default brandish animation for this particular item
    game:set_pause_allowed(true)
    hero:set_direction(entity:get_direction())
    open = true
    end)

    elseif not open then
      game:start_dialog("gameplay.cannot_open_chest_side")
end
end


It works fine when loaded in-game (though it can be optimized), the chest, animation, interraction and treasure works fine, same for the logic (if it is open then you can't re-open it), the issue is, if I place some copies of the same entity on the same map / another map, it acts as it was opened because it re-use the same way than the 1st chest do. And I want this script to be  copied, used and functionnal on many dungeon maps without recreating a code for each chest

I don't have many idea so I don't really know if that would work and I don't really know how to render this possible, i'm stuck with this issue, if someone have any idea, I'm on.

You never declared open as a local variable to your custom entity script, so it is a global value.

Quote from: Christopho on October 16, 2015, 04:04:09 PM
You never declared open as a local variable to your custom entity script, so it is a global value.

I do already tried to declare open as a local variable, but this time the chest resets even if it was open , I also trien to add a function to store the chest state on on_map_change, same thing occurs.

You mean it resets when you leave the map and come back?
You should save its state in the savegame then. You need a unique name of savegame variable for each chest: a way to obtain one is to concatenate the map id and the name of the chest.

Quote from: Christopho on October 16, 2015, 04:21:21 PM
You mean it resets when you leave the map and come back?
You should save its state in the savegame then. You need a unique name of savegame variable for each chest: a way to obtain one is to concatenate the map id and the name of the chest.

Yeah exactly it resets ^^

Hmmm, seems a bit complicated, is there any dedicated tutorial or such ?


Quote from: Christopho on October 16, 2015, 04:48:34 PM
It is easy: game:set_value("my_variable", value)
See http://www.solarus-games.org/doc/latest/lua_api_game.html

Oh yeah I completly forgot this one, shame on me, I'm gonna try with map:get_entity_name(entity) and map:get_id() as value

October 16, 2015, 08:41:19 PM #7 Last Edit: October 16, 2015, 09:03:18 PM by Username
Struggling so hard to get these to work, for the value, is there a way to retrieve the entity name info from the map ? entity:get_name(match("^small_key_chest_([1-9])$")) doesn't work and map:get_entity() too

edit :

entity:get_name() as value work but all chests do have the same state, thats why I ask if it is possible to get the info straight from the entity's name on the map

October 16, 2015, 09:29:27 PM #8 Last Edit: October 16, 2015, 09:38:00 PM by Diarandor
You can put a unique name for each entity in the map editor, so that each savegame variable would be different and that would solve your problem (but this would be a lot of work). The best solution is to include the (x,y) coordinates of the chest as part of the asociated savegame variable. You can concatenate something like:

local chest_savegame_variable = "chest_" .. map_name .. "_" .. x_coordinate .. "_" .. y_coordinate

This is also useful when you open the savegame file (which has the saved values for each savegame variable), because you can know which is the chest corresponding to each variable.

Edit: to get the map name and the entity position you can use:

local map_name = entity:get_map()
local x,y,layer = entity:get_position()
"If you make people think they're thinking, they'll love you. But if you really make them think, they'll hate you."

October 16, 2015, 09:41:50 PM #9 Last Edit: October 16, 2015, 09:47:29 PM by Username
Quote from: Diarandor on October 16, 2015, 09:29:27 PM
You can put a unique name for each entity in the map editor, so that each savegame variable would be different and that would solve your problem (but this would be a lot of work). The best solution is to include the (x,y) coordinates of the chest as part of the asociated savegame variable. You can concatenate something like:

local chest_savegame_variable = "chest_" .. map_name .. "_" .. x_coordinate .. "_" .. y_coordinate

This is also useful when you open the savegame file (which has the saved values for each savegame variable), because you can know which is the chest corresponding to each variable.

Edit: to get the map name and the entity position you can use:

local map_name = entity:get_map()
local x,y,layer = entity:get_position()


hmmm, strangely I got a "attempt to concatenate local 'map_name' (a user value)"

I don't know what is producing that error, but maybe you did something wrong with the map_name variable. Could you post the new version of your script?
Anyway, I realized that you already had the name of the map stored in the variable "map", defined at the begining of the script.
"If you make people think they're thinking, they'll love you. But if you really make them think, they'll hate you."

October 16, 2015, 11:20:26 PM #11 Last Edit: October 16, 2015, 11:22:49 PM by Username
Quote from: Diarandor on October 16, 2015, 11:03:02 PM
I don't know what is producing that error, but maybe you did something wrong with the map_name variable. Could you post the new version of your script?
Anyway, I realized that you already had the name of the map stored in the variable "map", defined at the begining of the script.

this is the "new" script, the error occurs at line 5 (local chest_savegame_variable)

Code (lua) Select
local entity = ...
local game = entity:get_game()
local map = entity:get_map()
local x_coordinate, y_coordinate, layer = entity:get_position()
local chest_savegame_variable = "chest_" .. map .. "_" .. x_coordinate .. "_" .. y_coordinate
local hero = entity:get_map():get_entity("hero")
local sk_chest = game:get_value("small_key", chest_savegame_variable)

-- Hud notification
entity:add_collision_test("touching", function()
   if sk_chest == nil and hero:get_direction() == entity:get_direction() then
    game:set_custom_command_effect("action", "open")
   else
    game:set_custom_command_effect("action", nil)
   end
end)

function entity:on_created()
  self:set_drawn_in_y_order(true)
  self:set_can_traverse("hero", false)
  self:set_traversable_by("hero", false)
   if sk_chest then
    self:get_sprite():set_animation("open")
   end
end

function entity:on_interaction()

local volume = sol.audio.get_music_volume() -- used later
local x,y = entity:get_position()
local hero = entity:get_map():get_entity("hero")

  if hero:get_direction() == entity:get_direction() and sk_chest == nil then
       if entity:get_direction() == 0 then --right
           hero:set_position(x-16, y)
       elseif entity:get_direction() == 1 then --up
           hero:set_position(x, y+16)
       elseif entity:get_direction() == 2 then --left
           hero:set_position(x+16, y)
       elseif entity:get_direction() == 3 then --down
           hero:set_position(x, y-16)
       end

hero:freeze()
game:set_pause_allowed(false)

    sol.timer.start(1,function()
            hero:set_animation("drop")
    end)

    sol.timer.start(200,function()
           if hero:get_direction() == 3 or hero:get_direction() == 1 then
            hero:set_animation("stopped")
           else
            hero:set_animation("grabbing")
           end
    end)

    sol.timer.start(300,function()
           if hero:get_direction() == 0 or hero:get_direction() == 2 then
            hero:set_animation("stopped")
           end
      self:get_sprite():set_animation("open")
      sol.audio.play_sound("/common/chest_open")
    end)
     
    sol.timer.start(600,function()
    hero:set_animation("stopped")
    if hero:get_direction() == entity:get_direction() then
       if entity:get_direction() == 0 then --right
           hero:set_direction(3)
       elseif entity:get_direction() == 1 then --up
           hero:set_direction(2)
       elseif entity:get_direction() == 2 then --left
           hero:set_direction(3)
       end
      end
     end)

    sol.timer.start(750,function()
    hero:set_animation("chest_holding_before_brandish")
    end)

    sol.timer.start(1500, function()
      hero:unfreeze()
      hero:start_treasure("small_key")
      hero:set_animation("brandish_alternate")
      game:set_pause_allowed(true) -- restore pause allowed
      hero:set_direction(entity:get_direction()) -- restore direction
      game:set_value("small_key", chest_savegame_variable)
    end)


    elseif sk_chest == nil then
      game:start_dialog("gameplay.cannot_open_chest_side")
end
end




October 16, 2015, 11:52:10 PM #12 Last Edit: October 16, 2015, 11:54:52 PM by Diarandor
Ok, I was wrong. The code to get the map name is actually "entity:get_map():get_id()". That will solve the problem. Sorry for my mistake.

Now you can use something like:

local chest_savegame_variable = "chest_".. map:get_id() .."_".. x_coordinate .."_".. y_coordinate

"If you make people think they're thinking, they'll love you. But if you really make them think, they'll hate you."

October 17, 2015, 12:10:16 AM #13 Last Edit: October 17, 2015, 12:21:34 AM by Username
Quote from: Diarandor on October 16, 2015, 11:52:10 PM
Ok, I was wrong. The code to get the map name is actually "entity:get_map():get_id()". That will solve the problem. Sorry for my mistake.

Now you can use something like:

local chest_savegame_variable = "chest_".. map:get_id() .."_".. x_coordinate .."_".. y_coordinate


The same issue occurs, if I open 1 chest, the others are stated as "open" too while they shouldn't

Here is the issue documented in video :

https://www.youtube.com/watch?v=dNfP2sjp_bI&feature=youtu.be

October 17, 2015, 12:23:15 AM #14 Last Edit: October 17, 2015, 12:29:41 AM by Diarandor
There are still some problems in your code.

First, you wrote "game:get_value("small_key", chest_savegame_variable)". But, the function "get_value" has only 1 parameter, so you should have something like :
"local open = game:get_value(chest_savegame_variable)".

Also, you wrote "game:set_value("small_key", chest_savegame_variable)", which is not what you should write. The first parameter must be the variable name, which in this case is the string stored in "chest_savegame_variable". The second variable should be the local variable "sk_chest" (I recommend you to set it as the boolean true when the chest has been opened). You will have something like:
"game:set_value(chest_savegame_variable, sk_chest)"
(I would use "open" or "is_open" for the name of the local variable where you store the boolean, instead of "sk_chest".)

With all of these changes you should be able to repair the script.
"If you make people think they're thinking, they'll love you. But if you really make them think, they'll hate you."