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);
}
/**
*