7b44b2f2dc
Server browsing & matchmaking: - HL2-style main menu (QUICK PLAY/SERVER SELECT/OPTIONS/PROFILE/QUIT) replaces the old CASUAL/RANKED tiles; RANKED removed end-to-end (menu, matchmaking-api's Mode.ranked queue path, MMR matching) until ranked is real. - New menu/server_select.gd lists real servers from the matchmaking API's GET /servers and connects directly, retiring the old unreachable menu/server_browser.gd. - Real game-server pool: NetworkManager.host_server() self-registers on boot and heartbeats every 8s with live player counts; API computes available/full status from player_count, and a background sweep marks any server offline once its heartbeat goes stale (catches a crashed server that never deregistered). - Server-select rows get a live UDP ping probe (query port = game port + 10000) instead of trusting stale DB numbers; post-connect HUD shows live RTT off ENet's own peer stats. Race roster overhaul: - Swapped Terran/Mechanos/Vorg for the pivoted roster — Apex Dynamics, Inner Sphere Navy, Outer Rim Collective — each with a 3-ship Fighter/Gunner/Tank lineup, art cropped from concept sheets with background removal + orientation fixes per sheet. - Live headcount + roster + "TEAM FULL" lock on the race-select screen, shared between the initial pre-spawn pick and the pause menu's live SELECT TEAM swap. - Bot personalities (bots/bot_personality.gd): aggression/caution/ accuracy/reaction/awareness traits rolled per bot instead of one fixed AI profile. HUD additions: - Player list (roster, teammates white/enemies yellow, bots flagged), kill feed, minimap, and explosion VFX on death. - Health and energy now render as bars (hud/stat_bar.gd) instead of text in the top-left HUD. Ship energy system: - Per-role max energy (Fighter 100 / Gunner 150 / Tank 250), 75 energy per shot, flat regen (100 per 2.5s), fully server-authoritative and piggybacked on the existing per-tick state broadcast alongside health. - New blue "mirrored" bar top-middle of the screen (hud/energy_bar.gd) whose fill drains from both edges toward the center instead of left-to-right. Ship handling tuning: - Turn rate reduced (4.0 -> 1.0 rad/s) so a quick tap no longer over-rotates; holding past 0.15s ramps to double speed (2.0 rad/s) for fast full turns, gated the same way damage already is so replay during reconciliation can't double-count the hold timer. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
180 lines
8.7 KiB
Markdown
180 lines
8.7 KiB
Markdown
# Project Structure
|
||
|
||
## File Tree
|
||
|
||
```
|
||
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)
|
||
├── 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)
|
||
├── ships/
|
||
│ ├── ship.tscn ← player ship scene (formerly node_2d.tscn)
|
||
│ ├── ship_movement.gd ← player ship logic
|
||
│ └── 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
|
||
├── 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_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_tileset.tres ← shared TileSet resource (walls.png + asteroids.png atlas sources)
|
||
│ └── maps/
|
||
│ └── map_01.tscn ← hand-painted map: Walls + Asteroids TileMapLayers, team spawn Marker2Ds
|
||
└── assets/
|
||
├── icon.svg
|
||
└── 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
|
||
```
|
||
|
||
> **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`).
|
||
|
||
## Scene Graph
|
||
|
||
### `menu/main_menu.tscn` — entry point
|
||
```
|
||
MainMenu (Control) ← main_menu.gd builds all UI in _ready()
|
||
```
|
||
|
||
### `menu/server_select.tscn` — server list
|
||
```
|
||
ServerSelect (Control) ← server_select.gd builds all UI in _ready(), fetches GET /servers
|
||
```
|
||
|
||
### `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/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
|
||
└── SpawnPoints (Node2D)
|
||
├── TeamASpawn1 (Marker2D) ← group "team_a_spawn"
|
||
└── TeamBSpawn1 (Marker2D) ← group "team_b_spawn"
|
||
```
|
||
|
||
### `ships/ship.tscn` — player ship
|
||
```
|
||
Player (CharacterBody2D) ← ship_movement.gd
|
||
├── CollisionShape2D ← CircleShape2D
|
||
├── Sprite2D ← texture set at runtime from GameConfig.player_ship_path
|
||
└── VisibleOnScreenNotifier2D
|
||
```
|
||
|
||
## Input Map
|
||
|
||
| Action | Key | Controller |
|
||
|--------|-----|------------|
|
||
| `move_up` | W | — |
|
||
| `move_left` | A | — |
|
||
| `move_right` | D | — |
|
||
| `move_down` | S | — |
|
||
| `shoot` | Space | — |
|
||
| `toggle_pause` | Esc | Start / Options button (JoyButton 6) |
|
||
| `chat_all` | T | — |
|
||
| `chat_team` | Y | — |
|
||
|
||
## Game Flow
|
||
|
||
```
|
||
main_menu.tscn
|
||
├── QUICK PLAY → matchmaking queue (casual) → world.tscn
|
||
│ ├── TeamSelect overlay: pick race (2 random of 3 offered)
|
||
│ ├── 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
|
||
├── PROFILE → callsign-edit overlay (also auto-opens from QUICK PLAY if no callsign is set)
|
||
└── QUIT
|
||
```
|
||
|
||
Ranked matchmaking is removed from the menu entirely (not just disabled) until it's real — see `CLAUDE.md` Current Tasks.
|
||
|
||
## Display
|
||
|
||
Launches borderless fullscreen at the real screen resolution (`window/size/mode=3`
|
||
in `project.godot`) with stretch mode **disabled** — 1 game pixel = 1 screen
|
||
pixel everywhere, no UI/world scaling. Chosen over Godot's default
|
||
`canvas_items`+`expand` stretch (which scales the whole 2D canvas to fill the
|
||
window) because the game's design canvas is deliberately sized to the Steam
|
||
Deck's native 1280×800; scaling that up to fill a PC monitor made everything
|
||
look zoomed in. With stretch disabled, PC monitors just reveal more of the
|
||
world/HUD at native size instead — the actual map (`world/maps/map_01.tscn`)
|
||
is already a fixed-size 14016×6000 arena, not viewport-sized, so there's more
|
||
world to reveal.
|
||
|
||
This means every menu Control has to position itself relative to the *real*
|
||
window size, not a fixed 1280×800 design canvas — anchors (0.0=edge,
|
||
0.5=center, 1.0=opposite edge) plus fixed pixel offsets from that anchor,
|
||
not raw absolute pixel coordinates. `main_menu.gd` and `team_select.gd` both
|
||
have a `_place()`/direct-property-assignment helper for this — **always set
|
||
anchor properties directly (`node.anchor_left = ...`) rather than through
|
||
sequential `set_anchor_and_offset()` calls**: that method's default
|
||
`push_opposite_anchor=true` drags the opposite side's anchor/offset along
|
||
whenever two sequential calls momentarily disagree (e.g. left set to a 0.5
|
||
anchor while right is still its 0.0 default), corrupting layout — hit this
|
||
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 |
|
||
|
||
## 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`.
|
||
|
||
## Key Autoload — `ChatManager`
|
||
|
||
Server-relayed chat (`autoload/chat_manager.gd`). `send_chat(text, team_only)` sends;
|
||
`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`).
|