mirror of
https://github.com/motajs/template.git
synced 2026-05-02 12:23:13 +08:00
refactor: 伤害计算
This commit is contained in:
parent
4d9c6720aa
commit
79d06c31df
5
dev.md
5
dev.md
@ -54,8 +54,9 @@
|
||||
- 没用到的变量、方法使用下划线开头。
|
||||
- 合理运用 `readonly` `protected` `private` 关键字。
|
||||
- 函数不建议使用过多可选参数,如果可选参数过多,可以考虑换用对象。
|
||||
- 尽量少地使用 `as` 关键字进行类型断言,一般情况下不建议进行任何 `as` 类型断言
|
||||
- 尽量少地使用 `as` 关键字进行类型断言,一般情况下不建议进行任何 `as` 类型断言。
|
||||
- 其他要求:
|
||||
- 严格遵循 `eslint` 配置,不允许出现 `eslint` 报错。
|
||||
- 尽量不使用 `?.` 运算符,一般建议仅在副作用函数调用(如 `this.obj?.func()`,`this.obj.func?.()`),或对象 `Required` 化(如 `{ value: obj?.value ?? 0 }`)中使用 `?.` 运算符
|
||||
- 尽量不使用 `?.` 运算符,一般建议仅在副作用函数调用(如 `this.obj?.func()`,`this.obj.func?.()`),或对象 `Required` 化(如 `{ value: obj?.value ?? 0 }`)中使用 `?.` 运算符。
|
||||
- 只进行必要的非空判断,不必要的非空判断直接使用非空断言 `!` 实现。
|
||||
- 除非参数要求传入函数等情况,不建议在函数内写任何局部函数。
|
||||
|
||||
@ -11,6 +11,7 @@ import {
|
||||
IReadonlyEnemy
|
||||
} from './types';
|
||||
import { IHeroAttribute, IReadonlyHeroAttribute } from '../hero';
|
||||
import { clamp } from 'lodash-es';
|
||||
|
||||
interface ICriticalSearchResult {
|
||||
/** 此临界点的属性值 */
|
||||
@ -84,6 +85,37 @@ export class DamageSystem<TAttr, THero> implements IDamageSystem<TAttr, THero> {
|
||||
return info;
|
||||
}
|
||||
|
||||
getDamageInfoByComputed(
|
||||
enemy: IReadonlyEnemy<TAttr>
|
||||
): IEnemyDamageInfo | null {
|
||||
if (!this.heroStatus) {
|
||||
logger.warn(107);
|
||||
return null;
|
||||
}
|
||||
if (!this.calculator) {
|
||||
logger.warn(106);
|
||||
return null;
|
||||
}
|
||||
|
||||
const hero = this.heroStatus;
|
||||
if (!hero) return null;
|
||||
const view = this.context.getViewByComputed(enemy);
|
||||
if (!view) return null;
|
||||
const locator = this.context.getEnemyLocatorByView(view);
|
||||
if (!locator) return null;
|
||||
|
||||
const cached = this.cache.get(view);
|
||||
if (cached) {
|
||||
return cached;
|
||||
}
|
||||
|
||||
const handler = this.createReadonlyHandler(enemy, locator, hero);
|
||||
const info = this.calculator.calculate(handler);
|
||||
this.cache.set(view, info);
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
markDirty(enemy: IEnemyView<TAttr>): void {
|
||||
this.cache.delete(enemy);
|
||||
}
|
||||
@ -127,7 +159,8 @@ export class DamageSystem<TAttr, THero> implements IDamageSystem<TAttr, THero> {
|
||||
|
||||
if (currentValue >= upperLimit) return;
|
||||
|
||||
const maxIterations = Math.max(0, Math.floor(precision));
|
||||
// 超过 64 位的精度没有意义,所以最高设置为 64
|
||||
const maxIterations = clamp(Math.floor(precision), 4, 64);
|
||||
let baseValue = currentValue;
|
||||
let baseInfo = currentInfo;
|
||||
|
||||
|
||||
@ -650,6 +650,14 @@ export interface IDamageSystem<TAttr, THero> {
|
||||
*/
|
||||
getDamageInfo(enemy: IEnemyView<TAttr>): IEnemyDamageInfo | null;
|
||||
|
||||
/**
|
||||
* 根据怪物对象获取战斗伤害信息
|
||||
* @param enemy 怪物对象
|
||||
*/
|
||||
getDamageInfoByComputed(
|
||||
enemy: IReadonlyEnemy<TAttr>
|
||||
): IEnemyDamageInfo | null;
|
||||
|
||||
/**
|
||||
* 将指定的怪物标记为脏
|
||||
* @param enemy 怪物视图
|
||||
|
||||
@ -18,6 +18,7 @@ import {
|
||||
CommonAuraConverter,
|
||||
EnemyLegacyBridge,
|
||||
GuardAuraConverter,
|
||||
MainDamageCalculator,
|
||||
MainEnemyFinalEffect,
|
||||
MainMapDamageConverter,
|
||||
MainMapDamageReducer,
|
||||
@ -67,6 +68,7 @@ export class CoreState implements ICoreState {
|
||||
// 怪物上下文初始化
|
||||
const enemyContext = new EnemyContext<IEnemyAttr, IHeroAttr>();
|
||||
const damageSystem = new DamageSystem(enemyContext);
|
||||
damageSystem.useCalculator(new MainDamageCalculator());
|
||||
const mapDamage = new MapDamage(enemyContext);
|
||||
mapDamage.useConverter(new MainMapDamageConverter());
|
||||
mapDamage.useReducer(new MainMapDamageReducer());
|
||||
|
||||
177
packages-user/data-state/src/enemy/calculator.ts
Normal file
177
packages-user/data-state/src/enemy/calculator.ts
Normal file
@ -0,0 +1,177 @@
|
||||
import {
|
||||
CriticalableHeroStatus,
|
||||
IDamageCalculator,
|
||||
IEnemyDamageInfo,
|
||||
IReadonlyEnemyHandler
|
||||
} from '@user/data-base';
|
||||
import { IEnemyAttr } from './types';
|
||||
import { IVampireValue } from './special';
|
||||
import { IHeroAttr } from '../hero';
|
||||
|
||||
export class MainDamageCalculator implements IDamageCalculator<
|
||||
IEnemyAttr,
|
||||
IHeroAttr
|
||||
> {
|
||||
/** 当前是否正在计算支援怪的伤害 */
|
||||
private inGuard: boolean = false;
|
||||
|
||||
/**
|
||||
* 计算战斗伤害信息
|
||||
* @param handler 信息对象
|
||||
*/
|
||||
calculate(
|
||||
handler: IReadonlyEnemyHandler<IEnemyAttr, IHeroAttr>
|
||||
): IEnemyDamageInfo {
|
||||
const { enemy, locator, hero } = handler;
|
||||
const hp = hero.getBaseAttribute('hp');
|
||||
const atk = hero.getFinalAttribute('atk');
|
||||
const def = hero.getFinalAttribute('def');
|
||||
const mdef = this.inGuard ? 0 : hero.getFinalAttribute('mdef');
|
||||
// 支援中魔防只会被计算一次,因此除了当前怪物,计算其他怪物伤害时魔防为 0
|
||||
const monAtk = enemy.getAttribute('atk');
|
||||
const monDef = enemy.getAttribute('def');
|
||||
let monHp = enemy.getAttribute('hp');
|
||||
|
||||
// 无敌
|
||||
if (enemy.hasSpecial(20) && core.itemCount('cross') < 1) {
|
||||
return { damage: Infinity, turn: 0 };
|
||||
}
|
||||
|
||||
/** 怪物会对勇士造成的总伤害 */
|
||||
let damage = 0;
|
||||
|
||||
/** 勇士每轮造成的伤害 */
|
||||
let heroPerDamage = 0;
|
||||
/** 怪物每轮造成的伤害 */
|
||||
let enemyPerDamage = 0;
|
||||
|
||||
// 勇士每轮伤害为勇士攻击减去怪物防御
|
||||
heroPerDamage += atk - monDef;
|
||||
if (heroPerDamage <= 0) {
|
||||
return { damage: Infinity, turn: 0 };
|
||||
}
|
||||
|
||||
// 吸血
|
||||
const vampire = enemy.getSpecial<IVampireValue>(11);
|
||||
if (vampire) {
|
||||
const value = (vampire.value.vampire / 100) * hp;
|
||||
damage += value;
|
||||
// 如果吸血加到自身
|
||||
if (vampire.value.add) {
|
||||
monHp += value;
|
||||
}
|
||||
}
|
||||
|
||||
// 魔攻
|
||||
if (enemy.hasSpecial(2)) {
|
||||
enemyPerDamage = monAtk;
|
||||
} else {
|
||||
enemyPerDamage = monAtk - def;
|
||||
}
|
||||
|
||||
// 连击
|
||||
if (enemy.hasSpecial(4)) enemyPerDamage *= 2;
|
||||
if (enemy.hasSpecial(5)) enemyPerDamage *= 3;
|
||||
|
||||
const multiHit = enemy.getSpecial<number>(6);
|
||||
if (multiHit) {
|
||||
enemyPerDamage *= multiHit.value;
|
||||
}
|
||||
|
||||
if (enemyPerDamage < 0) enemyPerDamage = 0;
|
||||
|
||||
let turn = Math.ceil(monHp / heroPerDamage);
|
||||
|
||||
// 支援,当怪物被支援且不包含支援标记时执行,因为支援怪不能再被支援了
|
||||
const guards = enemy.getAttribute('guard');
|
||||
if (guards.size > 0 && !this.inGuard) {
|
||||
this.inGuard = true;
|
||||
// 计算支援怪的伤害,同时把打支援怪花费的回合数加到当前怪物上,因为打支援怪的时候当前怪物也会打你
|
||||
// 因此回合数需要加上打支援怪的回合数
|
||||
for (const guard of guards) {
|
||||
// 直接把 enemy 传过去,因此支援的 enemy 会吃到其原本所在位置的光环加成
|
||||
const extraInfo = this.calculate({
|
||||
enemy: guard.getComputedEnemy(),
|
||||
locator,
|
||||
hero
|
||||
});
|
||||
turn += extraInfo.turn;
|
||||
damage += extraInfo.damage;
|
||||
}
|
||||
this.inGuard = false;
|
||||
}
|
||||
|
||||
// 先攻
|
||||
if (enemy.hasSpecial(1)) {
|
||||
damage += enemyPerDamage;
|
||||
}
|
||||
|
||||
// 破甲
|
||||
const breakArmor = enemy.getSpecial<number>(7);
|
||||
if (breakArmor) {
|
||||
damage += (breakArmor.value / 100) * def;
|
||||
}
|
||||
|
||||
// 反击
|
||||
const counterAttack = enemy.getSpecial<number>(8);
|
||||
if (counterAttack) {
|
||||
// 反击是每回合生效,因此加到 enemyPerDamage 上
|
||||
enemyPerDamage += (counterAttack.value / 100) * atk;
|
||||
}
|
||||
|
||||
// 净化
|
||||
const purify = enemy.getSpecial<number>(9);
|
||||
if (purify) {
|
||||
damage += purify.value * mdef;
|
||||
}
|
||||
|
||||
damage += (turn - 1) * enemyPerDamage;
|
||||
|
||||
// 魔防
|
||||
damage -= mdef;
|
||||
|
||||
// 未开启负伤时,如果伤害为负,则设为 0
|
||||
if (!core.flags.enableNegativeDamage && damage < 0) {
|
||||
damage = 0;
|
||||
}
|
||||
|
||||
// 固伤,无法被魔防减伤
|
||||
const fixedDamage = enemy.getSpecial<number>(22);
|
||||
if (fixedDamage) {
|
||||
damage += fixedDamage.value;
|
||||
}
|
||||
|
||||
// 仇恨,无法被魔防减伤
|
||||
if (enemy.hasSpecial(17)) {
|
||||
damage += core.getFlag('hatred', 0);
|
||||
}
|
||||
|
||||
return {
|
||||
damage: Math.floor(damage),
|
||||
turn
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取临界计算的上界
|
||||
* @param handler 信息对象
|
||||
* @param attribute 目标属性名
|
||||
*/
|
||||
getCriticalLimit(
|
||||
handler: IReadonlyEnemyHandler<IEnemyAttr, IHeroAttr>,
|
||||
attribute: CriticalableHeroStatus<IHeroAttr>
|
||||
): number {
|
||||
switch (attribute) {
|
||||
case 'atk': {
|
||||
if (handler.enemy.hasSpecial(3)) {
|
||||
return Infinity;
|
||||
}
|
||||
return (
|
||||
handler.enemy.getAttribute('def') +
|
||||
handler.enemy.getAttribute('hp')
|
||||
);
|
||||
}
|
||||
}
|
||||
return handler.hero.getFinalAttribute(attribute);
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,5 @@
|
||||
export * from './aura';
|
||||
export * from './calculator';
|
||||
export * from './damage';
|
||||
export * from './final';
|
||||
export * from './legacy';
|
||||
|
||||
@ -20,7 +20,8 @@ import {
|
||||
IReadonlyEnemyHandler,
|
||||
ISpecial,
|
||||
IMapDamageView,
|
||||
IReadonlyHeroAttribute
|
||||
IReadonlyHeroAttribute,
|
||||
IReadonlyEnemy
|
||||
} from '@user/data-base';
|
||||
import { IZoneValue } from './special';
|
||||
import { IEnemyAttr, MapDamageType } from './types';
|
||||
@ -182,6 +183,7 @@ export class LaserDamageView extends BaseMapDamageView<IRayRangeParam> {
|
||||
export class BetweenDamageView extends BaseMapDamageView<IManhattanRangeParam> {
|
||||
constructor(
|
||||
context: IEnemyContext<IEnemyAttr, IHeroAttr>,
|
||||
private readonly enemy: IReadonlyEnemy<IEnemyAttr>,
|
||||
private readonly locator: Readonly<ITileLocator>,
|
||||
private readonly hero: IReadonlyHeroAttribute<IHeroAttr>
|
||||
) {
|
||||
@ -224,12 +226,28 @@ export class BetweenDamageView extends BaseMapDamageView<IManhattanRangeParam> {
|
||||
if (!other) {
|
||||
return null;
|
||||
}
|
||||
if (!other.getComputedEnemy().hasSpecial(16)) {
|
||||
const otherEnemy = other.getComputedEnemy();
|
||||
if (!otherEnemy.hasSpecial(16)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const damage = this.hero.getFinalAttribute('hp');
|
||||
return this.createInfo(damage, MapDamageType.Between);
|
||||
const half = this.hero.getFinalAttribute('hp') / 2;
|
||||
if (core.flags.betweenAttackMax) {
|
||||
// 夹击不超伤害值,需要获取两个怪物的伤害
|
||||
const sys = this.context.getDamageSystem();
|
||||
if (!sys) {
|
||||
return this.createInfo(half, MapDamageType.Between);
|
||||
} else {
|
||||
const currInfo = sys.getDamageInfoByComputed(this.enemy);
|
||||
const otherInfo = sys.getDamageInfoByComputed(otherEnemy);
|
||||
const currDamage = currInfo?.damage ?? Infinity;
|
||||
const otherDamage = otherInfo?.damage ?? Infinity;
|
||||
const min = Math.min(half, currDamage, otherDamage);
|
||||
return this.createInfo(min, MapDamageType.Between);
|
||||
}
|
||||
} else {
|
||||
return this.createInfo(half, MapDamageType.Between);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -273,7 +291,7 @@ export class MainMapDamageConverter implements IMapDamageConverter<
|
||||
context: IEnemyContext<IEnemyAttr, IHeroAttr>
|
||||
): IMapDamageView<any>[] {
|
||||
const views: IMapDamageView<any>[] = [];
|
||||
const { enemy, locator } = handler;
|
||||
const { enemy, locator, hero } = handler;
|
||||
|
||||
const zone = enemy.getSpecial<IZoneValue>(15);
|
||||
if (zone) {
|
||||
@ -281,7 +299,7 @@ export class MainMapDamageConverter implements IMapDamageConverter<
|
||||
}
|
||||
|
||||
if (enemy.hasSpecial(16)) {
|
||||
views.push(new BetweenDamageView(context, locator, handler.hero));
|
||||
views.push(new BetweenDamageView(context, enemy, locator, hero));
|
||||
}
|
||||
|
||||
const repulse = enemy.getSpecial<number>(18);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user