feat: add new war traits for various unit types
- Implemented '궁병' trait with bonuses for archer units, including reduced recruitment costs and increased evasion. - Added '기병' trait providing cavalry units with damage bonuses and reduced recruitment costs. - Introduced '돌격' trait enhancing attack phases and damage against equal or weaker unit types. - Created '무쌍' trait that increases damage and reduces damage taken based on the number of victories. - Developed '반계' trait to reduce enemy strategy success rates and reflect damage back. - Added '보병' trait with reduced recruitment costs and damage penalties for allies during attacks. - Implemented '신산' trait enhancing magic success rates for various strategies. - Created '신중' trait guaranteeing strategy success. - Added '위압' trait to activate pressure effects on the first phase of battle. - Implemented '저격' trait allowing for a chance to activate a sniping effect against new opponents. - Developed '집중' trait to increase damage on successful strategies. - Created '척사' trait providing damage bonuses against regional and city units while reducing ally damage. - Implemented '필살' trait enhancing critical hit chances and disabling enemy evasion on critical hits. - Added '환술' trait to boost magic success rates and damage on successful magic attacks.
This commit is contained in:
@@ -0,0 +1,25 @@
|
||||
import type { TraitModule } from '@sammo-ts/logic/triggers/special/types.js';
|
||||
|
||||
// 내정 특기: 경작
|
||||
export const traitModule: TraitModule = {
|
||||
key: 'che_경작',
|
||||
name: '경작',
|
||||
info: '[내정] 농지 개간 : 기본 보정 +10%, 성공률 +10%p, 비용 -20%',
|
||||
kind: 'domestic',
|
||||
getName: () => '경작',
|
||||
getInfo: () => '[내정] 농지 개간 : 기본 보정 +10%, 성공률 +10%p, 비용 -20%',
|
||||
onCalcDomestic: (_context, turnType, varType, value) => {
|
||||
if (turnType === '농업') {
|
||||
if (varType === 'score') {
|
||||
return value * 1.1;
|
||||
}
|
||||
if (varType === 'cost') {
|
||||
return value * 0.8;
|
||||
}
|
||||
if (varType === 'success') {
|
||||
return value + 0.1;
|
||||
}
|
||||
}
|
||||
return value;
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,19 @@
|
||||
import type { TraitModule } from '@sammo-ts/logic/triggers/special/types.js';
|
||||
|
||||
// 내정 특기: 귀모
|
||||
export const traitModule: TraitModule = {
|
||||
key: 'che_귀모',
|
||||
name: '귀모',
|
||||
info: '[계략] 화계·탈취·파괴·선동 : 성공률 +20%p',
|
||||
kind: 'domestic',
|
||||
getName: () => '귀모',
|
||||
getInfo: () => '[계략] 화계·탈취·파괴·선동 : 성공률 +20%p',
|
||||
onCalcDomestic: (_context, turnType, varType, value) => {
|
||||
if (turnType === '계략') {
|
||||
if (varType === 'success') {
|
||||
return value + 0.2;
|
||||
}
|
||||
}
|
||||
return value;
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,25 @@
|
||||
import type { TraitModule } from '@sammo-ts/logic/triggers/special/types.js';
|
||||
|
||||
// 내정 특기: 상재
|
||||
export const traitModule: TraitModule = {
|
||||
key: 'che_상재',
|
||||
name: '상재',
|
||||
info: '[내정] 상업 투자 : 기본 보정 +10%, 성공률 +10%p, 비용 -20%',
|
||||
kind: 'domestic',
|
||||
getName: () => '상재',
|
||||
getInfo: () => '[내정] 상업 투자 : 기본 보정 +10%, 성공률 +10%p, 비용 -20%',
|
||||
onCalcDomestic: (_context, turnType, varType, value) => {
|
||||
if (turnType === '상업') {
|
||||
if (varType === 'score') {
|
||||
return value * 1.1;
|
||||
}
|
||||
if (varType === 'cost') {
|
||||
return value * 0.8;
|
||||
}
|
||||
if (varType === 'success') {
|
||||
return value + 0.1;
|
||||
}
|
||||
}
|
||||
return value;
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,25 @@
|
||||
import type { TraitModule } from '@sammo-ts/logic/triggers/special/types.js';
|
||||
|
||||
// 내정 특기: 수비
|
||||
export const traitModule: TraitModule = {
|
||||
key: 'che_수비',
|
||||
name: '수비',
|
||||
info: '[내정] 수비 강화 : 기본 보정 +10%, 성공률 +10%p, 비용 -20%',
|
||||
kind: 'domestic',
|
||||
getName: () => '수비',
|
||||
getInfo: () => '[내정] 수비 강화 : 기본 보정 +10%, 성공률 +10%p, 비용 -20%',
|
||||
onCalcDomestic: (_context, turnType, varType, value) => {
|
||||
if (turnType === '수비') {
|
||||
if (varType === 'score') {
|
||||
return value * 1.1;
|
||||
}
|
||||
if (varType === 'cost') {
|
||||
return value * 0.8;
|
||||
}
|
||||
if (varType === 'success') {
|
||||
return value + 0.1;
|
||||
}
|
||||
}
|
||||
return value;
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,25 @@
|
||||
import type { TraitModule } from '@sammo-ts/logic/triggers/special/types.js';
|
||||
|
||||
// 내정 특기: 축성
|
||||
export const traitModule: TraitModule = {
|
||||
key: 'che_축성',
|
||||
name: '축성',
|
||||
info: '[내정] 성벽 보수 : 기본 보정 +10%, 성공률 +10%p, 비용 -20%',
|
||||
kind: 'domestic',
|
||||
getName: () => '축성',
|
||||
getInfo: () => '[내정] 성벽 보수 : 기본 보정 +10%, 성공률 +10%p, 비용 -20%',
|
||||
onCalcDomestic: (_context, turnType, varType, value) => {
|
||||
if (turnType === '성벽') {
|
||||
if (varType === 'score') {
|
||||
return value * 1.1;
|
||||
}
|
||||
if (varType === 'cost') {
|
||||
return value * 0.8;
|
||||
}
|
||||
if (varType === 'success') {
|
||||
return value + 0.1;
|
||||
}
|
||||
}
|
||||
return value;
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,25 @@
|
||||
import type { TraitModule } from '@sammo-ts/logic/triggers/special/types.js';
|
||||
|
||||
// 내정 특기: 통찰
|
||||
export const traitModule: TraitModule = {
|
||||
key: 'che_통찰',
|
||||
name: '통찰',
|
||||
info: '[내정] 치안 강화 : 기본 보정 +10%, 성공률 +10%p, 비용 -20%',
|
||||
kind: 'domestic',
|
||||
getName: () => '통찰',
|
||||
getInfo: () => '[내정] 치안 강화 : 기본 보정 +10%, 성공률 +10%p, 비용 -20%',
|
||||
onCalcDomestic: (_context, turnType, varType, value) => {
|
||||
if (turnType === '치안') {
|
||||
if (varType === 'score') {
|
||||
return value * 1.1;
|
||||
}
|
||||
if (varType === 'cost') {
|
||||
return value * 0.8;
|
||||
}
|
||||
if (varType === 'success') {
|
||||
return value + 0.1;
|
||||
}
|
||||
}
|
||||
return value;
|
||||
},
|
||||
};
|
||||
@@ -1,6 +1,15 @@
|
||||
import type { TraitModule, TraitModuleExport } from '@sammo-ts/logic/triggers/special/types.js';
|
||||
|
||||
export const DOMESTIC_TRAIT_KEYS = ['che_인덕', 'che_발명'] as const;
|
||||
export const DOMESTIC_TRAIT_KEYS = [
|
||||
'che_인덕',
|
||||
'che_발명',
|
||||
'che_경작',
|
||||
'che_상재',
|
||||
'che_축성',
|
||||
'che_수비',
|
||||
'che_통찰',
|
||||
'che_귀모',
|
||||
] as const;
|
||||
|
||||
export type DomesticTraitKey = (typeof DOMESTIC_TRAIT_KEYS)[number];
|
||||
|
||||
@@ -11,6 +20,12 @@ export type DomesticTraitImporter = () => Promise<TraitModuleExport>;
|
||||
const defaultImporters: Record<DomesticTraitKey, DomesticTraitImporter> = {
|
||||
che_인덕: async () => import('./che_인덕.js'),
|
||||
che_발명: async () => import('./che_발명.js'),
|
||||
che_경작: async () => import('./che_경작.js'),
|
||||
che_상재: async () => import('./che_상재.js'),
|
||||
che_축성: async () => import('./che_축성.js'),
|
||||
che_수비: async () => import('./che_수비.js'),
|
||||
che_통찰: async () => import('./che_통찰.js'),
|
||||
che_귀모: async () => import('./che_귀모.js'),
|
||||
};
|
||||
|
||||
export const isDomesticTraitKey = (value: string): value is DomesticTraitKey =>
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
import type { GeneralActionContext } from '@sammo-ts/logic/triggers/general.js';
|
||||
import type { GeneralStatName, WarStatName } from '@sammo-ts/logic/triggers/types.js';
|
||||
import type { WarActionContext } from '@sammo-ts/logic/war/actions.js';
|
||||
import type { TraitModule } from '@sammo-ts/logic/triggers/special/types.js';
|
||||
import { BaseWarUnitTrigger, WarTriggerCaller } from '@sammo-ts/logic/war/triggers.js';
|
||||
import type { WarUnit } from '@sammo-ts/logic/war/units.js';
|
||||
import { LogFormat } from '@sammo-ts/logic/logging/types.js';
|
||||
|
||||
class che_격노시도 extends BaseWarUnitTrigger {
|
||||
constructor(unit: WarUnit) {
|
||||
super(unit, 30400);
|
||||
}
|
||||
|
||||
protected actionWar(
|
||||
self: WarUnit,
|
||||
oppose: WarUnit,
|
||||
_selfEnv: Record<string, unknown>,
|
||||
_opposeEnv: Record<string, unknown>
|
||||
): boolean {
|
||||
if (!oppose.hasActivatedSkill('필살') && !oppose.hasActivatedSkill('회피')) {
|
||||
return true;
|
||||
}
|
||||
if (self.hasActivatedSkill('격노불가')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (oppose.hasActivatedSkill('필살')) {
|
||||
self.activateSkill('격노');
|
||||
oppose.deactivateSkill('회피');
|
||||
if (self.isAttacker() && self.rng.nextBool(1 / 2)) {
|
||||
self.activateSkill('진노');
|
||||
}
|
||||
} else if (self.rng.nextBool(1 / 4)) {
|
||||
self.activateSkill('격노');
|
||||
oppose.deactivateSkill('회피');
|
||||
if (self.isAttacker() && self.rng.nextBool(1 / 2)) {
|
||||
self.activateSkill('진노');
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
class che_격노발동 extends BaseWarUnitTrigger {
|
||||
constructor(unit: WarUnit) {
|
||||
super(unit, 40600);
|
||||
}
|
||||
|
||||
protected actionWar(
|
||||
self: WarUnit,
|
||||
oppose: WarUnit,
|
||||
_selfEnv: Record<string, unknown>,
|
||||
_opposeEnv: Record<string, unknown>
|
||||
): boolean {
|
||||
if (!self.hasActivatedSkill('격노')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const targetAct = oppose.hasActivatedSkill('필살') ? '필살 공격' : '회피 시도';
|
||||
const is진노 = self.hasActivatedSkill('진노');
|
||||
const reaction = is진노 ? '진노' : '격노';
|
||||
|
||||
self.getLogger().pushGeneralBattleDetailLog(`상대의 ${targetAct}에 <C>${reaction}</>했다!</>`, LogFormat.PLAIN);
|
||||
oppose.getLogger().pushGeneralBattleDetailLog(`${targetAct}에 상대가 <R>${reaction}</>했다!</>`, LogFormat.PLAIN);
|
||||
|
||||
if (is진노) {
|
||||
self.addBonusPhase(1);
|
||||
}
|
||||
self.multiplyWarPowerMultiply(self.criticalDamage());
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
function onCalcStat(context: GeneralActionContext, statName: GeneralStatName, value: number, aux?: unknown): number;
|
||||
function onCalcStat(
|
||||
context: WarActionContext,
|
||||
statName: WarStatName,
|
||||
value: number | [number, number],
|
||||
aux?: unknown
|
||||
): number | [number, number];
|
||||
function onCalcStat(
|
||||
context: GeneralActionContext | WarActionContext,
|
||||
statName: GeneralStatName | WarStatName,
|
||||
value: number | [number, number],
|
||||
aux?: unknown
|
||||
): number | [number, number] {
|
||||
return value;
|
||||
}
|
||||
|
||||
export const traitModule: TraitModule = {
|
||||
key: 'che_격노',
|
||||
name: '격노',
|
||||
info: '[전투] 상대방 필살 시 격노(필살) 발동, 회피 시도시 25% 확률로 격노 발동, 공격 시 일정 확률로 진노(1페이즈 추가), 격노마다 대미지 20% 추가 중첩',
|
||||
kind: 'war',
|
||||
getName: () => '격노',
|
||||
getInfo: () =>
|
||||
'[전투] 상대방 필살 시 격노(필살) 발동, 회피 시도시 25% 확률로 격노 발동, 공격 시 일정 확률로 진노(1페이즈 추가), 격노마다 대미지 20% 추가 중첩',
|
||||
getWarPowerMultiplier: (_context, unit, _oppose) => {
|
||||
const activatedCnt = unit.hasActivatedSkillOnLog('격노');
|
||||
return [1 + 0.2 * activatedCnt, 1];
|
||||
},
|
||||
getBattlePhaseTriggerList: (_context) => {
|
||||
if (!_context.unit) return null;
|
||||
return new WarTriggerCaller(new che_격노시도(_context.unit), new che_격노발동(_context.unit));
|
||||
},
|
||||
onCalcStat,
|
||||
};
|
||||
@@ -0,0 +1,68 @@
|
||||
import type { GeneralActionContext } from '@sammo-ts/logic/triggers/general.js';
|
||||
import type { GeneralStatName, WarStatName } from '@sammo-ts/logic/triggers/types.js';
|
||||
import type { WarActionContext } from '@sammo-ts/logic/war/actions.js';
|
||||
import type { TraitModule } from '@sammo-ts/logic/triggers/special/types.js';
|
||||
import { BaseWarUnitTrigger, WarTriggerCaller } from '@sammo-ts/logic/war/triggers.js';
|
||||
import type { WarUnit } from '@sammo-ts/logic/war/units.js';
|
||||
|
||||
class che_부상무효 extends BaseWarUnitTrigger {
|
||||
constructor(unit: WarUnit) {
|
||||
super(unit, 10200);
|
||||
}
|
||||
|
||||
protected actionWar(
|
||||
self: WarUnit,
|
||||
_oppose: WarUnit,
|
||||
_selfEnv: Record<string, unknown>,
|
||||
_opposeEnv: Record<string, unknown>
|
||||
): boolean {
|
||||
self.activateSkill('부상무효');
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
function onCalcStat(context: GeneralActionContext, statName: GeneralStatName, value: number, aux?: unknown): number;
|
||||
function onCalcStat(
|
||||
context: WarActionContext,
|
||||
statName: WarStatName,
|
||||
value: number | [number, number],
|
||||
aux?: unknown
|
||||
): number | [number, number];
|
||||
function onCalcStat(
|
||||
context: GeneralActionContext | WarActionContext,
|
||||
statName: GeneralStatName | WarStatName,
|
||||
value: number | [number, number],
|
||||
aux?: unknown
|
||||
): number | [number, number] {
|
||||
return value;
|
||||
}
|
||||
|
||||
export const traitModule: TraitModule = {
|
||||
key: 'che_견고',
|
||||
name: '견고',
|
||||
info: '[전투] 상대 필살 확률 -20%p, 상대 계략 시도시 성공 확률 -10%p, 부상 없음, 아군 피해 -10%',
|
||||
kind: 'war',
|
||||
getName: () => '견고',
|
||||
getInfo: () => '[전투] 상대 필살 확률 -20%p, 상대 계략 시도시 성공 확률 -10%p, 부상 없음, 아군 피해 -10%',
|
||||
onCalcOpposeStat: (_context, statName, value, _aux) => {
|
||||
if (statName === 'warMagicSuccessProb') {
|
||||
return (value as number) - 0.1;
|
||||
}
|
||||
if (statName === 'warCriticalRatio') {
|
||||
return (value as number) - 0.2;
|
||||
}
|
||||
return value;
|
||||
},
|
||||
getBattleInitTriggerList: (_context) => {
|
||||
if (!_context.unit) return null;
|
||||
return new WarTriggerCaller(new che_부상무효(_context.unit));
|
||||
},
|
||||
getBattlePhaseTriggerList: (_context) => {
|
||||
if (!_context.unit) return null;
|
||||
return new WarTriggerCaller(new che_부상무효(_context.unit));
|
||||
},
|
||||
getWarPowerMultiplier: (_context, _unit, _oppose) => {
|
||||
return [1, 0.9];
|
||||
},
|
||||
onCalcStat,
|
||||
};
|
||||
@@ -0,0 +1,72 @@
|
||||
import type { GeneralActionContext } from '@sammo-ts/logic/triggers/general.js';
|
||||
import type { GeneralStatName, WarStatName } from '@sammo-ts/logic/triggers/types.js';
|
||||
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';
|
||||
|
||||
function onCalcStat(context: GeneralActionContext, statName: GeneralStatName, value: number, aux?: unknown): number;
|
||||
function onCalcStat(
|
||||
context: WarActionContext,
|
||||
statName: WarStatName,
|
||||
value: number | [number, number],
|
||||
aux?: unknown
|
||||
): number | [number, number];
|
||||
function onCalcStat(
|
||||
context: GeneralActionContext | WarActionContext,
|
||||
statName: GeneralStatName | WarStatName,
|
||||
value: number | [number, number],
|
||||
aux?: unknown
|
||||
): number | [number, number] {
|
||||
if (!('unit' in context) || !context.unit) {
|
||||
return value;
|
||||
}
|
||||
const unit = context.unit;
|
||||
|
||||
const siegeType = unit.getGameConfig().armTypes.siege;
|
||||
if (siegeType === undefined) {
|
||||
return value;
|
||||
}
|
||||
|
||||
if (statName.startsWith('dex')) {
|
||||
const myDex = getMetaNumber(context.general.meta, `dex${siegeType}`);
|
||||
const isAttacker = (aux as any)?.isAttacker;
|
||||
const opposeType = (aux as any)?.opposeType;
|
||||
|
||||
if (isAttacker && opposeType && statName === `dex${opposeType.armType}`) {
|
||||
return (value as number) + myDex;
|
||||
}
|
||||
if (!isAttacker && statName === `dex${siegeType}`) {
|
||||
return (value as number) + myDex;
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
// 전투 특기: 공성
|
||||
export const traitModule: TraitModule = {
|
||||
key: 'che_공성',
|
||||
name: '공성',
|
||||
info: '[군사] 차병 계통 징·모병비 -10%<br>[전투] 성벽 공격 시 대미지 +100%,<br>공격시 상대 병종에/수비시 자신 병종 숙련에 차병 숙련을 가산',
|
||||
kind: 'war',
|
||||
getName: () => '공성',
|
||||
getInfo: () =>
|
||||
'[군사] 차병 계통 징·모병비 -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
return value;
|
||||
},
|
||||
getWarPowerMultiplier: (_context, _unit, oppose) => {
|
||||
if (oppose instanceof WarUnitCity) {
|
||||
return [2, 1];
|
||||
}
|
||||
return [1, 1];
|
||||
},
|
||||
onCalcStat,
|
||||
};
|
||||
@@ -0,0 +1,71 @@
|
||||
import type { GeneralActionContext } from '@sammo-ts/logic/triggers/general.js';
|
||||
import type { GeneralStatName, WarStatName } from '@sammo-ts/logic/triggers/types.js';
|
||||
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';
|
||||
|
||||
function onCalcStat(context: GeneralActionContext, statName: GeneralStatName, value: number, aux?: unknown): number;
|
||||
function onCalcStat(
|
||||
context: WarActionContext,
|
||||
statName: WarStatName,
|
||||
value: number | [number, number],
|
||||
aux?: unknown
|
||||
): number | [number, number];
|
||||
function onCalcStat(
|
||||
context: GeneralActionContext | WarActionContext,
|
||||
statName: GeneralStatName | WarStatName,
|
||||
value: number | [number, number],
|
||||
aux?: unknown
|
||||
): number | [number, number] {
|
||||
if (statName === 'warAvoidRatio') {
|
||||
return (value as number) + 0.2;
|
||||
}
|
||||
|
||||
if (!('unit' in context) || !context.unit) {
|
||||
return value;
|
||||
}
|
||||
const unit = context.unit;
|
||||
|
||||
const archerType = unit.getGameConfig().armTypes.archer;
|
||||
if (archerType === undefined) {
|
||||
return value;
|
||||
}
|
||||
|
||||
if (statName.startsWith('dex')) {
|
||||
const myDex = getMetaNumber(context.general.meta, `dex${archerType}`);
|
||||
const isAttacker = (aux as any)?.isAttacker;
|
||||
const opposeType = (aux as any)?.opposeType;
|
||||
|
||||
if (isAttacker && opposeType && statName === `dex${opposeType.armType}`) {
|
||||
return (value as number) + myDex;
|
||||
}
|
||||
if (!isAttacker && statName === `dex${archerType}`) {
|
||||
return (value as number) + myDex;
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
// 전투 특기: 궁병
|
||||
export const traitModule: TraitModule = {
|
||||
key: 'che_궁병',
|
||||
name: '궁병',
|
||||
info: '[군사] 궁병 계통 징·모병비 -10%<br>[전투] 회피 확률 +20%p,<br>공격시 상대 병종에/수비시 자신 병종 숙련에 궁병 숙련을 가산',
|
||||
kind: 'war',
|
||||
getName: () => '궁병',
|
||||
getInfo: () =>
|
||||
'[군사] 궁병 계통 징·모병비 -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
return value;
|
||||
},
|
||||
onCalcStat,
|
||||
};
|
||||
@@ -0,0 +1,71 @@
|
||||
import type { GeneralActionContext } from '@sammo-ts/logic/triggers/general.js';
|
||||
import type { GeneralStatName, WarStatName } from '@sammo-ts/logic/triggers/types.js';
|
||||
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';
|
||||
|
||||
function onCalcStat(context: GeneralActionContext, statName: GeneralStatName, value: number, aux?: unknown): number;
|
||||
function onCalcStat(
|
||||
context: WarActionContext,
|
||||
statName: WarStatName,
|
||||
value: number | [number, number],
|
||||
aux?: unknown
|
||||
): number | [number, number];
|
||||
function onCalcStat(
|
||||
context: GeneralActionContext | WarActionContext,
|
||||
statName: GeneralStatName | WarStatName,
|
||||
value: number | [number, number],
|
||||
aux?: unknown
|
||||
): number | [number, number] {
|
||||
if (!('unit' in context) || !context.unit) {
|
||||
return value;
|
||||
}
|
||||
const unit = context.unit;
|
||||
|
||||
const cavalryType = unit.getGameConfig().armTypes.cavalry;
|
||||
if (cavalryType === undefined) {
|
||||
return value;
|
||||
}
|
||||
|
||||
if (statName.startsWith('dex')) {
|
||||
const myDex = getMetaNumber(context.general.meta, `dex${cavalryType}`);
|
||||
const isAttacker = (aux as any)?.isAttacker;
|
||||
const opposeType = (aux as any)?.opposeType;
|
||||
|
||||
if (isAttacker && opposeType && statName === `dex${opposeType.armType}`) {
|
||||
return (value as number) + myDex;
|
||||
}
|
||||
if (!isAttacker && statName === `dex${cavalryType}`) {
|
||||
return (value as number) + myDex;
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
// 전투 특기: 기병
|
||||
export const traitModule: TraitModule = {
|
||||
key: 'che_기병',
|
||||
name: '기병',
|
||||
info: '[군사] 기병 계통 징·모병비 -10%<br>[전투] 수비 시 대미지 +10%, 공격 시 대미지 +20%,<br>공격시 상대 병종에/수비시 자신 병종 숙련에 기병 숙련을 가산',
|
||||
kind: 'war',
|
||||
getName: () => '기병',
|
||||
getInfo: () =>
|
||||
'[군사] 기병 계통 징·모병비 -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
return value;
|
||||
},
|
||||
getWarPowerMultiplier: (_context, unit, _oppose) => {
|
||||
if (unit.isAttacker()) {
|
||||
return [1.2, 1];
|
||||
}
|
||||
return [1.1, 1];
|
||||
},
|
||||
onCalcStat,
|
||||
};
|
||||
@@ -0,0 +1,77 @@
|
||||
import type { GeneralActionContext } from '@sammo-ts/logic/triggers/general.js';
|
||||
import type { GeneralStatName, WarStatName } from '@sammo-ts/logic/triggers/types.js';
|
||||
import type { WarActionContext } from '@sammo-ts/logic/war/actions.js';
|
||||
import type { TraitModule } from '@sammo-ts/logic/triggers/special/types.js';
|
||||
import { BaseWarUnitTrigger, WarTriggerCaller } from '@sammo-ts/logic/war/triggers.js';
|
||||
import type { WarUnit } from '@sammo-ts/logic/war/units.js';
|
||||
|
||||
class che_돌격지속 extends BaseWarUnitTrigger {
|
||||
constructor(unit: WarUnit) {
|
||||
super(unit, 40900);
|
||||
}
|
||||
|
||||
protected actionWar(
|
||||
self: WarUnit,
|
||||
oppose: WarUnit,
|
||||
_selfEnv: Record<string, unknown>,
|
||||
_opposeEnv: Record<string, unknown>
|
||||
): boolean {
|
||||
if (oppose.constructor.name === 'WarUnitCity') {
|
||||
return true;
|
||||
}
|
||||
if (!self.isAttacker()) {
|
||||
return true;
|
||||
}
|
||||
const attackCoef = self.getCrewType().getAttackCoef(oppose.getCrewType());
|
||||
if (attackCoef < 1) {
|
||||
if (oppose.hasActivatedSkill('선제') && self.getPhase() >= self.getMaxPhase() - 2) {
|
||||
self.addBonusPhase(-1);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (self.getPhase() < self.getMaxPhase() - 1) {
|
||||
return true;
|
||||
}
|
||||
self.addBonusPhase(1);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
function onCalcStat(context: GeneralActionContext, statName: GeneralStatName, value: number, aux?: unknown): number;
|
||||
function onCalcStat(
|
||||
context: WarActionContext,
|
||||
statName: WarStatName,
|
||||
value: number | [number, number],
|
||||
aux?: unknown
|
||||
): number | [number, number];
|
||||
function onCalcStat(
|
||||
context: GeneralActionContext | WarActionContext,
|
||||
statName: GeneralStatName | WarStatName,
|
||||
value: number | [number, number],
|
||||
aux?: unknown
|
||||
): number | [number, number] {
|
||||
if (statName === 'initWarPhase') {
|
||||
return (value as number) + 2;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
export const traitModule: TraitModule = {
|
||||
key: 'che_돌격',
|
||||
name: '돌격',
|
||||
info: '[전투] 공격 시 대등/유리한 병종에게는 퇴각 전까지 전투, 공격 시 페이즈 + 2, 공격 시 대미지 +5%',
|
||||
kind: 'war',
|
||||
getName: () => '돌격',
|
||||
getInfo: () => '[전투] 공격 시 대등/유리한 병종에게는 퇴각 전까지 전투, 공격 시 페이즈 + 2, 공격 시 대미지 +5%',
|
||||
getWarPowerMultiplier: (_context, unit, _oppose) => {
|
||||
if (unit.isAttacker()) {
|
||||
return [1.05, 1];
|
||||
}
|
||||
return [1, 1];
|
||||
},
|
||||
getBattlePhaseTriggerList: (_context) => {
|
||||
if (!_context.unit) return null;
|
||||
return new WarTriggerCaller(new che_돌격지속(_context.unit));
|
||||
},
|
||||
onCalcStat,
|
||||
};
|
||||
@@ -0,0 +1,48 @@
|
||||
import type { GeneralActionContext } from '@sammo-ts/logic/triggers/general.js';
|
||||
import type { GeneralStatName, WarStatName } from '@sammo-ts/logic/triggers/types.js';
|
||||
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';
|
||||
|
||||
function onCalcStat(context: GeneralActionContext, statName: GeneralStatName, value: number, aux?: unknown): number;
|
||||
function onCalcStat(
|
||||
context: WarActionContext,
|
||||
statName: WarStatName,
|
||||
value: number | [number, number],
|
||||
aux?: unknown
|
||||
): number | [number, number];
|
||||
function onCalcStat(
|
||||
context: GeneralActionContext | WarActionContext,
|
||||
statName: GeneralStatName | WarStatName,
|
||||
value: number | [number, number],
|
||||
aux?: unknown
|
||||
): number | [number, number] {
|
||||
if (statName === 'warCriticalRatio' && (aux as any)?.isAttacker) {
|
||||
return (value as number) + 0.1;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
export const traitModule: TraitModule = {
|
||||
key: 'che_무쌍',
|
||||
name: '무쌍',
|
||||
info: '[전투] 대미지 +5%, 피해 -2%, 공격 시 필살 확률 +10%p, <br>승리 수의 로그 비례로 대미지 상승(10회 ⇒ +5%, 40회 ⇒ +15%)<br>승리 수의 로그 비례로 피해 감소(10회 ⇒ -2%, 40회 ⇒ -6%)',
|
||||
kind: 'war',
|
||||
getName: () => '무쌍',
|
||||
getInfo: () =>
|
||||
'[전투] 대미지 +5%, 피해 -2%, 공격 시 필살 확률 +10%p, <br>승리 수의 로그 비례로 대미지 상승(10회 ⇒ +5%, 40회 ⇒ +15%)<br>승리 수의 로그 비례로 피해 감소(10회 ⇒ -2%, 40회 ⇒ -6%)',
|
||||
getWarPowerMultiplier: (_context, unit, _oppose) => {
|
||||
let attackMultiplier = 1.05;
|
||||
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);
|
||||
const logVal = Math.log2(Math.max(1, killnum / 5));
|
||||
attackMultiplier += logVal / 20;
|
||||
defenceMultiplier -= logVal / 50;
|
||||
}
|
||||
return [attackMultiplier, defenceMultiplier];
|
||||
},
|
||||
onCalcStat,
|
||||
};
|
||||
@@ -0,0 +1,109 @@
|
||||
import type { GeneralActionContext } from '@sammo-ts/logic/triggers/general.js';
|
||||
import type { GeneralStatName, WarStatName } from '@sammo-ts/logic/triggers/types.js';
|
||||
import type { WarActionContext } from '@sammo-ts/logic/war/actions.js';
|
||||
import type { TraitModule } from '@sammo-ts/logic/triggers/special/types.js';
|
||||
import { BaseWarUnitTrigger, WarTriggerCaller } from '@sammo-ts/logic/war/triggers.js';
|
||||
import type { WarUnit } from '@sammo-ts/logic/war/units.js';
|
||||
import { LogFormat } from '@sammo-ts/logic/logging/types.js';
|
||||
|
||||
class che_반계시도 extends BaseWarUnitTrigger {
|
||||
private readonly prob: number;
|
||||
|
||||
constructor(unit: WarUnit, prob = 0.4) {
|
||||
super(unit, 30300);
|
||||
this.prob = prob;
|
||||
}
|
||||
|
||||
protected actionWar(
|
||||
self: WarUnit,
|
||||
oppose: WarUnit,
|
||||
_selfEnv: Record<string, unknown>,
|
||||
_opposeEnv: Record<string, unknown>
|
||||
): boolean {
|
||||
if (!oppose.hasActivatedSkill('계략')) {
|
||||
return true;
|
||||
}
|
||||
if (self.hasActivatedSkill('반계불가')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!self.rng.nextBool(this.prob)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
self.activateSkill('반계');
|
||||
oppose.deactivateSkill('계략');
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
class che_반계발동 extends BaseWarUnitTrigger {
|
||||
constructor(unit: WarUnit) {
|
||||
super(unit, 40250);
|
||||
}
|
||||
|
||||
protected actionWar(
|
||||
self: WarUnit,
|
||||
oppose: WarUnit,
|
||||
_selfEnv: Record<string, unknown>,
|
||||
opposeEnv: Record<string, unknown>
|
||||
): boolean {
|
||||
if (!self.hasActivatedSkill('반계')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const magicData = opposeEnv.magic as [string, number] | undefined;
|
||||
if (!magicData) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const [opposeMagic, damage] = magicData;
|
||||
|
||||
self.getLogger().pushGeneralBattleDetailLog(`<C>반계</>로 상대의 <D>${opposeMagic}</>을 되돌렸다!`, LogFormat.PLAIN);
|
||||
oppose.getLogger().pushGeneralBattleDetailLog(`<D>${opposeMagic}</>을 <R>역으로</> 당했다!`, LogFormat.PLAIN);
|
||||
|
||||
self.multiplyWarPowerMultiply(damage);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
function onCalcStat(context: GeneralActionContext, statName: GeneralStatName, value: number, aux?: unknown): number;
|
||||
function onCalcStat(
|
||||
context: WarActionContext,
|
||||
statName: WarStatName,
|
||||
value: number | [number, number],
|
||||
aux?: unknown
|
||||
): number | [number, number];
|
||||
function onCalcStat(
|
||||
context: GeneralActionContext | WarActionContext,
|
||||
statName: GeneralStatName | WarStatName,
|
||||
value: number | [number, number],
|
||||
aux?: unknown
|
||||
): number | [number, number] {
|
||||
if (statName === 'warMagicSuccessDamage' && aux === '반목') {
|
||||
return (value as number) + 0.9;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
export const traitModule: TraitModule = {
|
||||
key: 'che_반계',
|
||||
name: '반계',
|
||||
info: '[전투] 상대의 계략 성공 확률 -10%p, 상대의 계략을 40% 확률로 되돌림, 반목 성공시 대미지 추가(+60% → +150%)',
|
||||
kind: 'war',
|
||||
getName: () => '반계',
|
||||
getInfo: () => '[전투] 상대의 계략 성공 확률 -10%p, 상대의 계략을 40% 확률로 되돌림, 반목 성공시 대미지 추가(+60% → +150%)',
|
||||
onCalcOpposeStat: (_context, statName, value, _aux) => {
|
||||
if (statName === 'warMagicSuccessProb') {
|
||||
return (value as number) - 0.1;
|
||||
}
|
||||
return value;
|
||||
},
|
||||
getBattlePhaseTriggerList: (_context) => {
|
||||
if (!_context.unit) return null;
|
||||
return new WarTriggerCaller(new che_반계시도(_context.unit), new che_반계발동(_context.unit));
|
||||
},
|
||||
onCalcStat,
|
||||
};
|
||||
@@ -0,0 +1,73 @@
|
||||
import type { GeneralActionContext } from '@sammo-ts/logic/triggers/general.js';
|
||||
import type { GeneralStatName, WarStatName } from '@sammo-ts/logic/triggers/types.js';
|
||||
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';
|
||||
|
||||
function onCalcStat(context: GeneralActionContext, statName: GeneralStatName, value: number, aux?: unknown): number;
|
||||
function onCalcStat(
|
||||
context: WarActionContext,
|
||||
statName: WarStatName,
|
||||
value: number | [number, number],
|
||||
aux?: unknown
|
||||
): number | [number, number];
|
||||
function onCalcStat(
|
||||
context: GeneralActionContext | WarActionContext,
|
||||
statName: GeneralStatName | WarStatName,
|
||||
value: number | [number, number],
|
||||
aux?: unknown
|
||||
): number | [number, number] {
|
||||
if (!('unit' in context) || !context.unit) {
|
||||
return value;
|
||||
}
|
||||
const unit = context.unit;
|
||||
|
||||
const footmanType = unit.getGameConfig().armTypes.footman;
|
||||
if (footmanType === undefined) {
|
||||
return value;
|
||||
}
|
||||
|
||||
if (statName.startsWith('dex')) {
|
||||
const myDex = getMetaNumber(context.general.meta, `dex${footmanType}`);
|
||||
const isAttacker = (aux as any)?.isAttacker;
|
||||
const opposeType = (aux as any)?.opposeType;
|
||||
|
||||
if (isAttacker && opposeType && statName === `dex${opposeType.armType}`) {
|
||||
return (value as number) + myDex;
|
||||
}
|
||||
if (!isAttacker && statName === `dex${footmanType}`) {
|
||||
return (value as number) + myDex;
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
// 전투 특기: 보병
|
||||
export const traitModule: TraitModule = {
|
||||
key: 'che_보병',
|
||||
name: '보병',
|
||||
info: '[군사] 보병 계통 징·모병비 -10%<br>[전투] 공격 시 아군 피해 -10%, 수비 시 아군 피해 -20%,<br>공격시 상대 병종에/수비시 자신 병종 숙련에 보병 숙련을 가산',
|
||||
kind: 'war',
|
||||
getName: () => '보병',
|
||||
getInfo: () =>
|
||||
'[군사] 보병 계통 징·모병비 -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
return value;
|
||||
},
|
||||
getWarPowerMultiplier: (_context, unit, _oppose) => {
|
||||
if (unit.isAttacker()) {
|
||||
return [1, 0.9];
|
||||
}
|
||||
return [1, 0.8];
|
||||
},
|
||||
onCalcStat,
|
||||
};
|
||||
@@ -0,0 +1,42 @@
|
||||
import type { GeneralActionContext } from '@sammo-ts/logic/triggers/general.js';
|
||||
import type { GeneralStatName, WarStatName } from '@sammo-ts/logic/triggers/types.js';
|
||||
import type { WarActionContext } from '@sammo-ts/logic/war/actions.js';
|
||||
import type { TraitModule } from '@sammo-ts/logic/triggers/special/types.js';
|
||||
|
||||
function onCalcStat(context: GeneralActionContext, statName: GeneralStatName, value: number, aux?: unknown): number;
|
||||
function onCalcStat(
|
||||
context: WarActionContext,
|
||||
statName: WarStatName,
|
||||
value: number | [number, number],
|
||||
aux?: unknown
|
||||
): number | [number, number];
|
||||
function onCalcStat(
|
||||
context: GeneralActionContext | WarActionContext,
|
||||
statName: GeneralStatName | WarStatName,
|
||||
value: number | [number, number],
|
||||
aux?: unknown
|
||||
): number | [number, number] {
|
||||
if (statName === 'warMagicTrialProb') {
|
||||
return (value as number) + 0.2;
|
||||
}
|
||||
if (statName === 'warMagicSuccessProb') {
|
||||
return (value as number) + 0.2;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
export const traitModule: TraitModule = {
|
||||
key: 'che_신산',
|
||||
name: '신산',
|
||||
info: '[계략] 화계·탈취·파괴·선동 : 성공률 +10%p<br>[전투] 계략 시도 확률 +20%p, 계략 성공 확률 +20%p',
|
||||
kind: 'war',
|
||||
getName: () => '신산',
|
||||
getInfo: () => '[계략] 화계·탈취·파괴·선동 : 성공률 +10%p<br>[전투] 계략 시도 확률 +20%p, 계략 성공 확률 +20%p',
|
||||
onCalcDomestic: (_context, turnType, varType, value, _aux) => {
|
||||
if (turnType === '계략') {
|
||||
if (varType === 'success') return value + 0.1;
|
||||
}
|
||||
return value;
|
||||
},
|
||||
onCalcStat,
|
||||
};
|
||||
@@ -0,0 +1,33 @@
|
||||
import type { GeneralActionContext } from '@sammo-ts/logic/triggers/general.js';
|
||||
import type { GeneralStatName, WarStatName } from '@sammo-ts/logic/triggers/types.js';
|
||||
import type { WarActionContext } from '@sammo-ts/logic/war/actions.js';
|
||||
import type { TraitModule } from '@sammo-ts/logic/triggers/special/types.js';
|
||||
|
||||
function onCalcStat(context: GeneralActionContext, statName: GeneralStatName, value: number, aux?: unknown): number;
|
||||
function onCalcStat(
|
||||
context: WarActionContext,
|
||||
statName: WarStatName,
|
||||
value: number | [number, number],
|
||||
aux?: unknown
|
||||
): number | [number, number];
|
||||
function onCalcStat(
|
||||
context: GeneralActionContext | WarActionContext,
|
||||
statName: GeneralStatName | WarStatName,
|
||||
value: number | [number, number],
|
||||
aux?: unknown
|
||||
): number | [number, number] {
|
||||
if (statName === 'warMagicSuccessProb') {
|
||||
return (value as number) + 1;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
export const traitModule: TraitModule = {
|
||||
key: 'che_신중',
|
||||
name: '신중',
|
||||
info: '[전투] 계략 성공 확률 100%',
|
||||
kind: 'war',
|
||||
getName: () => '신중',
|
||||
getInfo: () => '[전투] 계략 성공 확률 100%',
|
||||
onCalcStat,
|
||||
};
|
||||
@@ -0,0 +1,87 @@
|
||||
import type { GeneralActionContext } from '@sammo-ts/logic/triggers/general.js';
|
||||
import type { GeneralStatName, WarStatName } from '@sammo-ts/logic/triggers/types.js';
|
||||
import type { WarActionContext } from '@sammo-ts/logic/war/actions.js';
|
||||
import type { TraitModule } from '@sammo-ts/logic/triggers/special/types.js';
|
||||
import { BaseWarUnitTrigger, WarTriggerCaller } from '@sammo-ts/logic/war/triggers.js';
|
||||
import type { WarUnit } from '@sammo-ts/logic/war/units.js';
|
||||
import { LogFormat } from '@sammo-ts/logic/logging/types.js';
|
||||
|
||||
class che_위압시도 extends BaseWarUnitTrigger {
|
||||
constructor(unit: WarUnit) {
|
||||
super(unit, 10100);
|
||||
}
|
||||
|
||||
protected actionWar(
|
||||
self: WarUnit,
|
||||
oppose: WarUnit,
|
||||
_selfEnv: Record<string, unknown>,
|
||||
_opposeEnv: Record<string, unknown>
|
||||
): boolean {
|
||||
if (self.getPhase() !== 0 && oppose.getPhase() !== 0) {
|
||||
return true;
|
||||
}
|
||||
if (self.hasActivatedSkill('위압불가')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
self.activateSkill('위압');
|
||||
oppose.activateSkill('회피불가', '필살불가', '계략불가');
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
class che_위압발동 extends BaseWarUnitTrigger {
|
||||
constructor(unit: WarUnit) {
|
||||
super(unit, 40700);
|
||||
}
|
||||
|
||||
protected actionWar(
|
||||
self: WarUnit,
|
||||
oppose: WarUnit,
|
||||
_selfEnv: Record<string, unknown>,
|
||||
_opposeEnv: Record<string, unknown>
|
||||
): boolean {
|
||||
if (!self.hasActivatedSkill('위압')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
oppose.getLogger().pushGeneralBattleDetailLog('상대에게 <R>위압</>받았다!', LogFormat.PLAIN);
|
||||
self.getLogger().pushGeneralBattleDetailLog('상대에게 <C>위압</>을 줬다!', LogFormat.PLAIN);
|
||||
oppose.setWarPowerMultiply(0);
|
||||
if ('addAtmos' in oppose) {
|
||||
(oppose as any).addAtmos(-5);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
function onCalcStat(context: GeneralActionContext, statName: GeneralStatName, value: number, aux?: unknown): number;
|
||||
function onCalcStat(
|
||||
context: WarActionContext,
|
||||
statName: WarStatName,
|
||||
value: number | [number, number],
|
||||
aux?: unknown
|
||||
): number | [number, number];
|
||||
function onCalcStat(
|
||||
context: GeneralActionContext | WarActionContext,
|
||||
statName: GeneralStatName | WarStatName,
|
||||
value: number | [number, number],
|
||||
aux?: unknown
|
||||
): number | [number, number] {
|
||||
return value;
|
||||
}
|
||||
|
||||
export const traitModule: TraitModule = {
|
||||
key: 'che_위압',
|
||||
name: '위압',
|
||||
info: '[전투] 첫 페이즈 위압 발동(적 공격, 회피 불가, 사기 5 감소)',
|
||||
kind: 'war',
|
||||
getName: () => '위압',
|
||||
getInfo: () => '[전투] 첫 페이즈 위압 발동(적 공격, 회피 불가, 사기 5 감소)',
|
||||
getBattlePhaseTriggerList: (_context) => {
|
||||
if (!_context.unit) return null;
|
||||
return new WarTriggerCaller(new che_위압시도(_context.unit), new che_위압발동(_context.unit));
|
||||
},
|
||||
onCalcStat,
|
||||
};
|
||||
@@ -0,0 +1,128 @@
|
||||
import type { GeneralActionContext } from '@sammo-ts/logic/triggers/general.js';
|
||||
import type { GeneralStatName, WarStatName } from '@sammo-ts/logic/triggers/types.js';
|
||||
import type { WarActionContext } from '@sammo-ts/logic/war/actions.js';
|
||||
import type { TraitModule } from '@sammo-ts/logic/triggers/special/types.js';
|
||||
import { BaseWarUnitTrigger, WarTriggerCaller } from '@sammo-ts/logic/war/triggers.js';
|
||||
import type { WarUnit } from '@sammo-ts/logic/war/units.js';
|
||||
import { LogFormat } from '@sammo-ts/logic/logging/types.js';
|
||||
|
||||
class che_저격시도 extends BaseWarUnitTrigger {
|
||||
private readonly ratio: number;
|
||||
private readonly woundMin: number;
|
||||
private readonly woundMax: number;
|
||||
private readonly addAtmos: number;
|
||||
|
||||
constructor(unit: WarUnit, ratio: number, woundMin: number, woundMax: number, addAtmos = 20) {
|
||||
super(unit, 20100);
|
||||
this.ratio = ratio;
|
||||
this.woundMin = woundMin;
|
||||
this.woundMax = woundMax;
|
||||
this.addAtmos = addAtmos;
|
||||
}
|
||||
|
||||
protected actionWar(
|
||||
self: WarUnit,
|
||||
oppose: WarUnit,
|
||||
selfEnv: Record<string, unknown>,
|
||||
_opposeEnv: Record<string, unknown>
|
||||
): boolean {
|
||||
if (self.getPhase() !== 0 && oppose.getPhase() !== 0) {
|
||||
return true;
|
||||
}
|
||||
if (oppose.getPhase() < 0) {
|
||||
return true;
|
||||
}
|
||||
if (self.hasActivatedSkill('저격')) {
|
||||
return true;
|
||||
}
|
||||
if (self.hasActivatedSkill('저격불가')) {
|
||||
return true;
|
||||
}
|
||||
if (!self.rng.nextBool(this.ratio)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
self.activateSkill('저격');
|
||||
selfEnv['저격발동자'] = this.raiseType;
|
||||
selfEnv['woundMin'] = this.woundMin;
|
||||
selfEnv['woundMax'] = this.woundMax;
|
||||
selfEnv['addAtmos'] = this.addAtmos;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
class che_저격발동 extends BaseWarUnitTrigger {
|
||||
constructor(unit: WarUnit) {
|
||||
super(unit, 40100);
|
||||
}
|
||||
|
||||
protected actionWar(
|
||||
self: WarUnit,
|
||||
oppose: WarUnit,
|
||||
selfEnv: Record<string, unknown>,
|
||||
_opposeEnv: Record<string, unknown>
|
||||
): boolean {
|
||||
if (!self.hasActivatedSkill('저격')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ((selfEnv['저격발동자'] as number ?? -1) !== this.raiseType) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (selfEnv['저격발동'] as boolean ?? false) {
|
||||
return true;
|
||||
}
|
||||
selfEnv['저격발동'] = true;
|
||||
|
||||
if (oppose.constructor.name === 'WarUnitGeneral') {
|
||||
self.getLogger().pushGeneralActionLog('상대를 <C>저격</>했다!', LogFormat.PLAIN);
|
||||
self.getLogger().pushGeneralBattleDetailLog('상대를 <C>저격</>했다!', LogFormat.PLAIN);
|
||||
oppose.getLogger().pushGeneralActionLog('상대에게 <R>저격</>당했다!', LogFormat.PLAIN);
|
||||
oppose.getLogger().pushGeneralBattleDetailLog('상대에게 <R>저격</>당했다!', LogFormat.PLAIN);
|
||||
} else {
|
||||
self.getLogger().pushGeneralActionLog('성벽 수비대장을 <C>저격</>했다!', LogFormat.PLAIN);
|
||||
self.getLogger().pushGeneralBattleDetailLog('성벽 수비대장을 <C>저격</>했다!', LogFormat.PLAIN);
|
||||
}
|
||||
|
||||
if ('addAtmos' in self) {
|
||||
(self as any).addAtmos(selfEnv['addAtmos'] as number);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
function onCalcStat(context: GeneralActionContext, statName: GeneralStatName, value: number, aux?: unknown): number;
|
||||
function onCalcStat(
|
||||
context: WarActionContext,
|
||||
statName: WarStatName,
|
||||
value: number | [number, number],
|
||||
aux?: unknown
|
||||
): number | [number, number];
|
||||
function onCalcStat(
|
||||
context: GeneralActionContext | WarActionContext,
|
||||
statName: GeneralStatName | WarStatName,
|
||||
value: number | [number, number],
|
||||
aux?: unknown
|
||||
): number | [number, number] {
|
||||
return value;
|
||||
}
|
||||
|
||||
export const traitModule: TraitModule = {
|
||||
key: 'che_저격',
|
||||
name: '저격',
|
||||
info: '[전투] 새로운 상대와 전투 시 50% 확률로 저격 발동, 성공 시 사기+20',
|
||||
kind: 'war',
|
||||
getName: () => '저격',
|
||||
getInfo: () => '[전투] 새로운 상대와 전투 시 50% 확률로 저격 발동, 성공 시 사기+20',
|
||||
getBattlePhaseTriggerList: (_context) => {
|
||||
if (!_context.unit) return null;
|
||||
return new WarTriggerCaller(
|
||||
new che_저격시도(_context.unit, 0.5, 20, 40, 20),
|
||||
new che_저격발동(_context.unit)
|
||||
);
|
||||
},
|
||||
onCalcStat,
|
||||
};
|
||||
@@ -0,0 +1,33 @@
|
||||
import type { GeneralActionContext } from '@sammo-ts/logic/triggers/general.js';
|
||||
import type { GeneralStatName, WarStatName } from '@sammo-ts/logic/triggers/types.js';
|
||||
import type { WarActionContext } from '@sammo-ts/logic/war/actions.js';
|
||||
import type { TraitModule } from '@sammo-ts/logic/triggers/special/types.js';
|
||||
|
||||
function onCalcStat(context: GeneralActionContext, statName: GeneralStatName, value: number, aux?: unknown): number;
|
||||
function onCalcStat(
|
||||
context: WarActionContext,
|
||||
statName: WarStatName,
|
||||
value: number | [number, number],
|
||||
aux?: unknown
|
||||
): number | [number, number];
|
||||
function onCalcStat(
|
||||
context: GeneralActionContext | WarActionContext,
|
||||
statName: GeneralStatName | WarStatName,
|
||||
value: number | [number, number],
|
||||
aux?: unknown
|
||||
): number | [number, number] {
|
||||
if (statName === 'warMagicSuccessDamage') {
|
||||
return (value as number) * 1.5;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
export const traitModule: TraitModule = {
|
||||
key: 'che_집중',
|
||||
name: '집중',
|
||||
info: '[전투] 계략 성공 시 대미지 +50%',
|
||||
kind: 'war',
|
||||
getName: () => '집중',
|
||||
getInfo: () => '[전투] 계략 성공 시 대미지 +50%',
|
||||
onCalcStat,
|
||||
};
|
||||
@@ -0,0 +1,37 @@
|
||||
import type { GeneralActionContext } from '@sammo-ts/logic/triggers/general.js';
|
||||
import type { GeneralStatName, WarStatName } from '@sammo-ts/logic/triggers/types.js';
|
||||
import type { WarActionContext } from '@sammo-ts/logic/war/actions.js';
|
||||
import type { TraitModule } from '@sammo-ts/logic/triggers/special/types.js';
|
||||
|
||||
function onCalcStat(context: GeneralActionContext, statName: GeneralStatName, value: number, aux?: unknown): number;
|
||||
function onCalcStat(
|
||||
context: WarActionContext,
|
||||
statName: WarStatName,
|
||||
value: number | [number, number],
|
||||
aux?: unknown
|
||||
): number | [number, number];
|
||||
function onCalcStat(
|
||||
context: GeneralActionContext | WarActionContext,
|
||||
statName: GeneralStatName | WarStatName,
|
||||
value: number | [number, number],
|
||||
aux?: unknown
|
||||
): number | [number, number] {
|
||||
return value;
|
||||
}
|
||||
|
||||
export const traitModule: TraitModule = {
|
||||
key: 'che_척사',
|
||||
name: '척사',
|
||||
info: '[전투] 지역·도시 병종 상대로 대미지 +20%, 아군 피해 -20%',
|
||||
kind: 'war',
|
||||
getName: () => '척사',
|
||||
getInfo: () => '[전투] 지역·도시 병종 상대로 대미지 +20%, 아군 피해 -20%',
|
||||
getWarPowerMultiplier: (_context, _unit, oppose) => {
|
||||
const opposeCrewType = oppose.getCrewType();
|
||||
if (opposeCrewType.reqCities() || opposeCrewType.reqRegions()) {
|
||||
return [1.2, 0.8];
|
||||
}
|
||||
return [1, 1];
|
||||
},
|
||||
onCalcStat,
|
||||
};
|
||||
@@ -0,0 +1,63 @@
|
||||
import type { GeneralActionContext } from '@sammo-ts/logic/triggers/general.js';
|
||||
import type { GeneralStatName, WarStatName } from '@sammo-ts/logic/triggers/types.js';
|
||||
import type { WarActionContext } from '@sammo-ts/logic/war/actions.js';
|
||||
import type { TraitModule } from '@sammo-ts/logic/triggers/special/types.js';
|
||||
import { BaseWarUnitTrigger, WarTriggerCaller } from '@sammo-ts/logic/war/triggers.js';
|
||||
import type { WarUnit } from '@sammo-ts/logic/war/units.js';
|
||||
|
||||
class che_필살강화_회피불가 extends BaseWarUnitTrigger {
|
||||
constructor(unit: WarUnit) {
|
||||
super(unit, 20150);
|
||||
}
|
||||
|
||||
protected actionWar(
|
||||
self: WarUnit,
|
||||
oppose: WarUnit,
|
||||
_selfEnv: Record<string, unknown>,
|
||||
_opposeEnv: Record<string, unknown>
|
||||
): boolean {
|
||||
if (!self.hasActivatedSkill('필살')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
oppose.activateSkill('회피불가');
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
function onCalcStat(context: GeneralActionContext, statName: GeneralStatName, value: number, aux?: unknown): number;
|
||||
function onCalcStat(
|
||||
context: WarActionContext,
|
||||
statName: WarStatName,
|
||||
value: number | [number, number],
|
||||
aux?: unknown
|
||||
): number | [number, number];
|
||||
function onCalcStat(
|
||||
context: GeneralActionContext | WarActionContext,
|
||||
statName: GeneralStatName | WarStatName,
|
||||
value: number | [number, number],
|
||||
aux?: unknown
|
||||
): number | [number, number] {
|
||||
if (statName === 'warCriticalRatio') {
|
||||
return (value as number) + 0.3;
|
||||
}
|
||||
if (statName === 'criticalDamageRange') {
|
||||
const [rangeMin, rangeMax] = value as [number, number];
|
||||
return [(rangeMin + rangeMax) / 2, rangeMax];
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
export const traitModule: TraitModule = {
|
||||
key: 'che_필살',
|
||||
name: '필살',
|
||||
info: '[전투] 필살 확률 +30%p, 필살 발동시 대상 회피 불가, 필살 계수 향상',
|
||||
kind: 'war',
|
||||
getName: () => '필살',
|
||||
getInfo: () => '[전투] 필살 확률 +30%p, 필살 발동시 대상 회피 불가, 필살 계수 향상',
|
||||
getBattlePhaseTriggerList: (_context) => {
|
||||
if (!_context.unit) return null;
|
||||
return new WarTriggerCaller(new che_필살강화_회피불가(_context.unit));
|
||||
},
|
||||
onCalcStat,
|
||||
};
|
||||
@@ -0,0 +1,36 @@
|
||||
import type { GeneralActionContext } from '@sammo-ts/logic/triggers/general.js';
|
||||
import type { GeneralStatName, WarStatName } from '@sammo-ts/logic/triggers/types.js';
|
||||
import type { WarActionContext } from '@sammo-ts/logic/war/actions.js';
|
||||
import type { TraitModule } from '@sammo-ts/logic/triggers/special/types.js';
|
||||
|
||||
function onCalcStat(context: GeneralActionContext, statName: GeneralStatName, value: number, aux?: unknown): number;
|
||||
function onCalcStat(
|
||||
context: WarActionContext,
|
||||
statName: WarStatName,
|
||||
value: number | [number, number],
|
||||
aux?: unknown
|
||||
): number | [number, number];
|
||||
function onCalcStat(
|
||||
context: GeneralActionContext | WarActionContext,
|
||||
statName: GeneralStatName | WarStatName,
|
||||
value: number | [number, number],
|
||||
aux?: unknown
|
||||
): number | [number, number] {
|
||||
if (statName === 'warMagicSuccessProb') {
|
||||
return (value as number) + 0.1;
|
||||
}
|
||||
if (statName === 'warMagicSuccessDamage') {
|
||||
return (value as number) * 1.3;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
export const traitModule: TraitModule = {
|
||||
key: 'che_환술',
|
||||
name: '환술',
|
||||
info: '[전투] 계략 성공 확률 +10%p, 계략 성공 시 대미지 +30%',
|
||||
kind: 'war',
|
||||
getName: () => '환술',
|
||||
getInfo: () => '[전투] 계략 성공 확률 +10%p, 계략 성공 시 대미지 +30%',
|
||||
onCalcStat,
|
||||
};
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { TraitModule, TraitModuleExport } from '@sammo-ts/logic/triggers/special/types.js';
|
||||
|
||||
export const WAR_TRAIT_KEYS = ['che_의술', 'che_징병'] as const;
|
||||
export const WAR_TRAIT_KEYS = ['che_의술', 'che_징병', 'che_보병', 'che_궁병', 'che_기병', 'che_공성'] as const;
|
||||
|
||||
export type WarTraitKey = (typeof WAR_TRAIT_KEYS)[number];
|
||||
|
||||
@@ -11,6 +11,10 @@ export type WarTraitImporter = () => Promise<TraitModuleExport>;
|
||||
const defaultImporters: Record<WarTraitKey, WarTraitImporter> = {
|
||||
che_의술: async () => import('./che_의술.js'),
|
||||
che_징병: async () => import('./che_징병.js'),
|
||||
che_보병: async () => import('./che_보병.js'),
|
||||
che_궁병: async () => import('./che_궁병.js'),
|
||||
che_기병: async () => import('./che_기병.js'),
|
||||
che_공성: async () => import('./che_공성.js'),
|
||||
};
|
||||
|
||||
export const isWarTraitKey = (value: string): value is WarTraitKey => WAR_TRAIT_KEYS.includes(value as WarTraitKey);
|
||||
|
||||
@@ -4,6 +4,10 @@ export type TriggerActionPhase = '판매' | '구매';
|
||||
|
||||
export type TriggerDomesticActionType =
|
||||
| '상업'
|
||||
| '농업'
|
||||
| '성벽'
|
||||
| '수비'
|
||||
| '치안'
|
||||
| '인재탐색'
|
||||
| '징병'
|
||||
| '징집인구'
|
||||
|
||||
Reference in New Issue
Block a user