don't allow structures to spawn too close to each other. When choosing a spawn, canBuild() finds a suitable nearby tile if chosen tile is too close to an existing structure.

This commit is contained in:
Evan
2025-04-18 11:51:54 -07:00
parent ae4f4d3ed6
commit 38b1845ed1
6 changed files with 84 additions and 21 deletions
+74 -10
View File
@@ -20,6 +20,7 @@ import {
AllianceRequest,
AllPlayers,
Attack,
BuildableUnit,
Cell,
EmojiMessage,
GameMode,
@@ -729,7 +730,22 @@ export class PlayerImpl implements Player {
return b;
}
canBuild(unitType: UnitType, targetTile: TileRef): TileRef | false {
public buildableUnits(tile: TileRef): BuildableUnit[] {
const validTiles = this.validStructureSpawnTiles(tile);
return Object.values(UnitType).map((u) => {
return {
type: u,
canBuild: this.canBuild(u, tile, validTiles) != false,
cost: this.mg.config().unitInfo(u).cost(this),
} as BuildableUnit;
});
}
canBuild(
unitType: UnitType,
targetTile: TileRef,
validTiles: TileRef[] | null = null,
): TileRef | false {
// prevent the building of nukes and nuke related buildings
if (this.mg.config().disableNukes()) {
if (
@@ -761,7 +777,7 @@ export class PlayerImpl implements Player {
case UnitType.MIRVWarhead:
return targetTile;
case UnitType.Port:
return this.portSpawn(targetTile);
return this.portSpawn(targetTile, validTiles);
case UnitType.Warship:
return this.warshipSpawn(targetTile);
case UnitType.Shell:
@@ -776,7 +792,7 @@ export class PlayerImpl implements Player {
case UnitType.SAMLauncher:
case UnitType.City:
case UnitType.Construction:
return this.landBasedStructureSpawn(targetTile);
return this.landBasedStructureSpawn(targetTile, validTiles);
default:
assertNever(unitType);
}
@@ -802,7 +818,7 @@ export class PlayerImpl implements Player {
return spawns[0].tile();
}
portSpawn(tile: TileRef): TileRef | false {
portSpawn(tile: TileRef, validTiles: TileRef[]): TileRef | false {
const spawns = Array.from(
this.mg.bfs(
tile,
@@ -814,10 +830,15 @@ export class PlayerImpl implements Player {
(a, b) =>
this.mg.manhattanDist(a, tile) - this.mg.manhattanDist(b, tile),
);
if (spawns.length == 0) {
return false;
const validTileSet = new Set(
validTiles ?? this.validStructureSpawnTiles(tile),
);
for (const t of spawns) {
if (validTileSet.has(t)) {
return t;
}
}
return spawns[0];
return false;
}
warshipSpawn(tile: TileRef): TileRef | false {
@@ -835,11 +856,54 @@ export class PlayerImpl implements Player {
return spawns[0].tile();
}
landBasedStructureSpawn(tile: TileRef): TileRef | false {
if (this.mg.owner(tile) != this) {
landBasedStructureSpawn(
tile: TileRef,
validTiles: TileRef[] | null = null,
): TileRef | false {
const tiles = validTiles ?? this.validStructureSpawnTiles(tile);
if (tiles.length == 0) {
return false;
}
return tile;
return tiles[0];
}
private validStructureSpawnTiles(tile: TileRef): TileRef[] {
if (this.mg.owner(tile) != this) {
return [];
}
const searchRadius = 15;
const searchRadiusSquared = searchRadius ** 2;
const types = Object.values(UnitType).filter((unitTypeValue) => {
return this.mg.config().unitInfo(unitTypeValue).territoryBound;
});
const nearbyUnits = this.mg
.nearbyUnits(tile, searchRadius * 2, types)
.map((u) => u.unit);
const nearbyTiles = this.mg.bfs(tile, (gm, t) => {
return (
this.mg.euclideanDistSquared(tile, t) < searchRadiusSquared &&
gm.ownerID(t) == this.smallID()
);
});
const validSet: Set<TileRef> = new Set(nearbyTiles);
const minDistSquared = this.mg.config().structureMinDist() ** 2;
for (const t of nearbyTiles) {
for (const unit of nearbyUnits) {
if (this.mg.euclideanDistSquared(unit.tile(), t) < minDistSquared) {
validSet.delete(t);
break;
}
}
}
const valid = Array.from(validSet);
valid.sort(
(a, b) =>
this.mg.euclideanDistSquared(a, tile) -
this.mg.euclideanDistSquared(b, tile),
);
return valid;
}
transportShipSpawn(targetTile: TileRef): TileRef | false {