refactor: 插件

This commit is contained in:
AncTe committed 2024-02-01 19:31:49 +08:00
1 parent ef9a3d6537
commit 325a1e85ec
69 files changed
+1704 -1549

No files matched your search

+2 -2
View File
@@ -123,7 +123,7 @@ export const camera1: ChaseCameraData[] = [
* 追逐战开始前的初始化函数,移除所有血瓶和门等
*/
export function init1() {
return core.plugin.chase.chaseInit1();
return Mota.Plugin.require('chase_g').chaseInit1();
}
export function chaseShake(chase: Chase) {
@@ -561,7 +561,7 @@ export function para3(chase: Chase) {
'MT14',
async () => {
flags.finishChase1 = true;
core.plugin.replay.clip('choices:0');
Mota.Plugin.require('replay_g').clip('choices:0');
core.showStatusBar();
ani.time(750).apply('rect', 0);
chase.end();
+10
View File
@@ -5,6 +5,7 @@ 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']
@@ -21,6 +22,15 @@ export const achiDict: Record<number, number> = {
1: 0
};
loading.once('coreInit', () => {
Object.values(floors).forEach((v, i) => {
const from = core.floorIds.indexOf(v[0]);
const to = core.floorIds.indexOf(v[1]);
const all = core.floorIds.slice(from, to + 1);
floors[i + 1] = all;
});
});
/**
* 检查所有到达过的楼层,用于成就的计算
*/
+39 -40
View File
@@ -7,7 +7,9 @@ tran.value.y = 0;
let needSmooth = false;
export default function init() {
export function init() {
const setting = Mota.require('var', 'mainSetting');
tran.ticker.add(() => {
if (core.isPlaying() && needSmooth) {
core.setViewport(tran.value.x, tran.value.y);
@@ -18,50 +20,47 @@ export default function init() {
needSmooth = false;
}, 700);
control.prototype._drawHero_updateViewport = function (
x: number,
y: number,
offset: Loc
) {
const ox = core.clamp(
(x - core._HALF_WIDTH_) * 32 + offset.x,
0,
Math.max(32 * core.bigmap.width - core._PX_, 0)
);
const oy = core.clamp(
(y - core._HALF_HEIGHT_) * 32 + offset.y,
0,
Math.max(32 * core.bigmap.height - core._PY_, 0)
);
Mota.rewrite(
core.control,
'_drawHero_updateViewport',
'full',
(x, y, offset) => {
const ox = core.clamp(
(x - core._HALF_WIDTH_) * 32 + offset.x,
0,
Math.max(32 * core.bigmap.width - core._PX_, 0)
);
const oy = core.clamp(
(y - core._HALF_HEIGHT_) * 32 + offset.y,
0,
Math.max(32 * core.bigmap.height - core._PY_, 0)
);
tran.transition('x', ox).transition('y', oy);
tran.transition('x', ox).transition('y', oy);
if (tran.easeTime > 0) {
needSmooth = true;
func();
} else {
core.setViewport(tran.value.x, tran.value.y);
const t = setting.getValue('screen.smoothView', false);
if (!t) return;
if (tran.easeTime > 0) {
needSmooth = true;
func();
} else {
core.setViewport(tran.value.x, tran.value.y);
}
}
};
);
const hso = hyper('sin', 'out');
let time2 = Date.now();
const origin1 = control.prototype._moveAction_moving;
control.prototype._moveAction_moving = function (...params: any[]) {
if (Date.now() - time2 > 20)
tran.mode(hyper('sin', 'out')).time(200).absolute();
return origin1.call(this, ...params);
};
const origin2 = control.prototype.moveDirectly;
control.prototype.moveDirectly = function (...params: any[]) {
Mota.rewrite(core.control, '_moveAction_moving', 'front', () => {
const t = setting.getValue('screen.smoothView', false) ? 200 : 1;
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;
time2 = Date.now();
tran.mode(hyper('sin', 'out')).time(600).absolute();
return origin2.call(this, ...params);
};
const origin3 = events.prototype._changeFloor_beforeChange;
events.prototype._changeFloor_beforeChange = function (...params: any[]) {
tran.mode(hso).time(t).absolute();
});
Mota.rewrite(core.events, '_changeFloor_beforeChange', 'front', () => {
tran.time(1).absolute();
return origin3.call(this, ...params);
};
});
}
@@ -1,6 +1,6 @@
export function chaseInit1() {
const ids = ['MT13', 'MT14', 'MT15'];
const toRemove = [];
const ids: FloorIds[] = ['MT13', 'MT14', 'MT15'];
const toRemove: [number, number, FloorIds][] = [];
ids.forEach(v => {
core.status.maps[v].cannotMoveDirectly = true;
core.extractBlocks(v);
@@ -17,7 +17,3 @@ export function chaseInit1() {
core.removeBlock(...v);
});
}
core.plugin.chase = {
chaseInit1
};
+210 -218
View File
@@ -22,232 +22,224 @@ export function getEnemy(
return enemy;
}
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;
};
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);
// 战前事件
if (!this.beforeBattle(enemy, x, y))
return core.clearContinueAutomaticRoute(callback);
// 战后事件
this.afterBattle(enemy, x, y);
callback?.();
};
core.events.beforeBattle = function () {
return true;
};
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) && core.has(x) && core.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);
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);
}
} else {
this.battle(data.x, data.y, false, callback);
}
};
// 自动存档
if (!core.status.event.id) core.autosave(true);
// 战前事件
// 战后事件
this.afterBattle(enemy, x, y);
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();
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;
}
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);
// 扣减体力值并记录统计数据
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;
}
});
return enemys.sort((a, b) => {
const ad = a.enemy.calDamage().damage;
const bd = b.enemy.calDamage().damage;
return ad - bd;
});
};
// 极昼永夜
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;
}
declare global {
interface Events {
beforeBattle(enemy: DamageEnemy, x: number, y: number): boolean;
afterBattle(enemy: DamageEnemy, x: number, y: number): void;
}
// if (core.plugin.skillTree.getSkillLevel(11) > 0) {
// core.plugin.study.declineStudiedSkill();
// }
interface Enemys {
getCurrentEnemys(floorId?: FloorIds): CurrentEnemy[];
}
// 如果是融化怪,需要特殊标记一下
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;
});
};
}
+152 -147
View File
@@ -1,157 +1,162 @@
import { drawHalo } from '../fx/halo';
// 伤害弹出
// 复写阻激夹域检测
control.prototype.checkBlock = function (forceMockery: boolean = false) {
const x = core.getHeroLoc('x'),
y = core.getHeroLoc('y'),
loc = x + ',' + y;
const info = core.status.thisMap.enemy.mapDamage[loc];
const damage = info?.damage;
if (damage) {
if (!main.replayChecking) {
Mota.Plugin.require('pop').addPop(
(x - core.bigmap.offsetX / 32) * 32 + 12,
(y - core.bigmap.offsetY / 32) * 32 + 20,
(-damage).toString()
);
}
core.status.hero.hp -= damage;
const type = [...info.type];
const text = type.join(',') || '伤害';
core.drawTip('受到' + text + damage + '点');
core.drawHeroAnimate('zone');
this._checkBlock_disableQuickShop();
core.status.hero.statistics.extraDamage += damage;
if (core.status.hero.hp <= 0) {
core.status.hero.hp = 0;
core.updateStatusBar();
core.events.lose();
return;
} else {
core.updateStatusBar();
}
}
checkMockery(loc, forceMockery);
};
control.prototype._drawDamage_draw = function (
ctx: CanvasRenderingContext2D,
onMap: boolean,
floorId: FloorIds
) {
if (!core.hasItem('book')) return;
drawHalo(ctx, onMap, floorId);
core.setFont(ctx, "14px 'normal'");
core.setTextAlign(ctx, 'center');
core.setTextBaseline(ctx, 'middle');
core.status.damage.extraData.forEach(function (one) {
var px = one.px,
py = one.py;
if (onMap && core.bigmap.v2) {
px -= core.bigmap.posX * 32;
py -= core.bigmap.posY * 32;
if (
px < -32 ||
px > core._PX_ + 32 ||
py < -32 ||
py > core._PY_ + 32
)
export function init() {
// 伤害弹出
// 复写阻激夹域检测
control.prototype.checkBlock = function (forceMockery: boolean = false) {
const x = core.getHeroLoc('x'),
y = core.getHeroLoc('y'),
loc = x + ',' + y;
const info = core.status.thisMap.enemy.mapDamage[loc];
const damage = info?.damage;
if (damage) {
if (!main.replayChecking) {
Mota.Plugin.require('pop_r').addPop(
(x - core.bigmap.offsetX / 32) * 32 + 12,
(y - core.bigmap.offsetY / 32) * 32 + 20,
(-damage).toString()
);
}
core.status.hero.hp -= damage;
const type = [...info.type];
const text = type.join(',') || '伤害';
core.drawTip('受到' + text + damage + '点');
core.drawHeroAnimate('zone');
this._checkBlock_disableQuickShop();
core.status.hero.statistics.extraDamage += damage;
if (core.status.hero.hp <= 0) {
core.status.hero.hp = 0;
core.updateStatusBar();
core.events.lose();
return;
} else {
core.updateStatusBar();
}
}
var alpha = core.setAlpha(ctx, one.alpha);
core.fillBoldText(ctx, one.text, px, py, one.color as string);
core.setAlpha(ctx, alpha);
});
checkMockery(loc, forceMockery);
};
core.setTextAlign(ctx, 'left');
core.setTextBaseline(ctx, 'alphabetic');
core.status.damage.data.forEach(function (one) {
var px = one.px,
py = one.py;
if (onMap && core.bigmap.v2) {
px -= core.bigmap.posX * 32;
py -= core.bigmap.posY * 32;
if (
px < -32 * 2 ||
px > core._PX_ + 32 ||
py < -32 ||
py > core._PY_ + 32
)
return;
control.prototype._drawDamage_draw = function (
ctx: CanvasRenderingContext2D,
onMap: boolean,
floorId: FloorIds
) {
if (!core.hasItem('book')) return;
drawHalo(ctx, onMap, floorId);
core.setFont(ctx, "14px 'normal'");
core.setTextAlign(ctx, 'center');
core.setTextBaseline(ctx, 'middle');
core.status.damage.extraData.forEach(function (one) {
var px = one.px,
py = one.py;
if (onMap && core.bigmap.v2) {
px -= core.bigmap.posX * 32;
py -= core.bigmap.posY * 32;
if (
px < -32 ||
px > core._PX_ + 32 ||
py < -32 ||
py > core._PY_ + 32
)
return;
}
var alpha = core.setAlpha(ctx, one.alpha);
core.fillBoldText(ctx, one.text, px, py, one.color as string);
core.setAlpha(ctx, alpha);
});
core.setTextAlign(ctx, 'left');
core.setTextBaseline(ctx, 'alphabetic');
core.status.damage.data.forEach(function (one) {
var px = one.px,
py = one.py;
if (onMap && core.bigmap.v2) {
px -= core.bigmap.posX * 32;
py -= core.bigmap.posY * 32;
if (
px < -32 * 2 ||
px > core._PX_ + 32 ||
py < -32 ||
py > core._PY_ + 32
)
return;
}
core.fillBoldText(ctx, one.text, px, py, one.color as string);
});
ctx.save();
ctx.lineCap = 'round';
ctx.lineJoin = 'round';
ctx.lineWidth = 2;
core.status.damage.dir.forEach(v => {
let x = v.x;
let y = v.y;
if (onMap && core.bigmap.v2) {
x -= core.bigmap.posX;
y -= core.bigmap.posY;
}
if (x < -1 || x > 15 || y < 0 || y > 15) return;
let px = x * 32;
let py = y * 32;
ctx.beginPath();
if (v.dir === 'down') {
py -= 32;
ctx.moveTo(px + 16, py + 18);
ctx.lineTo(px + 16, py + 32);
ctx.moveTo(px + 10, py + 26);
ctx.lineTo(px + 16, py + 32);
ctx.lineTo(px + 22, py + 26);
} else if (v.dir === 'left') {
px += 32;
ctx.moveTo(px + 14, py + 16);
ctx.lineTo(px, py + 16);
ctx.moveTo(px + 6, py + 10);
ctx.lineTo(px, py + 16);
ctx.lineTo(px + 6, py + 22);
} else if (v.dir === 'up') {
py += 32;
ctx.moveTo(px + 16, py + 14);
ctx.lineTo(px + 16, py);
ctx.moveTo(px + 10, py + 6);
ctx.lineTo(px + 16, py);
ctx.lineTo(px + 22, py + 6);
} else {
px -= 32;
ctx.moveTo(px + 18, py + 16);
ctx.lineTo(px + 32, py + 16);
ctx.moveTo(px + 26, py + 10);
ctx.lineTo(px + 32, py + 16);
ctx.lineTo(px + 26, py + 22);
}
ctx.strokeStyle = 'black';
ctx.lineWidth = 2.5;
ctx.stroke();
ctx.strokeStyle = v.color as string;
ctx.lineWidth = 1;
ctx.stroke();
});
ctx.restore();
};
control.prototype.moveHero = function (
direction: Dir,
callback: () => void
) {
// 如果正在移动,直接return
if (core.status.heroMoving != 0) return;
if (core.isset(direction)) core.setHeroLoc('direction', direction);
const nx = core.nextX();
const ny = core.nextY();
if (core.status.thisMap.enemy.mapDamage[`${nx},${ny}`]?.mockery) {
core.autosave();
}
core.fillBoldText(ctx, one.text, px, py, one.color as string);
});
ctx.save();
ctx.lineCap = 'round';
ctx.lineJoin = 'round';
ctx.lineWidth = 2;
core.status.damage.dir.forEach(v => {
let x = v.x;
let y = v.y;
if (onMap && core.bigmap.v2) {
x -= core.bigmap.posX;
y -= core.bigmap.posY;
}
if (x < -1 || x > 15 || y < 0 || y > 15) return;
let px = x * 32;
let py = y * 32;
ctx.beginPath();
if (v.dir === 'down') {
py -= 32;
ctx.moveTo(px + 16, py + 18);
ctx.lineTo(px + 16, py + 32);
ctx.moveTo(px + 10, py + 26);
ctx.lineTo(px + 16, py + 32);
ctx.lineTo(px + 22, py + 26);
} else if (v.dir === 'left') {
px += 32;
ctx.moveTo(px + 14, py + 16);
ctx.lineTo(px, py + 16);
ctx.moveTo(px + 6, py + 10);
ctx.lineTo(px, py + 16);
ctx.lineTo(px + 6, py + 22);
} else if (v.dir === 'up') {
py += 32;
ctx.moveTo(px + 16, py + 14);
ctx.lineTo(px + 16, py);
ctx.moveTo(px + 10, py + 6);
ctx.lineTo(px + 16, py);
ctx.lineTo(px + 22, py + 6);
} else {
px -= 32;
ctx.moveTo(px + 18, py + 16);
ctx.lineTo(px + 32, py + 16);
ctx.moveTo(px + 26, py + 10);
ctx.lineTo(px + 32, py + 16);
ctx.lineTo(px + 26, py + 22);
}
ctx.strokeStyle = 'black';
ctx.lineWidth = 2.5;
ctx.stroke();
ctx.strokeStyle = v.color as string;
ctx.lineWidth = 1;
ctx.stroke();
});
ctx.restore();
};
control.prototype.moveHero = function (direction: Dir, callback: () => void) {
// 如果正在移动,直接return
if (core.status.heroMoving != 0) return;
if (core.isset(direction)) core.setHeroLoc('direction', direction);
const nx = core.nextX();
const ny = core.nextY();
if (core.status.thisMap.enemy.mapDamage[`${nx},${ny}`]?.mockery) {
core.autosave();
}
if (callback) return this.moveAction(callback);
this._moveHero_moving();
};
if (callback) return this.moveAction(callback);
this._moveHero_moving();
};
}
function checkMockery(loc: string, force: boolean = false) {
if (core.status.lockControl && !force) return;
+2 -7
View File
@@ -394,7 +394,7 @@ export class DamageEnemy<T extends EnemyIds = EnemyIds> {
getHaloSpecials(): number[] {
if (!this.floorId) return [];
if (!core.has(this.x) || !core.has(this.y)) 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);
@@ -424,7 +424,7 @@ export class DamageEnemy<T extends EnemyIds = EnemyIds> {
if (this.progress !== 2) return;
this.progress = 3;
if (!this.floorId) return;
if (!core.has(this.x) || !core.has(this.y)) 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();
@@ -950,8 +950,3 @@ declare global {
enemy: EnemyCollection;
}
}
core.plugin.damage = {
Enemy: DamageEnemy,
Collection: EnemyCollection
};
@@ -1,27 +1,19 @@
///<reference path="../../../../src/types/core.d.ts" />
// todo: 优化,直接调用 floor.enemy.list 进行计算
/**
* 检查漏怪
* @param {FloorIds[]} floorIds
*/
export function checkRemainEnemy(floorIds) {
/**
* @type {Record<FloorIds, {loc: LocArr, id: EnemyIds}[]>}
*/
const enemy = {};
export function checkRemainEnemy(floorIds: FloorIds[]) {
const enemy: Partial<Record<FloorIds, { loc: LocArr; id: EnemyIds }[]>> =
{};
floorIds.forEach(v => {
core.extractBlocks(v);
const blocks = core.status.maps[v].blocks;
blocks.forEach(block => {
if (!block.event.cls.startsWith('enemy') || block.disable) return;
/**
* @type {EnemyIds}
*/
const id = block.event.id;
const id: EnemyIds = block.event.id as EnemyIds;
enemy[v] ??= [];
const info = enemy[v];
const info = enemy[v]!;
info.push({ loc: [block.x, block.y], id });
});
});
@@ -30,30 +22,23 @@ export function checkRemainEnemy(floorIds) {
/**
* 获取剩余怪物字符串
* @param {FloorIds[]} floorIds
*/
export function getRemainEnemyString(floorIds) {
export function getRemainEnemyString(floorIds: FloorIds[]) {
const enemy = checkRemainEnemy(floorIds);
const str = [];
let now = [];
for (const floor in enemy) {
/**
* @type {{loc: LocArr, id: EnemyIds}[]}
*/
const all = enemy[floor];
/**
* @type {Record<EnemyIds, number>}
*/
const remain = {};
const all: { loc: LocArr; id: EnemyIds }[] = enemy[floor as FloorIds]!;
const remain: Partial<Record<EnemyIds, number>> = {};
all.forEach(v => {
const id = v.id;
remain[id] ??= 0;
remain[id]++;
remain[id]!++;
});
const title = core.status.maps[floor].title;
const title = core.status.maps[floor as FloorIds].title;
for (const id in remain) {
const name = core.material.enemys[id].name;
now.push(`${title}(${floor}): ${name} * ${remain[id]}`);
const name = core.material.enemys[id as EnemyIds].name;
now.push(`${title}(${floor}): ${name} * ${remain[id as EnemyIds]}`);
if (now.length === 10) {
str.push(now.join('\n'));
now = [];
@@ -67,8 +52,3 @@ export function getRemainEnemyString(floorIds) {
return str;
}
core.plugin.remainEnemy = {
checkRemainEnemy,
getRemainEnemyString
};
+1 -9
View File
@@ -7,7 +7,7 @@ export interface SpecialDeclaration {
color: string;
}
export const enemySpecials: SpecialDeclaration[] = [
export const specials: SpecialDeclaration[] = [
{
code: 0,
name: '空',
@@ -202,11 +202,3 @@ export const enemySpecials: SpecialDeclaration[] = [
color: '#ff6f0a'
}
];
declare global {
interface PluginDeclaration {
special: SpecialDeclaration[];
}
}
core.plugin.special = enemySpecials;
-199
View File
@@ -1,199 +0,0 @@
///<reference path="../../../src/types/core.d.ts" />
export {};
// 创建新图层
function createCanvas(name, zIndex) {
if (!name) return;
var canvas = document.createElement('canvas');
canvas.id = name;
canvas.className = 'gameCanvas no-anti-aliasing';
// 编辑器模式下设置zIndex会导致加入的图层覆盖优先级过高
if (main.mode != 'editor') canvas.style.zIndex = zIndex || 0;
// 将图层插入进游戏内容
document.getElementById('gameDraw').appendChild(canvas);
var ctx = canvas.getContext('2d');
core.canvas[name] = ctx;
return canvas;
}
var bg2Canvas = createCanvas('bg2', 20);
var fg2Canvas = createCanvas('fg2', 63);
// 大地图适配
core.bigmap.canvas = ['bg2', 'fg2', 'bg', 'event', 'event2', 'fg', 'damage'];
core.initStatus.bg2maps = {};
core.initStatus.fg2maps = {};
if (main.mode == 'editor') {
/*插入编辑器的图层 不做此步新增图层无法在编辑器显示*/
// 编辑器图层覆盖优先级 eui > efg > fg(前景层) > event2(48*32图块的事件层) > event(事件层) > bg(背景层)
// 背景层2(bg2) 插入事件层(event)之前(即bg与event之间)
document
.getElementById('mapEdit')
.insertBefore(bg2Canvas, document.getElementById('event'));
// 前景层2(fg2) 插入编辑器前景(efg)之前(即fg之后)
document
.getElementById('mapEdit')
.insertBefore(fg2Canvas, document.getElementById('ebm'));
// 原本有三个图层 从4开始添加
var num = 4;
// 新增图层存入editor.dom中
editor.dom.bg2c = core.canvas.bg2.canvas;
editor.dom.bg2Ctx = core.canvas.bg2;
editor.dom.fg2c = core.canvas.fg2.canvas;
editor.dom.fg2Ctx = core.canvas.fg2;
editor.dom.maps.push('bg2map', 'fg2map');
editor.dom.canvas.push('bg2', 'fg2');
// 创建编辑器上的按钮
var createCanvasBtn = name => {
// 电脑端创建按钮
var input = document.createElement('input');
// layerMod4/layerMod5
var id = 'layerMod' + num++;
// bg2map/fg2map
var value = name + 'map';
input.type = 'radio';
input.name = 'layerMod';
input.id = id;
input.value = value;
editor.dom[id] = input;
input.onchange = () => {
editor.uifunctions.setLayerMod(value);
};
return input;
};
var createCanvasBtn_mobile = name => {
// 手机端往选择列表中添加子选项
var input = document.createElement('option');
var id = 'layerMod' + num++;
var value = name + 'map';
input.name = 'layerMod';
input.value = value;
editor.dom[id] = input;
return input;
};
if (!editor.isMobile) {
var input = createCanvasBtn('bg2');
var input2 = createCanvasBtn('fg2');
// 获取事件层及其父节点
var child = document.getElementById('layerMod'),
parent = child.parentNode;
// 背景层2插入事件层前
parent.insertBefore(input, child);
// 不能直接更改背景层2的innerText 所以创建文本节点
var txt = document.createTextNode('背2');
// 插入事件层前(即新插入的背景层2前)
parent.insertBefore(txt, child);
// 向最后插入前景层2(即插入前景层后)
parent.appendChild(input2);
var txt2 = document.createTextNode('前2');
parent.appendChild(txt2);
} else {
var input = createCanvasBtn_mobile('bg2');
var input2 = createCanvasBtn_mobile('fg2');
// 手机端因为是选项 所以可以直接改innerText
input.innerText = '背景2';
input2.innerText = '前景2';
var parent = document.getElementById('layerMod');
parent.insertBefore(input, parent.children[1]);
parent.appendChild(input2);
}
}
maps.prototype._loadFloor_doNotCopy = function () {
return [
'firstArrive',
'eachArrive',
'blocks',
'parallelDo',
'map',
'bgmap',
'fgmap',
'bg2map',
'fg2map',
'events',
'changeFloor',
'afterBattle',
'afterGetItem',
'afterOpenDoor',
'cannotMove',
'enemy'
];
};
////// 绘制背景和前景层 //////
maps.prototype._drawBg_draw = function (floorId, toDrawCtx, cacheCtx, config) {
config.ctx = cacheCtx;
core.maps._drawBg_drawBackground(floorId, config);
// ------ 调整这两行的顺序来控制是先绘制贴图还是先绘制背景图块;后绘制的覆盖先绘制的。
core.maps._drawFloorImages(
floorId,
config.ctx,
'bg',
null,
null,
config.onMap
);
core.maps._drawBgFgMap(floorId, 'bg', config);
if (config.onMap) {
core.drawImage(
toDrawCtx,
cacheCtx.canvas,
core.bigmap.v2 ? -32 : 0,
core.bigmap.v2 ? -32 : 0
);
core.clearMap('bg2');
core.clearMap(cacheCtx);
}
core.maps._drawBgFgMap(floorId, 'bg2', config);
if (config.onMap)
core.drawImage(
'bg2',
cacheCtx.canvas,
core.bigmap.v2 ? -32 : 0,
core.bigmap.v2 ? -32 : 0
);
config.ctx = toDrawCtx;
};
maps.prototype._drawFg_draw = function (floorId, toDrawCtx, cacheCtx, config) {
config.ctx = cacheCtx;
// ------ 调整这两行的顺序来控制是先绘制贴图还是先绘制前景图块;后绘制的覆盖先绘制的。
core.maps._drawFloorImages(
floorId,
config.ctx,
'fg',
null,
null,
config.onMap
);
core.maps._drawBgFgMap(floorId, 'fg', config);
if (config.onMap) {
core.drawImage(
toDrawCtx,
cacheCtx.canvas,
core.bigmap.v2 ? -32 : 0,
core.bigmap.v2 ? -32 : 0
);
core.clearMap('fg2');
core.clearMap(cacheCtx);
}
core.maps._drawBgFgMap(floorId, 'fg2', config);
if (config.onMap)
core.drawImage(
'fg2',
cacheCtx.canvas,
core.bigmap.v2 ? -32 : 0,
core.bigmap.v2 ? -32 : 0
);
config.ctx = toDrawCtx;
};
////// 移动判定 //////
maps.prototype._generateMovableArray_arrays = function (floorId) {
return {
bgArray: this.getBgMapArray(floorId),
fgArray: this.getFgMapArray(floorId),
eventArray: this.getMapArray(floorId),
bg2Array: this._getBgFgMapArray('bg2', floorId),
fg2Array: this._getBgFgMapArray('fg2', floorId)
};
};
+219
View File
@@ -0,0 +1,219 @@
// @ts-nocheck
// 创建新图层
function createCanvas(name, zIndex) {
if (!name) return;
var canvas = document.createElement('canvas');
canvas.id = name;
canvas.className = 'gameCanvas no-anti-aliasing';
// 编辑器模式下设置zIndex会导致加入的图层覆盖优先级过高
if (main.mode != 'editor') canvas.style.zIndex = zIndex || 0;
// 将图层插入进游戏内容
document.getElementById('gameDraw').appendChild(canvas);
var ctx = canvas.getContext('2d');
core.canvas[name] = ctx;
return canvas;
}
export function init() {
var bg2Canvas = createCanvas('bg2', 20);
var fg2Canvas = createCanvas('fg2', 63);
// 大地图适配
core.bigmap.canvas = [
'bg2',
'fg2',
'bg',
'event',
'event2',
'fg',
'damage'
];
core.initStatus.bg2maps = {};
core.initStatus.fg2maps = {};
if (main.mode == 'editor') {
/*插入编辑器的图层 不做此步新增图层无法在编辑器显示*/
// 编辑器图层覆盖优先级 eui > efg > fg(前景层) > event2(48*32图块的事件层) > event(事件层) > bg(背景层)
// 背景层2(bg2) 插入事件层(event)之前(即bg与event之间)
document
.getElementById('mapEdit')
.insertBefore(bg2Canvas, document.getElementById('event'));
// 前景层2(fg2) 插入编辑器前景(efg)之前(即fg之后)
document
.getElementById('mapEdit')
.insertBefore(fg2Canvas, document.getElementById('ebm'));
// 原本有三个图层 从4开始添加
var num = 4;
// 新增图层存入editor.dom中
editor.dom.bg2c = core.canvas.bg2.canvas;
editor.dom.bg2Ctx = core.canvas.bg2;
editor.dom.fg2c = core.canvas.fg2.canvas;
editor.dom.fg2Ctx = core.canvas.fg2;
editor.dom.maps.push('bg2map', 'fg2map');
editor.dom.canvas.push('bg2', 'fg2');
// 创建编辑器上的按钮
var createCanvasBtn = name => {
// 电脑端创建按钮
var input = document.createElement('input');
// layerMod4/layerMod5
var id = 'layerMod' + num++;
// bg2map/fg2map
var value = name + 'map';
input.type = 'radio';
input.name = 'layerMod';
input.id = id;
input.value = value;
editor.dom[id] = input;
input.onchange = () => {
editor.uifunctions.setLayerMod(value);
};
return input;
};
var createCanvasBtn_mobile = name => {
// 手机端往选择列表中添加子选项
var input = document.createElement('option');
var id = 'layerMod' + num++;
var value = name + 'map';
input.name = 'layerMod';
input.value = value;
editor.dom[id] = input;
return input;
};
if (!editor.isMobile) {
var input = createCanvasBtn('bg2');
var input2 = createCanvasBtn('fg2');
// 获取事件层及其父节点
var child = document.getElementById('layerMod'),
parent = child.parentNode;
// 背景层2插入事件层前
parent.insertBefore(input, child);
// 不能直接更改背景层2的innerText 所以创建文本节点
var txt = document.createTextNode('背2');
// 插入事件层前(即新插入的背景层2前)
parent.insertBefore(txt, child);
// 向最后插入前景层2(即插入前景层后)
parent.appendChild(input2);
var txt2 = document.createTextNode('前2');
parent.appendChild(txt2);
} else {
var input = createCanvasBtn_mobile('bg2');
var input2 = createCanvasBtn_mobile('fg2');
// 手机端因为是选项 所以可以直接改innerText
input.innerText = '背景2';
input2.innerText = '前景2';
var parent = document.getElementById('layerMod');
parent.insertBefore(input, parent.children[1]);
parent.appendChild(input2);
}
}
maps.prototype._loadFloor_doNotCopy = function () {
return [
'firstArrive',
'eachArrive',
'blocks',
'parallelDo',
'map',
'bgmap',
'fgmap',
'bg2map',
'fg2map',
'events',
'changeFloor',
'afterBattle',
'afterGetItem',
'afterOpenDoor',
'cannotMove',
'enemy'
];
};
////// 绘制背景和前景层 //////
maps.prototype._drawBg_draw = function (
floorId,
toDrawCtx,
cacheCtx,
config
) {
config.ctx = cacheCtx;
core.maps._drawBg_drawBackground(floorId, config);
// ------ 调整这两行的顺序来控制是先绘制贴图还是先绘制背景图块;后绘制的覆盖先绘制的。
core.maps._drawFloorImages(
floorId,
config.ctx,
'bg',
null,
null,
config.onMap
);
core.maps._drawBgFgMap(floorId, 'bg', config);
if (config.onMap) {
core.drawImage(
toDrawCtx,
cacheCtx.canvas,
core.bigmap.v2 ? -32 : 0,
core.bigmap.v2 ? -32 : 0
);
core.clearMap('bg2');
core.clearMap(cacheCtx);
}
core.maps._drawBgFgMap(floorId, 'bg2', config);
if (config.onMap)
core.drawImage(
'bg2',
cacheCtx.canvas,
core.bigmap.v2 ? -32 : 0,
core.bigmap.v2 ? -32 : 0
);
config.ctx = toDrawCtx;
};
maps.prototype._drawFg_draw = function (
floorId,
toDrawCtx,
cacheCtx,
config
) {
config.ctx = cacheCtx;
// ------ 调整这两行的顺序来控制是先绘制贴图还是先绘制前景图块;后绘制的覆盖先绘制的。
core.maps._drawFloorImages(
floorId,
config.ctx,
'fg',
null,
null,
config.onMap
);
core.maps._drawBgFgMap(floorId, 'fg', config);
if (config.onMap) {
core.drawImage(
toDrawCtx,
cacheCtx.canvas,
core.bigmap.v2 ? -32 : 0,
core.bigmap.v2 ? -32 : 0
);
core.clearMap('fg2');
core.clearMap(cacheCtx);
}
core.maps._drawBgFgMap(floorId, 'fg2', config);
if (config.onMap)
core.drawImage(
'fg2',
cacheCtx.canvas,
core.bigmap.v2 ? -32 : 0,
core.bigmap.v2 ? -32 : 0
);
config.ctx = toDrawCtx;
};
////// 移动判定 //////
maps.prototype._generateMovableArray_arrays = function (floorId) {
return {
bgArray: this.getBgMapArray(floorId),
fgArray: this.getFgMapArray(floorId),
eventArray: this.getMapArray(floorId),
bg2Array: this._getBgFgMapArray('bg2', floorId),
fg2Array: this._getBgFgMapArray('fg2', floorId)
};
};
}
-58
View File
@@ -1,58 +0,0 @@
///<reference path="../../../../src/types/core.d.ts" />
export {};
['up', 'down', 'left', 'right'].forEach(one => {
// 指定中间帧动画
core.material.icons.hero[one].midFoot = 2;
});
var heroMoving = timestamp => {
if (core.status.heroMoving <= 0) return;
if (timestamp - core.animateFrame.moveTime > core.values.moveSpeed) {
core.animateFrame.leftLeg++;
core.animateFrame.moveTime = timestamp;
}
core.drawHero(
['stop', 'leftFoot', 'midFoot', 'rightFoot'][
core.animateFrame.leftLeg % 4
],
4 * core.status.heroMoving
);
};
core.registerAnimationFrame('heroMoving', true, heroMoving);
core.events._eventMoveHero_moving = function (step, moveSteps) {
var curr = moveSteps[0];
var direction = curr[0],
x = core.getHeroLoc('x'),
y = core.getHeroLoc('y');
// ------ 前进/后退
var o = direction == 'backward' ? -1 : 1;
if (direction == 'forward' || direction == 'backward')
direction = core.getHeroLoc('direction');
var faceDirection = direction;
if (direction == 'leftup' || direction == 'leftdown')
faceDirection = 'left';
if (direction == 'rightup' || direction == 'rightdown')
faceDirection = 'right';
core.setHeroLoc('direction', direction);
if (curr[1] <= 0) {
core.setHeroLoc('direction', faceDirection);
moveSteps.shift();
return true;
}
if (step <= 4) core.drawHero('stop', 4 * o * step);
else if (step <= 8) core.drawHero('leftFoot', 4 * o * step);
else if (step <= 12) core.drawHero('midFoot', 4 * o * (step - 8));
else if (step <= 16) core.drawHero('rightFoot', 4 * o * (step - 8)); // if (step == 8) {
if (step == 8 || step == 16) {
core.setHeroLoc('x', x + o * core.utils.scan2[direction].x, true);
core.setHeroLoc('y', y + o * core.utils.scan2[direction].y, true);
core.updateFollowers();
curr[1]--;
if (curr[1] <= 0) moveSteps.shift();
core.setHeroLoc('direction', faceDirection);
return step == 16;
}
return false;
};
+60
View File
@@ -0,0 +1,60 @@
// @ts-nocheck
var heroMoving = timestamp => {
if (core.status.heroMoving <= 0) return;
if (timestamp - core.animateFrame.moveTime > core.values.moveSpeed) {
core.animateFrame.leftLeg++;
core.animateFrame.moveTime = timestamp;
}
core.drawHero(
['stop', 'leftFoot', 'midFoot', 'rightFoot'][
core.animateFrame.leftLeg % 4
],
4 * core.status.heroMoving
);
};
export function init() {
['up', 'down', 'left', 'right'].forEach(one => {
// 指定中间帧动画
core.material.icons.hero[one].midFoot = 2;
});
core.registerAnimationFrame('heroMoving', true, heroMoving);
core.events._eventMoveHero_moving = function (step, moveSteps) {
var curr = moveSteps[0];
var direction = curr[0],
x = core.getHeroLoc('x'),
y = core.getHeroLoc('y');
// ------ 前进/后退
var o = direction == 'backward' ? -1 : 1;
if (direction == 'forward' || direction == 'backward')
direction = core.getHeroLoc('direction');
var faceDirection = direction;
if (direction == 'leftup' || direction == 'leftdown')
faceDirection = 'left';
if (direction == 'rightup' || direction == 'rightdown')
faceDirection = 'right';
core.setHeroLoc('direction', direction);
if (curr[1] <= 0) {
core.setHeroLoc('direction', faceDirection);
moveSteps.shift();
return true;
}
if (step <= 4) core.drawHero('stop', 4 * o * step);
else if (step <= 8) core.drawHero('leftFoot', 4 * o * step);
else if (step <= 12) core.drawHero('midFoot', 4 * o * (step - 8));
else if (step <= 16) core.drawHero('rightFoot', 4 * o * (step - 8)); // if (step == 8) {
if (step == 8 || step == 16) {
core.setHeroLoc('x', x + o * core.utils.scan2[direction].x, true);
core.setHeroLoc('y', y + o * core.utils.scan2[direction].y, true);
core.updateFollowers();
curr[1]--;
if (curr[1] <= 0) moveSteps.shift();
core.setHeroLoc('direction', faceDirection);
return step == 16;
}
return false;
};
}
+26 -24
View File
@@ -1,33 +1,35 @@
import { ensureFloorDamage } from '../enemy/damage';
core.control.updateDamage = function (floorId = core.status.floorId, ctx) {
if (!floorId || core.status.gameOver || main.mode !== 'play') return;
const onMap = ctx == null;
const floor = core.status.maps[floorId];
export function init() {
core.control.updateDamage = function (floorId = core.status.floorId, ctx) {
if (!floorId || core.status.gameOver || main.mode !== 'play') return;
const onMap = ctx == null;
const floor = core.status.maps[floorId];
// 没有怪物手册
// if (!core.hasItem('book')) return;
core.status.damage.posX = core.bigmap.posX;
core.status.damage.posY = core.bigmap.posY;
if (!onMap) {
const width = core.floors[floorId].width,
height = core.floors[floorId].height;
// 地图过大的缩略图不绘制显伤
if (width * height > core.bigmap.threshold) return;
}
// 计算伤害
ensureFloorDamage(floorId);
// 没有怪物手册
// if (!core.hasItem('book')) return;
core.status.damage.posX = core.bigmap.posX;
core.status.damage.posY = core.bigmap.posY;
if (!onMap) {
const width = core.floors[floorId].width,
height = core.floors[floorId].height;
// 地图过大的缩略图不绘制显伤
if (width * height > core.bigmap.threshold) return;
}
// 计算伤害
ensureFloorDamage(floorId);
floor.enemy.extract();
floor.enemy.calDamage(true);
floor.enemy.calMapDamage();
core.status.damage.data = [];
floor.enemy.extract();
floor.enemy.calDamage(true);
floor.enemy.calMapDamage();
core.status.damage.data = [];
floor.enemy.render(true);
floor.enemy.render(true);
getItemDetail(floorId, onMap); // 宝石血瓶详细信息
this.drawDamage(ctx, floorId);
};
getItemDetail(floorId, onMap); // 宝石血瓶详细信息
this.drawDamage(ctx, floorId);
};
}
// 获取宝石信息 并绘制
function getItemDetail(floorId: FloorIds, onMap: boolean) {
+54 -52
View File
@@ -1,59 +1,61 @@
import { getEnemy } from '../enemy/battle';
import { formatDamage } from '../utils';
core.maps._initDetachedBlock = function (
info: BlockInfo,
x: number,
y: number,
displayDamage: boolean = false
) {
let headCanvas = null,
bodyCanvas = '__body_' + x + '_' + y,
damageCanvas = null;
// head
if (!info.bigImage && info.height > 32) {
headCanvas = '__head_' + x + '_' + y;
core.createCanvas(headCanvas, 0, 0, 32, info.height - 32, 55);
}
// body
if (info.bigImage) {
var bigImageInfo = this._getBigImageInfo(
info.bigImage,
info.face,
info.posX
);
const { per_width, per_height } = bigImageInfo;
core.createCanvas(bodyCanvas, 0, 0, per_width, per_height, 35);
} else {
core.createCanvas(bodyCanvas, 0, 0, 32, 32, 35);
}
// 伤害
if (
info.cls.indexOf('enemy') == 0 &&
core.hasItem('book') &&
displayDamage
export function init() {
core.maps._initDetachedBlock = function (
info: BlockInfo,
x: number,
y: number,
displayDamage: boolean = false
) {
const enemy = getEnemy(x, y);
const dam = enemy.calDamage();
const { damage, color } = formatDamage(dam.damage);
let headCanvas = null,
bodyCanvas = '__body_' + x + '_' + y,
damageCanvas = null;
damageCanvas = '__damage_' + x + '_' + y;
const ctx = core.createCanvas(damageCanvas, 0, 0, 32, 32, 65);
ctx.textAlign = 'left';
ctx.font = '14px normal';
core.fillBoldText(ctx, damage, 1, 31, color as string);
if (core.flags.displayCritical) {
const critical = enemy.calCritical(1);
const atk = core.formatBigNumber(critical[0]?.delta, true);
const display = atk === '???' ? '?' : atk;
core.fillBoldText(ctx, display, 1, 21, '#FFFFFF');
// head
if (!info.bigImage && info.height > 32) {
headCanvas = '__head_' + x + '_' + y;
core.createCanvas(headCanvas, 0, 0, 32, info.height - 32, 55);
}
}
return {
headCanvas,
bodyCanvas,
damageCanvas
// body
if (info.bigImage) {
var bigImageInfo = this._getBigImageInfo(
info.bigImage,
info.face,
info.posX
);
const { per_width, per_height } = bigImageInfo;
core.createCanvas(bodyCanvas, 0, 0, per_width, per_height, 35);
} else {
core.createCanvas(bodyCanvas, 0, 0, 32, 32, 35);
}
// 伤害
if (
info.cls.indexOf('enemy') == 0 &&
core.hasItem('book') &&
displayDamage
) {
const enemy = getEnemy(x, y);
const dam = enemy.calDamage();
const { damage, color } = formatDamage(dam.damage);
damageCanvas = '__damage_' + x + '_' + y;
const ctx = core.createCanvas(damageCanvas, 0, 0, 32, 32, 65);
ctx.textAlign = 'left';
ctx.font = '14px normal';
core.fillBoldText(ctx, damage, 1, 31, color as string);
if (core.flags.displayCritical) {
const critical = enemy.calCritical(1);
const atk = core.formatBigNumber(critical[0]?.delta, true);
const display = atk === '???' ? '?' : atk;
core.fillBoldText(ctx, display, 1, 21, '#FFFFFF');
}
}
return {
headCanvas,
bodyCanvas,
damageCanvas
};
};
};
}
+3 -7
View File
@@ -54,6 +54,7 @@ function getRealStatus(
name: keyof HeroStatus | 'all' | (keyof HeroStatus)[],
floorId: FloorIds = core.status.floorId
): any {
const { getSkillLevel } = Mota.Plugin.require('skillTree_g');
if (name instanceof Array) {
return Object.fromEntries(
name.map(v => [v, getRealStatus(status, v, floorId)])
@@ -84,7 +85,7 @@ function getRealStatus(
// 技能
if (flags.bladeOn && flags.blade) {
const level = core.plugin.skillTree.getSkillLevel(2);
const level = getSkillLevel(2);
if (name === 'atk') {
s *= 1 + 0.1 * level;
}
@@ -93,7 +94,7 @@ function getRealStatus(
}
}
if (flags.shield && flags.shieldOn) {
const level = core.plugin.skillTree.getSkillLevel(10);
const level = getSkillLevel(10);
if (name === 'atk') {
s *= 1 - 0.1 * level;
}
@@ -119,8 +120,3 @@ declare global {
};
}
}
core.plugin.hero = {
getHeroStatusOf,
getHeroStatusOn
};
-41
View File
@@ -1,41 +0,0 @@
import './fiveLayer';
import './fx/heroFourFrames';
import './dev/hotReload';
import './fx/itemDetail';
import './enemy/checkblock';
import './replay';
import './ui';
import './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 './skills';
import * as skillTree from './skillTree';
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';
export {
halo,
hero,
loopMap,
remainEnemy,
removeMap,
shop,
skill,
skillTree,
study,
towerBoss,
utils,
chase,
damage,
battle,
special
};
+65
View File
@@ -0,0 +1,65 @@
/* @__PURE__ */ import './dev/hotReload'; // 仅开发会用到
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';
import * as skillTree from './skillTree';
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';
Mota.Plugin.register('utils_g', utils);
Mota.Plugin.register('loopMap_g', loopMap, loopMap.init);
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);
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);
// 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);
// export {
// halo,
// hero,
// loopMap,
// remainEnemy,
// removeMap,
// shop,
// skill,
// skillTree,
// study,
// towerBoss,
// utils,
// chase,
// damage,
// battle,
// special
// };
-230
View File
@@ -1,230 +0,0 @@
///<reference path="../../../src/types/core.d.ts" />
import { slide } from './utils';
const list = ['tower6'];
/**
* 设置循环地图的偏移量
* @param {number} offset 横向偏移量
* @param {FloorIds} floorId
*/
function setLoopMap(offset, floorId) {
const floor = core.status.maps[floorId];
if (offset < 9) {
moveMap(floor.width - 17, floorId);
}
if (offset > floor.width - 9) {
moveMap(17 - floor.width, floorId);
}
}
/**
* 当勇士移动时自动设置循环地图
* @param {FloorIds} floorId
*/
function autoSetLoopMap(floorId) {
setLoopMap(core.status.hero.loc.x, floorId);
}
export function checkLoopMap() {
if (isLoopMap(core.status.floorId)) {
autoSetLoopMap(core.status.floorId);
}
}
/**
* 移动地图
* @param {number} delta
* @param {FloorIds} floorId
*/
function moveMap(delta, floorId) {
core.extractBlocks(floorId);
const floor = core.status.maps[floorId];
core.setHeroLoc('x', core.status.hero.loc.x + delta);
flags[`loop_${floorId}`] += delta;
flags[`loop_${floorId}`] %= floor.width;
const origin = floor.blocks.slice();
for (let i = 0; i < origin.length; i++) {
core.removeBlockByIndex(0, floorId);
core.removeGlobalAnimate(origin[i].x, origin[i].y);
}
origin.forEach(v => {
let to = v.x + delta;
if (to >= floor.width) to -= floor.width;
if (to < 0) to += floor.width;
core.setBlock(v.id, to, v.y, floorId, true);
core.setMapBlockDisabled(floorId, to, v.y, false);
});
core.drawMap();
core.drawHero();
}
function isLoopMap(floorId) {
return list.includes(floorId);
}
events.prototype._sys_changeFloor = function (data, callback) {
data = data.event.data;
let heroLoc = {};
if (isLoopMap(data.floorId)) {
const floor = core.status.maps[data.floorId];
flags[`loop_${data.floorId}`] ??= 0;
let tx = data.loc[0] + flags[`loop_${data.floorId}`];
tx %= floor.width;
if (tx < 0) tx += floor.width;
heroLoc = {
x: tx,
y: data.loc[1]
};
} else if (data.loc) heroLoc = { x: data.loc[0], y: data.loc[1] };
if (data.direction) heroLoc.direction = data.direction;
if (core.status.event.id != 'action') core.status.event.id = null;
core.changeFloor(data.floorId, data.stair, heroLoc, data.time, function () {
core.replay();
if (callback) callback();
});
};
events.prototype.trigger = function (x, y, callback) {
var _executeCallback = function () {
// 因为trigger之后还有可能触发其他同步脚本(比如阻激夹域检测)
// 所以这里强制callback被异步触发
if (callback) {
setTimeout(callback, 1); // +1是为了录像检测系统
}
return;
};
if (core.status.gameOver) return _executeCallback();
if (core.status.event.id == 'action') {
core.insertAction(
{
type: 'function',
function:
'function () { core.events._trigger_inAction(' +
x +
',' +
y +
'); }',
async: true
},
null,
null,
null,
true
);
return _executeCallback();
}
if (core.status.event.id) return _executeCallback();
let block = core.getBlock(x, y);
const id = core.status.floorId;
const loop = isLoopMap(id);
if (loop && flags[`loop_${id}`] !== 0) {
if (block && block.event.trigger === 'changeFloor') {
delete block.event.trigger;
core.maps._addInfo(block);
} else {
const floor = core.status.maps[id];
let tx = x - flags[`loop_${id}`];
tx %= floor.width;
if (tx < 0) tx += floor.width;
const c = core.floors[id].changeFloor[`${tx},${y}`];
if (c) {
const b = { event: {}, x: tx, y };
b.event.data = c;
b.event.trigger = 'changeFloor';
block = b;
}
}
}
if (block == null) return _executeCallback();
// 执行该点的脚本
if (block.event.script) {
core.clearRouteFolding();
try {
eval(block.event.script);
} catch (ee) {
console.error(ee);
}
}
// 碰触事件
if (block.event.event) {
core.clearRouteFolding();
core.insertAction(block.event.event, block.x, block.y);
// 不再执行该点的系统事件
return _executeCallback();
}
if (block.event.trigger && block.event.trigger != 'null') {
var noPass = block.event.noPass,
trigger = block.event.trigger;
if (noPass) core.clearAutomaticRouteNode(x, y);
// 转换楼层能否穿透
if (
trigger == 'changeFloor' &&
!noPass &&
this._trigger_ignoreChangeFloor(block) &&
!loop
)
return _executeCallback();
core.status.automaticRoute.moveDirectly = false;
this.doSystemEvent(trigger, block);
}
return _executeCallback();
};
maps.prototype._getBgFgMapArray = function (name, floorId, noCache) {
floorId = floorId || core.status.floorId;
if (!floorId) return [];
var width = core.floors[floorId].width;
var height = core.floors[floorId].height;
if (!noCache && core.status[name + 'maps'][floorId])
return core.status[name + 'maps'][floorId];
var arr =
main.mode == 'editor' &&
!(window.editor && editor.uievent && editor.uievent.isOpen)
? core.cloneArray(editor[name + 'map'])
: null;
if (arr == null)
arr = core.cloneArray(core.floors[floorId][name + 'map'] || []);
if (isLoopMap(floorId) && window.flags) {
flags[`loop_${floorId}`] ??= 0;
arr.forEach(v => {
slide(v, flags[`loop_${floorId}`] % width);
});
}
for (var y = 0; y < height; ++y) {
if (arr[y] == null) arr[y] = Array(width).fill(0);
}
(core.getFlag('__' + name + 'v__', {})[floorId] || []).forEach(function (
one
) {
arr[one[1]][one[0]] = one[2] || 0;
});
(core.getFlag('__' + name + 'd__', {})[floorId] || []).forEach(function (
one
) {
arr[one[1]][one[0]] = 0;
});
if (main.mode == 'editor') {
for (var x = 0; x < width; x++) {
for (var y = 0; y < height; y++) {
arr[y][x] = arr[y][x].idnum || arr[y][x] || 0;
}
}
}
if (core.status[name + 'maps']) core.status[name + 'maps'][floorId] = arr;
return arr;
};
core.plugin.loopMap = {
checkLoopMap
};
+257
View File
@@ -0,0 +1,257 @@
///<reference path="../../../src/types/core.d.ts" />
import { slide } from './utils';
const list = ['tower6'];
/**
* 设置循环地图的偏移量
* @param offset 横向偏移量
*/
function setLoopMap(offset: number, floorId: FloorIds) {
const floor = core.status.maps[floorId];
if (offset < 9) {
moveMap(floor.width - 17, floorId);
}
if (offset > floor.width - 9) {
moveMap(17 - floor.width, floorId);
}
}
/**
* 当勇士移动时自动设置循环地图
*/
function autoSetLoopMap(floorId: FloorIds) {
setLoopMap(core.status.hero.loc.x, floorId);
}
export function checkLoopMap() {
if (isLoopMap(core.status.floorId)) {
autoSetLoopMap(core.status.floorId);
}
}
/**
* 移动地图
*/
function moveMap(delta: number, floorId: FloorIds) {
core.extractBlocks(floorId);
const floor = core.status.maps[floorId];
core.setHeroLoc('x', core.status.hero.loc.x + delta);
flags[`loop_${floorId}`] += delta;
flags[`loop_${floorId}`] %= floor.width;
const origin = floor.blocks.slice();
for (let i = 0; i < origin.length; i++) {
core.removeBlockByIndex(0, floorId);
core.removeGlobalAnimate(origin[i].x, origin[i].y);
}
origin.forEach(v => {
let to = v.x + delta;
if (to >= floor.width) to -= floor.width;
if (to < 0) to += floor.width;
core.setBlock(v.id, to, v.y, floorId, true);
core.setMapBlockDisabled(floorId, to, v.y, false);
});
core.drawMap();
core.drawHero();
}
function isLoopMap(floorId: FloorIds) {
return list.includes(floorId);
}
export function init() {
events.prototype._sys_changeFloor = function (
data: any,
callback: () => void
) {
data = data.event.data;
let heroLoc: Partial<DiredLoc> = {};
if (isLoopMap(data.floorId)) {
const floor = core.status.maps[data.floorId as FloorIds] as Floor;
flags[`loop_${data.floorId}`] ??= 0;
let tx = data.loc[0] + flags[`loop_${data.floorId}`];
tx %= floor.width;
if (tx < 0) tx += floor.width;
heroLoc = {
x: tx,
y: data.loc[1]
};
} else if (data.loc) heroLoc = { x: data.loc[0], y: data.loc[1] };
if (data.direction) heroLoc.direction = data.direction;
// @ts-ignore
if (core.status.event.id != 'action') core.status.event.id = null;
core.changeFloor(
data.floorId,
data.stair,
heroLoc,
data.time,
function () {
core.replay();
if (callback) callback();
}
);
};
events.prototype.trigger = function (
x: number,
y: number,
callback: () => void
) {
var _executeCallback = function () {
// 因为trigger之后还有可能触发其他同步脚本(比如阻激夹域检测)
// 所以这里强制callback被异步触发
if (callback) {
setTimeout(callback, 1); // +1是为了录像检测系统
}
return;
};
if (core.status.gameOver) return _executeCallback();
if (core.status.event.id == 'action') {
core.insertAction(
{
type: 'function',
function:
'function () { core.events._trigger_inAction(' +
x +
',' +
y +
'); }',
async: true
},
void 0,
void 0,
void 0,
true
);
return _executeCallback();
}
if (core.status.event.id) return _executeCallback();
let block = core.getBlock(x, y);
const id = core.status.floorId;
const loop = isLoopMap(id);
if (loop && flags[`loop_${id}`] !== 0) {
if (block && block.event.trigger === 'changeFloor') {
delete block.event.trigger;
// @ts-ignore
core.maps._addInfo(block);
} else {
const floor = core.status.maps[id];
let tx = x - flags[`loop_${id}`];
tx %= floor.width;
if (tx < 0) tx += floor.width;
const c = core.floors[id].changeFloor[`${tx},${y}`];
if (c) {
const b: DeepPartial<Block> = { event: {}, x: tx, y };
b.event!.data = c;
b.event!.trigger = 'changeFloor';
block = b as Block;
}
}
}
if (block == null) return _executeCallback();
// 执行该点的脚本
if (block.event.script) {
core.clearRouteFolding();
try {
eval(block.event.script);
} catch (ee) {
console.error(ee);
}
}
// 碰触事件
if (block.event.event) {
core.clearRouteFolding();
core.insertAction(block.event.event, block.x, block.y);
// 不再执行该点的系统事件
return _executeCallback();
}
if (block.event.trigger && block.event.trigger !== 'null') {
var noPass = block.event.noPass,
trigger = block.event.trigger;
if (noPass) core.clearAutomaticRouteNode(x, y);
// 转换楼层能否穿透
if (
trigger == 'changeFloor' &&
!noPass &&
this._trigger_ignoreChangeFloor(block) &&
!loop
)
return _executeCallback();
// @ts-ignore
core.status.automaticRoute.moveDirectly = false;
this.doSystemEvent(trigger, block);
}
return _executeCallback();
};
maps.prototype._getBgFgMapArray = function (
name: string,
floorId: FloorIds,
noCache: boolean
) {
floorId = floorId || core.status.floorId;
if (!floorId) return [];
var width = core.floors[floorId].width;
var height = core.floors[floorId].height;
// @ts-ignore
if (!noCache && core.status[name + 'maps'][floorId])
// @ts-ignore
return core.status[name + 'maps'][floorId];
var arr: number[][] =
main.mode == 'editor' &&
// @ts-ignore
!(window.editor && editor.uievent && editor.uievent.isOpen)
? // @ts-ignore
core.cloneArray(editor[name + 'map'])
: null;
if (arr == null)
// @ts-ignore
arr = core.cloneArray(core.floors[floorId][name + 'map'] || []);
if (isLoopMap(floorId) && window.flags) {
flags[`loop_${floorId}`] ??= 0;
arr.forEach(v => {
slide(v, flags[`loop_${floorId}`] % width);
});
}
for (var y = 0; y < height; ++y) {
if (arr[y] == null) arr[y] = Array(width).fill(0);
}
// @ts-ignore
(core.getFlag('__' + name + 'v__', {})[floorId] || []).forEach(
// @ts-ignore
function (one) {
arr[one[1]][one[0]] = one[2] || 0;
}
);
// @ts-ignore
(core.getFlag('__' + name + 'd__', {})[floorId] || []).forEach(
// @ts-ignore
function (one) {
arr[one[1]][one[0]] = 0;
}
);
if (main.mode == 'editor') {
for (var x = 0; x < width; x++) {
for (var y = 0; y < height; y++) {
// @ts-ignore
arr[y][x] = arr[y][x].idnum || arr[y][x] || 0;
}
}
}
// @ts-ignore
if (core.status[name + 'maps'])
// @ts-ignore
core.status[name + 'maps'][floorId] = arr;
return arr;
};
}
+6 -4
View File
@@ -1,3 +1,5 @@
import { has } from './utils';
type RangeScanFn<C extends Partial<Loc>> = (
collection: Range<C>,
data: any
@@ -77,8 +79,8 @@ Range.registerRangeType(
return list.filter(v => {
return (
core.has(v.x) &&
core.has(v.y) &&
has(v.x) &&
has(v.y) &&
Math.abs(v.x - x) <= r &&
Math.abs(v.y - y) <= r
);
@@ -87,8 +89,8 @@ Range.registerRangeType(
(col, { x, y, d }, item) => {
const r = Math.floor(d / 2);
return (
core.has(item.x) &&
core.has(item.y) &&
has(item.x) &&
has(item.y) &&
Math.abs(item.x - x) <= r &&
Math.abs(item.y - y) <= r
);
@@ -1,14 +1,20 @@
///<reference path="../../../src/types/core.d.ts" />
export function removeMaps(fromId, toId, force) {
export function removeMaps(
fromId: FloorIds,
toId: FloorIds,
force: boolean = false
) {
toId = toId || fromId;
var fromIndex = core.floorIds.indexOf(fromId),
toIndex = core.floorIds.indexOf(toId);
if (toIndex < 0) toIndex = core.floorIds.length - 1;
flags.__visited__ = flags.__visited__ || {};
flags.__removed__ = flags.__removed__ || [];
flags.__disabled__ = flags.__disabled__ || {};
flags.__leaveLoc__ = flags.__leaveLoc__ || {};
// @ts-ignore
flags.__visited__ ??= {};
flags.__removed__ ??= [];
flags.__disabled__ ??= {};
// @ts-ignore
flags.__leaveLoc__ ??= {};
flags.__forceDelete__ ??= {};
let deleted = false;
for (var i = fromIndex; i <= toIndex; ++i) {
@@ -36,11 +42,11 @@ export function removeMaps(fromId, toId, force) {
deleted = true;
}
if (deleted && !main.replayChecking) {
Mota.Plugin.require('fly').splitArea();
Mota.Plugin.require('fly_r').splitArea();
}
}
export function deleteFlags(floorId) {
export function deleteFlags(floorId: FloorIds) {
delete flags[`jump_${floorId}`];
delete flags[`inte_${floorId}`];
delete flags[`loop_${floorId}`];
@@ -51,7 +57,7 @@ export function deleteFlags(floorId) {
// 恢复楼层
// core.plugin.removeMap.resumeMaps("MT1", "MT300") 恢复MT1~MT300之间的全部层
// core.plugin.removeMap.resumeMaps("MT10") 只恢复MT10层
export function resumeMaps(fromId, toId) {
export function resumeMaps(fromId: FloorIds, toId: FloorIds) {
toId = toId || fromId;
var fromIndex = core.floorIds.indexOf(fromId),
toIndex = core.floorIds.indexOf(toId);
@@ -65,19 +71,21 @@ export function resumeMaps(fromId, toId) {
flags.__forceDelete__[floorId]
)
continue;
// @ts-ignore
flags.__removed__ = flags.__removed__.filter(f => {
return f != floorId;
});
// @ts-ignore
core.status.maps[floorId] = core.loadFloor(floorId);
}
}
// 分区砍层相关
var inAnyPartition = floorId => {
var inAnyPartition = (floorId: FloorIds) => {
var inPartition = false;
(core.floorPartitions || []).forEach(floor => {
var fromIndex = core.floorIds.indexOf(floor[0]);
var toIndex = core.floorIds.indexOf(floor[1]);
var toIndex = core.floorIds.indexOf(floor[1]!);
var index = core.floorIds.indexOf(floorId);
if (fromIndex < 0 || index < 0) return;
if (toIndex < 0) toIndex = core.floorIds.length - 1;
@@ -87,29 +95,19 @@ var inAnyPartition = floorId => {
};
// 分区砍层
export function autoRemoveMaps(floorId) {
export function autoRemoveMaps(floorId: FloorIds) {
if (main.mode != 'play' || !inAnyPartition(floorId)) return;
// 根据分区信息自动砍层与恢复
(core.floorPartitions || []).forEach(floor => {
var fromIndex = core.floorIds.indexOf(floor[0]);
var toIndex = core.floorIds.indexOf(floor[1]);
var toIndex = core.floorIds.indexOf(floor[1]!);
var index = core.floorIds.indexOf(floorId);
if (fromIndex < 0 || index < 0) return;
if (toIndex < 0) toIndex = core.floorIds.length - 1;
if (index >= fromIndex && index <= toIndex) {
core.plugin.removeMap.resumeMaps(
core.floorIds[fromIndex],
core.floorIds[toIndex]
);
resumeMaps(core.floorIds[fromIndex], core.floorIds[toIndex]);
} else {
removeMaps(core.floorIds[fromIndex], core.floorIds[toIndex]);
}
});
}
core.plugin.removeMap = {
removeMaps,
deleteFlags,
resumeMaps,
autoRemoveMaps
};
-122
View File
@@ -1,122 +0,0 @@
///<reference path="../../../src/types/core.d.ts" />
import { studySkill, canStudySkill } from './study';
const replayableSettings = ['autoSkill'];
let cliping = false;
let startIndex = 0;
export function ready() {}
export function readyClip() {
cliping = true;
return (startIndex = core.status.route.length - 1);
}
export function clip(...replace) {
if (!cliping) return;
cliping = false;
core.status.route.splice(startIndex);
core.status.route.push(...replace);
}
// 注册修改设置的录像操作
core.registerReplayAction('settings', name => {
if (!name.startsWith('set:')) return false;
const [, setting, value] = name.split(':');
const v = eval(value);
if (typeof v !== 'boolean') return false;
if (!replayableSettings.includes(setting)) return false;
flags[setting] = v;
core.status.route.push(name);
core.replay();
return true;
});
core.registerReplayAction('upgradeSkill', name => {
if (!name.startsWith('skill:')) return false;
const skill = parseInt(name.slice(6));
core.plugin.skillTree.upgradeSkill(skill);
core.status.route.push(name);
core.replay();
return true;
});
core.registerReplayAction('study', name => {
// todo
// if (!name.startsWith('study:')) return false;
// const [num, x, y] = name
// .slice(6)
// .split(',')
// .map(v => parseInt(v));
// if (!canStudySkill(num)) return false;
// const id = core.getBlockId(x, y);
// const enemy = core.getEnemyInfo(id, void 0, x, y);
// if (!enemy.special.includes(num)) return false;
// studySkill(enemy, num);
// core.status.route.push(name);
// core.replay();
// return true;
});
// 商店
let shopOpened = false;
let openedShopId = '';
core.registerReplayAction('openShop', name => {
if (!name.startsWith('openShop:')) return false;
if (shopOpened) return false;
openedShopId = name.slice(9);
shopOpened = true;
core.status.route.push(name);
core.replay();
return true;
});
core.registerReplayAction('buy', name => {
if (!name.startsWith('buy:') && !name.startsWith('sell:')) return false;
if (!shopOpened) return false;
if (!openedShopId) return false;
const [type, id, num] = name
.split(':')
.map(v => (/^\d+$/.test(v) ? parseInt(v) : v));
const shop = core.status.shops[openedShopId];
const item = shop.choices.find(v => v.id === id);
if (!item) return false;
flags.itemShop ??= {};
flags.itemShop[openedShopId] ??= {};
flags.itemShop[openedShopId][id] ??= 0;
if (num > item.number - flags.itemShop[openedShopId][id]) {
return false;
}
let cost = 0;
if (type === 'buy') {
cost = item.money * num;
} else {
cost = -item.sell * num;
}
if (cost > core.status.hero.money) return false;
core.status.hero.money -= cost;
flags.itemShop[openedShopId][id] += type === 'buy' ? num : -num;
core.addItem(id, type === 'buy' ? num : -num);
core.status.route.push(name);
core.replay();
return true;
});
core.registerReplayAction('closeShop', name => {
if (name !== 'closeShop') return false;
if (!shopOpened) return false;
shopOpened = false;
openedShopId = '';
core.status.route.push(name);
core.replay();
return true;
});
core.plugin.replay = {
ready,
readyClip,
clip
};
+121
View File
@@ -0,0 +1,121 @@
///<reference path="../../../src/types/core.d.ts" />
import { upgradeSkill } from './skillTree';
const replayableSettings = ['autoSkill'];
let cliping = false;
let startIndex = 0;
export function ready() {}
export function readyClip() {
cliping = true;
return (startIndex = core.status.route.length - 1);
}
export function clip(...replace: string[]) {
if (!cliping) return;
cliping = false;
core.status.route.splice(startIndex);
core.status.route.push(...replace);
}
export function init() {
// 注册修改设置的录像操作
core.registerReplayAction('settings', name => {
if (!name.startsWith('set:')) return false;
const [, setting, value] = name.split(':');
const v = eval(value);
if (typeof v !== 'boolean') return false;
if (!replayableSettings.includes(setting)) return false;
flags[setting] = v;
core.status.route.push(name);
core.replay();
return true;
});
core.registerReplayAction('upgradeSkill', name => {
if (!name.startsWith('skill:')) return false;
const skill = parseInt(name.slice(6));
upgradeSkill(skill);
core.status.route.push(name);
core.replay();
return true;
});
core.registerReplayAction('study', name => {
// todo
// if (!name.startsWith('study:')) return false;
// const [num, x, y] = name
// .slice(6)
// .split(',')
// .map(v => parseInt(v));
// if (!canStudySkill(num)) return false;
// const id = core.getBlockId(x, y);
// const enemy = core.getEnemyInfo(id, void 0, x, y);
// if (!enemy.special.includes(num)) return false;
// studySkill(enemy, num);
// core.status.route.push(name);
// core.replay();
return true;
});
// 商店
let shopOpened = false;
let openedShopId = '';
core.registerReplayAction('openShop', name => {
if (!name.startsWith('openShop:')) return false;
if (shopOpened) return false;
openedShopId = name.slice(9);
shopOpened = true;
core.status.route.push(name);
core.replay();
return true;
});
core.registerReplayAction('buy', name => {
if (!name.startsWith('buy:') && !name.startsWith('sell:')) return false;
if (!shopOpened) return false;
if (!openedShopId) return false;
const [type, id, num] = name
.split(':')
.map(v => (/^\d+$/.test(v) ? parseInt(v) : v));
const shop = core.status.shops[openedShopId] as ItemShopEvent;
const item = shop.choices.find(v => v.id === id);
if (!item) return false;
flags.itemShop ??= {};
flags.itemShop[openedShopId] ??= {};
flags.itemShop[openedShopId][id] ??= 0;
if ((num as number) > item.number - flags.itemShop[openedShopId][id]) {
return false;
}
let cost = 0;
if (type === 'buy') {
cost = parseInt(item.money) * (num as number);
} else {
cost = -item.sell * (num as number);
}
if (cost > core.status.hero.money) return false;
core.status.hero.money -= cost;
flags.itemShop[openedShopId][id] += type === 'buy' ? num : -num;
core.addItem(
id as AllIdsOf<'items'>,
(type === 'buy' ? num : -num) as number
);
core.status.route.push(name);
core.replay();
return true;
});
core.registerReplayAction('closeShop', name => {
if (name !== 'closeShop') return false;
if (!shopOpened) return false;
shopOpened = false;
openedShopId = '';
core.status.route.push(name);
core.replay();
return true;
});
}
@@ -1,27 +1,29 @@
///<reference path="../../../src/types/core.d.ts" />
const { openItemShop } = core.plugin.gameUi;
export function openShop(shopId, noRoute) {
var shop = core.status.shops[shopId];
export function openShop(shopId: string, noRoute: boolean) {
const shop = core.status.shops[shopId] as ItemShopEvent;
// Step 1: 检查能否打开此商店
if (!this.canOpenShop(shopId)) {
if (!canOpenShop(shopId)) {
core.drawTip('该商店尚未开启');
return false;
}
// Step 3: 检查道具商店 or 公共事件
if (shop.item) {
if (openItemShop) openItemShop(shopId);
Mota.r(() => {
if (!core.isReplaying()) {
Mota.require('var', 'mainUi').open('shop', {
shopId: shopId
});
}
});
return;
}
return true;
}
/// 是否访问过某个快捷商店
export function isShopVisited(id) {
export function isShopVisited(id: string) {
flags.__shops__ ??= {};
var shops = core.getFlag('__shops__');
var shops = core.getFlag<any>('__shops__');
if (!shops[id]) shops[id] = {};
return shops[id].visited;
}
@@ -29,25 +31,24 @@ export function isShopVisited(id) {
/// 当前应当显示的快捷商店列表
export function listShopIds() {
return Object.keys(core.status.shops).filter(id => {
return (
core.plugin.shop.isShopVisited(id) ||
!core.status.shops[id].mustEnable
);
// @ts-ignore
return isShopVisited(id) || !core.status.shops[id].mustEnable;
});
}
/// 是否能够打开某个商店
export function canOpenShop(id) {
if (this.isShopVisited(id)) return true;
export function canOpenShop(id: string) {
if (isShopVisited(id)) return true;
var shop = core.status.shops[id];
// @ts-ignore
if (shop.item || shop.commonEvent || shop.mustEnable) return false;
return true;
}
/// 启用或禁用某个快捷商店
export function setShopVisited(id, visited) {
export function setShopVisited(id: string, visited: boolean) {
if (!core.hasFlag('__shops__')) core.setFlag('__shops__', {});
var shops = core.getFlag('__shops__');
var shops = core.getFlag<any>('__shops__');
if (!shops[id]) shops[id] = {};
if (visited) shops[id].visited = true;
else delete shops[id].visited;
@@ -63,12 +64,3 @@ export function canUseQuickShop() {
return '当前楼层不能使用快捷商店。';
return null;
}
core.plugin.shop = {
openShop,
isShopVisited,
listShopIds,
canOpenShop,
setShopVisited,
canUseQuickShop
};
@@ -1,4 +1,4 @@
///<reference path="../../../src/types/core.d.ts" />
// @ts-nocheck
// 所有的主动技能效果
var ignoreInJump = {
@@ -19,8 +19,7 @@ var ignoreInJump = {
]
};
/** @type {FloorIds[]} */
export const jumpIgnoreFloor = [
export const jumpIgnoreFloor: FloorIds[] = [
'MT31',
'snowTown',
'MT36',
@@ -178,8 +177,3 @@ export function jumpSkill() {
return { x: x, y: y };
}
}
core.plugin.skillEffects = {
jumpSkill,
jumpIgnoreFloor
};
@@ -1,14 +1,11 @@
///<reference path="../../../src/types/core.d.ts" />
/**
* @type {number[]}
*/
let levels = [];
let levels: number[] = [];
/**
* @type {Record<Chapter, Skill[]>}
*/
const skills = {
export const skills: Record<Chapter, Skill[]> = {
chapter1: [
{
index: 0,
@@ -195,13 +192,11 @@ const skills = {
]
};
core.plugin.skills = skills;
export function resetSkillLevel() {
levels = [];
}
export function getSkillFromIndex(index) {
export function getSkillFromIndex(index: number) {
for (const [, skill] of Object.entries(skills)) {
const s = skill.find(v => v.index === index);
if (s) return s;
@@ -212,16 +207,21 @@ export function getSkillFromIndex(index) {
* 获取技能等级
* @param {number} skill
*/
export function getSkillLevel(skill) {
export function getSkillLevel(skill: number) {
return (levels[skill] ??= 0);
}
export function getSkillConsume(skill) {
export function getSkillConsume(skill: number) {
return eval(
getSkillFromIndex(skill).consume.replace(/level(:\d+)?/g, (str, $1) => {
if ($1) return `core.plugin.skillTree.getSkillLevel(${$1})`;
else return `core.plugin.skillTree.getSkillLevel(${skill})`;
})
getSkillFromIndex(skill)?.consume.replace(
/level(:\d+)?/g,
(str, $1) => {
if ($1)
return `Mota.Plugin.require('skillTree_g').getSkillLevel(${$1})`;
else
return `Mota.Plugin.require('skillTree_g').getSkillLevel(${skill})`;
}
) ?? ''
);
}
@@ -232,17 +232,16 @@ export function openTree() {
/**
* 能否升级某个技能
* @param {number} skill
*/
export function canUpgrade(skill) {
const consume = core.plugin.skillTree.getSkillConsume(skill);
export function canUpgrade(skill: number) {
const consume = getSkillConsume(skill);
if (consume > core.status.hero.mdef) return false;
const level = core.plugin.skillTree.getSkillLevel(skill);
const level = getSkillLevel(skill);
const s = getSkillFromIndex(skill);
if (level === s.max) return false;
const front = s.front;
if (level === s?.max) return false;
const front = s?.front ?? [];
for (const [skill, level] of front) {
if (core.plugin.skillTree.getSkillLevel(skill) < level) return false;
if (getSkillLevel(skill) < level) return false;
}
return true;
}
@@ -251,7 +250,7 @@ export function canUpgrade(skill) {
* 实际升级效果
* @param {number} skill
*/
export function upgradeSkill(skill) {
export function upgradeSkill(skill: number) {
if (!canUpgrade(skill)) return false;
switch (skill) {
case 0: // 力量 +2攻击
@@ -302,17 +301,6 @@ export function saveSkillTree() {
return levels.slice();
}
export function loadSkillTree(data) {
export function loadSkillTree(data: number[]) {
levels = data ?? [];
}
core.plugin.skillTree = {
getSkillConsume,
getSkillFromIndex,
getSkillLevel,
saveSkillTree,
loadSkillTree,
upgradeSkill,
openTree,
resetSkillLevel
};
+2 -1
View File
@@ -12,7 +12,8 @@ const cannotStudy = [9, 12, 14, 15, 24];
export function canStudySkill(number: number) {
const s = (core.status.hero.special ??= { num: [], last: [] });
if (core.plugin.skillTree.getSkillLevel(11) === 0) return false;
if (Mota.Plugin.require('skillTree_g').getSkillLevel(11) === 0)
return false;
if (s.num.length >= 1) return false;
if (s.num.includes(number)) return false;
if (cannotStudy.includes(number)) return false;
@@ -1,4 +1,4 @@
///<reference path="../../../src/types/core.d.ts" />
// @ts-nocheck
import { clip } from './replay.js';
// 1000多行,改不动了,原来什么样就什么样吧
@@ -11,7 +11,7 @@ var stage = 1,
boomLocs = [], // 随机轰炸
heroHp;
// 初始化
function initTowerBoss() {
export function initTowerBoss() {
stage = 1;
hp = 10000;
seconds = 0;
@@ -496,7 +496,7 @@ function intelligentArrow(fromSelf) {
damaged[loc + direction] = true;
core.drawHeroAnimate('hand');
core.status.hero.hp -= 1000;
Mota.Plugin.require('pop').addPop(
Mota.Plugin.require('pop_r').addPop(
x * 32 + 16,
y * 32 + 16,
-1000
@@ -515,7 +515,7 @@ function intelligentArrow(fromSelf) {
damaged[loc + direction] = true;
core.drawHeroAnimate('hand');
core.status.hero.hp -= 1000;
Mota.Plugin.require('pop').addPop(
Mota.Plugin.require('pop_r').addPop(
x * 32 + 16,
y * 32 + 16,
-1000
@@ -645,7 +645,7 @@ function icyMomentem() {
if (x == locs[index][0] && y == locs[index][1]) {
core.drawHeroAnimate('hand');
core.status.hero.hp -= 5000;
Mota.Plugin.require('pop').addPop(
Mota.Plugin.require('pop_r').addPop(
x * 32 + 16,
y * 32 + 16,
-5000
@@ -824,7 +824,7 @@ function getThunderDamage(x, y, power) {
hy = core.status.hero.loc.y;
if (Math.abs(hx - x) <= 1 && Math.abs(hy - y) <= 1) {
core.status.hero.hp -= 3000 * power;
Mota.Plugin.require('pop').addPop(
Mota.Plugin.require('pop_r').addPop(
x * 32 + 16,
y * 32 + 16,
-3000 * power
@@ -992,7 +992,7 @@ function ballThunder() {
) {
damaged[i] = true;
core.status.hero.hp -= 3000;
Mota.Plugin.require('pop').addPop(
Mota.Plugin.require('pop_r').addPop(
x * 32 + 16,
y * 32 + 16,
-3000
@@ -1502,7 +1502,7 @@ function boomingAnimate() {
hy = core.status.hero.loc.y;
if (loc[0] == hx && loc[1] == hy) {
core.status.hero.hp -= 3000;
Mota.Plugin.require('pop').addPop(
Mota.Plugin.require('pop_r').addPop(
x * 32 + 16,
y * 32 + 16,
-3000
@@ -1545,7 +1545,7 @@ function lineDamage(x1, y1, x2, y2, damage) {
n2 = ((y2 - y1) / (x2 - x1)) * (loc2[0] - x1) + y1 - loc2[1];
if (n1 * n2 <= 0) {
core.status.hero.hp -= damage;
Mota.Plugin.require('pop').addPop(
Mota.Plugin.require('pop_r').addPop(
x * 32 + 16,
y * 32 + 16,
-damage
@@ -1569,7 +1569,7 @@ function lineDamage(x1, y1, x2, y2, damage) {
n2 = ((y2 - y1) / (x2 - x1)) * (loc2[0] - x1) + y1 - loc2[1];
if (n1 * n2 <= 0) {
core.status.hero.hp -= damage;
Mota.Plugin.require('pop').addPop(
Mota.Plugin.require('pop_r').addPop(
x * 32 + 16,
y * 32 + 16,
-damage
@@ -1587,9 +1587,3 @@ function lineDamage(x1, y1, x2, y2, damage) {
}
}
}
core.plugin.towerBoss = {
initTowerBoss
};
export {};
@@ -1,29 +1,8 @@
///<reference path="../../../src/types/core.d.ts" />
export {};
// @ts-nocheck
(function () {
export function init() {
const { mainUi, fixedUi } = Mota.requireAll('var');
if (main.replayChecking)
return (core.plugin.gameUi = {
openItemShop: () => 0,
openSkill: () => 0
});
function openItemShop(itemShopId) {
if (!core.isReplaying()) {
Mota.require('var', 'mainUi').open('shop', {
shopId: itemShopId
});
}
}
function updateVueStatusBar() {
if (main.replayChecking) return;
const status = Mota.Plugin.require('status');
status.statusBarStatus.value = !status.statusBarStatus.value;
}
ui.prototype.drawBook = function () {
if (!core.isReplaying()) return mainUi.open('book');
};
@@ -86,14 +65,11 @@ export {};
core.dom.toolBar.style.display = 'none';
}
};
}
function openSkill() {
if (core.isReplaying()) return;
mainUi.open('skill');
}
core.plugin.gameUi = {
openItemShop,
openSkill
};
})();
function updateVueStatusBar() {
Mota.r(() => {
const status = Mota.require('var', 'status');
status.value = !status.value;
});
}
-15
View File
@@ -169,18 +169,3 @@ export function findDir(from: Loc, to: Loc): Dir2 | 'none' {
})?.[0] as Dir2) ?? 'none'
);
}
declare global {
interface GamePluginUtils {
ofDir: typeof ofDir;
}
}
core.plugin.utils = {
slide,
backDir,
has,
maxGameScale,
ofDir
};
core.has = has;
+5 -4
View File
@@ -35,8 +35,8 @@ const marked: MarkInfo<EnemyIds>[] = [];
*/
export function markEnemy(id: EnemyIds) {
if (hasMarkedEnemy(id)) return;
const { Enemy } = core.plugin.damage;
const enemy = new Enemy(core.material.enemys[id]);
const { DamageEnemy } = Mota.Plugin.require('damage_g');
const enemy = new DamageEnemy(core.material.enemys[id]);
enemy.calAttribute();
enemy.getRealInfo();
@@ -44,7 +44,7 @@ export function markEnemy(id: EnemyIds) {
id,
enemy,
mode: 0b011111,
lastAtk: core.plugin.hero.getHeroStatusOn('atk', 'empty'),
lastAtk: Mota.Plugin.require('hero_g').getHeroStatusOn('atk', 'empty'),
lastDamage: enemy.calDamage().damage,
status: 0b0,
update: ref(true)
@@ -66,9 +66,10 @@ export function unmarkEnemy(id: EnemyIds) {
}
export function checkMarkedEnemy() {
const { getHeroStatusOn } = Mota.Plugin.require('hero_g');
marked.forEach(v => {
const { id, enemy, mode, lastAtk, lastDamage, markDamage } = v;
const atk = core.plugin.hero.getHeroStatusOn('atk', 'empty');
const atk = getHeroStatusOn('atk', 'empty');
let tip = 0;
if (mode & 0b11110) {
const damage = enemy.calDamage().damage;
+1 -16
View File
@@ -36,26 +36,11 @@ export interface Light {
_offset?: Loc;
}
export default function init() {
export function init() {
core.registerAnimationFrame('shadow', true, () => {
if (!needRefresh) return;
drawShadow();
});
return {
initShadowCanvas,
drawShadow,
addLight,
removeLight,
setLight,
setShadowNodes,
setBackground,
animateLight,
transitionLight,
moveLightAs,
getAllLights,
refreshLight
};
}
let canvas: HTMLCanvasElement;
+2 -1
View File
@@ -59,13 +59,14 @@ export function getNowStatus(nowEquip?: Equip, onCol: boolean = false) {
'hpmax',
'money'
] as (keyof SelectType<HeroStatus, number>)[];
const { getHeroStatusOn } = Mota.Plugin.require('hero_g');
return (
<div id="hero-status">
{toShow.map(v => {
let status: string;
if (v === 'lv') status = core.getLvName() ?? '';
else status = core.plugin.hero.getHeroStatusOn(v)?.toString();
else status = getHeroStatusOn(v)?.toString();
let add = 0;
if (has(nowEquip)) {
+1 -1
View File
@@ -29,7 +29,7 @@ export function getDetailedEnemy(
return typeof func === 'string' ? func : func(enemy);
};
const special: [string, string, string][] = enemy.enemy.special.map(vv => {
const s = core.plugin.special[vv];
const s = Mota.Plugin.require('special_g').special[vv];
return [
fromFunc(s.name, enemy.enemy),
fromFunc(s.desc, enemy.enemy),
+3 -1
View File
@@ -225,6 +225,7 @@ export async function doByInterval(
/**
* 更改一个本地存储
* @deprecated
* @param name 要更改的信息
* @param fn 更改时执行的函数
* @param defaultValue 如果不存在时获取的默认值
@@ -287,7 +288,8 @@ export function spliceBy<T>(arr: T[], from: T): T[] {
}
export async function triggerFullscreen(full: boolean) {
const { maxGameScale } = core.plugin.utils;
if (!Mota.Plugin.inited) return;
const { maxGameScale } = Mota.Plugin.require('utils_g');
if (!!document.fullscreenElement && !full) {
await document.exitFullscreen();
requestAnimationFrame(() => {