Nations build SAM launchers (#1931)

## Description:

Fixes #201 by adding the ability for nations to build SAM launchers.

<img width="888" height="625" alt="image"
src="https://github.com/user-attachments/assets/b07f1b4e-d022-4674-b842-bc8b4247825d"
/>

## 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-25 18:07:57 -04:00
committed by GitHub
parent e079bc772f
commit e4a4c5dc12
3 changed files with 66 additions and 17 deletions
+1
View File
@@ -437,6 +437,7 @@ export class FakeHumanExecution implements Execution {
this.maybeSpawnStructure(UnitType.Port) ||
this.maybeSpawnWarship() ||
this.maybeSpawnStructure(UnitType.Factory) ||
this.maybeSpawnStructure(UnitType.SAMLauncher) ||
this.maybeSpawnStructure(UnitType.MissileSilo)
);
}
+2 -1
View File
@@ -50,7 +50,8 @@ class SAMTargetingSystem {
private isInRange(tile: TileRef) {
const samTile = this.sam.tile();
const rangeSquared = this.mg.config().defaultSamRange() ** 2;
const range = this.mg.config().defaultSamRange();
const rangeSquared = range * range;
return this.mg.euclideanDistSquared(samTile, tile) <= rangeSquared;
}
@@ -13,24 +13,9 @@ export function structureSpawnTileValue(
const borderSpacing = mg.config().nukeMagnitudes(UnitType.AtomBomb).outer;
const structureSpacing = borderSpacing * 2;
switch (type) {
case UnitType.Port:
return (tile) => {
let w = 0;
// Prefer to be far away from other structures of the same type
const otherTiles: Set<TileRef> = new Set(otherUnits.map((u) => u.tile()));
otherTiles.delete(tile);
const closestOther = closestTwoTiles(mg, otherTiles, [tile]);
if (closestOther !== null) {
const d = mg.manhattanDist(closestOther.x, tile);
w += Math.min(d, structureSpacing);
}
return w;
};
case UnitType.City:
case UnitType.Factory:
case UnitType.MissileSilo:
case UnitType.MissileSilo: {
return (tile) => {
let w = 0;
@@ -56,6 +41,68 @@ export function structureSpawnTileValue(
// TODO: Cities and factories should consider train range limits
return w;
};
}
case UnitType.Port: {
return (tile) => {
let w = 0;
// Prefer to be far away from other structures of the same type
const otherTiles: Set<TileRef> = new Set(otherUnits.map((u) => u.tile()));
otherTiles.delete(tile);
const closestOther = closestTwoTiles(mg, otherTiles, [tile]);
if (closestOther !== null) {
const d = mg.manhattanDist(closestOther.x, tile);
w += Math.min(d, structureSpacing);
}
return w;
};
}
case UnitType.SAMLauncher: {
const protectTiles: Set<TileRef> = new Set();
for (const unit of player.units()) {
switch(unit.type()) {
case UnitType.City:
case UnitType.Factory:
case UnitType.MissileSilo:
case UnitType.Port:
protectTiles.add(unit.tile());
}
}
const range = mg.config().defaultSamRange();
const rangeSquared = range * range;
return (tile) => {
let w = 0;
// Prefer higher elevations
w += mg.magnitude(tile);
// Prefer to be away from the border
const closestBorder = closestTwoTiles(mg, borderTiles, [tile]);
if (closestBorder !== null) {
const d = mg.manhattanDist(closestBorder.x, tile);
w += Math.min(d, borderSpacing);
}
// Prefer to be far away from other structures of the same type
const otherTiles: Set<TileRef> = new Set(otherUnits.map((u) => u.tile()));
otherTiles.delete(tile);
const closestOther = closestTwoTiles(mg, otherTiles, [tile]);
if (closestOther !== null) {
const d = mg.manhattanDist(closestOther.x, tile);
w += Math.min(d, structureSpacing);
}
// Prefer to be in range of other structures
for (const maybeProtected of protectTiles) {
const distanceSquared = mg.euclideanDistSquared(tile, maybeProtected);
if (distanceSquared > rangeSquared) continue;
w += structureSpacing;
}
return w;
};
}
default:
throw new Error(`Value function not implemented for ${type}`);
}