diff --git a/resources/lang/en.json b/resources/lang/en.json index 981361068..1fef3cfd8 100644 --- a/resources/lang/en.json +++ b/resources/lang/en.json @@ -726,7 +726,8 @@ "warships": "Warships", "cities": "Cities", "show_control": "Show Control", - "show_units": "Show Units" + "show_units": "Show Units", + "show_competitive": "Show Competitive" }, "events_display": { "events": "Events", diff --git a/src/client/HostLobbyModal.ts b/src/client/HostLobbyModal.ts index a6bcd83eb..e4529f2dd 100644 --- a/src/client/HostLobbyModal.ts +++ b/src/client/HostLobbyModal.ts @@ -782,7 +782,7 @@ export class HostLobbyModal extends BaseModal { : undefined, startingGold: this.startingGold === true ? this.startingGoldValue : undefined, - competitiveScoring: this.competitiveScoring || undefined, + competitiveScoring: this.competitiveScoring ? true : undefined, } satisfies Partial, }, bubbles: true, diff --git a/src/client/graphics/PlayerIcons.ts b/src/client/graphics/PlayerIcons.ts index 706e25cc7..18cf8d007 100644 --- a/src/client/graphics/PlayerIcons.ts +++ b/src/client/graphics/PlayerIcons.ts @@ -93,13 +93,9 @@ export function getPlayerIcons( // Crown icon: in competitive mode, all members of the crown team get it; // otherwise only the individual first-place player. - if ( - crownTeam !== null && - crownTeam !== undefined && - player.team() === crownTeam - ) { + if (crownTeam !== null && player.team() === crownTeam) { icons.push({ id: "crown", kind: "image", src: crownIcon }); - } else if (crownTeam === null || crownTeam === undefined) { + } else if (crownTeam === null) { if (player === firstPlace) { icons.push({ id: "crown", kind: "image", src: crownIcon }); } diff --git a/src/client/graphics/layers/TeamStats.ts b/src/client/graphics/layers/TeamStats.ts index 9ae01e494..95ed56993 100644 --- a/src/client/graphics/layers/TeamStats.ts +++ b/src/client/graphics/layers/TeamStats.ts @@ -269,7 +269,7 @@ export class TeamStats extends LitElement implements Layer { case "control": return translateText("leaderboard.show_units"); case "units": - return "Show Competitive"; + return translateText("leaderboard.show_competitive"); case "competitive": return translateText("leaderboard.show_control"); } diff --git a/src/core/execution/AttackExecution.ts b/src/core/execution/AttackExecution.ts index cd6721a23..cd3f8fd46 100644 --- a/src/core/execution/AttackExecution.ts +++ b/src/core/execution/AttackExecution.ts @@ -18,6 +18,7 @@ import { assertNever } from "../Util"; import { FlatBinaryHeap } from "./utils/FlatBinaryHeap"; // adjust path if needed const malusForRetreat = 25; +const CROWN_ATTACK_BONUS = 0.25; export class AttackExecution implements Execution { private active: boolean = true; private toConquer = new FlatBinaryHeap(); @@ -133,7 +134,7 @@ export class AttackExecution implements Execution { targetTeam === crownTeam && attackerTeam !== crownTeam ) { - const bonus = Math.floor(this.attack.troops() * 0.25); + const bonus = Math.floor(this.attack.troops() * CROWN_ATTACK_BONUS); this.attack.setTroops(this.attack.troops() + bonus); } } diff --git a/src/core/execution/CrownTrackingExecution.ts b/src/core/execution/CrownTrackingExecution.ts deleted file mode 100644 index 4b0595afd..000000000 --- a/src/core/execution/CrownTrackingExecution.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { Execution, Game, GameMode, Team } from "../game/Game"; - -/** - * Tracks which team holds the "crown" (most total tiles) and accumulates - * crown ticks per team for competition scoring. - * - * Crown time contributes 20% of a team's competition score. - * Only active in Team game mode. - */ -export class CrownTrackingExecution implements Execution { - private active = true; - private mg: Game | null = null; - - init(mg: Game, _ticks: number) { - this.mg = mg; - // Only relevant in team mode - if (mg.config().gameConfig().gameMode !== GameMode.Team) { - this.active = false; - } - } - - tick(ticks: number) { - if (ticks % 10 !== 0) return; - if (this.mg === null) throw new Error("Not initialized"); - - const crown = this.computeCrownTeam(); - if (crown !== null) { - this.mg.addCrownTick(crown, 10); - } - } - - private computeCrownTeam(): Team | null { - if (this.mg === null) return null; - return this.mg.crownTeam(); - } - - isActive(): boolean { - return this.active; - } - - activeDuringSpawnPhase(): boolean { - return false; - } -} diff --git a/src/core/execution/WinCheckExecution.ts b/src/core/execution/WinCheckExecution.ts index 30724d51e..c55622aba 100644 --- a/src/core/execution/WinCheckExecution.ts +++ b/src/core/execution/WinCheckExecution.ts @@ -28,6 +28,8 @@ export class WinCheckExecution implements Execution { init(mg: Game, ticks: number) { this.mg = mg; + // Seed alive teams so eliminations on the very first tick are detected. + this.knownAliveTeams = this.computeAliveTeams(); } tick(ticks: number) { @@ -46,17 +48,23 @@ export class WinCheckExecution implements Execution { } } - private trackTeamEliminations(): void { - if (this.mg === null) return; - - const currentAlive = new Set(); + private computeAliveTeams(): Set { + const alive = new Set(); + if (this.mg === null) return alive; for (const player of this.mg.players()) { const team = player.team(); if (team === null || team === ColoredTeams.Bot) continue; if (player.numTilesOwned() > 0) { - currentAlive.add(team); + alive.add(team); } } + return alive; + } + + private trackTeamEliminations(): void { + if (this.mg === null) return; + + const currentAlive = this.computeAliveTeams(); // Record teams that just died for (const team of this.knownAliveTeams) { diff --git a/src/core/game/GameImpl.ts b/src/core/game/GameImpl.ts index 6ee555bd3..f63afb03a 100644 --- a/src/core/game/GameImpl.ts +++ b/src/core/game/GameImpl.ts @@ -1277,7 +1277,7 @@ export class GameImpl implements Game { } allTeamCrownTicks(): ReadonlyMap { - return this._teamCrownTicks; + return new Map(this._teamCrownTicks); } addCrownTick(team: Team, amount: number): void { @@ -1299,7 +1299,7 @@ export class GameImpl implements Game { } teamEliminationOrder(): Team[] { - return this._teamEliminationOrder; + return [...this._teamEliminationOrder]; } recordTeamElimination(team: Team): void {