diff --git a/_server/table/data.comment.js b/_server/table/data.comment.js index 6698265..358881b 100644 --- a/_server/table/data.comment.js +++ b/_server/table/data.comment.js @@ -269,7 +269,7 @@ var data_comment_c456ea59_6018_45ef_8bcc_211a24c627dc = { "_type": "textarea", "_data": "法强", }, - "spelldef": { + "mdef": { "_leaf": true, "_type": "textarea", "_data": "法抗百分比", @@ -280,7 +280,7 @@ var data_comment_c456ea59_6018_45ef_8bcc_211a24c627dc = { "_data": "100为法强转化为等值魔攻", "_docs": "魔攻比例" }, - "mdef": { + "mhp": { "_leaf": true, "_type": "textarea", "_data": "100为法强转化为等值护盾", diff --git a/project/data.js b/project/data.js index 0920725..0674e05 100644 --- a/project/data.js +++ b/project/data.js @@ -437,12 +437,14 @@ var data_a1e2fb4a_e986_4524_b0da_9b7ba7c0874d = "face_440110.webp", "green.webp", "hero.webp", + "jianji.webp", "l.webp", "lane1.webp", "light.webp", "lock.webp", "lr.webp", "maba.webp", + "miwu.webp", "null.webp", "other_0001.webp", "other_0002.webp", @@ -1116,7 +1118,7 @@ var data_a1e2fb4a_e986_4524_b0da_9b7ba7c0874d = "atk": 100, "def": 100, "mdef": 0, - "speed": 0, + "speed": 10, "money": 0, "exp": 0, "equipment": [], @@ -1494,14 +1496,12 @@ var data_a1e2fb4a_e986_4524_b0da_9b7ba7c0874d = ], "startText": [ { - "type": "setValue", - "name": "item:book", - "value": "1" + "type": "function", + "function": "function(){\ncore.getItem('book',1);core.getItem('fly',1)\n}" }, { - "type": "setValue", - "name": "item:fly", - "value": "1" + "type": "insert", + "name": "战斗动画特效注册" }, { "type": "insert", diff --git a/project/enemys.js b/project/enemys.js index f4da758..8c80082 100644 --- a/project/enemys.js +++ b/project/enemys.js @@ -4,7 +4,7 @@ var enemys_fcae963b_31c9_42b4_b48c_bb48d09f3f80 = "redSlime": {"name":"红头怪","hp":0,"atk":0,"def":0,"money":0,"exp":0,"point":0,"special":[],"value":10,"magic":false}, "blackSlime": {"name":"青头怪","hp":0,"atk":0,"def":0,"money":0,"exp":0,"point":0,"special":0}, "slimelord": {"name":"怪王","hp":100,"atk":120,"def":0,"money":10,"exp":0,"point":0,"special":[1,9]}, - "bat": {"name":"小蝙蝠","hp":100,"atk":120,"def":0,"money":2,"exp":0,"point":0,"special":[1],"mdef":0,"speed":10}, + "bat": {"name":"小蝙蝠","hp":100,"atk":120,"def":0,"money":2,"exp":0,"point":0,"special":[1],"mdef":0,"speed":15}, "bigBat": {"name":"大蝙蝠","hp":0,"atk":0,"def":0,"money":0,"exp":0,"point":0,"special":0}, "redBat": {"name":"红蝙蝠","hp":0,"atk":0,"def":0,"money":0,"exp":0,"point":0,"special":[]}, "vampire": {"name":"冥灵魔王","hp":888,"atk":888,"def":888,"money":888,"exp":888,"point":0,"special":[6],"n":8}, diff --git a/project/events.js b/project/events.js index 5c6c1a0..20f75d5 100644 --- a/project/events.js +++ b/project/events.js @@ -10883,6 +10883,136 @@ var events_c12a15a8_c380_4b28_8144_256cba95f760 = } ] } + ], + "战斗动画特效注册": [ + { + "type": "setanimate", + "name": "sword", + "px": 48, + "py": 48, + "width": 192, + "height": 192, + "allFarme": 15, + "imageList": [ + { + "image": "jianji.webp", + "beforefarme": 0, + "globalAlpha": 100, + "cx": 192, + "cy": 2112, + "cw": 192, + "ch": 192, + "x": 0, + "y": 0, + "w": 96, + "h": 96, + "afterfarme": 3, + "acx": 192, + "acy": 2112, + "acw": 192, + "ach": 192, + "ax": 0, + "ay": 0, + "aw": 96, + "ah": 96 + }, + { + "image": "jianji.webp", + "beforefarme": 4, + "globalAlpha": 100, + "cx": 384, + "cy": 2112, + "cw": 192, + "ch": 192, + "x": 0, + "y": 0, + "w": 96, + "h": 96, + "afterfarme": 6, + "acx": 384, + "acy": 2112, + "acw": 192, + "ach": 192, + "ax": 0, + "ay": 0, + "aw": 96, + "ah": 96 + }, + { + "image": "jianji.webp", + "beforefarme": 7, + "globalAlpha": 100, + "cx": 576, + "cy": 2112, + "cw": 192, + "ch": 192, + "x": 0, + "y": 0, + "w": 96, + "h": 96, + "afterfarme": 9, + "acx": 576, + "acy": 2112, + "acw": 192, + "ach": 192, + "ax": 0, + "ay": 0, + "aw": 96, + "ah": 96 + }, + { + "image": "jianji.webp", + "beforefarme": 10, + "globalAlpha": 100, + "cx": 768, + "cy": 2112, + "cw": 192, + "ch": 192, + "x": 0, + "y": 0, + "w": 96, + "h": 96, + "afterfarme": 12, + "acx": 768, + "acy": 2112, + "acw": 192, + "ach": 192, + "ax": 0, + "ay": 0, + "aw": 96, + "ah": 96 + }, + { + "image": "jianji.webp", + "beforefarme": 13, + "globalAlpha": 100, + "cx": 0, + "cy": 2304, + "cw": 192, + "ch": 192, + "x": 0, + "y": 0, + "w": 96, + "h": 96, + "afterfarme": 15, + "acx": 0, + "acy": 2304, + "acw": 192, + "ach": 192, + "ax": 0, + "ay": 0, + "aw": 96, + "ah": 96 + } + ], + "soundList": [ + { + "sound": "", + "startfarme": 0, + "stopbefore": false + } + ] + } ] }, "CommonEventTemplate": { diff --git a/project/functions.js b/project/functions.js index ea0a448..d202ca9 100644 --- a/project/functions.js +++ b/project/functions.js @@ -892,171 +892,170 @@ var functions_d6ad677b_427a_4623_b50f_a445a3b0ef8a = }; }, "getDamageInfo": function (enemy, hero, x, y, floorId) { - // 获得战斗伤害信息(实际伤害计算函数) - // - // 参数说明: - // enemy:该怪物信息 - // hero:勇士的当前数据;如果对应项不存在则会从core.status.hero中取。 - // x,y:该怪物的坐标(查看手册和强制战斗时为undefined) - // floorId:该怪物所在的楼层 - // 后面三个参数主要是可以在光环等效果上可以适用 - floorId = floorId || core.status.floorId; + // 获得战斗伤害信息(实际伤害计算函数) + // + // 参数说明: + // enemy:该怪物信息 + // hero:勇士的当前数据;如果对应项不存在则会从core.status.hero中取。 + // x,y:该怪物的坐标(查看手册和强制战斗时为undefined) + // floorId:该怪物所在的楼层 + // 后面三个参数主要是可以在光环等效果上可以适用 + floorId = floorId || core.status.floorId; - var hero_hp = core.getRealStatusOrDefault(hero, "hp"), - hero_atk = core.getRealStatusOrDefault(hero, "atk"), - hero_def = core.getRealStatusOrDefault(hero, "def"), - hero_mdef = core.getRealStatusOrDefault(hero, "mdef"), - hero_speed = core.getRealStatusOrDefault(hero, "speed"), - hero_magic = core.getRealStatusOrDefault(hero, "magic"), - origin_hero_hp = core.getStatusOrDefault(hero, "hp"), - origin_hero_atk = core.getStatusOrDefault(hero, "atk"), - origin_hero_def = core.getStatusOrDefault(hero, "def"); + var hero_hp = core.getRealStatusOrDefault(hero, "hp"), + hero_atk = core.getRealStatusOrDefault(hero, "atk"), + hero_def = core.getRealStatusOrDefault(hero, "def"), + hero_mdef = core.getRealStatusOrDefault(hero, "mdef"), + hero_speed = core.getRealStatusOrDefault(hero, "speed"), + hero_magic = core.getRealStatusOrDefault(hero, "magic"), + origin_hero_hp = core.getStatusOrDefault(hero, "hp"), + origin_hero_atk = core.getStatusOrDefault(hero, "atk"), + origin_hero_def = core.getStatusOrDefault(hero, "def"); - // 怪物的各项数据 - // 对坚固模仿等处理扔到了脚本编辑-getEnemyInfo之中 - var enemyInfo = core.enemys.getEnemyInfo(enemy, hero, x, y, floorId); - var mon_hp = enemyInfo.hp, - mon_atk = enemyInfo.atk, - mon_def = enemyInfo.def, - mon_mdef = enemyInfo.mdef, - mon_speed = enemyInfo.speed, - mon_special = enemyInfo.special, - mon_absorb_damage = enemyInfo.absorb, - mon_barrier = enemyInfo.barrier; + // 怪物的各项数据 + // 对坚固模仿等处理扔到了脚本编辑-getEnemyInfo之中 + var enemyInfo = core.enemys.getEnemyInfo(enemy, hero, x, y, floorId); + var mon_hp = enemyInfo.hp, + mon_atk = enemyInfo.atk, + mon_def = enemyInfo.def, + mon_mdef = enemyInfo.mdef, + mon_speed = enemyInfo.speed, + mon_special = enemyInfo.special, + mon_absorb_damage = enemyInfo.absorb, + mon_barrier = enemyInfo.barrier; - //---第一部分:静态属性修正--- - //此处写入静态影响勇士属性的勇士或怪物技能(静态影响怪物属性的技能于getEnemyInfo中写入) - // 技能的处理 - if (core.getFlag("skill", 0) == 1) { - // 开启了技能1:二倍斩 - hero_atk *= 2; // 计算时攻击力翻倍 - } - //勇士属性取整 - hero_atk = Math.max(0, Math.floor(hero_atk)); - hero_def = Math.max(0, Math.floor(hero_def)); - hero_mdef = Math.max(0, Math.floor(hero_mdef)); - hero_speed = Math.max(0, Math.floor(hero_speed)); + //---第一部分:静态属性修正--- + //此处写入静态影响勇士属性的勇士或怪物技能(静态影响怪物属性的技能于getEnemyInfo中写入) + // 技能的处理 + if (core.getFlag("skill", 0) == 1) { + // 开启了技能1:二倍斩 + hero_atk *= 2; // 计算时攻击力翻倍 + } + //勇士属性取整 + hero_atk = Math.max(0, Math.floor(hero_atk)); + hero_def = Math.max(0, Math.floor(hero_def)); + hero_mdef = Math.max(0, Math.floor(hero_mdef)); + hero_speed = Math.max(0, Math.floor(hero_speed)); - // 如果是无敌属性,且勇士未持有十字架 - if (core.hasSpecial(mon_special, 20) && !core.hasItem("cross")) - return null; // 不可战斗 + // 如果是无敌属性,且勇士未持有十字架 + if (core.hasSpecial(mon_special, 20) && !core.hasItem("cross")) + return null; // 不可战斗 - // 战前造成的额外伤害(可被护盾抵消) - let init_damage = 0; + // 战前造成的额外伤害(可被护盾抵消) + let init_damage = 0; - // 吸血 - if (core.hasSpecial(mon_special, 11)) { - let vampire_damage = hero_hp * enemy.vampire; + // 吸血 + if (core.hasSpecial(mon_special, 11)) { + let vampire_damage = hero_hp * enemy.vampire; - // 如果有神圣盾免疫吸血等可以在这里写 - // 也可以用hasItem和hasEquip来判定装备 - // if (core.hasFlag('shield5')) vampire_damage = 0; + // 如果有神圣盾免疫吸血等可以在这里写 + // 也可以用hasItem和hasEquip来判定装备 + // if (core.hasFlag('shield5')) vampire_damage = 0; - vampire_damage = Math.floor(vampire_damage) || 0; - // 加到自身 - if (enemy.add) - // 如果加到自身 - mon_hp += vampire_damage; + vampire_damage = Math.floor(vampire_damage) || 0; + // 加到自身 + if (enemy.add) + // 如果加到自身 + mon_hp += vampire_damage; - init_damage += vampire_damage; - } + init_damage += vampire_damage; + } - //——第二部分:变量定义和初始赋值—— - // 每回合怪物对勇士造成的战斗伤害 - let per_damage = Math.max(mon_atk - hero_def, 0); - if (enemyInfo.magic) per_damage = Math.max(mon_atk - hero_mdef, 0); + //——第二部分:变量定义和初始赋值—— + // 每回合怪物对勇士造成的战斗伤害 + let per_damage = Math.max(mon_atk - hero_def, 0); + if (enemyInfo.magic) per_damage = Math.max(mon_atk - hero_mdef, 0); - // - let hero_per_damage = Math.max(hero_atk - mon_def, 0); - if (hero_magic) hero_per_damage = Math.max(hero_atk - mon_mdef, 0); + // + let hero_per_damage = Math.max(hero_atk - mon_def, 0); + if (hero_magic) hero_per_damage = Math.max(hero_atk - mon_mdef, 0); - let damage = 0, - hero_turn = 0, - mon_turn = 0; - //---第三部分:递归开始--- - let length = hero_speed * mon_speed; + let damage = 0, + hero_turn = 0, + mon_turn = 0; + //---第三部分:递归开始--- + let length = hero_speed * mon_speed; - for ( - let now_mon_hp = mon_hp, - last_mon_hp = mon_hp, - mon_length = length, - hero_length = length, - mon_time = 0, - hero_time = 0; - now_mon_hp > 0; + for ( + let now_mon_hp = mon_hp, + last_mon_hp = mon_hp, + mon_length = length, + hero_length = length, + mon_time = 0, + hero_time = 0; now_mon_hp > 0; - ) { - //勇士和怪物的长度 - mon_time = mon_length / mon_speed; - hero_time = hero_length / hero_speed; - if ( - mon_time < hero_time || - ((mon_time = hero_time) && mon_speed > hero_speed) - ) { - //怪物攻击的回合 - //这里计算怪物攻击时发生的各种变化 + ) { + //勇士和怪物的长度 + mon_time = mon_length / mon_speed; + hero_time = hero_length / hero_speed; + if ( + mon_time < hero_time || + ((mon_time = hero_time) && mon_speed > hero_speed) + ) { + //怪物攻击的回合 + //这里计算怪物攻击时发生的各种变化 - //伤害与回合增加 - damage += per_damage; - if (core.hasSpecial(mon_special, 6)) { - mon_turn += enemy.n; - } else { - mon_turn += 1; - } - //重新为长度赋值 - mon_length = length; - hero_length = hero_length - mon_time * hero_speed; - } else { - //勇士攻击的回合 - // 这里计算勇士攻击时发生的各种变化 + //伤害与回合增加 + damage += per_damage; + if (core.hasSpecial(mon_special, 6)) { + mon_turn += enemy.n; + } else { + mon_turn += 1; + } + //重新为长度赋值 + mon_length = length; + hero_length = hero_length - mon_time * hero_speed; + } else { + //勇士攻击的回合 + // 这里计算勇士攻击时发生的各种变化 - // 伤害与回合数增加 - now_mon_hp -= hero_per_damage; - hero_turn += 1; //勇士回合+1,如果有勇士每回合多次攻击的情况,在这里写判断 - // 无法战斗计算 - if (hero_turn % 50 == 0) { - if (now_mon_hp >= last_mon_hp) return null; - last_mon_hp = now_mon_hp; - } - //重新为长度赋值 - hero_length = length; - mon_length = mon_length - hero_time * mon_speed; - } - } + // 伤害与回合数增加 + now_mon_hp -= hero_per_damage; + hero_turn += 1; //勇士回合+1,如果有勇士每回合多次攻击的情况,在这里写判断 + // 无法战斗计算 + if (hero_turn % 50 == 0) { + if (now_mon_hp >= last_mon_hp) return null; + last_mon_hp = now_mon_hp; + } + //重新为长度赋值 + hero_length = length; + mon_length = mon_length - hero_time * mon_speed; + } + } - //下面这些还没修改 - // 连击 - if (core.hasSpecial(mon_special, 6)) per_damage *= enemy.n || 2; + //下面这些还没修改 + // 连击 + if (core.hasSpecial(mon_special, 6)) per_damage *= enemy.n || 2; - // 每回合的反击伤害;反击是按照勇士的攻击次数来计算回合 - let counterDamage = 0; - if (core.hasSpecial(mon_special, 8)) - counterDamage += Math.floor( - (enemy.counterAttack || core.values.counterAttack) * hero_atk - ); + // 每回合的反击伤害;反击是按照勇士的攻击次数来计算回合 + let counterDamage = 0; + if (core.hasSpecial(mon_special, 8)) + counterDamage += Math.floor( + (enemy.counterAttack || core.values.counterAttack) * hero_atk + ); - // 先攻 - if (core.hasSpecial(mon_special, 1)) init_damage += per_damage; + // 先攻 + if (core.hasSpecial(mon_special, 1)) init_damage += per_damage; - // 破甲 - if (core.hasSpecial(mon_special, 7)) - init_damage += Math.floor( - (enemy.breakArmor || core.values.breakArmor) * hero_def - ); + // 破甲 + if (core.hasSpecial(mon_special, 7)) + init_damage += Math.floor( + (enemy.breakArmor || core.values.breakArmor) * hero_def + ); - // 净化 - if (core.hasSpecial(mon_special, 9)) - init_damage += Math.floor( - (enemy.purify || core.values.purify) * hero_mdef - ); - //上面这些还没修改 - //勇士护盾计算 - let barrier = hero_mdef; - if (enemyInfo.magic) barrier = hero_def; + // 净化 + if (core.hasSpecial(mon_special, 9)) + init_damage += Math.floor( + (enemy.purify || core.values.purify) * hero_mdef + ); + //上面这些还没修改 + //勇士护盾计算 + let barrier = hero_mdef; + if (enemyInfo.magic) barrier = hero_def; - // ------ 支援 ----- // - // 这个递归最好想明白为什么,flag:__extraTurn__是怎么用的 - /*var guards = core.getFlag("__guards__" + x + "_" + y, enemyInfo.guards); + // ------ 支援 ----- // + // 这个递归最好想明白为什么,flag:__extraTurn__是怎么用的 + /*var guards = core.getFlag("__guards__" + x + "_" + y, enemyInfo.guards); var guard_before_current_enemy = false; // ------ 支援怪是先打(true)还是后打(false)? turn += core.getFlag("__extraTurn__", 0); if (guards.length > 0) { @@ -1084,43 +1083,43 @@ var functions_d6ad677b_427a_4623_b50f_a445a3b0ef8a = } } core.removeFlag("__extraTurn__");*/ - // ------ 支援END ------ // + // ------ 支援END ------ // - // 最终伤害:初始伤害 + 怪物对勇士造成的伤害 + 反击伤害 - damage += init_damage + hero_turn * counterDamage; - // 再扣去护盾 - damage -= barrier; + // 最终伤害:初始伤害 + 怪物对勇士造成的伤害 + 反击伤害 + damage += init_damage + hero_turn * counterDamage; + // 再扣去护盾 + damage -= barrier; - // 检查是否允许负伤 - if (!core.flags.enableNegativeDamage) damage = Math.max(0, damage); + // 检查是否允许负伤 + if (!core.flags.enableNegativeDamage) damage = Math.max(0, damage); - // 最后处理仇恨和固伤(因为这两个不能被护盾减伤) - if (core.hasSpecial(mon_special, 17)) { - // 仇恨 - damage += core.getFlag("hatred", 0); - } - if (core.hasSpecial(mon_special, 22)) { - // 固伤 - damage += enemy.damage || 0; - } + // 最后处理仇恨和固伤(因为这两个不能被护盾减伤) + if (core.hasSpecial(mon_special, 17)) { + // 仇恨 + damage += core.getFlag("hatred", 0); + } + if (core.hasSpecial(mon_special, 22)) { + // 固伤 + damage += enemy.damage || 0; + } - return { - mon_hp: Math.floor(mon_hp), - mon_atk: Math.floor(mon_atk), - mon_def: Math.floor(mon_def), - mon_mdef: Math.floor(mon_mdef), - init_damage: Math.floor(init_damage), - per_damage: Math.floor(per_damage), - hero_per_damage: Math.floor(hero_per_damage), - turn: Math.floor(hero_turn), - mon_turn: Math.floor(mon_turn), - damage: Math.floor(damage), - }; - /*TODO:怪物手册的修改(需要修改这里return的内容以及一些战后判断) + return { + mon_hp: Math.floor(mon_hp), + mon_atk: Math.floor(mon_atk), + mon_def: Math.floor(mon_def), + mon_mdef: Math.floor(mon_mdef), + init_damage: Math.floor(init_damage), + per_damage: Math.floor(per_damage), + hero_per_damage: Math.floor(hero_per_damage), + turn: Math.floor(hero_turn), + mon_turn: Math.floor(mon_turn), + damage: Math.floor(damage), + }; + /*TODO:怪物手册的修改(需要修改这里return的内容以及一些战后判断) 1. 显示怪物是魔攻还是物攻(在怪物名字上做颜色变化,物攻是黄色,魔攻是蓝色) 2. 一防减伤是物防还是魔防(由怪物是物攻还是魔攻来转换) 3. 特殊战斗的怪物,在怪物手册里“伤害”写为“特殊战”*/ - } +} }, "actions": { "onKeyUp": function (keyCode, altKey) { diff --git a/project/images/jianji.webp b/project/images/jianji.webp new file mode 100644 index 0000000..40504a4 Binary files /dev/null and b/project/images/jianji.webp differ diff --git a/project/images/miwu.webp b/project/images/miwu.webp new file mode 100644 index 0000000..84e72cc Binary files /dev/null and b/project/images/miwu.webp differ diff --git a/project/plugins.js b/project/plugins.js index 170978f..64da3ac 100644 --- a/project/plugins.js +++ b/project/plugins.js @@ -1,262 +1,264 @@ var plugins_bb40132b_638b_4a9f_b028_d3fe47acc8d1 = { "init": function () { - this._afterLoadResources = function () { - // 本函数将在所有资源加载完毕后,游戏开启前被执行 - core.ui.statusBar.init(); - core.dom.playGame.style.fontFamily = "pala"; - core.dom.loadGame.style.fontFamily = "pala"; - core.dom.CGMode.style.fontFamily = "pala"; - core.dom.musicMode.style.fontFamily = "pala"; - core.dom.replayGame.style.fontFamily = "pala"; - core.registerEvent("changeMouse", function (data) { - if (!main.replayChecking && !core.isReplaying()) - core.changeMouse( - data.icon, - data.div, - data.translate[0], - data.translate[1], - data.scale[0], - data.scale[1], - data.angel, - data.px, - data.py - ); - core.doAction(); - }); - core.registerEvent("removeMouse", function (data) { - if (!main.replayChecking && !core.isReplaying()) - core.removeMouse(data.div); - core.doAction(); - }); - core.registerEvent("addPop", function (data) { - if (!main.replayChecking && !core.isReplaying()) { - data.value = core.replaceText(data.value); - core.addPop( - data.value, - data.px, - data.py, - data.color, - data.boldColor, - data.left, - data.jump, - data.time, - data.show, - data.font, - data.speed - ); - } - core.doAction(); - }); - core.registerEvent("drawWarning", function (data) { - if (!main.replayChecking && !core.isReplaying()) { - data.text = core.replaceText(data.text); - data.text2 = core.replaceText(data.text2); - core.drawWarning( - data.x, - data.y, - data?.text, - data?.text2, - data?.warning, - data.large, - data.size - ); + this._afterLoadResources = function () { + // 本函数将在所有资源加载完毕后,游戏开启前被执行 + core.ui.statusBar.init(); + core.dom.playGame.style.fontFamily = "pala"; + core.dom.loadGame.style.fontFamily = "pala"; + core.dom.CGMode.style.fontFamily = "pala"; + core.dom.musicMode.style.fontFamily = "pala"; + core.dom.replayGame.style.fontFamily = "pala"; + core.registerEvent("changeMouse", function (data) { + if (!main.replayChecking && !core.isReplaying()) + core.changeMouse( + data.icon, + data.div, + data.translate[0], + data.translate[1], + data.scale[0], + data.scale[1], + data.angel, + data.px, + data.py + ); + core.doAction(); + }); + core.registerEvent("removeMouse", function (data) { + if (!main.replayChecking && !core.isReplaying()) + core.removeMouse(data.div); + core.doAction(); + }); + core.registerEvent("addPop", function (data) { + if (!main.replayChecking && !core.isReplaying()) { + data.value = core.replaceText(data.value); + core.addPop( + data.value, + data.px, + data.py, + data.color, + data.boldColor, + data.left, + data.jump, + data.time, + data.show, + data.font, + data.speed + ); + } + core.doAction(); + }); + core.registerEvent("drawWarning", function (data) { + if (!main.replayChecking && !core.isReplaying()) { + data.text = core.replaceText(data.text); + data.text2 = core.replaceText(data.text2); + core.drawWarning( + data.x, + data.y, + data?.text, + data?.text2, + data?.warning, + data.large, + data.size + ); - setTimeout(() => core.doAction(), 3100); - } else { - core.doAction(); - } - }); + setTimeout(() => core.doAction(), 3100); + } else { + core.doAction(); + } + }); - core.registerEvent("over", function (data) { - let image = data.image ?? ""; - let time = data.time ?? 3000; - let sound = data.sound ?? ""; - let textColor = data.textColor ?? "#FFFFFF"; - let boldColor = data.boldColor ?? "#000000"; - let font = data.font ?? "bold 48px Verdana"; - let text = data.text ?? ""; - let hidetime = data.hidetime ?? 100; - if (!main.replayChecking && !core.isReplaying()) { - core.over( - image, - data.memory, - time, - hidetime, - sound, - textColor, - boldColor, - font, - text - ); - } else { - core.doAction(); - } - }); - core.registerEvent("changebg", function (data) { - if (!main.replayChecking && !core.isReplaying()) { - core.changebg( - data.img1, - data.memory1, - data.img2, - data.memory2, - data.time, - data.style - ); - } else { - core.doAction(); - } - }); - core.registerEvent("overlist", function (data) { - if (!main.replayChecking && !core.isReplaying()) { - core.overlist( - data.image, - data.memory, - data.hidetime || 30, - data.list || [{ - text: "", - sound: "", - time: 50, - textColor: "#FFFFFF", - boldColor: "#000000", - font: "bold 48px Verdana", - frame: 0, - }, ] - ); - } else { - core.doAction(); - } - }); - core.registerEvent("op", function (data) { - if (!main.replayChecking && !core.isReplaying()) { - core.openvideo(); - } else { - core.doAction(); - } - }); - core.registerEvent("animationDrawable", function (data) { - if (!main.replayChecking && !core.isReplaying()) { - core.animationDrawable( - data.allFarme, - data.color, - data.globalAlpha, - data.imageList, - data.soundList - ); - } else { - core.doAction(); - } - }); - core.registerEvent("setanimate", function (data) { - data.px = data.px ?? 0; - data.py = data.py ?? 0; - core.setanimate( - data.name, - data.px, - data.py, - data.width, - data.height, - data.allFarme, - data.imageList, - data.soundList - ); - core.doAction(); - }); - core.registerEvent("clearanimate", function (data) { - core.plugin.playing.clear(); + core.registerEvent("over", function (data) { + let image = data.image ?? ""; + let time = data.time ?? 3000; + let sound = data.sound ?? ""; + let textColor = data.textColor ?? "#FFFFFF"; + let boldColor = data.boldColor ?? "#000000"; + let font = data.font ?? "bold 48px Verdana"; + let text = data.text ?? ""; + let hidetime = data.hidetime ?? 100; + if (!main.replayChecking && !core.isReplaying()) { + core.over( + image, + data.memory, + time, + hidetime, + sound, + textColor, + boldColor, + font, + text + ); + } else { + core.doAction(); + } + }); + core.registerEvent("changebg", function (data) { + if (!main.replayChecking && !core.isReplaying()) { + core.changebg( + data.img1, + data.memory1, + data.img2, + data.memory2, + data.time, + data.style + ); + } else { + core.doAction(); + } + }); + core.registerEvent("overlist", function (data) { + if (!main.replayChecking && !core.isReplaying()) { + core.overlist( + data.image, + data.memory, + data.hidetime || 30, + data.list || [ + { + text: "", + sound: "", + time: 50, + textColor: "#FFFFFF", + boldColor: "#000000", + font: "bold 48px Verdana", + frame: 0, + }, + ] + ); + } else { + core.doAction(); + } + }); + core.registerEvent("op", function (data) { + if (!main.replayChecking && !core.isReplaying()) { + core.openvideo(); + } else { + core.doAction(); + } + }); + core.registerEvent("animationDrawable", function (data) { + if (!main.replayChecking && !core.isReplaying()) { + core.animationDrawable( + data.allFarme, + data.color, + data.globalAlpha, + data.imageList, + data.soundList + ); + } else { + core.doAction(); + } + }); + core.registerEvent("setanimate", function (data) { + data.px = data.px ?? 0; + data.py = data.py ?? 0; + core.setanimate( + data.name, + data.px, + data.py, + data.width, + data.height, + data.allFarme, + data.imageList, + data.soundList + ); + core.doAction(); + }); + core.registerEvent("clearanimate", function (data) { + core.plugin.playing.clear(); - core.doAction(); - }); - core.registerEvent("deleteanimate", function (data) { - core.deleteanimate(data.name); - core.doAction(); - }); - core.registerEvent("playanimate", function (data) { - if (!main.replayChecking && !core.isReplaying()) { - data.x = data.x ?? 0; - data.y = data.y ?? 0; - data.scalex = data.scalex ?? 1; - data.scaley = data.scaley ?? 1; - core.playanimate( - data.name, - data.x, - data.y, - data.hero, - data.scalex, - data.scaley - ); - core.doAction(); - } else { - core.doAction(); - } - }); - core.registerEvent("cgtextList", function (data) { - core.ui.cgText.textList = core.plugin[data.textList]; - core.doAction(); - }); - core.registerEvent("cgtext", function (data) { - if (!main.replayChecking && !core.isReplaying()) { - core.ui.cgText.image = data.bg; - core.ui.cgText.memory = data.memory; - core.ui.cgText.head = core.clone(data.head); - core.ui.cgText.index = data.index; - core.ui.cgText.name = core.ui.cgText.textList[data.index][0]; - core.ui.cgText.text = data.text ? - data.text : - core.ui.cgText.textList[data.index][1]; - core.ui.cgText.time = data.time; - core.ui.cgText.wait = data.wait; - core.ui.cgText.WindowSkin = data.WindowSkin; - core.ui.cgText.sound = data.sound || ""; - core.ui.cgText.bodyList = core.clone(data.bodyList); - main.dom.cgText.style.display = "block"; - core.ui.cgText.update(); - } else { - core.doAction(); - } - }); - core.registerEvent("introAndLoop", function (data) { - if (!main.replayChecking && !core.isReplaying()) { - core.plugin.introAndLoop(data.intro, data.time, data.loop); - core.doAction(); - } else { - core.doAction(); - } - }); - core.registerEvent("setq", function (data) { - core.setFlag("任务地点", data.id); + core.doAction(); + }); + core.registerEvent("deleteanimate", function (data) { + core.deleteanimate(data.name); + core.doAction(); + }); + core.registerEvent("playanimate", function (data) { + if (!main.replayChecking && !core.isReplaying()) { + data.x = data.x ?? 0; + data.y = data.y ?? 0; + data.scalex = data.scalex ?? 1; + data.scaley = data.scaley ?? 1; + core.playanimate( + data.name, + data.x, + data.y, + data.hero, + data.scalex, + data.scaley + ); + core.doAction(); + } else { + core.doAction(); + } + }); + core.registerEvent("cgtextList", function (data) { + core.ui.cgText.textList = core.plugin[data.textList]; + core.doAction(); + }); + core.registerEvent("cgtext", function (data) { + if (!main.replayChecking && !core.isReplaying()) { + core.ui.cgText.image = data.bg; + core.ui.cgText.memory = data.memory; + core.ui.cgText.head = core.clone(data.head); + core.ui.cgText.index = data.index; + core.ui.cgText.name = core.ui.cgText.textList[data.index][0]; + core.ui.cgText.text = data.text + ? data.text + : core.ui.cgText.textList[data.index][1]; + core.ui.cgText.time = data.time; + core.ui.cgText.wait = data.wait; + core.ui.cgText.WindowSkin = data.WindowSkin; + core.ui.cgText.sound = data.sound || ""; + core.ui.cgText.bodyList = core.clone(data.bodyList); + main.dom.cgText.style.display = "block"; + core.ui.cgText.update(); + } else { + core.doAction(); + } + }); + core.registerEvent("introAndLoop", function (data) { + if (!main.replayChecking && !core.isReplaying()) { + core.plugin.introAndLoop(data.intro, data.time, data.loop); + core.doAction(); + } else { + core.doAction(); + } + }); + core.registerEvent("setq", function (data) { + core.setFlag("任务地点", data.id); - core.doAction(); - }); + core.doAction(); + }); - core.registerEvent("setmusics", function (data) { - if ( - (core.getLocalStorage("musics") && - core.getLocalStorage("musics").length === 0) || - !core.getLocalStorage("musics") - ) - core.setLocalStorage("musics", ["theme.mp3"]); - let a = core.getLocalStorage("musics"); - if (!data.bgm) { - core.setLocalStorage("musics", ["theme.mp3"]); - } else { - if (!a.includes(data.bgm)) a.push(data.bgm); - core.setLocalStorage("musics", a); - } - core.doAction(); - }); - core.registerEvent("setcgs", function (data) { - if (!data.img) { - core.setLocalStorage("cgs", []); - } else { - let a = core.getLocalStorage("cgs") ?? []; - if (!a.includes(data.img)) a.push(data.img); - core.setLocalStorage("cgs", a); - } - core.doAction(); - }); - }; -}, + core.registerEvent("setmusics", function (data) { + if ( + (core.getLocalStorage("musics") && + core.getLocalStorage("musics").length === 0) || + !core.getLocalStorage("musics") + ) + core.setLocalStorage("musics", ["theme.mp3"]); + let a = core.getLocalStorage("musics"); + if (!data.bgm) { + core.setLocalStorage("musics", ["theme.mp3"]); + } else { + if (!a.includes(data.bgm)) a.push(data.bgm); + core.setLocalStorage("musics", a); + } + core.doAction(); + }); + core.registerEvent("setcgs", function (data) { + if (!data.img) { + core.setLocalStorage("cgs", []); + } else { + let a = core.getLocalStorage("cgs") ?? []; + if (!a.includes(data.img)) a.push(data.img); + core.setLocalStorage("cgs", a); + } + core.doAction(); + }); + }; + }, "drawLight": function () { // 绘制灯光/漆黑层效果。调用方式 core.plugin.drawLight(...) // 【参数说明】 @@ -2845,10 +2847,12 @@ var plugins_bb40132b_638b_4a9f_b028_d3fe47acc8d1 = 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, @@ -3485,13 +3489,15 @@ var plugins_bb40132b_638b_4a9f_b028_d3fe47acc8d1 = 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"], - ]; + 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", @@ -3731,588 +3737,591 @@ var plugins_bb40132b_638b_4a9f_b028_d3fe47acc8d1 = // init() called in `afterLoadResources`. }, "override": function () { - core.statusBar.icons = { - floor: 0, - name: null, - lv: 1, - hpmax: 2, - hp: 3, - atk: 4, - def: 5, - mdef: 6, - money: 7, - exp: 8, - up: 9, - book: 10, - fly: 11, - toolbox: 12, - keyboard: 13, - shop: 14, - save: 15, - load: 16, - settings: 17, - play: 18, - pause: 19, - stop: 20, - speedDown: 21, - speedUp: 22, - rewind: 23, - equipbox: 24, - mana: 25, - skill: 26, - exit: 27, - btn1: 28, - btn2: 29, - btn3: 30, - btn4: 31, - btn5: 32, - btn6: 33, - btn7: 34, - alt: 35, - keys: 36, - help: 37, - battle: 38, - }; - core.actions._getClickLoc = function (x, y) { - var size = 32 * core.domStyle.scale; - var left = main.dom.gameDraw.offsetLeft + main.dom.gameGroup.offsetLeft; - var top = main.dom.gameDraw.offsetTop + main.dom.gameGroup.offsetTop; - var loc = { - x: Math.max(x - left, 0), - y: Math.max(y - top, 0), - size: size, - }; - return loc; - }; - core.ui._drawWindowSelector = function (background, x, y, w, h) { - w = Math.round(w) + 48; - h = Math.round(h); - var ctx = core.ui.createCanvas("_selector", x - 24, y, w, h, 165); - ctx.canvas.id = ""; - this._drawSelector(ctx, background, w, h); - }; + core.statusBar.icons = { + floor: 0, + name: null, + lv: 1, + hpmax: 2, + hp: 3, + atk: 4, + def: 5, + mdef: 6, + money: 7, + exp: 8, + up: 9, + book: 10, + fly: 11, + toolbox: 12, + keyboard: 13, + shop: 14, + save: 15, + load: 16, + settings: 17, + play: 18, + pause: 19, + stop: 20, + speedDown: 21, + speedUp: 22, + rewind: 23, + equipbox: 24, + mana: 25, + skill: 26, + exit: 27, + btn1: 28, + btn2: 29, + btn3: 30, + btn4: 31, + btn5: 32, + btn6: 33, + btn7: 34, + alt: 35, + keys: 36, + help: 37, + battle: 38, + }; + core.actions._getClickLoc = function (x, y) { + var size = 32 * core.domStyle.scale; + var left = main.dom.gameDraw.offsetLeft + main.dom.gameGroup.offsetLeft; + var top = main.dom.gameDraw.offsetTop + main.dom.gameGroup.offsetTop; + var loc = { + x: Math.max(x - left, 0), + y: Math.max(y - top, 0), + size: size, + }; + return loc; + }; + core.ui._drawWindowSelector = function (background, x, y, w, h) { + w = Math.round(w) + 48; + h = Math.round(h); + var ctx = core.ui.createCanvas("_selector", x - 24, y, w, h, 165); + ctx.canvas.id = ""; + this._drawSelector(ctx, background, w, h); + }; - core.ui._drawSelector = function (ctx, background, w, h, left, top) { - left = left || 0; - top = top || 0; - ctx = this.getContextByName(ctx); - if (!ctx) return; - if (typeof background == "string") - background = core.material.images.images[background]; - if (!(background instanceof Image)) return; - // badge - ctx.drawImage(background, 132, 68, 24, 24, left + 4, top + 4, 24, 24); - ctx.drawImage( - background, - 132, - 68, - 24, - 24, - w - left - 28, - top + 4, - 24, - 24 - ); - }; + core.ui._drawSelector = function (ctx, background, w, h, left, top) { + left = left || 0; + top = top || 0; + ctx = this.getContextByName(ctx); + if (!ctx) return; + if (typeof background == "string") + background = core.material.images.images[background]; + if (!(background instanceof Image)) return; + // badge + ctx.drawImage(background, 132, 68, 24, 24, left + 4, top + 4, 24, 24); + ctx.drawImage( + background, + 132, + 68, + 24, + 24, + w - left - 28, + top + 4, + 24, + 24 + ); + }; - enemys.prototype._nextCriticals_useBinarySearch = function ( - enemy, - info, - number, - x, - y, - floorId - ) { - var mon_hp = info.mon_hp, - hero_atk = core.status.hero.atk, - mon_def = info.mon_def, - pre = info.damage; - var list = []; - var start_atk = hero_atk; - if (info.__over__) { - start_atk += info.__overAtk__; - list.push([info.__overAtk__, -info.damage]); - } - var calNext = function (currAtk, maxAtk) { - var start = Math.floor(currAtk), - end = Math.floor(maxAtk); - if (start > end) return null; + enemys.prototype._nextCriticals_useBinarySearch = function ( + enemy, + info, + number, + x, + y, + floorId + ) { + var mon_hp = info.mon_hp, + hero_atk = core.status.hero.atk, + mon_def = info.mon_def, + pre = info.damage; + var list = []; + var start_atk = hero_atk; + if (info.__over__) { + start_atk += info.__overAtk__; + list.push([info.__overAtk__, -info.damage]); + } + var calNext = function (currAtk, maxAtk) { + var start = Math.floor(currAtk), + end = Math.floor(maxAtk); + if (start > end) return null; - while (start < end) { - var mid = Math.floor((start + end) / 2); - if (mid - start > end - mid) mid--; - var nextInfo = core.enemys.getDamageInfo( - enemy, { atk: mid }, - x, - y, - floorId - ); - if (nextInfo == null || typeof nextInfo == "number") return null; - if (pre > nextInfo.damage) end = mid; - else start = mid + 1; - } - var nextInfo = core.enemys.getDamageInfo( - enemy, { atk: start }, - x, - y, - floorId - ); - return nextInfo == null || - typeof nextInfo == "number" || - nextInfo.damage >= pre ? - null : [start, nextInfo.damage]; - }; - var currAtk = start_atk; - while (true) { - var next = calNext(currAtk + 1, Number.MAX_SAFE_INTEGER, pre); - if (next == null) break; - currAtk = next[0]; - pre = next[1]; - list.push([currAtk - hero_atk, info.damage - pre]); - if (pre <= 0 && !core.flags.enableNegativeDamage) break; - if (list.length >= number) break; - } - if (list.length == 0) list.push([0, 0]); - return list; - }; - core.ui.clearMap = function (name, x, y, width, height) { - if (name == "all") { - for (var m in core.canvas) { - core.canvas[m].clearRect( - -32, - -32, - core.canvas[m].canvas.width + 32, - core.canvas[m].canvas.height + 32 - ); - } - core.clearMap("outerUI"); - core.dom.gif.innerHTML = ""; - core.removeGlobalAnimate(); - core.deleteCanvas(function (one) { - return one.startsWith("_bigImage_"); - }); - core.setWeather(null); - } else { - var ctx = this.getContextByName(name); - if (ctx) - ctx.clearRect( - x || 0, - y || 0, - width || ctx.canvas.width, - height || ctx.canvas.height - ); - } - }; - events.prototype.openBook = function (fromUserAction) { - if (core.isReplaying()) return; - // 如果能恢复事件(从callBook事件触发) - if ( - core.status.event.id == "book" && - core.events.recoverEvents(core.status.event.interval) - ) - return; - // 当前是book,且从“浏览地图”打开 - if (core.status.event.id == "book" && core.status.event.ui) { - core.status.boxAnimateObjs = []; - core.ui._drawViewMaps(core.status.event.ui); - return; - } - // 从“浏览地图”页面打开 - if (core.status.event.id == "viewMaps" || core.status.event.id == "fly") { - fromUserAction = false; - core.status.event.ui = core.status.event.data; - } - if (!this._checkStatus("book", fromUserAction, true)) return; - core.playSound("打开界面"); - core.useItem("book", true); - }; - ////// 怪物手册界面时,放开某个键的操作 ////// - core.actions._keyUpBook = function (keycode) { - if (keycode == 27 || keycode == 88) { - core.playSound("取消"); - if (core.events.recoverEvents(core.status.event.interval)) { - return; - } else if (core.status.event.ui != null) { - core.status.boxAnimateObjs = []; - if (typeof core.status.event.ui === "number") { - core.status.event.id = "fly"; - core.ui.drawFly(core.status.event.ui); - } else { - core.ui._drawViewMaps(core.status.event.ui); - } - } else core.ui.closePanel(); - return; - } - if (keycode == 13 || keycode == 32 || keycode == 67) { - var data = core.status.event.data; - if (data != null) { - core.ui._drawBookDetail(data); - } - return; - } - }; - ////// 怪物手册界面的点击操作 ////// - actions.prototype._clickBook = function (x, y) { - var pageinfo = core.ui._drawBook_pageinfo(); - // 上一页 - if ( - (x == this._HX_ - 2 || x == this._HX_ - 3) && - y === core._HEIGHT_ - 1 - ) { - core.playSound("光标移动"); - core.ui.drawBook(core.status.event.data - pageinfo.per_page); - return; - } - // 下一页 - if ( - (x == this._HX_ + 2 || x == this._HX_ + 3) && - y === core._HEIGHT_ - 1 - ) { - core.playSound("光标移动"); - core.ui.drawBook(core.status.event.data + pageinfo.per_page); - return; - } - // 返回 - if (x >= this.LAST - 2 && y === core._HEIGHT_ - 1) { - core.playSound("取消"); - if (core.events.recoverEvents(core.status.event.interval)) { - return; - } else if (core.status.event.ui != null) { - core.status.boxAnimateObjs = []; - if (typeof core.status.event.ui === "number") { - core.status.event.id = "fly"; - core.ui.drawFly(core.status.event.ui); - } else { - core.ui._drawViewMaps(core.status.event.ui); - } - } else core.ui.closePanel(); - return; - } - // 怪物信息 - var data = core.status.event.data; - if (data != null && y < core._HEIGHT_ - 1) { - var per_page = pageinfo.per_page, - page = parseInt(data / per_page); - var u = (core._HEIGHT_ - 1) / per_page; - for (var i = 0; i < per_page; ++i) { - if (y >= u * i && y < u * (i + 1)) { - var index = per_page * page + i; - core.ui.drawBook(index); - core.ui._drawBookDetail(index); - break; - } - } - return; - } - return; - }; + while (start < end) { + var mid = Math.floor((start + end) / 2); + if (mid - start > end - mid) mid--; + var nextInfo = core.enemys.getDamageInfo( + enemy, + { atk: mid }, + x, + y, + floorId + ); + if (nextInfo == null || typeof nextInfo == "number") return null; + if (pre > nextInfo.damage) end = mid; + else start = mid + 1; + } + var nextInfo = core.enemys.getDamageInfo( + enemy, + { atk: start }, + x, + y, + floorId + ); + return nextInfo == null || + typeof nextInfo == "number" || + nextInfo.damage >= pre + ? null + : [start, nextInfo.damage]; + }; + var currAtk = start_atk; + while (true) { + var next = calNext(currAtk + 1, Number.MAX_SAFE_INTEGER, pre); + if (next == null) break; + currAtk = next[0]; + pre = next[1]; + list.push([currAtk - hero_atk, info.damage - pre]); + if (pre <= 0 && !core.flags.enableNegativeDamage) break; + if (list.length >= number) break; + } + if (list.length == 0) list.push([0, 0]); + return list; + }; + core.ui.clearMap = function (name, x, y, width, height) { + if (name == "all") { + for (var m in core.canvas) { + core.canvas[m].clearRect( + -32, + -32, + core.canvas[m].canvas.width + 32, + core.canvas[m].canvas.height + 32 + ); + } + core.clearMap("outerUI"); + core.dom.gif.innerHTML = ""; + core.removeGlobalAnimate(); + core.deleteCanvas(function (one) { + return one.startsWith("_bigImage_"); + }); + core.setWeather(null); + } else { + var ctx = this.getContextByName(name); + if (ctx) + ctx.clearRect( + x || 0, + y || 0, + width || ctx.canvas.width, + height || ctx.canvas.height + ); + } + }; + events.prototype.openBook = function (fromUserAction) { + if (core.isReplaying()) return; + // 如果能恢复事件(从callBook事件触发) + if ( + core.status.event.id == "book" && + core.events.recoverEvents(core.status.event.interval) + ) + return; + // 当前是book,且从“浏览地图”打开 + if (core.status.event.id == "book" && core.status.event.ui) { + core.status.boxAnimateObjs = []; + core.ui._drawViewMaps(core.status.event.ui); + return; + } + // 从“浏览地图”页面打开 + if (core.status.event.id == "viewMaps" || core.status.event.id == "fly") { + fromUserAction = false; + core.status.event.ui = core.status.event.data; + } + if (!this._checkStatus("book", fromUserAction, true)) return; + core.playSound("打开界面"); + core.useItem("book", true); + }; + ////// 怪物手册界面时,放开某个键的操作 ////// + core.actions._keyUpBook = function (keycode) { + if (keycode == 27 || keycode == 88) { + core.playSound("取消"); + if (core.events.recoverEvents(core.status.event.interval)) { + return; + } else if (core.status.event.ui != null) { + core.status.boxAnimateObjs = []; + if (typeof core.status.event.ui === "number") { + core.status.event.id = "fly"; + core.ui.drawFly(core.status.event.ui); + } else { + core.ui._drawViewMaps(core.status.event.ui); + } + } else core.ui.closePanel(); + return; + } + if (keycode == 13 || keycode == 32 || keycode == 67) { + var data = core.status.event.data; + if (data != null) { + core.ui._drawBookDetail(data); + } + return; + } + }; + ////// 怪物手册界面的点击操作 ////// + actions.prototype._clickBook = function (x, y) { + var pageinfo = core.ui._drawBook_pageinfo(); + // 上一页 + if ( + (x == this._HX_ - 2 || x == this._HX_ - 3) && + y === core._HEIGHT_ - 1 + ) { + core.playSound("光标移动"); + core.ui.drawBook(core.status.event.data - pageinfo.per_page); + return; + } + // 下一页 + if ( + (x == this._HX_ + 2 || x == this._HX_ + 3) && + y === core._HEIGHT_ - 1 + ) { + core.playSound("光标移动"); + core.ui.drawBook(core.status.event.data + pageinfo.per_page); + return; + } + // 返回 + if (x >= this.LAST - 2 && y === core._HEIGHT_ - 1) { + core.playSound("取消"); + if (core.events.recoverEvents(core.status.event.interval)) { + return; + } else if (core.status.event.ui != null) { + core.status.boxAnimateObjs = []; + if (typeof core.status.event.ui === "number") { + core.status.event.id = "fly"; + core.ui.drawFly(core.status.event.ui); + } else { + core.ui._drawViewMaps(core.status.event.ui); + } + } else core.ui.closePanel(); + return; + } + // 怪物信息 + var data = core.status.event.data; + if (data != null && y < core._HEIGHT_ - 1) { + var per_page = pageinfo.per_page, + page = parseInt(data / per_page); + var u = (core._HEIGHT_ - 1) / per_page; + for (var i = 0; i < per_page; ++i) { + if (y >= u * i && y < u * (i + 1)) { + var index = per_page * page + i; + core.ui.drawBook(index); + core.ui._drawBookDetail(index); + break; + } + } + return; + } + return; + }; - ////// 执行当前自定义事件列表中的下一个事件 ////// - events.prototype.doAction = function () { - // 清空boxAnimate和UI层 - clearInterval(core.status.event.interval); - clearTimeout(core.status.event.interval); - clearInterval(core.status.event.animateUI); - core.status.event.interval = null; - delete core.status.event.aniamteUI; - if (core.status.gameOver || core.status.replay.failed) return; - // 判定是否执行完毕 - if (this._doAction_finishEvents()) return; - core.clearUI(); - var floorId = core.status.event.data.floorId || core.status.floorId; - // 当前点坐标和前缀 - var x = core.status.event.data.x, - y = core.status.event.data.y; - var prefix = [ - floorId || ":f", - x != null ? x : "x", - y != null ? y : "y", - ].join("@"); - var current = core.status.event.data.list[0]; - if (this._popEvents(current, prefix)) return; - // 当前要执行的事件 - var data = current.todo.shift(); - core.status.event.data.current = data; - if (typeof data == "string") data = { type: "text", text: data }; - // 该事件块已经被禁用 - if (data._disabled) return core.doAction(); - if (data.type !== "cgtext") { - core.unregisterAnimationFrame("skip"); - core.setFlag("skip", false); - } - data.floorId = data.floorId || floorId; - core.status.event.data.type = data.type; - this.doEvent(data, x, y, prefix); - return; - }; + ////// 执行当前自定义事件列表中的下一个事件 ////// + events.prototype.doAction = function () { + // 清空boxAnimate和UI层 + clearInterval(core.status.event.interval); + clearTimeout(core.status.event.interval); + clearInterval(core.status.event.animateUI); + core.status.event.interval = null; + delete core.status.event.aniamteUI; + if (core.status.gameOver || core.status.replay.failed) return; + // 判定是否执行完毕 + if (this._doAction_finishEvents()) return; + core.clearUI(); + var floorId = core.status.event.data.floorId || core.status.floorId; + // 当前点坐标和前缀 + var x = core.status.event.data.x, + y = core.status.event.data.y; + var prefix = [ + floorId || ":f", + x != null ? x : "x", + y != null ? y : "y", + ].join("@"); + var current = core.status.event.data.list[0]; + if (this._popEvents(current, prefix)) return; + // 当前要执行的事件 + var data = current.todo.shift(); + core.status.event.data.current = data; + if (typeof data == "string") data = { type: "text", text: data }; + // 该事件块已经被禁用 + if (data._disabled) return core.doAction(); + if (data.type !== "cgtext") { + core.unregisterAnimationFrame("skip"); + core.setFlag("skip", false); + } + data.floorId = data.floorId || floorId; + core.status.event.data.type = data.type; + this.doEvent(data, x, y, prefix); + return; + }; - ////// 在某个canvas上绘制粗体 ////// - core.fillBoldText1 = function ( - name, - text, - x, - y, - style, - strokeStyle, - lineWidth, - font, - maxWidth - ) { - var ctx = this.getContextByName(name); - if (!ctx) return; - if (font) ctx.font = font; - if (!style) style = ctx.fillStyle; - style = core.arrayToRGBA(style); - if (!strokeStyle) strokeStyle = "#000000"; - strokeStyle = core.arrayToRGBA(strokeStyle); - if (maxWidth != null) { - this.setFontForMaxWidth(ctx, text, maxWidth); - } - ctx.strokeStyle = strokeStyle; + ////// 在某个canvas上绘制粗体 ////// + core.fillBoldText1 = function ( + name, + text, + x, + y, + style, + strokeStyle, + lineWidth, + font, + maxWidth + ) { + var ctx = this.getContextByName(name); + if (!ctx) return; + if (font) ctx.font = font; + if (!style) style = ctx.fillStyle; + style = core.arrayToRGBA(style); + if (!strokeStyle) strokeStyle = "#000000"; + strokeStyle = core.arrayToRGBA(strokeStyle); + if (maxWidth != null) { + this.setFontForMaxWidth(ctx, text, maxWidth); + } + ctx.strokeStyle = strokeStyle; - if (!lineWidth) lineWidth = 2; - ctx.lineWidth = lineWidth; - ctx.strokeText(text, x, y); - ctx.fillStyle = style; - ctx.fillText(text, x, y); - }; - ////// 绘制 WindowSkin - ui.prototype.drawWindowSkin = function ( - background, - ctx, - x, - y, - w, - h, - direction, - px, - py, - size = 1 - ) { - background = background || core.status.textAttribute.background; + if (!lineWidth) lineWidth = 2; + ctx.lineWidth = lineWidth; + ctx.strokeText(text, x, y); + ctx.fillStyle = style; + ctx.fillText(text, x, y); + }; + ////// 绘制 WindowSkin + ui.prototype.drawWindowSkin = function ( + background, + ctx, + x, + y, + w, + h, + direction, + px, + py, + size = 1 + ) { + background = background || core.status.textAttribute.background; - // 仿RM窗口皮肤 ↓ - // 绘制背景 - core.drawImage( - ctx, - background, - 0, - 0, - 128, - 128, - x + 2 * size, - y + 2 * size, - w - 4 * size, - h - 4 * size - ); - // 绘制边框 - // 上方 - core.drawImage( - ctx, - background, - 128, - 0, - 16, - 16, - x, - y, - 16 * size, - 16 * size - ); - for (var dx = 0; dx < w - 64 * size; dx += 32 * size) { - core.drawImage( - ctx, - background, - 144, - 0, - 32, - 16, - x + dx + 16 * size, - y, - 32 * size, - 16 * size - ); - core.drawImage( - ctx, - background, - 144, - 48, - 32, - 16, - x + dx + 16 * size, - y + h - 16 * size, - 32 * size, - 16 * size - ); - } - core.drawImage( - ctx, - background, - 144, - 0, - (w - dx - 32 * size) / size, - 16, - x + dx + 16 * size, - y, - w - dx - 32 * size, - 16 * size - ); - core.drawImage( - ctx, - background, - 144, - 48, - (w - dx - 32 * size) / size, - 16, - x + dx + 16 * size, - y + h - 16 * size, - w - dx - 32 * size, - 16 * size - ); - core.drawImage( - ctx, - background, - 176, - 0, - 16, - 16, - x + w - 16 * size, - y, - 16 * size, - 16 * size - ); - // 左右 - for (var dy = 0; dy < h - 64 * size; dy += 32 * size) { - core.drawImage( - ctx, - background, - 128, - 16, - 16, - 32, - x, - y + dy + 16 * size, - 16 * size, - 32 * size - ); - core.drawImage( - ctx, - background, - 176, - 16, - 16, - 32, - x + w - 16 * size, - y + dy + 16 * size, - 16 * size, - 32 * size - ); - } - core.drawImage( - ctx, - background, - 128, - 16, - 16, - (h - dy - 32 * size) / size, - x, - y + dy + 16 * size, - 16 * size, - h - dy - 32 * size - ); - core.drawImage( - ctx, - background, - 176, - 16, - 16, - (h - dy - 32 * size) / size, - x + w - 16 * size, - y + dy + 16 * size, - 16 * size, - h - dy - 32 * size - ); - //下方 - core.drawImage( - ctx, - background, - 128, - 48, - 16, - 16, - x, - y + h - 16 * size, - 16 * size, - 16 * size - ); - core.drawImage( - ctx, - background, - 176, - 48, - 16, - 16, - x + w - 16 * size, - y + h - 16 * size, - 16 * size, - 16 * size - ); + // 仿RM窗口皮肤 ↓ + // 绘制背景 + core.drawImage( + ctx, + background, + 0, + 0, + 128, + 128, + x + 2 * size, + y + 2 * size, + w - 4 * size, + h - 4 * size + ); + // 绘制边框 + // 上方 + core.drawImage( + ctx, + background, + 128, + 0, + 16, + 16, + x, + y, + 16 * size, + 16 * size + ); + for (var dx = 0; dx < w - 64 * size; dx += 32 * size) { + core.drawImage( + ctx, + background, + 144, + 0, + 32, + 16, + x + dx + 16 * size, + y, + 32 * size, + 16 * size + ); + core.drawImage( + ctx, + background, + 144, + 48, + 32, + 16, + x + dx + 16 * size, + y + h - 16 * size, + 32 * size, + 16 * size + ); + } + core.drawImage( + ctx, + background, + 144, + 0, + (w - dx - 32 * size) / size, + 16, + x + dx + 16 * size, + y, + w - dx - 32 * size, + 16 * size + ); + core.drawImage( + ctx, + background, + 144, + 48, + (w - dx - 32 * size) / size, + 16, + x + dx + 16 * size, + y + h - 16 * size, + w - dx - 32 * size, + 16 * size + ); + core.drawImage( + ctx, + background, + 176, + 0, + 16, + 16, + x + w - 16 * size, + y, + 16 * size, + 16 * size + ); + // 左右 + for (var dy = 0; dy < h - 64 * size; dy += 32 * size) { + core.drawImage( + ctx, + background, + 128, + 16, + 16, + 32, + x, + y + dy + 16 * size, + 16 * size, + 32 * size + ); + core.drawImage( + ctx, + background, + 176, + 16, + 16, + 32, + x + w - 16 * size, + y + dy + 16 * size, + 16 * size, + 32 * size + ); + } + core.drawImage( + ctx, + background, + 128, + 16, + 16, + (h - dy - 32 * size) / size, + x, + y + dy + 16 * size, + 16 * size, + h - dy - 32 * size + ); + core.drawImage( + ctx, + background, + 176, + 16, + 16, + (h - dy - 32 * size) / size, + x + w - 16 * size, + y + dy + 16 * size, + 16 * size, + h - dy - 32 * size + ); + //下方 + core.drawImage( + ctx, + background, + 128, + 48, + 16, + 16, + x, + y + h - 16 * size, + 16 * size, + 16 * size + ); + core.drawImage( + ctx, + background, + 176, + 48, + 16, + 16, + x + w - 16 * size, + y + h - 16 * size, + 16 * size, + 16 * size + ); - // arrow - if (px != null && py != null) { - if (direction == "up") { - core.drawImage( - ctx, - background, - 128, - 96, - 32, - 32, - px, - y + h - 3 * size, - 32 * size, - 32 * size - ); - } else if (direction == "down") { - core.drawImage( - ctx, - background, - 160, - 96, - 32, - 32, - px, - y - 29 * size, - 32 * size, - 32 * size - ); - } - } - // 仿RM窗口皮肤 ↑ - }; - events.prototype.battle = function (id, x, y, force, callback) { - core.saveAndStopAutomaticRoute(); - id = id || core.getBlockId(x, y); - const cls = core.getClsFromId(id) - if (!id || !cls || !(cls === 'enemys' || cls === 'enemy48')) return core.clearContinueAutomaticRoute(callback); - // 非强制战斗 - if (!core.enemys.canBattle(id, x, y) && !force && !core.status.event.id) { - core.stopSound(); - core.playSound("操作失败"); - core.drawTip("你打不过此怪物!", id); - return core.clearContinueAutomaticRoute(callback); - } - // 自动存档 - if (!core.status.event.id) core.autosave(true); - // 战前事件 - if (!this.beforeBattle(id, x, y)) - return core.clearContinueAutomaticRoute(callback); - // 战后事件 - this.afterBattle(id, x, y); - if (callback) callback(); - }; - -}, + // arrow + if (px != null && py != null) { + if (direction == "up") { + core.drawImage( + ctx, + background, + 128, + 96, + 32, + 32, + px, + y + h - 3 * size, + 32 * size, + 32 * size + ); + } else if (direction == "down") { + core.drawImage( + ctx, + background, + 160, + 96, + 32, + 32, + px, + y - 29 * size, + 32 * size, + 32 * size + ); + } + } + // 仿RM窗口皮肤 ↑ + }; + events.prototype.battle = function (id, x, y, force, callback) { + core.saveAndStopAutomaticRoute(); + id = id || core.getBlockId(x, y); + const cls = core.getClsFromId(id); + if (!id || !cls || !(cls === "enemys" || cls === "enemy48")) + return core.clearContinueAutomaticRoute(callback); + // 非强制战斗 + if (!core.enemys.canBattle(id, x, y) && !force && !core.status.event.id) { + core.stopSound(); + core.playSound("操作失败"); + core.drawTip("你打不过此怪物!", id); + return core.clearContinueAutomaticRoute(callback); + } + // 自动存档 + if (!core.status.event.id) core.autosave(true); + // 战前事件 + if (!this.beforeBattle(id, x, y)) + return core.clearContinueAutomaticRoute(callback); + // 战后事件 + this.afterBattle(id, x, y); + if (callback) callback(); + }; + }, "额外信息": function () { /* 宝石血瓶左下角显示数值 * 注意!!!不要在道具属性中直接操作flags,使用core.status.hero.flags或core.setFlag系列函数代替! @@ -8724,8 +8733,8 @@ var plugins_bb40132b_638b_4a9f_b028_d3fe47acc8d1 = }; }, "音频系统": function () { - // 在此增加新插件 - /*首先,在造塔群下载所需的库文件,然后放置在塔目录下的 libs/thirdparty 或其他目录下,之后在 index.html 的最后加上下面这几行: + // 在此增加新插件 + /*首先,在造塔群下载所需的库文件,然后放置在塔目录下的 libs/thirdparty 或其他目录下,之后在 index.html 的最后加上下面这几行: @@ -8733,2327 +8742,2081 @@ var plugins_bb40132b_638b_4a9f_b028_d3fe47acc8d1 = */ - // 将__enable置为false将关闭插件 - let __enable = true; - if (!__enable || main.mode === "editor") return; - const { OggOpusDecoderWebWorker } = window["ogg-opus-decoder"]; - const { OggVorbisDecoderWebWorker } = window["ogg-vorbis-decoder"]; - const { CodecParser } = window.CodecParser; - const { Transition, linear } = core.plugin.animate; - - const audio = new Audio(); - const AudioStatus = { - Playing: 0, - Pausing: 1, - Paused: 2, - Stoping: 3, - Stoped: 4, - }; - const supportMap = new Map(); - const AudioType = { - Mp3: "audio/mpeg", - Wav: 'audio/wav; codecs="1"', - Flac: "audio/flac", - Opus: 'audio/ogg; codecs="opus"', - Ogg: 'audio/ogg; codecs="vorbis"', - Aac: "audio/aac", - }; - /** - * 检查一种音频类型是否能被播放 - * @param type 音频类型 AudioType - */ - function isAudioSupport(type) { - if (supportMap.has(type)) return supportMap.get(type); - else { - const support = audio.canPlayType(type); - const canPlay = support === "maybe" || support === "probably"; - supportMap.set(type, canPlay); - return canPlay; - } - } - - const typeMap = new Map([ - ["ogg", AudioType.Ogg], - ["mp3", AudioType.Mp3], - ["wav", AudioType.Wav], - ["flac", AudioType.Flac], - ["opus", AudioType.Opus], - ["aac", AudioType.Aac], - ]); - - /** - * 根据文件名拓展猜测其类型 - * @param file 文件名 string - */ - function guessTypeByExt(file) { - const ext = /\.[a-zA-Z\d]+$/.exec(file); - if (!ext?.[0]) return ""; - const type = ext[0].slice(1); - return typeMap.get(type.toLocaleLowerCase()) ?? ""; - } - - isAudioSupport(AudioType.Ogg); - isAudioSupport(AudioType.Mp3); - isAudioSupport(AudioType.Wav); - isAudioSupport(AudioType.Flac); - isAudioSupport(AudioType.Opus); - isAudioSupport(AudioType.Aac); - - function isNil(value) { - return value === void 0 || value === null; - } - - function sleep(time) { - return new Promise((res) => setTimeout(res, time)); - } - class AudioEffect { - constructor(ac) {} - /** - * 连接至其他效果器 - * @param target 目标输入 IAudioInput - * @param output 当前效果器输出通道 Number - * @param input 目标效果器的输入通道 Number - */ - connect(target, output, input) { - this.output.connect(target.input, output, input); - } - - /** - * 与其他效果器取消连接 - * @param target 目标输入 IAudioInput - * @param output 当前效果器输出通道 Number - * @param input 目标效果器的输入通道 Number - */ - disconnect(target, output, input) { - if (!target) { - if (!isNil(output)) { - this.output.disconnect(output); - } else { - this.output.disconnect(); - } - } else { - if (!isNil(output)) { - if (!isNil(input)) { - this.output.disconnect(target.input, output, input); - } else { - this.output.disconnect(target.input, output); - } - } else { - this.output.disconnect(target.input); - } - } - } - } - - class StereoEffect extends AudioEffect { - constructor(ac) { - super(ac); - const panner = ac.createPanner(); - this.input = panner; - this.output = panner; - } - - /** - * 设置音频朝向,x正方形水平向右,y正方形垂直于地面向上,z正方向垂直屏幕远离用户 - * @param x 朝向x坐标 Number - * @param y 朝向y坐标 Number - * @param z 朝向z坐标 Number - */ - setOrientation(x, y, z) { - this.output.orientationX.value = x; - this.output.orientationY.value = y; - this.output.orientationZ.value = z; - } - /** - * 设置音频位置,x正方形水平向右,y正方形垂直于地面向上,z正方向垂直屏幕远离用户 - * @param x 位置x坐标 Number - * @param y 位置y坐标 Number - * @param z 位置z坐标 Number - */ - setPosition(x, y, z) { - this.output.positionX.value = x; - this.output.positionY.value = y; - this.output.positionZ.value = z; - } - end() {} - - start() {} - } - class VolumeEffect extends AudioEffect { - constructor(ac) { - super(ac); - const gain = ac.createGain(); - this.input = gain; - this.output = gain; - } - - /** - * 设置音量大小 - * @param volume 音量大小 Number - */ - setVolume(volume) { - this.output.gain.value = volume; - } - - /** - * 获取音量大小 Number - */ - getVolume() { - return this.output.gain.value; - } - - end() {} - - start() {} - } - class ChannelVolumeEffect extends AudioEffect { - /** 所有的音量控制节点 */ - - constructor(ac) { - super(ac); - /** 所有的音量控制节点 */ - this.gain = []; - const splitter = ac.createChannelSplitter(); - const merger = ac.createChannelMerger(); - this.output = merger; - this.input = splitter; - for (let i = 0; i < 6; i++) { - const gain = ac.createGain(); - splitter.connect(gain, i); - gain.connect(merger, 0, i); - this.gain.push(gain); - } - } - - /** - * 设置某个声道的音量大小 - * @param channel 要设置的声道,可填0-5 Number - * @param volume 这个声道的音量大小 Number - */ - setVolume(channel, volume) { - if (!this.gain[channel]) return; - this.gain[channel].gain.value = volume; - } - - /** - * 获取某个声道的音量大小,可填0-5 - * @param channel 要获取的声道 Number - */ - getVolume(channel) { - if (!this.gain[channel]) return 0; - return this.gain[channel].gain.value; - } - - end() {} - - start() {} - } - class DelayEffect extends AudioEffect { - constructor(ac) { - super(ac); - - const delay = ac.createDelay(); - this.input = delay; - this.output = delay; - } - - /** - * 设置延迟时长 - * @param delay 延迟时长,单位秒 Number - */ - setDelay(delay) { - this.output.delayTime.value = delay; - } - - /** - * 获取延迟时长 - */ - getDelay() { - return this.output.delayTime.value; - } - - end() {} - - start() {} - } - class EchoEffect extends AudioEffect { - constructor(ac) { - super(ac); - /** 当前增益 */ - this.gain = 0.5; - /** 是否正在播放 */ - this.playing = false; - const delay = ac.createDelay(); - const gain = ac.createGain(); - gain.gain.value = 0.5; - delay.delayTime.value = 0.05; - delay.connect(gain); - gain.connect(delay); - /** 延迟节点 */ - this.delay = delay; - /** 反馈增益节点 */ - this.gainNode = gain; - - this.input = gain; - this.output = gain; - } - - /** - * 设置回声反馈增益大小 - * @param gain 增益大小,范围 0-1,大于等于1的视为0.5,小于0的视为0 Number - */ - setFeedbackGain(gain) { - const resolved = gain >= 1 ? 0.5 : gain < 0 ? 0 : gain; - this.gain = resolved; - if (this.playing) this.gainNode.gain.value = resolved; - } - - /** - * 设置回声间隔时长 - * @param delay 回声时长,范围 0.01-Infinity,小于0.01的视为0.01 Number - */ - setEchoDelay(delay) { - const resolved = delay < 0.01 ? 0.01 : delay; - this.delay.delayTime.value = resolved; - } - - /** - * 获取反馈节点增益 - */ - getFeedbackGain() { - return this.gain; - } - - /** - * 获取回声间隔时长 - */ - getEchoDelay() { - return this.delay.delayTime.value; - } - - end() { - this.playing = false; - const echoTime = Math.ceil(Math.log(0.001) / Math.log(this.gain)) + 10; - sleep(this.delay.delayTime.value * echoTime).then(() => { - if (!this.playing) this.gainNode.gain.value = 0; - }); - } - - start() { - this.playing = true; - this.gainNode.gain.value = this.gain; - } - } - - class StreamLoader { - constructor(url) { - /** 传输目标 Set*/ - this.target = new Set(); - this.loading = false; - } - - /** - * 将加载流传递给字节流读取对象 - * @param reader 字节流读取对象 IStreamReader - */ - pipe(reader) { - if (this.loading) { - console.warn( - "Cannot pipe new StreamReader object when stream is loading." - ); - return; - } - this.target.add(reader); - reader.piped(this); - return this; - } - - async start() { - if (this.loading) return; - this.loading = true; - const response = await window.fetch(this.url); - const stream = response.body; - if (!stream) { - console.error("Cannot get reader when fetching '" + this.url + "'."); - return; - } - // 获取读取器 - this.stream = stream; - const reader = response.body?.getReader(); - const targets = [...this.target]; - - await Promise.all(targets.map((v) => v.start(stream, this, response))); - if (reader && reader.read) { - // 开始流传输 - while (true) { - const { value, done } = await reader.read(); - await Promise.all( - targets.map((v) => v.pump(value, done, response)) - ); - if (done) break; - } - } else { - // 如果不支持流传输 - const buffer = await response.arrayBuffer(); - const data = new Uint8Array(buffer); - await Promise.all(targets.map((v) => v.pump(data, true, response))); - } - - this.loading = false; - targets.forEach((v) => v.end(true)); - - // - } - - cancel(reason) { - if (!this.stream) return; - this.stream.cancel(reason); - this.loading = false; - this.target.forEach((v) => v.end(false, reason)); - } - } - const fileSignatures = [ - [AudioType.Mp3, [0x49, 0x44, 0x33]], - [AudioType.Ogg, [0x4f, 0x67, 0x67, 0x53]], - [AudioType.Wav, [0x52, 0x49, 0x46, 0x46]], - [AudioType.Flac, [0x66, 0x4c, 0x61, 0x43]], - [AudioType.Aac, [0xff, 0xf1]], - [AudioType.Aac, [0xff, 0xf9]], - ]; - const oggHeaders = [ - [AudioType.Opus, [0x4f, 0x70, 0x75, 0x73, 0x48, 0x65, 0x61, 0x64]], - ]; - - function checkAudioType(data) { - let audioType = ""; - // 检查头文件获取音频类型,仅检查前256个字节 - const toCheck = data.slice(0, 256); - for (const [type, value] of fileSignatures) { - if (value.every((v, i) => toCheck[i] === v)) { - audioType = type; - break; - } - } - if (audioType === AudioType.Ogg) { - // 如果是ogg的话,进一步判断是不是opus - for (const [key, value] of oggHeaders) { - const has = toCheck.some((_, i) => { - return value.every((v, ii) => toCheck[i + ii] === v); - }); - if (has) { - audioType = key; - break; - } - } - } - - return audioType; - } - class AudioDecoder { - /** - * 注册一个解码器 - * @param type 要注册的解码器允许解码的类型 - * @param decoder 解码器对象 - */ - static registerDecoder(type, decoder) { - if (!this.decoderMap) this.decoderMap = new Map(); - if (this.decoderMap.has(type)) { - console.warn( - "Audio stream decoder for audio type '" + - type + - "' has already existed." - ); - return; - } - - this.decoderMap.set(type, decoder); - } - - /** - * 解码音频数据 - * @param data 音频文件数据 - * @param player AudioPlayer实例 - */ - static async decodeAudioData(data, player) { - // 检查头文件获取音频类型,仅检查前256个字节 - const toCheck = data.slice(0, 256); - const type = checkAudioType(data); - if (type === "") { - console.error( - "Unknown audio type. Header: '" + [...toCheck] - .map((v) => v.toString().padStart(2, "0")) - .join(" ") - .toUpperCase() + - "'" - ); - return null; - } - if (isAudioSupport(type)) { - if (data.buffer instanceof ArrayBuffer) { - return player.ac.decodeAudioData(data.buffer); - } else { - return null; - } - } else { - const Decoder = this.decoderMap.get(type); - if (!Decoder) { - return null; - } else { - const decoder = new Decoder(); - await decoder.create(); - const decodedData = await decoder.decode(data); - if (!decodedData) return null; - const buffer = player.ac.createBuffer( - decodedData.channelData.length, - decodedData.channelData[0].length, - decodedData.sampleRate - ); - decodedData.channelData.forEach((v, i) => { - buffer.copyToChannel(v, i); - }); - decoder.destroy(); - return buffer; - } - } - } - } - - class VorbisDecoder { - /** - * 创建音频解码器 - */ - async create() { - this.decoder = new OggVorbisDecoderWebWorker(); - await this.decoder.ready; - } - /** - * 摧毁这个解码器 - */ - destroy() { - this.decoder?.free(); - } - /** - * 解码流数据 - * @param data 流数据 - */ - - async decode(data) { - return this.decoder?.decode(data); - } - /** - * 解码整个文件 - * @param data 文件数据 - */ - async decodeAll(data) { - return this.decoder?.decodeFile(data); - } - /** - * 当音频解码完成后,会调用此函数,需要返回之前还未解析或未返回的音频数据。调用后,该解码器将不会被再次使用 - */ - async flush() { - return this.decoder?.flush(); - } - } - - class OpusDecoder { - /** - * 创建音频解码器 - */ - async create() { - this.decoder = new OggOpusDecoderWebWorker(); - await this.decoder.ready; - } - /** - * 摧毁这个解码器 - */ - destroy() { - this.decoder?.free(); - } - /** - * 解码流数据 - * @param data 流数据 - */ - async decode(data) { - return this.decoder?.decode(data); - } - /** - * 解码整个文件 - * @param data 文件数据 - */ - async decodeAll(data) { - return this.decoder?.decodeFile(data); - } - /** - * 当音频解码完成后,会调用此函数,需要返回之前还未解析或未返回的音频数据。调用后,该解码器将不会被再次使用 - */ - async flush() { - return await this.decoder?.flush(); - } - } - const mimeTypeMap = { - [AudioType.Aac]: "audio/aac", - [AudioType.Flac]: "audio/flac", - [AudioType.Mp3]: "audio/mpeg", - [AudioType.Ogg]: "application/ogg", - [AudioType.Opus]: "application/ogg", - [AudioType.Wav]: "application/ogg", - }; - - function isOggPage(data) { - return !isNil(data.isFirstPage); - } - class AudioStreamSource { - - constructor(context) { - this.output = context.createBufferSource(); - /** 是否已经完全加载完毕 */ - this.loaded = false; - /** 是否正在播放 */ - this.playing = false; - /** 已经缓冲了多长时间,如果缓冲完那么跟歌曲时长一致 */ - this.buffered = 0; - /** 已经缓冲的采样点数量 */ - this.bufferedSamples = 0; - /** 歌曲时长,加载完毕之前保持为 0 */ - this.duration = 0; - /** 在流传输阶段,至少缓冲多长时间的音频之后才开始播放,单位秒 */ - this.bufferPlayDuration = 1; - /** 音频的采样率,未成功解析出之前保持为 0 */ - this.sampleRate = 0; - //是否循环播放 - this.loop = false; - /** 上一次播放是从何时开始的 */ - this.lastStartWhen = 0; - /** 开始播放时刻 */ - this.lastStartTime = 0; - /** 上一次播放的缓存长度 */ - this.lastBufferSamples = 0; - - /** 是否已经获取到头文件 */ - this.headerRecieved = false; - /** 音频类型 */ - this.audioType = ""; - /** 每多长时间组成一个缓存 Float32Array */ - this.bufferChunkSize = 10; - /** 缓存音频数据,每 bufferChunkSize 秒钟组成一个 Float32Array,用于流式解码 */ - this.audioData = []; - - this.errored = false; - this.ac = context; - } - /** 当前已经播放了多长时间 */ - get currentTime() { - return this.ac.currentTime - this.lastStartTime + this.lastStartWhen; - } - /** - * 设置每个缓存数据的大小,默认为10秒钟一个缓存数据 - * @param size 每个缓存数据的时长,单位秒 - */ - setChunkSize(size) { - if (this.controller?.loading || this.loaded) return; - this.bufferChunkSize = size; - } - - piped(controller) { - this.controller = controller; - } - - async pump(data, done) { - if (!data || this.errored) return; - if (!this.headerRecieved) { - // 检查头文件获取音频类型,仅检查前256个字节 - const toCheck = data.slice(0, 256); - this.audioType = checkAudioType(data); - if (!this.audioType) { - console.error( - "Unknown audio type. Header: '" + [...toCheck] - .map((v) => v.toString(16).padStart(2, "0")) - .join(" ") - .toUpperCase() + - "'" - ); - return; - } - // 创建解码器 - const Decoder = AudioDecoder.decoderMap.get(this.audioType); - if (!Decoder) { - this.errored = true; - console.error( - "Cannot decode stream source type of '" + - this.audioType + - "', since there is no registered decoder for that type." - ); - return Promise.reject( - `Cannot decode stream source type of '${this.audioType}', since there is no registered decoder for that type.` - ); - } - this.decoder = new Decoder(); - // 创建数据解析器 - const mime = mimeTypeMap[this.audioType]; - const parser = new CodecParser(mime); - this.parser = parser; - await this.decoder.create(); - this.headerRecieved = true; - } - - const decoder = this.decoder; - const parser = this.parser; - if (!decoder || !parser) { - this.errored = true; - return Promise.reject( - "No parser or decoder attached in this AudioStreamSource" - ); - } - - await this.decodeData(data, decoder, parser); - if (done) await this.decodeFlushData(decoder, parser); - this.checkBufferedPlay(); - } - - /** - * 检查采样率,如果还未解析出采样率,那么将设置采样率,如果当前采样率与之前不同,那么发出警告 - */ - checkSampleRate(info) { - for (const one of info) { - const frame = isOggPage(one) ? one.codecFrames[0] : one; - if (frame) { - const rate = frame.header.sampleRate; - if (this.sampleRate === 0) { - this.sampleRate = rate; - break; - } else { - if (rate !== this.sampleRate) { - console.warn("Sample rate in stream audio must be constant."); - } - } - } - } - } - - /** - * 解析音频数据 - */ - async decodeData(data, decoder, parser) { - // 解析音频数据 - const audioData = await decoder.decode(data); - if (!audioData) return; - // @ts-expect-error 库类型声明错误 - const audioInfo = [...parser.parseChunk(data)]; - - // 检查采样率 - this.checkSampleRate(audioInfo); - // 追加音频数据 - this.appendDecodedData(audioData, audioInfo); - } - - /** - * 解码剩余数据 - */ - async decodeFlushData(decoder, parser) { - const audioData = await decoder.flush(); - if (!audioData) return; - // @ts-expect-error 库类型声明错误 - const audioInfo = [...parser.flush()]; - - this.checkSampleRate(audioInfo); - this.appendDecodedData(audioData, audioInfo); - } - - /** - * 追加音频数据 - */ - appendDecodedData(data, info) { - const channels = data.channelData.length; - if (channels === 0) return; - if (this.audioData.length !== channels) { - this.audioData = []; - for (let i = 0; i < channels; i++) { - this.audioData.push([]); - } - } - // 计算出应该放在哪 - const chunk = this.sampleRate * this.bufferChunkSize; - const sampled = this.bufferedSamples; - const pushIndex = Math.floor(sampled / chunk); - const bufferIndex = sampled % chunk; - const dataLength = data.channelData[0].length; - let buffered = 0; - let nowIndex = pushIndex; - let toBuffer = bufferIndex; - while (buffered < dataLength) { - const rest = toBuffer !== 0 ? chunk - bufferIndex : chunk; - - for (let i = 0; i < channels; i++) { - const audioData = this.audioData[i]; - if (!audioData[nowIndex]) { - audioData.push(new Float32Array(chunk)); - } - const toPush = data.channelData[i].slice(buffered, buffered + rest); - - audioData[nowIndex].set(toPush, toBuffer); - } - buffered += rest; - nowIndex++; - toBuffer = 0; - } - - this.buffered += - info.reduce((prev, curr) => prev + curr.duration, 0) / 1000; - this.bufferedSamples += info.reduce( - (prev, curr) => prev + curr.samples, - 0 - ); - } - - /** - * 检查已缓冲内容,并在未开始播放时播放 - */ - checkBufferedPlay() { - if (this.playing || this.sampleRate === 0) return; - const played = this.lastBufferSamples / this.sampleRate; - const dt = this.buffered - played; - if (this.loaded) { - this.playAudio(played); - return; - } - if (dt < this.bufferPlayDuration) return; - - this.lastBufferSamples = this.bufferedSamples; - // 需要播放 - this.mergeBuffers(); - if (!this.buffer) return; - if (this.playing) this.output.stop(); - this.createSourceNode(this.buffer); - this.output.loop = false; - this.output.start(0, played); - this.lastStartTime = this.ac.currentTime; - this.playing = true; - this.output.addEventListener("ended", () => { - this.playing = false; - this.checkBufferedPlay(); - }); - } - - mergeBuffers() { - const buffer = this.ac.createBuffer( - this.audioData.length, - this.bufferedSamples, - this.sampleRate - ); - const chunk = this.sampleRate * this.bufferChunkSize; - const bufferedChunks = Math.floor(this.bufferedSamples / chunk); - const restLength = this.bufferedSamples % chunk; - for (let i = 0; i < this.audioData.length; i++) { - const audio = this.audioData[i]; - const data = new Float32Array(this.bufferedSamples); - for (let j = 0; j < bufferedChunks; j++) { - data.set(audio[j], chunk * j); - } - if (restLength !== 0) { - data.set( - audio[bufferedChunks].slice(0, restLength), - chunk * bufferedChunks - ); - } - - buffer.copyToChannel(data, i, 0); - } - this.buffer = buffer; - } - - async start() { - delete this.buffer; - this.headerRecieved = false; - this.audioType = ""; - this.errored = false; - this.buffered = 0; - this.sampleRate = 0; - this.bufferedSamples = 0; - this.duration = 0; - this.loaded = false; - if (this.playing) this.output.stop(); - this.playing = false; - this.lastStartTime = this.ac.currentTime; - } - - end(done, reason) { - if (done && this.buffer) { - this.loaded = true; - delete this.controller; - this.mergeBuffers(); - - this.duration = this.buffered; - this.audioData = []; - this.decoder?.destroy(); - delete this.decoder; - delete this.parser; - } else { - console.warn( - "Unexpected end when loading stream audio, reason: '" + - (reason ?? "") + - "'" - ); - } - } - - playAudio(when) { - if (!this.buffer) return; - this.lastStartTime = this.ac.currentTime; - if (this.playing) this.output.stop(); - if (this.route.status !== AudioStatus.Playing) { - this.route.status = AudioStatus.Playing; - } - this.createSourceNode(this.buffer); - this.output.start(0, when); - this.playing = true; - - this.output.addEventListener("ended", () => { - this.playing = false; - if (this.route.status === AudioStatus.Playing) { - this.route.status = AudioStatus.Stoped; - } - if (this.loop && !this.output.loop) this.play(0); - }); - } - /** - * 开始播放这个音频源 - */ - play(when) { - if (this.playing || this.errored) return; - if (this.loaded && this.buffer) { - this.playing = true; - this.playAudio(when); - } else { - this.controller?.start(); - } - } - - createSourceNode(buffer) { - if (!this.target) return; - const node = this.ac.createBufferSource(); - node.buffer = buffer; - if (this.playing) this.output.stop(); - this.playing = false; - this.output = node; - node.connect(this.target.input); - node.loop = this.loop; - } - /** - * 停止播放这个音频源 - * @returns 音频暂停的时刻 number - */ - stop() { - if (this.playing) this.output.stop(); - this.playing = false; - return this.ac.currentTime - this.lastStartTime; - } - /** - * 连接到音频路由图上,每次调用播放的时候都会执行一次 - * @param target 连接至的目标 IAudioInput - */ - connect(target) { - this.target = target; - } - /** - * 设置是否循环播放 - * @param loop 是否循环 boolean) - */ - setLoop(loop) { - this.loop = loop; - } - } - class AudioElementSource { - - constructor(context) { - const audio = new Audio(); - audio.preload = "none"; - this.output = context.createMediaElementSource(audio); - this.audio = audio; - this.ac = context; - audio.addEventListener("play", () => { - this.playing = true; - if (this.route.status !== AudioStatus.Playing) { - this.route.status = AudioStatus.Playing; - } - }); - audio.addEventListener("ended", () => { - this.playing = false; - if (this.route.status === AudioStatus.Playing) { - this.route.status = AudioStatus.Stoped; - } - }); - } - get duration() { - return this.audio.duration; - } - get currentTime() { - return this.audio.currentTime; - } - /** - * 设置音频源的路径 - * @param url 音频路径 - */ - setSource(url) { - this.audio.src = url; - } - - play(when = 0) { - if (this.playing) return; - this.audio.currentTime = when; - this.audio.play(); - } - - stop() { - this.audio.pause(); - this.playing = false; - if (this.route.status === AudioStatus.Playing) { - this.route.status = AudioStatus.Stoped; - } - return this.audio.currentTime; - } - - connect(target) { - this.output.connect(target.input); - } - - setLoop(loop) { - this.audio.loop = loop; - } - } - class AudioBufferSource { - - constructor(context) { - this.output = context.createBufferSource(); - /** 是否循环 */ - this.loop = false; - /** 上一次播放是从何时开始的 */ - this.lastStartWhen = 0; - /** 播放开始时刻 */ - this.lastStartTime = 0; - this.duration = 0; - this.ac = context; - } - get currentTime() { - return this.ac.currentTime - this.lastStartTime + this.lastStartWhen; - } - - /** - * 设置音频源数据 - * @param buffer 音频源,可以是未解析的 ArrayBuffer,也可以是已解析的 AudioBuffer - */ - async setBuffer(buffer) { - if (buffer instanceof ArrayBuffer) { - this.buffer = await this.ac.decodeAudioData(buffer); - } else { - this.buffer = buffer; - } - this.duration = this.buffer.duration; - } - - play(when) { - if (this.playing || !this.buffer) return; - this.playing = true; - this.lastStartTime = this.ac.currentTime; - if (this.route.status !== AudioStatus.Playing) { - this.route.status = AudioStatus.Playing; - } - this.createSourceNode(this.buffer); - this.output.start(0, when); - this.output.addEventListener("ended", () => { - this.playing = false; - if (this.route.status === AudioStatus.Playing) { - this.route.status = AudioStatus.Stoped; - } - if (this.loop && !this.output.loop) this.play(0); - }); - } - - createSourceNode(buffer) { - if (!this.target) return; - const node = this.ac.createBufferSource(); - node.buffer = buffer; - this.output = node; - node.connect(this.target.input); - node.loop = this.loop; - } - - stop() { - this.output.stop(); - return this.ac.currentTime - this.lastStartTime; - } - - connect(target) { - this.target = target; - } - - setLoop(loop) { - this.loop = loop; - } - } - class AudioPlayer { - constructor() { - /** 音频播放上下文 */ - this.ac = new AudioContext(); - /** 音量节点 */ - this.gain = this.ac.createGain(); - this.gain.connect(this.ac.destination); - this.audioRoutes = new Map(); - } - /** - * 解码音频数据 - * @param data 音频数据 - */ - decodeAudioData(data) { - return AudioDecoder.decodeAudioData(data, this); - } - /** - * 设置音量 - * @param volume 音量 - */ - setVolume(volume) { - this.gain.gain.value = volume; - } - - /** - * 获取音量 - */ - getVolume() { - return this.gain.gain.value; - } - - /** - * 创建一个音频源 - * @param Source 音频源类 - */ - createSource(Source) { - return new Source(this.ac); - } - - /** - * 创建一个兼容流式音频源,可以与流式加载相结合,主要用于处理 opus ogg 不兼容的情况 - */ - createStreamSource() { - return new AudioStreamSource(this.ac); - } - - /** - * 创建一个通过 audio 元素播放的音频源 - */ - createElementSource() { - return new AudioElementSource(this.ac); - } - - /** - * 创建一个通过 AudioBuffer 播放的音频源 - */ - createBufferSource() { - return new AudioBufferSource(this.ac); - } - - /** - * 获取音频目的地 - */ - getDestination() { - return this.gain; - } - - /** - * 创建一个音频效果器 - * @param Effect 效果器类 - */ - createEffect(Effect) { - return new Effect(this.ac); - } - - /** - * 创建一个修改音量的效果器 - * ```txt - * |----------| - * Input ----> | GainNode | ----> Output - * |----------| - * ``` - */ - createVolumeEffect() { - return new VolumeEffect(this.ac); - } - - /** - * 创建一个立体声效果器 - * ```txt - * |------------| - * Input ----> | PannerNode | ----> Output - * |------------| - * ``` - */ - createStereoEffect() { - return new StereoEffect(this.ac); - } - - /** - * 创建一个修改单个声道音量的效果器 - * ```txt - * |----------| - * -> | GainNode | \ - * |--------------| / |----------| -> |------------| - * Input ----> | SplitterNode | ...... | MergerNode | ----> Output - * |--------------| \ |----------| -> |------------| - * -> | GainNode | / - * |----------| - * ``` - */ - createChannelVolumeEffect() { - return new ChannelVolumeEffect(this.ac); - } - - /** - * 创建一个延迟效果器 - * |-----------| - * Input ----> | DelayNode | ----> Output - * |-----------| - */ - createDelay() { - return new DelayEffect(this.ac); - } - - /** - * 创建一个回声效果器 - * ```txt - * |----------| - * Input ----> | GainNode | ----> Output - * ^ |----------| | - * | | - * | |------------| ↓ - * |-- | Delay Node | <-- - * |------------| - * ``` - */ - createEchoEffect() { - return new EchoEffect(this.ac); - } - - /** - * 创建一个音频播放路由 - * @param source 音频源 - */ - createRoute(source) { - return new AudioRoute(source, this); - } - - /** - * 添加一个音频播放路由,可以直接被播放 - * @param id 这个音频播放路由的名称 - * @param route 音频播放路由对象 - */ - addRoute(id, route) { - if (!this.audioRoutes) this.audioRoutes = new Map(); - if (this.audioRoutes.has(id)) { - console.warn( - "Audio route with id of '" + - id + - "' has already existed. New route will override old route." - ); - } - this.audioRoutes.set(id, route); - } - - /** - * 根据名称获取音频播放路由对象 - * @param id 音频播放路由的名称 - */ - getRoute(id) { - return this.audioRoutes.get(id); - } - /** - * 移除一个音频播放路由 - * @param id 要移除的播放路由的名称 - */ - removeRoute(id) { - this.audioRoutes.delete(id); - } - /** - * 播放音频 - * @param id 音频名称 - * @param when 从音频的哪个位置开始播放,单位秒 - */ - play(id, when) { - const route = this.getRoute(id); - if (!route) { - console.warn( - "Cannot play audio route '" + - id + - "', since there is not added route named it." - ); - return; - } - - route.play(when); - } - - /** - * 暂停音频播放 - * @param id 音频名称 - * @returns 当音乐真正停止时兑现 - */ - pause(id) { - const route = this.getRoute(id); - if (!route) { - console.warn( - "Cannot pause audio route '" + - id + - "', since there is not added route named it." - ); - return; - } - return route.pause(); - } - - /** - * 停止音频播放 - * @param id 音频名称 - * @returns 当音乐真正停止时兑现 - */ - stop(id) { - const route = this.getRoute(id); - if (!route) { - console.warn( - "Cannot stop audio route '" + - id + - "', since there is not added route named it." - ); - return; - } - return route.stop(); - } - - /** - * 继续音频播放 - * @param id 音频名称 - */ - resume(id) { - const route = this.getRoute(id); - if (!route) { - console.warn( - "Cannot pause audio route '" + - id + - "', since there is not added route named it." - ); - return; - } - route.resume(); - } - - /** - * 设置听者位置,x正方向水平向右,y正方向垂直于地面向上,z正方向垂直屏幕远离用户 - * @param x 位置x坐标 - * @param y 位置y坐标 - * @param z 位置z坐标 - */ - setListenerPosition(x, y, z) { - const listener = this.ac.listener; - listener.positionX.value = x; - listener.positionY.value = y; - listener.positionZ.value = z; - } - - /** - * 设置听者朝向,x正方向水平向右,y正方向垂直于地面向上,z正方向垂直屏幕远离用户 - * @param x 朝向x坐标 - * @param y 朝向y坐标 - * @param z 朝向z坐标 - */ - setListenerOrientation(x, y, z) { - const listener = this.ac.listener; - listener.forwardX.value = x; - listener.forwardY.value = y; - listener.forwardZ.value = z; - } - - /** - * 设置听者头顶朝向,x正方向水平向右,y正方向垂直于地面向上,z正方向垂直屏幕远离用户 - * @param x 头顶朝向x坐标 - * @param y 头顶朝向y坐标 - * @param z 头顶朝向z坐标 - */ - setListenerUp(x, y, z) { - const listener = this.ac.listener; - listener.upX.value = x; - listener.upY.value = y; - listener.upZ.value = z; - } - } - class AudioRoute { - constructor(source, player) { - source.route = this; - this.output = source.output; - - /** 效果器路由图 */ - this.effectRoute = []; - - /** 结束时长,当音频暂停或停止时,会经过这么长时间之后才真正终止播放,期间可以做音频淡入淡出等效果 */ - this.endTime = 0; - /** 暂停时播放了多长时间 */ - this.pauseCurrentTime = 0; - /** 当前播放状态 */ - this.player = player; - this.status = AudioStatus.Stoped; - - this.shouldStop = false; - /** - * 每次暂停或停止时自增,用于判断当前正在处理的情况。 - * 假如暂停后很快播放,然后很快暂停,那么需要根据这个来判断实际是否应该执行暂停后操作 - */ - this.stopIdentifier = 0; - /** 暂停时刻 */ - this.pauseTime = 0; - this.source = source; - this.source.player = player; - } - /** 音频时长,单位秒 */ - get duration() { - return this.source.duration; - } - /** 当前播放了多长时间,单位秒 */ - get currentTime() { - if (this.status === AudioStatus.Paused) { - return this.pauseCurrentTime; - } else { - return this.source.currentTime; - } - } - set currentTime(time) { - this.source.stop(); - this.source.play(time); - } - /** - * 设置结束时间,暂停或停止时,会经过这么长时间才终止音频的播放,这期间可以做一下音频淡出的效果。 - * @param time 暂停或停止时,经过多长时间之后才会结束音频的播放 - */ - setEndTime(time) { - this.endTime = time; - } - - /** - * 当音频播放时执行的函数,可以用于音频淡入效果 - * @param fn 音频开始播放时执行的函数 - */ - onStart(fn) { - this.audioStartHook = fn; - } - - /** - * 当音频暂停或停止时执行的函数,可以用于音频淡出效果 - * @param fn 音频在暂停或停止时执行的函数,不填时表示取消这个钩子。 - * 包含两个参数,第一个参数是结束时长,第二个参数是当前音频播放路由对象 - */ - onEnd(fn) { - this.audioEndHook = fn; - } - - /** - * 开始播放这个音频 - * @param when 从音频的什么时候开始播放,单位秒 - */ - async play(when = 0) { - if (this.status === AudioStatus.Playing) return; - this.link(); - await this.player.ac.resume(); - if (this.effectRoute.length > 0) { - const first = this.effectRoute[0]; - this.source.connect(first); - const last = this.effectRoute.at(-1); - last.connect({ input: this.player.getDestination() }); - } else { - this.source.connect({ input: this.player.getDestination() }); - } - this.source.play(when); - this.status = AudioStatus.Playing; - this.pauseTime = 0; - this.audioStartHook?.(this); - this.startAllEffect(); - if (this.status !== AudioStatus.Playing) { - this.status = AudioStatus.Playing; - } - } - - /** - * 暂停音频播放 - */ - async pause() { - if (this.status !== AudioStatus.Playing) return; - this.status = AudioStatus.Pausing; - this.stopIdentifier++; - const identifier = this.stopIdentifier; - if (this.audioEndHook) { - this.audioEndHook(this.endTime, this); - await sleep(this.endTime); - } - if ( - this.status !== AudioStatus.Pausing || - this.stopIdentifier !== identifier - ) { - return; - } - this.pauseCurrentTime = this.source.currentTime; - const time = this.source.stop(); - this.pauseTime = time; - if (this.shouldStop) { - this.status = AudioStatus.Stoped; - this.endAllEffect(); - - this.shouldStop = false; - } else { - this.status = AudioStatus.Paused; - this.endAllEffect(); - } - this.endAllEffect(); - } - - /** - * 继续音频播放 - */ - resume() { - if (this.status === AudioStatus.Playing) return; - if ( - this.status === AudioStatus.Pausing || - this.status === AudioStatus.Stoping - ) { - this.audioStartHook?.(this); - - return; - } - if (this.status === AudioStatus.Paused) { - this.play(this.pauseTime); - } else { - this.play(0); - } - this.status = AudioStatus.Playing; - this.pauseTime = 0; - this.audioStartHook?.(this); - this.startAllEffect(); - } - - /** - * 停止音频播放 - */ - async stop() { - if (this.status !== AudioStatus.Playing) { - if (this.status === AudioStatus.Pausing) { - this.shouldStop = true; - } - return; - } - this.status = AudioStatus.Stoping; - this.stopIdentifier++; - const identifier = this.stopIdentifier; - if (this.audioEndHook) { - this.audioEndHook(this.endTime, this); - await sleep(this.endTime); - } - if ( - this.status !== AudioStatus.Stoping || - this.stopIdentifier !== identifier - ) { - return; - } - this.source.stop(); - this.status = AudioStatus.Stoped; - this.pauseTime = 0; - this.endAllEffect(); - } - - /** - * 添加效果器 - * @param effect 要添加的效果,可以是数组,表示一次添加多个 - * @param index 从哪个位置开始添加,如果大于数组长度,那么加到末尾,如果小于0,那么将会从后面往前数。默认添加到末尾 - */ - addEffect(effect, index) { - if (isNil(index)) { - if (effect instanceof Array) { - this.effectRoute.push(...effect); - } else { - this.effectRoute.push(effect); - } - } else { - if (effect instanceof Array) { - this.effectRoute.splice(index, 0, ...effect); - } else { - this.effectRoute.splice(index, 0, effect); - } - } - this.setOutput(); - if (this.source.playing) this.link(); - } - - /** - * 移除一个效果器 - * @param effect 要移除的效果 - */ - removeEffect(effect) { - const index = this.effectRoute.indexOf(effect); - if (index === -1) return; - this.effectRoute.splice(index, 1); - effect.disconnect(); - this.setOutput(); - if (this.source.playing) this.link(); - } - - setOutput() { - const effect = this.effectRoute.at(-1); - if (!effect) this.output = this.source.output; - else this.output = effect.output; - } - - /** - * 连接音频路由图 - */ - link() { - this.effectRoute.forEach((v) => v.disconnect()); - this.effectRoute.forEach((v, i) => { - const next = this.effectRoute[i + 1]; - if (next) { - v.connect(next); - } - }); - } - - startAllEffect() { - this.effectRoute.forEach((v) => v.start()); - } - - endAllEffect() { - this.effectRoute.forEach((v) => v.end()); - } - } - - const audioPlayer = new AudioPlayer(); - - class BgmController { - constructor(player) { - this.mainGain = player.createVolumeEffect(); - this.player = player; - /** bgm音频名称的前缀 */ - this.prefix = "bgms."; - /** 每个 bgm 的音量控制器 */ - this.gain = new Map(); - - /** 正在播放的 bgm */ - this.playingBgm = ""; - /** 是否正在播放 */ - this.playing = false; - - /** 是否已经启用 */ - this.enabled = true; - /** 是否屏蔽所有的音乐切换 */ - this.blocking = false; - /** 渐变时长 */ - this.transitionTime = 2000; - } - - /** - * 设置音频渐变时长 - * @param time 渐变时长 - */ - setTransitionTime(time) { - this.transitionTime = time; - for (const [, value] of this.gain) { - value.transition.time(time); - } - } - - /** - * 屏蔽音乐切换 - */ - blockChange() { - this.blocking = true; - } - - /** - * 取消屏蔽音乐切换 - */ - unblockChange() { - this.blocking = false; - } - - /** - * 设置总音量大小 - * @param volume 音量大小 - */ - setVolume(volume) { - this.mainGain.setVolume(volume); - this._volume = volume; - } - /** - * 获取总音量大小 - */ - getVolume() { - return this.mainGain.getVolume(); - } - /** - * 设置是否启用 - * @param enabled 是否启用 - */ - setEnabled(enabled) { - if (enabled) this.resume(); - else this.stop(); - this.enabled = enabled; - } - - /** - * 设置 bgm 音频名称的前缀 - */ - setPrefix(prefix) { - this.prefix = prefix; - } - - getId(name) { - return `${this.prefix}${name}`; - } - - /** - * 根据 bgm 名称获取其 AudioRoute 实例 - * @param id 音频名称 - */ - get(id) { - return this.player.getRoute(this.getId(id)); - } - - /** - * 添加一个 bgm - * @param id 要添加的 bgm 的名称 - * @param url 指定 bgm 的加载地址 - */ - addBgm(id, url = `project/bgms/${id}`) { - const type = guessTypeByExt(id); - if (!type) { - console.warn( - "Unknown audio extension name: '" + - id.split(".").slice(0, -1).join(".") + - "'" - ); - return; - } - const gain = this.player.createVolumeEffect(); - if (isAudioSupport(type)) { - const source = audioPlayer.createElementSource(); - source.setSource(url); - source.setLoop(true); - const route = new AudioRoute(source, audioPlayer); - route.addEffect([gain, this.mainGain]); - audioPlayer.addRoute(this.getId(id), route); - this.setTransition(id, route, gain); - } else { - const source = audioPlayer.createStreamSource(); - const stream = new StreamLoader(url); - stream.pipe(source); - source.setLoop(true); - const route = new AudioRoute(source, audioPlayer); - route.addEffect([gain, this.mainGain]); - audioPlayer.addRoute(this.getId(id), route); - this.setTransition(id, route, gain); - } - } - - /** - * 移除一个 bgm - * @param id 要移除的 bgm 的名称 - */ - removeBgm(id) { - this.player.removeRoute(this.getId(id)); - const gain = this.gain.get(id); - gain?.transition.ticker.destroy(); - this.gain.delete(id); - } - - setTransition(id, route, gain) { - const transition = new Transition(); - transition - .time(this.transitionTime) - .mode(linear()) - .transition("volume", 0); - - const tick = () => { - gain.setVolume(transition.value.volume); - }; - - /** - * @param expect 在结束时应该是正在播放还是停止 - */ - const setTick = async (expect) => { - transition.ticker.remove(tick); - transition.ticker.add(tick); - const identifier = route.stopIdentifier; - await sleep(this.transitionTime + 500); - if (route.status === expect && identifier === route.stopIdentifier) { - transition.ticker.remove(tick); - if (route.status === AudioStatus.Playing) { - gain.setVolume(1); - } else { - gain.setVolume(0); - } - } - }; - - route.onStart(async () => { - transition.transition("volume", 1); - setTick(AudioStatus.Playing); - }); - route.onEnd(() => { - transition.transition("volume", 0); - setTick(AudioStatus.Paused); - }); - route.setEndTime(this.transitionTime); - - this.gain.set(id, { effect: gain, transition }); - } - - /** - * 播放一个 bgm - * @param id 要播放的 bgm 名称 - */ - play(id, when) { - if (this.blocking) return; - if (id !== this.playingBgm && this.playingBgm) { - this.player.pause(this.getId(this.playingBgm)); - } - this.playingBgm = id; - if (!this.enabled) return; - this.player.play(this.getId(id), when); - this.playing = true; - } - - /** - * 继续当前的 bgm - */ - resume() { - if (this.blocking || !this.enabled || this.playing) return; - if (this.playingBgm) { - this.player.resume(this.getId(this.playingBgm)); - } - this.playing = true; - } - - /** - * 暂停当前的 bgm - */ - pause() { - if (this.blocking || !this.enabled) return; - if (this.playingBgm) { - this.player.pause(this.getId(this.playingBgm)); - } - this.playing = false; - } - - /** - * 停止当前的 bgm - */ - stop() { - if (this.blocking || !this.enabled) return; - if (this.playingBgm) { - this.player.stop(this.getId(this.playingBgm)); - } - this.playing = false; - } - } - const bgmController = new BgmController(audioPlayer); - - class SoundPlayer { - constructor(player) { - /** 每个音效的唯一标识符 */ - this.num = 0; - this.enabled = true; - this.gain = player.createVolumeEffect(); - /** 每个音效的数据 */ - this.buffer = new Map(); - /** 所有正在播放的音乐 */ - this.playing = new Set(); - this.player = player; - } - /** - * 设置是否启用音效 - * @param enabled 是否启用音效 - */ - setEnabled(enabled) { - if (!enabled) this.stopAllSounds(); - this.enabled = enabled; - } - - /** - * 设置音量大小 - * @param volume 音量大小 - */ - setVolume(volume) { - this.gain.setVolume(volume); - } - /** - * 获取音量大小 - */ - getVolume() { - return this.gain.getVolume(); - } - /** - * 添加一个音效 - * @param id 音效名称 - * @param data 音效的Uint8Array数据 - */ - async add(id, data) { - const buffer = await this.player.decodeAudioData(data); - if (!buffer) { - console.warn( - "Cannot decode sound '" + - id + - "', since audio file may not supported by 2.b." - ); - return; - } - this.buffer.set(id, buffer); - } - - /** - * 播放一个音效 - * @param id 音效名称 - * @param position 音频位置,[0, 0, 0]表示正中心,x轴指向水平向右,y轴指向水平向上,z轴指向竖直向上 - * @param orientation 音频朝向,[0, 1, 0]表示朝向前方 - */ - play(id, position = [0, 0, 0], orientation = [1, 0, 0]) { - if (!this.enabled || !id) return -1; - const buffer = this.buffer.get(id); - if (!buffer) { - console.warn( - "Cannot play sound '" + - id + - "', since there is no added data named it." - ); - return -1; - } - const soundNum = this.num++; - - const source = this.player.createBufferSource(); - source.setBuffer(buffer); - const route = this.player.createRoute(source); - const stereo = this.player.createStereoEffect(); - stereo.setPosition(position[0], position[1], position[2]); - stereo.setOrientation(orientation[0], orientation[1], orientation[2]); - route.addEffect([stereo, this.gain]); - this.player.addRoute(`sounds.${soundNum}`, route); - route.play(); - source.output.addEventListener("ended", () => { - this.playing.delete(soundNum); - }); - this.playing.add(soundNum); - return soundNum; - } - - /** - * 停止一个音效 - * @param num 音效的唯一 id - */ - stop(num) { - const id = `sounds.${num}`; - const route = this.player.getRoute(id); - if (route) { - route.stop(); - this.player.removeRoute(id); - this.playing.delete(num); - } - } - - /** - * 停止播放所有音效 - */ - stopAllSounds() { - this.playing.forEach((v) => { - const id = `sounds.${v}`; - const route = this.player.getRoute(id); - if (route) { - route.stop(); - this.player.removeRoute(id); - } - }); - this.playing.clear(); - } - } - const soundPlayer = new SoundPlayer(audioPlayer); - - function loadAllBgm() { - const data = data_a1e2fb4a_e986_4524_b0da_9b7ba7c0874d; - for (const bgm of data.main.bgms) { - bgmController.addBgm(bgm); - } - } - loadAllBgm(); - AudioDecoder.registerDecoder(AudioType.Ogg, VorbisDecoder); - AudioDecoder.registerDecoder(AudioType.Opus, OpusDecoder); - - core.plugin.audioSystem = { - AudioType, - AudioDecoder, - AudioStatus, - checkAudioType, - isAudioSupport, - audioPlayer, - soundPlayer, - bgmController, - guessTypeByExt, - BgmController, - SoundPlayer, - EchoEffect, - DelayEffect, - ChannelVolumeEffect, - VolumeEffect, - StereoEffect, - AudioEffect, - AudioPlayer, - AudioRoute, - AudioStreamSource, - AudioElementSource, - AudioBufferSource, - loadAllBgm, - StreamLoader, - }; - //bgm相关复写 - control.prototype.playBgm = (bgm, when) => { - bgm = core.getMappedName(bgm); - bgmController.play(bgm, when); - core.setMusicBtn(); - }; - control.prototype.pauseBgm = () => { - bgmController.pause(); - core.setMusicBtn(); - }; - - control.prototype.resumeBgm = function () { - bgmController.resume(); - core.setMusicBtn(); - }; - control.prototype.checkBgm = function () { - core.playBgm(bgmController.playingBgm || main.startBgm); - }; - control.prototype.triggerBgm = function () { - core.musicStatus.bgmStatus = !core.musicStatus.bgmStatus; - if (bgmController.playing) bgmController.pause(); - else bgmController.resume(); - core.setMusicBtn(); - core.setLocalStorage("bgmStatus", core.musicStatus.bgmStatus); - }; - //sound相关复写 - control.prototype.playSound = function ( - sound, - _pitch, - callback, - position, - orientation - ) { - if (main.mode != "play" || !core.musicStatus.soundStatus) return; - const name = core.getMappedName(sound); - const num = soundPlayer.play(name, position, orientation); - const route = audioPlayer.getRoute(`sounds.${num}`); - if (!route) { - callback?.(); - return -1; - } else { - sleep(route.duration * 1000).then(() => callback?.()); - return num; - } - }; - control.prototype.stopSound = function (id) { - if (isNil(id)) { - soundPlayer.stopAllSounds(); - } else { - soundPlayer.stop(id); - } - }; - control.prototype.getPlayingSounds = function () { - return [...soundPlayer.playing]; - }; - //sound加载复写 - loader.prototype._loadOneSound_decodeData = function (name, data) { - if (data instanceof Blob) { - var blobReader = new zip.BlobReader(data); - blobReader.init(function () { - blobReader.readUint8Array(0, blobReader.size, function (uint8) { - //core.loader._loadOneSound_decodeData(name, uint8.buffer); - soundPlayer.add(name, uint8); - }); - }); - return; - } - if (data instanceof ArrayBuffer) { - const uint8 = new Uint8Array(data); - soundPlayer.add(name, uint8); - } - }; - //音量控制复写 - soundPlayer.setVolume( - core.musicStatus.userVolume * core.musicStatus.designVolume - ); - bgmController.setVolume( - core.musicStatus.userVolume * core.musicStatus.designVolume - ); - actions.prototype._clickSwitchs_sounds_userVolume = function (delta) { - var value = Math.round(Math.sqrt(100 * core.musicStatus.userVolume)); - if (value == 0 && delta < 0) return; - core.musicStatus.userVolume = core.clamp( - Math.pow(value + delta, 2) / 100, - 0, - 1 - ); - //audioContext 音效 不受designVolume 影响 - if (core.musicStatus.gainNode != null) - core.musicStatus.gainNode.gain.value = core.musicStatus.userVolume; - soundPlayer.setVolume( - core.musicStatus.userVolume * core.musicStatus.designVolume - ); - bgmController.setVolume( - core.musicStatus.userVolume * core.musicStatus.designVolume - ); - core.setLocalStorage("userVolume", core.musicStatus.userVolume); - core.playSound("确定"); - core.ui._drawSwitchs_sounds(); - }; -}, - "怪物碎裂特效": function () { - // 在此增加新插件 - // -------------------- 安装说明 -------------------- // - // 先安装两个在插件简介中说明的前置插件 - // 然后再将本插件复制到插件编写中即可 - // 插件自带一个打怪后显示碎裂特效的功能 - // -------------------- 使用说明 -------------------- // - /* - - 本插件的核心是一个名为 applyFragWith 的函数,打怪后的碎裂特效也是由它执行的。 - 我们来说明一下这个函数的使用方式。 - 1. 引入 - 你可以使用 const { applyFragWith } = core.plugin.frag; 在任何地方来引入这个函数。 - - 2. 函数的参数 - 该函数有三个参数,分别是canvas, length, time。 - 其中,第一个参数意思是,在执行碎裂时,其内容由该画布决定,这个特效并不会修改传入的画布,因此原画布的内容会依然存在 - 第二个参数指的是每个碎片的边长,虽然原则上每个碎片都是正方形,但边缘一周的碎片可能不是正方形,但中间的碎片一定是 - 第三个参数指的是这个特效要执行多长时间 - 第四个参数是一个对象,包含四种配置量,均为可选,分别是maxMoveLength,moveFlush,maxRotate,fragTiming - 当这些不存在时会默认取同名的常量作为默认值。这些值有什么用可以看下面的常量注释。 - - 3. 函数的返回值 - 这个函数会返回一个特效控制器对象,这个控制器共有三个属性。 - animation: 指的是当前特效的高级动画对象 - onEnd: 一个Promise,当这个特效执行完毕后会被 fulfilled - canvas: 特效所显示的画布。这个画布不会自动部署到样板中,需要你手动使用appendChild来部署,具体可参考打怪后碎裂的样例 - - 4. 修改一些常量 - 在下面有四个有注释的常量MAX_MOVE_LENGTH ~ FRAG_TIMING,你可以根据你自己的需要来更改。 - - */ - - if (main.replayChecking) return (core.plugin.frag = {}); - - const { Animation, linear, sleep } = core.plugin.animate; - const { has } = core.plugin.utils; - - /** 最大移动距离,最终位置距离中心的距离变成原来的几倍 */ - const MAX_MOVE_LENGTH = 1.15; - /** 移动距离波动,在最大移动距离的基础上加上多少倍距离的波动距离 */ - const MOVE_FLUSH = 0.7; - /** 最大旋转角,单位是弧度,每个碎片都会有自己的旋转程度,是随机的 */ - const MAX_ROTATE = 0.5; - /** 碎裂动画的速率曲线函数 */ - const FRAG_TIMING = linear(); - + // 将__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 {HTMLCanvasElement} canvas 要执行特效的画布 - * @param {number} length 切分成的碎片的边长,碎片为正方形 - * @param {number} time 特效持续时长 - * @returns 返回一个碎裂特效控制器,是一个对象,详见开头的使用注释 + * 检查一种音频类型是否能被播放 + * @param type 音频类型 AudioType */ - function applyFragWith(canvas, length = 4, time = 2000, config = {}) { - // 先切分图片 - const imgs = splitCanvas(canvas, length); - const cx = canvas.width / 2; - const cy = canvas.height / 2; - - let maxX = 0; - let maxY = 0; - const toMove = imgs.map((v) => { - const centerX = v.x + v.canvas.width / 2; - const centerY = v.y + v.canvas.height / 2; - const onX = centerX === cx; - const onY = centerY === cy; - const mml = config.maxMoveLength ?? MAX_MOVE_LENGTH; - const mf = config.moveFlush ?? MOVE_FLUSH; - const rate = mml - 1 + Math.random() ** 3 * mf; - let endX = onY ? 0 : (centerX - cx) * rate; - let endY = onX ? 0 : (centerY - cy) * rate; - const mx = Math.abs(endX + centerX) + Math.abs(v.canvas.width); - const my = Math.abs(endY + centerY) + Math.abs(v.canvas.height); - if (mx > maxX) maxX = mx; - if (my > maxY) maxY = my; - const r = config.maxRotate ?? MAX_ROTATE; - const endRad = Math.random() * r * 2 - r; - - return { - deltaX: endX, - deltaY: endY, - endRad, - x: centerX, - y: centerY, - canvas: v.canvas, - }; - }); - - // 再执行动画 - const frag = document.createElement("canvas"); - const ctx = frag.getContext("2d"); - const ani = new Animation(); - ani.register("rate", 0); - const ft = config.fragTiming ?? FRAG_TIMING; - ani.absolute().time(time).mode(ft).apply("rate", 1); - frag.width = maxX * 2; - frag.height = maxY * 2; - ctx.save(); - const dw = maxX - canvas.width / 2; - const dh = maxY - canvas.height / 2; - - const fragFn = () => { - const rate = ani.value.rate; - const opacity = 1 - rate; - ctx.globalAlpha = opacity; - ctx.clearRect(0, 0, frag.width, frag.height); - toMove.forEach((v) => { - ctx.save(); - const nx = v.deltaX * rate; - const ny = v.deltaY * rate; - const rotate = v.endRad * rate; - - ctx.translate(nx + v.x + dw, ny + v.y + dh); - ctx.rotate(rotate); - ctx.drawImage( - v.canvas, - nx - v.canvas.width / 2, - ny - v.canvas.height / 2 - ); - ctx.restore(); - }); - }; - const onEnd = () => {}; - ani.ticker.add(fragFn); - - return makeFragManager(frag, ani, time, onEnd); - } - - function makeFragManager(canvas, ani, time, onEnd) { - const promise = sleep(time + 50); - - return { - animation: ani, - - onEnd: promise.then(() => { - ani.ticker.destroy(); - onEnd(); - }), - canvas, - }; - } - - function withImage(image, sx, sy, sw, sh) { - const canvas = document.createElement("canvas"); - const ctx = canvas.getContext("2d"); - canvas.width = sw; - canvas.height = sh; - ctx.drawImage(image, sx, sy, sw, sh, 0, 0, sw, sh); - return { canvas, x: sx, y: sy }; - } - - /** - * 切分画布 - * @param canvas 要被切分的画布 - * @param l 切分小块的边长 - */ - function splitCanvas(canvas, l) { - if (canvas.width / l < 2 || canvas.height / l < 2) { - console.warn("切分画布要求切分边长大于等于画布长宽的一半!"); - return []; + 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 w = canvas.width; - const h = canvas.height; - const numX = Math.floor(w / l); - const numY = Math.floor(h / l); - const rw = (w - numX * l) / 2; - const rh = (h - numY * l) / 2; + } - const res = []; + const typeMap = new Map([ + ["ogg", AudioType.Ogg], + ["mp3", AudioType.Mp3], + ["wav", AudioType.Wav], + ["flac", AudioType.Flac], + ["opus", AudioType.Opus], + ["aac", AudioType.Aac], + ]); - if (rw > 0) { - if (rh > 0) { - res.push( - withImage(canvas, 0, 0, rw, rh), - withImage(canvas, 0, canvas.height - rh, rw, rh), - withImage(canvas, canvas.width - rw, 0, rw, rh), - withImage(canvas, canvas.width - rw, canvas.height - rh, rw, rh) - ); - } - for (const x of [0, canvas.width - rw]) { - for (let ny = 0; ny < numY; ny++) { - res.push(withImage(canvas, x, rh + l * ny, rw, l)); + /** + * 根据文件名拓展猜测其类型 + * @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); } } } - if (rh > 0) { - for (const y of [0, canvas.height - rh]) { - for (let nx = 0; nx < numX; nx++) { - res.push(withImage(canvas, rw + l * nx, y, l, rh)); + } + + class StereoEffect extends AudioEffect { + constructor(ac) { + super(ac); + const panner = ac.createPanner(); + this.input = panner; + this.output = panner; + } + + /** + * 设置音频朝向,x正方形水平向右,y正方形垂直于地面向上,z正方向垂直屏幕远离用户 + * @param x 朝向x坐标 Number + * @param y 朝向y坐标 Number + * @param z 朝向z坐标 Number + */ + setOrientation(x, y, z) { + this.output.orientationX.value = x; + this.output.orientationY.value = y; + this.output.orientationZ.value = z; + } + /** + * 设置音频位置,x正方形水平向右,y正方形垂直于地面向上,z正方向垂直屏幕远离用户 + * @param x 位置x坐标 Number + * @param y 位置y坐标 Number + * @param z 位置z坐标 Number + */ + setPosition(x, y, z) { + this.output.positionX.value = x; + this.output.positionY.value = y; + this.output.positionZ.value = z; + } + end() {} + + start() {} + } + class VolumeEffect extends AudioEffect { + constructor(ac) { + super(ac); + const gain = ac.createGain(); + this.input = gain; + this.output = gain; + } + + /** + * 设置音量大小 + * @param volume 音量大小 Number + */ + setVolume(volume) { + this.output.gain.value = volume; + } + + /** + * 获取音量大小 Number + */ + getVolume() { + return this.output.gain.value; + } + + end() {} + + start() {} + } + class ChannelVolumeEffect extends AudioEffect { + /** 所有的音量控制节点 */ + + constructor(ac) { + super(ac); + /** 所有的音量控制节点 */ + this.gain = []; + const splitter = ac.createChannelSplitter(); + const merger = ac.createChannelMerger(); + this.output = merger; + this.input = splitter; + for (let i = 0; i < 6; i++) { + const gain = ac.createGain(); + splitter.connect(gain, i); + gain.connect(merger, 0, i); + this.gain.push(gain); + } + } + + /** + * 设置某个声道的音量大小 + * @param channel 要设置的声道,可填0-5 Number + * @param volume 这个声道的音量大小 Number + */ + setVolume(channel, volume) { + if (!this.gain[channel]) return; + this.gain[channel].gain.value = volume; + } + + /** + * 获取某个声道的音量大小,可填0-5 + * @param channel 要获取的声道 Number + */ + getVolume(channel) { + if (!this.gain[channel]) return 0; + return this.gain[channel].gain.value; + } + + end() {} + + start() {} + } + class DelayEffect extends AudioEffect { + constructor(ac) { + super(ac); + + const delay = ac.createDelay(); + this.input = delay; + this.output = delay; + } + + /** + * 设置延迟时长 + * @param delay 延迟时长,单位秒 Number + */ + setDelay(delay) { + this.output.delayTime.value = delay; + } + + /** + * 获取延迟时长 + */ + getDelay() { + return this.output.delayTime.value; + } + + end() {} + + start() {} + } + class EchoEffect extends AudioEffect { + constructor(ac) { + super(ac); + /** 当前增益 */ + this.gain = 0.5; + /** 是否正在播放 */ + this.playing = false; + const delay = ac.createDelay(); + const gain = ac.createGain(); + gain.gain.value = 0.5; + delay.delayTime.value = 0.05; + delay.connect(gain); + gain.connect(delay); + /** 延迟节点 */ + this.delay = delay; + /** 反馈增益节点 */ + this.gainNode = gain; + + this.input = gain; + this.output = gain; + } + + /** + * 设置回声反馈增益大小 + * @param gain 增益大小,范围 0-1,大于等于1的视为0.5,小于0的视为0 Number + */ + setFeedbackGain(gain) { + const resolved = gain >= 1 ? 0.5 : gain < 0 ? 0 : gain; + this.gain = resolved; + if (this.playing) this.gainNode.gain.value = resolved; + } + + /** + * 设置回声间隔时长 + * @param delay 回声时长,范围 0.01-Infinity,小于0.01的视为0.01 Number + */ + setEchoDelay(delay) { + const resolved = delay < 0.01 ? 0.01 : delay; + this.delay.delayTime.value = resolved; + } + + /** + * 获取反馈节点增益 + */ + getFeedbackGain() { + return this.gain; + } + + /** + * 获取回声间隔时长 + */ + getEchoDelay() { + return this.delay.delayTime.value; + } + + end() { + this.playing = false; + const echoTime = Math.ceil(Math.log(0.001) / Math.log(this.gain)) + 10; + sleep(this.delay.delayTime.value * echoTime).then(() => { + if (!this.playing) this.gainNode.gain.value = 0; + }); + } + + start() { + this.playing = true; + this.gainNode.gain.value = this.gain; + } + } + + class StreamLoader { + constructor(url) { + /** 传输目标 Set*/ + this.target = new Set(); + this.loading = false; + } + + /** + * 将加载流传递给字节流读取对象 + * @param reader 字节流读取对象 IStreamReader + */ + pipe(reader) { + if (this.loading) { + console.warn( + "Cannot pipe new StreamReader object when stream is loading." + ); + return; + } + this.target.add(reader); + reader.piped(this); + return this; + } + + async start() { + if (this.loading) return; + this.loading = true; + const response = await window.fetch(this.url); + const stream = response.body; + if (!stream) { + console.error("Cannot get reader when fetching '" + this.url + "'."); + return; + } + // 获取读取器 + this.stream = stream; + const reader = response.body?.getReader(); + const targets = [...this.target]; + + await Promise.all(targets.map((v) => v.start(stream, this, response))); + if (reader && reader.read) { + // 开始流传输 + while (true) { + const { value, done } = await reader.read(); + await Promise.all( + targets.map((v) => v.pump(value, done, response)) + ); + if (done) break; + } + } else { + // 如果不支持流传输 + const buffer = await response.arrayBuffer(); + const data = new Uint8Array(buffer); + await Promise.all(targets.map((v) => v.pump(data, true, response))); + } + + this.loading = false; + targets.forEach((v) => v.end(true)); + + // + } + + cancel(reason) { + if (!this.stream) return; + this.stream.cancel(reason); + this.loading = false; + this.target.forEach((v) => v.end(false, reason)); + } + } + const fileSignatures = [ + [AudioType.Mp3, [0x49, 0x44, 0x33]], + [AudioType.Ogg, [0x4f, 0x67, 0x67, 0x53]], + [AudioType.Wav, [0x52, 0x49, 0x46, 0x46]], + [AudioType.Flac, [0x66, 0x4c, 0x61, 0x43]], + [AudioType.Aac, [0xff, 0xf1]], + [AudioType.Aac, [0xff, 0xf9]], + ]; + const oggHeaders = [ + [AudioType.Opus, [0x4f, 0x70, 0x75, 0x73, 0x48, 0x65, 0x61, 0x64]], + ]; + + function checkAudioType(data) { + let audioType = ""; + // 检查头文件获取音频类型,仅检查前256个字节 + const toCheck = data.slice(0, 256); + for (const [type, value] of fileSignatures) { + if (value.every((v, i) => toCheck[i] === v)) { + audioType = type; + break; + } + } + if (audioType === AudioType.Ogg) { + // 如果是ogg的话,进一步判断是不是opus + for (const [key, value] of oggHeaders) { + const has = toCheck.some((_, i) => { + return value.every((v, ii) => toCheck[i + ii] === v); + }); + if (has) { + audioType = key; + break; } } } - for (let nx = 0; nx < numX; nx++) { - for (let ny = 0; ny < numY; ny++) { - res.push(withImage(canvas, rw + l * nx, rh + l * ny, l, l)); + + return 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; + } } } - - return res; } - const origin = core.events.afterBattle; - core.events.afterBattle = function (enemyId, x, y) { - // 打怪特效 - if (has(x) && has(y)) { - const frame = core.status.globalAnimateStatus % 2; - // 生成怪物图像 - const canvas = document.createElement("canvas"); - canvas.width = 32; - canvas.height = 32; - core.drawIcon(canvas, enemyId, 0, 0, 32, 32, frame); - // 执行动画 - const manager = applyFragWith(canvas); - const frag = manager.canvas; - // 设置特效画布的css属性 - frag.style.imageRendering = "pixelated"; - frag.style.width = `${frag.width * core.domStyle.scale}px`; - frag.style.height = `${frag.height * core.domStyle.scale}px`; - const left = - (x * 32 + 16 - frag.width / 2 - core.bigmap.offsetX) * - core.domStyle.scale; - const top = - (y * 32 + 16 - frag.height / 2 - core.bigmap.offsetY) * - core.domStyle.scale; - frag.style.left = `${left}px`; - frag.style.top = `${top}px`; - frag.style.zIndex = "45"; - frag.style.position = "absolute"; - // 将特效画布部署到样板上 - core.dom.gameDraw.appendChild(frag); - // 当特效执行完毕后移除这个特效画布 - manager.onEnd.then(() => { - frag.remove(); - }); + class VorbisDecoder { + /** + * 创建音频解码器 + */ + async create() { + this.decoder = new OggVorbisDecoderWebWorker(); + await this.decoder.ready; } - return origin.apply(this, arguments); + /** + * 摧毁这个解码器 + */ + 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", }; - if ("frag" in core.plugin) { - throw new ReferenceError(`core.plugin上已存在名为frag的属性!`); + function isOggPage(data) { + return !isNil(data.isFirstPage); } - core.plugin.frag = { - applyFragWith, + class AudioStreamSource { + constructor(context) { + this.output = context.createBufferSource(); + /** 是否已经完全加载完毕 */ + this.loaded = false; + /** 是否正在播放 */ + this.playing = false; + /** 已经缓冲了多长时间,如果缓冲完那么跟歌曲时长一致 */ + this.buffered = 0; + /** 已经缓冲的采样点数量 */ + this.bufferedSamples = 0; + /** 歌曲时长,加载完毕之前保持为 0 */ + this.duration = 0; + /** 在流传输阶段,至少缓冲多长时间的音频之后才开始播放,单位秒 */ + this.bufferPlayDuration = 1; + /** 音频的采样率,未成功解析出之前保持为 0 */ + this.sampleRate = 0; + //是否循环播放 + this.loop = false; + /** 上一次播放是从何时开始的 */ + this.lastStartWhen = 0; + /** 开始播放时刻 */ + this.lastStartTime = 0; + /** 上一次播放的缓存长度 */ + this.lastBufferSamples = 0; + + /** 是否已经获取到头文件 */ + this.headerRecieved = false; + /** 音频类型 */ + this.audioType = ""; + /** 每多长时间组成一个缓存 Float32Array */ + this.bufferChunkSize = 10; + /** 缓存音频数据,每 bufferChunkSize 秒钟组成一个 Float32Array,用于流式解码 */ + this.audioData = []; + + this.errored = false; + this.ac = context; + } + /** 当前已经播放了多长时间 */ + get currentTime() { + return this.ac.currentTime - this.lastStartTime + this.lastStartWhen; + } + /** + * 设置每个缓存数据的大小,默认为10秒钟一个缓存数据 + * @param size 每个缓存数据的时长,单位秒 + */ + setChunkSize(size) { + if (this.controller?.loading || this.loaded) return; + this.bufferChunkSize = size; + } + + piped(controller) { + this.controller = controller; + } + + async pump(data, done) { + if (!data || this.errored) return; + if (!this.headerRecieved) { + // 检查头文件获取音频类型,仅检查前256个字节 + const toCheck = data.slice(0, 256); + this.audioType = checkAudioType(data); + if (!this.audioType) { + console.error( + "Unknown audio type. Header: '" + + [...toCheck] + .map((v) => v.toString(16).padStart(2, "0")) + .join(" ") + .toUpperCase() + + "'" + ); + return; + } + // 创建解码器 + const Decoder = AudioDecoder.decoderMap.get(this.audioType); + if (!Decoder) { + this.errored = true; + console.error( + "Cannot decode stream source type of '" + + this.audioType + + "', since there is no registered decoder for that type." + ); + return Promise.reject( + `Cannot decode stream source type of '${this.audioType}', since there is no registered decoder for that type.` + ); + } + this.decoder = new Decoder(); + // 创建数据解析器 + const mime = mimeTypeMap[this.audioType]; + const parser = new CodecParser(mime); + this.parser = parser; + await this.decoder.create(); + this.headerRecieved = true; + } + + const decoder = this.decoder; + const parser = this.parser; + if (!decoder || !parser) { + this.errored = true; + return Promise.reject( + "No parser or decoder attached in this AudioStreamSource" + ); + } + + await this.decodeData(data, decoder, parser); + if (done) await this.decodeFlushData(decoder, parser); + this.checkBufferedPlay(); + } + + /** + * 检查采样率,如果还未解析出采样率,那么将设置采样率,如果当前采样率与之前不同,那么发出警告 + */ + checkSampleRate(info) { + for (const one of info) { + const frame = isOggPage(one) ? one.codecFrames[0] : one; + if (frame) { + const rate = frame.header.sampleRate; + if (this.sampleRate === 0) { + this.sampleRate = rate; + break; + } else { + if (rate !== this.sampleRate) { + console.warn("Sample rate in stream audio must be constant."); + } + } + } + } + } + + /** + * 解析音频数据 + */ + async decodeData(data, decoder, parser) { + // 解析音频数据 + const audioData = await decoder.decode(data); + if (!audioData) return; + // @ts-expect-error 库类型声明错误 + const audioInfo = [...parser.parseChunk(data)]; + + // 检查采样率 + this.checkSampleRate(audioInfo); + // 追加音频数据 + this.appendDecodedData(audioData, audioInfo); + } + + /** + * 解码剩余数据 + */ + async decodeFlushData(decoder, parser) { + const audioData = await decoder.flush(); + if (!audioData) return; + // @ts-expect-error 库类型声明错误 + const audioInfo = [...parser.flush()]; + + this.checkSampleRate(audioInfo); + this.appendDecodedData(audioData, audioInfo); + } + + /** + * 追加音频数据 + */ + appendDecodedData(data, info) { + const channels = data.channelData.length; + if (channels === 0) return; + if (this.audioData.length !== channels) { + this.audioData = []; + for (let i = 0; i < channels; i++) { + this.audioData.push([]); + } + } + // 计算出应该放在哪 + const chunk = this.sampleRate * this.bufferChunkSize; + const sampled = this.bufferedSamples; + const pushIndex = Math.floor(sampled / chunk); + const bufferIndex = sampled % chunk; + const dataLength = data.channelData[0].length; + let buffered = 0; + let nowIndex = pushIndex; + let toBuffer = bufferIndex; + while (buffered < dataLength) { + const rest = toBuffer !== 0 ? chunk - bufferIndex : chunk; + + for (let i = 0; i < channels; i++) { + const audioData = this.audioData[i]; + if (!audioData[nowIndex]) { + audioData.push(new Float32Array(chunk)); + } + const toPush = data.channelData[i].slice(buffered, buffered + rest); + + audioData[nowIndex].set(toPush, toBuffer); + } + buffered += rest; + nowIndex++; + toBuffer = 0; + } + + this.buffered += + info.reduce((prev, curr) => prev + curr.duration, 0) / 1000; + this.bufferedSamples += info.reduce( + (prev, curr) => prev + curr.samples, + 0 + ); + } + + /** + * 检查已缓冲内容,并在未开始播放时播放 + */ + checkBufferedPlay() { + if (this.playing || this.sampleRate === 0) return; + const played = this.lastBufferSamples / this.sampleRate; + const dt = this.buffered - played; + if (this.loaded) { + this.playAudio(played); + return; + } + if (dt < this.bufferPlayDuration) return; + + this.lastBufferSamples = this.bufferedSamples; + // 需要播放 + this.mergeBuffers(); + if (!this.buffer) return; + if (this.playing) this.output.stop(); + this.createSourceNode(this.buffer); + this.output.loop = false; + this.output.start(0, played); + this.lastStartTime = this.ac.currentTime; + this.playing = true; + this.output.addEventListener("ended", () => { + this.playing = false; + this.checkBufferedPlay(); + }); + } + + mergeBuffers() { + const buffer = this.ac.createBuffer( + this.audioData.length, + this.bufferedSamples, + this.sampleRate + ); + const chunk = this.sampleRate * this.bufferChunkSize; + const bufferedChunks = Math.floor(this.bufferedSamples / chunk); + const restLength = this.bufferedSamples % chunk; + for (let i = 0; i < this.audioData.length; i++) { + const audio = this.audioData[i]; + const data = new Float32Array(this.bufferedSamples); + for (let j = 0; j < bufferedChunks; j++) { + data.set(audio[j], chunk * j); + } + if (restLength !== 0) { + data.set( + audio[bufferedChunks].slice(0, restLength), + chunk * bufferedChunks + ); + } + + buffer.copyToChannel(data, i, 0); + } + this.buffer = buffer; + } + + async start() { + delete this.buffer; + this.headerRecieved = false; + this.audioType = ""; + this.errored = false; + this.buffered = 0; + this.sampleRate = 0; + this.bufferedSamples = 0; + this.duration = 0; + this.loaded = false; + if (this.playing) this.output.stop(); + this.playing = false; + this.lastStartTime = this.ac.currentTime; + } + + end(done, reason) { + if (done && this.buffer) { + this.loaded = true; + delete this.controller; + this.mergeBuffers(); + + this.duration = this.buffered; + this.audioData = []; + this.decoder?.destroy(); + delete this.decoder; + delete this.parser; + } else { + console.warn( + "Unexpected end when loading stream audio, reason: '" + + (reason ?? "") + + "'" + ); + } + } + + playAudio(when) { + if (!this.buffer) return; + this.lastStartTime = this.ac.currentTime; + if (this.playing) this.output.stop(); + if (this.route.status !== AudioStatus.Playing) { + this.route.status = AudioStatus.Playing; + } + this.createSourceNode(this.buffer); + this.output.start(0, when); + this.playing = true; + + this.output.addEventListener("ended", () => { + this.playing = false; + if (this.route.status === AudioStatus.Playing) { + this.route.status = AudioStatus.Stoped; + } + if (this.loop && !this.output.loop) this.play(0); + }); + } + /** + * 开始播放这个音频源 + */ + play(when) { + if (this.playing || this.errored) return; + if (this.loaded && this.buffer) { + this.playing = true; + this.playAudio(when); + } else { + this.controller?.start(); + } + } + + createSourceNode(buffer) { + if (!this.target) return; + const node = this.ac.createBufferSource(); + node.buffer = buffer; + if (this.playing) this.output.stop(); + this.playing = false; + this.output = node; + node.connect(this.target.input); + node.loop = this.loop; + } + /** + * 停止播放这个音频源 + * @returns 音频暂停的时刻 number + */ + stop() { + if (this.playing) this.output.stop(); + this.playing = false; + return this.ac.currentTime - this.lastStartTime; + } + /** + * 连接到音频路由图上,每次调用播放的时候都会执行一次 + * @param target 连接至的目标 IAudioInput + */ + connect(target) { + this.target = target; + } + /** + * 设置是否循环播放 + * @param loop 是否循环 boolean) + */ + setLoop(loop) { + this.loop = loop; + } + } + class AudioElementSource { + constructor(context) { + const audio = new Audio(); + audio.preload = "none"; + this.output = context.createMediaElementSource(audio); + this.audio = audio; + this.ac = context; + audio.addEventListener("play", () => { + this.playing = true; + if (this.route.status !== AudioStatus.Playing) { + this.route.status = AudioStatus.Playing; + } + }); + audio.addEventListener("ended", () => { + this.playing = false; + if (this.route.status === AudioStatus.Playing) { + this.route.status = AudioStatus.Stoped; + } + }); + } + get duration() { + return this.audio.duration; + } + get currentTime() { + return this.audio.currentTime; + } + /** + * 设置音频源的路径 + * @param url 音频路径 + */ + setSource(url) { + this.audio.src = url; + } + + play(when = 0) { + if (this.playing) return; + this.audio.currentTime = when; + this.audio.play(); + } + + stop() { + this.audio.pause(); + this.playing = false; + if (this.route.status === AudioStatus.Playing) { + this.route.status = AudioStatus.Stoped; + } + return this.audio.currentTime; + } + + connect(target) { + this.output.connect(target.input); + } + + setLoop(loop) { + this.audio.loop = loop; + } + } + class AudioBufferSource { + constructor(context) { + this.output = context.createBufferSource(); + /** 是否循环 */ + this.loop = false; + /** 上一次播放是从何时开始的 */ + this.lastStartWhen = 0; + /** 播放开始时刻 */ + this.lastStartTime = 0; + this.duration = 0; + this.ac = context; + } + get currentTime() { + return this.ac.currentTime - this.lastStartTime + this.lastStartWhen; + } + + /** + * 设置音频源数据 + * @param buffer 音频源,可以是未解析的 ArrayBuffer,也可以是已解析的 AudioBuffer + */ + async setBuffer(buffer) { + if (buffer instanceof ArrayBuffer) { + this.buffer = await this.ac.decodeAudioData(buffer); + } else { + this.buffer = buffer; + } + this.duration = this.buffer.duration; + } + + play(when) { + if (this.playing || !this.buffer) return; + this.playing = true; + this.lastStartTime = this.ac.currentTime; + if (this.route.status !== AudioStatus.Playing) { + this.route.status = AudioStatus.Playing; + } + this.createSourceNode(this.buffer); + this.output.start(0, when); + this.output.addEventListener("ended", () => { + this.playing = false; + if (this.route.status === AudioStatus.Playing) { + this.route.status = AudioStatus.Stoped; + } + if (this.loop && !this.output.loop) this.play(0); + }); + } + + createSourceNode(buffer) { + if (!this.target) return; + const node = this.ac.createBufferSource(); + node.buffer = buffer; + this.output = node; + node.connect(this.target.input); + node.loop = this.loop; + } + + stop() { + this.output.stop(); + return this.ac.currentTime - this.lastStartTime; + } + + connect(target) { + this.target = target; + } + + setLoop(loop) { + this.loop = loop; + } + } + class AudioPlayer { + constructor() { + /** 音频播放上下文 */ + this.ac = new AudioContext(); + /** 音量节点 */ + this.gain = this.ac.createGain(); + this.gain.connect(this.ac.destination); + this.audioRoutes = new Map(); + } + /** + * 解码音频数据 + * @param data 音频数据 + */ + decodeAudioData(data) { + return AudioDecoder.decodeAudioData(data, this); + } + /** + * 设置音量 + * @param volume 音量 + */ + setVolume(volume) { + this.gain.gain.value = volume; + } + + /** + * 获取音量 + */ + getVolume() { + return this.gain.gain.value; + } + + /** + * 创建一个音频源 + * @param Source 音频源类 + */ + createSource(Source) { + return new Source(this.ac); + } + + /** + * 创建一个兼容流式音频源,可以与流式加载相结合,主要用于处理 opus ogg 不兼容的情况 + */ + createStreamSource() { + return new AudioStreamSource(this.ac); + } + + /** + * 创建一个通过 audio 元素播放的音频源 + */ + createElementSource() { + return new AudioElementSource(this.ac); + } + + /** + * 创建一个通过 AudioBuffer 播放的音频源 + */ + createBufferSource() { + return new AudioBufferSource(this.ac); + } + + /** + * 获取音频目的地 + */ + getDestination() { + return this.gain; + } + + /** + * 创建一个音频效果器 + * @param Effect 效果器类 + */ + createEffect(Effect) { + return new Effect(this.ac); + } + + /** + * 创建一个修改音量的效果器 + * ```txt + * |----------| + * Input ----> | GainNode | ----> Output + * |----------| + * ``` + */ + createVolumeEffect() { + return new VolumeEffect(this.ac); + } + + /** + * 创建一个立体声效果器 + * ```txt + * |------------| + * Input ----> | PannerNode | ----> Output + * |------------| + * ``` + */ + createStereoEffect() { + return new StereoEffect(this.ac); + } + + /** + * 创建一个修改单个声道音量的效果器 + * ```txt + * |----------| + * -> | GainNode | \ + * |--------------| / |----------| -> |------------| + * Input ----> | SplitterNode | ...... | MergerNode | ----> Output + * |--------------| \ |----------| -> |------------| + * -> | GainNode | / + * |----------| + * ``` + */ + createChannelVolumeEffect() { + return new ChannelVolumeEffect(this.ac); + } + + /** + * 创建一个延迟效果器 + * |-----------| + * Input ----> | DelayNode | ----> Output + * |-----------| + */ + createDelay() { + return new DelayEffect(this.ac); + } + + /** + * 创建一个回声效果器 + * ```txt + * |----------| + * Input ----> | GainNode | ----> Output + * ^ |----------| | + * | | + * | |------------| ↓ + * |-- | Delay Node | <-- + * |------------| + * ``` + */ + createEchoEffect() { + return new EchoEffect(this.ac); + } + + /** + * 创建一个音频播放路由 + * @param source 音频源 + */ + createRoute(source) { + return new AudioRoute(source, this); + } + + /** + * 添加一个音频播放路由,可以直接被播放 + * @param id 这个音频播放路由的名称 + * @param route 音频播放路由对象 + */ + addRoute(id, route) { + if (!this.audioRoutes) this.audioRoutes = new Map(); + if (this.audioRoutes.has(id)) { + console.warn( + "Audio route with id of '" + + id + + "' has already existed. New route will override old route." + ); + } + this.audioRoutes.set(id, route); + } + + /** + * 根据名称获取音频播放路由对象 + * @param id 音频播放路由的名称 + */ + getRoute(id) { + return this.audioRoutes.get(id); + } + /** + * 移除一个音频播放路由 + * @param id 要移除的播放路由的名称 + */ + removeRoute(id) { + this.audioRoutes.delete(id); + } + /** + * 播放音频 + * @param id 音频名称 + * @param when 从音频的哪个位置开始播放,单位秒 + */ + play(id, when) { + const route = this.getRoute(id); + if (!route) { + console.warn( + "Cannot play audio route '" + + id + + "', since there is not added route named it." + ); + return; + } + + route.play(when); + } + + /** + * 暂停音频播放 + * @param id 音频名称 + * @returns 当音乐真正停止时兑现 + */ + pause(id) { + const route = this.getRoute(id); + if (!route) { + console.warn( + "Cannot pause audio route '" + + id + + "', since there is not added route named it." + ); + return; + } + return route.pause(); + } + + /** + * 停止音频播放 + * @param id 音频名称 + * @returns 当音乐真正停止时兑现 + */ + stop(id) { + const route = this.getRoute(id); + if (!route) { + console.warn( + "Cannot stop audio route '" + + id + + "', since there is not added route named it." + ); + return; + } + return route.stop(); + } + + /** + * 继续音频播放 + * @param id 音频名称 + */ + resume(id) { + const route = this.getRoute(id); + if (!route) { + console.warn( + "Cannot pause audio route '" + + id + + "', since there is not added route named it." + ); + return; + } + route.resume(); + } + + /** + * 设置听者位置,x正方向水平向右,y正方向垂直于地面向上,z正方向垂直屏幕远离用户 + * @param x 位置x坐标 + * @param y 位置y坐标 + * @param z 位置z坐标 + */ + setListenerPosition(x, y, z) { + const listener = this.ac.listener; + listener.positionX.value = x; + listener.positionY.value = y; + listener.positionZ.value = z; + } + + /** + * 设置听者朝向,x正方向水平向右,y正方向垂直于地面向上,z正方向垂直屏幕远离用户 + * @param x 朝向x坐标 + * @param y 朝向y坐标 + * @param z 朝向z坐标 + */ + setListenerOrientation(x, y, z) { + const listener = this.ac.listener; + listener.forwardX.value = x; + listener.forwardY.value = y; + listener.forwardZ.value = z; + } + + /** + * 设置听者头顶朝向,x正方向水平向右,y正方向垂直于地面向上,z正方向垂直屏幕远离用户 + * @param x 头顶朝向x坐标 + * @param y 头顶朝向y坐标 + * @param z 头顶朝向z坐标 + */ + setListenerUp(x, y, z) { + const listener = this.ac.listener; + listener.upX.value = x; + listener.upY.value = y; + listener.upZ.value = z; + } + } + class AudioRoute { + constructor(source, player) { + source.route = this; + this.output = source.output; + + /** 效果器路由图 */ + this.effectRoute = []; + + /** 结束时长,当音频暂停或停止时,会经过这么长时间之后才真正终止播放,期间可以做音频淡入淡出等效果 */ + this.endTime = 0; + /** 暂停时播放了多长时间 */ + this.pauseCurrentTime = 0; + /** 当前播放状态 */ + this.player = player; + this.status = AudioStatus.Stoped; + + this.shouldStop = false; + /** + * 每次暂停或停止时自增,用于判断当前正在处理的情况。 + * 假如暂停后很快播放,然后很快暂停,那么需要根据这个来判断实际是否应该执行暂停后操作 + */ + this.stopIdentifier = 0; + /** 暂停时刻 */ + this.pauseTime = 0; + this.source = source; + this.source.player = player; + } + /** 音频时长,单位秒 */ + get duration() { + return this.source.duration; + } + /** 当前播放了多长时间,单位秒 */ + get currentTime() { + if (this.status === AudioStatus.Paused) { + return this.pauseCurrentTime; + } else { + return this.source.currentTime; + } + } + set currentTime(time) { + this.source.stop(); + this.source.play(time); + } + /** + * 设置结束时间,暂停或停止时,会经过这么长时间才终止音频的播放,这期间可以做一下音频淡出的效果。 + * @param time 暂停或停止时,经过多长时间之后才会结束音频的播放 + */ + setEndTime(time) { + this.endTime = time; + } + + /** + * 当音频播放时执行的函数,可以用于音频淡入效果 + * @param fn 音频开始播放时执行的函数 + */ + onStart(fn) { + this.audioStartHook = fn; + } + + /** + * 当音频暂停或停止时执行的函数,可以用于音频淡出效果 + * @param fn 音频在暂停或停止时执行的函数,不填时表示取消这个钩子。 + * 包含两个参数,第一个参数是结束时长,第二个参数是当前音频播放路由对象 + */ + onEnd(fn) { + this.audioEndHook = fn; + } + + /** + * 开始播放这个音频 + * @param when 从音频的什么时候开始播放,单位秒 + */ + async play(when = 0) { + if (this.status === AudioStatus.Playing) return; + this.link(); + await this.player.ac.resume(); + if (this.effectRoute.length > 0) { + const first = this.effectRoute[0]; + this.source.connect(first); + const last = this.effectRoute.at(-1); + last.connect({ input: this.player.getDestination() }); + } else { + this.source.connect({ input: this.player.getDestination() }); + } + this.source.play(when); + this.status = AudioStatus.Playing; + this.pauseTime = 0; + this.audioStartHook?.(this); + this.startAllEffect(); + if (this.status !== AudioStatus.Playing) { + this.status = AudioStatus.Playing; + } + } + + /** + * 暂停音频播放 + */ + async pause() { + if (this.status !== AudioStatus.Playing) return; + this.status = AudioStatus.Pausing; + this.stopIdentifier++; + const identifier = this.stopIdentifier; + if (this.audioEndHook) { + this.audioEndHook(this.endTime, this); + await sleep(this.endTime); + } + if ( + this.status !== AudioStatus.Pausing || + this.stopIdentifier !== identifier + ) { + return; + } + this.pauseCurrentTime = this.source.currentTime; + const time = this.source.stop(); + this.pauseTime = time; + if (this.shouldStop) { + this.status = AudioStatus.Stoped; + this.endAllEffect(); + + this.shouldStop = false; + } else { + this.status = AudioStatus.Paused; + this.endAllEffect(); + } + this.endAllEffect(); + } + + /** + * 继续音频播放 + */ + resume() { + if (this.status === AudioStatus.Playing) return; + if ( + this.status === AudioStatus.Pausing || + this.status === AudioStatus.Stoping + ) { + this.audioStartHook?.(this); + + return; + } + if (this.status === AudioStatus.Paused) { + this.play(this.pauseTime); + } else { + this.play(0); + } + this.status = AudioStatus.Playing; + this.pauseTime = 0; + this.audioStartHook?.(this); + this.startAllEffect(); + } + + /** + * 停止音频播放 + */ + async stop() { + if (this.status !== AudioStatus.Playing) { + if (this.status === AudioStatus.Pausing) { + this.shouldStop = true; + } + return; + } + this.status = AudioStatus.Stoping; + this.stopIdentifier++; + const identifier = this.stopIdentifier; + if (this.audioEndHook) { + this.audioEndHook(this.endTime, this); + await sleep(this.endTime); + } + if ( + this.status !== AudioStatus.Stoping || + this.stopIdentifier !== identifier + ) { + return; + } + this.source.stop(); + this.status = AudioStatus.Stoped; + this.pauseTime = 0; + this.endAllEffect(); + } + + /** + * 添加效果器 + * @param effect 要添加的效果,可以是数组,表示一次添加多个 + * @param index 从哪个位置开始添加,如果大于数组长度,那么加到末尾,如果小于0,那么将会从后面往前数。默认添加到末尾 + */ + addEffect(effect, index) { + if (isNil(index)) { + if (effect instanceof Array) { + this.effectRoute.push(...effect); + } else { + this.effectRoute.push(effect); + } + } else { + if (effect instanceof Array) { + this.effectRoute.splice(index, 0, ...effect); + } else { + this.effectRoute.splice(index, 0, effect); + } + } + this.setOutput(); + if (this.source.playing) this.link(); + } + + /** + * 移除一个效果器 + * @param effect 要移除的效果 + */ + removeEffect(effect) { + const index = this.effectRoute.indexOf(effect); + if (index === -1) return; + this.effectRoute.splice(index, 1); + effect.disconnect(); + this.setOutput(); + if (this.source.playing) this.link(); + } + + setOutput() { + const effect = this.effectRoute.at(-1); + if (!effect) this.output = this.source.output; + else this.output = effect.output; + } + + /** + * 连接音频路由图 + */ + link() { + this.effectRoute.forEach((v) => v.disconnect()); + this.effectRoute.forEach((v, i) => { + const next = this.effectRoute[i + 1]; + if (next) { + v.connect(next); + } + }); + } + + startAllEffect() { + this.effectRoute.forEach((v) => v.start()); + } + + endAllEffect() { + this.effectRoute.forEach((v) => v.end()); + } + } + + const audioPlayer = new AudioPlayer(); + + class BgmController { + constructor(player) { + this.mainGain = player.createVolumeEffect(); + this.player = player; + /** bgm音频名称的前缀 */ + this.prefix = "bgms."; + /** 每个 bgm 的音量控制器 */ + this.gain = new Map(); + + /** 正在播放的 bgm */ + this.playingBgm = ""; + /** 是否正在播放 */ + this.playing = false; + + /** 是否已经启用 */ + this.enabled = true; + /** 是否屏蔽所有的音乐切换 */ + this.blocking = false; + /** 渐变时长 */ + this.transitionTime = 2000; + } + + /** + * 设置音频渐变时长 + * @param time 渐变时长 + */ + setTransitionTime(time) { + this.transitionTime = time; + for (const [, value] of this.gain) { + value.transition.time(time); + } + } + + /** + * 屏蔽音乐切换 + */ + blockChange() { + this.blocking = true; + } + + /** + * 取消屏蔽音乐切换 + */ + unblockChange() { + this.blocking = false; + } + + /** + * 设置总音量大小 + * @param volume 音量大小 + */ + setVolume(volume) { + this.mainGain.setVolume(volume); + this._volume = volume; + } + /** + * 获取总音量大小 + */ + getVolume() { + return this.mainGain.getVolume(); + } + /** + * 设置是否启用 + * @param enabled 是否启用 + */ + setEnabled(enabled) { + if (enabled) this.resume(); + else this.stop(); + this.enabled = enabled; + } + + /** + * 设置 bgm 音频名称的前缀 + */ + setPrefix(prefix) { + this.prefix = prefix; + } + + getId(name) { + return `${this.prefix}${name}`; + } + + /** + * 根据 bgm 名称获取其 AudioRoute 实例 + * @param id 音频名称 + */ + get(id) { + return this.player.getRoute(this.getId(id)); + } + + /** + * 添加一个 bgm + * @param id 要添加的 bgm 的名称 + * @param url 指定 bgm 的加载地址 + */ + addBgm(id, url = `project/bgms/${id}`) { + const type = guessTypeByExt(id); + if (!type) { + console.warn( + "Unknown audio extension name: '" + + id.split(".").slice(0, -1).join(".") + + "'" + ); + return; + } + const gain = this.player.createVolumeEffect(); + if (isAudioSupport(type)) { + const source = audioPlayer.createElementSource(); + source.setSource(url); + source.setLoop(true); + const route = new AudioRoute(source, audioPlayer); + route.addEffect([gain, this.mainGain]); + audioPlayer.addRoute(this.getId(id), route); + this.setTransition(id, route, gain); + } else { + const source = audioPlayer.createStreamSource(); + const stream = new StreamLoader(url); + stream.pipe(source); + source.setLoop(true); + const route = new AudioRoute(source, audioPlayer); + route.addEffect([gain, this.mainGain]); + audioPlayer.addRoute(this.getId(id), route); + this.setTransition(id, route, gain); + } + } + + /** + * 移除一个 bgm + * @param id 要移除的 bgm 的名称 + */ + removeBgm(id) { + this.player.removeRoute(this.getId(id)); + const gain = this.gain.get(id); + gain?.transition.ticker.destroy(); + this.gain.delete(id); + } + + setTransition(id, route, gain) { + const transition = new Transition(); + transition + .time(this.transitionTime) + .mode(linear()) + .transition("volume", 0); + + const tick = () => { + gain.setVolume(transition.value.volume); + }; + + /** + * @param expect 在结束时应该是正在播放还是停止 + */ + const setTick = async (expect) => { + transition.ticker.remove(tick); + transition.ticker.add(tick); + const identifier = route.stopIdentifier; + await sleep(this.transitionTime + 500); + if (route.status === expect && identifier === route.stopIdentifier) { + transition.ticker.remove(tick); + if (route.status === AudioStatus.Playing) { + gain.setVolume(1); + } else { + gain.setVolume(0); + } + } + }; + + route.onStart(async () => { + transition.transition("volume", 1); + setTick(AudioStatus.Playing); + }); + route.onEnd(() => { + transition.transition("volume", 0); + setTick(AudioStatus.Paused); + }); + route.setEndTime(this.transitionTime); + + this.gain.set(id, { effect: gain, transition }); + } + + /** + * 播放一个 bgm + * @param id 要播放的 bgm 名称 + */ + play(id, when) { + if (this.blocking) return; + if (id !== this.playingBgm && this.playingBgm) { + this.player.pause(this.getId(this.playingBgm)); + } + this.playingBgm = id; + if (!this.enabled) return; + this.player.play(this.getId(id), when); + this.playing = true; + } + + /** + * 继续当前的 bgm + */ + resume() { + if (this.blocking || !this.enabled || this.playing) return; + if (this.playingBgm) { + this.player.resume(this.getId(this.playingBgm)); + } + this.playing = true; + } + + /** + * 暂停当前的 bgm + */ + pause() { + if (this.blocking || !this.enabled) return; + if (this.playingBgm) { + this.player.pause(this.getId(this.playingBgm)); + } + this.playing = false; + } + + /** + * 停止当前的 bgm + */ + stop() { + if (this.blocking || !this.enabled) return; + if (this.playingBgm) { + this.player.stop(this.getId(this.playingBgm)); + } + this.playing = false; + } + } + const bgmController = new BgmController(audioPlayer); + + class SoundPlayer { + constructor(player) { + /** 每个音效的唯一标识符 */ + this.num = 0; + this.enabled = true; + this.gain = player.createVolumeEffect(); + /** 每个音效的数据 */ + this.buffer = new Map(); + /** 所有正在播放的音乐 */ + this.playing = new Set(); + this.player = player; + } + /** + * 设置是否启用音效 + * @param enabled 是否启用音效 + */ + setEnabled(enabled) { + if (!enabled) this.stopAllSounds(); + this.enabled = enabled; + } + + /** + * 设置音量大小 + * @param volume 音量大小 + */ + setVolume(volume) { + this.gain.setVolume(volume); + } + /** + * 获取音量大小 + */ + getVolume() { + return this.gain.getVolume(); + } + /** + * 添加一个音效 + * @param id 音效名称 + * @param data 音效的Uint8Array数据 + */ + async add(id, data) { + const buffer = await this.player.decodeAudioData(data); + if (!buffer) { + console.warn( + "Cannot decode sound '" + + id + + "', since audio file may not supported by 2.b." + ); + return; + } + this.buffer.set(id, buffer); + } + + /** + * 播放一个音效 + * @param id 音效名称 + * @param position 音频位置,[0, 0, 0]表示正中心,x轴指向水平向右,y轴指向水平向上,z轴指向竖直向上 + * @param orientation 音频朝向,[0, 1, 0]表示朝向前方 + */ + play(id, position = [0, 0, 0], orientation = [1, 0, 0]) { + if (!this.enabled || !id) return -1; + const buffer = this.buffer.get(id); + if (!buffer) { + console.warn( + "Cannot play sound '" + + id + + "', since there is no added data named it." + ); + return -1; + } + const soundNum = this.num++; + + const source = this.player.createBufferSource(); + source.setBuffer(buffer); + const route = this.player.createRoute(source); + const stereo = this.player.createStereoEffect(); + stereo.setPosition(position[0], position[1], position[2]); + stereo.setOrientation(orientation[0], orientation[1], orientation[2]); + route.addEffect([stereo, this.gain]); + this.player.addRoute(`sounds.${soundNum}`, route); + route.play(); + source.output.addEventListener("ended", () => { + this.playing.delete(soundNum); + }); + this.playing.add(soundNum); + return soundNum; + } + + /** + * 停止一个音效 + * @param num 音效的唯一 id + */ + stop(num) { + const id = `sounds.${num}`; + const route = this.player.getRoute(id); + if (route) { + route.stop(); + this.player.removeRoute(id); + this.playing.delete(num); + } + } + + /** + * 停止播放所有音效 + */ + stopAllSounds() { + this.playing.forEach((v) => { + const id = `sounds.${v}`; + const route = this.player.getRoute(id); + if (route) { + route.stop(); + this.player.removeRoute(id); + } + }); + this.playing.clear(); + } + } + const soundPlayer = new SoundPlayer(audioPlayer); + + function loadAllBgm() { + const data = data_a1e2fb4a_e986_4524_b0da_9b7ba7c0874d; + for (const bgm of data.main.bgms) { + bgmController.addBgm(bgm); + } + } + loadAllBgm(); + AudioDecoder.registerDecoder(AudioType.Ogg, VorbisDecoder); + AudioDecoder.registerDecoder(AudioType.Opus, OpusDecoder); + + core.plugin.audioSystem = { + AudioType, + AudioDecoder, + AudioStatus, + checkAudioType, + isAudioSupport, + audioPlayer, + soundPlayer, + bgmController, + guessTypeByExt, + BgmController, + SoundPlayer, + EchoEffect, + DelayEffect, + ChannelVolumeEffect, + VolumeEffect, + StereoEffect, + AudioEffect, + AudioPlayer, + AudioRoute, + AudioStreamSource, + AudioElementSource, + AudioBufferSource, + loadAllBgm, + StreamLoader, + }; + //bgm相关复写 + control.prototype.playBgm = (bgm, when) => { + bgm = core.getMappedName(bgm); + bgmController.play(bgm, when); + core.setMusicBtn(); + }; + control.prototype.pauseBgm = () => { + bgmController.pause(); + core.setMusicBtn(); + }; + + control.prototype.resumeBgm = function () { + bgmController.resume(); + core.setMusicBtn(); + }; + control.prototype.checkBgm = function () { + core.playBgm(bgmController.playingBgm || main.startBgm); + }; + control.prototype.triggerBgm = function () { + core.musicStatus.bgmStatus = !core.musicStatus.bgmStatus; + if (bgmController.playing) bgmController.pause(); + else bgmController.resume(); + core.setMusicBtn(); + core.setLocalStorage("bgmStatus", core.musicStatus.bgmStatus); + }; + //sound相关复写 + control.prototype.playSound = function ( + sound, + _pitch, + callback, + position, + orientation + ) { + if (main.mode != "play" || !core.musicStatus.soundStatus) return; + const name = core.getMappedName(sound); + const num = soundPlayer.play(name, position, orientation); + const route = audioPlayer.getRoute(`sounds.${num}`); + if (!route) { + callback?.(); + return -1; + } else { + sleep(route.duration * 1000).then(() => callback?.()); + return num; + } + }; + control.prototype.stopSound = function (id) { + if (isNil(id)) { + soundPlayer.stopAllSounds(); + } else { + soundPlayer.stop(id); + } + }; + control.prototype.getPlayingSounds = function () { + return [...soundPlayer.playing]; + }; + //sound加载复写 + loader.prototype._loadOneSound_decodeData = function (name, data) { + if (data instanceof Blob) { + var blobReader = new zip.BlobReader(data); + blobReader.init(function () { + blobReader.readUint8Array(0, blobReader.size, function (uint8) { + //core.loader._loadOneSound_decodeData(name, uint8.buffer); + soundPlayer.add(name, uint8); + }); + }); + return; + } + if (data instanceof ArrayBuffer) { + const uint8 = new Uint8Array(data); + soundPlayer.add(name, uint8); + } + }; + //音量控制复写 + soundPlayer.setVolume( + core.musicStatus.userVolume * core.musicStatus.designVolume + ); + bgmController.setVolume( + core.musicStatus.userVolume * core.musicStatus.designVolume + ); + actions.prototype._clickSwitchs_sounds_userVolume = function (delta) { + var value = Math.round(Math.sqrt(100 * core.musicStatus.userVolume)); + if (value == 0 && delta < 0) return; + core.musicStatus.userVolume = core.clamp( + Math.pow(value + delta, 2) / 100, + 0, + 1 + ); + //audioContext 音效 不受designVolume 影响 + if (core.musicStatus.gainNode != null) + core.musicStatus.gainNode.gain.value = core.musicStatus.userVolume; + soundPlayer.setVolume( + core.musicStatus.userVolume * core.musicStatus.designVolume + ); + bgmController.setVolume( + core.musicStatus.userVolume * core.musicStatus.designVolume + ); + core.setLocalStorage("userVolume", core.musicStatus.userVolume); + core.playSound("确定"); + core.ui._drawSwitchs_sounds(); }; }, "自定义常用事件": function () { @@ -12724,424 +12487,434 @@ var plugins_bb40132b_638b_4a9f_b028_d3fe47acc8d1 = }; }, "小地图": function () { - // 在此增加新插件 - // ----- 不可自定义 杂七杂八的变量 - /** @type {{[x: string]: BFSResult}} */ - let mapCache = {}; // 地图缓存 - let drawCache = {}; // 绘制信息缓存 - let status = "none"; // 当前的绘制状态 - /** @type {{[x: string]: Sprite}} */ - let sprites = {}; // 当前所有的sprite - /** @type {{[x: string]: Sprite}} */ - let canDrag = {}; // 可以拖拽的sprite - /** @type {{[x: string]: Button}} */ - let areaSprite = {}; // 区域列表对应的sprite - let clicking = false; // 是否正在点击,用于拖拽判定 - let drawingMap = ""; // 正在绘制的中心楼层 - let nowScale = 0; // 当前绘制的放缩比例 - let lastTouch = {}; // 上一次的单点点击信息 - let lastLength = 0; // 手机端缩放时上一次的两指间距离 - let nowDepth = 0; // 当前的遍历深度 - let drawedThumbnail = {}; // 已经绘制过的缩略图 - let moved = false; // 鼠标按下后是否移动了 - let noBorder = false; // 是否是无边框拼接模式 - let lastScale = 0; // 上一次缩放,用于优化缩略图绘制 - let areaPage = 0; // 区域显示的当前页数 - let nowArea = 0; // 当前区域index - let selecting = ""; // 选择时当前正在选择的地图 + // 在此增加新插件 + // ----- 不可自定义 杂七杂八的变量 + /** @type {{[x: string]: BFSResult}} */ + let mapCache = {}; // 地图缓存 + let drawCache = {}; // 绘制信息缓存 + let status = "none"; // 当前的绘制状态 + /** @type {{[x: string]: Sprite}} */ + let sprites = {}; // 当前所有的sprite + /** @type {{[x: string]: Sprite}} */ + let canDrag = {}; // 可以拖拽的sprite + /** @type {{[x: string]: Button}} */ + let areaSprite = {}; // 区域列表对应的sprite + let clicking = false; // 是否正在点击,用于拖拽判定 + let drawingMap = ""; // 正在绘制的中心楼层 + let nowScale = 0; // 当前绘制的放缩比例 + let lastTouch = {}; // 上一次的单点点击信息 + let lastLength = 0; // 手机端缩放时上一次的两指间距离 + let nowDepth = 0; // 当前的遍历深度 + let drawedThumbnail = {}; // 已经绘制过的缩略图 + let moved = false; // 鼠标按下后是否移动了 + let noBorder = false; // 是否是无边框拼接模式 + let lastScale = 0; // 上一次缩放,用于优化缩略图绘制 + let areaPage = 0; // 区域显示的当前页数 + let nowArea = 0; // 当前区域index + let selecting = ""; // 选择时当前正在选择的地图 - // ---- 不可自定义,常量 - /** @type {Area} */ - let areas = []; // 区域信息 - const perPage = Math.floor((core._PY_ - 60) / 30); // 区域的每页显示数量 + // ---- 不可自定义,常量 + /** @type {Area} */ + let areas = []; // 区域信息 + const perPage = Math.floor((core._PY_ - 60) / 30); // 区域的每页显示数量 - // ---- 可自定义,默认的切换地图的图块id - const defaultChange = { - left: "leftPortal", // 左箭头 - up: "upPortal", // 上箭头 - right: "rightPortal", // 右箭头 - down: "downPortal", // 下箭头 - upFloor: "upFloor", // 上楼 - downFloor: "downFloor", // 下楼 - }; - // ---- 可自定义,默认数值 - const defaultValue = { - font: "Verdana", // 默认字体 - scale: 60, // 默认地图缩放比例 - depth: Infinity, // 默认的遍历深度 - }; + // ---- 可自定义,默认的切换地图的图块id + const defaultChange = { + left: "leftPortal", // 左箭头 + up: "upPortal", // 上箭头 + right: "rightPortal", // 右箭头 + down: "downPortal", // 下箭头 + upFloor: "upFloor", // 上楼 + downFloor: "downFloor", // 下楼 + }; + // ---- 可自定义,默认数值 + const defaultValue = { + font: "Verdana", // 默认字体 + scale: 60, // 默认地图缩放比例 + depth: Infinity, // 默认的遍历深度 + }; - // ---- 不可自定义,计算数据 - const dirData = { - up: [1, 0], - down: [-1, 0], - left: [0, 1], - right: [0, -1], - upFloor: [0, 0], - downFloor: [0, 0], - }; - let ignoreEnemies = (this.ignoreEnemies = []); + // ---- 不可自定义,计算数据 + const dirData = { + up: [1, 0], + down: [-1, 0], + left: [0, 1], + right: [0, -1], + upFloor: [0, 0], + downFloor: [0, 0], + }; + let ignoreEnemies = (this.ignoreEnemies = []); - let allChangeEntries = Object.entries(defaultChange); + let allChangeEntries = Object.entries(defaultChange); - this.setq = function (floorId) { - core.setFlag("任务地点", floorId); - }; + this.setq = function (floorId) { + core.setFlag("任务地点", floorId); + }; - const reset = core.events.resetGame; - this.bfs = function () { - areas = []; - // 获取所有分区,使用异步函数,保证不会卡顿 - // 原理是用bfs扫,将所有连在一起的地图合并成一个区域 - (async function () { - let all = core.floorIds.slice(); - const scanned = { - [all[0]]: true, - }; - while (all.length > 0) { - let now = all.shift(); - if (core.status.maps[now].deleted) continue; - if (!now) return; - await new Promise((res) => { - const result = core.plugin.bfsSearch(now, Infinity, true); - mapCache[`${now}_Infinity_false`] = result; - areas.push({ name: core.floors[now].areas, maps: result.order }); - for (const map of result.order) { - scanned[map] = true; - all = all.filter((v) => !result.order.includes(v)); - } - res("success"); - }).then(() => { - core.setFlag("areas", areas); - }); - } - })(); - }; - core.events.resetGame = function () { - reset.apply(core.events, arguments); - core.plugin.bfs(); - }; - /** - * 广度优先搜索搜索地图路径 - * @param {string} center 中心地图的id - * @param {number} depth 搜索深度 - * @param {boolean} noCache 是否不使用缓存 - * @returns {BFSResult} 格式:floorId_x_y_dir: floorId_x_y - */ - this.bfsSearch = function bfsSearch(center, depth, noCache) { - // 检查缓存 - const id = `${center}_${depth}_${noBorder}`; - if (mapCache[id] && !noCache) return mapCache[id]; - const used = { - [center]: true, - }; // 搜索过的楼层 - let queue = []; - let stack = [center]; // 当前栈 - let nowDepth = -1; - const mapOrder = [center]; // 遍历顺序,顺便还能记录遍历了哪些楼层 + const reset = core.events.resetGame; + this.bfs = function () { + areas = []; + // 获取所有分区,使用异步函数,保证不会卡顿 + // 原理是用bfs扫,将所有连在一起的地图合并成一个区域 + (async function () { + let all = core.floorIds.slice(); + const scanned = { + [all[0]]: true, + }; + while (all.length > 0) { + let now = all.shift(); + if (core.status.maps[now].deleted) continue; + if (!now) return; + await new Promise((res) => { + const result = core.plugin.bfsSearch(now, Infinity, true); + mapCache[`${now}_Infinity_false`] = result; + areas.push({ name: core.floors[now].areas, maps: result.order }); + for (const map of result.order) { + scanned[map] = true; + all = all.filter((v) => !result.order.includes(v)); + } + res("success"); + }).then(() => { + core.setFlag("areas", areas); + }); + } + })(); + }; + core.events.resetGame = function () { + reset.apply(core.events, arguments); + core.plugin.bfs(); + }; + /** + * 广度优先搜索搜索地图路径 + * @param {string} center 中心地图的id + * @param {number} depth 搜索深度 + * @param {boolean} noCache 是否不使用缓存 + * @returns {BFSResult} 格式:floorId_x_y_dir: floorId_x_y + */ + this.bfsSearch = function bfsSearch(center, depth, noCache) { + // 检查缓存 + const id = `${center}_${depth}_${noBorder}`; + if (mapCache[id] && !noCache) return mapCache[id]; + const used = { + [center]: true, + }; // 搜索过的楼层 + let queue = []; + let stack = [center]; // 当前栈 + let nowDepth = -1; + const mapOrder = [center]; // 遍历顺序,顺便还能记录遍历了哪些楼层 - const res = {}; // 输出结果,格式:floorId_x_y_dir: floorId_x_y - const enemies = {}; - const upOrDown = {}; - const mapdir = {}; - // 开始循环搜索 - while (nowDepth < depth && stack.length > 0) { - const now = stack.shift(); // 当前id - if (core.status.maps[now].deleted) continue; - mapdir[now] = mapdir[now] ?? []; - const blocks = core.getMapBlocksObj(now); // 获取当前地图的每点的事件 - enemies[now] = {}; - // 遍历,获取可以传送的点,只检测绿点事件,因此可用红点事件进行传送来实现分区功能 - for (const i in blocks) { - const block = blocks[i]; - // 整合漏怪检测,所以要检测怪物 - if (block.event.trigger === "battle") { - const id = block.event.id; - if (ignoreEnemies.includes(id)) continue; - else enemies[now][i] = block.event.id; - continue; - } - // 检测触发器是否为切换楼层,不是则直接跳过 - if (block.event.trigger !== "changeFloor") continue; - const dirEntries = allChangeEntries.find( - (v) => v[1] === block.event.id - ); - // 如果不是那六种传送门,直接忽略 - if (!dirEntries) continue; - const data = block.event.data; - const dir = dirEntries[0]; - const route = now + "_" + i.replace(",", "_") + "_" + dir; - const target = data.floorId + "_" + data.loc.join("_"); + const res = {}; // 输出结果,格式:floorId_x_y_dir: floorId_x_y + const enemies = {}; + const upOrDown = {}; + const mapdir = {}; + // 开始循环搜索 + while (nowDepth < depth && stack.length > 0) { + const now = stack.shift(); // 当前id + if (core.status.maps[now].deleted) continue; + mapdir[now] = mapdir[now] ?? []; + const blocks = core.getMapBlocksObj(now); // 获取当前地图的每点的事件 + enemies[now] = {}; + // 遍历,获取可以传送的点,只检测绿点事件,因此可用红点事件进行传送来实现分区功能 + for (const i in blocks) { + const block = blocks[i]; + // 整合漏怪检测,所以要检测怪物 + if (block.event.trigger === "battle") { + const id = block.event.id; + if (ignoreEnemies.includes(id)) continue; + else enemies[now][i] = block.event.id; + continue; + } + // 检测触发器是否为切换楼层,不是则直接跳过 + if (block.event.trigger !== "changeFloor") continue; + const dirEntries = allChangeEntries.find( + (v) => v[1] === block.event.id + ); + // 如果不是那六种传送门,直接忽略 + if (!dirEntries) continue; + const data = block.event.data; + const dir = dirEntries[0]; + const route = now + "_" + i.replace(",", "_") + "_" + dir; + const target = data.floorId + "_" + data.loc.join("_"); - mapdir[now].push(dir); - if (!used[data.floorId]) { - if (dir === "upFloor" || dir === "downFloor") { - upOrDown[now] = upOrDown[id] ?? []; - upOrDown[now].push(dir); - } + mapdir[now].push(dir); + if (!used[data.floorId]) { + if (dir === "upFloor" || dir === "downFloor") { + upOrDown[now] = upOrDown[id] ?? []; + upOrDown[now].push(dir); + } - queue.push(data.floorId); // 没有搜索过,则加入栈中 - mapOrder.push(data.floorId); - used[data.floorId] = true; - } - res[route] = target; - } - if (stack.length === 0) { - stack = queue; - queue = []; - nowDepth++; - } - if (stack.length === 0 && queue.length === 0) break; - } - return { res, order: mapOrder, enemies, upOrDown, mapdir }; - }; - /** - * 获取绘制信息 - * @param {string?} center 中心地图id - * @param {number?} depth 搜索深度 - * @param {boolean?} noCache 是否不使用缓存 - * @returns {MapDrawInfo} - */ - this.getMapDrawInfo = function ( - center = core.status.floorId, - depth = defaultValue.depth, - noCache = false - ) { - nowDepth = depth; - drawingMap = center; - const id = `${center}_${depth}_${noBorder}`; - // 检查缓存 - if (drawCache[id] && !noCache) return drawCache[id]; - const map = core.plugin.bfsSearch(center, depth, noCache); - mapCache[id] = map; - const res = getDrawInfo(map.res, center, map.order); - res.upOrDown = map.upOrDown; - res.mapdir = map.mapdir; - drawCache[id] = res; - return res; - }; - /** - * 提供地图的绘制信息 - * @param {{[x: string]: string}} map 要绘制的地图,格式:floorId_x_y_dir: floorId_x_y - * @param {string} center 中心地图的id - * @param {string[]} order 遍历顺序 - * @returns {MapDrawInfo} 地图的绘制信息 - */ + queue.push(data.floorId); // 没有搜索过,则加入栈中 + mapOrder.push(data.floorId); + used[data.floorId] = true; + } + res[route] = target; + } + if (stack.length === 0) { + stack = queue; + queue = []; + nowDepth++; + } + if (stack.length === 0 && queue.length === 0) break; + } + return { res, order: mapOrder, enemies, upOrDown, mapdir }; + }; + /** + * 获取绘制信息 + * @param {string?} center 中心地图id + * @param {number?} depth 搜索深度 + * @param {boolean?} noCache 是否不使用缓存 + * @returns {MapDrawInfo} + */ + this.getMapDrawInfo = function ( + center = core.status.floorId, + depth = defaultValue.depth, + noCache = false + ) { + nowDepth = depth; + drawingMap = center; + const id = `${center}_${depth}_${noBorder}`; + // 检查缓存 + if (drawCache[id] && !noCache) return drawCache[id]; + const map = core.plugin.bfsSearch(center, depth, noCache); + mapCache[id] = map; + const res = getDrawInfo(map.res, center, map.order); + res.upOrDown = map.upOrDown; + res.mapdir = map.mapdir; + drawCache[id] = res; + return res; + }; + /** + * 提供地图的绘制信息 + * @param {{[x: string]: string}} map 要绘制的地图,格式:floorId_x_y_dir: floorId_x_y + * @param {string} center 中心地图的id + * @param {string[]} order 遍历顺序 + * @returns {MapDrawInfo} 地图的绘制信息 + */ - function getDrawInfo(map, center, order) { - // 先根据地图id分类,从而确定每个地图连接哪些地图,同时方便处理 - const links = {}; - for (const i in map) { - const splitted = i.split("_"); - const id = splitted[0]; - if (!links[id]) links[id] = {}; - links[id][i] = map[i]; - } + function getDrawInfo(map, center, order) { + // 先根据地图id分类,从而确定每个地图连接哪些地图,同时方便处理 + const links = {}; + for (const i in map) { + const splitted = i.split("_"); + const id = splitted[0]; + if (!links[id]) links[id] = {}; + links[id][i] = map[i]; + } - // 分类完毕,然后根据连接点先计算出各个地图的坐标,然后再进行判断 - const centerFloor = core.status.maps[center]; - const visitedCenter = core.hasVisitedFloor(center); - const locs = { - // 格式:[中心x, 中心y, 宽, 高, 是否到达过] - [center]: [2, 2, 1, 1, visitedCenter], - }; - // 可以上楼下楼的地图 - const upOrDown = {}; - for (const id of order) { - const now = links[id]; - // 遍历每一个地图的连接情况 - for (const from in now) { - const to = now[from]; - // 先根据from to计算物理位置 - const fromData = from.split("_"), - toData = to.split("_"); - const dir = fromData[3]; - if (dir === "upFloor" || dir === "downFloor") continue; - if (!defaultChange[dir]) continue; - const v = dirData[dir][1], // 竖直数值 - h = dirData[dir][0], // 水平数值 - ha = Math.abs(h), - va = Math.abs(v); - const ff = id, // fromFloorId - tf = toData[0]; // toFloorId - const fromFloor = core.status.maps[ff], - toFloor = core.status.maps[tf]; - const fhw = Math.floor(fromFloor.width / 2), // fromFloorHalfWidth - fhh = Math.floor(fromFloor.height / 2), - thw = Math.floor(toFloor.width / 2), - thh = Math.floor(toFloor.height / 2); - const fLoc = locs[id] ?? [0, 0]; - if (!locs[ff]) continue; - let x, y; - if (locs && locs[tf]) { - x = locs[tf][0]; - y = locs[tf][1]; - } else { - // 计算坐标,公式可以通过画图推断出 - x = fLoc[0] - v; - y = fLoc[1] - h; - } - locs[tf] = locs[tf] ?? [x, y, 1, 1, core.hasVisitedFloor(tf)]; - } - } - // 获取地图绘制需要的长宽 - let width = 0, - height = 0; - let left, right, up, down; - for (const id in locs) { - const [x, y, w, h] = locs[id]; - if (left === void 0) { - left = right = x; - up = down = y; - } - left = Math.min(x - 1, left); - right = Math.max(x + 1, right); - up = Math.min(y - 1, up); - down = Math.max(y + 1, down); - } - width = right - left; - height = down - up; + // 分类完毕,然后根据连接点先计算出各个地图的坐标,然后再进行判断 + const centerFloor = core.status.maps[center]; + const visitedCenter = core.hasVisitedFloor(center); + const locs = { + // 格式:[中心x, 中心y, 宽, 高, 是否到达过] + [center]: [2, 2, 1, 1, visitedCenter], + }; + // 可以上楼下楼的地图 + const upOrDown = {}; + for (const id of order) { + const now = links[id]; + // 遍历每一个地图的连接情况 + for (const from in now) { + const to = now[from]; + // 先根据from to计算物理位置 + const fromData = from.split("_"), + toData = to.split("_"); + const dir = fromData[3]; + if (dir === "upFloor" || dir === "downFloor") continue; + if (!defaultChange[dir]) continue; + const v = dirData[dir][1], // 竖直数值 + h = dirData[dir][0], // 水平数值 + ha = Math.abs(h), + va = Math.abs(v); + const ff = id, // fromFloorId + tf = toData[0]; // toFloorId + const fromFloor = core.status.maps[ff], + toFloor = core.status.maps[tf]; + const fhw = Math.floor(fromFloor.width / 2), // fromFloorHalfWidth + fhh = Math.floor(fromFloor.height / 2), + thw = Math.floor(toFloor.width / 2), + thh = Math.floor(toFloor.height / 2); + const fLoc = locs[id] ?? [0, 0]; + if (!locs[ff]) continue; + let x, y; + if (locs && locs[tf]) { + x = locs[tf][0]; + y = locs[tf][1]; + } else { + // 计算坐标,公式可以通过画图推断出 + x = fLoc[0] - v; + y = fLoc[1] - h; + } + locs[tf] = locs[tf] ?? [x, y, 1, 1, core.hasVisitedFloor(tf)]; + } + } + // 获取地图绘制需要的长宽 + let width = 0, + height = 0; + let left, right, up, down; + for (const id in locs) { + const [x, y, w, h] = locs[id]; + if (left === void 0) { + left = right = x; + up = down = y; + } + left = Math.min(x - 1, left); + right = Math.max(x + 1, right); + up = Math.min(y - 1, up); + down = Math.max(y + 1, down); + } + width = right - left; + height = down - up; - return { locs, width, height, layer: upOrDown }; - } + return { locs, width, height, layer: upOrDown }; + } - function mapblock(mapdir) { - let mb = ""; - if (mapdir.includes("up")) mb += "u"; - if (mapdir.includes("down")) mb += "d"; - if (mapdir.includes("left")) mb += "l"; - if (mapdir.includes("right")) mb += "r"; - return mb ? mb + ".webp" : "null.webp"; - } - core.animateFrame.globalAlphaFloor = 0; - core.animateFrame.globalAlphaFloorStatus = 1; + function mapblock(mapdir) { + let mb = ""; + if (mapdir.includes("up")) mb += "u"; + if (mapdir.includes("down")) mb += "d"; + if (mapdir.includes("left")) mb += "l"; + if (mapdir.includes("right")) mb += "r"; + return mb ? mb + ".webp" : "null.webp"; + } + core.animateFrame.globalAlphaFloor = 0; + core.animateFrame.globalAlphaFloorStatus = 1; - const tesk = document.createElement("canvas"); - tesk.width = 300; - tesk.height = 300; - const teskctx = tesk.getContext("2d"); + const tesk = document.createElement("canvas"); + tesk.width = 300; + tesk.height = 300; + const teskctx = tesk.getContext("2d"); - let line = 50; - teskctx.strokeStyle = "green"; - teskctx.fillStyle = "green"; - let now = 0; - core.registerAnimationFrame("tesk", true, function (timestamp) { - if (timestamp - now > 1000 / 60) { - now = timestamp; - core.clearMap(teskctx); - teskctx.lineWidth = 150 - line; + let line = 50; + teskctx.strokeStyle = "green"; + teskctx.fillStyle = "green"; + let now = 0; + core.registerAnimationFrame("tesk", true, function (timestamp) { + if (timestamp - now > 1000 / 60) { + now = timestamp; + core.clearMap(teskctx); + teskctx.lineWidth = 150 - line; - if (line <= 150) { - teskctx.beginPath(); - teskctx.arc(150, 150, line, 0, Math.PI * 2); - line += 2; - teskctx.stroke(); - } else { - teskctx.beginPath(); - teskctx.arc(150, 150, line - 150, 0, Math.PI * 2); - line += 2; - teskctx.fill(); - if (line >= 250) line = 50; - } - } - }); + if (line <= 150) { + teskctx.beginPath(); + teskctx.arc(150, 150, line, 0, Math.PI * 2); + line += 2; + teskctx.stroke(); + } else { + teskctx.beginPath(); + teskctx.arc(150, 150, line - 150, 0, Math.PI * 2); + line += 2; + teskctx.fill(); + if (line >= 250) line = 50; + } + } + }); - /** - * 绘制小地图 - * @param {MapDrawInfo} info 地图绘制信息 - * @param {number} scale 地图的绘制比例 - */ - this.drawSmallMap = function ( - ctx, - info, - center, - sx, - sy, - sw, - sh, - scale = defaultValue.scale - ) { - core.clearMap(ctx, sx, sy, sw + 40, sh + 60); - if (core.domStyle.isVertical) { - sy += 50; - sx += 15; - } else { - sy += 60; - sx += 30; - } - core.fillRect(ctx, sx, sy, sw, sh, "#000"); - core.strokeRect(ctx, sx, sy, sw, sh, "#fff", 5); - core.setTextAlign("outerUI", "center"); - core.fillBoldText1( - ctx, - core.status.maps[center].areas, - sx + sw / 2, - sy - 10, - "#FFFFFF", - "#000000", - 6, - "bold 42px Verdana" - ); - const locs = info.locs; - for (const id in locs) { - const loc = locs[id]; - let color = "#000"; - if (!loc[4]) color = "#f0f"; - const [x, y, w, h] = loc.map((v) => typeof v === "number" && v * scale); - const fx = x + sx, - fy = y + sy; - const mapdir = info.mapdir[id]; - const img = mapblock(mapdir); - if (x < 0 || x > 4 * scale || y < 0 || y > 4 * scale) continue; - core.drawImage(ctx, img, 0, 0, 60, 60, fx, fy, w, h); - const layer = info.upOrDown[id]; - const min = Math.min(w, h); - if (core.getFlag("任务地点") && core.getFlag("任务地点") === id) - ctx.drawImage(tesk, fx + min / 4, fy + min / 4, min / 2, min / 2); - if (layer?.includes("upFloor")) - core.drawIcon( - ctx, - defaultChange.upFloor, - fx + min / 4, - fy + min / 4, - min / 2, - min / 2 - ); - if (layer?.includes("downFloor")) - core.drawIcon( - ctx, - defaultChange.downFloor, - fx + min / 4, - fy + min / 4, - min / 2, - min / 2 - ); + /** + * 绘制小地图 + * @param {MapDrawInfo} info 地图绘制信息 + * @param {number} scale 地图的绘制比例 + */ + this.drawSmallMap = function ( + ctx, + info, + center, + sx, + sy, + sw, + sh, + scale = defaultValue.scale + ) { + core.clearMap(ctx, sx, sy, sw + 40, sh + 60); + if (core.domStyle.isVertical) { + sy += 50; + sx += 15; + } else { + sy += 60; + sx += 30; + } + core.fillRect(ctx, sx, sy, sw, sh, "#000"); + core.strokeRect(ctx, sx, sy, sw, sh, "#fff", 5); + core.setTextAlign("outerUI", "center"); + core.fillBoldText1( + ctx, + core.status.maps[center].areas, + sx + sw / 2, + sy - 10, + "#FFFFFF", + "#000000", + 6, + "bold 42px Verdana" + ); + const locs = info.locs; + for (const id in locs) { + const loc = locs[id]; + let color = "#000"; + if (!loc[4]) color = "#f0f"; + const [x, y, w, h] = loc.map((v) => typeof v === "number" && v * scale); + const fx = x + sx, + fy = y + sy; + const mapdir = info.mapdir[id]; + const img = mapblock(mapdir); + if (x < 0 || x > 4 * scale || y < 0 || y > 4 * scale) continue; + core.drawImage(ctx, img, 0, 0, 60, 60, fx, fy, w, h); + const layer = info.upOrDown[id]; + const min = Math.min(w, h); - // 显示漏怪数量 - if (core.getFlag("showEnemy")) { - ctx.textAlign = "center"; - ctx.textBaseline = "middle"; - const c = drawingMap + "_" + nowDepth + "_" + noBorder; - const n = Object.keys(mapCache[c].enemies[id]).length; - color = "#fff"; - if (n > 10) color = "#fc3"; - if (n > 20) color = "#f22"; - ctx.shadowBlur = 0.6 * nowScale; - ctx.shadowColor = "#000"; - if (n > 0) - core.fillText( - ctx, - n, - fx + (w * 3) / 10, - fy + (h * 7) / 10, - color, - 22 + "px normal" - ); - ctx.shadowBlur = 0; - } - } - }; - }, + + if (layer?.includes("upFloor")) + core.drawIcon( + ctx, + defaultChange.upFloor, + fx + min / 4, + fy + min / 4, + min / 2, + min / 2 + ); + if (layer?.includes("downFloor")) + core.drawIcon( + ctx, + defaultChange.downFloor, + fx + min / 4, + fy + min / 4, + min / 2, + min / 2 + ); + if (core.getFlag("任务地点") && core.getFlag("任务地点") === id) + ctx.drawImage(tesk, fx + min / 4, fy + min / 4, min / 2, min / 2); + if (id === core.status.floorId) core.drawImage(ctx, "hero.webp", + 0, + 0, + 32, + 19, fx + min / 4, fy + min * 5 / 16, 32, 19); + // 显示漏怪数量 + if (core.getFlag("showEnemy")) { + ctx.textAlign = "center"; + ctx.textBaseline = "middle"; + const c = drawingMap + "_" + nowDepth + "_" + noBorder; + const n = Object.keys(mapCache[c].enemies[id]).length; + color = "#fff"; + if (n > 10) color = "#fc3"; + if (n > 20) color = "#f22"; + ctx.shadowBlur = 0.6 * nowScale; + ctx.shadowColor = "#000"; + if (n > 0) + core.fillText( + ctx, + n, + fx + (w * 3) / 10, + fy + (h * 7) / 10, + color, + 22 + "px normal" + ); + ctx.shadowBlur = 0; + } + if (!core.hasVisitedFloor(id)) { + core.fillRect(ctx, fx, fy, w, h, "rgba(0,0,0,0.7)") + core.fillText(ctx, "?", fx + min / 2, fy + min * 3 / 4, "#FFFFFF", "bold 42px Verdana") + } + } + }; +}, "楼传": function () { // 在此增加新插件 @@ -13528,20 +13301,30 @@ var plugins_bb40132b_638b_4a9f_b028_d3fe47acc8d1 = if ( !core.status.maps[core.floorIds[page]].canFlyTo || !core.hasVisitedFloor(core.floorIds[page]) - ) + ) { + /*core.fillRect("ui", 58, + 58, + size - 50, + size - 50, "rgba(0,0,0,0.5)")*/ + core.getContextByName("ui").globalAlpha = 0.7 + core.drawImage( "ui", - "lock.webp", + "miwu.webp", 0, 0, size, size, 58, 58, - size - 8, - size - 8 + size - 50, + size - 50 ); - + core.getContextByName("ui").globalAlpha = 1 + /*core.fillText("ui", '?', 188, + 278, + "rgba(255,255,255,0.2)", this._buildFont(250, true))*/ + } core.fillRoundRect( "ui", 15 + 44 - 2, @@ -13767,7 +13550,7 @@ var plugins_bb40132b_638b_4a9f_b028_d3fe47acc8d1 = title, 15 - 4 + size - 4 + 45, 85, - "#FFFFFF", + core.hasVisitedFloor(floorId) ? "#FFFFFF" : "#444444", "#000000", 18 ); @@ -14258,511 +14041,508 @@ var plugins_bb40132b_638b_4a9f_b028_d3fe47acc8d1 = ); }, "CG回廊": function () { - // 在此增加新插件 - const CGUI = document.createElement("canvas"); //CGui画布设置 - CGUI.style.position = "absolute"; - CGUI.style.zIndex = 300; - CGUI.style.display = "none"; - CGUI.id = "CGUI"; - main.dom.gameGroup.insertAdjacentElement("afterend", CGUI); - CGUI.style.top = "50%"; - CGUI.style.left = "50%"; - CGUI.style.transform = "translate(-50%,-50%)"; - const ctx = CGUI.getContext("2d"); - main.dom.CGUI = CGUI; - let page = 0; //初始页面 - let show = false; //展示状态 - CGUI.onclick = function (e) { - try { - e.preventDefault(); - if (core.isPlaying()) return false; - const left = core.dom.gameGroup.offsetLeft; - const top = core.dom.gameGroup.offsetTop; - const px = Math.floor((e.clientX - left) / core.domStyle.scale), - py = Math.floor((e.clientY - top) / core.domStyle.scale); - core.ui.CG.onclick(px * 3, py * 3); - } catch (ee) { - main.log(ee); - } - }; + // 在此增加新插件 + const CGUI = document.createElement("canvas"); //CGui画布设置 + CGUI.style.position = "absolute"; + CGUI.style.zIndex = 300; + CGUI.style.display = "none"; + CGUI.id = "CGUI"; + main.dom.gameGroup.insertAdjacentElement("afterend", CGUI); + CGUI.style.top = "50%"; + CGUI.style.left = "50%"; + CGUI.style.transform = "translate(-50%,-50%)"; + const ctx = CGUI.getContext("2d"); + main.dom.CGUI = CGUI; + let page = 0; //初始页面 + let show = false; //展示状态 + CGUI.onclick = function (e) { + try { + e.preventDefault(); + if (core.isPlaying()) return false; + const left = core.dom.gameGroup.offsetLeft; + const top = core.dom.gameGroup.offsetTop; + const px = Math.floor((e.clientX - left) / core.domStyle.scale), + py = Math.floor((e.clientY - top) / core.domStyle.scale); + core.ui.CG.onclick(px * 3, py * 3); + } catch (ee) { + main.log(ee); + } + }; - class CG { - constructor() { - this.cgs; - //cg列表 - this.UIMx = [ - //空位用‘none’填充,当前ui3*2 - [ - ["eve_010102.webp", "eve_010203.webp", "eve_010304.webp"], - ["eve_010501.webp", "eve_010601.webp", "eve_010701.webp"], - ], - [ - ["eve_010801.webp", "eve_010902.webp", "eve_011001.webp"], - ["eve_011101.webp", "eve_011202.webp", "eve_011302.webp"], - ], - [ - ["eve_011402.webp", "eve_020102.webp", "eve_020201.webp"], - ["eve_020301.webp", "eve_020401.webp", "eve_020501.webp"], - ], - [ - ["eve_020605.webp", "eve_020701.webp", "eve_020801.webp"], - ["eve_030101.webp", "eve_030206.webp", "eve_030302.webp"], - ], - [ - ["eve_030508.webp", "eve_030601.webp", "eve_030801.webp"], - ["eve_030901.webp", "eve_031002.webp", "eve_031101.webp"], - ], - [ - ["eve_040201.webp", "eve_040401.webp", "eve_040501.webp"], - ["eve_040601.webp", "eve_040702.webp", "eve_040801.webp"], - ], - [ - ["eve_050101.webp", "eve_050201.webp", "eve_050401.webp"], - ["eve_050501.webp", "eve_050601.webp", "eve_050704.webp"], - ], - [ - ["eve_050801.webp", "eve_070101.webp", "bg_1511.webp"], - ["bg_1521.webp", "bg_2011.webp", "bg_2521.webp"], - ], - [ - ["bg_3042.webp", "bg_3551.webp", "bg_3571.webp"], - ["bg_3721.webp", "bg_5033.webp", "bg_5044.webp"], - ], - ]; - } + class CG { + constructor() { + this.cgs; + //cg列表 + this.UIMx = [ + //空位用‘none’填充,当前ui3*2 + [ + ["eve_010102.webp", "eve_010203.webp", "eve_010304.webp"], + ["eve_010501.webp", "eve_010601.webp", "eve_010701.webp"], + ], + [ + ["eve_010801.webp", "eve_010902.webp", "eve_011001.webp"], + ["eve_011101.webp", "eve_011202.webp", "eve_011302.webp"], + ], + [ + ["eve_011402.webp", "eve_020102.webp", "eve_020201.webp"], + ["eve_020301.webp", "eve_020401.webp", "eve_020501.webp"], + ], + [ + ["eve_020605.webp", "eve_020701.webp", "eve_020801.webp"], + ["eve_030101.webp", "eve_030206.webp", "eve_030302.webp"], + ], + [ + ["eve_030508.webp", "eve_030601.webp", "eve_030801.webp"], + ["eve_030901.webp", "eve_031002.webp", "eve_031101.webp"], + ], + [ + ["eve_040201.webp", "eve_040401.webp", "eve_040501.webp"], + ["eve_040601.webp", "eve_040702.webp", "eve_040801.webp"], + ], + [ + ["eve_050101.webp", "eve_050201.webp", "eve_050401.webp"], + ["eve_050501.webp", "eve_050601.webp", "eve_050704.webp"], + ], + [ + ["eve_050801.webp", "eve_070101.webp", "bg_1511.webp"], + ["bg_1521.webp", "bg_2011.webp", "bg_2521.webp"], + ], + [ + ["bg_3042.webp", "bg_3551.webp", "bg_3571.webp"], + ["bg_3721.webp", "bg_5033.webp", "bg_5044.webp"], + ], + ]; + } - //更新 - update() { - this.background(); - this.drawUI(); - } - background() { - //画布大小设置 - if (core.domStyle.isVertical) { - CGUI.width = 1248; - CGUI.height = 2028; - } else { - CGUI.width = 2028; - CGUI.height = 1248; - } - core.setTextAlign(ctx, "center"); - } - onclick(px, py) { - //点击 + //更新 + update() { + this.background(); + this.drawUI(); + } + background() { + //画布大小设置 + if (core.domStyle.isVertical) { + CGUI.width = 1248; + CGUI.height = 2028; + } else { + CGUI.width = 2028; + CGUI.height = 1248; + } + core.setTextAlign(ctx, "center"); + } + onclick(px, py) { + //点击 - if (show) { - show = !show; - core.clearMap(ctx); - this.update(); - return; - } - const makeBox = ([x, y], [w, h]) => { - return [ - [x, y], - [x + w, y + h], - ]; - }; - const inRect = ([x, y], [ - [sx, sy], - [dx, dy] - ]) => { - return sx <= x && x <= dx && sy <= y && y <= dy; - }; - const pos = [px, py]; - const backbox = makeBox([15, 35], [210, 90]); - if (inRect(pos, backbox)) { - //离开按钮是一致的,其余的记区分横竖屏 - CGUI.style.display = "none"; - core.clearMap(ctx); - core.restart(); - return; - } - if (core.domStyle.isVertical) { - //竖屏 - const pageupbox = makeBox([200, 1830], [200, 100]); - const pagedownbox = makeBox([900, 1830], [200, 100]); + if (show) { + show = !show; + core.clearMap(ctx); + this.update(); + return; + } + const makeBox = ([x, y], [w, h]) => { + return [ + [x, y], + [x + w, y + h], + ]; + }; + const inRect = ([x, y], [[sx, sy], [dx, dy]]) => { + return sx <= x && x <= dx && sy <= y && y <= dy; + }; + const pos = [px, py]; + const backbox = makeBox([15, 35], [210, 90]); + if (inRect(pos, backbox)) { + //离开按钮是一致的,其余的记区分横竖屏 + CGUI.style.display = "none"; + core.clearMap(ctx); + core.restart(); + return; + } + if (core.domStyle.isVertical) { + //竖屏 + const pageupbox = makeBox([200, 1830], [200, 100]); + const pagedownbox = makeBox([900, 1830], [200, 100]); - const imagebox0 = makeBox([50, 200], [560, 420]); - const imagebox1 = makeBox([50, 750], [560, 420]); - const imagebox2 = makeBox([50, 1300], [560, 420]); + const imagebox0 = makeBox([50, 200], [560, 420]); + const imagebox1 = makeBox([50, 750], [560, 420]); + const imagebox2 = makeBox([50, 1300], [560, 420]); - const imagebox3 = makeBox([650, 200], [560, 420]); - const imagebox4 = makeBox([650, 750], [560, 420]); - const imagebox5 = makeBox([650, 1300], [560, 420]); - if (inRect(pos, pagedownbox)) { - //2代表当前最大页数-1 - if (page < this.UIMx.length - 1) { - page++; - core.clearMap(ctx); - this.update(); - } - } else if (inRect(pos, pageupbox)) { - if (page > 0) { - page--; - core.clearMap(ctx); - this.update(); - } - } else if (inRect(pos, imagebox0)) { - if (this.cgs.includes(this.UIMx[page][0][0])) { - const img = core.material.images.images[this.UIMx[page][0][0]]; - if (img) { - ctx.save(); //保存设置 - ctx.translate(1248, 0); //重新定位右上角为基准 - ctx.rotate(Math.PI / 2); //旋转90度 - ctx.drawImage(img, 0, 0, 2028, 1248); - ctx.restore(); //重置画布设置 - show = !show; - } - } - } else if (inRect(pos, imagebox1)) { - if (this.cgs.includes(this.UIMx[page][0][1])) { - const img = core.material.images.images[this.UIMx[page][0][1]]; - if (img) { - ctx.save(); //保存设置 - ctx.translate(1248, 0); //重新定位右上角为基准 - ctx.rotate(Math.PI / 2); //旋转90度 - ctx.drawImage(img, 0, 0, 2028, 1248); - ctx.restore(); //重置画布设置 - show = !show; - } - } - } else if (inRect(pos, imagebox2)) { - if (this.cgs.includes(this.UIMx[page][0][2])) { - const img = core.material.images.images[this.UIMx[page][0][2]]; - if (img) { - ctx.save(); //保存设置 - ctx.translate(1248, 0); //重新定位右上角为基准 - ctx.rotate(Math.PI / 2); //旋转90度 - ctx.drawImage(img, 0, 0, 2028, 1248); - ctx.restore(); //重置画布设置 - show = !show; - } - } - } else if (inRect(pos, imagebox3)) { - if (this.cgs.includes(this.UIMx[page][1][0])) { - const img = core.material.images.images[this.UIMx[page][1][0]]; - if (img) { - ctx.save(); //保存设置 - ctx.translate(1248, 0); //重新定位右上角为基准 - ctx.rotate(Math.PI / 2); //旋转90度 - ctx.drawImage(img, 0, 0, 2028, 1248); - ctx.restore(); //重置画布设置 - show = !show; - } - } - } else if (inRect(pos, imagebox4)) { - if (this.cgs.includes(this.UIMx[page][1][1])) { - const img = core.material.images.images[this.UIMx[page][1][1]]; - if (img) { - ctx.save(); //保存设置 - ctx.translate(1248, 0); //重新定位右上角为基准 - ctx.rotate(Math.PI / 2); //旋转90度 - ctx.drawImage(img, 0, 0, 2028, 1248); - ctx.restore(); //重置画布设置 - show = !show; - } - } - } else if (inRect(pos, imagebox5)) { - if (this.cgs.includes(this.UIMx[page][1][2])) { - const img = core.material.images.images[this.UIMx[page][1][2]]; - if (img) { - ctx.save(); //保存设置 - ctx.translate(1248, 0); //重新定位右上角为基准 - ctx.rotate(Math.PI / 2); //旋转90度 - ctx.drawImage(img, 0, 0, 2028, 1248); - ctx.restore(); //重置画布设置 - show = !show; - } - } - } - } else { - const pageupbox = makeBox([200, 1110], [200, 100]); - const pagedownbox = makeBox([1600, 1110], [200, 100]); - const imagebox0 = makeBox([75, 150], [600, 450]); - const imagebox1 = makeBox([725, 150], [600, 450]); - const imagebox2 = makeBox([1300, 150], [600, 450]); - const imagebox3 = makeBox([75, 650], [600, 450]); - const imagebox4 = makeBox([725, 650], [600, 450]); - const imagebox5 = makeBox([1375, 650], [600, 450]); - if (inRect(pos, pagedownbox)) { - if (page < this.UIMx.length - 1) { - page++; - core.clearMap(ctx); - this.update(); - } - } else if (inRect(pos, pageupbox)) { - if (page > 0) { - page--; - core.clearMap(ctx); - this.update(); - } - } else if (inRect(pos, imagebox0)) { - if (this.cgs.includes(this.UIMx[page][0][0])) { - const img = core.material.images.images[this.UIMx[page][0][0]]; - if (img) { - ctx.drawImage(img, 0, 0, 2028, 1248); - show = !show; - } - } - } else if (inRect(pos, imagebox1)) { - if (this.cgs.includes(this.UIMx[page][0][1])) { - const img = core.material.images.images[this.UIMx[page][0][1]]; - if (img) { - ctx.drawImage(img, 0, 0, 2028, 1248); - show = !show; - } - } - } else if (inRect(pos, imagebox2)) { - if (this.cgs.includes(this.UIMx[page][0][2])) { - const img = core.material.images.images[this.UIMx[page][0][2]]; - if (img) { - ctx.drawImage(img, 0, 0, 2028, 1248); - show = !show; - } - } - } else if (inRect(pos, imagebox3)) { - if (this.cgs.includes(this.UIMx[page][1][0])) { - const img = core.material.images.images[this.UIMx[page][1][0]]; - if (img) { - ctx.drawImage(img, 0, 0, 2028, 1248); - show = !show; - } - } - } else if (inRect(pos, imagebox4)) { - if (this.cgs.includes(this.UIMx[page][1][1])) { - const img = core.material.images.images[this.UIMx[page][1][1]]; - if (img) { - ctx.drawImage(img, 0, 0, 2028, 1248); - show = !show; - } - } - } else if (inRect(pos, imagebox5)) { - if (this.cgs.includes(this.UIMx[page][1][2])) { - const img = core.material.images.images[this.UIMx[page][1][2]]; - if (img) { - ctx.drawImage(img, 0, 0, 2028, 1248); - show = !show; - } - } - } - } - } - drawUI() { - //绘制页面 - core.clearMap(CGUI); - const bgVertical = core.material.images.images["bg_2010.webp"]; //竖屏背景 - const bg = core.material.images.images["bg_5043.webp"]; //横屏背景 + const imagebox3 = makeBox([650, 200], [560, 420]); + const imagebox4 = makeBox([650, 750], [560, 420]); + const imagebox5 = makeBox([650, 1300], [560, 420]); + if (inRect(pos, pagedownbox)) { + //2代表当前最大页数-1 + if (page < this.UIMx.length - 1) { + page++; + core.clearMap(ctx); + this.update(); + } + } else if (inRect(pos, pageupbox)) { + if (page > 0) { + page--; + core.clearMap(ctx); + this.update(); + } + } else if (inRect(pos, imagebox0)) { + if (this.cgs.includes(this.UIMx[page][0][0])) { + const img = core.material.images.images[this.UIMx[page][0][0]]; + if (img) { + ctx.save(); //保存设置 + ctx.translate(1248, 0); //重新定位右上角为基准 + ctx.rotate(Math.PI / 2); //旋转90度 + ctx.drawImage(img, 0, 0, 2028, 1248); + ctx.restore(); //重置画布设置 + show = !show; + } + } + } else if (inRect(pos, imagebox1)) { + if (this.cgs.includes(this.UIMx[page][0][1])) { + const img = core.material.images.images[this.UIMx[page][0][1]]; + if (img) { + ctx.save(); //保存设置 + ctx.translate(1248, 0); //重新定位右上角为基准 + ctx.rotate(Math.PI / 2); //旋转90度 + ctx.drawImage(img, 0, 0, 2028, 1248); + ctx.restore(); //重置画布设置 + show = !show; + } + } + } else if (inRect(pos, imagebox2)) { + if (this.cgs.includes(this.UIMx[page][0][2])) { + const img = core.material.images.images[this.UIMx[page][0][2]]; + if (img) { + ctx.save(); //保存设置 + ctx.translate(1248, 0); //重新定位右上角为基准 + ctx.rotate(Math.PI / 2); //旋转90度 + ctx.drawImage(img, 0, 0, 2028, 1248); + ctx.restore(); //重置画布设置 + show = !show; + } + } + } else if (inRect(pos, imagebox3)) { + if (this.cgs.includes(this.UIMx[page][1][0])) { + const img = core.material.images.images[this.UIMx[page][1][0]]; + if (img) { + ctx.save(); //保存设置 + ctx.translate(1248, 0); //重新定位右上角为基准 + ctx.rotate(Math.PI / 2); //旋转90度 + ctx.drawImage(img, 0, 0, 2028, 1248); + ctx.restore(); //重置画布设置 + show = !show; + } + } + } else if (inRect(pos, imagebox4)) { + if (this.cgs.includes(this.UIMx[page][1][1])) { + const img = core.material.images.images[this.UIMx[page][1][1]]; + if (img) { + ctx.save(); //保存设置 + ctx.translate(1248, 0); //重新定位右上角为基准 + ctx.rotate(Math.PI / 2); //旋转90度 + ctx.drawImage(img, 0, 0, 2028, 1248); + ctx.restore(); //重置画布设置 + show = !show; + } + } + } else if (inRect(pos, imagebox5)) { + if (this.cgs.includes(this.UIMx[page][1][2])) { + const img = core.material.images.images[this.UIMx[page][1][2]]; + if (img) { + ctx.save(); //保存设置 + ctx.translate(1248, 0); //重新定位右上角为基准 + ctx.rotate(Math.PI / 2); //旋转90度 + ctx.drawImage(img, 0, 0, 2028, 1248); + ctx.restore(); //重置画布设置 + show = !show; + } + } + } + } else { + const pageupbox = makeBox([200, 1110], [200, 100]); + const pagedownbox = makeBox([1600, 1110], [200, 100]); + const imagebox0 = makeBox([75, 150], [600, 450]); + const imagebox1 = makeBox([725, 150], [600, 450]); + const imagebox2 = makeBox([1300, 150], [600, 450]); + const imagebox3 = makeBox([75, 650], [600, 450]); + const imagebox4 = makeBox([725, 650], [600, 450]); + const imagebox5 = makeBox([1375, 650], [600, 450]); + if (inRect(pos, pagedownbox)) { + if (page < this.UIMx.length - 1) { + page++; + core.clearMap(ctx); + this.update(); + } + } else if (inRect(pos, pageupbox)) { + if (page > 0) { + page--; + core.clearMap(ctx); + this.update(); + } + } else if (inRect(pos, imagebox0)) { + if (this.cgs.includes(this.UIMx[page][0][0])) { + const img = core.material.images.images[this.UIMx[page][0][0]]; + if (img) { + ctx.drawImage(img, 0, 0, 2028, 1248); + show = !show; + } + } + } else if (inRect(pos, imagebox1)) { + if (this.cgs.includes(this.UIMx[page][0][1])) { + const img = core.material.images.images[this.UIMx[page][0][1]]; + if (img) { + ctx.drawImage(img, 0, 0, 2028, 1248); + show = !show; + } + } + } else if (inRect(pos, imagebox2)) { + if (this.cgs.includes(this.UIMx[page][0][2])) { + const img = core.material.images.images[this.UIMx[page][0][2]]; + if (img) { + ctx.drawImage(img, 0, 0, 2028, 1248); + show = !show; + } + } + } else if (inRect(pos, imagebox3)) { + if (this.cgs.includes(this.UIMx[page][1][0])) { + const img = core.material.images.images[this.UIMx[page][1][0]]; + if (img) { + ctx.drawImage(img, 0, 0, 2028, 1248); + show = !show; + } + } + } else if (inRect(pos, imagebox4)) { + if (this.cgs.includes(this.UIMx[page][1][1])) { + const img = core.material.images.images[this.UIMx[page][1][1]]; + if (img) { + ctx.drawImage(img, 0, 0, 2028, 1248); + show = !show; + } + } + } else if (inRect(pos, imagebox5)) { + if (this.cgs.includes(this.UIMx[page][1][2])) { + const img = core.material.images.images[this.UIMx[page][1][2]]; + if (img) { + ctx.drawImage(img, 0, 0, 2028, 1248); + show = !show; + } + } + } + } + } + drawUI() { + //绘制页面 + core.clearMap(CGUI); + const bgVertical = core.material.images.images["bg_2010.webp"]; //竖屏背景 + const bg = core.material.images.images["bg_5043.webp"]; //横屏背景 - if (core.domStyle.isVertical) { - //竖屏 + if (core.domStyle.isVertical) { + //竖屏 - core.fillRect(ctx, 0, 0, 1248, 2028, "#000000"); //黑色背景 - ctx.globalAlpha = 0.5; //透明度 - if (bgVertical) - ctx.drawImage(bgVertical, 0, 0, 1280, 1500, 0, 0, 1248, 2028); //绘制半透明背景图片 - ctx.globalAlpha = 1; //恢复为不透明 + core.fillRect(ctx, 0, 0, 1248, 2028, "#000000"); //黑色背景 + ctx.globalAlpha = 0.5; //透明度 + if (bgVertical) + ctx.drawImage(bgVertical, 0, 0, 1280, 1500, 0, 0, 1248, 2028); //绘制半透明背景图片 + ctx.globalAlpha = 1; //恢复为不透明 - core.setTextAlign(ctx, "center"); - core.fillBoldText1( - ctx, - "◀离开", - 100, - 110, - "#FFFFFF", - "#000000", - 6, - core.ui._buildFont(66, true) - ); + core.setTextAlign(ctx, "center"); + core.fillBoldText1( + ctx, + "◀离开", + 100, + 110, + "#FFFFFF", + "#000000", + 6, + core.ui._buildFont(66, true) + ); - core.fillBoldText1( - ctx, - "上一页", - 300, - 1900, - page === 0 ? "#444444" : "#FFFFFF", - "#000000", - 6, - core.ui._buildFont(66, true) - ); + core.fillBoldText1( + ctx, + "上一页", + 300, + 1900, + page === 0 ? "#444444" : "#FFFFFF", + "#000000", + 6, + core.ui._buildFont(66, true) + ); - core.fillBoldText1( - ctx, - page + 1 + "/" + this.UIMx.length, - 650, - 1900, - "#FFFFFF", - "#000000", - 6, - core.ui._buildFont(66, true) - ); - core.fillBoldText1( - ctx, - "下一页", - 1000, - 1900, - page === this.UIMx.length - 1 ? "#444444" : "#FFFFFF", - "#000000", - 6, - core.ui._buildFont(66, true) - ); - // 添加向上翻页和向下翻页的按钮 + core.fillBoldText1( + ctx, + page + 1 + "/" + this.UIMx.length, + 650, + 1900, + "#FFFFFF", + "#000000", + 6, + core.ui._buildFont(66, true) + ); + core.fillBoldText1( + ctx, + "下一页", + 1000, + 1900, + page === this.UIMx.length - 1 ? "#444444" : "#FFFFFF", + "#000000", + 6, + core.ui._buildFont(66, true) + ); + // 添加向上翻页和向下翻页的按钮 - // 添加3*2个4:3的画框,及图片 - for (let i = 0; i < 3; i++) { - for (let j = 0; j < 2; j++) { - const img = core.material.images.images[this.UIMx[page][j][i]]; - core.strokeRect( - ctx, - 50 + j * 600, - 200 + i * 550, - 560, - 420, - "#444444", - 5 - ); - if (this.cgs.includes(this.UIMx[page][j][i])) { - if (img) - ctx.drawImage( - img, - 50 + j * 600 + 15, - 200 + i * 550 + 15, - 560 - 30, - 420 - 30 - ); - } else { - ctx.fillStyle = "#000000"; - ctx.fillRect( - 50 + j * 600 + 15, - 200 + i * 550 + 15, - 560 - 30, - 420 - 30 - ); - const img = core.material.images.images["LOGO.webp"]; - if (img) - ctx.drawImage( - img, - 50 + j * 600 + 15, - 200 + i * 550 + 15, - 560 - 30, - 420 - 30 - ); - } - } - } - } else { - //横屏 - core.fillRect(ctx, 0, 0, 2028, 1248, "#000000"); //黑色背景 - ctx.globalAlpha = 0.5; //透明度 - if (bg) ctx.drawImage(bg, 0, 0, 1280, 720, 0, 0, 2028, 1248); //绘制半透明背景图片 - ctx.globalAlpha = 1; //恢复为不透明 + // 添加3*2个4:3的画框,及图片 + for (let i = 0; i < 3; i++) { + for (let j = 0; j < 2; j++) { + const img = core.material.images.images[this.UIMx[page][j][i]]; + core.strokeRect( + ctx, + 50 + j * 600, + 200 + i * 550, + 560, + 420, + "#444444", + 5 + ); + if (this.cgs.includes(this.UIMx[page][j][i])) { + if (img) + ctx.drawImage( + img, + 50 + j * 600 + 15, + 200 + i * 550 + 15, + 560 - 30, + 420 - 30 + ); + } else { + ctx.fillStyle = "#000000"; + ctx.fillRect( + 50 + j * 600 + 15, + 200 + i * 550 + 15, + 560 - 30, + 420 - 30 + ); + const img = core.material.images.images["LOGO.webp"]; + if (img) + ctx.drawImage( + img, + 50 + j * 600 + 15, + 200 + i * 550 + 15, + 560 - 30, + 420 - 30 + ); + } + } + } + } else { + //横屏 + core.fillRect(ctx, 0, 0, 2028, 1248, "#000000"); //黑色背景 + ctx.globalAlpha = 0.5; //透明度 + if (bg) ctx.drawImage(bg, 0, 0, 1280, 720, 0, 0, 2028, 1248); //绘制半透明背景图片 + ctx.globalAlpha = 1; //恢复为不透明 - core.setTextAlign(ctx, "center"); - core.fillBoldText1( - ctx, - "◀离开", - 110, - 100, - "#FFFFFF", - "#000000", - 6, - core.ui._buildFont(66, true) - ); + core.setTextAlign(ctx, "center"); + core.fillBoldText1( + ctx, + "◀离开", + 110, + 100, + "#FFFFFF", + "#000000", + 6, + core.ui._buildFont(66, true) + ); - // 添加向上翻页和向下翻页的按钮 - core.fillBoldText1( - ctx, - "上一页", - 300, - 1180, - page === 0 ? "#444444" : "#FFFFFF", - "#000000", - 6, - core.ui._buildFont(66, true) - ); + // 添加向上翻页和向下翻页的按钮 + core.fillBoldText1( + ctx, + "上一页", + 300, + 1180, + page === 0 ? "#444444" : "#FFFFFF", + "#000000", + 6, + core.ui._buildFont(66, true) + ); - core.fillBoldText1( - ctx, - page + 1 + "/" + this.UIMx.length, - 1000, - 1180, - "#FFFFFF", - "#000000", - 6, - core.ui._buildFont(66, true) - ); - core.fillBoldText1( - ctx, - "下一页", - 1700, - 1180, - page === this.UIMx.length - 1 ? "#444444" : "#FFFFFF", - "#000000", - 6, - core.ui._buildFont(66, true) - ); + core.fillBoldText1( + ctx, + page + 1 + "/" + this.UIMx.length, + 1000, + 1180, + "#FFFFFF", + "#000000", + 6, + core.ui._buildFont(66, true) + ); + core.fillBoldText1( + ctx, + "下一页", + 1700, + 1180, + page === this.UIMx.length - 1 ? "#444444" : "#FFFFFF", + "#000000", + 6, + core.ui._buildFont(66, true) + ); - // 添加3*2个4:3的画框 - for (let i = 0; i < 2; i++) { - for (let j = 0; j < 3; j++) { - core.strokeRect( - ctx, - 75 + j * 650, - 150 + i * 500, - 600, - 450, - "#444444", - 2 - ); - if (this.cgs.includes(this.UIMx[page][i][j])) { - const img = core.material.images.images[this.UIMx[page][i][j]]; - if (img) - ctx.drawImage( - img, - 75 + j * 650 + 15, - 150 + i * 500 + 15, - 600 - 30, - 450 - 30 - ); - } else { - ctx.fillStyle = "#000000"; - ctx.fillRect( - 75 + j * 650 + 15, - 150 + i * 500 + 15, - 600 - 30, - 450 - 30 - ); - const img = core.material.images.images["LOGO.webp"]; - if (img) - ctx.drawImage( - img, - 75 + j * 650 + 15, - 150 + i * 500 + 15, - 600 - 30, - 450 - 30 - ); - } - } - } - } - } - } - this.setcgs = function (img) { - const a = core.getLocalStorage("cgs", []); - if (img) { - if (!a.includes(img)) a.push(img); - core.setLocalStorage("cgs", a); - } else core.setLocalStorage("cgs"); - }; - core.ui.CG = new CG(); - main.dom.CGMode.onclick = function () { - //点击开始页面的CG MODE进入cg回廊 - main.core.control.checkBgm(); - page = 0; - main.core.ui.CG.cgs = core.getLocalStorage("cgs", []); - CGUI.style.display = "block"; - main.core.ui.CG.update(); - }; -}, + // 添加3*2个4:3的画框 + for (let i = 0; i < 2; i++) { + for (let j = 0; j < 3; j++) { + core.strokeRect( + ctx, + 75 + j * 650, + 150 + i * 500, + 600, + 450, + "#444444", + 2 + ); + if (this.cgs.includes(this.UIMx[page][i][j])) { + const img = core.material.images.images[this.UIMx[page][i][j]]; + if (img) + ctx.drawImage( + img, + 75 + j * 650 + 15, + 150 + i * 500 + 15, + 600 - 30, + 450 - 30 + ); + } else { + ctx.fillStyle = "#000000"; + ctx.fillRect( + 75 + j * 650 + 15, + 150 + i * 500 + 15, + 600 - 30, + 450 - 30 + ); + const img = core.material.images.images["LOGO.webp"]; + if (img) + ctx.drawImage( + img, + 75 + j * 650 + 15, + 150 + i * 500 + 15, + 600 - 30, + 450 - 30 + ); + } + } + } + } + } + } + this.setcgs = function (img) { + const a = core.getLocalStorage("cgs", []); + if (img) { + if (!a.includes(img)) a.push(img); + core.setLocalStorage("cgs", a); + } else core.setLocalStorage("cgs"); + }; + core.ui.CG = new CG(); + main.dom.CGMode.onclick = function () { + //点击开始页面的CG MODE进入cg回廊 + main.core.control.checkBgm(); + page = 0; + main.core.ui.CG.cgs = core.getLocalStorage("cgs", []); + CGUI.style.display = "block"; + main.core.ui.CG.update(); + }; + }, "光标设置": function () { // 在此增加新插件 this.changeMouse = function ( @@ -15214,902 +14994,901 @@ var plugins_bb40132b_638b_4a9f_b028_d3fe47acc8d1 = }; }, "剧情cg": function () { - // 在此增加新插件 - // 在此增加新插件 - const cg = document.createElement("canvas"); //cg画布设置 - cg.style.position = "absolute"; - cg.style.zIndex = 300; - cg.style.display = "none"; - cg.id = "cgText"; - main.dom.gameGroup.insertAdjacentElement("afterend", cg); - cg.style.top = "50%"; - cg.style.left = "50%"; - cg.style.transform = "translate(-50%,-50%)"; - const ctx = cg.getContext("2d"); - main.dom.cgText = cg; - const logcanvas = document.createElement("canvas"); //cg画布设置 - logcanvas.style.position = "absolute"; - logcanvas.style.zIndex = 301; - logcanvas.style.display = "none"; - logcanvas.id = "cgText"; - main.dom.gameGroup.insertAdjacentElement("afterend", logcanvas); - logcanvas.style.top = "50%"; - logcanvas.style.left = "50%"; - logcanvas.style.transform = "translate(-50%,-50%)"; - const logctx = logcanvas.getContext("2d"); - main.dom.logcanvas = logcanvas; - logcanvas.onmouseup = function (e) { - //鼠标抬起 + // 在此增加新插件 + // 在此增加新插件 + const cg = document.createElement("canvas"); //cg画布设置 + cg.style.position = "absolute"; + cg.style.zIndex = 300; + cg.style.display = "none"; + cg.id = "cgText"; + main.dom.gameGroup.insertAdjacentElement("afterend", cg); + cg.style.top = "50%"; + cg.style.left = "50%"; + cg.style.transform = "translate(-50%,-50%)"; + const ctx = cg.getContext("2d"); + main.dom.cgText = cg; + const logcanvas = document.createElement("canvas"); //cg画布设置 + logcanvas.style.position = "absolute"; + logcanvas.style.zIndex = 301; + logcanvas.style.display = "none"; + logcanvas.id = "cgText"; + main.dom.gameGroup.insertAdjacentElement("afterend", logcanvas); + logcanvas.style.top = "50%"; + logcanvas.style.left = "50%"; + logcanvas.style.transform = "translate(-50%,-50%)"; + const logctx = logcanvas.getContext("2d"); + main.dom.logcanvas = logcanvas; + logcanvas.onmouseup = function (e) { + //鼠标抬起 - try { - if (!core.isPlaying()) return false; - core.unregisterAnimationFrame("skip"); - let a = core.getFlag("skip", false); - core.setFlag("skip", false); - if (a) { - const data = core.clone(core.status.event.data.current); + try { + if (!core.isPlaying()) return false; + core.unregisterAnimationFrame("skip"); + let a = core.getFlag("skip", false); + core.setFlag("skip", false); + if (a) { + const data = core.clone(core.status.event.data.current); - core.insertAction(data); - core.doAction(); - } - } catch (ee) { - console.error(ee); - } - }; - logcanvas.onmousedown = function (e) { - //鼠标按下 - try { - if (!core.isPlaying()) return false; - const left = core.dom.gameGroup.offsetLeft; - const top = core.dom.gameGroup.offsetTop; - const px = Math.floor((e.clientX - left) / core.domStyle.scale), - py = Math.floor((e.clientY - top) / core.domStyle.scale); - core.ui.cgText.click(px * 3, py * 3); - } catch (ee) { - main.log(ee); - } - }; - logcanvas.ontouchend = function (e) { - //触摸抬起 + core.insertAction(data); + core.doAction(); + } + } catch (ee) { + console.error(ee); + } + }; + logcanvas.onmousedown = function (e) { + //鼠标按下 + try { + if (!core.isPlaying()) return false; + const left = core.dom.gameGroup.offsetLeft; + const top = core.dom.gameGroup.offsetTop; + const px = Math.floor((e.clientX - left) / core.domStyle.scale), + py = Math.floor((e.clientY - top) / core.domStyle.scale); + core.ui.cgText.click(px * 3, py * 3); + } catch (ee) { + main.log(ee); + } + }; + logcanvas.ontouchend = function (e) { + //触摸抬起 - try { - if (!core.isPlaying()) return false; - core.unregisterAnimationFrame("skip"); - core.setFlag("skip", false); - let a = core.getFlag("skip", false); - core.setFlag("skip", false); - if (a) { - const data = core.clone(core.status.event.data.current); + try { + if (!core.isPlaying()) return false; + core.unregisterAnimationFrame("skip"); + core.setFlag("skip", false); + let a = core.getFlag("skip", false); + core.setFlag("skip", false); + if (a) { + const data = core.clone(core.status.event.data.current); - core.insertAction(data); - core.doAction(); - } - } catch (ee) {} - }; - logcanvas.ontouchstart = function (e) { - //触摸按下 - try { - if (!core.isPlaying()) return false; - const left = core.dom.gameGroup.offsetLeft; - const top = core.dom.gameGroup.offsetTop; - const px = Math.floor( - (e.targetTouches[0].clientX - left) / core.domStyle.scale - ), - py = Math.floor( - (e.targetTouches[0].clientY - top) / core.domStyle.scale - ); - core.ui.cgText.click(px * 3, py * 3); - } catch (ee) { - main.log(ee); - } - }; - cg.onmouseup = function (e) { - //鼠标抬起 + core.insertAction(data); + core.doAction(); + } + } catch (ee) {} + }; + logcanvas.ontouchstart = function (e) { + //触摸按下 + try { + if (!core.isPlaying()) return false; + const left = core.dom.gameGroup.offsetLeft; + const top = core.dom.gameGroup.offsetTop; + const px = Math.floor( + (e.targetTouches[0].clientX - left) / core.domStyle.scale + ), + py = Math.floor( + (e.targetTouches[0].clientY - top) / core.domStyle.scale + ); + core.ui.cgText.click(px * 3, py * 3); + } catch (ee) { + main.log(ee); + } + }; + cg.onmouseup = function (e) { + //鼠标抬起 - try { - if (!core.isPlaying()) return false; - core.unregisterAnimationFrame("skip"); - let a = core.getFlag("skip", false); - core.setFlag("skip", false); - if (a) { - const data = core.clone(core.status.event.data.current); + try { + if (!core.isPlaying()) return false; + core.unregisterAnimationFrame("skip"); + let a = core.getFlag("skip", false); + core.setFlag("skip", false); + if (a) { + const data = core.clone(core.status.event.data.current); - core.insertAction(data); - core.doAction(); - } - } catch (ee) { - console.error(ee); - } - }; - cg.onmousedown = function (e) { - //鼠标按下 - try { - if (!core.isPlaying()) return false; - const left = core.dom.gameGroup.offsetLeft; - const top = core.dom.gameGroup.offsetTop; - const px = Math.floor((e.clientX - left) / core.domStyle.scale), - py = Math.floor((e.clientY - top) / core.domStyle.scale); - core.ui.cgText.click(px * 3, py * 3); - } catch (ee) { - main.log(ee); - } - }; - cg.ontouchend = function (e) { - //触摸抬起 + core.insertAction(data); + core.doAction(); + } + } catch (ee) { + console.error(ee); + } + }; + cg.onmousedown = function (e) { + //鼠标按下 + try { + if (!core.isPlaying()) return false; + const left = core.dom.gameGroup.offsetLeft; + const top = core.dom.gameGroup.offsetTop; + const px = Math.floor((e.clientX - left) / core.domStyle.scale), + py = Math.floor((e.clientY - top) / core.domStyle.scale); + core.ui.cgText.click(px * 3, py * 3); + } catch (ee) { + main.log(ee); + } + }; + cg.ontouchend = function (e) { + //触摸抬起 - try { - if (!core.isPlaying()) return false; - core.unregisterAnimationFrame("skip"); - core.setFlag("skip", false); - let a = core.getFlag("skip", false); - core.setFlag("skip", false); - if (a) { - const data = core.clone(core.status.event.data.current); + try { + if (!core.isPlaying()) return false; + core.unregisterAnimationFrame("skip"); + core.setFlag("skip", false); + let a = core.getFlag("skip", false); + core.setFlag("skip", false); + if (a) { + const data = core.clone(core.status.event.data.current); - core.insertAction(data); - core.doAction(); - } - } catch (ee) { - console.error(ee); - } - }; - cg.ontouchstart = function (e) { - //触摸按下 - try { - if (!core.isPlaying()) return false; - const left = core.dom.gameGroup.offsetLeft; - const top = core.dom.gameGroup.offsetTop; - const px = Math.floor( - (e.targetTouches[0].clientX - left) / core.domStyle.scale - ), - py = Math.floor( - (e.targetTouches[0].clientY - top) / core.domStyle.scale - ); - core.ui.cgText.click(px * 3, py * 3); - } catch (ee) { - main.log(ee); - } - }; - let auto = false; + core.insertAction(data); + core.doAction(); + } + } catch (ee) { + console.error(ee); + } + }; + cg.ontouchstart = function (e) { + //触摸按下 + try { + if (!core.isPlaying()) return false; + const left = core.dom.gameGroup.offsetLeft; + const top = core.dom.gameGroup.offsetTop; + const px = Math.floor( + (e.targetTouches[0].clientX - left) / core.domStyle.scale + ), + py = Math.floor( + (e.targetTouches[0].clientY - top) / core.domStyle.scale + ); + core.ui.cgText.click(px * 3, py * 3); + } catch (ee) { + main.log(ee); + } + }; + let auto = false; - class cgText { - constructor() { - //绘制需要的变量 - this.image = ""; - this.head = { name: "face_050445.webp", px: -300 }; - this.bodyList = [ - { name: "tati_050145a.webp", px: 100, filter: false }, - { name: "tati_120124.webp", px: 1100, filter: true }, - ]; - this.name = ""; - this.text = ""; - this.time = 0; - this.WindowSkin = false; - this.sound = ""; - this.beforeSound = 0; - this.wait = 1000; - this.memory = false; - this.textList = []; - this.page = 1; - this.overpage = 1; - this.log = false; - this.index = 0; - } - click(px, py) { - //点击效果 + class cgText { + constructor() { + //绘制需要的变量 + this.image = ""; + this.head = { name: "face_050445.webp", px: -300 }; + this.bodyList = [ + { name: "tati_050145a.webp", px: 100, filter: false }, + { name: "tati_120124.webp", px: 1100, filter: true }, + ]; + this.name = ""; + this.text = ""; + this.time = 0; + this.WindowSkin = false; + this.sound = ""; + this.beforeSound = 0; + this.wait = 1000; + this.memory = false; + this.textList = []; + this.page = 1; + this.overpage = 1; + this.log = false; + this.index = 0; + } + click(px, py) { + //点击效果 - const makeBox = ([x, y], [w, h]) => { - return [ - [x, y], - [x + w, y + h], - ]; - }; - const inRect = ([x, y], [ - [sx, sy], - [dx, dy] - ]) => { - return sx <= x && x <= dx && sy <= y && y <= dy; - }; - const pos = [px, py]; - const savebox = makeBox([1700, 1100], [192, 96]); - const saveboxVertical = makeBox([52, 1700], [96, 192]); - const skipbox = makeBox([1700, 1000], [192, 96]); - const skipboxVertical = makeBox([152, 1700], [96, 192]); - const autobox = makeBox([1700, 900], [192, 96]); - const autoboxVertical = makeBox([252, 1700], [96, 192]); - const textbox = makeBox([1700, 800], [192, 96]); - const textboxVertical = makeBox([352, 1700], [96, 192]); - const backbox = makeBox([15, 35], [210, 90]); - const backboxVertical = makeBox([1123, 15], [90, 210]); - const pageupbox = makeBox([300, 1130], [200, 100]); - const pageupboxVertical = makeBox([18, 300], [100, 200]); - const pagedownbox = makeBox([1500, 1130], [200, 100]); - const pagedownboxVertical = makeBox([18, 1500], [100, 200]); - if (this.log) { - if ( - (core.domStyle.isVertical && inRect(pos, backboxVertical)) || - (!core.domStyle.isVertical && inRect(pos, backbox)) - ) { - core.clearMap(logctx); - main.dom.logcanvas.style.display = "none"; - this.log = false; - } else if ( - (core.domStyle.isVertical && inRect(pos, pageupboxVertical)) || - (!core.domStyle.isVertical && inRect(pos, pageupbox)) - ) { - core.clearMap(logctx); - if (this.page > 1) this.page--; - this.logdraw(this.page); - } else if ( - (core.domStyle.isVertical && inRect(pos, pagedownboxVertical)) || - (!core.domStyle.isVertical && inRect(pos, pagedownbox)) - ) { - core.clearMap(logctx); - if (this.page < this.overpage) this.page++; - this.logdraw(this.page); - } - } else { - if ( - (core.domStyle.isVertical && - inRect(pos, skipboxVertical) && - !this.WindowSkin) || - (!core.domStyle.isVertical && - !this.WindowSkin && - inRect(pos, skipbox)) - ) { - auto = false; - let time = 0; - core.stopSound(this.beforeSound); - core.registerAnimationFrame("skip", true, (timestamp) => { - if (timestamp > time + 50) { - time = timestamp; - if ( - core.status.event.id == "action" && - core.status.event.data.type == "cgtext" - ) { - core.setFlag("skip", true); - main.dom.cgText.style.display = "none"; - core.doAction(); - } - } - }); - } else if ( - (core.domStyle.isVertical && - inRect(pos, textboxVertical) && - !this.WindowSkin) || - (!core.domStyle.isVertical && - !this.WindowSkin && - inRect(pos, textbox)) - ) { - auto = false; - this.log = true; - this.overpage = Math.floor(this.index / 6) + 1; - this.page = this.overpage; - logcanvas.style.display = "block"; - const data = core.clone(core.status.event.data.current); - data.showAll = true; - data.time = 0; - data.text = this.text.replaceAll(/(\\(z))(\[.*?\])?/g, ""); //去除打字机暂停效果 - data.sound = ""; - core.insertAction(data); - core.doAction(); - this.logdraw(this.page); - } else if ( - (core.domStyle.isVertical && - inRect(pos, autoboxVertical) && - !this.WindowSkin) || - (!core.domStyle.isVertical && - !this.WindowSkin && - inRect(pos, autobox)) - ) { - auto = !auto; + const makeBox = ([x, y], [w, h]) => { + return [ + [x, y], + [x + w, y + h], + ]; + }; + const inRect = ([x, y], [[sx, sy], [dx, dy]]) => { + return sx <= x && x <= dx && sy <= y && y <= dy; + }; + const pos = [px, py]; + const savebox = makeBox([1700, 1100], [192, 96]); + const saveboxVertical = makeBox([52, 1700], [96, 192]); + const skipbox = makeBox([1700, 1000], [192, 96]); + const skipboxVertical = makeBox([152, 1700], [96, 192]); + const autobox = makeBox([1700, 900], [192, 96]); + const autoboxVertical = makeBox([252, 1700], [96, 192]); + const textbox = makeBox([1700, 800], [192, 96]); + const textboxVertical = makeBox([352, 1700], [96, 192]); + const backbox = makeBox([15, 35], [210, 90]); + const backboxVertical = makeBox([1123, 15], [90, 210]); + const pageupbox = makeBox([300, 1130], [200, 100]); + const pageupboxVertical = makeBox([18, 300], [100, 200]); + const pagedownbox = makeBox([1500, 1130], [200, 100]); + const pagedownboxVertical = makeBox([18, 1500], [100, 200]); + if (this.log) { + if ( + (core.domStyle.isVertical && inRect(pos, backboxVertical)) || + (!core.domStyle.isVertical && inRect(pos, backbox)) + ) { + core.clearMap(logctx); + main.dom.logcanvas.style.display = "none"; + this.log = false; + } else if ( + (core.domStyle.isVertical && inRect(pos, pageupboxVertical)) || + (!core.domStyle.isVertical && inRect(pos, pageupbox)) + ) { + core.clearMap(logctx); + if (this.page > 1) this.page--; + this.logdraw(this.page); + } else if ( + (core.domStyle.isVertical && inRect(pos, pagedownboxVertical)) || + (!core.domStyle.isVertical && inRect(pos, pagedownbox)) + ) { + core.clearMap(logctx); + if (this.page < this.overpage) this.page++; + this.logdraw(this.page); + } + } else { + if ( + (core.domStyle.isVertical && + inRect(pos, skipboxVertical) && + !this.WindowSkin) || + (!core.domStyle.isVertical && + !this.WindowSkin && + inRect(pos, skipbox)) + ) { + auto = false; + let time = 0; + core.stopSound(this.beforeSound); + core.registerAnimationFrame("skip", true, (timestamp) => { + if (timestamp > time + 50) { + time = timestamp; + if ( + core.status.event.id == "action" && + core.status.event.data.type == "cgtext" + ) { + core.setFlag("skip", true); + main.dom.cgText.style.display = "none"; + core.doAction(); + } + } + }); + } else if ( + (core.domStyle.isVertical && + inRect(pos, textboxVertical) && + !this.WindowSkin) || + (!core.domStyle.isVertical && + !this.WindowSkin && + inRect(pos, textbox)) + ) { + auto = false; + this.log = true; + this.overpage = Math.floor(this.index / 6) + 1; + this.page = this.overpage; + logcanvas.style.display = "block"; + const data = core.clone(core.status.event.data.current); + data.showAll = true; + data.time = 0; + data.text = this.text.replaceAll(/(\\(z))(\[.*?\])?/g, ""); //去除打字机暂停效果 + data.sound = ""; + core.insertAction(data); + core.doAction(); + this.logdraw(this.page); + } else if ( + (core.domStyle.isVertical && + inRect(pos, autoboxVertical) && + !this.WindowSkin) || + (!core.domStyle.isVertical && + !this.WindowSkin && + inRect(pos, autobox)) + ) { + auto = !auto; - const data = core.clone(core.status.event.data.current); - data.showAll = true; - data.time = 0; - data.text = this.text.replaceAll(/(\\(z))(\[.*?\])?/g, ""); //去除打字机暂停效果 - data.sound = ""; - core.insertAction(data); - core.doAction(); - } else if ( - (core.domStyle.isVertical && - inRect(pos, saveboxVertical) && - !this.WindowSkin) || - (!core.domStyle.isVertical && - !this.WindowSkin && - inRect(pos, savebox)) - ) { - //存档 - auto = false; - if (core.status.event.animateUI) return; - if (core.status.event.interval != null) return; - const current = core.clone(core.status.event.data.current); - current.showAll = true; - current.time = 0; - current.sound = ""; - current.text = this.text.replaceAll(/(\\(z))(\[.*?\])?/g, ""); //去除当前事件所有打字机效果 - cg.style.display = "none"; - const data = [{ type: "callSave" }, current]; //插入存档事件 - core.insertAction(data); + const data = core.clone(core.status.event.data.current); + data.showAll = true; + data.time = 0; + data.text = this.text.replaceAll(/(\\(z))(\[.*?\])?/g, ""); //去除打字机暂停效果 + data.sound = ""; + core.insertAction(data); + core.doAction(); + } else if ( + (core.domStyle.isVertical && + inRect(pos, saveboxVertical) && + !this.WindowSkin) || + (!core.domStyle.isVertical && + !this.WindowSkin && + inRect(pos, savebox)) + ) { + //存档 + auto = false; + if (core.status.event.animateUI) return; + if (core.status.event.interval != null) return; + const current = core.clone(core.status.event.data.current); + current.showAll = true; + current.time = 0; + current.sound = ""; + current.text = this.text.replaceAll(/(\\(z))(\[.*?\])?/g, ""); //去除当前事件所有打字机效果 + cg.style.display = "none"; + const data = [{ type: "callSave" }, current]; //插入存档事件 + core.insertAction(data); - core.doAction(); - } else if (!core.status.event.data) { - cg.style.display = "none"; - core.ui._animateUI("hide", null, () => { - core.doAction(); - }); - } else { - // 正在淡入淡出的话不执行 - if (core.status.event.animateUI) return; - auto = false; + core.doAction(); + } else if (!core.status.event.data) { + cg.style.display = "none"; + core.ui._animateUI("hide", null, () => { + core.doAction(); + }); + } else { + // 正在淡入淡出的话不执行 + if (core.status.event.animateUI) return; + auto = false; - // 打字机效果显示全部文字 - if (core.status.event.interval != null) { - const data = core.clone(core.status.event.data?.current); - data.showAll = true; - data.time = 0; - data.text = this.text.replaceAll(/(\\(z))(\[.*?\])?/g, ""); //去除打字机暂停效果 - data.sound = ""; - core.insertAction(data); - core.doAction(); - return; - } else { - core.stopSound(this.beforeSound); - } + // 打字机效果显示全部文字 + if (core.status.event.interval != null) { + const data = core.clone(core.status.event.data?.current); + data.showAll = true; + data.time = 0; + data.text = this.text.replaceAll(/(\\(z))(\[.*?\])?/g, ""); //去除打字机暂停效果 + data.sound = ""; + core.insertAction(data); + core.doAction(); + return; + } else { + core.stopSound(this.beforeSound); + } - cg.style.display = "none"; - core.ui._animateUI("hide", null, () => { - core.doAction(); - }); - } - } - } + cg.style.display = "none"; + core.ui._animateUI("hide", null, () => { + core.doAction(); + }); + } + } + } - drawTextContent(ctx, content, config) { - //绘制多行文字并执行打字机效果 + drawTextContent(ctx, content, config) { + //绘制多行文字并执行打字机效果 - ctx = core.getContextByName(ctx); - // 设置默认配置项 - var textAttribute = - core.status.textAttribute || core.initStatus.textAttribute; - var globalAttribute = - core.status.globalAttribute || core.initStatus.globalAttribute; - config = core.clone(config || {}); - config.left = config.left || 0; - config.right = - config.left + (config.maxWidth == null ? core._PX_ : config.maxWidth); - config.top = config.top || 0; - config.color = core.arrayToRGBA(config.color || textAttribute.text); - if (config.bold == null) config.bold = textAttribute.bold; - config.italic = config.italic || false; - config.align = config.align || textAttribute.align || "left"; - config.fontSize = config.fontSize || textAttribute.textfont; - config.lineHeight = config.lineHeight || config.fontSize * 1.3; - config.defaultFont = config.font = config.font || globalAttribute.font; - config.time = config.time || 0; - config.letterSpacing = - config.letterSpacing == null ? - textAttribute.letterSpacing || 0 : - config.letterSpacing; + ctx = core.getContextByName(ctx); + // 设置默认配置项 + var textAttribute = + core.status.textAttribute || core.initStatus.textAttribute; + var globalAttribute = + core.status.globalAttribute || core.initStatus.globalAttribute; + config = core.clone(config || {}); + config.left = config.left || 0; + config.right = + config.left + (config.maxWidth == null ? core._PX_ : config.maxWidth); + config.top = config.top || 0; + config.color = core.arrayToRGBA(config.color || textAttribute.text); + if (config.bold == null) config.bold = textAttribute.bold; + config.italic = config.italic || false; + config.align = config.align || textAttribute.align || "left"; + config.fontSize = config.fontSize || textAttribute.textfont; + config.lineHeight = config.lineHeight || config.fontSize * 1.3; + config.defaultFont = config.font = config.font || globalAttribute.font; + config.time = config.time || 0; + config.letterSpacing = + config.letterSpacing == null + ? textAttribute.letterSpacing || 0 + : config.letterSpacing; - config.index = 0; - config.currcolor = config.color; - config.currfont = config.fontSize; - config.lineMargin = Math.max( - Math.round(config.fontSize / 4), - config.lineHeight - config.fontSize - ); - config.topMargin = parseInt(config.lineMargin / 2); - config.lineMaxHeight = config.lineMargin + config.fontSize; - config.offsetX = 0; - config.offsetY = 0; - config.line = 0; - config.blocks = []; - config.isHD = ctx == null || ctx.canvas.hasAttribute("isHD"); + config.index = 0; + config.currcolor = config.color; + config.currfont = config.fontSize; + config.lineMargin = Math.max( + Math.round(config.fontSize / 4), + config.lineHeight - config.fontSize + ); + config.topMargin = parseInt(config.lineMargin / 2); + config.lineMaxHeight = config.lineMargin + config.fontSize; + config.offsetX = 0; + config.offsetY = 0; + config.line = 0; + config.blocks = []; + config.isHD = ctx == null || ctx.canvas.hasAttribute("isHD"); - // 创建一个新的临时画布 - var tempCtx = document.createElement("canvas").getContext("2d"); - if (config.isHD && ctx) { - core.maps._setHDCanvasSize( - tempCtx, - ctx.canvas.width, - ctx.canvas.height - ); - } else { - tempCtx.canvas.width = ctx == null ? 1 : ctx.canvas.width; - tempCtx.canvas.height = ctx == null ? 1 : ctx.canvas.height; - } + // 创建一个新的临时画布 + var tempCtx = document.createElement("canvas").getContext("2d"); + if (config.isHD && ctx) { + core.maps._setHDCanvasSize( + tempCtx, + ctx.canvas.width, + ctx.canvas.height + ); + } else { + tempCtx.canvas.width = ctx == null ? 1 : ctx.canvas.width; + tempCtx.canvas.height = ctx == null ? 1 : ctx.canvas.height; + } - tempCtx.textBaseline = "top"; - tempCtx.font = core.ui._buildFont( - config.fontSize, - config.bold, - config.italic, - config.font - ); - tempCtx.fillStyle = config.color; - config = this._drawTextContent_draw(ctx, tempCtx, content, config); - return config; - } - _drawTextContent_draw(ctx, tempCtx, content, config) { - // Step 1: 绘制到tempCtx上,并记录下图块信息 - while (core.ui._drawTextContent_next(tempCtx, content, config)); + tempCtx.textBaseline = "top"; + tempCtx.font = core.ui._buildFont( + config.fontSize, + config.bold, + config.italic, + config.font + ); + tempCtx.fillStyle = config.color; + config = this._drawTextContent_draw(ctx, tempCtx, content, config); + return config; + } + _drawTextContent_draw(ctx, tempCtx, content, config) { + // Step 1: 绘制到tempCtx上,并记录下图块信息 + while (core.ui._drawTextContent_next(tempCtx, content, config)); - if (ctx == null) return config; + if (ctx == null) return config; - // Step 2: 从tempCtx绘制到画布上 - config.index = 0; - var _drawNext = function () { - if (config.index >= config.blocks.length) return false; - var block = config.blocks[config.index++]; - if (block != null) { - // It works, why? - const scale = config.isHD ? - devicePixelRatio * core.domStyle.scale : - 1; - ctx.restore(); - ctx.save(); //保存设置 - if (core.domStyle.isVertical) { - ctx.translate(1248, 0); //重新定位右上角为基准 - ctx.rotate(Math.PI / 2); //旋转90度 - } - ctx.drawImage( - tempCtx.canvas, - block.left * scale, - block.top * scale, - block.width * scale, - block.height * scale, - config.left + block.left + block.marginLeft, - config.top + block.top + block.marginTop, - block.width, - block.height - ); - ctx.restore(); - } - return true; - }; + // Step 2: 从tempCtx绘制到画布上 + config.index = 0; + var _drawNext = function () { + if (config.index >= config.blocks.length) return false; + var block = config.blocks[config.index++]; + if (block != null) { + // It works, why? + const scale = config.isHD + ? devicePixelRatio * core.domStyle.scale + : 1; + ctx.restore(); + ctx.save(); //保存设置 + if (core.domStyle.isVertical) { + ctx.translate(1248, 0); //重新定位右上角为基准 + ctx.rotate(Math.PI / 2); //旋转90度 + } + ctx.drawImage( + tempCtx.canvas, + block.left * scale, + block.top * scale, + block.width * scale, + block.height * scale, + config.left + block.left + block.marginLeft, + config.top + block.top + block.marginTop, + block.width, + block.height + ); + ctx.restore(); + } + return true; + }; - if (config.time == 0) { - while (_drawNext()); + if (config.time == 0) { + while (_drawNext()); - if ( - (auto && !core.ui.cgText.WindowSkin && !core.ui.cgText.sound) || - (core.ui.cgText.sound && !core.musicStatus.soundStatus) - ) { - setTimeout(() => { - if (auto) { - cg.style.display = "none"; - core.ui._animateUI("hide", null, () => { - core.doAction(); - }); - } - }, core.ui.cgText.wait); - } - } else { - clearInterval(core.status.event.interval); - core.status.event.interval = setInterval(function () { - if (!_drawNext()) { - clearInterval(core.status.event.interval); - core.status.event.interval = null; + if ( + (auto && !core.ui.cgText.WindowSkin && !core.ui.cgText.sound) || + (core.ui.cgText.sound && !core.musicStatus.soundStatus) + ) { + setTimeout(() => { + if (auto) { + cg.style.display = "none"; + core.ui._animateUI("hide", null, () => { + core.doAction(); + }); + } + }, core.ui.cgText.wait); + } + } else { + clearInterval(core.status.event.interval); + core.status.event.interval = setInterval(function () { + if (!_drawNext()) { + clearInterval(core.status.event.interval); + core.status.event.interval = null; - if ( - (auto && !core.ui.cgText.WindowSkin && !core.ui.cgText.sound) || - (core.ui.cgText.sound && !core.musicStatus.soundStatus) - ) - setTimeout(() => { - if (auto) { - cg.style.display = "none"; - core.ui._animateUI("hide", null, () => { - core.doAction(); - }); - } - }, core.ui.cgText.wait); - } - }, config.time); - } + if ( + (auto && !core.ui.cgText.WindowSkin && !core.ui.cgText.sound) || + (core.ui.cgText.sound && !core.musicStatus.soundStatus) + ) + setTimeout(() => { + if (auto) { + cg.style.display = "none"; + core.ui._animateUI("hide", null, () => { + core.doAction(); + }); + } + }, core.ui.cgText.wait); + } + }, config.time); + } - return config; - } + return config; + } - update() { - this.background(); - if (this.log) this.logdraw(this.page); - } - logdraw(page) { - if (core.domStyle.isVertical) { - logctx.canvas.width = 1248; - logctx.canvas.height = 2028; - logctx.save(); //保存设置 - logctx.translate(1248, 0); //重新定位右上角为基准 - logctx.rotate(Math.PI / 2); //旋转90度 - } else { - logctx.canvas.width = 2028; - logctx.canvas.height = 1248; - } - core.fillRect(logctx, 0, 0, 2028, 1248, "rgba(0,0,0,0.5)"); - core.setTextAlign(logctx, "center"); - core.fillBoldText1( - logctx, - "◀离开", - 110, - 100, - "#FFFFFF", - "#000000", - 6, - core.ui._buildFont(66, true) - ); - logctx.strokeStyle = "#FFFFFF"; - logctx.lineWidth = 3; - logctx.beginPath(); - logctx.moveTo(100, 150); - logctx.lineTo(1928, 150); + update() { + this.background(); + if (this.log) this.logdraw(this.page); + } + logdraw(page) { + if (core.domStyle.isVertical) { + logctx.canvas.width = 1248; + logctx.canvas.height = 2028; + logctx.save(); //保存设置 + logctx.translate(1248, 0); //重新定位右上角为基准 + logctx.rotate(Math.PI / 2); //旋转90度 + } else { + logctx.canvas.width = 2028; + logctx.canvas.height = 1248; + } + core.fillRect(logctx, 0, 0, 2028, 1248, "rgba(0,0,0,0.5)"); + core.setTextAlign(logctx, "center"); + core.fillBoldText1( + logctx, + "◀离开", + 110, + 100, + "#FFFFFF", + "#000000", + 6, + core.ui._buildFont(66, true) + ); + logctx.strokeStyle = "#FFFFFF"; + logctx.lineWidth = 3; + logctx.beginPath(); + logctx.moveTo(100, 150); + logctx.lineTo(1928, 150); - logctx.stroke(); - let posy = 150; - const indexList = this.textList; - core.setTextAlign(logctx, "left"); - for ( - let i = (page - 1) * 6; i <= Math.min(this.index, page * 6 - 1); i++ - ) { - const text = this.textList[i][1].replaceAll( - /(\\(d|e|f|g|i|n|r|b|c|t|z))(\[.*?\])?/g, - "" - ); //取消打字机 - const name = this.textList[i][0] ? - "【" + this.textList[i][0] + "】" : - ""; - if (name) { - core.fillBoldText1( - logctx, - name, - 150, - posy + 50, - "#FFFFFF", - "#000000", - 6, - core.ui._buildFont(42, true) - ); - } - if (text.length < 30) { - core.fillBoldText1( - logctx, - text, - 650, - posy + 50, - "#FFFFFF", - "#000000", - 6, - core.ui._buildFont(42, true) - ); - } else { - let text1 = text.slice(0, 30); - core.fillBoldText1( - logctx, - text1, - 650, - posy + 50, - "#FFFFFF", - "#000000", - 6, - core.ui._buildFont(42, true) - ); - if (text.length > 60) { - let text2 = text.slice(30, 60); - let text3 = text.slice(60); - core.fillBoldText1( - logctx, - text2, - 650, - posy + 100, - "#FFFFFF", - "#000000", - 6, - core.ui._buildFont(42, true) - ); - core.fillBoldText1( - logctx, - text3, - 650, - posy + 150, - "#FFFFFF", - "#000000", - 6, - core.ui._buildFont(42, true) - ); - } else { - let text2 = text.slice(30); - core.fillBoldText1( - logctx, - text2, - 650, - posy + 100, - "#FFFFFF", - "#000000", - 6, - core.ui._buildFont(42, true) - ); - } - } + logctx.stroke(); + let posy = 150; + const indexList = this.textList; + core.setTextAlign(logctx, "left"); + for ( + let i = (page - 1) * 6; + i <= Math.min(this.index, page * 6 - 1); + i++ + ) { + const text = this.textList[i][1].replaceAll( + /(\\(d|e|f|g|i|n|r|b|c|t|z))(\[.*?\])?/g, + "" + ); //取消打字机 + const name = this.textList[i][0] + ? "【" + this.textList[i][0] + "】" + : ""; + if (name) { + core.fillBoldText1( + logctx, + name, + 150, + posy + 50, + "#FFFFFF", + "#000000", + 6, + core.ui._buildFont(42, true) + ); + } + if (text.length < 30) { + core.fillBoldText1( + logctx, + text, + 650, + posy + 50, + "#FFFFFF", + "#000000", + 6, + core.ui._buildFont(42, true) + ); + } else { + let text1 = text.slice(0, 30); + core.fillBoldText1( + logctx, + text1, + 650, + posy + 50, + "#FFFFFF", + "#000000", + 6, + core.ui._buildFont(42, true) + ); + if (text.length > 60) { + let text2 = text.slice(30, 60); + let text3 = text.slice(60); + core.fillBoldText1( + logctx, + text2, + 650, + posy + 100, + "#FFFFFF", + "#000000", + 6, + core.ui._buildFont(42, true) + ); + core.fillBoldText1( + logctx, + text3, + 650, + posy + 150, + "#FFFFFF", + "#000000", + 6, + core.ui._buildFont(42, true) + ); + } else { + let text2 = text.slice(30); + core.fillBoldText1( + logctx, + text2, + 650, + posy + 100, + "#FFFFFF", + "#000000", + 6, + core.ui._buildFont(42, true) + ); + } + } - logctx.strokeStyle = "#FFFFFF"; - logctx.lineWidth = 3; - logctx.beginPath(); - logctx.moveTo(100, posy); - logctx.lineTo(1928, posy); - logctx.stroke(); + logctx.strokeStyle = "#FFFFFF"; + logctx.lineWidth = 3; + logctx.beginPath(); + logctx.moveTo(100, posy); + logctx.lineTo(1928, posy); + logctx.stroke(); - posy += 160; - } - logctx.beginPath(); - logctx.moveTo(100, 1120); - logctx.lineTo(1928, 1120); - logctx.moveTo(100, 1110); - logctx.lineTo(1928, 1110); - logctx.stroke(); - core.fillBoldText1( - logctx, - "上一页", - 300, - 1200, - page === 1 ? "#444444" : "#FFFFFF", - "#000000", - 6, - core.ui._buildFont(66, true) - ); + posy += 160; + } + logctx.beginPath(); + logctx.moveTo(100, 1120); + logctx.lineTo(1928, 1120); + logctx.moveTo(100, 1110); + logctx.lineTo(1928, 1110); + logctx.stroke(); + core.fillBoldText1( + logctx, + "上一页", + 300, + 1200, + page === 1 ? "#444444" : "#FFFFFF", + "#000000", + 6, + core.ui._buildFont(66, true) + ); - core.fillBoldText1( - logctx, - page + "/" + this.overpage, - 1000, - 1200, - "#FFFFFF", - "#000000", - 6, - core.ui._buildFont(66, true) - ); - core.fillBoldText1( - logctx, - "下一页", - 1500, - 1200, - page === this.overpage ? "#444444" : "#FFFFFF", - "#000000", - 6, - core.ui._buildFont(66, true) - ); - } - background() { - const img = core.material.images.images?.[this.image]; + core.fillBoldText1( + logctx, + page + "/" + this.overpage, + 1000, + 1200, + "#FFFFFF", + "#000000", + 6, + core.ui._buildFont(66, true) + ); + core.fillBoldText1( + logctx, + "下一页", + 1500, + 1200, + page === this.overpage ? "#444444" : "#FFFFFF", + "#000000", + 6, + core.ui._buildFont(66, true) + ); + } + background() { + const img = core.material.images.images?.[this.image]; - if (core.domStyle.isVertical) { - ctx.canvas.width = 1248; - ctx.canvas.height = 2028; - ctx.save(); //保存设置 - ctx.translate(1248, 0); //重新定位右上角为基准 - ctx.rotate(Math.PI / 2); //旋转90度 - } else { - ctx.canvas.width = 2028; - ctx.canvas.height = 1248; - } + if (core.domStyle.isVertical) { + ctx.canvas.width = 1248; + ctx.canvas.height = 2028; + ctx.save(); //保存设置 + ctx.translate(1248, 0); //重新定位右上角为基准 + ctx.rotate(Math.PI / 2); //旋转90度 + } else { + ctx.canvas.width = 2028; + ctx.canvas.height = 1248; + } - if (img) { - //绘制背景 - if (this.memory) ctx.filter = "sepia(50%)"; - ctx.drawImage(img, 0, 0, 2028, 1248); - ctx.filter = "none"; - } else { - core.fillRect(ctx, 0, 0, 2028, 1248); - } - this.bodyList.forEach((v) => { - //绘制立绘 - const body = core.material.images.images?.[v.name]; - if (v.filter) ctx.filter = "brightness(50%)"; - if (body) { - if (!v.w && !v.h && !v.scale) v.scale = 1.7; - if (!v.w && !v.h) { - ctx.drawImage( - body, - 0, - 0, - body.width, - body.height, - v.px, - 1248 - body.height * v.scale, - body.width * v.scale, - body.height * v.scale - ); - } else { - ctx.drawImage( - body, - 0, - 0, - body.width, - body.height, - v.px, - 1248 - (v.h ?? body.height), - v.w ?? body.width, - v.h ?? body.height - ); - } - } - ctx.filter = "none"; - }); - if (core.isPlaying() && !this.WindowSkin) - core.drawWindowSkin( - "winskin.webp", - ctx, - 30, - 780, - 1968, - 436, - null, - null, - null, - 3 - ); //绘制对话框 - const head = core.material.images.images?.[this.head.name]; - if (head) { - //绘制头像 - ctx.drawImage( - head, - 0, - 0, - head.width, - head.height, - this.head.px, - 1248 - head.height * 2.2, - head.width * 2.2, - head.height * 2.2 - ); - } - if (core.isPlaying() && !this.WindowSkin) { - core.drawWindowSkin( - "winskin.webp", - ctx, - 1700, - 800, - 192, - 96, - null, - null, - null, - 3 - ); - core.fillBoldText1( - ctx, - "记 录", - 1736, - 866, - "#FFFFFF", - "#000000", - 6, - "bold 48px Verdana" - ); - core.drawWindowSkin( - "winskin.webp", - ctx, - 1700, - 1100, - 192, - 96, - null, - null, - null, - 3 - ); - core.fillBoldText1( - ctx, - "存 档", - 1736, - 1166, - "#FFFFFF", - "#000000", - 6, - "bold 48px Verdana" - ); - core.drawWindowSkin( - "winskin.webp", - ctx, - 1700, - 1000, - 192, - 96, - null, - null, - null, - 3 - ); - core.fillBoldText1( - ctx, - "▶▶", - 1756, - 1066, - "#FFFFFF", - "#000000", - 6, - "bold 48px Verdana" - ); - core.drawWindowSkin( - "winskin.webp", - ctx, - 1700, - 900, - 192, - 96, - null, - null, - null, - 3 - ); - let autoText = "AUTO"; - if (auto) autoText = "STOP"; - core.fillBoldText1( - ctx, - autoText, - 1722, - 966, - "#FFFFFF", - "#000000", - 6, - "bold 48px Verdana" - ); - } - if (this.name) - core.fillBoldText1( - ctx, - `【${this.name}】`, - 550, - 880, - "#FFFFFF", - "#000000", - 6, - "bold 48px Verdana" - ); //绘制名字 + if (img) { + //绘制背景 + if (this.memory) ctx.filter = "sepia(50%)"; + ctx.drawImage(img, 0, 0, 2028, 1248); + ctx.filter = "none"; + } else { + core.fillRect(ctx, 0, 0, 2028, 1248); + } + this.bodyList.forEach((v) => { + //绘制立绘 + const body = core.material.images.images?.[v.name]; + if (v.filter) ctx.filter = "brightness(50%)"; + if (body) { + if (!v.w && !v.h && !v.scale) v.scale = 1.7; + if (!v.w && !v.h) { + ctx.drawImage( + body, + 0, + 0, + body.width, + body.height, + v.px, + 1248 - body.height * v.scale, + body.width * v.scale, + body.height * v.scale + ); + } else { + ctx.drawImage( + body, + 0, + 0, + body.width, + body.height, + v.px, + 1248 - (v.h ?? body.height), + v.w ?? body.width, + v.h ?? body.height + ); + } + } + ctx.filter = "none"; + }); + if (core.isPlaying() && !this.WindowSkin) + core.drawWindowSkin( + "winskin.webp", + ctx, + 30, + 780, + 1968, + 436, + null, + null, + null, + 3 + ); //绘制对话框 + const head = core.material.images.images?.[this.head.name]; + if (head) { + //绘制头像 + ctx.drawImage( + head, + 0, + 0, + head.width, + head.height, + this.head.px, + 1248 - head.height * 2.2, + head.width * 2.2, + head.height * 2.2 + ); + } + if (core.isPlaying() && !this.WindowSkin) { + core.drawWindowSkin( + "winskin.webp", + ctx, + 1700, + 800, + 192, + 96, + null, + null, + null, + 3 + ); + core.fillBoldText1( + ctx, + "记 录", + 1736, + 866, + "#FFFFFF", + "#000000", + 6, + "bold 48px Verdana" + ); + core.drawWindowSkin( + "winskin.webp", + ctx, + 1700, + 1100, + 192, + 96, + null, + null, + null, + 3 + ); + core.fillBoldText1( + ctx, + "存 档", + 1736, + 1166, + "#FFFFFF", + "#000000", + 6, + "bold 48px Verdana" + ); + core.drawWindowSkin( + "winskin.webp", + ctx, + 1700, + 1000, + 192, + 96, + null, + null, + null, + 3 + ); + core.fillBoldText1( + ctx, + "▶▶", + 1756, + 1066, + "#FFFFFF", + "#000000", + 6, + "bold 48px Verdana" + ); + core.drawWindowSkin( + "winskin.webp", + ctx, + 1700, + 900, + 192, + 96, + null, + null, + null, + 3 + ); + let autoText = "AUTO"; + if (auto) autoText = "STOP"; + core.fillBoldText1( + ctx, + autoText, + 1722, + 966, + "#FFFFFF", + "#000000", + 6, + "bold 48px Verdana" + ); + } + if (this.name) + core.fillBoldText1( + ctx, + `【${this.name}】`, + 550, + 880, + "#FFFFFF", + "#000000", + 6, + "bold 48px Verdana" + ); //绘制名字 - if ( - this.sound && - core.sounds && - !core.getFlag("skip", false) && - core.musicStatus.soundStatus - ) { - this.beforeSound = core.playSound(this.sound, null, () => { - if ( - this.sound && - auto && - !this.WindowSkin && - core.musicStatus.soundStatus - ) { - setTimeout(() => { - if (auto) { - cg.style.display = "none"; - core.ui._animateUI("hide", null, () => { - core.doAction(); - }); - } - }, this.wait); - } - }); - } - if (this.text && !core.getFlag("skip", false)) { - //绘制对话 - this.drawTextContent(ctx, this.text, { - left: 550, - top: 950, - bold: true, - color: "#FFFFFF", - align: "left", - fontSize: 48, - time: this.time || 0, - font: "Verdana", - maxWidth: 1000, - }); - } + if ( + this.sound && + core.sounds && + !core.getFlag("skip", false) && + core.musicStatus.soundStatus + ) { + this.beforeSound = core.playSound(this.sound, null, () => { + if ( + this.sound && + auto && + !this.WindowSkin && + core.musicStatus.soundStatus + ) { + setTimeout(() => { + if (auto) { + cg.style.display = "none"; + core.ui._animateUI("hide", null, () => { + core.doAction(); + }); + } + }, this.wait); + } + }); + } + if (this.text && !core.getFlag("skip", false)) { + //绘制对话 + this.drawTextContent(ctx, this.text, { + left: 550, + top: 950, + bold: true, + color: "#FFFFFF", + align: "left", + fontSize: 48, + time: this.time || 0, + font: "Verdana", + maxWidth: 1000, + }); + } - ctx.restore(); - } - } - core.ui.cgText = new cgText(); -}, + ctx.restore(); + } + } + core.ui.cgText = new cgText(); + }, "旁白": function () { // 在此增加新插件 const over = document.createElement("canvas"); //over画布设置 @@ -18096,7 +17875,6 @@ var plugins_bb40132b_638b_4a9f_b028_d3fe47acc8d1 = music.loop = "loop"; core.material.bgms[name] = music;*/ }; - }, "横屏切换": function () { // 在此增加新插件 @@ -18168,210 +17946,214 @@ var plugins_bb40132b_638b_4a9f_b028_d3fe47acc8d1 = }; }, "帧动画特效(游戏界面)": function () { - // 在此增加新插件 - const animate2 = document.createElement("canvas"); //画布设置 - animate2.style.zIndex = 91; - animate2.id = "animate2"; - animate2.classList.add("gameCanvas", "anti-aliasing"); - animate2.style.display = "block"; - animate2.width = 416; - animate2.height = 416; - animate2.style.width = core.__PIXELS__ * core.domStyle.scale + "px"; - animate2.style.height = core.__PIXELS__ * core.domStyle.scale + "px"; - main.dom.animate2 = animate2; - const anctx = animate2.getContext("2d"); - - main.dom.gameDraw.appendChild(animate2); - - core.plugin.playing = new Set(); - - this.setanimate = function ( - name, - px, - py, - width, - height, - allFarme, - imageList, - soundList - ) { - const data = { - px: px, - py: py, - width: width, - height: height, - allFarme: allFarme, - imageList: imageList, - soundList: soundList, - }; - core.setFlag("animate_" + name, data); - }; - this.deleteanimate = function (name) { - core.setFlag("animate_" + name); - }; - let thistime = 0; - this.playanimate = function (name, x, y, hero, scalex, scaley) { - const data = { - name: name, - x: x, - y: y, - hero: hero, - scalex: scalex, - scaley: scaley, - farme: 0, - }; - - core.plugin.playing.add(data); - }; - core.registerAnimationFrame("animateonmap", true, function (timestamp) { - if (timestamp - thistime > 1000 / 60) { - thistime = timestamp; - core.clearMap(anctx); - core.plugin.playing.forEach((one) => { - const data = flags["animate_" + one.name]; - if (!data) { - core.plugin.playing.delete(one); - } else { - data.imageList.forEach(function (image) { - if ( - one.farme >= (image.beforefarme ?? 0) && - one.farme <= (image.afterfarme ?? data.allFarme) - ) { - const img = core.material.images.images?.[image.image]; - if (img) { - const gla = image.globalAlpha ?? 100; - const agla = image.aglobalAlpha ?? gla, - beforefarme = image.beforefarme ?? 0; - const afterfarme = image.afterfarme ?? data.allFarme; - - anctx.globalAlpha = - (gla + - ((agla - gla) * (one.farme - beforefarme)) / - (afterfarme - beforefarme || 1)) / - 100; - - const cx = - (image.cx ?? 0) + - (((image.acx ?? 0) - (image.cx ?? 0)) * - (one.farme - beforefarme)) / - (afterfarme - beforefarme || 1), - cy = - (image.cy ?? 0) + - (((image.acy ?? 0) - (image.cy ?? 0)) * - (one.farme - beforefarme)) / - (afterfarme - beforefarme || 1), - cw = - (image.cw ?? img.width) + - (((image.acw ?? img.width) - (image.cw ?? img.width)) * - (one.farme - beforefarme)) / - (afterfarme - beforefarme || 1), - ch = - (image.ch ?? img.height) + - (((image.acw ?? img.height) - (image.cw ?? img.height)) * - (one.farme - beforefarme)) / - (afterfarme - beforefarme || 1), - x = - (image.x ?? 0) + - (((image.ax ?? 0) - (image.x ?? 0)) * - (one.farme - beforefarme)) / - (afterfarme - beforefarme || 1), - y = - (image.y ?? 0) + - (((image.ay ?? 0) - (image.y ?? 0)) * - (one.farme - beforefarme)) / - (afterfarme - beforefarme || 1), - w = - (image.w ?? one.width) + - (((image.aw ?? one.width) - (image.w ?? one.width)) * - (one.farme - beforefarme)) / - (afterfarme - beforefarme || 1), - h = - (image.h ?? one.height) + - (((image.aw ?? one.height) - (image.w ?? one.height)) * - (one.farme - beforefarme)) / - (afterfarme - beforefarme || 1), - angle = - (Math.PI * - ((image.image.angle ?? 0) + - (((image.aangle ?? 0) - (image.image.angle ?? 0)) * - (one.farme - beforefarme)) / - (afterfarme - beforefarme || 1))) / - 180; - - if (one.hero) { - let sx, sy; - if (core.status.heroMoving < 0) { - sx = 0; - sy = 0; - } else { - sx = - core.utils.scan[core.status.hero.loc.direction].x * - 4 * - core.status.heroMoving; - sy = - core.utils.scan[core.status.hero.loc.direction].y * - 4 * - core.status.heroMoving; - } - const herox = core.status.hero.loc.x * 32 + 16 + sx; - const heroy = core.status.hero.loc.y * 32 + 16 + sy; - core.drawImage( - anctx, - img, - cx, - cy, - cw, - ch, - herox + (x - data.px) * one.scalex, - heroy + (y - data.py) * one.scaley, - w * one.scalex, - h * one.scaley, - angle - ); - } else { - core.drawImage( - anctx, - img, - cx, - cy, - cw, - ch, - one.x + (x - data.px) * one.scalex, - one.y + (y - data.py) * one.scaley, - w * one.scalex, - h * one.scaley, - angle - ); - } - } - } - }); - data.soundList.forEach(function (sound) { - const lisen = - sound.sound && - core.sounds[sound.sound] && - core.musicStatus.soundStatus; - if (one.farme == sound.startfarme && lisen) { - if (sound.stopbefore) core.stopSound(); - core.playSound(sound.sound); - } - }); - one.farme++; - if (one.farme > data.allFarme) core.plugin.playing.delete(one); - } - }); - } - }); - }, - "intro&loop": function () { // 在此增加新插件 - this.introAndLoop = function (intro, time, loop) { - core.playBgm(intro); - setTimeout(() => { - core.playBgm(loop); - }, time * 1000); + const animate2 = document.createElement("canvas"); //画布设置 + animate2.style.zIndex = 91; + animate2.id = "animate2"; + animate2.classList.add("gameCanvas", "anti-aliasing"); + animate2.style.display = "block"; + animate2.width = 416; + animate2.height = 416; + animate2.style.width = core.__PIXELS__ * core.domStyle.scale + "px"; + animate2.style.height = core.__PIXELS__ * core.domStyle.scale + "px"; + main.dom.animate2 = animate2; + const anctx = animate2.getContext("2d"); + + main.dom.gameDraw.appendChild(animate2); + + core.plugin.playing = new Set(); + + this.setanimate = function ( + name, + px, + py, + width, + height, + allFarme, + imageList, + soundList + ) { + const data = { + px: px, + py: py, + width: width, + height: height, + allFarme: allFarme, + imageList: imageList, + soundList: soundList, + }; + core.setFlag("animate_" + name, data); }; + this.deleteanimate = function (name) { + core.setFlag("animate_" + name); + }; + let thistime = 0; + this.playanimate = function (name, x, y, hero, scalex, scaley, callback) { + const data = { + name: name, + x: x, + y: y, + hero: hero, + scalex: scalex, + scaley: scaley, + farme: 0, + callback + }; + + core.plugin.playing.add(data); + }; + core.registerAnimationFrame("animateonmap", true, function (timestamp) { + if (timestamp - thistime > 1000 / 60) { + thistime = timestamp; + core.clearMap(anctx); + core.plugin.playing.forEach((one) => { + const data = flags["animate_" + one.name]; + if (!data) { + core.plugin.playing.delete(one); + } else { + data.imageList.forEach(function (image) { + if ( + one.farme >= (image.beforefarme ?? 0) && + one.farme <= (image.afterfarme ?? data.allFarme) + ) { + const img = core.material.images.images?.[image.image]; + if (img) { + const gla = image.globalAlpha ?? 100; + const agla = image.aglobalAlpha ?? gla, + beforefarme = image.beforefarme ?? 0; + const afterfarme = image.afterfarme ?? data.allFarme; + + anctx.globalAlpha = + (gla + + ((agla - gla) * (one.farme - beforefarme)) / + (afterfarme - beforefarme || 1)) / + 100; + + const cx = + (image.cx ?? 0) + + (((image.acx ?? 0) - (image.cx ?? 0)) * + (one.farme - beforefarme)) / + (afterfarme - beforefarme || 1), + cy = + (image.cy ?? 0) + + (((image.acy ?? 0) - (image.cy ?? 0)) * + (one.farme - beforefarme)) / + (afterfarme - beforefarme || 1), + cw = + (image.cw ?? img.width) + + (((image.acw ?? img.width) - (image.cw ?? img.width)) * + (one.farme - beforefarme)) / + (afterfarme - beforefarme || 1), + ch = + (image.ch ?? img.height) + + (((image.acw ?? img.height) - (image.cw ?? img.height)) * + (one.farme - beforefarme)) / + (afterfarme - beforefarme || 1), + x = + (image.x ?? 0) + + (((image.ax ?? 0) - (image.x ?? 0)) * + (one.farme - beforefarme)) / + (afterfarme - beforefarme || 1), + y = + (image.y ?? 0) + + (((image.ay ?? 0) - (image.y ?? 0)) * + (one.farme - beforefarme)) / + (afterfarme - beforefarme || 1), + w = + (image.w ?? one.width) + + (((image.aw ?? one.width) - (image.w ?? one.width)) * + (one.farme - beforefarme)) / + (afterfarme - beforefarme || 1), + h = + (image.h ?? one.height) + + (((image.aw ?? one.height) - (image.w ?? one.height)) * + (one.farme - beforefarme)) / + (afterfarme - beforefarme || 1), + angle = + (Math.PI * + ((image.image.angle ?? 0) + + (((image.aangle ?? 0) - (image.image.angle ?? 0)) * + (one.farme - beforefarme)) / + (afterfarme - beforefarme || 1))) / + 180; + + if (one.hero) { + let sx, sy; + if (core.status.heroMoving < 0) { + sx = 0; + sy = 0; + } else { + sx = + core.utils.scan[core.status.hero.loc.direction].x * + 4 * + core.status.heroMoving; + sy = + core.utils.scan[core.status.hero.loc.direction].y * + 4 * + core.status.heroMoving; + } + const herox = core.status.hero.loc.x * 32 + 16 + sx; + const heroy = core.status.hero.loc.y * 32 + 16 + sy; + core.drawImage( + anctx, + img, + cx, + cy, + cw, + ch, + herox + (x - data.px) * one.scalex, + heroy + (y - data.py) * one.scaley, + w * one.scalex, + h * one.scaley, + angle + ); + } else { + core.drawImage( + anctx, + img, + cx, + cy, + cw, + ch, + one.x + (x - data.px) * one.scalex, + one.y + (y - data.py) * one.scaley, + w * one.scalex, + h * one.scaley, + angle + ); + } + } + } + }); + data.soundList.forEach(function (sound) { + const lisen = + sound.sound && + core.sounds[sound.sound] && + core.musicStatus.soundStatus; + if (one.farme == sound.startfarme && lisen) { + if (sound.stopbefore) core.stopSound(); + core.playSound(sound.sound); + } + }); + one.farme++; + if (one.farme > data.allFarme) { + core.plugin.playing.delete(one) + if (one.callback) { one.callback() } + }; + } + }); + } + }); }, + "intro&loop": function () { + // 在此增加新插件 + this.introAndLoop = function (intro, time, loop) { + core.playBgm(intro); + setTimeout(() => { + core.playBgm(loop); + }, time * 1000); + }; + }, "开局选项悬停": function () { // 在此增加新插件 @@ -18728,8 +18510,13 @@ var plugins_bb40132b_638b_4a9f_b028_d3fe47acc8d1 = oneTurn, enemyInfo, equipInfo, - farme + farme, + damageInfo ) { + let attack = false + if (heroInfo.isAttack) attack = true + if (enemyInfo.isAttack) attack = true + equipInfo.forEach(function (v) { if (v.isAttack) attack = true }) core.clearMap(ctx); let animate = Math.floor(farme / 30); core.fillRect(ctx, 64, 64, 288, 288, "rgba(0,0,0,0.5)"); @@ -18739,46 +18526,49 @@ var plugins_bb40132b_638b_4a9f_b028_d3fe47acc8d1 = ctx, hero.name, 127, - 123, + 128, "#FFFFFF", "#000000", - 'bold 14px pala' + "bold 14px pala" ); core.setTextAlign(ctx, "left"); - core.drawIcon(ctx, 'hp', 70, 190, 16, 16) - core.drawIcon(ctx, 'atk', 70, 210, 16, 16) - core.drawIcon(ctx, 'def', 70, 230, 16, 16) - core.drawIcon(ctx, 'I374', 70, 250, 16, 16) - core.drawIcon(ctx, 'I375', 70, 270, 16, 16) - core.drawIcon(ctx, 'mdef', 70, 290, 16, 16) - core.drawIcon(ctx, 'amulet', 70, 310, 16, 16) - core.drawIcon(ctx, 'jumpShoes', 70, 330, 16, 16) + core.drawIcon(ctx, "hp", 70, 190, 16, 16); + core.drawIcon(ctx, "atk", 70, 210, 16, 16); + core.drawIcon(ctx, "def", 70, 230, 16, 16); + core.drawIcon(ctx, "I374", 70, 250, 16, 16); + core.drawIcon(ctx, "I375", 70, 270, 16, 16); + core.drawIcon(ctx, "mdef", 70, 290, 16, 16); + core.drawIcon(ctx, "amulet", 70, 310, 16, 16); + core.drawIcon(ctx, "jumpShoes", 70, 330, 16, 16); core.fillBoldText( ctx, - '生命 ' + core.formatBigNumber(heroInfo.hp, true) + " / " + core.formatBigNumber(heroInfo.hpmax, true), + "生命 " + + core.formatBigNumber(heroInfo.hp, true) + + " / " + + core.formatBigNumber(heroInfo.hpmax, true), 90, 205, "#FFFFFF", "#000000", - 'bold 14px pala' + "bold 14px pala" ); core.fillBoldText( ctx, - '攻击 ' + core.formatBigNumber(heroInfo.atk), + "攻击 " + core.formatBigNumber(heroInfo.atk), 90, 225, "#FFFFFF", "#000000", - 'bold 14px pala' + "bold 14px pala" ); core.fillBoldText( ctx, - '防御 ' + core.formatBigNumber(heroInfo.def), + "防御 " + core.formatBigNumber(heroInfo.def), 90, 245, "#FFFFFF", "#000000", - 'bold 14px pala' + "bold 14px pala" ); core.fillBoldText( ctx, @@ -18787,43 +18577,51 @@ var plugins_bb40132b_638b_4a9f_b028_d3fe47acc8d1 = 265, "#FFFFFF", "#000000", - 'bold 14px pala' + "bold 14px pala" ); core.fillBoldText( ctx, - "法攻 " + core.formatBigNumber(heroInfo.matk / 100 * heroInfo.spell) + "(" + heroInfo.matk + "%)", + "法攻 " + + core.formatBigNumber((heroInfo.matk / 100) * heroInfo.spell) + + "(" + + heroInfo.matk + + "%)", 90, 285, "#FFFFFF", "#000000", - 'bold 14px pala' + "bold 14px pala" ); core.fillBoldText( ctx, - '护盾 ' + core.formatBigNumber(heroInfo.mdef / 100 * heroInfo.spell) + "(" + heroInfo.mdef + "%)", + "护盾 " + + core.formatBigNumber((heroInfo.mhp / 100) * heroInfo.spell) + + "(" + + heroInfo.mhp + + "%)", 90, 305, "#FFFFFF", "#000000", - 'bold 14px pala' + "bold 14px pala" ); core.fillBoldText( ctx, - '法抗 ' + heroInfo.spelldef + "%", + "法抗 " + heroInfo.mdef + "%", 90, 325, "#FFFFFF", "#000000", - 'bold 14px pala' + "bold 14px pala" ); core.fillBoldText( ctx, - '速度 ' + core.formatBigNumber(heroInfo.speed), + "速度 " + core.formatBigNumber(heroInfo.speed), 90, 345, "#FFFFFF", "#000000", - 'bold 14px pala' + "bold 14px pala" ); core.strokeRect(ctx, 112, 139, 32, 48, "rgba(255,255,255,1)", 1); @@ -18840,17 +18638,17 @@ var plugins_bb40132b_638b_4a9f_b028_d3fe47acc8d1 = 48 ); - core.setTextAlign(ctx, "center"); core.fillBoldText( ctx, enemyInfo.name, 289, - 123, + 128, "#FFFFFF", "#000000", - 'bold 14px pala' + "bold 14px pala" ); + core.setTextAlign(ctx, "right"); if (enemyInfo.cls === "enemys") { core.strokeRect(ctx, 272, 155, 32, 32, "rgba(255,255,255,1)", 1); @@ -18883,37 +18681,37 @@ var plugins_bb40132b_638b_4a9f_b028_d3fe47acc8d1 = 48 ); } - core.drawIcon(ctx, 'hp', 330, 190, 16, 16) - core.drawIcon(ctx, 'atk', 330, 210, 16, 16) - core.drawIcon(ctx, 'def', 330, 230, 16, 16) - core.drawIcon(ctx, 'amulet', 330, 250, 16, 16) - core.drawIcon(ctx, 'jumpShoes', 330, 270, 16, 16) + core.drawIcon(ctx, "hp", 330, 190, 16, 16); + core.drawIcon(ctx, "atk", 330, 210, 16, 16); + core.drawIcon(ctx, "def", 330, 230, 16, 16); + core.drawIcon(ctx, "amulet", 330, 250, 16, 16); + core.drawIcon(ctx, "jumpShoes", 330, 270, 16, 16); core.fillBoldText( ctx, - core.formatBigNumber(enemyInfo.hp, true) + ' 生命', + core.formatBigNumber(enemyInfo.hp, true) + " 生命", 330, 205, "#FFFFFF", "#000000", - 'bold 14px pala' + "bold 14px pala" ); core.fillBoldText( ctx, - core.formatBigNumber(enemyInfo.atk) + ' 攻击', + core.formatBigNumber(enemyInfo.atk) + " 攻击", 330, 225, "#FFFFFF", "#000000", - 'bold 14px pala' + "bold 14px pala" ); core.fillBoldText( ctx, - core.formatBigNumber(enemyInfo.def) + ' 防御', + core.formatBigNumber(enemyInfo.def) + " 防御", 330, 245, "#FFFFFF", "#000000", - 'bold 14px pala' + "bold 14px pala" ); core.fillBoldText( @@ -18923,7 +18721,7 @@ var plugins_bb40132b_638b_4a9f_b028_d3fe47acc8d1 = 265, "#FFFFFF", "#000000", - 'bold 14px pala' + "bold 14px pala" ); core.fillBoldText( ctx, @@ -18932,24 +18730,147 @@ var plugins_bb40132b_638b_4a9f_b028_d3fe47acc8d1 = 285, "#FFFFFF", "#000000", - 'bold 14px pala' + "bold 14px pala" ); - core.fillBoldText(ctx, + core.fillBoldText( + ctx, "V", 219, 163, "#FFFFFF", "#000000", - 'bold 48px pala') - core.fillBoldText(ctx, + "bold 48px pala" + ); + core.fillBoldText( + ctx, "s", 231, 163, "#FFFFFF", "#000000", - 'bold 36px pala') + "bold 36px pala" + ); + + if (!attack) enemyInfo.now += enemyInfo.speed / oneTurn * 215 + let enemynow = Math.min(100 + enemyInfo.now, 315) + ctx.fillStyle = "#FFFFFF" + ctx.beginPath() + ctx.moveTo(enemynow, 100) + ctx.lineTo(enemynow + 3, 90) + ctx.lineTo(enemynow - 3, 90) + ctx.closePath() + ctx.fill() + + if (enemyInfo.cls === "enemys") { + + + core.drawImage( + ctx, + core.getBlockInfo(enemyInfo.id).image, + 32, + core.getBlockInfo(enemyInfo.id).posY * 32, + 32, + 32, + enemynow - 16, + 64, + 32, + 32 + ); + } else { + + + core.drawImage( + ctx, + core.getBlockInfo(enemyInfo.id).image, + 32, + core.getBlockInfo(enemyInfo.id).posY * 48, + 32, + 19, + enemynow - 16, + 70, + 32, + 19 + ); + } + core.drawLine(ctx, 100, 105, 315, 105, '#FFFFFF', 5) + equipInfo.forEach(function (v) { + if (!attack) v.now += v.speed / oneTurn * 215 + let vnow = Math.min(100 + v.now, 315) + ctx.beginPath() + ctx.moveTo(vnow, 100) + ctx.lineTo(vnow + 3, 90) + ctx.lineTo(vnow - 3, 90) + ctx.closePath() + + ctx.fill() + + core.drawIcon(ctx, + v.id, + vnow - 16, + 64, + 32, + 32) + + }) + if (!attack) heroInfo.now += heroInfo.speed / oneTurn * 215 + let heronow = Math.min(100 + heroInfo.now, 315) + ctx.beginPath() + ctx.moveTo(heronow, 100) + ctx.lineTo(heronow + 3, 90) + ctx.lineTo(heronow - 3, 90) + ctx.closePath() + + ctx.fill() + console.log(heronow) + core.drawImage(ctx, + "hero.webp", + 0, + 0, + 32, + 19, + heronow - 16, + 70, + 32, + 19) + + if (heroInfo.now >= 215) { + heroInfo.isAttack = true; + animateOnAttack("sword", true, () => { + heroInfo.now -= 215 + heroInfo.isAttack = false + + }) + + } + if (enemyInfo.now >= 215) { + enemyInfo.isAttack = true + animateOnAttack("sword", false, () => { + enemyInfo.now -= 215 + enemyInfo.isAttack = false + + }) + } + equipInfo.forEach(v => { + if (v.now >= 215) { + v.isAttack = true + + animateOnAttack("sword", true, () => { + v.now -= 215 + v.isAttack = false + + }) + } + }) }; + function animateOnAttack(name, onenemy, callback) { + if (onenemy) { + core.playanimate(name, 290, 160, null, 1, 1, callback) + } else { + core.playanimate(name, 130, 160, null, 1, 1, callback) + } + } + this.attackAnimate = function ( enemyId, enemyInfo, @@ -18964,36 +18885,43 @@ var plugins_bb40132b_638b_4a9f_b028_d3fe47acc8d1 = hero_atk = core.getRealStatusOrDefault(hero, "atk"), hero_def = core.getRealStatusOrDefault(hero, "def"), hero_spell = core.getRealStatusOrDefault(hero, "spell"), - hero_matk = core.getRealStatusOrDefault(hero, "mdef"), + hero_matk = core.getRealStatusOrDefault(hero, "matk"), hero_mdef = core.getRealStatusOrDefault(hero, "mdef"), hero_speed = core.getRealStatusOrDefault(hero, "speed"), hero_hpmax = core.getRealStatusOrDefault(hero, "hpmax"), - hero_spelldef = core.getRealStatusOrDefault(hero, "spelldef"); + hero_mhp = core.getRealStatusOrDefault(hero, "mhp"); const heroInfo = { hp: hero_hp, hpmax: hero_hpmax, atk: hero_atk, def: hero_def, spell: hero_spell, - spelldef: hero_spelldef, + mhp: hero_mhp, matk: hero_matk, mdef: hero_mdef, speed: hero_speed, now: 0, + isAttack: false }; enemyInfo.id = enemyId; enemyInfo.cls = core.getClsFromId(enemyId); enemyInfo.name = core.material.enemys[enemyId].name; - + enemyInfo.now = 0; + enemyInfo.isAttack = false let oneTurn = heroInfo.speed + enemyInfo.speed; if (equipInfo.length > 0) { for (let i; i < equipInfo.length - 1; i++) { + equipInfo[i].now = 0 + equipInfo[i].isAttack = false oneTurn += equipInfo[i].speed; } } - oneTurn *= 100; + oneTurn *= 25; let time = 0, farme = 0; + + + core.registerAnimationFrame("attackAnimate", true, (temptime) => { if (temptime - time > 1000 / 60) { time = temptime; @@ -19002,7 +18930,8 @@ var plugins_bb40132b_638b_4a9f_b028_d3fe47acc8d1 = oneTurn, enemyInfo, equipInfo, - farme + farme, + damageInfo ); farme++; } @@ -19010,574 +18939,574 @@ var plugins_bb40132b_638b_4a9f_b028_d3fe47acc8d1 = }; }, "剧情内容": function () { - // 在此增加新插件 - // 每项为一个数组,第一项是名字,第二项是对话内容 - // 回放只会在同一个this下回放,进入剧情前请以事件块声明进入哪个剧情数组 - // 可以自由添加,但不能与已有插件及函数名相同,可以使用中文。 - this.chapter0 = [ - ["", "这些天,街道不曾下雨。"], - ["", "所以,那浸湿地面的,定是那些女孩们流落的鲜血无疑。"], - ["", "我蹲在充斥着铁锈味般恶臭的小巷中,悠闲地如是想着。"], - ["", "扑哧。"], - ["", "耳旁再次响起象征着某个女孩子死去的声音。"], - ["", "再一次——"], - ["", "再一次。"], - ["", "女子们被肢解成单纯的肉块。"], - ["", "我任由流下的血浸满全身,屏住自己的呼吸。"], - ["", "祈求自己能拥有从猎人手中逃脱的幸运。"], - ["", "扑哧。"], - ["", "直到刚才,我们还坐在去往娼馆的马车的路上。"], - ["", "而在这之中的某些人,已经不在这个世上了。"], - ["", "不,应该把“某些”换成“几乎所有”才更为恰当吧。"], - ["", "恐怕,不久之后我也会变成小巷中血腥的装饰品。"], - ["", "我是为了得到这种死法,才辛苦苟活至今的吗?"], - ["", "来个人告诉我啊——"], - ["", "谁都好。"], - ["", "来人啊!!"], - ["少女", "「呃······!?」"], - ["", "漆黑的物体充斥了我的整个视野"], - ["", "我很快意识到,那是只很大的脚。"], - ["", "必须要出声求救。"], - ["", "可是,耳中却只能听到自己的牙关不停交战的声音。"], - ["", "我是如此的无助。"], - ["", "逃跑也好,道歉也罢。"], - ["", "就连抬头看一眼将要杀掉我的人的面孔都做不到。"], - ["少女", "「······被杀」"], - ["", "会被杀。"], - ["", "会被杀!!"], - ["", "来自内心深处的冰冷预感,渐渐地在体内蔓延开来。"], - ["少女", "「不,不要······」"], - ["", "浮游都市,《诺瓦斯·艾蒂尔》。"], - ["", "《特别受灾地区》——"], - ["", "通称,《牢狱》"], - ["", "是被险峻的峭壁环绕,与世隔绝的,都市的最底部。"], - ["年轻人", "「放开我!」"], - ["年轻人", "「我只是在帮那个女人而已!」"], - ["年轻人", "「你们没听到吗!?」"], - ["年轻人", "「她是被受骗才会被卖到娼馆来的」"], - ["年轻人", "「用肮脏的手段把钱借给她父母的,就是你们这些家伙吧!?」"], - ["年轻人", "「给我说些什么啊」"], - ["凯伊姆", "「这些话等到了娼馆再说吧」"], - ["凯伊姆", "「我来抓你,只是受雇于人而已」"], - ["", "我走进娼馆《莉莉乌姆》的接待室。"], - ["", "正在桌旁整理账簿的奥兹停下手头的工作,抬起头向我看来。"], - ["奥兹", "「这不是凯伊姆先生吗,辛苦了」"], - ["奥兹", "「委托已经完成了吗?」"], - ["凯伊姆", "「啊啊,是这家伙没错吧」"], - ["", "奥兹用只要接触到就能杀人般的眼神在男人脸上搜过。"], - ["奥兹", "「没错,就是这个人」"], - ["凯伊姆", "「是么」"], - ["年轻人", "「你,你们要对我做什么」"], - ["奥兹", "「······」"], - ["", "奥兹用一个眼神,就让男人闭上了嘴。"], - ["", "然后,向我这边转过身来。"], - ["奥兹", "「抱歉啊,总是麻烦你去做这些无聊的事」"], - ["奥兹", "「都怪我们这边的年轻人太没用」"], - ["凯伊姆", "「客套话就免了」"], - ["奥兹", "「这还真是失礼了」"], - ["奥兹", "「喂,来个人」"], - ["光头男人", "「是」"], - ["奥兹", "「凯伊姆先生做完工作回来了」"], - ["光头男人", "「是,是,那个······」"], - ["奥兹", "「我是要你拿些酒来,这个蠢材!」"], - ["", "喀!"], - ["", "奥兹扔出的烟灰缸砸中了手下的额头。"], - ["", "鲜血四溅。"], - ["凯伊姆", "「不用这么麻烦」"], - ["凯伊姆", "「我接下来要去《菲诺列塔》」"], - ["奥兹", "「喔唷」"], - ["奥兹", "「既然如此,我就不留您在这里喝难饮的劣质酒了」"], - ["", "奥兹斜眼看着正捂住额头呻吟的手下,轻描淡写地说道。"], - ["凯伊姆", "「用这些钱去买药」"], - ["", "我将几枚铜钱仍在那个手下的身前。"], - ["奥兹", "「凯伊姆先生,不用对他们这么好」"], - ["凯伊姆", "「无妨」"], - ["凯伊姆", "「话说回来,那个要落跑的女人呢?」"], - ["奥兹", "「我把她交给那些年轻人了,现在应该正在体会人生的严苛吧」"], - ["奥兹", "「正好,趁此机会凯伊姆先生也来享受一番如何?」"], - ["年轻人", "「你,你们这些家伙,要对她做什么!?」"], - ["", "咣!"], - ["", "奥兹给了他一拳。"], - ["", "一击即倒。"], - ["", "喀,咚,咯!"], - ["", "奥兹毫不留情地向男人的脸上踩去。"], - ["年轻人", "「咕······呃咳······」"], - ["", "折断的牙齿伴着血泡被吐出。"], - ["", "这份白色在鲜红色的液体中格外显眼。"], - ["年轻人", "「你们以为做出这种事······卫兵会坐视不理吗······」"], - ["奥兹", "「啊啊,不会坐视不理的」"], - ["奥兹", "「应该会拿出你的钱包,和我们商量如何瓜分吧」"], - ["年轻人", "「那,那种事······」"], - ["", "这在牢狱是理所当然的事。"], - ["奥兹", "「怎么,头一回来牢狱么?」"], - ["", "男人点了点头。"], - ["奥兹", "「为了被骗的女人而来到牢狱,真是个规矩人啊」"], - ["奥兹", "「······前提是,被骗的人不是你」"], - ["年轻人", "「你说······我被骗了?」"], - ["年轻人", "「那,那是怎么回事!?」"], - ["奥兹", "「不用急,今天晚上会好好告诉你的」"], - ["", "奥兹抓起男人的脸。"], - ["", "为引诱客人的怜悯之心而装纯,是娼妇的惯用手段。"], - ["", "双亲被骗而借钱,结果作为抵押而将自己卖到这里,这是典型的说法。"], - [ - "", - "如果只是头脑发热而成为常客也就罢了,这次的男人热血过头,居然想出了要带女人私奔的计划。", - ], - [ - "", - "虽然女人半开玩笑地予以拒绝,但不知天高地厚的这家伙还是拉着她逃跑了。", - ], - ["", "不过,想要逃脱追击本来就是不可能的任务。"], - ["", "但即便如此,这种事情还是会一再的出现。"], - ["", "说谎的女人和被骗的男人。"], - ["", "在娼馆街,这是令人看到生厌的日常的风景。"], - ["凯伊姆", "「我要走了」"], - ["奥兹", "「好的,下次再麻烦您」"], - ["奥兹", "「之后吉克先生会将谢礼交给您的」"], - ["凯伊姆", "「啊啊」"], - ["", "我背向奥兹走出娼馆。"], - ["凯伊姆", "「······?」"], - ["", "从远方传来微弱的歌声。"], - ["", "是关卡广场的方向。"], - ["", "对了。"], - ["", "今天有觐见圣女的仪式。"], - ["", "当代的圣女伊莲——"], - ["", "俗称《盲眼之圣女》,据说即使在历代的圣女中,人气也是数一数二的。"], - ["", "广场上的人估计相当多吧。"], - ["", "虽然我也想去看看她长什么样,不过要在人潮中挤来挤去就免了。"], - ["", "还是老老实实去菲诺列塔喝烧酒吧。"], - ["", "正当我这样想着的时候,一个身影自小巷的那头走来。"], - ["凯伊姆", "「艾莉斯」"], - ]; - this.chapter01 = [ - ["艾莉斯", "「啊,凯伊姆」"], - ["艾莉斯", "「正好,我还想要去找你呢」"], - ["艾莉斯", "「没想到凯伊姆会主动出现······这是命运吗?」"], - ["凯伊姆", "「显然不是吧」"], - ["艾莉斯", "「啊,是么」"], - ["", "艾莉斯挑了挑整齐的双眉,微微地哼了一声。"], - [ - "", - "虽然是个相当引人注目的美人,但她这个将亲切儿子丢入无底深渊的性格,为自己扣了不少的分", - ], - ["", "给人印象最深的,就是那潭水般的双瞳。"], - ["", "在漆黑的瞳孔中,完全看不出感情的波动。"], - ["艾莉斯", "「喜欢我的眼睛吗?」"], - ["艾莉斯", "「如果想要的话就给你吧?」"], - ["凯伊姆", "「用不着」"], - ["艾莉斯", "「阿拉,可惜」"], - ["凯伊姆", "「那么,找我有什么事」"], - ["艾莉斯", "「梅尔特的钱好像被偷了」"], - ["凯伊姆", "「钱被偷了?都几岁了还这么没用」"], - ["艾莉斯", "「不要对我说啊」"], - ["凯伊姆", "「那家伙,该不会说要让我去抓那个小偷吧?」"], - ["艾莉斯", "「就是这样」"], - ["凯伊姆", "「笨蛋吗」"], - ["凯伊姆", "「如果是小钱的话,就当做是买个教训吧」"], - ["艾莉斯", "「说起来,被盗的是这个月的上纳金」"], - ["凯伊姆", "「你说什么?」"], - ["艾莉斯", "「用这些钱买教训,也太过奢侈了呢」"], - ["凯伊姆", "「知道了,我去找」"], - ["凯伊姆", "「小偷的特征呢」"], - ["艾莉斯", "「男孩子」"], - ["艾莉斯", "「······而且,背后有翅膀」"], - ["艾莉斯", "「虽然姑且是藏在身后,但是仔细观察的话是很明显的」"], - ["凯伊姆", "「羽化病吗」"], - [ - "艾莉斯", - "「那些人可是毫不留情的,所以即使是为了那个孩子,也要赶快抓到他」", - ], - ["凯伊姆", "「注意到他逃窜的方向了吗?」"], - ["艾莉斯", "「广场那边」"], - ["艾莉斯", "「虽然刚才《不蚀金锁》的人去追了,不过多半是······」"], - ["凯伊姆", "「偏偏还是广场吗」"], - ["艾莉斯", "「今天是觐见圣女大人的日子」"], - ["凯伊姆", "「我知道」"], - ["凯伊姆", "「尽量找找看就好」"], - ]; - this.chapter02 = [ - ["不蚀金锁成员", "「凯伊姆先生,凯伊姆先生」"], - ["不蚀金锁成员", "「您已经和艾莉斯大夫见过面了吗?」"], - ["凯伊姆", "「啊啊,所以才会追过来的」"], - ["凯伊姆", "「看到小偷了吗?」"], - [ - "不蚀金锁成员", - "「没有,他向广场那边逃了过去,今天这么拥挤,我们也只能放弃了」", - ], - [ - "不蚀金锁成员", - "「不过,我也只是刚好在店里所以才追了过去,并不是受人所托」", - ], - ["不蚀金锁成员", "「我已经准备撤退了」"], - ["不蚀金锁成员", "「凯伊姆先生还要继续追吗?」"], - ["凯伊姆", "「啊啊」"], - ["", "做完情报交换之后,我跟男人道别。"], - ["凯伊姆", "「和我想的一样啊······」"], - ["", "在牢狱中最大的广场上,聚集着看不到尽头的人群。"], - ["", "就算是来参见圣女祈祷,这人数也太多了点吧。"], - ["", "自然,我也找不到逃跑的孩子。"], - ["", "是混杂到人群中了吧。"], - ["", "如果已经从广场上脱身了的话,就更难发现了。"], - ["", "只好赌他还在这里了。"], - ["", "我先移动到了一个视野良好的地方。"], - ["", "从这里,一眼就可以看到人群的变化。"], - ["", "广场还是沸腾起来。"], - ["", "抬头望去,原来是在天台之上出现了一个人影"], - ["", "但是,与周围的期待不同,现身的是一名中年的神官。"], - ["", "骂声四溢。"], - ["", "神官则是笑着摆正衣领"], - ["神官", "「从现在开始,举行谒见的仪式」"], - [ - "神官", - "「在参见那位大人之前,我希望牢狱的诸位再次思考这个《诺瓦斯·艾蒂尔》存在的意义······」", - ], - [ - "神官", - "「初代圣女伊莲大人,便是也难怪这崇高的祈祷之力,令《诺瓦斯·艾蒂尔》浮在空中,拯救了我们的祖先」", - ], - [ - "神官", - "「这之后的几百年来,传承了初代大人力量的历代圣女伊莲大人,让这里留在了空中」", - ], - [ - "神官", - "「这座都市是被圣女大人守护的人类最后的圣域,而我们则是被选召的虔诚的信徒」", - ], - ["神官", "「怀着对圣女的感激祈祷吧,感谢圣女伊莲吧!并献上祈祷!」"], - ["圣女", "「不忘感谢与祈祷,神才会拯救我们」"], - ["圣女", "「与我一起,向神虔诚地祈祷吧」"], - ["", "广场上欢声雷动。"], - ["", "圣女没有回应喧嚣的人声,而是静静地合上双眼面向广场。"], - ["", "虽然感觉有些冷淡,但总比像个傻瓜似的笑着向这群人挥手要强。"], - ["", "她掌握着这条街道,还有在这条街上生活的人的命运。"], - ["", "比起揽得人气,她更想要为了街道的继续存在而献出全力。"], - ["", "也是为了不让《大崩落》的惨剧再度发生。"], - ["", "十几年前的那场悲剧。"], - [ - "", - "虽然在我脑海中的记忆已经相当模糊,但哪怕只是稍有触及,不快的感觉都会在胸口蔓延开。", - ], - ["凯伊姆", "「······」"], - ["", "这时我才想起,现在不是我在这里看圣女的时候。"], - ["女声", "「——っ!?」"], - ["围观的女人", "「羽,羽化病人!?」"], - ["围观的中年人", "「喂,谁去叫下羽狩」"], - ["惊慌的观众", "「你这家伙不要靠近我,要是传染了可怎么办」"], - ["粗鲁的观众", "「你这小鬼赶快滚开」"], - ["凯伊姆", "「接下来」"], - ["圣女", "「发生什么事了?看上去似乎很嘈杂」"], - ["随从", "「似乎是某个人逃跑了······具体的我也不是很清楚」"], - ["神官", "「圣女大人,继续待在天台上可能会出事,请您先回到室内吧」"], - ["圣女", "「不用在意我,比起那个,我更关心究竟发生了什么事」"], - ["神官", "「对不起,我真的不知道」"], - ["圣女", "「······是吗」"], - ["男", "「恕我僭越,请准许我说明情况」"], - ["男", "「在来觐见的人群中出现了《羽化病》的患者」"], - ["男", "「围观的人群因而产生了骚动」"], - [ - "男", - "「现在,《防疫局》已经派遣了部队。我想不久之后,他们就会安静下来了」", - ], - ["圣女", "「羽化病······」"], - ["男", "「怎么了?」"], - ["圣女", "「没什么」"], - ["圣女", "「辛苦了,你的名字是?」"], - ["男", "「属下是在防疫局任职的,鲁基乌斯· 迪斯·米利尤」"], - ["神官", "「噢噢,阁下就是鲁基乌斯卿吗,我听说过你的传闻」"], - ["神官", "「阁下是在工作上相当出色的人呢」"], - ["鲁基乌斯", "「不敢当」"], - [ - "鲁基乌斯", - "「话说回来,这次是属下警备工作的失职。让圣女大人见到这不成体统的一面,请您见谅」", - ], - [ - "圣女", - "「即使是目不见物的我,也能感受到聚集于此的民众数量之多。警备工作难以展开也在情理之中」", - ], - ["鲁基乌斯", "「属下不胜惶恐」"], - ["鲁基乌斯", "「接下来属下还要回到工作岗位上,在这里就先告退了」"], - ["圣女", "「鲁基乌斯先生」"], - ["鲁基乌斯", "「属下在」"], - ["圣女", "「你是怎样看待羽狩的工作的呢?」"], - ["神官", "「圣,圣女大人」"], - [ - "鲁基乌斯", - "「防疫局的工作是国王陛下赐予的重要职务。属下非常荣幸能够为这个都市的繁荣尽一份微薄之力」", - ], - ["神官", "「不,不亏是鲁基乌斯卿,相当优秀的想法」"], - ["圣女", "「是吗。辛苦你了」"], - ["随从", "「圣女大人······」"], - ["鲁基乌斯", "「······」"], - ["鲁基乌斯", "「那么,属下就回岗位去了」"], - ]; - this.chapter03 = [ - ["", "从羽化病的少年纷乱的足音中,可以听得出相当的疲劳。"], - ["", "显然,他并没有想到我会捷足先登吧。"], - ["", "少年惶恐地回头看了一眼后,微微露出安心的表情,双手拄在膝盖上。。"], - ["凯伊姆", "「辛苦你了」"], - ["羽化病患少年", "「稀!?」"], - ["凯伊姆", "「逃到贫民区是个不错的想法」"], - ["羽化病患少年", "「你,你是,羽狩吗?」"], - ["凯伊姆", "「不是」"], - ["羽化病患少年", "「什,什么啊······混蛋,不要吓我啊」"], - ["凯伊姆", "「我对令你受惊这件事致以歉意」"], - ["凯伊姆", "「作为回报,麻烦你把从店里偷的钱交出来吧」"], - ["羽化病患少年", "「钱?你在说什么」"], - ["凯伊姆", "「你要找的腰上的东西,掉在你身后了」"], - ["羽化病患少年", "「哎?」"], - ["羽化病患少年", "「呃呀」"], - ["羽化病患少年", "「你······你这混蛋」"], - ["凯伊姆", "「······」"], - ["凯伊姆", "「把偷的钱交出来」"], - ["羽化病患少年", "「我不知道你在······咕」"], - ["羽化病患少年", "「你,你说是我偷的······有什么证据吗」"], - ["凯伊姆", "「你还挺倔的啊」"], - ["凯伊姆", "「不过,给我听好了」"], - ["凯伊姆", "「你偷的那些钱,是要上缴给《不蚀金锁》的上纳金」"], - ["凯伊姆", "「而且,钱的主人是从前和吉克颇有渊源的女人」"], - ["羽化病患少年", "「吉克?」"], - ["凯伊姆", "「他是《不蚀金锁》的主人,这么说你就明白了吧」"], - ["羽化病患少年", "「哎?哎?怎么会······」"], - ["凯伊姆", "「再问你一遍,钱在哪里?」"], - ["羽化病患少年", "「是,是,是,在我的怀里」"], - ["凯伊姆", "「你没有擅自拿掉一部分吧」"], - ["羽化病患少年", "「是,是的」"], - ["羽化病患少年", "「那,那个,您是《不蚀金锁》的人吗?」"], - ["凯伊姆", "「算是吧」"], - ["羽化病患少年", "「我什么都可以做,请您一定要帮帮我」"], - ["凯伊姆", "「抱歉,我并没有兴趣去帮助他人」"], - ["羽化病患少年", "「我什么······什么,都会做的······」"], - ["羽化病患少年", "「我一直都是生活在下层的」"], - [ - "羽化病患少年", - "「可是,不知何时染上了羽化病······背后长出了翅膀······」", - ], - ["羽化病患少年", "「被寄宿工作的店赶了出来,只得流落到牢狱这里」"], - ["羽化病患少年", "「因为独自实在是饿的不行了,所以才会偷这些钱的」"], - [ - "羽化病患少年", - "「我明明没有做任何坏事······为什么······会遇到这种事······」", - ], - ["凯伊姆", "「谁知道」"], - ["羽化病患少年", "「呜······呜呜······接下来,要对我做什么?」"], - ["凯伊姆", "「我要把你带到组织那里」"], - ["羽化病患少年", "「怎,怎么这样」"], - ["凯伊姆", "「不过,那样做的前提是你不是羽化病人」"], - ["凯伊姆", "「组织也没有笨到把羽化病人招待到家里的程度」"], - ["羽化病患少年", "「那么,是要放我逃走吗?」"], - ["凯伊姆", "「我要让你学到教训」"], - ["凯伊姆", "「如果换做是组织的制裁,至少要有断条胳膊的觉悟」"], - ["凯伊姆", "「你的运气不错」"], - ["羽化病患少年", "「唔······啊,是的······」"], - ["凯伊姆", "「滚」"], - ["羽化病患少年", "「非常感谢」"], - ["羽化病患少年", "「唔啊!?」"], - ["男", "「到这里就结束了,羽化病人」"], - ["男", "「确认他的翅膀」"], - ["", "趁还没有被卷入麻烦的事情之前,赶快离开这里吧"], - ["羽狩的队长", "「那边的那个人」"], - ["凯伊姆", "「······有什么事?」"], - ["羽狩的队长", "「可以稍微让我问几句话吗」"], - ["凯伊姆", "「······」"], - ["凯伊姆", "「啊啊,无妨」"], - ["羽狩的队长", "「感谢您的合作」"], - ["", "队长殷勤地致以谢礼。"], - ["", "而在他的眼前,少年的衣服已经被他的补下们扯破。"], - ["", "在瘦骨嶙峋的裸露后背上,长有纯白的羽翼。"], - ["红发的羽狩", "「副队长,确认翅膀的持有了」"], - ["羽狩的副队长", "「保护他」"], - ["羽化病患少年", "「不要······请原谅,我······」"], - [ - "羽狩的副队长", - "「我们只是要带你去治愈院治疗羽化病,不是什么应该感到害怕的事情」", - ], - ["羽化病患少年", "「可是,可是」"], - ["羽狩的副队长", "「没关系的」"], - ["羽化病患少年", "「······哥,哥哥」"], - ["羽狩的副队长", "「你是羽化病人的亲属吗?」"], - ["凯伊姆", "「只是路人而已」"], - ["凯伊姆", "「顺带一提,我也没有打算找你们的麻烦」"], - [ - "羽狩的副队长", - "「前几天,有个和你说了同样的话的人,在我们背向他的瞬间对我们发动了袭击」", - ], - ["羽狩的副队长", "「我的一个部下就此永久失去了半截胳膊」"], - ["凯伊姆", "「我表示同情」"], - ["凯伊姆", "「我马上就会消失的,这样就没问题了吧?」"], - ["羽狩的副队长", "「嘛,不要这么慌张」"], - ["", "副队长看着羽化的少年。"], - ["羽狩的副队长", "「你与这个人是什么关系?没有被他殴打吗?」"], - ["羽化病患少年", "「没,没有」"], - [ - "羽狩的副队长", - "「如何对我们保持合作,你就可以在治愈院得到优先的治疗」", - ], - ["羽化病患少年", "「······」"], - ["羽化病患少年", "「那个人,是《不蚀金锁》的组织成员······」"], - ["羽化病患少年", "「突然说让我拿出钱来,我刚一拒绝他就打我」"], - ["羽狩的副队长", "「原来如此······」"], - [ - "羽狩的副队长", - "「那位少年说你是《不蚀金锁》的一员,不知此事是否属实?」", - ], - ["凯伊姆", "「当然不是」"], - ["凯伊姆", "「我只是从那里接受工作而已,并不是他们的成员」"], - ["羽狩的副队长", "「你的意思是说,少年在说谎吗?」"], - ["凯伊姆", "「啊啊」"], - [ - "凯伊姆", - "「如果你们和组织有关系的话,只要问问我是不是那里的成员就能明白事实了吧」", - ], - ["羽狩的副队长", "「就算我去询问,也无法从他们那里得到事实」"], - [ - "羽狩的副队长", - "「《不蚀金锁》的那些人一向都不对我们合作,我对此深感困扰」", - ], - ["凯伊姆", "「真是辛苦啊」"], - ["羽狩的副队长", "「说的是啊」"], - ["羽狩的副队长", "「其实,砍下我部下胳膊的似乎也是组织的成员呢」"], - [ - "羽狩的副队长", - "「无需如此警戒,我只是想在看守所向你咨询一些事情而已」", - ], - [ - "羽狩的副队长", - "「如果能够知晓牢狱与组织的事情,我们也可以尽可能地对更多的羽化病人进行保护」", - ], - ["羽狩的副队长", "「那和整条街道的和平也是紧密相关的吧?」"], - ["凯伊姆", "「我知道,你们有逮捕干扰狩猎羽化病人的权力」"], - [ - "凯伊姆", - "「但是,我没有对你们做出任何干扰,为什么要对我如此纠缠不休呢」", - ], - ["羽狩的副队长", "「那些话,我们会在看守所对你详细说明的」"], - ["凯伊姆", "「······」"], - ["", "在这里起争执的话,就会被羽狩加害。"], - ["", "就算能从这里脱身,今后只要碰面就会产生纠纷也是摆明的事情。"], - ["", "就算逃跑,也没有好的结果。"], - ["", "正当我想要打圆场的时候,刚才的气氛一瞬间产生了转变。"], - ["", "发生了什么事······"], - ["???", "「我认为,那位先生是正确的」"], - ["", "羽狩们一起回头。"], - ["", "而在他们视线的焦点处,"], - ["", "伫立着一位女性。"], - ["", "在端正的容颜下,代表着强烈意志的双眉十分显眼。"], - ["", "身体的柔软与紧紧包裹在其身上的羽狩制服,两者显得十分的不搭配。"], - ["", "我还是第一次看到女性的羽狩。"], - ["羽狩的副队长", "「队长,这是获得《不蚀金锁》情报的好机会」"], - [ - "羽狩的队长", - "「兰格副队长,就算是为了获得情报,也不能做出恫吓的发言啊」", - ], - ["兰格副队长", "「我并没有打算去恫吓他······」"], - ["羽狩的队长", "「告诉我那个被砍掉胳膊的队员的名字」"], - ["羽狩的队长", "「我会去探望他的」"], - ["兰格副队长", "「那个是······」"], - ["羽狩的队长", "「我知道,你一直在为有所收获而努力工作」"], - ["羽狩的队长", "「但是,正因为我们的工作是为民众提供帮助」"], - ["羽狩的队长", "「所以就更不能损害人与人之间的信赖」"], - ["兰格副队长", "「我会铭记在心」"], - ["羽狩的队长", "「这位先生,我的部下失礼了」"], - ["凯伊姆", "「只要不对我再来一次就好」"], - ["羽狩的队长", "「请稍等」"], - ["凯伊姆", "「有什么事?」"], - ["羽狩的队长", "「我想确认一件事」"], - ["羽狩的队长", "「你真的不是《不蚀金锁》的成员吗?」"], - ["凯伊姆", "「真的」"], - ["凯伊姆", "「如果我说是的话,你有什么打算?」"], - ["羽狩的队长", "「我听过传闻,说他们是用依靠暴力而得的钱在生活」"], - ["凯伊姆", "「······这样啊」"], - ["凯伊姆", "「如果能有收获就好了啊」"], - ]; - this.chapter04 = [ - ["梅尔特", "「欢迎光临」"], - ["梅尔特", "「辛苦了」"], - ["梅尔特", "「抱歉,又拜托给你了个这么麻烦的工作」"], - ["凯伊姆", "「没什么,比想象中完成的更容易」"], - ["梅尔特", "「那就好」"], - ["梅尔特", "「这是我的一点谢意」"], - ["凯伊姆", "「味道有些变化啊」"], - ["梅尔特", "「啊,被发现了?」"], - ["梅尔特", "「最近,没能到手什么好的原料呢」"], - ["凯伊姆", "「去拜托吉克如何?」"], - [ - "梅尔特", - "「话是这么说,但是总不能用店里采购的这种小事去麻烦他吧······」", - ], - ["凯伊姆", "「那希望你也不要来麻烦我」"], - ["梅尔特", "「那 是 两 码 事」"], - ["梅尔特", "「再说,凯伊姆是靠着工作来生活的吧」"], - [ - "梅尔特", - "「而且,自己的钱被偷了这么害羞的事,向凯伊姆意外的其他人都说不出口」", - ], - ["凯伊姆", "「反正,也已经传到吉克的耳朵里了」"], - ["梅尔特", "「这是面子问题啊,面子问题」"], - ["凯伊姆", "「嘛,算了」"], - ["凯伊姆", "「这样就好了吧?」"], - ["梅尔特", "「这是钱包呢」"], - ["梅尔特", "「嗯,东西没少」"], - ["梅尔特", "「太好啦—这个月的上纳金,我可全部都放在里面了呢」"], - ["梅尔特", "「如果没有找到的话,说不定就又会被送到娼馆里了呢」"], - ["凯伊姆", "「在那边不是来钱更快吗?」"], - ["梅尔特", "「阿拉,你是在说我还能有魅力吗?」"], - ["凯伊姆", "「这是客套话而已」"], - ["梅尔特", "「欺负人」"], - ["梅尔特", "「总而言之,今天帮大忙了」"], - ["梅尔特", "「谢礼嘛······」"], - ["凯伊姆", "「就记在账单上吧」"], - ["梅尔特", "「了解—盛谢惠顾了哦?」"], - ["", "喀啷喀啷"], - ["", "门铃响起"], - ["", "喧哗瞬间安静下来。"], - ["", "进来的人是吉克。"], - ["", "是掌控着牢狱的组织之一,《不蚀金锁》的头目。"], - ["", "不仅组织的成员,就连店内一般的客人也对他以注目礼表示敬意。"], - ["吉克", "「各位继续吧」"], - ["", "仿佛停滞的时钟重新转动了一般,店内恢复了热闹的气氛。"], - ["吉克", "「抱歉,今天拜托你去做了无聊的工作」。"], - ["凯伊姆", "「不用介意」"], - ["", "吉克轻轻点了点头,在我右边坐了下来"], - ["凯伊姆", "「逃跑的男人怎么样了?」"], - ["吉克", "「嗯?已经不在这个世上了」。"], - ["吉克", "「有什么想要知道的事吗?」"], - ["凯伊姆", "「不,没什么」"], - ["吉克", "「那个无聊的家伙,完全没有趣味呢」"], - ["吉克", "「真希望他也替我负责清扫的部下也考虑考虑」"], - ["凯伊姆", "「真是灾难啊」"], - ["吉克", "「比起那个,我听说了哦。你去追羽化病人了啊」"], - ["凯伊姆", "「消息真灵通」"], - ["吉克", "「梅尔特也注意点」"], - ["吉克", "「你丢钱已经不是一回两回了」"], - ["梅尔特", "「好的—我会注意的。」"], - ["梅尔特", "「吉克还是平常的点单吧」"], - ["梅尔特", "「凯伊姆要再来一杯吗?」"], - ["", "我们用眼神点头示意后,梅尔特开始准备起酒来。"], - ["", "悠然地吐出眼圈后,吉克取出一个纸包放在柜台上。"], - ["吉克", "「这是抓捕逃跑男人的报酬」"], - ["凯伊姆", "「下次有什么事再告诉我」"], - ["梅尔特", "「来,久等了」"], - ["凯伊姆", "「话说回来梅尔特,为什么会被那种孩子偷到钱?」"], - ["吉克", "「让我猜猜看」"], - ["吉克", "「是那个吧,看某个特立独行的男人入迷了,所以就有了空隙?」"], - ["梅尔特", "「可惜—」"], - ["梅尔特", "「事实恰恰相反,是那家伙一直在纠缠我」"], - ["凯伊姆", "「完全把你当成新进的女佣了么」"], - ["梅尔特", "「我从前可是很有名的,不会被当成这种下人吧」"], - ["梅尔特", "「······而且,我没法对对我这么钟情的人发火啊」"], - ["凯伊姆&吉克", "「你傻啊」"], - ["梅尔特", "「异口同声呢,不亏是兄弟」"], - ["凯伊姆", "「别用这种称呼,怪恶心的」"], - ["吉克", "「说得没错」"], - ["吉克", "「······说起来······」"], - ["梅尔特", "「怎么了?」"], - ["吉克", "「有件事我一直很在意,我和凯伊姆,哪个是哥哥啊?」"], - ["凯伊姆", "「你也说这么无聊的话题」"], - ["吉克", "「不,这是很重要的事情」"], - ["吉克", "「梅尔特,事实是怎么样的?」"], - ["梅尔特", "「啊~是怎么样的呢~」"], - ["梅尔特", "「我忘记了」"], - ["吉克", "「骗人」"], - ]; -} + // 在此增加新插件 + // 每项为一个数组,第一项是名字,第二项是对话内容 + // 回放只会在同一个this下回放,进入剧情前请以事件块声明进入哪个剧情数组 + // 可以自由添加,但不能与已有插件及函数名相同,可以使用中文。 + this.chapter0 = [ + ["", "这些天,街道不曾下雨。"], + ["", "所以,那浸湿地面的,定是那些女孩们流落的鲜血无疑。"], + ["", "我蹲在充斥着铁锈味般恶臭的小巷中,悠闲地如是想着。"], + ["", "扑哧。"], + ["", "耳旁再次响起象征着某个女孩子死去的声音。"], + ["", "再一次——"], + ["", "再一次。"], + ["", "女子们被肢解成单纯的肉块。"], + ["", "我任由流下的血浸满全身,屏住自己的呼吸。"], + ["", "祈求自己能拥有从猎人手中逃脱的幸运。"], + ["", "扑哧。"], + ["", "直到刚才,我们还坐在去往娼馆的马车的路上。"], + ["", "而在这之中的某些人,已经不在这个世上了。"], + ["", "不,应该把“某些”换成“几乎所有”才更为恰当吧。"], + ["", "恐怕,不久之后我也会变成小巷中血腥的装饰品。"], + ["", "我是为了得到这种死法,才辛苦苟活至今的吗?"], + ["", "来个人告诉我啊——"], + ["", "谁都好。"], + ["", "来人啊!!"], + ["少女", "「呃······!?」"], + ["", "漆黑的物体充斥了我的整个视野"], + ["", "我很快意识到,那是只很大的脚。"], + ["", "必须要出声求救。"], + ["", "可是,耳中却只能听到自己的牙关不停交战的声音。"], + ["", "我是如此的无助。"], + ["", "逃跑也好,道歉也罢。"], + ["", "就连抬头看一眼将要杀掉我的人的面孔都做不到。"], + ["少女", "「······被杀」"], + ["", "会被杀。"], + ["", "会被杀!!"], + ["", "来自内心深处的冰冷预感,渐渐地在体内蔓延开来。"], + ["少女", "「不,不要······」"], + ["", "浮游都市,《诺瓦斯·艾蒂尔》。"], + ["", "《特别受灾地区》——"], + ["", "通称,《牢狱》"], + ["", "是被险峻的峭壁环绕,与世隔绝的,都市的最底部。"], + ["年轻人", "「放开我!」"], + ["年轻人", "「我只是在帮那个女人而已!」"], + ["年轻人", "「你们没听到吗!?」"], + ["年轻人", "「她是被受骗才会被卖到娼馆来的」"], + ["年轻人", "「用肮脏的手段把钱借给她父母的,就是你们这些家伙吧!?」"], + ["年轻人", "「给我说些什么啊」"], + ["凯伊姆", "「这些话等到了娼馆再说吧」"], + ["凯伊姆", "「我来抓你,只是受雇于人而已」"], + ["", "我走进娼馆《莉莉乌姆》的接待室。"], + ["", "正在桌旁整理账簿的奥兹停下手头的工作,抬起头向我看来。"], + ["奥兹", "「这不是凯伊姆先生吗,辛苦了」"], + ["奥兹", "「委托已经完成了吗?」"], + ["凯伊姆", "「啊啊,是这家伙没错吧」"], + ["", "奥兹用只要接触到就能杀人般的眼神在男人脸上搜过。"], + ["奥兹", "「没错,就是这个人」"], + ["凯伊姆", "「是么」"], + ["年轻人", "「你,你们要对我做什么」"], + ["奥兹", "「······」"], + ["", "奥兹用一个眼神,就让男人闭上了嘴。"], + ["", "然后,向我这边转过身来。"], + ["奥兹", "「抱歉啊,总是麻烦你去做这些无聊的事」"], + ["奥兹", "「都怪我们这边的年轻人太没用」"], + ["凯伊姆", "「客套话就免了」"], + ["奥兹", "「这还真是失礼了」"], + ["奥兹", "「喂,来个人」"], + ["光头男人", "「是」"], + ["奥兹", "「凯伊姆先生做完工作回来了」"], + ["光头男人", "「是,是,那个······」"], + ["奥兹", "「我是要你拿些酒来,这个蠢材!」"], + ["", "喀!"], + ["", "奥兹扔出的烟灰缸砸中了手下的额头。"], + ["", "鲜血四溅。"], + ["凯伊姆", "「不用这么麻烦」"], + ["凯伊姆", "「我接下来要去《菲诺列塔》」"], + ["奥兹", "「喔唷」"], + ["奥兹", "「既然如此,我就不留您在这里喝难饮的劣质酒了」"], + ["", "奥兹斜眼看着正捂住额头呻吟的手下,轻描淡写地说道。"], + ["凯伊姆", "「用这些钱去买药」"], + ["", "我将几枚铜钱仍在那个手下的身前。"], + ["奥兹", "「凯伊姆先生,不用对他们这么好」"], + ["凯伊姆", "「无妨」"], + ["凯伊姆", "「话说回来,那个要落跑的女人呢?」"], + ["奥兹", "「我把她交给那些年轻人了,现在应该正在体会人生的严苛吧」"], + ["奥兹", "「正好,趁此机会凯伊姆先生也来享受一番如何?」"], + ["年轻人", "「你,你们这些家伙,要对她做什么!?」"], + ["", "咣!"], + ["", "奥兹给了他一拳。"], + ["", "一击即倒。"], + ["", "喀,咚,咯!"], + ["", "奥兹毫不留情地向男人的脸上踩去。"], + ["年轻人", "「咕······呃咳······」"], + ["", "折断的牙齿伴着血泡被吐出。"], + ["", "这份白色在鲜红色的液体中格外显眼。"], + ["年轻人", "「你们以为做出这种事······卫兵会坐视不理吗······」"], + ["奥兹", "「啊啊,不会坐视不理的」"], + ["奥兹", "「应该会拿出你的钱包,和我们商量如何瓜分吧」"], + ["年轻人", "「那,那种事······」"], + ["", "这在牢狱是理所当然的事。"], + ["奥兹", "「怎么,头一回来牢狱么?」"], + ["", "男人点了点头。"], + ["奥兹", "「为了被骗的女人而来到牢狱,真是个规矩人啊」"], + ["奥兹", "「······前提是,被骗的人不是你」"], + ["年轻人", "「你说······我被骗了?」"], + ["年轻人", "「那,那是怎么回事!?」"], + ["奥兹", "「不用急,今天晚上会好好告诉你的」"], + ["", "奥兹抓起男人的脸。"], + ["", "为引诱客人的怜悯之心而装纯,是娼妇的惯用手段。"], + ["", "双亲被骗而借钱,结果作为抵押而将自己卖到这里,这是典型的说法。"], + [ + "", + "如果只是头脑发热而成为常客也就罢了,这次的男人热血过头,居然想出了要带女人私奔的计划。", + ], + [ + "", + "虽然女人半开玩笑地予以拒绝,但不知天高地厚的这家伙还是拉着她逃跑了。", + ], + ["", "不过,想要逃脱追击本来就是不可能的任务。"], + ["", "但即便如此,这种事情还是会一再的出现。"], + ["", "说谎的女人和被骗的男人。"], + ["", "在娼馆街,这是令人看到生厌的日常的风景。"], + ["凯伊姆", "「我要走了」"], + ["奥兹", "「好的,下次再麻烦您」"], + ["奥兹", "「之后吉克先生会将谢礼交给您的」"], + ["凯伊姆", "「啊啊」"], + ["", "我背向奥兹走出娼馆。"], + ["凯伊姆", "「······?」"], + ["", "从远方传来微弱的歌声。"], + ["", "是关卡广场的方向。"], + ["", "对了。"], + ["", "今天有觐见圣女的仪式。"], + ["", "当代的圣女伊莲——"], + ["", "俗称《盲眼之圣女》,据说即使在历代的圣女中,人气也是数一数二的。"], + ["", "广场上的人估计相当多吧。"], + ["", "虽然我也想去看看她长什么样,不过要在人潮中挤来挤去就免了。"], + ["", "还是老老实实去菲诺列塔喝烧酒吧。"], + ["", "正当我这样想着的时候,一个身影自小巷的那头走来。"], + ["凯伊姆", "「艾莉斯」"], + ]; + this.chapter01 = [ + ["艾莉斯", "「啊,凯伊姆」"], + ["艾莉斯", "「正好,我还想要去找你呢」"], + ["艾莉斯", "「没想到凯伊姆会主动出现······这是命运吗?」"], + ["凯伊姆", "「显然不是吧」"], + ["艾莉斯", "「啊,是么」"], + ["", "艾莉斯挑了挑整齐的双眉,微微地哼了一声。"], + [ + "", + "虽然是个相当引人注目的美人,但她这个将亲切儿子丢入无底深渊的性格,为自己扣了不少的分", + ], + ["", "给人印象最深的,就是那潭水般的双瞳。"], + ["", "在漆黑的瞳孔中,完全看不出感情的波动。"], + ["艾莉斯", "「喜欢我的眼睛吗?」"], + ["艾莉斯", "「如果想要的话就给你吧?」"], + ["凯伊姆", "「用不着」"], + ["艾莉斯", "「阿拉,可惜」"], + ["凯伊姆", "「那么,找我有什么事」"], + ["艾莉斯", "「梅尔特的钱好像被偷了」"], + ["凯伊姆", "「钱被偷了?都几岁了还这么没用」"], + ["艾莉斯", "「不要对我说啊」"], + ["凯伊姆", "「那家伙,该不会说要让我去抓那个小偷吧?」"], + ["艾莉斯", "「就是这样」"], + ["凯伊姆", "「笨蛋吗」"], + ["凯伊姆", "「如果是小钱的话,就当做是买个教训吧」"], + ["艾莉斯", "「说起来,被盗的是这个月的上纳金」"], + ["凯伊姆", "「你说什么?」"], + ["艾莉斯", "「用这些钱买教训,也太过奢侈了呢」"], + ["凯伊姆", "「知道了,我去找」"], + ["凯伊姆", "「小偷的特征呢」"], + ["艾莉斯", "「男孩子」"], + ["艾莉斯", "「······而且,背后有翅膀」"], + ["艾莉斯", "「虽然姑且是藏在身后,但是仔细观察的话是很明显的」"], + ["凯伊姆", "「羽化病吗」"], + [ + "艾莉斯", + "「那些人可是毫不留情的,所以即使是为了那个孩子,也要赶快抓到他」", + ], + ["凯伊姆", "「注意到他逃窜的方向了吗?」"], + ["艾莉斯", "「广场那边」"], + ["艾莉斯", "「虽然刚才《不蚀金锁》的人去追了,不过多半是······」"], + ["凯伊姆", "「偏偏还是广场吗」"], + ["艾莉斯", "「今天是觐见圣女大人的日子」"], + ["凯伊姆", "「我知道」"], + ["凯伊姆", "「尽量找找看就好」"], + ]; + this.chapter02 = [ + ["不蚀金锁成员", "「凯伊姆先生,凯伊姆先生」"], + ["不蚀金锁成员", "「您已经和艾莉斯大夫见过面了吗?」"], + ["凯伊姆", "「啊啊,所以才会追过来的」"], + ["凯伊姆", "「看到小偷了吗?」"], + [ + "不蚀金锁成员", + "「没有,他向广场那边逃了过去,今天这么拥挤,我们也只能放弃了」", + ], + [ + "不蚀金锁成员", + "「不过,我也只是刚好在店里所以才追了过去,并不是受人所托」", + ], + ["不蚀金锁成员", "「我已经准备撤退了」"], + ["不蚀金锁成员", "「凯伊姆先生还要继续追吗?」"], + ["凯伊姆", "「啊啊」"], + ["", "做完情报交换之后,我跟男人道别。"], + ["凯伊姆", "「和我想的一样啊······」"], + ["", "在牢狱中最大的广场上,聚集着看不到尽头的人群。"], + ["", "就算是来参见圣女祈祷,这人数也太多了点吧。"], + ["", "自然,我也找不到逃跑的孩子。"], + ["", "是混杂到人群中了吧。"], + ["", "如果已经从广场上脱身了的话,就更难发现了。"], + ["", "只好赌他还在这里了。"], + ["", "我先移动到了一个视野良好的地方。"], + ["", "从这里,一眼就可以看到人群的变化。"], + ["", "广场还是沸腾起来。"], + ["", "抬头望去,原来是在天台之上出现了一个人影"], + ["", "但是,与周围的期待不同,现身的是一名中年的神官。"], + ["", "骂声四溢。"], + ["", "神官则是笑着摆正衣领"], + ["神官", "「从现在开始,举行谒见的仪式」"], + [ + "神官", + "「在参见那位大人之前,我希望牢狱的诸位再次思考这个《诺瓦斯·艾蒂尔》存在的意义······」", + ], + [ + "神官", + "「初代圣女伊莲大人,便是也难怪这崇高的祈祷之力,令《诺瓦斯·艾蒂尔》浮在空中,拯救了我们的祖先」", + ], + [ + "神官", + "「这之后的几百年来,传承了初代大人力量的历代圣女伊莲大人,让这里留在了空中」", + ], + [ + "神官", + "「这座都市是被圣女大人守护的人类最后的圣域,而我们则是被选召的虔诚的信徒」", + ], + ["神官", "「怀着对圣女的感激祈祷吧,感谢圣女伊莲吧!并献上祈祷!」"], + ["圣女", "「不忘感谢与祈祷,神才会拯救我们」"], + ["圣女", "「与我一起,向神虔诚地祈祷吧」"], + ["", "广场上欢声雷动。"], + ["", "圣女没有回应喧嚣的人声,而是静静地合上双眼面向广场。"], + ["", "虽然感觉有些冷淡,但总比像个傻瓜似的笑着向这群人挥手要强。"], + ["", "她掌握着这条街道,还有在这条街上生活的人的命运。"], + ["", "比起揽得人气,她更想要为了街道的继续存在而献出全力。"], + ["", "也是为了不让《大崩落》的惨剧再度发生。"], + ["", "十几年前的那场悲剧。"], + [ + "", + "虽然在我脑海中的记忆已经相当模糊,但哪怕只是稍有触及,不快的感觉都会在胸口蔓延开。", + ], + ["凯伊姆", "「······」"], + ["", "这时我才想起,现在不是我在这里看圣女的时候。"], + ["女声", "「——っ!?」"], + ["围观的女人", "「羽,羽化病人!?」"], + ["围观的中年人", "「喂,谁去叫下羽狩」"], + ["惊慌的观众", "「你这家伙不要靠近我,要是传染了可怎么办」"], + ["粗鲁的观众", "「你这小鬼赶快滚开」"], + ["凯伊姆", "「接下来」"], + ["圣女", "「发生什么事了?看上去似乎很嘈杂」"], + ["随从", "「似乎是某个人逃跑了······具体的我也不是很清楚」"], + ["神官", "「圣女大人,继续待在天台上可能会出事,请您先回到室内吧」"], + ["圣女", "「不用在意我,比起那个,我更关心究竟发生了什么事」"], + ["神官", "「对不起,我真的不知道」"], + ["圣女", "「······是吗」"], + ["男", "「恕我僭越,请准许我说明情况」"], + ["男", "「在来觐见的人群中出现了《羽化病》的患者」"], + ["男", "「围观的人群因而产生了骚动」"], + [ + "男", + "「现在,《防疫局》已经派遣了部队。我想不久之后,他们就会安静下来了」", + ], + ["圣女", "「羽化病······」"], + ["男", "「怎么了?」"], + ["圣女", "「没什么」"], + ["圣女", "「辛苦了,你的名字是?」"], + ["男", "「属下是在防疫局任职的,鲁基乌斯· 迪斯·米利尤」"], + ["神官", "「噢噢,阁下就是鲁基乌斯卿吗,我听说过你的传闻」"], + ["神官", "「阁下是在工作上相当出色的人呢」"], + ["鲁基乌斯", "「不敢当」"], + [ + "鲁基乌斯", + "「话说回来,这次是属下警备工作的失职。让圣女大人见到这不成体统的一面,请您见谅」", + ], + [ + "圣女", + "「即使是目不见物的我,也能感受到聚集于此的民众数量之多。警备工作难以展开也在情理之中」", + ], + ["鲁基乌斯", "「属下不胜惶恐」"], + ["鲁基乌斯", "「接下来属下还要回到工作岗位上,在这里就先告退了」"], + ["圣女", "「鲁基乌斯先生」"], + ["鲁基乌斯", "「属下在」"], + ["圣女", "「你是怎样看待羽狩的工作的呢?」"], + ["神官", "「圣,圣女大人」"], + [ + "鲁基乌斯", + "「防疫局的工作是国王陛下赐予的重要职务。属下非常荣幸能够为这个都市的繁荣尽一份微薄之力」", + ], + ["神官", "「不,不亏是鲁基乌斯卿,相当优秀的想法」"], + ["圣女", "「是吗。辛苦你了」"], + ["随从", "「圣女大人······」"], + ["鲁基乌斯", "「······」"], + ["鲁基乌斯", "「那么,属下就回岗位去了」"], + ]; + this.chapter03 = [ + ["", "从羽化病的少年纷乱的足音中,可以听得出相当的疲劳。"], + ["", "显然,他并没有想到我会捷足先登吧。"], + ["", "少年惶恐地回头看了一眼后,微微露出安心的表情,双手拄在膝盖上。。"], + ["凯伊姆", "「辛苦你了」"], + ["羽化病患少年", "「稀!?」"], + ["凯伊姆", "「逃到贫民区是个不错的想法」"], + ["羽化病患少年", "「你,你是,羽狩吗?」"], + ["凯伊姆", "「不是」"], + ["羽化病患少年", "「什,什么啊······混蛋,不要吓我啊」"], + ["凯伊姆", "「我对令你受惊这件事致以歉意」"], + ["凯伊姆", "「作为回报,麻烦你把从店里偷的钱交出来吧」"], + ["羽化病患少年", "「钱?你在说什么」"], + ["凯伊姆", "「你要找的腰上的东西,掉在你身后了」"], + ["羽化病患少年", "「哎?」"], + ["羽化病患少年", "「呃呀」"], + ["羽化病患少年", "「你······你这混蛋」"], + ["凯伊姆", "「······」"], + ["凯伊姆", "「把偷的钱交出来」"], + ["羽化病患少年", "「我不知道你在······咕」"], + ["羽化病患少年", "「你,你说是我偷的······有什么证据吗」"], + ["凯伊姆", "「你还挺倔的啊」"], + ["凯伊姆", "「不过,给我听好了」"], + ["凯伊姆", "「你偷的那些钱,是要上缴给《不蚀金锁》的上纳金」"], + ["凯伊姆", "「而且,钱的主人是从前和吉克颇有渊源的女人」"], + ["羽化病患少年", "「吉克?」"], + ["凯伊姆", "「他是《不蚀金锁》的主人,这么说你就明白了吧」"], + ["羽化病患少年", "「哎?哎?怎么会······」"], + ["凯伊姆", "「再问你一遍,钱在哪里?」"], + ["羽化病患少年", "「是,是,是,在我的怀里」"], + ["凯伊姆", "「你没有擅自拿掉一部分吧」"], + ["羽化病患少年", "「是,是的」"], + ["羽化病患少年", "「那,那个,您是《不蚀金锁》的人吗?」"], + ["凯伊姆", "「算是吧」"], + ["羽化病患少年", "「我什么都可以做,请您一定要帮帮我」"], + ["凯伊姆", "「抱歉,我并没有兴趣去帮助他人」"], + ["羽化病患少年", "「我什么······什么,都会做的······」"], + ["羽化病患少年", "「我一直都是生活在下层的」"], + [ + "羽化病患少年", + "「可是,不知何时染上了羽化病······背后长出了翅膀······」", + ], + ["羽化病患少年", "「被寄宿工作的店赶了出来,只得流落到牢狱这里」"], + ["羽化病患少年", "「因为独自实在是饿的不行了,所以才会偷这些钱的」"], + [ + "羽化病患少年", + "「我明明没有做任何坏事······为什么······会遇到这种事······」", + ], + ["凯伊姆", "「谁知道」"], + ["羽化病患少年", "「呜······呜呜······接下来,要对我做什么?」"], + ["凯伊姆", "「我要把你带到组织那里」"], + ["羽化病患少年", "「怎,怎么这样」"], + ["凯伊姆", "「不过,那样做的前提是你不是羽化病人」"], + ["凯伊姆", "「组织也没有笨到把羽化病人招待到家里的程度」"], + ["羽化病患少年", "「那么,是要放我逃走吗?」"], + ["凯伊姆", "「我要让你学到教训」"], + ["凯伊姆", "「如果换做是组织的制裁,至少要有断条胳膊的觉悟」"], + ["凯伊姆", "「你的运气不错」"], + ["羽化病患少年", "「唔······啊,是的······」"], + ["凯伊姆", "「滚」"], + ["羽化病患少年", "「非常感谢」"], + ["羽化病患少年", "「唔啊!?」"], + ["男", "「到这里就结束了,羽化病人」"], + ["男", "「确认他的翅膀」"], + ["", "趁还没有被卷入麻烦的事情之前,赶快离开这里吧"], + ["羽狩的队长", "「那边的那个人」"], + ["凯伊姆", "「······有什么事?」"], + ["羽狩的队长", "「可以稍微让我问几句话吗」"], + ["凯伊姆", "「······」"], + ["凯伊姆", "「啊啊,无妨」"], + ["羽狩的队长", "「感谢您的合作」"], + ["", "队长殷勤地致以谢礼。"], + ["", "而在他的眼前,少年的衣服已经被他的补下们扯破。"], + ["", "在瘦骨嶙峋的裸露后背上,长有纯白的羽翼。"], + ["红发的羽狩", "「副队长,确认翅膀的持有了」"], + ["羽狩的副队长", "「保护他」"], + ["羽化病患少年", "「不要······请原谅,我······」"], + [ + "羽狩的副队长", + "「我们只是要带你去治愈院治疗羽化病,不是什么应该感到害怕的事情」", + ], + ["羽化病患少年", "「可是,可是」"], + ["羽狩的副队长", "「没关系的」"], + ["羽化病患少年", "「······哥,哥哥」"], + ["羽狩的副队长", "「你是羽化病人的亲属吗?」"], + ["凯伊姆", "「只是路人而已」"], + ["凯伊姆", "「顺带一提,我也没有打算找你们的麻烦」"], + [ + "羽狩的副队长", + "「前几天,有个和你说了同样的话的人,在我们背向他的瞬间对我们发动了袭击」", + ], + ["羽狩的副队长", "「我的一个部下就此永久失去了半截胳膊」"], + ["凯伊姆", "「我表示同情」"], + ["凯伊姆", "「我马上就会消失的,这样就没问题了吧?」"], + ["羽狩的副队长", "「嘛,不要这么慌张」"], + ["", "副队长看着羽化的少年。"], + ["羽狩的副队长", "「你与这个人是什么关系?没有被他殴打吗?」"], + ["羽化病患少年", "「没,没有」"], + [ + "羽狩的副队长", + "「如何对我们保持合作,你就可以在治愈院得到优先的治疗」", + ], + ["羽化病患少年", "「······」"], + ["羽化病患少年", "「那个人,是《不蚀金锁》的组织成员······」"], + ["羽化病患少年", "「突然说让我拿出钱来,我刚一拒绝他就打我」"], + ["羽狩的副队长", "「原来如此······」"], + [ + "羽狩的副队长", + "「那位少年说你是《不蚀金锁》的一员,不知此事是否属实?」", + ], + ["凯伊姆", "「当然不是」"], + ["凯伊姆", "「我只是从那里接受工作而已,并不是他们的成员」"], + ["羽狩的副队长", "「你的意思是说,少年在说谎吗?」"], + ["凯伊姆", "「啊啊」"], + [ + "凯伊姆", + "「如果你们和组织有关系的话,只要问问我是不是那里的成员就能明白事实了吧」", + ], + ["羽狩的副队长", "「就算我去询问,也无法从他们那里得到事实」"], + [ + "羽狩的副队长", + "「《不蚀金锁》的那些人一向都不对我们合作,我对此深感困扰」", + ], + ["凯伊姆", "「真是辛苦啊」"], + ["羽狩的副队长", "「说的是啊」"], + ["羽狩的副队长", "「其实,砍下我部下胳膊的似乎也是组织的成员呢」"], + [ + "羽狩的副队长", + "「无需如此警戒,我只是想在看守所向你咨询一些事情而已」", + ], + [ + "羽狩的副队长", + "「如果能够知晓牢狱与组织的事情,我们也可以尽可能地对更多的羽化病人进行保护」", + ], + ["羽狩的副队长", "「那和整条街道的和平也是紧密相关的吧?」"], + ["凯伊姆", "「我知道,你们有逮捕干扰狩猎羽化病人的权力」"], + [ + "凯伊姆", + "「但是,我没有对你们做出任何干扰,为什么要对我如此纠缠不休呢」", + ], + ["羽狩的副队长", "「那些话,我们会在看守所对你详细说明的」"], + ["凯伊姆", "「······」"], + ["", "在这里起争执的话,就会被羽狩加害。"], + ["", "就算能从这里脱身,今后只要碰面就会产生纠纷也是摆明的事情。"], + ["", "就算逃跑,也没有好的结果。"], + ["", "正当我想要打圆场的时候,刚才的气氛一瞬间产生了转变。"], + ["", "发生了什么事······"], + ["???", "「我认为,那位先生是正确的」"], + ["", "羽狩们一起回头。"], + ["", "而在他们视线的焦点处,"], + ["", "伫立着一位女性。"], + ["", "在端正的容颜下,代表着强烈意志的双眉十分显眼。"], + ["", "身体的柔软与紧紧包裹在其身上的羽狩制服,两者显得十分的不搭配。"], + ["", "我还是第一次看到女性的羽狩。"], + ["羽狩的副队长", "「队长,这是获得《不蚀金锁》情报的好机会」"], + [ + "羽狩的队长", + "「兰格副队长,就算是为了获得情报,也不能做出恫吓的发言啊」", + ], + ["兰格副队长", "「我并没有打算去恫吓他······」"], + ["羽狩的队长", "「告诉我那个被砍掉胳膊的队员的名字」"], + ["羽狩的队长", "「我会去探望他的」"], + ["兰格副队长", "「那个是······」"], + ["羽狩的队长", "「我知道,你一直在为有所收获而努力工作」"], + ["羽狩的队长", "「但是,正因为我们的工作是为民众提供帮助」"], + ["羽狩的队长", "「所以就更不能损害人与人之间的信赖」"], + ["兰格副队长", "「我会铭记在心」"], + ["羽狩的队长", "「这位先生,我的部下失礼了」"], + ["凯伊姆", "「只要不对我再来一次就好」"], + ["羽狩的队长", "「请稍等」"], + ["凯伊姆", "「有什么事?」"], + ["羽狩的队长", "「我想确认一件事」"], + ["羽狩的队长", "「你真的不是《不蚀金锁》的成员吗?」"], + ["凯伊姆", "「真的」"], + ["凯伊姆", "「如果我说是的话,你有什么打算?」"], + ["羽狩的队长", "「我听过传闻,说他们是用依靠暴力而得的钱在生活」"], + ["凯伊姆", "「······这样啊」"], + ["凯伊姆", "「如果能有收获就好了啊」"], + ]; + this.chapter04 = [ + ["梅尔特", "「欢迎光临」"], + ["梅尔特", "「辛苦了」"], + ["梅尔特", "「抱歉,又拜托给你了个这么麻烦的工作」"], + ["凯伊姆", "「没什么,比想象中完成的更容易」"], + ["梅尔特", "「那就好」"], + ["梅尔特", "「这是我的一点谢意」"], + ["凯伊姆", "「味道有些变化啊」"], + ["梅尔特", "「啊,被发现了?」"], + ["梅尔特", "「最近,没能到手什么好的原料呢」"], + ["凯伊姆", "「去拜托吉克如何?」"], + [ + "梅尔特", + "「话是这么说,但是总不能用店里采购的这种小事去麻烦他吧······」", + ], + ["凯伊姆", "「那希望你也不要来麻烦我」"], + ["梅尔特", "「那 是 两 码 事」"], + ["梅尔特", "「再说,凯伊姆是靠着工作来生活的吧」"], + [ + "梅尔特", + "「而且,自己的钱被偷了这么害羞的事,向凯伊姆意外的其他人都说不出口」", + ], + ["凯伊姆", "「反正,也已经传到吉克的耳朵里了」"], + ["梅尔特", "「这是面子问题啊,面子问题」"], + ["凯伊姆", "「嘛,算了」"], + ["凯伊姆", "「这样就好了吧?」"], + ["梅尔特", "「这是钱包呢」"], + ["梅尔特", "「嗯,东西没少」"], + ["梅尔特", "「太好啦—这个月的上纳金,我可全部都放在里面了呢」"], + ["梅尔特", "「如果没有找到的话,说不定就又会被送到娼馆里了呢」"], + ["凯伊姆", "「在那边不是来钱更快吗?」"], + ["梅尔特", "「阿拉,你是在说我还能有魅力吗?」"], + ["凯伊姆", "「这是客套话而已」"], + ["梅尔特", "「欺负人」"], + ["梅尔特", "「总而言之,今天帮大忙了」"], + ["梅尔特", "「谢礼嘛······」"], + ["凯伊姆", "「就记在账单上吧」"], + ["梅尔特", "「了解—盛谢惠顾了哦?」"], + ["", "喀啷喀啷"], + ["", "门铃响起"], + ["", "喧哗瞬间安静下来。"], + ["", "进来的人是吉克。"], + ["", "是掌控着牢狱的组织之一,《不蚀金锁》的头目。"], + ["", "不仅组织的成员,就连店内一般的客人也对他以注目礼表示敬意。"], + ["吉克", "「各位继续吧」"], + ["", "仿佛停滞的时钟重新转动了一般,店内恢复了热闹的气氛。"], + ["吉克", "「抱歉,今天拜托你去做了无聊的工作」。"], + ["凯伊姆", "「不用介意」"], + ["", "吉克轻轻点了点头,在我右边坐了下来"], + ["凯伊姆", "「逃跑的男人怎么样了?」"], + ["吉克", "「嗯?已经不在这个世上了」。"], + ["吉克", "「有什么想要知道的事吗?」"], + ["凯伊姆", "「不,没什么」"], + ["吉克", "「那个无聊的家伙,完全没有趣味呢」"], + ["吉克", "「真希望他也替我负责清扫的部下也考虑考虑」"], + ["凯伊姆", "「真是灾难啊」"], + ["吉克", "「比起那个,我听说了哦。你去追羽化病人了啊」"], + ["凯伊姆", "「消息真灵通」"], + ["吉克", "「梅尔特也注意点」"], + ["吉克", "「你丢钱已经不是一回两回了」"], + ["梅尔特", "「好的—我会注意的。」"], + ["梅尔特", "「吉克还是平常的点单吧」"], + ["梅尔特", "「凯伊姆要再来一杯吗?」"], + ["", "我们用眼神点头示意后,梅尔特开始准备起酒来。"], + ["", "悠然地吐出眼圈后,吉克取出一个纸包放在柜台上。"], + ["吉克", "「这是抓捕逃跑男人的报酬」"], + ["凯伊姆", "「下次有什么事再告诉我」"], + ["梅尔特", "「来,久等了」"], + ["凯伊姆", "「话说回来梅尔特,为什么会被那种孩子偷到钱?」"], + ["吉克", "「让我猜猜看」"], + ["吉克", "「是那个吧,看某个特立独行的男人入迷了,所以就有了空隙?」"], + ["梅尔特", "「可惜—」"], + ["梅尔特", "「事实恰恰相反,是那家伙一直在纠缠我」"], + ["凯伊姆", "「完全把你当成新进的女佣了么」"], + ["梅尔特", "「我从前可是很有名的,不会被当成这种下人吧」"], + ["梅尔特", "「······而且,我没法对对我这么钟情的人发火啊」"], + ["凯伊姆&吉克", "「你傻啊」"], + ["梅尔特", "「异口同声呢,不亏是兄弟」"], + ["凯伊姆", "「别用这种称呼,怪恶心的」"], + ["吉克", "「说得没错」"], + ["吉克", "「······说起来······」"], + ["梅尔特", "「怎么了?」"], + ["吉克", "「有件事我一直很在意,我和凯伊姆,哪个是哥哥啊?」"], + ["凯伊姆", "「你也说这么无聊的话题」"], + ["吉克", "「不,这是很重要的事情」"], + ["吉克", "「梅尔特,事实是怎么样的?」"], + ["梅尔特", "「啊~是怎么样的呢~」"], + ["梅尔特", "「我忘记了」"], + ["吉克", "「骗人」"], + ]; + } } \ No newline at end of file diff --git a/styles.css b/styles.css index e7ba189..e300825 100644 --- a/styles.css +++ b/styles.css @@ -544,9 +544,9 @@ p#name { } /* 注释下面这三行以开启抗锯齿 */ -.anti-aliasing { +/* .anti-aliasing { image-rendering: pixelated; -} +} */ .warning { transition: left cubic-bezier(0, 0.9, 1, 0.1) 2.5s;