mirror of
				https://github.com/unanmed/HumanBreak.git
				synced 2025-11-04 15:12:58 +08:00 
			
		
		
		
	
		
			
				
	
	
		
			709 lines
		
	
	
		
			20 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			709 lines
		
	
	
		
			20 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
import { getHeroStatusOf, getHeroStatusOn } from './hero';
 | 
						||
import { Range, RangeCollection } from './range';
 | 
						||
import { backDir, checkV2, ensureArray, has, manhattan, ofDir } from './utils';
 | 
						||
 | 
						||
interface HaloType {
 | 
						||
    square: {
 | 
						||
        x: number;
 | 
						||
        y: number;
 | 
						||
        d: number;
 | 
						||
    };
 | 
						||
}
 | 
						||
 | 
						||
interface EnemyInfo {
 | 
						||
    atk: number;
 | 
						||
    def: number;
 | 
						||
    hp: number;
 | 
						||
    special: number[];
 | 
						||
    damageDecline: number;
 | 
						||
    atkBuff: number;
 | 
						||
    defBuff: number;
 | 
						||
    hpBuff: number;
 | 
						||
    enemy: Enemy;
 | 
						||
}
 | 
						||
 | 
						||
interface DamageInfo {
 | 
						||
    damage: number;
 | 
						||
    /** 从勇士位置指向怪物的方向 */
 | 
						||
    dir: Dir | 'none';
 | 
						||
    x?: number;
 | 
						||
    y?: number;
 | 
						||
    /** 自动切换技能时使用的技能 */
 | 
						||
    skill?: number;
 | 
						||
}
 | 
						||
 | 
						||
interface MapDamage {
 | 
						||
    damage: number;
 | 
						||
    type: Set<string>;
 | 
						||
    mockery?: LocArr[];
 | 
						||
}
 | 
						||
 | 
						||
interface HaloData<T extends keyof HaloType = keyof HaloType> {
 | 
						||
    type: T;
 | 
						||
    data: HaloType[T];
 | 
						||
    from: DamageEnemy;
 | 
						||
}
 | 
						||
 | 
						||
type HaloFn = (info: EnemyInfo, enemy: Enemy) => void;
 | 
						||
 | 
						||
export const haloSpecials: number[] = [21, 25, 26, 27];
 | 
						||
 | 
						||
