코드 이식

This commit is contained in:
2026-01-11 09:49:22 +00:00
parent 6497b09b9d
commit dc0b9271fb
14 changed files with 2195 additions and 76 deletions
+107 -15
View File
@@ -1,5 +1,6 @@
import type { City, General, Nation } from '@sammo-ts/logic/domain/entities.js';
import type { MapDefinition } from '@sammo-ts/logic/world/types.js';
import { getCityDistance } from '../world/distance.js';
import {
allow,
parsePercent,
@@ -113,11 +114,11 @@ export const suppliedDestCity = (): Constraint => ({
requires: (ctx) =>
resolveDestCityId(ctx) !== undefined
? [
{
kind: 'destCity',
id: resolveDestCityId(ctx) ?? 0,
},
]
{
kind: 'destCity',
id: resolveDestCityId(ctx) ?? 0,
},
]
: [],
test: (ctx, view) => {
const destCity = readDestCity(ctx, view);
@@ -249,11 +250,11 @@ export const existsDestCity = (): Constraint => ({
requires: (ctx) =>
resolveDestCityId(ctx) !== undefined
? [
{
kind: 'destCity',
id: resolveDestCityId(ctx) ?? 0,
},
]
{
kind: 'destCity',
id: resolveDestCityId(ctx) ?? 0,
},
]
: [],
test: (ctx, view) => {
const destCityId = resolveDestCityId(ctx);
@@ -315,11 +316,11 @@ export const notNeutralDestCity = (): Constraint => ({
requires: (ctx) =>
resolveDestCityId(ctx) !== undefined
? [
{
kind: 'destCity',
id: resolveDestCityId(ctx) ?? 0,
},
]
{
kind: 'destCity',
id: resolveDestCityId(ctx) ?? 0,
},
]
: [],
test: (ctx, view) => {
const destCity = readDestCity(ctx, view);
@@ -480,3 +481,94 @@ export const reqCityLevel = (levels: number[]): Constraint => ({
return { kind: 'deny', reason: '규모가 맞지 않습니다.' };
},
});
export const nearCity = (maxDistance: number): Constraint => ({
name: 'nearCity',
requires: (ctx) => {
const reqs: RequirementKey[] = [{ kind: 'env', key: 'map' }];
const destCityId = resolveDestCityId(ctx);
if (destCityId !== undefined) {
reqs.push({ kind: 'destCity', id: destCityId });
}
if (ctx.cityId !== undefined) {
reqs.push({ kind: 'city', id: ctx.cityId });
}
return reqs;
},
test: (ctx, view) => {
const map = view.get({ kind: 'env', key: 'map' }) as MapDefinition | null;
if (!map) {
return unknownOrDeny(ctx, [], '지도 정보가 없습니다.');
}
const destCityId = resolveDestCityId(ctx);
if (destCityId === undefined) {
return unknownOrDeny(ctx, [], '도시 정보가 없습니다.');
}
const city = readCity(view, ctx.cityId);
if (!city) {
if (ctx.cityId === undefined) {
return unknownOrDeny(ctx, [], '도시 정보가 없습니다.');
}
const req: RequirementKey = { kind: 'city', id: ctx.cityId };
return unknownOrDeny(ctx, [req], '도시 정보가 없습니다.');
}
const distance = getCityDistance(map, city.id, destCityId);
if (distance <= maxDistance) {
return allow();
}
return { kind: 'deny', reason: '너무 멉니다.' };
},
});
export const notCapital = (checkCurrentCity = false): Constraint => ({
name: 'notCapital',
requires: (ctx) => {
const reqs: RequirementKey[] = [{ kind: 'general', id: ctx.actorId }];
if (checkCurrentCity) {
if (ctx.cityId !== undefined) {
reqs.push({ kind: 'city', id: ctx.cityId });
}
} else {
// If checking dest, we need destCityId
const destCityId = resolveDestCityId(ctx);
if (destCityId !== undefined) {
reqs.push({ kind: 'destCity', id: destCityId });
}
}
// Need nation to know capital
reqs.push({ kind: 'nation', id: ctx.nationId });
return reqs;
},
test: (ctx, view) => {
const nationReq: RequirementKey = { kind: 'nation', id: ctx.nationId };
if (!view.has(nationReq)) return unknownOrDeny(ctx, [nationReq], '국가 정보가 없습니다.');
const nation = view.get(nationReq) as Nation | null;
if (!nation) return unknownOrDeny(ctx, [nationReq], '국가 정보가 없습니다.');
let targetCityId: number | undefined;
if (checkCurrentCity) {
targetCityId = ctx.cityId;
if (targetCityId === undefined) {
const general = readGeneral(ctx, view);
targetCityId = general?.cityId;
}
} else {
targetCityId = resolveDestCityId(ctx);
}
if (targetCityId === undefined) {
return unknownOrDeny(ctx, [], '도시 정보가 없습니다.');
}
if (targetCityId === nation.capitalCityId) {
return { kind: 'deny', reason: '수도입니다.' };
}
return allow();
},
});
+77 -5
View File
@@ -181,11 +181,11 @@ export const existsDestGeneral = (): Constraint => ({
requires: (ctx) =>
resolveDestGeneralId(ctx) !== undefined
? [
{
kind: 'destGeneral',
id: resolveDestGeneralId(ctx) ?? 0,
},
]
{
kind: 'destGeneral',
id: resolveDestGeneralId(ctx) ?? 0,
},
]
: [],
test: (ctx, view) => {
const destGeneralId = resolveDestGeneralId(ctx);
@@ -282,3 +282,75 @@ export const friendlyDestGeneral = (): Constraint => ({
return { kind: 'deny', reason: '아군이 아닙니다.' };
},
});
export const mustBeNPC = (): Constraint => ({
name: 'mustBeNPC',
requires: (ctx) => [{ kind: 'general', id: ctx.actorId }],
test: (ctx, view) => {
const req: RequirementKey = { kind: 'general', id: ctx.actorId };
const general = view.get(req) as General | null;
if (!general) {
return unknownOrDeny(ctx, [req], '장수 정보가 없습니다.');
}
// Assuming npcState >= 2 means NPC. Need to verify exact logic if possible,
// but typically 0=human, 1=?, 2=NPC.
// Legacy: $general->getNPC() where 0:User, 1:Virtual User(unused?), 2:NPC ...
if (general.npcState >= 2) {
return allow();
}
return { kind: 'deny', reason: 'NPC가 아닙니다.' };
},
});
export const notSameDestNation = (): Constraint => ({
name: 'notSameDestNation',
requires: (ctx) => {
const reqs: RequirementKey[] = [];
const destNationId = resolveDestNationId(ctx);
if (destNationId !== undefined) {
reqs.push({ kind: 'destNation', id: destNationId });
}
return reqs;
},
test: (ctx, _view) => {
const destNationId = resolveDestNationId(ctx);
if (destNationId === undefined) {
return unknownOrDeny(ctx, [], '목표 국가가 없습니다.');
}
if (ctx.nationId === destNationId) {
return { kind: 'deny', reason: '이미 소속된 국가입니다.' };
}
return allow();
},
});
export const notLord = (): Constraint => ({
name: 'notLord',
requires: (ctx) => [{ kind: 'general', id: ctx.actorId }],
test: (ctx, view) => {
const req: RequirementKey = { kind: 'general', id: ctx.actorId };
const general = view.get(req) as General | null;
if (!general) return unknownOrDeny(ctx, [req], '장수 정보가 없습니다.');
if (general.officerLevel !== 12) {
return allow();
}
return { kind: 'deny', reason: '군주는 불가능합니다.' };
},
});
export const notChief = (): Constraint => ({
name: 'notChief',
requires: (ctx) => [{ kind: 'general', id: ctx.actorId }],
test: (ctx, view) => {
const req: RequirementKey = { kind: 'general', id: ctx.actorId };
const general = view.get(req) as General | null;
if (!general) return unknownOrDeny(ctx, [req], '장수 정보가 없습니다.');
if (general.officerLevel <= 4) {
return allow();
}
return { kind: 'deny', reason: '수뇌입니다.' };
},
});