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.
This commit is contained in:
2026-02-04 12:03:47 +00:00
parent a2bb667b57
commit 568084927c
30 changed files with 461 additions and 2 deletions
+80 -2
View File
@@ -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<string, Record<string, number>>;
@@ -7,6 +9,15 @@ export type UniqueItemPool = Record<string, Record<string, number>>;
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 = <TriggerState extends GeneralTriggerState = GeneralTriggerState>(
request: UniqueLotteryRequest & { general: General<TriggerState> }
) => ItemModule | null;
export type UniqueLotteryConfig = {
allItems: UniqueItemPool;
maxUniqueItemLimit: Array<[number, number]>;
@@ -139,6 +150,17 @@ const serializeSeed = (...values: Array<string | number>): 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 = <TriggerState extends GeneralTriggerState = GeneralTriggerState>(
context: GeneralActionResolveContext<TriggerState>,
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(`<C>${itemName}</>${josaUl} 습득했습니다!`, {
scope: LogScope.GENERAL,
category: LogCategory.ACTION,
format: LogFormat.MONTH,
});
context.addLog(`<C>${itemName}</>${josaUl} 습득`, {
scope: LogScope.GENERAL,
category: LogCategory.HISTORY,
format: LogFormat.YEAR_MONTH,
});
context.addLog(`<Y>${generalName}</>${josaYi} <C>${itemName}</>${josaUl} 습득했습니다!`, {
scope: LogScope.SYSTEM,
category: LogCategory.SUMMARY,
format: LogFormat.MONTH,
});
context.addLog(
`<C><b>【${acquireType}】</b></><D><b>${nationName}</b></>의 <Y>${generalName}</>${josaYi} <C>${itemName}</>${josaUl} 습득했습니다!`,
{
scope: LogScope.SYSTEM,
category: LogCategory.HISTORY,
format: LogFormat.YEAR_MONTH,
}
);
};
export const tryApplyUniqueLottery = <TriggerState extends GeneralTriggerState = GeneralTriggerState>(
context: GeneralActionResolveContext<TriggerState> & { 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;
};