From 568084927ccbe1d48780ce3810f13c9be2f3896f Mon Sep 17 00:00:00 2001 From: Hide_D Date: Wed, 4 Feb 2026 12:03:47 +0000 Subject: [PATCH] feat: integrate unique lottery system into general actions - Added `tryApplyUniqueLottery` function to various action definitions to apply unique item rewards based on specific actions. - Enhanced the `uniqueLottery.ts` module to handle unique item acquisition and logging. - Created unit tests for the unique lottery feature to ensure proper functionality during general commands. - Updated action definitions to include unique item acquisition for actions such as training, technology research, and item procurement. --- .../src/turn/reservedTurnHandler.ts | 136 ++++++++++++++ .../test/uniqueLotteryCommand.test.ts | 176 ++++++++++++++++++ .../logic/src/actions/turn/actionContext.ts | 2 + .../src/actions/turn/general/che_강행.ts | 3 + .../src/actions/turn/general/che_거병.ts | 3 + .../src/actions/turn/general/che_건국.ts | 3 + .../src/actions/turn/general/che_견문.ts | 2 + .../src/actions/turn/general/che_군량매매.ts | 3 + .../src/actions/turn/general/che_귀환.ts | 3 + .../src/actions/turn/general/che_기술연구.ts | 2 + .../src/actions/turn/general/che_단련.ts | 2 + .../src/actions/turn/general/che_등용.ts | 3 + .../src/actions/turn/general/che_랜덤임관.ts | 3 + .../src/actions/turn/general/che_물자조달.ts | 3 + .../src/actions/turn/general/che_사기진작.ts | 2 + .../src/actions/turn/general/che_상업투자.ts | 2 + .../src/actions/turn/general/che_숙련전환.ts | 2 + .../src/actions/turn/general/che_은퇴.ts | 3 + .../src/actions/turn/general/che_이동.ts | 3 + .../src/actions/turn/general/che_인재탐색.ts | 4 + .../src/actions/turn/general/che_임관.ts | 3 + .../turn/general/che_전투특기초기화.ts | 2 + .../src/actions/turn/general/che_정착장려.ts | 2 + .../src/actions/turn/general/che_주민선정.ts | 2 + .../src/actions/turn/general/che_집합.ts | 2 + .../src/actions/turn/general/che_징병.ts | 2 + .../src/actions/turn/general/che_출병.ts | 3 + .../src/actions/turn/general/che_헌납.ts | 3 + .../src/actions/turn/general/che_훈련.ts | 2 + packages/logic/src/rewards/uniqueLottery.ts | 82 +++++++- 30 files changed, 461 insertions(+), 2 deletions(-) create mode 100644 app/game-engine/test/uniqueLotteryCommand.test.ts diff --git a/app/game-engine/src/turn/reservedTurnHandler.ts b/app/game-engine/src/turn/reservedTurnHandler.ts index cdb5a53..d19471a 100644 --- a/app/game-engine/src/turn/reservedTurnHandler.ts +++ b/app/game-engine/src/turn/reservedTurnHandler.ts @@ -20,6 +20,15 @@ import { defaultActionContextBuilder, evaluateConstraints, resolveGeneralAction, + ITEM_KEYS, + buildGenericUniqueSeed, + countOccupiedUniqueItems, + createItemModuleRegistry, + loadItemModules, + resolveUniqueConfig, + rollUniqueLottery, + type ItemModule, + type UniqueLotteryRunner, } from '@sammo-ts/logic'; import { LogCategory, LogFormat, LogScope } from '@sammo-ts/logic'; import { asRecord, LiteHashDRBG, RandUtil } from '@sammo-ts/common'; @@ -76,6 +85,122 @@ const serializeSeed = (...values: Array): string => .map((value) => (typeof value === 'string' ? `str(${value.length},${value})` : `int(${Math.floor(value)})`)) .join('|'); +const joinYearMonth = (year: number, month: number): number => year * 12 + month - 1; + +const readMetaNumber = (meta: Record, key: string, fallback: number): number => { + const value = meta[key]; + if (typeof value === 'number' && Number.isFinite(value)) { + return Math.floor(value); + } + if (typeof value === 'string') { + const parsed = Number(value); + if (Number.isFinite(parsed)) { + return Math.floor(parsed); + } + } + return fallback; +}; + +const readMetaBool = (meta: Record, key: string, fallback = false): boolean => { + const value = meta[key]; + if (typeof value === 'boolean') { + return value; + } + if (typeof value === 'number') { + return value !== 0; + } + if (typeof value === 'string') { + const lowered = value.toLowerCase(); + if (lowered === 'true' || lowered === '1') { + return true; + } + if (lowered === 'false' || lowered === '0') { + return false; + } + } + return fallback; +}; + +const resolveStartYear = (world: TurnWorldState, scenarioMeta?: ScenarioMeta): number => { + if (typeof scenarioMeta?.startYear === 'number') { + return scenarioMeta.startYear; + } + const worldMeta = asRecord(world.meta); + const scenarioMetaRecord = asRecord(worldMeta.scenarioMeta); + return readMetaNumber(scenarioMetaRecord, 'startYear', world.currentYear); +}; + +const buildUniqueLotteryRunner = (options: { + world: TurnWorldState; + worldView: WorldView | null; + scenarioMeta?: ScenarioMeta; + seedBase: string; + itemRegistry: Map; + uniqueConfig: ReturnType; +}): UniqueLotteryRunner => { + if (!options.worldView) { + return () => null; + } + const worldView = options.worldView; + const world = options.world; + const worldMeta = asRecord(world.meta); + const startYear = resolveStartYear(world, options.scenarioMeta); + const initYear = readMetaNumber(worldMeta, 'initYear', startYear); + const initMonth = readMetaNumber(worldMeta, 'initMonth', 1); + const scenarioId = readMetaNumber(worldMeta, 'scenarioId', 0); + const minMonthToAllowInherit = options.uniqueConfig.minMonthToAllowInheritItem; + + return ({ acquireType, reason, general }) => { + if (general.npcState >= 2) { + return null; + } + const allGenerals = worldView.listGenerals(); + const userCount = allGenerals.filter((entry) => entry.npcState < 2).length; + if (userCount <= 0) { + return null; + } + const generalItemsList = allGenerals.map((entry) => + entry.id === general.id ? general.role.items : entry.role.items + ); + const occupiedUniqueCounts = countOccupiedUniqueItems(generalItemsList, options.itemRegistry); + const rngSeed = buildGenericUniqueSeed( + options.seedBase, + world.currentYear, + world.currentMonth, + general.id, + reason + ); + const rng = new RandUtil(LiteHashDRBG.build(rngSeed)); + const inheritRandomUnique = readMetaBool(asRecord(general.meta), 'inheritRandomUnique', false); + const relMonthByInit = + joinYearMonth(world.currentYear, world.currentMonth) - joinYearMonth(initYear, initMonth); + const availableBuyUnique = relMonthByInit >= minMonthToAllowInherit; + const itemKey = rollUniqueLottery({ + rng, + config: options.uniqueConfig, + itemRegistry: options.itemRegistry, + generalItems: general.role.items, + occupiedUniqueCounts, + scenarioId, + userCount, + currentYear: world.currentYear, + currentMonth: world.currentMonth, + startYear, + initYear, + initMonth, + acquireType, + inheritRandomUnique, + }); + if (!itemKey) { + return null; + } + if (inheritRandomUnique && availableBuyUnique) { + delete asRecord(general.meta).inheritRandomUnique; + } + return options.itemRegistry.get(itemKey) ?? null; + }; +}; + type WorldView = { getGeneralById(id: number): TurnGeneral | null; @@ -349,6 +474,8 @@ export const createReservedTurnHandler = async (options: { }) => void; }): Promise => { const env = buildCommandEnv(options.scenarioConfig, options.unitSet); + const itemRegistry = createItemModuleRegistry(await loadItemModules([...ITEM_KEYS])); + const uniqueConfig = resolveUniqueConfig(asRecord(options.scenarioConfig.const)); const commandProfile = options.commandProfile ?? DEFAULT_TURN_COMMAND_PROFILE; const { general: generalDefinitions, nation: nationDefinitions } = await buildReservedTurnDefinitions({ env, @@ -518,11 +645,20 @@ export const createReservedTurnHandler = async (options: { }; const actionArgsRecord = extractArgsRecord(actionArgs); + const uniqueLottery = buildUniqueLotteryRunner({ + world: context.world, + worldView, + scenarioMeta: options.scenarioMeta, + seedBase, + itemRegistry, + uniqueConfig, + }); let baseContext: ActionContextBase = { general: currentGeneral, city: currentCity, nation: currentNation, rng: buildRng(actionKey), + uniqueLottery, }; let specificContext = buildActionContext( actionKey, diff --git a/app/game-engine/test/uniqueLotteryCommand.test.ts b/app/game-engine/test/uniqueLotteryCommand.test.ts new file mode 100644 index 0000000..c09664e --- /dev/null +++ b/app/game-engine/test/uniqueLotteryCommand.test.ts @@ -0,0 +1,176 @@ +import { describe, expect, it } from 'vitest'; + +import { InMemoryTurnWorld } from '../src/turn/inMemoryWorld.js'; +import type { TurnGeneral, TurnWorldSnapshot, TurnWorldState } from '../src/turn/types.js'; +import { createReservedTurnHandler } from '../src/turn/reservedTurnHandler.js'; +import { InMemoryReservedTurnStore } from '../src/turn/reservedTurnStore.js'; +import type { TurnSchedule } from '@sammo-ts/logic'; + +const buildGeneral = (id: number): TurnGeneral => ({ + id, + name: `General_${id}`, + nationId: 1, + cityId: 1, + troopId: 0, + stats: { leadership: 50, strength: 50, intelligence: 50 }, + turnTime: new Date('0180-01-01T00:00:00Z'), + role: { + items: { horse: null, weapon: null, book: null, item: null }, + personality: null, + specialDomestic: null, + specialWar: null, + }, + triggerState: { flags: {}, counters: {}, modifiers: {}, meta: {} }, + meta: { killturn: 24 }, + officerLevel: 1, + experience: 0, + dedication: 0, + injury: 0, + gold: 1000, + rice: 1000, + crew: 100, + crewTypeId: 0, + train: 0, + atmos: 0, + age: 30, + npcState: 0, +}); + +describe('unique lottery on general commands', () => { + it('awards a unique item for eligible commands', async () => { + const schedule: TurnSchedule = { entries: [{ startMinute: 0, tickMinutes: 10 }] }; + const generals = [buildGeneral(1)]; + const snapshot: TurnWorldSnapshot = { + generals: generals as any, + cities: [ + { + id: 1, + name: 'City_1', + nationId: 1, + viewName: 'City_1', + agriculture: 100, + agricultureMax: 2000, + commerce: 100, + commerceMax: 2000, + security: 100, + securityMax: 100, + def: 100, + defMax: 100, + wall: 100, + wallMax: 100, + pop: 10000, + popMax: 50000, + trust: 50, + supplyState: 1, + frontState: 0, + tradepoint: 0, + level: 1, + meta: {}, + }, + ] as any, + nations: [ + { + id: 1, + name: 'TestNation', + color: '#FF0000', + capitalCityId: 1, + chiefGeneralId: 1, + gold: 10000, + rice: 10000, + power: 0, + level: 1, + typeCode: 'che_def', + meta: {}, + }, + ] as any, + troops: [], + diplomacy: [], + events: [], + initialEvents: [], + map: { + id: 'test_map', + name: 'TestMap', + cities: [ + { + id: 1, + name: 'City_1', + level: 1, + region: 1, + position: { x: 0, y: 0 }, + connections: [], + max: {} as any, + initial: {} as any, + }, + ], + defaults: { trust: 50, trade: 100, supplyState: 1, frontState: 0 }, + } as any, + scenarioConfig: { + stat: { total: 300, min: 10, max: 100, npcTotal: 150, npcMax: 50, npcMin: 10, chiefMin: 70 }, + iconPath: '', + map: {}, + const: { + allItems: { + weapon: { + che_무기_12_칠성검: 1, + }, + }, + maxUniqueItemLimit: [[-1, 1]], + uniqueTrialCoef: 10, + maxUniqueTrialProb: 10, + minMonthToAllowInheritItem: 0, + }, + environment: { mapName: 'test_map', unitSet: 'default' }, + }, + scenarioMeta: { + startYear: 180, + } as any, + unitSet: {} as any, + }; + + const state: TurnWorldState = { + id: 1, + currentYear: 180, + currentMonth: 1, + tickSeconds: 3600, + lastTurnTime: new Date('0180-01-01T00:00:00Z'), + meta: { + hiddenSeed: 'seed', + scenarioId: 200, + initYear: 180, + initMonth: 1, + scenarioMeta: { startYear: 180 }, + }, + }; + + const world = new InMemoryTurnWorld(state, snapshot, { schedule }); + const reservedTurns = new InMemoryReservedTurnStore( + { + generalTurn: { findMany: async () => [] }, + nationTurn: { findMany: async () => [] }, + } as any, + { maxGeneralTurns: 30, maxNationTurns: 12 } + ); + reservedTurns.getGeneralTurns(1)[0] = { action: 'che_훈련', args: {} }; + + const handler = await createReservedTurnHandler({ + reservedTurns, + scenarioConfig: snapshot.scenarioConfig, + scenarioMeta: snapshot.scenarioMeta, + map: snapshot.map, + unitSet: snapshot.unitSet, + getWorld: () => world, + }); + + const result = handler.execute({ + general: world.getGeneralById(1)!, + city: world.getCityById(1)!, + nation: world.getNationById(1)!, + world: world.getState(), + schedule, + }); + + expect(result.general?.role.items.weapon).toBe('che_무기_12_칠성검'); + const logTexts = (result.logs ?? []).map((entry) => entry.text); + expect(logTexts.some((text) => text.includes('【아이템】'))).toBe(true); + }); +}); diff --git a/packages/logic/src/actions/turn/actionContext.ts b/packages/logic/src/actions/turn/actionContext.ts index 473a30a..6f423a0 100644 --- a/packages/logic/src/actions/turn/actionContext.ts +++ b/packages/logic/src/actions/turn/actionContext.ts @@ -1,4 +1,5 @@ import type { City, General, Nation, Troop } from '@sammo-ts/logic/domain/entities.js'; +import type { UniqueLotteryRunner } from '@sammo-ts/logic/rewards/uniqueLottery.js'; import type { ScenarioConfig } from '@sammo-ts/logic/scenario/types.js'; import type { ScenarioMeta } from '@sammo-ts/logic/world/types.js'; import type { MapDefinition, UnitSetDefinition } from '@sammo-ts/logic/world/types.js'; @@ -18,6 +19,7 @@ export type ActionContextBase = { city?: City; nation?: Nation | null; rng: ActionRandomSource; + uniqueLottery?: UniqueLotteryRunner; }; export type ActionResolveContext = ActionContextBase & Record; diff --git a/packages/logic/src/actions/turn/general/che_강행.ts b/packages/logic/src/actions/turn/general/che_강행.ts index 30b0736..de69aee 100644 --- a/packages/logic/src/actions/turn/general/che_강행.ts +++ b/packages/logic/src/actions/turn/general/che_강행.ts @@ -19,6 +19,7 @@ import { LogCategory, LogFormat } from '@sammo-ts/logic/logging/types.js'; import { JosaUtil } from '@sammo-ts/common'; import { z } from 'zod'; import type { TurnCommandEnv } from '@sammo-ts/logic/actions/turn/commandEnv.js'; +import { tryApplyUniqueLottery } from '@sammo-ts/logic/rewards/uniqueLottery.js'; import type { GeneralTurnCommandSpec } from './index.js'; import type { MapDefinition } from '@sammo-ts/logic/world/types.js'; import type { ActionContextBuilder } from '@sammo-ts/logic/actions/turn/actionContext.js'; @@ -101,6 +102,8 @@ export class ActionResolver< const nextLeadershipExp = (typeof general.meta.leadership_exp === 'number' ? general.meta.leadership_exp : 0) + 1; + tryApplyUniqueLottery(context, { acquireType: '아이템', reason: ACTION_NAME }); + effects.push( createGeneralPatchEffect( { diff --git a/packages/logic/src/actions/turn/general/che_거병.ts b/packages/logic/src/actions/turn/general/che_거병.ts index 4786028..b65121f 100644 --- a/packages/logic/src/actions/turn/general/che_거병.ts +++ b/packages/logic/src/actions/turn/general/che_거병.ts @@ -10,6 +10,7 @@ import { import { LogCategory, LogFormat } from '@sammo-ts/logic/logging/types.js'; import type { TurnCommandEnv } from '@sammo-ts/logic/actions/turn/commandEnv.js'; import type { ActionContextBuilder, ActionContextBase } from '@sammo-ts/logic/actions/turn/actionContext.js'; +import { tryApplyUniqueLottery } from '@sammo-ts/logic/rewards/uniqueLottery.js'; import type { GeneralTurnCommandSpec } from './index.js'; export interface UprisingArgs { } @@ -115,6 +116,8 @@ export class ActionDefinition< format: LogFormat.PLAIN, }); + tryApplyUniqueLottery(context, { acquireType: '아이템', reason: ACTION_NAME }); + const effects = [ createNationAddEffect(newNation), createGeneralPatchEffect({ diff --git a/packages/logic/src/actions/turn/general/che_건국.ts b/packages/logic/src/actions/turn/general/che_건국.ts index 9e1ee34..43340f6 100644 --- a/packages/logic/src/actions/turn/general/che_건국.ts +++ b/packages/logic/src/actions/turn/general/che_건국.ts @@ -20,6 +20,7 @@ import { LogCategory, LogFormat } from '@sammo-ts/logic/logging/types.js'; import { z } from 'zod'; import type { TurnCommandEnv } from '@sammo-ts/logic/actions/turn/commandEnv.js'; import { defaultActionContextBuilder } from '@sammo-ts/logic/actions/turn/actionContext.js'; +import { tryApplyUniqueLottery } from '@sammo-ts/logic/rewards/uniqueLottery.js'; import type { GeneralTurnCommandSpec } from './index.js'; import { parseArgsWithSchema } from '../parseArgs.js'; @@ -131,6 +132,8 @@ export class ActionDefinition< format: LogFormat.PLAIN, }); + tryApplyUniqueLottery(context, { acquireType: '건국', reason: ACTION_NAME }); + const effects = [ createNationPatchEffect( { diff --git a/packages/logic/src/actions/turn/general/che_견문.ts b/packages/logic/src/actions/turn/general/che_견문.ts index 8923c3d..714ee81 100644 --- a/packages/logic/src/actions/turn/general/che_견문.ts +++ b/packages/logic/src/actions/turn/general/che_견문.ts @@ -4,6 +4,7 @@ import type { GeneralActionDefinition } from '@sammo-ts/logic/actions/definition import type { GeneralActionOutcome, GeneralActionResolveContext } from '@sammo-ts/logic/actions/engine.js'; import type { TurnCommandEnv } from '@sammo-ts/logic/actions/turn/commandEnv.js'; import { defaultActionContextBuilder } from '@sammo-ts/logic/actions/turn/actionContext.js'; +import { tryApplyUniqueLottery } from '@sammo-ts/logic/rewards/uniqueLottery.js'; import type { GeneralTurnCommandSpec } from './index.js'; import { increaseMetaNumber } from '@sammo-ts/logic/war/utils.js'; @@ -218,6 +219,7 @@ export class ActionDefinition< general.experience += exp; context.addLog(message); + tryApplyUniqueLottery(context, { acquireType: '아이템', reason: ACTION_NAME }); return { effects: [] }; } diff --git a/packages/logic/src/actions/turn/general/che_군량매매.ts b/packages/logic/src/actions/turn/general/che_군량매매.ts index 8671705..cfa2f09 100644 --- a/packages/logic/src/actions/turn/general/che_군량매매.ts +++ b/packages/logic/src/actions/turn/general/che_군량매매.ts @@ -7,6 +7,7 @@ import { LogFormat } from '@sammo-ts/logic/logging/types.js'; import { z } from 'zod'; import type { TurnCommandEnv } from '@sammo-ts/logic/actions/turn/commandEnv.js'; import { defaultActionContextBuilder } from '@sammo-ts/logic/actions/turn/actionContext.js'; +import { tryApplyUniqueLottery } from '@sammo-ts/logic/rewards/uniqueLottery.js'; import type { GeneralTurnCommandSpec } from './index.js'; import { parseArgsWithSchema } from '../parseArgs.js'; @@ -112,6 +113,8 @@ export class ActionDefinition< general.experience += 30; general.dedication += 50; + tryApplyUniqueLottery(context, { acquireType: '아이템', reason: ACTION_NAME }); + return { effects: [] }; } } diff --git a/packages/logic/src/actions/turn/general/che_귀환.ts b/packages/logic/src/actions/turn/general/che_귀환.ts index 349a1f3..41f667e 100644 --- a/packages/logic/src/actions/turn/general/che_귀환.ts +++ b/packages/logic/src/actions/turn/general/che_귀환.ts @@ -20,6 +20,7 @@ import { LogCategory, LogFormat } from '@sammo-ts/logic/logging/types.js'; import { JosaUtil } from '@sammo-ts/common'; import type { TurnCommandEnv } from '@sammo-ts/logic/actions/turn/commandEnv.js'; import type { ActionContextBuilder } from '@sammo-ts/logic/actions/turn/actionContext.js'; +import { tryApplyUniqueLottery } from '@sammo-ts/logic/rewards/uniqueLottery.js'; import type { GeneralTurnCommandSpec } from './index.js'; export interface ReturnArgs {} @@ -115,6 +116,8 @@ export class ActionResolver< const exp = 70; const ded = 100; + tryApplyUniqueLottery(context, { acquireType: '아이템', reason: ACTION_NAME }); + effects.push( createGeneralPatchEffect( { diff --git a/packages/logic/src/actions/turn/general/che_기술연구.ts b/packages/logic/src/actions/turn/general/che_기술연구.ts index ce40a7d..390adc3 100644 --- a/packages/logic/src/actions/turn/general/che_기술연구.ts +++ b/packages/logic/src/actions/turn/general/che_기술연구.ts @@ -11,6 +11,7 @@ import type { GeneralActionDefinition } from '@sammo-ts/logic/actions/definition import type { GeneralActionOutcome, GeneralActionResolveContext } from '@sammo-ts/logic/actions/engine.js'; import type { TurnCommandEnv } from '@sammo-ts/logic/actions/turn/commandEnv.js'; import { defaultActionContextBuilder } from '@sammo-ts/logic/actions/turn/actionContext.js'; +import { tryApplyUniqueLottery } from '@sammo-ts/logic/rewards/uniqueLottery.js'; import type { GeneralTurnCommandSpec } from './index.js'; export interface TechResearchArgs {} @@ -79,6 +80,7 @@ export class ActionDefinition< general.gold = Math.max(0, general.gold - costGold); context.addLog(`${ACTION_NAME}로 기술이 ${applied} 상승했습니다.`); + tryApplyUniqueLottery(context, { acquireType: '아이템', reason: ACTION_NAME }); return { effects: [] }; } diff --git a/packages/logic/src/actions/turn/general/che_단련.ts b/packages/logic/src/actions/turn/general/che_단련.ts index a055b1a..2948f92 100644 --- a/packages/logic/src/actions/turn/general/che_단련.ts +++ b/packages/logic/src/actions/turn/general/che_단련.ts @@ -6,6 +6,7 @@ import type { GeneralActionDefinition } from '@sammo-ts/logic/actions/definition import type { GeneralActionOutcome, GeneralActionResolveContext } from '@sammo-ts/logic/actions/engine.js'; import type { ActionContextBuilder } from '@sammo-ts/logic/actions/turn/actionContext.js'; import type { TurnCommandEnv } from '@sammo-ts/logic/actions/turn/commandEnv.js'; +import { tryApplyUniqueLottery } from '@sammo-ts/logic/rewards/uniqueLottery.js'; import type { GeneralTurnCommandSpec } from './index.js'; import type { UnitSetDefinition } from '@sammo-ts/logic/world/types.js'; import { JosaUtil } from '@sammo-ts/common'; @@ -156,6 +157,7 @@ export class ActionDefinition< general.rice = Math.max(0, general.rice - cost); context.addLog(logText); + tryApplyUniqueLottery(context, { acquireType: '아이템', reason: ACTION_NAME }); return { effects: [] }; } diff --git a/packages/logic/src/actions/turn/general/che_등용.ts b/packages/logic/src/actions/turn/general/che_등용.ts index f4813bc..a3f8925 100644 --- a/packages/logic/src/actions/turn/general/che_등용.ts +++ b/packages/logic/src/actions/turn/general/che_등용.ts @@ -19,6 +19,7 @@ import { LogCategory, LogScope } from '@sammo-ts/logic/logging/types.js'; import { z } from 'zod'; import type { TurnCommandEnv } from '@sammo-ts/logic/actions/turn/commandEnv.js'; import type { ActionContextBase, ActionContextOptions } from '@sammo-ts/logic/actions/turn/actionContext.js'; +import { tryApplyUniqueLottery } from '@sammo-ts/logic/rewards/uniqueLottery.js'; import type { GeneralTurnCommandSpec } from './index.js'; import { parseArgsWithSchema } from '../parseArgs.js'; @@ -99,6 +100,8 @@ export class ActionResolver< const effects: GeneralActionEffect[] = []; + tryApplyUniqueLottery(context, { acquireType: '아이템', reason: ACTION_NAME }); + effects.push( createGeneralPatchEffect( { diff --git a/packages/logic/src/actions/turn/general/che_랜덤임관.ts b/packages/logic/src/actions/turn/general/che_랜덤임관.ts index 59ae627..c65483b 100644 --- a/packages/logic/src/actions/turn/general/che_랜덤임관.ts +++ b/packages/logic/src/actions/turn/general/che_랜덤임관.ts @@ -11,6 +11,7 @@ import type { } from '@sammo-ts/logic/actions/engine.js'; import { createGeneralPatchEffect, createLogEffect } from '@sammo-ts/logic/actions/engine.js'; import { LogCategory, LogFormat, LogScope } from '@sammo-ts/logic/logging/types.js'; +import { tryApplyUniqueLottery } from '@sammo-ts/logic/rewards/uniqueLottery.js'; import type { GeneralTurnCommandSpec } from './index.js'; import type { ActionContextBuilder } from '@sammo-ts/logic/actions/turn/actionContext.js'; import { resolveStartYear } from '@sammo-ts/logic/actions/turn/actionContextHelpers.js'; @@ -226,6 +227,8 @@ export class ActionDefinition< ) ); + tryApplyUniqueLottery(context, { acquireType: '랜덤 임관', reason: ACTION_NAME }); + return { effects }; } } diff --git a/packages/logic/src/actions/turn/general/che_물자조달.ts b/packages/logic/src/actions/turn/general/che_물자조달.ts index 80f95dd..5d40957 100644 --- a/packages/logic/src/actions/turn/general/che_물자조달.ts +++ b/packages/logic/src/actions/turn/general/che_물자조달.ts @@ -12,6 +12,7 @@ import { createGeneralPatchEffect, createNationPatchEffect } from '@sammo-ts/log import { LogCategory, LogFormat } from '@sammo-ts/logic/logging/types.js'; import type { TurnCommandEnv } from '@sammo-ts/logic/actions/turn/commandEnv.js'; import { defaultActionContextBuilder } from '@sammo-ts/logic/actions/turn/actionContext.js'; +import { tryApplyUniqueLottery } from '@sammo-ts/logic/rewards/uniqueLottery.js'; import type { GeneralTurnCommandSpec } from './index.js'; export interface ProcureArgs {} @@ -131,6 +132,8 @@ export class ActionResolver< }); } + tryApplyUniqueLottery(context, { acquireType: '아이템', reason: ACTION_NAME }); + return { effects: [ createGeneralPatchEffect( diff --git a/packages/logic/src/actions/turn/general/che_사기진작.ts b/packages/logic/src/actions/turn/general/che_사기진작.ts index b460363..de9bb03 100644 --- a/packages/logic/src/actions/turn/general/che_사기진작.ts +++ b/packages/logic/src/actions/turn/general/che_사기진작.ts @@ -11,6 +11,7 @@ import type { GeneralActionDefinition } from '@sammo-ts/logic/actions/definition import type { GeneralActionOutcome, GeneralActionResolveContext } from '@sammo-ts/logic/actions/engine.js'; import type { TurnCommandEnv } from '@sammo-ts/logic/actions/turn/commandEnv.js'; import { defaultActionContextBuilder } from '@sammo-ts/logic/actions/turn/actionContext.js'; +import { tryApplyUniqueLottery } from '@sammo-ts/logic/rewards/uniqueLottery.js'; import type { GeneralTurnCommandSpec } from './index.js'; import { clamp } from 'es-toolkit'; @@ -70,6 +71,7 @@ export class ActionDefinition< general.gold = Math.max(0, general.gold - costGold); context.addLog(`${ACTION_NAME}로 사기가 ${applied} 증가했습니다.`); + tryApplyUniqueLottery(context, { acquireType: '아이템', reason: ACTION_NAME }); return { effects: [] }; } diff --git a/packages/logic/src/actions/turn/general/che_상업투자.ts b/packages/logic/src/actions/turn/general/che_상업투자.ts index a8c4598..d862ad2 100644 --- a/packages/logic/src/actions/turn/general/che_상업투자.ts +++ b/packages/logic/src/actions/turn/general/che_상업투자.ts @@ -20,6 +20,7 @@ import type { } from '@sammo-ts/logic/actions/engine.js'; import type { TurnCommandEnv } from '@sammo-ts/logic/actions/turn/commandEnv.js'; import { defaultActionContextBuilder } from '@sammo-ts/logic/actions/turn/actionContext.js'; +import { tryApplyUniqueLottery } from '@sammo-ts/logic/rewards/uniqueLottery.js'; import type { GeneralTurnCommandSpec } from './index.js'; import { clamp } from 'es-toolkit'; @@ -255,6 +256,7 @@ export class ActionResolver< const pickLabel = result.pick === 'success' ? '성공' : result.pick === 'fail' ? '실패' : '완료'; const logMessage = `${ACTION_NAME} ${pickLabel}: +${Math.round(result.score)}`; context.addLog(logMessage); + tryApplyUniqueLottery(context, { acquireType: '아이템', reason: ACTION_NAME }); return { effects: [] }; } diff --git a/packages/logic/src/actions/turn/general/che_숙련전환.ts b/packages/logic/src/actions/turn/general/che_숙련전환.ts index 9e8e017..70eb299 100644 --- a/packages/logic/src/actions/turn/general/che_숙련전환.ts +++ b/packages/logic/src/actions/turn/general/che_숙련전환.ts @@ -5,6 +5,7 @@ import type { GeneralActionDefinition } from '@sammo-ts/logic/actions/definition import type { GeneralActionOutcome, GeneralActionResolveContext } from '@sammo-ts/logic/actions/engine.js'; import type { ActionContextBuilder } from '@sammo-ts/logic/actions/turn/actionContext.js'; import type { TurnCommandEnv } from '@sammo-ts/logic/actions/turn/commandEnv.js'; +import { tryApplyUniqueLottery } from '@sammo-ts/logic/rewards/uniqueLottery.js'; import type { GeneralTurnCommandSpec } from './index.js'; import type { UnitSetDefinition } from '@sammo-ts/logic/world/types.js'; import { JosaUtil } from '@sammo-ts/common'; @@ -80,6 +81,7 @@ export class ActionDefinition< increaseMetaNumber(general.meta, 'leadership_exp', 2); context.addLog(`${srcName} 숙련 ${cutDex}${cutJosa} ${destName} 숙련 ${addDex}${addJosa} 전환했습니다.`); + tryApplyUniqueLottery(context, { acquireType: '아이템', reason: ACTION_NAME }); return { effects: [] }; } diff --git a/packages/logic/src/actions/turn/general/che_은퇴.ts b/packages/logic/src/actions/turn/general/che_은퇴.ts index d7e15eb..eaf905b 100644 --- a/packages/logic/src/actions/turn/general/che_은퇴.ts +++ b/packages/logic/src/actions/turn/general/che_은퇴.ts @@ -12,6 +12,7 @@ import { createGeneralPatchEffect } from '@sammo-ts/logic/actions/engine.js'; import { LogCategory, LogFormat } from '@sammo-ts/logic/logging/types.js'; import type { TurnCommandEnv } from '@sammo-ts/logic/actions/turn/commandEnv.js'; import { defaultActionContextBuilder } from '@sammo-ts/logic/actions/turn/actionContext.js'; +import { tryApplyUniqueLottery } from '@sammo-ts/logic/rewards/uniqueLottery.js'; import type { GeneralTurnCommandSpec } from './index.js'; export interface RetireArgs {} @@ -73,6 +74,8 @@ export class ActionResolver< const effects: GeneralActionEffect[] = []; + tryApplyUniqueLottery(context, { acquireType: '아이템', reason: ACTION_NAME }); + effects.push( createGeneralPatchEffect( { diff --git a/packages/logic/src/actions/turn/general/che_이동.ts b/packages/logic/src/actions/turn/general/che_이동.ts index 25eb54e..194460d 100644 --- a/packages/logic/src/actions/turn/general/che_이동.ts +++ b/packages/logic/src/actions/turn/general/che_이동.ts @@ -20,6 +20,7 @@ import { JosaUtil } from '@sammo-ts/common'; import { z } from 'zod'; import type { TurnCommandEnv } from '@sammo-ts/logic/actions/turn/commandEnv.js'; import type { ActionContextBuilder } from '@sammo-ts/logic/actions/turn/actionContext.js'; +import { tryApplyUniqueLottery } from '@sammo-ts/logic/rewards/uniqueLottery.js'; import type { GeneralTurnCommandSpec } from './index.js'; import type { MapDefinition } from '@sammo-ts/logic/world/types.js'; import { parseArgsWithSchema } from '../parseArgs.js'; @@ -106,6 +107,8 @@ export class ActionResolver< format: LogFormat.MONTH, }); + tryApplyUniqueLottery(context, { acquireType: '아이템', reason: ACTION_NAME }); + for (const target of moveTargets) { const isSelf = target.id === general.id; diff --git a/packages/logic/src/actions/turn/general/che_인재탐색.ts b/packages/logic/src/actions/turn/general/che_인재탐색.ts index e8ba34f..8082ab9 100644 --- a/packages/logic/src/actions/turn/general/che_인재탐색.ts +++ b/packages/logic/src/actions/turn/general/che_인재탐색.ts @@ -23,6 +23,7 @@ import { JosaUtil } from '@sammo-ts/common'; import type { ActionContextBuilder } from '@sammo-ts/logic/actions/turn/actionContext.js'; import { buildWorldSummary } from '@sammo-ts/logic/actions/turn/actionContextHelpers.js'; import type { TurnCommandEnv } from '@sammo-ts/logic/actions/turn/commandEnv.js'; +import { tryApplyUniqueLottery } from '@sammo-ts/logic/rewards/uniqueLottery.js'; import type { GeneralTurnCommandSpec } from './index.js'; export interface TalentScoutArgs {} @@ -292,6 +293,7 @@ export class ActionResolver< category: LogCategory.ACTION, format: LogFormat.MONTH, }); + tryApplyUniqueLottery(context, { acquireType: '아이템', reason: ACTION_NAME }); return { effects: [] }; } @@ -369,6 +371,8 @@ export class ActionResolver< format: LogFormat.YEAR_MONTH, }); + tryApplyUniqueLottery(context, { acquireType: '아이템', reason: ACTION_NAME }); + return { effects: [createGeneralAddEffect(newGeneral)], }; diff --git a/packages/logic/src/actions/turn/general/che_임관.ts b/packages/logic/src/actions/turn/general/che_임관.ts index 0e6c015..1ffd0fa 100644 --- a/packages/logic/src/actions/turn/general/che_임관.ts +++ b/packages/logic/src/actions/turn/general/che_임관.ts @@ -8,6 +8,7 @@ import { LogCategory, LogFormat } from '@sammo-ts/logic/logging/types.js'; import { z } from 'zod'; import type { TurnCommandEnv } from '@sammo-ts/logic/actions/turn/commandEnv.js'; import { defaultActionContextBuilder } from '@sammo-ts/logic/actions/turn/actionContext.js'; +import { tryApplyUniqueLottery } from '@sammo-ts/logic/rewards/uniqueLottery.js'; import type { GeneralTurnCommandSpec } from './index.js'; import { parseArgsWithSchema } from '../parseArgs.js'; @@ -59,6 +60,8 @@ export class ActionDefinition< format: LogFormat.MONTH, }); + tryApplyUniqueLottery(context, { acquireType: '아이템', reason: ACTION_NAME }); + const effects = [ createGeneralPatchEffect({ nationId: args.destNationId, diff --git a/packages/logic/src/actions/turn/general/che_전투특기초기화.ts b/packages/logic/src/actions/turn/general/che_전투특기초기화.ts index 87f945b..1f287f4 100644 --- a/packages/logic/src/actions/turn/general/che_전투특기초기화.ts +++ b/packages/logic/src/actions/turn/general/che_전투특기초기화.ts @@ -5,6 +5,7 @@ import type { GeneralActionDefinition } from '@sammo-ts/logic/actions/definition import type { GeneralActionOutcome, GeneralActionResolveContext } from '@sammo-ts/logic/actions/engine.js'; import type { TurnCommandEnv } from '@sammo-ts/logic/actions/turn/commandEnv.js'; import { defaultActionContextBuilder } from '@sammo-ts/logic/actions/turn/actionContext.js'; +import { tryApplyUniqueLottery } from '@sammo-ts/logic/rewards/uniqueLottery.js'; import type { GeneralTurnCommandSpec } from './index.js'; import { setMetaNumber } from '@sammo-ts/logic/war/utils.js'; @@ -54,6 +55,7 @@ export class ActionDefinition< general.role.specialWar = null; setMetaNumber(general.meta, 'specAge2', general.age + 1); context.addLog('새로운 전투 특기를 가질 준비가 되었습니다.'); + tryApplyUniqueLottery(context, { acquireType: '아이템', reason: ACTION_NAME }); return { effects: [] }; } } diff --git a/packages/logic/src/actions/turn/general/che_정착장려.ts b/packages/logic/src/actions/turn/general/che_정착장려.ts index a11a0bb..154150d 100644 --- a/packages/logic/src/actions/turn/general/che_정착장려.ts +++ b/packages/logic/src/actions/turn/general/che_정착장려.ts @@ -12,6 +12,7 @@ import type { GeneralActionDefinition } from '@sammo-ts/logic/actions/definition import type { GeneralActionOutcome, GeneralActionResolveContext } from '@sammo-ts/logic/actions/engine.js'; import type { TurnCommandEnv } from '@sammo-ts/logic/actions/turn/commandEnv.js'; import { defaultActionContextBuilder } from '@sammo-ts/logic/actions/turn/actionContext.js'; +import { tryApplyUniqueLottery } from '@sammo-ts/logic/rewards/uniqueLottery.js'; import { clamp } from 'es-toolkit'; import type { GeneralTurnCommandSpec } from './index.js'; @@ -68,6 +69,7 @@ export class ActionDefinition< general.rice = Math.max(0, general.rice - costRice); context.addLog(`인구가 ${nextValue - current} 증가했습니다.`); + tryApplyUniqueLottery(context, { acquireType: '아이템', reason: '정착 장려' }); return { effects: [] }; } diff --git a/packages/logic/src/actions/turn/general/che_주민선정.ts b/packages/logic/src/actions/turn/general/che_주민선정.ts index 78003a8..304622d 100644 --- a/packages/logic/src/actions/turn/general/che_주민선정.ts +++ b/packages/logic/src/actions/turn/general/che_주민선정.ts @@ -14,6 +14,7 @@ import type { GeneralActionDefinition } from '@sammo-ts/logic/actions/definition import type { GeneralActionOutcome, GeneralActionResolveContext } from '@sammo-ts/logic/actions/engine.js'; import type { TurnCommandEnv } from '@sammo-ts/logic/actions/turn/commandEnv.js'; import { defaultActionContextBuilder } from '@sammo-ts/logic/actions/turn/actionContext.js'; +import { tryApplyUniqueLottery } from '@sammo-ts/logic/rewards/uniqueLottery.js'; import type { GeneralTurnCommandSpec } from './index.js'; import { clamp } from 'es-toolkit'; @@ -165,6 +166,7 @@ export class ActionDefinition< general.meta = { ...addMetaNumber(general.meta, STAT_EXP_KEY, 1), max_domestic_critical: 0 }; context.addLog(`민심이 ${result.trustDelta.toFixed(1)} 상승했습니다.`); + tryApplyUniqueLottery(context, { acquireType: '아이템', reason: ACTION_NAME }); return { effects: [] }; } } diff --git a/packages/logic/src/actions/turn/general/che_집합.ts b/packages/logic/src/actions/turn/general/che_집합.ts index 6d04346..81a4856 100644 --- a/packages/logic/src/actions/turn/general/che_집합.ts +++ b/packages/logic/src/actions/turn/general/che_집합.ts @@ -16,6 +16,7 @@ import type { import { createGeneralPatchEffect, createLogEffect } from '@sammo-ts/logic/actions/engine.js'; import type { ActionContextBuilder } from '@sammo-ts/logic/actions/turn/actionContext.js'; import type { TurnCommandEnv } from '@sammo-ts/logic/actions/turn/commandEnv.js'; +import { tryApplyUniqueLottery } from '@sammo-ts/logic/rewards/uniqueLottery.js'; import type { GeneralTurnCommandSpec } from './index.js'; import { LogCategory, LogFormat, LogScope } from '@sammo-ts/logic/logging/types.js'; import { JosaUtil } from '@sammo-ts/common'; @@ -78,6 +79,7 @@ export class ActionDefinition< general.experience += 70; general.dedication += 100; increaseMetaNumber(general.meta, 'leadership_exp', 1); + tryApplyUniqueLottery(context, { acquireType: '아이템', reason: ACTION_NAME }); return { effects }; } diff --git a/packages/logic/src/actions/turn/general/che_징병.ts b/packages/logic/src/actions/turn/general/che_징병.ts index 47e3e11..c68d59c 100644 --- a/packages/logic/src/actions/turn/general/che_징병.ts +++ b/packages/logic/src/actions/turn/general/che_징병.ts @@ -19,6 +19,7 @@ import type { import type { MapDefinition, UnitSetDefinition } from '@sammo-ts/logic/world/types.js'; import type { ActionContextBuilder } from '@sammo-ts/logic/actions/turn/actionContext.js'; import { resolveStartYear } from '@sammo-ts/logic/actions/turn/actionContextHelpers.js'; +import { tryApplyUniqueLottery } from '@sammo-ts/logic/rewards/uniqueLottery.js'; import { z } from 'zod'; import type { TurnCommandEnv } from '@sammo-ts/logic/actions/turn/commandEnv.js'; import type { GeneralTurnCommandSpec } from './index.js'; @@ -392,6 +393,7 @@ export class ActionResolver< general.meta = addMetaNumber(general.meta, 'leadership_exp', 1); context.addLog(logMessage); + tryApplyUniqueLottery(context, { acquireType: '아이템', reason: ACTION_NAME }); return { effects: [] }; } diff --git a/packages/logic/src/actions/turn/general/che_출병.ts b/packages/logic/src/actions/turn/general/che_출병.ts index ec2dc27..23c1326 100644 --- a/packages/logic/src/actions/turn/general/che_출병.ts +++ b/packages/logic/src/actions/turn/general/che_출병.ts @@ -36,6 +36,7 @@ import type { WarActionModule } from '@sammo-ts/logic/war/actions.js'; import { increaseMetaNumber, simpleSerialize } from '@sammo-ts/logic/war/utils.js'; import type { MapDefinition, UnitSetDefinition } from '@sammo-ts/logic/world/types.js'; import type { ActionContextBuilder } from '@sammo-ts/logic/actions/turn/actionContext.js'; +import { tryApplyUniqueLottery } from '@sammo-ts/logic/rewards/uniqueLottery.js'; import { buildWarAftermathConfig, buildWarConfig, @@ -509,6 +510,8 @@ export class ActionDefinition< ); } + tryApplyUniqueLottery(context, { acquireType: '아이템', reason: ACTION_NAME }); + return { effects }; } } diff --git a/packages/logic/src/actions/turn/general/che_헌납.ts b/packages/logic/src/actions/turn/general/che_헌납.ts index af7666f..4d65735 100644 --- a/packages/logic/src/actions/turn/general/che_헌납.ts +++ b/packages/logic/src/actions/turn/general/che_헌납.ts @@ -19,6 +19,7 @@ import { LogCategory, LogFormat } from '@sammo-ts/logic/logging/types.js'; import { z } from 'zod'; import type { TurnCommandEnv } from '@sammo-ts/logic/actions/turn/commandEnv.js'; import { defaultActionContextBuilder } from '@sammo-ts/logic/actions/turn/actionContext.js'; +import { tryApplyUniqueLottery } from '@sammo-ts/logic/rewards/uniqueLottery.js'; import type { GeneralTurnCommandSpec } from './index.js'; import { parseArgsWithSchema } from '../parseArgs.js'; @@ -60,6 +61,8 @@ export class ActionResolver< format: LogFormat.MONTH, }); + tryApplyUniqueLottery(context, { acquireType: '아이템', reason: ACTION_NAME }); + return { effects: [ createGeneralPatchEffect( diff --git a/packages/logic/src/actions/turn/general/che_훈련.ts b/packages/logic/src/actions/turn/general/che_훈련.ts index 0953251..11294bf 100644 --- a/packages/logic/src/actions/turn/general/che_훈련.ts +++ b/packages/logic/src/actions/turn/general/che_훈련.ts @@ -5,6 +5,7 @@ import type { GeneralActionDefinition } from '@sammo-ts/logic/actions/definition import type { GeneralActionOutcome, GeneralActionResolveContext } from '@sammo-ts/logic/actions/engine.js'; import type { TurnCommandEnv } from '@sammo-ts/logic/actions/turn/commandEnv.js'; import { defaultActionContextBuilder } from '@sammo-ts/logic/actions/turn/actionContext.js'; +import { tryApplyUniqueLottery } from '@sammo-ts/logic/rewards/uniqueLottery.js'; import type { GeneralTurnCommandSpec } from './index.js'; import { clamp } from 'es-toolkit'; @@ -64,6 +65,7 @@ export class ActionDefinition< general.gold = Math.max(0, general.gold - costGold); context.addLog(`${ACTION_NAME}을 통해 훈련도가 ${applied} 증가했습니다.`); + tryApplyUniqueLottery(context, { acquireType: '아이템', reason: ACTION_NAME }); return { effects: [] }; } diff --git a/packages/logic/src/rewards/uniqueLottery.ts b/packages/logic/src/rewards/uniqueLottery.ts index fc0a9b3..971ef5c 100644 --- a/packages/logic/src/rewards/uniqueLottery.ts +++ b/packages/logic/src/rewards/uniqueLottery.ts @@ -1,5 +1,7 @@ -import { asRecord, parseJson, type RandUtil } from '@sammo-ts/common'; -import type { GeneralItemSlots } from '../domain/entities.js'; +import { asRecord, JosaUtil, parseJson, type RandUtil } from '@sammo-ts/common'; +import type { General, GeneralItemSlots, GeneralTriggerState } from '../domain/entities.js'; +import type { GeneralActionResolveContext } from '../actions/engine.js'; +import { LogCategory, LogFormat, LogScope } from '../logging/types.js'; import type { ItemModule } from '../items/types.js'; export type UniqueItemPool = Record>; @@ -7,6 +9,15 @@ export type UniqueItemPool = Record>; export const UNIQUE_ACQUIRE_TYPES = ['아이템', '설문조사', '랜덤 임관', '건국'] as const; export type UniqueAcquireType = typeof UNIQUE_ACQUIRE_TYPES[number]; +export type UniqueLotteryRequest = { + acquireType: UniqueAcquireType; + reason: string; +}; + +export type UniqueLotteryRunner = ( + request: UniqueLotteryRequest & { general: General } +) => ItemModule | null; + export type UniqueLotteryConfig = { allItems: UniqueItemPool; maxUniqueItemLimit: Array<[number, number]>; @@ -139,6 +150,17 @@ const serializeSeed = (...values: Array): string => .map((value) => (typeof value === 'string' ? `str(${value.length},${value})` : `int(${Math.floor(value)})`)) .join('|'); +export const buildGenericUniqueSeed = ( + hiddenSeed: string | number, + year: number, + month: number, + generalId: number, + reason?: string +): string => + reason !== undefined + ? serializeSeed(hiddenSeed, 'unique', year, month, generalId, reason) + : serializeSeed(hiddenSeed, 'unique', year, month, generalId); + export const buildVoteUniqueSeed = (hiddenSeed: string | number, voteId: number, generalId: number): string => serializeSeed(hiddenSeed, 'voteUnique', voteId, generalId); @@ -280,3 +302,59 @@ export const rollUniqueLottery = (input: UniqueLotteryInput): string | null => { return rng.choiceUsingWeightPair(availableUnique); }; + +export const applyUniqueItemGain = ( + context: GeneralActionResolveContext, + itemModule: ItemModule, + acquireType: UniqueAcquireType +): void => { + const general = context.general; + const nationName = context.nation?.name ?? '재야'; + const generalName = general.name; + const itemName = itemModule.name; + const itemRawName = itemModule.rawName; + const josaYi = JosaUtil.pick(generalName, '이'); + const josaUl = JosaUtil.pick(itemRawName, '을'); + + general.role.items[itemModule.slot] = itemModule.key; + + context.addLog(`${itemName}${josaUl} 습득했습니다!`, { + scope: LogScope.GENERAL, + category: LogCategory.ACTION, + format: LogFormat.MONTH, + }); + context.addLog(`${itemName}${josaUl} 습득`, { + scope: LogScope.GENERAL, + category: LogCategory.HISTORY, + format: LogFormat.YEAR_MONTH, + }); + context.addLog(`${generalName}${josaYi} ${itemName}${josaUl} 습득했습니다!`, { + scope: LogScope.SYSTEM, + category: LogCategory.SUMMARY, + format: LogFormat.MONTH, + }); + context.addLog( + `【${acquireType}】${nationName}${generalName}${josaYi} ${itemName}${josaUl} 습득했습니다!`, + { + scope: LogScope.SYSTEM, + category: LogCategory.HISTORY, + format: LogFormat.YEAR_MONTH, + } + ); +}; + +export const tryApplyUniqueLottery = ( + context: GeneralActionResolveContext & { uniqueLottery?: UniqueLotteryRunner }, + request: UniqueLotteryRequest +): boolean => { + const runner = context.uniqueLottery; + if (!runner) { + return false; + } + const itemModule = runner({ ...request, general: context.general }); + if (!itemModule) { + return false; + } + applyUniqueItemGain(context, itemModule, request.acquireType); + return true; +};