Fix nations being blocked by PVP immunity 🛡️ (#4282)

## 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
This commit is contained in:
FloPinguin
2026-06-15 02:54:08 +02:00
committed by GitHub
parent f7ce58a49f
commit 6c8ce958b2
2 changed files with 80 additions and 3 deletions
+2 -3
View File
@@ -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);
}
+78
View File
@@ -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);
});
});