From c2663944e59396968549cfe30a6f4689eeff58ef Mon Sep 17 00:00:00 2001 From: FloPinguin <25036848+FloPinguin@users.noreply.github.com> Date: Wed, 4 Feb 2026 23:12:30 +0100 Subject: [PATCH] =?UTF-8?q?Stop=20getting=20gold=20from=20conquering=20ina?= =?UTF-8?q?ctive=20players=20=F0=9F=94=A7=20(#3020)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Description: Maybe for v29. In the 5M starting gold modifier games you can conquer a inactive player (spawned but didn't do anything) and get their 5M gold. Huge unfair advantage. I think that even without the starting gold modifier you should not get the gold of inactive players because its unfair. I identify inactive players (spawned but didn't do anything) by checking the attack stats. I added a translation for the displayMessage "Conquered {name}, received {gold} gold". Why was that not translated? I added a new message "Conquered {name} (Inactive player, received no gold)". ## 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: FloPinguin --------- Co-authored-by: Ryan <7389646+ryanbarlow97@users.noreply.github.com> --- resources/lang/en.json | 2 ++ src/core/game/GameImpl.ts | 49 ++++++++++++++++++++++++++++----------- tests/AttackStats.test.ts | 25 ++++++++++++++++++++ 3 files changed, 63 insertions(+), 13 deletions(-) diff --git a/resources/lang/en.json b/resources/lang/en.json index 44ebe941a..6f06528dc 100644 --- a/resources/lang/en.json +++ b/resources/lang/en.json @@ -771,6 +771,8 @@ "attack_cancelled_retreat": "Attack cancelled, {troops} soldiers killed during retreat", "received_gold_from_captured_ship": "Received {gold} gold from ship captured from {name}", "received_gold_from_trade": "Received {gold} gold from trade with {name}", + "received_gold_from_conquest": "Conquered {name}, received {gold} gold", + "conquered_no_gold": "Conquered {name} (didn't play, no gold awarded)", "missile_intercepted": "Missile intercepted {unit}", "mirv_warheads_intercepted": "{count, plural, one {{count} MIRV warhead intercepted} other {{count} MIRV warheads intercepted}}", "sent_troops_to_player": "Sent {troops} troops to {name}", diff --git a/src/core/game/GameImpl.ts b/src/core/game/GameImpl.ts index 6d409fc58..1f6a97add 100644 --- a/src/core/game/GameImpl.ts +++ b/src/core/game/GameImpl.ts @@ -7,6 +7,7 @@ import { import { AStarWaterHierarchical } from "../pathfinding/algorithms/AStar.WaterHierarchical"; import { PathFinder } from "../pathfinding/types"; import { AllPlayersStats, ClientID, Winner } from "../Schemas"; +import { ATTACK_INDEX_SENT } from "../StatsSchemas"; import { simpleHash } from "../Util"; import { AllianceImpl } from "./AllianceImpl"; import { AllianceRequestImpl } from "./AllianceRequestImpl"; @@ -1097,26 +1098,48 @@ export class GameImpl implements Game { } } - const gold = conquered.gold(); - this.displayMessage( - `Conquered ${conquered.displayName()} received ${renderNumber( + // Don't transfer gold when the conquered player didn't play (never attacked anyone) + // This is especially important when starting gold is enabled + const stats = this._stats.getPlayerStats(conquered); + const attacksSent = stats?.attacks?.[ATTACK_INDEX_SENT] ?? 0n; + const skipGoldTransfer = + attacksSent === 0n && conquered.type() === PlayerType.Human; + const gold = skipGoldTransfer ? 0n : conquered.gold(); + + if (skipGoldTransfer) { + this.displayMessage( + "events_display.conquered_no_gold", + MessageType.CONQUERED_PLAYER, + conqueror.id(), + undefined, + { + name: conquered.displayName(), + }, + ); + } else { + this.displayMessage( + "events_display.received_gold_from_conquest", + MessageType.CONQUERED_PLAYER, + conqueror.id(), gold, - )} gold`, - MessageType.CONQUERED_PLAYER, - conqueror.id(), - gold, - ); - conqueror.addGold(gold); - conquered.removeGold(gold); + { + gold: renderNumber(gold), + name: conquered.displayName(), + }, + ); + conqueror.addGold(gold); + conquered.removeGold(gold); + + // Record stats + this.stats().goldWar(conqueror, conquered, gold); + } + this.addUpdate({ type: GameUpdateType.ConquestEvent, conquerorId: conqueror.id(), conqueredId: conquered.id(), gold, }); - - // Record stats - this.stats().goldWar(conqueror, conquered, gold); } } diff --git a/tests/AttackStats.test.ts b/tests/AttackStats.test.ts index 10cb236c3..23e0ef98f 100644 --- a/tests/AttackStats.test.ts +++ b/tests/AttackStats.test.ts @@ -34,11 +34,36 @@ describe("AttackStats", () => { test("should increase war gold stat when a player is eliminated", () => { expect(player1.sharesBorderWith(player2)).toBeTruthy(); + // Player2 must attack to be considered active (otherwise gold won't transfer) + game.addExecution( + new AttackExecution(1, player2, game.terraNullius().id()), + ); + game.executeNextTick(); performAttack(game, player1, player2); expectWarGoldStatIsIncreasedAfterKill(game, player1, player2); }); + test("should NOT increase war gold stat when a inactive player is eliminated", () => { + expect(player1.sharesBorderWith(player2)).toBeTruthy(); + + const attackerStatsBefore = game.stats().stats()[player1.clientID()!]; + const warGoldBefore = attackerStatsBefore?.gold?.[GOLD_INDEX_WAR] ?? 0n; + + performAttack(game, player1, player2); + + const attackerStatsAfter = game.stats().stats()[player1.clientID()!]; + const warGoldAfter = attackerStatsAfter?.gold?.[GOLD_INDEX_WAR] ?? 0n; + + expect(warGoldAfter).toBe(warGoldBefore); + }); + test("should increase war gold stat when elimination occurs via territory annexation", () => { + // Player2 must attack to be considered active (otherwise gold won't transfer) + game.addExecution( + new AttackExecution(1, player2, game.terraNullius().id()), + ); + game.executeNextTick(); + // Mark every tile on the map as owned by player1 for (let x = 0; x < game.map().width(); x++) { for (let y = 0; y < game.map().height(); y++) {