From f280ed0c2342c6566ceb1e832f39b5991602f2fb Mon Sep 17 00:00:00 2001 From: Hide_D Date: Sat, 24 Jan 2026 05:40:58 +0000 Subject: [PATCH] =?UTF-8?q?feat:=20=EB=AA=85=EB=A0=B9=EC=96=B4=20=EC=A0=95?= =?UTF-8?q?=EA=B7=9C=ED=99=94=20=EB=B0=8F=20=ED=95=B8=EB=93=A4=EB=9F=AC=20?= =?UTF-8?q?=EB=A7=A4=ED=95=91=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/lifecycle/redisCommandStream.ts | 322 +------------- app/game-engine/src/turn/commandRegistry.ts | 414 ++++++++++++++++++ .../src/turn/worldCommandHandler.ts | 69 ++- packages/common/src/turnDaemon/types.ts | 9 + 4 files changed, 453 insertions(+), 361 deletions(-) create mode 100644 app/game-engine/src/turn/commandRegistry.ts diff --git a/app/game-engine/src/lifecycle/redisCommandStream.ts b/app/game-engine/src/lifecycle/redisCommandStream.ts index 5bdc070..fec7d46 100644 --- a/app/game-engine/src/lifecycle/redisCommandStream.ts +++ b/app/game-engine/src/lifecycle/redisCommandStream.ts @@ -5,6 +5,7 @@ import type { TurnDaemonStatus, TurnDaemonCommandResult, } from './types.js'; +import { normalizeTurnDaemonCommand } from '../turn/commandRegistry.js'; export interface TurnDaemonStreamKeys { commandStream: string; @@ -42,9 +43,6 @@ type TurnDaemonEventEnvelope = { event: TurnDaemonEvent; }; -const isRecord = (value: unknown): value is Record => - Boolean(value && typeof value === 'object' && !Array.isArray(value)); - const parseCommandEnvelope = (raw: string): TurnDaemonCommandEnvelope | null => { try { const parsed = JSON.parse(raw) as Partial; @@ -66,322 +64,6 @@ const parseCommandEnvelope = (raw: string): TurnDaemonCommandEnvelope | null => } }; -const normalizeCommand = (envelope: TurnDaemonCommandEnvelope): TurnDaemonCommand | null => { - const command = envelope.command as TurnDaemonCommand & { - requestId?: string; - }; - switch (command.type) { - case 'auctionFinalize': { - if (typeof command.auctionId !== 'number') { - return null; - } - return { - type: 'auctionFinalize', - requestId: envelope.requestId, - auctionId: command.auctionId, - }; - } - case 'troopJoin': { - if (typeof command.generalId !== 'number' || typeof command.troopId !== 'number') { - return null; - } - return { - type: 'troopJoin', - requestId: envelope.requestId, - generalId: command.generalId, - troopId: command.troopId, - }; - } - case 'troopExit': { - if (typeof command.generalId !== 'number') { - return null; - } - return { - type: 'troopExit', - requestId: envelope.requestId, - generalId: command.generalId, - }; - } - case 'dieOnPrestart': { - if (typeof command.generalId !== 'number') { - return null; - } - return { - type: 'dieOnPrestart', - requestId: envelope.requestId, - generalId: command.generalId, - }; - } - case 'buildNationCandidate': { - if (typeof command.generalId !== 'number') { - return null; - } - return { - type: 'buildNationCandidate', - requestId: envelope.requestId, - generalId: command.generalId, - }; - } - case 'instantRetreat': { - if (typeof command.generalId !== 'number') { - return null; - } - return { - type: 'instantRetreat', - requestId: envelope.requestId, - generalId: command.generalId, - }; - } - case 'vacation': { - if (typeof command.generalId !== 'number') { - return null; - } - return { - type: 'vacation', - requestId: envelope.requestId, - generalId: command.generalId, - }; - } - case 'setMySetting': { - if (typeof command.generalId !== 'number' || !command.settings || typeof command.settings !== 'object') { - return null; - } - return { - type: 'setMySetting', - requestId: envelope.requestId, - generalId: command.generalId, - settings: command.settings, - }; - } - case 'dropItem': { - if (typeof command.generalId !== 'number' || typeof command.itemType !== 'string') { - return null; - } - return { - type: 'dropItem', - requestId: envelope.requestId, - generalId: command.generalId, - itemType: command.itemType, - }; - } - case 'changePermission': { - if ( - typeof command.generalId !== 'number' || - typeof command.isAmbassador !== 'boolean' || - !Array.isArray(command.targetGeneralIds) - ) { - return null; - } - const targetGeneralIds = command.targetGeneralIds.filter((id) => typeof id === 'number'); - if (targetGeneralIds.length === 0) { - return null; - } - return { - type: 'changePermission', - requestId: envelope.requestId, - generalId: command.generalId, - isAmbassador: command.isAmbassador, - targetGeneralIds, - }; - } - case 'kick': { - if (typeof command.generalId !== 'number' || typeof command.destGeneralId !== 'number') { - return null; - } - return { - type: 'kick', - requestId: envelope.requestId, - generalId: command.generalId, - destGeneralId: command.destGeneralId, - }; - } - case 'appoint': { - if ( - typeof command.generalId !== 'number' || - typeof command.destGeneralId !== 'number' || - typeof command.destCityId !== 'number' || - typeof command.officerLevel !== 'number' - ) { - return null; - } - return { - type: 'appoint', - requestId: envelope.requestId, - generalId: command.generalId, - destGeneralId: command.destGeneralId, - destCityId: command.destCityId, - officerLevel: command.officerLevel, - }; - } - case 'tournamentRefund': { - if (!Array.isArray(command.refunds)) { - return null; - } - const refunds = command.refunds - .filter((entry) => - entry && typeof entry.generalId === 'number' && typeof entry.amount === 'number' - ) - .map((entry) => ({ - generalId: entry.generalId, - amount: entry.amount, - })); - if (refunds.length === 0) { - return null; - } - return { - type: 'tournamentRefund', - requestId: envelope.requestId, - bettingId: typeof command.bettingId === 'number' ? command.bettingId : undefined, - reason: typeof command.reason === 'string' ? command.reason : undefined, - refunds, - }; - } - case 'tournamentBettingPayout': { - if (!Array.isArray(command.payouts)) { - return null; - } - const payouts = command.payouts - .filter((entry) => - entry && typeof entry.generalId === 'number' && typeof entry.amount === 'number' - ) - .map((entry) => ({ - generalId: entry.generalId, - amount: entry.amount, - })); - if (payouts.length === 0) { - return null; - } - return { - type: 'tournamentBettingPayout', - requestId: envelope.requestId, - bettingId: typeof command.bettingId === 'number' ? command.bettingId : undefined, - reason: typeof command.reason === 'string' ? command.reason : undefined, - payouts, - }; - } - case 'tournamentReward': { - if (typeof command.winnerId !== 'number' || typeof command.runnerUpId !== 'number') { - return null; - } - if (!Array.isArray(command.top16) || !Array.isArray(command.top8) || !Array.isArray(command.top4)) { - return null; - } - const normalizeIds = (list: unknown[]): number[] => - list.filter((entry): entry is number => typeof entry === 'number' && Number.isFinite(entry)); - const top16 = normalizeIds(command.top16); - const top8 = normalizeIds(command.top8); - const top4 = normalizeIds(command.top4); - if (top16.length === 0) { - return null; - } - return { - type: 'tournamentReward', - requestId: envelope.requestId, - tournamentType: typeof command.tournamentType === 'number' ? command.tournamentType : 0, - winnerId: command.winnerId, - runnerUpId: command.runnerUpId, - top16, - top8, - top4, - }; - } - case 'setNationMeta': { - if (typeof command.nationId !== 'number' || !command.updates || typeof command.updates !== 'object') { - return null; - } - return { - type: 'setNationMeta', - requestId: envelope.requestId, - nationId: command.nationId, - updates: command.updates as Record, - expectedUpdatedAt: typeof command.expectedUpdatedAt === 'string' ? command.expectedUpdatedAt : undefined, - }; - } - case 'adjustGeneralResources': { - if (!Array.isArray(command.adjustments)) { - return null; - } - const adjustments = command.adjustments - .filter((entry) => entry && typeof entry.generalId === 'number') - .map((entry) => ({ - generalId: entry.generalId, - goldDelta: typeof entry.goldDelta === 'number' ? entry.goldDelta : undefined, - riceDelta: typeof entry.riceDelta === 'number' ? entry.riceDelta : undefined, - })) - .filter((entry) => entry.goldDelta !== undefined || entry.riceDelta !== undefined); - if (adjustments.length === 0) { - return null; - } - return { - type: 'adjustGeneralResources', - requestId: envelope.requestId, - reason: typeof command.reason === 'string' ? command.reason : undefined, - adjustments, - }; - } - case 'patchGeneral': { - if (typeof command.generalId !== 'number' || !command.patch || typeof command.patch !== 'object') { - return null; - } - return { - type: 'patchGeneral', - requestId: envelope.requestId, - generalId: command.generalId, - patch: { - meta: isRecord(command.patch.meta) ? command.patch.meta : undefined, - turnTime: typeof command.patch.turnTime === 'string' ? command.patch.turnTime : undefined, - stats: isRecord(command.patch.stats) - ? { - leadership: - typeof command.patch.stats.leadership === 'number' - ? command.patch.stats.leadership - : undefined, - strength: - typeof command.patch.stats.strength === 'number' - ? command.patch.stats.strength - : undefined, - intelligence: - typeof command.patch.stats.intelligence === 'number' - ? command.patch.stats.intelligence - : undefined, - } - : undefined, - specialWar: typeof command.patch.specialWar === 'string' ? command.patch.specialWar : undefined, - }, - }; - } - case 'auctionBid': { - if ( - typeof command.auctionId !== 'number' || - typeof command.generalId !== 'number' || - typeof command.amount !== 'number' - ) { - return null; - } - return { - type: 'auctionBid', - requestId: envelope.requestId, - auctionId: command.auctionId, - generalId: command.generalId, - amount: command.amount, - tryExtendCloseDate: - typeof command.tryExtendCloseDate === 'boolean' ? command.tryExtendCloseDate : undefined, - }; - } - case 'getStatus': { - const requestId = typeof command.requestId === 'string' ? command.requestId : envelope.requestId; - return { type: 'getStatus', requestId }; - } - case 'run': - case 'pause': - case 'resume': - case 'shutdown': - return command; - default: - return null; - } -}; - export class RedisTurnDaemonCommandStream implements TurnDaemonControlQueue, TurnDaemonCommandResponder { private readonly client: RedisStreamClient; private readonly keys: TurnDaemonStreamKeys; @@ -477,7 +159,7 @@ export class RedisTurnDaemonCommandStream implements TurnDaemonControlQueue, Tur if (!envelope) { continue; } - const command = normalizeCommand(envelope); + const command = normalizeTurnDaemonCommand(envelope); if (!command) { continue; } diff --git a/app/game-engine/src/turn/commandRegistry.ts b/app/game-engine/src/turn/commandRegistry.ts new file mode 100644 index 0000000..b36e263 --- /dev/null +++ b/app/game-engine/src/turn/commandRegistry.ts @@ -0,0 +1,414 @@ +import { z } from 'zod'; + +import type { + TurnDaemonCommand, + TurnDaemonCommandType, + TurnDaemonCommandByType, +} from '@sammo-ts/common'; + +export type TurnDaemonCommandEnvelope = { + requestId: string; + sentAt: string; + command: TurnDaemonCommand; +}; + +type CommandNormalizer = ( + envelope: TurnDaemonCommandEnvelope +) => TurnDaemonCommandByType | null; + +type CommandNormalizerMap = { + [T in TurnDaemonCommandType]?: CommandNormalizer; +}; + +const parseWith = (schema: z.ZodType, value: unknown): T | null => { + const parsed = schema.safeParse(value); + return parsed.success ? parsed.data : null; +}; + +const zFiniteNumber = z.number().finite(); +const zRecord = z.record(z.string(), z.unknown()); + +const zRunReason = z.enum(['schedule', 'manual', 'poke']); +const zTurnRunBudget = z.object({ + budgetMs: z.number().int().positive(), + maxGenerals: z.number().int().positive(), + catchUpCap: z.number().int().positive(), +}); + +const zAuctionFinalize = z.object({ + type: z.literal('auctionFinalize'), + auctionId: zFiniteNumber, +}); + +const zAuctionBid = z.object({ + type: z.literal('auctionBid'), + auctionId: zFiniteNumber, + generalId: zFiniteNumber, + amount: zFiniteNumber, + tryExtendCloseDate: z.boolean().optional(), +}); + +const zTroopJoin = z.object({ + type: z.literal('troopJoin'), + generalId: zFiniteNumber, + troopId: zFiniteNumber, +}); + +const zTroopExit = z.object({ + type: z.literal('troopExit'), + generalId: zFiniteNumber, +}); + +const zDieOnPrestart = z.object({ + type: z.literal('dieOnPrestart'), + generalId: zFiniteNumber, +}); + +const zBuildNationCandidate = z.object({ + type: z.literal('buildNationCandidate'), + generalId: zFiniteNumber, +}); + +const zInstantRetreat = z.object({ + type: z.literal('instantRetreat'), + generalId: zFiniteNumber, +}); + +const zVacation = z.object({ + type: z.literal('vacation'), + generalId: zFiniteNumber, +}); + +const zSetMySetting = z.object({ + type: z.literal('setMySetting'), + generalId: zFiniteNumber, + settings: z.object({ + tnmt: z.number().int().optional(), + defence_train: z.number().int().optional(), + use_treatment: z.number().int().optional(), + use_auto_nation_turn: z.number().int().optional(), + }), +}); + +const zDropItem = z.object({ + type: z.literal('dropItem'), + generalId: zFiniteNumber, + itemType: z.string().min(1), +}); + +const zChangePermission = z.object({ + type: z.literal('changePermission'), + generalId: zFiniteNumber, + isAmbassador: z.boolean(), + targetGeneralIds: z.array(zFiniteNumber).min(1), +}); + +const zKick = z.object({ + type: z.literal('kick'), + generalId: zFiniteNumber, + destGeneralId: zFiniteNumber, +}); + +const zAppoint = z.object({ + type: z.literal('appoint'), + generalId: zFiniteNumber, + destGeneralId: zFiniteNumber, + destCityId: zFiniteNumber, + officerLevel: zFiniteNumber, +}); + +const zTournamentRefund = z.object({ + type: z.literal('tournamentRefund'), + bettingId: zFiniteNumber.optional(), + reason: z.string().optional(), + refunds: z.array(z.object({ generalId: zFiniteNumber, amount: zFiniteNumber })).min(1), +}); + +const zTournamentBettingPayout = z.object({ + type: z.literal('tournamentBettingPayout'), + bettingId: zFiniteNumber.optional(), + reason: z.string().optional(), + payouts: z.array(z.object({ generalId: zFiniteNumber, amount: zFiniteNumber })).min(1), +}); + +const zTournamentReward = z.object({ + type: z.literal('tournamentReward'), + tournamentType: zFiniteNumber, + winnerId: zFiniteNumber, + runnerUpId: zFiniteNumber, + top16: z.array(zFiniteNumber).min(1), + top8: z.array(zFiniteNumber), + top4: z.array(zFiniteNumber), +}); + +const zSetNationMeta = z.object({ + type: z.literal('setNationMeta'), + nationId: zFiniteNumber, + updates: zRecord, + expectedUpdatedAt: z.string().optional(), +}); + +const zAdjustGeneralResources = z + .object({ + type: z.literal('adjustGeneralResources'), + reason: z.string().optional(), + adjustments: z + .array( + z + .object({ + generalId: zFiniteNumber, + goldDelta: zFiniteNumber.optional(), + riceDelta: zFiniteNumber.optional(), + }) + .refine((value) => value.goldDelta !== undefined || value.riceDelta !== undefined) + ) + .min(1), + }); + +const zPatchGeneral = z.object({ + type: z.literal('patchGeneral'), + generalId: zFiniteNumber, + patch: z.object({ + meta: zRecord.optional(), + turnTime: z.string().optional(), + stats: z + .object({ + leadership: zFiniteNumber.optional(), + strength: zFiniteNumber.optional(), + intelligence: zFiniteNumber.optional(), + }) + .optional(), + specialWar: z.string().optional(), + }), +}); + +const zGetStatus = z.object({ + type: z.literal('getStatus'), + requestId: z.string().optional(), +}); + +const zRun = z.object({ + type: z.literal('run'), + reason: zRunReason, + targetTime: z.string().optional(), + budget: zTurnRunBudget.optional(), +}); + +const zPause = z.object({ + type: z.literal('pause'), + reason: z.string().optional(), +}); + +const zResume = z.object({ + type: z.literal('resume'), + reason: z.string().optional(), +}); + +const zShutdown = z.object({ + type: z.literal('shutdown'), + reason: z.string().optional(), +}); + +const normalizeAuctionFinalize: CommandNormalizer<'auctionFinalize'> = (envelope) => { + const command = parseWith(zAuctionFinalize, envelope.command); + if (!command) { + return null; + } + return { ...command, requestId: envelope.requestId }; +}; + +const normalizeAuctionBid: CommandNormalizer<'auctionBid'> = (envelope) => { + const command = parseWith(zAuctionBid, envelope.command); + if (!command) { + return null; + } + return { ...command, requestId: envelope.requestId }; +}; + +const normalizeTroopJoin: CommandNormalizer<'troopJoin'> = (envelope) => { + const command = parseWith(zTroopJoin, envelope.command); + if (!command) { + return null; + } + return { ...command, requestId: envelope.requestId }; +}; + +const normalizeTroopExit: CommandNormalizer<'troopExit'> = (envelope) => { + const command = parseWith(zTroopExit, envelope.command); + if (!command) { + return null; + } + return { ...command, requestId: envelope.requestId }; +}; + +const normalizeDieOnPrestart: CommandNormalizer<'dieOnPrestart'> = (envelope) => { + const command = parseWith(zDieOnPrestart, envelope.command); + if (!command) { + return null; + } + return { ...command, requestId: envelope.requestId }; +}; + +const normalizeBuildNationCandidate: CommandNormalizer<'buildNationCandidate'> = (envelope) => { + const command = parseWith(zBuildNationCandidate, envelope.command); + if (!command) { + return null; + } + return { ...command, requestId: envelope.requestId }; +}; + +const normalizeInstantRetreat: CommandNormalizer<'instantRetreat'> = (envelope) => { + const command = parseWith(zInstantRetreat, envelope.command); + if (!command) { + return null; + } + return { ...command, requestId: envelope.requestId }; +}; + +const normalizeVacation: CommandNormalizer<'vacation'> = (envelope) => { + const command = parseWith(zVacation, envelope.command); + if (!command) { + return null; + } + return { ...command, requestId: envelope.requestId }; +}; + +const normalizeSetMySetting: CommandNormalizer<'setMySetting'> = (envelope) => { + const command = parseWith(zSetMySetting, envelope.command); + if (!command) { + return null; + } + return { ...command, requestId: envelope.requestId }; +}; + +const normalizeDropItem: CommandNormalizer<'dropItem'> = (envelope) => { + const command = parseWith(zDropItem, envelope.command); + if (!command) { + return null; + } + return { ...command, requestId: envelope.requestId }; +}; + +const normalizeChangePermission: CommandNormalizer<'changePermission'> = (envelope) => { + const command = parseWith(zChangePermission, envelope.command); + if (!command) { + return null; + } + return { ...command, requestId: envelope.requestId }; +}; + +const normalizeKick: CommandNormalizer<'kick'> = (envelope) => { + const command = parseWith(zKick, envelope.command); + if (!command) { + return null; + } + return { ...command, requestId: envelope.requestId }; +}; + +const normalizeAppoint: CommandNormalizer<'appoint'> = (envelope) => { + const command = parseWith(zAppoint, envelope.command); + if (!command) { + return null; + } + return { ...command, requestId: envelope.requestId }; +}; + +const normalizeTournamentRefund: CommandNormalizer<'tournamentRefund'> = (envelope) => { + const command = parseWith(zTournamentRefund, envelope.command); + if (!command) { + return null; + } + return { ...command, requestId: envelope.requestId }; +}; + +const normalizeTournamentBettingPayout: CommandNormalizer<'tournamentBettingPayout'> = (envelope) => { + const command = parseWith(zTournamentBettingPayout, envelope.command); + if (!command) { + return null; + } + return { ...command, requestId: envelope.requestId }; +}; + +const normalizeTournamentReward: CommandNormalizer<'tournamentReward'> = (envelope) => { + const command = parseWith(zTournamentReward, envelope.command); + if (!command) { + return null; + } + return { ...command, requestId: envelope.requestId }; +}; + +const normalizeSetNationMeta: CommandNormalizer<'setNationMeta'> = (envelope) => { + const command = parseWith(zSetNationMeta, envelope.command); + if (!command) { + return null; + } + return { ...command, requestId: envelope.requestId }; +}; + +const normalizeAdjustGeneralResources: CommandNormalizer<'adjustGeneralResources'> = (envelope) => { + const command = parseWith(zAdjustGeneralResources, envelope.command); + if (!command) { + return null; + } + return { ...command, requestId: envelope.requestId }; +}; + +const normalizePatchGeneral: CommandNormalizer<'patchGeneral'> = (envelope) => { + const command = parseWith(zPatchGeneral, envelope.command); + if (!command) { + return null; + } + return { ...command, requestId: envelope.requestId }; +}; + +const normalizeGetStatus: CommandNormalizer<'getStatus'> = (envelope) => { + const command = parseWith(zGetStatus, envelope.command); + if (!command) { + return null; + } + return { + type: 'getStatus', + requestId: command.requestId ?? envelope.requestId, + }; +}; + +const normalizeRun: CommandNormalizer<'run'> = (envelope) => parseWith(zRun, envelope.command); +const normalizePause: CommandNormalizer<'pause'> = (envelope) => parseWith(zPause, envelope.command); +const normalizeResume: CommandNormalizer<'resume'> = (envelope) => parseWith(zResume, envelope.command); +const normalizeShutdown: CommandNormalizer<'shutdown'> = (envelope) => parseWith(zShutdown, envelope.command); + +const normalizers: CommandNormalizerMap = { + auctionFinalize: normalizeAuctionFinalize, + auctionBid: normalizeAuctionBid, + troopJoin: normalizeTroopJoin, + troopExit: normalizeTroopExit, + dieOnPrestart: normalizeDieOnPrestart, + buildNationCandidate: normalizeBuildNationCandidate, + instantRetreat: normalizeInstantRetreat, + vacation: normalizeVacation, + setMySetting: normalizeSetMySetting, + dropItem: normalizeDropItem, + changePermission: normalizeChangePermission, + kick: normalizeKick, + appoint: normalizeAppoint, + tournamentRefund: normalizeTournamentRefund, + tournamentBettingPayout: normalizeTournamentBettingPayout, + tournamentReward: normalizeTournamentReward, + setNationMeta: normalizeSetNationMeta, + adjustGeneralResources: normalizeAdjustGeneralResources, + patchGeneral: normalizePatchGeneral, + getStatus: normalizeGetStatus, + run: normalizeRun, + pause: normalizePause, + resume: normalizeResume, + shutdown: normalizeShutdown, +}; + +export const normalizeTurnDaemonCommand = (envelope: TurnDaemonCommandEnvelope): TurnDaemonCommand | null => { + const commandType = envelope.command.type as TurnDaemonCommandType; + const normalizer = normalizers[commandType]; + if (!normalizer) { + return null; + } + return normalizer(envelope) as TurnDaemonCommand | null; +}; diff --git a/app/game-engine/src/turn/worldCommandHandler.ts b/app/game-engine/src/turn/worldCommandHandler.ts index 3c10ae4..c4e54d9 100644 --- a/app/game-engine/src/turn/worldCommandHandler.ts +++ b/app/game-engine/src/turn/worldCommandHandler.ts @@ -753,50 +753,37 @@ export const createTurnDaemonCommandHandler = (options: { tournamentRewardFinalizer: options.tournamentRewardFinalizer, }; + type HandlerMap = Partial Promise>>; + + const handlers: HandlerMap = { + troopJoin: (command) => handleTroopJoin(ctx, command as Extract), + troopExit: (command) => handleTroopExit(ctx, command as Extract), + dieOnPrestart: (command) => handleDieOnPrestart(ctx, command as Extract), + buildNationCandidate: (command) => handleBuildNationCandidate(ctx, command as Extract), + instantRetreat: (command) => handleInstantRetreat(ctx, command as Extract), + vacation: (command) => handleVacation(ctx, command as Extract), + setMySetting: (command) => handleSetMySetting(ctx, command as Extract), + dropItem: (command) => handleDropItem(ctx, command as Extract), + auctionFinalize: (command) => handleAuctionFinalize(ctx, command as Extract), + auctionBid: (command) => handleAuctionBid(ctx, command as Extract), + changePermission: (command) => handleChangePermission(ctx, command as Extract), + kick: (command) => handleKick(ctx, command as Extract), + appoint: (command) => handleAppoint(ctx, command as Extract), + tournamentRefund: (command) => handleTournamentRefund(ctx, command as Extract), + tournamentBettingPayout: (command) => handleTournamentBettingPayout(ctx, command as Extract), + tournamentReward: (command) => handleTournamentReward(ctx, command as Extract), + setNationMeta: (command) => handleSetNationMeta(ctx, command as Extract), + adjustGeneralResources: (command) => handleAdjustGeneralResources(ctx, command as Extract), + patchGeneral: (command) => handlePatchGeneral(ctx, command as Extract), + }; + return { handle: async (command): Promise => { - switch (command.type) { - case 'troopJoin': - return handleTroopJoin(ctx, command); - case 'troopExit': - return handleTroopExit(ctx, command); - case 'dieOnPrestart': - return handleDieOnPrestart(ctx, command); - case 'buildNationCandidate': - return handleBuildNationCandidate(ctx, command); - case 'instantRetreat': - return handleInstantRetreat(ctx, command); - case 'vacation': - return handleVacation(ctx, command); - case 'setMySetting': - return handleSetMySetting(ctx, command); - case 'dropItem': - return handleDropItem(ctx, command); - case 'auctionFinalize': - return handleAuctionFinalize(ctx, command); - case 'auctionBid': - return handleAuctionBid(ctx, command); - case 'changePermission': - return handleChangePermission(ctx, command); - case 'kick': - return handleKick(ctx, command); - case 'appoint': - return handleAppoint(ctx, command); - case 'tournamentRefund': - return handleTournamentRefund(ctx, command); - case 'tournamentBettingPayout': - return handleTournamentBettingPayout(ctx, command); - case 'tournamentReward': - return handleTournamentReward(ctx, command); - case 'setNationMeta': - return handleSetNationMeta(ctx, command); - case 'adjustGeneralResources': - return handleAdjustGeneralResources(ctx, command); - case 'patchGeneral': - return handlePatchGeneral(ctx, command); - default: - return null; + const handler = handlers[command.type]; + if (!handler) { + return null; } + return handler(command); }, }; }; diff --git a/packages/common/src/turnDaemon/types.ts b/packages/common/src/turnDaemon/types.ts index f795ab7..7f23d46 100644 --- a/packages/common/src/turnDaemon/types.ts +++ b/packages/common/src/turnDaemon/types.ts @@ -304,3 +304,12 @@ export type TurnDaemonEvent = | { type: 'runCompleted'; at: string; result: TurnRunResult } | { type: 'runFailed'; at: string; error: string } | { type: 'commandResult'; result: TurnDaemonCommandResult }; + +export type TurnDaemonCommandType = TurnDaemonCommand['type']; + +export type TurnDaemonCommandByType = Extract; + +export type TurnDaemonCommandResultByType = Extract< + TurnDaemonCommandResult, + { type: T } +>;