mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-21 07:40:43 +00:00
v30 nuke wars preparation: Disable boats & Team spawn zones (#3263)
## 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
This commit is contained in:
@@ -1,4 +1,10 @@
|
||||
{
|
||||
"name": "Baikal (Nuke Wars)",
|
||||
"nations": []
|
||||
"nations": [],
|
||||
"teamGameSpawnAreas": {
|
||||
"2": [
|
||||
{ "x": 0, "y": 0, "width": 1330, "height": 1564 },
|
||||
{ "x": 1430, "y": 0, "width": 1070, "height": 1564 }
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,5 +21,17 @@
|
||||
"flag": "",
|
||||
"name": "Myrkwind"
|
||||
}
|
||||
]
|
||||
],
|
||||
"teamGameSpawnAreas": {
|
||||
"2": [
|
||||
{ "x": 0, "y": 0, "width": 750, "height": 1500 },
|
||||
{ "x": 750, "y": 0, "width": 750, "height": 1500 }
|
||||
],
|
||||
"4": [
|
||||
{ "x": 0, "y": 0, "width": 750, "height": 750 },
|
||||
{ "x": 750, "y": 0, "width": 750, "height": 750 },
|
||||
{ "x": 0, "y": 750, "width": 750, "height": 750 },
|
||||
{ "x": 750, "y": 750, "width": 750, "height": 750 }
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -472,6 +472,7 @@
|
||||
"title": "Select Language"
|
||||
},
|
||||
"unit_type": {
|
||||
"boat": "Boat",
|
||||
"city": "City",
|
||||
"defense_post": "Defense Post",
|
||||
"port": "Port",
|
||||
|
||||
@@ -15,5 +15,21 @@
|
||||
"width": 1250
|
||||
},
|
||||
"name": "Baikal (Nuke Wars)",
|
||||
"nations": []
|
||||
"nations": [],
|
||||
"teamGameSpawnAreas": {
|
||||
"2": [
|
||||
{
|
||||
"height": 1564,
|
||||
"width": 1330,
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
{
|
||||
"height": 1564,
|
||||
"width": 1070,
|
||||
"x": 1430,
|
||||
"y": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,5 +36,47 @@
|
||||
"flag": "",
|
||||
"name": "Myrkwind"
|
||||
}
|
||||
]
|
||||
],
|
||||
"teamGameSpawnAreas": {
|
||||
"2": [
|
||||
{
|
||||
"height": 1500,
|
||||
"width": 750,
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
{
|
||||
"height": 1500,
|
||||
"width": 750,
|
||||
"x": 750,
|
||||
"y": 0
|
||||
}
|
||||
],
|
||||
"4": [
|
||||
{
|
||||
"height": 750,
|
||||
"width": 750,
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
{
|
||||
"height": 750,
|
||||
"width": 750,
|
||||
"x": 750,
|
||||
"y": 0
|
||||
},
|
||||
{
|
||||
"height": 750,
|
||||
"width": 750,
|
||||
"x": 0,
|
||||
"y": 750
|
||||
},
|
||||
{
|
||||
"height": 750,
|
||||
"width": 750,
|
||||
"x": 750,
|
||||
"y": 750
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,6 +93,7 @@ const unitOptions: { type: UnitType; translationKey: string }[] = [
|
||||
{ type: UnitType.DefensePost, translationKey: "unit_type.defense_post" },
|
||||
{ type: UnitType.Port, translationKey: "unit_type.port" },
|
||||
{ type: UnitType.Warship, translationKey: "unit_type.warship" },
|
||||
{ type: UnitType.TransportShip, translationKey: "unit_type.boat" },
|
||||
{ type: UnitType.MissileSilo, translationKey: "unit_type.missile_silo" },
|
||||
{ type: UnitType.SAMLauncher, translationKey: "unit_type.sam_launcher" },
|
||||
{ type: UnitType.AtomBomb, translationKey: "unit_type.atom_bomb" },
|
||||
|
||||
@@ -66,6 +66,7 @@ export async function createGameRunner(
|
||||
gameMap.gameMap,
|
||||
gameMap.miniGameMap,
|
||||
config,
|
||||
gameMap.teamGameSpawnAreas,
|
||||
);
|
||||
|
||||
const gr = new GameRunner(
|
||||
|
||||
@@ -530,6 +530,9 @@ export class DefaultConfig implements Config {
|
||||
return 80;
|
||||
}
|
||||
boatMaxNumber(): number {
|
||||
if (this.isUnitDisabled(UnitType.TransportShip)) {
|
||||
return 0;
|
||||
}
|
||||
return 3;
|
||||
}
|
||||
numSpawnPhaseTurns(): number {
|
||||
|
||||
@@ -110,6 +110,27 @@ export class NationExecution implements Execution {
|
||||
return;
|
||||
}
|
||||
|
||||
// If team spawn areas are configured and the nation's spawn cell
|
||||
// is outside its team's area, spawn randomly within the area instead.
|
||||
const team = this.player.team();
|
||||
if (team !== null) {
|
||||
const area = this.mg.teamSpawnArea(team);
|
||||
if (area !== undefined) {
|
||||
const cell = this.nation.spawnCell;
|
||||
const inArea =
|
||||
cell.x >= area.x &&
|
||||
cell.x < area.x + area.width &&
|
||||
cell.y >= area.y &&
|
||||
cell.y < area.y + area.height;
|
||||
if (!inArea) {
|
||||
this.mg.addExecution(
|
||||
new SpawnExecution(this.gameID, this.nation.playerInfo),
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Select a tile near the position defined in the map manifest
|
||||
const rl = this.randomSpawnLand();
|
||||
|
||||
|
||||
@@ -1,4 +1,11 @@
|
||||
import { Execution, Game, Player, PlayerInfo, PlayerType } from "../game/Game";
|
||||
import {
|
||||
Execution,
|
||||
Game,
|
||||
Player,
|
||||
PlayerInfo,
|
||||
PlayerType,
|
||||
SpawnArea,
|
||||
} from "../game/Game";
|
||||
import { TileRef } from "../game/GameMap";
|
||||
import { PseudoRandom } from "../PseudoRandom";
|
||||
import { GameID } from "../Schemas";
|
||||
@@ -90,12 +97,13 @@ export class SpawnExecution implements Execution {
|
||||
return { center, tiles };
|
||||
}
|
||||
|
||||
const spawnArea = this.getTeamSpawnArea();
|
||||
let tries = 0;
|
||||
|
||||
while (tries < SpawnExecution.MAX_SPAWN_TRIES) {
|
||||
tries++;
|
||||
|
||||
const center = this.randTile();
|
||||
const center = this.randTile(spawnArea);
|
||||
|
||||
if (
|
||||
!this.mg.isLand(center) ||
|
||||
@@ -137,10 +145,23 @@ export class SpawnExecution implements Execution {
|
||||
return;
|
||||
}
|
||||
|
||||
private randTile(): TileRef {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,6 +52,15 @@ export const isDifficulty = (value: unknown): value is Difficulty =>
|
||||
|
||||
export type Team = string;
|
||||
|
||||
export interface SpawnArea {
|
||||
x: number;
|
||||
y: number;
|
||||
width: number;
|
||||
height: number;
|
||||
}
|
||||
|
||||
export type TeamGameSpawnAreas = Record<string, SpawnArea[]>;
|
||||
|
||||
export const Duos = "Duos" as const;
|
||||
export const Trios = "Trios" as const;
|
||||
export const Quads = "Quads" as const;
|
||||
@@ -753,6 +762,7 @@ export interface Game extends GameMap {
|
||||
owner(ref: TileRef): Player | TerraNullius;
|
||||
|
||||
teams(): Team[];
|
||||
teamSpawnArea(team: Team): SpawnArea | undefined;
|
||||
|
||||
// Alliances
|
||||
alliances(): MutableAlliance[];
|
||||
|
||||
@@ -31,7 +31,9 @@ import {
|
||||
PlayerInfo,
|
||||
PlayerType,
|
||||
Quads,
|
||||
SpawnArea,
|
||||
Team,
|
||||
TeamGameSpawnAreas,
|
||||
TerrainType,
|
||||
TerraNullius,
|
||||
Trios,
|
||||
@@ -56,9 +58,18 @@ export function createGame(
|
||||
gameMap: GameMap,
|
||||
miniGameMap: GameMap,
|
||||
config: Config,
|
||||
teamGameSpawnAreas?: TeamGameSpawnAreas,
|
||||
): Game {
|
||||
const stats = new StatsImpl();
|
||||
return new GameImpl(humans, nations, gameMap, miniGameMap, config, stats);
|
||||
return new GameImpl(
|
||||
humans,
|
||||
nations,
|
||||
gameMap,
|
||||
miniGameMap,
|
||||
config,
|
||||
stats,
|
||||
teamGameSpawnAreas,
|
||||
);
|
||||
}
|
||||
|
||||
export type CellString = string;
|
||||
@@ -86,7 +97,7 @@ export class GameImpl implements Game {
|
||||
private tileUpdatePairs: number[] = [];
|
||||
private unitGrid: UnitGrid;
|
||||
|
||||
private playerTeams: Team[];
|
||||
private playerTeams: Team[] = [];
|
||||
private botTeam: Team = ColoredTeams.Bot;
|
||||
private _railNetwork: RailNetwork = createRailNetwork(this);
|
||||
|
||||
@@ -97,6 +108,7 @@ export class GameImpl implements Game {
|
||||
private _winner: Player | Team | null = null;
|
||||
private _miniWaterGraph: AbstractGraph | null = null;
|
||||
private _miniWaterHPA: AStarWaterHierarchical | null = null;
|
||||
private _teamGameSpawnAreas: TeamGameSpawnAreas | undefined;
|
||||
|
||||
constructor(
|
||||
private _humans: PlayerInfo[],
|
||||
@@ -105,9 +117,11 @@ export class GameImpl implements Game {
|
||||
private miniGameMap: GameMap,
|
||||
private _config: Config,
|
||||
private _stats: Stats,
|
||||
teamGameSpawnAreas?: TeamGameSpawnAreas,
|
||||
) {
|
||||
const constructorStart = performance.now();
|
||||
|
||||
this._teamGameSpawnAreas = teamGameSpawnAreas;
|
||||
this._terraNullius = new TerraNulliusImpl();
|
||||
this._width = _map.width();
|
||||
this._height = _map.height();
|
||||
@@ -791,6 +805,22 @@ export class GameImpl implements Game {
|
||||
return [this.botTeam, ...this.playerTeams];
|
||||
}
|
||||
|
||||
teamSpawnArea(team: Team): SpawnArea | undefined {
|
||||
if (!this._teamGameSpawnAreas) {
|
||||
return undefined;
|
||||
}
|
||||
const numTeams = this.playerTeams.length;
|
||||
const areas = this._teamGameSpawnAreas[String(numTeams)];
|
||||
if (!areas) {
|
||||
return undefined;
|
||||
}
|
||||
const teamIndex = this.playerTeams.indexOf(team);
|
||||
if (teamIndex < 0 || teamIndex >= areas.length) {
|
||||
return undefined;
|
||||
}
|
||||
return areas[teamIndex];
|
||||
}
|
||||
|
||||
displayMessage(
|
||||
message: string,
|
||||
type: MessageType,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { GameMapSize, GameMapType } from "./Game";
|
||||
import { GameMapSize, GameMapType, TeamGameSpawnAreas } from "./Game";
|
||||
import { GameMap, GameMapImpl } from "./GameMap";
|
||||
import { GameMapLoader } from "./GameMapLoader";
|
||||
|
||||
@@ -6,9 +6,10 @@ export type TerrainMapData = {
|
||||
nations: Nation[];
|
||||
gameMap: GameMap;
|
||||
miniGameMap: GameMap;
|
||||
teamGameSpawnAreas?: TeamGameSpawnAreas;
|
||||
};
|
||||
|
||||
const loadedMaps = new Map<GameMapType, TerrainMapData>();
|
||||
const loadedMaps = new Map<string, TerrainMapData>();
|
||||
|
||||
export interface MapMetadata {
|
||||
width: number;
|
||||
@@ -22,6 +23,7 @@ export interface MapManifest {
|
||||
map4x: MapMetadata;
|
||||
map16x: MapMetadata;
|
||||
nations: Nation[];
|
||||
teamGameSpawnAreas?: TeamGameSpawnAreas;
|
||||
}
|
||||
|
||||
export interface Nation {
|
||||
@@ -35,7 +37,8 @@ export async function loadTerrainMap(
|
||||
mapSize: GameMapSize,
|
||||
terrainMapFileLoader: GameMapLoader,
|
||||
): Promise<TerrainMapData> {
|
||||
const cached = loadedMaps.get(map);
|
||||
const cacheKey = `${map}:${mapSize}`;
|
||||
const cached = loadedMaps.get(cacheKey);
|
||||
if (cached !== undefined) return cached;
|
||||
const mapFiles = terrainMapFileLoader.getMapData(map);
|
||||
const manifest = await mapFiles.manifest();
|
||||
@@ -62,12 +65,28 @@ export async function loadTerrainMap(
|
||||
});
|
||||
}
|
||||
|
||||
// Scale spawn areas for compact maps
|
||||
let teamGameSpawnAreas = manifest.teamGameSpawnAreas;
|
||||
if (mapSize === GameMapSize.Compact && teamGameSpawnAreas) {
|
||||
const scaled: TeamGameSpawnAreas = {};
|
||||
for (const [key, areas] of Object.entries(teamGameSpawnAreas)) {
|
||||
scaled[key] = areas.map((a) => ({
|
||||
x: Math.floor(a.x / 2),
|
||||
y: Math.floor(a.y / 2),
|
||||
width: Math.max(1, Math.floor(a.width / 2)),
|
||||
height: Math.max(1, Math.floor(a.height / 2)),
|
||||
}));
|
||||
}
|
||||
teamGameSpawnAreas = scaled;
|
||||
}
|
||||
|
||||
const result = {
|
||||
nations: manifest.nations,
|
||||
gameMap: gameMap,
|
||||
miniGameMap: miniMap,
|
||||
teamGameSpawnAreas,
|
||||
};
|
||||
loadedMaps.set(map, result);
|
||||
loadedMaps.set(cacheKey, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user