diff --git a/.gitignore b/.gitignore index d981fa7..c418696 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,3 @@ log.txt -dist/ -tools/ \ No newline at end of file +dist/manifest.json +tools/ diff --git a/character/audio/ganning/fenwei_shuying1.mp3 b/character/audio/ganning/fenwei_shuying1.mp3 new file mode 100644 index 0000000..be3dbaa Binary files /dev/null and b/character/audio/ganning/fenwei_shuying1.mp3 differ diff --git a/character/audio/ganning/fenwei_shuying2.mp3 b/character/audio/ganning/fenwei_shuying2.mp3 new file mode 100644 index 0000000..128d431 Binary files /dev/null and b/character/audio/ganning/fenwei_shuying2.mp3 differ diff --git a/character/audio/ganning/ganning_shuying.mp3 b/character/audio/ganning/ganning_shuying.mp3 new file mode 100644 index 0000000..ff83701 Binary files /dev/null and b/character/audio/ganning/ganning_shuying.mp3 differ diff --git a/character/audio/ganning/qixi_shuying1.mp3 b/character/audio/ganning/qixi_shuying1.mp3 new file mode 100644 index 0000000..f1e9117 Binary files /dev/null and b/character/audio/ganning/qixi_shuying1.mp3 differ diff --git a/character/audio/ganning/qixi_shuying2.mp3 b/character/audio/ganning/qixi_shuying2.mp3 new file mode 100644 index 0000000..ae9ae2b Binary files /dev/null and b/character/audio/ganning/qixi_shuying2.mp3 differ diff --git a/character/character.js b/character/character.js index eef0c77..ac74ed0 100644 --- a/character/character.js +++ b/character/character.js @@ -7,6 +7,14 @@ const characters = { img: "extension/术樱包/character/images/zuoci_shuying.jpg", dieAudios: ["ext:术樱包/character/audio/zuoci/zuoci_shuying.mp3"] }, + ganning_shuying: { + sex: "male", + group: "wu", + hp: 4, + skills: ["qixi_shuying", "fenwei_shuying"], + img: "extension/术樱包/character/images/ganning_shuying.jpg", + dieAudios: ["ext:术樱包/character/audio/ganning/ganning_shuying.mp3"] + }, }; export default characters; diff --git a/character/dynamicTranslate.js b/character/dynamicTranslate.js index 162e610..9192b15 100644 --- a/character/dynamicTranslate.js +++ b/character/dynamicTranslate.js @@ -1,3 +1,11 @@ -const dynamicTranslates = {}; +const dynamicTranslates = { + qixi_shuying(player, skill) { + if (player?.storage.fenwei_shuying_upgraded) { + return "你可以将一张黑色牌当【过河拆桥】使用。
你使用【过河拆桥】结束后,若其弃置过一张牌,你记录该牌的花色。
每回合各限一次,若此次花色与上次花色不同,你可以消耗1点蓄力点并选择一项:①摸一张牌。②若该牌仍在弃牌堆中,获得之。"; + } + + return "你可以将一张黑色牌当【过河拆桥】使用。
你使用【过河拆桥】结束后,若其弃置过一张牌,你记录该牌的花色。
每回合限一次,若此次花色与上次花色不同,你可以消耗1点蓄力点并选择一项:①摸一张牌。②若该牌仍在弃牌堆中,获得之。"; + }, +}; export default dynamicTranslates; diff --git a/character/images/ganning_shuying.jpg b/character/images/ganning_shuying.jpg new file mode 100644 index 0000000..361f4cf Binary files /dev/null and b/character/images/ganning_shuying.jpg differ diff --git a/character/skill.js b/character/skill.js index 0831048..a1bf01c 100644 --- a/character/skill.js +++ b/character/skill.js @@ -383,6 +383,444 @@ const skills = { }, }, }, + + qixi_shuying: { + audio: "ext:术樱包/character/audio/ganning:2", + enable: "chooseToUse", + position: "hes", + filterCard(card) { + return get.color(card) == "black"; + }, + viewAs: { + name: "guohe", + }, + viewAsFilter(player) { + return player.countCards("hes", card => get.color(card) == "black") > 0; + }, + prompt: "将一张黑色牌当【过河拆桥】使用", + check(card) { + return 5 - get.value(card); + }, + mark: true, + intro: { + content(storage, player) { + let str = storage + ? `当前记录花色:${get.translation(storage)}` + : "尚未记录花色"; + + if (player.storage.fenwei_shuying_upgraded) { + str += "
〖奋威〗已升级:摸牌与获得弃牌每回合各限一次"; + } + return str; + }, + }, + group: [ + "qixi_shuying_capture", + "qixi_shuying_effect", + ], + subSkill: { + capture: { + charlotte: true, + forced: true, + silent: true, + popup: false, + trigger: { + global: ["loseAfter", "loseAsyncAfter"], + }, + filter(event, player) { + if ( + event.type != "discard" || + event.getlx === false || + event.position != ui.discardPile + ) { + return false; + } + + const useEvent = event.getParent("useCard"); + return ( + useEvent?.player == player && + get.name(useEvent.card) == "guohe" && + event.cards?.length > 0 + ); + }, + content(event, trigger, player) { + const useEvent = trigger.getParent("useCard"); + if (!useEvent.qixi_shuying_discarded) { + useEvent.qixi_shuying_discarded = []; + } + useEvent.qixi_shuying_discarded.addArray(trigger.cards); + }, + }, + + effect: { + charlotte: true, + forced: true, + popup: false, + trigger: { + player: "useCardAfter", + }, + filter(event, player) { + return ( + get.name(event.card) == "guohe" && + event.qixi_shuying_discarded?.length > 0 + ); + }, + async content(event, trigger, player) { + const cards = trigger.qixi_shuying_discarded; + const card = cards[cards.length - 1]; + delete trigger.qixi_shuying_discarded; + + if (!card) { + return; + } + + const suit = get.suit(card, false); + const previousSuit = player.storage.qixi_shuying; + + player.storage.qixi_shuying = suit; + player.syncStorage("qixi_shuying"); + player.markSkill("qixi_shuying"); + + if ( + !previousSuit || + previousSuit == suit || + player.countCharge() <= 0 + ) { + return; + } + + const upgraded = !!player.storage.fenwei_shuying_upgraded; + const turn = game.phaseNumber; + let usage = player.storage.qixi_shuying_usage; + if (!usage || usage.turn != turn) { + usage = { + turn, + options: [], + }; + player.storage.qixi_shuying_usage = usage; + player.syncStorage("qixi_shuying_usage"); + } + + const usedDraw = usage.options.includes("draw"); + const usedGain = usage.options.includes("gain"); + const canDraw = upgraded + ? !usedDraw + : !usedDraw && !usedGain; + + const canGain = + get.position(card, true) == "d" && + (upgraded + ? !usedGain + : !usedDraw && !usedGain); + + if (!canDraw && !canGain) { + return; + } + + const controls = []; + if (canDraw) { + controls.push("摸一张牌"); + } + if (canGain) { + controls.push("获得弃置的牌"); + } + controls.push("cancel2"); + + let choice = "cancel2"; + if (canGain && get.value(card, player) > 2) { + choice = "获得弃置的牌"; + } else if (canDraw) { + choice = "摸一张牌"; + } + + const result = await player + .chooseControl(controls) + .set( + "prompt", + `〖奇袭〗记录花色由${get.translation(previousSuit)}变为${get.translation(suit)}`, + ) + .set( + "prompt2", + `你可以消耗1点蓄力点,摸一张牌或获得此次弃置的${get.translation(card)}`, + ) + .set("choice", choice) + .set("ai", () => get.event().choice) + .forResult(); + + if ( + result.control != "摸一张牌" && + result.control != "获得弃置的牌" + ) { + return; + } + + if (player.countCharge() <= 0) { + return; + } + + // 先记录具体选项,再扣除蓄力,避免扣除蓄力触发奋威升级时发生嵌套重入。 + const option = result.control == "摸一张牌" ? "draw" : "gain"; + if (!usage.options.includes(option)) { + usage.options.push(option); + } + player.syncStorage("qixi_shuying_usage"); + player.removeCharge(1); + + if (result.control == "摸一张牌") { + await player.draw(); + return; + } + + if (get.position(card, true) == "d") { + await player.gain(card, "gain2"); + } + }, + }, + }, + ai: { + order: 9, + result: { + player: 1, + }, + }, + }, + + fenwei_shuying: { + audio: "ext:术樱包/character/audio/ganning:2", + chargeSkill: 8, + beginMarkCount: 2, + trigger: { + global: "useCardToPlayered", + }, + filter(event, player) { + return ( + event.isFirstTarget && + get.type(event.card) == "trick" && + event.targets?.length >= 2 && + player.countCharge() > 0 && + !player.hasSkill("fenwei_shuying_used") + ); + }, + async cost(event, trigger, player) { + const max = Math.min( + player.countCharge(), + trigger.targets.length, + ); + + event.result = await player + .chooseTarget( + get.prompt(event.skill), + `消耗任意点蓄力点,令${get.translation(trigger.card)}对等量名目标角色无效`, + [1, max], + (card, player, target) => { + return get.event().targets.includes(target); + }, + ) + .set("targets", trigger.targets) + .set("ai", target => { + const player = get.player(); + const trigger = get.event().getTrigger(); + return -get.effect( + target, + trigger.card, + trigger.player, + player, + ); + }) + .forResult(); + }, + async content(event, trigger, player) { + const targets = event.targets; + const num = targets.length; + + player.line(targets, "green"); + player.removeCharge(num); + player.addTempSkill( + "fenwei_shuying_used", + { global: "phaseAfter" }, + ); + + trigger.getParent().excluded.addArray(targets); + }, + group: [ + "fenwei_shuying_init", + "fenwei_shuying_charge", + "fenwei_shuying_choice", + ], + subSkill: { + init: { + charlotte: true, + forced: true, + locked: false, + audio: false, + trigger: { + player: "enterGame", + global: "phaseBefore", + }, + filter(event, player) { + if (!player.countCharge(true)) { + return false; + } + return event.name != "phase" || game.phaseNumber == 0; + }, + async content(event, trigger, player) { + const num = get.info("fenwei_shuying").beginMarkCount; + player.addCharge(num); + await game.delayx(); + }, + }, + + charge: { + charlotte: true, + forced: true, + locked: true, + audio: false, + popup: false, + usable: 1, + trigger: { + global: [ + "loseAfter", + "loseAsyncAfter", + "cardsDiscardAfter", + ], + }, + filter(event, player) { + if (!player.countCharge(true)) { + return false; + } + + if (event.name.indexOf("lose") == 0) { + if ( + event.getlx === false || + event.position != ui.discardPile + ) { + return false; + } + } else { + const parent = event.getParent(); + if (parent?.relatedEvent?.name == "useCard") { + return false; + } + } + + return event.cards?.length > 0; + }, + async content(event, trigger, player) { + player.addCharge(1); + await game.delayx(); + }, + }, + + choice: { + charlotte: true, + audio: false, + trigger: { + player: "removeMark", + }, + filter(event, player) { + const num = Math.ceil(game.countPlayer() / 2); + return ( + event.markName == "charge" && + event.num >= num && + !player.hasSkill("fenwei_shuying_choice_used") && + ( + !player.storage.fenwei_shuying_upgraded || + player.hasUseTarget({ name: "guohe", isCard: true }) + ) + ); + }, + async cost(event, trigger, player) { + const num = Math.ceil(game.countPlayer() / 2); + const canUse = player.hasUseTarget({ + name: "guohe", + isCard: true, + }); + + if (player.storage.fenwei_shuying_upgraded) { + const result = await player + .chooseBool( + get.prompt(event.skill), + `你本次消耗了至少${num}点蓄力点,是否视为使用一张【过河拆桥】?`, + ) + .set("ai", () => true) + .forResult(); + + event.result = { + bool: result.bool, + cost_data: "使用过河拆桥", + }; + return; + } + + const controls = []; + if (canUse) { + controls.push("使用过河拆桥"); + } + controls.push("升级奇袭", "cancel2"); + + const result = await player + .chooseControl(controls) + .set( + "prompt", + `〖奋威〗:你本次消耗了至少${num}点蓄力点`, + ) + .set("choice", "升级奇袭") + .set("ai", () => get.event().choice) + .forResult(); + + if ( + result.control == "使用过河拆桥" || + result.control == "升级奇袭" + ) { + event.result = { + bool: true, + cost_data: result.control, + }; + } + }, + async content(event, trigger, player) { + player.addTempSkill( + "fenwei_shuying_choice_used", + { global: "phaseAfter" }, + ); + + if (event.cost_data == "升级奇袭") { + player.storage.fenwei_shuying_upgraded = true; + player.syncStorage("fenwei_shuying_upgraded"); + + game.log(player, "升级了技能", "#g【奇袭】"); + player.popup("奇袭·改", "wood"); + + if (player.hasSkill("qixi_shuying")) { + player.markSkill("qixi_shuying"); + } + + await game.delayx(); + return; + } + + if (player.hasUseTarget({ name: "guohe", isCard: true })) { + await player.chooseUseTarget({ + prompt: "〖奋威〗:视为使用一张【过河拆桥】", + card: get.autoViewAs({ name: "guohe", isCard: true }), + forced: true, + }); + } + }, + }, + + used: { + charlotte: true, + }, + choice_used: { + charlotte: true, + }, + }, + ai: { + expose: 0.2, + threaten: 1.4, + }, + }, }; export default skills; diff --git a/character/translate.js b/character/translate.js index 36e78a2..46a5692 100644 --- a/character/translate.js +++ b/character/translate.js @@ -6,6 +6,12 @@ const translates = { xinsheng_shuying_info: "若你拥有技能〖化身〗,当你受到伤害后,你可以选择一项:①获得一张新化身牌;②更换所展示的化身牌。", jihun_shuying: "羁魂", jihun_shuying_info: "若你拥有技能〖化身〗,当你更换所展示的化身牌或技能时,保留上次声明的技能,直至该技能下次触发、再次更换所展示的化身牌或技能、重铸该技能对应的化身牌。", + + ganning_shuying: "术甘宁", + qixi_shuying: "奇袭", + qixi_shuying_info: "你可以将一张黑色牌当【过河拆桥】使用。
你使用【过河拆桥】结束后,若其弃置过一张牌,你记录该牌的花色。
每回合限一次,若此次花色与上次花色不同,你可以消耗1点蓄力点并选择一项:①摸一张牌。②若该牌仍在弃牌堆中,获得之。", + fenwei_shuying: "奋威", + fenwei_shuying_info: "蓄力技(2/8)每回合各限一次:①当一名角色使用的锦囊牌指定了至少两名角色为目标时,你可以消耗任意点蓄力点并选择其中等量名目标角色,令此牌对这些角色无效。②当有牌不因使用而进入弃牌堆后,你获得1点蓄力点。③当你一次性消耗至少X点蓄力点后(X为当前存活角色数的一半,向上取整),你可以选择一项:⒈视为使用一张【过河拆桥】。⒉升级〖奇袭〗", }; export default translates; diff --git a/dist/manifest.json b/dist/manifest.json index 3e9db24..983b44a 100644 --- a/dist/manifest.json +++ b/dist/manifest.json @@ -1,9 +1,9 @@ { - "version": "2.0.1.0", - "generated_at": "2026-08-13T09:28:20.285659+00:00", + "version": "2.0.1.1", + "generated_at": "2026-08-28T19:09:32.815143+00:00", "algorithm": "sha256", - "file_count": 276, - "folder_sha256": "ab1c54fd1136e50f96b2cec7a419fd22bde8cf8df8f0e40dab842f0d4fb3aaec", + "file_count": 282, + "folder_sha256": "09e932678b899ba89b5871fde641ab1f4e49d93082802ade6437b98065eca907", "core": [ "extension.js", "menu.js", @@ -16,13 +16,80 @@ "tianshu/extension.js", "tianshu/config.js", "pve/boss/index.js", + "pve/boss/card.js", "pve/boss/character.js", - "pve/boss/skill.js", + "pve/boss/characterFilter.js", + "pve/boss/characterTitle.js", "pve/boss/dynamicTranslate.js", - "pve/boss/translate.js" + "pve/boss/intro.js", + "pve/boss/merge.js", + "pve/boss/perfectPairs.js", + "pve/boss/pinyin.js", + "pve/boss/sets.js", + "pve/boss/skill.js", + "pve/boss/sort.js", + "pve/boss/translate.js", + "pve/boss/voices.js", + "pve/boss/sets/shiershengxiao/index.js", + "pve/boss/sets/shiershengxiao/card.js", + "pve/boss/sets/shiershengxiao/character.js", + "pve/boss/sets/shiershengxiao/characterFilter.js", + "pve/boss/sets/shiershengxiao/characterTitle.js", + "pve/boss/sets/shiershengxiao/dynamicTranslate.js", + "pve/boss/sets/shiershengxiao/intro.js", + "pve/boss/sets/shiershengxiao/perfectPairs.js", + "pve/boss/sets/shiershengxiao/pinyin.js", + "pve/boss/sets/shiershengxiao/skill.js", + "pve/boss/sets/shiershengxiao/sort.js", + "pve/boss/sets/shiershengxiao/translate.js", + "pve/boss/sets/shiershengxiao/voices.js", + "pve/boss/sets/zhuoguiquxie/index.js", + "pve/boss/sets/zhuoguiquxie/card.js", + "pve/boss/sets/zhuoguiquxie/character.js", + "pve/boss/sets/zhuoguiquxie/characterFilter.js", + "pve/boss/sets/zhuoguiquxie/characterTitle.js", + "pve/boss/sets/zhuoguiquxie/dynamicTranslate.js", + "pve/boss/sets/zhuoguiquxie/intro.js", + "pve/boss/sets/zhuoguiquxie/perfectPairs.js", + "pve/boss/sets/zhuoguiquxie/pinyin.js", + "pve/boss/sets/zhuoguiquxie/skill.js", + "pve/boss/sets/zhuoguiquxie/sort.js", + "pve/boss/sets/zhuoguiquxie/translate.js", + "pve/boss/sets/zhuoguiquxie/voices.js", + "pve/boss/sets/qingqingzijin/index.js", + "pve/boss/sets/qingqingzijin/card.js", + "pve/boss/sets/qingqingzijin/character.js", + "pve/boss/sets/qingqingzijin/characterFilter.js", + "pve/boss/sets/qingqingzijin/characterTitle.js", + "pve/boss/sets/qingqingzijin/dynamicTranslate.js", + "pve/boss/sets/qingqingzijin/intro.js", + "pve/boss/sets/qingqingzijin/perfectPairs.js", + "pve/boss/sets/qingqingzijin/pinyin.js", + "pve/boss/sets/qingqingzijin/skill.js", + "pve/boss/sets/qingqingzijin/sort.js", + "pve/boss/sets/qingqingzijin/translate.js", + "pve/boss/sets/qingqingzijin/voices.js", + "pve/boss/sets/tianshu/index.js", + "pve/boss/sets/tianshu/card.js", + "pve/boss/sets/tianshu/character.js", + "pve/boss/sets/tianshu/characterFilter.js", + "pve/boss/sets/tianshu/characterTitle.js", + "pve/boss/sets/tianshu/dynamicTranslate.js", + "pve/boss/sets/tianshu/intro.js", + "pve/boss/sets/tianshu/perfectPairs.js", + "pve/boss/sets/tianshu/pinyin.js", + "pve/boss/sets/tianshu/skill.js", + "pve/boss/sets/tianshu/sort.js", + "pve/boss/sets/tianshu/translate.js", + "pve/boss/sets/tianshu/voices.js" ], "assets": [ "LICENSE", + "character/audio/ganning/fenwei_shuying1.mp3", + "character/audio/ganning/fenwei_shuying2.mp3", + "character/audio/ganning/ganning_shuying.mp3", + "character/audio/ganning/qixi_shuying1.mp3", + "character/audio/ganning/qixi_shuying2.mp3", "character/audio/zuoci/huashen_shuying1.mp3", "character/audio/zuoci/huashen_shuying2.mp3", "character/audio/zuoci/jihun_shuying1.mp3", @@ -34,6 +101,7 @@ "character/characterFilter.js", "character/characterTitle.js", "character/dynamicTranslate.js", + "character/images/ganning_shuying.jpg", "character/images/zuoci_shuying.jpg", "character/index.js", "character/intro.js", @@ -144,69 +212,7 @@ "pve/audio/skill/驱鬼逐邪/yezhong_shuying1.mp3", "pve/audio/skill/驱鬼逐邪/yunju_shuying1.mp3", "pve/audio/skill/驱鬼逐邪/zhoucha_shuying1.mp3", - "pve/boss/card.js", - "pve/boss/characterFilter.js", - "pve/boss/characterTitle.js", - "pve/boss/intro.js", - "pve/boss/merge.js", - "pve/boss/perfectPairs.js", - "pve/boss/pinyin.js", - "pve/boss/sets.js", - "pve/boss/sets/qingqingzijin/card.js", - "pve/boss/sets/qingqingzijin/character.js", - "pve/boss/sets/qingqingzijin/characterFilter.js", - "pve/boss/sets/qingqingzijin/characterTitle.js", - "pve/boss/sets/qingqingzijin/dynamicTranslate.js", - "pve/boss/sets/qingqingzijin/index.js", - "pve/boss/sets/qingqingzijin/intro.js", - "pve/boss/sets/qingqingzijin/perfectPairs.js", - "pve/boss/sets/qingqingzijin/pinyin.js", - "pve/boss/sets/qingqingzijin/skill.js", - "pve/boss/sets/qingqingzijin/sort.js", - "pve/boss/sets/qingqingzijin/translate.js", - "pve/boss/sets/qingqingzijin/voices.js", - "pve/boss/sets/shiershengxiao/card.js", - "pve/boss/sets/shiershengxiao/character.js", - "pve/boss/sets/shiershengxiao/characterFilter.js", - "pve/boss/sets/shiershengxiao/characterTitle.js", - "pve/boss/sets/shiershengxiao/dynamicTranslate.js", - "pve/boss/sets/shiershengxiao/index.js", - "pve/boss/sets/shiershengxiao/intro.js", - "pve/boss/sets/shiershengxiao/perfectPairs.js", - "pve/boss/sets/shiershengxiao/pinyin.js", - "pve/boss/sets/shiershengxiao/skill.js", - "pve/boss/sets/shiershengxiao/sort.js", - "pve/boss/sets/shiershengxiao/translate.js", - "pve/boss/sets/shiershengxiao/voices.js", - "pve/boss/sets/tianshu/card.js", - "pve/boss/sets/tianshu/character.js", - "pve/boss/sets/tianshu/characterFilter.js", - "pve/boss/sets/tianshu/characterTitle.js", - "pve/boss/sets/tianshu/dynamicTranslate.js", - "pve/boss/sets/tianshu/index.js", - "pve/boss/sets/tianshu/intro.js", - "pve/boss/sets/tianshu/perfectPairs.js", - "pve/boss/sets/tianshu/pinyin.js", - "pve/boss/sets/tianshu/skill.js", - "pve/boss/sets/tianshu/sort.js", - "pve/boss/sets/tianshu/translate.js", - "pve/boss/sets/tianshu/voices.js", - "pve/boss/sets/zhuoguiquxie/card.js", - "pve/boss/sets/zhuoguiquxie/character.js", - "pve/boss/sets/zhuoguiquxie/characterFilter.js", - "pve/boss/sets/zhuoguiquxie/characterTitle.js", - "pve/boss/sets/zhuoguiquxie/dynamicTranslate.js", - "pve/boss/sets/zhuoguiquxie/index.js", - "pve/boss/sets/zhuoguiquxie/intro.js", - "pve/boss/sets/zhuoguiquxie/perfectPairs.js", - "pve/boss/sets/zhuoguiquxie/pinyin.js", - "pve/boss/sets/zhuoguiquxie/skill.js", - "pve/boss/sets/zhuoguiquxie/sort.js", - "pve/boss/sets/zhuoguiquxie/translate.js", - "pve/boss/sets/zhuoguiquxie/voices.js", "pve/boss/shared.js", - "pve/boss/sort.js", - "pve/boss/voices.js", "pve/images/tianshu.jpg", "pve/images/十二生肖/chenlong_boss_shuying.png", "pve/images/十二生肖/chouniu_boss_shuying.png", @@ -291,6 +297,26 @@ "size": 35147, "sha256": "8ceb4b9ee5adedde47b31e975c1d90c73ad27b6b165a1dcd80c7c545eb65b903" }, + "character/audio/ganning/fenwei_shuying1.mp3": { + "size": 83302, + "sha256": "5e57217928117db9fc854f0bf225b8ea2d84e85af691091ca9777256aee16a0f" + }, + "character/audio/ganning/fenwei_shuying2.mp3": { + "size": 67942, + "sha256": "43cea55fa096bb0038fb49a446b5439086cca1c633fe717cb17ed5e81eacbb63" + }, + "character/audio/ganning/ganning_shuying.mp3": { + "size": 185062, + "sha256": "886da7d390950e409e3b8257e029288761a42dadaff23afc061ec92aa5e2de38" + }, + "character/audio/ganning/qixi_shuying1.mp3": { + "size": 61702, + "sha256": "aaa5f411e309f6274a1fda6d66e37f85850585e274a9a315054e1dac6d6bed5b" + }, + "character/audio/ganning/qixi_shuying2.mp3": { + "size": 71302, + "sha256": "8e0642a4ee1a78d2cd592d22775f4b4dbabad3803a8c77b6bfba54ab38040dbf" + }, "character/audio/zuoci/huashen_shuying1.mp3": { "size": 120863, "sha256": "6ba387a1629e1c20cb4c1cb654a99d9574d78a521f3f6bc93191dbf11e9985cc" @@ -320,8 +346,8 @@ "sha256": "f330d2665fd2c9bb85cd4a80f596f44a283c715333a1bf8aafffa5ef030c5126" }, "character/character.js": { - "size": 360, - "sha256": "749cb31ca7b2f32f87f5a290b28009f14644d9b0f1a03a01530af1d12265a9da" + "size": 653, + "sha256": "6cbd7ec49ec9b9ec362f5dadb4251c219ecd468a099a082f17da86c61e0823f2" }, "character/characterFilter.js": { "size": 63, @@ -332,8 +358,12 @@ "sha256": "f4be860d37d46261a6d45e30bce661d301d79d548aeef0bfc2e8ad67fe783cea" }, "character/dynamicTranslate.js": { - "size": 65, - "sha256": "d409295767b35e4867e5aa6fae92020f2a8943f71569af5539436648a345bd2f" + "size": 872, + "sha256": "63a56c336e881b15e08374656863b67b7465eedf6af683f8bade3d33b12f2704" + }, + "character/images/ganning_shuying.jpg": { + "size": 1580990, + "sha256": "4ca2a65ab001b4247ba4fe0139a854eff1849c044ab87d50c1ea3193791d3747" }, "character/images/zuoci_shuying.jpg": { "size": 222468, @@ -352,16 +382,16 @@ "sha256": "7f2bd697eaaff284d2b356321a402a3cc7c0139ff6fdf339f6cbdd8da208a05f" }, "character/skill.js": { - "size": 15439, - "sha256": "ad6859ff3ed9e05e67b991f85961b85b21af4d486a76660d29305e962120bb94" + "size": 31285, + "sha256": "608ad1b6463bda8318b91f729dce0cdf98a223d0a72fd1fac35cf32b49e69c08" }, "character/sort.js": { "size": 112, "sha256": "42cf6bb834aca4ff883aa196604f3d9d2d9047bb320cf56a633aae4ecbb5ce4c" }, "character/translate.js": { - "size": 1126, - "sha256": "dd6e076e058514a11b5912c0893474494af825bc646e01e12c514296dc1390ab" + "size": 2091, + "sha256": "2a31d41939f1d636f9c1b2378d0f15cf7aa5c9e2caa35b465066deb4f877e4bb" }, "character/voices.js": { "size": 43, @@ -373,7 +403,7 @@ }, "extension.js": { "size": 14743, - "sha256": "3dae019a7941072f2c8b67ce14f697f9d4aa0a8bebc4187e913da5468bd88955" + "sha256": "16cbdcc2f8e2cdfab03a0fb6f80de865aa709d7058dbbbbcd5d3ccd523907cd2" }, "function.js": { "size": 33746, @@ -396,8 +426,8 @@ "sha256": "fdce7bb71f7290254d984a35415e5012f829ae9992765d1f3282f35528c1b92b" }, "menu.js": { - "size": 22310, - "sha256": "3a603a82bb80897cf809ba80ebd55bbf66634cdbe807ed33ad2e43d845d48211" + "size": 22235, + "sha256": "0920561e2b0214239dc81b1a54c955b96ea0750e57fa42f3b0e8935035c6fc84" }, "pve/audio/dead/十二生肖/chenlong.mp3": { "size": 68757, @@ -1040,16 +1070,16 @@ "sha256": "4a8648524425c2edfb8595971476398ff3c513d27ff4850895c0839945b5c1c6" }, "pve/boss/sets/zhuoguiquxie/skill.js": { - "size": 43229, - "sha256": "81c625e3f4c1ec77dea2556dc391182e241811b84d92dbfa4b72ebb50b604817" + "size": 46766, + "sha256": "406b8bc5e8db6010650e7e4674c5a39f54e27374e4ae358d988f28a2b6e04629" }, "pve/boss/sets/zhuoguiquxie/sort.js": { "size": 390, "sha256": "3855904cdc28e6301b9f9177b6c8e04d943ef44fd002a2ce83de5aab7572930c" }, "pve/boss/sets/zhuoguiquxie/translate.js": { - "size": 6787, - "sha256": "e714d7facef185ca0977521db78f327622a902c9b0659293d180c59f66179627" + "size": 6820, + "sha256": "9f20e9429faab842c5a2c3e94d94595cc2480e78752fff3e7f5ac54d09161c1b" }, "pve/boss/sets/zhuoguiquxie/voices.js": { "size": 2115, @@ -1272,16 +1302,16 @@ "sha256": "15adcfb80bd22035c604703005ae22d1721eeafcc0a1ee09c89c73713cd3d03a" }, "tianshu/bondSkills.js": { - "size": 40151, - "sha256": "c2a7925e56b9baeb8812e8762e8dc2b471908c9d2faf9a0b3ebb3f7792afacb4" + "size": 41604, + "sha256": "13fcda5805a7643144c55ae922f2406db2cba6e9822e086f4763f16063763e01" }, "tianshu/config.js": { - "size": 29131, - "sha256": "5f23724ecad5141ed98674919282ec8fd53e666f23adb6f097dd14be466402ed" + "size": 29707, + "sha256": "9b331adca63ff4b43dc06f707c3bd728d3ce0269b1c1f4cedec5e42281f13ff0" }, "tianshu/extension.js": { - "size": 64398, - "sha256": "3e6934e7281bf84e40fd28ea12bee02272573ee1dbba9421c45823677e45021c" + "size": 70478, + "sha256": "ff88e89a4067f18e10d1fbce72edc45aa89c95ee59e127520f5b2bed93023b98" }, "tianshu/shuYing_Skill_Xvxiang1.mp3": { "size": 15451, @@ -1292,8 +1322,8 @@ "sha256": "7594c68dd1308de5fd13ea7e76fde665a296400a7505b32054bf0d149986bb05" }, "update.txt": { - "size": 2901, - "sha256": "6ce45269f253f8fb42665ba3fc14a1ac8a5e48d60b9cb5c6fe5beccb9336cc2f" + "size": 3953, + "sha256": "a81636c5b7c6dcf67b2e7b88b87668f3bb9626d974249c6e5413015cd1d13a21" }, "voices.js": { "size": 1711, @@ -1407,6 +1437,36 @@ "audio": { "type": "directory", "children": { + "ganning": { + "type": "directory", + "children": { + "fenwei_shuying1.mp3": { + "type": "file", + "size": 83302, + "sha256": "5e57217928117db9fc854f0bf225b8ea2d84e85af691091ca9777256aee16a0f" + }, + "fenwei_shuying2.mp3": { + "type": "file", + "size": 67942, + "sha256": "43cea55fa096bb0038fb49a446b5439086cca1c633fe717cb17ed5e81eacbb63" + }, + "ganning_shuying.mp3": { + "type": "file", + "size": 185062, + "sha256": "886da7d390950e409e3b8257e029288761a42dadaff23afc061ec92aa5e2de38" + }, + "qixi_shuying1.mp3": { + "type": "file", + "size": 61702, + "sha256": "aaa5f411e309f6274a1fda6d66e37f85850585e274a9a315054e1dac6d6bed5b" + }, + "qixi_shuying2.mp3": { + "type": "file", + "size": 71302, + "sha256": "8e0642a4ee1a78d2cd592d22775f4b4dbabad3803a8c77b6bfba54ab38040dbf" + } + } + }, "zuoci": { "type": "directory", "children": { @@ -1451,8 +1511,8 @@ }, "character.js": { "type": "file", - "size": 360, - "sha256": "749cb31ca7b2f32f87f5a290b28009f14644d9b0f1a03a01530af1d12265a9da" + "size": 653, + "sha256": "6cbd7ec49ec9b9ec362f5dadb4251c219ecd468a099a082f17da86c61e0823f2" }, "characterFilter.js": { "type": "file", @@ -1466,12 +1526,17 @@ }, "dynamicTranslate.js": { "type": "file", - "size": 65, - "sha256": "d409295767b35e4867e5aa6fae92020f2a8943f71569af5539436648a345bd2f" + "size": 872, + "sha256": "63a56c336e881b15e08374656863b67b7465eedf6af683f8bade3d33b12f2704" }, "images": { "type": "directory", "children": { + "ganning_shuying.jpg": { + "type": "file", + "size": 1580990, + "sha256": "4ca2a65ab001b4247ba4fe0139a854eff1849c044ab87d50c1ea3193791d3747" + }, "zuoci_shuying.jpg": { "type": "file", "size": 222468, @@ -1496,8 +1561,8 @@ }, "skill.js": { "type": "file", - "size": 15439, - "sha256": "ad6859ff3ed9e05e67b991f85961b85b21af4d486a76660d29305e962120bb94" + "size": 31285, + "sha256": "608ad1b6463bda8318b91f729dce0cdf98a223d0a72fd1fac35cf32b49e69c08" }, "sort.js": { "type": "file", @@ -1506,8 +1571,8 @@ }, "translate.js": { "type": "file", - "size": 1126, - "sha256": "dd6e076e058514a11b5912c0893474494af825bc646e01e12c514296dc1390ab" + "size": 2091, + "sha256": "2a31d41939f1d636f9c1b2378d0f15cf7aa5c9e2caa35b465066deb4f877e4bb" }, "voices.js": { "type": "file", @@ -1524,7 +1589,7 @@ "extension.js": { "type": "file", "size": 14743, - "sha256": "3dae019a7941072f2c8b67ce14f697f9d4aa0a8bebc4187e913da5468bd88955" + "sha256": "16cbdcc2f8e2cdfab03a0fb6f80de865aa709d7058dbbbbcd5d3ccd523907cd2" }, "function.js": { "type": "file", @@ -1558,8 +1623,8 @@ }, "menu.js": { "type": "file", - "size": 22310, - "sha256": "3a603a82bb80897cf809ba80ebd55bbf66634cdbe807ed33ad2e43d845d48211" + "size": 22235, + "sha256": "0920561e2b0214239dc81b1a54c955b96ea0750e57fa42f3b0e8935035c6fc84" }, "pve": { "type": "directory", @@ -2445,8 +2510,8 @@ }, "skill.js": { "type": "file", - "size": 43229, - "sha256": "81c625e3f4c1ec77dea2556dc391182e241811b84d92dbfa4b72ebb50b604817" + "size": 46766, + "sha256": "406b8bc5e8db6010650e7e4674c5a39f54e27374e4ae358d988f28a2b6e04629" }, "sort.js": { "type": "file", @@ -2455,8 +2520,8 @@ }, "translate.js": { "type": "file", - "size": 6787, - "sha256": "e714d7facef185ca0977521db78f327622a902c9b0659293d180c59f66179627" + "size": 6820, + "sha256": "9f20e9429faab842c5a2c3e94d94595cc2480e78752fff3e7f5ac54d09161c1b" }, "voices.js": { "type": "file", @@ -2781,18 +2846,18 @@ "children": { "bondSkills.js": { "type": "file", - "size": 40151, - "sha256": "c2a7925e56b9baeb8812e8762e8dc2b471908c9d2faf9a0b3ebb3f7792afacb4" + "size": 41604, + "sha256": "13fcda5805a7643144c55ae922f2406db2cba6e9822e086f4763f16063763e01" }, "config.js": { "type": "file", - "size": 29131, - "sha256": "5f23724ecad5141ed98674919282ec8fd53e666f23adb6f097dd14be466402ed" + "size": 29707, + "sha256": "9b331adca63ff4b43dc06f707c3bd728d3ce0269b1c1f4cedec5e42281f13ff0" }, "extension.js": { "type": "file", - "size": 64398, - "sha256": "3e6934e7281bf84e40fd28ea12bee02272573ee1dbba9421c45823677e45021c" + "size": 70478, + "sha256": "ff88e89a4067f18e10d1fbce72edc45aa89c95ee59e127520f5b2bed93023b98" }, "shuYing_Skill_Xvxiang1.mp3": { "type": "file", @@ -2808,8 +2873,8 @@ }, "update.txt": { "type": "file", - "size": 2901, - "sha256": "6ce45269f253f8fb42665ba3fc14a1ac8a5e48d60b9cb5c6fe5beccb9336cc2f" + "size": 3953, + "sha256": "a81636c5b7c6dcf67b2e7b88b87668f3bb9626d974249c6e5413015cd1d13a21" }, "voices.js": { "type": "file", diff --git a/extension.js b/extension.js index d193ee1..95c674b 100644 --- a/extension.js +++ b/extension.js @@ -5,7 +5,7 @@ import initShuYingMenu from "./menu.js"; export const type = "extension"; export let shuYing = new Object(); -const shuYingLocalVersion = "2.0.1.0"; +const shuYingLocalVersion = "2.0.1.1"; shuYing.name = "术樱包"; shuYing.editable = false; diff --git a/menu.js b/menu.js index 13a3fd4..ed00fd3 100644 --- a/menu.js +++ b/menu.js @@ -218,27 +218,16 @@ export default function initShuYingMenu({ lib, game, ui, shuYing, updater }) { } const logs = [ - "-----< 2.0.1.0 改动 >-----", - "重构PVE武将包目录,按照不同活动BOSS进行独立分包和统一加载", - "新增独立PVP武将包结构,并加入术樱左慈及其技能、图片和配音资源", - "天书乱斗新增动态难度BOSS,不同难度将使用准确的体力上限和追加技能", - "天书乱斗新增羁绊系统,根据玩家过关选择的技能激活并替换不同等级效果", - "羁绊条件支持技能ID、技能译名、描述关键词以及武将名称关键词组合", - "新增羁绊谱册和局内已激活羁绊查看功能", - "重铸过关技能选择界面,新增六个技能选项、确认按钮和单次刷新功能", - "新增可配置的额外技能奖励池,过关时与武将技能池同步检索", - "新增天书过关技能选择记录,羁绊仅统计玩家通过过关奖励取得且当前仍持有的技能", - "统一活动BOSS武将包入口,并补充十二生肖、青青子衿和捉鬼驱邪相关内容及配音资源", - "自定义界面现已兼容十周年UI、原生UI以及移动端显示", - "-----< 2.0.1.0 修复 >-----", - "修复天书动态BOSS体力、技能、武将复制和玩家变身识别异常的问题", - "修复天书武将包被普通武将列表识别或无法被挑战模式加载的问题", - "修复PVE分包后技能台词无法正确识别的问题", - "修复羁绊中多个同译名技能会被重复计算的问题", - "修复羁绊谱册和局内羁绊列表在部分分辨率下重叠或标题越界的问题", - "修复过关后行动顺序异常,当前角色回合结束后将行动权交给其下家", - "调整天书开局保护的伤害结算顺序,优先结算其他免伤和减伤效果", - "修复生成更新清单时会覆盖原有remove和removeDirectories内容的问题", + "-----< 2.0.1.1 改动 >-----", + "新增“旧轮次”选项,可在过关后按照旧规则将行动权交给玩家方1号位上家的Boss", + "新增“羁绊技能倾向”选项,已激活羁绊的相关技能将按羁绊等级获得温和的出现概率提升", + "羁绊技能倾向权重现可在天书配置中按等级独立调整", + "新增“虚拟偶像参与奖励”选项,开启后虚拟偶像可以选择过关技能并激活、升级羁绊效果", + "-----< 2.0.1.1 修复 >-----", + "修复多次触发青龙偃月刀追加【杀】并击杀Boss后,过关奖励结束时无法正确跳过当前角色剩余回合的问题", + "修复旧轮次在新Boss召唤前提前判断座位,导致行动权交接条件无法正常触发的问题", + "修复当前行动角色为虚拟偶像时,旧轮次无法将行动权交给玩家方1号位上家Boss的问题", + "修复“南蛮入侵”和“群雄逐鹿”羁绊虽然对友方无效,但AI仍会将友方计入用牌收益而拒绝使用相关牌的问题", ]; const keywordColors = { BOSS: "#ff6b6b", @@ -349,12 +338,18 @@ export default function initShuYingMenu({ lib, game, ui, shuYing, updater }) { menu.tianShu_bonds = { name: "查看羁绊谱册", clear: true, - intro: "同步读取天书配置,展示当前可激活的羁绊、等级条件和奖励效果。", + intro: "同步读取天书配置,展示当前可激活的羁绊、等级条件和奖励效果", onclick() { openTianshuBondDialog(); }, }; + menu.tianShu_bondPreference = { + name: "羁绊技能倾向", + init: false, + intro: "开启后,过关选择技能时会小幅提高当前已激活羁绊相关技能的出现概率,羁绊等级越高增幅越高", + }; + menu.giveAiSkill = { name: "单人控制AI技能", init: false, @@ -378,6 +373,12 @@ export default function initShuYingMenu({ lib, game, ui, shuYing, updater }) { }, }; + menu.tianShu_XvniReward = { + name: "虚拟偶像参与奖励", + init: false, + intro: "开启后,虚拟偶像会参与每次过关技能选择,并可通过取得的过关技能激活和升级羁绊效果。", + }; + const virtualIdols = [ { key: "Xiaojiu", id: "vtb_xiaojiu", name: "小酒" }, { key: "Xiaosha", id: "vtb_xiaosha", name: "小杀" }, @@ -429,6 +430,12 @@ export default function initShuYingMenu({ lib, game, ui, shuYing, updater }) { intro: "开启后,进入下一关前复活玩家方已阵亡的角色。虚拟偶像不会因此复活。", }; + menu.tianShu_legacyRound = { + name: "旧轮次", + init: false, + intro: "开启后,玩家方角色(含虚拟偶像)的下家为Boss或虚拟偶像时,新关由玩家方1号位上家的Boss开始行动;关闭时始终由当前角色的实际下家行动。", + }; + menu.tianShu_addBoss = { name: "增加Boss", init: false, diff --git a/pve/boss/sets/zhuoguiquxie/skill.js b/pve/boss/sets/zhuoguiquxie/skill.js index 1369447..88a761d 100644 --- a/pve/boss/sets/zhuoguiquxie/skill.js +++ b/pve/boss/sets/zhuoguiquxie/skill.js @@ -50,15 +50,26 @@ const skills = { skills = [skills.randomGet()]; } - player.line(target); - target.disableSkill(disableKey, skills); - player.storage.aotang_shuying_disabled.push({ - target, - disableKey, - }); + player.line(target); + target.disableSkill(disableKey, skills); + player.storage.aotang_shuying_disabled.push({ + target, + disableKey, + }); - game.log( - target, + if (!Array.isArray(target.storage.aotang_shuying_mark)) { + target.storage.aotang_shuying_mark = []; + } + target.storage.aotang_shuying_mark.push({ + source: player, + disableKey, + skills: skills.slice(), + }); + target.addSkill("aotang_shuying_mark"); + target.markSkill("aotang_shuying_mark"); + + game.log( + target, "暂时遗忘了技能", `#g${get.translation(skills)}` ); @@ -84,15 +95,50 @@ const skills = { async content(event, trigger, player) { player.removeSkill("aotang_shuying_clear"); }, - onremove(player) { - for (const record of player.getStorage("aotang_shuying_disabled")) { - record.target?.enableSkill(record.disableKey); - } - delete player.storage.aotang_shuying_disabled; - }, - }, - }, - }, + onremove(player) { + for (const record of player.getStorage("aotang_shuying_disabled")) { + const target = record.target; + if (!target) continue; + + target.enableSkill(record.disableKey); + const records = target + .getStorage("aotang_shuying_mark") + .filter(current => current.disableKey != record.disableKey); + if (records.length) { + target.storage.aotang_shuying_mark = records; + target.markSkill("aotang_shuying_mark"); + } else { + delete target.storage.aotang_shuying_mark; + target.removeSkill("aotang_shuying_mark"); + } + } + delete player.storage.aotang_shuying_disabled; + }, + }, + mark: { + charlotte: true, + mark: true, + marktext: "忘", + intro: { + content(storage, player) { + const records = player.getStorage("aotang_shuying_mark"); + if (!records.length) return "没有因【熬汤】遗忘技能"; + + return records + .map(record => { + const source = record.source + ? get.translation(record.source) + : "未知角色"; + return `${source}令你暂时遗忘:${get.translation( + record.skills + )}`; + }) + .join("
"); + }, + }, + }, + }, + }, yunju_shuying: { mode: ["boss"], audio: "ext:术樱包/pve/audio/skill/驱鬼逐邪:1", diff --git a/pve/boss/sets/zhuoguiquxie/translate.js b/pve/boss/sets/zhuoguiquxie/translate.js index 489edc0..22e4099 100644 --- a/pve/boss/sets/zhuoguiquxie/translate.js +++ b/pve/boss/sets/zhuoguiquxie/translate.js @@ -2,6 +2,7 @@ const translates = { mengpo_boss_shuying: "孟婆", aotang_shuying: "熬汤", aotang_shuying_info: "锁定技,你的回合开始时,令随机一名敌方角色随机遗忘1个武将技能直到你的下回合开始。", + aotang_shuying_mark: "忘技", yunju_shuying: "云飓", yunju_shuying_info: "锁定技,敌方角色的回合结束时,该角色随机弃置1张手牌。", guimei_shuying: "鬼魅", diff --git a/tianshu/bondSkills.js b/tianshu/bondSkills.js index 016dab7..57724ac 100644 --- a/tianshu/bondSkills.js +++ b/tianshu/bondSkills.js @@ -553,6 +553,16 @@ const skill = { "shuYing_Tianshu_Skill_06_Skill1_damage", "shuYing_Tianshu_Skill_06_Skill1_exclude", ], + mod: { + targetEnabled(card, player, target) { + if ( + get.name(card, player) == "nanman" && + !player.getEnemies(null, false).includes(target) + ) { + return false; + } + }, + }, subSkill: { damage: { audio: false, @@ -609,6 +619,14 @@ const skill = { return "nanman"; } }, + targetEnabled(card, player, target) { + if ( + get.name(card, player) == "nanman" && + !player.getEnemies(null, false).includes(target) + ) { + return false; + } + }, }, subSkill: { damage: { @@ -664,6 +682,14 @@ const skill = { cardname(card, player) { if (get.position(card) == "h") return "nanman"; }, + targetEnabled(card, player, target) { + if ( + get.name(card, player) == "nanman" && + !player.getEnemies(null, false).includes(target) + ) { + return false; + } + }, }, subSkill: { damage: { @@ -1030,6 +1056,14 @@ const skill = { return "wanjian"; } }, + targetEnabled(card, player, target) { + if ( + get.name(card, player) == "wanjian" && + !player.getEnemies(null, false).includes(target) + ) { + return false; + } + }, }, subSkill: { exclude: { @@ -1069,6 +1103,14 @@ const skill = { return "wanjian"; } }, + targetEnabled(card, player, target) { + if ( + get.name(card, player) == "wanjian" && + !player.getEnemies(null, false).includes(target) + ) { + return false; + } + }, }, subSkill: { exclude: { diff --git a/tianshu/config.js b/tianshu/config.js index 859aa5e..f0400e5 100644 --- a/tianshu/config.js +++ b/tianshu/config.js @@ -172,13 +172,25 @@ const tianshuConfig = { stageRecover: 1, stageDraw: 2, skillChoiceCount: 6, + bondPreference: { + // 普通技能基础权重为1;这里只填写各羁绊实际等级对应的总权重。 + levelWeights: { + 1: 1.50, + 2: 1.75, + 3: 2.25, + }, + excludedBondIds: ["shafa"], + }, }, settings: { virtualIdolConfigKey: "extension_术樱包_tianShu_Xvni", virtualIdolRandomPoolConfigKey: "extension_术樱包_tianShu_XvniRandomPool", virtualIdolReviveConfigKey: "extension_术樱包_tianShu_dead", + virtualIdolRewardConfigKey: "extension_术樱包_tianShu_XvniReward", addBossConfigKey: "extension_术樱包_tianShu_addBoss", revivePlayersConfigKey: "extension_术樱包_tianShu_revivePlayers", + legacyRoundConfigKey: "extension_术樱包_tianShu_legacyRound", + bondPreferenceConfigKey: "extension_术樱包_tianShu_bondPreference", difficultyStatusKey: "shuYing_tianshuDifficulty", }, skillPool: { @@ -194,7 +206,6 @@ const tianshuConfig = { "shencai", "wuling", "reshuishi", "mbtiantao", "mbxinghun", "qiexie", "yuli", "tingwei", //神武再世 "nzry_shicai", "remingce", "mizhao", "xinfu_tushe", "rejigong", //群雄逐鹿 "xiaoji", "liangzhu", "mozhi", "olzhenlie", "zhiren", //乱世佳人 - ], banned: ["huoxin", "jueqing", "qinqing", "beige", "huashen", "drlt_zhiti", "olzhiti", "xinfu_pdgyingshi", "rebeige"], }, diff --git a/tianshu/extension.js b/tianshu/extension.js index d489498..ee5f1b9 100644 --- a/tianshu/extension.js +++ b/tianshu/extension.js @@ -71,6 +71,8 @@ export default function initTianshu(lib, game, ui, get, ai, _status, shuYing) { const getVirtualIdolConfig = () => lib.config[tianshuConfig.settings.virtualIdolConfigKey] || "random"; // 判断是否启用虚拟偶像。 const isVirtualIdolEnabled = () => getVirtualIdolConfig() != "off"; + // 判断虚拟偶像是否参与过关技能选择和羁绊系统。 + const canVirtualIdolReceiveRewards = () => Boolean(lib.config[tianshuConfig.settings.virtualIdolRewardConfigKey]); // 读取虚拟偶像随机池;仅在菜单选择“随机”时用于筛选可随机角色。 const getVirtualIdolRandomPool = () => { const list = lib.config[tianshuConfig.settings.virtualIdolRandomPoolConfigKey]; @@ -190,8 +192,9 @@ export default function initTianshu(lib, game, ui, get, ai, _status, shuYing) { if (phase) { if (typeof game.resetSkills == "function") game.resetSkills(); let current = _status.event; - let guard = 20; - while (current && current != phase && guard-- > 0) { + const visited = new Set(); + while (current && current != phase && !visited.has(current)) { + visited.add(current); current.finish(); current.untrigger(true); current = current.getParent ? current.getParent() : current.parent; @@ -217,6 +220,31 @@ export default function initTianshu(lib, game, ui, get, ai, _status, shuYing) { }); } }; + // 新 Boss 入场并重排座位后,若当前行动者属于玩家方(含虚拟偶像)且实际下家为 Boss 或虚拟偶像,则启用旧轮次。 + const shouldUseLegacyBossTurn = current => { + if (!lib.config[tianshuConfig.settings.legacyRoundConfigKey]) return false; + if (!current || !current.isIn?.() || !isCapturedPlayerSide(current)) return false; + const next = current.next || current.getNext?.(); + return Boolean(next && (isCurrentStageBoss(next) || isVirtualIdol(next))); + }; + // 从玩家方1号位沿上家方向查找当前关 Boss,作为旧轮次的行动者。 + const getLegacyBossTurnPlayer = () => { + const roundStart = getTianshuRoundStartPlayer(); + const getPrevious = player => game.players.includes(player) ? player.previous : player?.previousSeat; + let previous = getPrevious(roundStart); + const visited = new Set(); + while (previous && previous != roundStart && !visited.has(previous)) { + if (isCurrentStageBoss(previous)) return previous; + visited.add(previous); + previous = getPrevious(previous); + } + return null; + }; + const getNextStageTurnPlayer = current => { + const next = current?.next || null; + if (!shouldUseLegacyBossTurn(current)) return next; + return getLegacyBossTurnPlayer() || next; + }; // 开局捕获玩家方成员,避免后续 game.boss 变化导致阵营误判。 const capturePlayerSide = () => { state.playerSideIds = {}; @@ -614,7 +642,7 @@ export default function initTianshu(lib, game, ui, get, ai, _status, shuYing) { const getRewardSkillChooser = player => { const main = getMainControlPlayer(); const underControl = Boolean(main && typeof player.isUnderControl == "function" && player.isUnderControl(true, main)); - const tianshuSingleControl = Boolean(main && !_status.connectMode && lib.config.mode == "boss" && isCapturedPlayerSide(player) && !isVirtualIdol(player)); + const tianshuSingleControl = Boolean(main && !_status.connectMode && lib.config.mode == "boss" && isCapturedPlayerSide(player)); let chooser = player; if (lib.config.extension_术樱包_giveAiSkill && main && player != main && (underControl || tianshuSingleControl)) chooser = main; return chooser; @@ -712,6 +740,36 @@ export default function initTianshu(lib, game, ui, get, ai, _status, shuYing) { } return null; }; + // 判断候选技能是否属于某项羁绊条件;组合条件只提取正向技能要求,not 不参与概率提升。 + const skillMatchesBondCondition = (player, skill, condition) => { + if (!condition || typeof condition != "object" || !skill) return false; + if (!condition.type && Array.isArray(condition.all)) return condition.all.some(item => skillMatchesBondCondition(player, skill, item)); + if (!condition.type && Array.isArray(condition.any)) return condition.any.some(item => skillMatchesBondCondition(player, skill, item)); + if (!condition.type && condition.not) return false; + + const skillName = lib.translate[skill] || ""; + if (condition.type == "keyword") { + if (!condition.keyword) return false; + const info = typeof get.skillInfoTranslation == "function" ? get.skillInfoTranslation(skill, player) : lib.translate[`${skill}_info`]; + return String(info || "").includes(`【${condition.keyword}】`); + } + if (condition.type == "skill") return condition.skill == skill; + if (condition.type == "skillName") return condition.name == skillName; + if (condition.type == "skills") { + return [...(condition.all || []), ...(condition.any || [])].includes(skill); + } + if (condition.type == "skillNames") { + return [...(condition.all || []), ...(condition.any || [])].includes(skillName); + } + if (condition.type == "characterSkillNames") { + const characters = Array.from(new Set( + (Array.isArray(condition.characters) ? condition.characters : [condition.character]).filter(Boolean) + )); + if (characters.length && !characters.some(keyword => getBondCharacterNames(player).some(name => name.includes(keyword)))) return false; + return [...(condition.all || []), ...(condition.any || [])].includes(skillName); + } + return false; + }; // 只选取条件成立且等级最高的一档,低等级效果不会与高等级叠加。 const getActiveBondLevel = (player, bond) => { let active = null; @@ -744,7 +802,7 @@ export default function initTianshu(lib, game, ui, get, ai, _status, shuYing) { createNode("h2", "shuYing-bond-overview-title", "已激活羁绊", dialog); const content = createNode("div", "shuYing-bond-overview-content", null, dialog); let total = 0; - for (const current of getPlayerSidePlayers({ aliveOnly: false, includeVirtual: false })) { + for (const current of getPlayerSidePlayers({ aliveOnly: false, includeVirtual: canVirtualIdolReceiveRewards() })) { const levels = current.storage?.shuYing_tianshu_bondLevels || {}; const activeBonds = (tianshuConfig.bonds || []).map(bond => { const rank = Number(levels[bond.id]) || 0; @@ -822,7 +880,7 @@ export default function initTianshu(lib, game, ui, get, ai, _status, shuYing) { }; // 依据当前真实技能所有权重算一名玩家的全部羁绊来源和隐藏等级缓存。 const refreshTianshuBonds = player => { - if (!state.running || !player || !isCapturedPlayerSide(player) || isVirtualIdol(player) || bondSyncingPlayers.has(player)) return; + if (!state.running || !player || !isCapturedPlayerSide(player) || (isVirtualIdol(player) && !canVirtualIdolReceiveRewards()) || bondSyncingPlayers.has(player)) return; bondSyncingPlayers.add(player); try { delete player.storage.shuYing_tianshu_keywordBondLevels; @@ -865,13 +923,53 @@ export default function initTianshu(lib, game, ui, get, ai, _status, shuYing) { }; (lib.hooks.removeSkillCheck ||= []).push(hook); }; - // 从当前奖励池随机生成候选;刷新时优先排除上一组技能。 + // 在一次候选生成前缓存当前有效倾向,避免对奖励池里的每个技能重复计算羁绊等级。 + const getActiveBondPreferences = player => { + const preference = tianshuConfig.reward.bondPreference || {}; + if (!lib.config[tianshuConfig.settings.bondPreferenceConfigKey]) return []; + const levelWeights = preference.levelWeights || {}; + const excludedBondIds = new Set(preference.excludedBondIds || []); + const activePreferences = []; + for (const bond of tianshuConfig.bonds || []) { + if (!bond?.id || excludedBondIds.has(bond.id)) continue; + const active = getActiveBondLevel(player, bond); + if (!active) continue; + // 只读取该羁绊实际激活等级的配置;没有对应项时不增加权重,也不会向更高等级回退。 + const weight = Math.max(1, Number(levelWeights[active.rank]) || 1); + if (weight <= 1) continue; + const conditions = [bond.condition, ...(bond.levels || []).map(level => getBondLevelCondition(bond, level))].filter(Boolean); + activePreferences.push({ bonusWeight: weight - 1, conditions }); + } + return activePreferences; + }; + // 计算候选技能权重;同一技能关联多个有效羁绊时,各项温和增幅可以叠加。 + const getRewardSkillWeight = (player, skill, activePreferences) => { + let bonus = 0; + for (const preference of activePreferences) { + if (preference.conditions.some(condition => skillMatchesBondCondition(player, skill, condition))) { + bonus += preference.bonusWeight; + } + } + return 1 + bonus; + }; + const weightedRemoveRewardSkill = (pool, player, activePreferences) => { + const weights = pool.map(skill => getRewardSkillWeight(player, skill, activePreferences)); + const total = weights.reduce((sum, weight) => sum + weight, 0); + let cursor = Math.random() * total; + for (let index = 0; index < pool.length; index++) { + cursor -= weights[index]; + if (cursor <= 0) return pool.splice(index, 1)[0]; + } + return pool.pop(); + }; + // 从当前奖励池按羁绊倾向权重生成候选;刷新时优先排除上一组技能。 const createRewardSkillChoices = (player, excluded = []) => { const excludedSet = new Set(excluded); const pool = getAllSkillPool(player).filter(skill => !excludedSet.has(skill)); + const activePreferences = getActiveBondPreferences(player); const choices = []; while (choices.length < tianshuConfig.reward.skillChoiceCount && pool.length) { - const choice = pool.randomRemove(); + const choice = weightedRemoveRewardSkill(pool, player, activePreferences); if (choice) choices.push(choice); } return choices; @@ -894,7 +992,7 @@ export default function initTianshu(lib, game, ui, get, ai, _status, shuYing) { }; // 过关后让每个存活玩家方角色依次选择奖励技能。 const chooseSkillsForStageClear = async () => { - for (const player of getPlayerSidePlayers({ aliveOnly: true, includeVirtual: false })) { + for (const player of getPlayerSidePlayers({ aliveOnly: true, includeVirtual: canVirtualIdolReceiveRewards() })) { let choices = createRewardSkillChoices(player); if (!choices.length) continue; let refreshed = false; @@ -946,13 +1044,14 @@ export default function initTianshu(lib, game, ui, get, ai, _status, shuYing) { if (tianshuConfig.reward.stageDraw) await player.draw(tianshuConfig.reward.stageDraw); } await chooseSkillsForStageClear(); + const currentTurnPlayer = getParentEvent("phase")?.player || _status.currentPhase; await game.washCard(); hideCurrentStageBossCorpses(); - const currentTurnPlayer = getParentEvent("phase")?.player || _status.currentPhase; state.stageIndex++; await spawnStage(state.stageIndex); - // 过关后立即结束当前回合,并将新关的首个行动权交给当前角色的实际下家。 - if (currentTurnPlayer?.next) finishCurrentPhaseTo(currentTurnPlayer.next); + // 必须在新 Boss 召唤并完成座位重排后,再按新座位链决定控制权归属。 + const nextTurnPlayer = getNextStageTurnPlayer(currentTurnPlayer); + if (nextTurnPlayer) finishCurrentPhaseTo(nextTurnPlayer); }; // 处理 Boss 死亡事件:击杀奖励、隐藏尸体、判断是否清关。 const handleBossDie = async event => { @@ -1086,12 +1185,13 @@ export default function initTianshu(lib, game, ui, get, ai, _status, shuYing) { charlotte: true, firstDo: true, filter(event) { - if (!state.running || !event.player || !isCapturedPlayerSide(event.player) || isVirtualIdol(event.player)) return false; + if (!state.running || !event.player || !isCapturedPlayerSide(event.player)) return false; + if (isVirtualIdol(event.player) && !canVirtualIdolReceiveRewards()) return false; const removed = Array.isArray(event.removeSkill) ? event.removeSkill : []; const history = getTianshuRewardSkillHistory(event.player); return removed.some(skill => history.includes(skill)); }, - content(event, trigger) { + async content(event, trigger) { refreshTianshuBonds(trigger.player); }, }, diff --git a/tools/manifest_config.json b/tools/manifest_config.json index 02fac48..fa39bb8 100644 --- a/tools/manifest_config.json +++ b/tools/manifest_config.json @@ -11,10 +11,72 @@ "tianshu/extension.js", "tianshu/config.js", "pve/boss/index.js", + "pve/boss/card.js", "pve/boss/character.js", - "pve/boss/skill.js", + "pve/boss/characterFilter.js", + "pve/boss/characterTitle.js", "pve/boss/dynamicTranslate.js", - "pve/boss/translate.js" + "pve/boss/intro.js", + "pve/boss/merge.js", + "pve/boss/perfectPairs.js", + "pve/boss/pinyin.js", + "pve/boss/sets.js", + "pve/boss/skill.js", + "pve/boss/sort.js", + "pve/boss/translate.js", + "pve/boss/voices.js", + "pve/boss/sets/shiershengxiao/index.js", + "pve/boss/sets/shiershengxiao/card.js", + "pve/boss/sets/shiershengxiao/character.js", + "pve/boss/sets/shiershengxiao/characterFilter.js", + "pve/boss/sets/shiershengxiao/characterTitle.js", + "pve/boss/sets/shiershengxiao/dynamicTranslate.js", + "pve/boss/sets/shiershengxiao/intro.js", + "pve/boss/sets/shiershengxiao/perfectPairs.js", + "pve/boss/sets/shiershengxiao/pinyin.js", + "pve/boss/sets/shiershengxiao/skill.js", + "pve/boss/sets/shiershengxiao/sort.js", + "pve/boss/sets/shiershengxiao/translate.js", + "pve/boss/sets/shiershengxiao/voices.js", + "pve/boss/sets/zhuoguiquxie/index.js", + "pve/boss/sets/zhuoguiquxie/card.js", + "pve/boss/sets/zhuoguiquxie/character.js", + "pve/boss/sets/zhuoguiquxie/characterFilter.js", + "pve/boss/sets/zhuoguiquxie/characterTitle.js", + "pve/boss/sets/zhuoguiquxie/dynamicTranslate.js", + "pve/boss/sets/zhuoguiquxie/intro.js", + "pve/boss/sets/zhuoguiquxie/perfectPairs.js", + "pve/boss/sets/zhuoguiquxie/pinyin.js", + "pve/boss/sets/zhuoguiquxie/skill.js", + "pve/boss/sets/zhuoguiquxie/sort.js", + "pve/boss/sets/zhuoguiquxie/translate.js", + "pve/boss/sets/zhuoguiquxie/voices.js", + "pve/boss/sets/qingqingzijin/index.js", + "pve/boss/sets/qingqingzijin/card.js", + "pve/boss/sets/qingqingzijin/character.js", + "pve/boss/sets/qingqingzijin/characterFilter.js", + "pve/boss/sets/qingqingzijin/characterTitle.js", + "pve/boss/sets/qingqingzijin/dynamicTranslate.js", + "pve/boss/sets/qingqingzijin/intro.js", + "pve/boss/sets/qingqingzijin/perfectPairs.js", + "pve/boss/sets/qingqingzijin/pinyin.js", + "pve/boss/sets/qingqingzijin/skill.js", + "pve/boss/sets/qingqingzijin/sort.js", + "pve/boss/sets/qingqingzijin/translate.js", + "pve/boss/sets/qingqingzijin/voices.js", + "pve/boss/sets/tianshu/index.js", + "pve/boss/sets/tianshu/card.js", + "pve/boss/sets/tianshu/character.js", + "pve/boss/sets/tianshu/characterFilter.js", + "pve/boss/sets/tianshu/characterTitle.js", + "pve/boss/sets/tianshu/dynamicTranslate.js", + "pve/boss/sets/tianshu/intro.js", + "pve/boss/sets/tianshu/perfectPairs.js", + "pve/boss/sets/tianshu/pinyin.js", + "pve/boss/sets/tianshu/skill.js", + "pve/boss/sets/tianshu/sort.js", + "pve/boss/sets/tianshu/translate.js", + "pve/boss/sets/tianshu/voices.js" ], "remove": [], "removeDirectories": [] diff --git a/update.txt b/update.txt index 5539d75..f32bdb8 100644 --- a/update.txt +++ b/update.txt @@ -1,3 +1,13 @@ +"-----< 2.0.1.1 改动 >-----", +"新增“旧轮次”选项,可在过关后按照旧规则将行动权交给玩家方1号位上家的Boss", +"新增“羁绊技能倾向”选项,已激活羁绊的相关技能将按羁绊等级获得温和的出现概率提升", +"羁绊技能倾向权重现可在天书配置中按等级独立调整", +"新增“虚拟偶像参与奖励”选项,开启后虚拟偶像可以选择过关技能并激活、升级羁绊效果", +"-----< 2.0.1.1 修复 >-----", +"修复多次触发青龙偃月刀追加【杀】并击杀Boss后,过关奖励结束时无法正确跳过当前角色剩余回合的问题", +"修复旧轮次在新Boss召唤前提前判断座位,导致行动权交接条件无法正常触发的问题", +"修复当前行动角色为虚拟偶像时,旧轮次无法将行动权交给玩家方1号位上家Boss的问题", +"修复“南蛮入侵”和“群雄逐鹿”羁绊虽然对友方无效,但AI仍会将友方计入用牌收益而拒绝使用相关牌的问题", "-----< 2.0.1.0 改动 >-----", "重构PVE武将包目录,按照不同活动BOSS进行独立分包和统一加载", "新增独立PVP武将包结构,并加入术樱左慈及其技能、图片和配音资源",