mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-21 09:20:47 +00:00
339ace0bd6
## Description: Preparation for nuke wars, for v30. Next PR will be adding the nuke wars modifier for public games, but Wonders https://github.com/openfrontio/OpenFrontIO/pull/3224 needs to be merged first to avoid merge conflicts. ### 1. Disable boats setting It's possible to disable `UnitType.TransportShip` now. Because they are not needed in nuke wars and can even be annoying. <img width="720" height="320" alt="image" src="https://github.com/user-attachments/assets/661bc10d-b204-4b4f-b876-ee7c9b92de8c" /> ### 2. Team spawn zones for random spawn Maps can have `teamGameSpawnAreas` in their json file now. Spawn areas are currently active if - a supported map is chosen (Baikal Nuke Wars or Four Islands) - a supported team size is chosen (2 teams on Baikal Nuke Wars or 2/4 teams on Four Islands) - random spawn is enabled ## 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 ## Please put your Discord username so you can be contacted if a bug or regression is found: FloPinguin
168 lines
4.0 KiB
TypeScript
168 lines
4.0 KiB
TypeScript
import {
|
|
Execution,
|
|
Game,
|
|
Player,
|
|
PlayerInfo,
|
|
PlayerType,
|
|
SpawnArea,
|
|
} from "../game/Game";
|
|
import { TileRef } from "../game/GameMap";
|
|
import { PseudoRandom } from "../PseudoRandom";
|
|
import { GameID } from "../Schemas";
|
|
import { simpleHash } from "../Util";
|
|
import { BotExecution } from "./BotExecution";
|
|
import { PlayerExecution } from "./PlayerExecution";
|
|
import { getSpawnTiles } from "./Util";
|
|
|
|
type Spawn = { center: TileRef; tiles: TileRef[] };
|
|
|
|
export class SpawnExecution implements Execution {
|
|
private random: PseudoRandom;
|
|
active: boolean = true;
|
|
private mg: Game;
|
|
private static readonly MAX_SPAWN_TRIES = 1_000;
|
|
|
|
constructor(
|
|
gameID: GameID,
|
|
private playerInfo: PlayerInfo,
|
|
public tile?: TileRef,
|
|
) {
|
|
this.random = new PseudoRandom(
|
|
simpleHash(playerInfo.id) + simpleHash(gameID),
|
|
);
|
|
}
|
|
|
|
init(mg: Game, ticks: number) {
|
|
this.mg = mg;
|
|
}
|
|
|
|
tick(ticks: number) {
|
|
this.active = false;
|
|
|
|
if (!this.mg.inSpawnPhase()) {
|
|
this.active = false;
|
|
return;
|
|
}
|
|
|
|
let player: Player | null = null;
|
|
if (this.mg.hasPlayer(this.playerInfo.id)) {
|
|
player = this.mg.player(this.playerInfo.id);
|
|
} else {
|
|
player = this.mg.addPlayer(this.playerInfo);
|
|
}
|
|
|
|
// Security: If random spawn is enabled, prevent players from re-rolling their spawn location
|
|
if (this.mg.config().isRandomSpawn() && player.hasSpawned()) {
|
|
return;
|
|
}
|
|
|
|
player.tiles().forEach((t) => player.relinquish(t));
|
|
const spawn = this.getSpawn(this.tile);
|
|
|
|
if (!spawn) {
|
|
console.warn(`SpawnExecution: cannot spawn ${this.playerInfo.name}`);
|
|
return;
|
|
}
|
|
|
|
spawn.tiles.forEach((t) => {
|
|
player.conquer(t);
|
|
});
|
|
|
|
if (!player.hasSpawned()) {
|
|
this.mg.addExecution(new PlayerExecution(player));
|
|
if (player.type() === PlayerType.Bot) {
|
|
this.mg.addExecution(new BotExecution(player));
|
|
}
|
|
}
|
|
|
|
player.setSpawnTile(spawn.center);
|
|
}
|
|
|
|
isActive(): boolean {
|
|
return this.active;
|
|
}
|
|
|
|
activeDuringSpawnPhase(): boolean {
|
|
return true;
|
|
}
|
|
|
|
private getSpawn(center?: TileRef): Spawn | undefined {
|
|
if (center !== undefined) {
|
|
const tiles = getSpawnTiles(this.mg, center, false);
|
|
|
|
if (!tiles.length) {
|
|
return;
|
|
}
|
|
|
|
return { center, tiles };
|
|
}
|
|
|
|
const spawnArea = this.getTeamSpawnArea();
|
|
let tries = 0;
|
|
|
|
while (tries < SpawnExecution.MAX_SPAWN_TRIES) {
|
|
tries++;
|
|
|
|
const center = this.randTile(spawnArea);
|
|
|
|
if (
|
|
!this.mg.isLand(center) ||
|
|
this.mg.hasOwner(center) ||
|
|
this.mg.isBorder(center)
|
|
) {
|
|
continue;
|
|
}
|
|
|
|
const isOtherPlayerSpawnedNearby = this.mg
|
|
.allPlayers()
|
|
.filter((player) => player.id() !== this.playerInfo.id)
|
|
.some((player) => {
|
|
const spawnTile = player.spawnTile();
|
|
|
|
if (spawnTile === undefined) {
|
|
return false;
|
|
}
|
|
|
|
return (
|
|
this.mg.manhattanDist(spawnTile, center) <
|
|
this.mg.config().minDistanceBetweenPlayers()
|
|
);
|
|
});
|
|
|
|
if (isOtherPlayerSpawnedNearby) {
|
|
continue;
|
|
}
|
|
|
|
const tiles = getSpawnTiles(this.mg, center, true);
|
|
if (!tiles) {
|
|
// if some of the spawn tile is outside of the land, we want to find another spawn tile
|
|
continue;
|
|
}
|
|
|
|
return { center, tiles };
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
private randTile(area?: SpawnArea): TileRef {
|
|
if (area) {
|
|
const x = this.random.nextInt(area.x, area.x + area.width);
|
|
const y = this.random.nextInt(area.y, area.y + area.height);
|
|
return this.mg.ref(x, y);
|
|
}
|
|
const x = this.random.nextInt(0, this.mg.width());
|
|
const y = this.random.nextInt(0, this.mg.height());
|
|
return this.mg.ref(x, y);
|
|
}
|
|
|
|
private getTeamSpawnArea(): SpawnArea | undefined {
|
|
const player = this.mg.player(this.playerInfo.id);
|
|
const team = player.team();
|
|
if (team === null) {
|
|
return undefined;
|
|
}
|
|
return this.mg.teamSpawnArea(team);
|
|
}
|
|
}
|