Files
OpenFrontIO/src/core/StatsSchemas.ts
DevelopingTom f367ea1940 Record human/nation/bot conquests (#2949)
## Description:

Conquests are currently mixing all player types.

This is not ideal as people wonders why a 50 player game can lead to
hundred of kills.
Having separate records can also help with achievements and better
balancing.

This PR splits the conquests record into 3 categories: human, nations
and bots.

It is linked to this infra PR:
https://github.com/openfrontio/infra/pull/246

<img width="895" height="497" alt="image"
src="https://github.com/user-attachments/assets/66e49100-8114-4406-84ab-d9627355956d"
/>

While the recorded data make a distinction between bots/nations, it's
only displayed here as a single "bot" category.

## 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

## Please put your Discord username so you can be contacted if a bug or
regression is found:

IngloriousTom
2026-01-18 20:51:12 -08:00

118 lines
4.1 KiB
TypeScript

import { z } from "zod";
import { UnitType } from "./game/Game";
export const bombUnits = ["abomb", "hbomb", "mirv", "mirvw"] as const;
export const BombUnitSchema = z.enum(bombUnits);
export type BombUnit = z.infer<typeof BombUnitSchema>;
export type NukeType =
| UnitType.AtomBomb
| UnitType.HydrogenBomb
| UnitType.MIRV
| UnitType.MIRVWarhead;
export const unitTypeToBombUnit = {
[UnitType.AtomBomb]: "abomb",
[UnitType.HydrogenBomb]: "hbomb",
[UnitType.MIRV]: "mirv",
[UnitType.MIRVWarhead]: "mirvw",
} as const satisfies Record<NukeType, BombUnit>;
export const boatUnits = ["trade", "trans"] as const;
export const BoatUnitSchema = z.enum(boatUnits);
export type BoatUnit = z.infer<typeof BoatUnitSchema>;
export type BoatUnitType = UnitType.TradeShip | UnitType.TransportShip;
// export const unitTypeToBoatUnit = {
// [UnitType.TradeShip]: "trade",
// [UnitType.TransportShip]: "trans",
// } as const satisfies Record<BoatUnitType, BoatUnit>;
export const otherUnits = [
"city",
"defp",
"port",
"wshp",
"silo",
"saml",
"fact",
] as const;
export const OtherUnitSchema = z.enum(otherUnits);
export type OtherUnit = z.infer<typeof OtherUnitSchema>;
export type OtherUnitType =
| UnitType.City
| UnitType.DefensePost
| UnitType.MissileSilo
| UnitType.Port
| UnitType.SAMLauncher
| UnitType.Warship
| UnitType.Factory;
export const unitTypeToOtherUnit = {
[UnitType.City]: "city",
[UnitType.DefensePost]: "defp",
[UnitType.MissileSilo]: "silo",
[UnitType.Port]: "port",
[UnitType.SAMLauncher]: "saml",
[UnitType.Warship]: "wshp",
[UnitType.Factory]: "fact",
} as const satisfies Record<OtherUnitType, OtherUnit>;
// Attacks
export const ATTACK_INDEX_SENT = 0; // Outgoing attack troops
export const ATTACK_INDEX_RECV = 1; // Incmoing attack troops
export const ATTACK_INDEX_CANCEL = 2; // Cancelled attack troops
// Player types
export const PLAYER_INDEX_HUMAN = 0;
export const PLAYER_INDEX_NATION = 1;
export const PLAYER_INDEX_BOT = 2;
// Boats
export const BOAT_INDEX_SENT = 0; // Boats launched
export const BOAT_INDEX_ARRIVE = 1; // Boats arrived
export const BOAT_INDEX_CAPTURE = 2; // Boats captured
export const BOAT_INDEX_DESTROY = 3; // Boats destroyed
// Bombs
export const BOMB_INDEX_LAUNCH = 0; // Bombs launched
export const BOMB_INDEX_LAND = 1; // Bombs landed
export const BOMB_INDEX_INTERCEPT = 2; // Bombs intercepted
// Gold
export const GOLD_INDEX_WORK = 0; // Gold earned by workers
export const GOLD_INDEX_WAR = 1; // Gold earned by conquering players
export const GOLD_INDEX_TRADE = 2; // Gold earned by trade ships
export const GOLD_INDEX_STEAL = 3; // Gold earned by capturing trade ships
export const GOLD_INDEX_TRAIN_SELF = 4; // Gold earned by own trains
export const GOLD_INDEX_TRAIN_OTHER = 5; // Gold earned by other players trains
// Other Units
export const OTHER_INDEX_BUILT = 0; // Structures and warships built
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
export const OTHER_INDEX_UPGRADE = 4; // Structures upgraded
export const BigIntStringSchema = z.preprocess((val) => {
if (typeof val === "string" && /^-?\d+$/.test(val)) return BigInt(val);
if (typeof val === "bigint") return val;
return val;
}, z.bigint());
const AtLeastOneNumberSchema = BigIntStringSchema.array().min(1);
export type AtLeastOneNumber = z.infer<typeof AtLeastOneNumberSchema>;
export const PlayerStatsSchema = z
.object({
attacks: AtLeastOneNumberSchema.optional(),
betrayals: BigIntStringSchema.optional(),
killedAt: BigIntStringSchema.optional(),
conquests: AtLeastOneNumberSchema.optional(),
boats: z.partialRecord(BoatUnitSchema, AtLeastOneNumberSchema).optional(),
bombs: z.partialRecord(BombUnitSchema, AtLeastOneNumberSchema).optional(),
gold: AtLeastOneNumberSchema.optional(),
units: z.partialRecord(OtherUnitSchema, AtLeastOneNumberSchema).optional(),
})
.optional();
export type PlayerStats = z.infer<typeof PlayerStatsSchema>;