Merge branch 'main' into feature/main-signup-kakao-gate

# Conflicts:
#	app/gateway-frontend/src/views/LobbyView.vue
This commit is contained in:
2026-07-26 10:17:37 +00:00
22 changed files with 2313 additions and 43 deletions
+2
View File
@@ -34,6 +34,8 @@ export interface DatabaseClient {
inheritanceUserState: GamePrisma.InheritanceUserStateDelegate;
boardPost: GamePrisma.BoardPostDelegate;
boardComment: GamePrisma.BoardCommentDelegate;
votePoll: GamePrisma.VotePollDelegate;
vote: GamePrisma.VoteDelegate;
inputEvent: GamePrisma.InputEventDelegate;
turnDaemonLease: GamePrisma.TurnDaemonLeaseDelegate;
}
@@ -26,10 +26,7 @@ import { z } from 'zod';
import { parseArgsWithSchema } from '../parseArgs.js';
const ARGS_SCHEMA = z.object({
destNationId: z.preprocess(
(value) => (typeof value === 'number' ? Math.floor(value) : value),
z.number().int().positive()
),
destNationId: z.number().int().positive(),
});
export type RaidArgs = z.infer<typeof ARGS_SCHEMA>;
@@ -23,13 +23,10 @@ import type { TurnCommandEnv } from '@sammo-ts/logic/actions/turn/commandEnv.js'
import { JosaUtil } from '@sammo-ts/common';
import type { NationTurnCommandSpec } from './index.js';
import { z } from 'zod';
import { parseArgsWithSchema } from '../parseArgs.js';
import { normalizeLegacyIntegerArg, parseArgsWithSchema } from '../parseArgs.js';
const ARGS_SCHEMA = z.object({
destCityId: z.preprocess(
(value) => (typeof value === 'number' ? Math.floor(value) : value),
z.number().int().positive()
),
destCityId: z.preprocess(normalizeLegacyIntegerArg, z.number().int().positive()),
});
export type DeceptionArgs = z.infer<typeof ARGS_SCHEMA>;
@@ -59,7 +56,7 @@ const legacyChoiceIndex = (rng: GeneralActionResolveContext['rng'], length: numb
const pickMoveCityId = (rng: GeneralActionResolveContext['rng'], destCityId: number, candidates: City[]): number => {
if (candidates.length === 0) {
return destCityId;
throw new RangeError('Cannot choose a deception destination from an empty city collection.');
}
let idx = legacyChoiceIndex(rng, candidates.length);
let cityId = candidates[idx]?.id ?? destCityId;
@@ -258,7 +255,7 @@ export const actionContextBuilder: ActionContextBuilder<DeceptionArgs> = (base,
const friendlyGenerals = generals.filter((general) => general.nationId === base.general.nationId);
const destNationSupplyCities = worldRef
.listCities()
.filter((city) => city.nationId === destCity.nationId && city.supplyState > 0);
.filter((city) => city.nationId === destCity.nationId && city.supplyState === 1);
return {
...base,
destCity,
@@ -6,6 +6,7 @@ import { ActionDefinition as MoveCapitalAction } from '../../../src/actions/turn
import { ActionDefinition as ChangeNationNameAction } from '../../../src/actions/turn/nation/che_국호변경.js';
import { ActionDefinition as ExpandCityAction } from '../../../src/actions/turn/nation/che_증축.js';
import { ActionDefinition as LastStandAction } from '../../../src/actions/turn/nation/che_필사즉생.js';
import { ActionDefinition as DeceptionAction } from '../../../src/actions/turn/nation/che_허보.js';
import { LogCategory, LogScope } from '../../../src/logging/types.js';
import type { MapDefinition } from '../../../src/world/types.js';
import type { TurnSchedule } from '../../../src/turn/calendar.js';
@@ -248,6 +249,33 @@ describe('Nation Actions', () => {
});
});
describe('che_허보 (Deception)', () => {
it('fails like legacy choice when the target nation has no supplied city', () => {
const nation = buildNation(1);
const destNation = buildNation(2);
const general = buildGeneral(1, 1, 1);
const target = buildGeneral(2, 2, 2, 'Target');
const definition = new DeceptionAction([]);
expect(() =>
definition.resolve(
{
general,
nation,
destNation,
destCity: buildCity(2, 2),
destCityGenerals: [target],
friendlyGenerals: [general],
destNationSupplyCities: [],
addLog: () => {},
rng: {} as any,
} as any,
{ destCityId: 2 }
)
).toThrow(RangeError);
});
});
describe('che_천도 (Move Capital)', () => {
it('changes nation capital city', () => {
const nation = buildNation(1);