Issue with on_position_changed and set_direction

Started by MetalZelda, November 02, 2015, 08:39:37 PM

Previous topic - Next topic
November 02, 2015, 08:39:37 PM Last Edit: November 02, 2015, 09:03:50 PM by Username
Hello

I was creating some items when I faced something ... Strange

This is the code that is running to keep the hero direction

Code (lua) Select

function hero:on_position_changed()
hero:set_direction(direction)
end


Pretty basic but that work

The problem is, and that I can't figure out what does this. There is a frame reckeck when I press another direction, making some strange feedbacks, and if I press the item button to trigger it multiple time, Link can change direction, even if he is forced to keep the last memorized one because of this frame recheck

This code seems useless. (And the variable direction is probably nil, because you have not defined it. Also, you don't say in which script you have written it.) You should explain what is what you want to do.
"If you make people think they're thinking, they'll love you. But if you really make them think, they'll hate you."

November 02, 2015, 09:21:41 PM #2 Last Edit: November 02, 2015, 09:23:38 PM by Username
Quote from: Diarandor on November 02, 2015, 09:05:09 PM
This code seems useless. (And the variable direction is probably nil, because you have not defined it. Also, you don't say in which script you have written it.) You should explain what is what you want to do.

It works, the direction value is stored above in a locale variable (local direction = hero:get_direction()) and it keep the direction, there is just this strange thing, if I keep spamming the item button link can change direction while he's not allowed, if I stop spamming, the direction is fix again with another unwanted direction.
May I post video evidence ? it's kinda hard to describe the problem

The event on_position_changed is called every time the position of the hero has changed, so you are changing the direction each time the hero moves. Why? What are you trying to do?
"If you make people think they're thinking, they'll love you. But if you really make them think, they'll hate you."

Quote from: Diarandor on November 02, 2015, 09:40:50 PM
The event on_position_changed is called every time the position of the hero has changed, so you are changing the direction each time the hero moves. Why? What are you trying to do?

I'm trying to replicate RPG Maker's Direction Fix function especially for some items and functions, it keep the hero "saved" direction and apply this saved direction even if he didn't go to this direction


Ok, I understand now what you are doing. Very interesting to make the hero walk sideways. I don't know what is producing the problem without knowing the rest of the code of the script. This could also be a problem of the engine reseting the direction of the hero.
"If you make people think they're thinking, they'll love you. But if you really make them think, they'll hate you."

November 02, 2015, 10:54:33 PM #7 Last Edit: November 02, 2015, 10:59:25 PM by Username
Quote from: Diarandor on November 02, 2015, 10:37:28 PM
Ok, I understand now what you are doing. Very interesting to make the hero walk sideways. I don't know what is producing the problem without knowing the rest of the code of the script. This could also be a problem of the engine reseting the direction of the hero.

the code is kinda messy but here you go

the bow item script
Code (lua) Select

function item:on_finished() -- we are destroying the item, reset the hero
local map = game:get_map()
local hero = map:get_hero()
  game:set_ability("tunic", game:get_value("item_saved_tunic"))
  game:set_ability("sword", game:get_value("item_saved_sword"))
  game:set_ability("shield", game:get_value("item_saved_shield"))
  game:set_command_keyboard_binding("action", game:get_value("item_saved_action"))
  game:set_value("bow_state", 0)
  hero:set_walking_speed(88)
  game:set_pause_allowed(true)
  self:set_finished()
end

local function store_equipment()
    local tunic = game:get_ability("tunic")
    game:set_ability("tunic", 1)
    local sword = game:get_ability("sword")
    game:set_ability("sword", 0)
    local shield = game:get_ability("shield")
    game:set_ability("shield", 0)
local kb_action_key = game:get_command_keyboard_binding("action")
game:set_command_keyboard_binding("action", nil)
    game:set_value("item_saved_tunic", tunic)
    game:set_value("item_saved_sword", sword)
    game:set_value("item_saved_shield", shield)
game:set_value("item_saved_action", kb_action_key)
end

-- now, we have pressed it's input, tell the game what to do

function item:on_using()
  local map = game:get_map()
  local hero = map:get_hero()
  local direction_fix = game:get_value("direction_fix") -- first trying to fix the recheck issue then extend the on_position_changed with this condition
  local direction = hero:get_direction()
  local is_cutscene = game:get_value("is_cutscene") -- check if we are in a cutscene, for some special stuffs
 
function hero:on_position_changed()
hero:set_direction(direction)
end

function hero:on_obstacle_reached()
  hero:set_direction(direction)
end

  local bow_state = game:get_value("bow_state")
  local can_fire = game:get_value("can_shoot")
   
function hero:on_position_changed()
hero:set_direction(direction)
end

  if bow_state == 0 then
    game:set_value("item_using", true) -- return if we're using the item
hero:freeze()
game:set_value("direction_fix", true)
store_equipment()
sol.audio.play_sound("common/bars_dungeon")
sol.audio.play_sound("common/item_show")
hero:set_tunic_sprite_id("hero/item/bow/bow_shoot_tunic1")
      sol.timer.start(100, function()
    hero:set_walking_speed(40)
hero:unfreeze()
game:set_value("bow_state", 1)
game:set_pause_allowed(false)
hero:set_tunic_sprite_id("hero/item/bow/bow_moving_free_tunic1")
  end)
  elseif bow_state == 1 then
  if key == "c" and bow_state == 1 and can_fire == false and not game:is_suspended() and not is_cutscene then
    hero:freeze()
sol.audio.play_sound("common/item_show")
hero:set_tunic_sprite_id("hero/item/bow/bow_shoot_tunic1")
      sol.timer.start(100, function()
    hero:set_walking_speed(88)
hero:set_tunic_sprite_id("hero/tunic" .. game:get_value("item_saved_tunic"))
        game:set_ability("tunic", game:get_value("item_saved_tunic"))
        game:set_ability("sword", game:get_value("item_saved_sword"))
        game:set_ability("shield", game:get_value("item_saved_shield"))
game:set_command_keyboard_binding("action", game:get_value("item_saved_action"))
game:set_value("bow_state", 0)
hero:unfreeze()
game:get_item("bow"):set_finished()
  end)
end
end

function shoot_arrow()
      sol.audio.play_sound("/items/bow/shoot")
      self:remove_amount(1)
      local x, y = hero:get_center_position()
      local _, _, layer = hero:get_position()
      local arrow = map:create_custom_entity({
        x = x,
        y = y,
        layer = layer,
        direction = hero:get_direction(),
        model = "arrow",
      })
      arrow:set_force(self:get_force())
      arrow:set_sprite_id(self:get_arrow_sprite_id())
      arrow:go()
end

end


And the parallel script (for input update : item_manager) : check input state when using items by reading it's state, called when a item have a value > 0

code cleaned for the bow, but the same issue happend with the hookshot

Code (lua) Select


function game:on_key_pressed(key)
local map = game:get_map()
local hero = map:get_hero()
-- get the item state
-- can_fire is used by the bow, hookshot and boomerang
local can_fire = game:get_value("can_shoot")
local direction = hero:get_direction()
local direction_fix = game:get_value("direction_fix")
local is_cutscene = game:get_value("is_cutscene")

local bow_state = game:get_value("bow_state")

-- Bow and Arrow
if key == "c" and bow_state == 1 and can_fire == false and not game:is_suspended() and not is_cutscene then -- this one don't need key_press, it just check if the sword button is pressed and then end the bow
    hero:freeze()
sol.audio.play_sound("common/item_show")
hero:set_tunic_sprite_id("hero/item/bow/bow_shoot_tunic1")
      sol.timer.start(100, function()
    hero:set_walking_speed(88)
hero:set_tunic_sprite_id("hero/tunic" .. game:get_value("item_saved_tunic"))
        game:set_ability("tunic", game:get_value("item_saved_tunic"))
        game:set_ability("sword", game:get_value("item_saved_sword"))
        game:set_ability("shield", game:get_value("item_saved_shield"))
game:set_command_keyboard_binding("action", game:get_value("item_saved_action"))
game:set_value("bow_state", 0)
hero:unfreeze()
game:set_pause_allowed(true)
game:get_item("bow"):set_finished()
  end)
 
elseif key == item_assign_key and game:get_item("bow"):get_amount() == 0 and bow_state == 1 and not game:is_suspended() and not is_cutscene then -- Link don't have arrow
    game:set_pause_allowed(false)
    hero:set_tunic_sprite_id("hero/item/bow/bow_arming_no_arrow_tunic1")
  sol.timer.start(50, function()
        sol.audio.play_sound("/items/bow/arming")
    hero:set_tunic_sprite_id("hero/item/bow/bow_moving_no_arrow_tunic1")
    hero:unfreeze()
    game:set_value("can_shoot", true)
    hero:set_walking_speed(28)
  end)
elseif key == item_assign_key and game:get_item("bow"):get_amount() > 0 and bow_state == 1 and not game:is_suspended() and not is_cutscene then -- Link have arrows
    game:set_pause_allowed(false)
hero:set_tunic_sprite_id("hero/item/bow/bow_arming_arrow_tunic1")
  sol.timer.start(50, function()
sol.audio.play_sound("/items/bow/arming")
hero:set_tunic_sprite_id("hero/item/bow/bow_moving_with_arrow_tunic1")
hero:unfreeze()
game:set_value("can_shoot", true)
hero:set_walking_speed(28)
  end)
 
end


function game:on_key_released(key)
local map = game:get_map()
local hero = map:get_hero()
local can_fire = game:get_value("can_shoot")
local direction = hero:get_direction()
local direction_fix = game:get_value("direction_fix")
local is_cutscene = game:get_value("is_cutscene")

local bow_state = game:get_value("bow_state")

-- Bow and Arrows
if key == "x" and bow_state == 1 and game:get_item("bow"):get_amount() == 0 and can_fire == true and not game:is_suspended() and not is_cutscene then
hero:set_tunic_sprite_id("hero/item/bow/bow_shoot_tunic1")
sol.audio.play_sound("/items/bow/no_arrows_shoot")
hero:freeze()
-- can't shoot arrows, but reset to state 1
sol.timer.start(100, function()
    game:set_value("bow_state", 1)
game:set_value("can_shoot", false)
hero:set_walking_speed(40)
hero:set_tunic_sprite_id("hero/item/bow/bow_moving_free_tunic1")
hero:unfreeze()
end)
elseif key == "x" and bow_state == 1 and game:get_item("bow"):get_amount() > 0 and can_fire == true and not game:is_suspended() and not is_cutscene then
hero:set_tunic_sprite_id("hero/item/bow/bow_shoot_tunic1")
shoot_arrow()
hero:freeze()
sol.timer.start(100, function()
game:set_value("bow_state", 1)
game:set_value("can_shoot", false)
hero:unfreeze()
hero:set_walking_speed(40)
hero:set_tunic_sprite_id("hero/item/bow/bow_moving_free_tunic1")
end)

end
end


This is strange, the sword charging doesn't do this, so this is not engine-related i though

November 03, 2015, 01:45:50 AM #8 Last Edit: November 03, 2015, 01:51:09 AM by Diarandor
I agree, it is very strange what happens. I have a conjecture of what could be the problem, but I am not sure at all.

When you change direction very quickly while moving the hero, maybe the direction of the hero is changing before changing position sometimes, so the direction is updated a bit late, which could be the cause of the problem, but I am not sure of this. I would try to define the event:

function hero:on_movement _changed()
  hero:set_direction(direction)
end

If we are lucky this code might solve the problem...

Otherwise, I don't have any idea of how to solve it or what is producing the problem.

Edit: if we had an event hero:on_direction_changed(), we could use it to change the direction of the hero in that event, which would be the best solution. But there is no such event I think. Also, it is not possible yet to get the hero tunic sprite to define its event on_direction_changed, but it will probably be possible in a future version of solarus, which would allow to solve the problem too.
"If you make people think they're thinking, they'll love you. But if you really make them think, they'll hate you."

November 03, 2015, 11:33:06 AM #9 Last Edit: November 03, 2015, 11:49:05 AM by Username
Quote from: Diarandor on November 03, 2015, 01:45:50 AM
I agree, it is very strange what happens. I have a conjecture of what could be the problem, but I am not sure at all.

When you change direction very quickly while moving the hero, maybe the direction of the hero is changing before changing position sometimes, so the direction is updated a bit late, which could be the cause of the problem, but I am not sure of this. I would try to define the event:

function hero:on_movement _changed()
  hero:set_direction(direction)
end

If we are lucky this code might solve the problem...

Otherwise, I don't have any idea of how to solve it or what is producing the problem.

Edit: if we had an event hero:on_direction_changed(), we could use it to change the direction of the hero in that event, which would be the best solution. But there is no such event I think. Also, it is not possible yet to get the hero tunic sprite to define its event on_direction_changed, but it will probably be possible in a future version of solarus, which would allow to solve the problem too.

My theory about this is that on_using need a frame to be triggered by the engine, and since the movement update recall on_using each time an input has changed this might be that, but I don't know how I might fix this.
I don't know much.

Putting on_position and on_obstable in item manager didn't solve this

hero:on_movement_changed() doesn't do a thing, even if I hero is declared as map:get_entity()

This is an interesting use case.
You are right, there is no good way to do it without direct access to the hero's sprites. There is an event sprite:on_direction_changed() that would solve the problem.
Sprites of the hero will be accessible in a future release (this is planned for 1.5): https://github.com/christopho/solarus/issues/669

Now... There might actually be a hacky workaround to access hero sprites in 1.4, using metatables. This is advanced stuff :)
The idea is to define the on_direction_changed() event on all sprites, and when this is the sprite of the hero, change its direction.
Something like:
Code (lua) Select

local sprite_meta = sol.main.get_metatable("sprite")
function sprite_meta:on_direction_changed()
  if self:get_animation_set() == "hero/tunic1" then
    local game = sol.main.game
    if game ~= nil then
      local hero = game:get_hero()
      if hero ~= nil and hero.fixed_direction ~= nil and self:get_direction() ~= hero.fixed_direction then
        self:set_direction(hero.fixed_direction)
      end
    end
  end
end

Not tested... Metatables are something global to all instances, so this code should be done at global initialization time, outside of any game or hero (in my games, it goes in scripts/quest_manager.lua). So I am assuming that the current game, if any, is stored globally in sol.main.game. I am also assuming that the direction you want to fix is stored in hero.fixed_direction. You get the idea :) It should work while waiting for 1.5.

