refactor: improve war module type safety and reserved turns test data handling
This commit is contained in:
@@ -17,14 +17,10 @@ const buildDb = () => {
|
|||||||
type GeneralTurnFindManyArgs = Parameters<DatabaseClient['generalTurn']['findMany']>[0];
|
type GeneralTurnFindManyArgs = Parameters<DatabaseClient['generalTurn']['findMany']>[0];
|
||||||
type GeneralTurnDeleteManyArgs = NonNullable<Parameters<DatabaseClient['generalTurn']['deleteMany']>[0]>;
|
type GeneralTurnDeleteManyArgs = NonNullable<Parameters<DatabaseClient['generalTurn']['deleteMany']>[0]>;
|
||||||
type GeneralTurnCreateManyArgs = NonNullable<Parameters<DatabaseClient['generalTurn']['createMany']>[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 NationTurnFindManyArgs = Parameters<DatabaseClient['nationTurn']['findMany']>[0];
|
||||||
type NationTurnDeleteManyArgs = NonNullable<Parameters<DatabaseClient['nationTurn']['deleteMany']>[0]>;
|
type NationTurnDeleteManyArgs = NonNullable<Parameters<DatabaseClient['nationTurn']['deleteMany']>[0]>;
|
||||||
type NationTurnCreateManyArgs = NonNullable<Parameters<DatabaseClient['nationTurn']['createMany']>[0]>;
|
type NationTurnCreateManyArgs = NonNullable<Parameters<DatabaseClient['nationTurn']['createMany']>[0]>;
|
||||||
type NationTurnCreateManyData = NationTurnCreateManyArgs['data'];
|
|
||||||
type NationTurnCreateManyRow = NationTurnCreateManyData extends Array<infer Row> ? Row : never;
|
|
||||||
|
|
||||||
const db = {
|
const db = {
|
||||||
worldState: {
|
worldState: {
|
||||||
@@ -45,20 +41,23 @@ const buildDb = () => {
|
|||||||
return generalId !== undefined ? (generalTurns.get(generalId) ?? []) : [];
|
return generalId !== undefined ? (generalTurns.get(generalId) ?? []) : [];
|
||||||
},
|
},
|
||||||
deleteMany: async ({ where }: GeneralTurnDeleteManyArgs) => {
|
deleteMany: async ({ where }: GeneralTurnDeleteManyArgs) => {
|
||||||
if (typeof where.generalId === 'number') {
|
if (where && typeof where.generalId === 'number') {
|
||||||
generalTurns.delete(where.generalId);
|
generalTurns.delete(where.generalId);
|
||||||
}
|
}
|
||||||
return {};
|
return {};
|
||||||
},
|
},
|
||||||
createMany: async ({ data }: GeneralTurnCreateManyArgs) => {
|
createMany: async ({ data }: GeneralTurnCreateManyArgs) => {
|
||||||
const rows = data.map((row: GeneralTurnCreateManyRow, index: number) => ({
|
const dataList = (Array.isArray(data) ? data : [data]) as Record<string, unknown>[];
|
||||||
|
const rows: GeneralTurnRow[] = dataList.map((row, index: number) => ({
|
||||||
id: index + 1,
|
id: index + 1,
|
||||||
generalId: row.generalId,
|
generalId: row.generalId as number,
|
||||||
turnIdx: row.turnIdx,
|
turnIdx: row.turnIdx as number,
|
||||||
actionCode: row.actionCode,
|
actionCode: row.actionCode as string,
|
||||||
arg: row.arg,
|
arg: row.arg as unknown as GeneralTurnRow['arg'],
|
||||||
|
createdAt: new Date(),
|
||||||
}));
|
}));
|
||||||
const generalId = data[0]?.generalId;
|
const firstRow = dataList[0];
|
||||||
|
const generalId = firstRow?.generalId as number | undefined;
|
||||||
if (generalId !== undefined) {
|
if (generalId !== undefined) {
|
||||||
generalTurns.set(generalId, rows);
|
generalTurns.set(generalId, rows);
|
||||||
}
|
}
|
||||||
@@ -76,22 +75,25 @@ const buildDb = () => {
|
|||||||
return nationTurns.get(`${nationId}:${officerLevel}`) ?? [];
|
return nationTurns.get(`${nationId}:${officerLevel}`) ?? [];
|
||||||
},
|
},
|
||||||
deleteMany: async ({ where }: NationTurnDeleteManyArgs) => {
|
deleteMany: async ({ where }: NationTurnDeleteManyArgs) => {
|
||||||
if (typeof where.nationId === 'number' && typeof where.officerLevel === 'number') {
|
if (where && typeof where.nationId === 'number' && typeof where.officerLevel === 'number') {
|
||||||
nationTurns.delete(`${where.nationId}:${where.officerLevel}`);
|
nationTurns.delete(`${where.nationId}:${where.officerLevel}`);
|
||||||
}
|
}
|
||||||
return {};
|
return {};
|
||||||
},
|
},
|
||||||
createMany: async ({ data }: NationTurnCreateManyArgs) => {
|
createMany: async ({ data }: NationTurnCreateManyArgs) => {
|
||||||
const rows = data.map((row: NationTurnCreateManyRow, index: number) => ({
|
const dataList = (Array.isArray(data) ? data : [data]) as Record<string, unknown>[];
|
||||||
|
const rows: NationTurnRow[] = dataList.map((row, index: number) => ({
|
||||||
id: index + 1,
|
id: index + 1,
|
||||||
nationId: row.nationId,
|
nationId: row.nationId as number,
|
||||||
officerLevel: row.officerLevel,
|
officerLevel: row.officerLevel as number,
|
||||||
turnIdx: row.turnIdx,
|
turnIdx: row.turnIdx as number,
|
||||||
actionCode: row.actionCode,
|
actionCode: row.actionCode as string,
|
||||||
arg: row.arg,
|
arg: row.arg as unknown as NationTurnRow['arg'],
|
||||||
|
createdAt: new Date(),
|
||||||
}));
|
}));
|
||||||
const nationId = data[0]?.nationId;
|
const firstRow = dataList[0];
|
||||||
const officerLevel = data[0]?.officerLevel;
|
const nationId = firstRow?.nationId as number | undefined;
|
||||||
|
const officerLevel = firstRow?.officerLevel as number | undefined;
|
||||||
if (nationId !== undefined && officerLevel !== undefined) {
|
if (nationId !== undefined && officerLevel !== undefined) {
|
||||||
nationTurns.set(`${nationId}:${officerLevel}`, rows);
|
nationTurns.set(`${nationId}:${officerLevel}`, rows);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,6 +60,7 @@ const handleLogout = async () => {
|
|||||||
<div class="max-w-5xl mx-auto py-8 px-4 space-y-8">
|
<div class="max-w-5xl mx-auto py-8 px-4 space-y-8">
|
||||||
<!-- Notice -->
|
<!-- Notice -->
|
||||||
<div v-if="notice" class="text-center">
|
<div v-if="notice" class="text-center">
|
||||||
|
<!-- eslint-disable-next-line vue/no-v-html -->
|
||||||
<span class="text-orange-500 text-3xl font-bold" v-html="notice"></span>
|
<span class="text-orange-500 text-3xl font-bold" v-html="notice"></span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -91,15 +92,15 @@ const handleLogout = async () => {
|
|||||||
:style="{ color: profile.color }"
|
:style="{ color: profile.color }"
|
||||||
class="text-lg font-bold cursor-help"
|
class="text-lg font-bold cursor-help"
|
||||||
:title="
|
:title="
|
||||||
profileDetails[profile.profileName]
|
profileDetails[profile.profileName]?.starttime
|
||||||
? `시작일: ${profileDetails[profile.profileName].starttime}`
|
? `시작일: ${profileDetails[profile.profileName]?.starttime}`
|
||||||
: ''
|
: ''
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
{{ profile.korName }}섭
|
{{ profile.korName }}섭
|
||||||
</div>
|
</div>
|
||||||
<div v-if="profileDetails[profile.profileName]" class="text-xs text-zinc-500 mt-1">
|
<div v-if="profileDetails[profile.profileName]" class="text-xs text-zinc-500 mt-1">
|
||||||
<{{ profileDetails[profile.profileName].nationCnt }}국 경쟁중>
|
<{{ profileDetails[profile.profileName]?.nationCnt }}국 경쟁중>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
@@ -108,25 +109,25 @@ const handleLogout = async () => {
|
|||||||
<template v-if="profileDetails[profile.profileName]">
|
<template v-if="profileDetails[profile.profileName]">
|
||||||
<div class="space-y-1">
|
<div class="space-y-1">
|
||||||
<div>
|
<div>
|
||||||
서기 {{ profileDetails[profile.profileName].year }}년
|
서기 {{ profileDetails[profile.profileName]?.year }}년
|
||||||
{{ profileDetails[profile.profileName].month }}월 (<span
|
{{ profileDetails[profile.profileName]?.month }}월 (<span
|
||||||
class="text-orange-400"
|
class="text-orange-400"
|
||||||
>{{ profile.scenario }}</span
|
>{{ profile.scenario }}</span
|
||||||
>)
|
>)
|
||||||
</div>
|
</div>
|
||||||
<div class="text-zinc-400">
|
<div class="text-zinc-400">
|
||||||
유저 : {{ profileDetails[profile.profileName].userCnt }} /
|
유저 : {{ profileDetails[profile.profileName]?.userCnt }} /
|
||||||
{{ profileDetails[profile.profileName].maxUserCnt }}명
|
{{ profileDetails[profile.profileName]?.maxUserCnt }}명
|
||||||
<span class="text-cyan-400 ml-2"
|
<span class="text-cyan-400 ml-2"
|
||||||
>NPC : {{ profileDetails[profile.profileName].npcCnt }}명</span
|
>NPC : {{ profileDetails[profile.profileName]?.npcCnt }}명</span
|
||||||
>
|
>
|
||||||
<span class="text-green-400 ml-2"
|
<span class="text-green-400 ml-2"
|
||||||
>({{ profileDetails[profile.profileName].turnTerm }}분 턴 서버)</span
|
>({{ profileDetails[profile.profileName]?.turnTerm }}분 턴 서버)</span
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
<div class="text-xs text-zinc-500">
|
<div class="text-xs text-zinc-500">
|
||||||
(상성 설정:{{ profileDetails[profile.profileName].fictionMode }}), (기타
|
(상성 설정:{{ profileDetails[profile.profileName]?.fictionMode }}), (기타
|
||||||
설정:{{ profileDetails[profile.profileName].otherTextInfo }})
|
설정:{{ profileDetails[profile.profileName]?.otherTextInfo }})
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -145,14 +146,14 @@ const handleLogout = async () => {
|
|||||||
class="w-12 h-12 mx-auto bg-zinc-800 rounded overflow-hidden border border-zinc-700"
|
class="w-12 h-12 mx-auto bg-zinc-800 rounded overflow-hidden border border-zinc-700"
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
:src="profileDetails[profile.profileName].myGeneral.picture"
|
:src="profileDetails[profile.profileName]?.myGeneral?.picture ?? undefined"
|
||||||
class="w-full h-full object-cover"
|
class="w-full h-full object-cover"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="px-4 py-4 border-r border-zinc-800 text-center">
|
<td class="px-4 py-4 border-r border-zinc-800 text-center">
|
||||||
<div v-if="profileDetails[profile.profileName]?.myGeneral" class="font-medium">
|
<div v-if="profileDetails[profile.profileName]?.myGeneral" class="font-medium">
|
||||||
{{ profileDetails[profile.profileName].myGeneral.name }}
|
{{ profileDetails[profile.profileName]?.myGeneral?.name }}
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="text-zinc-600">- 미 등 록 -</div>
|
<div v-else class="text-zinc-600">- 미 등 록 -</div>
|
||||||
</td>
|
</td>
|
||||||
@@ -161,7 +162,7 @@ const handleLogout = async () => {
|
|||||||
<td class="px-4 py-4 text-center">
|
<td class="px-4 py-4 text-center">
|
||||||
<template v-if="profileDetails[profile.profileName]">
|
<template v-if="profileDetails[profile.profileName]">
|
||||||
<button
|
<button
|
||||||
v-if="profileDetails[profile.profileName].myGeneral"
|
v-if="profileDetails[profile.profileName]?.myGeneral"
|
||||||
class="w-full bg-zinc-700 hover:bg-zinc-600 text-white py-1.5 rounded text-sm transition-colors"
|
class="w-full bg-zinc-700 hover:bg-zinc-600 text-white py-1.5 rounded text-sm transition-colors"
|
||||||
>
|
>
|
||||||
입장
|
입장
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import type { GeneralActionDefinition } from '@sammo-ts/logic/actions/definition.js';
|
import type { GeneralActionDefinition } from '@sammo-ts/logic/actions/definition.js';
|
||||||
import type { GeneralActionResolver } from '@sammo-ts/logic/actions/engine.js';
|
|
||||||
import type { ActionContextBuilder } from './actionContext.js';
|
import type { ActionContextBuilder } from './actionContext.js';
|
||||||
import type { TurnCommandEnv } from './commandEnv.js';
|
import type { TurnCommandEnv } from './commandEnv.js';
|
||||||
|
|
||||||
@@ -13,8 +12,5 @@ export interface TurnCommandSpecBase<TKey extends string = string> {
|
|||||||
|
|
||||||
export interface TurnCommandModule<TSpec extends TurnCommandSpecBase = TurnCommandSpecBase> {
|
export interface TurnCommandModule<TSpec extends TurnCommandSpecBase = TurnCommandSpecBase> {
|
||||||
commandSpec: TSpec;
|
commandSpec: TSpec;
|
||||||
ActionDefinition: new (...args: unknown[]) => GeneralActionDefinition;
|
|
||||||
ActionResolver?: new (...args: unknown[]) => GeneralActionResolver;
|
|
||||||
CommandResolver?: new (...args: unknown[]) => unknown;
|
|
||||||
actionContextBuilder?: ActionContextBuilder;
|
actionContextBuilder?: ActionContextBuilder;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,54 +12,62 @@ import type {
|
|||||||
} from './types.js';
|
} from './types.js';
|
||||||
|
|
||||||
export interface GeneralActionModule<TriggerState extends GeneralTriggerState = GeneralTriggerState> {
|
export interface GeneralActionModule<TriggerState extends GeneralTriggerState = GeneralTriggerState> {
|
||||||
getName?(): string;
|
getName?: (() => string) | undefined;
|
||||||
getInfo?(): string;
|
getInfo?: (() => string) | undefined;
|
||||||
|
|
||||||
getPreTurnExecuteTriggerList?(
|
getPreTurnExecuteTriggerList?:
|
||||||
context: GeneralActionContext<TriggerState>
|
| ((context: GeneralActionContext<TriggerState>) => GeneralTriggerCaller<TriggerState> | null)
|
||||||
): GeneralTriggerCaller<TriggerState> | null;
|
| undefined;
|
||||||
|
|
||||||
onCalcDomestic?(
|
onCalcDomestic?:
|
||||||
context: GeneralActionContext<TriggerState>,
|
| ((
|
||||||
turnType: TriggerDomesticActionType,
|
context: GeneralActionContext<TriggerState>,
|
||||||
varType: TriggerDomesticVarType,
|
turnType: TriggerDomesticActionType,
|
||||||
value: number,
|
varType: TriggerDomesticVarType,
|
||||||
aux?: unknown
|
value: number,
|
||||||
): number;
|
aux?: unknown
|
||||||
|
) => number)
|
||||||
|
| undefined;
|
||||||
|
|
||||||
onCalcStat?(
|
onCalcStat?:
|
||||||
context: GeneralActionContext<TriggerState>,
|
| ((
|
||||||
statName: GeneralStatName,
|
context: GeneralActionContext<TriggerState>,
|
||||||
value: number,
|
statName: GeneralStatName,
|
||||||
aux?: unknown
|
value: number,
|
||||||
): number;
|
aux?: unknown
|
||||||
|
) => number)
|
||||||
|
| undefined;
|
||||||
|
|
||||||
onCalcOpposeStat?(
|
onCalcOpposeStat?:
|
||||||
context: GeneralActionContext<TriggerState>,
|
| ((
|
||||||
statName: GeneralStatName,
|
context: GeneralActionContext<TriggerState>,
|
||||||
value: number,
|
statName: GeneralStatName,
|
||||||
aux?: unknown
|
value: number,
|
||||||
): number;
|
aux?: unknown
|
||||||
|
) => number)
|
||||||
|
| undefined;
|
||||||
|
|
||||||
onCalcStrategic?(
|
onCalcStrategic?:
|
||||||
context: GeneralActionContext<TriggerState>,
|
| ((
|
||||||
turnType: TriggerStrategicActionType,
|
context: GeneralActionContext<TriggerState>,
|
||||||
varType: TriggerStrategicVarType,
|
turnType: TriggerStrategicActionType,
|
||||||
value: number
|
varType: TriggerStrategicVarType,
|
||||||
): number;
|
value: number
|
||||||
|
) => number)
|
||||||
|
| undefined;
|
||||||
|
|
||||||
onCalcNationalIncome?(
|
onCalcNationalIncome?:
|
||||||
context: GeneralActionContext<TriggerState>,
|
| ((context: GeneralActionContext<TriggerState>, type: TriggerNationalIncomeType, amount: number) => number)
|
||||||
type: TriggerNationalIncomeType,
|
| undefined;
|
||||||
amount: number
|
|
||||||
): number;
|
|
||||||
|
|
||||||
onArbitraryAction?(
|
onArbitraryAction?:
|
||||||
context: GeneralActionContext<TriggerState>,
|
| ((
|
||||||
actionType: TriggerActionType,
|
context: GeneralActionContext<TriggerState>,
|
||||||
phase?: TriggerActionPhase | null,
|
actionType: TriggerActionType,
|
||||||
aux?: Record<string, unknown> | null
|
phase?: TriggerActionPhase | null,
|
||||||
): Record<string, unknown> | null;
|
aux?: Record<string, unknown> | null
|
||||||
|
) => Record<string, unknown> | null)
|
||||||
|
| undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class GeneralActionPipeline<TriggerState extends GeneralTriggerState = GeneralTriggerState> {
|
export class GeneralActionPipeline<TriggerState extends GeneralTriggerState = GeneralTriggerState> {
|
||||||
|
|||||||
@@ -14,15 +14,16 @@ export const parseWarDexAux = (aux: unknown): WarDexAux => {
|
|||||||
const opposeRaw = aux.opposeType;
|
const opposeRaw = aux.opposeType;
|
||||||
|
|
||||||
if (!isRecord(opposeRaw)) {
|
if (!isRecord(opposeRaw)) {
|
||||||
return { isAttacker };
|
return isAttacker === undefined ? {} : { isAttacker };
|
||||||
}
|
}
|
||||||
|
|
||||||
const armType = opposeRaw.armType;
|
const armType = opposeRaw.armType;
|
||||||
if (typeof armType !== 'number') {
|
if (typeof armType !== 'number') {
|
||||||
return { isAttacker };
|
return isAttacker === undefined ? {} : { isAttacker };
|
||||||
}
|
}
|
||||||
|
|
||||||
return { isAttacker, opposeType: { armType } };
|
const opposeType = { armType };
|
||||||
|
return isAttacker === undefined ? { opposeType } : { isAttacker, opposeType };
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getAuxArmType = (aux: unknown): number | undefined => {
|
export const getAuxArmType = (aux: unknown): number | undefined => {
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ export const traitModule: TraitModule = {
|
|||||||
kind: 'war',
|
kind: 'war',
|
||||||
getName: () => '견고',
|
getName: () => '견고',
|
||||||
getInfo: () => '[전투] 상대 필살 확률 -20%p, 상대 계략 시도시 성공 확률 -10%p, 부상 없음, 아군 피해 -10%',
|
getInfo: () => '[전투] 상대 필살 확률 -20%p, 상대 계략 시도시 성공 확률 -10%p, 부상 없음, 아군 피해 -10%',
|
||||||
onCalcOpposeStat: (_context, statName, value, _aux) => {
|
onCalcOpposeStat: ((_context, statName, value, _aux) => {
|
||||||
if (statName === 'warMagicSuccessProb' && typeof value === 'number') {
|
if (statName === 'warMagicSuccessProb' && typeof value === 'number') {
|
||||||
return value - 0.1;
|
return value - 0.1;
|
||||||
}
|
}
|
||||||
@@ -52,7 +52,7 @@ export const traitModule: TraitModule = {
|
|||||||
return value - 0.2;
|
return value - 0.2;
|
||||||
}
|
}
|
||||||
return value;
|
return value;
|
||||||
},
|
}) as TraitModule['onCalcOpposeStat'],
|
||||||
getBattleInitTriggerList: (_context) => {
|
getBattleInitTriggerList: (_context) => {
|
||||||
if (!_context.unit) return null;
|
if (!_context.unit) return null;
|
||||||
return new WarTriggerCaller(new che_부상무효(_context.unit));
|
return new WarTriggerCaller(new che_부상무효(_context.unit));
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import type { WarActionContext } from '@sammo-ts/logic/war/actions.js';
|
|||||||
import type { TraitModule } from '@sammo-ts/logic/triggers/special/types.js';
|
import type { TraitModule } from '@sammo-ts/logic/triggers/special/types.js';
|
||||||
import { getMetaNumber } from '@sammo-ts/logic/war/utils.js';
|
import { getMetaNumber } from '@sammo-ts/logic/war/utils.js';
|
||||||
|
|
||||||
|
import { WarUnit } from '@sammo-ts/logic/war/units.js';
|
||||||
|
|
||||||
type WarUnitWithGeneral = WarUnit & { getGeneral: () => { meta: Record<string, unknown> } };
|
type WarUnitWithGeneral = WarUnit & { getGeneral: () => { meta: Record<string, unknown> } };
|
||||||
|
|
||||||
const hasGeneral = (unit: WarUnit): unit is WarUnitWithGeneral =>
|
const hasGeneral = (unit: WarUnit): unit is WarUnitWithGeneral =>
|
||||||
@@ -44,7 +46,11 @@ export const traitModule: TraitModule = {
|
|||||||
// Note: unit.getGeneral() is only available for WarUnitGeneral.
|
// Note: unit.getGeneral() is only available for WarUnitGeneral.
|
||||||
// In a real scenario, we should check if unit is WarUnitGeneral.
|
// In a real scenario, we should check if unit is WarUnitGeneral.
|
||||||
if (hasGeneral(unit)) {
|
if (hasGeneral(unit)) {
|
||||||
const killnum = getMetaNumber(unit.getGeneral().meta, 'rank_killnum', 0);
|
const killnum = getMetaNumber(
|
||||||
|
unit.getGeneral().meta as Record<string, import('@sammo-ts/logic/domain/entities.js').TriggerValue>,
|
||||||
|
'rank_killnum',
|
||||||
|
0
|
||||||
|
);
|
||||||
const logVal = Math.log2(Math.max(1, killnum / 5));
|
const logVal = Math.log2(Math.max(1, killnum / 5));
|
||||||
attackMultiplier += logVal / 20;
|
attackMultiplier += logVal / 20;
|
||||||
defenceMultiplier -= logVal / 50;
|
defenceMultiplier -= logVal / 50;
|
||||||
|
|||||||
@@ -99,12 +99,12 @@ export const traitModule: TraitModule = {
|
|||||||
getName: () => '반계',
|
getName: () => '반계',
|
||||||
getInfo: () =>
|
getInfo: () =>
|
||||||
'[전투] 상대의 계략 성공 확률 -10%p, 상대의 계략을 40% 확률로 되돌림, 반목 성공시 대미지 추가(+60% → +150%)',
|
'[전투] 상대의 계략 성공 확률 -10%p, 상대의 계략을 40% 확률로 되돌림, 반목 성공시 대미지 추가(+60% → +150%)',
|
||||||
onCalcOpposeStat: (_context, statName, value, _aux) => {
|
onCalcOpposeStat: ((_context, statName, value, _aux) => {
|
||||||
if (statName === 'warMagicSuccessProb' && typeof value === 'number') {
|
if (statName === 'warMagicSuccessProb' && typeof value === 'number') {
|
||||||
return value - 0.1;
|
return value - 0.1;
|
||||||
}
|
}
|
||||||
return value;
|
return value;
|
||||||
},
|
}) as TraitModule['onCalcOpposeStat'],
|
||||||
getBattlePhaseTriggerList: (_context) => {
|
getBattlePhaseTriggerList: (_context) => {
|
||||||
if (!_context.unit) return null;
|
if (!_context.unit) return null;
|
||||||
return new WarTriggerCaller(new che_반계시도(_context.unit), new che_반계발동(_context.unit));
|
return new WarTriggerCaller(new che_반계시도(_context.unit), new che_반계발동(_context.unit));
|
||||||
|
|||||||
@@ -16,32 +16,38 @@ export interface WarActionContext<TriggerState extends GeneralTriggerState = Gen
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface WarActionModule<TriggerState extends GeneralTriggerState = GeneralTriggerState> {
|
export interface WarActionModule<TriggerState extends GeneralTriggerState = GeneralTriggerState> {
|
||||||
getName?(): string;
|
getName?: (() => string) | undefined;
|
||||||
getInfo?(): string;
|
getInfo?: (() => string) | undefined;
|
||||||
|
|
||||||
getBattleInitTriggerList?(context: WarActionContext<TriggerState>): WarTriggerCaller | null;
|
getBattleInitTriggerList?: ((context: WarActionContext<TriggerState>) => WarTriggerCaller | null) | undefined;
|
||||||
|
|
||||||
getBattlePhaseTriggerList?(context: WarActionContext<TriggerState>): WarTriggerCaller | null;
|
getBattlePhaseTriggerList?: ((context: WarActionContext<TriggerState>) => WarTriggerCaller | null) | undefined;
|
||||||
|
|
||||||
onCalcStat?(
|
onCalcStat?:
|
||||||
context: WarActionContext<TriggerState>,
|
| ((
|
||||||
statName: WarStatName,
|
context: WarActionContext<TriggerState>,
|
||||||
value: number | [number, number],
|
statName: WarStatName,
|
||||||
aux?: unknown
|
value: number | [number, number],
|
||||||
): number | [number, number];
|
aux?: unknown
|
||||||
|
) => number | [number, number])
|
||||||
|
| undefined;
|
||||||
|
|
||||||
onCalcOpposeStat?(
|
onCalcOpposeStat?:
|
||||||
context: WarActionContext<TriggerState>,
|
| ((
|
||||||
statName: WarStatName,
|
context: WarActionContext<TriggerState>,
|
||||||
value: number | [number, number],
|
statName: WarStatName,
|
||||||
aux?: unknown
|
value: number | [number, number],
|
||||||
): number | [number, number];
|
aux?: unknown
|
||||||
|
) => number | [number, number])
|
||||||
|
| undefined;
|
||||||
|
|
||||||
getWarPowerMultiplier?(
|
getWarPowerMultiplier?:
|
||||||
context: WarActionContext<TriggerState>,
|
| ((
|
||||||
unit: WarUnit<TriggerState>,
|
context: WarActionContext<TriggerState>,
|
||||||
oppose: WarUnit<TriggerState>
|
unit: WarUnit<TriggerState>,
|
||||||
): [number, number];
|
oppose: WarUnit<TriggerState>
|
||||||
|
) => [number, number])
|
||||||
|
| undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class WarActionPipeline<TriggerState extends GeneralTriggerState = GeneralTriggerState> {
|
export class WarActionPipeline<TriggerState extends GeneralTriggerState = GeneralTriggerState> {
|
||||||
|
|||||||
@@ -110,7 +110,10 @@ const isSupplyCity = (city: City): boolean => {
|
|||||||
return city.supplyState > 0;
|
return city.supplyState > 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const computeBattleOrder = (defender: WarUnit, attacker: WarUnitGeneral): number => {
|
export const computeBattleOrder = <TriggerState extends GeneralTriggerState>(
|
||||||
|
defender: WarUnit<TriggerState>,
|
||||||
|
attacker: WarUnitGeneral<TriggerState>
|
||||||
|
): number => {
|
||||||
if (defender instanceof WarUnitCity) {
|
if (defender instanceof WarUnitCity) {
|
||||||
const context = attacker.getActionContext();
|
const context = attacker.getActionContext();
|
||||||
return attacker.getActionPipeline().onCalcOpposeStat(context, 'cityBattleOrder', -1);
|
return attacker.getActionPipeline().onCalcOpposeStat(context, 'cityBattleOrder', -1);
|
||||||
@@ -257,11 +260,14 @@ export const resolveWarBattle = <TriggerState extends GeneralTriggerState = Gene
|
|||||||
defenderGenerals.push(unit);
|
defenderGenerals.push(unit);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (defenderGenerals.length > 0 && computeBattleOrder(cityUnit, attackerUnit) > 0) {
|
if (defenderGenerals.length > 0 && computeBattleOrder<TriggerState>(cityUnit, attackerUnit) > 0) {
|
||||||
defenderUnits.push(cityUnit);
|
defenderUnits.push(cityUnit);
|
||||||
}
|
}
|
||||||
|
|
||||||
defenderUnits.sort((lhs, rhs) => computeBattleOrder(rhs, attackerUnit) - computeBattleOrder(lhs, attackerUnit));
|
defenderUnits.sort(
|
||||||
|
(lhs, rhs) =>
|
||||||
|
computeBattleOrder<TriggerState>(rhs, attackerUnit) - computeBattleOrder<TriggerState>(lhs, attackerUnit)
|
||||||
|
);
|
||||||
|
|
||||||
const iter = defenderUnits.values();
|
const iter = defenderUnits.values();
|
||||||
let defender: WarUnit<TriggerState> | null = null;
|
let defender: WarUnit<TriggerState> | null = null;
|
||||||
|
|||||||
Reference in New Issue
Block a user