코드 수정
This commit is contained in:
@@ -18,6 +18,8 @@ import {
|
|||||||
type UnitSetDefinition,
|
type UnitSetDefinition,
|
||||||
type WarBattleOutcome,
|
type WarBattleOutcome,
|
||||||
type WarActionModule,
|
type WarActionModule,
|
||||||
|
type WarUnitReport,
|
||||||
|
type CrewTypeDefinition,
|
||||||
} from '@sammo-ts/logic';
|
} from '@sammo-ts/logic';
|
||||||
|
|
||||||
import { type BattleSimJobPayload, type BattleSimLogBuckets, type BattleSimResultPayload } from './types.js';
|
import { type BattleSimJobPayload, type BattleSimLogBuckets, type BattleSimResultPayload } from './types.js';
|
||||||
@@ -217,7 +219,7 @@ const resolveCityRiceConsumption = (options: {
|
|||||||
year: number;
|
year: number;
|
||||||
startYear: number;
|
startYear: number;
|
||||||
}): number => {
|
}): number => {
|
||||||
const cityReport = options.battle.reports.find((report: any) => report.type === 'city');
|
const cityReport = options.battle.reports.find((report: WarUnitReport) => report.type === 'city');
|
||||||
if (!cityReport) {
|
if (!cityReport) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -225,7 +227,9 @@ const resolveCityRiceConsumption = (options: {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
const crewType = options.unitSet.crewTypes?.find((item: any) => item.id === options.castleCrewTypeId);
|
const crewType = options.unitSet.crewTypes?.find(
|
||||||
|
(item: CrewTypeDefinition) => item.id === options.castleCrewTypeId
|
||||||
|
);
|
||||||
const riceCoef = crewType?.rice ?? 1;
|
const riceCoef = crewType?.rice ?? 1;
|
||||||
const tech = Number(options.defenderNation.meta.tech ?? 0);
|
const tech = Number(options.defenderNation.meta.tech ?? 0);
|
||||||
const trainAtmos = resolveCityTrainAtmos(options.year, options.startYear);
|
const trainAtmos = resolveCityTrainAtmos(options.year, options.startYear);
|
||||||
@@ -333,7 +337,9 @@ export const processBattleSimJob = (payload: BattleSimJobPayload): BattleSimResu
|
|||||||
});
|
});
|
||||||
|
|
||||||
lastBattle = outcome;
|
lastBattle = outcome;
|
||||||
const attackerReport = outcome.reports.find((report: any) => report.type === 'general' && report.isAttacker);
|
const attackerReport = outcome.reports.find(
|
||||||
|
(report: WarUnitReport) => report.type === 'general' && report.isAttacker
|
||||||
|
);
|
||||||
const killed = attackerReport?.killed ?? 0;
|
const killed = attackerReport?.killed ?? 0;
|
||||||
const dead = attackerReport?.dead ?? 0;
|
const dead = attackerReport?.dead ?? 0;
|
||||||
|
|
||||||
|
|||||||
@@ -328,7 +328,7 @@ const evaluateAvailability = (
|
|||||||
reason: result.reason,
|
reason: result.reason,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
const missingKinds = new Set(result.missing.map((req: any) => req.kind));
|
const missingKinds = new Set(result.missing.map((req: RequirementKey) => req.kind));
|
||||||
const inputOnlyMissing =
|
const inputOnlyMissing =
|
||||||
missingKinds.size === 0 ? reqArg : Array.from(missingKinds).every((kind) => INPUT_REQUIREMENT_KINDS.has(kind));
|
missingKinds.size === 0 ? reqArg : Array.from(missingKinds).every((kind) => INPUT_REQUIREMENT_KINDS.has(kind));
|
||||||
if (inputOnlyMissing) {
|
if (inputOnlyMissing) {
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import {
|
|||||||
nearCity,
|
nearCity,
|
||||||
reqGeneralGold,
|
reqGeneralGold,
|
||||||
reqGeneralRice,
|
reqGeneralRice,
|
||||||
|
|
||||||
existsDestCity,
|
existsDestCity,
|
||||||
} from '@sammo-ts/logic/constraints/presets.js';
|
} from '@sammo-ts/logic/constraints/presets.js';
|
||||||
import type { GeneralActionDefinition } from '@sammo-ts/logic/actions/definition.js';
|
import type { GeneralActionDefinition } from '@sammo-ts/logic/actions/definition.js';
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ import type { TurnCommandEnv } from '@sammo-ts/logic/actions/turn/commandEnv.js'
|
|||||||
import type { ActionContextBuilder } from '@sammo-ts/logic/actions/turn/actionContext.js';
|
import type { ActionContextBuilder } from '@sammo-ts/logic/actions/turn/actionContext.js';
|
||||||
import type { GeneralTurnCommandSpec } from './index.js';
|
import type { GeneralTurnCommandSpec } from './index.js';
|
||||||
|
|
||||||
export interface ReturnArgs { }
|
export interface ReturnArgs {}
|
||||||
|
|
||||||
const ACTION_NAME = '귀환';
|
const ACTION_NAME = '귀환';
|
||||||
const ACTION_KEY = 'che_귀환';
|
const ACTION_KEY = 'che_귀환';
|
||||||
|
|||||||
@@ -1,14 +1,12 @@
|
|||||||
import type { General, GeneralTriggerState, Nation } from '@sammo-ts/logic/domain/entities.js';
|
import type { General, GeneralTriggerState, Nation } from '@sammo-ts/logic/domain/entities.js';
|
||||||
import type { Constraint, ConstraintContext } from '@sammo-ts/logic/constraints/types.js';
|
import type { Constraint, ConstraintContext } from '@sammo-ts/logic/constraints/types.js';
|
||||||
import {
|
import {
|
||||||
allow,
|
|
||||||
existsDestNation,
|
existsDestNation,
|
||||||
existsDestGeneral,
|
existsDestGeneral,
|
||||||
notSameDestNation,
|
notSameDestNation,
|
||||||
destGeneralInDestNation,
|
destGeneralInDestNation,
|
||||||
notLord,
|
notLord,
|
||||||
readMetaNumberFromUnknown,
|
readMetaNumberFromUnknown,
|
||||||
unknownOrDeny,
|
|
||||||
} from '@sammo-ts/logic/constraints/presets.js';
|
} from '@sammo-ts/logic/constraints/presets.js';
|
||||||
import type { GeneralActionDefinition } from '@sammo-ts/logic/actions/definition.js';
|
import type { GeneralActionDefinition } from '@sammo-ts/logic/actions/definition.js';
|
||||||
import type {
|
import type {
|
||||||
@@ -44,6 +42,8 @@ export class ActionResolver<
|
|||||||
> implements GeneralActionResolver<TriggerState, AcceptScoutArgs> {
|
> implements GeneralActionResolver<TriggerState, AcceptScoutArgs> {
|
||||||
readonly key = ACTION_KEY;
|
readonly key = ACTION_KEY;
|
||||||
|
|
||||||
|
constructor(private readonly env: TurnCommandEnv) {}
|
||||||
|
|
||||||
resolve(
|
resolve(
|
||||||
context: AcceptScoutResolveContext<TriggerState>,
|
context: AcceptScoutResolveContext<TriggerState>,
|
||||||
_args: AcceptScoutArgs
|
_args: AcceptScoutArgs
|
||||||
@@ -59,7 +59,6 @@ export class ActionResolver<
|
|||||||
|
|
||||||
// 1. Logs
|
// 1. Logs
|
||||||
const destNationName = destNation.name;
|
const destNationName = destNation.name;
|
||||||
const recruiterName = destGeneral.name;
|
|
||||||
const generalName = general.name;
|
const generalName = general.name;
|
||||||
|
|
||||||
const josaRo = JosaUtil.pick(destNationName, '로');
|
const josaRo = JosaUtil.pick(destNationName, '로');
|
||||||
@@ -67,28 +66,14 @@ export class ActionResolver<
|
|||||||
|
|
||||||
// Self Log
|
// Self Log
|
||||||
context.addLog(`<D>${destNationName}</>${josaRo} 망명하여 수도로 이동합니다.`, {
|
context.addLog(`<D>${destNationName}</>${josaRo} 망명하여 수도로 이동합니다.`, {
|
||||||
// Text says "Move to Capital", but logic might move to recruiter city.
|
|
||||||
// Legacy log says "수도로 이동합니다", but implementation moves to destGeneral city if present!
|
|
||||||
// We should match implementation or text? Text is just flavor.
|
|
||||||
category: LogCategory.ACTION,
|
category: LogCategory.ACTION,
|
||||||
format: LogFormat.InGame, // Using InGame or specific format?
|
format: LogFormat.PLAIN,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Recruiter Log
|
|
||||||
// We need to add log to recruiter? `context.addSideEffectLog`?
|
|
||||||
// Current system mostly logs for the actor.
|
|
||||||
// If we want to log for recruiter, we might need a way to push logs to others in `effects` or strictly via `addLog` with target?
|
|
||||||
// `GeneralActionResolveContext` usually implies logs are for the actor.
|
|
||||||
// But `GeneralTurnOutcome` doesn't explicitly return logs for others.
|
|
||||||
// We can create a patch for recruiter that appends to their log?
|
|
||||||
// Or usage of `addLog` might support target? No, `addLog` in context usually targets actor.
|
|
||||||
// We will skip Recruiter Log for now or rely on Global Log.
|
|
||||||
|
|
||||||
// Global Log
|
// Global Log
|
||||||
context.addLog(`<Y>${generalName}</>${josaYi} <D><b>${destNationName}</b></>${josaRo} <S>망명</>하였습니다.`, {
|
context.addLog(`<Y>${generalName}</>${josaYi} <D><b>${destNationName}</b></>${josaRo} <S>망명</>하였습니다.`, {
|
||||||
category: LogCategory.ACTION, // Global category?
|
category: LogCategory.ACTION,
|
||||||
format: LogFormat.InGame, // Global logs are handled by system?
|
format: LogFormat.PLAIN,
|
||||||
// In new system, we might need to specify it.
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// 2. Recruiter Rewards
|
// 2. Recruiter Rewards
|
||||||
@@ -103,12 +88,9 @@ export class ActionResolver<
|
|||||||
);
|
);
|
||||||
|
|
||||||
// 3. Betrayal Logic
|
// 3. Betrayal Logic
|
||||||
// If currentNation exists (and > 0), handle betrayal return logic.
|
|
||||||
const defaultGold = 1000; // From env? context.env.defaultNpcGold? Or GameConst?
|
|
||||||
// Using context.env values if available. SystemEnv has `baseGold`?
|
|
||||||
// Legacy: GameConst::$defaultGold (usually 1000/2000).
|
// Legacy: GameConst::$defaultGold (usually 1000/2000).
|
||||||
const safeGold = context.env.baseGold || 1000;
|
const safeGold = this.env.baseGold || 1000;
|
||||||
const safeRice = context.env.baseRice || 1000;
|
const safeRice = this.env.baseRice || 1000;
|
||||||
|
|
||||||
let newGold = general.gold;
|
let newGold = general.gold;
|
||||||
let newRice = general.rice;
|
let newRice = general.rice;
|
||||||
@@ -147,10 +129,6 @@ export class ActionResolver<
|
|||||||
// Penalty
|
// Penalty
|
||||||
// 10% * betray count deduction
|
// 10% * betray count deduction
|
||||||
const penaltyFactor = 1 - 0.1 * betrayCount;
|
const penaltyFactor = 1 - 0.1 * betrayCount;
|
||||||
if (penaltyFactor < 0) {
|
|
||||||
// Should not be less than 0? capped at ?
|
|
||||||
// Legacy: (1 - 0.1 * betray).
|
|
||||||
}
|
|
||||||
// Apply penalty
|
// Apply penalty
|
||||||
newExp = Math.floor(newExp * Math.max(0, penaltyFactor));
|
newExp = Math.floor(newExp * Math.max(0, penaltyFactor));
|
||||||
newDed = Math.floor(newDed * Math.max(0, penaltyFactor));
|
newDed = Math.floor(newDed * Math.max(0, penaltyFactor));
|
||||||
@@ -162,9 +140,8 @@ export class ActionResolver<
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 4. Update General (Self)
|
// 4. Update General (Self)
|
||||||
let targetCityId = destGeneral.cityId; // Join recruiter
|
const targetCityId = destNation.capitalCityId;
|
||||||
// If recruiter is not valid city?
|
if (!targetCityId) throw new Error('Capital city not found.');
|
||||||
if (!targetCityId) targetCityId = destNation.capitalCityId!;
|
|
||||||
|
|
||||||
effects.push(
|
effects.push(
|
||||||
createGeneralPatchEffect(
|
createGeneralPatchEffect(
|
||||||
@@ -176,30 +153,17 @@ export class ActionResolver<
|
|||||||
gold: newGold,
|
gold: newGold,
|
||||||
rice: newRice,
|
rice: newRice,
|
||||||
officerLevel: 1, // Reset rank
|
officerLevel: 1, // Reset rank
|
||||||
// officer_city: 0 via meta
|
|
||||||
crew: general.crew, // Keep crew? Legacy implies checking troop leader.
|
|
||||||
// If troop leader, disband troop.
|
|
||||||
// TS entity `troopId`.
|
|
||||||
troopId: 0, // Quit troop
|
troopId: 0, // Quit troop
|
||||||
meta: {
|
meta: {
|
||||||
...general.meta,
|
...general.meta,
|
||||||
officer_city: 0,
|
officer_city: 0,
|
||||||
betray: newBetray,
|
betray: newBetray,
|
||||||
// killturn logic?
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
general.id
|
general.id
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
// 5. Update Nations Gen Count (Visual only? or real count)
|
|
||||||
// Legacy updates `gennum`.
|
|
||||||
// We can create patches for nations if `gennum` is part of Nation entity?
|
|
||||||
// Nation entity usually doesn't store computed `gennum` in TS domain?
|
|
||||||
// If it's real column, we can update.
|
|
||||||
// Checking entity: `Nation` interface does NOT have `gennum`.
|
|
||||||
// So we skip updating gennum on Nation entity.
|
|
||||||
|
|
||||||
return { effects };
|
return { effects };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -211,26 +175,18 @@ export class ActionDefinition<
|
|||||||
public readonly name = ACTION_NAME;
|
public readonly name = ACTION_NAME;
|
||||||
private readonly resolver: ActionResolver<TriggerState>;
|
private readonly resolver: ActionResolver<TriggerState>;
|
||||||
|
|
||||||
constructor() {
|
constructor(env: TurnCommandEnv) {
|
||||||
this.resolver = new ActionResolver();
|
this.resolver = new ActionResolver(env);
|
||||||
}
|
}
|
||||||
|
|
||||||
parseArgs(raw: unknown): AcceptScoutArgs | null {
|
parseArgs(raw: unknown): AcceptScoutArgs | null {
|
||||||
// Validate args
|
|
||||||
const args = raw as Partial<AcceptScoutArgs>;
|
const args = raw as Partial<AcceptScoutArgs>;
|
||||||
if (typeof args.destNationId !== 'number' || typeof args.destGeneralId !== 'number') return null;
|
if (typeof args.destNationId !== 'number' || typeof args.destGeneralId !== 'number') return null;
|
||||||
return { destNationId: args.destNationId, destGeneralId: args.destGeneralId };
|
return { destNationId: args.destNationId, destGeneralId: args.destGeneralId };
|
||||||
}
|
}
|
||||||
|
|
||||||
buildConstraints(_ctx: ConstraintContext, _args: AcceptScoutArgs): Constraint[] {
|
buildConstraints(_ctx: ConstraintContext, _args: AcceptScoutArgs): Constraint[] {
|
||||||
return [
|
return [existsDestNation(), existsDestGeneral(), notSameDestNation(), destGeneralInDestNation(), notLord()];
|
||||||
// notBeNeutral(), // Ignored to allow betrayal
|
|
||||||
existsDestNation(),
|
|
||||||
existsDestGeneral(), // Need to check if destGeneral exists
|
|
||||||
notSameDestNation(),
|
|
||||||
destGeneralInDestNation(),
|
|
||||||
notLord(),
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
resolve(
|
resolve(
|
||||||
@@ -242,16 +198,15 @@ export class ActionDefinition<
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const actionContextBuilder: ActionContextBuilder = (base, options) => {
|
export const actionContextBuilder: ActionContextBuilder = (base, options) => {
|
||||||
// Populate destNation and destGeneral
|
const args = options.actionArgs as Partial<AcceptScoutArgs>;
|
||||||
const args = base.args as Partial<AcceptScoutArgs>;
|
|
||||||
let destNation: Nation | undefined;
|
let destNation: Nation | undefined;
|
||||||
let destGeneral: General | undefined;
|
let destGeneral: General | undefined;
|
||||||
|
|
||||||
if (args.destNationId) {
|
if (args.destNationId) {
|
||||||
destNation = options.worldRef?.getNation(args.destNationId);
|
destNation = options.worldRef?.getNationById(args.destNationId) ?? undefined;
|
||||||
}
|
}
|
||||||
if (args.destGeneralId) {
|
if (args.destGeneralId) {
|
||||||
destGeneral = options.worldRef?.getGeneral(args.destGeneralId);
|
destGeneral = options.worldRef?.getGeneralById(args.destGeneralId) ?? undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -263,14 +218,11 @@ export const actionContextBuilder: ActionContextBuilder = (base, options) => {
|
|||||||
|
|
||||||
export const commandSpec: GeneralTurnCommandSpec = {
|
export const commandSpec: GeneralTurnCommandSpec = {
|
||||||
key: 'che_등용수락',
|
key: 'che_등용수락',
|
||||||
category: '계략', // Strategy? or '인사'(Personnel)? Legacy not checked for category. "군사" in task.md?
|
category: '인사',
|
||||||
// che_등용수락 is usually separate.
|
|
||||||
// che_등용 is 인사(Personnel).
|
|
||||||
// Let's use '인사'.
|
|
||||||
reqArg: true,
|
reqArg: true,
|
||||||
args: {
|
args: {
|
||||||
destNationId: 'number',
|
destNationId: 'number',
|
||||||
destGeneralId: 'number',
|
destGeneralId: 'number',
|
||||||
},
|
},
|
||||||
createDefinition: (_env: TurnCommandEnv) => new ActionDefinition(),
|
createDefinition: (env: TurnCommandEnv) => new ActionDefinition(env),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -111,7 +111,7 @@ export class GeneralTurnCommandLoader {
|
|||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly importers: Record<GeneralTurnCommandKey, GeneralTurnCommandImporter> = defaultImporters
|
private readonly importers: Record<GeneralTurnCommandKey, GeneralTurnCommandImporter> = defaultImporters
|
||||||
) { }
|
) {}
|
||||||
|
|
||||||
async load(key: GeneralTurnCommandKey): Promise<GeneralTurnCommandModule> {
|
async load(key: GeneralTurnCommandKey): Promise<GeneralTurnCommandModule> {
|
||||||
const cached = this.cache.get(key);
|
const cached = this.cache.get(key);
|
||||||
|
|||||||
@@ -114,11 +114,11 @@ export const suppliedDestCity = (): Constraint => ({
|
|||||||
requires: (ctx) =>
|
requires: (ctx) =>
|
||||||
resolveDestCityId(ctx) !== undefined
|
resolveDestCityId(ctx) !== undefined
|
||||||
? [
|
? [
|
||||||
{
|
{
|
||||||
kind: 'destCity',
|
kind: 'destCity',
|
||||||
id: resolveDestCityId(ctx) ?? 0,
|
id: resolveDestCityId(ctx) ?? 0,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
: [],
|
: [],
|
||||||
test: (ctx, view) => {
|
test: (ctx, view) => {
|
||||||
const destCity = readDestCity(ctx, view);
|
const destCity = readDestCity(ctx, view);
|
||||||
@@ -250,11 +250,11 @@ export const existsDestCity = (): Constraint => ({
|
|||||||
requires: (ctx) =>
|
requires: (ctx) =>
|
||||||
resolveDestCityId(ctx) !== undefined
|
resolveDestCityId(ctx) !== undefined
|
||||||
? [
|
? [
|
||||||
{
|
{
|
||||||
kind: 'destCity',
|
kind: 'destCity',
|
||||||
id: resolveDestCityId(ctx) ?? 0,
|
id: resolveDestCityId(ctx) ?? 0,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
: [],
|
: [],
|
||||||
test: (ctx, view) => {
|
test: (ctx, view) => {
|
||||||
const destCityId = resolveDestCityId(ctx);
|
const destCityId = resolveDestCityId(ctx);
|
||||||
@@ -316,11 +316,11 @@ export const notNeutralDestCity = (): Constraint => ({
|
|||||||
requires: (ctx) =>
|
requires: (ctx) =>
|
||||||
resolveDestCityId(ctx) !== undefined
|
resolveDestCityId(ctx) !== undefined
|
||||||
? [
|
? [
|
||||||
{
|
{
|
||||||
kind: 'destCity',
|
kind: 'destCity',
|
||||||
id: resolveDestCityId(ctx) ?? 0,
|
id: resolveDestCityId(ctx) ?? 0,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
: [],
|
: [],
|
||||||
test: (ctx, view) => {
|
test: (ctx, view) => {
|
||||||
const destCity = readDestCity(ctx, view);
|
const destCity = readDestCity(ctx, view);
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import { InMemoryWorld, TestGameRunner } from '../../testEnv.js';
|
|||||||
import { evaluateActionConstraints } from '../../../src/constraints/evaluate.js';
|
import { evaluateActionConstraints } from '../../../src/constraints/evaluate.js';
|
||||||
import type { TurnCommandEnv } from '../../../src/actions/turn/commandEnv.js';
|
import type { TurnCommandEnv } from '../../../src/actions/turn/commandEnv.js';
|
||||||
import type { ConstraintContext, RequirementKey, StateView } from '../../../src/constraints/types.js';
|
import type { ConstraintContext, RequirementKey, StateView } from '../../../src/constraints/types.js';
|
||||||
import { readGeneral } from '../../../src/constraints/helpers.js';
|
|
||||||
|
|
||||||
const MOCK_SCENARIO_BASE = {
|
const MOCK_SCENARIO_BASE = {
|
||||||
title: 'Test',
|
title: 'Test',
|
||||||
@@ -84,15 +83,15 @@ const systemEnv: TurnCommandEnv = {
|
|||||||
maxResourceActionAmount: 1000,
|
maxResourceActionAmount: 1000,
|
||||||
};
|
};
|
||||||
|
|
||||||
function createConstraintContext(actor: General, year: number = 200, args: any = {}): ConstraintContext {
|
function createConstraintContext(actorId: number, cityId: number, nationId: number, args: any = {}): ConstraintContext {
|
||||||
return {
|
return {
|
||||||
actorId: actor.id,
|
actorId,
|
||||||
cityId: actor.cityId,
|
cityId,
|
||||||
nationId: actor.nationId || 0,
|
nationId,
|
||||||
args,
|
args,
|
||||||
env: {
|
env: {
|
||||||
...systemEnv,
|
...systemEnv,
|
||||||
world: { currentYear: year },
|
world: { currentYear: 200 },
|
||||||
openingPartYear: systemEnv.openingPartYear,
|
openingPartYear: systemEnv.openingPartYear,
|
||||||
map: MINIMAL_MAP,
|
map: MINIMAL_MAP,
|
||||||
cities: MINIMAL_MAP.cities,
|
cities: MINIMAL_MAP.cities,
|
||||||
@@ -186,7 +185,12 @@ describe('che_등용수락', () => {
|
|||||||
experience: 0,
|
experience: 0,
|
||||||
dedication: 0,
|
dedication: 0,
|
||||||
officerLevel: 1,
|
officerLevel: 1,
|
||||||
role: null as any,
|
role: {
|
||||||
|
personality: null,
|
||||||
|
specialDomestic: null,
|
||||||
|
specialWar: null,
|
||||||
|
items: { horse: null, weapon: null, book: null, item: null },
|
||||||
|
},
|
||||||
injury: 0,
|
injury: 0,
|
||||||
gold: 1000,
|
gold: 1000,
|
||||||
rice: 1000,
|
rice: 1000,
|
||||||
@@ -218,18 +222,17 @@ describe('che_등용수락', () => {
|
|||||||
world.snapshot.generals.push(recruiterGen);
|
world.snapshot.generals.push(recruiterGen);
|
||||||
world.snapshot.nations.push(nation2);
|
world.snapshot.nations.push(nation2);
|
||||||
|
|
||||||
|
const { commandSpec } = await import('../../../src/actions/turn/general/che_등용수락.js');
|
||||||
|
|
||||||
await runner.runTurn([
|
await runner.runTurn([
|
||||||
{
|
{
|
||||||
generalId: neutralGen.id,
|
generalId: neutralGen.id,
|
||||||
commandKey: 'che_등용수락',
|
commandKey: 'che_등용수락',
|
||||||
resolver: (
|
resolver: commandSpec.createDefinition(systemEnv),
|
||||||
await import('../../../src/actions/turn/general/che_등용수락.js')
|
|
||||||
).commandSpec.createDefinition({} as any),
|
|
||||||
args: { destNationId: 2, destGeneralId: 2 },
|
args: { destNationId: 2, destGeneralId: 2 },
|
||||||
context: {
|
context: {
|
||||||
destNation: nation2,
|
destNation: nation2,
|
||||||
destGeneral: recruiterGen,
|
destGeneral: recruiterGen,
|
||||||
env: systemEnv,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
@@ -238,7 +241,7 @@ describe('che_등용수락', () => {
|
|||||||
const updatedRecruiter = world.getGeneral(recruiterGen.id);
|
const updatedRecruiter = world.getGeneral(recruiterGen.id);
|
||||||
|
|
||||||
expect(updatedSelf?.nationId).toBe(2);
|
expect(updatedSelf?.nationId).toBe(2);
|
||||||
expect(updatedSelf?.cityId).toBe(102);
|
expect(updatedSelf?.cityId).toBe(102); // Capital of Nation2
|
||||||
expect(updatedSelf?.experience).toBe(100);
|
expect(updatedSelf?.experience).toBe(100);
|
||||||
expect(updatedSelf?.dedication).toBe(100);
|
expect(updatedSelf?.dedication).toBe(100);
|
||||||
|
|
||||||
@@ -265,7 +268,12 @@ describe('che_등용수락', () => {
|
|||||||
experience: 1000,
|
experience: 1000,
|
||||||
dedication: 1000,
|
dedication: 1000,
|
||||||
officerLevel: 1,
|
officerLevel: 1,
|
||||||
role: null as any,
|
role: {
|
||||||
|
personality: null,
|
||||||
|
specialDomestic: null,
|
||||||
|
specialWar: null,
|
||||||
|
items: { horse: null, weapon: null, book: null, item: null },
|
||||||
|
},
|
||||||
injury: 0,
|
injury: 0,
|
||||||
gold: 2000,
|
gold: 2000,
|
||||||
rice: 2000,
|
rice: 2000,
|
||||||
@@ -302,7 +310,12 @@ describe('che_등용수락', () => {
|
|||||||
experience: 0,
|
experience: 0,
|
||||||
dedication: 0,
|
dedication: 0,
|
||||||
officerLevel: 1,
|
officerLevel: 1,
|
||||||
role: null as any,
|
role: {
|
||||||
|
personality: null,
|
||||||
|
specialDomestic: null,
|
||||||
|
specialWar: null,
|
||||||
|
items: { horse: null, weapon: null, book: null, item: null },
|
||||||
|
},
|
||||||
injury: 0,
|
injury: 0,
|
||||||
gold: 1000,
|
gold: 1000,
|
||||||
rice: 1000,
|
rice: 1000,
|
||||||
@@ -334,18 +347,17 @@ describe('che_등용수락', () => {
|
|||||||
world.snapshot.nations.push(nation1);
|
world.snapshot.nations.push(nation1);
|
||||||
world.snapshot.nations.push(nation2);
|
world.snapshot.nations.push(nation2);
|
||||||
|
|
||||||
|
const { commandSpec } = await import('../../../src/actions/turn/general/che_등용수락.js');
|
||||||
|
|
||||||
await runner.runTurn([
|
await runner.runTurn([
|
||||||
{
|
{
|
||||||
generalId: betrayer.id,
|
generalId: betrayer.id,
|
||||||
commandKey: 'che_등용수락',
|
commandKey: 'che_등용수락',
|
||||||
resolver: (
|
resolver: commandSpec.createDefinition(systemEnv),
|
||||||
await import('../../../src/actions/turn/general/che_등용수락.js')
|
|
||||||
).commandSpec.createDefinition({} as any),
|
|
||||||
args: { destNationId: 2, destGeneralId: 2 },
|
args: { destNationId: 2, destGeneralId: 2 },
|
||||||
context: {
|
context: {
|
||||||
destNation: nation2,
|
destNation: nation2,
|
||||||
destGeneral: recruiterGen,
|
destGeneral: recruiterGen,
|
||||||
env: systemEnv,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
@@ -453,22 +465,16 @@ describe('che_등용수락', () => {
|
|||||||
world.snapshot.nations.push(nation1);
|
world.snapshot.nations.push(nation1);
|
||||||
world.snapshot.nations.push(nation2);
|
world.snapshot.nations.push(nation2);
|
||||||
|
|
||||||
// Manually check constraints for denial
|
const { commandSpec } = await import('../../../src/actions/turn/general/che_등용수락.js');
|
||||||
const def = (await import('../../../src/actions/turn/general/che_등용수락.js')).commandSpec.createDefinition(
|
const def = commandSpec.createDefinition(systemEnv);
|
||||||
{} as any
|
|
||||||
);
|
|
||||||
const args = { destNationId: 2, destGeneralId: 2 };
|
const args = { destNationId: 2, destGeneralId: 2 };
|
||||||
|
|
||||||
const ctx = createConstraintContext(monarch, 200, args);
|
const ctx = createConstraintContext(monarch.id, monarch.cityId, monarch.nationId!, args);
|
||||||
const view = createViewState(world, 200);
|
const view = createViewState(world, 200);
|
||||||
|
|
||||||
const result = evaluateActionConstraints(def, ctx, view, args);
|
const result = evaluateActionConstraints(def, ctx, view, args);
|
||||||
expect(result.kind).toBe('deny');
|
expect(result.kind).toBe('deny');
|
||||||
if (result.kind === 'deny') {
|
if (result.kind === 'deny') {
|
||||||
const reason = result.constraintName || result.reason;
|
|
||||||
// notLord constraint failure.
|
|
||||||
// In TS presets, notLord(monarch) returns deny.
|
|
||||||
// ConstraintName should be 'notLord' or 'NotLord'.
|
|
||||||
expect(result.constraintName).toMatch(/NotLord/i);
|
expect(result.constraintName).toMatch(/NotLord/i);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -490,7 +496,12 @@ describe('che_등용수락', () => {
|
|||||||
experience: 0,
|
experience: 0,
|
||||||
dedication: 0,
|
dedication: 0,
|
||||||
officerLevel: 1,
|
officerLevel: 1,
|
||||||
role: null as any,
|
role: {
|
||||||
|
personality: null,
|
||||||
|
specialDomestic: null,
|
||||||
|
specialWar: null,
|
||||||
|
items: { horse: null, weapon: null, book: null, item: null },
|
||||||
|
},
|
||||||
injury: 0,
|
injury: 0,
|
||||||
gold: 1000,
|
gold: 1000,
|
||||||
rice: 1000,
|
rice: 1000,
|
||||||
@@ -516,8 +527,6 @@ describe('che_등용수락', () => {
|
|||||||
typeCode: 'che_def',
|
typeCode: 'che_def',
|
||||||
meta: {},
|
meta: {},
|
||||||
};
|
};
|
||||||
// Recruiter also in same nation? Or different?
|
|
||||||
// Arg destNationId is key.
|
|
||||||
const recruiterGen: General = {
|
const recruiterGen: General = {
|
||||||
id: 2,
|
id: 2,
|
||||||
name: 'Recruiter',
|
name: 'Recruiter',
|
||||||
@@ -546,12 +555,11 @@ describe('che_등용수락', () => {
|
|||||||
world.snapshot.generals.push(recruiterGen);
|
world.snapshot.generals.push(recruiterGen);
|
||||||
world.snapshot.nations.push(nation1);
|
world.snapshot.nations.push(nation1);
|
||||||
|
|
||||||
const def = (await import('../../../src/actions/turn/general/che_등용수락.js')).commandSpec.createDefinition(
|
const { commandSpec } = await import('../../../src/actions/turn/general/che_등용수락.js');
|
||||||
{} as any
|
const def = commandSpec.createDefinition(systemEnv);
|
||||||
);
|
const args = { destNationId: 1, destGeneralId: 2 };
|
||||||
const args = { destNationId: 1, destGeneralId: 2 }; // Target same nation 1
|
|
||||||
|
|
||||||
const ctx = createConstraintContext(general, 200, args);
|
const ctx = createConstraintContext(general.id, general.cityId, general.nationId!, args);
|
||||||
const view = createViewState(world, 200);
|
const view = createViewState(world, 200);
|
||||||
|
|
||||||
const result = evaluateActionConstraints(def, ctx, view, args);
|
const result = evaluateActionConstraints(def, ctx, view, args);
|
||||||
|
|||||||
Reference in New Issue
Block a user