feat(gauntlet): replace Cleanser with Ghost powerup sticky bypass (v2.4.2)

- Remove entire Cleanser system (signal, vars, HUD, input, RPCs, bot AI)
- Ghost (Invisible Mode) now bypasses sticky tiles in Gauntlet
- Grant Ghost powerup every 2 missions instead of Cleanser charges
- Ghost tiles spawn naturally on Gauntlet arena (15% chance)
- Bots use Ghost powerup when boxed in by sticky tiles
- Players pushed into sticky while Ghost are not slowed
- Remove use_cleanser input action from project.godot
- Remove CleanserHBox UI from gauntlet_hud.tscn
- Bump version to 2.4.2
This commit is contained in:
2026-07-03 14:54:39 +08:00
parent 5ba7de3fd6
commit 19e7f619ab
10 changed files with 137 additions and 394 deletions
+8 -25
View File
@@ -155,15 +155,12 @@ func simple_move_to(grid_position: Vector2i) -> bool:
player.knock_tekton()
return false # Don't move into the tile, just knock
# If moving into a sticky cell: slow the player (unless cleanser active,
# which clears the cell instead). Sticky no longer hard-traps.
# If moving into a sticky cell: block movement unless player is in ghost
# mode (is_invisible), which lets them bypass sticky tiles in gauntlet.
if gm and gm.is_active and gm.is_sticky_cell(grid_position):
var pid = player.get("peer_id") if "peer_id" in player else -1
if pid != -1 and gm.is_cleanser_active(pid):
# Cleanser immunity: clear sticky cell, use one cell, don't slow
gm.clear_sticky_cell(grid_position)
gm.use_cleanser_cell(pid)
print("[Move] Cleanser cleared sticky cell at %s (%d cells left)" % [grid_position, gm.cleanser_cells_left.get(pid, 0)])
if player.get("is_invisible"):
# Ghost mode: walk through sticky tile freely
print("[Move] Ghost mode bypassed sticky cell at %s" % grid_position)
else:
print("[Move] Failed: Blocked by Gauntlet Sticky cell at %s" % grid_position)
return false
@@ -341,12 +338,9 @@ func try_push(target_pos: Vector2i, direction: Vector2i) -> bool:
if main_sticky and main_sticky.get("gauntlet_manager"):
var gm_sticky = main_sticky.gauntlet_manager
if gm_sticky.is_active and gm_sticky.is_sticky_cell(pushed_to_pos):
var push_pid = other_player.get("peer_id") if "peer_id" in other_player else -1
if push_pid != -1 and gm_sticky.is_cleanser_active(push_pid):
# Cleanser immunity: clear sticky cell, use one cell
gm_sticky.clear_sticky_cell(pushed_to_pos)
gm_sticky.use_cleanser_cell(push_pid)
print("[Move] Cleanser cleared push-into-sticky at %s" % pushed_to_pos)
if other_player.get("is_invisible"):
# Ghost mode: pushed player bypasses sticky
print("[Move] Ghost mode bypassed push-into-sticky at %s" % pushed_to_pos)
else:
print("[Move] Player pushed into sticky cell at %s — slowed" % pushed_to_pos)
if multiplayer.is_server() or other_player.is_multiplayer_authority():
@@ -406,17 +400,6 @@ func _on_movement_finished():
emit_signal("movement_finished")
else:
current_move_direction = Vector2i.ZERO
# Gauntlet (#072): a Cleanser ends early once the player rests on a safe
# cell. Gated on gm.is_active so other game modes are never affected.
var gm = null
var main_node = player.get_tree().root.get_node_or_null("Main")
if main_node and main_node.get("gauntlet_manager"):
gm = main_node.gauntlet_manager
if gm and gm.is_active and player.get("current_position") != null:
var mpid = player.get("peer_id") if "peer_id" in player else -1
if mpid != -1 and gm.is_cleanser_active(mpid):
if multiplayer.is_server() or player.is_multiplayer_authority():
gm.notify_movement_stopped(mpid, player.current_position)
emit_signal("movement_finished")
func move_to_clicked_position(grid_position: Vector2i) -> bool: