Raft script

Started by Diarandor, November 08, 2016, 11:15:00 PM

Previous topic - Next topic
Hi! I was asked by @Porksteak in a personal message if I could share the platform-raft script that I use in this video:
https://www.youtube.com/watch?v=gIKvf_0hIpU

So here goes the code. But note that some parts of the code are a bit old and could be improved (for instance, I should use "require" instead of "load_file" in the raft script, to make the code faster), and some things of the script may require other scripts. My raft script requires my platform script to work, so I include both scripts here. It is recommended that you study the code to make another script that suits better for your purposes, and remove unnecesary things from my code (for instance, you probably do not need the raft to move secondary heroes or custom entities, so you should delete these parts of the code and other similar things).

generic_platform.lua script:
Code (Lua) Select

-- Platform: entity which moves in either horizontally or vertically (depending on direction)
-- and carries the hero on it.
local entity = ...
local map = entity:get_map()
local game = entity:get_game()
local hero = map:get_hero()

entity.can_save_state = true

local time_stopped = 1000
local is_moving = false
-- Remark: speeds bigger than 12 give problems for water platforms (rafts) when trying to enter or go out. No problem above holes.
entity.speed = 50 -- Set speed.

function entity:on_created()
  --self:create_sprite("entities/platform")
  self:set_size(32, 32)
  self:set_origin(16, 13) -- Important: the 32x32 sprite must have center in (16,13).
  self:set_modified_ground("traversable")
 
  -- Set dynamic solid ground position for the hero to avoid problems.
  sol.timer.start(entity, 10, function()
    -- Do nothing if hero is not on solid ground.
    local is_hero_on_solid_ground = map:is_solid_ground(hero:get_ground_position())
    if hero.is_jumping or not is_hero_on_solid_ground then return true end
    -- Save or clear solid ground position on this platform.
    if map.current_hero_platform ~= entity and self:is_on_platform(hero) then
      map.current_hero_platform = entity
      self:save_hero_position() -- Save solid ground on the platform.
    elseif map.current_hero_platform == entity and (not self:is_on_platform(hero)) then
      map.current_hero_platform = nil
      hero:reset_solid_ground() -- Clear solid ground.
    end
    return true
  end)

  -- Custom function.
  if entity.on_custom_created then
    entity:on_custom_created()
    return
  end
 
  -- Initialize properties.
  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("traversable", true)
  self:set_can_traverse_ground("shallow_water", true)
  self:set_can_traverse_ground("wall", false)

  -- Start movement.
  local direction = self:get_direction()
  local m = sol.movement.create("path")
  m:set_path{direction * 2}; m:set_speed(entity.speed)
  m:set_loop(true); m:start(self)
  is_moving = true

end

-- Function to save hero position on the platform.
function entity:save_hero_position()
  hero:save_solid_ground(function()
    --[[ When the hero reappears, the camera moves towards the new position,
    which produces a delay, and the platform has already moved when the hero appers.
    To fix this problem, change the position of the hero directly, so the movement
    will be instantaneous with no delay. To avoid this we must not call
    "hero:get_solid_ground_position()", or there will be problems with platforms.
    --]]
    local x, y, layer = entity:get_center_position()
    -- Change position at next cycle to avoid engine problems (keep this timer!).
    sol.timer.start(self, 1, function()
      hero:unfreeze()
      hero:set_blinking(true, 1000)
      hero:set_invincible(true, 1000)
      hero:set_position(x, y, layer)
    end)
    return x, y, layer
  end)
end


-- Update shifting variables for the translation.
local function get_shifts()
  local direction = entity:get_direction()
  local dx, dy = 0, 0 -- Variables for the translation.
  if direction == 0 then dx = 1 elseif direction == 1 then dy = -1
  elseif direction == 2 then dx = -1 elseif direction == 3 then dy = 1 end
  return dx, dy
end

-- Get movable entities that were on the previous position of the platform.
function entity:get_movable_entities()
  local movable_entities = {}
  for other in map:get_entities_in_rectangle(entity:get_bounding_box()) do
    -- Check only entities that can be moved, including the hero.
    if other.moved_on_platform or other:get_type() == "hero" then
      -- Check if the entity was on the platform before the movement.
      if entity:was_on_platform(other) and entity:is_on_platform(other) then
        -- Exclude portable entities unless they are on the ground.
        if (not other.is_portable) or other.state == "on_ground" then
          table.insert(movable_entities, other)
        end         
      end
    end
  end
  return movable_entities
end

-- Return true if an entity is on the platform.
-- IMPORTANT: This function is only used in the script generic_portable.lua.
function entity:is_on_platform(other)
  local x, y, layer = other:get_ground_position()
  return entity:overlaps(x, y)
end

-- Return true if an entity was on the platform before the movement.
function entity:was_on_platform(other)
  local dx, dy = get_shifts()
  local ox, oy, oz = other:get_ground_position()
  local bx, by, w, h = entity:get_bounding_box()
  local pbx, pby = bx-dx, by-dy -- Previous position of bounding box (before the movement).
  return ox >= pbx and ox < pbx+w and oy >= pby and oy < pby+h
end

function entity:on_obstacle_reached(movement)
  --Make the platform turn back.
  local direction = self:get_direction()
  movement:stop(); is_moving = false
  movement = sol.movement.create("path")   
  direction = (direction+2)%4
  self:set_direction(direction)
  movement:set_path{direction * 2}
  movement:set_speed(entity.speed)
  movement:set_loop(true)
  sol.timer.start(self, time_stopped, function()
    movement:start(self)
    is_moving = true 
  end)
end

-- Move the movable entities that were on the platform before the movement.
function entity:on_position_changed()
  -- Get the movable entities that were on the platform before the change of position.
  local dx, dy = get_shifts() -- Variables for the translation.
  local movable_entities = entity:get_movable_entities()
  local ex, ey, ez = self:get_position()
  local bx, by, w, h = self:get_bounding_box()
  local pbx, pby = bx-dx, by-dy -- Previous position of bounding box (before the movement).
  for _, other in pairs(movable_entities) do
    local ox, oy, oz = other:get_position()
    -- Move the entity with the platform.
    if not other:test_obstacles(dx, dy, oz) then
      other:set_position(ox + dx, oy + dy, oz)
    end
  end
end


raft.lua script:
Code (Lua) Select

local entity = ...
-- Raft entity.

sol.main.load_file("entities/generic_platform")(entity)
local sea_foam_sprite -- Foam sprite for the raft.
entity.speed = 20 -- Set speed.

function entity:on_custom_created()
  -- Set custom properties.
  time_stopped = 1000
  self:set_can_traverse_ground("deep_water", true)
  self:set_can_traverse_ground("wall", false)
  self:set_can_traverse("jumper", false)
  self:set_can_traverse_ground("hole", false)
  self:set_can_traverse_ground("traversable", true)
  self:set_can_traverse_ground("shallow_water", false)
  self:set_can_traverse_ground("grass", false)
 
  -- Customize movement.
  local direction = self:get_direction()
  local m = sol.movement.create("path")
  m:set_path{direction * 2}; m:set_speed(entity.speed)
  m:set_loop(true); m:start(self)
  is_moving = true
 
  -- Start the sea foam and wavering.
  self:add_sea_foam()
  self:start_wavering() -- Start moving raft sprite to simulate the waves.
end

----------------- Raft functions:

-- Add the sea foam sprite.
function entity:add_sea_foam()
  -- Remark: The variable sea_foam_sprite is already local in this script!
  sea_foam_sprite = self:create_sprite("things/platform")
  sea_foam_sprite:set_animation("sea_foam")
  sea_foam_sprite:set_xy(-8,24)
end

-- Make the sprite move up and down.
function entity:start_wavering()
  local sprite = self:get_sprite()
  -- Define vectors with the shifts of each "frame" of the sprite and time for each pause between "frames".
  local raft_dy = {0,-1,-2,-1} -- Shifts for the "y" coordinate of the sprite.
  local entities_dy = {1,-1,-1,1} -- Shifts for the "y" coordinate of position of entities.
  local pause_time = {200,1000,200,1000} -- Pause times between "frames".
 
  -- Move the movable entities located over the platform, if possible.
  local function shift_entities(dy_shift)
    local movable_entities = entity:get_movable_entities()
    local x, y, z = self:get_position()
    local bx, by, w, h = self:get_bounding_box()
    for _, something in pairs(movable_entities) do   
      if entity:is_on_platform(something, bx, by, w, h) then   
      local sx, sy, sz = something:get_position()
        -- Move only entities that can be moved.
        local needs_move = false
        if something:get_type() == "hero" then
          if something:get_animation() ~= "walking" then
            needs_move = true
          end
        elseif something:get_sprite() then
          if something:get_sprite():get_animation() ~= "walking" then
            needs_move = true
          end
        end
        -- Check for obstacles.
        local found_obstacles = something:test_obstacles(0, dy_shift, sz)
        -- Finally check if the entity is not in the "bad border" to move it without getting out the platform.
        if needs_move and (not found_obstacles) then
          if (sy + dy_shift) >= by+2 and (sy + dy_shift) <= by+h-3 then sy = sy +dy_shift end
          something:set_position(sx, sy, sz)
        end
      end
    end
  end

  -- Shift sprite and move entities.
  local function set_frame_loop(frame_nb)
    -- Shift the sprite. Shift entities above the rift, if possible.
    sprite:set_xy(0, raft_dy[frame_nb])
    shift_entities(entities_dy[frame_nb])
    -- Restart the loop.
    frame_nb = (frame_nb %4) +1 -- Next frame index.
    sol.timer.start(entity, pause_time[frame_nb], function() set_frame_loop(frame_nb) end)
  end
 
  -- Start the wavering loop. 
  set_frame_loop(1)
end
"If you make people think they're thinking, they'll love you. But if you really make them think, they'll hate you."

Thanks so much, Diarandor! ^-^

No problem! :)

However my code is not clean, and there are probably other scripts that are needed to run this code without errors (there should be "requires" in the above part of this script for those other scripts to make this easier to share). I might make a clean version someday that can be shared easily, but not for now. I don't have time now (I am working on free art). So you will need to study/modify the code to be able to run it without errors.

If you need my raft image just ask for it. I think I have not shared it yet in my deviantart account.
"If you make people think they're thinking, they'll love you. But if you really make them think, they'll hate you."