mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-21 09:40:44 +00:00
a989bcbeb0
## Description: Change inspired by Territorial.io . Renames the Mediterranean sea map to Mare Nostrum, the ancient name of the sea given by the Romans. The NPCs of the map are already roman provinces, so this change is more thematic and also adds a creative name like "Gateway to the Atlantic" and "Between Two Seas" ## 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: tri.star1011
1081 lines
27 KiB
TypeScript
1081 lines
27 KiB
TypeScript
import { Config } from "../configuration/Config";
|
|
import { AbstractGraph } from "../pathfinding/algorithms/AbstractGraph";
|
|
import { PathFinder } from "../pathfinding/types";
|
|
import { AllPlayersStats, ClientID } from "../Schemas";
|
|
import { formatPlayerDisplayName } from "../Util";
|
|
import { GameMap, TileRef } from "./GameMap";
|
|
import {
|
|
GameUpdate,
|
|
GameUpdateType,
|
|
PlayerUpdate,
|
|
UnitUpdate,
|
|
} from "./GameUpdates";
|
|
import { MotionPlanRecord } from "./MotionPlans";
|
|
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",
|
|
Tourney1 = "Tourney 2 Teams",
|
|
Tourney2 = "Tourney 3 Teams",
|
|
Tourney3 = "Tourney 4 Teams",
|
|
Tourney4 = "Tourney 8 Teams",
|
|
Passage = "Passage",
|
|
Sierpinski = "Sierpinski",
|
|
TheBox = "The Box",
|
|
TwoLakes = "Two Lakes",
|
|
StraitOfHormuz = "Strait of Hormuz",
|
|
Surrounded = "Surrounded",
|
|
Didier = "Didier",
|
|
DidierFrance = "Didier France",
|
|
AmazonRiver = "Amazon River",
|
|
BosphorusStraits = "Bosphorus Straits",
|
|
BeringStrait = "Bering Strait",
|
|
Yenisei = "Yenisei",
|
|
TradersDream = "Traders Dream",
|
|
Hawaii = "Hawaii",
|
|
Alps = "Alps",
|
|
NileDelta = "Nile Delta",
|
|
Arctic = "Arctic",
|
|
SanFrancisco = "San Francisco",
|
|
Aegean = "Aegean",
|
|
MilkyWay = "MilkyWay",
|
|
MareNostrum = "Mare Nostrum",
|
|
Dyslexdria = "Dyslexdria",
|
|
GreatLakes = "Great Lakes",
|
|
StraitOfMalacca = "Strait Of Malacca",
|
|
Luna = "Luna",
|
|
Conakry = "Conakry",
|
|
Caucasus = "Caucasus",
|
|
LosAngeles = "Los Angeles",
|
|
BeringSea = "Bering Sea",
|
|
Antarctica = "Antarctica",
|
|
ArchipelagoSea = "ArchipelagoSea",
|
|
BajaCalifornia = "Baja California",
|
|
}
|
|
|
|
export type GameMapName = keyof typeof GameMapType;
|
|
|
|
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,
|
|
GameMapType.Antarctica,
|
|
],
|
|
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.BosphorusStraits,
|
|
GameMapType.BeringStrait,
|
|
GameMapType.Yenisei,
|
|
GameMapType.Hawaii,
|
|
GameMapType.Alps,
|
|
GameMapType.NileDelta,
|
|
GameMapType.Arctic,
|
|
GameMapType.SanFrancisco,
|
|
GameMapType.Aegean,
|
|
GameMapType.MareNostrum,
|
|
GameMapType.GreatLakes,
|
|
GameMapType.StraitOfMalacca,
|
|
GameMapType.Conakry,
|
|
GameMapType.Caucasus,
|
|
GameMapType.LosAngeles,
|
|
GameMapType.BeringSea,
|
|
GameMapType.ArchipelagoSea,
|
|
GameMapType.BajaCalifornia,
|
|
],
|
|
fantasy: [
|
|
GameMapType.Pangaea,
|
|
GameMapType.Pluto,
|
|
GameMapType.Mars,
|
|
GameMapType.DeglaciatedAntarctica,
|
|
GameMapType.Achiran,
|
|
GameMapType.BaikalNukeWars,
|
|
GameMapType.FourIslands,
|
|
GameMapType.Svalmel,
|
|
GameMapType.Surrounded,
|
|
GameMapType.TradersDream,
|
|
GameMapType.Passage,
|
|
GameMapType.MilkyWay,
|
|
GameMapType.Dyslexdria,
|
|
GameMapType.Luna,
|
|
],
|
|
arcade: [
|
|
GameMapType.TheBox,
|
|
GameMapType.Didier,
|
|
GameMapType.DidierFrance,
|
|
GameMapType.Sierpinski,
|
|
],
|
|
tournament: [
|
|
GameMapType.Tourney1,
|
|
GameMapType.Tourney2,
|
|
GameMapType.Tourney3,
|
|
GameMapType.Tourney4,
|
|
],
|
|
};
|
|
|
|
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;
|
|
isHardNations?: boolean;
|
|
startingGold?: number;
|
|
goldMultiplier?: number;
|
|
isAlliancesDisabled?: boolean;
|
|
isPortsDisabled?: boolean;
|
|
isNukesDisabled?: boolean;
|
|
isSAMsDisabled?: boolean;
|
|
isPeaceTime?: boolean;
|
|
isWaterNukes?: boolean;
|
|
}
|
|
|
|
export interface UnitInfo {
|
|
cost: (game: Game, player: Player) => Gold;
|
|
maxHealth?: number;
|
|
damage?: number;
|
|
constructionDuration?: number;
|
|
upgradable?: boolean;
|
|
}
|
|
|
|
function unitTypeGroup<T extends readonly UnitType[]>(types: T) {
|
|
return {
|
|
types,
|
|
has(type: UnitType): type is T[number] {
|
|
return (types as readonly UnitType[]).includes(type);
|
|
},
|
|
};
|
|
}
|
|
|
|
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",
|
|
}
|
|
|
|
export const Nukes = unitTypeGroup([
|
|
UnitType.AtomBomb,
|
|
UnitType.HydrogenBomb,
|
|
UnitType.MIRVWarhead,
|
|
UnitType.MIRV,
|
|
] as const);
|
|
|
|
export const BuildableAttacks = unitTypeGroup([
|
|
UnitType.AtomBomb,
|
|
UnitType.HydrogenBomb,
|
|
UnitType.MIRV,
|
|
UnitType.Warship,
|
|
] as const);
|
|
|
|
export const Structures = unitTypeGroup([
|
|
UnitType.City,
|
|
UnitType.DefensePost,
|
|
UnitType.SAMLauncher,
|
|
UnitType.MissileSilo,
|
|
UnitType.Port,
|
|
UnitType.Factory,
|
|
] as const);
|
|
|
|
export const BuildMenus = unitTypeGroup([
|
|
...Structures.types,
|
|
...BuildableAttacks.types,
|
|
] as const);
|
|
|
|
export const PlayerBuildable = unitTypeGroup([
|
|
...BuildMenus.types,
|
|
UnitType.TransportShip,
|
|
] as const);
|
|
|
|
export type PlayerBuildableUnitType = (typeof PlayerBuildable.types)[number];
|
|
|
|
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 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;
|
|
clusteredPositions(): TileRef[];
|
|
}
|
|
|
|
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 displayName: string;
|
|
|
|
constructor(
|
|
public readonly name: string,
|
|
public readonly playerType: PlayerType,
|
|
// null if tribe.
|
|
public readonly clientID: ClientID | null,
|
|
// TODO: make player id the small id
|
|
public readonly id: PlayerID,
|
|
public readonly isLobbyCreator: boolean = false,
|
|
public readonly clanTag: string | null = null,
|
|
) {
|
|
this.displayName = formatPlayerDisplayName(this.name, this.clanTag);
|
|
}
|
|
}
|
|
|
|
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;
|
|
setRetreating(retreating: boolean): void;
|
|
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?: readonly PlayerBuildableUnitType[],
|
|
): BuildableUnit[];
|
|
canBuild(
|
|
type: UnitType,
|
|
targetTile: TileRef,
|
|
validTiles?: TileRef[] | null,
|
|
): 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
|
|
nearby(): (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;
|
|
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;
|
|
recordMotionPlan(record: MotionPlanRecord): void;
|
|
drainPackedMotionPlans(): Uint32Array | null;
|
|
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;
|
|
/**
|
|
* Returns the set of water components that `player` shares with at least one
|
|
* valid trade partner (cached). Used by nation AI for port-placement
|
|
* heuristics. `null` means no usable water body for ports.
|
|
*/
|
|
sharedWaterComponents(player: Player): Set<number> | null;
|
|
/** Incremented each time the water navigation graph is rebuilt (e.g. after nuke terrain change). */
|
|
waterGraphVersion(): number;
|
|
|
|
/** Queue a land tile for conversion to water (batched every few ticks). Tile must be unowned. */
|
|
queueWaterConversion(tile: TileRef): void;
|
|
}
|
|
|
|
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: PlayerBuildableUnitType;
|
|
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;
|
|
}
|