[QUESTION] Traversable prickles?

Started by ffomega, February 10, 2017, 02:49:33 PM

Previous topic - Next topic
I was wondering why the prickles ground type is not traversable.  In Link to the Past, the Hero could still walk on a prickle ground type and take constant damage at the same time (a perfect example of this is the room inside Dark World's Death Mountain, where you had to travel across an entire room full of spikes to reach the chest at the end, containing the Cane of Byrna.

However in Solarus, when the hero touches a prickle ground type, he comes to a complete stop.  Should future versions of Solarus include traversable prickles, or was this something you decided upon?
My tilesets page can be found here:
http://absolute-hyrule-tutorials.solarus-games.org/

You are right, this is something different from A Link to the Past. There are currently no plans to add a traversable option to them. But it should be doable to implement this is Lua with custom entities.

February 19, 2017, 01:19:48 PM #2 Last Edit: February 21, 2017, 05:19:16 PM by SL1200
Not sure if its any use to you but I made the floor spikes using a custom entity, they damage the hero by 1 and push him away on collision, think the push away is linked to start_hurt() function.
I believe you could alternatively use the remove_life() function if you set up a timer to prevent it from instantly killing you.
Hope it's of some use:
Code (lua) Select
-- Floor Spikes: damages hero by 1 but can be traversed with magic_cape or if hero is invincible

local floor_spikes = ...
local game = floor_spikes:get_game()
local map = floor_spikes:get_map()
local hero = game:get_hero()


-- setup basic values for floor_spikes
function floor_spikes:on_created()

floor_spikes:set_size(16, 16)
floor_spikes:set_origin(8, 13)
floor_spikes:set_traversable_by("hero", true)
        floor_spikes:set_layer_independent_collisions(true)

if floor_spikes:get_sprite() == nil then
floor_spikes:create_sprite("entities/floor_spikes")
end
end

-- function to process results with collision test
local function spikes_collision_test(floor_spikes, entity)

  local x, y = hero:get_position()

  -- if not the hero ignore collision
  if entity:get_type() ~= "hero" then
    return false
  end

  if hero:is_blinking() then
    return false
  end

  -- if magic_cape is active ignore collision
  if game:get_item("magic_cape"):is_cape_active() then
    return false
  end
  -- return the collision point to be used by the collision test
  return floor_spikes:overlaps(x, y)

end
-- collision test: if triggered hurt hero by 1 and push away
floor_spikes:add_collision_test(spikes_collision_test, function(floor_spikes, entity)
  --game:remove_life(1) --instakill!
  hero:start_hurt(floor_spikes, 1) 
end)

Quote from: SL1200 on February 19, 2017, 01:19:48 PM
Not sure if its any use to you but I made the floor spikes using a custom entity, they damage the hero by 1 and push him away on collision, think the push away is linked to start_hurt() function.
I believe you could alternatively use the remove_life() function if you set up a timer to prevent it from instantly killing you.
Hope it's of some use:
Code (lua) Select
-- Floor Spikes: damages hero by 1 but can be traversed with magic_cape

local floor_spikes = ...
local game = floor_spikes:get_game()
local map = floor_spikes:get_map()
local hero = game:get_hero()
local direction = hero:get_direction()
local sprite
local triggered = false

-- setup basic values for floor_spikes
function floor_spikes:on_created()

floor_spikes:set_size(16, 16)
floor_spikes:set_origin(8, 13)
floor_spikes:set_traversable_by("hero", true)
  floor_spikes:set_layer_independent_collisions(true)
if floor_spikes:get_sprite() == nil then
floor_spikes:create_sprite("entities/floor_spikes")
end
sprite = floor_spikes:get_sprite()
end
-- function to process results with collision test
local function spikes_collision_test(floor_spikes, entity)

  local hero = entity
  local x, y = hero:get_position()
  -- if not the hero ignore collision
  if entity:get_type() ~= "hero" then
    return false
  end

  if hero:is_blinking() then
    return false
  end

  -- if magic_cape is active ignore collision
  if game:get_item("magic_cape"):is_cape_active() then
    return false
  end
  -- return the collision point to be used by the collision test
  return floor_spikes:overlaps(x, y)

end
-- collision test: if triggered hurt hero by 1 and push away
floor_spikes:add_collision_test(spikes_collision_test, function(floor_spikes, entity)
  --game:remove_life(1) --instakill!
  hero:start_hurt(floor_spikes, 1)
 
end)

-- constructor function
function floor_spikes:create_floor_spikes()

local map = floor_spikes:get_map()
local hero = map:get_hero()
local _direction = hero:get_direction()
local _x, _y

if direction == 0 then
_x, _y = 12, -4
elseif direction == 1 then
_x, _y = 0, -18
elseif direction == 2 then
_x, _y = -14, -4
elseif direction == 3 then
_x, _y = 0, 10
end

local x, y, layer = hero:get_position()
   
map:create_custom_entity{

model = "floor_spikes",
x = x + 32,
y = y + 32,
layer = layer,
width = 16,
height = 16,
direction = _direction,
}
 
end


There are some things wrong in this script, IMO. I'll come to the point:

-You should remove the unnecessary lines 26, 55 and 56, since the map and hero are already stored in the variables of lines 5 and 6.
-You should also remove line 7 because you are not using (or probably should not use) that direction, that is the initial direction of the hero when he enters the map.
-In line 21 you store the sprite in the "sprite" variable, but you never use it, so you should not store it. The same for the "triggered" variable in line 9.
-There are definitely things wrong in the function "floor_spikes:create_floor_spikes()": you never use the values of the variables "_x" and "_y". Besides, the "direction" variable in lines 60, 62, 64, 66, should that be the "_direction" variable instead? or maybe not? Actually I don't understand why you define the function "floor_spikes:create_floor_spikes()", that seems unnecessary (if you want to create a custom entity of some type you don't need to use another custom entity of that type with that method).

I guess that your script is either unfinished or an old version. Otherwise it may need some changes.
"If you make people think they're thinking, they'll love you. But if you really make them think, they'll hate you."

My bad, wasn't paying attention to how messy it was or all the unneeded junk in there that was left over from trying other ways of getting it working.
I basically went through loads of messing around due to using remove_life() as it made the hero die instantly, I had tried moving the hero back if he got damaged by it but couldn't get it working.
Then found start_hurt() worked perfectly and as everything was working just left it n moved onto another item.



February 26, 2017, 02:11:34 AM #5 Last Edit: February 26, 2017, 02:17:48 AM by MetalZelda
Also another thing, mostly performance wise, don't set a defined size, use the custom entity size, so there are less things to process, yet the effect will be the same

This is only if you plan to do corridor of spikes, like in ffomega's example, easier.

I have done this trick using Diarandor's platform script and in one of my Dungeon (fan streams), and it works great (https://github.com/MetalES/Project-Zelda/issues/51#issuecomment-269782380)

Code (lua) Select

local spike_width, spike height = self:get_size()
self:set_size(spike_width, spike_height)
self:set_origin(spike_width / 2, spike_height - 3)


This way you can easily create a corridor of spikes with only 1 custom entity, to fill the entity width and height with sprites, there should be a workaround with on_pre_draw(), ALTTP spikes are 8x8 I guess, so this would be easy

Sweet I see what you mean I'll have to look into setting it up like you suggested so 1 entity can cover a length of corridor instead of using n tiles like I probably would have.

Many thanks for the advice.

so far the only thing I was able to do is this:

I created a spiked floor in a map, then I put in the following code into a custom entity:

Code (lua) Select

local entity = ...
local game = entity:get_game()
local map = entity:get_map()

-- Event called when the custom entity is initialized.
function entity:on_created()
  self:snap_to_grid()
  self:set_modified_ground("traversable")
  self:set_traversable_by("hero", true)

  -- Initialize the properties of your custom entity here,
  -- like the sprite, the size, and whether it can traverse other
  -- entities and be traversed by them.
end


The hero can traverse the entity as expected, but he does not take any damage yet.  I want the code to fundtion like SL1200's code does, allowing the hero to walk on the spikes without taking damage as long as the magic capt is activated.  However, I do not want a knock back, I wand the hero to retain his x and y position and be able to walk on the entity as much as he wants while still taking the damage over time.  This is part of the reason why the "green and purple lava" looking tiles exists in my tilesets.  This entity would be usable with these tiles as well. 
My tilesets page can be found here:
http://absolute-hyrule-tutorials.solarus-games.org/

June 20, 2017, 01:09:02 PM #8 Last Edit: June 20, 2017, 01:14:07 PM by Diarandor
This is another example of why we need more ground customization (allowing custom tiles, especially for bad grounds).
"If you make people think they're thinking, they'll love you. But if you really make them think, they'll hate you."

June 21, 2017, 12:31:48 PM #9 Last Edit: June 21, 2017, 12:36:46 PM by MetalZelda
Maybe using dynamic tiles metatable and apply bad ground if id = spikes

http://www.solarus-games.org/doc/latest/lua_api_dynamic_tile.html#lua_api_dynamic_tile_get_pattern_id

Since dynamic tiles are entities, you can redefine it's behavious on creation time

Quote from: MetalZelda on June 21, 2017, 12:31:48 PM
Maybe using dynamic tiles metatable and apply bad ground if id = spikes

http://www.solarus-games.org/doc/latest/lua_api_dynamic_tile.html#lua_api_dynamic_tile_get_pattern_id

Since dynamic tiles are entities, you can redefine it's behavious on creation time

I agree with MetalZelda: this is probably one of the most elegant ways to code it. (Create normal tiles and replace them with custom entities immediately after their creation, which can be done from the tiles metatable.)

Note that you can allow the spike tiles to be resizable, but in this case you need the custom entity to create as many sprites as 16x16 rectangles you have in your spikes tile.
"If you make people think they're thinking, they'll love you. But if you really make them think, they'll hate you."