refactor & fix: 插件与进程分离

This commit is contained in:
AncTe committed 2024-02-02 17:10:21 +08:00
1 parent 325a1e85ec
commit a9c64565e1
41 files changed
+473 -467

No files matched your search

+2 -1
View File
@@ -5,7 +5,6 @@ import {
} from './ui/achievement';
import { changeLocalStorage } from './utils';
import list from '../data/achievement.json';
import { loading } from '@/core/loader/load';
export const floors: Record<number, FloorIds[]> = {
1: ['MT0', 'tower7']
@@ -22,6 +21,8 @@ export const achiDict: Record<number, number> = {
1: 0
};
const loading = Mota.require('var', 'loading');
loading.once('coreInit', () => {
Object.values(floors).forEach((v, i) => {
const from = core.floorIds.indexOf(v[0]);
+3 -3
View File
@@ -52,15 +52,15 @@ export function init() {
const hso = hyper('sin', 'out');
let time2 = Date.now();
Mota.rewrite(core.control, '_moveAction_moving', 'front', () => {
const t = setting.getValue('screen.smoothView', false) ? 200 : 1;
const t = setting.getValue('screen.smoothView', false) ? 200 : 0;
if (Date.now() - time2 > 20) tran.mode(hso).time(t).absolute();
});
Mota.rewrite(core.control, 'moveDirectly', 'front', () => {
const t = setting.getValue('screen.smoothView', false) ? 600 : 1;
const t = setting.getValue('screen.smoothView', false) ? 600 : 0;
time2 = Date.now();
tran.mode(hso).time(t).absolute();
});
Mota.rewrite(core.events, '_changeFloor_beforeChange', 'front', () => {
tran.time(1).absolute();
tran.time(0).absolute();
});
}
-245
View File
@@ -1,245 +0,0 @@
import { DamageEnemy, ensureFloorDamage, getSingleEnemy } from './damage';
import { findDir, has } from '../utils';
export interface CurrentEnemy {
enemy: DamageEnemy;
onMapEnemy: DamageEnemy[];
}
export function getEnemy(
x: number,
y: number,
floorId: FloorIds = core.status.floorId
) {
const enemy = core.status.maps[floorId].enemy.list.find(v => {
return v.x === x && v.y === y;
});
if (!enemy) {
throw new Error(
`Get null when getting enemy on '${x},${y}' in '${floorId}'`
);
}
return enemy;
}
export function init() {
core.enemys.canBattle = function (
x: number,
y: number,
floorId: FloorIds = core.status.floorId
) {
const enemy = getEnemy(x, y, floorId);
const { damage } = enemy.calDamage();
return damage < core.status.hero.hp;
};
core.events.battle = function (
x: number,
y: number,
force: boolean = false,
callback?: () => void
) {
core.saveAndStopAutomaticRoute();
const enemy = getEnemy(x, y);
// 非强制战斗
if (!core.enemys.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);
// 战前事件
// 战后事件
this.afterBattle(enemy, x, y);
callback?.();
};
core.events.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.mp3');
// 战斗伤害
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.includes(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.includes(22)) {
flags[`night_${floorId}`] ??= 0;
flags[`night_${floorId}`] -= enemy.enemy.night!;
}
if (special.includes(23)) {
flags[`night_${floorId}`] ??= 0;
flags[`night_${floorId}`] += enemy.enemy.day;
}
// if (core.plugin.skillTree.getSkillLevel(11) > 0) {
// core.plugin.study.declineStudiedSkill();
// }
// 如果是融化怪,需要特殊标记一下
if (special.includes(25) && has(x) && has(y)) {
flags[`melt_${floorId}`] ??= {};
flags[`melt_${floorId}`][`${x},${y}`] = enemy.enemy.melt;
}
// 获得金币
const money = enemy.enemy.money;
core.status.hero.money += money;
core.status.hero.statistics.money += money;
// 获得经验
const exp = enemy.enemy.exp;
core.status.hero.exp += exp;
core.status.hero.statistics.exp += exp;
const hint =
'打败 ' + enemy.enemy.name + ',金币+' + money + ',经验+' + exp;
core.drawTip(hint, enemy.id);
if (core.getFlag('bladeOn') && core.getFlag('blade')) {
core.setFlag('blade', false);
}
if (core.getFlag('shieldOn') && core.getFlag('shield')) {
core.setFlag('shield', false);
}
// 事件的处理
const todo: MotaEvent = [];
// 战后事件
if (has(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 (has(x) && has(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.events._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 {
this.battle(data.x, data.y, false, callback);
}
};
core.events._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] = this.__action_getLoc(
data.loc,
x,
y,
prefix
) as LocArr;
this.battle(ex, ey, true, core.doAction);
}
};
core.enemys.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 => {
if (!(v.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[v.id] = curr.onMapEnemy;
} else {
used[v.id].push(v);
}
});
return enemys.sort((a, b) => {
const ad = a.enemy.calDamage().damage;
const bd = b.enemy.calDamage().damage;
return ad - bd;
});
};
}
-952
View File
@@ -1,952 +0,0 @@
import { getHeroStatusOf, getHeroStatusOn } from '../hero';
import { Range, RangeCollection } from '../range';
import { checkV2, ensureArray, formatDamage, has, manhattan } 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;
x?: number;
y?: number;
floorId?: FloorIds;
}
interface DamageInfo {
damage: 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];
special: number;
from?: DamageEnemy;
}
interface DamageDelta {
/** 跟最小伤害值的减伤 */
delta: number;
damage: number;
info: DamageInfo;
}
interface CriticalDamageDelta extends Omit<DamageDelta, 'info'> {
/** 勇士的攻击增量 */
atkDelta: number;
}
type HaloFn = (info: EnemyInfo, enemy: Enemy) => void;
/** 光环属性 */
export const haloSpecials: number[] = [8, 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();
}
get(x: number, y: number) {
return this.list.find(v => v.x === x && v.y === y);
}
/**
* 解析本地图的怪物信息
*/
extract() {
this.list = [];
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() {
this.haloList = [];
this.list.forEach(v => {
v.reset();
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],
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, e.enemy);
});
});
} else {
enemy.forEach(e => {
arr.forEach(v => {
e.injectHalo(v, e.enemy);
e.preProvideHalo();
});
});
}
}
/**
* 预平衡光环
*/
preBalanceHalo() {
this.list.forEach(v => {
v.preProvideHalo();
});
}
render(onMap: boolean = false, cal: boolean = false) {
if (cal) {
this.calMapDamage();
}
core.status.damage.data = [];
core.status.damage.extraData = [];
core.status.damage.dir = [];
// 怪物伤害
this.list.forEach(v => {
if (onMap && !checkV2(v.x, v.y)) return;
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 criGem = core.getLocalStorage('criticalGem', false);
const n = critical?.atkDelta ?? Infinity;
const ratio = core.status.maps[this.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[this.floorId];
const width = floor.width;
const height = floor.height;
const objs = core.getMapBlocksObj(this.floorId);
const startX =
onMap && core.bigmap.v2
? Math.max(0, core.bigmap.posX - core.bigmap.extend)
: 0;
const endX =
onMap && core.bigmap.v2
? Math.min(
width,
core.bigmap.posX + core._WIDTH_ + core.bigmap.extend + 1
)
: width;
const startY =
onMap && core.bigmap.v2
? Math.max(0, core.bigmap.posY - core.bigmap.extend)
: 0;
const endY =
onMap && core.bigmap.v2
? Math.min(
height,
core.bigmap.posY + core._HEIGHT_ + core.bigmap.extend + 1
)
: height;
for (let x = startX; x < endX; x++) {
for (let y = startY; y < endY; y++) {
const id = `${x},${y}` as LocString;
const dam = this.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.mockery) {
dam.mockery.sort((a, b) =>
a[0] === b[0] ? a[1] - b[1] : a[0] - b[0]
);
const [tx, ty] = dam.mockery[0];
const dir =
x > tx ? '←' : x < tx ? '→' : y > ty ? '↑' : '↓';
core.status.damage.extraData.push({
text: '嘲' + dir,
px: 32 * x + 16,
py: 32 * (y + 1) - 14,
color: '#fd4',
alpha: 1
});
}
}
}
}
}
export class DamageEnemy<T extends EnemyIds = EnemyIds> {
id: T;
x?: number;
y?: number;
floorId?: FloorIds;
enemy: Enemy<T>;
col?: EnemyCollection;
/**
* 怪物属性。
* 属性计算流程:预平衡光环(即计算加光环的光环怪的光环) -> 计算怪物在没有光环下的属性
* -> provide inject 光环 -> 计算怪物的光环加成 -> 计算完毕
*/
info!: EnemyInfo;
/** 向其他怪提供过的光环 */
providedHalo: number[] = [];
/**
* 伤害计算进度,0 -> 预平衡光环 -> 1 -> 计算没有光环的属性 -> 2 -> provide inject 光环
* -> 3 -> 计算光环加成 -> 4 -> 计算完毕
*/
private progress: number = 0;
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,
x: this.x,
y: this.y,
floorId: this.floorId
};
this.progress = 0;
this.providedHalo = [];
}
/**
* 计算怪物在不计光环下的属性,在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;
// 智慧之源
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;
// 融化,融化不属于怪物光环,因此不能用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.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;
}
getHaloSpecials(): number[] {
if (!this.floorId) return [];
if (!has(this.x) || !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() {
if (this.progress !== 0) return;
this.progress = 1;
}
/**
* 向其他怪提供光环
*/
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 square7: HaloFn[] = [];
const square5: HaloFn[] = [];
// e 是被加成怪的属性,enemy 是施加光环的怪
// 抱团
if (special.includes(8)) {
square5.push((e, enemy) => {
if (
e.special.includes(8) &&
(e.x !== this.x || this.y !== e.y)
) {
e.atkBuff += enemy.together ?? 0;
e.defBuff += enemy.together ?? 0;
}
});
this.providedHalo.push(8);
}
// 冰封光环
if (special.includes(21)) {
square7.push(e => {
e.damageDecline += this.enemy.iceHalo ?? 0;
});
this.providedHalo.push(21);
col.haloList.push({
type: 'square',
data: { x: this.x, y: this.y, d: 7 },
special: 21,
from: this
});
}
// 冰封之核
if (special.includes(26)) {
square5.push(e => {
e.defBuff += this.enemy.iceCore ?? 0;
});
this.providedHalo.push(26);
col.haloList.push({
type: 'square',
data: { x: this.x, y: this.y, d: 5 },
special: 26,
from: this
});
}
// 火焰之核
if (special.includes(27)) {
square5.push(e => {
e.atkBuff += this.enemy.fireCore ?? 0;
});
this.providedHalo.push(27);
col.haloList.push({
type: 'square',
data: { x: this.x, y: this.y, d: 5 },
special: 27,
from: this
});
}
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, enemy: Enemy) {
halo(this.info, enemy);
}
/**
* 计算怪物伤害
*/
calDamage(hero: Partial<HeroStatus> = core.status.hero) {
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.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 (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.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);
}
private calEnemyDamageOf(hero: Partial<HeroStatus>, enemy: EnemyInfo) {
const status = getHeroStatusOf(hero, realStatus, this.floorId);
let damage = calDamageWith(enemy, 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 d = calDamageWith(enemy, status) ?? Infinity;
if (d < damage) {
damage = d;
skill = i;
}
flags[condition] = false;
}
}
return { damage, skill };
}
/**
* 计算怪物临界,计算临界时,根据当前方向计算临界,但也会输出与当前最少伤害的伤害差值
* @param num 要计算多少个临界
* @param dir 从怪物位置指向勇士的方向
* @param hero 勇士属性,最终结果将会与由此属性计算出的伤害相减计算减伤
*/
calCritical(
num: number = 1,
hero: Partial<HeroStatus> = 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<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 calDam = () => {
return this.calEnemyDamageOf({ atk: curr, def }, 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 - ori
});
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.includes(3)) {
return Infinity;
}
// 列方程求解,拿笔算一下就知道了
// 饥渴,会偷取勇士攻击
if (info.special.includes(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.includes(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'];
/**
* 主动技能列表
*/
const skills: [unlock: string, condition: string][] = [
['bladeOn', 'blade'],
['shieldOn', 'shield']
];
/**
* 计算怪物伤害
* @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;
let { hp: monHp, atk: monAtk, def: monDef, special, enemy } = info;
let damage = 0;
// 饥渴
if (special.includes(7)) {
const delta = Math.floor((atk * enemy.hungry!) / 100);
atk -= delta;
monAtk += delta;
}
let heroPerDamage: number;
// 绝对防御
if (special.includes(9)) {
heroPerDamage = atk + mana - monDef;
if (heroPerDamage <= 0) return null;
} else if (special.includes(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.includes(20) && !core.hasEquip('I589')) {
heroPerDamage *= 1 - enemy.ice! / 100;
}
heroPerDamage *= 1 - info.damageDecline / 100;
let enemyPerDamage: number;
// 魔攻
if (special.includes(2) || special.includes(13)) {
enemyPerDamage = monAtk;
} else {
enemyPerDamage = monAtk - def;
if (enemyPerDamage < 0) enemyPerDamage = 0;
}
// 先攻
if (special.includes(17)) {
damage += enemyPerDamage;
}
// 连击
if (special.includes(4)) enemyPerDamage *= 2;
if (special.includes(5)) enemyPerDamage *= 3;
if (special.includes(6)) enemyPerDamage *= enemy.n!;
// 苍蓝刻
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);
}
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;
}
declare global {
interface PluginDeclaration {
damage: {
Enemy: typeof DamageEnemy;
Collection: typeof EnemyCollection;
};
}
interface Floor {
enemy: EnemyCollection;
}
}
-204
View File
@@ -1,204 +0,0 @@
import { getHeroStatusOn } from '../hero';
export interface SpecialDeclaration {
code: number;
name: string | ((enemy: Enemy) => string);
desc: string | ((enemy: Enemy) => string);
color: string;
}
export const specials: SpecialDeclaration[] = [
{
code: 0,
name: '空',
desc: '空',
color: '#fff'
},
{
code: 1,
name: '致命一击',
desc: enemy => `怪物每5回合触发一次强力攻击,造成${enemy.crit}%的伤害`,
color: '#fc3'
},
{
code: 2,
name: '恶毒',
desc: '怪物攻击无视勇士的防御',
color: '#bbb0ff'
},
{
code: 3,
name: '坚固',
desc: '怪物防御不小于勇士攻击-1',
color: '#c0b088'
},
{
code: 4,
name: '2连击',
desc: '怪物每回合攻击2次',
color: '#fe7'
},
{
code: 5,
name: '3连击',
desc: '怪物每回合攻击3次',
color: '#fe7'
},
{
code: 6,
name: enemy => `${enemy.n}连击`,
desc: enemy => `怪物每回合攻击${enemy.n}次`,
color: '#fe7'
},
{
code: 7,
name: '饥渴',
desc: enemy =>
`战斗前,怪物降低勇士${enemy.hungry}%的攻击,并加在自己身上`,
color: '#b67'
},
{
code: 8,
name: '抱团',
desc: enemy =>
`怪物周围5×5范围内每有一个拥有该属性的怪物,自身攻防就增加${enemy.together}%(线性叠加)`,
color: '#fa4'
},
{
code: 9,
name: '绝对防御',
desc: '怪物的奇特护甲可以让勇士的额外攻击失效,攻击变为基础攻击+额外攻击',
color: '#80eed6'
},
{
code: 10,
name: '勇气之刃',
desc: enemy => `怪物第一回合造成${enemy.courage}%的伤害`,
color: '#b0c0dd'
},
{
code: 11,
name: '勇气冲锋',
desc: enemy =>
`怪物首先攻击,造成${enemy.charge}%的伤害,并眩晕勇士5回合`,
color: '#ff00d2'
},
{
code: 12,
name: '追猎',
desc: '当勇士移动到该怪物的水平或竖直方向上时,怪物向勇士移动一格',
color: '#9e8'
},
{
code: 13,
name: '魔攻',
desc: '怪物攻击无视勇士的防御',
color: '#bbb0ff'
},
{
code: 14,
name: '智慧之源',
desc: '困难难度下(简单难度没有效果),战斗后,怪物会吸取勇士30%的智慧(勇士智慧向下取整至整十)加在本层的拥有该属性的怪物攻击上',
color: '#bbeef0'
},
{
code: 15,
name: '突刺',
desc: enemy =>
`勇士走到怪物怪物周围四格时,怪物对勇士造成${core.formatBigNumber(
Math.max((enemy.value || 0) - getHeroStatusOn('def'))
)}点伤害`,
color: '#c677dd'
},
{
code: 16,
name: '空',
desc: '空',
color: '#fff'
},
{
code: 17,
name: '先攻',
desc: '战斗时,怪物首先攻击',
color: '#b0b666'
},
{
code: 18,
name: '阻击',
desc: enemy =>
`经过怪物十字范围内时怪物后退一格,同时对勇士造成${enemy.value}点伤害`,
color: '#8888e6'
},
{
code: 19,
name: '电摇嘲讽',
desc:
'当勇士移动到怪物同行或同列时,勇士会直接冲向怪物,撞碎路上的所有地形和门,拾取路上的道具,与路上的怪物战斗' +
',最后与该怪物战斗',
color: '#ff6666'
},
{
code: 20,
name: '霜冻',
desc: enemy =>
`怪物寒冷的攻击使勇士动作变慢,勇士每回合对怪物造成的伤害减少${enemy.ice}%。装备杰克的衣服后可以免疫。`,
color: 'cyan'
},
{
code: 21,
name: '冰封光环',
desc: enemy =>
`寒气逼人,使勇士对该怪物周围7*7范围内的怪物伤害减少${enemy.iceHalo}%(线性叠加)`,
color: 'cyan'
},
{
code: 22,
name: '永夜',
desc: enemy =>
`战斗后,减少勇士${enemy.night}点攻防,增加本层所有怪物${enemy.night}点攻防,仅在本层有效`,
color: '#d8a'
},
{
code: 23,
name: '极昼',
desc: enemy =>
`战斗后,减少本层所有怪物${enemy.day}点攻防,增加勇士${enemy.day}点攻防,仅在本层有效`,
color: '#ffd'
},
{
code: 24,
name: '射击',
desc: function () {
return '经过怪物同行或同列的可视范围内时受到一次普通攻击的伤害';
},
color: '#dda0dd'
},
{
code: 25,
name: '融化',
desc: enemy =>
`战斗后该怪物会融化,在怪物位置产生一个3*3的范围光环,光环内怪物的攻防增加${enemy.melt}%`,
color: '#e6e099'
},
{
code: 26,
name: '冰封之核',
desc: enemy =>
`怪物拥有逼人的寒气,使周围5*5范围内的怪物防御增加${enemy.iceCore}%`,
color: '#70ffd1'
},
{
code: 27,
name: '火焰之核',
desc: enemy =>
`怪物拥有灼热的火焰,使周围5*5范围内的怪物攻击增加${enemy.fireCore}%`,
color: '#ff6f0a'
},
{
code: 28,
name: '苍蓝刻',
desc: enemy =>
`怪物使用苍蓝之灵的力量,使自身受到的伤害减少${enemy.paleShield}%`,
color: '#ff6f0a'
}
];
+1 -1
View File
@@ -1,4 +1,4 @@
import { ensureFloorDamage } from '../enemy/damage';
import { ensureFloorDamage } from '@/game/enemy/damage';
export function init() {
core.control.updateDamage = function (floorId = core.status.floorId, ctx) {
+1 -1
View File
@@ -1,4 +1,4 @@
import { getEnemy } from '../enemy/battle';
import { getEnemy } from '@/game/enemy/battle';
import { formatDamage } from '../utils';
export function init() {
+4 -10
View File
@@ -2,14 +2,12 @@
import * as fiveLayer from './fiveLayer';
import * as heroFourFrames from './fx/heroFourFrames';
import * as itemDetail from './fx/itemDetail';
import * as checkBlock from './enemy/checkblock';
import * as replay from './replay';
import * as ui from './ui';
import * as rewrite from './fx/rewrite';
import * as halo from './fx/halo';
import * as hero from './hero';
import * as loopMap from './loopMap';
import * as remainEnemy from './enemy/remainEnemy';
import * as removeMap from './removeMap';
import * as shop from './shop';
import * as skill from './skill';
@@ -18,9 +16,8 @@ import * as study from './study';
import * as towerBoss from './towerBoss';
import * as utils from './utils';
import * as chase from './chase';
import * as damage from './enemy/damage';
import * as battle from './enemy/battle';
import * as special from './enemy/special';
import * as remainEnemy from './enemy/remainEnemy';
import * as checkBlock from './enemy/checkblock';
Mota.Plugin.register('utils_g', utils);
Mota.Plugin.register('loopMap_g', loopMap, loopMap.init);
@@ -28,7 +25,6 @@ Mota.Plugin.register('shop_g', shop);
Mota.Plugin.register('replay_g', replay, replay.init);
Mota.Plugin.register('skillTree_g', skillTree);
Mota.Plugin.register('removeMap_g', removeMap);
Mota.Plugin.register('remainEnemy_g', remainEnemy);
Mota.Plugin.register('chase_g', chase);
Mota.Plugin.register('skill_g', skill);
Mota.Plugin.register('towerBoss_g', towerBoss);
@@ -36,13 +32,11 @@ Mota.Plugin.register('fiveLayer_g', fiveLayer, fiveLayer.init);
Mota.Plugin.register('heroFourFrames_g', heroFourFrames, heroFourFrames.init);
Mota.Plugin.register('rewrite_g', rewrite, rewrite.init);
Mota.Plugin.register('itemDetail_g', itemDetail, itemDetail.init);
Mota.Plugin.register('checkBlock_g', checkBlock, checkBlock.init);
Mota.Plugin.register('halo_g', halo);
Mota.Plugin.register('study_g', study);
Mota.Plugin.register('remainEnemy_g', remainEnemy);
Mota.Plugin.register('checkBlock_g', checkBlock, checkBlock.init);
// todo: 这几个不应该放到插件
Mota.Plugin.register('damage_g', damage);
Mota.Plugin.register('battle_g', battle, battle.init);
Mota.Plugin.register('special_g', special);
Mota.Plugin.register('hero_g', hero);
Mota.Plugin.register('ui_g', ui, ui.init);
-1
View File
@@ -1,4 +1,3 @@
///<reference path="../../../src/types/core.d.ts" />
import { slide } from './utils';
const list = ['tower6'];
-2
View File
@@ -1,5 +1,3 @@
///<reference path="../../../src/types/core.d.ts" />
export function removeMaps(
fromId: FloorIds,
toId: FloorIds,
-2
View File
@@ -1,5 +1,3 @@
///<reference path="../../../src/types/core.d.ts" />
import { upgradeSkill } from './skillTree';
const replayableSettings = ['autoSkill'];
-2
View File
@@ -1,5 +1,3 @@
///<reference path="../../../src/types/core.d.ts" />
let levels: number[] = [];
/**
+1 -1
View File
@@ -1,5 +1,5 @@
// @ts-nocheck
import { clip } from './replay.js';
import { clip } from './replay';
// 1000多行,改不动了,原来什么样就什么样吧
+3 -3
View File
@@ -1,8 +1,7 @@
import { fixedUi } from '@/core/main/init/ui';
import type { DamageEnemy } from './game/enemy/damage';
import type { DamageEnemy } from '../game/enemy/damage';
import { tip } from './utils';
import { ref, Ref } from 'vue';
import { hook } from '@/core/main/game';
export interface MarkInfo<T extends EnemyIds> {
id: T;
@@ -35,7 +34,7 @@ const marked: MarkInfo<EnemyIds>[] = [];
*/
export function markEnemy(id: EnemyIds) {
if (hasMarkedEnemy(id)) return;
const { DamageEnemy } = Mota.Plugin.require('damage_g');
const DamageEnemy = Mota.require('class', 'DamageEnemy');
const enemy = new DamageEnemy(core.material.enemys[id]);
enemy.calAttribute();
enemy.getRealInfo();
@@ -159,6 +158,7 @@ export function hasMarkedEnemy(id: EnemyIds) {
return marked.some(v => v.id === id);
}
const hook = Mota.require('var', 'hook');
hook.on('statusBarUpdate', () => {
checkMarkedEnemy();
});
+1 -1
View File
@@ -1,4 +1,4 @@
import type { CurrentEnemy } from '../game/enemy/battle';
import type { CurrentEnemy } from '../../game/enemy/battle';
import { has } from '../utils';
export interface ToShowEnemy extends CurrentEnemy {
+1 -1
View File
@@ -1,6 +1,6 @@
import { getDamageColor } from '../utils';
import { ToShowEnemy } from './book';
import type { DamageEnemy } from '../game/enemy/damage';
import type { DamageEnemy } from '../../game/enemy/damage';
import { isMobile } from '../use';
export function getLocFromMouseLoc(x: number, y: number): LocArr {