Thanks Christopho! It's good news to know that we will have access to the sprite of the hero in the next release. Also this topic solves the problem of scripting the walking sideways feature, which will be like a breath of fresh air in the new Solarus games.

I'd like to point out (not important, just for novice programmers to know) that if we change the direction inside the event on_direction_changed, it is necessary (as Christopho did in his code above) to add the condition

if hero:get_direction() ~= hero.fixed_direction then

to change the direction only when necessary, because otherwise we would start an infinite/recursive loop that might cause a crash (because changing the direction would start again the event on_direction_changed, which changes the direction, so the event would be called again and again, probably).
"If you make people think they're thinking, they'll love you. But if you really make them think, they'll hate you."

Very good remark, yes. I confirm that there would be an infinite recursion without the check.

Quote from: Christopho on November 03, 2015, 12:39:06 PM
This is an interesting use case.
You are right, there is no good way to do it without direct access to the hero's sprites. There is an event sprite:on_direction_changed() that would solve the problem.
Sprites of the hero will be accessible in a future release (this is planned for 1.5): https://github.com/christopho/solarus/issues/669

Now... There might actually be a hacky workaround to access hero sprites in 1.4, using metatables. This is advanced stuff :)
The idea is to define the on_direction_changed() event on all sprites, and when this is the sprite of the hero, change its direction.
Something like:
Code (lua) Select

