HumanBreak/packages-user/data-fallback/src/battle.ts

283 lines
9.0 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 {
DamageEnemy,
ensureFloorDamage,
getSingleEnemy,
getEnemy,
HeroSkill,
NightSpecial
} from '@user/data-state';
import { hook, loading } 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-ignore
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<string, DamageEnemy[]> = {};
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('_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();
// 自动存档
var 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) {
const floorId = core.status.floorId;
const special = enemy.info.special;
// 播放战斗动画
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.calDamage(core.status.hero);
const damage = info.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++;
// 智慧之源
if (special.has(14) && flags.hard === 2) {
core.addFlag(
'inte_' + floorId,
Math.ceil((core.status.hero.mdef / 10) * 0.3) * 10
);
core.status.hero.mdef -=
Math.ceil((core.status.hero.mdef / 10) * 0.3) * 10;
}
// 极昼永夜
if (special.has(22)) {
NightSpecial.addNight(floorId, -enemy.info.night!);
}
if (special.has(23)) {
NightSpecial.addNight(floorId, enemy.info.day!);
}
// 如果是融化怪,需要特殊标记一下
if (special.has(25) && !isNil(x) && !isNil(y)) {
flags[`melt_${floorId}`] ??= {};
flags[`melt_${floorId}`][`${x},${y}`] = enemy.info.melt;
}
// 获得金币
const money = enemy.info.money!;
core.status.hero.money += money;
core.status.hero.statistics.money += money;
// 获得经验
const exp = enemy.info.exp!;
core.status.hero.exp += exp;
core.status.hero.statistics.exp += exp;
const hint =
'打败 ' +
enemy.enemy.name +
',金币+' +
money +
',经验+' +
exp;
core.drawTip(hint, enemy.id);
HeroSkill.disableSkill();
// 事件的处理
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 (core.status.event.id == null) core.continueAutomaticRoute();
else core.clearContinueAutomaticRoute();
core.checkAutoEvents();
hook.emit('afterBattle', enemy, x, y);
}
);
}
loading.once('coreInit', patchBattle);
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,
_?: number,
force?: boolean,
callback?: () => void
): void;
battle(
x: number,
y?: number,
force?: boolean,
callback?: () => void
): void;
}
}