Freeze when using movement:set_path()

Started by MetalZelda, January 20, 2017, 02:28:23 PM

Previous topic - Next topic
Hello

I was modifying a platform script (from Diarandor I guess), which was a simple platform script, and I decided to modify it so it allow custom movement, path movement, and in the future circular movement

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

-- Platform
-- changes:

-- size              -> Entity size
-- time_stopped      -> self.time_stopped
-- speed             -> self.speed
-- directionnal path -> self.path

--todo
--entity.movement_type (path / circular / straight / target)

-- And anything related to these movement

function entity:on_created()
  local size_x, size_y = self:get_size()
  self:set_size(size_x, size_y)
  self:set_origin(size_x / 2, size_y / 2)
  self:set_can_traverse("jumper", true)
  self:set_can_traverse_ground("hole", true)
  self:set_can_traverse_ground("deep_water", true)
  self:set_can_traverse_ground("lava", true)
  self:set_can_traverse_ground("traversable", true)
  self:set_can_traverse_ground("shallow_water", false)
  self:set_can_traverse_ground("wall", false)
  self:set_modified_ground("traversable")
  self:set_layer_independent_collisions(false)

  self:start_movement()
end

function entity:start_movement()
  local m = sol.movement.create("path")
  m:set_path(self.path)
  m:set_speed(self.speed)
  m:set_loop(true)
  m:start(self)
 
  self:add_collision_test("touching", function(_, other)
    if other:get_type() == "wall" or (other:get_type() == "custom_entity" and other:get_model() == "object/platform/limit") then
      self:on_obstacle_reached(m)
    end
  end)
end

