Version bump to 2.3.6. New game mode features 20×20 arena with central cannon obstacle, three escalating phases (Open Arena, Route Pressure, Survival), and collectible tiles (Hearts, Diamonds, Stars, Coins) with pattern-matching missions. Players dodge candy volleys while completing collection goals. Updated export paths and version strings across all platforms (Windows, Android, Web, Linux).
16 KiB
Candy Cannon Survival (Gauntlet) — Technical Implementation Plan
1. Feasibility Summary
Verdict: Feasible. The existing codebase provides ~70% of the infrastructure needed. The game mode architecture is modular — each mode has its own manager (StopNGoManager, PortalModeManager) that handles arena setup, HUD, phase logic, and win conditions. A new GauntletManager follows this identical pattern.
Reuse Breakdown
| GDD Feature | Existing System | Reuse Level | New Work |
|---|---|---|---|
| Game Mode registration | GameMode.gd enum + LobbyManager |
Direct | Add enum entry + strings |
| 20×20 Arena setup | StopNGoManager._setup_arena() pattern |
Heavy | Custom layout, same GridMap API |
| Tile collection / scoring | GoalsCycleManager |
Direct | Reuse goal completion + scoring |
| Mission system (goals) | GoalManager + goals_cycle_manager.gd |
Direct | Same 3×3 pattern matching |
| Timed match (3 min) | GoalsCycleManager.start_match() |
Direct | Pass 180s duration |
| Player movement | PlayerMovementManager |
Direct | No changes |
| Powerup system | SpecialTilesManager |
Partial | Cleanser is a new powerup type |
| Attack/Push mechanic | PlayerMovementManager.try_push() |
Adapt | Smack = modified push with new rules |
| NPC (Candy Cannon) | tekton.gd + TektonController |
Pattern | New NPC, reuses projectile/animation patterns |
| Sticky cells | StopNGoManager safe zone overlay (Layer 2) |
Pattern | New tile type, same GridMap layer approach |
| Telegraph VFX | VFXManager / animation.gd |
Pattern | New animations, same system |
| HUD | StopNGoManager._setup_hud() pattern |
Direct | Mode-specific labels |
| Network sync | RPC patterns throughout codebase | Direct | Same rpc() / sync_* patterns |
| Lobby settings | LobbyManager signal/sync pattern |
Direct | Add gauntlet-specific settings |
| Bot AI | BotController + BotStrategicPlanner |
Adapt | New strategy for cannon avoidance |
2. Architecture Overview
main.gd
├── _init_managers() ← Add GauntletManager instantiation (same as StopNGoManager pattern)
├── _setup_host_game() ← Add gauntlet arena setup branch
├── _start_game() ← Add gauntlet start_game_mode() call
│
GauntletManager (NEW)
├── _setup_arena() ← 20×20 grid, center 3×3 NPC zone
├── _setup_hud() ← Mission label, cleanser indicator
├── start_game_mode() ← Start cannon timer, spawn tiles
├── _process() ← Cannon volley timer, phase escalation
├── CandyCannonController ← Targeting logic, volley fire
├── StickyCell system ← Layer 2 overlay, trap logic
├── Cleanser system ← New powerup unlocked via missions
├── Smack system ← Modified push with charge/cooldown
└── Win condition ← Highest score at timer end
3. File-by-File Implementation
3.1 Game Mode Registration
scripts/game_mode.gd
enum Mode {
FREEMODE = 0,
STOP_N_GO = 1,
TEKTON_DOORS = 2,
GAUNTLET = 3 # NEW
}
# Add to from_string(), mode_to_string(), get_all_modes(), is_restricted()
scripts/managers/lobby_manager.gd
- Add
"Candy Cannon Survival"toavailable_game_modes - Add
_update_available_areas()entry →"Gauntlet Arena" - Add gauntlet-specific lobby settings (mirroring Stop N Go pattern):
gauntlet_round_duration: int = 180gauntlet_cannon_interval: int = 5gauntlet_volley_size: int = 5- Corresponding
set_gauntlet_*(),sync_gauntlet_*()RPCs - Corresponding signals
3.2 Core Manager — gauntlet_manager.gd (NEW)
Location: scripts/managers/gauntlet_manager.gd
Pattern source: StopNGoManager + PortalModeManager
class_name GauntletManager
extends Node
# Signals
signal phase_changed(phase_index: int)
signal cannon_fired(targets: Array)
signal player_trapped(player_id: int)
signal cleanser_granted(player_id: int)
# Constants
const ARENA_SIZE = 20
const NPC_SIZE = 3
const NPC_CENTER = Vector2i(9, 9) # Center of 20×20
const TILE_STICKY = 17 # New MeshLibrary item ID
const TILE_WALKABLE = 0
const TILE_OBSTACLE = 4
# Phase timing
enum Phase { OPEN_ARENA, ROUTE_PRESSURE, SURVIVAL_ENDGAME }
var current_phase: Phase = Phase.OPEN_ARENA
var elapsed_time: float = 0.0
# Cannon state
var cannon_timer: float = 0.0
var cannon_interval: float = 5.0
var volley_size: int = 5
var sticky_cells: Dictionary = {} # Vector2i → true
var last_targeted_player_id: int = -1
# Smack state (per-player)
var smack_cooldowns: Dictionary = {} # player_id → float (time remaining)
var smack_charged: Dictionary = {} # player_id → float (charge window remaining)
# Cleanser tracking
var player_mission_completions: Dictionary = {} # player_id → int
var player_cleansers: Dictionary = {} # player_id → int (0 or 1)
# Trapped players
var trapped_players: Dictionary = {} # player_id → true
Key methods (mapped to existing patterns):
| Method | Pattern Source | Purpose |
|---|---|---|
_setup_arena() |
StopNGoManager._setup_arena() |
20×20 grid, center 3×3 NPC block, walkable floor |
_setup_hud() |
StopNGoManager._setup_hud() |
Mission label, cleanser indicator |
start_game_mode() |
StopNGoManager.start_game_mode() |
Initialize cannon, spawn tiles, activate HUD |
_process(delta) |
StopNGoManager._process() |
Tick cannon timer, fire volleys, update phase |
_fire_volley() |
NEW (uses tekton.gd projectile pattern) |
Select targets, telegraph, apply sticky |
_apply_sticky(pos) |
StopNGoManager._spawn_dynamic_safe_zone() (Layer 2 overlay) |
Set GridMap Layer 2 to TILE_STICKY |
_check_player_trapped(player) |
StopNGoManager._is_in_safe_zone() (inverted) |
Check if player is on sticky cell |
check_win_condition() |
StopNGoManager.check_win_condition() |
Highest score at match end |
sync_phase() RPC |
StopNGoManager.sync_phase() |
Broadcast phase to clients |
sync_sticky_cells() RPC |
main.rpc("sync_grid_item") |
Sync sticky cell state |
3.3 Candy Cannon NPC — candy_cannon_controller.gd (NEW)
Location: scripts/controllers/candy_cannon_controller.gd
Pattern source: TektonController + tekton.gd projectile system
class_name CandyCannonController
extends Node
var gauntlet_manager: GauntletManager
var npc_center: Vector2i
var gridmap: Node
# Targeting weights per phase
var phase_weights: Array = [
# Phase 0 (Open Arena): 1×1=60%, 1×2=40%, 2×2=0%
{"1x1": 0.6, "1x2": 0.4, "2x2": 0.0},
# Phase 1 (Route Pressure): 1×1=30%, 1×2=55%, 2×2=15%
{"1x1": 0.3, "1x2": 0.55, "2x2": 0.15},
# Phase 2 (Survival): 1×1=15%, 1×2=55%, 2×2=30%
{"1x1": 0.15, "1x2": 0.55, "2x2": 0.30}
]
Targeting logic reuses the _is_position_valid() and get_neighbors() from EnhancedGridMap, and get_nodes_in_group("Players") for player-proximity targeting.
Projectile visuals reuse tekton.gd's spawn_projectile_rpc() pattern (arc tween from cannon → target cell).
3.4 Sticky Cell System
Approach: Use GridMap Layer 2 (same as StopNGoManager safe zone overlay and SpecialTilesManager freeze overlay).
New MeshLibrary item: TILE_STICKY = 17 — Pink/candy-colored semi-transparent panel (same approach as TILE_SAFE = 2).
| Feature | Implementation |
|---|---|
| Visual | Layer 2 overlay with transparent candy-pink mesh |
| Movement block | PlayerMovementManager.simple_move_to() — add sticky check alongside wall check |
| Trap on step | GauntletManager._check_player_on_sticky() in _process() |
| Trap on push | PlayerMovementManager.try_push() — check landing cell for sticky |
| Cleanser pass-through | Similar to is_invisible wall bypass — temporary flag |
Network sync: Use existing main.rpc("sync_grid_item", x, 2, z, TILE_STICKY) — identical to how safe zones and freeze overlays sync.
3.5 Telegraph System
Pattern source: StopNGoManager's sync_all_safe_zones_vfx() + _animate_safe_zone_appear()
- Server selects target cells
rpc("sync_telegraph", targets)— all clients show pink glow- 1-second delay (Timer)
rpc("sync_impact", targets)— apply sticky, VFX, screen shake
Visual approach:
- Reuse Layer 2 overlay with a temporary "warning" tile ID (e.g.,
TILE_TELEGRAPH = 18) - Animate alpha 0 → 1 over 0.8s (same
_animate_safe_zone_appear()tween pattern) - On impact: replace with
TILE_STICKY, playscreen_shake_managerviaplayer.rpc("trigger_screen_shake", "medium")
3.6 Smack Mechanic
Pattern source: PowerUpManager.use_special_effect() + PlayerMovementManager.try_push()
The smack mechanic is a reskin of the existing Attack Mode push, with modifications:
| Property | Current Attack Mode | Gauntlet Smack |
|---|---|---|
| Charge source | PowerUpManager.current_boost >= 100 |
8s cooldown timer (auto-refill) |
| Activation | Toggle is_attack_mode |
3s charged window (pink model) |
| Push distance | 3 cells backward (X=-1) | 3 cells in push direction |
| Stagger | 1.5s apply_stagger() |
1.0s stun |
| Sticky landing | N/A | Trapped on first sticky cell in path |
| Clash | N/A | Both stunned, no push, bars consumed |
Implementation in GauntletManager:
- New per-player smack state (cooldown, charged flag)
- Override or extend
PlayerMovementManager.try_push()behavior when in gauntlet mode - Sticky landing check: iterate push path, stop at first sticky cell → call
trap_player() - Clash detection: if two players activate smack within 0.5s of each other and are in range
3.7 Cleanser Power-Up
Pattern source: SpecialTilesManager.inventory system
| Property | Implementation |
|---|---|
| Unlock trigger | GoalsCycleManager.goal_count_updated signal — grant when count % 2 == 0 |
| Storage | GauntletManager.player_cleansers[peer_id] = 1 |
| Activation | New input action or existing powerup key |
| Effect | For 5 cells of movement, ignore sticky checks + clear sticky overlay on traversed cells |
| Sync | rpc("sync_cleanser_state", peer_id, count) |
| Clear sticky | main.rpc("sync_grid_item", x, 2, z, -1) — same as safe zone clear |
3.8 Candy Cannon NPC Scene — candy_cannon.tscn (NEW)
Pattern source: tekton.tscn + static_tekton_stand.tscn
- 3×3 footprint centered at
(9, 9)in 20×20 grid - Static body (non-movable, non-interactable)
- Animated mesh (cannon rotation, firing animation)
- No grab/throw/knock interactions (like
is_static_turret = true)
3.9 Arena Scene — gauntlet.tscn (NEW) or gauntlet.scn
Location: scenes/arena/gauntlet.tscn
Pattern source: scenes/arena/freemode.tscn, scenes/arena/stop_n_go.scn
- 3D environment for the gauntlet arena
- Referenced in
main.gd._apply_arena_background()under"Gauntlet Arena"match case
3.10 Integration Points in main.gd
Following the exact pattern of StopNGoManager / PortalModeManager:
# _init_managers() — Add after portal_mode_manager block:
if LobbyManager.game_mode == "Candy Cannon Survival":
gauntlet_manager = load("res://scripts/managers/gauntlet_manager.gd").new()
gauntlet_manager.name = "GauntletManager"
add_child(gauntlet_manager)
gauntlet_manager.initialize(self, $EnhancedGridMap)
# _setup_host_game() — Add arena setup branch:
elif LobbyManager.game_mode == "Candy Cannon Survival" and gauntlet_manager:
gauntlet_manager._setup_arena()
# _start_game() — Add game mode start:
elif LobbyManager.game_mode == "Candy Cannon Survival":
if gauntlet_manager:
gauntlet_manager.start_game_mode()
if goals_cycle_manager:
var match_duration = LobbyManager.get_match_duration()
goals_cycle_manager.start_match(float(match_duration))
4. New Files Summary
| File | Type | Purpose |
|---|---|---|
scripts/managers/gauntlet_manager.gd |
Script | Core mode logic, phases, sticky cells, cleanser, smack |
scripts/controllers/candy_cannon_controller.gd |
Script | Cannon targeting, volley fire, telegraph |
scenes/arena/gauntlet.tscn |
Scene | 3D arena environment |
scenes/candy_cannon.tscn |
Scene | Candy Cannon NPC (3×3, static) |
5. Modified Files Summary
| File | Changes |
|---|---|
scripts/game_mode.gd |
Add GAUNTLET = 3 enum, string mappings |
scripts/managers/lobby_manager.gd |
Add mode to available list, gauntlet settings, area mapping |
scenes/main.gd |
Add gauntlet_manager init, arena setup branch, start branch |
scripts/managers/player_movement_manager.gd |
Add sticky cell check in simple_move_to(), sticky landing in push |
scripts/managers/goals_cycle_manager.gd |
Cleanser grant on every 2nd goal completion (gauntlet mode only) |
scripts/managers/special_tiles_manager.gd |
Restrict certain powerups in gauntlet mode (like Stop N Go restrictions) |
MeshLibrary .tres |
Add TILE_STICKY (17) and TILE_TELEGRAPH (18) mesh items |
6. Anti-Unfairness Implementation
# In CandyCannonController._select_targets():
func _select_targets(count: int) -> Array[Vector2i]:
var targets: Array[Vector2i] = []
var players = get_tree().get_nodes_in_group("Players")
for i in range(count):
var roll = randf()
var target: Vector2i
if roll < 0.60:
# Near a player (not same as last targeted)
target = _get_near_player_target(players)
elif roll < 0.85:
# Route-blocking (pathfinding bottleneck)
target = _get_route_blocking_target()
elif roll < 0.95:
# Random non-sticky
target = _get_random_non_sticky_target()
else:
# Chaos (anywhere)
target = _get_random_target()
targets.append(target)
return targets
# Anti-unfairness rules:
# 1. last_targeted_player_id tracking prevents same-player targeting
# 2. 2×2 shots never placed directly ON a player (offset by 1)
# 3. Path validation: ensure at least one path from each active player
# to a non-sticky region (using EnhancedGridMap.initialize_astar())
# 4. Exception: final 30s allows aggressive blocking
7. Network Considerations
All sync follows existing patterns:
| Data | Sync Method | Existing Pattern |
|---|---|---|
| Sticky cells | main.rpc("sync_grid_item", x, 2, z, 17) |
Safe zone / freeze overlay |
| Telegraph | rpc("sync_telegraph", targets_array) |
StopNGoManager.sync_phase() |
| Phase changes | rpc("sync_gauntlet_phase", phase_idx, elapsed) |
StopNGoManager.sync_phase() |
| Trap state | player.rpc("sync_trapped", true) |
player.rpc("sync_stop_freeze", true) |
| Cleanser grant | rpc("sync_cleanser", peer_id, count) |
goals_cycle_manager.sync_goal_count() |
| Smack state | player.rpc("sync_smack_state", charged) |
player.rpc("sync_modulate", color) |
| Cannon NPC | Static scene, no movement sync needed | static_tekton_stand.tscn |
8. Implementation Priority (Recommended Order)
- Game Mode Registration —
game_mode.gd,lobby_manager.gd,main.gdbranches - Arena Setup —
gauntlet_manager._setup_arena(), 20×20 grid, NPC zone block - Tile Spawning — Reuse
StopNGoManager._spawn_mission_tiles()pattern - Cannon Timer + Volley — Basic 5s interval, 5 shots, 1×1 only (no sizes yet)
- Sticky Cell System — Layer 2 overlay, movement blocking, trap detection
- Telegraph VFX — Warning glow → impact
- Impact Sizes — 1×2 and 2×2 shapes, phase-based weights
- Smack Mechanic — Modified push with cooldown/charge
- Cleanser — Unlock tracking, activated movement through sticky
- Targeting Intelligence — Player proximity, route blocking, anti-unfairness
- Bot AI — Cannon avoidance, sticky path planning
- Polish — VFX, SFX, HUD animations, 3D arena scene
9. Risk Assessment
| Risk | Mitigation |
|---|---|
| GridMap Layer 2 conflict with existing freeze/safe overlays | Gauntlet mode is exclusive — no freeze/safe tiles in this mode |
| 20×20 grid performance (400 cells + overlays) | Existing 23×12 (Stop N Go) and 14×14 (Tekton Doors) work fine; 20×20 is comparable |
| Cannon targeting causing impossible arenas | Anti-unfairness pathfinding check via EnhancedGridMap.initialize_astar() |
| New MeshLibrary items (17, 18) colliding with existing IDs | Verify current max ID in .tres before adding |
| Smack clash detection timing | Use server-authoritative timestamp comparison (< 0.5s window) |