feat: add NpcControlView component for managing NPC policies and priorities

This commit is contained in:
2026-01-18 14:59:06 +00:00
parent e2accc31a7
commit bb618a9ade
11 changed files with 2858 additions and 0 deletions
+85
View File
@@ -23,7 +23,9 @@ import {
import type { WorldStateRow } from '../../context.js';
import { authedProcedure, router } from '../../trpc.js';
import { MAX_NATION_TURNS, listNationTurns } from '../../turns/reservedTurns.js';
import { getMyGeneral } from '../shared/general.js';
import { resolveSecretPermission } from '../shared/secretPermission.js';
type PermissionKind = 'normal' | 'ambassador' | 'auditor';
@@ -869,6 +871,89 @@ export const nationRouter = router({
},
};
}),
getChiefCenter: authedProcedure.query(async ({ ctx }) => {
const me = await getMyGeneral(ctx);
assertNationAccess(me);
const [nation, worldState, nationGenerals] = await Promise.all([
ctx.db.nation.findUnique({
where: { id: me.nationId },
select: {
id: true,
name: true,
level: true,
meta: true,
},
}),
ctx.db.worldState.findFirst(),
ctx.db.general.findMany({
where: { nationId: me.nationId, officerLevel: { gte: 5 } },
select: {
id: true,
name: true,
officerLevel: true,
npcState: true,
turnTime: true,
},
}),
]);
if (!nation) {
throw new TRPCError({ code: 'NOT_FOUND', message: 'Nation not found' });
}
if (!worldState) {
throw new TRPCError({ code: 'PRECONDITION_FAILED', message: 'World state is not initialized.' });
}
const permissionLevel = resolveSecretPermission(
{
nationId: me.nationId,
officerLevel: me.officerLevel,
meta: me.meta,
penalty: me.penalty,
},
nation.meta
);
if (permissionLevel < 1) {
throw new TRPCError({ code: 'FORBIDDEN', message: '권한이 부족합니다.' });
}
const chiefLevels = [12, 10, 8, 6, 11, 9, 7, 5];
const generalByLevel = new Map(nationGenerals.map((general) => [general.officerLevel, general]));
const turnsByLevel = await Promise.all(
chiefLevels.map((level) => listNationTurns(ctx.db, nation.id, level))
);
const chiefs = chiefLevels.map((level, idx) => {
const entry = generalByLevel.get(level);
return {
officerLevel: level,
name: entry?.name ?? null,
npcState: entry?.npcState ?? null,
turnTime: entry?.turnTime ? entry.turnTime.toISOString() : null,
turns: turnsByLevel[idx],
};
});
return {
me: {
id: me.id,
officerLevel: me.officerLevel,
nationId: me.nationId,
},
nation: {
id: nation.id,
name: nation.name,
level: nation.level,
},
currentYear: worldState.currentYear,
currentMonth: worldState.currentMonth,
turnTermMinutes: Math.max(1, Math.round(worldState.tickSeconds / 60)),
maxTurns: MAX_NATION_TURNS,
chiefs,
};
}),
changePermission: authedProcedure
.input(
z.object({