diff --git a/src/core/configuration/Config.ts b/src/core/configuration/Config.ts index abe0f0018c..a3d280925d 100644 --- a/src/core/configuration/Config.ts +++ b/src/core/configuration/Config.ts @@ -154,6 +154,7 @@ export interface Config { defensePostRange(): number; SAMCooldown(): number; SiloCooldown(): number; + minDistanceBetweenPlayers(): number; defensePostDefenseBonus(): number; defensePostSpeedBonus(): number; falloutDefenseModifier(percentOfFallout: number): number; diff --git a/src/core/configuration/DefaultConfig.ts b/src/core/configuration/DefaultConfig.ts index 0dfc68e03a..730881165e 100644 --- a/src/core/configuration/DefaultConfig.ts +++ b/src/core/configuration/DefaultConfig.ts @@ -618,6 +618,9 @@ export class DefaultConfig implements Config { temporaryEmbargoDuration(): Tick { return 300 * 10; // 5 minutes. } + minDistanceBetweenPlayers(): number { + return 30; + } percentageTilesOwnedToWin(): number { if (this._gameConfig.gameMode === GameMode.Team) { diff --git a/src/core/execution/BotSpawner.ts b/src/core/execution/BotSpawner.ts index 134a7c6663..e1712f99e3 100644 --- a/src/core/execution/BotSpawner.ts +++ b/src/core/execution/BotSpawner.ts @@ -40,15 +40,33 @@ export class BotSpawner { if (!this.gs.isLand(tile)) { return null; } - for (const spawn of this.bots) { - if (this.gs.manhattanDist(spawn.tile, tile) < 30) { - return null; + + const isOtherPlayerSpawnedNearby = this.gs.allPlayers().some((player) => { + const spawnTile = player.spawnTile(); + + if (spawnTile === undefined) { + return false; } + + return ( + this.gs.manhattanDist(spawnTile, tile) < + this.gs.config().minDistanceBetweenPlayers() + ); + }); + + if (isOtherPlayerSpawnedNearby) { + return null; } - return new SpawnExecution( - new PlayerInfo(botName, PlayerType.Bot, null, this.random.nextID()), - tile, + + const playerInfo = new PlayerInfo( + botName, + PlayerType.Bot, + null, + this.random.nextID(), ); + this.gs.addPlayer(playerInfo).setSpawnTile(tile); + + return new SpawnExecution(playerInfo, tile); } private randomBotName(): string { diff --git a/src/core/execution/ExecutionManager.ts b/src/core/execution/ExecutionManager.ts index 4a8e5df916..e58906921b 100644 --- a/src/core/execution/ExecutionManager.ts +++ b/src/core/execution/ExecutionManager.ts @@ -128,11 +128,11 @@ export class Executor { } } - spawnBots(numBots: number): Execution[] { + spawnBots(numBots: number): SpawnExecution[] { return new BotSpawner(this.mg, this.gameID).spawnBots(numBots); } - spawnPlayers(): Execution[] { + spawnPlayers(): SpawnExecution[] { return new PlayerSpawner(this.mg, this.gameID).spawnPlayers(); } diff --git a/src/core/execution/FakeHumanExecution.ts b/src/core/execution/FakeHumanExecution.ts index 4051339fc1..d2b87d2c57 100644 --- a/src/core/execution/FakeHumanExecution.ts +++ b/src/core/execution/FakeHumanExecution.ts @@ -91,6 +91,12 @@ export class FakeHumanExecution implements Execution { if (this.random.chance(10)) { // this.isTraitor = true } + + if (!this.mg.hasPlayer(this.nation.playerInfo.id)) { + this.player = this.mg.addPlayer(this.nation.playerInfo); + } else { + this.player = this.mg.player(this.nation.playerInfo.id); + } } private updateRelationsFromEmbargos() { @@ -153,25 +159,21 @@ export class FakeHumanExecution implements Execution { return; } + if (this.player === null) { + return; + } + if (this.mg.inSpawnPhase()) { const rl = this.randomSpawnLand(); if (rl === null) { console.warn(`cannot spawn ${this.nation.playerInfo.name}`); return; } + this.player.setSpawnTile(rl); this.mg.addExecution(new SpawnExecution(this.nation.playerInfo, rl)); return; } - if (this.player === null) { - this.player = - this.mg.players().find((p) => p.id() === this.nation.playerInfo.id) ?? - null; - if (this.player === null) { - return; - } - } - if (!this.player.isAlive()) { this.active = false; return; @@ -643,6 +645,24 @@ export class FakeHumanExecution implements Execution { continue; } const tile = this.mg.ref(x, y); + + const isOtherPlayerSpawnedNearby = this.mg.allPlayers().some((player) => { + const spawnTile = player.spawnTile(); + + if (spawnTile === undefined) { + return false; + } + + return ( + this.mg.manhattanDist(spawnTile, tile) < + this.mg.config().minDistanceBetweenPlayers() + ); + }); + + if (isOtherPlayerSpawnedNearby) { + continue; + } + if (this.mg.isLand(tile) && !this.mg.hasOwner(tile)) { if ( this.mg.terrainType(tile) === TerrainType.Mountain && diff --git a/src/core/execution/SpawnExecution.ts b/src/core/execution/SpawnExecution.ts index 57baff6eeb..149edc02e1 100644 --- a/src/core/execution/SpawnExecution.ts +++ b/src/core/execution/SpawnExecution.ts @@ -48,7 +48,10 @@ export class SpawnExecution implements Execution { this.mg.addExecution(new BotExecution(player)); } } - player.setHasSpawned(true); + + if (player.spawnTile() === undefined) { + player.setSpawnTile(this.tile); + } } isActive(): boolean { diff --git a/src/core/execution/utils/PlayerSpawner.ts b/src/core/execution/utils/PlayerSpawner.ts index 29c0fe1a6e..f6bbf3a79e 100644 --- a/src/core/execution/utils/PlayerSpawner.ts +++ b/src/core/execution/utils/PlayerSpawner.ts @@ -9,7 +9,6 @@ export class PlayerSpawner { private random: PseudoRandom; private players: SpawnExecution[] = []; private static readonly MAX_SPAWN_TRIES = 10_000; - private static readonly MIN_SPAWN_DISTANCE = 30; constructor( private gm: Game, @@ -41,18 +40,13 @@ export class PlayerSpawner { continue; } - let tooCloseToOtherPlayer = false; - for (const spawn of this.players) { - if ( + const isOtherPlayerSpawnedNearby = this.players.some( + (spawn) => this.gm.manhattanDist(spawn.tile, tile) < - PlayerSpawner.MIN_SPAWN_DISTANCE - ) { - tooCloseToOtherPlayer = true; - break; - } - } + this.gm.config().minDistanceBetweenPlayers(), + ); - if (tooCloseToOtherPlayer) { + if (isOtherPlayerSpawnedNearby) { continue; } @@ -75,6 +69,7 @@ export class PlayerSpawner { continue; } + player.setSpawnTile(spawnLand); this.players.push(new SpawnExecution(player.info(), spawnLand)); } diff --git a/src/core/game/Game.ts b/src/core/game/Game.ts index 2cf908b962..8b3ae15546 100644 --- a/src/core/game/Game.ts +++ b/src/core/game/Game.ts @@ -545,7 +545,8 @@ export interface Player { markDisconnected(isDisconnected: boolean): void; hasSpawned(): boolean; - setHasSpawned(hasSpawned: boolean): void; + setSpawnTile(spawnTile: TileRef): void; + spawnTile(): TileRef | undefined; // Territory tiles(): ReadonlySet; diff --git a/src/core/game/PlayerImpl.ts b/src/core/game/PlayerImpl.ts index 2dbe79784e..0787f25c44 100644 --- a/src/core/game/PlayerImpl.ts +++ b/src/core/game/PlayerImpl.ts @@ -101,7 +101,7 @@ export class PlayerImpl implements Player { public _outgoingAttacks: Attack[] = []; public _outgoingLandAttacks: Attack[] = []; - private _hasSpawned = false; + private _spawnTile: TileRef | undefined; private _isDisconnected = false; constructor( @@ -344,11 +344,15 @@ export class PlayerImpl implements Player { } hasSpawned(): boolean { - return this._hasSpawned; + return this._spawnTile !== undefined; } - setHasSpawned(hasSpawned: boolean): void { - this._hasSpawned = hasSpawned; + setSpawnTile(spawnTile: TileRef): void { + this._spawnTile = spawnTile; + } + + spawnTile(): TileRef | undefined { + return this._spawnTile; } incomingAllianceRequests(): AllianceRequest[] {