Add in-game chat, bot fill for casual, and borderless fullscreen

- In-game chat: T=all, Y=team (race doubles as team), server-relayed and
  team-filtered via new ChatManager autoload; last 10 messages shown
  bottom-left (chat/chat_box.gd).
- Bot fill for casual (bots/): keeps each of the match's 2 offered races
  at a minimum of 7 total humans+bots, spawning/despawning reactively as
  players join/leave. Bots always fly their race's fighter and use
  negative peer_ids so they ride the existing networked-ship stack
  (spawning, loadout sync, health/position sync, bullet attribution) with
  no special-casing. Casual matchmaking now forms with just 1 real player
  queued instead of waiting for a second (matchmaking-api/).
- Borderless fullscreen with stretch scaling disabled instead of scaling
  the Steam-Deck-matched 1280x800 canvas up to fill PC monitors (which
  read as zoomed in) — PC monitors now reveal more world/HUD at native
  size instead. Reworked main_menu/team_select/chat_box to position via
  anchors relative to the real window instead of hardcoded coordinates.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-13 17:02:12 -04:00
parent edcda92812
commit c0b5f421c5
28 changed files with 705 additions and 79 deletions
+55 -2
View File
@@ -17,6 +17,12 @@ spacewar/ ← repo root
│ ├── 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
├── 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/
@@ -49,7 +55,8 @@ World (Node2D) ← world.gd instances MAP_SCENE into MapContainer o
├── HUD (CanvasLayer)
│ └── HealthLabel
├── PauseMenu (CanvasLayer, layer=10) ← menu/pause_menu.tscn
── TeamSelect (CanvasLayer, layer=20) ← menu/team_select.tscn; queue_free()s after selection
── TeamSelect (CanvasLayer, layer=20) ← menu/team_select.tscn; queue_free()s after selection
└── ChatBox (CanvasLayer, layer=5) ← chat/chat_box.tscn
```
### `world/maps/map_01.tscn` — hand-painted map
@@ -81,6 +88,8 @@ Player (CharacterBody2D) ← ship_movement.gd
| `move_down` | S | — |
| `shoot` | Space | — |
| `toggle_pause` | Esc | Start / Options button (JoyButton 6) |
| `chat_all` | T | — |
| `chat_team` | Y | — |
## Game Flow
@@ -93,7 +102,32 @@ main_menu.tscn
└── RANKED → disabled (coming soon)
```
> **Note:** `server_browser.tscn` was built (Task 1) and is functional, but the current flow bypasses it — CASUAL goes directly to the world and TeamSelect handles name/race/ship. The server browser will be re-integrated when real multiplayer server listing is built.
> **Note:** `server_browser.tscn` was built (Task 1) and is functional, but the current flow bypasses it — CASUAL goes directly to the world and TeamSelect handles name/race/ship. The server browser will be re-integrated when real multiplayer server listing is built. It still uses the old hardcoded-1280px layout style (see Display below) — not yet updated since it isn't reachable in the live flow.
## 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`
@@ -113,3 +147,22 @@ main_menu.tscn
| `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`).