function entity:on_obstacle_reached(movement)
  -- Reverse the movement, reverse the table
  for i = 1, math.floor(#self.path / 2) do
    local tmp = self.path[i]
    self.path[i] = self.path[#tbl - i + 1]
    self.path[#self.path - i + 1] = tmp
  end

  --Make the platform turn back.
  movement:stop()
  movement = sol.movement.create("path")   
  movement:set_path(self.path)
  movement:set_speed(self.speed)
  movement:set_loop(true)

  sol.timer.start(self, self.time_stopped, function()
    movement:start(self)
  end)
end

function entity:on_position_changed()
  -- Moves the hero if located over the platform.
  if not self:is_on_platform(hero) then return end
    local hx, hy, hl = hero:get_position()
    local direction4 = self:get_direction()
    local dx, dy = 0, 0 --Variables for the translation.
    if direction4 == 0 then dx = 1
    elseif direction4 == 1 then dy = -1
    elseif direction4 == 2 then dx = -1
    elseif direction4 == 3 then dy = 1
    end
    if not hero:test_obstacles(dx, dy, hl) then hero:set_position(hx + dx, hy + dy, hl) end
end

function entity:on_movement_changed(movement)
  --Change direction of the sprite when the movement changes.
  local direction4 = movement:get_direction4()
  self:set_direction(direction4)
end

function entity:is_on_platform(other_entity)
  --Returns true if other_entity is on the platform.
  local ox, oy, ol = other_entity:get_position()
  local ex, ey, el = self:get_position()
  if ol ~= el then return false end
  local sx, sy = self:get_size()
  if math.abs(ox - ex) < sx/2 -1 and math.abs(oy - ey) < sy/2 -1 then return true end
  return false
end


Everything works fine, the entity's movement, speed and delay is now customizable from the map script

However

This is the script call in the map script, the platform is respectively named "platform0" and tried to test a simple squarishpath

Code (lua) Select
local platform0 = map:get_entity("platform0")
platform0.path = {0, 0, 0, 0, 6, 6, 6, 6, 4, 4, 4, 4, 2, 2, 2, 2}
platform0.speed = 60
platform0.time_stopped = 1000


Running the game, no errors whatsoever, but as soon as the map is loaded, the game freeze, the only way to quit the game is by force closing it.

What's wrong ?

January 20, 2017, 10:55:03 PM #1 Last Edit: January 20, 2017, 10:57:18 PM by Diarandor
Hi! I still don't know where is the problem (I haven't tested the script directly), but I will try to help a bit.

When the engine freezes, a possible cause is an infinite loop in the code, but I am not sure yet if that is the case here.

Since there is an obvious syntax error in line 52 ("tbl" variable is not defined, and should be "#self.path"), that means that the problem happens before the event "on_obstacle_reached(movement)" is called, which is probably not important. I suspected that there is a loop that starts with your collision test, which is probably being called many times, but if the event "on_obstacle_reached(movement)" is actually not being called, then the bug has to be a different thing happening before, probably.

Another possible problem is that you are initializing the path list after the entity is created, so maybe there is a problem in line 36: "m:set_path(self.path)". Maybe the path is nil when it is used, I don't know. (And the same for the speed.) If that is the case then this could be an engine bug that should display an error and it does not.

Try to use the "print" function, for debugging, to detect the possible line or function where the problem happens, or at least to get more info. Is the collision test being called?
"If you make people think they're thinking, they'll love you. But if you really make them think, they'll hate you."

January 20, 2017, 11:59:32 PM #2 Last Edit: January 21, 2017, 12:05:11 AM by MetalZelda
I will try something different that could work: create the platform dynamically through something like

map:create_platform(x, y, size_x, size_y, speed, sprite, direction, pause_delay, {path})

it is possible that when the map script register entity.path the entity script might have already been created, and thus, the movement might be started a frame before the entity.path got registered ?

Another thing, I did tried a circular motion styled platform, it works great, but link's position is not synced with the platform, too bad, it looked awesome and it seems that circle_movement:get_angle() doesn't exist so we can't get the current angle and make this type of platform impossible...
the solution might be place an invisible entity at the center for the circle movement and constantly getting the angle (http://www.solarus-games.org/doc/latest/lua_api_entity.html#lua_api_entity_get_angle), what do you think ? Mad scientist experiment or possible ?

January 21, 2017, 12:03:36 AM #3 Last Edit: January 21, 2017, 12:09:08 AM by Diarandor
Quote from: MetalZelda on January 20, 2017, 11:59:32 PM
...
it is possible that when the map script register entity.path the entity script might have already been created, and thus, the movement might be started a frame before the entity.path got registered ?

Yes, that is what I suspect, but I am not sure. I would use a function to initialize the parameters. That would be cleaner and will notify your platform script when it needs to create/initialize the movements.

EDIT: by the way, my script that you used was very old. You should get the "ground position" of the hero instead of the normal position, to move him. And the same for enemies. For other entities that is not so important since they do not fall in bad grounds by default.
"If you make people think they're thinking, they'll love you. But if you really make them think, they'll hate you."

January 21, 2017, 12:12:38 AM #4 Last Edit: January 21, 2017, 12:16:57 AM by Diarandor
For a circular movement I would not use angles, that is a bad approach in the pixel case. I would store the positions of the platform for each position change; in that way you can know in which direction you have to shift the position of the entities above the platform.

EDIT: note that with the trigonometric/polynomial functions (and others) you can make almost any possible custom movement you can imagine, so there should not be many limitations for movements, unless you don't know how to parametrize the trajectories. So if you have some problem due to limitations of the built-in movements, you can always use custom ones.
"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 January 21, 2017, 12:12:38 AM
For a circular movement I would not use angles, that is a bad approach in the pixel case. I would store the positions of the platform for each position change; in that way you can know in which direction you have to shift the position of the entities above the platfom.

I was thinking of using sin and cos at each position check, kinda like how I did the Majora's Mask Hud clock, but well, I'm stuck at this point, there might be some tutorial on the net about 2d circular motion

Quote from: Diarandor on January 21, 2017, 12:03:36 AM
Quote from: MetalZelda on January 20, 2017, 11:59:32 PM
...
it is possible that when the map script register entity.path the entity script might have already been created, and thus, the movement might be started a frame before the entity.path got registered ?

Yes, that is what I suspect, but I am not sure. I would use a function to initialize the parameters. That would be cleaner and will notify your platform script when it needs to create/initialize the movements.

EDIT: by the way, my script that you used was very old. You should get the "ground position" of the hero instead of the normal position, to move him. And the same for enemies. For other entities that is not so important since they do not fall in bad grounds by default.

Getting the ground position ? You mean this ? http://www.solarus-games.org/doc/latest/lua_api_hero.html#lua_api_hero_get_solid_ground_position

Exactly, I was refering to "hero:get_solid_ground_position()", that is the one the engine uses to make the hero fall in bad grounds. Since the usual position is different, it could happen (if you use the normal position in your script) that the hero is moved by the platform and suddenly he falls into some bad ground. (The hero will need to be in the down border of the platform to make this "bug" happen.)

If you need help just ask. I made this video some time ago with ellipses and rotations:
https://www.youtube.com/watch?v=7dw59QQrzL8&list=PLysNP7i5PKBCgT898-xreWyPcaByEB6Rg&index=17

If you can do what you want to do with the built-in circular movement, then you should avoid creating your custom one (that is a lot of work). So make sure that you really need a custom one.
"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 January 21, 2017, 12:28:07 AM
Exactly, I was refering to "hero:get_solid_ground_position()", that is the one the engine uses to make the hero fall in bad grounds. Since the usual position is different, it could happen (if you use the normal position in your script) that the hero is moved by the platform and suddenly he falls into some bad ground. (The hero will need to be in the down border of the platform to make this "bug" happen.)

If you need help just ask. I made this video some time ago with ellipses and rotations:
https://www.youtube.com/watch?v=7dw59QQrzL8&list=PLysNP7i5PKBCgT898-xreWyPcaByEB6Rg&index=17

If you can do what you want to do with the built-in circular movement, then you should avoid creating your custom one (that is a lot of work). So make sure that you really need a custom one.

Ooooh I like this, this is awesome, yet, all I want is a simple circle platform, is it possible with pixel movement type ? (with ease)

Yes, it should be easy (but not short to do) with the built-in circle movement, but I haven't tested it for platforms. Recall to use the event "on_position_changed" to save each new position of the platform, so that you can compare the new and old position of the platform, which will allow you to know in which direction you have to move the movable entities above the platform.
"If you make people think they're thinking, they'll love you. But if you really make them think, they'll hate you."

Ok now the path issue is fixed, I tried to fall, even if the hero ground is constantly saved through on_position_changed, the hero keeps respawning in the hole

That was the hardest problem I found when I coded my rafts, but I solved it somehow. It is annoying. You need to use a callback parameter for the function "hero:save_solid_ground(callback)", so that you can make the hero respawn above the raft. (Thanks to Christopho we can use a callback as parameter in Solarus 1.5!!!) You can do that if some collision test with the platform is called, and then start a timer to check if the hero is still over the platform. When the hero leaves the platform and is over solid ground, then stop the timer loop and remove the callback for saving solid ground position. If you allow the hero to jump from one platform to another, this is slightly more delicate, but possible.
"If you make people think they're thinking, they'll love you. But if you really make them think, they'll hate you."

January 21, 2017, 01:54:44 AM #11 Last Edit: January 21, 2017, 02:15:11 AM by MetalZelda
This seems complicated, the best workaround is to save the hero's ground before he gets in the platform, this looks more efficient.

Also spin attacking on the platform makes somethig weird, the hero is no longer affected by the platform movement and will fall if over holes, this might not cause this on your game because you are using a custom sword

Your workaround is easier (but different too), so yes, better to use it instead. But be careful when restoring ground position if you use several platforms, one after another. Note that your workaround is not good for other aims, like making a raft that changes map with the hero and is not controlled by him.

Yes, the custom sword gives no problem. But maybe the built-in one does not allow to move the hero, which would be bad.
"If you make people think they're thinking, they'll love you. But if you really make them think, they'll hate you."

January 21, 2017, 02:39:17 PM #13 Last Edit: January 21, 2017, 04:07:19 PM by MetalZelda
Is it possible to set a sprite direction higher than 3 ? movement:get_direction4() only returns the direction from 0 to 3, while path movement can go up to 7 and movement:get_angle() or movement:get_direction() doesn't exist

Which is problematic to do diagonal translation

Edit: I found a workaround and it kinda work

local time_changed = -1
function entity:on_movement_changed(movement)
  time_changed = (time_changed + 1) % #self.path

  --Change direction of the sprite when the movement changes.
  local direction = self.path[time_changed]
  self:get_sprite():set_direction(direction)
end


For the circle movement, why not recreating such function in pure lua since get_initial_angle exist and constatly calculate the sin and cos to position the hero on the platform

https://github.com/solarus-games/solarus/blob/dev/src/movements/CircleMovement.cpp#L354

January 21, 2017, 04:13:04 PM #14 Last Edit: January 21, 2017, 04:29:07 PM by Diarandor
Actually there is a function movement:get_angle() for some particular types of movements, like "target" and "circle" movements.

In any case, I still don't understand why you need to use the angle for the platform script. I guess you are programming it in a different way. But in my opinion it would be much better to use the event "on_position_changed" of the platform, as I did in my script. For instance, if the position of the platform before the movement position changed was x0, y0, and after the position changed it becomes x1, y1, then you can use the shifts "dx = x1 - x0" and "dy = y1 - y0" to shift the position of all entities above the platform. This is much more precise than getting an angle, which is approximate and introduce errors.
"If you make people think they're thinking, they'll love you. But if you really make them think, they'll hate you."