mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-07-23 21:26:39 +00:00
Massive nation improvement 🤖 (#3761)
## Description: - Hard / Impossible nations in team games auto-stop trading with all enemies - If there are a LOT of nations on the map (Enzo stream with 400 nation HvN private games) they no longer start with a city, they start with eco (port / factory) because they cannot gain much gold from bot-killing - Impossible nations built way too many missile silos sometimes, caused by the SAM overwhelming logic. Fixed now. - In public HvN games with 5M starting gold, nations placed their structures way too fast, which slowed down their expansion. And humans could easily cause a lot of damage with one atom bomb. Now their first structure is a SAM (on hard / impossible) and they wait between their earlygame structure placements. - Nations now spread out their port placements more evenly - Nations are now able to attack much stronger enemies in team games (They can expect donations) - Improve performance a bit by adding more early-returns (Dont run any nuking logic if nukes are disabled, no alliance logic if alliances are disabled, no boating logic if transport boats are disabled, ...) - Fix some of the "cannot send troops" messages in the console (DonateTroopExecution) - Nations build their first missile silo sooner, they should also build more SAMs - Nations spend their gold better after reaching the save-up-target (previously they stopped nuking) - Optimized save-up-targets for team games - The richest impossible nation is nuking very dense players now (lot of structure levels on a small island) ### How does a 5M gold HvN start look like now? https://github.com/user-attachments/assets/e9da89c3-c0d4-4144-a741-3101746b16da ## 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
This commit is contained in:
@@ -101,6 +101,10 @@ export class AiAttackBehavior {
|
||||
private attackWithRandomBoat(borderingEnemies: Player[] = []) {
|
||||
if (this.player === null) throw new Error("not initialized");
|
||||
|
||||
if (this.game.config().isUnitDisabled(UnitType.TransportShip)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if we've already sent out the maximum number of transport ships
|
||||
if (
|
||||
this.player.unitCount(UnitType.TransportShip) >=
|
||||
@@ -166,8 +170,12 @@ export class AiAttackBehavior {
|
||||
if (owner.isPlayer() && borderingEnemies.includes(owner)) {
|
||||
continue;
|
||||
}
|
||||
// Don't spam boats into players which are stronger than us
|
||||
if (owner.isPlayer() && owner.troops() > this.player.troops()) {
|
||||
// Don't spam boats into players which are stronger than us (FFA only)
|
||||
if (
|
||||
this.isFFA() &&
|
||||
owner.isPlayer() &&
|
||||
owner.troops() > this.player.troops()
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -258,7 +266,8 @@ export class AiAttackBehavior {
|
||||
// borderingEnemies is already sorted by troops (ascending), so first match is weakest afk enemy
|
||||
const afk = borderingEnemies.find(
|
||||
(enemy) =>
|
||||
enemy.isDisconnected() && enemy.troops() < this.player.troops() * 3,
|
||||
enemy.isDisconnected() &&
|
||||
(!this.isFFA() || enemy.troops() < this.player.troops() * 3),
|
||||
);
|
||||
if (afk) {
|
||||
this.sendAttack(afk);
|
||||
@@ -292,7 +301,7 @@ export class AiAttackBehavior {
|
||||
if (relation.relation !== Relation.Hostile) continue;
|
||||
const other = relation.player;
|
||||
if (this.player.isFriendly(other)) continue;
|
||||
if (other.troops() > this.player.troops() * 3) continue;
|
||||
if (this.isFFA() && other.troops() > this.player.troops() * 3) continue;
|
||||
this.sendAttack(other);
|
||||
return true;
|
||||
}
|
||||
@@ -312,8 +321,8 @@ export class AiAttackBehavior {
|
||||
if (borderingEnemies.length > 0) {
|
||||
// borderingEnemies is already sorted by troops (ascending), so first match is weakest
|
||||
const weakest = borderingEnemies[0];
|
||||
// Don't attack if they have more troops than us
|
||||
if (weakest.troops() < this.player.troops()) {
|
||||
// In FFA, don't attack if they have more troops than us
|
||||
if (!this.isFFA() || weakest.troops() < this.player.troops()) {
|
||||
this.sendAttack(weakest);
|
||||
return true;
|
||||
}
|
||||
@@ -463,6 +472,8 @@ export class AiAttackBehavior {
|
||||
private assistAllies(): boolean {
|
||||
if (this.emojiBehavior === undefined) throw new Error("not initialized");
|
||||
|
||||
if (this.game.config().disableAlliances()) return false;
|
||||
|
||||
for (const ally of this.player.allies()) {
|
||||
if (ally.targets().length === 0) continue;
|
||||
if (this.player.relation(ally) < Relation.Friendly) {
|
||||
@@ -490,11 +501,14 @@ export class AiAttackBehavior {
|
||||
|
||||
// Find a traitor who isn't significantly stronger than us
|
||||
private findTraitor(borderingEnemies: Player[]): Player | null {
|
||||
if (this.game.config().disableAlliances()) return null;
|
||||
|
||||
// borderingEnemies is already sorted by troops (ascending), so first match is weakest traitor
|
||||
return (
|
||||
borderingEnemies.find(
|
||||
(enemy) =>
|
||||
enemy.isTraitor() && enemy.troops() < this.player.troops() * 1.2,
|
||||
enemy.isTraitor() &&
|
||||
(!this.isFFA() || enemy.troops() < this.player.troops() * 1.2),
|
||||
) ?? null
|
||||
);
|
||||
}
|
||||
@@ -505,6 +519,8 @@ export class AiAttackBehavior {
|
||||
): boolean {
|
||||
if (this.allianceBehavior === undefined) throw new Error("not initialized");
|
||||
|
||||
if (this.game.config().disableAlliances()) return false;
|
||||
|
||||
if (borderingFriends.length > 0) {
|
||||
for (const friend of borderingFriends) {
|
||||
if (
|
||||
@@ -522,6 +538,10 @@ export class AiAttackBehavior {
|
||||
}
|
||||
|
||||
private isBorderingNukedTerritory(): boolean {
|
||||
if (this.game.config().isUnitDisabled(UnitType.MissileSilo)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const tile of this.player.borderTiles()) {
|
||||
for (const neighbor of this.game.neighbors(tile)) {
|
||||
if (
|
||||
@@ -541,7 +561,9 @@ export class AiAttackBehavior {
|
||||
// borderingEnemies is already sorted by troops (ascending), so first match is weakest victim
|
||||
return (
|
||||
borderingEnemies.find((enemy) => {
|
||||
if (enemy.troops() > this.player.troops() * 1.2) return false;
|
||||
if (this.isFFA() && enemy.troops() > this.player.troops() * 1.2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const totalIncomingTroops = enemy
|
||||
.incomingAttacks()
|
||||
@@ -559,7 +581,7 @@ export class AiAttackBehavior {
|
||||
const enemyMaxTroops = this.game.config().maxTroops(enemy);
|
||||
return (
|
||||
enemy.troops() < enemyMaxTroops * 0.15 &&
|
||||
enemy.troops() < this.player.troops() * 1.2
|
||||
(!this.isFFA() || enemy.troops() < this.player.troops() * 1.2)
|
||||
);
|
||||
});
|
||||
|
||||
@@ -568,6 +590,10 @@ export class AiAttackBehavior {
|
||||
}
|
||||
|
||||
private findNearestIslandEnemy(): Player | null {
|
||||
if (this.game.config().isUnitDisabled(UnitType.TransportShip)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Check if we've already sent out the maximum number of transport ships
|
||||
if (
|
||||
this.player.unitCount(UnitType.TransportShip) >=
|
||||
@@ -585,8 +611,8 @@ export class AiAttackBehavior {
|
||||
const filteredPlayers = this.game.players().filter((p) => {
|
||||
if (p === this.player) return false;
|
||||
if (this.player.isFriendly(p)) return false;
|
||||
// Don't spam boats into players with more troops
|
||||
return p.troops() < this.player.troops();
|
||||
// In FFA, don't spam boats into players with more troops
|
||||
return !this.isFFA() || p.troops() < this.player.troops();
|
||||
});
|
||||
|
||||
if (filteredPlayers.length === 0) return null;
|
||||
@@ -642,6 +668,13 @@ export class AiAttackBehavior {
|
||||
return reachablePlayers[0];
|
||||
}
|
||||
|
||||
// In team games, nations should be willing to attack/boat into stronger
|
||||
// enemies - they can rely on teammates to donate. In FFA, going after
|
||||
// someone significantly stronger is usually a losing proposition.
|
||||
private isFFA(): boolean {
|
||||
return this.game.config().gameConfig().gameMode === GameMode.FFA;
|
||||
}
|
||||
|
||||
private getPlayerCenter(player: Player) {
|
||||
if (player.largestClusterBoundingBox) {
|
||||
return boundingBoxCenter(player.largestClusterBoundingBox);
|
||||
@@ -688,6 +721,8 @@ export class AiAttackBehavior {
|
||||
}
|
||||
|
||||
getNeighborTraitorToAttack(): Player | null {
|
||||
if (this.game.config().disableAlliances()) return null;
|
||||
|
||||
const traitors = this.player
|
||||
.neighbors()
|
||||
.filter(
|
||||
@@ -786,6 +821,10 @@ export class AiAttackBehavior {
|
||||
}
|
||||
|
||||
private sendBoatAttack(target: Player) {
|
||||
if (this.game.config().isUnitDisabled(UnitType.TransportShip)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const closest = closestTwoTiles(
|
||||
this.game,
|
||||
Array.from(this.player.borderTiles()).filter((t) => this.game.isShore(t)),
|
||||
|
||||
Reference in New Issue
Block a user