mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-21 13:10:16 +00:00
a71ac7a218
## Description: **--Fix oversight in v24--** In v24, alliance renewal was introduced. But a Bot or Nation never answers to it. So the Event Panel expiration message + clicking to Renew and waiting, is all in vain if the other player is not a human. Like in Single player in all cases. The message after ~30 seconds is always "Alliance with xxx expired". This feels illogical and there's no purpose for showing a Request to Renew button if it then always expires. Also reported by players like here: https://discord.com/channels/1284581928254701718/1284581928833388619/1398249123093676094 This PR fixes it by having the non-human reciever of the request, say yes to depending on attiude towards the human and chance. This feels more realistic. The requestor is always the human player because they click a button in EventsDisplay. So there is always already an extension request which the bot can react to with another extension request to have the alliance be extended. **--Add tests--** It adds tests for extending alliance between human and non-human player. One for AllianceExtensionExecution simply testing if alliance between human and non-human can be extended. And one in BotBehavior, testing if it correctly handles an extension request by adding a new AllianceExtensionExecution. **--Fix silent bug in existing test--** Adding the new test for human and non-human for AllianceExtensionExecution, i ran into a bug in the existing test for extending alliance between humans. Which made the test always pass because expirationAt wasn't fetched correctly. Had to fix that too, without intending that for this PR beforehand. And then had to include the bugfix from PR #1582 (v25) in it too to have the alliance actually extended. More details below: (-- The existing test would always return 'all passed' because it did not get the expiresAt() but got the createdAt + config.AllianceDuration() for both expirationBefore and expirationAfter. createdAt is immutable so before and after would be the same. And then it did not test for toBeGreaterThan, which would have failed. But it tested wrongfully for toBeGreaterThanOrEqual which was a pass even when the expirationBefore and expirationAfter would be the same and no extension had taken place. -- The bugfix from PR 1582 needed to be included now too. Because only with those changes, the existing test has its alliance truly extended and only with that the expirationAt actually changed. Actually, checking if extend() was called isn't needed anymore, since we now check the expirationAt correctly which on its own tells us if extend() was succesful. But left this addition from PR 1582 in since it can't do any harm.) ## 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 - [ ] 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: tryout33 --------- Co-authored-by: evanpelle <openfrontio@gmail.com> Co-authored-by: Drills Kibo <59177241+drillskibo@users.noreply.github.com>
230 lines
6.6 KiB
TypeScript
230 lines
6.6 KiB
TypeScript
import { AllianceExtensionExecution } from "../src/core/execution/alliance/AllianceExtensionExecution";
|
|
import { BotBehavior } from "../src/core/execution/utils/BotBehavior";
|
|
import {
|
|
AllianceRequest,
|
|
Game,
|
|
Player,
|
|
PlayerInfo,
|
|
PlayerType,
|
|
Relation,
|
|
Tick,
|
|
} from "../src/core/game/Game";
|
|
import { PseudoRandom } from "../src/core/PseudoRandom";
|
|
import { setup } from "./util/Setup";
|
|
|
|
let game: Game;
|
|
let player: Player;
|
|
let requestor: Player;
|
|
let botBehavior: BotBehavior;
|
|
|
|
describe("BotBehavior.handleAllianceRequests", () => {
|
|
beforeEach(async () => {
|
|
game = await setup("big_plains", {
|
|
infiniteGold: true,
|
|
instantBuild: true,
|
|
});
|
|
|
|
const playerInfo = new PlayerInfo(
|
|
"player_id",
|
|
PlayerType.Bot,
|
|
null,
|
|
"player_id",
|
|
);
|
|
const requestorInfo = new PlayerInfo(
|
|
"requestor_id",
|
|
PlayerType.Human,
|
|
null,
|
|
"requestor_id",
|
|
);
|
|
|
|
game.addPlayer(playerInfo);
|
|
game.addPlayer(requestorInfo);
|
|
|
|
player = game.player("player_id");
|
|
requestor = game.player("requestor_id");
|
|
|
|
const random = new PseudoRandom(42);
|
|
|
|
botBehavior = new BotBehavior(random, game, player, 0.5, 0.5, 0.2);
|
|
});
|
|
|
|
function setupAllianceRequest({
|
|
isTraitor = false,
|
|
relationDelta = 2,
|
|
numTilesPlayer = 10,
|
|
numTilesRequestor = 10,
|
|
alliancesCount = 0,
|
|
} = {}) {
|
|
if (isTraitor) requestor.markTraitor();
|
|
|
|
player.updateRelation(requestor, relationDelta);
|
|
requestor.updateRelation(player, relationDelta);
|
|
|
|
game.map().forEachTile((tile) => {
|
|
if (game.map().isLand(tile)) {
|
|
if (numTilesPlayer > 0) {
|
|
player.conquer(tile);
|
|
numTilesPlayer--;
|
|
} else if (numTilesRequestor > 0) {
|
|
requestor.conquer(tile);
|
|
numTilesRequestor--;
|
|
}
|
|
}
|
|
});
|
|
|
|
jest
|
|
.spyOn(requestor, "alliances")
|
|
.mockReturnValue(new Array(alliancesCount));
|
|
|
|
const mockRequest = {
|
|
requestor: () => requestor,
|
|
recipient: () => player,
|
|
createdAt: () => 0 as unknown as Tick,
|
|
accept: jest.fn(),
|
|
reject: jest.fn(),
|
|
} as unknown as AllianceRequest;
|
|
|
|
jest
|
|
.spyOn(player, "incomingAllianceRequests")
|
|
.mockReturnValue([mockRequest]);
|
|
|
|
return mockRequest;
|
|
}
|
|
|
|
test("should accept alliance when all conditions are met", () => {
|
|
const request = setupAllianceRequest({});
|
|
|
|
botBehavior.handleAllianceRequests();
|
|
|
|
expect(request.accept).toHaveBeenCalled();
|
|
expect(request.reject).not.toHaveBeenCalled();
|
|
});
|
|
|
|
test("should reject alliance if requestor is a traitor", () => {
|
|
const request = setupAllianceRequest({ isTraitor: true });
|
|
|
|
botBehavior.handleAllianceRequests();
|
|
|
|
expect(request.accept).not.toHaveBeenCalled();
|
|
expect(request.reject).toHaveBeenCalled();
|
|
});
|
|
|
|
test("should reject alliance if relation is malicious", () => {
|
|
const request = setupAllianceRequest({ relationDelta: -2 });
|
|
|
|
botBehavior.handleAllianceRequests();
|
|
|
|
expect(request.accept).not.toHaveBeenCalled();
|
|
expect(request.reject).toHaveBeenCalled();
|
|
});
|
|
|
|
test("should accept alliance if requestor is much larger (> 3 times size of recipient) and has too many alliances (>= 3)", () => {
|
|
const request = setupAllianceRequest({
|
|
numTilesRequestor: 40,
|
|
alliancesCount: 4,
|
|
});
|
|
|
|
botBehavior.handleAllianceRequests();
|
|
|
|
expect(request.accept).toHaveBeenCalled();
|
|
expect(request.reject).not.toHaveBeenCalled();
|
|
});
|
|
|
|
test("should accept alliance if requestor is much larger (> 3 times size of recipient) and does not have too many alliances (< 3)", () => {
|
|
const request = setupAllianceRequest({
|
|
numTilesRequestor: 40,
|
|
alliancesCount: 2,
|
|
});
|
|
|
|
botBehavior.handleAllianceRequests();
|
|
|
|
expect(request.accept).toHaveBeenCalled();
|
|
expect(request.reject).not.toHaveBeenCalled();
|
|
});
|
|
|
|
test("should reject alliance if requestor is acceptably small (<= 3 times size of recipient) and has too many alliances (>= 3)", () => {
|
|
const request = setupAllianceRequest({ alliancesCount: 3 });
|
|
|
|
botBehavior.handleAllianceRequests();
|
|
|
|
expect(request.accept).not.toHaveBeenCalled();
|
|
expect(request.reject).toHaveBeenCalled();
|
|
});
|
|
});
|
|
|
|
describe("BotBehavior.handleAllianceExtensionRequests", () => {
|
|
let mockGame: any;
|
|
let mockPlayer: any;
|
|
let mockAlliance: any;
|
|
let mockHuman: any;
|
|
let mockRandom: any;
|
|
let botBehavior: BotBehavior;
|
|
|
|
beforeEach(() => {
|
|
mockGame = { addExecution: jest.fn() };
|
|
mockHuman = { id: jest.fn(() => "human_id") };
|
|
mockAlliance = {
|
|
onlyOneAgreedToExtend: jest.fn(() => true),
|
|
other: jest.fn(() => mockHuman),
|
|
};
|
|
mockRandom = { chance: jest.fn() };
|
|
|
|
mockPlayer = {
|
|
alliances: jest.fn(() => [mockAlliance]),
|
|
relation: jest.fn(),
|
|
id: jest.fn(() => "bot_id"),
|
|
type: jest.fn(() => PlayerType.FakeHuman),
|
|
};
|
|
|
|
botBehavior = new BotBehavior(
|
|
mockRandom,
|
|
mockGame,
|
|
mockPlayer,
|
|
0.5,
|
|
0.5,
|
|
0.2,
|
|
);
|
|
});
|
|
|
|
it("should NOT request extension if onlyOneAgreedToExtend is false (no expiration yet or both already agreed)", () => {
|
|
mockAlliance.onlyOneAgreedToExtend.mockReturnValue(false);
|
|
botBehavior.handleAllianceExtensionRequests();
|
|
expect(mockGame.addExecution).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("should always extend if type Bot", () => {
|
|
mockPlayer.type.mockReturnValue(PlayerType.Bot);
|
|
botBehavior.handleAllianceExtensionRequests();
|
|
expect(mockGame.addExecution).toHaveBeenCalledTimes(1);
|
|
expect(mockGame.addExecution.mock.calls[0][0]).toBeInstanceOf(
|
|
AllianceExtensionExecution,
|
|
);
|
|
});
|
|
|
|
it("should always extend if Nation and relation is Friendly", () => {
|
|
mockPlayer.relation.mockReturnValue(Relation.Friendly);
|
|
botBehavior.handleAllianceExtensionRequests();
|
|
expect(mockGame.addExecution).toHaveBeenCalledTimes(1);
|
|
expect(mockGame.addExecution.mock.calls[0][0]).toBeInstanceOf(
|
|
AllianceExtensionExecution,
|
|
);
|
|
});
|
|
|
|
it("should extend if Nation, relation is Neutral and random chance is true", () => {
|
|
mockPlayer.relation.mockReturnValue(Relation.Neutral);
|
|
mockRandom.chance.mockReturnValue(true);
|
|
botBehavior.handleAllianceExtensionRequests();
|
|
expect(mockGame.addExecution).toHaveBeenCalledTimes(1);
|
|
expect(mockGame.addExecution.mock.calls[0][0]).toBeInstanceOf(
|
|
AllianceExtensionExecution,
|
|
);
|
|
});
|
|
|
|
it("should NOT extend if Nation, relation is Neutral and random chance is false", () => {
|
|
mockPlayer.relation.mockReturnValue(Relation.Neutral);
|
|
mockRandom.chance.mockReturnValue(false);
|
|
botBehavior.handleAllianceExtensionRequests();
|
|
expect(mockGame.addExecution).not.toHaveBeenCalled();
|
|
});
|
|
});
|