From b56e3801078cb048cbd57192fba959ccf44bed1c Mon Sep 17 00:00:00 2001 From: Scott Anderson <662325+scottanderson@users.noreply.github.com> Date: Thu, 7 Aug 2025 19:13:42 -0400 Subject: [PATCH] Enable the `sort-keys` eslint rule (#1746) ## Description: Enable the `sort-keys` eslint rule. Fixes #1629 ## 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 - [ ] I have read and accepted the CLA agreement (only required once). --- eslint.config.js | 11 ++ src/core/ApiSchemas.ts | 1 + src/core/CosmeticSchemas.ts | 4 + src/core/CustomFlag.ts | 3 + src/core/GameRunner.ts | 14 +-- src/core/Schemas.ts | 120 ++++++++++----------- src/core/Util.ts | 25 ++--- src/core/configuration/Colors.ts | 1 + src/core/configuration/DefaultConfig.ts | 2 + src/core/configuration/PastelTheme.ts | 10 ++ src/core/configuration/PastelThemeDark.ts | 4 + src/core/execution/NukeExecution.ts | 3 +- src/core/execution/RailroadExecution.ts | 4 +- src/core/execution/SAMLauncherExecution.ts | 1 + src/core/execution/TradeShipExecution.ts | 2 +- src/core/execution/TrainExecution.ts | 2 +- src/core/execution/WarshipExecution.ts | 2 +- src/core/game/AllianceRequestImpl.ts | 6 +- src/core/game/BinaryLoaderGameMapLoader.ts | 14 +-- src/core/game/FetchGameMapLoader.ts | 2 +- src/core/game/Game.ts | 3 + src/core/game/GameImpl.ts | 54 +++++----- src/core/game/PlayerImpl.ts | 17 +-- src/core/game/RailNetworkImpl.ts | 2 + src/core/game/Railroad.ts | 4 +- src/core/game/TerrainMapLoader.ts | 2 +- src/core/game/TrainStation.ts | 4 +- src/core/game/TransportShipUtils.ts | 4 +- src/core/game/UnitGrid.ts | 3 +- src/core/game/UnitImpl.ts | 2 + src/core/pathfinding/PathFinding.ts | 2 + src/core/pathfinding/SerialAStar.ts | 3 + src/core/validations/username.ts | 7 +- src/core/worker/Worker.worker.ts | 14 +-- src/core/worker/WorkerClient.ts | 32 +++--- src/server/Archive.ts | 2 + src/server/Cloudflare.ts | 14 +-- src/server/GameManager.ts | 10 +- src/server/GameServer.ts | 52 ++++----- src/server/Logger.ts | 3 + src/server/MapPlaylist.ts | 58 +++++----- src/server/Master.ts | 12 +-- src/server/OtelResource.ts | 2 + src/server/Server.ts | 2 +- src/server/Worker.ts | 18 ++-- src/server/WorkerMetrics.ts | 6 +- src/server/jwt.ts | 5 +- tests/util/Setup.ts | 6 +- 48 files changed, 320 insertions(+), 254 deletions(-) diff --git a/eslint.config.js b/eslint.config.js index 4f309b3f9..2984c0ac7 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -50,6 +50,17 @@ export default [ // Enable rules "@typescript-eslint/prefer-nullish-coalescing": "error", eqeqeq: "error", + "sort-keys": "error", + }, + }, + { + files: [ + "**/*.config.{js,ts,jsx,tsx}", + "**/*.test.{js,ts,jsx,tsx}", + "src/client/**/*.{js,ts,jsx,tsx}", + ], + rules: { + "sort-keys": "off", }, }, ]; diff --git a/src/core/ApiSchemas.ts b/src/core/ApiSchemas.ts index 53dbcf597..e4d3f74fa 100644 --- a/src/core/ApiSchemas.ts +++ b/src/core/ApiSchemas.ts @@ -6,6 +6,7 @@ export const RefreshResponseSchema = z.object({ }); export type RefreshResponse = z.infer; +/* eslint-disable sort-keys */ export const TokenPayloadSchema = z.object({ jti: z.string(), sub: z diff --git a/src/core/CosmeticSchemas.ts b/src/core/CosmeticSchemas.ts index f4318fb5e..f2855ab18 100644 --- a/src/core/CosmeticSchemas.ts +++ b/src/core/CosmeticSchemas.ts @@ -3,8 +3,10 @@ import { RequiredPatternSchema } from "./Schemas"; export const ProductSchema = z.object({ productId: z.string(), + /* eslint-disable sort-keys */ priceId: z.string(), price: z.string(), + /* eslint-enable sort-keys */ }); const PatternSchema = z.object({ @@ -16,6 +18,7 @@ const PatternSchema = z.object({ // Schema for resources/cosmetics/cosmetics.json export const CosmeticsSchema = z.object({ patterns: z.record(z.string(), PatternSchema), + /* eslint-disable sort-keys */ flag: z .object({ layers: z.record( @@ -35,6 +38,7 @@ export const CosmeticsSchema = z.object({ ), }) .optional(), + /* eslint-enable sort-keys */ }); export type Cosmetics = z.infer; export type Pattern = z.infer; diff --git a/src/core/CustomFlag.ts b/src/core/CustomFlag.ts index 3347e5e8f..18827e599 100644 --- a/src/core/CustomFlag.ts +++ b/src/core/CustomFlag.ts @@ -2,12 +2,14 @@ import { Cosmetics } from "./CosmeticSchemas"; const ANIMATION_DURATIONS: Record = { rainbow: 4000, + /* eslint-disable sort-keys */ "bright-rainbow": 4000, "copper-glow": 3000, "silver-glow": 3000, "gold-glow": 3000, neon: 3000, lava: 6000, + /* eslint-enable sort-keys */ water: 6200, }; @@ -28,6 +30,7 @@ export function renderPlayerFlag( const code = flag.slice("!".length); const layers = code.split("_").map((segment) => { const [layerKey, colorKey] = segment.split("-"); + // eslint-disable-next-line sort-keys return { layerKey, colorKey }; }); diff --git a/src/core/GameRunner.ts b/src/core/GameRunner.ts index 2e8201240..403c353fd 100644 --- a/src/core/GameRunner.ts +++ b/src/core/GameRunner.ts @@ -165,10 +165,10 @@ export class GameRunner { updates[GameUpdateType.Tile] = []; this.callBack({ - tick: this.game.ticks(), packedTileUpdates: new BigUint64Array(packedTileUpdates), - updates: updates, playerNameViewData: this.playerViewData, + tick: this.game.ticks(), + updates: updates, }); this.isExecuting = false; } @@ -181,21 +181,21 @@ export class GameRunner { const player = this.game.player(playerID); const tile = this.game.ref(x, y); const actions = { - canAttack: player.canAttack(tile), buildableUnits: player.buildableUnits(tile), + canAttack: player.canAttack(tile), canSendEmojiAllPlayers: player.canSendEmoji(AllPlayers), } as PlayerActions; if (this.game.hasOwner(tile)) { const other = this.game.owner(tile) as Player; actions.interaction = { - sharedBorder: player.sharesBorderWith(other), - canSendEmoji: player.canSendEmoji(other), - canTarget: player.canTarget(other), - canSendAllianceRequest: player.canSendAllianceRequest(other), canBreakAlliance: player.isAlliedWith(other), canDonate: player.canDonate(other), canEmbargo: !player.hasEmbargoAgainst(other), + canSendAllianceRequest: player.canSendAllianceRequest(other), + canSendEmoji: player.canSendEmoji(other), + canTarget: player.canTarget(other), + sharedBorder: player.sharesBorderWith(other), }; const alliance = player.allianceWith(other as Player); if (alliance) { diff --git a/src/core/Schemas.ts b/src/core/Schemas.ts index e59b06d28..e96d0c17d 100644 --- a/src/core/Schemas.ts +++ b/src/core/Schemas.ts @@ -144,17 +144,17 @@ const TeamCountConfigSchema = z.union([ export type TeamCountConfig = z.infer; export const GameConfigSchema = z.object({ - gameMap: z.enum(GameMapType), - difficulty: z.enum(Difficulty), - gameType: z.enum(GameType), - gameMode: z.enum(GameMode), - disableNPCs: z.boolean(), bots: z.number().int().min(0).max(400), + difficulty: z.enum(Difficulty), + disableNPCs: z.boolean(), + disabledUnits: z.enum(UnitType).array().optional(), + gameMap: z.enum(GameMapType), + gameMode: z.enum(GameMode), + gameType: z.enum(GameType), infiniteGold: z.boolean(), infiniteTroops: z.boolean(), instantBuild: z.boolean(), maxPlayers: z.number().optional(), - disabledUnits: z.enum(UnitType).array().optional(), playerTeams: TeamCountConfigSchema.optional(), }); @@ -244,82 +244,82 @@ const BaseIntentSchema = z.object({ }); export const AllianceExtensionIntentSchema = BaseIntentSchema.extend({ - type: z.literal("allianceExtension"), recipient: ID, + type: z.literal("allianceExtension"), }); export const AttackIntentSchema = BaseIntentSchema.extend({ - type: z.literal("attack"), targetID: ID.nullable(), troops: z.number().nonnegative().nullable(), + type: z.literal("attack"), }); export const SpawnIntentSchema = BaseIntentSchema.extend({ - type: z.literal("spawn"), - name: UsernameSchema, flag: FlagSchema, + name: UsernameSchema, pattern: PatternSchema, playerType: PlayerTypeSchema, tile: z.number(), + type: z.literal("spawn"), }); export const BoatAttackIntentSchema = BaseIntentSchema.extend({ - type: z.literal("boat"), - targetID: ID.nullable(), - troops: z.number().nonnegative(), dst: z.number(), src: z.number().nullable(), + targetID: ID.nullable(), + troops: z.number().nonnegative(), + type: z.literal("boat"), }); export const AllianceRequestIntentSchema = BaseIntentSchema.extend({ - type: z.literal("allianceRequest"), recipient: ID, + type: z.literal("allianceRequest"), }); export const AllianceRequestReplyIntentSchema = BaseIntentSchema.extend({ - type: z.literal("allianceRequestReply"), - requestor: ID, // The one who made the original alliance request accept: z.boolean(), + requestor: ID, // The one who made the original alliance request + type: z.literal("allianceRequestReply"), }); export const BreakAllianceIntentSchema = BaseIntentSchema.extend({ - type: z.literal("breakAlliance"), recipient: ID, + type: z.literal("breakAlliance"), }); export const TargetPlayerIntentSchema = BaseIntentSchema.extend({ - type: z.literal("targetPlayer"), target: ID, + type: z.literal("targetPlayer"), }); export const EmojiIntentSchema = BaseIntentSchema.extend({ - type: z.literal("emoji"), - recipient: z.union([ID, z.literal(AllPlayers)]), emoji: EmojiSchema, + recipient: z.union([ID, z.literal(AllPlayers)]), + type: z.literal("emoji"), }); export const EmbargoIntentSchema = BaseIntentSchema.extend({ - type: z.literal("embargo"), - targetID: ID, action: z.union([z.literal("start"), z.literal("stop")]), + targetID: ID, + type: z.literal("embargo"), }); export const DonateGoldIntentSchema = BaseIntentSchema.extend({ - type: z.literal("donate_gold"), - recipient: ID, gold: z.bigint().nullable(), + recipient: ID, + type: z.literal("donate_gold"), }); export const DonateTroopIntentSchema = BaseIntentSchema.extend({ - type: z.literal("donate_troops"), recipient: ID, troops: z.number().nullable(), + type: z.literal("donate_troops"), }); export const BuildUnitIntentSchema = BaseIntentSchema.extend({ + tile: z.number(), type: z.literal("build_unit"), unit: z.enum(UnitType), - tile: z.number(), }); export const UpgradeStructureIntentSchema = BaseIntentSchema.extend({ @@ -329,8 +329,8 @@ export const UpgradeStructureIntentSchema = BaseIntentSchema.extend({ }); export const CancelAttackIntentSchema = BaseIntentSchema.extend({ - type: z.literal("cancel_attack"), attackID: z.string(), + type: z.literal("cancel_attack"), }); export const CancelBoatIntentSchema = BaseIntentSchema.extend({ @@ -339,9 +339,9 @@ export const CancelBoatIntentSchema = BaseIntentSchema.extend({ }); export const MoveWarshipIntentSchema = BaseIntentSchema.extend({ + tile: z.number(), type: z.literal("move_warship"), unitId: z.number(), - tile: z.number(), }); export const DeleteUnitIntentSchema = BaseIntentSchema.extend({ @@ -350,20 +350,20 @@ export const DeleteUnitIntentSchema = BaseIntentSchema.extend({ }); export const QuickChatIntentSchema = BaseIntentSchema.extend({ - type: z.literal("quick_chat"), - recipient: ID, quickChatKey: QuickChatKeySchema, + recipient: ID, target: ID.optional(), + type: z.literal("quick_chat"), }); export const MarkDisconnectedIntentSchema = BaseIntentSchema.extend({ - type: z.literal("mark_disconnected"), isDisconnected: z.boolean(), + type: z.literal("mark_disconnected"), }); export const KickPlayerIntentSchema = BaseIntentSchema.extend({ - type: z.literal("kick_player"), target: ID, + type: z.literal("kick_player"), }); const IntentSchema = z.discriminatedUnion("type", [ @@ -395,22 +395,22 @@ const IntentSchema = z.discriminatedUnion("type", [ // export const TurnSchema = z.object({ - turnNumber: z.number(), - intents: IntentSchema.array(), // The hash of the game state at the end of the turn. hash: z.number().nullable().optional(), + intents: IntentSchema.array(), + turnNumber: z.number(), }); export const PlayerSchema = z.object({ clientID: ID, - username: UsernameSchema, flag: FlagSchema, pattern: PatternSchema, + username: UsernameSchema, }); export const GameStartInfoSchema = z.object({ - gameID: ID, config: GameConfigSchema, + gameID: ID, players: PlayerSchema.array(), }); @@ -427,8 +427,8 @@ export type Winner = z.infer; // export const ServerTurnMessageSchema = z.object({ - type: z.literal("turn"), turn: TurnSchema, + type: z.literal("turn"), }); export const ServerPingMessageSchema = z.object({ @@ -436,30 +436,30 @@ export const ServerPingMessageSchema = z.object({ }); export const ServerPrestartMessageSchema = z.object({ - type: z.literal("prestart"), gameMap: z.nativeEnum(GameMapType), + type: z.literal("prestart"), }); export const ServerStartGameMessageSchema = z.object({ - type: z.literal("start"), + gameStartInfo: GameStartInfoSchema, // Turns the client missed if they are late to the game. turns: TurnSchema.array(), - gameStartInfo: GameStartInfoSchema, + type: z.literal("start"), }); export const ServerDesyncSchema = z.object({ - type: z.literal("desync"), - turn: z.number(), - correctHash: z.number().nullable(), clientsWithCorrectHash: z.number(), + correctHash: z.number().nullable(), totalActiveClients: z.number(), + turn: z.number(), + type: z.literal("desync"), yourHash: z.number().optional(), }); export const ServerErrorSchema = z.object({ - type: z.literal("error"), error: z.string(), message: z.string().optional(), + type: z.literal("error"), }); export const ServerMessageSchema = z.discriminatedUnion("type", [ @@ -476,21 +476,21 @@ export const ServerMessageSchema = z.discriminatedUnion("type", [ // export const ClientSendWinnerSchema = z.object({ + allPlayersStats: AllPlayersStatsSchema, type: z.literal("winner"), winner: WinnerSchema, - allPlayersStats: AllPlayersStatsSchema, }); export const ClientHashSchema = z.object({ - type: z.literal("hash"), hash: z.number(), turnNumber: z.number(), + type: z.literal("hash"), }); export const ClientLogMessageSchema = z.object({ - type: z.literal("log"), - severity: z.enum(LogSeverity), log: ID, + severity: z.enum(LogSeverity), + type: z.literal("log"), }); export const ClientPingMessageSchema = z.object({ @@ -498,20 +498,20 @@ export const ClientPingMessageSchema = z.object({ }); export const ClientIntentMessageSchema = z.object({ - type: z.literal("intent"), intent: IntentSchema, + type: z.literal("intent"), }); // WARNING: never send this message to clients. export const ClientJoinMessageSchema = z.object({ - type: z.literal("join"), clientID: ID, - token: TokenSchema, // WARNING: PII + flag: FlagSchema, gameID: ID, lastTurn: z.number(), // The last turn the client saw. - username: UsernameSchema, - flag: FlagSchema, pattern: PatternSchema, + token: TokenSchema, // WARNING: PII + type: z.literal("join"), + username: UsernameSchema, }); export const ClientMessageSchema = z.discriminatedUnion("type", [ @@ -534,11 +534,11 @@ export const PlayerRecordSchema = PlayerSchema.extend({ export type PlayerRecord = z.infer; export const GameEndInfoSchema = GameStartInfoSchema.extend({ + duration: z.number().nonnegative(), + end: z.number(), + num_turns: z.number(), players: PlayerRecordSchema.array(), start: z.number(), - end: z.number(), - duration: z.number().nonnegative(), - num_turns: z.number(), winner: WinnerSchema, }); export type GameEndInfo = z.infer; @@ -546,11 +546,11 @@ export type GameEndInfo = z.infer; const GitCommitSchema = z.string().regex(/^[0-9a-fA-F]{40}$/); export const AnalyticsRecordSchema = z.object({ - info: GameEndInfoSchema, - version: z.literal("v0.0.2"), - gitCommit: GitCommitSchema, - subdomain: z.string(), domain: z.string(), + gitCommit: GitCommitSchema, + info: GameEndInfoSchema, + subdomain: z.string(), + version: z.literal("v0.0.2"), }); export type AnalyticsRecord = z.infer; diff --git a/src/core/Util.ts b/src/core/Util.ts index 6133b9a18..543d8b442 100644 --- a/src/core/Util.ts +++ b/src/core/Util.ts @@ -88,6 +88,7 @@ export function calculateBoundingBox( maxY = Math.max(maxY, cell.y); }); + // eslint-disable-next-line sort-keys return { min: new Cell(minX, minY), max: new Cell(maxX, maxY) }; } @@ -143,10 +144,10 @@ export function sanitize(name: string): string { export function onlyImages(html: string) { return DOMPurify.sanitize(html, { - ALLOWED_TAGS: ["span", "img"], - ALLOWED_ATTR: ["src", "alt", "class", "style"], - ALLOWED_URI_REGEXP: /^https:\/\/cdn\.jsdelivr\.net\/gh\/twitter\/twemoji/, ADD_ATTR: ["style"], + ALLOWED_ATTR: ["src", "alt", "class", "style"], + ALLOWED_TAGS: ["span", "img"], + ALLOWED_URI_REGEXP: /^https:\/\/cdn\.jsdelivr\.net\/gh\/twitter\/twemoji/, }); } @@ -171,21 +172,21 @@ export function createGameRecord( (t) => t.intents.length !== 0 || t.hash !== undefined, ); const record: GameRecord = { + domain, + gitCommit, info: { - gameID, config, + duration, + end, + gameID, + num_turns, players, start, - end, - duration, - num_turns, winner, }, - version, - gitCommit, subdomain, - domain, turns, + version, }; return record; } @@ -197,8 +198,8 @@ export function decompressGameRecord(gameRecord: GameRecord) { while (lastTurnNum < turn.turnNumber - 1) { lastTurnNum++; turns.push({ - turnNumber: lastTurnNum, intents: [], + turnNumber: lastTurnNum, }); } turns.push(turn); @@ -207,8 +208,8 @@ export function decompressGameRecord(gameRecord: GameRecord) { const turnLength = turns.length; for (let i = turnLength; i < gameRecord.info.num_turns; i++) { turns.push({ - turnNumber: i, intents: [], + turnNumber: i, }); } gameRecord.turns = turns; diff --git a/src/core/configuration/Colors.ts b/src/core/configuration/Colors.ts index 1a0ccabd9..75441530a 100644 --- a/src/core/configuration/Colors.ts +++ b/src/core/configuration/Colors.ts @@ -5,6 +5,7 @@ import lchPlugin from "colord/plugins/lch"; extend([lchPlugin]); extend([labPlugin]); +/* eslint-disable sort-keys */ export const red = colord({ h: 0, s: 82, l: 56 }); export const blue = colord({ h: 224, s: 100, l: 58 }); export const teal = colord({ h: 172, s: 66, l: 50 }); diff --git a/src/core/configuration/DefaultConfig.ts b/src/core/configuration/DefaultConfig.ts index c85302cf5..fee1776ec 100644 --- a/src/core/configuration/DefaultConfig.ts +++ b/src/core/configuration/DefaultConfig.ts @@ -367,6 +367,7 @@ export class DefaultConfig implements Config { return 1_000_000; } + /* eslint-disable sort-keys */ unitInfo(type: UnitType): UnitInfo { switch (type) { case UnitType.TransportShip: @@ -488,6 +489,7 @@ export class DefaultConfig implements Config { assertNever(type); } } + /* eslint-enable sort-keys */ private costWrapper( type: UnitType, diff --git a/src/core/configuration/PastelTheme.ts b/src/core/configuration/PastelTheme.ts index 2318d6ac3..dfcf23640 100644 --- a/src/core/configuration/PastelTheme.ts +++ b/src/core/configuration/PastelTheme.ts @@ -17,6 +17,7 @@ export class PastelTheme implements Theme { private teamColorAllocator = new ColorAllocator(humanColors, fallbackColors); private nationColorAllocator = new ColorAllocator(nationColors, nationColors); + /* eslint-disable sort-keys */ private background = colord({ r: 60, g: 60, b: 60 }); private shore = colord({ r: 204, g: 203, b: 158 }); private falloutColors = [ @@ -34,6 +35,7 @@ export class PastelTheme implements Theme { private _enemyColor = colord({ r: 255, g: 0, b: 0 }); private _spawnHighlightColor = colord({ r: 255, g: 213, b: 79 }); + /* eslint-enable sort-keys */ teamColor(team: Team): Colord { return this.teamColorAllocator.assignTeamColor(team); @@ -59,20 +61,24 @@ export class PastelTheme implements Theme { specialBuildingColor(player: PlayerView): Colord { const tc = this.territoryColor(player).rgba; + /* eslint-disable sort-keys */ return colord({ r: Math.max(tc.r - 50, 0), g: Math.max(tc.g - 50, 0), b: Math.max(tc.b - 50, 0), }); + /* eslint-enable sort-keys */ } railroadColor(player: PlayerView): Colord { const tc = this.territoryColor(player).rgba; + /* eslint-disable sort-keys */ const color = colord({ r: Math.max(tc.r - 10, 0), g: Math.max(tc.g - 10, 0), b: Math.max(tc.b - 10, 0), }); + /* eslint-enable sort-keys */ return color; } @@ -81,16 +87,19 @@ export class PastelTheme implements Theme { return this.borderColorCache.get(player.id())!; } const tc = this.territoryColor(player).rgba; + /* eslint-disable sort-keys */ const color = colord({ r: Math.max(tc.r - 40, 0), g: Math.max(tc.g - 40, 0), b: Math.max(tc.b - 40, 0), }); + /* eslint-enable sort-keys */ this.borderColorCache.set(player.id(), color); return color; } + /* eslint-disable sort-keys */ defendedBorderColors(player: PlayerView): { light: Colord; dark: Colord } { return { light: this.territoryColor(player).darken(0.2), @@ -140,6 +149,7 @@ export class PastelTheme implements Theme { }); } } + /* eslint-enable sort-keys */ backgroundColor(): Colord { return this.background; diff --git a/src/core/configuration/PastelThemeDark.ts b/src/core/configuration/PastelThemeDark.ts index 73e1e88e7..c24016fb6 100644 --- a/src/core/configuration/PastelThemeDark.ts +++ b/src/core/configuration/PastelThemeDark.ts @@ -17,6 +17,7 @@ export class PastelThemeDark implements Theme { private teamColorAllocator = new ColorAllocator(humanColors, fallbackColors); private nationColorAllocator = new ColorAllocator(nationColors, nationColors); + /* eslint-disable sort-keys */ private background = colord({ r: 0, g: 0, b: 0 }); private shore = colord({ r: 134, g: 133, b: 88 }); private falloutColors = [ @@ -34,6 +35,7 @@ export class PastelThemeDark implements Theme { private _enemyColor = colord({ r: 255, g: 0, b: 0 }); private _spawnHighlightColor = colord({ r: 255, g: 213, b: 79 }); + /* eslint-enable sort-keys */ teamColor(team: Team): Colord { return this.teamColorAllocator.assignTeamColor(team); @@ -57,6 +59,7 @@ export class PastelThemeDark implements Theme { return player.type() === PlayerType.Human ? "#ffffff" : "#e6e6e6"; } + /* eslint-disable sort-keys */ specialBuildingColor(player: PlayerView): Colord { const tc = this.territoryColor(player).rgba; return colord({ @@ -142,6 +145,7 @@ export class PastelThemeDark implements Theme { }); } } + /* eslint-enable sort-keys */ backgroundColor(): Colord { return this.background; diff --git a/src/core/execution/NukeExecution.ts b/src/core/execution/NukeExecution.ts index b7ac4a32f..739932a5c 100644 --- a/src/core/execution/NukeExecution.ts +++ b/src/core/execution/NukeExecution.ts @@ -184,9 +184,10 @@ export class NukeExecution implements Execution { this.mg.config().defaultNukeTargetableRange() ** 2; const allTiles: TileRef[] = this.pathFinder.allTiles(); for (const tile of allTiles) { + const targetable = this.isTargetable(target, tile, targetRangeSquared); trajectoryTiles.push({ + targetable, tile, - targetable: this.isTargetable(target, tile, targetRangeSquared), }); } diff --git a/src/core/execution/RailroadExecution.ts b/src/core/execution/RailroadExecution.ts index 55f88760d..97f28f744 100644 --- a/src/core/execution/RailroadExecution.ts +++ b/src/core/execution/RailroadExecution.ts @@ -18,6 +18,7 @@ export class RailroadExecution implements Execution { return this.active; } + /* eslint-disable sort-keys */ init(mg: Game, ticks: number): void { this.mg = mg; const tiles = this.railRoad.tiles; @@ -48,6 +49,7 @@ export class RailroadExecution implements Execution { : RailType.VERTICAL, }); } + /* eslint-enable sort-keys */ private computeExtremityDirection(tile: TileRef, next: TileRef): RailType { const x = this.mg.x(tile); @@ -143,9 +145,9 @@ export class RailroadExecution implements Execution { } if (updatedRailTiles) { this.mg.addUpdate({ - type: GameUpdateType.RailroadEvent, isActive: true, railTiles: updatedRailTiles, + type: GameUpdateType.RailroadEvent, }); } } diff --git a/src/core/execution/SAMLauncherExecution.ts b/src/core/execution/SAMLauncherExecution.ts index 15891db07..4fb2765fe 100644 --- a/src/core/execution/SAMLauncherExecution.ts +++ b/src/core/execution/SAMLauncherExecution.ts @@ -98,6 +98,7 @@ class SAMTargetingSystem { } const interceptionTile = this.computeInterceptionTile(nuke.unit); if (interceptionTile !== undefined) { + // eslint-disable-next-line sort-keys targets.push({ unit: nuke.unit, tile: interceptionTile }); } else { // Store unreachable nukes in order to prevent useless interception computation diff --git a/src/core/execution/TradeShipExecution.ts b/src/core/execution/TradeShipExecution.ts index 482fb47de..3265334e9 100644 --- a/src/core/execution/TradeShipExecution.ts +++ b/src/core/execution/TradeShipExecution.ts @@ -43,8 +43,8 @@ export class TradeShipExecution implements Execution { return; } this.tradeShip = this.origOwner.buildUnit(UnitType.TradeShip, spawn, { - targetUnit: this._dstPort, lastSetSafeFromPirates: ticks, + targetUnit: this._dstPort, }); this.mg.stats().boatSendTrade(this.origOwner, this._dstPort.owner()); } diff --git a/src/core/execution/TrainExecution.ts b/src/core/execution/TrainExecution.ts index 5e165067a..34a8634e7 100644 --- a/src/core/execution/TrainExecution.ts +++ b/src/core/execution/TrainExecution.ts @@ -119,8 +119,8 @@ export class TrainExecution implements Execution { for (let i = 0; i < this.numCars; i++) { this.cars.push( this.player.buildUnit(UnitType.Train, tile, { - trainType: TrainType.Carriage, loaded: this.hasCargo, + trainType: TrainType.Carriage, }), ); } diff --git a/src/core/execution/WarshipExecution.ts b/src/core/execution/WarshipExecution.ts index f573a8a47..9e0a71309 100644 --- a/src/core/execution/WarshipExecution.ts +++ b/src/core/execution/WarshipExecution.ts @@ -113,7 +113,7 @@ export class WarshipExecution implements Execution { continue; } } - potentialTargets.push({ unit: unit, distSquared }); + potentialTargets.push({ distSquared, unit }); } return potentialTargets.sort((a, b) => { diff --git a/src/core/game/AllianceRequestImpl.ts b/src/core/game/AllianceRequestImpl.ts index 6d51acdb3..c1911544c 100644 --- a/src/core/game/AllianceRequestImpl.ts +++ b/src/core/game/AllianceRequestImpl.ts @@ -31,10 +31,10 @@ export class AllianceRequestImpl implements AllianceRequest { toUpdate(): AllianceRequestUpdate { return { - type: GameUpdateType.AllianceRequest, - requestorID: this.requestor_.smallID(), - recipientID: this.recipient_.smallID(), createdAt: this.tickCreated, + recipientID: this.recipient_.smallID(), + requestorID: this.requestor_.smallID(), + type: GameUpdateType.AllianceRequest, }; } } diff --git a/src/core/game/BinaryLoaderGameMapLoader.ts b/src/core/game/BinaryLoaderGameMapLoader.ts index 31e058026..47dccca7b 100644 --- a/src/core/game/BinaryLoaderGameMapLoader.ts +++ b/src/core/game/BinaryLoaderGameMapLoader.ts @@ -37,6 +37,13 @@ export class BinaryLoaderGameMapLoader implements GameMapLoader { const fileName = key?.toLowerCase(); const mapData = { + manifest: this.createLazyLoader(() => + ( + import( + `../../../resources/maps/${fileName}/manifest.json` + ) as Promise + ).then((m) => m.default), + ), mapBin: this.createLazyLoader(() => ( import( @@ -51,13 +58,6 @@ export class BinaryLoaderGameMapLoader implements GameMapLoader { ) as Promise ).then((m) => this.toUInt8Array(m.default)), ), - manifest: this.createLazyLoader(() => - ( - import( - `../../../resources/maps/${fileName}/manifest.json` - ) as Promise - ).then((m) => m.default), - ), webpPath: this.createLazyLoader(() => ( import( diff --git a/src/core/game/FetchGameMapLoader.ts b/src/core/game/FetchGameMapLoader.ts index cfde1360f..8e218b706 100644 --- a/src/core/game/FetchGameMapLoader.ts +++ b/src/core/game/FetchGameMapLoader.ts @@ -27,10 +27,10 @@ export class FetchGameMapLoader implements GameMapLoader { } const mapData = { + manifest: () => this.loadJsonFromUrl(this.url(fileName, "manifest.json")), mapBin: () => this.loadBinaryFromUrl(this.url(fileName, "map.bin")), miniMapBin: () => this.loadBinaryFromUrl(this.url(fileName, "mini_map.bin")), - manifest: () => this.loadJsonFromUrl(this.url(fileName, "manifest.json")), webpPath: async () => this.url(fileName, "thumbnail.webp"), } satisfies MapData; diff --git a/src/core/game/Game.ts b/src/core/game/Game.ts index 1c2bc8065..e8c6d12b9 100644 --- a/src/core/game/Game.ts +++ b/src/core/game/Game.ts @@ -44,6 +44,7 @@ export const Duos = "Duos" as const; export const Trios = "Trios" as const; export const Quads = "Quads" as const; +/* eslint-disable sort-keys */ export const ColoredTeams: Record = { Red: "Red", Blue: "Blue", @@ -54,6 +55,7 @@ export const ColoredTeams: Record = { Green: "Green", Bot: "Bot", } as const; +/* eslint-enable sort-keys */ export enum GameMapType { World = "World", @@ -118,6 +120,7 @@ export const mapCategories: Record = { GameMapType.Italia, GameMapType.Yenisei, ], + // eslint-disable-next-line sort-keys fantasy: [ GameMapType.Pangaea, GameMapType.Pluto, diff --git a/src/core/game/GameImpl.ts b/src/core/game/GameImpl.ts index b85073e75..02ec95eb5 100644 --- a/src/core/game/GameImpl.ts +++ b/src/core/game/GameImpl.ts @@ -292,9 +292,9 @@ export class GameImpl implements Game { recipient.endTemporaryEmbargo(requestor.id()); this.addUpdate({ - type: GameUpdateType.AllianceRequestReply, - request: request.toUpdate(), accepted: true, + request: request.toUpdate(), + type: GameUpdateType.AllianceRequestReply, }); } @@ -306,9 +306,9 @@ export class GameImpl implements Game { request, ); this.addUpdate({ - type: GameUpdateType.AllianceRequestReply, - request: request.toUpdate(), accepted: false, + request: request.toUpdate(), + type: GameUpdateType.AllianceRequestReply, }); } @@ -358,9 +358,9 @@ export class GameImpl implements Game { } if (this.ticks() % 10 === 0) { this.addUpdate({ - type: GameUpdateType.Hash, - tick: this.ticks(), hash: this.hash(), + tick: this.ticks(), + type: GameUpdateType.Hash, }); } this._ticks++; @@ -573,9 +573,9 @@ export class GameImpl implements Game { target(targeter: Player, target: Player) { this.addUpdate({ - type: GameUpdateType.TargetPlayer, playerID: targeter.smallID(), targetID: target.smallID(), + type: GameUpdateType.TargetPlayer, }); } @@ -604,9 +604,9 @@ export class GameImpl implements Game { } this.alliances_ = this.alliances_.filter((a) => a !== alliances[0]); this.addUpdate({ - type: GameUpdateType.BrokeAlliance, - traitorID: breaker.smallID(), betrayedID: other.smallID(), + traitorID: breaker.smallID(), + type: GameUpdateType.BrokeAlliance, }); } @@ -623,24 +623,24 @@ export class GameImpl implements Game { } this.alliances_ = this.alliances_.filter((a) => a !== alliances[0]); this.addUpdate({ - type: GameUpdateType.AllianceExpired, player1ID: alliance.requestor().smallID(), player2ID: alliance.recipient().smallID(), + type: GameUpdateType.AllianceExpired, }); } sendEmojiUpdate(msg: EmojiMessage): void { this.addUpdate({ - type: GameUpdateType.Emoji, emoji: msg, + type: GameUpdateType.Emoji, }); } setWinner(winner: Player | Team, allPlayersStats: AllPlayersStats): void { this.addUpdate({ + allPlayersStats, type: GameUpdateType.Win, winner: this.makeWinner(winner), - allPlayersStats, }); } @@ -683,12 +683,12 @@ export class GameImpl implements Game { id = this.player(playerID).smallID(); } this.addUpdate({ - type: GameUpdateType.DisplayEvent, + goldAmount, + message, messageType: type, - message: message, + params, playerID: id, - goldAmount: goldAmount, - params: params, + type: GameUpdateType.DisplayEvent, }); } @@ -705,13 +705,13 @@ export class GameImpl implements Game { id = this.player(playerID).smallID(); } this.addUpdate({ - type: GameUpdateType.DisplayChatEvent, - key: message, - category: category, - target: target, - playerID: id, + category, isFrom, - recipient: recipient, + key: message, + playerID: id, + recipient, + target, + type: GameUpdateType.DisplayChatEvent, }); } @@ -724,11 +724,11 @@ export class GameImpl implements Game { const id = this.player(playerID).smallID(); this.addUpdate({ - type: GameUpdateType.UnitIncoming, - unitID: unitID, - message: message, + message, messageType: type, playerID: id, + type: GameUpdateType.UnitIncoming, + unitID, }); } @@ -889,10 +889,10 @@ export class GameImpl implements Game { conqueror.addGold(gold); conquered.removeGold(gold); this.addUpdate({ - type: GameUpdateType.ConquestEvent, - conquerorId: conqueror.id(), conqueredId: conquered.id(), + conquerorId: conqueror.id(), gold, + type: GameUpdateType.ConquestEvent, }); // Record stats diff --git a/src/core/game/PlayerImpl.ts b/src/core/game/PlayerImpl.ts index 6e038e455..3f83fee22 100644 --- a/src/core/game/PlayerImpl.ts +++ b/src/core/game/PlayerImpl.ts @@ -126,6 +126,7 @@ export class PlayerImpl implements Player { ); const stats = this.mg.stats().getPlayerStats(this); + /* eslint-disable sort-keys */ return { type: GameUpdateType.Player, clientID: this.clientID(), @@ -176,6 +177,7 @@ export class PlayerImpl implements Player { hasSpawned: this.hasSpawned(), betrayals: stats?.betrayals, }; + /* eslint-enable sort-keys */ } smallID(): number { @@ -508,6 +510,7 @@ export class PlayerImpl implements Player { } target(other: Player): void { + // eslint-disable-next-line sort-keys this.targets_.push({ tick: this.mg.ticks(), target: other }); this.mg.target(this, other); } @@ -533,10 +536,10 @@ export class PlayerImpl implements Player { throw Error(`Cannot send emoji to oneself: ${this}`); } const msg: EmojiMessage = { - message: emoji, - senderID: this.smallID(), - recipientID: recipient === AllPlayers ? recipient : recipient.smallID(), createdAt: this.mg.ticks(), + message: emoji, + recipientID: recipient === AllPlayers ? recipient : recipient.smallID(), + senderID: this.smallID(), }; this.outgoingEmojis_.push(msg); this.mg.sendEmojiUpdate(msg); @@ -716,11 +719,11 @@ export class PlayerImpl implements Player { this._gold += toAdd; if (tile) { this.mg.addUpdate({ - type: GameUpdateType.BonusEvent, + gold: Number(toAdd), player: this.id(), tile, - gold: Number(toAdd), troops: 0, + type: GameUpdateType.BonusEvent, }); } } @@ -837,12 +840,12 @@ export class PlayerImpl implements Player { } } return { - type: u, canBuild: this.mg.inSpawnPhase() ? false : this.canBuild(u, tile, validTiles), canUpgrade: canUpgrade, cost: this.mg.config().unitInfo(u).cost(this), + type: u, } as BuildableUnit; }); } @@ -1042,13 +1045,13 @@ export class PlayerImpl implements Player { public playerProfile(): PlayerProfile { const rel = { + alliances: this.alliances().map((a) => a.other(this).smallID()), relations: Object.fromEntries( this.allRelationsSorted().map(({ player, relation }) => [ player.smallID(), relation, ]), ), - alliances: this.alliances().map((a) => a.other(this).smallID()), }; return rel; } diff --git a/src/core/game/RailNetworkImpl.ts b/src/core/game/RailNetworkImpl.ts index ca1a3319e..4d054f911 100644 --- a/src/core/game/RailNetworkImpl.ts +++ b/src/core/game/RailNetworkImpl.ts @@ -216,6 +216,7 @@ export class RailNetworkImpl implements RailNetwork { const visited = new Set(); const queue: Array<{ station: TrainStation; distance: number }> = [ + // eslint-disable-next-line sort-keys { station: start, distance: 0 }, ]; @@ -229,6 +230,7 @@ export class RailNetworkImpl implements RailNetwork { for (const neighbor of station.neighbors()) { if (neighbor === dest) return distance + 1; if (!visited.has(neighbor)) { + // eslint-disable-next-line sort-keys queue.push({ station: neighbor, distance: distance + 1 }); } } diff --git a/src/core/game/Railroad.ts b/src/core/game/Railroad.ts index 2a271055d..fb96833eb 100644 --- a/src/core/game/Railroad.ts +++ b/src/core/game/Railroad.ts @@ -12,13 +12,13 @@ export class Railroad { delete(game: Game) { const railTiles: RailTile[] = this.tiles.map((tile) => ({ - tile, railType: RailType.VERTICAL, + tile, })); game.addUpdate({ - type: GameUpdateType.RailroadEvent, isActive: false, railTiles, + type: GameUpdateType.RailroadEvent, }); this.from.getRailroads().delete(this); this.to.getRailroads().delete(this); diff --git a/src/core/game/TerrainMapLoader.ts b/src/core/game/TerrainMapLoader.ts index 766007ed7..f59799b38 100644 --- a/src/core/game/TerrainMapLoader.ts +++ b/src/core/game/TerrainMapLoader.ts @@ -48,8 +48,8 @@ export async function loadTerrainMap( await mapFiles.miniMapBin(), ); const result = { - manifest: await mapFiles.manifest(), gameMap: gameMap, + manifest: await mapFiles.manifest(), miniGameMap: miniGameMap, }; loadedMaps.set(map, result); diff --git a/src/core/game/TrainStation.ts b/src/core/game/TrainStation.ts index 8eb3bacee..458ba2785 100644 --- a/src/core/game/TrainStation.ts +++ b/src/core/game/TrainStation.ts @@ -119,13 +119,13 @@ export class TrainStation { ); if (toRemove) { const railTiles: RailTile[] = toRemove.tiles.map((tile) => ({ - tile, railType: RailType.VERTICAL, + tile, })); this.mg.addUpdate({ - type: GameUpdateType.RailroadEvent, isActive: false, railTiles, + type: GameUpdateType.RailroadEvent, }); this.railroads.delete(toRemove); } diff --git a/src/core/game/TransportShipUtils.ts b/src/core/game/TransportShipUtils.ts index b457ad94a..0e22eafdd 100644 --- a/src/core/game/TransportShipUtils.ts +++ b/src/core/game/TransportShipUtils.ts @@ -183,10 +183,10 @@ export function candidateShoreTiles( let bestByManhattan: TileRef | null = null; const extremumTiles: Record = { - minX: null, - minY: null, maxX: null, maxY: null, + minX: null, + minY: null, }; const borderShoreTiles = Array.from(player.borderTiles()).filter((t) => diff --git a/src/core/game/UnitGrid.ts b/src/core/game/UnitGrid.ts index 15617c752..a60fadf53 100644 --- a/src/core/game/UnitGrid.ts +++ b/src/core/game/UnitGrid.ts @@ -113,7 +113,7 @@ export class UnitGrid { gridY + Math.ceil((range - (cellSize - (y % cellSize))) / cellSize), ); - return { startGridX, endGridX, startGridY, endGridY }; + return { endGridX, endGridY, startGridX, startGridY }; } private squaredDistanceFromTile( @@ -154,6 +154,7 @@ export class UnitGrid { if (!unit.isActive()) continue; const distSquared = this.squaredDistanceFromTile(unit, tile); if (distSquared > rangeSquared) continue; + // eslint-disable-next-line sort-keys const value = { unit, distSquared }; if (predicate !== undefined && !predicate(value)) continue; nearby.push(value); diff --git a/src/core/game/UnitImpl.ts b/src/core/game/UnitImpl.ts index 899e56d79..c9c61b562 100644 --- a/src/core/game/UnitImpl.ts +++ b/src/core/game/UnitImpl.ts @@ -114,6 +114,7 @@ export class UnitImpl implements Unit { return this._id; } + /* eslint-disable sort-keys */ toUpdate(): UnitUpdate { return { type: GameUpdateType.Unit, @@ -139,6 +140,7 @@ export class UnitImpl implements Unit { loaded: this._loaded, }; } + /* eslint-enable sort-keys */ type(): UnitType { return this._type; diff --git a/src/core/pathfinding/PathFinding.ts b/src/core/pathfinding/PathFinding.ts index 8d5193ed4..9a34e3316 100644 --- a/src/core/pathfinding/PathFinding.ts +++ b/src/core/pathfinding/PathFinding.ts @@ -148,6 +148,7 @@ export class PathFinder { } if (this.game.manhattanDist(curr, dst) < dist) { + // eslint-disable-next-line sort-keys return { type: PathFindResultType.Completed, node: curr }; } @@ -164,6 +165,7 @@ export class PathFinder { if (tile === undefined) { throw new Error("missing tile"); } + // eslint-disable-next-line sort-keys return { type: PathFindResultType.NextTile, node: tile }; } } diff --git a/src/core/pathfinding/SerialAStar.ts b/src/core/pathfinding/SerialAStar.ts index 5e3e2c61f..fdb138c82 100644 --- a/src/core/pathfinding/SerialAStar.ts +++ b/src/core/pathfinding/SerialAStar.ts @@ -49,6 +49,7 @@ export class SerialAStar implements AStar { this.fwdGScore.set(startPoint, 0); this.fwdOpenSet.add({ tile: startPoint, + // eslint-disable-next-line sort-keys fScore: this.heuristic(startPoint, dst), }); }); @@ -57,6 +58,7 @@ export class SerialAStar implements AStar { this.bwdGScore.set(dst, 0); this.bwdOpenSet.add({ tile: dst, + // eslint-disable-next-line sort-keys fScore: this.heuristic(dst, this.findClosestSource(dst)), }); } @@ -145,6 +147,7 @@ export class SerialAStar implements AStar { const fScore = totalG + this.heuristic(neighbor, isForward ? this.dst : this.closestSource); + // eslint-disable-next-line sort-keys openSet.add({ tile: neighbor, fScore: fScore }); } } diff --git a/src/core/validations/username.ts b/src/core/validations/username.ts index a7fe4f9dd..a5abddef7 100644 --- a/src/core/validations/username.ts +++ b/src/core/validations/username.ts @@ -50,33 +50,34 @@ export function validateUsername(username: string): { error?: string; } { if (typeof username !== "string") { + // eslint-disable-next-line sort-keys return { isValid: false, error: translateText("username.not_string") }; } if (username.length < MIN_USERNAME_LENGTH) { return { - isValid: false, error: translateText("username.too_short", { min: MIN_USERNAME_LENGTH, }), + isValid: false, }; } if (username.length > MAX_USERNAME_LENGTH) { return { - isValid: false, error: translateText("username.too_long", { max: MAX_USERNAME_LENGTH, }), + isValid: false, }; } if (!validPattern.test(username)) { return { - isValid: false, error: translateText("username.invalid_chars", { max: MAX_USERNAME_LENGTH, }), + isValid: false, }; } diff --git a/src/core/worker/Worker.worker.ts b/src/core/worker/Worker.worker.ts index 1014968fb..866b70834 100644 --- a/src/core/worker/Worker.worker.ts +++ b/src/core/worker/Worker.worker.ts @@ -23,8 +23,8 @@ function gameUpdate(gu: GameUpdateViewData | ErrorUpdate) { return; } sendMessage({ - type: "game_update", gameUpdate: gu, + type: "game_update", }); } @@ -48,8 +48,8 @@ ctx.addEventListener("message", async (e: MessageEvent) => { gameUpdate, ).then((gr) => { sendMessage({ - type: "initialized", id: message.id, + type: "initialized", } as InitializedMessage); return gr; }); @@ -85,9 +85,9 @@ ctx.addEventListener("message", async (e: MessageEvent) => { message.y, ); sendMessage({ - type: "player_actions_result", id: message.id, result: actions, + type: "player_actions_result", } as PlayerActionsResultMessage); } catch (error) { console.error("Failed to check borders:", error); @@ -102,9 +102,9 @@ ctx.addEventListener("message", async (e: MessageEvent) => { try { const profile = (await gameRunner).playerProfile(message.playerID); sendMessage({ - type: "player_profile_result", id: message.id, result: profile, + type: "player_profile_result", } as PlayerProfileResultMessage); } catch (error) { console.error("Failed to check borders:", error); @@ -121,9 +121,9 @@ ctx.addEventListener("message", async (e: MessageEvent) => { message.playerID, ); sendMessage({ - type: "player_border_tiles_result", id: message.id, result: borderTiles, + type: "player_border_tiles_result", } as PlayerBorderTilesResultMessage); } catch (error) { console.error("Failed to get border tiles:", error); @@ -141,8 +141,8 @@ ctx.addEventListener("message", async (e: MessageEvent) => { message.attackID, ); sendMessage({ - type: "attack_average_position_result", id: message.id, + type: "attack_average_position_result", x: averagePosition ? averagePosition.x : null, y: averagePosition ? averagePosition.y : null, } as AttackAveragePositionResultMessage); @@ -162,9 +162,9 @@ ctx.addEventListener("message", async (e: MessageEvent) => { message.targetTile, ); sendMessage({ - type: "transport_ship_spawn_result", id: message.id, result: spawnTile, + type: "transport_ship_spawn_result", } as TransportShipSpawnResultMessage); } catch (error) { console.error("Failed to spawn transport ship:", error); diff --git a/src/core/worker/WorkerClient.ts b/src/core/worker/WorkerClient.ts index 10a5f28c6..4398092fe 100644 --- a/src/core/worker/WorkerClient.ts +++ b/src/core/worker/WorkerClient.ts @@ -66,10 +66,10 @@ export class WorkerClient { }); this.worker.postMessage({ - type: "init", - id: messageId, - gameStartInfo: this.gameStartInfo, clientID: this.clientID, + gameStartInfo: this.gameStartInfo, + id: messageId, + type: "init", }); // Add timeout for initialization @@ -95,8 +95,8 @@ export class WorkerClient { } this.worker.postMessage({ - type: "turn", turn, + type: "turn", }); } @@ -125,9 +125,9 @@ export class WorkerClient { }); this.worker.postMessage({ - type: "player_profile", id: messageId, - playerID: playerID, + playerID, + type: "player_profile", }); }); } @@ -151,9 +151,9 @@ export class WorkerClient { }); this.worker.postMessage({ - type: "player_border_tiles", id: messageId, - playerID: playerID, + playerID, + type: "player_border_tiles", }); }); } @@ -181,9 +181,9 @@ export class WorkerClient { }); this.worker.postMessage({ - type: "player_actions", id: messageId, - playerID: playerID, + playerID, + type: "player_actions", x: x, y: y, }); @@ -217,10 +217,10 @@ export class WorkerClient { }); this.worker.postMessage({ - type: "attack_average_position", + attackID, id: messageId, - playerID: playerID, - attackID: attackID, + playerID, + type: "attack_average_position", }); }); } @@ -247,10 +247,10 @@ export class WorkerClient { }); this.worker.postMessage({ - type: "transport_ship_spawn", id: messageId, - playerID: playerID, - targetTile: targetTile, + playerID, + targetTile, + type: "transport_ship_spawn", }); }); } diff --git a/src/server/Archive.ts b/src/server/Archive.ts index 6b3675d94..8c077940e 100644 --- a/src/server/Archive.ts +++ b/src/server/Archive.ts @@ -11,11 +11,13 @@ const log = logger.child({ component: "Archive" }); // R2 client configuration const r2 = new S3({ region: "auto", // R2 ignores region, but it's required by the SDK + /* eslint-disable sort-keys */ endpoint: config.r2Endpoint(), credentials: { accessKeyId: config.r2AccessKey(), secretAccessKey: config.r2SecretKey(), }, + /* eslint-disable sort-keys */ }); const bucket = config.r2Bucket(); diff --git a/src/server/Cloudflare.ts b/src/server/Cloudflare.ts index 758706937..eef337974 100644 --- a/src/server/Cloudflare.ts +++ b/src/server/Cloudflare.ts @@ -60,12 +60,12 @@ export class Cloudflare { data?: any, ): Promise { const response = await fetch(url, { - method, + body: data ? JSON.stringify(data) : undefined, headers: { Authorization: `Bearer ${this.apiToken}`, "Content-Type": "application/json", }, - body: data ? JSON.stringify(data) : undefined, + method, }); if (!response.ok) { @@ -178,7 +178,6 @@ export class Cloudflare { log.info(`Created credentials file at: ${this.credsPath}`); const tunnelConfig: CloudflaredConfig = { - tunnel: tunnelId, "credentials-file": this.credsPath, ingress: [ ...Array.from(subdomainToService.entries()).map( @@ -191,6 +190,7 @@ export class Cloudflare { service: "http_status:404", }, ], + tunnel: tunnelId, }; // Write config file @@ -210,11 +210,11 @@ export class Cloudflare { const recordId = existingRecords.result[0]?.id; const dnsData = { - type: "CNAME", - name: subdomain, content: `${tunnelId}.cfargotunnel.com`, - ttl: 1, + name: subdomain, proxied: true, + ttl: 1, + type: "CNAME", }; if (recordId) { @@ -240,12 +240,12 @@ export class Cloudflare { ["tunnel", "--config", this.configPath, "--loglevel", "error", "run"], { detached: true, - stdio: ["ignore", "pipe", "pipe"], env: { ...process.env, // Set this to bypass origin cert requirement for named tunnels TUNNEL_ORIGIN_CERT: "/dev/null", }, + stdio: ["ignore", "pipe", "pipe"], }, ); diff --git a/src/server/GameManager.ts b/src/server/GameManager.ts index 2375948f1..8870f00db 100644 --- a/src/server/GameManager.ts +++ b/src/server/GameManager.ts @@ -39,16 +39,16 @@ export class GameManager { Date.now(), this.config, { - gameMap: GameMapType.World, - gameType: GameType.Private, + bots: 400, difficulty: Difficulty.Medium, disableNPCs: false, + disabledUnits: [], + gameMap: GameMapType.World, + gameMode: GameMode.FFA, + gameType: GameType.Private, infiniteGold: false, infiniteTroops: false, instantBuild: false, - gameMode: GameMode.FFA, - bots: 400, - disabledUnits: [], ...gameConfig, }, creatorClientID, diff --git a/src/server/GameServer.ts b/src/server/GameServer.ts index b16dae9b3..ac0fe1be2 100644 --- a/src/server/GameServer.ts +++ b/src/server/GameServer.ts @@ -123,15 +123,15 @@ export class GameServer { // Log when lobby creator joins private game if (client.clientID === this.LobbyCreatorID) { this.log.info("Lobby creator joined", { - gameID: this.id, creatorID: this.LobbyCreatorID, + gameID: this.id, }); } this.log.info("client (re)joining game", { clientID: client.clientID, - persistentID: client.persistentID, clientIP: ipAnonymize(client.ip), isRejoin: lastTurn > 0, + persistentID: client.persistentID, }); if ( @@ -210,9 +210,9 @@ export class GameServer { }); client.ws.send( JSON.stringify({ - type: "error", error, message, + type: "error", } satisfies ServerErrorMessage), ); client.ws.close(1002, "ClientMessageSchema"); @@ -244,8 +244,8 @@ export class GameServer { this.log.warn(`Only lobby creator can kick players`, { clientID: authenticatedClientID, creatorID: this.LobbyCreatorID, - target: clientMsg.intent.target, gameID: this.id, + target: clientMsg.intent.target, }); return; } @@ -261,9 +261,9 @@ export class GameServer { // Log and execute the kick this.log.info(`Lobby creator initiated kick of player`, { creatorID: authenticatedClientID, - target: clientMsg.intent.target, gameID: this.id, kickMethod: "websocket", + target: clientMsg.intent.target, }); this.kickClient(clientMsg.intent.target); @@ -358,8 +358,8 @@ export class GameServer { this._hasPrestarted = true; const prestartMsg = ServerPrestartMessageSchema.safeParse({ - type: "prestart", gameMap: this.gameConfig.gameMap, + type: "prestart", }); if (!prestartMsg.success) { @@ -393,13 +393,13 @@ export class GameServer { this.lastPingUpdate = Date.now(); const result = GameStartInfoSchema.safeParse({ - gameID: this.id, config: this.gameConfig, + gameID: this.id, players: this.activeClients.map((c) => ({ - username: c.username, clientID: c.clientID, - pattern: c.pattern, flag: c.flag, + pattern: c.pattern, + username: c.username, })), }); if (!result.success) { @@ -430,9 +430,9 @@ export class GameServer { try { ws.send( JSON.stringify({ - type: "start", - turns: this.turns.slice(lastTurn), gameStartInfo: this.gameStartInfo, + turns: this.turns.slice(lastTurn), + type: "start", } satisfies ServerStartGameMessage), ); } catch (error) { @@ -447,8 +447,8 @@ export class GameServer { private endTurn() { const pastTurn: Turn = { - turnNumber: this.turns.length, intents: this.intents, + turnNumber: this.turns.length, }; this.turns.push(pastTurn); this.intents = []; @@ -457,8 +457,8 @@ export class GameServer { this.checkDisconnectedStatus(); const msg = JSON.stringify({ - type: "turn", turn: pastTurn, + type: "turn", } satisfies ServerTurnMessage); this.activeClients.forEach((c) => { c.ws.send(msg); @@ -510,9 +510,9 @@ export class GameServer { } this.log.error("Error archiving game record details:", { - gameId: this.id, - errorType: typeof error, error: errorDetails, + errorType: typeof error, + gameId: this.id, }); } } @@ -587,12 +587,12 @@ export class GameServer { public gameInfo(): GameInfo { return { - gameID: this.id, clients: this.activeClients.map((c) => ({ - username: c.username, clientID: c.clientID, + username: c.username, })), gameConfig: this.gameConfig, + gameID: this.id, msUntilStart: this.isPublic() ? this.createdAt + this.config.gameCreationRate() : undefined, @@ -618,8 +618,8 @@ export class GameServer { }); client.ws.send( JSON.stringify({ - type: "error", error: "Kicked from game (you may have been playing on another tab)", + type: "error", } satisfies ServerErrorMessage), ); client.ws.close(1000, "Kicked from game"); @@ -660,9 +660,9 @@ export class GameServer { private markClientDisconnected(clientID: string, isDisconnected: boolean) { this.clientsDisconnectedStatus.set(clientID, isDisconnected); this.addIntent({ - type: "mark_disconnected", - clientID: clientID, + clientID, isDisconnected: isDisconnected, + type: "mark_disconnected", }); } @@ -681,10 +681,10 @@ export class GameServer { } return { clientID: player.clientID, - username: player.username, persistentID: this.allClients.get(player.clientID)?.persistentID ?? "", stats, + username: player.username, } satisfies PlayerRecord; }, ); @@ -722,17 +722,17 @@ export class GameServer { } const serverDesync = ServerDesyncSchema.safeParse({ - type: "desync", - turn: lastHashTurn, - correctHash: mostCommonHash, clientsWithCorrectHash: this.activeClients.length - outOfSyncClients.length, + correctHash: mostCommonHash, totalActiveClients: this.activeClients.length, + turn: lastHashTurn, + type: "desync", }); if (!serverDesync.success) { this.log.warn("failed to create desync message", { - gameID: this.id, error: serverDesync.error, + gameID: this.id, }); return; } @@ -745,8 +745,8 @@ export class GameServer { } this.sentDesyncMessageClients.add(c.clientID); this.log.info("sending desync to client", { - gameID: this.id, clientID: c.clientID, + gameID: this.id, persistentID: c.persistentID, }); c.ws.send(desyncMsg); diff --git a/src/server/Logger.ts b/src/server/Logger.ts index 25be2683e..7ba07be31 100644 --- a/src/server/Logger.ts +++ b/src/server/Logger.ts @@ -29,6 +29,7 @@ if (config.otelEnabled()) { // Add OTLP exporter for logs const logExporter = new OTLPLogExporter({ url: `${config.otelEndpoint()}/v1/logs`, + // eslint-disable-next-line sort-keys headers, }); @@ -56,6 +57,7 @@ const addSeverityFormat = winston.format((info) => { // Define your base/parent logger const logger = winston.createLogger({ level: "info", + /* eslint-disable sort-keys */ format: winston.format.combine( winston.format.timestamp(), addSeverityFormat(), @@ -65,6 +67,7 @@ const logger = winston.createLogger({ service: "openfront", environment: process.env.GAME_ENV ?? "prod", }, + /* eslint-enable sort-keys */ transports: [ new winston.transports.Console(), new OpenTelemetryTransportV3(), diff --git a/src/server/MapPlaylist.ts b/src/server/MapPlaylist.ts index db55d2045..ca3d0e310 100644 --- a/src/server/MapPlaylist.ts +++ b/src/server/MapPlaylist.ts @@ -20,33 +20,33 @@ const config = getServerConfigFromServer(); // How many times each map should appear in the playlist. // Note: The Partial should eventually be removed for better type safety. const frequency: Partial> = { - World: 3, - Europe: 2, Africa: 2, - Baikal: 2, - Australia: 1, - NorthAmerica: 1, - Britannia: 1, - GatewayToTheAtlantic: 1, - Iceland: 1, - SouthAmerica: 1, - DeglaciatedAntarctica: 1, - EuropeClassic: 1, - Mena: 1, - Pangaea: 1, Asia: 1, + Australia: 1, + Baikal: 2, + BetweenTwoSeas: 1, + BlackSea: 1, + Britannia: 1, + DeglaciatedAntarctica: 1, + EastAsia: 1, + Europe: 2, + EuropeClassic: 1, + FalklandIslands: 1, + FaroeIslands: 1, + GatewayToTheAtlantic: 1, + Halkidiki: 1, + Iceland: 1, + Italia: 1, Mars: 1, MarsRevised: 1, - BetweenTwoSeas: 1, - EastAsia: 1, - BlackSea: 1, - FaroeIslands: 1, - FalklandIslands: 1, - Halkidiki: 1, - StraitOfGibraltar: 1, - Italia: 1, - Yenisei: 1, + Mena: 1, + NorthAmerica: 1, + Pangaea: 1, Pluto: 1, + SouthAmerica: 1, + StraitOfGibraltar: 1, + World: 3, + Yenisei: 1, }; interface MapWithMode { @@ -77,18 +77,18 @@ export class MapPlaylist { // Create the default public game config (from your GameManager) return { - gameMap: map, - maxPlayers: config.lobbyMaxPlayers(map, mode, playerTeams), - gameType: GameType.Public, + bots: 400, difficulty: Difficulty.Medium, + disableNPCs: mode === GameMode.Team, + disabledUnits: [], + gameMap: map, + gameMode: mode, + gameType: GameType.Public, infiniteGold: false, infiniteTroops: false, instantBuild: false, - disableNPCs: mode === GameMode.Team, - gameMode: mode, + maxPlayers: config.lobbyMaxPlayers(map, mode, playerTeams), playerTeams, - bots: 400, - disabledUnits: [], } satisfies GameConfig; } diff --git a/src/server/Master.ts b/src/server/Master.ts index 2b835ff85..ff693c323 100644 --- a/src/server/Master.ts +++ b/src/server/Master.ts @@ -54,8 +54,8 @@ app.use(express.json()); app.set("trust proxy", 3); app.use( rateLimit({ - windowMs: 1000, // 1 second max: 20, // 20 requests per IP per second + windowMs: 1000, // 1 second }), ); @@ -180,10 +180,10 @@ app.post( const response = await fetch( `http://localhost:${config.workerPort(gameID)}/api/kick_player/${gameID}/${clientID}`, { - method: "POST", headers: { [config.adminHeader()]: config.adminToken(), }, + method: "POST", }, ); @@ -232,10 +232,10 @@ async function fetchLobbies(): Promise { .filter((result) => result !== null) .map((gi: GameInfo) => { return { - gameID: gi.gameID, - numClients: gi?.clients?.length ?? 0, gameConfig: gi.gameConfig, + gameID: gi.gameID, msUntilStart: (gi.msUntilStart ?? Date.now()) - Date.now(), + numClients: gi?.clients?.length ?? 0, } as GameInfo; }); @@ -283,12 +283,12 @@ async function schedulePublicGame(playlist: MapPlaylist) { const response = await fetch( `http://localhost:${config.workerPort(gameID)}/api/create_game/${gameID}`, { - method: "POST", + body: JSON.stringify(playlist.gameConfig()), headers: { "Content-Type": "application/json", [config.adminHeader()]: config.adminToken(), }, - body: JSON.stringify(playlist.gameConfig()), + method: "POST", }, ); diff --git a/src/server/OtelResource.ts b/src/server/OtelResource.ts index 6fd7f0025..7bb93bfa8 100644 --- a/src/server/OtelResource.ts +++ b/src/server/OtelResource.ts @@ -18,6 +18,7 @@ export function getOtelResource() { export function getPromLabels() { return { "service.instance.id": process.env.HOSTNAME, + /* eslint-disable sort-keys */ "openfront.environment": config.env(), "openfront.host": process.env.HOST, "openfront.domain": process.env.DOMAIN, @@ -25,5 +26,6 @@ export function getPromLabels() { "openfront.component": process.env.WORKER_ID ? "Worker " + process.env.WORKER_ID : "Master", + /* eslint-enable sort-keys */ }; } diff --git a/src/server/Server.ts b/src/server/Server.ts index f47fe2fc1..23307f1ea 100644 --- a/src/server/Server.ts +++ b/src/server/Server.ts @@ -55,8 +55,8 @@ async function setupTunnels() { if (!(await cloudflare.configAlreadyExists())) { await cloudflare.createTunnel({ - subdomain: config.subdomain(), domain: config.domain(), + subdomain: config.subdomain(), subdomainToService: domainToService, } as TunnelConfig); } else { diff --git a/src/server/Worker.ts b/src/server/Worker.ts index 9f7da2eb1..21fd99012 100644 --- a/src/server/Worker.ts +++ b/src/server/Worker.ts @@ -85,8 +85,8 @@ export async function startWorker() { app.use(express.static(path.join(__dirname, "../../out"))); app.use( rateLimit({ - windowMs: 1000, // 1 second max: 20, // 20 requests per IP per second + windowMs: 1000, // 1 second }), ); @@ -232,9 +232,9 @@ export async function startWorker() { if (!gameRecord) { return res.status(404).json({ - success: false, error: "Game not found", exists: false, + success: false, }); } @@ -246,20 +246,20 @@ export async function startWorker() { `git commit mismatch for game ${req.params.id}, expected ${config.gitCommit()}, got ${gameRecord.gitCommit}`, ); return res.status(409).json({ - success: false, + details: { + actualCommit: gameRecord.gitCommit, + expectedCommit: config.gitCommit(), + }, error: "Version mismatch", exists: true, - details: { - expectedCommit: config.gitCommit(), - actualCommit: gameRecord.gitCommit, - }, + success: false, }); } return res.status(200).json({ - success: true, exists: true, gameRecord: gameRecord, + success: true, }); }), ); @@ -324,8 +324,8 @@ export async function startWorker() { log.warn("Error parsing client message", error); ws.send( JSON.stringify({ - type: "error", error: error.toString(), + type: "error", } satisfies ServerErrorMessage), ); ws.close(1002, "ClientJoinMessageSchema"); diff --git a/src/server/WorkerMetrics.ts b/src/server/WorkerMetrics.ts index 86d320e75..2539546bf 100644 --- a/src/server/WorkerMetrics.ts +++ b/src/server/WorkerMetrics.ts @@ -25,20 +25,20 @@ export function initWorkerMetrics(gameManager: GameManager): void { // Create metrics exporter const metricExporter = new OTLPMetricExporter({ - url: `${config.otelEndpoint()}/v1/metrics`, headers, + url: `${config.otelEndpoint()}/v1/metrics`, }); // Configure the metric reader const metricReader = new PeriodicExportingMetricReader({ - exporter: metricExporter, exportIntervalMillis: 15000, // Export metrics every 15 seconds + exporter: metricExporter, }); // Create a meter provider const meterProvider = new MeterProvider({ - resource, readers: [metricReader], + resource, }); // Get meter for creating metrics diff --git a/src/server/jwt.ts b/src/server/jwt.ts index d8a74384f..98ebb518f 100644 --- a/src/server/jwt.ts +++ b/src/server/jwt.ts @@ -21,6 +21,7 @@ export async function verifyClientToken( config: ServerConfig, ): Promise { if (PersistentIdSchema.safeParse(token).success) { + // eslint-disable-next-line sort-keys return { persistentId: token, claims: null }; } try { @@ -29,8 +30,8 @@ export async function verifyClientToken( const key = await config.jwkPublicKey(); const { payload, protectedHeader } = await jwtVerify(token, key, { algorithms: ["EdDSA"], - issuer, audience, + issuer, }); const result = TokenPayloadSchema.safeParse(payload); if (!result.success) { @@ -40,7 +41,7 @@ export async function verifyClientToken( } const claims = result.data; const persistentId = claims.sub; - return { persistentId, claims }; + return { claims, persistentId }; } catch (e) { return false; } diff --git a/tests/util/Setup.ts b/tests/util/Setup.ts index 20e8d67d5..3253eb9a8 100644 --- a/tests/util/Setup.ts +++ b/tests/util/Setup.ts @@ -57,12 +57,12 @@ export async function setup( // Configure the game const serverConfig = new TestServerConfig(); const gameConfig: GameConfig = { + bots: 0, + difficulty: Difficulty.Medium, + disableNPCs: false, gameMap: GameMapType.Asia, gameMode: GameMode.FFA, gameType: GameType.Singleplayer, - difficulty: Difficulty.Medium, - disableNPCs: false, - bots: 0, infiniteGold: false, infiniteTroops: false, instantBuild: false,