215 lines
7.6 KiB
Lua
215 lines
7.6 KiB
Lua
-- Periodically check the player position is valid.
|
||
-- A position is invalid if the player:
|
||
-- * Is out of level bounds
|
||
-- * Is below a defined Y coordinate in the ship
|
||
-- * Has their head in a solid node
|
||
--
|
||
-- If the player is in an invalid place, then:
|
||
-- * In the menu ship: Teleport back to ship spawn. Happens quickly
|
||
-- * In a level: Fail level, teleport back to ship and taunt player
|
||
-- This happens only if the player was out of bounds for
|
||
-- a few consecutive seconds.
|
||
-- * Any other game state: Do nothing
|
||
--
|
||
-- Having the fly or noclip privs bypasses all resets.
|
||
--
|
||
--
|
||
-- This mod exists for multiple reasons:
|
||
-- 1) Work with all levels: Even with badly-designed
|
||
-- levels where the player can somehow escape or
|
||
-- spawns in solid rock, the game ensures the
|
||
-- level fails automatically.
|
||
-- 2) "Lose" a level: The player can legitimately make
|
||
-- themselves stuck by triggering skulls into walkable
|
||
-- mode, right where they stand. If you're surrounded
|
||
-- by activated cursed ckulls, it's impossible to win, so the
|
||
-- level must fail. But there is still a chance the player
|
||
-- can fix this by jumping or walking out if not
|
||
-- *completely* surrounded, which is why this mod uses
|
||
-- a damage system via lzr_damage.
|
||
|
||
local S = minetest.get_translator("lzr_fallout")
|
||
|
||
lzr_fallout = {}
|
||
|
||
-- When the level has just been started, ignore the fallout mechanism
|
||
-- for this many seconds
|
||
local GRACE_PERIOD = 3
|
||
|
||
-- In a level, players in an invalid position do not immediately
|
||
-- fail but first take "damage" as long they are in danger.
|
||
|
||
-- Count the time for how long the player is being crushed, out of bounds
|
||
-- or safe (=not in any danger).
|
||
local crush_timer = 0
|
||
local out_of_bounds_timer = 0
|
||
|
||
-- Counts time for how long we've been in a level.
|
||
local level_timer = 0
|
||
local level_ready = false
|
||
|
||
local reset_player = function(player, reset_type)
|
||
if reset_type == "water" then
|
||
lzr_messages.show_message(player, S("You’re sleeping with the fishes!"), 6.0, 0xFF0000)
|
||
lzr_levels.leave_level(true)
|
||
elseif reset_type == "out_of_bounds_ship" then
|
||
-- Intentionally no message
|
||
local spawn = vector.add(lzr_globals.MENU_SHIP_POS, lzr_globals.MENU_SHIP_PLAYER_RESPAWN_OFFSET)
|
||
player:set_pos(spawn)
|
||
elseif reset_type == "out_of_bounds" then
|
||
lzr_messages.show_message(player, S("Where yer thinks yar goin’, landlubber?"), 6.0, 0xFF0000)
|
||
lzr_levels.leave_level(true)
|
||
elseif reset_type == "skull_crush" then
|
||
-- The skulls laugh at you when you got stuck ;-)
|
||
minetest.sound_play({name="lzr_fallout_skull_laugh", gain=0.9}, {to_player=player:get_player_name()}, true)
|
||
lzr_messages.show_message(player, S("You were skull-crushed!"), 6.0, 0xFF0000)
|
||
lzr_levels.leave_level(true)
|
||
elseif reset_type == "crush" then
|
||
-- The skulls laugh at you when you got stuck ;-)
|
||
minetest.sound_play({name="lzr_fallout_skull_laugh", gain=0.9}, {to_player=player:get_player_name()}, true)
|
||
lzr_messages.show_message(player, S("You were between a rock and a hard place."), 6.0, 0xFF0000)
|
||
lzr_levels.leave_level(true)
|
||
else
|
||
minetest.log("error", "[lzr_fallout] reset_player called with unknown reset_type: "..tostring(reset_type))
|
||
end
|
||
crush_timer = 0
|
||
out_of_bounds_timer = 0
|
||
lzr_damage.reset_player_damage(player)
|
||
end
|
||
|
||
local step_timer = 0
|
||
minetest.register_globalstep(function(dtime)
|
||
local ddtime = dtime + step_timer
|
||
step_timer = step_timer + dtime
|
||
if step_timer < 1 then
|
||
return
|
||
end
|
||
step_timer = 0
|
||
|
||
local players = minetest.get_connected_players()
|
||
local state = lzr_gamestate.get_state()
|
||
if state == lzr_gamestate.EDITOR or state == lzr_gamestate.DEV or state == lzr_gamestate.SHUTDOWN then
|
||
return
|
||
end
|
||
for p=1, #players do
|
||
local player = players[p]
|
||
local privs = minetest.get_player_privs(player:get_player_name())
|
||
if not (privs["fly"] or privs["noclip"]) then
|
||
local pos = player:get_pos()
|
||
local reset_type
|
||
if state == lzr_gamestate.MENU then
|
||
-- If fallen out of ship, or too far away, reset immediately
|
||
if not lzr_menu.SHIP_SIZE then
|
||
-- ship size is not initialized yet, no fallout
|
||
return
|
||
end
|
||
-- How many nodes player can be away from ship
|
||
local SHIPBUF = 20
|
||
-- How many nodes player can be below from ship
|
||
local SHIPBUF_BELOW = 0.5
|
||
local ship = lzr_globals.MENU_SHIP_POS
|
||
local shipmax = vector.add(ship, lzr_menu.SHIP_SIZE)
|
||
-- Check coords
|
||
if pos.x < ship.x-SHIPBUF or pos.y < ship.y-SHIPBUF_BELOW or pos.z < ship.z-SHIPBUF or
|
||
pos.x > shipmax.x+SHIPBUF or pos.y > shipmax.y+SHIPBUF or pos.z > shipmax.z+SHIPBUF then
|
||
local node = minetest.get_node(pos)
|
||
reset_type = "out_of_bounds_ship"
|
||
reset_player(player, reset_type)
|
||
return
|
||
end
|
||
crush_timer = 0
|
||
out_of_bounds_timer = 0
|
||
level_timer = 0
|
||
lzr_damage.reset_player_damage(player)
|
||
elseif state == lzr_gamestate.LEVEL then
|
||
-- Don't do fallout stuff when level is not fully loaded yet
|
||
if not level_ready then
|
||
return
|
||
end
|
||
level_timer = level_timer + ddtime
|
||
-- Don't apply fallout logic when the level has just been started
|
||
if level_timer < GRACE_PERIOD then
|
||
return
|
||
end
|
||
-- Outside of level bounds in other directions
|
||
local minpos, maxpos = lzr_world.get_level_bounds()
|
||
if pos.x < minpos.x -1.5 or pos.x > maxpos.x + 1.5 or pos.z < minpos.z -1.5 or pos.z > maxpos.z + 1.5 or pos.y < minpos.y - 1.5 or pos.y > maxpos.y + 1.5 then
|
||
out_of_bounds_timer = out_of_bounds_timer + ddtime
|
||
reset_type = "out_of_bounds"
|
||
else
|
||
out_of_bounds_timer = 0
|
||
end
|
||
|
||
-- Player head got stuck in solid node
|
||
local node2 = minetest.get_node(vector.offset(pos, 0, 1, 0))
|
||
local def2 = minetest.registered_nodes[node2.name]
|
||
if def2 and def2.walkable and minetest.get_item_group(node2.name, "slab") == 0 and minetest.get_item_group(node2.name, "stair") == 0 and minetest.get_item_group(node2.name, "takable") == 0 then
|
||
if minetest.get_item_group(node2.name, "skull") ~= 0 then
|
||
reset_type = "skull_crush"
|
||
crush_timer = crush_timer + ddtime
|
||
elseif (not def2.collision_box and not def2.node_box) or (def2.collision_box and def2.collision_box.type == "regular") or (not def2.collision_box and def2.node_box and def2.node_box.type == "regular") then
|
||
reset_type = "crush"
|
||
crush_timer = crush_timer + ddtime
|
||
else
|
||
crush_timer = 0
|
||
end
|
||
else
|
||
crush_timer = 0
|
||
end
|
||
|
||
if reset_type == "out_of_bounds" then
|
||
local node = minetest.get_node(pos)
|
||
if minetest.get_item_group(node.name, "water") ~= 0 then
|
||
reset_type = "water"
|
||
end
|
||
end
|
||
|
||
local max_damage = lzr_damage.get_player_damage(player) == lzr_damage.MAX_DAMAGE
|
||
|
||
-- Being in an invalid place does not immediately trigger
|
||
-- a player reset, but first increases an internal damage
|
||
-- counter. Only if the damage exceeds a limit will
|
||
-- the player reset be triggered.
|
||
-- This gives the player a chance to get out of sticky situations.
|
||
if out_of_bounds_timer > 1 then
|
||
out_of_bounds_timer = 0
|
||
if max_damage then
|
||
reset_player(player, reset_type)
|
||
else
|
||
lzr_damage.damage_player(player)
|
||
end
|
||
elseif crush_timer > 1 then
|
||
crush_timer = 0
|
||
if max_damage then
|
||
reset_player(player, reset_type)
|
||
else
|
||
lzr_damage.damage_player(player)
|
||
end
|
||
end
|
||
else
|
||
crush_timer = 0
|
||
out_of_bounds_timer = 0
|
||
level_timer = 0
|
||
lzr_damage.reset_player_damage(player)
|
||
end
|
||
else
|
||
crush_timer = 0
|
||
out_of_bounds_timer = 0
|
||
level_timer = 0
|
||
lzr_damage.reset_player_damage(player)
|
||
end
|
||
end
|
||
end)
|
||
|
||
-- Reset state when a new level starts or is about to start
|
||
lzr_levels.register_on_level_start(function()
|
||
level_ready = true
|
||
crush_timer = 0
|
||
out_of_bounds_timer = 0
|
||
level_timer = 0
|
||
end)
|
||
lzr_levels.register_on_level_start_loading(function()
|
||
level_ready = false
|
||
end)
|
||
|