From f18e17e1023612d1b4ce1eb6818d73d516ced5d2 Mon Sep 17 00:00:00 2001 From: Rintim Date: Sun, 12 May 2024 02:12:39 +0800 Subject: [PATCH 1/8] refactor: make `Get#dataUrlAsync` return URL. --- noname/get/index.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/noname/get/index.js b/noname/get/index.js index f6120ac42..2fde9c89b 100644 --- a/noname/get/index.js +++ b/noname/get/index.js @@ -216,7 +216,7 @@ export class Get { //装备栏 END /** * @param {string} chinese - * @param {boolean|undefined} withTone + * @param {boolean|undefined} withTone * @returns { any[] } */ pinyin(chinese, withTone) { @@ -224,10 +224,9 @@ export class Get { const pinyins = lib.pinyins; if (pinyins && pinyins[chinese] && Array.isArray(pinyins[chinese])) { result = pinyins[chinese].slice(0); - } - else { + } else { //@ts-ignore - result = pinyinPro.pinyin(chinese, {type: "array"}); + result = pinyinPro.pinyin(chinese, { type: "array" }); } //@ts-ignore if (withTone === false) result = pinyinPro.convert(result, { format: "toneNone" }); @@ -295,7 +294,7 @@ export class Get { */ yunjiao(str) { //@ts-ignore - str = pinyinPro.convert(str, { format: "toneNone" }) + str = pinyinPro.convert(str, { format: "toneNone" }); if (lib.pinyins._metadata.zhengtirendu.includes(str)) { str = "-" + str[str.length - 1]; } else { @@ -4601,7 +4600,8 @@ export class Get { ) temp2 = cache.delegate(temp2.effect).target(card, player, target, result2, isLink); else temp2 = undefined; - } else if (typeof temp2.effect == "function") { //考虑废弃 + } else if (typeof temp2.effect == "function") { + //考虑废弃 console.log("此写法使用频率极低且影响代码可读性,不建议使用"); if ( !player.hasSkillTag("ignoreSkill", true, { @@ -4832,7 +4832,7 @@ export class Get { * * @async * @param {Blob} blob - 需要转换的内容 - * @returns {Promise} 对应Blob内容的 + * @returns {Promise} 对应Blob内容的 * * @example * let text = "Hello, World!"; @@ -4840,7 +4840,7 @@ export class Get { * * let blob = new Blob([text], { type: "text/plain" }); * let url = await get.dataUrlAsync(blob); - * console.assert("data:text/plain;base64,SGVsbG8sIFdvcmxkIQ=="); + * console.assert(url.href === "data:text/plain;base64,SGVsbG8sIFdvcmxkIQ=="); */ dataUrlAsync(blob) { return new Promise((resolve, reject) => { @@ -4848,7 +4848,7 @@ export class Get { fileReader.onload = resolve; fileReader.onerror = reject; fileReader.readAsDataURL(blob); - }).then(event => event.target.result); + }).then(event => new URL(event.target.result)); } /** From 112f81467af50d5120c1eefa6f777bd7c6e28d88 Mon Sep 17 00:00:00 2001 From: Rintim Date: Sun, 12 May 2024 02:25:11 +0800 Subject: [PATCH 2/8] refactor: rename and chaos it. --- noname/get/index.js | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/noname/get/index.js b/noname/get/index.js index 2fde9c89b..c3d24680c 100644 --- a/noname/get/index.js +++ b/noname/get/index.js @@ -4852,7 +4852,9 @@ export class Get { } /** - * 通过`fetch`读取data URL的内容,转换成Blob后返回生成的blob URL + * 通过`Get#blobFromUrl`读取data URL的内容,转换成Blob后返回生成的blob URL + * + * > 实际上所有的URL都能通过此方法读取 * * 该方法具有缓存,同一data URL仅会返回同一blob URL * @@ -4866,16 +4868,30 @@ export class Get { * @param {string | URL} dataUrl - 需要转换的data URL * @returns {Promise} */ - async objectURLAsync(dataUrl) { + async objectUrlAsync(dataUrl) { let dataString = dataUrl instanceof URL ? dataUrl.href : dataUrl; const objectURLMap = lib.objectURL; if (objectURLMap.has(dataString)) return new URL(objectURLMap.get(dataString)); - let blob = await (await fetch(dataUrl)).blob(); + let blob = await this.blobFromUrl(dataUrl); const objectURL = URL.createObjectURL(blob); objectURLMap.set(dataString, objectURL); return new URL(objectURL); } + + /** + * 读取给定的URL,将其中的内容转换成Blob + * + * 在File协议下通过无名杀自带的文件处理函数读取内容,其他协议通过`fetch`读取内容 + * + * @async + * @param {string | URL} url - 需要读取的URL + * @returns {Promise} + */ + blobFromUrl(url) { + let link = url instanceof URL ? url : new URL(url); + return link.protocol == "file:" ? game.promises.readFile(get.relativePath(link)).then(buffer => new Blob([buffer])) : fetch(link).then(response => response.blob()); + } } export let get = new Get(); From 2ca737cead8b7544864712a6fd792670da800cca Mon Sep 17 00:00:00 2001 From: Rintim Date: Sun, 12 May 2024 02:28:49 +0800 Subject: [PATCH 3/8] refactor: change parseResourceAddress logic. --- noname/library/init/index.js | 47 +++++++++++++++--------------------- 1 file changed, 20 insertions(+), 27 deletions(-) diff --git a/noname/library/init/index.js b/noname/library/init/index.js index be1233119..704ba8831 100644 --- a/noname/library/init/index.js +++ b/noname/library/init/index.js @@ -838,18 +838,24 @@ export class LibInit { } /** - * @async * @param {string | URL} link - 需要解析的路径 * @param {((item: string) => string) | null} [defaultHandle] - 在给定路径不符合可用情况(或基于无名杀相关默认情况)时,处理路径的函数,返回的路径应是相对于根目录的相对路径,默认为`null`,当且仅当无法解析成`URL`时会调用该回调 - * @param {boolean} [forceLoadAsDataUrl] - 是否将资源加载为[Data URL](https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Basics_of_HTTP/Data_URLs),默认为`false` + * @param {((item: URL) => unknown) | null} [loadAsDataUrlCallback] - 若存在值,则将资源加载为[Data URL](https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Basics_of_HTTP/Data_URLs),然后传入进回调函数 * @param {boolean} [dbNow] - 此刻是否在解析数据库中的内容,请勿直接使用 - * @returns {Promise} + * @returns {URL} */ - async parseResourceAddress(link, defaultHandle = null, forceLoadAsDataUrl = false, dbNow = false) { + parseResourceAddress(link, defaultHandle = null, loadAsDataUrlCallback = null, dbNow = false) { + // 适当的摆了,中文错误应该没人会反对 + if (!link) throw new Error(dbNow ? "传入的数据库链接中不存在内容" : "请传入需要解析的链接"); + let linkString = link instanceof URL ? link.href : link; // 如果传入值为Data URL,经过分析可知无需处理,故直接返回成品URL - if (linkString.startsWith("data:")) return new URL(linkString); + if (linkString.startsWith("data:")) { + let result = new URL(linkString); + if (loadAsDataUrlCallback) loadAsDataUrlCallback(result); + return result; + } /** * @type {URL} @@ -862,37 +868,24 @@ export class LibInit { resultUrl = new URL(linkString); } else if (dbNow) { let content = new Blob([linkString], { type: "text/plain" }); - resultUrl = new URL(await get.dataUrlAsync(content)); + get.dataUrlAsync(content).then(loadAsDataUrlCallback); + // @ts-expect-error 此处的返回值无任何用处 + return; } else { let resultLink = defaultHandle == null ? linkString : defaultHandle(linkString); resultUrl = new URL(resultLink, rootURL); } - if (forceLoadAsDataUrl && !resultUrl.href.startsWith("data:")) { - if (linkString.startsWith("db:")) { - /** - * @type {string} - */ - let storeResult = await game.getDB("image", linkString.slice(3)); - + if (loadAsDataUrlCallback != null) { + if (resultUrl.protocol == "db:") { // 我思索了一下,如果这玩意能造成无限递归 // 那么我只能说,你赢了 - return this.parseResourceAddress(storeResult, defaultHandle, forceLoadAsDataUrl, true); - } - /** - * @type {Blob} - */ - let blob; - - if (linkString.startsWith("file:")) { - let buffer = await game.promises.readFile(get.relativePath(resultUrl)); - blob = new Blob([buffer]); + game.getDB("image", linkString.slice(3)).then(storeResult => this.parseResourceAddress(storeResult, defaultHandle, loadAsDataUrlCallback, true)); } else { - let response = await fetch(resultUrl.href); - blob = await response.blob(); + get.blobFromUrl(resultUrl) + .then(blob => get.dataUrlAsync(blob)) + .then(loadAsDataUrlCallback); } - - resultUrl.href = await get.dataUrlAsync(blob); } return resultUrl; From e19eb4693ea25c28f21f85a85dc99580657b1d8d Mon Sep 17 00:00:00 2001 From: Rintim Date: Sun, 12 May 2024 02:31:40 +0800 Subject: [PATCH 4/8] refactor: fix promises-version. --- noname/library/init/promises.js | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/noname/library/init/promises.js b/noname/library/init/promises.js index 0b50e1d3e..4597a0b8b 100644 --- a/noname/library/init/promises.js +++ b/noname/library/init/promises.js @@ -72,6 +72,23 @@ export class LibInitPromises { * @returns {Promise} */ parseResourceAddress(link, defaultHandle = null, forceLoadAsDataUrl = false) { - return lib.init.parseResourceAddress(link, defaultHandle, forceLoadAsDataUrl); + if (!forceLoadAsDataUrl) return Promise.resolve(lib.init.parseResourceAddress(link, defaultHandle)); + let { promise, resolve } = Promise.withResolvers(); + + lib.init.parseResourceAddress(link, defaultHandle, result => resolve(result)); + return promise; + } + + /** + * @async + * @param {string | URL} link - 需要解析的路径 + * @param {((item: string) => string) | null} [defaultHandle] - 在给定路径不符合可用情况(或基于无名杀相关默认情况)时,处理路径的函数,返回的路径应是相对于根目录的相对路径,默认为`null`,当且仅当无法解析成`URL`时会调用该回调 + * @returns {Promise<[origin: URL, data: URL]>} + */ + async parseResourceAddressExt(link, defaultHandle = null) { + let { promise, resolve } = Promise.withResolvers(); + + let origin = lib.init.parseResourceAddress(link, defaultHandle, result => resolve(result)); + return [origin, await promise]; } } From 215ead7e7baca71573a07dff2eae2d4e54d82768 Mon Sep 17 00:00:00 2001 From: Rintim Date: Sun, 12 May 2024 19:14:25 +0800 Subject: [PATCH 5/8] feat: add `Game#parseResourcePath` && replace `Game#playAudio` for test. --- noname/game/index.js | 110 +++++++++++++++++++++++-------------------- 1 file changed, 59 insertions(+), 51 deletions(-) diff --git a/noname/game/index.js b/noname/game/index.js index 4ed353e5f..c2ebc9537 100644 --- a/noname/game/index.js +++ b/noname/game/index.js @@ -1381,9 +1381,6 @@ export class Game { } else if (typeof argument == "function") onError = argument; if (_status.video) break; } - if (path.startsWith("ext:")) path = path.replace(/^ext:/, "extension/"); - else if (!["db:", "blob:", "data:"].some(prefix => path.startsWith(prefix))) path = `audio/${path}`; - if (!lib.config.repeat_audio && _status.skillaudio.includes(path)) return; } const audio = document.createElement("audio"); audio.autoplay = true; @@ -1400,13 +1397,10 @@ export class Game { audio.remove(); if (onError) onError(event); }; - new Promise((resolve, reject) => { - if (path.startsWith("db:")) game.getDB("image", path.slice(3)).then(octetStream => resolve(get.objectURL(octetStream)), reject); - else if (lib.path.extname(path)) resolve(`${lib.assetURL}${path}`); - else if (URL.canParse(path)) resolve(path); - else resolve(`${lib.assetURL}${path}.mp3`); - }).then(resolvedPath => { - audio.src = resolvedPath; + + game.parseResourcePath(path, path => (lib.path.extname(path) ? `audio/${path}` : `audio/${path}.mp3`)).then(path => { + audio.src = path.href; + if (!lib.config.repeat_audio && _status.skillaudio.includes(path.href)) return; ui.window.appendChild(audio); }); return audio; @@ -1461,7 +1455,10 @@ export class Game { * @returns { string[] } 语音地址列表 */ parseSkillText(skill, player, skillInfo) { - return game.parseSkillTextMap(skill, player, skillInfo).map(data => data.text).filter(Boolean); + return game + .parseSkillTextMap(skill, player, skillInfo) + .map(data => data.text) + .filter(Boolean); } /** * 根据skill中的audio,audioname,audioname2和player来获取技能台词列表及其对应的源文件名 @@ -1487,12 +1484,15 @@ export class Game { const getName = filter => { const name = (player.tempname || []).find(i => filter(i)); if (name) return name; - return [player.name, player.name1, player.name2].reduce((result, name) => { - if (result) return result; - if (!name) return result; - if (filter(name)) return name; - return get.character(name).tempname.find(i => filter(i)) || result; - }, void 0); + return [player.name, player.name1, player.name2].reduce( + (result, name) => { + if (result) return result; + if (!name) return result; + if (filter(name)) return name; + return get.character(name).tempname.find(i => filter(i)) || result; + }, + void 0 + ); }; const getTextMap = (path, name, ext) => ({ @@ -1529,8 +1529,8 @@ export class Game { } const map = {}; - audioInfo.forEach((i) => { - parseAudio(skill, options, i).forEach(data => map[data.name] = data); + audioInfo.forEach(i => { + parseAudio(skill, options, i).forEach(data => (map[data.name] = data)); }); return Object.values(map); } @@ -1539,12 +1539,13 @@ export class Game { if (audioInfo === false) return []; if (typeof audioInfo === "string") { if (["data:", "blob:"].some(prefix => audioInfo.startsWith(prefix))) return [getTextMap("", audioInfo, "")]; - if(checkSkill(audioInfo, history)) return getAudioList(audioInfo, options); + if (checkSkill(audioInfo, history)) return getAudioList(audioInfo, options); } audioInfo = String(audioInfo); const list = audioInfo.match(/(?:(.*):|^)(true|\d+)(?::(.*)|$)/); // [path, number|true, ext] if (!list) { - let path = "", ext = ""; + let path = "", + ext = ""; if (!/^db:|^ext:|\//.test(audioInfo)) path = "skill/"; if (!/\.\w+$/.test(audioInfo)) ext = ".mp3"; if (path && ext) return parseAudio(audioInfo, options, [true, 2]); @@ -1574,51 +1575,49 @@ export class Game { * @param { string | Player } player 角色名 * @returns { any[] } 语音地址列表 */ - parseDieTextMap(player){ + parseDieTextMap(player) { let name, rawName; if (typeof player === "string") { name = player; rawName = name; - } - else if (get.itemtype(player) === "player") { + } else if (get.itemtype(player) === "player") { // @ts-ignore name = player.skin.name || player.name; rawName = player.name; } - const info = get.character(name), datas = []; + const info = get.character(name), + datas = []; let dieAudios; - if(info && info.dieAudios.length > 0){ + if (info && info.dieAudios.length > 0) { dieAudios = info.dieAudios; } //@mengxinzxz写的屎山 - else if(rawName !== name && lib.characterSubstitute[rawName] && lib.characterSubstitute[rawName].some((i) => i[0] == name)){ - const trashes = lib.characterSubstitute[rawName].find((i) => i[0] == name)[1]; - const newCharacter = get.convertedCharacter(['','',0,[],trashes]); + else if (rawName !== name && lib.characterSubstitute[rawName] && lib.characterSubstitute[rawName].some(i => i[0] == name)) { + const trashes = lib.characterSubstitute[rawName].find(i => i[0] == name)[1]; + const newCharacter = get.convertedCharacter(["", "", 0, [], trashes]); dieAudios = newCharacter.dieAudios; } - if(dieAudios && dieAudios.length > 0){ + if (dieAudios && dieAudios.length > 0) { dieAudios.forEach(item => { let key, file; - if(item.startsWith("ext:")){ + if (item.startsWith("ext:")) { key = item.slice(4).split("/")[1]; file = item; - } - else { + } else { key = item; file = `die/${item}.mp3`; } - const data = {key, file} - if(lib.translate[`#${key}:die`]) data.text = lib.translate[`#${key}:die`]; + const data = { key, file }; + if (lib.translate[`#${key}:die`]) data.text = lib.translate[`#${key}:die`]; datas.push(data); }); - } - else { + } else { const data = { key: name, file: `die/${name}.mp3`, isDefault: true, - } - if(lib.translate[`#${name}:die`]) data.text = lib.translate[`#${name}:die`]; + }; + if (lib.translate[`#${name}:die`]) data.text = lib.translate[`#${name}:die`]; datas.push(data); } return datas; @@ -3332,8 +3331,7 @@ export class Game { }; // player.removeGaintag.apply(player, content); checkMatch(content[1], player.getCards("h")); - } - else player.removeGaintag(content); + } else player.removeGaintag(content); } else { console.log(player); } @@ -6998,7 +6996,7 @@ export class Game { for (let i = 0; i < event.config.size; i++) { ui.window.appendChild(event.nodes[i]); } - "step 1"; + ("step 1"); let rand1 = event.config.first; if (rand1 == "rand") { rand1 = Math.random() < 0.5; @@ -7035,7 +7033,7 @@ export class Game { } game.delay(); lib.init.onfree(); - "step 2"; + ("step 2"); if (event.checkredo()) return; if (event._skiprest) return; if (event.side < 2) { @@ -7051,7 +7049,7 @@ export class Game { event.aiMove(); game.delay(); } - "step 3"; + ("step 3"); if (typeof event.fast == "number" && get.time() - event.fast <= 1000) { event.fast = true; } else { @@ -7086,7 +7084,7 @@ export class Game { game.delay(); } } - "step 4"; + ("step 4"); if (event.checkredo()) return; if (event.skipnode) event.skipnode.delete(); if (event.replacenode) event.replacenode.delete(); @@ -7105,7 +7103,7 @@ export class Game { } } game.delay(); - "step 5"; + ("step 5"); event.prompt("选择" + get.cnNumber(event.config.num) + "名出场武将"); event.enemylist = []; for (let i = 0; i < event.avatars.length; i++) { @@ -7135,7 +7133,7 @@ export class Game { event.nodes[i].hide(); } game.pause(); - "step 6"; + ("step 6"); event.promptbar.delete(); if (ui.cardPileButton) ui.cardPileButton.style.display = ""; lib.onresize.remove(event.resize); @@ -7842,7 +7840,7 @@ export class Game { game.reload2(); resolve(result); }; - } + } : (resolve, reject) => { lib.status.reload++; const idbRequest = lib.db.transaction([storeName], "readwrite").objectStore(storeName).openCursor(), @@ -7872,7 +7870,7 @@ export class Game { game.reload2(); resolve(object); }; - } + } ); } /** @@ -7926,7 +7924,7 @@ export class Game { game.reload2(); resolve(event); }; - }) + }) : game.getDB(storeName).then(object => { const keys = Object.keys(object); lib.status.reload += keys.length; @@ -7947,7 +7945,7 @@ export class Game { }) ) ); - }); + }); } /** * @param { string } key @@ -8510,6 +8508,16 @@ export class Game { await Promise.resolve(asyncFunc(target, i)); } } + + /** + * @async + * @param {string | URL} link + * @param {((item: string) => string) | null} [defaultHandle] + * @returns {Promise} + */ + parseResourcePath(link, defaultHandle = null) { + return (link instanceof URL ? link.href : link).startsWith("db:") ? lib.init.promises.parseResourceAddress(link, defaultHandle, true) : Promise.resolve(lib.init.parseResourceAddress(link, defaultHandle)); + } } export let game = new Game(); From 37c21700a362f4a6d3a3a78d538f62fa2c3def01 Mon Sep 17 00:00:00 2001 From: Rintim Date: Sun, 12 May 2024 19:57:05 +0800 Subject: [PATCH 6/8] feat: change logic of die text parse. --- noname/game/index.js | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/noname/game/index.js b/noname/game/index.js index c2ebc9537..d454fd2f1 100644 --- a/noname/game/index.js +++ b/noname/game/index.js @@ -1599,14 +1599,16 @@ export class Game { } if (dieAudios && dieAudios.length > 0) { dieAudios.forEach(item => { - let key, file; - if (item.startsWith("ext:")) { - key = item.slice(4).split("/")[1]; - file = item; - } else { - key = item; - file = `die/${item}.mp3`; - } + let file = lib.init.parseResourceAddress(item, path => (lib.path.extname(path) ? `audio/die/${path}.mp3` : `audio/die/${path}`)).href; + let key = file.startsWith("db:") ? file.slice(3) : file.startsWith("data:") ? file : lib.path.basename(file); + + // if (item.startsWith("ext:")) { + // key = item.slice(4).split("/")[1]; + // file = item; + // } else { + // key = item; + // file = `die/${item}.mp3`; + // } const data = { key, file }; if (lib.translate[`#${key}:die`]) data.text = lib.translate[`#${key}:die`]; datas.push(data); From 4cf40dbe6c7609c4d84d778a99a67d93efa26c6f Mon Sep 17 00:00:00 2001 From: Rintim Date: Mon, 13 May 2024 00:40:21 +0800 Subject: [PATCH 7/8] pref: parse ext path. --- noname/game/index.js | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/noname/game/index.js b/noname/game/index.js index d454fd2f1..95bf5bef2 100644 --- a/noname/game/index.js +++ b/noname/game/index.js @@ -1599,8 +1599,21 @@ export class Game { } if (dieAudios && dieAudios.length > 0) { dieAudios.forEach(item => { - let file = lib.init.parseResourceAddress(item, path => (lib.path.extname(path) ? `audio/die/${path}.mp3` : `audio/die/${path}`)).href; - let key = file.startsWith("db:") ? file.slice(3) : file.startsWith("data:") ? file : lib.path.basename(file); + let file = lib.init.parseResourceAddress(item, path => (lib.path.extname(path) ? `audio/die/${path}.mp3` : `audio/die/${path}`)); + //let key = file.startsWith("db:") ? file.slice(3) : file.startsWith("data:") ? file : lib.path.basename(file); + + /** + * @type {string} + */ + let key; + if (item.startsWith("ext:")) { + let relativePath = get.relativePath(file); + let keyPath = relativePath.split("/").slice(2).join("/"); + if (lib.path.extname(keyPath)) keyPath = keyPath.replace(lib.path.extname(keyPath), ""); + key = keyPath; + } else { + key = file.href; + } // if (item.startsWith("ext:")) { // key = item.slice(4).split("/")[1]; @@ -1609,7 +1622,7 @@ export class Game { // key = item; // file = `die/${item}.mp3`; // } - const data = { key, file }; + const data = { key, file: file.href }; if (lib.translate[`#${key}:die`]) data.text = lib.translate[`#${key}:die`]; datas.push(data); }); From 6eecd30aa060a3f15aa335b72c33d05eac056623 Mon Sep 17 00:00:00 2001 From: Rintim Date: Mon, 13 May 2024 02:44:06 +0800 Subject: [PATCH 8/8] pref: use kuangshen04 version. --- noname/game/index.js | 322 ++++++++++++++++++++++++++----------------- 1 file changed, 196 insertions(+), 126 deletions(-) diff --git a/noname/game/index.js b/noname/game/index.js index 95bf5bef2..ed8ac4841 100644 --- a/noname/game/index.js +++ b/noname/game/index.js @@ -1381,6 +1381,9 @@ export class Game { } else if (typeof argument == "function") onError = argument; if (_status.video) break; } + if (path.startsWith("ext:")) path = path.replace(/^ext:/, "extension/"); + else if (!["db:", "blob:", "data:"].some(prefix => path.startsWith(prefix))) path = `audio/${path}`; + if (!lib.config.repeat_audio && _status.skillaudio.includes(path)) return; } const audio = document.createElement("audio"); audio.autoplay = true; @@ -1397,10 +1400,13 @@ export class Game { audio.remove(); if (onError) onError(event); }; - - game.parseResourcePath(path, path => (lib.path.extname(path) ? `audio/${path}` : `audio/${path}.mp3`)).then(path => { - audio.src = path.href; - if (!lib.config.repeat_audio && _status.skillaudio.includes(path.href)) return; + new Promise((resolve, reject) => { + if (path.startsWith("db:")) game.getDB("image", path.slice(3)).then(octetStream => resolve(get.objectURL(octetStream)), reject); + else if (lib.path.extname(path)) resolve(`${lib.assetURL}${path}`); + else if (URL.canParse(path)) resolve(path); + else resolve(`${lib.assetURL}${path}.mp3`); + }).then(resolvedPath => { + audio.src = resolvedPath; ui.window.appendChild(audio); }); return audio; @@ -1468,13 +1474,15 @@ export class Game { * @returns { any[] } 语音地址列表 */ parseSkillTextMap(skill, player, skillInfo) { - if (typeof player === "string") player = { name: player }; - else if (typeof player !== "object" || player === null) player = {}; + if (typeof player === "string") player = get.convertedCharacter({ name: player }); + else if (typeof player !== "object" || player === null) player = get.convertedCharacter({ isNull: true }); if (skillInfo && (typeof skillInfo !== "object" || Array.isArray(skillInfo))) skillInfo = { audio: skillInfo }; - const checkSkill = (skill, history) => { - if (!lib.skill[skill]) return false; + const defaultInfo = [true, 2]; + + const check = (skill, history) => { + if (!get.info(skill)) return false; if (!history.includes(skill)) return true; if (history[0] === skill) return false; //deadlock @@ -1495,17 +1503,18 @@ export class Game { ); }; - const getTextMap = (path, name, ext) => ({ + const getTextMap = (path, name, ext, isDefault) => ({ name, file: `${path}${name}${ext}`, text: lib.translate[`#${name}`], + isDefault, }); - function getAudioList(skill, options, skillInfo) { - const info = skillInfo || lib.skill[skill]; + const getAudioList = (skill, options, skillInfo) => { + const info = skillInfo || get.info(skill); if (!info) { - console.error(new ReferenceError(`parseSkillAudio: Cannot find ${skill} in lib.skill`)); - return parseAudio(skill, options, [true, 2]); + console.error(new ReferenceError(`parseSkillTextMap: Cannot find ${skill} in lib.skill`)); + return parseAudio(skill, Object.assign(options, { isDefault: true }), defaultInfo); } const { audioname, history } = options; @@ -1515,16 +1524,17 @@ export class Game { if (info.audioname2) audioInfo = info.audioname2[getName(i => info.audioname2[i])] || audioInfo; return parseAudio(skill, options, audioInfo); - } + }; - function parseAudio(skill, options, audioInfo) { + const parseAudio = (skill, options, audioInfo) => { const audioname = options.audioname.slice(); const history = options.history.slice(); - options = { audioname, history }; + const isDefault = options.isDefault; + options = { audioname, history, isDefault }; if (Array.isArray(audioInfo)) { if (audioInfo.length === 2 && typeof audioInfo[0] === "string" && typeof audioInfo[1] === "number") { const [name, number] = audioInfo; - if (checkSkill(name, history)) return getAudioList(name, options).slice(0, number); + if (check(name, history)) return getAudioList(name, options).slice(0, number); return parseAudio(name, options, number); } @@ -1535,40 +1545,40 @@ export class Game { return Object.values(map); } - if (!["string", "number", "boolean"].includes(typeof audioInfo)) return parseAudio(skill, options, [true, 2]); + if (!["string", "number", "boolean"].includes(typeof audioInfo)) return parseAudio(skill, Object.assign(options, { isDefault: true }), defaultInfo); if (audioInfo === false) return []; if (typeof audioInfo === "string") { - if (["data:", "blob:"].some(prefix => audioInfo.startsWith(prefix))) return [getTextMap("", audioInfo, "")]; - if (checkSkill(audioInfo, history)) return getAudioList(audioInfo, options); + if (["data:", "blob:"].some(prefix => audioInfo.startsWith(prefix))) return [getTextMap("", audioInfo, "", isDefault)]; + if (check(audioInfo, history)) return getAudioList(audioInfo, options); } audioInfo = String(audioInfo); const list = audioInfo.match(/(?:(.*):|^)(true|\d+)(?::(.*)|$)/); // [path, number|true, ext] - if (!list) { - let path = "", - ext = ""; - if (!/^db:|^ext:|\//.test(audioInfo)) path = "skill/"; - if (!/\.\w+$/.test(audioInfo)) ext = ".mp3"; - if (path && ext) return parseAudio(audioInfo, options, [true, 2]); - //@TODO - console.warn(`${skill}中“${audioInfo}”的地址写法暂时没有完全支持台词系统。`); - return [getTextMap(path, audioInfo, ext)]; + if (list) { + let [, path = "skill", audioNum, ext = "mp3"] = list; + let _audioname = getName(i => audioname.includes(i)); + _audioname = _audioname ? `_${_audioname}` : ""; + + if (audioNum === "true") return [getTextMap(`${path}/`, `${skill}${_audioname}`, `.${ext}`, isDefault)]; + + const audioList = []; + audioNum = parseInt(audioNum); + for (let i = 1; i <= audioNum; i++) { + audioList.push(getTextMap(`${path}/`, `${skill}${_audioname}${i}`, `.${ext}`, isDefault)); + } + return audioList; } - let [, path = "skill", audioNum, ext = "mp3"] = list; - let _audioname = getName(i => audioname.includes(i)); - _audioname = _audioname ? `_${_audioname}` : ""; + let path = "", + ext = ""; + if (!/^db:|^ext:|\//.test(audioInfo)) path = "skill/"; + if (!/\.\w+$/.test(audioInfo)) ext = ".mp3"; + if (path && ext) return parseAudio(audioInfo, Object.assign(options, { isDefault: true }), defaultInfo); + //@TODO + console.warn(`${skill}中的地址写法(${audioInfo})暂时没有完全支持台词系统。`); + return [getTextMap(path, audioInfo, ext, isDefault)]; + }; - if (audioNum === "true") return [getTextMap(`${path}/`, `${skill}${_audioname}`, `.${ext}`)]; - - const audioList = []; - audioNum = parseInt(audioNum); - for (let i = 1; i <= audioNum; i++) { - audioList.push(getTextMap(`${path}/`, `${skill}${_audioname}${i}`, `.${ext}`)); - } - return audioList; - } - - return getAudioList(skill, { audioname: [], history: [] }, skillInfo); + return getAudioList(skill, { audioname: [], history: [], isDefault: false }, skillInfo); } /** * 获取角色死亡时能播放的所有阵亡语音 @@ -1576,66 +1586,105 @@ export class Game { * @returns { any[] } 语音地址列表 */ parseDieTextMap(player) { - let name, rawName; - if (typeof player === "string") { - name = player; - rawName = name; - } else if (get.itemtype(player) === "player") { - // @ts-ignore - name = player.skin.name || player.name; - rawName = player.name; - } - const info = get.character(name), - datas = []; - let dieAudios; - if (info && info.dieAudios.length > 0) { - dieAudios = info.dieAudios; - } - //@mengxinzxz写的屎山 - else if (rawName !== name && lib.characterSubstitute[rawName] && lib.characterSubstitute[rawName].some(i => i[0] == name)) { - const trashes = lib.characterSubstitute[rawName].find(i => i[0] == name)[1]; - const newCharacter = get.convertedCharacter(["", "", 0, [], trashes]); - dieAudios = newCharacter.dieAudios; - } - if (dieAudios && dieAudios.length > 0) { - dieAudios.forEach(item => { - let file = lib.init.parseResourceAddress(item, path => (lib.path.extname(path) ? `audio/die/${path}.mp3` : `audio/die/${path}`)); - //let key = file.startsWith("db:") ? file.slice(3) : file.startsWith("data:") ? file : lib.path.basename(file); - - /** - * @type {string} - */ - let key; - if (item.startsWith("ext:")) { - let relativePath = get.relativePath(file); - let keyPath = relativePath.split("/").slice(2).join("/"); - if (lib.path.extname(keyPath)) keyPath = keyPath.replace(lib.path.extname(keyPath), ""); - key = keyPath; - } else { - key = file.href; + let name = typeof player === "string" ? player : player.name; + let audioInfo; + if (typeof player !== "string" && player.skin && player.skin.name) { + const skinName = player.skin.name; + if (skinName !== name && lib.characterSubstitute[name]) { + const skin = lib.characterSubstitute[name].find(i => i[0] === skinName); + if (skin) { + const newCharacter = get.convertedCharacter(["", "", 0, [], skin[1]]); + name = skinName; + audioInfo = newCharacter.dieAudios; } - - // if (item.startsWith("ext:")) { - // key = item.slice(4).split("/")[1]; - // file = item; - // } else { - // key = item; - // file = `die/${item}.mp3`; - // } - const data = { key, file: file.href }; - if (lib.translate[`#${key}:die`]) data.text = lib.translate[`#${key}:die`]; - datas.push(data); - }); - } else { - const data = { - key: name, - file: `die/${name}.mp3`, - isDefault: true, - }; - if (lib.translate[`#${name}:die`]) data.text = lib.translate[`#${name}:die`]; - datas.push(data); + } } - return datas; + + const defaultInfo = true; + + const check = (name, history) => { + if (get.character(name).isNull) return false; + if (!history.includes(name)) return true; + if (history[0] === name) return false; + //deadlock + throw new RangeError(`parseDieTextMap: ${name} in ${history} forms a deadlock`); + }; + + const getTextMap = (path, name, ext, isDefault) => ({ + name, + file: `${path}${name}${ext}`, + text: lib.translate[`#${name}:die`], + isDefault, + }); + + const getAudioList = (name, options, audioInfo) => { + if (!audioInfo) { + const info = get.character(name); + if (info.isNull) { + // console.error(new ReferenceError(`parseDieTextMap: Cannot find ${name} in lib.character`)); + return parseAudio(name, Object.assign(options, { isDefault: true }), defaultInfo); + } + audioInfo = info.dieAudios; + } + + if (audioInfo.length === 0) audioInfo = void 0; + + const { history } = options; + history.unshift(name); + + return parseAudio(name, options, audioInfo); + }; + + const parseAudio = (name, options, audioInfo) => { + const history = options.history.slice(); + const isDefault = options.isDefault; + options = { history, isDefault }; + if (Array.isArray(audioInfo)) { + // if (audioInfo.length === 2 && typeof audioInfo[0] === "string" && typeof audioInfo[1] === "number") { + // const [name, number] = audioInfo; + // if (check(name, history)) return getAudioList(name, options).slice(0, number); + // return parseAudio(name, options, number); + // } + + const map = {}; + audioInfo.forEach(i => { + parseAudio(name, options, i).forEach(data => (map[data.name] = data)); + }); + return Object.values(map); + } + + if (!["string", "number", "boolean"].includes(typeof audioInfo)) return parseAudio(name, Object.assign(options, { isDefault: true }), defaultInfo); + if (audioInfo === false) return []; + if (typeof audioInfo === "string") { + if (["data:", "blob:"].some(prefix => audioInfo.startsWith(prefix))) return [getTextMap("", audioInfo, "", isDefault)]; + if (check(audioInfo, history)) return getAudioList(audioInfo, options); + } + audioInfo = String(audioInfo); + const list = audioInfo.match(/(?:(.*):|^)(true|\d+)(?::(.*)|$)/); // [path, number|true, ext] + if (list) { + let [, path = "die", audioNum, ext = "mp3"] = list; + + if (audioNum === "true") return [getTextMap(`${path}/`, `${name}`, `.${ext}`, isDefault)]; + + const audioList = []; + audioNum = parseInt(audioNum); + for (let i = 1; i <= audioNum; i++) { + audioList.push(getTextMap(`${path}/`, `${name}${i}`, `.${ext}`, isDefault)); + } + return audioList; + } + + let path = "", + ext = ""; + if (!/^db:|^ext:|\//.test(audioInfo)) path = "die/"; + if (!/\.\w+$/.test(audioInfo)) ext = ".mp3"; + if (path && ext) return parseAudio(audioInfo, Object.assign(options, { isDefault: true }), defaultInfo); + //@TODO + console.warn(`${name}中的地址写法(${audioInfo})暂时没有完全支持台词系统。`); + return [getTextMap(path, audioInfo, ext, isDefault)]; + }; + + return getAudioList(name, { history: [], isDefault: false }, audioInfo); } /** * @@ -1655,11 +1704,42 @@ export class Game { if (lib.skill.global.includes(skill) && !info.forceaudio) return; let audio, - list = game.parseSkillAudio(skill, player, skillInfo).randomSort(); + list = game.parseSkillTextMap(skill, player, skillInfo).randomSort(); return (function play() { if (!list.length) return; audio = list.shift(); - return game.playAudio(audio, play); + return game.playAudio(audio.file, play); + })(); + } + /** + * @param { Player | string } player + * @returns + */ + tryDieAudio(player) { + game.broadcast(game.tryDieAudio, player); + if (!lib.config.background_speak) return; + + let playerName; + if (typeof player === "string") playerName = player; + else if (player.skin && player.skin.name) playerName = player.skin.name; + else playerName = player.name; + + let audio, + isDefault, + list = game.parseDieTextMap(player).randomSort(); + const check = () => { + if (list.length) return true; + if (!audio) return false; + if (!audio.isDefault) return false; + if (!playerName.includes("_")) return false; + playerName = playerName.slice(playerName.indexOf("_") + 1); + list = game.parseDieTextMap(playerName).randomSort(); + return check(); + }; + return (function play() { + if (!check()) return; + audio = list.shift(); + return game.playAudio(audio.file, play); })(); } /** @@ -5835,19 +5915,19 @@ export class Game { // 数组形式 if ("contents" in event && Array.isArray(event.contents)) { /* - event.contents[step](event, trigger, player, _storeEvent).then((evt) => { - if (evt) event._storeEvent = evt; - if (game.executingAsyncEventMap.has(event.toEvent())) { - game.executingAsyncEventMap.set(_status.event.toEvent(), game.executingAsyncEventMap.get(_status.event.toEvent()).then(() => { - if (event.step >= event.contents.length - 1) event.finish(); - resolve(); - })); - } else { - if (event.step >= event.contents.length - 1) event.finish(); - resolve(); - } - }); - */ + event.contents[step](event, trigger, player, _storeEvent).then((evt) => { + if (evt) event._storeEvent = evt; + if (game.executingAsyncEventMap.has(event.toEvent())) { + game.executingAsyncEventMap.set(_status.event.toEvent(), game.executingAsyncEventMap.get(_status.event.toEvent()).then(() => { + if (event.step >= event.contents.length - 1) event.finish(); + resolve(); + })); + } else { + if (event.step >= event.contents.length - 1) event.finish(); + resolve(); + } + }); + */ // 解决不了问题...就把问题统一 const run = async event => { if (typeof event.step !== "number") event.step = 0; @@ -8523,16 +8603,6 @@ export class Game { await Promise.resolve(asyncFunc(target, i)); } } - - /** - * @async - * @param {string | URL} link - * @param {((item: string) => string) | null} [defaultHandle] - * @returns {Promise} - */ - parseResourcePath(link, defaultHandle = null) { - return (link instanceof URL ? link.href : link).startsWith("db:") ? lib.init.promises.parseResourceAddress(link, defaultHandle, true) : Promise.resolve(lib.init.parseResourceAddress(link, defaultHandle)); - } } export let game = new Game();