merge: 최신 core2026 main을 접속 제한 작업에 통합
This commit is contained in:
@@ -4,6 +4,7 @@ import { asRecord } from '@sammo-ts/common';
|
||||
|
||||
import { zWorldStateConfig, zWorldStateMeta } from '../../context.js';
|
||||
import { isSelectionPoolWorld, resolveSelectionMaxGeneral } from '@sammo-ts/game-engine/turn/selectPoolService.js';
|
||||
import { loadCurrentGameTime } from '../../services/gameClock.js';
|
||||
import { procedure, router } from '../../trpc.js';
|
||||
|
||||
export const lobbyRouter = router({
|
||||
@@ -26,6 +27,7 @@ export const lobbyRouter = router({
|
||||
const npcCnt = await ctx.db.general.count({ where: { npcState: { gte: 2 } } });
|
||||
const nationCnt = await ctx.db.nation.count({ where: { level: { gt: 0 } } });
|
||||
const scenarioTitle = asRecord(asRecord(rawWorldState.meta).scenarioMeta).title;
|
||||
const gameTime = await loadCurrentGameTime(ctx.db);
|
||||
|
||||
let myGeneral = null;
|
||||
if (ctx.auth?.user.id) {
|
||||
@@ -54,6 +56,8 @@ export const lobbyRouter = router({
|
||||
starttime: worldState.meta.starttime ?? '',
|
||||
opentime: worldState.meta.opentime ?? '',
|
||||
turntime: worldState.meta.turntime ?? '',
|
||||
serverTime: gameTime.now.toISOString(),
|
||||
clockMode: gameTime.mode ?? 'realtime',
|
||||
otherTextInfo: worldState.meta.otherTextInfo ?? '',
|
||||
isUnited: worldState.meta.isunited ?? worldState.meta.isUnited ?? 0,
|
||||
selectionPoolEnabled: isSelectionPoolWorld(rawWorldState),
|
||||
|
||||
@@ -119,6 +119,7 @@ describe('buildTurnCommandTable', () => {
|
||||
'che_단련',
|
||||
'che_숙련전환',
|
||||
'che_견문',
|
||||
'che_은퇴',
|
||||
'che_장비매매',
|
||||
'che_군량매매',
|
||||
'che_내정특기초기화',
|
||||
@@ -135,9 +136,67 @@ describe('buildTurnCommandTable', () => {
|
||||
'che_주민선정',
|
||||
],
|
||||
군사: ['che_징병', 'che_모병', 'che_훈련', 'che_사기진작', 'che_출병', 'che_집합', 'che_소집해제'],
|
||||
인사: ['che_이동', 'che_인재탐색', 'che_귀환', 'che_임관', 'che_랜덤임관'],
|
||||
인사: ['che_이동', 'che_강행', 'che_인재탐색', 'che_귀환', 'che_임관', 'che_랜덤임관'],
|
||||
계략: ['che_화계'],
|
||||
국가: ['che_증여', 'che_헌납', 'che_물자조달', 'che_거병', 'che_건국', 'che_선양', 'che_해산'],
|
||||
국가: [
|
||||
'che_증여',
|
||||
'che_헌납',
|
||||
'che_물자조달',
|
||||
'che_하야',
|
||||
'che_거병',
|
||||
'che_건국',
|
||||
'che_선양',
|
||||
'che_해산',
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it('projects the Ref availability boundaries for force move, retirement, and resignation', async () => {
|
||||
const buildTable = (general: GeneralRow, nation: NationRow | null = buildNation()) =>
|
||||
buildTurnCommandTable({
|
||||
worldState: buildWorldState(),
|
||||
general,
|
||||
city: buildCity(),
|
||||
nation,
|
||||
nationGenerals: null,
|
||||
});
|
||||
const findCommand = (table: Awaited<ReturnType<typeof buildTable>>, key: string) =>
|
||||
table.general.flatMap((group) => group.values).find((command) => command.key === key);
|
||||
|
||||
const ordinary = await buildTable(buildGeneral());
|
||||
expect(findCommand(ordinary, 'che_강행')).toMatchObject({
|
||||
name: '강행',
|
||||
reqArg: true,
|
||||
possible: true,
|
||||
inputFields: [{ key: 'destCityId', optionSource: 'cities' }],
|
||||
});
|
||||
expect(findCommand(ordinary, 'che_은퇴')).toMatchObject({
|
||||
name: '은퇴',
|
||||
possible: false,
|
||||
status: 'blocked',
|
||||
reason: '나이가 60세 이상이어야 합니다.',
|
||||
});
|
||||
expect(findCommand(ordinary, 'che_하야')).toMatchObject({
|
||||
name: '하야',
|
||||
possible: true,
|
||||
status: 'available',
|
||||
});
|
||||
|
||||
const oldEnough = await buildTable({ ...buildGeneral(), age: 60 } as GeneralRow);
|
||||
expect(findCommand(oldEnough, 'che_은퇴')).toMatchObject({ possible: true, status: 'available' });
|
||||
|
||||
const ruler = await buildTable({ ...buildGeneral(), officerLevel: 12 } as GeneralRow);
|
||||
expect(findCommand(ruler, 'che_하야')).toMatchObject({
|
||||
possible: false,
|
||||
status: 'blocked',
|
||||
reason: expect.stringContaining('군주'),
|
||||
});
|
||||
|
||||
const neutral = await buildTable({ ...buildGeneral(), nationId: 0, officerLevel: 0 } as GeneralRow, null);
|
||||
expect(findCommand(neutral, 'che_하야')).toMatchObject({
|
||||
possible: false,
|
||||
status: 'blocked',
|
||||
reason: '재야입니다.',
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -3,7 +3,15 @@ import { describe, expect, it, vi } from 'vitest';
|
||||
import type { DatabaseClient, GameApiContext } from '../src/context.js';
|
||||
import { appRouter } from '../src/router.js';
|
||||
|
||||
const buildContext = (meta: Record<string, unknown>): GameApiContext =>
|
||||
const buildContext = (
|
||||
meta: Record<string, unknown>,
|
||||
clock: {
|
||||
baseTime?: Date;
|
||||
tick?: bigint;
|
||||
mode?: string;
|
||||
wallAnchor?: Date;
|
||||
} = {}
|
||||
): GameApiContext =>
|
||||
({
|
||||
auth: null,
|
||||
db: {
|
||||
@@ -16,6 +24,10 @@ const buildContext = (meta: Record<string, unknown>): GameApiContext =>
|
||||
tickSeconds: 3_600,
|
||||
config: {},
|
||||
meta,
|
||||
clockBaseTime: clock.baseTime ?? null,
|
||||
clockTick: clock.tick ?? null,
|
||||
clockMode: clock.mode ?? 'realtime',
|
||||
clockWallAnchor: clock.wallAnchor ?? null,
|
||||
updatedAt: new Date('2026-07-31T00:00:00.000Z'),
|
||||
})),
|
||||
},
|
||||
@@ -36,4 +48,23 @@ describe('lobby season state', () => {
|
||||
|
||||
expect(result.isUnited).toBe(isunited);
|
||||
});
|
||||
|
||||
it('returns the projected server game time and whether the clock is running', async () => {
|
||||
const result = await appRouter
|
||||
.createCaller(
|
||||
buildContext(
|
||||
{},
|
||||
{
|
||||
baseTime: new Date('2026-08-15T00:00:00.000Z'),
|
||||
tick: 72_000_000n,
|
||||
mode: 'manual',
|
||||
wallAnchor: new Date('2026-08-15T17:00:00.000Z'),
|
||||
}
|
||||
)
|
||||
)
|
||||
.lobby.info();
|
||||
|
||||
expect(result.serverTime).toBe('2026-08-15T02:00:00.000Z');
|
||||
expect(result.clockMode).toBe('manual');
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user