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