From 1330c13492b5fef57f485405c95365e61704c0ca Mon Sep 17 00:00:00 2001 From: kuangshen04 <2832899707@qq.com> Date: Mon, 20 May 2024 10:15:04 +0800 Subject: [PATCH 1/6] =?UTF-8?q?=E4=BD=BF=E7=94=A8get.Audio=E6=95=B4?= =?UTF-8?q?=E5=90=88parse=20audio=20api?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- noname/game/index.js | 254 +++---------------------------------------- noname/get/audio.js | 253 ++++++++++++++++++++++++++++++++++++++++++ noname/get/index.js | 2 + 3 files changed, 269 insertions(+), 240 deletions(-) create mode 100644 noname/get/audio.js diff --git a/noname/game/index.js b/noname/game/index.js index ee1a64f7f..be619da39 100644 --- a/noname/game/index.js +++ b/noname/game/index.js @@ -1417,271 +1417,45 @@ export class Game { * @typedef {{audio: audioInfo, audioname?:string[], audioname2?:{[playerName: string]: audioInfo}}} skillInfo * @param { string } skill 技能名 * @param { Player | Object | string } [player] 角色/角色名 - * @param { skillInfo | audioInfo } [skillInfo] 预设的skillInfo/audioInfo(转为skillInfo),覆盖lib.skill[skill] - * @param { boolean | undefined } [useRawAudio] + * @param { skillInfo | audioInfo } [skillInfo] 使用指定的skillInfo/audioInfo * @returns { string[] } 语音地址列表 - * @example - * ```js - * const info=lib.skill['skillname']; - * info.audio=undefined //默认值[true,2] - * info.audio=false // 不播放语音 - * info.audio=true // [skill/skillname.mp3] - * info.audio=3 // [skill/skillname1.mp3,skill/skillname2.mp3,skill/skillname3.mp3](项数为数字大小) - * info.audio="(ext:extName|db:extension-extName)(/anyPath):true|number(:format)" //间接路径 - * // 同上,只是将目录改为(ext:extName|db:extension-extName)(/anyPath),且可以指定格式(默认mp3) - * info.audio="(ext:extName|db:extension-extName/)(anyPath/)filename(.format)" //直接路径 - * //path和format至少有一个,否则会识别为引用技能 - * //起始位置为audio/(若无anyPath则为audio/skill/),若没有format默认mp3 - * info.audio="otherSkillname" //引用技能 - * //引用一个其他技能的语音,若lib.skill["otherSkillname"]不存在则读取"otherSkillname"的audio为默认值[true,2] - * info.audio=["otherSkillname", number] //带fixedNum的引用技能 - * //同样引用一个其他技能的语音,若lib.skill["otherSkillname"]不存在则读取"otherSkillname"的audio为number - * //若"otherSkillname"的语音数超过number,则只取前number个 - * info.audio=[true,2,"otherSkillname1",["otherSkillname2",2]] //任意元素拼接 - * //数组里可以放任何以上的格式,结果为分析完的结果合并 - * - * info.audioname=['player1','player2'] - * //audioname里可以放任意角色名。 - * //如果其中包含发动技能的角色名"player",且info.audio不是直接路径"(anyPath/)filename(.format)"的形式 - * //则在"skill"和number中插入"_player",形如 - * - * info.audioname2={'player1':audioInfo1,'player2':audioInfo2} - * //audioname2是一个对象,其中key为角色名,value的类型和info.audio一样 - * //如果key中包含发动技能的角色名player,则直接改用info.audioname2[player]来播放语音 - * ``` */ parseSkillAudio(skill, player, skillInfo) { - return game.parseSkillTextMap(skill, player, skillInfo).map(data => data.file); + return get.Audio.skill({ skill, player, info: skillInfo }).map(data => data.file); } /** * 根据skill中的audio,audioname,audioname2和player来获取技能台词列表 * @param { string } skill 技能名 * @param { Player | Object | string } [player] 角色/角色名 - * @param { skillInfo | audioInfo } [skillInfo] 预设的skillInfo/audioInfo(转为skillInfo),覆盖lib.skill[skill] + * @param { skillInfo | audioInfo } [skillInfo] 使用指定的skillInfo/audioInfo * @returns { string[] } 语音地址列表 */ parseSkillText(skill, player, skillInfo) { - return game - .parseSkillTextMap(skill, player, skillInfo) + return get.Audio.skill({ skill, player, info: skillInfo }) .map(data => data.text) - .filter(Boolean); + .filter(text => text !== void 0); } /** + * @deprecated 请使用get.Audio.skill + * * 根据skill中的audio,audioname,audioname2和player来获取技能台词列表及其对应的源文件名 * @param { string } skill 技能名 * @param { Player | Object | string } [player] 角色/角色名 - * @param { skillInfo | audioInfo } [skillInfo] 预设的skillInfo/audioInfo(转为skillInfo),覆盖lib.skill[skill] - * @returns { any[] } 语音地址列表 + * @param { skillInfo | audioInfo } [skillInfo] 使用指定的skillInfo/audioInfo + * @returns 语音地址列表 */ parseSkillTextMap(skill, player, skillInfo) { - 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 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 - throw new RangeError(`parseSkillTextMap: ${skill} in ${history} forms a deadlock`); - }; - - 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 - ); - }; - - const getTextMap = (path, name, ext, isDefault) => ({ - name, - file: `${path}${name}${ext}`, - text: lib.translate[`#${name}`], - isDefault, - }); - - const getAudioList = (skill, options, skillInfo) => { - const info = skillInfo || get.info(skill); - if (!info) { - console.error(new ReferenceError(`parseSkillTextMap: Cannot find ${skill} in lib.skill`)); - return parseAudio(skill, Object.assign(options, { isDefault: true }), defaultInfo); - } - - const { audioname, history } = options; - history.unshift(skill); - let audioInfo = info.audio; - if (Array.isArray(info.audioname)) audioname.addArray(info.audioname); - if (info.audioname2) audioInfo = info.audioname2[getName(i => info.audioname2[i])] || audioInfo; - - return parseAudio(skill, options, audioInfo); - }; - - const parseAudio = (skill, options, audioInfo) => { - const audioname = options.audioname.slice(); - const history = options.history.slice(); - 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 (check(name, history)) return getAudioList(name, options).slice(0, number); - return parseAudio(name, options, number); - } - - const map = {}; - audioInfo.forEach(i => { - parseAudio(skill, options, i).forEach(data => (map[data.name] = data)); - }); - return Object.values(map); - } - - 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, "", 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 = "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 = "", 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)]; - } - - return getAudioList(skill, { audioname: [], history: [], isDefault: false }, skillInfo); + return get.Audio.skill({ skill, player, info: skillInfo }); } /** + * @deprecated 请使用get.Audio.die + * * 获取角色死亡时能播放的所有阵亡语音 * @param { string | Player } player 角色名 - * @returns { any[] } 语音地址列表 + * @returns 语音地址列表 */ parseDieTextMap(player) { - 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; - } - } - } - - 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); + return get.Audio.die({ player }); } /** * diff --git a/noname/get/audio.js b/noname/get/audio.js new file mode 100644 index 000000000..003ef4380 --- /dev/null +++ b/noname/get/audio.js @@ -0,0 +1,253 @@ +import { lib } from "../library/index.js"; +import { get } from "./index.js"; + +/** + * @typedef { (string | number | boolean)[] | string | number | boolean } audioInfo + * @typedef { { + * name: string, + * file: string, + * text: string | undefined, + * type: string, + * isDefault: boolean + * } } textMap + */ +export class Audio { + /** + * @type { { [key: string]: textMap } } + */ + #Cache = {}; + + /** + * 根据skill中的audio,audioname,audioname2和player来获取技能台词列表及其对应的源文件名 + * @typedef {{audio: audioInfo, audioname?: string[], audioname2?: {[playerName: string]: audioInfo}}} skillInfo + * @param { object } options + * @param { string } options.skill 技能名 + * @param { Player | string } [options.player] 角色/角色名 + * @param { audioInfo | skillInfo } [options.info] 使用指定的skillInfo/audioInfo + * @returns { textMap[] } + */ + skill({ skill, player, info }) { + //@ts-ignore + if (typeof player === "string") player = get.convertedCharacter({ name: player }); + //@ts-ignore + else if (typeof player !== "object" || player === null) player = get.convertedCharacter({ isNull: true }); + + if (typeof info !== "undefined" && (typeof info !== "object" || Array.isArray(info))) info = { audio: info }; + + const data = { + audioname: [] + }; + + const options = { + type: "skill", + defaultPath: "skill", + defaultInfo: [true, 2], + }; + + const getInfo = name => get.info(name); + const isExist = name => get.info(name); + const getAudioInfo = (name, info, data, options) => { + let audioInfo = info.audio; + if (Array.isArray(info.audioname)) data.audioname.addArray(info.audioname); + data._audioname = getName(i => data.audioname.includes(i)); + if (info.audioname2) audioInfo = info.audioname2[getName(i => info.audioname2[i])] || audioInfo; + return { audioInfo, isDefault: false }; + } + + const getName = filter => { + //@ts-ignore + const tempname = (player.tempname || []).find(i => filter(i)); + if (tempname) return name; + //@ts-ignore + for (const name of [player.name, player.name1, player.name2]) { + if (filter(name)) return name; + const tempname = get.character(name).tempname.find(i => filter(i)); + if (tempname) return tempname; + } + } + + return this.#parse({ name: skill, info, data, options, getInfo, isExist, getAudioInfo }); + } + + /** + * 获取角色死亡时能播放的所有阵亡台词列表及其对应的源文件名 + * @param { object } options + * @param { Player | string } options.player 角色/角色名 + * @param { audioInfo } [options.info] 使用指定的audioInfo + * @returns { textMap[] } + */ + die({ player, info }) { + let name = typeof player === "string" ? player : player.name; + let skinInfo; + if (info) skinInfo = { dieAudios: info }; + else 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) { + skinInfo = get.convertedCharacter(["", "", 0, [], skin[1]]); + name = skinName; + } + } + } + + const options = { + type: "die", + defaultPath: "die", + defaultInfo: true, + }; + + const getInfo = name => get.character(name); + const isExist = name => !get.character(name).isNull; + const getAudioInfo = (name, info, data, options) => { + let audioInfo = info.dieAudios; + if (audioInfo.length === 0) audioInfo = void 0; + return { audioInfo, isDefault: false }; + } + + return this.#parse({ name, info: skinInfo, options, getInfo, isExist, getAudioInfo }); + } + + /** + * @this {typeof get.Audio} + * @returns { textMap[] } + */ + #parse = function (arg) { + const { name, info, data = {}, options, getInfo, isExist, getAudioInfo } = arg; + const { type, defaultPath, defaultInfo } = options; + data.history = []; + + const check = (name, history) => { + if (!isExist(name)) return false; + if (!history.includes(name)) return true; + if (history[0] === name) return false; + //deadlock + throw new RangeError(`parse: ${name} in ${history} forms a deadlock`); + } + + const getAudioList = (name, data, info) => { + data = JSON.parse(JSON.stringify(data)); + + if (typeof info === "undefined") { + if (!isExist(name)) { + console.warn(`parse: Cannot find ${name} when parsing ${type} audio.`); + return this.#parseAudioWithCache({ parseAudio, options }, name, defaultInfo, data, true); + } + data.history.unshift(name); + info = getInfo(name); + } + + const { audioInfo, isDefault } = getAudioInfo(name, info, data, options); + + return this.#parseAudioWithCache({ parseAudio, options }, name, audioInfo, data, isDefault); + } + + const parseAudio = (name, audioInfo, data, isDefault = false) => { + const { history, _audioname } = data; + if (Array.isArray(audioInfo)) { + if (type === "skill") {//skill的屎山 + if (audioInfo.length === 2 && typeof audioInfo[0] === "string" && typeof audioInfo[1] === "number") { + const [newName, number] = audioInfo; + if (check(newName, history)) return getAudioList(newName, data).slice(0, number); + return parseAudio(newName, number, data, isDefault); + } + } + const map = {}; + audioInfo.forEach(info => { + parseAudio(name, info, data, isDefault).forEach(i => (map[i.name] = i)); + }); + return Object.values(map); + } + + if (!["string", "number", "boolean"].includes(typeof audioInfo)) + return parseAudio(name, defaultInfo, data, true); + if (audioInfo === false) return []; + + audioInfo = String(audioInfo); + + if (["data:", "blob:"].some(prefix => audioInfo.startsWith(prefix))) { + return [this.#textMap({ path: "", name: audioInfo, ext: "", type, isDefault, defaultPath })]; + } + if (check(audioInfo, history)) return getAudioList(audioInfo, data); + + const list = audioInfo.match(/(?:(.*):|^)(true|\d+)(?::(.*)|$)/); // [path, number|true, ext] + if (list) { + let [, path = defaultPath, audioNum, ext = "mp3"] = list; + path = path + "/"; + ext = "." + ext; + if (_audioname) name += "_" + _audioname; + + if (audioNum === "true") return [this.#textMap({ path, name, ext, type, isDefault, defaultPath })]; + const audioList = []; + audioNum = parseInt(audioNum); + for (let i = 1; i <= audioNum; i++) { + audioList.push(this.#textMap({ path, name: name + i, ext, type, isDefault, defaultPath })); + } + return audioList; + } + + let path = defaultPath + "/"; + const pathIndex = audioInfo.lastIndexOf("/"); + if (pathIndex !== -1) { + path = audioInfo.slice(0, pathIndex); + audioInfo = audioInfo.slice(pathIndex); + if (!["db:", "ext:"].some(i => audioInfo.startsWith(i))) path = defaultPath + "/" + path; + } + + let ext = ".mp3"; + const extIndex = audioInfo.lastIndexOf("."); + if (extIndex !== -1) { + ext = audioInfo.slice(extIndex); + audioInfo = audioInfo.slice(0, extIndex); + } + + if (pathIndex === -1 && extIndex === -1) return parseAudio(name, defaultInfo, data, true); + return [this.#textMap({ path, name: audioInfo, ext, type, isDefault, defaultPath })]; + } + + return getAudioList(name, data, info); + } + + /** + * @this {typeof get.Audio} + */ + #parseAudioWithCache = function ({ parseAudio, options }, ...args) { + const key = this.#getCacheKey(options, ...args); + const result = this.#Cache[key]; + if (typeof result !== "undefined") return result; + else { + const result = parseAudio(...args); + this.#Cache[key] = result; + return result; + } + } + + /** + * @this {typeof get.Audio} + */ + #getCacheKey = function (options, name, audioInfo, data, isDefault = false) { + const key = { name, audioInfo, ...options, isDefault }; + for (const i in data) { + const type = typeof data[i]; + if (type !== 'object' && type !== 'function' || data[i] === null) key[i] = data[i]; + } + return JSON.stringify(key); + } + + /** + * @this {typeof get.Audio} + * @returns {textMap} + */ + #textMap = function ({ path, name, ext, type, isDefault = false, defaultPath }) { + const suffix = type === "skill" ? "" : ":" + type; //skill的屎山 + const translatePath = path.startsWith(defaultPath + "/") ? path.slice(defaultPath.length + 1) : path; + return { + name: translatePath + name, + file: path + name + ext, + text: lib.translate[`#${translatePath}${name}${suffix}`], + type, + isDefault, + } + } + +} \ No newline at end of file diff --git a/noname/get/index.js b/noname/get/index.js index 5e223e177..7688381eb 100644 --- a/noname/get/index.js +++ b/noname/get/index.js @@ -8,10 +8,12 @@ import { Is } from "./is.js"; import { Promises } from "./promises.js"; import { rootURL } from "../../noname.js"; import * as pinyinPro from "./pinyins/index.js"; +import { Audio } from "./audio.js"; export class Get { is = new Is(); promises = new Promises(); + Audio = new Audio(); /** * 获取当前内核版本信息 * From 3473708d859b0b9a7655af4bc08d96876c204dfe Mon Sep 17 00:00:00 2001 From: kuangshen04 <2832899707@qq.com> Date: Mon, 20 May 2024 11:28:27 +0800 Subject: [PATCH 2/6] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E9=83=A8=E5=88=86?= =?UTF-8?q?=E5=9C=B0=E6=96=B9null=E5=92=8Cundefined=E6=B7=B7=E7=94=A8?= =?UTF-8?q?=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- noname/get/audio.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/noname/get/audio.js b/noname/get/audio.js index 003ef4380..e42fcacd0 100644 --- a/noname/get/audio.js +++ b/noname/get/audio.js @@ -32,7 +32,7 @@ export class Audio { //@ts-ignore else if (typeof player !== "object" || player === null) player = get.convertedCharacter({ isNull: true }); - if (typeof info !== "undefined" && (typeof info !== "object" || Array.isArray(info))) info = { audio: info }; + if (info !== void 0 && info !== null && (typeof info !== "object" || Array.isArray(info))) info = { audio: info }; const data = { audioname: [] @@ -128,7 +128,7 @@ export class Audio { const getAudioList = (name, data, info) => { data = JSON.parse(JSON.stringify(data)); - if (typeof info === "undefined") { + if (info === void 0 || info === null) { if (!isExist(name)) { console.warn(`parse: Cannot find ${name} when parsing ${type} audio.`); return this.#parseAudioWithCache({ parseAudio, options }, name, defaultInfo, data, true); From d895fb4cc0c27a0e8a23f220fd1c08548cc2cbc1 Mon Sep 17 00:00:00 2001 From: kuangshen04 <2832899707@qq.com> Date: Tue, 21 May 2024 14:09:41 +0800 Subject: [PATCH 3/6] =?UTF-8?q?=E4=B8=BA=E6=AD=BB=E4=BA=A1=E5=A4=87?= =?UTF-8?q?=E7=94=A8=E8=AF=AD=E9=9F=B3=E6=8F=90=E4=BE=9B=E6=9B=B4=E6=B8=85?= =?UTF-8?q?=E6=99=B0=E7=9A=84=E5=86=99=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- noname/game/index.js | 35 ++++++++++++++++++----------------- noname/get/audio.js | 24 ++++++++++++++++++++++-- 2 files changed, 40 insertions(+), 19 deletions(-) diff --git a/noname/game/index.js b/noname/game/index.js index be619da39..14669206d 100644 --- a/noname/game/index.js +++ b/noname/game/index.js @@ -1468,15 +1468,24 @@ export class Game { */ trySkillAudio(skill, player, directaudio, nobroadcast, skillInfo) { if (!nobroadcast) game.broadcast(game.trySkillAudio, skill, player, directaudio, nobroadcast, skillInfo); + if (!lib.config.background_speak) return; + const info = skillInfo || lib.skill[skill]; if (!info) return; - if (!lib.config.background_speak) return; if (info.direct && !directaudio) return; if (lib.skill.global.includes(skill) && !info.forceaudio) return; - let audio, - list = game.parseSkillTextMap(skill, player, skillInfo).randomSort(); + + let audio, list = get.Audio.skill({ skill, player, info: skillInfo }).randomSort(); + const check = () => { + if (list.length) return true; + //@ts-ignore + if (!list.alternate) return false; + //@ts-ignore + list = list.alternate; + return check(); + }; return (function play() { - if (!list.length) return; + if (!check()) return; audio = list.shift(); return game.playAudio(audio.file, play); })(); @@ -1489,21 +1498,13 @@ export class Game { 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(); + let audio, list = get.Audio.die({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(); + //@ts-ignore + if (!list.alternate) return false; + //@ts-ignore + list = list.alternate; return check(); }; return (function play() { diff --git a/noname/get/audio.js b/noname/get/audio.js index e42fcacd0..a4fff3d17 100644 --- a/noname/get/audio.js +++ b/noname/get/audio.js @@ -27,6 +27,10 @@ export class Audio { * @returns { textMap[] } */ skill({ skill, player, info }) { + if (skill === void 0) { + console.error(new ReferenceError(`skill is not defined`)); + return []; + } //@ts-ignore if (typeof player === "string") player = get.convertedCharacter({ name: player }); //@ts-ignore @@ -77,6 +81,10 @@ export class Audio { * @returns { textMap[] } */ die({ player, info }) { + if (player === void 0) { + console.error(new ReferenceError(`player is not defined`)); + return []; + } let name = typeof player === "string" ? player : player.name; let skinInfo; if (info) skinInfo = { dieAudios: info }; @@ -113,7 +121,8 @@ export class Audio { * @returns { textMap[] } */ #parse = function (arg) { - const { name, info, data = {}, options, getInfo, isExist, getAudioInfo } = arg; + const { data = {}, options, getInfo, isExist, getAudioInfo } = arg; + let { name, info } = arg; const { type, defaultPath, defaultInfo } = options; data.history = []; @@ -205,7 +214,18 @@ export class Audio { return [this.#textMap({ path, name: audioInfo, ext, type, isDefault, defaultPath })]; } - return getAudioList(name, data, info); + const getResult = () => { + const result = getAudioList(name, data, info); + if (!result.every(i => i.isDefault)) return result; + if (name.includes("_")) { + name = name.slice(name.indexOf("_") + 1); + info = void 0; + //@ts-ignore + result.alternate = getResult(); + } + return result; + } + return getResult(); } /** From 1e779a13d6d579c176852a0c24f4a536ab6b82c9 Mon Sep 17 00:00:00 2001 From: kuangshen04 <2832899707@qq.com> Date: Tue, 21 May 2024 14:15:23 +0800 Subject: [PATCH 4/6] bugfix --- noname/get/audio.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/noname/get/audio.js b/noname/get/audio.js index a4fff3d17..e70d7cc10 100644 --- a/noname/get/audio.js +++ b/noname/get/audio.js @@ -18,8 +18,8 @@ export class Audio { #Cache = {}; /** - * 根据skill中的audio,audioname,audioname2和player来获取技能台词列表及其对应的源文件名 - * @typedef {{audio: audioInfo, audioname?: string[], audioname2?: {[playerName: string]: audioInfo}}} skillInfo + * 根据skill中的audio,audioname,audioname2和player来获取技能台词列表及其对应的源文件名 + * @typedef {{audio: audioInfo, audioname?: string[], audioname2?: {[playerName: string]: audioInfo}}} skillInfo * @param { object } options * @param { string } options.skill 技能名 * @param { Player | string } [options.player] 角色/角色名 @@ -30,7 +30,7 @@ export class Audio { if (skill === void 0) { console.error(new ReferenceError(`skill is not defined`)); return []; - } + } //@ts-ignore if (typeof player === "string") player = get.convertedCharacter({ name: player }); //@ts-ignore @@ -74,7 +74,7 @@ export class Audio { } /** - * 获取角色死亡时能播放的所有阵亡台词列表及其对应的源文件名 + * 获取角色死亡时能播放的所有阵亡台词列表及其对应的源文件名 * @param { object } options * @param { Player | string } options.player 角色/角色名 * @param { audioInfo } [options.info] 使用指定的audioInfo @@ -216,7 +216,7 @@ export class Audio { const getResult = () => { const result = getAudioList(name, data, info); - if (!result.every(i => i.isDefault)) return result; + if (!result.every(i => i.isDefault && !i.text)) return result; if (name.includes("_")) { name = name.slice(name.indexOf("_") + 1); info = void 0; From 1528a2f1f9703911bbe44b78ea2ca80780d2c9e8 Mon Sep 17 00:00:00 2001 From: kuangshen04 <2832899707@qq.com> Date: Tue, 21 May 2024 15:39:18 +0800 Subject: [PATCH 5/6] =?UTF-8?q?=E6=B6=88=E7=81=ADisDefault?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- noname/get/audio.js | 106 ++++++++++++++++++++------------------------ 1 file changed, 47 insertions(+), 59 deletions(-) diff --git a/noname/get/audio.js b/noname/get/audio.js index e70d7cc10..0a75f62bf 100644 --- a/noname/get/audio.js +++ b/noname/get/audio.js @@ -8,7 +8,6 @@ import { get } from "./index.js"; * file: string, * text: string | undefined, * type: string, - * isDefault: boolean * } } textMap */ export class Audio { @@ -24,9 +23,10 @@ export class Audio { * @param { string } options.skill 技能名 * @param { Player | string } [options.player] 角色/角色名 * @param { audioInfo | skillInfo } [options.info] 使用指定的skillInfo/audioInfo + * @param { audioInfo } [options.defaultInfo] 默认的audioInfo * @returns { textMap[] } */ - skill({ skill, player, info }) { + skill({ skill, player, info, defaultInfo = [true, 2] }) { if (skill === void 0) { console.error(new ReferenceError(`skill is not defined`)); return []; @@ -45,7 +45,7 @@ export class Audio { const options = { type: "skill", defaultPath: "skill", - defaultInfo: [true, 2], + defaultInfo, }; const getInfo = name => get.info(name); @@ -78,9 +78,10 @@ export class Audio { * @param { object } options * @param { Player | string } options.player 角色/角色名 * @param { audioInfo } [options.info] 使用指定的audioInfo + * @param { audioInfo } [options.defaultInfo] 默认的audioInfo * @returns { textMap[] } */ - die({ player, info }) { + die({ player, info, defaultInfo = true }) { if (player === void 0) { console.error(new ReferenceError(`player is not defined`)); return []; @@ -102,7 +103,7 @@ export class Audio { const options = { type: "die", defaultPath: "die", - defaultInfo: true, + defaultInfo, }; const getInfo = name => get.character(name); @@ -121,76 +122,89 @@ export class Audio { * @returns { textMap[] } */ #parse = function (arg) { - const { data = {}, options, getInfo, isExist, getAudioInfo } = arg; - let { name, info } = arg; - const { type, defaultPath, defaultInfo } = options; - data.history = []; + const { name, info, data: originData = {}, options, getInfo, isExist, getAudioInfo } = arg; + const { type, defaultPath } = options; + originData.history = []; const check = (name, history) => { if (!isExist(name)) return false; if (!history.includes(name)) return true; if (history[0] === name) return false; //deadlock - throw new RangeError(`parse: ${name} in ${history} forms a deadlock`); + throw new RangeError(`parseAudio: ${name} in ${history} forms a deadlock`); } - const getAudioList = (name, data, info) => { + const getInfoAudio = (name, data, { info = void 0, defaultInfo = options.defaultInfo } = {},) => { data = JSON.parse(JSON.stringify(data)); if (info === void 0 || info === null) { - if (!isExist(name)) { - console.warn(`parse: Cannot find ${name} when parsing ${type} audio.`); - return this.#parseAudioWithCache({ parseAudio, options }, name, defaultInfo, data, true); + if (!check(name, data.history)) { + // console.warn(`parse: Cannot find ${name} when parsing ${type} audio.`); + return parseAudioWithCache(name, defaultInfo, data, true); } data.history.unshift(name); info = getInfo(name); } const { audioInfo, isDefault } = getAudioInfo(name, info, data, options); + if (isDefault || audioInfo === null || audioInfo === void 0) + return parseAudioWithCache(name, defaultInfo, data, true); - return this.#parseAudioWithCache({ parseAudio, options }, name, audioInfo, data, isDefault); + return parseAudioWithCache(name, audioInfo, data); } - const parseAudio = (name, audioInfo, data, isDefault = false) => { - const { history, _audioname } = data; + const parseAudioWithCache = (name, audioInfo, data, isDefault = false) => { + const key = this.#getCacheKey(options, name, audioInfo, data); + const result = this.#Cache[key]; + if (result !== void 0) return result; + else { + const result = parseAudio(name, audioInfo, data); + if (isDefault && name.includes("_")) { + name = name.slice(name.indexOf("_") + 1); + result.alternate = getInfoAudio(name, originData); + } + this.#Cache[key] = result; + return result; + } + } + + const parseAudio = (name, audioInfo, data) => { if (Array.isArray(audioInfo)) { if (type === "skill") {//skill的屎山 if (audioInfo.length === 2 && typeof audioInfo[0] === "string" && typeof audioInfo[1] === "number") { const [newName, number] = audioInfo; - if (check(newName, history)) return getAudioList(newName, data).slice(0, number); - return parseAudio(newName, number, data, isDefault); + return getInfoAudio(newName, data, { defaultInfo: number }).slice(0, number); } } const map = {}; audioInfo.forEach(info => { - parseAudio(name, info, data, isDefault).forEach(i => (map[i.name] = i)); + parseAudio(name, info, data).forEach(i => (map[i.name] = i)); }); return Object.values(map); } - if (!["string", "number", "boolean"].includes(typeof audioInfo)) - return parseAudio(name, defaultInfo, data, true); - if (audioInfo === false) return []; audioInfo = String(audioInfo); + if (audioInfo === "false") return []; + if (["data:", "blob:"].some(prefix => audioInfo.startsWith(prefix))) { - return [this.#textMap({ path: "", name: audioInfo, ext: "", type, isDefault, defaultPath })]; + return [this.#textMap({ path: "", name: audioInfo, ext: "", type, defaultPath })]; } - if (check(audioInfo, history)) return getAudioList(audioInfo, data); const list = audioInfo.match(/(?:(.*):|^)(true|\d+)(?::(.*)|$)/); // [path, number|true, ext] if (list) { let [, path = defaultPath, audioNum, ext = "mp3"] = list; path = path + "/"; ext = "." + ext; + const { _audioname } = data; if (_audioname) name += "_" + _audioname; - if (audioNum === "true") return [this.#textMap({ path, name, ext, type, isDefault, defaultPath })]; + if (audioNum === "true") return [this.#textMap({ path, name, ext, type, defaultPath })]; const audioList = []; audioNum = parseInt(audioNum); for (let i = 1; i <= audioNum; i++) { - audioList.push(this.#textMap({ path, name: name + i, ext, type, isDefault, defaultPath })); + audioList.push(this.#textMap({ path, name: name + i, ext, type, defaultPath })); } return audioList; } @@ -210,43 +224,18 @@ export class Audio { audioInfo = audioInfo.slice(0, extIndex); } - if (pathIndex === -1 && extIndex === -1) return parseAudio(name, defaultInfo, data, true); - return [this.#textMap({ path, name: audioInfo, ext, type, isDefault, defaultPath })]; + if (pathIndex === -1 && extIndex === -1) return getInfoAudio(audioInfo, data); + return [this.#textMap({ path, name: audioInfo, ext, type, defaultPath })]; } - const getResult = () => { - const result = getAudioList(name, data, info); - if (!result.every(i => i.isDefault && !i.text)) return result; - if (name.includes("_")) { - name = name.slice(name.indexOf("_") + 1); - info = void 0; - //@ts-ignore - result.alternate = getResult(); - } - return result; - } - return getResult(); + return getInfoAudio(name, originData, { info }); } /** * @this {typeof get.Audio} */ - #parseAudioWithCache = function ({ parseAudio, options }, ...args) { - const key = this.#getCacheKey(options, ...args); - const result = this.#Cache[key]; - if (typeof result !== "undefined") return result; - else { - const result = parseAudio(...args); - this.#Cache[key] = result; - return result; - } - } - - /** - * @this {typeof get.Audio} - */ - #getCacheKey = function (options, name, audioInfo, data, isDefault = false) { - const key = { name, audioInfo, ...options, isDefault }; + #getCacheKey = function (options, name, audioInfo, data) { + const key = { name, audioInfo, ...options }; for (const i in data) { const type = typeof data[i]; if (type !== 'object' && type !== 'function' || data[i] === null) key[i] = data[i]; @@ -258,7 +247,7 @@ export class Audio { * @this {typeof get.Audio} * @returns {textMap} */ - #textMap = function ({ path, name, ext, type, isDefault = false, defaultPath }) { + #textMap = function ({ path, name, ext, type, defaultPath }) { const suffix = type === "skill" ? "" : ":" + type; //skill的屎山 const translatePath = path.startsWith(defaultPath + "/") ? path.slice(defaultPath.length + 1) : path; return { @@ -266,7 +255,6 @@ export class Audio { file: path + name + ext, text: lib.translate[`#${translatePath}${name}${suffix}`], type, - isDefault, } } From 477a9d38c7522be73405b2c9e186f6b46141cc18 Mon Sep 17 00:00:00 2001 From: kuangshen04 <2832899707@qq.com> Date: Tue, 21 May 2024 17:30:48 +0800 Subject: [PATCH 6/6] bugfix --- noname/get/audio.js | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/noname/get/audio.js b/noname/get/audio.js index 0a75f62bf..c416b2787 100644 --- a/noname/get/audio.js +++ b/noname/get/audio.js @@ -12,7 +12,7 @@ import { get } from "./index.js"; */ export class Audio { /** - * @type { { [key: string]: textMap } } + * @type { { [key: string]: textMap[] } } */ #Cache = {}; @@ -121,8 +121,7 @@ export class Audio { * @this {typeof get.Audio} * @returns { textMap[] } */ - #parse = function (arg) { - const { name, info, data: originData = {}, options, getInfo, isExist, getAudioInfo } = arg; + #parse = function ({ name, info, data: originData = {}, options, getInfo, isExist, getAudioInfo }) { const { type, defaultPath } = options; originData.history = []; @@ -156,7 +155,7 @@ export class Audio { const parseAudioWithCache = (name, audioInfo, data, isDefault = false) => { const key = this.#getCacheKey(options, name, audioInfo, data); const result = this.#Cache[key]; - if (result !== void 0) return result; + if (result !== void 0) return this.#copy(result); else { const result = parseAudio(name, audioInfo, data); if (isDefault && name.includes("_")) { @@ -164,7 +163,7 @@ export class Audio { result.alternate = getInfoAudio(name, originData); } this.#Cache[key] = result; - return result; + return this.#copy(result); } } @@ -258,4 +257,16 @@ export class Audio { } } + /** + * @param {textMap[]} list + * @this {typeof get.Audio} + * @returns {textMap[]} + */ + #copy = function (list) { + const result = JSON.parse(JSON.stringify(list)); + //@ts-ignore + if (list.alternate) result.alternate = this.#copy(list.alternate); + return result; + } + } \ No newline at end of file