From d40a242cbc5656ada15021c77a956df6150bdff1 Mon Sep 17 00:00:00 2001 From: adtpdn Date: Thu, 2 Jul 2026 18:07:40 +0800 Subject: [PATCH] feat(gauntlet): shrink arena per phase 20x20 -> 18x18 -> 7x7; sticky cells block movement --- scripts/managers/gauntlet_manager.gd | 66 +++++++++++++++++++-- scripts/managers/player_movement_manager.gd | 5 +- 2 files changed, 63 insertions(+), 8 deletions(-) diff --git a/scripts/managers/gauntlet_manager.gd b/scripts/managers/gauntlet_manager.gd index 0878f77..ccd1ac7 100644 --- a/scripts/managers/gauntlet_manager.gd +++ b/scripts/managers/gauntlet_manager.gd @@ -340,9 +340,12 @@ func _start_phase(phase: Phase) -> void: var phase_name = _phase_to_string(phase) print("[Gauntlet] Phase changed to: ", phase_name) - if _can_rpc(): + if _can_rpc() and multiplayer.is_server(): rpc("sync_phase", int(phase), phase_name) + # Update phase explicitly with setup_arena + _shrink_arena() + emit_signal("phase_changed", int(phase), phase_name) func _phase_to_string(phase: Phase) -> String: @@ -361,6 +364,14 @@ func sync_phase(phase_index: int, phase_name: String) -> void: if not is_active: activate_client_side() current_phase = phase_index as Phase + if not multiplayer.is_server(): + var bounds = get_arena_bounds() + for x in range(ARENA_COLUMNS): + for z in range(ARENA_ROWS): + var pos = Vector2i(x, z) + if pos.x <= bounds.min or pos.x >= bounds.max or pos.y <= bounds.min or pos.y >= bounds.max: + if not sticky_cells.has(pos): + sticky_cells[pos] = true _update_hud_phase(phase_name) # ============================================================================= @@ -419,11 +430,12 @@ func _apply_arena_setup() -> void: continue # Boundary walls: perimeter (row 0, row 19, col 0, col 19) - if x == 0 or x == ARENA_COLUMNS - 1 or z == 0 or z == ARENA_ROWS - 1: + if pos.x <= 0 or pos.x >= 19 or pos.y <= 0 or pos.y >= 19: # Also make border walls visually walkable floors instead of red blocks gridmap.set_cell_item(Vector3i(x, 0, z), TILE_WALKABLE) gridmap.set_cell_item(Vector3i(x, 1, z), -1) - gridmap.set_cell_item(Vector3i(x, 2, z), -1) + gridmap.set_cell_item(Vector3i(x, 2, z), TILE_STICKY) + sticky_cells[pos] = true continue # Interior: walkable floor @@ -1065,9 +1077,12 @@ func is_cleansed_cell(pos: Vector2i) -> bool: func cell_state(pos: Vector2i) -> CellState: """Logical state of a playable cell (v2 ground-growth model).""" + var b = get_arena_bounds() + if pos.x <= b.min or pos.x >= b.max or pos.y <= b.min or pos.y >= b.max: + return CellState.STICKY if _is_npc_zone(pos) or _is_boundary(pos): return CellState.BLOCKED - if sticky_cells.has(pos): + if is_sticky_cell(pos): return CellState.STICKY if cleansed_cells.has(pos): return CellState.CLEANSED @@ -1091,8 +1106,37 @@ func _tick_cleansed_cells(delta: float) -> void: for pos in expired: cleansed_cells.erase(pos) +func get_arena_bounds() -> Dictionary: + match current_phase: + Phase.OPEN_ARENA: + return {"min": 0, "max": 19} # 20x20 + Phase.ROUTE_PRESSURE: + return {"min": 1, "max": 18} # 18x18 + Phase.SURVIVAL_ENDGAME: + return {"min": 6, "max": 12} # 7x7 + return {"min": 0, "max": 19} + +func _shrink_arena() -> void: + if not multiplayer.is_server(): return + var b = get_arena_bounds() + var new_sticky = [] + for x in range(ARENA_COLUMNS): + for z in range(ARENA_ROWS): + var pos = Vector2i(x, z) + if _is_npc_zone(pos) or _is_boundary(pos): + continue + if pos.x <= b.min or pos.x >= b.max or pos.y <= b.min or pos.y >= b.max: + if not sticky_cells.has(pos): + new_sticky.append(pos) + + if new_sticky.size() > 0: + if _can_rpc() and multiplayer.is_server(): + rpc("sync_growth_apply", new_sticky) + else: + sync_growth_apply(new_sticky) + func _is_boundary(pos: Vector2i) -> bool: - return pos.x == 0 or pos.x == ARENA_COLUMNS - 1 or pos.y == 0 or pos.y == ARENA_ROWS - 1 + return pos.x <= 0 or pos.x >= ARENA_COLUMNS - 1 or pos.y <= 0 or pos.y >= ARENA_ROWS - 1 # ============================================================================= # Coverage tracking (v2 target: 70-75%, down from v1's 80%) @@ -1103,12 +1147,15 @@ const COVERAGE_TARGET_MAX: float = 0.75 func playable_cell_count() -> int: """Number of cells that can ever become sticky (interior, minus NPC zone).""" + var b = get_arena_bounds() var count := 0 for x in range(ARENA_COLUMNS): for z in range(ARENA_ROWS): var pos := Vector2i(x, z) if _is_boundary(pos) or _is_npc_zone(pos): continue + if pos.x <= b.min or pos.x >= b.max or pos.y <= b.min or pos.y >= b.max: + continue count += 1 return count @@ -1132,6 +1179,9 @@ const FORCED_TRAP_WINDOW: float = 30.0 # final seconds where trapping is allowed func _is_cell_passable(pos: Vector2i, extra_sticky: Dictionary = {}) -> bool: """Can a player stand on / move through this cell, given a hypothetical sticky set?""" + var b = get_arena_bounds() + if pos.x <= b.min or pos.x >= b.max or pos.y <= b.min or pos.y >= b.max: + return false if _is_boundary(pos) or _is_npc_zone(pos): return false if sticky_cells.has(pos) or extra_sticky.has(pos): @@ -1390,15 +1440,21 @@ func _bubble_score_unfair_trap(pos: Vector2i) -> float: func _bubble_blast_cells(center: Vector2i) -> Array: """The 3x3 (radius 1) sticky cells a bubble at `center` would create, clipped to passable/playable cells.""" + var b = get_arena_bounds() var cells: Array = [] for dx in range(-BUBBLE_EXPLOSION_RADIUS, BUBBLE_EXPLOSION_RADIUS + 1): for dz in range(-BUBBLE_EXPLOSION_RADIUS, BUBBLE_EXPLOSION_RADIUS + 1): var c := center + Vector2i(dx, dz) if _is_boundary(c) or _is_npc_zone(c): continue + if c.x <= b.min or c.x >= b.max or c.y <= b.min or c.y >= b.max: + continue cells.append(c) return cells +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.""" for player in get_tree().get_nodes_in_group("Players"): diff --git a/scripts/managers/player_movement_manager.gd b/scripts/managers/player_movement_manager.gd index 3b91f7a..f78b875 100644 --- a/scripts/managers/player_movement_manager.gd +++ b/scripts/managers/player_movement_manager.gd @@ -165,9 +165,8 @@ func simple_move_to(grid_position: Vector2i) -> bool: 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)]) else: - print("[Move] Player stepping into sticky cell at %s — slowed" % grid_position) - if player.is_multiplayer_authority() or multiplayer.is_server(): - gm.apply_sticky_slow(player) + print("[Move] Failed: Blocked by Gauntlet Sticky cell at %s" % grid_position) + return false rotate_towards_target(grid_position)