mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-07-13 22:59:02 +00:00
Convert stats to bigints (#909)
Fixes #880 ## Description: Convert numeric stat types to bigint, and serialize those values as strings. ## Please complete the following: - [x] I have added screenshots for all UI updates - [ ] 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 --------- Co-authored-by: Scott Anderson <662325+scottanderson@users.noreply.github.com>
This commit is contained in:
@@ -84,13 +84,13 @@ export const OTHER_INDEX_DESTROY = 1; // Structures and warships destroyed
|
||||
export const OTHER_INDEX_CAPTURE = 2; // Structures captured
|
||||
export const OTHER_INDEX_LOST = 3; // Structures/warships destroyed/captured by others
|
||||
|
||||
const AtLeastOneNumberSchema = z.number().array().min(1);
|
||||
const AtLeastOneNumberSchema = z.bigint().array().min(1);
|
||||
export type AtLeastOneNumber = z.infer<typeof AtLeastOneNumberSchema>;
|
||||
|
||||
export const PlayerStatsSchema = z
|
||||
.object({
|
||||
attacks: AtLeastOneNumberSchema.optional(),
|
||||
betrayals: z.number().positive().optional(),
|
||||
betrayals: z.bigint().positive().optional(),
|
||||
boats: z.record(BoatUnitSchema, AtLeastOneNumberSchema).optional(),
|
||||
bombs: z.record(BombUnitSchema, AtLeastOneNumberSchema).optional(),
|
||||
gold: AtLeastOneNumberSchema.optional(),
|
||||
|
||||
@@ -118,7 +118,7 @@ export interface PlayerUpdate {
|
||||
incomingAttacks: AttackUpdate[];
|
||||
outgoingAllianceRequests: PlayerID[];
|
||||
hasSpawned: boolean;
|
||||
betrayals?: number;
|
||||
betrayals?: bigint;
|
||||
}
|
||||
|
||||
export interface AllianceRequestUpdate {
|
||||
|
||||
+21
-9
@@ -7,13 +7,17 @@ export interface Stats {
|
||||
stats(): AllPlayersStats;
|
||||
|
||||
// Player attacks target
|
||||
attack(player: Player, target: Player | TerraNullius, troops: number): void;
|
||||
attack(
|
||||
player: Player,
|
||||
target: Player | TerraNullius,
|
||||
troops: number | bigint,
|
||||
): void;
|
||||
|
||||
// Player cancels attack on target
|
||||
attackCancel(
|
||||
player: Player,
|
||||
target: Player | TerraNullius,
|
||||
troops: number,
|
||||
troops: number | bigint,
|
||||
): void;
|
||||
|
||||
// Player betrays another player
|
||||
@@ -23,10 +27,14 @@ export interface Stats {
|
||||
boatSendTrade(player: Player, target: Player): void;
|
||||
|
||||
// Player's trade ship arrives at target, both players earn gold
|
||||
boatArriveTrade(player: Player, target: Player, gold: number): void;
|
||||
boatArriveTrade(player: Player, target: Player, gold: number | bigint): void;
|
||||
|
||||
// Player's trade ship, captured from target, arrives. Player earns gold.
|
||||
boatCapturedTrade(player: Player, target: Player, gold: number): void;
|
||||
boatCapturedTrade(
|
||||
player: Player,
|
||||
target: Player,
|
||||
gold: number | bigint,
|
||||
): void;
|
||||
|
||||
// Player destroys target's trade ship
|
||||
boatDestroyTrade(player: Player, target: Player): void;
|
||||
@@ -35,18 +43,22 @@ export interface Stats {
|
||||
boatSendTroops(
|
||||
player: Player,
|
||||
target: Player | TerraNullius,
|
||||
troops: number,
|
||||
troops: number | bigint,
|
||||
): void;
|
||||
|
||||
// Player's transport ship arrives at target with troops
|
||||
boatArriveTroops(
|
||||
player: Player,
|
||||
target: Player | TerraNullius,
|
||||
troops: number,
|
||||
troops: number | bigint,
|
||||
): void;
|
||||
|
||||
// Player destroys target's transport ship with troops
|
||||
boatDestroyTroops(player: Player, target: Player, troops: number): void;
|
||||
boatDestroyTroops(
|
||||
player: Player,
|
||||
target: Player,
|
||||
troops: number | bigint,
|
||||
): void;
|
||||
|
||||
// Player launches bomb at target
|
||||
bombLaunch(
|
||||
@@ -62,10 +74,10 @@ export interface Stats {
|
||||
bombIntercept(player: Player, attacker: Player, type: NukeType): void;
|
||||
|
||||
// Player earns gold from conquering tiles or trade ships from captured
|
||||
goldWar(player: Player, captured: Player, gold: number): void;
|
||||
goldWar(player: Player, captured: Player, gold: number | bigint): void;
|
||||
|
||||
// Player earns gold from workers
|
||||
goldWork(player: Player, gold: number): void;
|
||||
goldWork(player: Player, gold: number | bigint): void;
|
||||
|
||||
// Player builds a unit of type
|
||||
unitBuild(player: Player, type: OtherUnitType): void;
|
||||
|
||||
+47
-35
@@ -52,21 +52,21 @@ export class StatsImpl implements Stats {
|
||||
return data;
|
||||
}
|
||||
|
||||
private _addAttack(player: Player, index: number, value: number) {
|
||||
private _addAttack(player: Player, index: number, value: number | bigint) {
|
||||
const p = this._makePlayerStats(player);
|
||||
if (p === undefined) return;
|
||||
if (p.attacks === undefined) p.attacks = [0];
|
||||
while (p.attacks.length <= index) p.attacks.push(0);
|
||||
p.attacks[index] += value;
|
||||
if (p.attacks === undefined) p.attacks = [0n];
|
||||
while (p.attacks.length <= index) p.attacks.push(0n);
|
||||
p.attacks[index] += BigInt(value);
|
||||
}
|
||||
|
||||
private _addBetrayal(player: Player, value: number) {
|
||||
private _addBetrayal(player: Player, value: number | bigint) {
|
||||
const data = this._makePlayerStats(player);
|
||||
if (data === undefined) return;
|
||||
if (data.betrayals === undefined) {
|
||||
data.betrayals = value;
|
||||
data.betrayals = BigInt(value);
|
||||
} else {
|
||||
data.betrayals += value;
|
||||
data.betrayals += BigInt(value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,55 +74,59 @@ export class StatsImpl implements Stats {
|
||||
player: Player,
|
||||
type: BoatUnit,
|
||||
index: number,
|
||||
value: number,
|
||||
value: number | bigint,
|
||||
) {
|
||||
const p = this._makePlayerStats(player);
|
||||
if (p === undefined) return;
|
||||
if (p.boats === undefined) p.boats = { [type]: [0] };
|
||||
if (p.boats[type] === undefined) p.boats[type] = [0];
|
||||
while (p.boats[type].length <= index) p.boats[type].push(0);
|
||||
p.boats[type][index] += value;
|
||||
if (p.boats === undefined) p.boats = { [type]: [0n] };
|
||||
if (p.boats[type] === undefined) p.boats[type] = [0n];
|
||||
while (p.boats[type].length <= index) p.boats[type].push(0n);
|
||||
p.boats[type][index] += BigInt(value);
|
||||
}
|
||||
|
||||
private _addBomb(
|
||||
player: Player,
|
||||
nukeType: NukeType,
|
||||
index: number,
|
||||
value: number,
|
||||
value: number | bigint,
|
||||
): void {
|
||||
const type = unitTypeToBombUnit[nukeType];
|
||||
const p = this._makePlayerStats(player);
|
||||
if (p === undefined) return;
|
||||
if (p.bombs === undefined) p.bombs = { [type]: [0] };
|
||||
if (p.bombs[type] === undefined) p.bombs[type] = [0];
|
||||
while (p.bombs[type].length <= index) p.bombs[type].push(0);
|
||||
p.bombs[type][index] += value;
|
||||
if (p.bombs === undefined) p.bombs = { [type]: [0n] };
|
||||
if (p.bombs[type] === undefined) p.bombs[type] = [0n];
|
||||
while (p.bombs[type].length <= index) p.bombs[type].push(0n);
|
||||
p.bombs[type][index] += BigInt(value);
|
||||
}
|
||||
|
||||
private _addGold(player: Player, index: number, value: number) {
|
||||
private _addGold(player: Player, index: number, value: number | bigint) {
|
||||
const p = this._makePlayerStats(player);
|
||||
if (p === undefined) return;
|
||||
if (p.gold === undefined) p.gold = [0];
|
||||
while (p.gold.length <= index) p.gold.push(0);
|
||||
p.gold[index] += value;
|
||||
if (p.gold === undefined) p.gold = [0n];
|
||||
while (p.gold.length <= index) p.gold.push(0n);
|
||||
p.gold[index] += BigInt(value);
|
||||
}
|
||||
|
||||
private _addOtherUnit(
|
||||
player: Player,
|
||||
otherUnitType: OtherUnitType,
|
||||
index: number,
|
||||
value: number,
|
||||
value: number | bigint,
|
||||
) {
|
||||
const type = unitTypeToOtherUnit[otherUnitType];
|
||||
const p = this._makePlayerStats(player);
|
||||
if (p === undefined) return;
|
||||
if (p.units === undefined) p.units = { [type]: [0] };
|
||||
if (p.units[type] === undefined) p.units[type] = [0];
|
||||
while (p.units[type].length <= index) p.units[type].push(0);
|
||||
p.units[type][index] += value;
|
||||
if (p.units === undefined) p.units = { [type]: [0n] };
|
||||
if (p.units[type] === undefined) p.units[type] = [0n];
|
||||
while (p.units[type].length <= index) p.units[type].push(0n);
|
||||
p.units[type][index] += BigInt(value);
|
||||
}
|
||||
|
||||
attack(player: Player, target: Player | TerraNullius, troops: number): void {
|
||||
attack(
|
||||
player: Player,
|
||||
target: Player | TerraNullius,
|
||||
troops: number | bigint,
|
||||
): void {
|
||||
this._addAttack(player, ATTACK_INDEX_SENT, troops);
|
||||
if (target.isPlayer()) {
|
||||
this._addAttack(target, ATTACK_INDEX_RECV, troops);
|
||||
@@ -132,7 +136,7 @@ export class StatsImpl implements Stats {
|
||||
attackCancel(
|
||||
player: Player,
|
||||
target: Player | TerraNullius,
|
||||
troops: number,
|
||||
troops: number | bigint,
|
||||
): void {
|
||||
this._addAttack(player, ATTACK_INDEX_CANCEL, troops);
|
||||
this._addAttack(player, ATTACK_INDEX_SENT, -troops);
|
||||
@@ -149,13 +153,17 @@ export class StatsImpl implements Stats {
|
||||
this._addBoat(player, "trade", BOAT_INDEX_SENT, 1);
|
||||
}
|
||||
|
||||
boatArriveTrade(player: Player, target: Player, gold: number): void {
|
||||
boatArriveTrade(player: Player, target: Player, gold: number | bigint): void {
|
||||
this._addBoat(player, "trade", BOAT_INDEX_ARRIVE, 1);
|
||||
this._addGold(player, GOLD_INDEX_TRADE, gold);
|
||||
this._addGold(target, GOLD_INDEX_TRADE, gold);
|
||||
}
|
||||
|
||||
boatCapturedTrade(player: Player, target: Player, gold: number): void {
|
||||
boatCapturedTrade(
|
||||
player: Player,
|
||||
target: Player,
|
||||
gold: number | bigint,
|
||||
): void {
|
||||
this._addBoat(player, "trade", BOAT_INDEX_CAPTURE, 1);
|
||||
this._addGold(player, GOLD_INDEX_STEAL, gold);
|
||||
}
|
||||
@@ -167,7 +175,7 @@ export class StatsImpl implements Stats {
|
||||
boatSendTroops(
|
||||
player: Player,
|
||||
target: Player | TerraNullius,
|
||||
troops: number,
|
||||
troops: number | bigint,
|
||||
): void {
|
||||
this._addBoat(player, "trans", BOAT_INDEX_SENT, 1);
|
||||
}
|
||||
@@ -175,12 +183,16 @@ export class StatsImpl implements Stats {
|
||||
boatArriveTroops(
|
||||
player: Player,
|
||||
target: Player | TerraNullius,
|
||||
troops: number,
|
||||
troops: number | bigint,
|
||||
): void {
|
||||
this._addBoat(player, "trans", BOAT_INDEX_ARRIVE, 1);
|
||||
}
|
||||
|
||||
boatDestroyTroops(player: Player, target: Player, troops: number): void {
|
||||
boatDestroyTroops(
|
||||
player: Player,
|
||||
target: Player,
|
||||
troops: number | bigint,
|
||||
): void {
|
||||
this._addBoat(player, "trans", BOAT_INDEX_DESTROY, 1);
|
||||
}
|
||||
|
||||
@@ -204,11 +216,11 @@ export class StatsImpl implements Stats {
|
||||
this._addBomb(player, type, BOMB_INDEX_INTERCEPT, 1);
|
||||
}
|
||||
|
||||
goldWork(player: Player, gold: number): void {
|
||||
goldWork(player: Player, gold: number | bigint): void {
|
||||
this._addGold(player, GOLD_INDEX_WORK, gold);
|
||||
}
|
||||
|
||||
goldWar(player: Player, captured: Player, gold: number): void {
|
||||
goldWar(player: Player, captured: Player, gold: number | bigint): void {
|
||||
this._addGold(player, GOLD_INDEX_WAR, gold);
|
||||
}
|
||||
|
||||
|
||||
+10
-3
@@ -60,7 +60,7 @@ async function archiveAnalyticsToR2(gameRecord: GameRecord) {
|
||||
await r2.putObject({
|
||||
Bucket: bucket,
|
||||
Key: `${analyticsFolder}/${analyticsKey}`,
|
||||
Body: JSON.stringify(analyticsData),
|
||||
Body: JSON.stringify(analyticsData, replacer),
|
||||
ContentType: "application/json",
|
||||
});
|
||||
|
||||
@@ -78,7 +78,7 @@ async function archiveAnalyticsToR2(gameRecord: GameRecord) {
|
||||
|
||||
async function archiveFullGameToR2(gameRecord: GameRecord) {
|
||||
// Create a deep copy to avoid modifying the original
|
||||
const recordCopy: GameRecord = JSON.parse(JSON.stringify(gameRecord));
|
||||
const recordCopy = structuredClone(gameRecord);
|
||||
|
||||
// Players may see this so make sure to clear PII
|
||||
recordCopy.info.players.forEach((p) => {
|
||||
@@ -89,7 +89,7 @@ async function archiveFullGameToR2(gameRecord: GameRecord) {
|
||||
await r2.putObject({
|
||||
Bucket: bucket,
|
||||
Key: `${gameFolder}/${recordCopy.info.gameID}`,
|
||||
Body: JSON.stringify(recordCopy),
|
||||
Body: JSON.stringify(recordCopy, replacer),
|
||||
ContentType: "application/json",
|
||||
});
|
||||
} catch (error) {
|
||||
@@ -147,3 +147,10 @@ export async function gameRecordExists(gameId: GameID): Promise<boolean> {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* JSON.stringify replacer function that converts bigint values to strings.
|
||||
*/
|
||||
export function replacer(_key: string, value: any): any {
|
||||
return typeof value === "bigint" ? value.toString() : value;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user