feat: execute MyPage immediate actions in daemon

This commit is contained in:
2026-07-31 01:56:16 +00:00
parent c6a2a0da93
commit d82e493109
13 changed files with 1836 additions and 98 deletions
+2 -2
View File
@@ -70,8 +70,8 @@ export type TurnDaemonCommand =
}
| { type: 'troopRename'; requestId?: string; generalId: number; troopId: number; troopName: string }
| { type: 'dieOnPrestart'; requestId?: string; generalId: number }
| { type: 'buildNationCandidate'; requestId?: string; generalId: number }
| { type: 'instantRetreat'; requestId?: string; generalId: number }
| { type: 'buildNationCandidate'; requestId?: string; userId: string; generalId: number }
| { type: 'instantRetreat'; requestId?: string; userId: string; generalId: number }
| { type: 'vacation'; requestId?: string; generalId: number }
| {
type: 'setMySetting';
@@ -30,6 +30,9 @@ export interface UprisingContext extends ActionContextBase {
}
const ACTION_NAME = '거병';
const formatHourMinute = (date: Date): string =>
`${String(date.getUTCHours()).padStart(2, '0')}:${String(date.getUTCMinutes()).padStart(2, '0')}`;
const truncateLegacyWidth = (value: string, maxWidth: number): string => {
let result = '';
let width = 0;
@@ -119,7 +122,7 @@ export class ActionDefinition<
const cityName = context.city?.name ?? '??';
context.addLog(`거병에 성공하였습니다.`, {
context.addLog(`거병에 성공하였습니다. <1>${formatHourMinute(uprisingCtx.general.turnTime)}</>`, {
category: LogCategory.ACTION,
format: LogFormat.MONTH,
});
@@ -128,18 +131,21 @@ export class ActionDefinition<
category: LogCategory.SUMMARY,
format: LogFormat.MONTH,
});
context.addLog(
`<Y><b>【거병】</b></><D><b>${general.name}</b></>${josaYi} 세력을 결성하였습니다.`,
{
scope: LogScope.SYSTEM,
category: LogCategory.HISTORY,
format: LogFormat.YEAR_MONTH,
}
);
context.addLog(`<Y><b>【거병】</b></><D><b>${general.name}</b></>${josaYi} 세력을 결성하였습니다.`, {
scope: LogScope.SYSTEM,
category: LogCategory.HISTORY,
format: LogFormat.YEAR_MONTH,
});
context.addLog(`<G><b>${cityName}</b></>에서 거병`, {
category: LogCategory.HISTORY,
format: LogFormat.YEAR_MONTH,
});
context.addLog(`<Y>${general.name}</>${josaYi} <G><b>${cityName}</b></>에서 거병`, {
scope: LogScope.NATION,
nationId: newNationId,
category: LogCategory.HISTORY,
format: LogFormat.YEAR_MONTH,
});
tryApplyUniqueLottery(context, {
acquireType: '아이템',
@@ -8,7 +8,6 @@ import { LogCategory, LogFormat, LogScope } from '@sammo-ts/logic/logging/types.
import { JosaUtil } from '@sammo-ts/common';
import type { ActionContextBuilder } from '@sammo-ts/logic/actions/turn/actionContext.js';
import type { TurnCommandEnv } from '@sammo-ts/logic/actions/turn/commandEnv.js';
import { searchDistance } from '@sammo-ts/logic/world/distance.js';
import type { MapDefinition } from '@sammo-ts/logic/world/types.js';
import type { GeneralTurnCommandSpec } from './index.js';
@@ -23,9 +22,7 @@ type InclusiveRandomGenerator = GeneralActionResolveContext['rng'] & {
const legacyChoiceIndex = (rng: GeneralActionResolveContext['rng'], length: number): number => {
const inclusive = rng as InclusiveRandomGenerator;
return inclusive.nextIntInclusive
? inclusive.nextIntInclusive(length - 1)
: rng.nextInt(0, length);
return inclusive.nextIntInclusive ? inclusive.nextIntInclusive(length - 1) : rng.nextInt(0, length);
};
export interface BorderReturnResolveContext<
@@ -66,26 +63,32 @@ export class ActionDefinition<
return { effects: [] };
}
const distanceByCity = searchDistance(map, general.cityId, 3);
const candidates = allCities
.filter((city) => city.nationId === nation.id && city.supplyState === 1)
.map((city) => ({ city, distance: distanceByCity[city.id] }))
.filter((entry) => typeof entry.distance === 'number' && Number.isFinite(entry.distance));
if (candidates.length === 0) {
context.addLog('3칸 이내에 아국 도시가 없습니다.', {
scope: LogScope.GENERAL,
category: LogCategory.ACTION,
format: LogFormat.MONTH,
const cityById = new Map(allCities.map((city) => [city.id, city]));
const mapCityById = new Map(map.cities.map((city) => [city.id, city]));
const visited = new Set([general.cityId]);
let frontier = [general.cityId];
let nearestCities: City[] = [];
for (let distance = 1; distance <= 3 && frontier.length > 0; distance += 1) {
const nextFrontier: number[] = [];
for (const cityId of frontier) {
for (const connectedCityId of mapCityById.get(cityId)?.connections ?? []) {
if (visited.has(connectedCityId)) {
continue;
}
visited.add(connectedCityId);
nextFrontier.push(connectedCityId);
}
}
nearestCities = nextFrontier.flatMap((cityId) => {
const city = cityById.get(cityId);
return city?.nationId === nation.id && city.supplyState === 1 ? [city] : [];
});
return { effects: [] };
if (nearestCities.length > 0) {
break;
}
frontier = nextFrontier;
}
const minDistance = Math.min(...candidates.map((entry) => entry.distance as number));
const nearestCities = candidates
.filter((entry) => entry.distance === minDistance)
.map((entry) => entry.city);
if (nearestCities.length === 0) {
context.addLog('3칸 이내에 아국 도시가 없습니다.', {
scope: LogScope.GENERAL,