mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-22 08:48:10 +00:00
feat: save stats to local storage
Victory lose, player stats, lobby config are stored to local storage (for later use)
This commit is contained in:
@@ -22,6 +22,7 @@ import { getConfig, getServerConfig } from "../core/configuration/Config";
|
||||
import { GameView, PlayerView } from "../core/game/GameView";
|
||||
import { GameUpdateViewData } from "../core/game/GameUpdates";
|
||||
import { UserSettings } from "../core/game/UserSettings";
|
||||
import { LocalPersistantStats } from "./LocalPersistantStats";
|
||||
|
||||
export interface LobbyConfig {
|
||||
flag: () => string;
|
||||
@@ -120,6 +121,7 @@ export async function createClientGame(
|
||||
config,
|
||||
gameMap.gameMap,
|
||||
lobbyConfig.clientID,
|
||||
lobbyConfig.gameID,
|
||||
);
|
||||
|
||||
consolex.log("going to init path finder");
|
||||
@@ -137,7 +139,7 @@ export async function createClientGame(
|
||||
);
|
||||
|
||||
return new ClientGameRunner(
|
||||
lobbyConfig.clientID,
|
||||
lobbyConfig,
|
||||
eventBus,
|
||||
gameRenderer,
|
||||
new InputHandler(canvas, eventBus),
|
||||
@@ -148,6 +150,7 @@ export async function createClientGame(
|
||||
}
|
||||
|
||||
export class ClientGameRunner {
|
||||
private localPersistantsStats = new LocalPersistantStats();
|
||||
private myPlayer: PlayerView;
|
||||
private isActive = false;
|
||||
|
||||
@@ -155,7 +158,7 @@ export class ClientGameRunner {
|
||||
private hasJoined = false;
|
||||
|
||||
constructor(
|
||||
private clientID: ClientID,
|
||||
private lobby: LobbyConfig,
|
||||
private eventBus: EventBus,
|
||||
private renderer: GameRenderer,
|
||||
private input: InputHandler,
|
||||
@@ -165,6 +168,7 @@ export class ClientGameRunner {
|
||||
) {}
|
||||
|
||||
public start() {
|
||||
this.localPersistantsStats.startGame(this.lobby);
|
||||
consolex.log("starting client game");
|
||||
this.isActive = true;
|
||||
this.eventBus.on(MouseUpEvent, (e) => this.inputEvent(e));
|
||||
@@ -173,7 +177,7 @@ export class ClientGameRunner {
|
||||
this.input.initialize();
|
||||
this.worker.start((gu: GameUpdateViewData | ErrorUpdate) => {
|
||||
if ("errMsg" in gu) {
|
||||
showErrorModal(gu.errMsg, gu.stack, this.clientID);
|
||||
showErrorModal(gu.errMsg, gu.stack, this.lobby.clientID);
|
||||
return;
|
||||
}
|
||||
gu.updates[GameUpdateType.Hash].forEach((hu: HashUpdate) => {
|
||||
@@ -217,7 +221,7 @@ export class ClientGameRunner {
|
||||
showErrorModal(
|
||||
`desync from server: ${JSON.stringify(message)}`,
|
||||
"",
|
||||
this.clientID,
|
||||
this.lobby.clientID,
|
||||
);
|
||||
}
|
||||
if (message.type == "turn") {
|
||||
@@ -269,7 +273,7 @@ export class ClientGameRunner {
|
||||
return;
|
||||
}
|
||||
if (this.myPlayer == null) {
|
||||
this.myPlayer = this.gameView.playerByClientID(this.clientID);
|
||||
this.myPlayer = this.gameView.playerByClientID(this.lobby.clientID);
|
||||
if (this.myPlayer == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
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";
|
||||
|
||||
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 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);
|
||||
}
|
||||
|
||||
return stats;
|
||||
}
|
||||
|
||||
public startGame(lobby: LobbyConfig) {
|
||||
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));
|
||||
}
|
||||
|
||||
public endGame(
|
||||
id: GameID,
|
||||
playerStats: PlayerStats,
|
||||
outcome: GameStat["outcome"],
|
||||
) {
|
||||
if (typeof localStorage === "undefined") {
|
||||
return;
|
||||
}
|
||||
|
||||
const stats = this.getStats();
|
||||
const gameStat = stats.games[id];
|
||||
if (!gameStat) {
|
||||
consolex.log("game not found");
|
||||
return;
|
||||
}
|
||||
|
||||
gameStat.outcome = outcome;
|
||||
gameStat.playerStats = playerStats;
|
||||
localStorage.setItem("stats", JSON.stringify(stats));
|
||||
}
|
||||
}
|
||||
@@ -22,12 +22,14 @@ 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;
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
AllPlayers,
|
||||
Player,
|
||||
PlayerActions,
|
||||
PlayerID,
|
||||
UnitType,
|
||||
} from "../../../core/game/Game";
|
||||
import { TileRef } from "../../../core/game/GameMap";
|
||||
@@ -133,8 +134,8 @@ export class PlayerPanel extends LitElement implements Layer {
|
||||
this.requestUpdate();
|
||||
}
|
||||
|
||||
getTotalNukesSent(): number {
|
||||
const stats = this.actions.interaction?.stats;
|
||||
getTotalNukesSent(otherId: PlayerID): number {
|
||||
const stats = this.g.player(otherId).stats();
|
||||
if (!stats) {
|
||||
return 0;
|
||||
}
|
||||
@@ -189,7 +190,7 @@ export class PlayerPanel extends LitElement implements Layer {
|
||||
<button
|
||||
@click=${this.handleClose}
|
||||
class="absolute -top-2 -right-2 w-6 h-6 flex items-center justify-center
|
||||
bg-red-500 hover:bg-red-600 text-white rounded-full
|
||||
bg-red-500 hover:bg-red-600 text-white rounded-full
|
||||
text-sm font-bold transition-colors"
|
||||
>
|
||||
✕
|
||||
@@ -199,7 +200,7 @@ export class PlayerPanel extends LitElement implements Layer {
|
||||
<!-- Name section -->
|
||||
<div class="flex items-center gap-1 lg:gap-2">
|
||||
<div
|
||||
class="px-4 h-8 lg:h-10 flex items-center justify-center
|
||||
class="px-4 h-8 lg:h-10 flex items-center justify-center
|
||||
bg-opacity-50 bg-gray-700 text-opacity-90 text-white
|
||||
rounded text-sm lg:text-xl w-full"
|
||||
>
|
||||
@@ -251,7 +252,7 @@ export class PlayerPanel extends LitElement implements Layer {
|
||||
Nukes sent by them to you
|
||||
</div>
|
||||
<div class="bg-opacity-50 bg-gray-700 rounded p-2 text-white">
|
||||
${this.getTotalNukesSent()}
|
||||
${this.getTotalNukesSent(other.id())}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -261,7 +262,7 @@ export class PlayerPanel extends LitElement implements Layer {
|
||||
? html`<button
|
||||
@click=${(e) => this.handleTargetClick(e, other)}
|
||||
class="w-10 h-10 flex items-center justify-center
|
||||
bg-opacity-50 bg-gray-700 hover:bg-opacity-70
|
||||
bg-opacity-50 bg-gray-700 hover:bg-opacity-70
|
||||
text-white rounded-lg transition-colors"
|
||||
>
|
||||
<img src=${targetIcon} alt="Target" class="w-6 h-6" />
|
||||
|
||||
@@ -9,6 +9,7 @@ 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 {
|
||||
@@ -27,6 +28,7 @@ export class WinModal extends LitElement implements Layer {
|
||||
private rand: PseudoRandom;
|
||||
|
||||
private hasShownDeathModal = false;
|
||||
private localPersistantsStats = new LocalPersistantStats();
|
||||
|
||||
@state()
|
||||
isVisible = false;
|
||||
@@ -220,6 +222,14 @@ 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()) {
|
||||
@@ -235,9 +245,11 @@ export class WinModal extends LitElement implements Layer {
|
||||
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();
|
||||
});
|
||||
|
||||
@@ -168,7 +168,6 @@ export class GameRunner {
|
||||
canBreakAlliance: player.isAlliedWith(other),
|
||||
canDonate: player.canDonate(other),
|
||||
canEmbargo: !player.hasEmbargoAgainst(other),
|
||||
stats: this.game.stats().getPlayerStats(other.id()),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -421,7 +421,6 @@ export interface PlayerInteraction {
|
||||
canTarget: boolean;
|
||||
canDonate: boolean;
|
||||
canEmbargo: boolean;
|
||||
stats: PlayerStats;
|
||||
}
|
||||
|
||||
export interface EmojiMessage {
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
UnitType,
|
||||
} from "./Game";
|
||||
import { TileRef, TileUpdate } from "./GameMap";
|
||||
import { PlayerStats } from "./Stats";
|
||||
|
||||
export interface GameUpdateViewData {
|
||||
tick: number;
|
||||
@@ -105,6 +106,7 @@ export interface PlayerUpdate {
|
||||
outgoingAttacks: AttackUpdate[];
|
||||
incomingAttacks: AttackUpdate[];
|
||||
outgoingAllianceRequests: PlayerID[];
|
||||
stats: PlayerStats;
|
||||
}
|
||||
|
||||
export interface AllianceRequestUpdate {
|
||||
|
||||
@@ -24,12 +24,13 @@ import {
|
||||
UnitInfo,
|
||||
UnitType,
|
||||
} from "./Game";
|
||||
import { ClientID } from "../Schemas";
|
||||
import { ClientID, GameID } 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;
|
||||
@@ -220,6 +221,9 @@ export class PlayerView {
|
||||
this.id(),
|
||||
);
|
||||
}
|
||||
stats(): PlayerStats {
|
||||
return this.data.stats;
|
||||
}
|
||||
}
|
||||
|
||||
export class GameView implements GameMap {
|
||||
@@ -240,6 +244,7 @@ export class GameView implements GameMap {
|
||||
private _config: Config,
|
||||
private _map: GameMap,
|
||||
private _myClientID: ClientID,
|
||||
private _gameID: GameID,
|
||||
) {
|
||||
this.lastUpdate = {
|
||||
tick: 0,
|
||||
@@ -487,4 +492,7 @@ export class GameView implements GameMap {
|
||||
numTilesWithFallout(): number {
|
||||
return this._map.numTilesWithFallout();
|
||||
}
|
||||
gameID(): GameID {
|
||||
return this._gameID;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -150,6 +150,7 @@ export class PlayerImpl implements Player {
|
||||
}) as AttackUpdate,
|
||||
),
|
||||
outgoingAllianceRequests: outgoingAllianceRequests,
|
||||
stats: this.mg.stats().getPlayerStats(this.id()),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user