diff --git a/packages-user/client-modules/src/render/commonIns.ts b/packages-user/client-modules/src/render/commonIns.ts index 61799d4..1ab23ca 100644 --- a/packages-user/client-modules/src/render/commonIns.ts +++ b/packages-user/client-modules/src/render/commonIns.ts @@ -16,7 +16,7 @@ export async function createMainExtension() { mainMapRenderer.useAsset(materials.trackedAsset); const layer = state.layer.getLayerByAlias('event'); if (layer) { - mainMapExtension.addHero(state.hero, layer); + mainMapExtension.addHero(state.hero.mover, layer); mainMapExtension.addDoor(layer); } mainMapExtension.addText(); diff --git a/packages-user/data-base/src/enemy/context.ts b/packages-user/data-base/src/enemy/context.ts index 73ca631..5cbbe18 100644 --- a/packages-user/data-base/src/enemy/context.ts +++ b/packages-user/data-base/src/enemy/context.ts @@ -8,17 +8,20 @@ import { IEnemyCommonQueryEffect, IEnemyContext, IEnemyFinalEffect, + IEnemyHandler, IEnemySpecialModifier, IEnemySpecialQueryEffect, IEnemyView, IMapDamage, IReadonlyEnemy, + IReadonlyEnemyHandler, ISpecial } from './types'; import { EnemyView } from './enemy'; import { MapLocIndexer } from './utils'; +import { IReadonlyHeroAttribute } from '../hero'; -export class EnemyContext implements IEnemyContext { +export class EnemyContext implements IEnemyContext { private readonly enemyViewMap: Map> = new Map(); private readonly enemyMap: Map> = new Map(); private readonly locatorViewMap: Map, number> = new Map(); @@ -28,23 +31,26 @@ export class EnemyContext implements IEnemyContext { EnemyView > = new Map(); - private readonly auraConverter: Set> = new Set(); - private readonly converterStatus: Map, boolean> = - new Map(); + private readonly auraConverter: Set> = + new Set(); + private readonly converterStatus: Map< + IAuraConverter, + boolean + > = new Map(); private readonly convertedAura: Map, IAuraView> = new Map(); private readonly commonQueryMap: Map< number, - IEnemyCommonQueryEffect[] + IEnemyCommonQueryEffect[] > = new Map(); private readonly specialQueryEffects: Map< number, - IEnemySpecialQueryEffect[] + IEnemySpecialQueryEffect[] > = new Map(); - private readonly finalEffects: IEnemyFinalEffect[] = []; + private readonly finalEffects: IEnemyFinalEffect[] = []; private readonly globalAuraList: Set> = new Set(); private readonly sortedAura: Map>> = new Map(); @@ -52,10 +58,17 @@ export class EnemyContext implements IEnemyContext { private readonly requestedCommonContext: Set> = new Set(); private readonly dirtyEnemy: Set> = new Set(); - private mapDamage: IMapDamage | null = null; - private damageSystem: IDamageSystem | null = null; + /** 当前绑定的勇士属性对象 */ + private bindedHero: IReadonlyHeroAttribute | null = null; + /** 地图伤害对象 */ + private mapDamage: IMapDamage | null = null; + /** 伤害系统对象 */ + private damageSystem: IDamageSystem | null = null; + + /** 索引工具 */ readonly indexer: MapLocIndexer = new MapLocIndexer(); + /** 当前是否需要全量刷新 */ private needUpdate: boolean = true; built: boolean = false; @@ -70,20 +83,20 @@ export class EnemyContext implements IEnemyContext { this.needUpdate = true; } - registerAuraConverter(converter: IAuraConverter): void { + registerAuraConverter(converter: IAuraConverter): void { this.auraConverter.add(converter); this.converterStatus.set(converter, true); this.needUpdate = true; } - unregisterAuraConverter(converter: IAuraConverter): void { + unregisterAuraConverter(converter: IAuraConverter): void { this.auraConverter.delete(converter); this.converterStatus.delete(converter); this.needUpdate = true; } setAuraConverterEnabled( - converter: IAuraConverter, + converter: IAuraConverter, enabled: boolean ): void { if (!this.auraConverter.has(converter)) return; @@ -93,7 +106,7 @@ export class EnemyContext implements IEnemyContext { registerCommonQueryEffect( code: number, - effect: IEnemyCommonQueryEffect + effect: IEnemyCommonQueryEffect ): void { const array = this.commonQueryMap.getOrInsert(code, []); array.push(effect); @@ -103,7 +116,7 @@ export class EnemyContext implements IEnemyContext { unregisterCommonQueryEffect( code: number, - effect: IEnemyCommonQueryEffect + effect: IEnemyCommonQueryEffect ): void { const array = this.commonQueryMap.get(code); if (!array) return; @@ -113,14 +126,16 @@ export class EnemyContext implements IEnemyContext { this.needUpdate = true; } - registerSpecialQueryEffect(effect: IEnemySpecialQueryEffect): void { + registerSpecialQueryEffect( + effect: IEnemySpecialQueryEffect + ): void { const list = this.specialQueryEffects.getOrInsert(effect.priority, []); list.push(effect); this.needUpdate = true; } unregisterSpecialQueryEffect( - effect: IEnemySpecialQueryEffect + effect: IEnemySpecialQueryEffect ): void { const list = this.specialQueryEffects.get(effect.priority); if (!list) return; @@ -134,13 +149,13 @@ export class EnemyContext implements IEnemyContext { this.needUpdate = true; } - registerFinalEffect(effect: IEnemyFinalEffect): void { + registerFinalEffect(effect: IEnemyFinalEffect): void { this.finalEffects.push(effect); this.finalEffects.sort((a, b) => b.priority - a.priority); this.needUpdate = true; } - unregisterFinalEffect(effect: IEnemyFinalEffect): void { + unregisterFinalEffect(effect: IEnemyFinalEffect): void { const index = this.finalEffects.indexOf(effect); if (index !== -1) { this.finalEffects.splice(index, 1); @@ -148,6 +163,29 @@ export class EnemyContext implements IEnemyContext { this.needUpdate = true; } + bindHero(hero: IReadonlyHeroAttribute | null): void { + this.bindedHero = hero; + this.needUpdate = true; + this.damageSystem?.bindHeroStatus(hero); + this.mapDamage?.refreshAll(); + } + + getBindedHero(): IReadonlyHeroAttribute | null { + return this.bindedHero; + } + + /** + * 创建可修改信息对象 + * @param enemy 怪物对象 + * @param locator 怪物位置 + */ + private createHandler( + enemy: IEnemy, + locator: ITileLocator + ): IEnemyHandler { + return { enemy, locator, hero: this.bindedHero! }; + } + getEnemyLocator(enemy: IEnemy): Readonly | null { const index = this.locatorEnemyMap.get(enemy); if (index === undefined) return null; @@ -177,7 +215,7 @@ export class EnemyContext implements IEnemyContext { } /** - * 删除指定索引位置的怪物以及与之关联的所有运行时状态。 + * 删除指定索引位置的怪物以及与之关联的所有运行时状态 * @param index 地图索引 */ private deleteEnemyAt(index: number) { @@ -231,14 +269,14 @@ export class EnemyContext implements IEnemyContext { } /** - * 在指定范围内筛选出当前上下文中的怪物视图。 + * 在指定范围内筛选出当前上下文中的怪物视图 * @param range 范围对象 * @param param 范围参数 */ private *internalScanRange( range: IRange, param: T - ): Iterable> { + ): Iterable<[ITileLocator, EnemyView]> { range.bindHost(this); const keys = new Set(this.enemyViewMap.keys()); const matched = range.autoDetect(keys, param); @@ -246,12 +284,16 @@ export class EnemyContext implements IEnemyContext { for (const index of matched) { const view = viewMap.get(index); if (view) { - yield view; + const locator = this.indexer.indexToLocator(index); + yield [locator, view]; } } } - scanRange(range: IRange, param: T): Iterable> { + scanRange( + range: IRange, + param: T + ): Iterable<[ITileLocator, IEnemyView]> { return this.internalScanRange(range, param); } @@ -272,44 +314,42 @@ export class EnemyContext implements IEnemyContext { this.needUpdate = true; } - attachMapDamage(damage: IMapDamage | null): void { + attachMapDamage(damage: IMapDamage | null): void { this.mapDamage = damage; if (damage) { damage.refreshAll(); } } - getMapDamage(): IMapDamage | null { + getMapDamage(): IMapDamage | null { return this.mapDamage; } attachDamageSystem(system: IDamageSystem | null): void { this.damageSystem = system; if (system) { - system.markAllDirty(); + system.bindHeroStatus(this.bindedHero); } } - getDamageSystem(): IDamageSystem | null { - return this.damageSystem as IDamageSystem | null; + getDamageSystem(): IDamageSystem | null { + return this.damageSystem; } /** - * 将怪物身上的特殊属性尝试转换为光环视图。 + * 将怪物身上的特殊属性尝试转换为光环视图 * @param special 特殊属性 * @param enemy 怪物对象 * @param locator 怪物位置 */ private convertSpecial( special: ISpecial, - enemy: IReadonlyEnemy, - locator: ITileLocator + handler: IReadonlyEnemyHandler ): IEnemyAuraView | null { - let matched: IAuraConverter | null = null; - + let matched: IAuraConverter | null = null; for (const converter of this.auraConverter) { if (!this.converterStatus.get(converter)) continue; - if (converter.shouldConvert(special, enemy, locator)) { + if (converter.shouldConvert(special, handler)) { if (matched) { logger.warn(97, special.code.toString()); return null; @@ -319,11 +359,11 @@ export class EnemyContext implements IEnemyContext { } if (!matched) return null; - return matched.convert(special, enemy, locator, this); + return matched.convert(special, handler, this); } /** - * 将光环按优先级插入到有序表中。 + * 将光环按优先级插入到有序表中 * @param aura 光环视图 */ private insertIntoSortedAura(aura: IAuraView): void { @@ -335,7 +375,7 @@ export class EnemyContext implements IEnemyContext { } /** - * 从优先级表中移除一个光环。 + * 从优先级表中移除一个光环 * @param aura 光环视图 */ private removeFromSortedAura(aura: IAuraView): void { @@ -349,7 +389,7 @@ export class EnemyContext implements IEnemyContext { } /** - * 执行特殊属性修饰器,并返回因此受到影响的光环集合。 + * 执行特殊属性修饰器,并返回因此受到影响的光环集合 * @param modifier 特殊属性修饰器 * @param enemy 目标怪物 * @param locator 怪物位置 @@ -357,14 +397,13 @@ export class EnemyContext implements IEnemyContext { */ private processSpecialModifier( modifier: IEnemySpecialModifier, - enemy: IEnemy, - locator: ITileLocator, + handler: IEnemyHandler, currentPriority: number ): Set> { - const toAdd = modifier.add(enemy, locator); - const toDelete = modifier.delete(enemy, locator); - + const enemy = handler.enemy; const affectedAuras = new Set>(); + const toAdd = modifier.add(handler); + const toDelete = modifier.delete(handler); if (toAdd.length > 0 && toDelete.length > 0) { logger.warn(100); @@ -372,9 +411,9 @@ export class EnemyContext implements IEnemyContext { } for (const adding of toAdd) { - const aura = this.convertSpecial(adding, enemy, locator); + const aura = this.convertSpecial(adding, handler); if (aura) { - // 新生成的光环只能影响之后的阶段,不能反过来影响当前优先级链。 + // 新生成的光环只能影响之后的阶段,不能反过来影响当前优先级链 if (import.meta.env.DEV && aura.priority > currentPriority) { logger.warn( 99, @@ -410,7 +449,7 @@ export class EnemyContext implements IEnemyContext { } for (const special of enemy.iterateSpecials()) { - const success = modifier.modify(enemy, special, locator); + const success = modifier.modify(handler, special); if (!success) continue; const aura = this.convertedAura.get(special); if (!aura) continue; @@ -429,12 +468,12 @@ export class EnemyContext implements IEnemyContext { } /** - * 执行单个特殊查询效果。 + * 执行单个特殊查询效果 * @param effect 特殊查询效果 * @param currentPriority 当前处理的优先级 */ private processSpecialQuery( - effect: IEnemySpecialQueryEffect, + effect: IEnemySpecialQueryEffect, currentPriority: number ): void { const modifier = effect.for(this); @@ -442,13 +481,13 @@ export class EnemyContext implements IEnemyContext { 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(enemy, locator)) continue; + if (!modifier.shouldQuery(handler)) continue; const affectedAuras = this.processSpecialModifier( modifier, - enemy, - locator, + handler, currentPriority ); @@ -461,7 +500,7 @@ export class EnemyContext implements IEnemyContext { } /** - * 执行光环带来的特殊属性修饰效果。 + * 执行光环带来的特殊属性修饰效果 * @param aura 光环视图 * @param currentPriority 当前处理的优先级 */ @@ -470,30 +509,22 @@ export class EnemyContext implements IEnemyContext { currentPriority: number ): void { const param = aura.getRangeParam(); + const iter = this.internalScanRange(aura.range, param); - for (const enemyView of this.internalScanRange(aura.range, param)) { - const locator = this.getEnemyLocatorByView(enemyView); - if (!locator) continue; - + for (const [locator, enemyView] of iter) { const enemy = enemyView.getComputingEnemy(); const base = enemyView.getBaseEnemy(); - const modifier = aura.applySpecial(enemy, base, locator); - + const handler = this.createHandler(enemy, locator); + const modifier = aura.applySpecial(handler, base); if (!modifier) continue; - this.processSpecialModifier( - modifier, - enemy, - locator, - currentPriority - ); - + this.processSpecialModifier(modifier, handler, currentPriority); this.needTotallyRefresh.add(enemyView); } } /** - * 构建所有由特殊属性衍生出的光环与特殊查询结果。 + * 构建所有由特殊属性衍生出的光环与特殊查询结果 */ private buildupSpecials(): void { for (const aura of this.globalAuraList) { @@ -503,9 +534,10 @@ export class EnemyContext implements IEnemyContext { 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, enemy, locator); + const aura = this.convertSpecial(special, handler); if (!aura) continue; this.convertedAura.set(special, aura); this.insertIntoSortedAura(aura); @@ -554,7 +586,7 @@ export class EnemyContext implements IEnemyContext { } /** - * 按优先级执行所有基础属性光环效果。 + * 按优先级执行所有基础属性光环效果 */ private buildupBase(): void { const priorities = [...this.sortedAura.keys()].sort((a, b) => b - a); @@ -563,23 +595,25 @@ export class EnemyContext implements IEnemyContext { if (!auras) continue; for (const aura of auras) { const param = aura.getRangeParam(); - for (const view of this.internalScanRange(aura.range, param)) { + const iter = this.internalScanRange(aura.range, param); + for (const [locator, view] of iter) { const enemy = view.getComputingEnemy(); const base = view.getBaseEnemy(); - const locator = this.getEnemyLocatorByView(view)!; - aura.apply(enemy, base, locator); + 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; @@ -589,7 +623,7 @@ export class EnemyContext implements IEnemyContext { const effects = this.commonQueryMap.get(special.code); if (!effects) continue; for (const effect of effects) { - effect.apply(enemy, special, query, locator); + effect.apply(handler, special, query); } } if (queried) { @@ -599,20 +633,25 @@ export class EnemyContext implements IEnemyContext { } /** - * 执行最终效果阶段。 + * 执行最终效果阶段 */ 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(enemy, locator); + 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(); @@ -650,18 +689,18 @@ export class EnemyContext implements IEnemyContext { } /** - * 在局部刷新期间执行特殊属性修饰器,但不重建光环拓扑。 + * 在局部刷新期间执行特殊属性修饰器,但不重建光环拓扑 * @param modifier 特殊属性修饰器 * @param enemy 目标怪物 * @param locator 怪物位置 */ private refreshSpecialModifier( modifier: IEnemySpecialModifier, - enemy: IEnemy, - locator: ITileLocator + handler: IEnemyHandler ): void { - const toAdd = modifier.add(enemy, locator); - const toDelete = modifier.delete(enemy, locator); + const enemy = handler.enemy; + const toAdd = modifier.add(handler); + const toDelete = modifier.delete(handler); if (toAdd.length > 0 && toDelete.length > 0) { logger.warn(100); @@ -671,7 +710,7 @@ export class EnemyContext implements IEnemyContext { for (const adding of toAdd) { enemy.addSpecial(adding); if (import.meta.env.DEV) { - const aura = this.convertSpecial(adding, enemy, locator); + const aura = this.convertSpecial(adding, handler); if (aura) { logger.warn(101, adding.code.toString()); } @@ -681,7 +720,7 @@ export class EnemyContext implements IEnemyContext { for (const deleting of toDelete) { enemy.deleteSpecial(deleting); if (import.meta.env.DEV) { - const aura = this.convertSpecial(deleting, enemy, locator); + const aura = this.convertSpecial(deleting, handler); if (aura) { logger.warn(101, deleting.code.toString()); } @@ -689,7 +728,7 @@ export class EnemyContext implements IEnemyContext { } for (const special of enemy.iterateSpecials()) { - const success = modifier.modify(enemy, special, locator); + const success = modifier.modify(handler, special); if (import.meta.env.DEV && success) { const aura = this.convertedAura.get(special); if (aura) { @@ -700,7 +739,7 @@ export class EnemyContext implements IEnemyContext { } /** - * 刷新单个怪物视图的计算结果。 + * 刷新单个怪物视图的计算结果 * @param view 怪物视图 */ private refreshEnemy(view: EnemyView): void { @@ -710,6 +749,7 @@ export class EnemyContext implements IEnemyContext { view.reset(); const enemy = view.getComputingEnemy(); const base = view.getBaseEnemy(); + const handler = this.createHandler(enemy, locator); const specialPriorities = new Set(); for (const priority of this.sortedAura.keys()) { @@ -725,30 +765,28 @@ export class EnemyContext implements IEnemyContext { 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); - // 局部刷新只重新判断“这个怪物是否被该光环命中”。 - const inRange = aura.range.inRange( - locator.x, - locator.y, - param - ); - if (!inRange) continue; - const modifier = aura.applySpecial(enemy, base, locator); + // 局部刷新只重新判断“这个怪物是否被该光环命中” + if (!aura.range.inRange(locator.x, locator.y, param)) { + continue; + } + const modifier = aura.applySpecial(handler, base); if (!modifier) continue; - this.refreshSpecialModifier(modifier, enemy, locator); + this.refreshSpecialModifier(modifier, handler); } } - const effects = this.specialQueryEffects.get(priority); if (effects) { for (const effect of effects) { const modifier = effect.for(this); - if (!modifier.shouldQuery(enemy, locator)) continue; - this.refreshSpecialModifier(modifier, enemy, locator); + if (!modifier.shouldQuery(handler)) continue; + this.refreshSpecialModifier(modifier, handler); } } } @@ -762,10 +800,8 @@ export class EnemyContext implements IEnemyContext { 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(enemy, base, locator); + if (!aura.range.inRange(locator.x, locator.y, param)) continue; + aura.apply(handler, base); } } @@ -779,7 +815,7 @@ export class EnemyContext implements IEnemyContext { const effects = this.commonQueryMap.get(special.code); if (!effects) continue; for (const effect of effects) { - effect.apply(enemy, special, query, locator); + effect.apply(handler, special, query); } } if (queried) { @@ -787,7 +823,7 @@ export class EnemyContext implements IEnemyContext { } for (const effect of this.finalEffects) { - effect.apply(enemy, locator); + effect.apply(handler); } this.dirtyEnemy.delete(view); @@ -846,5 +882,6 @@ export class EnemyContext implements IEnemyContext { this.commonQueryMap.clear(); this.specialQueryEffects.clear(); this.finalEffects.length = 0; + this.bindedHero = null; } } diff --git a/packages-user/data-base/src/enemy/damage.ts b/packages-user/data-base/src/enemy/damage.ts index 52bb965..32b785c 100644 --- a/packages-user/data-base/src/enemy/damage.ts +++ b/packages-user/data-base/src/enemy/damage.ts @@ -1,4 +1,4 @@ -import { logger } from '@motajs/common'; +import { ITileLocator, logger } from '@motajs/common'; import { CriticalableHeroStatus, IDamageCalculator, @@ -6,9 +6,11 @@ import { IEnemyContext, IEnemyCritical, IEnemyDamageInfo, + IReadonlyEnemyHandler, IEnemyView, IReadonlyEnemy } from './types'; +import { IHeroAttribute, IReadonlyHeroAttribute } from '../hero'; interface ICriticalSearchResult { /** 此临界点的属性值 */ @@ -21,12 +23,12 @@ export class DamageSystem implements IDamageSystem { /** 当前正在使用的计算器 */ private calculator: IDamageCalculator | null = null; /** 当前勇士属性 */ - private heroStatus: Readonly | null = null; + private heroStatus: IReadonlyHeroAttribute | null = null; /** 怪物伤害缓存 */ private readonly cache: Map, IEnemyDamageInfo> = new Map(); - constructor(readonly context: IEnemyContext) {} + constructor(readonly context: IEnemyContext) {} useCalculator(calculator: IDamageCalculator): void { this.calculator = calculator; @@ -37,35 +39,23 @@ export class DamageSystem implements IDamageSystem { return this.calculator; } - bindHeroStatus(hero: Readonly): void { + bindHeroStatus(hero: IReadonlyHeroAttribute): void { this.heroStatus = hero; this.markAllDirty(); } /** - * 深拷贝勇士属性 + * 创建只读信息对象 + * @param enemy 怪物对象 + * @param locator 怪物位置 + * @param hero 勇士属性对象 */ - private cloneHeroStatus(): THero | null { - if (!this.heroStatus) return null; - else return structuredClone(this.heroStatus); - } - - /** - * 在修改勇士属性的情况下计算怪物伤害 - * @param enemy 怪物属性 - * @param attribute 修改的属性键名 - * @param value 修改为的属性值 - * @returns - */ - private calculateDamageWithModified( + private createReadonlyHandler( enemy: IReadonlyEnemy, - attribute: CriticalableHeroStatus, - value: number - ): IEnemyDamageInfo { - const hero = this.cloneHeroStatus()!; - // @ts-expect-error 之后会进行修复 - hero[attribute] = value; - return this.calculator!.calculate(hero, enemy); + locator: ITileLocator, + hero: IReadonlyHeroAttribute + ): IReadonlyEnemyHandler { + return { enemy, locator, hero }; } getDamageInfo(enemy: IEnemyView): IEnemyDamageInfo | null { @@ -77,15 +67,20 @@ export class DamageSystem implements IDamageSystem { logger.warn(106); return null; } - const hero = this.cloneHeroStatus()!; + const hero = this.heroStatus; + const locator = this.context.getEnemyLocatorByView(enemy); + if (!hero || !locator) return null; const cached = this.cache.get(enemy); if (cached) { return cached; } - const info = this.calculator.calculate(hero, enemy.getComputedEnemy()); + const computed = enemy.getComputedEnemy(); + const handler = this.createReadonlyHandler(computed, locator, hero); + const info = this.calculator.calculate(handler); this.cache.set(enemy, info); + return info; } @@ -104,7 +99,7 @@ export class DamageSystem implements IDamageSystem { *calculateCritical( view: IEnemyView, attribute: CriticalableHeroStatus, - precision: number + precision: number = 12 ): Generator { if (!this.heroStatus) { logger.warn(107); @@ -115,15 +110,19 @@ export class DamageSystem implements IDamageSystem { return; } - const currentInfo = this.getDamageInfo(view); - if (!currentInfo) return; + const locator = this.context.getEnemyLocatorByView(view); + if (!locator) return; const enemy = view.getComputedEnemy(); - const hero = this.cloneHeroStatus()!; - const currentValue = hero[attribute] as number; + 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(hero, enemy, attribute) + this.calculator.getCriticalLimit(handler, attribute) ); if (currentValue >= upperLimit) return; @@ -134,7 +133,8 @@ export class DamageSystem implements IDamageSystem { while (baseValue < upperLimit) { const next = this.findNextCritical( - enemy, + handler, + hero, attribute, baseValue, upperLimit, @@ -159,7 +159,8 @@ export class DamageSystem implements IDamageSystem { /** * 计算下一个临界点 - * @param enemy 怪物对象 + * @param handler 信息对象,其中的 `hero` 成员与 `hero` 参数同引用 + * @param hero 可修改勇士属性对象,与 `handler` 中的 `hero` 成员同引用 * @param attribute 勇士属性名 * @param currentValue 当前勇士属性值 * @param upperLimit 二分上界 @@ -167,7 +168,8 @@ export class DamageSystem implements IDamageSystem { * @param maxIterations 最大迭代数量 */ private findNextCritical( - enemy: IReadonlyEnemy, + handler: IReadonlyEnemyHandler, + hero: IHeroAttribute, attribute: CriticalableHeroStatus, currentValue: number, upperLimit: number, @@ -176,36 +178,30 @@ export class DamageSystem implements IDamageSystem { ): ICriticalSearchResult | null { let left = currentValue; let right = upperLimit; - let rightInfo = this.calculateDamageWithModified( - enemy, - attribute, - right - ); - if (rightInfo.damage >= referenceDamage) return null; + 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) { + while (iter++ < maxIterations) { const middle = Math.floor((left + right) / 2); - const middleInfo = this.calculateDamageWithModified( - enemy, - attribute, - middle - ); + hero.setBaseAttribute(attribute, middle as THero[typeof attribute]); + const middleInfo = this.calculator!.calculate(handler); + if (middleInfo.damage < referenceDamage) { right = middle; - rightInfo = middleInfo; } else { left = middle; + targetInfo = middleInfo; } if (right - left <= 1) break; - - iter++; } return { value: right, - info: rightInfo + info: targetInfo }; } } diff --git a/packages-user/data-base/src/enemy/enemy.ts b/packages-user/data-base/src/enemy/enemy.ts index 1c280a3..fc4fae3 100644 --- a/packages-user/data-base/src/enemy/enemy.ts +++ b/packages-user/data-base/src/enemy/enemy.ts @@ -93,7 +93,7 @@ export class EnemyView implements IEnemyView { constructor( readonly baseEnemy: IEnemy, - readonly context: IEnemyContext + readonly context: IEnemyContext ) { this.computedEnemy = baseEnemy.clone(); } diff --git a/packages-user/data-base/src/enemy/mapDamage.ts b/packages-user/data-base/src/enemy/mapDamage.ts index a204161..be665c4 100644 --- a/packages-user/data-base/src/enemy/mapDamage.ts +++ b/packages-user/data-base/src/enemy/mapDamage.ts @@ -1,6 +1,7 @@ import { logger, ITileLocator } from '@motajs/common'; import { IEnemyContext, + IReadonlyEnemyHandler, IEnemyView, IMapDamage, IMapDamageConverter, @@ -33,9 +34,9 @@ interface IDamageStore { readonly index: number; } -export class MapDamage implements IMapDamage { +export class MapDamage implements IMapDamage { /** 当前使用的地图伤害转换器 */ - private converter: IMapDamageConverter | null = null; + private converter: IMapDamageConverter | null = null; /** 当前使用的地图伤害合并器 */ private reducer: IMapDamageReducer | null = null; @@ -62,15 +63,33 @@ export class MapDamage implements IMapDamage { /** 坐标索引对象 */ private readonly indexer: IMapLocIndexer; - constructor(readonly context: IEnemyContext) { + constructor(readonly context: IEnemyContext) { this.indexer = context.indexer; } - useConverter(converter: IMapDamageConverter): void { + useConverter(converter: IMapDamageConverter): void { this.converter = converter; this.refreshAll(); } + /** + * 创建只读信息对象 + * @param view 怪物视图 + * @param locator 怪物位置 + */ + private createReadonlyHandler( + view: IEnemyView, + locator: ITileLocator + ): IReadonlyEnemyHandler | null { + const hero = this.context.getBindedHero(); + if (!hero) return null; + return { + enemy: view.getComputedEnemy(), + locator, + hero + }; + } + useReducer(reducer: IMapDamageReducer): void { this.reducer = reducer; this.reducedCache.clear(); @@ -174,6 +193,7 @@ export class MapDamage implements IMapDamage { const sourced = this.sourcedDamage.get(index); if (sourceless) { if (sourced) { + // 大集合 union 小集合会更快,一般有来源伤害更多,所以 source union sourceless return sourced.damages.union(sourceless.damages); } else { return sourceless.damages; @@ -237,9 +257,11 @@ export class MapDamage implements IMapDamage { locator: ITileLocator ) { this.removeEnemyAffecting(view); - const enemy = view.getComputedEnemy(); - const views = this.converter!.convert(enemy, locator, this.context); - const set = new Set(views); + 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>(views); if (set.size === 0) return; this.enemyStore.set(view, set); const collection = new Set(); @@ -275,9 +297,11 @@ export class MapDamage implements IMapDamage { */ private refreshEnemy(view: IEnemyView, locator: ITileLocator) { this.removeEnemyAffecting(view); - const enemy = view.getComputedEnemy(); - const views = this.converter!.convert(enemy, locator, this.context); - const set = new Set(views); + 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>(views); if (set.size === 0) return; this.enemyStore.set(view, set); set.forEach(viewItem => { diff --git a/packages-user/data-base/src/enemy/types.ts b/packages-user/data-base/src/enemy/types.ts index 2da3943..81d77e1 100644 --- a/packages-user/data-base/src/enemy/types.ts +++ b/packages-user/data-base/src/enemy/types.ts @@ -1,4 +1,5 @@ import { IRange, ITileLocator } from '@motajs/common'; +import { IReadonlyHeroAttribute } from '../hero'; //#region 怪物基础 @@ -246,13 +247,31 @@ export interface IMapLocIndexer extends IMapLocHelper { setWidth(width: number): void; } +export interface IEnemyHandler { + /** 怪物属性信息 */ + readonly enemy: IEnemy; + /** 怪物定位符 */ + readonly locator: ITileLocator; + /** 勇士属性信息 */ + readonly hero: IReadonlyHeroAttribute; +} + +export interface IReadonlyEnemyHandler { + /** 怪物属性信息 */ + readonly enemy: IReadonlyEnemy; + /** 怪物定位符 */ + readonly locator: ITileLocator; + /** 勇士属性信息 */ + readonly hero: IReadonlyHeroAttribute; +} + //#endregion //#region 怪物对象 export interface IEnemyView { /** 怪物视图所属的上下文 */ - readonly context: IEnemyContext; + readonly context: IEnemyContext; /** * 重置此怪物视图的状态,将计算后怪物对象恢复至初始状态 @@ -288,31 +307,24 @@ export interface IEnemyView { export interface IEnemySpecialModifier { /** * 获取要添加到指定怪物身上的特殊属性 - * @param enemy 怪物对象 - * @param locator 怪物定位符 + * @param handler 信息对象 */ - add(enemy: IReadonlyEnemy, locator: ITileLocator): ISpecial[]; + add(handler: IReadonlyEnemyHandler): ISpecial[]; /** * 获取制定怪物身上要删除的特殊属性 - * @param enemy 怪物对象 - * @param locator 怪物定位符 + * @param handler 信息对象 */ - delete( - enemy: IReadonlyEnemy, - locator: ITileLocator - ): ISpecial[]; + delete(handler: IReadonlyEnemyHandler): ISpecial[]; /** * 修改一个怪物的特殊属性,如果真正进行了修改则返回 true,否则返回 false - * @param enemy 怪物对象 + * @param handler 信息对象 * @param special 要修改的怪物特殊属性 - * @param locator 怪物定位符 */ modify( - enemy: IReadonlyEnemy, - special: ISpecial, - locator: ITileLocator + handler: IEnemyHandler, + special: ISpecial ): boolean; } @@ -334,26 +346,22 @@ export interface IAuraView { /** * 对指定怪物对象施加修饰器 - * @param enemy 怪物对象 + * @param handler 信息对象 * @param baseEnemy 原始怪物对象,即未进行任何修改的怪物对象 - * @param locator 怪物定位符 */ apply( - enemy: IEnemy, - baseEnemy: IReadonlyEnemy, - locator: ITileLocator + handler: IEnemyHandler, + baseEnemy: IReadonlyEnemy ): void; /** * 对指定怪物对象添加特殊属性修饰器 - * @param enemy 怪物对象 + * @param handler 信息对象 * @param baseEnemy 原始怪物对象,即未进行任何修改的怪物对象 - * @param locator 怪物定位符 */ applySpecial( - enemy: IReadonlyEnemy, - baseEnemy: IReadonlyEnemy, - locator: ITileLocator + handler: IEnemyHandler, + baseEnemy: IReadonlyEnemy ): IEnemySpecialModifier | null; } @@ -366,14 +374,15 @@ export interface IEnemyAuraView extends IAuraView { readonly locator: ITileLocator; } -export interface IAuraConverter { +export interface IAuraConverter { /** * 判断一个特殊属性是否应该被当前光环转换器执行转换 + * @param special 要转换的特殊属性 + * @param handler 信息对象 */ shouldConvert( special: ISpecial, - enemy: IReadonlyEnemy, - locator: ITileLocator + handler: IReadonlyEnemyHandler ): boolean; /** @@ -381,32 +390,34 @@ export interface IAuraConverter { */ convert( special: ISpecial, - enemy: IReadonlyEnemy, - locator: ITileLocator, - context: IEnemyContext + handler: IReadonlyEnemyHandler, + context: IEnemyContext ): IEnemyAuraView; } export interface IEnemySpecialQueryModifier< - TAttr + TAttr, + THero > extends IEnemySpecialModifier { /** * 判断一个怪物是否应该查询外部状态 */ - shouldQuery(enemy: IReadonlyEnemy, locator: ITileLocator): boolean; + shouldQuery(handler: IReadonlyEnemyHandler): boolean; } -export interface IEnemySpecialQueryEffect { +export interface IEnemySpecialQueryEffect { /** 效果优先级,与光环属性共用 */ readonly priority: number; /** * 根据传入的怪物上下文,获取对应的怪物特殊属性修饰器 */ - for(ctx: IEnemyContext): IEnemySpecialQueryModifier; + for( + ctx: IEnemyContext + ): IEnemySpecialQueryModifier; } -export interface IEnemyCommonQueryEffect { +export interface IEnemyCommonQueryEffect { /** 优先级,越高的越先执行 */ readonly priority: number; @@ -414,21 +425,20 @@ export interface IEnemyCommonQueryEffect { * 对怪物的某个特殊属性施加常规查询效果 */ apply( - enemy: IEnemy, + handler: IEnemyHandler, special: ISpecial, - query: () => IEnemyContext, - locator: ITileLocator + query: () => IEnemyContext ): void; } -export interface IEnemyFinalEffect { +export interface IEnemyFinalEffect { /** 效果优先级,越高会越先被执行 */ readonly priority: number; /** * 向怪物施加最终修饰效果 */ - apply(enemy: IEnemy, locator: ITileLocator): void; + apply(handler: IEnemyHandler): void; } //#endregion @@ -477,14 +487,13 @@ export interface IMapDamageView { ): Readonly | null; } -export interface IMapDamageConverter { +export interface IMapDamageConverter { /** * 转换地图伤害视图 */ convert( - enemy: IReadonlyEnemy, - locator: ITileLocator, - context: IEnemyContext + handler: IReadonlyEnemyHandler, + context: IEnemyContext ): IMapDamageView[]; } @@ -498,15 +507,15 @@ export interface IMapDamageReducer { ): Readonly; } -export interface IMapDamage { +export interface IMapDamage { /** 当前绑定的怪物上下文 */ - readonly context: IEnemyContext; + readonly context: IEnemyContext; /** * 设置地图伤害转换器,并基于当前上下文重建所有地图伤害视图 * @param converter 地图伤害转换器 */ - useConverter(converter: IMapDamageConverter): void; + useConverter(converter: IMapDamageConverter): void; /** * 设置地图伤害合并器 @@ -593,36 +602,30 @@ export interface IEnemyCritical { } export type CriticalableHeroStatus = keyof { - [P in keyof THero as THero[P] extends number ? P : never]: unknown; + [P in keyof THero as THero[P] extends number ? P : never]: number; }; export interface IDamageCalculator { /** * 计算战斗伤害信息 - * @param hero 勇士信息 - * @param enemy 怪物信息 + * @param handler 信息对象 */ - calculate( - hero: Readonly, - enemy: IReadonlyEnemy - ): IEnemyDamageInfo; + calculate(handler: IReadonlyEnemyHandler): IEnemyDamageInfo; /** * 获取临界计算的上界 - * @param hero 勇士信息 - * @param enemy 怪物信息 + * @param handler 信息对象 * @param attribute 勇士的临界属性 */ getCriticalLimit( - hero: Readonly, - enemy: IReadonlyEnemy, + handler: IReadonlyEnemyHandler, attribute: CriticalableHeroStatus ): number; } export interface IDamageSystem { /** 伤害系统所属的上下文 */ - readonly context: IEnemyContext; + readonly context: IEnemyContext; /** * 设置当前伤害计算系统使用的伤害计算器 @@ -639,7 +642,7 @@ export interface IDamageSystem { * 绑定勇士信息 * @param hero 勇士信息 */ - bindHeroStatus(hero: Readonly): void; + bindHeroStatus(hero: IReadonlyHeroAttribute | null): void; /** * 获取战斗伤害信息 @@ -668,12 +671,12 @@ export interface IDamageSystem { * 计算怪物在指定勇士属性下的临界 * @param enemy 怪物视图 * @param attribute 计算临界的目标勇士属性,比如计算攻击临界、自定义属性的临界等等 - * @param precision 临界计算精度,表示会进行多少次二分计算,一般填写 `12-16` 之间的数即可 + * @param precision 临界计算精度,表示会进行多少次二分计算,一般填写 `12-16` 之间的数即可,默认是 12 */ calculateCritical( enemy: IEnemyView, attribute: CriticalableHeroStatus, - precision: number + precision?: number ): Generator; } @@ -681,7 +684,7 @@ export interface IDamageSystem { //#region 上下文 -export interface IEnemyContext { +export interface IEnemyContext { /** 怪物上下文宽度 */ readonly width: number; /** 怪物上下文高度 */ @@ -700,13 +703,13 @@ export interface IEnemyContext { * 注册一个光环转换器 * @param converter 光环转换器 */ - registerAuraConverter(converter: IAuraConverter): void; + registerAuraConverter(converter: IAuraConverter): void; /** * 注销一个光环转换器 * @param converter 光环转换器 */ - unregisterAuraConverter(converter: IAuraConverter): void; + unregisterAuraConverter(converter: IAuraConverter): void; /** * 设置光环转换器的启用状态 @@ -714,7 +717,7 @@ export interface IEnemyContext { * @param enabled 是否启用 */ setAuraConverterEnabled( - converter: IAuraConverter, + converter: IAuraConverter, enabled: boolean ): void; @@ -722,13 +725,17 @@ export interface IEnemyContext { * 注册一个特殊属性查询效果 * @param effect 特殊属性查询效果 */ - registerSpecialQueryEffect(effect: IEnemySpecialQueryEffect): void; + registerSpecialQueryEffect( + effect: IEnemySpecialQueryEffect + ): void; /** * 注销一个特殊属性查询效果 * @param effect 特殊属性查询效果 */ - unregisterSpecialQueryEffect(effect: IEnemySpecialQueryEffect): void; + unregisterSpecialQueryEffect( + effect: IEnemySpecialQueryEffect + ): void; /** * 为指定特殊属性代码注册常规查询效果 @@ -737,7 +744,7 @@ export interface IEnemyContext { */ registerCommonQueryEffect( code: number, - effect: IEnemyCommonQueryEffect + effect: IEnemyCommonQueryEffect ): void; /** @@ -747,20 +754,31 @@ export interface IEnemyContext { */ unregisterCommonQueryEffect( code: number, - effect: IEnemyCommonQueryEffect + effect: IEnemyCommonQueryEffect ): void; /** * 注册一个最终效果 * @param effect 最终效果 */ - registerFinalEffect(effect: IEnemyFinalEffect): void; + registerFinalEffect(effect: IEnemyFinalEffect): void; /** * 注销一个最终效果 * @param effect 最终效果 */ - unregisterFinalEffect(effect: IEnemyFinalEffect): void; + unregisterFinalEffect(effect: IEnemyFinalEffect): void; + + /** + * 绑定勇士对象 + * @param hero 勇士属性对象 + */ + bindHero(hero: IReadonlyHeroAttribute | null): void; + + /** + * 获取当前绑定的勇士属性对象 + */ + getBindedHero(): IReadonlyHeroAttribute | null; /** * 获取指定怪物对象当前所在位置 @@ -813,7 +831,10 @@ export interface IEnemyContext { * @param range 范围对象 * @param param 范围参数 */ - scanRange(range: IRange, param: T): Iterable>; + scanRange( + range: IRange, + param: T + ): Iterable<[ITileLocator, IEnemyView]>; /** * 迭代上下文中的全部怪物 @@ -836,12 +857,12 @@ export interface IEnemyContext { * 绑定地图伤害管理器 * @param damage 地图伤害管理器 */ - attachMapDamage(damage: IMapDamage | null): void; + attachMapDamage(damage: IMapDamage | null): void; /** * 获取当前绑定的地图伤害管理器 */ - getMapDamage(): IMapDamage | null; + getMapDamage(): IMapDamage | null; /** * 绑定伤害计算系统 @@ -852,7 +873,7 @@ export interface IEnemyContext { /** * 获取当前绑定的伤害计算系统 */ - getDamageSystem(): IDamageSystem | null; + getDamageSystem(): IDamageSystem | null; /** * 重建当前上下文中的全部怪物计算结果 diff --git a/packages-user/data-base/src/hero/attribute.ts b/packages-user/data-base/src/hero/attribute.ts index f1bb76a..945dd1c 100644 --- a/packages-user/data-base/src/hero/attribute.ts +++ b/packages-user/data-base/src/hero/attribute.ts @@ -149,4 +149,8 @@ export class HeroAttribute implements IHeroAttribute { } return cloned; } + + getModifiableClone(): IHeroAttribute { + return this.clone(); + } } diff --git a/packages-user/data-base/src/hero/state.ts b/packages-user/data-base/src/hero/state.ts index 2960ab0..a202710 100644 --- a/packages-user/data-base/src/hero/state.ts +++ b/packages-user/data-base/src/hero/state.ts @@ -32,6 +32,6 @@ export class HeroState implements IHeroState { } getIsolatedAttribute(): IHeroAttribute { - return this.attribute.clone(); + return this.attribute.getModifiableClone(); } } diff --git a/packages-user/data-base/src/hero/types.ts b/packages-user/data-base/src/hero/types.ts index 876471e..f33a6a9 100644 --- a/packages-user/data-base/src/hero/types.ts +++ b/packages-user/data-base/src/hero/types.ts @@ -71,7 +71,12 @@ export interface IReadonlyHeroAttribute { * 深拷贝此勇士属性对象 * @param cloneModifier 是否同时复制修饰器,默认复制 */ - clone(cloneModifier?: boolean): IHeroAttribute; + clone(cloneModifier?: boolean): IReadonlyHeroAttribute; + + /** + * 获取此勇士属性对象的可修改副本 + */ + getModifiableClone(): IHeroAttribute; } export interface IHeroAttribute extends IReadonlyHeroAttribute { @@ -101,6 +106,12 @@ export interface IHeroAttribute extends IReadonlyHeroAttribute { name: K, modifier: IHeroModifier ): void; + + /** + * 深拷贝此勇士属性对象 + * @param cloneModifier 是否同时复制修饰器,默认复制 + */ + clone(cloneModifier?: boolean): IHeroAttribute; } //#endregion diff --git a/packages-user/data-state/src/core.ts b/packages-user/data-state/src/core.ts index a794295..7719a6b 100644 --- a/packages-user/data-state/src/core.ts +++ b/packages-user/data-state/src/core.ts @@ -13,7 +13,7 @@ import { HeroState, IHeroState } from '@user/data-base'; -import { IEnemyAttributes } from './enemy/types'; +import { IEnemyAttr } from './enemy/types'; import { CommonAuraConverter, EnemyLegacyBridge, @@ -24,7 +24,7 @@ import { registerSpecials } from './enemy'; import { HERO_DEFAULT_ATTRIBUTE, TILE_HEIGHT, TILE_WIDTH } from './shared'; -import { IHeroAttributeObject } from './hero'; +import { IHeroAttr } from './hero'; export class CoreState implements ICoreState { readonly layer: ILayerState; @@ -32,10 +32,10 @@ export class CoreState implements ICoreState { readonly idNumberMap: Map; readonly numberIdMap: Map; - readonly hero: IHeroState; + readonly hero: IHeroState; - readonly enemyManager: IEnemyManager; - readonly enemyContext: IEnemyContext; + readonly enemyManager: IEnemyManager; + readonly enemyContext: IEnemyContext; constructor() { this.layer = new LayerState(); @@ -43,6 +43,15 @@ export class CoreState implements ICoreState { this.idNumberMap = new Map(); this.numberIdMap = new Map(); + //#region 勇士初始化 + + const heroMover = new HeroMover(); + const heroAttribute = new HeroAttribute(HERO_DEFAULT_ATTRIBUTE); + const heroState = new HeroState(heroMover, heroAttribute); + this.hero = heroState; + + //#endregion + //#region 怪物初始化 // 怪物管理器初始化 @@ -56,7 +65,7 @@ export class CoreState implements ICoreState { registerSpecials(enemyManager); this.enemyManager = enemyManager; // 怪物上下文初始化 - const enemyContext = new EnemyContext(); + const enemyContext = new EnemyContext(); const damageSystem = new DamageSystem(enemyContext); const mapDamage = new MapDamage(enemyContext); mapDamage.useConverter(new MainMapDamageConverter()); @@ -67,18 +76,10 @@ export class CoreState implements ICoreState { enemyContext.registerAuraConverter(new GuardAuraConverter()); enemyContext.registerFinalEffect(new MainEnemyFinalEffect()); enemyContext.resize(TILE_WIDTH, TILE_HEIGHT); + enemyContext.bindHero(heroAttribute); this.enemyContext = enemyContext; //#endregion - - //#region 勇士初始化 - - const heroMover = new HeroMover(); - const heroAttribute = new HeroAttribute(HERO_DEFAULT_ATTRIBUTE); - const heroState = new HeroState(heroMover, heroAttribute); - this.hero = heroState; - - //#endregion } saveState(): IStateSaveData { diff --git a/packages-user/data-state/src/enemy/aura.ts b/packages-user/data-state/src/enemy/aura.ts index cc12fff..1220ba0 100644 --- a/packages-user/data-state/src/enemy/aura.ts +++ b/packages-user/data-state/src/enemy/aura.ts @@ -9,16 +9,18 @@ import { } from '@motajs/common'; import { IAuraConverter, + IEnemyHandler, IEnemyAuraView, IEnemyContext, IEnemySpecialModifier, IEnemyView, + IReadonlyEnemyHandler, IReadonlyEnemy, - ISpecial, - IEnemy + ISpecial } from '@user/data-base'; import { IHaloValue } from './special'; -import { IEnemyAttributes } from './types'; +import { IEnemyAttr } from './types'; +import { IHeroAttr } from '../hero'; const FULL_RANGE = new FullRange(); const RECT_RANGE = new RectRange(); @@ -26,22 +28,24 @@ const MANHATTAN_RANGE = new ManhattanRange(); //#region 25-光环 -export class CommonAuraConverter implements IAuraConverter { +export class CommonAuraConverter implements IAuraConverter< + IEnemyAttr, + IHeroAttr +> { shouldConvert(special: ISpecial): boolean { return special.code === 25; } convert( special: ISpecial, - enemy: IReadonlyEnemy, - locator: ITileLocator + handler: IReadonlyEnemyHandler ): CommonAura { - return new CommonAura(enemy, special, locator); + return new CommonAura(handler.enemy, special, handler.locator); } } export class CommonAura implements IEnemyAuraView< - IEnemyAttributes, + IEnemyAttr, IRectRangeParam | IManhattanRangeParam | void, IHaloValue > { @@ -51,7 +55,7 @@ export class CommonAura implements IEnemyAuraView< readonly range: IRange; constructor( - readonly enemy: IReadonlyEnemy, + readonly enemy: IReadonlyEnemy, readonly special: ISpecial, readonly locator: ITileLocator ) { @@ -89,9 +93,10 @@ export class CommonAura implements IEnemyAuraView< } apply( - enemy: IEnemy, - baseEnemy: IReadonlyEnemy + handler: IEnemyHandler, + baseEnemy: IReadonlyEnemy ): void { + const { enemy } = handler; const { hpBuff, atkBuff, defBuff } = this.special.value; if (hpBuff !== 0) { @@ -110,7 +115,7 @@ export class CommonAura implements IEnemyAuraView< } } - applySpecial(): IEnemySpecialModifier | null { + applySpecial(): IEnemySpecialModifier | null { return null; } } @@ -119,23 +124,25 @@ export class CommonAura implements IEnemyAuraView< //#region 26-支援 -export class GuardAuraConverter implements IAuraConverter { +export class GuardAuraConverter implements IAuraConverter< + IEnemyAttr, + IHeroAttr +> { shouldConvert(special: ISpecial): boolean { return special.code === 26; } convert( special: ISpecial, - enemy: IReadonlyEnemy, - locator: ITileLocator, - context: IEnemyContext + handler: IReadonlyEnemyHandler, + context: IEnemyContext ): GuardAura { - return new GuardAura(context, enemy, special, locator); + return new GuardAura(context, handler.enemy, special, handler.locator); } } export class GuardAura implements IEnemyAuraView< - IEnemyAttributes, + IEnemyAttr, IRectRangeParam, void > { @@ -144,11 +151,11 @@ export class GuardAura implements IEnemyAuraView< readonly couldApplySpecial: boolean = false; readonly range: IRange = RECT_RANGE; - private readonly sourceView: IEnemyView | null; + private readonly sourceView: IEnemyView | null; constructor( - context: IEnemyContext, - readonly enemy: IReadonlyEnemy, + context: IEnemyContext, + readonly enemy: IReadonlyEnemy, readonly special: ISpecial, readonly locator: ITileLocator ) { @@ -164,19 +171,16 @@ export class GuardAura implements IEnemyAuraView< }; } - apply( - enemy: IEnemy, - _baseEnemy: IReadonlyEnemy, - locator: ITileLocator - ): void { + apply(handler: IEnemyHandler): void { if (!this.sourceView) return; + const { enemy, locator } = handler; if (locator.x === this.locator.x && locator.y === this.locator.y) { return; } enemy.getAttribute('guard').add(this.sourceView); } - applySpecial(): IEnemySpecialModifier | null { + applySpecial(): IEnemySpecialModifier | null { return null; } } diff --git a/packages-user/data-state/src/enemy/final.ts b/packages-user/data-state/src/enemy/final.ts index 4d97277..0c722e7 100644 --- a/packages-user/data-state/src/enemy/final.ts +++ b/packages-user/data-state/src/enemy/final.ts @@ -1,29 +1,28 @@ -import { IEnemy, IEnemyFinalEffect } from '@user/data-base'; -import { IEnemyAttributes } from './types'; -import { ITileLocator } from '@motajs/common'; +import { IEnemyFinalEffect, IEnemyHandler } from '@user/data-base'; +import { IEnemyAttr } from './types'; +import { IHeroAttr } from '../hero'; -const HERO_STATUS_PLACEHOLDER = { - atk: 0, - def: 0 -} as const; - -export class MainEnemyFinalEffect implements IEnemyFinalEffect { +export class MainEnemyFinalEffect implements IEnemyFinalEffect< + IEnemyAttr, + IHeroAttr +> { readonly priority: number = 0; - apply(enemy: IEnemy, _locator: ITileLocator): void { + apply(handler: IEnemyHandler): void { + const enemy = handler.enemy; + const heroAtk = handler.hero.getFinalAttribute('atk'); + const heroDef = handler.hero.getFinalAttribute('def'); + // 3-坚固 if (enemy.hasSpecial(3)) { - const target = Math.max( - enemy.getAttribute('def'), - HERO_STATUS_PLACEHOLDER.atk - 1 - ); + const target = Math.max(enemy.getAttribute('def'), heroAtk - 1); enemy.setAttribute('def', target); } // 10-模仿 if (enemy.hasSpecial(10)) { - enemy.setAttribute('atk', HERO_STATUS_PLACEHOLDER.atk); - enemy.setAttribute('def', HERO_STATUS_PLACEHOLDER.def); + enemy.setAttribute('atk', heroAtk); + enemy.setAttribute('def', heroDef); } } } diff --git a/packages-user/data-state/src/enemy/legacy.ts b/packages-user/data-state/src/enemy/legacy.ts index 3c73f3a..60ddb52 100644 --- a/packages-user/data-state/src/enemy/legacy.ts +++ b/packages-user/data-state/src/enemy/legacy.ts @@ -1,11 +1,11 @@ import { IEnemyLegacyBridge } from '@user/data-base'; -import { IEnemyAttributes } from './types'; +import { IEnemyAttr } from './types'; -export class EnemyLegacyBridge implements IEnemyLegacyBridge { +export class EnemyLegacyBridge implements IEnemyLegacyBridge { fromLegacyEnemy( enemy: Enemy, - defaultAttr: Partial - ): IEnemyAttributes { + defaultAttr: Partial + ): IEnemyAttr { return { hp: enemy.hp ?? defaultAttr.hp ?? 0, atk: enemy.atk ?? defaultAttr.atk ?? 0, diff --git a/packages-user/data-state/src/enemy/mapDamage.ts b/packages-user/data-state/src/enemy/mapDamage.ts index 7684588..edd110c 100644 --- a/packages-user/data-state/src/enemy/mapDamage.ts +++ b/packages-user/data-state/src/enemy/mapDamage.ts @@ -11,17 +11,20 @@ import { RectRange, ITileLocator } from '@motajs/common'; -import { IReadonlyEnemy, ISpecial } from '@user/data-base'; import { IEnemyContext, IMapDamageConverter, IMapDamageInfo, IMapDamageInfoExtra, IMapDamageReducer, - IMapDamageView + IReadonlyEnemyHandler, + ISpecial, + IMapDamageView, + IReadonlyHeroAttribute } from '@user/data-base'; import { IZoneValue } from './special'; -import { IEnemyAttributes, MapDamageType } from './types'; +import { IEnemyAttr, MapDamageType } from './types'; +import { IHeroAttr } from '../hero'; const RECT_RANGE = new RectRange(); const MANHATTAN_RANGE = new ManhattanRange(); @@ -33,7 +36,9 @@ const DIR4 = [...DIRECTION_MAPPER.map(InternalDirectionGroup.Dir4)]; //#region 地图伤害 abstract class BaseMapDamageView implements IMapDamageView { - constructor(protected readonly context: IEnemyContext) {} + constructor( + protected readonly context: IEnemyContext + ) {} abstract getRange(): IRange; @@ -80,7 +85,7 @@ export class ZoneDamageView extends BaseMapDamageView< IRectRangeParam | IManhattanRangeParam > { constructor( - context: IEnemyContext, + context: IEnemyContext, private readonly locator: Readonly, private readonly special: Readonly> ) { @@ -108,16 +113,14 @@ export class ZoneDamageView extends BaseMapDamageView< }; } - getDamageWithoutCheck( - _locator: ITileLocator - ): Readonly | null { + getDamageWithoutCheck(): Readonly | null { return this.createInfo(this.special.value.zone, MapDamageType.Zone); } } export class RepulseDamageView extends BaseMapDamageView { constructor( - context: IEnemyContext, + context: IEnemyContext, private readonly locator: Readonly, private readonly special: Readonly> ) { @@ -151,7 +154,7 @@ export class RepulseDamageView extends BaseMapDamageView { export class LaserDamageView extends BaseMapDamageView { constructor( - context: IEnemyContext, + context: IEnemyContext, private readonly locator: Readonly, private readonly special: Readonly>, private readonly dir: IDirectionDescriptor[] = DIR4 @@ -171,23 +174,16 @@ export class LaserDamageView extends BaseMapDamageView { }; } - getDamageWithoutCheck( - locator: ITileLocator - ): Readonly | null { - if (locator.x === this.locator.x && locator.y === this.locator.y) { - return null; - } - + getDamageWithoutCheck(): Readonly | null { return this.createInfo(this.special.value, MapDamageType.Layer); } } export class BetweenDamageView extends BaseMapDamageView { - private static readonly DAMAGE = 1; - constructor( - context: IEnemyContext, - private readonly locator: Readonly + context: IEnemyContext, + private readonly locator: Readonly, + private readonly hero: IReadonlyHeroAttribute ) { super(context); } @@ -232,13 +228,14 @@ export class BetweenDamageView extends BaseMapDamageView { return null; } - return this.createInfo(BetweenDamageView.DAMAGE, MapDamageType.Between); + const damage = this.hero.getFinalAttribute('hp'); + return this.createInfo(damage, MapDamageType.Between); } } export class AmbushDamageView extends BaseMapDamageView { constructor( - context: IEnemyContext, + context: IEnemyContext, private readonly locator: Readonly ) { super(context); @@ -256,13 +253,7 @@ export class AmbushDamageView extends BaseMapDamageView { }; } - getDamageWithoutCheck( - locator: ITileLocator - ): Readonly | null { - if (locator.x === this.locator.x && locator.y === this.locator.y) { - return null; - } - + getDamageWithoutCheck(): Readonly | null { return this.createInfo(0, MapDamageType.Unknown, { catch: new Set([this.locator]) }); @@ -273,13 +264,16 @@ export class AmbushDamageView extends BaseMapDamageView { //#region 转换器 -export class MainMapDamageConverter implements IMapDamageConverter { +export class MainMapDamageConverter implements IMapDamageConverter< + IEnemyAttr, + IHeroAttr +> { convert( - enemy: IReadonlyEnemy, - locator: ITileLocator, - context: IEnemyContext + handler: IReadonlyEnemyHandler, + context: IEnemyContext ): IMapDamageView[] { const views: IMapDamageView[] = []; + const { enemy, locator } = handler; const zone = enemy.getSpecial(15); if (zone) { @@ -287,7 +281,7 @@ export class MainMapDamageConverter implements IMapDamageConverter(18); @@ -313,10 +307,7 @@ export class MainMapDamageConverter implements IMapDamageConverter>, - _locator: ITileLocator - ): Readonly { + reduce(info: Iterable>): Readonly { let damage = 0; let type = MapDamageType.Unknown; let maxDamage = -Infinity; diff --git a/packages-user/data-state/src/enemy/special.ts b/packages-user/data-state/src/enemy/special.ts index 71e3833..650dea4 100644 --- a/packages-user/data-state/src/enemy/special.ts +++ b/packages-user/data-state/src/enemy/special.ts @@ -4,7 +4,7 @@ import { IEnemyManager } from '@user/data-base'; import { getHeroStatusOn } from '../legacy/hero'; -import { IEnemyAttributes } from './types'; +import { IEnemyAttr } from './types'; //#region 复合属性值类型 @@ -48,9 +48,7 @@ export interface IHaloValue { * 8. 重生属性:还在脚本编辑的 changingFloor * 9. 阻击 | 捕捉 的每步效果:packages-user/legacy-plugin-data/src/enemy/checkblock.ts */ -export function registerSpecials( - manager: IEnemyManager -): void { +export function registerSpecials(manager: IEnemyManager): void { manager.setAttributeDefaults('guard', new Set()); // 0 - 空 diff --git a/packages-user/data-state/src/enemy/types.ts b/packages-user/data-state/src/enemy/types.ts index 4e2ddda..e008ebe 100644 --- a/packages-user/data-state/src/enemy/types.ts +++ b/packages-user/data-state/src/enemy/types.ts @@ -1,6 +1,6 @@ import { IEnemyView } from '@user/data-base'; -export interface IEnemyAttributes { +export interface IEnemyAttr { /** 怪物生命值 */ hp: number; /** 怪物攻击力 */ @@ -14,7 +14,7 @@ export interface IEnemyAttributes { /** 怪物加点量 */ point: number; /** 支援来源怪物视图列表 */ - guard: Set>; + guard: Set>; } export const enum MapDamageType { diff --git a/packages-user/data-state/src/hero/types.ts b/packages-user/data-state/src/hero/types.ts index bc86406..98147da 100644 --- a/packages-user/data-state/src/hero/types.ts +++ b/packages-user/data-state/src/hero/types.ts @@ -1,6 +1,6 @@ //#region 勇士属性 -export interface IHeroAttributeObject { +export interface IHeroAttr { /** 勇士名称 */ name: string; /** 勇士生命值 */ diff --git a/packages-user/data-state/src/legacy/move.ts b/packages-user/data-state/src/legacy/move.ts index 0f6d89d..51e4509 100644 --- a/packages-user/data-state/src/legacy/move.ts +++ b/packages-user/data-state/src/legacy/move.ts @@ -288,7 +288,7 @@ export class HeroMover extends ObjectMoverBase { protected async onMoveStart(controller: IMoveController): Promise { this.beforeMoveSpeed = this.moveSpeed; if (!core.isReplaying() || core.status.replay.speed <= 12) { - state.hero.startMove(); + state.hero.mover.startMove(); } // 这里要检查前面那一格能不能走,不能走则不触发平滑视角,以避免撞墙上视角卡住 if (!this.ignoreTerrain) { @@ -310,7 +310,7 @@ export class HeroMover extends ObjectMoverBase { protected async onMoveEnd(controller: IMoveController): Promise { this.moveSpeed = this.beforeMoveSpeed; this.onSetMoveSpeed(this.moveSpeed, controller); - await state.hero.endMove(); + await state.hero.mover.endMove(); // viewport.sync('endMove'); core.clearContinueAutomaticRoute(); core.stopAutomaticRoute(); @@ -442,19 +442,22 @@ export class HeroMover extends ObjectMoverBase { const replaying = core.isReplaying(); if (replaying) { if (core.status.replay.speed > 12) { - await state.hero.endMove(); + await state.hero.mover.endMove(); await sleep(speed); - state.hero.setPosition(x, y); + state.hero.mover.setPosition(x, y); } else { - state.hero.startMove(); - await state.hero.move( + state.hero.mover.startMove(); + await state.hero.mover.move( fromDirectionString(moveDir), this.moveSpeed / core.status.replay.speed ); } } else { - state.hero.startMove(); - await state.hero.move(fromDirectionString(moveDir), this.moveSpeed); + state.hero.mover.startMove(); + await state.hero.mover.move( + fromDirectionString(moveDir), + this.moveSpeed + ); } } diff --git a/packages-user/data-state/src/shared.ts b/packages-user/data-state/src/shared.ts index f08416d..33d5ebb 100644 --- a/packages-user/data-state/src/shared.ts +++ b/packages-user/data-state/src/shared.ts @@ -1,4 +1,4 @@ -import { IHeroAttributeObject } from './hero'; +import { IHeroAttr } from './hero'; /** 每个地图的默认宽度 */ export const TILE_WIDTH = 13; @@ -11,7 +11,7 @@ export const TILE_HEIGHT = 13; export const DEFAULT_HERO_IMAGE: ImageIds = 'hero.png'; /** 勇士的初始属性,数值填多少目前都无所谓,因为最终会从旧样板读取,但是必须得填 */ -export const HERO_DEFAULT_ATTRIBUTE: IHeroAttributeObject = { +export const HERO_DEFAULT_ATTRIBUTE: IHeroAttr = { name: '', hp: 1, hpmax: 0, diff --git a/packages-user/data-state/src/types.ts b/packages-user/data-state/src/types.ts index 5f01f1e..92f4328 100644 --- a/packages-user/data-state/src/types.ts +++ b/packages-user/data-state/src/types.ts @@ -6,12 +6,12 @@ import { IHeroFollower, IHeroState } from '@user/data-base'; -import { IEnemyAttributes } from './enemy/types'; -import { IHeroAttributeObject } from './hero'; +import { IEnemyAttr } from './enemy/types'; +import { IHeroAttr } from './hero'; export interface IGameDataState { /** 怪物管理器 */ - readonly enemyManager: IEnemyManager; + readonly enemyManager: IEnemyManager; } export interface IStateSaveData { @@ -23,7 +23,7 @@ export interface ICoreState { /** 地图状态 */ readonly layer: ILayerState; /** 勇士状态 */ - readonly hero: IHeroState; + readonly hero: IHeroState; /** 朝向绑定 */ readonly roleFace: IRoleFaceBinder; /** id 到图块数字的映射 */ @@ -32,9 +32,9 @@ export interface ICoreState { readonly numberIdMap: Map; /** 怪物管理器 */ - readonly enemyManager: IEnemyManager; + readonly enemyManager: IEnemyManager; /** 怪物上下文 */ - readonly enemyContext: IEnemyContext; + readonly enemyContext: IEnemyContext; /** * 保存状态 diff --git a/packages-user/legacy-plugin-data/src/fallback.ts b/packages-user/legacy-plugin-data/src/fallback.ts index b76177e..69308ae 100644 --- a/packages-user/legacy-plugin-data/src/fallback.ts +++ b/packages-user/legacy-plugin-data/src/fallback.ts @@ -153,13 +153,13 @@ export function initFallback() { core.status.hero.loc[name] = value; if (name === 'direction') { const dir = fromDirectionString(value as Dir); - state.hero.turn(dir); + state.hero.mover.turn(dir); setHeroDirection(value as Dir); } else if (name === 'x') { // 为了防止逆天样板出问题 core.bigmap.posX = value as number; if (!noGather) { - state.hero.setPosition( + state.hero.mover.setPosition( value as number, core.status.hero.loc.y ); @@ -168,7 +168,7 @@ export function initFallback() { // 为了防止逆天样板出问题 core.bigmap.posY = value as number; if (!noGather) { - state.hero.setPosition( + state.hero.mover.setPosition( core.status.hero.loc.x, value as number ); @@ -218,7 +218,7 @@ export function initFallback() { patch2.add('setHeroIcon', function (name: ImageIds) { core.status.hero.image = name; - state.hero.setImage(name); + state.hero.mover.setImage(name); }); patch.add('isMoving', function () { @@ -564,7 +564,7 @@ export function initFallback() { time /= core.status.replay.speed; if (core.status.replay.speed === 24) time = 1; - await state.hero.jumpHero(ex, ey, time); + await state.hero.mover.jumpHero(ex, ey, time); if (!locked) core.unlockControl(); core.setHeroLoc('x', ex); diff --git a/packages/common/src/logger.json b/packages/common/src/logger.json index b988c93..08415b7 100644 --- a/packages/common/src/logger.json +++ b/packages/common/src/logger.json @@ -165,6 +165,7 @@ "107": "Hero status is not bound, damage calculation is unavailable.", "108": "Hero modifier '$1' has already been added once. Ignore repeated add for attribute '$2'.", "109": "Expected a different object reference returned, but got a same reference at modifier '$1' for property '$2'.", + "110": "Expected a hero attribute binding before executing any enemy context calculation.", "1001": "Item-detail extension needs 'floor-binder' and 'floor-damage' extension as dependency." } } diff --git a/packages/common/src/utils/range.ts b/packages/common/src/utils/range.ts index 2ddecb4..f48c35b 100644 --- a/packages/common/src/utils/range.ts +++ b/packages/common/src/utils/range.ts @@ -104,19 +104,20 @@ export class ManhattanRange extends BaseRange { protected estimatePointCount( param: Readonly ): number { - const radius = Math.max(0, param.radius); + const radius = Math.abs(param.radius); return 1 + 2 * radius * (radius + 1); } *iterateLoc(param: Readonly): Iterable { const { width, height } = this.host; - for (let dy = -param.radius; dy <= param.radius; dy++) { + const radius = Math.abs(param.radius); + for (let dy = -radius; dy <= radius; dy++) { const y = param.cy + dy; if (y < 0 || y >= height) { continue; } - const span = param.radius - Math.abs(dy); + const span = radius - Math.abs(dy); const startX = Math.max(0, param.cx - span); const endX = Math.min(width - 1, param.cx + span); for (let x = startX; x <= endX; x++) { @@ -130,10 +131,10 @@ export class ManhattanRange extends BaseRange { y: number, param: Readonly ): boolean { - return ( - this.inBound(x, y) && - Math.abs(x - param.cx) + Math.abs(y - param.cy) <= param.radius - ); + const radius = Math.abs(param.radius); + const dx = Math.abs(x - param.cx); + const dy = Math.abs(y - param.cy); + return this.inBound(x, y) && dx + dy <= radius; } } @@ -141,6 +142,7 @@ export class RayRange extends BaseRange { protected estimatePointCount(param: Readonly): number { const { width, height } = this.host; // 考虑到这种范围的 `inRange` 判断要更加耗时,因此将返回值略微降低,更倾向于使用 `scan` 方式 + // 正常情况下应该 / 2 return ((width + height) * param.dir.length) / 3; }