Custom code debugging

Started by wizard_wizzle (aka ZeldaHistorian), August 25, 2015, 03:52:37 AM

Previous topic - Next topic
Alright - I can't tell if I coded something wrong here, or if there's an error in the engine. I have a cane that generates ice blocks. These blocks can either fill empty pits (another custom entity, which works correctly), freeze water so the hero can walk on it, or enable lava to be walked on. Pushing the block onto water or lava isn't working at all, and the collision detection when creating a new block on top of lava or water is hit or miss. The lava and deep water are both set up as dynamic tiles, and neither set_traversable_by() or set_can_traverse() seem to work for the block. Below is my code, any help would be appreciated :)


local entity = ...
local map = entity:get_map()
local pushing = false
local block_on_switch = false

-- Ice block: special block made of ice that can fill an
-- ice pit, turn lava solid, and freeze water.

function entity:on_created()
  self:set_size(16, 16)
  self:snap_to_grid()
  self:set_modified_ground("ice")
  self:set_traversable_by("hero", false)
  self:set_traversable_by("dynamic_tile", true)
  self:set_traversable_by("custom_entity", true) --to allow pushing block into pit
  self:set_can_traverse("dynamic_tile", true)
  self:create_sprite("entities/ice_block")

  self:add_collision_test("facing", function(self, other)
    if other:get_type() == "hero" and not pushing then
      pushing = true
      local m = sol.movement.create("path")
      m:set_ignore_obstacles(false)
      m:set_snap_to_grid(true)

      if other:get_direction() == 0 then m:set_path({0,0})
      elseif other:get_direction() == 1 then m:set_path({2,2})
      elseif other:get_direction() == 2 then m:set_path({4,4})
      elseif other:get_direction() == 3 then m:set_path({6,6}) end
      m:start(self, function()
pushing = false
      end)
    end
  end)

  self:add_collision_test("overlapping", function(self, other)
    local lava_crust, ice_patch
    if other:get_type() == "dynamic_tile" then
      local tsx, tsy = other:get_size()
      local tpx, tpy, tpl = other:get_position()
      local sx, sy, sl = self:get_position()
      self:clear_collision_tests()
      self:remove()
      if map:get_ground(sx,sy,sl) == "lava" then
        if (sx > tpx-32) and (sx < tpx+tsx+32) and (sy > tpy-32) and (sy < tpy+tsy+32) then
          lava_crust = map:create_custom_entity({ x = sx, y = sy, layer = sl, width = 32, height = 32, direction = 0 })
          lava_crust:snap_to_grid()
          sol.audio.play_sound("freeze")
          lava_crust:create_sprite("entities/lava")
          lava_crust:set_modified_ground("traversable")
          lava_crust:set_traversable_by("hero", true)
          lava_crust:set_traversable_by("enemy", true)
  sol.timer.start(map, 15000, function()
    lava_crust:remove()
  end)
        end
      elseif map:get_ground(sx,sy,sl) == "deep_water" then
        if (sx > tpx-16) and (sx < tpx+tsx+16) and (sy > tpy-16) and (sy < tpy+tsy+16) then
          ice_patch = map:create_custom_entity({ x = sx, y = sy, layer = sl, width = 32, height = 32, direction = 0 })
          sol.audio.play_sound("freeze")
          ice_patch:create_sprite("entities/ice")
          ice_patch:set_modified_ground("ice")
          ice_patch:set_traversable_by("hero", true)
          ice_patch:set_traversable_by("enemy", true)
  sol.timer.start(map, 15000, function()
    ice_patch:remove()
  end)
        end
      end
    elseif other:get_type() == "switch" then
      block_on_switch = true
      other:set_activated(true)
      if other:on_activated() ~= nil and not other.active then
        other:on_activated()
        other.active = true
      end
      sol.timer.start(map, 1000, function()
        if block_on_switch then
          return true
        else
          block_on_switch = false
          other:set_activated(false)
          if other:on_inactivated() ~= nil and other.active then
            other:on_inactivated()
            other.active = false
          end
        end
      end)
    elseif other:get_type() == "hole" then
      sol.audio.play_sound("hero_falls")
      self:remove()
    elseif other:get_type() == "fire" then
      sol.audio.play_sound("ice_melt")
      self:remove()
    elseif other:get_type() == "explosion" then
      sol.audio.play_sound("ice_melt")
      self:remove()
    else
      block_on_switch = false
    end
  end)
end

function entity:on_removed()
  self:get_sprite():set_animation("destroy")
end

I think there is the same problem as in http://forum.solarus-games.org/index.php/topic,371.msg1556.html#msg1556
custom_entity:set_can_traverse() does not work for dynamic tiles, this bug is not fixed yet.

