fix general founding and rebellion parity
This commit is contained in:
@@ -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,
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user