export class EnemyCollection implements RangeCollection<DamageEnemy> {
 | 
						||
    floorId: FloorIds;
 | 
						||
    list: DamageEnemy[] = [];
 | 
						||
 | 
						||
    range: Range<DamageEnemy> = new Range(this);
 | 
						||
    mapDamage: Record<string, MapDamage> = {};
 | 
						||
    haloList: HaloData[] = [];
 | 
						||
 | 
						||
    constructor(floorId: FloorIds) {
 | 
						||
        this.floorId = floorId;
 | 
						||
        this.extract();
 | 
						||
    }
 | 
						||
 | 
						||
    /**
 | 
						||
     * 解析本地图的怪物信息
 | 
						||
     */
 | 
						||
    extract() {
 | 
						||
        core.extractBlocks(this.floorId);
 | 
						||
        core.status.maps[this.floorId].blocks.forEach(v => {
 | 
						||
            if (v.event.cls !== 'enemy48' && v.event.cls !== 'enemys') return;
 | 
						||
            const enemy = core.material.enemys[v.event.id as EnemyIds];
 | 
						||
            this.list.push(
 | 
						||
                new DamageEnemy(enemy, v.x, v.y, this.floorId, this)
 | 
						||
            );
 | 
						||
        });
 | 
						||
    }
 | 
						||
 | 
						||
    /**
 | 
						||
     * 计算怪物真实属性
 | 
						||
     * @param noCache 是否不使用缓存
 | 
						||
     */
 | 
						||
    calRealAttribute(noCache: boolean = false) {
 | 
						||
        this.list.forEach(v => {
 | 
						||
            if (noCache) v.reset();
 | 
						||
            v.calRealAttribute();
 | 
						||
        });
 | 
						||
    }
 | 
						||
 | 
						||
    /**
 | 
						||
     * 计算怪物伤害
 | 
						||
     * @param noCache 是否不使用缓存
 | 
						||
     */
 | 
						||
    calDamage(noCache: boolean = false, onMap: boolean = false) {
 | 
						||
        this.list.forEach(v => {
 | 
						||
            if (noCache || v.needCalculate) {
 | 
						||
                v.reset();
 | 
						||
                v.calRealAttribute();
 | 
						||
            }
 | 
						||
            v.calDamage(void 0, onMap);
 | 
						||
            console.log(v.damage);
 | 
						||
        });
 | 
						||
    }
 | 
						||
 | 
						||
    /**
 | 
						||
     * 计算地图伤害
 | 
						||
     * @param noCache 是否不使用缓存
 | 
						||
     */
 | 
						||
    calMapDamage(noCache: boolean = false) {
 | 
						||
        if (noCache) this.mapDamage = {};
 | 
						||
        const hero = getHeroStatusOn(
 | 
						||
            realStatus,
 | 
						||
            core.status.hero.loc.x,
 | 
						||
            core.status.hero.loc.y,
 | 
						||
            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],
 | 
						||
        halo: HaloFn | HaloFn[],
 | 
						||
        recursion: boolean = false
 | 
						||
    ) {
 | 
						||
        const arr = ensureArray(halo);
 | 
						||
        const enemy = this.range.scan(type, data);
 | 
						||
        if (!recursion) {
 | 
						||
            arr.forEach(v => {
 | 
						||
                enemy.forEach(e => {
 | 
						||
                    e.injectHalo(v);
 | 
						||
                });
 | 
						||
            });
 | 
						||
        } else {
 | 
						||
            enemy.forEach(e => {
 | 
						||
                arr.forEach(v => {
 | 
						||
                    e.injectHalo(v);
 | 
						||
                    e.preProvideHalo();
 | 
						||
                });
 | 
						||
            });
 | 
						||
        }
 | 
						||
    }
 | 
						||
 | 
						||
    /**
 | 
						||
     * 预平衡光环
 | 
						||
     */
 | 
						||
    preBalanceHalo() {
 | 
						||
        this.list.forEach(v => {
 | 
						||
            v.preProvideHalo();
 | 
						||
        });
 | 
						||
    }
 | 
						||
}
 | 
						||
 | 
						||
