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
+22 -5
View File
@@ -13,9 +13,26 @@
- Avoids the ghost-town feeling of a 25v25 map with 3 players
- Bots are not added beyond 7v7 so human players always dominate strategy
## Implementation Notes (not yet built)
## Implementation Notes
- Bots use simplified AI: thrust toward nearest enemy, shoot when in range
- Bot difficulty: intentionally easy (this is casual — bots are filler, not challenge)
- Bot names: procedurally generated callsigns so they're not obviously bots in the HUD
- Server tracks human vs bot count per team and manages swap-in on player join
- **`bots/bot_manager.gd`** (server-authoritative autoload) owns bot lifecycle. All
spawn/despawn decisions funnel through one function, `_reconcile_race(race_id)`,
triggered by `PlayerRegistry`'s existing `loadout_updated`/`player_removed`
signals (join/leave) and `World.decide_offered_races()` (match start) — no
polling. This is the one place to change for future tweaks (fill curve, per-map
team size, etc.)
- Bots get **negative peer_ids** (`-1, -2, ...`), assigned by `BotManager`. This
lets them reuse every existing networked-ship system for free — spawning
(`MultiplayerSpawner`), loadout replication/backfill (`PlayerRegistry`),
position/health/visibility sync, bullet damage attribution — none of which
cares whether a `peer_id` came from ENet or from `BotManager`
- Bots always fly **the race's fighter/Interceptor** (`race.ships[0]` in
`team_select.gd`'s `RACES` data)
- AI (`bots/bot_ai.gd`, `class_name BotAI`): thrust toward nearest living enemy,
turn to face it, shoot when roughly aimed and in range — simplified
seek-and-shoot, intentionally easy (casual bots are filler, not a challenge).
Fully isolated from `ship_movement.gd`'s networking code so future
difficulty/behavior tuning only ever touches this one file
- Bot names: `bots/bot_names.gd`, a flat pool of procedural callsigns
- Tunables live in `GameConfig` (`bot_min_team_size`, `bot_engage_range`,
`bot_stop_distance`, `bot_aim_tolerance_deg`)
+5
View File
@@ -0,0 +1,5 @@
The chat system will be in the game. In order to focus the chat window, the user presses t. He then types a message and enter sends it.
The message will only be in the game that the user is in. The opposing team can see the chat message as well.
If he wants it just for the team, he presses y. It will say [team] infront of it . The chat will show the last 10 messages. It will be positioned on bottom left of screen. The chat input is at the bottom. I want it to look similiar to world of warcrafts chat system.
+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`).
+5 -4
View File
@@ -12,17 +12,18 @@
| 5 | Main menu | CASUAL / RANKED (disabled), callsign input, rank badge, quit button |
| 6 | In-game pause menu | ESC / controller Start; game runs behind it; working: RESUME, QUIT TO MENU, QUIT TO DESKTOP |
| 7 | Team & ship selection | On world load; 2 random races offered; ship grid with real sprites; player spawns after |
| 8 | Replace placeholder races with chosen 3 (Terran Republic, Mechanos Sovereignty, Vorg Swarm) | Done |
| 9 | Real ship sprites for all 3 races (5 ships each) | Done |
| 13 | Multiplayer networking — ENet authoritative server | Done |
| 18 | In-game chat (T=all, Y=team, last 10 messages, bottom-left) | `chat/chat_box.gd` + `autoload/chat_manager.gd` — see `chat.md` |
| 11 | Bot fill for casual (min 7v7) | `bots/bot_manager.gd` + `bots/bot_ai.gd` — see `bots.md` |
## Up Next
| # | Task | Priority |
|---|------|----------|
| 8 | Replace placeholder races with chosen 3 (Terran Republic, Mechanos Sovereignty, Void Wraiths) | High |
| 9 | Real ship sprites for all 3 races (5 ships each) | High |
| 10 | Asteroids + environment hazards | Medium |
| 11 | Bot fill for casual (min 7v7) | Medium — see `bots.md` |
| 12 | Sound effects (thrust, shoot, explosion, UI clicks) | Medium |
| 13 | Multiplayer networking — ENet authoritative server | High (big) |
| 14 | Galaxy war meta + sector control | Low (post-networking) |
| 15 | Ranked matchmaking + MMR | Low (post-networking) |
| 16 | Settings screen (audio, controls, display) | Low |