mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-21 09:50:43 +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
984 lines
25 KiB
TypeScript
984 lines
25 KiB
TypeScript
import { Config } from "../configuration/Config";
|
|
import { AbstractGraph } from "../pathfinding/algorithms/AbstractGraph";
|
|
import { PathFinder } from "../pathfinding/types";
|
|
import { AllPlayersStats, ClientID } from "../Schemas";
|
|
import { getClanTag } from "../Util";
|
|
import { GameMap, TileRef } from "./GameMap";
|
|
import {
|
|
GameUpdate,
|
|
GameUpdateType,
|
|
PlayerUpdate,
|
|
UnitUpdate,
|
|
} from "./GameUpdates";
|
|
import { RailNetwork } from "./RailNetwork";
|
|
import { Stats } from "./Stats";
|
|
import { UnitPredicate } from "./UnitGrid";
|
|
|
|
function isEnumValue<T extends Record<string, string | number>>(
|
|
enumObj: T,
|
|
value: unknown,
|
|
): value is T[keyof T] {
|
|
return Object.values(enumObj).includes(value as T[keyof T]);
|
|
}
|
|
|
|
export type PlayerID = string;
|
|
export type Tick = number;
|
|
export type Gold = bigint;
|
|
|
|
export const AllPlayers = "AllPlayers" as const;
|
|
|
|
// export type GameUpdates = Record<GameUpdateType, GameUpdate[]>;
|
|
// Create a type that maps GameUpdateType to its corresponding update type
|
|
type UpdateTypeMap<T extends GameUpdateType> = Extract<GameUpdate, { type: T }>;
|
|
|
|
// Then use it to create the record type
|
|
export type GameUpdates = {
|
|
[K in GameUpdateType]: UpdateTypeMap<K>[];
|
|
};
|
|
|
|
export interface MapPos {
|
|
x: number;
|
|
y: number;
|
|
}
|
|
|
|
export enum Difficulty {
|
|
Easy = "Easy",
|
|
Medium = "Medium",
|
|
Hard = "Hard",
|
|
Impossible = "Impossible",
|
|
}
|
|
export const isDifficulty = (value: unknown): value is Difficulty =>
|
|
isEnumValue(Difficulty, value);
|
|
|
|
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;
|
|
export const HumansVsNations = "Humans Vs Nations" as const;
|
|
|
|
export const ColoredTeams: Record<string, Team> = {
|
|
Red: "Red",
|
|
Blue: "Blue",
|
|
Teal: "Teal",
|
|
Purple: "Purple",
|
|
Yellow: "Yellow",
|
|
Orange: "Orange",
|
|
Green: "Green",
|
|
Bot: "Bot",
|
|
Humans: "Humans",
|
|
Nations: "Nations",
|
|
} as const;
|
|
|
|
export enum GameMapType {
|
|
World = "World",
|
|
GiantWorldMap = "Giant World Map",
|
|
Europe = "Europe",
|
|
EuropeClassic = "Europe Classic",
|
|
Mena = "Mena",
|
|
NorthAmerica = "North America",
|
|
SouthAmerica = "South America",
|
|
Oceania = "Oceania",
|
|
BlackSea = "Black Sea",
|
|
Africa = "Africa",
|
|
Pangaea = "Pangaea",
|
|
Asia = "Asia",
|
|
Mars = "Mars",
|
|
BritanniaClassic = "Britannia Classic",
|
|
Britannia = "Britannia",
|
|
GatewayToTheAtlantic = "Gateway to the Atlantic",
|
|
Australia = "Australia",
|
|
Iceland = "Iceland",
|
|
EastAsia = "East Asia",
|
|
BetweenTwoSeas = "Between Two Seas",
|
|
FaroeIslands = "Faroe Islands",
|
|
DeglaciatedAntarctica = "Deglaciated Antarctica",
|
|
FalklandIslands = "Falkland Islands",
|
|
Baikal = "Baikal",
|
|
Halkidiki = "Halkidiki",
|
|
StraitOfGibraltar = "Strait of Gibraltar",
|
|
Italia = "Italia",
|
|
Japan = "Japan",
|
|
Pluto = "Pluto",
|
|
Montreal = "Montreal",
|
|
NewYorkCity = "New York City",
|
|
Achiran = "Achiran",
|
|
BaikalNukeWars = "Baikal Nuke Wars",
|
|
FourIslands = "Four Islands",
|
|
Svalmel = "Svalmel",
|
|
GulfOfStLawrence = "Gulf of St. Lawrence",
|
|
Lisbon = "Lisbon",
|
|
Manicouagan = "Manicouagan",
|
|
Lemnos = "Lemnos",
|
|
Sierpinski = "Sierpinski",
|
|
TheBox = "The Box",
|
|
TwoLakes = "Two Lakes",
|
|
StraitOfHormuz = "Strait of Hormuz",
|
|
Surrounded = "Surrounded",
|
|
Didier = "Didier",
|
|
DidierFrance = "Didier France",
|
|
AmazonRiver = "Amazon River",
|
|
Yenisei = "Yenisei",
|
|
TradersDream = "Traders Dream",
|
|
Hawaii = "Hawaii",
|
|
Alps = "Alps",
|
|
}
|
|
|
|
export type GameMapName = keyof typeof GameMapType;
|
|
|
|
/** Maps that have unusual thumbnail dimensions requiring object-fit: cover */
|
|
export function hasUnusualThumbnailSize(map: GameMapType): boolean {
|
|
return map === GameMapType.AmazonRiver;
|
|
}
|
|
|
|
export const mapCategories: Record<string, GameMapType[]> = {
|
|
continental: [
|
|
GameMapType.World,
|
|
GameMapType.GiantWorldMap,
|
|
GameMapType.NorthAmerica,
|
|
GameMapType.SouthAmerica,
|
|
GameMapType.Europe,
|
|
GameMapType.EuropeClassic,
|
|
GameMapType.Asia,
|
|
GameMapType.Africa,
|
|
GameMapType.Oceania,
|
|
],
|
|
regional: [
|
|
GameMapType.BritanniaClassic,
|
|
GameMapType.Britannia,
|
|
GameMapType.BlackSea,
|
|
GameMapType.GatewayToTheAtlantic,
|
|
GameMapType.BetweenTwoSeas,
|
|
GameMapType.Iceland,
|
|
GameMapType.EastAsia,
|
|
GameMapType.Mena,
|
|
GameMapType.Australia,
|
|
GameMapType.FaroeIslands,
|
|
GameMapType.FalklandIslands,
|
|
GameMapType.Baikal,
|
|
GameMapType.Halkidiki,
|
|
GameMapType.StraitOfGibraltar,
|
|
GameMapType.Italia,
|
|
GameMapType.Japan,
|
|
GameMapType.Montreal,
|
|
GameMapType.GulfOfStLawrence,
|
|
GameMapType.Lisbon,
|
|
GameMapType.NewYorkCity,
|
|
GameMapType.Manicouagan,
|
|
GameMapType.Lemnos,
|
|
GameMapType.TwoLakes,
|
|
GameMapType.StraitOfHormuz,
|
|
GameMapType.AmazonRiver,
|
|
GameMapType.Yenisei,
|
|
GameMapType.Hawaii,
|
|
GameMapType.Alps,
|
|
],
|
|
fantasy: [
|
|
GameMapType.Pangaea,
|
|
GameMapType.Pluto,
|
|
GameMapType.Mars,
|
|
GameMapType.DeglaciatedAntarctica,
|
|
GameMapType.Achiran,
|
|
GameMapType.BaikalNukeWars,
|
|
GameMapType.FourIslands,
|
|
GameMapType.Svalmel,
|
|
GameMapType.Surrounded,
|
|
GameMapType.TradersDream,
|
|
],
|
|
arcade: [
|
|
GameMapType.TheBox,
|
|
GameMapType.Didier,
|
|
GameMapType.DidierFrance,
|
|
GameMapType.Sierpinski,
|
|
],
|
|
};
|
|
|
|
export enum GameType {
|
|
Singleplayer = "Singleplayer",
|
|
Public = "Public",
|
|
Private = "Private",
|
|
}
|
|
export const isGameType = (value: unknown): value is GameType =>
|
|
isEnumValue(GameType, value);
|
|
|
|
export enum GameMode {
|
|
FFA = "Free For All",
|
|
Team = "Team",
|
|
}
|
|
|
|
export enum RankedType {
|
|
OneVOne = "1v1",
|
|
}
|
|
|
|
export const isGameMode = (value: unknown): value is GameMode =>
|
|
isEnumValue(GameMode, value);
|
|
|
|
export enum GameMapSize {
|
|
Compact = "Compact",
|
|
Normal = "Normal",
|
|
}
|
|
|
|
export interface PublicGameModifiers {
|
|
isCompact: boolean;
|
|
isRandomSpawn: boolean;
|
|
isCrowded: boolean;
|
|
startingGold?: number;
|
|
}
|
|
|
|
export interface UnitInfo {
|
|
cost: (game: Game, player: Player) => Gold;
|
|
maxHealth?: number;
|
|
damage?: number;
|
|
constructionDuration?: number;
|
|
upgradable?: boolean;
|
|
}
|
|
|
|
export enum UnitType {
|
|
TransportShip = "Transport",
|
|
Warship = "Warship",
|
|
Shell = "Shell",
|
|
SAMMissile = "SAMMissile",
|
|
Port = "Port",
|
|
AtomBomb = "Atom Bomb",
|
|
HydrogenBomb = "Hydrogen Bomb",
|
|
TradeShip = "Trade Ship",
|
|
MissileSilo = "Missile Silo",
|
|
DefensePost = "Defense Post",
|
|
SAMLauncher = "SAM Launcher",
|
|
City = "City",
|
|
MIRV = "MIRV",
|
|
MIRVWarhead = "MIRV Warhead",
|
|
Train = "Train",
|
|
Factory = "Factory",
|
|
}
|
|
|
|
export enum TrainType {
|
|
Engine = "Engine",
|
|
TailEngine = "TailEngine",
|
|
Carriage = "Carriage",
|
|
}
|
|
|
|
const _structureTypes: ReadonlySet<UnitType> = new Set([
|
|
UnitType.City,
|
|
UnitType.DefensePost,
|
|
UnitType.SAMLauncher,
|
|
UnitType.MissileSilo,
|
|
UnitType.Port,
|
|
UnitType.Factory,
|
|
]);
|
|
|
|
export const StructureTypes: readonly UnitType[] = [..._structureTypes];
|
|
|
|
export function isStructureType(type: UnitType): boolean {
|
|
return _structureTypes.has(type);
|
|
}
|
|
|
|
export interface OwnerComp {
|
|
owner: Player;
|
|
}
|
|
|
|
export type TrajectoryTile = {
|
|
tile: TileRef;
|
|
targetable: boolean;
|
|
};
|
|
export interface UnitParamsMap {
|
|
[UnitType.TransportShip]: {
|
|
troops?: number;
|
|
targetTile?: TileRef;
|
|
};
|
|
|
|
[UnitType.Warship]: {
|
|
patrolTile: TileRef;
|
|
};
|
|
|
|
[UnitType.Shell]: Record<string, never>;
|
|
|
|
[UnitType.SAMMissile]: Record<string, never>;
|
|
|
|
[UnitType.Port]: Record<string, never>;
|
|
|
|
[UnitType.AtomBomb]: {
|
|
targetTile?: number;
|
|
trajectory: TrajectoryTile[];
|
|
};
|
|
|
|
[UnitType.HydrogenBomb]: {
|
|
targetTile?: number;
|
|
trajectory: TrajectoryTile[];
|
|
};
|
|
|
|
[UnitType.TradeShip]: {
|
|
targetUnit: Unit;
|
|
lastSetSafeFromPirates?: number;
|
|
};
|
|
|
|
[UnitType.Train]: {
|
|
trainType: TrainType;
|
|
targetUnit?: Unit;
|
|
loaded?: boolean;
|
|
};
|
|
|
|
[UnitType.Factory]: Record<string, never>;
|
|
|
|
[UnitType.MissileSilo]: Record<string, never>;
|
|
|
|
[UnitType.DefensePost]: Record<string, never>;
|
|
|
|
[UnitType.SAMLauncher]: Record<string, never>;
|
|
|
|
[UnitType.City]: Record<string, never>;
|
|
|
|
[UnitType.MIRV]: {
|
|
targetTile?: number;
|
|
};
|
|
|
|
[UnitType.MIRVWarhead]: {
|
|
targetTile?: number;
|
|
};
|
|
}
|
|
|
|
// Type helper to get params type for a specific unit type
|
|
export type UnitParams<T extends UnitType> = UnitParamsMap[T];
|
|
|
|
export type AllUnitParams = UnitParamsMap[keyof UnitParamsMap];
|
|
|
|
export const nukeTypes = [
|
|
UnitType.AtomBomb,
|
|
UnitType.HydrogenBomb,
|
|
UnitType.MIRVWarhead,
|
|
UnitType.MIRV,
|
|
] as UnitType[];
|
|
|
|
export enum Relation {
|
|
Hostile = 0,
|
|
Distrustful = 1,
|
|
Neutral = 2,
|
|
Friendly = 3,
|
|
}
|
|
|
|
export class Nation {
|
|
constructor(
|
|
public readonly spawnCell: Cell | undefined,
|
|
public readonly playerInfo: PlayerInfo,
|
|
) {}
|
|
}
|
|
|
|
export class Cell {
|
|
public index: number;
|
|
|
|
private strRepr: string;
|
|
|
|
constructor(
|
|
public readonly x: number,
|
|
public readonly y: number,
|
|
) {
|
|
this.strRepr = `Cell[${this.x},${this.y}]`;
|
|
}
|
|
|
|
pos(): MapPos {
|
|
return {
|
|
x: this.x,
|
|
y: this.y,
|
|
};
|
|
}
|
|
|
|
toString(): string {
|
|
return this.strRepr;
|
|
}
|
|
}
|
|
|
|
export enum TerrainType {
|
|
Plains,
|
|
Highland,
|
|
Mountain,
|
|
Lake,
|
|
Ocean,
|
|
}
|
|
|
|
export enum PlayerType {
|
|
Bot = "BOT",
|
|
Human = "HUMAN",
|
|
Nation = "NATION",
|
|
}
|
|
|
|
export interface Execution {
|
|
isActive(): boolean;
|
|
activeDuringSpawnPhase(): boolean;
|
|
init(mg: Game, ticks: number): void;
|
|
tick(ticks: number): void;
|
|
}
|
|
|
|
export interface Attack {
|
|
id(): string;
|
|
retreating(): boolean;
|
|
retreated(): boolean;
|
|
orderRetreat(): void;
|
|
executeRetreat(): void;
|
|
target(): Player | TerraNullius;
|
|
attacker(): Player;
|
|
troops(): number;
|
|
setTroops(troops: number): void;
|
|
isActive(): boolean;
|
|
delete(): void;
|
|
// The tile the attack originated from, mostly used for boat attacks.
|
|
sourceTile(): TileRef | null;
|
|
addBorderTile(tile: TileRef): void;
|
|
removeBorderTile(tile: TileRef): void;
|
|
clearBorder(): void;
|
|
borderSize(): number;
|
|
averagePosition(): Cell | null;
|
|
}
|
|
|
|
export interface AllianceRequest {
|
|
accept(): void;
|
|
reject(): void;
|
|
requestor(): Player;
|
|
recipient(): Player;
|
|
createdAt(): Tick;
|
|
status(): "pending" | "accepted" | "rejected";
|
|
}
|
|
|
|
export interface Alliance {
|
|
requestor(): Player;
|
|
recipient(): Player;
|
|
createdAt(): Tick;
|
|
expiresAt(): Tick;
|
|
other(player: Player): Player;
|
|
}
|
|
|
|
export interface MutableAlliance extends Alliance {
|
|
expire(): void;
|
|
other(player: Player): Player;
|
|
bothAgreedToExtend(): boolean;
|
|
addExtensionRequest(player: Player): void;
|
|
id(): number;
|
|
extend(): void;
|
|
onlyOneAgreedToExtend(): boolean;
|
|
|
|
agreedToExtend(player: Player): boolean;
|
|
}
|
|
|
|
export class PlayerInfo {
|
|
public readonly clan: string | null;
|
|
|
|
constructor(
|
|
public readonly name: string,
|
|
public readonly playerType: PlayerType,
|
|
// null if bot.
|
|
public readonly clientID: ClientID | null,
|
|
// TODO: make player id the small id
|
|
public readonly id: PlayerID,
|
|
public readonly isLobbyCreator: boolean = false,
|
|
) {
|
|
this.clan = getClanTag(name);
|
|
}
|
|
}
|
|
|
|
export function isUnit(unit: unknown): unit is Unit {
|
|
return (
|
|
unit &&
|
|
typeof unit === "object" &&
|
|
"isUnit" in unit &&
|
|
typeof unit.isUnit === "function" &&
|
|
unit.isUnit()
|
|
);
|
|
}
|
|
|
|
export interface Unit {
|
|
isUnit(): this is Unit;
|
|
|
|
// Common properties.
|
|
id(): number;
|
|
type(): UnitType;
|
|
owner(): Player;
|
|
info(): UnitInfo;
|
|
isMarkedForDeletion(): boolean;
|
|
markForDeletion(): void;
|
|
isOverdueDeletion(): boolean;
|
|
delete(displayMessage?: boolean, destroyer?: Player): void;
|
|
tile(): TileRef;
|
|
lastTile(): TileRef;
|
|
move(tile: TileRef): void;
|
|
isActive(): boolean;
|
|
setOwner(owner: Player): void;
|
|
touch(): void;
|
|
hash(): number;
|
|
toUpdate(): UnitUpdate;
|
|
hasTrainStation(): boolean;
|
|
setTrainStation(trainStation: boolean): void;
|
|
wasDestroyedByEnemy(): boolean;
|
|
destroyer(): Player | undefined;
|
|
|
|
// Train
|
|
trainType(): TrainType | undefined;
|
|
isLoaded(): boolean | undefined;
|
|
setLoaded(loaded: boolean): void;
|
|
|
|
// Targeting
|
|
setTargetTile(cell: TileRef | undefined): void;
|
|
targetTile(): TileRef | undefined;
|
|
setTrajectoryIndex(i: number): void;
|
|
trajectoryIndex(): number;
|
|
trajectory(): TrajectoryTile[];
|
|
setTargetUnit(unit: Unit | undefined): void;
|
|
targetUnit(): Unit | undefined;
|
|
setTargetedBySAM(targeted: boolean): void;
|
|
targetedBySAM(): boolean;
|
|
setReachedTarget(): void;
|
|
reachedTarget(): boolean;
|
|
isTargetable(): boolean;
|
|
setTargetable(targetable: boolean): void;
|
|
|
|
// Health
|
|
hasHealth(): boolean;
|
|
retreating(): boolean;
|
|
orderBoatRetreat(): void;
|
|
health(): number;
|
|
modifyHealth(delta: number, attacker?: Player): void;
|
|
|
|
// Troops
|
|
setTroops(troops: number): void;
|
|
troops(): number;
|
|
|
|
// --- UNIT SPECIFIC ---
|
|
|
|
// SAMs & Missile Silos
|
|
launch(): void;
|
|
reloadMissile(): void;
|
|
isInCooldown(): boolean;
|
|
missileTimerQueue(): number[];
|
|
|
|
// Trade Ships
|
|
setSafeFromPirates(): void; // Only for trade ships
|
|
isSafeFromPirates(): boolean; // Only for trade ships
|
|
|
|
// Construction phase on structures
|
|
isUnderConstruction(): boolean;
|
|
setUnderConstruction(underConstruction: boolean): void;
|
|
|
|
// Upgradable Structures
|
|
level(): number;
|
|
increaseLevel(): void;
|
|
decreaseLevel(destroyer?: Player): void;
|
|
|
|
// Warships
|
|
setPatrolTile(tile: TileRef): void;
|
|
patrolTile(): TileRef | undefined;
|
|
}
|
|
|
|
export interface TerraNullius {
|
|
isPlayer(): false;
|
|
id(): null;
|
|
clientID(): ClientID;
|
|
smallID(): number;
|
|
}
|
|
|
|
export interface Embargo {
|
|
createdAt: Tick;
|
|
isTemporary: boolean;
|
|
target: Player;
|
|
}
|
|
|
|
export interface Player {
|
|
// Basic Info
|
|
smallID(): number;
|
|
info(): PlayerInfo;
|
|
name(): string;
|
|
displayName(): string;
|
|
clientID(): ClientID | null;
|
|
id(): PlayerID;
|
|
type(): PlayerType;
|
|
isPlayer(): this is Player;
|
|
toString(): string;
|
|
isLobbyCreator(): boolean;
|
|
|
|
// State & Properties
|
|
isAlive(): boolean;
|
|
isTraitor(): boolean;
|
|
markTraitor(): void;
|
|
largestClusterBoundingBox: { min: Cell; max: Cell } | null;
|
|
lastTileChange(): Tick;
|
|
|
|
isDisconnected(): boolean;
|
|
markDisconnected(isDisconnected: boolean): void;
|
|
|
|
hasSpawned(): boolean;
|
|
setSpawnTile(spawnTile: TileRef): void;
|
|
spawnTile(): TileRef | undefined;
|
|
|
|
// Territory
|
|
tiles(): ReadonlySet<TileRef>;
|
|
borderTiles(): ReadonlySet<TileRef>;
|
|
numTilesOwned(): number;
|
|
conquer(tile: TileRef): void;
|
|
relinquish(tile: TileRef): void;
|
|
|
|
// Resources & Troops
|
|
gold(): Gold;
|
|
addGold(toAdd: Gold, tile?: TileRef): void;
|
|
removeGold(toRemove: Gold): Gold;
|
|
troops(): number;
|
|
setTroops(troops: number): void;
|
|
addTroops(troops: number): void;
|
|
removeTroops(troops: number): number;
|
|
|
|
// Units
|
|
units(...types: UnitType[]): Unit[];
|
|
unitCount(type: UnitType): number;
|
|
unitsConstructed(type: UnitType): number;
|
|
unitsOwned(type: UnitType): number;
|
|
buildableUnits(tile: TileRef | null, units?: UnitType[]): BuildableUnit[];
|
|
canBuild(type: UnitType, targetTile: TileRef): TileRef | false;
|
|
buildUnit<T extends UnitType>(
|
|
type: T,
|
|
spawnTile: TileRef,
|
|
params: UnitParams<T>,
|
|
): Unit;
|
|
|
|
// Returns the existing unit that can be upgraded,
|
|
// or false if it cannot be upgraded.
|
|
// New units of the same type can upgrade existing units.
|
|
// e.g. if a place a new city here, can it upgrade an existing city?
|
|
findUnitToUpgrade(type: UnitType, targetTile: TileRef): Unit | false;
|
|
canUpgradeUnit(unit: Unit): boolean;
|
|
upgradeUnit(unit: Unit): void;
|
|
captureUnit(unit: Unit): void;
|
|
|
|
// Relations & Diplomacy
|
|
neighbors(): (Player | TerraNullius)[];
|
|
sharesBorderWith(other: Player | TerraNullius): boolean;
|
|
relation(other: Player): Relation;
|
|
allRelationsSorted(): { player: Player; relation: Relation }[];
|
|
updateRelation(other: Player, delta: number): void;
|
|
decayRelations(): void;
|
|
isOnSameTeam(other: Player): boolean;
|
|
// Either allied or on same team.
|
|
isFriendly(other: Player, treatAFKFriendly?: boolean): boolean;
|
|
team(): Team | null;
|
|
clan(): string | null;
|
|
incomingAllianceRequests(): AllianceRequest[];
|
|
outgoingAllianceRequests(): AllianceRequest[];
|
|
alliances(): MutableAlliance[];
|
|
expiredAlliances(): Alliance[];
|
|
allies(): Player[];
|
|
isAlliedWith(other: Player): boolean;
|
|
allianceWith(other: Player): MutableAlliance | null;
|
|
allianceInfo(other: Player): AllianceInfo | null;
|
|
canSendAllianceRequest(other: Player): boolean;
|
|
breakAlliance(alliance: Alliance): void;
|
|
removeAllAlliances(): void;
|
|
createAllianceRequest(recipient: Player): AllianceRequest | null;
|
|
betrayals(): number;
|
|
|
|
// Targeting
|
|
canTarget(other: Player): boolean;
|
|
target(other: Player): void;
|
|
targets(): Player[];
|
|
transitiveTargets(): Player[];
|
|
|
|
// Communication
|
|
canSendEmoji(recipient: Player | typeof AllPlayers): boolean;
|
|
outgoingEmojis(): EmojiMessage[];
|
|
sendEmoji(recipient: Player | typeof AllPlayers, emoji: string): void;
|
|
|
|
// Donation
|
|
canDonateGold(recipient: Player): boolean;
|
|
canDonateTroops(recipient: Player): boolean;
|
|
donateTroops(recipient: Player, troops: number): boolean;
|
|
donateGold(recipient: Player, gold: Gold): boolean;
|
|
canDeleteUnit(): boolean;
|
|
recordDeleteUnit(): void;
|
|
canEmbargoAll(): boolean;
|
|
recordEmbargoAll(): void;
|
|
|
|
// Embargo
|
|
hasEmbargoAgainst(other: Player): boolean;
|
|
tradingPartners(): Player[];
|
|
addEmbargo(other: Player, isTemporary: boolean): void;
|
|
getEmbargoes(): Embargo[];
|
|
stopEmbargo(other: Player): void;
|
|
endTemporaryEmbargo(other: Player): void;
|
|
canTrade(other: Player): boolean;
|
|
|
|
// Attacking.
|
|
canAttack(tile: TileRef): boolean;
|
|
canAttackPlayer(player: Player, treatAFKFriendly?: boolean): boolean;
|
|
isImmune(): boolean;
|
|
|
|
createAttack(
|
|
target: Player | TerraNullius,
|
|
troops: number,
|
|
sourceTile: TileRef | null,
|
|
border: Set<number>,
|
|
): Attack;
|
|
outgoingAttacks(): Attack[];
|
|
incomingAttacks(): Attack[];
|
|
orderRetreat(attackID: string): void;
|
|
executeRetreat(attackID: string): void;
|
|
|
|
// Misc
|
|
toUpdate(): PlayerUpdate;
|
|
playerProfile(): PlayerProfile;
|
|
// WARNING: this operation is expensive.
|
|
bestTransportShipSpawn(tile: TileRef): TileRef | false;
|
|
}
|
|
|
|
export interface Game extends GameMap {
|
|
// Map & Dimensions
|
|
isOnMap(cell: Cell): boolean;
|
|
width(): number;
|
|
height(): number;
|
|
map(): GameMap;
|
|
miniMap(): GameMap;
|
|
forEachTile(fn: (tile: TileRef) => void): void;
|
|
// Zero-allocation neighbor iteration (cardinal only) to avoid creating arrays
|
|
forEachNeighbor(tile: TileRef, callback: (neighbor: TileRef) => void): void;
|
|
// Zero-allocation neighbor iteration for performance-critical cluster calculation
|
|
// Alternative to neighborsWithDiag() that returns arrays
|
|
// Avoids creating intermediate arrays and uses a callback for better performance
|
|
forEachNeighborWithDiag(
|
|
tile: TileRef,
|
|
callback: (neighbor: TileRef) => void,
|
|
): void;
|
|
|
|
// Player Management
|
|
player(id: PlayerID): Player;
|
|
players(): Player[];
|
|
allPlayers(): Player[];
|
|
playerByClientID(id: ClientID): Player | null;
|
|
playerBySmallID(id: number): Player | TerraNullius;
|
|
hasPlayer(id: PlayerID): boolean;
|
|
addPlayer(playerInfo: PlayerInfo): Player;
|
|
terraNullius(): TerraNullius;
|
|
owner(ref: TileRef): Player | TerraNullius;
|
|
|
|
teams(): Team[];
|
|
teamSpawnArea(team: Team): SpawnArea | undefined;
|
|
|
|
// Alliances
|
|
alliances(): MutableAlliance[];
|
|
expireAlliance(alliance: Alliance): void;
|
|
|
|
// Immunity timer
|
|
isSpawnImmunityActive(): boolean;
|
|
isNationSpawnImmunityActive(): boolean;
|
|
|
|
// Game State
|
|
ticks(): Tick;
|
|
inSpawnPhase(): boolean;
|
|
executeNextTick(): GameUpdates;
|
|
drainPackedTileUpdates(): Uint32Array;
|
|
setWinner(winner: Player | Team, allPlayersStats: AllPlayersStats): void;
|
|
getWinner(): Player | Team | null;
|
|
config(): Config;
|
|
isPaused(): boolean;
|
|
setPaused(paused: boolean): void;
|
|
|
|
// Units
|
|
units(...types: UnitType[]): Unit[];
|
|
unitCount(type: UnitType): number;
|
|
unitInfo(type: UnitType): UnitInfo;
|
|
hasUnitNearby(
|
|
tile: TileRef,
|
|
searchRange: number,
|
|
type: UnitType,
|
|
playerId?: PlayerID,
|
|
includeUnderConstruction?: boolean,
|
|
): boolean;
|
|
anyUnitNearby(
|
|
tile: TileRef,
|
|
searchRange: number,
|
|
types: readonly UnitType[],
|
|
predicate: (unit: Unit) => boolean,
|
|
playerId?: PlayerID,
|
|
includeUnderConstruction?: boolean,
|
|
): boolean;
|
|
nearbyUnits(
|
|
tile: TileRef,
|
|
searchRange: number,
|
|
types: UnitType | readonly UnitType[],
|
|
predicate?: UnitPredicate,
|
|
includeUnderConstruction?: boolean,
|
|
): Array<{ unit: Unit; distSquared: number }>;
|
|
|
|
addExecution(...exec: Execution[]): void;
|
|
displayMessage(
|
|
message: string,
|
|
type: MessageType,
|
|
playerID: PlayerID | null,
|
|
goldAmount?: bigint,
|
|
params?: Record<string, string | number>,
|
|
): void;
|
|
displayIncomingUnit(
|
|
unitID: number,
|
|
message: string,
|
|
type: MessageType,
|
|
playerID: PlayerID | null,
|
|
): void;
|
|
|
|
displayChat(
|
|
message: string,
|
|
category: string,
|
|
target: PlayerID | undefined,
|
|
playerID: PlayerID | null,
|
|
isFrom: boolean,
|
|
recipient: string,
|
|
): void;
|
|
|
|
// Nations
|
|
nations(): Nation[];
|
|
|
|
numTilesWithFallout(): number;
|
|
stats(): Stats;
|
|
|
|
addUpdate(update: GameUpdate): void;
|
|
railNetwork(): RailNetwork;
|
|
conquerPlayer(conqueror: Player, conquered: Player): void;
|
|
miniWaterHPA(): PathFinder<number> | null;
|
|
miniWaterGraph(): AbstractGraph | null;
|
|
getWaterComponent(tile: TileRef): number | null;
|
|
hasWaterComponent(tile: TileRef, component: number): boolean;
|
|
}
|
|
|
|
export interface PlayerActions {
|
|
canAttack: boolean;
|
|
buildableUnits: BuildableUnit[];
|
|
canSendEmojiAllPlayers: boolean;
|
|
canEmbargoAll?: boolean;
|
|
interaction?: PlayerInteraction;
|
|
}
|
|
|
|
export interface BuildableUnit {
|
|
canBuild: TileRef | false;
|
|
// unit id of the existing unit that can be upgraded, or false if it cannot be upgraded.
|
|
canUpgrade: number | false;
|
|
type: UnitType;
|
|
cost: Gold;
|
|
overlappingRailroads: number[];
|
|
ghostRailPaths: TileRef[][];
|
|
}
|
|
|
|
export interface PlayerProfile {
|
|
relations: Record<number, Relation>;
|
|
alliances: number[];
|
|
}
|
|
|
|
export interface PlayerBorderTiles {
|
|
borderTiles: ReadonlySet<TileRef>;
|
|
}
|
|
|
|
export interface AllianceInfo {
|
|
expiresAt: Tick;
|
|
inExtensionWindow: boolean;
|
|
myPlayerAgreedToExtend: boolean;
|
|
otherAgreedToExtend: boolean;
|
|
canExtend: boolean;
|
|
}
|
|
|
|
export interface PlayerInteraction {
|
|
sharedBorder: boolean;
|
|
canSendEmoji: boolean;
|
|
canSendAllianceRequest: boolean;
|
|
canBreakAlliance: boolean;
|
|
canTarget: boolean;
|
|
canDonateGold: boolean;
|
|
canDonateTroops: boolean;
|
|
canEmbargo: boolean;
|
|
allianceInfo?: AllianceInfo;
|
|
}
|
|
|
|
export interface EmojiMessage {
|
|
message: string;
|
|
senderID: number;
|
|
recipientID: number | typeof AllPlayers;
|
|
createdAt: Tick;
|
|
}
|
|
|
|
export enum MessageType {
|
|
ATTACK_FAILED,
|
|
ATTACK_CANCELLED,
|
|
ATTACK_REQUEST,
|
|
CONQUERED_PLAYER,
|
|
MIRV_INBOUND,
|
|
NUKE_INBOUND,
|
|
HYDROGEN_BOMB_INBOUND,
|
|
NAVAL_INVASION_INBOUND,
|
|
SAM_MISS,
|
|
SAM_HIT,
|
|
CAPTURED_ENEMY_UNIT,
|
|
UNIT_CAPTURED_BY_ENEMY,
|
|
UNIT_DESTROYED,
|
|
ALLIANCE_ACCEPTED,
|
|
ALLIANCE_REJECTED,
|
|
ALLIANCE_REQUEST,
|
|
ALLIANCE_BROKEN,
|
|
ALLIANCE_EXPIRED,
|
|
SENT_GOLD_TO_PLAYER,
|
|
RECEIVED_GOLD_FROM_PLAYER,
|
|
RECEIVED_GOLD_FROM_TRADE,
|
|
SENT_TROOPS_TO_PLAYER,
|
|
RECEIVED_TROOPS_FROM_PLAYER,
|
|
CHAT,
|
|
RENEW_ALLIANCE,
|
|
}
|
|
|
|
// Message categories used for filtering events in the EventsDisplay
|
|
export enum MessageCategory {
|
|
ATTACK = "ATTACK",
|
|
NUKE = "NUKE",
|
|
ALLIANCE = "ALLIANCE",
|
|
TRADE = "TRADE",
|
|
CHAT = "CHAT",
|
|
}
|
|
|
|
// Ensures that all message types are included in a category
|
|
export const MESSAGE_TYPE_CATEGORIES: Record<MessageType, MessageCategory> = {
|
|
[MessageType.ATTACK_FAILED]: MessageCategory.ATTACK,
|
|
[MessageType.ATTACK_CANCELLED]: MessageCategory.ATTACK,
|
|
[MessageType.ATTACK_REQUEST]: MessageCategory.ATTACK,
|
|
[MessageType.CONQUERED_PLAYER]: MessageCategory.ATTACK,
|
|
[MessageType.MIRV_INBOUND]: MessageCategory.NUKE,
|
|
[MessageType.NUKE_INBOUND]: MessageCategory.NUKE,
|
|
[MessageType.HYDROGEN_BOMB_INBOUND]: MessageCategory.NUKE,
|
|
[MessageType.NAVAL_INVASION_INBOUND]: MessageCategory.ATTACK,
|
|
[MessageType.SAM_MISS]: MessageCategory.ATTACK,
|
|
[MessageType.SAM_HIT]: MessageCategory.ATTACK,
|
|
[MessageType.CAPTURED_ENEMY_UNIT]: MessageCategory.ATTACK,
|
|
[MessageType.UNIT_CAPTURED_BY_ENEMY]: MessageCategory.ATTACK,
|
|
[MessageType.UNIT_DESTROYED]: MessageCategory.ATTACK,
|
|
[MessageType.ALLIANCE_ACCEPTED]: MessageCategory.ALLIANCE,
|
|
[MessageType.ALLIANCE_REJECTED]: MessageCategory.ALLIANCE,
|
|
[MessageType.ALLIANCE_REQUEST]: MessageCategory.ALLIANCE,
|
|
[MessageType.ALLIANCE_BROKEN]: MessageCategory.ALLIANCE,
|
|
[MessageType.ALLIANCE_EXPIRED]: MessageCategory.ALLIANCE,
|
|
[MessageType.RENEW_ALLIANCE]: MessageCategory.ALLIANCE,
|
|
[MessageType.SENT_GOLD_TO_PLAYER]: MessageCategory.TRADE,
|
|
[MessageType.RECEIVED_GOLD_FROM_PLAYER]: MessageCategory.TRADE,
|
|
[MessageType.RECEIVED_GOLD_FROM_TRADE]: MessageCategory.TRADE,
|
|
[MessageType.SENT_TROOPS_TO_PLAYER]: MessageCategory.TRADE,
|
|
[MessageType.RECEIVED_TROOPS_FROM_PLAYER]: MessageCategory.TRADE,
|
|
[MessageType.CHAT]: MessageCategory.CHAT,
|
|
} as const;
|
|
|
|
/**
|
|
* Get the category of a message type
|
|
*/
|
|
export function getMessageCategory(messageType: MessageType): MessageCategory {
|
|
return MESSAGE_TYPE_CATEGORIES[messageType];
|
|
}
|
|
|
|
export interface NameViewData {
|
|
x: number;
|
|
y: number;
|
|
size: number;
|
|
}
|