diff --git a/character/audio/zuoci/huashen_shuying1.mp3 b/character/audio/zuoci/huashen_shuying1.mp3 new file mode 100644 index 0000000..71e765c Binary files /dev/null and b/character/audio/zuoci/huashen_shuying1.mp3 differ diff --git a/character/audio/zuoci/huashen_shuying2.mp3 b/character/audio/zuoci/huashen_shuying2.mp3 new file mode 100644 index 0000000..39e8c43 Binary files /dev/null and b/character/audio/zuoci/huashen_shuying2.mp3 differ diff --git a/character/audio/zuoci/jihun_shuying1.mp3 b/character/audio/zuoci/jihun_shuying1.mp3 new file mode 100644 index 0000000..870c35f Binary files /dev/null and b/character/audio/zuoci/jihun_shuying1.mp3 differ diff --git a/character/audio/zuoci/jihun_shuying2.mp3 b/character/audio/zuoci/jihun_shuying2.mp3 new file mode 100644 index 0000000..42a71c6 Binary files /dev/null and b/character/audio/zuoci/jihun_shuying2.mp3 differ diff --git a/character/audio/zuoci/xinsheng_shuying1.mp3 b/character/audio/zuoci/xinsheng_shuying1.mp3 new file mode 100644 index 0000000..e95c058 Binary files /dev/null and b/character/audio/zuoci/xinsheng_shuying1.mp3 differ diff --git a/character/audio/zuoci/xinsheng_shuying2.mp3 b/character/audio/zuoci/xinsheng_shuying2.mp3 new file mode 100644 index 0000000..3e3cf29 Binary files /dev/null and b/character/audio/zuoci/xinsheng_shuying2.mp3 differ diff --git a/character/audio/zuoci/zuoci_shuying.mp3 b/character/audio/zuoci/zuoci_shuying.mp3 new file mode 100644 index 0000000..afad96f Binary files /dev/null and b/character/audio/zuoci/zuoci_shuying.mp3 differ diff --git a/character/character.js b/character/character.js new file mode 100644 index 0000000..eef0c77 --- /dev/null +++ b/character/character.js @@ -0,0 +1,12 @@ +const characters = { + zuoci_shuying: { + sex: "male", + group: "qun", + hp: 3, + skills: ["huashen_shuying", "xinsheng_shuying", "jihun_shuying"], + img: "extension/术樱包/character/images/zuoci_shuying.jpg", + dieAudios: ["ext:术樱包/character/audio/zuoci/zuoci_shuying.mp3"] + }, +}; + +export default characters; diff --git a/character/characterFilter.js b/character/characterFilter.js new file mode 100644 index 0000000..dfc3d19 --- /dev/null +++ b/character/characterFilter.js @@ -0,0 +1,3 @@ +const characterFilters = {}; + +export default characterFilters; diff --git a/character/characterTitle.js b/character/characterTitle.js new file mode 100644 index 0000000..b65d634 --- /dev/null +++ b/character/characterTitle.js @@ -0,0 +1,3 @@ +const characterTitles = {}; + +export default characterTitles; diff --git a/character/dynamicTranslate.js b/character/dynamicTranslate.js new file mode 100644 index 0000000..162e610 --- /dev/null +++ b/character/dynamicTranslate.js @@ -0,0 +1,3 @@ +const dynamicTranslates = {}; + +export default dynamicTranslates; diff --git a/character/images/zuoci_shuying.jpg b/character/images/zuoci_shuying.jpg new file mode 100644 index 0000000..e9a1b3e Binary files /dev/null and b/character/images/zuoci_shuying.jpg differ diff --git a/character/index.js b/character/index.js new file mode 100644 index 0000000..2677f1b --- /dev/null +++ b/character/index.js @@ -0,0 +1,28 @@ +import characters from "./character.js"; +import skills from "./skill.js"; +import translates from "./translate.js"; +import characterFilters from "./characterFilter.js"; +import characterTitles from "./characterTitle.js"; +import characterIntros from "./intro.js"; +import dynamicTranslates from "./dynamicTranslate.js"; +import perfectPairs from "./perfectPairs.js"; +import voices from "./voices.js"; +import { characterSort, characterSortTranslate } from "./sort.js"; + +const shuying_character = { + name: "shuYing_character", + connect: true, + character: { ...characters }, + characterSort: { + shuYing_character: characterSort, + }, + characterFilter: { ...characterFilters }, + characterTitle: { ...characterTitles }, + dynamicTranslate: { ...dynamicTranslates }, + characterIntro: { ...characterIntros }, + skill: { ...skills }, + perfectPair: { ...perfectPairs }, + translate: { ...translates, ...voices, ...characterSortTranslate }, +}; + +export default shuying_character; diff --git a/character/intro.js b/character/intro.js new file mode 100644 index 0000000..94c8d75 --- /dev/null +++ b/character/intro.js @@ -0,0 +1,3 @@ +const characterIntros = {}; + +export default characterIntros; diff --git a/character/perfectPairs.js b/character/perfectPairs.js new file mode 100644 index 0000000..af5539c --- /dev/null +++ b/character/perfectPairs.js @@ -0,0 +1,3 @@ +const perfectPairs = {}; + +export default perfectPairs; diff --git a/character/skill.js b/character/skill.js new file mode 100644 index 0000000..0831048 --- /dev/null +++ b/character/skill.js @@ -0,0 +1,388 @@ +import { lib, game, ui, get, ai, _status } from "../../../noname.js"; + +const skills = { + huashen_shuying: { + audio: "ext:术樱包/character/audio/zuoci:2", + inherit: "rehuashen", + unique: true, + init(player, skill) { + if (!player.storage.rehuashen) { + player.storage.rehuashen = { + character: [], + choosed: [], + map: {}, + }; + } + player.storage[skill] = player.storage.rehuashen; + }, + async content(event, trigger, player) { + const oldCharacter = player.storage.rehuashen?.current; + const oldSkill = player.storage.rehuashen?.current2; + const transitionKey = "jihun_shuying_transition"; + + // 单独处理重铸,保留本次实际移除的化身牌信息 + if (event.cost_data === "制衡其他化身") { + _status.noclearcountdown = true; + const id = lib.status.videoId++; + const prompt = "化身:选择制衡至多两张武将牌"; + const cards = player.storage.rehuashen.character; + + if (player.isOnline2()) { + player.send( + (cards, prompt, id) => { + const dialog = ui.create.dialog( + prompt, + [cards, lib.skill.rehuashen.$createButton] + ); + dialog.videoId = id; + }, + cards, + prompt, + id + ); + } + + const dialog = ui.create.dialog( + prompt, + [cards, lib.skill.rehuashen.$createButton] + ); + dialog.videoId = id; + if (!event.isMine()) { + dialog.style.display = "none"; + } + + const result = await player + .chooseButton(true) + .set("dialog", id) + .set("selectButton", [1, 2]) + .set( + "filterButton", + button => button.link !== get.event().current + ) + .set("current", player.storage.rehuashen.current) + .forResult(); + + if (player.isOnline2()) { + player.send("closeDialog", id); + } + dialog.close(); + delete _status.noclearcountdown; + if (!_status.noclearcountdown) { + game.stopCountChoose(); + } + + const removedCharacters = result.links || []; + lib.skill.rehuashen.removeHuashen( + player, + removedCharacters + ); + + if ( + player.storage.jihun_shuying_retainedCharacter && + removedCharacters.includes( + player.storage.jihun_shuying_retainedCharacter + ) + ) { + delete player.storage.jihun_shuying_retained; + delete player.storage.jihun_shuying_retainedCharacter; + delete player.storage.jihun_shuying_ignoredEvents; + await player.removeAdditionalSkills( + "jihun_shuying_retained" + ); + } + + lib.skill.rehuashen.addHuashens( + player, + removedCharacters.length + ); + player.storage.huashen_shuying = + player.storage.rehuashen; + player.syncStorage("huashen_shuying"); + player.updateMarks("huashen_shuying"); + return; + } + + // 切换前为当前技能增加临时所有权。官方化身移除旧来源时, + // getRemovableAdditionalSkills 会因此保留技能本体及全部状态。 + const protectOldSkill = Boolean( + oldSkill && player.hasSkill("jihun_shuying") + ); + if ( + protectOldSkill && + !player.additionalSkills[transitionKey]?.includes(oldSkill) + ) { + if (!Array.isArray(player.additionalSkills[transitionKey])) { + player.additionalSkills[transitionKey] = []; + } + player.additionalSkills[transitionKey].push(oldSkill); + game.broadcast( + (player, map) => { + player.additionalSkills = map; + }, + player, + player.additionalSkills + ); + _status.event.clearStepCache(); + } + + await lib.skill.rehuashen.content(event, trigger, player); + + const storage = player.storage.rehuashen; + const changed = Boolean( + oldSkill && + ( + oldCharacter !== storage?.current || + oldSkill !== storage?.current2 + ) + ); + + if ( + changed && + player.hasSkill("jihun_shuying") + ) { + // 过渡来源会阻止官方流程物理移除旧技能,因此这里仅删除 + // rehuashen 对旧技能的映射,不触发 removeSkill/onremove。 + if ( + oldSkill !== storage.current2 && + player.additionalSkills.rehuashen?.includes(oldSkill) + ) { + player.$removeAdditionalSkills("rehuashen", oldSkill); + _status.event.clearStepCache(); + } + + // 新的当前技能已经登记完毕后,再结束上一次羁魂保留。 + if (player.additionalSkills.jihun_shuying_retained) { + await player.removeAdditionalSkills( + "jihun_shuying_retained" + ); + } + delete player.storage.jihun_shuying_retained; + delete player.storage.jihun_shuying_retainedCharacter; + delete player.storage.jihun_shuying_ignoredEvents; + + player.logSkill("jihun_shuying"); + + if (oldSkill && oldSkill !== storage.current2) { + const ignoredEvents = player + .getAllHistory("useSkill", history => { + return get.sourceSkillFor(history) === oldSkill; + }) + .map(history => history.event) + .filter(Boolean); + + let currentEvent = _status.event; + while (currentEvent) { + const eventSkill = currentEvent.sourceSkill || + currentEvent.skill || + (lib.skill[currentEvent.name] ? currentEvent.name : null); + if ( + eventSkill && + get.sourceSkillFor(eventSkill) === oldSkill && + !ignoredEvents.includes(currentEvent) + ) { + ignoredEvents.push(currentEvent); + } + currentEvent = currentEvent.parent; + } + + player.storage.jihun_shuying_retained = oldSkill; + player.storage.jihun_shuying_retainedCharacter = + oldCharacter; + player.storage.jihun_shuying_ignoredEvents = ignoredEvents; + // 此时 oldSkill 仍由过渡键持有,增加羁魂来源不会重新 + // 初始化技能,也不会影响牌、标记和 storage。 + const retainedKey = "jihun_shuying_retained"; + if (!Array.isArray(player.additionalSkills[retainedKey])) { + player.additionalSkills[retainedKey] = []; + } + if (!player.additionalSkills[retainedKey].includes(oldSkill)) { + player.additionalSkills[retainedKey].push(oldSkill); + } + game.broadcast( + (player, map) => { + player.additionalSkills = map; + }, + player, + player.additionalSkills + ); + _status.event.clearStepCache(); + } + } + + // 最终所有权建立完毕后再撤销过渡来源。 + if (player.additionalSkills[transitionKey]) { + await player.removeAdditionalSkills(transitionKey); + } + + player.storage.huashen_shuying = storage; + player.syncStorage("huashen_shuying"); + player.updateMarks("huashen_shuying"); + }, + onremove(player) { + player.removeAdditionalSkill("rehuashen"); + player.removeAdditionalSkill("jihun_shuying_retained"); + player.removeAdditionalSkill("jihun_shuying_transition"); + delete player.storage.rehuashen; + delete player.storage.huashen_shuying; + delete player.storage.jihun_shuying_retained; + delete player.storage.jihun_shuying_retainedCharacter; + delete player.storage.jihun_shuying_ignoredEvents; + }, + ai: { + threaten: 2, + }, + }, + + xinsheng_shuying: { + audio: "ext:术樱包/character/audio/zuoci:2", + unique: true, + trigger: { + player: "damageEnd", + }, + filter(event, player) { + return event.num > 0 && + player.hasSkill("huashen_shuying") && + player.storage.rehuashen; + }, + async cost(event, trigger, player) { + const controls = ["获得一张新化身"]; + if (player.storage.rehuashen.character?.length) { + controls.push("更换所展示的化身"); + } + controls.push("cancel2"); + + const result = await player + .chooseControl(controls) + .set("prompt", get.prompt(event.skill)) + .set("prompt2", "获得一张新化身牌,或更换所展示的化身牌") + .set("ai", () => { + const player = get.player(); + const controls = get.event().controls; + if (!controls.includes("更换所展示的化身")) { + return "获得一张新化身"; + } + + const storage = player.storage.rehuashen; + const currentRank = storage.current2 ? + get.skillRank(storage.current2, "in") : 0; + const skills = storage.character + .flatMap(name => storage.map[name] || []) + .unique(); + const bestRank = skills.reduce((max, skill) => { + return Math.max(max, get.skillRank(skill, "in")); + }, 0); + + if (bestRank > currentRank + 1) { + return "更换所展示的化身"; + } + return "获得一张新化身"; + }) + .forResult(); + + event.result = { + bool: result.control !== "cancel2", + cost_data: result.control, + }; + }, + async content(event, trigger, player) { + if (event.cost_data === "获得一张新化身") { + lib.skill.rehuashen.addHuashens(player, 1); + player.storage.huashen_shuying = player.storage.rehuashen; + player.syncStorage("huashen_shuying"); + player.updateMarks("huashen_shuying"); + return; + } + + const next = game.createEvent("huashen_shuying_change"); + next.player = player; + next.skill = "huashen_shuying"; + next.cost_data = "替换当前化身"; + next.setContent(lib.skill.huashen_shuying.content); + await next; + }, + ai: { + combo: "huashen_shuying", + maixie: true, + maixie_hp: true, + effect: { + target(card, player, target) { + if ( + get.tag(card, "damage") && + target.hasSkill("huashen_shuying") + ) { + return [1, 0.7]; + } + }, + }, + }, + }, + + jihun_shuying: { + audio: "ext:术樱包/character/audio/zuoci:2", + locked: true, + group: "jihun_shuying_clear", + onremove(player) { + player.removeAdditionalSkill("jihun_shuying_retained"); + player.removeAdditionalSkill("jihun_shuying_transition"); + delete player.storage.jihun_shuying_retained; + delete player.storage.jihun_shuying_retainedCharacter; + delete player.storage.jihun_shuying_ignoredEvents; + }, + ai: { + combo: "huashen_shuying", + }, + subSkill: { + clear: { + charlotte: true, + silent: true, + lastDo: true, + priority: -Infinity, + trigger: { + player: [ + "useSkillAfter", + "useCardAfter", + "respondAfter", + "triggerAfter", + "skillAfter", + ], + }, + hookTrigger: { + after(event, player) { + const retained = + player.storage.jihun_shuying_retained; + return Boolean( + retained && + event.skill && + get.sourceSkillFor(event.skill) === retained + ); + }, + }, + filter(event, player) { + const retained = + player.storage.jihun_shuying_retained; + if ( + !retained || + !event.skill || + get.sourceSkillFor(event.skill) !== retained + ) { + return false; + } + + const ignoredEvents = + player.storage.jihun_shuying_ignoredEvents || []; + return !ignoredEvents.includes(event); + }, + async content(event, trigger, player) { + delete player.storage.jihun_shuying_retained; + delete player.storage.jihun_shuying_retainedCharacter; + delete player.storage.jihun_shuying_ignoredEvents; + await player.removeAdditionalSkills( + "jihun_shuying_retained" + ); + }, + }, + }, + }, +}; + +export default skills; diff --git a/character/sort.js b/character/sort.js new file mode 100644 index 0000000..bb87d8a --- /dev/null +++ b/character/sort.js @@ -0,0 +1,4 @@ +const characterSort = {}; +const characterSortTranslate = {}; + +export { characterSort, characterSortTranslate }; diff --git a/character/translate.js b/character/translate.js new file mode 100644 index 0000000..36e78a2 --- /dev/null +++ b/character/translate.js @@ -0,0 +1,11 @@ +const translates = { + zuoci_shuying: "术左慈", + huashen_shuying: "化身", + huashen_shuying_info: "游戏开始时,你随机获得三张未加入游戏的武将牌,选一张置于你面前并声明该武将的一项技能,你拥有该技能且同时将性别和势力属性变为与该武将相同,直到该化身被替换。回合开始时或结束时,你可以选择一项:①弃置至多两张未展示的化身并重新获得等量化身;②更换所展示的化身牌或技能。(你不可以声明限定技、觉醒技、隐匿技、使命技、主公技等特殊技能)", + xinsheng_shuying: "新生", + xinsheng_shuying_info: "若你拥有技能〖化身〗,当你受到伤害后,你可以选择一项:①获得一张新化身牌;②更换所展示的化身牌。", + jihun_shuying: "羁魂", + jihun_shuying_info: "若你拥有技能〖化身〗,当你更换所展示的化身牌或技能时,保留上次声明的技能,直至该技能下次触发、再次更换所展示的化身牌或技能、重铸该技能对应的化身牌。", +}; + +export default translates; diff --git a/character/voices.js b/character/voices.js new file mode 100644 index 0000000..97412c7 --- /dev/null +++ b/character/voices.js @@ -0,0 +1,3 @@ +const voices = {}; + +export default voices; diff --git a/extension.js b/extension.js index b509258..179af8d 100644 --- a/extension.js +++ b/extension.js @@ -223,13 +223,10 @@ shuYing.initCharacter = async () => { // "yuanshen", // ]; } - else { - // initBengTie(lib, game, ui, get, ai, _status, shuYing); - // shuYingList = - // [ - // "yuanshen", - // ]; + const shuying_character = await shuYing.loadModule("./character/index.js", "术樱包", true); + if (shuying_character) { + shuYing.appendExtension("shuYing_character", "术樱包", shuying_character); } // shuYingList.forEach(url => { @@ -247,6 +244,10 @@ shuYing.precontent = async () => { shuYing.initCSS(); shuYing.initFunction(); shuYing.initConfig(); + // const initMengsan = await shuYing.loadModule("./mengsan/mode.js", "梦三模式", true); + // if (typeof initMengsan == "function") { + // await initMengsan(); + // } initShuYingMenu({ lib, game, ui, shuYing, updater }); shuYing.getVersion(); await shuYing.initCharacter(); diff --git a/mengsan/config.js b/mengsan/config.js new file mode 100644 index 0000000..f8f886c --- /dev/null +++ b/mengsan/config.js @@ -0,0 +1,68 @@ +const mengsanConfig = { + modeId: "mengsan_shuying", + saveKey: "mengsan_run_shuying", + profileKey: "mengsan_profile_shuying", + schemaVersion: 1, + mapLayoutVersion: 3, + routeCount: 4, + characters: ["re_zhaoyun", "sunquan", "re_huangyueying", "re_zhangfei"], + acts: [ + { + id: "act1", + name: "第一关·初梦", + floorNodes: [1, 4, 4, 4, 1], + minFloorNodes: [1, 3, 3, 3, 1], + nodeWeights: { battle: 65, event: 15, rest: 10, shop: 10 }, + enemies: ["re_xiahoudun", "re_zhangliao", "re_xuzhu"], + boss: "re_lvbu", + baseGold: 20, + }, + { + id: "act2", + name: "第二关·迷梦", + floorNodes: [1, 4, 4, 4, 4, 1], + minFloorNodes: [1, 3, 2, 3, 3, 1], + nodeWeights: { battle: 55, elite: 15, event: 12, rest: 9, shop: 9 }, + enemies: ["re_ganning", "re_zhangfei", "re_huangzhong", "re_weiyan"], + eliteEnemies: ["re_guanyu", "dianwei"], + boss: "shen_guanyu", + baseGold: 30, + }, + { + id: "act3", + name: "第三关·终梦", + floorNodes: [1, 4, 4, 4, 4, 4, 1], + minFloorNodes: [1, 3, 2, 3, 2, 3, 1], + nodeWeights: { battle: 45, elite: 20, event: 15, rest: 10, shop: 10 }, + enemies: ["re_simayi", "re_sunben", "dongzhuo", "re_yuanshao"], + eliteEnemies: ["shen_zhaoyun", "shen_simayi"], + boss: "shen_caocao", + baseGold: 40, + }, + ], + startingDeck: [ + ["spade", 7, "sha"], + ["heart", 10, "sha"], + ["club", 4, "sha"], + ["diamond", 6, "sha"], + ["heart", 2, "shan"], + ["diamond", 7, "shan"], + ["club", 2, "shan"], + ["heart", 3, "tao"], + ["spade", 3, "guohe"], + ["heart", 7, "wuzhong"], + ["spade", 2, "bagua"], + ["club", 6, "sha", null, ["annihilate"]], + ], + nodeNames: { + battle: "战斗", + elite: "精英", + event: "事件", + story: "剧情战斗", + rest: "休息", + shop: "商店", + boss: "Boss", + }, +}; + +export default mengsanConfig; diff --git a/mengsan/mode.js b/mengsan/mode.js new file mode 100644 index 0000000..03b4700 --- /dev/null +++ b/mengsan/mode.js @@ -0,0 +1,540 @@ +import { lib, game, ui, get, _status } from "../../../noname.js"; +import config from "./config.js"; +import { + completeNode, + createRun, + enterNextAct, + generateActMap, + getNodeEncounter, + getSelectableNodes, + insertStoryNode, + nextRandom, +} from "./state.js"; + +const MODE_ID = config.modeId; +const STYLE_PATH = `${lib.assetURL}extension/术樱包/mengsan`; + +const copy = value => JSON.parse(JSON.stringify(value)); + +const persistModeStorage = async () => { + lib.storage.version = lib.version; + if (lib.db) { + await game.putDB("data", MODE_ID, copy(lib.storage)); + } + else { + localStorage.setItem(`${lib.configprefix}${MODE_ID}`, JSON.stringify(lib.storage)); + } +}; + +const saveRun = async run => { + try { + lib.storage[config.saveKey] = copy(run); + await persistModeStorage(); + } + catch (error) { + delete lib.storage[config.saveKey]; + try { + await persistModeStorage(); + } + catch {} + alert("梦三存档失败,当前征程存档已清空。请查看控制台错误。"); + console.error("梦三存档失败:", error); + throw error; + } +}; + +const clearRun = async () => { + delete lib.storage[config.saveKey]; + await persistModeStorage(); +}; + +const createOverlay = (title, className = "") => { + const overlay = ui.create.div(`.mengsan-overlay-shuying${className ? `.${className}` : ""}`, ui.window); + const panel = ui.create.div(".mengsan-panel-shuying", overlay); + ui.create.div(".mengsan-title-shuying", title, panel); + return { overlay, panel }; +}; + +const chooseButtons = (title, choices, description = "") => new Promise(resolve => { + const { overlay, panel } = createOverlay(title); + if (description) ui.create.div(".mengsan-description-shuying", description, panel); + const buttons = ui.create.div(".mengsan-choice-list-shuying", panel); + choices.forEach(choice => { + const button = ui.create.div(".mengsan-choice-shuying", buttons); + button.innerHTML = `
${choice.name}
${choice.description ? `
${choice.description}
` : ""}`; + if (choice.disabled) button.classList.add("disabled"); + button.addEventListener(lib.config.touchscreen ? "touchend" : "click", event => { + event.preventDefault(); + if (choice.disabled || overlay.dataset.resolved) return; + overlay.dataset.resolved = "true"; + overlay.remove(); + resolve(choice.id); + }); + }); +}); + +const chooseRun = async savedRun => { + if (!savedRun || savedRun.status != "running") return "new"; + const act = config.acts[savedRun.actIndex]; + return chooseButtons("梦三", [ + { + id: "continue", + name: "继续征程", + description: `${act?.name || "未知区域"} · 已通过 ${savedRun.statistics?.completedNodes || 0} 个节点`, + }, + { + id: "new", + name: "新的开始", + description: "覆盖当前征程并重新生成第一关地图", + }, + ], "Demo 采用单一自动存档。节点完成后才会覆盖检查点。" ); +}; + +const chooseCharacter = async () => { + const choices = config.characters.filter(name => lib.character[name]).map(name => ({ + id: name, + name: get.translation(name), + description: lib.translate[`${name}_title`] || "作为本次征程的角色", + })); + return chooseButtons("选择角色", choices.length ? choices : [{ id: "re_zhaoyun", name: "赵云" }]); +}; + +const nodeColor = { + battle: "#9d4937", + elite: "#7e3f79", + event: "#477aa6", + story: "#8c6a34", + rest: "#3f8560", + shop: "#a27b35", + boss: "#a62727", +}; + +const showMap = run => new Promise(resolve => { + const act = config.acts[run.actIndex]; + const selectable = getSelectableNodes(run); + const selectableIds = new Set(selectable.map(node => node.id)); + const { overlay, panel } = createOverlay(act.name, "mengsan-map-overlay-shuying"); + panel.classList.add("mengsan-map-panel-shuying"); + const status = ui.create.div(".mengsan-map-status-shuying", panel); + status.innerHTML = `体力:${run.player.hp == null ? "未初始化" : `${run.player.hp}/${run.player.maxHp}`} 金币:${run.player.gold} 牌组:${run.player.deck.length}`; + const map = ui.create.div(".mengsan-map-shuying", panel); + const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg"); + svg.classList.add("mengsan-lines-shuying"); + svg.setAttribute("viewBox", "0 0 100 100"); + svg.setAttribute("preserveAspectRatio", "none"); + svg.setAttribute("aria-hidden", "true"); + map.appendChild(svg); + const nodeMap = new Map(run.map.nodes.map(node => [node.id, node])); + run.map.edges.forEach(edge => { + const source = nodeMap.get(edge[0]); + const target = nodeMap.get(edge[1]); + if (!source || !target) return; + const line = document.createElementNS("http://www.w3.org/2000/svg", "line"); + line.setAttribute("x1", source.x); + line.setAttribute("y1", source.y); + line.setAttribute("x2", target.x); + line.setAttribute("y2", target.y); + if (source.completed) line.classList.add("completed"); + if (source.dynamic || target.dynamic) line.classList.add("dynamic"); + svg.appendChild(line); + }); + run.map.nodes.forEach(node => { + const button = ui.create.div(".mengsan-node-shuying", map); + button.style.left = `${node.x}%`; + button.style.top = `${node.y}%`; + button.style.setProperty("--node-color", nodeColor[node.type] || "#666"); + button.innerHTML = `${config.nodeNames[node.type] || node.type}`; + button.dataset.nodeId = node.id; + if (node.completed) button.classList.add("completed"); + else if (selectableIds.has(node.id)) button.classList.add("selectable"); + else button.classList.add("locked"); + button.addEventListener(lib.config.touchscreen ? "touchend" : "click", event => { + event.preventDefault(); + if (!selectableIds.has(node.id) || overlay.dataset.resolved) return; + overlay.dataset.resolved = "true"; + overlay.remove(); + resolve(node); + }); + }); +}); + +const createCardInstance = (run, name = "sha", upgrade = 0, affixes = []) => ({ + id: `mengsan_card_${Date.now()}_${Math.floor(nextRandom(run) * 1e6)}`, + suit: ["spade", "heart", "club", "diamond"][Math.floor(nextRandom(run) * 4)], + number: 1 + Math.floor(nextRandom(run) * 13), + name, + nature: null, + upgrade, + affixes: affixes.slice(), +}); + +const applyReward = (run, rewardId) => { + const player = run.player; + switch (rewardId) { + case "card_sha": + player.deck.push(createCardInstance(run, "sha")); + break; + case "card_tao": + player.deck.push(createCardInstance(run, "tao")); + break; + case "upgrade": { + const card = player.deck[Math.floor(nextRandom(run) * player.deck.length)]; + if (card) card.upgrade = Number(card.upgrade || 0) + 1; + break; + } + case "heal": + player.hp = Math.min(player.maxHp, player.hp + 2); + break; + case "max_hp": + player.maxHp++; + player.hp++; + break; + case "item_hand": + if (!player.items.includes("mengsan_hand_charm_shuying")) { + player.items.push("mengsan_hand_charm_shuying"); + player.handLimitBonus++; + } + break; + case "skill_yingyong": + if (!player.permanentSkills.includes("mengsan_yingyong_shuying")) { + player.permanentSkills.push("mengsan_yingyong_shuying"); + } + break; + } +}; + +const chooseBattleReward = async (run, boss) => { + const choices = boss ? [ + { id: "skill_yingyong", name: "极品技能·英勇", description: "本次征程永久获得【英勇】" }, + { id: "item_hand", name: "极品道具·束带", description: "基础手牌上限额外 +1" }, + { id: "max_hp", name: "极品强化·生机", description: "体力上限与当前体力各 +1" }, + ] : [ + { id: "card_sha", name: "获得一张【杀】", description: "加入独立永久牌组" }, + { id: "card_tao", name: "获得一张【桃】", description: "加入独立永久牌组" }, + { id: "upgrade", name: "随机强化", description: "随机一张牌强化一级" }, + ]; + const reward = await chooseButtons(boss ? "极品奖励(三选一)" : "战斗奖励(三选一)", choices); + applyReward(run, reward); +}; + +const finishUtilityNode = async (run, node) => { + if (node.type == "rest") { + const choice = await chooseButtons("休息节点", [ + { id: "heal", name: "休息", description: "回复 2 点体力" }, + { id: "upgrade", name: "磨砺", description: "随机强化一张卡牌" }, + ]); + applyReward(run, choice); + } + else if (node.type == "shop") { + const price = 20; + const choice = await chooseButtons("商店 Demo", [ + { id: "card_sha", name: `购买【杀】(${price}金币)`, description: "加入个人牌组", disabled: run.player.gold < price }, + { id: "heal", name: `恢复体力(${price}金币)`, description: "回复 2 点体力", disabled: run.player.gold < price }, + { id: "leave", name: "离开", description: "暂不购买" }, + ]); + if (choice != "leave") { + run.player.gold -= price; + applyReward(run, choice); + } + } + else { + const choice = await chooseButtons("随机事件", [ + { id: "branch", name: "调查异象", description: "在当前路线后插入一个额外剧情战斗节点" }, + { id: "gold", name: "收下钱袋", description: "获得 15 金币" }, + ]); + if (choice == "branch") insertStoryNode(run, node.id); + else { + run.player.gold += 15; + run.statistics.goldEarned += 15; + } + } + completeNode(run, node.id); + await saveRun(run); + game.reload(); +}; + +const shuffleBattlePile = (run, cards) => { + for (let index = cards.length - 1; index > 0; index--) { + const target = Math.floor(nextRandom(run) * (index + 1)); + [cards[index], cards[target]] = [cards[target], cards[index]]; + } +}; + +const setupBattle = async (run, node) => { + const encounter = getNodeEncounter(run, node); + _status.mengsanRun_shuying = run; + _status.mengsanNode_shuying = node; + _status.mengsanEncounter_shuying = encounter; + _status.mengsanBattle_shuying = { + resolving: false, + drawPile: copy(run.player.deck), + discardPile: [], + exhaustPile: [], + }; + shuffleBattlePile(run, _status.mengsanBattle_shuying.drawPile); + + game.prepareArena(2); + const me = game.me; + const enemy = game.players.find(current => current != me); + me.init(run.player.character); + enemy.init(lib.character[encounter.enemy] ? encounter.enemy : "re_lvbu"); + me.identity = "zhu"; + enemy.identity = "fan"; + me.side = false; + enemy.side = true; + me.setIdentity("zhu"); + enemy.setIdentity("fan"); + me.ai.shown = 1; + enemy.ai.shown = 1; + me.storage.mengsanPlayer_shuying = true; + game.zhu = me; + if (run.player.maxHp == null) { + run.player.maxHp = me.maxHp; + run.player.hp = me.hp; + } + me.maxHp = run.player.maxHp; + me.hp = Math.max(1, Math.min(run.player.hp, me.maxHp)); + me.update(); + run.player.permanentSkills.forEach(skill => { + if (lib.skill[skill] && !me.hasSkill(skill)) me.addSkill(skill); + }); + game.addGlobalSkill("mengsan_draw_shuying"); + game.addGlobalSkill("mengsan_card_use_shuying"); + game.syncState(); + _status.event.trigger("gameStart"); + await game.mengsanDraw_shuying(me, 4); + game.gameDraw(me, current => current == me ? 0 : 4); + game.phaseLoop(me); +}; + +const createMode = identityMode => { + const identityElement = identityMode.element || {}; + const identityPlayer = identityElement.player || {}; + return { + ...identityMode, + name: MODE_ID, + connect: false, + start: [ + async () => { + lib.translate.restart = "返回"; + let run = copy(lib.storage[config.saveKey] || null); + const action = await chooseRun(run); + if (action == "new") { + if (run) { + const confirmNew = await chooseButtons("确认新的开始", [ + { id: "cancel", name: "返回", description: "保留当前征程" }, + { id: "confirm", name: "覆盖存档", description: "当前征程将被清除" }, + ]); + if (confirmNew == "cancel") { + run = copy(lib.storage[config.saveKey]); + } + else { + const character = await chooseCharacter(); + run = createRun(character); + await saveRun(run); + } + } + else { + const character = await chooseCharacter(); + run = createRun(character); + await saveRun(run); + } + } + const canRefreshOldMap = !run.map?.completedNodeIds?.length && run.map?.layoutVersion != config.mapLayoutVersion; + if (!run.map || run.map.actIndex != run.actIndex || canRefreshOldMap) { + run.map = generateActMap(run, run.actIndex); + await saveRun(run); + } + const node = await showMap(run); + if (["event", "rest", "shop"].includes(node.type)) { + await finishUtilityNode(run, node); + return; + } + await setupBattle(run, node); + }, + ], + element: { + ...identityElement, + player: { + ...identityPlayer, + getHandcardLimit() { + if (this.storage?.mengsanPlayer_shuying) { + const run = _status.mengsanRun_shuying; + const bonus = Number(run?.player?.handLimitBonus || 0); + return Math.max(0, Math.min(8, this.hp) + bonus); + } + let number = Math.max(this.hp, 0); + number = game.checkMod(this, number, "maxHandcardBase", this); + number = game.checkMod(this, number, "maxHandcard", this); + number = game.checkMod(this, number, "maxHandcardFinal", this); + return Math.max(0, number); + }, + }, + }, + game: { + ...(identityMode.game || {}), + async mengsanPersistRun_shuying(run) { + await saveRun(run); + }, + async mengsanClearRun_shuying() { + await clearRun(); + }, + async mengsanFailRun_shuying(reason = "征程失败") { + if (_status.mengsanEnding_shuying) return; + _status.mengsanEnding_shuying = true; + await clearRun(); + game.log(reason); + game.over(false); + }, + async mengsanDraw_shuying(player, number = 1) { + if (!player?.storage?.mengsanPlayer_shuying || number <= 0) return []; + const battle = _status.mengsanBattle_shuying; + const run = _status.mengsanRun_shuying; + if (!battle || battle.drawPile.length + battle.discardPile.length < number) { + await game.mengsanFailRun_shuying("个人牌堆与弃牌堆不足,挑战失败"); + return []; + } + const result = []; + while (result.length < number) { + if (!battle.drawPile.length) { + battle.drawPile = battle.discardPile.splice(0); + shuffleBattlePile(run, battle.drawPile); + game.log(player, "洗切了个人弃牌堆"); + } + const data = battle.drawPile.shift(); + const card = game.createCard(data.name, data.suit, data.number, data.nature); + card.storage.mengsanCard_shuying = copy(data); + card.destroyLog = false; + card.destroyed = (current, position) => { + if (position != "discardPile") return false; + const info = copy(current.storage.mengsanCard_shuying); + if (info.affixes?.includes("annihilate") && current.storage.mengsanUsed_shuying) battle.exhaustPile.push(info); + else battle.discardPile.push(info); + return true; + }; + if (data.affixes?.includes("annihilate")) card.addGaintag("湮灭"); + if (data.upgrade) card.addGaintag(`强化+${data.upgrade}`); + result.push(card); + } + player.directgain(result); + player.$draw(result.length); + game.log(player, "从个人牌堆摸了", get.cnNumber(result.length), "张牌"); + return result; + }, + async mengsanFinishBattle_shuying() { + const battle = _status.mengsanBattle_shuying; + if (!battle || battle.resolving || _status.mengsanEnding_shuying) return; + battle.resolving = true; + game.pause(); + const run = _status.mengsanRun_shuying; + const node = _status.mengsanNode_shuying; + const encounter = _status.mengsanEncounter_shuying; + run.player.hp = Math.max(1, game.me.hp); + run.player.gold += encounter.gold; + run.statistics.goldEarned += encounter.gold; + run.statistics.defeatedEnemies++; + await chooseBattleReward(run, encounter.boss); + completeNode(run, node.id); + if (encounter.boss) { + if (!enterNextAct(run)) { + const profile = copy(lib.storage[config.profileKey] || { wins: 0, completedRuns: [] }); + profile.wins++; + profile.completedRuns.push({ + runId: run.runId, + character: run.player.character, + completedNodes: run.statistics.completedNodes, + goldEarned: run.statistics.goldEarned, + completedAt: Date.now(), + }); + lib.storage[config.profileKey] = profile; + delete lib.storage[config.saveKey]; + await persistModeStorage(); + game.over(true); + return; + } + } + await saveRun(run); + game.reload(); + }, + checkResult() { + if (_status.mengsanEnding_shuying || _status.mengsanBattle_shuying?.resolving) return; + if (!game.me || game.me.isDead()) { + game.mengsanFailRun_shuying("角色死亡,征程结束"); + return; + } + const hasEnemy = game.players.some(current => current != game.me && current.side === true && current.isAlive()); + if (!hasEnemy) game.mengsanFinishBattle_shuying(); + }, + }, + skill: { + ...(identityMode.skill || {}), + mengsan_draw_shuying: { + trigger: { player: "drawBegin" }, + firstDo: true, + forced: true, + silent: true, + priority: 10000, + filter(event, player) { + return Boolean(player.storage?.mengsanPlayer_shuying && event.num > 0); + }, + async content(event, trigger, player) { + const number = trigger.num; + trigger.cancel(); + await game.mengsanDraw_shuying(player, number); + }, + }, + mengsan_card_use_shuying: { + trigger: { global: "useCard1" }, + forced: true, + silent: true, + popup: false, + filter(event) { + return event.player?.storage?.mengsanPlayer_shuying && event.cards?.some(card => card.storage?.mengsanCard_shuying); + }, + async content(event, trigger) { + trigger.cards.forEach(card => { + if (card.storage?.mengsanCard_shuying) card.storage.mengsanUsed_shuying = true; + }); + }, + }, + mengsan_yingyong_shuying: { + trigger: { source: "damageBegin1" }, + forced: true, + usable: 1, + filter(event, player) { + return Boolean(event.card && _status.currentPhase == player); + }, + async content(event, trigger) { + trigger.num++; + }, + }, + }, + translate: { + ...(identityMode.translate || {}), + mengsan_draw_shuying: "梦三牌组", + mengsan_card_use_shuying: "梦三词缀", + mengsan_yingyong_shuying: "英勇", + mengsan_yingyong_shuying_info: "锁定技,每回合限一次,你于自己的回合内使用牌造成的伤害+1。", + }, + config: {}, + help: { + 梦三: "梦三 Demo:每次完成节点后保存检查点;玩家使用个人牌组,敌人继续使用 noname 公共牌堆。", + }, + }; +}; + +export default async function initMengsan() { + if (lib.config.all.mode.includes(MODE_ID)) return; + await new Promise(resolve => { + const style = lib.init.css(STYLE_PATH, "style", resolve); + style.addEventListener("error", resolve, { once: true }); + }); + const identityMode = await game.loadModeAsync("identity"); + const mode = createMode(identityMode); + mode.splash = "ext:术樱包/pve/images/tianshu.jpg"; + game.addMode(MODE_ID, mode, { + translate: "梦三", + config: {}, + extension: "术樱包", + }); +} diff --git a/mengsan/state.js b/mengsan/state.js new file mode 100644 index 0000000..adae405 --- /dev/null +++ b/mengsan/state.js @@ -0,0 +1,227 @@ +import config from "./config.js"; + +const hashText = text => { + let value = 2166136261; + for (let index = 0; index < text.length; index++) { + value ^= text.charCodeAt(index); + value = Math.imul(value, 16777619); + } + return value >>> 0; +}; + +export const nextRandom = run => { + let value = Number(run.randomState || run.seed || Date.now()) >>> 0; + value += 0x6d2b79f5; + run.randomState = value >>> 0; + let result = value; + result = Math.imul(result ^ result >>> 15, result | 1); + result ^= result + Math.imul(result ^ result >>> 7, result | 61); + return ((result ^ result >>> 14) >>> 0) / 4294967296; +}; + +export const randomGet = (run, list) => list[Math.floor(nextRandom(run) * list.length)]; + +const weightedType = (run, weights) => { + const entries = Object.entries(weights); + const total = entries.reduce((sum, entry) => sum + entry[1], 0); + let cursor = nextRandom(run) * total; + for (const [type, weight] of entries) { + cursor -= weight; + if (cursor <= 0) return type; + } + return entries[0][0]; +}; + +const connectFloors = (run, edges, currentFloor, nextFloor) => { + const addEdge = (source, target) => { + if (!edges.some(edge => edge[0] == source.id && edge[1] == target.id)) { + edges.push([source.id, target.id]); + } + }; + currentFloor.forEach(source => { + const targets = nextFloor.slice().sort((left, right) => { + const distance = Math.abs(left.routeIndex - source.routeIndex) - Math.abs(right.routeIndex - source.routeIndex); + return distance || left.routeIndex - right.routeIndex; + }); + addEdge(source, targets[0]); + if (targets.length > 1 && Math.abs(targets[1].routeIndex - source.routeIndex) <= 1 && nextRandom(run) < 0.45) { + addEdge(source, targets[1]); + } + }); + nextFloor.forEach(target => { + if (!edges.some(edge => edge[1] == target.id)) { + const source = currentFloor.slice().sort((left, right) => { + const distance = Math.abs(left.routeIndex - target.routeIndex) - Math.abs(right.routeIndex - target.routeIndex); + return distance || left.routeIndex - right.routeIndex; + })[0]; + addEdge(source, target); + } + }); +}; + +const getFloorRoutes = (run, act, floor, lastFloor) => { + const routeCount = Math.max(1, config.routeCount); + if (floor == 0 || floor == lastFloor) return [(routeCount - 1) / 2]; + const maximum = Math.max(1, Math.min(routeCount, act.floorNodes[floor] || routeCount)); + const configuredMinimum = act.minFloorNodes?.[floor] ?? Math.max(2, maximum - 1); + const minimum = Math.max(1, Math.min(maximum, configuredMinimum)); + const count = minimum + Math.floor(nextRandom(run) * (maximum - minimum + 1)); + const routes = Array.from({ length: routeCount }, (_, index) => index); + for (let index = routes.length - 1; index > 0; index--) { + const target = Math.floor(nextRandom(run) * (index + 1)); + [routes[index], routes[target]] = [routes[target], routes[index]]; + } + return routes.slice(0, count).sort((left, right) => left - right); +}; + +export const generateActMap = (run, actIndex) => { + const act = config.acts[actIndex]; + const nodes = []; + const edges = []; + const floors = []; + const lastFloor = act.floorNodes.length - 1; + act.floorNodes.forEach((count, floor) => { + const floorNodes = []; + const routes = getFloorRoutes(run, act, floor, lastFloor); + routes.forEach((routeIndex, index) => { + let type; + if (floor == lastFloor) type = "boss"; + else if (floor == 0) type = "battle"; + else type = weightedType(run, act.nodeWeights); + const id = `${act.id}_f${floor}_n${index}_${Math.floor(nextRandom(run) * 1e6)}`; + const node = { + id, + floor, + type, + routeIndex, + x: Math.round((routeIndex + 1) * 100 / (config.routeCount + 1)), + y: Math.round(90 - floor * 80 / Math.max(1, lastFloor)), + completed: false, + }; + nodes.push(node); + floorNodes.push(node); + }); + floors.push(floorNodes); + }); + for (let floor = 0; floor < floors.length - 1; floor++) { + connectFloors(run, edges, floors[floor], floors[floor + 1]); + } + return { + layoutVersion: config.mapLayoutVersion, + routeCount: config.routeCount, + actIndex, + actId: act.id, + nodes, + edges, + currentNodeId: null, + completedNodeIds: [], + }; +}; + +const createDeck = () => config.startingDeck.map((entry, index) => ({ + id: `mengsan_card_${Date.now()}_${index}`, + suit: entry[0], + number: entry[1], + name: entry[2], + nature: entry[3] || null, + affixes: entry[4] ? entry[4].slice() : [], + upgrade: 0, +})); + +export const createRun = character => { + const seed = (Date.now() ^ Math.floor(Math.random() * 0x7fffffff)) >>> 0; + const run = { + schemaVersion: config.schemaVersion, + runId: `mengsan_${Date.now()}`, + seed, + randomState: seed, + status: "running", + revision: 0, + actIndex: 0, + player: { + id: "player_1", + ownerId: "local", + character, + hp: null, + maxHp: null, + gold: 0, + deck: createDeck(), + permanentSkills: [], + temporarySkills: [], + items: [], + handLimitBonus: 0, + }, + statistics: { + completedNodes: 0, + defeatedEnemies: 0, + goldEarned: 0, + }, + }; + run.map = generateActMap(run, 0); + return run; +}; + +export const getSelectableNodes = run => { + const map = run.map; + if (!map.currentNodeId) { + return map.nodes.filter(node => node.floor == 0 && !node.completed); + } + const ids = map.edges.filter(edge => edge[0] == map.currentNodeId).map(edge => edge[1]); + return map.nodes.filter(node => ids.includes(node.id) && !node.completed); +}; + +export const completeNode = (run, nodeId) => { + const node = run.map.nodes.find(current => current.id == nodeId); + if (!node || node.completed) return false; + node.completed = true; + run.map.currentNodeId = node.id; + run.map.completedNodeIds.push(node.id); + run.statistics.completedNodes++; + run.revision++; + return true; +}; + +export const insertStoryNode = (run, sourceId) => { + const map = run.map; + const source = map.nodes.find(node => node.id == sourceId); + const edgeIndex = map.edges.findIndex(edge => edge[0] == sourceId); + if (!source || edgeIndex < 0) return null; + const oldEdge = map.edges[edgeIndex]; + const target = map.nodes.find(node => node.id == oldEdge[1]); + if (!target) return null; + const node = { + id: `story_${hashText(`${run.runId}_${sourceId}_${run.revision}`)}`, + floor: source.floor + 0.45, + type: "story", + x: Math.max(8, Math.min(92, Math.round((source.x + target.x) / 2 + (nextRandom(run) - 0.5) * 16))), + y: Math.round((source.y + target.y) / 2), + completed: false, + dynamic: true, + }; + map.edges.splice(edgeIndex, 1, [source.id, node.id], [node.id, target.id]); + map.nodes.push(node); + return node; +}; + +export const enterNextAct = run => { + run.actIndex++; + if (run.actIndex >= config.acts.length) { + run.status = "completed"; + return false; + } + run.map = generateActMap(run, run.actIndex); + run.revision++; + return true; +}; + +export const getNodeEncounter = (run, node) => { + const act = config.acts[run.actIndex]; + let pool = act.enemies; + if (node.type == "elite" || node.type == "story") pool = act.eliteEnemies?.length ? act.eliteEnemies : act.enemies; + if (node.type == "boss") return { enemy: act.boss, gold: act.baseGold * 3, boss: true }; + return { + enemy: randomGet(run, pool.filter(Boolean)), + gold: node.type == "elite" || node.type == "story" ? act.baseGold * 2 : act.baseGold, + boss: false, + }; +}; diff --git a/mengsan/style.css b/mengsan/style.css new file mode 100644 index 0000000..9f44f02 --- /dev/null +++ b/mengsan/style.css @@ -0,0 +1,258 @@ +.mengsan-overlay-shuying { + position: fixed; + inset: 0; + z-index: 80; + display: flex; + align-items: center; + justify-content: center; + box-sizing: border-box; + padding: 18px; + background: radial-gradient(circle at 50% 20%, rgba(68, 62, 83, 0.72), rgba(8, 10, 15, 0.96)); + color: #f6ead0; + font-family: "Microsoft YaHei", sans-serif; +} + +.mengsan-overlay-shuying *, +.mengsan-overlay-shuying *::before, +.mengsan-overlay-shuying *::after { + box-sizing: border-box; + float: none !important; + writing-mode: horizontal-tb !important; + -webkit-writing-mode: horizontal-tb !important; + text-orientation: mixed !important; + letter-spacing: normal; +} + +.mengsan-panel-shuying { + position: relative !important; + left: auto !important; + top: auto !important; + right: auto !important; + bottom: auto !important; + display: flex !important; + flex-direction: column !important; + flex: none !important; + width: min(720px, 94vw); + height: auto !important; + min-height: 160px; + max-height: 92vh; + overflow: auto; + box-sizing: border-box; + padding: 22px; + border: 1px solid rgba(232, 202, 140, 0.55); + border-radius: 14px; + background: linear-gradient(145deg, rgba(31, 28, 38, 0.98), rgba(17, 19, 26, 0.98)); + box-shadow: 0 20px 70px rgba(0, 0, 0, 0.58), inset 0 1px rgba(255, 255, 255, 0.08); +} + +.mengsan-panel-shuying > .mengsan-title-shuying, +.mengsan-panel-shuying > .mengsan-description-shuying, +.mengsan-panel-shuying > .mengsan-choice-list-shuying, +.mengsan-panel-shuying > .mengsan-map-status-shuying, +.mengsan-panel-shuying > .mengsan-map-shuying, +.mengsan-choice-list-shuying > .mengsan-choice-shuying, +.mengsan-choice-shuying > .mengsan-choice-name-shuying, +.mengsan-choice-shuying > .mengsan-choice-description-shuying { + position: relative !important; + left: auto !important; + top: auto !important; + right: auto !important; + bottom: auto !important; + transform: none !important; +} + +.mengsan-title-shuying { + display: block !important; + flex: none !important; + width: 100% !important; + height: auto !important; + margin-bottom: 14px; + color: #f2d28e; + font-size: 30px; + font-weight: 700; + text-align: center; + letter-spacing: 4px; + line-height: 1.35; + white-space: normal !important; +} + +.mengsan-description-shuying, +.mengsan-map-status-shuying { + display: block !important; + flex: none !important; + width: 100% !important; + height: auto !important; + margin-bottom: 14px; + color: rgba(255, 255, 255, 0.78); + text-align: center; + line-height: 1.6; +} + +.mengsan-choice-list-shuying { + display: grid; + flex: none !important; + width: 100% !important; + height: auto !important; + gap: 12px; +} + +.mengsan-choice-shuying { + display: block !important; + width: 100% !important; + height: auto !important; + min-height: 64px; + padding: 14px 16px; + border: 1px solid rgba(242, 210, 142, 0.34); + border-radius: 9px; + background: rgba(255, 255, 255, 0.055); + cursor: pointer; + transition: transform 0.16s ease, border-color 0.16s ease, background 0.16s ease; +} + +.mengsan-choice-shuying:hover { + transform: translateY(-2px); + border-color: rgba(242, 210, 142, 0.8); + background: rgba(242, 210, 142, 0.12); +} + +.mengsan-choice-shuying.disabled { + cursor: default; + opacity: 0.35; + filter: grayscale(1); +} + +.mengsan-choice-name-shuying { + display: block !important; + width: 100% !important; + height: auto !important; + color: #f6dca5; + font-size: 18px; +} + +.mengsan-choice-description-shuying { + display: block !important; + width: 100% !important; + height: auto !important; + margin-top: 5px; + color: rgba(255, 255, 255, 0.65); + font-size: 13px; +} + +.mengsan-map-panel-shuying { + position: absolute !important; + inset: 0 !important; + width: 100% !important; + height: 100% !important; + min-width: 100%; + min-height: 100%; + max-width: none !important; + max-height: none !important; + padding: clamp(12px, 2vw, 24px); + border: 0; + border-radius: 0; + background: transparent; + box-shadow: none; + overflow: hidden; +} + +.mengsan-map-overlay-shuying { + padding: 0 !important; + background: + radial-gradient(circle at 50% 18%, rgba(67, 59, 82, 0.82), rgba(9, 10, 16, 0.97)), + repeating-linear-gradient(12deg, rgba(255, 255, 255, 0.018) 0 2px, transparent 2px 12px); +} + +.mengsan-map-panel-shuying > .mengsan-title-shuying { + margin-bottom: 6px; + font-size: clamp(24px, 3vw, 36px); +} + +.mengsan-map-panel-shuying > .mengsan-map-status-shuying { + margin-bottom: 10px; +} + +.mengsan-map-shuying { + position: relative; + display: block !important; + flex: 1 1 auto !important; + width: 100%; + height: auto !important; + min-height: 0; + overflow: hidden; + border-radius: 0; + background: transparent; +} + +.mengsan-lines-shuying { + position: absolute; + inset: 0; + width: 100%; + height: 100%; + overflow: visible; + pointer-events: none; +} + +.mengsan-lines-shuying line { + vector-effect: non-scaling-stroke; + stroke: rgba(232, 219, 190, 0.68); + stroke-width: 2; + stroke-linecap: round; + stroke-dasharray: 6 5; +} + +.mengsan-lines-shuying line.completed { + stroke: rgba(243, 207, 128, 0.95); +} + +.mengsan-lines-shuying line.dynamic { + stroke: rgba(118, 205, 255, 0.95); + stroke-dasharray: 3 4; +} + +.mengsan-node-shuying { + position: absolute; + width: 66px; + height: 40px; + transform: translate(-50%, -50%); + display: flex; + align-items: center; + justify-content: center; + box-sizing: border-box; + border: 2px solid var(--node-color); + border-radius: 50%; + background: radial-gradient(circle at 38% 28%, rgba(255, 255, 255, 0.2), transparent 42%), var(--node-color); + box-shadow: 0 5px 15px rgba(0, 0, 0, 0.42); + color: #fff4df; + font-size: 12px; + text-align: center; + user-select: none; +} + +.mengsan-node-shuying.selectable { + cursor: pointer; + animation: mengsan-node-pulse-shuying 1.6s infinite; +} + +.mengsan-node-shuying.locked { + opacity: 0.45; + filter: grayscale(0.65); +} + +.mengsan-node-shuying.completed { + opacity: 0.72; + box-shadow: 0 0 0 3px rgba(244, 211, 139, 0.3); +} + +@keyframes mengsan-node-pulse-shuying { + 0%, 100% { box-shadow: 0 0 0 0 rgba(242, 207, 132, 0.18), 0 5px 15px rgba(0, 0, 0, 0.42); } + 50% { box-shadow: 0 0 0 8px rgba(242, 207, 132, 0), 0 5px 18px rgba(0, 0, 0, 0.55); } +} + +@media (max-width: 600px) { + .mengsan-overlay-shuying { padding: 7px; } + .mengsan-panel-shuying { padding: 14px; } + .mengsan-title-shuying { font-size: 24px; } + .mengsan-map-panel-shuying { padding: 8px; } + .mengsan-map-shuying { min-height: 0; } + .mengsan-node-shuying { width: 54px; height: 36px; font-size: 11px; } +} diff --git a/pve/boss/character.js b/pve/boss/character.js index 3348230..b09aa6d 100644 --- a/pve/boss/character.js +++ b/pve/boss/character.js @@ -1,227 +1,15 @@ -const characters = { - zishu_boss_shuying: { - sex: "male", - group: "qun", - hp: 4, - skills: ["zishu_shuying"], - isHiddenBoss: true, - isBossAllowed: true, - img: "extension/术樱包/pve/images/十二生肖/zishu_boss_shuying.png", - dieAudios: ["ext:术樱包/pve/audio/dead/十二生肖/zishu.mp3"] - }, - // chouniu_boss_shuying: {}, - // yinhu_boss_shuying: {}, - // maotu_boss_shuying: {}, - chenlong_boss_shuying: { - sex: "male", - group: "qun", - hp: 4, - skills: ["chenlong_shuying"], - isHiddenBoss: true, - isBossAllowed: true, - img: "extension/术樱包/pve/images/十二生肖/chenlong_boss_shuying.png", - dieAudios: ["ext:术樱包/pve/audio/dead/十二生肖/chenlong.mp3"] - }, - // sishe_boss_shuying: {}, - wuma_boss_shuying: { - sex: "male", - group: "qun", - hp: 4, - skills: ["wuma_shuying"], - isHiddenBoss: true, - isBossAllowed: true, - img: "extension/术樱包/pve/images/十二生肖/wuma_boss_shuying.png", - dieAudios: ["ext:术樱包/pve/audio/dead/十二生肖/wuma_shuying.mp3"] - }, - weiyang_boss_shuying: { - sex: "female", - group: "qun", - hp: 4, - skills: ["weiyang_shuying"], - isHiddenBoss: true, - isBossAllowed: true, - img: "extension/术樱包/pve/images/十二生肖/weiyang_boss_shuying.png", - dieAudios: ["ext:术樱包/pve/audio/dead/十二生肖/weiyang.mp3"] - }, - shenhou_boss_shuying: { - sex: "male", - group: "qun", - hp: 4, - skills: ["shenhou_shuying"], - isHiddenBoss: true, - isBossAllowed: true, - img: "extension/术樱包/pve/images/十二生肖/shenhou_boss_shuying.png", - dieAudios: ["ext:术樱包/pve/audio/dead/十二生肖/shenhou.mp3"] - }, - // youji_boss_shuying: {}, - xvgou_boss_shuying: { - sex: "male", - group: "qun", - hp: 4, - skills: ["xvgou_shuying"], - isHiddenBoss: true, - isBossAllowed: true, - img: "extension/术樱包/pve/images/十二生肖/xvgou_boss_shuying.png", - dieAudios: ["ext:术樱包/pve/audio/dead/十二生肖/xvgou.mp3"] - }, - // haizhu_boss_shuying: {}, +import shiershengxiao from "./sets/shiershengxiao/character.js"; +import zhuoguiquxie from "./sets/zhuoguiquxie/character.js"; +import qingqingzijin from "./sets/qingqingzijin/character.js"; +import tianshu from "./sets/tianshu/character.js"; +import mergeBossModules from "./merge.js"; - heibaiwuchang_boss_shuying: - { - sex: "male", - group: "qun", - hp: 4, - skills: ["mizui_shuying", "qiangzheng_shuying"], - isHiddenBoss: true, - isBossAllowed: true, - img: "extension/术樱包/pve/images/捉鬼驱邪/heibaiwuchang_boss_shuying.png" - }, - niutoumamian_boss_shuying: { - sex: "male", - group: "qun", - hp: 4, - skills: ["shiyv_shuying", "manji_shuying", "xiaoshou_shuying"], - isHiddenBoss: true, - isBossAllowed: true, - img: "extension/术樱包/pve/images/捉鬼驱邪/niutoumamian_boss_shuying.png" - }, - yvsai_boss_shuying: { - sex: "female", - group: "qun", - hp: 4, - skills: ["anchao_shuying", "guixi_shuying"], - isHiddenBoss: true, - isBossAllowed: true, - img: "extension/术樱包/pve/images/捉鬼驱邪/yvsai_boss_shuying.png" - }, - huangfeng_boss_shuying: { - sex: "male", - group: "qun", - hp: 4, - skills: ["mingchong_shuying", "duzhen_shuying"], - isHiddenBoss: true, - isBossAllowed: true, - img: "extension/术樱包/pve/images/捉鬼驱邪/huangfeng_boss_shuying.png" - }, - riyeyoushen_boss_shuying: { - sex: "male", - group: "qun", - hp: 4, - skills: ["huiyun_shuying", "yezhong_shuying", "zhoucha_shuying"], - isHiddenBoss: true, - isBossAllowed: true, - img: "extension/术樱包/pve/images/捉鬼驱邪/riyeyoushen_boss_shuying.png" - }, - - - - caocao_boss_shuying: { - sex: "male", - group: "wei", - hp: 4, - skills: ["jianxiong_shuying", "lingba_shuying"], - isHiddenBoss: true, - isBossAllowed: true, - img: "extension/术樱包/pve/images/青青子衿/caocao_boss_shuying.jpg", - dieAudios: ["ext:术樱包/pve/audio/dead/青青子衿/caocao_boss_shuying.mp3"] - }, - simayi_boss_shuying: { - sex: "male", - group: "wei", - hp: 4, - skills: ["fankui_shuying", "guicai_shuying", "langgu_shuying"], - isHiddenBoss: true, - isBossAllowed: true, - img: "extension/术樱包/pve/images/青青子衿/simayi_boss_shuying.jpg", - dieAudios: ["ext:术樱包/pve/audio/dead/青青子衿/simayi_boss_shuying.mp3"] - }, - lvbu_boss_shuying: { - sex: "male", - group: "qun", - hp: 4, - skills: ["mashu", "wushuang_shuying", "shenij_shuying"], - isHiddenBoss: true, - isBossAllowed: true, - img: "extension/术樱包/pve/images/青青子衿/lvbu_boss_shuying.jpg", - dieAudios: ["ext:术樱包/pve/audio/dead/青青子衿/lvbu_boss_shuying.mp3"] - }, - dongzhuo_boss_shuying: { - sex: "male", - group: "qun", - hp: 4, - skills: ["jiuchi_shuying", "roulin_shuying", "baonue_shuying"], - isHiddenBoss: true, - isBossAllowed: true, - img: "extension/术樱包/pve/images/青青子衿/dongzhuo_boss_shuying.jpg", - dieAudios: ["ext:术樱包/pve/audio/dead/青青子衿/dongzhuo_boss_shuying.mp3"] - }, - - shuishengonggong_boss_shuying: - { - sex: "male", - group: "shen", - hp: 4, - skills: ["tuanliu_shuying", "juehong_shuying"], - isHiddenBoss: true, - isBossAllowed: true, - img: "extension/术樱包/pve/images/天书乱斗/shuishengonggong_boss_shuying.png" - }, - // huoshenzhurong_boss_shuying: {}, - - - baiqi_boss_shuying: - { - sex: "male", - group: "shen", - hp: 4, - skills: ["changsheng_shuying", "shashen_shuying", "wuan_shuying"], - isHiddenBoss: true, - isBossAllowed: true, - img: "extension/术樱包/pve/images/天书乱斗/baiqi_boss_shuying.png" - }, - - kuafu_boss_shuying: - { - sex: "male", - group: "shen", - hp: 4, - skills: ["shenqu_shuying", "lieben_shuying", "yinjiang_shuying", "zhuri_shuying"], - isHiddenBoss: true, - isBossAllowed: true, - img: "extension/术樱包/pve/images/天书乱斗/kuafu_boss_shuying.png" - }, - - shaohao_boss_shuying: - { - sex: "male", - group: "shen", - hp: 4, - skills: ["baiyi_shuying", "shenen_shuying",], - isHiddenBoss: true, - isBossAllowed: true, - img: "extension/术樱包/pve/images/天书乱斗/shaohao_boss_shuying.png" - }, - xuannv_boss_shuying: { - sex: "female", - group: "shen", - hp: 4, - skills: ["xuanlie_shuying", "jiutian_shuying", "dishi_shuying"], - isHiddenBoss: true, - isBossAllowed: true, - img: "extension/术樱包/pve/images/天书乱斗/xuannv_boss_shuying.png", - dieAudios: ["ext:术樱包/pve/audio/dead/天书乱斗/xuannv_boss_shuying.mp3"] - }, - hanba_boss_shuying: { - sex: "female", - group: "shen", - hp: 4, - skills: ["xinji_shuying", "zhiri_shuying", "fenshi_shuying"], - isHiddenBoss: true, - isBossAllowed: true, - img: "extension/术樱包/pve/images/天书乱斗/hanba_boss_shuying.png", - dieAudios: ["ext:术樱包/pve/audio/dead/天书乱斗/hanba_boss_shuying.mp3"] - }, - -}; +// 汇总所有主题武将,PVE 玩法只需引用唯一的 BOSS ID。 +const characters = mergeBossModules("武将", [ + ["十二生肖", shiershengxiao], + ["捉鬼驱邪", zhuoguiquxie], + ["青青子衿", qingqingzijin], + ["天书乱斗", tianshu], +]); export default characters; diff --git a/pve/boss/dynamicTranslate.js b/pve/boss/dynamicTranslate.js index 0e41471..2912e8a 100644 --- a/pve/boss/dynamicTranslate.js +++ b/pve/boss/dynamicTranslate.js @@ -1,205 +1,13 @@ -import { lib, game, ui, get, ai, _status } from "../../../../noname.js"; -import tianshuConfig from "../../tianshu/config.js"; +import zhuoguiquxie from "./sets/zhuoguiquxie/dynamicTranslate.js"; +import qingqingzijin from "./sets/qingqingzijin/dynamicTranslate.js"; +import tianshu from "./sets/tianshu/dynamicTranslate.js"; +import mergeBossModules from "./merge.js"; -const getTianshuDifficulty = () => { - return _status[tianshuConfig.settings.difficultyStatusKey] || "normal"; -}; - -const dynamicTranslates = { - //黑白无常 - mizui_shuying(player, skill) { - const num = getTianshuDifficulty() == "normal" ? 1 : 2 - - return `锁定技,你的伤害牌造成伤害后,你随机弃置受伤角色的${num}张牌。` - }, - qiangzheng_shuying(player, skill) { - let num = 2; - if (getTianshuDifficulty() == "hard") num = 4; - if (getTianshuDifficulty() == "nightmare") num = 6; - - return `锁定技,敌方角色的结束阶段,若其手牌数小于${num},你获得其所有手牌。` - }, - xixing_shuying(player, skill) { - const difficulty = getTianshuDifficulty(); - if (difficulty == "nightmare") { - return "锁定技,准备阶段,你对每名敌方角色各造成2点雷电伤害,然后你回复等量体力"; - } - - return "锁定技,准备阶段,随机对敌方体力最多的一名角色造成2点雷电伤害,然后你回复等量体力。"; - }, - - //鱼鳃 - guixi_shuying(player, skill) { - const num = getTianshuDifficulty() == "nightmare" ? 2 : 1 - - return `锁定技,当你受到伤害后,你进行一次判定,若结果为红色,你回复1点体力;若结果不为红色,你摸${num}张牌。`; - }, - - //黄蜂 - mingchong_shuying(player, skill) { - if (getTianshuDifficulty() == "nightmare") { - return "锁定技,你死亡时,杀死你的角色弃置所有牌。" - } - - return "锁定技,你死亡时,杀死你的角色随机弃置一半的牌。" - }, - duzhen_shuyingg(player, skill) { - if (getTianshuDifficulty() == "nightmare") { - return "锁定技,你使用牌指定单一敌方角色为目标后,该角色随机弃置1张牌(优先装备区的牌)。" - } - - return "锁定技,你使用牌指定单一敌方角色为目标后,该角色随机弃置2张牌(优先装备区的牌)。" - - }, - - //日夜游神 - mingchong_shuying(player, skill) { - const num = getTianshuDifficulty() == "normal" ? 1 : 2 - - return `出牌阶段限一次,你可以展示一名敌方角色的手牌,并弃置其中至多两张牌。然后你可以弃置一张与该角色弃置牌牌名相同的牌,对其造成${num}点伤害。` - }, - yezhong_shuying(player, skill) { - let num = 1; - if (getTianshuDifficulty() == "hard") num = 2; - if (getTianshuDifficulty() == "nightmare") num = 3; - - return `锁定技,结束阶段,你进行一次判定并获得判定牌,若次牌为黑色,每名敌方角色随机弃${num}张手牌。` - }, - zhoucha_shuying(player, skill) { - let num = 1; - if (getTianshuDifficulty() == "hard") num = 2; - if (getTianshuDifficulty() == "nightmare") num = 3; - - return `锁定技,准备阶段,你进行一次判定并获得判定牌,若此牌为红色,你本回合使用【杀】的次数+${num}。` - }, - - - //曹操 - lingba_shuying(player, skill) { - const num = getTianshuDifficulty() == "nightmare" ? 2 : 1 - - return `锁定技,你的回合开始时,若你手牌数为全场最多,则对一名随机敌人造成${num}点伤害。若你手牌数大于等于你体力值的两倍,则改为对所有敌人造成伤害。` - }, - yishen_shuying(player, skill) { - const difficulty = getTianshuDifficulty(); - if (difficulty == "nightmare") { - return "当你回复体力时,可以改为获得所有敌人各一张随机装备。"; - } - - return "当你回复体力时,可以改为获得一名随机敌方角色一张随机装备。"; - }, - - //司马懿 - yuanlv_shuying(player, skill) { - const difficulty = getTianshuDifficulty(); - - if (difficulty == "nightmare") { - return "当你使用锦囊牌对敌方角色造成伤害时,你可以防止该伤害,改为摸一张牌且该敌方角色对你造成过1点伤害。"; - } - - return "当你使用锦囊牌对敌方角色造成伤害时,你可以防止该伤害,改为摸一张牌且该敌方角色对你造成1点伤害。"; - - }, - - //吕布 - wushuang_shuying(player, skill) { - const num = getTianshuDifficulty() == "nightmare" ? 2 : 1 - - return `锁定技。①你使用的【杀】需两张【闪】才能抵消;与你进行【决斗】的角色每次需要打出两张【杀】。②每回合限${num}次,当你使用【杀】或【决斗】造成伤害时,若受伤角色没有使用或打出过【杀】或【闪】响应此牌,则此伤害+1。`; - }, - shenij_shuying(player, skill) { - const difficulty = getTianshuDifficulty(); - let card = 2; - let sha = 1; - let target = 1; - - if (difficulty == "hard") { - card = 3; - sha = 2; - } else if (difficulty == "nightmare") { - card = 4; - sha = 2; - target = 2; - } - - return `判定阶段,你可以弃置两张手牌,然后弃置你判定区里的牌;摸牌阶段,你多摸${card}张牌;出牌阶段,你可以多使用${sha}张【杀】,你的【杀】可以多指定${target}名角色为目标。`; - }, - zhanjia_shuying(player, skill) { - const num = getTianshuDifficulty() == "nightmare" ? 3 : 2 - - return `锁定技,每回合限一次,当你受到大于2点的伤害时,将此伤害减至2点,然后摸${num}张牌。` - }, - - //董卓 - baonue_shuying(player, skill) { - let num = 3; - if (getTianshuDifficulty() == "hard") num = 4; - if (getTianshuDifficulty() == "nightmare") num = 5; - - return `锁定技,回合开始时,你摸X张牌并对至多X名角色造成1点伤害,然后你失去1点体力。X为你已损失体力且最大为${num}。` - }, - - //水神共工 - tuanliu_shuying(player, skill) { - let hp = 2; - let card = 4; - let damage = 1; - - if (getTianshuDifficulty() == "nightmare") { - hp = 3; - card = 6; - } - return `锁定技,结束阶段,若本回合进入弃牌堆的卡牌数量:大于4,你回复${hp}点体力;大于5,你摸${card}张牌;大于9,你对所有敌方角色造成${damage}点伤害。` - }, - - //少昊 - baiyi_shuying(player, skill) { - let num = 1; - const difficulty = getTianshuDifficulty(); - if (difficulty == "hard") { - num = 2; - } else if (difficulty == "nightmare") { - num = 3; - } - - return `锁定技,每名敌方角色的回合开始时,若当前轮数小于3,你随机获得其${num}张牌;若当前轮数小于5,对其造成${num}点雷电伤害;若当前轮数小于7,其随机弃置${num}张牌。`; - }, - - //玄女 - xuanlie_shuying(player, skill) { - const difficulty = getTianshuDifficulty(); - const num = difficulty == "normal" ? 1 : 2; - - return `锁定技,回合结束时,对所有本回合你获得过其牌的敌方角色依次造成${num}点伤害。`; - }, - jiutian_shuying(player, skill) { - const difficulty = getTianshuDifficulty(); - const num = difficulty == "normal" ? 1 : 2; - const extra = - difficulty == "normal" || difficulty == "hard" - ? "" - : "若这些牌包含4种花色,这些角色再额外失去1点体力。"; - - return `锁定技,准备阶段,你获得所有敌方角色各${num}张手牌。若你以此法获得的牌包含2种颜色,则对所有你以此法获得其牌的敌方角色造成1点伤害。${extra}`; - }, - - xinji_shuying(player, skill) { - const difficulty = getTianshuDifficulty(); - const num = difficulty == "nightmare" ? 2 : 1; - - return `锁定技,当友方于回合外因弃置而失去手牌时,你对当前角色造成${num}点伤害。` - }, - zhiri_shuying(player, skill) { - let num = 1; - const difficulty = getTianshuDifficulty(); - if (difficulty == "hard") { - num = 2; - } else if (difficulty == "nightmare") { - num = 3; - } - - return `锁定技,当敌方角色使用红色锦囊牌指定目标后,你摸${num}张牌。` - }, -}; +// 汇总存在动态描述的主题技能;无动态描述的主题不需要空模块占位。 +const dynamicTranslates = mergeBossModules("动态翻译", [ + ["捉鬼驱邪", zhuoguiquxie], + ["青青子衿", qingqingzijin], + ["天书乱斗", tianshu], +]); export default dynamicTranslates; diff --git a/pve/boss/merge.js b/pve/boss/merge.js new file mode 100644 index 0000000..98a5abb --- /dev/null +++ b/pve/boss/merge.js @@ -0,0 +1,16 @@ +// 合并各主题的同类数据;重复 ID 保留先加载项,跳过后加载项,避免中断整个 BOSS 包。 +const mergeBossModules = (dataType, modules) => { + const result = {}; + for (const [moduleName, data] of modules) { + for (const [id, value] of Object.entries(data || {})) { + if (Object.prototype.hasOwnProperty.call(result, id)) { + console.warn(`术樱包活动BOSS:跳过${moduleName}中重复的${dataType}ID“${id}”`); + continue; + } + result[id] = value; + } + } + return result; +}; + +export default mergeBossModules; diff --git a/pve/boss/sets/qingqingzijin/character.js b/pve/boss/sets/qingqingzijin/character.js new file mode 100644 index 0000000..c831aa9 --- /dev/null +++ b/pve/boss/sets/qingqingzijin/character.js @@ -0,0 +1,44 @@ +const characters = { + caocao_boss_shuying: { + sex: "male", + group: "wei", + hp: 4, + skills: ["jianxiong_shuying", "lingba_shuying"], + isHiddenBoss: true, + isBossAllowed: true, + img: "extension/术樱包/pve/images/青青子衿/caocao_boss_shuying.jpg", + dieAudios: ["ext:术樱包/pve/audio/dead/青青子衿/caocao_boss_shuying.mp3"] + }, + simayi_boss_shuying: { + sex: "male", + group: "wei", + hp: 4, + skills: ["fankui_shuying", "guicai_shuying", "langgu_shuying"], + isHiddenBoss: true, + isBossAllowed: true, + img: "extension/术樱包/pve/images/青青子衿/simayi_boss_shuying.jpg", + dieAudios: ["ext:术樱包/pve/audio/dead/青青子衿/simayi_boss_shuying.mp3"] + }, + lvbu_boss_shuying: { + sex: "male", + group: "qun", + hp: 4, + skills: ["mashu", "wushuang_shuying", "shenij_shuying"], + isHiddenBoss: true, + isBossAllowed: true, + img: "extension/术樱包/pve/images/青青子衿/lvbu_boss_shuying.jpg", + dieAudios: ["ext:术樱包/pve/audio/dead/青青子衿/lvbu_boss_shuying.mp3"] + }, + dongzhuo_boss_shuying: { + sex: "male", + group: "qun", + hp: 4, + skills: ["jiuchi_shuying", "roulin_shuying", "baonue_shuying"], + isHiddenBoss: true, + isBossAllowed: true, + img: "extension/术樱包/pve/images/青青子衿/dongzhuo_boss_shuying.jpg", + dieAudios: ["ext:术樱包/pve/audio/dead/青青子衿/dongzhuo_boss_shuying.mp3"] + }, +}; + +export default characters; diff --git a/pve/boss/sets/qingqingzijin/dynamicTranslate.js b/pve/boss/sets/qingqingzijin/dynamicTranslate.js new file mode 100644 index 0000000..b2c3677 --- /dev/null +++ b/pve/boss/sets/qingqingzijin/dynamicTranslate.js @@ -0,0 +1,71 @@ +import { getTianshuDifficulty } from "../../shared.js"; + +const dynamicTranslates = { + //曹操 + lingba_shuying(player, skill) { + const num = getTianshuDifficulty() == "nightmare" ? 2 : 1 + + return `锁定技,你的回合开始时,若你手牌数为全场最多,则对一名随机敌人造成${num}点伤害。若你手牌数大于等于你体力值的两倍,则改为对所有敌人造成伤害。` + }, + yishen_shuying(player, skill) { + const difficulty = getTianshuDifficulty(); + if (difficulty == "nightmare") { + return "当你回复体力时,可以改为获得所有敌人各一张随机装备。"; + } + + return "当你回复体力时,可以改为获得一名随机敌方角色一张随机装备。"; + }, + + //司马懿 + yuanlv_shuying(player, skill) { + const difficulty = getTianshuDifficulty(); + + if (difficulty == "nightmare") { + return "当你使用锦囊牌对敌方角色造成伤害时,你可以防止该伤害,改为摸一张牌且该敌方角色对你造成过1点伤害。"; + } + + return "当你使用锦囊牌对敌方角色造成伤害时,你可以防止该伤害,改为摸一张牌且该敌方角色对你造成1点伤害。"; + + }, + + //吕布 + wushuang_shuying(player, skill) { + const num = getTianshuDifficulty() == "nightmare" ? 2 : 1 + + return `锁定技。①你使用的【杀】需两张【闪】才能抵消;与你进行【决斗】的角色每次需要打出两张【杀】。②每回合限${num}次,当你使用【杀】或【决斗】造成伤害时,若受伤角色没有使用或打出过【杀】或【闪】响应此牌,则此伤害+1。`; + }, + shenij_shuying(player, skill) { + const difficulty = getTianshuDifficulty(); + let card = 2; + let sha = 1; + let target = 1; + + if (difficulty == "hard") { + card = 3; + sha = 2; + } else if (difficulty == "nightmare") { + card = 4; + sha = 2; + target = 2; + } + + return `判定阶段,你可以弃置两张手牌,然后弃置你判定区里的牌;摸牌阶段,你多摸${card}张牌;出牌阶段,你可以多使用${sha}张【杀】,你的【杀】可以多指定${target}名角色为目标。`; + }, + zhanjia_shuying(player, skill) { + const num = getTianshuDifficulty() == "nightmare" ? 3 : 2 + + return `锁定技,每回合限一次,当你受到大于2点的伤害时,将此伤害减至2点,然后摸${num}张牌。` + }, + + //董卓 + baonue_shuying(player, skill) { + let num = 3; + if (getTianshuDifficulty() == "hard") num = 4; + if (getTianshuDifficulty() == "nightmare") num = 5; + + return `锁定技,回合开始时,你摸X张牌并对至多X名角色造成1点伤害,然后你失去1点体力。X为你已损失体力且最大为${num}。` + }, + +}; + +export default dynamicTranslates; diff --git a/pve/boss/sets/qingqingzijin/skill.js b/pve/boss/sets/qingqingzijin/skill.js new file mode 100644 index 0000000..f3a505f --- /dev/null +++ b/pve/boss/sets/qingqingzijin/skill.js @@ -0,0 +1,676 @@ +import { lib, game, ui, get, ai, _status, getTianshuDifficulty } from "../../shared.js"; + +const skills = { + //曹操 over + jianxiong_shuying: { + mode: ["boss"], + audio: "ext:术樱包/pve/audio/skill/青青子衿:1", + trigger: { player: "damageEnd" }, + filter(event, player) { + return event.num > 0; + }, + async cost(event, trigger, player) { + event.result = await player.chooseBool(get.prompt2(event.skill)) + .set("ai", () => true) + .forResult(); + }, + async content(event, trigger, player) { + if (get.itemtype(trigger.cards) == "cards" && get.position(trigger.cards[0], true) == "o") { + await player.gain(trigger.cards, "gain2"); + } + await player.draw(); + }, + ai: { + maixie: true, + maixie_hp: true, + effect: { + target(card, player, target) { + if (player.hasSkillTag("jueqing", false, target)) return [1, -1]; + if (get.tag(card, "damage")) return [1, 0.6]; + }, + }, + }, + }, + lingba_shuying: { + mode: ["boss"], + audio: "ext:术樱包/pve/audio/skill/青青子衿:1", + trigger: { player: "phaseBegin" }, + forced: true, + filter(event, player) { + return player.getEnemies(null, false).some(target => target.isIn()) && game.players.every(current => player.countCards("h") >= current.countCards("h")); + }, + async content(event, trigger, player) { + const num = getTianshuDifficulty() == "nightmare" ? 2 : 1 + const enemies = player.getEnemies(null, false).filter(target => target.isIn()); + if (player.countCards("h") >= player.hp * 2) { + for (const target of enemies) { + player.line(target); + await target.damage(num, player); + } + } + else { + const target = enemies.randomGet(); + player.line(target); + await target.damage(num, player); + } + }, + }, + yishen_shuying: { + mode: ["boss"], + audio: "ext:术樱包/pve/audio/skill/青青子衿:1", + trigger: { player: "recoverBegin" }, + filter(event, player) { + return player.getEnemies(null, false).some(target => target.countGainableCards(player, "e") > 0); + }, + async cost(event, trigger, player) { + event.result = await player.chooseBool(get.prompt2(event.skill)) + .set("ai", () => { + const player = get.player(); + const trigger = get.event().getTrigger(); + + if (player.hp <= 1) return false; + if (player.hp + trigger.num <= 2) return false; + + const enemies = player.getEnemies(null, false).filter(target => { + return target.isIn() && target.countGainableCards(player, "e") > 0; + }); + if (!enemies.length) return false; + + const recoverEffect = get.recoverEffect(player, player, player); + const difficulty = getTianshuDifficulty(); + + if (difficulty == "nightmare") { + let total = 0; + let max = 0; + for (const target of enemies) { + for (const card of target.getCards("e")) { + const value = get.value(card, target); + total += value; + if (value > max) max = value; + } + } + if (max < 5) return false; + return total >= recoverEffect + 4; + } + + let best = 0; + for (const target of enemies) { + for (const card of target.getCards("e")) { + const value = get.value(card, target); + if (value > best) best = value; + } + } + + if (best < 5) return false; + return best >= recoverEffect; + }) + .forResult(); + }, + async content(event, trigger, player) { + const enemies = player.getEnemies(null, false).filter(target => target.countGainableCards(player, "e") > 0); + const difficulty = getTianshuDifficulty(); + + trigger.cancel(); + + if (difficulty == "nightmare") { + for (const target of enemies) { + player.line(target); + await player.gainPlayerCard({ target, position: "e", forced: true }); + } + } + else { + const target = enemies.randomGet(); + player.line(target); + await player.gainPlayerCard({ target, position: "e", forced: true }); + } + }, + ai: { + threaten: 1.4, + effect: { + target(card, player, target) { + if (get.tag(card, "recover") && target.hasSkill("yishen_shuying")) { + if (target.hp <= 1) return [1, 1.2]; + if (target.hp <= 2) return [1, 0.8]; + + const enemies = target.getEnemies(null, false).filter(current => { + return current.isIn() && current.countGainableCards(target, "e") > 0; + }); + if (!enemies.length) return; + + let best = 0; + for (const enemy of enemies) { + for (const equip of enemy.getCards("e")) { + best = Math.max(best, get.value(equip, enemy)); + } + } + + if (best >= 5) return [1, 0.4]; + } + }, + }, + }, + }, + + //司马懿 over + fankui_shuying: { + mode: ["boss"], + audio: "ext:术樱包/pve/audio/skill/青青子衿:1", + trigger: { player: "damageEnd" }, + logTarget: "source", + filter(event, player) { + return ( + event.num > 0 && + event.source?.isIn() && + player.getEnemies(null, false).includes(event.source) && + event.source.hasGainableCards(player, "he") + ); + }, + async content(event, trigger, player) { + await player.gainPlayerCard({ + target: trigger.source, + position: "he", + forced: true, + }); + }, + ai: { + maixie_defend: true, + effect: { + target(card, player, target) { + if (player.countCards("he") > 1 && get.tag(card, "damage")) { + if (player.hasSkillTag("jueqing", false, target)) return [1, -1.5]; + if (target.getEnemies(null, false).includes(player)) return [1, 1]; + } + }, + }, + }, + }, + guicai_shuying: { + mode: ["boss"], + audio: "ext:术樱包/pve/audio/skill/青青子衿:1", + trigger: { global: "judge" }, + filter(event, player) { + return player.countCards("hes") > 0; + }, + async cost(event, trigger, player) { + event.result = await player.chooseCard(`${get.translation(trigger.player)}的${trigger.judgestr || ""}判定为${get.translation(trigger.player.judging[0])},${get.prompt(event.skill)}`, "hes", card => { + const player = get.player(); + const mod2 = game.checkMod(card, player, "unchanged", "cardEnabled2", player); + if (mod2 != "unchanged") return mod2; + const mod = game.checkMod(card, player, "unchanged", "cardRespondable", player); + if (mod != "unchanged") return mod; + return true; + }).set("ai", card => { + const trigger = get.event().getTrigger(), player = get.player(), judging = trigger.player.judging[0]; + const result = trigger.judge(card) - trigger.judge(judging); + const attitude = player.getEnemies(null, false).includes(trigger.player) ? -1 : 1; + let value = get.value(card, player); + value /= get.subtype(card) == "equip2" ? 2 : 4; + if (result == 0) return 0; + return attitude > 0 ? result - value : -result - value; + }).set("judging", trigger.player.judging[0]).forResult(); + }, + popup: false, + async content(event, trigger, player) { + const next = player.respond(event.cards, event.name, "highlight", "noOrdering"); + await next; + const { cards } = next; + if (cards?.length) { + if (trigger.player.judging[0].clone) { + trigger.player.judging[0].clone.classList.remove("thrownhighlight"); + game.broadcast(card => { + if (card.clone) card.clone.classList.remove("thrownhighlight"); + }, trigger.player.judging[0]); + game.addVideo("deletenode", player, get.cardsInfo([trigger.player.judging[0].clone])); + } + await game.cardsDiscard(trigger.player.judging[0]); + trigger.player.judging[0] = cards[0]; + trigger.orderingCards.addArray(cards); + game.log(trigger.player, "的判定牌改为", cards); + await game.delay(2); + } + }, + ai: { rejudge: true, tag: { rejudge: 1 } }, + }, + langgu_shuying: { + mode: ["boss"], + audio: "ext:术樱包/pve/audio/skill/青青子衿:1", + trigger: { player: "gainAfter" }, + forced: true, + filter(event, player) { + if (player.hasSkill("langgu_shuying_used")) return false; + return game.hasPlayer(current => { + return ( + current != player && + player.getEnemies(null, false).includes(current) && + event.getl?.(current)?.cards2?.length + ); + }); + }, + async content(event, trigger, player) { + const source = game.findPlayer(current => { + return ( + current != player && + player.getEnemies(null, false).includes(current) && + trigger.getl?.(current)?.cards2?.length + ); + }); + if (!source) return; + + player.addTempSkill("langgu_shuying_used", "roundStart"); + + const result = await player.judge(card => get.suit(card) == "spade" ? 2 : 0).forResult(); + if (result.bool && source.isIn() && source.countDiscardableCards(player, "h") > 0) { + player.removeSkill("langgu_shuying_used"); + player.line(source); + const card = source.getCards("h").randomGet(); + await source.discard(card); + } + }, + subSkill: { + used: { charlotte: true }, + }, + }, + yuanlv_shuying: { + mode: ["boss"], + audio: "ext:术樱包/pve/audio/skill/青青子衿:1", + trigger: { source: "damageBegin4" }, + filter(event, player) { + return event.card && get.type2(event.card) == "trick" && event.player?.isIn() && player.getEnemies(null, false).includes(event.player); + }, + async cost(event, trigger, player) { + event.result = await player.chooseBool(get.prompt(event.skill, trigger.player)) + .set("ai", () => true) + .forResult(); + }, + async content(event, trigger, player) { + const target = trigger.player; + trigger.cancel(); + await player.draw(); + target.line(player); + if (getTianshuDifficulty() == "nightmare") { + await player.damage(1, target, "unreal"); + } + else { + await player.damage(1, target); + } + }, + }, + + //吕布 over + wushuang_shuying: { + mode: ["boss"], + audio: "ext:术樱包/pve/audio/skill/青青子衿:2", + trigger: { source: "damageBegin1" }, + forced: true, + locked: true, + usable(skill, player) { + return getTianshuDifficulty() == "nightmare" ? 2 : 1; + }, + logTarget: "player", + filter(event, player) { + const target = event.player; + const evtx = event.getParent(2); + const card = event.card; + if (!card || !["sha", "juedou"].includes(card.name)) return false; + + if (card.name == "sha") { + return !target.hasHistory("useCard", evt => { + return evt.card.name == "shan" && evt.respondTo && evt.getParent(3) == evtx; + }); + } + + return !target.hasHistory("respond", evt => { + return evt.card.name == "sha" && evt.respondTo && evt.getParent(3) == evtx; + }); + }, + async content(event, trigger, player) { + trigger.num++; + }, + group: ["wushuang_shuying_1", "wushuang_shuying_2"], + subSkill: { + 1: { + audio: "wushuang_shuying", + sourceSkill: "wushuang_shuying", + trigger: { player: "useCardToPlayered" }, + forced: true, + logTarget: "target", + filter(event, player) { + return event.card.name == "sha" && !event.getParent().directHit.includes(event.target); + }, + async content(event, trigger, player) { + const id = trigger.target.playerid; + const map = trigger.getParent().customArgs; + if (!map[id]) map[id] = {}; + + if (typeof map[id].shanRequired == "number") map[id].shanRequired++; + else map[id].shanRequired = 2; + }, + ai: { + directHit_ai: true, + skillTagFilter(player, tag, arg) { + if (arg.card.name != "sha" || arg.target.countCards("h", "shan") > 1) return false; + }, + }, + }, + 2: { + audio: "wushuang_shuying", + sourceSkill: "wushuang_shuying", + trigger: { player: "useCardToPlayered", target: "useCardToTargeted" }, + forced: true, + logTarget(trigger, player) { + return player == trigger.player ? trigger.target : trigger.player; + }, + filter(event, player) { + return event.card.name == "juedou"; + }, + async content(event, trigger, player) { + const id = (player == trigger.player ? trigger.target : trigger.player).playerid; + const idt = trigger.target.playerid; + const map = trigger.getParent().customArgs; + + if (!map[idt]) map[idt] = {}; + if (!map[idt].shaReq) map[idt].shaReq = {}; + if (!map[idt].shaReq[id]) map[idt].shaReq[id] = 1; + + map[idt].shaReq[id]++; + }, + ai: { + directHit_ai: true, + skillTagFilter(player, tag, arg) { + if ( + arg.card.name != "juedou" || + Math.floor(arg.target.countCards("h", "sha") / 2) > player.countCards("h", "sha") + ) { + return false; + } + }, + }, + }, + }, + ai: { + threaten: 1.8, + directHit_ai: true, + }, + }, + shenij_shuying: { + mode: ["boss"], + audio: "ext:术樱包/pve/audio/skill/青青子衿:2", + group: [ + "shenij_shuying_judge", + "shenij_shuying_draw", + "shenij_shuying_sha", + "shenij_shuying_target", + ], + subSkill: { + judge: { + audio: "shenij_shuying", + trigger: { player: "phaseJudgeBegin" }, + filter(event, player) { + return player.countCards("h") >= 2 && player.countCards("j") > 0; + }, + async cost(event, trigger, player) { + event.result = await player + .chooseToDiscard("h", 2, get.prompt2(event.skill)) + .set("ai", card => { + const player = get.player(); + if (!player.countCards("j")) return 0; + return 7 - get.value(card); + }) + .forResult(); + }, + async content(event, trigger, player) { + const cards = player.getCards("j"); + await player.discard(cards); + }, + ai: { + effect: { + target(card, player, target) { + if (get.type(card) == "delay") return [0, 2]; + }, + }, + }, + }, + draw: { + audio: "shenij_shuying", + trigger: { player: "phaseDrawBegin2" }, + forced: true, + filter(event, player) { + return !event.numFixed; + }, + async content(event, trigger, player) { + let num = 2; + if (getTianshuDifficulty() == "hard") num = 3; + if (getTianshuDifficulty() == "nightmare") num = 4; + + trigger.num += num; + }, + }, + sha: { + audio: "shenij_shuying", + forced: true, + mod: { + cardUsable(card, player, num) { + let shaNum = 2; + if (getTianshuDifficulty() == "normal") shaNum = 1; + + if (card.name == "sha") return num + shaNum; + }, + }, + }, + target: { + audio: "shenij_shuying", + trigger: { player: "useCard2" }, + filter(event, player) { + if (event.card.name != "sha") return false; + + const info = get.info(event.card); + if (info.allowMultiple == false || info.multitarget || !event.targets) return false; + + const num = getTianshuDifficulty() == "nightmare" ? 2 : 1; + return game.countPlayer(current => { + return !event.targets.includes(current) && + lib.filter.targetEnabled2(event.card, player, current) && + lib.filter.targetInRange(event.card, player, current); + }) > 0; + }, + async cost(event, trigger, player) { + const num = getTianshuDifficulty() == "nightmare" ? 2 : 1; + + event.result = await player + .chooseTarget( + get.prompt(event.skill), + `为此【杀】额外指定至多${get.cnNumber(num)}名目标`, + [1, num], + (card, player, target) => { + const trigger = get.event().getTrigger(); + return !trigger.targets.includes(target) && + lib.filter.targetEnabled2(trigger.card, player, target) && + lib.filter.targetInRange(trigger.card, player, target); + } + ) + .set("ai", target => { + const player = get.player(); + if (!player.getEnemies(null, false).includes(target)) return 0; + return get.effect(target, get.event().getTrigger().card, player, player); + }) + .forResult(); + }, + async content(event, trigger, player) { + for (const target of event.targets) { + player.line(target); + trigger.targets.add(target); + game.log(target, "成为了", trigger.card, "的额外目标"); + } + }, + ai: { + effect: { + player_use(card, player, target) { + if (card.name == "sha" && player.getEnemies(null, false).includes(target)) return [1, 0.5]; + }, + }, + }, + }, + }, + }, + zhanjia_shuying: { + mode: ["boss"], + audio: "ext:术樱包/pve/audio/skill/青青子衿:2", + trigger: { player: "damageBegin4" }, + forced: true, + locked: true, + usable: 1, + filter(event, player) { + return event.num > 2; + }, + async content(event, trigger, player) { + trigger.num = 2; + await player.draw(getTianshuDifficulty() == "nightmare" ? 3 : 2); + }, + ai: { + filterDamage: true, + effect: { + target(card, player, target) { + if (get.tag(card, "damage")) { + if (player.hasSkillTag("jueqing", false, target)) return; + return [1, 0.8]; + } + }, + }, + }, + }, + + //董卓 over + jiuchi_shuying: { + mode: ["boss"], + audio: "ext:术樱包/pve/audio/skill/青青子衿:1", + enable: "chooseToUse", + filterCard(card, player) { + return get.suit(card, player) == "spade"; + }, + position: "h", + viewAs: { name: "jiu" }, + viewAsFilter(player) { + return player.countCards("h", card => get.suit(card, player) == "spade") > 0; + }, + prompt: "将一张黑桃手牌当【酒】使用", + check(card) { + return 6 - get.value(card); + }, + ai: { + order: 3.5, + respondJiu: true, + skillTagFilter(player) { + return player.countCards("h", card => get.suit(card, player) == "spade") > 0; + }, + }, + }, + roulin_shuying: { + mode: ["boss"], + audio: "ext:术樱包/pve/audio/skill/青青子衿:1", + trigger: { player: "useCardToPlayered", target: "useCardToTargeted" }, + forced: true, + locked: true, + logTarget(trigger, player) { + return player == trigger.player ? trigger.target : trigger.player; + }, + filter(event, player) { + if (event.card.name != "sha") return false; + if (player == event.player) return event.target.hasSex("female"); + return event.player.hasSex("female"); + }, + async content(event, trigger, player) { + const id = (player == trigger.player ? trigger.target : player).playerid; + const map = trigger.getParent().customArgs; + if (!map[id]) map[id] = {}; + + if (typeof map[id].shanRequired == "number") map[id].shanRequired++; + else map[id].shanRequired = 2; + }, + ai: { + halfneg: true, + directHit_ai: true, + skillTagFilter(player, tag, arg) { + if (tag === "directHit_ai") return; + if (arg.card.name != "sha" || !arg.target.hasSex("female") || arg.target.countCards("h", "shan") > 1) { + return false; + } + }, + }, + }, + baonue_shuying: { + mode: ["boss"], + audio: "ext:术樱包/pve/audio/skill/青青子衿:1", + trigger: { player: "phaseBegin" }, + forced: true, + locked: true, + filter(event, player) { + return player.getDamagedHp() > 0; + }, + async content(event, trigger, player) { + let maxNum = 3; + if (getTianshuDifficulty() == "hard") maxNum = 4; + if (getTianshuDifficulty() == "nightmare") maxNum = 5; + + const num = Math.min(maxNum, player.getDamagedHp()); + await player.draw(num); + + const result = await player + .chooseTarget(`对至多${get.cnNumber(num)}名角色造成1点伤害`, [1, num], (card, player, target) => { + return target.isIn() && player.getEnemies(null, false).includes(target); + }) + .set("ai", target => { + const player = get.player(); + return get.damageEffect(target, player, player); + }) + .forResult(); + + if (result.bool) { + for (const target of result.targets) { + player.line(target); + await target.damage(1, player); + } + } + + await player.loseHp(); + }, + ai: { + threaten: 2, + }, + }, + qubu_shuying: { + mode: ["boss"], + audio: "ext:术樱包/pve/audio/skill/青青子衿:1", + trigger: { global: "useCardToPlayered" }, + forced: true, + locked: true, + filter(event, player) { + return ( + event.card.name == "sha" && + event.player?.isIn() && + !player.getEnemies(null, false).includes(event.player) && + event.targets?.length > 0 + ); + }, + async content(event, trigger, player) { + const result = await player + .judge(card => get.color(card) == "black" ? 2 : -1) + .set("judge2", result => result.bool) + .forResult(); + + if (result.bool) { + const targets = trigger.targets.filter(target => target.isIn()); + for (const target of targets) { + player.line(target); + await target.damage(1, player); + } + } + }, + ai: { + threaten: 1.8, + }, + }, + + +}; + +export default skills; diff --git a/pve/boss/sets/qingqingzijin/translate.js b/pve/boss/sets/qingqingzijin/translate.js new file mode 100644 index 0000000..cdbe35e --- /dev/null +++ b/pve/boss/sets/qingqingzijin/translate.js @@ -0,0 +1,41 @@ +const translates = { + caocao_boss_shuying: "曹操", + jianxiong_shuying: "奸雄", + jianxiong_shuying_info: "当你受到伤害后,你可以获得对你造成伤害的牌并摸一张牌。", + lingba_shuying: "凌霸", + lingba_shuying_info: "锁定技,你的回合开始时,若你手牌数为全场最多,则对一名随机敌人造成1点伤害。若你手牌数大于等于你体力值的两倍,则改为对所有敌人造成伤害。", + yishen_shuying: "疑神", + yishen_shuying_info: "当你回复体力时,可以改为获得一名随机敌方角色一张随机装备。", + + simayi_boss_shuying: "司马懿", + fankui_shuying: "反馈", + fankui_shuying_info: "当你受到1点伤害后,你可以获得伤害来源的一张牌。", + guicai_shuying: "鬼才", + guicai_shuying_info: "当一名角色的判定牌生效前,你可以打出一张牌代替之。", + langgu_shuying: "狼顾", + langgu_shuying_info: " 锁定技,每回合限一次,当你获得其他角色的牌时,进行一次判定:若结果为黑桃,随机弃置其1张手牌,且视为此技能本回合未发动过。", + yuanlv_shuying: "远虑", + yuanlv_shuying_info: "当你使用锦囊牌对敌方角色造成伤害时,你可以防止该伤害,改为摸一张牌且该敌方角色对你造成1点伤害。", + + lvbu_boss_shuying: "吕布", + wushuang_shuying: "无双", + wushuang_shuying_info: "锁定技。①你使用的【杀】需两张【闪】才能抵消;与你进行【决斗】的角色每次需要打出两张【杀】。②每回合限1次,当你使用【杀】或【决斗】造成伤害时,若受伤角色没有使用或打出过【杀】或【闪】响应此牌,则此伤害+1。", + shenij_shuying: "神戟", + shenij_shuying_info: "判定阶段,你可以弃置两张手牌,然后弃置你判定区里的牌;摸牌阶段,你多摸两张牌;出牌阶段,你可以多使用一张【杀】,你的【杀】可以多指定一名角色为目标。", + zhanjia_shuying: "战甲", + zhanjia_shuying_info: "锁定技,每回合限一次,当你受到大于2点的伤害时,将此伤害减至2点,然后摸两张牌。", + + dongzhuo_boss_shuying: "董卓", + jiuchi_shuying: "酒池", + jiuchi_shuying_info: "你可以将一张黑桃手牌当【酒】使用。", + roulin_shuying: "肉林", + roulin_shuying_info: "锁定技,你对女性角色使用的【杀】和女性角色对你使用的【杀】均需使用两张【闪】才能抵消。", + baonue_shuying: "暴虐", + baonue_shuying_info: "锁定技,回合开始时,你摸X张牌并对至多X名角色造成1点伤害,然后你失去1点体力。X为你已损失体力且最大为3。", + qubu_shuying: "驭布", + qubu_shuying_info: "锁定技,当友方角色使用【杀】指定目标时,进行一次判定:若结果为黑桃,你对此【杀】的所有目标造成1点伤害。", + + +}; + +export default translates; diff --git a/pve/boss/sets/shiershengxiao/character.js b/pve/boss/sets/shiershengxiao/character.js new file mode 100644 index 0000000..eb2b7f8 --- /dev/null +++ b/pve/boss/sets/shiershengxiao/character.js @@ -0,0 +1,70 @@ +const characters = { + zishu_boss_shuying: { + sex: "male", + group: "qun", + hp: 4, + skills: ["zishu_shuying"], + isHiddenBoss: true, + isBossAllowed: true, + img: "extension/术樱包/pve/images/十二生肖/zishu_boss_shuying.png", + dieAudios: ["ext:术樱包/pve/audio/dead/十二生肖/zishu.mp3"] + }, + // chouniu_boss_shuying: {}, + // yinhu_boss_shuying: {}, + // maotu_boss_shuying: {}, + chenlong_boss_shuying: { + sex: "male", + group: "qun", + hp: 4, + skills: ["chenlong_shuying"], + isHiddenBoss: true, + isBossAllowed: true, + img: "extension/术樱包/pve/images/十二生肖/chenlong_boss_shuying.png", + dieAudios: ["ext:术樱包/pve/audio/dead/十二生肖/chenlong.mp3"] + }, + // sishe_boss_shuying: {}, + wuma_boss_shuying: { + sex: "male", + group: "qun", + hp: 4, + skills: ["wuma_shuying"], + isHiddenBoss: true, + isBossAllowed: true, + img: "extension/术樱包/pve/images/十二生肖/wuma_boss_shuying.png", + dieAudios: ["ext:术樱包/pve/audio/dead/十二生肖/wuma_shuying.mp3"] + }, + weiyang_boss_shuying: { + sex: "female", + group: "qun", + hp: 4, + skills: ["weiyang_shuying"], + isHiddenBoss: true, + isBossAllowed: true, + img: "extension/术樱包/pve/images/十二生肖/weiyang_boss_shuying.png", + dieAudios: ["ext:术樱包/pve/audio/dead/十二生肖/weiyang.mp3"] + }, + shenhou_boss_shuying: { + sex: "male", + group: "qun", + hp: 4, + skills: ["shenhou_shuying"], + isHiddenBoss: true, + isBossAllowed: true, + img: "extension/术樱包/pve/images/十二生肖/shenhou_boss_shuying.png", + dieAudios: ["ext:术樱包/pve/audio/dead/十二生肖/shenhou.mp3"] + }, + // youji_boss_shuying: {}, + xvgou_boss_shuying: { + sex: "male", + group: "qun", + hp: 4, + skills: ["xvgou_shuying"], + isHiddenBoss: true, + isBossAllowed: true, + img: "extension/术樱包/pve/images/十二生肖/xvgou_boss_shuying.png", + dieAudios: ["ext:术樱包/pve/audio/dead/十二生肖/xvgou.mp3"] + }, + // haizhu_boss_shuying: {}, +}; + +export default characters; diff --git a/pve/boss/sets/shiershengxiao/skill.js b/pve/boss/sets/shiershengxiao/skill.js new file mode 100644 index 0000000..30ca4bd --- /dev/null +++ b/pve/boss/sets/shiershengxiao/skill.js @@ -0,0 +1,297 @@ +import { lib, game, ui, get, ai, _status, getTianshuDifficulty } from "../../shared.js"; + +const skills = { + //子鼠 + zishu_shuying: { + mode: ["boss"], + audio: "ext:术樱包/pve/audio/skill/十二生肖:1", + enable: "phaseUse", + usable: 1, + filter(event, player) { + return game.hasPlayer(current => { + return current != player && current.countCards("h") > player.countCards("h"); + }); + }, + async content(event, trigger, player) { + while (player.isIn()) { + const max = Math.max(...game.players.map(current => current.countCards("h"))); + if (player.countCards("h") >= max) break; + + const targets = game.filterPlayer(current => { + return current != player && current.countCards("h") > player.countCards("h"); + }); + if (!targets.length) break; + + const hasEnemy = targets.some(target => player.getEnemies(null, false).includes(target)); + + const result = await player + .chooseTarget("恣疏:获得一名手牌数大于你的角色一张手牌", (card, player, target) => { + return target != player && target.countCards("h") > player.countCards("h"); + }) + .set("ai", target => { + const player = get.player(); + const enemies = player.getEnemies(null, false); + const isEnemy = enemies.includes(target); + const hasEnemy = game.hasPlayer(current => { + return current != player && current.countCards("h") > player.countCards("h") && enemies.includes(current); + }); + + if (hasEnemy && !isEnemy) return 0; + if (isEnemy) return 8 + target.countCards("h") - get.attitude(player, target); + return 1 + target.countCards("h") / 10; + }) + .forResult(); + + if (!result.bool) break; + + const target = result.targets[0]; + player.line(target); + await player.gainPlayerCard({ + target, + position: "h", + forced: true, + }); + } + }, + ai: { + order: 4.5, + result: { + player(player) { + const enemies = player.getEnemies(null, false); + if (game.hasPlayer(current => current != player && current.countCards("h") > player.countCards("h") && enemies.includes(current))) { + return 1; + } + if (game.hasPlayer(current => current != player && current.countCards("h") > player.countCards("h") && !enemies.includes(current))) { + return 0.2; + } + return 0; + }, + }, + threaten: 1.4, + }, + }, + + //辰龙 + chenlong_shuying: { + mode: ["boss"], + audio: "ext:术樱包/pve/audio/skill/十二生肖:1", + enable: "phaseUse", + limited: true, + skillAnimation: true, + animationColor: "fire", + selectTarget: 1, + filter(event, player) { + return !player.storage.chenlong_shuying_used && game.hasPlayer(current => current != player && player.getEnemies(null, false).includes(current)); + }, + filterTarget(card, player, target) { + return target != player && player.getEnemies(null, false).includes(target); + }, + async content(event, trigger, player) { + const target = event.targets[0]; + const result = await player.chooseControl([1, 2, 3, 4, 5]) + .set("prompt", "失去任意点体力,然后对一名其他角色造成等量伤害") + .set("ai", () => 4) + .forResult(); + const num = result.control; + if (!num) return; + player.storage.chenlong_shuying_used = true; + player.storage.chenlong_shuying_dying = true; + await player.loseHp(num); + delete player.storage.chenlong_shuying_dying; + player.awakenSkill(event.name); + if (target.isIn()) { + player.line(target); + await target.damage(num, player); + } + }, + ai: { + order: 4, + result: { + target(player, target) { + if (!player.getEnemies(null, false).includes(target)) return 0; + const effect = get.damageEffect(target, player, target); + if (effect >= 0) return 0; + return target.hp <= 2 ? effect * 2 : effect; + }, + }, + }, + group: "chenlong_shuying_dying", + subSkill: { + dying: { + trigger: { player: ["dying", "dyingAfter"] }, + forced: true, + popup: false, + charlotte: true, + filter(event, player) { + return player.storage.chenlong_shuying_dying && player.hp <= 0; + }, + async content(event, trigger, player) { + await player.recoverTo(1); + if (player.maxHp > 0) await player.loseMaxHp(); + }, + }, + }, + }, + + //午马 + wuma_shuying: { + mode: ["boss"], + audio: "ext:术樱包/pve/audio/skill/十二生肖:1", + trigger: { target: "useCardToTargeted" }, + forced: true, + locked: true, + filter(event, player) { + return event.player != player && get.type2(event.card) == "trick"; + }, + async content(event, trigger, player) { + await player.draw(); + }, + group: ["wuma_shuying_turn", "wuma_shuying_skip"], + subSkill: { + turn: { + audio: "wuma_shuying", + trigger: { player: "turnOverBefore" }, + forced: true, + filter(event, player) { + return !player.isTurnedOver(); + }, + async content(event, trigger, player) { + trigger.cancel(); + }, + }, + skip: { + audio: "wuma_shuying", + trigger: { player: ["phaseAnySkipped", "phaseAnyCancelled"] }, + forced: true, + async content(event, trigger, player) { + game.log(player, "恢复了", trigger.name); + await player[trigger.name](); + }, + }, + }, + ai: { + effect: { + target(card, player, target) { + if (player != target && get.type2(card) == "trick") return [1, 0.5]; + }, + }, + }, + }, + //未羊 + weiyang_shuying: { + mode: ["boss"], + audio: "ext:术樱包/pve/audio/skill/十二生肖:1", + enable: "phaseUse", + usable: 1, + position: "he", + selectCard: [1, Infinity], + filter(event, player) { + return game.hasPlayer(current => current.isDamaged() && !player.getEnemies(null, false).includes(current)); + }, + filterCard(card, player) { + const type = get.type(card); + const friends = game.countPlayer(current => current.isDamaged() && !player.getEnemies(null, false).includes(current)); + return ui.selected.cards.length < friends && !ui.selected.cards.some(cardx => get.type(cardx) == type); + }, + check(card) { + return 6 - get.value(card); + }, + async content(event, trigger, player) { + const num = event.cards.length; + const friends = game.filterPlayer(current => current.isDamaged() && !player.getEnemies(null, false).includes(current)); + if (!friends.length) return; + const result = await player.chooseTarget(`令至多${get.cnNumber(num)}名角色回复1点体力`, [1, Math.min(num, friends.length)], (card, player, target) => { + return target.isDamaged() && !player.getEnemies(null, false).includes(target); + }).set("ai", target => get.recoverEffect(target, get.player(), get.player())).forResult(); + if (result.bool) { + for (const target of result.targets) { + player.line(target); + await target.recover(); + } + } + }, + ai: { + order: 6, + result: { + player(player) { + return game.hasPlayer(current => current.isDamaged() && !player.getEnemies(null, false).includes(current) && get.recoverEffect(current, player, player) > 0) ? 1 : 0; + }, + }, + }, + }, + + //申猴 + shenhou_shuying: { + mode: ["boss"], + audio: "ext:术樱包/pve/audio/skill/十二生肖:1", + trigger: { target: "useCardToTargeted" }, + filter(event, player) { + return event.card.name == "sha"; + }, + async cost(event, trigger, player) { + event.result = await player + .chooseBool(get.prompt2(event.skill)) + .set("ai", () => { + const player = get.player(); + const trigger = get.event().getTrigger(); + return get.effect(player, trigger.card, trigger.player, player) < 0; + }) + .forResult(); + }, + async content(event, trigger, player) { + const result = await player + .judge(card => get.color(card) == "red" ? 2 : -1) + .set("judge2", result => result.bool) + .forResult(); + + if (result.bool) { + trigger.getParent().excluded.add(player); + } + }, + ai: { + effect: { + target(card, player, target) { + if (card.name == "sha") return [1, 0.6]; + }, + }, + }, + }, + + //戌狗 + xvgou_shuying: { + mode: ["boss"], + audio: "ext:术樱包/pve/audio/skill/十二生肖:1", + forced: true, + group: ["xvgou_shuying_cancel", "xvgou_shuying_damage"], + mod: { + targetInRange(card, player, target) { + if (card.name == "sha" && get.color(card) == "red") return true; + }, + }, + subSkill: { + cancel: { + trigger: { target: "useCardToTargeted" }, + forced: true, + filter(event, player) { + return event.card.name == "sha" && get.color(event.card) == "red"; + }, + async content(event, trigger, player) { + trigger.getParent().excluded.add(player); + }, + }, + damage: { + trigger: { source: "damageBegin1" }, + forced: true, + filter(event, player) { + return event.card?.name == "sha" && get.color(event.card) == "red"; + }, + async content(event, trigger, player) { + trigger.num++; + }, + }, + }, + }, + +}; + +export default skills; diff --git a/pve/boss/sets/shiershengxiao/translate.js b/pve/boss/sets/shiershengxiao/translate.js new file mode 100644 index 0000000..398c712 --- /dev/null +++ b/pve/boss/sets/shiershengxiao/translate.js @@ -0,0 +1,28 @@ +const translates = { + zishu_boss_shuying: "子鼠", + zishu_shuying: "子鼠", + zishu_shuying_info: "出牌阶段限一次,你可以获得手牌数大于你的其他角色一张手牌,你可以重复此流程,直到你的手牌数为全场最多。", + + chenlong_boss_shuying: "辰龙", + chenlong_shuying: "辰龙", + chenlong_shuying_info: "限定技,出牌阶段,你可以失去任意点体力(至多为5),然后对一名其他角色造成等量的伤害。若你以此法进入濒死状态,则你将体力值回复至1,然后减少1点体力上限。", + + wuma_boss_shuying: "午马", + wuma_shuying: "午马", + wuma_shuying_info: "锁定技,你不能被翻面;你的阶段不能被跳过;当你成为其他角色使用锦囊牌的目标后,摸一张牌。", + + weiyang_boss_shuying: "未羊", + weiyang_shuying: "未羊", + weiyang_shuying_info: "出牌阶段限一次,你可以弃置任意张不同类型的牌,然后令至多等量角色回复1点体力。", + + shenhou_boss_shuying: "申猴", + shenhou_shuying: "申猴", + shenhou_shuying_info: "当你成为【杀】的目标时,你可以进行判定,若结果为红色,则此【杀】对你无效。", + + xvgou_boss_shuying: "戌狗", + xvgou_shuying: "戌狗", + xvgou_shuying_info: "锁定技,红色【杀】对你无效;你使用红色【杀】无距离限制且造成伤害+1.", + +}; + +export default translates; diff --git a/pve/boss/sets/tianshu/character.js b/pve/boss/sets/tianshu/character.js new file mode 100644 index 0000000..a72ea6b --- /dev/null +++ b/pve/boss/sets/tianshu/character.js @@ -0,0 +1,65 @@ +const characters = { + shuishengonggong_boss_shuying: + { + sex: "male", + group: "shen", + hp: 4, + skills: ["tuanliu_shuying", "juehong_shuying"], + isHiddenBoss: true, + isBossAllowed: true, + img: "extension/术樱包/pve/images/天书乱斗/shuishengonggong_boss_shuying.png" + }, + // huoshenzhurong_boss_shuying: {}, + baiqi_boss_shuying: + { + sex: "male", + group: "shen", + hp: 4, + skills: ["changsheng_shuying", "shashen_shuying", "wuan_shuying"], + isHiddenBoss: true, + isBossAllowed: true, + img: "extension/术樱包/pve/images/天书乱斗/baiqi_boss_shuying.png" + }, + kuafu_boss_shuying: + { + sex: "male", + group: "shen", + hp: 4, + skills: ["shenqu_shuying", "lieben_shuying", "yinjiang_shuying", "zhuri_shuying"], + isHiddenBoss: true, + isBossAllowed: true, + img: "extension/术樱包/pve/images/天书乱斗/kuafu_boss_shuying.png" + }, + shaohao_boss_shuying: + { + sex: "male", + group: "shen", + hp: 4, + skills: ["baiyi_shuying", "shenen_shuying",], + isHiddenBoss: true, + isBossAllowed: true, + img: "extension/术樱包/pve/images/天书乱斗/shaohao_boss_shuying.png" + }, + xuannv_boss_shuying: { + sex: "female", + group: "shen", + hp: 4, + skills: ["xuanlie_shuying", "jiutian_shuying", "dishi_shuying"], + isHiddenBoss: true, + isBossAllowed: true, + img: "extension/术樱包/pve/images/天书乱斗/xuannv_boss_shuying.png", + dieAudios: ["ext:术樱包/pve/audio/dead/天书乱斗/xuannv_boss_shuying.mp3"] + }, + hanba_boss_shuying: { + sex: "female", + group: "shen", + hp: 4, + skills: ["xinji_shuying", "zhiri_shuying", "fenshi_shuying"], + isHiddenBoss: true, + isBossAllowed: true, + img: "extension/术樱包/pve/images/天书乱斗/hanba_boss_shuying.png", + dieAudios: ["ext:术樱包/pve/audio/dead/天书乱斗/hanba_boss_shuying.mp3"] + }, +}; + +export default characters; diff --git a/pve/boss/sets/tianshu/dynamicTranslate.js b/pve/boss/sets/tianshu/dynamicTranslate.js new file mode 100644 index 0000000..83be793 --- /dev/null +++ b/pve/boss/sets/tianshu/dynamicTranslate.js @@ -0,0 +1,67 @@ +import { getTianshuDifficulty } from "../../shared.js"; + +const dynamicTranslates = { + //水神共工 + tuanliu_shuying(player, skill) { + let hp = 2; + let card = 4; + let damage = 1; + + if (getTianshuDifficulty() == "nightmare") { + hp = 3; + card = 6; + } + return `锁定技,结束阶段,若本回合进入弃牌堆的卡牌数量:大于4,你回复${hp}点体力;大于5,你摸${card}张牌;大于9,你对所有敌方角色造成${damage}点伤害。` + }, + + //少昊 + baiyi_shuying(player, skill) { + let num = 1; + const difficulty = getTianshuDifficulty(); + if (difficulty == "hard") { + num = 2; + } else if (difficulty == "nightmare") { + num = 3; + } + + return `锁定技,每名敌方角色的回合开始时,若当前轮数小于3,你随机获得其${num}张牌;若当前轮数小于5,对其造成${num}点雷电伤害;若当前轮数小于7,其随机弃置${num}张牌。`; + }, + + //玄女 + xuanlie_shuying(player, skill) { + const difficulty = getTianshuDifficulty(); + const num = difficulty == "normal" ? 1 : 2; + + return `锁定技,回合结束时,对所有本回合你获得过其牌的敌方角色依次造成${num}点伤害。`; + }, + jiutian_shuying(player, skill) { + const difficulty = getTianshuDifficulty(); + const num = difficulty == "normal" ? 1 : 2; + const extra = + difficulty == "normal" || difficulty == "hard" + ? "" + : "若这些牌包含4种花色,这些角色再额外失去1点体力。"; + + return `锁定技,准备阶段,你获得所有敌方角色各${num}张手牌。若你以此法获得的牌包含2种颜色,则对所有你以此法获得其牌的敌方角色造成1点伤害。${extra}`; + }, + + xinji_shuying(player, skill) { + const difficulty = getTianshuDifficulty(); + const num = difficulty == "nightmare" ? 2 : 1; + + return `锁定技,当友方于回合外因弃置而失去手牌时,你对当前角色造成${num}点伤害。` + }, + zhiri_shuying(player, skill) { + let num = 1; + const difficulty = getTianshuDifficulty(); + if (difficulty == "hard") { + num = 2; + } else if (difficulty == "nightmare") { + num = 3; + } + + return `锁定技,当敌方角色使用红色锦囊牌指定目标后,你摸${num}张牌。` + }, +}; + +export default dynamicTranslates; diff --git a/pve/boss/sets/tianshu/skill.js b/pve/boss/sets/tianshu/skill.js new file mode 100644 index 0000000..678c70d --- /dev/null +++ b/pve/boss/sets/tianshu/skill.js @@ -0,0 +1,584 @@ +import { lib, game, ui, get, ai, _status, getTianshuDifficulty } from "../../shared.js"; + +const skills = { + //水神共工 over + shuishen_shuying: { + mode: ["boss"], + forced: true, + group: ["shuishen_shuying_recover", "shuishen_shuying_draw", "shuishen_shuying_damage"], + subSkill: { + recover: { + trigger: { player: "recoverBegin" }, + audio: false, + forced: true, + filter(event, player) { + return game.shuying_getDiscardNamesNum() >= 4; + }, + async content(event, trigger, player) { + trigger.num++; + }, + }, + draw: { + trigger: { player: "phaseDrawBegin2" }, + audio: false, + forced: true, + filter(event, player) { + return !event.numFixed && game.shuying_getDiscardNamesNum() >= 5; + }, + async content(event, trigger, player) { + trigger.num += 5; + }, + }, + damage: { + trigger: { source: "damageBegin1" }, + audio: false, + forced: true, + filter(event, player) { + return game.shuying_getDiscardNamesNum() >= 9; + }, + async content(event, trigger, player) { + trigger.num++; + }, + }, + }, + }, + tuanliu_shuying: { + mode: ["boss"], + trigger: { player: "phaseJieshuBegin" }, + audio: false, + forced: true, + filter(event, player) { + return game.shuying_getDiscardNum() > 4; + }, + async content(event, trigger, player) { + let hp = 2; + let card = 4; + let damage = 1; + + if (getTianshuDifficulty() == "nightmare") { + hp = 3; + card = 6; + } + + + const num = game.shuying_getDiscardNum(); + if (num > 4) await player.recover(hp); + if (num > 5) await player.draw(card); + if (num > 9) { + for (const target of player.getEnemies(null, false).filter(target => target.isIn())) { + player.line(target); + await target.damage(player, damage); + } + } + }, + }, + juehong_shuying: { + mode: ["boss"], + trigger: { player: "phaseZhunbeiBegin" }, + audio: false, + forced: true, + filter(event, player) { + return player.getEnemies(null, false).some(target => target.isIn()); + }, + async content(event, trigger, player) { + for (const target of player.getEnemies(null, false).filter(target => target.isIn())) { + player.line(target); + if (target.countCards("e")) await target.chooseToDiscard("e", true, target.countCards("e")); + else if (target.countCards("h")) await target.discard(target.getCards("h").randomGets(Math.min(2, target.countCards("h")))); + } + }, + }, + + //白起 + changsheng_shuying: { + mode: ["boss"], + audio: "ext:术樱包/pve/audio/skill:1", + forced: true, + mod: { + targetInRange(card, player, target) { + if (card.name == "sha") return true; + }, + }, + }, + shashen_shuying: { + mode: ["boss"], + audio: "ext:术樱包/pve/audio/skill:1", + enable: ["chooseToUse", "chooseToRespond"], + filterCard: true, + position: "h", + viewAs: { name: "sha" }, + viewAsFilter(player) { + return player.countCards("h") > 0; + }, + prompt: "将一张手牌当【杀】使用或打出", + check(card) { + return 6 - get.value(card); + }, + group: "shashen_shuying_draw", + subSkill: { + draw: { + trigger: { source: "damageEnd" }, + filter(event, player) { + return event.card?.name == "sha"; + }, + async content(event, trigger, player) { + await player.draw(3); + }, + }, + }, + ai: { + respondSha: true, + skillTagFilter(player) { + return player.countCards("h") > 0; + }, + order: 4, + }, + }, + wuan_shuying: { + mode: ["boss"], + audio: "ext:术樱包/pve/audio/skill:1", + forced: true, + mod: { + cardUsable(card, player, num) { + if (card.name == "sha") return num + 3; + }, + }, + trigger: { source: "damageBegin1" }, + filter(event, player) { + return event.card?.name == "sha"; + }, + async content(event, trigger, player) { + trigger.num++; + }, + }, + + //盘古 + shenqu_shuying: { + mode: ["boss"], + audio: "ext:术樱包/pve/audio/skill:1", + trigger: { player: "damageEnd" }, + forced: true, + filter(event, player) { + return player.countCards("h", card => get.color(card) == "red") > 0; + }, + async content(event, trigger, player) { + const cards = player.getCards("h", card => get.color(card) == "red"); + await player.lose(cards, ui.cardPile, "insert"); + await player.draw(cards.length); + }, + }, + lieben_shuying: { + mode: ["boss"], + audio: "ext:术樱包/pve/audio/skill:1", + trigger: { player: "useCardToPlayered" }, + forced: true, + filter(event, player) { + return event.card.name == "sha" && ui.cardPile.lastChild; + }, + async content(event, trigger, player) { + const judge = player.judge(card => get.color(card) == "red" ? 2 : -1); + judge.directresult = ui.cardPile.lastChild; + judge.judge2 = result => result.bool; + const result = await judge.forResult(); + if (result.bool) { + trigger.getParent().addCount = false; + trigger.getParent().baseDamage++; + } + }, + }, + yinjiang_shuying: { + mode: ["boss"], + audio: "ext:术樱包/pve/audio/skill:1", + trigger: { player: "drawEnd" }, + forced: true, + filter(event, player) { + return player.isPhaseUsing() && !player.hasSkill("yinjiang_shuying_disabled") && ui.cardPile.lastChild; + }, + async content(event, trigger, player) { + const card = ui.cardPile.lastChild; + await player.gain(card, "gain2"); + if (get.color(card) == "red") { + let count = 0; + for (const target of player.getEnemies(null, false).filter(target => target.isIn())) { + player.line(target); + await target.damage(player); + count++; + } + player.storage.yinjiang_shuying_count = (player.storage.yinjiang_shuying_count || 0) + count; + if (player.storage.yinjiang_shuying_count >= 2) player.addTempSkill("yinjiang_shuying_disabled", { player: "phaseUseEnd" }); + } + }, + group: "yinjiang_shuying_clear", + subSkill: { + clear: { + trigger: { player: "phaseUseEnd" }, + silent: true, + async content(event, trigger, player) { + delete player.storage.yinjiang_shuying_count; + }, + }, + disabled: { charlotte: true }, + }, + }, + zhuri_shuying: { + mode: ["boss"], + audio: "ext:术樱包/pve/audio/skill:1", + forced: true, + group: ["zhuri_shuying_draw", "zhuri_shuying_bottom"], + subSkill: { + draw: { + trigger: { player: "useCard" }, + forced: true, + filter(event, player) { + return get.color(event.card) == "red"; + }, + async content(event, trigger, player) { + await player.draw(); + }, + }, + bottom: { + trigger: { global: "useCardAfter" }, + forced: true, + filter(event, player) { + return event.targets?.includes(player) && (event.card.name == "sha" || get.type(event.card) == "trick") && event.cards?.filterInD("d").length; + }, + async content(event, trigger, player) { + const cards = trigger.cards.filterInD("d"); + game.log(cards, "被置于牌堆底"); + for (const card of cards) ui.cardPile.appendChild(card); + }, + }, + }, + }, + + //少昊 over + baiyi_shuying: { + mode: ["boss"], + trigger: { global: "phaseBegin" }, + forced: true, + logTarget: "player", + filter(event, player) { + return ( + game.roundNumber < 7 && + event.player?.isIn() && + player.getEnemies(null, false).includes(event.player) + ); + }, + async content(event, trigger, player) { + let num = 1; + const target = trigger.player; + player.line(target); + + const difficulty = getTianshuDifficulty(); + + if (difficulty == "hard") { + num = 2; + } else if (difficulty == "nightmare") { + num = 3; + } + + if (game.roundNumber < 3 && target.countGainableCards(player, "he") > 0) { + const cards = target.getCards("he").randomGets(Math.min(num, target.countCards("he"))); + await player.gain(cards, target, "giveAuto"); + } + + if (game.roundNumber < 5 && target.isIn()) { + await target.damage(num, "thunder", player); + } + + if (game.roundNumber < 7 && target.isIn()) { + const cards = target + .getCards("he") + .filter(card => lib.filter.cardDiscardable(card, target, "baiyi_shuying")) + .randomGets(num); + + if (cards.length) { + await target.discard(cards); + } + } + }, + ai: { + threaten: 3, + }, + }, + shenen_shuying: { + mode: ["boss"], + forced: true, + global: "shenen_shuying_global", + subSkill: { + global: { + mod: { + targetInRange(card, player, target) { + const owner = game.findPlayer(current => current.hasSkill("shenen_shuying")); + if (owner && !owner.getEnemies(null, false).includes(player)) return true; + }, + }, + }, + }, + ai: { + threaten: 1.5, + }, + }, + + //玄女 over + xuanlie_shuying: { + mode: ["boss"], + audio: "ext:术樱包/pve/audio/skill/天书乱斗:1", + trigger: { player: "phaseJieshuBegin" }, + forced: true, + locked: true, + filter(event, player) { + return game.hasPlayer(current => { + return ( + current.isIn() && + player.getEnemies(null, false).includes(current) && + player.getHistory("gain", evt => evt.getl?.(current)?.cards2?.length).length + ); + }); + }, + async content(event, trigger, player) { + const targets = game.filterPlayer(current => { + return ( + current.isIn() && + player.getEnemies(null, false).includes(current) && + player.getHistory("gain", evt => evt.getl?.(current)?.cards2?.length).length + ); + }); + + let num = 2; + if (getTianshuDifficulty() == "normal") num = 1; + + for (const target of targets) { + player.line(target); + await target.damage(num, player); + } + }, + ai: { + threaten: 2, + }, + }, + jiutian_shuying: { + mode: ["boss"], + audio: "ext:术樱包/pve/audio/skill/天书乱斗:1", + trigger: { player: "phaseZhunbeiBegin" }, + forced: true, + locked: true, + filter(event, player) { + return player.getEnemies(null, false).some(target => target.isIn() && target.countGainableCards(player, "h") > 0); + }, + async content(event, trigger, player) { + let num = 2; + const difficulty = getTianshuDifficulty(); + if (difficulty == "normal") num = 1; + + const targets = player.getEnemies(null, false).filter(target => target.isIn() && target.countGainableCards(player, "h") > 0); + const gained = []; + + for (const target of targets) { + const cards = target.getCards("h").randomGets(Math.min(num, target.countCards("h"))); + if (cards.length) { + player.line(target); + await player.gain(cards, target, "giveAuto"); + gained.addArray(cards); + } + } + + if (!gained.length) return; + + const colors = gained.map(card => get.color(card, player)).unique(); + const suits = gained.map(card => get.suit(card, player)).unique(); + + if (colors.length >= 2) { + for (const target of targets) { + if (!target.isIn()) continue; + player.line(target); + + target.damage(1, player); + } + } + + if (difficulty == "nightmare" && suits.length >= 4) { + for (const target of targets) { + if (!target.isIn()) continue; + player.line(target); + await target.loseHp(); + } + } + }, + ai: { + threaten: 3, + }, + }, + dishi_shuying: { + mode: ["boss"], + audio: "ext:术樱包/pve/audio/skill/天书乱斗:1", + trigger: { player: "useCard2" }, + filter(event, player) { + if (!event.targets?.length) return false; + if (event.card.name != "sha" && get.type(event.card) != "trick") return false; + + const info = get.info(event.card); + if (event.targets.length == 1) { + if (info.allowMultiple == false || info.multitarget) return false; + return game.hasPlayer(current => { + return ( + !event.targets.includes(current) && + lib.filter.targetEnabled2(event.card, player, current) && + lib.filter.targetInRange(event.card, player, current) + ); + }); + } + + return event.targets.length > 1; + }, + async cost(event, trigger, player) { + if (trigger.targets.length == 1) { + event.result = await player + .chooseTarget(get.prompt(event.skill), `为${get.translation(trigger.card)}增加一个目标`, (card, player, target) => { + const trigger = get.event().getTrigger(); + return ( + !trigger.targets.includes(target) && + lib.filter.targetEnabled2(trigger.card, player, target) && + lib.filter.targetInRange(trigger.card, player, target) + ); + }) + .set("ai", target => { + const player = get.player(); + const trigger = get.event().getTrigger(); + const effect = get.effect(target, trigger.card, player, player); + if (player.getEnemies(null, false).includes(target)) return effect; + return Math.min(0, effect); + }) + .forResult(); + } + else { + event.result = await player + .chooseTarget(get.prompt(event.skill), `令${get.translation(trigger.card)}减少一个目标`, (card, player, target) => { + return get.event().getTrigger().targets.includes(target); + }) + .set("ai", target => { + const player = get.player(); + const trigger = get.event().getTrigger(); + return -get.effect(target, trigger.card, player, player); + }) + .forResult(); + } + }, + async content(event, trigger, player) { + const target = event.targets[0]; + player.line(target); + + if (trigger.targets.includes(target)) { + trigger.targets.remove(target); + game.log(target, "不再是", trigger.card, "的目标"); + } + else { + trigger.targets.add(target); + game.log(target, "成为了", trigger.card, "的额外目标"); + } + }, + ai: { + threaten: 1.6, + }, + }, + + //旱魃 + xinji_shuying: { + mode: ["boss"], + audio: "ext:术樱包/pve/audio/skill/天书乱斗:2", + trigger: { global: "loseAfter" }, + forced: true, + locked: true, + filter(event, player) { + return ( + _status.currentPhase && + _status.currentPhase.isIn() && + event.player?.isIn() && + event.player != _status.currentPhase && + !player.getEnemies(null, false).includes(event.player) && + event.type == "discard" && + event.cards2?.some(card => get.position(card, true) == "d" && event.hs?.includes(card)) + ); + }, + async content(event, trigger, player) { + const difficulty = getTianshuDifficulty(); + const num = difficulty == "nightmare" ? 2 : 1; + const target = _status.currentPhase; + player.line(target); + await target.damage(num, player); + }, + ai: { + threaten: 1.6, + }, + }, + zhiri_shuying: { + mode: ["boss"], + audio: "ext:术樱包/pve/audio/skill/天书乱斗:2", + trigger: { global: "useCardToPlayered" }, + forced: true, + locked: true, + filter(event, player) { + return ( + event.isFirstTarget && + event.player?.isIn() && + player.getEnemies(null, false).includes(event.player) && + get.type2(event.card) == "trick" && + get.color(event.card) == "red" + ); + }, + async content(event, trigger, player) { + let num = 1; + const difficulty = getTianshuDifficulty(); + if (difficulty == "hard") { + num = 2; + } else if (difficulty == "nightmare") { + num = 3; + } + + await player.draw(num); + }, + ai: { + threaten: 1.4, + }, + }, + fenshi_shuying: { + mode: ["boss"], + audio: "ext:术樱包/pve/audio/skill/天书乱斗:2", + trigger: { player: "phaseZhunbeiBegin" }, + forced: true, + locked: true, + filter(event, player) { + const num = player.countCards("h") - player.hp; + if (num < 0) return true; + if (num > 0) return player.getEnemies(null, false).some(target => target.isIn()); + return false; + }, + async content(event, trigger, player) { + const num = player.countCards("h") - player.hp; + + if (num < 0) { + await player.draw(-num); + return; + } + + const enemies = player.getEnemies(null, false).filter(target => target.isIn()); + const map = new Map(); + + for (let i = 0; i < num && enemies.length; i++) { + const target = enemies.randomGet(); + map.set(target, (map.get(target) || 0) + 1); + } + + for (const [target, damage] of map) { + if (!target.isIn()) continue; + player.line(target); + await target.damage(damage, player); + } + }, + ai: { + threaten: 2, + }, + }, +}; + +export default skills; diff --git a/pve/boss/sets/tianshu/translate.js b/pve/boss/sets/tianshu/translate.js new file mode 100644 index 0000000..fa28cbf --- /dev/null +++ b/pve/boss/sets/tianshu/translate.js @@ -0,0 +1,53 @@ +const translates = { + shuishengonggong_boss_shuying: "水神共工", + shuishen_shuying: "水神", + shuishen_shuying_info: "锁定技,弃牌堆中,不同牌名的牌数达到4时,你回复体力时回复量+1;达到5时,摸牌阶段额外摸5张牌;达到9时,你造成伤害时伤害+1。", + tuanliu_shuying: "湍流", + tuanliu_shuying_info: "锁定技,结束阶段,若本回合进入弃牌堆的卡牌数量:大于4,你回复2点体力;大于5,你摸4张牌;大于9,你对所有敌方角色造成1点伤害。", + juehong_shuying: "决洪", + juehong_shuying_info: "锁定技,准备阶段,你令所有敌方角色自己弃置自己的装备区内的所有牌,若其装备区内没有牌,则改为随机弃置2张手牌", + + baiqi_boss_shuying: "白起", + changsheng_shuying: "常胜", + changsheng_shuying_info: "锁定技,你使用【杀】无距离限制。", + shashen_shuying: "杀神", + shashen_shuying_info: "你可以将手牌中的任意牌当【杀】使用或打出。你使用的【杀】造成伤害后,摸三张牌。", + wuan_shuying: "武安", + wuan_shuying_info: "锁定技,你可使用的【杀】的次数+3,【杀】造成的伤害+1。", + + kuafu_boss_shuying: "盘古", + shenqu_shuying: "神躯", + shenqu_shuying_info: "锁定技,当你受到伤害后,将所有红色手牌置于牌堆底,并摸等量的牌。", + lieben_shuying: "烈奔", + lieben_shuying_info: "锁定技,当你使用【杀】指定目标后,使用牌堆底的牌进行一次判定:若判定结果为红色,则此杀不计入出牌阶段使用次数且伤害+1。", + yinjiang_shuying: "饮江", + yinjiang_shuying_info: "锁定技,当你在出牌阶段摸牌后,获得牌堆底的1张牌。若该牌为红色,则对所有敌方角色造成1点伤害。当你在同一个阶段内以此法造成过两次活更多的伤害后,该技能失效直到回合结束。", + zhuri_shuying: "逐日", + zhuri_shuying_info: "锁定技,你使用红色牌时摸1张牌。当你成为【杀】或普通锦囊牌的目标后,结算完毕后将该牌放置在牌堆底。", + + shaohao_boss_shuying: "少昊", + baiyi_shuying: "白仪", + baiyi_shuying_info: "锁定技,每名敌方角色的回合开始时,若当前轮数小于3,你随机获得其1张牌;若当前轮数小于5,对其造成1点雷电伤害;若当前轮数小于7,其随机弃置1张牌。", + shenen_shuying: "神恩", + shenen_shuying_info: "锁定技,所有己方角色使用牌锁定技,所有己方角色使用牌没有距离限制。", + + + xuannv_boss_shuying: "玄女", + xuanlie_shuying: "玄烈", + xuanlie_shuying_info: "锁定技,回合结束时,对所有本回合你获得过其牌的敌方角色依次造成1点伤害。", + jiutian_shuying: "九天", + jiutian_shuying_info: "锁定技,准备阶段,你获得所有敌方角色各1张手牌。若你以此法获得的牌包含2种颜色,则对所有你以此法获得其牌的敌方角色造成1点伤害。", + dishi_shuying: "帝师", + dishi_shuying_info: "当你使用【杀】或普通锦囊牌指定目标时,如果目标数为1,可以增加一个目标;如果目标数大于1,你可以减少一个目标。", + + hanba_boss_shuying: "旱魃", + xinji_shuying: "心悸", + xinji_shuying_info: "锁定技,当友方于回合外因弃置而失去手牌时,你对当前角色造成1点伤害。", + zhiri_shuying: "炙日", + zhiri_shuying_info: "锁定技,当敌方角色使用红色锦囊牌指定目标后,你摸1张牌。", + fenshi_shuying: "焚世", + fenshi_shuying_info: "锁定技,准备阶段,若你的手牌数小于体力值,则将手牌摸至于体力值相等;若你的手牌数大于体力值,则你对敌方角色造成共计X点伤害,点数随机分配(X为手牌数减体力值)。", + +}; + +export default translates; diff --git a/pve/boss/sets/zhuoguiquxie/character.js b/pve/boss/sets/zhuoguiquxie/character.js new file mode 100644 index 0000000..c2726db --- /dev/null +++ b/pve/boss/sets/zhuoguiquxie/character.js @@ -0,0 +1,50 @@ +const characters = { + heibaiwuchang_boss_shuying: + { + sex: "male", + group: "qun", + hp: 4, + skills: ["mizui_shuying", "qiangzheng_shuying"], + isHiddenBoss: true, + isBossAllowed: true, + img: "extension/术樱包/pve/images/捉鬼驱邪/heibaiwuchang_boss_shuying.png" + }, + niutoumamian_boss_shuying: { + sex: "male", + group: "qun", + hp: 4, + skills: ["shiyv_shuying", "manji_shuying", "xiaoshou_shuying"], + isHiddenBoss: true, + isBossAllowed: true, + img: "extension/术樱包/pve/images/捉鬼驱邪/niutoumamian_boss_shuying.png" + }, + yvsai_boss_shuying: { + sex: "female", + group: "qun", + hp: 4, + skills: ["anchao_shuying", "guixi_shuying"], + isHiddenBoss: true, + isBossAllowed: true, + img: "extension/术樱包/pve/images/捉鬼驱邪/yvsai_boss_shuying.png" + }, + huangfeng_boss_shuying: { + sex: "male", + group: "qun", + hp: 4, + skills: ["mingchong_shuying", "duzhen_shuying"], + isHiddenBoss: true, + isBossAllowed: true, + img: "extension/术樱包/pve/images/捉鬼驱邪/huangfeng_boss_shuying.png" + }, + riyeyoushen_boss_shuying: { + sex: "male", + group: "qun", + hp: 4, + skills: ["huiyun_shuying", "yezhong_shuying", "zhoucha_shuying"], + isHiddenBoss: true, + isBossAllowed: true, + img: "extension/术樱包/pve/images/捉鬼驱邪/riyeyoushen_boss_shuying.png" + }, +}; + +export default characters; diff --git a/pve/boss/sets/zhuoguiquxie/dynamicTranslate.js b/pve/boss/sets/zhuoguiquxie/dynamicTranslate.js new file mode 100644 index 0000000..300b4d0 --- /dev/null +++ b/pve/boss/sets/zhuoguiquxie/dynamicTranslate.js @@ -0,0 +1,74 @@ +import { getTianshuDifficulty } from "../../shared.js"; + +const dynamicTranslates = { + //黑白无常 + mizui_shuying(player, skill) { + const num = getTianshuDifficulty() == "normal" ? 1 : 2 + + return `锁定技,你的伤害牌造成伤害后,你随机弃置受伤角色的${num}张牌。` + }, + qiangzheng_shuying(player, skill) { + let num = 2; + if (getTianshuDifficulty() == "hard") num = 4; + if (getTianshuDifficulty() == "nightmare") num = 6; + + return `锁定技,敌方角色的结束阶段,若其手牌数小于${num},你获得其所有手牌。` + }, + xixing_shuying(player, skill) { + const difficulty = getTianshuDifficulty(); + if (difficulty == "nightmare") { + return "锁定技,准备阶段,你对每名敌方角色各造成2点雷电伤害,然后你回复等量体力"; + } + + return "锁定技,准备阶段,随机对敌方体力最多的一名角色造成2点雷电伤害,然后你回复等量体力。"; + }, + + //鱼鳃 + guixi_shuying(player, skill) { + const num = getTianshuDifficulty() == "nightmare" ? 2 : 1 + + return `锁定技,当你受到伤害后,你进行一次判定,若结果为红色,你回复1点体力;若结果不为红色,你摸${num}张牌。`; + }, + + //黄蜂 + mingchong_shuying(player, skill) { + if (getTianshuDifficulty() == "nightmare") { + return "锁定技,你死亡时,杀死你的角色弃置所有牌。" + } + + return "锁定技,你死亡时,杀死你的角色随机弃置一半的牌。" + }, + duzhen_shuying(player, skill) { + if (getTianshuDifficulty() == "nightmare") { + return "锁定技,你使用牌指定单一敌方角色为目标后,该角色随机弃置1张牌(优先装备区的牌)。" + } + + return "锁定技,你使用牌指定单一敌方角色为目标后,该角色随机弃置2张牌(优先装备区的牌)。" + + }, + + //日夜游神 + huiyun_shuying(player, skill) { + const num = getTianshuDifficulty() == "normal" ? 1 : 2 + + return `出牌阶段限一次,你可以展示一名敌方角色的手牌,并弃置其中至多两张牌。然后你可以弃置一张与该角色弃置牌牌名相同的牌,对其造成${num}点伤害。` + }, + yezhong_shuying(player, skill) { + let num = 1; + if (getTianshuDifficulty() == "hard") num = 2; + if (getTianshuDifficulty() == "nightmare") num = 3; + + return `锁定技,结束阶段,你进行一次判定并获得判定牌,若次牌为黑色,每名敌方角色随机弃${num}张手牌。` + }, + zhoucha_shuying(player, skill) { + let num = 1; + if (getTianshuDifficulty() == "hard") num = 2; + if (getTianshuDifficulty() == "nightmare") num = 3; + + return `锁定技,准备阶段,你进行一次判定并获得判定牌,若此牌为红色,你本回合使用【杀】的次数+${num}。` + }, + + +}; + +export default dynamicTranslates; diff --git a/pve/boss/sets/zhuoguiquxie/skill.js b/pve/boss/sets/zhuoguiquxie/skill.js new file mode 100644 index 0000000..0fff0e1 --- /dev/null +++ b/pve/boss/sets/zhuoguiquxie/skill.js @@ -0,0 +1,570 @@ +import { lib, game, ui, get, ai, _status, getTianshuDifficulty } from "../../shared.js"; + +const skills = { + //黑白无常 over + mizui_shuying: { + mode: ["boss"], + audio: "ext:术樱包/pve/audio/skill/驱鬼逐邪:1", + trigger: { source: "damageEnd" }, + forced: true, + filter(event, player) { + return event.player?.isIn() && event.player.countDiscardableCards(player, "he") > 0 && event.card && get.is.damageCard(event.card); + }, + async content(event, trigger, player) { + const num = getTianshuDifficulty() == "normal" ? 1 : 2; + + player.line(trigger.player); + await player.discardPlayerCard(trigger.player, num, "he", true); + }, + ai: { expose: 0.2 }, + }, + qiangzheng_shuying: { + mode: ["boss"], + audio: "ext:术樱包/pve/audio/skill/驱鬼逐邪:1", + trigger: { global: "phaseEnd" }, + forced: true, + logTarget: "player", + filter(event, player) { + let num = 2; + if (getTianshuDifficulty() == "hard") num = 4; + if (getTianshuDifficulty() == "nightmare") num = 6; + + return event.player?.isIn() && player.getEnemies(null, false).includes(event.player) && event.player.countCards("h") > 0 && event.player.countCards("h") < num; + }, + async content(event, trigger, player) { + player.line(trigger.player); + await player.gain(trigger.player.getCards("h"), trigger.player, "giveAuto"); + }, + }, + xixing_shuying: { + mode: ["boss"], + audio: "ext:术樱包/pve/audio/skill/驱鬼逐邪:1", + trigger: { player: "phaseZhunbeiBegin" }, + forced: true, + filter(event, player) { + return player.getEnemies(null, false).some(target => target.isIn()); + }, + async content(event, trigger, player) { + const enemies = player.getEnemies(null, false).filter(target => target.isIn()); + + if (difficulty == "nightmare") { + for (const target of enemies) { + player.line(target, "thunder"); + await target.damage(2, "thunder", player); + } + } else { + const max = Math.max(...enemies.map(target => target.hp)); + const target = enemies.filter(target => target.hp == max).randomGet(); + player.line(target, "thunder"); + await target.damage(2, "thunder", player); + } + + await player.recover(2); + }, + }, + taiping_shuying: { + mode: ["boss"], + audio: "ext:术樱包/pve/audio/skill/驱鬼逐邪:1", + trigger: { player: "damageEnd" }, + forced: true, + locked: true, + logTarget: "source", + filter(event, player) { + return ( + event.num > 0 && + event.source?.isIn() && + player.getEnemies(null, false).includes(event.source) + ); + }, + async content(event, trigger, player) { + const source = trigger.source; + player.line(source); + + const result = await source + .chooseToDiscard("h", 2, `太平:弃置两张花色不同的手牌,否则失去1点体力`, card => { + const cards = ui.selected.cards; + return !cards.some(cardx => get.suit(cardx) == get.suit(card)); + }) + .set("ai", card => { + const player = get.player(); + if (player.hp <= 1) return 8 - get.value(card); + return 6 - get.value(card); + }) + .forResult(); + + if (!result.bool) { + await source.loseHp(); + } + }, + ai: { + maixie_defend: true, + effect: { + target(card, player, target) { + if (get.tag(card, "damage") && target.getEnemies(null, false).includes(player)) { + if (player.countCards("h") < 2) return [1, -1]; + return [1, 0.4]; + } + }, + }, + }, + }, + + //牛头马面 + shiyv_shuying: { + mode: ["boss"], + audio: "ext:术樱包/pve/audio/skill/驱鬼逐邪:1", + trigger: { player: "phaseDrawBegin2" }, + forced: true, + filter(event, player) { + return !event.numFixed; + }, + async content(event, trigger, player) { + trigger.num = 0; + const cards = []; + for (const suit of lib.suit) { + const card = get.cardPile(card => get.suit(card) == suit, "cardPile", "random"); + if (card) cards.push(card); + } + if (cards.length) await player.gain(cards, "gain2"); + }, + }, + manji_shuying: { + mode: ["boss"], + audio: "ext:术樱包/pve/audio/skill/驱鬼逐邪:1", + trigger: { player: "useCardToPlayered" }, + filter(event, player) { + return event.card.name == "sha" && event.targets?.length == 1 && player.getEnemies(null, false).includes(event.target) && event.target.countDiscardableCards(player, "h") > 0; + }, + async cost(event, trigger, player) { + event.result = await player.chooseBool(get.prompt(event.skill, trigger.target)) + .set("ai", () => get.attitude(get.player(), get.event().getTrigger().target) < 0) + .forResult(); + }, + async content(event, trigger, player) { + const target = trigger.target; + player.line(target); + const cards = target.getCards("h").randomGets(Math.min(2, target.countCards("h"))); + await target.discard(cards); + if (cards.some(card => get.name(card, target) == "sha")) trigger.getParent().baseDamage++; + }, + ai: { expose: 0.2 }, + }, + xiaoshou_shuying: { + mode: ["boss"], + audio: "ext:术樱包/pve/audio/skill/驱鬼逐邪:1", + trigger: { player: "phaseZhunbeiBegin" }, + forced: true, + filter(event, player) { + return player.getEnemies(null, false).some(target => target.isIn()); + }, + async content(event, trigger, player) { + const target = player.getEnemies(null, false).filter(target => target.isIn()).randomGet(); + player.line(target); + await target.damage(2, player); + }, + }, + + //鱼鳃 over + anchao_shuying: { + mode: ["boss"], + audio: "ext:术樱包/pve/audio/skill/驱鬼逐邪:1", + forced: true, + locked: true, + group: ["anchao_shuying_gain", "anchao_shuying_start"], + marktext: "潮", + intro: { + name: "暗潮", + content: "mark", + }, + subSkill: { + gain: { + audio: "anchao_shuying", + trigger: { global: "phaseJieshuBegin" }, + forced: true, + logTarget: "player", + filter(event, player) { + return ( + event.player?.isIn() && + !player.getEnemies(null, false).includes(event.player) && + !event.player.getHistory("sourceDamage", evt => evt.num > 0).length + ); + }, + async content(event, trigger, player) { + trigger.player.addMark("anchao_shuying", 1); + }, + }, + start: { + audio: "anchao_shuying", + trigger: { global: "phaseBegin" }, + forced: true, + logTarget: "player", + filter(event, player) { + return ( + event.player?.isIn() && + !player.getEnemies(null, false).includes(event.player) && + event.player.countMark("anchao_shuying") > 0 + ); + }, + async content(event, trigger, player) { + const target = trigger.player; + target.storage.anchao_shuying_effect = target.countMark("anchao_shuying"); + target.addTempSkill("anchao_shuying_effect", { player: "phaseAfter" }); + }, + }, + effect: { + charlotte: true, + group: ["anchao_shuying_draw", "anchao_shuying_damage"], + onremove(player) { + delete player.storage.anchao_shuying_effect; + }, + }, + draw: { + audio: "anchao_shuying", + trigger: { player: "phaseDrawBegin2" }, + forced: true, + filter(event, player) { + return !event.numFixed && player.storage.anchao_shuying_effect > 0; + }, + async content(event, trigger, player) { + trigger.num += player.storage.anchao_shuying_effect; + }, + }, + damage: { + audio: "anchao_shuying", + trigger: { source: "damageBegin1" }, + forced: true, + filter(event, player) { + return ( + player.storage.anchao_shuying_effect > 0 && + event.player?.isIn() && + player.getEnemies(null, false).includes(event.player) + ); + }, + async content(event, trigger, player) { + trigger.num += player.storage.anchao_shuying_effect; + }, + }, + }, + ai: { + threaten: 2, + }, + }, + guixi_shuying: { + mode: ["boss"], + audio: "ext:术樱包/pve/audio/skill/驱鬼逐邪:1", + trigger: { player: "damageEnd" }, + forced: true, + locked: true, + filter(event, player) { + return event.num > 0; + }, + async content(event, trigger, player) { + const num = getTianshuDifficulty() == "nightmare" ? 2 : 1; + const result = await player + .judge(card => get.color(card) == "red" ? 2 : 0) + .set("judge2", result => result.bool) + .forResult(); + + if (result.bool && player.isDamaged()) { + await player.recover(); + } + else { + await player.draw(num); + } + }, + ai: { + maixie: true, + maixie_hp: true, + effect: { + target(card, player, target) { + if (get.tag(card, "damage")) { + if (player.hasSkillTag("jueqing", false, target)) return [1, -1]; + return [1, 0.7]; + } + }, + }, + }, + }, + + //黄蜂 over + mingchong_shuying: { + mode: ["boss"], + audio: "ext:术樱包/pve/audio/skill/驱鬼逐邪:1", + trigger: { player: "die" }, + forced: true, + locked: true, + forceDie: true, + logTarget: "source", + filter(event, player) { + return event.source?.isIn() && event.source.countDiscardableCards(player, "he") > 0; + }, + async content(event, trigger, player) { + const source = trigger.source; + player.line(source); + + const cards = source + .getCards("he") + .filter(card => lib.filter.cardDiscardable(card, source, "mingchong_shuying")); + + if (cards.length) { + if (getTianshuDifficulty() == "nightmare") { + await source.discard(cards); + } + else { + await source.discard(cards.randomGets(Math.ceil(cards.length / 2))); + } + } + }, + ai: { + threaten: 0.8, + effect: { + target(card, player, target) { + if (get.tag(card, "damage") && target.hp <= 1 && player.countCards("he") > 2) { + return [1, -0.8]; + } + }, + }, + }, + }, + duzhen_shuying: { + mode: ["boss"], + audio: "ext:术樱包/pve/audio/skill/驱鬼逐邪:1", + trigger: { player: "useCardToPlayered" }, + forced: true, + locked: true, + logTarget: "target", + filter(event, player) { + return ( + event.targets?.length == 1 && + event.target?.isIn() && + player.getEnemies(null, false).includes(event.target) && + event.target.countDiscardableCards(player, "he") > 0 + ); + }, + async content(event, trigger, player) { + const target = trigger.target; + const num = getTianshuDifficulty() == "nightmare" ? 2 : 1; + const cards = []; + + player.line(target); + + const equips = target + .getCards("e") + .filter(card => lib.filter.cardDiscardable(card, target, "duzhen_shuying")) + .randomGets(num); + cards.addArray(equips); + + if (cards.length < num) { + const hands = target + .getCards("h") + .filter(card => lib.filter.cardDiscardable(card, target, "duzhen_shuying")) + .randomGets(num - cards.length); + cards.addArray(hands); + } + + if (cards.length) { + await target.discard(cards); + } + }, + ai: { + threaten: 1.8, + effect: { + player_use(card, player, target) { + if (target && player.getEnemies(null, false).includes(target) && target.countCards("he") > 0) { + return [1, 0.5]; + } + }, + }, + }, + }, + + //日夜游神 over + huiyun_shuying: { + mode: ["boss"], + audio: "ext:术樱包/pve/audio/skill/驱鬼逐邪:1", + enable: "phaseUse", + usable: 1, + filter(event, player) { + return player.getEnemies(null, false).some(target => target.isIn() && target.countCards("h") > 0); + }, + filterTarget(card, player, target) { + return target.isIn() && player.getEnemies(null, false).includes(target) && target.countCards("h") > 0; + }, + async content(event, trigger, player) { + const target = event.targets[0]; + const hs = target.getCards("h"); + player.line(target); + await target.showCards(hs, `${get.translation(player)}对${get.translation(target)}发动了【${get.translation(event.name)}】`); + + const result = await player + .choosePlayerCard(target, "h", [1, 2], `弃置${get.translation(target)}至多两张手牌`) + .set("filterButton", button => { + const player = get.player(); + const target = get.event().target; + return lib.filter.canBeDiscarded(button.link, player, target); + }) + .set("ai", button => { + const card = button.link; + const player = get.player(); + const target = get.event().target; + let value = get.value(card, target); + if (get.name(card, target) == "tao") value += 3; + if (get.name(card, target) == "shan") value += 1; + return value; + }) + .set("target", target) + .forResult(); + + if (!result.bool || !result.cards?.length) return; + + const names = result.cards.map(card => get.name(card, target)).unique(); + await target.discard(result.cards); + + if (!target.isIn()) return; + + const result2 = await player + .chooseToDiscard("he", `是否弃置一张同名牌,对${get.translation(target)}造成2点伤害?`, card => { + return get.event().names.includes(get.name(card)); + }) + .set("names", names) + .set("ai", card => { + const player = get.player(); + const target = get.event().target; + if (!player.getEnemies(null, false).includes(target)) return 0; + const effect = get.damageEffect(target, player, player); + if (effect <= 0) return 0; + return effect * 3 - get.value(card); + }) + .set("target", target) + .forResult(); + + if (result2.bool) { + const num = getTianshuDifficulty() == "normal" ? 1 : 2 + + player.line(target); + await target.damage(num, player); + } + }, + ai: { + order: 5, + result: { + target(player, target) { + if (!player.getEnemies(null, false).includes(target)) return 0; + return -Math.min(2, target.countCards("h")); + }, + }, + threaten: 1.6, + }, + }, + yezhong_shuying: { + mode: ["boss"], + audio: "ext:术樱包/pve/audio/skill/驱鬼逐邪:1", + trigger: { player: "phaseJieshuBegin" }, + forced: true, + locked: true, + async content(event, trigger, player) { + const result = await player + .judge(card => get.color(card) == "black" ? 2 : -1) + .set("judge2", result => result.bool) + .forResult(); + + if (result.card) { + await player.gain(result.card, "gain2"); + } + + if (result.bool) { + let num = 1; + if (getTianshuDifficulty() == "hard") num = 2; + if (getTianshuDifficulty() == "nightmare") num = 3; + + const targets = player.getEnemies(null, false).filter(target => target.isIn() && target.countCards("h") > 0); + for (const target of targets) { + player.line(target); + const cards = target + .getCards("h") + .filter(card => lib.filter.cardDiscardable(card, target, "yezhong_shuying")) + .randomGets(num); + + if (cards.length) { + await target.discard(cards); + } + } + } + }, + ai: { + threaten: 1.8, + }, + }, + zhoucha_shuying: { + mode: ["boss"], + audio: "ext:术樱包/pve/audio/skill/驱鬼逐邪:1", + trigger: { player: "phaseZhunbeiBegin" }, + forced: true, + locked: true, + async content(event, trigger, player) { + const result = await player + .judge(card => get.color(card) == "red" ? 2 : -1) + .set("judge2", result => result.bool) + .forResult(); + + if (result.card) { + await player.gain(result.card, "gain2"); + } + + if (result.bool) { + player.addTempSkill("zhoucha_shuying_effect", { player: "phaseAfter" }); + } + }, + subSkill: { + effect: { + charlotte: true, + mod: { + cardUsable(card, player, num) { + let number = 1; + if (getTianshuDifficulty() == "hard") number = 2; + if (getTianshuDifficulty() == "nightmare") number = 3; + + if (card.name == "sha") return num + number; + }, + }, + }, + }, + ai: { + threaten: 1.4, + }, + }, + duane_shuying: { + mode: ["boss"], + audio: "ext:术樱包/pve/audio/skill/驱鬼逐邪:1", + trigger: { global: "phaseDiscardEnd" }, + forced: true, + locked: true, + logTarget: "player", + filter(event, player) { + const target = event.player; + return ( + target?.isIn() && + player.getEnemies(null, false).includes(target) && + target.getHistory("lose", evt => { + return ( + evt.type == "discard" && + evt.getParent("phaseDiscard") == event && + evt.cards2?.some(card => get.color(card, target) == "black") + ); + }).length + ); + }, + async content(event, trigger, player) { + const target = trigger.player; + player.line(target); + await target.loseHp(2); + }, + ai: { + threaten: 1.5, + }, + }, + + +}; + +export default skills; diff --git a/pve/boss/sets/zhuoguiquxie/translate.js b/pve/boss/sets/zhuoguiquxie/translate.js new file mode 100644 index 0000000..9eb5409 --- /dev/null +++ b/pve/boss/sets/zhuoguiquxie/translate.js @@ -0,0 +1,45 @@ +const translates = { + heibaiwuchang_boss_shuying: "黑白无常", + mizui_shuying: "迷醉", + mizui_shuying_info: "锁定技,你的伤害牌造成伤害后,你随机弃置受伤角色的1张牌。", + qiangzheng_shuying: "强征", + qiangzheng_shuying_info: "锁定技,敌方角色的结束阶段,若其手牌数小于4,你获得其所有手牌。", + xixing_shuying: "吸星", + xixing_shuying_info: "锁定技,准备阶段,随机对敌方体力最多的一名角色造成2点雷电伤害,然后你回复等量体力", + taiping_shuying: "太平", + taiping_shuying_info: "锁定技,当你受到敌方角色造成的伤害后,伤害来源须弃置两张花色不同的手牌,否则失去1点体力。", + + niutoumamian_boss_shuying: "牛头马面", + shiyv_shuying: "施狱", + shiyv_shuying_info: "锁定技,摸牌阶段,你放弃摸牌改为随机获得牌堆中每种花色的牌各一张。", + manji_shuying: "蛮击", + manji_shuying_info: "你使用【杀】指定单一目标后,你可以弃置该角色2张手牌。若其中有【杀】,你本次【杀】造成的伤害+1。", + xiaoshou_shuying: "枭首", + xiaoshou_shuying_info: "锁定技,准备阶段,你对随机一名敌方角色造成2点伤害", + + yvsai_boss_shuying: "鱼鳃", + anchao_shuying: "暗潮", + anchao_shuying_info: "锁定技,己方角色的回合结束时,若此回合该角色没有造成过伤害,则获得一个“暗潮”标记。己方回合开始时,若该角色有“暗潮”标记,则本回合摸牌阶段多摸X张牌且对敌方角色造成的伤害+X(X为“暗潮”标记数)", + guixi_shuying: "鬼吸", + guixi_shuying_info: "锁定技,当你受到伤害后,你进行一次判定,若结果为红色,你回复1点体力;若结果不为红色,你摸1张牌。", + + huangfeng_boss_shuying: "黄蜂", + mingchong_shuying: "冥虫", + mingchong_shuying_info: "锁定技,你死亡时,杀死你的角色随机弃置一半的牌。", + duzhen_shuying: "毒针", + duzhen_shuying_info: "锁定技,你使用牌指定单一敌方角色为目标后,该角色随机弃置1张牌(优先装备区的牌)。", + + riyeyoushen_boss_shuying: "日夜游神", + huiyun_shuying: "晦运", + huiyun_shuying_info: "出牌阶段限一次,你可以展示一名敌方角色的手牌,并弃置其中至多两张牌。然后你可以弃置一张与该角色弃置牌牌名相同的牌,对其造成2点伤害。", + yezhong_shuying: "夜冢", + yezhong_shuying_info: "锁定技,结束阶段,你进行一次判定并获得判定牌,若次牌为黑色,每名敌方角色随机弃1张手牌。", + zhoucha_shuying: "昼刹", + zhoucha_shuying_info: "锁定技,准备阶段,你进行一次判定并获得判定牌,若此牌为红色,你本回合使用【杀】的次数+1。", + duane_shuying: "断恶", + duane_shuying_info: "锁定技,敌方角色的弃牌阶段结束时,若其此阶段弃置了黑色牌,则该角色失去2点体力。", + + +}; + +export default translates; diff --git a/pve/boss/shared.js b/pve/boss/shared.js new file mode 100644 index 0000000..8bec778 --- /dev/null +++ b/pve/boss/shared.js @@ -0,0 +1,9 @@ +import { lib, game, ui, get, ai, _status } from "../../../../noname.js"; +import tianshuConfig from "../../tianshu/config.js"; + +// 统一提供活动 BOSS 技能依赖与天书难度,避免各主题模块重复维护加载路径。 +const getTianshuDifficulty = () => { + return _status[tianshuConfig.settings.difficultyStatusKey] || "normal"; +}; + +export { lib, game, ui, get, ai, _status, getTianshuDifficulty }; diff --git a/pve/boss/skill.js b/pve/boss/skill.js index 390b95f..98dba2a 100644 --- a/pve/boss/skill.js +++ b/pve/boss/skill.js @@ -1,2118 +1,15 @@ -import { lib, game, ui, get, ai, _status } from "../../../../noname.js"; -import tianshuConfig from "../../tianshu/config.js"; - -const getTianshuDifficulty = () => { - return _status[tianshuConfig.settings.difficultyStatusKey] || "normal"; -}; - -const isTianshuDifficultyAtLeast = difficulty => { - return tianshuDifficultyRank[getTianshuDifficulty()] >= tianshuDifficultyRank[difficulty]; -}; - -const skills = { - //子鼠 - zishu_shuying: { - mode: ["boss"], - audio: "ext:术樱包/pve/audio/skill/十二生肖:1", - enable: "phaseUse", - usable: 1, - filter(event, player) { - return game.hasPlayer(current => { - return current != player && current.countCards("h") > player.countCards("h"); - }); - }, - async content(event, trigger, player) { - while (player.isIn()) { - const max = Math.max(...game.players.map(current => current.countCards("h"))); - if (player.countCards("h") >= max) break; - - const targets = game.filterPlayer(current => { - return current != player && current.countCards("h") > player.countCards("h"); - }); - if (!targets.length) break; - - const hasEnemy = targets.some(target => player.getEnemies(null, false).includes(target)); - - const result = await player - .chooseTarget("恣疏:获得一名手牌数大于你的角色一张手牌", (card, player, target) => { - return target != player && target.countCards("h") > player.countCards("h"); - }) - .set("ai", target => { - const player = get.player(); - const enemies = player.getEnemies(null, false); - const isEnemy = enemies.includes(target); - const hasEnemy = game.hasPlayer(current => { - return current != player && current.countCards("h") > player.countCards("h") && enemies.includes(current); - }); - - if (hasEnemy && !isEnemy) return 0; - if (isEnemy) return 8 + target.countCards("h") - get.attitude(player, target); - return 1 + target.countCards("h") / 10; - }) - .forResult(); - - if (!result.bool) break; - - const target = result.targets[0]; - player.line(target); - await player.gainPlayerCard({ - target, - position: "h", - forced: true, - }); - } - }, - ai: { - order: 4.5, - result: { - player(player) { - const enemies = player.getEnemies(null, false); - if (game.hasPlayer(current => current != player && current.countCards("h") > player.countCards("h") && enemies.includes(current))) { - return 1; - } - if (game.hasPlayer(current => current != player && current.countCards("h") > player.countCards("h") && !enemies.includes(current))) { - return 0.2; - } - return 0; - }, - }, - threaten: 1.4, - }, - }, - - //辰龙 - chenlong_shuying: { - mode: ["boss"], - audio: "ext:术樱包/pve/audio/skill/十二生肖:1", - enable: "phaseUse", - limited: true, - skillAnimation: true, - animationColor: "fire", - selectTarget: 1, - filter(event, player) { - return !player.storage.chenlong_shuying_used && game.hasPlayer(current => current != player && player.getEnemies(null, false).includes(current)); - }, - filterTarget(card, player, target) { - return target != player && player.getEnemies(null, false).includes(target); - }, - async content(event, trigger, player) { - const target = event.targets[0]; - const result = await player.chooseControl([1, 2, 3, 4, 5]) - .set("prompt", "失去任意点体力,然后对一名其他角色造成等量伤害") - .set("ai", () => 4) - .forResult(); - const num = result.control; - if (!num) return; - player.storage.chenlong_shuying_used = true; - player.storage.chenlong_shuying_dying = true; - await player.loseHp(num); - delete player.storage.chenlong_shuying_dying; - player.awakenSkill(event.name); - if (target.isIn()) { - player.line(target); - await target.damage(num, player); - } - }, - ai: { - order: 4, - result: { - target(player, target) { - if (!player.getEnemies(null, false).includes(target)) return 0; - const effect = get.damageEffect(target, player, target); - if (effect >= 0) return 0; - return target.hp <= 2 ? effect * 2 : effect; - }, - }, - }, - group: "chenlong_shuying_dying", - subSkill: { - dying: { - trigger: { player: ["dying", "dyingAfter"] }, - forced: true, - popup: false, - charlotte: true, - filter(event, player) { - return player.storage.chenlong_shuying_dying && player.hp <= 0; - }, - async content(event, trigger, player) { - await player.recoverTo(1); - if (player.maxHp > 0) await player.loseMaxHp(); - }, - }, - }, - }, - - //午马 - wuma_shuying: { - mode: ["boss"], - audio: "ext:术樱包/pve/audio/skill/十二生肖:1", - trigger: { target: "useCardToTargeted" }, - forced: true, - locked: true, - filter(event, player) { - return event.player != player && get.type2(event.card) == "trick"; - }, - async content(event, trigger, player) { - await player.draw(); - }, - group: ["wuma_shuying_turn", "wuma_shuying_skip"], - subSkill: { - turn: { - audio: "wuma_shuying", - trigger: { player: "turnOverBefore" }, - forced: true, - filter(event, player) { - return !player.isTurnedOver(); - }, - async content(event, trigger, player) { - trigger.cancel(); - }, - }, - skip: { - audio: "wuma_shuying", - trigger: { player: ["phaseAnySkipped", "phaseAnyCancelled"] }, - forced: true, - async content(event, trigger, player) { - game.log(player, "恢复了", trigger.name); - await player[trigger.name](); - }, - }, - }, - ai: { - effect: { - target(card, player, target) { - if (player != target && get.type2(card) == "trick") return [1, 0.5]; - }, - }, - }, - }, - //未羊 - weiyang_shuying: { - mode: ["boss"], - audio: "ext:术樱包/pve/audio/skill/十二生肖:1", - enable: "phaseUse", - usable: 1, - position: "he", - selectCard: [1, Infinity], - filter(event, player) { - return game.hasPlayer(current => current.isDamaged() && !player.getEnemies(null, false).includes(current)); - }, - filterCard(card, player) { - const type = get.type(card); - const friends = game.countPlayer(current => current.isDamaged() && !player.getEnemies(null, false).includes(current)); - return ui.selected.cards.length < friends && !ui.selected.cards.some(cardx => get.type(cardx) == type); - }, - check(card) { - return 6 - get.value(card); - }, - async content(event, trigger, player) { - const num = event.cards.length; - const friends = game.filterPlayer(current => current.isDamaged() && !player.getEnemies(null, false).includes(current)); - if (!friends.length) return; - const result = await player.chooseTarget(`令至多${get.cnNumber(num)}名角色回复1点体力`, [1, Math.min(num, friends.length)], (card, player, target) => { - return target.isDamaged() && !player.getEnemies(null, false).includes(target); - }).set("ai", target => get.recoverEffect(target, get.player(), get.player())).forResult(); - if (result.bool) { - for (const target of result.targets) { - player.line(target); - await target.recover(); - } - } - }, - ai: { - order: 6, - result: { - player(player) { - return game.hasPlayer(current => current.isDamaged() && !player.getEnemies(null, false).includes(current) && get.recoverEffect(current, player, player) > 0) ? 1 : 0; - }, - }, - }, - }, - - //申猴 - shenhou_shuying: { - mode: ["boss"], - audio: "ext:术樱包/pve/audio/skill/十二生肖:1", - trigger: { target: "useCardToTargeted" }, - filter(event, player) { - return event.card.name == "sha"; - }, - async cost(event, trigger, player) { - event.result = await player - .chooseBool(get.prompt2(event.skill)) - .set("ai", () => { - const player = get.player(); - const trigger = get.event().getTrigger(); - return get.effect(player, trigger.card, trigger.player, player) < 0; - }) - .forResult(); - }, - async content(event, trigger, player) { - const result = await player - .judge(card => get.color(card) == "red" ? 2 : -1) - .set("judge2", result => result.bool) - .forResult(); - - if (result.bool) { - trigger.getParent().excluded.add(player); - } - }, - ai: { - effect: { - target(card, player, target) { - if (card.name == "sha") return [1, 0.6]; - }, - }, - }, - }, - - //戌狗 - xvgou_shuying: { - mode: ["boss"], - audio: "ext:术樱包/pve/audio/skill/十二生肖:1", - forced: true, - group: ["xvgou_shuying_cancel", "xvgou_shuying_damage"], - mod: { - targetInRange(card, player, target) { - if (card.name == "sha" && get.color(card) == "red") return true; - }, - }, - subSkill: { - cancel: { - trigger: { target: "useCardToTargeted" }, - forced: true, - filter(event, player) { - return event.card.name == "sha" && get.color(event.card) == "red"; - }, - async content(event, trigger, player) { - trigger.getParent().excluded.add(player); - }, - }, - damage: { - trigger: { source: "damageBegin1" }, - forced: true, - filter(event, player) { - return event.card?.name == "sha" && get.color(event.card) == "red"; - }, - async content(event, trigger, player) { - trigger.num++; - }, - }, - }, - }, - - //黑白无常 over - mizui_shuying: { - mode: ["boss"], - audio: "ext:术樱包/pve/audio/skill/驱鬼逐邪:1", - trigger: { source: "damageEnd" }, - forced: true, - filter(event, player) { - return event.player?.isIn() && event.player.countDiscardableCards(player, "he") > 0 && event.card && get.is.damageCard(event.card); - }, - async content(event, trigger, player) { - const num = getTianshuDifficulty() == "normal" ? 1 : 2; - - player.line(trigger.player); - await player.discardPlayerCard(trigger.player, num, "he", true); - }, - ai: { expose: 0.2 }, - }, - qiangzheng_shuying: { - mode: ["boss"], - audio: "ext:术樱包/pve/audio/skill/驱鬼逐邪:1", - trigger: { global: "phaseEnd" }, - forced: true, - logTarget: "player", - filter(event, player) { - let num = 2; - if (getTianshuDifficulty() == "hard") num = 4; - if (getTianshuDifficulty() == "nightmare") num = 6; - - return event.player?.isIn() && player.getEnemies(null, false).includes(event.player) && event.player.countCards("h") > 0 && event.player.countCards("h") < num; - }, - async content(event, trigger, player) { - player.line(trigger.player); - await player.gain(trigger.player.getCards("h"), trigger.player, "giveAuto"); - }, - }, - xixing_shuying: { - mode: ["boss"], - audio: "ext:术樱包/pve/audio/skill/驱鬼逐邪:1", - trigger: { player: "phaseZhunbeiBegin" }, - forced: true, - filter(event, player) { - return player.getEnemies(null, false).some(target => target.isIn()); - }, - async content(event, trigger, player) { - const enemies = player.getEnemies(null, false).filter(target => target.isIn()); - - if (difficulty == "nightmare") { - for (const target of enemies) { - player.line(target, "thunder"); - await target.damage(2, "thunder", player); - } - } else { - const max = Math.max(...enemies.map(target => target.hp)); - const target = enemies.filter(target => target.hp == max).randomGet(); - player.line(target, "thunder"); - await target.damage(2, "thunder", player); - } - - await player.recover(2); - }, - }, - taiping_shuying: { - mode: ["boss"], - audio: "ext:术樱包/pve/audio/skill/驱鬼逐邪:1", - trigger: { player: "damageEnd" }, - forced: true, - locked: true, - logTarget: "source", - filter(event, player) { - return ( - event.num > 0 && - event.source?.isIn() && - player.getEnemies(null, false).includes(event.source) - ); - }, - async content(event, trigger, player) { - const source = trigger.source; - player.line(source); - - const result = await source - .chooseToDiscard("h", 2, `太平:弃置两张花色不同的手牌,否则失去1点体力`, card => { - const cards = ui.selected.cards; - return !cards.some(cardx => get.suit(cardx) == get.suit(card)); - }) - .set("ai", card => { - const player = get.player(); - if (player.hp <= 1) return 8 - get.value(card); - return 6 - get.value(card); - }) - .forResult(); - - if (!result.bool) { - await source.loseHp(); - } - }, - ai: { - maixie_defend: true, - effect: { - target(card, player, target) { - if (get.tag(card, "damage") && target.getEnemies(null, false).includes(player)) { - if (player.countCards("h") < 2) return [1, -1]; - return [1, 0.4]; - } - }, - }, - }, - }, - - //牛头马面 - shiyv_shuying: { - mode: ["boss"], - audio: "ext:术樱包/pve/audio/skill/驱鬼逐邪:1", - trigger: { player: "phaseDrawBegin2" }, - forced: true, - filter(event, player) { - return !event.numFixed; - }, - async content(event, trigger, player) { - trigger.num = 0; - const cards = []; - for (const suit of lib.suit) { - const card = get.cardPile(card => get.suit(card) == suit, "cardPile", "random"); - if (card) cards.push(card); - } - if (cards.length) await player.gain(cards, "gain2"); - }, - }, - manji_shuying: { - mode: ["boss"], - audio: "ext:术樱包/pve/audio/skill/驱鬼逐邪:1", - trigger: { player: "useCardToPlayered" }, - filter(event, player) { - return event.card.name == "sha" && event.targets?.length == 1 && player.getEnemies(null, false).includes(event.target) && event.target.countDiscardableCards(player, "h") > 0; - }, - async cost(event, trigger, player) { - event.result = await player.chooseBool(get.prompt(event.skill, trigger.target)) - .set("ai", () => get.attitude(get.player(), get.event().getTrigger().target) < 0) - .forResult(); - }, - async content(event, trigger, player) { - const target = trigger.target; - player.line(target); - const cards = target.getCards("h").randomGets(Math.min(2, target.countCards("h"))); - await target.discard(cards); - if (cards.some(card => get.name(card, target) == "sha")) trigger.getParent().baseDamage++; - }, - ai: { expose: 0.2 }, - }, - xiaoshou_shuying: { - mode: ["boss"], - audio: "ext:术樱包/pve/audio/skill/驱鬼逐邪:1", - trigger: { player: "phaseZhunbeiBegin" }, - forced: true, - filter(event, player) { - return player.getEnemies(null, false).some(target => target.isIn()); - }, - async content(event, trigger, player) { - const target = player.getEnemies(null, false).filter(target => target.isIn()).randomGet(); - player.line(target); - await target.damage(2, player); - }, - }, - - //鱼鳃 over - anchao_shuying: { - mode: ["boss"], - audio: "ext:术樱包/pve/audio/skill/驱鬼逐邪:1", - forced: true, - locked: true, - group: ["anchao_shuying_gain", "anchao_shuying_start"], - marktext: "潮", - intro: { - name: "暗潮", - content: "mark", - }, - subSkill: { - gain: { - audio: "anchao_shuying", - trigger: { global: "phaseJieshuBegin" }, - forced: true, - logTarget: "player", - filter(event, player) { - return ( - event.player?.isIn() && - !player.getEnemies(null, false).includes(event.player) && - !event.player.getHistory("sourceDamage", evt => evt.num > 0).length - ); - }, - async content(event, trigger, player) { - trigger.player.addMark("anchao_shuying", 1); - }, - }, - start: { - audio: "anchao_shuying", - trigger: { global: "phaseBegin" }, - forced: true, - logTarget: "player", - filter(event, player) { - return ( - event.player?.isIn() && - !player.getEnemies(null, false).includes(event.player) && - event.player.countMark("anchao_shuying") > 0 - ); - }, - async content(event, trigger, player) { - const target = trigger.player; - target.storage.anchao_shuying_effect = target.countMark("anchao_shuying"); - target.addTempSkill("anchao_shuying_effect", { player: "phaseAfter" }); - }, - }, - effect: { - charlotte: true, - group: ["anchao_shuying_draw", "anchao_shuying_damage"], - onremove(player) { - delete player.storage.anchao_shuying_effect; - }, - }, - draw: { - audio: "anchao_shuying", - trigger: { player: "phaseDrawBegin2" }, - forced: true, - filter(event, player) { - return !event.numFixed && player.storage.anchao_shuying_effect > 0; - }, - async content(event, trigger, player) { - trigger.num += player.storage.anchao_shuying_effect; - }, - }, - damage: { - audio: "anchao_shuying", - trigger: { source: "damageBegin1" }, - forced: true, - filter(event, player) { - return ( - player.storage.anchao_shuying_effect > 0 && - event.player?.isIn() && - player.getEnemies(null, false).includes(event.player) - ); - }, - async content(event, trigger, player) { - trigger.num += player.storage.anchao_shuying_effect; - }, - }, - }, - ai: { - threaten: 2, - }, - }, - guixi_shuying: { - mode: ["boss"], - audio: "ext:术樱包/pve/audio/skill/驱鬼逐邪:1", - trigger: { player: "damageEnd" }, - forced: true, - locked: true, - filter(event, player) { - return event.num > 0; - }, - async content(event, trigger, player) { - const num = getTianshuDifficulty() == "nightmare" ? 2 : 1; - const result = await player - .judge(card => get.color(card) == "red" ? 2 : 0) - .set("judge2", result => result.bool) - .forResult(); - - if (result.bool && player.isDamaged()) { - await player.recover(); - } - else { - await player.draw(num); - } - }, - ai: { - maixie: true, - maixie_hp: true, - effect: { - target(card, player, target) { - if (get.tag(card, "damage")) { - if (player.hasSkillTag("jueqing", false, target)) return [1, -1]; - return [1, 0.7]; - } - }, - }, - }, - }, - - //黄蜂 over - mingchong_shuying: { - mode: ["boss"], - audio: "ext:术樱包/pve/audio/skill/驱鬼逐邪:1", - trigger: { player: "die" }, - forced: true, - locked: true, - forceDie: true, - logTarget: "source", - filter(event, player) { - return event.source?.isIn() && event.source.countDiscardableCards(player, "he") > 0; - }, - async content(event, trigger, player) { - const source = trigger.source; - player.line(source); - - const cards = source - .getCards("he") - .filter(card => lib.filter.cardDiscardable(card, source, "mingchong_shuying")); - - if (cards.length) { - if (getTianshuDifficulty() == "nightmare") { - await source.discard(cards); - } - else { - await source.discard(cards.randomGets(Math.ceil(cards.length / 2))); - } - } - }, - ai: { - threaten: 0.8, - effect: { - target(card, player, target) { - if (get.tag(card, "damage") && target.hp <= 1 && player.countCards("he") > 2) { - return [1, -0.8]; - } - }, - }, - }, - }, - duzhen_shuying: { - mode: ["boss"], - audio: "ext:术樱包/pve/audio/skill/驱鬼逐邪:1", - trigger: { player: "useCardToPlayered" }, - forced: true, - locked: true, - logTarget: "target", - filter(event, player) { - return ( - event.targets?.length == 1 && - event.target?.isIn() && - player.getEnemies(null, false).includes(event.target) && - event.target.countDiscardableCards(player, "he") > 0 - ); - }, - async content(event, trigger, player) { - const target = trigger.target; - const num = getTianshuDifficulty() == "nightmare" ? 2 : 1; - const cards = []; - - player.line(target); - - const equips = target - .getCards("e") - .filter(card => lib.filter.cardDiscardable(card, target, "duzhen_shuying")) - .randomGets(num); - cards.addArray(equips); - - if (cards.length < num) { - const hands = target - .getCards("h") - .filter(card => lib.filter.cardDiscardable(card, target, "duzhen_shuying")) - .randomGets(num - cards.length); - cards.addArray(hands); - } - - if (cards.length) { - await target.discard(cards); - } - }, - ai: { - threaten: 1.8, - effect: { - player_use(card, player, target) { - if (target && player.getEnemies(null, false).includes(target) && target.countCards("he") > 0) { - return [1, 0.5]; - } - }, - }, - }, - }, - - //日夜游神 over - huiyun_shuying: { - mode: ["boss"], - audio: "ext:术樱包/pve/audio/skill/驱鬼逐邪:1", - enable: "phaseUse", - usable: 1, - filter(event, player) { - return player.getEnemies(null, false).some(target => target.isIn() && target.countCards("h") > 0); - }, - filterTarget(card, player, target) { - return target.isIn() && player.getEnemies(null, false).includes(target) && target.countCards("h") > 0; - }, - async content(event, trigger, player) { - const target = event.targets[0]; - const hs = target.getCards("h"); - player.line(target); - await target.showCards(hs, `${get.translation(player)}对${get.translation(target)}发动了【${get.translation(event.name)}】`); - - const result = await player - .choosePlayerCard(target, "h", [1, 2], `弃置${get.translation(target)}至多两张手牌`) - .set("filterButton", button => { - const player = get.player(); - const target = get.event().target; - return lib.filter.canBeDiscarded(button.link, player, target); - }) - .set("ai", button => { - const card = button.link; - const player = get.player(); - const target = get.event().target; - let value = get.value(card, target); - if (get.name(card, target) == "tao") value += 3; - if (get.name(card, target) == "shan") value += 1; - return value; - }) - .set("target", target) - .forResult(); - - if (!result.bool || !result.cards?.length) return; - - const names = result.cards.map(card => get.name(card, target)).unique(); - await target.discard(result.cards); - - if (!target.isIn()) return; - - const result2 = await player - .chooseToDiscard("he", `是否弃置一张同名牌,对${get.translation(target)}造成2点伤害?`, card => { - return get.event().names.includes(get.name(card)); - }) - .set("names", names) - .set("ai", card => { - const player = get.player(); - const target = get.event().target; - if (!player.getEnemies(null, false).includes(target)) return 0; - const effect = get.damageEffect(target, player, player); - if (effect <= 0) return 0; - return effect * 3 - get.value(card); - }) - .set("target", target) - .forResult(); - - if (result2.bool) { - const num = getTianshuDifficulty() == "normal" ? 1 : 2 - - player.line(target); - await target.damage(num, player); - } - }, - ai: { - order: 5, - result: { - target(player, target) { - if (!player.getEnemies(null, false).includes(target)) return 0; - return -Math.min(2, target.countCards("h")); - }, - }, - threaten: 1.6, - }, - }, - yezhong_shuying: { - mode: ["boss"], - audio: "ext:术樱包/pve/audio/skill/驱鬼逐邪:1", - trigger: { player: "phaseJieshuBegin" }, - forced: true, - locked: true, - async content(event, trigger, player) { - const result = await player - .judge(card => get.color(card) == "black" ? 2 : -1) - .set("judge2", result => result.bool) - .forResult(); - - if (result.card) { - await player.gain(result.card, "gain2"); - } - - if (result.bool) { - let num = 1; - if (getTianshuDifficulty() == "hard") num = 2; - if (getTianshuDifficulty() == "nightmare") num = 3; - - const targets = player.getEnemies(null, false).filter(target => target.isIn() && target.countCards("h") > 0); - for (const target of targets) { - player.line(target); - const cards = target - .getCards("h") - .filter(card => lib.filter.cardDiscardable(card, target, "yezhong_shuying")) - .randomGets(num); - - if (cards.length) { - await target.discard(cards); - } - } - } - }, - ai: { - threaten: 1.8, - }, - }, - zhoucha_shuying: { - mode: ["boss"], - audio: "ext:术樱包/pve/audio/skill/驱鬼逐邪:1", - trigger: { player: "phaseZhunbeiBegin" }, - forced: true, - locked: true, - async content(event, trigger, player) { - const result = await player - .judge(card => get.color(card) == "red" ? 2 : -1) - .set("judge2", result => result.bool) - .forResult(); - - if (result.card) { - await player.gain(result.card, "gain2"); - } - - if (result.bool) { - player.addTempSkill("zhoucha_shuying_effect", { player: "phaseAfter" }); - } - }, - subSkill: { - effect: { - charlotte: true, - mod: { - cardUsable(card, player, num) { - let number = 1; - if (getTianshuDifficulty() == "hard") number = 2; - if (getTianshuDifficulty() == "nightmare") number = 3; - - if (card.name == "sha") return num + number; - }, - }, - }, - }, - ai: { - threaten: 1.4, - }, - }, - duane_shuying: { - mode: ["boss"], - audio: "ext:术樱包/pve/audio/skill/驱鬼逐邪:1", - trigger: { global: "phaseDiscardEnd" }, - forced: true, - locked: true, - logTarget: "player", - filter(event, player) { - const target = event.player; - return ( - target?.isIn() && - player.getEnemies(null, false).includes(target) && - target.getHistory("lose", evt => { - return ( - evt.type == "discard" && - evt.getParent("phaseDiscard") == event && - evt.cards2?.some(card => get.color(card, target) == "black") - ); - }).length - ); - }, - async content(event, trigger, player) { - const target = trigger.player; - player.line(target); - await target.loseHp(2); - }, - ai: { - threaten: 1.5, - }, - }, - - - //曹操 over - jianxiong_shuying: { - mode: ["boss"], - audio: "ext:术樱包/pve/audio/skill/青青子衿:1", - trigger: { player: "damageEnd" }, - filter(event, player) { - return event.num > 0; - }, - async cost(event, trigger, player) { - event.result = await player.chooseBool(get.prompt2(event.skill)) - .set("ai", () => true) - .forResult(); - }, - async content(event, trigger, player) { - if (get.itemtype(trigger.cards) == "cards" && get.position(trigger.cards[0], true) == "o") { - await player.gain(trigger.cards, "gain2"); - } - await player.draw(); - }, - ai: { - maixie: true, - maixie_hp: true, - effect: { - target(card, player, target) { - if (player.hasSkillTag("jueqing", false, target)) return [1, -1]; - if (get.tag(card, "damage")) return [1, 0.6]; - }, - }, - }, - }, - lingba_shuying: { - mode: ["boss"], - audio: "ext:术樱包/pve/audio/skill/青青子衿:1", - trigger: { player: "phaseBegin" }, - forced: true, - filter(event, player) { - return player.getEnemies(null, false).some(target => target.isIn()) && game.players.every(current => player.countCards("h") >= current.countCards("h")); - }, - async content(event, trigger, player) { - const num = getTianshuDifficulty() == "nightmare" ? 2 : 1 - const enemies = player.getEnemies(null, false).filter(target => target.isIn()); - if (player.countCards("h") >= player.hp * 2) { - for (const target of enemies) { - player.line(target); - await target.damage(num, player); - } - } - else { - const target = enemies.randomGet(); - player.line(target); - await target.damage(num, player); - } - }, - }, - yishen_shuying: { - mode: ["boss"], - audio: "ext:术樱包/pve/audio/skill/青青子衿:1", - trigger: { player: "recoverBegin" }, - filter(event, player) { - return player.getEnemies(null, false).some(target => target.countGainableCards(player, "e") > 0); - }, - async cost(event, trigger, player) { - event.result = await player.chooseBool(get.prompt2(event.skill)) - .set("ai", () => { - const player = get.player(); - const trigger = get.event().getTrigger(); - - if (player.hp <= 1) return false; - if (player.hp + trigger.num <= 2) return false; - - const enemies = player.getEnemies(null, false).filter(target => { - return target.isIn() && target.countGainableCards(player, "e") > 0; - }); - if (!enemies.length) return false; - - const recoverEffect = get.recoverEffect(player, player, player); - const difficulty = getTianshuDifficulty(); - - if (difficulty == "nightmare") { - let total = 0; - let max = 0; - for (const target of enemies) { - for (const card of target.getCards("e")) { - const value = get.value(card, target); - total += value; - if (value > max) max = value; - } - } - if (max < 5) return false; - return total >= recoverEffect + 4; - } - - let best = 0; - for (const target of enemies) { - for (const card of target.getCards("e")) { - const value = get.value(card, target); - if (value > best) best = value; - } - } - - if (best < 5) return false; - return best >= recoverEffect; - }) - .forResult(); - }, - async content(event, trigger, player) { - const enemies = player.getEnemies(null, false).filter(target => target.countGainableCards(player, "e") > 0); - const difficulty = getTianshuDifficulty(); - - trigger.cancel(); - - if (difficulty == "nightmare") { - for (const target of enemies) { - player.line(target); - await player.gainPlayerCard({ target, position: "e", forced: true }); - } - } - else { - const target = enemies.randomGet(); - player.line(target); - await player.gainPlayerCard({ target, position: "e", forced: true }); - } - }, - ai: { - threaten: 1.4, - effect: { - target(card, player, target) { - if (get.tag(card, "recover") && target.hasSkill("yishen_shuying")) { - if (target.hp <= 1) return [1, 1.2]; - if (target.hp <= 2) return [1, 0.8]; - - const enemies = target.getEnemies(null, false).filter(current => { - return current.isIn() && current.countGainableCards(target, "e") > 0; - }); - if (!enemies.length) return; - - let best = 0; - for (const enemy of enemies) { - for (const equip of enemy.getCards("e")) { - best = Math.max(best, get.value(equip, enemy)); - } - } - - if (best >= 5) return [1, 0.4]; - } - }, - }, - }, - }, - - //司马懿 over - fankui_shuying: { - mode: ["boss"], - audio: "ext:术樱包/pve/audio/skill/青青子衿:1", - trigger: { player: "damageEnd" }, - logTarget: "source", - filter(event, player) { - return ( - event.num > 0 && - event.source?.isIn() && - player.getEnemies(null, false).includes(event.source) && - event.source.hasGainableCards(player, "he") - ); - }, - async content(event, trigger, player) { - await player.gainPlayerCard({ - target: trigger.source, - position: "he", - forced: true, - }); - }, - ai: { - maixie_defend: true, - effect: { - target(card, player, target) { - if (player.countCards("he") > 1 && get.tag(card, "damage")) { - if (player.hasSkillTag("jueqing", false, target)) return [1, -1.5]; - if (target.getEnemies(null, false).includes(player)) return [1, 1]; - } - }, - }, - }, - }, - guicai_shuying: { - mode: ["boss"], - audio: "ext:术樱包/pve/audio/skill/青青子衿:1", - trigger: { global: "judge" }, - filter(event, player) { - return player.countCards("hes") > 0; - }, - async cost(event, trigger, player) { - event.result = await player.chooseCard(`${get.translation(trigger.player)}的${trigger.judgestr || ""}判定为${get.translation(trigger.player.judging[0])},${get.prompt(event.skill)}`, "hes", card => { - const player = get.player(); - const mod2 = game.checkMod(card, player, "unchanged", "cardEnabled2", player); - if (mod2 != "unchanged") return mod2; - const mod = game.checkMod(card, player, "unchanged", "cardRespondable", player); - if (mod != "unchanged") return mod; - return true; - }).set("ai", card => { - const trigger = get.event().getTrigger(), player = get.player(), judging = trigger.player.judging[0]; - const result = trigger.judge(card) - trigger.judge(judging); - const attitude = player.getEnemies(null, false).includes(trigger.player) ? -1 : 1; - let value = get.value(card, player); - value /= get.subtype(card) == "equip2" ? 2 : 4; - if (result == 0) return 0; - return attitude > 0 ? result - value : -result - value; - }).set("judging", trigger.player.judging[0]).forResult(); - }, - popup: false, - async content(event, trigger, player) { - const next = player.respond(event.cards, event.name, "highlight", "noOrdering"); - await next; - const { cards } = next; - if (cards?.length) { - if (trigger.player.judging[0].clone) { - trigger.player.judging[0].clone.classList.remove("thrownhighlight"); - game.broadcast(card => { - if (card.clone) card.clone.classList.remove("thrownhighlight"); - }, trigger.player.judging[0]); - game.addVideo("deletenode", player, get.cardsInfo([trigger.player.judging[0].clone])); - } - await game.cardsDiscard(trigger.player.judging[0]); - trigger.player.judging[0] = cards[0]; - trigger.orderingCards.addArray(cards); - game.log(trigger.player, "的判定牌改为", cards); - await game.delay(2); - } - }, - ai: { rejudge: true, tag: { rejudge: 1 } }, - }, - langgu_shuying: { - mode: ["boss"], - audio: "ext:术樱包/pve/audio/skill/青青子衿:1", - trigger: { player: "gainAfter" }, - forced: true, - filter(event, player) { - if (player.hasSkill("langgu_shuying_used")) return false; - return game.hasPlayer(current => { - return ( - current != player && - player.getEnemies(null, false).includes(current) && - event.getl?.(current)?.cards2?.length - ); - }); - }, - async content(event, trigger, player) { - const source = game.findPlayer(current => { - return ( - current != player && - player.getEnemies(null, false).includes(current) && - trigger.getl?.(current)?.cards2?.length - ); - }); - if (!source) return; - - player.addTempSkill("langgu_shuying_used", "roundStart"); - - const result = await player.judge(card => get.suit(card) == "spade" ? 2 : 0).forResult(); - if (result.bool && source.isIn() && source.countDiscardableCards(player, "h") > 0) { - player.removeSkill("langgu_shuying_used"); - player.line(source); - const card = source.getCards("h").randomGet(); - await source.discard(card); - } - }, - subSkill: { - used: { charlotte: true }, - }, - }, - yuanlv_shuying: { - mode: ["boss"], - audio: "ext:术樱包/pve/audio/skill/青青子衿:1", - trigger: { source: "damageBegin4" }, - filter(event, player) { - return event.card && get.type2(event.card) == "trick" && event.player?.isIn() && player.getEnemies(null, false).includes(event.player); - }, - async cost(event, trigger, player) { - event.result = await player.chooseBool(get.prompt(event.skill, trigger.player)) - .set("ai", () => true) - .forResult(); - }, - async content(event, trigger, player) { - const target = trigger.player; - trigger.cancel(); - await player.draw(); - target.line(player); - if (getTianshuDifficulty() == "nightmare") { - await player.damage(1, target, "unreal"); - } - else { - await player.damage(1, target); - } - }, - }, - - //吕布 over - wushuang_shuying: { - mode: ["boss"], - audio: "ext:术樱包/pve/audio/skill/青青子衿:2", - trigger: { source: "damageBegin1" }, - forced: true, - locked: true, - usable(skill, player) { - return getTianshuDifficulty() == "nightmare" ? 2 : 1; - }, - logTarget: "player", - filter(event, player) { - const target = event.player; - const evtx = event.getParent(2); - const card = event.card; - if (!card || !["sha", "juedou"].includes(card.name)) return false; - - if (card.name == "sha") { - return !target.hasHistory("useCard", evt => { - return evt.card.name == "shan" && evt.respondTo && evt.getParent(3) == evtx; - }); - } - - return !target.hasHistory("respond", evt => { - return evt.card.name == "sha" && evt.respondTo && evt.getParent(3) == evtx; - }); - }, - async content(event, trigger, player) { - trigger.num++; - }, - group: ["wushuang_shuying_1", "wushuang_shuying_2"], - subSkill: { - 1: { - audio: "wushuang_shuying", - sourceSkill: "wushuang_shuying", - trigger: { player: "useCardToPlayered" }, - forced: true, - logTarget: "target", - filter(event, player) { - return event.card.name == "sha" && !event.getParent().directHit.includes(event.target); - }, - async content(event, trigger, player) { - const id = trigger.target.playerid; - const map = trigger.getParent().customArgs; - if (!map[id]) map[id] = {}; - - if (typeof map[id].shanRequired == "number") map[id].shanRequired++; - else map[id].shanRequired = 2; - }, - ai: { - directHit_ai: true, - skillTagFilter(player, tag, arg) { - if (arg.card.name != "sha" || arg.target.countCards("h", "shan") > 1) return false; - }, - }, - }, - 2: { - audio: "wushuang_shuying", - sourceSkill: "wushuang_shuying", - trigger: { player: "useCardToPlayered", target: "useCardToTargeted" }, - forced: true, - logTarget(trigger, player) { - return player == trigger.player ? trigger.target : trigger.player; - }, - filter(event, player) { - return event.card.name == "juedou"; - }, - async content(event, trigger, player) { - const id = (player == trigger.player ? trigger.target : trigger.player).playerid; - const idt = trigger.target.playerid; - const map = trigger.getParent().customArgs; - - if (!map[idt]) map[idt] = {}; - if (!map[idt].shaReq) map[idt].shaReq = {}; - if (!map[idt].shaReq[id]) map[idt].shaReq[id] = 1; - - map[idt].shaReq[id]++; - }, - ai: { - directHit_ai: true, - skillTagFilter(player, tag, arg) { - if ( - arg.card.name != "juedou" || - Math.floor(arg.target.countCards("h", "sha") / 2) > player.countCards("h", "sha") - ) { - return false; - } - }, - }, - }, - }, - ai: { - threaten: 1.8, - directHit_ai: true, - }, - }, - shenij_shuying: { - mode: ["boss"], - audio: "ext:术樱包/pve/audio/skill/青青子衿:2", - group: [ - "shenij_shuying_judge", - "shenij_shuying_draw", - "shenij_shuying_sha", - "shenij_shuying_target", - ], - subSkill: { - judge: { - audio: "shenij_shuying", - trigger: { player: "phaseJudgeBegin" }, - filter(event, player) { - return player.countCards("h") >= 2 && player.countCards("j") > 0; - }, - async cost(event, trigger, player) { - event.result = await player - .chooseToDiscard("h", 2, get.prompt2(event.skill)) - .set("ai", card => { - const player = get.player(); - if (!player.countCards("j")) return 0; - return 7 - get.value(card); - }) - .forResult(); - }, - async content(event, trigger, player) { - const cards = player.getCards("j"); - await player.discard(cards); - }, - ai: { - effect: { - target(card, player, target) { - if (get.type(card) == "delay") return [0, 2]; - }, - }, - }, - }, - draw: { - audio: "shenij_shuying", - trigger: { player: "phaseDrawBegin2" }, - forced: true, - filter(event, player) { - return !event.numFixed; - }, - async content(event, trigger, player) { - let num = 2; - if (getTianshuDifficulty() == "hard") num = 3; - if (getTianshuDifficulty() == "nightmare") num = 4; - - trigger.num += num; - }, - }, - sha: { - audio: "shenij_shuying", - forced: true, - mod: { - cardUsable(card, player, num) { - let shaNum = 2; - if (getTianshuDifficulty() == "normal") shaNum = 1; - - if (card.name == "sha") return num + shaNum; - }, - }, - }, - target: { - audio: "shenij_shuying", - trigger: { player: "useCard2" }, - filter(event, player) { - if (event.card.name != "sha") return false; - - const info = get.info(event.card); - if (info.allowMultiple == false || info.multitarget || !event.targets) return false; - - const num = getTianshuDifficulty() == "nightmare" ? 2 : 1; - return game.countPlayer(current => { - return !event.targets.includes(current) && - lib.filter.targetEnabled2(event.card, player, current) && - lib.filter.targetInRange(event.card, player, current); - }) > 0; - }, - async cost(event, trigger, player) { - const num = getTianshuDifficulty() == "nightmare" ? 2 : 1; - - event.result = await player - .chooseTarget( - get.prompt(event.skill), - `为此【杀】额外指定至多${get.cnNumber(num)}名目标`, - [1, num], - (card, player, target) => { - const trigger = get.event().getTrigger(); - return !trigger.targets.includes(target) && - lib.filter.targetEnabled2(trigger.card, player, target) && - lib.filter.targetInRange(trigger.card, player, target); - } - ) - .set("ai", target => { - const player = get.player(); - if (!player.getEnemies(null, false).includes(target)) return 0; - return get.effect(target, get.event().getTrigger().card, player, player); - }) - .forResult(); - }, - async content(event, trigger, player) { - for (const target of event.targets) { - player.line(target); - trigger.targets.add(target); - game.log(target, "成为了", trigger.card, "的额外目标"); - } - }, - ai: { - effect: { - player_use(card, player, target) { - if (card.name == "sha" && player.getEnemies(null, false).includes(target)) return [1, 0.5]; - }, - }, - }, - }, - }, - }, - zhanjia_shuying: { - mode: ["boss"], - audio: "ext:术樱包/pve/audio/skill/青青子衿:2", - trigger: { player: "damageBegin4" }, - forced: true, - locked: true, - usable: 1, - filter(event, player) { - return event.num > 2; - }, - async content(event, trigger, player) { - trigger.num = 2; - await player.draw(getTianshuDifficulty() == "nightmare" ? 3 : 2); - }, - ai: { - filterDamage: true, - effect: { - target(card, player, target) { - if (get.tag(card, "damage")) { - if (player.hasSkillTag("jueqing", false, target)) return; - return [1, 0.8]; - } - }, - }, - }, - }, - - //董卓 over - jiuchi_shuying: { - mode: ["boss"], - audio: "ext:术樱包/pve/audio/skill/青青子衿:1", - enable: "chooseToUse", - filterCard(card, player) { - return get.suit(card, player) == "spade"; - }, - position: "h", - viewAs: { name: "jiu" }, - viewAsFilter(player) { - return player.countCards("h", card => get.suit(card, player) == "spade") > 0; - }, - prompt: "将一张黑桃手牌当【酒】使用", - check(card) { - return 6 - get.value(card); - }, - ai: { - order: 3.5, - respondJiu: true, - skillTagFilter(player) { - return player.countCards("h", card => get.suit(card, player) == "spade") > 0; - }, - }, - }, - roulin_shuying: { - mode: ["boss"], - audio: "ext:术樱包/pve/audio/skill/青青子衿:1", - trigger: { player: "useCardToPlayered", target: "useCardToTargeted" }, - forced: true, - locked: true, - logTarget(trigger, player) { - return player == trigger.player ? trigger.target : trigger.player; - }, - filter(event, player) { - if (event.card.name != "sha") return false; - if (player == event.player) return event.target.hasSex("female"); - return event.player.hasSex("female"); - }, - async content(event, trigger, player) { - const id = (player == trigger.player ? trigger.target : player).playerid; - const map = trigger.getParent().customArgs; - if (!map[id]) map[id] = {}; - - if (typeof map[id].shanRequired == "number") map[id].shanRequired++; - else map[id].shanRequired = 2; - }, - ai: { - halfneg: true, - directHit_ai: true, - skillTagFilter(player, tag, arg) { - if (tag === "directHit_ai") return; - if (arg.card.name != "sha" || !arg.target.hasSex("female") || arg.target.countCards("h", "shan") > 1) { - return false; - } - }, - }, - }, - baonue_shuying: { - mode: ["boss"], - audio: "ext:术樱包/pve/audio/skill/青青子衿:1", - trigger: { player: "phaseBegin" }, - forced: true, - locked: true, - filter(event, player) { - return player.getDamagedHp() > 0; - }, - async content(event, trigger, player) { - let maxNum = 3; - if (getTianshuDifficulty() == "hard") maxNum = 4; - if (getTianshuDifficulty() == "nightmare") maxNum = 5; - - const num = Math.min(maxNum, player.getDamagedHp()); - await player.draw(num); - - const result = await player - .chooseTarget(`对至多${get.cnNumber(num)}名角色造成1点伤害`, [1, num], (card, player, target) => { - return target.isIn() && player.getEnemies(null, false).includes(target); - }) - .set("ai", target => { - const player = get.player(); - return get.damageEffect(target, player, player); - }) - .forResult(); - - if (result.bool) { - for (const target of result.targets) { - player.line(target); - await target.damage(1, player); - } - } - - await player.loseHp(); - }, - ai: { - threaten: 2, - }, - }, - qubu_shuying: { - mode: ["boss"], - audio: "ext:术樱包/pve/audio/skill/青青子衿:1", - trigger: { global: "useCardToPlayered" }, - forced: true, - locked: true, - filter(event, player) { - return ( - event.card.name == "sha" && - event.player?.isIn() && - !player.getEnemies(null, false).includes(event.player) && - event.targets?.length > 0 - ); - }, - async content(event, trigger, player) { - const result = await player - .judge(card => get.color(card) == "black" ? 2 : -1) - .set("judge2", result => result.bool) - .forResult(); - - if (result.bool) { - const targets = trigger.targets.filter(target => target.isIn()); - for (const target of targets) { - player.line(target); - await target.damage(1, player); - } - } - }, - ai: { - threaten: 1.8, - }, - }, - - - //水神共工 over - shuishen_shuying: { - mode: ["boss"], - forced: true, - group: ["shuishen_shuying_recover", "shuishen_shuying_draw", "shuishen_shuying_damage"], - subSkill: { - recover: { - trigger: { player: "recoverBegin" }, - audio: false, - forced: true, - filter(event, player) { - return game.shuying_getDiscardNamesNum() >= 4; - }, - async content(event, trigger, player) { - trigger.num++; - }, - }, - draw: { - trigger: { player: "phaseDrawBegin2" }, - audio: false, - forced: true, - filter(event, player) { - return !event.numFixed && game.shuying_getDiscardNamesNum() >= 5; - }, - async content(event, trigger, player) { - trigger.num += 5; - }, - }, - damage: { - trigger: { source: "damageBegin1" }, - audio: false, - forced: true, - filter(event, player) { - return game.shuying_getDiscardNamesNum() >= 9; - }, - async content(event, trigger, player) { - trigger.num++; - }, - }, - }, - }, - tuanliu_shuying: { - mode: ["boss"], - trigger: { player: "phaseJieshuBegin" }, - audio: false, - forced: true, - filter(event, player) { - return game.shuying_getDiscardNum() > 4; - }, - async content(event, trigger, player) { - let hp = 2; - let card = 4; - let damage = 1; - - if (getTianshuDifficulty() == "nightmare") { - hp = 3; - card = 6; - } - - - const num = game.shuying_getDiscardNum(); - if (num > 4) await player.recover(hp); - if (num > 5) await player.draw(card); - if (num > 9) { - for (const target of player.getEnemies(null, false).filter(target => target.isIn())) { - player.line(target); - await target.damage(player, damage); - } - } - }, - }, - juehong_shuying: { - mode: ["boss"], - trigger: { player: "phaseZhunbeiBegin" }, - audio: false, - forced: true, - filter(event, player) { - return player.getEnemies(null, false).some(target => target.isIn()); - }, - async content(event, trigger, player) { - for (const target of player.getEnemies(null, false).filter(target => target.isIn())) { - player.line(target); - if (target.countCards("e")) await target.chooseToDiscard("e", true, target.countCards("e")); - else if (target.countCards("h")) await target.discard(target.getCards("h").randomGets(Math.min(2, target.countCards("h")))); - } - }, - }, - - //白起 - changsheng_shuying: { - mode: ["boss"], - audio: "ext:术樱包/pve/audio/skill:1", - forced: true, - mod: { - targetInRange(card, player, target) { - if (card.name == "sha") return true; - }, - }, - }, - shashen_shuying: { - mode: ["boss"], - audio: "ext:术樱包/pve/audio/skill:1", - enable: ["chooseToUse", "chooseToRespond"], - filterCard: true, - position: "h", - viewAs: { name: "sha" }, - viewAsFilter(player) { - return player.countCards("h") > 0; - }, - prompt: "将一张手牌当【杀】使用或打出", - check(card) { - return 6 - get.value(card); - }, - group: "shashen_shuying_draw", - subSkill: { - draw: { - trigger: { source: "damageEnd" }, - filter(event, player) { - return event.card?.name == "sha"; - }, - async content(event, trigger, player) { - await player.draw(3); - }, - }, - }, - ai: { - respondSha: true, - skillTagFilter(player) { - return player.countCards("h") > 0; - }, - order: 4, - }, - }, - wuan_shuying: { - mode: ["boss"], - audio: "ext:术樱包/pve/audio/skill:1", - forced: true, - mod: { - cardUsable(card, player, num) { - if (card.name == "sha") return num + 3; - }, - }, - trigger: { source: "damageBegin1" }, - filter(event, player) { - return event.card?.name == "sha"; - }, - async content(event, trigger, player) { - trigger.num++; - }, - }, - - //盘古 - shenqu_shuying: { - mode: ["boss"], - audio: "ext:术樱包/pve/audio/skill:1", - trigger: { player: "damageEnd" }, - forced: true, - filter(event, player) { - return player.countCards("h", card => get.color(card) == "red") > 0; - }, - async content(event, trigger, player) { - const cards = player.getCards("h", card => get.color(card) == "red"); - await player.lose(cards, ui.cardPile, "insert"); - await player.draw(cards.length); - }, - }, - lieben_shuying: { - mode: ["boss"], - audio: "ext:术樱包/pve/audio/skill:1", - trigger: { player: "useCardToPlayered" }, - forced: true, - filter(event, player) { - return event.card.name == "sha" && ui.cardPile.lastChild; - }, - async content(event, trigger, player) { - const judge = player.judge(card => get.color(card) == "red" ? 2 : -1); - judge.directresult = ui.cardPile.lastChild; - judge.judge2 = result => result.bool; - const result = await judge.forResult(); - if (result.bool) { - trigger.getParent().addCount = false; - trigger.getParent().baseDamage++; - } - }, - }, - yinjiang_shuying: { - mode: ["boss"], - audio: "ext:术樱包/pve/audio/skill:1", - trigger: { player: "drawEnd" }, - forced: true, - filter(event, player) { - return player.isPhaseUsing() && !player.hasSkill("yinjiang_shuying_disabled") && ui.cardPile.lastChild; - }, - async content(event, trigger, player) { - const card = ui.cardPile.lastChild; - await player.gain(card, "gain2"); - if (get.color(card) == "red") { - let count = 0; - for (const target of player.getEnemies(null, false).filter(target => target.isIn())) { - player.line(target); - await target.damage(player); - count++; - } - player.storage.yinjiang_shuying_count = (player.storage.yinjiang_shuying_count || 0) + count; - if (player.storage.yinjiang_shuying_count >= 2) player.addTempSkill("yinjiang_shuying_disabled", { player: "phaseUseEnd" }); - } - }, - group: "yinjiang_shuying_clear", - subSkill: { - clear: { - trigger: { player: "phaseUseEnd" }, - silent: true, - async content(event, trigger, player) { - delete player.storage.yinjiang_shuying_count; - }, - }, - disabled: { charlotte: true }, - }, - }, - zhuri_shuying: { - mode: ["boss"], - audio: "ext:术樱包/pve/audio/skill:1", - forced: true, - group: ["zhuri_shuying_draw", "zhuri_shuying_bottom"], - subSkill: { - draw: { - trigger: { player: "useCard" }, - forced: true, - filter(event, player) { - return get.color(event.card) == "red"; - }, - async content(event, trigger, player) { - await player.draw(); - }, - }, - bottom: { - trigger: { global: "useCardAfter" }, - forced: true, - filter(event, player) { - return event.targets?.includes(player) && (event.card.name == "sha" || get.type(event.card) == "trick") && event.cards?.filterInD("d").length; - }, - async content(event, trigger, player) { - const cards = trigger.cards.filterInD("d"); - game.log(cards, "被置于牌堆底"); - for (const card of cards) ui.cardPile.appendChild(card); - }, - }, - }, - }, - - //少昊 over - baiyi_shuying: { - mode: ["boss"], - trigger: { global: "phaseBegin" }, - forced: true, - logTarget: "player", - filter(event, player) { - return ( - game.roundNumber < 7 && - event.player?.isIn() && - player.getEnemies(null, false).includes(event.player) - ); - }, - async content(event, trigger, player) { - let num = 1; - const target = trigger.player; - player.line(target); - - const difficulty = getTianshuDifficulty(); - - if (difficulty == "hard") { - num = 2; - } else if (difficulty == "nightmare") { - num = 3; - } - - if (game.roundNumber < 3 && target.countGainableCards(player, "he") > 0) { - const cards = target.getCards("he").randomGets(Math.min(num, target.countCards("he"))); - await player.gain(cards, target, "giveAuto"); - } - - if (game.roundNumber < 5 && target.isIn()) { - await target.damage(num, "thunder", player); - } - - if (game.roundNumber < 7 && target.isIn()) { - const cards = target - .getCards("he") - .filter(card => lib.filter.cardDiscardable(card, target, "baiyi_shuying")) - .randomGets(num); - - if (cards.length) { - await target.discard(cards); - } - } - }, - ai: { - threaten: 3, - }, - }, - shenen_shuying: { - mode: ["boss"], - forced: true, - global: "shenen_shuying_global", - subSkill: { - global: { - mod: { - targetInRange(card, player, target) { - const owner = game.findPlayer(current => current.hasSkill("shenen_shuying")); - if (owner && !owner.getEnemies(null, false).includes(player)) return true; - }, - }, - }, - }, - ai: { - threaten: 1.5, - }, - }, - - //玄女 over - xuanlie_shuying: { - mode: ["boss"], - audio: "ext:术樱包/pve/audio/skill/天书乱斗:1", - trigger: { player: "phaseJieshuBegin" }, - forced: true, - locked: true, - filter(event, player) { - return game.hasPlayer(current => { - return ( - current.isIn() && - player.getEnemies(null, false).includes(current) && - player.getHistory("gain", evt => evt.getl?.(current)?.cards2?.length).length - ); - }); - }, - async content(event, trigger, player) { - const targets = game.filterPlayer(current => { - return ( - current.isIn() && - player.getEnemies(null, false).includes(current) && - player.getHistory("gain", evt => evt.getl?.(current)?.cards2?.length).length - ); - }); - - let num = 2; - if (getTianshuDifficulty() == "normal") num = 1; - - for (const target of targets) { - player.line(target); - await target.damage(num, player); - } - }, - ai: { - threaten: 2, - }, - }, - jiutian_shuying: { - mode: ["boss"], - audio: "ext:术樱包/pve/audio/skill/天书乱斗:1", - trigger: { player: "phaseZhunbeiBegin" }, - forced: true, - locked: true, - filter(event, player) { - return player.getEnemies(null, false).some(target => target.isIn() && target.countGainableCards(player, "h") > 0); - }, - async content(event, trigger, player) { - let num = 2; - const difficulty = getTianshuDifficulty(); - if (difficulty == "normal") num = 1; - - const targets = player.getEnemies(null, false).filter(target => target.isIn() && target.countGainableCards(player, "h") > 0); - const gained = []; - - for (const target of targets) { - const cards = target.getCards("h").randomGets(Math.min(num, target.countCards("h"))); - if (cards.length) { - player.line(target); - await player.gain(cards, target, "giveAuto"); - gained.addArray(cards); - } - } - - if (!gained.length) return; - - const colors = gained.map(card => get.color(card, player)).unique(); - const suits = gained.map(card => get.suit(card, player)).unique(); - - if (colors.length >= 2) { - for (const target of targets) { - if (!target.isIn()) continue; - player.line(target); - - target.damage(1, player); - } - } - - if (difficulty == "nightmare" && suits.length >= 4) { - for (const target of targets) { - if (!target.isIn()) continue; - player.line(target); - await target.loseHp(); - } - } - }, - ai: { - threaten: 3, - }, - }, - dishi_shuying: { - mode: ["boss"], - audio: "ext:术樱包/pve/audio/skill/天书乱斗:1", - trigger: { player: "useCard2" }, - filter(event, player) { - if (!event.targets?.length) return false; - if (event.card.name != "sha" && get.type(event.card) != "trick") return false; - - const info = get.info(event.card); - if (event.targets.length == 1) { - if (info.allowMultiple == false || info.multitarget) return false; - return game.hasPlayer(current => { - return ( - !event.targets.includes(current) && - lib.filter.targetEnabled2(event.card, player, current) && - lib.filter.targetInRange(event.card, player, current) - ); - }); - } - - return event.targets.length > 1; - }, - async cost(event, trigger, player) { - if (trigger.targets.length == 1) { - event.result = await player - .chooseTarget(get.prompt(event.skill), `为${get.translation(trigger.card)}增加一个目标`, (card, player, target) => { - const trigger = get.event().getTrigger(); - return ( - !trigger.targets.includes(target) && - lib.filter.targetEnabled2(trigger.card, player, target) && - lib.filter.targetInRange(trigger.card, player, target) - ); - }) - .set("ai", target => { - const player = get.player(); - const trigger = get.event().getTrigger(); - const effect = get.effect(target, trigger.card, player, player); - if (player.getEnemies(null, false).includes(target)) return effect; - return Math.min(0, effect); - }) - .forResult(); - } - else { - event.result = await player - .chooseTarget(get.prompt(event.skill), `令${get.translation(trigger.card)}减少一个目标`, (card, player, target) => { - return get.event().getTrigger().targets.includes(target); - }) - .set("ai", target => { - const player = get.player(); - const trigger = get.event().getTrigger(); - return -get.effect(target, trigger.card, player, player); - }) - .forResult(); - } - }, - async content(event, trigger, player) { - const target = event.targets[0]; - player.line(target); - - if (trigger.targets.includes(target)) { - trigger.targets.remove(target); - game.log(target, "不再是", trigger.card, "的目标"); - } - else { - trigger.targets.add(target); - game.log(target, "成为了", trigger.card, "的额外目标"); - } - }, - ai: { - threaten: 1.6, - }, - }, - - //旱魃 - xinji_shuying: { - mode: ["boss"], - audio: "ext:术樱包/pve/audio/skill/天书乱斗:2", - trigger: { global: "loseAfter" }, - forced: true, - locked: true, - filter(event, player) { - return ( - _status.currentPhase && - _status.currentPhase.isIn() && - event.player?.isIn() && - event.player != _status.currentPhase && - !player.getEnemies(null, false).includes(event.player) && - event.type == "discard" && - event.cards2?.some(card => get.position(card, true) == "d" && event.hs?.includes(card)) - ); - }, - async content(event, trigger, player) { - const difficulty = getTianshuDifficulty(); - const num = difficulty == "nightmare" ? 2 : 1; - const target = _status.currentPhase; - player.line(target); - await target.damage(num, player); - }, - ai: { - threaten: 1.6, - }, - }, - zhiri_shuying: { - mode: ["boss"], - audio: "ext:术樱包/pve/audio/skill/天书乱斗:2", - trigger: { global: "useCardToPlayered" }, - forced: true, - locked: true, - filter(event, player) { - return ( - event.isFirstTarget && - event.player?.isIn() && - player.getEnemies(null, false).includes(event.player) && - get.type2(event.card) == "trick" && - get.color(event.card) == "red" - ); - }, - async content(event, trigger, player) { - let num = 1; - const difficulty = getTianshuDifficulty(); - if (difficulty == "hard") { - num = 2; - } else if (difficulty == "nightmare") { - num = 3; - } - - await player.draw(num); - }, - ai: { - threaten: 1.4, - }, - }, - fenshi_shuying: { - mode: ["boss"], - audio: "ext:术樱包/pve/audio/skill/天书乱斗:2", - trigger: { player: "phaseZhunbeiBegin" }, - forced: true, - locked: true, - filter(event, player) { - const num = player.countCards("h") - player.hp; - if (num < 0) return true; - if (num > 0) return player.getEnemies(null, false).some(target => target.isIn()); - return false; - }, - async content(event, trigger, player) { - const num = player.countCards("h") - player.hp; - - if (num < 0) { - await player.draw(-num); - return; - } - - const enemies = player.getEnemies(null, false).filter(target => target.isIn()); - const map = new Map(); - - for (let i = 0; i < num && enemies.length; i++) { - const target = enemies.randomGet(); - map.set(target, (map.get(target) || 0) + 1); - } - - for (const [target, damage] of map) { - if (!target.isIn()) continue; - player.line(target); - await target.damage(damage, player); - } - }, - ai: { - threaten: 2, - }, - }, -}; +import shiershengxiao from "./sets/shiershengxiao/skill.js"; +import zhuoguiquxie from "./sets/zhuoguiquxie/skill.js"; +import qingqingzijin from "./sets/qingqingzijin/skill.js"; +import tianshu from "./sets/tianshu/skill.js"; +import mergeBossModules from "./merge.js"; + +// 汇总所有主题技能,确保跨 PVE 复用 BOSS 时技能始终随统一入口注册。 +const skills = mergeBossModules("技能", [ + ["十二生肖", shiershengxiao], + ["捉鬼驱邪", zhuoguiquxie], + ["青青子衿", qingqingzijin], + ["天书乱斗", tianshu], +]); export default skills; diff --git a/pve/boss/translate.js b/pve/boss/translate.js index 8d5af0e..648e591 100644 --- a/pve/boss/translate.js +++ b/pve/boss/translate.js @@ -1,153 +1,15 @@ -const translates = { - zishu_boss_shuying: "子鼠", - zishu_shuying: "子鼠", - zishu_shuying_info: "出牌阶段限一次,你可以获得手牌数大于你的其他角色一张手牌,你可以重复此流程,直到你的手牌数为全场最多。", +import shiershengxiao from "./sets/shiershengxiao/translate.js"; +import zhuoguiquxie from "./sets/zhuoguiquxie/translate.js"; +import qingqingzijin from "./sets/qingqingzijin/translate.js"; +import tianshu from "./sets/tianshu/translate.js"; +import mergeBossModules from "./merge.js"; - chenlong_boss_shuying: "辰龙", - chenlong_shuying: "辰龙", - chenlong_shuying_info: "限定技,出牌阶段,你可以失去任意点体力(至多为5),然后对一名其他角色造成等量的伤害。若你以此法进入濒死状态,则你将体力值回复至1,然后减少1点体力上限。", - - wuma_boss_shuying: "午马", - wuma_shuying: "午马", - wuma_shuying_info: "锁定技,你不能被翻面;你的阶段不能被跳过;当你成为其他角色使用锦囊牌的目标后,摸一张牌。", - - weiyang_boss_shuying: "未羊", - weiyang_shuying: "未羊", - weiyang_shuying_info: "出牌阶段限一次,你可以弃置任意张不同类型的牌,然后令至多等量角色回复1点体力。", - - shenhou_boss_shuying: "申猴", - shenhou_shuying: "申猴", - shenhou_shuying_info: "当你成为【杀】的目标时,你可以进行判定,若结果为红色,则此【杀】对你无效。", - - xvgou_boss_shuying: "戌狗", - xvgou_shuying: "戌狗", - xvgou_shuying_info: "锁定技,红色【杀】对你无效;你使用红色【杀】无距离限制且造成伤害+1.", - - heibaiwuchang_boss_shuying: "黑白无常", - mizui_shuying: "迷醉", - mizui_shuying_info: "锁定技,你的伤害牌造成伤害后,你随机弃置受伤角色的1张牌。", - qiangzheng_shuying: "强征", - qiangzheng_shuying_info: "锁定技,敌方角色的结束阶段,若其手牌数小于4,你获得其所有手牌。", - xixing_shuying: "吸星", - xixing_shuying_info: "锁定技,准备阶段,随机对敌方体力最多的一名角色造成2点雷电伤害,然后你回复等量体力", - - niutoumamian_boss_shuying: "牛头马面", - shiyv_shuying: "施狱", - shiyv_shuying_info: "锁定技,摸牌阶段,你放弃摸牌改为随机获得牌堆中每种花色的牌各一张。", - manji_shuying: "蛮击", - manji_shuying_info: "你使用【杀】指定单一目标后,你可以弃置该角色2张手牌。若其中有【杀】,你本次【杀】造成的伤害+1。", - xiaoshou_shuying: "枭首", - xiaoshou_shuying_info: "锁定技,准备阶段,你对随机一名敌方角色造成2点伤害", - - yvsai_boss_shuying: "鱼鳃", - anchao_shuying: "暗潮", - anchao_shuying_info: "锁定技,己方角色的回合结束时,若此回合该角色没有造成过伤害,则获得一个“暗潮”标记。己方回合开始时,若该角色有“暗潮”标记,则本回合摸牌阶段多摸X张牌且对敌方角色造成的伤害+X(X为“暗潮”标记数)", - guixi_shuying: "鬼吸", - guixi_shuying_info: "锁定技,当你受到伤害后,你进行一次判定,若结果为红色,你回复1点体力;若结果不为红色,你摸1张牌。", - - huangfeng_boss_shuying: "黄蜂", - mingchong_shuying: "冥虫", - mingchong_shuying_info: "锁定技,你死亡时,杀死你的角色随机弃置一半的牌。", - duzhen_shuying: "毒针", - duzhen_shuying_info: "锁定技,你使用牌指定单一敌方角色为目标后,该角色随机弃置1张牌(优先装备区的牌)。", - - riyeyoushen_boss_shuying: "日夜游神", - huiyun_shuying: "晦运", - huiyun_shuying_info: "出牌阶段限一次,你可以展示一名敌方角色的手牌,并弃置其中至多两张牌。然后你可以弃置一张与该角色弃置牌牌名相同的牌,对其造成2点伤害。", - yezhong_shuying: "夜冢", - yezhong_shuying_info: "锁定技,结束阶段,你进行一次判定并获得判定牌,若次牌为黑色,每名敌方角色随机弃1张手牌。", - zhoucha_shuying: "昼刹", - zhoucha_shuying_info: "锁定技,准备阶段,你进行一次判定并获得判定牌,若此牌为红色,你本回合使用【杀】的次数+1。", - duane_shuying: "断恶", - duane_shuying_info: "锁定技,敌方角色的弃牌阶段结束时,若其此阶段弃置了黑色牌,则该角色失去2点体力。", - - - caocao_boss_shuying: "曹操", - jianxiong_shuying: "奸雄", - jianxiong_shuying_info: "当你受到伤害后,你可以获得对你造成伤害的牌并摸一张牌。", - lingba_shuying: "凌霸", - lingba_shuying_info: "锁定技,你的回合开始时,若你手牌数为全场最多,则对一名随机敌人造成1点伤害。若你手牌数大于等于你体力值的两倍,则改为对所有敌人造成伤害。", - yishen_shuying: "疑神", - yishen_shuying_info: "当你回复体力时,可以改为获得一名随机敌方角色一张随机装备。", - - simayi_boss_shuying: "司马懿", - fankui_shuying: "反馈", - fankui_shuying_info: "当你受到1点伤害后,你可以获得伤害来源的一张牌。", - guicai_shuying: "鬼才", - guicai_shuying_info: "当一名角色的判定牌生效前,你可以打出一张牌代替之。", - langgu_shuying: "狼顾", - langgu_shuying_info: " 锁定技,每回合限一次,当你获得其他角色的牌时,进行一次判定:若结果为黑桃,随机弃置其1张手牌,且视为此技能本回合未发动过。", - yuanlv_shuying: "远虑", - yuanlv_shuying_info: "当你使用锦囊牌对敌方角色造成伤害时,你可以防止该伤害,改为摸一张牌且该敌方角色对你造成1点伤害。", - - lvbu_boss_shuying: "吕布", - wushuang_shuying: "无双", - wushuang_shuying_info: "锁定技。①你使用的【杀】需两张【闪】才能抵消;与你进行【决斗】的角色每次需要打出两张【杀】。②每回合限1次,当你使用【杀】或【决斗】造成伤害时,若受伤角色没有使用或打出过【杀】或【闪】响应此牌,则此伤害+1。", - shenij_shuying: "神戟", - shenij_shuying_info: "判定阶段,你可以弃置两张手牌,然后弃置你判定区里的牌;摸牌阶段,你多摸两张牌;出牌阶段,你可以多使用一张【杀】,你的【杀】可以多指定一名角色为目标。", - zhanjia_shuying: "战甲", - zhanjia_shuying_info: "锁定技,每回合限一次,当你受到大于2点的伤害时,将此伤害减至2点,然后摸两张牌。", - - dongzhuo_boss_shuying: "董卓", - jiuchi_shuying: "酒池", - jiuchi_shuying_info: "你可以将一张黑桃手牌当【酒】使用。", - roulin_shuying: "肉林", - roulin_shuying_info: "锁定技,你对女性角色使用的【杀】和女性角色对你使用的【杀】均需使用两张【闪】才能抵消。", - baonue_shuying: "暴虐", - baonue_shuying_info: "锁定技,回合开始时,你摸X张牌并对至多X名角色造成1点伤害,然后你失去1点体力。X为你已损失体力且最大为3。", - qubu_shuying: "驭布", - qubu_shuying_info: "锁定技,当友方角色使用【杀】指定目标时,进行一次判定:若结果为黑桃,你对此【杀】的所有目标造成1点伤害。", - - - shuishengonggong_boss_shuying: "水神共工", - shuishen_shuying: "水神", - shuishen_shuying_info: "锁定技,弃牌堆中,不同牌名的牌数达到4时,你回复体力时回复量+1;达到5时,摸牌阶段额外摸5张牌;达到9时,你造成伤害时伤害+1。", - tuanliu_shuying: "湍流", - tuanliu_shuying_info: "锁定技,结束阶段,若本回合进入弃牌堆的卡牌数量:大于4,你回复2点体力;大于5,你摸4张牌;大于9,你对所有敌方角色造成1点伤害。", - juehong_shuying: "决洪", - juehong_shuying_info: "锁定技,准备阶段,你令所有敌方角色自己弃置自己的装备区内的所有牌,若其装备区内没有牌,则改为随机弃置2张手牌", - - baiqi_boss_shuying: "白起", - changsheng_shuying: "常胜", - changsheng_shuying_info: "锁定技,你使用【杀】无距离限制。", - shashen_shuying: "杀神", - shashen_shuying_info: "你可以将手牌中的任意牌当【杀】使用或打出。你使用的【杀】造成伤害后,摸三张牌。", - wuan_shuying: "武安", - wuan_shuying_info: "锁定技,你可使用的【杀】的次数+3,【杀】造成的伤害+1。", - - kuafu_boss_shuying: "盘古", - shenqu_shuying: "神躯", - shenqu_shuying_info: "锁定技,当你受到伤害后,将所有红色手牌置于牌堆底,并摸等量的牌。", - lieben_shuying: "烈奔", - lieben_shuying_info: "锁定技,当你使用【杀】指定目标后,使用牌堆底的牌进行一次判定:若判定结果为红色,则此杀不计入出牌阶段使用次数且伤害+1。", - yinjiang_shuying: "饮江", - yinjiang_shuying_info: "锁定技,当你在出牌阶段摸牌后,获得牌堆底的1张牌。若该牌为红色,则对所有敌方角色造成1点伤害。当你在同一个阶段内以此法造成过两次活更多的伤害后,该技能失效直到回合结束。", - zhuri_shuying: "逐日", - zhuri_shuying_info: "锁定技,你使用红色牌时摸1张牌。当你成为【杀】或普通锦囊牌的目标后,结算完毕后将该牌放置在牌堆底。", - - shaohao_boss_shuying: "少昊", - baiyi_shuying: "白仪", - baiyi_shuying_info: "锁定技,每名敌方角色的回合开始时,若当前轮数小于3,你随机获得其1张牌;若当前轮数小于5,对其造成1点雷电伤害;若当前轮数小于7,其随机弃置1张牌。", - shenen_shuying: "神恩", - shenen_shuying_info: "锁定技,所有己方角色使用牌锁定技,所有己方角色使用牌没有距离限制。", - - - xuannv_boss_shuying: "玄女", - xuanlie_shuying: "玄烈", - xuanlie_shuying_info: "锁定技,回合结束时,对所有本回合你获得过其牌的敌方角色依次造成1点伤害。", - jiutian_shuying: "九天", - jiutian_shuying_info: "锁定技,准备阶段,你获得所有敌方角色各1张手牌。若你以此法获得的牌包含2种颜色,则对所有你以此法获得其牌的敌方角色造成1点伤害。", - dishi_shuying: "帝师", - dishi_shuying_info: "当你使用【杀】或普通锦囊牌指定目标时,如果目标数为1,可以增加一个目标;如果目标数大于1,你可以减少一个目标。", - - hanba_boss_shuying: "旱魃", - xinji_shuying: "心悸", - xinji_shuying_info: "锁定技,当友方于回合外因弃置而失去手牌时,你对当前角色造成1点伤害。", - zhiri_shuying: "炙日", - zhiri_shuying_info: "锁定技,当敌方角色使用红色锦囊牌指定目标后,你摸1张牌。", - fenshi_shuying: "焚世", - fenshi_shuying_info: "锁定技,准备阶段,若你的手牌数小于体力值,则将手牌摸至于体力值相等;若你的手牌数大于体力值,则你对敌方角色造成共计X点伤害,点数随机分配(X为手牌数减体力值)。", - -}; +// 汇总所有主题翻译,并阻止同名翻译在后加载模块中被覆盖。 +const translates = mergeBossModules("翻译", [ + ["十二生肖", shiershengxiao], + ["捉鬼驱邪", zhuoguiquxie], + ["青青子衿", qingqingzijin], + ["天书乱斗", tianshu], +]); export default translates; diff --git a/tianshu/config.js b/tianshu/config.js index d523d7b..4412bc7 100644 --- a/tianshu/config.js +++ b/tianshu/config.js @@ -1,6 +1,6 @@ const bossList = { - //stage1: ["riyeyoushen_boss_shuying", "dongzhuo_boss_shuying"], - stage1: ["chenlong_boss_shuying", "weiyang_boss_shuying", "shenhou_boss_shuying", "xvgou_boss_shuying"], + // stage1: ["caocao_boss_shuying", "dongzhuo_boss_shuying"], + stage1: ["zishu_boss_shuying", "wuma_boss_shuying", "chenlong_boss_shuying", "weiyang_boss_shuying", "shenhou_boss_shuying", "xvgou_boss_shuying"], stage2: ["heibaiwuchang_boss_shuying", "yvsai_boss_shuying", "huangfeng_boss_shuying", "riyeyoushen_boss_shuying"], stage3: ["caocao_boss_shuying", "simayi_boss_shuying", "lvbu_boss_shuying", "dongzhuo_boss_shuying"], stage4: ["shuishengonggong_boss_shuying", "shaohao_boss_shuying", "xuannv_boss_shuying", "hanba_boss_shuying"], @@ -121,11 +121,11 @@ const bossDifficulty = { ...bossDifficultyTemplates.zhuoguiquxie, hard: { ...bossDifficultyTemplates.zhuoguiquxie.hard, - skill: ["xixing_shuying"], + skills: ["xixing_shuying"], }, nightmare: { ...bossDifficultyTemplates.zhuoguiquxie.nightmare, - skill: ["xixing_shuying", "taiping_shuying"], + skills: ["xixing_shuying", "taiping_shuying"], }, }, yvsai_boss_shuying: { @@ -139,7 +139,7 @@ const bossDifficulty = { ...bossDifficultyTemplates.zhuoguiquxie, nightmare: { ...bossDifficultyTemplates.zhuoguiquxie.nightmare, - skill: ["duane_shuying"], + skills: ["duane_shuying"], }, }, @@ -194,11 +194,11 @@ const bossDifficulty = { ...bossDifficultyTemplates.tianshuluandou, hard: { ...bossDifficultyTemplates.tianshuluandou.hard, - skill: ["shuishen_shuying"], + skills: ["shuishen_shuying"], }, nightmare: { ...bossDifficultyTemplates.tianshuluandou.nightmare, - skill: ["shuishen_shuying"], + skills: ["shuishen_shuying"], }, }, shaohao_boss_shuying: { ...bossDifficultyTemplates.tianshuluandou, }, diff --git a/tianshu/extension.js b/tianshu/extension.js index 2b07447..ae674df 100644 --- a/tianshu/extension.js +++ b/tianshu/extension.js @@ -9,9 +9,10 @@ export default function initTianshu(lib, game, ui, get, ai, _status, shuYing) { const state = { difficulty: "normal", stageIndex: 0, - currentBosses: [], selectedSkills: {}, selectedSkillSet: {}, + difficultyBossMap: {}, + originalBossMap: {}, playerSideIds: {}, roundStartPlayer: null, roundStartSeat: 0, @@ -59,8 +60,8 @@ export default function initTianshu(lib, game, ui, get, ai, _status, shuYing) { const getVirtualIdolIds = () => tianshuConfig.virtualIdolList || Object.values(tianshuConfig.virtualIdols).flat(); // 判断指定角色是否为虚拟偶像。 const isVirtualIdol = player => Boolean(player && (getVirtualIdolIds().includes(player.name1) || getVirtualIdolIds().includes(player.name))); - // 判断指定角色是否属于当前关卡 Boss 列表。 - const isCurrentStageBoss = player => Boolean(player && (state.currentBosses.includes(player.name1) || state.currentBosses.includes(player.name))); + // 判断指定角色是否为当前关卡实际召唤的 Boss;玩家变身为同名动态武将时不会被误判。 + const isCurrentStageBoss = player => Boolean(player?.storage?.shuYing_tianshu_stageBoss && player.storage.shuYing_tianshu_stageIndex == state.stageIndex); // 判断指定角色是否属于开局捕获的玩家方。 const isCapturedPlayerSide = player => Boolean(getPlayerKey(player) && state.playerSideIds[getPlayerKey(player)]); // 获取玩家方角色列表,可按是否存活、是否包含虚拟偶像过滤。 @@ -227,8 +228,9 @@ export default function initTianshu(lib, game, ui, get, ai, _status, shuYing) { state.difficulty = keys[Math.max(0, controls.indexOf(result.control))] || "normal"; _status[tianshuConfig.settings.difficultyStatusKey] = state.difficulty; }; - // 在指定座位创建关卡 Boss;初始主 Boss 已静默死亡,所以关卡 Boss 全部使用忠臣身份。 - const addStageBoss = (position, name) => { + // 在指定座位创建当前难度的动态 Boss;初始主 Boss 已静默死亡,所以关卡 Boss 全部使用忠臣身份。 + const addStageBoss = (position, originalName) => { + const name = state.difficultyBossMap[originalName] || originalName; const boss = game.addFellow(position, name, "zoominanim"); boss.dataset.position = position; boss.side = true; @@ -236,6 +238,7 @@ export default function initTianshu(lib, game, ui, get, ai, _status, shuYing) { boss.setIdentity(boss.identity); boss.storage.shuYing_tianshu_stageBoss = true; boss.storage.shuYing_tianshu_stageIndex = state.stageIndex; + boss.storage.shuYing_tianshu_originalBossName = originalName; game.addVideo("setIdentity", boss, boss.identity); if (game.playerMap) game.playerMap[boss.dataset.position] = boss; game.arrangePlayers(); @@ -246,7 +249,12 @@ export default function initTianshu(lib, game, ui, get, ai, _status, shuYing) { const num = Number(value); return Number.isFinite(num) ? num : null; }; - const getBossConfigName = player => player?.name1 || player?.name || ""; + // 将动态难度武将 ID 还原为 config.js 中使用的原始 Boss ID。 + const getBossConfigName = source => { + if (typeof source == "string") return state.originalBossMap[source] || source; + const name = source?.storage?.shuYing_tianshu_originalBossName || source?.name1 || source?.name || ""; + return state.originalBossMap[name] || name; + }; // 读取当前 Boss 在当前难度下的数值配置;单 Boss 配置覆盖默认难度配置。 const getBossDifficultyConfig = player => { const defaultConfig = tianshuConfig.bossDifficulty?.default?.[state.difficulty] || {}; @@ -256,27 +264,61 @@ export default function initTianshu(lib, game, ui, get, ai, _status, shuYing) { // 补充技能只读取当前难度配置,不跨难度继承低难度技能。 const getBossDifficultySkills = player => { const defaultSkills = tianshuConfig.bossDifficulty?.default?.[state.difficulty]?.skills || []; - const bossSkills = tianshuConfig.bossDifficulty?.bosses?.[getBossConfigName(player)]?.[state.difficulty]?.skills || []; + const bossConfig = tianshuConfig.bossDifficulty?.bosses?.[getBossConfigName(player)]?.[state.difficulty] || {}; + const bossSkills = []; + if (Array.isArray(bossConfig.skills)) bossSkills.addArray(bossConfig.skills); const skills = []; for (const skill of defaultSkills.concat(bossSkills)) { if (skill && !skills.includes(skill)) skills.push(skill); } return skills; }; - // Boss 难度数值属于出场面板修正 - const applyBossDifficultyConfig = player => { - const config = getBossDifficultyConfig(player); + // 根据原始武将和当前难度,生成包含准确体力、上限及追加技能的隐藏动态武将。 + const createDifficultyBossCharacter = originalName => { + const original = lib.character[originalName]; + if (!original) return null; + const source = get.convertedCharacter(original); + const data = {}; + for (const [key, value] of Object.entries(source)) { + data[key] = value && typeof value == "object" ? get.copy(value) : value; + } + const config = getBossDifficultyConfig(originalName); const maxHp = getConfigNumber(config.maxHp); const maxHpBonus = getConfigNumber(config.maxHpBonus) || 0; const hp = getConfigNumber(config.hp); const hpBonus = getConfigNumber(config.hpBonus) || 0; - player.maxHp = Math.max(1, maxHp ?? player.maxHp + maxHpBonus); - player.hp = Math.min(Math.max(0, hp ?? player.hp + hpBonus), player.maxHp); - for (const skill of getBossDifficultySkills(player)) { - if (!player.hasSkill(skill)) player.addSkill(skill); + data.maxHp = Math.max(1, maxHp ?? source.maxHp + maxHpBonus); + data.hp = Math.min(Math.max(1, hp ?? source.hp + hpBonus), data.maxHp); + data.skills = source.skills.slice(); + data.skills.addArray(getBossDifficultySkills(originalName)); + data.isHiddenBoss = true; + data.isBossAllowed = true; + data.isAiForbidden = true; + data.tempname = Array.isArray(source.tempname) ? source.tempname.slice() : []; + data.tempname.add(originalName); + return get.convertedCharacter(data); + }; + // 难度选择后一次性注册本局全部动态 Boss,供召唤、变身及武将牌技能读取共同使用。 + const prepareDifficultyBossCharacters = () => { + state.difficultyBossMap = {}; + state.originalBossMap = {}; + const names = tianshuConfig.stages.flatMap(stage => stage?.bosses || []); + for (const originalName of Array.from(new Set(names))) { + const character = createDifficultyBossCharacter(originalName); + if (!character) continue; + const dynamicName = `${originalName}_tianshu_${state.difficulty}`; + state.difficultyBossMap[originalName] = dynamicName; + state.originalBossMap[dynamicName] = originalName; + lib.character[dynamicName] = character; + lib.translate[dynamicName] = lib.translate[originalName] || get.translation(originalName); + if (lib.translate[`${originalName}_ab`]) lib.translate[`${dynamicName}_ab`] = lib.translate[`${originalName}_ab`]; + if (lib.translate[`${originalName}_prefix`]) lib.translate[`${dynamicName}_prefix`] = lib.translate[`${originalName}_prefix`]; + if (lib.characterIntro?.[originalName]) lib.characterIntro[dynamicName] = lib.characterIntro[originalName]; + if (lib.characterTitle?.[originalName]) lib.characterTitle[dynamicName] = lib.characterTitle[originalName]; + lib.config.forbidai?.add(dynamicName); + lib.hiddenCharacters?.add(dynamicName); + lib.skilllist.addArray(character.skills); } - player.update(); - return config; }; // 让开局占位主 Boss 静默死亡,避免作为关卡 Boss 参与后续流程。 const killInitialBossSilently = async () => { @@ -445,7 +487,7 @@ export default function initTianshu(lib, game, ui, get, ai, _status, shuYing) { return result; }; }; - // 召唤指定关卡的 Boss,并完成初始 Boss 静默退场和难度强化。 + // 召唤指定关卡的动态 Boss,并按配置补足仅属于登场流程的初始手牌。 const spawnStage = async (stageIndex = state.stageIndex) => { state.stageIndex = stageIndex; state.clearingStage = false; @@ -456,7 +498,6 @@ export default function initTianshu(lib, game, ui, get, ai, _status, shuYing) { const boss = pool.randomRemove(); if (boss) bosses.push(boss); } - state.currentBosses = bosses.slice(); const seats = getBossSeats(); if (stageIndex == 0) await killInitialBossSilently(); bosses.forEach((boss, index) => { @@ -464,7 +505,7 @@ export default function initTianshu(lib, game, ui, get, ai, _status, shuYing) { }); game.arrangePlayers(); for (const player of game.players.filter(current => isCurrentStageBoss(current))) { - const config = applyBossDifficultyConfig(player); + const config = getBossDifficultyConfig(player); const startCards = getConfigNumber(config.startCards); if (startCards === null) continue; const needCards = startCards - player.countCards("h"); @@ -618,7 +659,7 @@ export default function initTianshu(lib, game, ui, get, ai, _status, shuYing) { if (!isCurrentStageBoss(deadBoss)) return; if (deadBoss.storage.shuYing_tianshu_bossDieHandled) return; deadBoss.storage.shuYing_tianshu_bossDieHandled = true; - const stageCleared = !state.currentBosses.some(name => game.players.some(current => current.name1 == name || current.name == name)); + const stageCleared = !game.players.some(current => isCurrentStageBoss(current)); const finalCleared = stageCleared && state.stageIndex >= tianshuConfig.stages.length - 1; if (!finalCleared && event.source && event.source.isIn()) { if (event.source.hp < event.source.maxHp) await event.source.recover(tianshuConfig.reward.killRecover); @@ -637,6 +678,7 @@ export default function initTianshu(lib, game, ui, get, ai, _status, shuYing) { capturePlayerSide(); state.pendingSpawn = true; await chooseDifficulty(player); + prepareDifficultyBossCharacters(); _status.additionalReward = () => 500; ["shandian", "huoshan", "hongshui", "fulei", "lebu", "bingliang"].forEach(name => lib.inpile.remove(name)); Array.from(ui.cardPile.childNodes).forEach(node => { @@ -662,6 +704,8 @@ export default function initTianshu(lib, game, ui, get, ai, _status, shuYing) { }, handleBossDie, isCurrentStageBoss, + getBossConfigName, + getDifficultyBossName: originalName => state.difficultyBossMap[originalName] || originalName, // 判断角色是否为玩家方成员,默认要求角色仍在场。 isPlayerSideMember: (player, aliveOnly = true) => Boolean(player && (!aliveOnly || player.isIn()) && isCapturedPlayerSide(player) && !isVirtualIdol(player)), // 判断玩家方是否已经全灭。