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:
@@ -7,7 +7,7 @@ class_name GauntletManager
|
||||
signal phase_changed(phase_index: int, phase_name: String)
|
||||
signal growth_tick(cells: Array)
|
||||
signal player_trapped(player_id: int)
|
||||
signal cleanser_granted(player_id: int)
|
||||
signal ghost_granted(player_id: int)
|
||||
|
||||
# =============================================================================
|
||||
# Constants
|
||||
@@ -31,10 +31,9 @@ enum CellState {
|
||||
STICKY, # Covered in sticky candy, blocks + traps
|
||||
BUBBLE_GROWING, # Candy bubble growing, not yet exploded
|
||||
BLOCKED, # NPC zone or permanent obstacle
|
||||
CLEANSED, # Recently cleaned by Cleanser (temp protection)
|
||||
}
|
||||
|
||||
# Cells temporarily protected after a Cleanser pass (Vector2i -> time remaining).
|
||||
# Cells temporarily protected after Ghost-clearing (not used — kept for compat).
|
||||
var cleansed_cells: Dictionary = {}
|
||||
const CLEANSED_PROTECTION_TIME: float = 5.0
|
||||
|
||||
@@ -146,15 +145,10 @@ const SMACK_COOLDOWN: float = 8.0
|
||||
const SMACK_CHARGE_WINDOW: float = 3.0
|
||||
|
||||
# =============================================================================
|
||||
# Cleanser Tracking
|
||||
# Ghost Reward Tracking (replaces Cleanser)
|
||||
# =============================================================================
|
||||
|
||||
var player_mission_completions: Dictionary = {} # player_id → int
|
||||
var player_cleansers: Dictionary = {} # player_id → int (0 or 1)
|
||||
var cleanser_active: Dictionary = {} # player_id → true when immunity active
|
||||
var cleanser_cells_left: Dictionary = {} # player_id → int (cells remaining)
|
||||
const CLEANSER_MAX_CELLS: int = 5
|
||||
const CLEANSER_ACTIVATION_DELAY: float = 0.3
|
||||
|
||||
# =============================================================================
|
||||
# Trapped Players
|
||||
@@ -189,9 +183,6 @@ var pump_instance: Node3D = null
|
||||
# HUD
|
||||
var hud_layer: CanvasLayer
|
||||
var phase_label: Label
|
||||
var cleanser_label: Label
|
||||
var cleanser_icon: TextureRect
|
||||
var cleanser_count: int = 0
|
||||
var slowmo_label: Label
|
||||
var _gauntlet_hud_scene: PackedScene = preload("res://scenes/gauntlet_hud.tscn")
|
||||
|
||||
@@ -284,10 +275,6 @@ func _process(delta: float) -> void:
|
||||
elif not multiplayer.is_server():
|
||||
player.sync_modulate(Color.WHITE)
|
||||
|
||||
# Cleanser input (local player only)
|
||||
if Input.is_action_just_pressed("use_cleanser"):
|
||||
_try_use_cleanser()
|
||||
|
||||
# Slow-mo timer (all peers for visual consistency)
|
||||
if slowmo_active:
|
||||
slowmo_timer -= delta
|
||||
@@ -1016,56 +1003,6 @@ func _spawn_impact_particles(targets: Array) -> void:
|
||||
if particles and is_instance_valid(particles):
|
||||
particles.queue_free()
|
||||
|
||||
# =============================================================================
|
||||
func _spawn_cleanser_particles(pos: Vector2i) -> void:
|
||||
"""Spawn bright cleansing particles when sticky is cleared."""
|
||||
if not main_scene or not gridmap:
|
||||
return
|
||||
|
||||
var world_pos = Vector3(
|
||||
pos.x * gridmap.cell_size.x + gridmap.cell_size.x / 2.0,
|
||||
0.5,
|
||||
pos.y * gridmap.cell_size.z + gridmap.cell_size.z / 2.0
|
||||
)
|
||||
|
||||
var particles = GPUParticles3D.new()
|
||||
particles.emitting = true
|
||||
particles.one_shot = true
|
||||
particles.amount = 12
|
||||
particles.lifetime = 0.6
|
||||
particles.explosiveness = 0.9
|
||||
|
||||
var material = ParticleProcessMaterial.new()
|
||||
material.emission_shape = ParticleProcessMaterial.EMISSION_SHAPE_SPHERE
|
||||
material.emission_sphere_radius = 0.3
|
||||
material.direction = Vector3(0, 1, 0)
|
||||
material.spread = 180.0
|
||||
material.initial_velocity_min = 3.0
|
||||
material.initial_velocity_max = 5.0
|
||||
material.gravity = Vector3(0, -5.0, 0)
|
||||
material.scale_min = 0.05
|
||||
material.scale_max = 0.15
|
||||
|
||||
var mesh = SphereMesh.new()
|
||||
mesh.radius = 0.2
|
||||
mesh.height = 0.4
|
||||
var spatial_mat = StandardMaterial3D.new()
|
||||
spatial_mat.albedo_color = Color(0.2, 1.0, 1.0) # Cyan/Blue for cleanser
|
||||
spatial_mat.emission_enabled = true
|
||||
spatial_mat.emission = Color(0.2, 1.0, 1.0)
|
||||
spatial_mat.emission_energy_multiplier = 3.0
|
||||
mesh.material = spatial_mat
|
||||
particles.draw_pass_1 = mesh
|
||||
|
||||
particles.process_material = material
|
||||
particles.position = world_pos
|
||||
|
||||
main_scene.add_child(particles)
|
||||
|
||||
await get_tree().create_timer(1.2).timeout
|
||||
if particles and is_instance_valid(particles):
|
||||
particles.queue_free()
|
||||
|
||||
# =============================================================================
|
||||
# Sticky / Trap System
|
||||
|
||||
@@ -1085,7 +1022,7 @@ func cell_state(pos: Vector2i) -> CellState:
|
||||
if is_sticky_cell(pos):
|
||||
return CellState.STICKY
|
||||
if cleansed_cells.has(pos):
|
||||
return CellState.CLEANSED
|
||||
return CellState.BLOCKED # Protected from regrowth temporarily
|
||||
if telegraphed_cells.has(pos):
|
||||
return CellState.TELEGRAPHED
|
||||
if bubble_cells.has(pos):
|
||||
@@ -1383,11 +1320,11 @@ func _calculate_bubble_score(pos: Vector2i, player_cells: Array = []) -> float:
|
||||
return score
|
||||
|
||||
func _bubble_score_camping(pos: Vector2i) -> float:
|
||||
"""Reward targeting campers. +40 >5s, +60 >8s, +80 >10s-with-cleanser."""
|
||||
"""Reward targeting campers. +40 >5s, +60 >8s, +80 >10s-with-ghost."""
|
||||
var t := _camp_time_for_region(_region_of(pos))
|
||||
if t > 10.0:
|
||||
# Stronger only if a nearby player actually holds a cleanser.
|
||||
if _any_cleanser_holder_near(pos):
|
||||
# Stronger only if a nearby player is in ghost mode.
|
||||
if _any_ghost_player_near(pos):
|
||||
return 80.0
|
||||
return 60.0
|
||||
elif t > 8.0:
|
||||
@@ -1455,13 +1392,10 @@ func _bubble_blast_cells(center: Vector2i) -> Array:
|
||||
func _bubble_footprint(center: Vector2i) -> Array:
|
||||
return _bubble_blast_cells(center)
|
||||
|
||||
func _any_cleanser_holder_near(pos: Vector2i) -> bool:
|
||||
"""True if a player holding a Cleanser charge is within the camping region."""
|
||||
func _any_ghost_player_near(pos: Vector2i) -> bool:
|
||||
"""True if a player in ghost mode is within the camping region."""
|
||||
for player in get_tree().get_nodes_in_group("Players"):
|
||||
var pid = player.get("peer_id") if "peer_id" in player else -1
|
||||
if pid == -1:
|
||||
continue
|
||||
if player_cleansers.get(pid, 0) <= 0:
|
||||
if not player.get("is_invisible"):
|
||||
continue
|
||||
if "current_position" in player and player.current_position != null:
|
||||
if _region_of(player.current_position) == _region_of(pos):
|
||||
@@ -1557,7 +1491,7 @@ func _explode_bubble(center: Vector2i, cells: Array) -> void:
|
||||
if "current_position" in player and player.current_position != null:
|
||||
if blast.has(player.current_position):
|
||||
var pid = player.get("peer_id") if "peer_id" in player else -1
|
||||
if pid != -1 and is_cleanser_active(pid):
|
||||
if pid != -1 and player.get("is_invisible"):
|
||||
continue
|
||||
apply_sticky_slow(player)
|
||||
|
||||
@@ -1664,8 +1598,8 @@ func _check_all_players_trapped() -> void:
|
||||
var pos = player.current_position if player.get("current_position") else Vector2i(-1, -1)
|
||||
if is_sticky_cell(pos):
|
||||
var pid = player.get("peer_id") if "peer_id" in player else -1
|
||||
if pid != -1 and is_cleanser_active(pid):
|
||||
continue # cleansing players are immune to the slow
|
||||
if pid != -1 and player.get("is_invisible"):
|
||||
continue # ghost players are immune to the slow
|
||||
apply_sticky_slow(player)
|
||||
|
||||
func apply_sticky_slow(player: Node) -> void:
|
||||
@@ -1697,25 +1631,22 @@ func _trap_player(player: Node) -> void:
|
||||
NotificationManager.send_message(player, "Stuck in Candy!", NotificationManager.MessageType.WARNING)
|
||||
|
||||
func clear_sticky_cell(pos: Vector2i) -> void:
|
||||
"""Used by Cleanser power-up to remove a sticky cell."""
|
||||
"""Remove a sticky cell (used when ghost player walks through)."""
|
||||
if _can_rpc():
|
||||
if multiplayer.is_server():
|
||||
rpc("sync_clear_sticky_cell", pos)
|
||||
else:
|
||||
sync_clear_sticky_cell(pos) # Predictive local clear
|
||||
rpc("rpc_use_cleanser", pos)
|
||||
else:
|
||||
sync_clear_sticky_cell(pos)
|
||||
|
||||
@rpc("authority", "call_local", "reliable")
|
||||
func sync_clear_sticky_cell(pos: Vector2i) -> void:
|
||||
sticky_cells.erase(pos)
|
||||
mark_cleansed(pos) # temporary regrowth protection (v2)
|
||||
mark_cleansed(pos) # temporary regrowth protection
|
||||
if gridmap:
|
||||
gridmap.set_cell_item(Vector3i(pos.x, 2, pos.y), -1)
|
||||
|
||||
# Play VFX and SFX
|
||||
_spawn_cleanser_particles(pos)
|
||||
if SfxManager:
|
||||
SfxManager.play("pick_up_power_tile")
|
||||
|
||||
@@ -1723,142 +1654,6 @@ func sync_clear_sticky_cell(pos: Vector2i) -> void:
|
||||
if main_scene and main_scene.has_method("sync_grid_item"):
|
||||
main_scene.sync_grid_item(pos.x, 2, pos.y, -1)
|
||||
|
||||
func _try_use_cleanser() -> void:
|
||||
"""Local player attempts to activate Cleanser for 5-cell sticky immunity."""
|
||||
var local_pid = multiplayer.get_unique_id()
|
||||
var count = player_cleansers.get(local_pid, 0)
|
||||
if count <= 0:
|
||||
return
|
||||
|
||||
# Block activation during stun
|
||||
var all_players = get_tree().get_nodes_in_group("Players")
|
||||
var local_player = null
|
||||
for p in all_players:
|
||||
var pid = p.get("peer_id") if "peer_id" in p else p.name.to_int()
|
||||
if pid == local_pid:
|
||||
local_player = p
|
||||
break
|
||||
if not local_player:
|
||||
return
|
||||
if local_player.get("is_frozen") or local_player.get("is_stop_frozen"):
|
||||
return
|
||||
# 0.3s activation delay
|
||||
await get_tree().create_timer(CLEANSER_ACTIVATION_DELAY).timeout
|
||||
|
||||
# Re-validate after delay
|
||||
if not is_instance_valid(local_player):
|
||||
return
|
||||
if local_player.get("is_frozen") or local_player.get("is_stop_frozen"):
|
||||
return
|
||||
|
||||
# Consume cleanser from inventory (only if client, host relies on rpc)
|
||||
if not multiplayer.is_server():
|
||||
cleanser_active[local_pid] = true
|
||||
cleanser_cells_left[local_pid] = CLEANSER_MAX_CELLS
|
||||
player_cleansers[local_pid] = max(0, player_cleansers[local_pid] - 1)
|
||||
update_cleanser_ui(player_cleansers[local_pid])
|
||||
|
||||
NotificationManager.send_message(local_player, "Cleanser Used! (5 charges)", NotificationManager.MessageType.POWERUP)
|
||||
|
||||
# Sync to server/clients
|
||||
if not multiplayer.is_server() and _can_rpc():
|
||||
rpc("rpc_activate_cleanser", local_pid)
|
||||
elif multiplayer.is_server():
|
||||
# Call RPC logic directly for host (it will set active/cells_left/consume)
|
||||
rpc_activate_cleanser(local_pid)
|
||||
|
||||
@rpc("any_peer", "call_local", "reliable")
|
||||
func deactivate_cleanser(player_id: int) -> void:
|
||||
"""Deactivate cleanser immunity for a player."""
|
||||
cleanser_active.erase(player_id)
|
||||
cleanser_cells_left.erase(player_id)
|
||||
|
||||
func is_cleanser_active(player_id: int) -> bool:
|
||||
"""Check if a player has active cleanser immunity."""
|
||||
return cleanser_active.has(player_id)
|
||||
|
||||
func use_cleanser_cell(player_id: int) -> bool:
|
||||
"""Use one cleanser cell. Returns true if still active, false if exhausted."""
|
||||
if not cleanser_active.has(player_id):
|
||||
return false
|
||||
cleanser_cells_left[player_id] -= 1
|
||||
if cleanser_cells_left[player_id] <= 0:
|
||||
if _can_rpc():
|
||||
rpc("deactivate_cleanser", player_id)
|
||||
else:
|
||||
deactivate_cleanser(player_id)
|
||||
return false
|
||||
return true
|
||||
|
||||
func notify_movement_stopped(player_id: int, pos: Vector2i) -> void:
|
||||
"""Called from PlayerMovementManager when a move chain settles.
|
||||
Previously deactivated cleanser here, but now immunity persists
|
||||
until charges run out to allow repeated use across safe gaps."""
|
||||
pass
|
||||
|
||||
@rpc("any_peer", "call_local", "reliable")
|
||||
func rpc_activate_cleanser(pid: int) -> void:
|
||||
"""RPC for clients to activate cleanser on server."""
|
||||
if multiplayer.is_server():
|
||||
# Verify they actually have a cleanser charge (prevents spam/cheats)
|
||||
if player_cleansers.get(pid, 0) <= 0:
|
||||
return
|
||||
|
||||
# Always apply the state and AoE, since this is the server authority
|
||||
cleanser_active[pid] = true
|
||||
cleanser_cells_left[pid] = CLEANSER_MAX_CELLS
|
||||
player_cleansers[pid] = max(0, player_cleansers[pid] - 1)
|
||||
if _can_rpc():
|
||||
rpc("sync_cleanser_count", pid, player_cleansers[pid])
|
||||
|
||||
# NEW: Clear 3x3 area around player
|
||||
var all_players = get_tree().get_nodes_in_group("Players")
|
||||
var target_player = null
|
||||
for p in all_players:
|
||||
var target_pid = p.get("peer_id") if "peer_id" in p else p.name.to_int()
|
||||
if target_pid == pid:
|
||||
target_player = p
|
||||
break
|
||||
|
||||
if gridmap and is_instance_valid(target_player):
|
||||
var map_pos = gridmap.local_to_map(target_player.global_position)
|
||||
var center_pos = Vector2i(map_pos.x, map_pos.z)
|
||||
|
||||
# 3x3 neighborhood
|
||||
for dx in range(-1, 2):
|
||||
for dz in range(-1, 2):
|
||||
var check_pos = center_pos + Vector2i(dx, dz)
|
||||
if is_sticky_cell(check_pos):
|
||||
clear_sticky_cell(check_pos)
|
||||
|
||||
# Remove slow effect for any player in the cleansed area
|
||||
for p in all_players:
|
||||
if is_instance_valid(p) and p.has_method("remove_slow_effect"):
|
||||
if gridmap:
|
||||
var p_map_pos = gridmap.local_to_map(p.global_position)
|
||||
var p_cell_pos = Vector2i(p_map_pos.x, p_map_pos.z)
|
||||
if abs(p_cell_pos.x - center_pos.x) <= 1 and abs(p_cell_pos.y - center_pos.y) <= 1:
|
||||
if _can_rpc():
|
||||
p.rpc("remove_slow_effect")
|
||||
else:
|
||||
p.remove_slow_effect()
|
||||
|
||||
print("[Cleanser] Server cleared 3x3 area around %s for player %d" % [center_pos, pid])
|
||||
|
||||
@rpc("any_peer", "call_local", "reliable")
|
||||
func rpc_use_cleanser(pos: Vector2i) -> void:
|
||||
"""RPC for clients to clear a sticky cell via Cleanser."""
|
||||
if multiplayer.is_server():
|
||||
clear_sticky_cell(pos)
|
||||
|
||||
@rpc("any_peer", "call_local", "reliable")
|
||||
func rpc_consume_cleanser(pid: int) -> void:
|
||||
"""RPC for clients to report Cleanser consumption to server."""
|
||||
if multiplayer.is_server():
|
||||
player_cleansers[pid] = 0
|
||||
if _can_rpc():
|
||||
rpc("sync_cleanser_count", pid, 0)
|
||||
|
||||
@rpc("any_peer", "reliable")
|
||||
func rpc_trigger_slowmo() -> void:
|
||||
"""RPC for clients to request slow-mo from server."""
|
||||
@@ -1945,25 +1740,7 @@ func _setup_hud() -> void:
|
||||
hud_layer.visible = false
|
||||
add_child(hud_layer)
|
||||
phase_label = hud_layer.get_node("BottomContainer/VBoxContainer/PhaseLabel")
|
||||
cleanser_icon = hud_layer.get_node("BottomContainer/VBoxContainer/CleanserHBox/CleanserIcon")
|
||||
cleanser_label = hud_layer.get_node("BottomContainer/VBoxContainer/CleanserHBox/CleanserLabel")
|
||||
slowmo_label = hud_layer.get_node_or_null("TopContainer/SlowMoLabel")
|
||||
_generate_cleanser_icon()
|
||||
|
||||
func _generate_cleanser_icon() -> void:
|
||||
var icon_img = Image.create(16, 16, false, Image.FORMAT_RGBA8)
|
||||
icon_img.fill(Color(0.4, 0.9, 1.0))
|
||||
icon_img.blend_rect(icon_img, Rect2i(2, 2, 12, 12), Vector2i(1, 1))
|
||||
for x in range(16):
|
||||
icon_img.set_pixel(x, 0, Color(0.2, 0.6, 0.7))
|
||||
icon_img.set_pixel(x, 15, Color(0.2, 0.6, 0.7))
|
||||
for y in range(16):
|
||||
icon_img.set_pixel(0, y, Color(0.2, 0.6, 0.7))
|
||||
icon_img.set_pixel(15, y, Color(0.2, 0.6, 0.7))
|
||||
for i in range(4, 12):
|
||||
icon_img.set_pixel(i, 7, Color(1.0, 1.0, 1.0, 0.8))
|
||||
icon_img.set_pixel(7, i, Color(1.0, 1.0, 1.0, 0.8))
|
||||
cleanser_icon.texture = ImageTexture.create_from_image(icon_img)
|
||||
|
||||
func _update_hud_phase(phase_name: String) -> void:
|
||||
if phase_label:
|
||||
@@ -1982,20 +1759,6 @@ func _update_hud_phase(phase_name: String) -> void:
|
||||
# Animate phase label with bounce effect
|
||||
_animate_phase_label()
|
||||
|
||||
func update_cleanser_ui(count: int) -> void:
|
||||
cleanser_count = count
|
||||
if cleanser_label:
|
||||
cleanser_label.text = "[E] Cleanser (%d)" % count
|
||||
# Show/hide icon based on availability
|
||||
if cleanser_icon:
|
||||
cleanser_icon.visible = count > 0
|
||||
if count > 0:
|
||||
# Pulse animation when cleanser is available
|
||||
var tween = create_tween()
|
||||
tween.set_loops(2)
|
||||
tween.tween_property(cleanser_icon, "modulate", Color(1.5, 1.5, 1.5, 1), 0.3)
|
||||
tween.tween_property(cleanser_icon, "modulate", Color.WHITE, 0.3)
|
||||
|
||||
func _animate_phase_label() -> void:
|
||||
"""Animate phase label with bounce effect."""
|
||||
if not phase_label:
|
||||
@@ -2020,7 +1783,7 @@ func _animate_phase_label() -> void:
|
||||
# =============================================================================
|
||||
|
||||
func _on_goal_count_updated(peer_id: int, count: int) -> void:
|
||||
"""Called when a player completes a goal cycle. Grant cleanser every 2 missions."""
|
||||
"""Called when a player completes a goal cycle. Grant ghost powerup every 2 missions."""
|
||||
if not multiplayer.is_server():
|
||||
return
|
||||
|
||||
@@ -2029,34 +1792,29 @@ func _on_goal_count_updated(peer_id: int, count: int) -> void:
|
||||
player_mission_completions[peer_id] = 0
|
||||
player_mission_completions[peer_id] += 1
|
||||
|
||||
# Grant cleanser every 2 missions
|
||||
# Grant ghost powerup every 2 missions
|
||||
var completions = player_mission_completions[peer_id]
|
||||
if completions % 2 == 0:
|
||||
if not player_cleansers.has(peer_id):
|
||||
player_cleansers[peer_id] = 0
|
||||
|
||||
# Allow stacking cleanser charges instead of capping at 1
|
||||
player_cleansers[peer_id] += 1
|
||||
emit_signal("cleanser_granted", peer_id)
|
||||
print("[Gauntlet] Player %d granted Cleanser (Total: %d) (mission %d)" % [peer_id, player_cleansers[peer_id], completions])
|
||||
|
||||
# Sync cleanser count to HUD
|
||||
rpc("sync_cleanser_count", peer_id, player_cleansers.get(peer_id, 0))
|
||||
_grant_ghost_powerup(peer_id)
|
||||
|
||||
func _grant_ghost_powerup(peer_id: int) -> void:
|
||||
"""Grant the ghost (invisible mode) powerup to a player."""
|
||||
var all_players = get_tree().get_nodes_in_group("Players")
|
||||
for p in all_players:
|
||||
var pid = p.get("peer_id") if "peer_id" in p else p.name.to_int()
|
||||
if pid == peer_id:
|
||||
var stm = p.get_node_or_null("SpecialTilesManager")
|
||||
if stm and stm.has_method("add_powerup_from_item"):
|
||||
stm.add_powerup_from_item(14) # 14 = Ghost / INVISIBLE_MODE
|
||||
emit_signal("ghost_granted", peer_id)
|
||||
print("[Gauntlet] Player %d granted Ghost powerup (mission %d)" % [peer_id, player_mission_completions[peer_id]])
|
||||
NotificationManager.send_message(p, "Ghost Power Earned!", NotificationManager.MessageType.POWERUP)
|
||||
break
|
||||
|
||||
func _on_score_updated(peer_id: int, new_score: int) -> void:
|
||||
"""Called when a player's score is updated."""
|
||||
pass # Score sync handled by GoalsCycleManager
|
||||
|
||||
|
||||
|
||||
@rpc("authority", "call_local", "reliable")
|
||||
func sync_cleanser_count(peer_id: int, count: int) -> void:
|
||||
"""Sync cleanser count to HUD for specific player."""
|
||||
# Update local player's cleanser UI
|
||||
var local_pid = multiplayer.get_unique_id()
|
||||
if peer_id == local_pid:
|
||||
update_cleanser_ui(count)
|
||||
|
||||
# =============================================================================
|
||||
# Utility
|
||||
# =============================================================================
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -547,9 +547,11 @@ func spawn_powerups_around(center: Vector2i, force_powerups: bool = true, only_c
|
||||
# Spawn ONLY common tiles (7-10) in Stop n Go mode (User Request)
|
||||
item_id = rng.randi_range(7, 10)
|
||||
elif LobbyManager.is_game_mode(GameMode.Mode.GAUNTLET):
|
||||
# Gauntlet mode: No power-up tile spawns from world.
|
||||
# Only common tiles (7-10) spawn; Smack/Cleanser are handled separately.
|
||||
item_id = rng.randi_range(7, 10)
|
||||
# Gauntlet mode: mostly common tiles, but ghost (14) can spawn too.
|
||||
if rng.randf() < 0.85:
|
||||
item_id = rng.randi_range(7, 10)
|
||||
else:
|
||||
item_id = 14 # Ghost powerup only
|
||||
else:
|
||||
# Other modes: 80% Chance for Common Tile (7-10), 20% for PowerUp
|
||||
if rng.randf() < 0.8:
|
||||
|
||||
Reference in New Issue
Block a user