feat: 새로운 제약 조건 추가 및 기존 제약 조건 개선
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import type { City, GeneralTriggerState, Nation } from '@sammo-ts/logic/domain/entities.js';
|
||||
import type { Constraint, ConstraintContext, StateView } from '@sammo-ts/logic/constraints/types.js';
|
||||
import { beChief, occupiedCity, suppliedCity } from '@sammo-ts/logic/constraints/presets.js';
|
||||
import { beChief, notBeNeutral, occupiedCity, suppliedCity } from '@sammo-ts/logic/constraints/presets.js';
|
||||
import type { GeneralActionDefinition } from '@sammo-ts/logic/actions/definition.js';
|
||||
import type {
|
||||
GeneralActionEffect,
|
||||
@@ -13,6 +13,7 @@ import { JosaUtil } from '@sammo-ts/common';
|
||||
import type { TurnCommandEnv } from '@sammo-ts/logic/actions/turn/commandEnv.js';
|
||||
import type { NationTurnCommandSpec } from './index.js';
|
||||
import type { ActionContextBuilder } from '@sammo-ts/logic/actions/turn/actionContext.js';
|
||||
import type { MapDefinition } from '@sammo-ts/logic/world/types.js';
|
||||
|
||||
export interface ReduceCityArgs {}
|
||||
|
||||
@@ -31,6 +32,64 @@ const DEFAULT_COST = 60000;
|
||||
const COST_COEF = 500;
|
||||
const MIN_POP = 30000;
|
||||
|
||||
const requireCapitalCity = (reason: string): Constraint => ({
|
||||
name: 'requireCapitalCity',
|
||||
requires: (ctx) => (ctx.nationId !== undefined ? [{ kind: 'nation', id: ctx.nationId }] : []),
|
||||
test: (ctx: ConstraintContext, view: StateView) => {
|
||||
if (ctx.nationId === undefined) {
|
||||
return { kind: 'deny', reason };
|
||||
}
|
||||
const nation = view.get({ kind: 'nation', id: ctx.nationId }) as Nation | undefined;
|
||||
if (!nation || !nation.capitalCityId) {
|
||||
return { kind: 'deny', reason };
|
||||
}
|
||||
return { kind: 'allow' };
|
||||
},
|
||||
});
|
||||
|
||||
const reqDestCityValue = (
|
||||
comp: '>' | '<' | '>=' | '<=',
|
||||
required: number | 'origin',
|
||||
reason: string
|
||||
): Constraint => ({
|
||||
name: 'reqDestCityValue',
|
||||
requires: (ctx) =>
|
||||
ctx.nationId !== undefined ? [{ kind: 'nation', id: ctx.nationId }, { kind: 'env', key: 'map' }] : [],
|
||||
test: (ctx: ConstraintContext, view: StateView) => {
|
||||
if (ctx.nationId === undefined) {
|
||||
return { kind: 'deny', reason };
|
||||
}
|
||||
const nation = view.get({ kind: 'nation', id: ctx.nationId }) as Nation | undefined;
|
||||
if (!nation || !nation.capitalCityId) {
|
||||
return { kind: 'deny', reason: '방랑상태에서는 불가능합니다.' };
|
||||
}
|
||||
const city = view.get({ kind: 'city', id: nation.capitalCityId }) as City | undefined;
|
||||
if (!city) {
|
||||
return { kind: 'deny', reason: '수도 정보를 찾을 수 없습니다.' };
|
||||
}
|
||||
const compareTarget =
|
||||
required === 'origin'
|
||||
? ((view.get({ kind: 'env', key: 'map' }) as MapDefinition | undefined)?.cities.find(
|
||||
(mapCity) => mapCity.id === nation.capitalCityId
|
||||
)?.level ??
|
||||
0)
|
||||
: required;
|
||||
const level = city.level;
|
||||
const allow =
|
||||
comp === '>'
|
||||
? level > compareTarget
|
||||
: comp === '<'
|
||||
? level < compareTarget
|
||||
: comp === '>='
|
||||
? level >= compareTarget
|
||||
: level <= compareTarget;
|
||||
if (allow) {
|
||||
return { kind: 'allow' };
|
||||
}
|
||||
return { kind: 'deny', reason };
|
||||
},
|
||||
});
|
||||
|
||||
export class ActionDefinition<
|
||||
TriggerState extends GeneralTriggerState = GeneralTriggerState,
|
||||
> implements GeneralActionDefinition<TriggerState, ReduceCityArgs, ReduceCityResolveContext<TriggerState>> {
|
||||
@@ -44,7 +103,7 @@ export class ActionDefinition<
|
||||
}
|
||||
|
||||
buildMinConstraints(_ctx: ConstraintContext, _args: ReduceCityArgs): Constraint[] {
|
||||
return [occupiedCity(), beChief(), suppliedCity()];
|
||||
return [notBeNeutral()];
|
||||
}
|
||||
|
||||
private getRecoverAmount(): number {
|
||||
@@ -53,28 +112,13 @@ export class ActionDefinition<
|
||||
|
||||
buildConstraints(_ctx: ConstraintContext, _args: ReduceCityArgs): Constraint[] {
|
||||
return [
|
||||
notBeNeutral(),
|
||||
occupiedCity(),
|
||||
beChief(),
|
||||
suppliedCity(),
|
||||
{
|
||||
name: 'reducibleCity',
|
||||
requires: (ctx) => [
|
||||
{ kind: 'nation', id: ctx.nationId! },
|
||||
{ kind: 'city', id: 0 },
|
||||
],
|
||||
test: (ctx: ConstraintContext, view: StateView) => {
|
||||
const nation = view.get({ kind: 'nation', id: ctx.nationId! }) as Nation | undefined;
|
||||
const capitalCityId = nation?.capitalCityId;
|
||||
|
||||
if (!capitalCityId) return { kind: 'deny', reason: '방랑상태에서는 불가능합니다.' };
|
||||
|
||||
const capitalCity = view.get({ kind: 'city', id: capitalCityId }) as City | undefined;
|
||||
if (!capitalCity) return { kind: 'deny', reason: '수도 정보를 찾을 수 없습니다.' };
|
||||
if (capitalCity.level <= 4) return { kind: 'deny', reason: '더이상 감축할 수 없습니다.' };
|
||||
|
||||
return { kind: 'allow' };
|
||||
},
|
||||
},
|
||||
requireCapitalCity('방랑상태에서는 불가능합니다.'),
|
||||
reqDestCityValue('>', 4, '더이상 감축할 수 없습니다.'),
|
||||
reqDestCityValue('>', 'origin', '더이상 감축할 수 없습니다.'),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,10 @@ import {
|
||||
suppliedCity,
|
||||
differentDestNation,
|
||||
existsDestNation,
|
||||
reqDestNationValue,
|
||||
reqNationGold,
|
||||
reqNationRice,
|
||||
reqNationValue,
|
||||
} from '@sammo-ts/logic/constraints/presets.js';
|
||||
import type { GeneralActionDefinition } from '@sammo-ts/logic/actions/definition.js';
|
||||
import type {
|
||||
@@ -39,6 +43,25 @@ const ACTION_NAME = '원조';
|
||||
const COEF_AID_AMOUNT = 10000;
|
||||
const POST_REQ_TURN = 12;
|
||||
|
||||
const reqAidWithinLimit = (goldAmount: number, riceAmount: number): Constraint => ({
|
||||
name: 'reqAidWithinLimit',
|
||||
requires: (ctx) => (ctx.nationId !== undefined ? [{ kind: 'nation', id: ctx.nationId }] : []),
|
||||
test: (ctx: ConstraintContext, view: StateView) => {
|
||||
if (ctx.nationId === undefined) {
|
||||
return { kind: 'deny', reason: '국가 정보가 없습니다.' };
|
||||
}
|
||||
const nation = view.get({ kind: 'nation', id: ctx.nationId }) as Nation | undefined;
|
||||
if (!nation) {
|
||||
return { kind: 'deny', reason: '국가 정보가 없습니다.' };
|
||||
}
|
||||
const limit = nation.level * COEF_AID_AMOUNT;
|
||||
if (goldAmount > limit || riceAmount > limit) {
|
||||
return { kind: 'deny', reason: '작위 제한량 이상은 보낼 수 없습니다.' };
|
||||
}
|
||||
return { kind: 'allow' };
|
||||
},
|
||||
});
|
||||
|
||||
export class ActionDefinition<
|
||||
TriggerState extends GeneralTriggerState = GeneralTriggerState,
|
||||
> implements GeneralActionDefinition<TriggerState, MaterialAidArgs, MaterialAidResolveContext<TriggerState>> {
|
||||
@@ -52,22 +75,7 @@ export class ActionDefinition<
|
||||
}
|
||||
|
||||
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' };
|
||||
},
|
||||
},
|
||||
];
|
||||
return [occupiedCity(), beChief(), suppliedCity(), reqNationValue('surlimit', '외교제한', '==', 0, '외교제한중입니다.')];
|
||||
}
|
||||
|
||||
buildConstraints(_ctx: ConstraintContext, args: MaterialAidArgs): Constraint[] {
|
||||
@@ -78,40 +86,11 @@ export class ActionDefinition<
|
||||
suppliedCity(),
|
||||
existsDestNation(),
|
||||
differentDestNation(),
|
||||
{
|
||||
name: 'aidLimit',
|
||||
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 limit = (nation?.level ?? 1) * COEF_AID_AMOUNT;
|
||||
if (goldAmount > limit || riceAmount > limit) {
|
||||
return { kind: 'deny', reason: '작위 제한량 이상은 보낼 수 없습니다.' };
|
||||
}
|
||||
return { kind: 'allow' };
|
||||
},
|
||||
},
|
||||
{
|
||||
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' };
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'destNationSurlimit',
|
||||
requires: () => [{ kind: 'nation', id: args.destNationId }],
|
||||
test: (_ctx: ConstraintContext, view: StateView) => {
|
||||
const destNation = view.get({ kind: 'nation', id: args.destNationId }) as Nation | undefined;
|
||||
const surlimitRaw = destNation?.meta.surlimit;
|
||||
const surlimit = typeof surlimitRaw === 'number' ? surlimitRaw : 0;
|
||||
if (surlimit > 0) return { kind: 'deny', reason: '상대국이 외교제한중입니다.' };
|
||||
return { kind: 'allow' };
|
||||
},
|
||||
},
|
||||
reqAidWithinLimit(goldAmount, riceAmount),
|
||||
reqNationGold(() => this.env.baseGold + (goldAmount > 0 ? 1 : 0)),
|
||||
reqNationRice(() => this.env.baseRice + (riceAmount > 0 ? 1 : 0)),
|
||||
reqNationValue('surlimit', '외교제한', '==', 0, '외교제한중입니다.'),
|
||||
reqDestNationValue('surlimit', '외교제한', '==', 0, '상대국이 외교제한중입니다.'),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import type { City, GeneralTriggerState, Nation } from '@sammo-ts/logic/domain/e
|
||||
import type { Constraint, ConstraintContext, StateView } from '@sammo-ts/logic/constraints/types.js';
|
||||
import {
|
||||
beChief,
|
||||
notBeNeutral,
|
||||
occupiedCity,
|
||||
suppliedCity,
|
||||
reqNationGold,
|
||||
@@ -36,6 +37,52 @@ const WALL_INCREASE = 2000;
|
||||
const DEFAULT_COST = 60000;
|
||||
const COST_COEF = 500;
|
||||
|
||||
const requireCapitalCity = (reason: string): Constraint => ({
|
||||
name: 'requireCapitalCity',
|
||||
requires: (ctx) => (ctx.nationId !== undefined ? [{ kind: 'nation', id: ctx.nationId }] : []),
|
||||
test: (ctx: ConstraintContext, view: StateView) => {
|
||||
if (ctx.nationId === undefined) {
|
||||
return { kind: 'deny', reason };
|
||||
}
|
||||
const nation = view.get({ kind: 'nation', id: ctx.nationId }) as Nation | undefined;
|
||||
if (!nation || !nation.capitalCityId) {
|
||||
return { kind: 'deny', reason };
|
||||
}
|
||||
return { kind: 'allow' };
|
||||
},
|
||||
});
|
||||
|
||||
const reqDestCityValue = (comp: '>' | '<' | '>=' | '<=', required: number, reason: string): Constraint => ({
|
||||
name: 'reqDestCityValue',
|
||||
requires: (ctx) => (ctx.nationId !== undefined ? [{ kind: 'nation', id: ctx.nationId }] : []),
|
||||
test: (ctx: ConstraintContext, view: StateView) => {
|
||||
if (ctx.nationId === undefined) {
|
||||
return { kind: 'deny', reason };
|
||||
}
|
||||
const nation = view.get({ kind: 'nation', id: ctx.nationId }) as Nation | undefined;
|
||||
if (!nation || !nation.capitalCityId) {
|
||||
return { kind: 'deny', reason: '방랑상태에서는 불가능합니다.' };
|
||||
}
|
||||
const city = view.get({ kind: 'city', id: nation.capitalCityId }) as City | undefined;
|
||||
if (!city) {
|
||||
return { kind: 'deny', reason: '수도 정보를 찾을 수 없습니다.' };
|
||||
}
|
||||
const level = city.level;
|
||||
const allow =
|
||||
comp === '>'
|
||||
? level > required
|
||||
: comp === '<'
|
||||
? level < required
|
||||
: comp === '>='
|
||||
? level >= required
|
||||
: level <= required;
|
||||
if (allow) {
|
||||
return { kind: 'allow' };
|
||||
}
|
||||
return { kind: 'deny', reason };
|
||||
},
|
||||
});
|
||||
|
||||
export class ActionDefinition<
|
||||
TriggerState extends GeneralTriggerState = GeneralTriggerState,
|
||||
> implements GeneralActionDefinition<TriggerState, ExpandCityArgs, ExpandCityResolveContext<TriggerState>> {
|
||||
@@ -49,7 +96,7 @@ export class ActionDefinition<
|
||||
}
|
||||
|
||||
buildMinConstraints(_ctx: ConstraintContext, _args: ExpandCityArgs): Constraint[] {
|
||||
return [occupiedCity(), beChief(), suppliedCity()];
|
||||
return [notBeNeutral()];
|
||||
}
|
||||
|
||||
private getCost(): number {
|
||||
@@ -59,31 +106,15 @@ export class ActionDefinition<
|
||||
buildConstraints(_ctx: ConstraintContext, _args: ExpandCityArgs): Constraint[] {
|
||||
const cost = this.getCost();
|
||||
return [
|
||||
notBeNeutral(),
|
||||
occupiedCity(),
|
||||
beChief(),
|
||||
suppliedCity(),
|
||||
requireCapitalCity('방랑상태에서는 불가능합니다.'),
|
||||
reqDestCityValue('>', 3, '수진, 진, 관문에서는 불가능합니다.'),
|
||||
reqDestCityValue('<', 8, '더이상 증축할 수 없습니다.'),
|
||||
reqNationGold(() => this.env.baseGold + cost),
|
||||
reqNationRice(() => this.env.baseRice + cost),
|
||||
{
|
||||
name: 'expandableCity',
|
||||
requires: (ctx) => [
|
||||
{ kind: 'nation', id: ctx.nationId! },
|
||||
{ kind: 'city', id: 0 },
|
||||
],
|
||||
test: (ctx: ConstraintContext, view: StateView) => {
|
||||
const nation = view.get({ kind: 'nation', id: ctx.nationId! }) as Nation | undefined;
|
||||
const capitalCityId = nation?.capitalCityId;
|
||||
|
||||
if (!capitalCityId) return { kind: 'deny', reason: '방랑상태에서는 불가능합니다.' };
|
||||
|
||||
const capitalCity = view.get({ kind: 'city', id: capitalCityId }) as City | undefined;
|
||||
if (!capitalCity) return { kind: 'deny', reason: '수도 정보를 찾을 수 없습니다.' };
|
||||
if (capitalCity.level <= 3) return { kind: 'deny', reason: '수진, 진, 관문에서는 불가능합니다.' };
|
||||
if (capitalCity.level >= 8) return { kind: 'deny', reason: '더이상 증축할 수 없습니다.' };
|
||||
|
||||
return { kind: 'allow' };
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import type { City, GeneralTriggerState, Nation, TriggerValue } from '@sammo-ts/logic/domain/entities.js';
|
||||
import type { Constraint, ConstraintContext, StateView } from '@sammo-ts/logic/constraints/types.js';
|
||||
import type { Constraint, ConstraintContext } from '@sammo-ts/logic/constraints/types.js';
|
||||
import {
|
||||
beChief,
|
||||
disallowDiplomacyBetweenStatus,
|
||||
disallowDiplomacyStatus,
|
||||
occupiedCity,
|
||||
occupiedDestCity,
|
||||
reqNationValue,
|
||||
suppliedCity,
|
||||
suppliedDestCity,
|
||||
} from '@sammo-ts/logic/constraints/presets.js';
|
||||
import { allow, unknownOrDeny } from '@sammo-ts/logic/constraints/helpers.js';
|
||||
import type { GeneralActionDefinition } from '@sammo-ts/logic/actions/definition.js';
|
||||
import type {
|
||||
GeneralActionEffect,
|
||||
@@ -43,45 +43,6 @@ const ACTION_NAME = '초토화';
|
||||
const PRE_REQ_TURN = 2;
|
||||
const POST_REQ_TURN = 24;
|
||||
|
||||
const requireNoDiplomacyLimit = (): Constraint => ({
|
||||
name: 'requireNoDiplomacyLimit',
|
||||
requires: (ctx) => (ctx.nationId !== undefined ? [{ kind: 'nation', id: ctx.nationId }] : []),
|
||||
test: (ctx: ConstraintContext, view: StateView) => {
|
||||
const nationId = ctx.nationId;
|
||||
if (nationId === undefined) {
|
||||
return unknownOrDeny(ctx, [], '국가 정보가 없습니다.');
|
||||
}
|
||||
const nation = view.get({ kind: 'nation', id: nationId }) as Nation | null;
|
||||
if (!nation) {
|
||||
return unknownOrDeny(ctx, [{ kind: 'nation', id: nationId }], '국가 정보가 없습니다.');
|
||||
}
|
||||
const surlimit = typeof nation.meta?.surlimit === 'number' ? Number(nation.meta.surlimit) : 0;
|
||||
if (surlimit > 0) {
|
||||
return { kind: 'deny', reason: '외교제한 턴이 남아있습니다.' };
|
||||
}
|
||||
return allow();
|
||||
},
|
||||
});
|
||||
|
||||
const notCapitalCity = (destCityId: number): Constraint => ({
|
||||
name: 'notCapitalCity',
|
||||
requires: (ctx) => (ctx.nationId !== undefined ? [{ kind: 'nation', id: ctx.nationId }] : []),
|
||||
test: (ctx: ConstraintContext, view: StateView) => {
|
||||
const nationId = ctx.nationId;
|
||||
if (nationId === undefined) {
|
||||
return unknownOrDeny(ctx, [], '국가 정보가 없습니다.');
|
||||
}
|
||||
const nation = view.get({ kind: 'nation', id: nationId }) as Nation | null;
|
||||
if (!nation) {
|
||||
return unknownOrDeny(ctx, [{ kind: 'nation', id: nationId }], '국가 정보가 없습니다.');
|
||||
}
|
||||
if (nation.capitalCityId === destCityId) {
|
||||
return { kind: 'deny', reason: '수도입니다.' };
|
||||
}
|
||||
return allow();
|
||||
},
|
||||
});
|
||||
|
||||
const calcReturnAmount = (destCity: City): number => {
|
||||
let amount = destCity.population / 5;
|
||||
const resourcePairs: Array<[number, number]> = [
|
||||
@@ -117,7 +78,12 @@ export class ActionDefinition<
|
||||
}
|
||||
|
||||
buildMinConstraints(_ctx: ConstraintContext, _args: ScorchedEarthArgs): Constraint[] {
|
||||
return [occupiedCity(), beChief(), suppliedCity(), requireNoDiplomacyLimit()];
|
||||
return [
|
||||
occupiedCity(),
|
||||
beChief(),
|
||||
suppliedCity(),
|
||||
reqNationValue('surlimit', '제한 턴', '==', 0, '외교제한 턴이 남아있습니다.'),
|
||||
];
|
||||
}
|
||||
|
||||
buildConstraints(_ctx: ConstraintContext, args: ScorchedEarthArgs): Constraint[] {
|
||||
@@ -128,9 +94,9 @@ export class ActionDefinition<
|
||||
beChief(),
|
||||
suppliedCity(),
|
||||
suppliedDestCity(),
|
||||
notCapitalCity(args.destCityId),
|
||||
requireNoDiplomacyLimit(),
|
||||
disallowDiplomacyBetweenStatus({
|
||||
reqNationValue('capitalCityId', '수도', '!=', args.destCityId, '수도입니다.'),
|
||||
reqNationValue('surlimit', '제한 턴', '==', 0, '외교제한 턴이 남아있습니다.'),
|
||||
disallowDiplomacyStatus({
|
||||
0: '평시에만 가능합니다.',
|
||||
}),
|
||||
];
|
||||
|
||||
@@ -92,6 +92,11 @@ export const disallowDiplomacyBetweenStatus = (disallowList: Record<number, stri
|
||||
},
|
||||
});
|
||||
|
||||
export const disallowDiplomacyStatus = (disallowList: Record<number, string>): Constraint => ({
|
||||
...disallowDiplomacyBetweenStatus(disallowList),
|
||||
name: 'disallowDiplomacyStatus',
|
||||
});
|
||||
|
||||
export const allowDiplomacyBetweenStatus = (allowList: number[], reason: string): Constraint => ({
|
||||
name: 'allowDiplomacyBetweenStatus',
|
||||
requires: (ctx) => {
|
||||
|
||||
@@ -310,6 +310,8 @@ const ALWAYS_FAIL_TS_ALIASES = new Set([
|
||||
'reqfuturetreatyterm',
|
||||
'reqvalidstrategiccommandtype',
|
||||
'hasroutetodestcity',
|
||||
'requirecapitalcity',
|
||||
'reqaidwithinlimit',
|
||||
]);
|
||||
|
||||
const canonicalizeConstraintName = (side, normalizedName) => {
|
||||
|
||||
Reference in New Issue
Block a user