From 5955f89fe8e73e3a82ccdd2f6c3d8b601553c1e7 Mon Sep 17 00:00:00 2001 From: FloPinguin <25036848+FloPinguin@users.noreply.github.com> Date: Sat, 10 Jan 2026 05:40:15 +0100 Subject: [PATCH] =?UTF-8?q?Nation=20build=20order=20improvements=20?= =?UTF-8?q?=F0=9F=A4=96=20(#2833)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Description: My first PR about the nation build order. This one is a bit important for HumansVsNations. - Nations build more SAMs in team games - Nations build less factories if they have access to the ocean (instead of focusing ports and factories with the same priority) - Nations no longer place defense posts "without reason" - only when they share a border with someone they haven't allied I'm planning to make the build order a bit different based on difficulty. ## 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 --- src/core/execution/NationExecution.ts | 20 ++++++++++++++++-- .../nation/structureSpawnTileValue.ts | 21 +++++++++++++++++-- 2 files changed, 37 insertions(+), 4 deletions(-) diff --git a/src/core/execution/NationExecution.ts b/src/core/execution/NationExecution.ts index 4e9754c88..854d07b03 100644 --- a/src/core/execution/NationExecution.ts +++ b/src/core/execution/NationExecution.ts @@ -2,6 +2,7 @@ import { Difficulty, Execution, Game, + GameMode, Gold, Nation, Player, @@ -250,17 +251,31 @@ export class NationExecution implements Execution { private handleUnits() { if (this.warshipBehavior === null) throw new Error("not initialized"); + const hasCoastalTiles = this.hasCoastalTiles(); + const isTeamGame = this.mg.config().gameConfig().gameMode === GameMode.Team; return ( this.maybeSpawnStructure(UnitType.City, (num) => num) || this.maybeSpawnStructure(UnitType.Port, (num) => num) || this.warshipBehavior.maybeSpawnWarship() || - this.maybeSpawnStructure(UnitType.Factory, (num) => num) || + this.maybeSpawnStructure(UnitType.Factory, (num) => + hasCoastalTiles ? num * 3 : num, + ) || this.maybeSpawnStructure(UnitType.DefensePost, (num) => (num + 2) ** 2) || - this.maybeSpawnStructure(UnitType.SAMLauncher, (num) => num ** 2) || + this.maybeSpawnStructure(UnitType.SAMLauncher, (num) => + isTeamGame ? num : num ** 2, + ) || this.maybeSpawnStructure(UnitType.MissileSilo, (num) => num ** 2) ); } + private hasCoastalTiles(): boolean { + if (this.player === null) throw new Error("not initialized"); + for (const tile of this.player.borderTiles()) { + if (this.mg.isOceanShore(tile)) return true; + } + return false; + } + private maybeSpawnStructure( type: UnitType, multiplier: (num: number) => number, @@ -294,6 +309,7 @@ export class NationExecution implements Execution { : randTerritoryTileArray(this.random, this.mg, this.player, 25); if (tiles.length === 0) return null; const valueFunction = structureSpawnTileValue(this.mg, this.player, type); + if (valueFunction === null) return null; let bestTile: TileRef | null = null; let bestValue = 0; for (const t of tiles) { diff --git a/src/core/execution/nation/structureSpawnTileValue.ts b/src/core/execution/nation/structureSpawnTileValue.ts index b01df682a..a882ca664 100644 --- a/src/core/execution/nation/structureSpawnTileValue.ts +++ b/src/core/execution/nation/structureSpawnTileValue.ts @@ -1,4 +1,4 @@ -import { Game, Player, Relation, UnitType } from "../../game/Game"; +import { Game, Player, PlayerType, Relation, UnitType } from "../../game/Game"; import { TileRef } from "../../game/GameMap"; import { closestTile, closestTwoTiles } from "../Util"; @@ -6,7 +6,7 @@ export function structureSpawnTileValue( mg: Game, player: Player, type: UnitType, -): (tile: TileRef) => number { +): ((tile: TileRef) => number) | null { const borderTiles = player.borderTiles(); const otherUnits = player.units(type); // Prefer spacing structures out of atom bomb range @@ -57,6 +57,22 @@ export function structureSpawnTileValue( }; } case UnitType.DefensePost: { + // Check if we have any non-friendly non-bot neighbors + const hasHostileNeighbor = + player + .neighbors() + .filter( + (n): n is Player => + n.isPlayer() && + player.isFriendly(n) === false && + n.type() !== PlayerType.Bot, + ).length > 0; + + // Don't build defense posts if there is no danger + if (!hasHostileNeighbor) { + return null; + } + return (tile) => { let w = 0; @@ -79,6 +95,7 @@ export function structureSpawnTileValue( if (id === player.smallID()) continue; const neighbor = mg.playerBySmallID(id); if (!neighbor.isPlayer()) continue; + if (neighbor.type() === PlayerType.Bot) continue; neighbors.add(neighbor); } for (const neighbor of neighbors) {