diff --git a/resources/lang/en.json b/resources/lang/en.json
index ea078c4fe..fe4ff0fa9 100644
--- a/resources/lang/en.json
+++ b/resources/lang/en.json
@@ -646,6 +646,48 @@
"delete_unit_title": "Delete Unit",
"delete_unit_description": "Click to delete the nearest unit"
},
+ "player_stats_table": {
+ "building_stats": "Building Statistics",
+ "ship_arrivals": "Ship Arrivals",
+ "nuke_stats": "Nuke Statistics",
+ "player_metrics": "Player Metrics",
+ "building": "Building",
+ "ship_type": "Ship Type",
+ "weapon": "Weapon",
+ "built": "Built",
+ "destroyed": "Destroyed",
+ "captured": "Captured",
+ "lost": "Lost",
+ "hits": "Hits",
+ "launched": "Launched",
+ "landed": "Landed",
+ "sent": "Sent",
+ "arrived": "Arrived",
+ "attack": "Attack",
+ "received": "Received",
+ "cancelled": "Cancelled",
+ "count": "Count",
+ "gold": "Gold",
+ "workers": "Workers",
+ "war": "War",
+ "trade": "Trade",
+ "steal": "Steal",
+ "unit": {
+ "city": "City",
+ "port": "Port",
+ "defp": "Defense Post",
+ "saml": "SAM Launcher",
+ "silo": "Missile Silo",
+ "wshp": "Warship",
+ "fact": "Factory",
+ "trade": "Trade Ship",
+ "trans": "Transport Ship",
+ "abomb": "Atom Bomb",
+ "hbomb": "Hydrogen Bomb",
+ "mirv": "MIRV",
+ "mirvw": "MIRV Warhead"
+ }
+ },
"game_list": {
"recent_games": "Recent Games",
"game_id": "Game ID",
diff --git a/src/client/components/baseComponents/stats/PlayerStatsTable.ts b/src/client/components/baseComponents/stats/PlayerStatsTable.ts
new file mode 100644
index 000000000..027a5a594
--- /dev/null
+++ b/src/client/components/baseComponents/stats/PlayerStatsTable.ts
@@ -0,0 +1,196 @@
+import { LitElement, css, html } from "lit";
+import { customElement, property } from "lit/decorators.js";
+import {
+ PlayerStats,
+ boatUnits,
+ bombUnits,
+ otherUnits,
+} from "../../../../core/StatsSchemas";
+import { renderNumber, translateText } from "../../../Utils";
+
+@customElement("player-stats-table")
+export class PlayerStatsTable extends LitElement {
+ static styles = css`
+ .table-container {
+ margin-top: 1rem;
+ width: 100%;
+ max-width: 28rem;
+ }
+ table {
+ width: 100%;
+ font-size: 0.95rem;
+ color: #ccc;
+ border-collapse: collapse;
+ }
+ th,
+ td {
+ padding: 0.25rem 0.5rem;
+ text-align: center;
+ }
+ th {
+ color: #bbb;
+ font-weight: 600;
+ }
+ .section-title {
+ color: #888;
+ font-size: 1rem;
+ font-weight: bold;
+ margin-bottom: 0.5rem;
+ }
+ `;
+
+ @property({ type: Object }) stats: PlayerStats;
+
+ render() {
+ return html`
+
+
+ ${translateText("player_stats_table.building_stats")}
+
+
+
+
+ |
+ ${translateText("player_stats_table.building")}
+ |
+ ${translateText("player_stats_table.built")} |
+ ${translateText("player_stats_table.destroyed")} |
+ ${translateText("player_stats_table.captured")} |
+ ${translateText("player_stats_table.lost")} |
+
+
+
+ ${otherUnits.map((key) => {
+ const built = this.stats?.units?.[key]?.[0] ?? 0n;
+ const destroyed = this.stats?.units?.[key]?.[1] ?? 0n;
+ const captured = this.stats?.units?.[key]?.[2] ?? 0n;
+ const lost = this.stats?.units?.[key]?.[3] ?? 0n;
+ return html`
+
+ | ${translateText(`player_stats_table.unit.${key}`)} |
+ ${renderNumber(built)} |
+ ${renderNumber(destroyed)} |
+ ${renderNumber(captured)} |
+ ${renderNumber(lost)} |
+
+ `;
+ })}
+
+
+
+
+
+ ${translateText("player_stats_table.ship_arrivals")}
+
+
+
+
+ |
+ ${translateText("player_stats_table.ship_type")}
+ |
+ ${translateText("player_stats_table.sent")} |
+ ${translateText("player_stats_table.destroyed")} |
+ ${translateText("player_stats_table.arrived")} |
+
+
+
+ ${boatUnits.map((key) => {
+ const sent = this.stats?.boats?.[key]?.[0] ?? 0n;
+ const arrived = this.stats?.boats?.[key]?.[1] ?? 0n;
+ const destroyed = this.stats?.boats?.[key]?.[3] ?? 0n;
+ return html`
+
+ | ${translateText(`player_stats_table.unit.${key}`)} |
+ ${renderNumber(sent)} |
+ ${renderNumber(destroyed)} |
+ ${renderNumber(arrived)} |
+
+ `;
+ })}
+
+
+
+
+
+ ${translateText("player_stats_table.nuke_stats")}
+
+
+
+
+ |
+ ${translateText("player_stats_table.weapon")}
+ |
+
+ ${translateText("player_stats_table.launched")}
+ |
+
+ ${translateText("player_stats_table.landed")}
+ |
+
+ ${translateText("player_stats_table.hits")}
+ |
+
+
+
+ ${bombUnits.map((bomb) => {
+ const launched = this.stats?.bombs?.[bomb]?.[0] ?? 0n;
+ const landed = this.stats?.bombs?.[bomb]?.[1] ?? 0n;
+ const intercepted = this.stats?.bombs?.[bomb]?.[2] ?? 0n;
+ return html`
+
+ | ${translateText(`player_stats_table.unit.${bomb}`)} |
+ ${renderNumber(launched)} |
+ ${renderNumber(landed)} |
+ ${renderNumber(intercepted)} |
+
+ `;
+ })}
+
+
+
+
+
+ ${translateText("player_stats_table.player_metrics")}
+
+
+
+
+ | ${translateText("player_stats_table.attack")} |
+ ${translateText("player_stats_table.sent")} |
+ ${translateText("player_stats_table.received")} |
+ ${translateText("player_stats_table.cancelled")} |
+
+
+
+
+ | ${translateText("player_stats_table.count")} |
+ ${renderNumber(this.stats?.attacks?.[0] ?? 0n)} |
+ ${renderNumber(this.stats?.attacks?.[1] ?? 0n)} |
+ ${renderNumber(this.stats?.attacks?.[2] ?? 0n)} |
+
+
+
+
+
+
+ | ${translateText("player_stats_table.gold")} |
+ ${translateText("player_stats_table.workers")} |
+ ${translateText("player_stats_table.war")} |
+ ${translateText("player_stats_table.trade")} |
+ ${translateText("player_stats_table.steal")} |
+
+
+
+
+ | ${translateText("player_stats_table.count")} |
+ ${renderNumber(this.stats?.gold?.[0] ?? 0n)} |
+ ${renderNumber(this.stats?.gold?.[1] ?? 0n)} |
+ ${renderNumber(this.stats?.gold?.[2] ?? 0n)} |
+ ${renderNumber(this.stats?.gold?.[3] ?? 0n)} |
+
+
+
+
+ `;
+ }
+}
diff --git a/src/core/StatsSchemas.ts b/src/core/StatsSchemas.ts
index 1a4a949ec..043b5dfd2 100644
--- a/src/core/StatsSchemas.ts
+++ b/src/core/StatsSchemas.ts
@@ -1,12 +1,8 @@
import { z } from "zod";
import { UnitType } from "./game/Game";
-export const BombUnitSchema = z.union([
- z.literal("abomb"),
- z.literal("hbomb"),
- z.literal("mirv"),
- z.literal("mirvw"),
-]);
+export const bombUnits = ["abomb", "hbomb", "mirv", "mirvw"] as const;
+export const BombUnitSchema = z.enum(bombUnits);
export type BombUnit = z.infer;
export type NukeType =
| UnitType.AtomBomb
@@ -21,7 +17,8 @@ export const unitTypeToBombUnit = {
[UnitType.MIRVWarhead]: "mirvw",
} as const satisfies Record;
-export const BoatUnitSchema = z.union([z.literal("trade"), z.literal("trans")]);
+export const boatUnits = ["trade", "trans"] as const;
+export const BoatUnitSchema = z.enum(boatUnits);
export type BoatUnit = z.infer;
export type BoatUnitType = UnitType.TradeShip | UnitType.TransportShip;
@@ -30,15 +27,16 @@ export type BoatUnitType = UnitType.TradeShip | UnitType.TransportShip;
// [UnitType.TransportShip]: "trans",
// } as const satisfies Record;
-export const OtherUnitSchema = z.union([
- z.literal("city"),
- z.literal("defp"),
- z.literal("port"),
- z.literal("wshp"),
- z.literal("silo"),
- z.literal("saml"),
- z.literal("fact"),
-]);
+export const otherUnits = [
+ "city",
+ "defp",
+ "port",
+ "wshp",
+ "silo",
+ "saml",
+ "fact",
+] as const;
+export const OtherUnitSchema = z.enum(otherUnits);
export type OtherUnit = z.infer;
export type OtherUnitType =
| UnitType.City