Fix external train trade attribution in match results (#3589)

## Description:

A player reported that income received from opponents' trains was
missing from match result logs.
Fix external train trade attribution to the station owner and add
regression tests for train income stats.

## 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:
aotumuri
This commit is contained in:
Aotumuri
2026-04-15 11:35:32 +09:00
committed by GitHub
parent e569c05fb1
commit d05f5e748c
3 changed files with 53 additions and 5 deletions
+1 -1
View File
@@ -29,7 +29,7 @@ class TradeStationStopHandler implements TrainStopHandler {
// Share revenue with the station owner if it's not the current player
if (trainOwner !== stationOwner) {
stationOwner.addGold(gold, station.tile());
mg.stats().trainExternalTrade(trainOwner, gold);
mg.stats().trainExternalTrade(stationOwner, gold);
}
trainOwner.addGold(gold, station.tile());
mg.stats().trainSelfTrade(trainOwner, gold);
+9
View File
@@ -181,6 +181,15 @@ describe("Stats", () => {
});
});
test("train trade gold", () => {
stats.trainSelfTrade(player1, 2);
stats.trainExternalTrade(player2, 1);
expect(stats.stats()).toStrictEqual({
client1: { gold: [0n, 0n, 0n, 0n, 2n] },
client2: { gold: [0n, 0n, 0n, 0n, 0n, 1n] },
});
});
test("unitBuild", () => {
stats.unitBuild(player1, UnitType.City);
expect(stats.stats()).toStrictEqual({
+43 -4
View File
@@ -24,11 +24,19 @@ vi.mock("../../../src/core/PseudoRandom");
describe("TrainStation", () => {
let game: Mocked<Game>;
let gameStats: {
trainExternalTrade: ReturnType<typeof vi.fn>;
trainSelfTrade: ReturnType<typeof vi.fn>;
};
let unit: Mocked<Unit>;
let player: Mocked<Player>;
let trainExecution: Mocked<TrainExecution>;
beforeEach(() => {
gameStats = {
trainExternalTrade: vi.fn(),
trainSelfTrade: vi.fn(),
};
game = {
ticks: vi.fn().mockReturnValue(123),
config: vi.fn().mockReturnValue({
@@ -37,16 +45,15 @@ describe("TrainStation", () => {
}),
addUpdate: vi.fn(),
addExecution: vi.fn(),
stats: vi.fn().mockReturnValue({
trainExternalTrade: vi.fn(),
trainSelfTrade: vi.fn(),
}),
stats: vi.fn().mockReturnValue(gameStats),
} as any;
player = {
addGold: vi.fn(),
id: 1,
canTrade: vi.fn().mockReturnValue(true),
isAlliedWith: vi.fn().mockReturnValue(false),
isOnSameTeam: vi.fn().mockReturnValue(false),
isFriendly: vi.fn().mockReturnValue(false),
} as any;
@@ -89,6 +96,38 @@ describe("TrainStation", () => {
);
});
it("records external trade on the station owner", () => {
const stationOwner = {
addGold: vi.fn(),
id: 1,
canTrade: vi.fn().mockReturnValue(true),
isAlliedWith: vi.fn().mockReturnValue(false),
isOnSameTeam: vi.fn().mockReturnValue(false),
} as any;
const trainOwner = {
addGold: vi.fn(),
id: 2,
canTrade: vi.fn().mockReturnValue(true),
isAlliedWith: vi.fn().mockReturnValue(false),
isOnSameTeam: vi.fn().mockReturnValue(false),
} as any;
unit.type.mockReturnValue(UnitType.City);
unit.owner.mockReturnValue(stationOwner);
trainExecution.owner.mockReturnValue(trainOwner);
const station = new TrainStation(game, unit);
station.onTrainStop(trainExecution);
expect(stationOwner.addGold).toHaveBeenCalledWith(500n, unit.tile());
expect(trainOwner.addGold).toHaveBeenCalledWith(500n, unit.tile());
expect(gameStats.trainExternalTrade).toHaveBeenCalledWith(
stationOwner,
500n,
);
expect(gameStats.trainSelfTrade).toHaveBeenCalledWith(trainOwner, 500n);
});
it("passes tradeStopsVisited to trainGold", () => {
unit.type.mockReturnValue(UnitType.City);
const trainGoldSpy = vi.fn().mockReturnValue(500n);