Extend winner schema (#1333)

## Description:

Extend the winner schema to support multi-player wins (vote for peace,
teams).

## Please complete the following:

- [x] I have added screenshots for all UI updates
- [x] I process any text displayed to the user through translateText()
and I've added it to the en.json file
- [x] I have added relevant tests to the test directory
- [x] I confirm I have thoroughly tested these changes and take full
responsibility for any bugs introduced
- [x] I understand that submitting code with bugs that could have been
caught through manual testing blocks releases and new features for all
contributors
This commit is contained in:
Scott Anderson
2025-07-03 17:23:55 -04:00
committed by GitHub
parent b923df5d08
commit 9fb9be142a
5 changed files with 32 additions and 23 deletions
+1 -10
View File
@@ -7,7 +7,6 @@ import {
GameStartInfo,
PlayerRecord,
ServerMessage,
Winner,
} from "../core/Schemas";
import { createGameRecord } from "../core/Util";
import { ServerConfig } from "../core/configuration/Config";
@@ -200,13 +199,6 @@ export class ClientGameRunner {
this.lastMessageTime = Date.now();
}
private getWinner(update: WinUpdate): Winner {
if (update.winner[0] !== "player") return update.winner;
const clientId = this.gameView.playerBySmallID(update.winner[1]).clientID();
if (clientId === null) return;
return ["player", clientId];
}
private saveGame(update: WinUpdate) {
if (this.myPlayer === null) {
return;
@@ -219,7 +211,6 @@ export class ClientGameRunner {
stats: update.allPlayersStats[this.lobby.clientID],
},
];
const winner = this.getWinner(update);
if (this.lobby.gameStartInfo === undefined) {
throw new Error("missing gameStartInfo");
@@ -232,7 +223,7 @@ export class ClientGameRunner {
[],
startTime(),
Date.now(),
winner,
update.winner,
);
endGame(record);
}
+5 -3
View File
@@ -249,7 +249,9 @@ export class WinModal extends LitElement implements Layer {
const updates = this.game.updatesSinceLastTick();
const winUpdates = updates !== null ? updates[GameUpdateType.Win] : [];
winUpdates.forEach((wu) => {
if (wu.winner[0] === "team") {
if (wu.winner === undefined) {
// ...
} else if (wu.winner[0] === "team") {
this.eventBus.emit(new SendWinnerEvent(wu.winner, wu.allPlayersStats));
if (wu.winner[1] === this.game.myPlayer()?.team()) {
this._title = translateText("win_modal.your_team");
@@ -260,8 +262,8 @@ export class WinModal extends LitElement implements Layer {
}
this.show();
} else {
const winner = this.game.playerBySmallID(wu.winner[1]);
if (!winner.isPlayer()) return;
const winner = this.game.playerByClientID(wu.winner[1]);
if (!winner?.isPlayer()) return;
const winnerClient = winner.clientID();
if (winnerClient !== null) {
this.eventBus.emit(
+2 -2
View File
@@ -393,8 +393,8 @@ export const GameStartInfoSchema = z.object({
export const WinnerSchema = z
.union([
z.tuple([z.literal("player"), ID]),
z.tuple([z.literal("team"), SafeString]),
z.tuple([z.literal("player"), ID]).rest(ID),
z.tuple([z.literal("team"), SafeString]).rest(ID),
])
.optional();
export type Winner = z.infer<typeof WinnerSchema>;
+22 -5
View File
@@ -1,5 +1,5 @@
import { Config } from "../configuration/Config";
import { AllPlayersStats, ClientID } from "../Schemas";
import { AllPlayersStats, ClientID, Winner } from "../Schemas";
import { simpleHash } from "../Util";
import { AllianceImpl } from "./AllianceImpl";
import { AllianceRequestImpl } from "./AllianceRequestImpl";
@@ -614,14 +614,31 @@ export class GameImpl implements Game {
setWinner(winner: Player | Team, allPlayersStats: AllPlayersStats): void {
this.addUpdate({
type: GameUpdateType.Win,
winner:
typeof winner === "string"
? ["team", winner]
: ["player", winner.smallID()],
winner: this.makeWinner(winner),
allPlayersStats,
});
}
private makeWinner(winner: string | Player): Winner | undefined {
if (typeof winner === "string") {
return [
"team",
winner,
...this.players()
.filter((p) => p.team() === winner && p.clientID() !== null)
.map((p) => p.clientID()!),
];
} else {
const clientId = winner.clientID();
if (clientId === null) return;
return [
"player",
clientId,
// TODO: Assists (vote for peace)
];
}
}
teams(): Team[] {
if (this._config.gameConfig().gameMode !== GameMode.Team) {
return [];
+2 -3
View File
@@ -1,4 +1,4 @@
import { AllPlayersStats, ClientID } from "../Schemas";
import { AllPlayersStats, ClientID, Winner } from "../Schemas";
import {
EmojiMessage,
GameUpdates,
@@ -232,8 +232,7 @@ export type DisplayChatMessageUpdate = {
export interface WinUpdate {
type: GameUpdateType.Win;
allPlayersStats: AllPlayersStats;
// Player id or team name.
winner: ["player", number] | ["team", Team];
winner: Winner;
}
export interface HashUpdate {