diff --git a/_server/MotaAction.g4 b/_server/MotaAction.g4
index 3937e68..5b1b0f7 100644
--- a/_server/MotaAction.g4
+++ b/_server/MotaAction.g4
@@ -2417,7 +2417,7 @@ stopAnimate_s
tooltip : stopAnimate:停止所有动画
helpUrl : /_docs/#/instruction
default : [false]
-colour : this.imageColor
+colour : this.soundColor
Bool_0 = Bool_0?', "doCallback": true':'';
var code = '{"type": "stopAnimate"'+Bool_0+'},\n';
return code;
diff --git a/project/events.js b/project/events.js
index 5c6c1a0..2ec5259 100644
--- a/project/events.js
+++ b/project/events.js
@@ -3282,6 +3282,9 @@ var events_c12a15a8_c380_4b28_8144_256cba95f760 =
}
]
},
+ {
+ "type": "pauseBgm"
+ },
{
"type": "playBgm",
"name": "Halbmond.opus"
diff --git a/project/floors/yiqu1.js b/project/floors/yiqu1.js
index ec80832..b2bb0f4 100644
--- a/project/floors/yiqu1.js
+++ b/project/floors/yiqu1.js
@@ -14,7 +14,14 @@ main.floors.yiqu1=
"firstArrive": [],
"eachArrive": [],
"parallelDo": "",
- "events": {},
+ "events": {
+ "5,9": [
+ {
+ "type": "insert",
+ "name": "chapter01"
+ }
+ ]
+ },
"changeFloor": {
"0,7": {
"floorId": "yiqu2",
diff --git a/project/plugins.js b/project/plugins.js
index 4ed7b66..24fb348 100644
--- a/project/plugins.js
+++ b/project/plugins.js
@@ -52,7 +52,15 @@ var plugins_bb40132b_638b_4a9f_b028_d3fe47acc8d1 =
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)
+ core.drawWarning(
+ data.x,
+ data.y,
+ data?.text,
+ data?.text2,
+ data?.warning,
+ data.large,
+ data.size
+ );
setTimeout(() => core.doAction(), 3100);
} else {
@@ -182,18 +190,20 @@ var plugins_bb40132b_638b_4a9f_b028_d3fe47acc8d1 =
core.doAction();
}
});
- core.registerEvent('cgtextList', function (data) {
- core.ui.cgText.textList = core.plugin[data.textList]
+ 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.memory = data.memory;
core.ui.cgText.head = core.clone(data.head);
- core.ui.cgText.index = data.index
+ 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.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;
@@ -2515,1224 +2525,1218 @@ var plugins_bb40132b_638b_4a9f_b028_d3fe47acc8d1 =
})();
},
"statusBar": function () {
- main.dom.floorMsgGroup.style.display = "none";
- main.dom.statusBar.style.display = "none";
- main.dom.toolBar.style.display = "none";
- //所有数据*3是为了实现高清画布
- const GAMEVIEW_WIDTH = 676 * 3; //横屏画面宽度
- const GAMEVIEW_HEIGHT = 416 * 3; //横屏画面高度
+ main.dom.floorMsgGroup.style.display = "none";
+ main.dom.statusBar.style.display = "none";
+ main.dom.toolBar.style.display = "none";
+ //所有数据*3是为了实现高清画布
+ const GAMEVIEW_WIDTH = 676 * 3; //横屏画面宽度
+ const GAMEVIEW_HEIGHT = 416 * 3; //横屏画面高度
- const GAMEVIEW_WIDTH_VERTICAL = 416 * 3; //竖屏画面宽度
- const GAMEVIEW_HEIGHT_VERTICAL = 676 * 3; //竖屏画面高度
+ const GAMEVIEW_WIDTH_VERTICAL = 416 * 3; //竖屏画面宽度
+ const GAMEVIEW_HEIGHT_VERTICAL = 676 * 3; //竖屏画面高度
- const BAR_WIDTH = 130 * 3; //横屏左侧额外距离(即边栏宽度)
- const BAR_HEIGHT_VERTICAL = 130 * 3; //竖屏上侧额外距离(即边栏高度)
- const BORDER_WIDTH = 0; //游戏画面左侧偏移距离
- const BORDER_HEIGHT = 0; //游戏画面上侧偏移距离
+ const BAR_WIDTH = 130 * 3; //横屏左侧额外距离(即边栏宽度)
+ const BAR_HEIGHT_VERTICAL = 130 * 3; //竖屏上侧额外距离(即边栏高度)
+ const BORDER_WIDTH = 0; //游戏画面左侧偏移距离
+ const BORDER_HEIGHT = 0; //游戏画面上侧偏移距离
- const ITEM_BOX_LEFT = 549 * 3; //横屏道具栏左侧距离(右侧边栏需增加BAR_WIDTH+GAMEVIEW_HEIGHT)
- const ITEM_BOX_TOP = 155 * 3; //横屏道具栏上侧距离
- const ITEM_BOX_LEFT_VERTICAL = 160 * 3; //竖屏道具栏左侧距离
- const ITEM_BOX_TOP_VERTICAL = 549 * 3; //竖屏道具栏上侧距离(下侧边栏需增加BAR_HEIGHT_VERTICAL+GAMEVIEW_WIDTH_VERTICAL)
+ const ITEM_BOX_LEFT = 549 * 3; //横屏道具栏左侧距离(右侧边栏需增加BAR_WIDTH+GAMEVIEW_HEIGHT)
+ const ITEM_BOX_TOP = 155 * 3; //横屏道具栏上侧距离
+ const ITEM_BOX_LEFT_VERTICAL = 160 * 3; //竖屏道具栏左侧距离
+ const ITEM_BOX_TOP_VERTICAL = 549 * 3; //竖屏道具栏上侧距离(下侧边栏需增加BAR_HEIGHT_VERTICAL+GAMEVIEW_WIDTH_VERTICAL)
- const EQUIP_BLOCK_LEFT = 549 * 3; //横屏装备栏左侧距离(右侧边栏需增加BAR_WIDTH+GAMEVIEW_HEIGHT)
- const EQUIP_BLOCK_TOP = 10 * 3; //横屏装备栏上侧距离
- const EQUIP_BLOCK_LEFT_VERTICAL = 10 * 3; //竖屏装备栏左侧距离
- const EQUIP_BLOCK_TOP_VERTICAL = 549 * 3; //竖屏装备栏上侧距离(下侧边栏需增加BAR_HEIGHT_VERTICAL+GAMEVIEW_WIDTH_VERTICAL)
+ const EQUIP_BLOCK_LEFT = 549 * 3; //横屏装备栏左侧距离(右侧边栏需增加BAR_WIDTH+GAMEVIEW_HEIGHT)
+ const EQUIP_BLOCK_TOP = 10 * 3; //横屏装备栏上侧距离
+ const EQUIP_BLOCK_LEFT_VERTICAL = 10 * 3; //竖屏装备栏左侧距离
+ const EQUIP_BLOCK_TOP_VERTICAL = 549 * 3; //竖屏装备栏上侧距离(下侧边栏需增加BAR_HEIGHT_VERTICAL+GAMEVIEW_WIDTH_VERTICAL)
- const MAP_BLOCK_LEFT = 551 * 3; //横屏小地图左侧距离(右侧边栏需增加BAR_WIDTH+GAMEVIEW_HEIGHT)
- const MAP_BLOCK_TOP = 0; //横屏小地图上侧距离
- const MAP_BLOCK_LEFT_VERTICAL = 0; //竖屏小地图左侧距离
- const MAP_BLOCK_TOP_VERTICAL = 551 * 3; //竖屏小地图上侧距离(下侧边栏需增加BAR_HEIGHT_VERTICAL+GAMEVIEW_WIDTH_VERTICAL)
+ const MAP_BLOCK_LEFT = 551 * 3; //横屏小地图左侧距离(右侧边栏需增加BAR_WIDTH+GAMEVIEW_HEIGHT)
+ const MAP_BLOCK_TOP = 0; //横屏小地图上侧距离
+ const MAP_BLOCK_LEFT_VERTICAL = 0; //竖屏小地图左侧距离
+ const MAP_BLOCK_TOP_VERTICAL = 551 * 3; //竖屏小地图上侧距离(下侧边栏需增加BAR_HEIGHT_VERTICAL+GAMEVIEW_WIDTH_VERTICAL)
- const KEY_BLOCK_LEFT = EQUIP_BLOCK_LEFT; //横屏钥匙栏左侧距离(右侧边栏需增加BAR_WIDTH+GAMEVIEW_HEIGHT)
- const KEY_BLOCK_TOP = 110 * 3; //横屏钥匙栏上侧距离
- const KEY_BLOCK_LEFT_VERTICAL = 110 * 3; //竖屏钥匙栏左侧距离
- const KEY_BLOCK_TOP_VERTICAL = EQUIP_BLOCK_TOP_VERTICAL; //竖屏钥匙栏上侧距离(下侧边栏需增加BAR_HEIGHT_VERTICAL+GAMEVIEW_WIDTH_VERTICAL)
+ const KEY_BLOCK_LEFT = EQUIP_BLOCK_LEFT; //横屏钥匙栏左侧距离(右侧边栏需增加BAR_WIDTH+GAMEVIEW_HEIGHT)
+ const KEY_BLOCK_TOP = 110 * 3; //横屏钥匙栏上侧距离
+ const KEY_BLOCK_LEFT_VERTICAL = 110 * 3; //竖屏钥匙栏左侧距离
+ const KEY_BLOCK_TOP_VERTICAL = EQUIP_BLOCK_TOP_VERTICAL; //竖屏钥匙栏上侧距离(下侧边栏需增加BAR_HEIGHT_VERTICAL+GAMEVIEW_WIDTH_VERTICAL)
- const INFO_BLOCK_LEFT = 10 * 3; //横屏道具说明左侧距离(右侧边栏需增加BAR_WIDTH+GAMEVIEW_HEIGHT)
- const INFO_BLOCK_TOP = 180 * 3; //横屏道具说明上侧距离
- const INFO_BLOCK_LEFT_VERTICAL = 113 * 3; //竖屏道具说明左侧距离
- const INFO_BLOCK_TOP_VERTICAL = 8 * 3; //竖屏道具说明上侧距离(下侧边栏需增加BAR_HEIGHT_VERTICAL+GAMEVIEW_WIDTH_VERTICAL)
+ const INFO_BLOCK_LEFT = 10 * 3; //横屏道具说明左侧距离(右侧边栏需增加BAR_WIDTH+GAMEVIEW_HEIGHT)
+ const INFO_BLOCK_TOP = 180 * 3; //横屏道具说明上侧距离
+ const INFO_BLOCK_LEFT_VERTICAL = 113 * 3; //竖屏道具说明左侧距离
+ const INFO_BLOCK_TOP_VERTICAL = 8 * 3; //竖屏道具说明上侧距离(下侧边栏需增加BAR_HEIGHT_VERTICAL+GAMEVIEW_WIDTH_VERTICAL)
- const TOOL_BOX_LEFT = EQUIP_BLOCK_LEFT; //横屏工具栏左侧距离(右侧边栏需增加BAR_WIDTH+GAMEVIEW_HEIGHT)
- const TOOL_BOX_TOP = 348 * 3; //横屏工具栏上侧距离
- const TOOL_BOX_LEFT_VERTICAL = 348 * 3; //竖屏工具栏左侧距离
- const TOOL_BOX_TOP_VERTICAL = 549 * 3; //竖屏工具栏上侧距离(下侧边栏需增加BAR_HEIGHT_VERTICAL+GAMEVIEW_WIDTH_VERTICAL)
+ const TOOL_BOX_LEFT = EQUIP_BLOCK_LEFT; //横屏工具栏左侧距离(右侧边栏需增加BAR_WIDTH+GAMEVIEW_HEIGHT)
+ const TOOL_BOX_TOP = 348 * 3; //横屏工具栏上侧距离
+ const TOOL_BOX_LEFT_VERTICAL = 348 * 3; //竖屏工具栏左侧距离
+ const TOOL_BOX_TOP_VERTICAL = 549 * 3; //竖屏工具栏上侧距离(下侧边栏需增加BAR_HEIGHT_VERTICAL+GAMEVIEW_WIDTH_VERTICAL)
- const TOOL_ICON_OUTER_SIZE = 34 * 3;
+ const TOOL_ICON_OUTER_SIZE = 34 * 3;
- const TEXT_COLOR = "#FFFFFF"; //默认文字颜色
- const globalAlpha = 0.7; //默认底框透明度
- const FORCE_COUNTABLE_ITEMS = ["centerFly"]; //常态显示数量的非永久道具,如果道具不在此数组中,则只有道具多余1时显示数量
+ const TEXT_COLOR = "#FFFFFF"; //默认文字颜色
+ const globalAlpha = 0.7; //默认底框透明度
+ const FORCE_COUNTABLE_ITEMS = ["centerFly"]; //常态显示数量的非永久道具,如果道具不在此数组中,则只有道具多余1时显示数量
- const outerBackground = document.createElement("canvas"); //背景画布设置
- let globalAlphafloor = 0,
- globalAlphafloorStatus = 4;
- outerBackground.style.position = "absolute";
- outerBackground.style.zIndex = 5;
- outerBackground.id = "outerBackground";
- main.dom.outerBackground = outerBackground;
- main.dom.startPanel.insertAdjacentElement("afterend", outerBackground);
+ const outerBackground = document.createElement("canvas"); //背景画布设置
+ let globalAlphafloor = 0,
+ globalAlphafloorStatus = 4;
+ outerBackground.style.position = "absolute";
+ outerBackground.style.zIndex = 5;
+ outerBackground.id = "outerBackground";
+ main.dom.outerBackground = outerBackground;
+ main.dom.startPanel.insertAdjacentElement("afterend", outerBackground);
- const outerUI = document.createElement("canvas"); //额外ui画布设置(状态栏所有绘制、点击都在额外ui上)
- outerUI.style.position = "absolute";
- outerUI.style.zIndex = 165;
- outerUI.id = "outerUI";
+ const outerUI = document.createElement("canvas"); //额外ui画布设置(状态栏所有绘制、点击都在额外ui上)
+ outerUI.style.position = "absolute";
+ outerUI.style.zIndex = 165;
+ outerUI.id = "outerUI";
- main.dom.outerUI = outerUI;
- outerBackground.insertAdjacentElement("afterend", outerUI);
- setTimeout(function () {
- // Should be executed immediately after init()
- main.canvas.outerUI = outerUI.getContext("2d");
- });
- outerUI.onclick = function (e) {
- try {
- e.preventDefault();
- if (!core.isPlaying()) return false;
- const left = core.dom.gameGroup.offsetLeft;
- const top = core.dom.gameGroup.offsetTop;
- const px = Math.floor((e.clientX - left) / core.domStyle.scale),
- py = Math.floor((e.clientY - top) / core.domStyle.scale);
- core.ui.statusBar.onclick(px * 3, py * 3);
- } catch (ee) {
- main.log(ee);
- }
- };
+ main.dom.outerUI = outerUI;
+ outerBackground.insertAdjacentElement("afterend", outerUI);
+ setTimeout(function () {
+ // Should be executed immediately after init()
+ main.canvas.outerUI = outerUI.getContext("2d");
+ });
+ outerUI.onclick = function (e) {
+ try {
+ e.preventDefault();
+ if (!core.isPlaying()) return false;
+ const left = core.dom.gameGroup.offsetLeft;
+ const top = core.dom.gameGroup.offsetTop;
+ const px = Math.floor((e.clientX - left) / core.domStyle.scale),
+ py = Math.floor((e.clientY - top) / core.domStyle.scale);
+ core.ui.statusBar.onclick(px * 3, py * 3);
+ } catch (ee) {
+ main.log(ee);
+ }
+ };
- const _resize_gameGroup = function (obj) {
- //游戏画面自适应调节
- const gameGroup = core.dom.gameGroup;
- gameGroup.style.width = obj.totalWidth + "px";
- gameGroup.style.height = obj.totalHeight + "px";
- gameGroup.style.left = (obj.clientWidth - obj.totalWidth) / 2 + "px";
- gameGroup.style.top = (obj.clientHeight - obj.totalHeight) / 2 + "px";
- //floorMsgGroup为切换楼层中生效,显示时间可通过‘全塔属性’——‘切换楼层时间’或游戏内设置调整
- //显示内容为游戏名/版本号/楼层名
- // floorMsgGroup
- var floorMsgGroup = core.dom.floorMsgGroup;
- var globalAttribute =
- core.status.globalAttribute || core.initStatus.globalAttribute;
- floorMsgGroup.style = globalAttribute.floorChangingStyle;
- floorMsgGroup.style.height = floorMsgGroup.style.width =
- (GAMEVIEW_HEIGHT / 3) * core.domStyle.scale + "px";
- floorMsgGroup.style.fontSize = 16 * core.domStyle.scale + "px";
+ const _resize_gameGroup = function (obj) {
+ //游戏画面自适应调节
+ const gameGroup = core.dom.gameGroup;
+ gameGroup.style.width = obj.totalWidth + "px";
+ gameGroup.style.height = obj.totalHeight + "px";
+ gameGroup.style.left = (obj.clientWidth - obj.totalWidth) / 2 + "px";
+ gameGroup.style.top = (obj.clientHeight - obj.totalHeight) / 2 + "px";
+ //floorMsgGroup为切换楼层中生效,显示时间可通过‘全塔属性’——‘切换楼层时间’或游戏内设置调整
+ //显示内容为游戏名/版本号/楼层名
+ // floorMsgGroup
+ var floorMsgGroup = core.dom.floorMsgGroup;
+ var globalAttribute =
+ core.status.globalAttribute || core.initStatus.globalAttribute;
+ floorMsgGroup.style = globalAttribute.floorChangingStyle;
+ floorMsgGroup.style.height = floorMsgGroup.style.width =
+ (GAMEVIEW_HEIGHT / 3) * core.domStyle.scale + "px";
+ floorMsgGroup.style.fontSize = 16 * core.domStyle.scale + "px";
- if (core.domStyle.isVertical) {
- floorMsgGroup.style.left = "0px";
- floorMsgGroup.style.top =
- ((GAMEVIEW_HEIGHT_VERTICAL / 3 - GAMEVIEW_WIDTH_VERTICAL / 3) *
- core.domStyle.scale) /
- 2 +
- "px";
- } else {
- floorMsgGroup.style.left =
- ((GAMEVIEW_WIDTH / 3 - GAMEVIEW_HEIGHT / 3) * core.domStyle.scale) /
- 2 +
- "px";
- floorMsgGroup.style.top = "0px";
- }
- core.dom.musicBtn.style.right =
- (obj.clientWidth - obj.totalWidth) / 2 + "px";
- core.dom.musicBtn.style.bottom =
- (obj.clientHeight - obj.totalHeight) / 2 - 27 + "px";
- let startBackground = core.domStyle.isVertical ?
- main.styles.startVerticalBackground || main.styles.startBackground :
- main.styles.startBackground;
- if (main.dom.startBackground.getAttribute("__src__") != startBackground) {
- main.dom.startBackground.setAttribute("__src__", startBackground);
- main.dom.startBackground.src = startBackground;
- }
- const span = document
- .getElementById("startButtons")
- .getElementsByTagName("span");
- let font = (GAMEVIEW_WIDTH / 100) * core.domStyle.scale;
- if (core.domStyle.isVertical)
- font = ((GAMEVIEW_WIDTH_VERTICAL * 2) / 100) * core.domStyle.scale;
+ if (core.domStyle.isVertical) {
+ floorMsgGroup.style.left = "0px";
+ floorMsgGroup.style.top =
+ ((GAMEVIEW_HEIGHT_VERTICAL / 3 - GAMEVIEW_WIDTH_VERTICAL / 3) *
+ core.domStyle.scale) /
+ 2 +
+ "px";
+ } else {
+ floorMsgGroup.style.left =
+ ((GAMEVIEW_WIDTH / 3 - GAMEVIEW_HEIGHT / 3) * core.domStyle.scale) /
+ 2 +
+ "px";
+ floorMsgGroup.style.top = "0px";
+ }
+ core.dom.musicBtn.style.right =
+ (obj.clientWidth - obj.totalWidth) / 2 + "px";
+ core.dom.musicBtn.style.bottom =
+ (obj.clientHeight - obj.totalHeight) / 2 - 27 + "px";
+ let startBackground = core.domStyle.isVertical
+ ? main.styles.startVerticalBackground || main.styles.startBackground
+ : main.styles.startBackground;
+ if (main.dom.startBackground.getAttribute("__src__") != startBackground) {
+ main.dom.startBackground.setAttribute("__src__", startBackground);
+ main.dom.startBackground.src = startBackground;
+ }
+ const span = document
+ .getElementById("startButtons")
+ .getElementsByTagName("span");
+ let font = (GAMEVIEW_WIDTH / 100) * core.domStyle.scale;
+ if (core.domStyle.isVertical)
+ font = ((GAMEVIEW_WIDTH_VERTICAL * 2) / 100) * core.domStyle.scale;
- core.dom.playGame.style.fontSize = font + "px";
- core.dom.loadGame.style.fontSize = font + "px";
- core.dom.CGMode.style.fontSize = font + "px";
- core.dom.musicMode.style.fontSize = font + "px";
- core.dom.replayGame.style.fontSize = font + "px";
- core.dom.startButtonGroup.style.padding = font * 0.3 + "px 25px";
- };
- const _resize_canvas = function (obj) {
- //自适应画布
- main.dom.outerBackground.style.width = obj.totalWidth + "px";
- main.dom.outerBackground.style.height = obj.totalHeight + "px";
- main.dom.outerUI.style.width = obj.totalWidth + "px";
- main.dom.outerUI.style.height = obj.totalHeight + "px";
- if (main.dom.CGUI) {
- main.dom.CGUI.style.width = obj.totalWidth + 3 + "px";
- main.dom.CGUI.style.height = obj.totalHeight + 3 + "px";
- }
- if (main.dom.music) {
- main.dom.music.style.width = obj.totalWidth + 3 + "px";
- main.dom.music.style.height = obj.totalHeight + 3 + "px";
- }
- if (main.dom.cgText) {
- main.dom.cgText.style.width = obj.totalWidth + 3 + "px";
- main.dom.cgText.style.height = obj.totalHeight + 3 + "px";
- }
- if (main.dom.logcanvas) {
+ core.dom.playGame.style.fontSize = font + "px";
+ core.dom.loadGame.style.fontSize = font + "px";
+ core.dom.CGMode.style.fontSize = font + "px";
+ core.dom.musicMode.style.fontSize = font + "px";
+ core.dom.replayGame.style.fontSize = font + "px";
+ core.dom.startButtonGroup.style.padding = font * 0.3 + "px 25px";
+ };
+ const _resize_canvas = function (obj) {
+ //自适应画布
+ main.dom.outerBackground.style.width = obj.totalWidth + "px";
+ main.dom.outerBackground.style.height = obj.totalHeight + "px";
+ main.dom.outerUI.style.width = obj.totalWidth + "px";
+ main.dom.outerUI.style.height = obj.totalHeight + "px";
+ if (main.dom.CGUI) {
+ main.dom.CGUI.style.width = obj.totalWidth + 3 + "px";
+ main.dom.CGUI.style.height = obj.totalHeight + 3 + "px";
+ }
+ if (main.dom.music) {
+ main.dom.music.style.width = obj.totalWidth + 3 + "px";
+ main.dom.music.style.height = obj.totalHeight + 3 + "px";
+ }
+ if (main.dom.cgText) {
+ main.dom.cgText.style.width = obj.totalWidth + 3 + "px";
+ main.dom.cgText.style.height = obj.totalHeight + 3 + "px";
+ }
+ if (main.dom.logcanvas) {
+ main.dom.logcanvas.style.width = obj.totalWidth + 3 + "px";
+ main.dom.logcanvas.style.height = obj.totalHeight + 3 + "px";
+ }
+ if (main.dom.over) {
+ main.dom.over.style.width = obj.totalWidth + 3 + "px";
+ main.dom.over.style.height = obj.totalHeight + 3 + "px";
+ }
+ if (main.dom.video) {
+ main.dom.video.style.width = obj.totalWidth + 3 + "px";
+ main.dom.video.style.height = obj.totalHeight + 3 + "px";
+ if (core.domStyle.isVertical)
+ main.dom.video.style.width = obj.totalHeight + 3 + "px";
+ if (core.domStyle.isVertical)
+ main.dom.video.style.height = obj.totalWidth + 3 + "px";
+ main.dom.video.style.top = "50%";
+ main.dom.video.style.left = "50%";
- main.dom.logcanvas.style.width = obj.totalWidth + 3 + "px";
- main.dom.logcanvas.style.height = obj.totalHeight + 3 + "px";
- }
- if (main.dom.over) {
- main.dom.over.style.width = obj.totalWidth + 3 + "px";
- main.dom.over.style.height = obj.totalHeight + 3 + "px";
- }
- if (main.dom.video) {
- main.dom.video.style.width = obj.totalWidth + 3 + "px";
- main.dom.video.style.height = obj.totalHeight + 3 + "px";
- if (core.domStyle.isVertical)
- main.dom.video.style.width = obj.totalHeight + 3 + "px";
- if (core.domStyle.isVertical)
- main.dom.video.style.height = obj.totalWidth + 3 + "px";
- main.dom.video.style.top = "50%";
- main.dom.video.style.left = "50%";
+ main.dom.video.style.transform = "translate(-50%,-50%)";
- main.dom.video.style.transform = "translate(-50%,-50%)";
+ if (core.domStyle.isVertical)
+ main.dom.video.style.transform = "translate(-50%,-50%) rotate(90deg)";
+ }
+ if (main.dom.video1) {
+ main.dom.video1.style.width = obj.totalWidth + 3 + "px";
+ main.dom.video1.style.height = obj.totalHeight + 3 + "px";
+ }
- if (core.domStyle.isVertical)
- main.dom.video.style.transform = "translate(-50%,-50%) rotate(90deg)";
- }
- if (main.dom.video1) {
- main.dom.video1.style.width = obj.totalWidth + 3 + "px";
- main.dom.video1.style.height = obj.totalHeight + 3 + "px";
- }
+ const innerSize = obj.canvasWidth * core.domStyle.scale + "px";
+ for (let i = 0; i < core.dom.gameCanvas.length; ++i)
+ core.dom.gameCanvas[i].style.width = core.dom.gameCanvas[
+ i
+ ].style.height = innerSize;
+ core.dom.gif.style.width = core.dom.gif.style.height = innerSize;
+ core.dom.gif2.style.width = core.dom.gif2.style.height = innerSize;
- const innerSize = obj.canvasWidth * core.domStyle.scale + "px";
- for (let i = 0; i < core.dom.gameCanvas.length; ++i)
- core.dom.gameCanvas[i].style.width = core.dom.gameCanvas[
- i
- ].style.height = innerSize;
- core.dom.gif.style.width = core.dom.gif.style.height = innerSize;
- core.dom.gif2.style.width = core.dom.gif2.style.height = innerSize;
+ core.dom.gameDraw.style.width = core.dom.gameDraw.style.height =
+ innerSize;
+ core.dom.gameDraw.style.top =
+ obj.gameDrawBox.top * core.domStyle.scale + "px";
+ core.dom.gameDraw.style.left =
+ obj.gameDrawBox.left * core.domStyle.scale + "px";
+ // resize bigmap
+ core.bigmap.canvas.forEach(function (cn) {
+ const ratio = core.canvas[cn].canvas.hasAttribute("isHD")
+ ? core.domStyle.ratio
+ : 1;
+ core.canvas[cn].canvas.style.width =
+ (innerSize / ratio) * core.domStyle.scale + "px";
+ core.canvas[cn].canvas.style.height =
+ (innerSize / ratio) * core.domStyle.scale + "px";
+ });
+ // resize dynamic canvas
+ for (const name in core.dymCanvas) {
+ const ctx = core.dymCanvas[name],
+ canvas = ctx.canvas;
+ const ratio = canvas.hasAttribute("isHD") ? core.domStyle.ratio : 1;
+ canvas.style.width = (innerSize / ratio) * core.domStyle.scale + "px";
+ canvas.style.height = (innerSize / ratio) * core.domStyle.scale + "px";
+ canvas.style.left =
+ parseFloat(canvas.getAttribute("_left")) * core.domStyle.scale + "px";
+ canvas.style.top =
+ parseFloat(canvas.getAttribute("_top")) * core.domStyle.scale + "px";
+ }
+ // resize next
+ main.dom.next.style.width = main.dom.next.style.height =
+ 5 * core.domStyle.scale + "px";
+ main.dom.next.style.borderBottomWidth =
+ main.dom.next.style.borderRightWidth = 4 * core.domStyle.scale + "px";
+ };
+ const bgctx = main.dom.outerBackground.getContext("2d");
+ const uictx = main.dom.outerUI.getContext("2d");
+ let now = 0;
+ core.registerAnimationFrame("lightFloor", true, function (timestamp) {
+ if (timestamp - now > 1000 / 60) {
+ now = timestamp;
+ globalAlphafloor += globalAlphafloorStatus;
+ if (globalAlphafloor === 100) globalAlphafloorStatus = -2;
+ if (globalAlphafloor === 0) globalAlphafloorStatus = 2;
- core.dom.gameDraw.style.width = core.dom.gameDraw.style.height =
- innerSize;
- core.dom.gameDraw.style.top =
- obj.gameDrawBox.top * core.domStyle.scale + "px";
- core.dom.gameDraw.style.left =
- obj.gameDrawBox.left * core.domStyle.scale + "px";
- // resize bigmap
- core.bigmap.canvas.forEach(function (cn) {
- const ratio = core.canvas[cn].canvas.hasAttribute("isHD") ?
- core.domStyle.ratio :
- 1;
- core.canvas[cn].canvas.style.width =
- (innerSize / ratio) * core.domStyle.scale + "px";
- core.canvas[cn].canvas.style.height =
- (innerSize / ratio) * core.domStyle.scale + "px";
- });
- // resize dynamic canvas
- for (const name in core.dymCanvas) {
- const ctx = core.dymCanvas[name],
- canvas = ctx.canvas;
- const ratio = canvas.hasAttribute("isHD") ? core.domStyle.ratio : 1;
- canvas.style.width = (innerSize / ratio) * core.domStyle.scale + "px";
- canvas.style.height = (innerSize / ratio) * core.domStyle.scale + "px";
- canvas.style.left =
- parseFloat(canvas.getAttribute("_left")) * core.domStyle.scale + "px";
- canvas.style.top =
- parseFloat(canvas.getAttribute("_top")) * core.domStyle.scale + "px";
- }
- // resize next
- main.dom.next.style.width = main.dom.next.style.height =
- 5 * core.domStyle.scale + "px";
- main.dom.next.style.borderBottomWidth =
- main.dom.next.style.borderRightWidth = 4 * core.domStyle.scale + "px";
- };
- const bgctx = main.dom.outerBackground.getContext("2d");
- const uictx = main.dom.outerUI.getContext("2d");
- let now = 0;
- core.registerAnimationFrame("lightFloor", true, function (timestamp) {
- if (timestamp - now > 1000 / 60) {
- now = timestamp;
- globalAlphafloor += globalAlphafloorStatus;
- if (globalAlphafloor === 100) globalAlphafloorStatus = -2;
- if (globalAlphafloor === 0) globalAlphafloorStatus = 2;
+ if (core.domStyle.isVertical) {
+ core.clearMap(
+ uictx,
+ MAP_BLOCK_LEFT_VERTICAL,
+ MAP_BLOCK_TOP_VERTICAL,
+ 340,
+ 360
+ );
+ if (core.status.event.id === "viewMaps") {
+ core.ui.statusBar._update_map(core.status.event.data.floorId);
+ } else {
+ core.ui.statusBar._update_map();
+ }
- if (core.domStyle.isVertical) {
- core.clearMap(
- uictx,
- MAP_BLOCK_LEFT_VERTICAL,
- MAP_BLOCK_TOP_VERTICAL,
- 340,
- 360
- );
- if (core.status.event.id === "viewMaps") {
- core.ui.statusBar._update_map(core.status.event.data.floorId);
- } else {
- core.ui.statusBar._update_map();
- }
+ uictx.globalAlpha = globalAlphafloor / 100;
+ core.drawImage(
+ uictx,
+ "green.webp",
+ MAP_BLOCK_LEFT_VERTICAL + 135,
+ MAP_BLOCK_TOP_VERTICAL + 170
+ );
+ uictx.globalAlpha = 1;
+ } else {
+ core.clearMap(uictx, MAP_BLOCK_LEFT, MAP_BLOCK_TOP, 340, 360);
+ if (core.status.event.id === "viewMaps") {
+ core.ui.statusBar._update_map(core.status.event.data.floorId);
+ } else {
+ core.ui.statusBar._update_map();
+ }
+ uictx.globalAlpha = globalAlphafloor / 100;
+ core.drawImage(
+ uictx,
+ "green.webp",
+ MAP_BLOCK_LEFT + 150,
+ MAP_BLOCK_TOP + 180
+ );
+ uictx.globalAlpha = 1;
+ }
+ }
+ });
- uictx.globalAlpha = globalAlphafloor / 100;
- core.drawImage(
- uictx,
- "green.webp",
- MAP_BLOCK_LEFT_VERTICAL + 135,
- MAP_BLOCK_TOP_VERTICAL + 170
- );
- uictx.globalAlpha = 1;
- } else {
- core.clearMap(uictx, MAP_BLOCK_LEFT, MAP_BLOCK_TOP, 340, 360);
- if (core.status.event.id === "viewMaps") {
- core.ui.statusBar._update_map(core.status.event.data.floorId);
- } else {
- core.ui.statusBar._update_map();
- }
- uictx.globalAlpha = globalAlphafloor / 100;
- core.drawImage(
- uictx,
- "green.webp",
- MAP_BLOCK_LEFT + 150,
- MAP_BLOCK_TOP + 180
- );
- uictx.globalAlpha = 1;
- }
- }
- });
+ core.control.resize = function () {
+ //自适应,可实现横竖屏切换
+ if (main.mode == "editor") return;
- core.control.resize = function () {
- //自适应,可实现横竖屏切换
- if (main.mode == "editor") return;
+ const clientWidth = main.dom.body.clientWidth,
+ clientHeight = main.dom.body.clientHeight;
+ const canvasWidth = core.__PIXELS__;
- const clientWidth = main.dom.body.clientWidth,
- clientHeight = main.dom.body.clientHeight;
- const canvasWidth = core.__PIXELS__;
+ const isVertical = clientHeight > clientWidth;
+ core.domStyle.isVertical = isVertical;
- const isVertical = clientHeight > clientWidth;
- core.domStyle.isVertical = isVertical;
+ const totalWidth = isVertical
+ ? GAMEVIEW_WIDTH_VERTICAL / 3
+ : GAMEVIEW_WIDTH / 3,
+ totalHeight = isVertical
+ ? GAMEVIEW_HEIGHT_VERTICAL / 3
+ : GAMEVIEW_HEIGHT / 3;
- const totalWidth = isVertical ?
- GAMEVIEW_WIDTH_VERTICAL / 3 :
- GAMEVIEW_WIDTH / 3,
- totalHeight = isVertical ?
- GAMEVIEW_HEIGHT_VERTICAL / 3 :
- GAMEVIEW_HEIGHT / 3;
+ const maxRatio = Math.min(
+ clientWidth / totalWidth,
+ clientHeight / totalHeight
+ );
- const maxRatio = Math.min(
- clientWidth / totalWidth,
- clientHeight / totalHeight
- );
+ core.domStyle.availableScale = [];
+ [1, 1.25, 1.5, 1.75, 2].forEach(function (v) {
+ if (maxRatio >= v) {
+ core.domStyle.availableScale.push(v);
+ }
+ });
- core.domStyle.availableScale = [];
- [1, 1.25, 1.5, 1.75, 2].forEach(function (v) {
- if (maxRatio >= v) {
- core.domStyle.availableScale.push(v);
- }
- });
+ if (core.domStyle.availableScale.indexOf(core.domStyle.scale) < 0) {
+ core.domStyle.scale = Math.min(1, maxRatio);
+ } else if (
+ core.getLocalStorage("scale") == null &&
+ core.domStyle.availableScale.length >= 2
+ ) {
+ core.domStyle.scale =
+ core.domStyle.availableScale[core.domStyle.availableScale.length - 2];
+ core.setLocalStorage("scale", core.domStyle.scale);
+ }
- if (core.domStyle.availableScale.indexOf(core.domStyle.scale) < 0) {
- core.domStyle.scale = Math.min(1, maxRatio);
- } else if (
- core.getLocalStorage("scale") == null &&
- core.domStyle.availableScale.length >= 2
- ) {
- core.domStyle.scale =
- core.domStyle.availableScale[core.domStyle.availableScale.length - 2];
- core.setLocalStorage("scale", core.domStyle.scale);
- }
+ const totalWidthScaled = totalWidth * core.domStyle.scale,
+ totalHeightScaled = totalHeight * core.domStyle.scale;
- const totalWidthScaled = totalWidth * core.domStyle.scale,
- totalHeightScaled = totalHeight * core.domStyle.scale;
+ const gameDrawBox = isVertical
+ ? {
+ left: BORDER_WIDTH / 3,
+ top: BAR_HEIGHT_VERTICAL / 3 + BORDER_HEIGHT / 3,
+ }
+ : { left: BAR_WIDTH / 3 + BORDER_WIDTH / 3, top: BORDER_HEIGHT / 3 };
- const gameDrawBox = isVertical ? {
- left: BORDER_WIDTH / 3,
- top: BAR_HEIGHT_VERTICAL / 3 + BORDER_HEIGHT / 3,
- } : { left: BAR_WIDTH / 3 + BORDER_WIDTH / 3, top: BORDER_HEIGHT / 3 };
+ const obj = {
+ clientWidth: clientWidth,
+ clientHeight: clientHeight,
+ canvasWidth: canvasWidth,
+ totalWidth: totalWidthScaled,
+ totalHeight: totalHeightScaled,
+ gameDrawBox: gameDrawBox,
+ globalAttribute:
+ core.status.globalAttribute || core.initStatus.globalAttribute,
+ };
- const obj = {
- clientWidth: clientWidth,
- clientHeight: clientHeight,
- canvasWidth: canvasWidth,
- totalWidth: totalWidthScaled,
- totalHeight: totalHeightScaled,
- gameDrawBox: gameDrawBox,
- globalAttribute: core.status.globalAttribute || core.initStatus.globalAttribute,
- };
+ _resize_gameGroup(obj);
+ _resize_canvas(obj);
- _resize_gameGroup(obj);
- _resize_canvas(obj);
+ if (core.status.automaticRoute == null) core.status.automaticRoute = {};
+ core.updateStatusBar();
+ if (main.dom.CGUI && main.dom.CGUI.style.display === "block")
+ core.ui.CG.update();
+ if (main.dom.music && main.dom.music.style.display === "block")
+ core.ui.music.update();
+ if (main.dom.cgText && main.dom.cgText.style.display === "block")
+ core.ui.cgText.update();
+ if (main.dom.logcanvas && main.dom.logcanvas.style.display === "block")
+ core.ui.cgText.update();
+ };
- if (core.status.automaticRoute == null) core.status.automaticRoute = {};
- core.updateStatusBar();
- if (main.dom.CGUI && main.dom.CGUI.style.display === "block")
- core.ui.CG.update();
- if (main.dom.music && main.dom.music.style.display === "block")
- core.ui.music.update();
- if (main.dom.cgText && main.dom.cgText.style.display === "block")
- core.ui.cgText.update();
- if (main.dom.logcanvas && main.dom.logcanvas.style.display === "block")
- core.ui.cgText.update();
- };
+ class StatusBar {
+ constructor() {
+ //道具栏列表
+ this.itemMx = [
+ //空位用‘none’填充,当前ui至多4列6行
+ ["book", "wand", "none", "fly"],
+ ["cross", "superPotion", "pickaxe"],
+ ["bomb", "centerFly", "upFly"],
+ ["none", "none", "none"],
+ ["downFly", "knife", "snow"],
+ ["bigKey", "earthquake", "coin"],
+ ];
+ }
+ //初始化内容(工具栏/录像操作执行函数)
+ init() {
+ this.toolbarAction = [
+ [
+ main.core.openKeyBoard,
+ main.core.openQuickShop,
+ core.openToolbox,
+ core.doSL,
+ ],
+ [main.core.openSettings, main.core.save, main.core.load, core.doSL],
+ ];
+ this.replayAction = [
+ [core.triggerReplay, core.stopReplay, core.rewindReplay],
+ [core.speedDownReplay, core.speedUpReplay, core.saveReplay],
+ ];
+ }
+ //更新
+ update() {
+ this._update_background(); //更新背景
+ this._update_props(); //更新属性
+ //this._update_items(); //更新道具
+ //this._update_equips(); //更新装备
+ //this._update_keys(); //更新钥匙
+ //this._update_infoWindow(); //更新道具说明
+ this._update_toolBox(); //更新工具栏
+ this._redrawMap();
+ }
+ _redrawMap() {
+ if (core.domStyle.isVertical) {
+ core.clearMap(
+ uictx,
+ MAP_BLOCK_LEFT_VERTICAL,
+ MAP_BLOCK_TOP_VERTICAL,
+ 340,
+ 360
+ );
+ this._update_map();
+ uictx.globalAlpha = globalAlphafloor / 100;
+ core.drawImage(
+ uictx,
+ "green.webp",
+ MAP_BLOCK_LEFT_VERTICAL + 125,
+ MAP_BLOCK_TOP_VERTICAL + 170
+ );
+ uictx.globalAlpha = 1;
+ } else {
+ core.clearMap(uictx, MAP_BLOCK_LEFT, MAP_BLOCK_TOP, 340, 360);
+ this._update_map();
+ uictx.globalAlpha = globalAlphafloor / 100;
+ core.drawImage(
+ uictx,
+ "green.webp",
+ MAP_BLOCK_LEFT + 150,
+ MAP_BLOCK_TOP + 170
+ );
+ uictx.globalAlpha = 1;
+ }
+ }
+ //更新背景
+ _update_background() {
+ if (core.domStyle.isVertical) {
+ bgctx.canvas.width = GAMEVIEW_WIDTH_VERTICAL;
+ bgctx.canvas.height = GAMEVIEW_HEIGHT_VERTICAL;
+ uictx.canvas.width = GAMEVIEW_WIDTH_VERTICAL;
+ uictx.canvas.height = GAMEVIEW_HEIGHT_VERTICAL;
- class StatusBar {
- constructor() {
- //道具栏列表
- this.itemMx = [
- //空位用‘none’填充,当前ui至多4列6行
- ["book", "wand", "none", "fly"],
- ["cross", "superPotion", "pickaxe"],
- ["bomb", "centerFly", "upFly"],
- ["none", "none", "none"],
- ["downFly", "knife", "snow"],
- ["bigKey", "earthquake", "coin"],
- ];
- }
- //初始化内容(工具栏/录像操作执行函数)
- init() {
- this.toolbarAction = [
- [
- main.core.openKeyBoard,
- main.core.openQuickShop,
- core.openToolbox,
- core.doSL,
- ],
- [main.core.openSettings, main.core.save, main.core.load, core.doSL],
- ];
- this.replayAction = [
- [core.triggerReplay, core.stopReplay, core.rewindReplay],
- [core.speedDownReplay, core.speedUpReplay, core.saveReplay],
- ];
- }
- //更新
- update() {
- this._update_background(); //更新背景
- this._update_props(); //更新属性
- //this._update_items(); //更新道具
- //this._update_equips(); //更新装备
- //this._update_keys(); //更新钥匙
- //this._update_infoWindow(); //更新道具说明
- this._update_toolBox(); //更新工具栏
- this._redrawMap();
- }
- _redrawMap() {
- if (core.domStyle.isVertical) {
- core.clearMap(
- uictx,
- MAP_BLOCK_LEFT_VERTICAL,
- MAP_BLOCK_TOP_VERTICAL,
- 340,
- 360
- );
- this._update_map();
- uictx.globalAlpha = globalAlphafloor / 100;
- core.drawImage(
- uictx,
- "green.webp",
- MAP_BLOCK_LEFT_VERTICAL + 125,
- MAP_BLOCK_TOP_VERTICAL + 170
- );
- uictx.globalAlpha = 1;
- } else {
- core.clearMap(uictx, MAP_BLOCK_LEFT, MAP_BLOCK_TOP, 340, 360);
- this._update_map();
- uictx.globalAlpha = globalAlphafloor / 100;
- core.drawImage(
- uictx,
- "green.webp",
- MAP_BLOCK_LEFT + 150,
- MAP_BLOCK_TOP + 170
- );
- uictx.globalAlpha = 1;
- }
- }
- //更新背景
- _update_background() {
- if (core.domStyle.isVertical) {
- bgctx.canvas.width = GAMEVIEW_WIDTH_VERTICAL;
- bgctx.canvas.height = GAMEVIEW_HEIGHT_VERTICAL;
- uictx.canvas.width = GAMEVIEW_WIDTH_VERTICAL;
- uictx.canvas.height = GAMEVIEW_HEIGHT_VERTICAL;
+ const bg = core.material.images.images["status.webp"]; //竖屏背景(上)
+ bgctx.drawImage(
+ bg,
+ 0,
+ 0,
+ GAMEVIEW_WIDTH_VERTICAL,
+ BAR_HEIGHT_VERTICAL
+ );
+ const bg2 = core.material.images.images["status.webp"]; //竖屏背景(下)
+ bgctx.drawImage(
+ bg2,
+ 0,
+ BAR_HEIGHT_VERTICAL + GAMEVIEW_WIDTH_VERTICAL,
+ GAMEVIEW_WIDTH_VERTICAL,
+ BAR_HEIGHT_VERTICAL
+ );
+ bgctx.globalAlpha = globalAlpha;
+ bgctx.globalAlpha = 1;
+ core.setTextAlign("outerUI", "center");
+ } else {
+ bgctx.canvas.width = GAMEVIEW_WIDTH;
+ bgctx.canvas.height = GAMEVIEW_HEIGHT;
+ uictx.canvas.width = GAMEVIEW_WIDTH;
+ uictx.canvas.height = GAMEVIEW_HEIGHT;
- const bg = core.material.images.images["status.webp"]; //竖屏背景(上)
- bgctx.drawImage(
- bg,
- 0,
- 0,
- GAMEVIEW_WIDTH_VERTICAL,
- BAR_HEIGHT_VERTICAL
- );
- const bg2 = core.material.images.images["status.webp"]; //竖屏背景(下)
- bgctx.drawImage(
- bg2,
- 0,
- BAR_HEIGHT_VERTICAL + GAMEVIEW_WIDTH_VERTICAL,
- GAMEVIEW_WIDTH_VERTICAL,
- BAR_HEIGHT_VERTICAL
- );
- bgctx.globalAlpha = globalAlpha;
- bgctx.globalAlpha = 1;
- core.setTextAlign("outerUI", "center");
- } else {
- bgctx.canvas.width = GAMEVIEW_WIDTH;
- bgctx.canvas.height = GAMEVIEW_HEIGHT;
- uictx.canvas.width = GAMEVIEW_WIDTH;
- uictx.canvas.height = GAMEVIEW_HEIGHT;
+ const bg = core.material.images.images["status.webp"]; //横屏背景(左)
+ bgctx.drawImage(bg, 0, 0, BAR_WIDTH, GAMEVIEW_HEIGHT);
+ const bg2 = core.material.images.images["status.webp"]; //横屏背景(右)
+ bgctx.drawImage(
+ bg2,
+ BAR_WIDTH + GAMEVIEW_HEIGHT,
+ 0,
+ BAR_WIDTH,
+ GAMEVIEW_HEIGHT
+ );
+ bgctx.globalAlpha = globalAlpha;
- const bg = core.material.images.images["status.webp"]; //横屏背景(左)
- bgctx.drawImage(bg, 0, 0, BAR_WIDTH, GAMEVIEW_HEIGHT);
- const bg2 = core.material.images.images["status.webp"]; //横屏背景(右)
- bgctx.drawImage(
- bg2,
- BAR_WIDTH + GAMEVIEW_HEIGHT,
- 0,
- BAR_WIDTH,
- GAMEVIEW_HEIGHT
- );
- bgctx.globalAlpha = globalAlpha;
+ bgctx.globalAlpha = 1;
+ core.setTextAlign("outerUI", "center");
+ }
+ }
+ // 更新属性
+ _update_props(updatedFloorTitle) {
+ if (!updatedFloorTitle && core.status.floorId) {
+ updatedFloorTitle = core.status.maps[core.status.floorId].title;
+ }
+ const statusList = ["hp", "atk", "def", "money"]; //属性列表,图标在函数复写core.statusBar.icons中声明,数字为project\materials\icons.png中的图标序号(可使用便捷ps追加,第一个序号为0)
+ const drawStatusList = (baseX, baseY) => {
+ let curh = baseY;
+ core.setTextAlign("outerUI", "right");
+ statusList.forEach((item) => {
+ // 绘制图标
+ core.drawIcon(
+ "outerUI",
+ item,
+ baseX - 95 * 3,
+ curh - 18 * 3,
+ 22 * 3,
+ 22 * 3
+ );
- bgctx.globalAlpha = 1;
- core.setTextAlign("outerUI", "center");
- }
- }
- // 更新属性
- _update_props(updatedFloorTitle) {
- if (!updatedFloorTitle && core.status.floorId) {
- updatedFloorTitle = core.status.maps[core.status.floorId].title;
- }
- const statusList = ["hp", "atk", "def", "money"]; //属性列表,图标在函数复写core.statusBar.icons中声明,数字为project\materials\icons.png中的图标序号(可使用便捷ps追加,第一个序号为0)
- const drawStatusList = (baseX, baseY) => {
- let curh = baseY;
- core.setTextAlign("outerUI", "right");
- statusList.forEach((item) => {
- // 绘制图标
- core.drawIcon(
- "outerUI",
- item,
- baseX - 95 * 3,
- curh - 18 * 3,
- 22 * 3,
- 22 * 3
- );
+ // 四舍五入
+ core.status.hero[item] = Math.round(core.status.hero[item]);
+ // 大数据格式化
+ core.fillBoldText1(
+ "outerUI",
+ core.getRealStatus(item),
+ baseX,
+ curh,
+ TEXT_COLOR,
+ "#000000",
+ 6
+ );
+ curh += 24 * 3;
+ if (curh > 130 * 3 && core.domStyle.isVertical) {
+ curh = 24 * 3;
+ baseX += 105 * 3;
+ }
+ });
+ core.setTextAlign("outerUI", "center");
+ };
+ if (core.domStyle.isVertical) {
+ core.clearMap("outerUI", 10 * 3, 0, 210 * 3, 120 * 3);
+ core.setFont("outerUI", "bold 42px Verdana");
+ if (updatedFloorTitle) {
+ core.fillBoldText1(
+ "outerUI",
+ updatedFloorTitle,
+ 60 * 3,
+ 22 * 3,
+ TEXT_COLOR,
+ "#000000",
+ 6
+ );
+ }
+ //drawStatusList(96 * 3, 46 * 3);
+ //core.drawImage("outerUI", "lane1.png", 0, 0)
+ core.drawImage("outerUI", "cao.webp", 0, 0);
+ } else {
+ core.clearMap("outerUI", 10 * 3, 40 * 3, 105 * 3, 250 * 3);
+ core.setFont("outerUI", "bold 48px Verdana");
+ if (updatedFloorTitle) {
+ core.fillBoldText1(
+ "outerUI",
+ updatedFloorTitle,
+ 62 * 3,
+ 41 * 3,
+ TEXT_COLOR,
+ "#000000",
+ 6
+ );
+ }
+ //drawStatusList(110 * 3, 93 * 3);
+ //core.drawImage("outerUI", "lane1.png", 0, 30)
+ core.drawImage(
+ "outerUI",
+ "cao.webp",
+ 0,
+ 0,
+ 400,
+ 350,
+ 0,
+ 30,
+ 360,
+ 315
+ );
+ }
+ }
+ _update_items() {
+ //更新道具栏
+ const drawItemMx = (drawFn) => {
+ for (let i = 0; i < this.itemMx.length; i++) {
+ for (let j = 0; j < this.itemMx[i].length; j++) {
+ var item = this.itemMx[i][j];
+ drawFn(i, j, item);
+ }
+ }
+ };
+ const drawItem = (item, posx, posy) => {
+ const icon = core.material.icons.items[item],
+ image = core.material.images.items;
+ core.drawImage(
+ "outerUI",
+ image,
+ 0,
+ 32 * icon,
+ 32,
+ 32,
+ posx,
+ posy,
+ 30 * 3,
+ 30 * 3
+ );
+ const cnt = core.itemCount(item);
+ if (
+ (core.items.items[item].cls === "tools" && cnt > 1) ||
+ FORCE_COUNTABLE_ITEMS.includes(item)
+ ) {
+ core.fillText(
+ "outerUI",
+ cnt,
+ posx + 25 * 3,
+ posy + 28 * 3,
+ "#FFFFFF",
+ "bold 36px Verdana"
+ );
+ }
+ };
+ if (core.domStyle.isVertical) {
+ core.clearMap(
+ "outerUI",
+ ITEM_BOX_LEFT_VERTICAL,
+ ITEM_BOX_TOP_VERTICAL,
+ 185 * 3,
+ 125 * 3
+ );
- // 四舍五入
- core.status.hero[item] = Math.round(core.status.hero[item]);
- // 大数据格式化
- core.fillBoldText1(
- "outerUI",
- core.getRealStatus(item),
- baseX,
- curh,
- TEXT_COLOR,
- "#000000",
- 6
- );
- curh += 24 * 3;
- if (curh > 130 * 3 && core.domStyle.isVertical) {
- curh = 24 * 3;
- baseX += 105 * 3;
- }
- });
- core.setTextAlign("outerUI", "center");
- };
- if (core.domStyle.isVertical) {
- core.clearMap("outerUI", 10 * 3, 0, 210 * 3, 120 * 3);
- core.setFont("outerUI", "bold 42px Verdana");
- if (updatedFloorTitle) {
- core.fillBoldText1(
- "outerUI",
- updatedFloorTitle,
- 60 * 3,
- 22 * 3,
- TEXT_COLOR,
- "#000000",
- 6
- );
- }
- //drawStatusList(96 * 3, 46 * 3);
- //core.drawImage("outerUI", "lane1.png", 0, 0)
- core.drawImage("outerUI", "cao.webp", 0, 0);
- } else {
- core.clearMap("outerUI", 10 * 3, 40 * 3, 105 * 3, 250 * 3);
- core.setFont("outerUI", "bold 48px Verdana");
- if (updatedFloorTitle) {
- core.fillBoldText1(
- "outerUI",
- updatedFloorTitle,
- 62 * 3,
- 41 * 3,
- TEXT_COLOR,
- "#000000",
- 6
- );
- }
- //drawStatusList(110 * 3, 93 * 3);
- //core.drawImage("outerUI", "lane1.png", 0, 30)
- core.drawImage(
- "outerUI",
- "cao.webp",
- 0,
- 0,
- 400,
- 350,
- 0,
- 30,
- 360,
- 315
- );
- }
- }
- _update_items() {
- //更新道具栏
- const drawItemMx = (drawFn) => {
- for (let i = 0; i < this.itemMx.length; i++) {
- for (let j = 0; j < this.itemMx[i].length; j++) {
- var item = this.itemMx[i][j];
- drawFn(i, j, item);
- }
- }
- };
- const drawItem = (item, posx, posy) => {
- const icon = core.material.icons.items[item],
- image = core.material.images.items;
- core.drawImage(
- "outerUI",
- image,
- 0,
- 32 * icon,
- 32,
- 32,
- posx,
- posy,
- 30 * 3,
- 30 * 3
- );
- const cnt = core.itemCount(item);
- if (
- (core.items.items[item].cls === "tools" && cnt > 1) ||
- FORCE_COUNTABLE_ITEMS.includes(item)
- ) {
- core.fillText(
- "outerUI",
- cnt,
- posx + 25 * 3,
- posy + 28 * 3,
- "#FFFFFF",
- "bold 36px Verdana"
- );
- }
- };
- if (core.domStyle.isVertical) {
- core.clearMap(
- "outerUI",
- ITEM_BOX_LEFT_VERTICAL,
- ITEM_BOX_TOP_VERTICAL,
- 185 * 3,
- 125 * 3
- );
+ drawItemMx((i, j, item) => {
+ if (core.hasItem(item)) {
+ const posx = ITEM_BOX_LEFT_VERTICAL + i * 30 * 3,
+ posy = ITEM_BOX_TOP_VERTICAL + j * 31 * 3;
+ drawItem(item, posx, posy);
+ }
+ });
+ } else {
+ core.clearMap(
+ "outerUI",
+ ITEM_BOX_LEFT,
+ ITEM_BOX_TOP,
+ 125 * 3,
+ 185 * 3
+ );
- drawItemMx((i, j, item) => {
- if (core.hasItem(item)) {
- const posx = ITEM_BOX_LEFT_VERTICAL + i * 30 * 3,
- posy = ITEM_BOX_TOP_VERTICAL + j * 31 * 3;
- drawItem(item, posx, posy);
- }
- });
- } else {
- core.clearMap(
- "outerUI",
- ITEM_BOX_LEFT,
- ITEM_BOX_TOP,
- 125 * 3,
- 185 * 3
- );
+ drawItemMx((i, j, item) => {
+ if (core.hasItem(item)) {
+ const posx = ITEM_BOX_LEFT + j * 30 * 3,
+ posy = ITEM_BOX_TOP + i * 31 * 3;
+ drawItem(item, posx, posy);
+ }
+ });
+ }
+ }
- drawItemMx((i, j, item) => {
- if (core.hasItem(item)) {
- const posx = ITEM_BOX_LEFT + j * 30 * 3,
- posy = ITEM_BOX_TOP + i * 31 * 3;
- drawItem(item, posx, posy);
- }
- });
- }
- }
+ _update_map(floorId = core.status.floorId) {
+ const x = core.domStyle.isVertical
+ ? MAP_BLOCK_LEFT_VERTICAL
+ : MAP_BLOCK_LEFT;
+ const y = core.domStyle.isVertical
+ ? MAP_BLOCK_TOP_VERTICAL
+ : MAP_BLOCK_TOP;
- _update_map(floorId = core.status.floorId) {
- const x = core.domStyle.isVertical ?
- MAP_BLOCK_LEFT_VERTICAL :
- MAP_BLOCK_LEFT;
- const y = core.domStyle.isVertical ?
- MAP_BLOCK_TOP_VERTICAL :
- MAP_BLOCK_TOP;
+ if (!floorId) return;
+ const info = core.plugin.getMapDrawInfo(floorId, Infinity, true);
+ core.setTextAlign("outerUI", "center");
- if (!floorId) return;
- const info = core.plugin.getMapDrawInfo(floorId, Infinity, true);
- core.setTextAlign("outerUI", "center");
+ core.plugin.drawSmallMap(uictx, info, floorId, x, y, 300, 300);
+ }
- core.plugin.drawSmallMap(uictx, info, floorId, x, y, 300, 300);
- }
+ _update_equips() {
+ return;
+ core.setFont("outerUI", "bold 48px Verdana");
+ const drawEquip = (baseX, baseY, id, color, back) => {
+ if (!id)
+ core.fillText(
+ "outerUI",
+ back,
+ baseX + 20 * 3,
+ baseY + 22 * 3,
+ color
+ );
+ else {
+ var icon = core.material.icons.items[id];
+ core.drawImage(
+ "outerUI",
+ core.material.images.items,
+ 0,
+ 32 * icon,
+ 32,
+ 32,
+ baseX + 5 * 3,
+ baseY,
+ 32 * 3,
+ 32 * 3
+ );
+ }
+ };
+ if (core.domStyle.isVertical) {
+ core.clearMap(
+ "outerUI",
+ EQUIP_BLOCK_LEFT_VERTICAL,
+ EQUIP_BLOCK_TOP_VERTICAL,
+ 90 * 3,
+ 130 * 3
+ );
+ drawEquip(
+ EQUIP_BLOCK_LEFT_VERTICAL,
+ EQUIP_BLOCK_TOP_VERTICAL,
+ core.getEquip(0),
+ "#D1CEFF",
+ "无"
+ );
+ drawEquip(
+ EQUIP_BLOCK_LEFT_VERTICAL + 45 * 3,
+ EQUIP_BLOCK_TOP_VERTICAL,
+ core.getEquip(1),
+ "#D1CEFF",
+ "无"
+ );
+ drawEquip(
+ EQUIP_BLOCK_LEFT_VERTICAL,
+ EQUIP_BLOCK_TOP_VERTICAL + 45 * 3,
+ core.getEquip(2),
+ "#D1CEFF",
+ "无"
+ );
+ drawEquip(
+ EQUIP_BLOCK_LEFT_VERTICAL + 45 * 3,
+ EQUIP_BLOCK_TOP_VERTICAL + 45 * 3,
+ core.getEquip(3),
+ "#D1CEFF",
+ "无"
+ );
+ drawEquip(
+ EQUIP_BLOCK_LEFT_VERTICAL,
+ EQUIP_BLOCK_TOP_VERTICAL + 90 * 3,
+ core.getEquip(4),
+ "#D1CEFF",
+ "无"
+ );
+ drawEquip(
+ EQUIP_BLOCK_LEFT_VERTICAL + 45 * 3,
+ EQUIP_BLOCK_TOP_VERTICAL + 90 * 3,
+ core.getEquip(5),
+ "#D1CEFF",
+ "无"
+ );
+ } else {
+ core.clearMap(
+ "outerUI",
+ EQUIP_BLOCK_LEFT,
+ EQUIP_BLOCK_TOP,
+ 130 * 3,
+ 95 * 3
+ );
+ drawEquip(
+ EQUIP_BLOCK_LEFT,
+ EQUIP_BLOCK_TOP,
+ core.getEquip(0),
+ "#D1CEFF",
+ "无"
+ );
+ drawEquip(
+ EQUIP_BLOCK_LEFT + 42 * 3,
+ EQUIP_BLOCK_TOP,
+ core.getEquip(1),
+ "#D1CEFF",
+ "无"
+ );
+ drawEquip(
+ EQUIP_BLOCK_LEFT + 85 * 3,
+ EQUIP_BLOCK_TOP,
+ core.getEquip(2),
+ "#D1CEFF",
+ "无"
+ );
+ drawEquip(
+ EQUIP_BLOCK_LEFT,
+ EQUIP_BLOCK_TOP + 45 * 3,
+ core.getEquip(3),
+ "#D1CEFF",
+ "无"
+ );
+ drawEquip(
+ EQUIP_BLOCK_LEFT + 42 * 3,
+ EQUIP_BLOCK_TOP + 45 * 3,
+ core.getEquip(4),
+ "#D1CEFF",
+ "无"
+ );
+ drawEquip(
+ EQUIP_BLOCK_LEFT + 85 * 3,
+ EQUIP_BLOCK_TOP + 45 * 3,
+ core.getEquip(5),
+ "#D1CEFF",
+ "无"
+ );
+ }
+ }
+ _update_keys() {
+ const drawKeyList = (baseX, baseY) => {
+ const todraw = [],
+ keyList = ["yellowKey", "blueKey", "redKey", "greenKey"];
+ let total = 0;
+ keyList.forEach(function (key, i) {
+ todraw[i] = core.itemCount(key);
+ total += todraw[i];
+ });
- _update_equips() {
- return;
- core.setFont("outerUI", "bold 48px Verdana");
- const drawEquip = (baseX, baseY, id, color, back) => {
- if (!id)
- core.fillText(
- "outerUI",
- back,
- baseX + 20 * 3,
- baseY + 22 * 3,
- color
- );
- else {
- var icon = core.material.icons.items[id];
- core.drawImage(
- "outerUI",
- core.material.images.items,
- 0,
- 32 * icon,
- 32,
- 32,
- baseX + 5 * 3,
- baseY,
- 32 * 3,
- 32 * 3
- );
- }
- };
- if (core.domStyle.isVertical) {
- core.clearMap(
- "outerUI",
- EQUIP_BLOCK_LEFT_VERTICAL,
- EQUIP_BLOCK_TOP_VERTICAL,
- 90 * 3,
- 130 * 3
- );
- drawEquip(
- EQUIP_BLOCK_LEFT_VERTICAL,
- EQUIP_BLOCK_TOP_VERTICAL,
- core.getEquip(0),
- "#D1CEFF",
- "无"
- );
- drawEquip(
- EQUIP_BLOCK_LEFT_VERTICAL + 45 * 3,
- EQUIP_BLOCK_TOP_VERTICAL,
- core.getEquip(1),
- "#D1CEFF",
- "无"
- );
- drawEquip(
- EQUIP_BLOCK_LEFT_VERTICAL,
- EQUIP_BLOCK_TOP_VERTICAL + 45 * 3,
- core.getEquip(2),
- "#D1CEFF",
- "无"
- );
- drawEquip(
- EQUIP_BLOCK_LEFT_VERTICAL + 45 * 3,
- EQUIP_BLOCK_TOP_VERTICAL + 45 * 3,
- core.getEquip(3),
- "#D1CEFF",
- "无"
- );
- drawEquip(
- EQUIP_BLOCK_LEFT_VERTICAL,
- EQUIP_BLOCK_TOP_VERTICAL + 90 * 3,
- core.getEquip(4),
- "#D1CEFF",
- "无"
- );
- drawEquip(
- EQUIP_BLOCK_LEFT_VERTICAL + 45 * 3,
- EQUIP_BLOCK_TOP_VERTICAL + 90 * 3,
- core.getEquip(5),
- "#D1CEFF",
- "无"
- );
- } else {
- core.clearMap(
- "outerUI",
- EQUIP_BLOCK_LEFT,
- EQUIP_BLOCK_TOP,
- 130 * 3,
- 95 * 3
- );
- drawEquip(
- EQUIP_BLOCK_LEFT,
- EQUIP_BLOCK_TOP,
- core.getEquip(0),
- "#D1CEFF",
- "无"
- );
- drawEquip(
- EQUIP_BLOCK_LEFT + 42 * 3,
- EQUIP_BLOCK_TOP,
- core.getEquip(1),
- "#D1CEFF",
- "无"
- );
- drawEquip(
- EQUIP_BLOCK_LEFT + 85 * 3,
- EQUIP_BLOCK_TOP,
- core.getEquip(2),
- "#D1CEFF",
- "无"
- );
- drawEquip(
- EQUIP_BLOCK_LEFT,
- EQUIP_BLOCK_TOP + 45 * 3,
- core.getEquip(3),
- "#D1CEFF",
- "无"
- );
- drawEquip(
- EQUIP_BLOCK_LEFT + 42 * 3,
- EQUIP_BLOCK_TOP + 45 * 3,
- core.getEquip(4),
- "#D1CEFF",
- "无"
- );
- drawEquip(
- EQUIP_BLOCK_LEFT + 85 * 3,
- EQUIP_BLOCK_TOP + 45 * 3,
- core.getEquip(5),
- "#D1CEFF",
- "无"
- );
- }
- }
- _update_keys() {
- const drawKeyList = (baseX, baseY) => {
- const todraw = [],
- keyList = ["yellowKey", "blueKey", "redKey", "greenKey"];
- let total = 0;
- keyList.forEach(function (key, i) {
- todraw[i] = core.itemCount(key);
- total += todraw[i];
- });
+ let dn = 3;
+ for (let i = 0; i <= dn; i++) {
+ let delta = i * 32 * 3;
- let dn = 3;
- for (let i = 0; i <= dn; i++) {
- let delta = i * 32 * 3;
+ if (core.domStyle.isVertical) {
+ this.drawKey(keyList[i], baseX, baseY + delta);
+ } else {
+ this.drawKey(keyList[i], baseX + delta, baseY);
+ }
- if (core.domStyle.isVertical) {
- this.drawKey(keyList[i], baseX, baseY + delta);
- } else {
- this.drawKey(keyList[i], baseX + delta, baseY);
- }
+ core.setFont("outerUI", "bold 48px Verdana");
+ core.setTextAlign("outerUI", "left");
+ if (core.domStyle.isVertical) {
+ core.fillText(
+ "outerUI",
+ todraw[i],
+ baseX + 20 * 3,
+ baseY + 14 * 3 + delta,
+ TEXT_COLOR
+ );
+ } else {
+ core.fillText(
+ "outerUI",
+ todraw[i],
+ baseX + delta,
+ baseY + 32 * 3,
+ TEXT_COLOR
+ );
+ }
+ }
+ };
+ if (core.domStyle.isVertical) {
+ core.clearMap(
+ "outerUI",
+ KEY_BLOCK_LEFT_VERTICAL,
+ KEY_BLOCK_TOP_VERTICAL,
+ 45 * 3,
+ 130 * 3
+ );
+ drawKeyList(
+ KEY_BLOCK_LEFT_VERTICAL + 3 * 3,
+ KEY_BLOCK_TOP_VERTICAL + 5 * 3
+ );
+ } else {
+ core.clearMap(
+ "outerUI",
+ KEY_BLOCK_LEFT,
+ KEY_BLOCK_TOP,
+ 130 * 3,
+ 45 * 3
+ );
+ drawKeyList(KEY_BLOCK_LEFT + 10 * 3, KEY_BLOCK_TOP);
+ }
+ }
+ drawKey(key, x, y) {
+ let sx = 0,
+ sy = 0;
- core.setFont("outerUI", "bold 48px Verdana");
- core.setTextAlign("outerUI", "left");
- if (core.domStyle.isVertical) {
- core.fillText(
- "outerUI",
- todraw[i],
- baseX + 20 * 3,
- baseY + 14 * 3 + delta,
- TEXT_COLOR
- );
- } else {
- core.fillText(
- "outerUI",
- todraw[i],
- baseX + delta,
- baseY + 32 * 3,
- TEXT_COLOR
- );
- }
- }
- };
- if (core.domStyle.isVertical) {
- core.clearMap(
- "outerUI",
- KEY_BLOCK_LEFT_VERTICAL,
- KEY_BLOCK_TOP_VERTICAL,
- 45 * 3,
- 130 * 3
- );
- drawKeyList(
- KEY_BLOCK_LEFT_VERTICAL + 3 * 3,
- KEY_BLOCK_TOP_VERTICAL + 5 * 3
- );
- } else {
- core.clearMap(
- "outerUI",
- KEY_BLOCK_LEFT,
- KEY_BLOCK_TOP,
- 130 * 3,
- 45 * 3
- );
- drawKeyList(KEY_BLOCK_LEFT + 10 * 3, KEY_BLOCK_TOP);
- }
- }
- drawKey(key, x, y) {
- let sx = 0,
- sy = 0;
+ if (key == "yellowKey") sx += 13;
+ else if (key == "blueKey") sx += 26;
+ else if (key == "greenKey") sx += 39;
- if (key == "yellowKey") sx += 13;
- else if (key == "blueKey") sx += 26;
- else if (key == "greenKey") sx += 39;
+ core.drawImage(
+ "outerUI",
+ "maba.webp",
+ sx,
+ sy,
+ 13,
+ 26,
+ x,
+ y,
+ 13 * 3,
+ 26 * 3
+ );
+ }
+ _update_infoWindow() {
+ const itemId = this.selectedItem;
+ let text = "";
+ if (this.selectedItem) {
+ text = core.replaceText(core.material.items[itemId]?.text);
+ if (text[0] == "," || text[0] == ",") text = text.substring(1);
+ }
+ if (core.domStyle.isVertical) {
+ core.clearMap(
+ "outerUI",
+ INFO_BLOCK_LEFT_VERTICAL,
+ INFO_BLOCK_TOP_VERTICAL,
+ 300 * 3,
+ 120 * 3
+ );
- core.drawImage(
- "outerUI",
- "maba.webp",
- sx,
- sy,
- 13,
- 26,
- x,
- y,
- 13 * 3,
- 26 * 3
- );
- }
- _update_infoWindow() {
- const itemId = this.selectedItem;
- let text = "";
- if (this.selectedItem) {
- text = core.replaceText(core.material.items[itemId]?.text);
- if (text[0] == "," || text[0] == ",") text = text.substring(1);
- }
- if (core.domStyle.isVertical) {
- core.clearMap(
- "outerUI",
- INFO_BLOCK_LEFT_VERTICAL,
- INFO_BLOCK_TOP_VERTICAL,
- 300 * 3,
- 120 * 3
- );
+ if (this.selectedItem) {
+ const icon = core.material.icons.items[itemId];
+ core.setTextAlign("outerUI", "left");
+ core.fillText(
+ "outerUI",
+ core.material.items[itemId].name,
+ INFO_BLOCK_LEFT_VERTICAL + 50 * 3,
+ INFO_BLOCK_TOP_VERTICAL + 27 * 3,
+ "#D1CEFF"
+ );
+ core.drawImage(
+ "outerUI",
+ core.material.images.items,
+ 0,
+ 32 * icon,
+ 32,
+ 32,
+ INFO_BLOCK_LEFT_VERTICAL + 10 * 3,
+ INFO_BLOCK_TOP_VERTICAL + 8 * 3,
+ 32 * 3,
+ 32 * 3
+ );
+ core.ui.drawTextContent("outerUI", text, {
+ left: INFO_BLOCK_LEFT_VERTICAL + 10 * 3,
+ top: INFO_BLOCK_TOP_VERTICAL + 40 * 3,
+ maxWidth: 275 * 3,
+ color: "#D1CEFF",
+ fontSize: 36,
+ });
+ }
+ } else {
+ core.clearMap(
+ "outerUI",
+ INFO_BLOCK_LEFT,
+ INFO_BLOCK_TOP,
+ 115 * 3,
+ 230 * 3
+ );
- if (this.selectedItem) {
- const icon = core.material.icons.items[itemId];
- core.setTextAlign("outerUI", "left");
- core.fillText(
- "outerUI",
- core.material.items[itemId].name,
- INFO_BLOCK_LEFT_VERTICAL + 50 * 3,
- INFO_BLOCK_TOP_VERTICAL + 27 * 3,
- "#D1CEFF"
- );
- core.drawImage(
- "outerUI",
- core.material.images.items,
- 0,
- 32 * icon,
- 32,
- 32,
- INFO_BLOCK_LEFT_VERTICAL + 10 * 3,
- INFO_BLOCK_TOP_VERTICAL + 8 * 3,
- 32 * 3,
- 32 * 3
- );
- core.ui.drawTextContent("outerUI", text, {
- left: INFO_BLOCK_LEFT_VERTICAL + 10 * 3,
- top: INFO_BLOCK_TOP_VERTICAL + 40 * 3,
- maxWidth: 275 * 3,
- color: "#D1CEFF",
- fontSize: 36,
- });
- }
- } else {
- core.clearMap(
- "outerUI",
- INFO_BLOCK_LEFT,
- INFO_BLOCK_TOP,
- 115 * 3,
- 230 * 3
- );
+ if (this.selectedItem) {
+ const icon = core.material.icons.items[itemId];
+ core.setTextAlign("outerUI", "center");
+ core.fillText(
+ "outerUI",
+ core.material.items[itemId].name,
+ INFO_BLOCK_LEFT + 60 * 3,
+ INFO_BLOCK_TOP + 25 * 3,
+ "#D1CEFF"
+ );
+ core.drawImage(
+ "outerUI",
+ core.material.images.items,
+ 0,
+ 32 * icon,
+ 32,
+ 32,
+ INFO_BLOCK_LEFT + 45 * 3,
+ INFO_BLOCK_TOP + 30 * 3,
+ 32 * 3,
+ 32 * 3
+ );
+ core.ui.drawTextContent("outerUI", text, {
+ left: INFO_BLOCK_LEFT + 10 * 3,
+ top: INFO_BLOCK_TOP + 60 * 3,
+ maxWidth: 105 * 3,
+ color: "#D1CEFF",
+ fontSize: 36,
+ });
+ }
+ }
+ }
+ showItemInfo(itemId) {
+ //展示道具说明
+ this.selectedItem = itemId;
+ this._update_infoWindow();
+ }
+ clearItemInfo() {
+ //清除道具说明
+ this.selectedItem = null;
+ this._update_infoWindow();
+ }
+ _update_toolBox() {
+ const tools = core.isReplaying()
+ ? [
+ [core.status.replay.pausing ? "play" : "pause", "stop", "rewind"],
+ ["speedDown", "speedUp", "save"],
+ ]
+ : [
+ ["keyboard", "shop", "pack", "T332"],
+ ["settings", "save", "load", "T331"],
+ ];
+ if (core.domStyle.isVertical) {
+ core.clearMap(
+ "outerUI",
+ TOOL_BOX_LEFT_VERTICAL,
+ TOOL_BOX_TOP_VERTICAL,
+ 115,
+ 130
+ );
- if (this.selectedItem) {
- const icon = core.material.icons.items[itemId];
- core.setTextAlign("outerUI", "center");
- core.fillText(
- "outerUI",
- core.material.items[itemId].name,
- INFO_BLOCK_LEFT + 60 * 3,
- INFO_BLOCK_TOP + 25 * 3,
- "#D1CEFF"
- );
- core.drawImage(
- "outerUI",
- core.material.images.items,
- 0,
- 32 * icon,
- 32,
- 32,
- INFO_BLOCK_LEFT + 45 * 3,
- INFO_BLOCK_TOP + 30 * 3,
- 32 * 3,
- 32 * 3
- );
- core.ui.drawTextContent("outerUI", text, {
- left: INFO_BLOCK_LEFT + 10 * 3,
- top: INFO_BLOCK_TOP + 60 * 3,
- maxWidth: 105 * 3,
- color: "#D1CEFF",
- fontSize: 36,
- });
- }
- }
- }
- showItemInfo(itemId) {
- //展示道具说明
- this.selectedItem = itemId;
- this._update_infoWindow();
- }
- clearItemInfo() {
- //清除道具说明
- this.selectedItem = null;
- this._update_infoWindow();
- }
- _update_toolBox() {
- const tools = core.isReplaying() ? [
- [core.status.replay.pausing ? "play" : "pause", "stop", "rewind"],
- ["speedDown", "speedUp", "save"],
- ] : [
- ["keyboard", "shop", "pack", "T332"],
- ["settings", "save", "load", "T331"],
- ];
- if (core.domStyle.isVertical) {
- core.clearMap(
- "outerUI",
- TOOL_BOX_LEFT_VERTICAL,
- TOOL_BOX_TOP_VERTICAL,
- 115,
- 130
- );
+ for (let i = 0; i < tools.length; i++) {
+ for (let j = 0; j < tools[i].length; j++) {
+ core.drawIcon(
+ "outerUI",
+ tools[i][j],
+ TOOL_BOX_LEFT_VERTICAL + i * 31 * 3,
+ TOOL_BOX_TOP_VERTICAL + j * 31 * 3,
+ 30 * 3,
+ 30 * 3
+ );
+ }
+ }
+ } else {
+ core.clearMap(
+ "outerUI",
+ TOOL_BOX_LEFT,
+ TOOL_BOX_TOP,
+ 130 * 3,
+ 80 * 3
+ );
- for (let i = 0; i < tools.length; i++) {
- for (let j = 0; j < tools[i].length; j++) {
- core.drawIcon(
- "outerUI",
- tools[i][j],
- TOOL_BOX_LEFT_VERTICAL + i * 31 * 3,
- TOOL_BOX_TOP_VERTICAL + j * 31 * 3,
- 30 * 3,
- 30 * 3
- );
- }
- }
- } else {
- core.clearMap(
- "outerUI",
- TOOL_BOX_LEFT,
- TOOL_BOX_TOP,
- 130 * 3,
- 80 * 3
- );
+ for (let i = 0; i < tools.length; i++) {
+ for (let j = 0; j < tools[i].length; j++) {
+ core.drawIcon(
+ "outerUI",
+ tools[i][j],
+ TOOL_BOX_LEFT + j * 31 * 3,
+ TOOL_BOX_TOP + i * 31 * 3,
+ 30 * 3,
+ 30 * 3
+ );
+ }
+ }
+ }
+ }
+ onclick(x, y) {
+ const makeBox = ([x, y], [w, h]) => {
+ return [
+ [x, y],
+ [x + w, y + h],
+ ];
+ };
+ const gridify = ([x, y], [gw, gh]) => {
+ return [Math.floor(x / gw), Math.floor(y / gh)];
+ };
+ const useItem = (itemId) => {
+ if (!core.hasItem(itemId)) return;
- for (let i = 0; i < tools.length; i++) {
- for (let j = 0; j < tools[i].length; j++) {
- core.drawIcon(
- "outerUI",
- tools[i][j],
- TOOL_BOX_LEFT + j * 31 * 3,
- TOOL_BOX_TOP + i * 31 * 3,
- 30 * 3,
- 30 * 3
- );
- }
- }
- }
- }
- onclick(x, y) {
- const makeBox = ([x, y], [w, h]) => {
- return [
- [x, y],
- [x + w, y + h],
- ];
- };
- const gridify = ([x, y], [gw, gh]) => {
- return [Math.floor(x / gw), Math.floor(y / gh)];
- };
- const useItem = (itemId) => {
- if (!core.hasItem(itemId)) return;
-
- if (itemId != this.selectedItem) {
- this.showItemInfo(itemId);
- } else {
- switch (itemId) {
- case "centerFly":
- core.ui._drawCenterFly();
- break;
- case "book":
- core.openBook(true);
- break;
- case "wand":
- core.insertAction({
- type: "useItem",
- id: itemId,
- });
- break;
- case "fly":
- core.useItem(itemId);
- break;
- default:
- core.useItem(itemId);
- }
- }
- };
- const inRect = ([x, y], [
- [sx, sy],
- [dx, dy]
- ]) => {
- return sx <= x && x <= dx && sy <= y && y <= dy;
- };
- const relativeTo = ([x, y], [ax, ay]) => {
- return [x - ax, y - ay];
- };
- const pos = [x, y];
- if (core.domStyle.isVertical) {
- const itemBox = makeBox(
- [ITEM_BOX_LEFT_VERTICAL, ITEM_BOX_TOP_VERTICAL],
- [30 * 6 * 3, 31 * 4 * 3]
- );
- if (inRect(pos, itemBox)) {
- const [gx, gy] = gridify(relativeTo(pos, itemBox[0]), [
- 30 * 3,
- 31 * 3,
- ]);
- const itemId = this.itemMx[gx][gy];
- if (
- (core.status.event.id == "viewMaps" ||
- core.status.event.id == "fly") &&
- itemId === "book"
- )
- core.openBook(true);
- if (
- core.isReplaying() ||
- core.status.lockControl ||
- core.isMoving()
- )
- return;
- useItem(itemId);
- return;
- }
- const toolBox = makeBox(
- [TOOL_BOX_LEFT_VERTICAL, TOOL_BOX_TOP_VERTICAL],
- [31 * 2 * 3, 31 * 4 * 3]
- );
- if (inRect(pos, toolBox)) {
- const [col, row] = gridify(relativeTo(pos, toolBox[0]), [
- 31 * 3,
- 31 * 3,
- ]);
- if (
- core.status.lockControl ||
- core.isMoving()
- )
- return;
- if (core.isReplaying()) {
- this.replayAction[col][row].call(core);
- } else if (core.isPlaying()) {
- if (col === 0 && row === 3) {
- core.doSL("autoSave", "load");
- } else if (col === 1 && row === 3) {
- core.doSL("autoSave", "reload");
- } else {
- this.toolbarAction[col][row].call(core, true);
- }
- }
- return;
- }
- const mapBox = makeBox(
- [MAP_BLOCK_LEFT_VERTICAL, MAP_BLOCK_TOP_VERTICAL],
- [350, 350]
- );
- if (inRect(pos, mapBox)) {
- if (
- core.isReplaying() ||
- core.status.lockControl ||
- core.isMoving()
- )
- return;
- core.useItem('fly');
- return;
- }
- /*const equipBox = makeBox([EQUIP_BLOCK_LEFT_VERTICAL, EQUIP_BLOCK_TOP_VERTICAL], [90 * 3, 130 * 3])
+ if (itemId != this.selectedItem) {
+ this.showItemInfo(itemId);
+ } else {
+ switch (itemId) {
+ case "centerFly":
+ core.ui._drawCenterFly();
+ break;
+ case "book":
+ core.openBook(true);
+ break;
+ case "wand":
+ core.insertAction({
+ type: "useItem",
+ id: itemId,
+ });
+ break;
+ case "fly":
+ core.useItem(itemId);
+ break;
+ default:
+ core.useItem(itemId);
+ }
+ }
+ };
+ const inRect = ([x, y], [[sx, sy], [dx, dy]]) => {
+ return sx <= x && x <= dx && sy <= y && y <= dy;
+ };
+ const relativeTo = ([x, y], [ax, ay]) => {
+ return [x - ax, y - ay];
+ };
+ const pos = [x, y];
+ if (core.domStyle.isVertical) {
+ const itemBox = makeBox(
+ [ITEM_BOX_LEFT_VERTICAL, ITEM_BOX_TOP_VERTICAL],
+ [30 * 6 * 3, 31 * 4 * 3]
+ );
+ if (inRect(pos, itemBox)) {
+ const [gx, gy] = gridify(relativeTo(pos, itemBox[0]), [
+ 30 * 3,
+ 31 * 3,
+ ]);
+ const itemId = this.itemMx[gx][gy];
+ if (
+ (core.status.event.id == "viewMaps" ||
+ core.status.event.id == "fly") &&
+ itemId === "book"
+ )
+ core.openBook(true);
+ if (
+ core.isReplaying() ||
+ core.status.lockControl ||
+ core.isMoving()
+ )
+ return;
+ useItem(itemId);
+ return;
+ }
+ const toolBox = makeBox(
+ [TOOL_BOX_LEFT_VERTICAL, TOOL_BOX_TOP_VERTICAL],
+ [31 * 2 * 3, 31 * 4 * 3]
+ );
+ if (inRect(pos, toolBox)) {
+ const [col, row] = gridify(relativeTo(pos, toolBox[0]), [
+ 31 * 3,
+ 31 * 3,
+ ]);
+ if (core.status.lockControl || core.isMoving()) return;
+ if (core.isReplaying()) {
+ this.replayAction[col][row].call(core);
+ } else if (core.isPlaying()) {
+ if (col === 0 && row === 3) {
+ core.doSL("autoSave", "load");
+ } else if (col === 1 && row === 3) {
+ core.doSL("autoSave", "reload");
+ } else {
+ this.toolbarAction[col][row].call(core, true);
+ }
+ }
+ return;
+ }
+ const mapBox = makeBox(
+ [MAP_BLOCK_LEFT_VERTICAL, MAP_BLOCK_TOP_VERTICAL],
+ [350, 350]
+ );
+ if (inRect(pos, mapBox)) {
+ if (
+ core.isReplaying() ||
+ core.status.lockControl ||
+ core.isMoving()
+ )
+ return;
+ core.useItem("fly");
+ return;
+ }
+ /*const equipBox = makeBox([EQUIP_BLOCK_LEFT_VERTICAL, EQUIP_BLOCK_TOP_VERTICAL], [90 * 3, 130 * 3])
if (inRect(pos, equipBox)) {
if (core.isReplaying() || core.status.lockControl || core.isMoving()) return;
core.openEquipbox(true)
return;
}*/
- } else {
- const mapBox = makeBox([MAP_BLOCK_LEFT, MAP_BLOCK_TOP], [350, 350]);
- if (inRect(pos, mapBox)) {
- if (
- core.isReplaying() ||
- core.status.lockControl ||
- core.isMoving()
- )
- return;
- core.useItem('fly');
- return;
- }
- /*
+ } else {
+ const mapBox = makeBox([MAP_BLOCK_LEFT, MAP_BLOCK_TOP], [350, 350]);
+ if (inRect(pos, mapBox)) {
+ if (
+ core.isReplaying() ||
+ core.status.lockControl ||
+ core.isMoving()
+ )
+ return;
+ core.useItem("fly");
+ return;
+ }
+ /*
const equipBox = makeBox([EQUIP_BLOCK_LEFT, EQUIP_BLOCK_TOP], [130, 95])
if (inRect(pos, equipBox)) {
if (core.isReplaying() || core.status.lockControl || core.isMoving()) return;
core.openEquipbox(true)
return;
}*/
- const itemBox = makeBox(
- [ITEM_BOX_LEFT, ITEM_BOX_TOP],
- [31 * 4 * 3, 30 * 6 * 3]
- );
- if (inRect(pos, itemBox)) {
- const [gx, gy] = gridify(relativeTo(pos, itemBox[0]), [
- 31 * 3,
- 30 * 3,
- ]);
- const itemId = this.itemMx[gy][gx];
- if (
- (core.status.event.id == "viewMaps" ||
- core.status.event.id == "fly") &&
- itemId === "book"
- )
- core.openBook(true);
- if (
- core.isReplaying() ||
- core.status.lockControl ||
- core.isMoving()
- )
- return;
- useItem(itemId);
- return;
- }
- const toolBox = makeBox(
- [TOOL_BOX_LEFT, TOOL_BOX_TOP],
- [31 * 4 * 3, 31 * 2 * 3]
- );
- if (inRect(pos, toolBox)) {
- const [row, col] = gridify(relativeTo(pos, toolBox[0]), [
- 31 * 3,
- 31 * 3,
- ]);
- if (
- core.status.lockControl ||
- core.isMoving()
- ) return
- if (core.isReplaying()) {
- this.replayAction[col][row].call(core);
- } else if (core.isPlaying()) {
- if (col === 0 && row === 3) {
- core.doSL("autoSave", "load");
- } else if (col === 1 && row === 3) {
- core.doSL("autoSave", "reload");
- } else {
- this.toolbarAction[col][row].call(core, true);
- }
- }
- return;
- }
- }
- }
- }
+ const itemBox = makeBox(
+ [ITEM_BOX_LEFT, ITEM_BOX_TOP],
+ [31 * 4 * 3, 30 * 6 * 3]
+ );
+ if (inRect(pos, itemBox)) {
+ const [gx, gy] = gridify(relativeTo(pos, itemBox[0]), [
+ 31 * 3,
+ 30 * 3,
+ ]);
+ const itemId = this.itemMx[gy][gx];
+ if (
+ (core.status.event.id == "viewMaps" ||
+ core.status.event.id == "fly") &&
+ itemId === "book"
+ )
+ core.openBook(true);
+ if (
+ core.isReplaying() ||
+ core.status.lockControl ||
+ core.isMoving()
+ )
+ return;
+ useItem(itemId);
+ return;
+ }
+ const toolBox = makeBox(
+ [TOOL_BOX_LEFT, TOOL_BOX_TOP],
+ [31 * 4 * 3, 31 * 2 * 3]
+ );
+ if (inRect(pos, toolBox)) {
+ const [row, col] = gridify(relativeTo(pos, toolBox[0]), [
+ 31 * 3,
+ 31 * 3,
+ ]);
+ if (core.status.lockControl || core.isMoving()) return;
+ if (core.isReplaying()) {
+ this.replayAction[col][row].call(core);
+ } else if (core.isPlaying()) {
+ if (col === 0 && row === 3) {
+ core.doSL("autoSave", "load");
+ } else if (col === 1 && row === 3) {
+ core.doSL("autoSave", "reload");
+ } else {
+ this.toolbarAction[col][row].call(core, true);
+ }
+ }
+ return;
+ }
+ }
+ }
+ }
- core.ui.statusBar = new StatusBar();
+ core.ui.statusBar = new StatusBar();
- core.control.clearStatusBar = function () {
- core.clearMap("outerUI");
- };
- // init() called in `afterLoadResources`.
-},
+ core.control.clearStatusBar = function () {
+ core.clearMap("outerUI");
+ };
+ // init() called in `afterLoadResources`.
+ },
"override": function () {
core.statusBar.icons = {
floor: 0,
@@ -8708,8 +8712,8 @@ var plugins_bb40132b_638b_4a9f_b028_d3fe47acc8d1 =
};
},
"音频系统": function () {
- // 在此增加新插件
- /*首先,在造塔群下载所需的库文件,然后放置在塔目录下的 libs/thirdparty 或其他目录下,之后在 index.html 的最后加上下面这几行:
+ // 在此增加新插件
+ /*首先,在造塔群下载所需的库文件,然后放置在塔目录下的 libs/thirdparty 或其他目录下,之后在 index.html 的最后加上下面这几行:
@@ -8717,1076 +8721,1078 @@ 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)));
- }
- // 开始流传输
- while (true) {
- const { value, done } = await reader.read();
- await Promise.all(targets.map((v) => v.pump(value, done, response)));
- if (done) break;
- }
-
- this.loading = false;
- targets.forEach((v) => v.end(true));
-
- //
- }
-
- 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;
- }
- on(event, fn, context) {}
- 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.player.status !== AudioStatus.Playing) {
- this.player.status = AudioStatus.Playing;
- }
- this.createSourceNode(this.buffer);
- this.output.start(0, when);
- this.playing = true;
-
- this.output.addEventListener("ended", () => {
- this.playing = false;
- if (this.player.status === AudioStatus.Playing) {
- this.player.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.player.status !== AudioStatus.Playing) {
- this.player.status = AudioStatus.Playing;
- }
- });
- audio.addEventListener("ended", () => {
- this.playing = false;
- if (this.player.status === AudioStatus.Playing) {
- this.player.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.player.status === AudioStatus.Playing) {
- this.player.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.player.status !== AudioStatus.Playing) {
- this.player.status = AudioStatus.Playing;
- }
- this.createSourceNode(this.buffer);
- this.output.start(0, when);
- this.output.addEventListener("ended", () => {
- this.playing = false;
- if (this.player.status === AudioStatus.Playing) {
- this.player.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();
- const func = () => {
+ // 将__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)));
+ }
+ // 开始流传输
+ while (true) {
+ const { value, done } = await reader.read();
+ await Promise.all(targets.map((v) => v.pump(value, done, response)));
+ if (done) break;
+ }
+
+ this.loading = false;
+ targets.forEach((v) => v.end(true));
+
+ //
+ }
+
+ 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;
+ }
+ on(event, fn, context) {}
+ 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.player.status !== AudioStatus.Playing) {
+ this.player.status = AudioStatus.Playing;
+ }
+ this.createSourceNode(this.buffer);
+ this.output.start(0, when);
+ this.playing = true;
+
+ this.output.addEventListener("ended", () => {
+ this.playing = false;
+ if (this.player.status === AudioStatus.Playing) {
+ this.player.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.player.status !== AudioStatus.Playing) {
+ this.player.status = AudioStatus.Playing;
+ }
+ });
+ audio.addEventListener("ended", () => {
+ this.playing = false;
+ if (this.player.status === AudioStatus.Playing) {
+ this.player.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.player.status === AudioStatus.Playing) {
+ this.player.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.player.status !== AudioStatus.Playing) {
+ this.player.status = AudioStatus.Playing;
+ }
+ this.createSourceNode(this.buffer);
+ this.output.start(0, when);
+ this.output.addEventListener("ended", () => {
+ this.playing = false;
+ if (this.player.status === AudioStatus.Playing) {
+ this.player.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();
+ /*const func = () => {
this.ac.resume();
document.body.removeEventListener("mousedown", func);
document.body.removeEventListener("touchstart", func);
@@ -9794,1057 +9800,1055 @@ var plugins_bb40132b_638b_4a9f_b028_d3fe47acc8d1 =
};
document.body.addEventListener("mousedown", func, { capture: true });
document.body.addEventListener("touchstart", func, { capture: true });
- document.body.addEventListener("keydown", func, { capture: true });
- }
- /**
- * 解码音频数据
- * @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) {
- this.output = source.output;
-
- /** 效果器路由图 */
- this.effectRoute = [];
-
- /** 结束时长,当音频暂停或停止时,会经过这么长时间之后才真正终止播放,期间可以做音频淡入淡出等效果 */
- this.endTime = 0;
- /** 暂停时播放了多长时间 */
- this.pauseCurrentTime = 0;
- /** 当前播放状态 */
- this.player = player;
- this.player.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.player.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 从音频的什么时候开始播放,单位秒
- */
- play(when = 0) {
- if (this.player.status === AudioStatus.Playing) return;
- this.link();
- 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.player.status = AudioStatus.Playing;
- this.pauseTime = 0;
- this.audioStartHook?.(this);
- this.startAllEffect();
- if (this.source.player.status !== AudioStatus.Playing) {
- this.source.player.status = AudioStatus.Playing;
- }
- }
-
- /**
- * 暂停音频播放
- */
- async pause() {
- if (this.player.status !== AudioStatus.Playing) return;
- this.player.status = AudioStatus.Pausing;
- this.stopIdentifier++;
- const identifier = this.stopIdentifier;
- if (this.audioEndHook) {
- this.audioEndHook(this.endTime, this);
- await sleep(this.endTime);
- }
- if (
- this.player.status !== AudioStatus.Pausing ||
- this.stopIdentifier !== identifier
- ) {
- return;
- }
- this.pauseCurrentTime = this.source.currentTime;
- const time = this.source.stop();
- this.pauseTime = time;
- if (this.shouldStop) {
- this.player.status = AudioStatus.Stoped;
- this.endAllEffect();
-
- this.shouldStop = false;
- } else {
- this.player.status = AudioStatus.Paused;
- this.endAllEffect();
- }
- this.endAllEffect();
- }
-
- /**
- * 继续音频播放
- */
- resume() {
- if (this.player.status === AudioStatus.Playing) return;
- if (
- this.player.status === AudioStatus.Pausing ||
- this.player.status === AudioStatus.Stoping
- ) {
- this.audioStartHook?.(this);
-
- return;
- }
- if (this.player.status === AudioStatus.Paused) {
- this.play(this.pauseTime);
- } else {
- this.play(0);
- }
- this.player.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;
- if (this.player.status !== AudioStatus.Playing) {
- this.player.status = AudioStatus.Playing;
- }
- }
-
- /**
- * 继续当前的 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) 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) => {
- 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();
- };
- //系统事件复写
- events.prototype._action_playBgm = function (data, x, y, prefix) {
- core.playBgm(data.name, data.startTime || 0);
- core.setFlag("__bgm__", data.keep ? data.name : null);
- core.doAction();
- };
-
- events.prototype._action_pauseBgm = function (data, x, y, prefix) {
- core.pauseBgm();
- core.doAction();
- };
-
- events.prototype._action_resumeBgm = function (data, x, y, prefix) {
- core.resumeBgm(data.resume);
- core.doAction();
- };
-
- events.prototype._action_loadBgm = function (data, x, y, prefix) {
- core.loadBgm(data.name);
- core.doAction();
- };
-
-
- events.prototype._action_playSound = function (data, x, y, prefix) {
- if (data.stop) core.stopSound();
- if (data.sync) {
- core.playSound(data.name, data.pitch, core.doAction);
- } else {
- core.playSound(data.name, data.pitch);
- core.doAction();
- }
- };
-
- events.prototype._action_stopSound = function (data, x, y, prefix) {
- core.stopSound();
- core.doAction();
- };
-
-},
+ document.body.addEventListener("keydown", func, { capture: true });*/
+ }
+ /**
+ * 解码音频数据
+ * @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) {
+ this.output = source.output;
+
+ /** 效果器路由图 */
+ this.effectRoute = [];
+
+ /** 结束时长,当音频暂停或停止时,会经过这么长时间之后才真正终止播放,期间可以做音频淡入淡出等效果 */
+ this.endTime = 0;
+ /** 暂停时播放了多长时间 */
+ this.pauseCurrentTime = 0;
+ /** 当前播放状态 */
+ this.player = player;
+ this.player.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.player.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 从音频的什么时候开始播放,单位秒
+ */
+ play(when = 0) {
+ if (this.player.status === AudioStatus.Playing) return;
+ this.link();
+ 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.player.status = AudioStatus.Playing;
+ this.pauseTime = 0;
+ this.audioStartHook?.(this);
+ this.startAllEffect();
+ if (this.source.player.status !== AudioStatus.Playing) {
+ this.source.player.status = AudioStatus.Playing;
+ }
+ }
+
+ /**
+ * 暂停音频播放
+ */
+ async pause() {
+ if (this.player.status !== AudioStatus.Playing) return;
+ this.player.status = AudioStatus.Pausing;
+ this.stopIdentifier++;
+ const identifier = this.stopIdentifier;
+ if (this.audioEndHook) {
+ this.audioEndHook(this.endTime, this);
+ await sleep(this.endTime);
+ }
+ if (
+ this.player.status !== AudioStatus.Pausing ||
+ this.stopIdentifier !== identifier
+ ) {
+ return;
+ }
+ this.pauseCurrentTime = this.source.currentTime;
+ const time = this.source.stop();
+ this.pauseTime = time;
+ if (this.shouldStop) {
+ this.player.status = AudioStatus.Stoped;
+ this.endAllEffect();
+
+ this.shouldStop = false;
+ } else {
+ this.player.status = AudioStatus.Paused;
+ this.endAllEffect();
+ }
+ this.endAllEffect();
+ }
+
+ /**
+ * 继续音频播放
+ */
+ resume() {
+ if (this.player.status === AudioStatus.Playing) return;
+ if (
+ this.player.status === AudioStatus.Pausing ||
+ this.player.status === AudioStatus.Stoping
+ ) {
+ this.audioStartHook?.(this);
+
+ return;
+ }
+ if (this.player.status === AudioStatus.Paused) {
+ this.play(this.pauseTime);
+ } else {
+ this.play(0);
+ }
+ this.player.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;
+ if (this.player.status !== AudioStatus.Playing) {
+ this.player.status = AudioStatus.Playing;
+ }
+ }
+
+ /**
+ * 继续当前的 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) 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) => {
+ 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();
+ };
+ //系统事件复写
+ events.prototype._action_playBgm = function (data, x, y, prefix) {
+ core.playBgm(data.name, data.startTime || 0);
+ core.setFlag("__bgm__", data.keep ? data.name : null);
+ core.doAction();
+ };
+
+ events.prototype._action_pauseBgm = function (data, x, y, prefix) {
+ core.pauseBgm();
+ core.doAction();
+ };
+
+ events.prototype._action_resumeBgm = function (data, x, y, prefix) {
+ core.resumeBgm(data.resume);
+ core.doAction();
+ };
+
+ events.prototype._action_loadBgm = function (data, x, y, prefix) {
+ core.loadBgm(data.name);
+ core.doAction();
+ };
+
+ events.prototype._action_playSound = function (data, x, y, prefix) {
+ if (data.stop) core.stopSound();
+ if (data.sync) {
+ core.playSound(data.name, data.pitch, core.doAction);
+ } else {
+ core.playSound(data.name, data.pitch);
+ core.doAction();
+ }
+ };
+
+ events.prototype._action_stopSound = function (data, x, y, prefix) {
+ core.stopSound();
+ core.doAction();
+ };
+ },
"怪物碎裂特效": function () {
// 在此增加新插件
// -------------------- 安装说明 -------------------- //
@@ -11091,21 +11095,21 @@ var plugins_bb40132b_638b_4a9f_b028_d3fe47acc8d1 =
};
},
"自定义常用事件": 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": {
@@ -11148,221 +11152,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 = `