fix general founding and rebellion parity
This commit is contained in:
@@ -154,12 +154,10 @@ export const do거병 = (ai: GeneralAI) => {
|
||||
};
|
||||
|
||||
export const do건국 = (ai: GeneralAI) => {
|
||||
const mapName = ai.scenarioConfig.environment.mapName ?? 'sammo';
|
||||
const prefix = mapName.endsWith('_') ? mapName : `${mapName}_`;
|
||||
const nationType =
|
||||
ai.aiConst.availableNationTypes.length > 0
|
||||
? (ai.rng.choice(ai.aiConst.availableNationTypes) as string)
|
||||
: `${prefix}def`;
|
||||
: 'che_도적';
|
||||
const colorType = ai.rng.nextRangeInt(0, 32);
|
||||
const nationName = `㉿${Array.from(ai.general.name).slice(1).join('')}`;
|
||||
|
||||
|
||||
@@ -1261,6 +1261,12 @@ export const createReservedTurnHandler = async (options: {
|
||||
if (resolution.created?.nations) {
|
||||
const newNations = resolution.created.nations as Nation[];
|
||||
createdNations.push(...newNations);
|
||||
if (actionKey === 'che_거병') {
|
||||
for (const nation of newNations) {
|
||||
options.reservedTurns.ensureNationTurns(nation.id, 12);
|
||||
options.reservedTurns.ensureNationTurns(nation.id, 11);
|
||||
}
|
||||
}
|
||||
if (worldOverlay) {
|
||||
for (const nation of newNations) {
|
||||
worldOverlay.syncNation(nation);
|
||||
|
||||
@@ -787,7 +787,7 @@ describe('Reserved Turn Execution Integration', () => {
|
||||
generalId: 1,
|
||||
turnIdx: 1,
|
||||
actionCode: 'che_건국',
|
||||
arg: { nationName: 'NewEmpire', nationType: 'che_def', colorType: 0 },
|
||||
arg: { nationName: 'NewEmpire', nationType: 'che_도적', colorType: 0 },
|
||||
},
|
||||
{ generalId: 2, turnIdx: 1, actionCode: 'che_농지개간', arg: {} },
|
||||
// 2
|
||||
@@ -798,7 +798,7 @@ describe('Reserved Turn Execution Integration', () => {
|
||||
generalId: 1,
|
||||
turnIdx: 3,
|
||||
actionCode: 'che_건국',
|
||||
arg: { nationName: 'NewEmpire', nationType: 'che_def', colorType: 0 },
|
||||
arg: { nationName: 'NewEmpire', nationType: 'che_도적', colorType: 0 },
|
||||
},
|
||||
{ generalId: 2, turnIdx: 3, actionCode: 'che_농지개간', arg: {} },
|
||||
// 4
|
||||
@@ -809,7 +809,7 @@ describe('Reserved Turn Execution Integration', () => {
|
||||
generalId: 1,
|
||||
turnIdx: 5,
|
||||
actionCode: 'che_건국',
|
||||
arg: { nationName: 'NewEmpire', nationType: 'che_def', colorType: 0 },
|
||||
arg: { nationName: 'NewEmpire', nationType: 'che_도적', colorType: 0 },
|
||||
},
|
||||
{ generalId: 2, turnIdx: 5, actionCode: 'che_농지개간', arg: {} },
|
||||
];
|
||||
@@ -1058,7 +1058,7 @@ describe('Reserved Turn Execution Integration', () => {
|
||||
generalId: 1,
|
||||
turnIdx: 0,
|
||||
actionCode: 'che_건국',
|
||||
arg: { nationName: 'Empire', nationType: 'che_def', colorType: 0 },
|
||||
arg: { nationName: 'Empire', nationType: 'che_도적', colorType: 0 },
|
||||
},
|
||||
];
|
||||
|
||||
@@ -1102,7 +1102,7 @@ describe('Reserved Turn Execution Integration', () => {
|
||||
turnsG1[0] = { action: 'che_거병', args: {} };
|
||||
turnsG1[1] = {
|
||||
action: 'che_건국',
|
||||
args: { nationName: 'Empire', nationType: 'che_def', colorType: 0 },
|
||||
args: { nationName: 'Empire', nationType: 'che_도적', colorType: 0 },
|
||||
};
|
||||
await reservedTurnStore.flushChanges();
|
||||
|
||||
@@ -1112,6 +1112,11 @@ describe('Reserved Turn Execution Integration', () => {
|
||||
maxGenerals: 100,
|
||||
catchUpCap: 10,
|
||||
});
|
||||
const uprisingNationId = world.getGeneralById(1)!.nationId;
|
||||
expect(reservedTurnStore.peekDirtyState().nationInitializationKeys).toEqual([
|
||||
`${uprisingNationId}:12`,
|
||||
`${uprisingNationId}:11`,
|
||||
]);
|
||||
world.consumeDirtyState(); // clear logs
|
||||
|
||||
// Execute Founding attempt (Turn 2)
|
||||
@@ -1145,7 +1150,7 @@ describe('Reserved Turn Execution Integration', () => {
|
||||
const turnsG1_v2 = reservedTurnStore.getGeneralTurns(1);
|
||||
turnsG1_v2[0] = {
|
||||
action: 'che_건국',
|
||||
args: { nationName: 'Empire', nationType: 'che_def', colorType: 0 },
|
||||
args: { nationName: 'Empire', nationType: 'che_도적', colorType: 0 },
|
||||
};
|
||||
await reservedTurnStore.flushChanges();
|
||||
|
||||
|
||||
@@ -13,11 +13,12 @@ import {
|
||||
createDiplomacyPatchEffect,
|
||||
createNationAddEffect,
|
||||
} from '@sammo-ts/logic/actions/engine.js';
|
||||
import { LogCategory, LogFormat } from '@sammo-ts/logic/logging/types.js';
|
||||
import { LogCategory, LogFormat, LogScope } 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';
|
||||
import { getLegacyStringWidth } from '@sammo-ts/logic/troop/management.js';
|
||||
|
||||
export interface UprisingArgs {}
|
||||
|
||||
@@ -29,6 +30,19 @@ export interface UprisingContext extends ActionContextBase {
|
||||
}
|
||||
|
||||
const ACTION_NAME = '거병';
|
||||
const truncateLegacyWidth = (value: string, maxWidth: number): string => {
|
||||
let result = '';
|
||||
let width = 0;
|
||||
for (const character of value) {
|
||||
const characterWidth = getLegacyStringWidth(character);
|
||||
if (width + characterWidth > maxWidth) {
|
||||
break;
|
||||
}
|
||||
result += character;
|
||||
width += characterWidth;
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
export class ActionDefinition<
|
||||
TriggerState extends GeneralTriggerState = GeneralTriggerState,
|
||||
@@ -65,8 +79,7 @@ export class ActionDefinition<
|
||||
const nations = uprisingCtx.listNations ? uprisingCtx.listNations() : [];
|
||||
|
||||
if (nations.some((n) => n.name === nationName)) {
|
||||
nationName = '㉥' + nationName;
|
||||
if (nationName.length > 18) nationName = nationName.substring(0, 18);
|
||||
nationName = truncateLegacyWidth('㉥' + nationName, 18);
|
||||
}
|
||||
|
||||
if (nations.some((n) => n.name === nationName)) {
|
||||
@@ -88,7 +101,7 @@ export class ActionDefinition<
|
||||
color: '#330000',
|
||||
typeCode: 'che_중립',
|
||||
level: 0,
|
||||
capitalCityId: null,
|
||||
capitalCityId: 0,
|
||||
chiefGeneralId: general.id,
|
||||
gold: 0,
|
||||
rice: uprisingCtx.baseRice,
|
||||
@@ -110,24 +123,29 @@ export class ActionDefinition<
|
||||
category: LogCategory.ACTION,
|
||||
format: LogFormat.MONTH,
|
||||
});
|
||||
context.addLog(`${general.name}${josaYi} ${cityName}에 거병하였습니다.`, {
|
||||
category: LogCategory.USER,
|
||||
format: LogFormat.PLAIN,
|
||||
context.addLog(`<Y>${general.name}</>${josaYi} <G><b>${cityName}</b></>에 거병하였습니다.`, {
|
||||
scope: LogScope.SYSTEM,
|
||||
category: LogCategory.SUMMARY,
|
||||
format: LogFormat.MONTH,
|
||||
});
|
||||
context.addLog(`【거병】${general.name}${josaYi} 세력을 결성하였습니다.`, {
|
||||
context.addLog(
|
||||
`<Y><b>【거병】</b></><D><b>${general.name}</b></>${josaYi} 세력을 결성하였습니다.`,
|
||||
{
|
||||
scope: LogScope.SYSTEM,
|
||||
category: LogCategory.HISTORY,
|
||||
format: LogFormat.YEAR_MONTH,
|
||||
}
|
||||
);
|
||||
context.addLog(`<G><b>${cityName}</b></>에서 거병`, {
|
||||
category: LogCategory.HISTORY,
|
||||
format: LogFormat.PLAIN,
|
||||
});
|
||||
context.addLog(`${cityName}에서 거병`, {
|
||||
category: LogCategory.HISTORY,
|
||||
format: LogFormat.PLAIN,
|
||||
});
|
||||
context.addLog(`${general.name}${josaYi} ${cityName}에서 거병`, {
|
||||
category: LogCategory.HISTORY,
|
||||
format: LogFormat.PLAIN,
|
||||
format: LogFormat.YEAR_MONTH,
|
||||
});
|
||||
|
||||
tryApplyUniqueLottery(context, { acquireType: '아이템', reason: ACTION_NAME });
|
||||
tryApplyUniqueLottery(context, {
|
||||
acquireType: '아이템',
|
||||
reason: ACTION_NAME,
|
||||
nationName,
|
||||
});
|
||||
|
||||
const effects: GeneralActionEffect<TriggerState>[] = [
|
||||
createNationAddEffect(newNation),
|
||||
|
||||
@@ -19,57 +19,26 @@ import {
|
||||
createNationPatchEffect,
|
||||
} from '@sammo-ts/logic/actions/engine.js';
|
||||
import { LogCategory, LogFormat, LogScope } 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 type { ActionContextBuilder } from '@sammo-ts/logic/actions/turn/actionContext.js';
|
||||
import { resolveInitYearMonth } from '@sammo-ts/logic/actions/turn/actionContextHelpers.js';
|
||||
import { tryApplyUniqueLottery } from '@sammo-ts/logic/rewards/uniqueLottery.js';
|
||||
import type { GeneralTurnCommandSpec } from './index.js';
|
||||
import { parseArgsWithSchema } from '../parseArgs.js';
|
||||
import { JosaUtil } from '@sammo-ts/common';
|
||||
import {
|
||||
FOUNDING_ARGS_SCHEMA,
|
||||
getNationTypeDisplayName,
|
||||
NATION_COLORS,
|
||||
type FoundingArgs,
|
||||
} from './foundingShared.js';
|
||||
|
||||
const ACTION_NAME = '건국';
|
||||
const ARGS_SCHEMA = z.object({
|
||||
nationName: z.string().min(1),
|
||||
nationType: z.string().min(1),
|
||||
colorType: z.number(),
|
||||
});
|
||||
export type FoundingArgs = z.infer<typeof ARGS_SCHEMA>;
|
||||
|
||||
const NATION_COLORS = [
|
||||
'#FF0000',
|
||||
'#800000',
|
||||
'#A0522D',
|
||||
'#FF6347',
|
||||
'#FFA500',
|
||||
'#FFDAB9',
|
||||
'#FFD700',
|
||||
'#FFFF00',
|
||||
'#7CFC00',
|
||||
'#00FF00',
|
||||
'#808000',
|
||||
'#008000',
|
||||
'#2E8B57',
|
||||
'#008080',
|
||||
'#20B2AA',
|
||||
'#6495ED',
|
||||
'#7FFFD4',
|
||||
'#AFEEEE',
|
||||
'#87CEEB',
|
||||
'#00FFFF',
|
||||
'#00BFFF',
|
||||
'#0000FF',
|
||||
'#000080',
|
||||
'#483D8B',
|
||||
'#7B68EE',
|
||||
'#BA55D3',
|
||||
'#800080',
|
||||
'#FF00FF',
|
||||
'#FFC0CB',
|
||||
'#F5F5DC',
|
||||
'#E0FFFF',
|
||||
'#FFFFFF',
|
||||
'#A9A9A9',
|
||||
];
|
||||
interface FoundingResolveContext<TriggerState extends GeneralTriggerState = GeneralTriggerState>
|
||||
extends GeneralActionResolveContext<TriggerState> {
|
||||
currentYearMonth?: number;
|
||||
initYearMonth?: number;
|
||||
}
|
||||
|
||||
export class ActionDefinition<
|
||||
TriggerState extends GeneralTriggerState = GeneralTriggerState,
|
||||
@@ -81,7 +50,7 @@ export class ActionDefinition<
|
||||
}
|
||||
|
||||
parseArgs(raw: unknown): FoundingArgs | null {
|
||||
return parseArgsWithSchema(ARGS_SCHEMA, raw);
|
||||
return parseArgsWithSchema(FOUNDING_ARGS_SCHEMA, raw);
|
||||
}
|
||||
|
||||
buildMinConstraints(_ctx: ConstraintContext, _args: FoundingArgs): Constraint[] {
|
||||
@@ -106,15 +75,20 @@ export class ActionDefinition<
|
||||
}
|
||||
|
||||
resolve(
|
||||
context: GeneralActionResolveContext<TriggerState>,
|
||||
context: FoundingResolveContext<TriggerState>,
|
||||
args: FoundingArgs
|
||||
): GeneralActionOutcome<TriggerState> {
|
||||
const general = context.general;
|
||||
const nation = context.nation!;
|
||||
const cityId = general.cityId!;
|
||||
|
||||
if (args.colorType < 0 || args.colorType >= NATION_COLORS.length) {
|
||||
throw new Error('Invalid color type');
|
||||
if ((context.currentYearMonth ?? 0) <= (context.initYearMonth ?? 0)) {
|
||||
context.addLog('다음 턴부터 건국할 수 있습니다.', {
|
||||
scope: LogScope.GENERAL,
|
||||
category: LogCategory.ACTION,
|
||||
format: LogFormat.MONTH,
|
||||
});
|
||||
return { effects: [], alternative: { commandKey: 'che_인재탐색', args: {} } };
|
||||
}
|
||||
const color = NATION_COLORS[args.colorType];
|
||||
|
||||
@@ -123,33 +97,42 @@ export class ActionDefinition<
|
||||
const josaGeneralYi = JosaUtil.pick(general.name, '이');
|
||||
const city = context.city;
|
||||
|
||||
context.addLog(`${args.nationName}${josaNationUl} 건국하였습니다.`, {
|
||||
category: LogCategory.USER,
|
||||
format: LogFormat.PLAIN,
|
||||
});
|
||||
context.addLog(`<D><b>${args.nationName}</b></>${josaNationUl} 건국하였습니다.`, {
|
||||
category: LogCategory.ACTION,
|
||||
format: LogFormat.MONTH,
|
||||
});
|
||||
context.addLog(`${general.name}${josaGeneralYi} ${city?.name}에 국가를 건설하였습니다.`, {
|
||||
category: LogCategory.ACTION,
|
||||
context.addLog(`<Y>${general.name}</>${josaGeneralYi} <G><b>${city?.name}</b></>에 국가를 건설하였습니다.`, {
|
||||
category: LogCategory.SUMMARY,
|
||||
scope: LogScope.SYSTEM,
|
||||
format: LogFormat.PLAIN,
|
||||
format: LogFormat.MONTH,
|
||||
});
|
||||
context.addLog(`【건국】${args.nationType} ${args.nationName}${josaNationYi} 새로이 등장하였습니다.`, {
|
||||
context.addLog(
|
||||
`<Y><b>【건국】</b></>${getNationTypeDisplayName(args.nationType)} <D><b>${args.nationName}</b></>${josaNationYi} 새로이 등장하였습니다.`,
|
||||
{
|
||||
category: LogCategory.HISTORY,
|
||||
scope: LogScope.SYSTEM,
|
||||
format: LogFormat.YEAR_MONTH,
|
||||
}
|
||||
);
|
||||
context.addLog(`<D><b>${args.nationName}</b></>${josaNationUl} 건국`, {
|
||||
category: LogCategory.HISTORY,
|
||||
format: LogFormat.PLAIN,
|
||||
});
|
||||
context.addLog(`${args.nationName}${josaNationUl} 건국`, {
|
||||
category: LogCategory.HISTORY,
|
||||
format: LogFormat.PLAIN,
|
||||
});
|
||||
context.addLog(`${general.name}${josaGeneralYi} ${args.nationName}${josaNationUl} 건국`, {
|
||||
category: LogCategory.HISTORY,
|
||||
format: LogFormat.PLAIN,
|
||||
scope: LogScope.GENERAL,
|
||||
format: LogFormat.YEAR_MONTH,
|
||||
});
|
||||
context.addLog(
|
||||
`<Y>${general.name}</>${josaGeneralYi} <D><b>${args.nationName}</b></>${josaNationUl} 건국`,
|
||||
{
|
||||
category: LogCategory.HISTORY,
|
||||
scope: LogScope.NATION,
|
||||
format: LogFormat.YEAR_MONTH,
|
||||
}
|
||||
);
|
||||
|
||||
tryApplyUniqueLottery(context, { acquireType: '건국', reason: ACTION_NAME });
|
||||
tryApplyUniqueLottery(context, {
|
||||
acquireType: '건국',
|
||||
reason: ACTION_NAME,
|
||||
nationName: args.nationName,
|
||||
});
|
||||
|
||||
const effects = [
|
||||
createNationPatchEffect(
|
||||
@@ -169,6 +152,7 @@ export class ActionDefinition<
|
||||
createCityPatchEffect(
|
||||
{
|
||||
nationId: nation.id,
|
||||
conflict: {},
|
||||
},
|
||||
cityId
|
||||
),
|
||||
@@ -183,7 +167,14 @@ export class ActionDefinition<
|
||||
}
|
||||
|
||||
// 예약 턴 실행은 기본 컨텍스트만 사용한다.
|
||||
export const actionContextBuilder = defaultActionContextBuilder;
|
||||
export const actionContextBuilder: ActionContextBuilder = (base, options) => {
|
||||
const init = resolveInitYearMonth(options.world, options.scenarioMeta);
|
||||
return {
|
||||
...base,
|
||||
currentYearMonth: options.world.currentYear * 12 + options.world.currentMonth - 1,
|
||||
initYearMonth: init.year * 12 + init.month - 1,
|
||||
};
|
||||
};
|
||||
|
||||
export const commandSpec: GeneralTurnCommandSpec = {
|
||||
key: 'che_건국',
|
||||
@@ -194,6 +185,6 @@ export const commandSpec: GeneralTurnCommandSpec = {
|
||||
nationType: 'string',
|
||||
colorType: 'number',
|
||||
},
|
||||
argsSchema: ARGS_SCHEMA,
|
||||
argsSchema: FOUNDING_ARGS_SCHEMA,
|
||||
createDefinition: (_env: TurnCommandEnv) => new ActionDefinition(),
|
||||
};
|
||||
|
||||
@@ -14,7 +14,11 @@ import type {
|
||||
GeneralActionOutcome,
|
||||
GeneralActionResolveContext,
|
||||
} from '@sammo-ts/logic/actions/engine.js';
|
||||
import { createGeneralPatchEffect, createLogEffect } from '@sammo-ts/logic/actions/engine.js';
|
||||
import {
|
||||
createGeneralPatchEffect,
|
||||
createLogEffect,
|
||||
createNationPatchEffect,
|
||||
} from '@sammo-ts/logic/actions/engine.js';
|
||||
import { LogCategory, LogFormat, LogScope } from '@sammo-ts/logic/logging/types.js';
|
||||
import { JosaUtil } from '@sammo-ts/common';
|
||||
import type { ActionContextBuilder } from '@sammo-ts/logic/actions/turn/actionContext.js';
|
||||
@@ -118,7 +122,7 @@ export class ActionDefinition<
|
||||
createGeneralPatchEffect(
|
||||
{
|
||||
officerLevel: 1,
|
||||
experience: Math.floor(lord.experience * 0.7),
|
||||
experience: Math.round(lord.experience * 0.7),
|
||||
meta: {
|
||||
...lord.meta,
|
||||
officer_city: 0,
|
||||
@@ -126,7 +130,8 @@ export class ActionDefinition<
|
||||
},
|
||||
},
|
||||
lord.id
|
||||
)
|
||||
),
|
||||
createNationPatchEffect({ chiefGeneralId: general.id }, nation.id)
|
||||
);
|
||||
|
||||
return { effects };
|
||||
|
||||
@@ -21,7 +21,6 @@ import {
|
||||
createNationPatchEffect,
|
||||
} from '@sammo-ts/logic/actions/engine.js';
|
||||
import { LogCategory, LogFormat, LogScope } from '@sammo-ts/logic/logging/types.js';
|
||||
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 { ActionContextBuilder } from '@sammo-ts/logic/actions/turn/actionContext.js';
|
||||
@@ -29,15 +28,15 @@ import { resolveInitYearMonth } from '@sammo-ts/logic/actions/turn/actionContext
|
||||
import type { GeneralTurnCommandSpec } from './index.js';
|
||||
import { parseArgsWithSchema } from '../parseArgs.js';
|
||||
import { JosaUtil } from '@sammo-ts/common';
|
||||
import {
|
||||
FOUNDING_ARGS_SCHEMA,
|
||||
getNationTypeDisplayName,
|
||||
NATION_COLORS,
|
||||
type FoundingArgs,
|
||||
} from './foundingShared.js';
|
||||
|
||||
const ACTION_NAME = '무작위 도시 건국';
|
||||
const ACTION_KEY = 'che_무작위건국';
|
||||
const ARGS_SCHEMA = z.object({
|
||||
nationName: z.string().min(1),
|
||||
nationType: z.string().min(1),
|
||||
colorType: z.number(),
|
||||
});
|
||||
export type FoundingArgs = z.infer<typeof ARGS_SCHEMA>;
|
||||
|
||||
export interface RandomFoundingResolveContext<
|
||||
TriggerState extends GeneralTriggerState = GeneralTriggerState,
|
||||
@@ -48,41 +47,14 @@ export interface RandomFoundingResolveContext<
|
||||
initYearMonth?: number;
|
||||
}
|
||||
|
||||
const NATION_COLORS = [
|
||||
'#FF0000',
|
||||
'#800000',
|
||||
'#A0522D',
|
||||
'#FF6347',
|
||||
'#FFA500',
|
||||
'#FFDAB9',
|
||||
'#FFD700',
|
||||
'#FFFF00',
|
||||
'#7CFC00',
|
||||
'#00FF00',
|
||||
'#808000',
|
||||
'#008000',
|
||||
'#2E8B57',
|
||||
'#008080',
|
||||
'#20B2AA',
|
||||
'#6495ED',
|
||||
'#7FFFD4',
|
||||
'#AFEEEE',
|
||||
'#87CEEB',
|
||||
'#00FFFF',
|
||||
'#00BFFF',
|
||||
'#0000FF',
|
||||
'#000080',
|
||||
'#483D8B',
|
||||
'#7B68EE',
|
||||
'#BA55D3',
|
||||
'#800080',
|
||||
'#FF00FF',
|
||||
'#FFC0CB',
|
||||
'#F5F5DC',
|
||||
'#E0FFFF',
|
||||
'#FFFFFF',
|
||||
'#A9A9A9',
|
||||
];
|
||||
type InclusiveRandomGenerator = GeneralActionResolveContext['rng'] & {
|
||||
nextIntInclusive?: (maxInclusive: number) => number;
|
||||
};
|
||||
|
||||
const legacyChoiceIndex = (rng: GeneralActionResolveContext['rng'], length: number): number => {
|
||||
const inclusive = rng as InclusiveRandomGenerator;
|
||||
return inclusive.nextIntInclusive ? inclusive.nextIntInclusive(length - 1) : rng.nextInt(0, length);
|
||||
};
|
||||
|
||||
export class ActionDefinition<
|
||||
TriggerState extends GeneralTriggerState = GeneralTriggerState,
|
||||
@@ -94,7 +66,7 @@ export class ActionDefinition<
|
||||
}
|
||||
|
||||
parseArgs(raw: unknown): FoundingArgs | null {
|
||||
return parseArgsWithSchema(ARGS_SCHEMA, raw);
|
||||
return parseArgsWithSchema(FOUNDING_ARGS_SCHEMA, raw);
|
||||
}
|
||||
|
||||
buildMinConstraints(_ctx: ConstraintContext, _args: FoundingArgs): Constraint[] {
|
||||
@@ -131,10 +103,6 @@ export class ActionDefinition<
|
||||
return { effects: [], alternative: { commandKey: 'che_인재탐색', args: {} } };
|
||||
}
|
||||
|
||||
if (args.colorType < 0 || args.colorType >= NATION_COLORS.length) {
|
||||
throw new Error('Invalid color type');
|
||||
}
|
||||
|
||||
const candidates = (context.allCities ?? []).filter(
|
||||
(city) => city.nationId === 0 && [5, 6].includes(city.level)
|
||||
);
|
||||
@@ -147,7 +115,7 @@ export class ActionDefinition<
|
||||
return { effects: [], alternative: { commandKey: 'che_해산', args: {} } };
|
||||
}
|
||||
|
||||
const picked = candidates[context.rng.nextInt(0, candidates.length)]!;
|
||||
const picked = candidates[legacyChoiceIndex(context.rng, candidates.length)]!;
|
||||
const cityId = picked.id;
|
||||
|
||||
const josaNationUl = JosaUtil.pick(args.nationName, '을');
|
||||
@@ -161,25 +129,33 @@ export class ActionDefinition<
|
||||
});
|
||||
context.addLog(`<Y>${general.name}</>${josaGeneralYi} <G><b>${picked.name}</b></>에 국가를 건설하였습니다.`, {
|
||||
scope: LogScope.SYSTEM,
|
||||
category: LogCategory.ACTION,
|
||||
category: LogCategory.SUMMARY,
|
||||
format: LogFormat.MONTH,
|
||||
});
|
||||
context.addLog(
|
||||
`<Y><b>【건국】</b></>${args.nationType} <D><b>${args.nationName}</b></>${josaNationYi} 새로이 등장하였습니다.`,
|
||||
`<Y><b>【건국】</b></>${getNationTypeDisplayName(args.nationType)} <D><b>${args.nationName}</b></>${josaNationYi} 새로이 등장하였습니다.`,
|
||||
{
|
||||
scope: LogScope.SYSTEM,
|
||||
category: LogCategory.HISTORY,
|
||||
format: LogFormat.YEAR_MONTH,
|
||||
}
|
||||
);
|
||||
context.addLog(`<D><b>${args.nationName}</b></>${josaNationUl} 건국`, {
|
||||
scope: LogScope.GENERAL,
|
||||
category: LogCategory.HISTORY,
|
||||
format: LogFormat.YEAR_MONTH,
|
||||
});
|
||||
context.addLog(`<Y>${general.name}</>${josaGeneralYi} <D><b>${args.nationName}</b></>${josaNationUl} 건국`, {
|
||||
scope: LogScope.NATION,
|
||||
category: LogCategory.HISTORY,
|
||||
format: LogFormat.YEAR_MONTH,
|
||||
});
|
||||
|
||||
tryApplyUniqueLottery(context, { acquireType: '건국', reason: ACTION_NAME });
|
||||
tryApplyUniqueLottery(context, {
|
||||
acquireType: '건국',
|
||||
reason: ACTION_NAME,
|
||||
nationName: args.nationName,
|
||||
});
|
||||
|
||||
const effects: Array<GeneralActionEffect<TriggerState>> = [
|
||||
createNationPatchEffect(
|
||||
@@ -200,6 +176,7 @@ export class ActionDefinition<
|
||||
createCityPatchEffect(
|
||||
{
|
||||
nationId: nation.id,
|
||||
conflict: {},
|
||||
},
|
||||
cityId
|
||||
),
|
||||
@@ -248,6 +225,6 @@ export const commandSpec: GeneralTurnCommandSpec = {
|
||||
nationType: 'string',
|
||||
colorType: 'number',
|
||||
},
|
||||
argsSchema: ARGS_SCHEMA,
|
||||
argsSchema: FOUNDING_ARGS_SCHEMA,
|
||||
createDefinition: (_env: TurnCommandEnv) => new ActionDefinition(),
|
||||
};
|
||||
|
||||
@@ -18,58 +18,27 @@ import {
|
||||
createNationPatchEffect,
|
||||
} from '@sammo-ts/logic/actions/engine.js';
|
||||
import { LogCategory, LogFormat, LogScope } 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 type { ActionContextBuilder } from '@sammo-ts/logic/actions/turn/actionContext.js';
|
||||
import { resolveInitYearMonth } from '@sammo-ts/logic/actions/turn/actionContextHelpers.js';
|
||||
import { tryApplyUniqueLottery } from '@sammo-ts/logic/rewards/uniqueLottery.js';
|
||||
import type { GeneralTurnCommandSpec } from './index.js';
|
||||
import { parseArgsWithSchema } from '../parseArgs.js';
|
||||
import { JosaUtil } from '@sammo-ts/common';
|
||||
import {
|
||||
FOUNDING_ARGS_SCHEMA,
|
||||
getNationTypeDisplayName,
|
||||
NATION_COLORS,
|
||||
type FoundingArgs,
|
||||
} from './foundingShared.js';
|
||||
|
||||
const ACTION_NAME = '건국';
|
||||
const ACTION_KEY = 'cr_건국';
|
||||
const ARGS_SCHEMA = z.object({
|
||||
nationName: z.string().min(1),
|
||||
nationType: z.string().min(1),
|
||||
colorType: z.number(),
|
||||
});
|
||||
export type FoundingArgs = z.infer<typeof ARGS_SCHEMA>;
|
||||
|
||||
const NATION_COLORS = [
|
||||
'#FF0000',
|
||||
'#800000',
|
||||
'#A0522D',
|
||||
'#FF6347',
|
||||
'#FFA500',
|
||||
'#FFDAB9',
|
||||
'#FFD700',
|
||||
'#FFFF00',
|
||||
'#7CFC00',
|
||||
'#00FF00',
|
||||
'#808000',
|
||||
'#008000',
|
||||
'#2E8B57',
|
||||
'#008080',
|
||||
'#20B2AA',
|
||||
'#6495ED',
|
||||
'#7FFFD4',
|
||||
'#AFEEEE',
|
||||
'#87CEEB',
|
||||
'#00FFFF',
|
||||
'#00BFFF',
|
||||
'#0000FF',
|
||||
'#000080',
|
||||
'#483D8B',
|
||||
'#7B68EE',
|
||||
'#BA55D3',
|
||||
'#800080',
|
||||
'#FF00FF',
|
||||
'#FFC0CB',
|
||||
'#F5F5DC',
|
||||
'#E0FFFF',
|
||||
'#FFFFFF',
|
||||
'#A9A9A9',
|
||||
];
|
||||
interface FoundingResolveContext<TriggerState extends GeneralTriggerState = GeneralTriggerState>
|
||||
extends GeneralActionResolveContext<TriggerState> {
|
||||
currentYearMonth?: number;
|
||||
initYearMonth?: number;
|
||||
}
|
||||
|
||||
export class ActionDefinition<
|
||||
TriggerState extends GeneralTriggerState = GeneralTriggerState,
|
||||
@@ -81,7 +50,7 @@ export class ActionDefinition<
|
||||
}
|
||||
|
||||
parseArgs(raw: unknown): FoundingArgs | null {
|
||||
return parseArgsWithSchema(ARGS_SCHEMA, raw);
|
||||
return parseArgsWithSchema(FOUNDING_ARGS_SCHEMA, raw);
|
||||
}
|
||||
|
||||
buildMinConstraints(_ctx: ConstraintContext, _args: FoundingArgs): Constraint[] {
|
||||
@@ -101,7 +70,7 @@ export class ActionDefinition<
|
||||
}
|
||||
|
||||
resolve(
|
||||
context: GeneralActionResolveContext<TriggerState>,
|
||||
context: FoundingResolveContext<TriggerState>,
|
||||
args: FoundingArgs
|
||||
): GeneralActionOutcome<TriggerState> {
|
||||
const general = context.general;
|
||||
@@ -111,8 +80,13 @@ export class ActionDefinition<
|
||||
}
|
||||
|
||||
const cityId = general.cityId;
|
||||
if (args.colorType < 0 || args.colorType >= NATION_COLORS.length) {
|
||||
throw new Error('Invalid color type');
|
||||
if ((context.currentYearMonth ?? 0) <= (context.initYearMonth ?? 0)) {
|
||||
context.addLog('다음 턴부터 건국할 수 있습니다.', {
|
||||
scope: LogScope.GENERAL,
|
||||
category: LogCategory.ACTION,
|
||||
format: LogFormat.MONTH,
|
||||
});
|
||||
return { effects: [], alternative: { commandKey: 'che_인재탐색', args: {} } };
|
||||
}
|
||||
const color = NATION_COLORS[args.colorType];
|
||||
|
||||
@@ -126,26 +100,34 @@ export class ActionDefinition<
|
||||
format: LogFormat.MONTH,
|
||||
});
|
||||
context.addLog(`<Y>${general.name}</>${josaGeneralYi} <G><b>${cityName}</b></>에 국가를 건설하였습니다.`, {
|
||||
category: LogCategory.ACTION,
|
||||
category: LogCategory.SUMMARY,
|
||||
scope: LogScope.SYSTEM,
|
||||
format: LogFormat.MONTH,
|
||||
});
|
||||
context.addLog(
|
||||
`<Y><b>【건국】</b></>${args.nationType} <D><b>${args.nationName}</b></>${josaNationYi} 새로이 등장하였습니다.`,
|
||||
`<Y><b>【건국】</b></>${getNationTypeDisplayName(args.nationType)} <D><b>${args.nationName}</b></>${josaNationYi} 새로이 등장하였습니다.`,
|
||||
{
|
||||
category: LogCategory.HISTORY,
|
||||
scope: LogScope.SYSTEM,
|
||||
format: LogFormat.YEAR_MONTH,
|
||||
}
|
||||
);
|
||||
context.addLog(`<D><b>${args.nationName}</b></>${josaNationUl} 건국`, {
|
||||
category: LogCategory.HISTORY,
|
||||
scope: LogScope.GENERAL,
|
||||
format: LogFormat.YEAR_MONTH,
|
||||
});
|
||||
context.addLog(`<Y>${general.name}</>${josaGeneralYi} <D><b>${args.nationName}</b></>${josaNationUl} 건국`, {
|
||||
category: LogCategory.HISTORY,
|
||||
scope: LogScope.NATION,
|
||||
format: LogFormat.YEAR_MONTH,
|
||||
});
|
||||
|
||||
tryApplyUniqueLottery(context, { acquireType: '건국', reason: ACTION_NAME });
|
||||
tryApplyUniqueLottery(context, {
|
||||
acquireType: '건국',
|
||||
reason: ACTION_NAME,
|
||||
nationName: args.nationName,
|
||||
});
|
||||
|
||||
const effects = [
|
||||
createNationPatchEffect(
|
||||
@@ -165,6 +147,7 @@ export class ActionDefinition<
|
||||
createCityPatchEffect(
|
||||
{
|
||||
nationId: nation.id,
|
||||
conflict: {},
|
||||
},
|
||||
cityId
|
||||
),
|
||||
@@ -178,7 +161,14 @@ export class ActionDefinition<
|
||||
}
|
||||
}
|
||||
|
||||
export const actionContextBuilder = defaultActionContextBuilder;
|
||||
export const actionContextBuilder: ActionContextBuilder = (base, options) => {
|
||||
const init = resolveInitYearMonth(options.world, options.scenarioMeta);
|
||||
return {
|
||||
...base,
|
||||
currentYearMonth: options.world.currentYear * 12 + options.world.currentMonth - 1,
|
||||
initYearMonth: init.year * 12 + init.month - 1,
|
||||
};
|
||||
};
|
||||
|
||||
export const commandSpec: GeneralTurnCommandSpec = {
|
||||
key: ACTION_KEY,
|
||||
@@ -189,6 +179,6 @@ export const commandSpec: GeneralTurnCommandSpec = {
|
||||
nationType: 'string',
|
||||
colorType: 'number',
|
||||
},
|
||||
argsSchema: ARGS_SCHEMA,
|
||||
argsSchema: FOUNDING_ARGS_SCHEMA,
|
||||
createDefinition: (_env: TurnCommandEnv) => new ActionDefinition(),
|
||||
};
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
import { NATION_TRAIT_KEYS } from '@sammo-ts/logic/triggers/special/nation/index.js';
|
||||
import { getLegacyStringWidth } from '@sammo-ts/logic/troop/management.js';
|
||||
import { z } from 'zod';
|
||||
|
||||
export const NATION_COLORS = [
|
||||
'#FF0000',
|
||||
'#800000',
|
||||
'#A0522D',
|
||||
'#FF6347',
|
||||
'#FFA500',
|
||||
'#FFDAB9',
|
||||
'#FFD700',
|
||||
'#FFFF00',
|
||||
'#7CFC00',
|
||||
'#00FF00',
|
||||
'#808000',
|
||||
'#008000',
|
||||
'#2E8B57',
|
||||
'#008080',
|
||||
'#20B2AA',
|
||||
'#6495ED',
|
||||
'#7FFFD4',
|
||||
'#AFEEEE',
|
||||
'#87CEEB',
|
||||
'#00FFFF',
|
||||
'#00BFFF',
|
||||
'#0000FF',
|
||||
'#000080',
|
||||
'#483D8B',
|
||||
'#7B68EE',
|
||||
'#BA55D3',
|
||||
'#800080',
|
||||
'#FF00FF',
|
||||
'#FFC0CB',
|
||||
'#F5F5DC',
|
||||
'#E0FFFF',
|
||||
'#FFFFFF',
|
||||
'#A9A9A9',
|
||||
] as const;
|
||||
|
||||
const SELECTABLE_NATION_TYPES = new Set<string>(NATION_TRAIT_KEYS.filter((key) => key !== 'che_중립'));
|
||||
|
||||
export const FOUNDING_ARGS_SCHEMA = z.object({
|
||||
nationName: z
|
||||
.string()
|
||||
.min(1)
|
||||
.refine((value) => getLegacyStringWidth(value) <= 18),
|
||||
nationType: z.string().refine((value) => SELECTABLE_NATION_TYPES.has(value)),
|
||||
colorType: z
|
||||
.number()
|
||||
.int()
|
||||
.min(0)
|
||||
.max(NATION_COLORS.length - 1),
|
||||
});
|
||||
|
||||
export type FoundingArgs = z.infer<typeof FOUNDING_ARGS_SCHEMA>;
|
||||
|
||||
export const getNationTypeDisplayName = (nationType: string): string =>
|
||||
nationType.startsWith('che_') ? nationType.slice(4) : nationType;
|
||||
@@ -268,7 +268,7 @@ export const noPenalty = (penaltyKey: string): Constraint => ({
|
||||
return unknownOrDeny(ctx, [generalReq], '장수 정보가 없습니다.');
|
||||
}
|
||||
|
||||
const penalty = general.meta.penalty;
|
||||
const penalty = (general as General & { penalty?: unknown }).penalty ?? general.meta.penalty;
|
||||
if (!penalty || typeof penalty !== 'object' || Array.isArray(penalty)) {
|
||||
return allow();
|
||||
}
|
||||
|
||||
@@ -265,24 +265,16 @@ export const differentDestNation = (): Constraint => ({
|
||||
export const reqNationGeneralCount = (min: number): Constraint => ({
|
||||
name: 'reqNationGeneralCount',
|
||||
requires: (ctx) => {
|
||||
const reqs: RequirementKey[] = [{ kind: 'generalList' }];
|
||||
const reqs: RequirementKey[] = [];
|
||||
if (ctx.nationId !== undefined) {
|
||||
reqs.push({ kind: 'nation', id: ctx.nationId });
|
||||
} else {
|
||||
reqs.push({ kind: 'general', id: ctx.actorId });
|
||||
reqs.push({ kind: 'nationList' });
|
||||
}
|
||||
return reqs;
|
||||
},
|
||||
test: (ctx, view) => {
|
||||
const listReq: RequirementKey = { kind: 'generalList' };
|
||||
if (!view.has(listReq)) {
|
||||
return unknownOrDeny(ctx, [listReq], '장수가 없습니다.');
|
||||
}
|
||||
const generals = view.get(listReq) as General[] | null;
|
||||
if (!generals) {
|
||||
return unknownOrDeny(ctx, [listReq], '장수가 없습니다.');
|
||||
}
|
||||
|
||||
let baseNationId = ctx.nationId;
|
||||
if (baseNationId === undefined) {
|
||||
const general = readGeneral(ctx, view);
|
||||
@@ -296,7 +288,16 @@ export const reqNationGeneralCount = (min: number): Constraint => ({
|
||||
baseNationId = general.nationId;
|
||||
}
|
||||
|
||||
const count = generals.filter((g) => g.nationId === baseNationId).length;
|
||||
const nationReq: RequirementKey =
|
||||
ctx.nationId !== undefined ? { kind: 'nation', id: baseNationId } : { kind: 'nationList' };
|
||||
const nation =
|
||||
ctx.nationId !== undefined
|
||||
? (view.get(nationReq) as Nation | null)
|
||||
: ((view.get(nationReq) as Nation[] | null)?.find((entry) => entry.id === baseNationId) ?? null);
|
||||
if (!nation) {
|
||||
return unknownOrDeny(ctx, [nationReq], '국가 정보가 없습니다.');
|
||||
}
|
||||
const count = typeof nation.meta.gennum === 'number' ? nation.meta.gennum : 0;
|
||||
if (count >= min) {
|
||||
return allow();
|
||||
}
|
||||
|
||||
@@ -144,7 +144,7 @@ describe('Blank Start Scenario', () => {
|
||||
|
||||
const FOUNDING_ARGS = {
|
||||
nationName: 'NewChosen',
|
||||
nationType: 'che_def',
|
||||
nationType: 'che_도적',
|
||||
colorType: 1,
|
||||
};
|
||||
|
||||
@@ -218,6 +218,7 @@ describe('Blank Start Scenario', () => {
|
||||
nationId: newNationId,
|
||||
officerLevel: 1,
|
||||
} as General;
|
||||
world.getNation(newNationId)!.meta.gennum = 2;
|
||||
|
||||
// --- Step 3: Gen 0 performs Founding ---
|
||||
// First, check name duplicate
|
||||
@@ -232,7 +233,7 @@ describe('Blank Start Scenario', () => {
|
||||
power: 0,
|
||||
level: 1,
|
||||
typeCode: 'che_def',
|
||||
meta: {},
|
||||
meta: { gennum: 2 },
|
||||
};
|
||||
world.snapshot.nations.push(duplicateNation);
|
||||
const ctxDuplicate = createConstraintContext(world.getGeneral(gen0.id)!, 189, {
|
||||
@@ -263,6 +264,10 @@ describe('Blank Start Scenario', () => {
|
||||
commandKey: 'che_건국',
|
||||
resolver: foundNationDef,
|
||||
args: FOUNDING_ARGS,
|
||||
context: {
|
||||
currentYearMonth: 189 * 12 + 2,
|
||||
initYearMonth: 189 * 12,
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
@@ -293,7 +298,7 @@ describe('Blank Start Scenario', () => {
|
||||
power: 0,
|
||||
level: 0,
|
||||
typeCode: 'che_def',
|
||||
meta: {},
|
||||
meta: { gennum: 2 },
|
||||
};
|
||||
world.snapshot.nations.push(newNation);
|
||||
const gen0Idx = world.snapshot.generals.findIndex((g) => g.id === gen0.id);
|
||||
|
||||
@@ -195,6 +195,7 @@ describe('migrated general commands', () => {
|
||||
expect(updatedChief.officerLevel).toBe(12);
|
||||
expect(updatedLord.officerLevel).toBe(1);
|
||||
expect(updatedLord.experience).toBe(700);
|
||||
expect(world.getNation(nation.id)?.chiefGeneralId).toBe(chief.id);
|
||||
});
|
||||
|
||||
it('che_선양: 제약 없는 대상에게 선양한다', async () => {
|
||||
@@ -316,7 +317,7 @@ describe('migrated general commands', () => {
|
||||
capitalCityId: null,
|
||||
level: 0,
|
||||
typeCode: 'None',
|
||||
meta: {},
|
||||
meta: { gennum: 2 },
|
||||
});
|
||||
const city = makeCity({ id: 1, nationId: 0, level: 5 });
|
||||
|
||||
@@ -330,7 +331,11 @@ describe('migrated general commands', () => {
|
||||
generalId: lord.id,
|
||||
commandKey: 'cr_건국',
|
||||
resolver: foundNationSpec.createDefinition(SYSTEM_ENV),
|
||||
args: { nationName: '신국', nationType: 'che_def', colorType: 1 },
|
||||
args: { nationName: '신국', nationType: 'che_도적', colorType: 1 },
|
||||
context: {
|
||||
currentYearMonth: 201 * 12,
|
||||
initYearMonth: 200 * 12,
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
|
||||
@@ -155,6 +155,7 @@ export const projectCoreDatabaseSnapshot = (rows: {
|
||||
dex5: readNumber(meta, 'dex5'),
|
||||
specAge: readNumber(meta, 'specage'),
|
||||
specAge2: readNumber(meta, 'specage2'),
|
||||
makeLimit: readNumber(meta, 'makelimit'),
|
||||
penalty: asRecord(row.penalty),
|
||||
killTurn: readNumber(meta, 'killturn'),
|
||||
mySet: readNumber(meta, 'myset'),
|
||||
|
||||
@@ -254,6 +254,7 @@ const buildGeneral = (row: Record<string, unknown>, fallbackTurnTime: Date): Tur
|
||||
...meta,
|
||||
specage: readNumber(row, 'specAge', readNumber(meta, 'specage')),
|
||||
specage2: readNumber(row, 'specAge2', readNumber(meta, 'specage2')),
|
||||
makelimit: readNumber(row, 'makeLimit', readNumber(meta, 'makelimit')),
|
||||
killturn: readNumber(row, 'killTurn', readNumber(meta, 'killturn', 24)),
|
||||
leadership_exp: readNumber(row, 'leadershipExp', readNumber(meta, 'leadership_exp')),
|
||||
strength_exp: readNumber(row, 'strengthExp', readNumber(meta, 'strength_exp')),
|
||||
@@ -573,6 +574,7 @@ const projectWorld = (
|
||||
dex5: toDatabaseInt(readNumber(general.meta, 'dex5')),
|
||||
specAge: toDatabaseInt(readNumber(general.meta, 'specage')),
|
||||
specAge2: toDatabaseInt(readNumber(general.meta, 'specage2')),
|
||||
makeLimit: toDatabaseInt(readNumber(general.meta, 'makelimit')),
|
||||
penalty: asRecord(general.penalty),
|
||||
killTurn: readNumber(general.meta, 'killturn'),
|
||||
mySet: readNumber(general.meta, 'myset'),
|
||||
@@ -721,7 +723,10 @@ export const runCoreTurnCommandTrace = async (
|
||||
...(request.observe?.generalIds ?? []),
|
||||
]),
|
||||
cityIds: new Set(referenceBefore.cities.map((row) => readNumber(row, 'id'))),
|
||||
nationIds: new Set(referenceBefore.nations.map((row) => readNumber(row, 'id'))),
|
||||
nationIds: new Set([
|
||||
...referenceBefore.nations.map((row) => readNumber(row, 'id')),
|
||||
...(request.observe?.nationIds ?? []),
|
||||
]),
|
||||
initialGeneralIds: new Set(referenceBefore.generals.map((row) => readNumber(row, 'id'))),
|
||||
generalCooldowns: request.observe?.generalCooldowns ?? [],
|
||||
nationCooldowns: request.observe?.nationCooldowns ?? [],
|
||||
|
||||
@@ -2240,6 +2240,192 @@ integration('general resignation, retirement, and abdication boundary, state, an
|
||||
);
|
||||
});
|
||||
|
||||
type GeneralFoundingRebellionBoundaryCase = {
|
||||
name: string;
|
||||
action: 'che_거병' | 'che_모반시도' | 'che_건국' | 'cr_건국' | 'che_무작위건국';
|
||||
args?: Record<string, unknown>;
|
||||
actorPatch?: Record<string, unknown>;
|
||||
fixturePatches?: FixturePatches;
|
||||
completed: boolean;
|
||||
expectedActionKey?: string;
|
||||
compareLogs?: boolean;
|
||||
};
|
||||
|
||||
const foundingArgs = { nationName: '신국', nationType: 'che_도적', colorType: 1 };
|
||||
const wanderingFoundingFixture = (overrides: FixturePatches = {}): FixturePatches => ({
|
||||
...overrides,
|
||||
world: { startYear: 180, initYear: 180, initMonth: 1, year: 181, ...overrides.world },
|
||||
nations: {
|
||||
...overrides.nations,
|
||||
1: { level: 0, capitalCityId: 0, typeCode: 'None', generalCount: 2, ...overrides.nations?.[1] },
|
||||
},
|
||||
cities: {
|
||||
...overrides.cities,
|
||||
3: { nationId: 0, level: 5, ...overrides.cities?.[3] },
|
||||
},
|
||||
});
|
||||
|
||||
const generalFoundingRebellionBoundaryCases: GeneralFoundingRebellionBoundaryCase[] = [
|
||||
{
|
||||
name: 'uprising rejects a no-founding penalty from the general column',
|
||||
action: 'che_거병',
|
||||
actorPatch: { nationId: 0, officerLevel: 0, penalty: { noFoundNation: true } },
|
||||
fixturePatches: { world: { startYear: 180, year: 181 } },
|
||||
completed: false,
|
||||
},
|
||||
{
|
||||
name: 'uprising rejects a remaining join-action cooldown',
|
||||
action: 'che_거병',
|
||||
actorPatch: { nationId: 0, officerLevel: 0, makeLimit: 1 },
|
||||
fixturePatches: { world: { startYear: 180, year: 181 } },
|
||||
completed: false,
|
||||
},
|
||||
{
|
||||
name: 'uprising trims a duplicate full-width nation name and preserves logs',
|
||||
action: 'che_거병',
|
||||
actorPatch: { name: '가나다라마바사아자차', nationId: 0, officerLevel: 0 },
|
||||
fixturePatches: {
|
||||
world: { startYear: 180, year: 181 },
|
||||
nations: { 1: { name: '가나다라마바사아자차' } },
|
||||
},
|
||||
completed: true,
|
||||
compareLogs: true,
|
||||
},
|
||||
{
|
||||
name: 'rebellion rejects an active monarch',
|
||||
action: 'che_모반시도',
|
||||
actorPatch: { officerLevel: 11 },
|
||||
fixturePatches: { generals: { 3: { officerLevel: 12, officerCityId: 3, killTurn: 999_999 } } },
|
||||
completed: false,
|
||||
},
|
||||
{
|
||||
name: 'rebellion rejects an npc monarch',
|
||||
action: 'che_모반시도',
|
||||
actorPatch: { officerLevel: 11 },
|
||||
fixturePatches: { generals: { 3: { officerLevel: 12, officerCityId: 3, killTurn: 0, npcState: 2 } } },
|
||||
completed: false,
|
||||
},
|
||||
{
|
||||
name: 'rebellion rounds the former monarch experience and transfers the chief cache',
|
||||
action: 'che_모반시도',
|
||||
actorPatch: { officerLevel: 11 },
|
||||
fixturePatches: {
|
||||
generals: { 3: { officerLevel: 12, officerCityId: 3, killTurn: 0, experience: 1001 } },
|
||||
},
|
||||
completed: true,
|
||||
compareLogs: true,
|
||||
},
|
||||
...(['che_건국', 'cr_건국', 'che_무작위건국'] as const).map((action) => ({
|
||||
name: `${action} rejects a fractional color index`,
|
||||
action,
|
||||
args: { ...foundingArgs, colorType: 1.5 },
|
||||
fixturePatches: wanderingFoundingFixture(),
|
||||
completed: false,
|
||||
})),
|
||||
{
|
||||
name: 'founding rejects a nation name wider than eighteen legacy columns',
|
||||
action: 'che_건국',
|
||||
args: { ...foundingArgs, nationName: '가나다라마바사아자차' },
|
||||
fixturePatches: wanderingFoundingFixture(),
|
||||
completed: false,
|
||||
},
|
||||
{
|
||||
name: 'founding rejects an unavailable nation type',
|
||||
action: 'che_건국',
|
||||
args: { ...foundingArgs, nationType: 'che_없는국가형' },
|
||||
fixturePatches: wanderingFoundingFixture(),
|
||||
completed: false,
|
||||
},
|
||||
...(['che_건국', 'cr_건국', 'che_무작위건국'] as const).map((action) => ({
|
||||
name: `${action} uses the cached nation general count`,
|
||||
action,
|
||||
args: foundingArgs,
|
||||
fixturePatches: wanderingFoundingFixture({ nations: { 1: { generalCount: 1 } } }),
|
||||
completed: false,
|
||||
})),
|
||||
{
|
||||
name: 'founding continues into talent scout during the initial month',
|
||||
action: 'che_건국',
|
||||
args: foundingArgs,
|
||||
fixturePatches: wanderingFoundingFixture({
|
||||
world: { startYear: 180, initYear: 181, initMonth: 1, year: 181 },
|
||||
}),
|
||||
completed: false,
|
||||
expectedActionKey: 'che_인재탐색',
|
||||
},
|
||||
{
|
||||
name: 'restricted founding continues into talent scout during the initial month',
|
||||
action: 'cr_건국',
|
||||
args: foundingArgs,
|
||||
fixturePatches: wanderingFoundingFixture({
|
||||
world: { startYear: 180, initYear: 181, initMonth: 1, year: 181 },
|
||||
}),
|
||||
completed: false,
|
||||
expectedActionKey: 'che_인재탐색',
|
||||
},
|
||||
...(['che_건국', 'cr_건국', 'che_무작위건국'] as const).map((action) => ({
|
||||
name: `${action} clears the founding city conflict and preserves success logs`,
|
||||
action,
|
||||
args: foundingArgs,
|
||||
fixturePatches: wanderingFoundingFixture({
|
||||
cities: { 3: { conflict: { 2: 7 } } },
|
||||
randomFoundingCandidateCityIds: action === 'che_무작위건국' ? [3] : undefined,
|
||||
}),
|
||||
completed: true,
|
||||
compareLogs: true,
|
||||
})),
|
||||
];
|
||||
|
||||
integration('general uprising, rebellion, and founding boundary, state, RNG, and log parity', () => {
|
||||
it.each(generalFoundingRebellionBoundaryCases)(
|
||||
'$name',
|
||||
async ({
|
||||
name,
|
||||
action,
|
||||
args,
|
||||
actorPatch,
|
||||
fixturePatches,
|
||||
completed,
|
||||
expectedActionKey,
|
||||
compareLogs,
|
||||
}) => {
|
||||
const request = buildRequest(action, args, actorPatch, fixturePatches);
|
||||
request.setup!.world!.hiddenSeed = `general-founding-rebellion-${name}`;
|
||||
if (action === 'che_거병') {
|
||||
request.observe!.nationIds!.push(3);
|
||||
}
|
||||
request.observe!.includeGlobalHistoryLogs = true;
|
||||
request.observe!.includeNationHistoryLogs = true;
|
||||
const reference = runReferenceTurnCommandTraceRequest(
|
||||
workspaceRoot!,
|
||||
request as unknown as Record<string, unknown>
|
||||
);
|
||||
const core = await runCoreTurnCommandTrace(request, reference.before);
|
||||
const actionKey = expectedActionKey ?? (completed ? action : '휴식');
|
||||
|
||||
expect(reference.execution.outcome).toMatchObject({ completed });
|
||||
expect(core.execution.outcome).toMatchObject({
|
||||
requestedAction: action,
|
||||
actionKey,
|
||||
usedFallback: !completed && actionKey === '휴식',
|
||||
});
|
||||
expect(core.rng).toEqual(reference.rng);
|
||||
expect(
|
||||
compareTurnSnapshotDeltas(reference.before, reference.after, core.before, core.after, {
|
||||
ignoredPathPatterns: ignoredLifecyclePaths,
|
||||
})
|
||||
).toEqual([]);
|
||||
|
||||
if (compareLogs) {
|
||||
expect(semanticLogSignatures(core.after.logs)).toEqual(
|
||||
semanticLogSignatures(addedReferenceLogs(reference.before, reference.after.logs))
|
||||
);
|
||||
}
|
||||
},
|
||||
120_000
|
||||
);
|
||||
});
|
||||
|
||||
type SabotageProbabilityClampCase = {
|
||||
name: string;
|
||||
action: 'che_화계' | 'che_선동' | 'che_파괴' | 'che_탈취';
|
||||
|
||||
Reference in New Issue
Block a user