diff --git a/src/client/graphics/layers/EventsDisplay.ts b/src/client/graphics/layers/EventsDisplay.ts index 92bff376c..67a8de81b 100644 --- a/src/client/graphics/layers/EventsDisplay.ts +++ b/src/client/graphics/layers/EventsDisplay.ts @@ -499,6 +499,8 @@ export class EventsDisplay extends LitElement implements Layer { const betrayed = this.game.playerBySmallID(update.betrayedID) as PlayerView; const traitor = this.game.playerBySmallID(update.traitorID) as PlayerView; + if (betrayed.isDisconnected()) return; // Do not send the message if betraying a disconnected player + if (!betrayed.isTraitor() && traitor === myPlayer) { const malusPercent = Math.round( (1 - this.game.config().traitorDefenseDebuff()) * 100, diff --git a/src/core/game/GameImpl.ts b/src/core/game/GameImpl.ts index f074cabef..1d6349608 100644 --- a/src/core/game/GameImpl.ts +++ b/src/core/game/GameImpl.ts @@ -591,7 +591,7 @@ export class GameImpl implements Game { `${breaker} not allied with ${other}, cannot break alliance`, ); } - if (!other.isTraitor()) { + if (!other.isTraitor() && !other.isDisconnected()) { breaker.markTraitor(); } diff --git a/tests/core/game/GameImpl.test.ts b/tests/core/game/GameImpl.test.ts new file mode 100644 index 000000000..a48cdb145 --- /dev/null +++ b/tests/core/game/GameImpl.test.ts @@ -0,0 +1,121 @@ +import { AttackExecution } from "../../../src/core/execution/AttackExecution"; +import { SpawnExecution } from "../../../src/core/execution/SpawnExecution"; +//import { TransportShipExecution } from "../../../src/core/execution/TransportShipExecution"; +import { AllianceRequestExecution } from "../../../src/core/execution/alliance/AllianceRequestExecution"; +import { AllianceRequestReplyExecution } from "../../../src/core/execution/alliance/AllianceRequestReplyExecution"; +import { + Game, + Player, + PlayerInfo, + PlayerType, +} from "../../../src/core/game/Game"; +import { TileRef } from "../../../src/core/game/GameMap"; +import { setup } from "../../util/Setup"; + +let game: Game; +let attacker: Player; +let defender: Player; +let defenderSpawn: TileRef; +let attackerSpawn: TileRef; + +describe("GameImpl", () => { + beforeEach(async () => { + game = await setup("ocean_and_land", { + infiniteGold: true, + instantBuild: true, + infiniteTroops: true, + }); + const attackerInfo = new PlayerInfo( + "attacker dude", + PlayerType.Human, + null, + "attacker_id", + ); + game.addPlayer(attackerInfo); + const defenderInfo = new PlayerInfo( + "defender dude", + PlayerType.Human, + null, + "defender_id", + ); + game.addPlayer(defenderInfo); + + defenderSpawn = game.ref(0, 15); + attackerSpawn = game.ref(0, 14); + + game.addExecution( + new SpawnExecution(game.player(attackerInfo.id).info(), attackerSpawn), + new SpawnExecution(game.player(defenderInfo.id).info(), defenderSpawn), + ); + + while (game.inSpawnPhase()) { + game.executeNextTick(); + } + + attacker = game.player(attackerInfo.id); + defender = game.player(defenderInfo.id); + }); + + test("Don't become traitor when betraying inactive player", async () => { + jest.spyOn(attacker, "canSendAllianceRequest").mockReturnValue(true); + game.addExecution(new AllianceRequestExecution(attacker, defender.id())); + + game.executeNextTick(); + game.executeNextTick(); + + game.addExecution( + new AllianceRequestReplyExecution(attacker.id(), defender, true), + ); + + game.executeNextTick(); + game.executeNextTick(); + + expect(attacker.allianceWith(defender)).toBeTruthy(); + expect(defender.allianceWith(attacker)).toBeTruthy(); + + //Defender is marked disconnected + defender.markDisconnected(true); + + game.executeNextTick(); + game.executeNextTick(); + + game.addExecution(new AttackExecution(100, attacker, defender.id())); + + do { + game.executeNextTick(); + } while (attacker.outgoingAttacks().length > 0); + + expect(attacker.isTraitor()).toBe(false); + }); + + test("Do become traitor when betraying active player", async () => { + jest.spyOn(attacker, "canSendAllianceRequest").mockReturnValue(true); + game.addExecution(new AllianceRequestExecution(attacker, defender.id())); + + game.executeNextTick(); + game.executeNextTick(); + + game.addExecution( + new AllianceRequestReplyExecution(attacker.id(), defender, true), + ); + + game.executeNextTick(); + game.executeNextTick(); + + expect(attacker.allianceWith(defender)).toBeTruthy(); + expect(defender.allianceWith(attacker)).toBeTruthy(); + + //Defender is NOT marked disconnected + + game.executeNextTick(); + game.executeNextTick(); + + game.addExecution(new AttackExecution(100, attacker, defender.id())); + + do { + game.executeNextTick(); + } while (attacker.outgoingAttacks().length > 0); + + expect(attacker.isTraitor()).toBe(true); + }); +});