mirror of
https://github.com/motajs/template.git
synced 2026-05-19 16:41:09 +08:00
359 lines
9.9 KiB
TypeScript
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
|