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
+19 -22
View File
@@ -210,9 +210,9 @@ func _run_ai_tick():
print("[BotController] Action Taken: Attack Pursuit")
return
# Priority 0.5: Gauntlet (#075) — burn Cleanser if boxed in
if await _try_activate_cleanser():
print("[BotController] Action Taken: Cleanser (trapped)")
# Priority 0.5: Gauntlet (#075) — use Ghost powerup if boxed in
if await _try_activate_ghost():
print("[BotController] Action Taken: Ghost (trapped)")
return
# Priority 1: Tekton Management (Grab Tekton if full boost, or spawn if carrying)
@@ -260,27 +260,24 @@ func _run_ai_tick():
return
# =============================================================================
# Gauntlet (#075) — Cleanser + Sticky Avoidance wiring
# Gauntlet (#075) — Ghost Powerup + Sticky Avoidance wiring
# =============================================================================
func _try_activate_cleanser() -> bool:
"""Activate Cleanser when the planner reports imminent danger.
func _try_activate_ghost() -> bool:
"""Activate Ghost powerup when the planner reports imminent danger.
Server-authoritative RPC; we only request it. Returns true if the request
was sent successfully (not a guarantee it landed on a sticky cell)."""
Uses the existing SpecialTilesManager to activate the held ghost powerup.
Returns true if activation was triggered."""
if not strategic_planner or not strategic_planner.is_gauntlet_mode():
return false
if not strategic_planner.should_activate_cleanser_now():
if not strategic_planner.should_activate_ghost_now():
return false
var gm = strategic_planner._get_gauntlet_manager()
if not gm:
var stm = actor.get_node_or_null("SpecialTilesManager")
if not stm:
return false
var pid = actor.get("peer_id") if "peer_id" in actor else actor.name.to_int()
if pid == null or pid < 0:
return false
if gm.has_method("rpc_activate_cleanser"):
gm.rpc_activate_cleanser(pid)
print("[BotController] %s requested Cleanser activation (trapped)" % actor.name)
if stm.has_method("activate_effect"):
stm.activate_effect(stm.SpecialEffect.INVISIBLE_MODE)
print("[BotController] %s activated Ghost powerup (trapped)" % actor.name)
return true
return false
@@ -292,14 +289,14 @@ func _on_step_onto_unsafe() -> bool:
var here = actor.current_position if "current_position" in actor else Vector2i(-1, -1)
if here == Vector2i(-1, -1):
return false
# Post-move guard: if we somehow landed on a sticky without cleanser active,
# burn Cleanser to clear ourselves out next tick.
# Post-move guard: if we somehow landed on a sticky without ghost active,
# burn Ghost powerup to phase through next tick.
if strategic_planner.is_gauntlet_mode() and strategic_planner._is_overlay_unsafe(here):
if not strategic_planner._is_bot_cleanser_active():
if not strategic_planner._is_bot_ghost_active():
var gm = strategic_planner._get_gauntlet_manager()
if gm and gm.has_method("is_sticky_cell") and gm.is_sticky_cell(here):
print("[BotController] %s stepped onto sticky at %sburning Cleanser" % [actor.name, here])
return _try_activate_cleanser()
print("[BotController] %s stepped onto sticky at %sactivating Ghost" % [actor.name, here])
return _try_activate_ghost()
return false
# =============================================================================