[Test]Reworked items

Started by MetalZelda, December 04, 2015, 04:27:01 PM

Previous topic - Next topic
December 04, 2015, 04:27:01 PM Last Edit: December 04, 2015, 10:34:15 PM by Username
These items scripts are still Work in Progress and thus still need more and more improvement (direction fix mostly, but that's gonna be possible on Solarus 1.5).
You probably have played Four Swords Adventure ? The items behavious is now the same.

But, as I am mostly alone to remake these I can't debug the whole thing, I know that there should be some other glitches wandering around but I can't find these, so I'm going around the players itself to test them and post feedbacks.

Explanations :
-When you use an item, the pause menu is disabled and the opposite item slot is too.
-You can't interract with NPC or such, you need to quit the item. (Similar to Wind Waker)
-Action input is disabled
- To quit the item, there are a lot of method
    - Falling
    - Sword button
    - be hurt by an ennemy (use an alternate function)
    - jumping
    - Going in water.
- Speed is slowed while using item, slower when you're arming the item
- You can rapid-fire (and I believe that bugs are here)
- Hookshot : If you are tracted, the item stop itself, else you can re-fire
- set_finished (containing saved input values) is called in on_map_changed, which is called in game start

The obtainable items are Fire Rod, Hookshot and the Bow, I did already have show the Fire Rod but it's way to function was way too hard and too complex, now, the whole item is scripted and working within it's item file, and allowed to implement joypad controls.

You can take these as you wish, I did not check the whole folder so there might be some files of my project wandering around, some files are from Book of Mudora (text, sprites), it's a really old backup of the project with these new files. But keep in mind that these need more and more improvement, essencially if you might use sensors to trigger cutscenes which will require a game function to halt all items.

The only language available is French but that's only testing things so)

Any feedbacks and bug reports would be nice (Except the dialogs, I already know these).
You can open with with Solarus 1.4.5

They only work for tunic1 (Green tunic)

Authors to credit :
Christopho (Hookshot script (ROTH SE), basic functions)
Wrightmat(some of his ressources are still used in the demo)

Download :
http://www.filedropper.com/test_51

Edit : I didn't included treasure pose management, because items are supposed to be obtainable on chests. But this is gonna be fixed.

Hi @MetalZelda. You might still have a problem that I have, but I still have not found a solution and we will probably need to wait for some improvements of the engine.

The problem is when you sidewalk against a wall while using some weapon (your bow and fire rod, and in my case a custom sword), the hero may start the pushing animation and it cannot be avoided. An animation fix is not a solution since the hero could still push built-in blocks (he would be in "push" state), and only the first frame would be shown if the "pushing" and "walking_with_weapon" animations do not have the same number of frames (i.e., the frame position would be reseted).

If you have any idea how to solve this issue please tell me. Thanks!
(I need this to program the "sword_tapping" for my custom sword, overriding the pushing state somehow.)
"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 April 20, 2016, 05:16:11 AM
Hi @MetalZelda. You might still have a problem that I have, but I still have not found a solution and we will probably need to wait for some improvements of the engine.

The problem is when you sidewalk against a wall while using some weapon (your bow and fire rod, and in my case a custom sword), the hero may start the pushing animation and it cannot be avoided. An animation fix is not a solution since the hero could still push built-in blocks (he would be in "push" state), and only the first frame would be shown if the "pushing" and "walking_with_weapon" animations do not have the same number of frames (i.e., the frame position would be reseted).

If you have any idea how to solve this issue please tell me. Thanks!
(I need this to program the "sword_tapping" for my custom sword, overriding the pushing state somehow.)

I might have a little workaround with this, adding a timer that is refreshed every frame (10ms) that check  the player state might do the thing, macgyver styled.
Something that release the input and immadiately press it again (so the movement is not lost if the player keep pressing the input) if the player enters this state until Solarus allows the modification of these abilities.

Concerning the items, this thread is outdated, direction fix and a shittons of bugfixes has been made on items (and a huge cleanup)

July 17, 2016, 03:40:10 PM #3 Last Edit: July 17, 2016, 05:35:01 PM by MetalZelda
Quote from: Diarandor on April 20, 2016, 05:16:11 AM
Hi @MetalZelda. You might still have a problem that I have, but I still have not found a solution and we will probably need to wait for some improvements of the engine.

The problem is when you sidewalk against a wall while using some weapon (your bow and fire rod, and in my case a custom sword), the hero may start the pushing animation and it cannot be avoided. An animation fix is not a solution since the hero could still push built-in blocks (he would be in "push" state), and only the first frame would be shown if the "pushing" and "walking_with_weapon" animations do not have the same number of frames (i.e., the frame position would be reseted).

If you have any idea how to solve this issue please tell me. Thanks!
(I need this to program the "sword_tapping" for my custom sword, overriding the pushing state somehow.)

Sorry for digging this up, but I might have a workaround.

using this on top of hero_meta:on_state_changed(state) to use an alternate function when needed

Code (lua) Select
  -- Rewrite the state function, temporarly.
  -- This function is used for items.
  if self.on_custom_state_changed ~= nil then
    self:on_custom_state_changed(state)
return
  end


and by doing like so in your item (example is from my hookshot item script)


Code (lua) Select
  -- Temporarly rewrite the default hero state, return to nil if finished
  function self.hero:on_custom_state_changed(state)
 
    local function end_by_collision(self, state)
      if state == "treasure" then
    ended_by_pickable = true
  end
      is_halted_by_anything_else = true
  if state == "stairs" then
    self:set_animation("walking")
  end
  hookshot_controller:stop_hookshot()
    end
   
if state == "pushing" then
  self:unfreeze()
  return
end
 
    for _, states in ipairs(cancel_state) do
  if state == states and self:get_animation() ~= "hookshot_tracting" then
self:cancel_direction_fix()
end_by_collision(self, state)
self.on_custom_state_changed = nil
        return
  end
end
  end


Seems to work, but there is 1 frame where the pushing animation can be seen, there might be some workaround to do with on_animation_changed or on_frame_changed to hide this but I'm quite glad of this result

Hi! No problem, this was an interesting topic to talk about.

I forgot to say it, sorry, but I had already solved this problem with an animation fix (similar to what you have done, but using the script I made for the animation fix with a few changes in the code). The only limitation I found is that, whenever you want to fix an animation, the new one needs to have the same number of frames than the original one, or otherwise it would be stuck at the first frame (each time the animation is changed by the engine the frame would be reseted to the first one, which is too bad). And to avoid pushing blocks while tapping with my custom sword, well... I will just use custom blocks instead of the built-in ones, which will avoid the problem and allow more customization.

I am a bit worried that in the end I will end up only using custom entities for everything, but that is the only way to fully customize everything...  :(
"If you make people think they're thinking, they'll love you. But if you really make them think, they'll hate you."

July 23, 2016, 07:30:44 PM #5 Last Edit: July 23, 2016, 07:37:36 PM by MetalZelda
Quote from: Diarandor on July 17, 2016, 08:28:32 PM
Hi! No problem, this was an interesting topic to talk about.

I forgot to say it, sorry, but I had already solved this problem with an animation fix (similar to what you have done, but using the script I made for the animation fix with a few changes in the code). The only limitation I found is that, whenever you want to fix an animation, the new one needs to have the same number of frames than the original one, or otherwise it would be stuck at the first frame (each time the animation is changed by the engine the frame would be reseted to the first one, which is too bad). And to avoid pushing blocks while tapping with my custom sword, well... I will just use custom blocks instead of the built-in ones, which will avoid the problem and allow more customization.

I am a bit worried that in the end I will end up only using custom entities for everything, but that is the only way to fully customize everything...  :(

Hmmm, I am curious about your solution, can you post the solution ?

EDIT : Small customizations in your fixing direction script makes me able to disable the pushing animation

July 25, 2016, 12:14:39 AM #6 Last Edit: July 25, 2016, 12:16:43 AM by Diarandor
I am posting all the code from my following scripts: "sword.lua", "enemy_metatable.lua", "hero_metatable.lua".
These files include the code I needed to make the custom sword. Take the code that you need.

Sword.lua
Code (Lua) Select

local item = ...

local use_builtin_sword -- = true -- Change this to switch between built-in and custom sword.

local loading_sword_time = 300 -- Do not change this unless you know what you are doing.
local inverted_direction = false -- Possible values: false (normal), true (inverted).
local invert_sword_direction_ability = true -- Enable this to allow direction change.
local sword_command_released -- Used to start loading_sword state if necessary.
local sword_command_pressed_times = 0 -- Used to know if the command has been pressed during an attack.
local sword_state -- Possible states: nil, "brandish", "loading", "loaded", "tapping", "spin".
local sword -- Custom entity sword.
local list_hit_entities = {} -- List of entities hit on the current attack.

-- TODO: remove this function and variable (here and later) when the built-in one is done.
function item:is_being_used() return sword_state ~= nil end

-- Return state of the sword.
function item:get_state() return sword_state end

function item:on_created()
  self:set_savegame_variable("possession_sword")
  self:set_variant(1)
  self:set_assignable(true)
  -- Enable/disable the built-in sword if necessary, just for testing.
  local sword_ability = use_builtin_sword and 1 or 0
  self:get_game():set_ability("sword", sword_ability)
end

-- Define the damage of the sword.
function item:get_damage()
  return self:get_variant() + 1
end

function item:on_variant_changed(variant)
  -- Define this to create several sword variants.
end

function item:on_obtained()
  local hero_index = item:get_game():get_hero():get_index()
  local inventory = item:get_game():get_inventory()
  inventory:add_item(hero_index, item)
end

-- Program sword attack, to replace the built-in one.
function item:on_using()
  local map = self:get_map()
  local game = map:get_game()
  local hero = map:get_hero()

  -- Do not use if there is bad ground below.
  if not map:is_solid_ground(hero:get_ground_position()) then return end
 
  -- Use built-in sword if enabled, or custom sword otherwise.
  if self:get_game():get_ability("sword") > 0 then
    hero:start_attack()
    return
  end
   
  -- Do nothing if game is suspended.
  if game:is_suspended() then return end
  -- Freeze hero and save state.
  if self:is_being_used() then
    sword_command_pressed_times = sword_command_pressed_times + 1
    return
  end
  hero:freeze() -- This is necessary, at least for the custom slot 0.
  if not sword_state then sword_state = "brandish" end
  sword_command_released = false
  -- Do not allow to push walls/blocks. Use sword tapping animation instead.
  hero:set_pushing_animation("sword_tapping")
  -- Remove fixed animations (used if jumping).
  hero:set_fixed_animations(nil, nil)
 
  -- Create custom entity sword.
  local x, y, layer = hero:get_position()
  local dir = hero:get_direction()
  local properties = {name = "sword", x = x, y = y, layer = layer, direction = dir, width = 16, height = 16}
  sword = map:create_custom_entity(properties) 
  local sword_sprite = sword:create_sprite("main_heroes/sword", "sword")
  sword_sprite:set_animation("sword")
  sword_sprite:set_direction(dir)
 
  -- Synchronize sword sprite with hero tunic sprite.
  local hero_tunic_sprite = hero:get_sprite("tunic")
  sword_sprite:synchronize(hero_tunic_sprite)

  -- To draw the sword above the hero, set position at "y -> y+2" and shift sprite by "y -> y-2".
  sword:set_drawn_in_y_order(true)
  local old_set_position = sword.set_position
  function sword:set_position(x, y, layer)
    old_set_position(self, x, y + 2, layer)
  end
  sword:set_position(x, y, layer) -- Update position.
  sword_sprite:set_xy(0, -2) -- Update sprite shift to (0 , -2).
  function sword:set_xy(x, y)
    for _, s in sword:get_sprites() do
      s:set_xy(x, y - 2)
    end
  end
   
  -- Start sword animation on hero. The direction changes on each attack.
  local tunic_animation = inverted_direction and "sword_inverted" or "sword"
  if invert_sword_direction_ability then
    -- Change direction for next attack if needed.
    inverted_direction = (not inverted_direction)
  end
  hero_tunic_sprite:set_animation(tunic_animation)
  sword_sprite:set_animation(tunic_animation)
 
  -- Stop using sword if there is bad ground under the hero.
  sol.timer.start(item, 5, function()
    if not self:get_map():is_solid_ground(hero:get_ground_position()) then
      self:finish_using()
    end
    return true
  end)

  -- Check if the sword command is being hold all the time.
  sol.timer.start(item, 1, function()
    local command = self:get_command()
    if not command or not game:is_command_pressed(command) then
      -- Notify that the sword button was released.
      sword_command_released = true
      return
    end
    return true
  end)
 
  -- Stop sword_loading and sword_tapping animations if the command is released.
  sol.timer.start(item, 1, function()
    if sword_state == "loading" or sword_state == "tapping" then
      if sword_command_released == true then
        -- Finish using sword.
        self:finish_using()
        return
      end
    end
    return true
  end)
 
  -- Synchronize shifts of sprites: used in case the sword is used while jumping.
  local ox, oy = sword:get_origin()
  sol.timer.start(item, 10, function()
    local _, h = hero_tunic_sprite:get_xy()
    sword:set_xy(0, h)
    return true
  end)
 
  -- Synchronize sword sprite with hero tunic sprite for tapping animation (the built-in synchronization
  -- does not work correctly for some unknown reason. TODO: should I open an issue for this? Nope...
  sol.timer.start(item, 1, function()
    if hero:get_state() == "pushing" and sword_state ~= "tapping" then
      -- Start tapping state. Stop timers. Destroy electricity sprite if any.
      sol.timer.stop_all(self)
      sword_state = "tapping"
      local electricity = sword:get_sprite("electricity")
      if electricity then sword:remove_sprite(electricity) end
      -- Select correct shifting for each direction of the sword_tapping animation.
      local dir = hero:get_direction()
      local sx, sy = 0, 0 -- Shifts.
      if dir == 0 or dir == 2 then sy = -2 end -- These shifts are ok.
      sword_sprite:set_xy(sx, sy)
      -- Start timer for the sound.
      sword_sprite:set_animation("sword_tapping")
      local tapping_time = sword_sprite:get_frame_delay() * sword_sprite:get_num_frames()
      sol.timer.start(item, tapping_time, function()
        sol.audio.play_sound("sword_tapping")
        return true
      end)
      -- Update animation frames (the built-in synchronization does not work here).
      sol.timer.start(item, 1, function()
        if hero:get_state() ~= "pushing" then
          -- Finish using sword after finishing tapping.
          self:finish_using()
          return
        end
        -- Update animation frame.
        local frame = hero:get_sprite("tunic"):get_frame()
        sword_sprite:set_animation("sword_tapping")
        sword_sprite:set_frame(frame)
        return true
      end)
    end
    return true
  end)
 
  -- Start sword loading state when necessary.
  local attack_duration = hero_tunic_sprite:get_num_frames()*hero_tunic_sprite:get_frame_delay()
  sol.timer.start(item, attack_duration, function() 
    -- Do not start loading sword if the sword command was released during the attack.
    if sword_command_released == true then
      self:finish_using()
      return
    end
    -- Start loading sword if necessary. Fix direction and loading animations.
    sword_state = "loading"
    hero:set_fixed_animations("sword_loading_stopped", "sword_loading_walking")
    hero:set_fixed_direction(dir)
    hero:set_animation("sword_loading_stopped")
    sword_sprite:set_animation("sword_loading_stopped")
    hero:unfreeze() -- Allow the hero to walk.
    -- Check if the command button is being pressed enough time to load the sword.
    -- Also, update position and animation.
    local loaded_time = 0
    local is_loading_finished = false
    local is_spin_attack_prepared = false
    sol.timer.start(item, 1, function()
      local command = self:get_command()
      if not command or not game:is_command_pressed(command) then
        if is_spin_attack_prepared then item:start_spin_attack(sword)
        else self:finish_using() end
        return false
      end
      if not is_loading_finished then
        loaded_time = loaded_time + 1
        if loaded_time == loading_sword_time then
          is_loading_finished = true
          sword_state = "loaded"
          -- Create electricity sprite when the sword has loaded.
          local electricity_sprite = sword:create_sprite("main_heroes/sword_electricity", "electricity")
          electricity_sprite:synchronize(sword_sprite)
          electricity_sprite:set_direction(dir)
          electricity_sprite:set_xy(0, -2)
          sol.timer.start(item, 1000, function()
            sol.audio.play_sound("sword_spin_attack_load")
            is_spin_attack_prepared = true
          end)
        end
      end
      local sword_animation = sword_sprite:get_animation()
      local new_sword_animation = hero:is_walking() and "sword_loading_walking" or "sword_loading_stopped"
      -- Update sword loading animation: stopped or walking.
      if sword_animation ~= new_sword_animation then sword_sprite:set_animation(new_sword_animation) end
      -- Update position.
      sword:set_position(hero:get_position())
      return true
    end)
  end)
 
  -- Play sword sound.
  sol.audio.play_sound("sword1")
  -- Add collision test to the sword. (Used to hit enemies, cut plants, etc.)
  sword:add_collision_test("sprite", function(entity, other_entity, sprite, other_sprite)
    item:on_collision(entity, other_entity, sprite, other_sprite) 
  end)
end

function item:on_collision(entity, other_entity, sprite, other_sprite)
  -- If the entity has an event for collision with sword, call it.
  local map = self:get_map()
  local hero = map:get_hero()
  if other_entity == hero then return end -- Do not check collisions with the hero.
  -- Do nothing if no consequence is defined.
  if other_entity.receive_attack_consequence == nil then return end
  -- Check if the entity was hit before. In that case do nothing.
  if item:has_hit(other_entity) then return false end
  -- Otherwise, hit the entity and add entity to the hit list.
  list_hit_entities[#list_hit_entities] = other_entity
  local reaction
  if other_entity.get_attack_consequence_sprite then
    reaction = other_entity:get_attack_consequence_sprite(other_sprite, "sword", reaction)
  elseif other_entity.get_attack_consequence then
    reaction = other_entity:get_attack_consequence("sword", reaction)
  end
  other_entity:receive_attack_consequence("sword", reaction)
  -- If the animation is sword_loading (stopped or walking), finish using sword after the hit.
  local state = self:get_state()
  if state == "loading" or state == "loaded" then
    item:finish_using()
  end
end

-- Check if an entity has been hit in the current sword attack.
function item:has_hit(entity)
  for _, e in pairs(list_hit_entities) do if entity == e then return true end end
  return false
end

function item:finish_using()
  -- Stop all timers (necessary if the map has changed, etc).
  sol.timer.stop_all(self)
  -- Destroy sword entity and finish using item.
  if sword then sword:remove(); sword = nil end
  self:set_finished()
  -- Clear list of hit entities.
  list_hit_entities = {}
  -- Reset fixed animations/direction. (Used while loading sword.)
  local hero = self:get_map():get_hero()
  hero:set_fixed_direction(nil)
  hero:set_fixed_animations(nil, nil)
  hero:set_pushing_animation(nil) -- Allow to push walls/blocks again.
  hero:unfreeze()
  hero:refresh_ground_below()
  -- Start new attack if necessary.
  local needs_new_attack = sword_state == "brandish" and sword_command_pressed_times > 0
  sword_state = nil
  sword_command_pressed_times = 0
  if needs_new_attack then self:on_using() end
end

-- Start spin attack.
function item:start_spin_attack(sword)
  sword_state = "spin"
  local hero = self:get_map():get_hero()
  hero:freeze()
  sol.audio.play_sound("sword_spin_attack_release")
  hero:set_animation("spin_attack")
  sword:get_sprite("sword"):set_animation("spin_attack")
  sword:remove_sprite(sword:get_sprite("electricity"))
  local hero_tunic_sprite = hero:get_sprite("tunic")
  local spin_duration = hero_tunic_sprite:get_num_frames()*hero_tunic_sprite:get_frame_delay()
  sol.timer.start(item, spin_duration, function() item:finish_using() end)
end

-- Stop using items when changing maps.
function item:on_map_changed(map)
  if sword_state ~= nil then self:finish_using() end
end

-- Called when a jump has finished.
function item:on_finish_jump(is_good_ground)
  local hero = self:get_game():get_hero()
  local hero_tunic = hero:get_sprite("tunic")
  if not is_good_ground then
  -- Stop using sword if fall on bad ground, or after normal attack on the air.
    self:finish_using()
  elseif sword_state == "brandish" or sword_state == "spin" then
  -- The spin and brandish animations of the hero are interrupted. Fix this.
    hero:freeze()
    local animation = sword:get_sprite("sword"):get_animation()
    local frame = sword:get_sprite("sword"):get_frame()
    hero_tunic:set_animation(animation)
    hero_tunic:set_frame(frame)
  elseif sword_state == "loading" or sword_state == "loaded" then
  -- Keep the loading animation on the hero (it is reseted when the jump finishes).
     hero:set_fixed_animations("sword_loading_stopped", "sword_loading_walking")
  end

  -- Refresh ground below hero (necessary because of a bug after unfreezing).
  hero:refresh_ground_below()

end


enemy_metatable.lua
Code (Lua) Select

local enemy_meta = sol.main.get_metatable("enemy")

-- Redefine how to calculate the damage inflicted by the sword.
function enemy_meta:on_hurt_by_sword(hero, enemy_sprite)
  local force = hero:get_game():get_item("sword"):get_damage()
  local reaction = self:get_attack_consequence_sprite(enemy_sprite, "sword")
  -- Multiply the sword consequence by the force of the hero.
  local life_lost = reaction * force
  if hero:get_state() == "sword spin attack" then
    -- And multiply this by 2 during a spin attack.
    life_lost = life_lost * 2
  end
  self:remove_life(life_lost)
end


-- Helper function to inflict an explicit reaction from a scripted weapon.
-- TODO this should be in the Solarus API one day
function enemy_meta:receive_attack_consequence(attack, reaction)
  if type(reaction) == "number" then
    self:hurt(reaction)
  elseif reaction == "immobilized" then
    self:immobilize()
  elseif reaction == "protected" then
    sol.audio.play_sound("sword_tapping")
  elseif reaction == "custom" then
    if self.on_custom_attack_received ~= nil then
      self:on_custom_attack_received(attack)
    end
  end
end

"If you make people think they're thinking, they'll love you. But if you really make them think, they'll hate you."

July 25, 2016, 12:14:49 AM #7 Last Edit: July 25, 2016, 12:16:52 AM by Diarandor
hero_metatable.lua
Code (Lua) Select

-- Define/redefine functions of the hero metatable.
local hero_meta = sol.main.get_metatable("hero")

-- Functions to get/set the index of the hero.
function hero_meta:get_index() return self:get_game():get_hero_index() end
function hero_meta:set_index(index) self:get_game():set_hero_index(index) end

-- Define a function to change to carrying state. (The same functions are defined for npc_heroes.)
function hero_meta:set_carrying(boolean)
  if boolean then
    self:set_fixed_animations("carrying_stopped", "carrying_walking")
    self:set_animation("carrying_stopped")
  else
    self:set_fixed_animations(nil, nil)
    if self:is_walking() then self:set_animation("walking")
    else self:set_animation("stopped") end
  end
end

-- Make carried entities follow the hero when the position changes.
function hero_meta:on_position_changed()
  if self.custom_carry then -- There is a carried item.
    local x, y, layer = self:get_position()
    if self.custom_carry.set_position then
      self.custom_carry:set_position(x, y+2, layer)
    end
  end
end 

-- Throw the carried entities when hurt.   
function hero_meta:on_taking_damage(damage)
  self:get_game():remove_life(damage) -- Remove life.
  if self.custom_carry then
    self:start_throw() -- Throw carried entity.
  end
end
   
-- Throw generic portable entities.
-- Display the throwing animation of the hero and throw the portable entity.
function hero_meta:start_throw()
  -- State changes and properties. Get direction to throw.
  local game = self:get_game()
  local animation = self:get_animation()
  local direction = self:get_direction()
  if animation == "carrying_stopped" or animation == "hurt" then direction = nil end
  local portable = self.custom_carry
  self.custom_carry = nil
  self:freeze()
  -- Change animation set of hero to stop carrying. Start animation throw of the hero.
  self:set_animation("throw", function()
    game:clear_interaction() -- Restart hero interaction.
    self:set_carrying(false)
    self:unfreeze()
  end)
  game:set_custom_command_effect("action", nil)
  game:set_custom_command_effect("attack", nil)
  local carrier = self -- Used to make platform inertia if carrier is on platform.
  -- Throw the portable entity.
  portable:set_position(self:get_position())
  local args = {falling_direction = direction, carrier = carrier}
  portable:throw(args)
end

-- Function to know if the hero is walking.
function hero_meta:is_walking()
  local m = self:get_movement()
  return m and m.get_speed and m:get_speed() > 0
end

-- Function to set a fixed direction for the hero (or nil to disable it).
function hero_meta:set_fixed_direction(direction)
  self.fixed_direction = direction
  if direction then self:get_sprite("tunic"):set_direction(direction) end
end
-- Function to get a fixed direction for the hero.
function hero_meta:get_fixed_direction()
  return self.fixed_direction
end
-- Function to set fixed stopped/walking animations for the hero (or nil to disable it).
function hero_meta:set_fixed_animations(stopped_animation, walking_animation)
  self.fixed_stopped_animation = stopped_animation
  self.fixed_walking_animation = walking_animation
  -- Initialize fixed animations if necessary.
  if self:get_state() == "free" then
    if self:is_walking() then self:set_animation(walking_animation or "walking")
    else self:set_animation(stopped_animation or "stopped") end
  end
end
-- Function to get fixed stopped/walking animations for the hero.
function hero_meta:get_fixed_animations()
  return self.fixed_stopped_animation, self.fixed_walking_animation
end

-- Functions to enable/disable pushing ability for the hero.
-- TODO: change this if necessary for a built-in function when it is done.
do
  local pushing_animation
  function hero_meta:get_pushing_animation() return pushing_animation end
  function hero_meta:set_pushing_animation(animation) pushing_animation = animation end
end

-- Initialize events to fix direction and animation for the tunic sprite of the hero.
-- To do it, we redefine the on_created and set_tunic_sprite_id events using the hero metatable.
do
  local function initialize_fixing_functions(hero)
    -- Define events for the tunic sprite.
    local sprite = hero:get_sprite("tunic")
    function sprite:on_animation_changed(animation)
      local fixed_stopped_animation = hero.fixed_stopped_animation
      local fixed_walking_animation = hero.fixed_walking_animation
      local tunic_animation = sprite:get_animation()
      if tunic_animation == "stopped" and fixed_stopped_animation ~= nil then
        if fixed_stopped_animation ~= tunic_animation then
          sprite:set_animation(fixed_stopped_animation)
        end
      elseif tunic_animation == "walking" and fixed_walking_animation ~= nil then
        if fixed_walking_animation ~= tunic_animation then
          sprite:set_animation(fixed_walking_animation)
        end
      elseif tunic_animation == "pushing" then
        local pushing_animation = hero:get_pushing_animation()
        if pushing_animation then hero:set_animation(pushing_animation) end
      end
    end
    function sprite:on_direction_changed(animation, direction)
      local fixed_direction = hero.fixed_direction
      local tunic_direction = sprite:get_direction()
      if fixed_direction ~= nil and fixed_direction ~= tunic_direction then
        sprite:set_direction(fixed_direction)
      end
    end
  end
  -- Initialize fixing functions when the hero is created.
  function hero_meta:on_created()
    initialize_fixing_functions(self)
  end
  -- Initialize fixing functions for the new sprite when the sprite is replaced for a new one.
  local old_set_tunic = hero_meta.set_tunic_sprite_id -- Redefine this function.
  function hero_meta:set_tunic_sprite_id(sprite_id)
    old_set_tunic(self, sprite_id)
    initialize_fixing_functions(self)
  end
end

-- TODO: delete next function after issue #827 is fixed.
-- Call this function to notify the engine that the ground under hero has changed.
-- Necessary after calling hero:unfreeze(), unless some entity has modified the ground below.
function hero_meta:refresh_ground_below()
  -- Create custom entity with traversable ground that under the hero.
  local x,y,layer = self:get_position()
  local tile = self:get_map():create_custom_entity({x=x,y=y,layer=layer,direction=0,width=8,height=8})
  tile:set_origin(4, 4)
  tile:set_modified_ground("traversable")
  tile:remove()
end

-- End of the script. Kill the local variable.
hero_meta = nil
"If you make people think they're thinking, they'll love you. But if you really make them think, they'll hate you."

July 25, 2016, 12:54:04 AM #8 Last Edit: July 03, 2018, 10:07:12 PM by Zefk
I tested your download link and I get a weird test.m3u video file that shows the text "blocked" in English and Hindi when played.