refactor: enforce package boundaries
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import type { WorldStateRow } from '../context.js';
|
||||
import type { BattleSimJobPayload, BattleSimRequestPayload } from './types.js';
|
||||
import { loadUnitSetDefinitionByName } from './unitSetLoader.js';
|
||||
import { loadUnitSetDefinitionByName } from '@sammo-ts/game-engine/scenario/unitSetLoader.js';
|
||||
import { normalizeScenarioEffect, type ScenarioEffectKey, type WarEngineConfig } from '@sammo-ts/logic';
|
||||
import { asRecord } from '@sammo-ts/common';
|
||||
import type { UnitSetDefinition } from '@sammo-ts/logic';
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
import fs from 'node:fs/promises';
|
||||
import path from 'node:path';
|
||||
|
||||
import { parseUnitSetDefinition, type UnitSetDefinition } from '@sammo-ts/logic';
|
||||
|
||||
import { resolveWorkspaceRoot } from '../paths.js';
|
||||
|
||||
const REPO_ROOT = resolveWorkspaceRoot();
|
||||
const DEFAULT_UNIT_SET_ROOT = path.resolve(REPO_ROOT, 'resources', 'unitset');
|
||||
|
||||
export interface UnitSetLoaderOptions {
|
||||
unitSetRoot?: string;
|
||||
filePrefix?: string;
|
||||
}
|
||||
|
||||
const readJsonFile = async (filePath: string): Promise<unknown> => {
|
||||
const raw = await fs.readFile(filePath, 'utf8');
|
||||
return JSON.parse(raw) as unknown;
|
||||
};
|
||||
|
||||
const resolveUnitSetRoot = (options?: UnitSetLoaderOptions): string => options?.unitSetRoot ?? DEFAULT_UNIT_SET_ROOT;
|
||||
|
||||
export const resolveUnitSetDefinitionPath = (unitSetName: string, options?: UnitSetLoaderOptions): string => {
|
||||
const prefix = options?.filePrefix ?? 'unitset_';
|
||||
return path.resolve(resolveUnitSetRoot(options), `${prefix}${unitSetName}.json`);
|
||||
};
|
||||
|
||||
export const loadUnitSetDefinition = async (unitSetPath: string): Promise<UnitSetDefinition> => {
|
||||
const raw = await readJsonFile(unitSetPath);
|
||||
return parseUnitSetDefinition(raw);
|
||||
};
|
||||
|
||||
export const loadUnitSetDefinitionByName = async (
|
||||
unitSetName: string,
|
||||
options?: UnitSetLoaderOptions
|
||||
): Promise<UnitSetDefinition> => {
|
||||
const unitSetPath = resolveUnitSetDefinitionPath(unitSetName, options);
|
||||
return loadUnitSetDefinition(unitSetPath);
|
||||
};
|
||||
@@ -1,25 +1,6 @@
|
||||
import fs from 'node:fs/promises';
|
||||
import { existsSync } from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import { loadMapDefinitionByName as loadRuntimeMapDefinitionByName } from '@sammo-ts/game-engine/scenario/mapLoader.js';
|
||||
import type { MapDefinition } from '@sammo-ts/logic';
|
||||
|
||||
import { MapDefinitionSchema, type MapDefinition } from '@sammo-ts/logic';
|
||||
|
||||
const resolveWorkspaceRoot = (): string => {
|
||||
let current = path.resolve(process.cwd());
|
||||
for (let depth = 0; depth <= 6; depth += 1) {
|
||||
if (existsSync(path.join(current, 'pnpm-workspace.yaml'))) {
|
||||
return current;
|
||||
}
|
||||
const parent = path.dirname(current);
|
||||
if (parent === current) {
|
||||
break;
|
||||
}
|
||||
current = parent;
|
||||
}
|
||||
return path.resolve(process.cwd());
|
||||
};
|
||||
|
||||
const RESOURCE_MAP_ROOT = path.resolve(resolveWorkspaceRoot(), 'resources/map');
|
||||
const mapCache = new Map<string, MapDefinition>();
|
||||
|
||||
export const loadMapDefinitionByName = async (mapName: string): Promise<MapDefinition> => {
|
||||
@@ -30,8 +11,7 @@ export const loadMapDefinitionByName = async (mapName: string): Promise<MapDefin
|
||||
if (cached) {
|
||||
return cached;
|
||||
}
|
||||
const raw = await fs.readFile(path.join(RESOURCE_MAP_ROOT, `map_${mapName}.json`), 'utf-8');
|
||||
const map = MapDefinitionSchema.parse(JSON.parse(raw));
|
||||
const map = await loadRuntimeMapDefinitionByName(mapName);
|
||||
mapCache.set(mapName, map);
|
||||
return map;
|
||||
};
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import fs from 'node:fs/promises';
|
||||
import path from 'node:path';
|
||||
import { loadScenarioDefinitionById } from '@sammo-ts/game-engine/scenario/scenarioLoader.js';
|
||||
import type { ScenarioDefinition } from '@sammo-ts/logic';
|
||||
|
||||
import { loadMapDefinitionByName } from './mapDefinition.js';
|
||||
|
||||
export interface MapLayoutCity {
|
||||
@@ -19,311 +20,52 @@ export interface MapLayout {
|
||||
levelMap: Record<number, string>;
|
||||
}
|
||||
|
||||
interface ParsedCityConst {
|
||||
initCity?: unknown[];
|
||||
regionMap?: Record<string, unknown>;
|
||||
levelMap?: Record<string, unknown>;
|
||||
export interface MapLayoutLoaderOptions {
|
||||
loadScenario?: (scenarioId: number) => Promise<ScenarioDefinition>;
|
||||
loadMap?: typeof loadMapDefinitionByName;
|
||||
}
|
||||
|
||||
const LEGACY_SCENARIO_ROOT = path.resolve(process.cwd(), 'legacy/hwe/scenario');
|
||||
const LEGACY_MAP_ROOT = path.resolve(LEGACY_SCENARIO_ROOT, 'map');
|
||||
const LEGACY_CITY_CONST = path.resolve(process.cwd(), 'legacy/hwe/sammo/CityConstBase.php');
|
||||
|
||||
const layoutCache = new Map<string, MapLayout>();
|
||||
|
||||
const stripComments = (value: string): string => value.replace(/\/\*[\s\S]*?\*\//g, '').replace(/\/\/.*$/gm, '');
|
||||
|
||||
const extractPhpArray = (source: string, marker: string): string | null => {
|
||||
const idx = source.indexOf(marker);
|
||||
if (idx < 0) {
|
||||
const parseScenarioId = (scenario: string): number | null => {
|
||||
const normalized = scenario.replace(/^scenario_/i, '').replace(/\.json$/i, '');
|
||||
if (!/^\d+$/.test(normalized)) {
|
||||
return null;
|
||||
}
|
||||
const start = source.indexOf('[', idx);
|
||||
if (start < 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let depth = 0;
|
||||
let inString = false;
|
||||
let stringChar = '';
|
||||
|
||||
for (let i = start; i < source.length; i += 1) {
|
||||
const char = source[i];
|
||||
if (inString) {
|
||||
if (char === stringChar && source[i - 1] !== '\\') {
|
||||
inString = false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (char === '"' || char === "'") {
|
||||
inString = true;
|
||||
stringChar = char;
|
||||
continue;
|
||||
}
|
||||
if (char === '[') {
|
||||
depth += 1;
|
||||
continue;
|
||||
}
|
||||
if (char === ']') {
|
||||
depth -= 1;
|
||||
if (depth === 0) {
|
||||
return source.slice(start, i + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
const scenarioId = Number(normalized);
|
||||
return Number.isSafeInteger(scenarioId) ? scenarioId : null;
|
||||
};
|
||||
|
||||
const parsePhpArray = (input: string): unknown => {
|
||||
let index = 0;
|
||||
|
||||
const skipWhitespace = () => {
|
||||
while (index < input.length && /\s/.test(input[index] ?? '')) {
|
||||
index += 1;
|
||||
}
|
||||
};
|
||||
|
||||
const parseString = () => {
|
||||
const quote = input[index];
|
||||
index += 1;
|
||||
let value = '';
|
||||
while (index < input.length) {
|
||||
const char = input[index];
|
||||
if (char === quote && input[index - 1] !== '\\') {
|
||||
index += 1;
|
||||
return value;
|
||||
}
|
||||
value += char;
|
||||
index += 1;
|
||||
}
|
||||
return value;
|
||||
};
|
||||
|
||||
const parseNumber = () => {
|
||||
let raw = '';
|
||||
while (index < input.length && /[0-9.+-]/.test(input[index] ?? '')) {
|
||||
raw += input[index];
|
||||
index += 1;
|
||||
}
|
||||
return Number(raw);
|
||||
};
|
||||
|
||||
const parseValue = (): unknown => {
|
||||
skipWhitespace();
|
||||
const char = input[index];
|
||||
if (!char) {
|
||||
return null;
|
||||
}
|
||||
if (char === '[') {
|
||||
return parseArray();
|
||||
}
|
||||
if (char === '"' || char === "'") {
|
||||
return parseString();
|
||||
}
|
||||
if (/[0-9.+-]/.test(char)) {
|
||||
return parseNumber();
|
||||
}
|
||||
if (input.startsWith('true', index)) {
|
||||
index += 4;
|
||||
return true;
|
||||
}
|
||||
if (input.startsWith('false', index)) {
|
||||
index += 5;
|
||||
return false;
|
||||
}
|
||||
if (input.startsWith('null', index)) {
|
||||
index += 4;
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
const parseArray = (): unknown => {
|
||||
const output: unknown[] = [];
|
||||
const objectOutput: Record<string, unknown> = {};
|
||||
let hasKeyed = false;
|
||||
index += 1;
|
||||
|
||||
while (index < input.length) {
|
||||
skipWhitespace();
|
||||
if (input[index] === ']') {
|
||||
index += 1;
|
||||
break;
|
||||
}
|
||||
const keyOrValue = parseValue();
|
||||
skipWhitespace();
|
||||
if (input.slice(index, index + 2) === '=>') {
|
||||
hasKeyed = true;
|
||||
index += 2;
|
||||
const value = parseValue();
|
||||
objectOutput[String(keyOrValue)] = value;
|
||||
} else if (keyOrValue !== null) {
|
||||
output.push(keyOrValue);
|
||||
}
|
||||
skipWhitespace();
|
||||
if (input[index] === ',') {
|
||||
index += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (hasKeyed) {
|
||||
return objectOutput;
|
||||
}
|
||||
return output;
|
||||
};
|
||||
|
||||
return parseValue();
|
||||
};
|
||||
|
||||
const parseCityConstFile = async (filePath: string): Promise<ParsedCityConst> => {
|
||||
const resolveMapName = async (
|
||||
scenario: string,
|
||||
loadScenario: (scenarioId: number) => Promise<ScenarioDefinition>
|
||||
): Promise<string> => {
|
||||
const scenarioId = parseScenarioId(scenario);
|
||||
if (scenarioId === null) {
|
||||
return 'che';
|
||||
}
|
||||
try {
|
||||
const raw = await fs.readFile(filePath, 'utf-8');
|
||||
const source = stripComments(raw);
|
||||
const initCityRaw = extractPhpArray(source, '$initCity');
|
||||
const regionMapRaw = extractPhpArray(source, '$regionMap');
|
||||
const levelMapRaw = extractPhpArray(source, '$levelMap');
|
||||
|
||||
return {
|
||||
initCity: initCityRaw ? (parsePhpArray(initCityRaw) as unknown[]) : undefined,
|
||||
regionMap: regionMapRaw ? (parsePhpArray(regionMapRaw) as Record<string, unknown>) : undefined,
|
||||
levelMap: levelMapRaw ? (parsePhpArray(levelMapRaw) as Record<string, unknown>) : undefined,
|
||||
};
|
||||
} catch {
|
||||
return {};
|
||||
}
|
||||
};
|
||||
|
||||
const resolveScenarioFile = async (scenario: string): Promise<string> => {
|
||||
const normalized = scenario.replace(/\.json$/i, '');
|
||||
const candidates = [`${normalized}.json`, `scenario_${normalized}.json`, 'default.json'];
|
||||
|
||||
for (const candidate of candidates) {
|
||||
const fullPath = path.join(LEGACY_SCENARIO_ROOT, candidate);
|
||||
try {
|
||||
await fs.access(fullPath);
|
||||
return fullPath;
|
||||
} catch {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return path.join(LEGACY_SCENARIO_ROOT, 'default.json');
|
||||
};
|
||||
|
||||
const resolveMapName = async (scenario: string): Promise<string> => {
|
||||
const scenarioPath = await resolveScenarioFile(scenario);
|
||||
try {
|
||||
const raw = await fs.readFile(scenarioPath, 'utf-8');
|
||||
const parsed = JSON.parse(raw) as { map?: { mapName?: string } };
|
||||
return parsed.map?.mapName ?? 'che';
|
||||
const definition = await loadScenario(scenarioId);
|
||||
return definition.config.environment.mapName;
|
||||
} catch {
|
||||
// 운영 DB가 보존된 상태에서 해당 commit에 scenario resource가 없을 수
|
||||
// 있으므로 기존 기본 map인 che로 안전하게 돌아갑니다.
|
||||
return 'che';
|
||||
}
|
||||
};
|
||||
|
||||
const buildLookupMap = (raw: Record<string, unknown> | undefined) => {
|
||||
const idToName: Record<number, string> = {};
|
||||
const nameToId: Record<string, number> = {};
|
||||
if (!raw) {
|
||||
return { idToName, nameToId };
|
||||
}
|
||||
|
||||
for (const [key, value] of Object.entries(raw)) {
|
||||
const numericKey = Number(key);
|
||||
if (typeof value === 'string' && Number.isFinite(numericKey)) {
|
||||
idToName[numericKey] = value;
|
||||
continue;
|
||||
}
|
||||
if (typeof value === 'number') {
|
||||
nameToId[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
return { idToName, nameToId };
|
||||
};
|
||||
|
||||
const normalizeInitCity = (
|
||||
initCity: unknown[],
|
||||
levelMap: ReturnType<typeof buildLookupMap>,
|
||||
regionMap: ReturnType<typeof buildLookupMap>
|
||||
): MapLayoutCity[] => {
|
||||
const rows = initCity.filter(Array.isArray) as unknown[][];
|
||||
const nameToId = new Map<string, number>();
|
||||
|
||||
for (const row of rows) {
|
||||
if (typeof row[0] === 'number' && typeof row[1] === 'string') {
|
||||
nameToId.set(row[1], row[0]);
|
||||
}
|
||||
}
|
||||
|
||||
return rows
|
||||
.map((row) => {
|
||||
const [id, name, levelLabel, _pop, _agri, _comm, _secu, _def, _wall, regionLabel, x, y, path] = row;
|
||||
if (typeof id !== 'number' || typeof name !== 'string') {
|
||||
return null;
|
||||
}
|
||||
|
||||
const levelValue =
|
||||
typeof levelLabel === 'number'
|
||||
? levelLabel
|
||||
: typeof levelLabel === 'string'
|
||||
? (levelMap.nameToId[levelLabel] ?? Number(levelLabel))
|
||||
: 0;
|
||||
|
||||
const regionValue =
|
||||
typeof regionLabel === 'number'
|
||||
? regionLabel
|
||||
: typeof regionLabel === 'string'
|
||||
? (regionMap.nameToId[regionLabel] ?? Number(regionLabel))
|
||||
: 0;
|
||||
|
||||
const pathNames = Array.isArray(path) ? (path as string[]) : [];
|
||||
const pathIds = pathNames
|
||||
.map((pathName) => nameToId.get(pathName))
|
||||
.filter((value): value is number => typeof value === 'number');
|
||||
|
||||
return {
|
||||
id,
|
||||
name,
|
||||
level: Number.isFinite(levelValue) ? levelValue : 0,
|
||||
region: Number.isFinite(regionValue) ? regionValue : 0,
|
||||
x: typeof x === 'number' ? x : 0,
|
||||
y: typeof y === 'number' ? y : 0,
|
||||
path: pathIds,
|
||||
} satisfies MapLayoutCity;
|
||||
})
|
||||
.filter((value): value is MapLayoutCity => value !== null);
|
||||
};
|
||||
|
||||
export const loadMapLayout = async (scenario: string): Promise<MapLayout> => {
|
||||
const mapName = await resolveMapName(scenario);
|
||||
const cached = layoutCache.get(mapName);
|
||||
export const loadMapLayout = async (scenario: string, options: MapLayoutLoaderOptions = {}): Promise<MapLayout> => {
|
||||
const mapName = await resolveMapName(scenario, options.loadScenario ?? loadScenarioDefinitionById);
|
||||
const useCache = !options.loadScenario && !options.loadMap;
|
||||
const cached = useCache ? layoutCache.get(mapName) : undefined;
|
||||
if (cached) {
|
||||
return cached;
|
||||
}
|
||||
|
||||
const base = await parseCityConstFile(LEGACY_CITY_CONST);
|
||||
const mapPath = path.join(LEGACY_MAP_ROOT, `${mapName}.php`);
|
||||
const map = await parseCityConstFile(mapPath);
|
||||
|
||||
const regionMapRaw = {
|
||||
...(base.regionMap ?? {}),
|
||||
...(map.regionMap ?? {}),
|
||||
};
|
||||
const levelMapRaw = {
|
||||
...(base.levelMap ?? {}),
|
||||
...(map.levelMap ?? {}),
|
||||
};
|
||||
|
||||
const regionMap = buildLookupMap(regionMapRaw);
|
||||
const levelMap = buildLookupMap(levelMapRaw);
|
||||
|
||||
const initCity = map.initCity ?? base.initCity ?? [];
|
||||
let cityList = normalizeInitCity(initCity, levelMap, regionMap);
|
||||
if (cityList.length === 0) {
|
||||
const resourceMap = await loadMapDefinitionByName(mapName);
|
||||
cityList = resourceMap.cities.map((city) => ({
|
||||
const map = await (options.loadMap ?? loadMapDefinitionByName)(mapName);
|
||||
const layout: MapLayout = {
|
||||
mapName,
|
||||
cityList: map.cities.map((city) => ({
|
||||
id: city.id,
|
||||
name: city.name,
|
||||
level: city.level,
|
||||
@@ -331,16 +73,13 @@ export const loadMapLayout = async (scenario: string): Promise<MapLayout> => {
|
||||
x: city.position.x,
|
||||
y: city.position.y,
|
||||
path: [...city.connections],
|
||||
}));
|
||||
}
|
||||
|
||||
const layout: MapLayout = {
|
||||
mapName,
|
||||
cityList,
|
||||
regionMap: regionMap.idToName,
|
||||
levelMap: levelMap.idToName,
|
||||
})),
|
||||
regionMap: {},
|
||||
levelMap: {},
|
||||
};
|
||||
|
||||
layoutCache.set(mapName, layout);
|
||||
if (useCache) {
|
||||
layoutCache.set(mapName, layout);
|
||||
}
|
||||
return layout;
|
||||
};
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
|
||||
const hasWorkspaceMarker = (dir: string): boolean => fs.existsSync(path.join(dir, 'pnpm-workspace.yaml'));
|
||||
|
||||
export const resolveWorkspaceRoot = (
|
||||
startDir: string = process.env.GAME_WORKSPACE_ROOT ?? process.env.GATEWAY_WORKSPACE_ROOT ?? process.cwd(),
|
||||
maxDepth = 6
|
||||
): string => {
|
||||
let current = path.resolve(startDir);
|
||||
for (let depth = 0; depth <= maxDepth; depth += 1) {
|
||||
if (hasWorkspaceMarker(current)) return current;
|
||||
const parent = path.dirname(current);
|
||||
if (parent === current) break;
|
||||
current = parent;
|
||||
}
|
||||
return path.resolve(startDir);
|
||||
};
|
||||
@@ -1,7 +1,7 @@
|
||||
import { TRPCError } from '@trpc/server';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { LogCategory, LogScope } from '@sammo-ts/infra';
|
||||
import { LogCategory, LogScope } from '@sammo-ts/logic';
|
||||
import { asRecord } from '@sammo-ts/common';
|
||||
|
||||
import type { GameApiContext } from '../../context.js';
|
||||
|
||||
@@ -21,7 +21,7 @@ import {
|
||||
ConflictingTurnDaemonCommandError,
|
||||
RejectedNpcPossessionCommandError,
|
||||
} from '../../daemon/databaseTransport.js';
|
||||
import { NpcPossessionError, reserveNpcPossessionCandidates } from '@sammo-ts/game-engine';
|
||||
import { NpcPossessionError, reserveNpcPossessionCandidates } from '@sammo-ts/game-engine/turn/npcPossessionService.js';
|
||||
import { resolveNationScoutMessage } from '../nation/shared.js';
|
||||
|
||||
const resolveSelectionCommandResult = (
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { TRPCError } from '@trpc/server';
|
||||
|
||||
import { LogCategory } from '@sammo-ts/infra';
|
||||
import { LogCategory } from '@sammo-ts/logic';
|
||||
|
||||
import { accessAuthedProcedure } from '../../../trpc.js';
|
||||
import { getMyGeneral } from '../../shared/general.js';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { TRPCError } from '@trpc/server';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { LogCategory, LogScope } from '@sammo-ts/infra';
|
||||
import { LogCategory, LogScope } from '@sammo-ts/logic';
|
||||
|
||||
import { authedProcedure } from '../../../trpc.js';
|
||||
import { getMyGeneral } from '../../shared/general.js';
|
||||
|
||||
@@ -1,8 +1,15 @@
|
||||
import { TRPCError } from '@trpc/server';
|
||||
|
||||
import { asRecord } from '@sammo-ts/common';
|
||||
import { LogCategory, LogScope } from '@sammo-ts/infra';
|
||||
import { getGoldIncome, getOutcome, getRiceIncome, getWallIncome, getWarGoldIncome } from '@sammo-ts/logic';
|
||||
import {
|
||||
getGoldIncome,
|
||||
getOutcome,
|
||||
getRiceIncome,
|
||||
getWallIncome,
|
||||
getWarGoldIncome,
|
||||
LogCategory,
|
||||
LogScope,
|
||||
} from '@sammo-ts/logic';
|
||||
|
||||
import { authedProcedure } from '../../../trpc.js';
|
||||
import { getMyGeneral } from '../../shared/general.js';
|
||||
|
||||
@@ -2,7 +2,7 @@ import { TRPCError } from '@trpc/server';
|
||||
|
||||
import { asRecord } from '@sammo-ts/common';
|
||||
|
||||
import { loadUnitSetDefinitionByName } from '../../../battleSim/unitSetLoader.js';
|
||||
import { loadUnitSetDefinitionByName } from '@sammo-ts/game-engine/scenario/unitSetLoader.js';
|
||||
import { accessAuthedProcedure } from '../../../trpc.js';
|
||||
import { getMyGeneral } from '../../shared/general.js';
|
||||
import { assertNationAccess, resolveNationPermission } from '../shared.js';
|
||||
|
||||
@@ -5,7 +5,7 @@ import { asRecord, isRecord } from '@sammo-ts/common';
|
||||
import { findCrewTypeById, getTechCost } from '@sammo-ts/logic/world/unitSet.js';
|
||||
|
||||
import { accessAuthedInputProcedure, authedProcedure, router } from '../../trpc.js';
|
||||
import { loadUnitSetDefinitionByName } from '../../battleSim/unitSetLoader.js';
|
||||
import { loadUnitSetDefinitionByName } from '@sammo-ts/game-engine/scenario/unitSetLoader.js';
|
||||
import type { GameApiContext } from '../../context.js';
|
||||
import { getMyGeneral } from '../shared/general.js';
|
||||
import { resolveSecretPermission } from '../shared/secretPermission.js';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { TRPCError } from '@trpc/server';
|
||||
import { asNumber, asRecord } from '@sammo-ts/common';
|
||||
import { LogCategory, LogScope } from '@sammo-ts/infra';
|
||||
import { LogCategory, LogScope } from '@sammo-ts/logic';
|
||||
import { z } from 'zod';
|
||||
|
||||
import type { GameApiContext } from '../../context.js';
|
||||
|
||||
@@ -6,7 +6,7 @@ import { accessAuthedProcedure, authedProcedure, procedure, router } from '../..
|
||||
import { asRecord, isRecord } from '@sammo-ts/common';
|
||||
import { loadWorldMap } from '../../maps/worldMap.js';
|
||||
import { loadMapLayout } from '../../maps/mapLayout.js';
|
||||
import { loadUnitSetDefinitionByName } from '../../battleSim/unitSetLoader.js';
|
||||
import { loadUnitSetDefinitionByName } from '@sammo-ts/game-engine/scenario/unitSetLoader.js';
|
||||
import { getMyGeneral, getOwnedGeneral } from '../shared/general.js';
|
||||
import { getGeneralDirectory, getNationDirectory } from './directory.js';
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import { TRPCError } from '@trpc/server';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { asRecord, isRecord } from '@sammo-ts/common';
|
||||
import { LogCategory, LogScope } from '@sammo-ts/infra';
|
||||
import { LogCategory, LogScope } from '@sammo-ts/logic';
|
||||
|
||||
import type { GameApiContext } from '../../context.js';
|
||||
import { loadPublicMap, type BaseMapResult } from '../../maps/worldMap.js';
|
||||
|
||||
@@ -37,7 +37,7 @@ export class RemoteContentImageStore implements ContentImageUploadStore {
|
||||
'x-image-request-id': requestId,
|
||||
'x-image-signature': signature,
|
||||
},
|
||||
body: input.body,
|
||||
body: new Uint8Array(input.body),
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error(`Image repository upload failed with HTTP ${response.status}.`);
|
||||
|
||||
@@ -9,4 +9,4 @@ export {
|
||||
type SelectPoolCandidateDto,
|
||||
type SelectPoolCandidateInfo,
|
||||
type SelectPoolReservationDto,
|
||||
} from '@sammo-ts/game-engine';
|
||||
} from '@sammo-ts/game-engine/turn/selectPoolService.js';
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
import { asRecord, isRecord } from '@sammo-ts/common';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { loadTurnCommandProfile } from './turnCommandProfile.js';
|
||||
import { loadTurnCommandProfile } from '@sammo-ts/game-engine/turn/turnCommandProfile.js';
|
||||
|
||||
export type TurnCommandOptionValue = string | number;
|
||||
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
import fs from 'node:fs/promises';
|
||||
import path from 'node:path';
|
||||
|
||||
import { DEFAULT_TURN_COMMAND_PROFILE, parseTurnCommandProfile, type TurnCommandProfile } from '@sammo-ts/logic';
|
||||
|
||||
import { resolveWorkspaceRoot } from '../paths.js';
|
||||
|
||||
const REPO_ROOT = resolveWorkspaceRoot();
|
||||
const DEFAULT_PROFILE_PATH = path.resolve(REPO_ROOT, 'resources', 'turn-commands', 'default.json');
|
||||
|
||||
export interface TurnCommandProfileOptions {
|
||||
filePath?: string;
|
||||
}
|
||||
|
||||
const readCommandProfile = async (filePath: string): Promise<TurnCommandProfile> => {
|
||||
const raw = await fs.readFile(filePath, 'utf8');
|
||||
return parseTurnCommandProfile(JSON.parse(raw) as unknown);
|
||||
};
|
||||
|
||||
export const loadTurnCommandProfile = async (options?: TurnCommandProfileOptions): Promise<TurnCommandProfile> => {
|
||||
const filePath = options?.filePath ?? process.env.TURN_COMMANDS_PATH ?? DEFAULT_PROFILE_PATH;
|
||||
try {
|
||||
return await readCommandProfile(filePath);
|
||||
} catch (error) {
|
||||
if ((error as NodeJS.ErrnoException).code === 'ENOENT') {
|
||||
return DEFAULT_TURN_COMMAND_PROFILE;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user