export class DamageEnemy<T extends EnemyIds = EnemyIds> {
 | 
						||
    id: T;
 | 
						||
    x?: number;
 | 
						||
    y?: number;
 | 
						||
    floorId?: FloorIds;
 | 
						||
    enemy: Enemy<T>;
 | 
						||
    col?: EnemyCollection;
 | 
						||
 | 
						||
    /**
 | 
						||
     * 怪物属性。
 | 
						||
     * 属性计算流程:预平衡光环(即计算加光环的光环怪的光环) -> 计算怪物在没有光环下的属性
 | 
						||
     * -> provide inject 光环 -> 计算怪物的光环加成 -> 计算完毕
 | 
						||
     */
 | 
						||
    info!: EnemyInfo;
 | 
						||
    /** 是否需要计算属性 */
 | 
						||
    needCalculate: boolean = true;
 | 
						||
    /** 怪物伤害 */
 | 
						||
    damage?: DamageInfo[];
 | 
						||
    /** 是否需要计算伤害 */
 | 
						||
    needCalDamage: boolean = true;
 | 
						||
 | 
						||
    /** 向其他怪提供过的光环 */
 | 
						||
    providedHalo: number[] = [];
 | 
						||
 | 
						||
    constructor(
 | 
						||
        enemy: Enemy<T>,
 | 
						||
        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: enemy.special.slice(),
 | 
						||
            damageDecline: 0,
 | 
						||
            atkBuff: 0,
 | 
						||
            defBuff: 0,
 | 
						||
            hpBuff: 0,
 | 
						||
            enemy: this.enemy
 | 
						||
        };
 | 
						||
        this.needCalculate = true;
 | 
						||
        this.needCalDamage = true;
 | 
						||
    }
 | 
						||
 | 
						||
    /**
 | 
						||
     * 计算怪物在不计光环下的属性,在inject光环之前,预平衡光环之后执行
 | 
						||
     * @param hero 勇士属性
 | 
						||
     * @param getReal 是否获取勇士真实属性,默认获取
 | 
						||
     */
 | 
						||
    calAttribute(
 | 
						||
        hero: Partial<HeroStatus> = core.status.hero,
 | 
						||
        getReal: boolean = true
 | 
						||
    ) {
 | 
						||
        if (!this.needCalculate) return;
 | 
						||
        const special = this.info.special;
 | 
						||
        const info = this.info;
 | 
						||
        const enemy = this.enemy;
 | 
						||
        const floorId = this.floorId ?? core.status.floorId;
 | 
						||
        const { atk } = getReal
 | 
						||
            ? getHeroStatusOf(hero, ['atk'], hero.x, hero.y, hero.floorId)
 | 
						||
            : hero;
 | 
						||
 | 
						||
        if (!has(atk)) return;
 | 
						||
 | 
						||
        // 饥渴
 | 
						||
        if (special.includes(7)) {
 | 
						||
            info.atk += (atk * (enemy.hungry ?? 0)) / 100;
 | 
						||
        }
 | 
						||
 | 
						||
        // 智慧之源
 | 
						||
        if (flags.hard === 2 && special.includes(14)) {
 | 
						||
            info.atk += flags[`inte_${floorId}`] ?? 0;
 | 
						||
        }
 | 
						||
 | 
						||
        // 极昼永夜
 | 
						||
        info.atk -= flags[`night_${floorId}`] ?? 0;
 | 
						||
        info.def -= flags[`night_${floorId}`] ?? 0;
 | 
						||
 | 
						||
        // 坚固
 | 
						||
        if (special.includes(3) && enemy.def < atk - 1) {
 | 
						||
            info.def = atk - 1;
 | 
						||
        }
 | 
						||
 | 
						||
        // 融化,融化不属于怪物光环,因此不能用provide和inject计算,需要在这里计算
 | 
						||
        if (has(flags[`melt_${floorId}`]) && has(this.x) && has(this.y)) {
 | 
						||
            for (const [loc, per] of Object.entries(flags[`melt_${floorId}`])) {
 | 
						||
                const [mx, my] = loc.split(',').map(v => parseInt(v));
 | 
						||
                if (Math.abs(mx - this.x) <= 1 && Math.abs(my - this.y) <= 1) {
 | 
						||
                    info.atkBuff += per as number;
 | 
						||
                    info.defBuff += per as number;
 | 
						||
                }
 | 
						||
            }
 | 
						||
        }
 | 
						||
    }
 | 
						||
 | 
						||
    /**
 | 
						||
     * 获取怪物的真实属性信息,在inject光环后执行
 | 
						||
     */
 | 
						||
    getRealInfo() {
 | 
						||
        if (!this.needCalculate) return this.info;
 | 
						||
 | 
						||
        // 此时已经inject光环,因此直接计算真实属性
 | 
						||
        const info = this.info;
 | 
						||
        info.atk *= info.atkBuff / 100 + 1;
 | 
						||
        info.def *= info.defBuff / 100 + 1;
 | 
						||
        info.hp *= info.hpBuff / 100 + 1;
 | 
						||
 | 
						||
        this.needCalculate = false;
 | 
						||
 | 
						||
        return this.info;
 | 
						||
    }
 | 
						||
 | 
						||
    /**
 | 
						||
     * 计算真实属性
 | 
						||
     */
 | 
						||
    calRealAttribute() {
 | 
						||
        this.preProvideHalo();
 | 
						||
        this.calAttribute();
 | 
						||
        this.provideHalo();
 | 
						||
        this.getRealInfo();
 | 
						||
    }
 | 
						||
 | 
						||
    getHaloSpecials(): number[] {
 | 
						||
        if (!this.floorId) return [];
 | 
						||
        if (!core.has(this.x) || !core.has(this.y)) return [];
 | 
						||
        const special = this.info.special ?? this.enemy.special;
 | 
						||
        const filter = special.filter(v => {
 | 
						||
            return haloSpecials.includes(v) && !this.providedHalo.includes(v);
 | 
						||
        });
 | 
						||
        if (filter.length === 0) return [];
 | 
						||
        const collection = this.col ?? core.status.maps[this.floorId].enemy;
 | 
						||
        if (!collection) {
 | 
						||
            throw new Error(
 | 
						||
                `Unexpected undefined of enemy collection in floor ${this.floorId}.`
 | 
						||
            );
 | 
						||
        }
 | 
						||
        return filter;
 | 
						||
    }
 | 
						||
 | 
						||
    /**
 | 
						||
     * 光环预提供,用于平衡所有怪的光环属性,避免出现不同情况下光环效果不一致的现象
 | 
						||
     */
 | 
						||
    preProvideHalo() {}
 | 
						||
 | 
						||
    /**
 | 
						||
     * 向其他怪提供光环
 | 
						||
     */
 | 
						||
    provideHalo() {
 | 
						||
        if (!this.floorId) return;
 | 
						||
        if (!core.has(this.x) || !core.has(this.y)) return;
 | 
						||
        const col = this.col ?? core.status.maps[this.floorId].enemy;
 | 
						||
        if (!col) return;
 | 
						||
        const speical = this.getHaloSpecials();
 | 
						||
 | 
						||
        const square7: HaloFn[] = [];
 | 
						||
        const square5: HaloFn[] = [];
 | 
						||
 | 
						||
        // 抱团
 | 
						||
        if (speical.includes(8)) {
 | 
						||
            square5.push((e, enemy) => {
 | 
						||
                if (e.special.includes(8)) {
 | 
						||
                    e.atkBuff += enemy.together ?? 0;
 | 
						||
                    e.defBuff += enemy.together ?? 0;
 | 
						||
                }
 | 
						||
            });
 | 
						||
            this.providedHalo.push(8);
 | 
						||
        }
 | 
						||
 | 
						||
        // 冰封光环
 | 
						||
        if (speical.includes(21)) {
 | 
						||
            square7.push((e, enemy) => {
 | 
						||
                e.damageDecline += enemy.iceDecline ?? 0;
 | 
						||
            });
 | 
						||
            this.providedHalo.push(21);
 | 
						||
        }
 | 
						||
 | 
						||
        // 冰封之核
 | 
						||
        if (speical.includes(26)) {
 | 
						||
            square5.push((e, enemy) => {
 | 
						||
                e.defBuff += enemy.iceCore ?? 0;
 | 
						||
            });
 | 
						||
            this.providedHalo.push(26);
 | 
						||
        }
 | 
						||
 | 
						||
        // 火焰之核
 | 
						||
        if (speical.includes(27)) {
 | 
						||
            square5.push((e, enemy) => {
 | 
						||
                e.atkBuff += enemy.fireCore ?? 0;
 | 
						||
            });
 | 
						||
            this.providedHalo.push(27);
 | 
						||
        }
 | 
						||
 | 
						||
        col.applyHalo('square', { x: this.x, y: this.y, d: 7 }, square7);
 | 
						||
        col.applyHalo('square', { x: this.x, y: this.y, d: 5 }, square5);
 | 
						||
    }
 | 
						||
 | 
						||
    /**
 | 
						||
     * 接受其他怪的光环
 | 
						||
     */
 | 
						||
    injectHalo(halo: HaloFn) {
 | 
						||
        halo.call(this, this.info, this.enemy);
 | 
						||
    }
 | 
						||
 | 
						||
    /**
 | 
						||
     * 计算怪物伤害
 | 
						||
     */
 | 
						||
    calDamage(
 | 
						||
        hero: Partial<HeroStatus> = core.status.hero,
 | 
						||
        onMap: boolean = false
 | 
						||
    ) {
 | 
						||
        if (onMap && !checkV2(this.x, this.y)) return this.damage!;
 | 
						||
        if (!this.needCalDamage) return this.damage!;
 | 
						||
        const info = this.getRealInfo();
 | 
						||
        const dirs = getNeedCalDir(this.x, this.y, this.floorId, hero);
 | 
						||
 | 
						||
        const damageCache: Record<string, number> = {};
 | 
						||
        this.needCalDamage = false;
 | 
						||
 | 
						||
        return (this.damage = dirs.map(dir => {
 | 
						||
            const status = getHeroStatusOf(hero, realStatus);
 | 
						||
            let damage = calDamageWith(info, status) ?? Infinity;
 | 
						||
            let skill = -1;
 | 
						||
 | 
						||
            // 自动切换技能
 | 
						||
            if (flags.autoSkill) {
 | 
						||
                for (let i = 0; i < skills.length; i++) {
 | 
						||
                    const [unlock, condition] = skills[i];
 | 
						||
                    if (!flags[unlock]) continue;
 | 
						||
                    flags[condition] = true;
 | 
						||
                    const status = getHeroStatusOf(hero, realStatus);
 | 
						||
                    const id = `${status.atk},${status.def}`;
 | 
						||
                    const d =
 | 
						||
                        id in damageCache
 | 
						||
                            ? damageCache[id]
 | 
						||
                            : calDamageWith(info, status) ?? Infinity;
 | 
						||
                    if (d < damage) {
 | 
						||
                        damage = d;
 | 
						||
                        skill = i;
 | 
						||
                    }
 | 
						||
                    flags[condition] = false;
 | 
						||
                    damageCache[id] = d;
 | 
						||
                }
 | 
						||
            }
 | 
						||
 | 
						||
            let x: number | undefined;
 | 
						||
            let y: number | undefined;
 | 
						||
            if (has(this.x) && has(this.y)) {
 | 
						||
                if (dir !== 'none') {
 | 
						||
                    [x, y] = ofDir(this.x, this.y, dir);
 | 
						||
                } else {
 | 
						||
                    x = hero.x ?? this.x;
 | 
						||
                    y = hero.y ?? this.y;
 | 
						||
                }
 | 
						||
            }
 | 
						||
 | 
						||
            return {
 | 
						||
                damage,
 | 
						||
                dir,
 | 
						||
                skill,
 | 
						||
                x,
 | 
						||
                y
 | 
						||
            };
 | 
						||
        }));
 | 
						||
    }
 | 
						||
 | 
						||
    /**
 | 
						||
     * 计算地图伤害
 | 
						||
     * @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.includes(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);
 | 
						||
 | 
						||
            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}`;
 | 
						||
                    this.setMapDamage(damage, loc, dam, '突刺');
 | 
						||
                }
 | 
						||
            }
 | 
						||
        }
 | 
						||
 | 
						||
        // 射击
 | 
						||
        if (this.info.special.includes(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 (1) {
 | 
						||
                    if (x < 0 || y < 0 || x >= w || y >= h) break;
 | 
						||
                    x += dx;
 | 
						||
                    y += dy;
 | 
						||
                    const loc = `${x},${y}` as LocString;
 | 
						||
                    const block = objs[loc];
 | 
						||
                    if (
 | 
						||
                        block.event.noPass &&
 | 
						||
                        block.event.cls !== 'enemys' &&
 | 
						||
                        block.event.cls !== 'enemy48' &&
 | 
						||
                        block.id !== 141 &&
 | 
						||
                        block.id !== 151
 | 
						||
                    ) {
 | 
						||
                        break;
 | 
						||
                    }
 | 
						||
                    this.setMapDamage(damage, loc, dam, '射击');
 | 
						||
                }
 | 
						||
            }
 | 
						||
        }
 | 
						||
 | 
						||
        // 电摇嘲讽
 | 
						||
        if (this.info.special.includes(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]);
 | 
						||
                }
 | 
						||
            }
 | 
						||
        }
 | 
						||
 | 
						||
        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;
 | 
						||
        damage[loc].type.add(type);
 | 
						||
    }
 | 
						||
}
 | 
						||
 | 
						||
/**
 | 
						||
 * 计算伤害时会用到的勇士属性,攻击防御,其余的不会有buff加成,直接从core.status.hero取
 | 
						||
 */
 | 
						||
