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 { ITileLocator } from '@user/types';
import {
IAuraConverter,
IAuraView,
@ -16,36 +17,42 @@ import {
} from './types';
import { EnemyView } from './enemy';
import { MapLocIndexer } from './utils';
import { ITileLocator } from '@user/types';
export class EnemyContext implements IEnemyContext {
private readonly enemyViewMap: Map<number, EnemyView> = new Map();
private readonly enemyMap: Map<number, IEnemy> = new Map();
private readonly locatorViewMap: Map<IEnemyView, number> = new Map();
private readonly locatorEnemyMap: Map<IEnemy, number> = new Map();
private readonly computedToView: Map<IReadonlyEnemy, EnemyView> = new Map();
export class EnemyContext<TAttr> implements IEnemyContext<TAttr> {
private readonly enemyViewMap: Map<number, EnemyView<TAttr>> = new Map();
private readonly enemyMap: Map<number, IEnemy<TAttr>> = new Map();
private readonly locatorViewMap: Map<IEnemyView<TAttr>, number> = new Map();
private readonly locatorEnemyMap: Map<IEnemy<TAttr>, number> = new Map();
private readonly computedToView: Map<
IReadonlyEnemy<TAttr>,
EnemyView<TAttr>
> = new Map();
private readonly auraConverter: Set<IAuraConverter> = new Set();
private readonly converterStatus: Map<IAuraConverter, boolean> = new Map();
private readonly convertedAura: Map<ISpecial<any>, IAuraView> = new Map();
private readonly commonQueryMap: Map<number, IEnemyCommonQueryEffect[]> =
private readonly auraConverter: Set<IAuraConverter<TAttr>> = new Set();
private readonly converterStatus: Map<IAuraConverter<TAttr>, boolean> =
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<
number,
IEnemySpecialQueryEffect[]
IEnemySpecialQueryEffect<TAttr>[]
> = new Map();
private readonly finalEffects: IEnemyFinalEffect[] = [];
private readonly globalAuraList: Set<IAuraView> = new Set();
private readonly sortedAura: Map<number, Set<IAuraView>> = new Map();
private readonly finalEffects: IEnemyFinalEffect<TAttr>[] = [];
private readonly globalAuraList: Set<IAuraView<TAttr>> = new Set();
private readonly sortedAura: Map<number, Set<IAuraView<TAttr>>> = new Map();
private readonly needTotallyRefresh: Set<IEnemyView> = new Set();
private readonly requestedCommonContext: Set<IEnemyView> = new Set();
private readonly dirtyEnemy: Set<IEnemyView> = new Set();
private readonly needTotallyRefresh: Set<IEnemyView<TAttr>> = new Set();
private readonly requestedCommonContext: Set<IEnemyView<TAttr>> = 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();
private needUpdate: boolean = true;
@ -61,24 +68,27 @@ export class EnemyContext implements IEnemyContext {
this.indexer.setWidth(width);
}
registerAuraConverter(converter: IAuraConverter): void {
registerAuraConverter(converter: IAuraConverter<TAttr>): void {
this.auraConverter.add(converter);
this.converterStatus.set(converter, true);
}
unregisterAuraConverter(converter: IAuraConverter): void {
unregisterAuraConverter(converter: IAuraConverter<TAttr>): void {
this.auraConverter.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;
this.converterStatus.set(converter, enabled);
}
registerCommonQueryEffect(
code: number,
effect: IEnemyCommonQueryEffect
effect: IEnemyCommonQueryEffect<TAttr>
): void {
const array = this.commonQueryMap.getOrInsert(code, []);
array.push(effect);
@ -87,7 +97,7 @@ export class EnemyContext implements IEnemyContext {
unregisterCommonQueryEffect(
code: number,
effect: IEnemyCommonQueryEffect
effect: IEnemyCommonQueryEffect<TAttr>
): void {
const array = this.commonQueryMap.get(code);
if (!array) return;
@ -96,12 +106,14 @@ export class EnemyContext implements IEnemyContext {
array.splice(index, 1);
}
registerSpecialQueryEffect(effect: IEnemySpecialQueryEffect): void {
registerSpecialQueryEffect(effect: IEnemySpecialQueryEffect<TAttr>): void {
const list = this.specialQueryEffects.getOrInsert(effect.priority, []);
list.push(effect);
}
unregisterSpecialQueryEffect(effect: IEnemySpecialQueryEffect): void {
unregisterSpecialQueryEffect(
effect: IEnemySpecialQueryEffect<TAttr>
): void {
const list = this.specialQueryEffects.get(effect.priority);
if (!list) return;
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.sort((a, b) => b.priority - a.priority);
}
unregisterFinalEffect(effect: IEnemyFinalEffect): void {
unregisterFinalEffect(effect: IEnemyFinalEffect<TAttr>): void {
const index = this.finalEffects.indexOf(effect);
if (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);
if (index === undefined) return null;
return this.indexer.indexToLocator(index);
}
getEnemyLocatorByView(view: IEnemyView): Readonly<ITileLocator> | null {
getEnemyLocatorByView(
view: IEnemyView<TAttr>
): Readonly<ITileLocator> | null {
const index = this.locatorViewMap.get(view);
if (index === undefined) return null;
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);
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);
return this.enemyViewMap.get(index) ?? null;
}
getViewByComputed(enemy: IReadonlyEnemy): IEnemyView | null {
getViewByComputed(enemy: IReadonlyEnemy<TAttr>): IEnemyView<TAttr> | null {
return this.computedToView.get(enemy) ?? null;
}
@ -172,11 +186,11 @@ export class EnemyContext implements IEnemyContext {
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);
this.deleteEnemyAt(index);
const view = new EnemyView(enemy, this);
const view = new EnemyView<TAttr>(enemy, this);
this.enemyMap.set(index, enemy);
this.enemyViewMap.set(index, view);
this.locatorEnemyMap.set(enemy, index);
@ -194,7 +208,7 @@ export class EnemyContext implements IEnemyContext {
private *internalScanRange<T>(
range: IRange<T>,
param: T
): Iterable<EnemyView> {
): Iterable<EnemyView<TAttr>> {
range.bindHost(this);
const keys = new Set(this.enemyViewMap.keys());
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);
}
*iterateEnemy(): Iterable<[ITileLocator, IEnemyView]> {
*iterateEnemy(): Iterable<[ITileLocator, IEnemyView<TAttr>]> {
for (const [index, view] of this.enemyViewMap) {
const locator = this.indexer.indexToLocator(index);
yield [locator, view];
}
}
addAura(aura: IAuraView): void {
addAura(aura: IAuraView<TAttr>): void {
this.globalAuraList.add(aura);
this.needUpdate = true;
}
deleteAura(aura: IAuraView): void {
deleteAura(aura: IAuraView<TAttr>): void {
this.globalAuraList.delete(aura);
this.needUpdate = true;
}
attachMapDamage(damage: IMapDamage | null): void {
attachMapDamage(damage: IMapDamage<TAttr> | null): void {
this.mapDamage = damage;
if (damage) {
damage.refreshAll();
}
}
getMapDamage(): IMapDamage | null {
getMapDamage(): IMapDamage<TAttr> | null {
return this.mapDamage;
}
private convertSpecial(
special: ISpecial<any>,
enemy: IReadonlyEnemy,
enemy: IReadonlyEnemy<TAttr>,
locator: ITileLocator
): IEnemyAuraView<any, any> | null {
let matched: IAuraConverter | null = null;
): IEnemyAuraView<TAttr, any, any> | null {
let matched: IAuraConverter<TAttr> | null = null;
for (const converter of this.auraConverter) {
if (!this.converterStatus.get(converter)) continue;
@ -258,10 +272,10 @@ export class EnemyContext implements IEnemyContext {
}
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(
aura.priority,
() => new Set()
@ -269,7 +283,7 @@ export class EnemyContext implements IEnemyContext {
set.add(aura);
}
private removeFromSortedAura(aura: IAuraView): void {
private removeFromSortedAura(aura: IAuraView<TAttr>): void {
const set = this.sortedAura.get(aura.priority);
if (set) {
set.delete(aura);
@ -280,15 +294,15 @@ export class EnemyContext implements IEnemyContext {
}
private processSpecialModifier(
modifier: IEnemySpecialModifier,
enemy: IEnemy,
modifier: IEnemySpecialModifier<TAttr>,
enemy: IEnemy<TAttr>,
locator: ITileLocator,
currentPriority: number
): Set<IAuraView> {
): Set<IAuraView<TAttr>> {
const toAdd = modifier.add(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) {
logger.warn(100);
@ -351,7 +365,7 @@ export class EnemyContext implements IEnemyContext {
}
private processSpecialQuery(
effect: IEnemySpecialQueryEffect,
effect: IEnemySpecialQueryEffect<TAttr>,
currentPriority: number
): void {
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();
for (const enemyView of this.internalScanRange(aura.range, param)) {
@ -410,7 +427,7 @@ export class EnemyContext implements IEnemyContext {
const enemy = view.getComputingEnemy();
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);
if (!aura) continue;
this.convertedAura.set(special, aura);
@ -484,7 +501,7 @@ export class EnemyContext implements IEnemyContext {
queried = true;
return this;
};
for (const special of enemy.specials) {
for (const special of enemy.iterateSpecials()) {
const effects = this.commonQueryMap.get(special.code);
if (!effects) continue;
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;
this.dirtyEnemy.add(view);
}
private refreshSpecialModifier(
modifier: IEnemySpecialModifier,
enemy: IEnemy,
modifier: IEnemySpecialModifier<TAttr>,
enemy: IEnemy<TAttr>,
locator: ITileLocator
): void {
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);
if (!locator) return;
@ -653,7 +670,7 @@ export class EnemyContext implements IEnemyContext {
queried = true;
return this;
};
for (const special of enemy.specials) {
for (const special of enemy.iterateSpecials()) {
const effects = this.commonQueryMap.get(special.code);
if (!effects) continue;
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.needTotallyRefresh.has(view)) {
this.needUpdate = true;
@ -685,11 +702,11 @@ export class EnemyContext implements IEnemyContext {
return;
}
this.refreshEnemy(view as EnemyView);
this.refreshEnemy(view as EnemyView<TAttr>);
for (const requestedView of this.requestedCommonContext) {
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 {
IEnemy,
IEnemyAttributes,
IEnemyContext,
IReadonlyEnemy,
ISpecial,
IEnemyView
} from './types';
export class Enemy implements IEnemy {
export class Enemy<TAttr> implements IEnemy<TAttr> {
readonly specials: Set<ISpecial<any>> = new Set();
/** code -> ISpecial 映射,用于快速查找 */
private readonly specialMap: Map<number, ISpecial<any>> = new Map();
@ -16,7 +15,7 @@ export class Enemy implements IEnemy {
constructor(
readonly id: string,
readonly code: number,
readonly attributes: IEnemyAttributes
private attributes: TAttr
) {}
getSpecial<T>(code: number): ISpecial<T> | null {
@ -48,21 +47,20 @@ export class Enemy implements IEnemy {
return this.specials;
}
setAttribute<K extends keyof IEnemyAttributes>(
key: K,
value: IEnemyAttributes[K]
): void {
setAttribute<K extends keyof TAttr>(key: K, value: TAttr[K]): void {
this.attributes[key] = value;
}
getAttribute<K extends keyof IEnemyAttributes>(
key: K
): IEnemyAttributes[K] {
getAttribute<K extends keyof TAttr>(key: K): TAttr[K] {
return this.attributes[key];
}
clone(): IEnemy {
const cloned = new Enemy(
cloneAttributes(): TAttr {
return structuredClone(this.attributes);
}
clone(): IEnemy<TAttr> {
const cloned = new Enemy<TAttr>(
this.id,
this.code,
structuredClone(this.attributes)
@ -73,10 +71,8 @@ export class Enemy implements IEnemy {
return cloned;
}
copy(enemy: IReadonlyEnemy): void {
ATTRIBUTE_KEYS.forEach(key => {
this.setAttribute(key, structuredClone(enemy.getAttribute(key)));
});
copyFrom(enemy: IReadonlyEnemy<TAttr>): void {
this.attributes = enemy.cloneAttributes();
this.specials.clear();
this.specialMap.clear();
for (const special of enemy.iterateSpecials()) {
@ -85,25 +81,25 @@ export class Enemy implements IEnemy {
}
}
export class EnemyView implements IEnemyView {
private computedEnemy: IEnemy;
export class EnemyView<TAttr> implements IEnemyView<TAttr> {
private computedEnemy: IEnemy<TAttr>;
constructor(
readonly baseEnemy: IEnemy,
readonly context: IEnemyContext
readonly baseEnemy: IEnemy<TAttr>,
readonly context: IEnemyContext<TAttr>
) {
this.computedEnemy = baseEnemy.clone();
}
reset(): void {
this.computedEnemy.copy(this.baseEnemy);
this.computedEnemy.copyFrom(this.baseEnemy);
}
getBaseEnemy(): IReadonlyEnemy {
getBaseEnemy(): IReadonlyEnemy<TAttr> {
return this.baseEnemy;
}
getComputedEnemy(): IReadonlyEnemy {
getComputedEnemy(): IReadonlyEnemy<TAttr> {
this.context.requestRefresh(this);
return this.computedEnemy;
}
@ -111,11 +107,11 @@ export class EnemyView implements IEnemyView {
/**
* EnemyContext 使
*/
getComputingEnemy(): IEnemy {
getComputingEnemy(): IEnemy<TAttr> {
return this.computedEnemy;
}
getModifiableEnemy(): IEnemy {
getModifiableEnemy(): IEnemy<TAttr> {
return this.baseEnemy;
}
@ -123,12 +119,3 @@ export class EnemyView implements IEnemyView {
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 { Enemy as EnemyImpl } from './enemy';
import {
IEnemy,
IEnemyAttributes,
IEnemyManager,
ISpecial,
SpecialCreation
} from './types';
import { IEnemy, IEnemyManager, SpecialCreation } from './types';
export class EnemyManager implements IEnemyManager {
export class EnemyManager<TAttr> implements IEnemyManager<TAttr> {
/** 特殊属性注册表code -> 创建函数 */
private readonly specialRegistry: Map<number, SpecialCreation<any>> =
private readonly specialRegistry: Map<number, SpecialCreation<any, TAttr>> =
new Map();
/** 自定义怪物属性注册表name -> 默认值 */
private readonly attributeRegistry: Map<string, any> = new Map();
/** 怪物模板表code -> IEnemy */
private readonly prefabByCode: Map<number, IEnemy> = new Map();
private readonly prefabByCode: Map<number, IEnemy<TAttr>> = new Map();
/** 怪物模板表id -> IEnemy */
private readonly prefabById: Map<string, IEnemy> = new Map();
private readonly prefabById: Map<string, IEnemy<TAttr>> = new Map();
/** 旧样板怪物 id 到 code 的映射,用于 fromLegacyEnemy 快速查找已有模板 */
private readonly legacyIdToCode: Map<string, number> = new Map();
registerSpecial(
code: number,
cons: (enemy: IEnemy) => ISpecial<any>
): void {
registerSpecial(code: number, cons: SpecialCreation<any, TAttr>): void {
this.specialRegistry.set(code, cons);
}
@ -41,7 +32,7 @@ export class EnemyManager implements IEnemyManager {
this.attributeRegistry.set(name, defaultValue);
}
fromLegacyEnemy(enemy: Enemy): IEnemy {
fromLegacyEnemy(enemy: Enemy): IEnemy<TAttr> {
// 如果该旧样板怪物已经通过 addPrefabFromLegacy 注册为模板,直接克隆模板
const existingCode = this.legacyIdToCode.get(enemy.id);
if (existingCode) {
@ -54,21 +45,36 @@ export class EnemyManager implements IEnemyManager {
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 enemy
*/
private convertLegacyEnemy(code: number, enemy: Enemy): IEnemy {
const attrs: IEnemyAttributes = {
hp: enemy.hp,
atk: enemy.atk,
def: enemy.def,
money: enemy.money,
exp: enemy.exp,
point: enemy.point
};
const result = new EnemyImpl(enemy.id, code, structuredClone(attrs));
private convertLegacyEnemy(code: number, enemy: Enemy): IEnemy<TAttr> {
const attrs = this.createAttributes(enemy);
const result = new EnemyImpl<TAttr>(
enemy.id,
code,
structuredClone(attrs)
);
// 转换特殊属性
if (enemy.special) {
@ -84,19 +90,19 @@ export class EnemyManager implements IEnemyManager {
return result;
}
createEnemy(code: number): IEnemy | null {
createEnemy(code: number): IEnemy<TAttr> | null {
const prefab = this.prefabByCode.get(code);
if (!prefab) return null;
return prefab.clone();
}
createEnemyById(id: string): IEnemy | null {
createEnemyById(id: string): IEnemy<TAttr> | null {
const prefab = this.prefabById.get(id);
if (!prefab) return null;
return prefab.clone();
}
addPrefab(enemy: IEnemy): void {
addPrefab(enemy: IEnemy<TAttr>): void {
if (
this.prefabByCode.has(enemy.code) ||
this.prefabById.has(enemy.id)
@ -118,11 +124,11 @@ export class EnemyManager implements IEnemyManager {
this.legacyIdToCode.set(enemy.id, code);
}
getPrefab(code: number): IEnemy | null {
getPrefab(code: number): IEnemy<TAttr> | null {
return this.prefabByCode.get(code) ?? null;
}
getPrefabById(id: string): IEnemy | null {
getPrefabById(id: string): IEnemy<TAttr> | null {
return this.prefabById.get(id) ?? null;
}
@ -136,7 +142,7 @@ export class EnemyManager implements IEnemyManager {
this.prefabById.delete(prefab.id);
}
changePrefab(code: number | string, enemy: IEnemy): void {
changePrefab(code: number | string, enemy: IEnemy<TAttr>): void {
// 先删除旧的模板(如果存在)
this.deletePrefab(code);
// 再添加新的模板

View File

@ -18,25 +18,25 @@ interface IPointInfo {
readonly affectedBy: Set<IMapDamageView<any>>;
}
interface IViewStore {
interface IViewStore<TAttr> {
/** 该地图伤害视图所影响的伤害信息 */
readonly damages: Map<number, Readonly<IMapDamageInfo>>;
/** 当前视图所属的怪物视图 */
readonly enemy: IEnemyView;
readonly enemy: IEnemyView<TAttr>;
}
interface IDamageStore {
interface IDamageStore<TAttr> {
/** 该地图伤害信息的地图伤害视图来源 */
readonly sourceView: IMapDamageView<any>;
/** 地图伤害信息的来源怪物 */
readonly sourceEnemy: IEnemyView;
readonly sourceEnemy: IEnemyView<TAttr>;
/** 该地图伤害信息所处的索引 */
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;
@ -45,23 +45,27 @@ export class MapDamage implements IMapDamage {
/** 有来源地图伤害,坐标 -> 点伤害信息 */
private readonly sourcedDamage: Map<number, IPointInfo> = new Map();
/** 地图伤害视图 -> 其信息对象 */
private readonly viewStore: Map<IMapDamageView, IViewStore> = new Map();
/** 地图伤害信息 -> 其信息对象 */
private readonly damageStore: Map<IMapDamageInfo, IDamageStore> = new Map();
/** 怪物视图 -> 其影响对象 */
private readonly enemyStore: Map<IEnemyView, Set<IMapDamageView>> =
private readonly viewStore: Map<IMapDamageView<any>, IViewStore<TAttr>> =
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 reducedCache: Map<number, IMapDamageInfo> = new Map();
constructor(
readonly context: IEnemyContext,
readonly context: IEnemyContext<TAttr>,
readonly indexer: IMapLocIndexer
) {}
useConverter(converter: IMapDamageConverter): void {
useConverter(converter: IMapDamageConverter<TAttr>): void {
this.converter = converter;
this.refreshAll();
}
@ -102,7 +106,7 @@ export class MapDamage implements IMapDamage {
this.markDirtyIndex(this.indexer.locaterToIndex(locator));
}
markEnemyDirty(view: IEnemyView): void {
markEnemyDirty(view: IEnemyView<TAttr>): void {
const store = this.enemyStore.get(view);
const locator = this.context.getEnemyLocatorByView(view);
if (!store) {
@ -117,7 +121,7 @@ export class MapDamage implements IMapDamage {
this.refreshEnemyAndClearCache(view, locator);
}
deleteEnemy(view: IEnemyView): void {
deleteEnemy(view: IEnemyView<TAttr>): void {
const store = this.enemyStore.get(view);
if (!store) return;
const collection = new Set<number>();
@ -206,7 +210,7 @@ export class MapDamage implements IMapDamage {
*
* @param view
*/
private removeEnemyAffecting(view: IEnemyView) {
private removeEnemyAffecting(view: IEnemyView<TAttr>) {
const views = this.enemyStore.get(view);
if (!views) return;
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);
const enemy = view.getComputedEnemy();
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);
const enemy = view.getComputedEnemy();
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,
value: T,
config: ICommonSerializableSpecialConfig<T>
): SpecialCreation<T> {
): SpecialCreation<T, TAttr> {
return () =>
new CommonSerializableSpecial(code, structuredClone(value), config);
}
export function defineNonePropertySpecial(
export function defineNonePropertySpecial<TAttr = any>(
code: number,
config: ICommonSerializableSpecialConfig<void>
): SpecialCreation<void> {
): SpecialCreation<void, TAttr> {
return () => new NonePropertySpecial(code, config);
}

View File

@ -1,21 +1,6 @@
import { IRange } from '@motajs/common';
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> {
/** 特殊属性代码 */
readonly code: number;
@ -55,7 +40,7 @@ export interface ISpecial<T = void> {
clone(): ISpecial<T>;
}
export interface IReadonlyEnemy {
export interface IReadonlyEnemy<TAttr> {
/** 怪物标识符 */
readonly id: string;
/** 怪物在地图上的标识数字 */
@ -82,24 +67,20 @@ export interface IReadonlyEnemy {
*
* @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 {
/** 怪物标识符 */
readonly id: string;
/** 怪物在地图上的标识数字 */
readonly code: number;
/** 怪物属性值 */
readonly attributes: Readonly<IEnemyAttributes>;
/** 怪物拥有的特殊属性列表 */
readonly specials: Set<ISpecial<any>>;
export interface IEnemy<TAttr> extends IReadonlyEnemy<TAttr> {
/**
*
* @param special
@ -117,32 +98,29 @@ export interface IEnemy extends IReadonlyEnemy {
* @param key
* @param value
*/
setAttribute<K extends keyof IEnemyAttributes>(
key: K,
value: IEnemyAttributes[K]
): void;
setAttribute<K extends keyof TAttr>(key: K, value: TAttr[K]): void;
/**
*
*/
clone(): IEnemy;
clone(): IEnemy<TAttr>;
/**
*
* @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 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
*/
fromLegacyEnemy(enemy: Enemy): IEnemy;
fromLegacyEnemy(enemy: Enemy): IEnemy<TAttr>;
/**
* `null`
* @param code
*/
createEnemy(code: number): IEnemy | null;
createEnemy(code: number): IEnemy<TAttr> | null;
/**
* `id` `null`
* @param id `id`
*/
createEnemyById(id: string): IEnemy | null;
createEnemyById(id: string): IEnemy<TAttr> | null;
/**
* `id` `code`
* 使 {@link changePrefab}
* @param enemy
*/
addPrefab(enemy: IEnemy): void;
addPrefab(enemy: IEnemy<TAttr>): void;
/**
*
@ -187,13 +165,13 @@ export interface IEnemyManager {
*
* @param code
*/
getPrefab(code: number): IEnemy | null;
getPrefab(code: number): IEnemy<TAttr> | null;
/**
* `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 enemy
*/
changePrefab(code: number | string, enemy: IEnemy): void;
changePrefab(code: number | string, enemy: IEnemy<TAttr>): void;
}
//#region 辅助接口
@ -244,9 +222,9 @@ export interface IMapLocIndexer extends IMapLocHelper {
//#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
*/
getModifiableEnemy(): IEnemy;
getModifiableEnemy(): IEnemy<TAttr>;
/**
*
@ -279,20 +257,23 @@ export interface IEnemyView {
//#region 光环与查询
export interface IEnemySpecialModifier {
export interface IEnemySpecialModifier<TAttr> {
/**
*
* @param enemy
* @param locator
*/
add(enemy: IReadonlyEnemy, locator: ITileLocator): ISpecial<any>[];
add(enemy: IReadonlyEnemy<TAttr>, locator: ITileLocator): ISpecial<any>[];
/**
*
* @param enemy
* @param locator
*/
delete(enemy: IReadonlyEnemy, locator: ITileLocator): ISpecial<any>[];
delete(
enemy: IReadonlyEnemy<TAttr>,
locator: ITileLocator
): ISpecial<any>[];
/**
* true false
@ -301,13 +282,13 @@ export interface IEnemySpecialModifier {
* @param locator
*/
modify(
enemy: IReadonlyEnemy,
enemy: IReadonlyEnemy<TAttr>,
special: ISpecial<any>,
locator: ITileLocator
): boolean;
}
export interface IAuraView<T = any> {
export interface IAuraView<TAttr, T = any> {
/** 此光环视图的优先级 */
readonly priority: number;
/** 此光环视图的影响范围 */
@ -326,42 +307,44 @@ export interface IAuraView<T = any> {
/**
*
* @param enemy
* @param baseEnemy
* @param locator
*/
apply(
enemy: IEnemy,
baseEnemy: IReadonlyEnemy,
enemy: IEnemy<TAttr>,
baseEnemy: IReadonlyEnemy<TAttr>,
locator: ITileLocator
): void;
/**
*
* @param enemy
* @param baseEnemy
* @param locator
*/
applySpecial(
enemy: IReadonlyEnemy,
baseEnemy: IReadonlyEnemy,
enemy: IReadonlyEnemy<TAttr>,
baseEnemy: IReadonlyEnemy<TAttr>,
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 locator: ITileLocator;
}
export interface IAuraConverter {
export interface IAuraConverter<TAttr> {
/**
*
*/
shouldConvert(
special: ISpecial<any>,
enemy: IReadonlyEnemy,
enemy: IReadonlyEnemy<TAttr>,
locator: ITileLocator
): boolean;
@ -370,29 +353,32 @@ export interface IAuraConverter {
*/
convert(
special: ISpecial<any>,
enemy: IReadonlyEnemy,
locator: ITileLocator
): IEnemyAuraView<any, any>;
enemy: IReadonlyEnemy<TAttr>,
locator: ITileLocator,
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;
/**
*
*/
for(ctx: IEnemyContext): IEnemySpecialQueryModifier;
for(ctx: IEnemyContext<TAttr>): IEnemySpecialQueryModifier<TAttr>;
}
export interface IEnemyCommonQueryEffect {
export interface IEnemyCommonQueryEffect<TAttr> {
/** 优先级,越高的越先执行 */
readonly priority: number;
@ -400,21 +386,21 @@ export interface IEnemyCommonQueryEffect {
*
*/
apply(
enemy: IEnemy,
enemy: IEnemy<TAttr>,
special: ISpecial<any>,
query: () => IEnemyContext,
query: () => IEnemyContext<TAttr>,
locator: ITileLocator
): void;
}
export interface IEnemyFinalEffect {
export interface IEnemyFinalEffect<TAttr> {
/** 效果优先级,越高会越先被执行 */
readonly priority: number;
/**
*
*/
apply(enemy: IEnemy, locator: ITileLocator): void;
apply(enemy: IEnemy<TAttr>, locator: ITileLocator): void;
}
//#endregion
@ -438,10 +424,14 @@ export interface IMapDamageInfo {
}
export interface IMapDamageView<T = any> {
/** 获取地图伤害影响范围 */
/**
*
*/
getRange(): IRange<T>;
/** 获取范围参数 */
/**
*
*/
getRangeParam(): T;
/**
@ -451,7 +441,7 @@ export interface IMapDamageView<T = any> {
getDamageAt(locator: ITileLocator): Readonly<IMapDamageInfo> | null;
/**
*
*
* @param locator
*/
getDamageWithoutCheck(
@ -459,32 +449,36 @@ export interface IMapDamageView<T = any> {
): Readonly<IMapDamageInfo> | null;
}
export interface IMapDamageConverter {
/** 转换地图伤害视图 */
export interface IMapDamageConverter<TAttr> {
/**
*
*/
convert(
enemy: IReadonlyEnemy,
enemy: IReadonlyEnemy<TAttr>,
locator: ITileLocator,
context: IEnemyContext
context: IEnemyContext<TAttr>
): IMapDamageView<any>[];
}
export interface IMapDamageReducer {
/** 对伤害信息进行合并 */
/**
*
*/
reduce(
info: Iterable<Readonly<IMapDamageInfo>>,
locator: ITileLocator
): Readonly<IMapDamageInfo>;
}
export interface IMapDamage {
export interface IMapDamage<TAttr> {
/** 当前绑定的怪物上下文 */
readonly context: IEnemyContext;
readonly context: IEnemyContext<TAttr>;
/**
*
* @param converter
*/
useConverter(converter: IMapDamageConverter): void;
useConverter(converter: IMapDamageConverter<TAttr>): void;
/**
*
@ -516,7 +510,7 @@ export interface IMapDamage {
*
* @param view
*/
markEnemyDirty(view: IEnemyView): void;
markEnemyDirty(view: IEnemyView<TAttr>): void;
/**
*
@ -527,7 +521,7 @@ export interface IMapDamage {
*
* @param view
*/
deleteEnemy(view: IEnemyView): void;
deleteEnemy(view: IEnemyView<TAttr>): void;
/**
*
@ -548,7 +542,7 @@ export interface IMapDamage {
//#region 上下文
export interface IEnemyContext {
export interface IEnemyContext<TAttr> {
/** 怪物上下文宽度 */
readonly width: number;
/** 怪物上下文高度 */
@ -565,32 +559,35 @@ export interface IEnemyContext {
*
* @param converter
*/
registerAuraConverter(converter: IAuraConverter): void;
registerAuraConverter(converter: IAuraConverter<TAttr>): void;
/**
*
* @param converter
*/
unregisterAuraConverter(converter: IAuraConverter): void;
unregisterAuraConverter(converter: IAuraConverter<TAttr>): void;
/**
*
* @param converter
* @param enabled
*/
setAuraConverterEnabled(converter: IAuraConverter, enabled: boolean): void;
setAuraConverterEnabled(
converter: IAuraConverter<TAttr>,
enabled: boolean
): void;
/**
*
* @param effect
*/
registerSpecialQueryEffect(effect: IEnemySpecialQueryEffect): void;
registerSpecialQueryEffect(effect: IEnemySpecialQueryEffect<TAttr>): void;
/**
*
* @param effect
*/
unregisterSpecialQueryEffect(effect: IEnemySpecialQueryEffect): void;
unregisterSpecialQueryEffect(effect: IEnemySpecialQueryEffect<TAttr>): void;
/**
*
@ -599,7 +596,7 @@ export interface IEnemyContext {
*/
registerCommonQueryEffect(
code: number,
effect: IEnemyCommonQueryEffect
effect: IEnemyCommonQueryEffect<TAttr>
): void;
/**
@ -609,58 +606,60 @@ export interface IEnemyContext {
*/
unregisterCommonQueryEffect(
code: number,
effect: IEnemyCommonQueryEffect
effect: IEnemyCommonQueryEffect<TAttr>
): void;
/**
*
* @param effect
*/
registerFinalEffect(effect: IEnemyFinalEffect): void;
registerFinalEffect(effect: IEnemyFinalEffect<TAttr>): void;
/**
*
* @param effect
*/
unregisterFinalEffect(effect: IEnemyFinalEffect): void;
unregisterFinalEffect(effect: IEnemyFinalEffect<TAttr>): void;
/**
*
* @param enemy
*/
getEnemyLocator(enemy: IEnemy): Readonly<ITileLocator> | null;
getEnemyLocator(enemy: IEnemy<TAttr>): Readonly<ITileLocator> | null;
/**
*
* @param view
*/
getEnemyLocatorByView(view: IEnemyView): Readonly<ITileLocator> | null;
getEnemyLocatorByView(
view: IEnemyView<TAttr>
): Readonly<ITileLocator> | null;
/**
*
* @param locator
*/
getEnemyByLocator(locator: ITileLocator): IEnemyView | null;
getEnemyByLocator(locator: ITileLocator): IEnemyView<TAttr> | null;
/**
*
* @param x
* @param y
*/
getEnemyByLoc(x: number, y: number): IEnemyView | null;
getEnemyByLoc(x: number, y: number): IEnemyView<TAttr> | null;
/**
*
* @param enemy
*/
getViewByComputed(enemy: IReadonlyEnemy): IEnemyView | null;
getViewByComputed(enemy: IReadonlyEnemy<TAttr>): IEnemyView<TAttr> | null;
/**
*
* @param locator
* @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 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
*/
addAura(aura: IAuraView): void;
addAura(aura: IAuraView<TAttr>): void;
/**
*
* @param aura
*/
deleteAura(aura: IAuraView): void;
deleteAura(aura: IAuraView<TAttr>): void;
/**
*
* @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
*/
markDirty(view: IEnemyView): void;
markDirty(view: IEnemyView<TAttr>): void;
/**
*
* @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 { IEnemyAttributes } from './enemy/types';
import { IGameDataState } from './types';
import { registerSpecials } from './enemy';
export class GameDataState implements IGameDataState {
readonly enemyManager: IEnemyManager;
readonly enemyManager: IEnemyManager<IEnemyAttributes>;
constructor() {
this.enemyManager = new EnemyManager();
this.enemyManager = new EnemyManager<IEnemyAttributes>();
registerSpecials(this.enemyManager);
}
}

View File

@ -9,13 +9,16 @@ import {
import {
IAuraConverter,
IEnemyAuraView,
IEnemyContext,
IEnemySpecialModifier,
IEnemyView,
IReadonlyEnemy,
ISpecial,
IEnemy
} from '@user/data-base';
import { IHaloValue } from './special';
import { ITileLocator } from '@user/types';
import { IEnemyAttributes } from './types';
const FULL_RANGE = new FullRange();
const RECT_RANGE = new RectRange();
@ -23,32 +26,32 @@ const MANHATTAN_RANGE = new ManhattanRange();
//#region 25-光环
export class CommonAuraConverter implements IAuraConverter {
export class CommonAuraConverter implements IAuraConverter<IEnemyAttributes> {
shouldConvert(special: ISpecial<any>): boolean {
return special.code === 25;
}
convert(
special: ISpecial<any>,
enemy: IReadonlyEnemy,
enemy: IReadonlyEnemy<IEnemyAttributes>,
locator: ITileLocator
): IEnemyAuraView<any, any> {
): CommonAura {
return new CommonAura(enemy, special as ISpecial<IHaloValue>, locator);
}
}
export class CommonAura implements IEnemyAuraView<
IEnemyAttributes,
IRectRangeParam | IManhattanRangeParam | void,
IHaloValue
> {
readonly priority: number = 25;
readonly couldApplyBase: boolean = true;
readonly couldApplySpecial: boolean = false;
readonly range: IRange<IRectRangeParam | IManhattanRangeParam | void>;
constructor(
readonly enemy: IReadonlyEnemy,
readonly enemy: IReadonlyEnemy<IEnemyAttributes>,
readonly special: ISpecial<IHaloValue>,
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;
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;
}
}

View File

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

View File

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

View File

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