template/packages-user/data-state/src/enemy/mapDamage.ts

359 lines
9.9 KiB
TypeScript

import {
IManhattanRangeParam,
IRange,
IRayRangeParam,
IRectRangeParam,
ManhattanRange,
RayRange,
RectRange,
ITileLocator,
IDirectionDescriptor
} from '@motajs/common';
import {
IEnemyContext,
IMapDamageConverter,
IMapDamageInfo,
IMapDamageInfoExtra,
IMapDamageReducer,
IReadonlyEnemyHandler,
IMapDamageView
} from '@user/data-system';
import {
ISpecial,
IReadonlyHeroAttribute,
IReadonlyEnemy
} from '@user/data-base';
import { IZoneValue } from './special';
import { MapDamageType } from './types';
import { IHeroAttr, IEnemyAttr } from '@user/data-common';
import { IFaceHandler, FaceGroup } from '@user/data-common';
const RECT_RANGE = new RectRange();
const MANHATTAN_RANGE = new ManhattanRange();
const RAY_RANGE = new RayRange();
//#region 地图伤害
abstract class BaseMapDamageView<T> implements IMapDamageView<T> {
constructor(
protected readonly context: IEnemyContext<IEnemyAttr, IHeroAttr>
) {}
abstract getRange(): IRange<T>;
abstract getRangeParam(): T;
getDamageAt(locator: ITileLocator): Readonly<IMapDamageInfo> | null {
const range = this.getRange();
const param = this.getRangeParam();
range.bindHost(this.context);
if (!range.inRange(locator.x, locator.y, param)) {
return null;
}
return this.getDamageWithoutCheck(locator);
}
abstract getDamageWithoutCheck(
locator: ITileLocator
): Readonly<IMapDamageInfo> | null;
/**
* 创建伤害信息
* @param damage 伤害值
* @param type 伤害类型
* @param extra 额外信息
*/
protected createInfo(
damage: number,
type: number,
extra?: Partial<IMapDamageInfoExtra>
): IMapDamageInfo {
return {
damage,
type,
extra: {
catch: extra?.catch ?? new Set(),
repulse: extra?.repulse ?? new Set()
}
};
}
}
export class ZoneDamageView extends BaseMapDamageView<
IRectRangeParam | IManhattanRangeParam
> {
constructor(
context: IEnemyContext<IEnemyAttr, IHeroAttr>,
private readonly locator: Readonly<ITileLocator>,
private readonly special: Readonly<ISpecial<IZoneValue>>
) {
super(context);
}
getRange(): IRange<IRectRangeParam | IManhattanRangeParam> {
return this.special.value.zoneSquare ? RECT_RANGE : MANHATTAN_RANGE;
}
getRangeParam(): IRectRangeParam | IManhattanRangeParam {
if (this.special.value.zoneSquare) {
return {
h: this.special.value.range * 2 + 1,
w: this.special.value.range * 2 + 1,
x: this.locator.x - this.special.value.range,
y: this.locator.y - this.special.value.range
};
}
return {
cx: this.locator.x,
cy: this.locator.y,
radius: this.special.value.range
};
}
getDamageWithoutCheck(): Readonly<IMapDamageInfo> | null {
return this.createInfo(this.special.value.zone, MapDamageType.Zone);
}
}
export class RepulseDamageView extends BaseMapDamageView<IManhattanRangeParam> {
constructor(
context: IEnemyContext<IEnemyAttr, IHeroAttr>,
private readonly locator: Readonly<ITileLocator>,
private readonly special: Readonly<ISpecial<number>>
) {
super(context);
}
getRange(): IRange<IManhattanRangeParam> {
return MANHATTAN_RANGE;
}
getRangeParam(): IManhattanRangeParam {
return {
cx: this.locator.x,
cy: this.locator.y,
radius: 1
};
}
getDamageWithoutCheck(
locator: ITileLocator
): Readonly<IMapDamageInfo> | null {
if (locator.x === this.locator.x && locator.y === this.locator.y) {
return null;
}
return this.createInfo(this.special.value, MapDamageType.Repulse, {
repulse: new Set([this.locator])
});
}
}
export class LaserDamageView extends BaseMapDamageView<IRayRangeParam> {
/** 激光方向列表 */
private readonly dirs: IDirectionDescriptor[];
constructor(
context: IEnemyContext<IEnemyAttr, IHeroAttr>,
private readonly locator: Readonly<ITileLocator>,
private readonly special: Readonly<ISpecial<number>>,
dir: IFaceHandler<number>
) {
super(context);
this.dirs = [...dir.mapMovement()].map(v => v[1]);
}
getRange(): IRange<IRayRangeParam> {
return RAY_RANGE;
}
getRangeParam(): IRayRangeParam {
return {
cx: this.locator.x,
cy: this.locator.y,
dir: this.dirs
};
}
getDamageWithoutCheck(): Readonly<IMapDamageInfo> | null {
return this.createInfo(this.special.value, MapDamageType.Layer);
}
}
export class BetweenDamageView extends BaseMapDamageView<IManhattanRangeParam> {
constructor(
context: IEnemyContext<IEnemyAttr, IHeroAttr>,
private readonly enemy: IReadonlyEnemy<IEnemyAttr>,
private readonly locator: Readonly<ITileLocator>,
private readonly hero: IReadonlyHeroAttribute<IHeroAttr>
) {
super(context);
}
getRange(): IRange<IManhattanRangeParam> {
return MANHATTAN_RANGE;
}
getRangeParam(): IManhattanRangeParam {
return {
cx: this.locator.x,
cy: this.locator.y,
radius: 1
};
}
getDamageWithoutCheck(
locator: ITileLocator
): Readonly<IMapDamageInfo> | null {
const deltaX = locator.x - this.locator.x;
const deltaY = locator.y - this.locator.y;
if (Math.abs(deltaX) + Math.abs(deltaY) !== 1) {
return null;
}
if (deltaX <= 0 && deltaY <= 0) {
return null;
}
const otherX = locator.x + deltaX;
const otherY = locator.y + deltaY;
const range = this.getRange();
range.bindHost(this.context);
if (!range.inBound(otherX, otherY)) {
return null;
}
const other = this.context.getEnemyByLoc(otherX, otherY);
if (!other) {
return null;
}
const otherEnemy = other.getComputedEnemy();
if (!otherEnemy.hasSpecial(16)) {
return null;
}
const half = this.hero.getFinalAttribute('hp') / 2;
if (core.flags.betweenAttackMax) {
// 夹击不超伤害值,需要获取两个怪物的伤害
const sys = this.context.getDamageSystem();
if (!sys) {
return this.createInfo(half, MapDamageType.Between);
} else {
const currInfo = sys.getDamageInfoByComputed(this.enemy);
const otherInfo = sys.getDamageInfoByComputed(otherEnemy);
const currDamage = currInfo?.damage ?? Infinity;
const otherDamage = otherInfo?.damage ?? Infinity;
const min = Math.min(half, currDamage, otherDamage);
return this.createInfo(min, MapDamageType.Between);
}
} else {
return this.createInfo(half, MapDamageType.Between);
}
}
}
export class AmbushDamageView extends BaseMapDamageView<IManhattanRangeParam> {
constructor(
context: IEnemyContext<IEnemyAttr, IHeroAttr>,
private readonly locator: Readonly<ITileLocator>
) {
super(context);
}
getRange(): IRange<IManhattanRangeParam> {
return MANHATTAN_RANGE;
}
getRangeParam(): IManhattanRangeParam {
return {
cx: this.locator.x,
cy: this.locator.y,
radius: 1
};
}
getDamageWithoutCheck(): Readonly<IMapDamageInfo> | null {
return this.createInfo(0, MapDamageType.Unknown, {
catch: new Set([this.locator])
});
}
}
//#endregion
//#region 转换器
export class MainMapDamageConverter implements IMapDamageConverter<
IEnemyAttr,
IHeroAttr
> {
convert(
handler: IReadonlyEnemyHandler<IEnemyAttr, IHeroAttr>,
context: IEnemyContext<IEnemyAttr, IHeroAttr>
): IMapDamageView<any>[] {
const views: IMapDamageView<any>[] = [];
const { enemy, locator, hero } = handler;
const zone = enemy.getSpecial<IZoneValue>(15);
if (zone) {
views.push(new ZoneDamageView(context, locator, zone));
}
if (enemy.hasSpecial(16)) {
views.push(new BetweenDamageView(context, enemy, locator, hero));
}
const repulse = enemy.getSpecial<number>(18);
if (repulse) {
views.push(new RepulseDamageView(context, locator, repulse));
}
const laser = enemy.getSpecial<number>(24);
if (laser) {
const face = handler.data.faceManager.get(FaceGroup.Dir4)!;
views.push(new LaserDamageView(context, locator, laser, face));
}
if (enemy.hasSpecial(27)) {
views.push(new AmbushDamageView(context, locator));
}
return views;
}
}
//#endregion
//#region 合并器
export class MainMapDamageReducer implements IMapDamageReducer {
reduce(info: Iterable<Readonly<IMapDamageInfo>>): Readonly<IMapDamageInfo> {
let damage = 0;
let type = MapDamageType.Unknown;
let maxDamage = -Infinity;
const extra = {
catch: new Set<ITileLocator>(),
repulse: new Set<ITileLocator>()
};
for (const item of info) {
damage += item.damage;
if (item.damage > maxDamage) {
maxDamage = item.damage;
type = item.type;
}
item.extra.catch.forEach(v => extra.catch.add(v));
item.extra.repulse.forEach(v => extra.repulse.add(v));
}
return {
damage,
extra,
type
};
}
}
//#endregion