mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-28 07:24:51 +00:00
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:
@@ -4,7 +4,6 @@ import { Executor } from "./execution/ExecutionManager";
|
||||
import { WinCheckExecution } from "./execution/WinCheckExecution";
|
||||
import {
|
||||
AllPlayers,
|
||||
BuildableUnit,
|
||||
Game,
|
||||
GameUpdates,
|
||||
NameViewData,
|
||||
@@ -15,7 +14,6 @@ import {
|
||||
PlayerInfo,
|
||||
PlayerProfile,
|
||||
PlayerType,
|
||||
UnitType,
|
||||
} from "./game/Game";
|
||||
import { createGame } from "./game/GameImpl";
|
||||
import {
|
||||
@@ -161,13 +159,7 @@ export class GameRunner {
|
||||
const actions = {
|
||||
canBoat: player.canBoat(tile),
|
||||
canAttack: player.canAttack(tile),
|
||||
buildableUnits: Object.values(UnitType).map((u) => {
|
||||
return {
|
||||
type: u,
|
||||
canBuild: player.canBuild(u, tile) != false,
|
||||
cost: this.game.config().unitInfo(u).cost(player),
|
||||
} as BuildableUnit;
|
||||
}),
|
||||
buildableUnits: player.buildableUnits(tile),
|
||||
canSendEmojiAllPlayers: player.canSendEmoji(AllPlayers),
|
||||
} as PlayerActions;
|
||||
|
||||
|
||||
@@ -123,6 +123,7 @@ export interface Config {
|
||||
nukeMagnitudes(unitType: UnitType): NukeMagnitude;
|
||||
defaultNukeSpeed(): number;
|
||||
nukeDeathFactor(humans: number, tilesOwned: number): number;
|
||||
structureMinDist(): number;
|
||||
}
|
||||
|
||||
export interface Theme {
|
||||
|
||||
@@ -676,4 +676,8 @@ export class DefaultConfig implements Config {
|
||||
nukeDeathFactor(humans: number, tilesOwned: number): number {
|
||||
return (5 * humans) / Math.max(1, tilesOwned);
|
||||
}
|
||||
|
||||
structureMinDist(): number {
|
||||
return 18;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,14 +33,15 @@ export class MissileSiloExecution implements Execution {
|
||||
|
||||
tick(ticks: number): void {
|
||||
if (this.silo == null) {
|
||||
if (!this.player.canBuild(UnitType.MissileSilo, this.tile)) {
|
||||
const spawn = this.player.canBuild(UnitType.MissileSilo, this.tile);
|
||||
if (spawn === false) {
|
||||
consolex.warn(
|
||||
`player ${this.player} cannot build missile silo at ${this.tile}`,
|
||||
);
|
||||
this.active = false;
|
||||
return;
|
||||
}
|
||||
this.silo = this.player.buildUnit(UnitType.MissileSilo, 0, this.tile, {
|
||||
this.silo = this.player.buildUnit(UnitType.MissileSilo, 0, spawn, {
|
||||
cooldownDuration: this.mg.config().SiloCooldown(),
|
||||
});
|
||||
|
||||
|
||||
@@ -347,6 +347,7 @@ export interface Player {
|
||||
// Units
|
||||
units(...types: UnitType[]): Unit[];
|
||||
unitsIncludingConstruction(type: UnitType): Unit[];
|
||||
buildableUnits(tile: TileRef): BuildableUnit[];
|
||||
canBuild(type: UnitType, targetTile: TileRef): TileRef | false;
|
||||
buildUnit(
|
||||
type: UnitType,
|
||||
|
||||
+74
-10
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user