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")} +
+ + + + + + + + + + + + ${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.building")} + ${translateText("player_stats_table.built")}${translateText("player_stats_table.destroyed")}${translateText("player_stats_table.captured")}${translateText("player_stats_table.lost")}
${translateText(`player_stats_table.unit.${key}`)}${renderNumber(built)}${renderNumber(destroyed)}${renderNumber(captured)}${renderNumber(lost)}
+
+
+
+ ${translateText("player_stats_table.ship_arrivals")} +
+ + + + + + + + + + + ${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.ship_type")} + ${translateText("player_stats_table.sent")}${translateText("player_stats_table.destroyed")}${translateText("player_stats_table.arrived")}
${translateText(`player_stats_table.unit.${key}`)}${renderNumber(sent)}${renderNumber(destroyed)}${renderNumber(arrived)}
+
+
+
+ ${translateText("player_stats_table.nuke_stats")} +
+ + + + + + + + + + + ${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.weapon")} + + ${translateText("player_stats_table.launched")} + + ${translateText("player_stats_table.landed")} + + ${translateText("player_stats_table.hits")} +
${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