import { getHeroStatusOf, getHeroStatusOn } from '../state/hero'; import { Range, ensureArray, has, manhattan } from '@user/data-utils'; import EventEmitter from 'eventemitter3'; import { hook } from '@user/data-base'; import { HeroSkill, NightSpecial } from '../mechanism/misc'; import { EnemyInfo, DamageInfo, DamageDelta, HaloData, CriticalDamageDelta, MapDamage, HaloFn, IEnemyCollection, IDamageEnemy, HaloType } from '@motajs/types'; // todo: 光环划分优先级,从而可以实现光环的多级运算 export interface UserEnemyInfo extends EnemyInfo { togetherNum?: number; } /** 光环属性 */ export const haloSpecials: Set = new Set([ 8, 21, 25, 26, 27, 29, 31, 32 ]); /** 不可被同化的属性 */ export const unassimilatable: Set = new Set(haloSpecials); unassimilatable.add(8).add(30).add(33); /** 特殊属性对应 */ export const specialValue: Map[]> = new Map(); specialValue .set(1, ['crit']) .set(6, ['n']) .set(7, ['hungry']) .set(8, ['together']) .set(10, ['courage']) .set(11, ['charge']) .set(15, ['value']) .set(18, ['value']) .set(20, ['ice']) .set(21, ['iceHalo']) .set(22, ['night']) .set(23, ['day']) .set(25, ['melt']) .set(26, ['iceCore']) .set(27, ['fireCore']) .set(28, ['paleShield']) .set(31, ['hpHalo']) .set(32, ['assimilateRange']); interface EnemyCollectionEvent { extract: []; calculated: []; } 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; /** 乾坤挪移属性 */ translation: [number, number] = [0, 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.translation = [0, 0]; 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!: UserEnemyInfo; /** 向其他怪提供过的光环 */ 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), damageDecline: 0, atkBuff_: 0, defBuff_: 0, hpBuff_: 0, 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-ignore this.info[key] = value; } } this.progress = 0; this.providedHalo.clear(); // 在这里计算乾坤挪移 if (this.col && enemy.special.includes(30)) { this.col.translation[0] += enemy.translation![0]; this.col.translation[1] += enemy.translation![1]; } } /** * 计算怪物在不计光环下的属性,在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 floorId = this.floorId ?? core.status.floorId; let [dx, dy] = [0, 0]; const col = this.col ?? core.status.maps[this.floorId!]?.enemy; if (col) { [dx, dy] = col.translation; } // 智慧之源 if (flags.hard === 2 && special.has(14)) { info.atk += flags[`inte_${floorId}`] ?? 0; } // 极昼永夜 const night = NightSpecial.getNight(floorId); info.atk -= night; info.def -= night; // 融化,融化不属于怪物光环,因此不能用provide和inject计算,需要在这里计算 const melt = flags[`melt_${floorId}`]; if (has(melt) && has(this.x) && has(this.y)) { for (const [loc, per] of Object.entries(melt)) { const [mx, my] = loc.split(',').map(v => parseInt(v)); if ( Math.abs(mx + dx - this.x) <= 1 && Math.abs(my + dy - this.y) <= 1 ) { info.atkBuff_ += per as number; info.defBuff_ += per as number; } } } } /** * 获取怪物的真实属性信息,在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; if (info.special.has(33)) { const count = this.col?.list.size ?? 0; const [hp, atk, def] = this.enemy.horn ?? [0, 0, 0]; info.hpBuff_ += hp * count; info.atkBuff_ += atk * count; info.defBuff_ += def * count; } 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; } getHaloSpecials(): Set { if (!this.floorId) return new Set(); if (!has(this.x) || !has(this.y)) return new Set(); const special = this.info.special ?? this.enemy.special; const res = new Set(); special.forEach(v => { if (haloSpecials.has(v) && !this.providedHalo.has(v)) { res.add(v); } }); return res; } /** * 光环预提供,用于平衡所有怪的光环属性,避免出现不同情况下光环效果不一致的现象 */ preProvideHalo() { if (this.progress !== 0) return; this.progress = 1; if (!this.floorId) return; if (!has(this.x) || !has(this.y)) return; const special = this.getHaloSpecials(); const col = this.col ?? core.status.maps[this.floorId!].enemy; let [dx, dy] = [0, 0]; if (col) [dx, dy] = col.translation; // e 是被加成怪的属性,enemy 是施加光环的怪 for (const halo of special) { switch (halo) { case 29: { // 特殊光环 const e = this.enemy; const type = 'square'; const r = Math.floor(e.haloRange!); const d = r * 2 + 1; const range = { x: this.x + dx, y: this.y + dy, d }; // 这一句必须放到applyHalo之前 this.providedHalo.add(29); const halo = (e: UserEnemyInfo, enemy: UserEnemyInfo) => { const s = enemy.specialHalo!; for (const spe of s) { e.special.add(spe); } // 如果是自身,就不进行特殊属性数值处理了 if (e === this.info) return; // 然后计算特殊属性数值 for (const spec of s) { // 如果目标怪物拥有杀戮光环,且光环会加成此属性,则忽略 if (e.specialHalo?.includes(spec)) continue; const toChange = specialValue.get(spec); if (!toChange) continue; for (const key of toChange) { // 这种光环应该获取怪物的原始数值,而不是真实数值 if (enemy.enemy.specialMultiply) { e[key] ??= 1; e[key] *= enemy[key] ?? 1; } else { e[key] ??= 0; e[key] += enemy[key] ?? 0; } } } }; col.applyHalo(type, range, this, halo, true); col.haloList.push({ type: 'square', data: { x: this.x + dx, y: this.y + dy, d }, special: 29, from: this }); } } } } /** * 向其他怪提供光环 */ 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.getHaloSpecials(); const [dx, dy] = col.translation; const square7: HaloFn[] = []; const square5: HaloFn[] = []; // e 是被加成怪的属性,enemy 是施加光环的怪 // 抱团 if (special.has(8)) { col.applyHalo( 'square', { x: this.x, y: this.y, d: 5 }, this, (e: UserEnemyInfo, enemy) => { if ( e.special.has(8) && (e.x !== this.x || this.y !== e.y) ) { e.atkBuff_ += enemy.together ?? 0; e.defBuff_ += enemy.together ?? 0; e.togetherNum ??= 0; e.togetherNum++; } } ); this.providedHalo.add(8); } // 冰封光环 if (special.has(21)) { square7.push(e => { e.damageDecline += this.info.iceHalo ?? 0; }); this.providedHalo.add(21); col.haloList.push({ type: 'square', data: { x: this.x + dx, y: this.y + dy, d: 7 }, special: 21, from: this }); } // 冰封之核 if (special.has(26)) { square5.push(e => { e.defBuff_ += this.info.iceCore ?? 0; }); this.providedHalo.add(26); col.haloList.push({ type: 'square', data: { x: this.x + dx, y: this.y + dy, d: 5 }, special: 26, from: this }); } // 火焰之核 if (special.has(27)) { square5.push(e => { e.atkBuff_ += this.info.fireCore ?? 0; }); this.providedHalo.add(27); col.haloList.push({ type: 'square', data: { x: this.x + dx, y: this.y + dy, d: 5 }, special: 27, from: this }); } // 再生光环 if (special.has(31)) { square7.push(e => { e.hpBuff_ += this.info.hpHalo ?? 0; }); this.providedHalo.add(31); col.haloList.push({ type: 'square', data: { x: this.x + dx, y: this.y + dy, d: 7 }, special: 31, from: this }); } // 同化,它不会被光环类属性影响,因此放到这 if (special.has(32)) { const e = this.info; const type = 'square'; const r = Math.floor(e.assimilateRange!); const d = r * 2 + 1; const range = { x: this.x, y: this.y, d }; col.applyHalo(type, range, this, (e, enemy) => { // 如果是自身,就不进行特殊属性数值处理了 if (e === this.info) return; const s = e.special; for (const spe of s) { if (unassimilatable.has(spe)) continue; enemy.special.add(spe); } // 然后计算特殊属性数值 for (const spec of s) { if (unassimilatable.has(spec)) continue; const toChange = specialValue.get(spec); if (!toChange) continue; for (const key of toChange) { // 这种光环应该获取怪物的原始数值,而不是真实数值 if (enemy.enemy.specialMultiply) { enemy[key] ??= 1; enemy[key] *= e[key] ?? 1; } else { enemy[key] ??= 0; enemy[key] += e[key] ?? 0; } } } }); col.haloList.push({ type: 'square', data: range, special: 32, from: this }); } col.applyHalo( 'square', { x: this.x + dx, y: this.y + dy, d: 7 }, this, square7 ); col.applyHalo( 'square', { x: this.x + dx, y: this.y + dy, d: 5 }, this, square5 ); } /** * 接受其他怪的光环 */ injectHalo(halo: HaloFn, enemy: UserEnemyInfo) { halo(this.info, enemy); } /** * 计算怪物伤害 */ calDamage(hero: Partial = core.status.hero) { // todo: 缓存怪物伤害 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; // 突刺 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.value ?? 0) - hero.def!, 0); const objs = core.getMapBlocksObj(this.floorId); 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.atk ?? 0) - hero.def!, 0); const objs = core.getMapBlocksObj(this.floorId); 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; const block = objs[loc]; if ( block && block.event.noPass && block.event.cls !== 'enemys' && block.id !== 141 && block.id !== 151 ) { break; } this.setMapDamage(damage, loc, dam, '射击'); } } } // 电摇嘲讽 if (this.info.special.has(19)) { const objs = core.getMapBlocksObj(this.floorId); for (let nx = 0; nx < w; nx++) { const loc = `${nx},${this.y}` as LocString; const block = objs[loc]; if (!block?.event.noPass) { damage[loc] ??= { damage: 0, type: new Set() }; damage[loc].mockery ??= []; damage[loc].mockery!.push([this.x, this.y]); } } for (let ny = 0; ny < h; ny++) { const loc = `${this.x},${ny}` as LocString; const block = objs[loc]; if (!block?.event.noPass) { damage[loc] ??= { damage: 0, type: new Set() }; damage[loc].mockery ??= []; damage[loc].mockery!.push([this.x, this.y]); } } } // 追猎 if (this.info.special.has(12)) { const objs = core.getMapBlocksObj(this.floorId); for (let nx = 0; nx < w; nx++) { const loc = `${nx},${this.y}` as LocString; const block = objs[loc]; if (!block?.event.noPass) { damage[loc] ??= { damage: 0, type: new Set() }; damage[loc].hunt ??= []; damage[loc].hunt!.push([ this.x, this.y, nx < this.x ? 'left' : 'right' ]); } } for (let ny = 0; ny < h; ny++) { const loc = `${this.x},${ny}` as LocString; const block = objs[loc]; if (!block?.event.noPass) { damage[loc] ??= { damage: 0, type: new Set() }; damage[loc].hunt ??= []; damage[loc].hunt!.push([ this.x, this.y, ny < this.y ? 'up' : 'down' ]); } } } 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: UserEnemyInfo) { const status = getHeroStatusOf(hero, realStatus, this.floorId); let damage = calDamageWith(enemy, status) ?? Infinity; let bestSkill = -1; // 自动切换技能 if (HeroSkill.getAutoSkill()) { for (const skill of skills) { if (!HeroSkill.learnedSkill(skill)) continue; HeroSkill.enableSkill(skill); const status = getHeroStatusOf(hero, realStatus); const d = calDamageWith(enemy, status) ?? Infinity; if (d < damage) { damage = d; bestSkill = skill; } HeroSkill.disableSkill(); } } return { damage, skill: bestSkill }; } /** * 计算怪物临界,计算临界时,根据当前方向计算临界,但也会输出与当前最少伤害的伤害差值 * @param num 要计算多少个临界 * @param dir 从怪物位置指向勇士的方向 * @param hero 勇士属性,最终结果将会与由此属性计算出的伤害相减计算减伤 */ calCritical( num: number = 1, hero: Partial = core.status.hero ): CriticalDamageDelta[] { // todo: 缓存临界 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[] { // todo: 可以优化,根据之前的计算可以直接确定下一个临界的范围 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) { 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(); const add = info.def + info.hp - core.status.hero.mana; // 坚固,不可能通过攻击秒杀 if (info.special.has(3)) { return Infinity; } // 列方程求解,拿笔算一下就知道了 // 饥渴,会偷取勇士攻击 if (info.special.has(7)) { if (info.damageDecline === 0) { return add / (1 - this.enemy.hungry! / 100); } else { return ( (info.hp / (1 - info.damageDecline / 100) - core.status.hero.mana + info.def) / (1 - this.enemy.hungry! / 100) ); } } // 霜冻 if (info.special.has(20) && !core.hasEquip('I589')) { return ( info.def + info.hp / (1 - this.enemy.ice! / 100) - core.status.hero.mana ); } if (info.damageDecline !== 0) { return ( info.def + info.hp / (1 - info.damageDecline / 100) - core.status.hero.mana ); } else { return add; } } } /** * 计算伤害时会用到的勇士属性,攻击防御,其余的不会有buff加成,直接从core.status.hero取 */ const realStatus: (keyof HeroStatus)[] = [ 'atk', 'def', 'hpmax', 'mana', 'magicDef' ]; /** * 主动技能列表 */ const skills: HeroSkill.Skill[] = [HeroSkill.Blade, HeroSkill.Shield]; /** * 计算怪物伤害 * @param info 怪物信息 * @param hero 勇士信息 */ export function calDamageWith( info: UserEnemyInfo, hero: Partial ): number | null { const { hp, mdef } = core.status.hero; let { atk, def, hpmax, mana, magicDef } = hero as HeroStatus; let { hp: monHp, atk: monAtk, def: monDef, special, enemy } = info; // 赏金,优先级最高 if (special.has(34)) return 0; hpmax = Math.min(hpmax, def / 10); let damage = 0; // 饥渴 if (special.has(7)) { const delta = Math.floor((atk * info.hungry!) / 100); atk -= delta; monAtk += delta; } let heroPerDamage: number; // 绝对防御 if (special.has(9)) { heroPerDamage = atk + mana - monDef; if (heroPerDamage <= 0) return null; } else if (special.has(3)) { // 由于坚固的特性,只能放到这来计算了 if (atk > enemy.def) heroPerDamage = 1 + mana; else return null; } else { heroPerDamage = atk - monDef; if (heroPerDamage > 0) heroPerDamage += mana; else return null; } // 霜冻 if (special.has(20) && !core.hasEquip('I589')) { heroPerDamage *= 1 - info.ice! / 100; } heroPerDamage *= 1 - info.damageDecline / 100; let enemyPerDamage: number; // 魔攻 if (special.has(2) || special.has(13)) { enemyPerDamage = monAtk; enemyPerDamage -= magicDef; } 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; // 苍蓝刻 if (special.has(28)) { heroPerDamage *= 1 - info.paleShield! / 100; } let turn = Math.ceil(monHp / heroPerDamage); // 致命一击 if (special.has(1)) { const times = Math.floor(turn / 5); damage += ((times * (info.crit! - 100)) / 100) * enemyPerDamage; } // 勇气之刃 if (turn > 1 && special.has(10)) { damage += (info.courage! / 100 - 1) * enemyPerDamage; } // 勇气冲锋 if (special.has(11)) { damage += (info.charge! / 100) * enemyPerDamage; turn += 5; } // 先攻 if (special.has(17)) { damage += enemyPerDamage; } damage += (turn - 1) * enemyPerDamage; // 无上之盾 if (flags.superSheild) { damage -= mdef / 10; } // 生命回复 damage -= hpmax * turn; if (flags.hard === 1) damage *= 0.9; if (flags.chapter > 1 && damage < 0) { const dm = -info.hp * 0.25; if (damage < dm) damage = dm; } return Math.floor(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; } }