const realStatus: (keyof HeroStatus)[] = ['atk', 'def'];
 | 
						||
/**
 | 
						||
 * 主动技能列表
 | 
						||
 */
 | 
						||
const skills: [unlock: string, condition: string][] = [
 | 
						||
    ['bladeOn', 'blade'],
 | 
						||
    ['shieldOn', 'shield']
 | 
						||
];
 | 
						||
 | 
						||
/**
 | 
						||
 * 获取需要计算怪物伤害的方向
 | 
						||
 * @param x 怪物横坐标
 | 
						||
 * @param y 怪物纵坐标
 | 
						||
 * @param floorId 怪物所在楼层
 | 
						||
 */
 | 
						||
export function getNeedCalDir(
 | 
						||
    x?: number,
 | 
						||
    y?: number,
 | 
						||
    floorId: FloorIds = core.status.floorId,
 | 
						||
    hero: Partial<HeroStatus> = core.status.hero
 | 
						||
): (Dir | 'none')[] {
 | 
						||
    // 第一章或序章,或者没有指定怪物位置,或者没开自动定位,用不到这个函数
 | 
						||
    if (flags.chapter < 2 || !has(x) || !has(y)) {
 | 
						||
        return ['none'];
 | 
						||
    }
 | 
						||
 | 
						||
    // 如果指定了勇士坐标
 | 
						||
    if (has(hero.x) && has(hero.y)) {
 | 
						||
        return ['none'];
 | 
						||
    }
 | 
						||
 | 
						||
    const needMap: Dir[] = ['left', 'down', 'right', 'up'];
 | 
						||
    const { width, height } = core.status.maps[floorId];
 | 
						||
    const blocks = core.getMapBlocksObj(floorId);
 | 
						||
 | 
						||
    const res = needMap.filter(v => {
 | 
						||
        const [tx, ty] = ofDir(x, y, v);
 | 
						||
        if (tx < 0 || ty < 0 || tx >= width || ty >= height) return false;
 | 
						||
        const index = `${tx},${ty}` as LocString;
 | 
						||
        const block = blocks[index];
 | 
						||
        if (!block || block.event.noPass) return false;
 | 
						||
        if (!core.canMoveHero(tx, ty, backDir(v), floorId)) return false;
 | 
						||
 | 
						||
        return true;
 | 
						||
    });
 | 
						||
    return res.length === 0 ? ['none'] : res;
 | 
						||
}
 | 
						||
 | 
						||
