HumanBreak/packages-user/data-state/src/enemy/damage.ts

1084 lines
32 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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<number> = new Set([
8, 21, 25, 26, 27, 29, 31, 32
]);
/** 不可被同化的属性 */
export const unassimilatable: Set<number> = new Set(haloSpecials);
unassimilatable.add(8).add(30).add(33);
/** 特殊属性对应 */
export const specialValue: Map<number, SelectKey<Enemy, number | undefined>[]> =
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<EnemyCollectionEvent>
implements IEnemyCollection
{
floorId: FloorIds;
list: Map<number, DamageEnemy> = new Map();
range: Range = new Range();
/** 地图伤害 */
mapDamage: Record<string, MapDamage> = {};
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<K extends keyof HaloType>(
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<number> = 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<number> {
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<number>();
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<HeroStatus> = core.status.hero) {
// todo: 缓存怪物伤害
const enemy = this.getRealInfo();
return this.calEnemyDamageOf(hero, enemy);
}
/**
* 计算地图伤害
* @param damage 存入的对象
*/
calMapDamage(
damage: Record<string, MapDamage> = {},
hero: Partial<HeroStatus> = 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<string, MapDamage>,
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<HeroStatus>, 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<HeroStatus> = 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<HeroStatus>
): 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<HeroStatus> = 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<HeroStatus>
): 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;
}
}