refactor: Layer2 数据拆分至 data-system

This commit is contained in:
AncTe committed 2026-05-16 17:06:18 +08:00
1 parent 80b73938b4
commit 9ada0796b3
20 files changed
+110 -155

No files matched your search

@@ -1,912 +0,0 @@
import { IRange, ITileLocator, logger } from '@motajs/common';
import {
IAuraConverter,
IAuraView,
IDamageSystem,
IEnemyAuraView,
IEnemyCommonQueryEffect,
IEnemyContext,
IEnemyFinalEffect,
IEnemyHandler,
IEnemySpecialModifier,
IEnemySpecialQueryEffect,
IEnemyView,
IMapDamage,
IReadonlyEnemyHandler
} from './types';
import { IReadonlyHeroAttribute } from '../hero';
import { IEnemy, IReadonlyEnemy, ISpecial } from '../enemy';
import { EnemyView } from './enemy';
import { ILocationIndexer, MapLocIndexer } from '../common/indexer';
import { IStateBase } from '../types';
export class EnemyContext<TEnemy, THero> implements IEnemyContext<
TEnemy,
THero
> {
/** 坐标索引 -> 怪物视图 */
private readonly enemyViewMap: Map<number, EnemyView<TEnemy>> = new Map();
/** 坐标索引 -> 计算前怪物对象 */
private readonly enemyMap: Map<number, IEnemy<TEnemy>> = new Map();
/** 怪物视图 -> 坐标索引 */
private readonly locatorViewMap: Map<IEnemyView<TEnemy>, number> =
new Map();
/** 计算前怪物对象 -> 坐标索引 */
private readonly locatorEnemyMap: Map<IEnemy<TEnemy>, number> = new Map();
/** 计算后怪物对象 -> 怪物视图 */
private readonly computedToView: Map<
IReadonlyEnemy<TEnemy>,
EnemyView<TEnemy>
> = new Map();
/** 当前已注册的光环转换器 */
private readonly auraConverter: Set<IAuraConverter<TEnemy, THero>> =
new Set();
/** 光环转换器是否启用 */
private readonly converterStatus: Map<
IAuraConverter<TEnemy, THero>,
boolean
> = new Map();
/** 所有已被转换的光环 */
private readonly convertedAura: Map<ISpecial<any>, IAuraView<TEnemy>> =
new Map();
/** 普通查询效果注册,特殊属性 -> 此特殊属性的查询效果列表,按照优先级从高到低排序 */
private readonly commonQueryMap: Map<
number,
IEnemyCommonQueryEffect<TEnemy, THero>[]
> = new Map();
/** 特殊查询效果注册,特殊属性 -> 此特殊属性的特殊查询效果列表,按照优先级从高到低排序 */
private readonly specialQueryEffects: Map<
number,
IEnemySpecialQueryEffect<TEnemy, THero>[]
> = new Map();
/** 最终效果列表,按照优先级从高到低排列 */
private readonly finalEffects: IEnemyFinalEffect<TEnemy, THero>[] = [];
/** 添加的无来源全局光环列表 */
private readonly globalAuraList: Set<IAuraView<TEnemy>> = new Set();
/** 排序后的光环视图,视图优先级 -> 光环视图列表 */
private readonly sortedAura: Map<number, Set<IAuraView<TEnemy>>> =
new Map();
/** 当怪物更新后,需要对上下文进行全量刷新的怪物列表 */
private readonly needTotallyRefresh: Set<IEnemyView<TEnemy>> = new Set();
/** 所有实际查询了上下文的常规查询效果,这些怪物需要在上下文或其他怪物刷新时一并刷新 */
private readonly requestedCommonContext: Set<IEnemyView<TEnemy>> =
new Set();
/** 所有需要被标记为脏的怪物 */
private readonly dirtyEnemy: Set<IEnemyView<TEnemy>> = new Set();
/** 当前绑定的勇士属性对象 */
private bindedHero: IReadonlyHeroAttribute<THero> | null = null;
/** 地图伤害对象 */
private mapDamage: IMapDamage<TEnemy, THero> | null = null;
/** 伤害系统对象 */
private damageSystem: IDamageSystem<TEnemy, THero> | null = null;
/** 索引工具 */
readonly indexer: ILocationIndexer = new MapLocIndexer();
/** 当前是否需要全量刷新 */
private needUpdate: boolean = true;
built: boolean = false;
width: number = 0;
height: number = 0;
constructor(readonly dataState: IStateBase<TEnemy, THero>) {}
resize(width: number, height: number): void {
this.clear();
this.width = width;
this.height = height;
this.indexer.setWidth(width);
this.needUpdate = true;
}
registerAuraConverter(converter: IAuraConverter<TEnemy, THero>): void {
this.auraConverter.add(converter);
this.converterStatus.set(converter, true);
this.needUpdate = true;
}
unregisterAuraConverter(converter: IAuraConverter<TEnemy, THero>): void {
this.auraConverter.delete(converter);
this.converterStatus.delete(converter);
this.needUpdate = true;
}
setAuraConverterEnabled(
converter: IAuraConverter<TEnemy, THero>,
enabled: boolean
): void {
if (!this.auraConverter.has(converter)) return;
this.converterStatus.set(converter, enabled);
this.needUpdate = true;
}
registerCommonQueryEffect(
code: number,
effect: IEnemyCommonQueryEffect<TEnemy, THero>
): void {
const array = this.commonQueryMap.getOrInsert(code, []);
array.push(effect);
array.sort((a, b) => b.priority - a.priority);
this.needUpdate = true;
}
unregisterCommonQueryEffect(
code: number,
effect: IEnemyCommonQueryEffect<TEnemy, THero>
): void {
const array = this.commonQueryMap.get(code);
if (!array) return;
const index = array.indexOf(effect);
if (index === -1) return;
array.splice(index, 1);
this.needUpdate = true;
}
registerSpecialQueryEffect(
effect: IEnemySpecialQueryEffect<TEnemy, THero>
): void {
const list = this.specialQueryEffects.getOrInsert(effect.priority, []);
list.push(effect);
this.needUpdate = true;
}
unregisterSpecialQueryEffect(
effect: IEnemySpecialQueryEffect<TEnemy, THero>
): void {
const list = this.specialQueryEffects.get(effect.priority);
if (!list) return;
const index = list.indexOf(effect);
if (index !== -1) {
list.splice(index, 1);
}
if (list.length === 0) {
this.specialQueryEffects.delete(effect.priority);
}
this.needUpdate = true;
}
registerFinalEffect(effect: IEnemyFinalEffect<TEnemy, THero>): void {
this.finalEffects.push(effect);
this.finalEffects.sort((a, b) => b.priority - a.priority);
this.needUpdate = true;
}
unregisterFinalEffect(effect: IEnemyFinalEffect<TEnemy, THero>): void {
const index = this.finalEffects.indexOf(effect);
if (index !== -1) {
this.finalEffects.splice(index, 1);
}
this.needUpdate = true;
}
bindHero(hero: IReadonlyHeroAttribute<THero> | null): void {
this.bindedHero = hero;
this.needUpdate = true;
this.damageSystem?.bindHeroStatus(hero);
this.mapDamage?.refreshAll();
}
getBindedHero(): IReadonlyHeroAttribute<THero> | null {
return this.bindedHero;
}
/**
* 创建可修改信息对象
* @param enemy 怪物对象
* @param locator 怪物位置
*/
private createHandler(
enemy: IEnemy<TEnemy>,
locator: ITileLocator
): IEnemyHandler<TEnemy, THero> {
return { enemy, locator, hero: this.bindedHero!, data: this.dataState };
}
getEnemyLocator(enemy: IEnemy<TEnemy>): Readonly<ITileLocator> | null {
const index = this.locatorEnemyMap.get(enemy);
if (index === undefined) return null;
return this.indexer.indexToLocator(index);
}
getEnemyLocatorByView(
view: IEnemyView<TEnemy>
): Readonly<ITileLocator> | null {
const index = this.locatorViewMap.get(view);
if (index === undefined) return null;
return this.indexer.indexToLocator(index);
}
getEnemyByLocator(locator: ITileLocator): IEnemyView<TEnemy> | null {
const index = this.indexer.locToIndex(locator.x, locator.y);
return this.enemyViewMap.get(index) ?? null;
}
getEnemyByLoc(x: number, y: number): IEnemyView<TEnemy> | null {
const index = this.indexer.locToIndex(x, y);
return this.enemyViewMap.get(index) ?? null;
}
getViewByComputed(
enemy: IReadonlyEnemy<TEnemy>
): IEnemyView<TEnemy> | null {
return this.computedToView.get(enemy) ?? null;
}
/**
* 删除指定索引位置的怪物以及与之关联的所有运行时状态
* @param index 地图索引
*/
private deleteEnemyAt(index: number) {
const view = this.enemyViewMap.get(index);
const enemy = this.enemyMap.get(index);
if (!view || !enemy) return;
this.needUpdate = true;
if (this.mapDamage) {
this.mapDamage.deleteEnemy(view);
}
if (this.damageSystem) {
this.damageSystem.deleteEnemy(view);
}
this.needTotallyRefresh.delete(view);
this.dirtyEnemy.delete(view);
this.requestedCommonContext.delete(view);
this.computedToView.delete(view.getComputingEnemy());
this.enemyViewMap.delete(index);
this.enemyMap.delete(index);
this.locatorViewMap.delete(view);
this.locatorEnemyMap.delete(enemy);
}
setEnemyAt(locator: ITileLocator, enemy: IEnemy<TEnemy>): void {
const index = this.indexer.locToIndex(locator.x, locator.y);
this.deleteEnemyAt(index);
const view = new EnemyView<TEnemy>(enemy, this);
this.enemyMap.set(index, enemy);
this.enemyViewMap.set(index, view);
this.locatorEnemyMap.set(enemy, index);
this.locatorViewMap.set(view, index);
this.computedToView.set(view.getComputingEnemy(), view);
if (this.mapDamage) {
this.mapDamage.markEnemyDirty(view);
}
if (this.damageSystem) {
this.damageSystem.markDirty(view);
}
this.needUpdate = true;
}
deleteEnemy(locator: ITileLocator): void {
const index = this.indexer.locToIndex(locator.x, locator.y);
this.deleteEnemyAt(index);
}
/**
* 在指定范围内筛选出当前上下文中的怪物视图
* @param range 范围对象
* @param param 范围参数
*/
private *internalScanRange<T>(
range: IRange<T>,
param: T
): Iterable<[ITileLocator, EnemyView<TEnemy>]> {
range.bindHost(this);
const keys = new Set(this.enemyViewMap.keys());
const matched = range.autoDetect(keys, param);
const viewMap = this.enemyViewMap;
for (const index of matched) {
const view = viewMap.get(index);
if (view) {
const locator = this.indexer.indexToLocator(index);
yield [locator, view];
}
}
}
scanRange<T>(
range: IRange<T>,
param: T
): Iterable<[ITileLocator, IEnemyView<TEnemy>]> {
return this.internalScanRange(range, param);
}
*iterateEnemy(): Iterable<[ITileLocator, IEnemyView<TEnemy>]> {
for (const [index, view] of this.enemyViewMap) {
const locator = this.indexer.indexToLocator(index);
yield [locator, view];
}
}
addAura(aura: IAuraView<TEnemy>): void {
this.globalAuraList.add(aura);
this.needUpdate = true;
}
deleteAura(aura: IAuraView<TEnemy>): void {
this.globalAuraList.delete(aura);
this.needUpdate = true;
}
attachMapDamage(damage: IMapDamage<TEnemy, THero> | null): void {
this.mapDamage = damage;
if (damage) {
damage.refreshAll();
}
}
getMapDamage(): IMapDamage<TEnemy, THero> | null {
return this.mapDamage;
}
attachDamageSystem(system: IDamageSystem<TEnemy, THero> | null): void {
this.damageSystem = system;
if (system) {
system.bindHeroStatus(this.bindedHero);
}
}
getDamageSystem(): IDamageSystem<TEnemy, THero> | null {
return this.damageSystem;
}
/**
* 将怪物身上的特殊属性尝试转换为光环视图
* @param special 特殊属性
* @param enemy 怪物对象
* @param locator 怪物位置
*/
private convertSpecial(
special: ISpecial<any>,
handler: IReadonlyEnemyHandler<TEnemy, THero>
): IEnemyAuraView<TEnemy, any, any> | null {
let matched: IAuraConverter<TEnemy, THero> | null = null;
for (const converter of this.auraConverter) {
if (!this.converterStatus.get(converter)) continue;
if (converter.shouldConvert(special, handler)) {
if (matched) {
logger.warn(97, special.code.toString());
return null;
}
matched = converter;
}
}
if (!matched) return null;
return matched.convert(special, handler, this);
}
/**
* 将光环按优先级插入到有序表中
* @param aura 光环视图
*/
private insertIntoSortedAura(aura: IAuraView<TEnemy>): void {
const set = this.sortedAura.getOrInsertComputed(
aura.priority,
() => new Set()
);
set.add(aura);
}
/**
* 从优先级表中移除一个光环
* @param aura 光环视图
*/
private removeFromSortedAura(aura: IAuraView<TEnemy>): void {
const set = this.sortedAura.get(aura.priority);
if (set) {
set.delete(aura);
if (set.size === 0) {
this.sortedAura.delete(aura.priority);
}
}
}
/**
* 执行特殊属性修饰器,并返回因此受到影响的光环集合
* @param modifier 特殊属性修饰器
* @param enemy 目标怪物
* @param locator 怪物位置
* @param currentPriority 当前处理的优先级
*/
private processSpecialModifier(
modifier: IEnemySpecialModifier<TEnemy>,
handler: IEnemyHandler<TEnemy, THero>,
currentPriority: number
): Set<IAuraView<TEnemy>> {
const enemy = handler.enemy;
const affectedAuras = new Set<IAuraView<TEnemy>>();
const toAdd = modifier.add(handler);
const toDelete = modifier.delete(handler);
if (toAdd.length > 0 && toDelete.length > 0) {
logger.warn(100);
return affectedAuras;
}
for (const adding of toAdd) {
const aura = this.convertSpecial(adding, handler);
if (aura) {
// 新生成的光环只能影响之后的阶段,不能反过来影响当前优先级链
if (import.meta.env.DEV && aura.priority > currentPriority) {
logger.warn(
99,
aura.priority.toString(),
currentPriority.toString()
);
continue;
}
this.convertedAura.set(adding, aura);
this.insertIntoSortedAura(aura);
affectedAuras.add(aura);
}
enemy.addSpecial(adding);
}
for (const deleting of toDelete) {
enemy.deleteSpecial(deleting);
const aura = this.convertedAura.get(deleting);
if (aura) {
// 当前阶段不允许删除同优先级或更高优先级的已生效光环。
if (import.meta.env.DEV && aura.priority >= currentPriority) {
logger.warn(
98,
aura.priority.toString(),
currentPriority.toString()
);
continue;
}
this.removeFromSortedAura(aura);
this.convertedAura.delete(deleting);
affectedAuras.add(aura);
}
}
for (const special of enemy.iterateSpecials()) {
const success = modifier.modify(handler, special);
if (!success) continue;
const aura = this.convertedAura.get(special);
if (!aura) continue;
affectedAuras.add(aura);
if (import.meta.env.DEV && aura.priority >= currentPriority) {
logger.warn(
98,
aura.priority.toString(),
currentPriority.toString()
);
}
}
return affectedAuras;
}
/**
* 执行单个特殊查询效果
* @param effect 特殊查询效果
* @param currentPriority 当前处理的优先级
*/
private processSpecialQuery(
effect: IEnemySpecialQueryEffect<TEnemy, THero>,
currentPriority: number
): void {
const modifier = effect.for(this);
for (const [index, view] of this.enemyViewMap) {
const locator = this.indexer.indexToLocator(index);
const enemy = view.getComputingEnemy();
const handler = this.createHandler(enemy, locator);
if (!modifier.shouldQuery(handler)) continue;
const affectedAuras = this.processSpecialModifier(
modifier,
handler,
currentPriority
);
if (affectedAuras.size > 0) {
this.needTotallyRefresh.add(view);
} else {
this.requestedCommonContext.add(view);
}
}
}
/**
* 执行光环带来的特殊属性修饰效果
* @param aura 光环视图
* @param currentPriority 当前处理的优先级
*/
private processAuraSpecial(
aura: IAuraView<TEnemy>,
currentPriority: number
): void {
const param = aura.getRangeParam();
const iter = this.internalScanRange(aura.range, param);
for (const [locator, enemyView] of iter) {
const enemy = enemyView.getComputingEnemy();
const base = enemyView.getBaseEnemy();
const handler = this.createHandler(enemy, locator);
const modifier = aura.applySpecial(handler, base);
if (!modifier) continue;
this.processSpecialModifier(modifier, handler, currentPriority);
this.needTotallyRefresh.add(enemyView);
}
}
/**
* 构建所有由特殊属性衍生出的光环与特殊查询结果
*/
private buildupSpecials(): void {
for (const aura of this.globalAuraList) {
this.insertIntoSortedAura(aura);
}
for (const [index, view] of this.enemyViewMap) {
const enemy = view.getComputingEnemy();
const locator = this.indexer.indexToLocator(index);
const handler = this.createHandler(enemy, locator);
for (const special of enemy.iterateSpecials()) {
const aura = this.convertSpecial(special, handler);
if (!aura) continue;
this.convertedAura.set(special, aura);
this.insertIntoSortedAura(aura);
}
}
const processedPriorities = new Set<number>();
// 由于期间可能会产生新优先级的光环,所以要用 while (true) 而不是直接遍历
while (true) {
let maxPriority: number | null = null;
for (const priority of this.sortedAura.keys()) {
if (!processedPriorities.has(priority)) {
if (maxPriority === null || priority > maxPriority) {
maxPriority = priority;
}
}
}
for (const priority of this.specialQueryEffects.keys()) {
if (!processedPriorities.has(priority)) {
if (maxPriority === null || priority > maxPriority) {
maxPriority = priority;
}
}
}
if (maxPriority === null) break;
processedPriorities.add(maxPriority);
const auras = this.sortedAura.get(maxPriority);
if (auras) {
for (const aura of auras) {
if (aura.couldApplySpecial) {
this.processAuraSpecial(aura, maxPriority);
}
}
}
const effects = this.specialQueryEffects.get(maxPriority);
if (effects) {
for (const effect of effects) {
this.processSpecialQuery(effect, maxPriority);
}
}
}
}
/**
* 按优先级执行所有基础属性光环效果
*/
private buildupBase(): void {
const priorities = [...this.sortedAura.keys()].sort((a, b) => b - a);
for (const p of priorities) {
const auras = this.sortedAura.get(p);
if (!auras) continue;
for (const aura of auras) {
const param = aura.getRangeParam();
const iter = this.internalScanRange(aura.range, param);
for (const [locator, view] of iter) {
const enemy = view.getComputingEnemy();
const base = view.getBaseEnemy();
const handler = this.createHandler(enemy, locator);
aura.apply(handler, base);
}
}
}
}
/**
* 执行常规查询效果,并记录哪些怪物查询了上下文
*/
private buildupQuery(): void {
for (const [index, view] of this.enemyViewMap) {
const enemy = view.getComputingEnemy();
const locator = this.indexer.indexToLocator(index);
const handler = this.createHandler(enemy, locator);
let queried = false;
const query = () => {
queried = true;
return this;
};
for (const special of enemy.iterateSpecials()) {
const effects = this.commonQueryMap.get(special.code);
if (!effects) continue;
for (const effect of effects) {
effect.apply(handler, special, query);
}
}
if (queried) {
this.requestedCommonContext.add(view);
}
}
}
/**
* 执行最终效果阶段
*/
private buildupFinal(): void {
for (const [index, view] of this.enemyViewMap) {
const enemy = view.getComputingEnemy();
const locator = this.indexer.indexToLocator(index);
const handler = this.createHandler(enemy, locator);
for (const effect of this.finalEffects) {
effect.apply(handler);
}
}
}
buildup(): void {
if (!this.needUpdate) return;
if (!this.bindedHero) {
logger.warn(110);
return;
}
this.needUpdate = false;
this.sortedAura.clear();
this.convertedAura.clear();
this.dirtyEnemy.clear();
this.needTotallyRefresh.clear();
this.requestedCommonContext.clear();
const hasAura = this.auraConverter.size > 0;
const hasSpecialQuery = this.specialQueryEffects.size > 0;
if (hasAura || hasSpecialQuery) {
this.buildupSpecials();
this.buildupBase();
}
if (this.commonQueryMap.size > 0) {
this.buildupQuery();
}
if (this.finalEffects.length > 0) {
this.buildupFinal();
}
if (this.damageSystem) {
this.damageSystem.markAllDirty();
}
if (this.mapDamage) {
this.mapDamage.refreshAll();
}
}
markDirty(view: IEnemyView<TEnemy>): void {
if (!this.locatorViewMap.has(view)) return;
this.dirtyEnemy.add(view);
if (this.damageSystem) {
this.damageSystem.markDirty(view);
}
}
/**
* 在局部刷新期间执行特殊属性修饰器,但不重建光环拓扑
* @param modifier 特殊属性修饰器
* @param enemy 目标怪物
* @param locator 怪物位置
*/
private refreshSpecialModifier(
modifier: IEnemySpecialModifier<TEnemy>,
handler: IEnemyHandler<TEnemy, THero>
): void {
const enemy = handler.enemy;
const toAdd = modifier.add(handler);
const toDelete = modifier.delete(handler);
if (toAdd.length > 0 && toDelete.length > 0) {
logger.warn(100);
return;
}
for (const adding of toAdd) {
enemy.addSpecial(adding);
if (import.meta.env.DEV) {
const aura = this.convertSpecial(adding, handler);
if (aura) {
logger.warn(101, adding.code.toString());
}
}
}
for (const deleting of toDelete) {
enemy.deleteSpecial(deleting);
if (import.meta.env.DEV) {
const aura = this.convertSpecial(deleting, handler);
if (aura) {
logger.warn(101, deleting.code.toString());
}
}
}
for (const special of enemy.iterateSpecials()) {
const success = modifier.modify(handler, special);
if (import.meta.env.DEV && success) {
const aura = this.convertedAura.get(special);
if (aura) {
logger.warn(101, special.code.toString());
}
}
}
}
/**
* 刷新单个怪物视图的计算结果
* @param view 怪物视图
*/
private refreshEnemy(view: EnemyView<TEnemy>): void {
const locator = this.getEnemyLocatorByView(view);
if (!locator) return;
view.reset();
const enemy = view.getComputingEnemy();
const base = view.getBaseEnemy();
const handler = this.createHandler(enemy, locator);
const specialPriorities = new Set<number>();
for (const priority of this.sortedAura.keys()) {
specialPriorities.add(priority);
}
for (const priority of this.specialQueryEffects.keys()) {
specialPriorities.add(priority);
}
const orderedSpecialPriorities = [...specialPriorities].sort(
(a, b) => b - a
);
for (const priority of orderedSpecialPriorities) {
const auras = this.sortedAura.get(priority);
const effects = this.specialQueryEffects.get(priority);
if (auras) {
for (const aura of auras) {
if (!aura.couldApplySpecial) continue;
const param = aura.getRangeParam();
aura.range.bindHost(this);
// 局部刷新只重新判断“这个怪物是否被该光环命中”
if (!aura.range.inRange(locator.x, locator.y, param)) {
continue;
}
const modifier = aura.applySpecial(handler, base);
if (!modifier) continue;
this.refreshSpecialModifier(modifier, handler);
}
}
if (effects) {
for (const effect of effects) {
const modifier = effect.for(this);
if (!modifier.shouldQuery(handler)) continue;
this.refreshSpecialModifier(modifier, handler);
}
}
}
const basePriorities = [...this.sortedAura.keys()].sort(
(a, b) => b - a
);
for (const priority of basePriorities) {
const auras = this.sortedAura.get(priority);
if (!auras) continue;
for (const aura of auras) {
const param = aura.getRangeParam();
aura.range.bindHost(this);
if (!aura.range.inRange(locator.x, locator.y, param)) continue;
aura.apply(handler, base);
}
}
this.requestedCommonContext.delete(view);
let queried = false;
const query = () => {
queried = true;
return this;
};
for (const special of enemy.iterateSpecials()) {
const effects = this.commonQueryMap.get(special.code);
if (!effects) continue;
for (const effect of effects) {
effect.apply(handler, special, query);
}
}
if (queried) {
this.requestedCommonContext.add(view);
}
for (const effect of this.finalEffects) {
effect.apply(handler);
}
this.dirtyEnemy.delete(view);
if (this.damageSystem) {
this.damageSystem.markDirty(view);
}
if (this.mapDamage) {
this.mapDamage.markEnemyDirty(view);
}
}
requestRefresh(view: IEnemyView<TEnemy>): void {
if (!this.dirtyEnemy.has(view)) return;
if (this.needTotallyRefresh.has(view)) {
this.needUpdate = true;
}
if (this.needUpdate) {
this.buildup();
return;
}
this.refreshEnemy(view as EnemyView<TEnemy>);
for (const requestedView of this.requestedCommonContext) {
if (requestedView === view) continue;
this.refreshEnemy(requestedView as EnemyView<TEnemy>);
}
}
clear(): void {
this.enemyViewMap.clear();
this.enemyMap.clear();
this.locatorViewMap.clear();
this.locatorEnemyMap.clear();
this.computedToView.clear();
this.globalAuraList.clear();
this.sortedAura.clear();
this.needTotallyRefresh.clear();
this.requestedCommonContext.clear();
this.dirtyEnemy.clear();
if (this.damageSystem) {
this.damageSystem.markAllDirty();
}
if (this.mapDamage) {
this.mapDamage.refreshAll();
}
}
destroy(): void {
this.clear();
this.attachMapDamage(null);
this.attachDamageSystem(null);
this.auraConverter.clear();
this.commonQueryMap.clear();
this.specialQueryEffects.clear();
this.finalEffects.length = 0;
this.bindedHero = null;
}
}
@@ -1,286 +0,0 @@
import { clamp } from 'lodash-es';
import { ITileLocator, logger } from '@motajs/common';
import {
CriticalableHeroStatus,
IDamageCalculator,
IDamageContext,
IDamageSystem,
IEnemyContext,
IEnemyCritical,
IEnemyDamageInfo,
IReadonlyEnemyHandler,
IEnemyView
} from './types';
import { IHeroAttribute, IReadonlyHeroAttribute } from '../hero';
import { IReadonlyEnemy } from '../enemy';
import { IStateBase } from '../types';
interface ICriticalSearchResult {
/** 此临界点的属性值 */
readonly value: number;
/** 此临界点的伤害信息 */
readonly info: IEnemyDamageInfo;
}
export class DamageContext<TEnemy, THero> implements IDamageContext<
TEnemy,
THero
> {
/** 当前正在使用的计算器 */
protected calculator: IDamageCalculator<TEnemy, THero> | null;
/** 当前勇士属性 */
protected heroStatus: IReadonlyHeroAttribute<THero> | null;
readonly dataState: IStateBase<TEnemy, THero>;
constructor(
readonly context: IEnemyContext<TEnemy, THero>,
calculator: IDamageCalculator<TEnemy, THero> | null = null,
heroStatus: IReadonlyHeroAttribute<THero> | null = null
) {
this.dataState = context.dataState;
this.calculator = calculator;
this.heroStatus = heroStatus;
}
/**
* 创建只读信息对象
* @param enemy 怪物对象
* @param locator 怪物位置
* @param hero 勇士属性对象
*/
private createReadonlyHandler(
enemy: IReadonlyEnemy<TEnemy>,
locator: ITileLocator,
hero: IReadonlyHeroAttribute<THero>
): IReadonlyEnemyHandler<TEnemy, THero> {
return { enemy, locator, hero, data: this.dataState };
}
getDamageInfo(enemy: IEnemyView<TEnemy>): IEnemyDamageInfo | null {
if (!this.heroStatus) {
logger.warn(107);
return null;
}
if (!this.calculator) {
logger.warn(106);
return null;
}
const hero = this.heroStatus;
const locator = this.context.getEnemyLocatorByView(enemy);
if (!hero || !locator) return null;
const computed = enemy.getComputedEnemy();
const handler = this.createReadonlyHandler(computed, locator, hero);
return this.calculator.calculate(handler);
}
getDamageInfoByComputed(
enemy: IReadonlyEnemy<TEnemy>
): IEnemyDamageInfo | null {
if (!this.heroStatus) {
logger.warn(107);
return null;
}
if (!this.calculator) {
logger.warn(106);
return null;
}
const hero = this.heroStatus;
const view = this.context.getViewByComputed(enemy);
if (!hero || !view) return null;
const locator = this.context.getEnemyLocatorByView(view);
if (!locator) return null;
const handler = this.createReadonlyHandler(enemy, locator, hero);
return this.calculator.calculate(handler);
}
*calculateCritical(
view: IEnemyView<TEnemy>,
attribute: CriticalableHeroStatus<THero>,
precision: number = 12
): Generator<IEnemyCritical, void, void> {
if (!this.heroStatus) {
logger.warn(107);
return;
}
if (!this.calculator) {
logger.warn(106);
return;
}
const locator = this.context.getEnemyLocatorByView(view);
if (!locator) return;
const enemy = view.getComputedEnemy();
const hero = this.heroStatus.getModifiableClone();
const handler = this.createReadonlyHandler(enemy, locator, hero);
const currentInfo = this.calculator.calculate(handler);
if (!currentInfo) return;
const currentValue = hero.getBaseAttribute(attribute) as number;
const upperLimit = Math.floor(
this.calculator.getCriticalLimit(handler, attribute)
);
if (currentValue >= upperLimit) return;
// 超过 64 位的精度没有意义,所以最高设置为 64
const maxIterations = clamp(Math.floor(precision), 4, 64);
let baseValue = currentValue;
let baseInfo = currentInfo;
while (baseValue < upperLimit) {
const next = this.findNextCritical(
handler,
hero,
attribute,
baseValue,
upperLimit,
baseInfo.damage,
maxIterations
);
if (!next) return;
yield {
nextValue: next.value,
baseValue: currentValue,
nextDiff: next.value - currentValue,
baseInfo: currentInfo,
info: next.info,
damageDiff: next.info.damage - currentInfo.damage
};
baseValue = next.value;
baseInfo = next.info;
}
}
/**
* 计算下一个临界点
* @param handler 信息对象,其中的 `hero` 成员与 `hero` 参数同引用
* @param hero 可修改勇士属性对象,与 `handler` 中的 `hero` 成员同引用
* @param attribute 勇士属性名
* @param currentValue 当前勇士属性值
* @param upperLimit 二分上界
* @param referenceDamage 参考伤害值
* @param maxIterations 最大迭代数量
*/
private findNextCritical(
handler: IReadonlyEnemyHandler<TEnemy, THero>,
hero: IHeroAttribute<THero>,
attribute: CriticalableHeroStatus<THero>,
currentValue: number,
upperLimit: number,
referenceDamage: number,
maxIterations: number
): ICriticalSearchResult | null {
let left = currentValue;
let right = upperLimit;
hero.setBaseAttribute(attribute, right as THero[typeof attribute]);
let targetInfo = this.calculator!.calculate(handler);
if (targetInfo.damage >= referenceDamage) return null;
let iter = 0;
while (iter++ < maxIterations) {
const middle = Math.floor((left + right) / 2);
hero.setBaseAttribute(attribute, middle as THero[typeof attribute]);
const middleInfo = this.calculator!.calculate(handler);
if (middleInfo.damage < referenceDamage) {
right = middle;
} else {
left = middle;
targetInfo = middleInfo;
}
if (right - left <= 1) break;
}
return {
value: right,
info: targetInfo
};
}
}
export class DamageSystem<TEnemy, THero>
extends DamageContext<TEnemy, THero>
implements IDamageSystem<TEnemy, THero>
{
/** 怪物伤害缓存 */
private readonly cache: Map<IEnemyView<TEnemy>, IEnemyDamageInfo> =
new Map();
constructor(context: IEnemyContext<TEnemy, THero>) {
super(context);
}
useCalculator(calculator: IDamageCalculator<TEnemy, THero>): void {
this.calculator = calculator;
this.markAllDirty();
}
getCalculator(): IDamageCalculator<TEnemy, THero> | null {
return this.calculator;
}
bindHeroStatus(hero: IReadonlyHeroAttribute<THero> | null): void {
this.heroStatus = hero;
this.markAllDirty();
}
getDamageInfo(enemy: IEnemyView<TEnemy>): IEnemyDamageInfo | null {
const cached = this.cache.get(enemy);
if (cached) {
return cached;
}
const info = super.getDamageInfo(enemy);
if (!info) return info;
this.cache.set(enemy, info);
return info;
}
getDamageInfoByComputed(
enemy: IReadonlyEnemy<TEnemy>
): IEnemyDamageInfo | null {
const view = this.context.getViewByComputed(enemy);
if (view) {
const cached = this.cache.get(view);
if (cached) {
return cached;
}
}
const info = super.getDamageInfoByComputed(enemy);
if (!view || !info) return info;
this.cache.set(view, info);
return info;
}
markDirty(enemy: IEnemyView<TEnemy>): void {
this.cache.delete(enemy);
}
deleteEnemy(enemy: IEnemyView<TEnemy>): void {
this.cache.delete(enemy);
}
markAllDirty(): void {
this.cache.clear();
}
with(hero: IHeroAttribute<THero>): IDamageContext<TEnemy, THero> {
return new DamageContext(this.context, this.calculator, hero);
}
}
@@ -1,42 +0,0 @@
import { IEnemy, IReadonlyEnemy } from '../enemy';
import { IEnemyView, IEnemyContext } from './types';
export class EnemyView<TAttr> implements IEnemyView<TAttr> {
/** 计算后怪物 */
private readonly computedEnemy: IEnemy<TAttr>;
constructor(
readonly baseEnemy: IEnemy<TAttr>,
readonly context: IEnemyContext<TAttr, unknown>
) {
this.computedEnemy = baseEnemy.clone();
}
reset(): void {
this.computedEnemy.copyFrom(this.baseEnemy);
}
getBaseEnemy(): IReadonlyEnemy<TAttr> {
return this.baseEnemy;
}
getComputedEnemy(): IReadonlyEnemy<TAttr> {
this.context.requestRefresh(this);
return this.computedEnemy;
}
/**
* 获取计算中怪物对象,这个接口不对外暴露,仅在系统内部的 EnemyContext 中使用。
*/
getComputingEnemy(): IEnemy<TAttr> {
return this.computedEnemy;
}
getModifiableEnemy(): IEnemy<TAttr> {
return this.baseEnemy;
}
markDirty(): void {
this.context.markDirty(this);
}
}
@@ -1,5 +0,0 @@
export * from './context';
export * from './damage';
export * from './enemy';
export * from './mapDamage';
export * from './types';
@@ -1,378 +0,0 @@
import { logger, ITileLocator } from '@motajs/common';
import {
IEnemyContext,
IReadonlyEnemyHandler,
IEnemyView,
IMapDamage,
IMapDamageConverter,
IMapDamageInfo,
IMapDamageReducer,
IMapDamageView
} from './types';
import { ILocationHelper } from '../common/indexer';
import { IStateBase } from '../types';
interface IPointInfo {
/** 该点所有的地图伤害 */
readonly damages: Set<Readonly<IMapDamageInfo>>;
/** 所有影响该点的地图伤害视图 */
readonly affectedBy: Set<IMapDamageView<any>>;
}
interface IViewStore<TAttr> {
/** 该地图伤害视图所影响的伤害信息 */
readonly damages: Map<number, Readonly<IMapDamageInfo>>;
/** 当前视图所属的怪物视图 */
readonly enemy: IEnemyView<TAttr>;
}
interface IDamageStore<TAttr> {
/** 该地图伤害信息的地图伤害视图来源 */
readonly sourceView: IMapDamageView<any>;
/** 地图伤害信息的来源怪物 */
readonly sourceEnemy: IEnemyView<TAttr>;
/** 该地图伤害信息所处的索引 */
readonly index: number;
}
export class MapDamage<TEnemy, THero> implements IMapDamage<TEnemy, THero> {
/** 当前使用的地图伤害转换器 */
private converter: IMapDamageConverter<TEnemy, THero> | null = null;
/** 当前使用的地图伤害合并器 */
private reducer: IMapDamageReducer | null = null;
/** 无来源地图伤害,坐标 -> 点伤害信息 */
private readonly sourcelessDamage: Map<number, IPointInfo> = new Map();
/** 有来源地图伤害,坐标 -> 点伤害信息 */
private readonly sourcedDamage: Map<number, IPointInfo> = new Map();
/** 地图伤害视图 -> 其信息对象 */
private readonly viewStore: Map<IMapDamageView<any>, IViewStore<TEnemy>> =
new Map();
/** 地图伤害信息 -> 其信息对象 */
private readonly damageStore: Map<IMapDamageInfo, IDamageStore<TEnemy>> =
new Map();
/** 怪物视图 -> 其影响对象 */
private readonly enemyStore: Map<
IEnemyView<TEnemy>,
Set<IMapDamageView<any>>
> = new Map();
/** 需要延迟刷新的坐标索引 */
private readonly dirtyIndexes: Set<number> = new Set();
/** 合并后伤害缓存,索引 -> 合并结果 */
private readonly reducedCache: Map<number, IMapDamageInfo> = new Map();
/** 坐标索引对象 */
private readonly indexer: ILocationHelper;
readonly dataState: IStateBase<TEnemy, THero>;
constructor(readonly context: IEnemyContext<TEnemy, THero>) {
this.indexer = context.indexer;
this.dataState = context.dataState;
}
useConverter(converter: IMapDamageConverter<TEnemy, THero>): void {
this.converter = converter;
this.refreshAll();
}
/**
* 创建只读信息对象
* @param view 怪物视图
* @param locator 怪物位置
*/
private createReadonlyHandler(
view: IEnemyView<TEnemy>,
locator: ITileLocator
): IReadonlyEnemyHandler<TEnemy, THero> | null {
const hero = this.context.getBindedHero();
if (!hero) return null;
return {
enemy: view.getComputedEnemy(),
locator,
hero,
data: this.context.dataState
};
}
useReducer(reducer: IMapDamageReducer): void {
this.reducer = reducer;
this.reducedCache.clear();
}
addMapDamage(locator: ITileLocator, info: IMapDamageInfo): void {
const index = this.indexer.locaterToIndex(locator);
const store = this.sourcelessDamage.getOrInsertComputed(index, () => ({
affectedBy: new Set(),
damages: new Set()
}));
store.damages.add(info);
this.markDirtyIndex(index);
}
deleteMapDamage(locator: ITileLocator, info: IMapDamageInfo): void {
const index = this.indexer.locaterToIndex(locator);
const current = this.sourcelessDamage.get(index);
if (!current) return;
current.damages.delete(info);
this.markDirtyIndex(index);
}
/**
* 将指定索引标记为脏
* @param index 坐标索引
*/
private markDirtyIndex(index: number) {
this.dirtyIndexes.add(index);
this.reducedCache.delete(index);
}
markDirty(locator: ITileLocator): void {
this.markDirtyIndex(this.indexer.locaterToIndex(locator));
}
markEnemyDirty(view: IEnemyView<TEnemy>): void {
const store = this.enemyStore.get(view);
const locator = this.context.getEnemyLocatorByView(view);
if (!store) {
if (!locator) {
logger.warn(104);
} else {
this.refreshAll();
}
return;
}
if (!locator) return;
this.refreshEnemyAndClearCache(view, locator);
}
deleteEnemy(view: IEnemyView<TEnemy>): void {
const store = this.enemyStore.get(view);
if (!store) return;
const collection = new Set<number>();
for (const viewItem of store) {
const affecting = this.viewStore.get(viewItem);
if (!affecting) continue;
affecting.damages.forEach((dam, index) => {
this.damageStore.delete(dam);
collection.add(index);
});
this.viewStore.delete(viewItem);
}
this.enemyStore.delete(view);
collection.forEach(v => {
this.markDirtyIndex(v);
});
}
getReducedDamage(locator: ITileLocator): Readonly<IMapDamageInfo> | null {
if (!this.reducer) {
logger.warn(103);
return null;
}
const index = this.indexer.locaterToIndex(locator);
if (this.dirtyIndexes.has(index)) {
this.refreshIndex(index);
}
const cache = this.reducedCache.get(index);
if (cache) return cache;
const separated = this.getSeparatedDamageByIndex(index);
if (separated.size === 0) return null;
const reduced = this.reducer.reduce(separated, locator);
this.reducedCache.set(index, reduced);
return reduced;
}
/**
* 根据索引获取指定位置的合并前伤害
* @param index 坐标索引
*/
private getSeparatedDamageByIndex(
index: number
): Set<Readonly<IMapDamageInfo>> {
const sourceless = this.sourcelessDamage.get(index);
const sourced = this.sourcedDamage.get(index);
if (sourceless) {
if (sourced) {
// 大集合 union 小集合会更快,一般有来源伤害更多,所以 source union sourceless
return sourced.damages.union(sourceless.damages);
} else {
return sourceless.damages;
}
} else if (sourced) {
return sourced.damages;
} else {
return new Set();
}
}
getSeparatedDamage(
locator: ITileLocator
): Iterable<Readonly<IMapDamageInfo>> {
const index = this.indexer.locaterToIndex(locator);
if (this.dirtyIndexes.has(index)) {
this.refreshIndex(index);
}
return this.getSeparatedDamageByIndex(index);
}
/**
* 清空所有有来源伤害的内部状态
*/
private clearSourceState(): void {
this.sourcedDamage.clear();
this.damageStore.clear();
this.viewStore.clear();
this.enemyStore.clear();
this.dirtyIndexes.clear();
this.reducedCache.clear();
}
/**
* 移除指定怪物所产生的地图伤害
* @param view 怪物视图
*/
private removeEnemyAffecting(view: IEnemyView<TEnemy>) {
const views = this.enemyStore.get(view);
if (!views) return;
views.forEach(viewItem => {
const store = this.viewStore.get(viewItem);
if (!store) return;
store.damages.forEach((dam, index) => {
const point = this.sourcedDamage.get(index);
if (!point) return;
point.affectedBy.delete(viewItem);
point.damages.delete(dam);
this.damageStore.delete(dam);
});
this.viewStore.delete(viewItem);
});
this.enemyStore.delete(view);
}
/**
* 刷新指定位置的怪物地图伤害,并执行刷新缓存的操作
*/
private refreshEnemyAndClearCache(
view: IEnemyView<TEnemy>,
locator: ITileLocator
) {
this.removeEnemyAffecting(view);
if (!this.converter) return;
const handler = this.createReadonlyHandler(view, locator);
if (!handler) return;
const views = this.converter.convert(handler, this.context);
const set = new Set<IMapDamageView<any>>(views);
if (set.size === 0) return;
this.enemyStore.set(view, set);
const collection = new Set<number>();
set.forEach(viewItem => {
const range = viewItem.getRange();
const param = viewItem.getRangeParam();
range.bindHost(this.context);
for (const index of range.iterateLoc(param)) {
const loc = this.indexer.indexToLocator(index);
const point = this.sourcedDamage.getOrInsertComputed(
index,
() => ({
affectedBy: new Set(),
damages: new Set()
})
);
const damage = viewItem.getDamageWithoutCheck(loc);
if (damage) {
point.affectedBy.add(viewItem);
point.damages.add(damage);
collection.add(index);
}
}
});
collection.forEach(v => {
this.dirtyIndexes.delete(v);
this.reducedCache.delete(v);
});
}
/**
* 刷新指定位置的怪物地图伤害
*/
private refreshEnemy(view: IEnemyView<TEnemy>, locator: ITileLocator) {
this.removeEnemyAffecting(view);
if (!this.converter) return;
const handler = this.createReadonlyHandler(view, locator);
if (!handler) return;
const views = this.converter.convert(handler, this.context);
const set = new Set<IMapDamageView<any>>(views);
if (set.size === 0) return;
this.enemyStore.set(view, set);
set.forEach(viewItem => {
const range = viewItem.getRange();
const param = viewItem.getRangeParam();
range.bindHost(this.context);
for (const index of range.iterateLoc(param)) {
const loc = this.indexer.indexToLocator(index);
const point = this.sourcedDamage.getOrInsertComputed(
index,
() => ({
affectedBy: new Set(),
damages: new Set()
})
);
const damage = viewItem.getDamageWithoutCheck(loc);
if (damage) {
point.affectedBy.add(viewItem);
point.damages.add(damage);
}
}
});
}
refreshAll(): void {
if (!this.converter) {
logger.warn(102);
return;
}
if (!this.reducer) {
logger.warn(103);
return;
}
this.clearSourceState();
this.reducedCache.clear();
for (const [locator, view] of this.context.iterateEnemy()) {
this.refreshEnemy(view, locator);
}
}
/**
* 重新计算指定点位的有来源伤害缓存
*/
private refreshIndex(index: number): void {
this.dirtyIndexes.delete(index);
this.reducedCache.delete(index);
const locator = this.indexer.indexToLocator(index);
const point = this.sourcedDamage.get(index);
if (!point) return;
for (const damage of point.damages) {
const store = this.damageStore.get(damage);
if (!store) continue;
const viewStore = this.viewStore.get(store.sourceView);
if (!viewStore) continue;
viewStore.damages.delete(index);
this.damageStore.delete(damage);
}
point.damages.clear();
point.affectedBy.forEach(view => {
const damage = view.getDamageWithoutCheck(locator);
if (damage) point.damages.add(damage);
});
}
}
-703
View File
@@ -1,703 +0,0 @@
import { ITileLocator, IRange } from '@motajs/common';
import { IEnemy, IReadonlyEnemy, ISpecial } from '../enemy';
import { IReadonlyHeroAttribute, IHeroAttribute } from '../hero';
import { ILocationHelper } from '../common/indexer';
import { IStateBase } from '../types';
//#region 辅助接口
export interface IEnemyHandler<TEnemy, THero> {
/** 怪物属性信息 */
readonly enemy: IEnemy<TEnemy>;
/** 怪物定位符 */
readonly locator: ITileLocator;
/** 勇士属性信息 */
readonly hero: IReadonlyHeroAttribute<THero>;
/** 当前全局状态对象 */
readonly data: IStateBase<TEnemy, THero>;
}
export interface IReadonlyEnemyHandler<TEnemy, THero> {
/** 怪物属性信息 */
readonly enemy: IReadonlyEnemy<TEnemy>;
/** 怪物定位符 */
readonly locator: ITileLocator;
/** 勇士属性信息 */
readonly hero: IReadonlyHeroAttribute<THero>;
/** 当前全局状态对象 */
readonly data: IStateBase<TEnemy, THero>;
}
//#endregion
//#region 怪物对象
export interface IEnemyView<TEnemy> {
/** 怪物视图所属的上下文 */
readonly context: IEnemyContext<TEnemy, unknown>;
/**
* 重置此怪物视图的状态,将计算后怪物对象恢复至初始状态
*/
reset(): void;
/**
* 获取基本怪物对象
*/
getBaseEnemy(): IReadonlyEnemy<TEnemy>;
/**
* 获取计算后的怪物对象,返回的怪物对象同引用
*/
getComputedEnemy(): IReadonlyEnemy<TEnemy>;
/**
* 获取可修改的怪物对象。如果修改此方法获取的怪物对象,那么怪物的真实信息是不会刷新的,
* 需要手动调用 markDirty 方法来刷新。
*/
getModifiableEnemy(): IEnemy<TEnemy>;
/**
* 将此怪物标记为脏,需要更新
*/
markDirty(): void;
}
//#endregion
//#region 光环与查询
export interface IEnemySpecialModifier<TEnemy> {
/**
* 获取要添加到指定怪物身上的特殊属性
* @param handler 信息对象
*/
add(handler: IReadonlyEnemyHandler<TEnemy, unknown>): ISpecial<any>[];
/**
* 获取制定怪物身上要删除的特殊属性
* @param handler 信息对象
*/
delete(handler: IReadonlyEnemyHandler<TEnemy, unknown>): ISpecial<any>[];
/**
* 修改一个怪物的特殊属性,如果真正进行了修改则返回 true,否则返回 false
* @param handler 信息对象
* @param special 要修改的怪物特殊属性
*/
modify(
handler: IEnemyHandler<TEnemy, unknown>,
special: ISpecial<any>
): boolean;
}
export interface IAuraView<TEnemy, TRange = any> {
/** 此光环视图的优先级 */
readonly priority: number;
/** 此光环视图的影响范围 */
readonly range: IRange<TRange>;
/** 这个光环视图是否有可能修改怪物的基本属性 */
readonly couldApplyBase: boolean;
/** 这个光环视图是否有可能修改怪物的特殊属性 */
readonly couldApplySpecial: boolean;
/**
* 获取范围扫描参数
*/
getRangeParam(): TRange;
/**
* 对指定怪物对象施加修饰器
* @param handler 信息对象
* @param baseEnemy 原始怪物对象,即未进行任何修改的怪物对象
*/
apply(
handler: IEnemyHandler<TEnemy, unknown>,
baseEnemy: IReadonlyEnemy<TEnemy>
): void;
/**
* 对指定怪物对象添加特殊属性修饰器
* @param handler 信息对象
* @param baseEnemy 原始怪物对象,即未进行任何修改的怪物对象
*/
applySpecial(
handler: IEnemyHandler<TEnemy, unknown>,
baseEnemy: IReadonlyEnemy<TEnemy>
): IEnemySpecialModifier<TEnemy> | null;
}
export interface IEnemyAuraView<TEnemy, TRange, TSpecial> extends IAuraView<
TEnemy,
TRange
> {
/** 此光环视图所属的怪物 */
readonly enemy: IReadonlyEnemy<TEnemy>;
/** 此光环视图所属的特殊属性 */
readonly special: ISpecial<TSpecial>;
/** 此光环视图所属怪物的定位符 */
readonly locator: ITileLocator;
}
export interface IAuraConverter<TEnemy, THero> {
/**
* 判断一个特殊属性是否应该被当前光环转换器执行转换
* @param special 要转换的特殊属性
* @param handler 信息对象
*/
shouldConvert(
special: ISpecial<any>,
handler: IReadonlyEnemyHandler<TEnemy, THero>
): boolean;
/**
* 将一个特殊属性转换为光环视图
*/
convert(
special: ISpecial<any>,
handler: IReadonlyEnemyHandler<TEnemy, THero>,
context: IEnemyContext<TEnemy, THero>
): IEnemyAuraView<TEnemy, any, any>;
}
export interface IEnemySpecialQueryModifier<
TEnemy,
THero
> extends IEnemySpecialModifier<TEnemy> {
/**
* 判断一个怪物是否应该查询外部状态
*/
shouldQuery(handler: IReadonlyEnemyHandler<TEnemy, THero>): boolean;
}
export interface IEnemySpecialQueryEffect<TEnemy, THero> {
/** 效果优先级,与光环属性共用 */
readonly priority: number;
/**
* 根据传入的怪物上下文,获取对应的怪物特殊属性修饰器
*/
for(
ctx: IEnemyContext<TEnemy, THero>
): IEnemySpecialQueryModifier<TEnemy, THero>;
}
export interface IEnemyCommonQueryEffect<TEnemy, THero> {
/** 优先级,越高的越先执行 */
readonly priority: number;
/**
* 对怪物的某个特殊属性施加常规查询效果
*/
apply(
handler: IEnemyHandler<TEnemy, THero>,
special: ISpecial<any>,
query: () => IEnemyContext<TEnemy, THero>
): void;
}
export interface IEnemyFinalEffect<TEnemy, THero> {
/** 效果优先级,越高会越先被执行 */
readonly priority: number;
/**
* 向怪物施加最终修饰效果
*/
apply(handler: IEnemyHandler<TEnemy, THero>): void;
}
//#endregion
//#region 地图伤害
export interface IMapDamageInfoExtra {
/** 捕捉怪物信息 */
catch: Set<ITileLocator>;
/** 阻击怪物信息 */
repulse: Set<ITileLocator>;
}
export interface IMapDamageInfo {
/** 伤害值 */
damage: number;
/** 伤害类型 */
type: number;
/** 地图伤害额外信息 */
extra: IMapDamageInfoExtra;
}
export interface IMapDamageView<T = any> {
/**
* 获取地图伤害影响范围
*/
getRange(): IRange<T>;
/**
* 获取范围参数
*/
getRangeParam(): T;
/**
* 获取指定位置的地图伤害,会对坐标进行判断
* @param locator 伤害位置
*/
getDamageAt(locator: ITileLocator): Readonly<IMapDamageInfo> | null;
/**
* 获取指定位置的地图伤害,不会对坐标进行判断
* @param locator 伤害位置
*/
getDamageWithoutCheck(
locator: ITileLocator
): Readonly<IMapDamageInfo> | null;
}
export interface IMapDamageConverter<TEnemy, THero> {
/**
* 转换地图伤害视图
*/
convert(
handler: IReadonlyEnemyHandler<TEnemy, THero>,
context: IEnemyContext<TEnemy, THero>
): IMapDamageView<any>[];
}
export interface IMapDamageReducer {
/**
* 对伤害信息进行合并
*/
reduce(
info: Iterable<Readonly<IMapDamageInfo>>,
locator: ITileLocator
): Readonly<IMapDamageInfo>;
}
export interface IMapDamage<TEnemy, THero> {
/** 当前绑定的怪物上下文 */
readonly context: IEnemyContext<TEnemy, THero>;
/** 地图伤害系统绑定的全局状态对象 */
readonly dataState: IStateBase<TEnemy, THero>;
/**
* 设置地图伤害转换器,并基于当前上下文重建所有地图伤害视图
* @param converter 地图伤害转换器
*/
useConverter(converter: IMapDamageConverter<TEnemy, THero>): void;
/**
* 设置地图伤害合并器
* @param reducer 地图伤害合并器
*/
useReducer(reducer: IMapDamageReducer): void;
/**
* 在指定位置添加一条无来源地图伤害
* @param locator 地图定位符
* @param info 地图伤害信息
*/
addMapDamage(locator: ITileLocator, info: IMapDamageInfo): void;
/**
* 在指定位置删除一条无来源地图伤害
* @param locator 地图定位符
* @param info 地图伤害信息
*/
deleteMapDamage(locator: ITileLocator, info: IMapDamageInfo): void;
/**
* 将指定位置标记为脏,后续访问时会重新计算该点的有来源伤害
* @param locator 地图定位符
*/
markDirty(locator: ITileLocator): void;
/**
* 将指定怪物对应的地图伤害标记为脏并刷新
* @param view 怪物视图
*/
markEnemyDirty(view: IEnemyView<TEnemy>): void;
/**
* 基于当前上下文重新刷新全部有来源地图伤害
*/
refreshAll(): void;
/**
* 删除指定怪物带来的全部地图伤害来源
* @param view 怪物视图
*/
deleteEnemy(view: IEnemyView<TEnemy>): void;
/**
* 获取指定位置合并后的地图伤害
* @param locator 地图定位符
*/
getReducedDamage(locator: ITileLocator): Readonly<IMapDamageInfo> | null;
/**
* 获取指定位置未合并的地图伤害列表
* @param locator 地图定位符
*/
getSeparatedDamage(
locator: ITileLocator
): Iterable<Readonly<IMapDamageInfo>>;
}
//#endregion
//#region 伤害系统
export interface IEnemyDamageInfo {
/** 战斗伤害值 */
readonly damage: number;
/** 战斗回合数 */
readonly turn: number;
}
export interface IEnemyCritical {
/** 此临界点中指定勇士属性的值 */
readonly nextValue: number;
/** 当前勇士指定属性的值 */
readonly baseValue: number;
/** 此临界点中指定勇士数值的值与当前值的差,即 `nextValue - baseValue` */
readonly nextDiff: number;
/** 当前状态下怪物的伤害信息 */
readonly baseInfo: IEnemyDamageInfo;
/** 此临界点下怪物的伤害信息 */
readonly info: IEnemyDamageInfo;
/** 此临界点的伤害值与当前伤害值的差 */
readonly damageDiff: number;
}
export type CriticalableHeroStatus<THero> = keyof {
[P in keyof THero as THero[P] extends number ? P : never]: number;
};
export interface IDamageCalculator<TEnemy, THero> {
/**
* 计算战斗伤害信息
* @param handler 信息对象
*/
calculate(handler: IReadonlyEnemyHandler<TEnemy, THero>): IEnemyDamageInfo;
/**
* 获取临界计算的上界
* @param handler 信息对象
* @param attribute 勇士的临界属性
*/
getCriticalLimit(
handler: IReadonlyEnemyHandler<TEnemy, THero>,
attribute: CriticalableHeroStatus<THero>
): number;
}
export interface IDamageContext<TEnemy, THero> {
/** 伤害上下文所属的全局状态对象 */
readonly dataState: IStateBase<TEnemy, THero>;
/**
* 获取战斗伤害信息
* @param enemy 怪物视图
*/
getDamageInfo(enemy: IEnemyView<TEnemy>): IEnemyDamageInfo | null;
/**
* 根据怪物对象获取战斗伤害信息
* @param enemy 怪物对象
*/
getDamageInfoByComputed(
enemy: IReadonlyEnemy<TEnemy>
): IEnemyDamageInfo | null;
/**
* 计算怪物在指定勇士属性下的临界
* @param enemy 怪物视图
* @param attribute 计算临界的目标勇士属性,比如计算攻击临界、自定义属性的临界等等
* @param precision 临界计算精度,表示会进行多少次二分计算,一般填写 `12-16` 之间的数即可,默认是 12
*/
calculateCritical(
enemy: IEnemyView<TEnemy>,
attribute: CriticalableHeroStatus<THero>,
precision?: number
): Generator<IEnemyCritical, void, void>;
}
export interface IDamageSystem<TEnemy, THero> extends IDamageContext<
TEnemy,
THero
> {
/** 伤害系统所属的上下文 */
readonly context: IEnemyContext<TEnemy, THero>;
/**
* 设置当前伤害计算系统使用的伤害计算器
* @param calculator 伤害计算器
*/
useCalculator(calculator: IDamageCalculator<TEnemy, THero>): void;
/**
* 获取当前使用的伤害计算器
*/
getCalculator(): IDamageCalculator<TEnemy, THero> | null;
/**
* 绑定勇士信息
* @param hero 勇士信息
*/
bindHeroStatus(hero: IReadonlyHeroAttribute<THero> | null): void;
/**
* 将指定的怪物标记为脏
* @param enemy 怪物视图
*/
markDirty(enemy: IEnemyView<TEnemy>): void;
/**
* 删除指定的怪物
* @param enemy 怪物视图
*/
deleteEnemy(enemy: IEnemyView<TEnemy>): void;
/**
* 将所有怪物标记为脏
*/
markAllDirty(): void;
/**
* 修改勇士属性,然后返回修改后勇士属性所组成的计算对象,不影响当前伤害系统的状态
* @param modify 勇士修改函数
*/
with(hero: IHeroAttribute<THero>): IDamageContext<TEnemy, THero>;
}
//#endregion
//#region 上下文
export interface IEnemyContext<TEnemy, THero> {
/** 怪物上下文宽度 */
readonly width: number;
/** 怪物上下文高度 */
readonly height: number;
/** 此上下文使用的索引对象 */
readonly indexer: ILocationHelper;
/** 当前怪物上下文绑定的全局状态对象 */
readonly dataState: IStateBase<TEnemy, THero>;
/**
* 调整上下文尺寸,并清空当前上下文中的所有怪物与状态
* @param width 地图宽度
* @param height 地图高度
*/
resize(width: number, height: number): void;
/**
* 注册一个光环转换器
* @param converter 光环转换器
*/
registerAuraConverter(converter: IAuraConverter<TEnemy, THero>): void;
/**
* 注销一个光环转换器
* @param converter 光环转换器
*/
unregisterAuraConverter(converter: IAuraConverter<TEnemy, THero>): void;
/**
* 设置光环转换器的启用状态
* @param converter 光环转换器
* @param enabled 是否启用
*/
setAuraConverterEnabled(
converter: IAuraConverter<TEnemy, THero>,
enabled: boolean
): void;
/**
* 注册一个特殊属性查询效果
* @param effect 特殊属性查询效果
*/
registerSpecialQueryEffect(
effect: IEnemySpecialQueryEffect<TEnemy, THero>
): void;
/**
* 注销一个特殊属性查询效果
* @param effect 特殊属性查询效果
*/
unregisterSpecialQueryEffect(
effect: IEnemySpecialQueryEffect<TEnemy, THero>
): void;
/**
* 为指定特殊属性代码注册常规查询效果
* @param code 特殊属性代码
* @param effect 常规查询效果
*/
registerCommonQueryEffect(
code: number,
effect: IEnemyCommonQueryEffect<TEnemy, THero>
): void;
/**
* 注销指定特殊属性代码上的常规查询效果
* @param code 特殊属性代码
* @param effect 常规查询效果
*/
unregisterCommonQueryEffect(
code: number,
effect: IEnemyCommonQueryEffect<TEnemy, THero>
): void;
/**
* 注册一个最终效果
* @param effect 最终效果
*/
registerFinalEffect(effect: IEnemyFinalEffect<TEnemy, THero>): void;
/**
* 注销一个最终效果
* @param effect 最终效果
*/
unregisterFinalEffect(effect: IEnemyFinalEffect<TEnemy, THero>): void;
/**
* 绑定勇士对象
* @param hero 勇士属性对象
*/
bindHero(hero: IReadonlyHeroAttribute<THero> | null): void;
/**
* 获取当前绑定的勇士属性对象
*/
getBindedHero(): IReadonlyHeroAttribute<THero> | null;
/**
* 获取指定怪物对象当前所在位置
* @param enemy 怪物对象
*/
getEnemyLocator(enemy: IEnemy<TEnemy>): Readonly<ITileLocator> | null;
/**
* 获取指定怪物视图当前所在位置
* @param view 怪物视图
*/
getEnemyLocatorByView(
view: IEnemyView<TEnemy>
): Readonly<ITileLocator> | null;
/**
* 根据定位符获取怪物视图
* @param locator 地图定位符
*/
getEnemyByLocator(locator: ITileLocator): IEnemyView<TEnemy> | null;
/**
* 根据坐标获取怪物视图
* @param x 横坐标
* @param y 纵坐标
*/
getEnemyByLoc(x: number, y: number): IEnemyView<TEnemy> | null;
/**
* 根据计算后怪物对象反查怪物视图
* @param enemy 计算后怪物对象
*/
getViewByComputed(enemy: IReadonlyEnemy<TEnemy>): IEnemyView<TEnemy> | null;
/**
* 在指定位置放置一个怪物对象
* @param locator 地图定位符
* @param enemy 怪物对象
*/
setEnemyAt(locator: ITileLocator, enemy: IEnemy<TEnemy>): void;
/**
* 删除指定位置的怪物
* @param locator 地图定位符
*/
deleteEnemy(locator: ITileLocator): void;
/**
* 扫描指定范围内的怪物视图
* @param range 范围对象
* @param param 范围参数
*/
scanRange<T>(
range: IRange<T>,
param: T
): Iterable<[ITileLocator, IEnemyView<TEnemy>]>;
/**
* 迭代上下文中的全部怪物
*/
iterateEnemy(): Iterable<[ITileLocator, IEnemyView<TEnemy>]>;
/**
* 添加一个全局光环视图
* @param aura 光环视图
*/
addAura(aura: IAuraView<TEnemy>): void;
/**
* 删除一个全局光环视图
* @param aura 光环视图
*/
deleteAura(aura: IAuraView<TEnemy>): void;
/**
* 绑定地图伤害管理器
* @param damage 地图伤害管理器
*/
attachMapDamage(damage: IMapDamage<TEnemy, THero> | null): void;
/**
* 获取当前绑定的地图伤害管理器
*/
getMapDamage(): IMapDamage<TEnemy, THero> | null;
/**
* 绑定伤害计算系统
* @param system 伤害系统
*/
attachDamageSystem(system: IDamageSystem<TEnemy, unknown> | null): void;
/**
* 获取当前绑定的伤害计算系统
*/
getDamageSystem(): IDamageSystem<TEnemy, THero> | null;
/**
* 重建当前上下文中的全部怪物计算结果
*
* 1. 对所有光环及特殊查询进行构建操作,这一步中会决定每个怪物所拥有的特殊属性,后续不会变动
* 2. 执行所有的普通光环效果,修改怪物的基础属性
* 3. 执行常规查询效果,允许查询上下文状态并修改怪物自身的基础属性
* 4. 执行最终效果,不允许查询上下文状态,仅允许修改怪物自身的基础属性
*/
buildup(): void;
/**
* 将指定怪物视图标记为脏
* @param view 怪物视图
*/
markDirty(view: IEnemyView<TEnemy>): void;
/**
* 申请刷新指定怪物视图
* @param view 怪物视图
*/
requestRefresh(view: IEnemyView<TEnemy>): void;
/**
* 清空当前上下文中的所有对象与运行状态
*/
clear(): void;
/**
* 销毁当前上下文
*/
destroy(): void;
}
//#endregion
@@ -1,5 +1,6 @@
export * from './face';
export * from './faceManager';
export * from './indexer';
export * from './mover';
export * from './types';
export * from './utils';
-1
View File
@@ -1,4 +1,3 @@
export * from './combat';
export * from './common';
export * from './enemy';
export * from './flag';