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 implements IMapDamageView { constructor( protected readonly context: IEnemyContext ) {} abstract getRange(): IRange; abstract getRangeParam(): T; getDamageAt(locator: ITileLocator): Readonly | 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 | null; /** * 创建伤害信息 * @param damage 伤害值 * @param type 伤害类型 * @param extra 额外信息 */ protected createInfo( damage: number, type: number, extra?: Partial ): 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, private readonly locator: Readonly, private readonly special: Readonly> ) { super(context); } getRange(): IRange { 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 | null { return this.createInfo(this.special.value.zone, MapDamageType.Zone); } } export class RepulseDamageView extends BaseMapDamageView { constructor( context: IEnemyContext, private readonly locator: Readonly, private readonly special: Readonly> ) { super(context); } getRange(): IRange { return MANHATTAN_RANGE; } getRangeParam(): IManhattanRangeParam { return { cx: this.locator.x, cy: this.locator.y, radius: 1 }; } getDamageWithoutCheck( locator: ITileLocator ): Readonly | 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 { /** 激光方向列表 */ private readonly dirs: IDirectionDescriptor[]; constructor( context: IEnemyContext, private readonly locator: Readonly, private readonly special: Readonly>, dir: IFaceHandler ) { super(context); this.dirs = [...dir.mapMovement()].map(v => v[1]); } getRange(): IRange { return RAY_RANGE; } getRangeParam(): IRayRangeParam { return { cx: this.locator.x, cy: this.locator.y, dir: this.dirs }; } getDamageWithoutCheck(): Readonly | null { return this.createInfo(this.special.value, MapDamageType.Layer); } } export class BetweenDamageView extends BaseMapDamageView { constructor( context: IEnemyContext, private readonly enemy: IReadonlyEnemy, private readonly locator: Readonly, private readonly hero: IReadonlyHeroAttribute ) { super(context); } getRange(): IRange { return MANHATTAN_RANGE; } getRangeParam(): IManhattanRangeParam { return { cx: this.locator.x, cy: this.locator.y, radius: 1 }; } getDamageWithoutCheck( locator: ITileLocator ): Readonly | 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 { constructor( context: IEnemyContext, private readonly locator: Readonly ) { super(context); } getRange(): IRange { return MANHATTAN_RANGE; } getRangeParam(): IManhattanRangeParam { return { cx: this.locator.x, cy: this.locator.y, radius: 1 }; } getDamageWithoutCheck(): Readonly | null { return this.createInfo(0, MapDamageType.Unknown, { catch: new Set([this.locator]) }); } } //#endregion //#region 转换器 export class MainMapDamageConverter implements IMapDamageConverter< IEnemyAttr, IHeroAttr > { convert( handler: IReadonlyEnemyHandler, context: IEnemyContext ): IMapDamageView[] { const views: IMapDamageView[] = []; const { enemy, locator, hero } = handler; const zone = enemy.getSpecial(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(18); if (repulse) { views.push(new RepulseDamageView(context, locator, repulse)); } const laser = enemy.getSpecial(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 { let damage = 0; let type = MapDamageType.Unknown; let maxDamage = -Infinity; const extra = { catch: new Set(), repulse: new Set() }; 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