Rewrite fallout algorithm to be more stable
This commit is contained in:
parent
bdb3d9a65a
commit
55fe5bd42f
@ -162,6 +162,9 @@ All levels by Wuzzy.
|
||||
- `lzr_menu_speaker_turn_off.ogg`:
|
||||
- by mincedbeats <https://freesound.org/people/mincedbeats/sounds/593996/>
|
||||
- License: CC0
|
||||
- `lzr_fallout_damage.ogg`:
|
||||
- by newagesoup <https://freesound.org/people/newagesoup/sounds/348242/>
|
||||
- License: CC0
|
||||
|
||||
- All other sounds come from Minetest Game (see license of Minetest Game 5.4.1 for details)
|
||||
|
||||
|
@ -1,89 +1,187 @@
|
||||
-- Periodically check the player position if it is in the valid range.
|
||||
-- For example, if the player "fell out" of the level.
|
||||
-- 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 out of range, then:
|
||||
-- * In the menu ship: Teleport back to ship spawn
|
||||
-- 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
|
||||
--
|
||||
-- Additionally, it is checked whether the player head is stuck
|
||||
-- in a solid node.
|
||||
--
|
||||
-- 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 the damage
|
||||
-- system has been added (see below)
|
||||
|
||||
local S = minetest.get_translator("lzr_fallout")
|
||||
|
||||
local CRUSH_TIMER = 3
|
||||
local FALLOUT_TIMER = 2
|
||||
-- If the player is in the ship, fallout occurs when below this Y
|
||||
local SHIP_FALLOUT_Y = lzr_globals.MENU_SHIP_POS.y + lzr_globals.MENU_SHIP_FALLOUT_Y_OFFSET
|
||||
|
||||
local timer = 0
|
||||
-- In a level, players in an invalid position do not immediately
|
||||
-- fail but first take "damage" as long they are in danger.
|
||||
-- Only once PLAYER_MAX_DAMAGE was surpassed, the failure is triggered.
|
||||
|
||||
-- Note this game does NOT use regular damage and health, as defined
|
||||
-- by Minetest, it's disabled. The "damage" here applies only to this mod!
|
||||
local PLAYER_MAX_DAMAGE = 3
|
||||
|
||||
-- Player damage increases roughly every second the player is in danger,
|
||||
-- and decreases while out of danger
|
||||
local player_damage = 0
|
||||
|
||||
-- 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
|
||||
local safe_timer = 0
|
||||
|
||||
local reset_player = function(player, reset_type)
|
||||
if reset_type == "fallout_ship" then
|
||||
local spawn = vector.add(lzr_globals.MENU_SHIP_POS, lzr_globals.MENU_SHIP_PLAYER_SPAWN_OFFSET)
|
||||
player:set_pos(spawn)
|
||||
elseif reset_type == "fallout" then
|
||||
lzr_messages.show_message(player, S("You’re sleeping with the fishes!"), 6.0, 0xFF0000)
|
||||
lzr_levels.leave_level()
|
||||
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()
|
||||
elseif reset_type == "skull_crush" then
|
||||
lzr_messages.show_message(player, S("You were skull-crushed!"), 6.0, 0xFF0000)
|
||||
lzr_levels.leave_level()
|
||||
elseif reset_type == "crush" then
|
||||
lzr_messages.show_message(player, S("You were between a rock and a hard place."), 6.0, 0xFF0000)
|
||||
lzr_levels.leave_level()
|
||||
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
|
||||
player_damage = 0
|
||||
safe_timer = 0
|
||||
end
|
||||
|
||||
local step_timer = 0
|
||||
minetest.register_globalstep(function(dtime)
|
||||
timer = timer + dtime
|
||||
if timer < FALLOUT_TIMER then
|
||||
local ddtime = dtime + step_timer
|
||||
step_timer = step_timer + dtime
|
||||
if step_timer < 1 then
|
||||
return
|
||||
end
|
||||
timer = 0
|
||||
step_timer = 0
|
||||
|
||||
local players = minetest.get_connected_players()
|
||||
local state = lzr_gamestate.get_state()
|
||||
if state == lzr_gamestate.EDITOR or state == lzr_gamestate.SHUTDOWN or state == lzr_gamestate.LEVEL_COMPLETE 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, reset immediately
|
||||
if pos.y < SHIP_FALLOUT_Y then
|
||||
local spawn = vector.add(lzr_globals.MENU_SHIP_POS, lzr_globals.MENU_SHIP_PLAYER_SPAWN_OFFSET)
|
||||
player:set_pos(spawn)
|
||||
reset_type = "fallout_ship"
|
||||
reset_player(player, reset_type)
|
||||
end
|
||||
crush_timer = 0
|
||||
out_of_bounds_timer = 0
|
||||
player_damage = 0
|
||||
safe_timer = 0
|
||||
elseif state == lzr_gamestate.LEVEL then
|
||||
-- Below allowed minimum Y
|
||||
if pos.y < lzr_globals.LEVEL_FALLOUT_Y then
|
||||
lzr_messages.show_message(player, S("You are sleeping with the fishes!"), 6.0, 0xFF0000)
|
||||
lzr_levels.leave_level()
|
||||
crush_timer = 0
|
||||
return
|
||||
out_of_bounds_timer = out_of_bounds_timer + ddtime
|
||||
reset_type = "fallout"
|
||||
else
|
||||
-- Outside of level bounds in other directions
|
||||
local minpos = lzr_globals.LEVEL_POS
|
||||
local maxpos = vector.add(lzr_globals.LEVEL_POS, lzr_levels.get_level_size())
|
||||
if pos.x < minpos.x -0.5 or pos.x > maxpos.x + 0.5 or pos.z < minpos.z -0.5 or pos.z > maxpos.z + 0.5 or pos.y > maxpos.y + 0.5 then
|
||||
lzr_messages.show_message(player, S("Where yer thinks yar goin’, landlubber?"), 6.0, 0xFF0000)
|
||||
lzr_levels.leave_level()
|
||||
crush_timer = 0
|
||||
return
|
||||
out_of_bounds_timer = out_of_bounds_timer + ddtime
|
||||
reset_type = "out_of_bounds"
|
||||
else
|
||||
-- Player head got stuck in solid node for a few seconds
|
||||
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
|
||||
-- Stuck in solid skull: No further delay
|
||||
if minetest.get_item_group(node2.name, "skull") ~= 0 then
|
||||
lzr_messages.show_message(player, S("You were skull-crushed!"), 6.0, 0xFF0000)
|
||||
lzr_levels.leave_level()
|
||||
crush_timer = 0
|
||||
return
|
||||
end
|
||||
-- Stuck in other solid node: After a short time
|
||||
-- Only full-size nodes count
|
||||
if (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
|
||||
crush_timer = crush_timer + FALLOUT_TIMER + dtime
|
||||
if crush_timer > CRUSH_TIMER then
|
||||
lzr_messages.show_message(player, S("You were between a rock and a hard place."), 6.0, 0xFF0000)
|
||||
lzr_levels.leave_level()
|
||||
crush_timer = 0
|
||||
return
|
||||
end
|
||||
end
|
||||
else
|
||||
crush_timer = 0
|
||||
end
|
||||
out_of_bounds_timer = 0
|
||||
end
|
||||
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
|
||||
|
||||
local damage_player = function(player, reset_type)
|
||||
player_damage = player_damage + 1
|
||||
local gain
|
||||
if player_damage >= 3 then
|
||||
gain = 1
|
||||
elseif player_damage >= 2 then
|
||||
gain = 0.7
|
||||
elseif player_damage >= 1 then
|
||||
gain = 0.4
|
||||
end
|
||||
minetest.sound_play({name="lzr_fallout_damage", gain=gain}, {to_player=player:get_player_name()}, true)
|
||||
if player_damage > PLAYER_MAX_DAMAGE then
|
||||
reset_player(player, reset_type)
|
||||
end
|
||||
end
|
||||
|
||||
-- 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
|
||||
safe_timer = 0
|
||||
damage_player(player, reset_type)
|
||||
elseif crush_timer > 1 then
|
||||
crush_timer = 0
|
||||
safe_timer = 0
|
||||
damage_player(player, reset_type)
|
||||
elseif out_of_bounds_timer == 0 and crush_timer == 0 then
|
||||
safe_timer = safe_timer + ddtime
|
||||
if safe_timer > 1 then
|
||||
-- Reduce damage when safe
|
||||
player_damage = math.max(0, player_damage - 1)
|
||||
safe_timer = 0
|
||||
end
|
||||
end
|
||||
else
|
||||
crush_timer = 0
|
||||
out_of_bounds_timer = 0
|
||||
safe_timer = 0
|
||||
player_damage = 0
|
||||
end
|
||||
end
|
||||
end
|
||||
|
BIN
mods/lzr_fallout/sounds/lzr_fallout_damage.ogg
Normal file
BIN
mods/lzr_fallout/sounds/lzr_fallout_damage.ogg
Normal file
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user