Files
OpenFrontIO/src/core/game/Game.ts
T

993 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 { 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",
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;
population(): number;
workers(): number;
troops(): number;
targetTroopRatio(): number;
addWorkers(toAdd: number): void;
removeWorkers(toRemove: number): void;
setTargetTroopRatio(target: number): void;
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;
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;
}
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;
}