refactor: 怪物系统与勇士属性系统联动

This commit is contained in:
unanmed 2026-04-17 15:34:44 +08:00
parent 92ff489811
commit eaa725553b
23 changed files with 473 additions and 381 deletions

View File

@ -16,7 +16,7 @@ export async function createMainExtension() {
mainMapRenderer.useAsset(materials.trackedAsset); mainMapRenderer.useAsset(materials.trackedAsset);
const layer = state.layer.getLayerByAlias('event'); const layer = state.layer.getLayerByAlias('event');
if (layer) { if (layer) {
mainMapExtension.addHero(state.hero, layer); mainMapExtension.addHero(state.hero.mover, layer);
mainMapExtension.addDoor(layer); mainMapExtension.addDoor(layer);
} }
mainMapExtension.addText(); mainMapExtension.addText();

View File

@ -8,17 +8,20 @@ import {
IEnemyCommonQueryEffect, IEnemyCommonQueryEffect,
IEnemyContext, IEnemyContext,
IEnemyFinalEffect, IEnemyFinalEffect,
IEnemyHandler,
IEnemySpecialModifier, IEnemySpecialModifier,
IEnemySpecialQueryEffect, IEnemySpecialQueryEffect,
IEnemyView, IEnemyView,
IMapDamage, IMapDamage,
IReadonlyEnemy, IReadonlyEnemy,
IReadonlyEnemyHandler,
ISpecial ISpecial
} from './types'; } from './types';
import { EnemyView } from './enemy'; import { EnemyView } from './enemy';
import { MapLocIndexer } from './utils'; import { MapLocIndexer } from './utils';
import { IReadonlyHeroAttribute } from '../hero';
export class EnemyContext<TAttr> implements IEnemyContext<TAttr> { export class EnemyContext<TAttr, THero> implements IEnemyContext<TAttr, THero> {
private readonly enemyViewMap: Map<number, EnemyView<TAttr>> = new Map(); private readonly enemyViewMap: Map<number, EnemyView<TAttr>> = new Map();
private readonly enemyMap: Map<number, IEnemy<TAttr>> = new Map(); private readonly enemyMap: Map<number, IEnemy<TAttr>> = new Map();
private readonly locatorViewMap: Map<IEnemyView<TAttr>, number> = new Map(); private readonly locatorViewMap: Map<IEnemyView<TAttr>, number> = new Map();
@ -28,23 +31,26 @@ export class EnemyContext<TAttr> implements IEnemyContext<TAttr> {
EnemyView<TAttr> EnemyView<TAttr>
> = new Map(); > = new Map();
private readonly auraConverter: Set<IAuraConverter<TAttr>> = new Set(); private readonly auraConverter: Set<IAuraConverter<TAttr, THero>> =
private readonly converterStatus: Map<IAuraConverter<TAttr>, boolean> = new Set();
new Map(); private readonly converterStatus: Map<
IAuraConverter<TAttr, THero>,
boolean
> = new Map();
private readonly convertedAura: Map<ISpecial<any>, IAuraView<TAttr>> = private readonly convertedAura: Map<ISpecial<any>, IAuraView<TAttr>> =
new Map(); new Map();
private readonly commonQueryMap: Map< private readonly commonQueryMap: Map<
number, number,
IEnemyCommonQueryEffect<TAttr>[] IEnemyCommonQueryEffect<TAttr, THero>[]
> = new Map(); > = new Map();
private readonly specialQueryEffects: Map< private readonly specialQueryEffects: Map<
number, number,
IEnemySpecialQueryEffect<TAttr>[] IEnemySpecialQueryEffect<TAttr, THero>[]
> = new Map(); > = new Map();
private readonly finalEffects: IEnemyFinalEffect<TAttr>[] = []; private readonly finalEffects: IEnemyFinalEffect<TAttr, THero>[] = [];
private readonly globalAuraList: Set<IAuraView<TAttr>> = new Set(); private readonly globalAuraList: Set<IAuraView<TAttr>> = new Set();
private readonly sortedAura: Map<number, Set<IAuraView<TAttr>>> = new Map(); private readonly sortedAura: Map<number, Set<IAuraView<TAttr>>> = new Map();
@ -52,10 +58,17 @@ export class EnemyContext<TAttr> implements IEnemyContext<TAttr> {
private readonly requestedCommonContext: Set<IEnemyView<TAttr>> = new Set(); private readonly requestedCommonContext: Set<IEnemyView<TAttr>> = new Set();
private readonly dirtyEnemy: Set<IEnemyView<TAttr>> = new Set(); private readonly dirtyEnemy: Set<IEnemyView<TAttr>> = new Set();
private mapDamage: IMapDamage<TAttr> | null = null; /** 当前绑定的勇士属性对象 */
private damageSystem: IDamageSystem<TAttr, unknown> | null = null; private bindedHero: IReadonlyHeroAttribute<THero> | null = null;
/** 地图伤害对象 */
private mapDamage: IMapDamage<TAttr, THero> | null = null;
/** 伤害系统对象 */
private damageSystem: IDamageSystem<TAttr, THero> | null = null;
/** 索引工具 */
readonly indexer: MapLocIndexer = new MapLocIndexer(); readonly indexer: MapLocIndexer = new MapLocIndexer();
/** 当前是否需要全量刷新 */
private needUpdate: boolean = true; private needUpdate: boolean = true;
built: boolean = false; built: boolean = false;
@ -70,20 +83,20 @@ export class EnemyContext<TAttr> implements IEnemyContext<TAttr> {
this.needUpdate = true; this.needUpdate = true;
} }
registerAuraConverter(converter: IAuraConverter<TAttr>): void { registerAuraConverter(converter: IAuraConverter<TAttr, THero>): void {
this.auraConverter.add(converter); this.auraConverter.add(converter);
this.converterStatus.set(converter, true); this.converterStatus.set(converter, true);
this.needUpdate = true; this.needUpdate = true;
} }
unregisterAuraConverter(converter: IAuraConverter<TAttr>): void { unregisterAuraConverter(converter: IAuraConverter<TAttr, THero>): void {
this.auraConverter.delete(converter); this.auraConverter.delete(converter);
this.converterStatus.delete(converter); this.converterStatus.delete(converter);
this.needUpdate = true; this.needUpdate = true;
} }
setAuraConverterEnabled( setAuraConverterEnabled(
converter: IAuraConverter<TAttr>, converter: IAuraConverter<TAttr, THero>,
enabled: boolean enabled: boolean
): void { ): void {
if (!this.auraConverter.has(converter)) return; if (!this.auraConverter.has(converter)) return;
@ -93,7 +106,7 @@ export class EnemyContext<TAttr> implements IEnemyContext<TAttr> {
registerCommonQueryEffect( registerCommonQueryEffect(
code: number, code: number,
effect: IEnemyCommonQueryEffect<TAttr> effect: IEnemyCommonQueryEffect<TAttr, THero>
): void { ): void {
const array = this.commonQueryMap.getOrInsert(code, []); const array = this.commonQueryMap.getOrInsert(code, []);
array.push(effect); array.push(effect);
@ -103,7 +116,7 @@ export class EnemyContext<TAttr> implements IEnemyContext<TAttr> {
unregisterCommonQueryEffect( unregisterCommonQueryEffect(
code: number, code: number,
effect: IEnemyCommonQueryEffect<TAttr> effect: IEnemyCommonQueryEffect<TAttr, THero>
): void { ): void {
const array = this.commonQueryMap.get(code); const array = this.commonQueryMap.get(code);
if (!array) return; if (!array) return;
@ -113,14 +126,16 @@ export class EnemyContext<TAttr> implements IEnemyContext<TAttr> {
this.needUpdate = true; this.needUpdate = true;
} }
registerSpecialQueryEffect(effect: IEnemySpecialQueryEffect<TAttr>): void { registerSpecialQueryEffect(
effect: IEnemySpecialQueryEffect<TAttr, THero>
): void {
const list = this.specialQueryEffects.getOrInsert(effect.priority, []); const list = this.specialQueryEffects.getOrInsert(effect.priority, []);
list.push(effect); list.push(effect);
this.needUpdate = true; this.needUpdate = true;
} }
unregisterSpecialQueryEffect( unregisterSpecialQueryEffect(
effect: IEnemySpecialQueryEffect<TAttr> effect: IEnemySpecialQueryEffect<TAttr, THero>
): void { ): void {
const list = this.specialQueryEffects.get(effect.priority); const list = this.specialQueryEffects.get(effect.priority);
if (!list) return; if (!list) return;
@ -134,13 +149,13 @@ export class EnemyContext<TAttr> implements IEnemyContext<TAttr> {
this.needUpdate = true; this.needUpdate = true;
} }
registerFinalEffect(effect: IEnemyFinalEffect<TAttr>): void { registerFinalEffect(effect: IEnemyFinalEffect<TAttr, THero>): void {
this.finalEffects.push(effect); this.finalEffects.push(effect);
this.finalEffects.sort((a, b) => b.priority - a.priority); this.finalEffects.sort((a, b) => b.priority - a.priority);
this.needUpdate = true; this.needUpdate = true;
} }
unregisterFinalEffect(effect: IEnemyFinalEffect<TAttr>): void { unregisterFinalEffect(effect: IEnemyFinalEffect<TAttr, THero>): void {
const index = this.finalEffects.indexOf(effect); const index = this.finalEffects.indexOf(effect);
if (index !== -1) { if (index !== -1) {
this.finalEffects.splice(index, 1); this.finalEffects.splice(index, 1);
@ -148,6 +163,29 @@ export class EnemyContext<TAttr> implements IEnemyContext<TAttr> {
this.needUpdate = true; 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<TAttr>,
locator: ITileLocator
): IEnemyHandler<TAttr, THero> {
return { enemy, locator, hero: this.bindedHero! };
}
getEnemyLocator(enemy: IEnemy<TAttr>): Readonly<ITileLocator> | null { getEnemyLocator(enemy: IEnemy<TAttr>): Readonly<ITileLocator> | null {
const index = this.locatorEnemyMap.get(enemy); const index = this.locatorEnemyMap.get(enemy);
if (index === undefined) return null; if (index === undefined) return null;
@ -177,7 +215,7 @@ export class EnemyContext<TAttr> implements IEnemyContext<TAttr> {
} }
/** /**
* *
* @param index * @param index
*/ */
private deleteEnemyAt(index: number) { private deleteEnemyAt(index: number) {
@ -231,14 +269,14 @@ export class EnemyContext<TAttr> implements IEnemyContext<TAttr> {
} }
/** /**
* *
* @param range * @param range
* @param param * @param param
*/ */
private *internalScanRange<T>( private *internalScanRange<T>(
range: IRange<T>, range: IRange<T>,
param: T param: T
): Iterable<EnemyView<TAttr>> { ): Iterable<[ITileLocator, EnemyView<TAttr>]> {
range.bindHost(this); range.bindHost(this);
const keys = new Set(this.enemyViewMap.keys()); const keys = new Set(this.enemyViewMap.keys());
const matched = range.autoDetect(keys, param); const matched = range.autoDetect(keys, param);
@ -246,12 +284,16 @@ export class EnemyContext<TAttr> implements IEnemyContext<TAttr> {
for (const index of matched) { for (const index of matched) {
const view = viewMap.get(index); const view = viewMap.get(index);
if (view) { if (view) {
yield view; const locator = this.indexer.indexToLocator(index);
yield [locator, view];
} }
} }
} }
scanRange<T>(range: IRange<T>, param: T): Iterable<IEnemyView<TAttr>> { scanRange<T>(
range: IRange<T>,
param: T
): Iterable<[ITileLocator, IEnemyView<TAttr>]> {
return this.internalScanRange(range, param); return this.internalScanRange(range, param);
} }
@ -272,44 +314,42 @@ export class EnemyContext<TAttr> implements IEnemyContext<TAttr> {
this.needUpdate = true; this.needUpdate = true;
} }
attachMapDamage(damage: IMapDamage<TAttr> | null): void { attachMapDamage(damage: IMapDamage<TAttr, THero> | null): void {
this.mapDamage = damage; this.mapDamage = damage;
if (damage) { if (damage) {
damage.refreshAll(); damage.refreshAll();
} }
} }
getMapDamage(): IMapDamage<TAttr> | null { getMapDamage(): IMapDamage<TAttr, THero> | null {
return this.mapDamage; return this.mapDamage;
} }
attachDamageSystem(system: IDamageSystem<TAttr, unknown> | null): void { attachDamageSystem(system: IDamageSystem<TAttr, unknown> | null): void {
this.damageSystem = system; this.damageSystem = system;
if (system) { if (system) {
system.markAllDirty(); system.bindHeroStatus(this.bindedHero);
} }
} }
getDamageSystem<THero>(): IDamageSystem<TAttr, THero> | null { getDamageSystem(): IDamageSystem<TAttr, THero> | null {
return this.damageSystem as IDamageSystem<TAttr, THero> | null; return this.damageSystem;
} }
/** /**
* *
* @param special * @param special
* @param enemy * @param enemy
* @param locator * @param locator
*/ */
private convertSpecial( private convertSpecial(
special: ISpecial<any>, special: ISpecial<any>,
enemy: IReadonlyEnemy<TAttr>, handler: IReadonlyEnemyHandler<TAttr, THero>
locator: ITileLocator
): IEnemyAuraView<TAttr, any, any> | null { ): IEnemyAuraView<TAttr, any, any> | null {
let matched: IAuraConverter<TAttr> | null = null; let matched: IAuraConverter<TAttr, THero> | null = null;
for (const converter of this.auraConverter) { for (const converter of this.auraConverter) {
if (!this.converterStatus.get(converter)) continue; if (!this.converterStatus.get(converter)) continue;
if (converter.shouldConvert(special, enemy, locator)) { if (converter.shouldConvert(special, handler)) {
if (matched) { if (matched) {
logger.warn(97, special.code.toString()); logger.warn(97, special.code.toString());
return null; return null;
@ -319,11 +359,11 @@ export class EnemyContext<TAttr> implements IEnemyContext<TAttr> {
} }
if (!matched) return null; if (!matched) return null;
return matched.convert(special, enemy, locator, this); return matched.convert(special, handler, this);
} }
/** /**
* *
* @param aura * @param aura
*/ */
private insertIntoSortedAura(aura: IAuraView<TAttr>): void { private insertIntoSortedAura(aura: IAuraView<TAttr>): void {
@ -335,7 +375,7 @@ export class EnemyContext<TAttr> implements IEnemyContext<TAttr> {
} }
/** /**
* *
* @param aura * @param aura
*/ */
private removeFromSortedAura(aura: IAuraView<TAttr>): void { private removeFromSortedAura(aura: IAuraView<TAttr>): void {
@ -349,7 +389,7 @@ export class EnemyContext<TAttr> implements IEnemyContext<TAttr> {
} }
/** /**
* *
* @param modifier * @param modifier
* @param enemy * @param enemy
* @param locator * @param locator
@ -357,14 +397,13 @@ export class EnemyContext<TAttr> implements IEnemyContext<TAttr> {
*/ */
private processSpecialModifier( private processSpecialModifier(
modifier: IEnemySpecialModifier<TAttr>, modifier: IEnemySpecialModifier<TAttr>,
enemy: IEnemy<TAttr>, handler: IEnemyHandler<TAttr, THero>,
locator: ITileLocator,
currentPriority: number currentPriority: number
): Set<IAuraView<TAttr>> { ): Set<IAuraView<TAttr>> {
const toAdd = modifier.add(enemy, locator); const enemy = handler.enemy;
const toDelete = modifier.delete(enemy, locator);
const affectedAuras = new Set<IAuraView<TAttr>>(); const affectedAuras = new Set<IAuraView<TAttr>>();
const toAdd = modifier.add(handler);
const toDelete = modifier.delete(handler);
if (toAdd.length > 0 && toDelete.length > 0) { if (toAdd.length > 0 && toDelete.length > 0) {
logger.warn(100); logger.warn(100);
@ -372,9 +411,9 @@ export class EnemyContext<TAttr> implements IEnemyContext<TAttr> {
} }
for (const adding of toAdd) { for (const adding of toAdd) {
const aura = this.convertSpecial(adding, enemy, locator); const aura = this.convertSpecial(adding, handler);
if (aura) { if (aura) {
// 新生成的光环只能影响之后的阶段,不能反过来影响当前优先级链 // 新生成的光环只能影响之后的阶段,不能反过来影响当前优先级链
if (import.meta.env.DEV && aura.priority > currentPriority) { if (import.meta.env.DEV && aura.priority > currentPriority) {
logger.warn( logger.warn(
99, 99,
@ -410,7 +449,7 @@ export class EnemyContext<TAttr> implements IEnemyContext<TAttr> {
} }
for (const special of enemy.iterateSpecials()) { for (const special of enemy.iterateSpecials()) {
const success = modifier.modify(enemy, special, locator); const success = modifier.modify(handler, special);
if (!success) continue; if (!success) continue;
const aura = this.convertedAura.get(special); const aura = this.convertedAura.get(special);
if (!aura) continue; if (!aura) continue;
@ -429,12 +468,12 @@ export class EnemyContext<TAttr> implements IEnemyContext<TAttr> {
} }
/** /**
* *
* @param effect * @param effect
* @param currentPriority * @param currentPriority
*/ */
private processSpecialQuery( private processSpecialQuery(
effect: IEnemySpecialQueryEffect<TAttr>, effect: IEnemySpecialQueryEffect<TAttr, THero>,
currentPriority: number currentPriority: number
): void { ): void {
const modifier = effect.for(this); const modifier = effect.for(this);
@ -442,13 +481,13 @@ export class EnemyContext<TAttr> implements IEnemyContext<TAttr> {
for (const [index, view] of this.enemyViewMap) { for (const [index, view] of this.enemyViewMap) {
const locator = this.indexer.indexToLocator(index); const locator = this.indexer.indexToLocator(index);
const enemy = view.getComputingEnemy(); 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( const affectedAuras = this.processSpecialModifier(
modifier, modifier,
enemy, handler,
locator,
currentPriority currentPriority
); );
@ -461,7 +500,7 @@ export class EnemyContext<TAttr> implements IEnemyContext<TAttr> {
} }
/** /**
* *
* @param aura * @param aura
* @param currentPriority * @param currentPriority
*/ */
@ -470,30 +509,22 @@ export class EnemyContext<TAttr> implements IEnemyContext<TAttr> {
currentPriority: number currentPriority: number
): void { ): void {
const param = aura.getRangeParam(); const param = aura.getRangeParam();
const iter = this.internalScanRange(aura.range, param);
for (const enemyView of this.internalScanRange(aura.range, param)) { for (const [locator, enemyView] of iter) {
const locator = this.getEnemyLocatorByView(enemyView);
if (!locator) continue;
const enemy = enemyView.getComputingEnemy(); const enemy = enemyView.getComputingEnemy();
const base = enemyView.getBaseEnemy(); 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; if (!modifier) continue;
this.processSpecialModifier( this.processSpecialModifier(modifier, handler, currentPriority);
modifier,
enemy,
locator,
currentPriority
);
this.needTotallyRefresh.add(enemyView); this.needTotallyRefresh.add(enemyView);
} }
} }
/** /**
* *
*/ */
private buildupSpecials(): void { private buildupSpecials(): void {
for (const aura of this.globalAuraList) { for (const aura of this.globalAuraList) {
@ -503,9 +534,10 @@ export class EnemyContext<TAttr> implements IEnemyContext<TAttr> {
for (const [index, view] of this.enemyViewMap) { for (const [index, view] of this.enemyViewMap) {
const enemy = view.getComputingEnemy(); const enemy = view.getComputingEnemy();
const locator = this.indexer.indexToLocator(index); const locator = this.indexer.indexToLocator(index);
const handler = this.createHandler(enemy, locator);
for (const special of enemy.iterateSpecials()) { for (const special of enemy.iterateSpecials()) {
const aura = this.convertSpecial(special, enemy, locator); const aura = this.convertSpecial(special, handler);
if (!aura) continue; if (!aura) continue;
this.convertedAura.set(special, aura); this.convertedAura.set(special, aura);
this.insertIntoSortedAura(aura); this.insertIntoSortedAura(aura);
@ -554,7 +586,7 @@ export class EnemyContext<TAttr> implements IEnemyContext<TAttr> {
} }
/** /**
* *
*/ */
private buildupBase(): void { private buildupBase(): void {
const priorities = [...this.sortedAura.keys()].sort((a, b) => b - a); const priorities = [...this.sortedAura.keys()].sort((a, b) => b - a);
@ -563,23 +595,25 @@ export class EnemyContext<TAttr> implements IEnemyContext<TAttr> {
if (!auras) continue; if (!auras) continue;
for (const aura of auras) { for (const aura of auras) {
const param = aura.getRangeParam(); 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 enemy = view.getComputingEnemy();
const base = view.getBaseEnemy(); const base = view.getBaseEnemy();
const locator = this.getEnemyLocatorByView(view)!; const handler = this.createHandler(enemy, locator);
aura.apply(enemy, base, locator); aura.apply(handler, base);
} }
} }
} }
} }
/** /**
* *
*/ */
private buildupQuery(): void { private buildupQuery(): void {
for (const [index, view] of this.enemyViewMap) { for (const [index, view] of this.enemyViewMap) {
const enemy = view.getComputingEnemy(); const enemy = view.getComputingEnemy();
const locator = this.indexer.indexToLocator(index); const locator = this.indexer.indexToLocator(index);
const handler = this.createHandler(enemy, locator);
let queried = false; let queried = false;
const query = () => { const query = () => {
queried = true; queried = true;
@ -589,7 +623,7 @@ export class EnemyContext<TAttr> implements IEnemyContext<TAttr> {
const effects = this.commonQueryMap.get(special.code); const effects = this.commonQueryMap.get(special.code);
if (!effects) continue; if (!effects) continue;
for (const effect of effects) { for (const effect of effects) {
effect.apply(enemy, special, query, locator); effect.apply(handler, special, query);
} }
} }
if (queried) { if (queried) {
@ -599,20 +633,25 @@ export class EnemyContext<TAttr> implements IEnemyContext<TAttr> {
} }
/** /**
* *
*/ */
private buildupFinal(): void { private buildupFinal(): void {
for (const [index, view] of this.enemyViewMap) { for (const [index, view] of this.enemyViewMap) {
const enemy = view.getComputingEnemy(); const enemy = view.getComputingEnemy();
const locator = this.indexer.indexToLocator(index); const locator = this.indexer.indexToLocator(index);
const handler = this.createHandler(enemy, locator);
for (const effect of this.finalEffects) { for (const effect of this.finalEffects) {
effect.apply(enemy, locator); effect.apply(handler);
} }
} }
} }
buildup(): void { buildup(): void {
if (!this.needUpdate) return; if (!this.needUpdate) return;
if (!this.bindedHero) {
logger.warn(110);
return;
}
this.needUpdate = false; this.needUpdate = false;
this.sortedAura.clear(); this.sortedAura.clear();
this.convertedAura.clear(); this.convertedAura.clear();
@ -650,18 +689,18 @@ export class EnemyContext<TAttr> implements IEnemyContext<TAttr> {
} }
/** /**
* *
* @param modifier * @param modifier
* @param enemy * @param enemy
* @param locator * @param locator
*/ */
private refreshSpecialModifier( private refreshSpecialModifier(
modifier: IEnemySpecialModifier<TAttr>, modifier: IEnemySpecialModifier<TAttr>,
enemy: IEnemy<TAttr>, handler: IEnemyHandler<TAttr, THero>
locator: ITileLocator
): void { ): void {
const toAdd = modifier.add(enemy, locator); const enemy = handler.enemy;
const toDelete = modifier.delete(enemy, locator); const toAdd = modifier.add(handler);
const toDelete = modifier.delete(handler);
if (toAdd.length > 0 && toDelete.length > 0) { if (toAdd.length > 0 && toDelete.length > 0) {
logger.warn(100); logger.warn(100);
@ -671,7 +710,7 @@ export class EnemyContext<TAttr> implements IEnemyContext<TAttr> {
for (const adding of toAdd) { for (const adding of toAdd) {
enemy.addSpecial(adding); enemy.addSpecial(adding);
if (import.meta.env.DEV) { if (import.meta.env.DEV) {
const aura = this.convertSpecial(adding, enemy, locator); const aura = this.convertSpecial(adding, handler);
if (aura) { if (aura) {
logger.warn(101, adding.code.toString()); logger.warn(101, adding.code.toString());
} }
@ -681,7 +720,7 @@ export class EnemyContext<TAttr> implements IEnemyContext<TAttr> {
for (const deleting of toDelete) { for (const deleting of toDelete) {
enemy.deleteSpecial(deleting); enemy.deleteSpecial(deleting);
if (import.meta.env.DEV) { if (import.meta.env.DEV) {
const aura = this.convertSpecial(deleting, enemy, locator); const aura = this.convertSpecial(deleting, handler);
if (aura) { if (aura) {
logger.warn(101, deleting.code.toString()); logger.warn(101, deleting.code.toString());
} }
@ -689,7 +728,7 @@ export class EnemyContext<TAttr> implements IEnemyContext<TAttr> {
} }
for (const special of enemy.iterateSpecials()) { 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) { if (import.meta.env.DEV && success) {
const aura = this.convertedAura.get(special); const aura = this.convertedAura.get(special);
if (aura) { if (aura) {
@ -700,7 +739,7 @@ export class EnemyContext<TAttr> implements IEnemyContext<TAttr> {
} }
/** /**
* *
* @param view * @param view
*/ */
private refreshEnemy(view: EnemyView<TAttr>): void { private refreshEnemy(view: EnemyView<TAttr>): void {
@ -710,6 +749,7 @@ export class EnemyContext<TAttr> implements IEnemyContext<TAttr> {
view.reset(); view.reset();
const enemy = view.getComputingEnemy(); const enemy = view.getComputingEnemy();
const base = view.getBaseEnemy(); const base = view.getBaseEnemy();
const handler = this.createHandler(enemy, locator);
const specialPriorities = new Set<number>(); const specialPriorities = new Set<number>();
for (const priority of this.sortedAura.keys()) { for (const priority of this.sortedAura.keys()) {
@ -725,30 +765,28 @@ export class EnemyContext<TAttr> implements IEnemyContext<TAttr> {
for (const priority of orderedSpecialPriorities) { for (const priority of orderedSpecialPriorities) {
const auras = this.sortedAura.get(priority); const auras = this.sortedAura.get(priority);
const effects = this.specialQueryEffects.get(priority);
if (auras) { if (auras) {
for (const aura of auras) { for (const aura of auras) {
if (!aura.couldApplySpecial) continue; if (!aura.couldApplySpecial) continue;
const param = aura.getRangeParam(); const param = aura.getRangeParam();
aura.range.bindHost(this); aura.range.bindHost(this);
// 局部刷新只重新判断“这个怪物是否被该光环命中”。 // 局部刷新只重新判断“这个怪物是否被该光环命中”
const inRange = aura.range.inRange( if (!aura.range.inRange(locator.x, locator.y, param)) {
locator.x, continue;
locator.y, }
param const modifier = aura.applySpecial(handler, base);
);
if (!inRange) continue;
const modifier = aura.applySpecial(enemy, base, locator);
if (!modifier) continue; if (!modifier) continue;
this.refreshSpecialModifier(modifier, enemy, locator); this.refreshSpecialModifier(modifier, handler);
} }
} }
const effects = this.specialQueryEffects.get(priority);
if (effects) { if (effects) {
for (const effect of effects) { for (const effect of effects) {
const modifier = effect.for(this); const modifier = effect.for(this);
if (!modifier.shouldQuery(enemy, locator)) continue; if (!modifier.shouldQuery(handler)) continue;
this.refreshSpecialModifier(modifier, enemy, locator); this.refreshSpecialModifier(modifier, handler);
} }
} }
} }
@ -762,10 +800,8 @@ export class EnemyContext<TAttr> implements IEnemyContext<TAttr> {
for (const aura of auras) { for (const aura of auras) {
const param = aura.getRangeParam(); const param = aura.getRangeParam();
aura.range.bindHost(this); aura.range.bindHost(this);
if (!aura.range.inRange(locator.x, locator.y, param)) { if (!aura.range.inRange(locator.x, locator.y, param)) continue;
continue; aura.apply(handler, base);
}
aura.apply(enemy, base, locator);
} }
} }
@ -779,7 +815,7 @@ export class EnemyContext<TAttr> implements IEnemyContext<TAttr> {
const effects = this.commonQueryMap.get(special.code); const effects = this.commonQueryMap.get(special.code);
if (!effects) continue; if (!effects) continue;
for (const effect of effects) { for (const effect of effects) {
effect.apply(enemy, special, query, locator); effect.apply(handler, special, query);
} }
} }
if (queried) { if (queried) {
@ -787,7 +823,7 @@ export class EnemyContext<TAttr> implements IEnemyContext<TAttr> {
} }
for (const effect of this.finalEffects) { for (const effect of this.finalEffects) {
effect.apply(enemy, locator); effect.apply(handler);
} }
this.dirtyEnemy.delete(view); this.dirtyEnemy.delete(view);
@ -846,5 +882,6 @@ export class EnemyContext<TAttr> implements IEnemyContext<TAttr> {
this.commonQueryMap.clear(); this.commonQueryMap.clear();
this.specialQueryEffects.clear(); this.specialQueryEffects.clear();
this.finalEffects.length = 0; this.finalEffects.length = 0;
this.bindedHero = null;
} }
} }

View File

@ -1,4 +1,4 @@
import { logger } from '@motajs/common'; import { ITileLocator, logger } from '@motajs/common';
import { import {
CriticalableHeroStatus, CriticalableHeroStatus,
IDamageCalculator, IDamageCalculator,
@ -6,9 +6,11 @@ import {
IEnemyContext, IEnemyContext,
IEnemyCritical, IEnemyCritical,
IEnemyDamageInfo, IEnemyDamageInfo,
IReadonlyEnemyHandler,
IEnemyView, IEnemyView,
IReadonlyEnemy IReadonlyEnemy
} from './types'; } from './types';
import { IHeroAttribute, IReadonlyHeroAttribute } from '../hero';
interface ICriticalSearchResult { interface ICriticalSearchResult {
/** 此临界点的属性值 */ /** 此临界点的属性值 */
@ -21,12 +23,12 @@ export class DamageSystem<TAttr, THero> implements IDamageSystem<TAttr, THero> {
/** 当前正在使用的计算器 */ /** 当前正在使用的计算器 */
private calculator: IDamageCalculator<TAttr, THero> | null = null; private calculator: IDamageCalculator<TAttr, THero> | null = null;
/** 当前勇士属性 */ /** 当前勇士属性 */
private heroStatus: Readonly<THero> | null = null; private heroStatus: IReadonlyHeroAttribute<THero> | null = null;
/** 怪物伤害缓存 */ /** 怪物伤害缓存 */
private readonly cache: Map<IEnemyView<TAttr>, IEnemyDamageInfo> = private readonly cache: Map<IEnemyView<TAttr>, IEnemyDamageInfo> =
new Map(); new Map();
constructor(readonly context: IEnemyContext<TAttr>) {} constructor(readonly context: IEnemyContext<TAttr, THero>) {}
useCalculator(calculator: IDamageCalculator<TAttr, THero>): void { useCalculator(calculator: IDamageCalculator<TAttr, THero>): void {
this.calculator = calculator; this.calculator = calculator;
@ -37,35 +39,23 @@ export class DamageSystem<TAttr, THero> implements IDamageSystem<TAttr, THero> {
return this.calculator; return this.calculator;
} }
bindHeroStatus(hero: Readonly<THero>): void { bindHeroStatus(hero: IReadonlyHeroAttribute<THero>): void {
this.heroStatus = hero; this.heroStatus = hero;
this.markAllDirty(); this.markAllDirty();
} }
/** /**
* *
* @param enemy
* @param locator
* @param hero
*/ */
private cloneHeroStatus(): THero | null { private createReadonlyHandler(
if (!this.heroStatus) return null;
else return structuredClone(this.heroStatus);
}
/**
*
* @param enemy
* @param attribute
* @param value
* @returns
*/
private calculateDamageWithModified(
enemy: IReadonlyEnemy<TAttr>, enemy: IReadonlyEnemy<TAttr>,
attribute: CriticalableHeroStatus<THero>, locator: ITileLocator,
value: number hero: IReadonlyHeroAttribute<THero>
): IEnemyDamageInfo { ): IReadonlyEnemyHandler<TAttr, THero> {
const hero = this.cloneHeroStatus()!; return { enemy, locator, hero };
// @ts-expect-error 之后会进行修复
hero[attribute] = value;
return this.calculator!.calculate(hero, enemy);
} }
getDamageInfo(enemy: IEnemyView<TAttr>): IEnemyDamageInfo | null { getDamageInfo(enemy: IEnemyView<TAttr>): IEnemyDamageInfo | null {
@ -77,15 +67,20 @@ export class DamageSystem<TAttr, THero> implements IDamageSystem<TAttr, THero> {
logger.warn(106); logger.warn(106);
return null; 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); const cached = this.cache.get(enemy);
if (cached) { if (cached) {
return 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); this.cache.set(enemy, info);
return info; return info;
} }
@ -104,7 +99,7 @@ export class DamageSystem<TAttr, THero> implements IDamageSystem<TAttr, THero> {
*calculateCritical( *calculateCritical(
view: IEnemyView<TAttr>, view: IEnemyView<TAttr>,
attribute: CriticalableHeroStatus<THero>, attribute: CriticalableHeroStatus<THero>,
precision: number precision: number = 12
): Generator<IEnemyCritical, void, void> { ): Generator<IEnemyCritical, void, void> {
if (!this.heroStatus) { if (!this.heroStatus) {
logger.warn(107); logger.warn(107);
@ -115,15 +110,19 @@ export class DamageSystem<TAttr, THero> implements IDamageSystem<TAttr, THero> {
return; return;
} }
const currentInfo = this.getDamageInfo(view); const locator = this.context.getEnemyLocatorByView(view);
if (!currentInfo) return; if (!locator) return;
const enemy = view.getComputedEnemy(); const enemy = view.getComputedEnemy();
const hero = this.cloneHeroStatus()!; const hero = this.heroStatus.getModifiableClone();
const currentValue = hero[attribute] as number; 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( const upperLimit = Math.floor(
this.calculator.getCriticalLimit(hero, enemy, attribute) this.calculator.getCriticalLimit(handler, attribute)
); );
if (currentValue >= upperLimit) return; if (currentValue >= upperLimit) return;
@ -134,7 +133,8 @@ export class DamageSystem<TAttr, THero> implements IDamageSystem<TAttr, THero> {
while (baseValue < upperLimit) { while (baseValue < upperLimit) {
const next = this.findNextCritical( const next = this.findNextCritical(
enemy, handler,
hero,
attribute, attribute,
baseValue, baseValue,
upperLimit, upperLimit,
@ -159,7 +159,8 @@ export class DamageSystem<TAttr, THero> implements IDamageSystem<TAttr, THero> {
/** /**
* *
* @param enemy * @param handler `hero` `hero`
* @param hero `handler` `hero`
* @param attribute * @param attribute
* @param currentValue * @param currentValue
* @param upperLimit * @param upperLimit
@ -167,7 +168,8 @@ export class DamageSystem<TAttr, THero> implements IDamageSystem<TAttr, THero> {
* @param maxIterations * @param maxIterations
*/ */
private findNextCritical( private findNextCritical(
enemy: IReadonlyEnemy<TAttr>, handler: IReadonlyEnemyHandler<TAttr, THero>,
hero: IHeroAttribute<THero>,
attribute: CriticalableHeroStatus<THero>, attribute: CriticalableHeroStatus<THero>,
currentValue: number, currentValue: number,
upperLimit: number, upperLimit: number,
@ -176,36 +178,30 @@ export class DamageSystem<TAttr, THero> implements IDamageSystem<TAttr, THero> {
): ICriticalSearchResult | null { ): ICriticalSearchResult | null {
let left = currentValue; let left = currentValue;
let right = upperLimit; 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; let iter = 0;
while (iter < maxIterations) { while (iter++ < maxIterations) {
const middle = Math.floor((left + right) / 2); const middle = Math.floor((left + right) / 2);
const middleInfo = this.calculateDamageWithModified( hero.setBaseAttribute(attribute, middle as THero[typeof attribute]);
enemy, const middleInfo = this.calculator!.calculate(handler);
attribute,
middle
);
if (middleInfo.damage < referenceDamage) { if (middleInfo.damage < referenceDamage) {
right = middle; right = middle;
rightInfo = middleInfo;
} else { } else {
left = middle; left = middle;
targetInfo = middleInfo;
} }
if (right - left <= 1) break; if (right - left <= 1) break;
iter++;
} }
return { return {
value: right, value: right,
info: rightInfo info: targetInfo
}; };
} }
} }

View File

@ -93,7 +93,7 @@ export class EnemyView<TAttr> implements IEnemyView<TAttr> {
constructor( constructor(
readonly baseEnemy: IEnemy<TAttr>, readonly baseEnemy: IEnemy<TAttr>,
readonly context: IEnemyContext<TAttr> readonly context: IEnemyContext<TAttr, unknown>
) { ) {
this.computedEnemy = baseEnemy.clone(); this.computedEnemy = baseEnemy.clone();
} }

View File

@ -1,6 +1,7 @@
import { logger, ITileLocator } from '@motajs/common'; import { logger, ITileLocator } from '@motajs/common';
import { import {
IEnemyContext, IEnemyContext,
IReadonlyEnemyHandler,
IEnemyView, IEnemyView,
IMapDamage, IMapDamage,
IMapDamageConverter, IMapDamageConverter,
@ -33,9 +34,9 @@ interface IDamageStore<TAttr> {
readonly index: number; readonly index: number;
} }
export class MapDamage<TAttr> implements IMapDamage<TAttr> { export class MapDamage<TAttr, THero> implements IMapDamage<TAttr, THero> {
/** 当前使用的地图伤害转换器 */ /** 当前使用的地图伤害转换器 */
private converter: IMapDamageConverter<TAttr> | null = null; private converter: IMapDamageConverter<TAttr, THero> | null = null;
/** 当前使用的地图伤害合并器 */ /** 当前使用的地图伤害合并器 */
private reducer: IMapDamageReducer | null = null; private reducer: IMapDamageReducer | null = null;
@ -62,15 +63,33 @@ export class MapDamage<TAttr> implements IMapDamage<TAttr> {
/** 坐标索引对象 */ /** 坐标索引对象 */
private readonly indexer: IMapLocIndexer; private readonly indexer: IMapLocIndexer;
constructor(readonly context: IEnemyContext<TAttr>) { constructor(readonly context: IEnemyContext<TAttr, THero>) {
this.indexer = context.indexer; this.indexer = context.indexer;
} }
useConverter(converter: IMapDamageConverter<TAttr>): void { useConverter(converter: IMapDamageConverter<TAttr, THero>): void {
this.converter = converter; this.converter = converter;
this.refreshAll(); this.refreshAll();
} }
/**
*
* @param view
* @param locator
*/
private createReadonlyHandler(
view: IEnemyView<TAttr>,
locator: ITileLocator
): IReadonlyEnemyHandler<TAttr, THero> | null {
const hero = this.context.getBindedHero();
if (!hero) return null;
return {
enemy: view.getComputedEnemy(),
locator,
hero
};
}
useReducer(reducer: IMapDamageReducer): void { useReducer(reducer: IMapDamageReducer): void {
this.reducer = reducer; this.reducer = reducer;
this.reducedCache.clear(); this.reducedCache.clear();
@ -174,6 +193,7 @@ export class MapDamage<TAttr> implements IMapDamage<TAttr> {
const sourced = this.sourcedDamage.get(index); const sourced = this.sourcedDamage.get(index);
if (sourceless) { if (sourceless) {
if (sourced) { if (sourced) {
// 大集合 union 小集合会更快,一般有来源伤害更多,所以 source union sourceless
return sourced.damages.union(sourceless.damages); return sourced.damages.union(sourceless.damages);
} else { } else {
return sourceless.damages; return sourceless.damages;
@ -237,9 +257,11 @@ export class MapDamage<TAttr> implements IMapDamage<TAttr> {
locator: ITileLocator locator: ITileLocator
) { ) {
this.removeEnemyAffecting(view); this.removeEnemyAffecting(view);
const enemy = view.getComputedEnemy(); if (!this.converter) return;
const views = this.converter!.convert(enemy, locator, this.context); const handler = this.createReadonlyHandler(view, locator);
const set = new Set(views); if (!handler) return;
const views = this.converter.convert(handler, this.context);
const set = new Set<IMapDamageView<any>>(views);
if (set.size === 0) return; if (set.size === 0) return;
this.enemyStore.set(view, set); this.enemyStore.set(view, set);
const collection = new Set<number>(); const collection = new Set<number>();
@ -275,9 +297,11 @@ export class MapDamage<TAttr> implements IMapDamage<TAttr> {
*/ */
private refreshEnemy(view: IEnemyView<TAttr>, locator: ITileLocator) { private refreshEnemy(view: IEnemyView<TAttr>, locator: ITileLocator) {
this.removeEnemyAffecting(view); this.removeEnemyAffecting(view);
const enemy = view.getComputedEnemy(); if (!this.converter) return;
const views = this.converter!.convert(enemy, locator, this.context); const handler = this.createReadonlyHandler(view, locator);
const set = new Set(views); if (!handler) return;
const views = this.converter.convert(handler, this.context);
const set = new Set<IMapDamageView<any>>(views);
if (set.size === 0) return; if (set.size === 0) return;
this.enemyStore.set(view, set); this.enemyStore.set(view, set);
set.forEach(viewItem => { set.forEach(viewItem => {

View File

@ -1,4 +1,5 @@
import { IRange, ITileLocator } from '@motajs/common'; import { IRange, ITileLocator } from '@motajs/common';
import { IReadonlyHeroAttribute } from '../hero';
//#region 怪物基础 //#region 怪物基础
@ -246,13 +247,31 @@ export interface IMapLocIndexer extends IMapLocHelper {
setWidth(width: number): void; setWidth(width: number): void;
} }
export interface IEnemyHandler<TAttr, THero> {
/** 怪物属性信息 */
readonly enemy: IEnemy<TAttr>;
/** 怪物定位符 */
readonly locator: ITileLocator;
/** 勇士属性信息 */
readonly hero: IReadonlyHeroAttribute<THero>;
}
export interface IReadonlyEnemyHandler<TAttr, THero> {
/** 怪物属性信息 */
readonly enemy: IReadonlyEnemy<TAttr>;
/** 怪物定位符 */
readonly locator: ITileLocator;
/** 勇士属性信息 */
readonly hero: IReadonlyHeroAttribute<THero>;
}
//#endregion //#endregion
//#region 怪物对象 //#region 怪物对象
export interface IEnemyView<TAttr> { export interface IEnemyView<TAttr> {
/** 怪物视图所属的上下文 */ /** 怪物视图所属的上下文 */
readonly context: IEnemyContext<TAttr>; readonly context: IEnemyContext<TAttr, unknown>;
/** /**
* *
@ -288,31 +307,24 @@ export interface IEnemyView<TAttr> {
export interface IEnemySpecialModifier<TAttr> { export interface IEnemySpecialModifier<TAttr> {
/** /**
* *
* @param enemy * @param handler
* @param locator
*/ */
add(enemy: IReadonlyEnemy<TAttr>, locator: ITileLocator): ISpecial<any>[]; add(handler: IReadonlyEnemyHandler<TAttr, unknown>): ISpecial<any>[];
/** /**
* *
* @param enemy * @param handler
* @param locator
*/ */
delete( delete(handler: IReadonlyEnemyHandler<TAttr, unknown>): ISpecial<any>[];
enemy: IReadonlyEnemy<TAttr>,
locator: ITileLocator
): ISpecial<any>[];
/** /**
* true false * true false
* @param enemy * @param handler
* @param special * @param special
* @param locator
*/ */
modify( modify(
enemy: IReadonlyEnemy<TAttr>, handler: IEnemyHandler<TAttr, unknown>,
special: ISpecial<any>, special: ISpecial<any>
locator: ITileLocator
): boolean; ): boolean;
} }
@ -334,26 +346,22 @@ export interface IAuraView<TAttr, T = any> {
/** /**
* *
* @param enemy * @param handler
* @param baseEnemy * @param baseEnemy
* @param locator
*/ */
apply( apply(
enemy: IEnemy<TAttr>, handler: IEnemyHandler<TAttr, unknown>,
baseEnemy: IReadonlyEnemy<TAttr>, baseEnemy: IReadonlyEnemy<TAttr>
locator: ITileLocator
): void; ): void;
/** /**
* *
* @param enemy * @param handler
* @param baseEnemy * @param baseEnemy
* @param locator
*/ */
applySpecial( applySpecial(
enemy: IReadonlyEnemy<TAttr>, handler: IEnemyHandler<TAttr, unknown>,
baseEnemy: IReadonlyEnemy<TAttr>, baseEnemy: IReadonlyEnemy<TAttr>
locator: ITileLocator
): IEnemySpecialModifier<TAttr> | null; ): IEnemySpecialModifier<TAttr> | null;
} }
@ -366,14 +374,15 @@ export interface IEnemyAuraView<TAttr, R, S> extends IAuraView<TAttr, R> {
readonly locator: ITileLocator; readonly locator: ITileLocator;
} }
export interface IAuraConverter<TAttr> { export interface IAuraConverter<TAttr, THero> {
/** /**
* *
* @param special
* @param handler
*/ */
shouldConvert( shouldConvert(
special: ISpecial<any>, special: ISpecial<any>,
enemy: IReadonlyEnemy<TAttr>, handler: IReadonlyEnemyHandler<TAttr, THero>
locator: ITileLocator
): boolean; ): boolean;
/** /**
@ -381,32 +390,34 @@ export interface IAuraConverter<TAttr> {
*/ */
convert( convert(
special: ISpecial<any>, special: ISpecial<any>,
enemy: IReadonlyEnemy<TAttr>, handler: IReadonlyEnemyHandler<TAttr, THero>,
locator: ITileLocator, context: IEnemyContext<TAttr, THero>
context: IEnemyContext<TAttr>
): IEnemyAuraView<TAttr, any, any>; ): IEnemyAuraView<TAttr, any, any>;
} }
export interface IEnemySpecialQueryModifier< export interface IEnemySpecialQueryModifier<
TAttr TAttr,
THero
> extends IEnemySpecialModifier<TAttr> { > extends IEnemySpecialModifier<TAttr> {
/** /**
* *
*/ */
shouldQuery(enemy: IReadonlyEnemy<TAttr>, locator: ITileLocator): boolean; shouldQuery(handler: IReadonlyEnemyHandler<TAttr, THero>): boolean;
} }
export interface IEnemySpecialQueryEffect<TAttr> { export interface IEnemySpecialQueryEffect<TAttr, THero> {
/** 效果优先级,与光环属性共用 */ /** 效果优先级,与光环属性共用 */
readonly priority: number; readonly priority: number;
/** /**
* *
*/ */
for(ctx: IEnemyContext<TAttr>): IEnemySpecialQueryModifier<TAttr>; for(
ctx: IEnemyContext<TAttr, THero>
): IEnemySpecialQueryModifier<TAttr, THero>;
} }
export interface IEnemyCommonQueryEffect<TAttr> { export interface IEnemyCommonQueryEffect<TAttr, THero> {
/** 优先级,越高的越先执行 */ /** 优先级,越高的越先执行 */
readonly priority: number; readonly priority: number;
@ -414,21 +425,20 @@ export interface IEnemyCommonQueryEffect<TAttr> {
* *
*/ */
apply( apply(
enemy: IEnemy<TAttr>, handler: IEnemyHandler<TAttr, THero>,
special: ISpecial<any>, special: ISpecial<any>,
query: () => IEnemyContext<TAttr>, query: () => IEnemyContext<TAttr, THero>
locator: ITileLocator
): void; ): void;
} }
export interface IEnemyFinalEffect<TAttr> { export interface IEnemyFinalEffect<TAttr, THero> {
/** 效果优先级,越高会越先被执行 */ /** 效果优先级,越高会越先被执行 */
readonly priority: number; readonly priority: number;
/** /**
* *
*/ */
apply(enemy: IEnemy<TAttr>, locator: ITileLocator): void; apply(handler: IEnemyHandler<TAttr, THero>): void;
} }
//#endregion //#endregion
@ -477,14 +487,13 @@ export interface IMapDamageView<T = any> {
): Readonly<IMapDamageInfo> | null; ): Readonly<IMapDamageInfo> | null;
} }
export interface IMapDamageConverter<TAttr> { export interface IMapDamageConverter<TAttr, THero> {
/** /**
* *
*/ */
convert( convert(
enemy: IReadonlyEnemy<TAttr>, handler: IReadonlyEnemyHandler<TAttr, THero>,
locator: ITileLocator, context: IEnemyContext<TAttr, THero>
context: IEnemyContext<TAttr>
): IMapDamageView<any>[]; ): IMapDamageView<any>[];
} }
@ -498,15 +507,15 @@ export interface IMapDamageReducer {
): Readonly<IMapDamageInfo>; ): Readonly<IMapDamageInfo>;
} }
export interface IMapDamage<TAttr> { export interface IMapDamage<TAttr, THero> {
/** 当前绑定的怪物上下文 */ /** 当前绑定的怪物上下文 */
readonly context: IEnemyContext<TAttr>; readonly context: IEnemyContext<TAttr, THero>;
/** /**
* *
* @param converter * @param converter
*/ */
useConverter(converter: IMapDamageConverter<TAttr>): void; useConverter(converter: IMapDamageConverter<TAttr, THero>): void;
/** /**
* *
@ -593,36 +602,30 @@ export interface IEnemyCritical {
} }
export type CriticalableHeroStatus<THero> = keyof { export type CriticalableHeroStatus<THero> = 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<TAttr, THero> { export interface IDamageCalculator<TAttr, THero> {
/** /**
* *
* @param hero * @param handler
* @param enemy
*/ */
calculate( calculate(handler: IReadonlyEnemyHandler<TAttr, THero>): IEnemyDamageInfo;
hero: Readonly<THero>,
enemy: IReadonlyEnemy<TAttr>
): IEnemyDamageInfo;
/** /**
* *
* @param hero * @param handler
* @param enemy
* @param attribute * @param attribute
*/ */
getCriticalLimit( getCriticalLimit(
hero: Readonly<THero>, handler: IReadonlyEnemyHandler<TAttr, THero>,
enemy: IReadonlyEnemy<TAttr>,
attribute: CriticalableHeroStatus<THero> attribute: CriticalableHeroStatus<THero>
): number; ): number;
} }
export interface IDamageSystem<TAttr, THero> { export interface IDamageSystem<TAttr, THero> {
/** 伤害系统所属的上下文 */ /** 伤害系统所属的上下文 */
readonly context: IEnemyContext<TAttr>; readonly context: IEnemyContext<TAttr, THero>;
/** /**
* 使 * 使
@ -639,7 +642,7 @@ export interface IDamageSystem<TAttr, THero> {
* *
* @param hero * @param hero
*/ */
bindHeroStatus(hero: Readonly<THero>): void; bindHeroStatus(hero: IReadonlyHeroAttribute<THero> | null): void;
/** /**
* *
@ -668,12 +671,12 @@ export interface IDamageSystem<TAttr, THero> {
* *
* @param enemy * @param enemy
* @param attribute * @param attribute
* @param precision `12-16` * @param precision `12-16` 12
*/ */
calculateCritical( calculateCritical(
enemy: IEnemyView<TAttr>, enemy: IEnemyView<TAttr>,
attribute: CriticalableHeroStatus<THero>, attribute: CriticalableHeroStatus<THero>,
precision: number precision?: number
): Generator<IEnemyCritical, void, void>; ): Generator<IEnemyCritical, void, void>;
} }
@ -681,7 +684,7 @@ export interface IDamageSystem<TAttr, THero> {
//#region 上下文 //#region 上下文
export interface IEnemyContext<TAttr> { export interface IEnemyContext<TAttr, THero> {
/** 怪物上下文宽度 */ /** 怪物上下文宽度 */
readonly width: number; readonly width: number;
/** 怪物上下文高度 */ /** 怪物上下文高度 */
@ -700,13 +703,13 @@ export interface IEnemyContext<TAttr> {
* *
* @param converter * @param converter
*/ */
registerAuraConverter(converter: IAuraConverter<TAttr>): void; registerAuraConverter(converter: IAuraConverter<TAttr, THero>): void;
/** /**
* *
* @param converter * @param converter
*/ */
unregisterAuraConverter(converter: IAuraConverter<TAttr>): void; unregisterAuraConverter(converter: IAuraConverter<TAttr, THero>): void;
/** /**
* *
@ -714,7 +717,7 @@ export interface IEnemyContext<TAttr> {
* @param enabled * @param enabled
*/ */
setAuraConverterEnabled( setAuraConverterEnabled(
converter: IAuraConverter<TAttr>, converter: IAuraConverter<TAttr, THero>,
enabled: boolean enabled: boolean
): void; ): void;
@ -722,13 +725,17 @@ export interface IEnemyContext<TAttr> {
* *
* @param effect * @param effect
*/ */
registerSpecialQueryEffect(effect: IEnemySpecialQueryEffect<TAttr>): void; registerSpecialQueryEffect(
effect: IEnemySpecialQueryEffect<TAttr, THero>
): void;
/** /**
* *
* @param effect * @param effect
*/ */
unregisterSpecialQueryEffect(effect: IEnemySpecialQueryEffect<TAttr>): void; unregisterSpecialQueryEffect(
effect: IEnemySpecialQueryEffect<TAttr, THero>
): void;
/** /**
* *
@ -737,7 +744,7 @@ export interface IEnemyContext<TAttr> {
*/ */
registerCommonQueryEffect( registerCommonQueryEffect(
code: number, code: number,
effect: IEnemyCommonQueryEffect<TAttr> effect: IEnemyCommonQueryEffect<TAttr, THero>
): void; ): void;
/** /**
@ -747,20 +754,31 @@ export interface IEnemyContext<TAttr> {
*/ */
unregisterCommonQueryEffect( unregisterCommonQueryEffect(
code: number, code: number,
effect: IEnemyCommonQueryEffect<TAttr> effect: IEnemyCommonQueryEffect<TAttr, THero>
): void; ): void;
/** /**
* *
* @param effect * @param effect
*/ */
registerFinalEffect(effect: IEnemyFinalEffect<TAttr>): void; registerFinalEffect(effect: IEnemyFinalEffect<TAttr, THero>): void;
/** /**
* *
* @param effect * @param effect
*/ */
unregisterFinalEffect(effect: IEnemyFinalEffect<TAttr>): void; unregisterFinalEffect(effect: IEnemyFinalEffect<TAttr, THero>): void;
/**
*
* @param hero
*/
bindHero(hero: IReadonlyHeroAttribute<THero> | null): void;
/**
*
*/
getBindedHero(): IReadonlyHeroAttribute<THero> | null;
/** /**
* *
@ -813,7 +831,10 @@ export interface IEnemyContext<TAttr> {
* @param range * @param range
* @param param * @param param
*/ */
scanRange<T>(range: IRange<T>, param: T): Iterable<IEnemyView<TAttr>>; scanRange<T>(
range: IRange<T>,
param: T
): Iterable<[ITileLocator, IEnemyView<TAttr>]>;
/** /**
* *
@ -836,12 +857,12 @@ export interface IEnemyContext<TAttr> {
* *
* @param damage * @param damage
*/ */
attachMapDamage(damage: IMapDamage<TAttr> | null): void; attachMapDamage(damage: IMapDamage<TAttr, THero> | null): void;
/** /**
* *
*/ */
getMapDamage(): IMapDamage<TAttr> | null; getMapDamage(): IMapDamage<TAttr, THero> | null;
/** /**
* *
@ -852,7 +873,7 @@ export interface IEnemyContext<TAttr> {
/** /**
* *
*/ */
getDamageSystem<THero>(): IDamageSystem<TAttr, THero> | null; getDamageSystem(): IDamageSystem<TAttr, THero> | null;
/** /**
* *

View File

@ -149,4 +149,8 @@ export class HeroAttribute<THero> implements IHeroAttribute<THero> {
} }
return cloned; return cloned;
} }
getModifiableClone(): IHeroAttribute<THero> {
return this.clone();
}
} }

View File

@ -32,6 +32,6 @@ export class HeroState<THero> implements IHeroState<THero> {
} }
getIsolatedAttribute(): IHeroAttribute<THero> { getIsolatedAttribute(): IHeroAttribute<THero> {
return this.attribute.clone(); return this.attribute.getModifiableClone();
} }
} }

View File

@ -71,7 +71,12 @@ export interface IReadonlyHeroAttribute<THero> {
* *
* @param cloneModifier * @param cloneModifier
*/ */
clone(cloneModifier?: boolean): IHeroAttribute<THero>; clone(cloneModifier?: boolean): IReadonlyHeroAttribute<THero>;
/**
*
*/
getModifiableClone(): IHeroAttribute<THero>;
} }
export interface IHeroAttribute<THero> extends IReadonlyHeroAttribute<THero> { export interface IHeroAttribute<THero> extends IReadonlyHeroAttribute<THero> {
@ -101,6 +106,12 @@ export interface IHeroAttribute<THero> extends IReadonlyHeroAttribute<THero> {
name: K, name: K,
modifier: IHeroModifier<THero[K], unknown> modifier: IHeroModifier<THero[K], unknown>
): void; ): void;
/**
*
* @param cloneModifier
*/
clone(cloneModifier?: boolean): IHeroAttribute<THero>;
} }
//#endregion //#endregion

View File

@ -13,7 +13,7 @@ import {
HeroState, HeroState,
IHeroState IHeroState
} from '@user/data-base'; } from '@user/data-base';
import { IEnemyAttributes } from './enemy/types'; import { IEnemyAttr } from './enemy/types';
import { import {
CommonAuraConverter, CommonAuraConverter,
EnemyLegacyBridge, EnemyLegacyBridge,
@ -24,7 +24,7 @@ import {
registerSpecials registerSpecials
} from './enemy'; } from './enemy';
import { HERO_DEFAULT_ATTRIBUTE, TILE_HEIGHT, TILE_WIDTH } from './shared'; import { HERO_DEFAULT_ATTRIBUTE, TILE_HEIGHT, TILE_WIDTH } from './shared';
import { IHeroAttributeObject } from './hero'; import { IHeroAttr } from './hero';
export class CoreState implements ICoreState { export class CoreState implements ICoreState {
readonly layer: ILayerState; readonly layer: ILayerState;
@ -32,10 +32,10 @@ export class CoreState implements ICoreState {
readonly idNumberMap: Map<string, number>; readonly idNumberMap: Map<string, number>;
readonly numberIdMap: Map<number, string>; readonly numberIdMap: Map<number, string>;
readonly hero: IHeroState<IHeroAttributeObject>; readonly hero: IHeroState<IHeroAttr>;
readonly enemyManager: IEnemyManager<IEnemyAttributes>; readonly enemyManager: IEnemyManager<IEnemyAttr>;
readonly enemyContext: IEnemyContext<IEnemyAttributes>; readonly enemyContext: IEnemyContext<IEnemyAttr, IHeroAttr>;
constructor() { constructor() {
this.layer = new LayerState(); this.layer = new LayerState();
@ -43,6 +43,15 @@ export class CoreState implements ICoreState {
this.idNumberMap = new Map(); this.idNumberMap = new Map();
this.numberIdMap = 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 怪物初始化 //#region 怪物初始化
// 怪物管理器初始化 // 怪物管理器初始化
@ -56,7 +65,7 @@ export class CoreState implements ICoreState {
registerSpecials(enemyManager); registerSpecials(enemyManager);
this.enemyManager = enemyManager; this.enemyManager = enemyManager;
// 怪物上下文初始化 // 怪物上下文初始化
const enemyContext = new EnemyContext<IEnemyAttributes>(); const enemyContext = new EnemyContext<IEnemyAttr, IHeroAttr>();
const damageSystem = new DamageSystem(enemyContext); const damageSystem = new DamageSystem(enemyContext);
const mapDamage = new MapDamage(enemyContext); const mapDamage = new MapDamage(enemyContext);
mapDamage.useConverter(new MainMapDamageConverter()); mapDamage.useConverter(new MainMapDamageConverter());
@ -67,18 +76,10 @@ export class CoreState implements ICoreState {
enemyContext.registerAuraConverter(new GuardAuraConverter()); enemyContext.registerAuraConverter(new GuardAuraConverter());
enemyContext.registerFinalEffect(new MainEnemyFinalEffect()); enemyContext.registerFinalEffect(new MainEnemyFinalEffect());
enemyContext.resize(TILE_WIDTH, TILE_HEIGHT); enemyContext.resize(TILE_WIDTH, TILE_HEIGHT);
enemyContext.bindHero(heroAttribute);
this.enemyContext = enemyContext; this.enemyContext = enemyContext;
//#endregion //#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 { saveState(): IStateSaveData {

View File

@ -9,16 +9,18 @@ import {
} from '@motajs/common'; } from '@motajs/common';
import { import {
IAuraConverter, IAuraConverter,
IEnemyHandler,
IEnemyAuraView, IEnemyAuraView,
IEnemyContext, IEnemyContext,
IEnemySpecialModifier, IEnemySpecialModifier,
IEnemyView, IEnemyView,
IReadonlyEnemyHandler,
IReadonlyEnemy, IReadonlyEnemy,
ISpecial, ISpecial
IEnemy
} from '@user/data-base'; } from '@user/data-base';
import { IHaloValue } from './special'; import { IHaloValue } from './special';
import { IEnemyAttributes } from './types'; import { IEnemyAttr } from './types';
import { IHeroAttr } from '../hero';
const FULL_RANGE = new FullRange(); const FULL_RANGE = new FullRange();
const RECT_RANGE = new RectRange(); const RECT_RANGE = new RectRange();
@ -26,22 +28,24 @@ const MANHATTAN_RANGE = new ManhattanRange();
//#region 25-光环 //#region 25-光环
export class CommonAuraConverter implements IAuraConverter<IEnemyAttributes> { export class CommonAuraConverter implements IAuraConverter<
IEnemyAttr,
IHeroAttr
> {
shouldConvert(special: ISpecial<any>): boolean { shouldConvert(special: ISpecial<any>): boolean {
return special.code === 25; return special.code === 25;
} }
convert( convert(
special: ISpecial<IHaloValue>, special: ISpecial<IHaloValue>,
enemy: IReadonlyEnemy<IEnemyAttributes>, handler: IReadonlyEnemyHandler<IEnemyAttr, IHeroAttr>
locator: ITileLocator
): CommonAura { ): CommonAura {
return new CommonAura(enemy, special, locator); return new CommonAura(handler.enemy, special, handler.locator);
} }
} }
export class CommonAura implements IEnemyAuraView< export class CommonAura implements IEnemyAuraView<
IEnemyAttributes, IEnemyAttr,
IRectRangeParam | IManhattanRangeParam | void, IRectRangeParam | IManhattanRangeParam | void,
IHaloValue IHaloValue
> { > {
@ -51,7 +55,7 @@ export class CommonAura implements IEnemyAuraView<
readonly range: IRange<IRectRangeParam | IManhattanRangeParam | void>; readonly range: IRange<IRectRangeParam | IManhattanRangeParam | void>;
constructor( constructor(
readonly enemy: IReadonlyEnemy<IEnemyAttributes>, readonly enemy: IReadonlyEnemy<IEnemyAttr>,
readonly special: ISpecial<IHaloValue>, readonly special: ISpecial<IHaloValue>,
readonly locator: ITileLocator readonly locator: ITileLocator
) { ) {
@ -89,9 +93,10 @@ export class CommonAura implements IEnemyAuraView<
} }
apply( apply(
enemy: IEnemy<IEnemyAttributes>, handler: IEnemyHandler<IEnemyAttr, unknown>,
baseEnemy: IReadonlyEnemy<IEnemyAttributes> baseEnemy: IReadonlyEnemy<IEnemyAttr>
): void { ): void {
const { enemy } = handler;
const { hpBuff, atkBuff, defBuff } = this.special.value; const { hpBuff, atkBuff, defBuff } = this.special.value;
if (hpBuff !== 0) { if (hpBuff !== 0) {
@ -110,7 +115,7 @@ export class CommonAura implements IEnemyAuraView<
} }
} }
applySpecial(): IEnemySpecialModifier<IEnemyAttributes> | null { applySpecial(): IEnemySpecialModifier<IEnemyAttr> | null {
return null; return null;
} }
} }
@ -119,23 +124,25 @@ export class CommonAura implements IEnemyAuraView<
//#region 26-支援 //#region 26-支援
export class GuardAuraConverter implements IAuraConverter<IEnemyAttributes> { export class GuardAuraConverter implements IAuraConverter<
IEnemyAttr,
IHeroAttr
> {
shouldConvert(special: ISpecial<any>): boolean { shouldConvert(special: ISpecial<any>): boolean {
return special.code === 26; return special.code === 26;
} }
convert( convert(
special: ISpecial<void>, special: ISpecial<void>,
enemy: IReadonlyEnemy<IEnemyAttributes>, handler: IReadonlyEnemyHandler<IEnemyAttr, IHeroAttr>,
locator: ITileLocator, context: IEnemyContext<IEnemyAttr, IHeroAttr>
context: IEnemyContext<IEnemyAttributes>
): GuardAura { ): GuardAura {
return new GuardAura(context, enemy, special, locator); return new GuardAura(context, handler.enemy, special, handler.locator);
} }
} }
export class GuardAura implements IEnemyAuraView< export class GuardAura implements IEnemyAuraView<
IEnemyAttributes, IEnemyAttr,
IRectRangeParam, IRectRangeParam,
void void
> { > {
@ -144,11 +151,11 @@ export class GuardAura implements IEnemyAuraView<
readonly couldApplySpecial: boolean = false; readonly couldApplySpecial: boolean = false;
readonly range: IRange<IRectRangeParam> = RECT_RANGE; readonly range: IRange<IRectRangeParam> = RECT_RANGE;
private readonly sourceView: IEnemyView<IEnemyAttributes> | null; private readonly sourceView: IEnemyView<IEnemyAttr> | null;
constructor( constructor(
context: IEnemyContext<IEnemyAttributes>, context: IEnemyContext<IEnemyAttr, IHeroAttr>,
readonly enemy: IReadonlyEnemy<IEnemyAttributes>, readonly enemy: IReadonlyEnemy<IEnemyAttr>,
readonly special: ISpecial<void>, readonly special: ISpecial<void>,
readonly locator: ITileLocator readonly locator: ITileLocator
) { ) {
@ -164,19 +171,16 @@ export class GuardAura implements IEnemyAuraView<
}; };
} }
apply( apply(handler: IEnemyHandler<IEnemyAttr, IHeroAttr>): void {
enemy: IEnemy<IEnemyAttributes>,
_baseEnemy: IReadonlyEnemy<IEnemyAttributes>,
locator: ITileLocator
): void {
if (!this.sourceView) return; if (!this.sourceView) return;
const { enemy, locator } = handler;
if (locator.x === this.locator.x && locator.y === this.locator.y) { if (locator.x === this.locator.x && locator.y === this.locator.y) {
return; return;
} }
enemy.getAttribute('guard').add(this.sourceView); enemy.getAttribute('guard').add(this.sourceView);
} }
applySpecial(): IEnemySpecialModifier<IEnemyAttributes> | null { applySpecial(): IEnemySpecialModifier<IEnemyAttr> | null {
return null; return null;
} }
} }

View File

@ -1,29 +1,28 @@
import { IEnemy, IEnemyFinalEffect } from '@user/data-base'; import { IEnemyFinalEffect, IEnemyHandler } from '@user/data-base';
import { IEnemyAttributes } from './types'; import { IEnemyAttr } from './types';
import { ITileLocator } from '@motajs/common'; import { IHeroAttr } from '../hero';
const HERO_STATUS_PLACEHOLDER = { export class MainEnemyFinalEffect implements IEnemyFinalEffect<
atk: 0, IEnemyAttr,
def: 0 IHeroAttr
} as const; > {
export class MainEnemyFinalEffect implements IEnemyFinalEffect<IEnemyAttributes> {
readonly priority: number = 0; readonly priority: number = 0;
apply(enemy: IEnemy<IEnemyAttributes>, _locator: ITileLocator): void { apply(handler: IEnemyHandler<IEnemyAttr, IHeroAttr>): void {
const enemy = handler.enemy;
const heroAtk = handler.hero.getFinalAttribute('atk');
const heroDef = handler.hero.getFinalAttribute('def');
// 3-坚固 // 3-坚固
if (enemy.hasSpecial(3)) { if (enemy.hasSpecial(3)) {
const target = Math.max( const target = Math.max(enemy.getAttribute('def'), heroAtk - 1);
enemy.getAttribute('def'),
HERO_STATUS_PLACEHOLDER.atk - 1
);
enemy.setAttribute('def', target); enemy.setAttribute('def', target);
} }
// 10-模仿 // 10-模仿
if (enemy.hasSpecial(10)) { if (enemy.hasSpecial(10)) {
enemy.setAttribute('atk', HERO_STATUS_PLACEHOLDER.atk); enemy.setAttribute('atk', heroAtk);
enemy.setAttribute('def', HERO_STATUS_PLACEHOLDER.def); enemy.setAttribute('def', heroDef);
} }
} }
} }

View File

@ -1,11 +1,11 @@
import { IEnemyLegacyBridge } from '@user/data-base'; import { IEnemyLegacyBridge } from '@user/data-base';
import { IEnemyAttributes } from './types'; import { IEnemyAttr } from './types';
export class EnemyLegacyBridge implements IEnemyLegacyBridge<IEnemyAttributes> { export class EnemyLegacyBridge implements IEnemyLegacyBridge<IEnemyAttr> {
fromLegacyEnemy( fromLegacyEnemy(
enemy: Enemy, enemy: Enemy,
defaultAttr: Partial<IEnemyAttributes> defaultAttr: Partial<IEnemyAttr>
): IEnemyAttributes { ): IEnemyAttr {
return { return {
hp: enemy.hp ?? defaultAttr.hp ?? 0, hp: enemy.hp ?? defaultAttr.hp ?? 0,
atk: enemy.atk ?? defaultAttr.atk ?? 0, atk: enemy.atk ?? defaultAttr.atk ?? 0,

View File

@ -11,17 +11,20 @@ import {
RectRange, RectRange,
ITileLocator ITileLocator
} from '@motajs/common'; } from '@motajs/common';
import { IReadonlyEnemy, ISpecial } from '@user/data-base';
import { import {
IEnemyContext, IEnemyContext,
IMapDamageConverter, IMapDamageConverter,
IMapDamageInfo, IMapDamageInfo,
IMapDamageInfoExtra, IMapDamageInfoExtra,
IMapDamageReducer, IMapDamageReducer,
IMapDamageView IReadonlyEnemyHandler,
ISpecial,
IMapDamageView,
IReadonlyHeroAttribute
} from '@user/data-base'; } from '@user/data-base';
import { IZoneValue } from './special'; import { IZoneValue } from './special';
import { IEnemyAttributes, MapDamageType } from './types'; import { IEnemyAttr, MapDamageType } from './types';
import { IHeroAttr } from '../hero';
const RECT_RANGE = new RectRange(); const RECT_RANGE = new RectRange();
const MANHATTAN_RANGE = new ManhattanRange(); const MANHATTAN_RANGE = new ManhattanRange();
@ -33,7 +36,9 @@ const DIR4 = [...DIRECTION_MAPPER.map(InternalDirectionGroup.Dir4)];
//#region 地图伤害 //#region 地图伤害
abstract class BaseMapDamageView<T> implements IMapDamageView<T> { abstract class BaseMapDamageView<T> implements IMapDamageView<T> {
constructor(protected readonly context: IEnemyContext<IEnemyAttributes>) {} constructor(
protected readonly context: IEnemyContext<IEnemyAttr, IHeroAttr>
) {}
abstract getRange(): IRange<T>; abstract getRange(): IRange<T>;
@ -80,7 +85,7 @@ export class ZoneDamageView extends BaseMapDamageView<
IRectRangeParam | IManhattanRangeParam IRectRangeParam | IManhattanRangeParam
> { > {
constructor( constructor(
context: IEnemyContext<IEnemyAttributes>, context: IEnemyContext<IEnemyAttr, IHeroAttr>,
private readonly locator: Readonly<ITileLocator>, private readonly locator: Readonly<ITileLocator>,
private readonly special: Readonly<ISpecial<IZoneValue>> private readonly special: Readonly<ISpecial<IZoneValue>>
) { ) {
@ -108,16 +113,14 @@ export class ZoneDamageView extends BaseMapDamageView<
}; };
} }
getDamageWithoutCheck( getDamageWithoutCheck(): Readonly<IMapDamageInfo> | null {
_locator: ITileLocator
): Readonly<IMapDamageInfo> | null {
return this.createInfo(this.special.value.zone, MapDamageType.Zone); return this.createInfo(this.special.value.zone, MapDamageType.Zone);
} }
} }
export class RepulseDamageView extends BaseMapDamageView<IManhattanRangeParam> { export class RepulseDamageView extends BaseMapDamageView<IManhattanRangeParam> {
constructor( constructor(
context: IEnemyContext<IEnemyAttributes>, context: IEnemyContext<IEnemyAttr, IHeroAttr>,
private readonly locator: Readonly<ITileLocator>, private readonly locator: Readonly<ITileLocator>,
private readonly special: Readonly<ISpecial<number>> private readonly special: Readonly<ISpecial<number>>
) { ) {
@ -151,7 +154,7 @@ export class RepulseDamageView extends BaseMapDamageView<IManhattanRangeParam> {
export class LaserDamageView extends BaseMapDamageView<IRayRangeParam> { export class LaserDamageView extends BaseMapDamageView<IRayRangeParam> {
constructor( constructor(
context: IEnemyContext<IEnemyAttributes>, context: IEnemyContext<IEnemyAttr, IHeroAttr>,
private readonly locator: Readonly<ITileLocator>, private readonly locator: Readonly<ITileLocator>,
private readonly special: Readonly<ISpecial<number>>, private readonly special: Readonly<ISpecial<number>>,
private readonly dir: IDirectionDescriptor[] = DIR4 private readonly dir: IDirectionDescriptor[] = DIR4
@ -171,23 +174,16 @@ export class LaserDamageView extends BaseMapDamageView<IRayRangeParam> {
}; };
} }
getDamageWithoutCheck( getDamageWithoutCheck(): Readonly<IMapDamageInfo> | null {
locator: ITileLocator
): Readonly<IMapDamageInfo> | null {
if (locator.x === this.locator.x && locator.y === this.locator.y) {
return null;
}
return this.createInfo(this.special.value, MapDamageType.Layer); return this.createInfo(this.special.value, MapDamageType.Layer);
} }
} }
export class BetweenDamageView extends BaseMapDamageView<IManhattanRangeParam> { export class BetweenDamageView extends BaseMapDamageView<IManhattanRangeParam> {
private static readonly DAMAGE = 1;
constructor( constructor(
context: IEnemyContext<IEnemyAttributes>, context: IEnemyContext<IEnemyAttr, IHeroAttr>,
private readonly locator: Readonly<ITileLocator> private readonly locator: Readonly<ITileLocator>,
private readonly hero: IReadonlyHeroAttribute<IHeroAttr>
) { ) {
super(context); super(context);
} }
@ -232,13 +228,14 @@ export class BetweenDamageView extends BaseMapDamageView<IManhattanRangeParam> {
return null; 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<IManhattanRangeParam> { export class AmbushDamageView extends BaseMapDamageView<IManhattanRangeParam> {
constructor( constructor(
context: IEnemyContext<IEnemyAttributes>, context: IEnemyContext<IEnemyAttr, IHeroAttr>,
private readonly locator: Readonly<ITileLocator> private readonly locator: Readonly<ITileLocator>
) { ) {
super(context); super(context);
@ -256,13 +253,7 @@ export class AmbushDamageView extends BaseMapDamageView<IManhattanRangeParam> {
}; };
} }
getDamageWithoutCheck( getDamageWithoutCheck(): Readonly<IMapDamageInfo> | null {
locator: ITileLocator
): Readonly<IMapDamageInfo> | null {
if (locator.x === this.locator.x && locator.y === this.locator.y) {
return null;
}
return this.createInfo(0, MapDamageType.Unknown, { return this.createInfo(0, MapDamageType.Unknown, {
catch: new Set([this.locator]) catch: new Set([this.locator])
}); });
@ -273,13 +264,16 @@ export class AmbushDamageView extends BaseMapDamageView<IManhattanRangeParam> {
//#region 转换器 //#region 转换器
export class MainMapDamageConverter implements IMapDamageConverter<IEnemyAttributes> { export class MainMapDamageConverter implements IMapDamageConverter<
IEnemyAttr,
IHeroAttr
> {
convert( convert(
enemy: IReadonlyEnemy<IEnemyAttributes>, handler: IReadonlyEnemyHandler<IEnemyAttr, IHeroAttr>,
locator: ITileLocator, context: IEnemyContext<IEnemyAttr, IHeroAttr>
context: IEnemyContext<IEnemyAttributes>
): IMapDamageView<any>[] { ): IMapDamageView<any>[] {
const views: IMapDamageView<any>[] = []; const views: IMapDamageView<any>[] = [];
const { enemy, locator } = handler;
const zone = enemy.getSpecial<IZoneValue>(15); const zone = enemy.getSpecial<IZoneValue>(15);
if (zone) { if (zone) {
@ -287,7 +281,7 @@ export class MainMapDamageConverter implements IMapDamageConverter<IEnemyAttribu
} }
if (enemy.hasSpecial(16)) { if (enemy.hasSpecial(16)) {
views.push(new BetweenDamageView(context, locator)); views.push(new BetweenDamageView(context, locator, handler.hero));
} }
const repulse = enemy.getSpecial<number>(18); const repulse = enemy.getSpecial<number>(18);
@ -313,10 +307,7 @@ export class MainMapDamageConverter implements IMapDamageConverter<IEnemyAttribu
//#region 合并器 //#region 合并器
export class MainMapDamageReducer implements IMapDamageReducer { export class MainMapDamageReducer implements IMapDamageReducer {
reduce( reduce(info: Iterable<Readonly<IMapDamageInfo>>): Readonly<IMapDamageInfo> {
info: Iterable<Readonly<IMapDamageInfo>>,
_locator: ITileLocator
): Readonly<IMapDamageInfo> {
let damage = 0; let damage = 0;
let type = MapDamageType.Unknown; let type = MapDamageType.Unknown;
let maxDamage = -Infinity; let maxDamage = -Infinity;

View File

@ -4,7 +4,7 @@ import {
IEnemyManager IEnemyManager
} from '@user/data-base'; } from '@user/data-base';
import { getHeroStatusOn } from '../legacy/hero'; import { getHeroStatusOn } from '../legacy/hero';
import { IEnemyAttributes } from './types'; import { IEnemyAttr } from './types';
//#region 复合属性值类型 //#region 复合属性值类型
@ -48,9 +48,7 @@ export interface IHaloValue {
* 8. changingFloor * 8. changingFloor
* 9. | packages-user/legacy-plugin-data/src/enemy/checkblock.ts * 9. | packages-user/legacy-plugin-data/src/enemy/checkblock.ts
*/ */
export function registerSpecials( export function registerSpecials(manager: IEnemyManager<IEnemyAttr>): void {
manager: IEnemyManager<IEnemyAttributes>
): void {
manager.setAttributeDefaults('guard', new Set()); manager.setAttributeDefaults('guard', new Set());
// 0 - 空 // 0 - 空

View File

@ -1,6 +1,6 @@
import { IEnemyView } from '@user/data-base'; import { IEnemyView } from '@user/data-base';
export interface IEnemyAttributes { export interface IEnemyAttr {
/** 怪物生命值 */ /** 怪物生命值 */
hp: number; hp: number;
/** 怪物攻击力 */ /** 怪物攻击力 */
@ -14,7 +14,7 @@ export interface IEnemyAttributes {
/** 怪物加点量 */ /** 怪物加点量 */
point: number; point: number;
/** 支援来源怪物视图列表 */ /** 支援来源怪物视图列表 */
guard: Set<IEnemyView<IEnemyAttributes>>; guard: Set<IEnemyView<IEnemyAttr>>;
} }
export const enum MapDamageType { export const enum MapDamageType {

View File

@ -1,6 +1,6 @@
//#region 勇士属性 //#region 勇士属性
export interface IHeroAttributeObject { export interface IHeroAttr {
/** 勇士名称 */ /** 勇士名称 */
name: string; name: string;
/** 勇士生命值 */ /** 勇士生命值 */

View File

@ -288,7 +288,7 @@ export class HeroMover extends ObjectMoverBase {
protected async onMoveStart(controller: IMoveController): Promise<void> { protected async onMoveStart(controller: IMoveController): Promise<void> {
this.beforeMoveSpeed = this.moveSpeed; this.beforeMoveSpeed = this.moveSpeed;
if (!core.isReplaying() || core.status.replay.speed <= 12) { if (!core.isReplaying() || core.status.replay.speed <= 12) {
state.hero.startMove(); state.hero.mover.startMove();
} }
// 这里要检查前面那一格能不能走,不能走则不触发平滑视角,以避免撞墙上视角卡住 // 这里要检查前面那一格能不能走,不能走则不触发平滑视角,以避免撞墙上视角卡住
if (!this.ignoreTerrain) { if (!this.ignoreTerrain) {
@ -310,7 +310,7 @@ export class HeroMover extends ObjectMoverBase {
protected async onMoveEnd(controller: IMoveController): Promise<void> { protected async onMoveEnd(controller: IMoveController): Promise<void> {
this.moveSpeed = this.beforeMoveSpeed; this.moveSpeed = this.beforeMoveSpeed;
this.onSetMoveSpeed(this.moveSpeed, controller); this.onSetMoveSpeed(this.moveSpeed, controller);
await state.hero.endMove(); await state.hero.mover.endMove();
// viewport.sync('endMove'); // viewport.sync('endMove');
core.clearContinueAutomaticRoute(); core.clearContinueAutomaticRoute();
core.stopAutomaticRoute(); core.stopAutomaticRoute();
@ -442,19 +442,22 @@ export class HeroMover extends ObjectMoverBase {
const replaying = core.isReplaying(); const replaying = core.isReplaying();
if (replaying) { if (replaying) {
if (core.status.replay.speed > 12) { if (core.status.replay.speed > 12) {
await state.hero.endMove(); await state.hero.mover.endMove();
await sleep(speed); await sleep(speed);
state.hero.setPosition(x, y); state.hero.mover.setPosition(x, y);
} else { } else {
state.hero.startMove(); state.hero.mover.startMove();
await state.hero.move( await state.hero.mover.move(
fromDirectionString(moveDir), fromDirectionString(moveDir),
this.moveSpeed / core.status.replay.speed this.moveSpeed / core.status.replay.speed
); );
} }
} else { } else {
state.hero.startMove(); state.hero.mover.startMove();
await state.hero.move(fromDirectionString(moveDir), this.moveSpeed); await state.hero.mover.move(
fromDirectionString(moveDir),
this.moveSpeed
);
} }
} }

View File

@ -1,4 +1,4 @@
import { IHeroAttributeObject } from './hero'; import { IHeroAttr } from './hero';
/** 每个地图的默认宽度 */ /** 每个地图的默认宽度 */
export const TILE_WIDTH = 13; export const TILE_WIDTH = 13;
@ -11,7 +11,7 @@ export const TILE_HEIGHT = 13;
export const DEFAULT_HERO_IMAGE: ImageIds = 'hero.png'; export const DEFAULT_HERO_IMAGE: ImageIds = 'hero.png';
/** 勇士的初始属性,数值填多少目前都无所谓,因为最终会从旧样板读取,但是必须得填 */ /** 勇士的初始属性,数值填多少目前都无所谓,因为最终会从旧样板读取,但是必须得填 */
export const HERO_DEFAULT_ATTRIBUTE: IHeroAttributeObject = { export const HERO_DEFAULT_ATTRIBUTE: IHeroAttr = {
name: '', name: '',
hp: 1, hp: 1,
hpmax: 0, hpmax: 0,

View File

@ -6,12 +6,12 @@ import {
IHeroFollower, IHeroFollower,
IHeroState IHeroState
} from '@user/data-base'; } from '@user/data-base';
import { IEnemyAttributes } from './enemy/types'; import { IEnemyAttr } from './enemy/types';
import { IHeroAttributeObject } from './hero'; import { IHeroAttr } from './hero';
export interface IGameDataState { export interface IGameDataState {
/** 怪物管理器 */ /** 怪物管理器 */
readonly enemyManager: IEnemyManager<IEnemyAttributes>; readonly enemyManager: IEnemyManager<IEnemyAttr>;
} }
export interface IStateSaveData { export interface IStateSaveData {
@ -23,7 +23,7 @@ export interface ICoreState {
/** 地图状态 */ /** 地图状态 */
readonly layer: ILayerState; readonly layer: ILayerState;
/** 勇士状态 */ /** 勇士状态 */
readonly hero: IHeroState<IHeroAttributeObject>; readonly hero: IHeroState<IHeroAttr>;
/** 朝向绑定 */ /** 朝向绑定 */
readonly roleFace: IRoleFaceBinder; readonly roleFace: IRoleFaceBinder;
/** id 到图块数字的映射 */ /** id 到图块数字的映射 */
@ -32,9 +32,9 @@ export interface ICoreState {
readonly numberIdMap: Map<number, string>; readonly numberIdMap: Map<number, string>;
/** 怪物管理器 */ /** 怪物管理器 */
readonly enemyManager: IEnemyManager<IEnemyAttributes>; readonly enemyManager: IEnemyManager<IEnemyAttr>;
/** 怪物上下文 */ /** 怪物上下文 */
readonly enemyContext: IEnemyContext<IEnemyAttributes>; readonly enemyContext: IEnemyContext<IEnemyAttr, IHeroAttr>;
/** /**
* *

View File

@ -153,13 +153,13 @@ export function initFallback() {
core.status.hero.loc[name] = value; core.status.hero.loc[name] = value;
if (name === 'direction') { if (name === 'direction') {
const dir = fromDirectionString(value as Dir); const dir = fromDirectionString(value as Dir);
state.hero.turn(dir); state.hero.mover.turn(dir);
setHeroDirection(value as Dir); setHeroDirection(value as Dir);
} else if (name === 'x') { } else if (name === 'x') {
// 为了防止逆天样板出问题 // 为了防止逆天样板出问题
core.bigmap.posX = value as number; core.bigmap.posX = value as number;
if (!noGather) { if (!noGather) {
state.hero.setPosition( state.hero.mover.setPosition(
value as number, value as number,
core.status.hero.loc.y core.status.hero.loc.y
); );
@ -168,7 +168,7 @@ export function initFallback() {
// 为了防止逆天样板出问题 // 为了防止逆天样板出问题
core.bigmap.posY = value as number; core.bigmap.posY = value as number;
if (!noGather) { if (!noGather) {
state.hero.setPosition( state.hero.mover.setPosition(
core.status.hero.loc.x, core.status.hero.loc.x,
value as number value as number
); );
@ -218,7 +218,7 @@ export function initFallback() {
patch2.add('setHeroIcon', function (name: ImageIds) { patch2.add('setHeroIcon', function (name: ImageIds) {
core.status.hero.image = name; core.status.hero.image = name;
state.hero.setImage(name); state.hero.mover.setImage(name);
}); });
patch.add('isMoving', function () { patch.add('isMoving', function () {
@ -564,7 +564,7 @@ export function initFallback() {
time /= core.status.replay.speed; time /= core.status.replay.speed;
if (core.status.replay.speed === 24) time = 1; 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(); if (!locked) core.unlockControl();
core.setHeroLoc('x', ex); core.setHeroLoc('x', ex);

View File

@ -165,6 +165,7 @@
"107": "Hero status is not bound, damage calculation is unavailable.", "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'.", "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'.", "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." "1001": "Item-detail extension needs 'floor-binder' and 'floor-damage' extension as dependency."
} }
} }

View File

@ -104,19 +104,20 @@ export class ManhattanRange extends BaseRange<IManhattanRangeParam> {
protected estimatePointCount( protected estimatePointCount(
param: Readonly<IManhattanRangeParam> param: Readonly<IManhattanRangeParam>
): number { ): number {
const radius = Math.max(0, param.radius); const radius = Math.abs(param.radius);
return 1 + 2 * radius * (radius + 1); return 1 + 2 * radius * (radius + 1);
} }
*iterateLoc(param: Readonly<IManhattanRangeParam>): Iterable<number> { *iterateLoc(param: Readonly<IManhattanRangeParam>): Iterable<number> {
const { width, height } = this.host; 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; const y = param.cy + dy;
if (y < 0 || y >= height) { if (y < 0 || y >= height) {
continue; continue;
} }
const span = param.radius - Math.abs(dy); const span = radius - Math.abs(dy);
const startX = Math.max(0, param.cx - span); const startX = Math.max(0, param.cx - span);
const endX = Math.min(width - 1, param.cx + span); const endX = Math.min(width - 1, param.cx + span);
for (let x = startX; x <= endX; x++) { for (let x = startX; x <= endX; x++) {
@ -130,10 +131,10 @@ export class ManhattanRange extends BaseRange<IManhattanRangeParam> {
y: number, y: number,
param: Readonly<IManhattanRangeParam> param: Readonly<IManhattanRangeParam>
): boolean { ): boolean {
return ( const radius = Math.abs(param.radius);
this.inBound(x, y) && const dx = Math.abs(x - param.cx);
Math.abs(x - param.cx) + Math.abs(y - param.cy) <= param.radius const dy = Math.abs(y - param.cy);
); return this.inBound(x, y) && dx + dy <= radius;
} }
} }
@ -141,6 +142,7 @@ export class RayRange extends BaseRange<IRayRangeParam> {
protected estimatePointCount(param: Readonly<IRayRangeParam>): number { protected estimatePointCount(param: Readonly<IRayRangeParam>): number {
const { width, height } = this.host; const { width, height } = this.host;
// 考虑到这种范围的 `inRange` 判断要更加耗时,因此将返回值略微降低,更倾向于使用 `scan` 方式 // 考虑到这种范围的 `inRange` 判断要更加耗时,因此将返回值略微降低,更倾向于使用 `scan` 方式
// 正常情况下应该 / 2
return ((width + height) * param.dir.length) / 3; return ((width + height) * param.dir.length) / 3;
} }