From ca903adb18a7da698a162c85ceea86632b30b961 Mon Sep 17 00:00:00 2001
From: strawberry42271 <2806566736@qq.com>
Date: Sat, 8 Mar 2025 16:59:40 +0800
Subject: [PATCH] =?UTF-8?q?=E8=A3=85=E5=A4=87=E6=A0=8F/=E7=AE=80=E6=98=93u?=
=?UTF-8?q?i/=E6=88=98=E6=96=97=E4=BF=AE=E5=A4=8D?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
project/data.js | 6 +-
project/floors/xiaoxiang01.js | 4 +-
project/floors/yiqu1.js | 4 +-
project/functions.js | 53 +-
project/plugins.js | 22242 +++++++++++++++++---------------
5 files changed, 11837 insertions(+), 10472 deletions(-)
diff --git a/project/data.js b/project/data.js
index f8a11c5..9fd4a58 100644
--- a/project/data.js
+++ b/project/data.js
@@ -1529,7 +1529,7 @@ var data_a1e2fb4a_e986_4524_b0da_9b7ba7c0874d =
"hp": 1000,
"manamax": -1,
"mana": 0,
- "atk": 60,
+ "atk": 10,
"def": 10,
"mdef": 100,
"speed": 10,
@@ -1555,9 +1555,9 @@ var data_a1e2fb4a_e986_4524_b0da_9b7ba7c0874d =
"followers": [],
"steps": 0,
"matk": 0,
- "spell": 100,
+ "spell": 0,
"spelldef": 0,
- "mhp": 42
+ "mhp": 0
},
"startCanvas": [
{
diff --git a/project/floors/xiaoxiang01.js b/project/floors/xiaoxiang01.js
index 2e55dad..85f6bd0 100644
--- a/project/floors/xiaoxiang01.js
+++ b/project/floors/xiaoxiang01.js
@@ -55,9 +55,9 @@ main.floors.xiaoxiang01=
[120147, 0, 32, 0,200199, 0,201029,225,110132,224, 0, 0,90675],
[120164,225,200998,200999,201037, 81,201029, 0, 0, 0,110116, 0, 94],
[120172, 0,222, 0,221, 0,201029, 81,110116,225,110137,110138,90650],
- [120180,201037,201037, 81,201037,201037,201037, 0,110124, 0, 81, 31,90658],
+ [120180,201037,201037, 81,201037,201037,201037,246,110124, 0, 81, 31,90658],
[ 92, 32, 32, 22, 21,80089, 31, 0,110132, 0,110122,110122,110197],
- [140,110191, 21, 21, 21,80089, 0, 0, 81, 0,222, 29,110197],
+ [140,110191, 21, 21, 21,80089, 0,246, 81, 0,222, 29,110197],
[140,140,140,110191,80083,80097, 93,110138,110138,140189,140,140,140]
],
"areas": "牢狱",
diff --git a/project/floors/yiqu1.js b/project/floors/yiqu1.js
index c7c3f66..ec80832 100644
--- a/project/floors/yiqu1.js
+++ b/project/floors/yiqu1.js
@@ -48,8 +48,8 @@ main.floors.yiqu1=
[140142, 0,120015,120028,201037, 0,201029, 0, 0, 0, 0, 0,110197],
[140150, 0, 0,120019, 0, 0,201029, 0,110189,140,110191, 0,110205],
[ 92, 0, 0,120019,201037,201037,201037, 0,110197,140,110199, 81,110224],
- [ 0, 0, 0,120027, 37, 37, 0, 0,110197,140,110199, 0,110232],
- [110191, 0, 0, 0, 36, 35,110189,140,140,140,110199, 0, 94],
+ [ 0, 0, 0,120027, 0, 0, 0, 0,110197,140,110199, 0,110232],
+ [110191, 0, 0, 0, 0, 0,110189,140,140,140,110199, 0, 94],
[110199,140,140,110191, 0, 0,110197,140,140,110204,110207, 0, 0],
[140,140,140,110199,90684,90684,110197,140,140,110199,110234, 0, 0],
[140,140,140,110199,100307,100308,110197,140,140,110199, 0, 0, 0]
diff --git a/project/functions.js b/project/functions.js
index fc5b908..a9a00a6 100644
--- a/project/functions.js
+++ b/project/functions.js
@@ -928,6 +928,17 @@ var functions_d6ad677b_427a_4623_b50f_a445a3b0ef8a =
origin_hero_atk = core.getStatusOrDefault(hero, "atk"),
origin_hero_def = core.getStatusOrDefault(hero, "def");
+ //编辑器特判
+ if (main.mode == "editor") {
+ hero_hp = hero?.hp ?? core.status.hero.hp,
+ hero_atk = hero?.atk ?? core.status.hero.atk,
+ hero_def = hero?.def ?? core.status.hero.def,
+ hero_matk = hero?.matk ?? core.status.hero.matk,
+ hero_mdef = hero?.mdef ?? core.status.hero.mdef,
+ hero_mhp = hero?.mhp ?? core.status.hero.mhp,
+ hero_speed = hero?.speed ?? core.status.hero.speed,
+ hero_spell = hero?.spell ?? core.status.hero.spell;
+ }
// 怪物的各项数据
// 对坚固模仿等处理扔到了脚本编辑-getEnemyInfo之中
var enemyInfo = core.enemys.getEnemyInfo(enemy, hero, x, y, floorId);
@@ -1017,7 +1028,7 @@ var functions_d6ad677b_427a_4623_b50f_a445a3b0ef8a =
//需要变更
const onegcd = gcd(...oneTurn) //最大公约数
- oneTurn = lcm(...oneTurn) / onegcd //单次回合长度
+ oneTurn = lcm(...oneTurn) //单次回合长度
//在这里处理equip的初始位置now
equipInfo.forEach(v => {
switch (v.id) {
@@ -1029,7 +1040,7 @@ var functions_d6ad677b_427a_4623_b50f_a445a3b0ef8a =
v.onAttack = false
if (v.now === oneTurn) v.onAttack = true //增加正在攻击的标志
})
- const heroinfo = { hp: hero_hp, atk: hero_atk, def: hero_def, mdef: hero_mdef, spell: hero_spell, mhp: Math.floor(hero_spell * hero_mhp / 100), matk: Math.floor(hero_spell * hero_matk / 100), speed: hero_speed, now: 0, isAttack: false } //勇士属性
+ const heroinfo = { hp: hero_hp, atk: hero_atk, def: hero_def, mdef: (!hero?.mdef || hero?.mdef === 100) ? hero_mdef : hero.mdef, spell: hero_spell, mhp: Math.floor(hero_spell * hero_mhp / 100), matk: Math.floor(hero_spell * hero_matk / 100), speed: hero_speed, now: 0, isAttack: false } //勇士属性
const enemyinfo = { hp: mon_hp, atk: mon_atk, def: mon_def, mdef: mon_mdef, spell: mon_spell, speed: mon_speed, special: mon_special, now: 0, onAttack: false, isAttack: false } //怪物属性
//先攻,先攻为怪物和勇士勇士行动前怪物出第一刀
if (core.hasSpecial(mon_special, 1)) {
@@ -1194,27 +1205,27 @@ var functions_d6ad677b_427a_4623_b50f_a445a3b0ef8a =
})
- if (onattack) { //处理完毕后的数据处理
- heroDiffList.push(hero_diff)
- enemyDiffList.push(enemy_diff)
- heroanimateList.push(hero_animate)
- enemyanimateList.push(enemy_animate)
- //处理属性变化
- for (let v in hero_diff) {
- heroinfo[v] += hero_diff[v]
- }
- for (let v in enemy_diff) {
- enemyinfo[v] += enemy_diff[v]
- }
- let a = hero_turn % 50 //出手50回合怪物生命未降低直接判负,避免死循环
- if (a === 0) {
- if (enemyinfo.hp >= beforehp) {
- return null
- } else {
- beforehp = enemyinfo.hp
- }
+ //处理完毕后的数据处理
+ heroDiffList.push(hero_diff)
+ enemyDiffList.push(enemy_diff)
+ heroanimateList.push(hero_animate)
+ enemyanimateList.push(enemy_animate)
+ //处理属性变化
+ for (let v in hero_diff) {
+ heroinfo[v] += hero_diff[v]
+ }
+ for (let v in enemy_diff) {
+ enemyinfo[v] += enemy_diff[v]
+ }
+
+ //出手50回合怪物生命未降低直接判负,避免死循环
+ if (hero_turn === 50) {
+
+ if (enemyinfo.hp >= beforehp) {
+ return null
}
}
+
}
//下面这些还没修改,原有技能除先攻、连击外暂时全部移除,所有技能需要在上方的模拟循环中做修正
diff --git a/project/plugins.js b/project/plugins.js
index 823da2a..71ba899 100644
--- a/project/plugins.js
+++ b/project/plugins.js
@@ -1,266 +1,268 @@
var plugins_bb40132b_638b_4a9f_b028_d3fe47acc8d1 =
{
"init": function () {
- this._afterLoadResources = function () {
- // 本函数将在所有资源加载完毕后,游戏开启前被执行
- core.ui.statusBar.init();
- core.dom.playGame.style.fontFamily = "pala";
- core.dom.loadGame.style.fontFamily = "pala";
- core.dom.CGMode.style.fontFamily = "pala";
- core.dom.musicMode.style.fontFamily = "pala";
- core.dom.replayGame.style.fontFamily = "pala";
- 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?.text,
- data?.text2,
- data?.warning,
- data.large,
- data.size
- );
+ this._afterLoadResources = function () {
+ // 本函数将在所有资源加载完毕后,游戏开启前被执行
+ core.ui.statusBar.init();
+ core.dom.playGame.style.fontFamily = "pala";
+ core.dom.loadGame.style.fontFamily = "pala";
+ core.dom.CGMode.style.fontFamily = "pala";
+ core.dom.musicMode.style.fontFamily = "pala";
+ core.dom.replayGame.style.fontFamily = "pala";
+ 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?.text,
+ data?.text2,
+ data?.warning,
+ data.large,
+ data.size
+ );
- setTimeout(() => core.doAction(), 3100);
- } else {
- core.doAction();
- }
- });
+ setTimeout(() => core.doAction(), 3100);
+ } else {
+ 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.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("cgtextList", function (data) {
- core.ui.cgText.textList = core.plugin[data.textList];
- core.doAction();
- });
- core.registerEvent("cgtext", function (data) {
- if (!main.replayChecking && !core.isReplaying()) {
- core.ui.cgText.image = data.bg;
- core.ui.cgText.nobg = data.nobg ?? false;
- core.ui.cgText.memory = data.memory;
- core.ui.cgText.head = core.clone(data.head);
- core.ui.cgText.index = data.index;
- core.ui.cgText.name = core.ui.cgText.textList[data.index][0];
- core.ui.cgText.text = data.text ?
- data.text :
- core.ui.cgText.textList[data.index][1];
- core.ui.cgText.time = data.time;
- core.ui.cgText.wait = data.wait;
- core.ui.cgText.WindowSkin = data.WindowSkin;
- core.ui.cgText.sound =
- data.sound === "" ?
- data.sound :
- core.ui.cgText.textList[data.index][2] || "";
- core.ui.cgText.bodyList = core.clone(data.bodyList);
- main.dom.cgText.style.display = "block";
- core.ui.cgText.update();
- } else {
- core.doAction();
- }
- });
- core.registerEvent("introAndLoop", function (data) {
- if (!main.replayChecking && !core.isReplaying()) {
- core.plugin.introAndLoop(data.intro, data.time, data.loop);
- core.doAction();
- } else {
- core.doAction();
- }
- });
- core.registerEvent("setq", function (data) {
- core.setFlag("任务地点", data.id);
+ 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("cgtextList", function (data) {
+ core.ui.cgText.textList = core.plugin[data.textList];
+ core.doAction();
+ });
+ core.registerEvent("cgtext", function (data) {
+ if (!main.replayChecking && !core.isReplaying()) {
+ core.ui.cgText.image = data.bg;
+ core.ui.cgText.nobg = data.nobg ?? false;
+ core.ui.cgText.memory = data.memory;
+ core.ui.cgText.head = core.clone(data.head);
+ core.ui.cgText.index = data.index;
+ core.ui.cgText.name = core.ui.cgText.textList[data.index][0];
+ core.ui.cgText.text = data.text
+ ? data.text
+ : core.ui.cgText.textList[data.index][1];
+ core.ui.cgText.time = data.time;
+ core.ui.cgText.wait = data.wait;
+ core.ui.cgText.WindowSkin = data.WindowSkin;
+ core.ui.cgText.sound =
+ data.sound === ""
+ ? data.sound
+ : core.ui.cgText.textList[data.index][2] || "";
+ core.ui.cgText.bodyList = core.clone(data.bodyList);
+ main.dom.cgText.style.display = "block";
+ core.ui.cgText.update();
+ } else {
+ core.doAction();
+ }
+ });
+ core.registerEvent("introAndLoop", function (data) {
+ if (!main.replayChecking && !core.isReplaying()) {
+ core.plugin.introAndLoop(data.intro, data.time, data.loop);
+ core.doAction();
+ } else {
+ core.doAction();
+ }
+ });
+ core.registerEvent("setq", function (data) {
+ core.setFlag("任务地点", data.id);
- core.doAction();
- });
+ core.doAction();
+ });
- core.registerEvent("setmusics", function (data) {
- if (
- (core.getLocalStorage("musics") &&
- core.getLocalStorage("musics").length === 0) ||
- !core.getLocalStorage("musics")
- )
- core.setLocalStorage("musics", ["theme.mp3"]);
- let a = core.getLocalStorage("musics");
- if (!data.bgm) {
- core.setLocalStorage("musics", ["theme.mp3"]);
- } else {
- if (!a.includes(data.bgm)) a.push(data.bgm);
- core.setLocalStorage("musics", a);
- }
- core.doAction();
- });
- core.registerEvent("setcgs", function (data) {
- if (!data.img) {
- core.setLocalStorage("cgs", []);
- } else {
- let a = core.getLocalStorage("cgs") ?? [];
- if (!a.includes(data.img)) a.push(data.img);
- core.setLocalStorage("cgs", a);
- }
- core.doAction();
- });
- };
-},
+ core.registerEvent("setmusics", function (data) {
+ if (
+ (core.getLocalStorage("musics") &&
+ core.getLocalStorage("musics").length === 0) ||
+ !core.getLocalStorage("musics")
+ )
+ core.setLocalStorage("musics", ["theme.mp3"]);
+ let a = core.getLocalStorage("musics");
+ if (!data.bgm) {
+ core.setLocalStorage("musics", ["theme.mp3"]);
+ } else {
+ if (!a.includes(data.bgm)) a.push(data.bgm);
+ core.setLocalStorage("musics", a);
+ }
+ core.doAction();
+ });
+ core.registerEvent("setcgs", function (data) {
+ if (!data.img) {
+ core.setLocalStorage("cgs", []);
+ } else {
+ let a = core.getLocalStorage("cgs") ?? [];
+ if (!a.includes(data.img)) a.push(data.img);
+ core.setLocalStorage("cgs", a);
+ }
+ core.doAction();
+ });
+ };
+ },
"drawLight": function () {
// 绘制灯光/漆黑层效果。调用方式 core.plugin.drawLight(...)
// 【参数说明】
@@ -698,118 +700,118 @@ var plugins_bb40132b_638b_4a9f_b028_d3fe47acc8d1 =
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;
- }
+ // 创建新图层
+ 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 = {};
+ 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");
+ 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 = 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 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 () {
@@ -2538,7 +2540,7 @@ var plugins_bb40132b_638b_4a9f_b028_d3fe47acc8d1 =
const BORDER_HEIGHT = 0; //游戏画面上侧偏移距离
const ITEM_BOX_LEFT = 549 * 3; //横屏道具栏左侧距离(右侧边栏需增加BAR_WIDTH+GAMEVIEW_HEIGHT)
- const ITEM_BOX_TOP = 155 * 3; //横屏道具栏上侧距离
+ const ITEM_BOX_TOP = 175 * 3; //横屏道具栏上侧距离
const ITEM_BOX_LEFT_VERTICAL = 160 * 3; //竖屏道具栏左侧距离
const ITEM_BOX_TOP_VERTICAL = 549 * 3; //竖屏道具栏上侧距离(下侧边栏需增加BAR_HEIGHT_VERTICAL+GAMEVIEW_WIDTH_VERTICAL)
@@ -2553,12 +2555,12 @@ var plugins_bb40132b_638b_4a9f_b028_d3fe47acc8d1 =
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_TOP = 130 * 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_TOP = 290 * 3; //横屏道具说明上侧距离
const INFO_BLOCK_LEFT_VERTICAL = 113 * 3; //竖屏道具说明左侧距离
const INFO_BLOCK_TOP_VERTICAL = 8 * 3; //竖屏道具说明上侧距离(下侧边栏需增加BAR_HEIGHT_VERTICAL+GAMEVIEW_WIDTH_VERTICAL)
@@ -2690,6 +2692,10 @@ var plugins_bb40132b_638b_4a9f_b028_d3fe47acc8d1 =
main.dom.over.style.width = obj.totalWidth + 3 + "px";
main.dom.over.style.height = obj.totalHeight + 3 + "px";
}
+ if (main.dom.book) {
+ main.dom.book.style.width = obj.totalWidth + 3 + "px";
+ main.dom.book.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";
@@ -2713,15 +2719,12 @@ var plugins_bb40132b_638b_4a9f_b028_d3fe47acc8d1 =
main.dom.boss1.style.width = obj.totalWidth + 3 + "px";
main.dom.boss1.style.height = obj.totalHeight + 3 + "px";
-
main.dom.boss2.style.width = obj.totalWidth + 3 + "px";
main.dom.boss2.style.height = obj.totalHeight + 3 + "px";
-
main.dom.boss3.style.width = obj.totalWidth + 3 + "px";
main.dom.boss3.style.height = obj.totalHeight + 3 + "px";
-
main.dom.boss4.style.width = obj.totalWidth + 3 + "px";
main.dom.boss4.style.height = obj.totalHeight + 3 + "px";
@@ -2906,7 +2909,6 @@ var plugins_bb40132b_638b_4a9f_b028_d3fe47acc8d1 =
core.ui.boss.update();
};
-
class StatusBar {
constructor() {
//道具栏列表
@@ -2933,16 +2935,22 @@ var plugins_bb40132b_638b_4a9f_b028_d3fe47acc8d1 =
];
this.replayAction = [
[core.triggerReplay, core.stopReplay, core.rewindReplay],
- [core.speedDownReplay, core.speedUpReplay, function () { core.control._replay_SL() }],
+ [
+ core.speedDownReplay,
+ core.speedUpReplay,
+ function () {
+ core.control._replay_SL();
+ },
+ ],
];
}
//更新
update() {
this._update_background(); //更新背景
this._update_props(); //更新属性
- //this._update_items(); //更新道具
+ this._update_items(); //更新道具
//this._update_equips(); //更新装备
- //this._update_keys(); //更新钥匙
+ this._update_keys(); //更新钥匙
//this._update_infoWindow(); //更新道具说明
this._update_toolBox(); //更新工具栏
this._redrawMap();
@@ -3032,33 +3040,66 @@ var plugins_bb40132b_638b_4a9f_b028_d3fe47acc8d1 =
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 statusList = [
+ "hp",
+ "atk",
+ "def",
+ "spell",
+ "mdef",
+ "matk",
+ "mhp",
+ "speed",
+ "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
- );
-
- // 四舍五入
- core.status.hero[item] = Math.round(core.status.hero[item]);
- // 大数据格式化
+ /*core.drawIcon(
+ "outerUI",
+ item,
+ baseX - 95 * 3,
+ curh - 18 * 3,
+ 22 * 3,
+ 22 * 3
+ );*/
+ core.strokeRect("outerUI", baseX - 96 * 3,
+ curh - 16 * 3, 100 * 3, 22 * 3, "#FFFFFF", 3)
+ core.setFont("outerUI", "bold 24px Verdana");
core.fillBoldText1(
"outerUI",
- core.getRealStatus(item),
+ core.getStatusLabel(item),
+ baseX - 65 * 3,
+ curh - 4 * 3,
+ TEXT_COLOR,
+ "#000000",
+ 6
+ );
+ core.setFont("outerUI", "bold 36px Verdana");
+ // 四舍五入
+ core.status.hero[item] = Math.round(core.status.hero[item]);
+ let text = core.getRealStatus(item);
+ // 大数据格式化
+ switch (item) {
+ case "mdef":
+ text += "%";
+ break;
+ case "matk":
+ case "mhp":
+ text += `(${core.status.hero[item]})%`;
+ break;
+ }
+ core.fillBoldText1(
+ "outerUI",
+ text,
baseX,
curh,
TEXT_COLOR,
"#000000",
6
);
+
curh += 24 * 3;
if (curh > 130 * 3 && core.domStyle.isVertical) {
curh = 24 * 3;
@@ -3081,9 +3122,9 @@ var plugins_bb40132b_638b_4a9f_b028_d3fe47acc8d1 =
6
);
}
- //drawStatusList(96 * 3, 46 * 3);
+ drawStatusList(96 * 3, 46 * 3);
//core.drawImage("outerUI", "lane1.png", 0, 0)
- core.drawImage("outerUI", "cao.webp", 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");
@@ -3098,8 +3139,8 @@ var plugins_bb40132b_638b_4a9f_b028_d3fe47acc8d1 =
6
);
}
- //drawStatusList(110 * 3, 93 * 3);
- //core.drawImage("outerUI", "lane1.png", 0, 30)
+ drawStatusList(110 * 3, 93 * 3);
+ //core.drawImage("outerUI", "lane1.png", 0, 30);
core.drawImage(
"outerUI",
"cao.webp",
@@ -3462,7 +3503,7 @@ var plugins_bb40132b_638b_4a9f_b028_d3fe47acc8d1 =
top: INFO_BLOCK_TOP_VERTICAL + 40 * 3,
maxWidth: 275 * 3,
color: "#D1CEFF",
- fontSize: 36,
+ fontSize: 24,
});
}
} else {
@@ -3473,7 +3514,7 @@ var plugins_bb40132b_638b_4a9f_b028_d3fe47acc8d1 =
115 * 3,
230 * 3
);
-
+ core.setFont("outerUI", "bold 36px Verdana")
if (this.selectedItem) {
const icon = core.material.icons.items[itemId];
core.setTextAlign("outerUI", "center");
@@ -3501,7 +3542,7 @@ var plugins_bb40132b_638b_4a9f_b028_d3fe47acc8d1 =
top: INFO_BLOCK_TOP + 60 * 3,
maxWidth: 105 * 3,
color: "#D1CEFF",
- fontSize: 36,
+ fontSize: 24,
});
}
}
@@ -3892,7 +3933,8 @@ var plugins_bb40132b_638b_4a9f_b028_d3fe47acc8d1 =
return nextInfo == null ||
typeof nextInfo == "number" ||
nextInfo.damage >= pre ?
- null : [start, nextInfo.damage];
+ null :
+ [start, nextInfo.damage];
};
var currAtk = start_atk;
while (true) {
@@ -4348,93 +4390,93 @@ var plugins_bb40132b_638b_4a9f_b028_d3fe47acc8d1 =
if (core.status.played && !core.status.lockControl) return false;
switch (core.status.event.id) {
- case 'battle':
+ case "battle":
core.plugin.battle_onclick(x, y, px, py);
break;
- case 'centerFly':
+ case "centerFly":
this._clickCenterFly(x, y, px, py);
break;
- case 'book':
+ case "book":
this._clickBook(x, y, px, py);
break;
- case 'book-detail':
+ case "book-detail":
this._clickBookDetail(x, y, px, py);
break;
- case 'fly':
+ case "fly":
this._clickFly(x, y, px, py);
break;
- case 'viewMaps':
+ case "viewMaps":
this._clickViewMaps(x, y, px, py);
break;
- case 'switchs':
+ case "switchs":
this._clickSwitchs(x, y, px, py);
break;
- case 'switchs-sounds':
+ case "switchs-sounds":
this._clickSwitchs_sounds(x, y, px, py);
break;
- case 'switchs-display':
+ case "switchs-display":
this._clickSwitchs_display(x, y, px, py);
break;
- case 'switchs-action':
+ case "switchs-action":
this._clickSwitchs_action(x, y, px, py);
break;
- case 'settings':
+ case "settings":
this._clickSettings(x, y, px, py);
break;
- case 'selectShop':
+ case "selectShop":
this._clickQuickShop(x, y, px, py);
break;
- case 'equipbox':
+ case "equipbox":
this._clickEquipbox(x, y, px, py);
break;
- case 'toolbox':
+ case "toolbox":
this._clickToolbox(x, y, px, py);
break;
- case 'save':
- case 'load':
- case 'replayLoad':
- case 'replayRemain':
- case 'replaySince':
+ case "save":
+ case "load":
+ case "replayLoad":
+ case "replayRemain":
+ case "replaySince":
this._clickSL(x, y, px, py);
break;
- case 'confirmBox':
+ case "confirmBox":
this._clickConfirmBox(x, y, px, py);
break;
- case 'keyBoard':
+ case "keyBoard":
this._clickKeyBoard(x, y, px, py);
break;
- case 'action':
+ case "action":
this._clickAction(x, y, px, py);
break;
- case 'text':
+ case "text":
core.drawText();
break;
- case 'notes':
+ case "notes":
this._clickNotes(x, y, px, py);
break;
- case 'syncSave':
+ case "syncSave":
this._clickSyncSave(x, y, px, py);
break;
- case 'syncSelect':
+ case "syncSelect":
this._clickSyncSelect(x, y, px, py);
break;
- case 'localSaveSelect':
+ case "localSaveSelect":
this._clickLocalSaveSelect(x, y, px, py);
break;
- case 'storageRemove':
+ case "storageRemove":
this._clickStorageRemove(x, y, px, py);
break;
- case 'cursor':
+ case "cursor":
this._clickCursor(x, y, px, py);
break;
- case 'replay':
+ case "replay":
this._clickReplay(x, y, px, py);
break;
- case 'gameInfo':
+ case "gameInfo":
this._clickGameInfo(x, y, px, py);
break;
- case 'about':
- case 'help':
+ case "about":
+ case "help":
core.ui.closePanel();
break;
}
@@ -4448,13 +4490,18 @@ var plugins_bb40132b_638b_4a9f_b028_d3fe47acc8d1 =
clearInterval(core.interval.onDownInterval);
core.interval.onDownInterval = null;
}
- }, 40)
+ }, 40);
}
}, 500);
}
return true;
- }
- core.registerAction('ondown', '_sys_ondown_lockControl', core.actions._sys_ondown_lockControl, 30);;
+ };
+ core.registerAction(
+ "ondown",
+ "_sys_ondown_lockControl",
+ core.actions._sys_ondown_lockControl,
+ 30
+ );
},
"额外信息": function () {
/* 宝石血瓶左下角显示数值
@@ -4614,8 +4661,8 @@ var plugins_bb40132b_638b_4a9f_b028_d3fe47acc8d1 =
}
},
"跳伤": function () {
- // 在此增加新插件
- /**
+ // 在此增加新插件
+ /**
函数使用说明
Dove.MorePerform.ShowDamagePop.PopDamage(
'ui', // 画布名称或画布2d上下文对象
@@ -4630,170 +4677,235 @@ var plugins_bb40132b_638b_4a9f_b028_d3fe47acc8d1 =
0.5, // 重力 (gravity)
120 // 显示时长(帧数)
*/
- if (!core.registerAnimationFrame) {
- throw new Error('require 2.6.1 or higher version');
- }
+ if (!core.registerAnimationFrame) {
+ throw new Error("require 2.6.1 or higher version");
+ }
- window.Dove = window.Dove || {};
- Dove.MorePerform = Dove.MorePerform || {};
+ window.Dove = window.Dove || {};
+ Dove.MorePerform = Dove.MorePerform || {};
- Dove.MorePerform.ShowDamagePop = {};
- Dove.MorePerform.ShowDamagePop.version = 1.0;
+ Dove.MorePerform.ShowDamagePop = {};
+ Dove.MorePerform.ShowDamagePop.version = 1.0;
- Dove.MorePerform.ShowDamagePop.AllPopingCanvas = [];
+ Dove.MorePerform.ShowDamagePop.AllPopingCanvas = [];
- // 每帧的处理
- Dove.MorePerform.ShowDamagePop.Update = function () {
- this.AllPopingCanvas = this.AllPopingCanvas.filter(function (spr) {
- spr.update();
- return spr.isAlive();
- });
- if (!this.AllPopingCanvas.length) PopSprite._count = 0;
- };
+ // 每帧的处理
+ Dove.MorePerform.ShowDamagePop.Update = function () {
+ this.AllPopingCanvas = this.AllPopingCanvas.filter(function (spr) {
+ spr.update();
+ return spr.isAlive();
+ });
+ if (!this.AllPopingCanvas.length) PopSprite._count = 0;
+ };
- // 弹出伤害气泡
- Dove.MorePerform.ShowDamagePop.PopDamage = function (canvasName, x, y, damageValue, fontSize, font, fontColor, outlineColor, speedX, speedY, gravity, duration) {
- if (damageValue) {
- var poper = new PopSprite(canvasName, x, y, damageValue, fontSize, font, fontColor, outlineColor, speedX, speedY, gravity, duration);
- Dove.MorePerform.ShowDamagePop.AllPopingCanvas.push(poper);
- }
- };
+ // 弹出伤害气泡
+ Dove.MorePerform.ShowDamagePop.PopDamage = function (
+ canvasName,
+ x,
+ y,
+ damageValue,
+ fontSize,
+ font,
+ fontColor,
+ outlineColor,
+ speedX,
+ speedY,
+ gravity,
+ duration
+ ) {
+ if (damageValue) {
+ var poper = new PopSprite(
+ canvasName,
+ x,
+ y,
+ damageValue,
+ fontSize,
+ font,
+ fontColor,
+ outlineColor,
+ speedX,
+ speedY,
+ gravity,
+ duration
+ );
+ Dove.MorePerform.ShowDamagePop.AllPopingCanvas.push(poper);
+ }
+ };
- // 战斗发生前后记录生命值并处理
- Dove.MorePerform.ShowDamagePop.OnBattle = core.events.battle;
- core.events.battle = function () {
- var hpBeforeBattle = core.status.hero.hp;
- Dove.MorePerform.ShowDamagePop.OnBattle.apply(core.events, arguments);
- if (core.getFlag('noAnimate')) Dove.MorePerform.ShowDamagePop.PopDamage(
- "ui", // 默认画布名称
- core.getHeroLoc('x') * 32, // 英雄位置 x
- core.getHeroLoc('y') * 32, // 英雄位置 y
- Math.floor(core.status.hero.hp - hpBeforeBattle), // 伤害值
- 16, // 默认字体大小
- "Arial", //默认字体
- null, // 默认颜色
- null, // 默认描边颜色
- null, // 默认水平速度
- null, // 默认垂直速度
- null, // 默认重力
- 90 // 默认显示时长(帧数)
- );
+ // 战斗发生前后记录生命值并处理
+ Dove.MorePerform.ShowDamagePop.OnBattle = core.events.battle;
+ core.events.battle = function () {
+ var hpBeforeBattle = core.status.hero.hp;
+ Dove.MorePerform.ShowDamagePop.OnBattle.apply(core.events, arguments);
+ if (core.getFlag("noAnimate"))
+ Dove.MorePerform.ShowDamagePop.PopDamage(
+ "ui", // 默认画布名称
+ core.getHeroLoc("x") * 32, // 英雄位置 x
+ core.getHeroLoc("y") * 32, // 英雄位置 y
+ Math.floor(core.status.hero.hp - hpBeforeBattle), // 伤害值
+ 16, // 默认字体大小
+ "Arial", //默认字体
+ null, // 默认颜色
+ null, // 默认描边颜色
+ null, // 默认水平速度
+ null, // 默认垂直速度
+ null, // 默认重力
+ 90 // 默认显示时长(帧数)
+ );
+ };
+ let time = 0;
+ // 注册每帧事件
+ core.registerAnimationFrame("ShowDamagePop", true, (temptime) => {
+ if (temptime - time > 1000 / 60) {
+ time = temptime;
- };
- let time = 0
- // 注册每帧事件
- core.registerAnimationFrame("ShowDamagePop", true,
- (temptime) => {
- if (temptime - time > 1000 / 60) {
- time = temptime
+ Dove.MorePerform.ShowDamagePop.Update.bind(
+ Dove.MorePerform.ShowDamagePop
+ )();
+ }
+ });
- Dove.MorePerform.ShowDamagePop.Update.bind(Dove.MorePerform.ShowDamagePop)()
- }
- }
+ // 弹出精灵类
+ function PopSprite(
+ canvasName,
+ x,
+ y,
+ damage,
+ fontSize,
+ font,
+ fontColor,
+ outlineColor,
+ speedX,
+ speedY,
+ gravity,
+ duration
+ ) {
+ this.initialize.apply(this, arguments);
+ }
- );
+ PopSprite.prototype = Object.create(Object.prototype);
+ PopSprite.prototype.constructor = PopSprite;
- // 弹出精灵类
- function PopSprite(canvasName, x, y, damage, fontSize, font, fontColor, outlineColor, speedX, speedY, gravity, duration) {
- this.initialize.apply(this, arguments);
- }
+ // 常量
+ PopSprite._count = 0;
+ PopSprite._baseZOrder = 50;
+ PopSprite._floorDis = 20;
- PopSprite.prototype = Object.create(Object.prototype);
- PopSprite.prototype.constructor = PopSprite;
+ // 初始化
+ PopSprite.prototype.initialize = function (
+ canvasName,
+ x,
+ y,
+ damage,
+ fontSize,
+ font,
+ fontColor,
+ outlineColor,
+ speedX,
+ speedY,
+ gravity,
+ duration
+ ) {
+ this._canvasName = canvasName ?? "ui"; // 默认画布名称
+ this._x = x;
+ this._y = y;
+ this._damage = damage;
+ this._fontSize = fontSize ?? 16; // 默认字体大小
+ this._font = font ?? "Arial";
+ this._fontColor = fontColor ?? (damage > 0 ? "#22FF44" : "lightcoral"); // 默认颜色
+ this._outlineColor = outlineColor ?? "#FFFFFF"; // 默认描边颜色
+ this._speedX = speedX ?? -1 + Math.random() * 2; // 水平速度,默认随机
+ this._speedY = speedY ?? -3 - Math.random() * 4; // 垂直速度,默认随机
+ this._gravity = gravity ?? 0.3; // 重力加速度,默认 0.3
+ this._duration = duration ?? 180; // 显示时长(帧数),默认 180 帧
+ this.initAllMembers();
+ this.requestCanvas();
+ };
- // 常量
- PopSprite._count = 0;
- PopSprite._baseZOrder = 50;
- PopSprite._floorDis = 20;
+ // 自更新
+ PopSprite.prototype.update = function () {
+ if (this._timer < this._duration) {
+ // 使用传入的显示时长
+ this._x += this._vx;
+ this._y += this._vy;
+ this._vy += this._gravity;
+ if (this._y >= this._floorY) {
+ this._y = this._floorY;
+ this._vy *= -0.75; // 反弹衰减
+ }
+ core.relocateCanvas(this._symbol, this._x, this._y);
+ core.setOpacity(this._symbol, 1 - this._timer / this._duration); // 根据显示时长设置透明度
+ } else {
+ this.dispose();
+ }
+ this._timer++;
+ };
- // 初始化
- PopSprite.prototype.initialize = function (canvasName, x, y, damage, fontSize, font, fontColor, outlineColor, speedX, speedY, gravity, duration) {
- this._canvasName = canvasName ?? 'ui'; // 默认画布名称
- this._x = x;
- this._y = y;
- this._damage = damage;
- this._fontSize = fontSize ?? 16; // 默认字体大小
- this._font = font ?? "Arial"
- this._fontColor = fontColor ?? (damage > 0 ? '#22FF44' : 'lightcoral'); // 默认颜色
- this._outlineColor = outlineColor ?? '#FFFFFF'; // 默认描边颜色
- this._speedX = speedX ?? (-1 + Math.random() * 2); // 水平速度,默认随机
- this._speedY = speedY ?? (-3 - Math.random() * 4); // 垂直速度,默认随机
- this._gravity = gravity ?? 0.3; // 重力加速度,默认 0.3
- this._duration = duration ?? 180; // 显示时长(帧数),默认 180 帧
- this.initAllMembers();
- this.requestCanvas();
- };
+ // 申请并描绘canvas
+ PopSprite.prototype.requestCanvas = function () {
+ core.createCanvas(
+ this._symbol,
+ this._x,
+ this._y,
+ this._width + 4,
+ this._height + 4,
+ this._z
+ );
- // 自更新
- PopSprite.prototype.update = function () {
- if (this._timer < this._duration) { // 使用传入的显示时长
- this._x += this._vx;
- this._y += this._vy;
- this._vy += this._gravity;
- if (this._y >= this._floorY) {
- this._y = this._floorY;
- this._vy *= -0.75; // 反弹衰减
- }
- core.relocateCanvas(this._symbol, this._x, this._y);
- core.setOpacity(this._symbol, 1 - this._timer / this._duration); // 根据显示时长设置透明度
- } else {
- this.dispose();
- }
- this._timer++;
- };
+ var canvas = core.getContextByName(this._symbol);
+ canvas.font = this._fontSize + "px " + this._font; // 动态设置字体大小
+ canvas.fillStyle = this._fontColor; // 动态设置字体颜色
+ canvas.strokeStyle = this._outlineColor; // 动态设置描边颜色
+ canvas.strokeText(this._text, 2, this._height);
+ canvas.fillText(this._text, 2, this._height);
+ };
+ // 初始化所有成员变量
+ PopSprite.prototype.initAllMembers = function () {
+ this._text = String(this._damage);
+ var uiContext = core.ui.getContextByName(this._canvasName); // 使用指定画布
+ uiContext.font = this._fontSize + "px " + this._font; // 动态设置字体大小
+ var textRect = uiContext.measureText(this._text);
+ this._width = textRect.width + 4;
+ this._height = this._fontSize + 4; // 动态设置高度
+ this._z = uiContext.canvas.style.zIndex
+ ? Number(uiContext.canvas.style.zIndex) + PopSprite._count
+ : PopSprite._baseZOrder + PopSprite._count;
+ this._symbol = "popSprite" + PopSprite._count++;
+ this._alive = true;
+ this._vx = this._speedX; // 使用传入的水平速度
+ this._vy = this._speedY; // 使用传入的垂直速度
+ this._floorY = this._y + PopSprite._floorDis;
+ this._timer = 0;
+ };
- // 申请并描绘canvas
- PopSprite.prototype.requestCanvas = function () {
- core.createCanvas(this._symbol, this._x, this._y, this._width + 4, this._height + 4, this._z);
+ // 判断是否存活
+ PopSprite.prototype.isAlive = function () {
+ return this._alive;
+ };
- var canvas = core.getContextByName(this._symbol)
- canvas.font = this._fontSize + 'px ' + this._font; // 动态设置字体大小
- canvas.fillStyle = this._fontColor; // 动态设置字体颜色
- canvas.strokeStyle = this._outlineColor; // 动态设置描边颜色
- canvas.strokeText(this._text, 2, this._height);
- canvas.fillText(this._text, 2, this._height);
- };
-
- // 初始化所有成员变量
- PopSprite.prototype.initAllMembers = function () {
- this._text = String(this._damage);
- var uiContext = core.ui.getContextByName(this._canvasName); // 使用指定画布
- uiContext.font = this._fontSize + 'px ' + this._font; // 动态设置字体大小
- var textRect = uiContext.measureText(this._text);
- this._width = textRect.width + 4;
- this._height = this._fontSize + 4; // 动态设置高度
- this._z = uiContext.canvas.style.zIndex ? Number(uiContext.canvas.style.zIndex) + PopSprite._count : PopSprite._baseZOrder + PopSprite._count;
- this._symbol = 'popSprite' + PopSprite._count++;
- this._alive = true;
- this._vx = this._speedX; // 使用传入的水平速度
- this._vy = this._speedY; // 使用传入的垂直速度
- this._floorY = this._y + PopSprite._floorDis;
- this._timer = 0;
-
- };
-
- // 判断是否存活
- PopSprite.prototype.isAlive = function () {
- return this._alive;
- };
-
-
-
- // 释放
- PopSprite.prototype.dispose = function () {
- this._alive = false;
- core.deleteCanvas(this._symbol, );
- };
-
-},
+ // 释放
+ PopSprite.prototype.dispose = function () {
+ this._alive = false;
+ core.deleteCanvas(this._symbol);
+ };
+ },
"编辑器显伤": function () {
// 在此增加新插件
/////// 用户设置 ///////
// 将__enable置为false将关闭插件
var __enable = true;
// 魔防攻速之类的属性可以在这里加 ['atk', 'def', 'mdef']
- var heroStatus = ["atk", "def", "mdef", "hp"];
+ var heroStatus = [
+ "atk",
+ "def",
+ "spell",
+ "mdef",
+ "matk",
+ "mhp",
+ "speed",
+ "hp",
+ ];
// saveHero为true 将会把每次造塔测试时的角色数据存下来 否则会读取初始属性
// 用不着可以关了 节约缓存空间 (虽然根本没多少 还没一个存档大
// 也可以手动清理 控制台输入core.removeLocalStorage('editorHero')即可
@@ -4802,12 +4914,17 @@ var plugins_bb40132b_638b_4a9f_b028_d3fe47acc8d1 =
// 下为具体实现 懒得写注释了 大概就是写HTML然后注册交互
if (!__enable || main.mode != "editor") return;
core.plugin.initEditorDamage = false;
- if (heroStatus.length >= 4 && !editor.isMobile)
+ if (heroStatus.length >= 4 && !editor.isMobile) {
editor.dom.mid2.style.top = 650 + 30 * (heroStatus.length - 3) + "px";
+ editor.dom.mid.style.height = 730 + "px";
+ document.querySelector("#mid .tools").style.height = 280 + "px";
+ }
editor.statusRatio = core.getLocalStorage("statusRatio", 1);
editor.saveHero = saveHero;
editor._heroStatus = heroStatus;
editor.dom.mapEdit.appendChild(core.canvas.damage.canvas);
+ const viewportButtons = document.getElementById("viewportButtons");
+
var HTML =
"";
@@ -4818,7 +4935,11 @@ var plugins_bb40132b_638b_4a9f_b028_d3fe47acc8d1 =
id3 = status + "rec",
id4 = status + "help";
HTML +=
- "
nowValue ? "#00FF00" : "#FF0000";
- nowValue = core.formatBigNumber(nowValue);
- newValue = core.formatBigNumber(newValue);
-
- if (name === "mdef") {
- nowValue += '%';
- newValue += '%'
- }
- statusText +=
- core.getStatusLabel(name) +
- " " +
- nowValue +
- "->\r[" +
- color +
- "]" +
- newValue +
- "\r\n";
- }
- }
- 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,
- });
- }
-
- ///// *** 退出按钮设置
- var btnRadius = 10;
- var btnBorderWidth = 2;
- var btnRight = toolboxRight - 2;
- var btnBottom = toolboxBottom - 2;
- var btnBorderStyle = "#fff";
- ///// ***
-
- // 获取圆心位置
- var btn_x = btnRight - btnRadius - btnBorderWidth / 2;
- btn_y = btnBottom - btnRadius - btnBorderWidth / 2;
- drawToolbox_setExitBtn(ctx, btn_x, btn_y, btnRadius, btnBorderStyle, btnBorderWidth);
-
- ///// *** 使用按钮设置
- var useBtnHeight = btnRadius * 2;
- // 这里不设置useBtnWidth而是根据各项数据自动得出width
- var useBtnRadius = useBtnHeight / 4;
- var useBtn_x = rightbar_x + 4,
- useBtn_y = btnBottom - useBtnHeight;
- var useBtnBorderStyle = "#fff";
- var useBtnBorderWidth = btnBorderWidth;
- const batchUseBtn_x = useBtn_x + 50; // 个人觉得,搞这么多参数还不如硬编码
- const hideBtn_y = useBtn_y - useBtnHeight - 8;
- ///// ***
-
- drawToolbox_setUseBtn(ctx, useBtn_x, useBtn_y, useBtnRadius, useBtnHeight, useBtnBorderStyle, useBtnBorderWidth);
- if (core.status.event.id === 'toolbox') {
- drawToolbox_setBatchUseBtn(ctx, batchUseBtn_x, useBtn_y, useBtnRadius, useBtnHeight, useBtnBorderStyle, useBtnBorderWidth);
- }
- drawToolbox_setHideBtn(ctx, useBtn_x, hideBtn_y, useBtnRadius, useBtnHeight, useBtnBorderStyle, useBtnBorderWidth);
- drawToolbox_setShowHideBtn(ctx, rightbar_x, useBtn_y, useBtnHeight, useBtnBorderStyle);
- }
-
- function drawEquipbox_drawOthers(ctx, obj) {
- var info = core.status.thisUIEventInfo;
-
- ///// *** 装备格设置
- var equipList_lineWidth = 2;
- var equipList_boxSize = 32;
- var equipList_borderWidth = 2;
- var equipList_borderStyle = "#fff";
- var equipList_nameColor = "#fff";
- ///// ***
-
- 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;
-
- ///// *** 装备格设置
- 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;
-
- 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;
- 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 + ")"
- );
- 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;
-
- 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;
- 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 + ")"
- );
- addUIEventListener(
- box_x - box_borderWidth / 2,
- box_y - box_borderWidth / 2,
- oneBoxWidth,
- oneBoxHeight,
- todo
- );
- box_x += space_x + oneBoxWidth;
- }
-
- }
-
- this.drawToolbox = function (ctx) {
- ctx = ctx || core.canvas.ui;
- core.status.thisEventClickArea = [];
-
- var info = drawBoxBackground(ctx);
- info.itemNum = itemNum;
- drawItemListbox(ctx, info.obj);
- drawToolboxRightbar(ctx, info);
- core.setTextBaseline(ctx, "alphabetic");
- core.setTextAlign("left");
- }
-
- var reduceItem = 4;
- this.drawEquipbox = function (ctx) {
- ctx = ctx || core.canvas.ui;
- core.status.thisEventClickArea = [];
- var info = drawBoxBackground(ctx);
- info.itemNum = itemNum - reduceItem;
- info.obj.y += info.obj.oneItemHeight * reduceItem;
- info.obj.height -= info.obj.oneItemHeight * reduceItem;
- drawItemListbox(ctx, info.obj);
- drawEquipbox_drawOthers(ctx, info);
- drawToolboxRightbar(ctx, info);
- core.setTextBaseline(ctx, "alphabetic");
- core.setTextAlign("left");
- }
-
-
- function drawEquipbox_drawOne(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.setAlpha(ctx, 1);
-
- core.setTextBaseline(ctx, "alphabetic");
- core.setTextAlign("left");
- }
-
- function drawItemListbox_drawItem(ctx, left, right, top, height, marginLeft, marginHeight, style, id) {
- var info = core.status.thisUIEventInfo;
- 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));
-
- const hideInfo = core.getFlag('hideInfo', {});
- if (item && (hideInfo.hasOwnProperty(id) ? hideInfo[id] : item.hideInToolbox)) core.setAlpha(ctx, 0.5);
-
- 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);
- core.setAlpha(ctx, 1);
-
- var todo = new Function("core.clickItemFunc('" + id + "');");
- addUIEventListener(left, top, right - left, height, todo);
- }
-
- function setPageItems(page) {
- var num = itemNum;
- if (core.status.event.id == "equipbox") num -= reduceItem;
- var info = core.status.thisUIEventInfo;
- if (!info) return;
- page = page || info.page;
- var items = core.getToolboxItems(core.status.event.id == "toolbox" ? "all" : "equips", core.getFlag('showHideItem', false));
- 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 setPageItems(info.page);
- }
- return pageItems;
- }
-
- function drawToolbox_setExitBtn(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");
-
- var todo = function () {
- core.closePanel();
- }
- addUIEventListener(x - r, y - r, r * 2, r * 2, todo);
- }
-
- function drawToolbox_setUseBtn(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;
-
- core.strokeRoundRect(ctx, x, y, w, h, r, style, lineWidth);
- core.fillText(ctx, text, x + r, y + lineWidth / 2 + 2, style, font);
-
- var todo = function () {
- core.useSelectItemInBox();
- }
- addUIEventListener(x, y, w, h, todo);
- }
-
- function getSelectedItem() {
- var info = core.status.thisUIEventInfo;
- if (!(info && info.select.id && ["toolbox", "equipbox"].includes(core.status.event.id))) {
- core.drawFailTip('发生了未知错误!');
- return;
- }
- return info.select.id;
- }
-
- function batchUse(item, count) {
- try {
- const itemCount = core.itemCount(item);
- if (count > itemCount) count = itemCount;
- core.closePanel();
- for (let i = 0; i < count; i++) {
- if (core.canUseItem(item)) core.useItem(item);
- else return;
- }
- } catch (e) {
- console.error(e);
- core.drawFailTip('批量使用时出现未知错误!');
- }
- }
-
- function drawToolbox_setBatchUseBtn(ctx, x, y, r, h, style, lineWidth) {
- try {
- const selectedItem = getSelectedItem();
- let canBatchUse = eval(core.material.items[selectedItem]?.canBatchUse);
- if (!canBatchUse) return;
- } catch (error) {
- console.error(error);
- return;
- }
- core.setTextAlign(ctx, "left");
- core.setTextBaseline(ctx, "top");
- var fontSize = h - 4;
- var font = core.ui._buildFont(fontSize);
- var text = "批量使用";
- var w = core.calWidth(ctx, text, font) + 2 * r + lineWidth / 2;
-
- core.strokeRoundRect(ctx, x, y, w, h, r, style, lineWidth);
- core.fillText(ctx, text, x + r, y + lineWidth / 2 + 2, style, font);
-
- var todo = function () {
- core.utils.myprompt('输入要使用该物品的次数(0~99)。', null, (value) => {
-
- value = parseInt(value);
- const id = getSelectedItem();
-
- if (Number.isNaN(value) || value < 0 || value > 99) {
- core.drawFailTip('输入不合法!');
- return;
- }
- if (!core.canUseItem(id)) {
- core.drawFailTip('当前无法使用该道具!');
- return;
- }
- core.closePanel();
- batchUse(id, value);
- });
- }
- addUIEventListener(x, y, w, h, todo);
- }
-
- function drawToolbox_setHideBtn(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 = "显示/隐藏";
- var w = core.calWidth(ctx, text, font) + 2 * r + lineWidth / 2;
-
- core.strokeRoundRect(ctx, x, y, w, h, r, style, lineWidth);
- core.fillText(ctx, text, x + r, y + lineWidth / 2 + 2, style, font);
-
- var todo = function () {
- //debugger;
- var id = getSelectedItem();
- let hideInfo = core.getFlag('hideInfo', {});
- console.log(id);
- if (hideInfo.hasOwnProperty(id)) {
- hideInfo[id] = !hideInfo[id];
- core.setFlag('hideInfo', hideInfo);
- } else {
- hideInfo[id] = !core.material.items[id].hideInToolbox;
- core.setFlag('hideInfo', hideInfo);
- }
- if (core.status.event.id === 'toolbox') core.plugin.drawToolbox();
- else if (core.status.event.id === 'equipbox') core.plugin.drawEquipbox();
- }
- addUIEventListener(x, y, w, h, todo);
- }
-
- ui.prototype.getToolboxItems = function (cls, showHide) {
- let list = Object.keys(core.status.hero.items[cls] || {});;
- if (cls === "all") {
- for (let name in core.status.hero.items) {
- if (name == "equips") continue;
- list = list.concat(Object.keys(core.status.hero.items[name]));
- }
- if (!showHide) list = list.filter(function (id2) {
- const hideInfo = core.getFlag("hideInfo", {});
- if (hideInfo.hasOwnProperty(id2)) return !hideInfo[id2];
- else return !core.material.items[id2].hideInToolbox;
- });
- list = list.sort();
- return list;
- }
- if (cls === "equips") {
- if (!showHide) list = list.filter(function (id2) {
- const hideInfo = core.getFlag("hideInfo", {});
- if (hideInfo.hasOwnProperty(id2)) return !hideInfo[id2];
- else return !core.material.items[id2].hideInToolbox;
- });
-
- list = list.sort();
- return list;
- }
- if (this.uidata.getToolboxItems) {
- return this.uidata.getToolboxItems(cls, showHide);
- }
- if (!showHide) list = list.filter(function (id2) {
- return !core.material.items[id2].hideInToolbox;
- });
- list = list.sort();
- return list;
- };
-
- function drawToolbox_setShowHideBtn(ctx, x, y, h, style) {
- core.setTextAlign(ctx, "left");
- core.setTextBaseline(ctx, "top");
- var fontSize = h - 6;
- var font = core.ui._buildFont(fontSize);
- var text = "显示隐藏";
- var w = core.calWidth(ctx, text, font)
- h += 4;
- const squareSize = h - 6;
-
- x -= w + squareSize + 26;
-
- const border = 2;
- core.fillRect(ctx, x, y, squareSize, squareSize, ' #F5F5F5');
- if (core.hasFlag('showHideItem')) {
- core.fillRect(ctx, x + border, y + border, squareSize - 2 * border, squareSize - 2 * border, 'lime');
- }
- core.fillText(ctx, text, x + squareSize + 2, y + 4, style, font);
-
- var todo = function () {
- core.setFlag('showHideItem', !core.getFlag('showHideItem', false));
- if (core.status.event.id === 'toolbox') core.plugin.drawToolbox();
- else if (core.status.event.id === 'equipbox') core.plugin.drawEquipbox();
- }
- addUIEventListener(x, y, w, h, todo);
- }
-
- function drawItemListbox_setPageBtn(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);
- }
- 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);
- }
- addUIEventListener(x - r - 2, y - r - 2, r * 2 + 4, r * 2 + 4, todo);
- }
-
- 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');
- refreshBox();
- }
-
- 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 refreshBox();
- }
-
- 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);
- });
- }
- if (equipCls0 === "双手剑" && !(equipClsid === "饰品" || equipClsid === "护具")) {
- core.unloadEquip(0, function () {
- core.status.route.push("unEquip:" + 0);
- });
- }
- core.loadEquip(id, function () {
- core.status.route.push("equip:" + id);
- info.select.type = type;
- core.setIndexAndSelect("select");
- core.drawEquipbox();
- });
- } else {
- 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();
- });
- }
- }
- core.updateStatusBar();
- }
-
- this.setIndexAndSelect = function (toChange) {
- var info = core.status.thisUIEventInfo;
- if (!info) return;
- setPageItems(info.page);
- var index = info.index || 1;
- var items = info.pageItems;
-
- 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];
-
-
- }
-
- 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;
- setPageItems(info.page);
- core.setIndexAndSelect("select");
- refreshBox();
- }
-
- 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");
- refreshBox();
- }
-
- 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")
- refreshBox();
- return;
- }
- }
-
- core.actions._keyDownToolbox = function (keycode) {
- if (!core.status.thisEventClickArea) return;
- if (keycode == 37) { // left
- core.addItemListboxPage(-1);
- return;
- }
- if (keycode == 38) { // up
- core.addItemListboxIndex(-1);
- return;
- }
- if (keycode == 39) { // right
- core.addItemListboxPage(1);
- return;
- }
- if (keycode == 40) { // down
- core.addItemListboxIndex(1);
- return;
- }
- }
-
- ////// 工具栏界面时,放开某个键的操作 //////
- 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();
- }
- return;
- }
- }
-
- 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 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);
- }
- }
-
- 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;
- }
- }
-
- core.registerAction("ondown", "inEventClickAction", function (x, y, px, py) {
- if (!core.status.thisEventClickArea) return false;
- 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;
- }
- }
- return true;
- }, 51);
- core.registerAction("onclick", "stopClick", function () {
- if (core.status.thisEventClickArea) return true;
- }, 51);
-
- function addUIEventListener(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);
- }
-
- this.initThisEventInfo = function () {
- core.status.thisUIEventInfo = {
- page: 1,
- select: {}
- };
- core.status.thisEventClickArea = [];
- }
-
- function refreshBox() {
- if (!core.status.event.id) return;
- if (core.status.event.id == "toolbox") core.drawToolbox();
- else core.drawEquipbox();
- }
-
- 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];
- }
- });
- }
- 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
- }
-
- 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;
- }
-
- 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();
- }
-
- 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();
- }
-
- 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();
- }
-
- 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("请等待当前事件的处理结束");
-
- core.lockControl();
- core.status.event.id = 'equipbox';
- core.drawEquipbox();
- }
-
- 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;
- }
- core.status.event.id = "toolbox";
- core.initThisEventInfo();
- var info = core.status.thisUIEventInfo;
- var items = core.getToolboxItems("all", core.getFlag('showHideItem', false));
- 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");
- setPageItems(info.page);
- core.drawToolbox();
- setTimeout(function () {
- core.ui.closePanel();
- core.useItem(itemId, false, core.replay);
- }, core.control.__replay_getTimeout());
- return true;
- }
-
- core.control._replayAction_equip = function (action) {
- if (action.indexOf("equip:") != 0) return false;
- var itemId = action.substring(6);
- var items = core.getToolboxItems('equips', core.getFlag('showHideItem', false));
- var index = items.indexOf(itemId) + 1;
- if (index < 1) {
- core.removeFlag('__doNotCheckAutoEvents__');
- return false;
- }
-
- var cb = function () {
- var next = core.status.replay.toReplay[0] || "";
- if (!next.startsWith('equip:') && !next.startsWith('unEquip:')) {
- core.removeFlag('__doNotCheckAutoEvents__');
- core.checkAutoEvents();
- }
- core.replay();
- }
- core.setFlag('__doNotCheckAutoEvents__', true);
-
- core.status.route.push(action);
- if (core.material.items[itemId].hideInReplay || core.status.replay.speed == 24) {
- core.loadEquip(itemId, cb);
- return true;
- }
- core.status.event.id = "equipbox";
- core.initThisEventInfo();
- var info = core.status.thisUIEventInfo;
- setPageItems(1);
- info.page = Math.ceil(index / info.maxItem);
- info.index = index % info.maxItem || info.maxItem;
- core.setIndexAndSelect("select");
- setPageItems(info.page);
- core.drawEquipbox();
- setTimeout(function () {
- core.ui.closePanel();
- core.loadEquip(itemId, cb);
- }, core.control.__replay_getTimeout());
- return true;
- }
-
- core.control._replayAction_unEquip = function (action) {
- if (action.indexOf("unEquip:") != 0) return false;
- var equipType = parseInt(action.substring(8));
- if (!core.isset(equipType)) {
- core.removeFlag('__doNotCheckAutoEvents__');
- return false;
- }
-
- var cb = function () {
- var next = core.status.replay.toReplay[0] || "";
- if (!next.startsWith('equip:') && !next.startsWith('unEquip:')) {
- core.removeFlag('__doNotCheckAutoEvents__');
- core.checkAutoEvents();
- }
- core.replay();
- }
- core.setFlag('__doNotCheckAutoEvents__', true);
-
- core.status.route.push(action);
- if (core.status.replay.speed == 24) {
- core.unloadEquip(equipType, cb);
- return true;
- }
- core.status.event.id = "equipbox";
- core.initThisEventInfo();
- var info = core.status.thisUIEventInfo;
- setPageItems(1);
- info.select.type = equipType;
- core.setIndexAndSelect();
- core.drawEquipbox();
- setTimeout(function () {
- core.ui.closePanel();
- core.unloadEquip(equipType, cb);
- }, core.control.__replay_getTimeout());
- return true;
- }
- core.registerReplayAction("item", core.control._replayAction_item);
- core.registerReplayAction("equip", core.control._replayAction_equip);
- core.registerReplayAction("unEquip", core.control._replayAction_unEquip);
-},
+ 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);
+
+ ///// *** 左栏配置
+ 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 = 2;
+ // 右箭头
+ 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(
+ ctx,
+ arrow_x + arrow_width,
+ arrow_y,
+ arrow_x,
+ arrow_y,
+ arrow_style,
+ arrow_lineWidth
+ );
+ 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();
+ };
+ core.fillText(
+ ctx,
+ boxName,
+ (leftbar_right + leftbar_x) / 2,
+ arrow_y + 2,
+ boxName_color,
+ boxName_font
+ );
+
+ ///// *** 底栏按钮
+ 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;
+ ///// ***
+
+ drawItemListbox_setPageBtn(
+ ctx,
+ pageBtn_left,
+ pageBtn_right,
+ pageBtn_bottom,
+ pageBtn_radius,
+ pageBtn_borderStyle,
+ pageBtn_borderWidth
+ );
+ var page = info.page || 1;
+ var pageFontSize = pageBtn_radius * 2 - 4;
+ var pageFont = core.ui._buildFont(pageFontSize);
+ 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
+ );
+ 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,
+ },
+ };
+ }
+
+ function drawItemListbox(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 || {};
+
+ ///// *** 道具栏内栏配置
+ 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";
+ ///// ***
+
+ core.setAlpha(ctx, itembar_alpha);
+ core.fillRect(
+ ctx,
+ itembar_x,
+ itembar_y,
+ itembar_width,
+ itembar_height,
+ itembar_style
+ );
+ core.setAlpha(ctx, 1);
+ var pageItems = 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";
+ 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;
+ }
+ }
+
+ function drawToolboxRightbar(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;
+
+ ///// *** 侧边栏(rightbar)背景设置(物品介绍)
+ var rightbar_width = width * 0.4;
+ var rightbar_height = height;
+ var rightbar_lineWidth = 2;
+ var rightbar_lineStyle = "#fff";
+ ///// ***
+
+ 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
+ );
+
+ // 获取道具id(有可能为null)
+ var itemId = select.id;
+ var item = core.material.items[itemId];
+
+ ///// *** 侧边栏物品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;
+ ///// ***
+
+ var iconRect_bottom = iconRect_y + iconRect_height,
+ iconRect_right = iconRect_x + iconRect_width;
+
+ ///// *** 侧边栏各项信息
+ 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: "一无所有,又何尝不是一种装备",
+ equip: {
+ type: "装备",
+ },
+ };
+ ///// ***
+
+ 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) itemText = "该道具无描述。";
+ /* 一个根据道具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;
+ }
+ 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 (
+ item.equipCls === "盾牌" &&
+ core.material.items[core.getEquip(0)]?.equipCls === "双手剑"
+ )
+ compare2 = core.compareEquipment(null, core.getEquip(0));
+ 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 (name === "mdef") {
+ var nowValue = core.getRealStatus(name);
+ var newValue = Math.round(
+ (core.getStatus(name) -
+ (compare.value[name] || 0) -
+ (compare2?.value[name] || 0)) *
+ (1 -
+ (1 - core.getBuff(name)) *
+ (compare.percentage[name] || 1) *
+ (compare2?.percentage[name] || 1))
+ );
+ }
+ if (nowValue == newValue) continue;
+ var color = newValue > nowValue ? "#00FF00" : "#FF0000";
+ nowValue = core.formatBigNumber(nowValue);
+ newValue = core.formatBigNumber(newValue);
+
+ if (name === "mdef") {
+ nowValue += "%";
+ newValue += "%";
+ }
+ statusText +=
+ core.getStatusLabel(name) +
+ " " +
+ nowValue +
+ "->\r[" +
+ color +
+ "]" +
+ newValue +
+ "\r\n";
+ }
+ }
+ 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,
+ });
+ }
+
+ ///// *** 退出按钮设置
+ var btnRadius = 10;
+ var btnBorderWidth = 2;
+ var btnRight = toolboxRight - 2;
+ var btnBottom = toolboxBottom - 2;
+ var btnBorderStyle = "#fff";
+ ///// ***
+
+ // 获取圆心位置
+ var btn_x = btnRight - btnRadius - btnBorderWidth / 2;
+ btn_y = btnBottom - btnRadius - btnBorderWidth / 2;
+ drawToolbox_setExitBtn(
+ ctx,
+ btn_x,
+ btn_y,
+ btnRadius,
+ btnBorderStyle,
+ btnBorderWidth
+ );
+
+ ///// *** 使用按钮设置
+ var useBtnHeight = btnRadius * 2;
+ // 这里不设置useBtnWidth而是根据各项数据自动得出width
+ var useBtnRadius = useBtnHeight / 4;
+ var useBtn_x = rightbar_x + 4,
+ useBtn_y = btnBottom - useBtnHeight;
+ var useBtnBorderStyle = "#fff";
+ var useBtnBorderWidth = btnBorderWidth;
+ const batchUseBtn_x = useBtn_x + 50; // 个人觉得,搞这么多参数还不如硬编码
+ const hideBtn_y = useBtn_y - useBtnHeight - 8;
+ ///// ***
+
+ drawToolbox_setUseBtn(
+ ctx,
+ useBtn_x,
+ useBtn_y,
+ useBtnRadius,
+ useBtnHeight,
+ useBtnBorderStyle,
+ useBtnBorderWidth
+ );
+ if (core.status.event.id === "toolbox") {
+ drawToolbox_setBatchUseBtn(
+ ctx,
+ batchUseBtn_x,
+ useBtn_y,
+ useBtnRadius,
+ useBtnHeight,
+ useBtnBorderStyle,
+ useBtnBorderWidth
+ );
+ }
+ drawToolbox_setHideBtn(
+ ctx,
+ useBtn_x,
+ hideBtn_y,
+ useBtnRadius,
+ useBtnHeight,
+ useBtnBorderStyle,
+ useBtnBorderWidth
+ );
+ drawToolbox_setShowHideBtn(
+ ctx,
+ rightbar_x,
+ useBtn_y,
+ useBtnHeight,
+ useBtnBorderStyle
+ );
+ }
+
+ function drawEquipbox_drawOthers(ctx, obj) {
+ var info = core.status.thisUIEventInfo;
+
+ ///// *** 装备格设置
+ var equipList_lineWidth = 2;
+ var equipList_boxSize = 32;
+ var equipList_borderWidth = 2;
+ var equipList_borderStyle = "#fff";
+ var equipList_nameColor = "#fff";
+ ///// ***
+
+ 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;
+
+ ///// *** 装备格设置
+ 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;
+
+ 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;
+ 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 + ")"
+ );
+ 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;
+
+ 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;
+ 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 + ")"
+ );
+ addUIEventListener(
+ box_x - box_borderWidth / 2,
+ box_y - box_borderWidth / 2,
+ oneBoxWidth,
+ oneBoxHeight,
+ todo
+ );
+ box_x += space_x + oneBoxWidth;
+ }
+ }
+
+ this.drawToolbox = function (ctx) {
+ ctx = ctx || core.canvas.ui;
+ core.status.thisEventClickArea = [];
+
+ var info = drawBoxBackground(ctx);
+ info.itemNum = itemNum;
+ drawItemListbox(ctx, info.obj);
+ drawToolboxRightbar(ctx, info);
+ core.setTextBaseline(ctx, "alphabetic");
+ core.setTextAlign("left");
+ };
+
+ var reduceItem = 4;
+ this.drawEquipbox = function (ctx) {
+ ctx = ctx || core.canvas.ui;
+ core.status.thisEventClickArea = [];
+ var info = drawBoxBackground(ctx);
+ info.itemNum = itemNum - reduceItem;
+ info.obj.y += info.obj.oneItemHeight * reduceItem;
+ info.obj.height -= info.obj.oneItemHeight * reduceItem;
+ drawItemListbox(ctx, info.obj);
+ drawEquipbox_drawOthers(ctx, info);
+ drawToolboxRightbar(ctx, info);
+ core.setTextBaseline(ctx, "alphabetic");
+ core.setTextAlign("left");
+ };
+
+ function drawEquipbox_drawOne(
+ 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.setAlpha(ctx, 1);
+
+ core.setTextBaseline(ctx, "alphabetic");
+ core.setTextAlign("left");
+ }
+
+ function drawItemListbox_drawItem(
+ ctx,
+ left,
+ right,
+ top,
+ height,
+ marginLeft,
+ marginHeight,
+ style,
+ id
+ ) {
+ var info = core.status.thisUIEventInfo;
+ 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)
+ );
+
+ const hideInfo = core.getFlag("hideInfo", {});
+ if (
+ item &&
+ (hideInfo.hasOwnProperty(id) ? hideInfo[id] : item.hideInToolbox)
+ )
+ core.setAlpha(ctx, 0.5);
+
+ 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
+ );
+ core.setAlpha(ctx, 1);
+
+ var todo = new Function("core.clickItemFunc('" + id + "');");
+ addUIEventListener(left, top, right - left, height, todo);
+ }
+
+ function setPageItems(page) {
+ var num = itemNum;
+ if (core.status.event.id == "equipbox") num -= reduceItem;
+ var info = core.status.thisUIEventInfo;
+ if (!info) return;
+ page = page || info.page;
+ var items = core.getToolboxItems(
+ core.status.event.id == "toolbox" ? "all" : "equips",
+ core.getFlag("showHideItem", false)
+ );
+ 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 setPageItems(info.page);
+ }
+ return pageItems;
+ }
+
+ function drawToolbox_setExitBtn(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");
+
+ var todo = function () {
+ core.closePanel();
+ };
+ addUIEventListener(x - r, y - r, r * 2, r * 2, todo);
+ }
+
+ function drawToolbox_setUseBtn(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;
+
+ core.strokeRoundRect(ctx, x, y, w, h, r, style, lineWidth);
+ core.fillText(ctx, text, x + r, y + lineWidth / 2 + 2, style, font);
+
+ var todo = function () {
+ core.useSelectItemInBox();
+ };
+ addUIEventListener(x, y, w, h, todo);
+ }
+
+ function getSelectedItem() {
+ var info = core.status.thisUIEventInfo;
+ if (
+ !(
+ info &&
+ info.select.id &&
+ ["toolbox", "equipbox"].includes(core.status.event.id)
+ )
+ ) {
+ core.drawFailTip("发生了未知错误!");
+ return;
+ }
+ return info.select.id;
+ }
+
+ function batchUse(item, count) {
+ try {
+ const itemCount = core.itemCount(item);
+ if (count > itemCount) count = itemCount;
+ core.closePanel();
+ for (let i = 0; i < count; i++) {
+ if (core.canUseItem(item)) core.useItem(item);
+ else return;
+ }
+ } catch (e) {
+ console.error(e);
+ core.drawFailTip("批量使用时出现未知错误!");
+ }
+ }
+
+ function drawToolbox_setBatchUseBtn(ctx, x, y, r, h, style, lineWidth) {
+ try {
+ const selectedItem = getSelectedItem();
+ let canBatchUse = eval(core.material.items[selectedItem]?.canBatchUse);
+ if (!canBatchUse) return;
+ } catch (error) {
+ console.error(error);
+ return;
+ }
+ core.setTextAlign(ctx, "left");
+ core.setTextBaseline(ctx, "top");
+ var fontSize = h - 4;
+ var font = core.ui._buildFont(fontSize);
+ var text = "批量使用";
+ var w = core.calWidth(ctx, text, font) + 2 * r + lineWidth / 2;
+
+ core.strokeRoundRect(ctx, x, y, w, h, r, style, lineWidth);
+ core.fillText(ctx, text, x + r, y + lineWidth / 2 + 2, style, font);
+
+ var todo = function () {
+ core.utils.myprompt("输入要使用该物品的次数(0~99)。", null, (value) => {
+ value = parseInt(value);
+ const id = getSelectedItem();
+
+ if (Number.isNaN(value) || value < 0 || value > 99) {
+ core.drawFailTip("输入不合法!");
+ return;
+ }
+ if (!core.canUseItem(id)) {
+ core.drawFailTip("当前无法使用该道具!");
+ return;
+ }
+ core.closePanel();
+ batchUse(id, value);
+ });
+ };
+ addUIEventListener(x, y, w, h, todo);
+ }
+
+ function drawToolbox_setHideBtn(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 = "显示/隐藏";
+ var w = core.calWidth(ctx, text, font) + 2 * r + lineWidth / 2;
+
+ core.strokeRoundRect(ctx, x, y, w, h, r, style, lineWidth);
+ core.fillText(ctx, text, x + r, y + lineWidth / 2 + 2, style, font);
+
+ var todo = function () {
+ //debugger;
+ var id = getSelectedItem();
+ let hideInfo = core.getFlag("hideInfo", {});
+ console.log(id);
+ if (hideInfo.hasOwnProperty(id)) {
+ hideInfo[id] = !hideInfo[id];
+ core.setFlag("hideInfo", hideInfo);
+ } else {
+ hideInfo[id] = !core.material.items[id].hideInToolbox;
+ core.setFlag("hideInfo", hideInfo);
+ }
+ if (core.status.event.id === "toolbox") core.plugin.drawToolbox();
+ else if (core.status.event.id === "equipbox")
+ core.plugin.drawEquipbox();
+ };
+ addUIEventListener(x, y, w, h, todo);
+ }
+
+ ui.prototype.getToolboxItems = function (cls, showHide) {
+ let list = Object.keys(core.status.hero.items[cls] || {});
+ if (cls === "all") {
+ for (let name in core.status.hero.items) {
+ if (name == "equips") continue;
+ list = list.concat(Object.keys(core.status.hero.items[name]));
+ }
+ if (!showHide)
+ list = list.filter(function (id2) {
+ const hideInfo = core.getFlag("hideInfo", {});
+ if (hideInfo.hasOwnProperty(id2)) return !hideInfo[id2];
+ else return !core.material.items[id2].hideInToolbox;
+ });
+ list = list.sort();
+ return list;
+ }
+ if (cls === "equips") {
+ if (!showHide)
+ list = list.filter(function (id2) {
+ const hideInfo = core.getFlag("hideInfo", {});
+ if (hideInfo.hasOwnProperty(id2)) return !hideInfo[id2];
+ else return !core.material.items[id2].hideInToolbox;
+ });
+
+ list = list.sort();
+ return list;
+ }
+ if (this.uidata.getToolboxItems) {
+ return this.uidata.getToolboxItems(cls, showHide);
+ }
+ if (!showHide)
+ list = list.filter(function (id2) {
+ return !core.material.items[id2].hideInToolbox;
+ });
+ list = list.sort();
+ return list;
+ };
+
+ function drawToolbox_setShowHideBtn(ctx, x, y, h, style) {
+ core.setTextAlign(ctx, "left");
+ core.setTextBaseline(ctx, "top");
+ var fontSize = h - 6;
+ var font = core.ui._buildFont(fontSize);
+ var text = "显示隐藏";
+ var w = core.calWidth(ctx, text, font);
+ h += 4;
+ const squareSize = h - 6;
+
+ x -= w + squareSize + 26;
+
+ const border = 2;
+ core.fillRect(ctx, x, y, squareSize, squareSize, " #F5F5F5");
+ if (core.hasFlag("showHideItem")) {
+ core.fillRect(
+ ctx,
+ x + border,
+ y + border,
+ squareSize - 2 * border,
+ squareSize - 2 * border,
+ "lime"
+ );
+ }
+ core.fillText(ctx, text, x + squareSize + 2, y + 4, style, font);
+
+ var todo = function () {
+ core.setFlag("showHideItem", !core.getFlag("showHideItem", false));
+ if (core.status.event.id === "toolbox") core.plugin.drawToolbox();
+ else if (core.status.event.id === "equipbox")
+ core.plugin.drawEquipbox();
+ };
+ addUIEventListener(x, y, w, h, todo);
+ }
+
+ function drawItemListbox_setPageBtn(
+ 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);
+ };
+ 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);
+ };
+ addUIEventListener(x - r - 2, y - r - 2, r * 2 + 4, r * 2 + 4, todo);
+ }
+
+ 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");
+ refreshBox();
+ };
+
+ 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 refreshBox();
+ };
+
+ 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);
+ });
+ }
+ if (
+ equipCls0 === "双手剑" &&
+ !(equipClsid === "饰品" || equipClsid === "护具")
+ ) {
+ core.unloadEquip(0, function () {
+ core.status.route.push("unEquip:" + 0);
+ });
+ }
+ core.loadEquip(id, function () {
+ core.status.route.push("equip:" + id);
+ info.select.type = type;
+ core.setIndexAndSelect("select");
+ core.drawEquipbox();
+ });
+ } else {
+ 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();
+ });
+ }
+ }
+ core.updateStatusBar();
+ };
+
+ this.setIndexAndSelect = function (toChange) {
+ var info = core.status.thisUIEventInfo;
+ if (!info) return;
+ setPageItems(info.page);
+ var index = info.index || 1;
+ var items = info.pageItems;
+
+ 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];
+ };
+
+ 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;
+ setPageItems(info.page);
+ core.setIndexAndSelect("select");
+ refreshBox();
+ };
+
+ 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");
+ refreshBox();
+ };
+
+ 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");
+ refreshBox();
+ return;
+ }
+ };
+
+ core.actions._keyDownToolbox = function (keycode) {
+ if (!core.status.thisEventClickArea) return;
+ if (keycode == 37) {
+ // left
+ core.addItemListboxPage(-1);
+ return;
+ }
+ if (keycode == 38) {
+ // up
+ core.addItemListboxIndex(-1);
+ return;
+ }
+ if (keycode == 39) {
+ // right
+ core.addItemListboxPage(1);
+ return;
+ }
+ if (keycode == 40) {
+ // down
+ core.addItemListboxIndex(1);
+ return;
+ }
+ };
+
+ ////// 工具栏界面时,放开某个键的操作 //////
+ 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();
+ }
+ return;
+ }
+ };
+
+ 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 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);
+ }
+ };
+
+ 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;
+ }
+ };
+
+ core.registerAction(
+ "ondown",
+ "inEventClickAction",
+ function (x, y, px, py) {
+ if (!core.status.thisEventClickArea) return false;
+ 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;
+ }
+ }
+ return true;
+ },
+ 51
+ );
+ core.registerAction(
+ "onclick",
+ "stopClick",
+ function () {
+ if (core.status.thisEventClickArea) return true;
+ },
+ 51
+ );
+
+ function addUIEventListener(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);
+ }
+
+ this.initThisEventInfo = function () {
+ core.status.thisUIEventInfo = {
+ page: 1,
+ select: {},
+ };
+ core.status.thisEventClickArea = [];
+ };
+
+ function refreshBox() {
+ if (!core.status.event.id) return;
+ if (core.status.event.id == "toolbox") core.drawToolbox();
+ else core.drawEquipbox();
+ }
+
+ 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];
+ }
+ });
+ }
+ 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;
+ };
+
+ 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;
+ };
+
+ 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();
+ };
+
+ 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();
+ };
+
+ 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();
+ };
+
+ 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("请等待当前事件的处理结束");
+
+ core.lockControl();
+ core.status.event.id = "equipbox";
+ core.drawEquipbox();
+ };
+
+ 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;
+ }
+ core.status.event.id = "toolbox";
+ core.initThisEventInfo();
+ var info = core.status.thisUIEventInfo;
+ var items = core.getToolboxItems(
+ "all",
+ core.getFlag("showHideItem", false)
+ );
+ 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");
+ setPageItems(info.page);
+ core.drawToolbox();
+ setTimeout(function () {
+ core.ui.closePanel();
+ core.useItem(itemId, false, core.replay);
+ }, core.control.__replay_getTimeout());
+ return true;
+ };
+
+ core.control._replayAction_equip = function (action) {
+ if (action.indexOf("equip:") != 0) return false;
+ var itemId = action.substring(6);
+ var items = core.getToolboxItems(
+ "equips",
+ core.getFlag("showHideItem", false)
+ );
+ var index = items.indexOf(itemId) + 1;
+ if (index < 1) {
+ core.removeFlag("__doNotCheckAutoEvents__");
+ return false;
+ }
+
+ var cb = function () {
+ var next = core.status.replay.toReplay[0] || "";
+ if (!next.startsWith("equip:") && !next.startsWith("unEquip:")) {
+ core.removeFlag("__doNotCheckAutoEvents__");
+ core.checkAutoEvents();
+ }
+ core.replay();
+ };
+ core.setFlag("__doNotCheckAutoEvents__", true);
+
+ core.status.route.push(action);
+ if (
+ core.material.items[itemId].hideInReplay ||
+ core.status.replay.speed == 24
+ ) {
+ core.loadEquip(itemId, cb);
+ return true;
+ }
+ core.status.event.id = "equipbox";
+ core.initThisEventInfo();
+ var info = core.status.thisUIEventInfo;
+ setPageItems(1);
+ info.page = Math.ceil(index / info.maxItem);
+ info.index = index % info.maxItem || info.maxItem;
+ core.setIndexAndSelect("select");
+ setPageItems(info.page);
+ core.drawEquipbox();
+ setTimeout(function () {
+ core.ui.closePanel();
+ core.loadEquip(itemId, cb);
+ }, core.control.__replay_getTimeout());
+ return true;
+ };
+
+ core.control._replayAction_unEquip = function (action) {
+ if (action.indexOf("unEquip:") != 0) return false;
+ var equipType = parseInt(action.substring(8));
+ if (!core.isset(equipType)) {
+ core.removeFlag("__doNotCheckAutoEvents__");
+ return false;
+ }
+
+ var cb = function () {
+ var next = core.status.replay.toReplay[0] || "";
+ if (!next.startsWith("equip:") && !next.startsWith("unEquip:")) {
+ core.removeFlag("__doNotCheckAutoEvents__");
+ core.checkAutoEvents();
+ }
+ core.replay();
+ };
+ core.setFlag("__doNotCheckAutoEvents__", true);
+
+ core.status.route.push(action);
+ if (core.status.replay.speed == 24) {
+ core.unloadEquip(equipType, cb);
+ return true;
+ }
+ core.status.event.id = "equipbox";
+ core.initThisEventInfo();
+ var info = core.status.thisUIEventInfo;
+ setPageItems(1);
+ info.select.type = equipType;
+ core.setIndexAndSelect();
+ core.drawEquipbox();
+ setTimeout(function () {
+ core.ui.closePanel();
+ core.unloadEquip(equipType, cb);
+ }, core.control.__replay_getTimeout());
+ return true;
+ };
+ core.registerReplayAction("item", core.control._replayAction_item);
+ core.registerReplayAction("equip", core.control._replayAction_equip);
+ core.registerReplayAction("unEquip", core.control._replayAction_unEquip);
+ },
"技能树": function () {
// 在此增加新插件
//
@@ -8557,519 +8967,517 @@ var plugins_bb40132b_638b_4a9f_b028_d3fe47acc8d1 =
};
},
"func": function () {
- // 功能函数集,具体有哪些函数看每个函数前的注释即可
- // 安装方式:直接复制到插件里面,注意新建插件自带的 function () { } 不能删
- // 使用方式:可以直接使用对象解构按需引入
- // 例如:const { has, slide } = core.plugin.utils;
- // slide([1, 2, 3], -1); // [2, 3, 1]
+ // 功能函数集,具体有哪些函数看每个函数前的注释即可
+ // 安装方式:直接复制到插件里面,注意新建插件自带的 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;
- }
- }
- /**
- * 图片叠加滤镜
- * @param {img} image 需要叠加的图片,
- * @param {color} style 需要叠加的颜色
- */
- function imagelighter(image, style) {
- // 创建一个canvas元素
- const canvas = document.createElement('canvas');
- const ctx = canvas.getContext('2d');
+ /**
+ * 滑动数组,使数组元素平移若干项
+ * @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;
+ }
+ }
+ /**
+ * 图片叠加滤镜
+ * @param {img} image 需要叠加的图片,
+ * @param {color} style 需要叠加的颜色
+ */
+ function imagelighter(image, style) {
+ // 创建一个canvas元素
+ const canvas = document.createElement("canvas");
+ const ctx = canvas.getContext("2d");
- // 设置canvas的尺寸与图片相同
- canvas.width = image.width;
- canvas.height = image.height;
- ctx.drawImage(image, 0, 0);
- // 创建一个临时canvas用于红色滤镜
- const tempCanvas = document.createElement('canvas');
- const tempCtx = tempCanvas.getContext('2d');
- tempCanvas.width = image.width;
- tempCanvas.height = image.height;
+ // 设置canvas的尺寸与图片相同
+ canvas.width = image.width;
+ canvas.height = image.height;
+ ctx.drawImage(image, 0, 0);
+ // 创建一个临时canvas用于红色滤镜
+ const tempCanvas = document.createElement("canvas");
+ const tempCtx = tempCanvas.getContext("2d");
+ tempCanvas.width = image.width;
+ tempCanvas.height = image.height;
- // 在临时canvas上绘制红色滤镜
- tempCtx.fillStyle = style ?? 'rgba(255, 0, 0, 0.5)'; // 半透明红色
- tempCtx.fillRect(0, 0, tempCanvas.width, tempCanvas.height);
- // 使用lighter混合模式叠加红色滤镜
- ctx.globalCompositeOperation = 'lighter';
- ctx.drawImage(tempCanvas, 0, 0);
- // 使用destination-in混合模式保留原始图片的透明度
- ctx.globalCompositeOperation = 'destination-in';
- ctx.drawImage(image, 0, 0);
+ // 在临时canvas上绘制红色滤镜
+ tempCtx.fillStyle = style ?? "rgba(255, 0, 0, 0.5)"; // 半透明红色
+ tempCtx.fillRect(0, 0, tempCanvas.width, tempCanvas.height);
+ // 使用lighter混合模式叠加红色滤镜
+ ctx.globalCompositeOperation = "lighter";
+ ctx.drawImage(tempCanvas, 0, 0);
+ // 使用destination-in混合模式保留原始图片的透明度
+ ctx.globalCompositeOperation = "destination-in";
+ ctx.drawImage(image, 0, 0);
+ // 恢复默认混合模式
+ ctx.globalCompositeOperation = "source-over";
+ // 返回处理后的canvas
+ return canvas;
+ }
+ /**
+ * 获取一个方向的反方向
+ * @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];
+ }
- // 恢复默认混合模式
- ctx.globalCompositeOperation = 'source-over';
+ /**
+ * 判断一个值是否不是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;
+ }
- // 返回处理后的canvas
- return canvas;
- }
- /**
- * 获取一个方向的反方向
- * @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];
- }
+ /**
+ * 解析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 = {};
- /**
- * 判断一个值是否不是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;
- }
+ 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;
+ }
- /**
- * 解析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 = {};
+ /**
+ * 等待一段时间,需在async function中使用,否则报错
+ * @example await sleep(500); // 等待500毫秒
+ * @param {number} time 等待的毫秒数
+ */
+ async function sleep(time) {
+ return new Promise((res) => setTimeout(res, time));
+ }
- 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;
- }
+ /**
+ * 在下一帧的下一帧执行一个函数
+ * @example nextFrame(() => console.log(1)); // 两帧后在控制台输出1
+ * @param cb 执行的函数
+ */
+ function nextFrame(cb) {
+ requestAnimationFrame(() => {
+ requestAnimationFrame(cb);
+ });
+ }
- /**
- * 等待一段时间,需在async function中使用,否则报错
- * @example await sleep(500); // 等待500毫秒
- * @param {number} time 等待的毫秒数
- */
- async function sleep(time) {
- return new Promise((res) => setTimeout(res, time));
- }
+ /**
+ * 将一个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!`);
+ }
- /**
- * 在下一帧的下一帧执行一个函数
- * @example nextFrame(() => console.log(1)); // 两帧后在控制台输出1
- * @param cb 执行的函数
- */
- function nextFrame(cb) {
- requestAnimationFrame(() => {
- requestAnimationFrame(cb);
- });
- }
+ 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);
+ }
+ }
- /**
- * 将一个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!`);
- }
+ /**
+ * 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;
+ };
- 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);
- }
- }
+ 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)];
+ }
+ }
- /**
- * 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;
- };
+ /**
+ * 确保一个变量是一个数组,不是的话转为数组并返回,是的话直接返回传入的数组
+ * @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];
+ }
- 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)];
- }
- }
+ /**
+ * 返回一个坐标在某个方向上移动 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];
+ }
- /**
- * 确保一个变量是一个数组,不是的话转为数组并返回,是的话直接返回传入的数组
- * @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];
- }
+ 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",
+ };
+ // 计算两个数的最大公约数
+ function gcdOfTwo(a, b) {
+ while (b !== 0) {
+ let temp = b;
+ b = a % b;
+ a = temp;
+ }
+ return a;
+ }
- /**
- * 返回一个坐标在某个方向上移动 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];
- }
+ // 计算任意项整数的最大公约数
+ function gcd(...numbers) {
+ if (numbers.length < 2) {
+ throw new Error("至少需要两个数");
+ }
+ return numbers.reduce((a, b) => gcdOfTwo(a, b));
+ }
+ // 计算两个数的最小公倍数
+ function lcmOfTwo(a, b) {
+ return (a * b) / gcdOfTwo(a, b);
+ }
- 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",
- };
- // 计算两个数的最大公约数
- function gcdOfTwo(a, b) {
- while (b !== 0) {
- let temp = b;
- b = a % b;
- a = temp;
- }
- return a;
- }
+ // 计算任意项整数的最小公倍数
+ function lcm(...numbers) {
+ if (numbers.length < 2) {
+ throw new Error("至少需要两个数");
+ }
+ return numbers.reduce((a, b) => lcmOfTwo(a, b));
+ }
- // 计算任意项整数的最大公约数
- function gcd(...numbers) {
- if (numbers.length < 2) {
- throw new Error("至少需要两个数");
- }
- return numbers.reduce((a, b) => gcdOfTwo(a, b));
- }
- // 计算两个数的最小公倍数
- function lcmOfTwo(a, b) {
- return (a * b) / gcdOfTwo(a, b);
- }
+ if (has(core.plugin.utils)) {
+ throw new ReferenceError(
+ `core.plugin上已经有'utils'属性,因此功能函数插件将无法使用!`
+ );
+ }
+ core.plugin.utils = {
+ imagelighter,
+ gcdOfTwo,
+ lcmOfTwo,
+ gcd,
+ lcm,
+ has,
+ slide,
+ backDir,
+ parseCss,
+ sleep,
+ nextFrame,
+ parseColor,
+ hslToRgb,
+ ensureArray,
+ ofDir,
+ };
+ // Utility.js
+ // 通用函數插件
+ // 本插件與古祠發佈的《功能插件 --- 实用功能函数集》不同,函數是定義在全域的。
+ // 自訂常見事件模板插件(editorBlocklyconfigPlus.js)的前置插件
- // 计算任意项整数的最小公倍数
- function lcm(...numbers) {
- if (numbers.length < 2) {
- throw new Error("至少需要两个数");
- }
- return numbers.reduce((a, b) => lcmOfTwo(a, b));
- }
+ /**
+ * 使js暫停指定時間
+ * async環境下await Sleep(500)
+ * @param {number} millisecond 暫停毫秒數
+ */
+ self.Sleep = async function (millisecond) {
+ return new Promise((resolve) => setTimeout(resolve, millisecond));
+ };
- if (has(core.plugin.utils)) {
- throw new ReferenceError(
- `core.plugin上已经有'utils'属性,因此功能函数插件将无法使用!`
- );
- }
- core.plugin.utils = {
- imagelighter,
- gcdOfTwo,
- lcmOfTwo,
- gcd,
- lcm,
- has,
- slide,
- backDir,
- parseCss,
- sleep,
- nextFrame,
- parseColor,
- hslToRgb,
- ensureArray,
- ofDir,
- };
- // Utility.js
- // 通用函數插件
- // 本插件與古祠發佈的《功能插件 --- 实用功能函数集》不同,函數是定義在全域的。
- // 自訂常見事件模板插件(editorBlocklyconfigPlus.js)的前置插件
+ /**
+ * 使js暫停一幀
+ * async環境下await SleepFrame()
+ */
+ self.SleepFrame = async function () {
+ return new Promise((resolve) => requestAnimationFrame(resolve));
+ };
- /**
- * 使js暫停指定時間
- * async環境下await Sleep(500)
- * @param {number} millisecond 暫停毫秒數
- */
- self.Sleep = async function (millisecond) {
- return new Promise((resolve) => setTimeout(resolve, millisecond));
- };
+ /**
+ * editor_file的isset函數
+ */
+ self.isset = function (val) {
+ if (val == undefined || val == null) {
+ return false;
+ }
+ return true;
+ };
- /**
- * 使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";
- }
- };
-},
+ /**
+ * editor_file的checkCallback函數
+ */
+ self.checkCallback = function (callback) {
+ if (!isset(callback)) {
+ printe("未设置callback");
+ throw "未设置callback";
+ }
+ };
+ },
"音频系统": function () {
- // 在此增加新插件
- /*首先,在造塔群下载所需的库文件,然后放置在塔目录下的 libs/thirdparty 或其他目录下,之后在 index.html 的最后加上下面这几行:
+ // 在此增加新插件
+ /*首先,在造塔群下载所需的库文件,然后放置在塔目录下的 libs/thirdparty 或其他目录下,之后在 index.html 的最后加上下面这几行:
@@ -9077,2097 +9485,2099 @@ var plugins_bb40132b_638b_4a9f_b028_d3fe47acc8d1 =
*/
- // 将__enable置为false将关闭插件
- let __enable = true;
- if (!__enable || main.mode === "editor") return;
- const { OggOpusDecoderWebWorker } = window["ogg-opus-decoder"];
- const { OggVorbisDecoderWebWorker } = window["ogg-vorbis-decoder"];
- const { CodecParser } = window.CodecParser;
- const { Transition, linear } = core.plugin.animate;
-
- const audio = new Audio();
- const AudioStatus = {
- Playing: 0,
- Pausing: 1,
- Paused: 2,
- Stoping: 3,
- Stoped: 4,
- };
- 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);
- /** 当前增益 */
- this.gain = 0.5;
- /** 是否正在播放 */
- this.playing = false;
- 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.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*/
- 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];
-
- 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)));
- }
-
- this.loading = false;
- targets.forEach((v) => v.end(true));
-
- //
- }
-
- 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, [0x52, 0x49, 0x46, 0x46]],
- [AudioType.Flac, [0x66, 0x4c, 0x61, 0x43]],
- [AudioType.Aac, [0xff, 0xf1]],
- [AudioType.Aac, [0xff, 0xf9]],
- ];
- const oggHeaders = [
- [AudioType.Opus, [0x4f, 0x70, 0x75, 0x73, 0x48, 0x65, 0x61, 0x64]],
- ];
-
- 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;
- }
- 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);
- });
- decoder.destroy();
- return buffer;
- }
- }
- }
- }
-
- class VorbisDecoder {
- /**
- * 创建音频解码器
- */
- async create() {
- this.decoder = new OggVorbisDecoderWebWorker();
- await this.decoder.ready;
- }
- /**
- * 摧毁这个解码器
- */
- destroy() {
- this.decoder?.free();
- }
- /**
- * 解码流数据
- * @param data 流数据
- */
-
- async decode(data) {
- return this.decoder?.decode(data);
- }
- /**
- * 解码整个文件
- * @param data 文件数据
- */
- async decodeAll(data) {
- return this.decoder?.decodeFile(data);
- }
- /**
- * 当音频解码完成后,会调用此函数,需要返回之前还未解析或未返回的音频数据。调用后,该解码器将不会被再次使用
- */
- async flush() {
- return this.decoder?.flush();
- }
- }
-
- class OpusDecoder {
- /**
- * 创建音频解码器
- */
- async create() {
- this.decoder = new OggOpusDecoderWebWorker();
- await this.decoder.ready;
- }
- /**
- * 摧毁这个解码器
- */
- destroy() {
- this.decoder?.free();
- }
- /**
- * 解码流数据
- * @param data 流数据
- */
- async decode(data) {
- return this.decoder?.decode(data);
- }
- /**
- * 解码整个文件
- * @param data 文件数据
- */
- async decodeAll(data) {
- return this.decoder?.decodeFile(data);
- }
- /**
- * 当音频解码完成后,会调用此函数,需要返回之前还未解析或未返回的音频数据。调用后,该解码器将不会被再次使用
- */
- async flush() {
- return await this.decoder?.flush();
- }
- }
- 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;
- /** 是否正在播放 */
- this.playing = false;
- /** 已经缓冲了多长时间,如果缓冲完那么跟歌曲时长一致 */
- this.buffered = 0;
- /** 已经缓冲的采样点数量 */
- this.bufferedSamples = 0;
- /** 歌曲时长,加载完毕之前保持为 0 */
- this.duration = 0;
- /** 在流传输阶段,至少缓冲多长时间的音频之后才开始播放,单位秒 */
- this.bufferPlayDuration = 1;
- /** 音频的采样率,未成功解析出之前保持为 0 */
- this.sampleRate = 0;
- //是否循环播放
- this.loop = false;
- /** 上一次播放是从何时开始的 */
- this.lastStartWhen = 0;
- /** 开始播放时刻 */
- this.lastStartTime = 0;
- /** 上一次播放的缓存长度 */
- this.lastBufferSamples = 0;
-
- /** 是否已经获取到头文件 */
- this.headerRecieved = false;
- /** 音频类型 */
- this.audioType = "";
- /** 每多长时间组成一个缓存 Float32Array */
- this.bufferChunkSize = 10;
- /** 缓存音频数据,每 bufferChunkSize 秒钟组成一个 Float32Array,用于流式解码 */
- this.audioData = [];
-
- this.errored = false;
- this.ac = context;
- }
- /** 当前已经播放了多长时间 */
- get currentTime() {
- return this.ac.currentTime - this.lastStartTime + this.lastStartWhen;
- }
- /**
- * 设置每个缓存数据的大小,默认为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);
- this.audioType = checkAudioType(data);
- if (!this.audioType) {
- console.error(
- "Unknown audio type. Header: '" + [...toCheck]
- .map((v) => v.toString(16).padStart(2, "0"))
- .join(" ")
- .toUpperCase() +
- "'"
- );
- return;
- }
- // 创建解码器
- const Decoder = AudioDecoder.decoderMap.get(this.audioType);
- 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;
-
- 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();
-
- 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();
- if (this.route.status !== AudioStatus.Playing) {
- this.route.status = AudioStatus.Playing;
- }
- this.createSourceNode(this.buffer);
- this.output.start(0, when);
- this.playing = true;
-
- this.output.addEventListener("ended", () => {
- this.playing = false;
- if (this.route.status === AudioStatus.Playing) {
- this.route.status = AudioStatus.Stoped;
- }
- 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();
- audio.preload = "none";
- this.output = context.createMediaElementSource(audio);
- this.audio = audio;
- this.ac = context;
- audio.addEventListener("play", () => {
- this.playing = true;
- if (this.route.status !== AudioStatus.Playing) {
- this.route.status = AudioStatus.Playing;
- }
- });
- audio.addEventListener("ended", () => {
- this.playing = false;
- if (this.route.status === AudioStatus.Playing) {
- this.route.status = AudioStatus.Stoped;
- }
- });
- }
- get duration() {
- return this.audio.duration;
- }
- get currentTime() {
- return this.audio.currentTime;
- }
- /**
- * 设置音频源的路径
- * @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;
- if (this.route.status === AudioStatus.Playing) {
- this.route.status = AudioStatus.Stoped;
- }
- 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;
- /** 上一次播放是从何时开始的 */
- this.lastStartWhen = 0;
- /** 播放开始时刻 */
- this.lastStartTime = 0;
- this.duration = 0;
- this.ac = context;
- }
- get currentTime() {
- return this.ac.currentTime - this.lastStartTime + this.lastStartWhen;
- }
-
- /**
- * 设置音频源数据
- * @param buffer 音频源,可以是未解析的 ArrayBuffer,也可以是已解析的 AudioBuffer
- */
- async setBuffer(buffer) {
- if (buffer instanceof ArrayBuffer) {
- this.buffer = await this.ac.decodeAudioData(buffer);
- } else {
- this.buffer = buffer;
- }
- this.duration = this.buffer.duration;
- }
-
- play(when) {
- if (this.playing || !this.buffer) return;
- this.playing = true;
- this.lastStartTime = this.ac.currentTime;
- if (this.route.status !== AudioStatus.Playing) {
- this.route.status = AudioStatus.Playing;
- }
- this.createSourceNode(this.buffer);
- this.output.start(0, when);
- this.output.addEventListener("ended", () => {
- this.playing = false;
- if (this.route.status === AudioStatus.Playing) {
- this.route.status = AudioStatus.Stoped;
- }
- 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);
- this.audioRoutes = new Map();
- }
- /**
- * 解码音频数据
- * @param data 音频数据
- */
- decodeAudioData(data) {
- return AudioDecoder.decodeAudioData(data, this);
- }
- /**
- * 设置音量
- * @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) {
- if (!this.audioRoutes) this.audioRoutes = new Map();
- 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 音频播放路由的名称
- */
- getRoute(id) {
- return this.audioRoutes.get(id);
- }
- /**
- * 移除一个音频播放路由
- * @param id 要移除的播放路由的名称
- */
- removeRoute(id) {
- this.audioRoutes.delete(id);
- }
- /**
- * 播放音频
- * @param id 音频名称
- * @param when 从音频的哪个位置开始播放,单位秒
- */
- play(id, when) {
- 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);
- }
-
- /**
- * 暂停音频播放
- * @param id 音频名称
- * @returns 当音乐真正停止时兑现
- */
- pause(id) {
- const route = this.getRoute(id);
- if (!route) {
- console.warn(
- "Cannot pause audio route '" +
- id +
- "', since there is not added route named it."
- );
- return;
- }
- return route.pause();
- }
-
- /**
- * 停止音频播放
- * @param id 音频名称
- * @returns 当音乐真正停止时兑现
- */
- stop(id) {
- const route = this.getRoute(id);
- if (!route) {
- console.warn(
- "Cannot stop audio route '" +
- id +
- "', since there is not added route named it."
- );
- return;
- }
- return route.stop();
- }
-
- /**
- * 继续音频播放
- * @param id 音频名称
- */
- resume(id) {
- 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();
- }
-
- /**
- * 设置听者位置,x正方向水平向右,y正方向垂直于地面向上,z正方向垂直屏幕远离用户
- * @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;
- }
-
- /**
- * 设置听者朝向,x正方向水平向右,y正方向垂直于地面向上,z正方向垂直屏幕远离用户
- * @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;
- }
-
- /**
- * 设置听者头顶朝向,x正方向水平向右,y正方向垂直于地面向上,z正方向垂直屏幕远离用户
- * @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;
- }
- }
- class AudioRoute {
- constructor(source, player) {
- source.route = this;
- this.output = source.output;
-
- /** 效果器路由图 */
- this.effectRoute = [];
-
- /** 结束时长,当音频暂停或停止时,会经过这么长时间之后才真正终止播放,期间可以做音频淡入淡出等效果 */
- this.endTime = 0;
- /** 暂停时播放了多长时间 */
- this.pauseCurrentTime = 0;
- /** 当前播放状态 */
- this.player = player;
- this.status = AudioStatus.Stoped;
-
- this.shouldStop = false;
- /**
- * 每次暂停或停止时自增,用于判断当前正在处理的情况。
- * 假如暂停后很快播放,然后很快暂停,那么需要根据这个来判断实际是否应该执行暂停后操作
- */
- this.stopIdentifier = 0;
- /** 暂停时刻 */
- this.pauseTime = 0;
- this.source = source;
- this.source.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);
- }
- /**
- * 设置结束时间,暂停或停止时,会经过这么长时间才终止音频的播放,这期间可以做一下音频淡出的效果。
- * @param time 暂停或停止时,经过多长时间之后才会结束音频的播放
- */
- setEndTime(time) {
- this.endTime = time;
- }
-
- /**
- * 当音频播放时执行的函数,可以用于音频淡入效果
- * @param fn 音频开始播放时执行的函数
- */
- onStart(fn) {
- this.audioStartHook = fn;
- }
-
- /**
- * 当音频暂停或停止时执行的函数,可以用于音频淡出效果
- * @param fn 音频在暂停或停止时执行的函数,不填时表示取消这个钩子。
- * 包含两个参数,第一个参数是结束时长,第二个参数是当前音频播放路由对象
- */
- onEnd(fn) {
- this.audioEndHook = fn;
- }
-
- /**
- * 开始播放这个音频
- * @param when 从音频的什么时候开始播放,单位秒
- */
- async play(when = 0) {
- if (this.status === AudioStatus.Playing) return;
- this.link();
- await this.player.ac.resume();
- if (this.effectRoute.length > 0) {
- const first = this.effectRoute[0];
- this.source.connect(first);
- const last = this.effectRoute.at(-1);
- last.connect({ input: this.player.getDestination() });
- } else {
- this.source.connect({ input: this.player.getDestination() });
- }
- this.source.play(when);
- this.status = AudioStatus.Playing;
- this.pauseTime = 0;
- this.audioStartHook?.(this);
- this.startAllEffect();
- if (this.status !== AudioStatus.Playing) {
- this.status = AudioStatus.Playing;
- }
- }
-
- /**
- * 暂停音频播放
- */
- async pause() {
- if (this.status !== AudioStatus.Playing) return;
- this.status = AudioStatus.Pausing;
- this.stopIdentifier++;
- const identifier = this.stopIdentifier;
- if (this.audioEndHook) {
- this.audioEndHook(this.endTime, this);
- await sleep(this.endTime);
- }
- if (
- this.status !== AudioStatus.Pausing ||
- this.stopIdentifier !== identifier
- ) {
- return;
- }
- this.pauseCurrentTime = this.source.currentTime;
- const time = this.source.stop();
- this.pauseTime = time;
- if (this.shouldStop) {
- this.status = AudioStatus.Stoped;
- this.endAllEffect();
-
- this.shouldStop = false;
- } else {
- this.status = AudioStatus.Paused;
- this.endAllEffect();
- }
- this.endAllEffect();
- }
-
- /**
- * 继续音频播放
- */
- resume() {
- if (this.status === AudioStatus.Playing) return;
- if (
- this.status === AudioStatus.Pausing ||
- this.status === AudioStatus.Stoping
- ) {
- this.audioStartHook?.(this);
-
- return;
- }
- if (this.status === AudioStatus.Paused) {
- this.play(this.pauseTime);
- } else {
- this.play(0);
- }
- this.status = AudioStatus.Playing;
- this.pauseTime = 0;
- this.audioStartHook?.(this);
- this.startAllEffect();
- }
-
- /**
- * 停止音频播放
- */
- async stop() {
- if (this.status !== AudioStatus.Playing) {
- if (this.status === AudioStatus.Pausing) {
- this.shouldStop = true;
- }
- return;
- }
- this.status = AudioStatus.Stoping;
- this.stopIdentifier++;
- const identifier = this.stopIdentifier;
- if (this.audioEndHook) {
- this.audioEndHook(this.endTime, this);
- await sleep(this.endTime);
- }
- if (
- this.status !== AudioStatus.Stoping ||
- this.stopIdentifier !== identifier
- ) {
- return;
- }
- this.source.stop();
- this.status = AudioStatus.Stoped;
- 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());
- }
- }
-
- const audioPlayer = new AudioPlayer();
-
- 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;
-
- /** 是否已经启用 */
- this.enabled = true;
- /** 是否屏蔽所有的音乐切换 */
- this.blocking = false;
- /** 渐变时长 */
- this.transitionTime = 2000;
- }
-
- /**
- * 设置音频渐变时长
- * @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);
- this._volume = volume;
- }
- /**
- * 获取总音量大小
- */
- getVolume() {
- return this.mainGain.getVolume();
- }
- /**
- * 设置是否启用
- * @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();
- if (isAudioSupport(type)) {
- const source = audioPlayer.createElementSource();
- source.setSource(url);
- source.setLoop(true);
- const route = new AudioRoute(source, audioPlayer);
- route.addEffect([gain, this.mainGain]);
- audioPlayer.addRoute(this.getId(id), route);
- this.setTransition(id, route, gain);
- } else {
- const source = audioPlayer.createStreamSource();
- const stream = new StreamLoader(url);
- stream.pipe(source);
- source.setLoop(true);
- const route = new AudioRoute(source, audioPlayer);
- 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);
- }
- /**
- * 获取音量大小
- */
- getVolume() {
- return this.gain.getVolume();
- }
- /**
- * 添加一个音效
- * @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 || !id) 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);
- }
- }
-
- /**
- * 停止播放所有音效
- */
- 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);
- }
- }
- loadAllBgm();
- AudioDecoder.registerDecoder(AudioType.Ogg, VorbisDecoder);
- AudioDecoder.registerDecoder(AudioType.Opus, OpusDecoder);
-
- core.plugin.audioSystem = {
- AudioType,
- AudioDecoder,
- AudioStatus,
- checkAudioType,
- isAudioSupport,
- audioPlayer,
- soundPlayer,
- bgmController,
- guessTypeByExt,
- BgmController,
- SoundPlayer,
- EchoEffect,
- DelayEffect,
- ChannelVolumeEffect,
- VolumeEffect,
- StereoEffect,
- AudioEffect,
- AudioPlayer,
- AudioRoute,
- AudioStreamSource,
- AudioElementSource,
- AudioBufferSource,
- loadAllBgm,
- StreamLoader,
- };
- //bgm相关复写
- control.prototype.playBgm = (bgm, when) => {
- bgm = core.getMappedName(bgm);
- 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 () {
- core.playBgm(bgmController.playingBgm || main.startBgm);
- };
- control.prototype.triggerBgm = function () {
- 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 * 1000).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();
- };
-},
+ // 将__enable置为false将关闭插件
+ let __enable = true;
+ if (!__enable || main.mode === "editor") return;
+ const { OggOpusDecoderWebWorker } = window["ogg-opus-decoder"];
+ const { OggVorbisDecoderWebWorker } = window["ogg-vorbis-decoder"];
+ const { CodecParser } = window.CodecParser;
+ const { Transition, linear } = core.plugin.animate;
+
+ const audio = new Audio();
+ const AudioStatus = {
+ Playing: 0,
+ Pausing: 1,
+ Paused: 2,
+ Stoping: 3,
+ Stoped: 4,
+ };
+ 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);
+ /** 当前增益 */
+ this.gain = 0.5;
+ /** 是否正在播放 */
+ this.playing = false;
+ 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.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*/
+ 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];
+
+ 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)));
+ }
+
+ this.loading = false;
+ targets.forEach((v) => v.end(true));
+
+ //
+ }
+
+ 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, [0x52, 0x49, 0x46, 0x46]],
+ [AudioType.Flac, [0x66, 0x4c, 0x61, 0x43]],
+ [AudioType.Aac, [0xff, 0xf1]],
+ [AudioType.Aac, [0xff, 0xf9]],
+ ];
+ const oggHeaders = [
+ [AudioType.Opus, [0x4f, 0x70, 0x75, 0x73, 0x48, 0x65, 0x61, 0x64]],
+ ];
+
+ 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;
+ }
+ 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);
+ });
+ decoder.destroy();
+ return buffer;
+ }
+ }
+ }
+ }
+
+ class VorbisDecoder {
+ /**
+ * 创建音频解码器
+ */
+ async create() {
+ this.decoder = new OggVorbisDecoderWebWorker();
+ await this.decoder.ready;
+ }
+ /**
+ * 摧毁这个解码器
+ */
+ destroy() {
+ this.decoder?.free();
+ }
+ /**
+ * 解码流数据
+ * @param data 流数据
+ */
+
+ async decode(data) {
+ return this.decoder?.decode(data);
+ }
+ /**
+ * 解码整个文件
+ * @param data 文件数据
+ */
+ async decodeAll(data) {
+ return this.decoder?.decodeFile(data);
+ }
+ /**
+ * 当音频解码完成后,会调用此函数,需要返回之前还未解析或未返回的音频数据。调用后,该解码器将不会被再次使用
+ */
+ async flush() {
+ return this.decoder?.flush();
+ }
+ }
+
+ class OpusDecoder {
+ /**
+ * 创建音频解码器
+ */
+ async create() {
+ this.decoder = new OggOpusDecoderWebWorker();
+ await this.decoder.ready;
+ }
+ /**
+ * 摧毁这个解码器
+ */
+ destroy() {
+ this.decoder?.free();
+ }
+ /**
+ * 解码流数据
+ * @param data 流数据
+ */
+ async decode(data) {
+ return this.decoder?.decode(data);
+ }
+ /**
+ * 解码整个文件
+ * @param data 文件数据
+ */
+ async decodeAll(data) {
+ return this.decoder?.decodeFile(data);
+ }
+ /**
+ * 当音频解码完成后,会调用此函数,需要返回之前还未解析或未返回的音频数据。调用后,该解码器将不会被再次使用
+ */
+ async flush() {
+ return await this.decoder?.flush();
+ }
+ }
+ 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;
+ /** 是否正在播放 */
+ this.playing = false;
+ /** 已经缓冲了多长时间,如果缓冲完那么跟歌曲时长一致 */
+ this.buffered = 0;
+ /** 已经缓冲的采样点数量 */
+ this.bufferedSamples = 0;
+ /** 歌曲时长,加载完毕之前保持为 0 */
+ this.duration = 0;
+ /** 在流传输阶段,至少缓冲多长时间的音频之后才开始播放,单位秒 */
+ this.bufferPlayDuration = 1;
+ /** 音频的采样率,未成功解析出之前保持为 0 */
+ this.sampleRate = 0;
+ //是否循环播放
+ this.loop = false;
+ /** 上一次播放是从何时开始的 */
+ this.lastStartWhen = 0;
+ /** 开始播放时刻 */
+ this.lastStartTime = 0;
+ /** 上一次播放的缓存长度 */
+ this.lastBufferSamples = 0;
+
+ /** 是否已经获取到头文件 */
+ this.headerRecieved = false;
+ /** 音频类型 */
+ this.audioType = "";
+ /** 每多长时间组成一个缓存 Float32Array */
+ this.bufferChunkSize = 10;
+ /** 缓存音频数据,每 bufferChunkSize 秒钟组成一个 Float32Array,用于流式解码 */
+ this.audioData = [];
+
+ this.errored = false;
+ this.ac = context;
+ }
+ /** 当前已经播放了多长时间 */
+ get currentTime() {
+ return this.ac.currentTime - this.lastStartTime + this.lastStartWhen;
+ }
+ /**
+ * 设置每个缓存数据的大小,默认为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);
+ this.audioType = checkAudioType(data);
+ if (!this.audioType) {
+ console.error(
+ "Unknown audio type. Header: '" +
+ [...toCheck]
+ .map((v) => v.toString(16).padStart(2, "0"))
+ .join(" ")
+ .toUpperCase() +
+ "'"
+ );
+ return;
+ }
+ // 创建解码器
+ const Decoder = AudioDecoder.decoderMap.get(this.audioType);
+ 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;
+
+ 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();
+
+ 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();
+ if (this.route.status !== AudioStatus.Playing) {
+ this.route.status = AudioStatus.Playing;
+ }
+ this.createSourceNode(this.buffer);
+ this.output.start(0, when);
+ this.playing = true;
+
+ this.output.addEventListener("ended", () => {
+ this.playing = false;
+ if (this.route.status === AudioStatus.Playing) {
+ this.route.status = AudioStatus.Stoped;
+ }
+ 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();
+ audio.preload = "none";
+ this.output = context.createMediaElementSource(audio);
+ this.audio = audio;
+ this.ac = context;
+ audio.addEventListener("play", () => {
+ this.playing = true;
+ if (this.route.status !== AudioStatus.Playing) {
+ this.route.status = AudioStatus.Playing;
+ }
+ });
+ audio.addEventListener("ended", () => {
+ this.playing = false;
+ if (this.route.status === AudioStatus.Playing) {
+ this.route.status = AudioStatus.Stoped;
+ }
+ });
+ }
+ get duration() {
+ return this.audio.duration;
+ }
+ get currentTime() {
+ return this.audio.currentTime;
+ }
+ /**
+ * 设置音频源的路径
+ * @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;
+ if (this.route.status === AudioStatus.Playing) {
+ this.route.status = AudioStatus.Stoped;
+ }
+ 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;
+ /** 上一次播放是从何时开始的 */
+ this.lastStartWhen = 0;
+ /** 播放开始时刻 */
+ this.lastStartTime = 0;
+ this.duration = 0;
+ this.ac = context;
+ }
+ get currentTime() {
+ return this.ac.currentTime - this.lastStartTime + this.lastStartWhen;
+ }
+
+ /**
+ * 设置音频源数据
+ * @param buffer 音频源,可以是未解析的 ArrayBuffer,也可以是已解析的 AudioBuffer
+ */
+ async setBuffer(buffer) {
+ if (buffer instanceof ArrayBuffer) {
+ this.buffer = await this.ac.decodeAudioData(buffer);
+ } else {
+ this.buffer = buffer;
+ }
+ this.duration = this.buffer.duration;
+ }
+
+ play(when) {
+ if (this.playing || !this.buffer) return;
+ this.playing = true;
+ this.lastStartTime = this.ac.currentTime;
+ if (this.route.status !== AudioStatus.Playing) {
+ this.route.status = AudioStatus.Playing;
+ }
+ this.createSourceNode(this.buffer);
+ this.output.start(0, when);
+ this.output.addEventListener("ended", () => {
+ this.playing = false;
+ if (this.route.status === AudioStatus.Playing) {
+ this.route.status = AudioStatus.Stoped;
+ }
+ 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);
+ this.audioRoutes = new Map();
+ }
+ /**
+ * 解码音频数据
+ * @param data 音频数据
+ */
+ decodeAudioData(data) {
+ return AudioDecoder.decodeAudioData(data, this);
+ }
+ /**
+ * 设置音量
+ * @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) {
+ if (!this.audioRoutes) this.audioRoutes = new Map();
+ 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 音频播放路由的名称
+ */
+ getRoute(id) {
+ return this.audioRoutes.get(id);
+ }
+ /**
+ * 移除一个音频播放路由
+ * @param id 要移除的播放路由的名称
+ */
+ removeRoute(id) {
+ this.audioRoutes.delete(id);
+ }
+ /**
+ * 播放音频
+ * @param id 音频名称
+ * @param when 从音频的哪个位置开始播放,单位秒
+ */
+ play(id, when) {
+ 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);
+ }
+
+ /**
+ * 暂停音频播放
+ * @param id 音频名称
+ * @returns 当音乐真正停止时兑现
+ */
+ pause(id) {
+ const route = this.getRoute(id);
+ if (!route) {
+ console.warn(
+ "Cannot pause audio route '" +
+ id +
+ "', since there is not added route named it."
+ );
+ return;
+ }
+ return route.pause();
+ }
+
+ /**
+ * 停止音频播放
+ * @param id 音频名称
+ * @returns 当音乐真正停止时兑现
+ */
+ stop(id) {
+ const route = this.getRoute(id);
+ if (!route) {
+ console.warn(
+ "Cannot stop audio route '" +
+ id +
+ "', since there is not added route named it."
+ );
+ return;
+ }
+ return route.stop();
+ }
+
+ /**
+ * 继续音频播放
+ * @param id 音频名称
+ */
+ resume(id) {
+ 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();
+ }
+
+ /**
+ * 设置听者位置,x正方向水平向右,y正方向垂直于地面向上,z正方向垂直屏幕远离用户
+ * @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;
+ }
+
+ /**
+ * 设置听者朝向,x正方向水平向右,y正方向垂直于地面向上,z正方向垂直屏幕远离用户
+ * @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;
+ }
+
+ /**
+ * 设置听者头顶朝向,x正方向水平向右,y正方向垂直于地面向上,z正方向垂直屏幕远离用户
+ * @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;
+ }
+ }
+ class AudioRoute {
+ constructor(source, player) {
+ source.route = this;
+ this.output = source.output;
+
+ /** 效果器路由图 */
+ this.effectRoute = [];
+
+ /** 结束时长,当音频暂停或停止时,会经过这么长时间之后才真正终止播放,期间可以做音频淡入淡出等效果 */
+ this.endTime = 0;
+ /** 暂停时播放了多长时间 */
+ this.pauseCurrentTime = 0;
+ /** 当前播放状态 */
+ this.player = player;
+ this.status = AudioStatus.Stoped;
+
+ this.shouldStop = false;
+ /**
+ * 每次暂停或停止时自增,用于判断当前正在处理的情况。
+ * 假如暂停后很快播放,然后很快暂停,那么需要根据这个来判断实际是否应该执行暂停后操作
+ */
+ this.stopIdentifier = 0;
+ /** 暂停时刻 */
+ this.pauseTime = 0;
+ this.source = source;
+ this.source.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);
+ }
+ /**
+ * 设置结束时间,暂停或停止时,会经过这么长时间才终止音频的播放,这期间可以做一下音频淡出的效果。
+ * @param time 暂停或停止时,经过多长时间之后才会结束音频的播放
+ */
+ setEndTime(time) {
+ this.endTime = time;
+ }
+
+ /**
+ * 当音频播放时执行的函数,可以用于音频淡入效果
+ * @param fn 音频开始播放时执行的函数
+ */
+ onStart(fn) {
+ this.audioStartHook = fn;
+ }
+
+ /**
+ * 当音频暂停或停止时执行的函数,可以用于音频淡出效果
+ * @param fn 音频在暂停或停止时执行的函数,不填时表示取消这个钩子。
+ * 包含两个参数,第一个参数是结束时长,第二个参数是当前音频播放路由对象
+ */
+ onEnd(fn) {
+ this.audioEndHook = fn;
+ }
+
+ /**
+ * 开始播放这个音频
+ * @param when 从音频的什么时候开始播放,单位秒
+ */
+ async play(when = 0) {
+ if (this.status === AudioStatus.Playing) return;
+ this.link();
+ await this.player.ac.resume();
+ if (this.effectRoute.length > 0) {
+ const first = this.effectRoute[0];
+ this.source.connect(first);
+ const last = this.effectRoute.at(-1);
+ last.connect({ input: this.player.getDestination() });
+ } else {
+ this.source.connect({ input: this.player.getDestination() });
+ }
+ this.source.play(when);
+ this.status = AudioStatus.Playing;
+ this.pauseTime = 0;
+ this.audioStartHook?.(this);
+ this.startAllEffect();
+ if (this.status !== AudioStatus.Playing) {
+ this.status = AudioStatus.Playing;
+ }
+ }
+
+ /**
+ * 暂停音频播放
+ */
+ async pause() {
+ if (this.status !== AudioStatus.Playing) return;
+ this.status = AudioStatus.Pausing;
+ this.stopIdentifier++;
+ const identifier = this.stopIdentifier;
+ if (this.audioEndHook) {
+ this.audioEndHook(this.endTime, this);
+ await sleep(this.endTime);
+ }
+ if (
+ this.status !== AudioStatus.Pausing ||
+ this.stopIdentifier !== identifier
+ ) {
+ return;
+ }
+ this.pauseCurrentTime = this.source.currentTime;
+ const time = this.source.stop();
+ this.pauseTime = time;
+ if (this.shouldStop) {
+ this.status = AudioStatus.Stoped;
+ this.endAllEffect();
+
+ this.shouldStop = false;
+ } else {
+ this.status = AudioStatus.Paused;
+ this.endAllEffect();
+ }
+ this.endAllEffect();
+ }
+
+ /**
+ * 继续音频播放
+ */
+ resume() {
+ if (this.status === AudioStatus.Playing) return;
+ if (
+ this.status === AudioStatus.Pausing ||
+ this.status === AudioStatus.Stoping
+ ) {
+ this.audioStartHook?.(this);
+
+ return;
+ }
+ if (this.status === AudioStatus.Paused) {
+ this.play(this.pauseTime);
+ } else {
+ this.play(0);
+ }
+ this.status = AudioStatus.Playing;
+ this.pauseTime = 0;
+ this.audioStartHook?.(this);
+ this.startAllEffect();
+ }
+
+ /**
+ * 停止音频播放
+ */
+ async stop() {
+ if (this.status !== AudioStatus.Playing) {
+ if (this.status === AudioStatus.Pausing) {
+ this.shouldStop = true;
+ }
+ return;
+ }
+ this.status = AudioStatus.Stoping;
+ this.stopIdentifier++;
+ const identifier = this.stopIdentifier;
+ if (this.audioEndHook) {
+ this.audioEndHook(this.endTime, this);
+ await sleep(this.endTime);
+ }
+ if (
+ this.status !== AudioStatus.Stoping ||
+ this.stopIdentifier !== identifier
+ ) {
+ return;
+ }
+ this.source.stop();
+ this.status = AudioStatus.Stoped;
+ 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());
+ }
+ }
+
+ const audioPlayer = new AudioPlayer();
+
+ 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;
+
+ /** 是否已经启用 */
+ this.enabled = true;
+ /** 是否屏蔽所有的音乐切换 */
+ this.blocking = false;
+ /** 渐变时长 */
+ this.transitionTime = 2000;
+ }
+
+ /**
+ * 设置音频渐变时长
+ * @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);
+ this._volume = volume;
+ }
+ /**
+ * 获取总音量大小
+ */
+ getVolume() {
+ return this.mainGain.getVolume();
+ }
+ /**
+ * 设置是否启用
+ * @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();
+ if (isAudioSupport(type)) {
+ const source = audioPlayer.createElementSource();
+ source.setSource(url);
+ source.setLoop(true);
+ const route = new AudioRoute(source, audioPlayer);
+ route.addEffect([gain, this.mainGain]);
+ audioPlayer.addRoute(this.getId(id), route);
+ this.setTransition(id, route, gain);
+ } else {
+ const source = audioPlayer.createStreamSource();
+ const stream = new StreamLoader(url);
+ stream.pipe(source);
+ source.setLoop(true);
+ const route = new AudioRoute(source, audioPlayer);
+ 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);
+ }
+ /**
+ * 获取音量大小
+ */
+ getVolume() {
+ return this.gain.getVolume();
+ }
+ /**
+ * 添加一个音效
+ * @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 || !id) 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);
+ }
+ }
+
+ /**
+ * 停止播放所有音效
+ */
+ 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);
+ }
+ }
+ loadAllBgm();
+ AudioDecoder.registerDecoder(AudioType.Ogg, VorbisDecoder);
+ AudioDecoder.registerDecoder(AudioType.Opus, OpusDecoder);
+
+ core.plugin.audioSystem = {
+ AudioType,
+ AudioDecoder,
+ AudioStatus,
+ checkAudioType,
+ isAudioSupport,
+ audioPlayer,
+ soundPlayer,
+ bgmController,
+ guessTypeByExt,
+ BgmController,
+ SoundPlayer,
+ EchoEffect,
+ DelayEffect,
+ ChannelVolumeEffect,
+ VolumeEffect,
+ StereoEffect,
+ AudioEffect,
+ AudioPlayer,
+ AudioRoute,
+ AudioStreamSource,
+ AudioElementSource,
+ AudioBufferSource,
+ loadAllBgm,
+ StreamLoader,
+ };
+ //bgm相关复写
+ control.prototype.playBgm = (bgm, when) => {
+ bgm = core.getMappedName(bgm);
+ 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 () {
+ core.playBgm(bgmController.playingBgm || main.startBgm);
+ };
+ control.prototype.triggerBgm = function () {
+ 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 * 1000).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();
+ };
+ },
"自定义常用事件": function () {
- // editorBlocklyconfigPlus.js
- // 自訂常見事件模板插件
- // 本插件引用了通用函數插件(Utility.js)
- // 適用樣板:2.10.3
- // 請注意:
- // 此插件對事件編輯器(editor_blocklyconfig)進行複寫,若還有其它針對事件編輯器做複寫的插件,請謹慎使用!
- // 此插件對表格操作行為(editor_mode.doActionList)進行複寫,若還有其它對表格操作行為做複寫的插件,請謹慎使用!
- // 使用方法:
- // 現在在主頁下拉選單多了個常用事件模版,在那邊可以自由設定常用事件模板。
- // 設定完後按F5刷新,再到事件編輯器看就有你設定好的常用事件模板了。
+ // 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 = `
+ if (main.mode == "editor") {
+ //#region 配置表格初始化
+ let TableFileName = "project/table/CommonEventTemplate_comment.js";
+ let TableRow = `
var CommonEventTemplate_comment = {"_type": "object",
"_data": {
"CommonEventTemplate": {
@@ -11210,221 +11620,241 @@ var plugins_bb40132b_638b_4a9f_b028_d3fe47acc8d1 =
}
}}
`;
- 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
+ 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);
+ // 新增模板選項
+ 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}`);
- }
+ //檢查可用的編輯模板ID
+ let leftIDNumber = 11 - 1;
+ let ExistLeftElement = document.querySelector(".main");
+ while (ExistLeftElement) {
+ leftIDNumber++;
+ ExistLeftElement = document.getElementById(`left${leftIDNumber}`);
+ }
- //新增編輯模板
- let MainDiv = document.querySelector(".main");
+ //新增編輯模板
+ let MainDiv = document.querySelector(".main");
- let CommonEventTemplateMainDiv = document.createElement("div");
- CommonEventTemplateMainDiv.id = `left${leftIDNumber}`;
- CommonEventTemplateMainDiv.className = "leftTab";
- CommonEventTemplateMainDiv.style.zIndex = "-1";
- CommonEventTemplateMainDiv.style.opacity = "0";
+ let CommonEventTemplateMainDiv = document.createElement("div");
+ CommonEventTemplateMainDiv.id = `left${leftIDNumber}`;
+ CommonEventTemplateMainDiv.className = "leftTab";
+ CommonEventTemplateMainDiv.style.zIndex = "-1";
+ CommonEventTemplateMainDiv.style.opacity = "0";
- CommonEventTemplateMainDiv.innerHTML = `
+ CommonEventTemplateMainDiv.innerHTML = `