Solarus-Games English Forum

Community => Your scripts => Topic started by: PhoenixII54 on September 05, 2017, 08:46:37 PM

Title: Turtle Rock (Alttp) Pipe system
Post by: PhoenixII54 on September 05, 2017, 08:46:37 PM
Hi !

Have you ever wanted to have those pipes in Crystal Palace of MoS DX move your characters automagically ? Then this script is for you !

Code ( lua) Select
--A simple string splitter, found on the Internet.
--Used for extracting parameters in the sensor name.
--You might want to use your own version to avoid possible license issues

local function string_split(source, delimiters)
  local elements = {}
  local pattern = '([^'..delimiters..']+)'
  string.gsub(source, pattern, function(value)
    elements[#elements + 1] = value
  end)
  return elements
end

--A simple yet powerful pipe system like in A link to the Past Turtle Rock
--Usage : Place a series of sensors on the map, then name them as this :
--pipe_<path_id>_<path_index>
--Warning : you MUST set the indexes to a continuous way (1,2,3,...) or else you will be stuck in the middle of the movement.
--Notice : While it requires nothing but requiring this script in the main file to run, you might want to use en event register like in Christopho's projects
--as it overwrites the sensors' on_activated event

local sensor_meta=sol.main.get_metatable("sensor")

function sensor_meta:on_activated()
  local name=self:get_name()
  local map=self:get_map()
  local hero=map:get_hero()
  if name~=nil then
    local type=string_split(name,"_")
       
    if type[1]=="pipe" then
    --We are now in the pipe, either on en end or in the middle of it.
      if not sensor_meta.pipe_active then
        --Actually entering the pipe
        length=map:get_entities_count("pipe_"..type[2])
        hero:freeze()
        hero:set_visible(false)
        --detecting whether we are going in the increasing index order or not
        local reversed=(tonumber(type[3])==length)
        local next
        if reversed then
          next=length-1
        else
          next=2
        end
        --Saving settings in the metatable for later usage
        sensor_meta.pipe_next=next
        sensor_meta.pipe_is_reversed=reversed
        sensor_meta.pipe_length=length
        sensor_meta.pipe_active=true
     
      else
        --Already in the pipe and continuing the movement
        if sensor_meta.pipe_is_reversed then
          sensor_meta.pipe_next=sensor_meta.pipe_next-1
        else
          sensor_meta.pipe_next=sensor_meta.pipe_next+1
        end
      end
 
      --Actually start the movement
      if sensor_meta.pipe_next==0 or sensor_meta.pipe_next==sensor_meta.pipe_length+1 then
      --We are about to finish the move : make the hero walk out of the pipe
        local movement=sol.movement.create("path")
        local dir=sensor_meta.pipe_outDir or 0
        local path={dir,dir}
        movement:set_path(path)
        hero:set_visible(true)
        hero:set_direction(dir/2)
        hero:set_animation("walking")
        movement:start(hero,function()
          hero:unfreeze()
          sensor_meta.pipe_active=false
        end)
      else
        --making the quick auto move in the pipe (and bonk in the corners)
        --Feel free to change this sound to your own !
        sol.audio.play_sound("bomb")
        local movement=sol.movement.create("target")
        local target="pipe_"..type[2].."_"..sensor_meta.pipe_next
        movement:set_target(map:get_entity(target))
        movement:set_speed(192)
        movement:start(hero)
        --Saving the movement direction for when we will leave the pipe
        sensor_meta.pipe_outDir=2*movement:get_direction4()
      end
    end
  end
  --And that(s all folks !
end


Note: if you find a simpler way, don't hesitate to tell me, i'll be happy to rework my code as a challenge !
Enjoy !

edit : correction of misnamed function call originally using another file.
edit #2 : correction of movement:set_garget :
It used map:get_entities(target)(1), now it uses map:set_entity(target) since now you should have ensured all sensors have a unique ID.
edit #3 : fixed a possible issue when you try to make a pipe with only 1 sensor.
+ fixed comments indentation, removed some typo and removed remaining debug prints.
Title: Re: Turtle Rock (Alttp) Pipe system
Post by: Diarandor on September 06, 2017, 02:02:04 AM
A different way (not necessarily better or worse) is to use streams that freeze the hero, as conveyor belts, and two sensors to start/finish the walking animations (at both sides of the pipe). My approach requires more mapping, though.

That code seems very interesting, for learning purposes, for people who is learning Lua or want to improve skills on coding (and looks very clean, short and polished). Also, I love that word: "automagically" ;D
Title: Re: Turtle Rock (Alttp) Pipe system
Post by: PhoenixII54 on September 06, 2017, 05:28:15 PM
You know what ? that's what i did in a first version with custom entities instead of the sensors, it worked well too, but wanted something "universal", as in just place any number of sensors carefully named and voila!

Anyways, thanks!
Title: Re: Turtle Rock (Alttp) Pipe system
Post by: MetalZelda on September 07, 2017, 10:01:33 AM
movement:set_target(map:get_entities(target)(1))

Wait such thing is possible ?
Title: Re: Turtle Rock (Alttp) Pipe system
Post by: Christopho on September 07, 2017, 10:52:23 AM
Hum, map:get_entities(prefix) returns an iterator so you can indeed get its first value by calling the result:
Code (lua) Select

local entity = map:get_entities(prefix)()

But this is obscure to read, and the parameter 1 is useless and has no effect.
Title: Re: Turtle Rock (Alttp) Pipe system
Post by: PhoenixII54 on September 07, 2017, 02:00:34 PM
Actually this is a reminiscence of an experimental way where i used the name to code the output direction when at the end of a pipe, which caused problems since i could''t get the next entity if i was at the very start of the chain, but since then i found that using the direction of the actual movement is way easier, so you can replace it by
Code ( lua) Select
map:get_entity(target)
since  the sensors now use the same name convention.

edit : code is now updated to apply the modification.