I have the snippet of code below where I'm essentially trying to re-create block pushing in a custom entity (in order to additional functionality for the block). The problem I'm having having is that the block doesn't start moving until after the hero has stopped pushing and has moved away from the block - rather than actually pushing the block. Is this the expected behavior and I'm doing something wrong? Is there a way to have the movement start as the hero is pushing?
self:add_collision_test("touching", function(self, other)
if other:get_type() == "hero" then
local m = sol.movement.create("path")
m:set_ignore_obstacles(true)
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)
Maybe it works, but the collision test is repeatedly called, starting a new movement every time?
If this is the problem, adding a boolean on the block to remember if it is already being pushed or not should work. Or maybe just checking first that the block has no movement.
Adding a boolean worked perfectly - thanks Christopho!
One more question. Is it possible to make my custom entity block act like a block for the purpose of activating switches? It appears that when I push my block onto a switch that requires a block to activate, my block knows it's on a switch, but it does not activate the switch.
No, but you should be able to do it manually with a collision test on switches.
I can activate it easily by adding this to the code above:
elseif other:get_type() == "switch" then
other:set_activated(true)
end
But I can't figure out how to inactive it on leaving. How do you detect when something's not colliding? (I'm sure it's obvious and I haven't thought of it!)
It is not that obvious in fact.
A way to detect when something is not colliding is to test if entity:overlaps(other). You can do it periodically from a timer, or only when your custom block moves (from custom_block:on_position_changed())
Thanks again! That general approach worked. Once I get some final issues worked out, I'll be done!
I've also created a custom entity to act as a block.
It works pretty well, but the only problem i have is, once the entity is pushed once, it doesn't react anymore (i can't push it a second time and the action icon doesn't appear anymore).
here's the code i have so far:
local boulder_small = ...
local map = boulder_small:get_map()
local game = boulder_small:get_game()
local hero = map:get_hero()
local x, y, layer = boulder_small:get_position()
local sprite = boulder_small:get_sprite()
local hero_facing_boulder_small = false
local action_command_boulder_small = false
local pushed = false
boulder_small:set_traversable_by("hero", false)
boulder_small:set_traversable_by("enemy", false)
boulder_small:set_traversable_by("custom_entity", false)
-- Show an action icon when the player faces the boulder.
boulder_small:add_collision_test("facing", function(boulder_small, other)
if other:get_type() == "hero" then
if other:get_animation() == "pushing"
and pushed == false then
pushed = true
hero:freeze()
boulder_small:go()
hero:go()
end
hero_facing_boulder_small = true
if boulder_small:get_movement() == nil
and game:get_command_effect("action") == nil
and game:get_custom_command_effect("action") == nil then
action_command_boulder_small = true
game:set_custom_command_effect("action", "grab")
end
end
end)
-- Remove the action icon when stopping facing the boulder.
function boulder_small:on_update()
if action_command_boulder_small and not hero_facing_boulder_small then
game:set_custom_command_effect("action", nil)
action_command_boulder_small = false
end
hero_facing_boulder_small = false
end
--Makes the boulder move.
function boulder_small:go()
local movement = sol.movement.create("path")
local direction = hero:get_direction()
sprite:set_animation("rolling")
sprite:set_direction(direction)
if direction == 0 then
sprite:set_animation("rolling")
sprite:set_direction(0)
movement:set_path({0, 0})
end
if direction == 1 then
sprite:set_animation("rolling")
sprite:set_direction(1)
movement:set_path({2, 2})
end
if direction == 2 then
sprite:set_animation("rolling")
sprite:set_direction(2)
movement:set_path({4, 4})
end
if direction == 3 then
sprite:set_animation("rolling")
sprite:set_direction(3)
movement:set_path({6, 6})
end
movement:start(boulder_small)
function movement:on_finished()
boulder_small:stop()
end
end
--When boulder stops.
function boulder_small:stop()
sprite = boulder_small:get_sprite()
sprite:set_animation("stopped")
hero:unfreeze()
end
-- Makes the hero move.
function hero:go()
local m = sol.movement.create("straight")
local direction = hero:get_direction()
if direction == 0 then
m:set_angle(0)
hero:set_animation("pushing")
end
if direction == 1 then
hero:set_animation("pushing")
m:set_angle(math.pi / 2)
end
if direction == 2 then
hero:set_animation("pushing")
m:set_angle(math.pi)
end
if direction == 3 then
hero:set_animation("pushing")
m:set_angle(3 * math.pi / 2)
end
m:start(hero)
end
Any help is welcome :)
Hi Dragon_noir,
the main problem in your code is probably that you set "pushed = true", but you forgot to change it to false later. I would not use the custom command effect "action" for pushing blocks, since that effect is usually used when the space bar is pressed, but this is just my opinion ;D. As an advice, the Lua API recommends to use a timer or other solution instead the on_update event since it is called too many times (if I understand correctly, this kind of methods could slow down the game a little bit, maybe on slow computers). Another advice, you are repeating some code a lot of times in some functions that is not necessary; this would improve some of your functions:
function boulder_small:go()
local movement = sol.movement.create("path")
local direction = hero:get_direction()
sprite:set_animation("rolling")
sprite:set_direction(direction)
movement:set_path({2*direction, 2*direction})
movement:start(boulder_small)
function movement:on_finished()
boulder_small:stop()
end
end
-- Makes the hero move.
function hero:go()
local m = sol.movement.create("straight")
local direction = hero:get_direction()
hero:set_animation("pushing")
m:set_angle(direction * math.pi / 2)
m:start(hero)
end
I hope this helps you to improve your script a bit (I have not tried it anyway).
function entity:on_created()
self:set_size(16, 16)
self:snap_to_grid()
self:set_modified_ground("ice") --is this useful?
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(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)
This is the code that I use, and it works fine.
Thanks guys ;)
The pushing problem is solved. I indeed forgot to set pushed as false again ;)
The reason i had the on_update() function in there was because I took example of Christopho's minecart.lua script and he used it there. I wanted the action button to appear to let players know you can interact with this item ( same as the blocks do)
Yes I am not very proud of this on_update() in the minecart script :(
I should replace it with a proper timer ^^