2024-10-10 03:20:53 +02:00

215 lines
7.6 KiB
Lua
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

-- 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("Youre 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)