refactor: 怪物基本属性改为泛型

This commit is contained in:
unanmed 2026-04-15 14:49:36 +08:00
parent 057d5ab813
commit a24911400f
12 changed files with 405 additions and 286 deletions

View File

@ -1,4 +1,5 @@
import { IRange, logger } from '@motajs/common'; import { IRange, logger } from '@motajs/common';
import { ITileLocator } from '@user/types';
import { import {
IAuraConverter, IAuraConverter,
IAuraView, IAuraView,
@ -16,36 +17,42 @@ import {
} from './types'; } from './types';
import { EnemyView } from './enemy'; import { EnemyView } from './enemy';
import { MapLocIndexer } from './utils'; import { MapLocIndexer } from './utils';
import { ITileLocator } from '@user/types';
export class EnemyContext implements IEnemyContext { export class EnemyContext<TAttr> implements IEnemyContext<TAttr> {
private readonly enemyViewMap: Map<number, EnemyView> = new Map(); private readonly enemyViewMap: Map<number, EnemyView<TAttr>> = new Map();
private readonly enemyMap: Map<number, IEnemy> = new Map(); private readonly enemyMap: Map<number, IEnemy<TAttr>> = new Map();
private readonly locatorViewMap: Map<IEnemyView, number> = new Map(); private readonly locatorViewMap: Map<IEnemyView<TAttr>, number> = new Map();
private readonly locatorEnemyMap: Map<IEnemy, number> = new Map(); private readonly locatorEnemyMap: Map<IEnemy<TAttr>, number> = new Map();
private readonly computedToView: Map<IReadonlyEnemy, EnemyView> = new Map(); private readonly computedToView: Map<
IReadonlyEnemy<TAttr>,
EnemyView<TAttr>
> = new Map();
private readonly auraConverter: Set<IAuraConverter> = new Set(); private readonly auraConverter: Set<IAuraConverter<TAttr>> = new Set();
private readonly converterStatus: Map<IAuraConverter, boolean> = new Map(); private readonly converterStatus: Map<IAuraConverter<TAttr>, boolean> =
private readonly convertedAura: Map<ISpecial<any>, IAuraView> = new Map();
private readonly commonQueryMap: Map<number, IEnemyCommonQueryEffect[]> =
new Map(); new Map();
private readonly convertedAura: Map<ISpecial<any>, IAuraView<TAttr>> =
new Map();
private readonly commonQueryMap: Map<
number,
IEnemyCommonQueryEffect<TAttr>[]
> = new Map();
private readonly specialQueryEffects: Map< private readonly specialQueryEffects: Map<
number, number,
IEnemySpecialQueryEffect[] IEnemySpecialQueryEffect<TAttr>[]
> = new Map(); > = new Map();
private readonly finalEffects: IEnemyFinalEffect[] = []; private readonly finalEffects: IEnemyFinalEffect<TAttr>[] = [];
private readonly globalAuraList: Set<IAuraView> = new Set(); private readonly globalAuraList: Set<IAuraView<TAttr>> = new Set();
private readonly sortedAura: Map<number, Set<IAuraView>> = new Map(); private readonly sortedAura: Map<number, Set<IAuraView<TAttr>>> = new Map();
private readonly needTotallyRefresh: Set<IEnemyView> = new Set(); private readonly needTotallyRefresh: Set<IEnemyView<TAttr>> = new Set();
private readonly requestedCommonContext: Set<IEnemyView> = new Set(); private readonly requestedCommonContext: Set<IEnemyView<TAttr>> = new Set();
private readonly dirtyEnemy: Set<IEnemyView> = new Set(); private readonly dirtyEnemy: Set<IEnemyView<TAttr>> = new Set();
private mapDamage: IMapDamage | null = null; private mapDamage: IMapDamage<TAttr> | null = null;
readonly indexer: MapLocIndexer = new MapLocIndexer(); readonly indexer: MapLocIndexer = new MapLocIndexer();
private needUpdate: boolean = true; private needUpdate: boolean = true;
@ -61,24 +68,27 @@ export class EnemyContext implements IEnemyContext {
this.indexer.setWidth(width); this.indexer.setWidth(width);
} }
registerAuraConverter(converter: IAuraConverter): void { registerAuraConverter(converter: IAuraConverter<TAttr>): void {
this.auraConverter.add(converter); this.auraConverter.add(converter);
this.converterStatus.set(converter, true); this.converterStatus.set(converter, true);
} }
unregisterAuraConverter(converter: IAuraConverter): void { unregisterAuraConverter(converter: IAuraConverter<TAttr>): void {
this.auraConverter.delete(converter); this.auraConverter.delete(converter);
this.converterStatus.delete(converter); this.converterStatus.delete(converter);
} }
setAuraConverterEnabled(converter: IAuraConverter, enabled: boolean): void { setAuraConverterEnabled(
converter: IAuraConverter<TAttr>,
enabled: boolean
): void {
if (!this.auraConverter.has(converter)) return; if (!this.auraConverter.has(converter)) return;
this.converterStatus.set(converter, enabled); this.converterStatus.set(converter, enabled);
} }
registerCommonQueryEffect( registerCommonQueryEffect(
code: number, code: number,
effect: IEnemyCommonQueryEffect effect: IEnemyCommonQueryEffect<TAttr>
): void { ): void {
const array = this.commonQueryMap.getOrInsert(code, []); const array = this.commonQueryMap.getOrInsert(code, []);
array.push(effect); array.push(effect);
@ -87,7 +97,7 @@ export class EnemyContext implements IEnemyContext {
unregisterCommonQueryEffect( unregisterCommonQueryEffect(
code: number, code: number,
effect: IEnemyCommonQueryEffect effect: IEnemyCommonQueryEffect<TAttr>
): void { ): void {
const array = this.commonQueryMap.get(code); const array = this.commonQueryMap.get(code);
if (!array) return; if (!array) return;
@ -96,12 +106,14 @@ export class EnemyContext implements IEnemyContext {
array.splice(index, 1); array.splice(index, 1);
} }
registerSpecialQueryEffect(effect: IEnemySpecialQueryEffect): void { registerSpecialQueryEffect(effect: IEnemySpecialQueryEffect<TAttr>): void {
const list = this.specialQueryEffects.getOrInsert(effect.priority, []); const list = this.specialQueryEffects.getOrInsert(effect.priority, []);
list.push(effect); list.push(effect);
} }
unregisterSpecialQueryEffect(effect: IEnemySpecialQueryEffect): void { unregisterSpecialQueryEffect(
effect: IEnemySpecialQueryEffect<TAttr>
): void {
const list = this.specialQueryEffects.get(effect.priority); const list = this.specialQueryEffects.get(effect.priority);
if (!list) return; if (!list) return;
const index = list.indexOf(effect); const index = list.indexOf(effect);
@ -113,41 +125,43 @@ export class EnemyContext implements IEnemyContext {
} }
} }
registerFinalEffect(effect: IEnemyFinalEffect): void { registerFinalEffect(effect: IEnemyFinalEffect<TAttr>): 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);
} }
unregisterFinalEffect(effect: IEnemyFinalEffect): void { unregisterFinalEffect(effect: IEnemyFinalEffect<TAttr>): 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);
} }
} }
getEnemyLocator(enemy: IEnemy): 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;
return this.indexer.indexToLocator(index); return this.indexer.indexToLocator(index);
} }
getEnemyLocatorByView(view: IEnemyView): Readonly<ITileLocator> | null { getEnemyLocatorByView(
view: IEnemyView<TAttr>
): Readonly<ITileLocator> | null {
const index = this.locatorViewMap.get(view); const index = this.locatorViewMap.get(view);
if (index === undefined) return null; if (index === undefined) return null;
return this.indexer.indexToLocator(index); return this.indexer.indexToLocator(index);
} }
getEnemyByLocator(locator: ITileLocator): IEnemyView | null { getEnemyByLocator(locator: ITileLocator): IEnemyView<TAttr> | null {
const index = this.indexer.locToIndex(locator.x, locator.y); const index = this.indexer.locToIndex(locator.x, locator.y);
return this.enemyViewMap.get(index) ?? null; return this.enemyViewMap.get(index) ?? null;
} }
getEnemyByLoc(x: number, y: number): IEnemyView | null { getEnemyByLoc(x: number, y: number): IEnemyView<TAttr> | null {
const index = this.indexer.locToIndex(x, y); const index = this.indexer.locToIndex(x, y);
return this.enemyViewMap.get(index) ?? null; return this.enemyViewMap.get(index) ?? null;
} }
getViewByComputed(enemy: IReadonlyEnemy): IEnemyView | null { getViewByComputed(enemy: IReadonlyEnemy<TAttr>): IEnemyView<TAttr> | null {
return this.computedToView.get(enemy) ?? null; return this.computedToView.get(enemy) ?? null;
} }
@ -172,11 +186,11 @@ export class EnemyContext implements IEnemyContext {
this.locatorEnemyMap.delete(enemy); this.locatorEnemyMap.delete(enemy);
} }
setEnemyAt(locator: ITileLocator, enemy: IEnemy): void { setEnemyAt(locator: ITileLocator, enemy: IEnemy<TAttr>): void {
const index = this.indexer.locToIndex(locator.x, locator.y); const index = this.indexer.locToIndex(locator.x, locator.y);
this.deleteEnemyAt(index); this.deleteEnemyAt(index);
const view = new EnemyView(enemy, this); const view = new EnemyView<TAttr>(enemy, this);
this.enemyMap.set(index, enemy); this.enemyMap.set(index, enemy);
this.enemyViewMap.set(index, view); this.enemyViewMap.set(index, view);
this.locatorEnemyMap.set(enemy, index); this.locatorEnemyMap.set(enemy, index);
@ -194,7 +208,7 @@ export class EnemyContext implements IEnemyContext {
private *internalScanRange<T>( private *internalScanRange<T>(
range: IRange<T>, range: IRange<T>,
param: T param: T
): Iterable<EnemyView> { ): Iterable<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);
@ -207,44 +221,44 @@ export class EnemyContext implements IEnemyContext {
} }
} }
scanRange<T>(range: IRange<T>, param: T): Iterable<IEnemyView> { scanRange<T>(range: IRange<T>, param: T): Iterable<IEnemyView<TAttr>> {
return this.internalScanRange(range, param); return this.internalScanRange(range, param);
} }
*iterateEnemy(): Iterable<[ITileLocator, IEnemyView]> { *iterateEnemy(): Iterable<[ITileLocator, IEnemyView<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);
yield [locator, view]; yield [locator, view];
} }
} }
addAura(aura: IAuraView): void { addAura(aura: IAuraView<TAttr>): void {
this.globalAuraList.add(aura); this.globalAuraList.add(aura);
this.needUpdate = true; this.needUpdate = true;
} }
deleteAura(aura: IAuraView): void { deleteAura(aura: IAuraView<TAttr>): void {
this.globalAuraList.delete(aura); this.globalAuraList.delete(aura);
this.needUpdate = true; this.needUpdate = true;
} }
attachMapDamage(damage: IMapDamage | null): void { attachMapDamage(damage: IMapDamage<TAttr> | null): void {
this.mapDamage = damage; this.mapDamage = damage;
if (damage) { if (damage) {
damage.refreshAll(); damage.refreshAll();
} }
} }
getMapDamage(): IMapDamage | null { getMapDamage(): IMapDamage<TAttr> | null {
return this.mapDamage; return this.mapDamage;
} }
private convertSpecial( private convertSpecial(
special: ISpecial<any>, special: ISpecial<any>,
enemy: IReadonlyEnemy, enemy: IReadonlyEnemy<TAttr>,
locator: ITileLocator locator: ITileLocator
): IEnemyAuraView<any, any> | null { ): IEnemyAuraView<TAttr, any, any> | null {
let matched: IAuraConverter | null = null; let matched: IAuraConverter<TAttr> | 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;
@ -258,10 +272,10 @@ export class EnemyContext implements IEnemyContext {
} }
if (!matched) return null; if (!matched) return null;
return matched.convert(special, enemy, locator); return matched.convert(special, enemy, locator, this);
} }
private insertIntoSortedAura(aura: IAuraView): void { private insertIntoSortedAura(aura: IAuraView<TAttr>): void {
const set = this.sortedAura.getOrInsertComputed( const set = this.sortedAura.getOrInsertComputed(
aura.priority, aura.priority,
() => new Set() () => new Set()
@ -269,7 +283,7 @@ export class EnemyContext implements IEnemyContext {
set.add(aura); set.add(aura);
} }
private removeFromSortedAura(aura: IAuraView): void { private removeFromSortedAura(aura: IAuraView<TAttr>): void {
const set = this.sortedAura.get(aura.priority); const set = this.sortedAura.get(aura.priority);
if (set) { if (set) {
set.delete(aura); set.delete(aura);
@ -280,15 +294,15 @@ export class EnemyContext implements IEnemyContext {
} }
private processSpecialModifier( private processSpecialModifier(
modifier: IEnemySpecialModifier, modifier: IEnemySpecialModifier<TAttr>,
enemy: IEnemy, enemy: IEnemy<TAttr>,
locator: ITileLocator, locator: ITileLocator,
currentPriority: number currentPriority: number
): Set<IAuraView> { ): Set<IAuraView<TAttr>> {
const toAdd = modifier.add(enemy, locator); const toAdd = modifier.add(enemy, locator);
const toDelete = modifier.delete(enemy, locator); const toDelete = modifier.delete(enemy, locator);
const affectedAuras = new Set<IAuraView>(); const affectedAuras = new Set<IAuraView<TAttr>>();
if (toAdd.length > 0 && toDelete.length > 0) { if (toAdd.length > 0 && toDelete.length > 0) {
logger.warn(100); logger.warn(100);
@ -351,7 +365,7 @@ export class EnemyContext implements IEnemyContext {
} }
private processSpecialQuery( private processSpecialQuery(
effect: IEnemySpecialQueryEffect, effect: IEnemySpecialQueryEffect<TAttr>,
currentPriority: number currentPriority: number
): void { ): void {
const modifier = effect.for(this); const modifier = effect.for(this);
@ -377,7 +391,10 @@ export class EnemyContext implements IEnemyContext {
} }
} }
private processAuraSpecial(aura: IAuraView, currentPriority: number): void { private processAuraSpecial(
aura: IAuraView<TAttr>,
currentPriority: number
): void {
const param = aura.getRangeParam(); const param = aura.getRangeParam();
for (const enemyView of this.internalScanRange(aura.range, param)) { for (const enemyView of this.internalScanRange(aura.range, param)) {
@ -410,7 +427,7 @@ export class EnemyContext implements IEnemyContext {
const enemy = view.getComputingEnemy(); const enemy = view.getComputingEnemy();
const locator = this.indexer.indexToLocator(index); const locator = this.indexer.indexToLocator(index);
for (const special of enemy.specials) { for (const special of enemy.iterateSpecials()) {
const aura = this.convertSpecial(special, enemy, locator); const aura = this.convertSpecial(special, enemy, locator);
if (!aura) continue; if (!aura) continue;
this.convertedAura.set(special, aura); this.convertedAura.set(special, aura);
@ -484,7 +501,7 @@ export class EnemyContext implements IEnemyContext {
queried = true; queried = true;
return this; return this;
}; };
for (const special of enemy.specials) { for (const special of enemy.iterateSpecials()) {
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) {
@ -533,14 +550,14 @@ export class EnemyContext implements IEnemyContext {
} }
} }
markDirty(view: IEnemyView): void { markDirty(view: IEnemyView<TAttr>): void {
if (!this.locatorViewMap.has(view)) return; if (!this.locatorViewMap.has(view)) return;
this.dirtyEnemy.add(view); this.dirtyEnemy.add(view);
} }
private refreshSpecialModifier( private refreshSpecialModifier(
modifier: IEnemySpecialModifier, modifier: IEnemySpecialModifier<TAttr>,
enemy: IEnemy, enemy: IEnemy<TAttr>,
locator: ITileLocator locator: ITileLocator
): void { ): void {
const toAdd = modifier.add(enemy, locator); const toAdd = modifier.add(enemy, locator);
@ -582,7 +599,7 @@ export class EnemyContext implements IEnemyContext {
} }
} }
private refreshEnemy(view: EnemyView): void { private refreshEnemy(view: EnemyView<TAttr>): void {
const locator = this.getEnemyLocatorByView(view); const locator = this.getEnemyLocatorByView(view);
if (!locator) return; if (!locator) return;
@ -653,7 +670,7 @@ export class EnemyContext implements IEnemyContext {
queried = true; queried = true;
return this; return this;
}; };
for (const special of enemy.specials) { for (const special of enemy.iterateSpecials()) {
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) {
@ -675,7 +692,7 @@ export class EnemyContext implements IEnemyContext {
} }
} }
requestRefresh(view: IEnemyView): void { requestRefresh(view: IEnemyView<TAttr>): void {
if (!this.dirtyEnemy.has(view)) return; if (!this.dirtyEnemy.has(view)) return;
if (this.needTotallyRefresh.has(view)) { if (this.needTotallyRefresh.has(view)) {
this.needUpdate = true; this.needUpdate = true;
@ -685,11 +702,11 @@ export class EnemyContext implements IEnemyContext {
return; return;
} }
this.refreshEnemy(view as EnemyView); this.refreshEnemy(view as EnemyView<TAttr>);
for (const requestedView of this.requestedCommonContext) { for (const requestedView of this.requestedCommonContext) {
if (requestedView === view) continue; if (requestedView === view) continue;
this.refreshEnemy(requestedView as EnemyView); this.refreshEnemy(requestedView as EnemyView<TAttr>);
} }
} }

View File

@ -1,14 +1,13 @@
import { logger } from '@motajs/common'; import { logger } from '@motajs/common';
import { import {
IEnemy, IEnemy,
IEnemyAttributes,
IEnemyContext, IEnemyContext,
IReadonlyEnemy, IReadonlyEnemy,
ISpecial, ISpecial,
IEnemyView IEnemyView
} from './types'; } from './types';
export class Enemy implements IEnemy { export class Enemy<TAttr> implements IEnemy<TAttr> {
readonly specials: Set<ISpecial<any>> = new Set(); readonly specials: Set<ISpecial<any>> = new Set();
/** code -> ISpecial 映射,用于快速查找 */ /** code -> ISpecial 映射,用于快速查找 */
private readonly specialMap: Map<number, ISpecial<any>> = new Map(); private readonly specialMap: Map<number, ISpecial<any>> = new Map();
@ -16,7 +15,7 @@ export class Enemy implements IEnemy {
constructor( constructor(
readonly id: string, readonly id: string,
readonly code: number, readonly code: number,
readonly attributes: IEnemyAttributes private attributes: TAttr
) {} ) {}
getSpecial<T>(code: number): ISpecial<T> | null { getSpecial<T>(code: number): ISpecial<T> | null {
@ -48,21 +47,20 @@ export class Enemy implements IEnemy {
return this.specials; return this.specials;
} }
setAttribute<K extends keyof IEnemyAttributes>( setAttribute<K extends keyof TAttr>(key: K, value: TAttr[K]): void {
key: K,
value: IEnemyAttributes[K]
): void {
this.attributes[key] = value; this.attributes[key] = value;
} }
getAttribute<K extends keyof IEnemyAttributes>( getAttribute<K extends keyof TAttr>(key: K): TAttr[K] {
key: K
): IEnemyAttributes[K] {
return this.attributes[key]; return this.attributes[key];
} }
clone(): IEnemy { cloneAttributes(): TAttr {
const cloned = new Enemy( return structuredClone(this.attributes);
}
clone(): IEnemy<TAttr> {
const cloned = new Enemy<TAttr>(
this.id, this.id,
this.code, this.code,
structuredClone(this.attributes) structuredClone(this.attributes)
@ -73,10 +71,8 @@ export class Enemy implements IEnemy {
return cloned; return cloned;
} }
copy(enemy: IReadonlyEnemy): void { copyFrom(enemy: IReadonlyEnemy<TAttr>): void {
ATTRIBUTE_KEYS.forEach(key => { this.attributes = enemy.cloneAttributes();
this.setAttribute(key, structuredClone(enemy.getAttribute(key)));
});
this.specials.clear(); this.specials.clear();
this.specialMap.clear(); this.specialMap.clear();
for (const special of enemy.iterateSpecials()) { for (const special of enemy.iterateSpecials()) {
@ -85,25 +81,25 @@ export class Enemy implements IEnemy {
} }
} }
export class EnemyView implements IEnemyView { export class EnemyView<TAttr> implements IEnemyView<TAttr> {
private computedEnemy: IEnemy; private computedEnemy: IEnemy<TAttr>;
constructor( constructor(
readonly baseEnemy: IEnemy, readonly baseEnemy: IEnemy<TAttr>,
readonly context: IEnemyContext readonly context: IEnemyContext<TAttr>
) { ) {
this.computedEnemy = baseEnemy.clone(); this.computedEnemy = baseEnemy.clone();
} }
reset(): void { reset(): void {
this.computedEnemy.copy(this.baseEnemy); this.computedEnemy.copyFrom(this.baseEnemy);
} }
getBaseEnemy(): IReadonlyEnemy { getBaseEnemy(): IReadonlyEnemy<TAttr> {
return this.baseEnemy; return this.baseEnemy;
} }
getComputedEnemy(): IReadonlyEnemy { getComputedEnemy(): IReadonlyEnemy<TAttr> {
this.context.requestRefresh(this); this.context.requestRefresh(this);
return this.computedEnemy; return this.computedEnemy;
} }
@ -111,11 +107,11 @@ export class EnemyView implements IEnemyView {
/** /**
* EnemyContext 使 * EnemyContext 使
*/ */
getComputingEnemy(): IEnemy { getComputingEnemy(): IEnemy<TAttr> {
return this.computedEnemy; return this.computedEnemy;
} }
getModifiableEnemy(): IEnemy { getModifiableEnemy(): IEnemy<TAttr> {
return this.baseEnemy; return this.baseEnemy;
} }
@ -123,12 +119,3 @@ export class EnemyView implements IEnemyView {
this.context.markDirty(this); this.context.markDirty(this);
} }
} }
export const ATTRIBUTE_KEYS: (keyof IEnemyAttributes)[] = [
'hp',
'atk',
'def',
'money',
'exp',
'point'
];

View File

@ -1,30 +1,21 @@
import { logger } from '@motajs/common'; import { logger } from '@motajs/common';
import { Enemy as EnemyImpl } from './enemy'; import { Enemy as EnemyImpl } from './enemy';
import { import { IEnemy, IEnemyManager, SpecialCreation } from './types';
IEnemy,
IEnemyAttributes,
IEnemyManager,
ISpecial,
SpecialCreation
} from './types';
export class EnemyManager implements IEnemyManager { export class EnemyManager<TAttr> implements IEnemyManager<TAttr> {
/** 特殊属性注册表code -> 创建函数 */ /** 特殊属性注册表code -> 创建函数 */
private readonly specialRegistry: Map<number, SpecialCreation<any>> = private readonly specialRegistry: Map<number, SpecialCreation<any, TAttr>> =
new Map(); new Map();
/** 自定义怪物属性注册表name -> 默认值 */ /** 自定义怪物属性注册表name -> 默认值 */
private readonly attributeRegistry: Map<string, any> = new Map(); private readonly attributeRegistry: Map<string, any> = new Map();
/** 怪物模板表code -> IEnemy */ /** 怪物模板表code -> IEnemy */
private readonly prefabByCode: Map<number, IEnemy> = new Map(); private readonly prefabByCode: Map<number, IEnemy<TAttr>> = new Map();
/** 怪物模板表id -> IEnemy */ /** 怪物模板表id -> IEnemy */
private readonly prefabById: Map<string, IEnemy> = new Map(); private readonly prefabById: Map<string, IEnemy<TAttr>> = new Map();
/** 旧样板怪物 id 到 code 的映射,用于 fromLegacyEnemy 快速查找已有模板 */ /** 旧样板怪物 id 到 code 的映射,用于 fromLegacyEnemy 快速查找已有模板 */
private readonly legacyIdToCode: Map<string, number> = new Map(); private readonly legacyIdToCode: Map<string, number> = new Map();
registerSpecial( registerSpecial(code: number, cons: SpecialCreation<any, TAttr>): void {
code: number,
cons: (enemy: IEnemy) => ISpecial<any>
): void {
this.specialRegistry.set(code, cons); this.specialRegistry.set(code, cons);
} }
@ -41,7 +32,7 @@ export class EnemyManager implements IEnemyManager {
this.attributeRegistry.set(name, defaultValue); this.attributeRegistry.set(name, defaultValue);
} }
fromLegacyEnemy(enemy: Enemy): IEnemy { fromLegacyEnemy(enemy: Enemy): IEnemy<TAttr> {
// 如果该旧样板怪物已经通过 addPrefabFromLegacy 注册为模板,直接克隆模板 // 如果该旧样板怪物已经通过 addPrefabFromLegacy 注册为模板,直接克隆模板
const existingCode = this.legacyIdToCode.get(enemy.id); const existingCode = this.legacyIdToCode.get(enemy.id);
if (existingCode) { if (existingCode) {
@ -54,21 +45,36 @@ export class EnemyManager implements IEnemyManager {
return this.convertLegacyEnemy(0, enemy); return this.convertLegacyEnemy(0, enemy);
} }
/**
*
* @param enemy
*/
private createAttributes(enemy: Enemy): TAttr {
const attrs: Record<string, any> = {};
for (const [name, defaultValue] of this.attributeRegistry) {
attrs[name] = structuredClone(defaultValue);
}
attrs.hp = enemy.hp;
attrs.atk = enemy.atk;
attrs.def = enemy.def;
attrs.money = enemy.money;
attrs.exp = enemy.exp;
attrs.point = enemy.point;
return attrs as TAttr;
}
/** /**
* *
* @param code * @param code
* @param enemy * @param enemy
*/ */
private convertLegacyEnemy(code: number, enemy: Enemy): IEnemy { private convertLegacyEnemy(code: number, enemy: Enemy): IEnemy<TAttr> {
const attrs: IEnemyAttributes = { const attrs = this.createAttributes(enemy);
hp: enemy.hp, const result = new EnemyImpl<TAttr>(
atk: enemy.atk, enemy.id,
def: enemy.def, code,
money: enemy.money, structuredClone(attrs)
exp: enemy.exp, );
point: enemy.point
};
const result = new EnemyImpl(enemy.id, code, structuredClone(attrs));
// 转换特殊属性 // 转换特殊属性
if (enemy.special) { if (enemy.special) {
@ -84,19 +90,19 @@ export class EnemyManager implements IEnemyManager {
return result; return result;
} }
createEnemy(code: number): IEnemy | null { createEnemy(code: number): IEnemy<TAttr> | null {
const prefab = this.prefabByCode.get(code); const prefab = this.prefabByCode.get(code);
if (!prefab) return null; if (!prefab) return null;
return prefab.clone(); return prefab.clone();
} }
createEnemyById(id: string): IEnemy | null { createEnemyById(id: string): IEnemy<TAttr> | null {
const prefab = this.prefabById.get(id); const prefab = this.prefabById.get(id);
if (!prefab) return null; if (!prefab) return null;
return prefab.clone(); return prefab.clone();
} }
addPrefab(enemy: IEnemy): void { addPrefab(enemy: IEnemy<TAttr>): void {
if ( if (
this.prefabByCode.has(enemy.code) || this.prefabByCode.has(enemy.code) ||
this.prefabById.has(enemy.id) this.prefabById.has(enemy.id)
@ -118,11 +124,11 @@ export class EnemyManager implements IEnemyManager {
this.legacyIdToCode.set(enemy.id, code); this.legacyIdToCode.set(enemy.id, code);
} }
getPrefab(code: number): IEnemy | null { getPrefab(code: number): IEnemy<TAttr> | null {
return this.prefabByCode.get(code) ?? null; return this.prefabByCode.get(code) ?? null;
} }
getPrefabById(id: string): IEnemy | null { getPrefabById(id: string): IEnemy<TAttr> | null {
return this.prefabById.get(id) ?? null; return this.prefabById.get(id) ?? null;
} }
@ -136,7 +142,7 @@ export class EnemyManager implements IEnemyManager {
this.prefabById.delete(prefab.id); this.prefabById.delete(prefab.id);
} }
changePrefab(code: number | string, enemy: IEnemy): void { changePrefab(code: number | string, enemy: IEnemy<TAttr>): void {
// 先删除旧的模板(如果存在) // 先删除旧的模板(如果存在)
this.deletePrefab(code); this.deletePrefab(code);
// 再添加新的模板 // 再添加新的模板

View File

@ -18,25 +18,25 @@ interface IPointInfo {
readonly affectedBy: Set<IMapDamageView<any>>; readonly affectedBy: Set<IMapDamageView<any>>;
} }
interface IViewStore { interface IViewStore<TAttr> {
/** 该地图伤害视图所影响的伤害信息 */ /** 该地图伤害视图所影响的伤害信息 */
readonly damages: Map<number, Readonly<IMapDamageInfo>>; readonly damages: Map<number, Readonly<IMapDamageInfo>>;
/** 当前视图所属的怪物视图 */ /** 当前视图所属的怪物视图 */
readonly enemy: IEnemyView; readonly enemy: IEnemyView<TAttr>;
} }
interface IDamageStore { interface IDamageStore<TAttr> {
/** 该地图伤害信息的地图伤害视图来源 */ /** 该地图伤害信息的地图伤害视图来源 */
readonly sourceView: IMapDamageView<any>; readonly sourceView: IMapDamageView<any>;
/** 地图伤害信息的来源怪物 */ /** 地图伤害信息的来源怪物 */
readonly sourceEnemy: IEnemyView; readonly sourceEnemy: IEnemyView<TAttr>;
/** 该地图伤害信息所处的索引 */ /** 该地图伤害信息所处的索引 */
readonly index: number; readonly index: number;
} }
export class MapDamage implements IMapDamage { export class MapDamage<TAttr> implements IMapDamage<TAttr> {
/** 当前使用的地图伤害转换器 */ /** 当前使用的地图伤害转换器 */
private converter: IMapDamageConverter | null = null; private converter: IMapDamageConverter<TAttr> | null = null;
/** 当前使用的地图伤害合并器 */ /** 当前使用的地图伤害合并器 */
private reducer: IMapDamageReducer | null = null; private reducer: IMapDamageReducer | null = null;
@ -45,23 +45,27 @@ export class MapDamage implements IMapDamage {
/** 有来源地图伤害,坐标 -> 点伤害信息 */ /** 有来源地图伤害,坐标 -> 点伤害信息 */
private readonly sourcedDamage: Map<number, IPointInfo> = new Map(); private readonly sourcedDamage: Map<number, IPointInfo> = new Map();
/** 地图伤害视图 -> 其信息对象 */ /** 地图伤害视图 -> 其信息对象 */
private readonly viewStore: Map<IMapDamageView, IViewStore> = new Map(); private readonly viewStore: Map<IMapDamageView<any>, IViewStore<TAttr>> =
/** 地图伤害信息 -> 其信息对象 */
private readonly damageStore: Map<IMapDamageInfo, IDamageStore> = new Map();
/** 怪物视图 -> 其影响对象 */
private readonly enemyStore: Map<IEnemyView, Set<IMapDamageView>> =
new Map(); new Map();
/** 地图伤害信息 -> 其信息对象 */
private readonly damageStore: Map<IMapDamageInfo, IDamageStore<TAttr>> =
new Map();
/** 怪物视图 -> 其影响对象 */
private readonly enemyStore: Map<
IEnemyView<TAttr>,
Set<IMapDamageView<any>>
> = new Map();
/** 需要延迟刷新的坐标索引 */ /** 需要延迟刷新的坐标索引 */
private readonly dirtyIndexes: Set<number> = new Set(); private readonly dirtyIndexes: Set<number> = new Set();
/** 合并后伤害缓存,索引 -> 合并结果 */ /** 合并后伤害缓存,索引 -> 合并结果 */
private readonly reducedCache: Map<number, IMapDamageInfo> = new Map(); private readonly reducedCache: Map<number, IMapDamageInfo> = new Map();
constructor( constructor(
readonly context: IEnemyContext, readonly context: IEnemyContext<TAttr>,
readonly indexer: IMapLocIndexer readonly indexer: IMapLocIndexer
) {} ) {}
useConverter(converter: IMapDamageConverter): void { useConverter(converter: IMapDamageConverter<TAttr>): void {
this.converter = converter; this.converter = converter;
this.refreshAll(); this.refreshAll();
} }
@ -102,7 +106,7 @@ export class MapDamage implements IMapDamage {
this.markDirtyIndex(this.indexer.locaterToIndex(locator)); this.markDirtyIndex(this.indexer.locaterToIndex(locator));
} }
markEnemyDirty(view: IEnemyView): void { markEnemyDirty(view: IEnemyView<TAttr>): void {
const store = this.enemyStore.get(view); const store = this.enemyStore.get(view);
const locator = this.context.getEnemyLocatorByView(view); const locator = this.context.getEnemyLocatorByView(view);
if (!store) { if (!store) {
@ -117,7 +121,7 @@ export class MapDamage implements IMapDamage {
this.refreshEnemyAndClearCache(view, locator); this.refreshEnemyAndClearCache(view, locator);
} }
deleteEnemy(view: IEnemyView): void { deleteEnemy(view: IEnemyView<TAttr>): void {
const store = this.enemyStore.get(view); const store = this.enemyStore.get(view);
if (!store) return; if (!store) return;
const collection = new Set<number>(); const collection = new Set<number>();
@ -206,7 +210,7 @@ export class MapDamage implements IMapDamage {
* *
* @param view * @param view
*/ */
private removeEnemyAffecting(view: IEnemyView) { private removeEnemyAffecting(view: IEnemyView<TAttr>) {
const views = this.enemyStore.get(view); const views = this.enemyStore.get(view);
if (!views) return; if (!views) return;
views.forEach(viewItem => { views.forEach(viewItem => {
@ -227,7 +231,10 @@ export class MapDamage implements IMapDamage {
/** /**
* *
*/ */
private refreshEnemyAndClearCache(view: IEnemyView, locator: ITileLocator) { private refreshEnemyAndClearCache(
view: IEnemyView<TAttr>,
locator: ITileLocator
) {
this.removeEnemyAffecting(view); this.removeEnemyAffecting(view);
const enemy = view.getComputedEnemy(); const enemy = view.getComputedEnemy();
const views = this.converter!.convert(enemy, locator, this.context); const views = this.converter!.convert(enemy, locator, this.context);
@ -265,7 +272,7 @@ export class MapDamage implements IMapDamage {
/** /**
* *
*/ */
private refreshEnemy(view: IEnemyView, locator: ITileLocator) { private refreshEnemy(view: IEnemyView<TAttr>, locator: ITileLocator) {
this.removeEnemyAffecting(view); this.removeEnemyAffecting(view);
const enemy = view.getComputedEnemy(); const enemy = view.getComputedEnemy();
const views = this.converter!.convert(enemy, locator, this.context); const views = this.converter!.convert(enemy, locator, this.context);

View File

@ -78,18 +78,18 @@ export class NonePropertySpecial implements ISpecial<void> {
} }
} }
export function defineCommonSerializableSpecial<T>( export function defineCommonSerializableSpecial<T, TAttr = any>(
code: number, code: number,
value: T, value: T,
config: ICommonSerializableSpecialConfig<T> config: ICommonSerializableSpecialConfig<T>
): SpecialCreation<T> { ): SpecialCreation<T, TAttr> {
return () => return () =>
new CommonSerializableSpecial(code, structuredClone(value), config); new CommonSerializableSpecial(code, structuredClone(value), config);
} }
export function defineNonePropertySpecial( export function defineNonePropertySpecial<TAttr = any>(
code: number, code: number,
config: ICommonSerializableSpecialConfig<void> config: ICommonSerializableSpecialConfig<void>
): SpecialCreation<void> { ): SpecialCreation<void, TAttr> {
return () => new NonePropertySpecial(code, config); return () => new NonePropertySpecial(code, config);
} }

View File

@ -1,21 +1,6 @@
import { IRange } from '@motajs/common'; import { IRange } from '@motajs/common';
import { ITileLocator } from '@user/types'; import { ITileLocator } from '@user/types';
export interface IEnemyAttributes {
/** 怪物生命值 */
hp: number;
/** 怪物攻击力 */
atk: number;
/** 怪物防御力 */
def: number;
/** 怪物金币 */
money: number;
/** 怪物经验值 */
exp: number;
/** 怪物加点量 */
point: number;
}
export interface ISpecial<T = void> { export interface ISpecial<T = void> {
/** 特殊属性代码 */ /** 特殊属性代码 */
readonly code: number; readonly code: number;
@ -55,7 +40,7 @@ export interface ISpecial<T = void> {
clone(): ISpecial<T>; clone(): ISpecial<T>;
} }
export interface IReadonlyEnemy { export interface IReadonlyEnemy<TAttr> {
/** 怪物标识符 */ /** 怪物标识符 */
readonly id: string; readonly id: string;
/** 怪物在地图上的标识数字 */ /** 怪物在地图上的标识数字 */
@ -82,24 +67,20 @@ export interface IReadonlyEnemy {
* *
* @param key * @param key
*/ */
getAttribute<K extends keyof IEnemyAttributes>(key: K): IEnemyAttributes[K]; getAttribute<K extends keyof TAttr>(key: K): TAttr[K];
/**
*
*/
cloneAttributes(): TAttr;
/** /**
* *
*/ */
clone(): IReadonlyEnemy; clone(): IReadonlyEnemy<TAttr>;
} }
export interface IEnemy extends IReadonlyEnemy { export interface IEnemy<TAttr> extends IReadonlyEnemy<TAttr> {
/** 怪物标识符 */
readonly id: string;
/** 怪物在地图上的标识数字 */
readonly code: number;
/** 怪物属性值 */
readonly attributes: Readonly<IEnemyAttributes>;
/** 怪物拥有的特殊属性列表 */
readonly specials: Set<ISpecial<any>>;
/** /**
* *
* @param special * @param special
@ -117,32 +98,29 @@ export interface IEnemy extends IReadonlyEnemy {
* @param key * @param key
* @param value * @param value
*/ */
setAttribute<K extends keyof IEnemyAttributes>( setAttribute<K extends keyof TAttr>(key: K, value: TAttr[K]): void;
key: K,
value: IEnemyAttributes[K]
): void;
/** /**
* *
*/ */
clone(): IEnemy; clone(): IEnemy<TAttr>;
/** /**
* *
* @param enemy * @param enemy
*/ */
copy(enemy: IReadonlyEnemy): void; copyFrom(enemy: IReadonlyEnemy<TAttr>): void;
} }
export type SpecialCreation<T> = (enemy: IEnemy) => ISpecial<T>; export type SpecialCreation<T, TAttr> = (enemy: IEnemy<TAttr>) => ISpecial<T>;
export interface IEnemyManager { export interface IEnemyManager<TAttr> {
/** /**
* *
* @param code * @param code
* @param cons * @param cons
*/ */
registerSpecial(code: number, cons: SpecialCreation<any>): void; registerSpecial(code: number, cons: SpecialCreation<any, TAttr>): void;
/** /**
* *
@ -155,26 +133,26 @@ export interface IEnemyManager {
* *
* @param enemy * @param enemy
*/ */
fromLegacyEnemy(enemy: Enemy): IEnemy; fromLegacyEnemy(enemy: Enemy): IEnemy<TAttr>;
/** /**
* `null` * `null`
* @param code * @param code
*/ */
createEnemy(code: number): IEnemy | null; createEnemy(code: number): IEnemy<TAttr> | null;
/** /**
* `id` `null` * `id` `null`
* @param id `id` * @param id `id`
*/ */
createEnemyById(id: string): IEnemy | null; createEnemyById(id: string): IEnemy<TAttr> | null;
/** /**
* `id` `code` * `id` `code`
* 使 {@link changePrefab} * 使 {@link changePrefab}
* @param enemy * @param enemy
*/ */
addPrefab(enemy: IEnemy): void; addPrefab(enemy: IEnemy<TAttr>): void;
/** /**
* *
@ -187,13 +165,13 @@ export interface IEnemyManager {
* *
* @param code * @param code
*/ */
getPrefab(code: number): IEnemy | null; getPrefab(code: number): IEnemy<TAttr> | null;
/** /**
* `id` * `id`
* @param id `id` * @param id `id`
*/ */
getPrefabById(id: string): IEnemy | null; getPrefabById(id: string): IEnemy<TAttr> | null;
/** /**
* *
@ -206,7 +184,7 @@ export interface IEnemyManager {
* @param code `id` * @param code `id`
* @param enemy * @param enemy
*/ */
changePrefab(code: number | string, enemy: IEnemy): void; changePrefab(code: number | string, enemy: IEnemy<TAttr>): void;
} }
//#region 辅助接口 //#region 辅助接口
@ -244,9 +222,9 @@ export interface IMapLocIndexer extends IMapLocHelper {
//#region 怪物对象 //#region 怪物对象
export interface IEnemyView { export interface IEnemyView<TAttr> {
/** 怪物视图所属的上下文 */ /** 怪物视图所属的上下文 */
readonly context: IEnemyContext; readonly context: IEnemyContext<TAttr>;
/** /**
* *
@ -256,18 +234,18 @@ export interface IEnemyView {
/** /**
* *
*/ */
getBaseEnemy(): IReadonlyEnemy; getBaseEnemy(): IReadonlyEnemy<TAttr>;
/** /**
* *
*/ */
getComputedEnemy(): IReadonlyEnemy; getComputedEnemy(): IReadonlyEnemy<TAttr>;
/** /**
* *
* markDirty * markDirty
*/ */
getModifiableEnemy(): IEnemy; getModifiableEnemy(): IEnemy<TAttr>;
/** /**
* *
@ -279,20 +257,23 @@ export interface IEnemyView {
//#region 光环与查询 //#region 光环与查询
export interface IEnemySpecialModifier { export interface IEnemySpecialModifier<TAttr> {
/** /**
* *
* @param enemy * @param enemy
* @param locator * @param locator
*/ */
add(enemy: IReadonlyEnemy, locator: ITileLocator): ISpecial<any>[]; add(enemy: IReadonlyEnemy<TAttr>, locator: ITileLocator): ISpecial<any>[];
/** /**
* *
* @param enemy * @param enemy
* @param locator * @param locator
*/ */
delete(enemy: IReadonlyEnemy, locator: ITileLocator): ISpecial<any>[]; delete(
enemy: IReadonlyEnemy<TAttr>,
locator: ITileLocator
): ISpecial<any>[];
/** /**
* true false * true false
@ -301,13 +282,13 @@ export interface IEnemySpecialModifier {
* @param locator * @param locator
*/ */
modify( modify(
enemy: IReadonlyEnemy, enemy: IReadonlyEnemy<TAttr>,
special: ISpecial<any>, special: ISpecial<any>,
locator: ITileLocator locator: ITileLocator
): boolean; ): boolean;
} }
export interface IAuraView<T = any> { export interface IAuraView<TAttr, T = any> {
/** 此光环视图的优先级 */ /** 此光环视图的优先级 */
readonly priority: number; readonly priority: number;
/** 此光环视图的影响范围 */ /** 此光环视图的影响范围 */
@ -326,42 +307,44 @@ export interface IAuraView<T = any> {
/** /**
* *
* @param enemy * @param enemy
* @param baseEnemy
* @param locator * @param locator
*/ */
apply( apply(
enemy: IEnemy, enemy: IEnemy<TAttr>,
baseEnemy: IReadonlyEnemy, baseEnemy: IReadonlyEnemy<TAttr>,
locator: ITileLocator locator: ITileLocator
): void; ): void;
/** /**
* *
* @param enemy * @param enemy
* @param baseEnemy
* @param locator * @param locator
*/ */
applySpecial( applySpecial(
enemy: IReadonlyEnemy, enemy: IReadonlyEnemy<TAttr>,
baseEnemy: IReadonlyEnemy, baseEnemy: IReadonlyEnemy<TAttr>,
locator: ITileLocator locator: ITileLocator
): IEnemySpecialModifier | null; ): IEnemySpecialModifier<TAttr> | null;
} }
export interface IEnemyAuraView<T, S> extends IAuraView<T> { export interface IEnemyAuraView<TAttr, R, S> extends IAuraView<TAttr, R> {
/** 此光环视图所属的怪物 */ /** 此光环视图所属的怪物 */
readonly enemy: IReadonlyEnemy; readonly enemy: IReadonlyEnemy<TAttr>;
/** 此光环视图所属的特殊属性 */ /** 此光环视图所属的特殊属性 */
readonly special: ISpecial<S>; readonly special: ISpecial<S>;
/** 此光环视图所属怪物的定位符 */ /** 此光环视图所属怪物的定位符 */
readonly locator: ITileLocator; readonly locator: ITileLocator;
} }
export interface IAuraConverter { export interface IAuraConverter<TAttr> {
/** /**
* *
*/ */
shouldConvert( shouldConvert(
special: ISpecial<any>, special: ISpecial<any>,
enemy: IReadonlyEnemy, enemy: IReadonlyEnemy<TAttr>,
locator: ITileLocator locator: ITileLocator
): boolean; ): boolean;
@ -370,29 +353,32 @@ export interface IAuraConverter {
*/ */
convert( convert(
special: ISpecial<any>, special: ISpecial<any>,
enemy: IReadonlyEnemy, enemy: IReadonlyEnemy<TAttr>,
locator: ITileLocator locator: ITileLocator,
): IEnemyAuraView<any, any>; context: IEnemyContext<TAttr>
): IEnemyAuraView<TAttr, any, any>;
} }
export interface IEnemySpecialQueryModifier extends IEnemySpecialModifier { export interface IEnemySpecialQueryModifier<
TAttr
> extends IEnemySpecialModifier<TAttr> {
/** /**
* *
*/ */
shouldQuery(enemy: IReadonlyEnemy, locator: ITileLocator): boolean; shouldQuery(enemy: IReadonlyEnemy<TAttr>, locator: ITileLocator): boolean;
} }
export interface IEnemySpecialQueryEffect { export interface IEnemySpecialQueryEffect<TAttr> {
/** 效果优先级,与光环属性共用 */ /** 效果优先级,与光环属性共用 */
readonly priority: number; readonly priority: number;
/** /**
* *
*/ */
for(ctx: IEnemyContext): IEnemySpecialQueryModifier; for(ctx: IEnemyContext<TAttr>): IEnemySpecialQueryModifier<TAttr>;
} }
export interface IEnemyCommonQueryEffect { export interface IEnemyCommonQueryEffect<TAttr> {
/** 优先级,越高的越先执行 */ /** 优先级,越高的越先执行 */
readonly priority: number; readonly priority: number;
@ -400,21 +386,21 @@ export interface IEnemyCommonQueryEffect {
* *
*/ */
apply( apply(
enemy: IEnemy, enemy: IEnemy<TAttr>,
special: ISpecial<any>, special: ISpecial<any>,
query: () => IEnemyContext, query: () => IEnemyContext<TAttr>,
locator: ITileLocator locator: ITileLocator
): void; ): void;
} }
export interface IEnemyFinalEffect { export interface IEnemyFinalEffect<TAttr> {
/** 效果优先级,越高会越先被执行 */ /** 效果优先级,越高会越先被执行 */
readonly priority: number; readonly priority: number;
/** /**
* *
*/ */
apply(enemy: IEnemy, locator: ITileLocator): void; apply(enemy: IEnemy<TAttr>, locator: ITileLocator): void;
} }
//#endregion //#endregion
@ -438,10 +424,14 @@ export interface IMapDamageInfo {
} }
export interface IMapDamageView<T = any> { export interface IMapDamageView<T = any> {
/** 获取地图伤害影响范围 */ /**
*
*/
getRange(): IRange<T>; getRange(): IRange<T>;
/** 获取范围参数 */ /**
*
*/
getRangeParam(): T; getRangeParam(): T;
/** /**
@ -451,7 +441,7 @@ export interface IMapDamageView<T = any> {
getDamageAt(locator: ITileLocator): Readonly<IMapDamageInfo> | null; getDamageAt(locator: ITileLocator): Readonly<IMapDamageInfo> | null;
/** /**
* *
* @param locator * @param locator
*/ */
getDamageWithoutCheck( getDamageWithoutCheck(
@ -459,32 +449,36 @@ export interface IMapDamageView<T = any> {
): Readonly<IMapDamageInfo> | null; ): Readonly<IMapDamageInfo> | null;
} }
export interface IMapDamageConverter { export interface IMapDamageConverter<TAttr> {
/** 转换地图伤害视图 */ /**
*
*/
convert( convert(
enemy: IReadonlyEnemy, enemy: IReadonlyEnemy<TAttr>,
locator: ITileLocator, locator: ITileLocator,
context: IEnemyContext context: IEnemyContext<TAttr>
): IMapDamageView<any>[]; ): IMapDamageView<any>[];
} }
export interface IMapDamageReducer { export interface IMapDamageReducer {
/** 对伤害信息进行合并 */ /**
*
*/
reduce( reduce(
info: Iterable<Readonly<IMapDamageInfo>>, info: Iterable<Readonly<IMapDamageInfo>>,
locator: ITileLocator locator: ITileLocator
): Readonly<IMapDamageInfo>; ): Readonly<IMapDamageInfo>;
} }
export interface IMapDamage { export interface IMapDamage<TAttr> {
/** 当前绑定的怪物上下文 */ /** 当前绑定的怪物上下文 */
readonly context: IEnemyContext; readonly context: IEnemyContext<TAttr>;
/** /**
* *
* @param converter * @param converter
*/ */
useConverter(converter: IMapDamageConverter): void; useConverter(converter: IMapDamageConverter<TAttr>): void;
/** /**
* *
@ -516,7 +510,7 @@ export interface IMapDamage {
* *
* @param view * @param view
*/ */
markEnemyDirty(view: IEnemyView): void; markEnemyDirty(view: IEnemyView<TAttr>): void;
/** /**
* *
@ -527,7 +521,7 @@ export interface IMapDamage {
* *
* @param view * @param view
*/ */
deleteEnemy(view: IEnemyView): void; deleteEnemy(view: IEnemyView<TAttr>): void;
/** /**
* *
@ -548,7 +542,7 @@ export interface IMapDamage {
//#region 上下文 //#region 上下文
export interface IEnemyContext { export interface IEnemyContext<TAttr> {
/** 怪物上下文宽度 */ /** 怪物上下文宽度 */
readonly width: number; readonly width: number;
/** 怪物上下文高度 */ /** 怪物上下文高度 */
@ -565,32 +559,35 @@ export interface IEnemyContext {
* *
* @param converter * @param converter
*/ */
registerAuraConverter(converter: IAuraConverter): void; registerAuraConverter(converter: IAuraConverter<TAttr>): void;
/** /**
* *
* @param converter * @param converter
*/ */
unregisterAuraConverter(converter: IAuraConverter): void; unregisterAuraConverter(converter: IAuraConverter<TAttr>): void;
/** /**
* *
* @param converter * @param converter
* @param enabled * @param enabled
*/ */
setAuraConverterEnabled(converter: IAuraConverter, enabled: boolean): void; setAuraConverterEnabled(
converter: IAuraConverter<TAttr>,
enabled: boolean
): void;
/** /**
* *
* @param effect * @param effect
*/ */
registerSpecialQueryEffect(effect: IEnemySpecialQueryEffect): void; registerSpecialQueryEffect(effect: IEnemySpecialQueryEffect<TAttr>): void;
/** /**
* *
* @param effect * @param effect
*/ */
unregisterSpecialQueryEffect(effect: IEnemySpecialQueryEffect): void; unregisterSpecialQueryEffect(effect: IEnemySpecialQueryEffect<TAttr>): void;
/** /**
* *
@ -599,7 +596,7 @@ export interface IEnemyContext {
*/ */
registerCommonQueryEffect( registerCommonQueryEffect(
code: number, code: number,
effect: IEnemyCommonQueryEffect effect: IEnemyCommonQueryEffect<TAttr>
): void; ): void;
/** /**
@ -609,58 +606,60 @@ export interface IEnemyContext {
*/ */
unregisterCommonQueryEffect( unregisterCommonQueryEffect(
code: number, code: number,
effect: IEnemyCommonQueryEffect effect: IEnemyCommonQueryEffect<TAttr>
): void; ): void;
/** /**
* *
* @param effect * @param effect
*/ */
registerFinalEffect(effect: IEnemyFinalEffect): void; registerFinalEffect(effect: IEnemyFinalEffect<TAttr>): void;
/** /**
* *
* @param effect * @param effect
*/ */
unregisterFinalEffect(effect: IEnemyFinalEffect): void; unregisterFinalEffect(effect: IEnemyFinalEffect<TAttr>): void;
/** /**
* *
* @param enemy * @param enemy
*/ */
getEnemyLocator(enemy: IEnemy): Readonly<ITileLocator> | null; getEnemyLocator(enemy: IEnemy<TAttr>): Readonly<ITileLocator> | null;
/** /**
* *
* @param view * @param view
*/ */
getEnemyLocatorByView(view: IEnemyView): Readonly<ITileLocator> | null; getEnemyLocatorByView(
view: IEnemyView<TAttr>
): Readonly<ITileLocator> | null;
/** /**
* *
* @param locator * @param locator
*/ */
getEnemyByLocator(locator: ITileLocator): IEnemyView | null; getEnemyByLocator(locator: ITileLocator): IEnemyView<TAttr> | null;
/** /**
* *
* @param x * @param x
* @param y * @param y
*/ */
getEnemyByLoc(x: number, y: number): IEnemyView | null; getEnemyByLoc(x: number, y: number): IEnemyView<TAttr> | null;
/** /**
* *
* @param enemy * @param enemy
*/ */
getViewByComputed(enemy: IReadonlyEnemy): IEnemyView | null; getViewByComputed(enemy: IReadonlyEnemy<TAttr>): IEnemyView<TAttr> | null;
/** /**
* *
* @param locator * @param locator
* @param enemy * @param enemy
*/ */
setEnemyAt(locator: ITileLocator, enemy: IEnemy): void; setEnemyAt(locator: ITileLocator, enemy: IEnemy<TAttr>): void;
/** /**
* *
@ -673,35 +672,35 @@ export interface IEnemyContext {
* @param range * @param range
* @param param * @param param
*/ */
scanRange<T>(range: IRange<T>, param: T): Iterable<IEnemyView>; scanRange<T>(range: IRange<T>, param: T): Iterable<IEnemyView<TAttr>>;
/** /**
* *
*/ */
iterateEnemy(): Iterable<[ITileLocator, IEnemyView]>; iterateEnemy(): Iterable<[ITileLocator, IEnemyView<TAttr>]>;
/** /**
* *
* @param aura * @param aura
*/ */
addAura(aura: IAuraView): void; addAura(aura: IAuraView<TAttr>): void;
/** /**
* *
* @param aura * @param aura
*/ */
deleteAura(aura: IAuraView): void; deleteAura(aura: IAuraView<TAttr>): void;
/** /**
* *
* @param damage * @param damage
*/ */
attachMapDamage(damage: IMapDamage | null): void; attachMapDamage(damage: IMapDamage<TAttr> | null): void;
/** /**
* *
*/ */
getMapDamage(): IMapDamage | null; getMapDamage(): IMapDamage<TAttr> | null;
/** /**
* *
@ -717,13 +716,13 @@ export interface IEnemyContext {
* *
* @param view * @param view
*/ */
markDirty(view: IEnemyView): void; markDirty(view: IEnemyView<TAttr>): void;
/** /**
* *
* @param view * @param view
*/ */
requestRefresh(view: IEnemyView): void; requestRefresh(view: IEnemyView<TAttr>): void;
/** /**
* *

View File

@ -1,12 +1,13 @@
import { EnemyManager, IEnemyManager } from '@user/data-base'; import { EnemyManager, IEnemyManager } from '@user/data-base';
import { IEnemyAttributes } from './enemy/types';
import { IGameDataState } from './types'; import { IGameDataState } from './types';
import { registerSpecials } from './enemy'; import { registerSpecials } from './enemy';
export class GameDataState implements IGameDataState { export class GameDataState implements IGameDataState {
readonly enemyManager: IEnemyManager; readonly enemyManager: IEnemyManager<IEnemyAttributes>;
constructor() { constructor() {
this.enemyManager = new EnemyManager(); this.enemyManager = new EnemyManager<IEnemyAttributes>();
registerSpecials(this.enemyManager); registerSpecials(this.enemyManager);
} }
} }

View File

@ -9,13 +9,16 @@ import {
import { import {
IAuraConverter, IAuraConverter,
IEnemyAuraView, IEnemyAuraView,
IEnemyContext,
IEnemySpecialModifier, IEnemySpecialModifier,
IEnemyView,
IReadonlyEnemy, IReadonlyEnemy,
ISpecial, ISpecial,
IEnemy IEnemy
} from '@user/data-base'; } from '@user/data-base';
import { IHaloValue } from './special'; import { IHaloValue } from './special';
import { ITileLocator } from '@user/types'; import { ITileLocator } from '@user/types';
import { IEnemyAttributes } from './types';
const FULL_RANGE = new FullRange(); const FULL_RANGE = new FullRange();
const RECT_RANGE = new RectRange(); const RECT_RANGE = new RectRange();
@ -23,32 +26,32 @@ const MANHATTAN_RANGE = new ManhattanRange();
//#region 25-光环 //#region 25-光环
export class CommonAuraConverter implements IAuraConverter { export class CommonAuraConverter implements IAuraConverter<IEnemyAttributes> {
shouldConvert(special: ISpecial<any>): boolean { shouldConvert(special: ISpecial<any>): boolean {
return special.code === 25; return special.code === 25;
} }
convert( convert(
special: ISpecial<any>, special: ISpecial<any>,
enemy: IReadonlyEnemy, enemy: IReadonlyEnemy<IEnemyAttributes>,
locator: ITileLocator locator: ITileLocator
): IEnemyAuraView<any, any> { ): CommonAura {
return new CommonAura(enemy, special as ISpecial<IHaloValue>, locator); return new CommonAura(enemy, special as ISpecial<IHaloValue>, locator);
} }
} }
export class CommonAura implements IEnemyAuraView< export class CommonAura implements IEnemyAuraView<
IEnemyAttributes,
IRectRangeParam | IManhattanRangeParam | void, IRectRangeParam | IManhattanRangeParam | void,
IHaloValue IHaloValue
> { > {
readonly priority: number = 25; readonly priority: number = 25;
readonly couldApplyBase: boolean = true; readonly couldApplyBase: boolean = true;
readonly couldApplySpecial: boolean = false; readonly couldApplySpecial: boolean = false;
readonly range: IRange<IRectRangeParam | IManhattanRangeParam | void>; readonly range: IRange<IRectRangeParam | IManhattanRangeParam | void>;
constructor( constructor(
readonly enemy: IReadonlyEnemy, readonly enemy: IReadonlyEnemy<IEnemyAttributes>,
readonly special: ISpecial<IHaloValue>, readonly special: ISpecial<IHaloValue>,
readonly locator: ITileLocator readonly locator: ITileLocator
) { ) {
@ -85,7 +88,10 @@ export class CommonAura implements IEnemyAuraView<
}; };
} }
apply(enemy: IEnemy, baseEnemy: IReadonlyEnemy): void { apply(
enemy: IEnemy<IEnemyAttributes>,
baseEnemy: IReadonlyEnemy<IEnemyAttributes>
): void {
const { hpBuff, atkBuff, defBuff } = this.special.value; const { hpBuff, atkBuff, defBuff } = this.special.value;
if (hpBuff !== 0) { if (hpBuff !== 0) {
@ -113,7 +119,78 @@ export class CommonAura implements IEnemyAuraView<
} }
} }
applySpecial(): IEnemySpecialModifier | null { applySpecial(): IEnemySpecialModifier<IEnemyAttributes> | null {
return null;
}
}
//#endregion
//#region 26-支援
export class GuardAuraConverter implements IAuraConverter<IEnemyAttributes> {
shouldConvert(special: ISpecial<any>): boolean {
return special.code === 26;
}
convert(
special: ISpecial<any>,
enemy: IReadonlyEnemy<IEnemyAttributes>,
locator: ITileLocator,
context: IEnemyContext<IEnemyAttributes>
): GuardAura {
return new GuardAura(
context,
enemy,
special as ISpecial<void>,
locator
);
}
}
export class GuardAura implements IEnemyAuraView<
IEnemyAttributes,
IRectRangeParam,
void
> {
readonly priority: number = 26;
readonly couldApplyBase: boolean = true;
readonly couldApplySpecial: boolean = false;
readonly range: IRange<IRectRangeParam> = RECT_RANGE;
private readonly sourceView: IEnemyView<IEnemyAttributes> | null;
constructor(
context: IEnemyContext<IEnemyAttributes>,
readonly enemy: IReadonlyEnemy<IEnemyAttributes>,
readonly special: ISpecial<void>,
readonly locator: ITileLocator
) {
this.sourceView = context.getViewByComputed(enemy);
}
getRangeParam(): IRectRangeParam {
return {
x: this.locator.x - 1,
y: this.locator.y - 1,
w: 3,
h: 3
};
}
apply(
enemy: IEnemy<IEnemyAttributes>,
_baseEnemy: IReadonlyEnemy<IEnemyAttributes>,
locator: ITileLocator
): void {
if (!this.sourceView) return;
if (locator.x === this.locator.x && locator.y === this.locator.y) {
return;
}
enemy.getAttribute('guard').add(this.sourceView);
}
applySpecial(): IEnemySpecialModifier<IEnemyAttributes> | null {
return null; return null;
} }
} }

View File

@ -21,7 +21,7 @@ import {
IMapDamageView IMapDamageView
} from '@user/data-base'; } from '@user/data-base';
import { IZoneValue } from './special'; import { IZoneValue } from './special';
import { MapDamageType } from './types'; import { IEnemyAttributes, MapDamageType } from './types';
const RECT_RANGE = new RectRange(); const RECT_RANGE = new RectRange();
const MANHATTAN_RANGE = new ManhattanRange(); const MANHATTAN_RANGE = new ManhattanRange();
@ -33,7 +33,7 @@ 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) {} constructor(protected readonly context: IEnemyContext<IEnemyAttributes>) {}
abstract getRange(): IRange<T>; abstract getRange(): IRange<T>;
@ -94,7 +94,7 @@ export class ZoneDamageView extends BaseMapDamageView<
IRectRangeParam | IManhattanRangeParam IRectRangeParam | IManhattanRangeParam
> { > {
constructor( constructor(
context: IEnemyContext, context: IEnemyContext<IEnemyAttributes>,
private readonly locator: Readonly<ITileLocator>, private readonly locator: Readonly<ITileLocator>,
private readonly special: Readonly<ISpecial<IZoneValue>> private readonly special: Readonly<ISpecial<IZoneValue>>
) { ) {
@ -131,7 +131,7 @@ export class ZoneDamageView extends BaseMapDamageView<
export class RepulseDamageView extends BaseMapDamageView<IManhattanRangeParam> { export class RepulseDamageView extends BaseMapDamageView<IManhattanRangeParam> {
constructor( constructor(
context: IEnemyContext, context: IEnemyContext<IEnemyAttributes>,
private readonly locator: Readonly<ITileLocator>, private readonly locator: Readonly<ITileLocator>,
private readonly special: Readonly<ISpecial<number>> private readonly special: Readonly<ISpecial<number>>
) { ) {
@ -165,7 +165,7 @@ export class RepulseDamageView extends BaseMapDamageView<IManhattanRangeParam> {
export class LaserDamageView extends BaseMapDamageView<IRayRangeParam> { export class LaserDamageView extends BaseMapDamageView<IRayRangeParam> {
constructor( constructor(
context: IEnemyContext, context: IEnemyContext<IEnemyAttributes>,
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
@ -200,7 +200,7 @@ export class BetweenDamageView extends BaseMapDamageView<IManhattanRangeParam> {
private static readonly DAMAGE = 1; private static readonly DAMAGE = 1;
constructor( constructor(
context: IEnemyContext, context: IEnemyContext<IEnemyAttributes>,
private readonly locator: Readonly<ITileLocator> private readonly locator: Readonly<ITileLocator>
) { ) {
super(context); super(context);
@ -250,7 +250,7 @@ export class BetweenDamageView extends BaseMapDamageView<IManhattanRangeParam> {
export class AmbushDamageView extends BaseMapDamageView<IManhattanRangeParam> { export class AmbushDamageView extends BaseMapDamageView<IManhattanRangeParam> {
constructor( constructor(
context: IEnemyContext, context: IEnemyContext<IEnemyAttributes>,
private readonly locator: Readonly<ITileLocator> private readonly locator: Readonly<ITileLocator>
) { ) {
super(context); super(context);
@ -285,11 +285,11 @@ export class AmbushDamageView extends BaseMapDamageView<IManhattanRangeParam> {
//#region 转换器 //#region 转换器
export class MainMapDamageConverter implements IMapDamageConverter { export class MainMapDamageConverter implements IMapDamageConverter<IEnemyAttributes> {
convert( convert(
enemy: IReadonlyEnemy, enemy: IReadonlyEnemy<IEnemyAttributes>,
locator: ITileLocator, locator: ITileLocator,
context: IEnemyContext context: IEnemyContext<IEnemyAttributes>
): IMapDamageView<any>[] { ): IMapDamageView<any>[] {
const views: IMapDamageView<any>[] = []; const views: IMapDamageView<any>[] = [];

View File

@ -4,6 +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';
//#region 复合属性值类型 //#region 复合属性值类型
@ -47,7 +48,11 @@ 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(manager: IEnemyManager): void { export function registerSpecials(
manager: IEnemyManager<IEnemyAttributes>
): void {
manager.registerAttribute('guard', new Set());
// 0 - 空 // 0 - 空
manager.registerSpecial( manager.registerSpecial(
0, 0,

View File

@ -1,3 +1,22 @@
import { IEnemyView } from '@user/data-base';
export interface IEnemyAttributes {
/** 怪物生命值 */
hp: number;
/** 怪物攻击力 */
atk: number;
/** 怪物防御力 */
def: number;
/** 怪物金币 */
money: number;
/** 怪物经验值 */
exp: number;
/** 怪物加点量 */
point: number;
/** 支援来源怪物视图列表 */
guard: Set<IEnemyView<IEnemyAttributes>>;
}
export const enum MapDamageType { export const enum MapDamageType {
/** 未知伤害 */ /** 未知伤害 */
Unknown, Unknown,

View File

@ -2,10 +2,11 @@ import { ILayerState } from './map';
import { IHeroFollower, IHeroState } from './hero'; import { IHeroFollower, IHeroState } from './hero';
import { IRoleFaceBinder } from './common'; import { IRoleFaceBinder } from './common';
import { IEnemyManager } from '@user/data-base'; import { IEnemyManager } from '@user/data-base';
import { IEnemyAttributes } from './enemy/types';
export interface IGameDataState { export interface IGameDataState {
/** 怪物管理器 */ /** 怪物管理器 */
readonly enemyManager: IEnemyManager; readonly enemyManager: IEnemyManager<IEnemyAttributes>;
} }
export interface IStateSaveData { export interface IStateSaveData {