local sprite_meta = sol.main.get_metatable("sprite")
function sprite_meta:on_direction_changed()
  if self:get_animation_set() == "hero/tunic1" then
    local game = sol.main.game
    if game ~= nil then
      local hero = game:get_hero()
      if hero ~= nil and hero.fixed_direction ~= nil and self:get_direction() ~= hero.fixed_direction then
        self:set_direction(hero.fixed_direction)
      end
    end
  end
end

Not tested... Metatables are something global to all instances, so this code should be done at global initialization time, outside of any game or hero (in my games, it goes in scripts/quest_manager.lua). So I am assuming that the current game, if any, is stored globally in sol.main.game. I am also assuming that the direction you want to fix is stored in hero.fixed_direction. You get the idea :) It should work while waiting for 1.5.

I'd test your solution when i'll be at home, so you recommand to programm the function in quest_manager ?

You can put it in your quest_manager.lua, or main.lua, or game_manager.lua file if you prefer, because they are good places to put that code. But the main point of doing so is that you need to define the function on_direction_changed only once. If you define it inside your item script in the event item:on_using(), that function would be defined each time you use the item, which is unnecessary. Anyway, you can still put it in the item script where it is called once, but it is not the best place to put that code (if the item is created several times from the engine part, you would still be defining that function several times, unnecessarily).
"If you make people think they're thinking, they'll love you. But if you really make them think, they'll hate you."