diff --git a/src/client/Utils.ts b/src/client/Utils.ts index 98517c45e..51dff4258 100644 --- a/src/client/Utils.ts +++ b/src/client/Utils.ts @@ -5,24 +5,27 @@ export function renderTroops(troops: number): string { return renderNumber(troops / 10); } -export function renderNumber(num: number | bigint): string { +export function renderNumber( + num: number | bigint, + fixedPoints?: number, +): string { num = Number(num); num = Math.max(num, 0); if (num >= 10_000_000) { const value = Math.floor(num / 100000) / 10; - return value.toFixed(1) + "M"; + return value.toFixed(fixedPoints ?? 1) + "M"; } else if (num >= 1_000_000) { const value = Math.floor(num / 10000) / 100; - return value.toFixed(2) + "M"; + return value.toFixed(fixedPoints ?? 2) + "M"; } else if (num >= 100000) { return Math.floor(num / 1000) + "K"; } else if (num >= 10000) { const value = Math.floor(num / 100) / 10; - return value.toFixed(1) + "K"; + return value.toFixed(fixedPoints ?? 1) + "K"; } else if (num >= 1000) { const value = Math.floor(num / 10) / 100; - return value.toFixed(2) + "K"; + return value.toFixed(fixedPoints ?? 2) + "K"; } else { return Math.floor(num).toString(); } diff --git a/src/client/graphics/layers/FxLayer.ts b/src/client/graphics/layers/FxLayer.ts index 8b0364c7c..1d33ab123 100644 --- a/src/client/graphics/layers/FxLayer.ts +++ b/src/client/graphics/layers/FxLayer.ts @@ -66,33 +66,27 @@ export class FxLayer implements Layer { } onBonusEvent(bonus: BonusEventUpdate) { - const tile = bonus.tile; - if (this.game.owner(tile) !== this.game.myPlayer()) { + if (this.game.player(bonus.player) !== this.game.myPlayer()) { // Only display text fx for the current player return; } + const tile = bonus.tile; const x = this.game.x(tile); let y = this.game.y(tile); const gold = bonus.gold; const troops = bonus.troops; - const workers = bonus.workers; if (gold > 0) { - const shortened = renderNumber(gold); + const shortened = renderNumber(gold, 0); this.addTextFx(`+ ${shortened}`, x, y); y += 10; // increase y so the next popup starts bellow } if (troops > 0) { - const shortened = renderNumber(troops); + const shortened = renderNumber(troops, 0); this.addTextFx(`+ ${shortened} troops`, x, y); y += 10; } - - if (workers > 0) { - const shortened = renderNumber(workers); - this.addTextFx(`+ ${shortened} workers`, x, y); - } } addTextFx(text: string, x: number, y: number) { diff --git a/src/core/configuration/DefaultConfig.ts b/src/core/configuration/DefaultConfig.ts index 3d8f43628..c8713a54c 100644 --- a/src/core/configuration/DefaultConfig.ts +++ b/src/core/configuration/DefaultConfig.ts @@ -329,7 +329,7 @@ export class DefaultConfig implements Config { return Math.min(1400, Math.round(20 * Math.pow(numberOfStations, 0.5))); } trainGold(): Gold { - return BigInt(10_000); + return BigInt(4_000); } trainStationMinRange(): number { return 15; diff --git a/src/core/execution/TrainExecution.ts b/src/core/execution/TrainExecution.ts index 30e9df7b6..5e165067a 100644 --- a/src/core/execution/TrainExecution.ts +++ b/src/core/execution/TrainExecution.ts @@ -32,6 +32,10 @@ export class TrainExecution implements Execution { private numCars: number, ) {} + public owner(): Player { + return this.player; + } + init(mg: Game, ticks: number): void { this.mg = mg; const stations = this.railNetwork.findStationsPath( diff --git a/src/core/game/GameUpdates.ts b/src/core/game/GameUpdates.ts index c04eea8d8..565960479 100644 --- a/src/core/game/GameUpdates.ts +++ b/src/core/game/GameUpdates.ts @@ -69,9 +69,9 @@ export type GameUpdate = export interface BonusEventUpdate { type: GameUpdateType.BonusEvent; + player: PlayerID; tile: TileRef; gold: number; - workers: number; troops: number; } diff --git a/src/core/game/PlayerImpl.ts b/src/core/game/PlayerImpl.ts index 3fe210f59..a57419642 100644 --- a/src/core/game/PlayerImpl.ts +++ b/src/core/game/PlayerImpl.ts @@ -704,9 +704,9 @@ export class PlayerImpl implements Player { if (tile) { this.mg.addUpdate({ type: GameUpdateType.BonusEvent, + player: this.id(), tile, gold: Number(toAdd), - workers: 0, troops: 0, }); } diff --git a/src/core/game/TrainStation.ts b/src/core/game/TrainStation.ts index b0149e598..a0e78fa01 100644 --- a/src/core/game/TrainStation.ts +++ b/src/core/game/TrainStation.ts @@ -25,8 +25,15 @@ class CityStopHandler implements TrainStopHandler { trainExecution: TrainExecution, ): void { const level = BigInt(station.unit.level() + 1); - const goldBonus = (mg.config().trainGold() * level) / this.factor; - station.unit.owner().addGold(goldBonus, station.tile()); + let goldBonus = (mg.config().trainGold() * level) / this.factor; + const stationOwner = station.unit.owner(); + const trainOwner = trainExecution.owner(); + // Share revenue with the station owner if it's not the current player + if (stationOwner.isFriendly(trainOwner)) { + goldBonus += BigInt(1_000); // Bonus for everybody when trading with an ally! + stationOwner.addGold(goldBonus, station.tile()); + } + trainOwner.addGold(goldBonus, station.tile()); } } @@ -39,8 +46,15 @@ class PortStopHandler implements TrainStopHandler { trainExecution: TrainExecution, ): void { const level = BigInt(station.unit.level() + 1); - const goldBonus = (mg.config().trainGold() * level) / this.factor; - station.unit.owner().addGold(goldBonus, station.tile()); + let goldBonus = (mg.config().trainGold() * level) / this.factor; + const stationOwner = station.unit.owner(); + const trainOwner = trainExecution.owner(); + // Share revenue with the station owner if it's not the current player + if (stationOwner.isFriendly(trainOwner)) { + goldBonus += BigInt(1_000); // Bonus for everybody when trading with an ally! + stationOwner.addGold(goldBonus, station.tile()); + } + trainOwner.addGold(goldBonus, station.tile()); } } @@ -51,7 +65,15 @@ class FactoryStopHandler implements TrainStopHandler { station: TrainStation, trainExecution: TrainExecution, ): void { - station.unit.owner().addGold(mg.config().trainGold(), station.tile()); + let goldBonus = mg.config().trainGold(); + const stationOwner = station.unit.owner(); + const trainOwner = trainExecution.owner(); + // Share revenue with the station owner if it's not the current player + if (stationOwner.isFriendly(trainOwner)) { + goldBonus += BigInt(1_000); // Bonus for everybody when trading with an ally! + stationOwner.addGold(goldBonus, station.tile()); + } + trainOwner.addGold(goldBonus, station.tile()); } } @@ -207,7 +229,10 @@ export class Cluster { availableForTrade(player: Player): Set { const tradingStations = new Set(); for (const station of this.stations) { - if (station.tradeAvailable(player)) { + if ( + station.unit.owner() === player || + station.unit.owner().isFriendly(player) + ) { tradingStations.add(station); } } diff --git a/tests/core/game/TrainStation.test.ts b/tests/core/game/TrainStation.test.ts index e67458964..fba6d03da 100644 --- a/tests/core/game/TrainStation.test.ts +++ b/tests/core/game/TrainStation.test.ts @@ -1,5 +1,5 @@ import { TrainExecution } from "../../../src/core/execution/TrainExecution"; -import { Game, Unit, UnitType } from "../../../src/core/game/Game"; +import { Game, Player, Unit, UnitType } from "../../../src/core/game/Game"; import { Cluster, TrainStation } from "../../../src/core/game/TrainStation"; jest.mock("../../../src/core/game/Game"); @@ -9,25 +9,28 @@ jest.mock("../../../src/core/PseudoRandom"); describe("TrainStation", () => { let game: jest.Mocked; let unit: jest.Mocked; + let player: jest.Mocked; let trainExecution: jest.Mocked; beforeEach(() => { game = { ticks: jest.fn().mockReturnValue(123), config: jest.fn().mockReturnValue({ - trainGold: () => BigInt(10), + trainGold: () => BigInt(4000), }), addUpdate: jest.fn(), addExecution: jest.fn(), } as any; + player = { + addGold: jest.fn(), + id: 1, + canTrade: jest.fn().mockReturnValue(true), + isFriendly: jest.fn().mockReturnValue(false), + } as any; + unit = { - owner: jest.fn().mockReturnValue({ - addGold: jest.fn(), - id: 1, - canTrade: jest.fn().mockReturnValue(true), - tradingPorts: jest.fn().mockReturnValue([{ name: "Port1" }]), - }), + owner: jest.fn().mockReturnValue(player), level: jest.fn().mockReturnValue(1), tile: jest.fn().mockReturnValue({ x: 0, y: 0 }), type: jest.fn(), @@ -36,6 +39,8 @@ describe("TrainStation", () => { trainExecution = { loadCargo: jest.fn(), + owner: jest.fn().mockReturnValue(player), + level: jest.fn(), } as any; }); @@ -45,7 +50,21 @@ describe("TrainStation", () => { station.onTrainStop(trainExecution); - expect(unit.owner().addGold).toHaveBeenCalledWith(10n, unit.tile()); + expect(unit.owner().addGold).toHaveBeenCalledWith(4000n, unit.tile()); + }); + + it("handles allied trade", () => { + unit.type.mockReturnValue(UnitType.City); + player.isFriendly.mockReturnValue(true); + const station = new TrainStation(game, unit); + + station.onTrainStop(trainExecution); + + expect(unit.owner().addGold).toHaveBeenCalledWith(5000n, unit.tile()); + expect(trainExecution.owner().addGold).toHaveBeenCalledWith( + 5000n, + unit.tile(), + ); }); it("checks trade availability (same owner)", () => {