diff --git a/src/client/Transport.ts b/src/client/Transport.ts
index cbef4bbba..7d85ae931 100644
--- a/src/client/Transport.ts
+++ b/src/client/Transport.ts
@@ -4,6 +4,7 @@ import {
AllPlayers,
Cell,
GameType,
+ Gold,
PlayerID,
PlayerType,
Tick,
@@ -94,15 +95,13 @@ export class SendEmojiIntentEvent implements GameEvent {
export class SendDonateGoldIntentEvent implements GameEvent {
constructor(
- public readonly sender: PlayerView,
public readonly recipient: PlayerView,
- public readonly gold: number | null,
+ public readonly gold: Gold | null,
) {}
}
export class SendDonateTroopsIntentEvent implements GameEvent {
constructor(
- public readonly sender: PlayerView,
public readonly recipient: PlayerView,
public readonly troops: number | null,
) {}
@@ -110,7 +109,6 @@ export class SendDonateTroopsIntentEvent implements GameEvent {
export class SendQuickChatEvent implements GameEvent {
constructor(
- public readonly sender: PlayerView,
public readonly recipient: PlayerView,
public readonly quickChatKey: string,
public readonly variables: { [key: string]: string },
@@ -119,17 +117,13 @@ export class SendQuickChatEvent implements GameEvent {
export class SendEmbargoIntentEvent implements GameEvent {
constructor(
- public readonly sender: PlayerView,
public readonly target: PlayerView,
public readonly action: "start" | "stop",
) {}
}
export class CancelAttackIntentEvent implements GameEvent {
- constructor(
- public readonly playerID: PlayerID,
- public readonly attackID: string,
- ) {}
+ constructor(public readonly attackID: string) {}
}
export class CancelBoatIntentEvent implements GameEvent {
diff --git a/src/client/Utils.ts b/src/client/Utils.ts
index a6c90191f..62fc2e80e 100644
--- a/src/client/Utils.ts
+++ b/src/client/Utils.ts
@@ -4,7 +4,8 @@ export function renderTroops(troops: number): string {
return renderNumber(troops / 10);
}
-export function renderNumber(num: number): string {
+export function renderNumber(num: number | bigint): string {
+ num = Number(num);
num = Math.max(num, 0);
if (num >= 10_000_000) {
diff --git a/src/client/graphics/layers/BuildMenu.ts b/src/client/graphics/layers/BuildMenu.ts
index f048c2271..70f28ea65 100644
--- a/src/client/graphics/layers/BuildMenu.ts
+++ b/src/client/graphics/layers/BuildMenu.ts
@@ -12,7 +12,7 @@ import samlauncherIcon from "../../../../resources/images/SamLauncherIconWhite.s
import shieldIcon from "../../../../resources/images/ShieldIconWhite.svg";
import { translateText } from "../../../client/Utils";
import { EventBus } from "../../../core/EventBus";
-import { Cell, PlayerActions, UnitType } from "../../../core/game/Game";
+import { Cell, Gold, PlayerActions, UnitType } from "../../../core/game/Game";
import { TileRef } from "../../../core/game/GameMap";
import { GameView } from "../../../core/game/GameView";
import { BuildUnitIntentEvent } from "../../Transport";
@@ -314,13 +314,13 @@ export class BuildMenu extends LitElement implements Layer {
return unit[0].canBuild !== false;
}
- private cost(item: BuildItemDisplay): number {
+ private cost(item: BuildItemDisplay): Gold {
for (const bu of this.playerActions?.buildableUnits ?? []) {
if (bu.type === item.unitType) {
return bu.cost;
}
}
- return 0;
+ return 0n;
}
private count(item: BuildItemDisplay): string {
diff --git a/src/client/graphics/layers/ChatModal.ts b/src/client/graphics/layers/ChatModal.ts
index 40cb4e4a4..7bec6d3b4 100644
--- a/src/client/graphics/layers/ChatModal.ts
+++ b/src/client/graphics/layers/ChatModal.ts
@@ -236,7 +236,6 @@ export class ChatModal extends LitElement {
this.eventBus.emit(
new SendQuickChatEvent(
- this.sender,
this.recipient,
this.selectedQuickChatKey,
variables,
diff --git a/src/client/graphics/layers/ControlPanel.ts b/src/client/graphics/layers/ControlPanel.ts
index 9ce3cc7cf..836d9390c 100644
--- a/src/client/graphics/layers/ControlPanel.ts
+++ b/src/client/graphics/layers/ControlPanel.ts
@@ -2,6 +2,7 @@ import { LitElement, html } from "lit";
import { customElement, state } from "lit/decorators.js";
import { translateText } from "../../../client/Utils";
import { EventBus } from "../../../core/EventBus";
+import { Gold } from "../../../core/game/Game";
import { GameView } from "../../../core/game/GameView";
import { AttackRatioEvent } from "../../InputHandler";
import { SendSetTargetTroopRatioEvent } from "../../Transport";
@@ -46,10 +47,10 @@ export class ControlPanel extends LitElement implements Layer {
private _manpower: number = 0;
@state()
- private _gold: number;
+ private _gold: Gold;
@state()
- private _goldPerSecond: number;
+ private _goldPerSecond: Gold;
private _lastPopulationIncreaseRate: number;
@@ -124,7 +125,7 @@ export class ControlPanel extends LitElement implements Layer {
this._troops = player.troops();
this._workers = player.workers();
this.popRate = this.game.config().populationIncreaseRate(player) * 10;
- this._goldPerSecond = this.game.config().goldAdditionRate(player) * 10;
+ this._goldPerSecond = this.game.config().goldAdditionRate(player) * 10n;
this.currentTroopRatio = player.troops() / player.population();
this.requestUpdate();
diff --git a/src/client/graphics/layers/EventsDisplay.ts b/src/client/graphics/layers/EventsDisplay.ts
index 0ccba3800..ef236985e 100644
--- a/src/client/graphics/layers/EventsDisplay.ts
+++ b/src/client/graphics/layers/EventsDisplay.ts
@@ -380,7 +380,7 @@ export class EventsDisplay extends LitElement implements Layer {
emitCancelAttackIntent(id: string) {
const myPlayer = this.game.myPlayer();
if (!myPlayer) return;
- this.eventBus.emit(new CancelAttackIntentEvent(myPlayer.id(), id));
+ this.eventBus.emit(new CancelAttackIntentEvent(id));
}
emitBoatCancelIntent(id: number) {
diff --git a/src/client/graphics/layers/Leaderboard.ts b/src/client/graphics/layers/Leaderboard.ts
index 0749a90aa..1d9dbb35d 100644
--- a/src/client/graphics/layers/Leaderboard.ts
+++ b/src/client/graphics/layers/Leaderboard.ts
@@ -89,7 +89,9 @@ export class Leaderboard extends LitElement implements Layer {
switch (this._sortKey) {
case "gold":
- sorted = sorted.sort((a, b) => compare(a.gold(), b.gold()));
+ sorted = sorted.sort((a, b) =>
+ compare(Number(a.gold()), Number(b.gold())),
+ );
break;
case "troops":
sorted = sorted.sort((a, b) => compare(a.troops(), b.troops()));
diff --git a/src/client/graphics/layers/PlayerPanel.ts b/src/client/graphics/layers/PlayerPanel.ts
index 4e82be5bb..859803e7c 100644
--- a/src/client/graphics/layers/PlayerPanel.ts
+++ b/src/client/graphics/layers/PlayerPanel.ts
@@ -90,7 +90,6 @@ export class PlayerPanel extends LitElement implements Layer {
e.stopPropagation();
this.eventBus.emit(
new SendDonateTroopsIntentEvent(
- myPlayer,
other,
myPlayer.troops() * this.uiState.attackRatio,
),
@@ -104,7 +103,7 @@ export class PlayerPanel extends LitElement implements Layer {
other: PlayerView,
) {
e.stopPropagation();
- this.eventBus.emit(new SendDonateGoldIntentEvent(myPlayer, other, null));
+ this.eventBus.emit(new SendDonateGoldIntentEvent(other, null));
this.hide();
}
@@ -114,7 +113,7 @@ export class PlayerPanel extends LitElement implements Layer {
other: PlayerView,
) {
e.stopPropagation();
- this.eventBus.emit(new SendEmbargoIntentEvent(myPlayer, other, "start"));
+ this.eventBus.emit(new SendEmbargoIntentEvent(other, "start"));
this.hide();
}
@@ -124,7 +123,7 @@ export class PlayerPanel extends LitElement implements Layer {
other: PlayerView,
) {
e.stopPropagation();
- this.eventBus.emit(new SendEmbargoIntentEvent(myPlayer, other, "stop"));
+ this.eventBus.emit(new SendEmbargoIntentEvent(other, "stop"));
this.hide();
}
diff --git a/src/client/graphics/layers/TeamStats.ts b/src/client/graphics/layers/TeamStats.ts
index e62152f13..dcbbd10b0 100644
--- a/src/client/graphics/layers/TeamStats.ts
+++ b/src/client/graphics/layers/TeamStats.ts
@@ -58,7 +58,7 @@ export class TeamStats extends LitElement implements Layer {
this.teams = Object.entries(grouped)
.map(([teamStr, teamPlayers]) => {
- let totalGold = 0;
+ let totalGold = 0n;
let totalTroops = 0;
let totalScoreSort = 0;
diff --git a/src/client/graphics/layers/TopBar.ts b/src/client/graphics/layers/TopBar.ts
index 76e218ba5..991fda739 100644
--- a/src/client/graphics/layers/TopBar.ts
+++ b/src/client/graphics/layers/TopBar.ts
@@ -50,7 +50,7 @@ export class TopBar extends LitElement implements Layer {
const popRate = this.game.config().populationIncreaseRate(myPlayer) * 10;
const maxPop = this.game.config().maxPopulation(myPlayer);
- const goldPerSecond = this.game.config().goldAdditionRate(myPlayer) * 10;
+ const goldPerSecond = this.game.config().goldAdditionRate(myPlayer) * 10n;
return html`
0,
+ cost: () => 0n,
territoryBound: false,
};
case UnitType.Warship:
return {
cost: (p: Player) =>
p.type() === PlayerType.Human && this.infiniteGold()
- ? 0
- : Math.min(
- 1_000_000,
- (p.unitsIncludingConstruction(UnitType.Warship).length + 1) *
- 250_000,
+ ? 0n
+ : BigInt(
+ Math.min(
+ 1_000_000,
+ (p.unitsIncludingConstruction(UnitType.Warship).length +
+ 1) *
+ 250_000,
+ ),
),
territoryBound: false,
maxHealth: 1000,
};
case UnitType.Shell:
return {
- cost: () => 0,
+ cost: () => 0n,
territoryBound: false,
damage: 250,
};
case UnitType.SAMMissile:
return {
- cost: () => 0,
+ cost: () => 0n,
territoryBound: false,
};
case UnitType.Port:
return {
cost: (p: Player) =>
p.type() === PlayerType.Human && this.infiniteGold()
- ? 0
- : Math.min(
- 1_000_000,
- Math.pow(
- 2,
- p.unitsIncludingConstruction(UnitType.Port).length,
- ) * 125_000,
+ ? 0n
+ : BigInt(
+ Math.min(
+ 1_000_000,
+ Math.pow(
+ 2,
+ p.unitsIncludingConstruction(UnitType.Port).length,
+ ) * 125_000,
+ ),
),
territoryBound: true,
constructionDuration: this.instantBuild() ? 0 : 2 * 10,
@@ -336,41 +341,43 @@ export class DefaultConfig implements Config {
case UnitType.AtomBomb:
return {
cost: (p: Player) =>
- p.type() === PlayerType.Human && this.infiniteGold() ? 0 : 750_000,
+ p.type() === PlayerType.Human && this.infiniteGold()
+ ? 0n
+ : 750_000n,
territoryBound: false,
};
case UnitType.HydrogenBomb:
return {
cost: (p: Player) =>
p.type() === PlayerType.Human && this.infiniteGold()
- ? 0
- : 5_000_000,
+ ? 0n
+ : 5_000_000n,
territoryBound: false,
};
case UnitType.MIRV:
return {
cost: (p: Player) =>
p.type() === PlayerType.Human && this.infiniteGold()
- ? 0
- : 25_000_000,
+ ? 0n
+ : 25_000_000n,
territoryBound: false,
};
case UnitType.MIRVWarhead:
return {
- cost: () => 0,
+ cost: () => 0n,
territoryBound: false,
};
case UnitType.TradeShip:
return {
- cost: () => 0,
+ cost: () => 0n,
territoryBound: false,
};
case UnitType.MissileSilo:
return {
cost: (p: Player) =>
p.type() === PlayerType.Human && this.infiniteGold()
- ? 0
- : 1_000_000,
+ ? 0n
+ : 1_000_000n,
territoryBound: true,
constructionDuration: this.instantBuild() ? 0 : 10 * 10,
};
@@ -378,12 +385,14 @@ export class DefaultConfig implements Config {
return {
cost: (p: Player) =>
p.type() === PlayerType.Human && this.infiniteGold()
- ? 0
- : Math.min(
- 250_000,
- (p.unitsIncludingConstruction(UnitType.DefensePost).length +
- 1) *
- 50_000,
+ ? 0n
+ : BigInt(
+ Math.min(
+ 250_000,
+ (p.unitsIncludingConstruction(UnitType.DefensePost).length +
+ 1) *
+ 50_000,
+ ),
),
territoryBound: true,
constructionDuration: this.instantBuild() ? 0 : 5 * 10,
@@ -392,12 +401,14 @@ export class DefaultConfig implements Config {
return {
cost: (p: Player) =>
p.type() === PlayerType.Human && this.infiniteGold()
- ? 0
- : Math.min(
- 3_000_000,
- (p.unitsIncludingConstruction(UnitType.SAMLauncher).length +
- 1) *
- 1_500_000,
+ ? 0n
+ : BigInt(
+ Math.min(
+ 3_000_000,
+ (p.unitsIncludingConstruction(UnitType.SAMLauncher).length +
+ 1) *
+ 1_500_000,
+ ),
),
territoryBound: true,
constructionDuration: this.instantBuild() ? 0 : 30 * 10,
@@ -406,20 +417,22 @@ export class DefaultConfig implements Config {
return {
cost: (p: Player) =>
p.type() === PlayerType.Human && this.infiniteGold()
- ? 0
- : Math.min(
- 1_000_000,
- Math.pow(
- 2,
- p.unitsIncludingConstruction(UnitType.City).length,
- ) * 125_000,
+ ? 0n
+ : BigInt(
+ Math.min(
+ 1_000_000,
+ Math.pow(
+ 2,
+ p.unitsIncludingConstruction(UnitType.City).length,
+ ) * 125_000,
+ ),
),
territoryBound: true,
constructionDuration: this.instantBuild() ? 0 : 2 * 10,
};
case UnitType.Construction:
return {
- cost: () => 0,
+ cost: () => 0n,
territoryBound: true,
};
default:
@@ -684,8 +697,8 @@ export class DefaultConfig implements Config {
return Math.min(totalPop + toAdd, max) - totalPop;
}
- goldAdditionRate(player: Player): number {
- return 0.08 * player.workers() ** 0.65;
+ goldAdditionRate(player: Player): bigint {
+ return BigInt(Math.floor(0.08 * player.workers() ** 0.65));
}
troopAdjustmentRate(player: Player): number {
diff --git a/src/core/execution/ConstructionExecution.ts b/src/core/execution/ConstructionExecution.ts
index 49ed2e89f..ba7a7c9c0 100644
--- a/src/core/execution/ConstructionExecution.ts
+++ b/src/core/execution/ConstructionExecution.ts
@@ -2,6 +2,7 @@ import { consolex } from "../Consolex";
import {
Execution,
Game,
+ Gold,
Player,
PlayerID,
Tick,
@@ -26,7 +27,7 @@ export class ConstructionExecution implements Execution {
private ticksUntilComplete: Tick;
- private cost: number;
+ private cost: Gold;
constructor(
private ownerId: PlayerID,
diff --git a/src/core/execution/DonateGoldExecution.ts b/src/core/execution/DonateGoldExecution.ts
index 166f34cde..20837d66e 100644
--- a/src/core/execution/DonateGoldExecution.ts
+++ b/src/core/execution/DonateGoldExecution.ts
@@ -1,5 +1,5 @@
import { consolex } from "../Consolex";
-import { Execution, Game, Player, PlayerID } from "../game/Game";
+import { Execution, Game, Gold, Player, PlayerID } from "../game/Game";
export class DonateGoldExecution implements Execution {
private sender: Player;
@@ -10,7 +10,7 @@ export class DonateGoldExecution implements Execution {
constructor(
private senderID: PlayerID,
private recipientID: PlayerID,
- private gold: number | null,
+ private gold: Gold | null,
) {}
init(mg: Game, ticks: number): void {
@@ -28,14 +28,16 @@ export class DonateGoldExecution implements Execution {
this.sender = mg.player(this.senderID);
this.recipient = mg.player(this.recipientID);
if (this.gold === null) {
- this.gold = Math.round(this.sender.gold() / 3);
+ this.gold = this.sender.gold() / 3n;
}
}
tick(ticks: number): void {
if (this.gold === null) throw new Error("not initialized");
- if (this.sender.canDonate(this.recipient)) {
- this.sender.donateGold(this.recipient, this.gold);
+ if (
+ this.sender.canDonate(this.recipient) &&
+ this.sender.donateGold(this.recipient, this.gold)
+ ) {
this.recipient.updateRelation(this.sender, 50);
} else {
consolex.warn(
diff --git a/src/core/execution/DonateTroopExecution.ts b/src/core/execution/DonateTroopExecution.ts
index 99dc4ca66..40fde1781 100644
--- a/src/core/execution/DonateTroopExecution.ts
+++ b/src/core/execution/DonateTroopExecution.ts
@@ -34,8 +34,10 @@ export class DonateTroopsExecution implements Execution {
tick(ticks: number): void {
if (this.troops === null) throw new Error("not initialized");
- if (this.sender.canDonate(this.recipient)) {
- this.sender.donateTroops(this.recipient, this.troops);
+ if (
+ this.sender.canDonate(this.recipient) &&
+ this.sender.donateTroops(this.recipient, this.troops)
+ ) {
this.recipient.updateRelation(this.sender, 50);
} else {
consolex.warn(
diff --git a/src/core/execution/FakeHumanExecution.ts b/src/core/execution/FakeHumanExecution.ts
index 6800d6f6f..de34803a0 100644
--- a/src/core/execution/FakeHumanExecution.ts
+++ b/src/core/execution/FakeHumanExecution.ts
@@ -4,6 +4,7 @@ import {
Difficulty,
Execution,
Game,
+ Gold,
Nation,
Player,
PlayerID,
@@ -543,7 +544,7 @@ export class FakeHumanExecution implements Execution {
return null;
}
- private cost(type: UnitType): number {
+ private cost(type: UnitType): Gold {
if (this.player === null) throw new Error("not initialized");
return this.mg.unitInfo(type).cost(this.player);
}
diff --git a/src/core/game/Game.ts b/src/core/game/Game.ts
index eb52a6b10..b2de35cbb 100644
--- a/src/core/game/Game.ts
+++ b/src/core/game/Game.ts
@@ -12,7 +12,7 @@ import { Stats } from "./Stats";
export type PlayerID = string;
export type Tick = number;
-export type Gold = number;
+export type Gold = bigint;
export const AllPlayers = "AllPlayers" as const;
@@ -451,7 +451,7 @@ export interface Player {
troops(): number;
targetTroopRatio(): number;
addGold(toAdd: Gold): void;
- removeGold(toRemove: Gold): void;
+ removeGold(toRemove: Gold): Gold;
addWorkers(toAdd: number): void;
removeWorkers(toRemove: number): void;
setTargetTroopRatio(target: number): void;
@@ -507,8 +507,8 @@ export interface Player {
// Donation
canDonate(recipient: Player): boolean;
- donateTroops(recipient: Player, troops: number): void;
- donateGold(recipient: Player, gold: number): void;
+ donateTroops(recipient: Player, troops: number): boolean;
+ donateGold(recipient: Player, gold: Gold): boolean;
// Embargo
hasEmbargoAgainst(other: Player): boolean;
@@ -620,7 +620,7 @@ export interface PlayerActions {
export interface BuildableUnit {
canBuild: TileRef | false;
type: UnitType;
- cost: number;
+ cost: Gold;
}
export interface PlayerProfile {
diff --git a/src/core/game/GameUpdates.ts b/src/core/game/GameUpdates.ts
index 7f3acd1cd..679688036 100644
--- a/src/core/game/GameUpdates.ts
+++ b/src/core/game/GameUpdates.ts
@@ -2,6 +2,7 @@ import { AllPlayersStats, ClientID } from "../Schemas";
import {
EmojiMessage,
GameUpdates,
+ Gold,
MessageType,
NameViewData,
PlayerID,
@@ -103,7 +104,7 @@ export interface PlayerUpdate {
playerType: PlayerType;
isAlive: boolean;
tilesOwned: number;
- gold: number;
+ gold: Gold;
population: number;
totalPopulation: number;
workers: number;
diff --git a/src/core/game/PlayerImpl.ts b/src/core/game/PlayerImpl.ts
index 46e4fe0a9..b5ba70d79 100644
--- a/src/core/game/PlayerImpl.ts
+++ b/src/core/game/PlayerImpl.ts
@@ -137,7 +137,7 @@ export class PlayerImpl implements Player {
playerType: this.type(),
isAlive: this.isAlive(),
tilesOwned: this.numTilesOwned(),
- gold: Number(this._gold),
+ gold: this._gold,
population: this.population(),
totalPopulation: this.totalPopulation(),
workers: this.workers(),
@@ -540,9 +540,13 @@ export class PlayerImpl implements Player {
return true;
}
- donateTroops(recipient: Player, troops: number): void {
+ donateTroops(recipient: Player, troops: number): boolean {
+ if (troops <= 0) return false;
+ const removed = this.removeTroops(troops);
+ if (removed === 0) return false;
+ recipient.addTroops(removed);
+
this.sentDonations.push(new Donation(recipient, this.mg.ticks()));
- recipient.addTroops(this.removeTroops(troops));
this.mg.displayMessage(
`Sent ${renderTroops(troops)} troops to ${recipient.name()}`,
MessageType.INFO,
@@ -553,10 +557,16 @@ export class PlayerImpl implements Player {
MessageType.SUCCESS,
recipient.id(),
);
+ return true;
}
- donateGold(recipient: Player, gold: number): void {
+
+ donateGold(recipient: Player, gold: Gold): boolean {
+ if (gold <= 0n) return false;
+ const removed = this.removeGold(gold);
+ if (removed === 0n) return false;
+ recipient.addGold(removed);
+
this.sentDonations.push(new Donation(recipient, this.mg.ticks()));
- recipient.addGold(this.removeGold(gold));
this.mg.displayMessage(
`Sent ${renderNumber(gold)} gold to ${recipient.name()}`,
MessageType.INFO,
@@ -567,6 +577,7 @@ export class PlayerImpl implements Player {
MessageType.SUCCESS,
recipient.id(),
);
+ return true;
}
hasEmbargoAgainst(other: Player): boolean {
@@ -633,20 +644,20 @@ export class PlayerImpl implements Player {
}
gold(): Gold {
- return Number(this._gold);
+ return this._gold;
}
addGold(toAdd: Gold): void {
- this._gold += toInt(toAdd);
+ this._gold += toAdd;
}
- removeGold(toRemove: Gold): number {
- if (toRemove <= 1) {
- return 0;
+ removeGold(toRemove: Gold): Gold {
+ if (toRemove <= 0n) {
+ return 0n;
}
- const actualRemoved = minInt(this._gold, toInt(toRemove));
+ const actualRemoved = minInt(this._gold, toRemove);
this._gold -= actualRemoved;
- return Number(actualRemoved);
+ return actualRemoved;
}
population(): number {