mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-21 11:40:42 +00:00
feat: remove LocalPersistantStats so we locally save GameRecords
GameRecords also now include PlayerStats
This commit is contained in:
@@ -2,7 +2,13 @@ import { PlayerID, GameMapType, Difficulty, GameType } from "../core/game/Game";
|
||||
import { EventBus } from "../core/EventBus";
|
||||
import { createRenderer, GameRenderer } from "./graphics/GameRenderer";
|
||||
import { InputHandler, MouseUpEvent } from "./InputHandler";
|
||||
import { ClientID, GameConfig, GameID, ServerMessage } from "../core/Schemas";
|
||||
import {
|
||||
ClientID,
|
||||
GameConfig,
|
||||
GameID,
|
||||
ServerMessage,
|
||||
PlayerRecord,
|
||||
} from "../core/Schemas";
|
||||
import { loadTerrainMap } from "../core/game/TerrainMapLoader";
|
||||
import {
|
||||
SendAttackIntentEvent,
|
||||
@@ -15,6 +21,7 @@ import {
|
||||
ErrorUpdate,
|
||||
GameUpdateType,
|
||||
HashUpdate,
|
||||
WinUpdate,
|
||||
} from "../core/game/GameUpdates";
|
||||
import { WorkerClient } from "../core/worker/WorkerClient";
|
||||
import { consolex, initRemoteSender } from "../core/Consolex";
|
||||
@@ -23,6 +30,8 @@ import { GameView, PlayerView } from "../core/game/GameView";
|
||||
import { GameUpdateViewData } from "../core/game/GameUpdates";
|
||||
import { UserSettings } from "../core/game/UserSettings";
|
||||
import { LocalPersistantStats } from "./LocalPersistantStats";
|
||||
import { CreateGameRecord } from "../core/Util";
|
||||
import { getPersistentIDFromCookie } from "./Main";
|
||||
|
||||
export interface LobbyConfig {
|
||||
serverConfig: ServerConfig;
|
||||
@@ -54,19 +63,21 @@ export function joinLobby(
|
||||
);
|
||||
|
||||
const userSettings: UserSettings = new UserSettings();
|
||||
let gameConfig: GameConfig = null;
|
||||
if (lobbyConfig.gameType == GameType.Singleplayer) {
|
||||
gameConfig = {
|
||||
gameType: GameType.Singleplayer,
|
||||
gameMap: lobbyConfig.map,
|
||||
difficulty: lobbyConfig.difficulty,
|
||||
disableNPCs: lobbyConfig.disableNPCs,
|
||||
bots: lobbyConfig.bots,
|
||||
infiniteGold: lobbyConfig.infiniteGold,
|
||||
infiniteTroops: lobbyConfig.infiniteTroops,
|
||||
instantBuild: lobbyConfig.instantBuild,
|
||||
};
|
||||
}
|
||||
const gameConfig: GameConfig = {
|
||||
gameType: lobbyConfig.gameType,
|
||||
gameMap: lobbyConfig.map,
|
||||
difficulty: lobbyConfig.difficulty,
|
||||
disableNPCs: lobbyConfig.disableNPCs,
|
||||
bots: lobbyConfig.bots,
|
||||
infiniteGold: lobbyConfig.infiniteGold,
|
||||
infiniteTroops: lobbyConfig.infiniteTroops,
|
||||
instantBuild: lobbyConfig.instantBuild,
|
||||
};
|
||||
LocalPersistantStats.startGame(
|
||||
lobbyConfig.gameID,
|
||||
lobbyConfig.playerID,
|
||||
gameConfig,
|
||||
);
|
||||
|
||||
const transport = new Transport(
|
||||
lobbyConfig,
|
||||
@@ -138,6 +149,7 @@ export async function createClientGame(
|
||||
);
|
||||
|
||||
return new ClientGameRunner(
|
||||
gameConfig,
|
||||
lobbyConfig,
|
||||
eventBus,
|
||||
gameRenderer,
|
||||
@@ -149,7 +161,6 @@ export async function createClientGame(
|
||||
}
|
||||
|
||||
export class ClientGameRunner {
|
||||
private localPersistantsStats = new LocalPersistantStats();
|
||||
private myPlayer: PlayerView;
|
||||
private isActive = false;
|
||||
|
||||
@@ -157,6 +168,7 @@ export class ClientGameRunner {
|
||||
private hasJoined = false;
|
||||
|
||||
constructor(
|
||||
private gameConfig: GameConfig,
|
||||
private lobby: LobbyConfig,
|
||||
private eventBus: EventBus,
|
||||
private renderer: GameRenderer,
|
||||
@@ -166,8 +178,30 @@ export class ClientGameRunner {
|
||||
private gameView: GameView,
|
||||
) {}
|
||||
|
||||
private saveGame(update: WinUpdate) {
|
||||
const players: PlayerRecord[] = [
|
||||
{
|
||||
ip: null,
|
||||
persistentID: getPersistentIDFromCookie(),
|
||||
username: this.lobby.playerName(),
|
||||
clientID: this.lobby.clientID,
|
||||
},
|
||||
];
|
||||
const record = CreateGameRecord(
|
||||
this.lobby.gameID,
|
||||
this.gameConfig,
|
||||
players,
|
||||
// Not saving turns locally
|
||||
[],
|
||||
LocalPersistantStats.startTime(),
|
||||
Date.now(),
|
||||
this.gameView.playerBySmallID(update.winnerID).id(),
|
||||
update.allPlayersStats,
|
||||
);
|
||||
LocalPersistantStats.endGame(record);
|
||||
}
|
||||
|
||||
public start() {
|
||||
this.localPersistantsStats.startGame(this.lobby);
|
||||
consolex.log("starting client game");
|
||||
this.isActive = true;
|
||||
this.eventBus.on(MouseUpEvent, (e) => this.inputEvent(e));
|
||||
@@ -184,6 +218,10 @@ export class ClientGameRunner {
|
||||
});
|
||||
this.gameView.update(gu);
|
||||
this.renderer.tick();
|
||||
|
||||
if (gu.updates[GameUpdateType.Win].length > 0) {
|
||||
this.saveGame(gu.updates[GameUpdateType.Win][0]);
|
||||
}
|
||||
});
|
||||
const worker = this.worker;
|
||||
const keepWorkerAlive = () => {
|
||||
|
||||
@@ -1,88 +1,63 @@
|
||||
import { consolex } from "../core/Consolex";
|
||||
import { Difficulty, GameMapType, GameType } from "../core/game/Game";
|
||||
import { PlayerStats } from "../core/game/Stats";
|
||||
import { ClientID, GameID } from "../core/Schemas";
|
||||
import { LobbyConfig } from "./ClientGameRunner";
|
||||
import { PlayerID } from "../core/game/Game";
|
||||
import { GameConfig, GameID, GameRecord } from "../core/Schemas";
|
||||
|
||||
export interface GameStat {
|
||||
lobby: {
|
||||
clientID: ClientID;
|
||||
persistentID: string;
|
||||
map: GameMapType | null;
|
||||
gameType: GameType;
|
||||
difficulty: Difficulty | null;
|
||||
infiniteGold: boolean | null;
|
||||
infiniteTroops: boolean | null;
|
||||
instantBuild: boolean | null;
|
||||
bots: number | null;
|
||||
disableNPCs: boolean | null;
|
||||
};
|
||||
playerStats?: PlayerStats;
|
||||
outcome?: "victory" | "defeat";
|
||||
}
|
||||
|
||||
export class PersistantStats {
|
||||
// Can be used to handle breaking changes
|
||||
version: "v0.0.1";
|
||||
games: {
|
||||
[key: GameID]: GameStat;
|
||||
export interface LocalStatsData {
|
||||
[key: GameID]: {
|
||||
playerId: PlayerID;
|
||||
lobby: GameConfig;
|
||||
// Only once the game is over
|
||||
gameRecord?: GameRecord;
|
||||
};
|
||||
}
|
||||
|
||||
export class LocalPersistantStats {
|
||||
private getStats() {
|
||||
const statsStr = localStorage.getItem("stats");
|
||||
let stats: PersistantStats;
|
||||
if (!statsStr) {
|
||||
stats = { version: "v0.0.1", games: {} };
|
||||
} else {
|
||||
stats = JSON.parse(statsStr);
|
||||
}
|
||||
export namespace LocalPersistantStats {
|
||||
let _startTime: number;
|
||||
|
||||
return stats;
|
||||
function getStats(): LocalStatsData {
|
||||
const statsStr = localStorage.getItem("game-records");
|
||||
return statsStr ? JSON.parse(statsStr) : {};
|
||||
}
|
||||
|
||||
public startGame(lobby: LobbyConfig) {
|
||||
function save(stats: LocalStatsData) {
|
||||
// To execute asynchronously
|
||||
setTimeout(
|
||||
() => localStorage.setItem("game-records", JSON.stringify(stats)),
|
||||
0,
|
||||
);
|
||||
}
|
||||
|
||||
// The user can quit the game anytime so better save the lobby as soon as the
|
||||
// game starts.
|
||||
export function startGame(id: GameID, playerId: PlayerID, lobby: GameConfig) {
|
||||
if (typeof localStorage === "undefined") {
|
||||
return;
|
||||
}
|
||||
|
||||
const stats = this.getStats();
|
||||
stats.games[lobby.gameID] = {
|
||||
lobby: {
|
||||
clientID: lobby.clientID,
|
||||
persistentID: lobby.persistentID,
|
||||
map: lobby.map,
|
||||
gameType: lobby.gameType,
|
||||
difficulty: lobby.difficulty,
|
||||
infiniteGold: lobby.infiniteGold,
|
||||
infiniteTroops: lobby.infiniteTroops,
|
||||
instantBuild: lobby.instantBuild,
|
||||
bots: lobby.bots,
|
||||
disableNPCs: lobby.disableNPCs,
|
||||
},
|
||||
};
|
||||
localStorage.setItem("stats", JSON.stringify(stats));
|
||||
_startTime = Date.now();
|
||||
const stats = getStats();
|
||||
stats[id] = { playerId, lobby };
|
||||
save(stats);
|
||||
}
|
||||
|
||||
public endGame(
|
||||
id: GameID,
|
||||
playerStats: PlayerStats,
|
||||
outcome: GameStat["outcome"],
|
||||
) {
|
||||
export function startTime() {
|
||||
return _startTime;
|
||||
}
|
||||
|
||||
export function endGame(gameRecord: GameRecord) {
|
||||
if (typeof localStorage === "undefined") {
|
||||
return;
|
||||
}
|
||||
|
||||
const stats = this.getStats();
|
||||
const gameStat = stats.games[id];
|
||||
const stats = getStats();
|
||||
const gameStat = stats[gameRecord.id];
|
||||
|
||||
if (!gameStat) {
|
||||
consolex.log("game not found");
|
||||
consolex.log("LocalPersistantStats: game not found");
|
||||
return;
|
||||
}
|
||||
|
||||
gameStat.outcome = outcome;
|
||||
gameStat.playerStats = playerStats;
|
||||
localStorage.setItem("stats", JSON.stringify(stats));
|
||||
gameStat.gameRecord = gameRecord;
|
||||
save(stats);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import { Config, GameEnv, ServerConfig } from "../core/configuration/Config";
|
||||
import { consolex } from "../core/Consolex";
|
||||
import { GameEvent } from "../core/EventBus";
|
||||
import {
|
||||
AllPlayersStats,
|
||||
ClientID,
|
||||
ClientMessage,
|
||||
ClientMessageSchema,
|
||||
@@ -17,20 +18,19 @@ import {
|
||||
} from "../core/Schemas";
|
||||
import { CreateGameRecord, generateID } from "../core/Util";
|
||||
import { LobbyConfig } from "./ClientGameRunner";
|
||||
import { LocalPersistantStats } from "./LocalPersistantStats";
|
||||
import { getPersistentIDFromCookie } from "./Main";
|
||||
|
||||
export class LocalServer {
|
||||
private turns: Turn[] = [];
|
||||
private intents: Intent[] = [];
|
||||
private startedAt: number;
|
||||
private localPersistantsStats = new LocalPersistantStats();
|
||||
|
||||
private endTurnIntervalID;
|
||||
|
||||
private paused = false;
|
||||
|
||||
private winner: ClientID | null = null;
|
||||
private allPlayersStats: AllPlayersStats = {};
|
||||
|
||||
constructor(
|
||||
private serverConfig: ServerConfig,
|
||||
@@ -81,6 +81,7 @@ export class LocalServer {
|
||||
}
|
||||
if (clientMsg.type == "winner") {
|
||||
this.winner = clientMsg.winner;
|
||||
this.allPlayersStats = clientMsg.allPlayersStats;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,6 +121,7 @@ export class LocalServer {
|
||||
this.startedAt,
|
||||
Date.now(),
|
||||
this.winner,
|
||||
this.allPlayersStats,
|
||||
);
|
||||
// Clear turns because beacon only supports up to 64kb
|
||||
record.turns = [];
|
||||
|
||||
@@ -25,6 +25,7 @@ import {
|
||||
ClientLogMessageSchema,
|
||||
ClientSendWinnerSchema,
|
||||
ClientMessageSchema,
|
||||
AllPlayersStats,
|
||||
} from "../core/Schemas";
|
||||
import { LobbyConfig } from "./ClientGameRunner";
|
||||
import { LocalServer } from "./LocalServer";
|
||||
@@ -122,7 +123,10 @@ export class SendSetTargetTroopRatioEvent implements GameEvent {
|
||||
}
|
||||
|
||||
export class SendWinnerEvent implements GameEvent {
|
||||
constructor(public readonly winner: ClientID) {}
|
||||
constructor(
|
||||
public readonly winner: ClientID,
|
||||
public readonly allPlayersStats: AllPlayersStats,
|
||||
) {}
|
||||
}
|
||||
export class SendHashEvent implements GameEvent {
|
||||
constructor(
|
||||
@@ -480,6 +484,7 @@ export class Transport {
|
||||
persistentID: this.lobbyConfig.persistentID,
|
||||
gameID: this.lobbyConfig.gameID,
|
||||
winner: event.winner,
|
||||
allPlayersStats: event.allPlayersStats,
|
||||
});
|
||||
this.sendMsg(JSON.stringify(msg));
|
||||
} else {
|
||||
|
||||
@@ -9,7 +9,6 @@ import { PseudoRandom } from "../../../core/PseudoRandom";
|
||||
import { simpleHash } from "../../../core/Util";
|
||||
import { EventBus } from "../../../core/EventBus";
|
||||
import { SendWinnerEvent } from "../../Transport";
|
||||
import { GameStat, LocalPersistantStats } from "../../LocalPersistantStats";
|
||||
|
||||
// Add this at the top of your file
|
||||
declare global {
|
||||
@@ -28,7 +27,6 @@ export class WinModal extends LitElement implements Layer {
|
||||
private rand: PseudoRandom;
|
||||
|
||||
private hasShownDeathModal = false;
|
||||
private localPersistantsStats = new LocalPersistantStats();
|
||||
|
||||
@state()
|
||||
isVisible = false;
|
||||
@@ -222,14 +220,6 @@ export class WinModal extends LitElement implements Layer {
|
||||
this.rand = new PseudoRandom(simpleHash(this.game.myClientID()));
|
||||
}
|
||||
|
||||
private updateGameStats(outcome: GameStat["outcome"]) {
|
||||
this.localPersistantsStats.endGame(
|
||||
this.game.gameID(),
|
||||
this.game.myPlayer().stats(),
|
||||
outcome,
|
||||
);
|
||||
}
|
||||
|
||||
tick() {
|
||||
const myPlayer = this.game.myPlayer();
|
||||
if (!this.hasShownDeathModal && myPlayer && !myPlayer.isAlive()) {
|
||||
@@ -240,15 +230,15 @@ export class WinModal extends LitElement implements Layer {
|
||||
}
|
||||
this.game.updatesSinceLastTick()[GameUpdateType.Win].forEach((wu) => {
|
||||
const winner = this.game.playerBySmallID(wu.winnerID) as PlayerView;
|
||||
this.eventBus.emit(new SendWinnerEvent(winner.clientID()));
|
||||
this.eventBus.emit(
|
||||
new SendWinnerEvent(winner.clientID(), wu.allPlayersStats),
|
||||
);
|
||||
if (winner == this.game.myPlayer()) {
|
||||
this._title = "You Won!";
|
||||
this.won = true;
|
||||
this.updateGameStats("victory");
|
||||
} else {
|
||||
this._title = `${winner.name()} has won!`;
|
||||
this.won = false;
|
||||
this.updateGameStats("defeat");
|
||||
}
|
||||
this.show();
|
||||
});
|
||||
|
||||
@@ -77,6 +77,9 @@ export type ClientHashMessage = z.infer<typeof ClientHashSchema>;
|
||||
export type PlayerRecord = z.infer<typeof PlayerRecordSchema>;
|
||||
export type GameRecord = z.infer<typeof GameRecordSchema>;
|
||||
|
||||
export type AllPlayersStats = z.infer<typeof AllPlayersStatsSchema>;
|
||||
export type PlayerStats = z.infer<typeof PlayerStatsSchema>;
|
||||
|
||||
const PlayerTypeSchema = z.nativeEnum(PlayerType);
|
||||
|
||||
export interface GameInfo {
|
||||
@@ -132,6 +135,21 @@ const ID = z
|
||||
.regex(/^[a-zA-Z0-9]+$/)
|
||||
.length(8);
|
||||
|
||||
const NukesEnum = z.enum([
|
||||
"Atom Bomb",
|
||||
"Hydrogen Bomb",
|
||||
"MIRV",
|
||||
"MIRV Warhead",
|
||||
]);
|
||||
|
||||
const NukeStatsSchema = z.record(NukesEnum, z.number());
|
||||
|
||||
export const PlayerStatsSchema = z.object({
|
||||
sentNukes: z.record(ID, NukeStatsSchema),
|
||||
});
|
||||
|
||||
export const AllPlayersStatsSchema = z.record(ID, PlayerStatsSchema);
|
||||
|
||||
// Zod schemas
|
||||
const BaseIntentSchema = z.object({
|
||||
type: z.enum([
|
||||
@@ -313,6 +331,7 @@ const ClientBaseMessageSchema = z.object({
|
||||
export const ClientSendWinnerSchema = ClientBaseMessageSchema.extend({
|
||||
type: z.literal("winner"),
|
||||
winner: ID.nullable(),
|
||||
allPlayersStats: AllPlayersStatsSchema,
|
||||
});
|
||||
|
||||
export const ClientHashSchema = ClientBaseMessageSchema.extend({
|
||||
@@ -371,4 +390,6 @@ export const GameRecordSchema = z.object({
|
||||
num_turns: z.number(),
|
||||
turns: z.array(TurnSchema),
|
||||
winner: ID.nullable(),
|
||||
allPlayersStats: z.record(ID, PlayerStatsSchema),
|
||||
version: z.enum(["v0.0.1"]),
|
||||
});
|
||||
|
||||
@@ -3,11 +3,13 @@ import twemoji from "twemoji";
|
||||
import DOMPurify from "dompurify";
|
||||
import { Cell, Game, Player, Unit } from "./game/Game";
|
||||
import {
|
||||
AllPlayersStats,
|
||||
ClientID,
|
||||
GameConfig,
|
||||
GameID,
|
||||
GameRecord,
|
||||
PlayerRecord,
|
||||
PlayerStats,
|
||||
Turn,
|
||||
} from "./Schemas";
|
||||
import { customAlphabet, nanoid } from "nanoid";
|
||||
@@ -262,6 +264,7 @@ export function CreateGameRecord(
|
||||
start: number,
|
||||
end: number,
|
||||
winner: ClientID | null,
|
||||
allPlayersStats: AllPlayersStats,
|
||||
): GameRecord {
|
||||
const record: GameRecord = {
|
||||
id: id,
|
||||
@@ -270,6 +273,8 @@ export function CreateGameRecord(
|
||||
endTimestampMS: end,
|
||||
date: new Date().toISOString().split("T")[0],
|
||||
turns: [],
|
||||
allPlayersStats,
|
||||
version: "v0.0.1",
|
||||
};
|
||||
|
||||
for (const turn of turns) {
|
||||
|
||||
@@ -33,7 +33,7 @@ export class WinCheckExecution implements Execution {
|
||||
(max.numTilesOwned() / numTilesWithoutFallout) * 100 >
|
||||
this.mg.config().percentageTilesOwnedToWin()
|
||||
) {
|
||||
this.mg.setWinner(max);
|
||||
this.mg.setWinner(max, this.mg.stats().stats());
|
||||
console.log(`${max.name()} has won the game`);
|
||||
this.active = false;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Config } from "../configuration/Config";
|
||||
import { GameEvent } from "../EventBus";
|
||||
import { PlayerView } from "./GameView";
|
||||
import { ClientID, GameConfig, GameID } from "../Schemas";
|
||||
import { ClientID, GameConfig, GameID, AllPlayersStats } from "../Schemas";
|
||||
import { GameMap, GameMapImpl, TileRef } from "./GameMap";
|
||||
import {
|
||||
GameUpdate,
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
PlayerUpdate,
|
||||
UnitUpdate,
|
||||
} from "./GameUpdates";
|
||||
import { PlayerStats, Stats } from "./Stats";
|
||||
import { Stats } from "./Stats";
|
||||
|
||||
export type PlayerID = string;
|
||||
export type Tick = number;
|
||||
@@ -378,7 +378,7 @@ export interface Game extends GameMap {
|
||||
ticks(): Tick;
|
||||
inSpawnPhase(): boolean;
|
||||
executeNextTick(): GameUpdates;
|
||||
setWinner(winner: Player): void;
|
||||
setWinner(winner: Player, allPlayersStats: AllPlayersStats): void;
|
||||
config(): Config;
|
||||
|
||||
// Units
|
||||
|
||||
@@ -24,7 +24,7 @@ import { PlayerImpl } from "./PlayerImpl";
|
||||
import { TerraNulliusImpl } from "./TerraNulliusImpl";
|
||||
import { AllianceRequestImpl } from "./AllianceRequestImpl";
|
||||
import { AllianceImpl } from "./AllianceImpl";
|
||||
import { ClientID, GameConfig } from "../Schemas";
|
||||
import { ClientID, AllPlayersStats } from "../Schemas";
|
||||
import { MessageType } from "./Game";
|
||||
import { UnitImpl } from "./UnitImpl";
|
||||
import { consolex } from "../Consolex";
|
||||
@@ -516,10 +516,11 @@ export class GameImpl implements Game {
|
||||
});
|
||||
}
|
||||
|
||||
setWinner(winner: Player): void {
|
||||
setWinner(winner: Player, allPlayersStats: AllPlayersStats): void {
|
||||
this.addUpdate({
|
||||
type: GameUpdateType.Win,
|
||||
winnerID: winner.smallID(),
|
||||
allPlayersStats,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ClientID } from "../Schemas";
|
||||
import { ClientID, PlayerStats, AllPlayersStats } from "../Schemas";
|
||||
import {
|
||||
AllianceRequest,
|
||||
EmojiMessage,
|
||||
@@ -12,7 +12,6 @@ import {
|
||||
UnitType,
|
||||
} from "./Game";
|
||||
import { TileRef, TileUpdate } from "./GameMap";
|
||||
import { PlayerStats } from "./Stats";
|
||||
|
||||
export interface GameUpdateViewData {
|
||||
tick: number;
|
||||
@@ -156,6 +155,7 @@ export interface DisplayMessageUpdate {
|
||||
|
||||
export interface WinUpdate {
|
||||
type: GameUpdateType.Win;
|
||||
allPlayersStats: AllPlayersStats;
|
||||
winnerID: number;
|
||||
}
|
||||
|
||||
|
||||
@@ -24,13 +24,12 @@ import {
|
||||
UnitInfo,
|
||||
UnitType,
|
||||
} from "./Game";
|
||||
import { ClientID, GameID } from "../Schemas";
|
||||
import { ClientID, GameID, PlayerStats } from "../Schemas";
|
||||
import { TerraNulliusImpl } from "./TerraNulliusImpl";
|
||||
import { WorkerClient } from "../worker/WorkerClient";
|
||||
import { GameMap, GameMapImpl, TileRef, TileUpdate } from "./GameMap";
|
||||
import { GameUpdateViewData } from "./GameUpdates";
|
||||
import { DefenseGrid } from "./DefensePostGrid";
|
||||
import { PlayerStats } from "./Stats";
|
||||
|
||||
export class UnitView {
|
||||
public _wasUpdated = true;
|
||||
|
||||
@@ -1,15 +1,8 @@
|
||||
import { AllPlayersStats, PlayerStats } from "../Schemas";
|
||||
import { NukeType, PlayerID } from "./Game";
|
||||
|
||||
export interface PlayerStats {
|
||||
sentNukes: {
|
||||
// target
|
||||
[key: PlayerID]: {
|
||||
[key in NukeType]: number;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export interface Stats {
|
||||
increaseNukeCount(sender: PlayerID, target: PlayerID, type: NukeType): void;
|
||||
getPlayerStats(player: PlayerID): PlayerStats;
|
||||
stats(): AllPlayersStats;
|
||||
}
|
||||
|
||||
@@ -1,13 +1,9 @@
|
||||
import { NukeType, Player, PlayerID, UnitType } from "./Game";
|
||||
import { PlayerStats, Stats } from "./Stats";
|
||||
|
||||
interface StatsInternalData {
|
||||
// player
|
||||
[key: PlayerID]: PlayerStats;
|
||||
}
|
||||
import { AllPlayersStats, PlayerStats } from "../Schemas";
|
||||
import { NukeType, PlayerID, UnitType } from "./Game";
|
||||
import { Stats } from "./Stats";
|
||||
|
||||
export class StatsImpl implements Stats {
|
||||
data: StatsInternalData = {};
|
||||
data: AllPlayersStats = {};
|
||||
|
||||
_createUserData(sender: PlayerID, target: PlayerID): void {
|
||||
if (!this.data[sender]) {
|
||||
@@ -31,4 +27,8 @@ export class StatsImpl implements Stats {
|
||||
getPlayerStats(player: PlayerID): PlayerStats {
|
||||
return this.data[player];
|
||||
}
|
||||
|
||||
stats() {
|
||||
return this.data;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { RateLimiterMemory } from "rate-limiter-flexible";
|
||||
import WebSocket from "ws";
|
||||
import {
|
||||
AllPlayersStats,
|
||||
ClientID,
|
||||
ClientMessage,
|
||||
ClientMessageSchema,
|
||||
@@ -45,6 +46,8 @@ export class GameServer {
|
||||
private lastPingUpdate = 0;
|
||||
|
||||
private winner: ClientID | null = null;
|
||||
// This field is currently only filled at victory
|
||||
private allPlayersStats: AllPlayersStats = {};
|
||||
|
||||
constructor(
|
||||
public readonly id: string,
|
||||
@@ -162,6 +165,7 @@ export class GameServer {
|
||||
}
|
||||
if (clientMsg.type == "winner") {
|
||||
this.winner = clientMsg.winner;
|
||||
this.allPlayersStats = clientMsg.allPlayersStats;
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(
|
||||
@@ -298,6 +302,7 @@ export class GameServer {
|
||||
this._startTime,
|
||||
Date.now(),
|
||||
this.winner,
|
||||
this.allPlayersStats,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user