From 0f5b1f4d80f7803a51edac95795084b89af6e1ed Mon Sep 17 00:00:00 2001 From: unanmed <1319491857@qq.com> Date: Sun, 17 May 2026 21:50:49 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20=E5=88=A0=E9=99=A4=E6=97=A7=20Range?= =?UTF-8?q?=20=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dev.md | 2 +- packages-user/data-fallback/src/battle.ts | 270 ------ packages-user/data-fallback/src/damage.ts | 124 --- packages-user/data-fallback/src/index.ts | 4 - packages-user/data-state/src/enemy/damage.ts | 813 ------------------ packages-user/data-utils/src/index.ts | 1 - packages-user/data-utils/src/range.ts | 86 -- .../src/enemy/checkblock.ts | 91 -- .../legacy-plugin-data/src/enemy/index.ts | 7 - packages-user/legacy-plugin-data/src/index.ts | 3 - 10 files changed, 1 insertion(+), 1400 deletions(-) delete mode 100644 packages-user/data-fallback/src/battle.ts delete mode 100644 packages-user/data-fallback/src/damage.ts delete mode 100644 packages-user/data-state/src/enemy/damage.ts delete mode 100644 packages-user/data-utils/src/range.ts delete mode 100644 packages-user/legacy-plugin-data/src/enemy/checkblock.ts delete mode 100644 packages-user/legacy-plugin-data/src/enemy/index.ts diff --git a/dev.md b/dev.md index c124f90..05714d5 100644 --- a/dev.md +++ b/dev.md @@ -45,7 +45,7 @@ ### 模块原则 - **无副作用**:所有模块只包含函数、类、常量的声明,不允许出现导出的变量声明或顶层代码执行,允许但不建议编写类的静态块。 -- **模块初始化**:如需初始化,编写一个 `createXxx` 函数,在 `index.ts` 中整合后逐级向上传递,直至顶层模块统一执行。 +- **模块初始化**:如需初始化,编写一个 `createXxx` 函数,在 `index.ts` 中整合后逐级向上传递,直至顶层模块统一执行。注意,当前设计理念下,**不应该**有场景会需要这种函数。 - **不转发导出**:不允许一个文件导出不属于当前 monorepo 或当前文件夹的内容。 - **无循环引用**:不允许出现循环引用。若遇到不得不循环引用的情况,应首先反思接口设计是否存在问题。 diff --git a/packages-user/data-fallback/src/battle.ts b/packages-user/data-fallback/src/battle.ts deleted file mode 100644 index bcdaacf..0000000 --- a/packages-user/data-fallback/src/battle.ts +++ /dev/null @@ -1,270 +0,0 @@ -import { - DamageEnemy, - ensureFloorDamage, - getEnemy, - state -} from '@user/data-state'; -import { hook } from '@user/data-base'; -import { Patch, PatchClass } from '@motajs/legacy-common'; -import { isNil } from 'lodash-es'; - -export interface CurrentEnemy { - enemy: DamageEnemy; - // 这个是干啥的? - onMapEnemy: DamageEnemy[]; -} - -export function patchBattle() { - const patch = new Patch(PatchClass.Enemys); - const patch2 = new Patch(PatchClass.Events); - - patch.add('canBattle', function (x, y, floorId) { - const enemy = typeof x === 'number' ? getEnemy(x, y!, floorId) : x; - if (!enemy) { - throw new Error( - `Cannot get enemy on x:${x}, y:${y}, floor: ${floorId}` - ); - } - const { damage } = enemy.calDamage(); - - return damage < core.status.hero.hp; - }); - - function battle( - x: number | DamageEnemy, - y: number, - force: boolean = false, - callback?: () => void - ) { - core.saveAndStopAutomaticRoute(); - const isLoc = typeof x === 'number'; - const enemy = isLoc ? getEnemy(x, y) : x; - if (!enemy) { - throw new Error( - `Cannot battle with enemy since no enemy on ${x},${y}` - ); - } - // 非强制战斗 - // @ts-expect-error 2.c 重构 - if (!core.canBattle(x, y) && !force && !core.status.event.id) { - core.stopSound(); - core.playSound('操作失败'); - core.drawTip('你打不过此怪物!', enemy!.id); - return core.clearContinueAutomaticRoute(callback); - } - // 自动存档 - if (!core.status.event.id) core.autosave(true); - // 战前事件 - // 战后事件 - core.afterBattle(enemy, isLoc ? x : enemy.x, y); - callback?.(); - } - - const getFacedId = (enemy: DamageEnemy) => { - const e = enemy.enemy; - - if (e.displayIdInBook) return e.displayIdInBook; - if (e.faceIds) return e.faceIds.down; - return e.id; - }; - - patch.add('getCurrentEnemys', function (floorId = core.status.floorId) { - floorId = floorId || core.status.floorId; - const enemys: CurrentEnemy[] = []; - const used: Record = {}; - ensureFloorDamage(floorId); - const floor = core.status.maps[floorId]; - floor.enemy.list.forEach(v => { - const id = getFacedId(v); - if (!(id in used)) { - const e = new DamageEnemy(v.enemy); - e.calAttribute(); - e.getRealInfo(); - e.calDamage(); - const curr: CurrentEnemy = { - enemy: e, - onMapEnemy: [v] - }; - enemys.push(curr); - used[id] = curr.onMapEnemy; - } else { - used[id].push(v); - } - }); - - return enemys.sort((a, b) => { - const ad = a.enemy.calDamage().damage; - const bd = b.enemy.calDamage().damage; - return ad - bd; - }); - }); - - patch2.add('battle', battle); - - patch2.add('_sys_battle', function (data: Block, callback?: () => void) { - // 检查战前事件 - const floor = core.floors[core.status.floorId]; - const beforeBattle: MotaEvent = []; - const loc = `${data.x},${data.y}` as LocString; - const enemy = getEnemy(data.x, data.y); - - beforeBattle.push(...(floor.beforeBattle[loc] ?? [])); - beforeBattle.push(...(enemy!.enemy.beforeBattle ?? [])); - - if (beforeBattle.length > 0) { - beforeBattle.push({ type: 'battle', x: data.x, y: data.y }); - core.clearContinueAutomaticRoute(); - - // 自动存档 - const inAction = core.status.event.id === 'action'; - if (inAction) { - core.insertAction(beforeBattle, data.x, data.y); - core.doAction(); - } else { - core.autosave(true); - core.insertAction(beforeBattle, data.x, data.y, callback); - } - } else { - battle(data.x, data.y, false, callback); - } - }); - - patch2.add('_action_battle', function (data, x, y, prefix) { - if (data.id) { - // const enemy = getSingleEnemy(data.id as EnemyIds); - // todo: 与不在地图上的怪物战斗 - } else { - if (data.floorId !== core.status.floorId) { - core.doAction(); - return; - } - const [ex, ey] = core.events.__action_getLoc( - data.loc, - x, - y, - prefix - ) as LocArr; - battle(ex, ey, true, core.doAction); - } - }); - - patch2.add( - 'afterBattle', - function (enemy: DamageEnemy, x?: number, y?: number) { - // 播放战斗动画 - let animate: AnimationIds = 'hand'; - // 检查当前装备是否存在攻击动画 - const equipId = core.getEquip(0); - if (equipId && (core.material.items[equipId].equip || {}).animate) - animate = core.material.items[equipId].equip.animate; - - // 检查该动画是否存在SE,如果不存在则使用默认音效 - if (!core.material.animates[animate]?.se) - core.playSound('attack.opus'); - - // 战斗伤害 - const info = enemy.getRealInfo(); - const damageInfo = enemy.calDamage(core.status.hero); - const damage = damageInfo.damage; - // 判定是否致死 - if (damage >= core.status.hero.hp) { - core.status.hero.hp = 0; - core.updateStatusBar(false, true); - core.events.lose('战斗失败'); - return; - } - - // 扣减体力值并记录统计数据 - core.status.hero.hp -= damage; - core.status.hero.statistics.battleDamage += damage; - core.status.hero.statistics.battle++; - - // 获得金币经验 - const money = core.hasFlag('curse') ? 0 : enemy.info.money!; - const exp = core.hasFlag('curse') ? 0 : enemy.info.exp!; - - core.status.hero.money += money; - core.status.hero.statistics.money += money; - core.status.hero.exp += exp; - core.status.hero.statistics.exp += exp; - - const hint = `打败 ${enemy.enemy.name},金币+${money},经验+${exp}`; - core.drawTip(hint, enemy.id); - - // 毒衰咒 - if (info.special.has(12)) core.setFlag('poison', true); - if (info.special.has(13)) core.setFlag('weak', true); - if (info.special.has(14)) core.setFlag('curse', true); - - // 仇恨 - if (info.special.has(17)) { - const hatred = state.flags.getFieldValueDefaults('hatred', 0); - core.setFlag('hatred', hatred / 2); - } else { - core.addFlag('hatred', core.values.hatred); - } - - // 自爆 - if (info.special.has(19)) { - core.status.hero.hp = 1; - } - - // 退化 - if (info.special.has(21)) { - core.status.hero.atk -= info.atkValue ?? 0; - core.status.hero.def -= info.defValue ?? 0; - } - - // 事件的处理 - const todo: MotaEvent = []; - - // 战后事件 - if (!isNil(core.status.floorId)) { - const loc = `${x},${y}` as LocString; - todo.push( - ...(core.floors[core.status.floorId].afterBattle[loc] ?? []) - ); - } - todo.push(...(enemy.enemy.afterBattle ?? [])); - - // 如果事件不为空,将其插入 - if (todo.length > 0) core.insertAction(todo, x, y); - - if (!isNil(x) && !isNil(y)) { - core.drawAnimate(animate, x, y); - core.removeBlock(x, y); - } else core.drawHeroAnimate(animate); - - // 如果已有事件正在处理中 - if (isNil(core.status.event.id)) core.continueAutomaticRoute(); - else core.clearContinueAutomaticRoute(); - - core.checkAutoEvents(); - - hook.emit('afterBattle', enemy, x, y); - } - ); -} - -declare global { - interface Enemys { - getCurrentEnemys(floorId: FloorIds): CurrentEnemy[]; - canBattle(enemy: DamageEnemy, _?: number, floorId?: FloorIds): boolean; - canBattle(x: number, y: number, floorId?: FloorIds): boolean; - } - - interface Events { - battle( - enemy: DamageEnemy, - y?: number, - force?: boolean, - callback?: () => void - ): void; - battle( - x: number, - y?: number, - force?: boolean, - callback?: () => void - ): void; - } -} diff --git a/packages-user/data-fallback/src/damage.ts b/packages-user/data-fallback/src/damage.ts deleted file mode 100644 index 19261bc..0000000 --- a/packages-user/data-fallback/src/damage.ts +++ /dev/null @@ -1,124 +0,0 @@ -import { Patch, PatchClass } from '@motajs/legacy-common'; -import { EnemyCollection, ensureFloorDamage } from '@user/data-state'; -import { formatDamage } from '@user/data-utils'; -import { isNil } from 'lodash-es'; - -export function patchDamage() { - const patch = new Patch(PatchClass.Control); - patch.add( - 'updateDamage', - function ( - floorId = core.status.floorId, - ctx, - thumbnail: boolean = false - ) { - if (!floorId || core.status.gameOver || main.mode !== 'play') - return; - const onMap = isNil(ctx); - const floor = core.status.maps[floorId]; - - // 没有怪物手册 - // if (!core.hasItem('book')) return; - core.status.damage.posX = core.bigmap.posX; - core.status.damage.posY = core.bigmap.posY; - if (!onMap) { - const width = core.floors[floorId].width, - height = core.floors[floorId].height; - // 地图过大的缩略图不绘制显伤 - if (width * height > core.bigmap.threshold) return; - } - // 计算伤害 - ensureFloorDamage(floorId); - - floor.enemy.extract(); - floor.enemy.calRealAttribute(); - floor.enemy.calMapDamage(); - floor.enemy.emit('calculated'); - core.status.damage.data = []; - - // floor.enemy.render(true); - - // getItemDetail(floorId, onMap); // 宝石血瓶详细信息 - if (thumbnail) { - renderThumbnailDamage(floor.enemy); - core.control.drawDamage(ctx, floorId); - } - } - ); -} - -function renderThumbnailDamage(col: EnemyCollection) { - if (main.replayChecking) return; - core.status.damage.data = []; - core.status.damage.extraData = []; - - // 怪物伤害 - col.list.forEach(v => { - const { damage } = v.calDamage(); - - // 伤害全部相等,绘制在怪物本身所在位置 - const { damage: dam, color } = formatDamage(damage); - const critical = v.calCritical(1)[0]; - core.status.damage.data.push({ - text: dam, - px: 32 * v.x! + 1, - py: 32 * (v.y! + 1) - 1, - color: color - }); - const setting = Mota.require('@motajs/legacy-ui').mainSetting; - const criGem = setting.getValue('screen.criticalGem', false); - const n = critical?.atkDelta ?? Infinity; - const ratio = core.status.maps[col.floorId].ratio; - const cri = criGem ? Math.ceil(n / ratio) : n; - - core.status.damage.data.push({ - text: isFinite(cri) ? cri.toString() : '?', - px: 32 * v.x! + 1, - py: 32 * (v.y! + 1) - 11, - color: '#fff' - }); - }); - - // 地图伤害 - const floor = core.status.maps[col.floorId]; - const width = floor.width; - const height = floor.height; - const objs = core.getMapBlocksObj(col.floorId); - - const startX = 0; - const endX = width; - const startY = 0; - const endY = height; - - for (let x = startX; x < endX; x++) { - for (let y = startY; y < endY; y++) { - const id = `${x},${y}` as LocString; - const dam = col.mapDamage[id]; - if (!dam || objs[id]?.event.noPass) continue; - - // 地图伤害 - if (dam.damage !== 0) { - const damage = core.formatBigNumber(dam.damage, true); - const color = dam.damage < 0 ? '#6eff6a' : '#fa3'; - core.status.damage.extraData.push({ - text: damage, - px: 32 * x + 16, - py: 32 * y + 16, - color, - alpha: 1 - }); - } - - // 追猎 - if (dam.ambush) { - core.status.damage.extraData.push({ - text: '!', - px: 32 * x + 16, - py: 32 * (y + 1) - 14, - color: '#fd4', - alpha: 1 - }); - } - } - } -} diff --git a/packages-user/data-fallback/src/index.ts b/packages-user/data-fallback/src/index.ts index 525aaa6..71800e6 100644 --- a/packages-user/data-fallback/src/index.ts +++ b/packages-user/data-fallback/src/index.ts @@ -1,12 +1,8 @@ import { ICoreState } from '@user/data-state'; -import { patchBattle } from './battle'; -import { patchDamage } from './damage'; import { patchFlags } from './flag'; import { patchHero } from './hero'; export function patchAll(state: ICoreState) { - patchBattle(); - patchDamage(); patchFlags(state); patchHero(state); } diff --git a/packages-user/data-state/src/enemy/damage.ts b/packages-user/data-state/src/enemy/damage.ts deleted file mode 100644 index 46d5774..0000000 --- a/packages-user/data-state/src/enemy/damage.ts +++ /dev/null @@ -1,813 +0,0 @@ -import { getHeroStatusOf, getHeroStatusOn } from '../legacy/hero'; -import { Range, ensureArray, has, manhattan } from '@user/data-utils'; -import EventEmitter from 'eventemitter3'; -import { hook } from '@user/data-base'; -import { - EnemyInfo, - DamageInfo, - DamageDelta, - HaloData, - CriticalDamageDelta, - MapDamage, - HaloFn, - IEnemyCollection, - IDamageEnemy, - HaloType, - IEnemyCollectionEvent -} from '@motajs/types'; - -export class EnemyCollection - extends EventEmitter - implements IEnemyCollection -{ - floorId: FloorIds; - list: Map = new Map(); - - range: Range = new Range(); - /** 地图伤害 */ - mapDamage: Record = {}; - haloList: HaloData[] = []; - - /** 楼层宽度 */ - width: number = 0; - /** 楼层高度 */ - height: number = 0; - - constructor(floorId: FloorIds) { - super(); - this.floorId = floorId; - this.extract(); - } - - get(x: number, y: number) { - const index = x + y * this.width; - return this.list.get(index) ?? null; - } - - /** - * 解析本地图的怪物信息 - */ - extract() { - this.list.clear(); - core.extractBlocks(this.floorId); - const floor = core.status.maps[this.floorId]; - this.width = floor.width; - this.height = floor.height; - floor.blocks.forEach(v => { - if (v.disable) return; - if (v.event.cls !== 'enemy48' && v.event.cls !== 'enemys') return; - const { x, y } = v; - const index = x + y * this.width; - const enemy = core.material.enemys[v.event.id as EnemyIds]; - this.list.set( - index, - new DamageEnemy(enemy, v.x, v.y, this.floorId, this) - ); - }); - this.emit('extract'); - hook.emit('enemyExtract', this); - } - - /** - * 计算怪物真实属性 - */ - calRealAttribute() { - this.haloList = []; - this.list.forEach(v => { - v.reset(); - }); - this.list.forEach(v => { - v.preProvideHalo(); - }); - this.list.forEach(v => { - v.calAttribute(); - v.provideHalo(); - }); - this.list.forEach(v => { - v.getRealInfo(); - }); - } - - /** - * 计算怪物伤害 - * @param noCache 是否不使用缓存 - */ - calDamage(noCache: boolean = false) { - if (noCache) this.calRealAttribute(); - this.list.forEach(v => { - v.calDamage(void 0); - }); - } - - /** - * 计算地图伤害 - */ - calMapDamage() { - this.mapDamage = {}; - const hero = getHeroStatusOn(realStatus, this.floorId); - this.list.forEach(v => { - v.calMapDamage(this.mapDamage, hero); - }); - } - - /** - * 向怪物施加光环 - * @param type 光环的范围类型 - * @param data 光环范围信息 - * @param halo 光环效果函数 - * @param recursion 是否递归施加,只有在光环预平衡阶段会使用到 - */ - applyHalo( - type: K, - data: HaloType[K], - enemy: DamageEnemy, - halo: HaloFn | HaloFn[], - recursion: boolean = false - ) { - const arr = ensureArray(halo); - const enemys = this.range.type(type).scan(this.list.values(), data); - if (!recursion) { - arr.forEach(v => { - enemys.forEach(e => { - e.injectHalo(v, enemy.info); - }); - }); - } else { - enemys.forEach(e => { - arr.forEach(v => { - e.injectHalo(v, enemy.info); - e.preProvideHalo(); - }); - }); - } - } - - /** - * 预平衡光环 - */ - preBalanceHalo() { - this.list.forEach(v => { - v.preProvideHalo(); - }); - } -} - -export class DamageEnemy implements IDamageEnemy { - id: EnemyIds; - x?: number; - y?: number; - floorId?: FloorIds; - enemy: Enemy; - col?: EnemyCollection; - - /** - * 怪物属性。 - * 属性计算流程:预平衡光环(即计算加光环的光环怪的光环) -> 计算怪物在没有光环下的属性 - * -> provide inject 光环 -> 计算怪物的光环加成 -> 计算完毕 - */ - info!: EnemyInfo; - - /** 向其他怪提供过的光环 */ - providedHalo: Set = new Set(); - - /** - * 伤害计算进度,0 -> 预平衡光环 -> 1 -> 计算没有光环的属性 -> 2 -> provide inject 光环 - * -> 3 -> 计算光环加成 -> 4 -> 计算完毕 - */ - progress: number = 0; - - constructor( - enemy: Enemy, - x?: number, - y?: number, - floorId?: FloorIds, - col?: EnemyCollection - ) { - this.id = enemy.id; - this.enemy = enemy; - this.x = x; - this.y = y; - this.floorId = floorId; - this.col = col; - this.reset(); - } - - reset() { - const enemy = this.enemy; - this.info = { - hp: enemy.hp, - atk: enemy.atk, - def: enemy.def, - special: new Set(enemy.special), - atkBuff_: 0, - defBuff_: 0, - hpBuff_: 0, - guard: [], - enemy: this.enemy, - x: this.x, - y: this.y, - floorId: this.floorId - }; - - for (const [key, value] of Object.entries(enemy)) { - if (!(key in this.info) && has(value)) { - // @ts-expect-error 无法推导 - this.info[key] = value; - } - } - this.progress = 0; - this.providedHalo.clear(); - } - - /** - * 计算怪物在不计光环下的属性,在inject光环之前,预平衡光环之后执行 - */ - calAttribute() { - if (this.progress !== 1 && has(this.x) && has(this.floorId)) return; - this.progress = 2; - const special = this.info.special; - const info = this.info; - - const { atk = 0, def = 0 } = getHeroStatusOn(realStatus); - - // 坚固 - if (special.has(3)) { - info.def = Math.max(info.def, atk - 1); - } - - // 模仿 - if (special.has(10)) { - info.atk = atk; - info.def = def; - } - } - - /** - * 获取怪物的真实属性信息,在inject光环后执行 - */ - getRealInfo() { - if (this.progress < 3 && has(this.x) && has(this.floorId)) { - throw new Error( - `Unexpected early real info calculating. Progress: ${this.progress}` - ); - } - if (this.progress === 4) return this.info; - this.progress = 4; - - // 此时已经inject光环,因此直接计算真实属性 - const info = this.info; - - info.atk = Math.floor(info.atk * (info.atkBuff_ / 100 + 1)); - info.def = Math.floor(info.def * (info.defBuff_ / 100 + 1)); - info.hp = Math.floor(info.hp * (info.hpBuff_ / 100 + 1)); - - return this.info; - } - - /** - * 光环预提供,用于平衡所有怪的光环属性,避免出现不同情况下光环效果不一致的现象 - */ - preProvideHalo() { - if (this.progress !== 0) return; - this.progress = 1; - if (!this.floorId) return; - if (!has(this.x) || !has(this.y)) return; - - // 这里可以做优先级更高的光环,比如加光环的光环怪等,写法与 provideHalo 类似 - - // e 是被加成怪的属性,enemy 是施加光环的怪 - } - - /** - * 向其他怪提供光环 - */ - provideHalo() { - if (this.progress !== 2) return; - this.progress = 3; - if (!this.floorId) return; - if (!has(this.x) || !has(this.y)) return; - const col = this.col ?? core.status.maps[this.floorId].enemy; - if (!col) return; - const special = this.info.special; - - // e 是被加成怪的属性,enemy 是施加光环的怪 - - // 普通光环 - if (special.has(25)) { - // 光环效果,这里直接增加 e 的 buff 属性 - const halo = (e: EnemyInfo, enemy: EnemyInfo) => { - if (enemy.haloAdd) { - e.hpBuff_ += enemy.hpBuff ?? 0; - e.atkBuff_ += enemy.atkBuff ?? 0; - e.defBuff_ += enemy.defBuff ?? 0; - } else { - e.hpBuff_ = Math.max(e.hpBuff_, enemy.hpBuff ?? 0); - e.atkBuff_ = Math.max(e.atkBuff_, enemy.atkBuff ?? 0); - e.defBuff_ = Math.max(e.defBuff_, enemy.defBuff ?? 0); - } - }; - // 根据范围施加光环 - const range = this.info.haloRange ?? 1; - if (this.info.haloSquare) { - col.applyHalo( - 'square', - { x: this.x, y: this.y, d: range * 2 + 1 }, - this, - halo - ); - } else { - col.applyHalo( - 'manhattan', - { x: this.x, y: this.y, d: range }, - this, - halo - ); - } - } - - // 支援也是一类光环 - if (special.has(26)) { - col.applyHalo( - 'square', - { x: this.x, y: this.y, d: 3 }, - this, - (e, enemy) => { - e.guard.push(enemy); - } - ); - } - } - - /** - * 接受其他怪的光环 - */ - injectHalo(halo: HaloFn, enemy: EnemyInfo) { - halo(this.info, enemy); - } - - /** - * 计算怪物伤害 - */ - calDamage(hero: Partial = core.status.hero): DamageInfo { - const enemy = this.getRealInfo(); - return this.calEnemyDamageOf(hero, enemy); - } - - /** - * 计算地图伤害 - * @param damage 存入的对象 - */ - calMapDamage( - damage: Record = {}, - _hero: Partial = getHeroStatusOn(realStatus) - ) { - if (!has(this.x) || !has(this.y) || !has(this.floorId)) return damage; - const enemy = this.enemy; - const floor = core.status.maps[this.floorId]; - const w = floor.width; - const h = floor.height; - const objs = core.getMapBlocksObj(this.floorId); - - // 领域 - if (this.info.special.has(15)) { - const range = enemy.range ?? 1; - const startX = Math.max(0, this.x - range); - const startY = Math.max(0, this.y - range); - const endX = Math.min(floor.width - 1, this.x + range); - const endY = Math.min(floor.height - 1, this.y + range); - const dam = Math.max(enemy.zone ?? 0, 0); - - for (let x = startX; x <= endX; x++) { - for (let y = startY; y <= endY; y++) { - if ( - !enemy.zoneSquare && - manhattan(x, y, this.x, this.y) > range - ) { - // 如果是十字范围而且曼哈顿距离大于范围,则跳过此格 - continue; - } - const loc = `${x},${y}` as LocString; - if (objs[loc]?.event.noPass) continue; - this.setMapDamage(damage, loc, dam, '领域'); - } - } - } - - // 激光 - if (this.info.special.has(24)) { - const dirs: Dir[] = ['left', 'down', 'up', 'right']; - const dam = Math.max(enemy.laser ?? 0, 0); - - for (const dir of dirs) { - let x = this.x; - let y = this.y; - const { x: dx, y: dy } = core.utils.scan[dir]; - while (x >= 0 && y >= 0 && x < w && y < h) { - x += dx; - y += dy; - const loc = `${x},${y}` as LocString; - if (objs[loc]?.event.noPass) continue; - this.setMapDamage(damage, loc, dam, '激光'); - } - } - } - - // 阻击 - if (this.info.special.has(18)) { - const dirs: Dir[] = ['left', 'down', 'up', 'right']; - for (const dir of dirs) { - const { x: dx, y: dy } = core.utils.scan[dir]; - const x = this.x + dx; - const y = this.y + dy; - const loc = `${x},${y}` as LocString; - if (objs[loc]?.event.noPass) continue; - this.setMapDamage(damage, loc, this.info.repulse ?? 0, '阻击'); - damage[loc].repulse ??= []; - damage[loc].repulse.push([this.x, this.y]); - } - } - - // 捕捉 - if (this.info.special.has(27)) { - const dirs: Dir[] = ['left', 'down', 'up', 'right']; - for (const dir of dirs) { - const { x: dx, y: dy } = core.utils.scan[dir]; - const x = this.x + dx; - const y = this.y + dy; - const loc = `${x},${y}` as LocString; - if (objs[loc]?.event.noPass) continue; - damage[loc] ??= { damage: 0, type: new Set() }; - damage[loc].ambush ??= []; - damage[loc].ambush.push([this.x, this.y]); - } - } - - // 夹击 - if (this.info.special.has(16)) { - // 只计算右方和下方的怪物,这样就可以避免一个点被重复计算两次 - const dirs: Dir[] = ['down', 'right']; - for (const dir of dirs) { - const { x: dx, y: dy } = core.utils.scan[dir]; - const x = this.x + dx * 2; - const y = this.y + dy * 2; - const e = this.col?.get(x, y); - if (!e) continue; - const info = e.getRealInfo(); - if (!info.special.has(16)) continue; - const cx = this.x + dx; - const cy = this.y + dy; - const loc = `${cx},${cy}` as LocString; - if (objs[loc]?.event.noPass) continue; - const half = getHeroStatusOn('hp') / 2; - let bt = half; - // 夹击不超伤害值 - if (core.flags.betweenAttackMax) { - const aDamage = this.calDamage().damage; - const bDamage = e.calDamage().damage; - bt = Math.min(aDamage, bDamage, half); - } - this.setMapDamage(damage, loc, bt, '夹击'); - } - } - - return damage; - } - - private setMapDamage( - damage: Record, - loc: string, - dam: number, - type: string - ) { - damage[loc] ??= { damage: 0, type: new Set() }; - damage[loc].damage += dam; - if (type) damage[loc].type.add(type); - } - - private calEnemyDamageOf( - hero: Partial, - enemy: EnemyInfo - ): DamageInfo { - const status = getHeroStatusOf(hero, realStatus, this.floorId); - const damage = calDamageWith(enemy, status) ?? Infinity; - - return { damage }; - } - - /** - * 计算怪物临界,计算临界时,根据当前方向计算临界,但也会输出与当前最少伤害的伤害差值 - * @param num 要计算多少个临界 - * @param dir 从怪物位置指向勇士的方向 - * @param hero 勇士属性,最终结果将会与由此属性计算出的伤害相减计算减伤 - */ - calCritical( - num: number = 1, - hero: Partial = core.status.hero - ): CriticalDamageDelta[] { - const origin = this.calDamage(hero); - const seckill = this.getSeckillAtk(); - return this.calCriticalWith(num, seckill, origin, hero); - } - - /** - * 二分计算怪物临界 - * @param num 计算的临界数量 - * @param min 当前怪物伤害最小值 - * @param seckill 秒杀怪物时的攻击 - * @param hero 勇士真实属性 - */ - private calCriticalWith( - num: number, - seckill: number, - origin: DamageInfo, - hero: Partial - ): CriticalDamageDelta[] { - if (!isFinite(seckill)) return []; - - const res: CriticalDamageDelta[] = []; - const def = hero.def!; - const precision = - (seckill < Number.MAX_SAFE_INTEGER ? 1 : seckill / 1e15) * 2; - const enemy = this.getRealInfo(); - - let curr = hero.atk!; - let start = curr; - let end = seckill; - let ori = origin.damage; - - const status = { atk: curr, def }; - - const calDam = () => { - status.atk = curr; - return this.calEnemyDamageOf(status, enemy).damage; - }; - - let i = 0; - while (res.length < num) { - if (end - start <= precision) { - // 到达二分所需精度,计算临界准确值 - let cal = false; - for (const v of [(start + end) / 2, end]) { - curr = v; - const dam = calDam(); - if (dam < ori) { - res.push({ - damage: dam, - atkDelta: Math.ceil(v - hero.atk!), - delta: -(dam - origin.damage) - }); - - start = v; - end = seckill; - cal = true; - ori = dam; - break; - } - } - if (!cal) break; - } - curr = Math.floor((start + end) / 2); - - const damage = calDam(); - - if (damage < ori) { - end = curr; - } else { - start = curr; - } - if (i++ >= 10000) { - // eslint-disable-next-line no-console - console.warn( - `Unexpected endless loop in calculating critical.` + - `Enemy Id: ${this.id}. Loc: ${this.x},${this.y}. Floor: ${this.floorId}` - ); - break; - } - } - - if (res.length === 0) { - curr = hero.atk!; - const dam = calDam(); - res.push({ - damage: dam, - atkDelta: 0, - delta: 0 - }); - } - - return res; - } - - /** - * 计算n防减伤 - * @param num 要加多少防御 - * @param dir 从怪物位置指向勇士的方向 - * @param hero 勇士属性,最终结果将会与由此属性计算出的伤害相减计算减伤 - */ - calDefDamage( - num: number = 1, - hero: Partial = core.status.hero - ): DamageDelta { - const damage = this.calDamage({ - def: (hero.def ?? core.status.hero.def) + num - }); - const origin = this.calDamage(hero); - const finite = isFinite(damage.damage); - - return { - damage: damage.damage, - info: damage, - delta: -(finite ? damage.damage - origin.damage : Infinity) - }; - } - - /** - * 获取怪物秒杀时所需的攻击 - */ - getSeckillAtk(): number { - const info = this.getRealInfo(); - - // 坚固,不可能通过攻击秒杀 - if (info.special.has(3)) { - return Infinity; - } - - // 常规怪物秒杀攻击是怪物防御+怪物生命 - return info.def + info.hp; - } -} - -export interface DamageWithTurn { - damage: number; - turn: number; -} - -/** - * 计算伤害时会用到的勇士属性,攻击防御,其余的不会有buff加成,直接从core.status.hero取 - * 如果有属性不会被 buff 加成请在这里去除,有助于提高性能表现 - */ -const realStatus: (keyof HeroStatus)[] = ['atk', 'def', 'mdef', 'hpmax']; - -/** 当前是否正在计算支援怪的伤害 */ -let inGuard = false; - -/** - * 计算伤害,返回值包含伤害与回合数 - * @param info 怪物信息 - * @param hero 勇士真实属性 - */ -export function calDamageWithTurn( - info: EnemyInfo, - hero: Partial -): DamageWithTurn { - const { hp } = core.status.hero; - const { atk, def, mdef } = hero as HeroStatus; - const { atk: monAtk, def: monDef, special } = info; - let { hp: monHp } = info; - - // 无敌 - if (special.has(20) && core.itemCount('cross') < 1) { - return { damage: Infinity, turn: 0 }; - } - - /** 怪物会对勇士造成的总伤害 */ - let damage = 0; - - /** 勇士每轮造成的伤害 */ - let heroPerDamage: number = 0; - /** 怪物每轮造成的伤害 */ - let enemyPerDamage: number = 0; - - // 勇士每轮伤害为勇士攻击减去怪物防御 - heroPerDamage += atk - monDef; - - // 吸血 - if (special.has(11)) { - const vampire = info.vampire ?? 0; - const value = (vampire / 100) * hp; - damage += value; - // 如果吸血加到自身 - if (info.add) { - monHp += value; - } - } - - // 魔攻 - if (special.has(2)) { - enemyPerDamage = monAtk; - } else { - enemyPerDamage = monAtk - def; - } - - // 连击 - if (special.has(4)) enemyPerDamage *= 2; - if (special.has(5)) enemyPerDamage *= 3; - if (special.has(6)) enemyPerDamage *= info.n!; - - if (enemyPerDamage < 0) enemyPerDamage = 0; - - let turn = Math.ceil(monHp / heroPerDamage); - - // 支援,当怪物被支援且不包含支援标记时执行,因为支援怪不能再被支援了 - if (info.guard.length > 0 && !inGuard) { - inGuard = true; - // 支援中魔防只会被计算一次,因此除了当前怪物,计算其他怪物伤害时魔防为 0 - const status = { ...hero, mdef: 0 }; - // 计算支援怪的伤害,同时把打支援怪花费的回合数加到当前怪物上,因为打支援怪的时候当前怪物也会打你 - // 因此回合数需要加上打支援怪的回合数 - for (const enemy of info.guard) { - // 直接把 enemy 传过去,因此支援的 enemy 会吃到其原本所在位置的光环加成 - const extraInfo = calDamageWithTurn(enemy, status); - turn += extraInfo.turn; - damage += extraInfo.damage; - } - inGuard = false; - } - - // 先攻 - if (special.has(1)) { - damage += enemyPerDamage; - } - - // 破甲 - if (special.has(7)) { - const value = info.breakArmor ?? core.values.breakArmor; - damage += (value / 100) * def; - } - - // 反击 - if (special.has(8)) { - const value = info.counterAttack ?? core.values.counterAttack; - // 反击是每回合生效,因此加到 enemyPerDamage 上 - enemyPerDamage += (value / 100) * atk; - } - - // 净化 - if (special.has(9)) { - const value = info.purify ?? core.values.purify; - damage += mdef * value; - } - - damage += (turn - 1) * enemyPerDamage; - - // 魔防 - damage -= mdef; - - // 未开启负伤时,如果伤害为负,则设为 0 - if (!core.flags.enableNegativeDamage && damage < 0) { - damage = 0; - } - - // 固伤,无法被魔防减伤 - if (special.has(22)) { - damage += info.damage ?? 0; - } - - // 仇恨,无法被魔防减伤 - if (special.has(17)) { - damage += core.getFlag('hatred', 0); - } - - return { damage: Math.floor(damage), turn }; -} - -/** - * 计算怪物伤害 - * @param info 怪物信息 - * @param hero 勇士信息 - */ -export function calDamageWith( - info: EnemyInfo, - hero: Partial -): number { - return calDamageWithTurn(info, hero).damage; -} - -export function ensureFloorDamage(floorId: FloorIds) { - const floor = core.status.maps[floorId]; - floor.enemy ??= new EnemyCollection(floorId); -} - -export function getSingleEnemy(id: EnemyIds) { - const e = core.material.enemys[id]; - const enemy = new DamageEnemy(e); - enemy.calAttribute(); - enemy.getRealInfo(); - enemy.calDamage(core.status.hero); - return enemy; -} - -export function getEnemy( - x: number, - y: number, - floorId: FloorIds = core.status.floorId -) { - const enemy = core.status.maps[floorId].enemy.get(x, y); - return enemy; -} - -declare global { - interface Floor { - enemy: EnemyCollection; - } -} diff --git a/packages-user/data-utils/src/index.ts b/packages-user/data-utils/src/index.ts index 43ce4e4..04bca77 100644 --- a/packages-user/data-utils/src/index.ts +++ b/packages-user/data-utils/src/index.ts @@ -1,2 +1 @@ -export * from './range'; export * from './utils'; diff --git a/packages-user/data-utils/src/range.ts b/packages-user/data-utils/src/range.ts deleted file mode 100644 index 5c73819..0000000 --- a/packages-user/data-utils/src/range.ts +++ /dev/null @@ -1,86 +0,0 @@ -import { isNil } from 'lodash-es'; - -interface RangeTypeData { - square: { x: number; y: number; d: number }; - rect: { x: number; y: number; w: number; h: number }; - manhattan: { x: number; y: number; d: number }; -} - -type InRangeFn, T> = (item: E, data: T) => boolean; - -export class Range { - static rangeType: Record = {}; - - /** - * 获取一个范围类型,并进行判断 - * @param type 范围类型 - */ - type( - type: T - ): T extends keyof RangeTypeData ? RangeType : RangeType { - return Range.rangeType[type] as T extends keyof RangeTypeData - ? RangeType - : RangeType; - } - - /** - * 注册一个新的范围类型 - * @param type 范围类型 - * @param fn 判断是否在范围内的函数 - */ - static register( - type: K, - fn: InRangeFn, RangeTypeData[K]> - ): void; - static register(type: string, fn: InRangeFn, any>): void; - static register(type: string, fn: InRangeFn, any>): void { - const range = new RangeType(type, fn); - this.rangeType[type] = range; - } -} - -class RangeType { - readonly type: string; - /** - * 判断一个元素是否在指定范围内 - * @param item 要判断的元素 - * @param data 范围数据 - */ - readonly inRange: InRangeFn, Type>; - - constructor(type: string, fn: InRangeFn, Type>) { - this.type = type; - this.inRange = fn; - } - - /** - * 扫描一个列表中所有在范围内的元素 - * @param items 元素列表 - * @param data 范围数据 - */ - scan>(items: Iterable, data: Type): T[] { - const res: T[] = []; - for (const ele of items) { - if (this.inRange(ele, data)) { - res.push(ele); - } - } - return res; - } -} - -Range.register('square', (item, { x, y, d }) => { - if (isNil(item.x) || isNil(item.y)) return false; - const r = Math.floor(d / 2); - return Math.abs(item.x - x) <= r && Math.abs(item.y - y) <= r; -}); -Range.register('rect', (item, { x, y, w, h }) => { - if (isNil(item.x) || isNil(item.y)) return false; - const ex = x + w; - const ey = y + h; - return item.x >= x && item.y >= y && item.x < ex && item.y < ey; -}); -Range.register('manhattan', (item, { x, y, d }) => { - if (isNil(item.x) || isNil(item.y)) return false; - return Math.abs(item.x - x) + Math.abs(item.y - y) < d; -}); diff --git a/packages-user/legacy-plugin-data/src/enemy/checkblock.ts b/packages-user/legacy-plugin-data/src/enemy/checkblock.ts deleted file mode 100644 index f1294a9..0000000 --- a/packages-user/legacy-plugin-data/src/enemy/checkblock.ts +++ /dev/null @@ -1,91 +0,0 @@ -import { DamageEnemy } from '@user/data-state'; -import { findDir, ofDir } from '@user/data-utils'; - -export function createCheckBlock() { - // 地图伤害在这实现。2.C 会修改实现方式 - control.prototype.checkBlock = function () { - const heroLoc = core.status.hero.loc; - const { x, y } = heroLoc; - const loc = `${x},${y}`; - const col = core.status.thisMap.enemy; - const info = col.mapDamage[loc]; - if (!info) return; - const damage = info.damage; - - // 阻击夹域伤害 - if (damage) { - core.status.hero.hp -= damage; - const type = [...info.type]; - const text = type.join(',') || '伤害'; - core.drawTip('受到' + text + damage + '点'); - core.drawHeroAnimate('zone'); - this._checkBlock_disableQuickShop(); - core.status.hero.statistics.extraDamage += damage; - if (core.status.hero.hp <= 0) { - core.status.hero.hp = 0; - core.updateStatusBar(); - core.events.lose(); - return; - } else { - core.updateStatusBar(); - } - } - - const actions: MotaAction[] = []; - - // 阻击效果 - if (info.repulse) { - for (const [x, y] of info.repulse) { - const loc2 = { x, y }; - const dir = findDir(heroLoc, loc2); - if (dir === 'none') continue; - const [nx, ny] = ofDir(x, y, dir); - if (core.noPass(nx, ny) || !core.canMoveHero(x, y, dir)) { - continue; - } - actions.push({ - type: 'move', - time: 250, - keep: true, - loc: [x, y], - steps: [`${dir}:1`], - async: true - }); - } - } - - /** 存储要和哪些捕捉怪战斗 */ - const ambushEnemies: DamageEnemy[] = []; - - // 捕捉效果 - if (info.ambush) { - for (const [x, y] of info.ambush) { - const loc2 = { x, y }; - const dir = findDir(loc2, heroLoc); - if (dir === 'none') continue; - actions.push({ - type: 'move', - time: 250, - keep: false, - loc: [x, y], - steps: [`${dir}:1`], - async: true - }); - const enemy = col.get(x, y); - if (enemy) { - ambushEnemies.push(enemy); - } - } - } - - if (actions.length > 0) { - actions.push({ type: 'waitAsync' }); - // 与捕捉怪战斗 - core.insertAction(actions, void 0, void 0, () => { - ambushEnemies.forEach(v => { - core.battle(v, v.y, true); - }); - }); - } - }; -} diff --git a/packages-user/legacy-plugin-data/src/enemy/index.ts b/packages-user/legacy-plugin-data/src/enemy/index.ts deleted file mode 100644 index c3a151a..0000000 --- a/packages-user/legacy-plugin-data/src/enemy/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { createCheckBlock } from './checkblock'; - -export function createEnemy() { - createCheckBlock(); -} - -export * from './checkblock'; diff --git a/packages-user/legacy-plugin-data/src/index.ts b/packages-user/legacy-plugin-data/src/index.ts index 278ba30..fb04005 100644 --- a/packages-user/legacy-plugin-data/src/index.ts +++ b/packages-user/legacy-plugin-data/src/index.ts @@ -4,12 +4,10 @@ import { initFiveLayer } from './fiveLayer'; import { createHook } from './hook'; import { initReplay } from './replay'; import { initUI } from './ui'; -import { createEnemy } from './enemy'; export function createLegacy() { initFallback(); loading.once('coreInit', () => { - createEnemy(); initFiveLayer(); createHook(); initReplay(); @@ -17,7 +15,6 @@ export function createLegacy() { }); } -export * from './enemy'; export * from './fallback'; export * from './fiveLayer'; export * from './removeMap';