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.
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?
@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.
Isn't it possible to get the pattern or other information from a dynamic tile with some function?
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
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.
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