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>
99 lines
4.5 KiB
Python
99 lines
4.5 KiB
Python
import enum
|
|
import uuid
|
|
from datetime import datetime
|
|
|
|
from sqlalchemy import BigInteger, DateTime, Float, ForeignKey, Integer, String
|
|
from sqlalchemy import Enum as SAEnum
|
|
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
|
|
|
from app.database import Base
|
|
|
|
|
|
class Mode(str, enum.Enum):
|
|
casual = "casual"
|
|
|
|
|
|
class ServerStatus(str, enum.Enum):
|
|
available = "available"
|
|
full = "full"
|
|
offline = "offline"
|
|
|
|
|
|
# No account/auth system yet (GodotSteam auth is still on the networking
|
|
# checklist), so callsign is the identity key for now — same as the game
|
|
# client itself, which only has a free-text name field today.
|
|
class Player(Base):
|
|
__tablename__ = "players"
|
|
|
|
id: Mapped[uuid.UUID] = mapped_column(primary_key=True, default=uuid.uuid4)
|
|
callsign: Mapped[str] = mapped_column(String(32), unique=True, index=True)
|
|
mmr: Mapped[int] = mapped_column(Integer, default=1000)
|
|
wins: Mapped[int] = mapped_column(Integer, default=0)
|
|
losses: Mapped[int] = mapped_column(Integer, default=0)
|
|
# Match-history stats (see spacewar/autoload/match_manager.gd's
|
|
# _report_match_result(), POST /matches/report) -- kills/deaths accumulate
|
|
# across every match reported for this callsign; hours_played is derived
|
|
# from each match's full duration for every player present at match end
|
|
# (not precise per-player join/leave timing -- a documented simplification).
|
|
kills: Mapped[int] = mapped_column(Integer, default=0)
|
|
deaths: Mapped[int] = mapped_column(Integer, default=0)
|
|
hours_played: Mapped[float] = mapped_column(Float, default=0.0)
|
|
# In-game currency, see overview/onfoot.md -- named RAM, stored as a
|
|
# single integer count of kilobytes (the smallest denomination); the
|
|
# client formats it up into KB/MB/GB/TB (1000 per step, not 1024) for
|
|
# display. BigInteger since a long-lived player's total is expected to
|
|
# climb well past 32-bit Integer's ~2.1 billion ceiling (2.1 billion KB
|
|
# is only ~2.1 TB).
|
|
ram_kb: Mapped[int] = mapped_column(BigInteger, default=0)
|
|
created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)
|
|
|
|
|
|
class GameServer(Base):
|
|
__tablename__ = "game_servers"
|
|
|
|
id: Mapped[uuid.UUID] = mapped_column(primary_key=True, default=uuid.uuid4)
|
|
ip: Mapped[str] = mapped_column(String(255))
|
|
port: Mapped[int] = mapped_column(Integer)
|
|
mode: Mapped[Mode] = mapped_column(SAEnum(Mode))
|
|
status: Mapped[ServerStatus] = mapped_column(SAEnum(ServerStatus), default=ServerStatus.available)
|
|
last_heartbeat: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)
|
|
# DB-stored fallback shown immediately in the server list; a live server
|
|
# is queried directly for its real-time count (see
|
|
# spacewar/autoload/network_manager.gd's UDP query responder and
|
|
# menu/server_select.gd's ping probe), which overrides this in the UI
|
|
# once that probe answers.
|
|
player_count: Mapped[int] = mapped_column(Integer, default=0)
|
|
max_players: Mapped[int] = mapped_column(Integer, default=50)
|
|
# The actual in-match GameMode id (e.g. "team_deathmatch") and map
|
|
# display name (e.g. "Sector Alpha") this server is running -- distinct
|
|
# from `mode` above, which is only the matchmaking queue mode ("casual").
|
|
# Plain strings, not a Mode-style enum: the set of GameMode ids lives in
|
|
# the Godot client (spacewar/world/game_modes/, world.gd's MAPS), which
|
|
# this API has no reason to duplicate/validate against. Reported by the
|
|
# hosting server on every register/heartbeat call (see
|
|
# spacewar/autoload/network_manager.gd's host_server()).
|
|
game_mode: Mapped[str] = mapped_column(String(64), default="team_deathmatch")
|
|
map_name: Mapped[str] = mapped_column(String(64), default="Sector Alpha")
|
|
|
|
|
|
class Match(Base):
|
|
__tablename__ = "matches"
|
|
|
|
id: Mapped[uuid.UUID] = mapped_column(primary_key=True, default=uuid.uuid4)
|
|
mode: Mapped[Mode] = mapped_column(SAEnum(Mode))
|
|
server_id: Mapped[uuid.UUID] = mapped_column(ForeignKey("game_servers.id"))
|
|
started_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)
|
|
|
|
players: Mapped[list["MatchPlayer"]] = relationship(back_populates="match")
|
|
|
|
|
|
class MatchPlayer(Base):
|
|
__tablename__ = "match_players"
|
|
|
|
id: Mapped[uuid.UUID] = mapped_column(primary_key=True, default=uuid.uuid4)
|
|
match_id: Mapped[uuid.UUID] = mapped_column(ForeignKey("matches.id"))
|
|
player_id: Mapped[uuid.UUID] = mapped_column(ForeignKey("players.id"))
|
|
team: Mapped[int] = mapped_column(Integer) # 0 or 1
|
|
|
|
match: Mapped["Match"] = relationship(back_populates="players")
|