feat: 타입 정의 개선 및 any 사용 제거
This commit is contained in:
@@ -14,6 +14,18 @@ const buildDb = () => {
|
||||
const generalTurns = new Map<number, GeneralTurnRow[]>();
|
||||
const nationTurns = new Map<string, NationTurnRow[]>();
|
||||
|
||||
type GeneralTurnFindManyArgs = Parameters<DatabaseClient['generalTurn']['findMany']>[0];
|
||||
type GeneralTurnDeleteManyArgs = NonNullable<Parameters<DatabaseClient['generalTurn']['deleteMany']>[0]>;
|
||||
type GeneralTurnCreateManyArgs = NonNullable<Parameters<DatabaseClient['generalTurn']['createMany']>[0]>;
|
||||
type GeneralTurnCreateManyData = GeneralTurnCreateManyArgs['data'];
|
||||
type GeneralTurnCreateManyRow = GeneralTurnCreateManyData extends Array<infer Row> ? Row : never;
|
||||
|
||||
type NationTurnFindManyArgs = Parameters<DatabaseClient['nationTurn']['findMany']>[0];
|
||||
type NationTurnDeleteManyArgs = NonNullable<Parameters<DatabaseClient['nationTurn']['deleteMany']>[0]>;
|
||||
type NationTurnCreateManyArgs = NonNullable<Parameters<DatabaseClient['nationTurn']['createMany']>[0]>;
|
||||
type NationTurnCreateManyData = NationTurnCreateManyArgs['data'];
|
||||
type NationTurnCreateManyRow = NationTurnCreateManyData extends Array<infer Row> ? Row : never;
|
||||
|
||||
const db = {
|
||||
worldState: {
|
||||
findFirst: async () => null,
|
||||
@@ -28,13 +40,18 @@ const buildDb = () => {
|
||||
findUnique: async () => null,
|
||||
},
|
||||
generalTurn: {
|
||||
findMany: async ({ where }: any) => generalTurns.get(where.generalId) ?? [],
|
||||
deleteMany: async ({ where }: any) => {
|
||||
generalTurns.delete(where.generalId);
|
||||
findMany: async (args?: GeneralTurnFindManyArgs) => {
|
||||
const generalId = typeof args?.where?.generalId === 'number' ? args.where.generalId : undefined;
|
||||
return generalId !== undefined ? (generalTurns.get(generalId) ?? []) : [];
|
||||
},
|
||||
deleteMany: async ({ where }: GeneralTurnDeleteManyArgs) => {
|
||||
if (typeof where.generalId === 'number') {
|
||||
generalTurns.delete(where.generalId);
|
||||
}
|
||||
return {};
|
||||
},
|
||||
createMany: async ({ data }: any) => {
|
||||
const rows = data.map((row: any, index: number) => ({
|
||||
createMany: async ({ data }: GeneralTurnCreateManyArgs) => {
|
||||
const rows = data.map((row: GeneralTurnCreateManyRow, index: number) => ({
|
||||
id: index + 1,
|
||||
generalId: row.generalId,
|
||||
turnIdx: row.turnIdx,
|
||||
@@ -49,13 +66,23 @@ const buildDb = () => {
|
||||
},
|
||||
},
|
||||
nationTurn: {
|
||||
findMany: async ({ where }: any) => nationTurns.get(`${where.nationId}:${where.officerLevel}`) ?? [],
|
||||
deleteMany: async ({ where }: any) => {
|
||||
nationTurns.delete(`${where.nationId}:${where.officerLevel}`);
|
||||
findMany: async (args?: NationTurnFindManyArgs) => {
|
||||
const nationId = typeof args?.where?.nationId === 'number' ? args.where.nationId : undefined;
|
||||
const officerLevel =
|
||||
typeof args?.where?.officerLevel === 'number' ? args.where.officerLevel : undefined;
|
||||
if (nationId === undefined || officerLevel === undefined) {
|
||||
return [];
|
||||
}
|
||||
return nationTurns.get(`${nationId}:${officerLevel}`) ?? [];
|
||||
},
|
||||
deleteMany: async ({ where }: NationTurnDeleteManyArgs) => {
|
||||
if (typeof where.nationId === 'number' && typeof where.officerLevel === 'number') {
|
||||
nationTurns.delete(`${where.nationId}:${where.officerLevel}`);
|
||||
}
|
||||
return {};
|
||||
},
|
||||
createMany: async ({ data }: any) => {
|
||||
const rows = data.map((row: any, index: number) => ({
|
||||
createMany: async ({ data }: NationTurnCreateManyArgs) => {
|
||||
const rows = data.map((row: NationTurnCreateManyRow, index: number) => ({
|
||||
id: index + 1,
|
||||
nationId: row.nationId,
|
||||
officerLevel: row.officerLevel,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import type { GameApiContext, GameProfile, WorldStateRow } from '../src/context.js';
|
||||
import type { DatabaseClient, GameApiContext, GameProfile, WorldStateRow } from '../src/context.js';
|
||||
import type { RedisConnector } from '@sammo-ts/infra';
|
||||
import { InMemoryBattleSimTransport } from '../src/battleSim/inMemoryTransport.js';
|
||||
import { InMemoryTurnDaemonTransport } from '../src/daemon/inMemoryTransport.js';
|
||||
import { appRouter } from '../src/router.js';
|
||||
@@ -43,12 +44,12 @@ const buildContext = (options?: {
|
||||
},
|
||||
};
|
||||
return {
|
||||
db: db as any,
|
||||
db: db as unknown as DatabaseClient,
|
||||
turnDaemon: transport,
|
||||
battleSim,
|
||||
profile,
|
||||
auth: null,
|
||||
redis: {} as any,
|
||||
redis: {} as unknown as RedisConnector['client'],
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -3,9 +3,11 @@ import { describe, expect, it } from 'vitest';
|
||||
import { InMemoryGatewaySessionService } from '../src/auth/inMemorySessionService.js';
|
||||
import { createInMemoryUserRepository } from '../src/auth/inMemoryUserRepository.js';
|
||||
import { InMemoryOAuthSessionStore } from '../src/auth/oauthSessionStore.js';
|
||||
import type { KakaoOAuthClient } from '../src/auth/kakaoClient.js';
|
||||
import { createGatewayApiContext } from '../src/context.js';
|
||||
import { InMemoryProfileStatusService } from '../src/lobby/profileStatusService.js';
|
||||
import { appRouter } from '../src/router.js';
|
||||
import type { GatewayPrismaClient } from '@sammo-ts/infra';
|
||||
|
||||
const buildCaller = () => {
|
||||
const users = createInMemoryUserRepository();
|
||||
@@ -76,14 +78,14 @@ const buildCaller = () => {
|
||||
flushPublisher,
|
||||
gameTokenSecret: 'test-secret',
|
||||
gameSessionTtlSeconds: 600,
|
||||
kakaoClient: kakaoClient as any,
|
||||
kakaoClient: kakaoClient as unknown as KakaoOAuthClient,
|
||||
oauthSessions,
|
||||
publicBaseUrl: 'http://localhost',
|
||||
profiles,
|
||||
orchestrator,
|
||||
profileStatus,
|
||||
requestHeaders: {},
|
||||
prisma: {} as any,
|
||||
prisma: {} as unknown as GatewayPrismaClient,
|
||||
})
|
||||
);
|
||||
return { caller, oauthSessions };
|
||||
|
||||
@@ -1,15 +1,24 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import type { inferRouterOutputs } from '@trpc/server';
|
||||
import type { AppRouter } from '@sammo-ts/gateway-api';
|
||||
import DefaultLayout from '../layouts/DefaultLayout.vue';
|
||||
import { trpc } from '../utils/trpc';
|
||||
import { createGameTrpc } from '../utils/gameTrpc';
|
||||
import type { GameRouter } from '../utils/gameTrpc';
|
||||
|
||||
type GatewayRouterOutput = inferRouterOutputs<AppRouter>;
|
||||
type GameRouterOutput = inferRouterOutputs<GameRouter>;
|
||||
type MeOutput = GatewayRouterOutput['me'];
|
||||
type LobbyProfile = GatewayRouterOutput['lobby']['profiles'][number];
|
||||
type LobbyInfo = GameRouterOutput['lobby']['info'];
|
||||
|
||||
const router = useRouter();
|
||||
const me = ref<any>(null);
|
||||
const me = ref<MeOutput>(null);
|
||||
const notice = ref('');
|
||||
const profiles = ref<any[]>([]);
|
||||
const profileDetails = ref<Record<string, any>>({});
|
||||
const profiles = ref<LobbyProfile[]>([]);
|
||||
const profileDetails = ref<Record<string, LobbyInfo | undefined>>({});
|
||||
|
||||
onMounted(async () => {
|
||||
try {
|
||||
|
||||
@@ -13,8 +13,8 @@ export interface TurnCommandSpecBase<TKey extends string = string> {
|
||||
|
||||
export interface TurnCommandModule<TSpec extends TurnCommandSpecBase = TurnCommandSpecBase> {
|
||||
commandSpec: TSpec;
|
||||
ActionDefinition: new (...args: any[]) => GeneralActionDefinition;
|
||||
ActionResolver?: new (...args: any[]) => GeneralActionResolver;
|
||||
CommandResolver?: new (...args: any[]) => any;
|
||||
ActionDefinition: new (...args: unknown[]) => GeneralActionDefinition;
|
||||
ActionResolver?: new (...args: unknown[]) => GeneralActionResolver;
|
||||
CommandResolver?: new (...args: unknown[]) => unknown;
|
||||
actionContextBuilder?: ActionContextBuilder;
|
||||
}
|
||||
|
||||
@@ -35,7 +35,9 @@ export class ActionDefinition<
|
||||
if (typeof raw !== 'object' || raw === null) {
|
||||
return null;
|
||||
}
|
||||
const { buyRice, amount } = raw as any;
|
||||
const record = raw as Record<string, unknown>;
|
||||
const buyRice = record.buyRice;
|
||||
const amount = record.amount;
|
||||
if (typeof buyRice !== 'boolean' || typeof amount !== 'number') {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
export interface WarDexAux {
|
||||
isAttacker?: boolean;
|
||||
opposeType?: { armType: number };
|
||||
}
|
||||
|
||||
const isRecord = (value: unknown): value is Record<string, unknown> => value !== null && typeof value === 'object';
|
||||
|
||||
export const parseWarDexAux = (aux: unknown): WarDexAux => {
|
||||
if (!isRecord(aux)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const isAttacker = typeof aux.isAttacker === 'boolean' ? aux.isAttacker : undefined;
|
||||
const opposeRaw = aux.opposeType;
|
||||
|
||||
if (!isRecord(opposeRaw)) {
|
||||
return { isAttacker };
|
||||
}
|
||||
|
||||
const armType = opposeRaw.armType;
|
||||
if (typeof armType !== 'number') {
|
||||
return { isAttacker };
|
||||
}
|
||||
|
||||
return { isAttacker, opposeType: { armType } };
|
||||
};
|
||||
|
||||
export const getAuxArmType = (aux: unknown): number | undefined => {
|
||||
if (!isRecord(aux)) {
|
||||
return undefined;
|
||||
}
|
||||
const armType = aux.armType;
|
||||
return typeof armType === 'number' ? armType : undefined;
|
||||
};
|
||||
@@ -44,12 +44,12 @@ export const traitModule: TraitModule = {
|
||||
kind: 'war',
|
||||
getName: () => '견고',
|
||||
getInfo: () => '[전투] 상대 필살 확률 -20%p, 상대 계략 시도시 성공 확률 -10%p, 부상 없음, 아군 피해 -10%',
|
||||
onCalcOpposeStat: (_context, statName, value: any, _aux) => {
|
||||
if (statName === 'warMagicSuccessProb') {
|
||||
return (value as number) - 0.1;
|
||||
onCalcOpposeStat: (_context, statName, value, _aux) => {
|
||||
if (statName === 'warMagicSuccessProb' && typeof value === 'number') {
|
||||
return value - 0.1;
|
||||
}
|
||||
if (statName === 'warCriticalRatio') {
|
||||
return (value as number) - 0.2;
|
||||
if (statName === 'warCriticalRatio' && typeof value === 'number') {
|
||||
return value - 0.2;
|
||||
}
|
||||
return value;
|
||||
},
|
||||
|
||||
@@ -4,6 +4,7 @@ import type { WarActionContext } from '@sammo-ts/logic/war/actions.js';
|
||||
import type { TraitModule } from '@sammo-ts/logic/triggers/special/types.js';
|
||||
import { getMetaNumber } from '@sammo-ts/logic/war/utils.js';
|
||||
import { WarUnitCity } from '@sammo-ts/logic/war/units.js';
|
||||
import { getAuxArmType, parseWarDexAux } from './aux.js';
|
||||
|
||||
function onCalcStat(context: GeneralActionContext, statName: GeneralStatName, value: number, aux?: unknown): number;
|
||||
function onCalcStat(
|
||||
@@ -30,8 +31,7 @@ function onCalcStat(
|
||||
|
||||
if (statName.startsWith('dex')) {
|
||||
const myDex = getMetaNumber(context.general.meta, `dex${siegeType}`);
|
||||
const isAttacker = (aux as any)?.isAttacker;
|
||||
const opposeType = (aux as any)?.opposeType;
|
||||
const { isAttacker, opposeType } = parseWarDexAux(aux);
|
||||
|
||||
if (isAttacker && opposeType && statName === `dex${opposeType.armType}`) {
|
||||
return (value as number) + myDex;
|
||||
@@ -54,10 +54,9 @@ export const traitModule: TraitModule = {
|
||||
'[군사] 차병 계통 징·모병비 -10%<br>[전투] 성벽 공격 시 대미지 +100%,<br>공격시 상대 병종에/수비시 자신 병종 숙련에 차병 숙련을 가산',
|
||||
onCalcDomestic: (_context, turnType, varType, value, aux) => {
|
||||
if (turnType === '징병' || turnType === '모병') {
|
||||
if (varType === 'cost' && aux && typeof aux === 'object' && 'armType' in aux) {
|
||||
if ((aux as any).armType === 4) {
|
||||
return value * 0.9;
|
||||
}
|
||||
const armType = getAuxArmType(aux);
|
||||
if (varType === 'cost' && armType === 4) {
|
||||
return value * 0.9;
|
||||
}
|
||||
}
|
||||
return value;
|
||||
|
||||
@@ -3,6 +3,7 @@ import type { GeneralStatName, WarStatName } from '@sammo-ts/logic/triggers/type
|
||||
import type { WarActionContext } from '@sammo-ts/logic/war/actions.js';
|
||||
import type { TraitModule } from '@sammo-ts/logic/triggers/special/types.js';
|
||||
import { getMetaNumber } from '@sammo-ts/logic/war/utils.js';
|
||||
import { getAuxArmType, parseWarDexAux } from './aux.js';
|
||||
|
||||
function onCalcStat(context: GeneralActionContext, statName: GeneralStatName, value: number, aux?: unknown): number;
|
||||
function onCalcStat(
|
||||
@@ -33,8 +34,7 @@ function onCalcStat(
|
||||
|
||||
if (statName.startsWith('dex')) {
|
||||
const myDex = getMetaNumber(context.general.meta, `dex${archerType}`);
|
||||
const isAttacker = (aux as any)?.isAttacker;
|
||||
const opposeType = (aux as any)?.opposeType;
|
||||
const { isAttacker, opposeType } = parseWarDexAux(aux);
|
||||
|
||||
if (isAttacker && opposeType && statName === `dex${opposeType.armType}`) {
|
||||
return (value as number) + myDex;
|
||||
@@ -57,12 +57,11 @@ export const traitModule: TraitModule = {
|
||||
'[군사] 궁병 계통 징·모병비 -10%<br>[전투] 회피 확률 +20%p,<br>공격시 상대 병종에/수비시 자신 병종 숙련에 궁병 숙련을 가산',
|
||||
onCalcDomestic: (_context, turnType, varType, value, aux) => {
|
||||
if (turnType === '징병' || turnType === '모병') {
|
||||
if (varType === 'cost' && aux && typeof aux === 'object' && 'armType' in aux) {
|
||||
// Note: In a real scenario, we should check if aux.armType is archer.
|
||||
// Since we don't have easy access to config here, we might need to assume legacy ID 2 or similar.
|
||||
if ((aux as any).armType === 2) {
|
||||
return value * 0.9;
|
||||
}
|
||||
const armType = getAuxArmType(aux);
|
||||
// Note: In a real scenario, we should check if aux.armType is archer.
|
||||
// Since we don't have easy access to config here, we might need to assume legacy ID 2 or similar.
|
||||
if (varType === 'cost' && armType === 2) {
|
||||
return value * 0.9;
|
||||
}
|
||||
}
|
||||
return value;
|
||||
|
||||
@@ -3,6 +3,7 @@ import type { GeneralStatName, WarStatName } from '@sammo-ts/logic/triggers/type
|
||||
import type { WarActionContext } from '@sammo-ts/logic/war/actions.js';
|
||||
import type { TraitModule } from '@sammo-ts/logic/triggers/special/types.js';
|
||||
import { getMetaNumber } from '@sammo-ts/logic/war/utils.js';
|
||||
import { getAuxArmType, parseWarDexAux } from './aux.js';
|
||||
|
||||
function onCalcStat(context: GeneralActionContext, statName: GeneralStatName, value: number, aux?: unknown): number;
|
||||
function onCalcStat(
|
||||
@@ -29,8 +30,7 @@ function onCalcStat(
|
||||
|
||||
if (statName.startsWith('dex')) {
|
||||
const myDex = getMetaNumber(context.general.meta, `dex${cavalryType}`);
|
||||
const isAttacker = (aux as any)?.isAttacker;
|
||||
const opposeType = (aux as any)?.opposeType;
|
||||
const { isAttacker, opposeType } = parseWarDexAux(aux);
|
||||
|
||||
if (isAttacker && opposeType && statName === `dex${opposeType.armType}`) {
|
||||
return (value as number) + myDex;
|
||||
@@ -53,10 +53,9 @@ export const traitModule: TraitModule = {
|
||||
'[군사] 기병 계통 징·모병비 -10%<br>[전투] 수비 시 대미지 +10%, 공격 시 대미지 +20%,<br>공격시 상대 병종에/수비시 자신 병종 숙련에 기병 숙련을 가산',
|
||||
onCalcDomestic: (_context, turnType, varType, value, aux) => {
|
||||
if (turnType === '징병' || turnType === '모병') {
|
||||
if (varType === 'cost' && aux && typeof aux === 'object' && 'armType' in aux) {
|
||||
if ((aux as any).armType === 3) {
|
||||
return value * 0.9;
|
||||
}
|
||||
const armType = getAuxArmType(aux);
|
||||
if (varType === 'cost' && armType === 3) {
|
||||
return value * 0.9;
|
||||
}
|
||||
}
|
||||
return value;
|
||||
|
||||
@@ -4,6 +4,11 @@ import type { WarActionContext } from '@sammo-ts/logic/war/actions.js';
|
||||
import type { TraitModule } from '@sammo-ts/logic/triggers/special/types.js';
|
||||
import { getMetaNumber } from '@sammo-ts/logic/war/utils.js';
|
||||
|
||||
type WarUnitWithGeneral = WarUnit & { getGeneral: () => { meta: Record<string, unknown> } };
|
||||
|
||||
const hasGeneral = (unit: WarUnit): unit is WarUnitWithGeneral =>
|
||||
'getGeneral' in unit && typeof (unit as { getGeneral?: unknown }).getGeneral === 'function';
|
||||
|
||||
function onCalcStat(context: GeneralActionContext, statName: GeneralStatName, value: number, aux?: unknown): number;
|
||||
function onCalcStat(
|
||||
context: WarActionContext,
|
||||
@@ -17,8 +22,10 @@ function onCalcStat(
|
||||
value: number | [number, number],
|
||||
aux?: unknown
|
||||
): number | [number, number] {
|
||||
if (statName === 'warCriticalRatio' && (aux as any)?.isAttacker) {
|
||||
return (value as number) + 0.1;
|
||||
const isAttacker = typeof aux === 'object' && aux !== null && (aux as { isAttacker?: unknown }).isAttacker === true;
|
||||
|
||||
if (statName === 'warCriticalRatio' && isAttacker && typeof value === 'number') {
|
||||
return value + 0.1;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
@@ -36,8 +43,8 @@ export const traitModule: TraitModule = {
|
||||
let defenceMultiplier = 0.98;
|
||||
// Note: unit.getGeneral() is only available for WarUnitGeneral.
|
||||
// In a real scenario, we should check if unit is WarUnitGeneral.
|
||||
if ('getGeneral' in unit) {
|
||||
const killnum = getMetaNumber((unit as any).getGeneral().meta, 'rank_killnum', 0);
|
||||
if (hasGeneral(unit)) {
|
||||
const killnum = getMetaNumber(unit.getGeneral().meta, 'rank_killnum', 0);
|
||||
const logVal = Math.log2(Math.max(1, killnum / 5));
|
||||
attackMultiplier += logVal / 20;
|
||||
defenceMultiplier -= logVal / 50;
|
||||
|
||||
@@ -99,9 +99,9 @@ export const traitModule: TraitModule = {
|
||||
getName: () => '반계',
|
||||
getInfo: () =>
|
||||
'[전투] 상대의 계략 성공 확률 -10%p, 상대의 계략을 40% 확률로 되돌림, 반목 성공시 대미지 추가(+60% → +150%)',
|
||||
onCalcOpposeStat: (_context, statName, value: any, _aux) => {
|
||||
if (statName === 'warMagicSuccessProb') {
|
||||
return (value as number) - 0.1;
|
||||
onCalcOpposeStat: (_context, statName, value, _aux) => {
|
||||
if (statName === 'warMagicSuccessProb' && typeof value === 'number') {
|
||||
return value - 0.1;
|
||||
}
|
||||
return value;
|
||||
},
|
||||
|
||||
@@ -3,6 +3,7 @@ import type { GeneralStatName, WarStatName } from '@sammo-ts/logic/triggers/type
|
||||
import type { WarActionContext } from '@sammo-ts/logic/war/actions.js';
|
||||
import type { TraitModule } from '@sammo-ts/logic/triggers/special/types.js';
|
||||
import { getMetaNumber } from '@sammo-ts/logic/war/utils.js';
|
||||
import { getAuxArmType, parseWarDexAux } from './aux.js';
|
||||
|
||||
function onCalcStat(context: GeneralActionContext, statName: GeneralStatName, value: number, aux?: unknown): number;
|
||||
function onCalcStat(
|
||||
@@ -29,8 +30,7 @@ function onCalcStat(
|
||||
|
||||
if (statName.startsWith('dex')) {
|
||||
const myDex = getMetaNumber(context.general.meta, `dex${footmanType}`);
|
||||
const isAttacker = (aux as any)?.isAttacker;
|
||||
const opposeType = (aux as any)?.opposeType;
|
||||
const { isAttacker, opposeType } = parseWarDexAux(aux);
|
||||
|
||||
if (isAttacker && opposeType && statName === `dex${opposeType.armType}`) {
|
||||
return (value as number) + myDex;
|
||||
@@ -53,12 +53,11 @@ export const traitModule: TraitModule = {
|
||||
'[군사] 보병 계통 징·모병비 -10%<br>[전투] 공격 시 아군 피해 -10%, 수비 시 아군 피해 -20%,<br>공격시 상대 병종에/수비시 자신 병종 숙련에 보병 숙련을 가산',
|
||||
onCalcDomestic: (_context, turnType, varType, value, aux) => {
|
||||
if (turnType === '징병' || turnType === '모병') {
|
||||
if (varType === 'cost' && aux && typeof aux === 'object' && 'armType' in aux) {
|
||||
// Note: In a real scenario, we should check if aux.armType is footman.
|
||||
// Since we don't have easy access to config here, we might need to assume legacy ID 1 or similar.
|
||||
if ((aux as any).armType === 1) {
|
||||
return value * 0.9;
|
||||
}
|
||||
const armType = getAuxArmType(aux);
|
||||
// Note: In a real scenario, we should check if aux.armType is footman.
|
||||
// Since we don't have easy access to config here, we might need to assume legacy ID 1 or similar.
|
||||
if (varType === 'cost' && armType === 1) {
|
||||
return value * 0.9;
|
||||
}
|
||||
}
|
||||
return value;
|
||||
|
||||
@@ -6,6 +6,11 @@ import { BaseWarUnitTrigger, WarTriggerCaller } from '@sammo-ts/logic/war/trigge
|
||||
import type { WarUnit } from '@sammo-ts/logic/war/units.js';
|
||||
import { LogFormat } from '@sammo-ts/logic/logging/types.js';
|
||||
|
||||
type AtmosUnit = WarUnit & { addAtmos: (amount: number) => void };
|
||||
|
||||
const canAddAtmos = (unit: WarUnit): unit is AtmosUnit =>
|
||||
'addAtmos' in unit && typeof (unit as { addAtmos?: unknown }).addAtmos === 'function';
|
||||
|
||||
class che_위압시도 extends BaseWarUnitTrigger {
|
||||
constructor(unit: WarUnit) {
|
||||
super(unit, 10100);
|
||||
@@ -48,8 +53,8 @@ class che_위압발동 extends BaseWarUnitTrigger {
|
||||
oppose.getLogger().pushGeneralBattleDetailLog('상대에게 <R>위압</>받았다!', LogFormat.PLAIN);
|
||||
self.getLogger().pushGeneralBattleDetailLog('상대에게 <C>위압</>을 줬다!', LogFormat.PLAIN);
|
||||
oppose.setWarPowerMultiply(0);
|
||||
if ('addAtmos' in oppose) {
|
||||
(oppose as any).addAtmos(-5);
|
||||
if (canAddAtmos(oppose)) {
|
||||
oppose.addAtmos(-5);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
@@ -6,6 +6,11 @@ import { BaseWarUnitTrigger, WarTriggerCaller } from '@sammo-ts/logic/war/trigge
|
||||
import type { WarUnit } from '@sammo-ts/logic/war/units.js';
|
||||
import { LogFormat } from '@sammo-ts/logic/logging/types.js';
|
||||
|
||||
type AtmosUnit = WarUnit & { addAtmos: (amount: number) => void };
|
||||
|
||||
const canAddAtmos = (unit: WarUnit): unit is AtmosUnit =>
|
||||
'addAtmos' in unit && typeof (unit as { addAtmos?: unknown }).addAtmos === 'function';
|
||||
|
||||
class che_저격시도 extends BaseWarUnitTrigger {
|
||||
private readonly ratio: number;
|
||||
private readonly woundMin: number;
|
||||
@@ -86,8 +91,8 @@ class che_저격발동 extends BaseWarUnitTrigger {
|
||||
self.getLogger().pushGeneralBattleDetailLog('성벽 수비대장을 <C>저격</>했다!', LogFormat.PLAIN);
|
||||
}
|
||||
|
||||
if ('addAtmos' in self) {
|
||||
(self as any).addAtmos(selfEnv['addAtmos'] as number);
|
||||
if (canAddAtmos(self)) {
|
||||
self.addAtmos(selfEnv['addAtmos'] as number);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
@@ -3,6 +3,7 @@ import { describe, expect, it } from 'vitest';
|
||||
import type { City, General, Nation } from '../src/domain/entities.js';
|
||||
import { resolveGeneralAction } from '../src/actions/engine.js';
|
||||
import { ActionDefinition } from '../src/actions/turn/general/che_출병.js';
|
||||
import type { DispatchResolveContext } from '../src/actions/turn/general/che_출병.js';
|
||||
import type { TurnSchedule } from '../src/turn/calendar.js';
|
||||
import type { WarAftermathConfig, WarEngineConfig } from '../src/war/types.js';
|
||||
import type { UnitSetDefinition } from '../src/world/types.js';
|
||||
@@ -184,28 +185,29 @@ describe('che_출병', () => {
|
||||
const defender = buildGeneral(2, defenderNation.id, defenderCity.id);
|
||||
|
||||
const definition = new ActionDefinition();
|
||||
const context: Omit<DispatchResolveContext, 'addLog'> = {
|
||||
general: attacker,
|
||||
city: attackerCity,
|
||||
nation: attackerNation,
|
||||
rng,
|
||||
destCity: defenderCity,
|
||||
destNation: defenderNation,
|
||||
cities: [attackerCity, defenderCity],
|
||||
nations: [attackerNation, defenderNation],
|
||||
generals: [attacker, defender],
|
||||
unitSet,
|
||||
time: {
|
||||
year: 200,
|
||||
month: 1,
|
||||
startYear: 180,
|
||||
},
|
||||
seedBase: 'test-seed',
|
||||
warConfig,
|
||||
aftermathConfig,
|
||||
};
|
||||
const resolution = resolveGeneralAction(
|
||||
definition,
|
||||
{
|
||||
general: attacker,
|
||||
city: attackerCity,
|
||||
nation: attackerNation,
|
||||
rng,
|
||||
destCity: defenderCity,
|
||||
destNation: defenderNation,
|
||||
cities: [attackerCity, defenderCity],
|
||||
nations: [attackerNation, defenderNation],
|
||||
generals: [attacker, defender],
|
||||
unitSet,
|
||||
time: {
|
||||
year: 200,
|
||||
month: 1,
|
||||
startYear: 180,
|
||||
},
|
||||
seedBase: 'test-seed',
|
||||
warConfig,
|
||||
aftermathConfig,
|
||||
} as any,
|
||||
context,
|
||||
{
|
||||
now: new Date('2000-01-01T00:00:00Z'),
|
||||
schedule,
|
||||
|
||||
Reference in New Issue
Block a user