Compare commits

...

4 Commits

Author SHA1 Message Date
39e91b241f refactor: 将旧样板怪物移植单独提取出来 2026-04-15 23:12:28 +08:00
6c1acb990e reafactor: 目录结构修改 2026-04-15 22:32:07 +08:00
63e1a01fab feat: 内置最终效果 2026-04-15 18:48:59 +08:00
0517da5000 feat: 伤害计算系统 2026-04-15 18:26:25 +08:00
25 changed files with 561 additions and 160 deletions

View File

@ -1,8 +1,8 @@
import { IRange, logger } from '@motajs/common';
import { ITileLocator } from '@user/types';
import { IRange, ITileLocator, logger } from '@motajs/common';
import {
IAuraConverter,
IAuraView,
IDamageSystem,
IEnemy,
IEnemyAuraView,
IEnemyCommonQueryEffect,
@ -53,6 +53,7 @@ export class EnemyContext<TAttr> implements IEnemyContext<TAttr> {
private readonly dirtyEnemy: Set<IEnemyView<TAttr>> = new Set();
private mapDamage: IMapDamage<TAttr> | null = null;
private damageSystem: IDamageSystem<TAttr, unknown> | null = null;
readonly indexer: MapLocIndexer = new MapLocIndexer();
private needUpdate: boolean = true;
@ -174,6 +175,9 @@ export class EnemyContext<TAttr> implements IEnemyContext<TAttr> {
if (this.mapDamage) {
this.mapDamage.deleteEnemy(view);
}
if (this.damageSystem) {
this.damageSystem.deleteEnemy(view);
}
this.needTotallyRefresh.delete(view);
this.dirtyEnemy.delete(view);
@ -253,6 +257,15 @@ export class EnemyContext<TAttr> implements IEnemyContext<TAttr> {
return this.mapDamage;
}
attachDamageSystem(system: IDamageSystem<TAttr, unknown>): void {
this.damageSystem = system;
system.markAllDirty();
}
getDamageSystem<THero>(): IDamageSystem<TAttr, THero> | null {
return this.damageSystem as IDamageSystem<TAttr, THero> | null;
}
private convertSpecial(
special: ISpecial<any>,
enemy: IReadonlyEnemy<TAttr>,
@ -545,6 +558,10 @@ export class EnemyContext<TAttr> implements IEnemyContext<TAttr> {
this.buildupFinal();
}
if (this.damageSystem) {
this.damageSystem.markAllDirty();
}
if (this.mapDamage) {
this.mapDamage.refreshAll();
}
@ -553,6 +570,9 @@ export class EnemyContext<TAttr> implements IEnemyContext<TAttr> {
markDirty(view: IEnemyView<TAttr>): void {
if (!this.locatorViewMap.has(view)) return;
this.dirtyEnemy.add(view);
if (this.damageSystem) {
this.damageSystem.markDirty(view);
}
}
private refreshSpecialModifier(
@ -687,6 +707,10 @@ export class EnemyContext<TAttr> implements IEnemyContext<TAttr> {
this.dirtyEnemy.delete(view);
if (this.damageSystem) {
this.damageSystem.markDirty(view);
}
if (this.mapDamage) {
this.mapDamage.markEnemyDirty(view);
}
@ -721,6 +745,9 @@ export class EnemyContext<TAttr> implements IEnemyContext<TAttr> {
this.needTotallyRefresh.clear();
this.requestedCommonContext.clear();
this.dirtyEnemy.clear();
if (this.damageSystem) {
this.damageSystem.markAllDirty();
}
if (this.mapDamage) {
this.mapDamage.refreshAll();
}
@ -729,6 +756,7 @@ export class EnemyContext<TAttr> implements IEnemyContext<TAttr> {
destroy(): void {
this.clear();
this.attachMapDamage(null);
this.damageSystem = null;
this.auraConverter.clear();
this.commonQueryMap.clear();
this.specialQueryEffects.clear();

View File

@ -0,0 +1,211 @@
import { logger } from '@motajs/common';
import {
CriticalableHeroStatus,
IDamageCalculator,
IDamageSystem,
IEnemyContext,
IEnemyCritical,
IEnemyDamageInfo,
IEnemyView,
IReadonlyEnemy
} from './types';
interface ICriticalSearchResult {
/** 此临界点的属性值 */
readonly value: number;
/** 此临界点的伤害信息 */
readonly info: IEnemyDamageInfo;
}
export class DamageSystem<TAttr, THero> implements IDamageSystem<TAttr, THero> {
/** 当前正在使用的计算器 */
private calculator: IDamageCalculator<TAttr, THero> | null = null;
/** 当前勇士属性 */
private heroStatus: Readonly<THero> | null = null;
/** 怪物伤害缓存 */
private readonly cache: Map<IEnemyView<TAttr>, IEnemyDamageInfo> =
new Map();
constructor(readonly context: IEnemyContext<TAttr>) {}
useCalculator(calculator: IDamageCalculator<TAttr, THero>): void {
this.calculator = calculator;
this.markAllDirty();
}
getCalculator(): IDamageCalculator<TAttr, THero> | null {
return this.calculator;
}
bindHeroStatus(hero: Readonly<THero>): void {
this.heroStatus = hero;
this.markAllDirty();
}
/**
*
*/
private cloneHeroStatus(): THero | null {
if (!this.heroStatus) return null;
else return structuredClone(this.heroStatus);
}
/**
*
* @param enemy
* @param attribute
* @param value
* @returns
*/
private calculateDamageWithModified(
enemy: IReadonlyEnemy<TAttr>,
attribute: CriticalableHeroStatus<THero>,
value: number
): IEnemyDamageInfo {
const hero = this.cloneHeroStatus()!;
// @ts-expect-error 之后会进行修复
hero[attribute] = value;
return this.calculator!.calculate(hero, enemy);
}
getDamageInfo(enemy: IEnemyView<TAttr>): IEnemyDamageInfo | null {
if (!this.heroStatus) {
logger.warn(107);
return null;
}
if (!this.calculator) {
logger.warn(106);
return null;
}
const hero = this.cloneHeroStatus()!;
const cached = this.cache.get(enemy);
if (cached) {
return cached;
}
const info = this.calculator.calculate(hero, enemy.getComputedEnemy());
this.cache.set(enemy, info);
return info;
}
markDirty(enemy: IEnemyView<TAttr>): void {
this.cache.delete(enemy);
}
deleteEnemy(enemy: IEnemyView<TAttr>): void {
this.cache.delete(enemy);
}
markAllDirty(): void {
this.cache.clear();
}
*calculateCritical(
view: IEnemyView<TAttr>,
attribute: CriticalableHeroStatus<THero>,
precision: number
): Generator<IEnemyCritical, void, void> {
if (!this.heroStatus) {
logger.warn(107);
return;
}
if (!this.calculator) {
logger.warn(106);
return;
}
const currentInfo = this.getDamageInfo(view);
if (!currentInfo) return;
const enemy = view.getComputedEnemy();
const hero = this.cloneHeroStatus()!;
const currentValue = hero[attribute] as number;
const upperLimit = Math.floor(
this.calculator.getCriticalLimit(hero, enemy, attribute)
);
if (currentValue >= upperLimit) return;
const maxIterations = Math.max(0, Math.floor(precision));
let baseValue = currentValue;
let baseInfo = currentInfo;
while (baseValue < upperLimit) {
const next = this.findNextCritical(
enemy,
attribute,
baseValue,
upperLimit,
baseInfo.damage,
maxIterations
);
if (!next) return;
yield {
nextValue: next.value,
baseValue: currentValue,
nextDiff: next.value - currentValue,
baseInfo: currentInfo,
info: next.info,
damageDiff: next.info.damage - currentInfo.damage
};
baseValue = next.value;
baseInfo = next.info;
}
}
/**
*
* @param enemy
* @param attribute
* @param currentValue
* @param upperLimit
* @param referenceDamage
* @param maxIterations
*/
private findNextCritical(
enemy: IReadonlyEnemy<TAttr>,
attribute: CriticalableHeroStatus<THero>,
currentValue: number,
upperLimit: number,
referenceDamage: number,
maxIterations: number
): ICriticalSearchResult | null {
let left = currentValue;
let right = upperLimit;
let rightInfo = this.calculateDamageWithModified(
enemy,
attribute,
right
);
if (rightInfo.damage >= referenceDamage) return null;
let iter = 0;
while (iter < maxIterations) {
const middle = Math.floor((left + right) / 2);
const middleInfo = this.calculateDamageWithModified(
enemy,
attribute,
middle
);
if (middleInfo.damage < referenceDamage) {
right = middle;
rightInfo = middleInfo;
} else {
left = middle;
}
if (right - left <= 1) break;
iter++;
}
return {
value: right,
info: rightInfo
};
}
}

View File

@ -51,6 +51,13 @@ export class Enemy<TAttr> implements IEnemy<TAttr> {
this.attributes[key] = value;
}
addAttribute<K extends SelectKey<TAttr, number>>(
key: K,
value: number
): void {
(this.attributes[key] as number) += value;
}
getAttribute<K extends keyof TAttr>(key: K): TAttr[K] {
return this.attributes[key];
}

View File

@ -1,5 +1,6 @@
export * from './enemy';
export * from './context';
export * from './damage';
export * from './mapDamage';
export * from './manager';
export * from './special';

View File

@ -1,13 +1,18 @@
import { logger } from '@motajs/common';
import { Enemy as EnemyImpl } from './enemy';
import { IEnemy, IEnemyManager, SpecialCreation } from './types';
import {
IEnemy,
IEnemyManager,
IEnemyLegacyBridge,
SpecialCreation
} from './types';
export class EnemyManager<TAttr> implements IEnemyManager<TAttr> {
/** 特殊属性注册表code -> 创建函数 */
private readonly specialRegistry: Map<number, SpecialCreation<any, TAttr>> =
new Map();
/** 自定义怪物属性注册表name -> 默认值 */
private readonly attributeRegistry: Map<string, any> = new Map();
private readonly attributeRegistry: Map<keyof TAttr, any> = new Map();
/** 怪物模板表code -> IEnemy */
private readonly prefabByCode: Map<number, IEnemy<TAttr>> = new Map();
/** 怪物模板表id -> IEnemy */
@ -15,11 +20,16 @@ export class EnemyManager<TAttr> implements IEnemyManager<TAttr> {
/** 旧样板怪物 id 到 code 的映射,用于 fromLegacyEnemy 快速查找已有模板 */
private readonly legacyIdToCode: Map<string, number> = new Map();
constructor(readonly bridge: IEnemyLegacyBridge<TAttr>) {}
registerSpecial(code: number, cons: SpecialCreation<any, TAttr>): void {
this.specialRegistry.set(code, cons);
}
registerAttribute(name: string, defaultValue: any): void {
setAttributeDefaults<K extends keyof TAttr>(
name: K,
defaultValue: TAttr[K]
): void {
if (
typeof defaultValue === 'function' ||
typeof defaultValue === 'symbol' ||
@ -50,16 +60,13 @@ export class EnemyManager<TAttr> implements IEnemyManager<TAttr> {
* @param enemy
*/
private createAttributes(enemy: Enemy): TAttr {
const attrs: Record<string, any> = {};
const attrs: Partial<TAttr> = {};
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;
Object.assign(attrs, this.bridge.fromLegacyEnemy(enemy, attrs));
return attrs as TAttr;
}

View File

@ -1,5 +1,4 @@
import { logger } from '@motajs/common';
import { ITileLocator } from '@user/types';
import { logger, ITileLocator } from '@motajs/common';
import {
IEnemyContext,
IEnemyView,
@ -60,10 +59,12 @@ export class MapDamage<TAttr> implements IMapDamage<TAttr> {
/** 合并后伤害缓存,索引 -> 合并结果 */
private readonly reducedCache: Map<number, IMapDamageInfo> = new Map();
constructor(
readonly context: IEnemyContext<TAttr>,
readonly indexer: IMapLocIndexer
) {}
/** 坐标索引对象 */
private readonly indexer: IMapLocIndexer;
constructor(readonly context: IEnemyContext<TAttr>) {
this.indexer = context.indexer;
}
useConverter(converter: IMapDamageConverter<TAttr>): void {
this.converter = converter;

View File

@ -1,5 +1,6 @@
import { IRange } from '@motajs/common';
import { ITileLocator } from '@user/types';
import { IRange, ITileLocator } from '@motajs/common';
//#region 怪物基础
export interface ISpecial<T = void> {
/** 特殊属性代码 */
@ -100,6 +101,16 @@ export interface IEnemy<TAttr> extends IReadonlyEnemy<TAttr> {
*/
setAttribute<K extends keyof TAttr>(key: K, value: TAttr[K]): void;
/**
*
* @param key
* @param value
*/
addAttribute<K extends SelectKey<TAttr, number>>(
key: K,
value: number
): void;
/**
*
*/
@ -112,8 +123,20 @@ export interface IEnemy<TAttr> extends IReadonlyEnemy<TAttr> {
copyFrom(enemy: IReadonlyEnemy<TAttr>): void;
}
//#endregion
//#region 怪物管理器
export type SpecialCreation<T, TAttr> = (enemy: IEnemy<TAttr>) => ISpecial<T>;
export interface IEnemyLegacyBridge<TAttr> {
/**
*
* @param enemy
*/
fromLegacyEnemy(enemy: Enemy, defaultValue: Partial<TAttr>): TAttr;
}
export interface IEnemyManager<TAttr> {
/**
*
@ -127,7 +150,10 @@ export interface IEnemyManager<TAttr> {
* @param name
* @param defaultValue
*/
registerAttribute(name: string, defaultValue: any): void;
setAttributeDefaults<K extends keyof TAttr>(
name: K,
defaultValue: TAttr[K]
): void;
/**
*
@ -187,6 +213,8 @@ export interface IEnemyManager<TAttr> {
changePrefab(code: number | string, enemy: IEnemy<TAttr>): void;
}
//#endregion
//#region 辅助接口
export interface IMapLocHelper {
@ -540,6 +568,117 @@ export interface IMapDamage<TAttr> {
//#endregion
//#region 伤害系统
export interface IEnemyDamageInfo {
/** 战斗伤害值 */
readonly damage: number;
/** 战斗回合数 */
readonly turn: number;
}
export interface IEnemyCritical {
/** 此临界点中指定勇士属性的值 */
readonly nextValue: number;
/** 当前勇士指定属性的值 */
readonly baseValue: number;
/** 此临界点中指定勇士数值的值与当前值的差,即 `nextValue - baseValue` */
readonly nextDiff: number;
/** 当前状态下怪物的伤害信息 */
readonly baseInfo: IEnemyDamageInfo;
/** 此临界点下怪物的伤害信息 */
readonly info: IEnemyDamageInfo;
/** 此临界点的伤害值与当前伤害值的差 */
readonly damageDiff: number;
}
export type CriticalableHeroStatus<THero> = keyof {
[P in keyof THero as THero[P] extends number ? P : never]: unknown;
};
export interface IDamageCalculator<TAttr, THero> {
/**
*
* @param hero
* @param enemy
*/
calculate(
hero: Readonly<THero>,
enemy: IReadonlyEnemy<TAttr>
): IEnemyDamageInfo;
/**
*
* @param hero
* @param enemy
* @param attribute
*/
getCriticalLimit(
hero: Readonly<THero>,
enemy: IReadonlyEnemy<TAttr>,
attribute: CriticalableHeroStatus<THero>
): number;
}
export interface IDamageSystem<TAttr, THero> {
/** 伤害系统所属的上下文 */
readonly context: IEnemyContext<TAttr>;
/**
* 使
* @param calculator
*/
useCalculator(calculator: IDamageCalculator<TAttr, THero>): void;
/**
* 使
*/
getCalculator(): IDamageCalculator<TAttr, THero> | null;
/**
*
* @param hero
*/
bindHeroStatus(hero: Readonly<THero>): void;
/**
*
* @param enemy
*/
getDamageInfo(enemy: IEnemyView<TAttr>): IEnemyDamageInfo | null;
/**
*
* @param enemy
*/
markDirty(enemy: IEnemyView<TAttr>): void;
/**
*
* @param enemy
*/
deleteEnemy(enemy: IEnemyView<TAttr>): void;
/**
*
*/
markAllDirty(): void;
/**
*
* @param enemy
* @param attribute
* @param precision `12-16`
*/
calculateCritical(
enemy: IEnemyView<TAttr>,
attribute: CriticalableHeroStatus<THero>,
precision: number
): Generator<IEnemyCritical, void, void>;
}
//#endregion
//#region 上下文
export interface IEnemyContext<TAttr> {
@ -547,6 +686,8 @@ export interface IEnemyContext<TAttr> {
readonly width: number;
/** 怪物上下文高度 */
readonly height: number;
/** 此上下文使用的索引对象 */
readonly indexer: IMapLocIndexer;
/**
*
@ -702,6 +843,17 @@ export interface IEnemyContext<TAttr> {
*/
getMapDamage(): IMapDamage<TAttr> | null;
/**
*
* @param system
*/
attachDamageSystem(system: IDamageSystem<TAttr, unknown>): void;
/**
*
*/
getDamageSystem<THero>(): IDamageSystem<TAttr, THero> | null;
/**
*
*

View File

@ -1,4 +1,4 @@
import { ITileLocator } from '@user/types';
import { ITileLocator } from '@motajs/common';
import { IMapLocIndexer } from './types';
export class MapLocIndexer implements IMapLocIndexer {

View File

@ -6,4 +6,4 @@
"@user/data-base": "workspace:*",
"@user/data-utils": "workspace:*"
}
}
}

View File

@ -1,24 +1,71 @@
import { ICoreState, IGameDataState, IStateSaveData } from './types';
import { ICoreState, IStateSaveData } from './types';
import { IHeroState, HeroState } from './hero';
import { ILayerState, LayerState } from './map';
import { IRoleFaceBinder, RoleFaceBinder } from './common';
import { GameDataState } from './data';
import {
DamageSystem,
EnemyContext,
EnemyManager,
IEnemyContext,
IEnemyManager,
MapDamage
} from '@user/data-base';
import { IEnemyAttributes } from './enemy/types';
import {
CommonAuraConverter,
EnemyLegacyBridge,
GuardAuraConverter,
MainEnemyFinalEffect,
MainMapDamageConverter,
MainMapDamageReducer,
registerSpecials
} from './enemy';
import { TILE_HEIGHT, TILE_WIDTH } from './shared';
export class CoreState implements ICoreState {
readonly layer: ILayerState;
readonly hero: IHeroState;
readonly roleFace: IRoleFaceBinder;
readonly data: IGameDataState;
readonly idNumberMap: Map<string, number>;
readonly numberIdMap: Map<number, string>;
readonly enemyManager: IEnemyManager<IEnemyAttributes>;
readonly enemyContext: IEnemyContext<IEnemyAttributes>;
constructor() {
this.layer = new LayerState();
this.hero = new HeroState();
this.roleFace = new RoleFaceBinder();
this.idNumberMap = new Map();
this.numberIdMap = new Map();
this.data = new GameDataState();
//#region 怪物初始化
// 怪物管理器初始化
const enemyManager = new EnemyManager(new EnemyLegacyBridge());
enemyManager.setAttributeDefaults('hp', 0);
enemyManager.setAttributeDefaults('atk', 0);
enemyManager.setAttributeDefaults('def', 0);
enemyManager.setAttributeDefaults('exp', 0);
enemyManager.setAttributeDefaults('money', 0);
enemyManager.setAttributeDefaults('point', 0);
registerSpecials(enemyManager);
this.enemyManager = enemyManager;
// 怪物上下文初始化
const enemyContext = new EnemyContext<IEnemyAttributes>();
const damageSystem = new DamageSystem(enemyContext);
const mapDamage = new MapDamage(enemyContext);
mapDamage.useConverter(new MainMapDamageConverter());
mapDamage.useReducer(new MainMapDamageReducer());
enemyContext.attachDamageSystem(damageSystem);
enemyContext.attachMapDamage(mapDamage);
enemyContext.registerAuraConverter(new CommonAuraConverter());
enemyContext.registerAuraConverter(new GuardAuraConverter());
enemyContext.registerFinalEffect(new MainEnemyFinalEffect());
enemyContext.resize(TILE_WIDTH, TILE_HEIGHT);
this.enemyContext = enemyContext;
//#endregion
}
saveState(): IStateSaveData {

View File

@ -1,13 +0,0 @@
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<IEnemyAttributes>;
constructor() {
this.enemyManager = new EnemyManager<IEnemyAttributes>();
registerSpecials(this.enemyManager);
}
}

View File

@ -3,6 +3,7 @@ import {
IManhattanRangeParam,
IRange,
IRectRangeParam,
ITileLocator,
ManhattanRange,
RectRange
} from '@motajs/common';
@ -17,7 +18,6 @@ import {
IEnemy
} from '@user/data-base';
import { IHaloValue } from './special';
import { ITileLocator } from '@user/types';
import { IEnemyAttributes } from './types';
const FULL_RANGE = new FullRange();
@ -95,27 +95,18 @@ export class CommonAura implements IEnemyAuraView<
const { hpBuff, atkBuff, defBuff } = this.special.value;
if (hpBuff !== 0) {
enemy.setAttribute(
'hp',
enemy.getAttribute('hp') +
Math.floor((baseEnemy.getAttribute('hp') * hpBuff) / 100)
);
const hpValue = (baseEnemy.getAttribute('hp') * hpBuff) / 100;
enemy.addAttribute('hp', Math.floor(hpValue));
}
if (atkBuff !== 0) {
enemy.setAttribute(
'atk',
enemy.getAttribute('atk') +
Math.floor((baseEnemy.getAttribute('atk') * atkBuff) / 100)
);
const atkValue = (baseEnemy.getAttribute('atk') * atkBuff) / 100;
enemy.addAttribute('atk', Math.floor(atkValue));
}
if (defBuff !== 0) {
enemy.setAttribute(
'def',
enemy.getAttribute('def') +
Math.floor((baseEnemy.getAttribute('def') * defBuff) / 100)
);
const defValue = (baseEnemy.getAttribute('def') * defBuff) / 100;
enemy.addAttribute('def', Math.floor(defValue));
}
}

View File

@ -0,0 +1,29 @@
import { IEnemy, IEnemyFinalEffect } from '@user/data-base';
import { IEnemyAttributes } from './types';
import { ITileLocator } from '@motajs/common';
const HERO_STATUS_PLACEHOLDER = {
atk: 0,
def: 0
} as const;
export class MainEnemyFinalEffect implements IEnemyFinalEffect<IEnemyAttributes> {
readonly priority: number = 0;
apply(enemy: IEnemy<IEnemyAttributes>, _locator: ITileLocator): void {
if (enemy.hasSpecial(3)) {
enemy.setAttribute(
'def',
Math.max(
enemy.getAttribute('def'),
HERO_STATUS_PLACEHOLDER.atk - 1
)
);
}
if (enemy.hasSpecial(10)) {
enemy.setAttribute('atk', HERO_STATUS_PLACEHOLDER.atk);
enemy.setAttribute('def', HERO_STATUS_PLACEHOLDER.def);
}
}
}

View File

@ -1,4 +1,6 @@
export * from './aura';
export * from './damage';
export * from './final';
export * from './legacy';
export * from './mapDamage';
export * from './special';

View File

@ -0,0 +1,19 @@
import { IEnemyLegacyBridge } from '@user/data-base';
import { IEnemyAttributes } from './types';
export class EnemyLegacyBridge implements IEnemyLegacyBridge<IEnemyAttributes> {
fromLegacyEnemy(
enemy: Enemy,
defaultAttr: Partial<IEnemyAttributes>
): IEnemyAttributes {
return {
hp: enemy.hp ?? defaultAttr.hp ?? 0,
atk: enemy.atk ?? defaultAttr.atk ?? 0,
def: enemy.def ?? defaultAttr.def ?? 0,
money: enemy.money ?? defaultAttr.money ?? 0,
exp: enemy.exp ?? defaultAttr.exp ?? 0,
point: enemy.point ?? defaultAttr.point ?? 0,
guard: defaultAttr.guard ?? new Set()
};
}
}

View File

@ -8,9 +8,9 @@ import {
IRectRangeParam,
ManhattanRange,
RayRange,
RectRange
RectRange,
ITileLocator
} from '@motajs/common';
import { ITileLocator } from '@user/types';
import { IReadonlyEnemy, ISpecial } from '@user/data-base';
import {
IEnemyContext,

View File

@ -51,7 +51,7 @@ export interface IHaloValue {
export function registerSpecials(
manager: IEnemyManager<IEnemyAttributes>
): void {
manager.registerAttribute('guard', new Set());
manager.setAttributeDefaults('guard', new Set());
// 0 - 空
manager.registerSpecial(

View File

@ -1,2 +1,7 @@
/** 每个地图的默认宽度 */
export const TILE_WIDTH = 13;
/** 每个地图的默认高度 */
export const TILE_HEIGHT = 13;
/** 默认的勇士图片 */
export const DEFAULT_HERO_IMAGE: ImageIds = 'hero.png';

View File

@ -1,7 +1,7 @@
import { ILayerState } from './map';
import { IHeroFollower, IHeroState } from './hero';
import { IRoleFaceBinder } from './common';
import { IEnemyManager } from '@user/data-base';
import { IEnemyContext, IEnemyManager } from '@user/data-base';
import { IEnemyAttributes } from './enemy/types';
export interface IGameDataState {
@ -21,13 +21,16 @@ export interface ICoreState {
readonly hero: IHeroState;
/** 朝向绑定 */
readonly roleFace: IRoleFaceBinder;
/** 游戏数据状态 */
readonly data: IGameDataState;
/** id 到图块数字的映射 */
readonly idNumberMap: Map<string, number>;
/** 图块数字到 id 的映射 */
readonly numberIdMap: Map<number, string>;
/** 怪物管理器 */
readonly enemyManager: IEnemyManager<IEnemyAttributes>;
/** 怪物上下文 */
readonly enemyContext: IEnemyContext<IEnemyAttributes>;
/**
*
*/

View File

@ -1,3 +0,0 @@
{
"name": "@user/types"
}

View File

@ -1,87 +0,0 @@
// todo: 2.C
export interface IEnemyInfo {}
export interface IDamageInfo {}
export interface IDamageEnemy {
/** 原始怪物信息 */
readonly enemy: Enemy;
/** 该怪物所属的怪物列表 */
readonly collection: IEnemyCollection | null;
/** 怪物横坐标 */
readonly x: number | undefined;
/**
*
*/
getEnemyInfo(): IEnemyInfo;
/**
*
*/
getDamageInfo(): IDamageInfo;
}
export interface IMapDamage {
/** 伤害类型 */
readonly type: string;
/** 伤害值 */
readonly damage: number;
/** 伤害优先级 */
readonly priority: number;
}
export interface IMapDamageSummary {
/** 该点的总伤害 */
readonly totalDamage: number;
/** 该点的伤害信息 */
readonly damages: IMapDamage[];
}
export interface IEnemyCollection {
/** 怪物列表,索引为 x + width * y值表示该点对应的怪物 */
readonly list: Map<number, IDamageEnemy>;
/** 楼层 id */
readonly floorId: FloorIds;
/** 楼层宽度 */
readonly width: number;
/** 楼层高度 */
readonly height: number;
/** 地图伤害 */
readonly mapDamage: Map<number, IMapDamageSummary>;
/** 用于计算本怪物列表中怪物信息的勇士属性 */
readonly hero: HeroStatus;
/**
* null
* @param x
* @param y
*/
getEnemy(x: number, y: number): IDamageEnemy | null;
/**
* null
* @param x
* @param y
*/
getMapDamage(x: number, y: number): IMapDamageSummary | null;
/**
*
*/
refresh(): void;
/**
*
* @param status
*/
with(status: HeroStatus): IEnemyCollection;
}
export interface IDamageSystem {
readonly collections: Map<FloorIds, IEnemyCollection>;
}

View File

@ -1,2 +0,0 @@
export * from './enemy';
export * from './spatial';

View File

@ -1,6 +0,0 @@
export interface ITileLocator {
/** 图块所在横坐标 */
x: number;
/** 图块所在纵坐标 */
y: number;
}

View File

@ -161,6 +161,8 @@
"103": "Map damage reducer is missing, reduced map damage is unavailable.",
"104": "Enemy dirty marking failed since specific enemy is not in current context.",
"105": "No specific map damage view stored, which seems like an internal bug of map damage system.",
"106": "Damage calculator is missing, damage calculation is unavailable.",
"107": "Hero status is not bound, damage calculation is unavailable.",
"1001": "Item-detail extension needs 'floor-binder' and 'floor-damage' extension as dependency."
}
}

View File

@ -135,6 +135,13 @@ export interface IRayRangeParam {
//#region 实用接口
export interface ITileLocator {
/** 图块所在横坐标 */
x: number;
/** 图块所在纵坐标 */
y: number;
}
export const enum InternalDirectionGroup {
/** 上下左右四方向 */
Dir4,