feat: add city, diplomacy, general, nation, and helper constraints

- Implemented various constraints for city management including occupied, supplied, and capacity checks.
- Added diplomacy constraints to manage relationships between nations.
- Introduced general constraints to validate general states and resources.
- Created helper functions for reading entities and resolving IDs.
- Enhanced the overall logic for managing game state and constraints.
This commit is contained in:
2026-01-02 06:06:33 +00:00
parent 6d421e81a0
commit 56495c65e3
7 changed files with 991 additions and 957 deletions
+155
View File
@@ -0,0 +1,155 @@
import type { City, General, Nation, TriggerValue } from '../domain/entities.js';
import type {
ConstraintContext,
ConstraintResult,
RequirementKey,
StateView,
} from './types.js';
export const allow = (): ConstraintResult => ({ kind: 'allow' });
export const unknownOrDeny = (
ctx: ConstraintContext,
missing: RequirementKey[],
reason: string
): ConstraintResult =>
ctx.mode === 'precheck'
? { kind: 'unknown', missing }
: { kind: 'deny', reason };
export const readGeneral = (
ctx: ConstraintContext,
view: StateView
): General | null => {
const req: RequirementKey = { kind: 'general', id: ctx.actorId };
if (!view.has(req)) {
return null;
}
return view.get(req) as General | null;
};
export const readCity = (view: StateView, id?: number): City | null => {
if (id === undefined) {
return null;
}
const req: RequirementKey = { kind: 'city', id };
if (!view.has(req)) {
return null;
}
return view.get(req) as City | null;
};
export const resolveDestGeneralId = (ctx: ConstraintContext): number | undefined => {
if (ctx.destGeneralId !== undefined) {
return ctx.destGeneralId;
}
const raw = ctx.args?.destGeneralId;
return typeof raw === 'number' ? raw : undefined;
};
export const readDestGeneral = (
ctx: ConstraintContext,
view: StateView
): General | null => {
const destGeneralId = resolveDestGeneralId(ctx);
if (destGeneralId === undefined) {
return null;
}
const req: RequirementKey = { kind: 'destGeneral', id: destGeneralId };
if (!view.has(req)) {
return null;
}
return view.get(req) as General | null;
};
export const resolveDestCityId = (ctx: ConstraintContext): number | undefined => {
if (ctx.destCityId !== undefined) {
return ctx.destCityId;
}
const raw = ctx.args?.destCityId;
return typeof raw === 'number' ? raw : undefined;
};
export const resolveDestNationId = (ctx: ConstraintContext): number | undefined => {
if (ctx.destNationId !== undefined) {
return ctx.destNationId;
}
const raw = ctx.args?.destNationId;
return typeof raw === 'number' ? raw : undefined;
};
export const readDestCity = (
ctx: ConstraintContext,
view: StateView
): City | null => {
const destCityId = resolveDestCityId(ctx);
return readCity(view, destCityId);
};
export const readNation = (
view: StateView,
id?: number
): Nation | null => {
if (id === undefined) {
return null;
}
const req: RequirementKey = { kind: 'nation', id };
if (!view.has(req)) {
return null;
}
return view.get(req) as Nation | null;
};
export const readMetaNumber = (
meta: Record<string, TriggerValue>,
key: string
): number | null => {
const value = meta[key];
return typeof value === 'number' ? value : null;
};
export const readDiplomacyState = (
view: StateView,
srcNationId: number,
destNationId: number
): number | null => {
const req: RequirementKey = {
kind: 'diplomacy',
srcNationId,
destNationId,
};
if (!view.has(req)) {
return null;
}
const value = view.get(req);
if (typeof value === 'number') {
return value;
}
if (value && typeof value === 'object') {
const state = (value as { state?: number; stateCode?: number }).state;
const stateCode = (value as { state?: number; stateCode?: number }).stateCode;
if (typeof state === 'number') {
return state;
}
if (typeof stateCode === 'number') {
return stateCode;
}
}
return null;
};
export const readMetaNumberFromUnknown = (
meta: Record<string, unknown>,
key: string
): number | null => {
const value = meta[key];
return typeof value === 'number' ? value : null;
};
export const parsePercent = (value: string): number | null => {
const match = /^(\d+(?:\.\d+)?)%$/.exec(value);
if (!match) {
return null;
}
return Number(match[1]) / 100;
};