Add match structure (countdown, timer, scoreboard, banner, persistent stats) and capital-ship fleet polish
Match phases (autoload/match_manager.gd, world/game_modes/):
- New server-authoritative MatchManager loops PRE_MATCH (30s countdown,
ships held invisible/uncollidable, flagships creep into formation) ->
IN_PROGRESS (active GameMode's clock runs) -> POST_MATCH (winner
banner, result reported to matchmaking-api) -> back to a fresh
PRE_MATCH forever, matching the always-on server-pool model instead
of kicking players to the menu at match end.
- Win-condition logic lives in a new GameMode abstraction (game_mode.gd
base + team_deathmatch_mode.gd, the only mode so far) so a future
mode is a new subclass plus one factory branch, no timer/scoreboard/
banner code changes needed.
- Three new HUD pieces: match_timer.gd (countdown/clock), scoreboard.gd
(hold-Tab two-team panel), match_banner.gd (winner banner).
- New MatchStats autoload tracks per-match kills/deaths by peer_id
(including bots), hooked into kill_feed_manager's existing
report_kill() call site.
Persistent stats (matchmaking-api/):
- Player gains kills/deaths/hours_played columns; new
POST /matches/report endpoint (server-only, called once at
POST_MATCH) upserts each real player's totals by callsign via a
shared app/crud.py helper also used by the matchmaking-queue join
path. GET /stats/{callsign} returns the new fields alongside mmr/
wins/losses.
Capital-ship fleet polish (world/flagship.gd, world/world.gd,
ships/ship_movement.gd, autoload/game_config.gd):
- Flagship formations are now a clean vertical line (no per-ship
position/rotation jitter) so play_creep_in()'s rigid-group tween
reads as one disciplined fleet arriving together, rising from
directly below (not a random compass direction) over the full 30s
countdown.
- Fixed a real bug where the creep-in tween only ever played on the
server -- MatchManager._run_pre_match() called straight into World,
server-only code a remote client's own process never runs, leaving
their flagships static all match. World now triggers it off
MatchManager.phase_changed instead, which fires identically on every
peer.
- Fixed a second bug (only reachable on a fresh server boot's very
first spawn): a phase==PRE_MATCH check that's true even before the
match loop has genuinely started that phase for the first time fired
play_creep_in() with a bogus zero-duration tween, corrupting the
target the real 30s tween read moments later -- flagships would
settle 4000 units off from their intended formation slot and fire
from there instead. Guarded on get_remaining_seconds() > 0 too.
- The held ship's camera now actively tracks its own team's flagship
centroid every tick during the countdown (position_smoothing
disabled for the hold, since it fights a manually-driven target and
was the reason the fleet read as invisible) instead of sitting fixed
and wide-angle; local offset/zoom/smoothing are explicitly reset on
release so control handback doesn't inherit a stale camera transform.
- _apply_pre_match_hold()/_apply_respawn() now set _dead/
_held_for_pre_match inside the RPC itself, not just in the
server-only caller -- those flags never reached remote clients
before, so WASD wasn't actually blocked for them during the hold.
- Ships now launch from a narrow point directly beneath their own
flagship formation instead of a full-circle scatter around the spawn
marker (which could land a spawn behind/inside a hull). Bumped
flagship_defense_radius so it still comfortably reaches a player who
flies a straight line to the enemy side without correcting for that
new offset.
ISN gets a Stealth Corvette hull mixed into its flagship formation
(assets/images/ships/isn/), banner art renamed off opaque UUID
filenames to isn_banner.jpeg/orc_banner.jpeg.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
+156
-57
@@ -7,40 +7,67 @@ spacewar/ ← repo root
|
||||
└── spacewar/ ← Godot project root (open this in Godot editor)
|
||||
├── project.godot
|
||||
├── autoload/
|
||||
│ └── game_config.gd ← autoload singleton (tuning values, player state, signals)
|
||||
│ ├── game_config.gd ← match-wide tuning constants, input-focus flags, persisted user settings (see GameConfig table below)
|
||||
│ ├── network_manager.gd ← owns the ENetMultiplayerPeer; host_server()/join_server(); server self-registration+heartbeat with matchmaking-api; UDP query responder for pre-connect server-select pings
|
||||
│ ├── player_registry.gd ← per-peer loadout registry (name/race/ship/role/flame/sound paths), keyed by peer_id; server-relayed to every peer
|
||||
│ ├── matchmaking_client.gd ← HTTP wrapper around matchmaking-api (queue, list_servers, register_server); don't call _request() directly from outside this file
|
||||
│ ├── chat_manager.gd ← server-relayed all/team chat
|
||||
│ ├── kill_feed_manager.gd ← server-authoritative kill-feed broadcast (victim/killer name+race per death); also feeds MatchStats
|
||||
│ ├── match_stats.gd ← per-match (resets every loop) kills/deaths by peer_id, incl. bots; hooked from kill_feed_manager.gd
|
||||
│ ├── match_manager.gd ← server-authoritative match-phase state machine (PRE_MATCH/IN_PROGRESS/POST_MATCH, loops forever); owns the active GameMode
|
||||
│ └── music_manager.gd ← menu background music player, routed through the Music audio bus
|
||||
├── menu/
|
||||
│ ├── main_menu.tscn/gd ← entry point / main scene; HL2-style vertical nav
|
||||
│ ├── server_select.tscn/gd ← server list UI, backed by matchmaking-api's GET /servers
|
||||
│ ├── pause_menu.tscn/gd ← in-game pause overlay (ESC / Start button)
|
||||
│ └── team_select.tscn/gd ← race + ship selection overlay (shown on world load)
|
||||
│ ├── main_menu.tscn/gd ← entry point / main scene; HL2-style vertical nav (QUICK PLAY, SERVER SELECT, OPTIONS, PROFILE, QUIT)
|
||||
│ ├── server_select.tscn/gd ← server list UI, backed by matchmaking-api's GET /servers + a live UDP ping probe
|
||||
│ ├── pause_menu.tscn/gd ← in-game pause overlay (ESC / Start button)
|
||||
│ ├── settings_panel.tscn/gd ← Options screen; one instance shared by main menu (OPTIONS) and pause menu (SETTINGS); audio/frame-rate/vsync/window-mode/colorblind/key-rebind sections
|
||||
│ └── team_select.tscn/gd ← faction + ship selection overlay (shown on world load and on pause-menu SELECT TEAM)
|
||||
├── ships/
|
||||
│ ├── ship.tscn ← player ship scene (formerly node_2d.tscn)
|
||||
│ ├── ship_movement.gd ← player ship logic
|
||||
│ ├── ship.tscn ← player ship scene
|
||||
│ ├── ship_movement.gd ← player ship logic (movement, prediction/reconciliation, health/energy, nameplate interpolation)
|
||||
│ └── bullet.tscn/gd ← projectile
|
||||
├── chat/
|
||||
│ └── chat_box.tscn/gd ← WoW-style chat overlay (T=all, Y=team), added to world.tscn
|
||||
├── hud/
|
||||
│ └── player_list.tscn/gd ← top-left player roster (white=team, yellow=enemy, "(b)"=bot), added to world.tscn
|
||||
│ ├── player_list.tscn/gd ← top-left player roster (white=team, yellow/orange=enemy per colorblind mode, "(b)"=bot)
|
||||
│ ├── kill_feed.tscn/gd ← last 4 deaths, stacked above ChatBox's history panel
|
||||
│ ├── mini_map.tscn/gd ← polls game state (hazards, players, flagships), pokes MiniMapView, calls queue_redraw()
|
||||
│ ├── mini_map_view.gd ← pure Control _draw() surface for the minimap (split out since CanvasLayer can't override _draw())
|
||||
│ ├── ping_display.tscn/gd ← top-right live RTT via ENetPacketPeer.get_statistic(PEER_ROUND_TRIP_TIME)
|
||||
│ ├── energy_bar.tscn/gd ← blue center-out mirrored bar; also instanced for the health bar (top of world.tscn)
|
||||
│ ├── match_timer.tscn/gd ← top-center match clock (PRE_MATCH countdown / IN_PROGRESS 7:00 countdown), driven by MatchManager
|
||||
│ ├── scoreboard.tscn/gd ← hold-Tab CS:GO-style scoreboard, two team panels (name/role/K/D), driven by MatchStats + the active GameMode
|
||||
│ └── match_banner.tscn/gd ← full-screen winner banner (ISN/ORC art + "<RACE> WINS"/"DRAW"), shown during MatchManager's POST_MATCH phase
|
||||
├── bots/
|
||||
│ ├── bot_manager.gd ← autoload; server-only bot fill (join/leave reconciliation)
|
||||
│ ├── bot_ai.gd ← class_name BotAI; seek-nearest-enemy-and-shoot brain
|
||||
│ ├── bot_ai.gd ← class_name BotAI; seek-nearest-enemy-and-shoot brain, tuned per-bot by BotPersonality
|
||||
│ ├── bot_personality.gd ← class_name BotPersonality; 5 independent 0-1 traits (aggression/caution/accuracy/reaction/awareness) rolled once per bot-slot
|
||||
│ └── bot_names.gd ← class_name BotNames; procedural callsign pool
|
||||
├── world/
|
||||
│ ├── world.tscn ← game world; world.gd loads the active map into MapContainer
|
||||
│ ├── world.gd ← picks/instances a map scene from world/maps/
|
||||
│ ├── world.tscn ← game world; world.gd loads the active map, spawns flagships, brokers bullets/hazard colliders
|
||||
│ ├── world.gd ← picks/instances a map scene from world/maps/ (MAPS array), decides offered races, spawns flagship formations, hosts MatchManager's ship-hold/creep-in hooks
|
||||
│ ├── world_tileset.tres ← shared TileSet resource (walls.png + asteroids.png atlas sources)
|
||||
│ ├── flagship.tscn/gd ← capital-ship point-defense formation (class_name Flagship), see CLAUDE.md item 24; also plays the PRE_MATCH creep-in tween
|
||||
│ ├── starfield.gdshader ← procedural starfield background
|
||||
│ ├── game_modes/
|
||||
│ │ ├── game_mode.gd ← class_name GameMode; base ruleset (win condition, score label, match duration) MatchManager drives every mode through
|
||||
│ │ └── team_deathmatch_mode.gd ← class_name TeamDeathmatchMode; most-kills-when-clock-runs-out, the only implemented mode today (see overview/map1.md)
|
||||
│ └── maps/
|
||||
│ └── map_01.tscn ← hand-painted map: Walls + Asteroids TileMapLayers, team spawn Marker2Ds
|
||||
│ └── map_01.tscn ← hand-painted map: Walls + Asteroids TileMapLayers (asteroids deal impact damage, not just bounce), team spawn Marker2Ds
|
||||
└── assets/
|
||||
├── icon.svg
|
||||
├── default_bus_layout.tres ← Master/Music/SFX audio buses
|
||||
└── images/
|
||||
├── background/skybox/ ← 6 space background PNGs (1.png – 6.png)
|
||||
├── effects/ ← explosion1.png
|
||||
├── ships/example_ships/ ← 4 placeholder ship sprites (1.png, 1B.png, 2a.png, 3b.png)
|
||||
└── tiles/ ← walls.png, asteroids.png, used by world_tileset.tres
|
||||
├── background/skybox/ ← space background PNGs
|
||||
├── effects/
|
||||
│ ├── explosion/ ← ship-death explosion animation frames
|
||||
│ └── bullets/ ← per-race/role bullet sprites, incl. flagship turret fire
|
||||
├── banners/ ← faction banner art
|
||||
├── ships/isn/, ships/orc/ ← live faction ship art (idle + _flame sprites), each with a source/ concept-sheet copy
|
||||
└── tiles/ ← walls.png, asteroids.png, used by world_tileset.tres
|
||||
```
|
||||
|
||||
> **Note:** `asteroid_movement.gd` referenced in an earlier version of this doc does not exist yet — it's still an open task (see `CLAUDE.md`).
|
||||
> `assets/images/ships/apex/` and the older `terran/mech/vorg` folders are left on disk but unreferenced by any code — not deleted, see CLAUDE.md items 17/19.
|
||||
|
||||
## Scene Graph
|
||||
|
||||
@@ -56,33 +83,43 @@ ServerSelect (Control) ← server_select.gd builds all UI in _ready(), fetches
|
||||
|
||||
### `world/world.tscn` — game world
|
||||
```
|
||||
World (Node2D) ← world.gd instances MAP_SCENE into MapContainer on _ready()
|
||||
├── MapContainer (Node2D) ← holds the instanced map (world/maps/map_01.tscn), background included
|
||||
├── Player ← instance of ships/ship.tscn
|
||||
├── HUD (CanvasLayer)
|
||||
│ └── HealthLabel
|
||||
├── PauseMenu (CanvasLayer, layer=10) ← menu/pause_menu.tscn
|
||||
├── TeamSelect (CanvasLayer, layer=20) ← menu/team_select.tscn; queue_free()s after selection
|
||||
├── ChatBox (CanvasLayer, layer=5) ← chat/chat_box.tscn
|
||||
└── PlayerList (CanvasLayer, layer=4) ← hud/player_list.tscn
|
||||
World (Node2D) ← world.gd instances a map into MapContainer, spawns flagships, brokers bullet/hazard colliders
|
||||
├── MapContainer (Node2D) ← holds the instanced map (world/maps/map_01.tscn), background included
|
||||
├── Players (Node2D) ← per-peer ship.tscn instances live here
|
||||
├── MultiplayerSpawner ← spawns/despawns ships into Players
|
||||
├── Bullets (Node2D) ← server-spawned bullet.tscn instances
|
||||
├── BulletSpawner (MultiplayerSpawner)
|
||||
├── PauseMenu (CanvasLayer, layer=10) ← menu/pause_menu.tscn
|
||||
├── TeamSelect (CanvasLayer, layer=20) ← menu/team_select.tscn; also reopened live from the pause menu's SELECT TEAM
|
||||
├── SettingsPanel (CanvasLayer, layer=25) ← menu/settings_panel.tscn; opened by pause menu's SETTINGS
|
||||
├── ChatBox (CanvasLayer, layer=5) ← chat/chat_box.tscn
|
||||
├── PlayerList (CanvasLayer) ← hud/player_list.tscn
|
||||
├── KillFeed (CanvasLayer, layer=5) ← hud/kill_feed.tscn
|
||||
├── MiniMap ← hud/mini_map.tscn
|
||||
├── PingDisplay ← hud/ping_display.tscn
|
||||
├── HealthBar ← hud/energy_bar.tscn instance, reused for health
|
||||
├── EnergyBar ← hud/energy_bar.tscn instance
|
||||
├── MatchTimer (CanvasLayer, layer=4) ← hud/match_timer.tscn
|
||||
├── Scoreboard (CanvasLayer, layer=6) ← hud/scoreboard.tscn; visible only while Tab is held
|
||||
└── MatchBanner (CanvasLayer, layer=30) ← hud/match_banner.tscn; visible only during MatchManager's POST_MATCH phase
|
||||
```
|
||||
|
||||
### `world/maps/map_01.tscn` — hand-painted map
|
||||
```
|
||||
Map01 (Node2D)
|
||||
├── Background (TextureRect) ← per-map space background (skybox/1.png); visible while editing this scene
|
||||
├── Walls (TileMapLayer) ← painted by hand in the Godot Tile Editor, uses world_tileset.tres
|
||||
├── Asteroids (TileMapLayer) ← painted by hand, same shared tileset
|
||||
├── Background (TextureRect) ← per-map space background; procedural starfield.gdshader, not a tiled skybox
|
||||
├── Walls (TileMapLayer) ← painted by hand in the Godot Tile Editor, uses world_tileset.tres; bounces ships (environment_wall group)
|
||||
├── Asteroids (TileMapLayer) ← painted by hand, same shared tileset; bounces AND deals impact damage (environment_hazard group, GameConfig.ship_wall_damage)
|
||||
└── SpawnPoints (Node2D)
|
||||
├── TeamASpawn1 (Marker2D) ← group "team_a_spawn"
|
||||
└── TeamBSpawn1 (Marker2D) ← group "team_b_spawn"
|
||||
├── TeamASpawn1 (Marker2D) ← group "team_a_spawn"; also where world.gd spawns Team A's flagship formation
|
||||
└── TeamBSpawn1 (Marker2D) ← group "team_b_spawn"; also where world.gd spawns Team B's flagship formation
|
||||
```
|
||||
|
||||
### `ships/ship.tscn` — player ship
|
||||
```
|
||||
Player (CharacterBody2D) ← ship_movement.gd
|
||||
├── CollisionShape2D ← CircleShape2D
|
||||
├── Sprite2D ← texture set at runtime from GameConfig.player_ship_path
|
||||
├── Sprite2D ← texture set at runtime from PlayerRegistry's per-peer ship_path
|
||||
└── VisibleOnScreenNotifier2D
|
||||
```
|
||||
|
||||
@@ -98,6 +135,7 @@ Player (CharacterBody2D) ← ship_movement.gd
|
||||
| `toggle_pause` | Esc | Start / Options button (JoyButton 6) |
|
||||
| `chat_all` | T | — |
|
||||
| `chat_team` | Y | — |
|
||||
| `scoreboard` | Tab | — |
|
||||
|
||||
## Game Flow
|
||||
|
||||
@@ -108,7 +146,7 @@ main_menu.tscn
|
||||
│ ├── TeamSelect overlay: pick ship
|
||||
│ └── Player spawns → game live
|
||||
├── SERVER SELECT → server_select.tscn → pick a server → world.tscn (same TeamSelect flow)
|
||||
├── OPTIONS → stub, coming soon
|
||||
├── OPTIONS → settings_panel.tscn (audio, frame-rate cap, vsync, window mode, colorblind mode, key rebinding) — same panel the in-game pause menu's SETTINGS opens
|
||||
├── PROFILE → callsign-edit overlay (also auto-opens from QUICK PLAY if no callsign is set)
|
||||
└── QUIT
|
||||
```
|
||||
@@ -142,34 +180,61 @@ exact bug converting the play-mode buttons to be centered.
|
||||
|
||||
## Key Autoload — `GameConfig`
|
||||
|
||||
| Property | Type | Set by |
|
||||
|----------|------|--------|
|
||||
| `player_name` | String | Main menu callsign input |
|
||||
| `player_race` | int | TeamSelect race pick |
|
||||
| `player_ship_path` | String | TeamSelect ship pick |
|
||||
| `team_selected` | signal | Emitted by TeamSelect when done |
|
||||
| `ship_thrust` | float | Tuning constant |
|
||||
| `ship_max_speed` | float | Tuning constant |
|
||||
| `ship_rotation_speed` | float | Tuning constant |
|
||||
| `ship_fire_rate` | float | Tuning constant |
|
||||
| `bullet_speed` | float | Tuning constant |
|
||||
| `ship_max_health` | int | Tuning constant |
|
||||
| `ship_respawn_delay` | float | Tuning constant |
|
||||
| `ship_invincibility_time` | float | Tuning constant |
|
||||
| `bullet_damage` | int | Tuning constant |
|
||||
| `ship_wall_damage` | int | Tuning constant |
|
||||
| `chat_focused` | bool | Set by ChatBox while its input line has keyboard focus; gates ship movement/fire input |
|
||||
| `bot_min_team_size` | int | Tuning constant — min humans+bots per race, see `bots/bot_manager.gd` |
|
||||
| `bot_engage_range` | float | Tuning constant — bot max shoot distance |
|
||||
| `bot_stop_distance` | float | Tuning constant — bot stops closing distance below this |
|
||||
| `bot_aim_tolerance_deg` | float | Tuning constant — how precisely a bot must face a target to fire |
|
||||
Per-peer identity (`player_race`, `player_ship_path`) moved out of `GameConfig`
|
||||
early on, per the networking plan's Phase 2 — that's `PlayerRegistry`'s job
|
||||
now (see below). `GameConfig` today holds match-wide tuning constants plus
|
||||
persisted user settings. Grouped by area rather than listed exhaustively
|
||||
(~70 properties) — see `autoload/game_config.gd` for the full list:
|
||||
|
||||
| Area | Examples | Notes |
|
||||
|------|----------|-------|
|
||||
| Persisted settings | `max_fps`, `vsync_mode`, `window_mode`, `master_volume`/`music_volume`/`sfx_volume`, `colorblind_mode`, `keybinds` | Loaded/applied in `_ready()`, written to `user://settings.cfg` by `save_settings()`; each has a `set_*()` that applies live |
|
||||
| Ship movement | `ship_thrust`, `ship_max_speed`, `ship_rotation_speed`, `ship_rotation_ramp_delay` | Tuning constants |
|
||||
| Combat | `ship_fire_rate`, `bullet_speed`, `ship_bullet_damage_by_role`, `ship_bullet_count_by_role`, `bullet_max_range` | Per-role dictionaries keyed `"Fighter"/"Gunner"/"Tank"` |
|
||||
| Energy | `ship_max_energy_by_role`, `ship_bullet_energy_cost_by_role`, `ship_energy_regen_rate` | See CLAUDE.md item 23 |
|
||||
| Health/respawn | `ship_max_health`, `ship_respawn_delay`, `ship_invincibility_time`, `ship_wall_damage` | `ship_wall_damage` applies on both wall bounces and asteroid hazard hits |
|
||||
| Collision/visuals | `ship_bounce_restitution`, `ship_collision_damage_scale`, `ship_scale_factor`, `ship_hitbox_scale`, `explosion_scale`, `damage_number_*` | |
|
||||
| Colors | `team_color`, `enemy_color`, `colorblind_mode` (swaps `enemy_color` between `ENEMY_COLOR_DEFAULT`/`ENEMY_COLOR_COLORBLIND`) | Read by nameplates, `hud/player_list.gd`, `hud/mini_map.gd` |
|
||||
| Camera/world | `camera_zoom`, `world_bounds`, `spawn_area_inner_radius`/`outer_radius` | |
|
||||
| Flagships | `flagship_count_per_spawn`, `flagship_defense_radius`, `flagship_fire_rate`, `flagship_missile_*`, `flagship_bullet_*`, `flagship_aim_jitter_px`, `flagship_creep_in_distance` | See CLAUDE.md item 24; `flagship_creep_in_distance` is the PRE_MATCH cosmetic tween's start offset |
|
||||
| Match structure | `default_game_mode_id`, `match_pre_match_duration`, `match_duration`, `match_post_match_duration` | Read by `MatchManager`'s phase state machine — see item 26 |
|
||||
| Input-focus gates | `chat_focused`, `team_select_focused`, `settings_focused` | Each blocks ship movement/fire input while its overlay owns keyboard focus |
|
||||
| Bot tuning | `bot_min_team_size`, `bot_engage_range`, `bot_stop_distance_min`/`max`, `bot_retreat_health_frac_min`/`max`, `bot_aim_tolerance_best_deg`/`worst_deg`, `bot_aim_jitter_max_px`, `bot_reaction_update_best_sec`/`worst_sec`, `bot_awareness_range_min`/`max` | Min/max pairs are interpolated per-bot by that bot's rolled `BotPersonality` (0=worst trait, 1=best) |
|
||||
|
||||
## Key Autoload — `NetworkManager`
|
||||
|
||||
Owns the `ENetMultiplayerPeer` (`autoload/network_manager.gd`). `host_server(port)` /
|
||||
`join_server(ip, port)`. A hosted server self-registers with matchmaking-api on boot
|
||||
and re-heartbeats every 8s via `MatchmakingClient.register_server()` (reusing one
|
||||
persistent `HTTPRequest` node — a fresh one per call caused a periodic stutter, see
|
||||
CLAUDE.md item 20/21). Also runs a `UDPServer` on game-port+10000 (`_start_query_responder`)
|
||||
that answers a raw `"SPACEWAR_PING"` datagram with live player count, used by
|
||||
`menu/server_select.gd`'s pre-connect probe (post-connect ping uses ENet's own RTT stat
|
||||
instead, via `hud/ping_display.gd`).
|
||||
|
||||
## Key Autoload — `PlayerRegistry`
|
||||
|
||||
Per-peer loadout registry (`autoload/player_registry.gd`), `Dictionary[peer_id, info]`
|
||||
where `info` holds `name`/`race`/`ship_path`/`ship_scale`/`ship_speed_factor`/`role`/
|
||||
`ship_flame_path`/`ship_sound_path`. `submit_local_loadout()` (real players) and
|
||||
`register_bot()` (bots, negative peer_ids) both funnel through the same server-relayed
|
||||
storage, so every system that reads a loadout (spawning, HUD, bots, bullets) treats
|
||||
players and bots identically. `loadout_updated`/`race_changed`/`player_removed` signals
|
||||
drive `BotManager`, `hud/player_list.gd`, and TeamSelect's live headcounts.
|
||||
|
||||
## Key Autoload — `MatchmakingClient`
|
||||
|
||||
HTTP wrapper around `matchmaking-api` (`autoload/matchmaking_client.gd`) — queueing,
|
||||
`list_servers()` (backs `menu/server_select.gd`'s `GET /servers`), and
|
||||
`register_server()`/heartbeat. Don't call the internal `_request()` directly from
|
||||
outside this file; use the public wrapper methods.
|
||||
|
||||
## Key Autoload — `BotManager`
|
||||
|
||||
Server-authoritative bot fill for casual (`bots/bot_manager.gd`). Keeps each of the
|
||||
match's 2 offered races at `GameConfig.bot_min_team_size` total humans+bots,
|
||||
reacting to `PlayerRegistry.loadout_updated`/`player_removed` and
|
||||
`World.decide_offered_races()`. See `overview/bots.md`.
|
||||
match's 2 offered factions at `GameConfig.bot_min_team_size` total humans+bots,
|
||||
reacting to `PlayerRegistry.loadout_updated`/`race_changed`/`player_removed` and
|
||||
`World.decide_offered_races()`. Also rolls a `BotPersonality` per bot-slot. See `overview/bots.md`.
|
||||
|
||||
## Key Autoload — `ChatManager`
|
||||
|
||||
@@ -177,3 +242,37 @@ Server-relayed chat (`autoload/chat_manager.gd`). `send_chat(text, team_only)` s
|
||||
`message_received(sender_name, sender_race, team_only, text)` signal delivers
|
||||
incoming messages to `ChatBox`. "Team" messages are filtered server-side to
|
||||
peers sharing the sender's race (race doubles as team — see `PlayerRegistry`).
|
||||
|
||||
## Key Autoload — `KillFeedManager`
|
||||
|
||||
Server-authoritative kill-feed broadcast (`autoload/kill_feed_manager.gd`). One RPC per
|
||||
death, called from `ship_movement.gd`'s `_die()` — no client → server leg, unlike chat.
|
||||
`kill_reported` signal feeds `hud/kill_feed.gd`'s last-4-lines display, and
|
||||
`report_kill()` also calls `MatchStats.record_kill()` — the one hook point both
|
||||
systems share.
|
||||
|
||||
## Key Autoload — `MatchStats`
|
||||
|
||||
Per-match (resets every `MatchManager` loop) kills/deaths by `peer_id`, including
|
||||
bots (`autoload/match_stats.gd`). `get_team_kills(race_id)` sums a whole team via
|
||||
`PlayerRegistry`. `stats_updated`/`stats_reset` signals drive `hud/scoreboard.gd`.
|
||||
|
||||
## Key Autoload — `MatchManager`
|
||||
|
||||
Server-authoritative match-phase state machine (`autoload/match_manager.gd`):
|
||||
`PRE_MATCH` (30s countdown, ships held via `ship_movement.gd`'s
|
||||
`server_hold_for_match_start()`, flagships cosmetically creep into formation) →
|
||||
`IN_PROGRESS` (the active `GameMode`'s clock runs, ships released) → `POST_MATCH`
|
||||
(winner banner, match result reported to matchmaking-api) → loops back into a
|
||||
fresh `PRE_MATCH` indefinitely — no menu kick, matching this project's always-on
|
||||
server-pool model (see item 16). Follows the same "decide once on the server,
|
||||
RPC-broadcast the value, late joiners pull it" pattern `world.gd`'s
|
||||
`decide_offered_races()` established. Owns the active `GameMode`
|
||||
(`world/game_modes/`) instance, built identically on every peer off the shared
|
||||
`GameConfig.default_game_mode_id` constant — adding a new mode is a new
|
||||
`GameMode` subclass plus one factory branch, no changes to this state machine.
|
||||
|
||||
## Key Autoload — `MusicManager`
|
||||
|
||||
Menu background-music player (`autoload/music_manager.gd`), routed through the Music
|
||||
audio bus so `GameConfig.music_volume` controls it live.
|
||||
|
||||
Reference in New Issue
Block a user