mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-08-18 00:28:14 +00:00
store values as bigints to prevent floating point drift
This commit is contained in:
@@ -306,3 +306,19 @@ export function generateID(): GameID {
|
|||||||
);
|
);
|
||||||
return nanoid();
|
return nanoid();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function toInt(num: number): bigint {
|
||||||
|
return BigInt(Math.floor(num));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function maxInt(a: bigint, b: bigint): bigint {
|
||||||
|
return a > b ? a : b;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function minInt(a: bigint, b: bigint): bigint {
|
||||||
|
return a < b ? a : b;
|
||||||
|
}
|
||||||
|
export function withinInt(num: bigint, min: bigint, max: bigint): bigint {
|
||||||
|
const atLeastMin = maxInt(num, min);
|
||||||
|
return minInt(atLeastMin, max);
|
||||||
|
}
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ export class PlayerExecution implements Execution {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const popInc = this.config.populationIncreaseRate(this.player);
|
const popInc = this.config.populationIncreaseRate(this.player);
|
||||||
this.player.addWorkers(popInc * (1 - this.player.targetTroopRatio())); // (1 - this.player.targetTroopRatio()))
|
this.player.addWorkers(popInc * (1 - this.player.targetTroopRatio()));
|
||||||
this.player.addTroops(popInc * this.player.targetTroopRatio());
|
this.player.addTroops(popInc * this.player.targetTroopRatio());
|
||||||
this.player.addGold(this.config.goldAdditionRate(this.player));
|
this.player.addGold(this.config.goldAdditionRate(this.player));
|
||||||
const adjustRate = this.config.troopAdjustmentRate(this.player);
|
const adjustRate = this.config.troopAdjustmentRate(this.player);
|
||||||
|
|||||||
+29
-25
@@ -26,9 +26,12 @@ import {
|
|||||||
assertNever,
|
assertNever,
|
||||||
closestOceanShoreFromPlayer,
|
closestOceanShoreFromPlayer,
|
||||||
distSortUnit,
|
distSortUnit,
|
||||||
|
maxInt,
|
||||||
|
minInt,
|
||||||
simpleHash,
|
simpleHash,
|
||||||
sourceDstOceanShore,
|
sourceDstOceanShore,
|
||||||
targetTransportTile,
|
targetTransportTile,
|
||||||
|
toInt,
|
||||||
within,
|
within,
|
||||||
} from "../Util";
|
} from "../Util";
|
||||||
import { CellString, GameImpl } from "./GameImpl";
|
import { CellString, GameImpl } from "./GameImpl";
|
||||||
@@ -37,7 +40,6 @@ import { MessageType } from "./Game";
|
|||||||
import { renderTroops } from "../../client/Utils";
|
import { renderTroops } from "../../client/Utils";
|
||||||
import { TerraNulliusImpl } from "./TerraNulliusImpl";
|
import { TerraNulliusImpl } from "./TerraNulliusImpl";
|
||||||
import { andFN, manhattanDistFN, TileRef } from "./GameMap";
|
import { andFN, manhattanDistFN, TileRef } from "./GameMap";
|
||||||
import { Emoji } from "discord.js";
|
|
||||||
import { AttackImpl } from "./AttackImpl";
|
import { AttackImpl } from "./AttackImpl";
|
||||||
|
|
||||||
interface Target {
|
interface Target {
|
||||||
@@ -55,10 +57,12 @@ class Donation {
|
|||||||
export class PlayerImpl implements Player {
|
export class PlayerImpl implements Player {
|
||||||
public _lastTileChange: number = 0;
|
public _lastTileChange: number = 0;
|
||||||
|
|
||||||
private _gold: Gold;
|
private _gold: bigint;
|
||||||
private _troops: number;
|
private _troops: bigint;
|
||||||
private _workers: number;
|
private _workers: bigint;
|
||||||
private _targetTroopRatio: number = 1;
|
|
||||||
|
// 0 to 100
|
||||||
|
private _targetTroopRatio: bigint = 100n;
|
||||||
|
|
||||||
isTraitor_ = false;
|
isTraitor_ = false;
|
||||||
|
|
||||||
@@ -88,14 +92,14 @@ export class PlayerImpl implements Player {
|
|||||||
private mg: GameImpl,
|
private mg: GameImpl,
|
||||||
private _smallID: number,
|
private _smallID: number,
|
||||||
private readonly playerInfo: PlayerInfo,
|
private readonly playerInfo: PlayerInfo,
|
||||||
startPopulation: number,
|
startTroops: number,
|
||||||
) {
|
) {
|
||||||
this._flag = playerInfo.flag;
|
this._flag = playerInfo.flag;
|
||||||
this._name = playerInfo.name;
|
this._name = playerInfo.name;
|
||||||
this._targetTroopRatio = 1;
|
this._targetTroopRatio = 100n;
|
||||||
this._troops = startPopulation * this._targetTroopRatio;
|
this._troops = toInt(startTroops);
|
||||||
this._workers = startPopulation * (1 - this._targetTroopRatio);
|
this._workers = 0n;
|
||||||
this._gold = 0;
|
this._gold = 0n;
|
||||||
this._displayName = this._name; // processName(this._name)
|
this._displayName = this._name; // processName(this._name)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -117,7 +121,7 @@ export class PlayerImpl implements Player {
|
|||||||
playerType: this.type(),
|
playerType: this.type(),
|
||||||
isAlive: this.isAlive(),
|
isAlive: this.isAlive(),
|
||||||
tilesOwned: this.numTilesOwned(),
|
tilesOwned: this.numTilesOwned(),
|
||||||
gold: this._gold,
|
gold: Number(this._gold),
|
||||||
population: this.population(),
|
population: this.population(),
|
||||||
workers: this.workers(),
|
workers: this.workers(),
|
||||||
troops: this.troops(),
|
troops: this.troops(),
|
||||||
@@ -234,7 +238,7 @@ export class PlayerImpl implements Player {
|
|||||||
return true as const;
|
return true as const;
|
||||||
}
|
}
|
||||||
setTroops(troops: number) {
|
setTroops(troops: number) {
|
||||||
this._troops = Math.floor(troops);
|
this._troops = toInt(troops);
|
||||||
}
|
}
|
||||||
conquer(tile: TileRef) {
|
conquer(tile: TileRef) {
|
||||||
this.mg.conquer(this, tile);
|
this.mg.conquer(this, tile);
|
||||||
@@ -503,11 +507,11 @@ export class PlayerImpl implements Player {
|
|||||||
}
|
}
|
||||||
|
|
||||||
gold(): Gold {
|
gold(): Gold {
|
||||||
return this._gold;
|
return Number(this._gold);
|
||||||
}
|
}
|
||||||
|
|
||||||
addGold(toAdd: Gold): void {
|
addGold(toAdd: Gold): void {
|
||||||
this._gold += toAdd;
|
this._gold += toInt(toAdd);
|
||||||
}
|
}
|
||||||
|
|
||||||
removeGold(toRemove: Gold): void {
|
removeGold(toRemove: Gold): void {
|
||||||
@@ -516,24 +520,24 @@ export class PlayerImpl implements Player {
|
|||||||
`Player ${this} does not enough gold (${toRemove} vs ${this._gold}))`,
|
`Player ${this} does not enough gold (${toRemove} vs ${this._gold}))`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
this._gold -= toRemove;
|
this._gold -= toInt(toRemove);
|
||||||
}
|
}
|
||||||
|
|
||||||
population(): number {
|
population(): number {
|
||||||
return this._troops + this._workers;
|
return Number(this._troops + this._workers);
|
||||||
}
|
}
|
||||||
workers(): number {
|
workers(): number {
|
||||||
return Math.max(1, this._workers);
|
return Math.max(1, Number(this._workers));
|
||||||
}
|
}
|
||||||
addWorkers(toAdd: number): void {
|
addWorkers(toAdd: number): void {
|
||||||
this._workers += toAdd;
|
this._workers += toInt(toAdd);
|
||||||
}
|
}
|
||||||
removeWorkers(toRemove: number): void {
|
removeWorkers(toRemove: number): void {
|
||||||
this._workers = Math.max(1, this._workers - toRemove);
|
this._workers = maxInt(1n, this._workers - toInt(toRemove));
|
||||||
}
|
}
|
||||||
|
|
||||||
targetTroopRatio(): number {
|
targetTroopRatio(): number {
|
||||||
return this._targetTroopRatio;
|
return Number(this._targetTroopRatio) / 100;
|
||||||
}
|
}
|
||||||
|
|
||||||
setTargetTroopRatio(target: number): void {
|
setTargetTroopRatio(target: number): void {
|
||||||
@@ -542,11 +546,11 @@ export class PlayerImpl implements Player {
|
|||||||
`invalid targetTroopRatio ${target} set on player ${PlayerImpl}`,
|
`invalid targetTroopRatio ${target} set on player ${PlayerImpl}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
this._targetTroopRatio = target;
|
this._targetTroopRatio = toInt(target * 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
troops(): number {
|
troops(): number {
|
||||||
return this._troops;
|
return Number(this._troops);
|
||||||
}
|
}
|
||||||
|
|
||||||
addTroops(troops: number): void {
|
addTroops(troops: number): void {
|
||||||
@@ -554,15 +558,15 @@ export class PlayerImpl implements Player {
|
|||||||
this.removeTroops(-1 * troops);
|
this.removeTroops(-1 * troops);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this._troops += Math.floor(troops);
|
this._troops += toInt(troops);
|
||||||
}
|
}
|
||||||
removeTroops(troops: number): number {
|
removeTroops(troops: number): number {
|
||||||
if (troops <= 1) {
|
if (troops <= 1) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
const toRemove = Math.floor(Math.min(this._troops - 1, troops));
|
const toRemove = minInt(this._troops, toInt(troops));
|
||||||
this._troops -= toRemove;
|
this._troops -= toRemove;
|
||||||
return toRemove;
|
return Number(toRemove);
|
||||||
}
|
}
|
||||||
|
|
||||||
captureUnit(unit: Unit): void {
|
captureUnit(unit: Unit): void {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { MessageType } from "./Game";
|
import { MessageType } from "./Game";
|
||||||
import { UnitUpdate } from "./GameUpdates";
|
import { UnitUpdate } from "./GameUpdates";
|
||||||
import { GameUpdateType } from "./GameUpdates";
|
import { GameUpdateType } from "./GameUpdates";
|
||||||
import { simpleHash, within } from "../Util";
|
import { simpleHash, toInt, within, withinInt } from "../Util";
|
||||||
import { Unit, TerraNullius, UnitType, Player, UnitInfo } from "./Game";
|
import { Unit, TerraNullius, UnitType, Player, UnitInfo } from "./Game";
|
||||||
import { GameImpl } from "./GameImpl";
|
import { GameImpl } from "./GameImpl";
|
||||||
import { PlayerImpl } from "./PlayerImpl";
|
import { PlayerImpl } from "./PlayerImpl";
|
||||||
@@ -9,7 +9,7 @@ import { TileRef } from "./GameMap";
|
|||||||
|
|
||||||
export class UnitImpl implements Unit {
|
export class UnitImpl implements Unit {
|
||||||
private _active = true;
|
private _active = true;
|
||||||
private _health: number;
|
private _health: bigint;
|
||||||
private _lastTile: TileRef = null;
|
private _lastTile: TileRef = null;
|
||||||
|
|
||||||
private _constructionType: UnitType = undefined;
|
private _constructionType: UnitType = undefined;
|
||||||
@@ -37,7 +37,7 @@ export class UnitImpl implements Unit {
|
|||||||
isActive: this._active,
|
isActive: this._active,
|
||||||
pos: this._tile,
|
pos: this._tile,
|
||||||
lastPos: this._lastTile,
|
lastPos: this._lastTile,
|
||||||
health: this.hasHealth() ? this._health : undefined,
|
health: this.hasHealth() ? Number(this._health) : undefined,
|
||||||
constructionType: this._constructionType,
|
constructionType: this._constructionType,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -65,7 +65,7 @@ export class UnitImpl implements Unit {
|
|||||||
return this._troops;
|
return this._troops;
|
||||||
}
|
}
|
||||||
health(): number {
|
health(): number {
|
||||||
return this._health;
|
return Number(this._health);
|
||||||
}
|
}
|
||||||
hasHealth(): boolean {
|
hasHealth(): boolean {
|
||||||
return this.info().maxHealth != undefined;
|
return this.info().maxHealth != undefined;
|
||||||
@@ -94,7 +94,11 @@ export class UnitImpl implements Unit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
modifyHealth(delta: number): void {
|
modifyHealth(delta: number): void {
|
||||||
this._health = within(this._health + delta, 0, this.info().maxHealth ?? 1);
|
this._health = withinInt(
|
||||||
|
this._health + toInt(delta),
|
||||||
|
0n,
|
||||||
|
toInt(this.info().maxHealth ?? 1),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
delete(displayMessage: boolean = true): void {
|
delete(displayMessage: boolean = true): void {
|
||||||
|
|||||||
Reference in New Issue
Block a user