d3bed34c6c
Bundles several sessions' worth of previously uncommitted work: map categories with Domination/Conquest/King of the Hill mode stubs and a server-list mode filter; a procedurally-drawn character-creation screen replacing the old callsign-only PROFILE overlay; the on-foot groundwork (walkable station hub, ship interior, character controller) plus the RAM currency backend; and today's addition, an in-ship Helldivers-2-style navigation table that QUICK PLAY's queue/connect flow and the Belters/ Military hub travel now live behind, with hub-and-ship return paths and a context-aware pause menu usable both in-match and inside the ship. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
66 lines
2.6 KiB
Python
66 lines
2.6 KiB
Python
from fastapi import APIRouter, Depends
|
|
from sqlalchemy import select
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
from app import crud
|
|
from app.config import settings
|
|
from app.database import get_db
|
|
from app.models import GameServer, Match, MatchPlayer
|
|
from app.schemas import MatchReportRequest
|
|
|
|
router = APIRouter(prefix="/matches", tags=["matches"])
|
|
|
|
|
|
# Called once by the hosting server's MatchManager (spacewar/autoload/
|
|
# match_manager.gd's _report_match_result()) when a match's POST_MATCH phase
|
|
# begins -- persists this match's final kills/deaths/win-loss into each
|
|
# player's running Player totals (kills, deaths, wins, losses, hours_played),
|
|
# and records a Match/MatchPlayer row if the reporting server is a known,
|
|
# registered GameServer. No auth -- same posture as /servers/register today,
|
|
# not a new gap introduced here (see overview/map1.md's design notes).
|
|
#
|
|
# Independent of any Match row the matchmaking queue may have already created
|
|
# when this match was *formed* (see queue_manager.py's _try_form_match) --
|
|
# this always writes a fresh Match/MatchPlayer pair representing how the
|
|
# match actually ended. A queued match can therefore end up with two Match
|
|
# rows (one "how it was formed", one "how it ended"); acceptable since no
|
|
# existing code reads these tables today and there's no migrations tooling to
|
|
# reconcile the schema around it.
|
|
@router.post("/report")
|
|
async def report_match(req: MatchReportRequest, db: AsyncSession = Depends(get_db)) -> dict:
|
|
server = (
|
|
await db.execute(
|
|
select(GameServer).where(
|
|
GameServer.ip == req.server_ip,
|
|
GameServer.port == req.server_port,
|
|
GameServer.mode == req.mode,
|
|
)
|
|
)
|
|
).scalars().first()
|
|
|
|
match_row = None
|
|
if server is not None:
|
|
match_row = Match(mode=req.mode, server_id=server.id)
|
|
db.add(match_row)
|
|
await db.flush()
|
|
|
|
for result in req.players:
|
|
player = await crud.get_or_create_player(db, result.callsign)
|
|
player.kills += result.kills
|
|
player.deaths += result.deaths
|
|
player.hours_played += result.seconds_played / 3600.0
|
|
player.ram_kb += (
|
|
settings.ram_payout_participation_kb
|
|
+ result.kills * settings.ram_payout_per_kill_kb
|
|
+ (settings.ram_payout_win_bonus_kb if result.is_winner else 0)
|
|
)
|
|
if result.is_winner:
|
|
player.wins += 1
|
|
elif req.winner_team is not None:
|
|
player.losses += 1
|
|
if match_row is not None:
|
|
db.add(MatchPlayer(match_id=match_row.id, player_id=player.id, team=result.team))
|
|
|
|
await db.commit()
|
|
return {"status": "recorded"}
|