buildMinConstraints 구현

This commit is contained in:
2026-01-15 17:29:26 +00:00
parent 4a0b18954d
commit 7a289c22d9
37 changed files with 366 additions and 9 deletions
+7 -1
View File
@@ -184,6 +184,8 @@ const buildCommandEnv = (worldState: WorldStateRow): CommandEnv => {
};
const buildConstraintEnv = (worldState: WorldStateRow): Record<string, unknown> => {
const config = asRecord(worldState.config);
const constValues = asRecord(config.const);
const meta = asRecord(worldState.meta);
const scenarioMeta = asRecord(meta.scenarioMeta);
const startYear = typeof scenarioMeta.startYear === 'number' ? scenarioMeta.startYear : undefined;
@@ -196,6 +198,7 @@ const buildConstraintEnv = (worldState: WorldStateRow): Record<string, unknown>
month: worldState.currentMonth,
startYear,
relYear,
openingPartYear: resolveNumber(constValues, ['openingPartYear'], 0),
};
};
@@ -352,7 +355,10 @@ const evaluateDefinition = (
reqArg: boolean,
args: unknown
): AvailabilityCore => {
const constraints = definition.buildConstraints(ctx, args);
const constraints =
ctx.mode === 'precheck' && definition.buildMinConstraints
? definition.buildMinConstraints(ctx, args)
: definition.buildConstraints(ctx, args);
return evaluateAvailability(constraints, ctx, view, reqArg);
};
+118
View File
@@ -0,0 +1,118 @@
import { describe, expect, it } from 'vitest';
import type { CityRow, GeneralRow, NationRow, WorldStateRow } from '../src/context.js';
import { buildTurnCommandTable } from '../src/turns/commandTable.js';
const buildWorldState = (): WorldStateRow =>
({
id: 1,
scenarioCode: 'default',
currentYear: 3,
currentMonth: 1,
tickSeconds: 600,
config: {
const: {
baseGold: 1000,
baseRice: 1000,
develCost: 100,
},
},
meta: {
scenarioMeta: {
startYear: 1,
},
},
updatedAt: new Date('2026-01-01T00:00:00Z'),
}) as unknown as WorldStateRow;
const buildGeneral = (): GeneralRow =>
({
id: 1,
name: 'TestGeneral',
nationId: 1,
cityId: 1,
troopId: 0,
leadership: 70,
strength: 60,
intel: 60,
experience: 0,
dedication: 0,
officerLevel: 6,
personalCode: null,
specialCode: null,
special2Code: null,
horseCode: null,
weaponCode: null,
bookCode: null,
itemCode: null,
injury: 0,
gold: 0,
rice: 0,
crew: 100,
crewTypeId: 1100,
train: 0,
atmos: 0,
age: 25,
npcState: 0,
meta: {},
}) as unknown as GeneralRow;
const buildCity = (): CityRow =>
({
id: 1,
name: 'TestCity',
nationId: 1,
level: 5,
population: 100000,
populationMax: 200000,
agriculture: 1000,
agricultureMax: 2000,
commerce: 1000,
commerceMax: 2000,
security: 1000,
securityMax: 2000,
supplyState: 1,
frontState: 0,
defence: 0,
defenceMax: 0,
wall: 0,
wallMax: 0,
meta: {},
trust: 50,
trade: 0,
region: 0,
}) as unknown as CityRow;
const buildNation = (): NationRow =>
({
id: 1,
name: 'TestNation',
color: '#000000',
capitalCityId: 1,
gold: 0,
rice: 0,
level: 1,
typeCode: 'default',
tech: 0,
meta: {},
}) as unknown as NationRow;
describe('buildTurnCommandTable', () => {
it('uses min-condition constraints for availability', async () => {
const table = await buildTurnCommandTable({
worldState: buildWorldState(),
general: buildGeneral(),
city: buildCity(),
nation: buildNation(),
nationGenerals: null,
});
const nationCommand = table.nation
.flatMap((group) => group.values)
.find((command) => command.key === 'che_포상');
expect(nationCommand).toBeDefined();
expect(nationCommand?.possible).toBe(true);
expect(nationCommand?.status).not.toBe('blocked');
});
});
+3
View File
@@ -7,9 +7,12 @@ export interface GeneralActionDefinition<
Args = unknown,
Context extends GeneralActionResolveContext<TriggerState> = GeneralActionResolveContext<TriggerState>,
> {
// TODO: legacy permissionConstraints(예약 권한) 모델링 필요.
key: string;
name: string;
parseArgs(raw: unknown): Args | null;
// 커맨드 입력 단계에서 최소 조건만 평가할 때 사용한다.
buildMinConstraints?(ctx: ConstraintContext, args: Args): Constraint[];
buildConstraints(ctx: ConstraintContext, args: Args): Constraint[];
resolve(context: Context, args: Args): GeneralActionOutcome<TriggerState>;
}
@@ -164,6 +164,10 @@ export class ActionDefinition<
return parseArgsWithSchema(ARGS_SCHEMA, raw);
}
buildMinConstraints(_ctx: ConstraintContext, _args: ForcedMoveArgs): Constraint[] {
return [];
}
buildConstraints(ctx: ConstraintContext, _args: ForcedMoveArgs): Constraint[] {
return [
existsDestCity(),
@@ -77,6 +77,10 @@ export class ActionDefinition<
return parseArgsWithSchema(ARGS_SCHEMA, raw);
}
buildMinConstraints(_ctx: ConstraintContext, _args: FoundingArgs): Constraint[] {
return [beOpeningPart(), beWanderingNation()];
}
buildConstraints(_ctx: ConstraintContext, args: FoundingArgs): Constraint[] {
return [
beOpeningPart(),
@@ -1,6 +1,6 @@
import type { GeneralTriggerState } from '@sammo-ts/logic/domain/entities.js';
import type { Constraint, ConstraintContext } from '@sammo-ts/logic/constraints/types.js';
import { occupiedCity, reqGeneralGold, reqGeneralRice } from '@sammo-ts/logic/constraints/presets.js';
import { occupiedCity, reqGeneralGold, reqGeneralRice, suppliedCity } from '@sammo-ts/logic/constraints/presets.js';
import type { GeneralActionDefinition } from '@sammo-ts/logic/actions/definition.js';
import type { GeneralActionOutcome, GeneralActionResolveContext } from '@sammo-ts/logic/actions/engine.js';
import { LogFormat } from '@sammo-ts/logic/logging/types.js';
@@ -37,8 +37,12 @@ export class ActionDefinition<
return parseArgsWithSchema(ARGS_SCHEMA, raw);
}
buildMinConstraints(_ctx: ConstraintContext, _args: TradeArgs): Constraint[] {
return [occupiedCity(), suppliedCity()];
}
buildConstraints(_ctx: ConstraintContext, args: TradeArgs): Constraint[] {
const constraints: Constraint[] = [occupiedCity()];
const constraints: Constraint[] = [occupiedCity(), suppliedCity()];
if (args.buyRice) {
constraints.push(reqGeneralGold(() => 1));
} else {
@@ -150,6 +150,10 @@ export class ActionDefinition<
return parseArgsWithSchema(ARGS_SCHEMA, raw);
}
buildMinConstraints(_ctx: ConstraintContext, _args: EmployArgs): Constraint[] {
return [notBeNeutral(), occupiedCity(), suppliedCity()];
}
buildConstraints(_ctx: ConstraintContext, args: EmployArgs): Constraint[] {
const develCost = this.env.develCost ?? 100;
@@ -1,6 +1,12 @@
import type { GeneralTriggerState } from '@sammo-ts/logic/domain/entities.js';
import type { Constraint, ConstraintContext, StateView } from '@sammo-ts/logic/constraints/types.js';
import { notBeNeutral, reqGeneralGold } from '@sammo-ts/logic/constraints/presets.js';
import {
notBeNeutral,
notWanderingNation,
occupiedCity,
reqGeneralCrew,
reqGeneralGold,
} from '@sammo-ts/logic/constraints/presets.js';
import type { GeneralActionDefinition } from '@sammo-ts/logic/actions/definition.js';
import type { GeneralActionOutcome, GeneralActionResolveContext } from '@sammo-ts/logic/actions/engine.js';
import type { TurnCommandEnv } from '@sammo-ts/logic/actions/turn/commandEnv.js';
@@ -36,9 +42,13 @@ export class ActionDefinition<
return {};
}
buildMinConstraints(_ctx: ConstraintContext, _args: BoostMoraleArgs): Constraint[] {
return [notBeNeutral(), notWanderingNation(), occupiedCity()];
}
buildConstraints(_ctx: ConstraintContext, _args: BoostMoraleArgs): Constraint[] {
const getRequiredGold = (_context: ConstraintContext, _view: StateView): number => this.env.costGold ?? 0;
return [notBeNeutral(), reqGeneralGold(getRequiredGold)];
return [notBeNeutral(), notWanderingNation(), occupiedCity(), reqGeneralCrew(), reqGeneralGold(getRequiredGold)];
}
resolve(
@@ -160,6 +160,15 @@ export class ActionDefinition<
return parseArgsWithSchema(ARGS_SCHEMA, raw);
}
buildMinConstraints(ctx: ConstraintContext, _args: MoveArgs): Constraint[] {
return [
reqGeneralGold(() => {
const develCost = ctx.env.develCost as number;
return develCost ?? 0;
}),
];
}
buildConstraints(ctx: ConstraintContext, _args: MoveArgs): Constraint[] {
const constraints = [
existsDestCity(),
@@ -42,6 +42,10 @@ export class ActionDefinition<
return { destNationId };
}
buildMinConstraints(_ctx: ConstraintContext, _args: AppointmentArgs): Constraint[] {
return [beNeutral()];
}
buildConstraints(_ctx: ConstraintContext, _args: AppointmentArgs): Constraint[] {
return [beNeutral(), existsDestNation()];
}
@@ -419,6 +419,16 @@ export class ActionDefinition<
return parseArgsWithSchema(ARGS_SCHEMA, raw);
}
buildMinConstraints(_ctx: ConstraintContext, _args: RecruitArgs): Constraint[] {
const minPopBase = this.env.minAvailableRecruitPop ?? DEFAULT_MIN_POP;
return [
notBeNeutral(),
occupiedCity(),
reqCityCapacity('population', '주민', minPopBase + MIN_CREW),
reqCityTrust(20),
];
}
buildConstraints(ctx: ConstraintContext, _args: RecruitArgs): Constraint[] {
const requirements: RequirementKey[] = [
{ kind: 'arg', key: 'crewType' },
@@ -131,6 +131,12 @@ export class ActionDefinition<
return parseArgsWithSchema(ARGS_SCHEMA, raw);
}
buildMinConstraints(ctx: ConstraintContext, _args: SpyArgs): Constraint[] {
const env = ctx.env;
const cost = ((env.develCost as number) ?? 100) * 3;
return [notBeNeutral(), reqGeneralGold(() => cost), reqGeneralRice(() => cost)];
}
buildConstraints(ctx: ConstraintContext, _args: SpyArgs): Constraint[] {
const env = ctx.env;
const cost = ((env.develCost as number) ?? 100) * 3;
@@ -260,6 +260,19 @@ export class ActionDefinition<
return { destCityId };
}
buildMinConstraints(ctx: ConstraintContext, _args: DispatchArgs): Constraint[] {
const relYear = typeof ctx.env.relYear === 'number' ? ctx.env.relYear : 0;
const openingPartYear = typeof ctx.env.openingPartYear === 'number' ? ctx.env.openingPartYear : 0;
return [
notOpeningPart(relYear + 2, openingPartYear),
notBeNeutral(),
occupiedCity(),
suppliedCity(),
reqGeneralCrew(),
reqGeneralRice(getRequiredRice),
];
}
buildConstraints(_ctx: ConstraintContext, _args: DispatchArgs): Constraint[] {
const relYear = typeof _ctx.env.relYear === 'number' ? _ctx.env.relYear : 0;
const openingPartYear = typeof _ctx.env.openingPartYear === 'number' ? _ctx.env.openingPartYear : 0;
@@ -3,8 +3,10 @@ import type { Constraint, ConstraintContext } from '@sammo-ts/logic/constraints/
import {
notBeNeutral,
notWanderingNation,
occupiedCity,
reqGeneralGold,
reqGeneralRice,
suppliedCity,
} from '@sammo-ts/logic/constraints/presets.js';
import type { GeneralActionDefinition } from '@sammo-ts/logic/actions/definition.js';
import type {
@@ -96,11 +98,15 @@ export class ActionDefinition<
return parseArgsWithSchema(ARGS_SCHEMA, raw);
}
buildMinConstraints(_ctx: ConstraintContext, _args: DonateArgs): Constraint[] {
return [notBeNeutral(), occupiedCity(), suppliedCity()];
}
buildConstraints(_ctx: ConstraintContext, args: DonateArgs): Constraint[] {
if (args.isGold) {
return [notBeNeutral(), notWanderingNation(), reqGeneralGold(() => args.amount)];
return [notBeNeutral(), notWanderingNation(), occupiedCity(), suppliedCity(), reqGeneralGold(() => args.amount)];
}
return [notBeNeutral(), notWanderingNation(), reqGeneralRice(() => args.amount)];
return [notBeNeutral(), notWanderingNation(), occupiedCity(), suppliedCity(), reqGeneralRice(() => args.amount)];
}
resolve(context: GeneralActionResolveContext<TriggerState>, args: DonateArgs): GeneralActionOutcome<TriggerState> {
@@ -362,6 +362,17 @@ export class ActionDefinition<
return parseArgsWithSchema(ARGS_SCHEMA, raw);
}
buildMinConstraints(_ctx: ConstraintContext, _args: FireAttackArgs): Constraint[] {
const { gold, rice } = this.command.getCost();
return [
notBeNeutral(),
occupiedCity(),
suppliedCity(),
reqGeneralGold(() => gold),
reqGeneralRice(() => rice),
];
}
buildConstraints(_ctx: ConstraintContext, _args: FireAttackArgs): Constraint[] {
void _ctx;
void _args;
@@ -1,6 +1,6 @@
import type { GeneralTriggerState } from '@sammo-ts/logic/domain/entities.js';
import type { Constraint, ConstraintContext, StateView } from '@sammo-ts/logic/constraints/types.js';
import { notBeNeutral, reqGeneralGold } from '@sammo-ts/logic/constraints/presets.js';
import { notBeNeutral, notWanderingNation, occupiedCity, reqGeneralCrew, reqGeneralGold } from '@sammo-ts/logic/constraints/presets.js';
import type { GeneralActionDefinition } from '@sammo-ts/logic/actions/definition.js';
import type { GeneralActionOutcome, GeneralActionResolveContext } from '@sammo-ts/logic/actions/engine.js';
import type { TurnCommandEnv } from '@sammo-ts/logic/actions/turn/commandEnv.js';
@@ -36,9 +36,13 @@ export class ActionDefinition<
return {};
}
buildMinConstraints(_ctx: ConstraintContext, _args: TrainingArgs): Constraint[] {
return [notBeNeutral(), notWanderingNation(), occupiedCity()];
}
buildConstraints(_ctx: ConstraintContext, _args: TrainingArgs): Constraint[] {
const getRequiredGold = (_context: ConstraintContext, _view: StateView): number => this.env.costGold ?? 0;
return [notBeNeutral(), reqGeneralGold(getRequiredGold)];
return [notBeNeutral(), notWanderingNation(), occupiedCity(), reqGeneralCrew(), reqGeneralGold(getRequiredGold)];
}
resolve(
@@ -43,6 +43,10 @@ export class ActionDefinition<
return {};
}
buildMinConstraints(_ctx: ConstraintContext, _args: ReduceCityArgs): Constraint[] {
return [occupiedCity(), beChief(), suppliedCity()];
}
private getRecoverAmount(): number {
return this.env.develCost * COST_COEF + DEFAULT_COST / 2;
}
@@ -32,6 +32,25 @@ export class ActionDefinition<
return parseArgsWithSchema(ARGS_SCHEMA, raw);
}
buildMinConstraints(_ctx: ConstraintContext, _args: ChangeNationNameArgs): Constraint[] {
return [
occupiedCity(),
beChief(),
suppliedCity(),
{
name: 'canChangeNationName',
requires: (ctx) => [{ kind: 'nation', id: ctx.nationId! }],
test: (_ctx: ConstraintContext, view: StateView) => {
const nation = view.get({ kind: 'nation', id: _ctx.nationId! }) as Nation | undefined;
const canChangeRaw = nation?.meta[`can_${ACTION_NAME}`];
const canChange = (typeof canChangeRaw === 'number' ? canChangeRaw : 0) !== 0;
if (!canChange) return { kind: 'deny', reason: '더이상 변경이 불가능합니다.' };
return { kind: 'allow' };
},
},
];
}
buildConstraints(_ctx: ConstraintContext, args: ChangeNationNameArgs): Constraint[] {
return [
occupiedCity(),
@@ -174,6 +174,10 @@ export class ActionDefinition<
return parseArgsWithSchema(ARGS_SCHEMA, raw);
}
buildMinConstraints(_ctx: ConstraintContext, _args: RaidArgs): Constraint[] {
return [occupiedCity(), beChief(), availableStrategicCommand()];
}
buildConstraints(_ctx: ConstraintContext, _args: RaidArgs): Constraint[] {
void _ctx;
void _args;
@@ -4,6 +4,8 @@ import {
beChief,
existsDestGeneral,
friendlyDestGeneral,
notBeNeutral,
notOpeningPart,
occupiedCity,
suppliedCity,
} from '@sammo-ts/logic/constraints/presets.js';
@@ -60,10 +62,20 @@ export class ActionDefinition<
};
}
buildMinConstraints(ctx: ConstraintContext, _args: SeizureArgs): Constraint[] {
const relYear = typeof ctx.env.relYear === 'number' ? ctx.env.relYear : 0;
const openingPartYear = typeof ctx.env.openingPartYear === 'number' ? ctx.env.openingPartYear : 0;
return [notBeNeutral(), occupiedCity(), beChief(), notOpeningPart(relYear, openingPartYear), suppliedCity()];
}
buildConstraints(_ctx: ConstraintContext, args: SeizureArgs): Constraint[] {
const relYear = typeof _ctx.env.relYear === 'number' ? _ctx.env.relYear : 0;
const openingPartYear = typeof _ctx.env.openingPartYear === 'number' ? _ctx.env.openingPartYear : 0;
return [
notBeNeutral(),
occupiedCity(),
beChief(),
notOpeningPart(relYear, openingPartYear),
suppliedCity(),
existsDestGeneral(),
friendlyDestGeneral(),
@@ -51,6 +51,25 @@ export class ActionDefinition<
return parseArgsWithSchema(ARGS_SCHEMA, raw);
}
buildMinConstraints(_ctx: ConstraintContext, _args: MaterialAidArgs): Constraint[] {
return [
occupiedCity(),
beChief(),
suppliedCity(),
{
name: 'nationSurlimit',
requires: (ctx) => [{ kind: 'nation', id: ctx.nationId! }],
test: (_ctx: ConstraintContext, view: StateView) => {
const nation = view.get({ kind: 'nation', id: _ctx.nationId! }) as Nation | undefined;
const surlimitRaw = nation?.meta.surlimit;
const surlimit = typeof surlimitRaw === 'number' ? surlimitRaw : 0;
if (surlimit > 0) return { kind: 'deny', reason: '외교제한중입니다.' };
return { kind: 'allow' };
},
},
];
}
buildConstraints(_ctx: ConstraintContext, args: MaterialAidArgs): Constraint[] {
const [goldAmount, riceAmount] = args.amountList;
return [
@@ -152,6 +152,10 @@ export class ActionDefinition<
return parseArgsWithSchema(ARGS_SCHEMA, raw);
}
buildMinConstraints(_ctx: ConstraintContext, _args: AssignmentArgs): Constraint[] {
return [beChief(), notBeNeutral(), occupiedCity(), suppliedCity()];
}
buildConstraints(ctx: ConstraintContext, _args: AssignmentArgs): Constraint[] {
void _args;
if (ctx.destGeneralId === ctx.actorId) {
@@ -152,6 +152,10 @@ export class ActionDefinition<
return parseArgsWithSchema(ARGS_SCHEMA, raw);
}
buildMinConstraints(_ctx: ConstraintContext, _args: MobilizePeopleArgs): Constraint[] {
return [occupiedCity(), beChief(), availableStrategicCommand()];
}
buildConstraints(_ctx: ConstraintContext, _args: MobilizePeopleArgs): Constraint[] {
void _ctx;
void _args;
@@ -48,6 +48,10 @@ export class ActionDefinition<
return parseArgsWithSchema(ARGS_SCHEMA, raw);
}
buildMinConstraints(_ctx: ConstraintContext, _args: TroopKickArgs): Constraint[] {
return [notBeNeutral(), beChief()];
}
buildConstraints(ctx: ConstraintContext, _args: TroopKickArgs): Constraint[] {
if (ctx.destGeneralId !== undefined && ctx.destGeneralId === ctx.actorId) {
return [alwaysFail('본인입니다')];
@@ -97,6 +97,10 @@ export class ActionDefinition<
return parseArgsWithSchema(ARGS_SCHEMA, raw);
}
buildMinConstraints(_ctx: ConstraintContext, _args: NonAggressionProposalArgs): Constraint[] {
return [beChief(), notBeNeutral()];
}
buildConstraints(_ctx: ConstraintContext, _args: NonAggressionProposalArgs): Constraint[] {
return [
beChief(),
@@ -40,6 +40,10 @@ export class ActionDefinition<
return parseArgsWithSchema(ARGS_SCHEMA, raw);
}
buildMinConstraints(_ctx: ConstraintContext, _args: NonAggressionCancelProposalArgs): Constraint[] {
return [beChief(), notBeNeutral(), occupiedCity(), suppliedCity()];
}
buildConstraints(_ctx: ConstraintContext, _args: NonAggressionCancelProposalArgs): Constraint[] {
return [
beChief(),
@@ -53,6 +53,24 @@ export class ActionDefinition<
return parseArgsWithSchema(ARGS_SCHEMA, raw);
}
buildMinConstraints(_ctx: ConstraintContext, _args: DeclareWarArgs): Constraint[] {
const relYear = typeof _ctx.env.relYear === 'number' ? _ctx.env.relYear : 0;
return [
notBeNeutral(),
occupiedCity(),
suppliedCity(),
beChief(),
{
name: 'declareWarYearLimit',
requires: () => [],
test: () => {
if (relYear >= 1) return { kind: 'allow' };
return { kind: 'deny', reason: '초반제한 해제 2년전부터 가능합니다.' };
},
},
];
}
buildConstraints(_ctx: ConstraintContext, _args: DeclareWarArgs): Constraint[] {
const relYear = typeof _ctx.env.relYear === 'number' ? _ctx.env.relYear : 0;
@@ -177,6 +177,10 @@ export class ActionDefinition<
return parseArgsWithSchema(ARGS_SCHEMA, raw);
}
buildMinConstraints(_ctx: ConstraintContext, _args: FloodArgs): Constraint[] {
return [occupiedCity(), beChief(), availableStrategicCommand()];
}
buildConstraints(_ctx: ConstraintContext, _args: FloodArgs): Constraint[] {
void _ctx;
void _args;
@@ -181,6 +181,10 @@ export class ActionDefinition<
return parseArgsWithSchema(ARGS_SCHEMA, raw);
}
buildMinConstraints(_ctx: ConstraintContext, _args: DegradeRelationsArgs): Constraint[] {
return [occupiedCity(), beChief(), availableStrategicCommand()];
}
buildConstraints(_ctx: ConstraintContext, _args: DegradeRelationsArgs): Constraint[] {
void _ctx;
void _args;
@@ -40,6 +40,10 @@ export class ActionDefinition<
return parseArgsWithSchema(ARGS_SCHEMA, raw);
}
buildMinConstraints(_ctx: ConstraintContext, _args: StopWarProposalArgs): Constraint[] {
return [beChief(), notBeNeutral(), occupiedCity(), suppliedCity()];
}
buildConstraints(_ctx: ConstraintContext, _args: StopWarProposalArgs): Constraint[] {
return [
beChief(),
@@ -48,6 +48,10 @@ export class ActionDefinition<
return {};
}
buildMinConstraints(_ctx: ConstraintContext, _args: ExpandCityArgs): Constraint[] {
return [occupiedCity(), beChief(), suppliedCity()];
}
private getCost(): number {
return this.env.develCost * COST_COEF + DEFAULT_COST;
}
@@ -77,6 +77,10 @@ export class ActionDefinition<
return parseArgsWithSchema(ARGS_SCHEMA, raw);
}
buildMinConstraints(_ctx: ConstraintContext, _args: MoveCapitalArgs): Constraint[] {
return [occupiedCity(), beChief(), suppliedCity()];
}
buildConstraints(_ctx: ConstraintContext, args: MoveCapitalArgs): Constraint[] {
const develcost = this.env.develCost;
@@ -116,6 +116,10 @@ export class ActionDefinition<
return parseArgsWithSchema(ARGS_SCHEMA, raw);
}
buildMinConstraints(_ctx: ConstraintContext, _args: ScorchedEarthArgs): Constraint[] {
return [occupiedCity(), beChief(), suppliedCity(), requireNoDiplomacyLimit()];
}
buildConstraints(_ctx: ConstraintContext, args: ScorchedEarthArgs): Constraint[] {
void _ctx;
return [
@@ -180,6 +180,10 @@ export class ActionDefinition<
};
}
buildMinConstraints(_ctx: ConstraintContext, _args: AwardArgs): Constraint[] {
return [notBeNeutral(), occupiedCity(), beChief(), suppliedCity()];
}
buildConstraints(ctx: ConstraintContext, args: AwardArgs): Constraint[] {
const requirements: RequirementKey[] = [];
if (ctx.cityId !== undefined) {
@@ -211,6 +211,10 @@ export class ActionDefinition<
return parseArgsWithSchema(ARGS_SCHEMA, raw);
}
buildMinConstraints(_ctx: ConstraintContext, _args: CounterStrategyArgs): Constraint[] {
return [occupiedCity(), beChief()];
}
buildConstraints(_ctx: ConstraintContext, _args: CounterStrategyArgs): Constraint[] {
void _ctx;
void _args;
@@ -184,6 +184,10 @@ export class ActionDefinition<
return parseArgsWithSchema(ARGS_SCHEMA, raw);
}
buildMinConstraints(_ctx: ConstraintContext, _args: DeceptionArgs): Constraint[] {
return [occupiedCity(), beChief(), availableStrategicCommand()];
}
buildConstraints(_ctx: ConstraintContext, _args: DeceptionArgs): Constraint[] {
void _ctx;
void _args;
@@ -65,6 +65,15 @@ export class ActionDefinition<
return parseArgsWithSchema(ARGS_SCHEMA, raw);
}
buildMinConstraints(_ctx: ConstraintContext, _args: PopulationMoveArgs): Constraint[] {
return [
occupiedCity(),
beChief(),
suppliedCity(),
reqCityCapacity('population', '주민', MIN_AVAILABLE_RECRUIT_POP + 100),
];
}
buildConstraints(_ctx: ConstraintContext, args: PopulationMoveArgs): Constraint[] {
const cost = calcCost(this.env.develCost, args.amount);
return [