mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-23 20:43:36 +00:00
f367ea1940
## 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
299 lines
7.9 KiB
TypeScript
299 lines
7.9 KiB
TypeScript
import { AllPlayersStats } from "../Schemas";
|
|
import {
|
|
ATTACK_INDEX_CANCEL,
|
|
ATTACK_INDEX_RECV,
|
|
ATTACK_INDEX_SENT,
|
|
BOAT_INDEX_ARRIVE,
|
|
BOAT_INDEX_CAPTURE,
|
|
BOAT_INDEX_DESTROY,
|
|
BOAT_INDEX_SENT,
|
|
BoatUnit,
|
|
BOMB_INDEX_INTERCEPT,
|
|
BOMB_INDEX_LAND,
|
|
BOMB_INDEX_LAUNCH,
|
|
GOLD_INDEX_STEAL,
|
|
GOLD_INDEX_TRADE,
|
|
GOLD_INDEX_TRAIN_OTHER,
|
|
GOLD_INDEX_TRAIN_SELF,
|
|
GOLD_INDEX_WAR,
|
|
GOLD_INDEX_WORK,
|
|
NukeType,
|
|
OTHER_INDEX_BUILT,
|
|
OTHER_INDEX_CAPTURE,
|
|
OTHER_INDEX_DESTROY,
|
|
OTHER_INDEX_LOST,
|
|
OTHER_INDEX_UPGRADE,
|
|
OtherUnitType,
|
|
PLAYER_INDEX_BOT,
|
|
PLAYER_INDEX_HUMAN,
|
|
PLAYER_INDEX_NATION,
|
|
PlayerStats,
|
|
unitTypeToBombUnit,
|
|
unitTypeToOtherUnit,
|
|
} from "../StatsSchemas";
|
|
import { Player, PlayerType, TerraNullius, UnitType } from "./Game";
|
|
import { Stats } from "./Stats";
|
|
|
|
type BigIntLike = bigint | number;
|
|
function _bigint(value: BigIntLike): bigint {
|
|
switch (typeof value) {
|
|
case "bigint":
|
|
return value;
|
|
case "number":
|
|
return BigInt(Math.floor(value));
|
|
}
|
|
}
|
|
|
|
const conquest_by_type: Record<PlayerType, number> = {
|
|
[PlayerType.Human]: PLAYER_INDEX_HUMAN,
|
|
[PlayerType.Nation]: PLAYER_INDEX_NATION,
|
|
[PlayerType.Bot]: PLAYER_INDEX_BOT,
|
|
};
|
|
|
|
export class StatsImpl implements Stats {
|
|
private readonly data: AllPlayersStats = {};
|
|
|
|
private _numMirvLaunched: bigint = 0n;
|
|
|
|
numMirvsLaunched(): bigint {
|
|
return this._numMirvLaunched;
|
|
}
|
|
|
|
getPlayerStats(player: Player): PlayerStats {
|
|
const clientID = player.clientID();
|
|
if (clientID === null) return undefined;
|
|
return this.data[clientID];
|
|
}
|
|
|
|
stats() {
|
|
return this.data;
|
|
}
|
|
|
|
private _makePlayerStats(player: Player): PlayerStats {
|
|
const clientID = player.clientID();
|
|
if (clientID === null) return undefined;
|
|
if (clientID in this.data) {
|
|
return this.data[clientID];
|
|
}
|
|
const data = {} satisfies PlayerStats;
|
|
this.data[clientID] = data;
|
|
return data;
|
|
}
|
|
|
|
private _addAttack(player: Player, index: number, value: BigIntLike) {
|
|
const p = this._makePlayerStats(player);
|
|
if (p === undefined) return;
|
|
p.attacks ??= [0n];
|
|
while (p.attacks.length <= index) p.attacks.push(0n);
|
|
p.attacks[index] += _bigint(value);
|
|
}
|
|
|
|
private _addBetrayal(player: Player, value: BigIntLike) {
|
|
const data = this._makePlayerStats(player);
|
|
if (data === undefined) return;
|
|
data.betrayals ??= 0n;
|
|
data.betrayals += _bigint(value);
|
|
}
|
|
|
|
private _addBoat(
|
|
player: Player,
|
|
type: BoatUnit,
|
|
index: number,
|
|
value: BigIntLike,
|
|
) {
|
|
const p = this._makePlayerStats(player);
|
|
if (p === undefined) return;
|
|
p.boats ??= { [type]: [0n] };
|
|
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: BigIntLike,
|
|
): void {
|
|
const type = unitTypeToBombUnit[nukeType];
|
|
const p = this._makePlayerStats(player);
|
|
if (p === undefined) return;
|
|
p.bombs ??= { [type]: [0n] };
|
|
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: BigIntLike) {
|
|
const p = this._makePlayerStats(player);
|
|
if (p === undefined) return;
|
|
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: BigIntLike,
|
|
) {
|
|
const type = unitTypeToOtherUnit[otherUnitType];
|
|
const p = this._makePlayerStats(player);
|
|
if (p === undefined) return;
|
|
p.units ??= { [type]: [0n] };
|
|
p.units[type] ??= [0n];
|
|
while (p.units[type].length <= index) p.units[type].push(0n);
|
|
p.units[type][index] += _bigint(value);
|
|
}
|
|
|
|
private _addConquest(player: Player, index: number) {
|
|
const p = this._makePlayerStats(player);
|
|
if (p === undefined) return;
|
|
p.conquests ??= [0n];
|
|
while (p.conquests.length <= index) p.conquests.push(0n);
|
|
p.conquests[index] += _bigint(1);
|
|
}
|
|
|
|
private _addPlayerKilled(player: Player, tick: number) {
|
|
const p = this._makePlayerStats(player);
|
|
if (p === undefined) return;
|
|
p.killedAt = _bigint(tick);
|
|
}
|
|
|
|
attack(
|
|
player: Player,
|
|
target: Player | TerraNullius,
|
|
troops: BigIntLike,
|
|
): void {
|
|
this._addAttack(player, ATTACK_INDEX_SENT, troops);
|
|
if (target.isPlayer()) {
|
|
this._addAttack(target, ATTACK_INDEX_RECV, troops);
|
|
}
|
|
}
|
|
|
|
attackCancel(
|
|
player: Player,
|
|
target: Player | TerraNullius,
|
|
troops: BigIntLike,
|
|
): void {
|
|
this._addAttack(player, ATTACK_INDEX_CANCEL, troops);
|
|
this._addAttack(player, ATTACK_INDEX_SENT, -troops);
|
|
if (target.isPlayer()) {
|
|
this._addAttack(target, ATTACK_INDEX_RECV, -troops);
|
|
}
|
|
}
|
|
|
|
betray(player: Player): void {
|
|
this._addBetrayal(player, 1);
|
|
}
|
|
|
|
boatSendTrade(player: Player, target: Player): void {
|
|
this._addBoat(player, "trade", BOAT_INDEX_SENT, 1);
|
|
}
|
|
|
|
boatArriveTrade(player: Player, target: Player, gold: BigIntLike): 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: BigIntLike): void {
|
|
this._addBoat(player, "trade", BOAT_INDEX_CAPTURE, 1);
|
|
this._addGold(player, GOLD_INDEX_STEAL, gold);
|
|
}
|
|
|
|
boatDestroyTrade(player: Player, target: Player): void {
|
|
this._addBoat(player, "trade", BOAT_INDEX_DESTROY, 1);
|
|
}
|
|
|
|
boatSendTroops(
|
|
player: Player,
|
|
target: Player | TerraNullius,
|
|
troops: BigIntLike,
|
|
): void {
|
|
this._addBoat(player, "trans", BOAT_INDEX_SENT, 1);
|
|
}
|
|
|
|
boatArriveTroops(
|
|
player: Player,
|
|
target: Player | TerraNullius,
|
|
troops: BigIntLike,
|
|
): void {
|
|
this._addBoat(player, "trans", BOAT_INDEX_ARRIVE, 1);
|
|
}
|
|
|
|
boatDestroyTroops(player: Player, target: Player, troops: BigIntLike): void {
|
|
this._addBoat(player, "trans", BOAT_INDEX_DESTROY, 1);
|
|
}
|
|
|
|
bombLaunch(
|
|
player: Player,
|
|
target: Player | TerraNullius,
|
|
type: NukeType,
|
|
): void {
|
|
if (type === UnitType.MIRV) {
|
|
this._numMirvLaunched++;
|
|
}
|
|
this._addBomb(player, type, BOMB_INDEX_LAUNCH, 1);
|
|
}
|
|
|
|
bombLand(
|
|
player: Player,
|
|
target: Player | TerraNullius,
|
|
type: NukeType,
|
|
): void {
|
|
this._addBomb(player, type, BOMB_INDEX_LAND, 1);
|
|
}
|
|
|
|
bombIntercept(player: Player, type: NukeType, count: BigIntLike): void {
|
|
this._addBomb(player, type, BOMB_INDEX_INTERCEPT, count);
|
|
}
|
|
|
|
goldWork(player: Player, gold: BigIntLike): void {
|
|
this._addGold(player, GOLD_INDEX_WORK, gold);
|
|
}
|
|
|
|
goldWar(player: Player, captured: Player, gold: BigIntLike): void {
|
|
this._addGold(player, GOLD_INDEX_WAR, gold);
|
|
const conquestType = conquest_by_type[captured.type()];
|
|
if (conquestType !== undefined) {
|
|
this._addConquest(player, conquestType);
|
|
}
|
|
}
|
|
|
|
unitBuild(player: Player, type: OtherUnitType): void {
|
|
this._addOtherUnit(player, type, OTHER_INDEX_BUILT, 1);
|
|
}
|
|
|
|
unitCapture(player: Player, type: OtherUnitType): void {
|
|
this._addOtherUnit(player, type, OTHER_INDEX_CAPTURE, 1);
|
|
}
|
|
|
|
unitUpgrade(player: Player, type: OtherUnitType): void {
|
|
this._addOtherUnit(player, type, OTHER_INDEX_UPGRADE, 1);
|
|
}
|
|
|
|
unitDestroy(player: Player, type: OtherUnitType): void {
|
|
this._addOtherUnit(player, type, OTHER_INDEX_DESTROY, 1);
|
|
}
|
|
|
|
unitLose(player: Player, type: OtherUnitType): void {
|
|
this._addOtherUnit(player, type, OTHER_INDEX_LOST, 1);
|
|
}
|
|
|
|
playerKilled(player: Player, tick: number): void {
|
|
this._addPlayerKilled(player, tick);
|
|
}
|
|
|
|
trainSelfTrade(player: Player, gold: BigIntLike): void {
|
|
this._addGold(player, GOLD_INDEX_TRAIN_SELF, gold);
|
|
}
|
|
|
|
trainExternalTrade(player: Player, gold: BigIntLike): void {
|
|
this._addGold(player, GOLD_INDEX_TRAIN_OTHER, gold);
|
|
}
|
|
|
|
lobbyFillTime(fillTimeMs: number): void {}
|
|
}
|