Improve random spawn (#2465)

## Description:

After the v27 playtest, some players experienced instant death on spawn.
The issue was that the human random spawn occasionally coincided with a
bot’s spawn. Previously, bot spawns didn’t account for human spawn
locations and could appear on the same tile, now they don’t.

## Please complete the following:

- [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:

nikolaj_mykola

---------

Co-authored-by: Evan <evanpelle@gmail.com>
This commit is contained in:
Mykola
2025-11-19 01:07:25 +02:00
committed by GitHub
parent 90b73451a8
commit 2b2200c808
9 changed files with 71 additions and 19 deletions
+1
View File
@@ -153,6 +153,7 @@ export interface Config {
defensePostRange(): number;
SAMCooldown(): number;
SiloCooldown(): number;
minDistanceBetweenPlayers(): number;
defensePostDefenseBonus(): number;
defensePostSpeedBonus(): number;
falloutDefenseModifier(percentOfFallout: number): number;
+3
View File
@@ -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) {
+24 -6
View File
@@ -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 {
+2 -2
View File
@@ -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();
}
+19
View File
@@ -159,6 +159,7 @@ export class FakeHumanExecution implements Execution {
console.warn(`cannot spawn ${this.nation.playerInfo.name}`);
return;
}
this.mg.addPlayer(this.nation.playerInfo).setSpawnTile(rl);
this.mg.addExecution(new SpawnExecution(this.nation.playerInfo, rl));
return;
}
@@ -643,6 +644,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 &&
+5
View File
@@ -48,6 +48,11 @@ export class SpawnExecution implements Execution {
this.mg.addExecution(new BotExecution(player));
}
}
if (player.spawnTile() === undefined) {
player.setSpawnTile(this.tile);
}
player.setHasSpawned(true);
}
+6 -11
View File
@@ -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));
}
+2
View File
@@ -547,6 +547,8 @@ export interface Player {
hasSpawned(): boolean;
setHasSpawned(hasSpawned: boolean): void;
setSpawnTile(spawnTile: TileRef): void;
spawnTile(): TileRef | undefined;
// Territory
tiles(): ReadonlySet<TileRef>;
+9
View File
@@ -102,6 +102,7 @@ export class PlayerImpl implements Player {
public _outgoingLandAttacks: Attack[] = [];
private _hasSpawned = false;
private _spawnTile: TileRef | undefined;
private _isDisconnected = false;
constructor(
@@ -347,6 +348,14 @@ export class PlayerImpl implements Player {
this._hasSpawned = hasSpawned;
}
setSpawnTile(spawnTile: TileRef): void {
this._spawnTile = spawnTile;
}
spawnTile(): TileRef | undefined {
return this._spawnTile;
}
incomingAllianceRequests(): AllianceRequest[] {
return this.mg.allianceRequests.filter((ar) => ar.recipient() === this);
}