Eustia/project/plugins.js

17670 lines
561 KiB
JavaScript
Raw Normal View History

2025-01-18 17:49:44 +08:00
var plugins_bb40132b_638b_4a9f_b028_d3fe47acc8d1 =
{
"init": function () {
2025-01-16 13:17:19 +08:00
this._afterLoadResources = function () {
// 本函数将在所有资源加载完毕后,游戏开启前被执行
core.ui.statusBar.init();
core.registerEvent("changeMouse", function (data) {
if (!main.replayChecking && !core.isReplaying())
core.changeMouse(
data.icon,
data.div,
data.translate[0],
data.translate[1],
data.scale[0],
data.scale[1],
data.angel,
data.px,
data.py
);
core.doAction();
});
core.registerEvent("removeMouse", function (data) {
if (!main.replayChecking && !core.isReplaying())
core.removeMouse(data.div);
core.doAction();
});
core.registerEvent("addPop", function (data) {
if (!main.replayChecking && !core.isReplaying()) {
data.value = core.replaceText(data.value);
core.addPop(
data.value,
data.px,
data.py,
data.color,
data.boldColor,
data.left,
data.jump,
data.time,
data.show,
data.font,
data.speed
);
}
core.doAction();
});
core.registerEvent("drawWarning", function (data) {
if (!main.replayChecking && !core.isReplaying()) {
data.text = core.replaceText(data.text);
data.text2 = core.replaceText(data.text2);
core.drawWarning(
data.x,
data.y,
data.size,
data?.text,
data?.text2,
data?.warning
);
setTimeout(() => core.doAction(), 3100);
} else {
core.doAction();
}
});
core.registerEvent("playStereo", function (data) {
if (!main.replayChecking && !core.isReplaying())
core.playStereo(data.name, data.left, data.right, data.split);
core.doAction();
});
core.registerEvent("moveStereo", function (data) {
if (!main.replayChecking && !core.isReplaying()) {
const id = core.playStereo(
data.name,
data.left,
data.right,
data.split
);
core.moveStereo(id, data.leftTo, data.rightTo, data.time);
}
core.doAction();
});
core.registerEvent("over", function (data) {
let image = data.image ?? "";
let time = data.time ?? 3000;
let sound = data.sound ?? "";
let textColor = data.textColor ?? "#FFFFFF";
let boldColor = data.boldColor ?? "#000000";
let font = data.font ?? "bold 48px Verdana";
let text = data.text ?? "";
let hidetime = data.hidetime ?? 100;
if (!main.replayChecking && !core.isReplaying()) {
core.over(
image,
data.memory,
time,
hidetime,
sound,
textColor,
boldColor,
font,
text
);
} else {
core.doAction();
}
});
core.registerEvent("changebg", function (data) {
if (!main.replayChecking && !core.isReplaying()) {
core.changebg(
data.img1,
data.memory1,
data.img2,
data.memory2,
data.time,
data.style
);
} else {
core.doAction();
}
});
core.registerEvent("overlist", function (data) {
if (!main.replayChecking && !core.isReplaying()) {
core.overlist(
data.image,
data.memory,
data.hidetime || 30,
data.list || [
{
text: "",
sound: "",
time: 50,
textColor: "#FFFFFF",
boldColor: "#000000",
font: "bold 48px Verdana",
frame: 0,
},
]
);
} else {
core.doAction();
}
});
core.registerEvent("op", function (data) {
if (!main.replayChecking && !core.isReplaying()) {
core.openvideo();
} else {
core.doAction();
}
});
core.registerEvent("animationDrawable", function (data) {
if (!main.replayChecking && !core.isReplaying()) {
core.animationDrawable(
data.allFarme,
data.color,
data.globalAlpha,
data.imageList,
data.soundList
);
} else {
core.doAction();
}
});
core.registerEvent("setanimate", function (data) {
data.px = data.px ?? 0;
data.py = data.py ?? 0;
core.setanimate(
data.name,
data.px,
data.py,
data.width,
data.height,
data.allFarme,
data.imageList,
data.soundList
);
core.doAction();
});
core.registerEvent("clearanimate", function (data) {
core.plugin.playing.clear();
core.doAction();
});
core.registerEvent("deleteanimate", function (data) {
core.deleteanimate(data.name);
core.doAction();
});
core.registerEvent("playanimate", function (data) {
if (!main.replayChecking && !core.isReplaying()) {
data.x = data.x ?? 0;
data.y = data.y ?? 0;
data.scalex = data.scalex ?? 1;
data.scaley = data.scaley ?? 1;
core.playanimate(
data.name,
data.x,
data.y,
data.hero,
data.scalex,
data.scaley
);
core.doAction();
} else {
core.doAction();
}
});
core.registerEvent("cgtext", function (data) {
if (!main.replayChecking && !core.isReplaying()) {
core.ui.cgText.image = data.bg;
core.ui.cgText.memory = data.memory;
core.ui.cgText.head = core.clone(data.head);
core.ui.cgText.name = data.name;
core.ui.cgText.text = data.text;
core.ui.cgText.time = data.time;
core.ui.cgText.wait = data.wait;
core.ui.cgText.WindowSkin = data.WindowSkin;
core.ui.cgText.sound = data.sound || "";
core.ui.cgText.bodyList = core.clone(data.bodyList);
main.dom.cgText.style.display = "block";
core.ui.cgText.update();
} else {
core.doAction();
}
});
};
},
2025-01-18 17:49:44 +08:00
"drawLight": function () {
2024-12-27 11:55:28 +08:00
// 绘制灯光/漆黑层效果。调用方式 core.plugin.drawLight(...)
// 【参数说明】
// name必填要绘制到的画布名可以是一个系统画布或者是个自定义画布如果不存在则创建
// color可选只能是一个0~1之间的数为不透明度的值。不填则默认为0.9。
// lights可选一个数组定义了每个独立的灯光。
// 其中每一项是三元组 [x,y,r] x和y分别为该灯光的横纵坐标r为该灯光的半径。
// lightDec可选0到1之间光从多少百分比才开始衰减在此范围内保持全亮不设置默认为0。
// 比如lightDec为0.5代表每个灯光部分内圈50%的范围全亮50%以后才开始快速衰减。
// 【调用样例】
// core.plugin.drawLight('curtain'); // 在curtain层绘制全图不透明度0.9,等价于更改画面色调为[0,0,0,0.9]。
// core.plugin.drawLight('ui', 0.95, [[25,11,46]]); // 在ui层绘制全图不透明度0.95,其中在(25,11)点存在一个半径为46的灯光效果。
// core.plugin.drawLight('test', 0.2, [[25,11,46,0.1]]); // 创建一个test图层不透明度0.2,其中在(25,11)点存在一个半径为46的灯光效果灯光中心不透明度0.1。
// core.plugin.drawLight('test2', 0.9, [[25,11,46],[105,121,88],[301,221,106]]); // 创建test2图层且存在三个灯光效果分别是中心(25,11)半径46中心(105,121)半径88中心(301,221)半径106。
// core.plugin.drawLight('xxx', 0.3, [[25,11,46],[105,121,88,0.2]], 0.4); // 存在两个灯光效果它们在内圈40%范围内保持全亮40%后才开始衰减。
this.drawLight = function (name, color, lights, lightDec) {
// 清空色调层也可以修改成其它层比如animate/weather层或者用自己创建的canvas
var ctx = core.getContextByName(name);
if (ctx == null) {
if (typeof name == "string")
ctx = core.createCanvas(
name,
0,
0,
core._PX_ || core.__PIXELS__,
core._PY_ || core.__PIXELS__,
98
);
else return;
}
ctx.mozImageSmoothingEnabled = false;
ctx.webkitImageSmoothingEnabled = false;
ctx.msImageSmoothingEnabled = false;
ctx.imageSmoothingEnabled = false;
core.clearMap(name);
// 绘制色调层,默认不透明度
if (color == null) color = 0.9;
ctx.fillStyle = "rgba(0,0,0," + color + ")";
ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);
lightDec = core.clamp(lightDec, 0, 1);
// 绘制每个灯光效果
ctx.globalCompositeOperation = "destination-out";
lights.forEach(function (light) {
// 坐标,半径,中心不透明度
var x = light[0],
y = light[1],
r = light[2];
// 计算衰减距离
var decDistance = parseInt(r * lightDec);
// 正方形区域的直径和左上角坐标
var grd = ctx.createRadialGradient(x, y, decDistance, x, y, r);
grd.addColorStop(0, "rgba(0,0,0,1)");
grd.addColorStop(1, "rgba(0,0,0,0)");
ctx.beginPath();
ctx.fillStyle = grd;
ctx.arc(x, y, r, 0, 2 * Math.PI);
ctx.fill();
});
ctx.globalCompositeOperation = "source-over";
// 可以在任何地方如afterXXX或自定义脚本事件调用函数方法为 core.plugin.xxx();
};
},
2025-01-18 17:49:44 +08:00
"shop": function () {
2024-12-27 11:55:28 +08:00
// 【全局商店】相关的功能
//
// 打开一个全局商店
// shopId要打开的商店idnoRoute是否不计入录像
this.openShop = function (shopId, noRoute) {
var shop = core.status.shops[shopId];
// Step 1: 检查能否打开此商店
if (!this.canOpenShop(shopId)) {
core.drawTip("该商店尚未开启");
return false;
}
// Step 2: (如有必要)记录打开商店的脚本事件
if (!noRoute) {
core.status.route.push("shop:" + shopId);
}
// Step 3: 检查道具商店 or 公共事件
if (shop.item) {
if (core.openItemShop) {
core.openItemShop(shopId);
} else {
core.playSound("操作失败");
core.insertAction("道具商店插件不存在!请检查是否存在该插件!");
}
return;
}
if (shop.commonEvent) {
core.insertCommonEvent(shop.commonEvent, shop.args);
return;
}
_shouldProcessKeyUp = true;
// Step 4: 执行标准公共商店
core.insertAction(this._convertShop(shop));
return true;
};
////// 将一个全局商店转变成可预览的公共事件 //////
this._convertShop = function (shop) {
return [
{
type: "function",
function: "function() {core.addFlag('@temp@shop', 1);}",
},
{
type: "while",
condition: "true",
data: [
// 检测能否访问该商店
{
type: "if",
condition: "core.isShopVisited('" + shop.id + "')",
true: [
// 可以访问,直接插入执行效果
{
type: "function",
function:
"function() { core.plugin._convertShop_replaceChoices('" +
shop.id +
"', false) }",
},
],
false: [
// 不能访问的情况下:检测能否预览
{
type: "if",
condition: shop.disablePreview,
true: [
// 不可预览,提示并退出
{ type: "playSound", name: "操作失败" },
"当前无法访问该商店!",
{ type: "break" },
],
false: [
// 可以预览:将商店全部内容进行替换
{ type: "tip", text: "当前处于预览模式,不可购买" },
{
type: "function",
function:
"function() { core.plugin._convertShop_replaceChoices('" +
shop.id +
"', true) }",
},
],
},
],
},
],
},
{
type: "function",
function: "function() {core.addFlag('@temp@shop', -1);}",
},
];
};
this._convertShop_replaceChoices = function (shopId, previewMode) {
var shop = core.status.shops[shopId];
var choices = (shop.choices || [])
.filter(function (choice) {
if (choice.condition == null || choice.condition == "") return true;
try {
return core.calValue(choice.condition);
} catch (e) {
return true;
}
})
.map(function (choice) {
var ableToBuy = core.calValue(choice.need);
return {
text: core.replaceText(choice.text),
icon: choice.icon,
color:
ableToBuy && !previewMode ? choice.color : [153, 153, 153, 1],
action:
ableToBuy && !previewMode
? [{ type: "playSound", name: "商店" }].concat(choice.action)
: [
{ type: "playSound", name: "操作失败" },
{
type: "tip",
text: previewMode ? "预览模式下不可购买" : "购买条件不足",
},
],
};
})
.concat({
text: "离开",
action: [{ type: "playSound", name: "取消" }, { type: "break" }],
});
core.insertAction({
type: "choices",
text: core.replaceText(shop.text),
choices: choices,
});
};
/// 是否访问过某个快捷商店
this.isShopVisited = function (id) {
if (!core.hasFlag("__shops__")) core.setFlag("__shops__", {});
var shops = core.getFlag("__shops__");
if (!shops[id]) shops[id] = {};
return shops[id].visited;
};
/// 当前应当显示的快捷商店列表
this.listShopIds = function () {
return Object.keys(core.status.shops).filter(function (id) {
return core.isShopVisited(id) || !core.status.shops[id].mustEnable;
});
};
/// 是否能够打开某个商店
this.canOpenShop = function (id) {
if (this.isShopVisited(id)) return true;
var shop = core.status.shops[id];
if (shop.item || shop.commonEvent || shop.mustEnable) return false;
return true;
};
/// 启用或禁用某个快捷商店
this.setShopVisited = function (id, visited) {
if (!core.hasFlag("__shops__")) core.setFlag("__shops__", {});
var shops = core.getFlag("__shops__");
if (!shops[id]) shops[id] = {};
if (visited) shops[id].visited = true;
else delete shops[id].visited;
};
/// 能否使用快捷商店
this.canUseQuickShop = function (id) {
// 如果返回一个字符串,表示不能,字符串为不能使用的提示
// 返回null代表可以使用
// 检查当前楼层的canUseQuickShop选项是否为false
if (core.status.thisMap.canUseQuickShop === false)
return "当前楼层不能使用快捷商店。";
return null;
};
var _shouldProcessKeyUp = true;
/// 允许商店X键退出
core.registerAction(
"keyUp",
"shops",
function (keycode) {
if (!core.status.lockControl || core.status.event.id != "action")
return false;
if ((keycode == 13 || keycode == 32) && !_shouldProcessKeyUp) {
_shouldProcessKeyUp = true;
return true;
}
if (
!core.hasFlag("@temp@shop") ||
core.status.event.data.type != "choices"
)
return false;
var data = core.status.event.data.current;
var choices = data.choices;
var topIndex = core.actions._getChoicesTopIndex(choices.length);
if (keycode == 88 || keycode == 27) {
// X, ESC
core.actions._clickAction(
core._HALF_WIDTH_ || core.__HALF_SIZE__,
topIndex + choices.length - 1
);
return true;
}
return false;
},
60
);
/// 允许长按空格或回车连续执行操作
core.registerAction(
"keyDown",
"shops",
function (keycode) {
if (
!core.status.lockControl ||
!core.hasFlag("@temp@shop") ||
core.status.event.id != "action"
)
return false;
if (core.status.event.data.type != "choices") return false;
core.status.onShopLongDown = true;
var data = core.status.event.data.current;
var choices = data.choices;
var topIndex = core.actions._getChoicesTopIndex(choices.length);
if (keycode == 13 || keycode == 32) {
// Space, Enter
core.actions._clickAction(
core._HALF_WIDTH_ || core.__HALF_SIZE__,
topIndex + core.status.event.selection
);
_shouldProcessKeyUp = false;
return true;
}
return false;
},
60
);
// 允许长按屏幕连续执行操作
core.registerAction(
"longClick",
"shops",
function (x, y, px, py) {
if (
!core.status.lockControl ||
!core.hasFlag("@temp@shop") ||
core.status.event.id != "action"
)
return false;
if (core.status.event.data.type != "choices") return false;
var data = core.status.event.data.current;
var choices = data.choices;
var topIndex = core.actions._getChoicesTopIndex(choices.length);
if (
Math.abs(x - (core._HALF_WIDTH_ || core.__HALF_SIZE__)) <= 2 &&
y >= topIndex &&
y < topIndex + choices.length
) {
core.actions._clickAction(x, y);
return true;
}
return false;
},
60
);
},
2025-01-18 17:49:44 +08:00
"removeMap": function () {
2024-12-27 11:55:28 +08:00
// 高层塔砍层插件,删除后不会存入存档,不可浏览地图也不可飞到。
// 推荐用法:
// 对于超高层或分区域塔当在1区时将2区以后的地图删除1区结束时恢复2区进二区时删除1区地图以此类推
// 这样可以大幅减少存档空间,以及加快存读档速度
// 删除楼层
// core.removeMaps("MT1", "MT300") 删除MT1~MT300之间的全部层
// core.removeMaps("MT10") 只删除MT10层
this.removeMaps = function (fromId, toId) {
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__ || {};
for (var i = fromIndex; i <= toIndex; ++i) {
var floorId = core.floorIds[i];
if (core.status.maps[floorId].deleted) continue;
delete flags.__visited__[floorId];
flags.__removed__.push(floorId);
delete flags.__disabled__[floorId];
delete flags.__leaveLoc__[floorId];
(core.status.autoEvents || []).forEach(function (event) {
if (event.floorId == floorId && event.currentFloor) {
core.autoEventExecuting(event.symbol, false);
core.autoEventExecuted(event.symbol, false);
}
});
core.status.maps[floorId].deleted = true;
core.status.maps[floorId].canFlyTo = false;
core.status.maps[floorId].canFlyFrom = false;
core.status.maps[floorId].cannotViewMap = true;
}
};
// 恢复楼层
// core.resumeMaps("MT1", "MT300") 恢复MT1~MT300之间的全部层
// core.resumeMaps("MT10") 只恢复MT10层
this.resumeMaps = function (fromId, toId) {
toId = toId || fromId;
var fromIndex = core.floorIds.indexOf(fromId),
toIndex = core.floorIds.indexOf(toId);
if (toIndex < 0) toIndex = core.floorIds.length - 1;
flags.__removed__ = flags.__removed__ || [];
for (var i = fromIndex; i <= toIndex; ++i) {
var floorId = core.floorIds[i];
if (!core.status.maps[floorId].deleted) continue;
flags.__removed__ = flags.__removed__.filter(function (f) {
return f != floorId;
});
core.status.maps[floorId] = core.loadFloor(floorId);
}
};
// 分区砍层相关
var inAnyPartition = function (floorId) {
var inPartition = false;
(core.floorPartitions || []).forEach(function (floor) {
var fromIndex = core.floorIds.indexOf(floor[0]);
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) inPartition = true;
});
return inPartition;
};
// 分区砍层
this.autoRemoveMaps = function (floorId) {
if (main.mode != "play" || !inAnyPartition(floorId)) return;
// 根据分区信息自动砍层与恢复
(core.floorPartitions || []).forEach(function (floor) {
var fromIndex = core.floorIds.indexOf(floor[0]);
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.resumeMaps(core.floorIds[fromIndex], core.floorIds[toIndex]);
} else {
core.removeMaps(core.floorIds[fromIndex], core.floorIds[toIndex]);
}
});
};
},
2025-01-18 17:49:44 +08:00
"fiveLayers": function () {
2024-12-27 11:55:28 +08:00
// 是否启用五图层增加背景2层和前景2层 将__enable置为true即会启用启用后请保存后刷新编辑器
// 背景层2将会覆盖背景层 被事件层覆盖 前景层2将会覆盖前景层
// 另外 请注意加入两个新图层 会让大地图的性能降低一些
// 插件作者ad
var __enable = true;
if (!__enable) return;
// 创建新图层
function createCanvas(name, zIndex) {
if (!name) return;
var canvas = document.createElement("canvas");
canvas.id = name;
canvas.className = "gameCanvas 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;
canvas.width = core._PX_ || core.__PIXELS__;
canvas.height = core._PY_ || core.__PIXELS__;
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 = function (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 = function () {
editor.uifunctions.setLayerMod(value);
};
return input;
};
var createCanvasBtn_mobile = function (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("bg2");
// 插入事件层前(即新插入的背景层2前)
parent.insertBefore(txt, child);
// 向最后插入前景层2(即插入前景层后)
parent.appendChild(input2);
var txt2 = document.createTextNode("fg2");
parent.appendChild(txt2);
parent.childNodes[2].replaceWith("bg");
parent.childNodes[6].replaceWith("事件");
parent.childNodes[8].replaceWith("fg");
} 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);
}
}
var _loadFloor_doNotCopy = core.maps._loadFloor_doNotCopy;
core.maps._loadFloor_doNotCopy = function () {
return ["bg2map", "fg2map"].concat(_loadFloor_doNotCopy());
};
////// 绘制背景和前景层 //////
core.maps._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;
};
core.maps._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;
};
////// 移动判定 //////
core.maps._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),
};
};
},
2025-01-18 17:49:44 +08:00
"itemShop": function () {
2024-12-27 11:55:28 +08:00
// 道具商店相关的插件
// 可在全塔属性-全局商店中使用「道具商店」事件块进行编辑(如果找不到可以在入口方块中找)
var shopId = null; // 当前商店ID
var type = 0; // 当前正在选中的类型0买入1卖出
var selectItem = 0; // 当前正在选中的道具
var selectCount = 0; // 当前已经选中的数量
var page = 0;
var totalPage = 0;
var totalMoney = 0;
var list = [];
var shopInfo = null; // 商店信息
var choices = []; // 商店选项
var use = "money";
var useText = "金币";
var bigFont = core.ui._buildFont(20, false),
middleFont = core.ui._buildFont(18, false);
this._drawItemShop = function () {
// 绘制道具商店
// Step 1: 背景和固定的几个文字
core.ui._createUIEvent();
core.clearMap("uievent");
core.ui.clearUIEventSelector();
core.setTextAlign("uievent", "left");
core.setTextBaseline("uievent", "top");
core.fillRect("uievent", 0, 0, 416, 416, "black");
2025-01-05 00:03:41 +08:00
core.drawWindowSkin("winskin.webp", "uievent", 0, 0, 416, 56);
core.drawWindowSkin("winskin.webp", "uievent", 0, 56, 312, 56);
core.drawWindowSkin("winskin.webp", "uievent", 0, 112, 312, 304);
core.drawWindowSkin("winskin.webp", "uievent", 312, 56, 104, 56);
core.drawWindowSkin("winskin.webp", "uievent", 312, 112, 104, 304);
2024-12-27 11:55:28 +08:00
core.setFillStyle("uievent", "white");
core.setStrokeStyle("uievent", "white");
core.fillText("uievent", "购买", 32, 74, "white", bigFont);
core.fillText("uievent", "卖出", 132, 74);
core.fillText("uievent", "离开", 232, 74);
core.fillText("uievent", "当前" + useText, 324, 66, null, middleFont);
core.setTextAlign("uievent", "right");
core.fillText(
"uievent",
core.formatBigNumber(core.status.hero[use]),
405,
89
);
core.setTextAlign("uievent", "left");
core.ui.drawUIEventSelector(
1,
2025-01-05 00:03:41 +08:00
"winskin.webp",
2024-12-27 11:55:28 +08:00
22 + 100 * type,
66,
60,
33
);
if (selectItem != null) {
core.setTextAlign("uievent", "center");
core.fillText(
"uievent",
type == 0 ? "买入个数" : "卖出个数",
364,
320,
null,
bigFont
);
core.fillText("uievent", "< " + selectCount + " >", 364, 350);
core.fillText("uievent", "确定", 364, 380);
}
// Step 2获得列表并展示
list = choices.filter(function (one) {
if (one.condition != null && one.condition != "") {
try {
if (!core.calValue(one.condition)) return false;
} catch (e) {}
}
return (
(type == 0 && one.money != null) || (type == 1 && one.sell != null)
);
});
var per_page = 6;
totalPage = Math.ceil(list.length / per_page);
page = Math.floor((selectItem || 0) / per_page) + 1;
// 绘制分页
if (totalPage > 1) {
var half = 156;
core.setTextAlign("uievent", "center");
core.fillText(
"uievent",
page + " / " + totalPage,
half,
388,
null,
middleFont
);
if (page > 1) core.fillText("uievent", "上一页", half - 80, 388);
if (page < totalPage)
core.fillText("uievent", "下一页", half + 80, 388);
}
core.setTextAlign("uievent", "left");
// 绘制每一项
var start = (page - 1) * per_page;
for (var i = 0; i < per_page; ++i) {
var curr = start + i;
if (curr >= list.length) break;
var item = list[curr];
core.drawIcon("uievent", item.id, 10, 125 + i * 40);
core.setTextAlign("uievent", "left");
core.fillText(
"uievent",
core.material.items[item.id].name,
50,
132 + i * 40,
null,
bigFont
);
core.setTextAlign("uievent", "right");
core.fillText(
"uievent",
(type == 0 ? core.calValue(item.money) : core.calValue(item.sell)) +
useText +
"/个",
300,
133 + i * 40,
null,
middleFont
);
core.setTextAlign("uievent", "left");
if (curr == selectItem) {
// 绘制描述,文字自动放缩
var text =
core.replaceText(core.material.items[item.id].text) ||
"该道具暂无描述";
if (text[0] == "," || text[0] == "") text = text.substring(1);
try {
text = core.replaceText(text);
} catch (e) {}
for (var fontSize = 20; fontSize >= 8; fontSize -= 2) {
var config = { left: 10, fontSize: fontSize, maxWidth: 403 };
var height = core.getTextContentHeight(text, config);
if (height <= 50) {
config.top = (56 - height) / 2;
core.drawTextContent("uievent", text, config);
break;
}
}
core.ui.drawUIEventSelector(
2,
2025-01-05 00:03:41 +08:00
"winskin.webp",
2024-12-27 11:55:28 +08:00
8,
120 + i * 40,
295,
40
);
if (type == 0 && item.number != null) {
core.fillText("uievent", "存货", 324, 132, null, bigFont);
core.setTextAlign("uievent", "right");
core.fillText("uievent", item.number, 406, 132, null, null, 40);
} else if (type == 1) {
core.fillText("uievent", "数量", 324, 132, null, bigFont);
core.setTextAlign("uievent", "right");
core.fillText(
"uievent",
core.itemCount(item.id),
406,
132,
null,
null,
40
);
}
core.setTextAlign("uievent", "left");
core.fillText("uievent", "预计" + useText, 324, 250);
core.setTextAlign("uievent", "right");
totalMoney =
selectCount *
(type == 0 ? core.calValue(item.money) : core.calValue(item.sell));
core.fillText("uievent", core.formatBigNumber(totalMoney), 405, 280);
core.setTextAlign("uievent", "left");
core.fillText(
"uievent",
type == 0 ? "已购次数" : "已卖次数",
324,
170
);
core.setTextAlign("uievent", "right");
core.fillText(
"uievent",
(type == 0 ? item.money_count : item.sell_count) || 0,
405,
200
);
}
}
core.setTextAlign("uievent", "left");
core.setTextBaseline("uievent", "alphabetic");
};
var _add = function (item, delta) {
if (item == null) return;
selectCount = core.clamp(
selectCount + delta,
0,
Math.min(
type == 0
? Math.floor(core.status.hero[use] / core.calValue(item.money))
: core.itemCount(item.id),
type == 0 && item.number != null
? item.number
: Number.MAX_SAFE_INTEGER
)
);
};
var _confirm = function (item) {
if (item == null || selectCount == 0) return;
if (type == 0) {
core.status.hero[use] -= totalMoney;
core.getItem(item.id, selectCount);
core.stopSound();
core.playSound("确定");
if (item.number != null) item.number -= selectCount;
item.money_count = (item.money_count || 0) + selectCount;
} else {
core.status.hero[use] += totalMoney;
core.removeItem(item.id, selectCount);
core.playSound("确定");
core.drawTip(
"成功卖出" + selectCount + "个" + core.material.items[item.id].name,
item.id
);
if (item.number != null) item.number += selectCount;
item.sell_count = (item.sell_count || 0) + selectCount;
}
selectCount = 0;
};
this._performItemShopKeyBoard = function (keycode) {
var item = list[selectItem] || null;
// 键盘操作
switch (keycode) {
case 38: // up
if (selectItem == null) break;
if (selectItem == 0) selectItem = null;
else selectItem--;
selectCount = 0;
break;
case 37: // left
if (selectItem == null) {
if (type > 0) type--;
break;
}
_add(item, -1);
break;
case 39: // right
if (selectItem == null) {
if (type < 2) type++;
break;
}
_add(item, 1);
break;
case 40: // down
if (selectItem == null) {
if (list.length > 0) selectItem = 0;
break;
}
if (list.length == 0) break;
selectItem = Math.min(selectItem + 1, list.length - 1);
selectCount = 0;
break;
case 13:
case 32: // Enter/Space
if (selectItem == null) {
if (type == 2) core.insertAction({ type: "break" });
else if (list.length > 0) selectItem = 0;
break;
}
_confirm(item);
break;
case 27: // ESC
if (selectItem == null) {
core.insertAction({ type: "break" });
break;
}
selectItem = null;
break;
}
};
this._performItemShopClick = function (px, py) {
var item = list[selectItem] || null;
// 鼠标操作
if (px >= 22 && px <= 82 && py >= 71 && py <= 102) {
// 买
if (type != 0) {
type = 0;
selectItem = null;
selectCount = 0;
}
return;
}
if (px >= 122 && px <= 182 && py >= 71 && py <= 102) {
// 卖
if (type != 1) {
type = 1;
selectItem = null;
selectCount = 0;
}
return;
}
if (px >= 222 && px <= 282 && py >= 71 && py <= 102)
// 离开
return core.insertAction({ type: "break" });
// < >
if (px >= 318 && px <= 341 && py >= 348 && py <= 376)
return _add(item, -1);
if (px >= 388 && px <= 416 && py >= 348 && py <= 376)
return _add(item, 1);
// 确定
if (px >= 341 && px <= 387 && py >= 380 && py <= 407)
return _confirm(item);
// 上一页/下一页
if (px >= 45 && px <= 105 && py >= 388) {
if (page > 1) {
selectItem -= 6;
selectCount = 0;
}
return;
}
if (px >= 208 && px <= 268 && py >= 388) {
if (page < totalPage) {
selectItem = Math.min(selectItem + 6, list.length - 1);
selectCount = 0;
}
return;
}
// 实际区域
if (px >= 9 && px <= 300 && py >= 120 && py < 360) {
if (list.length == 0) return;
var index = parseInt((py - 120) / 40);
var newItem = 6 * (page - 1) + index;
if (newItem >= list.length) newItem = list.length - 1;
if (newItem != selectItem) {
selectItem = newItem;
selectCount = 0;
}
return;
}
};
this._performItemShopAction = function () {
if (flags.type == 0) return this._performItemShopKeyBoard(flags.keycode);
else return this._performItemShopClick(flags.px, flags.py);
};
this.openItemShop = function (itemShopId) {
shopId = itemShopId;
type = 0;
page = 0;
selectItem = null;
selectCount = 0;
core.isShopVisited(itemShopId);
shopInfo = flags.__shops__[shopId];
if (shopInfo.choices == null)
shopInfo.choices = core.clone(core.status.shops[shopId].choices);
choices = shopInfo.choices;
use = core.status.shops[shopId].use;
if (use != "exp") use = "money";
useText = use == "money" ? "金币" : "经验";
core.insertAction([
{
type: "while",
condition: "true",
data: [
{
type: "function",
function: "function () { core.plugin._drawItemShop(); }",
},
{ type: "wait" },
{
type: "function",
function: "function() { core.plugin._performItemShopAction(); }",
},
],
},
{
type: "function",
function:
"function () { core.deleteCanvas('uievent'); core.ui.clearUIEventSelector(); }",
},
]);
};
},
2025-01-18 17:49:44 +08:00
"enemyLevel": function () {
2024-12-27 11:55:28 +08:00
// 此插件将提供怪物手册中的怪物境界显示
// 使用此插件需要先给每个怪物定义境界,方法如下:
// 点击怪物的【配置表格】,找到“【怪物】相关的表格配置”,然后在【名称】仿照增加境界定义:
/*
"level": {
"_leaf": true,
"_type": "textarea",
"_string": true,
"_data": "境界"
},
*/
2024-12-27 11:55:28 +08:00
// 然后保存刷新,可以看到怪物的属性定义中出现了【境界】。再开启本插件即可。
// 是否开启本插件,默认禁用;将此改成 true 将启用本插件。
var __enable = false;
if (!__enable) return;
// 这里定义每个境界的显示颜色;可以写'red', '#RRGGBB' 或者[r,g,b,a]四元数组
var levelToColors = {
萌新一阶: "red",
萌新二阶: "#FF0000",
萌新三阶: [255, 0, 0, 1],
};
// 复写 _drawBook_drawName
var originDrawBook = core.ui._drawBook_drawName;
core.ui._drawBook_drawName = function (index, enemy, top, left, width) {
// 如果没有境界,则直接调用原始代码绘制
if (!enemy.level)
return originDrawBook.call(core.ui, index, enemy, top, left, width);
// 存在境界,则额外进行绘制
core.setTextAlign("ui", "center");
if (enemy.specialText.length == 0) {
core.fillText(
"ui",
enemy.name,
left + width / 2,
top + 27,
"#DDDDDD",
this._buildFont(17, true)
);
core.fillText(
"ui",
enemy.level,
left + width / 2,
top + 51,
core.arrayToRGBA(levelToColors[enemy.level] || "#DDDDDD"),
this._buildFont(14, true)
);
} else {
core.fillText(
"ui",
enemy.name,
left + width / 2,
top + 20,
"#DDDDDD",
this._buildFont(17, true),
width
);
switch (enemy.specialText.length) {
case 1:
core.fillText(
"ui",
enemy.specialText[0],
left + width / 2,
top + 38,
core.arrayToRGBA((enemy.specialColor || [])[0] || "#FF6A6A"),
this._buildFont(14, true),
width
);
break;
case 2:
// Step 1: 计算字体
var text = enemy.specialText[0] + " " + enemy.specialText[1];
core.setFontForMaxWidth(
"ui",
text,
width,
this._buildFont(14, true)
);
// Step 2: 计算总宽度
var totalWidth = core.calWidth("ui", text);
var leftWidth = core.calWidth("ui", enemy.specialText[0]);
var rightWidth = core.calWidth("ui", enemy.specialText[1]);
// Step 3: 绘制
core.fillText(
"ui",
enemy.specialText[0],
left + (width + leftWidth - totalWidth) / 2,
top + 38,
core.arrayToRGBA((enemy.specialColor || [])[0] || "#FF6A6A")
);
core.fillText(
"ui",
enemy.specialText[1],
left + (width + totalWidth - rightWidth) / 2,
top + 38,
core.arrayToRGBA((enemy.specialColor || [])[1] || "#FF6A6A")
);
break;
default:
core.fillText(
"ui",
"多属性...",
left + width / 2,
top + 38,
"#FF6A6A",
this._buildFont(14, true),
width
);
}
core.fillText(
"ui",
enemy.level,
left + width / 2,
top + 56,
core.arrayToRGBA(levelToColors[enemy.level] || "#DDDDDD"),
this._buildFont(14, true)
);
}
};
// 也可以复写其他的属性颜色如怪物攻防等,具体参见下面的例子的注释部分
core.ui._drawBook_drawRow1 = function (
index,
enemy,
top,
left,
width,
position
) {
// 绘制第一行
core.setTextAlign("ui", "left");
var b13 = this._buildFont(13, true),
f13 = this._buildFont(13, false);
var col1 = left,
col2 = left + (width * 9) / 25,
col3 = left + (width * 17) / 25;
core.fillText("ui", "生命", col1, position, "#DDDDDD", f13);
core.fillText(
"ui",
core.formatBigNumber(enemy.hp || 0),
col1 + 30,
position,
/*'red' */ null,
b13
);
core.fillText("ui", "攻击", col2, position, null, f13);
core.fillText(
"ui",
core.formatBigNumber(enemy.atk || 0),
col2 + 30,
position,
/* '#FF0000' */ null,
b13
);
core.fillText("ui", "防御", col3, position, null, f13);
core.fillText(
"ui",
core.formatBigNumber(enemy.def || 0),
col3 + 30,
position,
/* [255, 0, 0, 1] */ null,
b13
);
};
},
2025-01-18 17:49:44 +08:00
"multiHeros": function () {
2024-12-27 11:55:28 +08:00
// 多角色插件
// Step 1: 启用本插件
// Step 2: 定义每个新的角色各项初始数据(参见下方注释)
// Step 3: 在游戏中的任何地方都可以调用 `core.changeHero()` 进行切换;也可以 `core.changeHero(1)` 来切换到某个具体的角色上
// 是否开启本插件,默认禁用;将此改成 true 将启用本插件。
var __enable = false;
if (!__enable) return;
// 在这里定义全部的新角色属性
// 请注意,在这里定义的内容不会多角色共用,在切换时会进行恢复。
// 你也可以自行新增或删除,比如不共用金币则可以加上"money"的初始化,不共用道具则可以加上"items"的初始化,
// 多角色共用hp的话则删除hp等等。总之不共用的属性都在这里进行定义就好。
var hero1 = {
floorId: "MT0", // 该角色初始楼层ID如果共用楼层可以注释此项
2025-01-05 00:03:41 +08:00
image: "brave.webp", // 角色的行走图名称;此项必填不然会报错
2024-12-27 11:55:28 +08:00
name: "1号角色",
lv: 1,
hp: 10000, // 如果HP共用可注释此项
atk: 1000,
def: 1000,
mdef: 0,
// "money": 0, // 如果要不共用金币则取消此项注释
// "exp": 0, // 如果要不共用经验则取消此项注释
loc: { x: 0, y: 0, direction: "up" }, // 该角色初始位置;如果共用位置可注释此项
items: {
tools: {}, // 如果共用消耗道具(含钥匙)则可注释此项
// "constants": {}, // 如果不共用永久道具(如手册)可取消注释此项
equips: {}, // 如果共用在背包的装备可注释此项
},
equipment: [], // 如果共用装备可注释此项;此项和上面的「共用在背包的装备」需要拥有相同状态,不然可能出现问题
};
// 也可以类似新增其他角色
// 新增的角色,各项属性共用与不共用的选择必须和上面完全相同,否则可能出现问题。
// var hero2 = { ...
var heroCount = 2; // 包含默认角色在内总共多少个角色,该值需手动修改。
this.initHeros = function () {
core.setFlag("hero1", core.clone(hero1)); // 将属性值存到变量中
// core.setFlag("hero2", core.clone(hero2)); // 更多的角色也存入变量中;每个定义的角色都需要新增一行
// 检测是否存在装备
if (hero1.equipment) {
if (!hero1.items || !hero1.items.equips) {
alert("多角色插件的equipment和道具中的equips必须拥有相同状态");
}
// 存99号套装为全空
var saveEquips = core.getFlag("saveEquips", []);
saveEquips[99] = [];
core.setFlag("saveEquips", saveEquips);
} else {
if (hero1.items && hero1.items.equips) {
alert("多角色插件的equipment和道具中的equips必须拥有相同状态");
}
}
};
// 在游戏开始注入initHeros
var _startGame_setHard = core.events._startGame_setHard;
core.events._startGame_setHard = function () {
_startGame_setHard.call(core.events);
core.initHeros();
};
// 切换角色
// 可以使用 core.changeHero() 来切换到下一个角色
// 也可以 core.changeHero(1) 来切换到某个角色默认角色为0
this.changeHero = function (toHeroId) {
var currHeroId = core.getFlag("heroId", 0); // 获得当前角色ID
if (toHeroId == null) {
toHeroId = (currHeroId + 1) % heroCount;
}
if (currHeroId == toHeroId) return;
var saveList = Object.keys(hero1);
// 保存当前内容
var toSave = {};
// 暂时干掉 drawTip 和 音效,避免切装时的提示
var _drawTip = core.ui.drawTip;
core.ui.drawTip = function () {};
var _playSound = core.control.playSound;
core.control.playSound = function () {};
// 记录当前录像,因为可能存在换装问题
core.clearRouteFolding();
var routeLength = core.status.route.length;
// 优先判定装备
if (hero1.equipment) {
core.items.quickSaveEquip(100 + currHeroId);
core.items.quickLoadEquip(99);
}
saveList.forEach(function (name) {
if (name == "floorId")
toSave[name] = core.status.floorId; // 楼层单独设置
else if (name == "items") {
toSave.items = core.clone(core.status.hero.items);
Object.keys(toSave.items).forEach(function (one) {
if (!hero1.items[one]) delete toSave.items[one];
});
} else toSave[name] = core.clone(core.status.hero[name]); // 使用core.clone()来创建新对象
});
core.setFlag("hero" + currHeroId, toSave); // 将当前角色信息进行保存
var data = core.getFlag("hero" + toHeroId); // 获得要切换的角色保存内容
// 设置角色的属性值
saveList.forEach(function (name) {
if (name == "floorId");
else if (name == "items") {
Object.keys(core.status.hero.items).forEach(function (one) {
if (data.items[one])
core.status.hero.items[one] = core.clone(data.items[one]);
});
} else {
core.status.hero[name] = core.clone(data[name]);
}
});
// 最后装上装备
if (hero1.equipment) {
core.items.quickLoadEquip(100 + toHeroId);
}
core.ui.drawTip = _drawTip;
core.control.playSound = _playSound;
core.status.route = core.status.route.slice(0, routeLength);
core.control._bindRoutePush();
// 插入事件:改变角色行走图并进行楼层切换
var toFloorId = data.floorId || core.status.floorId;
var toLoc = data.loc || core.status.hero.loc;
core.insertAction([
2025-01-05 00:03:41 +08:00
{ type: "setHeroIcon", name: data.image || "hero.webp" }, // 改变行走图
2024-12-27 11:55:28 +08:00
// 同层则用changePos不同层则用changeFloor这是为了避免共用楼层造成触发eachArrive
toFloorId != core.status.floorId
? {
type: "changeFloor",
floorId: toFloorId,
loc: [toLoc.x, toLoc.y],
direction: toLoc.direction,
time: 0, // 可以在这里设置切换时间
}
: {
type: "changePos",
loc: [toLoc.x, toLoc.y],
direction: toLoc.direction,
},
// 你还可以在这里执行其他事件,比如增加或取消跟随效果
]);
core.setFlag("heroId", toHeroId); // 保存切换到的角色ID
};
},
2025-01-18 17:49:44 +08:00
"heroFourFrames": function () {
2024-12-27 11:55:28 +08:00
// 样板的勇士/跟随者移动时只使用2、4两帧观感较差。本插件可以将四帧全用上。
// 是否启用本插件
var __enable = true;
if (!__enable) return;
["up", "down", "left", "right"].forEach(function (one) {
// 指定中间帧动画
core.material.icons.hero[one].midFoot = 2;
});
var heroMoving = function (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;
};
},
2025-01-18 17:49:44 +08:00
"routeFixing": function () {
2024-12-27 11:55:28 +08:00
// 是否开启本插件true 表示启用false 表示禁用。
var __enable = true;
if (!__enable) return;
/*
使用说明启用本插件后录像回放时您可以用数字键1或6分别切换到原速或24倍速
暂停播放时按数字键7电脑按N可以单步播放手机端可以点击难度单词切换出数字键
数字键2-5可以进行录像自助精修具体描述见下实际弹窗请求您输入时不要带有任何空格
up down left right 勇士向某个方向行走一步或撞击
item:ID 使用某件道具 item:bomb 表示使用炸弹
unEquip:n 卸掉身上第(n+1)件装备n从0开始 unEquip:1 默认表示卸掉盾牌
equip:ID 穿上某件装备 equip:sword1 表示装上铁剑
saveEquip:n 将身上的当前套装保存到第n套快捷套装n从0开始
loadEquip:n 快捷换上之前保存好的第n套套装
fly:ID 使用楼传飞到某一层 fly:MT10 表示飞到主塔10层
choices:none 确认框/选择项超时作者未设置超时时间则此项视为缺失
choices:n 确认框/选择项选择第(n+1)选择项n从0开始确认框n为0表示确定1表示取消
选择项n为负数时表示选择倒数第 -n -1 表示最后一项V2.8.2起标准全局商店的离开
此项缺失的话确认框将选择作者指定的默认项初始光标位置选择项将弹窗请求补选后台录像验证中选最后一项可以复写函数来修改
shop:ID 打开某个全局商店 shop:itemShop 表示打开道具商店因此连载塔千万不要中途修改商店ID
turn 单击勇士Z键转身core.turnHero() 会产生此项因此通过事件等方式强制让勇士转向应该用 core.setHeroLoc()
turn:dir 勇士转向某个方向dir 可以为 up down left right此项一般是读取自动存档产生的属于样板的不良特性请勿滥用
getNext 轻按获得身边道具优先获得面前的面前没有则按上下左右顺序依次获得身边如果没有道具则此项会被跳过
input:none 等待用户操作事件中超时作者未设置超时时间则此项会导致报错
input:xxx 可能表示等待用户操作事件的一个操作如按键操作将直接记录 input:keycode
也可能表示一个接受用户输入数字的输入后者的情况下 xxx 为输入的整数此项缺失的话前者将直接报错后者将用0代替后者现在支持负数了
input2:xxx 可能表示读取全局存储core.getGlobal读取到的值也可能表示一个接受用户输入文本的输入
两种情况下 xxx 都为 base64 编码此项缺失的话前者将重新现场读取后者将用空字符串代替
no 走到可穿透的楼梯上不触发楼层切换事件通过本插件可以让勇士停在旁边没有障碍物的楼梯上哦
move:x:y 尝试瞬移到 [x,y] 不改变朝向该点甚至可以和勇士相邻或者位于视野外
key:n 松开键值为n的键 key:49 表示松开大键盘数字键1默认会触发使用破墙镐
click:n:px:py 点击自绘状态栏n为0表示横屏1表示竖屏[px,py] 为点击的像素坐标
random:n 生成了随机数n core.rand2(num) 的返回结果n必须在 [0,num-1] 范围num必须为正整数此项缺失将导致现场重新随机生成数值可能导致回放结果不一致
作者自定义的新项一般为js对象可以先JSON.stringify()再core.encodeBase64()得到纯英文数字的内容需要用(半角圆括弧)括起来
当您使用数字键5将一些项追加到即将播放内容的开头时请注意要逆序逐项追加或者每追加一项就按下数字键7或字母键N单步播放一步
但是input input2 random choices是被动读取的单步播放如果触发了相应的事件就会连续读取这时候只能提前逐项追加好
电脑端熟练以后推荐直接在控制台操作 core.status.route core.status.replay.toReplay后者录像回放时才有配合 core.push() core.unshift() 更加灵活自由哦
*/
2024-12-27 11:55:28 +08:00
core.actions.registerAction(
"onkeyUp",
"_sys_onkeyUp_replay",
function (e) {
if (this._checkReplaying()) {
if (e.keyCode == 27)
// ESCAPE
core.stopReplay();
else if (e.keyCode == 90)
// Z
core.speedDownReplay();
else if (e.keyCode == 67)
// C
core.speedUpReplay();
else if (e.keyCode == 32)
// SPACE
core.triggerReplay();
else if (e.keyCode == 65)
// A
core.rewindReplay();
else if (e.keyCode == 83)
// S
core.control._replay_SL();
else if (e.keyCode == 88)
// X
core.control._replay_book();
else if (e.keyCode == 33 || e.keyCode == 34)
// PgUp/PgDn
core.control._replay_viewMap();
else if (e.keyCode == 78)
// N
core.stepReplay();
else if (e.keyCode == 84)
// T
core.control._replay_toolbox();
else if (e.keyCode == 81)
// Q
core.control._replay_equipbox();
else if (e.keyCode == 66)
// B
core.ui._drawStatistics();
else if (e.keyCode == 49 || e.keyCode == 54)
// 1/6原速/24倍速播放
core.setReplaySpeed(e.keyCode == 49 ? 1 : 24);
else if (e.keyCode > 49 && e.keyCode < 54) {
// 2-5录像精修
switch (e.keyCode - 48) {
case 2: // pop
alert(
"您已移除已录制内容的最后一项:" + core.status.route.pop()
);
break;
case 3: // push
core.utils.myprompt(
"请输入您要追加到已录制内容末尾的项:",
"",
function (value) {
if (value != null) core.status.route.push(value);
}
);
break;
case 4: // shift
alert(
"您已移除即将播放内容的第一项:" +
core.status.replay.toReplay.shift()
);
break;
case 5: // unshift
core.utils.myprompt(
"请输入您要追加到即将播放内容开头的项:",
"",
function (value) {
if (value != null)
core.status.replay.toReplay.unshift(value);
}
);
}
}
return true;
}
},
100
);
},
2025-01-18 17:49:44 +08:00
"numpad": function () {
2024-12-27 11:55:28 +08:00
// 样板自带的整数输入事件为白屏弹窗且可以误输入任意非法内容但不支持负整数观感较差。本插件可以将其美化成仿RM样式使其支持负整数同时带有音效
// 另一方面4399等第三方平台不允许使用包括 core.myprompt() 和 core.myconfirm() 在内的弹窗,因此也需要此插件来替代,不然类似生命魔杖的道具就不好实现了
// 关于负整数输入V2.8.2原生支持其录像的压缩和解压,只是默认的 core.events._action_input() 函数将负数取了绝对值,可以只复写下面的 core.isReplaying() 部分来取消
// 是否启用本插件false表示禁用true表示启用
var __enable = true;
if (!__enable) return;
core.events._action_input = function (data, x, y, prefix) {
// 复写整数输入事件
if (core.isReplaying()) {
// 录像回放时,处理方式不变,但增加负整数支持
core.events.__action_getInput(
core.replaceText(data.text, prefix),
false,
function (value) {
value = parseInt(value) || 0; // 去掉了取绝对值的步骤
core.status.route.push("input:" + value);
core.setFlag("input", value);
core.doAction();
}
);
} else {
// 正常游戏中,采用暂停录制的方式然后用事件流循环“绘制-等待-变量操作”三板斧实现按照13*13适配的
// 您可以自行修改循环内的内容来适配15*15或其他需求或干脆作为公共事件编辑。
core.insertAction(
[
// 记录当前录像长度,下面的循环结束后裁剪。达到“暂停录制”的效果
{
type: "function",
function:
"function(){flags['@temp@length']=core.status.route.length}",
},
{ type: "setValue", name: "flag:input", value: "0" },
{
type: "while",
condition: "true",
data: [
{
type: "drawBackground",
2025-01-05 00:03:41 +08:00
background: "winskin.webp",
2024-12-27 11:55:28 +08:00
x: 16,
y: 16,
width: 384,
height: 384,
},
{ type: "drawIcon", id: "X10181", x: 32, y: 288 },
{ type: "drawIcon", id: "X10185", x: 64, y: 288 },
{ type: "drawIcon", id: "X10186", x: 96, y: 288 },
{ type: "drawIcon", id: "X10187", x: 128, y: 288 },
{ type: "drawIcon", id: "X10188", x: 160, y: 288 },
{ type: "drawIcon", id: "X10189", x: 192, y: 288 },
{ type: "drawIcon", id: "X10193", x: 224, y: 288 },
{ type: "drawIcon", id: "X10194", x: 256, y: 288 },
{ type: "drawIcon", id: "X10195", x: 288, y: 288 },
{ type: "drawIcon", id: "X10196", x: 320, y: 288 },
{ type: "drawIcon", id: "X10197", x: 352, y: 288 },
{ type: "drawIcon", id: "X10286", x: 32, y: 352 },
{ type: "drawIcon", id: "X10169", x: 96, y: 352 },
{ type: "drawIcon", id: "X10232", x: 128, y: 352 },
{ type: "drawIcon", id: "X10185", x: 320, y: 352 },
{ type: "drawIcon", id: "X10242", x: 352, y: 352 },
{
2024-12-28 23:53:21 +08:00
type: "fillBoldText ",
2024-12-27 11:55:28 +08:00
x: 48,
y: 256,
style: [255, 255, 255, 1],
font: "bold 32px Consolas",
text: "${flag:input}",
},
{
type: "fillBoldText",
x: 32,
y: 48,
style: [255, 255, 255, 1],
font: "16px Consolas",
text: core.replaceText(data.text, prefix),
},
{
type: "wait",
forceChild: true,
data: [
{
case: "keyboard",
keycode: "48,49,50,51,52,53,54,55,56,57",
action: [
// 按下数字键追加到已输入内容的末尾但禁止越界。变量keycode-48就是末位数字
{ type: "playSound", name: "光标移动" },
{
type: "if",
condition: "(flag:input<0)",
true: [
{
type: "setValue",
name: "flag:input",
value: "10*flag:input-(flag:keycode-48)",
},
],
false: [
{
type: "setValue",
name: "flag:input",
value: "10*flag:input+(flag:keycode-48)",
},
],
},
{
type: "setValue",
name: "flag:input",
value: "core.clamp(flag:input,-9e15,9e15)",
},
],
},
{
case: "keyboard",
keycode: "189",
action: [
// 按下减号键,变更已输入内容的符号
{ type: "playSound", name: "跳跃" },
{
type: "setValue",
name: "flag:input",
value: "-flag:input",
},
],
},
{
case: "keyboard",
keycode: "8",
action: [
// 按下退格键,从已输入内容的末尾删除一位
{ type: "playSound", name: "取消" },
{
type: "setValue",
name: "flag:input",
operator: "//=",
value: "10",
},
],
},
{
case: "keyboard",
keycode: "27",
action: [
// 按下ESC键清空已输入内容
{ type: "playSound", name: "读档" },
{ type: "setValue", name: "flag:input", value: "0" },
],
},
{
case: "keyboard",
keycode: "13",
action: [
// 按下回车键,确定
{ type: "break", n: 1 },
],
},
{
case: "mouse",
px: [32, 63],
py: [288, 320],
action: [
// 点击减号变号。右边界写63防止和下面重叠
{ type: "playSound", name: "跳跃" },
{
type: "setValue",
name: "flag:input",
value: "-flag:input",
},
],
},
{
case: "mouse",
px: [64, 384],
py: [288, 320],
action: [
// 点击数字追加到已输入内容的末尾但禁止越界。变量x-2就是末位数字
{ type: "playSound", name: "光标移动" },
{
type: "if",
condition: "(flag:input<0)",
true: [
{
type: "setValue",
name: "flag:input",
value: "10*flag:input-(flag:x-2)",
},
],
false: [
{
type: "setValue",
name: "flag:input",
value: "10*flag:input+(flag:x-2)",
},
],
},
{
type: "setValue",
name: "flag:input",
value: "core.clamp(flag:input,-9e15,9e15)",
},
],
},
{
case: "mouse",
px: [32, 64],
py: [352, 384],
action: [
// 点击左箭头,退格
{ type: "playSound", name: "取消" },
{
type: "setValue",
name: "flag:input",
operator: "//=",
value: "10",
},
],
},
{
case: "mouse",
px: [96, 160],
py: [352, 384],
action: [
// 点击CE清空
{ type: "playSound", name: "读档" },
{ type: "setValue", name: "flag:input", value: "0" },
],
},
{
case: "mouse",
px: [320, 384],
py: [352, 384],
action: [
// 点击OK确定
{ type: "break", n: 1 },
],
},
],
},
],
},
{ type: "clearMap" },
// 裁剪录像,只保留'input:n',然后继续录制
{
type: "function",
function:
"function(){core.status.route.splice(flags['@temp@length']);core.status.route.push('input:'+core.getFlag('input',0))}",
},
],
x,
y
);
core.events.doAction();
}
};
},
2025-01-18 17:49:44 +08:00
"sprites": function () {
2024-12-27 11:55:28 +08:00
// 基于canvas的sprite化摘编整理自万宁魔塔
//
// ---------------------------------------- 第一部分 js代码 (必装) --------------------------------------- //
/* ---------------- ---------------- *
* 1. 创建sprite: var sprite = new Sprite(x, y, w, h, z, reference, name);
* 其中x y w h为画布的横纵坐标及长宽reference为参考系只能填game相对于游戏画面和window相对于窗口
* 且当为相对游戏画面时长宽与坐标将会乘以放缩比例相当于用createCanvas创建
* z为纵深表示不同元素之间的覆盖关系大的覆盖小的
* name为自定义名称可以不填
* 2. 删除: sprite.destroy();
* 3. 设置css特效: sprite.setCss(css);
* 其中css直接填 box-shadow: 0px 0px 10px black;的形式即可与style标签与css文件内写法相同
* 对于已设置的特效如果之后不需要再次设置可以不填
* 4. 添加事件监听器: sprite.addEventListener(); 用法与html元素的addEventListener完全一致
* 5. 移除事件监听器: sprite.removeEventListener(); 用法与html元素的removeEventListener完全一致
* 6. 属性列表
* (1) sprite.x | sprite.y | sprite.width | sprite.height | sprite.zIndex | sprite.reference 顾名思义
* (2) sprite.canvas 该sprite的画布
* (3) sprite.context 该画布的CanvasRenderingContext2d对象即样板中常见的ctx
* (4) sprite.count 不要改这个玩意
* 7. 使用样板api进行绘制
* 示例
* var ctx = sprite.context;
* core.fillText(ctx, 'xxx', 100, 100);
* core.fillRect(ctx, 0, 0, 50, 50);
* 当然也可以使用原生js
* ctx.moveTo(0, 0);
* ctx.bezierCurveTo(50, 50, 100, 0, 100, 50);
* ctx.stroke();
* ---------------- 用法说明 ---------------- */
var count = 0;
/** sprite
* @param {number} x
* @param {number} y
* @param {number} w
* @param {number} h
* @param {number} z
* @param {'game' | 'window'} reference 参考系游戏画面或者窗口
* @param {string} name 可选sprite的名称方便通过core.dymCanvas获取
*/
function Sprite(x, y, w, h, z, reference, name) {
this.x = x;
this.y = y;
this.width = w;
this.height = h;
this.zIndex = z;
this.reference = reference;
this.canvas = null;
this.context = null;
this.count = 0;
this.name = name || "_sprite_" + count;
this.style = null;
/** 初始化 */
this.init = function () {
if (reference === "window") {
var canvas = document.createElement("canvas");
this.canvas = canvas;
this.context = canvas.getContext("2d");
canvas.width = w;
canvas.height = h;
canvas.style.width = w + "px";
canvas.style.height = h + "px";
canvas.style.position = "absolute";
canvas.style.top = y + "px";
canvas.style.left = x + "px";
canvas.style.zIndex = z.toString();
document.body.appendChild(canvas);
this.style = canvas.style;
} else {
this.context = core.createCanvas(
this.name || "_sprite_" + count,
x,
y,
w,
h,
z
);
this.canvas = this.context.canvas;
this.canvas.style.pointerEvents = "auto";
this.style = this.canvas.style;
}
this.count = count;
count++;
};
this.init();
/** css
* @param {string} css
*/
this.setCss = function (css) {
css = css.replace("\n", ";").replace(";;", ";");
var effects = css.split(";");
var self = this;
effects.forEach(function (v) {
var content = v.split(":");
var name = content[0];
var value = content[1];
name = name
.trim()
.split("-")
.reduce(function (pre, curr, i, a) {
if (i === 0 && curr !== "") return curr;
if (a[0] === "" && i === 1) return curr;
return pre + curr.toUpperCase()[0] + curr.slice(1);
}, "");
var canvas = self.canvas;
if (name in canvas.style) canvas.style[name] = value;
});
return this;
};
/**
* 移动sprite
* @param {boolean} isDelta 是否是相对位置如果是那么sprite会相对于原先的位置进行移动
*/
this.move = function (x, y, isDelta) {
if (x !== undefined && x !== null) this.x = x;
if (y !== undefined && y !== null) this.y = y;
if (this.reference === "window") {
var ele = this.canvas;
ele.style.left =
x + (isDelta ? parseFloat(ele.style.left) : 0) + "px";
ele.style.top = y + (isDelta ? parseFloat(ele.style.top) : 0) + "px";
} else core.relocateCanvas(this.context, x, y, isDelta);
return this;
};
/**
* 重新设置sprite的大小
* @param {boolean} styleOnly 是否只修改css效果如果是那么将会不高清如果不是那么会清空画布
*/
this.resize = function (w, h, styleOnly) {
if (w !== undefined && w !== null) this.w = w;
if (h !== undefined && h !== null) this.h = h;
if (reference === "window") {
var ele = this.canvas;
ele.style.width = w + "px";
ele.style.height = h + "px";
if (!styleOnly) {
ele.width = w;
ele.height = h;
}
} else core.resizeCanvas(this.context, w, h, styleOnly);
return this;
};
/**
* 旋转画布
*/
this.rotate = function (angle, cx, cy) {
if (this.reference === "window") {
var left = this.x;
var top = this.y;
this.canvas.style.transformOrigin =
cx - left + "px " + (cy - top) + "px";
if (angle === 0) {
canvas.style.transform = "";
} else {
canvas.style.transform = "rotate(" + angle + "deg)";
}
} else {
core.rotateCanvas(this.context, angle, cx, cy);
}
return this;
};
/**
* 清除sprite
*/
this.clear = function (x, y, w, h) {
if (this.reference === "window") {
this.context.clearRect(x, y, w, h);
} else {
core.clearMap(this.context, x, y, w, h);
}
return this;
};
/** 删除 */
this.destroy = function () {
if (this.reference === "window") {
if (this.canvas) document.body.removeChild(this.canvas);
} else {
core.deleteCanvas(this.name || "_sprite_" + this.count);
}
};
/** 添加事件监听器 */
this.addEventListener = function () {
this.canvas.addEventListener.apply(this.canvas, arguments);
};
/** 移除事件监听器 */
this.removeEventListener = function () {
this.canvas.removeEventListener.apply(this.canvas, arguments);
};
}
window.Sprite = Sprite;
},
2025-01-18 17:49:44 +08:00
"hotReload": function () {
2024-12-27 11:55:28 +08:00
/* ---------- ---------- *
1. libs/ main.js index.html 中的任意一个文件被更改后会自动刷新塔的页面
2. 修改楼层文件后自动在塔的页面上显示出来不需要刷新
3. 修改脚本编辑或插件编写后也能自动更新更改的插件或脚本但不保证不会出问题一般都不会有问题的
4. 修改图块属性怪物属性等后会自动更新
5. 当全塔属性被修改时会自动刷新塔的页面
6. 样板的 styles.css 被修改后也可以直接显示不需要刷新
7. 其余内容修改后不会自动更新也不会刷新
/* ---------- 使 ---------- *
1. 前往 https://nodejs.org/en/ 下载node.js的LTS版本点左边那个绿色按钮并安装
2. 将该插件复制到插件编写中
3. 在造塔群的群文件-魔塔样板·改中找到server.js下载并放到塔的根目录与启动服务同一级
4. 在该目录下按下shift+鼠标右键win11只按右键即可选择在终端打开或在powershell打开
5. 运行node server.js即可
*/
2024-12-27 11:55:28 +08:00
if (main.mode !== "play" || main.replayChecking) return;
/**
* 发送请求
* @param {string} url
* @param {string} type
* @param {string} data
* @returns {Promise<string>}
*/
async function post(url, type, data) {
const xhr = new XMLHttpRequest();
xhr.open(type, url);
xhr.send(data);
const res = await new Promise((res) => {
xhr.onload = (e) => {
if (xhr.status !== 200) {
console.error(`hot reload: http ${xhr.status}`);
res("@error");
} else res("success");
};
xhr.onerror = (e) => {
res("@error");
console.error(`hot reload: error on connection`);
};
});
if (res === "success") return xhr.response;
else return "@error";
}
/**
* 热重载css
* @param {string} data
*/
function reloadCss(data) {
const all = Array.from(document.getElementsByTagName("link"));
all.forEach((v) => {
if (v.rel !== "stylesheet") return;
if (v.href === `http://127.0.0.1:3000/${data}`) {
v.remove();
const link = document.createElement("link");
link.rel = "stylesheet";
link.type = "text/css";
link.href = data;
document.head.appendChild(link);
console.log(`css hot reload: ${data}`);
}
});
}
/**
* 热重载楼层
* @param {string} data
*/
async function reloadFloor(data) {
// 首先重新加载main.floors对应的楼层
await import(`/project/floors/${data}.js?v=${Date.now()}`);
// 然后写入core.floors并解析
core.floors[data] = main.floors[data];
const floor = core.loadFloor(data);
if (core.isPlaying()) {
core.status.maps[data] = floor;
delete core.status.mapBlockObjs[data];
core.extractBlocks(data);
if (data === core.status.floorId) {
core.drawMap(data);
core.setWeather(
core.animateFrame.weather.type,
core.animateFrame.weather.level
);
}
core.updateStatusBar(true, true);
}
console.log(`floor hot reload: ${data}`);
}
/**
* 热重载脚本编辑及插件编写
* @param {string} data
*/
async function reloadScript(data) {
if (data === "plugins") {
// 插件编写比较好办
const before = plugins_bb40132b_638b_4a9f_b028_d3fe47acc8d1;
// 这里不能用动态导入,因为动态导入会变成模块,变量就不是全局的了
const script = document.createElement("script");
script.src = `/project/plugins.js?v=${Date.now()}`;
document.body.appendChild(script);
await new Promise((res) => {
script.onload = () => res("success");
});
const after = plugins_bb40132b_638b_4a9f_b028_d3fe47acc8d1;
// 找到差异的函数
for (const id in before) {
const fn = before[id];
if (typeof fn !== "function") continue;
if (fn.toString() !== after[id]?.toString()) {
try {
core.plugin[id] = after[id];
core.plugin[id].call(core.plugin);
core.updateStatusBar(true, true);
console.log(`plugin hot reload: ${id}`);
} catch (e) {
console.error(e);
}
}
}
} else if (data === "functions") {
// 脚本编辑略微麻烦点
const before = functions_d6ad677b_427a_4623_b50f_a445a3b0ef8a;
// 这里不能用动态导入,因为动态导入会变成模块,变量就不是全局的了
const script = document.createElement("script");
script.src = `/project/functions.js?v=${Date.now()}`;
document.body.appendChild(script);
await new Promise((res) => {
script.onload = () => res("success");
});
const after = functions_d6ad677b_427a_4623_b50f_a445a3b0ef8a;
// 找到差异的函数
for (const mod in before) {
const fns = before[mod];
for (const id in fns) {
const fn = fns[id];
if (typeof fn !== "function" || id === "hasSpecial") continue;
const now = after[mod][id];
if (fn.toString() !== now.toString()) {
try {
if (mod === "events") {
core.events.eventdata[id] = now;
} else if (mod === "enemys") {
core.enemys.enemydata[id] = now;
} else if (mod === "actions") {
core.actions.actionsdata[id] = now;
} else if (mod === "control") {
core.control.controldata[id] = now;
} else if (mod === "ui") {
core.ui.uidata[id] = now;
}
core.updateStatusBar(true, true);
console.log(`function hot reload: ${mod}.${id}`);
} catch (e) {
console.error(e);
}
}
}
}
}
}
/**
* 属性热重载包括全塔属性等
* @param {string} data
*/
async function reloadData(data) {
const script = document.createElement("script");
script.src = `/project/${data}.js?v=${Date.now()}`;
document.body.appendChild(script);
await new Promise((res) => {
script.onload = () => res("success");
});
let after;
if (data === "data") after = data_a1e2fb4a_e986_4524_b0da_9b7ba7c0874d;
if (data === "enemys")
after = enemys_fcae963b_31c9_42b4_b48c_bb48d09f3f80;
if (data === "icons") after = icons_4665ee12_3a1f_44a4_bea3_0fccba634dc1;
if (data === "items") after = items_296f5d02_12fd_4166_a7c1_b5e830c9ee3a;
if (data === "maps") after = maps_90f36752_8815_4be8_b32b_d7fad1d0542e;
if (data === "events")
after = events_c12a15a8_c380_4b28_8144_256cba95f760;
if (data === "enemys") {
core.enemys.enemys = after;
for (var enemyId in after) {
core.enemys.enemys[enemyId].id = enemyId;
}
core.material.enemys = core.getEnemys();
} else if (data === "icons") {
core.icons.icons = after;
core.material.icons = core.getIcons();
} else if (data === "items") {
core.items.items = after;
for (var itemId in after) {
core.items.items[itemId].id = itemId;
}
core.material.items = core.getItems();
} else if (data === "maps") {
core.maps.blocksInfo = after;
core.status.mapBlockObjs = {};
core.status.number2block = {};
Object.values(core.status.maps).forEach((v) => delete v.blocks);
core.extractBlocks();
core.setWeather(
core.animateFrame.weather.type,
core.animateFrame.weather.level
);
core.drawMap();
} else if (data === "events") {
core.events.commonEvent = after.commonEvent;
} else if (data === "data") {
location.reload();
}
core.updateStatusBar(true, true);
console.log(`data hot reload: ${data}`);
}
// 初始化
(async function () {
const data = await post("/reload", "POST", "test");
if (data === "@error") {
console.log(`未检测到node服务热重载插件将无法使用`);
} else {
console.log(`热重载插件加载成功`);
// reload
setInterval(async () => {
const res = await post("/reload", "POST");
if (res === "@error") return;
if (res === "true") location.reload();
else return;
}, 1000);
// hot reload
setInterval(async () => {
const res = await post("/hotReload", "POST");
const data = res.split("@@");
data.forEach((v) => {
if (v === "") return;
const [type, file] = v.split(":");
if (type === "css") reloadCss(file);
if (type === "data") reloadData(file);
if (type === "floor") reloadFloor(file);
if (type === "script") reloadScript(file);
});
}, 1000);
}
})();
},
2025-01-18 17:49:44 +08:00
"statusBar": function () {
2025-01-16 13:17:19 +08:00
main.dom.floorMsgGroup.style.display = "none";
main.dom.statusBar.style.display = "none";
main.dom.toolBar.style.display = "none";
//所有数据*3是为了实现高清画布
const GAMEVIEW_WIDTH = 676 * 3; //横屏画面宽度
const GAMEVIEW_HEIGHT = 416 * 3; //横屏画面高度
const GAMEVIEW_WIDTH_VERTICAL = 416 * 3; //竖屏画面宽度
const GAMEVIEW_HEIGHT_VERTICAL = 676 * 3; //竖屏画面高度
const BAR_WIDTH = 130 * 3; //横屏左侧额外距离(即边栏宽度)
const BAR_HEIGHT_VERTICAL = 130 * 3; //竖屏上侧额外距离(即边栏高度)
const BORDER_WIDTH = 0; //游戏画面左侧偏移距离
const BORDER_HEIGHT = 0; //游戏画面上侧偏移距离
const ITEM_BOX_LEFT = 549 * 3; //横屏道具栏左侧距离右侧边栏需增加BAR_WIDTH+GAMEVIEW_HEIGHT
const ITEM_BOX_TOP = 155 * 3; //横屏道具栏上侧距离
const ITEM_BOX_LEFT_VERTICAL = 160 * 3; //竖屏道具栏左侧距离
const ITEM_BOX_TOP_VERTICAL = 549 * 3; //竖屏道具栏上侧距离下侧边栏需增加BAR_HEIGHT_VERTICAL+GAMEVIEW_WIDTH_VERTICAL
const EQUIP_BLOCK_LEFT = 549 * 3; //横屏装备栏左侧距离右侧边栏需增加BAR_WIDTH+GAMEVIEW_HEIGHT
const EQUIP_BLOCK_TOP = 10 * 3; //横屏装备栏上侧距离
const EQUIP_BLOCK_LEFT_VERTICAL = 10 * 3; //竖屏装备栏左侧距离
const EQUIP_BLOCK_TOP_VERTICAL = 549 * 3; //竖屏装备栏上侧距离下侧边栏需增加BAR_HEIGHT_VERTICAL+GAMEVIEW_WIDTH_VERTICAL
const MAP_BLOCK_LEFT = 551 * 3; //横屏小地图左侧距离右侧边栏需增加BAR_WIDTH+GAMEVIEW_HEIGHT
const MAP_BLOCK_TOP = 0; //横屏小地图上侧距离
const MAP_BLOCK_LEFT_VERTICAL = 0; //竖屏小地图左侧距离
const MAP_BLOCK_TOP_VERTICAL = 551 * 3; //竖屏小地图上侧距离下侧边栏需增加BAR_HEIGHT_VERTICAL+GAMEVIEW_WIDTH_VERTICAL
const KEY_BLOCK_LEFT = EQUIP_BLOCK_LEFT; //横屏钥匙栏左侧距离右侧边栏需增加BAR_WIDTH+GAMEVIEW_HEIGHT
const KEY_BLOCK_TOP = 110 * 3; //横屏钥匙栏上侧距离
const KEY_BLOCK_LEFT_VERTICAL = 110 * 3; //竖屏钥匙栏左侧距离
const KEY_BLOCK_TOP_VERTICAL = EQUIP_BLOCK_TOP_VERTICAL; //竖屏钥匙栏上侧距离下侧边栏需增加BAR_HEIGHT_VERTICAL+GAMEVIEW_WIDTH_VERTICAL
const INFO_BLOCK_LEFT = 10 * 3; //横屏道具说明左侧距离右侧边栏需增加BAR_WIDTH+GAMEVIEW_HEIGHT
const INFO_BLOCK_TOP = 180 * 3; //横屏道具说明上侧距离
const INFO_BLOCK_LEFT_VERTICAL = 113 * 3; //竖屏道具说明左侧距离
const INFO_BLOCK_TOP_VERTICAL = 8 * 3; //竖屏道具说明上侧距离下侧边栏需增加BAR_HEIGHT_VERTICAL+GAMEVIEW_WIDTH_VERTICAL
const TOOL_BOX_LEFT = EQUIP_BLOCK_LEFT; //横屏工具栏左侧距离右侧边栏需增加BAR_WIDTH+GAMEVIEW_HEIGHT
const TOOL_BOX_TOP = 348 * 3; //横屏工具栏上侧距离
const TOOL_BOX_LEFT_VERTICAL = 348 * 3; //竖屏工具栏左侧距离
const TOOL_BOX_TOP_VERTICAL = 549 * 3; //竖屏工具栏上侧距离下侧边栏需增加BAR_HEIGHT_VERTICAL+GAMEVIEW_WIDTH_VERTICAL
const TOOL_ICON_OUTER_SIZE = 34 * 3;
const TEXT_COLOR = "#FFFFFF"; //默认文字颜色
const globalAlpha = 0.7; //默认底框透明度
const FORCE_COUNTABLE_ITEMS = ["centerFly"]; //常态显示数量的非永久道具如果道具不在此数组中则只有道具多余1时显示数量
const outerBackground = document.createElement("canvas"); //背景画布设置
let globalAlphafloor = 0,
globalAlphafloorStatus = 4;
outerBackground.style.position = "absolute";
outerBackground.style.zIndex = 5;
outerBackground.id = "outerBackground";
main.dom.outerBackground = outerBackground;
main.dom.startPanel.insertAdjacentElement("afterend", outerBackground);
const outerUI = document.createElement("canvas"); //额外ui画布设置状态栏所有绘制、点击都在额外ui上
outerUI.style.position = "absolute";
outerUI.style.zIndex = 165;
outerUI.id = "outerUI";
main.dom.outerUI = outerUI;
outerBackground.insertAdjacentElement("afterend", outerUI);
setTimeout(function () {
// Should be executed immediately after init()
main.canvas.outerUI = outerUI.getContext("2d");
});
outerUI.onclick = function (e) {
try {
e.preventDefault();
if (!core.isPlaying()) return false;
const left = core.dom.gameGroup.offsetLeft;
const top = core.dom.gameGroup.offsetTop;
const px = Math.floor((e.clientX - left) / core.domStyle.scale),
py = Math.floor((e.clientY - top) / core.domStyle.scale);
core.ui.statusBar.onclick(px * 3, py * 3);
} catch (ee) {
main.log(ee);
}
2024-12-27 11:55:28 +08:00
};
2025-01-16 13:17:19 +08:00
const _resize_gameGroup = function (obj) {
//游戏画面自适应调节
const gameGroup = core.dom.gameGroup;
gameGroup.style.width = obj.totalWidth + "px";
gameGroup.style.height = obj.totalHeight + "px";
gameGroup.style.left = (obj.clientWidth - obj.totalWidth) / 2 + "px";
gameGroup.style.top = (obj.clientHeight - obj.totalHeight) / 2 + "px";
//floorMsgGroup为切换楼层中生效显示时间可通过全塔属性——切换楼层时间或游戏内设置调整
//显示内容为游戏名/版本号/楼层名
// floorMsgGroup
var floorMsgGroup = core.dom.floorMsgGroup;
var globalAttribute =
core.status.globalAttribute || core.initStatus.globalAttribute;
floorMsgGroup.style = globalAttribute.floorChangingStyle;
floorMsgGroup.style.height = floorMsgGroup.style.width =
(GAMEVIEW_HEIGHT / 3) * core.domStyle.scale + "px";
floorMsgGroup.style.fontSize = 16 * core.domStyle.scale + "px";
2024-12-27 20:18:22 +08:00
2025-01-16 13:17:19 +08:00
if (core.domStyle.isVertical) {
floorMsgGroup.style.left = "0px";
floorMsgGroup.style.top =
((GAMEVIEW_HEIGHT_VERTICAL / 3 - GAMEVIEW_WIDTH_VERTICAL / 3) *
core.domStyle.scale) /
2 +
"px";
} else {
floorMsgGroup.style.left =
((GAMEVIEW_WIDTH / 3 - GAMEVIEW_HEIGHT / 3) * core.domStyle.scale) /
2 +
"px";
floorMsgGroup.style.top = "0px";
}
core.dom.musicBtn.style.right =
(obj.clientWidth - obj.totalWidth) / 2 + "px";
core.dom.musicBtn.style.bottom =
(obj.clientHeight - obj.totalHeight) / 2 - 27 + "px";
let startBackground = core.domStyle.isVertical
? main.styles.startVerticalBackground || main.styles.startBackground
: main.styles.startBackground;
if (main.dom.startBackground.getAttribute("__src__") != startBackground) {
main.dom.startBackground.setAttribute("__src__", startBackground);
main.dom.startBackground.src = startBackground;
}
const span = document
.getElementById("startButtons")
.getElementsByTagName("span");
let font = (GAMEVIEW_WIDTH / 100) * core.domStyle.scale;
if (core.domStyle.isVertical)
font = ((GAMEVIEW_WIDTH_VERTICAL * 2) / 100) * core.domStyle.scale;
core.dom.playGame.style.fontSize = font + "px";
core.dom.loadGame.style.fontSize = font + "px";
core.dom.CGMode.style.fontSize = font + "px";
core.dom.musicMode.style.fontSize = font + "px";
core.dom.replayGame.style.fontSize = font + "px";
core.dom.startButtonGroup.style.padding = font * 0.3 + "px 25px";
};
const _resize_canvas = function (obj) {
//自适应画布
main.dom.outerBackground.style.width = obj.totalWidth + "px";
main.dom.outerBackground.style.height = obj.totalHeight + "px";
main.dom.outerUI.style.width = obj.totalWidth + "px";
main.dom.outerUI.style.height = obj.totalHeight + "px";
if (main.dom.CGUI) {
main.dom.CGUI.style.width = obj.totalWidth + 3 + "px";
main.dom.CGUI.style.height = obj.totalHeight + 3 + "px";
}
if (main.dom.music) {
main.dom.music.style.width = obj.totalWidth + 3 + "px";
main.dom.music.style.height = obj.totalHeight + 3 + "px";
}
if (main.dom.cgText) {
main.dom.cgText.style.width = obj.totalWidth + 3 + "px";
main.dom.cgText.style.height = obj.totalHeight + 3 + "px";
}
if (main.dom.over) {
main.dom.over.style.width = obj.totalWidth + 3 + "px";
main.dom.over.style.height = obj.totalHeight + 3 + "px";
}
if (main.dom.video) {
main.dom.video.style.width = obj.totalWidth + 3 + "px";
main.dom.video.style.height = obj.totalHeight + 3 + "px";
if (core.domStyle.isVertical)
main.dom.video.style.width = obj.totalHeight + 3 + "px";
if (core.domStyle.isVertical)
main.dom.video.style.height = obj.totalWidth + 3 + "px";
main.dom.video.style.top = "50%";
main.dom.video.style.left = "50%";
main.dom.video.style.transform = "translate(-50%,-50%)";
if (core.domStyle.isVertical)
main.dom.video.style.transform = "translate(-50%,-50%) rotate(90deg)";
}
if (main.dom.video1) {
main.dom.video1.style.width = obj.totalWidth + 3 + "px";
main.dom.video1.style.height = obj.totalHeight + 3 + "px";
}
const innerSize = obj.canvasWidth * core.domStyle.scale + "px";
for (let i = 0; i < core.dom.gameCanvas.length; ++i)
core.dom.gameCanvas[i].style.width = core.dom.gameCanvas[
i
].style.height = innerSize;
core.dom.gif.style.width = core.dom.gif.style.height = innerSize;
core.dom.gif2.style.width = core.dom.gif2.style.height = innerSize;
core.dom.gameDraw.style.width = core.dom.gameDraw.style.height =
innerSize;
core.dom.gameDraw.style.top =
obj.gameDrawBox.top * core.domStyle.scale + "px";
core.dom.gameDraw.style.left =
obj.gameDrawBox.left * core.domStyle.scale + "px";
// resize bigmap
core.bigmap.canvas.forEach(function (cn) {
const ratio = core.canvas[cn].canvas.hasAttribute("isHD")
? core.domStyle.ratio
: 1;
core.canvas[cn].canvas.style.width =
(innerSize / ratio) * core.domStyle.scale + "px";
core.canvas[cn].canvas.style.height =
(innerSize / ratio) * core.domStyle.scale + "px";
});
// resize dynamic canvas
for (const name in core.dymCanvas) {
const ctx = core.dymCanvas[name],
canvas = ctx.canvas;
const ratio = canvas.hasAttribute("isHD") ? core.domStyle.ratio : 1;
canvas.style.width = (innerSize / ratio) * core.domStyle.scale + "px";
canvas.style.height = (innerSize / ratio) * core.domStyle.scale + "px";
canvas.style.left =
parseFloat(canvas.getAttribute("_left")) * core.domStyle.scale + "px";
canvas.style.top =
parseFloat(canvas.getAttribute("_top")) * core.domStyle.scale + "px";
}
// resize next
main.dom.next.style.width = main.dom.next.style.height =
5 * core.domStyle.scale + "px";
main.dom.next.style.borderBottomWidth =
main.dom.next.style.borderRightWidth = 4 * core.domStyle.scale + "px";
};
const bgctx = main.dom.outerBackground.getContext("2d");
const uictx = main.dom.outerUI.getContext("2d");
let now = 0;
core.registerAnimationFrame("lightFloor", true, function (timestamp) {
if (timestamp - now > 1000 / 60) {
now = timestamp;
globalAlphafloor += globalAlphafloorStatus;
if (globalAlphafloor === 100) globalAlphafloorStatus = -2;
if (globalAlphafloor === 0) globalAlphafloorStatus = 2;
2025-01-05 00:03:41 +08:00
2025-01-16 13:17:19 +08:00
if (core.domStyle.isVertical) {
core.clearMap(
uictx,
MAP_BLOCK_LEFT_VERTICAL,
MAP_BLOCK_TOP_VERTICAL,
340,
360
);
if (core.status.event.id === "viewMaps") {
core.ui.statusBar._update_map(core.status.event.data.floorId);
} else {
core.ui.statusBar._update_map();
}
2025-01-05 00:03:41 +08:00
2025-01-16 13:17:19 +08:00
uictx.globalAlpha = globalAlphafloor / 100;
core.drawImage(
uictx,
"green.webp",
MAP_BLOCK_LEFT_VERTICAL + 135,
MAP_BLOCK_TOP_VERTICAL + 170
);
uictx.globalAlpha = 1;
} else {
core.clearMap(uictx, MAP_BLOCK_LEFT, MAP_BLOCK_TOP, 340, 360);
if (core.status.event.id === "viewMaps") {
core.ui.statusBar._update_map(core.status.event.data.floorId);
} else {
core.ui.statusBar._update_map();
}
uictx.globalAlpha = globalAlphafloor / 100;
core.drawImage(
uictx,
"green.webp",
MAP_BLOCK_LEFT + 150,
MAP_BLOCK_TOP + 180
);
uictx.globalAlpha = 1;
}
}
});
2025-01-05 00:03:41 +08:00
2025-01-16 13:17:19 +08:00
core.control.resize = function () {
//自适应,可实现横竖屏切换
if (main.mode == "editor") return;
2025-01-05 00:03:41 +08:00
2025-01-16 13:17:19 +08:00
const clientWidth = main.dom.body.clientWidth,
clientHeight = main.dom.body.clientHeight;
const canvasWidth = core.__PIXELS__;
2025-01-05 00:03:41 +08:00
2025-01-16 13:17:19 +08:00
const isVertical = clientHeight > clientWidth;
core.domStyle.isVertical = isVertical;
2025-01-05 00:03:41 +08:00
2025-01-16 13:17:19 +08:00
const totalWidth = isVertical
? GAMEVIEW_WIDTH_VERTICAL / 3
: GAMEVIEW_WIDTH / 3,
totalHeight = isVertical
? GAMEVIEW_HEIGHT_VERTICAL / 3
: GAMEVIEW_HEIGHT / 3;
2025-01-05 00:03:41 +08:00
2025-01-16 13:17:19 +08:00
const maxRatio = Math.min(
clientWidth / totalWidth,
clientHeight / totalHeight
2024-12-27 11:55:28 +08:00
);
2025-01-16 13:17:19 +08:00
core.domStyle.availableScale = [];
[1, 1.25, 1.5, 1.75, 2].forEach(function (v) {
if (maxRatio >= v) {
core.domStyle.availableScale.push(v);
}
});
2024-12-27 11:55:28 +08:00
2025-01-16 13:17:19 +08:00
if (core.domStyle.availableScale.indexOf(core.domStyle.scale) < 0) {
core.domStyle.scale = Math.min(1, maxRatio);
} else if (
core.getLocalStorage("scale") == null &&
core.domStyle.availableScale.length >= 2
) {
core.domStyle.scale =
core.domStyle.availableScale[core.domStyle.availableScale.length - 2];
core.setLocalStorage("scale", core.domStyle.scale);
2024-12-27 11:55:28 +08:00
}
2025-01-16 13:17:19 +08:00
const totalWidthScaled = totalWidth * core.domStyle.scale,
totalHeightScaled = totalHeight * core.domStyle.scale;
2025-01-05 00:03:41 +08:00
2025-01-16 13:17:19 +08:00
const gameDrawBox = isVertical
? {
left: BORDER_WIDTH / 3,
top: BAR_HEIGHT_VERTICAL / 3 + BORDER_HEIGHT / 3,
}
: { left: BAR_WIDTH / 3 + BORDER_WIDTH / 3, top: BORDER_HEIGHT / 3 };
const obj = {
clientWidth: clientWidth,
clientHeight: clientHeight,
canvasWidth: canvasWidth,
totalWidth: totalWidthScaled,
totalHeight: totalHeightScaled,
gameDrawBox: gameDrawBox,
globalAttribute:
core.status.globalAttribute || core.initStatus.globalAttribute,
};
2024-12-27 11:55:28 +08:00
2025-01-16 13:17:19 +08:00
_resize_gameGroup(obj);
_resize_canvas(obj);
2024-12-27 11:55:28 +08:00
2025-01-16 13:17:19 +08:00
if (core.status.automaticRoute == null) core.status.automaticRoute = {};
core.updateStatusBar();
if (main.dom.CGUI && main.dom.CGUI.style.display === "block")
core.ui.CG.update();
if (main.dom.music && main.dom.music.style.display === "block")
core.ui.music.update();
if (main.dom.cgText && main.dom.cgText.style.display === "block")
core.ui.cgText.update();
};
2024-12-27 11:55:28 +08:00
2025-01-16 13:17:19 +08:00
class StatusBar {
constructor() {
//道具栏列表
this.itemMx = [
//空位用none填充当前ui至多4列6行
["book", "wand", "none", "fly"],
["cross", "superPotion", "pickaxe"],
["bomb", "centerFly", "upFly"],
["none", "none", "none"],
["downFly", "knife", "snow"],
["bigKey", "earthquake", "coin"],
];
}
//初始化内容(工具栏/录像操作执行函数)
init() {
this.toolbarAction = [
[
main.core.openKeyBoard,
main.core.openQuickShop,
core.openToolbox,
core.doSL,
],
[main.core.openSettings, main.core.save, main.core.load, core.doSL],
];
this.replayAction = [
[core.triggerReplay, core.stopReplay, core.rewindReplay],
[core.speedDownReplay, core.speedUpReplay, core.saveReplay],
];
}
//更新
update() {
this._update_background(); //更新背景
this._update_props(); //更新属性
//this._update_items(); //更新道具
//this._update_equips(); //更新装备
//this._update_keys(); //更新钥匙
//this._update_infoWindow(); //更新道具说明
this._update_toolBox(); //更新工具栏
this._redrawMap();
}
_redrawMap() {
if (core.domStyle.isVertical) {
core.clearMap(
uictx,
MAP_BLOCK_LEFT_VERTICAL,
MAP_BLOCK_TOP_VERTICAL,
340,
360
2024-12-27 11:55:28 +08:00
);
2025-01-16 13:17:19 +08:00
this._update_map();
uictx.globalAlpha = globalAlphafloor / 100;
core.drawImage(
uictx,
"green.webp",
MAP_BLOCK_LEFT_VERTICAL + 125,
MAP_BLOCK_TOP_VERTICAL + 170
);
uictx.globalAlpha = 1;
} else {
core.clearMap(uictx, MAP_BLOCK_LEFT, MAP_BLOCK_TOP, 340, 360);
this._update_map();
uictx.globalAlpha = globalAlphafloor / 100;
core.drawImage(
uictx,
"green.webp",
MAP_BLOCK_LEFT + 150,
MAP_BLOCK_TOP + 170
);
uictx.globalAlpha = 1;
2024-12-27 11:55:28 +08:00
}
}
2025-01-16 13:17:19 +08:00
//更新背景
_update_background() {
if (core.domStyle.isVertical) {
bgctx.canvas.width = GAMEVIEW_WIDTH_VERTICAL;
bgctx.canvas.height = GAMEVIEW_HEIGHT_VERTICAL;
uictx.canvas.width = GAMEVIEW_WIDTH_VERTICAL;
uictx.canvas.height = GAMEVIEW_HEIGHT_VERTICAL;
const bg = core.material.images.images["status.webp"]; //竖屏背景(上)
bgctx.drawImage(
bg,
0,
0,
GAMEVIEW_WIDTH_VERTICAL,
BAR_HEIGHT_VERTICAL
2024-12-27 11:55:28 +08:00
);
2025-01-16 13:17:19 +08:00
const bg2 = core.material.images.images["status.webp"]; //竖屏背景(下)
bgctx.drawImage(
bg2,
0,
BAR_HEIGHT_VERTICAL + GAMEVIEW_WIDTH_VERTICAL,
GAMEVIEW_WIDTH_VERTICAL,
BAR_HEIGHT_VERTICAL
2024-12-27 11:55:28 +08:00
);
2025-01-16 13:17:19 +08:00
bgctx.globalAlpha = globalAlpha;
bgctx.globalAlpha = 1;
core.setTextAlign("outerUI", "center");
} else {
bgctx.canvas.width = GAMEVIEW_WIDTH;
bgctx.canvas.height = GAMEVIEW_HEIGHT;
uictx.canvas.width = GAMEVIEW_WIDTH;
uictx.canvas.height = GAMEVIEW_HEIGHT;
const bg = core.material.images.images["status.webp"]; //横屏背景(左)
bgctx.drawImage(bg, 0, 0, BAR_WIDTH, GAMEVIEW_HEIGHT);
const bg2 = core.material.images.images["status.webp"]; //横屏背景(右)
bgctx.drawImage(
bg2,
BAR_WIDTH + GAMEVIEW_HEIGHT,
0,
BAR_WIDTH,
GAMEVIEW_HEIGHT
);
bgctx.globalAlpha = globalAlpha;
2024-12-27 11:55:28 +08:00
2025-01-16 13:17:19 +08:00
bgctx.globalAlpha = 1;
core.setTextAlign("outerUI", "center");
}
2024-12-27 11:55:28 +08:00
}
2025-01-16 13:17:19 +08:00
// 更新属性
_update_props(updatedFloorTitle) {
if (!updatedFloorTitle && core.status.floorId) {
updatedFloorTitle = core.status.maps[core.status.floorId].title;
}
const statusList = ["hp", "atk", "def", "money"]; //属性列表图标在函数复写core.statusBar.icons中声明数字为project\materials\icons.png中的图标序号可使用便捷ps追加第一个序号为0
const drawStatusList = (baseX, baseY) => {
let curh = baseY;
core.setTextAlign("outerUI", "right");
statusList.forEach((item) => {
// 绘制图标
core.drawIcon(
"outerUI",
item,
baseX - 95 * 3,
curh - 18 * 3,
22 * 3,
22 * 3
);
2024-12-27 11:55:28 +08:00
2025-01-16 13:17:19 +08:00
// 四舍五入
core.status.hero[item] = Math.round(core.status.hero[item]);
// 大数据格式化
core.fillBoldText1(
"outerUI",
core.getRealStatus(item),
baseX,
curh,
TEXT_COLOR,
"#000000",
6
);
curh += 24 * 3;
if (curh > 130 * 3 && core.domStyle.isVertical) {
curh = 24 * 3;
baseX += 105 * 3;
}
});
core.setTextAlign("outerUI", "center");
};
if (core.domStyle.isVertical) {
core.clearMap("outerUI", 10 * 3, 0, 210 * 3, 120 * 3);
core.setFont("outerUI", "bold 42px Verdana");
if (updatedFloorTitle) {
core.fillBoldText1(
"outerUI",
updatedFloorTitle,
60 * 3,
22 * 3,
TEXT_COLOR,
"#000000",
6
);
2024-12-27 11:55:28 +08:00
}
2025-01-16 13:17:19 +08:00
//drawStatusList(96 * 3, 46 * 3);
//core.drawImage("outerUI", "lane1.png", 0, 0)
core.drawImage("outerUI", "cao.webp", 0, 0);
} else {
core.clearMap("outerUI", 10 * 3, 40 * 3, 105 * 3, 250 * 3);
core.setFont("outerUI", "bold 48px Verdana");
if (updatedFloorTitle) {
core.fillBoldText1(
"outerUI",
updatedFloorTitle,
62 * 3,
41 * 3,
TEXT_COLOR,
"#000000",
6
);
}
//drawStatusList(110 * 3, 93 * 3);
//core.drawImage("outerUI", "lane1.png", 0, 30)
core.drawImage(
"outerUI",
"cao.webp",
0,
0,
400,
350,
0,
30,
360,
315
);
}
2024-12-27 11:55:28 +08:00
}
2025-01-16 13:17:19 +08:00
_update_items() {
//更新道具栏
const drawItemMx = (drawFn) => {
for (let i = 0; i < this.itemMx.length; i++) {
for (let j = 0; j < this.itemMx[i].length; j++) {
var item = this.itemMx[i][j];
drawFn(i, j, item);
}
}
};
const drawItem = (item, posx, posy) => {
const icon = core.material.icons.items[item],
image = core.material.images.items;
core.drawImage(
"outerUI",
image,
0,
32 * icon,
32,
32,
posx,
posy,
30 * 3,
30 * 3
);
const cnt = core.itemCount(item);
if (
(core.items.items[item].cls === "tools" && cnt > 1) ||
FORCE_COUNTABLE_ITEMS.includes(item)
) {
core.fillText(
"outerUI",
cnt,
posx + 25 * 3,
posy + 28 * 3,
"#FFFFFF",
"bold 36px Verdana"
);
}
};
if (core.domStyle.isVertical) {
core.clearMap(
"outerUI",
ITEM_BOX_LEFT_VERTICAL,
ITEM_BOX_TOP_VERTICAL,
185 * 3,
125 * 3
);
2024-12-27 11:55:28 +08:00
2025-01-16 13:17:19 +08:00
drawItemMx((i, j, item) => {
if (core.hasItem(item)) {
const posx = ITEM_BOX_LEFT_VERTICAL + i * 30 * 3,
posy = ITEM_BOX_TOP_VERTICAL + j * 31 * 3;
drawItem(item, posx, posy);
}
});
} else {
core.clearMap(
"outerUI",
ITEM_BOX_LEFT,
ITEM_BOX_TOP,
125 * 3,
185 * 3
);
2024-12-27 20:18:22 +08:00
2025-01-16 13:17:19 +08:00
drawItemMx((i, j, item) => {
if (core.hasItem(item)) {
const posx = ITEM_BOX_LEFT + j * 30 * 3,
posy = ITEM_BOX_TOP + i * 31 * 3;
drawItem(item, posx, posy);
}
});
2024-12-27 11:55:28 +08:00
}
}
2024-12-28 23:53:21 +08:00
2025-01-16 13:17:19 +08:00
_update_map(floorId = core.status.floorId) {
const x = core.domStyle.isVertical
? MAP_BLOCK_LEFT_VERTICAL
: MAP_BLOCK_LEFT;
const y = core.domStyle.isVertical
? MAP_BLOCK_TOP_VERTICAL
: MAP_BLOCK_TOP;
2025-01-05 00:03:41 +08:00
2025-01-16 13:17:19 +08:00
if (!floorId) return;
const info = core.plugin.getMapDrawInfo(floorId, Infinity, true);
core.setTextAlign("outerUI", "center");
2025-01-05 00:03:41 +08:00
2025-01-16 13:17:19 +08:00
core.plugin.drawSmallMap(uictx, info, floorId, x, y, 300, 300);
2024-12-27 11:55:28 +08:00
}
2024-12-30 20:44:53 +08:00
2025-01-16 13:17:19 +08:00
_update_equips() {
2024-12-27 11:55:28 +08:00
return;
2025-01-16 13:17:19 +08:00
core.setFont("outerUI", "bold 48px Verdana");
const drawEquip = (baseX, baseY, id, color, back) => {
if (!id)
core.fillText(
"outerUI",
back,
baseX + 20 * 3,
baseY + 22 * 3,
color
);
else {
var icon = core.material.icons.items[id];
core.drawImage(
"outerUI",
core.material.images.items,
0,
32 * icon,
32,
32,
baseX + 5 * 3,
baseY,
32 * 3,
32 * 3
);
}
};
if (core.domStyle.isVertical) {
core.clearMap(
"outerUI",
EQUIP_BLOCK_LEFT_VERTICAL,
EQUIP_BLOCK_TOP_VERTICAL,
90 * 3,
130 * 3
);
drawEquip(
EQUIP_BLOCK_LEFT_VERTICAL,
EQUIP_BLOCK_TOP_VERTICAL,
core.getEquip(0),
"#D1CEFF",
"无"
);
drawEquip(
EQUIP_BLOCK_LEFT_VERTICAL + 45 * 3,
EQUIP_BLOCK_TOP_VERTICAL,
core.getEquip(1),
"#D1CEFF",
"无"
);
drawEquip(
EQUIP_BLOCK_LEFT_VERTICAL,
EQUIP_BLOCK_TOP_VERTICAL + 45 * 3,
core.getEquip(2),
"#D1CEFF",
"无"
);
drawEquip(
EQUIP_BLOCK_LEFT_VERTICAL + 45 * 3,
EQUIP_BLOCK_TOP_VERTICAL + 45 * 3,
core.getEquip(3),
"#D1CEFF",
"无"
);
drawEquip(
EQUIP_BLOCK_LEFT_VERTICAL,
EQUIP_BLOCK_TOP_VERTICAL + 90 * 3,
core.getEquip(4),
"#D1CEFF",
"无"
);
drawEquip(
EQUIP_BLOCK_LEFT_VERTICAL + 45 * 3,
EQUIP_BLOCK_TOP_VERTICAL + 90 * 3,
core.getEquip(5),
"#D1CEFF",
"无"
);
} else {
core.clearMap(
"outerUI",
EQUIP_BLOCK_LEFT,
EQUIP_BLOCK_TOP,
130 * 3,
95 * 3
);
drawEquip(
EQUIP_BLOCK_LEFT,
EQUIP_BLOCK_TOP,
core.getEquip(0),
"#D1CEFF",
"无"
);
drawEquip(
EQUIP_BLOCK_LEFT + 42 * 3,
EQUIP_BLOCK_TOP,
core.getEquip(1),
"#D1CEFF",
"无"
);
drawEquip(
EQUIP_BLOCK_LEFT + 85 * 3,
EQUIP_BLOCK_TOP,
core.getEquip(2),
"#D1CEFF",
"无"
);
drawEquip(
EQUIP_BLOCK_LEFT,
EQUIP_BLOCK_TOP + 45 * 3,
core.getEquip(3),
"#D1CEFF",
"无"
);
drawEquip(
EQUIP_BLOCK_LEFT + 42 * 3,
EQUIP_BLOCK_TOP + 45 * 3,
core.getEquip(4),
"#D1CEFF",
"无"
);
drawEquip(
EQUIP_BLOCK_LEFT + 85 * 3,
EQUIP_BLOCK_TOP + 45 * 3,
core.getEquip(5),
"#D1CEFF",
"无"
);
}
2024-12-27 11:55:28 +08:00
}
2025-01-16 13:17:19 +08:00
_update_keys() {
const drawKeyList = (baseX, baseY) => {
const todraw = [],
keyList = ["yellowKey", "blueKey", "redKey", "greenKey"];
let total = 0;
keyList.forEach(function (key, i) {
todraw[i] = core.itemCount(key);
total += todraw[i];
});
2024-12-30 20:44:53 +08:00
2025-01-16 13:17:19 +08:00
let dn = 3;
for (let i = 0; i <= dn; i++) {
let delta = i * 32 * 3;
2024-12-30 20:44:53 +08:00
2025-01-16 13:17:19 +08:00
if (core.domStyle.isVertical) {
this.drawKey(keyList[i], baseX, baseY + delta);
} else {
this.drawKey(keyList[i], baseX + delta, baseY);
}
2025-01-02 17:04:30 +08:00
2025-01-16 13:17:19 +08:00
core.setFont("outerUI", "bold 48px Verdana");
core.setTextAlign("outerUI", "left");
if (core.domStyle.isVertical) {
core.fillText(
"outerUI",
todraw[i],
baseX + 20 * 3,
baseY + 14 * 3 + delta,
TEXT_COLOR
);
} else {
core.fillText(
"outerUI",
todraw[i],
baseX + delta,
baseY + 32 * 3,
TEXT_COLOR
);
}
2024-12-27 11:55:28 +08:00
}
2025-01-16 13:17:19 +08:00
};
if (core.domStyle.isVertical) {
core.clearMap(
"outerUI",
KEY_BLOCK_LEFT_VERTICAL,
KEY_BLOCK_TOP_VERTICAL,
45 * 3,
130 * 3
);
drawKeyList(
KEY_BLOCK_LEFT_VERTICAL + 3 * 3,
KEY_BLOCK_TOP_VERTICAL + 5 * 3
);
} else {
core.clearMap(
"outerUI",
KEY_BLOCK_LEFT,
KEY_BLOCK_TOP,
130 * 3,
45 * 3
);
drawKeyList(KEY_BLOCK_LEFT + 10 * 3, KEY_BLOCK_TOP);
}
2024-12-27 11:55:28 +08:00
}
2025-01-16 13:17:19 +08:00
drawKey(key, x, y) {
let sx = 0,
sy = 0;
2025-01-02 17:04:30 +08:00
2025-01-16 13:17:19 +08:00
if (key == "yellowKey") sx += 13;
else if (key == "blueKey") sx += 26;
else if (key == "greenKey") sx += 39;
core.drawImage(
"outerUI",
"maba.webp",
sx,
sy,
13,
26,
x,
y,
13 * 3,
26 * 3
);
}
_update_infoWindow() {
const itemId = this.selectedItem;
let text = "";
if (this.selectedItem) {
text = core.replaceText(core.material.items[itemId]?.text);
if (text[0] == "," || text[0] == "") text = text.substring(1);
}
if (core.domStyle.isVertical) {
core.clearMap(
"outerUI",
INFO_BLOCK_LEFT_VERTICAL,
INFO_BLOCK_TOP_VERTICAL,
300 * 3,
120 * 3
);
if (this.selectedItem) {
const icon = core.material.icons.items[itemId];
core.setTextAlign("outerUI", "left");
core.fillText(
"outerUI",
core.material.items[itemId].name,
INFO_BLOCK_LEFT_VERTICAL + 50 * 3,
INFO_BLOCK_TOP_VERTICAL + 27 * 3,
"#D1CEFF"
);
core.drawImage(
"outerUI",
core.material.images.items,
0,
32 * icon,
32,
32,
INFO_BLOCK_LEFT_VERTICAL + 10 * 3,
INFO_BLOCK_TOP_VERTICAL + 8 * 3,
32 * 3,
32 * 3
);
core.ui.drawTextContent("outerUI", text, {
left: INFO_BLOCK_LEFT_VERTICAL + 10 * 3,
top: INFO_BLOCK_TOP_VERTICAL + 40 * 3,
maxWidth: 275 * 3,
color: "#D1CEFF",
fontSize: 36,
});
}
} else {
core.clearMap(
"outerUI",
INFO_BLOCK_LEFT,
INFO_BLOCK_TOP,
115 * 3,
230 * 3
);
if (this.selectedItem) {
const icon = core.material.icons.items[itemId];
core.setTextAlign("outerUI", "center");
core.fillText(
"outerUI",
core.material.items[itemId].name,
INFO_BLOCK_LEFT + 60 * 3,
INFO_BLOCK_TOP + 25 * 3,
"#D1CEFF"
);
core.drawImage(
"outerUI",
core.material.images.items,
0,
32 * icon,
32,
32,
INFO_BLOCK_LEFT + 45 * 3,
INFO_BLOCK_TOP + 30 * 3,
32 * 3,
32 * 3
);
core.ui.drawTextContent("outerUI", text, {
left: INFO_BLOCK_LEFT + 10 * 3,
top: INFO_BLOCK_TOP + 60 * 3,
maxWidth: 105 * 3,
color: "#D1CEFF",
fontSize: 36,
});
2024-12-27 11:55:28 +08:00
}
}
}
2025-01-16 13:17:19 +08:00
showItemInfo(itemId) {
//展示道具说明
this.selectedItem = itemId;
this._update_infoWindow();
}
clearItemInfo() {
//清除道具说明
this.selectedItem = null;
this._update_infoWindow();
}
_update_toolBox() {
const tools = core.isReplaying()
? [
[core.status.replay.pausing ? "play" : "pause", "stop", "rewind"],
["speedDown", "speedUp", "save"],
]
: [
["keyboard", "shop", "pack", "T332"],
["settings", "save", "load", "T331"],
];
if (core.domStyle.isVertical) {
core.clearMap(
"outerUI",
TOOL_BOX_LEFT_VERTICAL,
TOOL_BOX_TOP_VERTICAL,
115,
130
);
2024-12-27 11:55:28 +08:00
2025-01-16 13:17:19 +08:00
for (let i = 0; i < tools.length; i++) {
for (let j = 0; j < tools[i].length; j++) {
core.drawIcon(
"outerUI",
tools[i][j],
TOOL_BOX_LEFT_VERTICAL + i * 31 * 3,
TOOL_BOX_TOP_VERTICAL + j * 31 * 3,
30 * 3,
30 * 3
);
}
}
} else {
core.clearMap(
"outerUI",
TOOL_BOX_LEFT,
TOOL_BOX_TOP,
130 * 3,
80 * 3
);
2024-12-27 11:55:28 +08:00
2025-01-16 13:17:19 +08:00
for (let i = 0; i < tools.length; i++) {
for (let j = 0; j < tools[i].length; j++) {
core.drawIcon(
"outerUI",
tools[i][j],
TOOL_BOX_LEFT + j * 31 * 3,
TOOL_BOX_TOP + i * 31 * 3,
30 * 3,
30 * 3
);
}
}
}
2024-12-27 11:55:28 +08:00
}
2025-01-16 13:17:19 +08:00
onclick(x, y) {
const makeBox = ([x, y], [w, h]) => {
return [
[x, y],
[x + w, y + h],
];
};
const gridify = ([x, y], [gw, gh]) => {
return [Math.floor(x / gw), Math.floor(y / gh)];
};
const useItem = (itemId) => {
if (!core.hasItem(itemId)) return;
2024-12-27 11:55:28 +08:00
2025-01-16 13:17:19 +08:00
if (itemId != this.selectedItem) {
this.showItemInfo(itemId);
} else {
switch (itemId) {
case "centerFly":
core.ui._drawCenterFly();
break;
case "book":
core.openBook(true);
break;
case "wand":
core.insertAction({
type: "useItem",
id: itemId,
});
break;
case "fly":
core.useItem(itemId, true);
break;
default:
core.useItem(itemId);
}
}
};
const inRect = ([x, y], [[sx, sy], [dx, dy]]) => {
return sx <= x && x <= dx && sy <= y && y <= dy;
};
const relativeTo = ([x, y], [ax, ay]) => {
return [x - ax, y - ay];
};
const pos = [x, y];
if (core.domStyle.isVertical) {
const itemBox = makeBox(
[ITEM_BOX_LEFT_VERTICAL, ITEM_BOX_TOP_VERTICAL],
[30 * 6 * 3, 31 * 4 * 3]
);
if (inRect(pos, itemBox)) {
const [gx, gy] = gridify(relativeTo(pos, itemBox[0]), [
30 * 3,
31 * 3,
]);
const itemId = this.itemMx[gx][gy];
if (
(core.status.event.id == "viewMaps" ||
core.status.event.id == "fly") &&
itemId === "book"
)
core.openBook(true);
if (
core.isReplaying() ||
core.status.lockControl ||
core.isMoving()
)
return;
useItem(itemId);
2024-12-27 11:55:28 +08:00
return;
}
2025-01-16 13:17:19 +08:00
const toolBox = makeBox(
[TOOL_BOX_LEFT_VERTICAL, TOOL_BOX_TOP_VERTICAL],
[31 * 2 * 3, 31 * 4 * 3]
);
if (inRect(pos, toolBox)) {
const [col, row] = gridify(relativeTo(pos, toolBox[0]), [
31 * 3,
31 * 3,
]);
if (core.isReplaying()) {
this.replayAction[col][row].call(core);
} else if (core.isPlaying()) {
if (col === 0 && row === 3) {
core.doSL("autoSave", "load");
} else if (col === 1 && row === 3) {
core.doSL("autoSave", "reload");
} else {
this.toolbarAction[col][row].call(core, true);
}
}
return;
}
const mapBox = makeBox(
[MAP_BLOCK_LEFT_VERTICAL, MAP_BLOCK_TOP_VERTICAL],
[350, 350]
);
if (inRect(pos, mapBox)) {
if (
core.isReplaying() ||
core.status.lockControl ||
core.isMoving()
)
return;
core.ui._drawViewMaps(core.floorIds.indexOf(core.status.floorId));
return;
}
/*const equipBox = makeBox([EQUIP_BLOCK_LEFT_VERTICAL, EQUIP_BLOCK_TOP_VERTICAL], [90 * 3, 130 * 3])
2024-12-27 11:55:28 +08:00
if (inRect(pos, equipBox)) {
if (core.isReplaying() || core.status.lockControl || core.isMoving()) return;
core.openEquipbox(true)
return;
}*/
2025-01-16 13:17:19 +08:00
} else {
const mapBox = makeBox([MAP_BLOCK_LEFT, MAP_BLOCK_TOP], [350, 350]);
if (inRect(pos, mapBox)) {
if (
core.isReplaying() ||
core.status.lockControl ||
core.isMoving()
)
return;
core.ui._drawViewMaps(core.floorIds.indexOf(core.status.floorId));
return;
}
/*
2024-12-27 11:55:28 +08:00
const equipBox = makeBox([EQUIP_BLOCK_LEFT, EQUIP_BLOCK_TOP], [130, 95])
if (inRect(pos, equipBox)) {
if (core.isReplaying() || core.status.lockControl || core.isMoving()) return;
core.openEquipbox(true)
return;
}*/
2025-01-16 13:17:19 +08:00
const itemBox = makeBox(
[ITEM_BOX_LEFT, ITEM_BOX_TOP],
[31 * 4 * 3, 30 * 6 * 3]
);
if (inRect(pos, itemBox)) {
const [gx, gy] = gridify(relativeTo(pos, itemBox[0]), [
31 * 3,
30 * 3,
]);
const itemId = this.itemMx[gy][gx];
if (
(core.status.event.id == "viewMaps" ||
core.status.event.id == "fly") &&
itemId === "book"
)
core.openBook(true);
if (
core.isReplaying() ||
core.status.lockControl ||
core.isMoving()
)
return;
useItem(itemId);
return;
}
const toolBox = makeBox(
[TOOL_BOX_LEFT, TOOL_BOX_TOP],
[31 * 4 * 3, 31 * 2 * 3]
);
if (inRect(pos, toolBox)) {
const [row, col] = gridify(relativeTo(pos, toolBox[0]), [
31 * 3,
31 * 3,
]);
if (core.isReplaying()) {
this.replayAction[col][row].call(core);
} else if (core.isPlaying()) {
if (col === 0 && row === 3) {
core.doSL("autoSave", "load");
} else if (col === 1 && row === 3) {
core.doSL("autoSave", "reload");
} else {
this.toolbarAction[col][row].call(core, true);
}
}
return;
2024-12-27 11:55:28 +08:00
}
}
2025-01-16 13:17:19 +08:00
}
}
2024-12-27 11:55:28 +08:00
2025-01-16 13:17:19 +08:00
core.ui.statusBar = new StatusBar();
2024-12-27 11:55:28 +08:00
2025-01-16 13:17:19 +08:00
core.control.clearStatusBar = function () {
core.clearMap("outerUI");
};
// init() called in `afterLoadResources`.
2024-12-27 11:55:28 +08:00
},
2025-01-18 17:49:44 +08:00
"override": function () {
2024-12-27 11:55:28 +08:00
core.statusBar.icons = {
floor: 0,
name: null,
lv: 1,
hpmax: 2,
hp: 3,
atk: 4,
def: 5,
mdef: 6,
money: 7,
exp: 8,
up: 9,
book: 10,
fly: 11,
toolbox: 12,
keyboard: 13,
shop: 14,
save: 15,
load: 16,
settings: 17,
play: 18,
pause: 19,
stop: 20,
speedDown: 21,
speedUp: 22,
rewind: 23,
equipbox: 24,
mana: 25,
skill: 26,
exit: 27,
btn1: 28,
btn2: 29,
btn3: 30,
btn4: 31,
btn5: 32,
btn6: 33,
btn7: 34,
alt: 35,
keys: 36,
help: 37,
battle: 38,
};
core.actions._getClickLoc = function (x, y) {
var size = 32 * core.domStyle.scale;
var left = main.dom.gameDraw.offsetLeft + main.dom.gameGroup.offsetLeft;
var top = main.dom.gameDraw.offsetTop + main.dom.gameGroup.offsetTop;
var loc = {
x: Math.max(x - left, 0),
y: Math.max(y - top, 0),
size: size,
};
return loc;
};
core.ui._drawWindowSelector = function (background, x, y, w, h) {
w = Math.round(w) + 48;
h = Math.round(h);
var ctx = core.ui.createCanvas("_selector", x - 24, y, w, h, 165);
ctx.canvas.id = "";
this._drawSelector(ctx, background, w, h);
};
core.ui._drawSelector = function (ctx, background, w, h, left, top) {
left = left || 0;
top = top || 0;
ctx = this.getContextByName(ctx);
if (!ctx) return;
if (typeof background == "string")
background = core.material.images.images[background];
if (!(background instanceof Image)) return;
// badge
ctx.drawImage(background, 132, 68, 24, 24, left + 4, top + 4, 24, 24);
ctx.drawImage(
background,
132,
68,
24,
24,
w - left - 28,
top + 4,
24,
24
);
};
enemys.prototype._nextCriticals_useBinarySearch = function (
enemy,
info,
number,
x,
y,
floorId
) {
var mon_hp = info.mon_hp,
hero_atk = core.status.hero.atk,
mon_def = info.mon_def,
pre = info.damage;
var list = [];
var start_atk = hero_atk;
if (info.__over__) {
start_atk += info.__overAtk__;
list.push([info.__overAtk__, -info.damage]);
}
var calNext = function (currAtk, maxAtk) {
var start = Math.floor(currAtk),
end = Math.floor(maxAtk);
if (start > end) return null;
while (start < end) {
var mid = Math.floor((start + end) / 2);
if (mid - start > end - mid) mid--;
var nextInfo = core.enemys.getDamageInfo(
enemy,
{ atk: mid },
x,
y,
floorId
);
if (nextInfo == null || typeof nextInfo == "number") return null;
if (pre > nextInfo.damage) end = mid;
else start = mid + 1;
}
var nextInfo = core.enemys.getDamageInfo(
enemy,
{ atk: start },
x,
y,
floorId
);
return nextInfo == null ||
typeof nextInfo == "number" ||
nextInfo.damage >= pre
? null
: [start, nextInfo.damage];
};
var currAtk = start_atk;
while (true) {
var next = calNext(currAtk + 1, Number.MAX_SAFE_INTEGER, pre);
if (next == null) break;
currAtk = next[0];
pre = next[1];
list.push([currAtk - hero_atk, info.damage - pre]);
if (pre <= 0 && !core.flags.enableNegativeDamage) break;
if (list.length >= number) break;
}
if (list.length == 0) list.push([0, 0]);
return list;
};
core.ui.clearMap = function (name, x, y, width, height) {
if (name == "all") {
for (var m in core.canvas) {
core.canvas[m].clearRect(
-32,
-32,
core.canvas[m].canvas.width + 32,
core.canvas[m].canvas.height + 32
);
}
core.clearMap("outerUI");
core.dom.gif.innerHTML = "";
core.removeGlobalAnimate();
core.deleteCanvas(function (one) {
return one.startsWith("_bigImage_");
});
core.setWeather(null);
} else {
var ctx = this.getContextByName(name);
if (ctx)
ctx.clearRect(
x || 0,
y || 0,
width || ctx.canvas.width,
height || ctx.canvas.height
);
}
};
events.prototype.openBook = function (fromUserAction) {
if (core.isReplaying()) return;
// 如果能恢复事件从callBook事件触发
if (
core.status.event.id == "book" &&
core.events.recoverEvents(core.status.event.interval)
)
return;
// 当前是book且从“浏览地图”打开
if (core.status.event.id == "book" && core.status.event.ui) {
core.status.boxAnimateObjs = [];
core.ui._drawViewMaps(core.status.event.ui);
return;
}
// 从“浏览地图”页面打开
if (core.status.event.id == "viewMaps" || core.status.event.id == "fly") {
fromUserAction = false;
core.status.event.ui = core.status.event.data;
}
if (!this._checkStatus("book", fromUserAction, true)) return;
core.playSound("打开界面");
core.useItem("book", true);
};
////// 怪物手册界面时,放开某个键的操作 //////
core.actions._keyUpBook = function (keycode) {
if (keycode == 27 || keycode == 88) {
core.playSound("取消");
if (core.events.recoverEvents(core.status.event.interval)) {
return;
} else if (core.status.event.ui != null) {
core.status.boxAnimateObjs = [];
if (typeof core.status.event.ui === "number") {
core.status.event.id = "fly";
core.ui.drawFly(core.status.event.ui);
} else {
core.ui._drawViewMaps(core.status.event.ui);
}
} else core.ui.closePanel();
return;
}
if (keycode == 13 || keycode == 32 || keycode == 67) {
var data = core.status.event.data;
if (data != null) {
core.ui._drawBookDetail(data);
}
return;
}
};
////// 怪物手册界面的点击操作 //////
actions.prototype._clickBook = function (x, y) {
var pageinfo = core.ui._drawBook_pageinfo();
// 上一页
if (
(x == this._HX_ - 2 || x == this._HX_ - 3) &&
y === core._HEIGHT_ - 1
) {
core.playSound("光标移动");
core.ui.drawBook(core.status.event.data - pageinfo.per_page);
return;
}
// 下一页
if (
(x == this._HX_ + 2 || x == this._HX_ + 3) &&
y === core._HEIGHT_ - 1
) {
core.playSound("光标移动");
core.ui.drawBook(core.status.event.data + pageinfo.per_page);
return;
}
// 返回
if (x >= this.LAST - 2 && y === core._HEIGHT_ - 1) {
core.playSound("取消");
if (core.events.recoverEvents(core.status.event.interval)) {
return;
} else if (core.status.event.ui != null) {
core.status.boxAnimateObjs = [];
if (typeof core.status.event.ui === "number") {
core.status.event.id = "fly";
core.ui.drawFly(core.status.event.ui);
} else {
core.ui._drawViewMaps(core.status.event.ui);
}
} else core.ui.closePanel();
return;
}
// 怪物信息
var data = core.status.event.data;
if (data != null && y < core._HEIGHT_ - 1) {
var per_page = pageinfo.per_page,
page = parseInt(data / per_page);
var u = (core._HEIGHT_ - 1) / per_page;
for (var i = 0; i < per_page; ++i) {
if (y >= u * i && y < u * (i + 1)) {
var index = per_page * page + i;
core.ui.drawBook(index);
core.ui._drawBookDetail(index);
break;
}
}
return;
}
return;
};
////// 执行当前自定义事件列表中的下一个事件 //////
events.prototype.doAction = function () {
// 清空boxAnimate和UI层
clearInterval(core.status.event.interval);
clearTimeout(core.status.event.interval);
clearInterval(core.status.event.animateUI);
core.status.event.interval = null;
delete core.status.event.aniamteUI;
if (core.status.gameOver || core.status.replay.failed) return;
// 判定是否执行完毕
if (this._doAction_finishEvents()) return;
core.clearUI();
var floorId = core.status.event.data.floorId || core.status.floorId;
// 当前点坐标和前缀
var x = core.status.event.data.x,
y = core.status.event.data.y;
var prefix = [
floorId || ":f",
x != null ? x : "x",
y != null ? y : "y",
].join("@");
var current = core.status.event.data.list[0];
if (this._popEvents(current, prefix)) return;
// 当前要执行的事件
var data = current.todo.shift();
core.status.event.data.current = data;
if (typeof data == "string") data = { type: "text", text: data };
// 该事件块已经被禁用
if (data._disabled) return core.doAction();
if (data.type !== "cgtext") {
core.unregisterAnimationFrame("skip");
core.setFlag("skip", false);
}
data.floorId = data.floorId || floorId;
core.status.event.data.type = data.type;
this.doEvent(data, x, y, prefix);
return;
};
////// 在某个canvas上绘制粗体 //////
2024-12-28 23:53:21 +08:00
core.fillBoldText1 = function (
2024-12-27 11:55:28 +08:00
name,
text,
x,
y,
style,
strokeStyle,
lineWidth,
font,
maxWidth
) {
var ctx = this.getContextByName(name);
if (!ctx) return;
if (font) ctx.font = font;
if (!style) style = ctx.fillStyle;
style = core.arrayToRGBA(style);
if (!strokeStyle) strokeStyle = "#000000";
strokeStyle = core.arrayToRGBA(strokeStyle);
if (maxWidth != null) {
this.setFontForMaxWidth(ctx, text, maxWidth);
}
ctx.strokeStyle = strokeStyle;
if (!lineWidth) lineWidth = 2;
ctx.lineWidth = lineWidth;
ctx.strokeText(text, x, y);
ctx.fillStyle = style;
ctx.fillText(text, x, y);
};
},
2025-01-18 17:49:44 +08:00
"额外信息": function () {
2024-12-27 11:55:28 +08:00
/*
* 注意不要在道具属性中直接操作flags使用core.status.hero.flags或core.setFlag系列函数代替
* 需要将 变量itemDetail改为true才可正常运行
* 请尽量减少勇士的属性数量否则可能会出现严重卡顿划掉现在你放一万个属性也不会卡
* 注意这里的属性必须是core.status.hero里面的flag无法显示
* 如果不想显示可以core.setFlag("itemDetail", false);
* 然后再core.getItemDetail();
* 如有bug在大群或造塔群@古祠
*/
2024-12-28 23:53:21 +08:00
2024-12-27 11:55:28 +08:00
// 忽略的道具
2024-12-28 23:53:21 +08:00
2024-12-27 11:55:28 +08:00
const ignore = ["superPotion"];
2024-12-28 23:53:21 +08:00
2024-12-27 11:55:28 +08:00
// 取消注释下面这句可以减少超大地图的判定。
// 如果地图宝石过多,可能会略有卡顿,可以尝试取消注释下面这句话来解决。
// core.bigmap.threshold = 256;
const origin = core.control.updateStatusBar;
core.updateStatusBar = core.control.updateStatusBar = function () {
if (core.getFlag("__statistics__")) return;
else return origin.apply(core.control, arguments);
};
2024-12-28 23:53:21 +08:00
2024-12-27 11:55:28 +08:00
core.control.updateDamage = function (floorId, ctx) {
floorId = floorId || core.status.floorId;
if (!floorId || core.status.gameOver || main.mode != "play") return;
const onMap = ctx == null;
2024-12-28 23:53:21 +08:00
2024-12-27 11:55:28 +08:00
// 没有怪物手册
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;
}
this._updateDamage_damage(floorId, onMap);
this._updateDamage_extraDamage(floorId, onMap);
core.getItemDetail(floorId); // 宝石血瓶详细信息
this.drawDamage(ctx);
};
// 获取宝石信息 并绘制
this.getItemDetail = function (floorId) {
if (!core.getFlag("itemDetail")) return;
if (!core.status.thisMap) return;
floorId = floorId ?? core.status.thisMap.floorId;
const beforeRatio = core.status.thisMap.ratio;
core.status.thisMap.ratio = core.status.maps[floorId].ratio;
let diff = {};
const before = core.status.hero;
const hero = core.clone(core.status.hero);
const handler = {
set(target, key, v) {
diff[key] = v - (target[key] || 0);
if (!diff[key]) diff[key] = void 0;
return true;
},
};
core.status.hero = new Proxy(hero, handler);
core.status.maps[floorId].blocks.forEach(function (block) {
if (
block.event.cls !== "items" ||
ignore.includes(block.event.id) ||
block.disable
)
return;
const x = block.x,
y = block.y;
// v2优化只绘制范围内的部分
if (core.bigmap.v2) {
if (
x < core.bigmap.posX - core.bigmap.extend ||
x > core.bigmap.posX + core._WIDTH_ + core.bigmap.extend ||
y < core.bigmap.posY - core.bigmap.extend ||
y > core.bigmap.posY + core._HEIGHT_ + core.bigmap.extend
) {
return;
}
}
diff = {};
const id = block.event.id;
const item = core.material.items[id];
if (item.cls === "equips") {
// 装备也显示
const diff = item.equip.value ?? {};
const per = item.equip.percentage ?? {};
for (const name in per) {
diff[name + "per"] = per[name].toString() + "%";
}
drawItemDetail(diff, x, y);
return;
}
// 跟数据统计原理一样 执行效果 前后比较
core.setFlag("__statistics__", true);
try {
eval(item.itemEffect);
} catch (error) {}
drawItemDetail(diff, x, y);
});
core.status.thisMap.ratio = beforeRatio;
core.status.hero = before;
window.hero = before;
window.flags = before.flags;
};
2024-12-28 23:53:21 +08:00
2024-12-27 11:55:28 +08:00
// 绘制
function drawItemDetail(diff, x, y) {
const px = 32 * x + 2,
py = 32 * y + 30;
let content = "";
// 获得数据和颜色
let i = 0;
for (const name in diff) {
if (!diff[name]) continue;
let color = "#fff";
2024-12-28 23:53:21 +08:00
2024-12-27 11:55:28 +08:00
if (typeof diff[name] === "number")
content = core.formatBigNumber(diff[name], true);
else content = diff[name];
switch (name) {
case "atk":
case "atkper":
color = "#FF7A7A";
break;
case "def":
case "defper":
color = "#00E6F1";
break;
case "mdef":
case "mdefper":
color = "#6EFF83";
break;
case "hp":
color = "#A4FF00";
break;
case "hpmax":
case "hpmaxper":
color = "#F9FF00";
break;
case "mana":
color = "#c66";
break;
}
// 绘制
core.status.damage.data.push({
text: content,
px: px,
py: py - 10 * i,
color: color,
});
i++;
}
}
},
2025-01-18 17:49:44 +08:00
"编辑器显伤": function () {
2025-01-19 20:20:48 +08:00
// 在此增加新插件
/////// 用户设置 ///////
// 将__enable置为false将关闭插件
var __enable = true;
// 魔防攻速之类的属性可以在这里加 ['atk', 'def', 'mdef']
var heroStatus = ["atk", "def", "mdef", "hp"];
// saveHero为true 将会把每次造塔测试时的角色数据存下来 否则会读取初始属性
// 用不着可以关了 节约缓存空间 (虽然根本没多少 还没一个存档大
// 也可以手动清理 控制台输入core.removeLocalStorage('editorHero')即可
var saveHero = true;
// 下为具体实现 懒得写注释了 大概就是写HTML然后注册交互
if (!__enable || main.mode != "editor") return;
core.plugin.initEditorDamage = false;
if (heroStatus.length >= 4 && !editor.isMobile)
editor.dom.mid2.style.top = 650 + 30 * (heroStatus.length - 3) + "px";
editor.statusRatio = core.getLocalStorage("statusRatio", 1);
editor.saveHero = saveHero;
editor._heroStatus = heroStatus;
editor.dom.mapEdit.appendChild(core.canvas.damage.canvas);
var HTML =
"<input type='button' value='←'/><input type='button' value='↑'/><input type='button' value='↓'/><input type='button' value='→'/><input type='button' id='bigmapBtn' value='大地图'' style='margin-left: '5px'/>";
//if (heroStatus.length >= 4 && !editor.isMobile) editor.dom.mid2.style.top = 650 + 30 * (heroStatus.length - 3) + 'px';
heroStatus.forEach(function (status) {
var id = status + "set",
id2 = status + "add",
id3 = status + "rec",
id4 = status + "help";
HTML +=
"<br/><input type='text' size='15' id='" +
id +
"'><input type='button' id='" +
id2 +
"' value = '+'><input type='button' id='" +
id3 +
"' value = '-'><input type='button' value='?' id = '" +
id4 +
"'>";
});
document.getElementById("viewportButtons").innerHTML = HTML;
["set", "add", "rec", "help"].forEach(function (e) {
heroStatus.forEach(function (status) {
editor.dom[status + e] = document.getElementById(status + e);
});
});
var _hasItem = core.items.hasItem;
core.items.hasItem = function (itemId) {
if (itemId == "book" && main.mode == "editor") return true;
return _hasItem.call(core.items, itemId);
};
if (main.mode == "editor") {
var applyList = [
"getDamageString",
"nextCriticals",
"getEnemyInfo",
"getEnemyValue",
];
applyList.forEach(function (name) {
var func = core.enemys[name];
core.enemys[name] = function () {
var args =
arguments.length === 1 ? [arguments[0]] :
Array.apply(null, arguments);
if (typeof args[0] == "string") args[0] = core.enemys.enemys[args[0]];
return func.apply(core.enemys, args);
};
});
}
2024-12-28 23:53:21 +08:00
2025-01-19 20:20:48 +08:00
////// 获得勇士属性 //////
core.control.getStatus = function (name) {
if (!core.status.hero) return null;
if (name == "x" || name == "y" || name == "direction")
return this.getHeroLoc(name);
/*if ( main.mode == 'editor' && !core.hasFlag('__statistics__')) {
2024-12-27 11:55:28 +08:00
return data_a1e2fb4a_e986_4524_b0da_9b7ba7c0874d.firstData.hero[name];
}*/
2025-01-19 20:20:48 +08:00
return core.status.hero[name];
};
2024-12-27 11:55:28 +08:00
2025-01-19 20:20:48 +08:00
core.control.updateDamage = function (floorId, ctx) {
floorId = floorId || core.status.floorId;
if (!floorId || core.status.gameOver) return;
var onMap = ctx == null;
if (main.mode == "editor") {
ctx = core.canvas.damage;
core.updateCheckBlock();
core.clearMap(ctx);
if (editor.uivalues.bigmap) return;
}
2024-12-28 23:53:21 +08:00
2025-01-19 20:20:48 +08:00
// 没有怪物手册
if (!core.hasItem("book")) return;
core.status.damage.posX = core.bigmap.posX;
core.status.damage.posY = core.bigmap.posY;
if (!onMap) {
var width = core.floors[floorId].width,
height = core.floors[floorId].height;
// 地图过大的缩略图不绘制显伤
if (width * height > core.bigmap.threshold) return;
}
this._updateDamage_damage(floorId, onMap);
this._updateDamage_extraDamage(floorId, onMap);
this.drawDamage(ctx);
};
2024-12-27 11:55:28 +08:00
2025-01-19 20:20:48 +08:00
core.control.drawDamage = function (ctx) {
if (
core.status.gameOver ||
!core.status.damage /* || main.mode != 'play'*/
)
return;
var onMap = false;
if (ctx == null) {
ctx = core.canvas.damage;
core.clearMap("damage");
onMap = true;
}
2024-12-27 11:55:28 +08:00
2025-01-19 20:20:48 +08:00
if (onMap && core.bigmap.v2) {
// 检查是否需要重算...
if (
Math.abs(core.bigmap.posX - core.status.damage.posX) >=
core.bigmap.extend - 1 ||
Math.abs(core.bigmap.posY - core.status.damage.posY) >=
core.bigmap.extend - 1
) {
return this.updateDamage();
}
}
return this._drawDamage_draw(ctx, onMap);
};
2024-12-28 23:53:21 +08:00
2025-01-19 20:20:48 +08:00
////// 以x,y的形式返回每个点的事件 //////
core.maps.getMapBlocksObj = function (floorId, noCache) {
floorId = floorId || core.status.floorId;
if (
core.status.mapBlockObjs[floorId] &&
!noCache &&
main.mode != "editor"
)
return core.status.mapBlockObjs[floorId];
var obj = {};
core.extractBlocks(floorId);
core.status.maps[floorId].blocks.forEach(function (block) {
obj[block.x + "," + block.y] = block;
});
core.status.mapBlockObjs[floorId] = obj;
return obj;
};
2024-12-27 11:55:28 +08:00
2025-01-19 20:20:48 +08:00
this.bignum = function (num, defaultValue) {
if (num == null || num == "") return defaultValue;
num = num + "";
var list = {
w: 1e4,
e: 1e8,
z: 1e12,
j: 1e16,
g: 1e20,
};
// 浮点数问题
function checkFloat(num) {
if (!core.isset(num)) return 0;
num = num + "";
var index = num.indexOf(".");
if (index < 0) return 0;
else return num.slice(index + 1).length;
}
var index = num.search(/w|e|z|j|g/);
if (index <= 0) {
num = parseInt(num);
if (core.isset(num)) return num;
else {
alert("不正确的输入");
return defaultValue;
}
}
for (; index > 0; index = num.search(/w|e|z|j|g/)) {
var p = num[index],
q = list[p],
n = num.slice(0, index),
m = Math.pow(10, checkFloat(n));
num = (n * m * q) / m + num.slice(index + 1);
}
return parseInt(num);
};
2024-12-28 23:53:21 +08:00
2025-01-19 20:20:48 +08:00
this.updateEditorDamage = function (noSave) {
core.updateDamage();
heroStatus.forEach(function (status) {
editor.dom[status + "set"].value = core.status.hero[status];
});
if (!noSave && editor.saveHero)
core.setLocalStorage("editorHero", core.status.hero);
};
2024-12-27 11:55:28 +08:00
2025-01-19 20:20:48 +08:00
var _resizeMap = core.maps.resizeMap;
core.maps.resizeMap = function (floorId) {
_resizeMap.call(core.maps, floorId);
if (!core.plugin.initEditorDamage && main.mode == "editor") {
core.plugin.initEditorDamage = true;
var editorHero = core.getLocalStorage("editorHero");
if (editorHero && saveHero) core.status.hero = editorHero;
else core.removeLocalStorage("editorHero");
editor._heroStatus.forEach(function (e) {
editor.dom[e + "set"].onchange = function () {
var status = this.id.slice(0, -3);
core.status.hero[status] = core.bignum(
this.value,
core.status.hero[status]
);
core.updateEditorDamage();
};
editor.dom[e + "add"].onclick = function () {
var status = this.id.slice(0, -3);
core.status.hero[status] += editor.statusRatio;
core.updateEditorDamage();
};
editor.dom[e + "rec"].onclick = function () {
var status = this.id.slice(0, -3);
core.status.hero[status] -= editor.statusRatio;
core.updateEditorDamage();
};
editor.dom[e + "help"].onclick = function () {
var status = this.id.slice(0, -4),
name = core.getStatusLabel(status);
var ratio = parseInt(
prompt(
"当前属性:" +
name +
"\n现在的点击按钮变化值:" +
editor.statusRatio +
",请输入按下一次+/-按钮的属性变化量,可以写4w 10.2e这种字母缩写"
)
);
if (!core.isset(ratio)) {
printe("不合法的输入");
return;
}
editor.statusRatio = ratio;
core.setLocalStorage("statusRatio", ratio);
};
});
var _updateMap = editor.updateMap;
editor.updateMap = function () {
_updateMap.call(editor);
core.updateEditorDamage(true);
};
editor.mode.onmode = function (mode, callback) {
if (editor_mode.mode != mode) {
if (mode === "save") {
editor_mode.doActionList(
editor_mode.mode,
editor_mode.actionList,
function () {
if (callback) callback();
core.updateEditorDamage();
}
);
}
if (editor_mode.mode === "nextChange" && mode)
editor_mode.showMode(mode);
if (mode !== "save") editor_mode.mode = mode;
editor_mode.actionList = [];
}
};
}
};
},
2025-01-18 17:49:44 +08:00
"手册区分特殊属性": function () {
2024-12-27 11:55:28 +08:00
// 在此增加新插件
this.arrsame = function (Arraya, Arrayb) {
let a = Arraya || [];
let b = Arrayb || [];
if (typeof a === "number") a = [a];
if (typeof b === "number") b = [b];
let c = [...a, ...b];
for (const i of c)
if (!a.includes(i) || !b.includes(i)) {
return false;
}
return true;
};
enemys.prototype.getCurrentEnemys = function (floorId) {
floorId = floorId || core.status.floorId;
var enemys = [],
used = {};
core.extractBlocks(floorId);
core.status.maps[floorId].blocks.forEach(function (block) {
if (!block.disable && block.event.cls.indexOf("enemy") == 0) {
this._getCurrentEnemys_addEnemy(
block.event.id,
enemys,
used,
block.x,
block.y,
floorId
);
}
}, this);
return this._getCurrentEnemys_sort(enemys);
};
2024-12-28 23:53:21 +08:00
2024-12-27 11:55:28 +08:00
enemys.prototype._getCurrentEnemys_getEnemy = function (enemyId) {
var enemy = core.material.enemys[enemyId];
if (!enemy) return null;
2024-12-28 23:53:21 +08:00
2024-12-27 11:55:28 +08:00
// 检查朝向displayIdInBook
return (
core.material.enemys[enemy.displayIdInBook] ||
core.material.enemys[(enemy.faceIds || {}).down] ||
enemy
2024-12-28 23:53:21 +08:00
);
2024-12-27 11:55:28 +08:00
};
2024-12-27 20:18:22 +08:00
2024-12-27 11:55:28 +08:00
enemys.prototype._getCurrentEnemys_addEnemy = function (
enemyId,
enemys,
used,
x,
y,
floorId
) {
var enemy = this._getCurrentEnemys_getEnemy(enemyId);
if (enemy == null) return;
2024-12-28 23:53:21 +08:00
2024-12-27 11:55:28 +08:00
var id = enemy.id;
2024-12-28 23:53:21 +08:00
2024-12-27 11:55:28 +08:00
var enemyInfo = this.getEnemyInfo(enemy, null, null, null, floorId);
var locEnemyInfo = this.getEnemyInfo(enemy, null, x, y, floorId);
if (
!core.flags.enableEnemyPoint ||
(locEnemyInfo.atk == enemyInfo.atk &&
locEnemyInfo.def == enemyInfo.def &&
locEnemyInfo.hp == enemyInfo.hp &&
core.plugin.arrsame(locEnemyInfo.special, enemyInfo.special))
) {
x = null;
y = null;
} else {
// 检查enemys里面是否使用了存在的内容
for (var i = 0; i < enemys.length; ++i) {
var one = enemys[i];
if (
id == one.id &&
one.locs != null &&
locEnemyInfo.atk == one.atk &&
locEnemyInfo.def == one.def &&
locEnemyInfo.hp == one.hp &&
core.plugin.arrsame(locEnemyInfo.special, one.special)
) {
one.locs.push([x, y]);
return;
}
}
enemyInfo = locEnemyInfo;
}
2024-12-28 23:53:21 +08:00
2024-12-27 11:55:28 +08:00
var id = enemy.id + ":" + x + ":" + y;
if (used[id]) return;
used[id] = true;
2024-12-28 23:53:21 +08:00
2024-12-27 11:55:28 +08:00
var specialText = core.enemys.getSpecialText(enemy, x, y, floorId);
var specialColor = core.enemys.getSpecialColor(enemy, x, y, floorId);
var critical = this.nextCriticals(enemy, 1, x, y, floorId);
if (critical.length > 0) critical = critical[0];
2024-12-28 23:53:21 +08:00
2024-12-27 11:55:28 +08:00
var e = core.clone(enemy);
for (var v in enemyInfo) {
e[v] = enemyInfo[v];
}
if (x != null && y != null) {
e.locs = [[x, y]];
}
e.name = core.getEnemyValue(enemy, "name", x, y, floorId);
e.specialText = specialText;
e.specialColor = specialColor;
e.damage = this.getDamage(enemy, x, y, floorId);
e.critical = critical[0];
e.criticalDamage = critical[1];
e.defDamage = this._getCurrentEnemys_addEnemy_defDamage(
enemy,
x,
y,
floorId
2024-12-28 23:53:21 +08:00
);
2024-12-27 11:55:28 +08:00
enemys.push(e);
};
enemys.prototype._getCurrentEnemys_addEnemy_defDamage = function (
enemy,
x,
y,
floorId
) {
var ratio = core.status.maps[floorId || core.status.floorId].ratio || 1;
return this.getDefDamage(enemy, ratio, x, y, floorId);
};
2024-12-28 23:53:21 +08:00
2024-12-27 11:55:28 +08:00
enemys.prototype._getCurrentEnemys_sort = function (enemys) {
return enemys.sort(function (a, b) {
if (a.damage == b.damage) {
return a.money - b.money;
}
if (a.damage == null) {
return 1;
}
if (b.damage == null) {
return -1;
}
return a.damage - b.damage;
});
2024-12-28 23:53:21 +08:00
};
2024-12-27 11:55:28 +08:00
////// 获得所有特殊属性的名称 //////
enemys.prototype.getSpecialText = function (enemy, x, y, floorId) {
if (typeof enemy == "string")
enemy = this.getEnemyInfo(enemy, null, x, y, floorId);
if (!enemy) return [];
var special = enemy.special;
var text = [];
2024-12-28 23:53:21 +08:00
2024-12-27 11:55:28 +08:00
var specials = this.getSpecials();
if (specials) {
for (var i = 0; i < specials.length; i++) {
if (this.hasSpecial(special, specials[i][0]))
text.push(this._calSpecialContent(enemy, specials[i][1]));
}
2024-12-28 23:53:21 +08:00
}
2024-12-27 11:55:28 +08:00
return text;
};
2024-12-28 23:53:21 +08:00
2024-12-27 11:55:28 +08:00
////// 获得所有特殊属性的颜色 //////
enemys.prototype.getSpecialColor = function (enemy, x, y, floorId) {
if (typeof enemy == "string")
enemy = this.getEnemyInfo(enemy, null, x, y, floorId);
if (!enemy) return [];
var special = enemy.special;
var colors = [];
var specials = this.getSpecials();
if (specials) {
for (var i = 0; i < specials.length; i++) {
if (this.hasSpecial(special, specials[i][0]))
colors.push(specials[i][3] || null);
2024-12-28 23:53:21 +08:00
}
}
2024-12-27 11:55:28 +08:00
return colors;
2024-12-28 23:53:21 +08:00
};
2024-12-27 20:18:22 +08:00
2024-12-27 11:55:28 +08:00
////// 获得所有特殊属性的额外标记 //////
enemys.prototype.getSpecialFlag = function (enemy, x, y, floorId) {
if (typeof enemy == "string")
enemy = getEnemyInfo(enemy, null, x, y, floorId);
if (!enemy) return [];
var special = enemy.special;
var flag = 0;
var specials = this.getSpecials();
if (specials) {
for (var i = 0; i < specials.length; i++) {
if (this.hasSpecial(special, specials[i][0]))
flag |= specials[i][4] || 0;
}
}
return flag;
2024-12-28 23:53:21 +08:00
};
2024-12-27 20:18:22 +08:00
2024-12-27 11:55:28 +08:00
////// 获得每个特殊属性的说明 //////
enemys.prototype.getSpecialHint = function (enemy, special) {
var specials = this.getSpecials();
if (special == null) {
if (specials == null) return [];
var hints = [];
for (var i = 0; i < specials.length; i++) {
if (this.hasSpecial(enemy, specials[i][0]))
hints.push(
"\r[" +
core.arrayToRGBA(specials[i][3] || "#FF6A6A") +
"]\\d" +
this._calSpecialContent(enemy, specials[i][1]) +
"\\d\r[]" +
this._calSpecialContent(enemy, specials[i][2])
);
}
return hints;
}
2024-12-27 20:18:22 +08:00
2024-12-27 11:55:28 +08:00
if (specials == null) return "";
for (var i = 0; i < specials.length; i++) {
if (special == specials[i][0])
return (
"\r[#FF6A6A]\\d" +
this._calSpecialContent(enemy, specials[i][1]) +
"\\d\r[]" +
this._calSpecialContent(enemy, specials[i][2])
);
}
return "";
2024-12-28 23:53:21 +08:00
};
2024-12-27 11:55:28 +08:00
ui.prototype._drawBook_drawName = function (
index,
enemy,
2024-12-28 23:53:21 +08:00
top,
2024-12-27 11:55:28 +08:00
left,
width
2024-12-28 23:53:21 +08:00
) {
2024-12-27 11:55:28 +08:00
// 绘制第零列(名称和特殊属性)
// 如果需要添加自己的比如怪物的称号等,也可以在这里绘制
core.setTextAlign("ui", "center");
if (core.enemys.getSpecialText(enemy).length == 0) {
core.fillText(
"ui",
enemy.name,
left + width / 2,
top + 35,
"#DDDDDD",
this._buildFont(17, true),
width
);
} else {
core.fillText(
"ui",
enemy.name,
left + width / 2,
top + 28,
"#DDDDDD",
this._buildFont(17, true),
width
);
switch (core.enemys.getSpecialText(enemy).length) {
case 1:
core.fillText(
"ui",
core.enemys.getSpecialText(enemy)[0],
left + width / 2,
top + 50,
core.arrayToRGBA(
(core.enemys.getSpecialColor(enemy) || [])[0] || "#FF6A6A"
),
this._buildFont(15, true),
width
);
break;
case 2:
// Step 1: 计算字体
var text =
core.enemys.getSpecialText(enemy)[0] +
" " +
core.enemys.getSpecialText(enemy)[1];
core.setFontForMaxWidth(
"ui",
text,
width,
this._buildFont(15, true)
);
// Step 2: 计算总宽度
var totalWidth = core.calWidth("ui", text);
var leftWidth = core.calWidth(
"ui",
core.enemys.getSpecialText(enemy)[0]
);
var rightWidth = core.calWidth(
"ui",
core.enemys.getSpecialText(enemy)[1]
);
// Step 3: 绘制
core.fillText(
"ui",
core.enemys.getSpecialText(enemy)[0],
left + (width + leftWidth - totalWidth) / 2,
top + 50,
core.arrayToRGBA(
(core.enemys.getSpecialColor(enemy) || [])[0] || "#FF6A6A"
)
);
core.fillText(
"ui",
core.enemys.getSpecialText(enemy)[1],
left + (width + totalWidth - rightWidth) / 2,
top + 50,
core.arrayToRGBA(
(core.enemys.getSpecialColor(enemy) || [])[1] || "#FF6A6A"
)
);
break;
default:
core.fillText(
"ui",
"多属性...",
left + width / 2,
top + 50,
"#FF6A6A",
this._buildFont(15, true),
width
);
}
}
};
ui.prototype._drawBookDetail_getInfo = function (index) {
var floorId =
core.floorIds[(core.status.event.ui || {}).index] ||
core.status.floorId;
// 清除浏览地图时的光环缓存
if (floorId != core.status.floorId && core.status.checkBlock) {
core.status.checkBlock.cache = {};
}
var enemys = core.enemys.getCurrentEnemys(floorId);
console.log(123);
if (enemys.length == 0) return [];
index = core.clamp(index, 0, enemys.length - 1);
var enemy = enemys[index];
var texts = core.enemys.getSpecialHint(enemy);
if (texts.length == 0) texts.push("该怪物无特殊属性。");
if (enemy.description) texts.push(enemy.description + "\r");
this._drawBookDetail_getTexts(enemy, floorId, texts);
texts.push("");
return [enemy, texts];
};
},
2025-01-18 17:49:44 +08:00
"一防减伤": function () {
2024-12-27 11:55:28 +08:00
// 在此增加新插件
ui.prototype._drawBook_drawRow3 = function (
index,
enemy,
top,
left,
width,
position
) {
// 绘制第三行
core.setTextAlign("ui", "left");
var b13 = this._buildFont(13, true),
f13 = this._buildFont(13, false);
var col1 = left,
col2 = left + (width * 9) / 25,
col3 = left + (width * 17) / 25;
core.fillText("ui", "临界", col1, position, "#DDDDDD", f13);
2024-12-28 23:53:21 +08:00
core.fillText(
2024-12-27 11:55:28 +08:00
"ui",
core.formatBigNumber(enemy.critical || 0),
col1 + 30,
position,
null,
b13
2024-12-28 23:53:21 +08:00
);
2024-12-27 11:55:28 +08:00
core.fillText("ui", "减伤", col2, position, null, f13);
2024-12-28 23:53:21 +08:00
core.fillText(
2024-12-27 11:55:28 +08:00
"ui",
core.formatBigNumber(enemy.criticalDamage || 0),
col2 + 30,
position,
null,
b13
2024-12-28 23:53:21 +08:00
);
2024-12-27 11:55:28 +08:00
//core.fillText('ui', '加防', col3, position, null, f13);
core.fillText("ui", "1防", col3, position, null, f13);
core.fillText(
"ui",
core.formatBigNumber(enemy.defDamage || 0),
col3 + 30,
position,
null,
b13
2024-12-28 23:53:21 +08:00
);
};
2024-12-27 11:55:28 +08:00
////// 1防减伤计算 //////
enemys.prototype.getDefDamage = function (enemy, k, x, y, floorId) {
if (typeof enemy == "string") enemy = core.material.enemys[enemy];
k = k || 1;
var nowDamage = this._getDamage(enemy, null, x, y, floorId);
var nextDamage = this._getDamage(
enemy,
{ def: core.status.hero.def + k },
2024-12-28 23:53:21 +08:00
x,
y,
2024-12-27 11:55:28 +08:00
floorId
2024-12-28 23:53:21 +08:00
);
2024-12-27 11:55:28 +08:00
if (nowDamage == null || nextDamage == null) return "???";
return nowDamage - nextDamage;
};
//防御倍数
enemys.prototype._getCurrentEnemys_addEnemy_defDamage = function (
enemy,
x,
y,
floorId
) {
var ratio = core.status.maps[floorId || core.status.floorId].ratio || 1;
//第一行为按照ratio值计算减防第二行为1防减伤
//return this.getDefDamage(enemy, ratio, x, y, floorId);
return this.getDefDamage(enemy, null, x, y, floorId);
};
},
2025-01-18 17:49:44 +08:00
"新道具栏/装备栏": function () {
2025-01-16 13:28:25 +08:00
// 在此增加新插件
// 注:///// *** 裹起来的区域: 该区域内参数可以随意更改调整ui绘制 不会影响总体布局
// 请尽量修改该区域而不是其他区域 修改的时候最好可以对照现有ui修改
2024-12-27 20:18:22 +08:00
2025-01-16 13:28:25 +08:00
///// *** 道具类型
// cls对应name
var itemClsName = {
constants: "永久道具",
tools: "消耗道具",
2024-12-28 23:53:21 +08:00
};
2025-01-16 13:28:25 +08:00
// 一页最大放的道具数量 将把整个道具左栏分成num份 每份是一个道具项
var itemNum = 12;
///// ***
2024-12-27 20:18:22 +08:00
2025-01-16 13:28:25 +08:00
// 背景设置
this.drawBoxBackground = function (ctx) {
2024-12-28 23:53:21 +08:00
core.setTextAlign(ctx, "left");
2025-01-16 13:28:25 +08:00
core.clearMap(ctx);
core.deleteCanvas("_selector");
var info = core.status.thisUIEventInfo || {};
2024-12-27 11:55:28 +08:00
2025-01-16 13:28:25 +08:00
///// *** 背景设置
var max = core.__PIXELS__;
var x = 2,
y = x,
w = max - x * 2,
h = w;
var borderWidth = 2,
borderRadius = 5, // radius:圆角矩形的圆角半径
borderStyle = "#fff";
var backgroundColor = "gray";
// 设置背景不透明度(0.85)
var backgroundAlpha = 0.85;
///// ***
2024-12-27 11:55:28 +08:00
2025-01-16 13:28:25 +08:00
var start_x = x + borderWidth / 2,
start_y = y + borderWidth / 2,
width = max - start_x * 2,
height = max - start_y * 2;
2024-12-27 11:55:28 +08:00
2025-01-16 13:28:25 +08:00
// 渐变色背景的一个例子(黑色渐变白色)
// 有关渐变色的具体知识请网上搜索canvas createGradient了解
/*
2024-12-27 11:55:28 +08:00
var grd = ctx.createLinearGradient(x, y, x + w, y);
grd.addColorStop(0, "black");
grd.addColorStop(1, "white");
backgroundColor = grd;
*/
2025-01-16 13:28:25 +08:00
// 使用图片背景要注释掉下面的strokeRect和fillRoundRect
// 图片背景的一个例子:
/*
2024-12-27 11:55:28 +08:00
core.drawImage(ctx, "xxx.png", x, y, w, h);
core.strokeRect(ctx, x, y, w, h, borderStyle, borderWidth);
*/
2025-01-16 13:28:25 +08:00
core.setAlpha(ctx, backgroundAlpha);
core.strokeRoundRect(
ctx,
x,
y,
w,
h,
borderRadius,
borderStyle,
borderWidth
);
core.fillRoundRect(
ctx,
start_x,
start_y,
width,
height,
borderRadius,
backgroundColor
);
core.setAlpha(ctx, 1);
2024-12-27 11:55:28 +08:00
2025-01-16 13:28:25 +08:00
///// *** 左栏配置
var leftbar_height = height;
// 左边栏宽度(width*0.6) 本身仅为坐标使用 需要与底下的rightbar_width(width*0.4)同时更改
var leftbar_width = width * 0.6;
///// ***
// xxx_right参数 代表最右侧坐标
var leftbar_right = start_x + leftbar_width - borderWidth / 2;
var leftbar_bottom = start_y + leftbar_height;
var leftbar_x = start_x;
var leftbar_y = start_y;
///// *** 道具栏配置
var boxName_color = "#fff";
var boxName_fontSize = 15;
var boxName_font = core.ui._buildFont(boxName_fontSize, true);
var arrow_x = 10 + start_x;
var arrow_y = 10 + start_y;
var arrow_width = 20;
var arrow_style = "white";
// 暂时只能是1 否则不太行 等待新样板(2.7.3)之后对drawArrow做优化
var arrow_lineWidth = 1;
// 右箭头
var rightArrow_right = leftbar_right - 10;
// 道具内栏顶部坐标 本质是通过该项 控制(道具栏顶部文字和箭头)与道具内栏顶部的间隔
var itembar_top = arrow_y + 15;
///// ***
var itembar_right = rightArrow_right;
var boxName =
core.status.event.id == "toolbox"
? "\r[yellow]道具栏\r | 装备栏"
: "道具栏 | \r[yellow]装备栏\r";
core.drawArrow(
2024-12-28 23:53:21 +08:00
ctx,
2025-01-16 13:28:25 +08:00
arrow_x + arrow_width,
arrow_y,
arrow_x,
arrow_y,
arrow_style,
arrow_lineWidth
2024-12-28 23:53:21 +08:00
);
2025-01-16 13:28:25 +08:00
core.drawArrow(
ctx,
rightArrow_right - arrow_width,
arrow_y,
rightArrow_right,
arrow_y,
arrow_style,
arrow_lineWidth
);
core.setTextAlign(ctx, "center");
core.setTextBaseline(ctx, "middle");
var changeBox = function () {
var id = core.status.event.id;
core.closePanel();
if (id == "toolbox") core.openEquipbox();
else core.openToolbox();
2024-12-28 23:53:21 +08:00
};
2025-01-16 13:28:25 +08:00
core.fillText(
ctx,
boxName,
(leftbar_right + leftbar_x) / 2,
arrow_y + 2,
boxName_color,
boxName_font
);
2024-12-27 11:55:28 +08:00
2025-01-16 13:28:25 +08:00
///// *** 底栏按钮
var pageBtn_radius = 8;
// xxx_left 最左侧坐标
var pageBtn_left = leftbar_x + 3;
var pageBtn_right = leftbar_right - 3;
// xxx_bottom 最底部坐标
var pageBtn_bottom = leftbar_bottom - 2;
var pageBtn_borderStyle = "#fff";
var pageBtn_borderWidth = 2;
var pageText_color = "#fff";
// 底部按钮与上面的道具内栏的间隔大小
var bottomSpace = 8;
///// ***
core.drawItemListbox_setPageBtn(
2024-12-28 23:53:21 +08:00
ctx,
2025-01-16 13:28:25 +08:00
pageBtn_left,
pageBtn_right,
pageBtn_bottom,
pageBtn_radius,
pageBtn_borderStyle,
pageBtn_borderWidth
2024-12-28 23:53:21 +08:00
);
2025-01-16 13:28:25 +08:00
var page = info.page || 1;
var pageFontSize = pageBtn_radius * 2 - 4;
var pageFont = core.ui._buildFont(pageFontSize);
core.setPageItems(page);
var num = itemNum;
if (core.status.event.id == "equipbox") num -= 5;
var maxPage = info.maxPage;
var pageText = page + " / " + maxPage;
core.setTextAlign(ctx, "center");
core.setTextBaseline(ctx, "bottom");
core.fillText(
ctx,
pageText,
(leftbar_x + leftbar_right) / 2,
pageBtn_bottom,
pageText_color,
pageFont
);
core.addUIEventListener(
start_x,
start_y,
leftbar_right - start_x,
arrow_y - start_y + 13,
changeBox
);
var itembar_height = Math.ceil(
pageBtn_bottom -
pageBtn_radius * 2 -
pageBtn_borderWidth / 2 -
bottomSpace -
itembar_top
);
var oneItemHeight = (itembar_height - 4) / itemNum;
return {
x: start_x,
y: start_y,
width: width,
height: height,
leftbar_right: leftbar_right,
obj: {
x: arrow_x,
y: itembar_top,
width: itembar_right - arrow_x,
height: itembar_height,
oneItemHeight: oneItemHeight,
},
2024-12-28 23:53:21 +08:00
};
};
2025-01-16 13:28:25 +08:00
this.drawItemListbox = function (ctx, obj) {
ctx = ctx || core.canvas.ui;
var itembar_x = obj.x,
itembar_y = obj.y,
itembar_width = obj.width,
itembar_height = obj.height,
itemNum = obj.itemNum,
oneItemHeight = obj.oneItemHeight;
var itembar_right = itembar_x + itembar_width;
var info = core.status.thisUIEventInfo || {};
var obj = {};
var page = info.page || 1,
index = info.index,
select = info.select || {};
2024-12-27 11:55:28 +08:00
2025-01-16 13:28:25 +08:00
///// *** 道具栏内栏配置
var itembar_style = "black";
var itembar_alpha = 0.7;
// 一个竖屏下减少道具显示的例子:
// if (core.domStyle.isVertical) itemNum = 10;
// 每个道具项的上下空隙占总高度的比例
var itembar_marginHeightRatio = 0.2;
// 左右间隔空隙
var item_marginLeft = 2;
var item_x = itembar_x + 2,
item_y = itembar_y + 2,
item_right = itembar_right - 2,
itemName_color = "#fff";
// 修改此项以更换闪烁光标
var item_selector = "winskin.webp";
///// ***
2024-12-27 11:55:28 +08:00
2025-01-16 13:28:25 +08:00
core.setAlpha(ctx, itembar_alpha);
core.fillRect(
ctx,
itembar_x,
itembar_y,
itembar_width,
itembar_height,
itembar_style
);
core.setAlpha(ctx, 1);
var pageItems = core.setPageItems(page);
var marginHeight = itembar_marginHeightRatio * oneItemHeight;
core.setTextBaseline(ctx, "middle");
var originColor = itemName_color;
for (var i = 0; i < pageItems.length; i++) {
itemName_color = originColor;
var item = pageItems[i];
// 设置某个的字体颜色的一个例子
// if (item.id == "xxx") itemName_color = "green";
core.drawItemListbox_drawItem(
ctx,
item_x,
item_right,
item_y,
oneItemHeight,
item_marginLeft,
marginHeight,
itemName_color,
pageItems[i]
);
if (index == i + 1)
core.ui._drawWindowSelector(
item_selector,
item_x + 1,
item_y - 1,
item_right - item_x - 2,
oneItemHeight - 2
);
item_y += oneItemHeight;
2024-12-28 23:53:21 +08:00
}
};
2025-01-16 13:28:25 +08:00
this.drawToolboxRightbar = function (ctx, obj) {
ctx = ctx || core.canvas.ui;
var info = core.status.thisUIEventInfo || {};
var page = info.page || 1,
index = info.index || 1,
select = info.select || {};
var start_x = obj.x,
start_y = obj.y,
width = obj.width,
height = obj.height;
var toolboxRight = start_x + width,
toolboxBottom = start_y + height;
2024-12-28 23:53:21 +08:00
2025-01-16 13:28:25 +08:00
///// *** 侧边栏(rightbar)背景设置(物品介绍)
var rightbar_width = width * 0.4;
var rightbar_height = height;
var rightbar_lineWidth = 2;
var rightbar_lineStyle = "#fff";
///// ***
2024-12-28 23:53:21 +08:00
2025-01-16 13:28:25 +08:00
var rightbar_x = toolboxRight - rightbar_width - rightbar_lineWidth / 2;
var rightbar_y = start_y;
core.drawLine(
ctx,
rightbar_x,
rightbar_y,
rightbar_x,
rightbar_y + rightbar_height,
rightbar_lineStyle,
rightbar_lineWidth
);
2024-12-28 23:53:21 +08:00
2025-01-16 13:28:25 +08:00
// 获取道具id(有可能为null)
var itemId = select.id;
var item = core.material.items[itemId];
2024-12-27 11:55:28 +08:00
2025-01-16 13:28:25 +08:00
///// *** 侧边栏物品Icon信息
var iconRect_y = rightbar_y + 10;
// space间距
// 这里布局设定iconRect与侧边栏左边框 itemName与工具栏右边框 itemRect与itemName的间距均为space
var space = 15;
var iconRect_x = rightbar_x + space;
var iconRect_radius = 2,
iconRect_width = 32,
iconRect_height = 32,
iconRect_style = "#fff",
iconRect_lineWidth = 2;
///// ***
2024-12-27 11:55:28 +08:00
2025-01-16 13:28:25 +08:00
var iconRect_bottom = iconRect_y + iconRect_height,
iconRect_right = iconRect_x + iconRect_width;
2024-12-27 11:55:28 +08:00
2025-01-16 13:28:25 +08:00
///// *** 侧边栏各项信息
var itemTextFontSize = 15,
itemText_x = iconRect_x - 4,
itemText_y = Math.floor(start_y + rightbar_height * 0.25), // 坐标取整防止模糊
itemClsFontSize = 15,
itemClsFont = core.ui._buildFont(itemClsFontSize),
itemClsColor = "#fff",
itemCls_x = itemText_x - itemClsFontSize / 2,
itemCls_middle = (iconRect_bottom + itemText_y) / 2, //_middle代表文字的中心y坐标
itemNameFontSize = 18,
itemNameColor = "#fff",
itemNameFont = core.ui._buildFont(itemNameFontSize, true);
var itemName_x = iconRect_right + space;
var itemName_middle =
iconRect_y + iconRect_height / 2 + iconRect_lineWidth;
// 修改这里可以编辑未选中道具时的默认值
var defaultItem = {
cls: "constants",
name: "未知道具",
text: "没有道具最永久",
};
var defaultEquip = {
cls: "equips",
name: "未知装备",
text: "一无所有,又何尝不是一种装备",
equipCls: "无",
equip: {
type: "装备",
},
};
///// ***
2024-12-27 11:55:28 +08:00
2025-01-16 13:28:25 +08:00
var originItem = item;
if (core.status.event.id == "equipbox") item = item || defaultEquip;
item = item || defaultItem;
var itemCls = item.cls,
itemName = item.name,
itemText = item.text;
itemText = core.replaceText(itemText);
if (itemText[0] == "," || itemText[0] == "")
itemText = itemText.substring(1);
/* id()
* if (item.id == "xxx") itemNameColor = "red";
*/
var itemClsName = core.getItemClsName(item);
var itemNameMaxWidth =
rightbar_width - iconRect_width - iconRect_lineWidth * 2 - space * 2;
core.strokeRoundRect(
ctx,
iconRect_x,
iconRect_y,
iconRect_width,
iconRect_height,
iconRect_radius,
iconRect_style,
iconRect_lineWidth
);
if (item.id)
core.drawIcon(
ctx,
item.id,
iconRect_x + iconRect_lineWidth / 2,
iconRect_y + iconRect_lineWidth / 2,
iconRect_width - iconRect_lineWidth,
iconRect_height - iconRect_lineWidth
);
core.setTextAlign(ctx, "left");
core.setTextBaseline(ctx, "middle");
if (itemCls === "equips" && item.id) {
itemName = "【" + item.equipCls + "】" + itemName;
2024-12-28 23:53:21 +08:00
}
2025-01-16 13:28:25 +08:00
core.fillText(
ctx,
itemName,
itemName_x,
itemName_middle,
itemNameColor,
itemNameFont,
itemNameMaxWidth
);
if (!item.equip)
core.fillText(
ctx,
"【" + itemClsName + "】",
itemCls_x,
itemCls_middle,
itemClsColor,
itemClsFont
);
var statusText = "";
if (core.status.event.id == "equipbox") {
var type = item.equip.type;
if (typeof type == "string") type = core.getEquipTypeByName(type);
var compare = core.compareEquipment(item.id, core.getEquip(type));
var compare2;
if (item.equipCls === "双手剑")
compare2 = core.compareEquipment(null, core.getEquip(1));
if (info.select.action == "unload")
compare = core.compareEquipment(null, item.id);
// --- 变化值...
for (var name in core.status.hero) {
if (typeof core.status.hero[name] != "number") continue;
var nowValue = core.getRealStatus(name);
// 查询新值
var newValue = Math.floor(
((core.getStatus(name) +
(compare.value[name] || 0) +
(compare2?.value[name] || 0)) *
(core.getBuff(name) * 100 +
(compare.percentage[name] || 0) +
(compare2?.percentage[name] || 0))) /
100
);
if (nowValue == newValue) continue;
var color = newValue > nowValue ? "#00FF00" : "#FF0000";
nowValue = core.formatBigNumber(nowValue);
newValue = core.formatBigNumber(newValue);
statusText +=
core.getStatusLabel(name) +
" " +
nowValue +
"->\r[" +
color +
"]" +
newValue +
"\r\n";
}
2024-12-28 23:53:21 +08:00
}
2025-01-16 13:28:25 +08:00
itemText = statusText + itemText;
if (item.equip) {
core.drawTextContent(ctx, itemText, {
left: itemText_x,
top: itemCls_middle,
bold: false,
color: "white",
align: "left",
fontSize: itemTextFontSize,
maxWidth:
rightbar_width -
(itemText_x - rightbar_x) * 2 +
itemTextFontSize / 2,
});
} else {
core.drawTextContent(ctx, itemText, {
left: itemText_x,
top: itemText_y,
bold: false,
color: "white",
align: "left",
fontSize: itemTextFontSize,
maxWidth:
rightbar_width -
(itemText_x - rightbar_x) * 2 +
itemTextFontSize / 2,
});
2024-12-28 23:53:21 +08:00
}
2024-12-27 11:55:28 +08:00
2025-01-16 13:28:25 +08:00
///// *** 退出按钮设置
var btnRadius = 10;
var btnBorderWidth = 2;
var btnRight = toolboxRight - 2;
var btnBottom = toolboxBottom - 2;
var btnBorderStyle = "#fff";
///// ***
2024-12-27 11:55:28 +08:00
2025-01-16 13:28:25 +08:00
// 获取圆心位置
var btn_x = btnRight - btnRadius - btnBorderWidth / 2,
btn_y = btnBottom - btnRadius - btnBorderWidth / 2;
core.drawToolbox_setExitBtn(
ctx,
btn_x,
btn_y,
btnRadius,
btnBorderStyle,
btnBorderWidth
);
2024-12-27 20:18:22 +08:00
2025-01-16 13:28:25 +08:00
///// *** 使用按钮设置
var useBtnHeight = btnRadius * 2;
// 这里不设置useBtnWidth而是根据各项数据自动得出width
var useBtnRadius = useBtnHeight / 2;
var useBtn_x = rightbar_x + 4,
useBtn_y = btnBottom - useBtnHeight;
var useBtnBorderStyle = "#fff";
var useBtnBorderWidth = btnBorderWidth;
///// ***
2024-12-27 11:55:28 +08:00
2025-01-16 13:28:25 +08:00
core.drawToolbox_setUseBtn(
ctx,
useBtn_x,
useBtn_y,
useBtnRadius,
useBtnHeight,
useBtnBorderStyle,
useBtnBorderWidth
);
2024-12-28 23:53:21 +08:00
};
2024-12-27 11:55:28 +08:00
2025-01-16 13:28:25 +08:00
this.drawEquipbox_drawOthers = function (ctx, obj) {
var info = core.status.thisUIEventInfo;
2024-12-27 11:55:28 +08:00
2025-01-16 13:28:25 +08:00
///// *** 装备格设置
var equipList_lineWidth = 2;
var equipList_boxSize = 32;
var equipList_borderWidth = 2;
var equipList_borderStyle = "#fff";
var equipList_nameColor = "#fff";
///// ***
2024-12-27 11:55:28 +08:00
2025-01-16 13:28:25 +08:00
var equipList_x = obj.x + 4,
equipList_bottom = obj.obj.y - equipList_lineWidth,
equipList_y = equipList_bottom - obj.obj.oneItemHeight * reduceItem - 2,
equipList_height = equipList_bottom - equipList_y;
var equipList_right = obj.leftbar_right,
equipList_width = equipList_right - equipList_x;
core.drawLine(
ctx,
obj.x,
equipList_bottom + equipList_lineWidth / 2,
equipList_right,
equipList_bottom + equipList_lineWidth / 2,
equipList_borderStyle,
equipList_lineWidth
);
var toDrawList = core.status.globalAttribute.equipName,
len = toDrawList.length;
2024-12-27 11:55:28 +08:00
2025-01-16 13:28:25 +08:00
///// *** 装备格设置
var maxItem = 2;
var box_width = 32,
box_height = 32,
box_borderStyle = "#fff",
box_selectBorderStyle = "gold", // 选中的装备格的颜色
box_borderWidth = 2;
var boxName_fontSize = 14,
boxName_space = 2,
boxName_color = "#fff"; // 装备格名称与上面的装备格框的距离
var maxLine = Math.ceil(len / maxItem);
///// ***
var l = Math.sqrt(len);
if (Math.pow(l) == len && len != 4) {
if (l <= maxItem) maxItem = l;
}
maxItem = Math.min(toDrawList.length, maxItem);
info.equips = maxItem;
2024-12-27 11:55:28 +08:00
2025-01-16 13:28:25 +08:00
var boxName_font = core.ui._buildFont(boxName_fontSize);
// 总宽高减去所有装备格宽高得到空隙大小
var oneBoxWidth = box_width + box_borderWidth * 2;
var oneBoxHeight =
box_height + boxName_fontSize + boxName_space + 2 * box_borderWidth;
var space_y = (equipList_height - maxLine * oneBoxHeight) / (1 + maxLine),
space_x = (equipList_width - maxItem * oneBoxWidth) / (1 + maxItem);
var box_x = equipList_x + space_x,
box_y = equipList_y + space_y + 12;
for (var i = 0; i < 2; i++) {
var id = core.getEquip(i),
name = toDrawList[i];
if (i === 0) name = "主手";
if (i === 1) name = "副手";
var selectBorder = false;
if (core.status.thisUIEventInfo.select.type == i) selectBorder = true;
var borderStyle = selectBorder
? box_selectBorderStyle
: box_borderStyle;
core.drawEquipbox_drawOne(
ctx,
name,
id,
box_x,
box_y,
box_width,
box_height,
boxName_space,
boxName_font,
boxName_color,
borderStyle,
box_borderWidth
);
var todo = new Function(
"core.clickOneEquipbox('" + id + "'," + i + ")"
);
core.addUIEventListener(
box_x - box_borderWidth / 2,
box_y - box_borderWidth / 2,
oneBoxWidth,
oneBoxHeight,
todo
);
box_x += space_x + oneBoxWidth;
if ((i + 1) % maxItem == 0) {
box_x = equipList_x + space_x;
box_y += space_y + oneBoxHeight;
}
}
if (core.material.items[core.getEquip(0)]?.equipCls === "双手剑") {
core.drawLine(
ctx,
equipList_x + space_x + space_x + oneBoxWidth,
equipList_y + space_y + 12,
equipList_x +
space_x +
space_x +
oneBoxWidth +
box_width +
box_borderWidth,
equipList_y + space_y + box_height + 12
);
core.drawLine(
ctx,
equipList_x + space_x + space_x + oneBoxWidth,
equipList_y + space_y + box_height + 12,
equipList_x +
space_x +
space_x +
oneBoxWidth +
box_width +
box_borderWidth,
equipList_y + space_y + 12
);
}
///// *** 装备格设置
var maxItem = 3;
var box_width = 32,
box_height = 32,
box_borderStyle = "#fff",
box_selectBorderStyle = "gold", // 选中的装备格的颜色
box_borderWidth = 2;
var boxName_fontSize = 14,
boxName_space = 2,
boxName_color = "#fff"; // 装备格名称与上面的装备格框的距离
var maxLine = Math.ceil(len / maxItem);
///// ***
var l = Math.sqrt(len);
if (Math.pow(l) == len && len != 4) {
if (l <= maxItem) maxItem = l;
}
maxItem = Math.min(toDrawList.length, maxItem);
info.equips = maxItem;
2024-12-27 20:18:22 +08:00
2025-01-16 13:28:25 +08:00
var boxName_font = core.ui._buildFont(boxName_fontSize);
// 总宽高减去所有装备格宽高得到空隙大小
var oneBoxWidth = box_width + box_borderWidth * 2;
var oneBoxHeight =
box_height + boxName_fontSize + boxName_space + 2 * box_borderWidth;
var space_y = (equipList_height - maxLine * oneBoxHeight) / (1 + maxLine),
space_x = (equipList_width - maxItem * oneBoxWidth) / (1 + maxItem);
var box_x = equipList_x + space_x,
box_y = equipList_y + space_y + space_y + oneBoxHeight;
for (var i = 2; i < len; i++) {
var id = core.getEquip(i),
name = toDrawList[i];
var selectBorder = false;
if (core.status.thisUIEventInfo.select.type == i) selectBorder = true;
var borderStyle = selectBorder
? box_selectBorderStyle
: box_borderStyle;
core.drawEquipbox_drawOne(
ctx,
name,
id,
box_x,
box_y,
box_width,
box_height,
boxName_space,
boxName_font,
boxName_color,
borderStyle,
box_borderWidth
);
var todo = new Function(
"core.clickOneEquipbox('" + id + "'," + i + ")"
);
core.addUIEventListener(
box_x - box_borderWidth / 2,
box_y - box_borderWidth / 2,
oneBoxWidth,
oneBoxHeight,
todo
);
box_x += space_x + oneBoxWidth;
}
2024-12-28 23:53:21 +08:00
};
2024-12-27 20:18:22 +08:00
2025-01-16 13:28:25 +08:00
this.drawToolbox = function (ctx) {
ctx = ctx || core.canvas.ui;
core.status.thisEventClickArea = [];
2024-12-27 20:18:22 +08:00
2025-01-16 13:28:25 +08:00
var info = core.drawBoxBackground(ctx);
info.itemNum = itemNum;
core.drawItemListbox(ctx, info.obj);
core.drawToolboxRightbar(ctx, info);
core.setTextBaseline(ctx, "alphabetic");
core.setTextAlign("left");
2024-12-28 23:53:21 +08:00
};
2024-12-27 11:55:28 +08:00
2025-01-16 13:28:25 +08:00
var reduceItem = 4;
this.drawEquipbox = function (ctx) {
ctx = ctx || core.canvas.ui;
core.status.thisEventClickArea = [];
var info = core.drawBoxBackground(ctx);
info.itemNum = itemNum - reduceItem;
info.obj.y += info.obj.oneItemHeight * reduceItem;
info.obj.height -= info.obj.oneItemHeight * reduceItem;
core.drawItemListbox(ctx, info.obj);
core.drawEquipbox_drawOthers(ctx, info);
core.drawToolboxRightbar(ctx, info);
core.setTextBaseline(ctx, "alphabetic");
core.setTextAlign("left");
2024-12-28 23:53:21 +08:00
};
2024-12-27 11:55:28 +08:00
2025-01-16 13:28:25 +08:00
this.drawEquipbox_drawOne = function (
ctx,
name,
id,
x,
y,
width,
height,
space,
font,
color,
style,
lineWidth
) {
if (id)
core.drawIcon(
ctx,
id,
x + lineWidth / 2,
y + lineWidth / 2,
width,
height
);
core.strokeRect(
ctx,
x,
y,
width + lineWidth,
height + lineWidth,
style,
lineWidth
);
core.setTextAlign(ctx, "center");
core.setTextBaseline(ctx, "top");
var tx = (x + x + lineWidth / 2 + width) / 2,
ty = y + height + (lineWidth / 2) * 3 + space;
core.fillText(ctx, name, tx, ty, color, font);
core.setTextBaseline(ctx, "alphabetic");
core.setTextAlign("left");
};
this.drawItemListbox_drawItem = function (
ctx,
left,
right,
top,
height,
marginLeft,
marginHeight,
style,
id
) {
2024-12-28 23:53:21 +08:00
var info = core.status.thisUIEventInfo;
2025-01-16 13:28:25 +08:00
var nowClick = info.index;
var item = core.material.items[id] || {};
var name = item.name || "???";
var num = core.itemCount(id) || 0;
var fontSize = Math.floor(height - marginHeight * 2);
core.setTextAlign(ctx, "right");
var numText = "x" + num;
core.fillText(
ctx,
numText,
right - marginLeft,
top + height / 2,
style,
core.ui._buildFont(fontSize)
);
if (name != "???")
core.drawIcon(
ctx,
id,
left + marginLeft,
top + marginHeight,
fontSize,
fontSize
);
var text_x = left + marginLeft + fontSize + 2;
var maxWidth = right - core.calWidth(ctx, numText) - text_x;
core.setTextAlign(ctx, "left");
core.fillText(
ctx,
name,
text_x,
top + height / 2,
style,
core.ui._buildFont(fontSize),
maxWidth
);
var todo = new Function("core.clickItemFunc('" + id + "');");
core.addUIEventListener(left, top, right - left, height, todo);
2024-12-28 23:53:21 +08:00
};
2024-12-27 20:18:22 +08:00
2025-01-16 13:28:25 +08:00
this.setPageItems = function (page) {
var num = itemNum;
if (core.status.event.id == "equipbox") num -= reduceItem;
2024-12-28 23:53:21 +08:00
var info = core.status.thisUIEventInfo;
2025-01-16 13:28:25 +08:00
if (!info) return;
page = page || info.page;
var items = core.getToolboxItems(
core.status.event.id == "toolbox" ? "all" : "equips"
);
info.allItems = items;
var maxPage = Math.ceil(items.length / num);
info.maxPage = maxPage;
var pageItems = items.slice((page - 1) * num, page * num);
info.pageItems = pageItems;
info.maxItem = pageItems.length;
if (items.length == 0 && pageItems.length == 0) info.index = null;
if (pageItems.length == 0 && info.page > 1) {
info.page = Math.max(1, info.page - 1);
return core.setPageItems(info.page);
}
return pageItems;
2024-12-28 23:53:21 +08:00
};
2024-12-27 20:18:22 +08:00
2025-01-16 13:28:25 +08:00
this.drawToolbox_setExitBtn = function (ctx, x, y, r, style, lineWidth) {
core.strokeCircle(ctx, x, y, r, style, lineWidth);
ctx.textAlign = "center";
ctx.textBaseline = "middle";
var textSize = Math.sqrt(2) * r;
core.fillText(
ctx,
"x",
x,
y,
style,
core.ui._buildFont(textSize),
textSize
);
core.setTextAlign(ctx, "start");
core.setTextBaseline(ctx, "top");
2024-12-27 11:55:28 +08:00
2025-01-16 13:28:25 +08:00
var todo = function () {
core.closePanel();
};
core.addUIEventListener(x - r, y - r, r * 2, r * 2, todo);
};
2024-12-27 11:55:28 +08:00
2025-01-16 13:28:25 +08:00
this.drawToolbox_setUseBtn = function (ctx, x, y, r, h, style, lineWidth) {
core.setTextAlign(ctx, "left");
core.setTextBaseline(ctx, "top");
var fontSize = h - 4;
var font = core.ui._buildFont(fontSize);
var text = core.status.event.id == "toolbox" ? "使用" : "装备";
if (core.status.thisUIEventInfo.select.action == "unload") text = "卸下";
var w = core.calWidth(ctx, text, font) + 2 * r + lineWidth / 2;
2024-12-27 11:55:28 +08:00
2025-01-16 13:28:25 +08:00
core.strokeRoundRect(ctx, x, y, w, h, r, style, lineWidth);
core.fillText(ctx, text, x + r, y + lineWidth / 2 + 2, style, font);
2024-12-27 11:55:28 +08:00
2025-01-16 13:28:25 +08:00
var todo = function () {
core.useSelectItemInBox();
};
core.addUIEventListener(x, y, w, h, todo);
2024-12-27 11:55:28 +08:00
};
2025-01-16 13:28:25 +08:00
this.drawItemListbox_setPageBtn = function (
ctx,
left,
right,
bottom,
r,
style,
lineWidth
) {
var offset = lineWidth / 2 + r;
var x = left + offset;
var y = bottom - offset;
var pos = (Math.sqrt(2) / 2) * (r - lineWidth / 2);
core.fillPolygon(
ctx,
[
[x - pos, y],
[x + pos - 2, y - pos],
[x + pos - 2, y + pos],
],
style
);
core.strokeCircle(ctx, x, y, r, style, lineWidth);
var todo = function () {
core.addItemListboxPage(-1);
};
core.addUIEventListener(x - r - 2, y - r - 2, r * 2 + 4, r * 2 + 4, todo);
x = right - offset;
core.fillPolygon(
ctx,
[
[x + pos, y],
[x - pos + 2, y - pos],
[x - pos + 2, y + pos],
],
style
);
core.strokeCircle(ctx, x, y, r, style, lineWidth);
var todo = function () {
core.addItemListboxPage(1);
};
core.addUIEventListener(x - r - 2, y - r - 2, r * 2 + 4, r * 2 + 4, todo);
2024-12-27 11:55:28 +08:00
};
2025-01-16 13:28:25 +08:00
this.clickItemFunc = function (id) {
var info = core.status.thisUIEventInfo;
if (!info) return;
if (info.select.id == id) return core.useSelectItemInBox();
info.select = {};
info.select.id = id;
core.setIndexAndSelect("index");
core.refreshBox();
2024-12-27 11:55:28 +08:00
};
2025-01-16 13:28:25 +08:00
this.clickOneEquipbox = function (id, type) {
var info = core.status.thisUIEventInfo;
if (!info) return;
if (info.select.id == id && info.select.type == type)
core.useSelectItemInBox();
else
core.status.thisUIEventInfo.select = {
id: id,
type: type,
action: "unload",
};
return core.refreshBox();
};
2024-12-27 11:55:28 +08:00
2025-01-16 13:28:25 +08:00
core.ui.getToolboxItems = function (cls) {
var list = Object.keys(core.status.hero.items[cls] || {});
if (cls == "all") {
for (var name in core.status.hero.items) {
if (name == "equips") continue;
list = list.concat(Object.keys(core.status.hero.items[name]));
}
return list
.filter(function (id) {
return !core.material.items[id].hideInToolbox;
})
.sort();
}
if (this.uidata.getToolboxItems) {
return this.uidata.getToolboxItems(cls);
}
return list
.filter(function (id) {
return !core.material.items[id].hideInToolbox;
})
.sort();
};
this.useSelectItemInBox = function () {
var info = core.status.thisUIEventInfo;
if (!info) return;
if (!info.select.id) return;
var id = info.select.id;
if (core.status.event.id == "toolbox") {
core.events.tryUseItem(id);
// core.closePanel();
} else if (core.status.event.id == "equipbox") {
var action = info.select.action || "load";
info.index = 1;
if (action == "load") {
var type = core.getEquipTypeById(id);
let equipClsid = core.material.items[id]?.equipCls;
let equipCls0 = core.material.items[core.getEquip(0)]?.equipCls;
let equipCls1 = core.material.items[core.getEquip(1)]?.equipCls;
if (equipClsid === "双手剑") {
core.unloadEquip(0, function () {
core.status.route.push("unEquip:" + 0);
});
core.unloadEquip(1, function () {
core.status.route.push("unEquip:" + 1);
});
2024-12-27 11:55:28 +08:00
}
2025-01-16 13:28:25 +08:00
core.loadEquip(id, function () {
core.status.route.push("equip:" + id);
info.select.type = type;
core.setIndexAndSelect("select");
core.drawEquipbox();
});
2024-12-27 11:55:28 +08:00
} else {
2025-01-16 13:28:25 +08:00
var type = info.select.type;
core.unloadEquip(type, function () {
core.status.route.push("unEquip:" + type);
info.select.type = type;
//info.select.action = 'load'
core.setIndexAndSelect("select");
core.drawEquipbox();
});
2024-12-27 11:55:28 +08:00
}
}
2025-01-16 13:28:25 +08:00
core.updateStatusBar();
};
this.setIndexAndSelect = function (toChange) {
var info = core.status.thisUIEventInfo;
if (!info) return;
core.setPageItems(info.page);
var index = info.index || 1;
var items = info.pageItems;
if (info.select.type != null) {
var type = info.select.type;
id = core.getEquip(type);
info.index = null;
info.select = {
id: id,
action: "unload",
type: type,
};
return;
2024-12-27 11:55:28 +08:00
} else {
2025-01-16 13:28:25 +08:00
info.select.action = null;
info.select.type = null;
if (toChange == "index") info.index = items.indexOf(info.select.id) + 1;
info.select.id = items[info.index - 1];
2024-12-27 11:55:28 +08:00
}
};
2025-01-16 13:28:25 +08:00
this.addItemListboxPage = function (num) {
var info = core.status.thisUIEventInfo;
if (!info) return;
var maxPage = info.maxPage || 1;
info.page = info.page || 1;
info.page += num;
if (info.page <= 0) info.page = maxPage;
if (info.page > maxPage) info.page = 1;
info.index = 1;
core.setPageItems(info.page);
core.setIndexAndSelect("select");
core.refreshBox();
2024-12-27 11:55:28 +08:00
};
2025-01-16 13:28:25 +08:00
this.addItemListboxIndex = function (num) {
var info = core.status.thisUIEventInfo;
if (!info) return;
var maxItem = info.maxItem || 0;
info.index = info.index || 0;
info.index += num;
if (info.index <= 0) info.index = 1;
if (info.index > maxItem) info.index = maxItem;
core.setIndexAndSelect("select");
core.refreshBox();
};
2024-12-27 11:55:28 +08:00
2025-01-16 13:28:25 +08:00
this.addEquipboxType = function (num) {
var info = core.status.thisUIEventInfo;
var type = info.select.type;
if (type == null && num > 0) info.select.type = 0;
else info.select.type = type + num;
var max = core.status.globalAttribute.equipName.length;
if (info.select.type >= max) {
info.select = {};
core.setIndexAndSelect("select");
return core.addItemListboxPage(0);
} else {
var m = Math.abs(info.select.type);
if (info.select.type < 0) info.select.type = max - m;
core.setIndexAndSelect("select");
core.refreshBox();
return;
}
2024-12-27 11:55:28 +08:00
};
2025-01-16 13:28:25 +08:00
core.actions._keyDownToolbox = function (keycode) {
if (!core.status.thisEventClickArea) return;
if (keycode == 37) {
// left
core.addItemListboxPage(-1);
return;
2024-12-27 11:55:28 +08:00
}
2025-01-16 13:28:25 +08:00
if (keycode == 38) {
// up
core.addItemListboxIndex(-1);
return;
}
if (keycode == 39) {
// right
core.addItemListboxPage(1);
return;
}
if (keycode == 40) {
// down
core.addItemListboxIndex(1);
return;
2024-12-27 11:55:28 +08:00
}
};
2025-01-16 13:28:25 +08:00
////// 工具栏界面时,放开某个键的操作 //////
core.actions._keyUpToolbox = function (keycode) {
if (keycode == 81) {
core.ui.closePanel();
if (core.isReplaying()) core.control._replay_equipbox();
else core.openEquipbox();
return;
}
if (keycode == 84 || keycode == 27 || keycode == 88) {
core.closePanel();
return;
}
if (keycode == 13 || keycode == 32 || keycode == 67) {
var info = core.status.thisUIEventInfo;
if (info.select) {
core.useSelectItemInBox();
2024-12-27 11:55:28 +08:00
}
2025-01-16 13:28:25 +08:00
return;
2024-12-27 11:55:28 +08:00
}
};
2025-01-16 13:28:25 +08:00
core.actions._keyDownEquipbox = function (keycode) {
if (!core.status.thisEventClickArea) return;
if (keycode == 37) {
// left
var info = core.status.thisUIEventInfo;
if (info.index != null) return core.addItemListboxPage(-1);
return core.addEquipboxType(-1);
}
if (keycode == 38) {
// up
var info = core.status.thisUIEventInfo;
if (info.index == 1) {
info.select.type = core.status.globalAttribute.equipName.length - 1;
core.setIndexAndSelect();
return core.refreshBox();
}
if (info.index) return core.addItemListboxIndex(-1);
return core.addEquipboxType(-1 * info.equips);
}
if (keycode == 39) {
// right
var info = core.status.thisUIEventInfo;
if (info.index != null) return core.addItemListboxPage(1);
return core.addEquipboxType(1);
}
if (keycode == 40) {
// down
var info = core.status.thisUIEventInfo;
if (info.index) return core.addItemListboxIndex(1);
return core.addEquipboxType(info.equips);
}
2024-12-27 11:55:28 +08:00
};
2025-01-16 13:28:25 +08:00
core.actions._keyUpEquipbox = function (keycode, altKey) {
if (altKey && keycode >= 48 && keycode <= 57) {
core.items.quickSaveEquip(keycode - 48);
return;
}
if (keycode == 84) {
core.ui.closePanel();
if (core.isReplaying()) core.control._replay_toolbox();
else core.openToolbox();
return;
}
if (keycode == 81 || keycode == 27 || keycode == 88) {
core.closePanel();
return;
}
if (keycode == 13 || keycode == 32 || keycode == 67) {
var info = core.status.thisUIEventInfo;
if (info.select) core.useSelectItemInBox();
return;
2024-12-27 11:55:28 +08:00
}
};
2025-01-16 13:28:25 +08:00
core.registerAction(
"ondown",
"inEventClickAction",
function (x, y, px, py) {
if (!core.status.thisEventClickArea) return false;
// console.log(px + "," + py);
var info = core.status.thisEventClickArea;
for (var i = 0; i < info.length; i++) {
var obj = info[i];
if (
px >= obj.x &&
px <= obj.x + obj.width &&
py > obj.y &&
py < obj.y + obj.height
) {
if (obj.todo) obj.todo();
break;
2024-12-27 11:55:28 +08:00
}
}
2025-01-16 13:28:25 +08:00
return true;
},
51
);
core.registerAction(
"onclick",
"stopClick",
function () {
if (core.status.thisEventClickArea) return true;
},
51
);
this.addUIEventListener = function (x, y, width, height, todo) {
if (!core.status.thisEventClickArea) return;
var obj = {
x: x,
y: y,
width: width,
height: height,
todo: todo,
};
core.status.thisEventClickArea.push(obj);
2024-12-27 11:55:28 +08:00
};
2025-01-16 13:28:25 +08:00
this.initThisEventInfo = function () {
core.status.thisUIEventInfo = {
page: 1,
select: {},
};
core.status.thisEventClickArea = [];
};
2024-12-27 11:55:28 +08:00
2025-01-16 13:28:25 +08:00
this.refreshBox = function () {
if (!core.status.event.id) return;
if (core.status.event.id == "toolbox") core.drawToolbox();
else core.drawEquipbox();
};
2024-12-27 11:55:28 +08:00
2025-01-16 13:28:25 +08:00
core.ui.closePanel = function () {
if (core.status.hero && core.status.hero.flags) {
// 清除全部临时变量
Object.keys(core.status.hero.flags).forEach(function (name) {
if (name.startsWith("@temp@") || /^arg\d+$/.test(name)) {
delete core.status.hero.flags[name];
}
});
2024-12-27 11:55:28 +08:00
}
2025-01-16 13:28:25 +08:00
this.clearUI();
core.maps.generateGroundPattern();
core.updateStatusBar(true);
core.unlockControl();
core.status.event.data = null;
core.status.event.id = null;
core.status.event.selection = null;
core.status.event.ui = null;
core.status.event.interval = null;
core.status.thisUIEventInfo = null;
core.status.thisEventClickArea = null;
};
2024-12-27 11:55:28 +08:00
2025-01-16 13:28:25 +08:00
this.getItemClsName = function (item) {
if (item == null) return itemClsName;
if (item.cls == "equips") {
if (typeof item.equip.type == "string") return item.equip.type;
var type = core.getEquipTypeById(item.id);
return core.status.globalAttribute.equipName[type];
} else return itemClsName[item.cls] || item.cls;
};
2024-12-27 11:55:28 +08:00
2025-01-16 13:28:25 +08:00
core.events.openToolbox = function (fromUserAction) {
if (core.isReplaying()) return;
if (!this._checkStatus("toolbox", fromUserAction)) return;
core.initThisEventInfo();
let info = core.status.thisUIEventInfo;
info.index = 1;
core.setIndexAndSelect("select");
core.drawToolbox();
2024-12-27 11:55:28 +08:00
};
2025-01-16 13:28:25 +08:00
core.events.openEquipbox = function (fromUserAction) {
if (core.isReplaying()) return;
if (!this._checkStatus("equipbox", fromUserAction)) return;
core.initThisEventInfo();
let info = core.status.thisUIEventInfo;
info.select.type = 0;
core.setIndexAndSelect("select");
core.drawEquipbox();
};
2024-12-27 11:55:28 +08:00
2025-01-16 13:28:25 +08:00
core.control._replay_toolbox = function () {
if (!core.isPlaying() || !core.isReplaying()) return;
if (!core.status.replay.pausing) return core.drawTip("请先暂停录像");
if (core.isMoving() || core.status.replay.animate || core.status.event.id)
return core.drawTip("请等待当前事件的处理结束");
core.lockControl();
core.status.event.id = "toolbox";
core.drawToolbox();
2024-12-27 11:55:28 +08:00
};
2025-01-16 13:28:25 +08:00
core.control._replay_equipbox = function () {
if (!core.isPlaying() || !core.isReplaying()) return;
if (!core.status.replay.pausing) return core.drawTip("请先暂停录像");
if (core.isMoving() || core.status.replay.animate || core.status.event.id)
return core.drawTip("请等待当前事件的处理结束");
2024-12-27 11:55:28 +08:00
2025-01-16 13:28:25 +08:00
core.lockControl();
core.status.event.id = "equipbox";
core.drawEquipbox();
};
2024-12-27 11:55:28 +08:00
2025-01-16 13:28:25 +08:00
core.control._replayAction_item = function (action) {
if (action.indexOf("item:") != 0) return false;
var itemId = action.substring(5);
if (!core.canUseItem(itemId)) return false;
if (
core.material.items[itemId].hideInReplay ||
core.status.replay.speed == 24
) {
core.useItem(itemId, false, core.replay);
return true;
2024-12-27 11:55:28 +08:00
}
2025-01-16 13:28:25 +08:00
core.status.event.id = "toolbox";
core.initThisEventInfo();
var info = core.status.thisUIEventInfo;
var items = core.getToolboxItems("all");
core.setPageItems(1);
var index = items.indexOf(itemId) + 1;
info.page = Math.ceil(index / info.maxItem);
info.index = index % info.maxItem || info.maxItem;
core.setIndexAndSelect("select");
core.setPageItems(info.page);
core.drawToolbox();
setTimeout(function () {
core.ui.closePanel();
core.useItem(itemId, false, core.replay);
}, core.control.__replay_getTimeout());
2024-12-27 11:55:28 +08:00
return true;
};
2025-01-16 13:28:25 +08:00
core.control._replayAction_equip = function (action) {
if (action.indexOf("equip:") != 0) return false;
var itemId = action.substring(6);
var items = core.getToolboxItems("equips");
var index = items.indexOf(itemId) + 1;
if (index < 1) return false;
core.status.route.push(action);
if (
core.material.items[itemId].hideInReplay ||
core.status.replay.speed == 24
) {
core.loadEquip(itemId, core.replay);
2024-12-27 11:55:28 +08:00
return true;
2025-01-16 13:28:25 +08:00
}
core.status.event.id = "equipbox";
core.initThisEventInfo();
var info = core.status.thisUIEventInfo;
core.setPageItems(1);
info.page = Math.ceil(index / info.maxItem);
info.index = index % info.maxItem || info.maxItem;
core.setIndexAndSelect("select");
core.setPageItems(info.page);
core.drawEquipbox();
setTimeout(function () {
core.ui.closePanel();
core.loadEquip(itemId, core.replay);
}, core.control.__replay_getTimeout());
return true;
};
2024-12-27 11:55:28 +08:00
2025-01-16 13:28:25 +08:00
core.control._replayAction_unEquip = function (action) {
if (action.indexOf("unEquip:") != 0) return false;
var equipType = parseInt(action.substring(8));
if (!core.isset(equipType)) return false;
core.status.route.push(action);
2024-12-27 11:55:28 +08:00
if (core.status.replay.speed == 24) {
2025-01-16 13:28:25 +08:00
core.unloadEquip(equipType, core.replay);
2024-12-27 11:55:28 +08:00
return true;
}
2025-01-16 13:28:25 +08:00
core.status.event.id = "equipbox";
core.initThisEventInfo();
var info = core.status.thisUIEventInfo;
core.setPageItems(1);
info.select.type = equipType;
core.setIndexAndSelect();
core.drawEquipbox();
2024-12-27 11:55:28 +08:00
setTimeout(function () {
2025-01-16 13:28:25 +08:00
core.ui.closePanel();
core.unloadEquip(equipType, core.replay);
2024-12-27 11:55:28 +08:00
}, core.control.__replay_getTimeout());
return true;
};
2025-01-16 13:28:25 +08:00
core.registerReplayAction("item", core.control._replayAction_item);
core.registerReplayAction("equip", core.control._replayAction_equip);
core.registerReplayAction("unEquip", core.control._replayAction_unEquip);
},
2025-01-18 17:49:44 +08:00
"技能树": function () {
2024-12-27 11:55:28 +08:00
// 在此增加新插件
//
// 已学习的技能等级 flags._hasSkill_
//打开技能树 core.myskilltree()
//不需要自动连线可以在 core.myskilltree_draw_line开头取消return的注释
core.AllStatus = function () {
//////在这个函数填写技能列表
var Skill_1 = {
召唤之书: {
name: "召唤之书",
text: "每种召唤物至多存在一只,当存在召唤物时,伤害以花花、灰太狼、牛牛、蝴蝶、兔兔、勇士顺序进行结算,直到当前序列召唤物死亡前,下一序列不受伤。吸血、反伤、固伤等特殊属性直接作用于勇士",
image: "I393",
maxLv: 1,
pos: [5, 7],
need: null,
exp: 0,
},
冥想: {
name: "冥想",
text: "默认主动技能战斗后恢复技能等级的MP",
image: "I395",
maxLv: 5,
pos: [5, 11],
need: null,
exp: 20 + 30 * flags._hasSkill_["冥想"], //基础+每个等级需求提升
},
召唤回收: {
name: "召唤回收",
text: "献祭召唤物回复20MP召唤物离场会导致对应图腾失效(涅槃图腾除外)",
image: "I405",
maxLv: 1,
pos: [5, 5],
need: [{ 召唤之书: 1 }],
exp: 100,
},
蝴蝶: {
name: "蝴蝶",
text: "40MP召唤50+30*Lv生命的蝴蝶在场时提升0.5*LV点战后回蓝效果向上取整并提升Lv点经验获取",
image: "I388",
maxLv: 5,
pos: [7, 9],
need: [{ 召唤之书: 1 }],
exp: 50 * flags._hasSkill_["蝴蝶"],
},
兔兔: {
name: "兔兔",
text: "40MP召唤50+30*Lv生命的兔兔在场时提勇士10%*LV金币获取",
image: "I389",
maxLv: 5,
pos: [9, 9],
need: [{ 召唤之书: 1 }],
exp: 50 * flags._hasSkill_["兔兔"],
},
狼狼: {
name: "狼狼",
text: "40MP召唤30+50*LV生命的灰太狼在场时提升勇士2+4*LV点攻击",
image: "I390",
maxLv: 5,
pos: [3, 9],
need: [{ 召唤之书: 1 }],
exp: 50 * flags._hasSkill_["狼狼"],
},
牛牛: {
name: "牛牛",
text: "40MP召唤50+40*LV生命的牛牛在场时提升勇士3%+5%*Lv伤害增幅",
image: "I391",
maxLv: 5,
pos: [5, 9],
need: [{ 召唤之书: 1 }],
exp: 50 * flags._hasSkill_["牛牛"],
},
花花: {
name: "花花",
text: "40MP召唤50+100*LV生命上限、具有同等生命的花花战后回复15%已损失生命值",
image: "I392",
maxLv: 5,
pos: [1, 9],
need: [{ 召唤之书: 1 }],
exp: 50 * flags._hasSkill_["花花"],
},
};
var Skill_2 = {
图腾之书: {
name: "图腾之书",
text: "学习后各图腾提升为1级获得图腾之书所有图腾共享20次公共战斗冷却",
image: "I394",
maxLv: 1,
pos: [1, 7],
need: null,
exp: 200,
},
再生: {
name: "再生",
text: "召唤兽:花花在场时可使用生命恢复提升10%*LV召唤兽:花花离场后移除",
image: "I392",
maxLv: 5,
pos: [1, 5],
need: [{ 图腾之书: 1 }],
exp: 50 * flags._hasSkill_["再生"],
},
嗜血: {
name: "嗜血",
text: "召唤兽:灰太狼在场时可使用勇士获得3%*LV吸血召唤兽:灰太狼离场后移除",
image: "I390",
maxLv: 5,
pos: [3, 5],
need: [{ 图腾之书: 1 }],
exp: 50 * flags._hasSkill_["嗜血"],
},
顽强: {
name: "顽强",
text: "召唤兽:牛牛在场时可使用除牛牛外所有召唤物战后增加5*Lv生命花花不可超过生命上限召唤兽:牛牛离场后移除",
image: "I391",
maxLv: 5,
pos: [5, 5],
need: [{ 图腾之书: 1 }],
exp: 50 * flags._hasSkill_["顽强"],
},
涅槃: {
name: "涅槃",
text: "召唤兽:蝴蝶在场时可使用战斗时任意召唤兽死亡时触发召唤兽临时消失该召唤兽生命变更为50*LV并在战斗结束后重新召唤此技能触发后战斗结束后移除",
image: "I388",
maxLv: 5,
pos: [7, 5],
need: [{ 图腾之书: 1 }],
exp: 50 * flags._hasSkill_["涅槃"],
},
贪婪: {
name: "贪婪",
text: "召唤兽:兔兔在场时可使用每次战斗后额外获得0.5*Lv点法力向上取整0和LV点金币召唤兽:兔兔离场后移除",
image: "I389",
maxLv: 5,
pos: [9, 5],
need: [{ 图腾之书: 1 }],
exp: 50 * flags._hasSkill_["贪婪"],
},
攻击: {
name: "攻击",
text: "被动提升勇者2点攻击",
image: "atk",
maxLv: null,
pos: [3, 9],
need: null,
exp: 20 + 5 * flags._hasSkill_["攻击"],
},
防御: {
name: "防御",
text: "被动提升勇者2点防御",
image: "def",
maxLv: null,
pos: [5, 9],
need: null,
exp: 20 + 5 * flags._hasSkill_["防御"],
},
魔力上限: {
name: "魔力上限",
text: "被动提升勇者10点MP上限",
image: "mana",
maxLv: null,
pos: [7, 9],
need: null,
exp: 30 + 10 * flags._hasSkill_["魔力上限"],
},
火球: {
name: "火球",
text: "消耗8MP第一回合不进行普通攻击改为发出一枚100%+LV*20%攻击伤害的火球",
image: "I397",
maxLv: 5,
pos: [3, 11],
need: null,
exp: 30 * flags._hasSkill_["火球"],
},
嘲讽: {
name: "嘲讽",
text: "消耗10MP本次强制怪物以勇士为目标同时勇士获得5%*LV减伤",
image: "I399",
maxLv: 5,
pos: [5, 11],
need: null,
exp: 30 + 30 * flags._hasSkill_["嘲讽"],
},
治疗: {
name: "治疗",
text: "消耗10点MP战斗时勇者第一回合改为为自己施加100%+20%*lv防御数值的治疗术",
image: "I396",
maxLv: 5,
pos: [7, 11],
need: null,
exp: 20 + 20 * flags._hasSkill_["治疗"],
},
};
var AllSkill = [Skill_1, Skill_2]; ///////////把每页技能数组id填入这里比如三页技能要加上Skill3
return AllSkill;
};
core.myskilltree = function () {
//进入
if (!flags._hasSkill_) flags._hasSkill_ = {};
core.clearUI();
core.status.holdingKeys = [];
core.lockControl();
core.status.event.page = 0;
core.status.event.data = 0;
let Skill = core.AllStatus()[core.status.event.page];
core.status.event.pos = Object.values(Skill)[0].pos;
//初始坐标
core.status.event.id = "myskilltree";
core.myskilltree_draw(); ///重绘页面
};
core.quit_myskilltree = function () {
core.clearUI();
core.status.event.id = null;
core.unlockControl();
if (core.isReplaying()) core.replay();
};
core.myskilltree_add = function () {
//加点
let Skill = core.AllStatus()[core.status.event.page];
var Index = core.status.event.data;
var id = Object.keys(Skill)[Index];
var data = Object.values(Skill)[Index];
let ness;
if (data.need) {
if (Array.isArray(data.need)) {
//多前置
for (var need of data.need) {
var need_name = Object.keys(need);
//if (!Object.keys(flags._hasSkill_).includes(need))
if (
!flags._hasSkill_[need_name] ||
flags._hasSkill_[need_name] < need[need_name]
) {
ness = true;
}
}
} else {
//单前置
if (!Object.keys(flags._hasSkill_).includes(data.need)) {
ness = true;
}
}
}
if (ness) {
core.drawTip(id + " 未习得前置");
} else if (hero.exp < data.exp) {
core.drawTip(id + " 经验值不足");
} else if (flags._hasSkill_[id] === data.maxLv) {
core.drawTip(id + " 技能已满级");
} else {
///学习成功
flags._hasSkill_[id] = flags._hasSkill_[id] + 1 || 1;
hero.exp -= data.exp;
core.myskilltree_get(id);
core.status.route.push(
"skill:" +
Index +
":" +
data.pos[0] +
":" +
data.pos[1] +
":" +
core.status.event.page
);
}
core.myskilltree_draw(); ///重绘页面
};
core.myskilltree_get = function (id) {
////技能升级效果
switch (id) {
case "图腾之书":
flags._hasSkill_["再生"] = 1;
flags._hasSkill_["嗜血"] = 1;
flags._hasSkill_["顽强"] = 1;
flags._hasSkill_["涅槃"] = 1;
flags._hasSkill_["贪婪"] = 1;
core.getItem("I394");
break;
case "攻击":
core.status.hero.atk += 2;
break;
case "防御":
core.status.hero.def += 2;
break;
case "魔力上限":
core.status.hero.manamax += 10;
break;
case "嘲讽":
core.getItem("I399");
break;
case "治疗":
core.getItem("I396");
break;
}
};
core.myskilltree_draw = function () {
//绘制 总
core.clearMap("ui");
let Skill = core.AllStatus()[core.status.event.page];
var Index = core.status.event.data;
///推荐在此处 drawImage 绘制背景图
core.setAlpha("ui", 0.8);
core.setFillStyle("ui", "#dddddd");
core.fillRect("ui", 0, 0, core.__PIXELS__, core.__PIXELS__, "#000000");
core.setAlpha("ui", 1);
let name = Object.keys(Skill)[Index];
let text = Object.values(Skill)[Index].text;
let exp = Object.values(Skill)[Index].exp;
core.myskilltree_draw_text(name, text, exp);
core.myskilltree_draw_tree();
core.setTextAlign("ui", "right");
core.fillText(
"ui",
"返回游戏",
342,
342,
"#ffffff",
ui.prototype._buildFont(15, true)
);
core.setTextAlign("ui", "center");
core.fillText(
"ui",
"上页",
352 / 2 - 50,
342,
"#ffffff",
ui.prototype._buildFont(15, true)
);
core.fillText(
"ui",
"下页",
352 / 2 + 50,
342,
"#ffffff",
ui.prototype._buildFont(15, true)
);
core.fillText(
"ui",
core.status.event.page + 1 + "/" + core.AllStatus().length,
352 / 2,
342,
"#ffffff",
ui.prototype._buildFont(15, true)
);
};
2024-12-27 11:55:28 +08:00
core.myskilltree_draw_tree = function () {
//绘制 树
let x0 = 0,
y0 = -80; //相对坐标
let nx = 32,
ny = 32; //间隔
let Skill = core.AllStatus()[core.status.event.page];
for (var value of Object.values(Skill)) {
//先绘制前置线条,否则会遮挡技能图标
let posx = value.pos[0];
let posy = value.pos[1];
//前置技能
if (value.need) {
if (Array.isArray(value.need)) {
//多前置
for (var need of value.need) {
var need_name = Object.keys(need);
//if (!Object.keys(flags._hasSkill_).includes(need))
if (
!flags._hasSkill_[need_name] ||
flags._hasSkill_[need_name] < need[need_name]
) {
core.setFilter("ui", "brightness(50%)grayscale(70%)");
core.setAlpha("ui", 0.7);
}
let need_x = Skill[need_name].pos[0];
let need_y = Skill[need_name].pos[1];
core.myskilltree_draw_line(
x0 + posx * nx + 16,
y0 + posy * ny + 16,
x0 + posx * nx + 16,
y0 + need_y * ny + 16,
1
); //y
core.myskilltree_draw_line(
x0 + posx * nx + 16,
y0 + need_y * ny + 16,
x0 + need_x * nx + 16,
y0 + need_y * ny + 16,
2
); //x
}
} else {
//单前置
if (!Object.keys(flags._hasSkill_).includes(value.need)) {
core.setFilter("ui", "brightness(50%)grayscale(70%)");
core.setAlpha("ui", 0.7);
}
let need_x = Skill[value.need].pos[0];
let need_y = Skill[value.need].pos[1];
core.myskilltree_draw_line(
x0 + posx * nx + 16,
y0 + posy * ny + 16,
x0 + posx * nx + 16,
y0 + need_y * ny + 16,
1
); //y
core.myskilltree_draw_line(
x0 + posx * nx + 16,
y0 + need_y * ny + 16,
x0 + need_x * nx + 16,
y0 + need_y * ny + 16,
2
); //x
}
}
core.setFilter("ui", "");
core.setAlpha("ui", 1);
}
core.setTextAlign("ui", "center");
let Ex = core.status.event.pos[0];
let Ey = core.status.event.pos[1];
//技能图标
for (var value of Object.values(Skill)) {
let posx = value.pos[0];
let posy = value.pos[1];
let image = value.image;
let boxx = x0 + posx * nx;
let boxy = y0 + posy * ny;
let name = value.name;
let lv = flags._hasSkill_[name] || 0;
//图标
core.myskilltree_draw_box(boxx, boxy, image);
//未学习前置的技能 黑色滤镜
let ness;
if (value.need) {
if (Array.isArray(value.need)) {
//多前置
for (var need of value.need) {
var need_name = Object.keys(need);
// if (!Object.keys(flags._hasSkill_).includes(need))
if (
!flags._hasSkill_[need_name] ||
flags._hasSkill_[need_name] < need[need_name]
) {
core.setFilter("ui", "brightness(0%)");
core.setAlpha("ui", 0.7);
ness = true;
}
}
} else {
//单前置
if (!Object.keys(flags._hasSkill_).includes(value.need)) {
core.setFilter("ui", "brightness(0%)");
core.setAlpha("ui", 0.7);
ness = true;
}
}
}
//未学习的技能 灰色滤镜
if (!ness && !flags._hasSkill_[name]) {
core.setFilter("ui", "brightness(50%)grayscale(70%)");
core.setAlpha("ui", 0.7);
}
core.drawIcon("ui", image, boxx, boxy, 32, 32);
//core.drawImage('ui', image, 0, 0, 32, 32, boxx, boxy, 32, 32);
core.setFilter("ui", "");
core.setAlpha("ui", 1);
if (Ex === posx && Ey === posy)
core.strokeRoundRect(
"ui",
boxx - 2,
boxy - 2,
36,
36,
4,
"#ffff80",
2
);
core.setAlpha("ui", 0.6);
core.fillRoundRect("ui", boxx, boxy + 16 + 8, 32, 16, 4, "#000000");
core.setAlpha("ui", 1);
core.fillText(
"ui",
lv + (value.maxLv == null ? "" : "/" + value.maxLv),
boxx + 16,
boxy + 16 + 8 + 15,
lv === value.maxLv ? "#ffff80" : "#ffffff",
ui.prototype._buildFont(15, true)
);
}
};
core.myskilltree_draw_text = function (name, text, exp) {
// 绘制 说明文本
let x0 = 0,
y0 = 50; //相对坐标
core.setTextAlign("ui", "left");
core.fillText(
"ui",
name,
x0 + 10,
y0 - 20,
"#ffffff",
ui.prototype._buildFont(22, true)
);
core.fillText(
"ui",
"升级经验:" + exp + "/" + hero.exp,
x0 + 200,
y0 - 20,
"#ffffff",
ui.prototype._buildFont(15, true)
);
var height = null;
var max_height = 110;
if (text) {
for (var fontSize = 17; fontSize >= 9; fontSize -= 2) {
var config = {
left: x0 + 10,
top: y0 - 12,
fontSize: 12,
maxWidth: 352 - 20,
bold: true,
color: "#E1E1E1",
};
height = 42 + core.getTextContentHeight(text, config);
if (height < max_height || fontSize == 9) {
core.drawTextContent("ui", text, config);
break;
}
}
}
};
2024-12-27 11:55:28 +08:00
core.myskilltree_draw_box = function (x, y, image) {
//盒子包装
let col = "#dddddd";
core.fillRoundRect("ui", x, y, 32, 32, 4, col);
core.strokeRoundRect("ui", x - 2, y - 2, 36, 36, 4, col, 1);
};
2024-12-27 11:55:28 +08:00
core.myskilltree_draw_line = function (x1, y1, x2, y2, type) {
//线 包装
//return;
let col = "#dddddd";
if (type === 1) {
core.drawLine("ui", x1 - 4, y1, x2 - 4, y2, col, 1);
core.drawLine("ui", x1 + 4, y1, x2 + 4, y2, col, 1);
core.drawLine("ui", x1, y1, x2, y2, col, 4);
} else if (type === 2) {
core.drawLine("ui", x1, y1 - 4, x2, y2 - 4, col, 1);
core.drawLine("ui", x1, y1 + 4, x2, y2 + 4, col, 1);
core.drawLine("ui", x1, y1, x2, y2, col, 4);
}
};
2024-12-27 11:55:28 +08:00
core.myChooseOnKey = function (dir) {
//计算方向键应选择的坐标
let Skill = core.AllStatus()[core.status.event.page];
let x = core.status.event.pos[0];
let y = core.status.event.pos[1];
var temp = [];
var temp2 = [];
for (var i in Object.values(Skill)) {
let posx = Object.values(Skill)[i].pos[0];
let posy = Object.values(Skill)[i].pos[1];
let Index = Number(i);
let ok =
(dir === "up" && posy < y) ||
(dir === "down" && posy > y) ||
(dir === "left" && posx < x) ||
(dir === "right" && posx > x);
let ok2 =
((dir === "up" || dir === "down") && x === posx) ||
((dir === "left" || dir === "right") && y === posy);
if (ok) temp.push([posx, posy, Index]);
if (ok && ok2) temp2.push([posx, posy, Index]);
}
if (temp2[0]) {
if (temp2.length === 1) {
return temp2[0];
} else {
let R = 999;
let rx, ry, ri;
for (var value of temp2) {
let posx = value[0];
let posy = value[1];
let Index = value[2];
let r = Math.abs(posx - x + posy - y);
if (r < R) {
R = r;
rx = posx;
ry = posy;
ri = Index;
}
}
return [rx, ry, ri];
}
} else if (temp.length === 1) {
return temp[0];
} else {
let R = 9999;
let rx, ry, ri;
for (var value of temp) {
let posx = value[0];
let posy = value[1];
let Index = value[2];
let r = Math.pow(posx - x, 2) + Math.pow(posy - y, 2);
if (r < R) {
R = r;
rx = posx;
ry = posy;
ri = Index;
}
}
if (rx && ry) return [rx, ry, ri];
}
};
2024-12-27 11:55:28 +08:00
this.myskilltree_keyDown = function (keycode) {
if (core.status.event.id !== "myskilltree") return false;
let Skill = core.AllStatus()[core.status.event.page];
let temp;
2024-12-27 11:55:28 +08:00
if (
keycode === 37 &&
!core.myChooseOnKey("left") &&
core.status.event.page > 0
) {
core.status.event.page = core.status.event.page - 1;
core.status.event.data = 0;
Skill = core.AllStatus()[core.status.event.page];
core.status.event.pos = Object.values(Skill)[0].pos;
} else if (
keycode === 39 &&
!core.myChooseOnKey("right") &&
core.status.event.page < core.AllStatus().length - 1
) {
core.status.event.page = core.status.event.page + 1;
core.status.event.data = 0;
Skill = core.AllStatus()[core.status.event.page];
core.status.event.pos = Object.values(Skill)[0].pos;
} else if (keycode === 38 && core.myChooseOnKey("up")) {
temp = core.myChooseOnKey("up");
} else if (keycode === 40 && core.myChooseOnKey("down")) {
temp = core.myChooseOnKey("down");
} else if (keycode === 37 && core.myChooseOnKey("left")) {
temp = core.myChooseOnKey("left");
} else if (keycode === 39 && core.myChooseOnKey("right")) {
temp = core.myChooseOnKey("right");
}
2024-12-27 11:55:28 +08:00
if (temp) {
core.status.event.pos[0] = temp[0];
core.status.event.pos[1] = temp[1];
core.status.event.data = temp[2];
}
if (keycode === 32 || keycode === 13 || keycode === 67)
core.myskilltree_add();
2024-12-27 11:55:28 +08:00
core.myskilltree_draw(); ///重绘页面
2024-12-27 11:55:28 +08:00
return true;
};
core.registerAction("keyDown", "myskilltree", "myskilltree_keyDown", 100);
2024-12-27 11:55:28 +08:00
var myskilltree = function (keycode) {
if (core.status.event.id !== "myskilltree") return false;
2024-12-27 11:55:28 +08:00
if (keycode == 88 || keycode == 70 || keycode == 27) {
////x、q和Esc退出
core.quit_myskilltree();
}
return true; ///全部拦截
};
core.registerAction("keyUp", "myskilltree", myskilltree, 100);
2024-12-27 11:55:28 +08:00
this.myskilltree_onmove = function (x, y, px, py) {
if (core.status.event.id !== "myskilltree") return false;
2024-12-27 11:55:28 +08:00
//if (core.domStyle.isVertical) return false
2024-12-27 11:55:28 +08:00
let x0 = 0,
y0 = -80; //相对坐标
let nx = 32,
ny = 32; //间隔
if (py >= 65 && py <= 320) {
let Skill = core.AllStatus()[core.status.event.page];
for (var i in Object.values(Skill)) {
let posx = Object.values(Skill)[i].pos[0];
let posy = Object.values(Skill)[i].pos[1];
let boxx = x0 + posx * nx;
let boxy = y0 + posy * ny;
if (px >= boxx && px <= boxx + 32 && py >= boxy && py <= boxy + 32) {
if (core.status.event.data !== Number(i)) {
core.status.event.pos[0] = posx;
core.status.event.pos[1] = posy;
core.status.event.data = Number(i);
core.myskilltree_draw(); ///重绘页面
}
//core.status.event.mouse = Number(i)
//if(core.status.event.mouse)
}
}
}
return true;
};
core.registerAction("onmove", "myskilltree", "myskilltree_onmove", 100);
2024-12-27 11:55:28 +08:00
core.registerAction(
"onclick",
"myskilltree",
function (x, y, px, py) {
if (core.status.event.id !== "myskilltree") return false;
2024-12-27 11:55:28 +08:00
let x0 = 0,
y0 = -80; //相对坐标
let nx = 32,
ny = 32; //间隔
let Skill = core.AllStatus()[core.status.event.page];
//console.log(px + ',' + py);
if (py >= 65 && py <= 320) {
for (var i in Object.values(Skill)) {
let posx = Object.values(Skill)[i].pos[0];
let posy = Object.values(Skill)[i].pos[1];
let boxx = x0 + posx * nx;
let boxy = y0 + posy * ny;
if (
px >= boxx &&
px <= boxx + 32 &&
py >= boxy &&
py <= boxy + 32
) {
if (core.status.event.data === Number(i)) {
core.myskilltree_add();
} else if (core.status.event.data !== Number(i)) {
core.status.event.pos[0] = posx;
core.status.event.pos[1] = posy;
core.status.event.data = Number(i);
core.myskilltree_draw(); ///重绘页面
}
}
}
} else if (px >= 280 && py >= 320) {
core.quit_myskilltree();
} else if (
px >= 90 &&
px <= 165 &&
py >= 320 &&
core.status.event.page > 0
) {
core.status.event.page = core.status.event.page - 1;
core.status.event.data = 0;
core.status.event.pos = Object.values(Skill)[0].pos;
core.myskilltree_draw(); ///重绘页面
} else if (
px >= 190 &&
px <= 280 &&
py >= 320 &&
core.status.event.page < core.AllStatus().length - 1
) {
core.status.event.page = core.status.event.page + 1;
core.status.event.data = 0;
core.status.event.pos = Object.values(Skill)[0].pos;
core.myskilltree_draw(); ///重绘页面
}
2024-12-27 11:55:28 +08:00
return true;
},
100
);
2024-12-27 11:55:28 +08:00
control.prototype._replayAction_skill = function (action) {
if (action.indexOf("skill:") != 0) return false;
core.myskilltree();
var pos = action.substring(6).split(":");
core.status.event.data = Number(pos[0]);
core.status.event.pos[0] = Number(pos[1]);
core.status.event.pos[1] = Number(pos[2]);
core.status.event.page = Number(pos[3]);
core.myskilltree_add();
//core.myskilltree_draw();
core.replay();
if (core.status.replay.speed == 24) {
core.quit_myskilltree();
return true;
}
setTimeout(function () {
core.quit_myskilltree();
//core.ui.closePanel();
}, core.control.__replay_getTimeout());
return true;
};
core.registerReplayAction("skill", control.prototype._replayAction_skill);
},
2025-01-18 17:49:44 +08:00
"animate": function () {
2024-12-27 11:55:28 +08:00
// -------------------- 插件说明 -------------------- //
// github仓库https://github.com/unanmed/animate
// npm包名mutate-animate
// npm地址https://www.npmjs.com/package/mutate-animate
// 不要去尝试读这个插件,这个插件是经过了打包的,不是人类可读的(
// 想读的话可以去github读
// 该插件是一个轻量型多功能动画插件,可以允许你使用内置或自定义的速率曲线或轨迹等
// 除此之外,你还可以自定义绘制函数,来让你的动画可视化
// -------------------- 安装说明 -------------------- //
// 直接复制到插件中即可注意所有插件中不能出现插件名为animate的插件
// 该插件分为动画和渐变两部分,教程分开,动画在前,渐变在后
// -------------------- 动画使用教程 -------------------- //
// 1. 首先创建一个异步函数
// async function ani() { }
// 2. 引入插件中的类和函数,引入内容要看个人需求,所有可用的函数在本插件末尾可以看到
// const { Animation, linear, bezier, circle, hyper, trigo, power, inverseTrigo, shake, sleep } = core.plugin.animate
// 3. 在函数内部创建一个动画
// const animate = new Animation();
// 4. 为动画创建一个绘制函数这里以绘制一个矩形为例当然也可以使用core.fillRect替代ctx.fillRect来绘制矩形
// const ctx = core.createCanvas('animate', 0, 0, 416, 416, 100);
// ctx.save();
// const fn = () => {
// ctx.restore();
// ctx.save();
// ctx.clearRect(0, 0, 800, 800);
// ctx.translate(animate.x, animate.y);
// ctx.rotate(animate.angle * Math.PI / 180);
// const size = animate.size;
// ctx.fillRect(-30 * size, -30 * size, 60 * size, 60 * size);
// }
// animate.ticker.add(fn);
// 5. 执行动画
// 下面先对一些概念进行解释
// 动画分为很多种内置的有move(移动至某一点) rotate(旋转) scale(放缩) moveAs(以指定路径移动) shake(震动)
// 对于不同的动画种类其所对应的属性也不同move moveAs shake均对应x和y这两个属性
// rotate对应anglescale对应size。你也可以自定义属性这个之后会提到
// 除了执行动画之外,这里还提供了三个等待函数,可以等待某个动画执行完毕,以及一个等待指定时长的函数
// 分别是animate.n(等待指定数量的动画执行完毕)
// animate.w(等待指定类型的动画执行完毕,也可以是自定义类型)
// animate.all(等待所有动画执行完毕)
// sleep(等待指定时长)
// 执行动画时,要求一个渐变函数,当然这个插件内置了非常丰富的渐变函数,也就是速率曲线。
// 线性渐变函数 linear(),该函数返回一个线性变化函数
// 三角渐变函数 trigo('sin' | 'sec', EaseMode),该函数返回一个指定属性的三角函数变化函数
// 其中EaseMode可以填'in' 'out' 'in-out' 'center'
// 分别表示 慢-快 快-慢 慢-快-慢 快-慢-快
// 幂函数渐变 power(n, EaseMode)该函数返回一个以x^n变化的函数n是指数
2024-12-27 11:55:28 +08:00
// 双曲渐变函数 hyper('sin' | 'tan' | 'sec', EaseMode),该函数返回一个双曲函数,分别是双曲正弦、双曲正切、双曲正割
2024-12-27 11:55:28 +08:00
// 反三角渐变函数 inverseTrigo('sin' | 'tan', EaseMode),该函数返回一个反三角函数
2024-12-27 11:55:28 +08:00
// 贝塞尔曲线渐变函数 bezier(...cps),参数为贝塞尔曲线的控制点纵坐标(横坐标不能自定义,毕竟一个时刻不能对应多个速率)
// 示例bezier(0.4, 0.2, 0.7); // 三个控制点的四次贝塞尔曲线渐变函数
2024-12-27 11:55:28 +08:00
// 了解完渐变函数以后,这里还有一个特殊的渐变函数-shake
// shake(power, timing),这个函数是一个震荡函数,会让一个值来回变化,实现震动的效果
// 其中power是震动的最大值timing是渐变函数描述了power在震动时大小的变化
// 下面,我们就可以进行动画的执行了,我们以 运动 + 旋转 + 放缩为例
// animate.mode(hyper('sin', 'out')) // 设置渐变函数为 双曲正弦 快 -> 慢,注意不能加分号
// .time(1000) // 设置动画的执行时间为1000毫秒
// .move(300, 300) // 移动至[300, 300]的位置
// .relative() // 设置相对模式为相对之前,与之前为相加的关系
// .mode(power(3, 'center')) // 设置为 x^3 快-慢-快 的渐变函数
// .time(3000)
// .rotate(720) // 旋转720度
// .absolute() // 设置相对模式为绝对
// .mode(trigo('sin', 'in')) // 设置渐变函数为 正弦 慢 -> 快
// .time(1500)
// .scale(3); // 放缩大小至3倍
2024-12-27 11:55:28 +08:00
// 这样,我们就把三种基础动画都执行了一遍,同时,这种写法非常直观,出现问题时也可以很快地找到问题所在
// 下面,我们需要等待动画执行完毕,因为同一种动画不可能同时执行两个
2024-12-01 14:52:03 +08:00
2024-12-27 11:55:28 +08:00
// await animate.n(1); // 等待任意一个动画执行完毕别把await忘了
// await animate.w('scale'); // 等待放缩动画执行完毕
// await animate.all(); // 等待所有动画执行完毕
// await sleep(1000); // 等待1000毫秒
2024-12-27 11:55:28 +08:00
// 下面,还有一个特殊的动画函数-moveAs
// 这是一个非常强大的函数,它允许你让你的物体按照指定路线运动
// 说到这,我们需要先了解一下运动函数。
// 该插件内置了两个运动函数,分别是圆形运动和贝塞尔曲线运动
2024-12-27 11:55:28 +08:00
// 圆形运动 circle(r, n, timing, inverse)r是圆的半径n是圈数timing描述半径大小的变化inverse说明了是否翻转timing函数后面三个可以不填
2024-12-27 11:55:28 +08:00
// 贝塞尔曲线 bezierPath(start, end, ...cps)
// 其中start和end是起点和结束点应当填入[x, y]数组cps是控制点也是[x, y]数组
// 示例bezierPath([0, 0], [200, 200], [100, 50], [300, 150], [200, 180]);
// 这是一个起点为 [0, 0],终点为[200, 200],有三个控制点的四次贝塞尔曲线
2024-12-27 11:55:28 +08:00
// 下面,我们就可以使用路径函数了
2024-12-27 11:55:28 +08:00
// animate.mode(hyper('sin', 'in-out')) // 设置渐变曲线
// .time(5000)
// .relative() // 设置为相对模式,这个比较必要,不然的话很可能出现瞬移
// .moveAs(circle(100, 5, linear())) // 创建一个5圈的半径从0至100逐渐变大的圆轨迹是个螺旋线并让物体沿着它运动
//
// 最后,还有一个震动函数 shake(x, y)x和y表示了在横向上和纵向上的震动幅度1表示为震动幅度的100%
// 示例:
// animate.mode(shake(5, hyper('sin', 'in')), true) // 这里第二个参数说明是震动函数
// .time(2000)
// .shake(1, 0.5)
2024-12-27 11:55:28 +08:00
// 这样,所有内置动画就已经介绍完毕
2024-12-27 11:55:28 +08:00
// 6. 自定义动画属性
2024-12-27 11:55:28 +08:00
// 本插件允许你自定义一个动画属性,但功能可能不会像自带的属性那么强大
// 你可以在创建动画之后使用animate.register(key, init)来注册一个自定义属性
// 其中key是自定义属性的名称init是自定义属性的初始值这个值应当在0-1之间变化
2024-12-27 11:55:28 +08:00
// 你可以通过animate.value[key]来获取你注册的自定义属性
2024-12-27 11:55:28 +08:00
// 对于自定义属性的动画你应当使用animate.apply(key, n, first)
// 其中key是你的自定义属性的名称n是其目标值first是一个布尔值说明了是否将该动画插入到目前所有的动画之前即每帧会优先执行该动画
2024-12-27 11:55:28 +08:00
// 下面是一个不透明度的示例
2024-12-27 11:55:28 +08:00
// animate.register('opacity', 1); // 这句话应该放到刚创建动画之后
2024-12-27 11:55:28 +08:00
// ctx.globalAlpha = animate.value.opacity; // 这句话应当放到每帧绘制的函数里面,放在绘制之前
2024-12-27 11:55:28 +08:00
// animate.mode(bezier(0.9, 0.1, 0.05)) // 设置渐变函数
// .time(2000)
// .absolute()
// .apply('opacity', 0.3); // 将不透明度按照渐变曲线更改为0.3
2024-12-27 11:55:28 +08:00
// 7. 运行动画
2024-12-27 11:55:28 +08:00
// 还记得刚开始定义的async function 吗,直接调用它就能执行动画了!
// 示例ani(); // 执行刚刚写的所有动画
// 8. 自定义速率曲线和路径
// 该插件中,速率曲线和路径均可自定义
// 对于速率曲线,其类型为 (input: number) => number
// 它接受一个范围在 0-1 的值,输出一个 0-1 的值表示了动画的完成度1表示动画已完成0表示动画刚开始当前大于1小于0也不会报错也会执行相应的动画
// 对于路径,其类型为 (input: number) => [number, number]
// 它与速率曲线类似,接收一个 0-1 的值,输出一个坐标数组
// 9. 多个属性绑定
// 该插件中你可以绑定多个动画属性你可以使用ani.bind(...attr)来绑定。
// 绑定之后这三个动画属性可以被一个返回了长度为3的数组的渐变函数执行。
// 绑定使用ani.bind设置渐变函数仍然使用ani.mode注意它与单个动画属性是分开的也就是它不会影响正常的渐变函数。
// 然后使用ani.applyMulti即可执行动画
// 例如:
// // 自定义的一个三属性渐变函数
// function b(input) {
// return [input * 100, input ** 2 * 100, input ** 3 * 100];
// }
// ani.bind('a', 'b', 'c') // 这样会绑定abc这三个动画属性
// .mode(b) // 自定义的一个返回了长度为3的数组的函数
// .time(5000)
// .absolute()
// .applyMulti(); // 执行这个动画
// 9. 监听 动画的生命周期钩子
// 这个插件还允许你去监听动画的状态,可以监听动画的开始、结束、运行
// 你可以使用 animate.listen(type, fn)来监听fn的类型是 (a: Animation, type: string) => void
// 当然,一般情况下你不会用到这个功能,插件中已经帮你包装了三个等待函数,他们就是以这些监听为基础的
// 10. 自定义时间获取函数
// 你可以修改ani.getTime来修改动画的时间获取函数例如想让动画速度变成一半可以写ani.getTime = () => Date.now() / 2
// 这样可以允许你随意控制动画的运行速度,暂停,甚至是倒退。该值默认为`Date.now`
// -------------------- 渐变使用教程 -------------------- //
// 相比于动画,渐变属于一种较为简便的动画,它可以让你在设置一个属性后使属性缓慢变化值目标值而不是突变至目标值
// 现在假设你已经了解了动画的使用,下面我们来了解渐变。
// 1. 创建一个渐变实例
// 与动画类似你需要使用new来实例化一个渐变当然别忘了引入
// const { Transition } = core.plugin.animate;
// const tran = new Transition();
// 2. 绘制
// const ctx = core.createCanvas('transition', 0, 0, 416, 416, 100);
// ctx.save();
// const fn = () => {
// ctx.restore();
// ctx.save();
// ctx.clearRect(0, 0, 800, 800);
// ctx.beginPath();
// ctx.arc(tran.value.x, tran.value.y, 50, 0, Math.PI * 2); // 使用tran.value.xxx获取当前的属性
// ctx.fill();
// // 当然也可以用样板的api例如core.fillCircle();等
// }
// animate.ticker.add(fn);
// 3. 设置渐变
// 同样与动画类似你可以使用tran.time()设置渐变时间使用tran.mode()设置渐变函数使用tran.absolute()和tran.relative()设置相对模式
// 例如:
// tran.time(1000)
// .mode(hyper('sin', 'out'))
// .absolute();
// 4. 初始化渐变属性
// 与动画不同的是动画在执行一个自定义属性前都需要register而渐变不需要。
// 你可以通过tran.value.xxx = yyy来设置动画属性或使用tran.transition('xxx', yyy)来设置
// 你的首次赋值即是初始化了渐变属性,这时是不会执行渐变的,例如:
// tran.value.x = 200;
// tran.transition('y', 200);
// 上述例子便是将 x 和 y 初始化成了200
// 5. 执行渐变
// 初始化完成后,便可以直接执行渐变了,有两种方法
// tran.value.x = 400; // 将 x 缓慢移动至400
// tran.transition('y', 400); // 将 y 缓慢移动至400
// 6. 自定义时间获取函数
// 与动画类似你依然可以通过修改tran.getTime来修改时间获取函数
if (main.replayChecking) return (core.plugin.animate = {});
var M = Object.defineProperty;
2025-01-19 20:20:48 +08:00
var E = (n, i, t) =>
i in n
? M(n, i, { enumerable: !0, configurable: !0, writable: !0, value: t })
: (n[i] = t);
var o = (n, i, t) => (E(n, typeof i != "symbol" ? i + "" : i, t), t);
let w = [];
2024-12-27 11:55:28 +08:00
const k = (n) => {
2025-01-19 20:20:48 +08:00
for (const i of w)
if (i.status === "running")
2024-12-27 11:55:28 +08:00
try {
2025-01-19 20:20:48 +08:00
for (const t of i.funcs) t(n - i.startTime);
2024-12-27 11:55:28 +08:00
} catch (t) {
2025-01-19 20:20:48 +08:00
i.destroy(), console.error(t);
2024-12-27 11:55:28 +08:00
}
requestAnimationFrame(k);
};
requestAnimationFrame(k);
class I {
constructor() {
2025-01-19 20:20:48 +08:00
o(this, "funcs", /* @__PURE__ */ new Set());
2024-12-27 11:55:28 +08:00
o(this, "status", "stop");
o(this, "startTime", 0);
(this.status = "running"),
2025-01-19 20:20:48 +08:00
w.push(this),
requestAnimationFrame((i) => (this.startTime = i));
2024-12-27 11:55:28 +08:00
}
2025-01-19 20:20:48 +08:00
add(i) {
return this.funcs.add(i), this;
2024-12-27 11:55:28 +08:00
}
2025-01-19 20:20:48 +08:00
remove(i) {
return this.funcs.delete(i), this;
2024-12-27 11:55:28 +08:00
}
clear() {
2025-01-19 20:20:48 +08:00
this.funcs.clear();
2024-12-27 11:55:28 +08:00
}
destroy() {
this.clear(), this.stop();
}
stop() {
2025-01-19 20:20:48 +08:00
(this.status = "stop"), (w = w.filter((i) => i !== this));
2024-12-27 11:55:28 +08:00
}
}
class F {
constructor() {
o(this, "timing");
o(this, "relation", "absolute");
o(this, "easeTime", 0);
o(this, "applying", {});
o(this, "getTime", Date.now);
o(this, "ticker", new I());
o(this, "value", {});
o(this, "listener", {});
2025-01-19 20:20:48 +08:00
this.timing = (i) => i;
2024-12-27 11:55:28 +08:00
}
async all() {
2025-01-19 20:20:48 +08:00
if (Object.values(this.applying).every((i) => i === !0))
2024-12-27 11:55:28 +08:00
throw new ReferenceError("There is no animates to be waited.");
2025-01-19 20:20:48 +08:00
await new Promise((i) => {
2024-12-27 11:55:28 +08:00
const t = () => {
Object.values(this.applying).every((e) => e === !1) &&
2025-01-19 20:20:48 +08:00
(this.unlisten("end", t), i("all animated."));
2024-12-27 11:55:28 +08:00
};
this.listen("end", t);
});
}
2025-01-19 20:20:48 +08:00
async n(i) {
const t = Object.values(this.applying).filter((s) => s === !0).length;
if (t < i)
2024-12-27 11:55:28 +08:00
throw new ReferenceError(
2025-01-19 20:20:48 +08:00
`You are trying to wait ${i} animate, but there are only ${t} animate animating.`
2024-12-27 11:55:28 +08:00
);
let e = 0;
2025-01-19 20:20:48 +08:00
await new Promise((s) => {
2024-12-27 11:55:28 +08:00
const r = () => {
2025-01-19 20:20:48 +08:00
e++, e === i && (this.unlisten("end", r), s(`${i} animated.`));
2024-12-27 11:55:28 +08:00
};
this.listen("end", r);
});
}
2025-01-19 20:20:48 +08:00
async w(i) {
if (this.applying[i] === !1)
throw new ReferenceError(`The ${i} animate is not animating.`);
2024-12-27 11:55:28 +08:00
await new Promise((t) => {
const e = () => {
2025-01-19 20:20:48 +08:00
this.applying[i] === !1 &&
(this.unlisten("end", e), t(`${i} animated.`));
2024-12-27 11:55:28 +08:00
};
this.listen("end", e);
});
}
2025-01-19 20:20:48 +08:00
listen(i, t) {
var e, s;
(s = (e = this.listener)[i]) != null || (e[i] = []),
this.listener[i].push(t);
2024-12-27 11:55:28 +08:00
}
2025-01-19 20:20:48 +08:00
unlisten(i, t) {
const e = this.listener[i].findIndex((s) => s === t);
2024-12-27 11:55:28 +08:00
if (e === -1)
throw new ReferenceError(
"You are trying to remove a nonexistent listener."
);
2025-01-19 20:20:48 +08:00
this.listener[i].splice(e, 1);
2024-12-27 11:55:28 +08:00
}
2025-01-19 20:20:48 +08:00
hook(...i) {
const t = Object.entries(this.listener).filter((e) => i.includes(e[0]));
for (const [e, s] of t) for (const r of s) r(this, e);
2024-12-27 11:55:28 +08:00
}
}
2025-01-19 20:20:48 +08:00
function y(n) {
2024-12-27 11:55:28 +08:00
return n != null;
}
async function R(n) {
2025-01-19 20:20:48 +08:00
return new Promise((i) => setTimeout(i, n));
2024-12-27 11:55:28 +08:00
}
2025-01-19 20:20:48 +08:00
class j extends F {
2024-12-27 11:55:28 +08:00
constructor() {
super();
o(this, "shakeTiming");
o(this, "path");
o(this, "multiTiming");
o(this, "value", {});
o(this, "size", 1);
o(this, "angle", 0);
o(this, "targetValue", {
system: {
move: [0, 0],
moveAs: [0, 0],
resize: 0,
rotate: 0,
shake: 0,
"@@bind": [],
},
custom: {},
});
o(this, "animateFn", {
system: {
move: [() => 0, () => 0],
moveAs: () => 0,
resize: () => 0,
rotate: () => 0,
shake: () => 0,
"@@bind": () => 0,
},
custom: {},
});
o(this, "ox", 0);
o(this, "oy", 0);
o(this, "sx", 0);
o(this, "sy", 0);
o(this, "bindInfo", []);
(this.timing = (t) => t),
(this.shakeTiming = (t) => t),
(this.multiTiming = (t) => [t, t]),
(this.path = (t) => [t, t]),
(this.applying = {
move: !1,
scale: !1,
rotate: !1,
shake: !1,
}),
this.ticker.add(() => {
const { running: t } = this.listener;
2025-01-19 20:20:48 +08:00
if (y(t)) for (const e of t) e(this, "running");
2024-12-27 11:55:28 +08:00
});
}
get x() {
return this.ox + this.sx;
}
get y() {
return this.oy + this.sy;
}
mode(t, e = !1) {
return (
typeof t(0) == "number"
? e
? (this.shakeTiming = t)
: (this.timing = t)
: (this.multiTiming = t),
this
);
}
time(t) {
return (this.easeTime = t), this;
}
relative() {
return (this.relation = "relative"), this;
}
absolute() {
return (this.relation = "absolute"), this;
}
bind(...t) {
return (
this.applying["@@bind"] === !0 && this.end(!1, "@@bind"),
(this.bindInfo = t),
this
);
}
unbind() {
return (
this.applying["@@bind"] === !0 && this.end(!1, "@@bind"),
(this.bindInfo = []),
this
);
}
move(t, e) {
return (
this.applying.move && this.end(!0, "move"),
this.applySys("ox", t, "move"),
this.applySys("oy", e, "move"),
this
);
}
rotate(t) {
return this.applySys("angle", t, "rotate"), this;
}
scale(t) {
return this.applySys("size", t, "resize"), this;
}
shake(t, e) {
this.applying.shake === !0 && this.end(!0, "shake"),
(this.applying.shake = !0);
2025-01-19 20:20:48 +08:00
const { easeTime: s, shakeTiming: r } = this,
l = this.getTime();
if ((this.hook("start", "shakestart"), s <= 0))
2024-12-27 11:55:28 +08:00
return this.end(!1, "shake"), this;
2025-01-19 20:20:48 +08:00
const a = () => {
const c = this.getTime() - l;
if (c > s) {
this.ticker.remove(a),
2024-12-27 11:55:28 +08:00
(this.applying.shake = !1),
(this.sx = 0),
(this.sy = 0),
this.hook("end", "shakeend");
return;
}
2025-01-19 20:20:48 +08:00
const h = c / s,
m = r(h);
2024-12-27 11:55:28 +08:00
(this.sx = m * t), (this.sy = m * e);
};
2025-01-19 20:20:48 +08:00
return this.ticker.add(a), (this.animateFn.system.shake = a), this;
2024-12-27 11:55:28 +08:00
}
moveAs(t) {
this.applying.moveAs && this.end(!0, "moveAs"),
(this.applying.moveAs = !0),
(this.path = t);
2025-01-19 20:20:48 +08:00
const { easeTime: e, relation: s, timing: r } = this,
l = this.getTime(),
[a, u] = [this.x, this.y],
[c, h] = (() => {
if (s === "absolute") return t(1);
2024-12-27 11:55:28 +08:00
{
const [d, f] = t(1);
2025-01-19 20:20:48 +08:00
return [a + d, u + f];
2024-12-27 11:55:28 +08:00
}
})();
if ((this.hook("start", "movestart"), e <= 0))
return this.end(!1, "moveAs"), this;
const m = () => {
2025-01-19 20:20:48 +08:00
const f = this.getTime() - l;
2024-12-27 11:55:28 +08:00
if (f > e) {
this.end(!0, "moveAs");
return;
}
2025-01-19 20:20:48 +08:00
const g = f / e,
[v, x] = t(r(g));
s === "absolute"
? ((this.ox = v), (this.oy = x))
: ((this.ox = a + v), (this.oy = u + x));
2024-12-27 11:55:28 +08:00
};
return (
2025-01-19 20:20:48 +08:00
this.ticker.add(m),
2024-12-27 11:55:28 +08:00
(this.animateFn.system.moveAs = m),
2025-01-19 20:20:48 +08:00
(this.targetValue.system.moveAs = [c, h]),
2024-12-27 11:55:28 +08:00
this
);
}
register(t, e) {
if (typeof this.value[t] == "number")
return this.error(
`Property ${t} has been regietered twice.`,
"reregister"
);
(this.value[t] = e), (this.applying[t] = !1);
}
2025-01-19 20:20:48 +08:00
apply(t, e) {
2024-12-27 11:55:28 +08:00
this.applying[t] === !0 && this.end(!1, t),
t in this.value ||
this.error(`You are trying to execute nonexistent property ${t}.`),
(this.applying[t] = !0);
2025-01-19 20:20:48 +08:00
const s = this.value[t],
r = this.getTime(),
{ timing: l, relation: a, easeTime: u } = this,
c = a === "absolute" ? e - s : e;
if ((this.hook("start"), u <= 0)) return this.end(!1, t), this;
const h = () => {
const d = this.getTime() - r;
if (d > u) {
2024-12-27 11:55:28 +08:00
this.end(!1, t);
return;
}
2025-01-19 20:20:48 +08:00
const f = d / u,
g = l(f);
this.value[t] = s + g * c;
2024-12-27 11:55:28 +08:00
};
return (
2025-01-19 20:20:48 +08:00
this.ticker.add(h),
(this.animateFn.custom[t] = h),
(this.targetValue.custom[t] = c + s),
2024-12-27 11:55:28 +08:00
this
);
}
2025-01-19 20:20:48 +08:00
applyMulti() {
2024-12-27 11:55:28 +08:00
this.applying["@@bind"] === !0 && this.end(!1, "@@bind"),
(this.applying["@@bind"] = !0);
2025-01-19 20:20:48 +08:00
const t = this.bindInfo,
e = t.map((h) => this.value[h]),
s = this.getTime(),
{ multiTiming: r, relation: l, easeTime: a } = this,
u = r(1);
if (u.length !== e.length)
2024-12-27 11:55:28 +08:00
throw new TypeError(
2025-01-19 20:20:48 +08:00
`The number of binded animate attributes and timing function returns's length does not match. binded: ${t.length}, timing: ${u.length}`
2024-12-27 11:55:28 +08:00
);
2025-01-19 20:20:48 +08:00
if ((this.hook("start"), a <= 0)) return this.end(!1, "@@bind"), this;
const c = () => {
const m = this.getTime() - s;
if (m > a) {
2024-12-27 11:55:28 +08:00
this.end(!1, "@@bind");
return;
}
2025-01-19 20:20:48 +08:00
const d = m / a,
f = r(d);
t.forEach((g, v) => {
2024-12-27 11:55:28 +08:00
l === "absolute"
2025-01-19 20:20:48 +08:00
? (this.value[g] = f[v])
: (this.value[g] = e[v] + f[v]);
2024-12-27 11:55:28 +08:00
});
};
return (
2025-01-19 20:20:48 +08:00
this.ticker.add(c),
(this.animateFn.custom["@@bind"] = c),
(this.targetValue.system["@@bind"] = u),
2024-12-27 11:55:28 +08:00
this
);
}
2025-01-19 20:20:48 +08:00
applySys(t, e, s) {
s !== "move" && this.applying[s] === !0 && this.end(!0, s),
(this.applying[s] = !0);
2024-12-27 11:55:28 +08:00
const r = this[t],
2025-01-19 20:20:48 +08:00
l = this.getTime(),
a = this.timing,
2024-12-27 11:55:28 +08:00
u = this.relation,
c = this.easeTime,
2025-01-19 20:20:48 +08:00
h = u === "absolute" ? e - r : e;
if ((this.hook("start", `${s}start`), c <= 0)) return this.end(!0, s);
2024-12-27 11:55:28 +08:00
const m = () => {
2025-01-19 20:20:48 +08:00
const f = this.getTime() - l;
2024-12-27 11:55:28 +08:00
if (f > c) {
2025-01-19 20:20:48 +08:00
this.end(!0, s);
2024-12-27 11:55:28 +08:00
return;
}
2025-01-19 20:20:48 +08:00
const g = f / c,
v = a(g);
(this[t] = r + h * v), t !== "oy" && this.hook(s);
2024-12-27 11:55:28 +08:00
};
2025-01-19 20:20:48 +08:00
this.ticker.add(m),
2024-12-27 11:55:28 +08:00
t === "ox"
? (this.animateFn.system.move[0] = m)
: t === "oy"
? (this.animateFn.system.move[1] = m)
2025-01-19 20:20:48 +08:00
: (this.animateFn.system[s] = m),
s === "move"
? (t === "ox" && (this.targetValue.system.move[0] = h + r),
t === "oy" && (this.targetValue.system.move[1] = h + r))
: s !== "shake" && (this.targetValue.system[s] = h + r);
2024-12-27 11:55:28 +08:00
}
error(t, e) {
throw e === "repeat"
? new Error(`Cannot execute the same animation twice. Info: ${t}`)
: e === "reregister"
? new Error(`Cannot register an animated property twice. Info: ${t}`)
: new Error(t);
}
end(t, e) {
if (t === !0)
if (
((this.applying[e] = !1),
e === "move"
? (this.ticker.remove(this.animateFn.system.move[0]),
this.ticker.remove(this.animateFn.system.move[1]))
: e === "moveAs"
? this.ticker.remove(this.animateFn.system.moveAs)
: e === "@@bind"
? this.ticker.remove(this.animateFn.system["@@bind"])
: this.ticker.remove(this.animateFn.system[e]),
e === "move")
) {
2025-01-19 20:20:48 +08:00
const [s, r] = this.targetValue.system.move;
(this.ox = s), (this.oy = r), this.hook("moveend", "end");
2024-12-27 11:55:28 +08:00
} else if (e === "moveAs") {
2025-01-19 20:20:48 +08:00
const [s, r] = this.targetValue.system.moveAs;
(this.ox = s), (this.oy = r), this.hook("moveend", "end");
2024-12-27 11:55:28 +08:00
} else
e === "rotate"
? ((this.angle = this.targetValue.system.rotate),
this.hook("rotateend", "end"))
: e === "resize"
? ((this.size = this.targetValue.system.resize),
this.hook("resizeend", "end"))
: e === "@@bind"
2025-01-19 20:20:48 +08:00
? this.bindInfo.forEach((r, l) => {
this.value[r] = this.targetValue.system["@@bind"][l];
2024-12-27 11:55:28 +08:00
})
: ((this.sx = 0), (this.sy = 0), this.hook("shakeend", "end"));
else
(this.applying[e] = !1),
this.ticker.remove(this.animateFn.custom[e]),
(this.value[e] = this.targetValue.custom[e]),
this.hook("end");
}
}
2025-01-19 20:20:48 +08:00
class O extends F {
2024-12-27 11:55:28 +08:00
constructor() {
super();
o(this, "now", {});
o(this, "target", {});
o(this, "transitionFn", {});
o(this, "value");
2025-01-19 20:20:48 +08:00
o(this, "handleSet", (t, e, s) => (this.transition(e, s), !0));
2024-12-27 11:55:28 +08:00
o(this, "handleGet", (t, e) => this.now[e]);
(this.timing = (t) => t),
(this.value = new Proxy(this.target, {
set: this.handleSet,
get: this.handleGet,
}));
}
mode(t) {
return (this.timing = t), this;
}
time(t) {
return (this.easeTime = t), this;
}
relative() {
return (this.relation = "relative"), this;
}
absolute() {
return (this.relation = "absolute"), this;
}
transition(t, e) {
if (e === this.target[t]) return this;
2025-01-19 20:20:48 +08:00
if (!y(this.now[t])) return (this.now[t] = e), this;
2024-12-27 11:55:28 +08:00
this.applying[t] && this.end(t, !0),
(this.applying[t] = !0),
this.hook("start");
2025-01-19 20:20:48 +08:00
const s = this.getTime(),
2024-12-27 11:55:28 +08:00
r = this.easeTime,
2025-01-19 20:20:48 +08:00
l = this.timing,
a = this.now[t],
u = e + (this.relation === "absolute" ? 0 : a),
c = u - a;
2024-12-27 11:55:28 +08:00
this.target[t] = u;
2025-01-19 20:20:48 +08:00
const h = () => {
const d = this.getTime() - s;
2024-12-27 11:55:28 +08:00
if (d >= r) {
this.end(t);
return;
}
const f = d / r;
2025-01-19 20:20:48 +08:00
(this.now[t] = l(f) * c + a), this.hook("running");
2024-12-27 11:55:28 +08:00
};
return (
2025-01-19 20:20:48 +08:00
(this.transitionFn[t] = h),
this.ticker.add(h),
r <= 0 ? (this.end(t), this) : this
2024-12-27 11:55:28 +08:00
);
}
end(t, e = !1) {
2025-01-19 20:20:48 +08:00
const s = this.transitionFn[t];
if (!y(s))
2024-12-27 11:55:28 +08:00
throw new ReferenceError(
`You are trying to end an ended transition: ${t}`
);
this.ticker.remove(this.transitionFn[t]),
delete this.transitionFn[t],
(this.applying[t] = !1),
this.hook("end"),
e || (this.now[t] = this.target[t]);
}
}
2025-01-19 20:20:48 +08:00
const T = (...n) => n.reduce((i, t) => i + t, 0),
b = (n) => {
2024-12-27 11:55:28 +08:00
if (n === 0) return 1;
2025-01-19 20:20:48 +08:00
let i = n;
for (; n > 1; ) n--, (i *= n);
return i;
2024-12-27 11:55:28 +08:00
},
2025-01-19 20:20:48 +08:00
A = (n, i) => Math.round(b(i) / (b(n) * b(i - n))),
p = (n, i, t = (e) => 1 - i(1 - e)) =>
2024-12-27 11:55:28 +08:00
n === "in"
2025-01-19 20:20:48 +08:00
? i
2024-12-27 11:55:28 +08:00
: n === "out"
? t
: n === "in-out"
2025-01-19 20:20:48 +08:00
? (e) => (e < 0.5 ? i(e * 2) / 2 : 0.5 + t((e - 0.5) * 2) / 2)
: (e) => (e < 0.5 ? t(e * 2) / 2 : 0.5 + i((e - 0.5) * 2) / 2),
2024-12-27 11:55:28 +08:00
$ = Math.cosh(2),
z = Math.acosh(2),
V = Math.tanh(3),
P = Math.atan(5);
2025-01-19 20:20:48 +08:00
function Y() {
2024-12-27 11:55:28 +08:00
return (n) => n;
}
function q(...n) {
2025-01-19 20:20:48 +08:00
const i = [0].concat(n);
i.push(1);
const t = i.length,
2024-12-27 11:55:28 +08:00
e = Array(t)
.fill(0)
2025-01-19 20:20:48 +08:00
.map((s, r) => A(r, t - 1));
return (s) => {
const r = e.map((l, a) => l * i[a] * (1 - s) ** (t - a - 1) * s ** a);
return T(...r);
2024-12-27 11:55:28 +08:00
};
}
2025-01-19 20:20:48 +08:00
function U(n, i) {
2024-12-27 11:55:28 +08:00
if (n === "sin") {
2025-01-19 20:20:48 +08:00
const t = (s) => Math.sin((s * Math.PI) / 2);
return p(i, (s) => 1 - t(1 - s), t);
2024-12-27 11:55:28 +08:00
}
if (n === "sec") {
2025-01-19 20:20:48 +08:00
const t = (s) => 1 / Math.cos(s);
return p(i, (s) => t((s * Math.PI) / 3) - 1);
2024-12-27 11:55:28 +08:00
}
throw new TypeError(
"Unexpected parameters are delivered in trigo timing function."
);
}
2025-01-19 20:20:48 +08:00
function C(n, i) {
2024-12-27 11:55:28 +08:00
if (!Number.isInteger(n))
throw new TypeError(
"The first parameter of power timing function only allow integer."
);
2025-01-19 20:20:48 +08:00
return p(i, (e) => e ** n);
2024-12-27 11:55:28 +08:00
}
2025-01-19 20:20:48 +08:00
function G(n, i) {
if (n === "sin") return p(i, (e) => (Math.cosh(e * 2) - 1) / ($ - 1));
2024-12-27 11:55:28 +08:00
if (n === "tan") {
2025-01-19 20:20:48 +08:00
const t = (s) => (Math.tanh(s * 3) * 1) / V;
return p(i, (s) => 1 - t(1 - s), t);
2024-12-27 11:55:28 +08:00
}
if (n === "sec") {
2025-01-19 20:20:48 +08:00
const t = (s) => 1 / Math.cosh(s);
return p(i, (s) => 1 - (t(s * z) - 0.5) * 2);
2024-12-27 11:55:28 +08:00
}
throw new TypeError(
"Unexpected parameters are delivered in hyper timing function."
);
}
2025-01-19 20:20:48 +08:00
function N(n, i) {
2024-12-27 11:55:28 +08:00
if (n === "sin") {
2025-01-19 20:20:48 +08:00
const t = (s) => (Math.asin(s) / Math.PI) * 2;
return p(i, (s) => 1 - t(1 - s), t);
2024-12-27 11:55:28 +08:00
}
if (n === "tan") {
2025-01-19 20:20:48 +08:00
const t = (s) => Math.atan(s * 5) / P;
return p(i, (s) => 1 - t(1 - s), t);
2024-12-27 11:55:28 +08:00
}
throw new TypeError(
"Unexpected parameters are delivered in inverse trigo timing function."
);
}
2025-01-19 20:20:48 +08:00
function B(n, i = () => 1) {
2024-12-27 11:55:28 +08:00
let t = -1;
return (e) => (
2025-01-19 20:20:48 +08:00
(t *= -1), e < 0.5 ? n * i(e * 2) * t : n * i((1 - e) * 2) * t
2024-12-27 11:55:28 +08:00
);
}
2025-01-19 20:20:48 +08:00
function D(n, i = 1, t = [0, 0], e = 0, s = (l) => 1, r = !1) {
return (l) => {
const a = i * l * Math.PI * 2 + (e * Math.PI) / 180,
u = Math.cos(a),
c = Math.sin(a),
h = n * s(s(r ? 1 - l : l));
return [h * u + t[0], h * c + t[1]];
2024-12-27 11:55:28 +08:00
};
}
2025-01-19 20:20:48 +08:00
function H(n, i, ...t) {
2024-12-27 11:55:28 +08:00
const e = [n].concat(t);
2025-01-19 20:20:48 +08:00
e.push(i);
const s = e.length,
r = Array(s)
2024-12-27 11:55:28 +08:00
.fill(0)
2025-01-19 20:20:48 +08:00
.map((l, a) => A(a, s - 1));
return (l) => {
const a = r.map(
(c, h) => c * e[h][0] * (1 - l) ** (s - h - 1) * l ** h
2024-12-27 11:55:28 +08:00
),
2025-01-19 20:20:48 +08:00
u = r.map((c, h) => c * e[h][1] * (1 - l) ** (s - h - 1) * l ** h);
return [T(...a), T(...u)];
2024-12-27 11:55:28 +08:00
};
}
if ("animate" in core.plugin)
throw new ReferenceError(`插件中已存在名为animate的属性`);
core.plugin.animate = {
2025-01-19 20:20:48 +08:00
Animation: j,
2024-12-27 11:55:28 +08:00
AnimationBase: F,
Ticker: I,
2025-01-19 20:20:48 +08:00
Transition: O,
2024-12-27 11:55:28 +08:00
bezier: q,
2025-01-19 20:20:48 +08:00
bezierPath: H,
circle: D,
2024-12-27 11:55:28 +08:00
hyper: G,
2025-01-19 20:20:48 +08:00
linear: Y,
power: C,
2024-12-27 11:55:28 +08:00
shake: B,
2025-01-19 20:20:48 +08:00
sleep: R,
trigo: U,
inverseTrigo: N,
2024-12-27 11:55:28 +08:00
};
},
2025-01-18 17:49:44 +08:00
"func": function () {
2024-12-27 11:55:28 +08:00
// 功能函数集,具体有哪些函数看每个函数前的注释即可
// 安装方式:直接复制到插件里面,注意新建插件自带的 function () { } 不能删
// 使用方式:可以直接使用对象解构按需引入
// 例如const { has, slide } = core.plugin.utils;
// slide([1, 2, 3], -1); // [2, 3, 1]
/**
* 滑动数组使数组元素平移若干项
* @example slide([1, 2, 3], -1); // [2, 3, 1]
* @example slide([1, 3, 5], 10); // [5, 3, 1];
* @param {any[]} arr 需要滑动的数组
* @param {number} delta 滑动的项数正负均可
*/
function slide(arr, delta) {
if (delta === 0) return arr;
delta %= arr.length;
if (delta > 0) {
arr.unshift(...arr.splice(arr.length - delta, delta));
return arr;
}
if (delta < 0) {
arr.push(...arr.splice(0, -delta));
return arr;
}
}
/**
* 获取一个方向的反方向
* @example backDir('up'); // 'down'
* @example backDir('leftup'); // 'rightdown'
* @param {string} dir 方向
*/
function backDir(dir) {
const map = {
up: "down",
down: "up",
left: "right",
right: "left",
leftup: "rightdown",
leftdown: "rightup",
rightdown: "leftup",
rightup: "leftdown",
};
if (!dir in map) {
throw new TypeError(
`Wrong dir is delivered when getting back direction.`
);
}
return map[dir];
}
/**
* 判断一个值是否不是undefined和null
* @example has(0); // true
* @example has(false); // true
* @example has(NaN); // true
* @example has(null); // false
* @param {any} v 要判断的值
*/
function has(v) {
return v !== null && v !== void 0;
}
/**
* 解析css字符串为CSSStyleDeclaration对象
* @example
* parseCss('background-color: cyan; cursor: pointer; user-select: none');
* // 输出 { backgroundColor: 'cyan', cursor: 'pointer', userSelect: 'none' }
* @param {string} css 要解析的css字符串
*/
function parseCss(css) {
const str = css.replace(/[\n\s\t]*/g, "").replace(/;*/g, ";");
const styles = str.split(";");
const res = {};
for (const one of styles) {
const [key, data] = one.split(":");
const cssKey = key.replace(/\-([a-z])/g, (str, $1) => $1.toUpperCase());
res[cssKey] = data;
}
return res;
}
/**
* 等待一段时间需在async function中使用否则报错
* @example await sleep(500); // 等待500毫秒
* @param {number} time 等待的毫秒数
*/
async function sleep(time) {
return new Promise((res) => setTimeout(res, time));
}
/**
* 在下一帧的下一帧执行一个函数
* @example nextFrame(() => console.log(1)); // 两帧后在控制台输出1
* @param cb 执行的函数
*/
function nextFrame(cb) {
requestAnimationFrame(() => {
requestAnimationFrame(cb);
});
}
/**
* 将一个css颜色解析成一个rgba数组
* 目前仅支持 #RGB #RGBA #RRGGBB #RRGGBBAA rgb() rgba() hsl() hsla() css自带颜色 这几种的转换
* @exmaple parseColor('#fff'); // [255, 255, 255]
* @example parseColor('#abcd'); // [170, 187, 204, 0.8666666666666667]
* @example parseColor('rgba(170, 230, 13, 0.2)'); // [170, 230, 13, 0.2]
* @example parseColor('cyan'); // [0, 255, 255]
* @example parseColor('lightcoral'); // [240, 128, 128]
* @example parseColor('hsla(0.2, 0.3, 0.4, 0.2)'); // [120, 133, 71, 0.2]
* @example parseColor('rgba(20%, 50, 33%, 0.2)'); // [51, 50, 84.15, 0.2]
* @param color 要解析的颜色字符串
*/
function parseColor(color) {
if (color.startsWith("rgb")) {
// rgb
const match = color.match(/rgba?\([\d\,\s\.%]+\)/);
if (!has(match)) throw new Error(`Invalid color is delivered!`);
const l = color.includes("a");
return match[0]
.slice(l ? 5 : 4, -1)
.split(",")
.map((v, i) => {
const vv = v.trim();
if (vv.endsWith("%")) {
if (i === 3) {
return parseInt(vv) / 100;
} else {
return (parseInt(vv) * 255) / 100;
}
} else return parseFloat(vv);
})
.slice(0, l ? 4 : 3);
} else if (color.startsWith("#")) {
// 十六进制
const content = color.slice(1);
if (![3, 4, 6, 8].includes(content.length)) {
throw new Error(`Invalid color is delivered!`);
}
if (content.length <= 4) {
const res = content.split("").map((v) => Number(`0x${v}${v}`));
if (res.length === 4) res[3] /= 255;
return res;
} else {
const res = Array(content.length / 2)
.fill(1)
.map((v, i) => Number(`0x${content[i * 2]}${content[i * 2 + 1]}`));
if (res.length === 4) res[3] /= 255;
return res;
}
} else if (color.startsWith("hsl")) {
// hsl转成rgb后输出
const match = color.match(/hsla?\([\d\,\s\.%]+\)/);
if (!has(match)) throw new Error(`Invalid color is delivered!`);
const l = color.includes("a");
const hsl = match[0]
.slice(l ? 5 : 4, -1)
.split(",")
.map((v) => {
const vv = v.trim();
if (vv.endsWith("%")) return parseInt(vv) / 100;
else return parseFloat(vv);
});
const rgb = hslToRgb(hsl[0], hsl[1], hsl[2]);
return l ? rgb.concat([hsl[3]]) : rgb;
} else {
// 单词
const rgb = cssColors[color];
if (!has(rgb)) {
throw new Error(`Invalid color is delivered!`);
}
return parseColor(rgb);
}
}
/**
* hsl转rgb
* @param h 色相
* @param s 饱和度
* @param l 亮度
*/
function hslToRgb(h, s, l) {
if (s == 0) {
return [0, 0, 0];
} else {
const hue2rgb = (p, q, t) => {
if (t < 0) t += 1;
if (t > 1) t -= 1;
if (t < 1 / 6) return p + (q - p) * 6 * t;
if (t < 1 / 2) return q;
if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
return p;
};
const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
const p = 2 * l - q;
const r = hue2rgb(p, q, h + 1 / 3);
const g = hue2rgb(p, q, h);
const b = hue2rgb(p, q, h - 1 / 3);
return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)];
}
}
/**
* 确保一个变量是一个数组不是的话转为数组并返回是的话直接返回传入的数组
* @param arr 要判断的变量
* @example ensureArray(1); // [1]
* @example ensureArray([1, 2]); // [1, 2]
* @example ensureArray('test'); // ['test']
*/
function ensureArray(arr) {
// @ts-ignore
return arr instanceof Array ? arr : [arr];
}
/**
* 返回一个坐标在某个方向上移动 d 格后的坐标
* @param d 移动多少格默认为1
* @example ofDir(7, 7, 'left'); // [6, 7]
* @example ofDir(10, 8, 'leftup', 5); // [5, 3]
*/
function ofDir(x, y, dir, d = 1) {
const { x: dx, y: dy } = core.utils.scan2[dir];
return [x + dx * d, y + dy * d];
}
const cssColors = {
black: "#000000",
silver: "#c0c0c0",
gray: "#808080",
white: "#ffffff",
maroon: "#800000",
red: "#ff0000",
purple: "#800080",
fuchsia: "#ff00ff",
green: "#008000",
lime: "#00ff00",
olive: "#808000",
yellow: "#ffff00",
navy: "#000080",
blue: "#0000ff",
teal: "#008080",
aqua: "#00ffff",
orange: "#ffa500",
aliceblue: "#f0f8ff",
antiquewhite: "#faebd7",
aquamarine: "#7fffd4",
azure: "#f0ffff",
beige: "#f5f5dc",
bisque: "#ffe4c4",
blanchedalmond: "#ffebcd",
blueviolet: "#8a2be2",
brown: "#a52a2a",
burlywood: "#deb887",
cadetblue: "#5f9ea0",
chartreuse: "#7fff00",
chocolate: "#d2691e",
coral: "#ff7f50",
cornflowerblue: "#6495ed",
cornsilk: "#fff8dc",
crimson: "#dc143c",
cyan: "#00ffff",
darkblue: "#00008b",
darkcyan: "#008b8b",
darkgoldenrod: "#b8860b",
darkgray: "#a9a9a9",
darkgreen: "#006400",
darkgrey: "#a9a9a9",
darkkhaki: "#bdb76b",
darkmagenta: "#8b008b",
darkolivegreen: "#556b2f",
darkorange: "#ff8c00",
darkorchid: "#9932cc",
darkred: "#8b0000",
darksalmon: "#e9967a",
darkseagreen: "#8fbc8f",
darkslateblue: "#483d8b",
darkslategray: "#2f4f4f",
darkslategrey: "#2f4f4f",
darkturquoise: "#00ced1",
darkviolet: "#9400d3",
deeppink: "#ff1493",
deepskyblue: "#00bfff",
dimgray: "#696969",
dimgrey: "#696969",
dodgerblue: "#1e90ff",
firebrick: "#b22222",
floralwhite: "#fffaf0",
forestgreen: "#228b22",
gainsboro: "#dcdcdc",
ghostwhite: "#f8f8ff",
gold: "#ffd700",
goldenrod: "#daa520",
greenyellow: "#adff2f",
grey: "#808080",
honeydew: "#f0fff0",
hotpink: "#ff69b4",
indianred: "#cd5c5c",
indigo: "#4b0082",
ivory: "#fffff0",
khaki: "#f0e68c",
lavender: "#e6e6fa",
lavenderblush: "#fff0f5",
lawngreen: "#7cfc00",
lemonchiffon: "#fffacd",
lightblue: "#add8e6",
lightcoral: "#f08080",
lightcyan: "#e0ffff",
lightgoldenrodyellow: "#fafad2",
lightgray: "#d3d3d3",
lightgreen: "#90ee90",
lightgrey: "#d3d3d3",
lightpink: "#ffb6c1",
lightsalmon: "#ffa07a",
lightseagreen: "#20b2aa",
lightskyblue: "#87cefa",
lightslategray: "#778899",
lightslategrey: "#778899",
lightsteelblue: "#b0c4de",
lightyellow: "#ffffe0",
limegreen: "#32cd32",
linen: "#faf0e6",
magenta: "#ff00ff",
mediumaquamarine: "#66cdaa",
mediumblue: "#0000cd",
mediumorchid: "#ba55d3",
mediumpurple: "#9370db",
mediumseagreen: "#3cb371",
mediumslateblue: "#7b68ee",
mediumspringgreen: "#00fa9a",
mediumturquoise: "#48d1cc",
mediumvioletred: "#c71585",
midnightblue: "#191970",
mintcream: "#f5fffa",
mistyrose: "#ffe4e1",
moccasin: "#ffe4b5",
navajowhite: "#ffdead",
oldlace: "#fdf5e6",
olivedrab: "#6b8e23",
orangered: "#ff4500",
orchid: "#da70d6",
palegoldenrod: "#eee8aa",
palegreen: "#98fb98",
paleturquoise: "#afeeee",
palevioletred: "#db7093",
papayawhip: "#ffefd5",
peachpuff: "#ffdab9",
peru: "#cd853f",
pink: "#ffc0cb",
plum: "#dda0dd",
powderblue: "#b0e0e6",
rosybrown: "#bc8f8f",
royalblue: "#4169e1",
saddlebrown: "#8b4513",
salmon: "#fa8072",
sandybrown: "#f4a460",
seagreen: "#2e8b57",
seashell: "#fff5ee",
sienna: "#a0522d",
skyblue: "#87ceeb",
slateblue: "#6a5acd",
slategray: "#708090",
slategrey: "#708090",
snow: "#fffafa",
springgreen: "#00ff7f",
steelblue: "#4682b4",
tan: "#d2b48c",
thistle: "#d8bfd8",
tomato: "#ff6347",
turquoise: "#40e0d0",
violet: "#ee82ee",
wheat: "#f5deb3",
whitesmoke: "#f5f5f5",
yellowgreen: "#9acd32",
transparent: "#0000",
};
if (has(core.plugin.utils)) {
throw new ReferenceError(
`core.plugin上已经有'utils'属性,因此功能函数插件将无法使用!`
);
}
core.plugin.utils = {
has,
slide,
backDir,
parseCss,
sleep,
nextFrame,
parseColor,
hslToRgb,
ensureArray,
ofDir,
};
// Utility.js
// 通用函數插件
// 本插件與古祠發佈的《功能插件 --- 实用功能函数集》不同,函數是定義在全域的。
// 自訂常見事件模板插件(editorBlocklyconfigPlus.js)的前置插件
/**
* 使js暫停指定時間
* async環境下await Sleep(500)
* @param {number} millisecond 暫停毫秒數
*/
self.Sleep = async function (millisecond) {
return new Promise((resolve) => setTimeout(resolve, millisecond));
};
/**
* 使js暫停一幀
* async環境下await SleepFrame()
*/
self.SleepFrame = async function () {
return new Promise((resolve) => requestAnimationFrame(resolve));
};
/**
* editor_file的isset函數
*/
self.isset = function (val) {
if (val == undefined || val == null) {
return false;
}
return true;
};
/**
* editor_file的checkCallback函數
*/
self.checkCallback = function (callback) {
if (!isset(callback)) {
printe("未设置callback");
throw "未设置callback";
}
};
},
2025-01-18 17:49:44 +08:00
"怪物碎裂特效": function () {
2024-12-27 11:55:28 +08:00
// 在此增加新插件
// -------------------- 安装说明 -------------------- //
// 先安装两个在插件简介中说明的前置插件
// 然后再将本插件复制到插件编写中即可
// 插件自带一个打怪后显示碎裂特效的功能
// -------------------- 使用说明 -------------------- //
/*
2024-12-27 11:55:28 +08:00
本插件的核心是一个名为 applyFragWith 的函数打怪后的碎裂特效也是由它执行的
我们来说明一下这个函数的使用方式
1. 引入
你可以使用 const { applyFragWith } = core.plugin.frag; 在任何地方来引入这个函数
2024-12-27 11:55:28 +08:00
2. 函数的参数
该函数有三个参数分别是canvas, length, time
其中第一个参数意思是在执行碎裂时其内容由该画布决定这个特效并不会修改传入的画布因此原画布的内容会依然存在
第二个参数指的是每个碎片的边长虽然原则上每个碎片都是正方形但边缘一周的碎片可能不是正方形但中间的碎片一定是
第三个参数指的是这个特效要执行多长时间
第四个参数是一个对象包含四种配置量均为可选分别是maxMoveLengthmoveFlushmaxRotatefragTiming
当这些不存在时会默认取同名的常量作为默认值这些值有什么用可以看下面的常量注释
2024-12-27 11:55:28 +08:00
3. 函数的返回值
这个函数会返回一个特效控制器对象这个控制器共有三个属性
animation: 指的是当前特效的高级动画对象
onEnd: 一个Promise当这个特效执行完毕后会被 fulfilled
canvas: 特效所显示的画布这个画布不会自动部署到样板中需要你手动使用appendChild来部署具体可参考打怪后碎裂的样例
2024-12-27 11:55:28 +08:00
4. 修改一些常量
在下面有四个有注释的常量MAX_MOVE_LENGTH ~ FRAG_TIMING你可以根据你自己的需要来更改
2024-12-27 11:55:28 +08:00
*/
2024-12-27 11:55:28 +08:00
if (main.replayChecking) return (core.plugin.frag = {});
const { Animation, linear, sleep } = core.plugin.animate;
const { has } = core.plugin.utils;
/** 最大移动距离,最终位置距离中心的距离变成原来的几倍 */
const MAX_MOVE_LENGTH = 1.15;
/** 移动距离波动,在最大移动距离的基础上加上多少倍距离的波动距离 */
const MOVE_FLUSH = 0.7;
/** 最大旋转角,单位是弧度,每个碎片都会有自己的旋转程度,是随机的 */
const MAX_ROTATE = 0.5;
/** 碎裂动画的速率曲线函数 */
const FRAG_TIMING = linear();
/**
* @param {HTMLCanvasElement} canvas 要执行特效的画布
* @param {number} length 切分成的碎片的边长碎片为正方形
* @param {number} time 特效持续时长
* @returns 返回一个碎裂特效控制器是一个对象详见开头的使用注释
*/
function applyFragWith(canvas, length = 4, time = 2000, config = {}) {
// 先切分图片
const imgs = splitCanvas(canvas, length);
const cx = canvas.width / 2;
const cy = canvas.height / 2;
let maxX = 0;
let maxY = 0;
const toMove = imgs.map((v) => {
const centerX = v.x + v.canvas.width / 2;
const centerY = v.y + v.canvas.height / 2;
const onX = centerX === cx;
const onY = centerY === cy;
const mml = config.maxMoveLength ?? MAX_MOVE_LENGTH;
const mf = config.moveFlush ?? MOVE_FLUSH;
const rate = mml - 1 + Math.random() ** 3 * mf;
let endX = onY ? 0 : (centerX - cx) * rate;
let endY = onX ? 0 : (centerY - cy) * rate;
const mx = Math.abs(endX + centerX) + Math.abs(v.canvas.width);
const my = Math.abs(endY + centerY) + Math.abs(v.canvas.height);
if (mx > maxX) maxX = mx;
if (my > maxY) maxY = my;
const r = config.maxRotate ?? MAX_ROTATE;
const endRad = Math.random() * r * 2 - r;
return {
deltaX: endX,
deltaY: endY,
endRad,
x: centerX,
y: centerY,
canvas: v.canvas,
};
});
// 再执行动画
const frag = document.createElement("canvas");
const ctx = frag.getContext("2d");
const ani = new Animation();
ani.register("rate", 0);
const ft = config.fragTiming ?? FRAG_TIMING;
ani.absolute().time(time).mode(ft).apply("rate", 1);
frag.width = maxX * 2;
frag.height = maxY * 2;
ctx.save();
const dw = maxX - canvas.width / 2;
const dh = maxY - canvas.height / 2;
const fragFn = () => {
const rate = ani.value.rate;
const opacity = 1 - rate;
ctx.globalAlpha = opacity;
ctx.clearRect(0, 0, frag.width, frag.height);
toMove.forEach((v) => {
ctx.save();
const nx = v.deltaX * rate;
const ny = v.deltaY * rate;
const rotate = v.endRad * rate;
ctx.translate(nx + v.x + dw, ny + v.y + dh);
ctx.rotate(rotate);
ctx.drawImage(
v.canvas,
nx - v.canvas.width / 2,
ny - v.canvas.height / 2
);
ctx.restore();
});
};
const onEnd = () => {};
ani.ticker.add(fragFn);
return makeFragManager(frag, ani, time, onEnd);
}
function makeFragManager(canvas, ani, time, onEnd) {
const promise = sleep(time + 50);
return {
animation: ani,
onEnd: promise.then(() => {
ani.ticker.destroy();
onEnd();
}),
canvas,
};
}
function withImage(image, sx, sy, sw, sh) {
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d");
canvas.width = sw;
canvas.height = sh;
ctx.drawImage(image, sx, sy, sw, sh, 0, 0, sw, sh);
return { canvas, x: sx, y: sy };
}
/**
* 切分画布
* @param canvas 要被切分的画布
* @param l 切分小块的边长
*/
function splitCanvas(canvas, l) {
if (canvas.width / l < 2 || canvas.height / l < 2) {
console.warn("切分画布要求切分边长大于等于画布长宽的一半!");
return [];
}
const w = canvas.width;
const h = canvas.height;
const numX = Math.floor(w / l);
const numY = Math.floor(h / l);
const rw = (w - numX * l) / 2;
const rh = (h - numY * l) / 2;
const res = [];
if (rw > 0) {
if (rh > 0) {
res.push(
withImage(canvas, 0, 0, rw, rh),
withImage(canvas, 0, canvas.height - rh, rw, rh),
withImage(canvas, canvas.width - rw, 0, rw, rh),
withImage(canvas, canvas.width - rw, canvas.height - rh, rw, rh)
);
}
for (const x of [0, canvas.width - rw]) {
for (let ny = 0; ny < numY; ny++) {
res.push(withImage(canvas, x, rh + l * ny, rw, l));
}
}
}
if (rh > 0) {
for (const y of [0, canvas.height - rh]) {
for (let nx = 0; nx < numX; nx++) {
res.push(withImage(canvas, rw + l * nx, y, l, rh));
}
}
}
for (let nx = 0; nx < numX; nx++) {
for (let ny = 0; ny < numY; ny++) {
res.push(withImage(canvas, rw + l * nx, rh + l * ny, l, l));
}
}
return res;
}
const origin = core.events.afterBattle;
core.events.afterBattle = function (enemyId, x, y) {
// 打怪特效
if (has(x) && has(y)) {
const frame = core.status.globalAnimateStatus % 2;
// 生成怪物图像
const canvas = document.createElement("canvas");
canvas.width = 32;
canvas.height = 32;
core.drawIcon(canvas, enemyId, 0, 0, 32, 32, frame);
// 执行动画
const manager = applyFragWith(canvas);
const frag = manager.canvas;
// 设置特效画布的css属性
frag.style.imageRendering = "pixelated";
frag.style.width = `${frag.width * core.domStyle.scale}px`;
frag.style.height = `${frag.height * core.domStyle.scale}px`;
const left =
(x * 32 + 16 - frag.width / 2 - core.bigmap.offsetX) *
core.domStyle.scale;
const top =
(y * 32 + 16 - frag.height / 2 - core.bigmap.offsetY) *
core.domStyle.scale;
frag.style.left = `${left}px`;
frag.style.top = `${top}px`;
frag.style.zIndex = "45";
frag.style.position = "absolute";
// 将特效画布部署到样板上
core.dom.gameDraw.appendChild(frag);
// 当特效执行完毕后移除这个特效画布
manager.onEnd.then(() => {
frag.remove();
});
}
return origin.apply(this, arguments);
};
if ("frag" in core.plugin) {
throw new ReferenceError(`core.plugin上已存在名为frag的属性`);
}
core.plugin.frag = {
applyFragWith,
};
},
2025-01-18 17:49:44 +08:00
"自定义常用事件": function () {
2025-01-16 13:17:19 +08:00
// editorBlocklyconfigPlus.js
// 自訂常見事件模板插件
// 本插件引用了通用函數插件(Utility.js)
// 適用樣板2.10.3
// 請注意:
// 此插件對事件編輯器(editor_blocklyconfig)進行複寫,若還有其它針對事件編輯器做複寫的插件,請謹慎使用!
// 此插件對表格操作行為(editor_mode.doActionList)進行複寫,若還有其它對表格操作行為做複寫的插件,請謹慎使用!
// 使用方法:
// 現在在主頁下拉選單多了個常用事件模版,在那邊可以自由設定常用事件模板。
// 設定完後按F5刷新再到事件編輯器看就有你設定好的常用事件模板了。
if (main.mode == "editor") {
//#region 配置表格初始化
let TableFileName = "project/table/CommonEventTemplate_comment.js";
let TableRow = `
2024-12-27 11:55:28 +08:00
var CommonEventTemplate_comment = {"_type": "object",
"_data": {
"CommonEventTemplate": {
"_type": "object",
"_data": function (key) {
var obj = {
"检测音乐如果没有开启则系统提示开启": {
"_leaf": true,
"_type": "event",
"_event": "commonEvent",
"_data": "检测音乐如果没有开启则系统提示开启"
},
"仿新新魔塔一次性商人": {
"_leaf": true,
"_type": "event",
"_event": "commonEvent",
"_data": "仿新新魔塔一次性商人"
},
"全地图选中一个点": {
"_leaf": true,
"_type": "event",
"_event": "commonEvent",
"_data": "全地图选中一个点"
},
"多阶段Boss战斗": {
"_leaf": true,
"_type": "event",
"_event": "commonEvent",
"_data": "多阶段Boss战斗"
},
}
if (obj[key]) return obj[key];
return {
"_leaf": true,
"_type": "event",
"_event": "commonEvent",
"_data": "常見事件模板"
}
}
}
2024-12-27 11:55:28 +08:00
}}
`;
2025-01-16 13:17:19 +08:00
if (!events_c12a15a8_c380_4b28_8144_256cba95f760.CommonEventTemplate) {
/**
* @type {{[EvnetName:actionParserJson]}}
*/
events_c12a15a8_c380_4b28_8144_256cba95f760.CommonEventTemplate = {
检测音乐如果没有开启则系统提示开启: [
{
type: "if",
condition: "!core.musicStatus.bgmStatus",
true: [
"\t[系统提示]你当前音乐处于关闭状态,本塔开音乐游戏效果更佳",
],
false: [],
},
],
仿新新魔塔一次性商人: [
{
type: "if",
condition: "switch:A",
true: [
"\t[行商,trader]\b[this]这是购买我的道具后我给玩家的提示。",
{
type: "comment",
text: "下一条指令可视情况使用或不使用",
},
{
type: "hide",
remove: true,
time: 250,
},
],
false: [
{
type: "confirm",
text: "我有3把黄钥匙\n你出50金币就卖给你。",
yes: [
{
type: "if",
condition: "status:money>=50",
true: [
{
type: "setValue",
name: "status:money",
operator: "-=",
value: "50",
},
{
type: "setValue",
name: "item:yellowKey",
operator: "+=",
value: "3",
},
{
type: "playSound",
name: "确定",
stop: true,
},
{
type: "setValue",
name: "switch:A",
value: "true",
},
],
false: [
{
type: "playSound",
name: "操作失败",
},
"\t[行商,trader]\b[this]你的金币不足!",
],
},
],
no: [],
},
],
},
],
全地图选中一个点: [
{
type: "comment",
text: "全地图选中一个点,需要用鼠标或触屏操作",
},
{
type: "setValue",
name: "temp:X",
value: "status:x",
},
{
type: "setValue",
name: "temp:Y",
value: "status:y",
},
{
type: "tip",
text: "再次点击闪烁位置确认",
},
{
type: "while",
condition: "true",
data: [
{
type: "drawSelector",
image: "winskin.webp",
code: 1,
x: "32*temp:X",
y: "32*temp:Y",
width: 32,
height: 32,
},
{
type: "wait",
},
{
type: "if",
condition: "(flag:type === 1)",
true: [
{
type: "if",
condition: "((temp:X===flag:x)&&(temp:Y===flag:y))",
true: [
{
type: "break",
n: 1,
},
],
},
{
type: "setValue",
name: "temp:X",
value: "flag:x",
},
{
type: "setValue",
name: "temp:Y",
value: "flag:y",
},
],
},
],
},
{
type: "drawSelector",
code: 1,
},
{
type: "comment",
text: "流程进行到这里可以对[X,Y]点进行处理,比如",
},
{
type: "closeDoor",
id: "yellowDoor",
loc: ["temp:X", "temp:Y"],
},
],
多阶段Boss战斗: [
{
type: "comment",
text: "多阶段boss请直接作为战后事件使用",
},
{
type: "setValue",
name: "switch:A",
operator: "+=",
value: "1",
},
{
type: "switch",
condition: "switch:A",
caseList: [
{
case: "1",
action: [
{
type: "setBlock",
number: "redSlime",
},
"\t[2阶段boss,redSlime]\b[this]你以为你已经打败我了吗?没听说过史莱姆有九条命吗?",
],
},
{
case: "2",
action: [
{
type: "setBlock",
number: "blackSlime",
},
"\t[3阶段boss,blackSlime]\b[this]不能消灭我的,只会让我更强大!",
],
},
{
case: "3",
action: [
{
type: "setBlock",
number: "slimelord",
},
"\t[4阶段boss,slimelord]\b[this]我还能打!",
],
},
{
case: "4",
action: ["\t[4阶段boss,slimelord]我一定会回来的!"],
},
],
},
],
};
}
//#endregion
// 新增模板選項
let editModeSelect = document.getElementById("editModeSelect");
let newEditModeOption = document.createElement("option");
newEditModeOption.value = "CommonEventTemplate";
newEditModeOption.text = "常見事件模板";
editModeSelect.add(newEditModeOption);
//檢查可用的編輯模板ID
let leftIDNumber = 11 - 1;
let ExistLeftElement = document.querySelector(".main");
while (ExistLeftElement) {
leftIDNumber++;
ExistLeftElement = document.getElementById(`left${leftIDNumber}`);
}
2025-01-02 17:04:30 +08:00
2025-01-16 13:17:19 +08:00
//新增編輯模板
let MainDiv = document.querySelector(".main");
2025-01-16 13:17:19 +08:00
let CommonEventTemplateMainDiv = document.createElement("div");
CommonEventTemplateMainDiv.id = `left${leftIDNumber}`;
CommonEventTemplateMainDiv.className = "leftTab";
CommonEventTemplateMainDiv.style.zIndex = "-1";
CommonEventTemplateMainDiv.style.opacity = "0";
2025-01-02 17:04:30 +08:00
2025-01-16 13:17:19 +08:00
CommonEventTemplateMainDiv.innerHTML = `
2024-12-27 11:55:28 +08:00
<!-- CommonEventTemplate -->
<h3 class="leftTabHeader">
常見事件模板  
<button onclick="editor.mode.onmode('save')">保存</button>  
<button onclick="editor.table.CommonEventTemplateAddFunc()">添加</button>  
<button onclick="editor.mode.changeDoubleClickModeByButton('delete')">删除</button>  
<button onclick="editor_multi.CommonEventTemplateEditCommentJs('CommonEventTemplate')">配置表格</button>
</h3>
<div class="leftTabContent">
<div class='etable'>
<table>
<tbody id='table_298572d8-93dd-4c6e-a278-6a7d49831e3a'>
<tr>
<td>条目</td>
<td>注释</td>
<td></td>
</tr>
</tbody>
</table>
</div>
</div>
`;
2025-01-16 13:17:19 +08:00
MainDiv.appendChild(CommonEventTemplateMainDiv);
2025-01-16 13:17:19 +08:00
(async function () {
//等待編輯器初始化
while (!editor_mode.ids) {
await Sleep(100);
}
//新增編輯模板ID
editor_mode.ids["CommonEventTemplate"] = `left${leftIDNumber}`;
editor_mode.init_dom_ids();
//切換至常見事件模板
editor_mode.CommonEventTemplate = function (callback) {
var objs = [];
editor.file.editCommonEventTemplate([], function (objs_) {
objs = objs_;
//console.log(objs_)
});
//只查询不修改时,内部实现不是异步的,所以可以这么写
var tableinfo = editor.table.objToTable(objs[0], objs[1]);
document.getElementById(
"table_298572d8-93dd-4c6e-a278-6a7d49831e3a"
).innerHTML = tableinfo.HTML;
tableinfo.listen(tableinfo.guids);
if (Boolean(callback)) callback();
};
2024-12-01 17:42:24 +08:00
2025-01-16 13:17:19 +08:00
//檢查配置表格存在
let TableRowExist = null;
fs.readFile(TableFileName, "base64", function (err, data) {
if (err) {
console.log(`察覺常見事件模板配置表格不存在,原因:${err}`);
console.log("新建一個常見事件模板配置表格。");
TableRowExist = false;
} else {
TableRowExist = true;
}
});
//等待配置表格載入完畢(最多0.3秒,超過則視為失敗)
for (let i = 0; i < 3; i++) {
if (TableRowExist == null) {
await Sleep(100);
}
}
//配置表格初始化
if (TableRowExist != true) {
fs.mkdir("project/table", function (err, data) {
if (err) throw `常見事件模板配置表格目錄初始化失敗,原因:${err}`;
});
fs.writeFile(
TableFileName,
editor.util.encode64(TableRow || ""),
"base64",
function (err, data) {
if (err) throw `常見事件模板配置表格文件初始化失敗,原因:${err}`;
}
);
}
//載入配置表格
//editor.file.loadCommentjs(callback);
(function () {
var key = "CommonEventTemplate_comment";
var script = document.createElement("script");
script.src = "project/table/" + key + ".js";
document.body.appendChild(script);
script.onload = function () {
editor.file[key] = eval(key.replace(".", "_"));
var loaded = Boolean(editor.file[key]);
};
})();
//按下配置表格
editor_multi.CommonEventTemplateEditCommentJs = function (mod) {
editor_multi.lintAutocomplete = true;
editor_multi.setLint();
editor_multi.importFile(TableFileName);
};
2024-12-01 14:52:03 +08:00
2025-01-16 13:17:19 +08:00
//定義表格操作行為
editor_mode.OriginDoActionList = editor_mode.doActionList;
editor_mode.doActionList = function (mode, actionList, callback) {
if (editor_mode.mode == "CommonEventTemplate") {
if (actionList.length == 0) return;
printf("修改中...");
var cb = function (objs_) {
if (objs_.slice(-1)[0] != null) {
printe(objs_.slice(-1)[0]);
throw objs_.slice(-1)[0];
}
var str = "修改成功!";
if (
data_a1e2fb4a_e986_4524_b0da_9b7ba7c0874d.firstData.name ==
"template"
)
str += "<br/>请注意全塔属性的name尚未修改请及时予以设置。";
printf(str);
if (callback) callback();
};
editor.file.editCommonEventTemplate(actionList, cb);
} else {
editor_mode.OriginDoActionList(mode, actionList, callback);
}
};
//添加表格列
editor.table.CommonEventTemplateAddFunc = function () {
let obj = events_c12a15a8_c380_4b28_8144_256cba95f760;
2024-12-01 14:52:03 +08:00
2025-01-16 13:17:19 +08:00
// 1.输入id
let newid = prompt("请输入新项的ID支持中文");
if (newid == null || newid.length == 0) {
return;
}
2024-12-01 14:52:03 +08:00
2025-01-16 13:17:19 +08:00
// 2.检查id是否符合规范或与已有id重复
var conflict = true;
var basefield = "".replace(/\[[^\[]*\]$/, "");
2025-01-02 17:04:30 +08:00
2025-01-16 13:17:19 +08:00
try {
var baseobj = eval("obj" + basefield);
conflict = newid in baseobj;
} catch (ee) {
// 理论上这里不会发生错误
printe(ee);
throw ee;
}
if (conflict) {
printe("id已存在, 请直接修改该项的值");
return;
}
// 3.添加
editor_mode.addAction(["add", basefield + "['" + newid + "']", null]);
editor_mode.onmode("save", function () {
printf("添加成功,刷新后生效;也可以继续新增其他项目。");
}); //自动保存 删掉此行的话点保存按钮才会保存
};
//對表格的存讀
editor.file.editCommonEventTemplate = function (actionList, callback) {
/*actionList:[
2024-12-27 11:55:28 +08:00
["change","['test']",['123']],
]
[]时只查询不修改
*/
2025-01-16 13:17:19 +08:00
var data_obj =
events_c12a15a8_c380_4b28_8144_256cba95f760.CommonEventTemplate;
checkCallback(callback);
if (isset(actionList) && actionList.length > 0) {
actionList.forEach(function (value) {
value[1] = "['CommonEventTemplate']" + value[1];
});
editor.file.saveSetting("events", actionList, function (err) {
callback([err]);
});
} else {
callback([
Object.assign({}, data_obj),
editor.file.CommonEventTemplate_comment._data.CommonEventTemplate,
null,
]);
}
};
})();
2025-01-02 17:04:30 +08:00
2025-01-16 13:17:19 +08:00
//複寫事件編輯器(editor_blocklyconfig)
editor_blocklyconfig = function () {
// start mark sfergsvae
2025-01-02 17:04:30 +08:00
2025-01-16 13:17:19 +08:00
(function () {
var getCategory = function (name, custom) {
for (var node of document.getElementById("toolbox").children) {
if (node.getAttribute("name") == name) return node;
}
var node = document.createElement("category");
node.setAttribute("name", name);
if (custom) node.setAttribute("custom", custom);
document.getElementById("toolbox").appendChild(node);
return node;
};
2025-01-02 17:04:30 +08:00
2025-01-16 13:17:19 +08:00
var toolboxObj = {
入口方块: [
'<label text="入口方块会根据当前类型在此数组中筛选,具体控制在editor_blockly.entranceCategoryCallback中"></label>',
MotaActionFunctions.actionParser.parse(
[
"欢迎使用事件编辑器",
"本事件触发一次后会消失",
{ type: "hide", time: 500 },
],
"event"
),
MotaActionFunctions.actionParser.parse(
{
condition: "flag:__door__===2",
currentFloor: true,
priority: 0,
delayExecute: false,
multiExecute: false,
data: [{ type: "openDoor", loc: [10, 5] }],
},
"autoEvent"
),
MotaActionBlocks["changeFloor_m"].xmlText(),
MotaActionFunctions.actionParser.parse(
[
{
id: "shop1",
text: "\t[贪婪之神,moneyShop]勇敢的武士啊, 给我${20+2*flag:shop1}金币就可以:",
textInList: "1F金币商店",
choices: [
{
text: "生命+800",
need: "status:money>=20+2*flag:shop1",
action: [
{
type: "comment",
text: "新版商店中需要手动扣减金币和增加访问次数",
},
{
type: "setValue",
name: "status:money",
operator: "-=",
value: "20+2*flag:shop1",
},
{
type: "setValue",
name: "flag:shop1",
operator: "+=",
value: "1",
},
{
type: "setValue",
name: "status:hp",
operator: "+=",
value: "800",
},
],
},
],
},
{
id: "itemShop",
item: true,
textInList: "道具商店",
choices: [{ id: "yellowKey", number: 10, money: 10 }],
},
{
id: "keyShop1",
textInList: "回收钥匙商店",
commonEvent: "回收钥匙商店",
args: "",
},
],
"shop"
),
MotaActionBlocks["common_m"].xmlText(),
MotaActionBlocks["beforeBattle_m"].xmlText(),
MotaActionBlocks["afterBattle_m"].xmlText(),
MotaActionBlocks["afterGetItem_m"].xmlText(),
MotaActionBlocks["afterOpenDoor_m"].xmlText(),
MotaActionBlocks["firstArrive_m"].xmlText(),
MotaActionBlocks["eachArrive_m"].xmlText(),
MotaActionBlocks["level_m"].xmlText(),
MotaActionFunctions.actionParser.parse(
[["MTx", ""]],
"floorPartition"
),
MotaActionBlocks["commonEvent_m"].xmlText(),
MotaActionBlocks["item_m"].xmlText(),
MotaActionFunctions.actionParser.parse(
[
{
title: "简单",
name: "Easy",
hard: 1,
action: [
{ type: "comment", text: "在这里写该难度需执行的事件" },
],
},
],
"levelChoose"
),
MotaActionFunctions.actionParser.parse(
{
type: 0,
value: { atk: 10 },
percentage: { speed: 10 },
},
"equip"
),
MotaActionFunctions.actionParser.parse(
[
{
name: "bg.webp",
x: 0,
y: 0,
canvas: "bg",
},
],
"floorImage"
),
MotaActionFunctions.actionParser.parse(
{
time: 160,
openSound: "door.mp3",
closeSound: "door.mp3",
keys: { yellowKey: 1, orangeKey: 1 },
},
"doorInfo"
),
MotaActionBlocks["faceIds_m"].xmlText(),
MotaActionBlocks["mainStyle_m"].xmlText(),
MotaActionFunctions.actionParser.parse(
{
背景音乐: "bgm.mp3",
确定: "confirm.mp3",
攻击: "attack.mp3",
背景图: "bg.webp",
领域: "zone",
文件名: "file.jpg",
},
"nameMap"
),
MotaActionFunctions.actionParser.parse(
[{ name: "hero.webp", width: 32, height: 32, prefix: "hero_" }],
"splitImages"
),
],
显示文字: [
MotaActionBlocks["text_0_s"].xmlText(),
MotaActionBlocks["text_1_s"].xmlText(),
MotaActionFunctions.actionParser.parseList(
"\t[小妖精,fairy]\f[fairy.webp,0,0]欢迎使用事件编辑器(双击方块可直接预览)"
),
MotaActionBlocks["over_s"].xmlText(),
MotaActionFunctions.actionParser.parseList([
{
type: "overlist",
image: "bg_5043.webp",
memory: false,
hidetime: 30,
list: [
{
text: "",
sound: "",
time: 50,
textColor: "255,255,255,1",
boldColor: "0,0,0,1",
font: "bold 48px Verdana",
frame: 0,
},
],
},
]),
MotaActionFunctions.actionParser.parseList([
{
type: "cgtext",
bg: "bg_5043.webp",
memory: false,
WindowSkin: false,
head: { name: "face_050445.webp", px: -300 },
name: "菲奥奈",
time: 0,
wait: 2000,
sound: "",
text: "这句话显示在对话框内",
bodyList: [
{ name: "tati_050145a.webp", px: 100, filter: false },
],
},
]),
MotaActionBlocks["moveTextBox_s"].xmlText(),
MotaActionBlocks["clearTextBox_s"].xmlText(),
MotaActionBlocks["comment_s"].xmlText(),
MotaActionBlocks["autoText_s"].xmlText(),
MotaActionBlocks["scrollText_s"].xmlText(),
MotaActionBlocks["setText_s"].xmlText(),
MotaActionBlocks["tip_s"].xmlText(),
MotaActionBlocks["addPop_s"].xmlText(),
MotaActionBlocks["confirm_s"].xmlText(),
MotaActionBlocks["choices_s"].xmlText([
"选择剑或者盾",
"流浪者",
"man",
0,
"",
MotaActionBlocks["choicesContext"].xmlText([
"剑",
"",
"",
null,
"",
"",
MotaActionFunctions.actionParser.parseList([
{ type: "openDoor", loc: [3, 3] },
]),
]),
]),
MotaActionBlocks["win_s"].xmlText(),
MotaActionBlocks["lose_s"].xmlText(),
MotaActionBlocks["restart_s"].xmlText(),
],
数据相关: [
MotaActionBlocks["setValue_s"].xmlText([
MotaActionBlocks["idIdList_e"].xmlText(["status", "生命"]),
"=",
"",
false,
]),
MotaActionBlocks["setEnemy_s"].xmlText(),
MotaActionBlocks["setEnemyOnPoint_s"].xmlText(),
MotaActionBlocks["resetEnemyOnPoint_s"].xmlText(),
MotaActionBlocks["moveEnemyOnPoint_s"].xmlText(),
MotaActionBlocks["moveEnemyOnPoint_1_s"].xmlText(),
MotaActionBlocks["setEquip_s"].xmlText(),
MotaActionBlocks["setFloor_s"].xmlText(),
MotaActionBlocks["setGlobalAttribute_s"].xmlText(),
MotaActionBlocks["setGlobalValue_s"].xmlText(),
MotaActionBlocks["setGlobalFlag_s"].xmlText(),
MotaActionBlocks["setNameMap_s"].xmlText(),
MotaActionBlocks["input_s"].xmlText(),
MotaActionBlocks["input2_s"].xmlText(),
MotaActionBlocks["update_s"].xmlText(),
MotaActionBlocks["moveAction_s"].xmlText(),
MotaActionBlocks["changeFloor_s"].xmlText(),
MotaActionBlocks["changePos_s"].xmlText(),
MotaActionBlocks["battle_s"].xmlText(),
MotaActionBlocks["useItem_s"].xmlText(),
MotaActionBlocks["loadEquip_s"].xmlText(),
MotaActionBlocks["unloadEquip_s"].xmlText(),
MotaActionBlocks["openShop_s"].xmlText(),
MotaActionBlocks["disableShop_s"].xmlText(),
MotaActionBlocks["setHeroIcon_s"].xmlText(),
MotaActionBlocks["follow_s"].xmlText(),
MotaActionBlocks["unfollow_s"].xmlText(),
],
地图处理: [
MotaActionBlocks["battle_1_s"].xmlText(),
MotaActionBlocks["openDoor_s"].xmlText(),
MotaActionBlocks["closeDoor_s"].xmlText(),
MotaActionBlocks["show_s"].xmlText(),
MotaActionBlocks["hide_s"].xmlText(),
MotaActionBlocks["setBlock_s"].xmlText(),
MotaActionBlocks["setBlockOpacity_s"].xmlText(),
MotaActionBlocks["setBlockFilter_s"].xmlText(),
MotaActionBlocks["turnBlock_s"].xmlText(),
MotaActionBlocks["moveHero_s"].xmlText(),
MotaActionBlocks["move_s"].xmlText(),
MotaActionBlocks["jumpHero_s"].xmlText(),
MotaActionBlocks["jumpHero_1_s"].xmlText(),
MotaActionBlocks["jump_s"].xmlText(),
MotaActionBlocks["jump_1_s"].xmlText(),
MotaActionBlocks["showBgFgMap_s"].xmlText(),
MotaActionBlocks["hideBgFgMap_s"].xmlText(),
MotaActionBlocks["setBgFgBlock_s"].xmlText(),
MotaActionBlocks["showFloorImg_s"].xmlText(),
MotaActionBlocks["hideFloorImg_s"].xmlText(),
],
事件控制: [
MotaActionBlocks["if_1_s"].xmlText(),
MotaActionBlocks["if_s"].xmlText(),
MotaActionFunctions.actionParser.parseList({
type: "switch",
condition: "判别值",
caseList: [
{
action: [
{ type: "comment", text: "当判别值是值的场合执行此事件" },
],
},
{
case: "default",
action: [
{
type: "comment",
text: "当没有符合的值的场合执行default事件",
},
],
},
],
}),
MotaActionFunctions.actionParser.parseList({
type: "for",
name: "temp:A",
from: "0",
to: "12",
step: "1",
data: [],
}),
MotaActionFunctions.actionParser.parseList({
type: "forEach",
name: "temp:A",
list: ["status:atk", "status:def"],
data: [],
}),
MotaActionBlocks["while_s"].xmlText(),
MotaActionBlocks["dowhile_s"].xmlText(),
MotaActionBlocks["break_s"].xmlText(),
MotaActionBlocks["continue_s"].xmlText(),
MotaActionBlocks["exit_s"].xmlText(),
MotaActionBlocks["trigger_s"].xmlText(),
MotaActionBlocks["insert_1_s"].xmlText(),
MotaActionBlocks["insert_2_s"].xmlText(),
],
特效表现: [
MotaActionBlocks["sleep_s"].xmlText(),
MotaActionBlocks["changebg_s"].xmlText(),
MotaActionFunctions.actionParser.parseList({
type: "wait",
timeout: 0,
data: [
{
case: "keyboard",
keycode: "13,32",
action: [
{
type: "comment",
text: "当按下回车(keycode=13)或空格(keycode=32)时执行此事件\n超时剩余时间会写入flag:timeout",
},
],
},
{
case: "mouse",
px: [0, 32],
py: [0, 32],
action: [
{
type: "comment",
text: "当点击地图左上角时执行此事件\n超时剩余时间会写入flag:timeout",
},
],
},
{
case: "condition",
condition: "flag:type==0\n&&flag:keycode==13",
action: [
{
type: "comment",
text: "当满足自定义条件时会执行此事件\n超时剩余时间会写入flag:timeout",
},
],
},
{
case: "timeout",
action: [
{ type: "comment", text: "当超时未操作时执行此事件" },
],
},
],
}),
MotaActionBlocks["waitAsync_s"].xmlText(),
MotaActionBlocks["stopAsync_s"].xmlText(),
MotaActionBlocks["op_s"].xmlText(),
MotaActionBlocks["drawWarning_s"].xmlText(),
MotaActionBlocks["changeMouse_s"].xmlText(),
MotaActionBlocks["removeMouse_s"].xmlText(),
MotaActionBlocks["vibrate_s"].xmlText(),
MotaActionBlocks["setanimate_s"].xmlText(),
MotaActionBlocks["deleteanimate_s"].xmlText(),
MotaActionBlocks["playanimate_s"].xmlText(),
MotaActionBlocks["clearanimate_s"].xmlText(),
MotaActionBlocks["animate_s"].xmlText(),
MotaActionBlocks["animate_1_s"].xmlText(),
MotaActionBlocks["stopAnimate_s"].xmlText(),
MotaActionBlocks["setViewport_s"].xmlText(),
MotaActionBlocks["setViewport_1_s"].xmlText(),
MotaActionBlocks["lockViewport_s"].xmlText(),
MotaActionBlocks["showStatusBar_s"].xmlText(),
MotaActionBlocks["hideStatusBar_s"].xmlText(),
MotaActionBlocks["setHeroOpacity_s"].xmlText(),
MotaActionBlocks["setCurtain_0_s"].xmlText(),
MotaActionBlocks["setCurtain_1_s"].xmlText(),
MotaActionBlocks["screenFlash_s"].xmlText(),
MotaActionBlocks["setWeather_s"].xmlText(),
MotaActionBlocks["callBook_s"].xmlText(),
MotaActionBlocks["callSave_s"].xmlText(),
MotaActionBlocks["autoSave_s"].xmlText(),
MotaActionBlocks["forbidSave_s"].xmlText(),
MotaActionBlocks["callLoad_s"].xmlText(),
],
音像处理: [
MotaActionBlocks["animationDrawable_s"].xmlText(),
MotaActionBlocks["setanimate_s"].xmlText(),
MotaActionBlocks["deleteanimate_s"].xmlText(),
MotaActionBlocks["playanimate_s"].xmlText(),
MotaActionBlocks["clearanimate_s"].xmlText(),
MotaActionBlocks["showImage_s"].xmlText(),
MotaActionBlocks["showImage_1_s"].xmlText(),
MotaActionBlocks["hideImage_s"].xmlText(),
MotaActionBlocks["showTextImage_s"].xmlText(),
MotaActionBlocks["moveImage_s"].xmlText(),
MotaActionBlocks["rotateImage_s"].xmlText(),
MotaActionBlocks["scaleImage_s"].xmlText(),
MotaActionBlocks["showGif_s"].xmlText(),
MotaActionBlocks["playBgm_s"].xmlText(),
MotaActionBlocks["playStereo_s"].xmlText(),
MotaActionBlocks["moveStereo_s"].xmlText(),
MotaActionBlocks["pauseBgm_s"].xmlText(),
MotaActionBlocks["resumeBgm_s"].xmlText(),
MotaActionBlocks["loadBgm_s"].xmlText(),
MotaActionBlocks["freeBgm_s"].xmlText(),
MotaActionBlocks["playSound_s"].xmlText(),
MotaActionBlocks["playSound_1_s"].xmlText(),
MotaActionBlocks["stopSound_s"].xmlText(),
MotaActionBlocks["setVolume_s"].xmlText(),
MotaActionBlocks["setBgmSpeed_s"].xmlText(),
],
UI绘制: [
MotaActionBlocks["previewUI_s"].xmlText(),
MotaActionBlocks["clearMap_s"].xmlText(),
MotaActionBlocks["setAttribute_s"].xmlText(),
MotaActionBlocks["setFilter_s"].xmlText(),
MotaActionBlocks["fillText_s"].xmlText(),
MotaActionBlocks["fillBoldText_s"].xmlText(),
MotaActionBlocks["drawTextContent_s"].xmlText(),
MotaActionBlocks["fillRect_s"].xmlText(),
MotaActionBlocks["strokeRect_s"].xmlText(),
MotaActionBlocks["drawLine_s"].xmlText(),
MotaActionBlocks["drawArrow_s"].xmlText(),
MotaActionBlocks["fillPolygon_s"].xmlText(),
MotaActionBlocks["strokePolygon_s"].xmlText(),
MotaActionBlocks["fillEllipse_s"].xmlText(),
MotaActionBlocks["strokeEllipse_s"].xmlText(),
MotaActionBlocks["fillArc_s"].xmlText(),
MotaActionBlocks["strokeArc_s"].xmlText(),
MotaActionBlocks["drawImage_s"].xmlText(),
MotaActionBlocks["drawImage_1_s"].xmlText(),
MotaActionBlocks["drawIcon_s"].xmlText(),
MotaActionBlocks["drawBackground_s"].xmlText(),
MotaActionBlocks["drawSelector_s"].xmlText(),
MotaActionBlocks["drawSelector_1_s"].xmlText(),
],
原生脚本: [
MotaActionBlocks["function_s"].xmlText(),
MotaActionBlocks["unknown_s"].xmlText(),
],
值块: [
MotaActionBlocks["setValue_s"].xmlText([
MotaActionBlocks["idIdList_e"].xmlText(["status", "生命"]),
"=",
"",
false,
]),
MotaActionBlocks["expression_arithmetic_0"].xmlText(),
MotaActionBlocks["idFlag_e"].xmlText(),
MotaActionBlocks["idTemp_e"].xmlText(),
MotaActionBlocks["negate_e"].xmlText(),
MotaActionBlocks["unaryOperation_e"].xmlText(),
MotaActionBlocks["bool_e"].xmlText(),
MotaActionBlocks["idString_e"].xmlText(),
MotaActionBlocks["idIdList_e"].xmlText(),
MotaActionBlocks["idFixedList_e"].xmlText(),
MotaActionBlocks["enemyattr_e"].xmlText(),
MotaActionBlocks["blockId_e"].xmlText(),
MotaActionBlocks["blockNumber_e"].xmlText(),
MotaActionBlocks["blockCls_e"].xmlText(),
MotaActionBlocks["hasEquip_e"].xmlText(),
MotaActionBlocks["equip_e"].xmlText(),
MotaActionBlocks["nextXY_e"].xmlText(),
MotaActionBlocks["isReplaying_e"].xmlText(),
MotaActionBlocks["hasVisitedFloor_e"].xmlText(),
MotaActionBlocks["isShopVisited_e"].xmlText(),
MotaActionBlocks["canBattle_e"].xmlText(),
MotaActionBlocks["damage_e"].xmlText(),
MotaActionBlocks["damage_1_e"].xmlText(),
MotaActionBlocks["rand_e"].xmlText(),
MotaActionBlocks["evalString_e"].xmlText(),
],
常见事件模板: [
'<label text="此处只是占位符,实际定义在#region 動態常見事件模板"></label>',
],
最近使用事件: [
'<label text="此处只是占位符,实际定义在editor_blockly.searchBlockCategoryCallback中"></label>',
],
};
var toolboxgap = '<sep gap="5"></sep>';
//xml_text = MotaActionFunctions.actionParser.parse(obj,type||'event')
//MotaActionBlocks['idString_e'].xmlText()
2025-01-02 17:04:30 +08:00
2025-01-16 13:17:19 +08:00
//#region 動態常見事件模板
let CommonEventTemplateHTML = [];
2025-01-02 17:04:30 +08:00
2025-01-16 13:17:19 +08:00
for (let commonEventName in events_c12a15a8_c380_4b28_8144_256cba95f760.CommonEventTemplate) {
if (
events_c12a15a8_c380_4b28_8144_256cba95f760.CommonEventTemplate.hasOwnProperty(
commonEventName
)
) {
let actionParserJson = Array.from(
events_c12a15a8_c380_4b28_8144_256cba95f760.CommonEventTemplate[
commonEventName
] ?? []
);
2025-01-02 17:04:30 +08:00
2025-01-16 13:17:19 +08:00
let labelHTML = "";
let blockHTML = "";
2025-01-02 17:04:30 +08:00
2025-01-16 13:17:19 +08:00
labelHTML = `<label text="${commonEventName}"></label>`;
2025-01-02 17:04:30 +08:00
2025-01-16 13:17:19 +08:00
if (actionParserJson.length > 1) {
actionParserJson = {
type: "if",
condition: "true",
true: actionParserJson,
};
} else if (actionParserJson.length < 1) {
actionParserJson = [
"空的常用事件模板。\n請在主頁下拉菜單中選擇常用事件模板進行編輯。\n編輯後需按F5刷新事件編輯器。",
];
}
blockHTML =
MotaActionFunctions.actionParser.parseList(actionParserJson);
2025-01-02 17:04:30 +08:00
2025-01-16 13:17:19 +08:00
CommonEventTemplateHTML.push(labelHTML);
CommonEventTemplateHTML.push(blockHTML);
}
}
toolboxObj["常见事件模板"] = CommonEventTemplateHTML;
//#endregion
for (var name in toolboxObj) {
var custom = null;
if (name == "最近使用事件") custom = "searchBlockCategory";
if (name == "入口方块") custom = "entranceCategory";
getCategory(name, custom).innerHTML =
toolboxObj[name].join(toolboxgap);
}
var blocklyArea = document.getElementById("blocklyArea");
var blocklyDiv = document.getElementById("blocklyDiv");
var workspace = Blockly.inject(blocklyDiv, {
media: "_server/blockly/media/",
toolbox: document.getElementById("toolbox"),
zoom: {
controls: true,
wheel: false, //滚轮改为上下(shift:左右)翻滚
startScale: 1.0,
maxScale: 3,
minScale: 0.3,
scaleSpeed: 1.08,
},
trashcan: false,
});
editor_blockly.isCommonEntry = function () {
var commonEntries = [
"beforeBattle",
"afterBattle",
"afterOpenDoor",
"firstArrive",
"eachArrive",
"commonEvent",
"item",
];
return commonEntries.indexOf(editor_blockly.entryType) >= 0;
};
editor_blockly.entranceCategoryCallback = function (workspace) {
var list = toolboxObj["入口方块"];
var xmlList = [];
var eventType =
(editor_blockly.isCommonEntry()
? "common"
: editor_blockly.entryType) + "_m";
for (var ii = 0, blockText; (blockText = list[ii]); ii++) {
if (
new RegExp('<block type="' + eventType + '">').exec(blockText)
) {
var block = Blockly.Xml.textToDom(
"<xml>" + blockText + "</xml>"
).firstChild;
block.setAttribute("gap", 5);
xmlList.push(block);
}
}
return xmlList;
};
workspace.registerToolboxCategoryCallback(
"entranceCategory",
editor_blockly.entranceCategoryCallback
);
editor_blockly.searchBlockCategoryCallback = function (workspace) {
var xmlList = [];
var labels = editor_blockly.searchBlock();
for (var i = 0; i < labels.length; i++) {
var blockText =
"<xml>" + MotaActionBlocks[labels[i]].xmlText() + "</xml>";
var block = Blockly.Xml.textToDom(blockText).firstChild;
block.setAttribute("gap", 5);
xmlList.push(block);
}
return xmlList;
};
workspace.registerToolboxCategoryCallback(
"searchBlockCategory",
editor_blockly.searchBlockCategoryCallback
);
var onresize = function (e) {
blocklyDiv.style.width = blocklyArea.offsetWidth + "px";
blocklyDiv.style.height = blocklyArea.offsetHeight + "px";
Blockly.svgResize(workspace);
};
if (typeof editor !== "undefined" && !editor.isMobile)
window.addEventListener("resize", onresize, false);
onresize();
//Blockly.svgResize(workspace);
//Blockly.bindEventWithChecks_(workspace.svgGroup_,"wheel",workspace,function(e){});
document.getElementById("blocklyDiv").onmousewheel = function (e) {
//console.log(e);
e.preventDefault();
var hvScroll = e.shiftKey ? "hScroll" : "vScroll";
var mousewheelOffsetValue =
(20 / 380) * workspace.scrollbar[hvScroll].handleLength_ * 3;
workspace.scrollbar[hvScroll].handlePosition_ +=
(e.deltaY || 0) + (e.detail || 0) > 0
? mousewheelOffsetValue
: -mousewheelOffsetValue;
workspace.scrollbar[hvScroll].onScroll_();
// workspace.setScale(workspace.scale);
};
var doubleClickCheck = [[0, "abc"]];
function omitedcheckUpdateFunction(event) {
if (event.type === "create") {
editor_blockly.addIntoLastUsedType(event.blockId);
}
if (event.type === "ui" && event.element == "click") {
var newClick = [new Date().getTime(), event.blockId];
var lastClick = doubleClickCheck.shift();
doubleClickCheck.push(newClick);
if (newClick[0] - lastClick[0] < 500) {
if (newClick[1] === lastClick[1]) {
editor_blockly.doubleClickBlock(newClick[1]);
}
}
}
// Only handle these events
if (["create", "move", "change", "delete"].indexOf(event.type) < 0)
return;
if (editor_blockly.workspace.topBlocks_.length >= 2) {
editor_blockly.setValue("入口方块只能有一个");
return;
}
var eventType = editor_blockly.entryType;
if (editor_blockly.workspace.topBlocks_.length == 1) {
var blockType = editor_blockly.workspace.topBlocks_[0].type;
if (
blockType !== eventType + "_m" &&
!(editor_blockly.isCommonEntry() && blockType == "common_m")
) {
editor_blockly.setValue("入口方块类型错误");
return;
}
}
try {
var code = Blockly.JavaScript.workspaceToCode(workspace).replace(
/\\(i|c|d|e|g|z)/g,
"\\\\$1"
);
editor_blockly.setValue(code);
} catch (error) {
editor_blockly.setValue(String(error));
if (error instanceof OmitedError) {
var blockName = error.blockName;
var varName = error.varName;
var block = error.block;
}
// console.log(error);
}
}
workspace.addChangeListener(omitedcheckUpdateFunction);
workspace.addChangeListener(Blockly.Events.disableOrphans);
editor_blockly.workspace = workspace;
MotaActionFunctions.workspace = function () {
return editor_blockly.workspace;
};
// 因为在editor_blockly.parse里已经HTML转义过一次了,所以这里要覆盖掉以避免在注释中出现<等
MotaActionFunctions.xmlText = function (
ruleName,
inputs,
isShadow,
comment,
collapsed,
disabled
) {
var rule = MotaActionBlocks[ruleName];
var blocktext = isShadow ? "shadow" : "block";
var xmlText = [];
xmlText.push(
"<" +
blocktext +
' type="' +
ruleName +
'"' +
(collapsed ? ' collapsed="true"' : "") +
(disabled ? ' disabled="true"' : "") +
">"
);
if (!inputs) inputs = [];
for (var ii = 0, inputType; (inputType = rule.argsType[ii]); ii++) {
var input = inputs[ii];
var _input = "";
var noinput = input === null || input === undefined;
if (
noinput &&
inputType === "field" &&
MotaActionBlocks[rule.argsGrammarName[ii]].type !==
"field_dropdown"
)
continue;
if (noinput && inputType === "field") {
noinput = false;
input = rule.fieldDefault(rule.args[ii]);
}
if (noinput) input = "";
if (
inputType === "field" &&
MotaActionBlocks[rule.argsGrammarName[ii]].type ===
"field_checkbox"
)
input = input ? "TRUE" : "FALSE";
if (inputType !== "field") {
var subList = false;
var subrulename = rule.argsGrammarName[ii];
var subrule = MotaActionBlocks[subrulename];
if (subrule instanceof Array) {
subrulename = subrule[subrule.length - 1];
subrule = MotaActionBlocks[subrulename];
subList = true;
}
_input = subrule.xmlText([], true);
if (noinput && !subList && !isShadow) {
//无输入的默认行为是: 如果语句块的备选方块只有一个,直接代入方块
input = subrule.xmlText();
}
}
xmlText.push("<" + inputType + ' name="' + rule.args[ii] + '">');
xmlText.push(_input + input);
xmlText.push("</" + inputType + ">");
}
if (comment) {
xmlText.push("<comment>");
xmlText.push(comment);
xmlText.push("</comment>");
}
var next = inputs[rule.args.length];
if (next) {
//next
xmlText.push("<next>");
xmlText.push(next);
xmlText.push("</next>");
}
xmlText.push("</" + blocktext + ">");
return xmlText.join("");
};
})();
// end mark sfergsvae
}
.toString()
.split("// start mark sfergsvae")[1]
.split("// end mark sfergsvae")[0];
}
},
2025-01-18 17:49:44 +08:00
"夹击激光动画": function () {
2025-01-02 17:04:30 +08:00
function createCanvas(name, zIndex) {
if (!name) return;
var canvas = document.createElement("canvas");
canvas.id = name;
canvas.className = "gameCanvas";
// 将图层插入进游戏内容
document.getElementById("gameDraw").appendChild(canvas);
canvas.style.zIndex = zIndex || 0;
2024-12-27 11:55:28 +08:00
var ctx = canvas.getContext("2d");
core.canvas[name] = ctx;
canvas.width = core.__PIXELS__;
canvas.height = core.__PIXELS__;
return canvas;
}
var bg3Canvas = createCanvas("bg3", 25);
if (main.mode == "editor") {
// 与编辑器显伤的神秘联动(((
editor.dom.mapEdit.insertBefore(bg3Canvas, core.canvas.event.canvas);
bg3Canvas.style.zIndex = null;
}
core.bigmap.canvas = ["bg", "bg3", "event", "event2", "fg", "damage"];
core.plugin._betCanvas = "bg3";
this._drawBetweenAttack = function (x, y, pos, frame) {
var ctx = core.plugin._betCanvas;
var w = 32,
h = 68,
ix = x * 32,
iy = y * 32;
// 左右夹击
if (pos[0])
core.drawImage(
ctx,
2025-01-05 00:03:41 +08:00
"light.webp",
2024-12-27 11:55:28 +08:00
32 * (frame - 1),
0,
32,
68,
ix,
iy - 18,
w,
h,
(90 * Math.PI) / 180
);
// 上下夹击
if (pos[1])
core.drawImage(
ctx,
2025-01-05 00:03:41 +08:00
"light.webp",
2024-12-27 11:55:28 +08:00
32 * (frame - 1),
0,
32,
68,
ix,
iy - 18,
w,
h
);
};
core.registerAnimationFrame("betweenAttack", true, function (timestamp) {
if (!flags.betweenAttackData) {
core.clearMap(
core.plugin._betCanvas,
0,
0,
core.__PIXELS__,
core.__PIXELS__
);
return;
}
var time = core.events._timestamp;
if (time && timestamp - time < 400) return;
core.clearMap(
core.plugin._betCanvas,
0,
0,
core.__PIXELS__,
core.__PIXELS__
);
core.events._timestamp = timestamp;
var data = flags.betweenAttackData || {};
flags._frame = flags._frame || 1;
var frame = flags._frame;
for (var loc in data) {
var l = loc.split(",");
var x = parseInt(l[0]),
y = parseInt(l[1]);
core.plugin._drawBetweenAttack(x, y, data[loc], frame);
}
flags._frame = frame + 1;
if (flags._frame > 4) flags._frame = 1;
});
var origin_extraDamage = core.control._updateDamage_extraDamage;
core.control._updateDamage_extraDamage = function (floorId, onMap) {
flags.betweenAttackData = null;
if (!flags.useBetweenLight)
return origin_extraDamage.call(core.control, floorId, onMap);
else {
core.status.damage.extraData = [];
if (!core.flags.displayExtraDamage) return;
var width = core.floors[floorId].width,
height = core.floors[floorId].height;
var startX =
onMap && core.bigmap.v2
? Math.max(0, core.bigmap.posX - core.bigmap.extend)
: 0;
var endX =
onMap && core.bigmap.v2
? Math.min(
width,
core.bigmap.posX + core.__SIZE__ + core.bigmap.extend + 1
)
: width;
var startY =
onMap && core.bigmap.v2
? Math.max(0, core.bigmap.posY - core.bigmap.extend)
: 0;
var endY =
onMap && core.bigmap.v2
? Math.min(
height,
core.bigmap.posY + core.__SIZE__ + core.bigmap.extend + 1
)
: height;
const find = function (x, y) {
return core.status.damage.extraData.find(function (data) {
return data.x == x && data.y == y;
});
};
for (var x = startX; x < endX; x++) {
for (var y = startY; y < endY; y++) {
var alpha = 1;
if (core.noPass(x, y, floorId)) {
if (core.flags.extraDamageType == 2) alpha = 0;
else if (core.flags.extraDamageType == 1) alpha = 0.6;
}
var loc = x + "," + y;
var damage = core.status.checkBlock.damage[loc] || 0;
var getEnemy = function (x, y) {
var id = core.getBlockId(x, y, floorId);
var e = core.material.enemys[id];
if (main.mode == "editor") e = core.enemys.enemys[id];
return e;
};
if (damage > 0) {
// 该点伤害
damage = core.formatBigNumber(damage, true);
var left = false,
top = false;
var e_left = getEnemy(x - 1, y),
e_right = getEnemy(x + 1, y);
var e_bottom = getEnemy(x, y - 1),
e_top = getEnemy(x, y + 1);
if (
core.hasSpecial(e_left, 16) &&
core.hasSpecial(e_right, 16) &&
e_left.id == e_right.id
)
left = true;
if (
core.hasSpecial(e_bottom, 16) &&
core.hasSpecial(e_top, 16) &&
e_bottom.id == e_top.id
)
top = true;
flags.betweenAttackData = flags.betweenAttackData || {};
if (flags.betweenAttackData[x + "," + y]) continue;
var data = [left, top];
let [px, py] = [32 * x + 16, 32 * (y + 1) - 14];
if (left || top) {
px += 15;
py -= 10;
flags.betweenAttackData[x + "," + y] = data;
}
core.plugin._drawBetweenAttack(x, y, data, 1);
if (left) {
if (!find(x - 1, y))
core.status.damage.extraData.push({
x: x,
y: y,
text: damage,
px: 32 * x + 16,
py: 32 * (y + 1) - 14,
color: "#ffaa33",
alpha: alpha,
});
} else if (top) {
if (!find(x, y - 1))
core.status.damage.extraData.push({
x: x,
y: y,
text: damage,
px: 32 * x + 16,
py: 32 * (y + 1) - 14,
color: "#ffaa33",
alpha: alpha,
});
} else {
core.status.damage.extraData.push({
text: damage,
px: 32 * x + 16,
py: 32 * (y + 1) - 14,
color: "#ffaa33",
alpha: alpha,
});
}
} else {
// 检查捕捉
if (core.status.checkBlock.ambush[x + "," + y]) {
core.status.damage.extraData.push({
text: "!",
px: 32 * x + 16,
py: 32 * (y + 1) - 14,
color: "#ffaa33",
alpha: alpha,
});
}
}
}
}
}
};
},
2025-01-18 17:49:44 +08:00
"瞬移轨迹": function () {
2025-01-16 13:17:19 +08:00
// 在此增加新插件
function popMove() {
var ctx = core.getContextByName("popMove");
if (!ctx)
ctx = core.createCanvas(
"popMove",
0,
0,
core.__PIXELS__,
core.__PIXELS__,
71
);
ctx.canvas.classList.add("gameCanvas", "anti-aliasing");
core.clearMap(ctx);
if (core.status.replay.speed <= 3 && !flags.stopPop) {
var list = core.status.popMove || [];
var count = 0;
list.forEach(function (one) {
// 由frame计算出dy
one.frame++;
// 绘制
2025-01-06 17:00:58 +08:00
2025-01-16 13:17:19 +08:00
if (one.frame >= 0) core.setAlpha(ctx, 1 - one.frame / 30);
else core.setAlpha(ctx, 1);
2025-01-06 17:00:58 +08:00
2025-01-16 13:17:19 +08:00
var length = Math.sqrt(
Math.pow(one.px2 - one.px, 2) + Math.pow(one.py2 - one.py, 2)
);
//console.log(length)
var li = length / 16;
var lx = (one.px2 - one.px) / li;
var ly = (one.py2 - one.py) / li;
for (var i = 0; i < li; i += 1) {
core.setAlpha(ctx, (1 - one.frame / 30) * ((i / li) * 0.8 + 0.2));
core.drawLine(
"popMove",
one.px + i * lx,
one.py + i * ly,
one.px + (i + 0.5) * lx,
one.py + (i + 0.5) * ly,
"#E1E1E1",
6
);
if (i == 0)
core.strokeCircle("popMove", one.px, one.py, 6, "#E1E1E1", 3);
}
core.strokeCircle("popMove", one.px2, one.py2, 6, "#E1E1E1", 3);
core.strokeCircle(
"popMove",
one.px2,
one.py2,
6 + (16 * one.frame) / 30,
"#E1E1E1",
6 * (1 - one.frame / 30)
);
//core.drawLine('popMove', one.px, one.py, one.px2, one.py2, '#E1E1E1', 6);
2025-01-06 17:00:58 +08:00
2025-01-16 13:17:19 +08:00
if (one.frame >= 30) count++;
});
if (count > 0) list.splice(0, count);
}
}
2024-12-27 11:55:28 +08:00
2025-01-16 13:17:19 +08:00
if (!main.replayChecking) {
core.registerAnimationFrame("popMove", true, popMove);
}
this.addPopMove = function (px, py, px2, py2, frame) {
var data = { px: px, py: py, px2: px2, py2: py2, frame: frame || 0 };
if (core.status.replay.speed <= 3) {
if (!core.status.popMove) core.status.popMove = [data];
else core.status.popMove.push(data);
}
};
},
2025-01-18 17:49:44 +08:00
"墓碑(编辑器)": function () {
2024-12-27 11:55:28 +08:00
// 在此增加新插件
if (main.mode != "editor") return; // 编辑器模式下使用
var mapData = null;
var drawMap = core.maps._drawMap_drawAll;
core.maps._drawMap_drawAll = function (floorId) {
floorId = floorId || core.status.floorId;
if (!main.useCompress) {
core.getLocalForage(
"autoSave",
{ maps: [] },
function (v) {
mapData = v.maps[floorId]?.map;
drawMap.call(core.maps, floorId);
},
function (e) {
console.log(e);
}
);
}
};
maps.prototype._drawBlockInfo = function (blockInfo, x, y) {
var alpha = 1.0;
if (mapData && !!mapData[y] && mapData[y][x] == 0) {
core.setAlpha("event", 0.5);
core.setAlpha("event2", 0.5);
}
var image = blockInfo.image,
posX = blockInfo.posX,
posY = blockInfo.posY,
height = blockInfo.height;
core.clearMap("event", x * 32, y * 32, 32, 32);
if (blockInfo.bigImage) {
this._drawBlockInfo_bigImage(blockInfo, x, y, "event");
core.setAlpha("event", alpha);
core.setAlpha("event2", alpha);
return;
}
core.drawImage(
"event",
image,
posX * 32,
posY * height + height - 32,
32,
32,
x * 32,
y * 32,
32,
32
);
if (height > 32) {
core.clearMap("event2", x * 32, y * 32 + 32 - height, 32, height - 32);
core.drawImage(
"event2",
image,
posX * 32,
posY * height,
32,
height - 32,
x * 32,
y * 32 + 32 - height,
32,
height - 32
);
}
core.setAlpha("event", alpha);
core.setAlpha("event2", alpha);
};
},
2025-01-18 17:49:44 +08:00
"小地图": function () {
2024-12-27 11:55:28 +08:00
// 在此增加新插件
// ----- 不可自定义 杂七杂八的变量
/** @type {{[x: string]: BFSResult}} */
let mapCache = {}; // 地图缓存
let drawCache = {}; // 绘制信息缓存
let status = "none"; // 当前的绘制状态
/** @type {{[x: string]: Sprite}} */
let sprites = {}; // 当前所有的sprite
/** @type {{[x: string]: Sprite}} */
let canDrag = {}; // 可以拖拽的sprite
/** @type {{[x: string]: Button}} */
let areaSprite = {}; // 区域列表对应的sprite
let clicking = false; // 是否正在点击,用于拖拽判定
let drawingMap = ""; // 正在绘制的中心楼层
let nowScale = 0; // 当前绘制的放缩比例
let lastTouch = {}; // 上一次的单点点击信息
let lastLength = 0; // 手机端缩放时上一次的两指间距离
let nowDepth = 0; // 当前的遍历深度
let drawedThumbnail = {}; // 已经绘制过的缩略图
let moved = false; // 鼠标按下后是否移动了
let noBorder = false; // 是否是无边框拼接模式
let lastScale = 0; // 上一次缩放,用于优化缩略图绘制
let areaPage = 0; // 区域显示的当前页数
let nowArea = 0; // 当前区域index
let selecting = ""; // 选择时当前正在选择的地图
// ---- 不可自定义,常量
/** @type {Area} */
let areas = []; // 区域信息
const perPage = Math.floor((core._PY_ - 60) / 30); // 区域的每页显示数量
// ---- 可自定义默认的切换地图的图块id
const defaultChange = {
left: "leftPortal", // 左箭头
up: "upPortal", // 上箭头
right: "rightPortal", // 右箭头
down: "downPortal", // 下箭头
upFloor: "upFloor", // 上楼
downFloor: "downFloor", // 下楼
};
// ---- 可自定义,默认数值
const defaultValue = {
font: "Verdana", // 默认字体
scale: 60, // 默认地图缩放比例
depth: Infinity, // 默认的遍历深度
};
// ---- 不可自定义,计算数据
const dirData = {
up: [1, 0],
down: [-1, 0],
left: [0, 1],
right: [0, -1],
upFloor: [0, 0],
downFloor: [0, 0],
};
let ignoreEnemies = (this.ignoreEnemies = []);
let allChangeEntries = Object.entries(defaultChange);
2025-01-16 13:28:25 +08:00
this.setq = function (floorId) {
core.setFlag("任务地点", floorId);
};
2024-12-27 11:55:28 +08:00
const reset = core.events.resetGame;
this.bfs = function () {
areas = [];
// 获取所有分区,使用异步函数,保证不会卡顿
// 原理是用bfs扫将所有连在一起的地图合并成一个区域
(async function () {
let all = core.floorIds.slice();
const scanned = {
[all[0]]: true,
};
while (all.length > 0) {
let now = all.shift();
if (core.status.maps[now].deleted) continue;
if (!now) return;
await new Promise((res) => {
const result = core.plugin.bfsSearch(now, Infinity, true);
mapCache[`${now}_Infinity_false`] = result;
areas.push({ name: core.floors[now].areas, maps: result.order });
for (const map of result.order) {
scanned[map] = true;
all = all.filter((v) => !result.order.includes(v));
}
res("success");
}).then(() => {
core.setFlag("areas", areas);
});
}
})();
};
core.events.resetGame = function () {
reset.apply(core.events, arguments);
core.plugin.bfs();
};
/**
* 广度优先搜索搜索地图路径
* @param {string} center 中心地图的id
* @param {number} depth 搜索深度
* @param {boolean} noCache 是否不使用缓存
* @returns {BFSResult} 格式floorId_x_y_dir: floorId_x_y
*/
this.bfsSearch = function bfsSearch(center, depth, noCache) {
// 检查缓存
const id = `${center}_${depth}_${noBorder}`;
if (mapCache[id] && !noCache) return mapCache[id];
const used = {
[center]: true,
}; // 搜索过的楼层
let queue = [];
let stack = [center]; // 当前栈
let nowDepth = -1;
const mapOrder = [center]; // 遍历顺序,顺便还能记录遍历了哪些楼层
const res = {}; // 输出结果格式floorId_x_y_dir: floorId_x_y
const enemies = {};
const upOrDown = {};
const mapdir = {};
// 开始循环搜索
while (nowDepth < depth && stack.length > 0) {
const now = stack.shift(); // 当前id
if (core.status.maps[now].deleted) continue;
mapdir[now] = mapdir[now] ?? [];
const blocks = core.getMapBlocksObj(now); // 获取当前地图的每点的事件
enemies[now] = {};
// 遍历,获取可以传送的点,只检测绿点事件,因此可用红点事件进行传送来实现分区功能
for (const i in blocks) {
const block = blocks[i];
// 整合漏怪检测,所以要检测怪物
if (block.event.trigger === "battle") {
const id = block.event.id;
if (ignoreEnemies.includes(id)) continue;
else enemies[now][i] = block.event.id;
continue;
}
// 检测触发器是否为切换楼层,不是则直接跳过
if (block.event.trigger !== "changeFloor") continue;
const dirEntries = allChangeEntries.find(
(v) => v[1] === block.event.id
);
// 如果不是那六种传送门,直接忽略
if (!dirEntries) continue;
const data = block.event.data;
const dir = dirEntries[0];
const route = now + "_" + i.replace(",", "_") + "_" + dir;
const target = data.floorId + "_" + data.loc.join("_");
mapdir[now].push(dir);
if (!used[data.floorId]) {
if (dir === "upFloor" || dir === "downFloor") {
upOrDown[now] = upOrDown[id] ?? [];
upOrDown[now].push(dir);
}
queue.push(data.floorId); // 没有搜索过,则加入栈中
mapOrder.push(data.floorId);
used[data.floorId] = true;
}
res[route] = target;
}
if (stack.length === 0) {
stack = queue;
queue = [];
nowDepth++;
}
if (stack.length === 0 && queue.length === 0) break;
}
return { res, order: mapOrder, enemies, upOrDown, mapdir };
};
/**
* 获取绘制信息
* @param {string?} center 中心地图id
* @param {number?} depth 搜索深度
* @param {boolean?} noCache 是否不使用缓存
* @returns {MapDrawInfo}
*/
this.getMapDrawInfo = function (
center = core.status.floorId,
depth = defaultValue.depth,
noCache = false
) {
nowDepth = depth;
drawingMap = center;
const id = `${center}_${depth}_${noBorder}`;
// 检查缓存
if (drawCache[id] && !noCache) return drawCache[id];
const map = core.plugin.bfsSearch(center, depth, noCache);
mapCache[id] = map;
const res = getDrawInfo(map.res, center, map.order);
res.upOrDown = map.upOrDown;
res.mapdir = map.mapdir;
drawCache[id] = res;
return res;
};
/**
* 提供地图的绘制信息
* @param {{[x: string]: string}} map 要绘制的地图格式floorId_x_y_dir: floorId_x_y
* @param {string} center 中心地图的id
* @param {string[]} order 遍历顺序
* @returns {MapDrawInfo} 地图的绘制信息
*/
function getDrawInfo(map, center, order) {
// 先根据地图id分类从而确定每个地图连接哪些地图同时方便处理
const links = {};
for (const i in map) {
const splitted = i.split("_");
const id = splitted[0];
if (!links[id]) links[id] = {};
links[id][i] = map[i];
}
// 分类完毕,然后根据连接点先计算出各个地图的坐标,然后再进行判断
const centerFloor = core.status.maps[center];
const visitedCenter = core.hasVisitedFloor(center);
const locs = {
// 格式:[中心x, 中心y, 宽, 高, 是否到达过]
[center]: [2, 2, 1, 1, visitedCenter],
};
// 可以上楼下楼的地图
const upOrDown = {};
for (const id of order) {
const now = links[id];
// 遍历每一个地图的连接情况
for (const from in now) {
const to = now[from];
// 先根据from to计算物理位置
const fromData = from.split("_"),
toData = to.split("_");
const dir = fromData[3];
if (dir === "upFloor" || dir === "downFloor") continue;
if (!defaultChange[dir]) continue;
const v = dirData[dir][1], // 竖直数值
h = dirData[dir][0], // 水平数值
ha = Math.abs(h),
va = Math.abs(v);
const ff = id, // fromFloorId
tf = toData[0]; // toFloorId
const fromFloor = core.status.maps[ff],
toFloor = core.status.maps[tf];
const fhw = Math.floor(fromFloor.width / 2), // fromFloorHalfWidth
fhh = Math.floor(fromFloor.height / 2),
thw = Math.floor(toFloor.width / 2),
thh = Math.floor(toFloor.height / 2);
const fLoc = locs[id] ?? [0, 0];
if (!locs[ff]) continue;
let x, y;
if (locs && locs[tf]) {
x = locs[tf][0];
y = locs[tf][1];
} else {
// 计算坐标,公式可以通过画图推断出
x = fLoc[0] - v;
y = fLoc[1] - h;
}
locs[tf] = locs[tf] ?? [x, y, 1, 1, core.hasVisitedFloor(tf)];
}
}
// 获取地图绘制需要的长宽
let width = 0,
height = 0;
let left, right, up, down;
for (const id in locs) {
const [x, y, w, h] = locs[id];
if (left === void 0) {
left = right = x;
up = down = y;
}
left = Math.min(x - 1, left);
right = Math.max(x + 1, right);
up = Math.min(y - 1, up);
down = Math.max(y + 1, down);
}
width = right - left;
height = down - up;
return { locs, width, height, layer: upOrDown };
}
function mapblock(mapdir) {
let mb = "";
if (mapdir.includes("up")) mb += "u";
if (mapdir.includes("down")) mb += "d";
if (mapdir.includes("left")) mb += "l";
if (mapdir.includes("right")) mb += "r";
2025-01-05 00:03:41 +08:00
return mb ? mb + ".webp" : "null.webp";
2024-12-27 11:55:28 +08:00
}
core.animateFrame.globalAlphaFloor = 0;
core.animateFrame.globalAlphaFloorStatus = 1;
const tesk = document.createElement("canvas");
tesk.width = 300;
tesk.height = 300;
const teskctx = tesk.getContext("2d");
let line = 50;
teskctx.strokeStyle = "green";
teskctx.fillStyle = "green";
2025-01-02 17:04:30 +08:00
let now = 0;
core.registerAnimationFrame("tesk", true, function (timestamp) {
if (timestamp - now > 1000 / 60) {
now = timestamp;
core.clearMap(teskctx);
teskctx.lineWidth = 150 - line;
if (line <= 150) {
teskctx.beginPath();
teskctx.arc(150, 150, line, 0, Math.PI * 2);
line += 2;
teskctx.stroke();
} else {
teskctx.beginPath();
teskctx.arc(150, 150, line - 150, 0, Math.PI * 2);
line += 2;
teskctx.fill();
if (line >= 250) line = 50;
}
2024-12-27 11:55:28 +08:00
}
});
/**
* 绘制小地图
* @param {MapDrawInfo} info 地图绘制信息
* @param {number} scale 地图的绘制比例
*/
this.drawSmallMap = function (
ctx,
info,
center,
sx,
sy,
sw,
sh,
scale = defaultValue.scale
) {
core.clearMap(ctx, sx, sy, sw + 40, sh + 60);
2024-12-30 20:44:53 +08:00
if (core.domStyle.isVertical) {
2024-12-27 11:55:28 +08:00
sy += 50;
2024-12-30 20:44:53 +08:00
sx += 15;
2024-12-27 11:55:28 +08:00
} else {
2024-12-30 20:44:53 +08:00
sy += 60;
2024-12-27 11:55:28 +08:00
sx += 30;
}
core.fillRect(ctx, sx, sy, sw, sh, "#000");
core.strokeRect(ctx, sx, sy, sw, sh, "#fff", 5);
core.setTextAlign("outerUI", "center");
2024-12-28 23:53:21 +08:00
core.fillBoldText1(
2024-12-27 11:55:28 +08:00
ctx,
core.status.maps[center].areas,
2024-12-30 20:44:53 +08:00
sx + sw / 2,
2024-12-27 11:55:28 +08:00
sy - 10,
"#FFFFFF",
"#000000",
6,
"bold 42px Verdana"
);
const locs = info.locs;
for (const id in locs) {
const loc = locs[id];
let color = "#000";
if (!loc[4]) color = "#f0f";
const [x, y, w, h] = loc.map((v) => typeof v === "number" && v * scale);
const fx = x + sx,
fy = y + sy;
const mapdir = info.mapdir[id];
const img = mapblock(mapdir);
if (x < 0 || x > 4 * scale || y < 0 || y > 4 * scale) continue;
core.drawImage(ctx, img, 0, 0, 60, 60, fx, fy, w, h);
const layer = info.upOrDown[id];
const min = Math.min(w, h);
if (core.getFlag("任务地点") && core.getFlag("任务地点") === id)
ctx.drawImage(tesk, fx + min / 4, fy + min / 4, min / 2, min / 2);
if (layer?.includes("upFloor"))
core.drawIcon(
ctx,
defaultChange.upFloor,
fx + min / 4,
fy + min / 4,
min / 2,
min / 2
);
if (layer?.includes("downFloor"))
core.drawIcon(
ctx,
defaultChange.downFloor,
fx + min / 4,
fy + min / 4,
min / 2,
min / 2
);
// 显示漏怪数量
if (core.getFlag("showEnemy")) {
ctx.textAlign = "center";
ctx.textBaseline = "middle";
const c = drawingMap + "_" + nowDepth + "_" + noBorder;
const n = Object.keys(mapCache[c].enemies[id]).length;
color = "#fff";
if (n > 10) color = "#fc3";
if (n > 20) color = "#f22";
ctx.shadowBlur = 0.6 * nowScale;
ctx.shadowColor = "#000";
if (n > 0)
core.fillText(
ctx,
n,
fx + (w * 3) / 10,
fy + (h * 7) / 10,
color,
22 + "px normal"
);
ctx.shadowBlur = 0;
}
}
};
},
2025-01-18 17:49:44 +08:00
"楼传": function () {
2024-12-30 20:44:53 +08:00
// 在此增加新插件
2024-12-27 11:55:28 +08:00
2024-12-30 20:44:53 +08:00
core.canMoveFloor = function () {
let canmove = false;
core.status.thisMap.blocks.forEach((block) => {
if (
!block.disable &&
(block.event.id == "upFloor" || block.event.id == "downFloor")
) {
let automaticRoute = core.automaticRoute(block.x, block.y);
if (!core.flags.flyNearStair || automaticRoute.length > 0) {
let loc = automaticRoute.pop();
loc = automaticRoute.pop();
if (core.canMoveDirectly(loc?.x, loc?.y) >= 0 || !loc) {
canmove = true;
}
}
}
});
return canmove;
};
ui.prototype._drawViewMaps_drawHint = function () {
core.playSound("打开界面");
};
2024-12-27 11:55:28 +08:00
2024-12-30 20:44:53 +08:00
////// 绘制浏览地图界面 //////
ui.prototype._drawViewMaps = function (index, x, y) {
core.lockControl();
core.clearMap("data");
core.status.event.id = "viewMaps";
this.clearUI();
//console.log(index)
if (index == null) index = core.floorIds.indexOf(core.status.floorId);
core.animateFrame.tip = null;
core.status.checkBlock.cache = {};
let data = this._drawViewMaps_buildData(index, x, y);
2025-01-05 00:03:41 +08:00
core.drawWindowSkin("winskin1.webp", "ui", 0, 0, 416, 416);
2024-12-30 20:44:53 +08:00
let page = core.status.event.data.index;
let floorId = core.status.event.data.floorId;
core.ui.statusBar._update_map(floorId);
const bfs = core.plugin.bfsSearch(floorId, 1, true);
const mapdir = bfs.mapdir[floorId];
core.setTextAlign("ui", "center");
let size = (core.__PIXELS__ * 3) / 4; //312
const areas = core.getFlag("areas");
2024-12-27 11:55:28 +08:00
2024-12-30 20:44:53 +08:00
let i = areas.findIndex((v) => v.maps.includes(floorId));
core.fillRoundRect("ui", 15 - 2, 15 - 2, 35 + 4, 35 + 4, 4, "#444444");
core.strokeRoundRect(
"ui",
15 - 4,
15 - 4,
35 + 8,
35 + 8,
4,
"#444444",
1
);
core.fillBoldText1(
"ui",
"当前",
13 + 20,
17 + 20,
"#FFFFFF",
"#000000",
2,
this._buildFont(18, true)
);
2024-12-27 11:55:28 +08:00
2024-12-30 20:44:53 +08:00
core.fillRoundRect(
"ui",
15 - 2,
15 - 2 + 35 + 8 + size + 8 - 54,
35 + 4,
35 + 4,
4,
"#444444"
);
core.strokeRoundRect(
"ui",
15 - 4,
15 - 4 + 35 + 8 + size + 8 - 54,
35 + 8,
35 + 8,
4,
"#444444",
1
);
if (
!core.status.maps[core.floorIds[page]].canFlyTo ||
!core.hasVisitedFloor(core.floorIds[page])
) {
core.fillBoldText1(
"ui",
"预览",
13 + 20,
17 + 20 + 35 + 8 + size + 8 - 54,
"#909090",
"#000000",
2,
this._buildFont(18, true)
);
} else {
core.fillBoldText1(
"ui",
"传送",
13 + 20,
17 + 20 + 35 + 8 + size + 8 - 54,
"#909090",
"#000000",
2,
this._buildFont(18, true)
);
}
core.fillRoundRect(
"ui",
15 - 4 + size - 2 + 45,
15 - 2 + size - 4 + 45,
35 + 4,
35 + 4,
4,
"#444444"
);
core.strokeRoundRect(
"ui",
15 - 4 + size - 4 + 45,
15 - 4 + size - 4 + 45,
35 + 8,
35 + 8,
4,
"#444444",
1
);
core.fillBoldText1(
"ui",
"离开",
15 - 4 + size - 4 + 45 + 22,
15 - 4 + size - 4 + 45 + 26,
"#FFFFFF",
"#000000",
2,
this._buildFont(18, true)
);
2024-12-27 11:55:28 +08:00
2024-12-30 20:44:53 +08:00
core.fillRoundRect(
"ui",
15 + 44 - 2,
15 - 2,
size + 4 - 58,
35 + 4,
4,
"#444444"
);
core.strokeRoundRect(
"ui",
15 + 44 - 4,
15 - 4,
size + 8 - 58,
35 + 8,
4,
"#444444",
1
);
if (mapdir.includes("up")) {
core.fillBoldText1(
"ui",
"北▲",
30 + 145 + 10,
17 + 20,
"#FFFFFF",
"#000000",
2,
this._buildFont(18, true)
);
} else {
core.fillBoldText1(
"ui",
"北▲",
30 + 145 + 10,
17 + 20,
"#909090",
"#000000",
2,
this._buildFont(18, true)
);
}
2024-12-27 11:55:28 +08:00
2024-12-30 20:44:53 +08:00
core.fillRoundRect(
"ui",
15 - 2,
59 - 2,
35 + 4,
size + 4 - 58,
4,
"#444444"
);
core.strokeRoundRect(
"ui",
15 - 4,
59 - 4,
35 + 8,
size + 8 - 58,
4,
"#444444",
1
);
if (mapdir.includes("left")) {
core.fillBoldText1(
"ui",
"西",
15 + 17,
25 + 150,
"#FFFFFF",
"#000000",
2,
this._buildFont(18, true)
);
core.fillBoldText1(
"ui",
"◀",
15 + 17,
45 + 150,
"#FFFFFF",
"#000000",
2,
this._buildFont(18, true)
);
} else {
core.fillBoldText1(
"ui",
"西",
15 + 17,
25 + 150,
"#909090",
"#000000",
2,
this._buildFont(18, true)
);
core.fillBoldText1(
"ui",
"◀",
15 + 17,
45 + 150,
"#909090",
"#000000",
2,
this._buildFont(18, true)
);
}
core.fillRoundRect(
"ui",
15 + 44 - 2,
15 - 2 + size - 4,
size + 4 - 58,
35 + 4,
4,
"#444444"
);
core.strokeRoundRect(
"ui",
15 + 44 - 4,
15 - 4 + size - 4,
size + 8 - 58,
35 + 8,
4,
"#444444",
1
);
if (mapdir.includes("down")) {
core.fillBoldText1(
"ui",
"南▼",
30 + 145 + 10,
17 + 20 + size - 4,
"#FFFFFF",
"#000000",
2,
this._buildFont(18, true)
);
} else {
core.fillBoldText1(
"ui",
"南▼",
30 + 145 + 10,
17 + 20 + size - 4,
"#909090",
"#000000",
2,
this._buildFont(18, true)
);
}
2024-12-27 11:55:28 +08:00
2024-12-30 20:44:53 +08:00
core.fillRoundRect(
"ui",
15 - 2 + size - 4,
59 - 2,
35 + 4,
size + 4 - 58,
4,
"#444444"
);
core.strokeRoundRect(
"ui",
15 - 4 + size - 4,
59 - 4,
35 + 8,
size + 8 - 58,
4,
"#444444",
1
);
if (mapdir.includes("right")) {
core.fillBoldText1(
"ui",
"东",
15 + 17 + size - 4,
25 + 150,
"#FFFFFF",
"#000000",
2,
this._buildFont(18, true)
);
core.fillBoldText1(
"ui",
"▶",
15 + 17 + size - 4,
45 + 150,
"#FFFFFF",
"#000000",
2,
this._buildFont(18, true)
);
} else {
core.fillBoldText1(
"ui",
"东",
15 + 17 + size - 4,
25 + 150,
"#909090",
"#000000",
2,
this._buildFont(18, true)
);
core.fillBoldText1(
"ui",
"▶",
15 + 17 + size - 4,
45 + 150,
"#909090",
"#000000",
2,
this._buildFont(18, true)
2024-12-27 11:55:28 +08:00
);
2024-12-30 20:44:53 +08:00
}
core.fillRoundRect(
"ui",
60 - 2,
60 - 2,
size - 58 + 4,
size - 58 + 4,
4,
"#444444"
);
core.strokeRoundRect(
"ui",
60 - 4,
60 - 4,
size - 58 + 8,
size - 58 + 8,
4,
"#444444",
1
);
core.drawThumbnail(floorId, null, {
damage: data.damage,
ctx: "ui",
x: 58,
y: 58,
size: 0.62,
all: data.all,
});
if (
!core.status.maps[core.floorIds[page]].canFlyTo ||
!core.hasVisitedFloor(core.floorIds[page])
)
2024-12-27 11:55:28 +08:00
core.drawImage(
2024-12-30 20:44:53 +08:00
"ui",
2025-01-05 00:03:41 +08:00
"lock.webp",
2024-12-27 11:55:28 +08:00
0,
2024-12-30 20:44:53 +08:00
0,
size,
size,
58,
58,
size - 8,
size - 8
2024-12-27 11:55:28 +08:00
);
2024-12-30 20:44:53 +08:00
core.fillRoundRect(
"ui",
15 + 44 - 2,
60 - 2 + size - 4,
size + 4 - 58,
35 + 4,
4,
"#444444"
);
core.strokeRoundRect(
"ui",
15 + 44 - 4,
60 - 4 + size - 4,
size + 8 - 58,
35 + 8,
4,
"#444444",
1
);
core.fillBoldText1(
"ui",
core.status.maps[floorId].areas,
30 + 145 + 10,
17 + 65 + size - 4,
"#FFFFFF",
"#000000",
2,
this._buildFont(18, true)
);
core.fillRoundRect(
"ui",
15 - 2,
60 - 2 + size - 4,
35 + 4,
35 + 4,
4,
"#444444"
);
core.strokeRoundRect(
"ui",
15 - 4,
60 - 4 + size - 4,
35 + 8,
35 + 8,
4,
"#444444",
1
);
if (i === 0) {
core.fillBoldText1(
"ui",
"◀",
30,
17 + 65 + size - 4,
"#909090",
"#000000",
2,
this._buildFont(18, true)
);
} else {
core.fillBoldText1(
"ui",
"◀",
30,
17 + 65 + size - 4,
"#FFFFFF",
"#000000",
2,
this._buildFont(18, true)
);
}
core.fillRoundRect(
"ui",
15 - 2 + size - 4,
60 - 2 + size - 4,
35 + 4,
35 + 4,
4,
"#444444"
);
core.strokeRoundRect(
"ui",
15 - 4 + size - 4,
60 - 4 + size - 4,
35 + 8,
35 + 8,
4,
"#444444",
1
);
if (i === areas.length - 1) {
core.fillBoldText1(
"ui",
"▶",
30 + 300 + 10,
17 + 65 + size - 4,
"#909090",
"#000000",
2,
this._buildFont(18, true)
);
} else {
core.fillBoldText1(
"ui",
"▶",
30 + 300 + 10,
17 + 65 + size - 4,
"#FFFFFF",
"#000000",
2,
this._buildFont(18, true)
);
}
2024-12-27 11:55:28 +08:00
2024-12-30 20:44:53 +08:00
core.fillRoundRect(
"ui",
15 - 2 + size - 4,
15 - 2,
80 + 4,
35 + 4,
4,
"#444444"
);
core.strokeRoundRect(
"ui",
15 - 4 + size - 4,
15 - 4,
80 + 8,
35 + 8,
4,
"#444444",
1
);
if (mapdir.includes("upFloor")) {
core.fillBoldText1(
"ui",
"上楼",
30 + 320 + 10,
17 + 20,
"#FFFFFF",
"#000000",
2,
this._buildFont(18, true)
);
} else {
core.fillBoldText1(
"ui",
"上楼",
30 + 320 + 10,
17 + 20,
"#909090",
"#000000",
2,
this._buildFont(18, true)
);
}
core.fillRoundRect(
"ui",
15 - 2 + size - 4,
15 - 2 + size - 4,
80 + 4,
35 + 4,
4,
"#444444"
);
core.strokeRoundRect(
"ui",
15 - 4 + size - 4,
15 - 4 + size - 4,
80 + 8,
35 + 8,
4,
"#444444",
1
);
if (mapdir.includes("downFloor")) {
core.fillBoldText1(
"ui",
"下楼",
30 + 320 + 10,
17 + 20 + size - 4,
"#FFFFFF",
"#000000",
2,
this._buildFont(18, true)
);
} else {
core.fillBoldText1(
"ui",
"下楼",
30 + 320 + 10,
17 + 20 + size - 4,
"#909090",
"#000000",
2,
this._buildFont(18, true)
2024-12-27 11:55:28 +08:00
);
}
2024-12-30 20:44:53 +08:00
core.fillRoundRect(
"ui",
15 - 2 + size - 4 + 35 + 8,
59 - 2,
37 + 4,
(size - 58) / 2 + 4,
4,
"#444444"
2024-12-27 11:55:28 +08:00
);
2024-12-30 20:44:53 +08:00
core.strokeRoundRect(
"ui",
15 - 4 + size - 4 + 35 + 8,
59 - 4,
37 + 8,
(size - 58) / 2 + 8,
4,
"#444444",
1
);
const title = core.status.maps[floorId].title;
//const length = title.length
fillTextVertical(
"ui",
title,
15 - 4 + size - 4 + 45,
85,
"#FFFFFF",
"#000000",
18
);
//const uictx = main.dom.gameCanvas.ui.getContext('2d')
core.fillRoundRect(
"ui",
15 - 2 + size - 4 + 35 + 8,
59 - 2 + (size - 58) / 2 + 8,
37 + 4,
119 + 4,
4,
"#444444"
);
core.strokeRoundRect(
"ui",
15 - 4 + size - 4 + 35 + 8,
59 - 4 + (size - 58) / 2 + 8,
37 + 8,
119 + 8,
4,
"#444444",
1
);
if (core.getFlag("showEnemy")) {
fillTextVertical(
"ui",
"关闭漏怪检测",
15 - 4 + size - 4 + 45,
220,
"#FFFFFF",
"#000000",
18
);
} else {
fillTextVertical(
"ui",
"开启漏怪检测",
15 - 4 + size - 4 + 45,
220,
"#FFFFFF",
"#000000",
18
);
2024-12-27 11:55:28 +08:00
}
2024-12-30 20:44:53 +08:00
//uictx.fillTextVertical(title, 15 - 4 + size - 4 + 35 + 29, 25 + 150)
//fillTextVertical('ui', title, 15 - 4 + size - 4 + 35 + 29, 25 + 150, '#FFFFFF', this._buildFont(18, true))
};
2024-12-27 11:55:28 +08:00
2024-12-30 20:44:53 +08:00
function fillTextVertical(name, text, x, y, style, boldstyle, fontsize) {
//竖向文字绘制
2024-12-27 11:55:28 +08:00
2024-12-30 20:44:53 +08:00
const ctx = core.ui.getContextByName(name);
if (!ctx) return;
const canvas = document.createElement("canvas");
const context = canvas.getContext("2d");
fontsize *= 3;
const length = text.length;
canvas.width = fontsize * 2;
canvas.height = fontsize * length * 2;
if (style) context.fillStyle = core.arrayToRGBA(style);
if (boldstyle) context.strokeStyle = core.arrayToRGBA(boldstyle);
context.lineWidth = 2;
if (fontsize) context.font = core.ui._buildFont(fontsize, true);
let arrText = text.split("");
let arrWidth = arrText.map(function (letter) {
return context.measureText(letter).width;
});
2024-12-27 11:55:28 +08:00
2024-12-30 20:44:53 +08:00
let align = context.textAlign;
2024-12-27 11:55:28 +08:00
2024-12-30 20:44:53 +08:00
let baseline = context.textBaseline;
let sx = fontsize,
sy = fontsize * length;
if (align == "left") {
sx = sx + Math.max.apply(null, arrWidth) / 2;
} else if (align == "right") {
sx = sx - Math.max.apply(null, arrWidth) / 2;
2024-12-27 11:55:28 +08:00
}
2024-12-30 20:44:53 +08:00
if (
baseline == "bottom" ||
baseline == "alphabetic" ||
baseline == "ideographic"
) {
sy = sy - arrWidth[0] / 2;
} else if (baseline == "top" || baseline == "hanging") {
sy = sy + arrWidth[0] / 2;
2024-12-27 11:55:28 +08:00
}
2024-12-30 20:44:53 +08:00
context.textAlign = "center";
2024-12-27 11:55:28 +08:00
2024-12-30 20:44:53 +08:00
context.textBaseline = "middle";
context.lineWidth = 6;
2024-12-27 11:55:28 +08:00
2024-12-30 20:44:53 +08:00
// 开始逐字绘制
2024-12-27 11:55:28 +08:00
2024-12-30 20:44:53 +08:00
arrText.forEach(function (letter, index) {
// 确定下一个字符的纵坐标位置
2024-12-27 11:55:28 +08:00
2024-12-30 20:44:53 +08:00
context.strokeText(letter, sx, sy);
context.fillText(letter, sx, sy);
// 旋转坐标系还原成初始态
2024-12-27 11:55:28 +08:00
2024-12-30 20:44:53 +08:00
context.setTransform(1, 0, 0, 1, 0, 0);
2024-12-27 11:55:28 +08:00
2024-12-30 20:44:53 +08:00
// 确定下一个字符的纵坐标位置
2024-12-27 11:55:28 +08:00
2024-12-30 20:44:53 +08:00
var letterWidth = 54;
2024-12-27 11:55:28 +08:00
2024-12-30 20:44:53 +08:00
sy = sy + letterWidth;
});
// 水平垂直对齐方式还原
context.textAlign = align;
context.textBaseline = baseline;
//绘制到目标位置
ctx.drawImage(
canvas,
x,
y - (fontsize / 3) * length,
canvas.width / 3,
canvas.height / 3
);
}
////// 点击楼层传送器时的打开操作 //////
events.prototype.useFly = function (fromUserAction) {
if (core.isReplaying()) return;
if (!core.status.maps[core.status.floorId].canFlyFrom) {
core.drawTip(core.material.items["fly"].name + "好像失效了", "fly");
2024-12-27 11:55:28 +08:00
return;
}
2024-12-30 20:44:53 +08:00
// 从“浏览地图”页面:尝试直接传送到该层
if (core.status.event.id == "viewMaps") {
if (!core.hasItem("fly")) {
core.playSound("操作失败");
core.drawTip("你没有" + core.material.items["fly"].name, "fly");
} else if (
core.flags.flyNearStair &&
!core.nearStair() &&
!core.canMoveFloor()
) {
core.playSound("操作失败");
core.drawTip(
"无法到达楼梯边使用" + core.material.items["fly"].name,
"fly"
);
} else {
core.flyTo(core.status.event.data.floorId);
}
return;
2024-12-27 11:55:28 +08:00
}
2024-12-30 20:44:53 +08:00
if (!this._checkStatus("fly", fromUserAction, true)) return;
//if (core.flags.flyNearStair && !core.nearStair())
2024-12-27 11:55:28 +08:00
2024-12-30 20:44:53 +08:00
if (
(core.flags.flyNearStair && !core.nearStair()) ||
!core.canMoveFloor()
) {
core.playSound("操作失败");
core.drawTip(
"无法到达楼梯边使用" + core.material.items["fly"].name,
"fly"
);
core.unlockControl();
core.status.event.data = null;
core.status.event.id = null;
return;
}
if (!core.canUseItem("fly")) {
core.playSound("操作失败");
core.drawTip(core.material.items["fly"].name + "好像失效了", "fly");
core.unlockControl();
core.status.event.data = null;
core.status.event.id = null;
return;
}
core.playSound("打开界面");
core.useItem("fly", true);
return;
};
////// 系统菜单栏界面时的点击操作 //////
actions.prototype._clickSettings = function (x, y) {
if (this._out(x)) return;
var choices = core.status.event.ui.choices;
var topIndex = this._getChoicesTopIndex(choices.length);
if (y >= topIndex && y < topIndex + choices.length) {
var selection = y - topIndex;
core.status.event.selection = selection;
switch (selection) {
case 0:
core.status.event.selection = 0;
core.playSound("确定");
core.ui._drawSwitchs();
break;
case 1:
// core.playSound('确定');
core.ui._drawKeyBoard();
break;
case 2:
// core.playSound('确定');
core.clearUI();
core.ui._drawViewMaps(core.floorIds.indexOf(core.status.floorId));
break;
case 3:
core.status.event.selection = 0;
core.playSound("确定");
core.ui._drawNotes();
break;
case 4:
core.status.event.selection = 0;
core.playSound("确定");
core.ui._drawSyncSave();
break;
case 5:
core.status.event.selection = 0;
core.playSound("确定");
core.ui._drawGameInfo();
break;
case 6:
return core.confirmRestart();
case 7:
core.playSound("取消");
core.ui.closePanel();
break;
}
}
return;
};
////// 查看地图界面时的点击操作 //////
actions.prototype._clickViewMaps = function (x, y, px, py) {
if (core.status.event.data == null) {
core.ui._drawViewMaps(core.floorIds.indexOf(core.status.floorId));
return;
}
let now = core.floorIds.indexOf(core.status.floorId);
let index = core.status.event.data.index;
let cx = core.status.event.data.x,
cy = core.status.event.data.y;
let floorId = core.floorIds[index],
mw = core.floors[floorId].width,
mh = core.floors[floorId].height;
let perpx = core.__PIXELS__ / 5,
cornerpx = (perpx * 3) / 4;
const bfs = core.plugin.bfsSearch(floorId, 1, true);
const mapdir = bfs.mapdir[floorId];
const res = bfs.res;
const formto = {};
for (let from in res) {
const to = res[from];
const [fromfloorId, fromsx, fromsy, dir] = from.split("_");
const [tofloorId, tosx, tosy] = to.split("_");
if (!formto[fromfloorId]) formto[fromfloorId] = {};
if (!formto[fromfloorId][dir]) formto[fromfloorId][dir] = tofloorId;
}
const areas = core.getFlag("areas");
let i = areas.findIndex((v) => v.maps.includes(floorId));
2024-12-27 11:55:28 +08:00
2024-12-30 20:44:53 +08:00
if (px >= 11 && px <= 54 && py >= 11 && py <= 54) {
core.ui._drawViewMaps(core.floorIds.indexOf(core.status.floorId));
} else if (px >= 362 && px <= 407 && py >= 191 && py <= 318) {
flags.showEnemy = !flags.showEnemy;
core.ui._drawViewMaps(index);
} else if (px >= 364 && px <= 407 && py >= 364 && py <= 407) {
core.clearMap("data");
core.playSound("取消");
core.ui.closePanel();
core.getItemDetail();
core.redrawMap();
core.ui.statusBar._update_map();
return;
} else if (px >= 55 && px <= 317 && py >= 11 && py <= 54) {
if (mapdir.includes("up"))
core.ui._drawViewMaps(core.floorIds.indexOf(formto[floorId].up));
} else if (px >= 55 && px <= 317 && py >= 319 && py <= 362) {
if (mapdir.includes("down"))
core.ui._drawViewMaps(core.floorIds.indexOf(formto[floorId].down));
} else if (px >= 11 && px <= 54 && py >= 55 && py <= 317) {
if (mapdir.includes("left"))
core.ui._drawViewMaps(core.floorIds.indexOf(formto[floorId].left));
} else if (px >= 319 && px <= 362 && py >= 55 && py <= 317) {
if (mapdir.includes("right"))
core.ui._drawViewMaps(core.floorIds.indexOf(formto[floorId].right));
} else if (px >= 319 && px <= 407 && py >= 11 && py <= 54) {
if (mapdir.includes("upFloor"))
core.ui._drawViewMaps(core.floorIds.indexOf(formto[floorId].upFloor));
} else if (px >= 319 && px <= 407 && py >= 319 && py <= 362) {
if (mapdir.includes("downFloor"))
core.ui._drawViewMaps(
core.floorIds.indexOf(formto[floorId].downFloor)
);
} else if (
px >= 55 &&
px <= 317 &&
py >= 55 &&
py <= 317 &&
!core.isReplaying()
) {
core.useFly(false);
return;
} else if (px >= 11 && px <= 54 && py >= 364 && py <= 407) {
if (i > 0) {
i -= 1;
core.ui._drawViewMaps(core.floorIds.indexOf(areas[i].maps[0]));
}
} else if (px >= 319 && px <= 362 && py >= 364 && py <= 407) {
if (i < areas.length - 1) {
i += 1;
core.ui._drawViewMaps(core.floorIds.indexOf(areas[i].maps[0]));
}
}
};
const replayAction_fly = function (action) {
//楼层传送的录像操作
if (action.indexOf("fly:") != 0) return false;
var floorId = action.substring(4);
var toIndex = core.floorIds.indexOf(floorId);
if (
!core.canUseItem("fly") ||
(core.flags.flyNearStair && !core.nearStair())
)
return false;
core.ui._drawViewMaps(toIndex);
if (core.status.replay.speed == 24) {
if (!core.flyTo(floorId, core.replay))
core.control._replay_error(action);
return true;
}
setTimeout(function () {
if (!core.flyTo(floorId, core.replay))
core.control._replay_error(action);
}, core.control.__replay_getTimeout());
return true;
};
core.registerReplayAction("fly", replayAction_fly);
////// 查看地图界面时,放开某个键的操作 //////
actions.prototype._keyUpViewMaps = function (keycode) {
if (core.status.event.data == null) {
core.ui._drawViewMaps(core.floorIds.indexOf(core.status.floorId));
return;
}
var floorId = core.floorIds[core.status.event.data.index];
2024-12-27 11:55:28 +08:00
2024-12-30 20:44:53 +08:00
if (keycode == 27 || keycode == 71) {
core.clearMap("data");
core.playSound("取消");
core.ui.closePanel();
core.getItemDetail();
core.redrawMap();
core.ui.statusBar._update_map();
return;
}
if (keycode == 88) {
core.openBook(true);
return;
}
if (keycode == 86) {
core.status.event.data.damage = !core.status.event.data.damage;
core.playSound("光标移动");
core.ui._drawViewMaps(core.status.event.data);
return;
}
if (keycode == 66) {
core.openBook(false);
return;
}
if (
(keycode == 13 || keycode == 32 || keycode == 67) &&
!core.isReplaying()
) {
core.useFly(false);
return;
}
return;
};
actions.prototype._keyDownViewMaps = function (keycode) {
if (core.status.event.data == null) return;
2024-12-27 11:55:28 +08:00
2024-12-30 20:44:53 +08:00
var floorId = core.floorIds[core.status.event.data.index],
mh = core.floors[floorId].height;
2024-12-29 13:11:43 +08:00
2024-12-30 20:44:53 +08:00
if (keycode == 39) this._clickViewMaps(9, 1, 330, 250);
if (keycode == 37) this._clickViewMaps(9, 8, 25, 200);
if (keycode == 40) this._clickViewMaps(9, 6, 250, 330);
if (keycode == 38) this._clickViewMaps(9, 3, 200, 25);
if (keycode == 34) this._clickViewMaps(9, 3, 350, 330);
if (keycode == 33) this._clickViewMaps(9, 3, 350, 25);
return;
};
2024-12-27 11:55:28 +08:00
2024-12-30 20:44:53 +08:00
actions.prototype._sys_onmousewheel = function (direct) {
// 向下滚动是 -1 ,向上是 1
2024-12-27 11:55:28 +08:00
2024-12-30 20:44:53 +08:00
if (this._checkReplaying()) {
// 滚轮控制速度
if (direct == 1) core.speedUpReplay();
if (direct == -1) core.speedDownReplay();
return;
}
2024-12-27 11:55:28 +08:00
2024-12-30 20:44:53 +08:00
// 楼层飞行器
if (core.status.lockControl && core.status.event.id == "fly") {
if (direct == 1) core.ui.drawFly(this._getNextFlyFloor(1));
if (direct == -1) core.ui.drawFly(this._getNextFlyFloor(-1));
return;
}
2024-12-27 11:55:28 +08:00
2024-12-30 20:44:53 +08:00
// 怪物手册
if (core.status.lockControl && core.status.event.id == "book") {
var pageinfo = core.ui._drawBook_pageinfo();
if (direct == 1)
core.ui.drawBook(core.status.event.data - pageinfo.per_page);
if (direct == -1)
core.ui.drawBook(core.status.event.data + pageinfo.per_page);
return;
}
2024-12-27 11:55:28 +08:00
2024-12-30 20:44:53 +08:00
// 存读档
if (
core.status.lockControl &&
(core.status.event.id == "save" || core.status.event.id == "load")
) {
var index =
core.status.event.data.page * 10 + core.status.event.data.offset;
if (direct == 1) core.ui._drawSLPanel(index - 10);
if (direct == -1) core.ui._drawSLPanel(index + 10);
return;
}
2024-12-27 11:55:28 +08:00
2024-12-30 20:44:53 +08:00
// 浏览地图
if (core.status.lockControl && core.status.event.id == "viewMaps") {
let floorId = core.floorIds[core.status.event.data.index];
if (!flags.__visited__[floorId]) floorId = core.status.floorId;
const visit = Object.keys(flags.__visited__);
let index = visit.indexOf(floorId);
if (direct == 1) {
if (index > 0)
core.ui._drawViewMaps(core.floorIds.indexOf(visit[index - 1]));
}
if (direct == -1) {
if (index < visit.length - 1)
core.ui._drawViewMaps(core.floorIds.indexOf(visit[index + 1]));
}
return;
}
2024-12-27 11:55:28 +08:00
2024-12-30 20:44:53 +08:00
// wait事件
if (
core.status.lockControl &&
core.status.event.id == "action" &&
core.status.event.data.type == "wait"
) {
var timeout =
Math.max(0, core.status.event.timeout - new Date().getTime()) || 0;
core.setFlag("type", 0);
var keycode = direct == 1 ? 33 : 34;
core.setFlag("keycode", keycode);
core.setFlag("timeout", timeout);
var executed = core.events.__action_wait_afterGet(
core.status.event.data.current
);
if (executed || !core.status.event.data.current.forceChild) {
core.status.route.push("input:" + (1e8 * timeout + keycode));
clearTimeout(core.status.event.interval);
delete core.status.event.timeout;
core.doAction();
}
return;
}
};
core.registerAction(
"onmousewheel",
"_sys_onmousewheel",
actions.prototype._sys_onmousewheel,
0
);
},
2025-01-18 17:49:44 +08:00
"CG回廊": function () {
2025-01-02 17:04:30 +08:00
// 在此增加新插件
const CGUI = document.createElement("canvas"); //CGui画布设置
CGUI.style.position = "absolute";
CGUI.style.zIndex = 300;
CGUI.style.display = "none";
CGUI.id = "CGUI";
main.dom.gameGroup.insertAdjacentElement("afterend", CGUI);
CGUI.style.top = "50%";
CGUI.style.left = "50%";
CGUI.style.transform = "translate(-50%,-50%)";
const ctx = CGUI.getContext("2d");
main.dom.CGUI = CGUI;
let page = 0; //初始页面
let show = false; //展示状态
CGUI.onclick = function (e) {
try {
e.preventDefault();
if (core.isPlaying()) return false;
const left = core.dom.gameGroup.offsetLeft;
const top = core.dom.gameGroup.offsetTop;
const px = Math.floor((e.clientX - left) / core.domStyle.scale),
py = Math.floor((e.clientY - top) / core.domStyle.scale);
core.ui.CG.onclick(px * 3, py * 3);
} catch (ee) {
main.log(ee);
}
};
2024-12-27 11:55:28 +08:00
2025-01-02 17:04:30 +08:00
class CG {
constructor() {
2025-01-16 13:28:25 +08:00
this.cgs;
2025-01-02 17:04:30 +08:00
//cg列表
this.UIMx = [
//空位用none填充当前ui至多4列6行
[
2025-01-05 00:03:41 +08:00
["eve_010102.webp", "eve_010203.webp", "eve_010304.webp"],
["eve_010501.webp", "eve_010601.webp", "eve_010701.webp"],
2025-01-02 17:04:30 +08:00
],
[
2025-01-05 00:03:41 +08:00
["eve_010801.webp", "eve_010902.webp", "eve_011001.webp"],
["eve_011101.webp", "eve_011202.webp", "eve_011302.webp"],
2025-01-02 17:04:30 +08:00
],
[
2025-01-05 00:03:41 +08:00
["eve_011402.webp", "eve_020102.webp", "eve_020201.webp"],
["eve_020301.webp", "eve_020401.webp", "eve_020501.webp"],
2025-01-02 17:04:30 +08:00
],
[
2025-01-05 00:03:41 +08:00
["eve_020605.webp", "eve_020701.webp", "eve_020801.webp"],
["eve_030101.webp", "eve_030206.webp", "eve_030302.webp"],
2025-01-02 17:04:30 +08:00
],
[
2025-01-05 00:03:41 +08:00
["eve_030508.webp", "eve_030601.webp", "eve_030801.webp"],
["eve_030901.webp", "eve_031002.webp", "eve_031101.webp"],
2025-01-02 17:04:30 +08:00
],
[
2025-01-05 00:03:41 +08:00
["eve_040201.webp", "eve_040401.webp", "eve_040501.webp"],
["eve_040601.webp", "eve_040702.webp", "eve_040801.webp"],
2025-01-02 17:04:30 +08:00
],
[
2025-01-05 00:03:41 +08:00
["eve_050101.webp", "eve_050201.webp", "eve_050401.webp"],
["eve_050501.webp", "eve_050601.webp", "eve_050704.webp"],
2025-01-02 17:04:30 +08:00
],
[
2025-01-05 00:03:41 +08:00
["eve_050801.webp", "eve_070101.webp", "bg_1511.webp"],
["bg_1521.webp", "bg_2011.webp", "bg_2521.webp"],
2025-01-02 17:04:30 +08:00
],
[
2025-01-05 00:03:41 +08:00
["bg_3042.webp", "bg_3551.webp", "bg_3571.webp"],
["bg_3721.webp", "bg_5033.webp", "bg_5044.webp"],
2025-01-02 17:04:30 +08:00
],
];
2024-12-30 20:44:53 +08:00
}
2025-01-02 17:04:30 +08:00
//更新
update() {
this.background();
this.drawUI();
}
background() {
//画布大小设置
if (core.domStyle.isVertical) {
CGUI.width = 1248;
CGUI.height = 2028;
} else {
CGUI.width = 2028;
CGUI.height = 1248;
}
core.setTextAlign(ctx, "center");
}
onclick(px, py) {
//点击
2025-01-16 13:28:25 +08:00
2025-01-02 17:04:30 +08:00
if (show) {
show = !show;
core.clearMap(ctx);
this.update();
return;
}
const makeBox = ([x, y], [w, h]) => {
return [
[x, y],
[x + w, y + h],
];
};
const inRect = ([x, y], [[sx, sy], [dx, dy]]) => {
return sx <= x && x <= dx && sy <= y && y <= dy;
};
const pos = [px, py];
2025-01-16 13:28:25 +08:00
const backbox = makeBox([15, 35], [210, 90]);
2025-01-02 17:04:30 +08:00
if (inRect(pos, backbox)) {
//离开按钮是一致的,其余的记区分横竖屏
CGUI.style.display = "none";
core.clearMap(ctx);
core.restart();
return;
}
if (core.domStyle.isVertical) {
//竖屏
2025-01-16 13:28:25 +08:00
const pageupbox = makeBox([200, 1830], [200, 100]);
const pagedownbox = makeBox([900, 1830], [200, 100]);
2025-01-02 17:04:30 +08:00
2025-01-16 13:28:25 +08:00
const imagebox0 = makeBox([50, 200], [560, 420]);
const imagebox1 = makeBox([50, 750], [560, 420]);
const imagebox2 = makeBox([50, 1300], [560, 420]);
2025-01-02 17:04:30 +08:00
2025-01-16 13:28:25 +08:00
const imagebox3 = makeBox([650, 200], [560, 420]);
const imagebox4 = makeBox([650, 750], [560, 420]);
const imagebox5 = makeBox([650, 1300], [560, 420]);
2025-01-02 17:04:30 +08:00
if (inRect(pos, pagedownbox)) {
//2代表当前最大页数-1
if (page < this.UIMx.length - 1) {
page++;
core.clearMap(ctx);
this.update();
}
} else if (inRect(pos, pageupbox)) {
if (page > 0) {
page--;
core.clearMap(ctx);
this.update();
}
} else if (inRect(pos, imagebox0)) {
2025-01-16 13:28:25 +08:00
if (this.cgs.includes(this.UIMx[page][0][0])) {
const img = core.material.images.images[this.UIMx[page][0][0]];
if (img) {
ctx.save(); //保存设置
ctx.translate(1248, 0); //重新定位右上角为基准
ctx.rotate(Math.PI / 2); //旋转90度
ctx.drawImage(img, 0, 0, 2028, 1248);
ctx.restore(); //重置画布设置
show = !show;
}
2025-01-02 17:04:30 +08:00
}
} else if (inRect(pos, imagebox1)) {
2025-01-16 13:28:25 +08:00
if (this.cgs.includes(this.UIMx[page][0][1])) {
const img = core.material.images.images[this.UIMx[page][0][1]];
if (img) {
ctx.save(); //保存设置
ctx.translate(1248, 0); //重新定位右上角为基准
ctx.rotate(Math.PI / 2); //旋转90度
ctx.drawImage(img, 0, 0, 2028, 1248);
ctx.restore(); //重置画布设置
show = !show;
}
2025-01-02 17:04:30 +08:00
}
} else if (inRect(pos, imagebox2)) {
2025-01-16 13:28:25 +08:00
if (this.cgs.includes(this.UIMx[page][0][2])) {
const img = core.material.images.images[this.UIMx[page][0][2]];
if (img) {
ctx.save(); //保存设置
ctx.translate(1248, 0); //重新定位右上角为基准
ctx.rotate(Math.PI / 2); //旋转90度
ctx.drawImage(img, 0, 0, 2028, 1248);
ctx.restore(); //重置画布设置
show = !show;
}
2025-01-02 17:04:30 +08:00
}
} else if (inRect(pos, imagebox3)) {
2025-01-16 13:28:25 +08:00
if (this.cgs.includes(this.UIMx[page][1][0])) {
const img = core.material.images.images[this.UIMx[page][1][0]];
if (img) {
ctx.save(); //保存设置
ctx.translate(1248, 0); //重新定位右上角为基准
ctx.rotate(Math.PI / 2); //旋转90度
ctx.drawImage(img, 0, 0, 2028, 1248);
ctx.restore(); //重置画布设置
show = !show;
}
2025-01-02 17:04:30 +08:00
}
} else if (inRect(pos, imagebox4)) {
2025-01-16 13:28:25 +08:00
if (this.cgs.includes(this.UIMx[page][1][1])) {
const img = core.material.images.images[this.UIMx[page][1][1]];
if (img) {
ctx.save(); //保存设置
ctx.translate(1248, 0); //重新定位右上角为基准
ctx.rotate(Math.PI / 2); //旋转90度
ctx.drawImage(img, 0, 0, 2028, 1248);
ctx.restore(); //重置画布设置
show = !show;
}
2025-01-02 17:04:30 +08:00
}
} else if (inRect(pos, imagebox5)) {
2025-01-16 13:28:25 +08:00
if (this.cgs.includes(this.UIMx[page][1][2])) {
const img = core.material.images.images[this.UIMx[page][1][2]];
if (img) {
ctx.save(); //保存设置
ctx.translate(1248, 0); //重新定位右上角为基准
ctx.rotate(Math.PI / 2); //旋转90度
ctx.drawImage(img, 0, 0, 2028, 1248);
ctx.restore(); //重置画布设置
show = !show;
}
2025-01-02 17:04:30 +08:00
}
}
} else {
2025-01-16 13:28:25 +08:00
const pageupbox = makeBox([200, 1110], [200, 100]);
const pagedownbox = makeBox([1600, 1110], [200, 100]);
const imagebox0 = makeBox([75, 150], [600, 450]);
const imagebox1 = makeBox([725, 150], [600, 450]);
const imagebox2 = makeBox([1300, 150], [600, 450]);
const imagebox3 = makeBox([75, 650], [600, 450]);
const imagebox4 = makeBox([725, 650], [600, 450]);
const imagebox5 = makeBox([1375, 650], [600, 450]);
2025-01-02 17:04:30 +08:00
if (inRect(pos, pagedownbox)) {
if (page < this.UIMx.length - 1) {
page++;
core.clearMap(ctx);
this.update();
}
} else if (inRect(pos, pageupbox)) {
if (page > 0) {
page--;
core.clearMap(ctx);
this.update();
}
} else if (inRect(pos, imagebox0)) {
2025-01-16 13:28:25 +08:00
if (this.cgs.includes(this.UIMx[page][0][0])) {
const img = core.material.images.images[this.UIMx[page][0][0]];
if (img) {
ctx.drawImage(img, 0, 0, 2028, 1248);
show = !show;
}
2025-01-02 17:04:30 +08:00
}
} else if (inRect(pos, imagebox1)) {
2025-01-16 13:28:25 +08:00
if (this.cgs.includes(this.UIMx[page][0][1])) {
const img = core.material.images.images[this.UIMx[page][0][1]];
if (img) {
ctx.drawImage(img, 0, 0, 2028, 1248);
show = !show;
}
2025-01-02 17:04:30 +08:00
}
} else if (inRect(pos, imagebox2)) {
2025-01-16 13:28:25 +08:00
if (this.cgs.includes(this.UIMx[page][0][2])) {
const img = core.material.images.images[this.UIMx[page][0][2]];
if (img) {
ctx.drawImage(img, 0, 0, 2028, 1248);
show = !show;
}
2025-01-02 17:04:30 +08:00
}
} else if (inRect(pos, imagebox3)) {
2025-01-16 13:28:25 +08:00
if (this.cgs.includes(this.UIMx[page][1][0])) {
const img = core.material.images.images[this.UIMx[page][1][0]];
if (img) {
ctx.drawImage(img, 0, 0, 2028, 1248);
show = !show;
}
2025-01-02 17:04:30 +08:00
}
} else if (inRect(pos, imagebox4)) {
2025-01-16 13:28:25 +08:00
if (this.cgs.includes(this.UIMx[page][1][1])) {
const img = core.material.images.images[this.UIMx[page][1][1]];
if (img) {
ctx.drawImage(img, 0, 0, 2028, 1248);
show = !show;
}
2025-01-02 17:04:30 +08:00
}
} else if (inRect(pos, imagebox5)) {
2025-01-16 13:28:25 +08:00
if (this.cgs.includes(this.UIMx[page][1][2])) {
const img = core.material.images.images[this.UIMx[page][1][2]];
if (img) {
ctx.drawImage(img, 0, 0, 2028, 1248);
show = !show;
}
2025-01-02 17:04:30 +08:00
}
}
}
2024-12-30 20:44:53 +08:00
}
2025-01-02 17:04:30 +08:00
drawUI() {
//绘制页面
core.clearMap(CGUI);
2025-01-05 00:03:41 +08:00
const bgVertical = core.material.images.images["bg_2010.webp"]; //竖屏背景
const bg = core.material.images.images["bg_5043.webp"]; //横屏背景
2024-12-27 11:55:28 +08:00
2025-01-02 17:04:30 +08:00
if (core.domStyle.isVertical) {
//竖屏
2025-01-01 14:05:45 +08:00
2025-01-02 17:04:30 +08:00
core.fillRect(ctx, 0, 0, 1248, 2028, "#000000"); //黑色背景
ctx.globalAlpha = 0.5; //透明度
if (bgVertical)
ctx.drawImage(bgVertical, 0, 0, 1280, 1500, 0, 0, 1248, 2028); //绘制半透明背景图片
ctx.globalAlpha = 1; //恢复为不透明
2024-12-27 20:18:22 +08:00
2025-01-02 17:04:30 +08:00
core.setTextAlign(ctx, "center");
core.fillBoldText1(
ctx,
2025-01-16 13:28:25 +08:00
"◀离开",
2025-01-02 17:04:30 +08:00
100,
2025-01-16 13:28:25 +08:00
110,
2025-01-02 17:04:30 +08:00
"#FFFFFF",
"#000000",
6,
2025-01-16 13:28:25 +08:00
core.ui._buildFont(66, true)
2025-01-02 17:04:30 +08:00
);
2025-01-16 13:28:25 +08:00
core.fillBoldText1(
2025-01-02 17:04:30 +08:00
ctx,
2025-01-16 13:28:25 +08:00
"上一页",
300,
1900,
page === 0 ? "#444444" : "#FFFFFF",
"#000000",
2025-01-02 17:04:30 +08:00
6,
2025-01-16 13:28:25 +08:00
core.ui._buildFont(66, true)
2025-01-02 17:04:30 +08:00
);
2025-01-16 13:28:25 +08:00
2025-01-02 17:04:30 +08:00
core.fillBoldText1(
ctx,
2025-01-16 13:28:25 +08:00
page + 1 + "/" + this.UIMx.length,
650,
1900,
2025-01-02 17:04:30 +08:00
"#FFFFFF",
"#000000",
6,
2025-01-16 13:28:25 +08:00
core.ui._buildFont(66, true)
2025-01-02 17:04:30 +08:00
);
core.fillBoldText1(
ctx,
2025-01-16 13:28:25 +08:00
"下一页",
1000,
1900,
page === this.UIMx.length - 1 ? "#444444" : "#FFFFFF",
2025-01-02 17:04:30 +08:00
"#000000",
6,
2025-01-16 13:28:25 +08:00
core.ui._buildFont(66, true)
2025-01-02 17:04:30 +08:00
);
2025-01-16 13:28:25 +08:00
// 添加向上翻页和向下翻页的按钮
2024-12-27 11:55:28 +08:00
2025-01-02 17:04:30 +08:00
// 添加3*2个4:3的画框及图片
for (let i = 0; i < 3; i++) {
for (let j = 0; j < 2; j++) {
const img = core.material.images.images[this.UIMx[page][j][i]];
core.strokeRect(
ctx,
2025-01-16 13:28:25 +08:00
50 + j * 600,
200 + i * 550,
560,
420,
2025-01-02 17:04:30 +08:00
"#444444",
5
);
2025-01-16 13:28:25 +08:00
if (this.cgs.includes(this.UIMx[page][j][i])) {
if (img)
ctx.drawImage(
img,
50 + j * 600 + 15,
200 + i * 550 + 15,
560 - 30,
420 - 30
);
} else {
ctx.fillStyle = "#000000";
ctx.fillRect(
50 + j * 600 + 15,
200 + i * 550 + 15,
560 - 30,
420 - 30
2025-01-02 17:04:30 +08:00
);
2025-01-16 13:28:25 +08:00
const img = core.material.images.images["LOGO.webp"];
if (img)
ctx.drawImage(
img,
50 + j * 600 + 15,
200 + i * 550 + 15,
560 - 30,
420 - 30
);
}
2025-01-02 17:04:30 +08:00
}
}
} else {
//横屏
core.fillRect(ctx, 0, 0, 2028, 1248, "#000000"); //黑色背景
ctx.globalAlpha = 0.5; //透明度
if (bg) ctx.drawImage(bg, 0, 0, 1280, 720, 0, 0, 2028, 1248); //绘制半透明背景图片
ctx.globalAlpha = 1; //恢复为不透明
//core.drawWindowSkin('winskin1.png', ctx, 0, 0, 2028, 1248);
core.setTextAlign(ctx, "center");
core.fillBoldText1(
ctx,
2025-01-16 13:28:25 +08:00
"◀离开",
2025-01-02 17:04:30 +08:00
110,
100,
"#FFFFFF",
"#000000",
6,
2025-01-16 13:28:25 +08:00
core.ui._buildFont(66, true)
2025-01-02 17:04:30 +08:00
);
2024-12-27 11:55:28 +08:00
2025-01-02 17:04:30 +08:00
// 添加向上翻页和向下翻页的按钮
2025-01-16 13:28:25 +08:00
core.fillBoldText1(
2025-01-02 17:04:30 +08:00
ctx,
2025-01-16 13:28:25 +08:00
"上一页",
300,
1180,
page === 0 ? "#444444" : "#FFFFFF",
"#000000",
2025-01-02 17:04:30 +08:00
6,
2025-01-16 13:28:25 +08:00
core.ui._buildFont(66, true)
2025-01-02 17:04:30 +08:00
);
2025-01-16 13:28:25 +08:00
2025-01-02 17:04:30 +08:00
core.fillBoldText1(
ctx,
2025-01-16 13:28:25 +08:00
page + 1 + "/" + this.UIMx.length,
1000,
1180,
2025-01-02 17:04:30 +08:00
"#FFFFFF",
"#000000",
6,
2025-01-16 13:28:25 +08:00
core.ui._buildFont(66, true)
2025-01-02 17:04:30 +08:00
);
core.fillBoldText1(
ctx,
2025-01-16 13:28:25 +08:00
"下一页",
1700,
1180,
page === this.UIMx.length - 1 ? "#444444" : "#FFFFFF",
2025-01-02 17:04:30 +08:00
"#000000",
6,
2025-01-16 13:28:25 +08:00
core.ui._buildFont(66, true)
2025-01-02 17:04:30 +08:00
);
2025-01-01 14:05:45 +08:00
2025-01-02 17:04:30 +08:00
// 添加3*2个4:3的画框
for (let i = 0; i < 2; i++) {
for (let j = 0; j < 3; j++) {
core.strokeRect(
ctx,
2025-01-16 13:28:25 +08:00
75 + j * 650,
150 + i * 500,
600,
450,
2025-01-02 17:04:30 +08:00
"#444444",
2
);
2025-01-16 13:28:25 +08:00
if (this.cgs.includes(this.UIMx[page][i][j])) {
const img = core.material.images.images[this.UIMx[page][i][j]];
if (img)
ctx.drawImage(
img,
75 + j * 650 + 15,
150 + i * 500 + 15,
600 - 30,
450 - 30
);
} else {
ctx.fillStyle = "#000000";
ctx.fillRect(
75 + j * 650 + 15,
150 + i * 500 + 15,
600 - 30,
450 - 30
2025-01-02 17:04:30 +08:00
);
2025-01-16 13:28:25 +08:00
const img = core.material.images.images["LOGO.webp"];
if (img)
ctx.drawImage(
img,
75 + j * 650 + 15,
150 + i * 500 + 15,
600 - 30,
450 - 30
);
}
2025-01-02 17:04:30 +08:00
}
}
}
2024-12-30 20:44:53 +08:00
}
2025-01-02 17:04:30 +08:00
}
2025-01-16 13:28:25 +08:00
this.setcgs = function (img) {
const a = core.getLocalStorage("cgs", []);
if (img) {
if (!a.includes(img)) a.push(img);
core.setLocalStorage("cgs", a);
} else core.setLocalStorage("cgs");
};
2025-01-02 17:04:30 +08:00
core.ui.CG = new CG();
main.dom.CGMode.onclick = function () {
//点击开始页面的CG MODE进入cg回廊
main.core.control.checkBgm();
page = 0;
2025-01-16 13:28:25 +08:00
main.core.ui.CG.cgs = core.getLocalStorage("cgs", []);
2025-01-02 17:04:30 +08:00
CGUI.style.display = "block";
main.core.ui.CG.update();
};
},
2025-01-18 17:49:44 +08:00
"光标设置": function () {
2024-12-27 11:55:28 +08:00
// 在此增加新插件
this.changeMouse = function (
icon,
div = "gameGroup",
translateX = 0,
translateY = 0,
scaleX = 1,
scaleY = 1,
degree = 0,
px = 0,
py = 0
) {
const canvas = document.createElement("canvas");
canvas.width = 64;
canvas.height = 64;
const ctx = canvas.getContext("2d");
ctx.translate(translateX, translateY); //偏移画布x,y
ctx.scale(scaleX, scaleY); //(x,y轴缩放倍率-1为沿XY轴翻转)
const angle = (degree * Math.PI) / 180; //根据角度计算参数
ctx.rotate(angle); //顺时针旋转(以画布原点为中心,可通过偏移画布影响中心点)
let info = {
image: core.statusBar.icons[icon],
posX: 0,
posY: 0,
height: 32,
};
core.drawIcon(ctx, icon, 0, 0, 32, 32);
const data = canvas.toDataURL("image/png");
core.dom[div].style.cursor = `url(${data}) ${px} ${py},url(${data}),auto`; //div为你要改变光标的元素默认为包含状态栏的整个游戏画面,px/py为点击点偏移像素
};
this.removeMouse = function (div = "gameGroup") {
core.dom[div].style.cursor = "auto";
};
},
2025-01-18 17:49:44 +08:00
"信息弹出": function () {
2024-12-27 11:55:28 +08:00
// 在此增加新插件
2025-01-16 13:17:19 +08:00
/*
* 使用方法core.addPop(px, py, value, color, boldColor, left, jump, time, show, font, speed)
* 参数说明:
* px & py: number 弹出位置
* value: string 显示内容
* color: string 填充颜色
* boldColor: string 描边颜色
2024-12-27 11:55:28 +08:00
*/
2025-01-16 13:17:19 +08:00
// 默认字体
var fontD = "16px Verdana";
// 默认颜色
var colorD = "red";
// 默认描边颜色
var boldColorD = "black";
/** 血量弹出 */
function pop() {
var ctx = core.getContextByName("pop");
if (!ctx)
ctx = core.createCanvas(
"pop",
0,
0,
core.__PIXELS__,
core.__PIXELS__,
90
2024-12-30 20:44:53 +08:00
);
2025-01-16 13:17:19 +08:00
ctx.canvas.classList.add("gameCanvas", "anti-aliasing");
core.clearMap(ctx);
core.setTextAlign("pop", "left");
var list = core.status.pop || [];
var count = 0;
list.forEach(function (one) {
// 由frame计算出dy
var dy = 6 - one.frame * 0.2;
var dx = one.speed;
if (one.jump) {
one.py -= dy;
}
if (!one.left) {
one.px += dx;
} else {
one.px -= dx;
}
one.frame++;
// 绘制
if (one.frame >= one.time)
core.setAlpha(ctx, 1 - (one.frame - one.time) / one.show);
else core.setAlpha(ctx, 1);
core.fillBoldText1(
ctx,
one.value,
one.px,
one.py,
one.color || "red",
one.boldColor || "black",
2,
one.font
2024-12-30 20:44:53 +08:00
);
2025-01-16 13:17:19 +08:00
if (one.frame >= one.time + one.show) count++;
});
if (count > 0) list.splice(0, count);
}
if (!main.replayChecking) core.registerAnimationFrame("pop", true, pop);
2024-12-27 11:55:28 +08:00
2025-01-16 13:17:19 +08:00
/** 添加弹出内容 */
this.addPop = function (
value,
px,
py,
color,
boldColor,
left,
jump,
time,
show,
font,
speed
) {
var data = {
px: px,
py: py,
value: value,
color: color || colorD,
boldColor: boldColor || boldColorD,
frame: 0,
left: left || false,
jump: jump || false,
time: time || 60,
show: show || 30,
font: font || fontD,
speed: speed || 1,
};
if (!core.status.pop) core.status.pop = [data];
else core.status.pop.push(data);
2024-12-30 20:44:53 +08:00
};
2025-01-16 13:17:19 +08:00
},
2025-01-18 17:49:44 +08:00
"warning": function () {
2025-01-16 13:17:19 +08:00
// 在此增加新插件
// 默认音效名
var defaultSound = "jingbao.mp3";
// 默认字体名
var defaultFont = "Verdana";
var timeout;
/** warning
* @param {number} x 横坐标
* @param {number} y 纵坐标
* @param {string} text 显示的文字
*/
this.drawWarning = function (x, y, size, text, text2, warning) {
if (timeout) return;
x = x ?? 6;
y = y ?? 6;
text = text || "boss";
text += "</br>";
for (var i = 0; i < 10; i++) text += " ";
text += text2;
// 生成文字
var elements = document.querySelectorAll(".gameCanvas");
var t = document.createElement("p");
t.innerHTML = text;
t.style.position = "absolute";
t.style.fontSize = size * core.domStyle.scale + "px";
t.style.left = -(300 * core.domStyle.scale) + "px";
t.style.top = parseInt(elements[0].style.height) / 2 - 100 + "px";
t.style.zIndex = "300";
t.style.color = "#f11";
t.style.fontFamily = defaultFont;
t.style.overflow = "none";
t.style.width = "100%";
t.classList.add("warning");
core.dom.gameDraw.appendChild(t);
setTimeout(function () {
t.style.left = 416 * core.domStyle.scale + "px";
}, 50);
// 计算偏移量
var px = ((6 - x) / 12) * 50;
var py = ((6 - y) / 12) * 50;
// 修改画布的scale和transform
elements.forEach(function (v) {
if (v instanceof HTMLCanvasElement) {
v.style.transform = "scale(2)translate(" + px + "%, " + py + "%)";
}
2024-12-30 20:44:53 +08:00
});
2025-01-16 13:17:19 +08:00
if (!warning) core.playSound(defaultSound);
// 拉回镜头
timeout = setTimeout(function () {
timeout = setTimeout(function () {
timeout = void 0;
core.dom.gameDraw.removeChild(t);
}, 1500);
elements.forEach(function (v) {
if (v instanceof HTMLCanvasElement) {
v.style.transform = "none";
}
});
}, 1600);
};
},
2025-01-18 17:49:44 +08:00
"立体声音效": function () {
2025-01-16 13:17:19 +08:00
// 在此增加新插件
// 音效双声道播放
var can = true;
if (!AudioContext) {
console.warn("该浏览器不支持AudioContext无法播放立体声");
can = false;
}
if (can) var ac = new AudioContext();
var datas = {};
2024-12-27 11:55:28 +08:00
2025-01-16 13:17:19 +08:00
/** 播放立体声 参考https://developer.mozilla.org/zh-CN/docs/Web/API/BaseAudioContext/createChannelSplitter
* @param {number} name 音效名
* @param {number} left 左声道音量默认为1
* @param {number} right 右声道音量默认为1
* @param {boolean} split 音效为双声道请填true为单声道请填false或不填
* @returns 该音效的唯一id
*/
this.playStereo = function (name, left, right, split) {
if (!can) return core.playSound(name);
var sound = core.getMappedName(name);
2024-12-30 20:44:53 +08:00
if (
2025-01-16 13:17:19 +08:00
main.mode != "play" ||
2024-12-27 11:55:28 +08:00
!core.musicStatus.soundStatus ||
!core.material.sounds[sound]
)
return;
if (!core.status.stereo) core.status.stereo = {};
var buffer = core.material.sounds[sound];
var source = ac.createBufferSource();
source.buffer = buffer;
var splitter = ac.createChannelSplitter(2);
source.connect(splitter);
var merger = ac.createChannelMerger(2);
/*
2024-12-07 23:13:31 +08:00
gain(L)
/ \
source ---- splitter merger ---- destination
\ /
gain(R)
*/
2024-12-27 11:55:28 +08:00
var L = ac.createGain();
var R = ac.createGain();
L.gain.value = left * core.musicStatus.userVolume;
R.gain.value = right * core.musicStatus.userVolume;
splitter.connect(L, 0, 0);
if (!split) splitter.connect(R, 0, 0);
else splitter.connect(R, 1, 0);
var id = setTimeout(null);
core.status.stereo[id] = { source: source, L: L, R: R };
L.connect(merger, 0, 0);
R.connect(merger, 0, 1);
var dest = ac.destination;
source.onended = function () {
delete datas[id];
source = void 0;
};
2024-12-30 20:44:53 +08:00
2024-12-27 11:55:28 +08:00
merger.connect(dest);
source.start(0);
return id;
};
2024-12-30 20:44:53 +08:00
2024-12-27 11:55:28 +08:00
/**
* @param {number} id 为playStereo返回的id
* @param {number} left 要渐变到的左声道音量
* @param {number} right 要渐变到的右声道音量
* @param {number} time 渐变时间
*/
this.moveStereo = function (id, left, right, time) {
if (!can) return;
if (main.mode != "play" || !core.musicStatus.soundStatus) return;
var stereo = core.status.stereo[id];
datas[id] = {
time: time,
curr: 0,
dL:
((left - stereo.L.gain.value) / time) *
10 *
core.musicStatus.userVolume,
dR:
((right - stereo.R.gain.value) / time) *
10 *
core.musicStatus.userVolume,
};
var interval = setInterval(function () {
var data = datas[id];
if (!data) return clearInterval(interval);
data.curr += 10;
if (data.curr >= data.time) {
clearInterval(interval);
datas[id] = void 0;
}
stereo.L.gain.value += data.dL;
stereo.R.gain.value += data.dR;
}, 10);
};
},
2025-01-18 17:49:44 +08:00
"滑动转场": function () {
2024-12-27 11:55:28 +08:00
// 在此增加新插件
const defaultChange = {
left: "leftPortal", // 左箭头
up: "upPortal", // 上箭头
right: "rightPortal", // 右箭头
down: "downPortal", // 下箭头
upFloor: "upFloor", // 上楼
downFloor: "downFloor", // 下楼
};
const dirData = {
//方向坐标
up: [-1, 0],
down: [1, 0],
left: [0, -1],
right: [0, 1],
upFloor: [0, 0],
downFloor: [0, 0],
};
let allChangeEntries = Object.entries(defaultChange);
const move = document.createElement("canvas");
const speed = 12;
let modedata = 0;
2024-12-30 20:44:53 +08:00
2024-12-27 11:55:28 +08:00
move.width = 1248;
move.height = 1248;
2024-12-30 20:44:53 +08:00
2024-12-27 11:55:28 +08:00
const ctx = move.getContext("2d");
2024-12-30 20:44:53 +08:00
2024-12-27 11:55:28 +08:00
events.prototype.changeFloor = function (
floorId,
stair,
heroLoc,
time,
callback
) {
let block = core.getBlock(hero.loc.x, hero.loc.y);
var info = this._changeFloor_getInfo(floorId, stair, heroLoc, time);
if (info == null) {
if (callback) callback();
2024-12-30 20:44:53 +08:00
return;
}
2024-12-27 11:55:28 +08:00
floorId = info.floorId;
info.locked = core.status.lockControl;
2024-12-30 20:44:53 +08:00
2024-12-27 11:55:28 +08:00
core.dom.floorNameLabel.innerText = core.status.maps[floorId].title;
core.lockControl();
core.stopAutomaticRoute();
core.clearContinueAutomaticRoute();
core.status.replay.animate = true;
clearInterval(core.interval.onDownInterval);
core.interval.onDownInterval = "tmp";
2024-12-30 20:44:53 +08:00
2024-12-27 11:55:28 +08:00
this._changeFloor_beforeChange(info, block, callback);
};
events.prototype._changeFloor_beforeChange = function (
info,
block,
callback
) {
this._changeFloor_playSound();
2024-12-30 20:44:53 +08:00
if (
2024-12-27 11:55:28 +08:00
block &&
block?.event &&
!main.replayChecking &&
!core.isReplaying()
2024-12-30 20:44:53 +08:00
) {
2024-12-27 11:55:28 +08:00
const dirEntries = allChangeEntries.find(
(v) => v[1] === block.event.id
2024-12-30 20:44:53 +08:00
);
2024-12-27 11:55:28 +08:00
if (block?.event?.trigger === "changeFloor" && dirEntries) {
const toFloorId = block.event.data.floorId;
const dir = dirEntries[0];
2024-12-30 20:44:53 +08:00
2024-12-27 11:55:28 +08:00
const data = core.ui._drawViewMaps_buildData(
core.floorIds.indexOf(core.status.floorId)
2024-12-30 20:44:53 +08:00
);
2024-12-27 11:55:28 +08:00
const dataTo = core.ui._drawViewMaps_buildData(
core.floorIds.indexOf(toFloorId)
);
const v = dirData[dir][1], // 水平数值
h = dirData[dir][0]; //竖直数值
ctx.clearRect(0, 0, 1248, 1248);
core.drawThumbnail(core.status.floorId, null, {
damage: data.damage,
ctx: ctx,
x: 416,
y: 416,
size: 1,
all: data.all,
});
if (dir !== "upFloor" && dir !== "downFloor") {
core.drawThumbnail(toFloorId, null, {
damage: dataTo.damage,
ctx: ctx,
x: 416 + 416 * v,
y: 416 + 416 * h,
size: 1,
all: dataTo.all,
});
var _run = function () {
var cb = function () {
modedata = 0;
core.clearUI();
core.clearMap("data");
core.events._changeFloor_changing(info, callback);
};
var animate = window.setInterval(
function () {
if (modedata >= 416) {
delete core.animateFrame.asyncId[animate];
clearInterval(animate);
cb();
} else {
core.clearUI();
core.clearMap("data");
core.canvas.data.drawImage(
move,
416 + modedata * v,
416 + modedata * h,
416,
416,
0,
0,
416,
416
);
let status = "leftFoot";
if (modedata > 208) {
status = "rightFoot";
}
const img = core.material.images.hero;
const heroIconArr = core.material.icons.hero;
const width = core.material.icons.hero.width || 32;
const height = core.material.icons.hero.height;
const heroIcon = heroIconArr[dir];
core.canvas.data.drawImage(
img,
(heroIcon[status] % 4) * width,
heroIcon.loc * height,
width,
height,
core.status.hero.loc.x * 32 -
core.bigmap.offsetX -
(modedata - (modedata * 32) / 416) * v,
core.status.hero.loc.y * 32 -
16 -
(modedata - (modedata * 32) / 416) * h,
width,
height
);
modedata += speed;
clearInterval(animate);
delete core.animateFrame.asyncId[animate];
_run();
}
},
core.status.replay.speed == 24
? 1
: 10 / core.status.replay.speed
);
core.animateFrame.lastAsyncId = animate;
core.animateFrame.asyncId[animate] = cb;
};
_run();
return;
}
2024-12-30 20:44:53 +08:00
}
}
2024-12-27 11:55:28 +08:00
// 需要 setTimeout 执行,不然会出错
window.setTimeout(function () {
if (info.time == 0) core.events._changeFloor_changing(info, callback);
else
core.showWithAnimate(
core.dom.floorMsgGroup,
info.time / 2,
function () {
core.events._changeFloor_changing(info, callback);
}
);
}, 25);
2024-12-30 20:44:53 +08:00
};
2024-12-27 11:55:28 +08:00
},
2025-01-18 17:49:44 +08:00
"剧情cg": function () {
2025-01-16 13:17:19 +08:00
// 在此增加新插件
const cg = document.createElement("canvas"); //cg画布设置
cg.style.position = "absolute";
cg.style.zIndex = 300;
cg.style.display = "none";
cg.id = "cgText";
main.dom.gameGroup.insertAdjacentElement("afterend", cg);
cg.style.top = "50%";
cg.style.left = "50%";
cg.style.transform = "translate(-50%,-50%)";
const ctx = cg.getContext("2d");
main.dom.cgText = cg;
cg.onmouseup = function (e) {
//鼠标抬起
2024-12-27 11:55:28 +08:00
2025-01-16 13:17:19 +08:00
try {
e.stopPropagation();
if (!core.isPlaying()) return false;
core.unregisterAnimationFrame("skip");
let a = core.getFlag("skip", false);
core.setFlag("skip", false);
if (a) {
const data = core.clone(core.status.event.data.current);
2024-12-11 15:26:24 +08:00
2025-01-16 13:17:19 +08:00
core.insertAction(data);
core.doAction();
}
} catch (ee) {
console.error(ee);
2024-12-30 20:44:53 +08:00
}
};
2025-01-16 13:17:19 +08:00
cg.onmousedown = function (e) {
//鼠标按下
try {
e.stopPropagation();
if (!core.isPlaying()) return false;
const left = core.dom.gameGroup.offsetLeft;
const top = core.dom.gameGroup.offsetTop;
const px = Math.floor((e.clientX - left) / core.domStyle.scale),
py = Math.floor((e.clientY - top) / core.domStyle.scale);
core.ui.cgText.click(px * 3, py * 3);
} catch (ee) {
main.log(ee);
}
2024-12-30 20:44:53 +08:00
};
2025-01-16 13:17:19 +08:00
cg.ontouchend = function (e) {
//触摸抬起
2024-12-27 11:55:28 +08:00
2025-01-16 13:17:19 +08:00
try {
e.preventDefault();
if (!core.isPlaying()) return false;
core.unregisterAnimationFrame("skip");
core.setFlag("skip", false);
let a = core.getFlag("skip", false);
core.setFlag("skip", false);
if (a) {
const data = core.clone(core.status.event.data.current);
2024-12-27 11:55:28 +08:00
2025-01-16 13:17:19 +08:00
core.insertAction(data);
core.doAction();
}
} catch (ee) {
console.error(ee);
2024-12-30 20:44:53 +08:00
}
2025-01-16 13:17:19 +08:00
};
cg.ontouchstart = function (e) {
//触摸按下
try {
e.preventDefault();
if (!core.isPlaying()) return false;
const left = core.dom.gameGroup.offsetLeft;
const top = core.dom.gameGroup.offsetTop;
const px = Math.floor(
(e.targetTouches[0].clientX - left) / core.domStyle.scale
),
py = Math.floor(
(e.targetTouches[0].clientY - top) / core.domStyle.scale
);
core.ui.cgText.click(px * 3, py * 3);
} catch (ee) {
main.log(ee);
2024-12-30 20:44:53 +08:00
}
2025-01-16 13:17:19 +08:00
};
let auto = false;
class cgText {
constructor() {
//绘制需要的变量
this.image = "";
this.head = { name: "face_050445.webp", px: -300 };
this.bodyList = [
{ name: "tati_050145a.webp", px: 100, filter: false },
{ name: "tati_120124.webp", px: 1100, filter: true },
];
this.name = "";
this.text = "";
this.time = 0;
this.WindowSkin = false;
this.sound = "";
this.beforeSound = 0;
this.wait = 200;
this.memory = false;
}
click(px, py) {
//点击效果
2024-12-11 15:26:24 +08:00
2025-01-16 13:17:19 +08:00
const makeBox = ([x, y], [w, h]) => {
return [
[x, y],
[x + w, y + h],
];
};
const inRect = ([x, y], [[sx, sy], [dx, dy]]) => {
return sx <= x && x <= dx && sy <= y && y <= dy;
};
const pos = [px, py];
const savebox = makeBox([1700, 1100], [192, 96]);
const saveboxVertical = makeBox([52, 1700], [96, 192]);
const skipbox = makeBox([1400, 1100], [192, 96]);
const skipboxVertical = makeBox([52, 1400], [96, 192]);
const autobox = makeBox([1700, 900], [192, 96]);
const autoboxVertical = makeBox([256, 1700], [96, 192]);
if (
(core.domStyle.isVertical &&
inRect(pos, skipboxVertical) &&
!this.WindowSkin) ||
(!core.domStyle.isVertical &&
!this.WindowSkin &&
inRect(pos, skipbox))
) {
auto = false;
let time = 0;
core.stopSound(this.beforeSound);
core.registerAnimationFrame("skip", true, (timestamp) => {
if (timestamp > time + 50) {
time = timestamp;
if (
core.status.event.id == "action" &&
core.status.event.data.type == "cgtext"
) {
core.setFlag("skip", true);
main.dom.cgText.style.display = "none";
core.doAction();
}
}
});
} else if (
(core.domStyle.isVertical &&
inRect(pos, autoboxVertical) &&
!this.WindowSkin) ||
(!core.domStyle.isVertical &&
!this.WindowSkin &&
inRect(pos, autobox))
) {
auto = !auto;
const data = core.clone(core.status.event.data.current);
data.showAll = true;
data.time = 0;
data.text = data.text.replace(/(\\(z))(\[.*?])?/g, ""); //去除打字机暂停效果
data.sound = "";
core.insertAction(data);
core.doAction();
} else if (
(core.domStyle.isVertical &&
inRect(pos, saveboxVertical) &&
!this.WindowSkin) ||
(!core.domStyle.isVertical &&
!this.WindowSkin &&
inRect(pos, savebox))
) {
//存档
auto = false;
if (core.status.event.animateUI) return;
if (core.status.event.interval != null) return;
const current = core.clone(core.status.event.data.current);
current.showAll = true;
current.time = 0;
current.sound = "";
current.text = current.text.replace(/(\\(z))(\[.*?])?/g, ""); //去除当前事件所有打字机效果
cg.style.display = "none";
const data = [{ type: "callSave" }, current]; //插入存档事件
core.insertAction(data);
2024-12-27 11:55:28 +08:00
2025-01-16 13:17:19 +08:00
core.doAction();
} else if (!core.status.event.data) {
cg.style.display = "none";
core.ui._animateUI("hide", null, () => {
core.doAction();
});
} else {
// 正在淡入淡出的话不执行
if (core.status.event.animateUI) return;
auto = false;
// 打字机效果显示全部文字
if (core.status.event.interval != null) {
const data = core.clone(core.status.event.data?.current);
data.showAll = true;
data.time = 0;
data.text = data.text.replace(/(\\(z))(\[.*?])?/g, ""); //去除打字机暂停效果
data.sound = "";
core.insertAction(data);
core.doAction();
return;
} else {
core.stopSound(this.beforeSound);
}
2024-12-27 11:55:28 +08:00
2025-01-16 13:17:19 +08:00
cg.style.display = "none";
core.ui._animateUI("hide", null, () => {
core.doAction();
});
}
2024-12-30 20:44:53 +08:00
}
2024-12-27 11:55:28 +08:00
2025-01-16 13:17:19 +08:00
drawTextContent(ctx, content, config) {
//绘制多行文字并执行打字机效果
ctx = core.getContextByName(ctx);
// 设置默认配置项
var textAttribute =
core.status.textAttribute || core.initStatus.textAttribute;
var globalAttribute =
core.status.globalAttribute || core.initStatus.globalAttribute;
config = core.clone(config || {});
config.left = config.left || 0;
config.right =
config.left + (config.maxWidth == null ? core._PX_ : config.maxWidth);
config.top = config.top || 0;
config.color = core.arrayToRGBA(config.color || textAttribute.text);
if (config.bold == null) config.bold = textAttribute.bold;
config.italic = config.italic || false;
config.align = config.align || textAttribute.align || "left";
config.fontSize = config.fontSize || textAttribute.textfont;
config.lineHeight = config.lineHeight || config.fontSize * 1.3;
config.defaultFont = config.font = config.font || globalAttribute.font;
config.time = config.time || 0;
config.letterSpacing =
config.letterSpacing == null
? textAttribute.letterSpacing || 0
: config.letterSpacing;
config.index = 0;
config.currcolor = config.color;
config.currfont = config.fontSize;
config.lineMargin = Math.max(
Math.round(config.fontSize / 4),
config.lineHeight - config.fontSize
);
config.topMargin = parseInt(config.lineMargin / 2);
config.lineMaxHeight = config.lineMargin + config.fontSize;
config.offsetX = 0;
config.offsetY = 0;
config.line = 0;
config.blocks = [];
config.isHD = ctx == null || ctx.canvas.hasAttribute("isHD");
// 创建一个新的临时画布
var tempCtx = document.createElement("canvas").getContext("2d");
if (config.isHD && ctx) {
core.maps._setHDCanvasSize(
tempCtx,
ctx.canvas.width,
ctx.canvas.height
);
} else {
tempCtx.canvas.width = ctx == null ? 1 : ctx.canvas.width;
tempCtx.canvas.height = ctx == null ? 1 : ctx.canvas.height;
2024-12-30 20:44:53 +08:00
}
2024-12-27 11:55:28 +08:00
2025-01-16 13:17:19 +08:00
tempCtx.textBaseline = "top";
tempCtx.font = core.ui._buildFont(
config.fontSize,
config.bold,
config.italic,
config.font
2024-12-30 20:44:53 +08:00
);
2025-01-16 13:17:19 +08:00
tempCtx.fillStyle = config.color;
config = this._drawTextContent_draw(ctx, tempCtx, content, config);
return config;
}
_drawTextContent_draw(ctx, tempCtx, content, config) {
// Step 1: 绘制到tempCtx上并记录下图块信息
while (core.ui._drawTextContent_next(tempCtx, content, config));
if (ctx == null) return config;
// Step 2: 从tempCtx绘制到画布上
config.index = 0;
var _drawNext = function () {
if (config.index >= config.blocks.length) return false;
var block = config.blocks[config.index++];
if (block != null) {
// It works, why?
const scale = config.isHD
? devicePixelRatio * core.domStyle.scale
: 1;
ctx.restore();
ctx.save(); //保存设置
if (core.domStyle.isVertical) {
ctx.translate(1248, 0); //重新定位右上角为基准
ctx.rotate(Math.PI / 2); //旋转90度
}
ctx.drawImage(
tempCtx.canvas,
block.left * scale,
block.top * scale,
block.width * scale,
block.height * scale,
config.left + block.left + block.marginLeft,
config.top + block.top + block.marginTop,
block.width,
block.height
);
ctx.restore();
}
return true;
};
if (config.time == 0) {
while (_drawNext());
if (
(auto && !this.WindowSkin && !core.ui.cgText.sound) ||
(core.ui.cgText.sound && !core.musicStatus.soundStatus)
) {
setTimeout(() => {
if (auto) {
cg.style.display = "none";
core.ui._animateUI("hide", null, () => {
core.doAction();
});
}
}, core.ui.cgText.wait);
}
} else {
clearInterval(core.status.event.interval);
core.status.event.interval = setInterval(function () {
if (!_drawNext()) {
clearInterval(core.status.event.interval);
core.status.event.interval = null;
if (
(auto && !this.WindowSkin && !core.ui.cgText.sound) ||
(core.ui.cgText.sound && !core.musicStatus.soundStatus)
)
setTimeout(() => {
if (auto) {
cg.style.display = "none";
core.ui._animateUI("hide", null, () => {
core.doAction();
});
}
}, core.ui.cgText.wait);
}
}, config.time);
2024-12-30 20:44:53 +08:00
}
2025-01-16 13:17:19 +08:00
return config;
2024-12-30 20:44:53 +08:00
}
2024-12-27 11:55:28 +08:00
2025-01-16 13:17:19 +08:00
update() {
this.background();
}
background() {
const img = core.material.images.images?.[this.image];
2024-12-27 11:55:28 +08:00
2025-01-16 13:17:19 +08:00
if (core.domStyle.isVertical) {
ctx.canvas.width = 1248;
ctx.canvas.height = 2028;
ctx.save(); //保存设置
ctx.translate(1248, 0); //重新定位右上角为基准
ctx.rotate(Math.PI / 2); //旋转90度
} else {
ctx.canvas.width = 2028;
ctx.canvas.height = 1248;
}
if (img) {
//绘制背景
if (this.memory) ctx.filter = "sepia(50%)";
ctx.drawImage(img, 0, 0, 2028, 1248);
ctx.filter = "none";
} else {
core.fillRect(ctx, 0, 0, 2028, 1248);
}
this.bodyList.forEach((v) => {
//绘制立绘
const body = core.material.images.images?.[v.name];
if (v.filter) ctx.filter = "brightness(50%)";
if (body) {
if (!v.w && !v.h && !v.scale) v.scale = 1.7;
if (!v.w && !v.h) {
ctx.drawImage(
body,
0,
0,
body.width,
body.height,
v.px,
1248 - body.height * v.scale,
body.width * v.scale,
body.height * v.scale
);
} else {
ctx.drawImage(
body,
0,
0,
body.width,
body.height,
v.px,
1248 - (v.h ?? body.height),
v.w ?? body.width,
v.h ?? body.height
);
}
}
ctx.filter = "none";
});
if (core.isPlaying() && !this.WindowSkin)
core.drawWindowSkin("winskin.webp", ctx, 30, 802, 1968, 416); //绘制对话框
const head = core.material.images.images?.[this.head.name];
if (head) {
//绘制头像
ctx.drawImage(
head,
0,
0,
head.width,
head.height,
this.head.px,
1248 - head.height * 2,
head.width * 2,
head.height * 2
);
}
if (core.isPlaying() && !this.WindowSkin) {
core.drawWindowSkin("winskin.webp", ctx, 1700, 1100, 192, 96);
core.fillBoldText1(
ctx,
"存 档",
1736,
1166,
"#FFFFFF",
"#000000",
6,
"bold 48px Verdana"
);
core.drawWindowSkin("winskin.webp", ctx, 1400, 1100, 192, 96);
core.fillBoldText1(
ctx,
"▶▶",
1456,
1166,
"#FFFFFF",
"#000000",
6,
"bold 48px Verdana"
);
core.drawWindowSkin("winskin.webp", ctx, 1700, 900, 192, 96);
let autoText = "AUTO";
if (auto) autoText = "STOP";
core.fillBoldText1(
ctx,
autoText,
1722,
966,
"#FFFFFF",
"#000000",
6,
"bold 48px Verdana"
);
}
if (this.name)
core.fillBoldText1(
ctx,
`${this.name}`,
500,
880,
"#FFFFFF",
"#000000",
6,
"bold 48px Verdana"
); //绘制名字
if (
this.sound &&
core.material.sounds[this.sound] &&
!core.getFlag("skip", false) &&
core.musicStatus.soundStatus
) {
this.beforeSound = core.playSound(this.sound, null, () => {
if (
this.sound &&
auto &&
!this.WindowSkin &&
core.musicStatus.soundStatus
) {
setTimeout(() => {
if (auto) {
cg.style.display = "none";
core.ui._animateUI("hide", null, () => {
core.doAction();
});
}
}, core.ui.cgText.wait);
}
});
}
if (this.text && !core.getFlag("skip", false)) {
//绘制对话
this.drawTextContent(ctx, this.text, {
left: 500,
top: 950,
bold: true,
color: "#FFFFFF",
align: "left",
fontSize: 48,
time: this.time || 0,
font: "Verdana",
maxWidth: 1000,
});
}
ctx.restore();
}
}
core.ui.cgText = new cgText();
2024-12-27 11:55:28 +08:00
},
2025-01-18 17:49:44 +08:00
"旁白": function () {
2024-12-27 11:55:28 +08:00
// 在此增加新插件
2025-01-02 17:04:30 +08:00
const over = document.createElement("canvas"); //over画布设置
over.style.position = "absolute";
over.style.zIndex = 310;
over.style.display = "none";
over.id = "over";
main.dom.gameGroup.insertAdjacentElement("afterend", over);
over.style.top = "50%";
over.style.left = "50%";
over.style.transform = "translate(-50%,-50%)";
const ctx = over.getContext("2d");
main.dom.over = over;
2024-12-27 11:55:28 +08:00
2025-01-02 17:04:30 +08:00
this.over = function (
image,
memory,
time = 100,
hidetime = 30,
sound = "",
textColor = "#FFFFFF",
boldColor = "#000000",
font = "bold 48px Verdana",
text = ""
) {
if (!core.isPlaying()) {
return core.doAction();
}
const img = core.material.images.images?.[image];
over.style.display = "block";
let frame = 0;
let sod = 0;
let now = 0;
core.registerAnimationFrame("over", true, (timestamp) => {
if (timestamp - now > 1000 / 60) {
now = timestamp;
core.clearMap(ctx);
if (core.domStyle.isVertical) {
ctx.canvas.width = 416 * 3;
ctx.canvas.height = 676 * 3;
ctx.save(); //保存设置
ctx.translate(416 * 3, 0); //重新定位右上角为基准
ctx.rotate(Math.PI / 2); //旋转90度
} else {
ctx.canvas.width = 676 * 3;
ctx.canvas.height = 416 * 3;
}
ctx.globalAlpha = 1;
if (img) {
//绘制背景
if (memory) ctx.filter = "sepia(50%)";
ctx.drawImage(img, 0, 0, 676 * 3, 416 * 3);
ctx.filter = "none";
} else {
core.fillRect(ctx, 0, 0, 676 * 3, 416 * 3);
}
frame++;
// 绘制
if (frame <= hidetime)
core.setAlpha(ctx, 1 - (hidetime - frame) / hidetime);
if (frame > hidetime && frame <= hidetime + time) ctx.globalAlpha = 1;
if (frame > hidetime + time && frame <= hidetime * 2 + time)
core.setAlpha(ctx, 1 - (frame - hidetime - time) / hidetime);
const lisen =
sound &&
core.material.sounds[sound] &&
core.musicStatus.soundStatus;
if (frame == hidetime && lisen) {
sod = core.playSound(sound);
}
if (frame > hidetime * 2 + time) {
core.unregisterAnimationFrame("over");
ctx.restore();
over.style.display = "none";
core.stopSound(sod);
core.doAction();
return;
}
2024-12-07 23:13:31 +08:00
2025-01-02 17:04:30 +08:00
core.setTextAlign(ctx, "center");
core.fillBoldText1(
ctx,
text,
1014,
624,
textColor,
boldColor,
6,
font
);
2024-12-27 11:55:28 +08:00
2025-01-02 17:04:30 +08:00
ctx.restore();
}
});
};
2024-12-27 11:55:28 +08:00
2025-01-02 17:04:30 +08:00
this.overlist = function (
image,
memory,
hidetime = 30,
list = [
{
text: "",
sound: "",
time: 50,
textColor: "#FFFFFF",
boldColor: "#000000",
font: "bold 48px Verdana",
frame: 0,
},
]
) {
if (!core.isPlaying()) {
return core.doAction();
}
const img = core.material.images.images?.[image];
over.style.display = "block";
let sod = 0;
let i = 0;
let now = 0;
core.registerAnimationFrame("overlist", true, (timestamp) => {
if (timestamp - now > 1000 / 60) {
now = timestamp;
core.clearMap(ctx);
if (core.domStyle.isVertical) {
ctx.canvas.width = 416 * 3;
ctx.canvas.height = 676 * 3;
ctx.save(); //保存设置
ctx.translate(416 * 3, 0); //重新定位右上角为基准
ctx.rotate(Math.PI / 2); //旋转90度
} else {
ctx.canvas.width = 676 * 3;
ctx.canvas.height = 416 * 3;
}
ctx.globalAlpha = 1;
if (img) {
//绘制背景
if (memory) ctx.filter = "sepia(50%)";
ctx.drawImage(img, 0, 0, 676 * 3, 416 * 3);
ctx.filter = "none";
} else {
core.fillRect(ctx, 0, 0, 676 * 3, 416 * 3);
}
const a = list[i];
const b = list[i - 1];
const c = list[i - 2];
const d = list[i - 3];
let ay = 624,
by = 624,
cy = 624,
dy = 624;
if (i === 0 && !list[1]) {
core.over(
image,
memory,
a.time,
hidetime,
a.sound,
a.textColor,
a.boldColor,
a.font,
a.text
);
} else {
const numa =
parseInt(a?.font?.match(/\s*[\d.-]+[a-zA-Z%]*\s*/)?.[0].trim()) ||
48;
const numb =
parseInt(b?.font?.match(/\s*[\d.-]+[a-zA-Z%]*\s*/)?.[0].trim()) ||
48;
const numc =
parseInt(c?.font?.match(/\s*[\d.-]+[a-zA-Z%]*\s*/)?.[0].trim()) ||
48;
const numd =
parseInt(d?.font?.match(/\s*[\d.-]+[a-zA-Z%]*\s*/)?.[0].trim()) ||
48;
2024-12-27 11:55:28 +08:00
2025-01-02 17:04:30 +08:00
// 绘制
if (a) {
if (a.frame < hidetime / 2) {
a.frame++;
core.setAlpha(ctx, 1 - (hidetime - a.frame) / hidetime);
ay += ((numa * (hidetime - a.frame)) / hidetime) * 3;
core.setTextAlign(ctx, "center");
core.fillBoldText1(
ctx,
a.text,
1014,
ay,
a.textColor,
a.boldColor,
6,
a.font
);
}
if (a.frame === hidetime / 2) {
core.setAlpha(ctx, 1 - (hidetime - a.frame) / hidetime);
ay = 624 + ((numa * (hidetime - a.frame)) / hidetime) * 3;
core.setTextAlign(ctx, "center");
2024-12-27 11:55:28 +08:00
2025-01-02 17:04:30 +08:00
core.fillBoldText1(
ctx,
a.text,
1014,
ay,
a.textColor,
a.boldColor,
6,
a.font
);
if (!b) {
a.frame++;
i++;
}
}
}
if (b) {
if (b.frame > hidetime / 2 && b.frame <= hidetime) {
b.frame++;
core.setAlpha(ctx, 1 - (hidetime - b.frame) / hidetime);
by += ((numb * (hidetime - b.frame)) / hidetime) * 3;
core.setTextAlign(ctx, "center");
core.fillBoldText1(
ctx,
b.text,
1014,
by,
b.textColor,
b.boldColor,
6,
b.font
);
2024-12-27 11:55:28 +08:00
2025-01-02 17:04:30 +08:00
core.stopSound(sod);
}
const lisenb =
b.sound &&
core.material.sounds[b.sound] &&
core.musicStatus.soundStatus;
if (b.frame && lisenb) {
sod = core.playSound(sound);
}
if (b.frame > hidetime && b.frame < hidetime + b.time) {
b.frame++;
ctx.globalAlpha = 1;
core.setTextAlign(ctx, "center");
core.fillBoldText1(
ctx,
b.text,
1014,
by,
b.textColor,
b.boldColor,
6,
b.font
);
}
2024-12-27 11:55:28 +08:00
2025-01-02 17:04:30 +08:00
if (b.frame == hidetime + b.time) {
ctx.globalAlpha = 1;
core.setTextAlign(ctx, "center");
core.fillBoldText1(
ctx,
b.text,
1014,
by,
b.textColor,
b.boldColor,
6,
b.font
);
if (a) a.frame++;
if (b) b.frame++;
if (c) c.frame++;
i++;
}
}
if (c) {
if (
c.frame > hidetime + c.time &&
c.frame < (hidetime * 3) / 2 + c.time
) {
c.frame++;
core.setAlpha(
ctx,
1 - (c.frame - hidetime - c.time) / hidetime
);
cy -= ((numc * (c.frame - hidetime - c.time)) / hidetime) * 3;
core.setTextAlign(ctx, "center");
core.fillBoldText1(
ctx,
c.text,
1014,
cy,
c.textColor,
c.boldColor,
6,
c.font
);
}
if (c.frame === (hidetime * 3) / 2 + c.time) {
core.setAlpha(
ctx,
1 - (c.frame - hidetime - c.time) / hidetime
);
cy =
624 - ((numc * (c.frame - hidetime - c.time)) / hidetime) * 3;
core.setTextAlign(ctx, "center");
core.fillBoldText1(
ctx,
c.text,
1014,
cy,
c.textColor,
c.boldColor,
6,
c.font
);
if (!b) {
c.frame++;
i++;
}
}
}
if (d) {
if (
d.frame > (hidetime * 3) / 2 + d.time &&
d.frame < hidetime * 2 + d.time
) {
d.frame++;
core.setAlpha(
ctx,
1 - (d.frame - hidetime - d.time) / hidetime
);
dy -= ((numd * (d.frame - hidetime - d.time)) / hidetime) * 3;
core.setTextAlign(ctx, "center");
core.fillBoldText1(
ctx,
d.text,
1014,
dy,
d.textColor,
d.boldColor,
6,
d.font
);
}
if (d.frame == hidetime * 2 + d.time && !c) {
core.unregisterAnimationFrame("overlist");
ctx.restore();
over.style.display = "none";
core.stopSound(sod);
core.doAction();
return;
}
}
}
ctx.restore();
2024-12-27 11:55:28 +08:00
}
2025-01-02 17:04:30 +08:00
});
2024-12-27 11:55:28 +08:00
};
2025-01-02 17:04:30 +08:00
this.changebg = function (img1, memory1, img2, memory2, time, style) {
let globalAlpha1 = 0;
let globalAlpha2 = time;
img1 = core.material.images.images?.[img1];
img2 = core.material.images.images?.[img2];
over.style.display = "block";
let now = 0;
switch (style) {
case "引入":
core.registerAnimationFrame("bgin", true, (timestamp) => {
if (timestamp - now > 1000 / 60) {
now = timestamp;
core.clearMap(ctx);
if (core.domStyle.isVertical) {
ctx.canvas.width = 1248;
ctx.canvas.height = 676 * 3;
ctx.save(); //保存设置
ctx.translate(1248, 0); //重新定位右上角为基准
ctx.rotate(Math.PI / 2); //旋转90度
} else {
ctx.canvas.width = 676 * 3;
ctx.canvas.height = 1248;
}
ctx.globalAlpha = globalAlpha1 / time;
if (img2) {
//绘制背景
if (memory2) ctx.filter = "sepia(50%)";
ctx.drawImage(img2, 0, 0, 676 * 3, 1248);
ctx.filter = "none";
} else {
core.fillRect(ctx, 0, 0, 676 * 3, 1248);
}
globalAlpha1++;
ctx.restore();
if (globalAlpha1 >= time) {
core.unregisterAnimationFrame("bgin");
over.style.display = "none";
core.doAction();
}
}
});
2024-12-27 11:55:28 +08:00
2025-01-02 17:04:30 +08:00
break;
case "引出":
core.registerAnimationFrame("bgout", true, (timestamp) => {
if (timestamp - now > 1000 / 60) {
now = timestamp;
core.clearMap(ctx);
if (core.domStyle.isVertical) {
ctx.canvas.width = 1248;
ctx.canvas.height = 676 * 3;
ctx.save(); //保存设置
ctx.translate(1248, 0); //重新定位右上角为基准
ctx.rotate(Math.PI / 2); //旋转90度
} else {
ctx.canvas.width = 676 * 3;
ctx.canvas.height = 1248;
}
ctx.globalAlpha = globalAlpha2 / time;
if (img1) {
//绘制背景
if (memory1) ctx.filter = "sepia(50%)";
ctx.drawImage(img1, 0, 0, 676 * 3, 1248);
ctx.filter = "none";
} else {
core.fillRect(ctx, 0, 0, 676 * 3, 1248);
}
globalAlpha2--;
ctx.restore();
if (globalAlpha2 <= 0) {
core.unregisterAnimationFrame("bgout");
over.style.display = "none";
core.doAction();
}
}
});
2024-12-27 11:55:28 +08:00
2025-01-02 17:04:30 +08:00
break;
case "场景切换":
core.registerAnimationFrame("changebg", true, (timestamp) => {
if (timestamp - now > 1000 / 60) {
now = timestamp;
core.clearMap(ctx);
if (core.domStyle.isVertical) {
ctx.canvas.width = 1248;
ctx.canvas.height = 676 * 3;
ctx.save(); //保存设置
ctx.translate(1248, 0); //重新定位右上角为基准
ctx.rotate(Math.PI / 2); //旋转90度
} else {
ctx.canvas.width = 676 * 3;
ctx.canvas.height = 1248;
}
ctx.globalAlpha = 1;
core.fillRect(ctx, 0, 0, 676 * 3, 1248);
ctx.globalAlpha = globalAlpha2 / time;
if (img1) {
//绘制背景
if (memory1) ctx.filter = "sepia(50%)";
ctx.drawImage(img1, 0, 0, 676 * 3, 1248);
ctx.filter = "none";
} else {
core.fillRect(ctx, 0, 0, 676 * 3, 1248);
}
ctx.globalAlpha = globalAlpha1 / time;
if (img2) {
//绘制背景
if (memory2) ctx.filter = "sepia(50%)";
ctx.drawImage(img2, 0, 0, 676 * 3, 1248);
ctx.filter = "none";
} else {
core.fillRect(ctx, 0, 0, 676 * 3, 1248);
}
globalAlpha2--;
globalAlpha1++;
ctx.restore();
if (globalAlpha2 <= 0 || globalAlpha1 >= time) {
core.unregisterAnimationFrame("changebg");
over.style.display = "none";
core.doAction();
}
}
2024-12-27 11:55:28 +08:00
});
2025-01-02 17:04:30 +08:00
break;
}
};
},
2025-01-18 17:49:44 +08:00
"回合制boss战": function () {
2024-12-27 11:55:28 +08:00
// 在此增加新插件
const boss = document.createElement("canvas"); //boss战画布设置
boss.style.position = "absolute";
boss.style.zIndex = 300;
boss.style.display = "none";
boss.id = "boss";
main.dom.gameGroup.insertAdjacentElement("afterend", boss);
boss.style.top = "50%";
boss.style.left = "50%";
boss.style.transform = "translate(-50%,-50%)";
const ctx = boss.getContext("2d");
main.dom.boss = boss;
boss.onclick = function (e) {
try {
e.preventDefault();
if (core.isPlaying()) return false;
const left = core.dom.gameGroup.offsetLeft;
const top = core.dom.gameGroup.offsetTop;
const px = Math.floor((e.clientX - left) / core.domStyle.scale),
py = Math.floor((e.clientY - top) / core.domStyle.scale);
core.ui.boss.onclick(px * 3, py * 3);
} catch (ee) {
main.log(ee);
}
};
class boss1 {
constructor() {
this.bg = "";
}
cavans() {
if (core.domStyle.isVertical) {
ctx.canvas.width = 1248;
ctx.canvas.height = 2028;
core.setTextAlign(ctx, "center");
} else {
ctx.canvas.width = 2028;
ctx.canvas.height = 1248;
core.setTextAlign(ctx, "center");
}
}
start() {}
onclick(px, py) {}
}
core.ui.boss = new boss1();
},
2025-01-18 17:49:44 +08:00
"剧情视频引用": function () {
2025-01-02 17:04:30 +08:00
// 在此增加新插件
2025-01-16 13:17:19 +08:00
let a;
let bgm;
function gtouchstart() {
timeOutEvent = setTimeout(() => {
video.remove();
video1.remove();
core.doAction();
clearTimeout(a);
core.playBgm(bgm);
core.resumeBgm();
}, 2000); //这里设置定时器定义长按500毫秒触发长按事件时间可以自己改个人感觉500毫秒非常合适
return false;
}
2025-01-02 17:04:30 +08:00
2025-01-16 13:17:19 +08:00
//手释放如果在500毫秒内就释放则取消长按事件此时可以执行onclick应该执行的事件
function gtouchend() {
if (timeOutEvent != 0) {
//这里写要执行的内容尤如onclick事件
console.log("你这是点击,不是长按");
2025-01-02 17:04:30 +08:00
}
2025-01-16 13:17:19 +08:00
clearTimeout(timeOutEvent); //清除定时器
return false;
}
this.openvideo = function () {
if (!core.isPlaying()) return;
const video = document.createElement("iframe"); //iframe设置
video.style.position = "absolute";
video.style.zIndex = 320;
video.style.display = "block";
video.id = "video";
main.dom.gameGroup.insertAdjacentElement("afterend", video);
video.style.top = "50%";
video.style.left = "50%";
video.style.transform = "translate(-50%,-50%)";
main.dom.video = video;
const video1 = document.createElement("canvas"); //video1画布设置
video1.style.position = "absolute";
video1.style.zIndex = 330;
video1.style.display = "block";
video1.id = "video1";
main.dom.gameGroup.insertAdjacentElement("afterend", video1);
video1.style.top = "50%";
video1.style.left = "50%";
video1.style.transform = "translate(-50%,-50%)";
const ctx = video1.getContext("2d");
main.dom.video1 = video1;
if (core.domStyle.isVertical) {
video.width = 416 * 3;
video.height = 676 * 3;
video.style.transform = "translate(-50%,-50%) rotate(90deg)"; //重新定位右上角为基准
} else {
video.width = 676 * 3;
video.height = 416 * 3;
video.style.transform = "translate(-50%,-50%)";
}
video1.ontouchstart = function (e) {
try {
e.preventDefault();
if (!core.isPlaying()) return false;
gtouchstart();
} catch (ee) {
main.log(ee);
}
};
video1.ontouchend = function (e) {
try {
e.preventDefault();
if (!core.isPlaying()) return false;
gtouchend();
} catch (ee) {
main.log(ee);
}
};
video1.onmouseup = function (e) {
//鼠标抬起
try {
e.stopPropagation();
if (!core.isPlaying()) return false;
gtouchend();
} catch (ee) {
console.error(ee);
}
};
video1.onmousedown = function (e) {
//鼠标按下
try {
e.stopPropagation();
if (!core.isPlaying()) return false;
gtouchstart();
} catch (ee) {
main.log(ee);
}
};
let globalAlpha = 0;
let frame = 1;
let al = 0;
core.registerAnimationFrame("beforeop", true, function () {
al++;
core.clearMap(ctx);
ctx.globalAlpha = al / 30;
core.fillRect(ctx, 0, 0, video1.width, video1.height, "#000000");
ctx.globalAlpha = 1;
core.fillBoldText1(
ctx,
"Loading...",
1014,
624,
"#FFFFFF",
"#000000",
6,
"bold 72px Verdana"
);
});
core.control.resize();
//player.bilibili.com/player.html
//www.bilibili.com/blackboard/html5mobileplayer.html
//<iframe src="//player.bilibili.com/player.html?isOutside=true&aid=6484104&bvid=BV1cs411b7cH&cid=10546155&p=1" scrolling="no" border="0" frameborder="no" framespacing="0" allowfullscreen="true"></iframe>
video.src =
"///www.bilibili.com/blackboard/html5mobileplayer.html?isOutside=true&aid=6484104&bvid=BV1cs411b7cH&cid=10546155&p=1&poster=0&autoplay=1&high_quality=1&muted=0&danmaku=0";
video.scrolling = "no";
video.border = "0";
video.crossorigin = true;
video.frameborder = "no";
video.framespacing = "0";
video.allowfullscreen = false;
video.sandbox =
"allow-top-navigation allow-same-origin allow-forms allow-scripts";
//gsl_play_mask
video.addEventListener("load", function () {
core.unregisterAnimationFrame("beforeop");
core.registerAnimationFrame("op", true, function () {
core.clearMap(ctx);
if (core.domStyle.isVertical) {
ctx.canvas.width = 416 * 3;
ctx.canvas.height = 676 * 3;
ctx.save(); //保存设置
ctx.translate(416 * 3, 0); //重新定位右上角为基准
ctx.rotate(Math.PI / 2); //旋转90度
} else {
ctx.canvas.width = 676 * 3;
ctx.canvas.height = 416 * 3;
2025-01-02 17:04:30 +08:00
}
2025-01-16 13:17:19 +08:00
ctx.globalAlpha = 1;
core.fillRect(ctx, 0, 0, video1.width, video1.height, "#000000");
2024-12-11 15:26:24 +08:00
2025-01-16 13:17:19 +08:00
ctx.globalAlpha = globalAlpha / 30;
2025-01-02 17:04:30 +08:00
core.setTextAlign(ctx, "center");
core.fillBoldText1(
ctx,
2025-01-16 13:17:19 +08:00
"长按2秒后跳过op",
2025-01-02 17:04:30 +08:00
1014,
624,
2025-01-16 13:17:19 +08:00
"#FFFFFF",
"#000000",
2025-01-02 17:04:30 +08:00
6,
2025-01-16 13:17:19 +08:00
"bold 48px Verdana"
2025-01-02 17:04:30 +08:00
);
2025-01-16 13:17:19 +08:00
globalAlpha += frame;
if (globalAlpha > 29) frame = -1;
2025-01-02 17:04:30 +08:00
ctx.restore();
2025-01-16 13:17:19 +08:00
if (frame === -1 && globalAlpha < 0) {
core.clearMap(ctx);
core.unregisterAnimationFrame("op");
}
});
bgm = core.musicStatus.playingBgm;
core.playBgm("op.mp3");
a = setTimeout(() => {
video.remove();
video1.remove();
core.playBgm(bgm);
core.doAction();
}, 127500);
2025-01-02 17:04:30 +08:00
});
};
2025-01-16 13:17:19 +08:00
},
2025-01-18 17:49:44 +08:00
"帧动画/图片叠拼": function () {
2025-01-16 13:17:19 +08:00
// 在此增加新插件
this.animationDrawable = function (
allFarme,
color,
globalAlpha,
imageList,
soundList
2025-01-02 17:04:30 +08:00
) {
if (!core.isPlaying()) {
return core.doAction();
}
2025-01-16 13:17:19 +08:00
const over = main.dom.over;
const ctx = over.getContext("2d");
2025-01-02 17:04:30 +08:00
over.style.display = "block";
2025-01-03 13:05:30 +08:00
2025-01-16 13:17:19 +08:00
let farme = 0;
2025-01-02 17:04:30 +08:00
let now = 0;
2025-01-16 13:17:19 +08:00
core.registerAnimationFrame(
"animationDrawable",
true,
function (timestamp) {
if (timestamp - now > 1000 / 60) {
now = timestamp;
if (core.domStyle.isVertical) {
over.width = 1248;
over.height = 2028;
ctx.save(); //保存设置
ctx.translate(1248, 0); //重新定位右上角为基准
ctx.rotate(Math.PI / 2); //旋转90度
} else {
over.width = 2028;
over.height = 1248;
}
ctx.globalAlpha = (globalAlpha ?? 100) / 100;
core.fillRect(ctx, 0, 0, 2028, 1248, color);
imageList.forEach(function (one) {
if (
farme >= (one.beforefarme ?? 0) &&
farme <= (one.afterfarme ?? allFarme)
) {
const img = core.material.images.images?.[one.image];
if (img) {
const gla = one.globalAlpha ?? 100;
const agla = one.aglobalAlpha ?? gla,
beforefarme = one.beforefarme ?? 0;
const afterfarme = one.afterfarme ?? allFarme;
ctx.globalAlpha =
(gla +
((agla - gla) * (farme - beforefarme)) /
(afterfarme - beforefarme || 1)) /
100;
const cx =
(one.cx ?? 0) +
(((one.acx ?? 0) - (one.cx ?? 0)) *
(farme - beforefarme)) /
(afterfarme - beforefarme || 1),
cy =
(one.cy ?? 0) +
(((one.acy ?? 0) - (one.cy ?? 0)) *
(farme - beforefarme)) /
(afterfarme - beforefarme || 1),
cw =
(one.cw ?? img.width) +
(((one.acw ?? img.width) - (one.cw ?? img.width)) *
(farme - beforefarme)) /
(afterfarme - beforefarme || 1),
ch =
(one.ch ?? img.height) +
(((one.acw ?? img.height) - (one.cw ?? img.height)) *
(farme - beforefarme)) /
(afterfarme - beforefarme || 1),
x =
(one.x ?? 0) +
(((one.ax ?? 0) - (one.x ?? 0)) * (farme - beforefarme)) /
(afterfarme - beforefarme || 1),
y =
(one.y ?? 0) +
(((one.ay ?? 0) - (one.y ?? 0)) * (farme - beforefarme)) /
(afterfarme - beforefarme || 1),
w =
(one.w ?? 2028) +
(((one.aw ?? 2028) - (one.w ?? 2028)) *
(farme - beforefarme)) /
(afterfarme - beforefarme || 1),
h =
(one.h ?? 1248) +
(((one.aw ?? 1248) - (one.w ?? 1248)) *
(farme - beforefarme)) /
(afterfarme - beforefarme || 1);
ctx.drawImage(img, cx, cy, cw, ch, x, y, w, h);
}
}
});
soundList.forEach(function (one) {
const lisen =
one.sound &&
core.material.sounds[one.sound] &&
core.musicStatus.soundStatus;
if (farme == one.startfarme && lisen) {
if (one.stopbefore) core.stopSound();
core.playSound(one.sound);
}
});
farme++;
ctx.globalAlpha = 1;
ctx.restore();
if (farme > allFarme) {
core.unregisterAnimationFrame("animationDrawable");
over.style.display = "none";
core.doAction();
}
}
}
);
};
},
2025-01-18 17:49:44 +08:00
"musicMode": function () {
2025-01-19 20:20:48 +08:00
// 在此增加新插件
const music = document.createElement("canvas");
music.style.position = "absolute";
music.style.zIndex = 300;
music.style.display = "none";
music.id = "music";
main.dom.gameGroup.insertAdjacentElement("afterend", music);
music.style.top = "50%";
music.style.left = "50%";
music.style.transform = "translate(-50%,-50%)";
const ctx = music.getContext("2d");
main.dom.music = music;
const audio = document.createElement("audio");
audio.autoplay = true;
audio.preload = "auto";
function getRandomInt(min, max) {
const minCeiled = Math.ceil(min);
const maxFloored = Math.floor(max);
return Math.floor(Math.random() * (maxFloored - minCeiled) + minCeiled);
}
let page = 0; //初始页面
let ischange = false;
let isvolume = false;
2025-01-18 17:49:44 +08:00
2025-01-19 20:20:48 +08:00
function shuffle(arr) {
let n = arr.length,
random;
while (n) {
random = (Math.random() * n--) >>> 0;
[arr[n], arr[random]] = [arr[random], arr[n]];
}
return arr;
}
music.addEventListener("mousedown", function (e) {
e.stopPropagation();
const left = core.dom.gameGroup.offsetLeft;
const top = core.dom.gameGroup.offsetTop;
const px = Math.floor((e.clientX - left) / core.domStyle.scale),
py = Math.floor((e.clientY - top) / core.domStyle.scale);
core.ui.music.mousedown(px * 3, py * 3);
});
music.addEventListener("mousemove", function (e) {
e.stopPropagation();
const left = core.dom.gameGroup.offsetLeft;
const top = core.dom.gameGroup.offsetTop;
const px = Math.floor((e.clientX - left) / core.domStyle.scale),
py = Math.floor((e.clientY - top) / core.domStyle.scale);
core.ui.music.mousemove(px * 3, py * 3);
});
music.addEventListener("mouseup", function (e) {
e.stopPropagation();
ischange = false;
isvolume = false;
if (!main.core.ui.music.stop) audio.play();
});
music.addEventListener("mouseleave", function (e) {
e.stopPropagation();
ischange = false;
isvolume = false;
if (!main.core.ui.music.stop) audio.play();
});
music.addEventListener("touchstart", function (e) {
e.preventDefault();
const left = core.dom.gameGroup.offsetLeft;
const top = core.dom.gameGroup.offsetTop;
const px = Math.floor(
(e.touches[0].clientX - left) / core.domStyle.scale
),
py = Math.floor((e.touches[0].clientY - top) / core.domStyle.scale);
core.ui.music.mousedown(px * 3, py * 3);
});
music.addEventListener("touchmove", function (e) {
e.stopPropagation();
const left = core.dom.gameGroup.offsetLeft;
const top = core.dom.gameGroup.offsetTop;
const px = Math.floor(
(e.touches[0].clientX - left) / core.domStyle.scale
),
py = Math.floor((e.touches[0].clientY - top) / core.domStyle.scale);
core.ui.music.mousemove(px * 3, py * 3);
});
music.addEventListener("touchend", function (e) {
e.stopPropagation();
ischange = false;
isvolume = false;
if (!main.core.ui.music.stop) audio.play();
});
music.addEventListener("touchcancel", function (e) {
e.stopPropagation();
ischange = false;
isvolume = false;
});
2025-01-18 17:49:44 +08:00
2025-01-19 20:20:48 +08:00
audio.addEventListener("ended", function () {
switch (main.core.ui.music.type) {
case "danqu":
audio.currentTime = 0;
if (!main.core.ui.music.stop) audio.play();
main.core.ui.music.stop = false;
page = main.core.ui.music.selection[0];
2025-01-18 17:49:44 +08:00
2025-01-19 20:20:48 +08:00
break;
case "xunhuan":
if (
main.core.ui.music.selection[1] ===
main.core.ui.music.musicMx[main.core.ui.music.selection[0]].length -
1
) {
if (
main.core.ui.music.selection[0] ===
main.core.ui.music.musicMx.length - 1
) {
main.core.ui.music.selection[0] = 0;
main.core.ui.music.selection[1] = 0;
} else {
main.core.ui.music.selection[0] += 1;
main.core.ui.music.selection[1] = 0;
}
} else {
main.core.ui.music.selection[1] += 1;
}
main.core.ui.music.randomList.indexOf(
(v) =>
v ===
main.core.ui.music.musicMx[main.core.ui.music.selection[0]][
main.core.ui.music.selection[1]
]
);
page = main.core.ui.music.selection[0];
audio.src =
"project/bgms/" +
main.core.ui.music.musicMx[main.core.ui.music.selection[0]][
main.core.ui.music.selection[1]
];
2025-01-18 17:49:44 +08:00
2025-01-19 20:20:48 +08:00
if (!main.core.ui.music.stop) audio.play();
main.core.ui.music.stop = false;
break;
case "suiji":
if (
main.core.ui.music.random <
main.core.ui.music.randomList.length - 1
) {
main.core.ui.music.random += 1;
} else {
main.core.ui.music.random = 0;
}
main.core.ui.music.selection[0] =
main.core.ui.music.musicMx.findIndex((v) =>
v.includes(
main.core.ui.music.randomList[main.core.ui.music.random]
)
);
main.core.ui.music.selection[1] = main.core.ui.music.musicMx[
main.core.ui.music.selection[0]
].indexOf(main.core.ui.music.randomList[main.core.ui.music.random]);
audio.src =
"project/bgms/" +
main.core.ui.music.musicMx[main.core.ui.music.selection[0]][
main.core.ui.music.selection[1]
];
page = main.core.ui.music.selection[0];
2025-01-18 17:49:44 +08:00
2025-01-19 20:20:48 +08:00
if (!main.core.ui.music.stop) audio.play();
main.core.ui.music.stop = false;
break;
}
});
2025-01-18 17:49:44 +08:00
2025-01-19 20:20:48 +08:00
class musicclass {
constructor() {
//music列表
//需全塔属性注册并保存在bgms文件夹,每个数组为显示的一页内容
this.musicMx = [
["Asphodelus_Ceui.mp3", "Blind_Alley.mp3"],
["Crawler.mp3", "op.mp3", "theme.mp3"],
];
//音乐别名将在播放器内显示的音乐名music列表内的都要有对应歌名
this.musicname = {
"Asphodelus_Ceui.mp3": "Asphodelus",
"Blind_Alley.mp3": "Blind",
"Crawler.mp3": "Crawler",
"op.mp3": "op",
"theme.mp3": "theme",
};
this.selection = [0, 0];
this.stop = false;
this.type = "xunhuan";
this.randomList = [];
this.random = 0;
}
2025-01-18 17:49:44 +08:00
2025-01-19 20:20:48 +08:00
//更新
update() {
this.background();
this.drawUI();
}
background() {
//画布大小设置
if (core.domStyle.isVertical) {
music.width = 1248;
music.height = 2028;
} else {
music.width = 2028;
music.height = 1248;
}
}
mousedown(px, py) {
//鼠标按下时
//console.log(px, py)
const makeBox = ([x, y], [w, h]) => {
return [
[x, y],
[x + w, y + h],
];
};
const inRect = ([x, y], [[sx, sy], [dx, dy]]) => {
return sx <= x && x <= dx && sy <= y && y <= dy;
};
const pos = [px, py];
const backbox = makeBox([15, 35], [210, 90]);
if (inRect(pos, backbox)) {
//离开按钮是一致的,其余的记区分横竖屏
music.style.display = "none";
core.clearMap(ctx);
core.unregisterAnimationFrame("music");
audio.src = "";
core.restart();
2025-01-18 17:49:44 +08:00
2025-01-19 20:20:48 +08:00
return;
}
if (core.domStyle.isVertical) {
//竖屏
2025-01-18 17:49:44 +08:00
2025-01-19 20:20:48 +08:00
const pageupbox = makeBox([100, 1230], [200, 100]);
const pagedownbox = makeBox([950, 1230], [200, 100]);
const musicbox = makeBox(
[100, 200],
[1048, this.musicMx[page].length * 100]
);
const beforebox = makeBox([120, 1720], [100, 100]);
const afterbox = makeBox([780, 1720], [100, 100]);
const playbox = makeBox([420, 1680], [200, 200]);
const typebox = makeBox([1040, 1700], [120, 120]);
const changebox = makeBox([100, 1590], [1048, 20]);
const volumebox = makeBox([250, 1940], [1050, 20]);
if (inRect(pos, pageupbox)) {
if (page !== 0) page -= 1;
return;
}
if (inRect(pos, pagedownbox)) {
if (page !== this.musicMx.length - 1) page += 1;
return;
}
if (inRect(pos, playbox)) {
if (this.stop) {
this.stop = !this.stop;
audio.play();
} else {
this.stop = !this.stop;
audio.pause();
}
return;
}
if (inRect(pos, beforebox)) {
switch (this.type) {
case "danqu":
audio.currentTime = 0;
if (!this.stop) audio.play();
this.stop = false;
page = this.selection[0];
2025-01-18 17:49:44 +08:00
2025-01-19 20:20:48 +08:00
break;
case "xunhuan":
if (this.selection[1] === 0) {
if (this.selection[0] === 0) {
this.selection[0] = this.musicMx.length - 1;
this.selection[1] =
this.musicMx[this.selection[0]].length - 1;
} else {
this.selection[0] -= 1;
this.selection[1] =
this.musicMx[this.selection[0]].length - 1;
}
} else {
this.selection[1] -= 1;
}
this.randomList.indexOf(
this.musicMx[this.selection[0]][this.selection[1]]
);
page = this.selection[0];
audio.src =
"project/bgms/" +
this.musicMx[this.selection[0]][this.selection[1]];
2025-01-18 17:49:44 +08:00
2025-01-19 20:20:48 +08:00
if (!this.stop) audio.play();
this.stop = false;
break;
case "suiji":
if (this.random > 0) {
this.random -= 1;
} else {
this.random = this.randomList.length - 1;
}
this.selection[0] = this.musicMx.findIndex((v) =>
v.includes(this.randomList[this.random])
);
this.selection[1] = this.musicMx[this.selection[0]].indexOf(
this.randomList[this.random]
);
audio.src =
"project/bgms/" +
this.musicMx[this.selection[0]][this.selection[1]];
page = this.selection[0];
2025-01-18 17:49:44 +08:00
2025-01-19 20:20:48 +08:00
if (!this.stop) audio.play();
this.stop = false;
break;
}
return;
}
if (inRect(pos, afterbox)) {
switch (this.type) {
case "danqu":
audio.currentTime = 0;
if (!this.stop) audio.play();
this.stop = false;
page = this.selection[0];
break;
case "xunhuan":
if (
this.selection[1] ===
this.musicMx[this.selection[0]].length - 1
) {
if (this.selection[0] === this.musicMx.length - 1) {
this.selection[0] = 0;
this.selection[1] = 0;
} else {
this.selection[0] += 1;
this.selection[1] = 0;
}
} else {
this.selection[1] += 1;
}
this.random = this.randomList.indexOf(
this.musicMx[this.selection[0]][this.selection[1]]
);
page = this.selection[0];
audio.src =
"project/bgms/" +
this.musicMx[this.selection[0]][this.selection[1]];
2025-01-18 17:49:44 +08:00
2025-01-19 20:20:48 +08:00
if (!this.stop) audio.play();
this.stop = false;
break;
case "suiji":
if (this.random < this.randomList.length - 1) {
this.random += 1;
} else {
this.random = 0;
}
this.selection[0] = this.musicMx.findIndex((v) =>
v.includes(this.randomList[this.random])
);
this.selection[1] = this.musicMx[this.selection[0]].indexOf(
this.randomList[this.random]
);
audio.src =
"project/bgms/" +
this.musicMx[this.selection[0]][this.selection[1]];
2025-01-18 17:49:44 +08:00
2025-01-19 20:20:48 +08:00
page = this.selection[0];
if (!this.stop) audio.play();
this.stop = false;
break;
}
return;
}
if (inRect(pos, typebox)) {
switch (this.type) {
case "danqu":
this.type = "xunhuan";
break;
case "xunhuan":
this.type = "suiji";
break;
case "suiji":
this.type = "danqu";
break;
}
return;
}
if (inRect(pos, musicbox)) {
const index = Math.floor((py - 200) / 100);
if (page !== this.selection[0] || index !== this.selection[1]) {
this.selection[0] = page;
this.selection[1] = index;
this.randomList.indexOf(
this.musicMx[this.selection[0]][this.selection[1]]
);
audio.src = "project/bgms/" + this.musicMx[page][index];
2025-01-18 17:49:44 +08:00
2025-01-19 20:20:48 +08:00
if (!this.stop) audio.play();
this.stop = false;
} else {
if (this.stop) {
this.stop = !this.stop;
audio.play();
} else {
this.stop = !this.stop;
audio.pause();
}
}
return;
}
if (inRect(pos, changebox)) {
const time = Math.floor(((px - 100) / 1000) * audio.duration);
2025-01-18 17:49:44 +08:00
2025-01-19 20:20:48 +08:00
audio.pause();
audio.currentTime = time;
2025-01-18 17:49:44 +08:00
2025-01-19 20:20:48 +08:00
ischange = true;
}
if (inRect(pos, volumebox)) {
const time = Math.min(Math.max((px - 250) / 800, 0), 1);
audio.volume = time;
isvolume = true;
}
} else {
//横屏
const pageupbox = makeBox([1050, 1100], [200, 100]);
const pagedownbox = makeBox([1550, 1100], [200, 100]);
const musicbox = makeBox(
[900, 100],
[1000, this.musicMx[page].length * 100]
);
const beforebox = makeBox([135, 740], [50, 50]);
const afterbox = makeBox([450, 740], [50, 50]);
const playbox = makeBox([250, 700], [200, 200]);
const typebox = makeBox([600, 700], [100, 100]);
const changebox = makeBox([100, 590], [600, 20]);
const volumebox = makeBox([100, 990], [600, 20]);
if (inRect(pos, pageupbox)) {
if (page !== 0) page -= 1;
return;
}
if (inRect(pos, pagedownbox)) {
if (page !== this.musicMx.length - 1) page += 1;
return;
}
if (inRect(pos, playbox)) {
if (this.stop) {
this.stop = !this.stop;
audio.play();
} else {
this.stop = !this.stop;
audio.pause();
}
return;
}
if (inRect(pos, beforebox)) {
switch (this.type) {
case "danqu":
audio.currentTime = 0;
if (!this.stop) audio.play();
this.stop = false;
page = this.selection[0];
2025-01-18 17:49:44 +08:00
2025-01-19 20:20:48 +08:00
break;
case "xunhuan":
if (this.selection[1] === 0) {
if (this.selection[0] === 0) {
this.selection[0] = this.musicMx.length - 1;
this.selection[1] =
this.musicMx[this.selection[0]].length - 1;
} else {
this.selection[0] -= 1;
this.selection[1] =
this.musicMx[this.selection[0]].length - 1;
}
} else {
this.selection[1] -= 1;
}
this.random = this.randomList.indexOf(
this.musicMx[this.selection[0]][this.selection[1]]
);
page = this.selection[0];
audio.src =
"project/bgms/" +
this.musicMx[this.selection[0]][this.selection[1]];
2025-01-18 17:49:44 +08:00
2025-01-19 20:20:48 +08:00
if (!this.stop) audio.play();
this.stop = false;
break;
case "suiji":
if (this.random > 0) {
this.random -= 1;
} else {
this.random = this.randomList.length - 1;
}
this.selection[0] = this.musicMx.findIndex((v) =>
v.includes(this.randomList[this.random])
);
this.selection[1] = this.musicMx[this.selection[0]].indexOf(
this.randomList[this.random]
);
audio.src =
"project/bgms/" +
this.musicMx[this.selection[0]][this.selection[1]];
page = this.selection[0];
2025-01-18 17:49:44 +08:00
2025-01-19 20:20:48 +08:00
if (!this.stop) audio.play();
this.stop = false;
break;
}
return;
}
if (inRect(pos, afterbox)) {
switch (this.type) {
case "danqu":
audio.currentTime = 0;
if (!this.stop) audio.play();
this.stop = false;
page = this.selection[0];
break;
case "xunhuan":
if (
this.selection[1] ===
this.musicMx[this.selection[0]].length - 1
) {
if (this.selection[0] === this.musicMx.length - 1) {
this.selection[0] = 0;
this.selection[1] = 0;
} else {
this.selection[0] += 1;
this.selection[1] = 0;
}
} else {
this.selection[1] += 1;
}
this.randomList.findIndex(
(v) =>
v === this.musicMx[this.selection[0]][this.selection[1]]
);
page = this.selection[0];
audio.src =
"project/bgms/" +
this.musicMx[this.selection[0]][this.selection[1]];
2025-01-18 17:49:44 +08:00
2025-01-19 20:20:48 +08:00
if (!this.stop) audio.play();
this.stop = false;
break;
case "suiji":
if (this.random < this.randomList.length - 1) {
this.random += 1;
} else {
this.random = 0;
}
this.selection[0] = this.musicMx.findIndex((v) =>
v.includes(this.randomList[this.random])
);
this.selection[1] = this.musicMx[this.selection[0]].indexOf(
main.core.ui.music.randomList[main.core.ui.music.random]
);
audio.src =
"project/bgms/" +
this.musicMx[this.selection[0]][this.selection[1]];
2025-01-18 17:49:44 +08:00
2025-01-19 20:20:48 +08:00
page = this.selection[0];
if (!this.stop) audio.play();
this.stop = false;
break;
}
return;
}
if (inRect(pos, typebox)) {
switch (this.type) {
case "danqu":
this.type = "xunhuan";
break;
case "xunhuan":
this.type = "suiji";
break;
case "suiji":
this.type = "danqu";
break;
}
return;
}
if (inRect(pos, musicbox)) {
const index = Math.floor((py - 100) / 100);
if (page !== this.selection[0] || index !== this.selection[1]) {
this.selection[0] = page;
this.selection[1] = index;
this.randomList.indexOf(
(v) => v === this.musicMx[this.selection[0]][this.selection[1]]
);
audio.src = "project/bgms/" + this.musicMx[page][index];
2025-01-18 17:49:44 +08:00
2025-01-19 20:20:48 +08:00
if (!this.stop) audio.play();
this.stop = false;
} else {
if (this.stop) {
this.stop = !this.stop;
audio.play();
} else {
this.stop = !this.stop;
audio.pause();
}
}
return;
}
if (inRect(pos, changebox)) {
const time = Math.floor(((px - 100) / 600) * audio.duration);
2025-01-18 17:49:44 +08:00
2025-01-19 20:20:48 +08:00
audio.pause();
audio.currentTime = time;
2025-01-18 17:49:44 +08:00
2025-01-19 20:20:48 +08:00
ischange = true;
}
if (inRect(pos, volumebox)) {
const time = Math.min(Math.max((px - 100) / 600, 0), 1);
audio.volume = time;
isvolume = true;
}
}
}
mousemove(px, py) {
if (ischange) {
if (core.domStyle.isVertical) {
const time = Math.min(
Math.max(Math.floor(((px - 100) / 600) * audio.duration), 0),
audio.duration
);
2025-01-18 17:49:44 +08:00
2025-01-19 20:20:48 +08:00
audio.currentTime = time;
} else {
const time = Math.min(
Math.max(Math.floor(((px - 100) / 600) * audio.duration), 0),
audio.duration
);
2025-01-18 17:49:44 +08:00
2025-01-19 20:20:48 +08:00
audio.currentTime = time;
}
}
if (isvolume) {
if (core.domStyle.isVertical) {
const time = Math.min(Math.max((px - 250) / 800, 0), 1);
audio.volume = time;
} else {
const time = Math.min(Math.max((px - 100) / 600, 0), 1);
audio.volume = time;
}
}
}
2025-01-18 17:49:44 +08:00
2025-01-19 20:20:48 +08:00
drawUI() {
//绘制页面
core.clearMap(music);
const bgVertical = core.material.images.images["bg_2010.webp"]; //竖屏背景
const bg = core.material.images.images["bg_5043.webp"]; //竖屏背景
if (core.domStyle.isVertical) {
//竖屏
2025-01-18 17:49:44 +08:00
2025-01-19 20:20:48 +08:00
core.fillRect(ctx, 0, 0, 1248, 2028, "#000000"); //黑色背景
ctx.globalAlpha = 0.3; //透明度
if (bgVertical)
ctx.drawImage(bgVertical, 0, 0, 1280, 1500, 0, 0, 1248, 2028); //绘制半透明背景图片
ctx.globalAlpha = 1; //恢复为不透明
2025-01-18 17:49:44 +08:00
2025-01-19 20:20:48 +08:00
core.setTextAlign(ctx, "center");
core.fillBoldText1(
ctx,
"◀离开",
110,
100,
"#FFFFFF",
"#000000",
6,
core.ui._buildFont(66, true)
);
2025-01-18 17:49:44 +08:00
2025-01-19 20:20:48 +08:00
ctx.strokeStyle = "#FFFFFF";
ctx.lineWidth = 3;
ctx.beginPath();
ctx.moveTo(100, 200);
ctx.lineTo(1148, 200);
ctx.stroke();
let posy = 300;
const indexList = this.musicMx[page];
core.setTextAlign(ctx, "left");
for (let i = 0; i < indexList.length; i++) {
const text = this.musicname[indexList[i]];
core.fillBoldText1(
ctx,
text,
150,
posy - 30,
page === this.selection[0] && i === this.selection[1]
? "#FFFFFF"
: "#444444",
"#000000",
6,
core.ui._buildFont(66, true)
);
ctx.strokeStyle = "#FFFFFF";
ctx.lineWidth = 3;
ctx.beginPath();
ctx.moveTo(100, posy);
ctx.lineTo(1148, posy);
ctx.stroke();
posy += 100;
}
ctx.beginPath();
ctx.moveTo(100, 1210);
ctx.lineTo(1148, 1210);
ctx.moveTo(100, 1200);
ctx.lineTo(1148, 1200);
ctx.stroke();
2025-01-18 17:49:44 +08:00
2025-01-19 20:20:48 +08:00
core.fillBoldText1(
ctx,
"上一页",
100,
1300,
page === 0 ? "#444444" : "#FFFFFF",
"#000000",
6,
core.ui._buildFont(66, true)
);
core.fillBoldText1(
ctx,
page + 1 + "/" + this.musicMx.length,
580,
1300,
"#FFFFFF",
"#000000",
6,
core.ui._buildFont(66, true)
);
core.fillBoldText1(
ctx,
"下一页",
950,
1300,
page === this.musicMx.length - 1 ? "#444444" : "#FFFFFF",
"#000000",
6,
core.ui._buildFont(66, true)
);
ctx.strokeStyle = "#ffffff";
ctx.lineWidth = 3;
ctx.beginPath();
ctx.moveTo(100, 1600);
ctx.lineTo(1148, 1600);
ctx.stroke();
ctx.fillStyle = "#ffffff";
ctx.font = "bold 96px Verdana";
ctx.fillText("|", 100, 1797);
ctx.fillText("◀", 115, 1800);
ctx.beginPath();
ctx.arc(505, 1770, 80, 0, 3 * Math.PI);
ctx.stroke();
ctx.fillText("|", 835, 1797);
ctx.fillText("▶", 785, 1800);
if (this.stop) {
ctx.fillText("▶", 473, 1797);
} else {
ctx.fillText("||", 453, 1794);
}
const img = core.material.images.images[this.type + ".webp"];
if (img) ctx.drawImage(img, 1000, 1655, 200, 200);
core.setTextAlign(ctx, "center");
ctx.font = "bold 52px Verdana";
ctx.fillText("当前歌曲", 625, 1397);
ctx.fillText(
this.musicname[this.musicMx[this.selection[0]][this.selection[1]]],
625,
1507
);
ctx.font = "bold 36px Verdana";
const thistime = audio.currentTime;
if (thistime) {
const timetext =
Math.floor(thistime / 60)
.toString()
.padStart(2, "0") +
":" +
Math.floor(thistime % 60)
.toString()
.padStart(2, "0");
ctx.fillText(timetext, 960, 1650);
} else {
const timetext = "00:00";
ctx.fillText(timetext, 960, 1650);
}
ctx.fillText("/", 1030, 1650);
const fulltime = audio.duration;
if (fulltime) {
const timetext =
Math.floor(fulltime / 60)
.toString()
.padStart(2, "0") +
":" +
Math.floor(fulltime % 60)
.toString()
.padStart(2, "0");
ctx.fillText(timetext, 1100, 1650);
} else {
const timetext = "00:00";
ctx.fillText(timetext, 1100, 1650);
}
ctx.strokeStyle = "#ffffff";
ctx.lineWidth = 9;
ctx.fillStyle = "rgba(255,255,255,0.5)";
const pointx = (1048 * thistime) / fulltime + 100;
if (fulltime && thistime) {
ctx.beginPath();
ctx.moveTo(100, 1600);
ctx.lineTo(pointx, 1600);
ctx.stroke();
ctx.beginPath();
ctx.arc(pointx, 1600, 10, 0, 2 * Math.PI);
ctx.fill();
} else {
ctx.beginPath();
ctx.arc(100, 1600, 10, 0, 2 * Math.PI);
ctx.fill();
}
ctx.fillStyle = "#ffffff";
ctx.font = "bold 48px Verdana";
ctx.fillText("音量", 150, 1970);
ctx.lineWidth = 3;
ctx.beginPath();
ctx.moveTo(250, 1950);
ctx.lineTo(1050, 1950);
ctx.stroke();
ctx.strokeStyle = "#ffffff";
ctx.lineWidth = 9;
ctx.fillStyle = "rgba(255,255,255,0.5)";
ctx.beginPath();
ctx.moveTo(250, 1950);
ctx.lineTo(800 * audio.volume + 250, 1950);
ctx.stroke();
ctx.beginPath();
ctx.arc(800 * audio.volume + 250, 1950, 10, 0, 2 * Math.PI);
ctx.fill();
core.fillBoldText1(
ctx,
Math.floor(100 * audio.volume),
1120,
1970,
"#FFFFFF",
"#000000",
6,
core.ui._buildFont(48, true)
);
} else {
//横屏
core.fillRect(ctx, 0, 0, 2028, 1248, "#000000"); //黑色背景
ctx.globalAlpha = 0.5; //透明度
if (bg) ctx.drawImage(bg, 0, 0, 1280, 720, 0, 0, 2028, 1248); //绘制半透明背景图片
ctx.globalAlpha = 1; //恢复为不透明
core.setTextAlign(ctx, "center");
core.fillBoldText1(
ctx,
"◀离开",
110,
100,
"#FFFFFF",
"#000000",
6,
core.ui._buildFont(66, true)
);
//core.fillRect(ctx, 440, 760, 50, 50)
ctx.strokeStyle = "#FFFFFF";
ctx.lineWidth = 3;
ctx.beginPath();
ctx.moveTo(800, 100);
ctx.lineTo(800, 1148);
ctx.moveTo(900, 100);
ctx.lineTo(1900, 100);
ctx.stroke();
let posy = 200;
const indexList = this.musicMx[page];
core.setTextAlign(ctx, "left");
for (let i = 0; i < indexList.length; i++) {
const text = this.musicname[indexList[i]];
core.fillBoldText1(
ctx,
text,
950,
posy - 30,
page === this.selection[0] && i === this.selection[1]
? "#FFFFFF"
: "#444444",
"#000000",
6,
core.ui._buildFont(66, true)
);
ctx.strokeStyle = "#FFFFFF";
ctx.lineWidth = 3;
ctx.beginPath();
ctx.moveTo(900, posy);
ctx.lineTo(1900, posy);
ctx.stroke();
posy += 100;
}
core.fillBoldText1(
ctx,
"上一页",
1050,
1200 - 30,
page === 0 ? "#444444" : "#FFFFFF",
"#000000",
6,
core.ui._buildFont(66, true)
);
core.fillBoldText1(
ctx,
page + 1 + "/" + this.musicMx.length,
1350,
1200 - 30,
"#FFFFFF",
"#000000",
6,
core.ui._buildFont(66, true)
);
core.fillBoldText1(
ctx,
"下一页",
1550,
1200 - 30,
page === this.musicMx.length - 1 ? "#444444" : "#FFFFFF",
"#000000",
6,
core.ui._buildFont(66, true)
);
ctx.strokeStyle = "#ffffff";
ctx.lineWidth = 3;
ctx.beginPath();
ctx.moveTo(100, 600);
ctx.lineTo(700, 600);
ctx.stroke();
ctx.fillStyle = "#ffffff";
ctx.font = "bold 48px Verdana";
ctx.fillText("|", 130, 797);
ctx.fillText("◀", 140, 800);
ctx.beginPath();
ctx.arc(310, 780, 50, 0, 2 * Math.PI);
ctx.stroke();
if (this.stop) {
ctx.fillText("▶", 295, 797);
} else {
ctx.fillText("||", 285, 794);
}
ctx.fillText("|", 470, 797);
ctx.fillText("▶", 450, 800);
ctx.fillText("音量", 350, 900);
ctx.beginPath();
ctx.moveTo(100, 1000);
ctx.lineTo(700, 1000);
ctx.stroke();
ctx.strokeStyle = "#ffffff";
ctx.lineWidth = 9;
ctx.fillStyle = "rgba(255,255,255,0.5)";
ctx.beginPath();
ctx.moveTo(100, 1000);
ctx.lineTo(600 * audio.volume + 100, 1000);
ctx.stroke();
ctx.beginPath();
ctx.arc(600 * audio.volume + 100, 1000, 10, 0, 2 * Math.PI);
ctx.fill();
core.fillBoldText1(
ctx,
Math.floor(100 * audio.volume),
720,
1010,
"#FFFFFF",
"#000000",
6,
core.ui._buildFont(32, true)
);
const img = core.material.images.images[this.type + ".webp"];
if (img) ctx.drawImage(img, 580, 730, 100, 100);
core.setTextAlign(ctx, "center");
ctx.font = "bold 48px Verdana";
ctx.fillText("当前歌曲", 400, 297);
ctx.fillText(
this.musicname[this.musicMx[this.selection[0]][this.selection[1]]],
400,
397
);
ctx.font = "bold 36px Verdana";
const thistime = audio.currentTime;
if (thistime) {
const timetext =
Math.floor(thistime / 60)
.toString()
.padStart(2, "0") +
":" +
Math.floor(thistime % 60)
.toString()
.padStart(2, "0");
ctx.fillText(timetext, 510, 650);
} else {
const timetext = "00:00";
ctx.fillText(timetext, 510, 650);
}
ctx.fillText("/", 580, 650);
const fulltime = audio.duration;
if (fulltime) {
const timetext =
Math.floor(fulltime / 60)
.toString()
.padStart(2, "0") +
":" +
Math.floor(fulltime % 60)
.toString()
.padStart(2, "0");
ctx.fillText(timetext, 650, 650);
} else {
const timetext = "00:00";
ctx.fillText(timetext, 650, 650);
}
ctx.strokeStyle = "#ffffff";
ctx.lineWidth = 9;
ctx.fillStyle = "rgba(255,255,255,0.5)";
const pointx = (600 * thistime) / fulltime + 100;
if (fulltime && thistime) {
ctx.beginPath();
ctx.moveTo(100, 600);
ctx.lineTo(pointx, 600);
ctx.stroke();
ctx.beginPath();
ctx.arc(pointx, 600, 10, 0, 2 * Math.PI);
ctx.fill();
} else {
ctx.beginPath();
ctx.arc(100, 600, 10, 0, 2 * Math.PI);
ctx.fill();
}
}
}
}
core.ui.music = new musicclass();
main.dom.musicMode.onclick = function () {
//点击开始页面的CG MODE进入cg回廊
main.core.control.checkBgm();
main.core.control.pauseBgm();
audio.src =
"project/bgms/" +
main.core.ui.music.musicMx[main.core.ui.music.selection[0]][
main.core.ui.music.selection[1]
];
const arr = main.core.ui.music.musicMx.flat(Infinity);
main.core.ui.music.randomList = shuffle(arr);
main.core.ui.music.random = main.core.ui.music.randomList.indexOf(
main.core.ui.music.musicMx[main.core.ui.music.selection[0]][
main.core.ui.music.selection[1]
]
);
page = 0;
music.style.display = "block";
let time = 0;
core.registerAnimationFrame("music", null, (temptime) => {
if (temptime > time + 1000 / 60) {
time = temptime;
main.core.ui.music.update();
}
});
};
},
2025-01-18 17:49:44 +08:00
"横屏切换": function () {
2025-01-05 00:03:41 +08:00
// 在此增加新插件
this.triggerFullscreen = async function (full) {
if (!!document.fullscreenElement && !full) {
if (window.jsinterface) {
window.jsinterface.requestPortrait();
return;
}
await document.exitFullscreen();
}
if (full && !document.fullscreenElement) {
if (window.jsinterface) {
window.jsinterface.requestLandscape();
return;
}
await document.body.requestFullscreen();
}
};
this.abc = function () {
var orientation =
(screen.orientation || {}).type ||
screen.mozOrientation ||
screen.msOrientation;
if (orientation === "landscape-primary") {
console.log("That looks good.");
} else if (orientation === "landscape-secondary") {
console.log("Mmmh... the screen is upside down!");
} else if (
orientation === "portrait-secondary" ||
orientation === "portrait-primary"
) {
console.log("Mmmh... you should rotate your device to landscape");
} else if (orientation === undefined) {
console.log("The orientation API isn't supported in this browser :(");
}
};
},
2025-01-18 17:49:44 +08:00
"图片压缩webp导出": function () {
2025-01-16 13:17:19 +08:00
// 在此增加新插件
//使用方法进入游戏后开始游戏F12打开控制台输入core.towebp(image),image为已在全塔属性中注册过的图片名字需要""括起来
this.towebp = function (image) {
const canvas = document.createElement("canvas"); //背景画布设置
const ctx = canvas.getContext("2d");
const img = core.material.images.images[image];
canvas.width = img.width;
canvas.height = img.height;
ctx.drawImage(img, 0, 0);
const webpDataURL = canvas.toDataURL("image/webp", 0.85); //第二个参数为画面质量范围为0-1,1为无损
console.log(webpDataURL);
const link = document.createElement("a");
link.href = webpDataURL;
const name = image.substring(0, image.indexOf("."));
link.download = name + ".webp";
link.click();
};
this.towebpall = function () {
const canvas = document.createElement("canvas"); //背景画布设置
const ctx = canvas.getContext("2d");
for (const image in core.material.images.images) {
this.towebp(image);
}
};
this.towebpsome = function (images) {
images.forEach((image) => {
core.towebp(image);
});
};
},
2025-01-18 17:49:44 +08:00
"帧动画特效(游戏界面)": function () {
2025-01-16 13:17:19 +08:00
// 在此增加新插件
const animate2 = document.createElement("canvas"); //画布设置
animate2.style.zIndex = 71;
animate2.id = "animate2";
animate2.classList.add("gameCanvas", "anti-aliasing");
animate2.style.display = "block";
animate2.width = 416;
animate2.height = 416;
animate2.style.width = core.__PIXELS__ * core.domStyle.scale + "px";
animate2.style.height = core.__PIXELS__ * core.domStyle.scale + "px";
main.dom.animate2 = animate2;
const anctx = animate2.getContext("2d");
main.dom.gameDraw.appendChild(animate2);
core.plugin.playing = new Set();
this.setanimate = function (
name,
px,
py,
width,
height,
allFarme,
imageList,
soundList
) {
const data = {
px: px,
py: py,
width: width,
height: height,
allFarme: allFarme,
imageList: imageList,
soundList: soundList,
};
core.setFlag("animate_" + name, data);
};
this.deleteanimate = function (name) {
core.setFlag("animate_" + name);
};
let thistime = 0;
this.playanimate = function (name, x, y, hero, scalex, scaley) {
const data = {
name: name,
x: x,
y: y,
hero: hero,
scalex: scalex,
scaley: scaley,
farme: 0,
};
2025-01-06 17:00:58 +08:00
2025-01-16 13:17:19 +08:00
core.plugin.playing.add(data);
};
core.registerAnimationFrame("animateonmap", true, function (timestamp) {
if (timestamp - thistime > 1000 / 60) {
thistime = timestamp;
core.clearMap(anctx);
core.plugin.playing.forEach((one) => {
const data = flags["animate_" + one.name];
if (!data) {
core.plugin.playing.delete(one);
} else {
data.imageList.forEach(function (image) {
if (
one.farme >= (image.beforefarme ?? 0) &&
one.farme <= (image.afterfarme ?? data.allFarme)
) {
const img = core.material.images.images?.[image.image];
if (img) {
const gla = image.globalAlpha ?? 100;
const agla = image.aglobalAlpha ?? gla,
beforefarme = image.beforefarme ?? 0;
const afterfarme = image.afterfarme ?? data.allFarme;
anctx.globalAlpha =
(gla +
((agla - gla) * (one.farme - beforefarme)) /
(afterfarme - beforefarme || 1)) /
100;
const cx =
(image.cx ?? 0) +
(((image.acx ?? 0) - (image.cx ?? 0)) *
(one.farme - beforefarme)) /
(afterfarme - beforefarme || 1),
cy =
(image.cy ?? 0) +
(((image.acy ?? 0) - (image.cy ?? 0)) *
(one.farme - beforefarme)) /
(afterfarme - beforefarme || 1),
cw =
(image.cw ?? img.width) +
(((image.acw ?? img.width) - (image.cw ?? img.width)) *
(one.farme - beforefarme)) /
(afterfarme - beforefarme || 1),
ch =
(image.ch ?? img.height) +
(((image.acw ?? img.height) - (image.cw ?? img.height)) *
(one.farme - beforefarme)) /
(afterfarme - beforefarme || 1),
x =
(image.x ?? 0) +
(((image.ax ?? 0) - (image.x ?? 0)) *
(one.farme - beforefarme)) /
(afterfarme - beforefarme || 1),
y =
(image.y ?? 0) +
(((image.ay ?? 0) - (image.y ?? 0)) *
(one.farme - beforefarme)) /
(afterfarme - beforefarme || 1),
w =
(image.w ?? one.width) +
(((image.aw ?? one.width) - (image.w ?? one.width)) *
(one.farme - beforefarme)) /
(afterfarme - beforefarme || 1),
h =
(image.h ?? one.height) +
(((image.aw ?? one.height) - (image.w ?? one.height)) *
(one.farme - beforefarme)) /
(afterfarme - beforefarme || 1),
angle =
(Math.PI * (image.angel ?? 0)) / 180 +
((Math.PI * (image.aangel ?? 0)) / 180 -
((Math.PI * (image.angel ?? 0)) / 180) *
(one.farme - beforefarme)) /
(afterfarme - beforefarme || 1);
if (one.hero) {
let sx, sy;
if (core.status.heroMoving < 0) {
sx = 0;
sy = 0;
} else {
sx =
core.utils.scan[core.status.hero.loc.direction].x *
4 *
core.status.heroMoving;
sy =
core.utils.scan[core.status.hero.loc.direction].y *
4 *
core.status.heroMoving;
}
const herox = core.status.hero.loc.x * 32 + 16 + sx;
const heroy = core.status.hero.loc.y * 32 + 16 + sy;
core.drawImage(
anctx,
img,
cx,
cy,
cw,
ch,
herox + (x - data.px) * one.scalex,
heroy + (y - data.py) * one.scaley,
w * one.scalex,
h * one.scaley,
angle
);
} else {
core.drawImage(
anctx,
img,
cx,
cy,
cw,
ch,
one.x + (x - data.px) * one.scalex,
one.y + (y - data.py) * one.scaley,
w * one.scalex,
h * one.scaley,
angle
);
}
}
}
});
data.soundList.forEach(function (sound) {
const lisen =
sound.sound &&
core.material.sounds[sound.sound] &&
core.musicStatus.soundStatus;
if (one.farme == sound.startfarme && lisen) {
if (sound.stopbefore) core.stopSound();
core.playSound(sound.sound);
}
});
one.farme++;
if (one.farme > data.allFarme) core.plugin.playing.delete(one);
}
});
}
});
2025-01-16 13:28:25 +08:00
},
2025-01-18 17:49:44 +08:00
"音频系统": function () {
// 在此增加新插件
/* libs/thirdparty index.html
<script src="libs/thirdparty/ogg-vorbis-decoder.min.js"></script>
<script src="libs/thirdparty/ogg-opus-decoder.min.js"></script>
<script src="libs/thirdparty/codec-parser.min.js"></script>
*/
2025-01-19 20:20:48 +08:00
// 将__enable置为false将关闭插件
let __enable = true;
if (!__enable || main.mode === "editor") return;
const { OggOpusDecoderWebWorker } = window["ogg-opus-decoder"];
const { OggVorbisDecoderWebWorker } = window["ogg-vorbis-decoder"];
2025-01-18 17:49:44 +08:00
const { CodecParser } = window.CodecParser;
2025-01-19 20:20:48 +08:00
const { Transition, linear } = core.plugin.animate;
2025-01-18 17:49:44 +08:00
const audio = new Audio();
const supportMap = new Map();
const AudioType = {
Mp3: "audio/mpeg",
Wav: 'audio/wav; codecs="1"',
Flac: "audio/flac",
Opus: 'audio/ogg; codecs="opus"',
Ogg: 'audio/ogg; codecs="vorbis"',
Aac: "audio/aac",
};
/**
* 检查一种音频类型是否能被播放
* @param type 音频类型 AudioType
*/
function isAudioSupport(type) {
if (supportMap.has(type)) return supportMap.get(type);
else {
const support = audio.canPlayType(type);
const canPlay = support === "maybe" || support === "probably";
supportMap.set(type, canPlay);
return canPlay;
}
}
const typeMap = new Map([
["ogg", AudioType.Ogg],
["mp3", AudioType.Mp3],
["wav", AudioType.Wav],
["flac", AudioType.Flac],
["opus", AudioType.Opus],
["aac", AudioType.Aac],
]);
/**
* 根据文件名拓展猜测其类型
* @param file 文件名 string
*/
function guessTypeByExt(file) {
const ext = /\.[a-zA-Z\d]+$/.exec(file);
if (!ext?.[0]) return "";
const type = ext[0].slice(1);
return typeMap.get(type.toLocaleLowerCase()) ?? "";
}
isAudioSupport(AudioType.Ogg);
isAudioSupport(AudioType.Mp3);
isAudioSupport(AudioType.Wav);
isAudioSupport(AudioType.Flac);
isAudioSupport(AudioType.Opus);
isAudioSupport(AudioType.Aac);
function isNil(value) {
return value === void 0 || value === null;
}
function sleep(time) {
return new Promise((res) => setTimeout(res, time));
}
class AudioEffect {
constructor(ac) {}
/**
* 连接至其他效果器
* @param target 目标输入 IAudioInput
* @param output 当前效果器输出通道 Number
* @param input 目标效果器的输入通道 Number
*/
connect(target, output, input) {
this.output.connect(target.input, output, input);
}
/**
* 与其他效果器取消连接
* @param target 目标输入 IAudioInput
* @param output 当前效果器输出通道 Number
* @param input 目标效果器的输入通道 Number
*/
disconnect(target, output, input) {
if (!target) {
if (!isNil(output)) {
this.output.disconnect(output);
} else {
this.output.disconnect();
}
} else {
if (!isNil(output)) {
if (!isNil(input)) {
this.output.disconnect(target.input, output, input);
} else {
this.output.disconnect(target.input, output);
}
} else {
this.output.disconnect(target.input);
}
}
}
}
class StereoEffect extends AudioEffect {
constructor(ac) {
super(ac);
const panner = ac.createPanner();
this.input = panner;
this.output = panner;
}
/**
* 设置音频朝向x正方形水平向右y正方形垂直于地面向上z正方向垂直屏幕远离用户
* @param x 朝向x坐标 Number
* @param y 朝向y坐标 Number
* @param z 朝向z坐标 Number
*/
setOrientation(x, y, z) {
this.output.orientationX.value = x;
this.output.orientationY.value = y;
this.output.orientationZ.value = z;
}
/**
* 设置音频位置x正方形水平向右y正方形垂直于地面向上z正方向垂直屏幕远离用户
* @param x 位置x坐标 Number
* @param y 位置y坐标 Number
* @param z 位置z坐标 Number
*/
setPosition(x, y, z) {
this.output.positionX.value = x;
this.output.positionY.value = y;
this.output.positionZ.value = z;
}
end() {}
start() {}
}
class VolumeEffect extends AudioEffect {
constructor(ac) {
super(ac);
const gain = ac.createGain();
this.input = gain;
this.output = gain;
}
/**
* 设置音量大小
* @param volume 音量大小 Number
*/
setVolume(volume) {
this.output.gain.value = volume;
}
/**
* 获取音量大小 Number
*/
getVolume() {
return this.output.gain.value;
}
end() {}
start() {}
}
class ChannelVolumeEffect extends AudioEffect {
/** 所有的音量控制节点 */
constructor(ac) {
super(ac);
/** 所有的音量控制节点 */
this.gain = [];
const splitter = ac.createChannelSplitter();
const merger = ac.createChannelMerger();
this.output = merger;
this.input = splitter;
for (let i = 0; i < 6; i++) {
const gain = ac.createGain();
splitter.connect(gain, i);
gain.connect(merger, 0, i);
this.gain.push(gain);
}
}
/**
* 设置某个声道的音量大小
* @param channel 要设置的声道可填0-5 Number
* @param volume 这个声道的音量大小 Number
*/
setVolume(channel, volume) {
if (!this.gain[channel]) return;
this.gain[channel].gain.value = volume;
}
/**
* 获取某个声道的音量大小可填0-5
* @param channel 要获取的声道 Number
*/
getVolume(channel) {
if (!this.gain[channel]) return 0;
return this.gain[channel].gain.value;
}
end() {}
start() {}
}
class DelayEffect extends AudioEffect {
constructor(ac) {
super(ac);
const delay = ac.createDelay();
this.input = delay;
this.output = delay;
}
/**
* 设置延迟时长
* @param delay 延迟时长单位秒 Number
*/
setDelay(delay) {
this.output.delayTime.value = delay;
}
/**
* 获取延迟时长
*/
getDelay() {
return this.output.delayTime.value;
}
end() {}
start() {}
}
class EchoEffect extends AudioEffect {
constructor(ac) {
super(ac);
const delay = ac.createDelay();
const gain = ac.createGain();
gain.gain.value = 0.5;
delay.delayTime.value = 0.05;
delay.connect(gain);
gain.connect(delay);
/** 延迟节点 */
this.delay = delay;
/** 反馈增益节点 */
this.gainNode = gain;
/** 当前增益 */
this.gain = 0.5;
/** 是否正在播放 */
this.playing = false;
this.input = gain;
this.output = gain;
}
/**
* 设置回声反馈增益大小
* @param gain 增益大小范围 0-1大于等于1的视为0.5小于0的视为0 Number
*/
setFeedbackGain(gain) {
const resolved = gain >= 1 ? 0.5 : gain < 0 ? 0 : gain;
this.gain = resolved;
if (this.playing) this.gainNode.gain.value = resolved;
}
/**
* 设置回声间隔时长
* @param delay 回声时长范围 0.01-Infinity小于0.01的视为0.01 Number
*/
setEchoDelay(delay) {
const resolved = delay < 0.01 ? 0.01 : delay;
this.delay.delayTime.value = resolved;
}
/**
* 获取反馈节点增益
*/
getFeedbackGain() {
return this.gain;
}
/**
* 获取回声间隔时长
*/
getEchoDelay() {
return this.delay.delayTime.value;
}
end() {
this.playing = false;
const echoTime = Math.ceil(Math.log(0.001) / Math.log(this.gain)) + 10;
sleep(this.delay.delayTime.value * echoTime).then(() => {
if (!this.playing) this.gainNode.gain.value = 0;
});
}
start() {
this.playing = true;
this.gainNode.gain.value = this.gain;
}
}
class StreamLoader {
constructor(url) {
/** 传输目标 Set<IStreamReader>*/
this.target = new Set();
this.loading = false;
}
/**
* 将加载流传递给字节流读取对象
* @param reader 字节流读取对象 IStreamReader
*/
pipe(reader) {
if (this.loading) {
console.warn(
"Cannot pipe new StreamReader object when stream is loading."
);
return;
}
this.target.add(reader);
reader.piped(this);
return this;
}
async start() {
if (this.loading) return;
this.loading = true;
const response = await window.fetch(this.url);
const stream = response.body;
if (!stream) {
console.error("Cannot get reader when fetching '" + this.url + "'.");
return;
}
// 获取读取器
/** 读取流对象 */
this.stream = stream;
const reader = response.body?.getReader();
const targets = [...this.target];
2025-01-19 20:20:48 +08:00
await Promise.all(targets.map((v) => v.start(stream, this, response)));
if (reader && reader.read) {
// 开始流传输
while (true) {
const { value, done } = await reader.read();
await Promise.all(
targets.map(v => v.pump(value, done, response))
);
if (done) break;
}
} else {
// 如果不支持流传输
const buffer = await response.arrayBuffer();
const data = new Uint8Array(buffer);
await Promise.all(targets.map(v => v.pump(data, true, response)));
}
2025-01-18 17:49:44 +08:00
// 开始流传输
while (true) {
const { value, done } = await reader.read();
await Promise.all(targets.map((v) => v.pump(value, done, response)));
if (done) break;
}
this.loading = false;
targets.forEach((v) => v.end(true));
2025-01-19 20:20:48 +08:00
//
2025-01-18 17:49:44 +08:00
}
cancel(reason) {
if (!this.stream) return;
this.stream.cancel(reason);
this.loading = false;
this.target.forEach((v) => v.end(false, reason));
}
}
const fileSignatures = [
[AudioType.Mp3, [0x49, 0x44, 0x33]],
[AudioType.Ogg, [0x4f, 0x67, 0x67, 0x53]],
[AudioType.Wav, [52, 0x49, 0x46, 0x46]],
[AudioType.Flac, [0x66, 0x4c, 0x61, 0x43]],
[AudioType.Aac, [0xff, 0xf1]],
2025-01-19 20:20:48 +08:00
[AudioType.Aac, [0xff, 0xf9]]
2025-01-18 17:49:44 +08:00
];
const oggHeaders = [
2025-01-19 20:20:48 +08:00
[AudioType.Opus, [0x4f, 0x70, 0x75, 0x73, 0x48, 0x65, 0x61, 0x64]]
2025-01-18 17:49:44 +08:00
];
2025-01-19 20:20:48 +08:00
function checkAudioType(data) {
let audioType = '';
// 检查头文件获取音频类型仅检查前256个字节
const toCheck = data.slice(0, 256);
for (const [type, value] of fileSignatures) {
if (value.every((v, i) => toCheck[i] === v)) {
audioType = type;
break;
}
}
if (audioType === AudioType.Ogg) {
// 如果是ogg的话进一步判断是不是opus
for (const [key, value] of oggHeaders) {
const has = toCheck.some((_, i) => {
return value.every((v, ii) => toCheck[i + ii] === v);
});
if (has) {
audioType = key;
break;
}
}
}
return audioType;
}
2025-01-18 17:49:44 +08:00
const mimeTypeMap = {
[AudioType.Aac]: "audio/aac",
[AudioType.Flac]: "audio/flac",
[AudioType.Mp3]: "audio/mpeg",
[AudioType.Ogg]: "application/ogg",
[AudioType.Opus]: "application/ogg",
[AudioType.Wav]: "application/ogg",
};
function isOggPage(data) {
return !isNil(data.isFirstPage);
}
class AudioStreamSource {
constructor(context) {
this.output = context.createBufferSource();
/** 是否已经完全加载完毕 */
this.loaded = false;
2025-01-19 20:20:48 +08:00
2025-01-18 17:49:44 +08:00
/** 已经缓冲了多长时间,如果缓冲完那么跟歌曲时长一致 */
this.buffered = 0;
/** 已经缓冲的采样点数量 */
this.bufferedSamples = 0;
/** 歌曲时长,加载完毕之前保持为 0 */
this.duration = 0;
/** 在流传输阶段,至少缓冲多长时间的音频之后才开始播放,单位秒 */
this.bufferPlayDuration = 1;
/** 音频的采样率,未成功解析出之前保持为 0 */
this.sampleRate = 0;
//是否循环播放
this.loop = false;
2025-01-19 20:20:48 +08:00
/** 上一次播放是从何时开始的 */
this.lastStartWhen = 0;
2025-01-18 17:49:44 +08:00
/** 开始播放时刻 */
this.lastStartTime = 0;
/** 上一次播放的缓存长度 */
this.lastBufferSamples = 0;
/** 是否已经获取到头文件 */
this.headerRecieved = false;
/** 音频类型 */
this.audioType = "";
/** 每多长时间组成一个缓存 Float32Array */
this.bufferChunkSize = 10;
/** 缓存音频数据,每 bufferChunkSize 秒钟组成一个 Float32Array用于流式解码 */
this.audioData = [];
this.errored = false;
2025-01-19 20:20:48 +08:00
this.ac = context;
}
/** 当前已经播放了多长时间 */
get currentTime() {
return this.ac.currentTime - this.lastStartTime + this.lastStartWhen;
2025-01-18 17:49:44 +08:00
}
/**
* 设置每个缓存数据的大小默认为10秒钟一个缓存数据
* @param size 每个缓存数据的时长单位秒
*/
setChunkSize(size) {
if (this.controller?.loading || this.loaded) return;
this.bufferChunkSize = size;
}
piped(controller) {
this.controller = controller;
}
async pump(data, done) {
if (!data || this.errored) return;
if (!this.headerRecieved) {
// 检查头文件获取音频类型仅检查前256个字节
const toCheck = data.slice(0, 256);
2025-01-19 20:20:48 +08:00
this.audioType = checkAudioType(data);
2025-01-18 17:49:44 +08:00
if (!this.audioType) {
console.error(
"Unknown audio type. Header: '" + [...toCheck]
.map((v) => v.toString().padStart(2, "0"))
.join(" ")
.toUpperCase() +
"'"
);
return;
}
// 创建解码器
2025-01-19 20:20:48 +08:00
const Decoder = AudioDecoder.decoderMap.get(this.audioType);
2025-01-18 17:49:44 +08:00
if (!Decoder) {
this.errored = true;
console.error(
"Cannot decode stream source type of '" +
this.audioType +
"', since there is no registered decoder for that type."
);
return Promise.reject(
`Cannot decode stream source type of '${this.audioType}', since there is no registered decoder for that type.`
);
}
this.decoder = new Decoder();
// 创建数据解析器
const mime = mimeTypeMap[this.audioType];
const parser = new CodecParser(mime);
this.parser = parser;
await this.decoder.create();
this.headerRecieved = true;
}
const decoder = this.decoder;
const parser = this.parser;
if (!decoder || !parser) {
this.errored = true;
return Promise.reject(
"No parser or decoder attached in this AudioStreamSource"
);
}
await this.decodeData(data, decoder, parser);
if (done) await this.decodeFlushData(decoder, parser);
this.checkBufferedPlay();
}
/**
* 检查采样率如果还未解析出采样率那么将设置采样率如果当前采样率与之前不同那么发出警告
*/
checkSampleRate(info) {
for (const one of info) {
const frame = isOggPage(one) ? one.codecFrames[0] : one;
if (frame) {
const rate = frame.header.sampleRate;
if (this.sampleRate === 0) {
this.sampleRate = rate;
break;
} else {
if (rate !== this.sampleRate) {
console.warn("Sample rate in stream audio must be constant.");
}
}
}
}
}
/**
* 解析音频数据
*/
async decodeData(data, decoder, parser) {
// 解析音频数据
const audioData = await decoder.decode(data);
if (!audioData) return;
// @ts-expect-error 库类型声明错误
const audioInfo = [...parser.parseChunk(data)];
// 检查采样率
this.checkSampleRate(audioInfo);
// 追加音频数据
this.appendDecodedData(audioData, audioInfo);
}
/**
* 解码剩余数据
*/
async decodeFlushData(decoder, parser) {
const audioData = await decoder.flush();
if (!audioData) return;
// @ts-expect-error 库类型声明错误
const audioInfo = [...parser.flush()];
this.checkSampleRate(audioInfo);
this.appendDecodedData(audioData, audioInfo);
}
/**
* 追加音频数据
*/
appendDecodedData(data, info) {
const channels = data.channelData.length;
if (channels === 0) return;
if (this.audioData.length !== channels) {
this.audioData = [];
for (let i = 0; i < channels; i++) {
this.audioData.push([]);
}
}
// 计算出应该放在哪
const chunk = this.sampleRate * this.bufferChunkSize;
const sampled = this.bufferedSamples;
const pushIndex = Math.floor(sampled / chunk);
const bufferIndex = sampled % chunk;
const dataLength = data.channelData[0].length;
let buffered = 0;
let nowIndex = pushIndex;
let toBuffer = bufferIndex;
while (buffered < dataLength) {
const rest = toBuffer !== 0 ? chunk - bufferIndex : chunk;
for (let i = 0; i < channels; i++) {
const audioData = this.audioData[i];
if (!audioData[nowIndex]) {
audioData.push(new Float32Array(chunk));
}
const toPush = data.channelData[i].slice(buffered, buffered + rest);
audioData[nowIndex].set(toPush, toBuffer);
}
buffered += rest;
nowIndex++;
toBuffer = 0;
}
this.buffered +=
info.reduce((prev, curr) => prev + curr.duration, 0) / 1000;
this.bufferedSamples += info.reduce(
(prev, curr) => prev + curr.samples,
0
);
}
/**
* 检查已缓冲内容并在未开始播放时播放
*/
checkBufferedPlay() {
if (this.playing || this.sampleRate === 0) return;
const played = this.lastBufferSamples / this.sampleRate;
const dt = this.buffered - played;
if (this.loaded) {
this.playAudio(played);
return;
}
if (dt < this.bufferPlayDuration) return;
2025-01-19 20:20:48 +08:00
2025-01-18 17:49:44 +08:00
this.lastBufferSamples = this.bufferedSamples;
// 需要播放
this.mergeBuffers();
if (!this.buffer) return;
if (this.playing) this.output.stop();
this.createSourceNode(this.buffer);
this.output.loop = false;
this.output.start(0, played);
this.lastStartTime = this.ac.currentTime;
this.playing = true;
this.output.addEventListener("ended", () => {
this.playing = false;
this.checkBufferedPlay();
});
}
mergeBuffers() {
const buffer = this.ac.createBuffer(
this.audioData.length,
this.bufferedSamples,
this.sampleRate
);
const chunk = this.sampleRate * this.bufferChunkSize;
const bufferedChunks = Math.floor(this.bufferedSamples / chunk);
const restLength = this.bufferedSamples % chunk;
for (let i = 0; i < this.audioData.length; i++) {
const audio = this.audioData[i];
const data = new Float32Array(this.bufferedSamples);
for (let j = 0; j < bufferedChunks; j++) {
data.set(audio[j], chunk * j);
}
if (restLength !== 0) {
data.set(
audio[bufferedChunks].slice(0, restLength),
chunk * bufferedChunks
);
}
buffer.copyToChannel(data, i, 0);
}
this.buffer = buffer;
}
async start() {
delete this.buffer;
this.headerRecieved = false;
this.audioType = "";
this.errored = false;
this.buffered = 0;
this.sampleRate = 0;
this.bufferedSamples = 0;
this.duration = 0;
this.loaded = false;
if (this.playing) this.output.stop();
this.playing = false;
this.lastStartTime = this.ac.currentTime;
}
end(done, reason) {
if (done && this.buffer) {
this.loaded = true;
delete this.controller;
this.mergeBuffers();
2025-01-19 20:20:48 +08:00
2025-01-18 17:49:44 +08:00
this.duration = this.buffered;
this.audioData = [];
this.decoder?.destroy();
delete this.decoder;
delete this.parser;
} else {
console.warn(
"Unexpected end when loading stream audio, reason: '" +
(reason ?? "") +
"'"
);
}
}
playAudio(when) {
if (!this.buffer) return;
this.lastStartTime = this.ac.currentTime;
if (this.playing) this.output.stop();
2025-01-19 20:20:48 +08:00
2025-01-18 17:49:44 +08:00
this.createSourceNode(this.buffer);
this.output.start(0, when);
this.playing = true;
2025-01-19 20:20:48 +08:00
2025-01-18 17:49:44 +08:00
this.output.addEventListener("ended", () => {
this.playing = false;
2025-01-19 20:20:48 +08:00
2025-01-18 17:49:44 +08:00
if (this.loop && !this.output.loop) this.play(0);
});
}
/**
* 开始播放这个音频源
*/
play(when) {
if (this.playing || this.errored) return;
if (this.loaded && this.buffer) {
this.playing = true;
this.playAudio(when);
} else {
this.controller?.start();
}
}
createSourceNode(buffer) {
if (!this.target) return;
const node = this.ac.createBufferSource();
node.buffer = buffer;
if (this.playing) this.output.stop();
this.playing = false;
this.output = node;
node.connect(this.target.input);
node.loop = this.loop;
}
/**
* 停止播放这个音频源
* @returns 音频暂停的时刻 number
*/
stop() {
if (this.playing) this.output.stop();
this.playing = false;
return this.ac.currentTime - this.lastStartTime;
}
/**
* 连接到音频路由图上每次调用播放的时候都会执行一次
* @param target 连接至的目标 IAudioInput
*/
connect(target) {
this.target = target;
}
/**
* 设置是否循环播放
* @param loop 是否循环 boolean)
*/
setLoop(loop) {
this.loop = loop;
}
}
class AudioElementSource {
constructor(context) {
const audio = new Audio();
2025-01-19 20:20:48 +08:00
audio.preload = "none";
2025-01-18 17:49:44 +08:00
this.output = context.createMediaElementSource(audio);
this.audio = audio;
2025-01-19 20:20:48 +08:00
audio.addEventListener("play", () => {
2025-01-18 17:49:44 +08:00
this.playing = true;
});
2025-01-19 20:20:48 +08:00
audio.addEventListener("ended", () => {
2025-01-18 17:49:44 +08:00
this.playing = false;
2025-01-19 20:20:48 +08:00
this.ac = context;
2025-01-18 17:49:44 +08:00
});
}
2025-01-19 20:20:48 +08:00
get duration() {
return this.audio.duration;
}
get currentTime() {
return this.audio.currentTime;
}
2025-01-18 17:49:44 +08:00
/**
* 设置音频源的路径
* @param url 音频路径
*/
setSource(url) {
this.audio.src = url;
}
play(when = 0) {
if (this.playing) return;
this.audio.currentTime = when;
this.audio.play();
}
stop() {
this.audio.pause();
this.playing = false;
2025-01-19 20:20:48 +08:00
2025-01-18 17:49:44 +08:00
return this.audio.currentTime;
}
connect(target) {
this.output.connect(target.input);
}
setLoop(loop) {
this.audio.loop = loop;
}
}
class AudioBufferSource {
constructor(context) {
this.output = context.createBufferSource();
/** 是否循环 */
this.loop = false;
2025-01-19 20:20:48 +08:00
/** 上一次播放是从何时开始的 */
this.lastStartWhen = 0;
2025-01-18 17:49:44 +08:00
/** 播放开始时刻 */
this.lastStartTime = 0;
2025-01-19 20:20:48 +08:00
this.duration = 0;
this.ac = context;
}
get currentTime() {
return this.ac.currentTime - this.lastStartTime + this.lastStartWhen;
2025-01-18 17:49:44 +08:00
}
/**
* 设置音频源数据
* @param buffer 音频源可以是未解析的 ArrayBuffer也可以是已解析的 AudioBuffer
*/
async setBuffer(buffer) {
if (buffer instanceof ArrayBuffer) {
this.buffer = await this.ac.decodeAudioData(buffer);
} else {
this.buffer = buffer;
}
2025-01-19 20:20:48 +08:00
this.duration = this.buffer.duration;
2025-01-18 17:49:44 +08:00
}
play(when) {
if (this.playing || !this.buffer) return;
this.playing = true;
this.lastStartTime = this.ac.currentTime;
2025-01-19 20:20:48 +08:00
2025-01-18 17:49:44 +08:00
this.createSourceNode(this.buffer);
this.output.start(0, when);
2025-01-19 20:20:48 +08:00
this.output.addEventListener("ended", () => {
2025-01-18 17:49:44 +08:00
this.playing = false;
if (this.loop && !this.output.loop) this.play(0);
});
}
createSourceNode(buffer) {
if (!this.target) return;
const node = this.ac.createBufferSource();
node.buffer = buffer;
this.output = node;
node.connect(this.target.input);
node.loop = this.loop;
}
stop() {
this.output.stop();
return this.ac.currentTime - this.lastStartTime;
}
connect(target) {
this.target = target;
}
setLoop(loop) {
this.loop = loop;
}
}
class AudioPlayer {
constructor() {
/** 音频播放上下文 */
this.ac = new AudioContext();
/** 音量节点 */
this.gain = this.ac.createGain();
this.gain.connect(this.ac.destination);
2025-01-19 20:20:48 +08:00
this.audioRoutes = new Map();
}
/**
* 解码音频数据
* @param data 音频数据
*/
decodeAudioData(data) {
return AudioDecoder.decodeAudioData(data, this);
2025-01-18 17:49:44 +08:00
}
/**
* 设置音量
* @param volume 音量
*/
setVolume(volume) {
this.gain.gain.value = volume;
}
/**
* 获取音量
*/
getVolume() {
return this.gain.gain.value;
}
/**
* 创建一个音频源
* @param Source 音频源类
*/
createSource(Source) {
return new Source(this.ac);
}
/**
* 创建一个兼容流式音频源可以与流式加载相结合主要用于处理 opus ogg 不兼容的情况
*/
createStreamSource() {
return new AudioStreamSource(this.ac);
}
/**
* 创建一个通过 audio 元素播放的音频源
*/
createElementSource() {
return new AudioElementSource(this.ac);
}
/**
* 创建一个通过 AudioBuffer 播放的音频源
*/
createBufferSource() {
return new AudioBufferSource(this.ac);
}
/**
* 获取音频目的地
*/
getDestination() {
return this.gain;
}
/**
* 创建一个音频效果器
* @param Effect 效果器类
*/
createEffect(Effect) {
return new Effect(this.ac);
}
/**
* 创建一个修改音量的效果器
* ```txt
* |----------|
* Input ----> | GainNode | ----> Output
* |----------|
* ```
*/
createVolumeEffect() {
return new VolumeEffect(this.ac);
}
/**
* 创建一个立体声效果器
* ```txt
* |------------|
* Input ----> | PannerNode | ----> Output
* |------------|
* ```
*/
createStereoEffect() {
return new StereoEffect(this.ac);
}
/**
* 创建一个修改单个声道音量的效果器
* ```txt
* |----------|
* -> | GainNode | \
* |--------------| / |----------| -> |------------|
* Input ----> | SplitterNode | ...... | MergerNode | ----> Output
* |--------------| \ |----------| -> |------------|
* -> | GainNode | /
* |----------|
* ```
*/
createChannelVolumeEffect() {
return new ChannelVolumeEffect(this.ac);
}
/**
* 创建一个延迟效果器
* |-----------|
* Input ----> | DelayNode | ----> Output
* |-----------|
*/
createDelay() {
return new DelayEffect(this.ac);
}
/**
* 创建一个回声效果器
* ```txt
* |----------|
* Input ----> | GainNode | ----> Output
* ^ |----------| |
* | |
* | |------------|
* |-- | Delay Node | <--
* |------------|
* ```
*/
createEchoEffect() {
return new EchoEffect(this.ac);
}
/**
* 创建一个音频播放路由
* @param source 音频源
*/
createRoute(source) {
return new AudioRoute(source, this);
}
/**
* 添加一个音频播放路由可以直接被播放
* @param id 这个音频播放路由的名称
* @param route 音频播放路由对象
*/
addRoute(id, route) {
2025-01-19 20:20:48 +08:00
if (!this.audioRoutes) this.audioRoutes = new Map();
2025-01-18 17:49:44 +08:00
if (this.audioRoutes.has(id)) {
console.warn(
"Audio route with id of '" +
id +
"' has already existed. New route will override old route."
);
}
this.audioRoutes.set(id, route);
}
/**
* 根据名称获取音频播放路由对象
* @param id 音频播放路由的名称
*/
2025-01-19 20:20:48 +08:00
getRoute(id) {
return this.audioRoutes.get(id);
}
/**
* 移除一个音频播放路由
* @param id 要移除的播放路由的名称
*/
removeRoute(id) {
this.audioRoutes.delete(id);
2025-01-18 17:49:44 +08:00
}
/**
* 播放音频
* @param id 音频名称
* @param when 从音频的哪个位置开始播放单位秒
*/
play(id, when) {
2025-01-19 20:20:48 +08:00
const route = this.getRoute(id);
if (!route) {
console.warn(
"Cannot play audio route '" +
id +
"', since there is not added route named it."
);
return;
}
route.play(when);
2025-01-18 17:49:44 +08:00
}
/**
* 暂停音频播放
* @param id 音频名称
* @returns 当音乐真正停止时兑现
*/
pause(id) {
const route = this.getRoute(id);
2025-01-19 20:20:48 +08:00
if (!route) {
console.warn(
"Cannot pause audio route '" +
id +
"', since there is not added route named it."
);
return;
}
return route.pause();
2025-01-18 17:49:44 +08:00
}
/**
* 停止音频播放
* @param id 音频名称
* @returns 当音乐真正停止时兑现
*/
stop(id) {
const route = this.getRoute(id);
2025-01-19 20:20:48 +08:00
if (!route) {
console.warn(
"Cannot stop audio route '" +
id +
"', since there is not added route named it."
);
return;
}
return route.stop();
2025-01-18 17:49:44 +08:00
}
/**
* 继续音频播放
* @param id 音频名称
*/
resume(id) {
2025-01-19 20:20:48 +08:00
const route = this.getRoute(id);
if (!route) {
console.warn(
"Cannot pause audio route '" +
id +
"', since there is not added route named it."
);
return;
}
route.resume();
2025-01-18 17:49:44 +08:00
}
/**
2025-01-19 20:20:48 +08:00
* 设置听者位置x正方向水平向右y正方向垂直于地面向上z正方向垂直屏幕远离用户
2025-01-18 17:49:44 +08:00
* @param x 位置x坐标
* @param y 位置y坐标
* @param z 位置z坐标
*/
setListenerPosition(x, y, z) {
const listener = this.ac.listener;
listener.positionX.value = x;
listener.positionY.value = y;
listener.positionZ.value = z;
}
/**
2025-01-19 20:20:48 +08:00
* 设置听者朝向x正方向水平向右y正方向垂直于地面向上z正方向垂直屏幕远离用户
2025-01-18 17:49:44 +08:00
* @param x 朝向x坐标
* @param y 朝向y坐标
* @param z 朝向z坐标
*/
setListenerOrientation(x, y, z) {
const listener = this.ac.listener;
listener.forwardX.value = x;
listener.forwardY.value = y;
listener.forwardZ.value = z;
}
/**
2025-01-19 20:20:48 +08:00
* 设置听者头顶朝向x正方向水平向右y正方向垂直于地面向上z正方向垂直屏幕远离用户
2025-01-18 17:49:44 +08:00
* @param x 头顶朝向x坐标
* @param y 头顶朝向y坐标
* @param z 头顶朝向z坐标
*/
setListenerUp(x, y, z) {
const listener = this.ac.listener;
listener.upX.value = x;
listener.upY.value = y;
listener.upZ.value = z;
}
}
2025-01-19 20:20:48 +08:00
const AudioStatus = {
Playing: 0,
Pausing: 1,
Paused: 2,
Stoping: 3,
Stoped: 4,
};
2025-01-18 17:49:44 +08:00
const AudioRouteEvent = {
updateEffect: [],
play: [],
stop: [],
pause: [],
resume: [],
};
class AudioRoute {
constructor(source, player) {
this.output = source.output;
/** 效果器路由图 */
this.effectRoute = [];
/** 结束时长,当音频暂停或停止时,会经过这么长时间之后才真正终止播放,期间可以做音频淡入淡出等效果 */
this.endTime = 0;
2025-01-19 20:20:48 +08:00
/** 暂停时播放了多长时间 */
this.pauseCurrentTime = 0;
/** 当前播放状态 */
this.status = AudioStatus.Stoped;
this.shouldStop = false;
/**
* 每次暂停或停止时自增用于判断当前正在处理的情况
* 假如暂停后很快播放然后很快暂停那么需要根据这个来判断实际是否应该执行暂停后操作
*/
this.stopIdentifier = 0;
2025-01-18 17:49:44 +08:00
/** 暂停时刻 */
this.pauseTime = 0;
2025-01-19 20:20:48 +08:00
this.source = source;
this.player = player;
}
/** 音频时长,单位秒 */
get duration() {
return this.source.duration;
}
/** 当前播放了多长时间,单位秒 */
get currentTime() {
if (this.status === AudioStatus.Paused) {
return this.pauseCurrentTime;
} else {
return this.source.currentTime;
}
}
set currentTime(time) {
this.source.stop();
this.source.play(time);
2025-01-18 17:49:44 +08:00
}
/**
* 设置结束时间暂停或停止时会经过这么长时间才终止音频的播放这期间可以做一下音频淡出的效果
* @param time 暂停或停止时经过多长时间之后才会结束音频的播放
*/
setEndTime(time) {
this.endTime = time;
}
/**
* 当音频播放时执行的函数可以用于音频淡入效果
* @param fn 音频开始播放时执行的函数
*/
onStart(fn) {
this.audioStartHook = fn;
}
/**
* 当音频暂停或停止时执行的函数可以用于音频淡出效果
* @param fn 音频在暂停或停止时执行的函数不填时表示取消这个钩子
* 包含两个参数第一个参数是结束时长第二个参数是当前音频播放路由对象
*/
onEnd(fn) {
this.audioEndHook = fn;
}
/**
* 开始播放这个音频
* @param when 从音频的什么时候开始播放单位秒
*/
play(when = 0) {
2025-01-19 20:20:48 +08:00
if (this.status === AudioStatus.Playing) return;
2025-01-18 17:49:44 +08:00
this.link();
if (this.effectRoute.length > 0) {
const first = this.effectRoute[0];
this.source.connect(first);
2025-01-19 20:20:48 +08:00
const last = this.effectRoute.at(-1);
last.connect({ input: this.player.getDestination() });
2025-01-18 17:49:44 +08:00
} else {
this.source.connect({ input: this.player.getDestination() });
}
this.source.play(when);
2025-01-19 20:20:48 +08:00
this.status = AudioStatus.Playing;
2025-01-18 17:49:44 +08:00
this.pauseTime = 0;
this.audioStartHook?.(this);
this.startAllEffect();
}
/**
* 暂停音频播放
*/
async pause() {
2025-01-19 20:20:48 +08:00
if (this.status !== AudioStatus.Playing) return;
this.status = AudioStatus.Pausing;
this.stopIdentifier++;
const identifier = this.stopIdentifier;
2025-01-18 17:49:44 +08:00
if (this.audioEndHook) {
this.audioEndHook(this.endTime, this);
await sleep(this.endTime);
}
2025-01-19 20:20:48 +08:00
if (
this.status !== AudioStatus.Pausing ||
this.stopIdentifier !== identifier
) {
return;
}
this.pauseCurrentTime = this.source.currentTime;
2025-01-18 17:49:44 +08:00
const time = this.source.stop();
this.pauseTime = time;
2025-01-19 20:20:48 +08:00
if (this.shouldStop) {
this.status = AudioStatus.Stoped;
this.endAllEffect();
2025-01-18 17:49:44 +08:00
2025-01-19 20:20:48 +08:00
this.shouldStop = false;
} else {
this.status = AudioStatus.Paused;
this.endAllEffect();
}
this.endAllEffect();
2025-01-18 17:49:44 +08:00
}
/**
* 继续音频播放
*/
resume() {
2025-01-19 20:20:48 +08:00
if (this.status === AudioStatus.Playing) return;
if (
this.status === AudioStatus.Pausing ||
this.status === AudioStatus.Stoping
) {
this.audioStartHook?.(this);
return;
}
if (this.status === AudioStatus.Paused) {
2025-01-18 17:49:44 +08:00
this.play(this.pauseTime);
} else {
this.play(0);
}
2025-01-19 20:20:48 +08:00
this.status = AudioStatus.Playing;
2025-01-18 17:49:44 +08:00
this.pauseTime = 0;
this.audioStartHook?.(this);
this.startAllEffect();
}
/**
* 停止音频播放
*/
async stop() {
2025-01-19 20:20:48 +08:00
if (this.status !== AudioStatus.Playing) {
if (this.status === AudioStatus.Pausing) {
this.shouldStop = true;
}
return;
}
this.status = AudioStatus.Stoping;
this.stopIdentifier++;
const identifier = this.stopIdentifier;
2025-01-18 17:49:44 +08:00
if (this.audioEndHook) {
this.audioEndHook(this.endTime, this);
await sleep(this.endTime);
}
2025-01-19 20:20:48 +08:00
if (
this.status !== AudioStatus.Stoping ||
this.stopIdentifier !== identifier
) {
return;
}
2025-01-18 17:49:44 +08:00
this.source.stop();
2025-01-19 20:20:48 +08:00
this.status = AudioStatus.Stoped;
2025-01-18 17:49:44 +08:00
this.pauseTime = 0;
this.endAllEffect();
}
/**
* 添加效果器
* @param effect 要添加的效果可以是数组表示一次添加多个
* @param index 从哪个位置开始添加如果大于数组长度那么加到末尾如果小于0那么将会从后面往前数默认添加到末尾
*/
addEffect(effect, index) {
if (isNil(index)) {
if (effect instanceof Array) {
this.effectRoute.push(...effect);
} else {
this.effectRoute.push(effect);
}
} else {
if (effect instanceof Array) {
this.effectRoute.splice(index, 0, ...effect);
} else {
this.effectRoute.splice(index, 0, effect);
}
}
this.setOutput();
if (this.source.playing) this.link();
}
/**
* 移除一个效果器
* @param effect 要移除的效果
*/
removeEffect(effect) {
const index = this.effectRoute.indexOf(effect);
if (index === -1) return;
this.effectRoute.splice(index, 1);
effect.disconnect();
this.setOutput();
if (this.source.playing) this.link();
}
setOutput() {
const effect = this.effectRoute.at(-1);
if (!effect) this.output = this.source.output;
else this.output = effect.output;
}
/**
* 连接音频路由图
*/
link() {
this.effectRoute.forEach((v) => v.disconnect());
this.effectRoute.forEach((v, i) => {
const next = this.effectRoute[i + 1];
if (next) {
v.connect(next);
}
});
}
startAllEffect() {
this.effectRoute.forEach((v) => v.start());
}
endAllEffect() {
this.effectRoute.forEach((v) => v.end());
}
}
2025-01-19 20:20:48 +08:00
const audioPlayer = new AudioPlayer()
class AudioDecoder {
/**
* 注册一个解码器
* @param type 要注册的解码器允许解码的类型
* @param decoder 解码器对象
*/
static registerDecoder(type, decoder) {
if (!this.decoderMap) this.decoderMap = new Map();
if (this.decoderMap.has(type)) {
console.warn(
"Audio stream decoder for audio type '" +
type +
"' has already existed."
);
return;
}
this.decoderMap.set(type, decoder);
}
/**
* 解码音频数据
* @param data 音频文件数据
* @param player AudioPlayer实例
*/
static async decodeAudioData(data, player) {
// 检查头文件获取音频类型仅检查前256个字节
const toCheck = data.slice(0, 256);
const type = checkAudioType(data);
if (type === "") {
console.error(
"Unknown audio type. Header: '" + [...toCheck]
.map((v) => v.toString().padStart(2, "0"))
.join(" ")
.toUpperCase() +
"'"
);
return null;
}
if (isAudioSupport(type)) {
if (data.buffer instanceof ArrayBuffer) {
return player.ac.decodeAudioData(data.buffer);
} else {
return null;
}
} else {
const Decoder = this.decoderMap.get(type);
if (!Decoder) {
return null;
} else {
const decoder = new Decoder();
await decoder.create();
const decodedData = await decoder.decode(data);
if (!decodedData) return null;
const buffer = player.ac.createBuffer(
decodedData.channelData.length,
decodedData.channelData[0].length,
decodedData.sampleRate
);
decodedData.channelData.forEach((v, i) => {
buffer.copyToChannel(v, i);
});
return buffer;
}
}
}
}
2025-01-18 17:49:44 +08:00
class VorbisDecoder {
/**
* 创建音频解码器
*/
async create() {
2025-01-19 20:20:48 +08:00
this.decoder = new OggVorbisDecoderWebWorker();
2025-01-18 17:49:44 +08:00
await this.decoder.ready;
}
/**
* 摧毁这个解码器
*/
destroy() {
this.decoder?.free();
}
/**
* 解码流数据
* @param data 流数据
*/
async decode(data) {
return this.decoder?.decode(data);
}
2025-01-19 20:20:48 +08:00
async decodeAll(data) {
return this.decoder?.decodeFile(data);
}
2025-01-18 17:49:44 +08:00
/**
* 当音频解码完成后会调用此函数需要返回之前还未解析或未返回的音频数据调用后该解码器将不会被再次使用
*/
async flush() {
2025-01-19 20:20:48 +08:00
return this.decoder?.flush();
2025-01-18 17:49:44 +08:00
}
}
class OpusDecoder {
/**
* 创建音频解码器
*/
async create() {
2025-01-19 20:20:48 +08:00
this.decoder = new OggOpusDecoderWebWorker();
2025-01-18 17:49:44 +08:00
await this.decoder.ready;
}
/**
* 摧毁这个解码器
*/
destroy() {
this.decoder?.free();
}
/**
* 解码流数据
* @param data 流数据
*/
async decode(data) {
return this.decoder?.decode(data);
}
2025-01-19 20:20:48 +08:00
async decodeAll(data) {
return this.decoder?.decodeFile(data);
}
2025-01-18 17:49:44 +08:00
/**
* 当音频解码完成后会调用此函数需要返回之前还未解析或未返回的音频数据调用后该解码器将不会被再次使用
*/
async flush() {
return await this.decoder?.flush();
}
}
2025-01-19 20:20:48 +08:00
class BgmController {
constructor(player) {
this.mainGain = player.createVolumeEffect();
this.player = player;
/** bgm音频名称的前缀 */
this.prefix = "bgms.";
/** 每个 bgm 的音量控制器 */
this.gain = new Map();
/** 正在播放的 bgm */
this.playingBgm = "";
/** 是否正在播放 */
this.playing = false;
2025-01-18 17:49:44 +08:00
2025-01-19 20:20:48 +08:00
/** 是否已经启用 */
this.enabled = true;
/** 是否屏蔽所有的音乐切换 */
this.blocking = false;
/** 渐变时长 */
this.transitionTime = 2000;
}
2025-01-18 17:49:44 +08:00
2025-01-19 20:20:48 +08:00
/**
* 设置音频渐变时长
* @param time 渐变时长
*/
setTransitionTime(time) {
this.transitionTime = time;
for (const [, value] of this.gain) {
value.transition.time(time);
}
}
/**
* 屏蔽音乐切换
*/
blockChange() {
this.blocking = true;
}
/**
* 取消屏蔽音乐切换
*/
unblockChange() {
this.blocking = false;
}
/**
* 设置总音量大小
* @param volume 音量大小
*/
setVolume(volume) {
this.mainGain.setVolume(volume);
}
/**
* 设置是否启用
* @param enabled 是否启用
*/
setEnabled(enabled) {
if (enabled) this.resume();
else this.stop();
this.enabled = enabled;
}
/**
* 设置 bgm 音频名称的前缀
*/
setPrefix(prefix) {
this.prefix = prefix;
}
getId(name) {
return `${this.prefix}${name}`;
}
/**
* 根据 bgm 名称获取其 AudioRoute 实例
* @param id 音频名称
*/
get(id) {
return this.player.getRoute(this.getId(id));
}
/**
* 添加一个 bgm
* @param id 要添加的 bgm 的名称
* @param url 指定 bgm 的加载地址
*/
addBgm(id, url = `project/bgms/${id}`) {
const type = guessTypeByExt(id);
if (!type) {
console.warn(
"Unknown audio extension name: '" +
id.split(".").slice(0, -1).join(".") +
"'"
);
return;
}
const gain = this.player.createVolumeEffect();
2025-01-18 17:49:44 +08:00
if (isAudioSupport(type)) {
const source = audioPlayer.createElementSource();
2025-01-19 20:20:48 +08:00
source.setSource(url);
2025-01-18 17:49:44 +08:00
source.setLoop(true);
const route = new AudioRoute(source, audioPlayer);
2025-01-19 20:20:48 +08:00
route.addEffect([gain, this.mainGain]);
audioPlayer.addRoute(this.getId(id), route);
this.setTransition(id, route, gain);
2025-01-18 17:49:44 +08:00
} else {
const source = audioPlayer.createStreamSource();
2025-01-19 20:20:48 +08:00
const stream = new StreamLoader(url);
2025-01-18 17:49:44 +08:00
stream.pipe(source);
source.setLoop(true);
const route = new AudioRoute(source, audioPlayer);
2025-01-19 20:20:48 +08:00
route.addEffect([gain, this.mainGain]);
audioPlayer.addRoute(this.getId(id), route);
this.setTransition(id, route, gain);
}
}
/**
* 移除一个 bgm
* @param id 要移除的 bgm 的名称
*/
removeBgm(id) {
this.player.removeRoute(this.getId(id));
const gain = this.gain.get(id);
gain?.transition.ticker.destroy();
this.gain.delete(id);
}
setTransition(id, route, gain) {
const transition = new Transition();
transition
.time(this.transitionTime)
.mode(linear())
.transition("volume", 0);
const tick = () => {
gain.setVolume(transition.value.volume);
};
/**
* @param expect 在结束时应该是正在播放还是停止
*/
const setTick = async (expect) => {
transition.ticker.remove(tick);
transition.ticker.add(tick);
const identifier = route.stopIdentifier;
await sleep(this.transitionTime + 500);
if (route.status === expect && identifier === route.stopIdentifier) {
transition.ticker.remove(tick);
if (route.status === AudioStatus.Playing) {
gain.setVolume(1);
} else {
gain.setVolume(0);
}
}
};
route.onStart(async () => {
transition.transition("volume", 1);
setTick(AudioStatus.Playing);
});
route.onEnd(() => {
transition.transition("volume", 0);
setTick(AudioStatus.Paused);
});
route.setEndTime(this.transitionTime);
this.gain.set(id, { effect: gain, transition });
}
/**
* 播放一个 bgm
* @param id 要播放的 bgm 名称
*/
play(id, when) {
if (this.blocking) return;
if (id !== this.playingBgm && this.playingBgm) {
this.player.pause(this.getId(this.playingBgm));
}
this.playingBgm = id;
if (!this.enabled) return;
this.player.play(this.getId(id), when);
this.playing = true;
}
/**
* 继续当前的 bgm
*/
resume() {
if (this.blocking || !this.enabled || this.playing) return;
if (this.playingBgm) {
this.player.resume(this.getId(this.playingBgm));
}
this.playing = true;
}
/**
* 暂停当前的 bgm
*/
pause() {
if (this.blocking || !this.enabled) return;
if (this.playingBgm) {
this.player.pause(this.getId(this.playingBgm));
}
this.playing = false;
}
/**
* 停止当前的 bgm
*/
stop() {
if (this.blocking || !this.enabled) return;
if (this.playingBgm) {
this.player.stop(this.getId(this.playingBgm));
}
this.playing = false;
}
}
const bgmController = new BgmController(audioPlayer);
class SoundPlayer {
constructor(player) {
/** 每个音效的唯一标识符 */
this.num = 0;
this.enabled = true;
this.gain = player.createVolumeEffect();
/** 每个音效的数据 */
this.buffer = new Map();
/** 所有正在播放的音乐 */
this.playing = new Set();
this.player = player;
}
/**
* 设置是否启用音效
* @param enabled 是否启用音效
*/
setEnabled(enabled) {
if (!enabled) this.stopAllSounds();
this.enabled = enabled;
}
/**
* 设置音量大小
* @param volume 音量大小
*/
setVolume(volume) {
this.gain.setVolume(volume);
}
/**
* 添加一个音效
* @param id 音效名称
* @param data 音效的Uint8Array数据
*/
async add(id, data) {
const buffer = await this.player.decodeAudioData(data);
if (!buffer) {
console.warn(
"Cannot decode sound '" +
id +
"', since audio file may not supported by 2.b."
);
return;
}
this.buffer.set(id, buffer);
}
/**
* 播放一个音效
* @param id 音效名称
* @param position 音频位置[0, 0, 0]表示正中心x轴指向水平向右y轴指向水平向上z轴指向竖直向上
* @param orientation 音频朝向[0, 1, 0]表示朝向前方
*/
play(id, position = [0, 0, 0], orientation = [1, 0, 0]) {
if (!this.enabled) return -1;
const buffer = this.buffer.get(id);
if (!buffer) {
console.warn(
"Cannot play sound '" +
id +
"', since there is no added data named it."
);
return -1;
}
const soundNum = this.num++;
const source = this.player.createBufferSource();
source.setBuffer(buffer);
const route = this.player.createRoute(source);
const stereo = this.player.createStereoEffect();
stereo.setPosition(position[0], position[1], position[2]);
stereo.setOrientation(orientation[0], orientation[1], orientation[2]);
route.addEffect([stereo, this.gain]);
this.player.addRoute(`sounds.${soundNum}`, route);
route.play();
source.output.addEventListener("ended", () => {
this.playing.delete(soundNum);
});
this.playing.add(soundNum);
return soundNum;
}
/**
* 停止一个音效
* @param num 音效的唯一 id
*/
stop(num) {
const id = `sounds.${num}`;
const route = this.player.getRoute(id);
if (route) {
route.stop();
this.player.removeRoute(id);
this.playing.delete(num);
2025-01-18 17:49:44 +08:00
}
}
2025-01-19 20:20:48 +08:00
/**
* 停止播放所有音效
*/
stopAllSounds() {
this.playing.forEach((v) => {
const id = `sounds.${v}`;
const route = this.player.getRoute(id);
if (route) {
route.stop();
this.player.removeRoute(id);
}
});
this.playing.clear();
}
}
const soundPlayer = new SoundPlayer(audioPlayer);
function loadAllBgm() {
const data = data_a1e2fb4a_e986_4524_b0da_9b7ba7c0874d;
for (const bgm of data.main.bgms) {
bgmController.addBgm(bgm);
}
2025-01-18 17:49:44 +08:00
}
loadAllBgm();
2025-01-19 20:20:48 +08:00
AudioDecoder.registerDecoder(AudioType.Ogg, VorbisDecoder);
AudioDecoder.registerDecoder(AudioType.Opus, OpusDecoder);
2025-01-18 17:49:44 +08:00
core.plugin.audioSystem = {
AudioType,
2025-01-19 20:20:48 +08:00
AudioDecoder,
AudioStatus,
checkAudioType,
2025-01-18 17:49:44 +08:00
isAudioSupport,
2025-01-19 20:20:48 +08:00
audioPlayer,
soundPlayer,
bgmController,
2025-01-18 17:49:44 +08:00
guessTypeByExt,
2025-01-19 20:20:48 +08:00
BgmController,
SoundPlayer,
2025-01-18 17:49:44 +08:00
EchoEffect,
DelayEffect,
ChannelVolumeEffect,
VolumeEffect,
StereoEffect,
AudioEffect,
AudioPlayer,
AudioRoute,
AudioStreamSource,
AudioElementSource,
AudioBufferSource,
loadAllBgm,
StreamLoader,
};
2025-01-19 20:20:48 +08:00
//bgm相关复写
control.prototype.playBgm = (bgm, when) => {
bgmController.play(bgm, when);
core.setMusicBtn();
};
control.prototype.pauseBgm = () => {
bgmController.pause();
core.setMusicBtn();
};
control.prototype.resumeBgm = function () {
bgmController.resume();
core.setMusicBtn();
};
control.prototype.checkBgm = function () {
if (bgmController.playing) return;
if (core.musicStatus.bgmStatus) {
if (bgmController.playingBgm) {
bgmController.play(bgmController.playingBgm);
} else {
play(main.startBgm, 0);
}
} else {
pause();
}
};
control.prototype.triggerBgm = function () {
2025-01-18 17:49:44 +08:00
2025-01-19 20:20:48 +08:00
core.musicStatus.bgmStatus = !core.musicStatus.bgmStatus;
if (bgmController.playing) bgmController.pause();
else bgmController.resume();
core.setMusicBtn();
core.setLocalStorage('bgmStatus', core.musicStatus.bgmStatus);
};
//sound相关复写
control.prototype.playSound = function (
sound,
_pitch,
callback,
position,
orientation
) {
if (main.mode != 'play' || !core.musicStatus.soundStatus) return
const name = core.getMappedName(sound);
const num = soundPlayer.play(name, position, orientation);
const route = audioPlayer.getRoute(`sounds.${num}`);
if (!route) {
callback?.();
return -1;
} else {
sleep(route.duration).then(() => callback?.());
return num;
}
};
control.prototype.stopSound = function (id) {
if (isNil(id)) {
soundPlayer.stopAllSounds();
} else {
soundPlayer.stop(id);
}
};
control.prototype.getPlayingSounds = function () {
return [...soundPlayer.playing];
};
//sound加载复写
loader.prototype._loadOneSound_decodeData = function (name, data) {
if (data instanceof Blob) {
var blobReader = new zip.BlobReader(data);
blobReader.init(function () {
blobReader.readUint8Array(0, blobReader.size, function (uint8) {
//core.loader._loadOneSound_decodeData(name, uint8.buffer);
soundPlayer.add(name, uint8)
})
});
return;
}
if (data instanceof ArrayBuffer) {
const uint8 = new Uint8Array(data)
soundPlayer.add(name, uint8)
}
}
//音量控制复写
soundPlayer.setVolume(core.musicStatus.userVolume * core.musicStatus.designVolume)
bgmController.setVolume(core.musicStatus.userVolume * core.musicStatus.designVolume)
actions.prototype._clickSwitchs_sounds_userVolume = function (delta) {
var value = Math.round(Math.sqrt(100 * core.musicStatus.userVolume));
if (value == 0 && delta < 0) return;
core.musicStatus.userVolume = core.clamp(Math.pow(value + delta, 2) / 100, 0, 1);
//audioContext 音效 不受designVolume 影响
if (core.musicStatus.gainNode != null) core.musicStatus.gainNode.gain.value = core.musicStatus.userVolume;
soundPlayer.setVolume(core.musicStatus.userVolume * core.musicStatus.designVolume)
bgmController.setVolume(core.musicStatus.userVolume * core.musicStatus.designVolume)
core.setLocalStorage('userVolume', core.musicStatus.userVolume);
core.playSound('确定');
core.ui._drawSwitchs_sounds();
}
2025-01-18 17:49:44 +08:00
}
}