mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-21 17:46:46 +00:00
0943b1544c
## Description: Players received "traitor" debuff when alliances were formed after attacks started, creating an unfair race condition. the problem was mentioned here https://discord.com/channels/1284581928254701718/1399115120486912100 ## 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: Kipstzz --------- Co-authored-by: Scott Anderson <662325+scottanderson@users.noreply.github.com>
259 lines
7.2 KiB
TypeScript
259 lines
7.2 KiB
TypeScript
import { AttackExecution } from "../src/core/execution/AttackExecution";
|
|
import { SpawnExecution } from "../src/core/execution/SpawnExecution";
|
|
import { TransportShipExecution } from "../src/core/execution/TransportShipExecution";
|
|
import {
|
|
Game,
|
|
Player,
|
|
PlayerInfo,
|
|
PlayerType,
|
|
UnitType,
|
|
} from "../src/core/game/Game";
|
|
import { TileRef } from "../src/core/game/GameMap";
|
|
import { setup } from "./util/Setup";
|
|
import { TestConfig } from "./util/TestConfig";
|
|
import { constructionExecution } from "./util/utils";
|
|
|
|
let game: Game;
|
|
let attacker: Player;
|
|
let defender: Player;
|
|
let defenderSpawn: TileRef;
|
|
let attackerSpawn: TileRef;
|
|
|
|
function sendBoat(target: TileRef, source: TileRef, troops: number) {
|
|
game.addExecution(
|
|
new TransportShipExecution(defender, null, target, troops, source),
|
|
);
|
|
}
|
|
|
|
describe("Attack", () => {
|
|
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, 10);
|
|
|
|
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);
|
|
|
|
game.addExecution(
|
|
new AttackExecution(100, defender, game.terraNullius().id()),
|
|
);
|
|
game.executeNextTick();
|
|
while (defender.outgoingAttacks().length > 0) {
|
|
game.executeNextTick();
|
|
}
|
|
|
|
(game.config() as TestConfig).setDefaultNukeSpeed(50);
|
|
});
|
|
|
|
test("Nuke reduce attacking troop counts", async () => {
|
|
// Not building exactly spawn to it's better protected from attacks (but still
|
|
// on defender territory)
|
|
constructionExecution(game, defender, 1, 1, UnitType.MissileSilo);
|
|
expect(defender.units(UnitType.MissileSilo)).toHaveLength(1);
|
|
game.addExecution(new AttackExecution(100, attacker, defender.id()));
|
|
constructionExecution(game, defender, 0, 15, UnitType.AtomBomb, 3);
|
|
const nuke = defender.units(UnitType.AtomBomb)[0];
|
|
expect(nuke.isActive()).toBe(true);
|
|
|
|
expect(attacker.outgoingAttacks()).toHaveLength(1);
|
|
expect(attacker.outgoingAttacks()[0].troops()).toBe(98);
|
|
|
|
// Make the nuke go kaboom
|
|
game.executeNextTick();
|
|
expect(nuke.isActive()).toBe(false);
|
|
expect(attacker.outgoingAttacks()[0].troops()).not.toBe(97);
|
|
expect(attacker.outgoingAttacks()[0].troops()).toBeLessThan(90);
|
|
});
|
|
|
|
test("Nuke reduce attacking boat troop count", async () => {
|
|
constructionExecution(game, defender, 1, 1, UnitType.MissileSilo);
|
|
expect(defender.units(UnitType.MissileSilo)).toHaveLength(1);
|
|
|
|
sendBoat(game.ref(15, 8), game.ref(10, 5), 100);
|
|
|
|
constructionExecution(game, defender, 0, 15, UnitType.AtomBomb, 3);
|
|
const nuke = defender.units(UnitType.AtomBomb)[0];
|
|
expect(nuke.isActive()).toBe(true);
|
|
|
|
const ship = defender.units(UnitType.TransportShip)[0];
|
|
expect(ship.troops()).toBe(100);
|
|
|
|
game.executeNextTick();
|
|
|
|
expect(nuke.isActive()).toBe(false);
|
|
expect(defender.units(UnitType.TransportShip)[0].troops()).toBeLessThan(90);
|
|
});
|
|
});
|
|
|
|
describe("Attack race condition with alliance requests", () => {
|
|
it("should not mark attacker as traitor when alliance is formed after attack starts", async () => {
|
|
const game = await setup("ocean_and_land", {
|
|
infiniteGold: true,
|
|
instantBuild: true,
|
|
infiniteTroops: true,
|
|
});
|
|
|
|
const playerAInfo = new PlayerInfo(
|
|
"playerA",
|
|
PlayerType.Human,
|
|
null,
|
|
"playerA_id",
|
|
);
|
|
const playerBInfo = new PlayerInfo(
|
|
"playerB",
|
|
PlayerType.Human,
|
|
null,
|
|
"playerB_id",
|
|
);
|
|
|
|
game.addPlayer(playerAInfo);
|
|
game.addPlayer(playerBInfo);
|
|
|
|
const playerA = game.player(playerAInfo.id);
|
|
const playerB = game.player(playerBInfo.id);
|
|
|
|
// Spawn both players
|
|
const spawnA = game.ref(0, 10);
|
|
const spawnB = game.ref(0, 15);
|
|
|
|
game.addExecution(
|
|
new SpawnExecution(playerAInfo, spawnA),
|
|
new SpawnExecution(playerBInfo, spawnB),
|
|
);
|
|
|
|
while (game.inSpawnPhase()) {
|
|
game.executeNextTick();
|
|
}
|
|
|
|
// Player A sends alliance request to Player B
|
|
const allianceRequest = playerA.createAllianceRequest(playerB);
|
|
expect(allianceRequest).not.toBeNull();
|
|
|
|
// Player A attacks Player B
|
|
const attackExecution = new AttackExecution(
|
|
null,
|
|
playerA,
|
|
playerB.id(),
|
|
null,
|
|
);
|
|
game.addExecution(attackExecution);
|
|
|
|
// Player B counter-attacks Player A
|
|
const counterAttackExecution = new AttackExecution(
|
|
null,
|
|
playerB,
|
|
playerA.id(),
|
|
null,
|
|
);
|
|
game.addExecution(counterAttackExecution);
|
|
|
|
// Player B accepts the alliance request
|
|
if (allianceRequest) {
|
|
allianceRequest.accept();
|
|
}
|
|
|
|
// Execute a few ticks to process the attacks
|
|
for (let i = 0; i < 5; i++) {
|
|
game.executeNextTick();
|
|
}
|
|
|
|
// Player A should not be marked as traitor because the alliance was formed after the attack started
|
|
expect(playerA.isTraitor()).toBe(false);
|
|
|
|
// The attacks should have retreated due to the alliance being formed
|
|
expect(playerA.outgoingAttacks()).toHaveLength(0);
|
|
expect(playerB.outgoingAttacks()).toHaveLength(0);
|
|
});
|
|
|
|
it("should mark attacker as traitor when alliance existed before attack", async () => {
|
|
const game = await setup("ocean_and_land", {
|
|
infiniteGold: true,
|
|
instantBuild: true,
|
|
infiniteTroops: true,
|
|
});
|
|
|
|
const playerAInfo = new PlayerInfo(
|
|
"playerA",
|
|
PlayerType.Human,
|
|
null,
|
|
"playerA_id",
|
|
);
|
|
const playerBInfo = new PlayerInfo(
|
|
"playerB",
|
|
PlayerType.Human,
|
|
null,
|
|
"playerB_id",
|
|
);
|
|
|
|
game.addPlayer(playerAInfo);
|
|
game.addPlayer(playerBInfo);
|
|
|
|
const playerA = game.player(playerAInfo.id);
|
|
const playerB = game.player(playerBInfo.id);
|
|
|
|
// Spawn both players
|
|
const spawnA = game.ref(0, 10);
|
|
const spawnB = game.ref(0, 15);
|
|
|
|
game.addExecution(
|
|
new SpawnExecution(playerAInfo, spawnA),
|
|
new SpawnExecution(playerBInfo, spawnB),
|
|
);
|
|
|
|
while (game.inSpawnPhase()) {
|
|
game.executeNextTick();
|
|
}
|
|
|
|
// Create an alliance between Player A and Player B
|
|
const allianceRequest = playerA.createAllianceRequest(playerB);
|
|
if (allianceRequest) {
|
|
allianceRequest.accept();
|
|
}
|
|
|
|
// Player A attacks Player B (should break the alliance)
|
|
const attackExecution = new AttackExecution(
|
|
null,
|
|
playerA,
|
|
playerB.id(),
|
|
null,
|
|
);
|
|
game.addExecution(attackExecution);
|
|
|
|
// Execute a few ticks to process the attack
|
|
for (let i = 0; i < 10; i++) {
|
|
game.executeNextTick();
|
|
}
|
|
|
|
// Player A should be marked as traitor because they attacked an ally
|
|
expect(playerA.isTraitor()).toBe(true);
|
|
});
|
|
});
|