Don't become traitor when betraying inactive player (#1610)

## Description:

Don't become traitor when betraying inactive player

This PR makes the following changes
- Do not mark the attacking player as a traitor, if they attack/betray
an ally who is disconnected from the game
- Do not send the attacking player the betrayal message (traitor debuff
applied message), if they attack/betray an ally who is disconnected from
the game
- Add test case for traitor debuff being applied if the attacking player
attacks an ally who is still connected to the game
- Add test case for traitor debuff NOT being applied if the attacking
player attacks an ally who is disconnected from the game


I also tested this manually with nuking an allied player who is
connected and nuking an allied player who is disconnected. The logic
worked as expected.

This PR was made in regards to the following issue: #1599 

## 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
- [x] I have read and accepted the CLA agreement (only required once).

## Please put your Discord username so you can be contacted if a bug or
regression is found:

slyty
This commit is contained in:
Tyler Hanavan
2025-07-28 14:22:07 -04:00
committed by GitHub
parent e8c6b93661
commit 0726449b6e
3 changed files with 124 additions and 1 deletions
@@ -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,
+1 -1
View File
@@ -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();
}
+121
View File
@@ -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);
});
});