There is also some problems of collisions not detected when entities are fixed. Currently, the engine only checks collisions when entities move or when their sprite change, for performance reason (except the hero who is checked at every frame). This could be fixed in 1.5 thanks to quadtrees, because now we can finally check collisions efficiently.

Yes, I had that problem for custom switches (made with custom entities) not detecting collision tests (when pushed by some entity) if the other entity was created above them, or something like that (I don't remember the details). In the end, I had to use a timer to detect collisions as a workaround.
"If you make people think they're thinking, they'll love you. But if you really make them think, they'll hate you."

Thanks Christopho, I wasn't sure if that bug was fixed yet.

Diarandor, any chance you could help me modify my code with timers to make it work in the mean time as well?

August 25, 2015, 02:23:59 PM #5 Last Edit: August 25, 2015, 02:39:38 PM by Diarandor
@wrightmat:

These seem hard problems. I have never used dynamic tiles, so I'm not sure if this would work. For the collision test when you create the block, I would try to do it in a different way, without the collision test. Try to use a loop on all entities (

for e in map:get_entities("") do
   if e:get_type() == "dynamic_tile" then
     -- Try to detect somehow if the dynamic tile is of lava. (And in that case, do something.)
     -- Also, check if the entities are overlapping with entity:overlaps(other_entity).
  end
end

I don't know if it is possible to detect if a dynamic tile represents water or lava (there is no function to get the info). But still, when you create the dynamic tile dynamically, you can set a boolean property like "entity.is_lava = true" and use it in the loop above to know if the entity is the good one. This might solve one of your problems.

@Christopho:

By the way, there is an error in the Lua API. One of the functions "entity:overlaps(other_entity)"
should appear as "entity:overlaps(x,y,width,height)".
http://www.solarus-games.org/doc/latest/lua_api_entity.html#lua_api_entity_overlaps

EDIT: another idea, for your dynamic tiles would be to set a property in the moment you create them: "dynamic_tile.is_of_type = ..." and save the type of it there (lava, water, ice, etc), and modify it when necessary, so you can get the info you need from there.
"If you make people think they're thinking, they'll love you. But if you really make them think, they'll hate you."

Isn't it possible to get the pattern or other information from a dynamic tile with some function?
"If you make people think they're thinking, they'll love you. But if you really make them think, they'll hate you."

No. I can do that for 1.5: dynamic_tile:get_pattern_id() and dynamic_tile:get_ground().

Yes there is an error in the doc. entity:overlaps(other_entity) and entity:overlaps(x,y,width,height) both exist but there is a confusion in the doc.

A workaround for these dynamic tile problems might be to use a custom entity with set_modified_ground().

I would do as Christopho suggests. Using custom entities instead of dynamic tiles, as a workaround, until the day these bugs are fixed. Custom entities are great ;D
"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 August 25, 2015, 02:44:38 PM
Custom entities are great ;D
Yes, they can supersede most other types of entities :)
They saved me more than once to finish Zelda ROTH in time without changing the Solarus API.

Using custom entities would make level design significantly harder for me since I couldn't place these graphically in the editor, so I likely won't go that route. Checking all entities instead of using a collision test did help the accuracy of the script a bit. I think I'll wait until those engine bugs are fixed and just not allow pushing blocks into those dynamic tiles for now. Thanks to both of you!

Yes, you can put custom entities in the editor with a sprite which is graphically shown in the editor.
"If you make people think they're thinking, they'll love you. But if you really make them think, they'll hate you."

I didn't feel like creating these particular resources as sprites instead of tiles, haha. I got it to work (mostly) by looping through all dynamic tile entities in on_position_changed. I ended up having my block pushing code ignore obstacles to get this working, so now I just need to figure out why my code to check for a wall and not move the block is allowing me to push blocks into walls.


local entity = ...
local map = entity:get_map()
local pushing = false
local block_on_switch = false
local lava_crust, ice_patch

-- Ice block: special block made of ice that can fill an
-- ice pit, turn lava solid, and freeze water.

