bugfix: Nations rarely launch nukes (#1860)

## Description:

Simplify nation enemy selection to make nations more likely to launch
nukes.

Partially fixes #1855 by addressing a v24 regression in nation behavior.

## 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
This commit is contained in:
Scott Anderson
2025-08-28 02:16:22 -04:00
committed by evanpelle
parent 8308d7f1e7
commit d83a66196a
3 changed files with 73 additions and 25 deletions
+67 -8
View File
@@ -1,5 +1,6 @@
import {
AllianceRequest,
Difficulty,
Game,
Player,
PlayerType,
@@ -71,11 +72,48 @@ export class BotBehavior {
this.game.addExecution(new EmojiExecution(this.player, player.id(), emoji));
}
private setNewEnemy(newEnemy: Player | null) {
private setNewEnemy(newEnemy: Player | null, force = false) {
if (newEnemy !== null && !force && !this.shouldAttack(newEnemy)) return;
this.enemy = newEnemy;
this.enemyUpdated = this.game.ticks();
}
private shouldAttack(other: Player): boolean {
if (this.player === null) throw new Error("not initialized");
if (this.player.isOnSameTeam(other)) {
return false;
}
if (this.player.isFriendly(other)) {
if (this.shouldDiscourageAttack(other)) {
return this.random.chance(200);
}
return this.random.chance(50);
} else {
if (this.shouldDiscourageAttack(other)) {
return this.random.chance(4);
}
return true;
}
}
private shouldDiscourageAttack(other: Player) {
if (other.isTraitor()) {
return false;
}
const { difficulty } = this.game.config().gameConfig();
if (
difficulty === Difficulty.Hard ||
difficulty === Difficulty.Impossible
) {
return false;
}
if (other.type() !== PlayerType.Human) {
return false;
}
// Only discourage attacks on Humans who are not traitors on easy or medium difficulty.
return true;
}
private clearEnemy() {
this.enemy = null;
}
@@ -87,7 +125,13 @@ export class BotBehavior {
}
}
private hasSufficientTroops(): boolean {
private hasReserveRatioTroops(): boolean {
const maxTroops = this.game.config().maxTroops(this.player);
const ratio = this.player.troops() / maxTroops;
return ratio >= this.reserveRatio;
}
private hasTriggerRatioTroops(): boolean {
const maxTroops = this.game.config().maxTroops(this.player);
const ratio = this.player.troops() / maxTroops;
return ratio >= this.triggerRatio;
@@ -104,7 +148,7 @@ export class BotBehavior {
largestAttacker = attack.attacker();
}
if (largestAttacker !== undefined) {
this.setNewEnemy(largestAttacker);
this.setNewEnemy(largestAttacker, true);
}
}
@@ -140,10 +184,13 @@ export class BotBehavior {
}
}
selectEnemy(): Player | null {
selectEnemy(enemies: Player[]): Player | null {
if (this.enemy === null) {
// Save up troops until we reach the trigger ratio
if (!this.hasSufficientTroops()) return null;
// Save up troops until we reach the reserve ratio
if (!this.hasReserveRatioTroops()) return null;
// Maybe save up troops until we reach the trigger ratio
if (!this.hasTriggerRatioTroops() && !this.random.chance(10)) return null;
// Prefer neighboring bots
const bots = this.player
@@ -171,11 +218,13 @@ export class BotBehavior {
// Retaliate against incoming attacks
if (this.enemy === null) {
// Only after clearing bots
this.checkIncomingAttacks();
}
// Select the most hated player
if (this.enemy === null) {
if (this.enemy === null && this.random.chance(2)) {
// 50% chance
const mostHated = this.player.allRelationsSorted()[0];
if (
mostHated !== undefined &&
@@ -184,6 +233,16 @@ export class BotBehavior {
this.setNewEnemy(mostHated.player);
}
}
// Select the weakest player
if (this.enemy === null && enemies.length > 0) {
this.setNewEnemy(enemies[0]);
}
// Select a random player
if (this.enemy === null && enemies.length > 0) {
this.setNewEnemy(this.random.randElement(enemies));
}
}
// Sanity check, don't attack our allies or teammates
@@ -193,7 +252,7 @@ export class BotBehavior {
selectRandomEnemy(): Player | TerraNullius | null {
if (this.enemy === null) {
// Save up troops until we reach the trigger ratio
if (!this.hasSufficientTroops()) return null;
if (!this.hasTriggerRatioTroops()) return null;
// Choose a new enemy randomly
const neighbors = this.player.neighbors();