mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-21 17:56:43 +00:00
427e462fe5
## Description: I closed my previous PR #2533 which was already reviewed by evan (but not yet merged) because I noticed some issues. Which led me to changing the enemy selection entirely. Nations / Bots previously had a fixed enemy which they kept for 100 ticks (10 seconds). This could make them react too late and feel slow. Now they are a bit more responsive. But the main benefit: Without a fixed enemy we can do multiple sendAttack() on the same tick, which allowed me to give impossible nations extremely efficient parallel bot attacks: https://github.com/user-attachments/assets/38f65623-fbf0-4e98-a833-5fcba2ee6eee Previously nations were so slow in taking out bots that you could even encircle them on the Archiran map... Now they are like 200% faster (but only on the impossible difficulty) ## Nuke enemy selection Previously, the enemy for troop attacks and nukes was identical. Now, as we no longer have a fixed enemy in BotBehaviour, I added findBestNukeTarget() to select better nuke-targets. I will probably open a PR soon which makes nations nuke the crown :) ## Betrayal logic While revamping the attack logic I had to work on the betrayal logic, which was quite confusing, with many negations. And the betrayals were just random. So I made it easier to understand with maybeBetrayAndAttack(). Now it does betray friends if we have 10 times more troops than them. I will improve that method in a future PR, but already now it should be better than just betraying randomly. ## Attack order Previously, nations attacked in this order: - TerraNullius (Untaken land and nuked territory) - Bots - Retaliate against incoming attacks Now its in this order: - TerraNullius (Untaken land) - Retaliate against incoming attacks - Bots - TerraNullius (Nuked territory) So the changes are these: - After throwing a nuke onto a nation, they will no longer ignore incoming attacks. Previously they attacked the nuked territory first. Very common singleplayer problem. - Nations now retaliate against incoming attacks before attacking bots. Previously you could attack a nation but they did not care because there were still bots left. I also changed the attack order of bots a bit (retaliate before attacking randoms), but that isn't even noticeable. ## Big bug fixed Additionally, I fixed a big bug: selectEnemy() oftentimes returned null (because of enemySanityCheck) and therefore no attack happened. This was especially visible in games where nations are surrounded by friends (Team games and nations vs humans). This was also the reason why Enzo could play nations vs humans in singleplayer and NO NATION of the much bigger nation team would try to attack him. ## 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 ## Please put your Discord username so you can be contacted if a bug or regression is found: FloPinguin
99 lines
2.5 KiB
TypeScript
99 lines
2.5 KiB
TypeScript
import { Execution, Game, Player } from "../game/Game";
|
|
import { PseudoRandom } from "../PseudoRandom";
|
|
import { simpleHash } from "../Util";
|
|
import { BotBehavior } from "./utils/BotBehavior";
|
|
|
|
export class BotExecution implements Execution {
|
|
private active = true;
|
|
private random: PseudoRandom;
|
|
private mg: Game;
|
|
private neighborsTerraNullius = true;
|
|
|
|
private behavior: BotBehavior | null = null;
|
|
private attackRate: number;
|
|
private attackTick: number;
|
|
private triggerRatio: number;
|
|
private reserveRatio: number;
|
|
private expandRatio: number;
|
|
|
|
constructor(private bot: Player) {
|
|
this.random = new PseudoRandom(simpleHash(bot.id()));
|
|
this.attackRate = this.random.nextInt(40, 80);
|
|
this.attackTick = this.random.nextInt(0, this.attackRate);
|
|
this.triggerRatio = this.random.nextInt(50, 60) / 100;
|
|
this.reserveRatio = this.random.nextInt(30, 40) / 100;
|
|
this.expandRatio = this.random.nextInt(10, 20) / 100;
|
|
}
|
|
|
|
activeDuringSpawnPhase(): boolean {
|
|
return false;
|
|
}
|
|
|
|
init(mg: Game) {
|
|
this.mg = mg;
|
|
}
|
|
|
|
tick(ticks: number) {
|
|
if (ticks % this.attackRate !== this.attackTick) return;
|
|
|
|
if (!this.bot.isAlive()) {
|
|
this.active = false;
|
|
return;
|
|
}
|
|
|
|
if (this.behavior === null) {
|
|
this.behavior = new BotBehavior(
|
|
this.random,
|
|
this.mg,
|
|
this.bot,
|
|
this.triggerRatio,
|
|
this.reserveRatio,
|
|
this.expandRatio,
|
|
);
|
|
|
|
// Send an attack on the first tick
|
|
this.behavior.sendAttack(this.mg.terraNullius());
|
|
return;
|
|
}
|
|
|
|
this.behavior.handleAllianceRequests();
|
|
this.behavior.handleAllianceExtensionRequests();
|
|
this.maybeAttack();
|
|
}
|
|
|
|
private maybeAttack() {
|
|
if (this.behavior === null) {
|
|
throw new Error("not initialized");
|
|
}
|
|
const toAttack = this.behavior.getNeighborTraitorToAttack();
|
|
if (toAttack !== null) {
|
|
const odds = this.bot.isFriendly(toAttack) ? 6 : 3;
|
|
if (this.random.chance(odds)) {
|
|
// Check and break alliance before attacking if needed
|
|
const alliance = this.bot.allianceWith(toAttack);
|
|
|
|
if (alliance !== null) {
|
|
this.bot.breakAlliance(alliance);
|
|
}
|
|
|
|
this.behavior.sendAttack(toAttack);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (this.neighborsTerraNullius) {
|
|
if (this.bot.sharesBorderWith(this.mg.terraNullius())) {
|
|
this.behavior.sendAttack(this.mg.terraNullius());
|
|
return;
|
|
}
|
|
this.neighborsTerraNullius = false;
|
|
}
|
|
|
|
this.behavior.attackRandomTarget();
|
|
}
|
|
|
|
isActive(): boolean {
|
|
return this.active;
|
|
}
|
|
}
|