function entity:on_created()
  self:set_size(16, 16)
  self:snap_to_grid()
  self:set_modified_ground("ice")
  self:set_traversable_by("hero", false)
  self:set_traversable_by("custom_entity", true) --to allow pushing block into pit
  self:create_sprite("entities/ice_block")

  self:add_collision_test("facing", function(self, other)
    if other:get_type() == "hero" and not pushing then
      pushing = true
      local m = sol.movement.create("path")
      m:set_ignore_obstacles(true)
      m:set_snap_to_grid(true)

      local sx, sy, sl = self:get_position()
      if other:get_direction() == 0 then
        if map:get_ground(sx+8,sy,sl) ~= "wall" and map:get_ground(sx+16,sy,sl) ~= "wall" then m:set_path({0,0}) end
      elseif other:get_direction() == 1 then
        if map:get_ground(sx,sy+8,sl) ~= "wall" and map:get_ground(sx,sy+16,sl) ~= "wall" then m:set_path({2,2}) end
      elseif other:get_direction() == 2 then
        if map:get_ground(sx-8,sy,sl) ~= "wall" and map:get_ground(sx-16,sy,sl) ~= "wall" then m:set_path({4,4}) end
      elseif other:get_direction() == 3 then
        if map:get_ground(sx,sy-8,sl) ~= "wall" and map:get_ground(sx,sy-16,sl) ~= "wall" then m:set_path({6,6}) end
      end
      m:start(self, function() pushing = false end)
    end
  end)

  self:add_collision_test("overlapping", function(self, other)
    if other:get_type() == "switch" then
      block_on_switch = true
      other:set_activated(true)
      if other:on_activated() ~= nil and not other.active then
        other:on_activated()
        other.active = true
      end
      sol.timer.start(map, 1000, function()
        if block_on_switch then
          return true
        else
          block_on_switch = false
          other:set_activated(false)
          if other:on_inactivated() ~= nil and other.active then
            other:on_inactivated()
            other.active = false
          end
        end
      end)
    elseif other:get_type() == "hole" then
      sol.audio.play_sound("hero_falls")
      self:remove()
    elseif other:get_type() == "fire" then
      sol.audio.play_sound("ice_melt")
      self:remove()
    elseif other:get_type() == "explosion" then
      sol.audio.play_sound("ice_melt")
      self:remove()
    else
      block_on_switch = false
    end
  end)

  local sx, sy, sl = self:get_position()
  self:on_position_changed(sx, sy, sl)
end

function entity:on_position_changed(x, y, layer)
  for e in map:get_entities("") do
    if e:get_type() == "dynamic_tile" then
      if self:overlaps(e) then --if block overlaps dynamic tile
        if map:get_ground(e:get_position()) == "lava" then
          self:remove()
  if map:get_hero():get_direction() == 0 then
            lava_crust = map:create_custom_entity({ x=x+8, y=y-16, layer=layer, width=32, height=32, direction=0 })
  elseif map:get_hero():get_direction() == 1 then
            lava_crust = map:create_custom_entity({ x=x-16, y=y-48, layer=layer, width=32, height=32, direction=0 })
  elseif map:get_hero():get_direction() == 2 then
            lava_crust = map:create_custom_entity({ x=x-40, y=y-16, layer=layer, width=32, height=32, direction=0 })
  elseif map:get_hero():get_direction() == 3 then
            lava_crust = map:create_custom_entity({ x=x-16, y=y, layer=layer, width=32, height=32, direction=0 })
  end
          lava_crust = map:create_custom_entity({ x = x, y = y, layer = layer, width = 32, height = 32, direction = 0 })
          lava_crust:snap_to_grid()
          sol.audio.play_sound("freeze")
          lava_crust:create_sprite("entities/lava")
          lava_crust:set_modified_ground("traversable")
          lava_crust:set_traversable_by("hero", true)
          lava_crust:set_traversable_by("enemy", true)
          lava_crust:set_traversable_by("block", true)
  sol.timer.start(map, 15000, function() lava_crust:remove() end)
        elseif map:get_ground(e:get_position()) == "deep_water" then
          self:remove()
  if map:get_hero():get_direction() == 0 then
            ice_patch = map:create_custom_entity({ x=x+8, y=y-16, layer=layer, width=32, height=32, direction=0 })
  elseif map:get_hero():get_direction() == 1 then
            ice_patch = map:create_custom_entity({ x=x-16, y=y-48, layer=layer, width=32, height=32, direction=0 })
  elseif map:get_hero():get_direction() == 2 then
            ice_patch = map:create_custom_entity({ x=x-40, y=y-16, layer=layer, width=32, height=32, direction=0 })
  elseif map:get_hero():get_direction() == 3 then
            ice_patch = map:create_custom_entity({ x=x-16, y=y, layer=layer, width=32, height=32, direction=0 })
  end
          sol.audio.play_sound("freeze")
          ice_patch:create_sprite("entities/ice")
          ice_patch:set_modified_ground("ice")
          ice_patch:set_traversable_by("hero", true)
          ice_patch:set_traversable_by("enemy", true)
          ice_patch:set_traversable_by("block", true)
  sol.timer.start(map, 15000, function() ice_patch:remove() end)
        end
      end
    elseif e:get_type() == "wall" or e:get_type() == "hole" then
      e:stop_movement()
    end
  end
end

function entity:on_removed()
  self:get_sprite():set_animation("destroy")
end