From 6c8ce958b2e94de2849015c2a2d7b58dd487101b Mon Sep 17 00:00:00 2001 From: FloPinguin <25036848+FloPinguin@users.noreply.github.com> Date: Mon, 15 Jun 2026 02:54:08 +0200 Subject: [PATCH] =?UTF-8?q?Fix=20nations=20being=20blocked=20by=20PVP=20im?= =?UTF-8?q?munity=20=F0=9F=9B=A1=EF=B8=8F=20(#4282)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Description: ### Problem PVP immunity (the extended spawn immunity setting) was incorrectly preventing AI nations from attacking human players. The intent of PVP immunity is to protect human-vs-human combat only, but nations were subject to the same restriction. ### Root Cause In `canAttackPlayer()`, only `PlayerType.Bot` was exempt from checking target immunity. Nations fell through to the same path as humans, so when a nation tried to attack an immune human, `player.isImmune()` returned true and the attack was blocked. ### Fix Changed the immunity bypass condition from `this.type() === PlayerType.Bot` to `this.type() !== PlayerType.Human`. Now only human attackers check target immunity. Both bots and nations bypass it (they only check alliance status). This does not affect nation spawn immunity (`nationSpawnImmunityDuration`), which is a separate mechanism that protects newly spawned nations from all attackers and continues to work independently. ## 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 ## Please put your Discord username so you can be contacted if a bug or regression is found: FloPinguin --- src/core/game/PlayerImpl.ts | 5 +-- tests/Attack.test.ts | 78 +++++++++++++++++++++++++++++++++++++ 2 files changed, 80 insertions(+), 3 deletions(-) diff --git a/src/core/game/PlayerImpl.ts b/src/core/game/PlayerImpl.ts index e2d3d14ee..5d216f29b 100644 --- a/src/core/game/PlayerImpl.ts +++ b/src/core/game/PlayerImpl.ts @@ -1608,11 +1608,10 @@ export class PlayerImpl implements Player { player: Player, treatAFKFriendly: boolean = false, ): boolean { - if (this.type() === PlayerType.Bot) { - // Bots are not affected by immunity + if (this.type() !== PlayerType.Human) { + // Only human attackers respect PVP immunity return !this.isFriendly(player, treatAFKFriendly); } - // Humans and Nations respect immunity return !player.isImmune() && !this.isFriendly(player, treatAFKFriendly); } diff --git a/tests/Attack.test.ts b/tests/Attack.test.ts index a376de974..da7ec9a2a 100644 --- a/tests/Attack.test.ts +++ b/tests/Attack.test.ts @@ -542,4 +542,82 @@ describe("Attack immunity", () => { expect(exec.isActive()).toBe(false); expect(playerA.units(UnitType.TransportShip)).toHaveLength(0); }); + + test("Nation can attack human during PVP immunity", async () => { + const nationInfo = new PlayerInfo( + "nation", + PlayerType.Nation, + null, + "nation_id", + ); + const nation = addPlayerToGame(nationInfo, game, game.ref(15, 0)); + game.executeNextTick(); + game.executeNextTick(); + + // Nation attacks playerA during PVP immunity - should succeed + game.addExecution(new AttackExecution(null, nation, "playerA_id", null)); + game.executeNextTick(); + expect(nation.outgoingAttacks()).toHaveLength(1); + }); + + test("Bot can attack human during PVP immunity", async () => { + const botInfo = new PlayerInfo("bot", PlayerType.Bot, null, "bot_id"); + const bot = addPlayerToGame(botInfo, game, game.ref(15, 0)); + game.executeNextTick(); + game.executeNextTick(); + + // Bot attacks playerA during PVP immunity - should succeed + game.addExecution(new AttackExecution(null, bot, "playerA_id", null)); + game.executeNextTick(); + expect(bot.outgoingAttacks()).toHaveLength(1); + }); + + test("Nation can attack nation during PVP immunity", async () => { + const nationAInfo = new PlayerInfo( + "nationA", + PlayerType.Nation, + null, + "nationA_id", + ); + const nationA = addPlayerToGame(nationAInfo, game, game.ref(15, 0)); + + const nationBInfo = new PlayerInfo( + "nationB", + PlayerType.Nation, + null, + "nationB_id", + ); + addPlayerToGame(nationBInfo, game, game.ref(15, 15)); + game.executeNextTick(); + game.executeNextTick(); + + // Nation A attacks Nation B during PVP immunity - should succeed + game.addExecution(new AttackExecution(null, nationA, "nationB_id", null)); + game.executeNextTick(); + expect(nationA.outgoingAttacks()).toHaveLength(1); + }); + + test("Nation cannot attack allied human during PVP immunity", async () => { + const nationInfo = new PlayerInfo( + "nation", + PlayerType.Nation, + null, + "nation_id", + ); + const nation = addPlayerToGame(nationInfo, game, game.ref(15, 0)); + game.executeNextTick(); + game.executeNextTick(); + + // Create alliance between nation and playerA + const allianceRequest = nation.createAllianceRequest(playerA); + if (allianceRequest) { + allianceRequest.accept(); + } + expect(nation.isAlliedWith(playerA)).toBe(true); + + // Nation tries to attack allied playerA during immunity - should be blocked by friendliness + game.addExecution(new AttackExecution(null, nation, "playerA_id", null)); + game.executeNextTick(); + expect(nation.outgoingAttacks()).toHaveLength(0); + }); });