feat: 添加裸怪物临界计算

This commit is contained in:
unanmed 2026-10-10 18:45:29 +08:00
parent 1d0c7db3cc
commit 751f206829
8 changed files with 81 additions and 23 deletions

View File

@ -14,6 +14,7 @@
"@user/client-system": "workspace:*",
"@user/data-common": "workspace:*",
"@user/data-base": "workspace:*",
"@user/data-system": "workspace:*",
"@user/data-state": "workspace:*"
}
}

View File

@ -677,7 +677,7 @@ describe('真实大地图战斗场景性能', () => {
const record = measureCase('全图怪物单次临界计算', label, () => {
for (const entry of fixture.state.enemyContext.iterateEnemy()) {
const view = entry[1];
for (const _critical of fixture.damageSystem.calculateCritical(
for (const _critical of fixture.damageSystem.calculateViewCritical(
view,
'atk'
)) {

View File

@ -5,8 +5,8 @@ import {
IReadonlyEnemyHandler
} from '@user/data-system';
import { IEnemyAttr, IHeroAttr } from '@user/data-common';
import { IVampireValue } from './special';
import { logger } from '@motajs/common';
import { ISpecial9Value } from './special';
export class MainDamageCalculator implements IDamageCalculator<
IEnemyAttr,
@ -51,7 +51,7 @@ export class MainDamageCalculator implements IDamageCalculator<
}
// 吸血
const vampire = enemy.getSpecial<IVampireValue>(11);
const vampire = enemy.getSpecial<ISpecial9Value>(11);
if (vampire) {
const value = (vampire.value.vampire / 100) * hp;
damage += value;

View File

@ -253,7 +253,7 @@ describe('临界计算性能', () => {
measureCase('临界计算', String(count), () => {
for (let i = 0; i < count; i++) {
for (const _ of damageContext.calculateCritical(
for (const _ of damageContext.calculateViewCritical(
fixture.view,
'atk'
)) {
@ -275,7 +275,7 @@ describe('临界计算性能', () => {
measureCase('临界计算', String(count), () => {
for (let i = 0; i < count; i++) {
for (const _ of damageContext.calculateCritical(
for (const _ of damageContext.calculateViewCritical(
fixture.view,
'atk'
)) {
@ -297,7 +297,7 @@ describe('临界计算性能', () => {
measureCase('临界计算', String(count), () => {
for (let i = 0; i < count; i++) {
for (const _ of damageContext.calculateCritical(
for (const _ of damageContext.calculateViewCritical(
fixture.view,
'atk'
)) {

View File

@ -514,7 +514,7 @@ describe('DamageContext critical generation', () => {
);
const first = context
.calculateCritical(fixture.view, 'atk')
.calculateViewCritical(fixture.view, 'atk')
.next().value!;
expect(first.baseValue).toBe(0);
@ -533,7 +533,7 @@ describe('DamageContext critical generation', () => {
fixture.hero
);
const results = [...context.calculateCritical(fixture.view, 'atk')];
const results = [...context.calculateViewCritical(fixture.view, 'atk')];
expect(results).toEqual([]);
});
@ -558,13 +558,16 @@ describe('DamageContext critical generation', () => {
);
const heroResult = modules.logger.catch(() => [
...noHero.calculateCritical(fixture.view, 'atk')
...noHero.calculateViewCritical(fixture.view, 'atk')
]);
const calculatorResult = modules.logger.catch(() => [
...noCalculator.calculateCritical(fixture.view, 'atk')
...noCalculator.calculateViewCritical(fixture.view, 'atk')
]);
const locatorResult = modules.logger.catch(() => [
...withLocator.calculateCritical(createUnknownView(fixture), 'atk')
...withLocator.calculateViewCritical(
createUnknownView(fixture),
'atk'
)
]);
expect(heroResult.ret).toEqual([]);
@ -585,7 +588,7 @@ describe('DamageContext critical generation', () => {
);
const first = context
.calculateCritical(fixture.view, 'atk')
.calculateViewCritical(fixture.view, 'atk')
.next().value!;
expect(first.nextValue).toBe(1);

View File

@ -10,7 +10,8 @@ import {
IEnemyDamageInfo,
IReadonlyEnemyHandler,
IEnemyView,
IEnemyDamageInfoBase
IEnemyDamageInfoBase,
IReadonlyDamageHandler
} from './types';
import {
IHeroAttribute,
@ -57,9 +58,9 @@ export class DamageContext<TEnemy, THero> implements IDamageContext<
*/
private createReadonlyHandler(
enemy: IReadonlyEnemy<TEnemy>,
locator: ITileLocator,
locator: ITileLocator | null,
hero: IReadonlyHeroAttribute<THero>
): IReadonlyEnemyHandler<TEnemy, THero> {
): IReadonlyDamageHandler<TEnemy, THero> {
return {
enemy,
context: this.context,
@ -144,7 +145,7 @@ export class DamageContext<TEnemy, THero> implements IDamageContext<
* @param maxIterations 最大迭代数量
*/
private findNextCritical(
handler: IReadonlyEnemyHandler<TEnemy, THero>,
handler: IReadonlyDamageHandler<TEnemy, THero>,
hero: IHeroAttribute<THero>,
attribute: CriticalableHeroStatus<THero>,
currentValue: number,
@ -181,7 +182,7 @@ export class DamageContext<TEnemy, THero> implements IDamageContext<
};
}
*calculateCritical(
*calculateViewCritical(
view: IEnemyView<TEnemy>,
attribute: CriticalableHeroStatus<THero>,
precision: number = 12
@ -196,9 +197,31 @@ export class DamageContext<TEnemy, THero> implements IDamageContext<
}
const locator = this.context.getEnemyLocatorByView(view);
if (!locator) return;
const enemy = view.getComputedEnemy();
yield* this.calculateEnemyCritical(
enemy,
attribute,
locator,
precision
);
}
*calculateEnemyCritical(
enemy: IReadonlyEnemy<TEnemy>,
attribute: CriticalableHeroStatus<THero>,
locator: ITileLocator | null,
precision: number = 12
): Generator<IEnemyCritical, void, void> {
if (!this.heroStatus) {
logger.warn(107);
return;
}
if (!this.calculator) {
logger.warn(106);
return;
}
const hero = this.heroStatus.getModifiableClone();
const handler = this.createReadonlyHandler(enemy, locator, hero);

View File

@ -36,6 +36,20 @@ export interface IReadonlyEnemyHandler<
readonly hero: IReadonlyHeroAttribute<THero>;
}
export interface IReadonlyDamageHandler<
TEnemy,
THero
> extends IDataBaseExtended {
/** 怪物属性信息 */
readonly enemy: IReadonlyEnemy<TEnemy>;
/** 怪物上下文 */
readonly context: IReadonlyEnemyContext<TEnemy, THero>;
/** 怪物定位符 */
readonly locator: ITileLocator | null;
/** 勇士属性信息 */
readonly hero: IReadonlyHeroAttribute<THero>;
}
//#endregion
//#region 怪物对象
@ -369,7 +383,7 @@ export interface IEnemyDamageInfoBase {
export interface IEnemyDamageInfo<TEnemy, THero> extends IEnemyDamageInfoBase {
/** 信息对象 */
readonly handler: IReadonlyEnemyHandler<TEnemy, THero>;
readonly handler: IReadonlyDamageHandler<TEnemy, THero>;
}
export interface IEnemyCritical {
@ -397,7 +411,7 @@ export interface IDamageCalculator<TEnemy, THero> {
* @param handler 信息对象
*/
calculate(
handler: IReadonlyEnemyHandler<TEnemy, THero>
handler: IReadonlyDamageHandler<TEnemy, THero>
): IEnemyDamageInfoBase;
/**
@ -406,7 +420,7 @@ export interface IDamageCalculator<TEnemy, THero> {
* @param attribute 勇士的临界属性
*/
getCriticalLimit(
handler: IReadonlyEnemyHandler<TEnemy, THero>,
handler: IReadonlyDamageHandler<TEnemy, THero>,
attribute: CriticalableHeroStatus<THero>
): number;
}
@ -442,11 +456,25 @@ export interface IDamageContext<TEnemy, THero> extends IDataBaseExtended {
* @param attribute 计算临界的目标勇士属性,比如计算攻击临界、自定义属性的临界等等
* @param precision 临界计算精度,表示会进行多少次二分计算,一般填写 `12-16` 之间的数即可,默认是 12
*/
calculateCritical(
calculateViewCritical(
enemy: IEnemyView<TEnemy>,
attribute: CriticalableHeroStatus<THero>,
precision?: number
): Generator<IEnemyCritical, void, void>;
/**
* 计算怪物在指定勇士属性下的临界
* @param enemy 怪物只读对象
* @param attribute 计算临界的目标勇士属性,比如计算攻击临界、自定义属性的临界等等
* @param locator 怪物所在位置
* @param precision 临界计算精度,表示会进行多少次二分计算,一般填写 `12-16` 之间的数即可,默认是 12
*/
calculateEnemyCritical(
enemy: IReadonlyEnemy<TEnemy>,
attribute: CriticalableHeroStatus<THero>,
locator: ITileLocator | null,
precision?: number
): Generator<IEnemyCritical, void, void>;
}
export interface IDamageSystem<TEnemy, THero> extends IDamageContext<

View File

@ -461,6 +461,9 @@ importers:
'@user/data-state':
specifier: workspace:*
version: link:../data-state
'@user/data-system':
specifier: workspace:*
version: link:../data-system
packages-user/client-system:
dependencies: