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:
@@ -1,6 +1,9 @@
|
||||
DATABASE_URL=postgresql+asyncpg://matchmaking:matchmaking@postgres:5432/matchmaking
|
||||
|
||||
# Real-value targets per overview/bots.md (casual min 7v7) and overview/racesclasses.md (ranked 5v5).
|
||||
# CASUAL_TEAM_SIZE no longer gates when a casual match forms (bot fill means
|
||||
# 1 real player is enough — see queue_manager.py); it only caps how many real
|
||||
# players group into one casual match.
|
||||
CASUAL_TEAM_SIZE=7
|
||||
RANKED_TEAM_SIZE=5
|
||||
|
||||
|
||||
@@ -33,10 +33,15 @@ rebuilding the image. Rebuild (`docker compose up -d --build`) only when
|
||||
returns the assigned server's `server_ip`/`server_port`/`team`.
|
||||
- `POST /matchmaking/queue/leave?ticket_id=...` — cancel a queued ticket.
|
||||
|
||||
A background loop (`app/queue_manager.py`) runs every 2s, and once enough
|
||||
players are queued for a mode (`CASUAL_TEAM_SIZE`/`RANKED_TEAM_SIZE` × 2) it
|
||||
forms a match, splits them into two teams, and assigns the first available
|
||||
`GameServer` for that mode.
|
||||
A background loop (`app/queue_manager.py`) runs every 2s and forms a match
|
||||
once enough players are queued for a mode, then splits them into two teams
|
||||
and assigns the first available `GameServer` for that mode. Casual and
|
||||
ranked differ here: casual has bot fill (`spacewar/bots/bot_manager.gd`
|
||||
pads both teams up to `GameConfig.bot_min_team_size` in-game), so 1 queued
|
||||
player is enough to form a casual match — `CASUAL_TEAM_SIZE` just caps how
|
||||
many real players can group into one match, it no longer gates formation.
|
||||
Ranked has no bots, so it still waits for the full `RANKED_TEAM_SIZE × 2`
|
||||
real players before forming.
|
||||
|
||||
There's no real game-server pool yet — one dev server is auto-seeded on API
|
||||
startup from `DEV_SEED_SERVER_IP`/`DEV_SEED_SERVER_PORT` (defaults to
|
||||
|
||||
@@ -65,13 +65,12 @@ class QueueManager:
|
||||
tickets.sort(key=lambda t: t.queued_at)
|
||||
return tickets
|
||||
|
||||
async def _try_form_match(self, mode: Mode, team_size: int, db: AsyncSession) -> None:
|
||||
required = team_size * 2
|
||||
async def _try_form_match(self, mode: Mode, required: int, cap: int, db: AsyncSession) -> None:
|
||||
async with self._lock:
|
||||
waiting = self._waiting(mode)
|
||||
if len(waiting) < required:
|
||||
return
|
||||
group = waiting[:required]
|
||||
group = waiting[:cap]
|
||||
|
||||
server = (
|
||||
await db.execute(
|
||||
@@ -99,8 +98,20 @@ class QueueManager:
|
||||
while True:
|
||||
await asyncio.sleep(2)
|
||||
async with async_session() as db:
|
||||
await self._try_form_match(Mode.casual, settings.casual_team_size, db)
|
||||
await self._try_form_match(Mode.ranked, settings.ranked_team_size, db)
|
||||
# Casual has bot fill (spacewar/bots/bot_manager.gd) — bots pad
|
||||
# both teams up to GameConfig.bot_min_team_size, so a single
|
||||
# queued player is enough to form a match. Still group in
|
||||
# anyone else who queues in the same tick, up to the real
|
||||
# casual_team_size*2 target, rather than capping at 1v1.
|
||||
# Ranked has no bots, so it still needs the full
|
||||
# ranked_team_size*2 real players queued before forming.
|
||||
await self._try_form_match(Mode.casual, required=1, cap=settings.casual_team_size * 2, db=db)
|
||||
await self._try_form_match(
|
||||
Mode.ranked,
|
||||
required=settings.ranked_team_size * 2,
|
||||
cap=settings.ranked_team_size * 2,
|
||||
db=db,
|
||||
)
|
||||
|
||||
|
||||
queue_manager = QueueManager()
|
||||
|
||||
Reference in New Issue
Block a user