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>( 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 type WarshipState = { state: "patrolling" | "retreating" | "docked"; patrolTile?: TileRef; retreatPort?: TileRef; isInCombat?: boolean; lastCombatTick: number; }; export type TransportShipState = { isRetreating: boolean; troops: number; }; export const AllPlayers = "AllPlayers" as const; // export type GameUpdates = Record; // Create a type that maps GameUpdateType to its corresponding update type type UpdateTypeMap = Extract; // Then use it to create the record type export type GameUpdates = { [K in GameUpdateType]: UpdateTypeMap[]; }; 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; 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 = { 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", MiddleEast = "Middle East", TaiwanStrait = "Taiwan Strait", DanishStraits = "Danish Straits", NorthwestPassage = "Northwest Passage", } export type GameMapName = keyof typeof GameMapType; export const mapCategories: Record = { 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, GameMapType.MiddleEast, GameMapType.TaiwanStrait, GameMapType.DanishStraits, GameMapType.NorthwestPassage, ], 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(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; [UnitType.SAMMissile]: Record; [UnitType.Port]: Record; [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; [UnitType.MissileSilo]: Record; [UnitType.DefensePost]: Record; [UnitType.SAMLauncher]: Record; [UnitType.City]: Record; [UnitType.MIRV]: { targetTile?: number; }; [UnitType.MIRVWarhead]: { targetTile?: number; }; } // Type helper to get params type for a specific unit type export type UnitParams = 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; warshipState(): WarshipState; updateWarshipState(update: Partial): void; transportShipState(): TransportShipState; updateTransportShipState(update: Partial): 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; } 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; borderTiles(): ReadonlySet; 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( type: T, spawnTile: TileRef, params: UnitParams, ): 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, ): Attack; outgoingAttacks(): Attack[]; incomingAttacks(): Attack[]; orderRetreat(attackID: string): void; executeRetreat(attackID: string): void; // Misc toUpdate(): PlayerUpdate | null; 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; elapsedGameSeconds(): number; // Game State ticks(): Tick; inSpawnPhase(): boolean; endSpawnPhase(): void; 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 unit(id: number): Unit | undefined; 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, ): 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 | 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 | 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; alliances: number[]; } export interface PlayerBorderTiles { borderTiles: ReadonlySet; } 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.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; }