From 21d8507fc57dfcfbb3f20c33dbd61e95d78aa07c Mon Sep 17 00:00:00 2001 From: Hide_D Date: Sun, 25 Jan 2026 06:17:27 +0000 Subject: [PATCH] =?UTF-8?q?feat:=20=EC=A3=BD=EC=9D=8C=20=EC=97=B0=EB=8F=84?= =?UTF-8?q?=EC=97=90=20=EB=94=B0=EB=A5=B8=20killturn=20=EA=B3=84=EC=82=B0?= =?UTF-8?q?=20=EB=A1=9C=EC=A7=81=20=EC=B6=94=EA=B0=80=20=EB=B0=8F=20?= =?UTF-8?q?=EA=B4=80=EB=A0=A8=20=EB=A9=94=ED=83=80=20=EB=8D=B0=EC=9D=B4?= =?UTF-8?q?=ED=84=B0=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/scenario/scenarioSeeder.ts | 45 ++++++++++++++++--- .../src/actions/turn/general/che_인재탐색.ts | 24 +++++++++- .../src/actions/turn/nation/che_의병모집.ts | 26 +++++++++-- packages/logic/src/world/bootstrap.ts | 17 ++++++- 4 files changed, 99 insertions(+), 13 deletions(-) diff --git a/app/game-engine/src/scenario/scenarioSeeder.ts b/app/game-engine/src/scenario/scenarioSeeder.ts index 8ad6125..87d4f02 100644 --- a/app/game-engine/src/scenario/scenarioSeeder.ts +++ b/app/game-engine/src/scenario/scenarioSeeder.ts @@ -1,6 +1,6 @@ import { createGamePostgresConnector, type InputJsonValue, type TurnEngineEventCreateManyInput } from '@sammo-ts/infra'; import { asRecord } from '@sammo-ts/common'; -import { buildScenarioBootstrap, type ScenarioBootstrapWarning, type WorldSeedPayload } from '@sammo-ts/logic'; +import { buildScenarioBootstrap, type GeneralMeta, type ScenarioBootstrapWarning, type WorldSeedPayload } from '@sammo-ts/logic'; import type { MapLoaderOptions } from './mapLoader.js'; import { loadMapDefinitionByName } from './mapLoader.js'; @@ -143,6 +143,20 @@ const resolveGeneralAge = (startYear: number | null, birthYear: number): number return Math.max(startYear - birthYear, 0); }; +const resolveKillturnFromDeathYear = ( + currentYear: number, + currentMonth: number, + deathYear: number, + fallback: number +): number => { + if (!Number.isFinite(deathYear) || deathYear <= 0) { + return fallback; + } + const deathMonth = Math.floor(Math.random() * 12) + 1; + const diff = (deathYear - currentYear) * 12 + (deathMonth - currentMonth); + return Math.max(diff, 0); +}; + const buildEventRows = (rows: unknown[], targetOverride?: string): TurnEngineEventCreateManyInput[] => { const result: TurnEngineEventCreateManyInput[] = []; @@ -369,11 +383,30 @@ export const seedScenarioToDatabase = async (options: ScenarioSeedOptions): Prom specialCode: general.special ?? 'None', special2Code: general.specialWar ?? 'None', lastTurn: asJson({}), - meta: asJson({ - npcType: general.npcType, - crewTypeId: general.crewTypeId, - ...general.meta, - }), + meta: asJson( + (() => { + const meta = { ...general.meta } as Record; + if (typeof meta.birthYear !== 'number' || !Number.isFinite(meta.birthYear)) { + meta.birthYear = general.birthYear; + } + delete meta.deathYear; + delete meta.deadYear; + const fallbackKillturn = + typeof meta.killturn === 'number' && Number.isFinite(meta.killturn) ? meta.killturn : 0; + const killturn = resolveKillturnFromDeathYear( + startState.currentYear, + startState.currentMonth, + general.deathYear, + fallbackKillturn + ); + return { + ...meta, + killturn, + npcType: general.npcType, + crewTypeId: general.crewTypeId, + } satisfies GeneralMeta; + })() + ), penalty: asJson({}), })), }); diff --git a/packages/logic/src/actions/turn/general/che_인재탐색.ts b/packages/logic/src/actions/turn/general/che_인재탐색.ts index 9599575..e8ba34f 100644 --- a/packages/logic/src/actions/turn/general/che_인재탐색.ts +++ b/packages/logic/src/actions/turn/general/che_인재탐색.ts @@ -48,6 +48,7 @@ export interface TalentScoutResolveContext< TriggerState extends GeneralTriggerState = GeneralTriggerState, > extends GeneralActionResolveContext { currentYear: number; + currentMonth: number; worldSummary: TalentScoutWorldSummary; generalPool?: TalentScoutCandidate[]; cityPool?: City[]; @@ -86,6 +87,20 @@ const DEFAULT_MAX_AGE = 25; const DEFAULT_DEATH_MIN = 10; const DEFAULT_DEATH_MAX = 50; +const resolveKillturnFromDeathYear = ( + currentYear: number, + currentMonth: number, + deathYear: number, + rng: RandomGenerator +): number => { + if (!Number.isFinite(deathYear) || deathYear <= 0) { + return 0; + } + const deathMonth = randomRangeInt(rng, 1, 12); + const diff = (deathYear - currentYear) * 12 + (deathMonth - currentMonth); + return Math.max(diff, 0); +}; + const addMetaValue = ( meta: Record, key: string, @@ -302,14 +317,18 @@ export class ActionResolver< ? this.env.decorateName(resolvedCandidate.name, NPC_TYPE) : resolvedCandidate.name; const meta: GeneralMeta = { - killturn: context.general.meta.killturn, + killturn: resolveKillturnFromDeathYear( + context.currentYear, + context.currentMonth, + deathYear, + context.rng + ), npcType: NPC_TYPE, crewTypeId: this.env.defaultCrewTypeId, }; addMetaValue(meta, 'affinity', resolvedCandidate.affinity ?? null); addMetaValue(meta, 'picture', resolvedCandidate.picture ?? null); addMetaValue(meta, 'birthYear', birthYear); - addMetaValue(meta, 'deathYear', deathYear); addMetaValue(meta, 'text', resolvedCandidate.text ?? null); const newGeneral = buildRecruitmentGeneral({ @@ -393,6 +412,7 @@ export class ActionDefinition< export const actionContextBuilder: ActionContextBuilder = (base, options) => ({ ...base, currentYear: options.world.currentYear, + currentMonth: options.world.currentMonth, worldSummary: buildWorldSummary(options.worldRef), createGeneralId: options.createGeneralId, }); diff --git a/packages/logic/src/actions/turn/nation/che_의병모집.ts b/packages/logic/src/actions/turn/nation/che_의병모집.ts index 35dc71f..0508bf3 100644 --- a/packages/logic/src/actions/turn/nation/che_의병모집.ts +++ b/packages/logic/src/actions/turn/nation/che_의병모집.ts @@ -46,6 +46,7 @@ export interface VolunteerRecruitResolveContext< TriggerState extends GeneralTriggerState = GeneralTriggerState, > extends GeneralActionResolveContext { currentYear: number; + currentMonth: number; startYear: number; averageNationGeneralCount: number; nationAverageStats?: StatBlock; @@ -91,6 +92,20 @@ const DEFAULT_KILLTURN_MIN = 64; const DEFAULT_KILLTURN_MAX = 70; const DEFAULT_SPEC_AGE = 19; +const resolveKillturnFromDeathYear = ( + currentYear: number, + currentMonth: number, + deathYear: number, + rng: RandomGenerator +): number => { + if (!Number.isFinite(deathYear) || deathYear <= 0) { + return 0; + } + const deathMonth = randomRangeInt(rng, 1, 12); + const diff = (deathYear - currentYear) * 12 + (deathMonth - currentMonth); + return Math.max(diff, 0); +}; + const addMetaValue = ( meta: Record, key: string, @@ -269,17 +284,21 @@ export class ActionResolver< const deathYear = context.currentYear + deathYears; const stats = resolveStats(context, context.rng, this.env, candidate); const meta: GeneralMeta = { - killturn: randomRangeInt(context.rng, killTurnMin, killTurnMax), + killturn: resolveKillturnFromDeathYear( + context.currentYear, + context.currentMonth, + deathYear, + context.rng + ), npcType: NPC_TYPE, crewTypeId: this.env.defaultCrewTypeId, }; addMetaValue(meta, 'affinity', candidate.affinity ?? null); addMetaValue(meta, 'picture', candidate.picture ?? null); addMetaValue(meta, 'birthYear', birthYear); - addMetaValue(meta, 'deathYear', deathYear); addMetaValue(meta, 'specAge', DEFAULT_SPEC_AGE); addMetaValue(meta, 'specAge2', DEFAULT_SPEC_AGE); - addMetaValue(meta, 'killturn', meta.killturn); + addMetaValue(meta, 'killturn', meta.killturn || randomRangeInt(context.rng, killTurnMin, killTurnMax)); addMetaValue(meta, 'text', candidate.text ?? null); const newGeneral = buildRecruitmentGeneral({ @@ -357,6 +376,7 @@ export const actionContextBuilder: ActionContextBuilder = (base, options) => { return { ...base, currentYear: options.world.currentYear, + currentMonth: options.world.currentMonth, startYear: resolveStartYear(options.world, options.scenarioMeta), averageNationGeneralCount: buildAverageNationGeneralCount(options.worldRef), nationAverageStats: nationSummary.averageStats, diff --git a/packages/logic/src/world/bootstrap.ts b/packages/logic/src/world/bootstrap.ts index 6c4b816..0744053 100644 --- a/packages/logic/src/world/bootstrap.ts +++ b/packages/logic/src/world/bootstrap.ts @@ -140,6 +140,20 @@ const resolveDeathYear = (deathYear: number, birthYear: number, startYear: numbe return 0; }; +const resolveKillturnFromDeathYear = ( + currentYear: number | null, + currentMonth: number, + deathYear: number, + fallback: number +): number => { + if (currentYear === null || !Number.isFinite(deathYear) || deathYear <= 0) { + return fallback; + } + const deathMonth = Math.floor(Math.random() * 12) + 1; + const diff = (deathYear - currentYear) * 12 + (deathMonth - currentMonth); + return Math.max(diff, 0); +}; + const resolveAge = (startYear: number | null, birthYear: number): number => { if (startYear === null || birthYear <= 0) { return 20; @@ -299,7 +313,7 @@ const buildGeneralSeeds = ( seeds.push(seed); const generalMeta: GeneralMeta = { - killturn: DEFAULT_GENERAL_KILLTURN, + killturn: resolveKillturnFromDeathYear(scenario.startYear, 1, deathYear, DEFAULT_GENERAL_KILLTURN), npcType, crewTypeId: defaultCrewTypeId, }; @@ -313,7 +327,6 @@ const buildGeneralSeeds = ( addTriggerMeta(generalMeta, 'book', row.book ?? undefined); addTriggerMeta(generalMeta, 'item', row.item ?? undefined); addTriggerMeta(generalMeta, 'birthYear', birthYear); - addTriggerMeta(generalMeta, 'deathYear', deathYear); addTriggerMeta(generalMeta, 'text', row.text ?? undefined); generals.push({