mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-21 15:30:43 +00:00
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:
@@ -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",
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
@@ -6,6 +6,7 @@ export const RefreshResponseSchema = z.object({
|
||||
});
|
||||
export type RefreshResponse = z.infer<typeof RefreshResponseSchema>;
|
||||
|
||||
/* eslint-disable sort-keys */
|
||||
export const TokenPayloadSchema = z.object({
|
||||
jti: z.string(),
|
||||
sub: z
|
||||
|
||||
@@ -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<typeof CosmeticsSchema>;
|
||||
export type Pattern = z.infer<typeof PatternSchema>;
|
||||
|
||||
@@ -2,12 +2,14 @@ import { Cosmetics } from "./CosmeticSchemas";
|
||||
|
||||
const ANIMATION_DURATIONS: Record<string, number> = {
|
||||
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 };
|
||||
});
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
+60
-60
@@ -144,17 +144,17 @@ const TeamCountConfigSchema = z.union([
|
||||
export type TeamCountConfig = z.infer<typeof TeamCountConfigSchema>;
|
||||
|
||||
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<typeof WinnerSchema>;
|
||||
//
|
||||
|
||||
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<typeof PlayerRecordSchema>;
|
||||
|
||||
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<typeof GameEndInfoSchema>;
|
||||
@@ -546,11 +546,11 @@ export type GameEndInfo = z.infer<typeof GameEndInfoSchema>;
|
||||
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<typeof AnalyticsRecordSchema>;
|
||||
|
||||
|
||||
+13
-12
@@ -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;
|
||||
|
||||
@@ -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 });
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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),
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -113,7 +113,7 @@ export class WarshipExecution implements Execution {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
potentialTargets.push({ unit: unit, distSquared });
|
||||
potentialTargets.push({ distSquared, unit });
|
||||
}
|
||||
|
||||
return potentialTargets.sort((a, b) => {
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<NationMapModule>
|
||||
).then((m) => m.default),
|
||||
),
|
||||
mapBin: this.createLazyLoader(() =>
|
||||
(
|
||||
import(
|
||||
@@ -51,13 +58,6 @@ export class BinaryLoaderGameMapLoader implements GameMapLoader {
|
||||
) as Promise<BinModule>
|
||||
).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(() =>
|
||||
(
|
||||
import(
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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<string, Team> = {
|
||||
Red: "Red",
|
||||
Blue: "Blue",
|
||||
@@ -54,6 +55,7 @@ export const ColoredTeams: Record<string, Team> = {
|
||||
Green: "Green",
|
||||
Bot: "Bot",
|
||||
} as const;
|
||||
/* eslint-enable sort-keys */
|
||||
|
||||
export enum GameMapType {
|
||||
World = "World",
|
||||
@@ -118,6 +120,7 @@ export const mapCategories: Record<string, GameMapType[]> = {
|
||||
GameMapType.Italia,
|
||||
GameMapType.Yenisei,
|
||||
],
|
||||
// eslint-disable-next-line sort-keys
|
||||
fantasy: [
|
||||
GameMapType.Pangaea,
|
||||
GameMapType.Pluto,
|
||||
|
||||
+27
-27
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -216,6 +216,7 @@ export class RailNetworkImpl implements RailNetwork {
|
||||
|
||||
const visited = new Set<TrainStation>();
|
||||
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 });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -183,10 +183,10 @@ export function candidateShoreTiles(
|
||||
|
||||
let bestByManhattan: TileRef | null = null;
|
||||
const extremumTiles: Record<string, TileRef | null> = {
|
||||
minX: null,
|
||||
minY: null,
|
||||
maxX: null,
|
||||
maxY: null,
|
||||
minX: null,
|
||||
minY: null,
|
||||
};
|
||||
|
||||
const borderShoreTiles = Array.from(player.borderTiles()).filter((t) =>
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,6 +49,7 @@ export class SerialAStar<NodeType> implements AStar<NodeType> {
|
||||
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<NodeType> implements AStar<NodeType> {
|
||||
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<NodeType> implements AStar<NodeType> {
|
||||
const fScore =
|
||||
totalG +
|
||||
this.heuristic(neighbor, isForward ? this.dst : this.closestSource);
|
||||
// eslint-disable-next-line sort-keys
|
||||
openSet.add({ tile: neighbor, fScore: fScore });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -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<MainThreadMessage>) => {
|
||||
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<MainThreadMessage>) => {
|
||||
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<MainThreadMessage>) => {
|
||||
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<MainThreadMessage>) => {
|
||||
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<MainThreadMessage>) => {
|
||||
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<MainThreadMessage>) => {
|
||||
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);
|
||||
|
||||
@@ -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",
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -60,12 +60,12 @@ export class Cloudflare {
|
||||
data?: any,
|
||||
): Promise<T> {
|
||||
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"],
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
+26
-26
@@ -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);
|
||||
|
||||
@@ -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(),
|
||||
|
||||
+29
-29
@@ -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<Record<GameMapName, number>> = {
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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<number> {
|
||||
.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",
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
@@ -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 */
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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
|
||||
|
||||
+3
-2
@@ -21,6 +21,7 @@ export async function verifyClientToken(
|
||||
config: ServerConfig,
|
||||
): Promise<TokenVerificationResult> {
|
||||
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;
|
||||
}
|
||||
|
||||
+3
-3
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user