fix: 커맨드 차등 생명주기와 로그 그래프를 보강
장수 55종과 수뇌 35종의 상태, 로그, 메시지, 예약 턴 비교를 닫습니다. 실제 시나리오 프로필과 대표 PostgreSQL 수명주기, 즉시 외교와 출병 회귀를 추가하고 발견된 Ref 로그 및 생성 장수 저장 차이를 교정합니다.
This commit is contained in:
@@ -7,6 +7,7 @@ import {
|
||||
finalizeLogEntry,
|
||||
LogFormat,
|
||||
MESSAGE_MAILBOX_NATIONAL_BASE,
|
||||
orderLegacyActionLoggerFlush,
|
||||
resolveInstantDiplomacyResponse,
|
||||
sendMessage,
|
||||
type GeneralActionEffect,
|
||||
@@ -121,7 +122,7 @@ const persistEffects = async (
|
||||
logs.push(effect.entry);
|
||||
}
|
||||
}
|
||||
await persistLogs(db, logs, year, month, at);
|
||||
await persistLogs(db, orderLegacyActionLoggerFlush(logs), year, month, at);
|
||||
};
|
||||
|
||||
const refreshFrontStates = async (db: DatabaseClient, mapName: string, nationIds: number[]): Promise<number[]> => {
|
||||
|
||||
@@ -13,8 +13,9 @@ import {
|
||||
} from '../../turns/commandTable.js';
|
||||
import { loadMapDefinitionByName } from '../../maps/mapDefinition.js';
|
||||
import {
|
||||
assertReservedTurnActionAvailable,
|
||||
buildEquipmentTradeItemOptions,
|
||||
parseReservedTurnArgs,
|
||||
parseRegisteredTurnArgs,
|
||||
TURN_COMMAND_NATION_COLORS,
|
||||
type TurnCommandInputOptions,
|
||||
} from '../../turns/commandInput.js';
|
||||
@@ -65,7 +66,7 @@ const buildBulkEntrySchema = (turnList: z.ZodType<number[]>) =>
|
||||
|
||||
const parseCommandArgs = async (scope: 'general' | 'nation', action: string, args: unknown) => {
|
||||
try {
|
||||
return await parseReservedTurnArgs(scope, action, args);
|
||||
return await parseRegisteredTurnArgs(scope, action, args);
|
||||
} catch (error) {
|
||||
throw new TRPCError({
|
||||
code: 'BAD_REQUEST',
|
||||
@@ -75,6 +76,22 @@ const parseCommandArgs = async (scope: 'general' | 'nation', action: string, arg
|
||||
}
|
||||
};
|
||||
|
||||
const assertScenarioCommandAvailable = async (
|
||||
scope: 'general' | 'nation',
|
||||
action: string,
|
||||
worldState: WorldStateRow
|
||||
): Promise<void> => {
|
||||
try {
|
||||
await assertReservedTurnActionAvailable(scope, action, asRecord(worldState.config).const);
|
||||
} catch (error) {
|
||||
throw new TRPCError({
|
||||
code: 'BAD_REQUEST',
|
||||
message: error instanceof Error ? error.message : 'Unavailable turn command.',
|
||||
cause: error,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const mutateReservedTurns = async <T>(mutation: () => Promise<T>): Promise<T> => {
|
||||
try {
|
||||
return await mutation();
|
||||
@@ -413,6 +430,7 @@ export const turnsRouter = router({
|
||||
const general = await getOwnedGeneral(ctx, input.generalId);
|
||||
const args = await parseCommandArgs('general', input.action, input.args);
|
||||
const worldState = await getReservationWorldState(ctx);
|
||||
await assertScenarioCommandAvailable('general', input.action, worldState);
|
||||
await assertReservedTurnPermission(worldState, general, 'general', input.action, args);
|
||||
|
||||
const snapshot = await mutateReservedTurns(() =>
|
||||
@@ -476,6 +494,7 @@ export const turnsRouter = router({
|
||||
);
|
||||
const worldState = await getReservationWorldState(ctx);
|
||||
for (const update of updates) {
|
||||
await assertScenarioCommandAvailable('general', update.action, worldState);
|
||||
await assertReservedTurnPermission(worldState, general, 'general', update.action, update.args);
|
||||
}
|
||||
const snapshot = await mutateReservedTurns(() =>
|
||||
@@ -515,6 +534,7 @@ export const turnsRouter = router({
|
||||
}
|
||||
const args = await parseCommandArgs('nation', input.action, input.args);
|
||||
const worldState = await getReservationWorldState(ctx);
|
||||
await assertScenarioCommandAvailable('nation', input.action, worldState);
|
||||
await assertReservedTurnPermission(worldState, general, 'nation', input.action, args);
|
||||
|
||||
const snapshot = await mutateReservedTurns(() =>
|
||||
@@ -628,6 +648,7 @@ export const turnsRouter = router({
|
||||
);
|
||||
const worldState = await getReservationWorldState(ctx);
|
||||
for (const update of updates) {
|
||||
await assertScenarioCommandAvailable('nation', update.action, worldState);
|
||||
await assertReservedTurnPermission(worldState, general, 'nation', update.action, update.args);
|
||||
}
|
||||
const snapshot = await mutateReservedTurns(() =>
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import {
|
||||
isGeneralTurnCommandKey,
|
||||
isNationTurnCommandKey,
|
||||
loadGeneralTurnCommandSpecs,
|
||||
loadNationTurnCommandSpecs,
|
||||
type GeneralTurnCommandSpec,
|
||||
@@ -9,7 +11,7 @@ import type { ItemModule } from '@sammo-ts/logic/items/types.js';
|
||||
import { resolveLegacyPurchasableItemKeys } from '@sammo-ts/logic/rewards/legacyUniqueItemPool.js';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { loadTurnCommandProfile } from '@sammo-ts/game-engine/turn/turnCommandProfile.js';
|
||||
import { loadScenarioTurnCommandProfile } from '@sammo-ts/game-engine/turn/turnCommandProfile.js';
|
||||
|
||||
export type TurnCommandOptionValue = string | number;
|
||||
|
||||
@@ -345,22 +347,35 @@ export const buildTurnCommandInputFields = (
|
||||
return Object.entries(properties).map(([key, schema]) => buildField(key, schema, required.has(key)));
|
||||
};
|
||||
|
||||
export const loadTurnCommandSpecs = async () => {
|
||||
const profile = await loadTurnCommandProfile();
|
||||
export const loadTurnCommandSpecs = async (scenarioConst?: unknown) => {
|
||||
const resolution = await loadScenarioTurnCommandProfile({ scenarioConst });
|
||||
const profile = resolution.profile;
|
||||
const [general, nation] = await Promise.all([
|
||||
loadGeneralTurnCommandSpecs(profile.general),
|
||||
loadNationTurnCommandSpecs(profile.nation),
|
||||
]);
|
||||
return { general, nation };
|
||||
return {
|
||||
general,
|
||||
nation,
|
||||
generalGroups: resolution.generalGroups,
|
||||
nationGroups: resolution.nationGroups,
|
||||
};
|
||||
};
|
||||
|
||||
export const parseReservedTurnArgs = async (
|
||||
export const parseRegisteredTurnArgs = async (
|
||||
scope: 'general' | 'nation',
|
||||
action: string,
|
||||
rawArgs: unknown
|
||||
): Promise<Record<string, unknown>> => {
|
||||
const specs = await loadTurnCommandSpecs();
|
||||
const spec = specs[scope].find((entry) => entry.key === action);
|
||||
const specs =
|
||||
scope === 'general'
|
||||
? isGeneralTurnCommandKey(action)
|
||||
? await loadGeneralTurnCommandSpecs([action])
|
||||
: []
|
||||
: isNationTurnCommandKey(action)
|
||||
? await loadNationTurnCommandSpecs([action])
|
||||
: [];
|
||||
const spec = specs[0];
|
||||
if (!spec) {
|
||||
throw new Error(`Unknown ${scope} turn command: ${action}`);
|
||||
}
|
||||
@@ -369,3 +384,24 @@ export const parseReservedTurnArgs = async (
|
||||
}
|
||||
return spec.argsSchema.parse(rawArgs);
|
||||
};
|
||||
|
||||
export const assertReservedTurnActionAvailable = async (
|
||||
scope: 'general' | 'nation',
|
||||
action: string,
|
||||
scenarioConst?: unknown
|
||||
): Promise<void> => {
|
||||
const specs = await loadTurnCommandSpecs(scenarioConst);
|
||||
if (!specs[scope].some((entry) => entry.key === action)) {
|
||||
throw new Error(`Unknown ${scope} turn command: ${action}`);
|
||||
}
|
||||
};
|
||||
|
||||
export const parseReservedTurnArgs = async (
|
||||
scope: 'general' | 'nation',
|
||||
action: string,
|
||||
rawArgs: unknown,
|
||||
scenarioConst?: unknown
|
||||
): Promise<Record<string, unknown>> => {
|
||||
await assertReservedTurnActionAvailable(scope, action, scenarioConst);
|
||||
return parseRegisteredTurnArgs(scope, action, rawArgs);
|
||||
};
|
||||
|
||||
@@ -142,15 +142,6 @@ const REF_GENERAL_COMMAND_GROUPS = [
|
||||
commands: ReadonlyArray<GeneralTurnCommandKey>;
|
||||
}>;
|
||||
|
||||
const REF_GENERAL_CATEGORY_ORDER = new Map<string, number>(
|
||||
REF_GENERAL_COMMAND_GROUPS.map(({ category }, index) => [category, index] as const)
|
||||
);
|
||||
const REF_GENERAL_COMMAND_POSITION = new Map<string, { category: string; index: number }>(
|
||||
REF_GENERAL_COMMAND_GROUPS.flatMap(({ category, commands }) =>
|
||||
commands.map((command, index) => [command, { category, index }] as const)
|
||||
)
|
||||
);
|
||||
|
||||
const REF_NATION_COMMAND_GROUPS = [
|
||||
{
|
||||
category: '휴식',
|
||||
@@ -190,15 +181,6 @@ const REF_NATION_COMMAND_GROUPS = [
|
||||
commands: ReadonlyArray<NationTurnCommandKey>;
|
||||
}>;
|
||||
|
||||
const REF_NATION_CATEGORY_ORDER = new Map<string, number>(
|
||||
REF_NATION_COMMAND_GROUPS.map(({ category }, index) => [category, index] as const)
|
||||
);
|
||||
const REF_NATION_COMMAND_POSITION = new Map<string, { category: string; index: number }>(
|
||||
REF_NATION_COMMAND_GROUPS.flatMap(({ category, commands }) =>
|
||||
commands.map((command, index) => [command, { category, index }] as const)
|
||||
)
|
||||
);
|
||||
|
||||
const INPUT_REQUIREMENT_KINDS = new Set<RequirementKey['kind']>([
|
||||
'destGeneral',
|
||||
'destCity',
|
||||
@@ -746,34 +728,22 @@ const buildGroups = (entries: CommandEntry[], ctx: ConstraintContext, view: Stat
|
||||
}));
|
||||
};
|
||||
|
||||
const projectRefGeneralCommandGroups = (entries: CommandEntry[]): CommandEntry[] =>
|
||||
entries
|
||||
.map((entry, profileIndex) => {
|
||||
const refPosition = REF_GENERAL_COMMAND_POSITION.get(entry.definition.key as GeneralTurnCommandKey);
|
||||
return {
|
||||
entry: refPosition ? { ...entry, category: refPosition.category } : entry,
|
||||
categoryIndex:
|
||||
REF_GENERAL_CATEGORY_ORDER.get(refPosition?.category ?? entry.category) ?? Number.MAX_SAFE_INTEGER,
|
||||
commandIndex: refPosition?.index ?? profileIndex,
|
||||
profileIndex,
|
||||
};
|
||||
})
|
||||
.sort(
|
||||
(left, right) =>
|
||||
left.categoryIndex - right.categoryIndex ||
|
||||
left.commandIndex - right.commandIndex ||
|
||||
left.profileIndex - right.profileIndex
|
||||
)
|
||||
.map(({ entry }) => entry);
|
||||
type CommandGroupLayout = ReadonlyArray<{ category: string; commands: ReadonlyArray<string> }>;
|
||||
|
||||
const projectRefNationCommandGroups = (entries: CommandEntry[]): CommandEntry[] =>
|
||||
entries
|
||||
const projectCommandGroups = (entries: CommandEntry[], layout: CommandGroupLayout): CommandEntry[] => {
|
||||
const categoryOrder = new Map<string, number>(layout.map(({ category }, index) => [category, index] as const));
|
||||
const commandPosition = new Map<string, { category: string; index: number }>(
|
||||
layout.flatMap(({ category, commands }) =>
|
||||
commands.map((command, index) => [command, { category, index }] as const)
|
||||
)
|
||||
);
|
||||
|
||||
return entries
|
||||
.map((entry, profileIndex) => {
|
||||
const refPosition = REF_NATION_COMMAND_POSITION.get(entry.definition.key as NationTurnCommandKey);
|
||||
const refPosition = commandPosition.get(entry.definition.key);
|
||||
return {
|
||||
entry: refPosition ? { ...entry, category: refPosition.category } : entry,
|
||||
categoryIndex:
|
||||
REF_NATION_CATEGORY_ORDER.get(refPosition?.category ?? entry.category) ?? Number.MAX_SAFE_INTEGER,
|
||||
categoryIndex: categoryOrder.get(refPosition?.category ?? entry.category) ?? Number.MAX_SAFE_INTEGER,
|
||||
commandIndex: refPosition?.index ?? profileIndex,
|
||||
profileIndex,
|
||||
};
|
||||
@@ -785,6 +755,7 @@ const projectRefNationCommandGroups = (entries: CommandEntry[]): CommandEntry[]
|
||||
left.profileIndex - right.profileIndex
|
||||
)
|
||||
.map(({ entry }) => entry);
|
||||
};
|
||||
|
||||
export const buildTurnCommandTable = async (options: {
|
||||
worldState: WorldStateRow;
|
||||
@@ -814,7 +785,13 @@ export const buildTurnCommandTable = async (options: {
|
||||
};
|
||||
|
||||
const env = buildCommandEnv(options.worldState);
|
||||
const { general: generalSpecs, nation: nationSpecs } = await loadTurnCommandSpecs();
|
||||
const scenarioConst = asRecord(options.worldState.config).const;
|
||||
const {
|
||||
general: generalSpecs,
|
||||
nation: nationSpecs,
|
||||
generalGroups,
|
||||
nationGroups,
|
||||
} = await loadTurnCommandSpecs(scenarioConst);
|
||||
const generalEntries = buildEntries(env, generalSpecs, {
|
||||
foundingAvailable:
|
||||
options.realNationCount === undefined
|
||||
@@ -824,8 +801,12 @@ export const buildTurnCommandTable = async (options: {
|
||||
const nationEntries = buildEntries(env, nationSpecs);
|
||||
|
||||
return {
|
||||
general: buildGroups(projectRefGeneralCommandGroups(generalEntries), ctx, view),
|
||||
nation: buildGroups(projectRefNationCommandGroups(nationEntries), ctx, view),
|
||||
general: buildGroups(
|
||||
projectCommandGroups(generalEntries, generalGroups ?? REF_GENERAL_COMMAND_GROUPS),
|
||||
ctx,
|
||||
view
|
||||
),
|
||||
nation: buildGroups(projectCommandGroups(nationEntries, nationGroups ?? REF_NATION_COMMAND_GROUPS), ctx, view),
|
||||
inputOptions: options.inputOptions ?? {
|
||||
cities: [],
|
||||
nations: [],
|
||||
@@ -850,7 +831,8 @@ export const evaluateReservedTurnPermission = async (options: {
|
||||
action: string;
|
||||
args: Record<string, unknown>;
|
||||
}): Promise<ConstraintResult> => {
|
||||
const specs = await loadTurnCommandSpecs();
|
||||
const scenarioConst = asRecord(options.worldState.config).const;
|
||||
const specs = await loadTurnCommandSpecs(scenarioConst);
|
||||
const spec = specs[options.scope].find((entry) => entry.key === options.action);
|
||||
if (!spec) {
|
||||
throw new Error(`Unknown ${options.scope} turn command: ${options.action}`);
|
||||
|
||||
Reference in New Issue
Block a user