/**
 | 
						||
 * 计算怪物伤害
 | 
						||
 * @param info 怪物信息
 | 
						||
 * @param hero 勇士信息
 | 
						||
 */
 | 
						||
export function calDamageWith(
 | 
						||
    info: EnemyInfo,
 | 
						||
    hero: Partial<HeroStatus>
 | 
						||
): number | null {
 | 
						||
    const { hp, hpmax, mana, mdef } = core.status.hero;
 | 
						||
    let { atk, def } = hero as HeroStatus;
 | 
						||
    const { hp: monHp, atk: monAtk, def: monDef, special, enemy } = info;
 | 
						||
 | 
						||
    let damage = 0;
 | 
						||
 | 
						||
    // 饥渴
 | 
						||
    if (special.includes(7)) {
 | 
						||
        atk *= 1 - enemy.hungry! / 100;
 | 
						||
    }
 | 
						||
 | 
						||
    let heroPerDamage: number;
 | 
						||
 | 
						||
    // 绝对防御
 | 
						||
    if (special.includes(9)) {
 | 
						||
        heroPerDamage = atk + mana - monDef;
 | 
						||
        if (heroPerDamage <= 0) return null;
 | 
						||
    } else {
 | 
						||
        heroPerDamage = atk - monDef;
 | 
						||
        if (heroPerDamage > 0) heroPerDamage += mana;
 | 
						||
        else return null;
 | 
						||
    }
 | 
						||
 | 
						||
    let enemyPerDamage: number;
 | 
						||
 | 
						||
    // 魔攻
 | 
						||
    if (special.includes(2) || special.includes(13)) {
 | 
						||
        enemyPerDamage = monAtk;
 | 
						||
    } else {
 | 
						||
        enemyPerDamage = monAtk - def;
 | 
						||
        if (enemyPerDamage < 0) enemyPerDamage = 0;
 | 
						||
    }
 | 
						||
 | 
						||
    // 连击
 | 
						||
    if (special.includes(4)) enemyPerDamage *= 2;
 | 
						||
    if (special.includes(5)) enemyPerDamage *= 3;
 | 
						||
    if (special.includes(6)) enemyPerDamage *= enemy.n!;
 | 
						||
 | 
						||
    // 霜冻
 | 
						||
    if (special.includes(20) && !core.hasEquip('I589')) {
 | 
						||
        heroPerDamage *= 1 - enemy.ice! / 100;
 | 
						||
    }
 | 
						||
    heroPerDamage *= 1 - info.damageDecline;
 | 
						||
 | 
						||
    // 苍蓝刻
 | 
						||
    if (special.includes(28)) {
 | 
						||
        heroPerDamage *= 1 - enemy.paleShield! / 100;
 | 
						||
    }
 | 
						||
 | 
						||
    let turn = Math.ceil(monHp / heroPerDamage);
 | 
						||
 | 
						||
    // 致命一击
 | 
						||
    if (special.includes(1)) {
 | 
						||
        const times = Math.floor(turn / 5);
 | 
						||
        damage += ((times * (enemy.crit! - 100)) / 100) * enemyPerDamage;
 | 
						||
    }
 | 
						||
 | 
						||
    // 勇气之刃
 | 
						||
    if (turn > 1 && special.includes(10)) {
 | 
						||
        damage += (enemy.courage! / 100 - 1) * enemyPerDamage;
 | 
						||
    }
 | 
						||
 | 
						||
    // 勇气冲锋
 | 
						||
    if (special.includes(11)) {
 | 
						||
        damage += (enemy.charge! / 100) * enemyPerDamage;
 | 
						||
        turn += 5;
 | 
						||
    }
 | 
						||
 | 
						||
    damage += (turn - 1) * enemyPerDamage;
 | 
						||
    // 无上之盾
 | 
						||
    if (flags.superSheild) {
 | 
						||
        damage -= mdef / 10;
 | 
						||
    }
 | 
						||
    // 生命回复
 | 
						||
    damage -= hpmax * turn;
 | 
						||
    if (flags.hard === 1) damage *= 0.9;
 | 
						||
 | 
						||
    return damage;
 | 
						||
}
 | 
						||
 | 
						||
export function ensureFloorDamage(floorId: FloorIds) {
 | 
						||
    const floor = core.status.maps[floorId];
 | 
						||
    floor.enemy ??= new EnemyCollection(floorId);
 | 
						||
}
 | 
						||
 | 
						||
declare global {
 | 
						||
    interface PluginDeclaration {
 | 
						||
        damage: {
 | 
						||
            Enemy: typeof DamageEnemy;
 | 
						||
            Collection: typeof EnemyCollection;
 | 
						||
            ensureFloorDamage: typeof ensureFloorDamage;
 | 
						||
        };
 | 
						||
    }
 | 
						||
 | 
						||
    interface Floor {
 | 
						||
        enemy: EnemyCollection;
 | 
						||
    }
 | 
						||
}
 | 
						||
 | 
						||
core.plugin.damage = {
 | 
						||
    Enemy: DamageEnemy,
 | 
						||
    Collection: EnemyCollection,
 | 
						||
    ensureFloorDamage
 | 
						||
};
 |