Merge branch 'v29'

This commit is contained in:
evanpelle
2026-01-16 10:20:47 -08:00
10 changed files with 138 additions and 54 deletions
+2
View File
@@ -228,6 +228,8 @@
"total_gold": "Total",
"all_gold": "All gold",
"trade": "Trade",
"train_trade": "Train",
"naval_trade": "Tradeship",
"conquest_gold": "Conquered player gold",
"stolen_gold": "Stolen with warships",
"num_of_conquests": "Number of conquered players",
+5 -3
View File
@@ -49,7 +49,9 @@ export class GameInfoModal extends LitElement {
title="${translateText("game_info_modal.title")}"
translationKey="main.game_info"
>
<div class="flex flex-col items-center px-25 text-center mb-4">
<div
class="h-full flex flex-col items-center px-25 text-center mb-4 scrollbar-thin scrollbar-thumb-white/20 scrollbar-track-transparent"
>
<div class="w-75 sm:w-125">
${this.isLoadingGame
? this.renderLoadingAnimation()
@@ -108,7 +110,7 @@ export class GameInfoModal extends LitElement {
const isUnusualThumbnailSize = hasUnusualThumbnailSize(info.config.gameMap);
return html`
<div
class="h-37.5 flex relative justify-between rounded-xl bg-blue-600 items-center"
class="h-37.5 flex relative justify-between rounded-xl bg-black/20 items-center"
>
${this.mapImage
? html`<img
@@ -140,7 +142,7 @@ export class GameInfoModal extends LitElement {
const bestScore =
this.rankedPlayers.length > 0 ? this.score(this.rankedPlayers[0]) : 0;
return html`
<ul class="">
<ul>
<ranking-header
.rankType=${this.rankType}
@sort=${this.sort}
+4 -2
View File
@@ -1,4 +1,6 @@
import version from "resources/version.txt?raw";
import { FetchGameMapLoader } from "../core/game/FetchGameMapLoader";
export const terrainMapFileLoader = new FetchGameMapLoader(`/maps`, version);
export const terrainMapFileLoader = new FetchGameMapLoader(
`/maps`,
window.GIT_COMMIT,
);
@@ -63,7 +63,7 @@ export class OModal extends LitElement {
render() {
const backdropClass = this.inline
? "relative z-10 w-full h-full flex items-stretch bg-transparent"
: "fixed inset-0 z-[9999] bg-black/70 flex items-center justify-center overflow-hidden";
: "fixed inset-0 z-[9999] bg-black/60 flex items-center justify-center overflow-hidden";
const wrapperClass = this.inline
? "relative flex flex-col w-full h-full m-0 max-w-full max-h-none shadow-none"
@@ -95,7 +95,7 @@ export class OModal extends LitElement {
</div>`}
${!this.hideHeader && this.title
? html`<div
class="px-[1.4rem] py-[1rem] pt-0 text-2xl font-bold text-white"
class="px-[1.4rem] py-[1rem] text-2xl font-bold text-white"
>
${this.title}
</div>`
@@ -2,6 +2,8 @@ import { AnalyticsRecord, PlayerRecord } from "../../../../core/Schemas";
import {
GOLD_INDEX_STEAL,
GOLD_INDEX_TRADE,
GOLD_INDEX_TRAIN_OTHER,
GOLD_INDEX_TRAIN_SELF,
GOLD_INDEX_WAR,
} from "../../../../core/StatsSchemas";
@@ -12,7 +14,8 @@ export enum RankType {
MIRV = "MIRV",
TotalGold = "TotalGold",
StolenGold = "StolenGold",
TradedGold = "TradedGold",
NavalTrade = "NavalTrade",
TrainTrade = "TrainTrade",
ConqueredGold = "ConqueredGold",
Lifetime = "Lifetime",
}
@@ -134,10 +137,15 @@ export class Ranking {
return Number(player.gold.reduce((sum, gold) => sum + gold, 0n));
case RankType.StolenGold:
return Number(player.gold[GOLD_INDEX_STEAL] ?? 0n);
case RankType.TradedGold:
case RankType.NavalTrade:
return Number(player.gold[GOLD_INDEX_TRADE] ?? 0n);
case RankType.ConqueredGold:
return Number(player.gold[GOLD_INDEX_WAR] ?? 0n);
case RankType.TrainTrade: {
const ownTrains = player.gold[GOLD_INDEX_TRAIN_SELF] ?? 0n;
const otherTrains = player.gold[GOLD_INDEX_TRAIN_OTHER] ?? 0n;
return Number(ownTrains + otherTrains);
}
}
}
@@ -1,5 +1,10 @@
import { LitElement, html } from "lit";
import { customElement, property } from "lit/decorators.js";
import {
GOLD_INDEX_TRADE,
GOLD_INDEX_TRAIN_OTHER,
GOLD_INDEX_TRAIN_SELF,
} from "src/core/StatsSchemas";
import { renderNumber } from "../../../Utils";
import { PlayerInfo, RankType } from "./GameInfoRanking";
@@ -22,15 +27,13 @@ export class PlayerRow extends LitElement {
const visibleBorder = player.winner || this.currentPlayer;
return html`
<li
class="${player.winner
? "bg-linear-to-r via-none from-sky-400 to-blue-700"
: "bg-slate-700"} border-2
class="${player.winner ? "bg-black/20" : "bg-black/20"} border-b-1
${player.winner
? "border-yellow-500"
? "border-yellow-500 border-1 box-content"
: visibleBorder
? "border-yellow-50"
: "border-yellow-50/0"}
relative pt-1 pb-1 pr-2 pl-2 sm:pl-5 sm:pr-5 mb-1.25 rounded-lg flex justify-between items-center hover:bg-slate-500 transition duration-150 ease-in-out"
? "border-white/5"
: "border-transparent"}
relative pt-1 pb-1 pr-2 pl-2 sm:pl-5 sm:pr-5 flex justify-between items-center hover:bg-white/[0.07] transition-colors duration-150 ease-in-out"
>
<div
class="font-bold text-right w-7.5 text-lg text-white absolute -left-10"
@@ -67,10 +70,12 @@ export class PlayerRow extends LitElement {
case RankType.MIRV:
return this.renderBombScore();
case RankType.TotalGold:
case RankType.TradedGold:
case RankType.ConqueredGold:
case RankType.StolenGold:
return this.renderGoldScore();
case RankType.NavalTrade:
case RankType.TrainTrade:
return this.renderTradeScore();
default:
return html``;
}
@@ -86,7 +91,7 @@ export class PlayerRow extends LitElement {
</div>
<div>
<div
class="font-bold rounded-[50%] size-7.5 leading-[1.6rem] border border-gray-200 text-center bg-white text-black"
class="font-bold rounded-[50%] size-7.5 leading-[1.6rem] border border-white/10 text-center bg-white/5 text-white/80"
>
${Number(this.score).toFixed(0)}
</div>
@@ -99,24 +104,25 @@ export class PlayerRow extends LitElement {
const width = Math.min(Math.max((this.score / bestScore) * 100, 0), 100);
return html`
<div class="w-full pr-2.5 m-auto">
<div class="h-1.75 bg-neutral-800 w-full">
<div class="h-1.75 bg-white/10 w-full">
<!-- bar background -->
<div
class="h-1.75 bg-white w-(--width)"
class="h-1.75 bg-blue-500/50 w-(--width)"
style="--width: ${width}%;"
></div>
</div>
</div>
`;
}
private renderBombType(value: number, highlight: boolean) {
private renderMultiScoreType(value: number, highlight: boolean) {
return html`
<div
class="${highlight
? "font-bold text-[18px]"
: ""} min-w-7.5 sm:min-w-15 inline-block text-center"
? "font-bold text-[18px] text-white/80"
: "leading-[24px] text-white/40"} min-w-7.5 sm:min-w-15 inline-block text-center"
>
${value}
${renderNumber(value)}
</div>
`;
}
@@ -124,17 +130,17 @@ export class PlayerRow extends LitElement {
private renderAllBombs() {
return html`
<div class="flex justify-between text-sm sm:pr-20">
${this.renderBombType(
${this.renderMultiScoreType(
this.player.atoms,
this.rankType === RankType.Atoms,
)}
/
${this.renderBombType(
${this.renderMultiScoreType(
this.player.hydros,
this.rankType === RankType.Hydros,
)}
/
${this.renderBombType(
${this.renderMultiScoreType(
this.player.mirv,
this.rankType === RankType.MIRV,
)}
@@ -142,9 +148,28 @@ export class PlayerRow extends LitElement {
`;
}
private renderAllTrades() {
const navalTrade = this.player.gold[GOLD_INDEX_TRADE] ?? 0n;
const ownTrainTrade = this.player.gold[GOLD_INDEX_TRAIN_SELF] ?? 0n;
const otherTrainTrade = this.player.gold[GOLD_INDEX_TRAIN_OTHER] ?? 0n;
return html`
<div class="flex justify-between text-sm align-baseline">
${this.renderMultiScoreType(
Number(ownTrainTrade + otherTrainTrade),
this.rankType === RankType.TrainTrade,
)}
/
${this.renderMultiScoreType(
Number(navalTrade),
this.rankType === RankType.NavalTrade,
)}
</div>
`;
}
private renderBombScore() {
return html`
<div class="flex gap-3 items-center w-full">
<div class="flex gap-3 items-center align-baseline w-full">
${this.renderPlayerIcon()}
<div class="flex flex-col sm:flex-row gap-1 text-left w-full">
${this.renderPlayerName()} ${this.renderAllBombs()}
@@ -157,13 +182,12 @@ export class PlayerRow extends LitElement {
return html`
<div class="flex gap-3 items-center">
${this.renderPlayerIcon()}
<div class="text-left w-31.25 sm:w-62.5">
${this.renderPlayerName()}
</div>
<div class="text-left w-31.25 sm:w-50">${this.renderPlayerName()}</div>
</div>
<div class="flex gap-2">
<div
class="font-bold rounded-md w-15 shrink-0 h-7.5 text-sm sm:w-25 sm:h-7.5 leading-[1.9rem] text-center"
class="font-bold rounded-md w-15 text-white/80 text-sm sm:w-25 leading-[1.9rem] text-center"
>
${renderNumber(this.score)}
</div>
@@ -172,12 +196,32 @@ export class PlayerRow extends LitElement {
`;
}
private renderTradeScore() {
return html`
<div class="flex flex-col sm:flex-row gap-1 text-left w-full">
<div class="flex gap-3 items-center">
${this.renderPlayerIcon()}
<div class="text-left w-31.25 sm:w-50">
${this.renderPlayerName()}
</div>
</div>
<div class="flex gap-2 justify-between items-center w-full">
<div class="rounded-md text-sm leading-[1.9rem] text-center w-full">
${this.renderAllTrades()}
</div>
<img src="/images/GoldCoinIcon.svg" class="w-5 size-3.5 sm:size-5" />
</div>
</div>
`;
}
private renderPlayerName() {
return html`
<div class="flex gap-1 items-center w-50 shrink-0">
${this.player.tag ? this.renderTag(this.player.tag) : ""}
<div
class="text-xs sm:text-sm font-bold text-ellipsis w-37.5 shrink-0 overflow-hidden whitespace-nowrap"
class="text-xs sm:text-sm font-bold tracking-wide text-white/80 text-ellipsis w-37.5 shrink-0 overflow-hidden whitespace-nowrap"
>
${this.player.username}
</div>
@@ -188,7 +232,7 @@ export class PlayerRow extends LitElement {
private renderTag(tag: string) {
return html`
<div
class="bg-white text-black rounded-lg sm:rounded-xl border border-gray-200 text-xs leading-3 sm:leading-4.5 text-blue-900 h-3.75 px-1 sm:h-5 sm:px-2 font-bold"
class="px-2.5 py-1 rounded bg-blue-500/10 border border-blue-500/20 text-blue-300 font-bold text-xs tracking-wide group-hover:bg-blue-500/20 transition-colors"
>
${tag}
</div>
@@ -7,8 +7,10 @@ const economyRankings = new Set([
RankType.TotalGold,
RankType.StolenGold,
RankType.ConqueredGold,
RankType.TradedGold,
RankType.NavalTrade,
RankType.TrainTrade,
]);
const tradeRankings = new Set([RankType.NavalTrade, RankType.TrainTrade]);
const bombRankings = new Set([RankType.Atoms, RankType.Hydros, RankType.MIRV]);
const warRankings = new Set([
RankType.Conquests,
@@ -18,6 +20,7 @@ const warRankings = new Set([
]);
const isEconomyRanking = (t: RankType) => economyRankings.has(t);
const isTradeRanking = (t: RankType) => tradeRankings.has(t);
const isBombRanking = (t: RankType) => bombRankings.has(t);
const isWarRanking = (t: RankType) => warRankings.has(t);
@@ -54,9 +57,9 @@ export class RankingControls extends LitElement {
private renderButton(type: RankType, active: boolean, label: string) {
return html`
<button
class="rounded-lg bg-blue-600 text-white text-lg p-3 hover:bg-blue-400 ${active
? "active outline-2 outline-white font-bold"
: ""}"
class="px-6 py-2 text-xs font-bold transition-all duration-200 rounded-lg uppercase tracking-widest hover:text-white hover:bg-white/5 border ${active
? "bg-blue-500/20 text-blue-400 border-blue-500/30 shadow-[0_0_15px_rgba(59,130,246,0.2)]"
: "text-white/40 border-transparent"}"
@click=${() => this.onSort(type)}
>
${translateText(label)}
@@ -87,7 +90,6 @@ export class RankingControls extends LitElement {
if (!isEconomyRanking(this.rankType)) return "";
const econButtons = [
[RankType.TradedGold, "game_info_modal.trade"],
[RankType.StolenGold, "game_info_modal.pirate"],
[RankType.ConqueredGold, "game_info_modal.conquered"],
[RankType.TotalGold, "game_info_modal.total_gold"],
@@ -95,6 +97,11 @@ export class RankingControls extends LitElement {
return html`
<div class="flex justify-center gap-3 pb-1">
${this.renderSubButton(
RankType.NavalTrade,
isTradeRanking(this.rankType),
"game_info_modal.trade",
)}
${econButtons.map(([type, label]) =>
this.renderSubButton(type as RankType, this.rankType === type, label),
)}
@@ -106,8 +113,8 @@ export class RankingControls extends LitElement {
return html`
<button
@click=${() => this.onSort(type)}
class="rounded-md bg-blue-50 text-black text-sm p-2 hover:bg-blue-200 ${active
? "outline-2 outline-white font-bold"
class="text-[10px] font-bold uppercase tracking-wider bg-white/5 border border-white/10 hover:bg-white/20 px-3 py-1 rounded text-white/60 hover:text-white transition-colors ${active
? "outline-1 outline-white/80 font-bold"
: ""}"
>
${translateText(label)}
@@ -14,7 +14,7 @@ export class RankingHeader extends LitElement {
render() {
return html`
<li
class="text-lg bg-gray-800 font-bold relative pt-2 pb-2 pr-5 pl-5 mb-1.25 rounded-md flex justify-between items-center"
class="text-lg border-white/5 bg-white/[0.02] text-white/60 text-xs uppercase tracking-wider relative pt-2 pb-2 pr-5 pl-5 flex justify-between items-center"
>
${this.renderHeaderContent()}
</li>
@@ -36,17 +36,17 @@ export class RankingHeader extends LitElement {
case RankType.MIRV:
return html`
<div class="flex justify-between sm:px-17.5 w-full">
${this.renderBombHeaderButton(
${this.renderMultipleChoiceHeaderButton(
translateText("game_info_modal.atoms"),
RankType.Atoms,
)}
/
${this.renderBombHeaderButton(
${this.renderMultipleChoiceHeaderButton(
translateText("game_info_modal.hydros"),
RankType.Hydros,
)}
/
${this.renderBombHeaderButton(
${this.renderMultipleChoiceHeaderButton(
translateText("game_info_modal.mirv"),
RankType.MIRV,
)}
@@ -56,10 +56,21 @@ export class RankingHeader extends LitElement {
return html`<div class="w-full">
${translateText("game_info_modal.all_gold")}
</div>`;
case RankType.TradedGold:
return html`<div class="w-full">
${translateText("game_info_modal.trade")}
</div>`;
case RankType.NavalTrade:
case RankType.TrainTrade:
return html`
<div class="flex justify-between sm:px-17.5 w-full">
${this.renderMultipleChoiceHeaderButton(
translateText("game_info_modal.train_trade"),
RankType.TrainTrade,
)}
/
${this.renderMultipleChoiceHeaderButton(
translateText("game_info_modal.naval_trade"),
RankType.NavalTrade,
)}
</div>
`;
case RankType.ConqueredGold:
return html`<div class="w-full">
${translateText("game_info_modal.conquest_gold")}
@@ -74,7 +85,7 @@ export class RankingHeader extends LitElement {
}
}
private renderBombHeaderButton(label: string, type: RankType) {
private renderMultipleChoiceHeaderButton(label: string, type: RankType) {
return html`
<button
@click=${() => this.onSort(type)}
+1
View File
@@ -92,6 +92,7 @@ export function createGrid(
const tile = game.ref(cell.x, cell.y);
grid[x - scaledBoundingBox.min.x][y - scaledBoundingBox.min.y] =
game.isLake(tile) ||
game.isShore(tile) ||
game.owner(tile) === player ||
game.hasFallout(tile);
}
+11 -4
View File
@@ -13,6 +13,8 @@ import { AnalyticsRecord } from "../src/core/Schemas";
import {
GOLD_INDEX_STEAL,
GOLD_INDEX_TRADE,
GOLD_INDEX_TRAIN_OTHER,
GOLD_INDEX_TRAIN_SELF,
GOLD_INDEX_WAR,
} from "../src/core/StatsSchemas";
@@ -55,7 +57,7 @@ describe("Ranking class", () => {
stats: {
units: { port: [2n, 0n, 0n, 2n] },
conquests: 5n,
gold: [0n, 100n, 20n, 0n], // total 120
gold: [0n, 100n, 20n, 0n, 15n, 5n], // total 140
bombs: {
abomb: [1n],
hbomb: [1n],
@@ -70,7 +72,7 @@ describe("Ranking class", () => {
stats: {
units: { city: [2n, 0n, 0n, 2n] },
conquests: 8n,
gold: [0n, 50n, 10n, 5n], // total 65
gold: [0n, 50n, 10n, 5n], // total 65, no train trade
bombs: {
abomb: [0n],
hbomb: [2n],
@@ -86,7 +88,7 @@ describe("Ranking class", () => {
// no units, but has conquests/killedAt to count as played
conquests: 8n,
killedAt: BigInt(600),
gold: [0n, 10n, 2n, 10n], // total 22
gold: [0n, 10n, 2n, 10n, 0n, 5n], // total 27
bombs: {},
},
persistentID: null,
@@ -178,9 +180,14 @@ describe("Ranking class", () => {
expect(r.score(p1, RankType.StolenGold)).toBe(
Number(p1.gold[GOLD_INDEX_STEAL] ?? 0n),
);
expect(r.score(p1, RankType.TradedGold)).toBe(
expect(r.score(p1, RankType.NavalTrade)).toBe(
Number(p1.gold[GOLD_INDEX_TRADE] ?? 0n),
);
const ownTrain = p1.gold[GOLD_INDEX_TRAIN_SELF] ?? 0n;
const otherTrain = p1.gold[GOLD_INDEX_TRAIN_OTHER] ?? 0n;
expect(r.score(p1, RankType.TrainTrade)).toBe(
Number(ownTrain + otherTrain),
);
expect(r.score(p1, RankType.ConqueredGold)).toBe(
Number(p1.gold[GOLD_INDEX_WAR] ?? 0n),
);