diff --git a/character/collab/character.js b/character/collab/character.js index 99c8fea39..55361aca1 100644 --- a/character/collab/character.js +++ b/character/collab/character.js @@ -19,6 +19,7 @@ const characters = { sp_fuwan: ["male", "qun", 3, ["spfengyin", "spchizhong"]], old_lingju: ["female", "qun", 3, ["jieyuan", "fenxin_old"]], sp_mushun: ["male", "qun", 4, ["moukui"]], + dc_wuyi: ["male", "shu", 4, ["dcbenxi"]], }; export default characters; diff --git a/character/collab/skill.js b/character/collab/skill.js index 450c2fb5f..9661df59d 100644 --- a/character/collab/skill.js +++ b/character/collab/skill.js @@ -2,6 +2,90 @@ import { lib, game, ui, get, ai, _status } from "../../noname.js"; /** @type { importCharacterConfig['skill'] } */ const skills = { + //名将吴懿 + dcbenxi: { + trigger: { + player: ["loseAfter"], + global: ["equipAfter", "addJudgeAfter", "gainAfter", "loseAsyncAfter", "addToExpansionAfter"], + }, + forced: true, + zhuanhuanji: true, + filter(event, player) { + const evt = event.getl(player); + return evt && evt.hs && evt.hs.length > 0; + }, + async content(event, trigger, player) { + player.changeZhuanhuanji("dcbenxi"); + if (player.storage.dcbenxi) { + const map = lib.skill.dcbenxi.getMap(), + list = Object.keys(map); + if (list.length > 0) { + const skill = list.randomGet(), + voiceMap = game.parseSkillTextMap(skill, map[skill]); + console.log(voiceMap); + player.storage.dcbenxi_pending = skill; + findaudio: for (let data of voiceMap) { + if(!data.text) continue; + const pinyins = get.pinyin(data.text, false); + for (let i = 0; i < pinyins.length - 1; i++) { + if (pinyins[i] === "wu" && pinyins[i + 1] === "yi") { + player.chat(data.text); + game.broadcastAll(file => game.playAudio(file), data.file) + break findaudio; + } + } + } + } + } else { + const skill = player.storage.dcbenxi_pending; + if (skill) { + if (player.hasSkill(skill, null, false)){ + const targets = game.filterPlayer(current => current != player).sortBySeat(); + player.line(targets, 'fire'); + for(let target of targets){ + if(target.isIn()) await target.damage(); + } + } + else{ + await player.addTempSkills([skill], {player: 'phaseBegin'}); + } + delete player.storage.dcbenxi_pending; + } + } + }, + getMap() { + if (!_status.dcbenxi_map) { + _status.dcbenxi_map = {}; + let list; + if (_status.connectMode) { + list = get.charactersOL(); + } else { + list = get.gainableCharacters(); + } + list.forEach(name => { + if (name !== "dc_wuyi") { + const skills = get.character(name).skills; + skills.forEach(skill => { + if (skill in _status.dcbenxi_map) return; + const voices = game.parseSkillText(skill, name); + if ( + voices.some(text => { + const pinyins = get.pinyin(text, false); + for (let i = 0; i < pinyins.length - 1; i++) { + if (pinyins[i] === "wu" && pinyins[i + 1] === "yi") return true; + } + return false; + }) + ) { + _status.dcbenxi_map[skill] = name; + } + }); + } + }); + } + return _status.dcbenxi_map; + }, + }, //新InitFilter测试高达一号 //打赢复活赛的牢达[哭] dclonghun: { @@ -355,7 +439,7 @@ const skills = { player.gain(trigger.cards, "gain2"); } player.draw(player.countMark("dcjianxiong") + 1, "nodelay"); - "step 1"; + ("step 1"); if (player.countMark("dcjianxiong") < 4) player.addMark("dcjianxiong", 1, false); }, marktext: "雄", @@ -410,7 +494,7 @@ const skills = { player.addTempSkill("dcrende_targeted", "phaseUseAfter"); player.markAuto("dcrende_targeted", [target]); player.gainPlayerCard(target, "h", true, 2); - "step 1"; + ("step 1"); var list = []; for (var name of lib.inpile) { if (get.type(name) != "basic") continue; @@ -469,7 +553,7 @@ const skills = { } else { event.finish(); } - "step 2"; + ("step 2"); if (result && result.bool && result.links[0]) { var card = { name: result.links[0][2], @@ -543,7 +627,7 @@ const skills = { break; } } - "step 1"; + ("step 1"); player.draw(event.num + cards.length); }, subSkill: { @@ -624,7 +708,7 @@ const skills = { } var skills = characters.map(i => lib.skill.dcbianzhuang.characterMap[i]); player.chooseControl(skills).set("dialog", ["选择获得一个技能并“变装”", [characters, "character"]]); - "step 1"; + ("step 1"); var skill = result.control; player.addTempSkills(skill, "dcbianzhuangAfter"); for (var i in lib.skill.dcbianzhuang.characterMap) { @@ -636,7 +720,7 @@ const skills = { } } player.chooseUseTarget("sha", true, false, "nodistance"); - "step 2"; + ("step 2"); if (result.bool && !player.storage.dcbianzhuang_inited) { player.addMark("dcbianzhuang", 1, false); if (player.countMark("dcbianzhuang") > 2) { @@ -713,7 +797,7 @@ const skills = { var player = _status.event.player; return 1 + Math.max(0, player.getUseValue(card, null, true)); }); - "step 1"; + ("step 1"); if (result.bool) { player.logSkill("dctongliao"); player.addGaintag(result.cards, "dctongliao"); @@ -835,7 +919,7 @@ const skills = { return get.effect(target, { name: "sha" }, _status.event.player); }) .setHiddenSkill("clbjisu"); - "step 1"; + ("step 1"); if (result.bool) { player.useCard({ name: "sha", isCard: true }, result.targets[0], false, "clbjisu"); trigger.cancel(); @@ -983,7 +1067,7 @@ const skills = { content() { "step 0"; player.removeGaintag("dcshixian_yayun"); - "step 1"; + ("step 1"); player.addGaintag( player.getCards("h", card => { return get.is.yayun(get.translation(card.name), get.translation(trigger.card.name)); @@ -1009,7 +1093,7 @@ const skills = { content() { "step 0"; trigger.cancel(); - "step 1"; + ("step 1"); var card = get.cardPile2(function (card) { return get.type(card, null, false) == "equip"; }), @@ -1054,7 +1138,7 @@ const skills = { player.chooseButton(true, ["请选择执行一个天气", [list.map(i => [i, '"]), "textbutton"]]).set("ai", function (button) { return lib.skill.dcsitian.weathers[button.link].ai(_status.event.player); }); - "step 1"; + ("step 1"); if (result.bool) { var choice = result.links[0]; game.log(player, "将当前天气变更为", "#g" + choice); @@ -1126,7 +1210,7 @@ const skills = { var targets = game.filterPlayer(current => current != player).sortBySeat(); player.line(targets, "thunder"); event.targets = targets; - "step 1"; + ("step 1"); var target = targets.shift(); if (!target.isIn()) { if (targets.length > 0) event.redo(); @@ -1139,7 +1223,7 @@ const skills = { event.judgestr = get.translation("shandian"); target.judge(lib.card.shandian.judge, event.judgestr).judge2 = lib.card.shandian.judge2; //game.delayx(1.5); - "step 2"; + ("step 2"); var name = "shandian"; if (event.cancelled && !event.direct) { if (lib.card[name].cancel) { @@ -1179,7 +1263,7 @@ const skills = { var targets = game.filterPlayer(current => current != player).sortBySeat(); player.line(targets, "green"); event.targets = targets; - "step 1"; + ("step 1"); var target = targets.shift(); if (target.isIn()) { var num = target.countCards("e"); @@ -1220,7 +1304,7 @@ const skills = { } return get.effect(current, { name: "losehp" }, player, player); }); - "step 1"; + ("step 1"); if (result.bool) { var target = result.targets[0]; player.line(target, "green"); @@ -1512,14 +1596,14 @@ const skills = { } event.finish(); } - "step 1"; + ("step 1"); if (result.bool) { if (!event.isMine() && !event.isOnline()) game.delayx(); event.targets = result.targets; } else { event.finish(); } - "step 2"; + ("step 2"); player.logSkill("ruyijingubang_effect", event.targets); trigger.targets.addArray(event.targets); }, @@ -1625,7 +1709,7 @@ const skills = { event.card = get.cards()[0]; game.cardsGotoOrdering(event.card); player.showCards(event.card); - "step 1"; + ("step 1"); player .chooseTarget("令一名角色获得" + get.translation(card), true) .set("ai", function (target) { @@ -1641,7 +1725,7 @@ const skills = { return att; }) .set("du", card.name == "du"); - "step 2"; + ("step 2"); if (result && result.bool) { var target = result.targets[0]; target.gain(card, "gain2"); @@ -1673,7 +1757,7 @@ const skills = { content() { "step 0"; player.chooseDrawRecover("###" + get.prompt("spcangni") + "###摸两张牌或回复1点体力,然后将武将牌翻面", 2).logSkill = "spcangni"; - "step 1"; + ("step 1"); if (result.control != "cancel2") player.turnOver(); }, group: ["spcangni_gain", "spcangni_lose"], @@ -1755,7 +1839,7 @@ const skills = { content() { "step 0"; player.give(cards, targets[0]); - "step 1"; + ("step 1"); if (!targets[0].isIn() || !targets[1].isIn()) { event.finish(); return; @@ -1774,7 +1858,7 @@ const skills = { return lib.filter.targetEnabled.apply(this, arguments); }) .set("sourcex", targets[1]); - "step 2"; + ("step 2"); if (!result.bool && targets[0].countCards("h")) targets[1].gainPlayerCard(targets[0], "visible", "h", true); }, ai: { @@ -1828,7 +1912,7 @@ const skills = { return true; })() ); - "step 1"; + ("step 1"); if (result.bool) { var target = trigger.player; player.logSkill("spfengyin", target); diff --git a/character/collab/translate.js b/character/collab/translate.js index 3ef849aed..43969a5e6 100644 --- a/character/collab/translate.js +++ b/character/collab/translate.js @@ -95,11 +95,10 @@ const translates = { dczhanjiang: "斩将", dczhanjiang_info: "准备阶段,若场上有【青釭剑】,则你可以获得之。", - collab_olympic: "OL·伦敦奥运会", - collab_tongque: "OL·铜雀台", - collab_duanwu: "新服·端午畅玩", - collab_decade: "新服·创玩节", - collab_remake: "新服·共创武将", + dc_wuyi: "经典吴懿", + dc_wuyi_prefix: "经典", + dcbenxi: "奔袭", + dcbenxi_info: "转换技,锁定技。当你失去手牌后,阴:系统随机检索出一句转换为拼音后包含“wu,yi”的技能台词,然后你念出此台词。阳:你获得上次所念出的台词对应的技能;若你已拥有该技能,则改为对其他角色各造成1点伤害。", }; export default translates; diff --git a/character/key/index.js b/character/key/index.js index 99fbd9bde..e351d6512 100644 --- a/character/key/index.js +++ b/character/key/index.js @@ -10,6 +10,7 @@ import { characterSort, characterSortTranslate } from "./sort.js"; game.import("character", function () { return { name: "key", + connect: true, character: { ...characters }, characterSort: { key: characterSort, diff --git a/noname/game/index.js b/noname/game/index.js index 1661ceb90..f001165a5 100644 --- a/noname/game/index.js +++ b/noname/game/index.js @@ -1450,7 +1450,7 @@ export class Game { * //如果key中包含发动技能的角色名player,则直接改用info.audioname2[player]来播放语音 * ``` */ - parseSkillAudio(skill, player, skillInfo, useRawAudio) { + parseSkillAudio(skill, player, skillInfo) { if (typeof player === "string") player = { name: player }; else if (typeof player !== "object" || player === null) player = {}; @@ -1537,7 +1537,7 @@ export class Game { if (!/^db:|^ext:|\//.test(audioInfo)) path = "skill/"; if (!/\.\w+$/.test(audioInfo) && !["data:", "blob:"].some(name => audioInfo.startsWith(name))) format = ".mp3"; if (path && format) return parseAudio(audioInfo, options, [true, 2]); - return [useRawAudio ? audioInfo : `${path}${audioInfo}${format}`]; + return [`${path}${audioInfo}${format}`]; } let _audioname = getName(i => audioname.includes(i)); @@ -1548,8 +1548,7 @@ export class Game { const audioList = []; list[2] = parseInt(list[2]); for (let i = 1; i <= list[2]; i++) { - if (useRawAudio) audioList.push(`${skill}${_audioname}${i}`); - else audioList.push(`${list[1] || "skill"}/${skill}${_audioname}${i}.${list[3] || "mp3"}`); + audioList.push(`${list[1] || "skill"}/${skill}${_audioname}${i}.${list[3] || "mp3"}`); } return audioList; } @@ -1564,11 +1563,10 @@ export class Game { * @returns { string[] } 语音地址列表 */ parseSkillText(skill, player, skillInfo) { - const audios = game.parseSkillAudio(skill, player, skillInfo, true); + const audios = game.parseSkillTextMap(skill, player, skillInfo); const voiceMap = []; audios.forEach(audioname => { - const voiceText = lib.translate[`#${audioname}`]; - if (voiceText) voiceMap.push(voiceText); + if(audioname.text) voiceMap.push(audioname.text); }); return voiceMap; } @@ -1577,16 +1575,112 @@ export class Game { * @param { string } skill 技能名 * @param { Player | Object | string } [player] 角色/角色名 * @param { skillInfo | audioInfo } [skillInfo] 预设的skillInfo/audioInfo(转为skillInfo),覆盖lib.skill[skill] - * @returns { Object } 语音地址列表 + * @returns { any[] } 语音地址列表 */ parseSkillTextMap(skill, player, skillInfo) { - const audios = game.parseSkillAudio(skill, player, skillInfo, true); - const voiceMap = {}; - audios.forEach(audioname => { - const voiceText = lib.translate[`#${audioname}`]; - if (voiceText) voiceMap[audioname] = voiceText; - }); - return voiceMap; + if (typeof player === "string") player = { name: player }; + else if (typeof player !== "object" || player === null) player = {}; + + if (skillInfo && (typeof skillInfo !== "object" || Array.isArray(skillInfo))) skillInfo = { audio: skillInfo }; + + const checkSkill = (skill, history) => { + if (!lib.skill[skill]) return false; + if (!history.includes(skill)) return true; + if (history[0] === skill) return false; + //deadlock + throw new RangeError(`parseSkillAudio: ${skill} in ${history}forms a deadlock`); + }; + + const getName = filter => { + const name = (player.tempname || []).find(i => filter(i)); + return ( + name || + [player.name, player.name1, player.name2].reduce((result, name) => { + if (result) return result; + if (!name) return result; + if (filter(name)) return name; + let tempname = (get.character(name).trashBin || []).find(tag => tag.startsWith("tempname:")); + if (!tempname) return result; + tempname = tempname + .split(":") + .slice(1) + .find(i => filter(i)); + return tempname || result; + }, void 0) + ); + }; + + function getAudioList(skill, options, skillInfo) { + const info = skillInfo || lib.skill[skill]; + if (!info) { + console.error(new ReferenceError(`parseSkillAudio: Cannot find ${skill} in lib.skill`)); + return parseAudio(skill, options, [true, 2]); + } + + 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; + if (typeof audioInfo === "function") audioInfo = audioInfo(player); + + return parseAudio(skill, options, audioInfo); + } + + function parseAudio(skill, options, audioInfo) { + const audioname = options.audioname.slice(); + const history = options.history.slice(); + options = { audioname, history }; + if (Array.isArray(audioInfo)) { + if (typeof audioInfo[0] === "string" && typeof audioInfo[1] === "number") { + // [audioname, number] + if (checkSkill(audioInfo[0], history)) return getAudioList(audioInfo[0], options).slice(0, audioInfo[1]); + return parseAudio(audioInfo[0], options, audioInfo[1]); + } + return audioInfo.reduce((total, i) => total.addArray(parseAudio(skill, options, i)), []); + } + + if (!["string", "number", "boolean"].includes(typeof audioInfo)) return parseAudio(skill, options, [true, 2]); + if (audioInfo === false) return []; + if (typeof audioInfo === "string" && checkSkill(audioInfo, history)) return getAudioList(audioInfo, options); + + audioInfo = String(audioInfo); + let list = audioInfo.match(/(?:(.*):|^)(true|\d+)(?::(.*)|$)/); // [path, number|true, format] + if (!list) { + let path = "", + format = ""; + if (!/^db:|^ext:|\//.test(audioInfo)) path = "skill/"; + if (!/\.\w+$/.test(audioInfo) && !["data:", "blob:"].some(name => audioInfo.startsWith(name))) format = ".mp3"; + if (path && format) return parseAudio(audioInfo, options, [true, 2]); + + const key = audioInfo, file = `${path}${audioInfo}${format}`; + const data = {key, file}; + if(lib.translate[`#${key}`]) data.text = lib.translate[`#${key}`]; + return [data]; + } + + let _audioname = getName(i => audioname.includes(i)); + _audioname = _audioname ? `_${_audioname}` : ""; + + if (list[2] === "true") { + const key = `${skill}${_audioname}`, file = `${list[1] || "skill"}/${skill}${_audioname}.${list[3] || "mp3"}`; + const data = {key, file}; + if(lib.translate[`#${key}`]) data.text = lib.translate[`#${key}`]; + return [data]; + } + + const audioList = [] + list[2] = parseInt(list[2]); + for (let i = 1; i <= list[2]; i++) { + const key = `${skill}${_audioname}${i}`, file = `${list[1] || "skill"}/${skill}${_audioname}${i}.${list[3] || "mp3"}`; + const data = {key, file}; + if(lib.translate[`#${key}`]) data.text = lib.translate[`#${key}`]; + audioList.push(data); + } + return audioList; + } + + return getAudioList(skill, { audioname: [], history: [] }, skillInfo); } /** *