import "node:fs/promises"; import path, { join, resolve } from "node:path"; import { existsSync, mkdirSync, readdirSync, statSync, writeFileSync } from "node:fs"; import { spawn } from "node:child_process"; import { createInterface } from "node:readline"; import { fileURLToPath } from "node:url"; //#region \0rolldown/runtime.js var __defProp = Object.defineProperty; var __esmMin = (fn, res) => () => (fn && (res = fn(fn = 0)), res); var __exportAll = (all, no_symbols) => { let target = {}; for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" }); return target; }; //#endregion //#region src/engine/data/characters/曹操.ts var 曹操; var init_曹操 = __esmMin((() => { 曹操 = { name: "曹操", maxHealth: 4, gender: "男", faction: "魏", skills: [{ name: "奸雄", path: "../../skills/奸雄" }, { name: "护驾", path: "../../skills/护驾" }] }; })); //#endregion //#region src/engine/data/characters/司马懿.ts var 司马懿; var init_司马懿 = __esmMin((() => { 司马懿 = { name: "司马懿", maxHealth: 3, gender: "男", faction: "魏", skills: [{ name: "反馈", path: "../../skills/反馈" }, { name: "鬼才", path: "../../skills/鬼才" }] }; })); //#endregion //#region src/engine/data/characters/夏侯惇.ts var 夏侯惇; var init_夏侯惇 = __esmMin((() => { 夏侯惇 = { name: "夏侯惇", maxHealth: 4, gender: "男", faction: "魏", skills: [{ name: "刚烈", path: "../../skills/刚烈" }] }; })); //#endregion //#region src/engine/data/characters/张辽.ts var 张辽; var init_张辽 = __esmMin((() => { 张辽 = { name: "张辽", maxHealth: 4, gender: "男", faction: "魏", skills: [{ name: "突袭", path: "../../skills/突袭" }] }; })); //#endregion //#region src/engine/data/characters/许褚.ts var 许褚; var init_许褚 = __esmMin((() => { 许褚 = { name: "许褚", maxHealth: 4, gender: "男", faction: "魏", skills: [{ name: "裸衣", path: "../../skills/裸衣" }] }; })); //#endregion //#region src/engine/data/characters/郭嘉.ts var 郭嘉; var init_郭嘉 = __esmMin((() => { 郭嘉 = { name: "郭嘉", maxHealth: 3, gender: "男", faction: "魏", skills: [{ name: "天妒", path: "../../skills/天妒" }, { name: "遗计", path: "../../skills/遗计" }] }; })); //#endregion //#region src/engine/data/characters/甄姬.ts var 甄姬; var init_甄姬 = __esmMin((() => { 甄姬 = { name: "甄姬", maxHealth: 3, gender: "女", faction: "魏", skills: [{ name: "倾国", path: "../../skills/倾国" }, { name: "洛神", path: "../../skills/洛神" }] }; })); //#endregion //#region src/engine/data/characters/夏侯渊.ts var 夏侯渊; var init_夏侯渊 = __esmMin((() => { 夏侯渊 = { name: "夏侯渊", maxHealth: 4, gender: "男", faction: "魏", skills: [{ name: "神速", path: "../../skills/神速" }] }; })); //#endregion //#region src/engine/data/characters/曹仁.ts var 曹仁; var init_曹仁 = __esmMin((() => { 曹仁 = { name: "曹仁", maxHealth: 4, gender: "男", faction: "魏", skills: [{ name: "据守", path: "../../skills/据守" }] }; })); //#endregion //#region src/engine/data/characters/荀彧.ts var 荀彧; var init_荀彧 = __esmMin((() => { 荀彧 = { name: "荀彧", maxHealth: 3, gender: "男", faction: "魏", skills: [{ name: "驱虎", path: "../../skills/驱虎" }, { name: "节命", path: "../../skills/节命" }] }; })); //#endregion //#region src/engine/data/characters/典韦.ts var 典韦; var init_典韦 = __esmMin((() => { 典韦 = { name: "典韦", maxHealth: 4, gender: "男", faction: "魏", skills: [{ name: "强袭", path: "../../skills/强袭" }] }; })); //#endregion //#region src/engine/data/characters/曹丕.ts var 曹丕; var init_曹丕 = __esmMin((() => { 曹丕 = { name: "曹丕", maxHealth: 3, gender: "男", faction: "魏", skills: [ { name: "行殇", path: "../../skills/行殇" }, { name: "放逐", path: "../../skills/放逐" }, { name: "颂威", path: "../../skills/颂威" } ] }; })); //#endregion //#region src/engine/data/characters/徐晃.ts var 徐晃; var init_徐晃 = __esmMin((() => { 徐晃 = { name: "徐晃", maxHealth: 4, gender: "男", faction: "魏", skills: [{ name: "断粮", path: "../../skills/断粮" }] }; })); //#endregion //#region src/engine/data/characters/张郃.ts var 张郃; var init_张郃 = __esmMin((() => { 张郃 = { name: "张郃", maxHealth: 4, gender: "男", faction: "魏", skills: [{ name: "巧变", path: "../../skills/巧变" }] }; })); //#endregion //#region src/engine/data/characters/邓艾.ts var 邓艾; var init_邓艾 = __esmMin((() => { 邓艾 = { name: "邓艾", maxHealth: 4, gender: "男", faction: "魏", skills: [{ name: "屯田", path: "../../skills/屯田" }, { name: "凿险", path: "../../skills/凿险" }] }; })); //#endregion //#region src/engine/data/characters/界曹操.ts var 界曹操; var init_界曹操 = __esmMin((() => { 界曹操 = { name: "界曹操", maxHealth: 4, gender: "男", faction: "魏", skills: [{ name: "界奸雄", path: "../../skills/界奸雄" }, { name: "界护驾", path: "../../skills/界护驾" }] }; })); //#endregion //#region src/engine/data/characters/界司马懿.ts var 界司马懿; var init_界司马懿 = __esmMin((() => { 界司马懿 = { name: "界司马懿", maxHealth: 3, gender: "男", faction: "魏", skills: [{ name: "反馈", path: "../../skills/反馈" }, { name: "界鬼才", path: "../../skills/界鬼才" }] }; })); //#endregion //#region src/engine/data/characters/界夏侯惇.ts var 界夏侯惇; var init_界夏侯惇 = __esmMin((() => { 界夏侯惇 = { name: "界夏侯惇", maxHealth: 4, gender: "男", faction: "魏", skills: [{ name: "界刚烈", path: "../../skills/界刚烈" }] }; })); //#endregion //#region src/engine/data/characters/界张辽.ts var 界张辽; var init_界张辽 = __esmMin((() => { 界张辽 = { name: "界张辽", maxHealth: 4, gender: "男", faction: "魏", skills: [{ name: "界突袭", path: "../../skills/界突袭" }] }; })); //#endregion //#region src/engine/data/characters/界许褚.ts var 界许褚; var init_界许褚 = __esmMin((() => { 界许褚 = { name: "界许褚", maxHealth: 4, gender: "男", faction: "魏", skills: [{ name: "界裸衣", path: "../../skills/界裸衣" }] }; })); //#endregion //#region src/engine/data/characters/界郭嘉.ts var 界郭嘉; var init_界郭嘉 = __esmMin((() => { 界郭嘉 = { name: "界郭嘉", maxHealth: 3, gender: "男", faction: "魏", skills: [{ name: "天妒", path: "../../skills/天妒" }, { name: "界遗计", path: "../../skills/界遗计" }] }; })); //#endregion //#region src/engine/data/characters/界甄姬.ts var 界甄姬; var init_界甄姬 = __esmMin((() => { 界甄姬 = { name: "界甄姬", maxHealth: 3, gender: "女", faction: "魏", skills: [{ name: "倾国", path: "../../skills/倾国" }, { name: "界洛神", path: "../../skills/界洛神" }] }; })); //#endregion //#region src/engine/data/characters/界夏侯渊.ts var 界夏侯渊; var init_界夏侯渊 = __esmMin((() => { 界夏侯渊 = { name: "界夏侯渊", maxHealth: 4, gender: "男", faction: "魏", skills: [{ name: "界神速", path: "../../skills/界神速" }] }; })); //#endregion //#region src/engine/data/characters/界曹仁.ts var 界曹仁; var init_界曹仁 = __esmMin((() => { 界曹仁 = { name: "界曹仁", maxHealth: 4, gender: "男", faction: "魏", skills: [{ name: "界据守", path: "../../skills/界据守" }, { name: "解围", path: "../../skills/解围" }] }; })); //#endregion //#region src/engine/data/characters/界荀彧.ts var 界荀彧; var init_界荀彧 = __esmMin((() => { 界荀彧 = { name: "界荀彧", maxHealth: 3, gender: "男", faction: "魏", skills: [{ name: "驱虎", path: "../../skills/驱虎" }, { name: "界节命", path: "../../skills/界节命" }] }; })); //#endregion //#region src/engine/data/characters/界典韦.ts var 界典韦; var init_界典韦 = __esmMin((() => { 界典韦 = { name: "界典韦", maxHealth: 4, gender: "男", faction: "魏", skills: [{ name: "界强袭", path: "../../skills/界强袭" }] }; })); //#endregion //#region src/engine/data/characters/界曹丕.ts var 界曹丕; var init_界曹丕 = __esmMin((() => { 界曹丕 = { name: "界曹丕", maxHealth: 3, gender: "男", faction: "魏", skills: [ { name: "行殇", path: "../../skills/行殇" }, { name: "界放逐", path: "../../skills/界放逐" }, { name: "颂威", path: "../../skills/颂威" } ] }; })); //#endregion //#region src/engine/data/characters/界徐晃.ts var 界徐晃; var init_界徐晃 = __esmMin((() => { 界徐晃 = { name: "界徐晃", maxHealth: 4, gender: "男", faction: "魏", skills: [{ name: "界断粮", path: "../../skills/界断粮" }, { name: "截辎", path: "../../skills/截辎" }] }; })); //#endregion //#region src/engine/data/characters/界张郃.ts var 界张郃; var init_界张郃 = __esmMin((() => { 界张郃 = { name: "界张郃", maxHealth: 4, gender: "男", faction: "魏", skills: [{ name: "界巧变", path: "../../skills/界巧变" }] }; })); //#endregion //#region src/engine/data/characters/界邓艾.ts var 界邓艾; var init_界邓艾 = __esmMin((() => { 界邓艾 = { name: "界邓艾", maxHealth: 4, gender: "男", faction: "魏", skills: [ { name: "界屯田", path: "../../skills/界屯田" }, { name: "界凿险", path: "../../skills/界凿险" }, { name: "急袭", path: "../../skills/急袭" } ] }; })); //#endregion //#region src/engine/data/characters/界于禁.ts var 界于禁; var init_界于禁 = __esmMin((() => { 界于禁 = { name: "界于禁", maxHealth: 4, gender: "男", faction: "魏", skills: [{ name: "界镇军", path: "../../skills/界镇军" }, { name: "界毅重", path: "../../skills/界毅重" }] }; })); //#endregion //#region src/engine/data/characters/界张春华.ts var 界张春华; var init_界张春华 = __esmMin((() => { 界张春华 = { name: "界张春华", maxHealth: 3, gender: "女", faction: "魏", skills: [ { name: "界绝情", path: "../../skills/界绝情" }, { name: "界伤逝", path: "../../skills/界伤逝" }, { name: "界翦灭", path: "../../skills/界翦灭" } ] }; })); //#endregion //#region src/engine/data/characters/界曹冲.ts var 界曹冲; var init_界曹冲 = __esmMin((() => { 界曹冲 = { name: "界曹冲", maxHealth: 3, gender: "男", faction: "魏", skills: [{ name: "界称象", path: "../../skills/界称象" }, { name: "界仁心", path: "../../skills/界仁心" }] }; })); //#endregion //#region src/engine/data/characters/界曹彰.ts var 界曹彰; var init_界曹彰 = __esmMin((() => { 界曹彰 = { name: "界曹彰", maxHealth: 4, gender: "男", faction: "魏", skills: [{ name: "界将驰", path: "../../skills/界将驰" }] }; })); //#endregion //#region src/engine/data/characters/界曹植.ts var 界曹植; var init_界曹植 = __esmMin((() => { 界曹植 = { name: "界曹植", maxHealth: 3, gender: "男", faction: "魏", skills: [{ name: "界落英", path: "../../skills/界落英" }, { name: "界酒诗", path: "../../skills/界酒诗" }] }; })); //#endregion //#region src/engine/data/characters/界李典.ts var 界李典; var init_界李典 = __esmMin((() => { 界李典 = { name: "界李典", maxHealth: 3, gender: "男", faction: "魏", skills: [{ name: "界恂恂", path: "../../skills/界恂恂" }, { name: "界忘隙", path: "../../skills/界忘隙" }] }; })); //#endregion //#region src/engine/data/characters/界王异.ts var 界王异; var init_界王异 = __esmMin((() => { 界王异 = { name: "界王异", maxHealth: 3, gender: "女", faction: "魏", skills: [{ name: "界贞烈", path: "../../skills/界贞烈" }, { name: "界秘计", path: "../../skills/界秘计" }] }; })); //#endregion //#region src/engine/data/characters/界郭淮.ts var 界郭淮; var init_界郭淮 = __esmMin((() => { 界郭淮 = { name: "界郭淮", maxHealth: 4, gender: "男", faction: "魏", skills: [{ name: "界精策", path: "../../skills/界精策" }] }; })); //#endregion //#region src/engine/data/characters/界郭皇后.ts var 界郭皇后; var init_界郭皇后 = __esmMin((() => { 界郭皇后 = { name: "界郭皇后", maxHealth: 3, gender: "女", faction: "魏", skills: [{ name: "界矫诏", path: "../../skills/界矫诏" }, { name: "界殚心", path: "../../skills/界殚心" }] }; })); //#endregion //#region src/engine/data/characters/刘备.ts var 刘备; var init_刘备 = __esmMin((() => { 刘备 = { name: "刘备", maxHealth: 4, gender: "男", faction: "蜀", skills: [{ name: "仁德", path: "../../skills/仁德" }, { name: "激将", path: "../../skills/激将" }] }; })); //#endregion //#region src/engine/data/characters/界刘备.ts var 界刘备; var init_界刘备 = __esmMin((() => { 界刘备 = { name: "界刘备", maxHealth: 4, gender: "男", faction: "蜀", skills: [{ name: "界仁德", path: "../../skills/界仁德" }, { name: "界激将", path: "../../skills/界激将" }] }; })); //#endregion //#region src/engine/data/characters/关羽.ts var 关羽; var init_关羽 = __esmMin((() => { 关羽 = { name: "关羽", maxHealth: 4, gender: "男", faction: "蜀", skills: [{ name: "武圣", path: "../../skills/武圣" }] }; })); //#endregion //#region src/engine/data/characters/界关羽.ts var 界关羽; var init_界关羽 = __esmMin((() => { 界关羽 = { name: "界关羽", maxHealth: 4, gender: "男", faction: "蜀", skills: [{ name: "界武圣", path: "../../skills/界武圣" }, { name: "义绝", path: "../../skills/义绝" }] }; })); //#endregion //#region src/engine/data/characters/张飞.ts var 张飞; var init_张飞 = __esmMin((() => { 张飞 = { name: "张飞", maxHealth: 4, gender: "男", faction: "蜀", skills: [{ name: "咆哮", path: "../../skills/咆哮" }] }; })); //#endregion //#region src/engine/data/characters/界张飞.ts var 界张飞; var init_界张飞 = __esmMin((() => { 界张飞 = { name: "界张飞", maxHealth: 4, gender: "男", faction: "蜀", skills: [{ name: "界咆哮", path: "../../skills/界咆哮" }] }; })); //#endregion //#region src/engine/data/characters/赵云.ts var 赵云; var init_赵云 = __esmMin((() => { 赵云 = { name: "赵云", maxHealth: 4, gender: "男", faction: "蜀", skills: [{ name: "龙胆", path: "../../skills/龙胆" }] }; })); //#endregion //#region src/engine/data/characters/界赵云.ts var 界赵云; var init_界赵云 = __esmMin((() => { 界赵云 = { name: "界赵云", maxHealth: 4, gender: "男", faction: "蜀", skills: [{ name: "界龙胆", path: "../../skills/界龙胆" }, { name: "涯角", path: "../../skills/涯角" }] }; })); //#endregion //#region src/engine/data/characters/诸葛亮.ts var 诸葛亮; var init_诸葛亮 = __esmMin((() => { 诸葛亮 = { name: "诸葛亮", maxHealth: 3, gender: "男", faction: "蜀", skills: [{ name: "观星", path: "../../skills/观星" }, { name: "空城", path: "../../skills/空城" }] }; })); //#endregion //#region src/engine/data/characters/界诸葛亮.ts var 界诸葛亮; var init_界诸葛亮 = __esmMin((() => { 界诸葛亮 = { name: "界诸葛亮", maxHealth: 3, gender: "男", faction: "蜀", skills: [{ name: "界观星", path: "../../skills/界观星" }, { name: "界空城", path: "../../skills/界空城" }] }; })); //#endregion //#region src/engine/data/characters/黄月英.ts var 黄月英; var init_黄月英 = __esmMin((() => { 黄月英 = { name: "黄月英", maxHealth: 3, gender: "女", faction: "蜀", skills: [{ name: "集智", path: "../../skills/集智" }, { name: "奇才", path: "../../skills/奇才" }] }; })); //#endregion //#region src/engine/data/characters/界黄月英.ts var 界黄月英; var init_界黄月英 = __esmMin((() => { 界黄月英 = { name: "界黄月英", maxHealth: 3, gender: "女", faction: "蜀", skills: [{ name: "界集智", path: "../../skills/界集智" }, { name: "界奇才", path: "../../skills/界奇才" }] }; })); //#endregion //#region src/engine/data/characters/马超.ts var 马超; var init_马超 = __esmMin((() => { 马超 = { name: "马超", maxHealth: 4, gender: "男", faction: "蜀", skills: [{ name: "马术", path: "../../skills/马术" }, { name: "铁骑", path: "../../skills/铁骑" }] }; })); //#endregion //#region src/engine/data/characters/界马超.ts var 界马超; var init_界马超 = __esmMin((() => { 界马超 = { name: "界马超", maxHealth: 4, gender: "男", faction: "蜀", skills: [{ name: "马术", path: "../../skills/马术" }, { name: "界铁骑", path: "../../skills/界铁骑" }] }; })); //#endregion //#region src/engine/data/characters/界马岱.ts var 界马岱; var init_界马岱 = __esmMin((() => { 界马岱 = { name: "界马岱", maxHealth: 4, gender: "男", faction: "蜀", skills: [{ name: "马术", path: "../../skills/马术" }, { name: "界潜袭", path: "../../skills/界潜袭" }] }; })); //#endregion //#region src/engine/data/characters/黄忠.ts var 黄忠; var init_黄忠 = __esmMin((() => { 黄忠 = { name: "黄忠", maxHealth: 4, gender: "男", faction: "蜀", skills: [{ name: "烈弓", path: "../../skills/烈弓" }] }; })); //#endregion //#region src/engine/data/characters/界黄忠.ts var 界黄忠; var init_界黄忠 = __esmMin((() => { 界黄忠 = { name: "界黄忠", maxHealth: 4, gender: "男", faction: "蜀", skills: [{ name: "界烈弓", path: "../../skills/界烈弓" }] }; })); //#endregion //#region src/engine/data/characters/魏延.ts var 魏延; var init_魏延 = __esmMin((() => { 魏延 = { name: "魏延", maxHealth: 4, gender: "男", faction: "蜀", skills: [{ name: "狂骨", path: "../../skills/狂骨" }] }; })); //#endregion //#region src/engine/data/characters/界魏延.ts var 界魏延; var init_界魏延 = __esmMin((() => { 界魏延 = { name: "界魏延", maxHealth: 4, gender: "男", faction: "蜀", skills: [{ name: "界狂骨", path: "../../skills/界狂骨" }, { name: "奇谋" }] }; })); //#endregion //#region src/engine/data/characters/卧龙诸葛.ts var 卧龙诸葛; var init_卧龙诸葛 = __esmMin((() => { 卧龙诸葛 = { name: "卧龙诸葛", maxHealth: 3, gender: "男", faction: "蜀", skills: [ { name: "八阵", path: "../../skills/八阵" }, { name: "火计", path: "../../skills/火计" }, { name: "看破", path: "../../skills/看破" } ] }; })); //#endregion //#region src/engine/data/characters/界卧龙诸葛.ts var 界卧龙诸葛; var init_界卧龙诸葛 = __esmMin((() => { 界卧龙诸葛 = { name: "界卧龙诸葛", maxHealth: 3, gender: "男", faction: "蜀", skills: [ { name: "八阵", path: "../../skills/八阵" }, { name: "界火计", path: "../../skills/界火计" }, { name: "界看破", path: "../../skills/界看破" } ] }; })); //#endregion //#region src/engine/data/characters/庞统.ts var 庞统; var init_庞统 = __esmMin((() => { 庞统 = { name: "庞统", maxHealth: 3, gender: "男", faction: "蜀", skills: [{ name: "连环", path: "../../skills/连环" }, { name: "涅槃", path: "../../skills/涅槃" }] }; })); //#endregion //#region src/engine/data/characters/界庞统.ts var 界庞统; var init_界庞统 = __esmMin((() => { 界庞统 = { name: "界庞统", maxHealth: 3, gender: "男", faction: "蜀", skills: [{ name: "界连环", path: "../../skills/界连环" }, { name: "界涅槃", path: "../../skills/界涅槃" }] }; })); //#endregion //#region src/engine/data/characters/孟获.ts var 孟获; var init_孟获 = __esmMin((() => { 孟获 = { name: "孟获", maxHealth: 4, gender: "男", faction: "蜀", skills: [{ name: "祸首", path: "../../skills/祸首" }, { name: "再起", path: "../../skills/再起" }] }; })); //#endregion //#region src/engine/data/characters/界孟获.ts var 界孟获; var init_界孟获 = __esmMin((() => { 界孟获 = { name: "界孟获", maxHealth: 4, gender: "男", faction: "蜀", skills: [{ name: "祸首", path: "../../skills/祸首" }, { name: "界再起", path: "../../skills/界再起" }] }; })); //#endregion //#region src/engine/data/characters/祝融.ts var 祝融; var init_祝融 = __esmMin((() => { 祝融 = { name: "祝融", maxHealth: 4, gender: "女", faction: "蜀", skills: [{ name: "巨象", path: "../../skills/巨象" }, { name: "烈刃", path: "../../skills/烈刃" }] }; })); //#endregion //#region src/engine/data/characters/界祝融.ts var 界祝融; var init_界祝融 = __esmMin((() => { 界祝融 = { name: "界祝融", maxHealth: 4, gender: "女", faction: "蜀", skills: [{ name: "巨象", path: "../../skills/巨象" }, { name: "烈刃", path: "../../skills/烈刃" }] }; })); //#endregion //#region src/engine/data/characters/姜维.ts var 姜维; var init_姜维 = __esmMin((() => { 姜维 = { name: "姜维", maxHealth: 4, gender: "男", faction: "蜀", skills: [{ name: "挑衅", path: "../../skills/挑衅" }, { name: "志继", path: "../../skills/志继" }] }; })); //#endregion //#region src/engine/data/characters/界姜维.ts var 界姜维; var init_界姜维 = __esmMin((() => { 界姜维 = { name: "界姜维", maxHealth: 4, gender: "男", faction: "蜀", skills: [{ name: "界挑衅", path: "../../skills/界挑衅" }, { name: "界志继", path: "../../skills/界志继" }] }; })); //#endregion //#region src/engine/data/characters/刘禅.ts var 刘禅; var init_刘禅 = __esmMin((() => { 刘禅 = { name: "刘禅", maxHealth: 3, gender: "男", faction: "蜀", skills: [ { name: "享乐", path: "../../skills/享乐" }, { name: "放权", path: "../../skills/放权" }, { name: "若愚", path: "../../skills/若愚" } ] }; })); //#endregion //#region src/engine/data/characters/界刘禅.ts var 界刘禅; var init_界刘禅 = __esmMin((() => { 界刘禅 = { name: "界刘禅", maxHealth: 3, gender: "男", faction: "蜀", skills: [ { name: "享乐", path: "../../skills/享乐" }, { name: "界放权", path: "../../skills/界放权" }, { name: "界若愚", path: "../../skills/界若愚" } ] }; })); //#endregion //#region src/engine/data/characters/界关兴张苞.ts var 界关兴张苞; var init_界关兴张苞 = __esmMin((() => { 界关兴张苞 = { name: "界关兴张苞", maxHealth: 4, gender: "男", faction: "蜀", skills: [{ name: "界父魂", path: "../../skills/界父魂" }] }; })); //#endregion //#region src/engine/data/characters/界夏侯氏.ts var 界夏侯氏; var init_界夏侯氏 = __esmMin((() => { 界夏侯氏 = { name: "界夏侯氏", maxHealth: 3, gender: "女", faction: "蜀", skills: [{ name: "界樵拾", path: "../../skills/界樵拾" }, { name: "界燕语", path: "../../skills/界燕语" }] }; })); //#endregion //#region src/engine/data/characters/界廖化.ts var 界廖化; var init_界廖化 = __esmMin((() => { 界廖化 = { name: "界廖化", maxHealth: 4, gender: "男", faction: "蜀", skills: [{ name: "界当先", path: "../../skills/界当先" }, { name: "界伏枥", path: "../../skills/界伏枥" }] }; })); //#endregion //#region src/engine/data/characters/界张松.ts var 界张松; var init_界张松 = __esmMin((() => { 界张松 = { name: "界张松", maxHealth: 3, gender: "男", faction: "蜀", skills: [{ name: "界强识", path: "../../skills/界强识" }, { name: "界献图", path: "../../skills/界献图" }] }; })); //#endregion //#region src/engine/data/characters/界徐庶.ts var 界徐庶; var init_界徐庶 = __esmMin((() => { 界徐庶 = { name: "界徐庶", maxHealth: 4, gender: "男", faction: "蜀", skills: [{ name: "界诛害", path: "../../skills/界诛害" }, { name: "界潜心", path: "../../skills/界潜心" }] }; })); //#endregion //#region src/engine/data/characters/界法正.ts var 界法正; var init_界法正 = __esmMin((() => { 界法正 = { name: "界法正", maxHealth: 3, gender: "男", faction: "蜀", skills: [{ name: "界眩惑", path: "../../skills/界眩惑" }, { name: "界恩怨", path: "../../skills/界恩怨" }] }; })); //#endregion //#region src/engine/data/characters/界简雍.ts var 界简雍; var init_界简雍 = __esmMin((() => { 界简雍 = { name: "界简雍", maxHealth: 3, gender: "男", faction: "蜀", skills: [{ name: "界纵适", path: "../../skills/界纵适" }, { name: "界巧说", path: "../../skills/界巧说" }] }; })); //#endregion //#region src/engine/data/characters/孙权.ts var 孙权; var init_孙权 = __esmMin((() => { 孙权 = { name: "孙权", maxHealth: 4, gender: "男", faction: "吴", skills: [{ name: "制衡", path: "../../skills/制衡" }, { name: "救援", path: "../../skills/救援" }] }; })); //#endregion //#region src/engine/data/characters/界孙权.ts var 界孙权; var init_界孙权 = __esmMin((() => { 界孙权 = { name: "界孙权", maxHealth: 4, gender: "男", faction: "吴", skills: [{ name: "界制衡", path: "../../skills/界制衡" }, { name: "界救援", path: "../../skills/界救援" }] }; })); //#endregion //#region src/engine/data/characters/甘宁.ts var 甘宁; var init_甘宁 = __esmMin((() => { 甘宁 = { name: "甘宁", maxHealth: 4, gender: "男", faction: "吴", skills: [{ name: "奇袭", path: "../../skills/奇袭" }] }; })); //#endregion //#region src/engine/data/characters/界甘宁.ts var 界甘宁; var init_界甘宁 = __esmMin((() => { 界甘宁 = { name: "界甘宁", maxHealth: 4, gender: "男", faction: "吴", skills: [{ name: "奇袭", path: "../../skills/奇袭" }, { name: "奋威", path: "../../skills/奋威" }] }; })); //#endregion //#region src/engine/data/characters/吕蒙.ts var 吕蒙; var init_吕蒙 = __esmMin((() => { 吕蒙 = { name: "吕蒙", maxHealth: 4, gender: "男", faction: "吴", skills: [{ name: "克己", path: "../../skills/克己" }] }; })); //#endregion //#region src/engine/data/characters/界吕蒙.ts var 界吕蒙; var init_界吕蒙 = __esmMin((() => { 界吕蒙 = { name: "界吕蒙", maxHealth: 4, gender: "男", faction: "吴", skills: [ { name: "界克己", path: "../../skills/界克己" }, { name: "勤学", path: "../../skills/勤学" }, { name: "博图", path: "../../skills/博图" } ] }; })); //#endregion //#region src/engine/data/characters/黄盖.ts var 黄盖; var init_黄盖 = __esmMin((() => { 黄盖 = { name: "黄盖", maxHealth: 4, gender: "男", faction: "吴", skills: [{ name: "苦肉", path: "../../skills/苦肉" }] }; })); //#endregion //#region src/engine/data/characters/界黄盖.ts var 界黄盖; var init_界黄盖 = __esmMin((() => { 界黄盖 = { name: "界黄盖", maxHealth: 4, gender: "男", faction: "吴", skills: [{ name: "界苦肉", path: "../../skills/界苦肉" }, { name: "诈降", path: "../../skills/诈降" }] }; })); //#endregion //#region src/engine/data/characters/周瑜.ts var 周瑜; var init_周瑜 = __esmMin((() => { 周瑜 = { name: "周瑜", maxHealth: 3, gender: "男", faction: "吴", skills: [{ name: "反间", path: "../../skills/反间" }, { name: "英姿", path: "../../skills/英姿" }] }; })); //#endregion //#region src/engine/data/characters/界周瑜.ts var 界周瑜; var init_界周瑜 = __esmMin((() => { 界周瑜 = { name: "界周瑜", maxHealth: 3, gender: "男", faction: "吴", skills: [{ name: "界英姿", path: "../../skills/界英姿" }, { name: "界反间", path: "../../skills/界反间" }] }; })); //#endregion //#region src/engine/data/characters/大乔.ts var 大乔; var init_大乔 = __esmMin((() => { 大乔 = { name: "大乔", maxHealth: 3, gender: "女", faction: "吴", skills: [{ name: "国色", path: "../../skills/国色" }, { name: "流离", path: "../../skills/流离" }] }; })); //#endregion //#region src/engine/data/characters/界大乔.ts var 界大乔; var init_界大乔 = __esmMin((() => { 界大乔 = { name: "界大乔", maxHealth: 3, gender: "女", faction: "吴", skills: [{ name: "界国色", path: "../../skills/界国色" }, { name: "流离", path: "../../skills/流离" }] }; })); //#endregion //#region src/engine/data/characters/陆逊.ts var 陆逊; var init_陆逊 = __esmMin((() => { 陆逊 = { name: "陆逊", maxHealth: 3, gender: "男", faction: "吴", skills: [{ name: "谦逊", path: "../../skills/谦逊" }, { name: "连营", path: "../../skills/连营" }] }; })); //#endregion //#region src/engine/data/characters/界陆逊.ts var 界陆逊; var init_界陆逊 = __esmMin((() => { 界陆逊 = { name: "界陆逊", maxHealth: 3, gender: "男", faction: "吴", skills: [{ name: "界谦逊", path: "../../skills/界谦逊" }, { name: "界连营", path: "../../skills/界连营" }] }; })); //#endregion //#region src/engine/data/characters/孙尚香.ts var 孙尚香; var init_孙尚香 = __esmMin((() => { 孙尚香 = { name: "孙尚香", maxHealth: 3, gender: "女", faction: "吴", skills: [{ name: "结姻", path: "../../skills/结姻" }, { name: "枭姬", path: "../../skills/枭姬" }] }; })); //#endregion //#region src/engine/data/characters/界孙尚香.ts var 界孙尚香; var init_界孙尚香 = __esmMin((() => { 界孙尚香 = { name: "界孙尚香", maxHealth: 3, gender: "女", faction: "吴", skills: [{ name: "界结姻", path: "../../skills/界结姻" }, { name: "枭姬", path: "../../skills/枭姬" }] }; })); //#endregion //#region src/engine/data/characters/小乔.ts var 小乔; var init_小乔 = __esmMin((() => { 小乔 = { name: "小乔", maxHealth: 3, gender: "女", faction: "吴", skills: [{ name: "天香", path: "../../skills/天香" }, { name: "红颜", path: "../../skills/红颜" }] }; })); //#endregion //#region src/engine/data/characters/界小乔.ts var 界小乔; var init_界小乔 = __esmMin((() => { 界小乔 = { name: "界小乔", maxHealth: 3, gender: "女", faction: "吴", skills: [ { name: "界红颜", path: "../../skills/界红颜" }, { name: "界天香", path: "../../skills/界天香" }, { name: "飘零", path: "../../skills/飘零" } ] }; })); //#endregion //#region src/engine/data/characters/周泰.ts var 周泰; var init_周泰 = __esmMin((() => { 周泰 = { name: "周泰", maxHealth: 4, gender: "男", faction: "吴", skills: [{ name: "不屈", path: "../../skills/不屈" }] }; })); //#endregion //#region src/engine/data/characters/界周泰.ts var 界周泰; var init_界周泰 = __esmMin((() => { 界周泰 = { name: "界周泰", maxHealth: 4, gender: "男", faction: "吴", skills: [{ name: "界不屈" }, { name: "奋激" }] }; })); //#endregion //#region src/engine/data/characters/太史慈.ts var 太史慈; var init_太史慈 = __esmMin((() => { 太史慈 = { name: "太史慈", maxHealth: 4, gender: "男", faction: "吴", skills: [{ name: "天义", path: "../../skills/天义" }] }; })); //#endregion //#region src/engine/data/characters/界太史慈.ts var 界太史慈; var init_界太史慈 = __esmMin((() => { 界太史慈 = { name: "界太史慈", maxHealth: 4, gender: "男", faction: "吴", skills: [{ name: "天义", path: "../../skills/天义" }, { name: "酣战", path: "../../skills/酣战" }] }; })); //#endregion //#region src/engine/data/characters/鲁肃.ts var 鲁肃; var init_鲁肃 = __esmMin((() => { 鲁肃 = { name: "鲁肃", maxHealth: 3, gender: "男", faction: "吴", skills: [{ name: "好施", path: "../../skills/好施" }, { name: "缔盟", path: "../../skills/缔盟" }] }; })); //#endregion //#region src/engine/data/characters/界鲁肃.ts var 界鲁肃; var init_界鲁肃 = __esmMin((() => { 界鲁肃 = { name: "界鲁肃", maxHealth: 3, gender: "男", faction: "吴", skills: [{ name: "界好施", path: "../../skills/界好施" }, { name: "界缔盟", path: "../../skills/界缔盟" }] }; })); //#endregion //#region src/engine/data/characters/孙坚.ts var 孙坚; var init_孙坚 = __esmMin((() => { 孙坚 = { name: "孙坚", maxHealth: 4, gender: "男", faction: "吴", skills: [{ name: "英魂", path: "../../skills/英魂" }] }; })); //#endregion //#region src/engine/data/characters/界孙坚.ts var 界孙坚; var init_界孙坚 = __esmMin((() => { 界孙坚 = { name: "界孙坚", maxHealth: 4, gender: "男", faction: "吴", skills: [{ name: "英魂" }, { name: "武烈" }] }; })); //#endregion //#region src/engine/data/characters/孙策.ts var 孙策; var init_孙策 = __esmMin((() => { 孙策 = { name: "孙策", maxHealth: 4, gender: "男", faction: "吴", skills: [ { name: "激昂", path: "../../skills/激昂" }, { name: "魂姿", path: "../../skills/魂姿" }, { name: "制霸", path: "../../skills/制霸" } ] }; })); //#endregion //#region src/engine/data/characters/界孙策.ts var 界孙策; var init_界孙策 = __esmMin((() => { 界孙策 = { name: "界孙策", maxHealth: 4, gender: "男", faction: "吴", skills: [ { name: "界激昂", path: "../../skills/界激昂" }, { name: "界魂姿", path: "../../skills/界魂姿" }, { name: "界制霸", path: "../../skills/界制霸" } ] }; })); //#endregion //#region src/engine/data/characters/张昭张纮.ts var 张昭张纮; var init_张昭张纮 = __esmMin((() => { 张昭张纮 = { name: "张昭张纮", maxHealth: 3, gender: "男", faction: "吴", skills: [{ name: "直谏", path: "../../skills/直谏" }, { name: "固政", path: "../../skills/固政" }] }; })); //#endregion //#region src/engine/data/characters/界张昭张纮.ts var 界张昭张纮; var init_界张昭张纮 = __esmMin((() => { 界张昭张纮 = { name: "界张昭张纮", maxHealth: 3, gender: "男", faction: "吴", skills: [{ name: "界直谏", path: "../../skills/界直谏" }, { name: "界固政", path: "../../skills/界固政" }] }; })); //#endregion //#region src/engine/data/characters/界凌统.ts var 界凌统; var init_界凌统 = __esmMin((() => { 界凌统 = { name: "界凌统", maxHealth: 4, gender: "男", faction: "吴", skills: [{ name: "界旋风", path: "../../skills/界旋风" }] }; })); //#endregion //#region src/engine/data/characters/界吴国太.ts var 界吴国太; var init_界吴国太 = __esmMin((() => { 界吴国太 = { name: "界吴国太", maxHealth: 3, gender: "女", faction: "吴", skills: [{ name: "界甘露", path: "../../skills/界甘露" }, { name: "界补益", path: "../../skills/界补益" }] }; })); //#endregion //#region src/engine/data/characters/界徐盛.ts var 界徐盛; var init_界徐盛 = __esmMin((() => { 界徐盛 = { name: "界徐盛", maxHealth: 4, gender: "男", faction: "吴", skills: [{ name: "界破军", path: "../../skills/界破军" }] }; })); //#endregion //#region src/engine/data/characters/界程普.ts var 界程普; var init_界程普 = __esmMin((() => { 界程普 = { name: "界程普", maxHealth: 4, gender: "男", faction: "吴", skills: [{ name: "界疠火", path: "../../skills/界疠火" }, { name: "界醇醪", path: "../../skills/界醇醪" }] }; })); //#endregion //#region src/engine/data/characters/界虞翻.ts var 界虞翻; var init_界虞翻 = __esmMin((() => { 界虞翻 = { name: "界虞翻", maxHealth: 3, gender: "男", faction: "吴", skills: [{ name: "界纵玄", path: "../../skills/界纵玄" }, { name: "界直言", path: "../../skills/界直言" }] }; })); //#endregion //#region src/engine/data/characters/界韩当.ts var 界韩当; var init_界韩当 = __esmMin((() => { 界韩当 = { name: "界韩当", maxHealth: 4, gender: "男", faction: "吴", skills: [{ name: "界弓骑", path: "../../skills/界弓骑" }, { name: "界解烦", path: "../../skills/界解烦" }] }; })); //#endregion //#region src/engine/data/characters/华佗.ts var 华佗; var init_华佗 = __esmMin((() => { 华佗 = { name: "华佗", maxHealth: 3, gender: "男", faction: "群", skills: [{ name: "急救", path: "../../skills/急救" }, { name: "青囊", path: "../../skills/青囊" }] }; })); //#endregion //#region src/engine/data/characters/吕布.ts var 吕布; var init_吕布 = __esmMin((() => { 吕布 = { name: "吕布", maxHealth: 4, gender: "男", faction: "群", skills: [{ name: "无双", path: "../../skills/无双" }] }; })); //#endregion //#region src/engine/data/characters/貂蝉.ts var 貂蝉; var init_貂蝉 = __esmMin((() => { 貂蝉 = { name: "貂蝉", maxHealth: 3, gender: "女", faction: "群", skills: [{ name: "离间", path: "../../skills/离间" }, { name: "闭月", path: "../../skills/闭月" }] }; })); //#endregion //#region src/engine/data/characters/界貂蝉.ts var 界貂蝉; var init_界貂蝉 = __esmMin((() => { 界貂蝉 = { name: "界貂蝉", maxHealth: 3, gender: "女", faction: "群", skills: [{ name: "离间", path: "../../skills/离间" }, { name: "界闭月", path: "../../skills/界闭月" }] }; })); //#endregion //#region src/engine/data/characters/张角.ts var 张角; var init_张角 = __esmMin((() => { 张角 = { name: "张角", maxHealth: 3, gender: "男", faction: "群", skills: [ { name: "雷击", path: "../../skills/雷击" }, { name: "鬼道", path: "../../skills/鬼道" }, { name: "黄天", path: "../../skills/黄天" } ] }; })); //#endregion //#region src/engine/data/characters/于吉.ts var 于吉; var init_于吉 = __esmMin((() => { 于吉 = { name: "于吉", maxHealth: 3, gender: "男", faction: "群", skills: [{ name: "蛊惑", path: "../../skills/蛊惑" }] }; })); //#endregion //#region src/engine/data/characters/界于吉.ts var 界于吉; var init_界于吉 = __esmMin((() => { 界于吉 = { name: "界于吉", maxHealth: 3, gender: "男", faction: "群", skills: [{ name: "界蛊惑", path: "../../skills/界蛊惑" }] }; })); //#endregion //#region src/engine/data/characters/袁绍.ts var 袁绍; var init_袁绍 = __esmMin((() => { 袁绍 = { name: "袁绍", maxHealth: 4, gender: "男", faction: "群", skills: [{ name: "乱击", path: "../../skills/乱击" }] }; })); //#endregion //#region src/engine/data/characters/界袁绍.ts var 界袁绍; var init_界袁绍 = __esmMin((() => { 界袁绍 = { name: "界袁绍", maxHealth: 4, gender: "男", faction: "群", isLord: true, skills: [{ name: "界乱击", path: "../../skills/界乱击" }, { name: "界血裔", path: "../../skills/界血裔" }] }; })); //#endregion //#region src/engine/data/characters/庞德.ts var 庞德; var init_庞德 = __esmMin((() => { 庞德 = { name: "庞德", maxHealth: 4, gender: "男", faction: "群", skills: [{ name: "马术", path: "../../skills/马术" }, { name: "鞬出", path: "../../skills/鞬出" }] }; })); //#endregion //#region src/engine/data/characters/界庞德.ts var 界庞德; var init_界庞德 = __esmMin((() => { 界庞德 = { name: "界庞德", maxHealth: 4, gender: "男", faction: "群", skills: [{ name: "马术", path: "../../skills/马术" }, { name: "界鞬出", path: "../../skills/界鞬出" }] }; })); //#endregion //#region src/engine/data/characters/颜良文丑.ts var 颜良文丑; var init_颜良文丑 = __esmMin((() => { 颜良文丑 = { name: "颜良文丑", maxHealth: 4, gender: "男", faction: "群", skills: [{ name: "双雄", path: "../../skills/双雄" }] }; })); //#endregion //#region src/engine/data/characters/界颜良文丑.ts var 界颜良文丑; var init_界颜良文丑 = __esmMin((() => { 界颜良文丑 = { name: "界颜良文丑", maxHealth: 4, gender: "男", faction: "群", skills: [{ name: "界双雄", path: "../../skills/界双雄" }] }; })); //#endregion //#region src/engine/data/characters/董卓.ts var 董卓; var init_董卓 = __esmMin((() => { 董卓 = { name: "董卓", maxHealth: 8, gender: "男", faction: "群", skills: [ { name: "酒池", path: "../../skills/酒池" }, { name: "肉林", path: "../../skills/肉林" }, { name: "崩坏", path: "../../skills/崩坏" }, { name: "暴虐", path: "../../skills/暴虐" } ] }; })); //#endregion //#region src/engine/data/characters/界董卓.ts var 界董卓; var init_界董卓 = __esmMin((() => { 界董卓 = { name: "界董卓", maxHealth: 8, gender: "男", faction: "群", skills: [ { name: "界酒池", path: "../../skills/界酒池" }, { name: "肉林", path: "../../skills/肉林" }, { name: "界崩坏", path: "../../skills/界崩坏" }, { name: "界暴虐", path: "../../skills/界暴虐" } ] }; })); //#endregion //#region src/engine/data/characters/贾诩.ts var 贾诩; var init_贾诩 = __esmMin((() => { 贾诩 = { name: "贾诩", maxHealth: 3, gender: "男", faction: "群", skills: [ { name: "完杀", path: "../../skills/完杀" }, { name: "乱武", path: "../../skills/乱武" }, { name: "帷幕", path: "../../skills/帷幕" } ] }; })); //#endregion //#region src/engine/data/characters/界贾诩.ts var 界贾诩; var init_界贾诩 = __esmMin((() => { 界贾诩 = { name: "界贾诩", maxHealth: 3, gender: "男", faction: "群", skills: [ { name: "界帷幕", path: "../../skills/界帷幕" }, { name: "界乱武", path: "../../skills/界乱武" }, { name: "界完杀", path: "../../skills/界完杀" } ] }; })); //#endregion //#region src/engine/data/characters/左慈.ts var 左慈; var init_左慈 = __esmMin((() => { 左慈 = { name: "左慈", maxHealth: 3, gender: "男", faction: "群", skills: [{ name: "化身", path: "../../skills/化身" }, { name: "新生", path: "../../skills/新生" }] }; })); //#endregion //#region src/engine/data/characters/蔡文姬.ts var 蔡文姬; var init_蔡文姬 = __esmMin((() => { 蔡文姬 = { name: "蔡文姬", maxHealth: 3, gender: "女", faction: "群", skills: [{ name: "悲歌", path: "../../skills/悲歌" }, { name: "断肠", path: "../../skills/断肠" }] }; })); //#endregion //#region src/engine/data/characters/界蔡文姬.ts var 界蔡文姬; var init_界蔡文姬 = __esmMin((() => { 界蔡文姬 = { name: "界蔡文姬", maxHealth: 3, gender: "女", faction: "群", skills: [{ name: "界悲歌", path: "../../skills/界悲歌" }, { name: "断肠", path: "../../skills/断肠" }] }; })); //#endregion //#region src/engine/data/characters/界伏皇后.ts var 界伏皇后; var init_界伏皇后 = __esmMin((() => { 界伏皇后 = { name: "界伏皇后", maxHealth: 3, gender: "女", faction: "群", skills: [{ name: "界惴恐", path: "../../skills/界惴恐" }, { name: "界求援", path: "../../skills/界求援" }] }; })); //#endregion //#region src/engine/data/characters/界公孙瓒.ts var 界公孙瓒; var init_界公孙瓒 = __esmMin((() => { 界公孙瓒 = { name: "界公孙瓒", maxHealth: 4, gender: "男", faction: "群", skills: [{ name: "界义从", path: "../../skills/界义从" }, { name: "界趫猛", path: "../../skills/界趫猛" }] }; })); //#endregion //#region src/engine/data/characters/界刘表.ts var 界刘表; var init_界刘表 = __esmMin((() => { 界刘表 = { name: "界刘表", maxHealth: 3, gender: "男", faction: "群", skills: [{ name: "界自守", path: "../../skills/界自守" }, { name: "界宗室", path: "../../skills/界宗室" }] }; })); //#endregion //#region src/engine/data/characters/界华佗.ts var 界华佗; var init_界华佗 = __esmMin((() => { 界华佗 = { name: "界华佗", maxHealth: 3, gender: "男", faction: "群", skills: [{ name: "急救", path: "../../skills/急救" }, { name: "界除疠", path: "../../skills/界除疠" }] }; })); //#endregion //#region src/engine/data/characters/界华雄.ts var 界华雄; var init_界华雄 = __esmMin((() => { 界华雄 = { name: "界华雄", maxHealth: 6, gender: "男", faction: "群", skills: [{ name: "界耀武", path: "../../skills/界耀武" }, { name: "界势斩", path: "../../skills/界势斩" }] }; })); //#endregion //#region src/engine/data/characters/界吕布.ts var 界吕布; var init_界吕布 = __esmMin((() => { 界吕布 = { name: "界吕布", maxHealth: 5, gender: "男", faction: "群", skills: [{ name: "无双", path: "../../skills/无双" }, { name: "界利驭", path: "../../skills/界利驭" }] }; })); //#endregion //#region src/engine/data/characters/界左慈.ts var 界左慈; var init_界左慈 = __esmMin((() => { 界左慈 = { name: "界左慈", maxHealth: 3, gender: "男", faction: "群", skills: [{ name: "化身", path: "../../skills/化身" }, { name: "新生", path: "../../skills/新生" }] }; })); //#endregion //#region src/engine/data/characters/界张角.ts var 界张角; var init_界张角 = __esmMin((() => { 界张角 = { name: "界张角", maxHealth: 3, gender: "男", faction: "群", skills: [ { name: "界雷击", path: "../../skills/界雷击" }, { name: "界鬼道", path: "../../skills/界鬼道" }, { name: "界黄天", path: "../../skills/界黄天" } ] }; })); //#endregion //#region src/engine/data/characters/界李儒.ts var 界李儒; var init_界李儒 = __esmMin((() => { 界李儒 = { name: "界李儒", maxHealth: 3, gender: "男", faction: "群", skills: [ { name: "界灭计", path: "../../skills/界灭计" }, { name: "界绝策", path: "../../skills/界绝策" }, { name: "界焚城", path: "../../skills/界焚城" } ] }; })); //#endregion //#region src/engine/data/characters/界沮授.ts var 界沮授; var init_界沮授 = __esmMin((() => { 界沮授 = { name: "界沮授", maxHealth: 3, gender: "男", faction: "群", skills: [{ name: "界矢北", path: "../../skills/界矢北" }, { name: "界渐营", path: "../../skills/界渐营" }] }; })); //#endregion //#region src/engine/data/characters/界蔡夫人.ts var 界蔡夫人; var init_界蔡夫人 = __esmMin((() => { 界蔡夫人 = { name: "界蔡夫人", maxHealth: 3, gender: "女", faction: "群", skills: [{ name: "界窃听", path: "../../skills/界窃听" }, { name: "界献州", path: "../../skills/界献州" }] }; })); //#endregion //#region src/engine/data/characters/界陈宫.ts var 界陈宫; var init_界陈宫 = __esmMin((() => { 界陈宫 = { name: "界陈宫", maxHealth: 3, gender: "男", faction: "群", skills: [{ name: "界智迟", path: "../../skills/界智迟" }, { name: "界明策", path: "../../skills/界明策" }] }; })); //#endregion //#region src/engine/data/characters/界高顺.ts var 界高顺; var init_界高顺 = __esmMin((() => { 界高顺 = { name: "界高顺", maxHealth: 4, gender: "男", faction: "群", skills: [{ name: "界陷阵", path: "../../skills/界陷阵" }, { name: "界禁酒", path: "../../skills/界禁酒" }] }; })); //#endregion //#region src/engine/data/characters/华雄.ts var 华雄; var init_华雄 = __esmMin((() => { 华雄 = { name: "华雄", maxHealth: 6, gender: "男", faction: "群", skills: [{ name: "耀武", path: "../../skills/耀武" }] }; })); //#endregion //#region src/engine/data/characters/毌丘俭.ts var 毌丘俭; var init_毌丘俭 = __esmMin((() => { 毌丘俭 = { name: "毌丘俭", maxHealth: 4, gender: "男", faction: "魏", skills: [{ name: "征荣", path: "../../skills/征荣" }, { name: "鸿举", path: "../../skills/鸿举" }] }; })); //#endregion //#region src/engine/data/characters/王基.ts var 王基; var init_王基 = __esmMin((() => { 王基 = { name: "王基", maxHealth: 3, gender: "男", faction: "魏", skills: [{ name: "奇制", path: "../../skills/奇制" }, { name: "进趋", path: "../../skills/进趋" }] }; })); //#endregion //#region src/engine/data/characters/蒯越蒯良.ts var 蒯越蒯良; var init_蒯越蒯良 = __esmMin((() => { 蒯越蒯良 = { name: "蒯越蒯良", maxHealth: 3, gender: "男", faction: "魏", skills: [{ name: "荐降", path: "../../skills/荐降" }, { name: "审时", path: "../../skills/审时" }] }; })); //#endregion //#region src/engine/data/characters/郝昭.ts var 郝昭; var init_郝昭 = __esmMin((() => { 郝昭 = { name: "郝昭", maxHealth: 4, gender: "男", faction: "魏", skills: [{ name: "镇骨", path: "../../skills/镇骨" }] }; })); //#endregion //#region src/engine/data/characters/王平.ts var 王平; var init_王平 = __esmMin((() => { 王平 = { name: "王平", maxHealth: 4, gender: "男", faction: "蜀", skills: [{ name: "飞军", path: "../../skills/飞军" }, { name: "兵略", path: "../../skills/兵略" }] }; })); //#endregion //#region src/engine/data/characters/诸葛瞻.ts var 诸葛瞻; var init_诸葛瞻 = __esmMin((() => { 诸葛瞻 = { name: "诸葛瞻", maxHealth: 3, gender: "男", faction: "蜀", skills: [{ name: "罪论", path: "../../skills/罪论" }, { name: "父荫", path: "../../skills/父荫" }] }; })); //#endregion //#region src/engine/data/characters/陈到.ts var 陈到; var init_陈到 = __esmMin((() => { 陈到 = { name: "陈到", maxHealth: 4, gender: "男", faction: "蜀", skills: [{ name: "往烈", path: "../../skills/往烈" }] }; })); //#endregion //#region src/engine/data/characters/卧龙诸葛亮.ts var 卧龙诸葛亮; var init_卧龙诸葛亮 = __esmMin((() => { 卧龙诸葛亮 = { name: "卧龙诸葛亮", maxHealth: 3, gender: "男", faction: "蜀", skills: [ { name: "八阵", path: "../../skills/八阵" }, { name: "火计", path: "../../skills/火计" }, { name: "看破", path: "../../skills/看破" } ] }; })); //#endregion //#region src/engine/data/characters/严颜.ts var 严颜; var init_严颜 = __esmMin((() => { 严颜 = { name: "严颜", maxHealth: 4, gender: "男", faction: "蜀", skills: [{ name: "拒战", path: "../../skills/拒战" }] }; })); //#endregion //#region src/engine/data/characters/陆抗.ts var 陆抗; var init_陆抗 = __esmMin((() => { 陆抗 = { name: "陆抗", maxHealth: 4, gender: "男", faction: "吴", skills: [ { name: "谦节", path: "../../skills/谦节" }, { name: "决堰", path: "../../skills/决堰" }, { name: "破势", path: "../../skills/破势" } ] }; })); //#endregion //#region src/engine/data/characters/陆绩.ts var 陆绩; var init_陆绩 = __esmMin((() => { 陆绩 = { name: "陆绩", maxHealth: 3, gender: "男", faction: "吴", skills: [ { name: "怀橘", path: "../../skills/怀橘" }, { name: "遗礼", path: "../../skills/遗礼" }, { name: "整论", path: "../../skills/整论" } ] }; })); //#endregion //#region src/engine/data/characters/张昭张弘.ts var 张昭张弘; var init_张昭张弘 = __esmMin((() => { 张昭张弘 = { name: "张昭张弘", maxHealth: 3, gender: "男", faction: "吴", skills: [{ name: "直谏", path: "../../skills/直谏" }, { name: "固政", path: "../../skills/固政" }] }; })); //#endregion //#region src/engine/data/characters/周妃.ts var 周妃; var init_周妃 = __esmMin((() => { 周妃 = { name: "周妃", maxHealth: 3, gender: "男", faction: "吴", skills: [{ name: "良姻", path: "../../skills/良姻" }, { name: "箜声", path: "../../skills/箜声" }] }; })); //#endregion //#region src/engine/data/characters/孙亮.ts var 孙亮; var init_孙亮 = __esmMin((() => { 孙亮 = { name: "孙亮", maxHealth: 3, gender: "男", faction: "吴", skills: [ { name: "溃诛", path: "../../skills/溃诛" }, { name: "掣政", path: "../../skills/掣政" }, { name: "立军", path: "../../skills/立军" } ] }; })); //#endregion //#region src/engine/data/characters/袁术.ts var 袁术; var init_袁术 = __esmMin((() => { 袁术 = { name: "袁术", maxHealth: 4, gender: "男", faction: "群", skills: [{ name: "庸肆", path: "../../skills/庸肆" }, { name: "伪帝", path: "../../skills/伪帝" }] }; })); //#endregion //#region src/engine/data/characters/卢植.ts var 卢植; var init_卢植 = __esmMin((() => { 卢植 = { name: "卢植", maxHealth: 3, gender: "男", faction: "群", skills: [{ name: "明任", path: "../../skills/明任" }, { name: "贞良", path: "../../skills/贞良" }] }; })); //#endregion //#region src/engine/data/characters/张绣.ts var 张绣; var init_张绣 = __esmMin((() => { 张绣 = { name: "张绣", maxHealth: 4, gender: "男", faction: "群", skills: [{ name: "雄乱", path: "../../skills/雄乱" }, { name: "从谏", path: "../../skills/从谏" }] }; })); //#endregion //#region src/engine/data/characters/许攸.ts var 许攸; var init_许攸 = __esmMin((() => { 许攸 = { name: "许攸", maxHealth: 3, gender: "男", faction: "群", skills: [ { name: "成略", path: "../../skills/成略" }, { name: "恃才", path: "../../skills/恃才" }, { name: "寸目", path: "../../skills/寸目" } ] }; })); //#endregion //#region src/engine/data/characters/index.ts var weiCharacters, shuCharacters, wuCharacters, qunCharacters, allCharacters; var init_characters$1 = __esmMin((() => { init_曹操(); init_司马懿(); init_夏侯惇(); init_张辽(); init_许褚(); init_郭嘉(); init_甄姬(); init_夏侯渊(); init_曹仁(); init_荀彧(); init_典韦(); init_曹丕(); init_徐晃(); init_张郃(); init_邓艾(); init_界曹操(); init_界司马懿(); init_界夏侯惇(); init_界张辽(); init_界许褚(); init_界郭嘉(); init_界甄姬(); init_界夏侯渊(); init_界曹仁(); init_界荀彧(); init_界典韦(); init_界曹丕(); init_界徐晃(); init_界张郃(); init_界邓艾(); init_界于禁(); init_界张春华(); init_界曹冲(); init_界曹彰(); init_界曹植(); init_界李典(); init_界王异(); init_界郭淮(); init_界郭皇后(); init_刘备(); init_界刘备(); init_关羽(); init_界关羽(); init_张飞(); init_界张飞(); init_赵云(); init_界赵云(); init_诸葛亮(); init_界诸葛亮(); init_黄月英(); init_界黄月英(); init_马超(); init_界马超(); init_界马岱(); init_黄忠(); init_界黄忠(); init_魏延(); init_界魏延(); init_卧龙诸葛(); init_界卧龙诸葛(); init_庞统(); init_界庞统(); init_孟获(); init_界孟获(); init_祝融(); init_界祝融(); init_姜维(); init_界姜维(); init_刘禅(); init_界刘禅(); init_界关兴张苞(); init_界夏侯氏(); init_界廖化(); init_界张松(); init_界徐庶(); init_界法正(); init_界简雍(); init_孙权(); init_界孙权(); init_甘宁(); init_界甘宁(); init_吕蒙(); init_界吕蒙(); init_黄盖(); init_界黄盖(); init_周瑜(); init_界周瑜(); init_大乔(); init_界大乔(); init_陆逊(); init_界陆逊(); init_孙尚香(); init_界孙尚香(); init_小乔(); init_界小乔(); init_周泰(); init_界周泰(); init_太史慈(); init_界太史慈(); init_鲁肃(); init_界鲁肃(); init_孙坚(); init_界孙坚(); init_孙策(); init_界孙策(); init_张昭张纮(); init_界张昭张纮(); init_界凌统(); init_界吴国太(); init_界徐盛(); init_界程普(); init_界虞翻(); init_界韩当(); init_华佗(); init_吕布(); init_貂蝉(); init_界貂蝉(); init_张角(); init_于吉(); init_界于吉(); init_袁绍(); init_界袁绍(); init_庞德(); init_界庞德(); init_颜良文丑(); init_界颜良文丑(); init_董卓(); init_界董卓(); init_贾诩(); init_界贾诩(); init_左慈(); init_蔡文姬(); init_界蔡文姬(); init_界伏皇后(); init_界公孙瓒(); init_界刘表(); init_界华佗(); init_界华雄(); init_界吕布(); init_界左慈(); init_界张角(); init_界李儒(); init_界沮授(); init_界蔡夫人(); init_界陈宫(); init_界高顺(); init_华雄(); init_毌丘俭(); init_王基(); init_蒯越蒯良(); init_郝昭(); init_王平(); init_诸葛瞻(); init_陈到(); init_卧龙诸葛亮(); init_严颜(); init_陆抗(); init_陆绩(); init_张昭张弘(); init_周妃(); init_孙亮(); init_袁术(); init_卢植(); init_张绣(); init_许攸(); weiCharacters = [ 曹操, 司马懿, 夏侯惇, 张辽, 许褚, 郭嘉, 甄姬, 夏侯渊, 曹仁, 荀彧, 典韦, 曹丕, 徐晃, 张郃, 邓艾, 界曹操, 界司马懿, 界夏侯惇, 界张辽, 界许褚, 界郭嘉, 界甄姬, 界夏侯渊, 界曹仁, 界荀彧, 界典韦, 界曹丕, 界徐晃, 界张郃, 界邓艾, 界于禁, 界张春华, 界曹冲, 界曹彰, 界曹植, 界李典, 界王异, 界郭淮, 界郭皇后, 毌丘俭, 王基, 蒯越蒯良, 郝昭 ]; shuCharacters = [ 刘备, 界刘备, 关羽, 界关羽, 张飞, 界张飞, 赵云, 界赵云, 黄月英, 界黄月英, 马超, 界马超, 界马岱, 黄忠, 界黄忠, 魏延, 界魏延, 卧龙诸葛, 界卧龙诸葛, 诸葛亮, 界诸葛亮, 庞统, 界庞统, 孟获, 界孟获, 祝融, 界祝融, 姜维, 界姜维, 刘禅, 界刘禅, 界关兴张苞, 界夏侯氏, 界廖化, 界张松, 界徐庶, 界法正, 界简雍, 王平, 诸葛瞻, 陈到, 卧龙诸葛亮, 严颜 ]; wuCharacters = [ 孙权, 界孙权, 甘宁, 界甘宁, 吕蒙, 界吕蒙, 黄盖, 界黄盖, 周瑜, 界周瑜, 大乔, 界大乔, 陆逊, 界陆逊, 孙尚香, 界孙尚香, 小乔, 界小乔, 周泰, 界周泰, 太史慈, 界太史慈, 鲁肃, 界鲁肃, 孙坚, 界孙坚, 孙策, 界孙策, 张昭张纮, 界张昭张纮, 界凌统, 界吴国太, 界徐盛, 界程普, 界虞翻, 界韩当, 陆抗, 陆绩, 张昭张弘, 周妃, 孙亮 ]; qunCharacters = [ 华佗, 吕布, 貂蝉, 界貂蝉, 张角, 于吉, 界于吉, 袁绍, 界袁绍, 庞德, 界庞德, 颜良文丑, 界颜良文丑, 董卓, 界董卓, 贾诩, 界贾诩, 左慈, 蔡文姬, 界蔡文姬, 界伏皇后, 界公孙瓒, 界刘表, 界华佗, 界华雄, 界吕布, 界左慈, 界张角, 界李儒, 界沮授, 界蔡夫人, 界陈宫, 界高顺, 华雄, 袁术, 卢植, 张绣, 许攸 ]; allCharacters = [ ...weiCharacters, ...shuCharacters, ...wuCharacters, ...qunCharacters ]; })); //#endregion //#region src/engine/data/characters.ts var init_characters = __esmMin((() => { init_characters$1(); })); //#endregion //#region src/engine/core/clock.ts var RealClock, VirtualClock; var init_clock = __esmMin((() => { RealClock = class { now() { return Date.now(); } schedule(delayMs, fn) { const timer = setTimeout(fn, delayMs); return () => clearTimeout(timer); } }; VirtualClock = class { current = 0; events = []; now() { return this.current; } schedule(delayMs, fn) { const ev = { at: this.current + delayMs, fn, cancelled: false }; this.events.push(ev); return () => { ev.cancelled = true; }; } /** 推进到时刻 t(单调不减),按到期顺序 await 触发所有到期事件。 * 循环收集以处理「事件触发过程中新 schedule 的到期事件」。 * fn 内部可能 schedule 新事件(如超时编排创建新 slot),其 at 基于推进后的 current。 */ async advanceTo(t) { if (t < this.current) return; this.current = t; for (;;) { const due = this.events.filter((e) => !e.cancelled && e.at <= this.current).sort((a, b) => a.at - b.at); if (due.length === 0) break; this.events = this.events.filter((e) => e.cancelled || e.at > this.current); for (const ev of due) await ev.fn(); } } }; })); //#endregion //#region src/engine/types/state.ts /** 由花色派生颜色。仅用于真实花色牌(♥♦→红, ♠♣→黑);空花色(转化合成卡)→无色。 */ function suitColor(suit) { if (suit === "♥" || suit === "♦") return "红"; if (suit === "♠" || suit === "♣") return "黑"; return "无色"; } /** * 体力值 = Math.max(0, 体力)。体力(health)可小于 0(过量伤害/濒死), * 体力值封底为 0。手牌上限、已损失体力值、距离、显示等一律取体力值。 * 详见 glossary/value.md「体力」「体力值」。 */ function getHealthValue(player) { return Math.max(0, player?.health ?? 0); } /** 创建 GameState 的统一工厂。缺失字段自动补默认值 */ function createGameState(partial) { const players = partial.players.map((p) => p.tags ? p : { ...p, tags: [] }); return { currentPlayerIndex: 0, phase: "准备", turn: { round: 1, phase: "准备", vars: {} }, zones: { deck: [], discardPile: [], processing: [] }, settlementStack: [], atomStack: [], pendingSlots: /* @__PURE__ */ new Map(), cardWrappers: {}, rngSeed: 0, clock: new RealClock(), seatRotation: 0, marks: [], localVars: {}, meta: { gameId: "", createdAt: 0 }, seq: 0, startedAt: 0, actionLog: [], atomHistory: [], ...partial, players }; } var init_state = __esmMin((() => { init_clock(); })); //#endregion //#region src/engine/types/prompt.ts var init_prompt = __esmMin((() => {})); //#endregion //#region src/engine/types/atom.ts var init_atom$1 = __esmMin((() => {})); //#endregion //#region src/engine/types/view.ts var init_view = __esmMin((() => {})); //#endregion //#region src/engine/types/skill.ts var init_skill$1 = __esmMin((() => {})); //#endregion //#region src/engine/types/card-def.ts var init_card_def = __esmMin((() => {})); //#endregion //#region src/engine/types/card-effect.ts var init_card_effect = __esmMin((() => {})); //#endregion //#region src/engine/types/index.ts var init_types = __esmMin((() => { init_state(); init_prompt(); init_atom$1(); init_view(); init_skill$1(); init_card_def(); init_card_effect(); })); //#endregion //#region src/engine/view/choosePlayerCandidates.ts function resolveChoosePlayerCandidates(prompt, state) { if (prompt.type !== "choosePlayer") return prompt; const cp = prompt; if (cp.candidates !== void 0) return prompt; if (!cp.filter) return prompt; const players = state.players.map((p, i) => ({ index: i, name: p.name, character: p.character, faction: p.faction, health: p.health, maxHealth: p.maxHealth, alive: p.alive, equipment: { ...p.equipment }, skills: [...p.skills], handCount: p.hand.length, marks: [...p.marks], pendingTricks: p.pendingTricks.map((t) => t.card.id) })); const partialView = { players, cardMap: state.cardMap }; const candidates = []; for (let i = 0; i < players.length; i++) { if (!players[i].alive) continue; let ok; try { ok = !!cp.filter(partialView, i); } catch { ok = true; } if (ok) candidates.push(i); } return { ...cp, candidates }; } var init_choosePlayerCandidates = __esmMin((() => {})); //#endregion //#region src/engine/view/cardFilterCandidates.ts /** * 对 useCard / useCardAndTarget prompt 注入可序列化的 cardFilter.candidates。 * @param prompt 原始 prompt(可能含 filter 函数) * @param state 引擎状态(读取 target 手牌 + cardMap) * @param target 被询问座次(filter 针对其手牌计算);target<0(广播型)时跳过 * @returns 带 candidates 的 prompt;无 filter / 无手牌 / 广播型时原样返回 */ function resolveCardFilterCandidates(prompt, state, target) { if (prompt.type !== "useCard" && prompt.type !== "useCardAndTarget") return prompt; const p = prompt; const cf = p.cardFilter; if (cf.candidates !== void 0) return prompt; if (target < 0) return prompt; if (!cf.filter) return prompt; const player = state.players[target]; if (!player) return prompt; const candidates = []; for (const cardId of player.hand) { const card = state.cardMap[cardId]; if (card && cf.filter(card)) candidates.push(cardId); } return { ...p, cardFilter: { ...cf, candidates } }; } var init_cardFilterCandidates = __esmMin((() => {})); //#endregion //#region src/engine/rules/vars-keys.ts function slashUnlimitedKey(source) { return SLASH_UNLIMITED_PREFIX + source; } function slashExtraKey(source) { return SLASH_EXTRA_PREFIX + source; } function slashBlockedKey(source) { return SLASH_BLOCKED_PREFIX + source; } function slashTargetKey(source) { return SLASH_TARGET_PREFIX + source; } /** 限一次投影 key。写:once-per-turn.markOncePerTurn(参数为技能名,动态);读:各技能 activeWhen。 */ function usedThisTurnKey(skillName) { return skillName + USED_THIS_TURN_SUFFIX; } /** * 秘计挂起标记 key:界贞烈选项②写入,界秘计结束阶段消费(跨回合持久,存 player.vars)。 * 写:界贞烈.ts;读:界秘计.ts。 */ function mijiPendingKey(ownerId) { return `秘计/pendingFrom贞烈/${ownerId}`; } /** * 手牌上限加成 key:存 turn.vars(回合结束自动清空)。 * 写:决堰/界集智/界精策;读:rules/hand-limit.ts(默认公式 health + bonus)+ 多技能覆盖 provider。 */ function handLimitBonusKey(player) { return `手牌上限/bonus:${player}`; } function asNumber(v) { return typeof v === "number" ? v : void 0; } function asBoolean(v) { return typeof v === "boolean" ? v : void 0; } function asStringArray(v) { return Array.isArray(v) && v.every((x) => typeof x === "string") ? v : void 0; } function asNumberArray(v) { return Array.isArray(v) && v.every((x) => typeof x === "number") ? v : void 0; } /** 距离/进攻修正(默认 0)。正值=缩短距离(进攻马/马术/屯田/急袭)。 */ function getDistanceAttackMod(vars) { return asNumber(vars?.[DISTANCE_ATTACK_MOD_KEY]); } /** 距离/防御修正(默认 0)。正值=增加距离(防御马)。 */ function getDistanceDefenseMod(vars) { return asNumber(vars?.[DISTANCE_DEFENSE_MOD_KEY]); } /** 距离/出杀范围(默认 1)。武器攻击范围,徒手为 1。 */ function getDistanceAttackRange(vars) { return asNumber(vars?.[DISTANCE_ATTACK_RANGE_KEY]); } /** 手牌上限/bonus:(回合内,回合结束自动清空)。 */ function getHandLimitBonus(turnVars, player) { return asNumber(turnVars?.[handLimitBonusKey(player)]); } /** 魂姿/awakened(整局一次,boolean)。 */ function getHunziAwakened(vars) { return asBoolean(vars?.[HUNZI_AWAKENED_KEY]); } /** 化身/牌池(string[],默认 [])。 */ function getHuashenPool(vars) { return asStringArray(vars?.[HUASHEN_POOL_KEY]); } /** 兵略/已飞军目标(number[],默认 [])。 */ function getBinglueTargets(vars) { return asNumberArray(vars?.[BINGLUE_TARGETS_KEY]); } /** 秘计/pendingFrom贞烈/(player.vars,boolean)。 */ function getMijiPending(vars, ownerId) { return asBoolean(vars?.[mijiPendingKey(ownerId)]); } /** 奇制/count(本回合奇制发动次数)。 */ function getQizhiCount(turnVars) { return asNumber(turnVars?.[QIZHI_COUNT_KEY]); } /** 杀/quotaUsed(本回合额定已用次数)。 */ function getSlashQuotaUsed(turnVars) { return asNumber(turnVars?.[SLASH_QUOTA_USED_KEY]); } /** 杀/extraUsed(本回合额外已用次数)。 */ function getSlashExtraUsed(turnVars) { return asNumber(turnVars?.[SLASH_EXTRA_USED_KEY]); } /** 选牌/结果(PickResult)。形状不符返回 undefined(消费端据此走默认选择)。 */ function getPickResult(localVars) { const v = localVars?.[PICK_RESULT_KEY$3]; if (v === null || typeof v !== "object" || Array.isArray(v)) return void 0; const obj = v; if (typeof obj.zone !== "string") return void 0; if (obj.cardId !== null && typeof obj.cardId !== "string") return void 0; if (obj.handIndex !== null && typeof obj.handIndex !== "number") return void 0; return v; } var QIZHI_COUNT_KEY, SLASH_QUOTA_USED_KEY, SLASH_EXTRA_USED_KEY, HUNZI_AWAKENED_KEY, HUASHEN_POOL_KEY, BINGLUE_TARGETS_KEY, DISTANCE_ATTACK_MOD_KEY, DISTANCE_DEFENSE_MOD_KEY, DISTANCE_ATTACK_RANGE_KEY, PICK_RESULT_KEY$3, SLASH_USED_COUNT_KEY, SLASH_EXEMPT_SUIT_KEY, SLASH_UNLIMITED_PREFIX, SLASH_EXTRA_PREFIX, SLASH_BLOCKED_PREFIX, SLASH_TARGET_PREFIX, USED_THIS_TURN_SUFFIX, XIANZHEN_WIN_TARGET_VIEW_KEY, CHENGLUE_SUITS_VIEW_KEY, JIANGCHI_CHOICE2_VIEW_KEY, GONGQI_ACTIVE_VIEW_KEY, DANGXIAN_NO_RANGE_VIEW_KEY, TIANYI_WIN_VIEW_KEY, JUEYAN_MOUNT_VIEW_KEY, WANGLIE_FIRST_VIEW_KEY, XIONGLUAN_TARGET_VIEW_KEY, SHUANGXIONG_COLOR_VIEW_KEY, JIESHUANGXIONG_COLOR_VIEW_KEY, SHENSHI_STATE_VIEW_KEY, CHENGLUE_STATE_VIEW_KEY, DANGXIAN_EXTRA_PHASE_VIEW_KEY, JUZHAN_STATE_VIEW_KEY, VIEW_MIRROR_PREFIX, TURN_SCOPED_VIEW_KEYS; var init_vars_keys = __esmMin((() => { QIZHI_COUNT_KEY = "奇制/count"; SLASH_QUOTA_USED_KEY = "杀/quotaUsed"; SLASH_EXTRA_USED_KEY = "杀/extraUsed"; HUNZI_AWAKENED_KEY = "魂姿/awakened"; HUASHEN_POOL_KEY = "化身/牌池"; BINGLUE_TARGETS_KEY = "兵略/已飞军目标"; DISTANCE_ATTACK_MOD_KEY = "距离/进攻修正"; DISTANCE_DEFENSE_MOD_KEY = "距离/防御修正"; DISTANCE_ATTACK_RANGE_KEY = "距离/出杀范围"; PICK_RESULT_KEY$3 = "选牌/结果"; SLASH_USED_COUNT_KEY = "杀/usedCount"; SLASH_EXEMPT_SUIT_KEY = "杀/exemptSuit"; SLASH_UNLIMITED_PREFIX = "杀/unlimited/"; SLASH_EXTRA_PREFIX = "杀/extra/"; SLASH_BLOCKED_PREFIX = "杀/blocked/"; SLASH_TARGET_PREFIX = "杀/target/"; USED_THIS_TURN_SUFFIX = "/usedThisTurn"; XIANZHEN_WIN_TARGET_VIEW_KEY = "陷阵/winTarget"; CHENGLUE_SUITS_VIEW_KEY = "成略/suits"; JIANGCHI_CHOICE2_VIEW_KEY = "将驰/choice2"; GONGQI_ACTIVE_VIEW_KEY = "界弓骑/active"; DANGXIAN_NO_RANGE_VIEW_KEY = "当先/noRangeActive"; TIANYI_WIN_VIEW_KEY = "天义/win"; JUEYAN_MOUNT_VIEW_KEY = "决堰/本回合:坐骑"; WANGLIE_FIRST_VIEW_KEY = "往烈/首张可用"; XIONGLUAN_TARGET_VIEW_KEY = "雄乱/目标"; SHUANGXIONG_COLOR_VIEW_KEY = "双雄/color"; JIESHUANGXIONG_COLOR_VIEW_KEY = "界双雄/color"; SHENSHI_STATE_VIEW_KEY = "审时/态"; CHENGLUE_STATE_VIEW_KEY = "成略/态"; DANGXIAN_EXTRA_PHASE_VIEW_KEY = "当先/extraPhase"; JUZHAN_STATE_VIEW_KEY = "拒战/态"; VIEW_MIRROR_PREFIX = "__view/"; TURN_SCOPED_VIEW_KEYS = [ XIANZHEN_WIN_TARGET_VIEW_KEY, CHENGLUE_SUITS_VIEW_KEY, JIANGCHI_CHOICE2_VIEW_KEY, GONGQI_ACTIVE_VIEW_KEY, DANGXIAN_NO_RANGE_VIEW_KEY, TIANYI_WIN_VIEW_KEY, JUEYAN_MOUNT_VIEW_KEY, WANGLIE_FIRST_VIEW_KEY, XIONGLUAN_TARGET_VIEW_KEY, SHUANGXIONG_COLOR_VIEW_KEY, JIESHUANGXIONG_COLOR_VIEW_KEY, SHENSHI_STATE_VIEW_KEY, CHENGLUE_STATE_VIEW_KEY, DANGXIAN_EXTRA_PHASE_VIEW_KEY, JUZHAN_STATE_VIEW_KEY ]; })); //#endregion //#region src/engine/rules/slash-quota.ts function getSlashRegistry(state) { let reg = slashRegistries.get(state); if (!reg) { reg = { quotaProviders: /* @__PURE__ */ new Map(), extraProviders: /* @__PURE__ */ new Map(), unlimitedProviders: /* @__PURE__ */ new Map(), blockers: /* @__PURE__ */ new Map(), exemptors: /* @__PURE__ */ new Map() }; slashRegistries.set(state, reg); } return reg; } /** 通用注册:把 provider 加入 owner 桶,返回取消注册函数。 */ function addToRegistry(reg, bucket, ownerId, provider) { const map = reg[bucket]; let set = map.get(ownerId); if (!set) { set = /* @__PURE__ */ new Set(); map.set(ownerId, set); } set.add(provider); return () => { const s = map.get(ownerId); if (s) { s.delete(provider); if (s.size === 0) map.delete(ownerId); } }; } /** * 注册一个额定上限提供者(技能 onInit 时调用,与 registerAction 同构)。 * 返回取消注册函数——技能 onInit 应将其并入返回的 unload,由 setSkillInstanceUnload * 统一管理,卸载技能实例时自动清理。 */ function registerSlashQuotaProvider(state, ownerId, provider) { return addToRegistry(getSlashRegistry(state), "quotaProviders", ownerId, provider); } /** * 注册一个额外次数提供者(技能 onInit 时调用)。 * 返回取消注册函数——应并入 onInit 返回的 unload。 */ function registerSlashExtraProvider(state, ownerId, provider) { return addToRegistry(getSlashRegistry(state), "extraProviders", ownerId, provider); } /** * 注册一个无限出杀提供者(技能 onInit 时调用)。返回 true 则该玩家本回合出杀无上限。 * 返回取消注册函数——应并入 onInit 返回的 unload。 */ function registerSlashUnlimitedProvider(state, ownerId, provider) { return addToRegistry(getSlashRegistry(state), "unlimitedProviders", ownerId, provider); } /** * 该玩家本回合的额定出杀上限。基础 1;各额定提供者取最大(覆盖型)。 */ function slashQuotaMax(state, player) { let max = 1; const set = getSlashRegistry(state).quotaProviders.get(player); if (set) for (const fn of set) { const v = fn(state, player); if (v > max) max = v; } return max; } /** * 该玩家本回合的额外出杀次数。各额外提供者求和(叠加型)。 */ function slashExtraMax(state, player) { let sum = 0; const set = getSlashRegistry(state).extraProviders.get(player); if (set) for (const fn of set) sum += fn(state, player); return sum; } /** 该玩家本回合是否出杀无上限(任一无限提供者返回 true)。 */ function isSlashUnlimited(state, player) { const set = getSlashRegistry(state).unlimitedProviders.get(player); if (!set) return false; for (const fn of set) if (fn(state, player)) return true; return false; } /** 该玩家本回合额定已用次数(默认 0) */ function slashQuotaUsed(state) { return getSlashQuotaUsed(state.turn.vars) ?? 0; } /** 该玩家本回合额外已用次数(默认 0) */ function slashExtraUsed(state) { return getSlashExtraUsed(state.turn.vars) ?? 0; } /** 当前玩家本回合已出杀次数(额定 + 额外,默认 0) */ function slashUsed(state) { return slashQuotaUsed(state) + slashExtraUsed(state); } /** * 注册一个出杀阻断器(技能 onInit 时调用)。返回取消注册函数—— * 必须并入 onInit 返回的 unload,由 setSkillInstanceUnload 在卸载技能实例时自动清理。 */ function registerSlashBlocker(state, ownerId, blocker) { return addToRegistry(getSlashRegistry(state), "blockers", ownerId, blocker); } /** 该玩家本回合是否被阻断出杀(任一阻断器返回 true 即阻断) */ function isSlashBlocked(state, player) { const set = getSlashRegistry(state).blockers.get(player); if (!set) return false; for (const fn of set) if (fn(state, player)) return true; return false; } /** * 注册一个出杀豁免器(技能 onInit 时调用,与 registerSlashBlocker 同构)。 * 返回的取消注册函数应并入 onInit 返回的 unload。 */ function registerSlashExemptor(state, ownerId, exemptor) { return addToRegistry(getSlashRegistry(state), "exemptors", ownerId, exemptor); } /** 该玩家当前使用的这张【杀】是否豁免出杀次数(任一豁免器返回 true 即豁免)。 * cardId 缺省时只问"无卡牌上下文"的豁免器(目前无此用例,恒返回 false)。 */ function isSlashExempted(state, player, cardId) { if (cardId === void 0) return false; const set = getSlashRegistry(state).exemptors.get(player); if (!set) return false; for (const fn of set) if (fn(state, player, cardId)) return true; return false; } /** 当前玩家是否还能出杀(未被阻断 且 额定/额外次数至少一层未达上限)。 * cardId 可选:若提供且该卡被任一豁免器命中,则绕过次数上限检查。 * 无限出杀(诸葛连弩/咆哮)始终放行。 * 否则按文档两层 OR 判断:quotaUsed < quotaMax || extraUsed < extraMax * (即"不能出杀" = 额定达上限 AND 额外达上限)。 */ function canSlash(state, player, cardId) { if (isSlashBlocked(state, player)) return false; if (cardId !== void 0 && isSlashExempted(state, player, cardId)) return true; if (isSlashUnlimited(state, player)) return true; return slashQuotaUsed(state) < slashQuotaMax(state, player) || slashExtraUsed(state) < slashExtraMax(state, player); } /** 记录一次出杀(已用次数 +1)。 * 无限出杀(诸葛连弩/咆哮)时始终计入 quotaUsed(不计入 extraUsed), * 即使超过 quotaMax 也不动 extraUsed——文档规定无限出杀的杀只计入额定次数。 * 否则优先扣额定,额定满后扣额外。在杀 use 的 execute 末尾调用。 */ function incSlashUsed(state) { const player = state.currentPlayerIndex; if (isSlashUnlimited(state, player)) state.turn.vars[SLASH_QUOTA_USED_KEY] = slashQuotaUsed(state) + 1; else if (slashQuotaUsed(state) < slashQuotaMax(state, player)) state.turn.vars[SLASH_QUOTA_USED_KEY] = slashQuotaUsed(state) + 1; else state.turn.vars[SLASH_EXTRA_USED_KEY] = slashExtraUsed(state) + 1; } var slashRegistries; var init_slash_quota = __esmMin((() => { init_vars_keys(); slashRegistries = /* @__PURE__ */ new WeakMap(); })); //#endregion //#region src/engine/rules/trick-quota.ts function getTrickRegistry(state) { let r = trickRegistries.get(state); if (!r) { r = { blockers: /* @__PURE__ */ new Map() }; trickRegistries.set(state, r); } return r; } /** * 注册一个普通锦囊牌阻断器(技能 onInit 时调用,与 registerSlashBlocker 同构)。 * 返回取消注册函数——必须并入 onInit 返回的 unload,在卸载技能实例时自动清理。 */ function registerTrickBlocker(state, ownerId, blocker) { const key = `${ownerId}:${++nextBlockerId}`; getTrickRegistry(state).blockers.set(key, blocker); return () => { getTrickRegistry(state).blockers.delete(key); }; } /** 该玩家本回合是否被阻断使用普通锦囊牌(任一阻断器返回 true 即阻断)。 */ function isTrickBlocked(state, player) { const reg = trickRegistries.get(state); if (!reg) return false; for (const blocker of reg.blockers.values()) if (blocker(state, player)) return true; return false; } var trickRegistries, nextBlockerId; var init_trick_quota = __esmMin((() => { trickRegistries = /* @__PURE__ */ new WeakMap(); nextBlockerId = 0; })); //#endregion //#region src/engine/core/skill.ts /** 取(或懒创建)state 绑定的注册表。 */ function getRegistry(state) { let r = registries.get(state); if (!r) { r = { actions: /* @__PURE__ */ new Map(), beforeHooks: /* @__PURE__ */ new Map(), afterHooks: /* @__PURE__ */ new Map(), judgeModifiers: /* @__PURE__ */ new Map(), instanceUnloads: /* @__PURE__ */ new Map(), lockedSkills: /* @__PURE__ */ new Map(), altResponseDecls: [] }; registries.set(state, r); } return r; } function actionKey$1(skillId, ownerId, actionType) { return `${skillId}:${ownerId}:${actionType}`; } function instanceKey(skillId, ownerId) { return `${skillId}:${ownerId}`; } /** 查找某玩家的活跃 pending slot。 * 查找顺序:ownerId 阻塞型精确匹配 → 广播型(target { const t = s.atom.target; return typeof t === "number" && t < -1; }); if (broadcast) return broadcast; if (state.pendingSlots.size === 1) { const slot = [...state.pendingSlots.values()][0]; const target = slot.atom.target ?? slot.atom.player; return typeof target === "number" && target === ownerId ? slot : void 0; } } /** 是否存在阻塞型 pending——即需要玩家先回应的询问(询问闪/杀/无瓣/弃牌等)。 * 非阻塞型 pending(出牌阶段的 出牌窗口)不阻止玩家出牌/用技,不计入此判断。 * 判断依据是 slot.isBlocking 字段,由 atom 定义的 pending.isBlocking 声明。 * validateUseCard 和 end action 用此函数替代旧的 pendingSlots.size > 0 检查。 */ function hasBlockingPending(state) { for (const slot of state.pendingSlots.values()) if (slot.isBlocking) return true; return false; } /** 出牌阶段使用牌 action 的通用 validate,覆盖 90% 的 use 场景。 * 检查:自己回合、出牌阶段、无阻塞型 pending、存活、手牌中有牌。 * 返回 null=通过,字符串=拒绝理由。skills 可在此之上追加校验。 * @param opts.cardName 需要的卡牌名称。缺省则不校验牌名。 * @param opts.requireTarget 是否需要非空 targets 数组。缺省则不校验目标。 */ function validateUseCard(state, ownerId, params, opts) { if (state.currentPlayerIndex !== ownerId) return "不是你的回合"; if (state.phase !== "出牌") return "不是出牌阶段"; if (hasBlockingPending(state)) return "当前有等待响应"; const self = state.players[ownerId]; if (!self.alive) return "你已死亡"; const cardId = params.cardId; if (!cardId) return "cardId required"; if (!self.hand.includes(cardId)) return "牌不在手牌中"; if (opts?.cardName && state.cardMap[cardId]?.name !== opts.cardName) return `不是${opts.cardName}`; const card = state.cardMap[cardId]; if (card?.type === "锦囊牌" && card.trickSubtype !== "延时锦囊" && card.trickSubtype !== "响应锦囊" && isTrickBlocked(state, ownerId)) return "本回合不能使用锦囊牌"; if (opts?.requireTarget) { const targets = params.targets; if (!Array.isArray(targets) || targets.length === 0) return "target required"; } return null; } function registerActionEntry(state, entry) { const k = actionKey$1(entry.skillId, entry.ownerId, entry.actionType); getRegistry(state).actions.set(k, entry); } function findActionEntry(state, skillId, ownerId, actionType) { return getRegistry(state).actions.get(actionKey$1(skillId, ownerId, actionType)); } function unregisterActionEntry(state, skillId, ownerId, actionType) { getRegistry(state).actions.delete(actionKey$1(skillId, ownerId, actionType)); } function unregisterActionsForInstance(state, skillId, ownerId) { const reg = getRegistry(state); const prefix = `${skillId}:${ownerId}:`; for (const key of [...reg.actions.keys()]) if (key.startsWith(prefix)) reg.actions.delete(key); for (const list of [reg.beforeHooks, reg.afterHooks]) for (const [atomType, arr] of list) { const filtered = arr.filter((e) => !(e.skillId === skillId && e.ownerId === ownerId)); if (filtered.length === 0) list.delete(atomType); else if (filtered.length !== arr.length) list.set(atomType, filtered); } if (reg.judgeModifiers.get(ownerId)?.skillId === skillId) reg.judgeModifiers.delete(ownerId); } function getBeforeHooks(state, atomType) { return getRegistry(state).beforeHooks.get(atomType) ?? []; } function getAfterHooks(state, atomType) { return getRegistry(state).afterHooks.get(atomType) ?? []; } /** 取判定改判钩子表(key=ownerId 座次)。由 判定 atom 的 afterApply 阶段遍历调用。 */ function getJudgeModifierMap(state) { return getRegistry(state).judgeModifiers; } /** * 声明 ownerId 拥有者在指定 atomType(+可选 requestType)下的「替代回应能力」: * 能用非字面响应牌的方式回应(如龙胆杀当闪、急救红牌当桃、蛊惑声明当桃)。 * * 卡牌回应预检(card-response-availability)据此决定是否走 normal 询问—— * 拥有替代能力的 target 即使手牌中无字面匹配牌,也必须正常询问(skip/silent 会错误剥夺技能)。 * * 与 before-hook 型替代(八卦阵/八阵自动检测)互补:本 API 用于 action/转化型技能。 * 返回 unloader,随技能卸载自动清理(与 registerAction/registerBeforeHook 一致)。 */ function declareAlternativeResponse(state, ownerId, atomType, requestType) { const decl = { ownerId, atomType, requestType }; const reg = getRegistry(state); reg.altResponseDecls.push(decl); return () => { const idx = reg.altResponseDecls.indexOf(decl); if (idx >= 0) reg.altResponseDecls.splice(idx, 1); }; } /** 查询 target 是否声明了指定 atomType(+可选 requestType)的替代回应能力。 */ function hasDeclaredAlternativeResponse(state, atomType, target, requestType) { const list = getRegistry(state).altResponseDecls; for (const d of list) { if (d.ownerId !== target) continue; if (d.atomType !== atomType) continue; if (d.requestType && requestType && d.requestType !== requestType) continue; return true; } return false; } /** * 注册一个 action(主动出牌/使用技能/回应/开始等)。 * 内部封装 registerActionEntry;返回 unloader。 */ function registerAction(state, skillId, ownerId, actionType, validate, execute, rollback) { registerActionEntry(state, { skillId, ownerId, actionType, validate, execute, rollback }); return () => unregisterActionEntry(state, skillId, ownerId, actionType); } /** * 注册一个 before atom 钩子。ownerId 在注册时绑定,handler 通过 ctx.ownerId 拿(无需闭包)。 * before 钩子可返回 HookResult(pass/modify/cancel),after 钩子返回 void。 */ function registerBeforeHook(state, skillId, ownerId, atomType, handler) { const entry = { skillId, ownerId, atomType, phase: "before", handler }; const reg = getRegistry(state); const list = reg.beforeHooks.get(atomType) ?? []; list.push(entry); reg.beforeHooks.set(atomType, list); return () => { const arr = reg.beforeHooks.get(atomType); if (!arr) return; const idx = arr.indexOf(entry); if (idx >= 0) arr.splice(idx, 1); }; } /** * 注册一个 after atom 钩子。ownerId 在注册时绑定。 */ function registerAfterHook(state, skillId, ownerId, atomType, handler) { const entry = { skillId, ownerId, atomType, phase: "after", handler }; const reg = getRegistry(state); const list = reg.afterHooks.get(atomType) ?? []; list.push(entry); reg.afterHooks.set(atomType, list); return () => { const arr = reg.afterHooks.get(atomType); if (!arr) return; const idx = arr.indexOf(entry); if (idx >= 0) arr.splice(idx, 1); }; } /** * 注册判定改判钩子(鬼才/鬼道)。每玩家座次至多一个改判能力(key=ownerId)。 * * 由 判定 atom 的 afterApply 阶段触发——在判定牌翻开(apply 完成)后、 * 消费方(闪电/兵粮寸断/乐不思蜀/八卦阵…)的 after hook 读取判定牌之前。 * * 与 registerAfterHook('判定', ...) 的关键差异:遍历顺序不依赖注册序,而是 * 由 runJudgeModifiers 按「从判定目标起逆时针」依次询问存活玩家, * 彻底消除旧实现「改判方须座次靠前于消费方才能生效」的缺陷。 */ function registerJudgeModifier(state, skillId, ownerId, handler) { const entry = { skillId, ownerId, atomType: "判定", phase: "after", handler }; const reg = getRegistry(state); reg.judgeModifiers.set(ownerId, entry); return () => { if (reg.judgeModifiers.get(ownerId) === entry) reg.judgeModifiers.delete(ownerId); }; } function setSkillInstanceUnload(state, skillId, ownerId, unload) { getRegistry(state).instanceUnloads.set(instanceKey(skillId, ownerId), unload); } function getSkillInstanceUnload(state, skillId, ownerId) { return getRegistry(state).instanceUnloads.get(instanceKey(skillId, ownerId)); } function deleteSkillInstanceUnload(state, skillId, ownerId) { getRegistry(state).instanceUnloads.delete(instanceKey(skillId, ownerId)); } /** 写入技能 isLocked 元数据(实例化时由 lifecycle.ts 调用)。 */ function setSkillLocked(state, skillId, isLocked) { getRegistry(state).lockedSkills.set(skillId, isLocked); } /** 查询技能是否为锁定技(suppression provider 用,锁定技永不压制)。 */ function isSkillLocked(state, skillId) { return getRegistry(state).lockedSkills.get(skillId) ?? false; } var registries; var init_skill = __esmMin((() => { init_types(); init_trick_quota(); registries = /* @__PURE__ */ new WeakMap(); })); //#endregion //#region src/engine/core/card-response-availability.ts /** target 是否拥有「不依赖字面响应牌」的替代回应能力。有则必须走正常询问(skip/silent 会错误剥夺技能)。 * - before-hook 型(八卦阵/八阵):检查 target 自己注册在 atomType 上的 before-hook(自动检测)。 * 攻击方拥有的 hook(界潜袭/义绝)ownerId≠target,正确排除(且它们 cancel 在 preResolve 之前处理)。 * - 声明型(龙胆/急救/蛊惑/...):查技能 onInit 中通过 declareAlternativeResponse 注册的声明表。 * 涵盖转化/转交/action 型技能,无需维护硬编码技能名单——新增技能在其 onInit 中声明即可。 */ function hasAlternativeResponse(state, atomType, target, requestType) { const hooks = getBeforeHooks(state, atomType); for (const h of hooks) if (h.ownerId === target) return true; return hasDeclaredAlternativeResponse(state, atomType, target, requestType); } /** 由可用性推导回应模式。 */ function resolveCardResponseMode(avail) { if (avail.handEmpty) return "skip"; if (!avail.hasMatching) return "silent"; return "normal"; } /** 由可用性推导 preResolve 结果(skip / {delayMs} / null)。 */ function availabilityToPreResolve(avail) { const mode = resolveCardResponseMode(avail); if (mode === "skip") return "skip"; if (mode === "silent") return { delayMs: SHORT_DELAY_MS }; return null; } /** 给定 target + 匹配 filter,评估可回应性(核心纯函数)。 * target 不存在时回退为 handEmpty(保守:调用方一般已校验 target 存在)。 */ function evaluateCardResponseForTarget(state, target, filter) { const player = state.players[target]; if (!player) return { handEmpty: true, hasMatching: false }; const hand = player.hand; if (hand.length === 0) return { handEmpty: true, hasMatching: false }; let hasMatching = false; for (const id of hand) { const card = state.cardMap[id]; if (!card) continue; try { if (filter(card)) { hasMatching = true; break; } } catch { hasMatching = true; break; } } return { handEmpty: false, hasMatching }; } /** 从卡牌回应型 atom(完整 Atom,含 type)解析出"匹配响应牌"的判定函数。 * 非卡牌回应型 / 广播(target<0)/ 强制弃牌(mandatory)返回 null。 * 供 buildView 等持有完整 slot.atom 的调用方使用。 */ function resolveCardResponseFilter(atom) { if (atom.type === "询问闪") return (c) => c.name === "闪"; if (atom.type === "询问杀") return (c) => c.name === "杀"; if (atom.type === "请求回应") { if (atom.mandatory === true) return null; const prompt = atom.prompt; if (prompt?.type !== "useCard") return null; const filter = prompt.cardFilter?.filter; if (typeof filter !== "function") return null; return filter; } return null; } /** 对卡牌回应型 atom(完整 Atom)评估 target 的可回应性。 * 非卡牌回应型 / 广播(target<0)/ target 不存在时返回 null(走正常询问流程)。 * 供 buildView(视图一致性)等持有完整 slot.atom 的调用方使用。 */ function evaluateCardResponse(state, atom) { const filter = resolveCardResponseFilter(atom); if (!filter) return null; const target = atom.target; if (typeof target !== "number" || target < 0) return null; return evaluateCardResponseForTarget(state, target, filter); } /** 便捷:直接由 (state, 完整 atom) 得到回应模式。非卡牌回应型返回 'normal'。 * 供 buildView 使用(slot.atom 是完整 Atom)。应用 hasAlternativeResponse 门控。 */ function getCardResponseMode(state, atom) { const target = atom.target; const requestType = atom.requestType; if (typeof target === "number" && target >= 0 && hasAlternativeResponse(state, atom.type, target, requestType)) return "normal"; const avail = evaluateCardResponse(state, atom); if (!avail) return "normal"; return resolveCardResponseMode(avail); } /** 给定 atomType + target + 匹配 filter,得到门控后的回应模式(供 atom 的 toViewEvents 使用)。 * target 有替代回应能力(转化/转交/判定防御技/救援转化技)时返回 'normal'(不剥夺其技能)。 */ function evaluateCardResponseModeForTarget(state, atomType, target, filter, requestType) { if (hasAlternativeResponse(state, atomType, target, requestType)) return "normal"; return resolveCardResponseMode(evaluateCardResponseForTarget(state, target, filter)); } /** 给定 atomType + target + 匹配 filter,得到门控后的 preResolve 结果(供 atom 的 preResolve 使用)。 * target 有替代回应能力时返回 null(正常询问)。 */ function cardResponsePreResolveForTarget(state, atomType, target, filter, requestType) { if (hasAlternativeResponse(state, atomType, target, requestType)) return null; return availabilityToPreResolve(evaluateCardResponseForTarget(state, target, filter)); } var SHORT_DELAY_MS, SILENT_RESPONSE_PROMPT; var init_card_response_availability = __esmMin((() => { init_skill(); SHORT_DELAY_MS = 1500; SILENT_RESPONSE_PROMPT = { type: "confirm", title: "等待回应", cancelLabel: "" }; })); //#endregion //#region src/engine/view/buildView.ts /** 从 ClientMessage 生成可读日志文本(不含玩家名——player 字段单独携带,由展示层映射) */ function formatLogEntry(msg) { const { skillId, actionType, params } = msg; if (actionType === "use") { const cardId = params.cardId; const targets = params.targets; const targetStr = targets?.length ? ` → ${targets.join(",")}` : ""; return `使用 ${skillId}${cardId ? `(${cardId})` : ""}${targetStr}`; } if (actionType === "respond") { const cardId = params.cardId; return cardId ? `响应 ${cardId}` : "不响应"; } if (actionType === "start") return "开始游戏"; if (actionType === "end") return "结束回合"; return `${skillId}:${actionType}`; } function buildView$1(state, viewer, debug = false) { const log = state.actionLog.slice(-50).map((e) => ({ time: e.timestamp, player: e.message.ownerId, text: formatLogEntry(e.message) })); let pending = null; const broadcastSlot = viewer >= 0 && state.players[viewer]?.alive === true ? [...state.pendingSlots.values()].find((s) => s.atom.target === -2 && !s.isPaused) : void 0; const ownSlot = viewer >= 0 ? state.pendingSlots.get(viewer) : void 0; const ownOrBroadcastSlot = ownSlot && !ownSlot.isPaused ? ownSlot : broadcastSlot; if (ownOrBroadcastSlot) { const slot = ownOrBroadcastSlot; const def = slot.definition; const rawPrompt = slot.atom.prompt ?? def.pending?.prompt ?? (slot.atom.type === "询问闪" ? { type: "useCard", title: "请出闪", cardFilter: { min: 1, max: 1 } } : slot.atom.type === "询问杀" ? { type: "useCard", title: "请出杀", cardFilter: { min: 1, max: 1 } } : { type: "confirm", title: "请回应" }); const targetForResolve = "target" in slot.atom && typeof slot.atom.target === "number" ? slot.atom.target : "player" in slot.atom && typeof slot.atom.player === "number" ? slot.atom.player : -1; const target = targetForResolve; const isSilent = getCardResponseMode(state, slot.atom) === "silent"; const prompt = isSilent ? SILENT_RESPONSE_PROMPT : resolveCardFilterCandidates(resolveChoosePlayerCandidates(rawPrompt, state), state, targetForResolve); pending = { type: "awaits", atom: slot.atom, prompt, target, isBlocking: slot.isBlocking, ...isSilent ? { responseMode: "silent" } : {}, deadline: state.startedAt + slot.deadline, totalMs: slot.resolvedTimeoutMs ?? (slot.atom.timeout ?? def.pending?.timeout ?? 30) * 1e3 }; } else { const OBSERVER_PENDING_TYPES = new Set([ "询问闪", "询问杀", "请求回应", "出牌窗口", ...debug ? ["选将询问", "选将"] : [] ]); const observerSlot = [...state.pendingSlots.values()].find((s) => { const t = s.atom.target ?? s.atom.player; return typeof t === "number" && t >= 0 && t !== viewer && OBSERVER_PENDING_TYPES.has(s.atom.type); }); if (observerSlot) { const slot = observerSlot; const target = slot.atom.target ?? slot.atom.player; pending = { type: "awaits", atom: slot.atom, prompt: { type: "confirm", title: "等待回应", cancelLabel: "" }, target, isBlocking: slot.isBlocking, deadline: state.startedAt + slot.deadline, totalMs: slot.resolvedTimeoutMs ?? (slot.atom.timeout ?? slot.definition.pending?.timeout ?? 30) * 1e3 }; } } const deadline = pending?.deadline ?? null; const deadlineTotalMs = pending?.totalMs ?? 0; return { viewer, currentPlayerIndex: state.currentPlayerIndex, phase: state.phase, turn: state.turn, players: state.players.map((p, i) => { const hideChar = state.charSelecting === true && !debug && i !== viewer; return { index: i, name: hideChar ? p.nickname ?? p.name : p.name, character: hideChar ? "" : p.character, faction: hideChar ? void 0 : p.faction, health: p.health, maxHealth: p.maxHealth, alive: p.alive, equipment: { ...p.equipment }, skills: hideChar ? [] : [...p.skills], handCount: p.hand.length, hand: i === viewer || debug ? p.hand.map((id) => state.cardMap[id]).filter(Boolean) : void 0, marks: [...p.marks], tags: [...p.tags], distanceVars: { attackMod: getDistanceAttackMod(p.vars), defenseMod: getDistanceDefenseMod(p.vars), attackRange: getDistanceAttackRange(p.vars) }, turnUsage: { ...slashUsed(state) > 0 ? { [SLASH_USED_COUNT_KEY]: slashUsed(state) } : {}, ...Object.fromEntries(Object.entries(p.vars).filter(([k, v]) => k.endsWith("/usedThisTurn") && v).map(([k, v]) => [k, v])), ...i === state.currentPlayerIndex ? Object.fromEntries(Object.entries(state.turn.vars).filter(([k]) => k.startsWith(VIEW_MIRROR_PREFIX)).map(([k, v]) => [k.slice(VIEW_MIRROR_PREFIX.length), v])) : {} }, pendingTricks: p.pendingTricks.map((t) => t.card.id), ...(() => { const rawIdentity = p.identity; if (!rawIdentity) return { identity: void 0, identityHidden: false }; if (i === viewer) return { identity: rawIdentity, identityHidden: false }; if (rawIdentity === "主公") return { identity: rawIdentity, identityHidden: false }; if (!p.alive) return { identity: rawIdentity, identityHidden: false }; return { identity: void 0, identityHidden: true }; })() }; }), cardMap: state.cardMap, pending, deadline, /** deadline 对应的倒计时总时长(ms);deadline 为 null 时无意义 */ deadlineTotalMs, log, zones: { deckCount: state.zones.deck.length, discardPileCount: state.zones.discardPile.length, processing: [...state.zones.processing, ...state.settlementStack.flatMap((f) => f.cards)] }, settlementStack: state.settlementStack.map((f) => ({ skillId: f.skillId, from: f.from, params: { ...f.params }, cards: [...f.cards], cancelled: f.cancelled })) }; } var init_buildView = __esmMin((() => { init_types(); init_choosePlayerCandidates(); init_cardFilterCandidates(); init_slash_quota(); init_card_response_availability(); init_vars_keys(); })); //#endregion //#region src/engine/data/card-defs/equipment.ts var 诸葛连弩, 青釭剑, 寒冰剑, 雌雄双股剑, 贯石斧, 青龙偃月刀, 丈八蛇矛, 方天画戟, 麒麟弓, 八卦阵, 仁王盾, 藤甲, 白银狮子, 古锭刀, 朱雀羽扇, 骅骝$1, 赤兔$1, 紫骍$1, 大宛$1, 的卢$1, 绝影$1, 爪黄飞电$1, 武器列表, 防具列表, 马列表, 装备牌列表, 马匹距离修正表, EQUIPMENT_SKILL_MODULE_SPECS; var init_equipment = __esmMin((() => { 诸葛连弩 = { name: "诸葛连弩", type: "装备牌", subtype: "武器", targetFilter: { type: "none" }, effect: { type: "sequence", steps: [] }, range: 1, weaponEffect: { type: "诸葛连弩" }, slashUnlimited: true }; 青釭剑 = { name: "青釭剑", type: "装备牌", subtype: "武器", targetFilter: { type: "none" }, effect: { type: "sequence", steps: [] }, range: 2, weaponEffect: { type: "青釭剑" } }; 寒冰剑 = { name: "寒冰剑", type: "装备牌", subtype: "武器", targetFilter: { type: "none" }, effect: { type: "sequence", steps: [] }, range: 2, weaponEffect: { type: "寒冰剑" } }; 雌雄双股剑 = { name: "雌雄双股剑", type: "装备牌", subtype: "武器", targetFilter: { type: "none" }, effect: { type: "sequence", steps: [] }, range: 2, weaponEffect: { type: "雌雄双股剑" } }; 贯石斧 = { name: "贯石斧", type: "装备牌", subtype: "武器", targetFilter: { type: "none" }, effect: { type: "sequence", steps: [] }, range: 3, weaponEffect: { type: "贯石斧" } }; 青龙偃月刀 = { name: "青龙偃月刀", type: "装备牌", subtype: "武器", targetFilter: { type: "none" }, effect: { type: "sequence", steps: [] }, range: 3, weaponEffect: { type: "青龙偃月刀" } }; 丈八蛇矛 = { name: "丈八蛇矛", type: "装备牌", subtype: "武器", targetFilter: { type: "none" }, effect: { type: "sequence", steps: [] }, range: 3 }; 方天画戟 = { name: "方天画戟", type: "装备牌", subtype: "武器", targetFilter: { type: "none" }, effect: { type: "sequence", steps: [] }, range: 4, slashTargetBonusWhenLastCard: 3 }; 麒麟弓 = { name: "麒麟弓", type: "装备牌", subtype: "武器", targetFilter: { type: "none" }, effect: { type: "sequence", steps: [] }, range: 5 }; 八卦阵 = { name: "八卦阵", type: "装备牌", subtype: "防具", targetFilter: { type: "none" }, effect: { type: "sequence", steps: [] }, armorEffect: { type: "八卦阵" } }; 仁王盾 = { name: "仁王盾", type: "装备牌", subtype: "防具", targetFilter: { type: "none" }, effect: { type: "sequence", steps: [] }, armorEffect: { type: "仁王盾" } }; 藤甲 = { name: "藤甲", type: "装备牌", subtype: "防具", targetFilter: { type: "none" }, effect: { type: "sequence", steps: [] }, armorEffect: { type: "藤甲" } }; 白银狮子 = { name: "白银狮子", type: "装备牌", subtype: "防具", targetFilter: { type: "none" }, effect: { type: "sequence", steps: [] }, armorEffect: { type: "白银狮子" } }; 古锭刀 = { name: "古锭刀", type: "装备牌", subtype: "武器", targetFilter: { type: "none" }, effect: { type: "sequence", steps: [] }, range: 2 }; 朱雀羽扇 = { name: "朱雀羽扇", type: "装备牌", subtype: "武器", targetFilter: { type: "none" }, effect: { type: "sequence", steps: [] }, range: 4 }; 骅骝$1 = { name: "骅骝", type: "装备牌", subtype: "防御马", targetFilter: { type: "none" }, effect: { type: "sequence", steps: [] } }; 赤兔$1 = { name: "赤兔", type: "装备牌", subtype: "进攻马", targetFilter: { type: "none" }, effect: { type: "sequence", steps: [] } }; 紫骍$1 = { name: "紫骍", type: "装备牌", subtype: "进攻马", targetFilter: { type: "none" }, effect: { type: "sequence", steps: [] } }; 大宛$1 = { name: "大宛", type: "装备牌", subtype: "进攻马", targetFilter: { type: "none" }, effect: { type: "sequence", steps: [] } }; 的卢$1 = { name: "的卢", type: "装备牌", subtype: "防御马", targetFilter: { type: "none" }, effect: { type: "sequence", steps: [] } }; 绝影$1 = { name: "绝影", type: "装备牌", subtype: "防御马", targetFilter: { type: "none" }, effect: { type: "sequence", steps: [] } }; 爪黄飞电$1 = { name: "爪黄飞电", type: "装备牌", subtype: "防御马", targetFilter: { type: "none" }, effect: { type: "sequence", steps: [] } }; 武器列表 = [ 诸葛连弩, 青釭剑, 寒冰剑, 雌雄双股剑, 贯石斧, 青龙偃月刀, 丈八蛇矛, 方天画戟, 麒麟弓, 古锭刀, 朱雀羽扇 ]; 防具列表 = [ 八卦阵, 仁王盾, 藤甲, 白银狮子 ]; 马列表 = [ 赤兔$1, 紫骍$1, 大宛$1, 的卢$1, 绝影$1, 爪黄飞电$1, 骅骝$1 ]; 装备牌列表 = [ ...武器列表, ...防具列表, ...马列表 ]; 马匹距离修正表 = new Map(马列表.map((c) => [c.name, c.subtype === "进攻马" ? { attackMod: 1 } : { defenseMod: 1 }])); EQUIPMENT_SKILL_MODULE_SPECS = Object.fromEntries(马列表.map((c) => [c.name, `skills/马匹技能#${c.name}`])); })); //#endregion //#region src/engine/skills/registry.ts /** path 缺省规则:技能名即模块文件名(skills/.ts)。 */ function defaultSpec(skillId) { return `skills/${skillId}`; } /** spec 归一化为引擎根相对路径(如 'skills/龙胆')。 * 武将文件里的 path 相对武将文件本身(保持 IDE 可跳转的语义); * data/characters/*.ts 深度固定,前导 '../../' 直达引擎根, * 故剥掉前导 '../' 序列即得引擎根相对 spec。 * path 深度写错由 meta-test 的磁盘存在性校验兜底。 */ function normalizeSpec(spec) { return spec.replace(/^(?:\.\.\/)+/, ""); } /** * 聚合全部技能声明,构建 skillId → moduleSpec 映射。 * 纯函数,每次调用重建(166 武将遍历为微秒级,不值得引入模块级缓存状态)。 * 同一 id 被多处声明且 spec 一致时幂等(如 标左慈/界左慈 共享 '化身'); * spec 冲突视为数据错误,立即抛出。 */ function buildSkillModuleSpecs() { const specs = /* @__PURE__ */ new Map(); const add = (id, rawSpec, source) => { const spec = normalizeSpec(rawSpec); const existing = specs.get(id); if (existing !== void 0 && existing !== spec) throw new Error(`技能 "${id}" 声明冲突: ${source} 声明 ${spec},但已有 ${existing}`); specs.set(id, spec); }; for (const ch of allCharacters) for (const ref of ch.skills) add(ref.name, ref.path ?? defaultSpec(ref.name), `武将 ${ch.name}`); for (const card of 装备牌列表) add(card.name, EQUIPMENT_SKILL_MODULE_SPECS[card.name] ?? defaultSpec(card.name), `装备 ${card.name}`); for (const [id, spec] of Object.entries(SYSTEM_SKILL_SPECS)) add(id, spec, "系统技能"); for (const [id, spec] of Object.entries(DERIVED_SKILL_SPECS)) add(id, spec, "衍生技"); return specs; } /** 查询技能 id 的模块 spec;未声明返回 undefined。 */ function getSkillModuleSpec(id) { return buildSkillModuleSpecs().get(id); } /** 技能 id 是否有声明(取代旧 `id in skillLoaders` 检查)。 */ function hasSkillModule(id) { return buildSkillModuleSpecs().has(id); } var SYSTEM_SKILL_SPECS, DERIVED_SKILL_SPECS; var init_registry$1 = __esmMin((() => { init_characters$1(); init_equipment(); SYSTEM_SKILL_SPECS = { 回合管理: "skills/回合管理", 装备通用: "skills/装备通用", 使用牌: "skills/cards/use-card", 打出牌: "skills/cards/play-card", 铁索连环: "skills/铁索连环", 开局: "skills/开局", 护甲: "skills/护甲" }; DERIVED_SKILL_SPECS = { 界荐言: "skills/界荐言", 攻心: "skills/攻心" }; })); //#endregion //#region src/engine/atoms/damage-timing.ts /** 校验 source/target 玩家存在(纯标记,不校验存活/amount——编排函数前置保证)。 */ function validateDamageTiming(state, atom) { if (!state.players[atom.target]) return `target ${atom.target} not found`; return null; } /** 无 before-hook 时静默(no-op 视图),有 before-hook 时发通知事件。 */ function damageTimingView(state, type, atom) { if (getBeforeHooks(state, type).length === 0) return { ownerViews: /* @__PURE__ */ new Map(), othersView: null }; const view = { type, source: atom.source, target: atom.target, amount: atom.amount, damageType: atom.damageType }; return { ownerViews: /* @__PURE__ */ new Map(), othersView: view }; } var DAMAGE_AMOUNT_KEY, DAMAGE_SOURCE_KEY, 伤害结算开始时, 造成伤害时, 受到伤害时, 造成伤害后, 受到伤害后, 伤害结算结束时, 伤害结算结束后; var init_damage_timing = __esmMin((() => { init_skill(); DAMAGE_AMOUNT_KEY = "__damageAmount"; DAMAGE_SOURCE_KEY = "__damageSource"; 伤害结算开始时 = { type: "伤害结算开始时", validate: validateDamageTiming, apply() {}, async afterApply(state, atom) { state.localVars[DAMAGE_AMOUNT_KEY] = atom.amount; state.localVars[DAMAGE_SOURCE_KEY] = atom.source; }, toViewEvents(state, atom) { return damageTimingView(state, "伤害结算开始时", atom); }, applyView() {} }; 造成伤害时 = { type: "造成伤害时", validate: validateDamageTiming, apply() {}, async afterApply(state, atom) { state.localVars[DAMAGE_AMOUNT_KEY] = atom.amount; }, toViewEvents(state, atom) { return damageTimingView(state, "造成伤害时", atom); }, applyView() {} }; 受到伤害时 = { type: "受到伤害时", validate: validateDamageTiming, apply() {}, async afterApply(state, atom) { state.localVars[DAMAGE_AMOUNT_KEY] = atom.amount; }, toViewEvents(state, atom) { return damageTimingView(state, "受到伤害时", atom); }, applyView() {} }; 造成伤害后 = { type: "造成伤害后", validate: validateDamageTiming, apply() {}, toViewEvents(state, atom) { return damageTimingView(state, "造成伤害后", atom); }, applyView() {} }; 受到伤害后 = { type: "受到伤害后", validate: validateDamageTiming, apply() {}, toViewEvents(state, atom) { return damageTimingView(state, "受到伤害后", atom); }, applyView() {} }; 伤害结算结束时 = { type: "伤害结算结束时", validate: validateDamageTiming, apply() {}, toViewEvents(state, atom) { return damageTimingView(state, "伤害结算结束时", atom); }, applyView() {} }; 伤害结算结束后 = { type: "伤害结算结束后", validate: validateDamageTiming, apply() {}, toViewEvents(state, atom) { return damageTimingView(state, "伤害结算结束后", atom); }, applyView() {} }; })); //#endregion //#region src/engine/core/judge-zone.ts /** 查询实体牌当前所在区。牌不在任何区(仅存于 cardMap 的历史快照/已销毁的虚拟牌)返回 null。 */ function locateCard(state, cardId) { if (state.zones.deck.includes(cardId)) return { zone: "牌堆" }; if (state.zones.discardPile.includes(cardId)) return { zone: "弃牌堆" }; if (state.zones.processing.includes(cardId)) return { zone: "处理区" }; for (const frame of state.settlementStack) if (frame.cards.includes(cardId)) return { zone: "处理区" }; for (const p of state.players) { if (p.hand.includes(cardId)) return { zone: "手牌", player: p.index }; for (const slot of [ "武器", "防具", "进攻马", "防御马", "宝物" ]) if (p.equipment[slot] === cardId) return { zone: "装备", player: p.index }; } return null; } /** 弃置判定区延时锦囊的实体牌(拆牌/弃牌类:过河拆桥、涅槃弃判定区、死亡清理)。 * * 实体牌已在某区(正常对局中即弃牌堆)时不再动作——判定区只是快照,重复入堆会让 * 同一张牌在弃牌堆出现两次。仅当牌不在任何区(历史快照/未实体化的牌)时补入弃牌堆, * 与旧行为一致(测试夹具直接构造 pendingTricks 时仍会落堆)。 * * 调用方负责先 移除延时锦囊(清 pendingTricks 快照)。 */ async function discardJudgeZoneCard(state, player, cardId) { if (locateCard(state, cardId) !== null) return; await applyAtom(state, { type: "弃置", player, cardIds: [cardId] }); } /** 获得判定区延时锦囊的实体牌(顺手牵羊/反馈/行殇)。 * * 实体牌在弃牌堆 → 从弃牌堆移入获得方手牌(牌随技能转移,不复制)。 * 牌不在任何区 → 直接进获得方手牌(历史快照/未实体化的牌)。 * 牌在牌堆/他人手牌/处理区 → 快照已过期(实体牌被重洗后已被他人获得),不转移。 * * 调用方负责先 移除延时锦囊(清 pendingTricks 快照)。 */ async function obtainJudgeZoneCard(state, player, cardId, to) { const loc = locateCard(state, cardId); if (loc === null) { await applyAtom(state, { type: "获得", player: to, cardId, from: player }); return; } if (loc.zone === "弃牌堆") await applyAtom(state, { type: "移动牌", cardId, from: { zone: "弃牌堆" }, to: { zone: "手牌", player: to } }); } var init_judge_zone = __esmMin((() => { init_apply(); })); //#endregion //#region src/engine/atoms/death-timing.ts /** 校验 player 存在(纯标记,不校验存活——编排函数前置保证)。 */ function validateDeathTiming(state, atom) { if (!state.players[atom.player]) return `player ${atom.player} not found`; return null; } /** 无 before hook 时静默(no-op 视图),有 before-hook 时发通知事件(携带 killer)。 */ function deathTimingView(state, type, atom) { if (getBeforeHooks(state, type).length === 0) return { ownerViews: /* @__PURE__ */ new Map(), othersView: null }; const view = { type, player: atom.player, killer: atom.killer }; return { ownerViews: /* @__PURE__ */ new Map(), othersView: view }; } /** 校验 target 存在(纯标记,不校验存活/濒死——编排函数前置保证)。 */ function validateDyingTiming(state, atom) { if (!state.players[atom.target]) return `target ${atom.target} not found`; return null; } var 亮身份牌前, 亮身份牌, 死亡时, 系统处理牌, 死亡后, 进入濒死状态时, 新的濒死状态时; var init_death_timing = __esmMin((() => { init_skill(); init_judge_zone(); 亮身份牌前 = { type: "亮身份牌前", validate: validateDeathTiming, apply() {}, toViewEvents(state, atom) { if (getBeforeHooks(state, "亮身份牌前").length === 0) return { ownerViews: /* @__PURE__ */ new Map(), othersView: null }; const view = { type: "亮身份牌前", player: atom.player }; return { ownerViews: /* @__PURE__ */ new Map(), othersView: view }; }, applyView() {} }; 亮身份牌 = { type: "亮身份牌", validate: validateDeathTiming, apply() {}, effect: { sound: "death", animation: "fade", duration: 1500, vfx: "card/killer" }, toViewEvents(state, atom) { const identity = state.players[atom.player]?.identity; const view = { type: "亮身份牌", player: atom.player, identity }; return { ownerViews: /* @__PURE__ */ new Map(), othersView: view }; }, applyView(view, event) { const pi = view.players.findIndex((p) => p.index === event.player); if (pi >= 0) { const identity = event.identity; if (identity) { view.players[pi].identity = identity; view.players[pi].identityHidden = false; } } } }; 死亡时 = { type: "死亡时", validate: validateDeathTiming, apply() {}, toViewEvents(state, atom) { return deathTimingView(state, "死亡时", atom); }, applyView() {} }; 系统处理牌 = { type: "系统处理牌", validate(state, atom) { if (!state.players[atom.player]) return `player ${atom.player} not found`; return null; }, apply(state, atom) { const p = state.players[atom.player]; p.alive = false; for (const cardId of p.hand) state.zones.discardPile.push(cardId); p.hand = []; for (const slot of Object.keys(p.equipment)) { const equipId = p.equipment[slot]; if (equipId) { state.zones.discardPile.push(equipId); delete p.equipment[slot]; } } for (const trick of p.pendingTricks) if (locateCard(state, trick.card.id) === null) state.zones.discardPile.push(trick.card.id); p.pendingTricks = []; }, effect: { animation: "fade", duration: 1500 }, toViewEvents(state, atom) { const character = state.players[atom.player]?.character; const trickDiscardCount = (state.players[atom.player]?.pendingTricks ?? []).filter((t) => locateCard(state, t.card.id) === null).length; const view = { type: "系统处理牌", player: atom.player, trickDiscardCount, ...character ? { effect: { sound: `death/${character}` } } : {} }; return { ownerViews: /* @__PURE__ */ new Map(), othersView: view }; }, applyView(view, event) { const pi = view.players.findIndex((p) => p.index === event.player); if (pi >= 0) { const p = view.players[pi]; const handCount = p.handCount; const equipCount = Object.values(p.equipment).filter(Boolean).length; const trickCount = typeof event.trickDiscardCount === "number" ? event.trickDiscardCount : p.pendingTricks?.length ?? 0; if (view.zones) view.zones.discardPileCount += handCount + equipCount + trickCount; p.alive = false; if (view.viewer === event.player) p.hand = []; p.handCount = 0; p.equipment = {}; p.pendingTricks = []; } }, toViewLog(event) { return { player: event.player, text: "阵亡" }; } }; 死亡后 = { type: "死亡后", validate: validateDeathTiming, apply() {}, toViewEvents(state, atom) { return deathTimingView(state, "死亡后", atom); }, applyView() {} }; 进入濒死状态时 = { type: "进入濒死状态时", validate: validateDyingTiming, apply() {}, toViewEvents(state, atom) { if (getBeforeHooks(state, "进入濒死状态时").length === 0) return { ownerViews: /* @__PURE__ */ new Map(), othersView: null }; const view = { type: "进入濒死状态时", target: atom.target }; return { ownerViews: /* @__PURE__ */ new Map(), othersView: view }; }, applyView() {} }; 新的濒死状态时 = { type: "新的濒死状态时", validate: validateDyingTiming, apply() {}, toViewEvents(state, atom) { if (getBeforeHooks(state, "新的濒死状态时").length === 0) return { ownerViews: /* @__PURE__ */ new Map(), othersView: null }; const view = { type: "新的濒死状态时", target: atom.target }; return { ownerViews: /* @__PURE__ */ new Map(), othersView: view }; }, applyView() {} }; })); //#endregion //#region src/engine/atoms/judge-timing.ts /** 校验 player 存在(纯标记,不校验存活——编排函数前置保证)。 */ function validateJudgeTiming(state, atom) { if (!state.players[atom.player]) return `player ${atom.player} not found`; return null; } /** 校验 player 与判定牌存在(生效前/后 携带 cardId)。 */ function validateJudgeTimingWithCard(state, atom) { if (!state.players[atom.player]) return `player ${atom.player} not found`; if (atom.cardId !== void 0 && !state.cardMap[atom.cardId]) return `card ${atom.cardId} not found`; return null; } /** 无 before hook 时静默(no-op 视图),有 before-hook 时发通知事件。 */ function judgeTimingView(state, type, atom) { if (getBeforeHooks(state, type).length === 0) return { ownerViews: /* @__PURE__ */ new Map(), othersView: null }; const view = { type, player: atom.player, judgeType: atom.judgeType }; if (atom.cardId !== void 0) view.cardId = atom.cardId; return { ownerViews: /* @__PURE__ */ new Map(), othersView: view }; } var 判定时, 判定牌生效前, 判定牌生效后; var init_judge_timing = __esmMin((() => { init_apply(); init_skill(); 判定时 = { type: "判定时", validate: validateJudgeTiming, apply() {}, toViewEvents(state, atom) { return judgeTimingView(state, "判定时", atom); }, applyView() {} }; 判定牌生效前 = { type: "判定牌生效前", validate: validateJudgeTimingWithCard, apply() {}, async afterApply(state) { await runJudgeModifiers(state); }, toViewEvents(state, atom) { return judgeTimingView(state, "判定牌生效前", atom); }, applyView() {} }; 判定牌生效后 = { type: "判定牌生效后", validate: validateJudgeTimingWithCard, apply() {}, toViewEvents(state, atom) { return judgeTimingView(state, "判定牌生效后", atom); }, applyView() {} }; })); //#endregion //#region src/engine/atoms/life-timing.ts /** 校验 target/player 玩家存在(纯标记,不校验存活/amount——编排函数前置保证)。 */ function validateLifeTiming(state, atom) { if ("target" in atom) { if (!state.players[atom.target]) return `target ${atom.target} not found`; } else if (!state.players[atom.player]) return `player ${atom.player} not found`; return null; } /** 无 before-hook 时静默(no-op 视图),有 before-hook 时发通知事件(携带 target/player/amount/source)。 */ function lifeTimingView(state, type, atom) { if (getBeforeHooks(state, type).length === 0) return { ownerViews: /* @__PURE__ */ new Map(), othersView: null }; const view = "target" in atom ? { type, target: atom.target, amount: atom.amount, ..."source" in atom ? { source: atom.source } : {} } : { type, player: atom.player }; return { ownerViews: /* @__PURE__ */ new Map(), othersView: view }; } /** 标记型 atom 视图应用 no-op(实质变更由编排函数/扣减体力等实质 atom 负责)。 */ function lifeTimingApplyView() {} var 扣减体力, MODIFY_AMOUNT_KEY, 确定回复数值时, 回复体力后, 失去体力时, 失去体力后, 扣减体力前, 扣减体力时, 扣减体力后, 减上限后, 加上限后; var init_life_timing = __esmMin((() => { init_skill(); 扣减体力 = { type: "扣减体力", validate(state, atom) { if (atom.amount < 0) return "amount must be >= 0"; const p = state.players[atom.target]; if (!p) return `target ${atom.target} not found`; if (!p.alive) return `target ${atom.target} is dead`; return null; }, apply(state, atom) { const target = state.players[atom.target]; target.health = target.health - atom.amount; }, effect: { sound: "injure_1", animation: "shake", particles: "blood", duration: 800 }, toViewEvents(state, atom) { const view = { type: "扣减体力", target: atom.target, amount: atom.amount, effect: { sound: `injure_${Math.min(Math.max(atom.amount, 1), 3)}`, vfx: "card/damage" } }; return { ownerViews: /* @__PURE__ */ new Map(), othersView: view }; }, applyView(view, event) { const pi = view.players.findIndex((p) => p.index === event.target); if (pi < 0) return; const p = view.players[pi]; p.health = p.health - event.amount; }, toViewLog(event) { return { player: event.target, text: `扣减 ${event.amount ?? 0} 点体力` }; } }; MODIFY_AMOUNT_KEY = "__modifyAmount"; 确定回复数值时 = { type: "确定回复数值时", validate: validateLifeTiming, apply(state, atom) { state.localVars[MODIFY_AMOUNT_KEY] = atom.amount; }, toViewEvents(state, atom) { return lifeTimingView(state, "确定回复数值时", atom); }, applyView: lifeTimingApplyView }; 回复体力后 = { type: "回复体力后", validate: validateLifeTiming, apply() {}, toViewEvents(state, atom) { return lifeTimingView(state, "回复体力后", atom); }, applyView: lifeTimingApplyView }; 失去体力时 = { type: "失去体力时", validate: validateLifeTiming, apply() {}, toViewEvents(state, atom) { return lifeTimingView(state, "失去体力时", atom); }, applyView: lifeTimingApplyView }; 失去体力后 = { type: "失去体力后", validate: validateLifeTiming, apply() {}, toViewEvents(state, atom) { return lifeTimingView(state, "失去体力后", atom); }, applyView: lifeTimingApplyView }; 扣减体力前 = { type: "扣减体力前", validate: validateLifeTiming, apply() {}, toViewEvents(state, atom) { return lifeTimingView(state, "扣减体力前", atom); }, applyView: lifeTimingApplyView }; 扣减体力时 = { type: "扣减体力时", validate: validateLifeTiming, apply() {}, toViewEvents(state, atom) { return lifeTimingView(state, "扣减体力时", atom); }, applyView: lifeTimingApplyView }; 扣减体力后 = { type: "扣减体力后", validate: validateLifeTiming, apply() {}, toViewEvents(state, atom) { return lifeTimingView(state, "扣减体力后", atom); }, applyView: lifeTimingApplyView }; 减上限后 = { type: "减上限后", validate: validateLifeTiming, apply() {}, toViewEvents(state, atom) { return lifeTimingView(state, "减上限后", atom); }, applyView: lifeTimingApplyView }; 加上限后 = { type: "加上限后", validate: validateLifeTiming, apply() {}, toViewEvents(state, atom) { return lifeTimingView(state, "加上限后", atom); }, applyView: lifeTimingApplyView }; })); //#endregion //#region src/engine/atoms/move-timing.ts /** 校验 cardId 存在(纯标记,不校验 from/to 合法性——编排函数前置保证)。 */ function validateMoveTiming(state, atom) { if (!state.cardMap[atom.cardId]) return `card ${atom.cardId} not found`; return null; } /** 无 before-hook 时静默(no-op 视图),有 before-hook 时发通知事件。 */ function moveTimingView(state, type, atom) { if (getBeforeHooks(state, type).length === 0) return { ownerViews: /* @__PURE__ */ new Map(), othersView: null }; const view = { type, cardId: atom.cardId, from: atom.from, to: atom.to, reason: atom.reason }; return { ownerViews: /* @__PURE__ */ new Map(), othersView: view }; } var MOVE_TO_KEY, 移动到目标区域前, 移动到目标区域后; var init_move_timing = __esmMin((() => { init_skill(); MOVE_TO_KEY = "__moveTo"; 移动到目标区域前 = { type: "移动到目标区域前", validate: validateMoveTiming, apply() {}, async afterApply(state, atom) { state.localVars[MOVE_TO_KEY] = atom.to; }, toViewEvents(state, atom) { return moveTimingView(state, "移动到目标区域前", atom); }, applyView() {} }; 移动到目标区域后 = { type: "移动到目标区域后", validate: validateMoveTiming, apply() {}, toViewEvents(state, atom) { return moveTimingView(state, "移动到目标区域后", atom); }, applyView() {} }; })); //#endregion //#region src/engine/atoms/rank-timing.ts /** 校验 initiator/target 玩家存在(纯标记视角;不校验存活/手牌——编排函数前置保证)。 */ function validateRankCompare(state, atom) { if (!state.players[atom.initiator]) return `initiator ${atom.initiator} not found`; if (!state.players[atom.target]) return `target ${atom.target} not found`; return null; } function cardFace(card) { return card ? { name: card.name, suit: card.suit, rank: card.rank } : null; } var 拼点扣置, 拼点亮出, 拼点后; var init_rank_timing = __esmMin((() => { init_skill(); 拼点扣置 = { type: "拼点扣置", validate: validateRankCompare, apply(state, atom) { const frame = state.settlementStack[state.settlementStack.length - 1]; const dest = frame ? frame.cards : state.zones.processing; const moves = [{ player: atom.initiator, cardId: atom.initiatorCard }, { player: atom.target, cardId: atom.targetCard }]; for (const { player, cardId } of moves) { if (!cardId) continue; const p = state.players[player]; if (!p) continue; p.hand = p.hand.filter((id) => id !== cardId); dest.push(cardId); } }, effect: { sound: "flip", animation: "flip", duration: 800 }, toViewEvents(state, atom) { const initFace = cardFace(state.cardMap[atom.initiatorCard]); const tgtFace = cardFace(state.cardMap[atom.targetCard]); const base = { type: "拼点扣置", initiator: atom.initiator, target: atom.target, initiatorCard: atom.initiatorCard, targetCard: atom.targetCard, effect: { sound: "flip", animation: "flip", duration: 800 } }; const initiatorView = { ...base, initiatorCardFace: initFace }; const targetView = { ...base, targetCardFace: tgtFace }; const othersView = { ...base }; return { ownerViews: new Map([[atom.initiator, initiatorView], [atom.target, targetView]]), othersView }; }, applyView(view, event) { const initiator = event.initiator; const target = event.target; const initiatorCard = event.initiatorCard; const targetCard = event.targetCard; const removeOne = (playerIdx, cardId) => { const pi = view.players.findIndex((p) => p.index === playerIdx); if (pi < 0) return; view.players[pi].handCount = Math.max(0, view.players[pi].handCount - 1); if (cardId && view.players[pi].hand) view.players[pi].hand = view.players[pi].hand.filter((c) => c.id !== cardId); }; removeOne(initiator, initiatorCard); removeOne(target, targetCard); if (view.zones) { if (initiatorCard) view.zones.processing.push(initiatorCard); if (targetCard) view.zones.processing.push(targetCard); } const f = view.settlementStack[view.settlementStack.length - 1]; if (f) { if (initiatorCard) f.cards.push(initiatorCard); if (targetCard) f.cards.push(targetCard); } }, toViewLog(event, viewer) { const initiator = event.initiator; const target = event.target; const myFace = viewer === initiator ? event.initiatorCardFace : viewer === target ? event.targetCardFace : void 0; if (myFace) return { player: initiator, text: `拼点:扣置 ${myFace.suit}${myFace.rank}(面朝下)` }; return { player: initiator, text: "拼点:扣置一张牌(面朝下)" }; } }; 拼点亮出 = { type: "拼点亮出", validate: validateRankCompare, apply() {}, effect: { sound: "flip", animation: "flip", duration: 600 }, toViewEvents(state, atom) { const view = { type: "拼点亮出", initiator: atom.initiator, target: atom.target, initiatorCard: atom.initiatorCard, targetCard: atom.targetCard, initiatorCardFace: cardFace(state.cardMap[atom.initiatorCard]), targetCardFace: cardFace(state.cardMap[atom.targetCard]), effect: { sound: "flip", animation: "flip", duration: 600 } }; return { ownerViews: /* @__PURE__ */ new Map(), othersView: view }; }, applyView() {}, toViewLog(event) { const iFace = event.initiatorCardFace; const tFace = event.targetCardFace; const initiator = event.initiator; const target = event.target; return { player: initiator, text: `拼点亮出:${iFace ? `${iFace.suit}${iFace.rank}` : "?"} vs ${tFace ? `${tFace.suit}${tFace.rank}` : "?"}(目标 ${target})` }; } }; 拼点后 = { type: "拼点后", validate(state, atom) { if (!state.players[atom.initiator]) return `initiator ${atom.initiator} not found`; if (!state.players[atom.target]) return `target ${atom.target} not found`; return null; }, apply() {}, toViewEvents(state, atom) { if (getBeforeHooks(state, "拼点后").length === 0) return { ownerViews: /* @__PURE__ */ new Map(), othersView: null }; const view = { type: "拼点后", initiator: atom.initiator, target: atom.target, initiatorCard: atom.initiatorCard, targetCard: atom.targetCard, result: atom.result }; return { ownerViews: /* @__PURE__ */ new Map(), othersView: view }; }, applyView() {} }; })); //#endregion //#region src/engine/atoms/statechange-timing.ts /** 校验 player 存在(纯标记,不校验存活——编排函数前置保证)。 */ function validatePlayer(state, player) { if (!state.players[player]) return `player ${player} not found`; return null; } /** 单 player 标记型 atom 的视图:无 before hook 时静默,有则发通知事件。 */ function singlePlayerView(state, type, player) { if (getBeforeHooks(state, type).length === 0) return { ownerViews: /* @__PURE__ */ new Map(), othersView: null }; return { ownerViews: /* @__PURE__ */ new Map(), othersView: { type, player } }; } var 翻面后, 横置后, 武将牌明置后, 武将牌移除后, 游戏牌亮出后; var init_statechange_timing = __esmMin((() => { init_skill(); 翻面后 = { type: "翻面后", validate(state, atom) { return validatePlayer(state, atom.player); }, apply() {}, toViewEvents(state, atom) { if (getBeforeHooks(state, "翻面后").length === 0) return { ownerViews: /* @__PURE__ */ new Map(), othersView: null }; const view = { type: "翻面后", player: atom.player, faceDown: atom.faceDown }; return { ownerViews: /* @__PURE__ */ new Map(), othersView: view }; }, applyView() {} }; 横置后 = { type: "横置后", validate(state, atom) { return validatePlayer(state, atom.player); }, apply() {}, toViewEvents(state, atom) { if (getBeforeHooks(state, "横置后").length === 0) return { ownerViews: /* @__PURE__ */ new Map(), othersView: null }; const view = { type: "横置后", player: atom.player, chained: atom.chained }; return { ownerViews: /* @__PURE__ */ new Map(), othersView: view }; }, applyView() {} }; 武将牌明置后 = { type: "武将牌明置后", validate(state, atom) { return validatePlayer(state, atom.player); }, apply() {}, toViewEvents(state, atom) { return singlePlayerView(state, "武将牌明置后", atom.player); }, applyView() {} }; 武将牌移除后 = { type: "武将牌移除后", validate(state, atom) { return validatePlayer(state, atom.player); }, apply() {}, toViewEvents(state, atom) { return singlePlayerView(state, "武将牌移除后", atom.player); }, applyView() {} }; 游戏牌亮出后 = { type: "游戏牌亮出后", validate(state, atom) { if (!state.players[atom.player]) return `player ${atom.player} not found`; if (!state.cardMap[atom.cardId]) return `card ${atom.cardId} not found`; return null; }, apply() {}, toViewEvents(state, atom) { if (getBeforeHooks(state, "游戏牌亮出后").length === 0) return { ownerViews: /* @__PURE__ */ new Map(), othersView: null }; const view = { type: "游戏牌亮出后", player: atom.player, cardId: atom.cardId }; return { ownerViews: /* @__PURE__ */ new Map(), othersView: view }; }, applyView() {} }; })); //#endregion //#region src/engine/atoms/下一玩家.ts /** 计算下一个存活玩家的索引(apply 和 toViewEvents 共用) */ function calcNextPlayer(state) { const n = state.players.length; if (n === 0) return { nextIdx: 0, roundIncrement: false }; const prev = state.currentPlayerIndex; let nextIdx = (prev + 1) % n; let safety = n; while (!state.players[nextIdx].alive && safety > 0) { nextIdx = (nextIdx + 1) % n; safety--; } return { nextIdx, roundIncrement: nextIdx <= prev }; } var 下一玩家; var init_下一玩家 = __esmMin((() => { 下一玩家 = { type: "下一玩家", validate() { return null; }, apply(state) { const { nextIdx, roundIncrement } = calcNextPlayer(state); if (roundIncrement) state.turn.round += 1; state.currentPlayerIndex = nextIdx; }, toViewEvents(state, _atom) { const { nextIdx } = calcNextPlayer(state); return { ownerViews: /* @__PURE__ */ new Map(), othersView: { type: "下一玩家", newPlayer: nextIdx } }; }, applyView(view, event) { view.currentPlayerIndex = event.newPlayer; }, toViewLog() { return null; } }; })); //#endregion //#region src/engine/atoms/使用时.ts var 使用时; var init_使用时 = __esmMin((() => { 使用时 = { type: "使用时", validate(state, atom) { if (!state.players[atom.source]) return `source ${atom.source} not found`; return null; }, apply() {}, effect: { animation: "highlight", duration: 600 }, toViewEvents(state, atom) { const cardName = state.cardMap[atom.cardId]?.name ?? atom.cardId; const view = { type: "使用时", source: atom.source, cardId: atom.cardId, cardName, effect: { sound: `card/${cardName}`, animation: "highlight", duration: 600 } }; return { ownerViews: /* @__PURE__ */ new Map(), othersView: view }; }, applyView() {} }; })); //#endregion //#region src/engine/atoms/使用结算结束后.ts var 使用结算结束后; var init_使用结算结束后 = __esmMin((() => { init_skill(); 使用结算结束后 = { type: "使用结算结束后", validate(state, atom) { if (!state.players[atom.source]) return `source ${atom.source} not found`; return null; }, apply() {}, toViewEvents(state, atom) { if (getBeforeHooks(state, "使用结算结束后").length === 0 && getAfterHooks(state, "使用结算结束后").length === 0) return { ownerViews: /* @__PURE__ */ new Map(), othersView: null }; const view = { type: "使用结算结束后", source: atom.source, cardId: atom.cardId }; return { ownerViews: /* @__PURE__ */ new Map(), othersView: view }; }, applyView() {} }; })); //#endregion //#region src/engine/atoms/使用结算结束时.ts var 使用结算结束时; var init_使用结算结束时 = __esmMin((() => { init_skill(); 使用结算结束时 = { type: "使用结算结束时", validate(state, atom) { if (!state.players[atom.target]) return `target ${atom.target} not found`; return null; }, apply() {}, toViewEvents(state, atom) { if (getBeforeHooks(state, "使用结算结束时").length === 0) return { ownerViews: /* @__PURE__ */ new Map(), othersView: null }; const view = { type: "使用结算结束时", source: atom.source, target: atom.target, cardId: atom.cardId }; return { ownerViews: /* @__PURE__ */ new Map(), othersView: view }; }, applyView() {} }; })); //#endregion //#region src/engine/core/timeout.ts /** 计算应用了房间配置后的 pending 超时毫秒数。 * timeoutSec 为绝对秒数(房间配置):正值=秒数,0=无限(返回 24h,定时器实际不触发)。 * 未配置 timeoutSec(旧 state/测试)时回退到 atom 的 baseSeconds。 * slot 创建(createAndAwaitSlot)与 view deadline(applyView/toViewEvents)共用此函数, * 保证后端真实定时器与前端倒计时口径一致。 */ function resolveTimeoutMs(state, baseSeconds, isBroadcast = false) { const cfg = state.config?.timeoutSec; const MAX_TIMEOUT_MS = 864e5; if (cfg === void 0) return Math.min(baseSeconds * 1e3, MAX_TIMEOUT_MS); if (cfg <= 0) { if (isBroadcast) return Math.min(baseSeconds * 1e3, MAX_TIMEOUT_MS); return MAX_TIMEOUT_MS; } return Math.min(cfg * 1e3, MAX_TIMEOUT_MS); } var init_timeout = __esmMin((() => {})); //#endregion //#region src/engine/atoms/出牌窗口.ts var DEFAULT_TIMEOUT_SEC, 出牌窗口; var init_出牌窗口 = __esmMin((() => { init_apply(); init_timeout(); DEFAULT_TIMEOUT_SEC = 50; 出牌窗口 = { type: "出牌窗口", validate(state, atom) { if (!state.players[atom.player]) return `player not found`; return null; }, apply(_state) {}, pending: { async onTimeout(state, atom) { await applyAtom(state, { type: "阶段结束", player: atom.player, phase: "出牌" }); }, prompt: { type: "confirm", title: "出牌阶段", cancelLabel: "结束回合" }, timeout: DEFAULT_TIMEOUT_SEC, isBlocking: false }, effect: { blockUntilDone: true, duration: 200 }, toViewEvents(state, atom) { const timeoutMs = resolveTimeoutMs(state, DEFAULT_TIMEOUT_SEC); const playerView = { type: "出牌窗口", player: atom.player, timeoutMs }; const othersView = { type: "出牌窗口", player: atom.player, timeoutMs }; return { ownerViews: new Map([[atom.player, playerView]]), othersView }; }, applyView(view, event) { const player = event.player; const timeoutMs = event.timeoutMs ?? DEFAULT_TIMEOUT_SEC * 1e3; if (view.viewer === player) view.pending = { type: "awaits", atom: { type: "出牌窗口", player }, prompt: { type: "confirm", title: "出牌阶段", cancelLabel: "结束回合" }, target: player, isBlocking: false, deadline: Date.now() + timeoutMs, totalMs: timeoutMs }; else view.pending = { type: "awaits", atom: { type: "出牌窗口", player }, prompt: { type: "confirm", title: "等待出牌", cancelLabel: "" }, target: player, isBlocking: false, deadline: Date.now() + timeoutMs, totalMs: timeoutMs }; } }; })); //#endregion //#region src/engine/data/character-meta.ts /** 获取武将的基础身份(baseId):去掉版本前缀后的名字。 * 同一武将的不同版本共享 baseId,用于选将分组。 */ function getCharacterBaseId(name) { for (const prefix of VARIANT_PREFIXES) if (name.startsWith(prefix)) return name.slice(prefix.length); return name; } function getCharacterMeta(name) { return META.get(name); } /** 查询武将性别。未知武将默认 '男'(三国杀武将绝大多数为男性)。 */ function getGender(name) { return META.get(name)?.gender ?? "男"; } /** 查询武将是否是常备主公(拥有主公技)。 */ function isLord(name) { return META.get(name)?.isLord ?? false; } var LORD_CANDIDATES, VARIANT_PREFIXES, META, LORD_SKILLS; var init_character_meta = __esmMin((() => { init_characters(); LORD_CANDIDATES = [ "刘备", "曹操", "孙权", "孙策", "张角", "董卓", "刘禅" ]; VARIANT_PREFIXES = ["界", "SP"]; META = new Map(allCharacters.map((c) => { const src = c; return [src.name, { name: src.name, faction: src.faction, maxHealth: src.maxHealth, gender: src.gender, skills: src.skills.map((s) => s.name), isLord: src.isLord === true || LORD_CANDIDATES.includes(getCharacterBaseId(src.name)) }]; })); LORD_SKILLS = new Set([ "护驾", "界护驾", "激将", "界激将", "救援", "界救援", "制霸", "界制霸", "暴虐", "界暴虐", "黄天", "界黄天", "若愚", "界若愚", "界血裔" ]); })); //#endregion //#region src/engine/atoms/分配武将.ts /** 计算分配武将后的体力上限:基础值=武将卡牌 maxHealth。 * 主公加成:标准身份局(玩家数>4,即 5 人起)主公体力上限 +1。 * baseMax 缺失时 fallback 4(与 create() stub 默认值一致)。 */ function resolveMaxHealth(baseMax, identity, playerCount) { const base = baseMax ?? 4; return identity === "主公" && playerCount > 4 ? base + 1 : base; } var 分配武将; var init_分配武将 = __esmMin((() => { init_character_meta(); 分配武将 = { type: "分配武将", validate(state, atom) { if (!state.players[atom.target]) return `target ${atom.target} not found`; if (!atom.character) return "character required"; return null; }, apply(state, atom) { const p = state.players[atom.target]; if (!p) throw new Error(`分配武将: target ${atom.target} not found (validate 已通过)`); p.nickname ??= p.name; p.character = atom.character; p.name = atom.character; p.skills = atom.skills; const meta = getCharacterMeta(atom.character); if (meta) p.faction = meta.faction; const maxHp = resolveMaxHealth(meta?.maxHealth, p.identity, state.players.length); p.maxHealth = maxHp; p.health = maxHp; }, toViewEvents(state, atom) { const meta = getCharacterMeta(atom.character); const identity = state.players[atom.target]?.identity; const maxHp = resolveMaxHealth(meta?.maxHealth, identity, state.players.length); const view = { type: "分配武将", target: atom.target, character: atom.character, skills: atom.skills, faction: meta?.faction, maxHealth: maxHp, health: maxHp }; if (state.charSelecting) { const redacted = { type: "分配武将", target: atom.target }; return { ownerViews: new Map([[atom.target, view]]), othersView: redacted }; } return { ownerViews: /* @__PURE__ */ new Map(), othersView: view }; }, applyView(view, event) { const target = event.target; const character = event.character; if (!character) return; const skills = event.skills ?? []; const pi = view.players.findIndex((p) => p.index === target); if (pi < 0) return; view.players[pi].character = character; view.players[pi].name = character; view.players[pi].skills = skills; const faction = event.faction; if (faction) view.players[pi].faction = faction; const maxHp = event.maxHealth; if (typeof maxHp === "number") { view.players[pi].maxHealth = maxHp; view.players[pi].health = maxHp; } } }; })); //#endregion //#region src/engine/atoms/判定.ts var 判定; var init_判定 = __esmMin((() => { 判定 = { type: "判定", validate(state, atom) { if (!state.players[atom.player]) return `player ${atom.player} not found`; return null; }, apply(state) { if (state.zones.deck.length === 0) return; const topCardId = state.zones.deck.pop(); const frame = state.settlementStack[state.settlementStack.length - 1]; if (frame) frame.cards.push(topCardId); else state.zones.processing.push(topCardId); }, toViewEvents(state, atom) { const topCardId = state.zones.deck[state.zones.deck.length - 1]; const card = topCardId ? state.cardMap[topCardId] : void 0; const pendingTrick = state.players[atom.player]?.pendingTricks.find((t) => t.name === atom.judgeType); const view = { type: "判定", player: atom.player, judgeType: atom.judgeType, ...card ? { cardId: topCardId, card: { name: card.name, suit: card.suit, rank: card.rank } } : {}, ...pendingTrick ? { pendingCard: { name: pendingTrick.card.name, suit: pendingTrick.card.suit, rank: pendingTrick.card.rank } } : {} }; return { ownerViews: /* @__PURE__ */ new Map(), othersView: view }; }, effect: { sound: "flip", animation: "flip", blockUntilDone: true, duration: 1800 }, applyView(view, _event) { if (!view.zones) return; const cardId = _event.cardId; view.zones.deckCount = Math.max(0, view.zones.deckCount - 1); if (!cardId) return; const f = view.settlementStack[view.settlementStack.length - 1]; if (f) f.cards.push(cardId); view.zones.processing.push(cardId); }, toViewLog(event) { const card = event.card; const judgeType = event.judgeType ?? ""; if (card) return { player: event.player, text: `判定(${judgeType}):${card.suit ?? ""}${card.rank ?? ""} ${card.name ?? ""}` }; return { player: event.player, text: `判定(${judgeType})` }; } }; })); //#endregion //#region src/engine/core/frame.ts /** 创建帧并压入 state.settlementStack,返回帧引用。 * * 走 applyAtom({ type: '结算帧入栈' }) 管线,保证 view.settlementStack 与后端同步。 * 返回被压入的 frame 引用(从栈顶取,与入栈 atom apply 写入的是同一对象)。 * 变为 async(applyAtom 是 async);技能 execute 已是 async,加 await 即可。 */ async function pushFrame(state, skillId, from, params) { await applyAtom(state, { type: "结算帧入栈", skillId, from, params }); return state.settlementStack[state.settlementStack.length - 1]; } /** 弹出栈顶帧。 * * 走 applyAtom({ type: '结算帧出栈' }) 管线,保证 view.settlementStack 同步。 * 变为 async;技能 execute 加 await。 */ async function popFrame(state) { await applyAtom(state, { type: "结算帧出栈" }); } /** 取栈顶帧(只读引用) */ function topFrame(state) { return state.settlementStack[state.settlementStack.length - 1]; } /** 取栈顶帧的牌区(替代全局 zones.processing)。 * 无栈时回退到 state.zones.processing(仅用于无帧上下文的兼容场景)。 */ function frameCards(state) { const frame = state.settlementStack[state.settlementStack.length - 1]; return frame ? frame.cards : state.zones.processing; } /** 兜底空帧 */ function emptyFrame() { return { skillId: "", from: -1, params: Object.freeze({}), cards: [], cancelled: false }; } /** 取栈顶帧的 cancelled 状态。 */ function isCancelled(state, _cardId, _target) { return state.settlementStack[state.settlementStack.length - 1]?.cancelled === true; } /** 清除栈顶帧的抵消状态。 */ function clearCancelled(state, _cardId, _target) { const frame = state.settlementStack[state.settlementStack.length - 1]; if (frame) frame.cancelled = false; } var init_frame = __esmMin((() => { init_types(); init_apply(); })); //#endregion //#region src/engine/atoms/收取判定牌.ts var 收取判定牌; var init_收取判定牌 = __esmMin((() => { init_frame(); 收取判定牌 = { type: "收取判定牌", validate(state, atom) { if (!state.cardMap[atom.cardId]) return `card ${atom.cardId} not found`; if (!frameCards(state).includes(atom.cardId)) return `card ${atom.cardId} 不在处理区`; return null; }, apply(state, atom) { const frame = state.settlementStack[state.settlementStack.length - 1]; const cards = frame ? frame.cards : state.zones.processing; const idx = cards.indexOf(atom.cardId); if (idx >= 0) cards.splice(idx, 1); }, toViewEvents(_state, atom) { const view = { type: "收取判定牌", player: atom.player, cardId: atom.cardId }; return { ownerViews: /* @__PURE__ */ new Map(), othersView: view }; }, applyView(view, event) { const cardId = event.cardId; if (!cardId) return; const f = view.settlementStack[view.settlementStack.length - 1]; if (f) f.cards = f.cards.filter((id) => id !== cardId); if (view.zones) view.zones.processing = view.zones.processing.filter((id) => id !== cardId); } }; })); //#endregion //#region src/engine/atoms/加标签.ts var 加标签; var init_加标签 = __esmMin((() => { 加标签 = { type: "加标签", validate(state, atom) { if (!state.players[atom.player]) return `player ${atom.player} not found`; return null; }, apply(state, atom) { const player = state.players[atom.player]; if (!player.tags.includes(atom.tag)) player.tags.push(atom.tag); } }; })); //#endregion //#region src/engine/atoms/加标记.ts var 加标记; var init_加标记 = __esmMin((() => { 加标记 = { type: "加标记", validate(state, atom) { if (!state.players[atom.player]) return `player ${atom.player} not found`; return null; }, apply(state, atom) { state.players[atom.player].marks.push(atom.mark); }, effect: { sound: "chain", animation: "pulse", duration: 400 }, toViewEvents(_state, atom) { const view = { type: "加标记", player: atom.player, mark: atom.mark }; if (atom.distanceVars) view.distanceVars = atom.distanceVars; return { ownerViews: /* @__PURE__ */ new Map(), othersView: view }; }, applyView(view, event) { const pi = view.players.findIndex((p) => p.index === event.player); if (pi < 0) return; const mark = event.mark; if (mark) view.players[pi].marks.push(mark); const dv = event.distanceVars; if (dv) view.players[pi].distanceVars = { ...view.players[pi].distanceVars, ...dv }; } }; })); //#endregion //#region src/engine/atoms/卸下.ts /** 清除装备带来的 vars(仅武器范围;马匹由技能处理) */ function clearEquipVars(state, playerIdx, slot) { const vars = state.players[playerIdx].vars; if (slot === "武器") delete vars[DISTANCE_ATTACK_RANGE_KEY]; } var 卸下; var init_卸下 = __esmMin((() => { init_vars_keys(); 卸下 = { type: "卸下", validate(state, atom) { const p = state.players[atom.player]; if (!p) return `player ${atom.player} not found`; if (!p.equipment[atom.slot]) return `no equipment in slot ${atom.slot}`; return null; }, apply(state, atom) { const player = state.players[atom.player]; const cardId = player.equipment[atom.slot]; delete player.equipment[atom.slot]; player.hand.push(cardId); clearEquipVars(state, atom.player, atom.slot); }, effect: { sound: "unequip", animation: "fade", duration: 400 }, toViewEvents(state, atom) { const cardId = state.players[atom.player].equipment[atom.slot]; const view = { type: "卸下", player: atom.player, slot: atom.slot, ...cardId ? { cardId } : {} }; return { ownerViews: /* @__PURE__ */ new Map(), othersView: view }; }, applyView(view, event) { const pi = view.players.findIndex((p) => p.index === event.player); if (pi < 0) return; const slot = event.slot; const cardId = event.cardId; if (slot) delete view.players[pi].equipment[slot]; if (cardId) { view.players[pi].handCount += 1; if (view.players[pi].hand) { const card = view.cardMap[cardId]; if (card) view.players[pi].hand.push(card); } } if (slot === "武器" && view.players[pi].distanceVars) view.players[pi].distanceVars = { ...view.players[pi].distanceVars, attackRange: void 0 }; } }; })); //#endregion //#region src/engine/atoms/去标签.ts var 去标签; var init_去标签 = __esmMin((() => { 去标签 = { type: "去标签", validate(state, atom) { if (!state.players[atom.player]) return `player ${atom.player} not found`; return null; }, apply(state, atom) { const player = state.players[atom.player]; player.tags = player.tags.filter((t) => t !== atom.tag); } }; })); //#endregion //#region src/engine/atoms/去标记.ts var 去标记; var init_去标记 = __esmMin((() => { 去标记 = { type: "去标记", validate(state, atom) { if (!state.players[atom.player]) return `player ${atom.player} not found`; return null; }, apply(state, atom) { const player = state.players[atom.player]; const idx = player.marks.findIndex((m) => m.id === atom.markId); if (idx < 0) return; player.marks.splice(idx, 1); }, effect: { sound: "chain", animation: "pulse", duration: 300 }, toViewEvents(_state, atom) { const view = { type: "去标记", player: atom.player, markId: atom.markId }; if (atom.distanceVars) view.distanceVars = atom.distanceVars; return { ownerViews: /* @__PURE__ */ new Map(), othersView: view }; }, applyView(view, event) { const pi = view.players.findIndex((p) => p.index === event.player); if (pi < 0) return; const markId = event.markId; if (markId) view.players[pi].marks = view.players[pi].marks.filter((m) => m.id !== markId); const dv = event.distanceVars; if (dv) view.players[pi].distanceVars = { ...view.players[pi].distanceVars, ...dv }; } }; })); //#endregion //#region src/engine/atoms/回合开始.ts var 回合开始; var init_回合开始 = __esmMin((() => { 回合开始 = { type: "回合开始", validate(state, atom) { if (!state.players[atom.player]) return `player ${atom.player} not found`; return null; }, apply(_state) {}, toViewEvents(state, atom) { const view = { type: "回合开始", player: atom.player, round: state.turn.round }; return { ownerViews: /* @__PURE__ */ new Map(), othersView: view }; }, effect: { sound: "turn_start", duration: 1500 }, applyView(view, event) { view.currentPlayerIndex = event.player; if (typeof event.round === "number") view.turn.round = event.round; }, toViewLog(event) { return { player: event.player, text: "回合开始" }; } }; })); //#endregion //#region src/engine/atoms/回合用量.ts var 回合用量; var init_回合用量 = __esmMin((() => { init_vars_keys(); 回合用量 = { type: "回合用量", validate(state, atom) { if (!state.players[atom.player]) return `player ${atom.player} not found`; return null; }, apply(state, atom) { if ((atom.key.startsWith("杀/") || TURN_SCOPED_VIEW_KEYS.includes(atom.key)) && atom.player === state.currentPlayerIndex) state.turn.vars[VIEW_MIRROR_PREFIX + atom.key] = atom.value; }, toViewEvents(_state, atom) { const view = { type: "回合用量", player: atom.player, key: atom.key, value: atom.value }; return { ownerViews: /* @__PURE__ */ new Map(), othersView: view }; }, applyView(view, event) { const p = view.players[event.player]; if (!p) return; p.turnUsage ??= {}; p.turnUsage[event.key] = event.value; } }; })); //#endregion //#region src/engine/atoms/回合结束.ts var 回合结束; var init_回合结束 = __esmMin((() => { 回合结束 = { type: "回合结束", validate(state, atom) { if (!state.players[atom.player]) return `player ${atom.player} not found`; return null; }, apply(state) { state.turn.vars = {}; for (const p of state.players) { p.marks = p.marks.filter((m) => m.duration !== "turn"); p.vars = Object.fromEntries(Object.entries(p.vars).filter(([k]) => !k.endsWith("/usedThisTurn") && !k.endsWith("/healed") && !k.endsWith("/givenCount") && !k.endsWith("/givenTargets") && !k.endsWith("/basicUsed"))); } }, toViewEvents(_state, atom) { const view = { type: "回合结束", player: atom.player }; return { ownerViews: /* @__PURE__ */ new Map(), othersView: view }; }, effect: { sound: "turn_end", duration: 800 }, applyView(view) { view.turn.vars = {}; for (const p of view.players) { p.marks = p.marks.filter((m) => m.duration !== "turn"); p.turnUsage = {}; } }, toViewLog(event) { return { player: event.player, text: "回合结束" }; } }; })); //#endregion //#region src/engine/atoms/回合结束后.ts var 回合结束后; var init_回合结束后 = __esmMin((() => { 回合结束后 = { type: "回合结束后", validate(state, atom) { if (!state.players[atom.player]) return `player ${atom.player} not found`; return null; }, apply() {}, toViewEvents() { return { ownerViews: /* @__PURE__ */ new Map(), othersView: null }; }, applyView() {} }; })); //#endregion //#region src/engine/atoms/回复体力.ts var 回复体力; var init_回复体力 = __esmMin((() => { 回复体力 = { type: "回复体力", validate(state, atom) { if (atom.amount <= 0) return "amount must be > 0"; const p = state.players[atom.target]; if (!p) return `target ${atom.target} not found`; if (!p.alive) return `target is dead`; return null; }, apply(state, atom) { const target = state.players[atom.target]; target.health = Math.min(target.maxHealth, target.health + atom.amount); }, effect: { sound: "heal", particles: "ice", duration: 800 }, toViewEvents(_state, atom) { const view = { type: "回复体力", target: atom.target, amount: atom.amount }; return { ownerViews: /* @__PURE__ */ new Map(), othersView: view }; }, applyView(view, event) { const pi = view.players.findIndex((p) => p.index === event.target); if (pi < 0) return; const p = view.players[pi]; p.health = Math.min(p.maxHealth, p.health + event.amount); }, toViewLog(event) { return { player: event.target, text: `回复 ${event.amount ?? 0} 点体力` }; } }; })); //#endregion //#region src/engine/atoms/声明打出时.ts var 声明打出时; var init_声明打出时 = __esmMin((() => { 声明打出时 = { type: "声明打出时", validate(state, atom) { if (!state.players[atom.player]) return `player ${atom.player} not found`; return null; }, apply() {}, effect: { animation: "highlight", duration: 400 }, toViewEvents(state, atom) { const cardName = state.cardMap[atom.cardId]?.name ?? atom.cardId; const view = { type: "声明打出时", player: atom.player, cardId: atom.cardId, cardName }; return { ownerViews: /* @__PURE__ */ new Map(), othersView: view }; }, applyView() {} }; })); //#endregion //#region src/engine/atoms/失去体力.ts var 失去体力; var init_失去体力 = __esmMin((() => { 失去体力 = { type: "失去体力", validate(state, atom) { if (atom.amount <= 0) return "amount must be > 0"; const p = state.players[atom.target]; if (!p) return `target ${atom.target} not found`; if (!p.alive) return `target is dead`; return null; }, apply(state, atom) { const target = state.players[atom.target]; target.health = target.health - atom.amount; }, effect: { sound: "lose_health", animation: "shake", duration: 800 }, toViewEvents(_state, atom) { const view = { type: "失去体力", target: atom.target, amount: atom.amount }; return { ownerViews: /* @__PURE__ */ new Map(), othersView: view }; }, applyView(view, event) { const pi = view.players.findIndex((p) => p.index === event.target); if (pi < 0) return; const p = view.players[pi]; p.health = p.health - event.amount; }, toViewLog(event) { return { player: event.target, text: `失去 ${event.amount ?? 0} 点体力` }; } }; })); //#endregion //#region src/engine/atoms/展示.ts var REVEAL_ENTER_MS, 展示; var init_展示 = __esmMin((() => { REVEAL_ENTER_MS = 700; 展示 = { type: "展示", validate(state, atom) { if (!state.players[atom.player]) return `player ${atom.player} not found`; if (!state.cardMap[atom.cardId]) return `card ${atom.cardId} not found`; return null; }, apply() {}, effect: { sound: "flip", animation: "flip", duration: REVEAL_ENTER_MS }, toViewEvents(state, atom) { const card = state.cardMap[atom.cardId]; const cardInfo = card ? { name: card.name, suit: card.suit, rank: card.rank } : null; const effect = { sound: "flip", animation: "flip", duration: REVEAL_ENTER_MS }; const view = { type: "展示", player: atom.player, cardId: atom.cardId, card: cardInfo, effect }; return { ownerViews: /* @__PURE__ */ new Map(), othersView: view }; }, applyView() {}, toViewLog(event) { const c = event.card; if (c) return { player: event.player, text: `展示:${c.suit}${c.rank}(${c.name})` }; return { player: event.player, text: `展示` }; } }; })); //#endregion //#region src/engine/atoms/展示结束.ts var 展示结束; var init_展示结束 = __esmMin((() => { 展示结束 = { type: "展示结束", validate(state, atom) { if (!state.players[atom.player]) return `player ${atom.player} not found`; if (!state.cardMap[atom.cardId]) return `card ${atom.cardId} not found`; return null; }, apply() {}, toViewEvents(_state, atom) { const view = { type: "展示结束", player: atom.player, cardId: atom.cardId }; return { ownerViews: /* @__PURE__ */ new Map(), othersView: view }; }, applyView() {} }; })); //#endregion //#region src/engine/atoms/帧参数赋值.ts var 帧参数赋值; var init_帧参数赋值 = __esmMin((() => { 帧参数赋值 = { type: "帧参数赋值", validate(state) { if (state.settlementStack.length === 0) return "settlementStack 为空,无栈顶帧可赋值"; return null; }, apply(state, atom) { const frame = state.settlementStack[state.settlementStack.length - 1]; frame.params[atom.key] = atom.value; }, toViewEvents(_state, atom) { const view = { type: "帧参数赋值", key: atom.key, value: atom.value }; return { ownerViews: /* @__PURE__ */ new Map(), othersView: view }; }, applyView(view, event) { const frame = view.settlementStack[view.settlementStack.length - 1]; if (frame) frame.params[event.key] = event.value; } }; })); //#endregion //#region src/engine/atoms/弃置.ts var MOVED_HAND_KEY, 弃置; var init_弃置 = __esmMin((() => { init_apply(); MOVED_HAND_KEY = "__弃置/手牌卡"; 弃置 = { type: "弃置", validate(state, atom) { if (atom.cardIds.length === 0) return "no cards to discard"; if (!state.players[atom.player]) return `player ${atom.player} not found`; return null; }, apply(state, atom) { const player = state.players[atom.player]; const discardSet = new Set(atom.cardIds); state.localVars[MOVED_HAND_KEY] = atom.cardIds.filter((id) => player.hand.includes(id)); player.hand = player.hand.filter((id) => !discardSet.has(id)); const equipment = {}; for (const [slot, id] of Object.entries(player.equipment)) if (id && !discardSet.has(id)) equipment[slot] = id; player.equipment = equipment; state.zones.discardPile.push(...atom.cardIds); }, /** 模块 F:为来自手牌的弃牌逐张发出「移动到目标区域后」时机标记(reason='弃置')。 * 标记为事件型(无副作用、无 view),不改变本 atom 的视图/状态语义。 */ async afterApply(state, atom) { const handCardIds = state.localVars[MOVED_HAND_KEY]; delete state.localVars[MOVED_HAND_KEY]; if (!handCardIds || handCardIds.length === 0) return; for (const cardId of handCardIds) await applyAtom(state, { type: "移动到目标区域后", cardId, from: { zone: "手牌", player: atom.player }, to: { zone: "弃牌堆" }, reason: "弃置" }); }, effect: { sound: "flip", animation: "flip", duration: 600 }, toViewEvents(state, atom) { const player = state.players[atom.player]; const equipSet = new Set(Object.values(player.equipment).filter(Boolean)); const zones = {}; for (const cardId of atom.cardIds) if (player.hand.includes(cardId)) zones[cardId] = "hand"; else if (equipSet.has(cardId)) zones[cardId] = "equipment"; else zones[cardId] = "judge"; const view = { type: "弃置", player: atom.player, cardIds: atom.cardIds, zones, cardNames: atom.cardIds.map((id) => state.cardMap[id]?.name).filter(Boolean) }; return { ownerViews: /* @__PURE__ */ new Map(), othersView: view }; }, applyView(view, event) { const pi = view.players.findIndex((p) => p.index === event.player); if (pi < 0) return; const cardIds = event.cardIds ?? []; const zones = event.zones ?? {}; const discardSet = new Set(cardIds); const equipment = {}; for (const [slot, id] of Object.entries(view.players[pi].equipment)) if (id && discardSet.has(id)) {} else if (id) equipment[slot] = id; view.players[pi].equipment = equipment; const handRemoved = cardIds.filter((id) => zones[id] === "hand").length; view.players[pi].handCount = Math.max(0, view.players[pi].handCount - handRemoved); if (view.players[pi].hand && handRemoved > 0) view.players[pi].hand = view.players[pi].hand.filter((c) => !discardSet.has(c.id)); if (view.zones) view.zones.discardPileCount += cardIds.length; }, toViewLog(event, _viewer) { const cardIds = event.cardIds; const count = Array.isArray(cardIds) ? cardIds.length : 0; const cardNames = event.cardNames; if (cardNames && cardNames.length > 0) { const names = cardNames.join("、"); return { player: event.player, text: `弃置了 ${count} 张牌:${names}` }; } return { player: event.player, text: `弃置了 ${count} 张牌` }; } }; })); //#endregion //#region src/engine/atoms/归还暂存牌.ts var 归还暂存牌; var init_归还暂存牌 = __esmMin((() => { 归还暂存牌 = { type: "归还暂存牌", validate(state, atom) { if (!state.players[atom.player]) return `player ${atom.player} not found`; if (typeof atom.varsKey !== "string" || atom.varsKey.length === 0) return `varsKey 不能为空`; if ((state.players[atom.player].vars[atom.varsKey] ?? []).length === 0) return `player ${atom.player} 的 vars['${atom.varsKey}'] 无暂存牌可归还`; return null; }, apply(state, atom) { const player = state.players[atom.player]; const list = player.vars[atom.varsKey] ?? []; delete player.vars[atom.varsKey]; if (!player.alive) { for (const id of list) state.zones.discardPile.push(id); return; } for (const id of list) if (!player.hand.includes(id)) player.hand.push(id); }, effect: { sound: "flip", animation: "slide", duration: 600 }, toViewEvents(state, atom) { const list = state.players[atom.player].vars[atom.varsKey] ?? []; const alive = state.players[atom.player].alive === true; const cardInfos = list.map((id) => state.cardMap[id]).filter((c) => !!c).map((c) => ({ id: c.id, name: c.name, suit: c.suit, rank: c.rank })); if (!alive) { const view = { type: "归还暂存牌", player: atom.player, count: list.length, cards: cardInfos, toDiscard: true }; return { ownerViews: /* @__PURE__ */ new Map(), othersView: view }; } const ownerView = { type: "归还暂存牌", player: atom.player, cards: cardInfos }; const othersView = { type: "归还暂存牌", player: atom.player, count: list.length }; return { ownerViews: new Map([[atom.player, ownerView]]), othersView }; }, applyView(view, event) { const pi = view.players.findIndex((p) => p.index === event.player); if (pi < 0) return; const cards = event.cards; const count = cards ? cards.length : event.count ?? 0; if (event.toDiscard) { if (view.zones) view.zones.discardPileCount += count; return; } view.players[pi].handCount += count; if (cards && view.players[pi].hand) for (const c of cards) { const card = view.cardMap[c.id]; if (card) { if (!view.players[pi].hand.some((x) => x.id === c.id)) view.players[pi].hand.push(card); } } }, toViewLog(event) { const count = event.cards?.length ?? event.count ?? 0; return { player: event.player, text: `归还 ${count} 张移出的牌` }; } }; })); //#endregion //#region src/engine/data/card-defs/basic.ts var 杀, 闪, 桃, 酒, 基本牌列表; var init_basic = __esmMin((() => { 杀 = { name: "杀", type: "基本牌", subtype: "杀", targetFilter: { type: "inRange" }, effect: { type: "damage", amount: 1 }, responseWindow: "kill_response", usageLimit: { perTurn: 1 } }; 闪 = { name: "闪", type: "基本牌", subtype: "闪", targetFilter: { type: "none" }, effect: { type: "sequence", steps: [] } }; 桃 = { name: "桃", type: "基本牌", subtype: "桃", targetFilter: { type: "self" }, effect: { type: "回复体力", amount: 1 } }; 酒 = { name: "酒", type: "基本牌", subtype: "酒", targetFilter: { type: "self" }, effect: { type: "sequence", steps: [] } }; 基本牌列表 = [ 杀, 闪, 桃, 酒 ]; })); //#endregion //#region src/engine/data/card-defs/tricks.ts var 过河拆桥, 顺手牵羊, 无中生有, 决斗, 万箭齐发, 南蛮入侵, 桃园结义, 五谷丰登, 乐不思蜀, 兵粮寸断, 闪电, 无懈可击, 铁索连环, 火攻, 借刀杀人, 锦囊牌列表; var init_tricks = __esmMin((() => { 过河拆桥 = { name: "过河拆桥", type: "锦囊牌", subtype: "锦囊", targetFilter: { type: "other", condition: (p) => p.hand.length > 0 }, effect: { type: "弃置", source: "hand", count: 1, target: "selected" }, responseWindow: "trick_response" }; 顺手牵羊 = { name: "顺手牵羊", type: "锦囊牌", subtype: "锦囊", targetFilter: { type: "inRange" }, effect: { type: "获得", from: "player", source: "selected" }, responseWindow: "trick_response" }; 无中生有 = { name: "无中生有", type: "锦囊牌", subtype: "锦囊", targetFilter: { type: "self" }, effect: { type: "摸牌", count: 2 }, responseWindow: "trick_response" }; 决斗 = { name: "决斗", type: "锦囊牌", subtype: "锦囊", targetFilter: { type: "other" }, effect: { type: "damage", amount: 1 }, responseWindow: "trick_response" }; 万箭齐发 = { name: "万箭齐发", type: "锦囊牌", subtype: "锦囊", targetFilter: { type: "none" }, effect: { type: "damage", amount: 1 }, aoeResponse: "闪" }; 南蛮入侵 = { name: "南蛮入侵", type: "锦囊牌", subtype: "锦囊", targetFilter: { type: "none" }, effect: { type: "damage", amount: 1 }, aoeResponse: "杀" }; 桃园结义 = { name: "桃园结义", type: "锦囊牌", subtype: "锦囊", targetFilter: { type: "none" }, effect: { type: "回复体力", amount: 1 } }; 五谷丰登 = { name: "五谷丰登", type: "锦囊牌", subtype: "锦囊", targetFilter: { type: "none" }, effect: { type: "摸牌", count: 1 } }; 乐不思蜀 = { name: "乐不思蜀", type: "锦囊牌", subtype: "锦囊", targetFilter: { type: "other" }, effect: { type: "添加延时锦囊", trickName: "乐不思蜀", target: "selected" }, responseWindow: "trick_response" }; 兵粮寸断 = { name: "兵粮寸断", type: "锦囊牌", subtype: "锦囊", targetFilter: { type: "other" }, effect: { type: "添加延时锦囊", trickName: "兵粮寸断", target: "selected" }, responseWindow: "trick_response" }; 闪电 = { name: "闪电", type: "锦囊牌", subtype: "锦囊", targetFilter: { type: "self" }, effect: { type: "添加延时锦囊", trickName: "闪电", target: "self" } }; 无懈可击 = { name: "无懈可击", type: "锦囊牌", subtype: "锦囊", targetFilter: { type: "none" }, effect: { type: "sequence", steps: [] } }; 铁索连环 = { name: "铁索连环", type: "锦囊牌", subtype: "锦囊", targetFilter: { type: "other" }, effect: { type: "sequence", steps: [] }, responseWindow: "trick_response" }; 火攻 = { name: "火攻", type: "锦囊牌", subtype: "锦囊", targetFilter: { type: "other", condition: (p) => p.hand.length > 0 }, effect: { type: "damage", amount: 1, damageType: "火焰" }, responseWindow: "trick_response" }; 借刀杀人 = { name: "借刀杀人", type: "锦囊牌", subtype: "锦囊", targetFilter: { type: "other" }, effect: { type: "sequence", steps: [] }, responseWindow: "trick_response" }; 锦囊牌列表 = [ 过河拆桥, 顺手牵羊, 无中生有, 决斗, 万箭齐发, 南蛮入侵, 桃园结义, 五谷丰登, 乐不思蜀, 兵粮寸断, 闪电, 无懈可击, 铁索连环, 火攻, 借刀杀人 ]; })); //#endregion //#region src/engine/data/card-defs/index.ts var init_card_defs = __esmMin((() => { init_basic(); init_tricks(); init_equipment(); })); //#endregion //#region src/engine/atoms/当作.ts /** 根据转化后的牌名推断卡牌类型。 * 从卡牌定义派生的静态映射查询;未知牌名默认 '基本牌'(常见转化场景)。 */ function inferCardType(name) { return CARD_TYPE_MAP.get(name) ?? "基本牌"; } /** * 转化后牌的颜色: * - 单张:继承原卡颜色 * - 多张:全同色→该色;含异色→'无色' */ function transformColor(cardIds, cardMap) { if (cardIds.length === 1) return cardMap[cardIds[0]]?.color ?? "无色"; let color; for (const id of cardIds) { const c = cardMap[id]?.color; if (!c) return "无色"; if (color === void 0) color = c; else if (color !== c) return "无色"; } return color ?? "无色"; } var CARD_TYPE_MAP, 当作; var init_当作 = __esmMin((() => { init_card_defs(); CARD_TYPE_MAP = (() => { const map = /* @__PURE__ */ new Map(); for (const def of [ ...基本牌列表, ...锦囊牌列表, ...装备牌列表 ]) map.set(def.name, def.type); return map; })(); 当作 = { type: "当作", validate(state, atom) { const self = state.players[atom.player]; if (!self) return `player ${atom.player} not found`; if (!self.alive) return "player not alive"; if (!atom.cardIds.every((id) => self.hand.includes(id))) return "cards not in hand"; if (!atom.cardIds.every((id) => state.cardMap[id])) return "cards not found"; return null; }, apply(state, atom) { const self = state.players[atom.player]; const firstCard = state.cardMap[atom.cardIds[0]]; const single = atom.cardIds.length === 1; const shadow = { id: atom.shadowId, name: atom.outputName, suit: single ? firstCard.suit : "", color: transformColor(atom.cardIds, state.cardMap), rank: firstCard.rank, type: inferCardType(atom.outputName), shadowOf: single ? atom.cardIds[0] : void 0 }; if (atom.outputDamageType) shadow.damageType = atom.outputDamageType; state.cardMap[atom.shadowId] = shadow; self.hand = self.hand.filter((c) => !atom.cardIds.includes(c)); self.hand.push(atom.shadowId); }, effect: { sound: "flip", animation: "flash", duration: 400 }, toViewEvents(_state, atom) { const effect = { sound: "flip", animation: "flash", duration: 400 }; const ownerView = { type: "当作", player: atom.player, cardIds: atom.cardIds, shadowId: atom.shadowId, outputName: atom.outputName, outputDamageType: atom.outputDamageType, effect }; const othersView = { type: "当作", player: atom.player, count: atom.cardIds.length, effect }; return { ownerViews: new Map([[atom.player, ownerView]]), othersView }; }, applyView(view, event) { const playerIdx = event.player; const pi = view.players.findIndex((p) => p.index === playerIdx); if (pi < 0) return; const ev = event; const cardIds = ev.cardIds; const shadowId = ev.shadowId; const count = ev.count ?? cardIds?.length ?? 1; const outputName = typeof ev.outputName === "string" ? ev.outputName : "杀"; view.players[pi].handCount = Math.max(0, view.players[pi].handCount - (count - 1)); if (cardIds && shadowId && view.players[pi].hand) { let shadowCard = view.cardMap[shadowId]; if (!shadowCard) { const origCard = view.cardMap[cardIds[0]]; const single = count === 1; const outputDamageType = ev.outputDamageType; shadowCard = { id: shadowId, name: outputName, suit: single ? origCard?.suit ?? "" : "", color: transformColor(cardIds, view.cardMap), rank: origCard?.rank ?? "A", type: inferCardType(outputName), shadowOf: single ? cardIds[0] : void 0 }; if (outputDamageType) shadowCard.damageType = outputDamageType; view.cardMap[shadowId] = shadowCard; } const idSet = new Set(cardIds); view.players[pi].hand = view.players[pi].hand.filter((c) => !idSet.has(c.id)); view.players[pi].hand.push(shadowCard); } }, toViewLog(event) { const ev = event; return { player: event.player, text: `使用转化出${typeof ev.outputName === "string" ? ev.outputName : "牌"}` }; } }; })); //#endregion //#region src/engine/atoms/成为目标.ts var 成为目标; var init_成为目标 = __esmMin((() => { 成为目标 = { type: "成为目标", validate(state, atom) { if (!state.players[atom.source]) return `source ${atom.source} not found`; if (!state.players[atom.target]) return `target ${atom.target} not found`; return null; }, apply(_state) {}, effect: { animation: "highlight", duration: 400 }, toViewEvents(state, atom) { const cardName = atom.cardId ? state.cardMap[atom.cardId]?.name ?? atom.cardId : void 0; const view = { type: "成为目标", source: atom.source, target: atom.target, ...atom.cardId !== void 0 ? { cardId: atom.cardId } : {}, ...cardName !== void 0 ? { cardName } : {} }; return { ownerViews: /* @__PURE__ */ new Map(), othersView: view }; }, applyView(_view, _event) {} }; })); //#endregion //#region src/engine/atoms/成为目标后.ts var 成为目标后; var init_成为目标后 = __esmMin((() => { 成为目标后 = { type: "成为目标后", validate(state, atom) { if (!state.players[atom.source]) return `source ${atom.source} not found`; if (!state.players[atom.target]) return `target ${atom.target} not found`; return null; }, apply() {}, effect: { animation: "highlight", duration: 400 }, toViewEvents(state, atom) { const cardName = atom.cardId ? state.cardMap[atom.cardId]?.name ?? atom.cardId : void 0; const view = { type: "成为目标后", source: atom.source, target: atom.target, ...atom.cardId !== void 0 ? { cardId: atom.cardId } : {}, ...cardName !== void 0 ? { cardName } : {} }; return { ownerViews: /* @__PURE__ */ new Map(), othersView: view }; }, applyView() {} }; })); //#endregion //#region src/engine/atoms/打出牌时.ts var 打出牌时; var init_打出牌时 = __esmMin((() => { 打出牌时 = { type: "打出牌时", validate(state, atom) { if (!state.players[atom.player]) return `player ${atom.player} not found`; return null; }, apply() {}, effect: { animation: "highlight", duration: 400 }, toViewEvents(state, atom) { const cardName = state.cardMap[atom.cardId]?.name ?? atom.cardId; const view = { type: "打出牌时", player: atom.player, cardId: atom.cardId, cardName, effect: { sound: `card/${cardName}`, animation: "highlight", duration: 400 } }; return { ownerViews: /* @__PURE__ */ new Map(), othersView: view }; }, applyView() {} }; })); //#endregion //#region src/engine/atoms/扣牌.ts var 扣牌; var init_扣牌 = __esmMin((() => { 扣牌 = { type: "扣牌", validate(state, atom) { if (!state.players[atom.player]) return `player ${atom.player} not found`; if (!state.cardMap[atom.cardId]) return `card ${atom.cardId} not found`; if (!state.players[atom.player].hand.includes(atom.cardId)) return `card not in hand`; return null; }, apply(state, atom) { const p = state.players[atom.player]; p.hand = p.hand.filter((id) => id !== atom.cardId); state.zones.discardPile.push(atom.cardId); state.localVars["蛊惑/扣牌"] = atom.cardId; state.localVars["蛊惑/声明"] = atom.declaredName; }, effect: { sound: "flip", animation: "flip", duration: 800 }, toViewEvents(state, atom) { const card = state.cardMap[atom.cardId]; const cardInfo = card ? { name: card.name, suit: card.suit, rank: card.rank } : null; const effect = { sound: "flip", animation: "flip", duration: 800 }; const ownerView = { type: "扣牌", player: atom.player, cardId: atom.cardId, card: cardInfo, declaredName: atom.declaredName, effect }; const othersView = { type: "扣牌", player: atom.player, declaredName: atom.declaredName, effect }; return { ownerViews: new Map([[atom.player, ownerView]]), othersView }; }, applyView(view, event) { const pi = view.players.findIndex((p) => p.index === event.player); if (pi >= 0) { view.players[pi].handCount = Math.max(0, view.players[pi].handCount - 1); const cardId = event.cardId; if (cardId && view.players[pi].hand) view.players[pi].hand = view.players[pi].hand.filter((c) => c.id !== cardId); } if (view.zones) view.zones.discardPileCount += 1; }, toViewLog(event, viewer) { const declared = event.declaredName; if (viewer === event.player && event.card) { const c = event.card; return { player: event.player, text: `蛊惑:扣置 ${c.suit}${c.rank},声明为【${declared}】` }; } return { player: event.player, text: `蛊惑:扣置一张手牌,声明为【${declared}】` }; } }; })); //#endregion //#region src/engine/atoms/指定目标.ts var 指定目标; var init_指定目标 = __esmMin((() => { 指定目标 = { type: "指定目标", validate(state, atom) { if (!state.players[atom.source]) return `source ${atom.source} not found`; if (!state.players[atom.target]) return `target ${atom.target} not found`; return null; }, apply(_state) {}, effect: { animation: "highlight", duration: 400 }, toViewEvents(state, atom) { const cardName = atom.cardId ? state.cardMap[atom.cardId]?.name ?? atom.cardId : void 0; const view = { type: "指定目标", source: atom.source, target: atom.target, ...atom.cardId !== void 0 ? { cardId: atom.cardId } : {}, ...cardName !== void 0 ? { cardName } : {} }; return { ownerViews: /* @__PURE__ */ new Map(), othersView: view }; }, applyView(_view, _event) {}, toViewLog(event, _viewer, resolveName) { const target = event.target; const targetName = resolveName?.(target) ?? `P${target}`; const source = event.source; resolveName?.(source) ?? `${source}`; const cardName = event.cardName ?? event.cardId; return { player: source, text: `${cardName ? `使用 ${cardName} ` : ""}指定 ${targetName} 为目标` }; } }; })); //#endregion //#region src/engine/atoms/指定目标后.ts var 指定目标后; var init_指定目标后 = __esmMin((() => { 指定目标后 = { type: "指定目标后", validate(state, atom) { if (!state.players[atom.source]) return `source ${atom.source} not found`; if (!state.players[atom.target]) return `target ${atom.target} not found`; return null; }, apply() {}, effect: { animation: "highlight", duration: 400 }, toViewEvents(state, atom) { const cardName = atom.cardId ? state.cardMap[atom.cardId]?.name ?? atom.cardId : void 0; const view = { type: "指定目标后", source: atom.source, target: atom.target, ...atom.cardId !== void 0 ? { cardId: atom.cardId } : {}, ...cardName !== void 0 ? { cardName } : {} }; return { ownerViews: /* @__PURE__ */ new Map(), othersView: view }; }, applyView() {} }; })); //#endregion //#region src/engine/util/rng.ts function createRng(seed) { let state = seed | 0; return { next() { state = state + 1831565813 | 0; let t = Math.imul(state ^ state >>> 15, 1 | state); t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t; return ((t ^ t >>> 14) >>> 0) / 4294967296; }, nextInt(max) { return Math.floor(this.next() * max); }, getState() { return state; } }; } var init_rng = __esmMin((() => {})); //#endregion //#region src/engine/atoms/摸牌.ts /** * 规划一次摸牌:计算将要摸入的牌,以及(必要时)重洗后的新牌堆。 * 纯函数,不修改 state——apply 与 toViewEvents 共用此逻辑,保证两边一致。 * 调用方需保证 deck+discardPile 总数 >= count(validate 已拦截)。 */ function planDraw(state, count, fromBottom = false) { if (state.zones.deck.length >= count) return { drawn: fromBottom ? state.zones.deck.slice(0, count) : state.zones.deck.slice(-count).reverse(), reshuffled: false }; const combined = [...state.zones.deck, ...state.zones.discardPile]; const rng = createRng(state.rngSeed); for (let i = combined.length - 1; i > 0; i--) { const j = rng.nextInt(i + 1); [combined[i], combined[j]] = [combined[j], combined[i]]; } return { drawn: fromBottom ? combined.slice(0, count) : combined.slice(-count).reverse(), newDeck: fromBottom ? combined.slice(count) : combined.slice(0, -count), reshuffled: true, newSeed: rng.getState() }; } var 摸牌; var init_摸牌 = __esmMin((() => { init_rng(); 摸牌 = { type: "摸牌", validate(state, atom) { if (!state.players[atom.player]) return `player ${atom.player} not found`; if (atom.count <= 0) return "count must be > 0"; if (state.zones.deck.length + state.zones.discardPile.length < atom.count) return "no cards available (deck + discardPile)"; return null; }, apply(state, atom) { const fromBottom = !!atom.fromBottom; const plan = planDraw(state, atom.count, fromBottom); if (plan.reshuffled) { state.zones.deck = plan.newDeck; state.zones.discardPile = []; state.rngSeed = plan.newSeed; } else state.zones.deck = fromBottom ? state.zones.deck.slice(atom.count) : state.zones.deck.slice(0, -atom.count); state.players[atom.player].hand.push(...plan.drawn); }, effect: { sound: "flip", animation: "slide", duration: 600 }, toViewEvents(state, atom) { const effect = { sound: "flip", animation: "slide", duration: 600 }; const plan = planDraw(state, atom.count, !!atom.fromBottom); const cards = plan.drawn.map((id) => state.cardMap[id]).filter(Boolean); const base = { type: "摸牌", player: atom.player, count: atom.count, effect, reshuffled: plan.reshuffled, ...plan.reshuffled ? { newDeckCount: plan.newDeck.length, newDiscardPileCount: 0 } : {} }; const ownerView = { ...base, cards }; const othersView = { ...base }; return { ownerViews: new Map([[atom.player, ownerView]]), othersView }; }, applyView(view, event) { const pi = view.players.findIndex((p) => p.index === event.player); if (pi < 0) return; const count = event.count ?? 0; view.players[pi].handCount += count; if (event.cards && view.players[pi].hand) view.players[pi].hand.push(...event.cards); if (view.zones) if (event.reshuffled) { view.zones.deckCount = event.newDeckCount ?? Math.max(0, view.zones.deckCount - count); view.zones.discardPileCount = event.newDiscardPileCount ?? 0; } else view.zones.deckCount = Math.max(0, view.zones.deckCount - count); }, toViewLog(event, viewer) { const count = event.count ?? 0; const cards = event.cards; if ((event.player === viewer || cards && cards.length > 0) && cards && cards.length > 0) { const cardNames = cards.map((c) => `${c.suit ?? ""}${c.rank ?? ""}${c.name}`).join("、"); return { player: event.player, text: `摸了 ${count} 张牌:${cardNames}` }; } return { player: event.player, text: `摸了 ${count} 张牌` }; } }; })); //#endregion //#region src/engine/atoms/整理牌堆.ts var 整理牌堆; var init_整理牌堆 = __esmMin((() => { 整理牌堆 = { type: "整理牌堆", validate(state, atom) { const deckSet = new Set(state.zones.deck); if (atom.cards.length !== state.zones.deck.length) return "card count mismatch"; for (const id of atom.cards) if (!deckSet.has(id)) return `card ${id} not in deck`; return null; }, apply(state, atom) { state.zones.deck = [...atom.cards]; }, effect: { sound: "card_place", animation: "flip", duration: 600 }, toViewEvents(_state, atom) { const view = { type: "整理牌堆", topCount: atom.topCount ?? 0, bottomCount: atom.bottomCount ?? 0 }; return { ownerViews: /* @__PURE__ */ new Map(), othersView: view }; }, applyView() {}, toViewLog(event) { const top = event.topCount ?? 0; const bottom = event.bottomCount ?? 0; const parts = []; if (top > 0) parts.push(`${top}张放牌堆顶`); if (bottom > 0) parts.push(`${bottom}张放牌堆底`); if (parts.length === 0) return null; return { player: -1, text: `整理牌堆:${parts.join(",")}` }; } }; })); //#endregion //#region src/engine/atoms/检测有效性.ts var 检测有效性; var init_检测有效性 = __esmMin((() => { init_skill(); 检测有效性 = { type: "检测有效性", validate(state, atom) { if (!state.players[atom.target]) return `target ${atom.target} not found`; return null; }, apply() {}, toViewEvents(state, atom) { if (getBeforeHooks(state, "检测有效性").length === 0) return { ownerViews: /* @__PURE__ */ new Map(), othersView: null }; const view = { type: "检测有效性", source: atom.source, target: atom.target, cardId: atom.cardId }; return { ownerViews: new Map([[atom.target, view]]), othersView: view }; }, applyView() {}, toViewLog() { return null; } }; })); //#endregion //#region src/engine/atoms/添加延时锦囊.ts var 添加延时锦囊; var init_添加延时锦囊 = __esmMin((() => { 添加延时锦囊 = { type: "添加延时锦囊", validate(state, atom) { if (!state.players[atom.player]) return `player ${atom.player} not found`; return null; }, apply(state, atom) { const player = state.players[atom.player]; if (player.pendingTricks.some((t) => t.name === atom.trick.name)) return; player.pendingTricks.push(atom.trick); }, effect: { sound: "flip", animation: "glow", duration: 800 }, toViewEvents(_state, atom) { const view = { type: "添加延时锦囊", player: atom.player, trickName: atom.trick.name, trick: atom.trick }; return { ownerViews: /* @__PURE__ */ new Map(), othersView: view }; }, applyView(view, event) { const pi = view.players.findIndex((p) => p.index === event.player); if (pi < 0) return; view.players[pi].pendingTricks ??= []; const trick = event.trick; if (trick?.card?.id) { const list = view.players[pi].pendingTricks; const cardId = trick.card.id; if (!list.includes(cardId)) list.push(cardId); } }, toViewLog(event) { const trick = event.trick; return { player: event.player, text: `判定区放入 ${trick?.name ?? "延时锦囊"}` }; } }; })); //#endregion //#region src/engine/atoms/添加技能.ts var 添加技能; var init_添加技能 = __esmMin((() => { init_equipment(); 添加技能 = { type: "添加技能", validate(state, atom) { if (!state.players[atom.player]) return `player ${atom.player} not found`; return null; }, apply(state, atom) { const player = state.players[atom.player]; if (player.skills.includes(atom.skillId)) return; player.skills.push(atom.skillId); }, effect: { animation: "glow", duration: 500 }, toViewEvents(_state, atom) { const view = { type: "添加技能", player: atom.player, skillId: atom.skillId }; const mountVars = 马匹距离修正表.get(atom.skillId); if (mountVars) view.mountDistanceVars = mountVars; return { ownerViews: /* @__PURE__ */ new Map(), othersView: view }; }, applyView(view, event) { const pi = view.players.findIndex((p) => p.index === event.player); if (pi < 0) return; const skillId = event.skillId; if (!view.players[pi].skills.includes(skillId)) view.players[pi].skills.push(skillId); const mountVars = event.mountDistanceVars; if (mountVars) view.players[pi].distanceVars = { ...view.players[pi].distanceVars, ...mountVars }; } }; })); //#endregion //#region src/engine/atoms/清过期标记.ts var 清过期标记; var init_清过期标记 = __esmMin((() => { 清过期标记 = { type: "清过期标记", validate(state, atom) { if (!state.players[atom.player]) return `player ${atom.player} not found`; return null; }, apply(state, atom) { state.players[atom.player].marks = state.players[atom.player].marks.filter((m) => m.duration !== "turn"); }, toViewEvents(_state, atom) { const view = { type: "清过期标记", player: atom.player }; return { ownerViews: /* @__PURE__ */ new Map(), othersView: view }; }, applyView(view, event) { const pi = view.players.findIndex((p) => p.index === event.player); if (pi < 0) return; view.players[pi].marks = view.players[pi].marks.filter((m) => m.duration !== "turn"); } }; })); //#endregion //#region src/engine/atoms/生效前.ts var 生效前; var init_生效前 = __esmMin((() => { init_skill(); 生效前 = { type: "生效前", validate(state, atom) { if (!state.players[atom.target]) return `target ${atom.target} not found`; return null; }, apply() {}, toViewEvents(state, atom) { if (getBeforeHooks(state, "生效前").length === 0) return { ownerViews: /* @__PURE__ */ new Map(), othersView: null }; const view = { type: "生效前", source: atom.source, target: atom.target, cardId: atom.cardId }; return { ownerViews: /* @__PURE__ */ new Map(), othersView: view }; }, applyView() {} }; })); //#endregion //#region src/engine/atoms/生效后.ts var 生效后; var init_生效后 = __esmMin((() => { 生效后 = { type: "生效后", validate(state, atom) { if (!state.players[atom.target]) return `target ${atom.target} not found`; return null; }, apply() {}, toViewEvents(state, atom) { const cardName = state.cardMap[atom.cardId]?.name ?? atom.cardId; const view = { type: "生效后", source: atom.source, target: atom.target, cardId: atom.cardId, cardName }; return { ownerViews: /* @__PURE__ */ new Map(), othersView: view }; }, applyView() {} }; })); //#endregion //#region src/engine/atoms/生效时.ts var 生效时; var init_生效时 = __esmMin((() => { init_skill(); 生效时 = { type: "生效时", validate(state, atom) { if (!state.players[atom.target]) return `target ${atom.target} not found`; return null; }, apply() {}, toViewEvents(state, atom) { if (getBeforeHooks(state, "生效时").length === 0) return { ownerViews: /* @__PURE__ */ new Map(), othersView: null }; const view = { type: "生效时", source: atom.source, target: atom.target, cardId: atom.cardId }; return { ownerViews: /* @__PURE__ */ new Map(), othersView: view }; }, applyView() {} }; })); //#endregion //#region src/engine/atoms/移出至暂存区.ts var 移出至暂存区; var init_移出至暂存区 = __esmMin((() => { init_vars_keys(); 移出至暂存区 = { type: "移出至暂存区", validate(state, atom) { if (!state.players[atom.source]) return `source ${atom.source} not found`; if (!state.players[atom.target]) return `target ${atom.target} not found`; if (typeof atom.varsKey !== "string" || atom.varsKey.length === 0) return `varsKey 不能为空`; if (!Array.isArray(atom.cardIds) || atom.cardIds.length === 0) return `cardIds 不能为空`; const target = state.players[atom.target]; const equipValues = new Set(Object.values(target.equipment).filter((id) => !!id)); for (const id of atom.cardIds) if (!target.hand.includes(id) && !equipValues.has(id)) return `card ${id} not in target ${atom.target} hand or equipment`; if (new Set(atom.cardIds).size !== atom.cardIds.length) return `cardIds 不能重复`; return null; }, apply(state, atom) { const target = state.players[atom.target]; const removeSet = new Set(atom.cardIds); target.hand = target.hand.filter((id) => !removeSet.has(id)); for (const slot of Object.keys(target.equipment)) { const id = target.equipment[slot]; if (id && removeSet.has(id)) { delete target.equipment[slot]; if (slot === "武器") delete target.vars[DISTANCE_ATTACK_RANGE_KEY]; } } const list = target.vars[atom.varsKey] ?? []; for (const id of atom.cardIds) if (!list.includes(id)) list.push(id); target.vars[atom.varsKey] = list; }, effect: { sound: "flip", animation: "slide", duration: 600 }, toViewEvents(state, atom) { const target = state.players[atom.target]; const cardInfos = atom.cardIds.map((id) => state.cardMap[id]).filter((c) => !!c).map((c) => ({ id: c.id, name: c.name, suit: c.suit, rank: c.rank })); const equipSlotOf = (id) => { for (const slot of Object.keys(target.equipment)) if (target.equipment[slot] === id) return slot; }; const handIds = []; const equipEntries = []; for (const id of atom.cardIds) { const slot = equipSlotOf(id); if (slot) equipEntries.push({ slot, cardId: id }); else handIds.push(id); } const privView = { type: "移出至暂存区", source: atom.source, target: atom.target, cardIds: atom.cardIds, cards: cardInfos, handIds, equipEntries }; const othersView = { type: "移出至暂存区", source: atom.source, target: atom.target, handCount: handIds.length, equipEntries }; return { ownerViews: new Map([[atom.source, privView], [atom.target, privView]]), othersView }; }, applyView(view, event) { const pi = view.players.findIndex((p) => p.index === event.target); if (pi < 0) return; const handIds = event.handIds; const handCount = handIds ? handIds.length : event.handCount ?? 0; if (handCount > 0) view.players[pi].handCount = Math.max(0, view.players[pi].handCount - handCount); if (handIds && view.players[pi].hand) { const removeSet = new Set(handIds); view.players[pi].hand = view.players[pi].hand.filter((c) => !removeSet.has(c.id)); } const equipEntries = event.equipEntries; if (equipEntries) for (const e of equipEntries) { delete view.players[pi].equipment[e.slot]; if (e.slot === "武器" && view.players[pi].distanceVars) view.players[pi].distanceVars = { ...view.players[pi].distanceVars, attackRange: void 0 }; } }, toViewLog(event) { const handCount = event.handIds?.length ?? event.handCount ?? 0; const equipCount = event.equipEntries?.length ?? 0; const total = handCount + equipCount; return { player: event.source, text: `将 P${event.target} 的 ${total} 张牌移出游戏(手牌${handCount} 装备${equipCount})` }; } }; })); //#endregion //#region src/engine/atoms/移动牌.ts var 移动牌; var init_移动牌 = __esmMin((() => { 移动牌 = { type: "移动牌", validate(state, atom) { if (!state.cardMap[atom.cardId]) return `card ${atom.cardId} not found`; return null; }, apply(state, atom) { if (atom.from.zone === "手牌") { const fromP = atom.from; state.players[fromP.player].hand = state.players[fromP.player].hand.filter((id) => id !== atom.cardId); } else if (atom.from.zone === "牌堆") state.zones.deck = state.zones.deck.filter((id) => id !== atom.cardId); else if (atom.from.zone === "弃牌堆") state.zones.discardPile = state.zones.discardPile.filter((id) => id !== atom.cardId); else if (atom.from.zone === "处理区") { const frame = state.settlementStack[state.settlementStack.length - 1]; if (frame) frame.cards = frame.cards.filter((id) => id !== atom.cardId); else state.zones.processing = state.zones.processing.filter((id) => id !== atom.cardId); } if (atom.to.zone === "手牌") { const toP = atom.to; state.players[toP.player].hand.push(atom.cardId); } else if (atom.to.zone === "牌堆") state.zones.deck.push(atom.cardId); else if (atom.to.zone === "弃牌堆") { const card = state.cardMap[atom.cardId]; if (card?.ephemeral) delete state.cardMap[atom.cardId]; else if (card?.shadowOf) { state.zones.discardPile.push(card.shadowOf); delete state.cardMap[atom.cardId]; } else state.zones.discardPile.push(atom.cardId); } else if (atom.to.zone === "处理区") { const frame = state.settlementStack[state.settlementStack.length - 1]; if (frame) frame.cards.push(atom.cardId); else state.zones.processing.push(atom.cardId); } }, toViewEvents(state, atom) { const card = state.cardMap[atom.cardId]; const cardInfo = card ? { name: card.name, suit: card.suit, rank: card.rank } : null; const fromPlayer = atom.from.zone === "手牌" ? atom.from.player : void 0; const toPlayer = atom.to.zone === "手牌" ? atom.to.player : void 0; if (atom.to.zone === "弃牌堆" && fromPlayer !== void 0 && cardInfo) { const origCard = card?.shadowOf ? state.cardMap[card.shadowOf] : void 0; const view = { type: "弃牌", player: fromPlayer, card: origCard ? { name: origCard.name, suit: origCard.suit, rank: origCard.rank } : cardInfo, cardId: atom.cardId, effect: { sound: "flip", duration: 600 } }; return { ownerViews: /* @__PURE__ */ new Map(), othersView: view }; } if (atom.to.zone === "处理区" && fromPlayer !== void 0 && cardInfo) { const view = { type: "打出", player: fromPlayer, card: cardInfo, cardId: atom.cardId, effect: { animation: "flip", duration: 800 } }; return { ownerViews: /* @__PURE__ */ new Map(), othersView: view }; } if (atom.from.zone === "牌堆" && toPlayer !== void 0 && card) { const effect = { sound: "flip", animation: "slide", duration: 600 }; return { ownerViews: new Map([[toPlayer, { type: "摸牌", player: toPlayer, count: 1, cards: [card], effect }]]), othersView: { type: "摸牌", player: toPlayer, count: 1, effect } }; } const view = { type: "移动牌", cardId: atom.cardId, from: atom.from, to: atom.to, player: fromPlayer ?? toPlayer, ...state.cardMap[atom.cardId]?.ephemeral ? { ephemeral: true } : {} }; return { ownerViews: /* @__PURE__ */ new Map(), othersView: view }; }, applyView(view, event) { if (event.type === "移动牌" && view.zones) { const from = event.from; const to = event.to; const cardId = event.cardId; if (from?.zone === "牌堆") view.zones.deckCount = Math.max(0, view.zones.deckCount - 1); else if (from?.zone === "弃牌堆") view.zones.discardPileCount = Math.max(0, view.zones.discardPileCount - 1); else if (from?.zone === "处理区") { const f = view.settlementStack[view.settlementStack.length - 1]; if (f) f.cards = f.cards.filter((id) => id !== cardId); if (view.zones) view.zones.processing = view.zones.processing.filter((id) => id !== cardId); } if (to?.zone === "牌堆") view.zones.deckCount += 1; else if (to?.zone === "弃牌堆") { if (event.ephemeral !== true) view.zones.discardPileCount += 1; } else if (to?.zone === "处理区") { const f = view.settlementStack[view.settlementStack.length - 1]; if (f) f.cards.push(cardId); if (view.zones) view.zones.processing.push(cardId); } } const pi = view.players.findIndex((p) => p.index === event.player); if (pi < 0) return; switch (event.type) { case "弃牌": view.players[pi].handCount = Math.max(0, view.players[pi].handCount - 1); if (view.players[pi].hand && event.cardId) { const cardId = event.cardId; view.players[pi].hand = view.players[pi].hand.filter((c) => c.id !== cardId); } if (view.zones) view.zones.discardPileCount += 1; break; case "打出": view.players[pi].handCount = Math.max(0, view.players[pi].handCount - 1); if (view.players[pi].hand && event.cardId) { const cardId = event.cardId; view.players[pi].hand = view.players[pi].hand.filter((c) => c.id !== cardId); } if (view.zones && event.cardId) { view.zones.processing.push(event.cardId); const f = view.settlementStack[view.settlementStack.length - 1]; if (f) f.cards.push(event.cardId); } break; case "摸牌": { const count = event.count ?? 1; view.players[pi].handCount += count; if (event.cards && view.players[pi].hand) view.players[pi].hand.push(...event.cards); if (view.zones) view.zones.deckCount = Math.max(0, view.zones.deckCount - count); break; } default: { const from = event.from; const to = event.to; const cardId = event.cardId; if (from?.zone === "手牌" && from?.player !== void 0) { const fromPi = view.players.findIndex((p) => p.index === from.player); if (fromPi >= 0) { view.players[fromPi].handCount = Math.max(0, view.players[fromPi].handCount - 1); if (view.players[fromPi].hand) view.players[fromPi].hand = view.players[fromPi].hand.filter((c) => c.id !== cardId); } } if (to?.zone === "手牌" && to?.player !== void 0) { const toPi = view.players.findIndex((p) => p.index === to.player); if (toPi >= 0) { view.players[toPi].handCount += 1; if (cardId && view.players[toPi].hand) { const card = view.cardMap[cardId]; if (card) view.players[toPi].hand.push(card); } } } break; } } } }; })); //#endregion //#region src/engine/atoms/移除延时锦囊.ts var 移除延时锦囊; var init_移除延时锦囊 = __esmMin((() => { 移除延时锦囊 = { type: "移除延时锦囊", validate(state, atom) { if (!state.players[atom.player]) return `player ${atom.player} not found`; return null; }, apply(state, atom) { state.players[atom.player].pendingTricks = state.players[atom.player].pendingTricks.filter((t) => t.name !== atom.trickName); }, effect: { sound: "flip", animation: "fade", duration: 600 }, toViewEvents(state, atom) { const trick = state.players[atom.player].pendingTricks.find((t) => t.name === atom.trickName); const view = { type: "移除延时锦囊", player: atom.player, trickName: atom.trickName, ...trick?.card?.id ? { cardId: trick.card.id } : {} }; return { ownerViews: /* @__PURE__ */ new Map(), othersView: view }; }, applyView(view, event) { const pi = view.players.findIndex((p) => p.index === event.player); if (pi < 0) return; const list = view.players[pi].pendingTricks; if (!list || list.length === 0) return; const targetId = event.cardId; const targetName = event.trickName; if (targetId) { view.players[pi].pendingTricks = list.filter((id) => id !== targetId); return; } if (!targetName) return; const cardId = Object.keys(view.cardMap).find((id) => view.cardMap[id]?.name === targetName); if (cardId) view.players[pi].pendingTricks = list.filter((id) => id !== cardId); else view.players[pi].pendingTricks = [...list]; } }; })); //#endregion //#region src/engine/atoms/移除技能.ts var 移除技能; var init_移除技能 = __esmMin((() => { init_equipment(); 移除技能 = { type: "移除技能", validate(state, atom) { if (!state.players[atom.player]) return `player ${atom.player} not found`; return null; }, apply(state, atom) { state.players[atom.player].skills = state.players[atom.player].skills.filter((id) => id !== atom.skillId); }, effect: { animation: "fade", duration: 400 }, toViewEvents(_state, atom) { const view = { type: "移除技能", player: atom.player, skillId: atom.skillId }; const mountVars = 马匹距离修正表.get(atom.skillId); if (mountVars) view.clearMountDistanceVars = mountVars; return { ownerViews: /* @__PURE__ */ new Map(), othersView: view }; }, applyView(view, event) { const pi = view.players.findIndex((p) => p.index === event.player); if (pi < 0) return; const skillId = event.skillId; view.players[pi].skills = view.players[pi].skills.filter((id) => id !== skillId); const clearVars = event.clearMountDistanceVars; if (clearVars) view.players[pi].distanceVars = { ...view.players[pi].distanceVars, attackMod: clearVars.attackMod !== void 0 ? void 0 : view.players[pi].distanceVars?.attackMod, defenseMod: clearVars.defenseMod !== void 0 ? void 0 : view.players[pi].distanceVars?.defenseMod }; } }; })); //#endregion //#region src/engine/atoms/结算帧入栈.ts var 结算帧入栈; var init_结算帧入栈 = __esmMin((() => { init_rng(); 结算帧入栈 = { type: "结算帧入栈", validate() { return null; }, apply(state, atom) { const frame = { skillId: atom.skillId, from: atom.from, params: { ...atom.params ?? {} }, cards: [], cancelled: false }; state.settlementStack.push(frame); }, toViewEvents(state, atom) { const variant = createRng(state.rngSeed).next() < .5 ? "1" : "2"; const view = { type: "结算帧入栈", skillId: atom.skillId, from: atom.from, params: { ...atom.params ?? {} }, effect: { sound: `skill/${atom.skillId}/${variant}` } }; return { ownerViews: /* @__PURE__ */ new Map(), othersView: view }; }, applyView(view, event) { view.settlementStack.push({ skillId: event.skillId, from: event.from, params: { ...event.params ?? {} }, cards: [], cancelled: false }); } }; })); //#endregion //#region src/engine/atoms/结算帧出栈.ts var 结算帧出栈; var init_结算帧出栈 = __esmMin((() => { 结算帧出栈 = { type: "结算帧出栈", validate(state) { if (state.settlementStack.length === 0) return "settlementStack 为空,无可弹出帧"; return null; }, apply(state) { state.settlementStack.pop(); }, toViewEvents() { return { ownerViews: /* @__PURE__ */ new Map(), othersView: { type: "结算帧出栈" } }; }, applyView(view) { view.settlementStack.pop(); } }; })); //#endregion //#region src/engine/atoms/给予.ts var 给予; var init_给予 = __esmMin((() => { init_apply(); 给予 = { type: "给予", validate(state, atom) { if (!state.cardMap[atom.cardId]) return `card ${atom.cardId} not found`; const fromP = state.players[atom.from]; if (!fromP) return `from ${atom.from} not found`; if (!fromP.hand.includes(atom.cardId)) return `card not in from's hand`; if (!state.players[atom.to]) return `to ${atom.to} not found`; return null; }, apply(state, atom) { const fromIdx = atom.from; const toIdx = atom.to; state.players[fromIdx].hand = state.players[fromIdx].hand.filter((id) => id !== atom.cardId); state.players[toIdx].hand.push(atom.cardId); }, effect: { sound: "flip", animation: "slide", duration: 600 }, /** 模块 F:发出「移动到目标区域后」时机标记(reason='给予',手牌→手牌)。 */ async afterApply(state, atom) { await applyAtom(state, { type: "移动到目标区域后", cardId: atom.cardId, from: { zone: "手牌", player: atom.from }, to: { zone: "手牌", player: atom.to }, reason: "给予" }); }, toViewEvents(_state, atom) { const effect = { sound: "flip", animation: "slide", duration: 600 }; const ownerView = { type: "给予", cardId: atom.cardId, cardName: _state.cardMap[atom.cardId]?.name, from: atom.from, to: atom.to, effect }; const othersView = { type: "给予", from: atom.from, to: atom.to, effect }; return { ownerViews: new Map([[atom.from, ownerView], [atom.to, ownerView]]), othersView }; }, applyView(view, event) { const cardId = event.cardId; const fromPi = view.players.findIndex((p) => p.index === event.from); if (fromPi >= 0) { view.players[fromPi].handCount = Math.max(0, view.players[fromPi].handCount - 1); if (cardId && view.players[fromPi].hand) view.players[fromPi].hand = view.players[fromPi].hand.filter((c) => c.id !== cardId); } const toPi = view.players.findIndex((p) => p.index === event.to); if (toPi >= 0) { view.players[toPi].handCount += 1; if (cardId && view.players[toPi].hand) { const card = view.cardMap[cardId]; if (card) view.players[toPi].hand.push(card); } } }, toViewLog(event, viewer, resolveName) { const from = event.from; const to = event.to; const cardName = event.cardName; const toName = resolveName?.(to) ?? `P${to}`; if ((from === viewer || to === viewer) && cardName) return { player: from, text: `将 ${cardName} 给予了 ${toName}` }; return { player: from, text: `给 ${toName} 一张牌` }; } }; })); //#endregion //#region src/engine/atoms/置创牌.ts var 置创牌; var init_置创牌 = __esmMin((() => { 置创牌 = { type: "置创牌", validate(state, atom) { if (!state.players[atom.player]) return `player ${atom.player} not found`; if (state.zones.deck.length === 0) return `牌堆为空,无法置创牌`; return null; }, apply(state, atom) { const player = state.players[atom.player]; const { varsKey, resultKey } = atom; const cardId = state.zones.deck.pop(); const rank = state.cardMap[cardId]?.rank; const list = player.vars[varsKey] ?? []; const duplicate = rank !== void 0 && list.some((id) => state.cardMap[id]?.rank === rank); if (duplicate) state.zones.discardPile.push(cardId); else { list.push(cardId); player.vars[varsKey] = list; } state.localVars[resultKey] = duplicate; }, effect: { sound: "flip", animation: "flip", duration: 800 }, toViewEvents(state, atom) { const { varsKey } = atom; const cardId = state.zones.deck[state.zones.deck.length - 1]; const card = cardId ? state.cardMap[cardId] : void 0; const exist = state.players[atom.player].vars[varsKey] ?? []; const rank = card?.rank; const duplicate = rank !== void 0 && exist.some((id) => state.cardMap[id]?.rank === rank); const view = { type: "置创牌", player: atom.player, cardId, suit: card?.suit, rank, duplicate }; return { ownerViews: /* @__PURE__ */ new Map(), othersView: view }; }, applyView(view, event) { if (view.zones) { view.zones.deckCount = Math.max(0, view.zones.deckCount - 1); if (event.duplicate) view.zones.discardPileCount += 1; } }, toViewLog(event) { const rank = event.rank; const suit = event.suit; const dup = event.duplicate; return { player: event.player, text: `翻开创牌 ${suit ?? ""}${rank ?? "?"}${dup ? "(点数重复!)" : ""}` }; } }; })); //#endregion //#region src/engine/atoms/获得.ts var FROM_HAND_KEY, 获得; var init_获得 = __esmMin((() => { init_apply(); FROM_HAND_KEY = "__获得/来自手牌"; 获得 = { type: "获得", validate(state, atom) { if (!state.cardMap[atom.cardId]) return `card ${atom.cardId} not found`; if (!state.players[atom.player]) return `player ${atom.player} not found`; if (atom.from !== void 0 && !state.players[atom.from]) return `from ${atom.from} not found`; return null; }, apply(state, atom) { let fromWasHand = false; if (atom.from !== void 0) { const fromP = state.players[atom.from]; fromWasHand = fromP.hand.includes(atom.cardId); fromP.hand = fromP.hand.filter((id) => id !== atom.cardId); const equipment = {}; for (const [slot, id] of Object.entries(fromP.equipment)) if (id && id !== atom.cardId) equipment[slot] = id; fromP.equipment = equipment; } state.localVars[FROM_HAND_KEY] = fromWasHand; state.players[atom.player].hand.push(atom.cardId); }, effect: { sound: "flip", animation: "slide", duration: 600 }, /** 模块 F:为「从他人手牌获得」发出「移动到目标区域后」时机标记(reason='获得')。 * 从装备区获得(ZoneLoc 不含装备)或无 from(牌凭空产生,如仁德/再起)不发标记。 */ async afterApply(state, atom) { const fromWasHand = state.localVars[FROM_HAND_KEY]; delete state.localVars[FROM_HAND_KEY]; if (!fromWasHand || atom.from === void 0) return; await applyAtom(state, { type: "移动到目标区域后", cardId: atom.cardId, from: { zone: "手牌", player: atom.from }, to: { zone: "手牌", player: atom.player }, reason: "获得" }); }, toViewEvents(state, atom) { const effect = { sound: "flip", animation: "slide", duration: 600 }; const fromZone = atom.from !== void 0 ? state.players[atom.from].hand.includes(atom.cardId) ? "hand" : "equipment" : void 0; const ownerView = { type: "获得", player: atom.player, cardId: atom.cardId, cardName: state.cardMap[atom.cardId]?.name, ...atom.from !== void 0 ? { from: atom.from, fromZone } : {}, effect }; const othersView = { type: "获得", player: atom.player, ...atom.from !== void 0 ? { from: atom.from, fromZone } : {}, ...fromZone === "equipment" ? { cardId: atom.cardId } : {}, effect }; const ownerViews = new Map([[atom.player, ownerView]]); if (atom.from !== void 0) ownerViews.set(atom.from, ownerView); return { ownerViews, othersView }; }, applyView(view, event) { const cardId = event.cardId; const pi = view.players.findIndex((p) => p.index === event.player); if (pi < 0) return; view.players[pi].handCount += 1; if (cardId && view.players[pi].hand) { const card = view.cardMap[cardId]; if (card) view.players[pi].hand.push(card); } const from = event.from; const fromZone = event.fromZone; if (from !== void 0) { const fromPi = view.players.findIndex((p) => p.index === from); if (fromPi >= 0) if (fromZone === "equipment") { const equipment = {}; for (const [slot, id] of Object.entries(view.players[fromPi].equipment)) if (id && id !== cardId) equipment[slot] = id; view.players[fromPi].equipment = equipment; } else { view.players[fromPi].handCount = Math.max(0, view.players[fromPi].handCount - 1); if (cardId && view.players[fromPi].hand) view.players[fromPi].hand = view.players[fromPi].hand.filter((c) => c.id !== cardId); } } }, toViewLog(event, viewer, resolveName) { const player = event.player; const cardName = event.cardName; const from = event.from; const isOwner = player === viewer || from === viewer; if (from !== void 0) { const fromName = resolveName?.(from) ?? `P${from}`; if (isOwner && cardName) return { player, text: `从 ${fromName} 处获得了 ${cardName}` }; return { player, text: `从 ${fromName} 处获得了一张牌` }; } if (isOwner && cardName) return { player, text: `获得了 ${cardName}` }; return { player, text: "获得了一张牌" }; } }; })); //#endregion //#region src/engine/atoms/被抵消.ts var 被抵消; var init_被抵消 = __esmMin((() => { 被抵消 = { type: "被抵消", validate(state, atom) { if (!state.players[atom.target]) return `target ${atom.target} not found`; return null; }, apply() {}, toViewEvents(_state, atom) { const view = { type: "被抵消", source: atom.source, target: atom.target, cardId: atom.cardId }; return { ownerViews: /* @__PURE__ */ new Map(), othersView: view }; }, applyView() {}, toViewLog(event) { return { player: event.target, text: `抵消` }; } }; })); //#endregion //#region src/engine/atoms/装备.ts function inferSlot(cardType) { switch (cardType) { case "武器": return "武器"; case "防具": return "防具"; case "进攻马": return "进攻马"; case "防御马": return "防御马"; case "宝物": return "宝物"; default: return null; } } /** 设装备带来的距离修正 vars(仅武器攻击范围;马匹由技能处理) */ function applyEquipVars(state, playerIdx, slot, card) { const vars = state.players[playerIdx].vars; if (slot === "武器") vars[DISTANCE_ATTACK_RANGE_KEY] = card.range ?? 1; } var 装备; var init_装备 = __esmMin((() => { init_vars_keys(); 装备 = { type: "装备", validate(state, atom) { const p = state.players[atom.player]; if (!p) return `player ${atom.player} not found`; if (!p.hand.includes(atom.cardId)) return `card not in player's hand`; const card = state.cardMap[atom.cardId]; if (!card) return `card ${atom.cardId} not found`; if (!inferSlot(card.subtype)) return `card is not equipment`; return null; }, apply(state, atom) { const card = state.cardMap[atom.cardId]; const slot = inferSlot(card.subtype); const player = state.players[atom.player]; player.hand = player.hand.filter((id) => id !== atom.cardId); player.equipment[slot] = atom.cardId; applyEquipVars(state, atom.player, slot, card); }, effect: { animation: "glow", duration: 400 }, toViewEvents(state, atom) { const card = state.cardMap[atom.cardId]; const slot = inferSlot(card.subtype); const view = { type: "装备", player: atom.player, cardId: atom.cardId, cardName: card.name, slot, ...slot === "武器" ? { range: card.range ?? 1 } : {}, effect: { sound: `equip/${card.name}` } }; return { ownerViews: /* @__PURE__ */ new Map(), othersView: view }; }, applyView(view, event) { const pi = view.players.findIndex((p) => p.index === event.player); if (pi < 0) return; const slot = event.slot; if (slot) view.players[pi].equipment[slot] = event.cardId; if (slot === "武器" && typeof event.range === "number") view.players[pi].distanceVars = { ...view.players[pi].distanceVars, attackRange: event.range }; view.players[pi].handCount = Math.max(0, view.players[pi].handCount - 1); if (view.players[pi].hand) view.players[pi].hand = view.players[pi].hand.filter((c) => c.id !== event.cardId); }, toViewLog(event) { return { player: event.player, text: `装备了 ${event.cardName ?? event.cardId}` }; } }; })); //#endregion //#region src/engine/atoms/设上限.ts var 设上限; var init_设上限 = __esmMin((() => { 设上限 = { type: "设上限", validate(state, atom) { if (!state.players[atom.player]) return `player ${atom.player} not found`; return null; }, apply(state, atom) { const player = state.players[atom.player]; player.maxHealth = atom.amount; player.health = Math.min(player.health, atom.amount); }, applyView(view, event) { const pi = view.players.findIndex((p) => p.index === event.player); if (pi < 0) return; const p = view.players[pi]; p.maxHealth = event.amount; p.health = Math.min(p.health, event.amount); } }; })); //#endregion //#region src/engine/atoms/设横置.ts var 设横置; var init_设横置 = __esmMin((() => { 设横置 = { type: "设横置", validate(state, atom) { if (!state.players[atom.player]) return `player ${atom.player} not found`; return null; }, apply(state, atom) { const player = state.players[atom.player]; player.marks = player.marks.filter((m) => m.id !== "chained"); if (atom.chained) player.marks.push({ id: "chained", scope: player.index }); }, effect: { sound: "chain", animation: "pulse", duration: 500 }, toViewEvents(_state, atom) { const view = { type: "设横置", player: atom.player, chained: atom.chained }; return { ownerViews: /* @__PURE__ */ new Map(), othersView: view }; }, applyView(view, event) { const pi = view.players.findIndex((p) => p.index === event.player); if (pi < 0) return; const chained = event.chained; view.players[pi].marks = view.players[pi].marks.filter((m) => m.id !== "chained"); if (chained) view.players[pi].marks.push({ id: "chained", scope: view.players[pi].index }); } }; })); //#endregion //#region src/engine/atoms/询问杀.ts var TIMEOUT_SEC$1, PROMPT$1, 询问杀; var init_询问杀 = __esmMin((() => { init_timeout(); init_card_response_availability(); TIMEOUT_SEC$1 = 15; PROMPT$1 = { type: "useCard", title: "是否出杀", cardFilter: { filter: (c) => c.name === "杀", min: 1, max: 1 } }; 询问杀 = { type: "询问杀", validate(state, atom) { if (!state.players[atom.target]) return `target not found`; return null; }, apply(_state) {}, pending: { onTimeout: async () => {}, prompt: PROMPT$1, timeout: TIMEOUT_SEC$1, preResolve: (state, atom) => cardResponsePreResolveForTarget(state, "询问杀", atom.target, (c) => c.name === "杀") }, effect: { blockUntilDone: true, duration: 200 }, toViewEvents(state, atom) { const mode = evaluateCardResponseModeForTarget(state, "询问杀", atom.target, (c) => c.name === "杀"); const timeoutMs = mode === "silent" ? SHORT_DELAY_MS : resolveTimeoutMs(state, TIMEOUT_SEC$1); const base = { type: "询问杀", target: atom.target, source: atom.source, timeoutMs, responseMode: mode }; return { ownerViews: new Map([[atom.target, base]]), othersView: base }; }, applyView(view, event) { const target = event.target; const mode = event.responseMode ?? "normal"; if (mode === "skip") return; const timeoutMs = event.timeoutMs ?? TIMEOUT_SEC$1 * 1e3; const atomObj = { type: "询问杀", target, source: event.source }; if (mode === "silent") { view.pending = { type: "awaits", atom: atomObj, prompt: SILENT_RESPONSE_PROMPT, target, responseMode: "silent", deadline: Date.now() + timeoutMs, totalMs: timeoutMs }; return; } if (view.viewer === target) view.pending = { type: "awaits", atom: atomObj, prompt: PROMPT$1, target, responseMode: "normal", deadline: Date.now() + timeoutMs, totalMs: timeoutMs }; else view.pending = { type: "awaits", atom: atomObj, prompt: { type: "confirm", title: `等待出杀`, cancelLabel: "" }, target, deadline: Date.now() + timeoutMs, totalMs: timeoutMs }; } }; })); //#endregion //#region src/engine/atoms/询问闪.ts var TIMEOUT_SEC, PROMPT, 询问闪; var init_询问闪 = __esmMin((() => { init_timeout(); init_card_response_availability(); TIMEOUT_SEC = 15; PROMPT = { type: "useCard", title: "是否出闪", cardFilter: { filter: (c) => c.name === "闪", min: 1, max: 1 } }; 询问闪 = { type: "询问闪", validate(state, atom) { if (!state.players[atom.target]) return `target not found`; return null; }, apply(_state) {}, pending: { onTimeout: async () => {}, prompt: PROMPT, timeout: TIMEOUT_SEC, preResolve: (state, atom) => cardResponsePreResolveForTarget(state, "询问闪", atom.target, (c) => c.name === "闪") }, effect: { blockUntilDone: true, duration: 200 }, toViewEvents(state, atom) { const mode = evaluateCardResponseModeForTarget(state, "询问闪", atom.target, (c) => c.name === "闪"); const timeoutMs = mode === "silent" ? SHORT_DELAY_MS : resolveTimeoutMs(state, TIMEOUT_SEC); const base = { type: "询问闪", target: atom.target, source: atom.source, timeoutMs, responseMode: mode }; return { ownerViews: new Map([[atom.target, base]]), othersView: base }; }, applyView(view, event) { const target = event.target; const mode = event.responseMode ?? "normal"; if (mode === "skip") return; const timeoutMs = event.timeoutMs ?? TIMEOUT_SEC * 1e3; const atomObj = { type: "询问闪", target, source: event.source }; if (mode === "silent") { view.pending = { type: "awaits", atom: atomObj, prompt: SILENT_RESPONSE_PROMPT, target, responseMode: "silent", deadline: Date.now() + timeoutMs, totalMs: timeoutMs }; return; } if (view.viewer === target) view.pending = { type: "awaits", atom: atomObj, prompt: PROMPT, target, responseMode: "normal", deadline: Date.now() + timeoutMs, totalMs: timeoutMs }; else view.pending = { type: "awaits", atom: atomObj, prompt: { type: "confirm", title: `等待出闪`, cancelLabel: "" }, target, deadline: Date.now() + timeoutMs, totalMs: timeoutMs }; } }; })); //#endregion //#region src/engine/rules/hand-limit.ts function getHandLimitRegistry(state) { let reg = handLimitRegistries.get(state); if (!reg) { reg = { providers: /* @__PURE__ */ new Map() }; handLimitRegistries.set(state, reg); } return reg; } /** * 注册一个手牌上限覆盖提供者(技能 onInit 时调用,与 registerAction/registerSlashQuotaProvider 同构)。 * 返回取消注册函数——技能 onInit 应将其并入返回的 unload,由 setSkillInstanceUnload * 统一管理,卸载技能实例时自动清理。 */ function registerHandLimitProvider(state, ownerId, provider) { const reg = getHandLimitRegistry(state); let set = reg.providers.get(ownerId); if (!set) { set = /* @__PURE__ */ new Set(); reg.providers.set(ownerId, set); } set.add(provider); return () => { const s = reg.providers.get(ownerId); if (s) { s.delete(provider); if (s.size === 0) reg.providers.delete(ownerId); } }; } /** * 玩家手牌上限。 * - 若注册了覆盖提供者:取所有覆盖值中的最大值(最宽松),完全替代默认公式。 * - 否则:当前体力值 + 本回合加成(turn.vars['手牌上限/bonus:'])。 * * 弃牌阶段(回合管理.ts)与弃牌超时(请求回应.ts)统一调用本函数, * 确保界英姿等"手牌上限=体力上限"的锁定技在所有读取点一致生效。 */ function handLimit(state, player) { const set = getHandLimitRegistry(state).providers.get(player); if (set && set.size > 0) { let maxOverride = -Infinity; for (const fn of set) { const v = fn(state, player); if (typeof v === "number" && v > maxOverride) maxOverride = v; } if (maxOverride !== -Infinity) return maxOverride; } const bonus = getHandLimitBonus(state.turn.vars, player) ?? 0; return getHealthValue(state.players[player]) + bonus; } var handLimitRegistries; var init_hand_limit = __esmMin((() => { init_types(); init_vars_keys(); handLimitRegistries = /* @__PURE__ */ new WeakMap(); })); //#endregion //#region src/engine/atoms/请求回应.ts /** 请求回应 的卡牌回应 filter:仅 useCard+cardFilter.filter 的非 mandatory 回应。 * 其他(confirm / choosePlayer / distribute / pickTargetCard / mandatory 等)返回 null → 正常询问。 * type-safe:仅依赖 atom 的 prompt/mandatory/target 字段(均在 A 参数型内)。 */ function responseCardFilter(atom) { if (atom.mandatory === true) return null; if (atom.prompt.type !== "useCard") return null; const filter = atom.prompt.cardFilter?.filter; return typeof filter === "function" ? filter : null; } var 请求回应; var init_请求回应 = __esmMin((() => { init_apply(); init_timeout(); init_hand_limit(); init_choosePlayerCandidates(); init_cardFilterCandidates(); init_card_response_availability(); 请求回应 = { type: "请求回应", validate(state, atom) { if (atom.target < 0) return null; if (!state.players[atom.target]) return `target not found`; return null; }, apply(_state) {}, pending: { async onTimeout(state, atom) { if (atom.requestType === "__弃牌") { const target = atom.target; const p = state.players[target]; if (!p) return; const limit = handLimit(state, target); if (p.hand.length <= limit) return; const excess = p.hand.length - limit; const priority = { "桃": 0, "杀": 1, "闪": 2, "无懈可击": 3 }; await applyAtom(state, { type: "弃置", player: target, cardIds: [...p.hand].sort((a, b) => { const ca = state.cardMap[a]; return (priority[state.cardMap[b]?.name ?? ""] ?? 9) - (priority[ca?.name ?? ""] ?? 9); }).slice(0, excess) }); return; } }, prompt: { type: "confirm", title: "请回应" }, timeout: 30, preResolve: (state, atom) => { if (atom.target < 0) return null; const filter = responseCardFilter(atom); if (!filter) return null; return cardResponsePreResolveForTarget(state, "请求回应", atom.target, filter, atom.requestType); } }, effect: { blockUntilDone: true, duration: 200 }, toViewEvents(state, atom) { const timeoutSec = atom.timeout ?? 请求回应.pending.timeout; const isBroadcast = atom.target < 0; const cardFilter = responseCardFilter(atom); const mode = cardFilter && atom.target >= 0 ? evaluateCardResponseModeForTarget(state, "请求回应", atom.target, cardFilter, atom.requestType) : "normal"; const timeoutMs = mode === "silent" ? SHORT_DELAY_MS : resolveTimeoutMs(state, timeoutSec, isBroadcast); const resolvedPrompt = resolveCardFilterCandidates(resolveChoosePlayerCandidates(atom.prompt, state), state, atom.target); const targetView = { type: "请求回应", requestType: atom.requestType, target: atom.target, prompt: resolvedPrompt, timeoutMs, mandatory: atom.mandatory === true ? true : void 0, responseMode: mode }; if (atom.target < 0) return { ownerViews: new Map([[atom.target, targetView]]), othersView: targetView }; const othersView = { type: "请求回应", requestType: atom.requestType, target: atom.target, timeoutMs, mandatory: atom.mandatory === true ? true : void 0, responseMode: mode }; return { ownerViews: new Map([[atom.target, targetView]]), othersView }; }, applyView(view, event) { const target = event.target; const requestType = event.requestType; const prompt = event.prompt; const timeoutMs = event.timeoutMs ?? 30 * 1e3; const mandatory = event.mandatory === true; const mode = event.responseMode ?? "normal"; if (target < 0) { if (!prompt) return; if (view.viewer >= 0 && view.players[view.viewer]?.alive === false) return; view.pending = { type: "awaits", atom: { type: "请求回应", requestType, target, prompt }, prompt, target, deadline: Date.now() + timeoutMs, totalMs: timeoutMs, mandatory }; return; } if (mode === "skip") return; if (view.viewer === target) { if (mode === "silent") { view.pending = { type: "awaits", atom: { type: "请求回应", requestType, target }, prompt: SILENT_RESPONSE_PROMPT, target, responseMode: "silent", deadline: Date.now() + timeoutMs, totalMs: timeoutMs, mandatory }; return; } if (!prompt) return; view.pending = { type: "awaits", atom: { type: "请求回应", requestType, target, prompt }, prompt, target, responseMode: "normal", deadline: Date.now() + timeoutMs, totalMs: timeoutMs, mandatory }; } else view.pending = { type: "awaits", atom: { type: "请求回应", requestType, target }, prompt: { type: "confirm", title: "等待回应", cancelLabel: "" }, target, deadline: Date.now() + timeoutMs, totalMs: timeoutMs, mandatory }; } }; })); //#endregion //#region src/engine/data/card-defs/description.ts /** 按卡牌 name 取描述,无则返回空串 */ function getCardDescription(name) { return CARD_DESCRIPTIONS[name] ?? ""; } var CARD_DESCRIPTIONS; var init_description = __esmMin((() => { CARD_DESCRIPTIONS = { 杀: "对攻击范围内的一名角色使用,目标可出闪抵消。默认每回合限一次。", 闪: "抵消一次杀的效果。", 桃: "濒死时回复 1 点体力,或自己的出牌阶段回复 1 点体力。", 酒: "出牌阶段使用,本回合下一张杀伤害+1;或濒死时当桃用。", 过河拆桥: "弃置目标区域内一张牌(无距离限制)。", 顺手牵羊: "获取目标区域内一张牌(距离限制1)。", 无中生有: "摸两张牌。", 桃园结义: "所有角色回复 1 点体力。", 借刀杀人: "令目标对其攻击范围内的另一角色出杀。", 决斗: "与目标轮流出杀,先不出杀的一方受 1 点伤害。", 南蛮入侵: "所有其他角色需出杀,否则受 1 点伤害。", 万箭齐发: "所有其他角色需出闪,否则受 1 点伤害。", 五谷丰登: "从牌堆亮出等同于存活角色数的牌,每名角色依次拿一张。", 乐不思蜀: "延时锦囊,判定非红桃则跳过出牌阶段。", 无懈可击: "抵消一张锦囊牌的效果。", 闪电: "延时锦囊,判定黑桃2-9则造成 3 点伤害。", 兵粮寸断: "延时锦囊,判定非草花则跳过摸牌阶段。", 铁索连环: "锦囊,横置或重置一至两名角色的连环状态;或重铸(弃此牌摸一张)。连环状态下受到属性伤害时传导至所有连环角色。", 火攻: "锦囊,目标展示一张手牌,若你弃置一张同花色手牌,对其造成 1 点火焰伤害。", 诸葛连弩: "锁定技,出杀次数不限。", 青釭剑: "锁定技,你使用杀无视目标防具。", 寒冰剑: "当你使用杀造成伤害时,可改为弃置目标两张牌。", 雌雄双股剑: "当你使用杀指定异性目标时,可令其选择弃一张手牌或让你摸一张牌。", 贯石斧: "杀被闪后可弃2张牌强命。", 青龙偃月刀: "杀被闪后可再出一张杀。", 丈八蛇矛: "你可将两张手牌当一张杀使用或打出。", 方天画戟: "当你使用杀是手牌最后一张时,可额外指定至多两个目标。", 麒麟弓: "当你使用杀造成伤害时,可弃置目标一匹坐骑。", 八卦阵: "防具,需出闪时可判定,红牌视为闪。", 仁王盾: "防具,锁定技,对你使用的黑色杀无效。", 赤兔: "进攻马,距离-1。", 紫骍: "进攻马,距离-1。", 大宛: "进攻马,距离-1。", 的卢: "防御马,距离+1。", 绝影: "防御马,距离+1。", 爪黄飞电: "防御马,距离+1。", 古锭刀: "锁定技,当你使用杀造成伤害时,若目标没有手牌,此伤害+1。", 朱雀羽扇: "你可将一张普通杀当火杀使用或打出。", 藤甲: "锁定技,南蛮入侵/万箭齐发/普通杀对你无效;当你受到火焰伤害时,此伤害+1。", 白银狮子: "锁定技,当你受到大于1点的伤害时,伤害改为1点;当你失去装备区里的白银狮子后,回复1点体力。", 骅骝: "防御马,距离+1。" }; })); //#endregion //#region src/engine/core/deck.ts function cardType(name) { const t = DEF_INDEX.get(name); if (!t) throw new Error(`deck.ts: 未知牌名「${name}」,未在 CardDef 注册表中找到`); return t; } function createStandardDeck() { const deck = []; let seq = 0; for (const [name, suit, rank, opts] of [...STANDARD_DECK, ...JUNZHENG_DECK]) { const { type, subtype } = cardType(name); const description = getCardDescription(name); const card = { name, type, subtype, suit, color: suitColor(suit), rank, description, id: `${name}-${suit}${rank}-${seq++}` }; const range = equipmentRangeMap.get(name); if (range != null) card.range = range; const meta = DEF_INDEX.get(name); if (meta?.slashUnlimited) card.slashUnlimited = true; if (meta?.slashTargetBonusWhenLastCard != null) card.slashTargetBonusWhenLastCard = meta.slashTargetBonusWhenLastCard; if (opts?.trickSubtype) card.trickSubtype = opts.trickSubtype; if (opts?.damageType) card.damageType = opts.damageType; deck.push(card); } return deck; } function shuffle(deck, rng) { const result = [...deck]; for (let i = result.length - 1; i > 0; i--) { const j = rng.nextInt(i + 1); [result[i], result[j]] = [result[j], result[i]]; } return result; } var equipmentRangeMap, STANDARD_DECK, JUNZHENG_DECK, DEF_INDEX; var init_deck = __esmMin((() => { init_types(); init_equipment(); init_description(); init_card_defs(); equipmentRangeMap = /* @__PURE__ */ new Map(); for (const def of 装备牌列表) if (typeof def.range === "number") equipmentRangeMap.set(def.name, def.range); STANDARD_DECK = [ [ "桃园结义", "♥", "A" ], [ "万箭齐发", "♥", "A" ], [ "闪", "♥", "2" ], [ "闪", "♥", "2" ], [ "桃", "♥", "3" ], [ "五谷丰登", "♥", "3" ], [ "桃", "♥", "4" ], [ "五谷丰登", "♥", "4" ], [ "麒麟弓", "♥", "5" ], [ "赤兔", "♥", "5" ], [ "桃", "♥", "6" ], [ "乐不思蜀", "♥", "6", { trickSubtype: "延时锦囊" } ], [ "桃", "♥", "7" ], [ "无中生有", "♥", "7" ], [ "桃", "♥", "8" ], [ "无中生有", "♥", "8" ], [ "桃", "♥", "9" ], [ "无中生有", "♥", "9" ], [ "杀", "♥", "10" ], [ "杀", "♥", "10" ], [ "杀", "♥", "J" ], [ "无中生有", "♥", "J" ], [ "桃", "♥", "Q" ], [ "过河拆桥", "♥", "Q" ], [ "闪电", "♥", "Q", { trickSubtype: "延时锦囊" } ], [ "闪", "♥", "K" ], [ "爪黄飞电", "♥", "K" ], [ "决斗", "♠", "A" ], [ "闪电", "♠", "A", { trickSubtype: "延时锦囊" } ], [ "雌雄双股剑", "♠", "2" ], [ "八卦阵", "♠", "2" ], [ "寒冰剑", "♠", "2" ], [ "过河拆桥", "♠", "3" ], [ "顺手牵羊", "♠", "3" ], [ "过河拆桥", "♠", "4" ], [ "顺手牵羊", "♠", "4" ], [ "青龙偃月刀", "♠", "5" ], [ "绝影", "♠", "5" ], [ "乐不思蜀", "♠", "6", { trickSubtype: "延时锦囊" } ], [ "青釭剑", "♠", "6" ], [ "杀", "♠", "7" ], [ "南蛮入侵", "♠", "7" ], [ "杀", "♠", "8" ], [ "杀", "♠", "8" ], [ "杀", "♠", "9" ], [ "杀", "♠", "9" ], [ "杀", "♠", "10" ], [ "杀", "♠", "10" ], [ "顺手牵羊", "♠", "J" ], [ "无懈可击", "♠", "J", { trickSubtype: "响应锦囊" } ], [ "过河拆桥", "♠", "Q" ], [ "丈八蛇矛", "♠", "Q" ], [ "南蛮入侵", "♠", "K" ], [ "大宛", "♠", "K" ], [ "诸葛连弩", "♦", "A" ], [ "决斗", "♦", "A" ], [ "闪", "♦", "2" ], [ "闪", "♦", "2" ], [ "闪", "♦", "3" ], [ "顺手牵羊", "♦", "3" ], [ "闪", "♦", "4" ], [ "顺手牵羊", "♦", "4" ], [ "闪", "♦", "5" ], [ "贯石斧", "♦", "5" ], [ "杀", "♦", "6" ], [ "闪", "♦", "6" ], [ "杀", "♦", "7" ], [ "闪", "♦", "7" ], [ "杀", "♦", "8" ], [ "闪", "♦", "8" ], [ "杀", "♦", "9" ], [ "闪", "♦", "9" ], [ "杀", "♦", "10" ], [ "闪", "♦", "10" ], [ "闪", "♦", "J" ], [ "闪", "♦", "J" ], [ "桃", "♦", "Q" ], [ "方天画戟", "♦", "Q" ], [ "无懈可击", "♦", "Q", { trickSubtype: "响应锦囊" } ], [ "杀", "♦", "K" ], [ "紫骍", "♦", "K" ], [ "决斗", "♣", "A" ], [ "诸葛连弩", "♣", "A" ], [ "杀", "♣", "2" ], [ "八卦阵", "♣", "2" ], [ "仁王盾", "♣", "2" ], [ "杀", "♣", "3" ], [ "过河拆桥", "♣", "3" ], [ "杀", "♣", "4" ], [ "过河拆桥", "♣", "4" ], [ "杀", "♣", "5" ], [ "的卢", "♣", "5" ], [ "杀", "♣", "6" ], [ "乐不思蜀", "♣", "6", { trickSubtype: "延时锦囊" } ], [ "杀", "♣", "7" ], [ "南蛮入侵", "♣", "7" ], [ "杀", "♣", "8" ], [ "杀", "♣", "8" ], [ "杀", "♣", "9" ], [ "杀", "♣", "9" ], [ "杀", "♣", "10" ], [ "杀", "♣", "10" ], [ "杀", "♣", "J" ], [ "杀", "♣", "J" ], [ "借刀杀人", "♣", "Q" ], [ "无懈可击", "♣", "Q", { trickSubtype: "响应锦囊" } ], [ "借刀杀人", "♣", "K" ], [ "无懈可击", "♣", "K", { trickSubtype: "响应锦囊" } ] ]; JUNZHENG_DECK = [ [ "无懈可击", "♥", "A", { trickSubtype: "响应锦囊" } ], [ "火攻", "♥", "2" ], [ "火攻", "♥", "3" ], [ "杀", "♥", "4", { damageType: "火焰" } ], [ "桃", "♥", "5" ], [ "桃", "♥", "6" ], [ "杀", "♥", "7", { damageType: "火焰" } ], [ "闪", "♥", "8" ], [ "闪", "♥", "9" ], [ "杀", "♥", "10", { damageType: "火焰" } ], [ "闪", "♥", "J" ], [ "闪", "♥", "Q" ], [ "无懈可击", "♥", "K", { trickSubtype: "响应锦囊" } ], [ "白银狮子", "♣", "A" ], [ "藤甲", "♣", "2" ], [ "酒", "♣", "3" ], [ "兵粮寸断", "♣", "4", { trickSubtype: "延时锦囊" } ], [ "杀", "♣", "5", { damageType: "雷电" } ], [ "杀", "♣", "6", { damageType: "雷电" } ], [ "杀", "♣", "7", { damageType: "雷电" } ], [ "杀", "♣", "8", { damageType: "雷电" } ], [ "酒", "♣", "9" ], [ "铁索连环", "♣", "10" ], [ "铁索连环", "♣", "J" ], [ "铁索连环", "♣", "Q" ], [ "铁索连环", "♣", "K" ], [ "古锭刀", "♠", "A" ], [ "藤甲", "♠", "2" ], [ "酒", "♠", "3" ], [ "杀", "♠", "4", { damageType: "雷电" } ], [ "杀", "♠", "5", { damageType: "雷电" } ], [ "杀", "♠", "6", { damageType: "雷电" } ], [ "杀", "♠", "7", { damageType: "雷电" } ], [ "杀", "♠", "8", { damageType: "雷电" } ], [ "酒", "♠", "9" ], [ "兵粮寸断", "♠", "10", { trickSubtype: "延时锦囊" } ], [ "铁索连环", "♠", "J" ], [ "铁索连环", "♠", "Q" ], [ "无懈可击", "♠", "K", { trickSubtype: "响应锦囊" } ], [ "朱雀羽扇", "♦", "A" ], [ "桃", "♦", "2" ], [ "桃", "♦", "3" ], [ "杀", "♦", "4", { damageType: "火焰" } ], [ "杀", "♦", "5", { damageType: "火焰" } ], [ "闪", "♦", "6" ], [ "闪", "♦", "7" ], [ "闪", "♦", "8" ], [ "酒", "♦", "9" ], [ "闪", "♦", "10" ], [ "闪", "♦", "J" ], [ "火攻", "♦", "Q" ], [ "骅骝", "♦", "K" ] ]; DEF_INDEX = /* @__PURE__ */ new Map(); for (const def of [ ...基本牌列表, ...锦囊牌列表, ...装备牌列表 ]) DEF_INDEX.set(def.name, { type: def.type, subtype: def.subtype, slashUnlimited: def.slashUnlimited, slashTargetBonusWhenLastCard: def.slashTargetBonusWhenLastCard }); })); //#endregion //#region src/engine/atoms/选将.ts var DEFAULT_SKILLS, IDENTITY_COUNTS, 抽身份, 初始化洗牌, 发牌, 选将询问, 并行选将, 亮将; var init_选将 = __esmMin((() => { init_types(); init_rng(); init_deck(); init_apply(); init_timeout(); DEFAULT_SKILLS = [ "回合管理", "装备通用", "使用牌", "打出牌", "铁索连环" ]; IDENTITY_COUNTS = { 2: { 主公: 1, 忠臣: 0, 反贼: 1, 内奸: 0 }, 3: { 主公: 1, 忠臣: 0, 反贼: 1, 内奸: 1 }, 4: { 主公: 1, 忠臣: 1, 反贼: 1, 内奸: 1 }, 5: { 主公: 1, 忠臣: 1, 反贼: 2, 内奸: 1 }, 6: { 主公: 1, 忠臣: 1, 反贼: 3, 内奸: 1 }, 7: { 主公: 1, 忠臣: 2, 反贼: 3, 内奸: 1 }, 8: { 主公: 1, 忠臣: 2, 反贼: 4, 内奸: 1 } }; 抽身份 = { type: "抽身份", validate: (state, atom) => { if (state.players.length === 0) return "没有玩家"; if (!IDENTITY_COUNTS[atom.playerCount]) return `不支持的玩家数量: ${atom.playerCount}`; return null; }, apply(state, atom) { const { playerCount, seed } = atom; const rng = createRng(seed); const counts = IDENTITY_COUNTS[playerCount]; const identities = []; for (const [role, count] of Object.entries(counts)) for (let i = 0; i < count; i++) identities.push(role); for (let i = identities.length - 1; i > 0; i--) { const j = rng.nextInt(i + 1); const tmp = identities[i]; identities[i] = identities[j]; identities[j] = tmp; } const lordIndex = identities.indexOf("主公"); const rotated = lordIndex > 0 ? [...identities.slice(lordIndex), ...identities.slice(0, lordIndex)] : identities; for (let i = 0; i < state.players.length; i++) { const id = rotated[i] || "未知"; state.players[i].identity = id; } } }; 初始化洗牌 = { type: "初始化洗牌", validate: () => null, apply(state, atom) { const rng = createRng(atom.seed); const allCards = shuffle(createStandardDeck(), rng); const keys = Object.keys(state.cardMap); for (const key of keys) delete state.cardMap[key]; for (const card of allCards) state.cardMap[card.id] = card; state.zones.deck.length = 0; for (const card of allCards) state.zones.deck.push(card.id); state.rngSeed = atom.seed; }, toViewEvents() { return { ownerViews: /* @__PURE__ */ new Map(), othersView: { type: "初始化洗牌", deckCount: createStandardDeck().length } }; }, applyView(view, event) { if (view.zones) view.zones.deckCount = event.deckCount ?? view.zones.deckCount; } }; 发牌 = { type: "发牌", validate: (state) => { if (state.players.length === 0) return "没有玩家"; if (state.zones.deck.length === 0) return "牌堆为空"; return null; }, apply(state, atom) { const { handSize } = atom; let cursor = 0; for (const p of state.players) { const drawn = state.zones.deck.slice(cursor, cursor + handSize); p.hand.push(...drawn); cursor += handSize; } state.zones.deck = state.zones.deck.slice(cursor); }, toViewEvents(state, atom) { const { handSize } = atom; const ownerViews = /* @__PURE__ */ new Map(); let cursor = 0; for (const p of state.players) { const cards = state.zones.deck.slice(cursor, cursor + handSize).map((id) => state.cardMap[id]).filter(Boolean); cursor += handSize; ownerViews.set(p.index, { type: "发牌", handSize, cards }); } return { ownerViews, othersView: { type: "发牌", handSize } }; }, applyView(view, event) { const handSize = event.handSize ?? 4; for (const p of view.players) { p.handCount += handSize; if (event.cards && p.hand) p.hand.push(...event.cards); } const total = view.players.length * handSize; if (view.zones) view.zones.deckCount = Math.max(0, view.zones.deckCount - total); } }; 选将询问 = { type: "选将询问", validate(state, atom) { if (!state.players[atom.target]) return `target ${atom.target} not found`; if (!Array.isArray(atom.candidates) || atom.candidates.length === 0) return "candidates required"; return null; }, apply(_state) {}, toViewEvents(state, atom) { const target = atom.target; const candidates = atom.candidates; const timeoutMs = resolveTimeoutMs(state, 60); const ownerView = { type: "选将询问", target, candidates, timeoutMs, pending: { startTime: state.clock.now(), deadline: state.clock.now() + timeoutMs, prompt: { type: "chooseCharacter", title: "请选择武将", candidates } } }; return { ownerViews: new Map([[target, ownerView]]), othersView: { type: "等待选将", waitingFor: target, waitingForName: "主公" } }; }, applyView(view, event) { const target = event.target; const candidates = event.candidates ?? []; const timeoutMs = event.timeoutMs ?? 6e4; if (view.viewer === target) view.pending = { type: "awaits", atom: { type: "选将询问", target, candidates }, prompt: { type: "chooseCharacter", title: "请选择武将", candidates }, target, deadline: Date.now() + timeoutMs, totalMs: timeoutMs }; }, pending: { async onTimeout(state, atom) { const target = atom.target; const p = state.players[target]; if (!p || p.character) return; const candidates = atom.candidates; const taken = new Set(state.players.map((pl) => pl.character)); const available = candidates.filter((c) => !taken.has(c.name)); const pool = available.length > 0 ? available : candidates; const pick = pool[createRng(state.seq ^ target * 2654435761).nextInt(pool.length)]; await applyAtom(state, { type: "分配武将", target, character: pick.name, skills: [...DEFAULT_SKILLS, ...pick.skills] }); }, prompt: { type: "chooseCharacter", title: "请选择武将", candidates: [] }, timeout: 60 }, effect: { blockUntilDone: true, duration: 200 } }; 并行选将 = { type: "并行选将", validate(state, atom) { if (!Array.isArray(atom.selections) || atom.selections.length === 0) return "selections required"; for (const s of atom.selections) { if (!state.players[s.target]) return `target ${s.target} not found`; if (!Array.isArray(s.candidates) || s.candidates.length === 0) return `candidates required for target ${s.target}`; } return null; }, apply(_state) {}, toViewEvents(state, atom) { const selections = atom.selections; const timeoutMs = resolveTimeoutMs(state, 60); const ownerViews = /* @__PURE__ */ new Map(); for (const s of selections) ownerViews.set(s.target, { type: "并行选将", selections: selections.map((sel) => ({ target: sel.target, candidates: sel.candidates })), timeoutMs, pending: { startTime: state.clock.now(), deadline: state.clock.now() + timeoutMs, prompt: { type: "chooseCharacter", title: "请选择武将", candidates: s.candidates } } }); return { ownerViews, othersView: { type: "等待选将", waitingFor: -1, waitingForName: "其他玩家" } }; }, applyView(view, event) { const mySelection = (event.selections ?? []).find((s) => s.target === view.viewer); if (mySelection) { const timeoutMs = event.timeoutMs ?? 6e4; view.pending = { type: "awaits", atom: { type: "选将询问", target: mySelection.target, candidates: mySelection.candidates }, prompt: { type: "chooseCharacter", title: "请选择武将", candidates: mySelection.candidates }, target: mySelection.target, deadline: Date.now() + timeoutMs, totalMs: timeoutMs }; } }, pending: { onTimeout: async () => {}, prompt: { type: "chooseCharacter", title: "请选择武将", candidates: [] }, timeout: 60 }, parallelSplit(atom) { return atom.selections.map((s) => ({ target: s.target, slotAtom: { type: "选将询问", target: s.target, candidates: s.candidates } })); }, effect: { blockUntilDone: true, duration: 200 } }; 亮将 = { type: "亮将", validate(state, atom) { if (!Array.isArray(atom.assignments) || atom.assignments.length === 0) return "assignments required"; for (const a of atom.assignments) { if (!state.players[a.target]) return `target ${a.target} not found`; if (!a.character) return "character required"; } return null; }, apply(_state) {}, toViewEvents(state, atom) { const view = { type: "亮将", assignments: atom.assignments }; return { ownerViews: /* @__PURE__ */ new Map(), othersView: view }; }, applyView(view, event) { const assignments = event.assignments ?? []; for (const a of assignments) { const p = view.players.find((q) => q.index === a.target); if (!p) continue; p.character = a.character; p.name = a.character; p.faction = a.faction; p.skills = a.skills; if (typeof a.maxHealth === "number") p.maxHealth = a.maxHealth; if (typeof a.health === "number") p.health = a.health; } } }; })); //#endregion //#region src/engine/atoms/选择目标时.ts var 选择目标时; var init_选择目标时 = __esmMin((() => { 选择目标时 = { type: "选择目标时", validate(state, atom) { if (!state.players[atom.source]) return `source ${atom.source} not found`; for (const t of atom.targets) if (!state.players[t]) return `target ${t} not found`; return null; }, apply() {}, effect: { animation: "highlight", duration: 400 }, toViewEvents(state, atom) { const cardName = state.cardMap[atom.cardId]?.name ?? atom.cardId; const view = { type: "选择目标时", source: atom.source, cardId: atom.cardId, cardName, targets: atom.targets }; return { ownerViews: /* @__PURE__ */ new Map(), othersView: view }; }, applyView() {} }; })); //#endregion //#region src/engine/atoms/重洗.ts var 重洗; var init_重洗 = __esmMin((() => { init_rng(); 重洗 = { type: "重洗", validate(state) { if (state.zones.discardPile.length === 0) return "discardPile is empty"; return null; }, apply(state) { const combined = [...state.zones.deck, ...state.zones.discardPile]; const rng = createRng(state.rngSeed); for (let i = combined.length - 1; i > 0; i--) { const j = rng.nextInt(i + 1); [combined[i], combined[j]] = [combined[j], combined[i]]; } state.zones.deck = combined; state.zones.discardPile = []; state.rngSeed = rng.getState(); }, effect: { sound: "shuffle", animation: "flip", duration: 400 }, toViewEvents(state) { const totalCards = state.zones.deck.length + state.zones.discardPile.length; return { ownerViews: /* @__PURE__ */ new Map(), othersView: { type: "重洗", totalCards, newDeckCount: totalCards, newDiscardPileCount: 0 } }; }, applyView(view, event) { if (view.zones) { view.zones.deckCount = event.newDeckCount ?? view.zones.deckCount + view.zones.discardPileCount; view.zones.discardPileCount = event.newDiscardPileCount ?? 0; } }, toViewLog() { return { player: -1, text: "重洗牌堆" }; } }; })); //#endregion //#region src/engine/atoms/阶段开始.ts var VALID_PHASES, 阶段开始; var init_阶段开始 = __esmMin((() => { VALID_PHASES = [ "准备", "判定", "摸牌", "出牌", "弃牌", "回合结束" ]; 阶段开始 = { type: "阶段开始", validate(state, atom) { if (!state.players[atom.player]) return `player ${atom.player} not found`; if (!VALID_PHASES.includes(atom.phase)) return `invalid phase ${atom.phase}`; return null; }, apply(state, atom) { const phase = atom.phase; state.phase = phase; state.turn.phase = phase; }, toViewEvents(_state, atom) { const view = { type: "阶段开始", player: atom.player, phase: atom.phase }; return { ownerViews: /* @__PURE__ */ new Map(), othersView: view }; }, effect: { sound: "phase_start", duration: 1e3 }, applyView(view, event) { view.phase = event.phase; view.turn.phase = event.phase; }, toViewLog(event) { return { player: event.player, text: `${event.phase}阶段开始` }; } }; })); //#endregion //#region src/engine/atoms/阶段结束.ts var 阶段结束; var init_阶段结束 = __esmMin((() => { 阶段结束 = { type: "阶段结束", validate(state, atom) { if (!state.players[atom.player]) return `player ${atom.player} not found`; return null; }, apply(_state) {}, toViewEvents(_state, atom) { const view = { type: "阶段结束", player: atom.player, phase: atom.phase }; return { ownerViews: /* @__PURE__ */ new Map(), othersView: view }; }, effect: { sound: "phase_end", duration: 600 }, applyView(view, event) { const phase = event.phase; if (view.phase !== phase) view.phase = phase; }, toViewLog(event) { return { player: event.player, text: `${event.phase}阶段结束` }; } }; })); //#endregion //#region src/engine/atoms/阶段间.ts /** 校验 player 存在(纯标记,不校验 from/to 合法性——编排函数前置保证)。 */ function validatePhaseBetween(state, atom) { if (!state.players[atom.player]) return `player ${atom.player} not found`; return null; } /** 无 before-hook 时静默(no-op 视图),有 before-hook 时发通知事件。 */ function phaseBetweenView(state, atom) { if (getBeforeHooks(state, "阶段间").length === 0) return { ownerViews: /* @__PURE__ */ new Map(), othersView: null }; const view = { type: "阶段间", player: atom.player, from: atom.from, to: atom.to }; return { ownerViews: /* @__PURE__ */ new Map(), othersView: view }; } var 阶段间; var init_阶段间 = __esmMin((() => { init_skill(); 阶段间 = { type: "阶段间", validate: validatePhaseBetween, apply() {}, toViewEvents(state, atom) { return phaseBetweenView(state, atom); }, applyView() {} }; })); //#endregion //#region src/engine/atoms/陷入濒死.ts var FEMALE_CHARACTERS, 陷入濒死; var init_陷入濒死 = __esmMin((() => { FEMALE_CHARACTERS = new Set([ "甄姬", "貂蝉", "黄月英", "大乔", "小乔", "孙尚香", "蔡文姬", "祝融", "界甄姬", "界貂蝉", "界黄月英", "界大乔", "界小乔", "界孙尚香", "界蔡文姬", "界祝融", "界张春华", "界王异", "界夏侯氏", "界郭皇后", "界吴国太", "界蔡夫人", "界伏皇后" ]); 陷入濒死 = { type: "陷入濒死", validate(state, atom) { if (!state.players[atom.target]) return `target not found`; return null; }, apply() {}, effect: { animation: "flash_red", duration: 600 }, toViewEvents(state, atom) { const target = state.players[atom.target]; const sound = FEMALE_CHARACTERS.has(target?.character ?? "") ? "sos_female" : "sos_male"; const view = { type: "陷入濒死", target: atom.target, effect: { sound } }; return { ownerViews: /* @__PURE__ */ new Map(), othersView: view }; }, applyView() {} }; })); //#endregion //#region src/engine/atoms/展示事件.ts /** 纯展示型 atom 模板:无 validate/apply 副作用,空 applyView */ function displayOnly(type) { return { type, validate: () => null, apply: () => {}, applyView: () => {} }; } var 展示型atoms; var init_展示事件 = __esmMin((() => { 展示型atoms = { "等待选将": displayOnly("等待选将"), "打出": displayOnly("打出") }; })); //#endregion //#region src/engine/atoms/index.ts var atomMap; var init_atoms = __esmMin((() => { init_damage_timing(); init_death_timing(); init_judge_timing(); init_life_timing(); init_move_timing(); init_rank_timing(); init_statechange_timing(); init_下一玩家(); init_使用时(); init_使用结算结束后(); init_使用结算结束时(); init_出牌窗口(); init_分配武将(); init_判定(); init_收取判定牌(); init_加标签(); init_加标记(); init_卸下(); init_去标签(); init_去标记(); init_回合开始(); init_回合用量(); init_回合结束(); init_回合结束后(); init_回复体力(); init_声明打出时(); init_失去体力(); init_展示(); init_展示结束(); init_帧参数赋值(); init_弃置(); init_归还暂存牌(); init_当作(); init_成为目标(); init_成为目标后(); init_打出牌时(); init_扣牌(); init_指定目标(); init_指定目标后(); init_摸牌(); init_整理牌堆(); init_检测有效性(); init_添加延时锦囊(); init_添加技能(); init_清过期标记(); init_生效前(); init_生效后(); init_生效时(); init_移出至暂存区(); init_移动牌(); init_移除延时锦囊(); init_移除技能(); init_结算帧入栈(); init_结算帧出栈(); init_给予(); init_置创牌(); init_获得(); init_被抵消(); init_装备(); init_设上限(); init_设横置(); init_询问杀(); init_询问闪(); init_请求回应(); init_选将(); init_选择目标时(); init_重洗(); init_阶段开始(); init_阶段结束(); init_阶段间(); init_陷入濒死(); init_展示事件(); atomMap = { 伤害结算开始时, 造成伤害时, 受到伤害时, 造成伤害后, 受到伤害后, 伤害结算结束时, 伤害结算结束后, 亮身份牌前, 亮身份牌, 死亡时, 系统处理牌, 死亡后, 进入濒死状态时, 新的濒死状态时, 判定时, 判定牌生效前, 判定牌生效后, 扣减体力, 确定回复数值时, 回复体力后, 失去体力时, 失去体力后, 扣减体力前, 扣减体力时, 扣减体力后, 减上限后, 加上限后, 移动到目标区域前, 移动到目标区域后, 拼点扣置, 拼点亮出, 拼点后, 翻面后, 横置后, 武将牌明置后, 武将牌移除后, 游戏牌亮出后, 下一玩家, 使用时, 使用结算结束后, 使用结算结束时, 出牌窗口, 分配武将, 判定, 收取判定牌, 加标签, 加标记, 卸下, 去标签, 去标记, 回合开始, 回合用量, 回合结束, 回合结束后, 回复体力, 声明打出时, 失去体力, 展示, 展示结束, 帧参数赋值, 弃置, 归还暂存牌, 当作, 成为目标, 成为目标后, 打出牌时, 扣牌, 指定目标, 指定目标后, 摸牌, 整理牌堆, 检测有效性, 添加延时锦囊, 添加技能, 清过期标记, 生效前, 生效后, 生效时, 移出至暂存区, 移动牌, 移除延时锦囊, 移除技能, 结算帧入栈, 结算帧出栈, 给予, 置创牌, 获得, 被抵消, 装备, 设上限, 设横置, 询问杀, 询问闪, 请求回应, 抽身份, 初始化洗牌, 发牌, 选将询问, 并行选将, 亮将, 选择目标时, 重洗, 阶段开始, 阶段结束, 阶段间, 陷入濒死 }; })); //#endregion //#region src/engine/core/atom.ts /** 按 type 查 atom 定义。优先 atomMap(引擎 Atom),fallback 展示型atoms(ViewEvent-only type)。 */ function getAtomDef(type) { return atomMap[type] ?? 展示型atoms[type] ?? throwMissing(type); } function throwMissing(type) { throw new Error(`Atom "${type}" not found`); } /** 同步应用一个 atom 的 apply(不走 hook pipeline)。完整管线见 core/apply.ts:applyAtom。 */ function applyAtom$1(state, atom) { getAtomDef(atom.type).apply(state, atom); } /** * 解析 atom 的前端视图事件。 * 优先使用 AtomDefinition.toViewEvents;未实现则 fallback 为带 effect 的原始 atom。 * 当 ViewEvent.type 与 atom.type 不同时,自动设置 atomType 供前端查找 applyView。 */ function resolveViewEvents(state, atom) { const explicit = getAtomDef(atom.type).toViewEvents?.(state, atom); /** ViewEvent.type != atom.type 时补 atomType */ const ensureAtomType = (event) => event.type === atom.type ? event : { ...event, atomType: atom.type }; if (explicit) { const ownerViews = /* @__PURE__ */ new Map(); for (const [player, evt] of explicit.ownerViews) ownerViews.set(player, evt ? ensureAtomType(evt) : null); return { ownerViews, othersView: explicit.othersView ? ensureAtomType(explicit.othersView) : null }; } const viewEvent = { ...atom }; return { ownerViews: /* @__PURE__ */ new Map(), othersView: viewEvent }; } var init_atom = __esmMin((() => { init_atoms(); })); //#endregion //#region src/engine/core/skill-suppression.ts function getSuppressionRegistry(state) { let reg = suppressionRegistries.get(state); if (!reg) { reg = { providers: /* @__PURE__ */ new Set() }; suppressionRegistries.set(state, reg); } return reg; } /** * 注册一个非锁定技失效判定器(技能 onInit 时调用,与 registerSlashBlocker 同构)。 * 返回的取消注册函数应并入 onInit 返回的 unload,由 setSkillInstanceUnload 统一清理。 * provider 加入全局集合(不按 owner 桶化,原因见文件头注释),生命周期由技能实例卸载管理。 */ function registerSuppressionProvider(state, provider) { const reg = getSuppressionRegistry(state); reg.providers.add(provider); return () => { reg.providers.delete(provider); }; } /** * 判定某 owner 的某技能 hook 是否被压制。 * - 系统级(ownerId===TARGET_SYSTEM)永不压制。 * - 锁定技/防具/武器(Skill.isLocked=true)永不压制——规则:锁定技不受"非锁定技失效"影响。 * - 其余情况查 provider 集合,任一返回 true 即压制。 * 无法解析技能模块时保守不压制(避免误吞未知技能)。 */ function isHookSuppressed(state, ownerId, skillId) { if (ownerId < 0) return false; if (isSkillLocked(state, skillId)) return false; const set = getSuppressionRegistry(state).providers; for (const fn of set) if (fn(state, ownerId, skillId)) return true; return false; } var suppressionRegistries; var init_skill_suppression = __esmMin((() => { init_skill(); suppressionRegistries = /* @__PURE__ */ new WeakMap(); })); //#endregion //#region src/engine/util/invariants.ts /** * 检查「牌唯一归属」不变量:遍历所有牌区,统计每个 cardId 的出现位置。 * 若同一 cardId 出现超过 1 次,抛 Error(列出 cardId 与所有出现位置)。 * * 复杂度 O(n),n 为所有牌区的 cardId 引用总数(一局约百张,可接受)。 * * cardMap 与各区的「孤儿/悬空」关系(影子卡、转化牌、延时锦囊 pendingTricks 等) * 属合法边界,本次只识别、不抛错。 * * @throws Error 当某 cardId 在多个牌区重复出现时。 */ function assertCardInvariants(state) { const locations = /* @__PURE__ */ new Map(); const record = (cardId, loc) => { let arr = locations.get(cardId); if (arr === void 0) { arr = []; locations.set(cardId, arr); } arr.push(loc); }; for (const id of state.zones.deck) record(id, "牌堆"); for (const id of state.zones.discardPile) record(id, "弃牌堆"); for (const id of state.zones.processing) record(id, "处理区"); for (const p of state.players) { const tag = `玩家${p.index}`; for (const id of p.hand) record(id, `${tag}.手牌`); for (const slot of EQUIP_SLOTS$4) { const id = p.equipment[slot]; if (id !== void 0) record(id, `${tag}.装备(${slot})`); } } for (let i = 0; i < state.settlementStack.length; i++) { const frame = state.settlementStack[i]; const loc = `结算帧[${i}](${frame.skillId || "?"})`; for (const id of frame.cards) record(id, loc); } const dups = []; for (const [cardId, locs] of locations) if (locs.length > 1) { const name = state.cardMap[cardId]?.name ?? "?"; dups.push(` ${cardId}(${name}) ×${locs.length}: ${locs.join(" / ")}`); } if (dups.length > 0) throw new Error(`[牌唯一归属不变量] 检测到重复卡牌:\n${dups.join("\n")}`); } var EQUIP_SLOTS$4; var init_invariants = __esmMin((() => { EQUIP_SLOTS$4 = [ "武器", "防具", "进攻马", "防御马", "宝物" ]; })); //#endregion //#region src/engine/core/notify.ts /** 从 pending atom 中提取等待目标玩家(座次下标)。所有内置等待型 atom 都有 target 字段。 * 返回 TARGET_SYSTEM(-1)表示系统(开局 action),不对应任何真实玩家槽位。 * 注意:TARGET_SYSTEM 与广播型 target(TARGET_BROADCAST=-2)不同, * 广播型 slot 本身已携带 target=TARGET_BROADCAST,能被此函数准确提取。 * 出牌窗口 atom 用 player 字段而非 target,此处兼容。 */ function extractPendingTarget(atom) { if ("target" in atom && typeof atom.target === "number") return atom.target; if ("player" in atom && typeof atom.player === "number") return atom.player; return SYSTEM_OWNER$1; } /** 通知 session:state 已变更(每次 applyAtom 结束后触发)。 */ function notifyStateChange(state) { if (state.viewBuffering) return; state.onStateChange?.(); } /** 通知前端:某 pending slot 已 resolve(respond 完成 / 超时),前端应清除 view.pending。 * 事件流模式下 view.pending 不再由 buildView 每次重建,而由 applyView 增量维护, * 因此 slot 的删除(服务端静默 mutation)必须显式发事件,否则前端 pending 永驻。 * target<0 = 广播型 slot(如无懈可击),所有 viewer 都应清除。 */ function notifyPendingResolved(state, slot) { const target = extractPendingTarget(slot.atom); state.seq += 1; state.atomHistory.push({ kind: "notify", seq: state.seq, timestamp: state.clock.now() - state.startedAt, skillId: "", eventType: "pendingResolved", data: { target, atomType: slot.atom.type } }); notifyStateChange(state); } /** 推送 notify 事件(不改变 state) */ function pushNotify(state, event) { state.seq += 1; state.atomHistory.push({ kind: "notify", seq: state.seq, timestamp: state.clock.now() - state.startedAt, ...event }); } /** 记录 actionLog 条目 */ function logAction(state, message) { state.actionLog.push({ id: String(state.actionLog.length), timestamp: state.clock.now() - state.startedAt, message, baseSeq: message.baseSeq ?? -1 }); } var SYSTEM_OWNER$1; var init_notify = __esmMin((() => { init_types(); SYSTEM_OWNER$1 = -1; })); //#endregion //#region src/engine/core/apply.ts /** 多角色结算顺序排序(原则 §2.2b):从当前回合角色起按 ownerId 逆时针排列玩家 hook。 * 系统级 hook(ownerId<0)非"角色",不受多角色原则约束,统一排在最后——保持既有 * "系统级 hook 最后执行"语义(延时锦囊判定在勇略等玩家技能之后、濒死/死亡检查在 * 受伤害技能之后)。同一玩家的多个 hook 维持注册序(Array.sort 在 V8/Node 下稳定)。 * * 历史:仅 '摸牌' before-hook 与 '伤害结算结束后' after-hook 做了逆时针排序,其余 * atom 一律按注册插入序(从 0 号座起)遍历,在 currentPlayerIndex≠0 时违反多角色 * 结算原则——此处泛化到所有 atom。'伤害结算结束后' 原把系统级 hook(连环传导)排最前, * 经核实其传导/重置与玩家"伤害结算结束后"技能(遗计/节命/忘隙)无共享状态、且每次 * atom 仅命中单一目标玩家,系统级排前/排后功能等价,故统一为排最后。返回新数组, * 不修改注册表中的原数组。 */ function sortHooksCounterclockwise(hooks, state) { if (hooks.length <= 1) return [...hooks]; const cur = state.currentPlayerIndex; const n = state.players.length; return [...hooks].sort((a, b) => { const aSys = a.ownerId < 0; const bSys = b.ownerId < 0; if (aSys && bSys) return 0; if (aSys) return 1; if (bSys) return -1; return (a.ownerId - cur + n) % n - (b.ownerId - cur + n) % n; }); } /** 运行 after hooks:按多角色结算顺序(逆时针,系统级排最后)遍历。 */ async function runAfterHooks(state, atom) { const sortedHooks = sortHooksCounterclockwise(getAfterHooks(state, atom.type), state); for (const h of sortedHooks) { if (isHookSuppressed(state, h.ownerId, h.skillId)) continue; const curFrame = topFrame(state) ?? emptyFrame(); const afterCtx = { state, atom, ownerId: h.ownerId, frame: curFrame, params: curFrame.params }; await h.handler(afterCtx); } } /** 运行判定改判钩子(鬼才/鬼道):从当前判定角色起,逆时针依次询问每个 * 存活玩家的改判能力。在 判定牌生效前 atom 的 afterApply 阶段调用。 * * 与普通 after-hook 的区别:遍历顺序不依赖 hook 注册序,而由判定目标座次 * 逆时针推导,确保「改判方座次靠后于消费方也能生效」——旧实现挂在判定 * after-hook 靠注册序混排,改判方须座次靠前才能生效,此处彻底修正。 */ async function runJudgeModifiers(state) { const atom = state.atomStack[state.atomStack.length - 1]; if (!atom) return; const player = atom.player; if (typeof player !== "number") return; const modifiers = getJudgeModifierMap(state); if (modifiers.size === 0) return; const n = state.players.length; for (let i = 0; i < n; i++) { const idx = (player - i + n) % n; const entry = modifiers.get(idx); if (!entry) continue; if (!state.players[idx]?.alive) continue; const curFrame = topFrame(state) ?? emptyFrame(); const afterCtx = { state, atom, ownerId: entry.ownerId, frame: curFrame, params: curFrame.params }; await entry.handler(afterCtx); } } /** * 应用一个 atom:走完整 pipeline(before hooks → validate → apply → emit event → after hooks → pending)。 * 等待型 atom 的 Promise 会挂起直到回应/超时。 */ async function applyAtom(state, atom) { state.atomStack.push(atom); let current = atom; let cancelled = false; const hooks = sortHooksCounterclockwise(getBeforeHooks(state, atom.type), state); for (const h of hooks) { if (isHookSuppressed(state, h.ownerId, h.skillId)) continue; const frame = topFrame(state) ?? emptyFrame(); const beforeCtx = { state, atom: current, ownerId: h.ownerId, frame, params: frame.params }; const result = await h.handler(beforeCtx); if (result === void 0) continue; if (result.kind === "cancel") { cancelled = true; break; } if (result.kind === "modify") current = result.atom; } if (cancelled) { state.atomStack.pop(); pushNotify(state, { skillId: "", eventType: "atomCancelled", data: { atomType: atom.type } }); notifyStateChange(state); return false; } const def = getAtomDef(current.type); const error = def.validate(state, current); if (error !== null) { state.atomStack.pop(); throw new Error(`applyAtom validate 失败: ${current.type} → ${error}`); } const viewEvents = resolveViewEvents(state, current); applyAtom$1(state, current); state.seq += 1; state.atomHistory.push({ kind: "atom", seq: state.seq, timestamp: state.clock.now() - state.startedAt, atom: current, viewEvents }); if (def.pending) { let targets; let slotAtoms; const splits = def.parallelSplit?.(current); if (splits && splits.length > 0) { targets = splits.map((s) => s.target); slotAtoms = splits.map((s) => s.slotAtom); } else { targets = [extractPendingTarget(current)]; slotAtoms = [current]; } const pre = def.pending.preResolve?.(state, current) ?? null; const isSkip = pre === "skip"; const silentDelayMs = !isSkip && pre && typeof pre === "object" ? pre.delayMs : void 0; if (isSkip) notifyStateChange(state); else { const slotPromises = []; for (let i = 0; i < slotAtoms.length; i++) { const slotAtom = slotAtoms[i]; const slotTarget = targets[i]; const slotDef = slotAtom.type !== current.type ? getAtomDef(slotAtom.type) : def; slotPromises.push(createAndAwaitSlot(state, slotAtom, slotDef, slotTarget, silentDelayMs)); } await Promise.all(slotPromises); } if (def.afterApply) await def.afterApply(state, current); await runAfterHooks(state, current); if (def.afterHooks) def.afterHooks(state, current); state.atomStack.pop(); if (state.assertInvariants) assertCardInvariants(state); return true; } notifyStateChange(state); if (def.afterApply) await def.afterApply(state, current); await runAfterHooks(state, current); if (def.afterHooks) def.afterHooks(state, current); state.atomStack.pop(); if (state.assertInvariants) assertCardInvariants(state); return true; } /** 为单个 target 创建 PendingSlot 并 await 到它 resolve。 * silentDelayMs:卡牌回应 silent 模式下的固定短延时(不走 timeoutSec 缩放)。 * undefined = 正常(走 resolveTimeoutMs)。 */ function createAndAwaitSlot(state, atom, def, target, silentDelayMs) { return new Promise((resolve) => { const pending = def.pending; const atomTimeout = atom.timeout; const timeoutSec = typeof atomTimeout === "number" ? atomTimeout : pending.timeout; const timeoutMs = silentDelayMs ?? resolveTimeoutMs(state, timeoutSec, target < 0); let resolveCalled = false; let timedOut = false; let paused = false; const safeResolve = () => { if (resolveCalled) return; resolveCalled = true; cancelTimer(); resolve(); }; const slot = { atom, definition: def, startTime: state.clock.now() - state.startedAt, deadline: state.clock.now() - state.startedAt + timeoutMs, resolvedTimeoutMs: timeoutMs, createdSeq: state.seq, isBlocking: pending.isBlocking !== false, resolve: safeResolve, get isTimeout() { return timedOut; }, get isPaused() { return paused; }, pause() { if (timedOut) return; paused = true; cancelTimer(); } }; const fireTimeoutNow = async () => { if (state.pendingSlots.get(target) !== slot) return; if (paused) return; timedOut = true; cancelTimer(); try { await pending.onTimeout(state, atom); notifyStateChange(state); } finally { let deleted = false; if (state.pendingSlots.get(target) === slot) { state.pendingSlots.delete(target); deleted = true; } if (deleted) notifyPendingResolved(state, slot); safeResolve(); } }; slot._fireTimeoutNow = fireTimeoutNow; const cancelTimer = state.clock.schedule(timeoutMs, fireTimeoutNow); state.pendingSlots.set(target, slot); notifyStateChange(state); state.onExecuteSettle?.(); state.onExecuteSettle = null; }); } var init_apply = __esmMin((() => { init_atom(); init_skill(); init_skill_suppression(); init_invariants(); init_timeout(); init_notify(); init_frame(); })); //#endregion //#region src/engine/flows/life.ts /** 扣减体力子流程(decreaselife.md 三时机,被 runDamageFlow 和 runLoseLifeFlow 共用)。 * * 时机1 扣减体力前:酒诗②/连环条件检测/重置 * 时机2 扣减体力时:不屈 * 实质扣减:底层 扣减体力 atom(只扣 health,无伤害 hook) * 时机3 扣减体力后:伤逝 * * 濒死检查不在此处直接调 runDyingFlow——保持现有 系统规则 after-hook 触发逻辑。 * (runLoseLifeFlow 末尾的 失去体力 after-hook 会检查 health<=0。) * * source 供模块 A 的 runDamageFlow 透传(记录伤害来源),当前扣减 atom 不消费它。 */ async function runDecreaseLifeFlow(state, target, amount, source) { if (source === void 0) delete state.localVars["死亡/killer"]; else state.localVars["死亡/killer"] = source; await applyAtom(state, { type: "扣减体力前", target, amount }); await applyAtom(state, { type: "扣减体力时", target, amount }); await applyAtom(state, { type: "扣减体力", target, amount }); await applyAtom(state, { type: "扣减体力后", target, amount }); } var init_life = __esmMin((() => { init_apply(); init_life_timing(); })); //#endregion //#region src/engine/flows/damage.ts /** 伤害结算编排函数——对齐 damage.md 8 时机。 * * 时机1 伤害结算开始时:绝情(cancel 整个结算)/ 狂风大雾(修正伤害值或类型) * 时机2 造成伤害时:来源方加伤(裸衣/古锭刀/暗箭/酒)——modify amount * 时机3 受到伤害时:目标方减伤/防止(藤甲/白银狮子/天香/名士)——modify amount 或 cancel * 时机4 扣减体力:模块 M 的 runDecreaseLifeFlow(含扣减前/时/扣减/后四子时机) * 时机5 造成伤害后:来源方(狂骨/破军) * 时机6 受到伤害后:目标方(奸雄/反馈/遗计/刚烈) * 时机7 伤害结算结束时:天香摸牌/连环重置 * 时机8 伤害结算结束后:酒诗②/连环传导——可能触发新伤害(嵌套 runDamageFlow) * * cardId 为伤害来源牌(杀/决斗等),透传到各时机 atom(供 奸雄等 hook 读取伤害牌)。 */ async function runDamageFlow(state, source, target, baseAmount, cardId, damageType) { let amount = baseAmount; state.localVars[DAMAGE_AMOUNT_KEY] = amount; if (!await applyAtom(state, { type: "伤害结算开始时", source, target, amount, cardId, damageType })) return; amount = state.localVars[DAMAGE_AMOUNT_KEY]; source = state.localVars[DAMAGE_SOURCE_KEY]; state.localVars[DAMAGE_AMOUNT_KEY] = amount; await applyAtom(state, { type: "造成伤害时", source, target, amount, cardId, damageType }); amount = state.localVars[DAMAGE_AMOUNT_KEY]; state.localVars[DAMAGE_AMOUNT_KEY] = amount; const sufferResult = await applyAtom(state, { type: "受到伤害时", source, target, amount, cardId, damageType }); amount = state.localVars[DAMAGE_AMOUNT_KEY]; if (!sufferResult || amount <= 0) { await applyAtom(state, { type: "伤害结算结束时", source, target, amount: 0, cardId, damageType }); await applyAtom(state, { type: "伤害结算结束后", source, target, amount: 0, cardId, damageType }); return; } if (amount > 0) await runDecreaseLifeFlow(state, target, amount, source); await applyAtom(state, { type: "造成伤害后", source, target, amount, cardId, damageType }); await applyAtom(state, { type: "受到伤害后", source, target, amount, cardId, damageType }); await applyAtom(state, { type: "伤害结算结束时", source, target, amount, cardId, damageType }); await applyAtom(state, { type: "伤害结算结束后", source, target, amount, cardId, damageType }); } var init_damage = __esmMin((() => { init_apply(); init_life(); init_damage_timing(); })); //#endregion //#region src/engine/skills/cards/万箭齐发.ts var 万箭齐发_exports = /* @__PURE__ */ __exportAll({ arrowVolleyEffect: () => arrowVolleyEffect }); /** 万箭齐发的逐目标结算:询问闪 → 检查帧 cancelled → 伤害/抵消。 * 闪走 runUseFlow,resolve 设万箭齐发帧 cancelled=true;此处检查该字段。 */ async function resolveArrowVolley(ctx) { const { state, source, target, cardId } = ctx; await applyAtom(state, { type: "询问闪", target, source }); if (isCancelled(state, cardId, target)) await applyAtom(state, { type: "被抵消", source, target, cardId }); else { if (!state.players[target]?.alive) return; await runDamageFlow(state, source, target, 1, cardId); } } var arrowVolleyEffect; var init_万箭齐发 = __esmMin((() => { init_apply(); init_damage(); init_frame(); arrowVolleyEffect = { timing: "出牌阶段", target: { kind: "allOthers" }, resolve: resolveArrowVolley, prompt: { type: "useCard", title: "万箭齐发", cardFilter: { filter: (c) => c.name === "万箭齐发", min: 1, max: 1 } }, label: "万箭齐发", style: "danger" }; })); //#endregion //#region src/engine/flows/judge.ts /** 判定结算编排函数——对齐 judge.md / 出牌流程重设计.md 模块 H 四时机。 * * 时机1 判定时:咒缚 before-hook 可替换判定牌来源。 * 时机2 判定(翻牌):底层操作,牌堆顶→结算帧牌区。 * 时机3 判定牌生效前:鬼才/鬼道 改判(afterApply 调 runJudgeModifiers,逆时针从目标起, * 经 replaceJudgeCard 以两步「移动牌」atom 替换结算帧顶牌)。 * 时机4 判定牌生效后:天妒/洛神/屯田 获得判定牌 / 闪电·乐不思蜀 等消费方 after-hook 读牌。 * 收尾:记录最终判定牌 cardId 到 localVars,再把判定牌从结算帧移入弃牌堆 * (天妒/屯田 可能已在 生效后 拿走 → splice 为 no-op,但仍记录 cardId)。 * * 返回最终判定牌 cardId(可能被改判替换),供调用方读取判定结果。 * 牌堆为空(判定 atom apply 早退,未翻牌)时返回 undefined。 * * @param state 游戏状态 * @param player 判定目标(座次下标) * @param judgeType 判定类型(乐不思蜀/闪电/八卦阵/铁骑 等) * @returns 最终判定牌 cardId(牌堆空时为 undefined) */ async function runJudgeFlow(state, player, judgeType) { await applyAtom(state, { type: "判定时", player, judgeType }); await applyAtom(state, { type: "判定", player, judgeType }); const judgeCardId = topFrameCardId(state); delete state.localVars[JUDGE_FINAL_CARD_KEY]; await applyAtom(state, { type: "判定牌生效前", player, judgeType, cardId: judgeCardId }); const finalCardId = topFrameCardId(state); if (finalCardId !== void 0) state.localVars[JUDGE_FINAL_CARD_KEY] = finalCardId; await applyAtom(state, { type: "判定牌生效后", player, judgeType, cardId: finalCardId }); await cleanupJudgeCard(state, finalCardId); return state.localVars[JUDGE_FINAL_CARD_KEY]; } /** 结算帧牌区顶的牌 id(frame 优先,否则 processing);空则 undefined。 */ function topFrameCardId(state) { const frame = state.settlementStack[state.settlementStack.length - 1]; const cards = frame ? frame.cards : state.zones.processing; return cards.length > 0 ? cards[cards.length - 1] : void 0; } /** 改判:把当前判定牌移入弃牌堆,再把替换牌从 owner 手牌打出到处理区(成为新判定牌)。 * * 两步都走「移动牌」atom(而非直接 mutate frameCards):判定牌的进出必须经 view 通道投影, * 否则增量视图(processedView / 前端 viewReducer)与权威 buildView 漂移—— * 直接 mutate 会漏掉「替换牌离手(handCount)」与「原判定牌入弃牌堆(discardPileCount)」 * 两项公开信息,客户端一直显示张角/司马懿手里还有那张牌。 * * 先移除原判定牌再压入替换牌,保证帧顶(消费方读的判定牌)= 替换牌。 * 调用方需已保证替换牌在 owner 手牌中。 */ async function replaceJudgeCard(state, ownerId, replaceCardId) { const cur = frameCards(state); const originalJudgeId = cur[cur.length - 1]; if (originalJudgeId === void 0) return; await applyAtom(state, { type: "移动牌", cardId: originalJudgeId, from: { zone: "处理区" }, to: { zone: "弃牌堆" } }); await applyAtom(state, { type: "移动牌", cardId: replaceCardId, from: { zone: "手牌", player: ownerId }, to: { zone: "处理区" } }); } /** 把最终判定牌从结算帧移入弃牌堆。 * 按 finalCardId 精确定位(不盲取末尾):天妒/屯田 可能已在 判定牌生效后 拿走判定牌 * (结算帧空或已含其他嵌套牌),按 id 找不到即为 no-op,绝不误删帧内其他牌。 * 走「移动牌」atom 而非直接 mutate:弃牌堆 +1 与处理区 -1 都必须投影到增量视图。 * 最终判定牌 cardId 已由 runJudgeFlow 在 生效后 hook 之前写入 localVars(此处不再记录)。 */ async function cleanupJudgeCard(state, finalCardId) { if (finalCardId === void 0) return; const frame = state.settlementStack[state.settlementStack.length - 1]; if (!(frame ? frame.cards : state.zones.processing).includes(finalCardId)) return; await applyAtom(state, { type: "移动牌", cardId: finalCardId, from: { zone: "处理区" }, to: { zone: "弃牌堆" } }); } var JUDGE_FINAL_CARD_KEY; var init_judge = __esmMin((() => { init_apply(); init_frame(); JUDGE_FINAL_CARD_KEY = "判定/finalJudgeCardId"; })); //#endregion //#region src/engine/skills/cards/乐不思蜀.ts var 乐不思蜀_exports = /* @__PURE__ */ __exportAll({ indulgenceEffect: () => indulgenceEffect }); /** 乐不思蜀牌特有校验:任意其他角色、判定区无同名(无距离限制) */ function canUseIndulgence(state, ownerId, params) { const target = params.target ?? params.targets?.[0]; if (target === void 0) return "目标不合法"; if (target === ownerId) return "不能对自己使用"; if (!state.players[target]?.alive) return "目标已死亡"; if (state.players[target].pendingTricks.some((t) => t.name === "乐不思蜀")) return "目标判定区已有乐不思蜀"; return null; } /** 乐不思蜀的生效后效果:判定 → 非♥加跳过标签 → 移除延时锦囊 */ async function resolveIndulgence(ctx) { const { state, target } = ctx; await runJudgeFlow(state, target, "乐不思蜀"); const judgeCardId = state.localVars["判定/finalJudgeCardId"]; delete state.localVars["判定/finalJudgeCardId"]; const judgeCard = judgeCardId ? state.cardMap[judgeCardId] : void 0; if (judgeCard && judgeCard.suit !== "♥") await applyAtom(state, { type: "加标签", player: target, tag: SKIP_TAG$6 }); await applyAtom(state, { type: "移除延时锦囊", player: target, trickName: "乐不思蜀" }); } var SKIP_TAG$6, indulgenceEffect; var init_乐不思蜀 = __esmMin((() => { init_apply(); init_judge(); SKIP_TAG$6 = "乐不思蜀/跳过出牌"; indulgenceEffect = { timing: "出牌阶段", target: { kind: "other", min: 1, max: 1 }, delayed: true, skipPhase: { tag: SKIP_TAG$6, phase: "出牌" }, cancelledBy: { cardName: "无懈可击", broadcast: true }, canUse: canUseIndulgence, resolve: resolveIndulgence, prompt: { type: "useCardAndTarget", title: "乐不思蜀", cardFilter: { filter: (c) => c.name === "乐不思蜀", min: 1, max: 1 }, targetFilter: { min: 1, max: 1 } }, label: "乐不思蜀", style: "danger" }; })); //#endregion //#region src/engine/skills/cards/五谷丰登.ts var 五谷丰登_exports = /* @__PURE__ */ __exportAll({ bountifulHarvestEffect: () => bountifulHarvestEffect }); /** 从使用者开始,按座次旋转的所有存活玩家 */ function alivePlayersFrom(state, from) { const alive = state.players.filter((p) => p.alive); const n = alive.length; if (n === 0) return []; const fromPos = alive.findIndex((p) => p.index === from); if (fromPos < 0) return alive.map((p) => p.index); const result = []; for (let i = 0; i < n; i++) result.push(alive[(fromPos + i) % n].index); return result; } /** 选牌面板:弹 pickProcessingCard pending,超时兜底选候选列表第一张 */ async function runPickProcessingCard(state, target, revealedIds, frame) { const available = revealedIds.filter((id) => frameCards(state).includes(id)); if (available.length === 0) return; await applyAtom(state, { type: "请求回应", requestType: "五谷丰登/select", target, prompt: { type: "pickProcessingCard", title: "五谷丰登:选择 1 张牌", cards: available.map((id) => { const c = state.cardMap[id]; if (!c) return null; return { cardId: id, cardName: c.name, suit: c.suit, rank: c.rank }; }).filter((c) => c !== null) }, timeout: 20 }); const pickedId = state.localVars["五谷丰登/选择"]; delete state.localVars["五谷丰登/选择"]; const stillAvailable = revealedIds.filter((id) => frameCards(state).includes(id)); const cardId = pickedId && stillAvailable.includes(pickedId) ? pickedId : stillAvailable[0]; if (cardId) { await applyAtom(state, { type: "移动牌", cardId, from: { zone: "处理区" }, to: { zone: "手牌", player: target } }); const pickerName = state.players[target]?.name ?? `P${target}`; await applyAtom(state, { type: "帧参数赋值", key: "pickedBy", value: { ...frame.params.pickedBy ?? {}, [cardId]: pickerName } }); } } /** 五谷丰登的逐目标结算:首轮翻牌 → 无懈 → 选牌 */ async function resolveBountifulHarvest(ctx) { const { state, source, target } = ctx; if (!state.localVars["五谷丰登/已翻牌"]) { state.localVars["五谷丰登/已翻牌"] = true; const cardCount = alivePlayersFrom(state, source).length; const revealedIds = []; for (let i = 0; i < cardCount; i++) { if (state.zones.deck.length === 0) break; const topId = state.zones.deck[state.zones.deck.length - 1]; await applyAtom(state, { type: "移动牌", cardId: topId, from: { zone: "牌堆" }, to: { zone: "处理区" } }); revealedIds.push(topId); } state.localVars["五谷丰登/亮牌"] = revealedIds; await applyAtom(state, { type: "帧参数赋值", key: "revealedIds", value: revealedIds }); } const revealedIds = state.localVars["五谷丰登/亮牌"]; if (!revealedIds) return; if (!revealedIds.some((id) => frameCards(state).includes(id))) return; const frame = state.settlementStack[state.settlementStack.length - 1]; if (!revealedIds.some((id) => frameCards(state).includes(id))) return; await runPickProcessingCard(state, target, revealedIds, frame); } /** 五谷丰登的结算后回调:剩余亮出的牌入弃牌堆 + 清理 localVars */ async function onSettleBountifulHarvest(state, _source, _cardId) { const revealedIds = state.localVars["五谷丰登/亮牌"]; if (revealedIds) { for (const id of revealedIds) if (frameCards(state).includes(id)) await applyAtom(state, { type: "移动牌", cardId: id, from: { zone: "处理区" }, to: { zone: "弃牌堆" } }); } delete state.localVars["五谷丰登/亮牌"]; delete state.localVars["五谷丰登/已翻牌"]; delete state.localVars["五谷丰登/选择"]; } var bountifulHarvestEffect; var init_五谷丰登 = __esmMin((() => { init_apply(); init_frame(); bountifulHarvestEffect = { timing: "出牌阶段", target: { kind: "allPlayers" }, resolve: resolveBountifulHarvest, onSettle: onSettleBountifulHarvest, respond: { validate: (state, ownerId, params) => { const slot = state.pendingSlots.get(ownerId); if (!slot) return "当前不需要回应"; if (slot.atom.type !== "请求回应") return "当前不是五谷丰登选牌窗口"; if (slot.atom.requestType !== "五谷丰登/select") return "当前不是五谷丰登选牌窗口"; const cardId = params.cardId; if (!cardId) return "cardId required"; if (!state.localVars["五谷丰登/亮牌"]?.includes(cardId)) return "该牌不在可选范围"; if (!frameCards(state).includes(cardId)) return "该牌已被选走"; return null; }, execute: async (state, _ownerId, params) => { const cardId = params.cardId; state.localVars["五谷丰登/选择"] = cardId; } }, prompt: { type: "useCard", title: "五谷丰登", cardFilter: { filter: (c) => c.name === "五谷丰登", min: 1, max: 1 } }, label: "五谷丰登", style: "primary" }; })); //#endregion //#region src/engine/flows/cancel.ts /** * 询问抵消并返回栈顶帧是否被抵消。 * * 调用方负责 pushFrame(栈顶帧即被抵消的牌的结算帧)。 * 抵消牌的 respond action 走 runUseFlow,resolve 时设下层帧 cancelled=true; * 本函数通过 RESPONDED_KEY localVars 标记判断是否有人 respond。 * * @param config 抵消配置(cardName + broadcast) * @param source 被抵消牌的使用者 * @param target 被抵消牌的目标座次(定向询问时 = 杀目标;广播时 = 锦囊目标) * @returns 栈顶帧是否被抵消 */ async function promptCancel(state, config, source, target) { if (!config.broadcast) { state.localVars[RESPONDED_KEY] = false; await applyAtom(state, { type: "询问闪", target, source }); if (!isFrameCancelled(state)) { const frame = state.settlementStack[state.settlementStack.length - 1]; const dodgeIds = (frame ? frame.cards : state.zones.processing).filter((id) => state.cardMap[id]?.name === "闪"); if (dodgeIds.length > 0 && frame) { frame.cancelled = true; for (const id of dodgeIds) await applyAtom(state, { type: "移动牌", cardId: id, from: { zone: "处理区" }, to: { zone: "弃牌堆" } }); } } delete state.localVars[RESPONDED_KEY]; return isFrameCancelled(state); } while (true) { state.localVars[RESPONDED_KEY] = false; await applyAtom(state, { type: "请求回应", requestType: config.cardName, target: -2, cancelTarget: target, prompt: { type: "useCard", title: `是否打出${config.cardName}?`, cardFilter: { filter: (c) => c.name === config.cardName, min: 1, max: 1 } }, timeout: 10 }); if (!(state.localVars[RESPONDED_KEY] === true)) break; if (isFrameCancelled(state)) break; } delete state.localVars[RESPONDED_KEY]; return isFrameCancelled(state); } /** 读取栈顶帧 cancelled。每次重新索引避免 TS 类型窄化(跨 await mutation 不可跟踪)。 */ function isFrameCancelled(state) { const arr = state.settlementStack; const f = arr[arr.length - 1]; return f ? f.cancelled === true : false; } var RESPONDED_KEY; var init_cancel = __esmMin((() => { init_types(); init_apply(); RESPONDED_KEY = "抵消/已回应"; })); //#endregion //#region src/engine/rules/distance.ts /** * 环形座位距离(只算存活玩家)。 * 两人之间最短的顺时针/逆时针步数,跳过死亡玩家。 */ function seatDistance(aliveCount, fromIdx, toIdx) { if (aliveCount <= 1) return 0; const d = Math.abs(fromIdx - toIdx); return Math.min(d, aliveCount - d); } /** * 两人之间的实际距离(from 看 to)。 * = 座位距离 + to 的防御修正 - from 的进攻修正,最小 1。 * * 修正来源(技能/装备通过 player.vars 设置): * vars[DISTANCE_ATTACK_MOD_KEY] — 进攻马、马术等缩短距离的技能(正值=缩短) * vars[DISTANCE_DEFENSE_MOD_KEY] — 防御马等增加距离的技能(正值=增加) * * 扩展点:registerDistanceExemptor 注册的豁免器命中时返回 1(视为无距离限制)。 * 用于"特定条件下用牌无距离限制"类效果(界陷阵拼点赢后对其用牌无距离等)。 */ function effectiveDistance(state, from, to, cardId) { if (isDistanceExempted(state, from, to, cardId)) return 1; const alive = state.players.filter((p) => p.alive); const aliveFrom = alive.findIndex((p) => p.index === from); const aliveTo = alive.findIndex((p) => p.index === to); if (aliveFrom < 0 || aliveTo < 0) return Infinity; let dist = seatDistance(alive.length, aliveFrom, aliveTo); const attackMod = getDistanceAttackMod(state.players[from].vars) ?? 0; dist -= attackMod; const defenseMod = getDistanceDefenseMod(state.players[to].vars) ?? 0; dist += defenseMod; return Math.max(1, dist); } /** * from 是否能对 to 使用【杀】(出杀距离)。 * = effectiveDistance(from, to) <= from 的出杀范围 * * 出杀范围来源(技能/装备通过 player.vars 设置): * vars[DISTANCE_ATTACK_RANGE_KEY] — 武器攻击范围,默认 1(徒手)。诸葛连弩/青釭剑等在装备时设值 * * cardId 可选:用于 per-card 距离豁免(如界当先特定卡牌无距离、界武圣方片杀无距离)。 * 豁免逻辑由 registerAttackRangeExemptor 注册的 provider 提供,本函数不感知具体技能。 */ function inAttackRange(state, from, to, cardId) { if (from === to) return false; if (isAttackRangeExempted(state, from, to, cardId)) return true; const range = getDistanceAttackRange(state.players[from].vars) ?? 1; return effectiveDistance(state, from, to) <= range; } function getDistanceRegistry(state) { let reg = distanceRegistries.get(state); if (!reg) { reg = { exemptors: /* @__PURE__ */ new Map(), distanceExemptors: /* @__PURE__ */ new Map() }; distanceRegistries.set(state, reg); } return reg; } /** * 注册一个攻击范围豁免器(技能 onInit 时调用)。 * 返回的取消注册函数应并入 onInit 返回的 unload,由 setSkillInstanceUnload 统一清理。 */ function registerAttackRangeExemptor(state, ownerId, exemptor) { const reg = getDistanceRegistry(state); let set = reg.exemptors.get(ownerId); if (!set) { set = /* @__PURE__ */ new Set(); reg.exemptors.set(ownerId, set); } set.add(exemptor); return () => { const s = reg.exemptors.get(ownerId); if (s) { s.delete(exemptor); if (s.size === 0) reg.exemptors.delete(ownerId); } }; } /** 该 from→to 的此张【杀】是否被任一豁免器放行(任一返回 true 即放行)。 */ function isAttackRangeExempted(state, from, to, cardId) { const set = getDistanceRegistry(state).exemptors.get(from); if (!set) return false; for (const fn of set) if (fn(state, from, to, cardId)) return true; return false; } /** * 注册一个通用距离豁免器(技能 onInit 时调用)。 * 返回的取消注册函数应并入 onInit 返回的 unload,由 setSkillInstanceUnload 统一清理。 */ function registerDistanceExemptor(state, ownerId, exemptor) { const reg = getDistanceRegistry(state); let set = reg.distanceExemptors.get(ownerId); if (!set) { set = /* @__PURE__ */ new Set(); reg.distanceExemptors.set(ownerId, set); } set.add(exemptor); return () => { const s = reg.distanceExemptors.get(ownerId); if (s) { s.delete(exemptor); if (s.size === 0) reg.distanceExemptors.delete(ownerId); } }; } /** 该 from→to 是否被任一通用距离豁免器放行(任一返回 true 即视为距离 1)。 * cardId 可选:已知当前使用牌时传入,供 per-card 距离豁免(成略:同花色牌无距离)。 */ function isDistanceExempted(state, from, to, cardId) { const set = getDistanceRegistry(state).distanceExemptors.get(from); if (!set) return false; for (const fn of set) if (fn(state, from, to, cardId)) return true; return false; } var distanceRegistries; var init_distance = __esmMin((() => { init_vars_keys(); distanceRegistries = /* @__PURE__ */ new WeakMap(); })); //#endregion //#region src/engine/skills/cards/validate.ts var validate_exports = /* @__PURE__ */ __exportAll({ computeAutoTargets: () => computeAutoTargets, findLegalTargets: () => findLegalTargets, isCardBanned: () => isCardBanned, isLegalTarget: () => isLegalTarget, validateCardUse: () => validateCardUse }); function isCardBanned(state, ownerId, _cardName) { const player = state.players[ownerId]; if (!player) return false; if (player.tags.includes("义绝/禁出牌")) return true; if (state.turn.vars["往烈/禁出牌"] === ownerId) return true; return false; } function checkUsageLimit(state, ownerId, cardName, params) { if (cardName === "杀") { const cardId = params.cardId; if (!canSlash(state, ownerId, cardId)) return "出杀次数已达上限"; } return null; } function isLegalTarget(state, ownerId, cardName, target, cardId) { const effect = getCardEffect(cardName); if (!effect) return false; const spec = effect.target; if (target === ownerId) { if (spec.kind === "self" || spec.kind === "allPlayers" || spec.kind === "any") return true; if (spec.kind === "wounded") { const p = state.players[ownerId]; return !!p && p.health < p.maxHealth; } return false; } const targetPlayer = state.players[target]; if (!targetPlayer?.alive) return false; switch (spec.kind) { case "effect": case "self": return false; case "inAttackRange": return inAttackRange(state, ownerId, target); case "distance": if (state.players[ownerId]?.tags.includes("奇才/无距离限制")) return true; return effectiveDistance(state, ownerId, target, cardId) <= spec.dist; case "allOthers": case "allPlayers": case "other": case "any": return true; case "wounded": return targetPlayer.health < targetPlayer.maxHealth; default: return false; } } function computeAutoTargets(state, ownerId, cardName) { const effect = getCardEffect(cardName); if (!effect) return []; const spec = effect.target; if (spec.kind !== "allOthers" && spec.kind !== "allPlayers") return []; const alive = state.players.filter((p) => p.alive); const n = alive.length; if (n === 0) return []; const fromPos = alive.findIndex((p) => p.index === ownerId); if (fromPos < 0) return alive.map((p) => p.index); const result = []; const start = spec.kind === "allOthers" ? 1 : 0; for (let i = start; i < n; i++) result.push(alive[(fromPos + i) % n].index); return result; } function findLegalTargets(state, ownerId, cardName, cardId) { const result = []; for (let i = 0; i < state.players.length; i++) if (isLegalTarget(state, ownerId, cardName, i, cardId)) result.push(i); return result; } function validateCardUse(state, ownerId, params, cardName, mode = "play") { if (mode === "play") { const base = validateUseCard(state, ownerId, params, { cardName }); if (base) return base; } else { const cardId = params.cardId; if (!cardId) return "cardId required"; const card = state.cardMap[cardId]; if (!card) return "牌不存在"; if (card.name !== cardName) return `不是${cardName}`; } if (isCardBanned(state, ownerId, cardName)) return "你不能使用此牌"; if (mode === "play") { const limit = checkUsageLimit(state, ownerId, cardName, params); if (limit) return limit; } const effect = getCardEffect(cardName); if (!effect) return `${cardName} 尚未注册 CardEffect`; if (effect.target.kind !== "self" && effect.target.kind !== "effect") { const cardId = params.cardId; if (findLegalTargets(state, ownerId, cardName, cardId).length === 0) return "没有合法目标"; } if (effect.canUse) { const customErr = effect.canUse(state, ownerId, params); if (customErr) return customErr; } return null; } var init_validate = __esmMin((() => { init_distance(); init_slash_quota(); init_skill(); init_cards(); })); //#endregion //#region src/engine/rules/action-active.ts /** 默认出牌激活条件:当前视角回合 + 出牌阶段 + 无阻塞型 pending。 * 非阻塞型 pending(出牌窗口)不阻止出牌/用技。这是绝大多数主动出牌/用技场景的 * 激活条件;主动技若需额外约束(限一次/转化条件),在 activeWhen 中 * `defaultPlayActive(ctx) && <额外条件>` 叠加。 */ function defaultPlayActive(ctx) { const { view, perspectiveIdx } = ctx; const pending = view.pending; const blocked = pending != null && pending.isBlocking !== false; return view.currentPlayerIndex === perspectiveIdx && view.phase === "出牌" && !blocked; } /** 主公技门槛:技能拥有者是否处于主公位。 * 项目约定「主公固定 0 号位」——各主公技的引擎 validate 一律以 `ownerId === 0` 为门槛 * (激将/界激将/制霸/界制霸/黄天/界黄天/救援/界救援/界血裔/回合管理…)。 * 客户端 activeWhen 必须复用同一判据:非主公座次的武将仍带着自己的主公技 * (孙策带 界制霸、刘备带 激将),少了这道门槛,无头/AI 客户端会枚举出引擎必拒的 * action,启发式评分确定性反复挑中 → 空转。 */ function isLordOwner(ownerId) { return ownerId === 0; } /** 前端可计算的出杀次数上限(基于 view 装备元数据/turnUsage 前缀聚合推断)。 * 后端采用三层模型(slash-quota.ts):额定(覆盖 max)+ 额外(叠加 Σ)+ 无限(任一 true→∞)。 * 前端无法访问提供者集合,故用武器元数据 + turnUsage 通用前缀推断,与后端 slashMax 同源。 * turnUsage 前缀约定: * '杀/unlimited/<来源>' — 任一真值 → 无限出杀 * '杀/extra/<来源>' — 数字,叠加到出杀上限 * '杀/blocked/<来源>' — 任一真值 → 禁止出杀 * '杀/target/<来源>' — 数字,叠加到目标数上限 * '杀/exemptSuit' — 花色字符串,同花色杀无次数限制 */ function viewSlashMax(view, player) { const p = view.players[player]; if (!p) return 1; const weaponId = p.equipment["武器"]; if ((weaponId ? view.cardMap[weaponId] : void 0)?.slashUnlimited) return Infinity; const tu = p.turnUsage ?? {}; for (const [k, v] of Object.entries(tu)) if (k.startsWith("杀/unlimited/") && v) return Infinity; let max = 1; for (const [k, v] of Object.entries(tu)) if (k.startsWith("杀/extra/") && typeof v === "number") max += v; return max; } /** 前端可计算的【杀】目标数上限(基于 view 装备元数据/turnUsage 前缀/tag 推断)。 * 与后端 slashTargetMax(state,player,cardId) 同源:默认 1;武器/回合制效果/tag 放宽。 * 供前端选目标 UI 限制可选目标数,与后端 canUseSlash 权威校验对齐 * (后端是权威闸门,前端仅 UX 提示,避免玩家选了却被拒)。 */ function viewSlashTargetMax(view, player, card) { const p = view.players[player]; if (!p) return 1; let max = 1; const weaponId = p.equipment["武器"]; const weapon = weaponId ? view.cardMap[weaponId] : void 0; if (weapon?.slashTargetBonusWhenLastCard && (p.hand?.length ?? 0) === 1) max = Math.max(max, weapon.slashTargetBonusWhenLastCard); const tu = p.turnUsage ?? {}; for (const [k, v] of Object.entries(tu)) if (k.startsWith("杀/target/") && typeof v === "number") max += v; if (card?.name === "杀" && card.damageType === "火焰" && p.tags?.includes("杀/火杀多目标")) max = Math.max(max, 2); return max; } /** 前端视角下某玩家本回合已出杀次数(从 view.turnUsage 投影读)。 * turnUsage 由「回合用量」atom 实时同步,与后端 slashUsed()(额定+额外 合计)一致。 */ function viewSlashUsed(view, player) { const used = view.players[player]?.turnUsage?.[SLASH_USED_COUNT_KEY]; return typeof used === "number" ? used : 0; } /** 前端视角下某玩家本回合是否还能出杀(已用 < 上限,且未被回合制效果阻断)。 */ function viewCanSlash(view, player) { const p = view.players[player]; if (!p) return false; const tu = p.turnUsage ?? {}; for (const [k, v] of Object.entries(tu)) if (k.startsWith("杀/blocked/") && v) return false; const exemptSuit = tu[SLASH_EXEMPT_SUIT_KEY]; if (typeof exemptSuit === "string" && exemptSuit !== "") if (view.viewer === player) { if (p.hand?.some((c) => c.name === "杀" && c.suit === exemptSuit)) return true; } else return true; return viewSlashUsed(view, player) < viewSlashMax(view, player); } var init_action_active = __esmMin((() => { init_vars_keys(); })); //#endregion //#region src/engine/rules/once-per-turn.ts /** 某技能本回合是否已用过(限一次)。读 player.vars。 */ function usedThisTurn$3(state, ownerId, skillName) { return !!state.players[ownerId]?.vars[usedThisTurnKey(skillName)]; } /** 标记技能本回合已用(限一次):同步设 vars + 回合用量 atom 同步 view。 * 必须在 execute 第一个 await 之前调用,以防 dispatch 重入(见制衡.ts 注释)。 */ async function markOncePerTurn(state, ownerId, skillName) { state.players[ownerId].vars[usedThisTurnKey(skillName)] = true; await applyAtom(state, { type: "回合用量", player: ownerId, key: usedThisTurnKey(skillName), value: true }); } /** activeWhen 谓词:默认出牌条件 + 本回合未用过该技能。 */ function activeUnlessUsedThisTurn(skillName) { return (ctx) => defaultPlayActive(ctx) && !ctx.view.players[ctx.perspectiveIdx]?.turnUsage?.[usedThisTurnKey(skillName)]; } var init_once_per_turn = __esmMin((() => { init_apply(); init_action_active(); init_vars_keys(); })); //#endregion //#region src/engine/skills/cards/酒.ts var 酒_exports = /* @__PURE__ */ __exportAll({ registerWineHook: () => registerWineHook, wineEffect: () => wineEffect }); /** 注册酒的全局「造成伤害」before-hook(消费增伤标记)。 * 酒的使用者拥有 '酒/nextKillDamageBonus' 标记时,其造成的伤害 +1,然后消耗标记。 * 全局注册(ownerId=-1)——酒是基本牌面能力,不限技能持有者。 * 在 index bootstrap 中调用。 */ function registerWineHook(state) { registerBeforeHook(state, "酒", -1, "造成伤害时", async (ctx) => { const atom = ctx.atom; if (atom.source === void 0) return; if ((atom.amount ?? 0) <= 0) return; const self = ctx.state.players[atom.source]; if (!self) return; if (!self.marks.some((m) => m.id === "酒/nextKillDamageBonus")) return; if ((atom.cardId !== void 0 ? ctx.state.cardMap[atom.cardId] : void 0)?.name !== "杀") return; await applyAtom(ctx.state, { type: "去标记", player: atom.source, markId: "酒/nextKillDamageBonus" }); return { kind: "modify", atom: { ...ctx.atom, amount: (atom.amount ?? 0) + 1 } }; }); } /** 酒的结算:加增伤标记 + 标记本回合已用过酒增伤(使用方法Ⅰ每回合限一次) */ async function resolveWine(ctx) { const { state, source } = ctx; await markOncePerTurn(state, source, "酒"); await applyAtom(state, { type: "加标记", player: source, mark: { id: "酒/nextKillDamageBonus", scope: -1, payload: 1, duration: "turn" } }); } /** 酒牌特有校验:只能对自己使用 + 使用方法Ⅰ每回合限一次 */ function canUseWine(state, ownerId, params) { if (usedThisTurn$3(state, ownerId, "酒") && !state.players[ownerId]?.tags.includes("酒/无次数限制")) return "本回合已使用过酒"; const target = params.target ?? params.targets?.[0]; if (target !== void 0 && target !== ownerId) return "只能对自己使用酒"; return null; } var wineEffect; var init_酒 = __esmMin((() => { init_apply(); init_skill(); init_action_active(); init_once_per_turn(); wineEffect = { timing: "出牌阶段", target: { kind: "self" }, canUse: canUseWine, resolve: resolveWine, respond: { validate: (state, ownerId, params) => { if (state.pendingSlots.get(ownerId)?.atom.type !== "请求回应") return "当前不需要回应"; if (state.pendingSlots.get(ownerId).atom.requestType !== "桃/求桃") return "当前不是求桃"; const cardId = params.cardId; if (!cardId) return "cardId required"; if (!state.players[ownerId].hand.includes(cardId)) return "牌不在手牌中"; if (state.cardMap[cardId].name !== "酒") return "只能用酒救援"; return null; }, execute: async (state, ownerId, params) => { const cardId = params.cardId; await applyAtom(state, { type: "移动牌", cardId, from: { zone: "手牌", player: ownerId }, to: { zone: "弃牌堆" } }); state.localVars["求桃/已救"] = true; } }, prompt: { type: "useCard", title: "饮酒", cardFilter: { filter: (c) => c.name === "酒", min: 1, max: 1 } }, respondPrompt: { type: "useCard", title: "濒死:使用酒(当桃)", cardFilter: { filter: (c) => c.name === "酒", min: 1, max: 1 } }, label: "酒", style: "default", activeWhen: (ctx) => { if (ctx.view.players[ctx.perspectiveIdx]?.tags?.includes("酒/无次数限制")) return defaultPlayActive(ctx); return activeUnlessUsedThisTurn("酒")(ctx); } }; })); //#endregion //#region src/engine/flows/face-down.ts /** 翻面(翻成背面朝上):加 `${source}/翻面` 标签 + 发「翻面后」时机标记。 * source 为技能名前缀(如 '放逐'、'悲歌'),与各技能既有的标签常量前缀一致。 */ async function flipFaceDown(state, player, source) { await applyAtom(state, { type: "加标签", player, tag: `${source}/翻面` }); await applyAtom(state, { type: "翻面后", player, faceDown: true }); } /** 翻回正面:去 `${source}/翻面` 标签 + 发「翻面后」时机标记。 */ async function flipFaceUp(state, player, source) { await applyAtom(state, { type: "去标签", player, tag: `${source}/翻面` }); await applyAtom(state, { type: "翻面后", player, faceDown: false }); } /** 设置横置状态(模块 E 横置时机编排):设横置 后补发「横置后」时机标记。 * 返回 设横置 是否实际生效(被 before-hook cancel 时返回 false 且不补发 横置后)。 */ async function setChain(state, player, chained) { if (!await applyAtom(state, { type: "设横置", player, chained })) return false; await applyAtom(state, { type: "横置后", player, chained }); return true; } /** 检查 player 是否处于连环(横置)状态。 */ function isChained$1(state, idx) { return state.players[idx]?.marks.some((m) => m.id === "chained") ?? false; } /** 注册连环传导全局 after-hook:属性伤害结算结束后,从受伤害的连环角色起, * 将同等同属性伤害传导给其他所有连环角色,随后重置所有因此传导的连环状态。 * * 这是「连环状态 × 属性伤害」的联动行为,与铁索连环牌解耦:置入连环状态的途径 * 不限于铁索连环牌(武将技能可直接调 setChain)。原驻 skills/铁索连环.ts, * 现作为伤害结算基础设施由 index 的 bootstrap/registerSkillsFromState 注册。 * localVars[CONDUCTING_VAR] 防止传导伤害递归触发。 */ function registerChainConductionHook(state) { registerAfterHook(state, "铁索连环", -1, "伤害结算结束后", async (ctx) => { const atom = ctx.atom; const dt = atom.damageType; if (dt !== "火焰" && dt !== "雷电") return; const amount = atom.amount ?? 1; if (amount <= 0) return; const target = atom.target; if (typeof target !== "number") return; if (!isChained$1(ctx.state, target)) return; if (ctx.state.localVars[CONDUCTING_VAR]) return; ctx.state.localVars[CONDUCTING_VAR] = true; try { const source = atom.source ?? -1; const others = ctx.state.players.filter((p) => p.alive && p.index !== target && p.marks.some((m) => m.id === "chained")); for (const p of others) { if (!ctx.state.players[p.index]?.alive) continue; await runDamageFlow(ctx.state, source, p.index, amount, void 0, dt); } const allChained = ctx.state.players.filter((p) => p.marks.some((m) => m.id === CHAIN_MARK$1)); for (const p of allChained) await setChain(ctx.state, p.index, false); } finally { delete ctx.state.localVars[CONDUCTING_VAR]; } }); } /** 检查 player 武将牌是否处于翻面(背面朝上)状态:存在任意 `/翻面` 后缀标签。 */ function isFaceDown(state, player) { return state.players[player].tags.some((t) => t.endsWith("/翻面")); } /** 翻回正面(清除该玩家所有 `/翻面` 后缀标签)+ 发「翻面后」时机(faceDown=false)。 * 与 flipFaceUp(source) 的区别:一次性清除全部翻面标签——一个角色可能被多个技能 * 叠加翻面(如同时持有 `放逐/翻面` 与 `悲歌/翻面`)。供 回合管理 跳过翻面角色时使用。 */ async function flipFaceUpAll(state, player) { const flipTags = (state.players[player]?.tags ?? []).filter((t) => t.endsWith("/翻面")); for (const tag of flipTags) await applyAtom(state, { type: "去标签", player, tag }); if (flipTags.length > 0) await applyAtom(state, { type: "翻面后", player, faceDown: false }); } /** 跳过整回合(翻面的系统效果):清过期标记 → 下一玩家 → 回合结束 → 回合结束后。 * 与 回合管理.end action 的尾段一致。调用方负责 cancel 触发它的 阶段结束 原子, * 以免 phase-end after-hook 推进产生幻影阶段链(沿用原内联实现的手法)。 * 回合结束后 仅在 回合结束 未被 cancel 时发出——下一家 beginTurn 挂在其 after-hook, * 不发出则下一家不启动。 */ async function performSkipTurn(state, player) { await applyAtom(state, { type: "清过期标记", player }); await applyAtom(state, { type: "下一玩家" }); if (await applyAtom(state, { type: "回合结束", player })) await applyAtom(state, { type: "回合结束后", player }); } var CHAIN_MARK$1, CONDUCTING_VAR; var init_face_down = __esmMin((() => { init_types(); init_apply(); init_damage(); init_skill(); CHAIN_MARK$1 = "chained"; CONDUCTING_VAR = "铁索连环/传导中"; })); //#endregion //#region src/engine/rules/skip-phase.ts var skip_phase_exports = /* @__PURE__ */ __exportAll({ skipPhase: () => skipPhase }); /** * 在「阶段开始」atom 的 before-hook 中跳过当前阶段。 * * 用法: * ```ts * registerBeforeHook(state, skillId, ownerId, '阶段开始', async (ctx) => { * if (shouldSkip) return skipPhase(ctx.state, ctx.atom, cleanupTag); * }); * ``` * * @param state 游戏状态 * @param atom 触发跳过的「阶段开始」atom(取其 player/phase) * @param cleanupTag 可选:跳过前要清除的标签名(标签型跳过,如「兵粮寸断/跳过摸牌」)。 * 直接型跳过(如神速①当场跳过判定、巧变弃牌跳过)不传。 * @returns cancel 结果,直接 `return` 给 before-hook。 */ async function skipPhase(state, atom, cleanupTag) { if (cleanupTag !== void 0) await applyAtom(state, { type: "去标签", player: atom.player, tag: cleanupTag }); await applyAtom(state, { type: "阶段结束", player: atom.player, phase: atom.phase }); return { kind: "cancel" }; } var init_skip_phase = __esmMin((() => { init_apply(); })); //#endregion //#region src/engine/skills/cards/use-card.ts var use_card_exports = /* @__PURE__ */ __exportAll({ chargeOnSettle: () => chargeOnSettle, createSkill: () => createSkill$306, default: () => use_card_default, onInit: () => onInit$306, onMount: () => onMount$274, registerDelayedTrickHooks: () => registerDelayedTrickHooks, resumeDelayedSettlement: () => resumeDelayedSettlement, runUseFlow: () => runUseFlow }); function createSkill$306(id, ownerId) { return { id, ownerId, name: "使用牌", description: "统一的卡牌使用入口" }; } /** * 使用结算中:逐目标执行 5 个时机 atom(use.md 使用结算中)。 * * 检测有效性 → 生效前 → 检查标记 → 生效时 → 生效后[resolve] → 使用结算结束时 * * 完全通用,不区分牌类型。闪/无藉的抵消通过「已抵消」标记实现: * 响应牌在「生效前」时机的 after-hook 中设置标记(见闪/无藉 skill)。 * 此处只检查标记 → 已抵消则发出被抵消 atom(武器技介入)→ 跳过 resolve。 */ async function runSettlementPhase(state, effect, source, target, cardId, targetIndex, skipWuxie = false) { if (!await applyAtom(state, { type: "检测有效性", source, target, cardId })) return; if (effect.hasEffect && !effect.hasEffect(state, target)) return; const settleFrame = topFrame(state); if (settleFrame) settleFrame.cancelled = false; await applyAtom(state, { type: "生效前", source, target, cardId }); { const card = state.cardMap[cardId]; const autoCancellable = !skipWuxie && card?.type === "锦囊牌" ? { cardName: "无懈可击", broadcast: true } : void 0; const cancellable = effect.cancelledBy ?? autoCancellable; if (cancellable) await promptCancel(state, cancellable, source, target); } if (isCancelled(state, cardId, target)) { await applyAtom(state, { type: "被抵消", source, target, cardId }); if (isCancelled(state, cardId, target)) return; } await applyAtom(state, { type: "生效时", source, target, cardId }); await applyAtom(state, { type: "生效后", source, target, cardId }); await effect.resolve({ state, source, target, cardId, targetIndex }); await applyAtom(state, { type: "使用结算结束时", source, target, cardId }); } /** * runUseFlow:编排使用事件的完整结算流程(文档 use.md)。 * * 整个流程包裹在 pushFrame / popFrame 中(popFrame 在 finally 块调用,异常安全)。 * 目标列表从帧 params.resolvedTargets 读取(流离等技能可能修改),fallback 到传入 targets。 * * @param state 游戏状态 * @param source 使用者 * @param cardId 实体牌 id(须在手牌中);虚拟使用时为虚拟卡 id(须已写入 cardMap) * @param targets 目标列表 * @param cardName 牌名(查 CardEffect 注册表) * @param opts 可选:virtual=true 虚拟使用模式 */ async function runUseFlow(state, source, cardId, targets, cardName, opts) { const effect = requireCardEffect(cardName); const skippedTargets = []; const frame = await pushFrame(state, cardName, source, { cardId, targets: [...targets], resolvedTargets: [...targets], skippedTargets }); try { await applyAtom(state, { type: "选择目标时", source, cardId, targets }); if (effect.target.kind === "effect") { if (!opts?.virtual) await applyAtom(state, { type: "移动牌", cardId, from: { zone: "手牌", player: source }, to: { zone: "处理区" } }); await applyAtom(state, { type: "使用时", source, cardId }); await runSettlementPhase(state, effect, source, source, cardId, 0, opts?.virtual || opts?.skipCancelQuery); if (!opts?.virtual && frameCards(state).includes(cardId)) await applyAtom(state, { type: "移动牌", cardId, from: { zone: "处理区" }, to: { zone: "弃牌堆" } }); await applyAtom(state, { type: "使用结算结束后", source, cardId }); return; } if (effect.delayed) { if (!opts?.virtual) await applyAtom(state, { type: "移动牌", cardId, from: { zone: "手牌", player: source }, to: { zone: "处理区" } }); } else if (!opts?.virtual) await applyAtom(state, { type: "移动牌", cardId, from: { zone: "手牌", player: source }, to: { zone: "处理区" } }); await applyAtom(state, { type: "使用时", source, cardId }); for (const target of targets) await applyAtom(state, { type: "指定目标", source, target, cardId }); for (let i = 0; i < targets.length; i++) { const target = (frame.params.resolvedTargets ?? targets)[i]; if (!state.players[target]?.alive) continue; if (!await applyAtom(state, { type: "成为目标", source, target, cardId })) frame.params.skippedTargets.push(target); } if (effect.delayed) { const resolved = frame.params.resolvedTargets ?? targets; const trickCard = state.cardMap[cardId]; for (const target of resolved) { if (frame.params.skippedTargets.includes(target)) continue; await applyAtom(state, { type: "添加延时锦囊", player: target, trick: { name: cardName, source, card: trickCard } }); } if (!opts?.virtual) await applyAtom(state, { type: "移动牌", cardId, from: { zone: "处理区" }, to: { zone: "弃牌堆" } }); } for (let i = 0; i < targets.length; i++) { const target = (frame.params.resolvedTargets ?? targets)[i]; if (!state.players[target]?.alive) continue; if (frame.params.skippedTargets.includes(target)) continue; await applyAtom(state, { type: "指定目标后", source, target, cardId }); } for (let i = 0; i < targets.length; i++) { const target = (frame.params.resolvedTargets ?? targets)[i]; if (!state.players[target]?.alive) continue; if (frame.params.skippedTargets.includes(target)) continue; await applyAtom(state, { type: "成为目标后", source, target, cardId }); } for (let i = 0; i < targets.length; i++) { const target = (frame.params.resolvedTargets ?? targets)[i]; if (!state.players[target]?.alive) continue; if (effect.delayed) continue; if (frame.params.skippedTargets.includes(target)) continue; await runSettlementPhase(state, effect, source, target, cardId, i, opts?.virtual); } if (!opts?.virtual && frameCards(state).includes(cardId)) await applyAtom(state, { type: "移动牌", cardId, from: { zone: "处理区" }, to: { zone: "弃牌堆" } }); if (!effect.delayed) await applyAtom(state, { type: "使用结算结束后", source, cardId }); if (opts?.onSettle) await opts.onSettle(); } finally { if (frameCards(state).includes(cardId)) await applyAtom(state, { type: "移动牌", cardId, from: { zone: "处理区" }, to: { zone: "弃牌堆" } }); await popFrame(state); } } /** * 恢复延迟类锦囊的使用结算中(判定阶段触发)。 * * 延迟类锦囊的 runUseFlow 在使用结算前(成为目标后)暂停, * 牌已置入判定区。判定阶段恢复时调用此函数走完使用结算中: * 检测有效性 → 生效前 → 询问抵消(无懈)→ 生效时 → 生效后[resolve:判定+效果] → 使用结算结束时 * * 被无懈抵消 → frame.cancelled=true → 跳过 resolve → 调用方移除延时锦囊。 */ async function resumeDelayedSettlement(state, source, target, cardName, cardId) { const effect = requireCardEffect(cardName); const frame = await pushFrame(state, cardName, source, { cardId, targets: [target], resolvedTargets: [target] }); try { await runSettlementPhase(state, effect, source, target, cardId, 0, false); return frame.cancelled; } finally { await popFrame(state); } } /** 注册延时锦囊(乐不思蜀/兵粮寸断/闪电)的全局判定阶段 after-hook + 跳过阶段 before-hook。 * 在 index bootstrap / registerSkillsFromState 中调用。 * 全局注册(ownerId=-1):判定阶段 hook 检查 atom.player 的判定区有无延时锦囊。 */ function registerDelayedTrickHooks(state) { registerAfterHook(state, "延时锦囊", -1, "阶段开始", async (ctx) => { const atom = ctx.atom; if (atom.type !== "阶段开始" || atom.phase !== "判定") return; const player = atom.player; const self = ctx.state.players[player]; if (!self) return; const delayedTrickNames = getAllCardEffects().filter(([, e]) => e.delayed).map(([name]) => name); while (true) { if (ctx.state.zones.deck.length === 0) return; const trick = [...self.pendingTricks].reverse().find((t) => delayedTrickNames.includes(t.name)); if (!trick) break; if (await resumeDelayedSettlement(ctx.state, trick.source, player, trick.name, trick.card.id)) { const effect = getCardEffect(trick.name); if (effect?.onCancelled) await effect.onCancelled(ctx.state, player, trick.card.id); else await applyAtom(ctx.state, { type: "移除延时锦囊", player, trickName: trick.name }); } } }); registerBeforeHook(state, "延时锦囊", -1, "阶段开始", async (ctx) => { const atom = ctx.atom; if (atom.type !== "阶段开始") return; const player = atom.player; const self = ctx.state.players[player]; if (!self) return; if (atom.phase !== "出牌" && atom.phase !== "摸牌") return; for (const [, effect] of getAllCardEffects()) { const sp = effect.skipPhase; if (!sp) continue; if (sp.phase !== atom.phase) continue; if (self.tags.includes(sp.tag)) { const { skipPhase: doSkip } = await Promise.resolve().then(() => (init_skip_phase(), skip_phase_exports)); return doSkip(ctx.state, atom, sp.tag); } } }); } /** 注册 use action:逐卡名注册(skillId=卡名),validate 查 CardEffect 注册表,execute 调 runUseFlow。 * 按卡名注册而非统一 '使用牌' skillId,是为了: * 1. 保持 triggerAction('杀','use',...) 等调用向后兼容 * 2. 让 界火计/界乱击 等 registerAction('万箭齐发',...) 覆盖机制仍然生效 * 3. 前端 transform.name='万箭齐发' → skillId='万箭齐发' 路由不变 * * 全局 hooks(酒增伤/延时锦囊判定/连环传导)在首次实例化时注册,后续座次跳过。 */ function onInit$306(skill, state) { const ownerId = skill.ownerId; const unloads = []; if (!globalHooksRegistered.has(state)) { globalHooksRegistered.add(state); registerWineHook(state); registerDelayedTrickHooks(state); registerChainConductionHook(state); } for (const [cardName, effect] of getAllCardEffects()) { const u = registerAction(state, cardName, ownerId, "use", (state, params) => { const cardId = params.cardId; if (!cardId) return "cardId required"; const card = state.cardMap[cardId]; if (!card) return "牌不存在"; if (card.name !== cardName) return `不是${cardName}`; if (!Array.isArray(params.targets) && typeof params.target === "number") params.targets = [params.target]; return validateCardUse(state, ownerId, params, cardName); }, async (state, params) => { const cardId = params.cardId; if (!Array.isArray(params.targets) && typeof params.target === "number") params.targets = [params.target]; let targets = effect.preUse ? effect.preUse(state, ownerId, params) : params.targets ?? []; if (effect.target.kind === "self") targets = [ownerId]; else if (targets.length === 0) targets = computeAutoTargets(state, ownerId, cardName); await runUseFlow(state, ownerId, cardId, targets, cardName, { onSettle: chargeOnSettle(state, ownerId, cardId) }); }); if (u) unloads.push(u); } return () => unloads.forEach((u) => u()); } /** 使用牌的 UI:按卡名注册 use action 的 UI 配置(从 CardEffect 注册表驱动)。 * 每张 CardEffect 的 prompt/label/style/activeWhen 成为一个 use action 定义。 * skillIdOverride=卡名(与 onInit 按卡名注册的 engine action 对齐)。 */ function onMount$274(_skill, api) { for (const [cardName, effect] of getAllCardEffects()) { if (effect.timing === "生效前") continue; api.defineAction("use", { label: effect.label, style: effect.style, prompt: effect.prompt, ...effect.activeWhen ? { activeWhen: effect.activeWhen } : {} }, cardName); } } /** 构造 runUseFlow 的 onSettle 回调:使用结算完成后计费(杀的出杀次数累加等)。 * 仅当 CardEffect 声明了 onSettle 且非延时锦囊时才计费;无可计费项时返回 undefined。 * * 合法性校验不在本助手职责内——由各调用方自行负责: * - 主动使用:use action 的 validate(validateCardUse mode='play') * - 逼杀/代杀:各 skill 的 respond.validate(攻击范围/必含目标/禁出牌等) * 调用方校验通过后直接调 runUseFlow,需要计费时传入本助手构造的回调。 */ function chargeOnSettle(state, source, cardId) { const cardName = state.cardMap[cardId]?.name; if (!cardName) return void 0; const eff = getCardEffect(cardName); if (!eff?.onSettle || eff.delayed) return void 0; return () => eff.onSettle(state, source, cardId); } var globalHooksRegistered, use_card_default; var init_use_card = __esmMin((() => { init_apply(); init_frame(); init_skill(); init_cancel(); init_validate(); init_cards(); init_酒(); init_face_down(); globalHooksRegistered = /* @__PURE__ */ new WeakSet(); use_card_default = { createSkill: createSkill$306, onInit: onInit$306, onMount: onMount$274 }; })); //#endregion //#region src/engine/rules/slash-target.ts function getSlashTargetRegistry(state) { let reg = slashTargetRegistries.get(state); if (!reg) { reg = { targetProviders: /* @__PURE__ */ new Map() }; slashTargetRegistries.set(state, reg); } return reg; } /** * 注册一个杀目标数提供者(技能 onInit 时调用,与 registerAction 同构)。 * 返回取消注册函数——应并入 onInit 返回的 unload,由 setSkillInstanceUnload * 在卸载技能实例时自动清理(装备被换下/弃置时随实例销毁)。 */ function registerSlashTargetProvider(state, ownerId, provider) { const map = getSlashTargetRegistry(state).targetProviders; let set = map.get(ownerId); if (!set) { set = /* @__PURE__ */ new Set(); map.set(ownerId, set); } set.add(provider); return () => { const s = map.get(ownerId); if (s) { s.delete(provider); if (s.size === 0) map.delete(ownerId); } }; } /** * 该玩家使用指定【杀】时可指定的最大目标数。 * 默认 1;各提供者取最大值(最宽松生效)。无提供者或均无贡献时返回 1。 * * cardId 可选:传入时提供者可做 per-card 决策(疠火判火杀);缺省时仅问无卡牌上下文的提供者。 */ function slashTargetMax(state, player, cardId) { const set = getSlashTargetRegistry(state).targetProviders.get(player); if (!set || set.size === 0) return 1; let max = 1; for (const provider of set) { const v = provider(state, player, cardId); if (v > max) max = v; } return max; } var slashTargetRegistries; var init_slash_target = __esmMin((() => { slashTargetRegistries = /* @__PURE__ */ new WeakMap(); })); //#endregion //#region src/engine/skills/cards/借刀杀人.ts var 借刀杀人_exports = /* @__PURE__ */ __exportAll({ borrowedSwordEffect: () => borrowedSwordEffect }); /** 从 target 处卸下武器并交给 source(借刀杀人“不出杀/交武器”分支) */ async function acquireWeapon(state, source, target) { const weaponId = state.players[target]?.equipment["武器"]; if (!weaponId) return; await applyAtom(state, { type: "卸下", player: target, slot: "武器" }); await applyAtom(state, { type: "获得", player: source, cardId: weaponId, from: target }); } /** 借刀杀人的结算:请求出杀 → 读取选择 → runUseFlow 杀结算/获得武器 */ async function resolveBorrowedSword(ctx) { const { state, source, target } = ctx; const killTarget = state.localVars[KILL_TARGET_VAR$1]; await applyAtom(state, { type: "请求回应", requestType: REQUEST_TYPE$8, target, prompt: { type: "useCardAndTarget", title: `借刀杀人:对 ${state.players[killTarget]?.name ?? "?"} 使用一张杀,或交出武器`, cardFilter: { filter: (c) => c.name === "杀", min: 1, max: 1 }, targetFilter: { min: 1, max: 3, filter: () => true } }, timeout: 20 }); const choice = state.localVars[CHOICE_VAR$5]; delete state.localVars[CHOICE_VAR$5]; if (choice) await runUseFlow(state, target, choice.cardId, choice.targets, "杀"); else await acquireWeapon(state, source, target); } /** 借刀杀人牌特有校验:目标有武器、killTarget 合法、A 不能是自己 * killTarget 允许是发起者自己(借别人的刀杀自己)。 */ function canUseBorrowedSword(state, ownerId, params) { let targetIdx; let killTargetIdx; if (Array.isArray(params.targets) && params.targets.length >= 2 && typeof params.targets[0] === "number" && typeof params.targets[1] === "number") { const arr = params.targets; targetIdx = arr[0]; killTargetIdx = arr[1]; } else { targetIdx = params.target; killTargetIdx = params.killTarget; } if (typeof targetIdx !== "number") return "target required"; if (typeof killTargetIdx !== "number") return "killTarget required"; const target = state.players[targetIdx]; if (!target?.alive) return "目标不合法"; if (!target.equipment["武器"]) return "目标没有武器"; if (targetIdx === ownerId) return "不能对自己使用"; if (!state.players[killTargetIdx]?.alive) return "杀的目标不合法"; if (killTargetIdx === targetIdx) return "杀的目标不能是借刀杀人目标"; return null; } var REQUEST_TYPE$8, CHOICE_VAR$5, KILL_TARGET_VAR$1, borrowedSwordEffect; var init_借刀杀人 = __esmMin((() => { init_apply(); init_use_card(); init_validate(); init_distance(); init_slash_target(); REQUEST_TYPE$8 = "借刀杀人/出杀"; CHOICE_VAR$5 = "借刀杀人/出杀选择"; KILL_TARGET_VAR$1 = "借刀杀人/killTarget"; borrowedSwordEffect = { timing: "出牌阶段", target: { kind: "other", min: 1, max: 1 }, canUse: canUseBorrowedSword, resolve: resolveBorrowedSword, preUse: (state, _ownerId, params) => { let targetIdx; let killTargetIdx; if (Array.isArray(params.targets) && params.targets.length >= 2 && typeof params.targets[0] === "number" && typeof params.targets[1] === "number") { const arr = params.targets; targetIdx = arr[0]; killTargetIdx = arr[1]; } else { targetIdx = params.target; killTargetIdx = params.killTarget; } state.localVars[KILL_TARGET_VAR$1] = killTargetIdx; return [targetIdx]; }, respond: { validate: (state, ownerId, params) => { const slot = state.pendingSlots.get(ownerId); if (!slot) return "当前不需要回应"; if (slot.atom.target !== ownerId) return "不是问你的"; if (slot.atom.requestType !== REQUEST_TYPE$8) return "当前不是借刀杀人询问"; const cardId = params.cardId; if (cardId === void 0) return null; const targets = params.targets; if (!Array.isArray(targets) || targets.length === 0) return "请选择杀的目标"; if (!state.players[ownerId]?.hand.includes(cardId)) return "牌不在手牌中"; if (state.cardMap[cardId]?.name !== "杀") return "只能使用杀"; if (isCardBanned(state, ownerId, "杀")) return "你不能使用杀"; const killTarget = state.localVars[KILL_TARGET_VAR$1]; if (killTarget !== void 0 && !targets.includes(killTarget)) return "必须包含借刀杀人指定的目标"; for (const t of targets) if (!inAttackRange(state, ownerId, t, cardId)) return "目标不在攻击范围内"; const maxTargets = slashTargetMax(state, ownerId, cardId); if (targets.length > maxTargets) return `最多指定 ${maxTargets} 个目标`; return null; }, execute: async (state, ownerId, params) => { const cardId = params.cardId; const targets = params.targets; if (typeof cardId === "string" && Array.isArray(targets)) state.localVars[CHOICE_VAR$5] = { cardId, targets }; } }, prompt: { type: "useCardAndTarget", title: "借刀杀人", cardFilter: { filter: (c) => c.name === "借刀杀人", min: 1, max: 1 }, targetFilter: { min: 2, max: 2, slots: [{ label: "借刀目标", filter: (view, t) => t !== view.currentPlayerIndex && !!view.players[t]?.alive && !!view.players[t]?.equipment["武器"] }, { label: "杀的目标", filter: (view, t, ctx) => !!view.players[t]?.alive && !ctx.selected.includes(t) }] } }, respondPrompt: { type: "useCardAndTarget", title: "借刀杀人:对指定角色使用一张杀,或交出武器", cardFilter: { filter: (c) => c.name === "杀", min: 1, max: 1 }, targetFilter: { min: 1, max: 3, filter: () => true } }, label: "借刀杀人", style: "danger" }; })); //#endregion //#region src/engine/rules/viewDistance.ts /** GameView 上 from 到 to 的环形座位距离(只算存活玩家) */ function viewSeatDistance(players, fromIdx, toIdx) { const alive = players.filter((p) => p.alive); const n = alive.length; if (n <= 1) return 0; const aliveFrom = alive.findIndex((p) => p.name === players[fromIdx]?.name); const aliveTo = alive.findIndex((p) => p.name === players[toIdx]?.name); if (aliveFrom < 0 || aliveTo < 0) return Infinity; const d = Math.abs(aliveFrom - aliveTo); return Math.min(d, n - d); } /** GameView 上 from 到 to 的实际距离(含马修正,>= 1) */ function viewEffectiveDistance(players, fromIdx, toIdx) { if (players.some((p) => p.turnUsage?.["陷阵/winTarget"] === toIdx)) return 1; if (players[fromIdx]?.turnUsage?.["成略/suits"]) return 1; if (players[fromIdx]?.turnUsage?.["决堰/本回合:坐骑"]) return 1; if (players[fromIdx]?.turnUsage?.["往烈/首张可用"]) return 1; if (players[fromIdx]?.turnUsage?.["雄乱/目标"] === toIdx) return 1; let dist = viewSeatDistance(players, fromIdx, toIdx); const fromP = players[fromIdx]; const toP = players[toIdx]; dist -= fromP?.distanceVars?.attackMod ?? 0; dist += toP?.distanceVars?.defenseMod ?? 0; return Math.max(1, dist); } /** GameView 上 from 是否能对 to 出杀(基于 from 的攻击范围,徒手默认 1) */ function viewCanAttack(players, cardMap, fromIdx, toIdx) { if (fromIdx === toIdx) return false; if (players.some((p) => p.turnUsage?.["陷阵/winTarget"] === toIdx)) return true; if (players[fromIdx]?.turnUsage?.["天义/win"] === fromIdx) return true; if (players[fromIdx]?.turnUsage?.[slashExtraKey("诈降")]) return true; if (players[fromIdx]?.turnUsage?.["成略/suits"]) return true; if (players[fromIdx]?.skills?.includes("界武圣")) return true; if (players[fromIdx]?.skills?.includes("界烈弓")) return true; if (players[fromIdx]?.turnUsage?.["将驰/choice2"]) return true; if (players[fromIdx]?.turnUsage?.["界弓骑/active"]) return true; if (players[fromIdx]?.turnUsage?.["当先/noRangeActive"]) return true; const range = players[fromIdx]?.distanceVars?.attackRange ?? 1; return viewEffectiveDistance(players, fromIdx, toIdx) <= range; } var init_viewDistance = __esmMin((() => { init_vars_keys(); })); //#endregion //#region src/engine/skills/cards/兵粮寸断.ts var 兵粮寸断_exports = /* @__PURE__ */ __exportAll({ supplyShortageEffect: () => supplyShortageEffect }); function canUseSupplyShortage(state, ownerId, params) { const target = params.target ?? params.targets?.[0]; if (target === void 0) return "目标不合法"; if (target === ownerId) return "不能对自己使用"; if (!state.players[target]?.alive) return "目标已死亡"; const ignoreDistance = !!state.players[ownerId]?.tags.includes("奇才/无距离限制"); const useCardId = params.cardId; if (!ignoreDistance && effectiveDistance(state, ownerId, target, useCardId) > 1) return "距离太远"; if (state.players[target].pendingTricks.some((t) => t.name === "兵粮寸断")) return "目标判定区已有兵粮寸断"; return null; } /** 兵粮寸断的生效后效果:判定 → 非♣加跳过标签 → 移除延时锦囊 */ async function resolveSupplyShortage(ctx) { const { state, target } = ctx; await runJudgeFlow(state, target, "兵粮寸断"); const judgeCardId = state.localVars["判定/finalJudgeCardId"]; delete state.localVars["判定/finalJudgeCardId"]; const judgeCard = judgeCardId ? state.cardMap[judgeCardId] : void 0; if (judgeCard && judgeCard.suit !== "♣") await applyAtom(state, { type: "加标签", player: target, tag: SKIP_TAG$5 }); await applyAtom(state, { type: "移除延时锦囊", player: target, trickName: "兵粮寸断" }); } var SKIP_TAG$5, supplyShortageEffect; var init_兵粮寸断 = __esmMin((() => { init_apply(); init_judge(); init_distance(); init_viewDistance(); SKIP_TAG$5 = "兵粮寸断/跳过摸牌"; supplyShortageEffect = { timing: "出牌阶段", target: { kind: "distance", dist: 1, min: 1, max: 1 }, delayed: true, skipPhase: { tag: SKIP_TAG$5, phase: "摸牌" }, cancelledBy: { cardName: "无懈可击", broadcast: true }, canUse: canUseSupplyShortage, resolve: resolveSupplyShortage, prompt: { type: "useCardAndTarget", title: "兵粮寸断", cardFilter: { filter: (c) => c.name === "兵粮寸断", min: 1, max: 1 }, targetFilter: { min: 1, max: 1, filter: (view, t) => { if (t === view.currentPlayerIndex) return false; const tp = view.players[t]; if (!tp || tp.alive === false) return false; if (view.players[view.currentPlayerIndex]?.tags?.includes("奇才/无距离限制")) return true; return viewEffectiveDistance(view.players, view.currentPlayerIndex, t) <= 1; } } }, label: "兵粮寸断", style: "danger" }; })); //#endregion //#region src/engine/skills/cards/play-card.ts var play_card_exports = /* @__PURE__ */ __exportAll({ consumePlayedSlashes: () => consumePlayedSlashes, createSkill: () => createSkill$305, default: () => play_card_default, onInit: () => onInit$305, onMount: () => onMount$273, runPlayFlow: () => runPlayFlow }); function createSkill$305(id, ownerId) { return { id, ownerId, name: "打出牌", description: "统一的卡牌打出入口" }; } /** * runPlayFlow:编排打出事件的结算流程(文档 play.md)。 * * 三步原子: * 1. 声明打出时(转化技 before-hook 可替换牌) * 2. 置入处理区(移动牌:手牌→处理区) * 3. 打出牌时(雷击/涯角/银月枪 after-hook) * * 不含帧管理:打出不涉及结算循环,调用方负责自己的帧。 * * @param state 游戏状态 * @param player 打出者 * @param cardId 实体牌 id(须在手牌中) */ async function runPlayFlow(state, player, cardId) { await applyAtom(state, { type: "声明打出时", player, cardId }); await applyAtom(state, { type: "移动牌", cardId, from: { zone: "手牌", player }, to: { zone: "处理区" } }); await applyAtom(state, { type: "打出牌时", player, cardId }); } /** 打出牌的统一清理:消耗当前结算帧处理区中的全部【杀】牌(移入弃牌堆), * 返回被消耗的牌 id 列表(按帧内顺序)。 * * 使用场景:询问出杀(`询问杀` atom 或 `请求回应` 杀/respondKill)后,调用方据返回值 * 判断目标是否出了杀(长度 > 0 = 出了杀),并把杀牌统一移入弃牌堆。 * 替代此前散落在 南蛮入侵/决斗/借刀杀人/无双 各自手写的 `处理区→弃牌堆` 清理。 * * 设计要点: * - 清理必须在 `applyAtom(询问杀)` 返回之后进行——询问期间杀牌须留在处理区供检测 * (决斗循环的下一轮还会复用处理区检测,故每轮询问后即清理)。 * - 闪/无懈走 runUseFlow,内部已自洽清理(手牌→处理区→弃牌堆),不经过本函数。 * - runPlayFlow 本身不含清理(打出牌的清理时机取决于调用方逻辑)。 */ async function consumePlayedSlashes(state) { const kills = frameCards(state).filter((id) => state.cardMap[id]?.name === "杀"); for (const id of kills) await applyAtom(state, { type: "移动牌", cardId: id, from: { zone: "处理区" }, to: { zone: "弃牌堆" } }); return kills; } /** 注册 respond action:逐卡名注册(skillId=卡名),路由到 CardEffect.respond。 * 有 respond 字段的卡牌(闪/桃/酒/无懈可击 等)按卡名注册 respond action。 * 无 respond 字段的牌(如杀)不注册 respond——南蛮/决斗 的杀由 询问杀 atom * 的默认 resolver 处理。 * * effect kind(闪/无懈):respond execute 走 runUseFlow(与普通牌一致)。 * 牌移动(手牌→处理区→弃牌堆)由 runUseFlow 内部完成。 * resolve 中设下层帧(被抵消牌的帧)cancelled=true。 * 其他 kind:按原样走 effect.respond.execute。 */ function onInit$305(skill, state) { const ownerId = skill.ownerId; const unloads = []; for (const [cardName, effect] of getAllCardEffects()) { if (!effect.respond) continue; if (effect.target.kind === "effect") { const u = registerAction(state, cardName, ownerId, "respond", (s, p) => effect.respond.validate(s, ownerId, p), async (s, p) => { const cardId = p.cardId; if (!cardId) return; s.localVars["抵消/已回应"] = true; await runUseFlow(s, ownerId, cardId, [ownerId], cardName); }); if (u) unloads.push(u); } else { const u = registerAction(state, cardName, ownerId, "respond", (s, p) => effect.respond.validate(s, ownerId, p), (s, p) => effect.respond.execute(s, ownerId, p)); if (u) unloads.push(u); } } return () => unloads.forEach((u) => u()); } /** 打出牌的 UI:按卡名注册 respond action 的 UI 配置(从 CardEffect.respond 驱动)。 * 有 respond 字段的牌(闪/桃/酒/无懈可击 等)注册一个 respond action。 * skillIdOverride=卡名(与 onInit 按卡名注册的 engine action 对齐)。 */ function onMount$273(_skill, api) { for (const [cardName, effect] of getAllCardEffects()) { if (!effect.respond) continue; api.defineAction("respond", { label: effect.label, style: effect.style, prompt: effect.respondPrompt ?? effect.prompt }, cardName); } } var play_card_default; var init_play_card = __esmMin((() => { init_apply(); init_frame(); init_skill(); init_cards(); init_use_card(); play_card_default = { createSkill: createSkill$305, onInit: onInit$305, onMount: onMount$273 }; })); //#endregion //#region src/engine/skills/cards/决斗.ts var 决斗_exports = /* @__PURE__ */ __exportAll({ duelEffect: () => duelEffect }); /** 决斗牌特有校验:不能对自己使用 */ function canUseDuel(_state, ownerId, params) { const targets = params.targets; if (targets.length !== 1) return "决斗只能指定一名目标"; if (targets[0] === ownerId) return "不能对自己使用决斗"; return null; } /** 决斗循环(不含无懈):目标先出杀,之后发起者出杀,轮流。 */ async function runDuelLoop(state, from, target, cardId) { const MAX_ROUNDS = 100; let turn = 0; let loser = null; let rounds = 0; while (loser === null) { if (rounds++ >= MAX_ROUNDS) { loser = turn === 0 ? target : from; break; } const current = turn === 0 ? target : from; await applyAtom(state, { type: "询问杀", target: current, source: turn === 0 ? from : target }); if ((await consumePlayedSlashes(state)).length > 0) turn = turn === 0 ? 1 : 0; else loser = current; } await runDamageFlow(state, loser === target ? from : target, loser, 1, cardId); } /** 决斗的结算:无懈 → 决斗循环 */ async function resolveDuel(ctx) { const { state, source, target, cardId } = ctx; await runDuelLoop(state, source, target, cardId); } var duelEffect; var init_决斗 = __esmMin((() => { init_apply(); init_damage(); init_play_card(); duelEffect = { timing: "出牌阶段", target: { kind: "other", min: 1, max: 1 }, canUse: canUseDuel, resolve: resolveDuel, prompt: { type: "useCardAndTarget", title: "决斗", cardFilter: { filter: (c) => c.name === "决斗", min: 1, max: 1 }, targetFilter: { min: 1, max: 1 } }, label: "决斗", style: "danger" }; })); //#endregion //#region src/engine/skills/cards/南蛮入侵.ts var 南蛮入侵_exports = /* @__PURE__ */ __exportAll({ barbarianInvasionEffect: () => barbarianInvasionEffect }); /** 南蛮入侵的逐目标结算:询问杀 → 统一清理打出杀 → 伤害 */ async function resolveBarbarianInvasion(ctx) { const { state, source, target, cardId } = ctx; await applyAtom(state, { type: "询问杀", target, source }); if ((await consumePlayedSlashes(state)).length === 0) { if (!state.players[target]?.alive) return; await runDamageFlow(state, source, target, 1, cardId); } } var barbarianInvasionEffect; var init_南蛮入侵 = __esmMin((() => { init_apply(); init_damage(); init_play_card(); barbarianInvasionEffect = { timing: "出牌阶段", target: { kind: "allOthers" }, resolve: resolveBarbarianInvasion, prompt: { type: "useCard", title: "南蛮入侵", cardFilter: { filter: (c) => c.name === "南蛮入侵", min: 1, max: 1 } }, label: "南蛮入侵", style: "danger" }; })); //#endregion //#region src/engine/skills/cards/无中生有.ts var 无中生有_exports = /* @__PURE__ */ __exportAll({ exNihiloEffect: () => exNihiloEffect }); /** 无中生有的结算:使用者摸牌(2) */ async function resolveExNihilo(ctx) { const { state, target } = ctx; await applyAtom(state, { type: "摸牌", player: target, count: 2 }); } var exNihiloEffect; var init_无中生有 = __esmMin((() => { init_apply(); exNihiloEffect = { timing: "出牌阶段", target: { kind: "self" }, resolve: resolveExNihilo, prompt: { type: "useCard", title: "无中生有", cardFilter: { filter: (c) => c.name === "无中生有", min: 1, max: 1 } }, label: "无中生有", style: "primary" }; })); //#endregion //#region src/engine/skills/cards/无懈可击.ts var 无懈可击_exports = /* @__PURE__ */ __exportAll({ nullificationEffect: () => nullificationEffect }); var CARD_NAME, nullificationEffect; var init_无懈可击 = __esmMin((() => { init_skill(); CARD_NAME = "无懈可击"; nullificationEffect = { timing: "生效前", target: { kind: "effect" }, resolve: async (ctx) => { const { state } = ctx; const targetFrame = state.settlementStack[state.settlementStack.length - 2]; if (targetFrame) targetFrame.cancelled = true; }, respond: { validate: (state, ownerId, params) => { const slot = findPendingSlot(state, ownerId); if (!slot) return "当前不需要回应"; if (slot.atom.type !== "请求回应") return "当前不是无懈可击窗口"; if (slot.atom.requestType !== CARD_NAME) return "当前不是无懈可击窗口"; const cardId = params.cardId; if (!cardId) return "cardId required"; const self = state.players[ownerId]; if (!self.alive) return "你已死亡"; if (!self.hand.includes(cardId)) return "牌不在手牌中"; if (state.cardMap[cardId].name !== CARD_NAME) return "只能打出无懈可击"; return null; }, execute: async () => {} }, prompt: { type: "useCard", title: "打出无懈可击", cardFilter: { filter: (c) => c.name === CARD_NAME, min: 1, max: 1 } }, respondPrompt: { type: "useCard", title: "打出无懈可击", cardFilter: { filter: (c) => c.name === CARD_NAME, min: 1, max: 1 } }, label: CARD_NAME, style: "danger" }; })); //#endregion //#region src/engine/skills/cards/杀.ts var 杀_exports = /* @__PURE__ */ __exportAll({ slashEffect: () => slashEffect }); /** 杀的合法性追加检测:仅校验目标在攻击范围内。 * 出杀次数限制(slash-quota)不由此处检查——由 validateCardUse.checkUsageLimit * 统一负责(mode='play' 强制,mode='forced' 跳过)。此处若重复检查 canSlash, * 会导致 forced 模式(借刀杀人/激将/挑衅/乱武 逼杀)被回合内出杀次数误挡。 */ function canUseSlash(state, ownerId, params) { const cardId = params.cardId; if (!(Array.isArray(params.targets) && params.targets.every((t) => { if (state.players[t]?.alive !== true) return false; return inAttackRange(state, ownerId, t, cardId); }))) return "目标不合法"; const targetsArr = Array.isArray(params.targets) ? params.targets : []; const maxTargets = slashTargetMax(state, ownerId, cardId); if (targetsArr.length > maxTargets) return `最多指定 ${maxTargets} 个目标`; return null; } /** 杀的生效后效果:对目标角色造成 1 点伤害 */ async function resolveSlash(ctx) { const { state, source, target, cardId } = ctx; const damageType = state.cardMap[cardId]?.damageType; await runDamageFlow(state, source, target, 1, cardId, damageType); } /** 杀的结算后回调:出杀次数累加 + view 同步 */ async function onSettleSlash(state, source, cardId) { if (!isSlashExempted(state, source, cardId)) { incSlashUsed(state); await applyAtom(state, { type: "回合用量", player: source, key: SLASH_USED_COUNT_KEY, value: slashUsed(state) }); } } var slashEffect; var init_杀 = __esmMin((() => { init_apply(); init_damage(); init_distance(); init_viewDistance(); init_slash_quota(); init_slash_target(); init_action_active(); init_vars_keys(); slashEffect = { timing: "出牌阶段", target: { kind: "inAttackRange", min: 1, max: 3 }, canUse: canUseSlash, resolve: resolveSlash, onSettle: onSettleSlash, cancelledBy: { cardName: "闪", broadcast: false }, respond: { validate: (state, ownerId, params) => { const slot = state.pendingSlots.get(ownerId); if (!slot) return "当前不需要回应"; if (slot.atom.target !== ownerId) return "不是问你的"; const atomType = slot.atom.type; const reqType = slot.atom.requestType; if (!(atomType === "询问杀" || atomType === "请求回应" && reqType === "杀/respondKill")) return "当前不是出杀的窗口"; const cardId = params.cardId; if (cardId) { if (!state.players[ownerId].hand.includes(cardId)) return "牌不在手牌中"; if (state.cardMap[cardId]?.name !== "杀") return "只能打出杀"; } return null; }, execute: async (state, ownerId, params) => { const cardId = params.cardId; if (!cardId) return; await applyAtom(state, { type: "移动牌", cardId, from: { zone: "手牌", player: ownerId }, to: { zone: "处理区" } }); } }, prompt: { type: "useCardAndTarget", title: "出杀", cardFilter: { filter: (c) => c.name === "杀", min: 1, max: 1 }, targetFilter: { min: 1, max: 3, filter: (view, t) => viewCanAttack(view.players, view.cardMap, view.currentPlayerIndex, t) } }, respondPrompt: { type: "useCard", title: "打出杀", cardFilter: { filter: (c) => c.name === "杀", min: 1, max: 1 } }, label: "杀", style: "danger", activeWhen: (ctx) => defaultPlayActive(ctx) && viewCanSlash(ctx.view, ctx.perspectiveIdx) }; })); //#endregion //#region src/engine/skills/cards/桃.ts var 桃_exports = /* @__PURE__ */ __exportAll({ peachEffect: () => peachEffect }); /** 桃的牌特有校验:目标必须已受伤 */ function canUsePeach(state, ownerId, params) { const target = params.target ?? params.targets?.[0] ?? ownerId; return state.players[target]?.alive === true && state.players[target]?.health < state.players[target]?.maxHealth ? null : "桃只能对受伤角色使用"; } /** 桃的结算:回复目标 1 点体力 */ async function resolvePeach(ctx) { const { state, source, target } = ctx; await applyAtom(state, { type: "回复体力", target, amount: 1, source }); } /** 桃使用阶段激活条件:默认出牌条件 + 自己已受伤(满血不可出桃)。 */ function peachActiveWhen(ctx) { if (!defaultPlayActive(ctx)) return false; const p = ctx.view.players[ctx.perspectiveIdx]; return p ? p.health < p.maxHealth : false; } var peachEffect; var init_桃 = __esmMin((() => { init_apply(); init_action_active(); peachEffect = { timing: "出牌阶段", target: { kind: "wounded", min: 0, max: 1 }, canUse: canUsePeach, resolve: resolvePeach, respond: { validate: (state, ownerId, params) => { const slot = state.pendingSlots.get(ownerId); if (!slot) return "当前不需要回应"; if (slot.atom.target !== ownerId) return "不是问你的"; if (slot.atom.type !== "请求回应") return "当前不是求桃"; if (slot.atom.requestType !== "桃/求桃") return "当前不是求桃"; const cardId = params.cardId; if (cardId) { if (!state.players[ownerId].hand.includes(cardId)) return "牌不在手牌中"; if (state.cardMap[cardId].name !== "桃") return "只能用桃救援"; } return null; }, execute: async (state, ownerId, params) => { const cardId = params.cardId; if (!cardId) return; await applyAtom(state, { type: "移动牌", cardId, from: { zone: "手牌", player: ownerId }, to: { zone: "弃牌堆" } }); state.localVars["求桃/已救"] = true; } }, prompt: { type: "useCardAndTarget", title: "出桃", cardFilter: { filter: (c) => c.name === "桃", min: 1, max: 1 }, targetFilter: { min: 0, max: 1, filter: (view, target) => { const p = view.players[target]; return p?.alive === true && p.health < p.maxHealth; } }, selfTarget: true }, respondPrompt: { type: "useCard", title: "打出桃", cardFilter: { filter: (c) => c.name === "桃", min: 1, max: 1 } }, label: "桃", style: "primary", activeWhen: peachActiveWhen }; })); //#endregion //#region src/engine/skills/cards/桃园结义.ts var 桃园结义_exports = /* @__PURE__ */ __exportAll({ peachGardenEffect: () => peachGardenEffect }); /** 桃园结义的逐目标结算:满血跳过 → 回复体力 */ async function resolvePeachGarden(ctx) { const { state, target } = ctx; const p = state.players[target]; if (!p?.alive) return; if (p.health >= p.maxHealth) return; await applyAtom(state, { type: "回复体力", target, amount: 1 }); } var peachGardenEffect; var init_桃园结义 = __esmMin((() => { init_apply(); peachGardenEffect = { timing: "出牌阶段", target: { kind: "allPlayers" }, /** 满血目标无回血效果 → 不询问无懈、不结算 */ hasEffect: (state, target) => { const p = state.players[target]; return !!p?.alive && p.health < p.maxHealth; }, resolve: resolvePeachGarden, prompt: { type: "useCard", title: "桃园结义", cardFilter: { filter: (c) => c.name === "桃园结义", min: 1, max: 1 } }, label: "桃园结义", style: "primary" }; })); //#endregion //#region src/engine/skills/cards/火攻.ts var 火攻_exports = /* @__PURE__ */ __exportAll({ fireAttackEffect: () => fireAttackEffect }); /** 火攻的结算:展示手牌 → 弃同花色 → 火焰伤害 */ async function resolveFireAttack(ctx) { const { state, source, target, cardId } = ctx; const targetPlayer = state.players[target]; if (!targetPlayer || targetPlayer.hand.length === 0) return; delete state.localVars["火攻/展示"]; delete state.localVars["火攻/展示花色"]; delete state.localVars["火攻/弃牌"]; const revealFallback = targetPlayer.hand[0]; await applyAtom(state, { type: "请求回应", requestType: "火攻/展示", target, prompt: { type: "useCard", title: "火攻:展示一张手牌", cardFilter: { filter: () => true, min: 1, max: 1 } }, timeout: 15 }); let revealed = state.localVars["火攻/展示"]; if (!revealed) { revealed = { cardId: revealFallback, suit: state.cardMap[revealFallback]?.suit ?? "" }; state.localVars["火攻/展示"] = revealed; state.localVars["火攻/展示花色"] = revealed.suit; } await applyAtom(state, { type: "展示", player: target, cardId: revealed.cardId }); const revealedSuit = revealed.suit; const fromPlayer = state.players[source]; if (!(fromPlayer?.alive ? fromPlayer.hand.some((id) => state.cardMap[id]?.suit === revealedSuit) : false)) { await applyAtom(state, { type: "展示结束", player: target, cardId: revealed.cardId }); return; } delete state.localVars["火攻/弃牌"]; await applyAtom(state, { type: "请求回应", requestType: "火攻/弃牌", target: source, prompt: { type: "useCard", title: `火攻:弃置一张 ${revealedSuit} 手牌对其造成1点火焰伤害(不弃则无效)`, cardFilter: { filter: (c) => c.suit === revealedSuit, min: 1, max: 1 } }, timeout: 15 }); const discardId = state.localVars["火攻/弃牌"]; if (discardId && state.players[target]?.alive) { await applyAtom(state, { type: "弃置", player: source, cardIds: [discardId], voluntary: true }); await runDamageFlow(state, source, target, 1, cardId, "火焰"); } await applyAtom(state, { type: "展示结束", player: target, cardId: revealed.cardId }); } /** 火攻牌特有校验:目标有手牌(可对自己使用火攻) */ function canUseFireAttack(state, ownerId, params) { const targets = params.targets; if (!Array.isArray(targets) || targets.length !== 1) return "火攻只能指定一名目标"; const target = targets[0]; const targetPlayer = state.players[target]; if (!targetPlayer?.alive) return "目标不合法"; if (targetPlayer.hand.length === 0) return "目标必须有手牌"; return null; } var fireAttackEffect; var init_火攻 = __esmMin((() => { init_apply(); init_damage(); fireAttackEffect = { timing: "出牌阶段", target: { kind: "any", min: 1, max: 1 }, canUse: canUseFireAttack, resolve: resolveFireAttack, respond: { validate: (state, ownerId, params) => { const slot = state.pendingSlots.get(ownerId); if (!slot) return "当前不需要回应"; if (slot.atom.type !== "请求回应") return "当前不是火攻窗口"; const reqType = slot.atom.requestType; if (reqType !== "火攻/展示" && reqType !== "火攻/弃牌") return "当前不是火攻窗口"; const cardId = params.cardId; if (typeof cardId !== "string") return "cardId required"; const self = state.players[ownerId]; if (!self?.alive) return "你已死亡"; if (!self.hand.includes(cardId)) return "牌不在手牌中"; if (reqType === "火攻/弃牌") { const revealedSuit = state.localVars["火攻/展示花色"]; const card = state.cardMap[cardId]; if (!revealedSuit || card?.suit !== revealedSuit) return "必须弃置与展示牌相同花色的手牌"; } return null; }, execute: async (state, ownerId, params) => { const reqType = state.pendingSlots.get(ownerId).atom.requestType; const cardId = params.cardId; const card = state.cardMap[cardId]; if (reqType === "火攻/展示") { state.localVars["火攻/展示"] = { cardId, suit: card?.suit ?? "" }; state.localVars["火攻/展示花色"] = card?.suit ?? ""; } else state.localVars["火攻/弃牌"] = cardId; } }, prompt: { type: "useCardAndTarget", title: "火攻", cardFilter: { filter: (c) => c.name === "火攻", min: 1, max: 1 }, targetFilter: { min: 1, max: 1, allowSelf: true } }, respondPrompt: { type: "useCard", title: "火攻", cardFilter: { filter: () => true, min: 1, max: 1 } }, label: "火攻", style: "danger" }; })); //#endregion //#region src/engine/flows/pick-card-panel.ts /** 在 actionLog 中当前(最后一条)条目之前插入一条"设置手牌顺序"条目。 * 重放时 restore() 识别该条目后直接恢复目标 hand 顺序(不走 dispatch)→ 后续盲选 * 取 hand[K] 确定性正确。去重:若前一条已是同 target 的设置手牌顺序且 order 一致, * 跳过(避免重放二次插入)。 */ function spliceHandOrderEntry(state, target) { const player = state.players[target]; if (!player) return; const order = [...player.hand]; if (order.length === 0) return; const log = state.actionLog; const myIndex = log.length - 1; if (myIndex > 0) { const prev = log[myIndex - 1]; if (prev.message.skillId === "系统规则" && prev.message.actionType === "设置手牌顺序" && prev.message.params.target === target) { const prevOrder = prev.message.params.order; if (Array.isArray(prevOrder) && prevOrder.length === order.length && prevOrder.every((id, i) => id === order[i])) return; } } const entry = { id: `order-${log.length}-${target}`, timestamp: state.clock.now() - state.startedAt, message: { skillId: "系统规则", actionType: "设置手牌顺序", ownerId: log[myIndex].message.ownerId, params: { target, order }, baseSeq: log[myIndex].baseSeq }, baseSeq: log[myIndex].baseSeq }; log.splice(myIndex, 0, entry); } /** 弹 pickTargetCard 选牌面板,使用者(from)从目标区域选一张牌(弃置或获得)。 * - discard: 弃置选定牌(过河拆桥) * - obtain: 获得选定牌(顺手牵羊/反馈) * 手牌盲选时 splice 手牌顺序(重放确定性);超时默认明牌优先(装备→判定)否则 hand[0]。 * 装备保护由调用方的 before-hook(界奇才)在「请求回应」atom 上过滤,本函数不感知。 */ async function runPickTargetCardPanel(state, from, target, targetPlayer, opts) { const obtain = opts.mode === "obtain"; const equipment = Object.entries(targetPlayer.equipment).filter(([, id]) => typeof id === "string").map(([slot, id]) => ({ slot, cardId: id, cardName: state.cardMap[id]?.name ?? "?" })); const judge = opts.includeJudge === false ? [] : targetPlayer.pendingTricks.map((t) => ({ cardId: t.card.id, cardName: t.card.name })); const handCount = targetPlayer.hand.length; const defaultZone = equipment.length > 0 ? { zone: "equipment", cardId: equipment[0].cardId } : judge.length > 0 ? { zone: "judge", cardId: judge[0].cardId } : { zone: "hand", handIndex: 0 }; if (handCount > 0) spliceHandOrderEntry(state, target); const pickAtom = { type: "请求回应", requestType: opts.requestType, target: from, prompt: { type: "pickTargetCard", mode: opts.mode, title: opts.title, target, equipment, judge, handCount }, defaultChoice: defaultZone, timeout: 20 }; await applyAtom(state, pickAtom); const result = getPickResult(state.localVars); delete state.localVars[PICK_RESULT_KEY$3]; const filteredEquip = pickAtom.prompt.equipment ?? []; let fallback = defaultZone; if (fallback.zone === "equipment") { if (!filteredEquip.some((e) => e.cardId === fallback.cardId)) fallback = filteredEquip.length > 0 ? { zone: "equipment", cardId: filteredEquip[0].cardId } : judge.length > 0 ? { zone: "judge", cardId: judge[0].cardId } : { zone: "hand", handIndex: 0 }; } const zone = result?.zone ?? fallback.zone; if (zone === "equipment") { const cardId = result?.cardId ?? fallback.cardId; if (!filteredEquip.some((e) => e.cardId === cardId)) return; if (obtain) await applyAtom(state, { type: "获得", player: from, cardId, from: target }); else await applyAtom(state, { type: "弃置", player: target, cardIds: [cardId] }); } else if (zone === "judge") { const cardId = result?.cardId ?? fallback.cardId; const trick = targetPlayer.pendingTricks.find((t) => t.card.id === cardId); if (trick) { await applyAtom(state, { type: "移除延时锦囊", player: target, trickName: trick.name }); if (obtain) await obtainJudgeZoneCard(state, target, cardId, from); else await discardJudgeZoneCard(state, target, cardId); } } else { const handIndex = result?.handIndex ?? 0; const pickedCard = targetPlayer.hand[handIndex] ?? targetPlayer.hand[0]; if (pickedCard) if (obtain) await applyAtom(state, { type: "获得", player: from, cardId: pickedCard, from: target }); else await applyAtom(state, { type: "弃置", player: target, cardIds: [pickedCard] }); } } var init_pick_card_panel = __esmMin((() => { init_apply(); init_judge_zone(); init_vars_keys(); })); //#endregion //#region src/engine/skills/cards/过河拆桥.ts var 过河拆桥_exports = /* @__PURE__ */ __exportAll({ dismantleEffect: () => dismantleEffect }); /** 过河拆桥牌特有校验:目标有牌、非自己 */ function canUseDismantle(state, ownerId, params) { const targets = params.targets; if (!Array.isArray(targets) || targets.length === 0) return "目标不合法"; for (const t of targets) { if (t === ownerId) return "不能对自己使用"; if (!state.players[t]?.alive) return "目标已死亡"; const p = state.players[t]; if (!p) return "目标不合法"; if (!(p.hand.length > 0 || Object.keys(p.equipment).length > 0 || p.pendingTricks.length > 0)) return "目标无可弃置的牌"; } return null; } /** 过河拆桥的结算:选牌面板(弃置) */ async function resolveDismantle(ctx) { const { state, source, target } = ctx; const targetPlayer = state.players[target]; if (targetPlayer) await runPickTargetCardPanel(state, source, target, targetPlayer, { mode: "discard", requestType: "过河拆桥_选牌", title: "选择弃置的目标牌" }); } var dismantleEffect; var init_过河拆桥 = __esmMin((() => { init_pick_card_panel(); init_vars_keys(); dismantleEffect = { timing: "出牌阶段", target: { kind: "other", min: 1, max: 1 }, canUse: canUseDismantle, resolve: resolveDismantle, respond: { validate: (state, ownerId, params) => { const slot = state.pendingSlots.get(ownerId); if (!slot) return "当前不需要回应"; if (slot.atom.type !== "请求回应") return "当前不是选牌窗口"; const atom = slot.atom; if (atom.requestType !== "过河拆桥_选牌") return "当前不是选牌窗口"; const zone = params.zone; if (zone === "equipment" || zone === "judge") { if (typeof params.cardId !== "string") return "cardId required"; } else if (zone === "hand") { if (typeof params.handIndex !== "number") return "handIndex required"; } else return "zone required (equipment|judge|hand)"; if (zone === "equipment" && typeof params.cardId === "string") { const equipList = atom.prompt?.equipment; if (equipList && !equipList.some((e) => e.cardId === params.cardId)) return "该装备不可选"; } return null; }, execute: async (state, _ownerId, params) => { state.localVars[PICK_RESULT_KEY$3] = { zone: params.zone, cardId: params.cardId ?? null, handIndex: params.handIndex ?? null }; } }, prompt: { type: "useCardAndTarget", title: "过河拆桥", cardFilter: { filter: (c) => c.name === "过河拆桥", min: 1, max: 1 }, targetFilter: { min: 1, max: 1 } }, label: "过河拆桥", style: "primary" }; })); //#endregion //#region src/engine/skills/cards/铁索连环.ts var 铁索连环_exports$1 = /* @__PURE__ */ __exportAll({ chainEffect: () => chainEffect }); /** 铁索连环的逐目标结算:按当前状态切换(未横置→横置,已横置→重置)。 * 可指定两个目标,每个目标各自独立切换。 */ async function resolveChain(ctx) { const { state, target } = ctx; await setChain(state, target, !isChained$1(state, target)); } /** 铁索连环牌特有校验:1-2 名存活角色(含自己) */ function canUseChain(state, _ownerId, params) { const targets = params.targets; if (!Array.isArray(targets) || targets.length < 1 || targets.length > 2) return "需选择一至两名角色"; for (const t of targets) if (!state.players[t]?.alive) return "目标不合法"; return null; } var chainEffect; var init_铁索连环$1 = __esmMin((() => { init_face_down(); chainEffect = { timing: "出牌阶段", target: { kind: "any", min: 1, max: 2 }, canUse: canUseChain, resolve: resolveChain, prompt: { type: "useCardAndTarget", title: "铁索连环", cardFilter: { filter: (c) => c.name === "铁索连环", min: 1, max: 1 }, targetFilter: { min: 1, max: 2, allowSelf: true } }, label: "铁索连环", style: "primary" }; })); //#endregion //#region src/engine/skills/cards/闪.ts var 闪_exports = /* @__PURE__ */ __exportAll({ dodgeEffect: () => dodgeEffect }); var COLOR_LIMIT_VAR$1, dodgeEffect; var init_闪 = __esmMin((() => { COLOR_LIMIT_VAR$1 = "闪/色限制"; dodgeEffect = { timing: "生效前", target: { kind: "effect" }, resolve: async (ctx) => { const { state } = ctx; const targetFrame = state.settlementStack[state.settlementStack.length - 2]; if (targetFrame) targetFrame.cancelled = true; }, respond: { validate: (state, ownerId, params) => { const slot = state.pendingSlots.get(ownerId); if (!slot) return "当前不需要回应"; if (slot.atom.target !== ownerId) return "不是问你的"; if (!(slot.atom.type === "询问闪" || slot.atom.type === "请求回应" && slot.atom.requestType === "闪")) return "当前不是出闪的窗口"; const cardId = params.cardId; if (cardId) { if (!state.players[ownerId].hand.includes(cardId)) return "牌不在手牌中"; const card = state.cardMap[cardId]; if (card?.name !== "闪") return "只能打出闪"; const limit = state.localVars[COLOR_LIMIT_VAR$1]; if (limit && card.color !== limit) return `只能打出${limit}色的闪`; } return null; }, execute: async () => {} }, prompt: { type: "useCard", title: "打出闪", cardFilter: { filter: (c) => c.name === "闪", min: 1, max: 1 } }, respondPrompt: { type: "useCard", title: "打出闪", cardFilter: { filter: (c) => c.name === "闪", min: 1, max: 1 } }, label: "闪", style: "default" }; })); //#endregion //#region src/engine/skills/cards/闪电.ts var 闪电_exports = /* @__PURE__ */ __exportAll({ lightningEffect: () => lightningEffect }); /** 判定结果是否触发:黑桃 2~9 */ function isLightningHit(card) { if (card.suit !== "♠") return false; const rank = card.rank; const n = rank === "A" ? 1 : rank === "J" ? 11 : rank === "Q" ? 12 : rank === "K" ? 13 : parseInt(rank, 10); return n >= 2 && n <= 9; } /** 找下一个判定区没有闪电的存活玩家(从 current 之后按座次环形搜索) */ function findNextRecipient(state, current) { const n = state.players.length; for (let i = 1; i <= n; i++) { const idx = (current + i) % n; const p = state.players[idx]; if (!p.alive) continue; if (p.pendingTricks.some((t) => t.name === TRICK_NAME$4)) continue; return idx; } return null; } /** 闪电传递给下家:移除当前玩家的闪电,添加到下一个判定区无闪电的存活玩家。 * 用于:判定非命中(resolveLightning)+ 被无懈可击抵消(onCancelled)。 */ async function passLightningToNext(state, from, card) { await applyAtom(state, { type: "移除延时锦囊", player: from, trickName: TRICK_NAME$4 }); const next = findNextRecipient(state, from); if (next !== null) await applyAtom(state, { type: "添加延时锦囊", player: next, trick: { name: TRICK_NAME$4, source: from, card } }); } /** 闪电被无懈可击抵消(判定前):不判定、不受伤、不弃置,传递给下家。 */ async function onLightningCancelled(state, target, cardId) { await passLightningToNext(state, target, (state.players[target]?.pendingTricks.find((t) => t.name === TRICK_NAME$4))?.card ?? state.cardMap[cardId] ?? { id: cardId, name: TRICK_NAME$4, suit: "♠", color: "黑", rank: "A", type: "锦囊牌" }); } /** 闪电的生效后效果:判定 → 命中则伤害+移除,否则传递下家 */ async function resolveLightning(ctx) { const { state, target, cardId } = ctx; await runJudgeFlow(state, target, TRICK_NAME$4); const judgeCardId = state.localVars["判定/finalJudgeCardId"]; delete state.localVars["判定/finalJudgeCardId"]; const judgeCard = judgeCardId ? state.cardMap[judgeCardId] : void 0; const lightningCard = (state.players[target]?.pendingTricks.find((t) => t.name === TRICK_NAME$4))?.card ?? state.cardMap[cardId] ?? judgeCard ?? { id: cardId, name: TRICK_NAME$4, suit: "♠", color: "黑", rank: "A", type: "锦囊牌" }; if (judgeCard && isLightningHit(judgeCard)) { await runDamageFlow(state, -1, target, 3, void 0, "雷电"); await applyAtom(state, { type: "移除延时锦囊", player: target, trickName: TRICK_NAME$4 }); } else await passLightningToNext(state, target, lightningCard); } var TRICK_NAME$4, lightningEffect; var init_闪电 = __esmMin((() => { init_types(); init_apply(); init_judge(); init_damage(); TRICK_NAME$4 = "闪电"; lightningEffect = { timing: "出牌阶段", target: { kind: "self" }, delayed: true, cancelledBy: { cardName: "无懈可击", broadcast: true }, resolve: resolveLightning, onCancelled: onLightningCancelled, prompt: { type: "useCard", title: "闪电", cardFilter: { filter: (c) => c.name === TRICK_NAME$4, min: 1, max: 1 } }, label: "闪电", style: "danger" }; })); //#endregion //#region src/engine/skills/cards/顺手牵羊.ts var 顺手牵羊_exports = /* @__PURE__ */ __exportAll({ snatchEffect: () => snatchEffect }); function canUseSnatch(state, ownerId, params) { const target = params.target ?? params.targets?.[0]; if (target === void 0) return "目标不合法"; if (target === ownerId) return "不能对自己使用"; if (!state.players[target]?.alive) return "目标已死亡"; const ignoreDistance = !!state.players[ownerId]?.tags.includes("奇才/无距离限制"); const useCardId = params.cardId; if (!ignoreDistance && effectiveDistance(state, ownerId, target, useCardId) > 1) return "距离太远"; const p = state.players[target]; if (!p) return "目标不合法"; if (!(p.hand.length > 0 || Object.keys(p.equipment).length > 0 || p.pendingTricks.length > 0)) return "目标无可获取的牌"; return null; } /** 顺手牵羊的结算:选牌面板(获得) */ async function resolveSnatch(ctx) { const { state, source, target } = ctx; const targetPlayer = state.players[target]; if (targetPlayer) await runPickTargetCardPanel(state, source, target, targetPlayer, { mode: "obtain", requestType: "顺手牵羊_选牌", title: "选择获得的目标牌" }); } var snatchEffect; var init_顺手牵羊 = __esmMin((() => { init_distance(); init_viewDistance(); init_pick_card_panel(); init_vars_keys(); snatchEffect = { timing: "出牌阶段", target: { kind: "distance", dist: 1, min: 1, max: 1 }, canUse: canUseSnatch, resolve: resolveSnatch, respond: { validate: (state, ownerId, params) => { const slot = state.pendingSlots.get(ownerId); if (!slot) return "当前不需要回应"; if (slot.atom.type !== "请求回应") return "当前不是选牌窗口"; if (slot.atom.requestType !== "顺手牵羊_选牌") return "当前不是选牌窗口"; const zone = params.zone; if (zone === "equipment" || zone === "judge") { if (typeof params.cardId !== "string") return "cardId required"; } else if (zone === "hand") { if (typeof params.handIndex !== "number") return "handIndex required"; } else return "zone required (equipment|judge|hand)"; return null; }, execute: async (state, _ownerId, params) => { state.localVars[PICK_RESULT_KEY$3] = { zone: params.zone, cardId: params.cardId ?? null, handIndex: params.handIndex ?? null }; } }, prompt: { type: "useCardAndTarget", title: "顺手牵羊", cardFilter: { filter: (c) => c.name === "顺手牵羊", min: 1, max: 1 }, targetFilter: { min: 1, max: 1, filter: (view, t) => { if (t === view.currentPlayerIndex) return false; const tp = view.players[t]; if (!tp || tp.alive === false) return false; if (view.players[view.currentPlayerIndex]?.tags?.includes("奇才/无距离限制")) return true; return viewEffectiveDistance(view.players, view.currentPlayerIndex, t) <= 1; } } }, label: "顺手牵羊", style: "primary" }; })); //#endregion //#region src/engine/skills/cards/index.ts var cards_exports = /* @__PURE__ */ __exportAll({ cardEffectMap: () => cardEffectMap, getAllCardEffects: () => getAllCardEffects, getCardEffect: () => getCardEffect, hasCardEffect: () => hasCardEffect, requireCardEffect: () => requireCardEffect }); function getCardEffect(cardName) { return cardEffectMap[cardName]; } function requireCardEffect(cardName) { const effect = cardEffectMap[cardName]; if (!effect) throw new Error(`CardEffect 未注册: ${cardName}`); return effect; } function hasCardEffect(cardName) { return cardName in cardEffectMap; } function getAllCardEffects() { return Object.entries(cardEffectMap); } var cardEffectMap; var init_cards = __esmMin((() => { init_万箭齐发(); init_乐不思蜀(); init_五谷丰登(); init_借刀杀人(); init_兵粮寸断(); init_决斗(); init_南蛮入侵(); init_无中生有(); init_无懈可击(); init_杀(); init_桃(); init_桃园结义(); init_火攻(); init_过河拆桥(); init_酒(); init_铁索连环$1(); init_闪(); init_闪电(); init_顺手牵羊(); cardEffectMap = { "万箭齐发": arrowVolleyEffect, "乐不思蜀": indulgenceEffect, "五谷丰登": bountifulHarvestEffect, "借刀杀人": borrowedSwordEffect, "兵粮寸断": supplyShortageEffect, "决斗": duelEffect, "南蛮入侵": barbarianInvasionEffect, "无中生有": exNihiloEffect, "无懈可击": nullificationEffect, "杀": slashEffect, "桃": peachEffect, "桃园结义": peachGardenEffect, "火攻": fireAttackEffect, "过河拆桥": dismantleEffect, "酒": wineEffect, "铁索连环": chainEffect, "闪": dodgeEffect, "闪电": lightningEffect, "顺手牵羊": snatchEffect }; })); //#endregion //#region src/engine/skills/丈八蛇矛.ts var 丈八蛇矛_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$304, onInit: () => onInit$304, onMount: () => onMount$272 }); function createSkill$304(id, ownerId) { return { id, ownerId, name: "丈八蛇矛", description: "可将两张手牌当杀使用" }; } /** 影子卡 id:${id1}#${id2}#丈八蛇矛 —— 拼接两张原卡 id 避免与单卡 shadow 冲突 */ function shadowIdOf$26(id1, id2) { return `${id1}#${id2}#丈八蛇矛`; } function onInit$304(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "transform", (state, params) => { const myTurn = state.currentPlayerIndex === ownerId; const inActPhase = state.phase === "出牌"; const free = !hasBlockingPending(state); const self = state.players[ownerId]; const selfAlive = self.alive === true; const cardIds = params.cardIds; if (!Array.isArray(cardIds) || cardIds.length !== 2) return "需要选择 2 张手牌"; const [id1, id2] = cardIds; if (typeof id1 !== "string" || typeof id2 !== "string") return "cardIds 必须为字符串"; if (id1 === id2) return "不能选择同一张牌"; const cardInHand = !!self && self.hand.includes(id1) && self.hand.includes(id2); const c1 = state.cardMap[id1]; const c2 = state.cardMap[id2]; const cardsExist = !!c1 && !!c2; const weaponId = self.equipment["武器"]; const hasZhangba = (weaponId ? state.cardMap[weaponId] : void 0)?.name === "丈八蛇矛"; const slot = state.pendingSlots.get(ownerId); const atomType = (slot?.atom)?.type; const reqType = (slot?.atom)?.requestType; const atomTarget = (slot?.atom)?.target; return selfAlive && cardInHand && cardsExist && hasZhangba && (myTurn && inActPhase && free || !!slot && atomTarget === ownerId && (atomType === "询问杀" || atomType === "请求回应" && reqType === "杀/respondKill")) ? null : "丈八蛇矛转化条件不满足"; }, async (state, params) => { const cardIds = params.cardIds; const [id1, id2] = cardIds; await applyAtom(state, { type: "当作", player: ownerId, cardIds, shadowId: shadowIdOf$26(id1, id2), outputName: "杀" }); }, (state, params) => { const cardIds = params.cardIds; const [id1, id2] = Array.isArray(cardIds) ? cardIds : []; const sId = id1 && id2 ? shadowIdOf$26(id1, id2) : void 0; if (sId) { delete state.cardMap[sId]; const self = state.players[ownerId]; const idx = self.hand.indexOf(sId); if (idx >= 0) self.hand.splice(idx, 1); self.hand.push(id1, id2); } }); const unloadAlt = declareAlternativeResponse(state, ownerId, "询问杀"); return () => { unloadAlt(); }; } function onMount$272(skill, api) { api.defineAction("transform", { label: "丈八蛇矛", style: "passive", prompt: { type: "useCardAndTarget", title: "选择 2 张手牌当杀使用", cardFilter: { filter: () => true, min: 2, max: 2 }, targetFilter: { min: 1, max: 1, filter: (view, t) => viewCanAttack(view.players, view.cardMap, view.currentPlayerIndex, t) } }, transform: (card) => ({ name: "杀", sourceCardId: card.id, fromSkill: skill.id }), activeWhen: (ctx) => { const p = ctx.view.players[ctx.perspectiveIdx]; if (!p) return false; const weaponId = p.equipment["武器"]; if ((weaponId ? ctx.view.cardMap[weaponId] : void 0)?.name !== "丈八蛇矛") return false; if ((p.handCount ?? 0) < 2) return false; const ownTurnCanSlash = defaultPlayActive(ctx) && viewCanSlash(ctx.view, ctx.perspectiveIdx); const slot = ctx.view.pending; const atomType = (slot?.atom)?.type; const reqType = (slot?.atom)?.requestType; const askedKill = !!slot && slot.target === ctx.perspectiveIdx && (atomType === "询问杀" || atomType === "请求回应" && reqType === "杀/respondKill"); return ownTurnCanSlash || askedKill; } }); } var init_丈八蛇矛 = __esmMin((() => { init_skill(); init_apply(); init_viewDistance(); init_action_active(); })); //#endregion //#region src/engine/skills/不屈.ts var 不屈_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$303, default: () => 不屈_default, onInit: () => onInit$303 }); function createSkill$303(id, ownerId) { return { id, ownerId, name: "不屈", description: "锁定技:濒死时将牌堆顶一张牌作\"创\"牌置于武将牌上,点数与已有创牌均不同则回复至1体力,相同则移去此牌", isLocked: true }; } function onInit$303(skill, state) { const ownerId = skill.ownerId; registerAfterHook(state, skill.id, ownerId, "陷入濒死", async (ctx) => { if (ctx.atom.target !== ownerId) return; if (!ctx.state.players[ownerId]?.alive) return; if (ctx.state.zones.deck.length === 0) return; delete ctx.state.localVars[DUPLICATE_KEY$1]; await applyAtom(ctx.state, { type: "置创牌", player: ownerId, varsKey: WOUND_KEY$1, resultKey: DUPLICATE_KEY$1 }); const duplicate = ctx.state.localVars[DUPLICATE_KEY$1]; delete ctx.state.localVars[DUPLICATE_KEY$1]; if (duplicate) return; const healthAfter = ctx.state.players[ownerId].health; const amount = Math.max(0, 1 - healthAfter); if (amount > 0) await applyAtom(ctx.state, { type: "回复体力", target: ownerId, amount }); ctx.state.localVars[SURVIVE_KEY$1] = ownerId; }); return () => {}; } var SURVIVE_KEY$1, WOUND_KEY$1, DUPLICATE_KEY$1, 不屈_default; var init_不屈 = __esmMin((() => { init_apply(); init_skill(); SURVIVE_KEY$1 = "不屈/存活"; WOUND_KEY$1 = "不屈/创牌"; DUPLICATE_KEY$1 = "不屈/重复"; 不屈_default = { createSkill: createSkill$303, onInit: onInit$303 }; })); //#endregion //#region src/engine/skills/义绝.ts var 义绝_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$302, default: () => _skillModule$131, onInit: () => onInit$302, onMount: () => onMount$271 }); function createSkill$302(id, ownerId) { return { id, ownerId, name: "义绝", description: "出牌阶段限一次,弃置一张牌并令一名其他角色展示一张手牌;黑色则其本回合非锁定技失效且不能使用或打出手牌,你对其红桃杀伤害+1;红色则你获得之,然后可令其回1点体力" }; } function onInit$302(skill, state) { const ownerId = skill.ownerId; const unloadSuppression = registerSuppressionProvider(state, (st, targetOwnerId, _skillId) => st.players[targetOwnerId]?.tags.includes(SUPPRESSION_TAG$2) === true); registerAction(state, skill.id, ownerId, "use", (st, params) => { const self = st.players[ownerId]; if (!self) return "player not found"; if (!self.alive) return "你已死亡"; if (st.currentPlayerIndex !== ownerId) return "只能在你的回合使用"; if (st.phase !== "出牌") return "只能在出牌阶段使用"; if (hasBlockingPending(st)) return "当前有未完成的询问"; if (usedThisTurn$3(st, ownerId, "义绝")) return "本回合已使用过义绝"; const cardId = params.cardId; if (!cardId) return "请选择要弃置的牌"; if (!self.hand.includes(cardId)) return "弃置的牌必须在手牌中"; const targets = params.targets; if (!Array.isArray(targets) || targets.length !== 1) return "需要指定一名目标"; const target = targets[0]; if (target === ownerId) return "不能对自己使用义绝"; const targetPlayer = st.players[target]; if (!targetPlayer?.alive) return "目标不合法"; if (targetPlayer.hand.length === 0) return "目标没有手牌"; return null; }, async (st, params) => { const from = ownerId; await markOncePerTurn(st, from, "义绝"); const target = params.targets[0]; const costCardId = params.cardId; await pushFrame(st, "义绝", from, { ...params }); await applyAtom(st, { type: "弃置", player: from, cardIds: [costCardId], voluntary: true }); const targetPlayer = st.players[target]; if (!targetPlayer?.alive || targetPlayer.hand.length === 0) { await popFrame(st); return; } const cards = targetPlayer.hand.map((id) => { const c = st.cardMap[id]; if (!c) return null; return { cardId: id, cardName: c.name, suit: c.suit, rank: c.rank }; }).filter((c) => c !== null); delete st.localVars[REVEAL_KEY]; await applyAtom(st, { type: "请求回应", requestType: REVEAL_REQUEST, target, prompt: { type: "pickProcessingCard", title: "义绝:选择一张手牌展示", cards }, timeout: 30 }); let revealedId = st.localVars[REVEAL_KEY]; const targetHand = st.players[target]?.hand ?? []; if (!revealedId || !targetHand.includes(revealedId)) revealedId = targetHand[0]; delete st.localVars[REVEAL_KEY]; const revealedCard = revealedId ? st.cardMap[revealedId] : void 0; if (!revealedCard) { await popFrame(st); return; } await applyAtom(st, { type: "展示", player: target, cardId: revealedId }); if (revealedCard.color === "黑") { await applyAtom(st, { type: "加标签", player: target, tag: SUPPRESSION_TAG$2 }); await applyAtom(st, { type: "加标签", player: target, tag: BAN_TAG }); await applyAtom(st, { type: "加标签", player: target, tag: HEART_BONUS_TAG }); } else { await applyAtom(st, { type: "移动牌", cardId: revealedId, from: { zone: "手牌", player: target }, to: { zone: "手牌", player: from } }); delete st.localVars[HEAL_KEY$1]; await applyAtom(st, { type: "请求回应", requestType: HEAL_REQUEST, target: from, prompt: { type: "confirm", title: `义绝:是否令 ${st.players[target]?.name ?? "目标"} 回复1点体力?`, confirmLabel: "回复", cancelLabel: "不回复" }, defaultChoice: false, timeout: 15 }); if (st.localVars[HEAL_KEY$1] === true) { const tp = st.players[target]; if (tp?.alive && tp.health < tp.maxHealth) await applyAtom(st, { type: "回复体力", target, amount: 1, source: from }); } delete st.localVars[HEAL_KEY$1]; } await popFrame(st); }); const unloaders = []; for (const p of state.players) { const seatId = p.index; const u = registerAction(state, skill.id, seatId, "respond", (st, params) => { const slot = st.pendingSlots.get(seatId); if (!slot) return "当前不需要回应"; const atom = slot.atom; if (atom["type"] !== "请求回应") return "当前不需要回应"; const reqType = atom["requestType"]; if (reqType === REVEAL_REQUEST) { const cardId = params.cardId; if (!cardId) return "请选择一张手牌展示"; if (!st.players[seatId].hand.includes(cardId)) return "牌不在手牌中"; return null; } if (reqType === HEAL_REQUEST) { if (typeof params.choice !== "boolean") return "需要选择一项"; return null; } return "当前不是义绝回应"; }, async (st, params) => { const reqType = (st.pendingSlots.get(seatId)?.atom)?.requestType; if (reqType === REVEAL_REQUEST) st.localVars[REVEAL_KEY] = params.cardId; else if (reqType === HEAL_REQUEST) st.localVars[HEAL_KEY$1] = params.choice === true; }); unloaders.push(u); } const banHandler = async (ctx) => { const atom = ctx.atom; const target = atom.target; if (typeof target !== "number" || target < 0) return; if (target === ownerId) return; if (!ctx.state.players[target]?.tags.includes(BAN_TAG)) return; if (atom.type === "询问闪" || atom.type === "询问杀") return { kind: "cancel" }; const promptType = atom.prompt?.type; if (promptType && CARD_PLAY_PROMPTS$2.has(promptType)) return { kind: "cancel" }; }; registerBeforeHook(state, skill.id, ownerId, "请求回应", banHandler); registerBeforeHook(state, skill.id, ownerId, "询问闪", banHandler); registerBeforeHook(state, skill.id, ownerId, "询问杀", banHandler); registerBeforeHook(state, skill.id, ownerId, "造成伤害时", async (ctx) => { const atom = ctx.atom; if (atom.source !== ownerId) return; if ((atom.amount ?? 0) <= 0) return; const target = atom.target; if (target === void 0) return; const cardId = atom.cardId; if (typeof cardId !== "string") return; const card = ctx.state.cardMap[cardId]; if (card?.name !== "杀" || card.suit !== "♥") return; if (!ctx.state.players[target]?.tags.includes(HEART_BONUS_TAG)) return; return { kind: "modify", atom: { ...ctx.atom, amount: (atom.amount ?? 0) + 1 } }; }); registerAfterHook(state, skill.id, ownerId, "回合结束", async (ctx) => { if (ctx.atom.player !== ownerId) return; for (const p of ctx.state.players) for (const tag of ALL_TAGS$1) if (p.tags.includes(tag)) await applyAtom(ctx.state, { type: "去标签", player: p.index, tag }); }); return () => { unloaders.forEach((u) => u()); unloadSuppression(); }; } function onMount$271(skill, api) { api.defineAction("use", { label: "义绝", style: "danger", prompt: { type: "useCardAndTarget", title: "义绝:弃置一张牌,令一名其他角色展示一张手牌", cardFilter: { filter: () => true, min: 1, max: 1 }, targetFilter: { min: 1, max: 1, filter: (view, t) => t !== skill.ownerId && (view.players[t]?.handCount ?? 0) > 0 } }, activeWhen: (ctx) => { if (!activeUnlessUsedThisTurn("义绝")(ctx)) return false; const p = ctx.view.players[ctx.perspectiveIdx]; if (!p) return false; if (!((p.hand?.length ?? 0) > 0)) return false; return ctx.view.players.some((other) => other.index !== skill.ownerId && other.alive && (other.handCount ?? 0) > 0); } }); api.defineAction("respond", { label: "义绝", style: "danger", prompt: { type: "pickProcessingCard", title: "义绝:选择一张手牌展示", cards: [] } }); return () => {}; } var REVEAL_REQUEST, HEAL_REQUEST, REVEAL_KEY, HEAL_KEY$1, SUPPRESSION_TAG$2, BAN_TAG, HEART_BONUS_TAG, ALL_TAGS$1, CARD_PLAY_PROMPTS$2, _skillModule$131; var init_义绝 = __esmMin((() => { init_apply(); init_frame(); init_once_per_turn(); init_skill(); init_skill_suppression(); REVEAL_REQUEST = "义绝/reveal"; HEAL_REQUEST = "义绝/heal"; REVEAL_KEY = "义绝/revealedCardId"; HEAL_KEY$1 = "义绝/healChoice"; SUPPRESSION_TAG$2 = "义绝/非锁定技失效"; BAN_TAG = "义绝/禁出牌"; HEART_BONUS_TAG = "义绝/红桃杀加伤"; ALL_TAGS$1 = [ SUPPRESSION_TAG$2, BAN_TAG, HEART_BONUS_TAG ]; CARD_PLAY_PROMPTS$2 = new Set([ "useCard", "useCardAndTarget", "pickProcessingCard", "pickTargetCard", "distribute" ]); _skillModule$131 = { createSkill: createSkill$302, onInit: onInit$302, onMount: onMount$271 }; })); //#endregion //#region src/engine/skills/乱击.ts var 乱击_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$301, default: () => 乱击_default, onInit: () => onInit$301, onMount: () => onMount$270 }); function createSkill$301(id, ownerId) { return { id, ownerId, name: "乱击", description: "你可以将两张同花色的手牌当【万箭齐发】使用" }; } /** 影子卡 id:${id1}#${id2}#乱击 —— 拼接两张原卡 id 避免与单卡 shadow 冲突 */ function shadowIdOf$25(id1, id2) { return `${id1}#${id2}#乱击`; } function onInit$301(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "transform", (state, params) => { const myTurn = state.currentPlayerIndex === ownerId; const inActPhase = state.phase === "出牌"; const free = !hasBlockingPending(state); const self = state.players[ownerId]; const selfAlive = self.alive === true; const cardIds = params.cardIds; if (!Array.isArray(cardIds) || cardIds.length !== 2) return "需要选择 2 张手牌"; const [id1, id2] = cardIds; if (typeof id1 !== "string" || typeof id2 !== "string") return "cardIds 必须为字符串"; if (id1 === id2) return "不能选择同一张牌"; const cardInHand = !!self && self.hand.includes(id1) && self.hand.includes(id2); const c1 = state.cardMap[id1]; const c2 = state.cardMap[id2]; const cardsExist = !!c1 && !!c2; const sameSuit = !!c1 && !!c2 && c1.suit !== "" && c1.suit === c2.suit; return myTurn && inActPhase && free && selfAlive && cardInHand && cardsExist && sameSuit ? null : "乱击需要两张同花色的手牌"; }, async (state, params) => { const cardIds = params.cardIds; const [id1, id2] = cardIds; await applyAtom(state, { type: "当作", player: ownerId, cardIds, shadowId: shadowIdOf$25(id1, id2), outputName: "万箭齐发" }); }, (state, params) => { const cardIds = params.cardIds; const [id1, id2] = Array.isArray(cardIds) ? cardIds : []; const sId = id1 && id2 ? shadowIdOf$25(id1, id2) : void 0; if (sId) { delete state.cardMap[sId]; const self = state.players[ownerId]; const idx = self.hand.indexOf(sId); if (idx >= 0) self.hand.splice(idx, 1); self.hand.push(id1, id2); } }); return () => {}; } function onMount$270(skill, api) { api.defineAction("transform", { label: "乱击", style: "danger", prompt: { type: "useCardAndTarget", title: "选择 2 张同花色的手牌当万箭齐发使用", cardFilter: { filter: () => true, min: 2, max: 2, comboFilter: (cards) => cards[0]?.suit === cards[1]?.suit }, targetFilter: { min: 0, max: 0 } }, transform: (card) => ({ name: "万箭齐发", sourceCardId: card.id, fromSkill: skill.id }), activeWhen: (ctx) => { if (!defaultPlayActive(ctx)) return false; const p = ctx.view.players[ctx.perspectiveIdx]; if (!p?.hand) return false; const suitCount = {}; for (const c of p.hand) { if (!c.suit) continue; suitCount[c.suit] = (suitCount[c.suit] ?? 0) + 1; if (suitCount[c.suit] >= 2) return true; } return false; } }); } var 乱击_default; var init_乱击 = __esmMin((() => { init_skill(); init_apply(); init_action_active(); 乱击_default = { createSkill: createSkill$301, onInit: onInit$301, onMount: onMount$270 }; })); //#endregion //#region src/engine/skills/乱武.ts var 乱武_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$300, default: () => 乱武_default, onInit: () => onInit$300, onMount: () => onMount$269 }); function createSkill$300(id, ownerId) { return { id, ownerId, name: "乱武", description: "限定技:出牌阶段令所有其他角色依次对距离最近者出杀,无法如此做者失去1点体力" }; } /** P 的"距离最近的另一名角色"集合(存活,含贾诩) */ function nearestOthers$1(state, p) { let min = Infinity; const dist = /* @__PURE__ */ new Map(); for (let i = 0; i < state.players.length; i++) { if (i === p) continue; if (!state.players[i]?.alive) continue; const d = effectiveDistance(state, p, i); dist.set(i, d); if (d < min) min = d; } const result = []; for (const [i, d] of dist) if (d === min) result.push(i); return result; } function onInit$300(skill, state) { const ownerId = skill.ownerId; const unloaders = []; unloaders.push(registerAction(state, skill.id, ownerId, "use", (st, _params) => { if (st.currentPlayerIndex !== ownerId) return "不是你的回合"; if (st.phase !== "出牌") return "只能在出牌阶段发动"; if (hasBlockingPending(st)) return "当前有未完成的询问"; if (st.players[ownerId]?.vars[USED_KEY$20]) return "乱武已使用过(限定技)"; if (!st.players[ownerId]?.alive) return "玩家不存在或已死亡"; if (st.players.filter((p) => p.alive && p.index !== ownerId).length === 0) return "场上没有其他角色"; return null; }, async (st, _params) => { const from = ownerId; st.players[from].vars[USED_KEY$20] = true; await pushFrame(state, "乱武", from, {}); try { const n = st.players.length; for (let step = 1; step < n; step++) { const p = (from + step) % n; if (p === from) continue; if (!st.players[p]?.alive) continue; const nearest = nearestOthers$1(st, p); if (nearest.length === 0) { await applyAtom(st, { type: "失去体力", target: p, amount: 1 }); continue; } delete st.localVars[CHOICE_VAR$4]; await applyAtom(st, { type: "请求回应", requestType: REQUEST_TYPE$7, target: p, prompt: { type: "useCardAndTarget", title: `乱武:对距离最近的角色使用一张【杀】,否则失去1点体力`, cardFilter: { filter: (c) => c.name === "杀", min: 1, max: 1 }, targetFilter: { min: 1, max: 1, filter: (_view, t) => nearest.includes(t) } }, timeout: 20 }); const choice = st.localVars[CHOICE_VAR$4]; delete st.localVars[CHOICE_VAR$4]; if (choice && st.players[p].hand.includes(choice.cardId) && nearestOthers$1(st, p).includes(choice.target) && st.players[choice.target]?.alive) await runUseFlow(st, p, choice.cardId, [choice.target], "杀"); else await applyAtom(st, { type: "失去体力", target: p, amount: 1 }); } } finally { await popFrame(state); } })); for (const pl of state.players) { const seat = pl.index; unloaders.push(registerAction(state, skill.id, seat, "respond", (st, params) => { const slot = st.pendingSlots.get(seat); if (!slot) return "当前不需要回应"; if (slot.atom.requestType !== REQUEST_TYPE$7) return "当前不是乱武询问"; const cardId = params.cardId; const target = params.target; if (typeof cardId !== "string") return "请选择一张杀"; if (typeof target !== "number") return "请选择目标"; if (!st.players[seat]?.hand.includes(cardId)) return "牌不在手牌中"; if (st.cardMap[cardId]?.name !== "杀") return "只能使用杀"; if (!nearestOthers$1(st, seat).includes(target)) return "目标不是距离最近的角色"; return null; }, async (st, params) => { st.localVars[CHOICE_VAR$4] = { cardId: params.cardId, target: params.target }; })); } return () => { for (const u of unloaders) u(); }; } function onMount$269(skill, api) { api.defineAction("use", { label: "乱武", style: "primary", prompt: { type: "confirm", title: "乱武(限定技):令所有其他角色依次对距离最近者出杀,无法如此做者失去1点体力" }, activeWhen: (ctx) => defaultPlayActive(ctx) }); api.defineAction("respond", { label: "出杀", style: "danger", prompt: { type: "useCardAndTarget", title: "乱武:对距离最近的角色使用一张【杀】,否则失去1点体力", cardFilter: { filter: (c) => c.name === "杀", min: 1, max: 1 }, targetFilter: { min: 1, max: 1 } } }); return () => {}; } var USED_KEY$20, CHOICE_VAR$4, REQUEST_TYPE$7, 乱武_default; var init_乱武 = __esmMin((() => { init_apply(); init_frame(); init_use_card(); init_action_active(); init_skill(); init_distance(); USED_KEY$20 = "乱武/used"; CHOICE_VAR$4 = "乱武/出杀"; REQUEST_TYPE$7 = "乱武/出杀"; 乱武_default = { createSkill: createSkill$300, onInit: onInit$300, onMount: onMount$269 }; })); //#endregion //#region src/engine/skills/享乐.ts var 享乐_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$299, default: () => 享乐_default, onInit: () => onInit$299, onMount: () => onMount$268 }); /** 是否基本牌(杀/闪/桃) */ function isBasicCard(state, cardId) { return state.cardMap[cardId]?.type === "基本牌"; } function createSkill$299(id, ownerId) { return { id, ownerId, name: "享乐", description: "锁定技:其他角色用杀指定你为目标时,需额外弃一张基本牌,否则杀对你无效", isLocked: true }; } function onInit$299(skill, state) { const ownerId = skill.ownerId; for (const p of state.players) { const pid = p.index; registerAction(state, skill.id, pid, "respond", (st, params) => { const slot = st.pendingSlots.get(pid); if (!slot) return "当前不需要回应"; if (slot.atom.type !== "请求回应") return "当前不需要回应"; if (slot.atom.requestType !== "享乐/discard") return "当前不是享乐回应"; const cardId = params.cardId; if (typeof cardId !== "string") return "请选择一张基本牌"; if (!st.players[pid].hand.includes(cardId)) return "牌不在手牌中"; if (!isBasicCard(st, cardId)) return "只能弃置基本牌(杀/闪/桃)"; return null; }, async (st, params) => { st.localVars["享乐/discardCard"] = params.cardId; }); } registerBeforeHook(state, skill.id, ownerId, "检测有效性", async (ctx) => { const atom = ctx.atom; if (atom.target !== ownerId) return; const source = atom.source; if (source === void 0 || source === ownerId) return; const killCardId = atom.cardId; if (!killCardId) return; if (ctx.state.cardMap[killCardId]?.name !== "杀") return; const sourcePlayer = ctx.state.players[source]; if (!sourcePlayer?.alive) return; if (!sourcePlayer.hand.some((id) => isBasicCard(ctx.state, id))) return { kind: "cancel" }; delete ctx.state.localVars["享乐/discardCard"]; await applyAtom(ctx.state, { type: "请求回应", requestType: "享乐/discard", target: source, prompt: { type: "useCard", title: `享乐:弃置一张基本牌,否则杀对 ${ctx.state.players[ownerId].name} 无效`, cardFilter: { filter: (c) => c.type === "基本牌", min: 1, max: 1 } }, timeout: 20 }); const discardCardId = ctx.state.localVars["享乐/discardCard"]; delete ctx.state.localVars["享乐/discardCard"]; if (discardCardId && sourcePlayer.hand.includes(discardCardId)) { await applyAtom(ctx.state, { type: "弃置", player: source, cardIds: [discardCardId] }); return; } return { kind: "cancel" }; }); return () => {}; } function onMount$268(_skill, api) { api.defineAction("respond", { label: "享乐", style: "default", prompt: { type: "useCard", title: "享乐:弃置一张基本牌", cardFilter: { filter: (c) => c.type === "基本牌", min: 1, max: 1 } } }); } var 享乐_default; var init_享乐 = __esmMin((() => { init_apply(); init_skill(); 享乐_default = { createSkill: createSkill$299, onInit: onInit$299, onMount: onMount$268 }; })); //#endregion //#region src/engine/skills/仁德.ts var 仁德_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$298, default: () => _skillModule$130, onInit: () => onInit$298, onMount: () => onMount$267 }); function createSkill$298(id, ownerId) { return { id, ownerId, name: "仁德", description: "出牌阶段:将任意张手牌交给其他角色;以此法失去第二张牌时回复1点体力" }; } function onInit$298(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "use", (state, params) => { const myTurn = state.currentPlayerIndex === ownerId; const inActPhase = state.phase === "出牌"; const free = !hasBlockingPending(state); const self = state.players[ownerId]; const selfAlive = self.alive === true; const allocParam = params.allocation; const allocTargets = params.targets; const simpleCardId = params.cardId; const simpleTargets = params.targets; let normalized; if (Array.isArray(allocParam) && allocParam.length > 0 && Array.isArray(allocParam[0].cardIds)) normalized = allocParam; else if (Array.isArray(allocTargets) && allocTargets.length > 0 && Array.isArray(allocTargets[0].cardIds)) normalized = allocTargets; else if (simpleCardId && Array.isArray(simpleTargets) && simpleTargets.length > 0) normalized = [{ target: simpleTargets[0], cardIds: [simpleCardId] }]; else return "需要指定牌和目标"; const hasCards = normalized.reduce((n, t) => n + t.cardIds.length, 0) > 0; const allCardIds = []; let noDuplicates = true; let allInHand = true; if (hasCards) for (const t of normalized) { if (!Array.isArray(t.cardIds)) { allInHand = false; continue; } for (const cardId of t.cardIds) { if (allCardIds.includes(cardId)) noDuplicates = false; allCardIds.push(cardId); if (!self.hand.includes(cardId)) allInHand = false; } } const targetsLegal = hasCards && normalized.every((t) => t.target !== ownerId && state.players[t.target]?.alive === true); return myTurn && inActPhase && free && selfAlive && hasCards && noDuplicates && allInHand && targetsLegal ? null : "现在不能使用仁德"; }, async (state, params) => { const from = ownerId; await pushFrame(state, "仁德", from, { ...params }); const allocParam = params.allocation; const rawTargets = params.targets; let targets; if (Array.isArray(allocParam) && allocParam.length > 0 && Array.isArray(allocParam[0].cardIds)) targets = allocParam; else if (Array.isArray(rawTargets) && rawTargets.length > 0 && Array.isArray(rawTargets[0].cardIds)) targets = rawTargets; else targets = [{ target: rawTargets[0], cardIds: [params.cardId] }]; const self = state.players[from]; const afterCount = (typeof self.vars["仁德/givenCount"] === "number" ? self.vars["仁德/givenCount"] : 0) + targets.reduce((n, t) => n + t.cardIds.length, 0); const shouldHeal = afterCount >= 2 && !self.vars["仁德/healed"]; for (const t of targets) for (const cardId of t.cardIds) await applyAtom(state, { type: "移动牌", cardId, from: { zone: "手牌", player: from }, to: { zone: "手牌", player: t.target } }); self.vars["仁德/givenCount"] = afterCount; if (shouldHeal) { await applyAtom(state, { type: "回复体力", target: from, amount: 1 }); self.vars["仁德/healed"] = true; } await popFrame(state); }); return () => {}; } function onMount$267(skill, api) { api.defineAction("use", { label: "仁德", style: "primary", prompt: { type: "distribute", mode: "allocate", title: "仁德:选择要送出的手牌和目标角色", source: "hand", minPerTarget: 1, maxPerTarget: 99, minTotal: 1, maxTotal: 99, allowSelf: false } }); return () => {}; } var _skillModule$130; var init_仁德 = __esmMin((() => { init_apply(); init_frame(); init_skill(); _skillModule$130 = { createSkill: createSkill$298, onInit: onInit$298, onMount: onMount$267 }; })); //#endregion //#region src/engine/skills/仁王盾.ts var 仁王盾_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$297, onInit: () => onInit$297 }); function createSkill$297(id, ownerId) { return { id, ownerId, name: "仁王盾", description: "防具:黑色杀对你无效", isLocked: true }; } function onInit$297(skill, state) { const ownerId = skill.ownerId; registerBeforeHook(state, skill.id, ownerId, "检测有效性", async (ctx) => { const atom = ctx.atom; if (atom.target !== ownerId) return; const killCardId = atom.cardId; if (!killCardId) return; const killCard = ctx.state.cardMap[killCardId]; if (killCard?.name !== "杀") return; if (killCard.color === "黑") return { kind: "cancel" }; }); return () => {}; } var init_仁王盾 = __esmMin((() => { init_skill(); })); //#endregion //#region src/engine/skills/从谏.ts var 从谏_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$296, default: () => _skillModule$129, onInit: () => onInit$296, onMount: () => onMount$266 }); function createSkill$296(id, ownerId) { return { id, ownerId, name: SKILL_NAME$7, description: "当你成为锦囊牌的目标时,若此牌的目标数大于1,你可以交给其中一名目标角色一张牌,然后摸一张牌,若你给出的是装备牌,改为摸两张牌" }; } function onInit$296(skill, state) { const ownerId = skill.ownerId; const unloadAction = registerAction(state, skill.id, ownerId, "respond", (st, params) => { const slot = st.pendingSlots.get(ownerId); if (!slot) return "当前不需要回应"; const atom = slot.atom; if (atom["type"] !== "请求回应") return "当前不需要回应"; if (atom["requestType"] !== GIVE_RT$5) return "当前不是从谏询问"; const cardId = params.cardId; if (!cardId) return null; if (!st.players[ownerId]?.hand.includes(cardId)) return "牌不在手牌中"; const validTargets = st.localVars[TARGETS_LV]; const target = params.target ?? (Array.isArray(params.targets) ? params.targets[0] : void 0); if (typeof target !== "number") return "请选择一名目标角色"; if (!Array.isArray(validTargets) || !validTargets.includes(target)) return "目标不在本牌目标中"; return null; }, async (st, params) => { const cardId = params.cardId; if (!cardId) { st.localVars[CHOICE_LV] = null; return; } const target = params.target ?? (Array.isArray(params.targets) ? params.targets[0] : void 0); if (typeof target !== "number") { st.localVars[CHOICE_LV] = null; return; } st.localVars[CHOICE_LV] = { cardId, target }; }); const unloadHook = registerAfterHook(state, skill.id, ownerId, "成为目标", async (ctx) => { const atom = ctx.atom; if (atom.target !== ownerId) return; const st = ctx.state; const self = st.players[ownerId]; if (!self?.alive) return; if ((atom.cardId ? st.cardMap[atom.cardId] : void 0)?.type !== "锦囊牌") return; const resolved = ctx.params.resolvedTargets ?? ctx.params.targets ?? []; if (resolved.length <= 1) return; if (self.hand.length === 0) return; const validTargets = resolved.filter((t) => st.players[t]?.alive); st.localVars[TARGETS_LV] = validTargets; delete st.localVars[CHOICE_LV]; await applyAtom(st, { type: "请求回应", requestType: GIVE_RT$5, target: ownerId, prompt: { type: "useCardAndTarget", title: "从谏:交给一名目标角色一张牌(装备牌摸两张,其他摸一张);不交可放弃", cardFilter: { filter: () => true, min: 1, max: 1 }, targetFilter: { min: 1, max: 1, filter: (_view, t) => validTargets.includes(t) && !!_view.players[t]?.alive } }, timeout: 30 }); const choice = st.localVars[CHOICE_LV]; delete st.localVars[CHOICE_LV]; delete st.localVars[TARGETS_LV]; if (!choice) return; await applyAtom(st, { type: "给予", cardId: choice.cardId, from: ownerId, to: choice.target }); await applyAtom(st, { type: "摸牌", player: ownerId, count: st.cardMap[choice.cardId]?.type === "装备牌" ? 2 : 1 }); }); return () => { unloadAction(); unloadHook(); }; } function onMount$266(_skill, _api) {} var SKILL_NAME$7, GIVE_RT$5, CHOICE_LV, TARGETS_LV, _skillModule$129; var init_从谏 = __esmMin((() => { init_apply(); init_skill(); SKILL_NAME$7 = "从谏"; GIVE_RT$5 = "从谏/给牌"; CHOICE_LV = "从谏/给牌"; TARGETS_LV = "从谏/可选目标"; _skillModule$129 = { createSkill: createSkill$296, onInit: onInit$296, onMount: onMount$266 }; })); //#endregion //#region src/engine/skills/伪帝.ts var 伪帝_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$295, default: () => 伪帝_default, onInit: () => onInit$295, onMount: () => onMount$265 }); function createSkill$295(id, ownerId) { return { id, ownerId, name: "伪帝", description: "锁定技,你视为拥有主公的主公技", isLocked: true }; } function onInit$295(skill, state) { const ownerId = skill.ownerId; const unload = registerAfterHook(state, skill.id, ownerId, "回合开始", async (ctx) => { const st = ctx.state; const me = st.players[ownerId]; if (!me?.alive) return; if (me.vars[GRANTED_KEY]) return; const lord = st.players.find((p) => p.alive && p.identity === "主公" && p.index !== ownerId); if (!lord) return; const lordSkills = lord.skills.filter((s) => LORD_SKILLS.has(s)); if (lordSkills.length === 0) return; me.vars[GRANTED_KEY] = true; for (const skillId of lordSkills) await applyAtom(st, { type: "添加技能", player: ownerId, skillId }); }); return () => { unload(); }; } function onMount$265(_skill, _api) {} var GRANTED_KEY, 伪帝_default; var init_伪帝 = __esmMin((() => { init_apply(); init_skill(); init_character_meta(); GRANTED_KEY = "伪帝/granted"; 伪帝_default = { createSkill: createSkill$295, onInit: onInit$295, onMount: onMount$265 }; })); //#endregion //#region src/engine/skills/倾国.ts var 倾国_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$294, default: () => 倾国_default, onInit: () => onInit$294, onMount: () => onMount$264 }); function createSkill$294(id, ownerId) { return { id, ownerId, name: "倾国", description: "你可以将一张黑色手牌当【闪】使用或打出" }; } /** 影子卡 id:${原id}#倾国 */ function shadowIdOf$24(cardId) { return `${cardId}#倾国`; } function onInit$294(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "transform", (state, params) => { const self = state.players[ownerId]; const selfAlive = self.alive === true; const cardId = params.cardId; const cardIdOk = typeof cardId === "string"; const card = cardIdOk ? state.cardMap[cardId] : void 0; const cardInHand = cardIdOk && self.hand.includes(cardId); const isBlack = !!card && card.color === "黑"; return selfAlive && cardInHand && isBlack ? null : "现在不能使用倾国"; }, async (state, params) => { const cardId = params.cardId; const shadowId = shadowIdOf$24(cardId); await applyAtom(state, { type: "当作", player: ownerId, cardIds: [cardId], shadowId, outputName: "闪" }); }, (state, params) => { const cardId = params.cardId; const sId = shadowIdOf$24(cardId); delete state.cardMap[sId]; const self = state.players[ownerId]; const idx = self.hand.indexOf(sId); if (idx >= 0) self.hand[idx] = cardId; }); const unloadDecl = declareAlternativeResponse(state, ownerId, "询问闪"); return () => { unloadDecl(); }; } function onMount$264(skill, api) { api.defineAction("transform", { label: "倾国", style: "passive", prompt: { type: "useCard", title: "选择一张黑色手牌当闪打出", cardFilter: { filter: (c) => c.color === "黑", min: 1, max: 1 } }, transform: (card) => ({ name: "闪", sourceCardId: card.id, fromSkill: skill.id }), activeWhen: (ctx) => { const slot = ctx.view.pending; if (!slot) return false; if (slot.atom.type !== "询问闪") return false; if (slot.target !== ctx.perspectiveIdx) return false; const p = ctx.view.players[ctx.perspectiveIdx]; if (!p) return false; return p.hand?.some((c) => c.color === "黑") ?? false; } }); } var 倾国_default; var init_倾国 = __esmMin((() => { init_skill(); init_apply(); 倾国_default = { createSkill: createSkill$294, onInit: onInit$294, onMount: onMount$264 }; })); //#endregion //#region src/engine/skills/克己.ts var 克己_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$293, default: () => 克己_default, onInit: () => onInit$293, onMount: () => onMount$263 }); function createSkill$293(id, ownerId) { return { id, ownerId, name: "克己", description: "出牌阶段结束时,若你本回合未使用或打出过【杀】,你可以跳过弃牌阶段" }; } function onInit$293(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "respond", (state, _params) => { const slot = state.pendingSlots.get(ownerId); if (slot?.atom.type !== "请求回应") return "当前不需要回应"; if (slot.atom.requestType !== CONFIRM_REQUEST$18) return "当前不是克己确认"; return null; }, async (state, params) => { state.localVars[CONFIRMED_VAR$8] = params.choice === true || params.confirmed === true; }); registerAfterHook(state, skill.id, ownerId, "移动牌", async (ctx) => { const atom = ctx.atom; if (atom.from.zone !== "手牌" || atom.from.player !== ownerId) return; if (atom.to.zone !== "处理区") return; if (ctx.state.cardMap[atom.cardId]?.name === "杀") ctx.state.turn.vars[PLAYED_SLASH_VAR$1] = true; }); registerBeforeHook(state, skill.id, ownerId, "阶段开始", async (ctx) => { const atom = ctx.atom; if (atom.type !== "阶段开始") return; if (atom.player !== ownerId) return; if (atom.phase !== "弃牌") return; if (ctx.state.turn.vars[PLAYED_SLASH_VAR$1]) return; delete ctx.state.localVars[CONFIRMED_VAR$8]; await applyAtom(ctx.state, { type: "请求回应", requestType: CONFIRM_REQUEST$18, target: ownerId, prompt: { type: "confirm", title: "克己:本回合未出杀,是否跳过弃牌阶段?", confirmLabel: "跳过", cancelLabel: "不跳过" }, defaultChoice: false, timeout: 15 }); if (!ctx.state.localVars[CONFIRMED_VAR$8]) return; await applyAtom(ctx.state, { type: "阶段结束", player: ownerId, phase: "弃牌" }); return { kind: "cancel" }; }); return () => {}; } function onMount$263(_skill, api) { api.defineAction("respond", { label: "克己", style: "primary", prompt: { type: "confirm", title: "克己:是否跳过弃牌阶段?", confirmLabel: "跳过", cancelLabel: "不跳过" } }); } var PLAYED_SLASH_VAR$1, CONFIRMED_VAR$8, CONFIRM_REQUEST$18, 克己_default; var init_克己 = __esmMin((() => { init_apply(); init_skill(); PLAYED_SLASH_VAR$1 = "克己/playedSlash"; CONFIRMED_VAR$8 = "克己/confirmed"; CONFIRM_REQUEST$18 = "克己/confirm"; 克己_default = { createSkill: createSkill$293, onInit: onInit$293, onMount: onMount$263 }; })); //#endregion //#region src/engine/skills/八卦阵.ts var 八卦阵_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$292, onInit: () => onInit$292, onMount: () => onMount$262 }); function createSkill$292(id, ownerId) { return { id, ownerId, name: "八卦阵", description: "防具技:当你需要出闪时,可以判定,若为红色则视为出闪" }; } function onInit$292(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "respond", (state, _params) => { if (state.pendingSlots.get(ownerId)?.atom.type !== "请求回应") return "当前不需要回应"; if (state.pendingSlots.get(ownerId).atom.requestType !== "八卦阵/confirm") return "当前不是八卦阵确认"; return null; }, async (state, params) => { state.localVars["八卦阵/confirmed"] = params.choice === true || params.confirmed === true; }); registerBeforeHook(state, skill.id, ownerId, "询问闪", async (ctx) => { if (ctx.atom.target !== ownerId) return; if (ctx.state.zones.deck.length === 0) return; const armorId = ctx.state.players[ownerId]?.equipment?.["防具"]; if (!armorId || ctx.state.cardMap[armorId]?.name !== "八卦阵") return; delete ctx.state.localVars["八卦阵/confirmed"]; await applyAtom(ctx.state, { type: "请求回应", requestType: "八卦阵/confirm", target: ownerId, prompt: { type: "confirm", title: "是否发动八卦阵?", confirmLabel: "发动", cancelLabel: "不发动" }, defaultChoice: false, timeout: 10 }); if (!ctx.state.localVars["八卦阵/confirmed"]) return; const judgeCardId = await runJudgeFlow(ctx.state, ownerId, "八卦阵"); if (!judgeCardId) return; const judgeCard = ctx.state.cardMap[judgeCardId]; if (!judgeCard) return; if (judgeCard.suit === "♥" || judgeCard.suit === "♦") { const dodgeId = `八卦阵:${ownerId}:${judgeCardId}`; const virtualDodge = { id: dodgeId, name: "闪", suit: judgeCard.suit, color: judgeCard.color, rank: judgeCard.rank, type: "基本牌", ephemeral: true }; ctx.state.cardMap[dodgeId] = virtualDodge; await applyAtom(ctx.state, { type: "移动牌", cardId: dodgeId, from: { zone: "处理区" }, to: { zone: "处理区" } }); return { kind: "cancel" }; } }); return () => {}; } function onMount$262(_skill, api) { api.defineAction("respond", { label: "八卦阵", style: "default", prompt: { type: "confirm", title: "是否发动八卦阵?", confirmLabel: "发动", cancelLabel: "不发动" } }); } var init_八卦阵 = __esmMin((() => { init_apply(); init_judge(); init_skill(); })); //#endregion //#region src/engine/skills/八阵.ts var 八阵_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$291, default: () => 八阵_default, onInit: () => onInit$291, onMount: () => onMount$261 }); function createSkill$291(id, ownerId) { return { id, ownerId, name: "八阵", description: "锁定技:没有装备防具时,视为装备着八卦阵", isLocked: true }; } /** owner 当前是否无防具(八阵生效条件) */ function noArmor(state, ownerId) { const equip = state.players[ownerId]?.equipment; if (!equip) return true; return !equip["防具"]; } function onInit$291(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "respond", (state, _params) => { if (state.pendingSlots.get(ownerId)?.atom.type !== "请求回应") return "当前不需要回应"; if (state.pendingSlots.get(ownerId).atom.requestType !== "八阵/confirm") return "当前不是八阵确认"; return null; }, async (state, params) => { state.localVars["八阵/confirmed"] = params.choice === true || params.confirmed === true; }); registerBeforeHook(state, skill.id, ownerId, "询问闪", async (ctx) => { if (ctx.atom.target !== ownerId) return; if (!noArmor(ctx.state, ownerId)) return; if (ctx.state.zones.deck.length === 0) return; delete ctx.state.localVars["八阵/confirmed"]; await applyAtom(ctx.state, { type: "请求回应", requestType: "八阵/confirm", target: ownerId, prompt: { type: "confirm", title: "是否发动八阵(判定,红色视为出闪)?", confirmLabel: "发动", cancelLabel: "不发动" }, defaultChoice: false, timeout: 10 }); if (!ctx.state.localVars["八阵/confirmed"]) return; const judgeCardId = await runJudgeFlow(ctx.state, ownerId, "八阵"); if (!judgeCardId) return; const judgeCard = ctx.state.cardMap[judgeCardId]; if (!judgeCard) return; if (judgeCard.suit === "♥" || judgeCard.suit === "♦") { const dodgeId = `八阵:${ownerId}:${judgeCardId}`; const virtualDodge = { id: dodgeId, name: "闪", suit: judgeCard.suit, color: judgeCard.color, rank: judgeCard.rank, type: "基本牌", ephemeral: true }; ctx.state.cardMap[dodgeId] = virtualDodge; await applyAtom(ctx.state, { type: "移动牌", cardId: dodgeId, from: { zone: "处理区" }, to: { zone: "处理区" } }); return { kind: "cancel" }; } }); return () => {}; } function onMount$261(_skill, api) { api.defineAction("respond", { label: "八阵", style: "default", prompt: { type: "confirm", title: "是否发动八阵?", confirmLabel: "发动", cancelLabel: "不发动" } }); } var 八阵_default; var init_八阵 = __esmMin((() => { init_apply(); init_judge(); init_skill(); 八阵_default = { createSkill: createSkill$291, onInit: onInit$291, onMount: onMount$261 }; })); //#endregion //#region src/engine/skills/兵略.ts var 兵略_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$290, default: () => _skillModule$128, onInit: () => onInit$290, onMount: () => onMount$260 }); function createSkill$290(id, ownerId) { return { id, ownerId, name: "兵略", description: "锁定技,当你首次对一名角色发动\"飞军\"时,你摸两张牌", isLocked: true }; } function onInit$290(_skill, _state) { return () => {}; } function onMount$260(_skill, _api) { return () => {}; } var _skillModule$128; var init_兵略 = __esmMin((() => { _skillModule$128 = { createSkill: createSkill$290, onInit: onInit$290, onMount: onMount$260 }; })); //#endregion //#region src/engine/skills/再起.ts var 再起_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$289, default: () => 再起_default, onInit: () => onInit$289, onMount: () => onMount$259 }); function createSkill$289(id, ownerId) { return { id, ownerId, name: "再起", description: "摸牌阶段,若已受伤,可放弃摸牌,展示牌堆顶X张(X=已损失体力+1),红桃回血弃置,其余入门" }; } function onInit$289(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "respond", (s, _params) => { const slot = s.pendingSlots.get(ownerId); if (slot?.atom.type !== "请求回应") return "当前不需要回应"; if (slot.atom.requestType !== TRIGGER_RT$9) return "当前不是再起回应"; return null; }, async (s, params) => { s.localVars[TRIGGERED_KEY$6] = params.choice === true || params.confirmed === true; }); registerBeforeHook(state, skill.id, ownerId, "阶段开始", async (ctx) => { const atom = ctx.atom; if (atom.type !== "阶段开始") return; if (atom.player !== ownerId) return; if (atom.phase !== "摸牌") return; if (ctx.state.currentPlayerIndex !== ownerId) return; const self = ctx.state.players[ownerId]; if (!self?.alive) return; const lostHealth = self.maxHealth - getHealthValue(self); if (lostHealth <= 0) return; if (ctx.state.zones.deck.length === 0) return; const x = Math.min(lostHealth + 1, ctx.state.zones.deck.length); delete ctx.state.localVars[TRIGGERED_KEY$6]; await applyAtom(ctx.state, { type: "请求回应", requestType: TRIGGER_RT$9, target: ownerId, prompt: { type: "confirm", title: `是否发动再起?(放弃摸牌,展示牌堆顶${x}张,红桃回血,其余入手)`, confirmLabel: "发动", cancelLabel: "不发动" }, defaultChoice: false, timeout: 10 }); if (ctx.state.localVars[TRIGGERED_KEY$6] !== true) return; const shownCardIds = []; for (let i = 0; i < x; i++) { const topCardId = ctx.state.zones.deck[ctx.state.zones.deck.length - 1]; if (!topCardId) break; await applyAtom(ctx.state, { type: "移动牌", cardId: topCardId, from: { zone: "牌堆" }, to: { zone: "处理区" } }); shownCardIds.push(topCardId); } if (shownCardIds.length === 0) return; for (const cardId of shownCardIds) { const card = ctx.state.cardMap[cardId]; if (!card) continue; if (card.suit === "♥") { const self2 = ctx.state.players[ownerId]; if (self2.health < self2.maxHealth) await applyAtom(ctx.state, { type: "回复体力", target: ownerId, amount: 1 }); await applyAtom(ctx.state, { type: "移动牌", cardId, from: { zone: "处理区" }, to: { zone: "弃牌堆" } }); } else await applyAtom(ctx.state, { type: "移动牌", cardId, from: { zone: "处理区" }, to: { zone: "手牌", player: ownerId } }); } const remaining = frameCards(ctx.state).filter((id) => shownCardIds.includes(id)); for (const id of remaining) await applyAtom(ctx.state, { type: "移动牌", cardId: id, from: { zone: "处理区" }, to: { zone: "弃牌堆" } }); await applyAtom(ctx.state, { type: "阶段结束", player: ownerId, phase: "摸牌" }); return { kind: "cancel" }; }); return () => {}; } function onMount$259(_skill, api) { api.defineAction("respond", { label: "再起", style: "primary", prompt: { type: "confirm", title: "是否发动再起?", confirmLabel: "发动", cancelLabel: "不发动" } }); } var TRIGGER_RT$9, TRIGGERED_KEY$6, 再起_default; var init_再起 = __esmMin((() => { init_types(); init_apply(); init_frame(); init_skill(); TRIGGER_RT$9 = "再起/trigger"; TRIGGERED_KEY$6 = "再起/triggered"; 再起_default = { createSkill: createSkill$289, onInit: onInit$289, onMount: onMount$259 }; })); //#endregion //#region src/engine/skills/决堰.ts var 决堰_exports = /* @__PURE__ */ __exportAll({ ABOLISH_PREFIX: () => ABOLISH_PREFIX$1, createSkill: () => createSkill$288, default: () => _skillModule$127, isSlotGroupAbolished: () => isSlotGroupAbolished, onInit: () => onInit$288, onMount: () => onMount$258 }); function createSkill$288(id, ownerId) { return { id, ownerId, name: SKILL_NAME$6, description: "出牌阶段限一次,废除一个装备栏并于本回合获得对应效果:武器栏杀次数+3;防具栏摸三张且手牌上限+3;坐骑栏使用牌无距离限制;宝物栏获得集智" }; } /** 玩家某栏是否已全部废除(栏内所有槽都标记废除) */ function isSlotGroupAbolished(state, player, group) { const slots = SLOT_GROUPS[group]; if (!slots) return false; return slots.every((s) => state.players[player]?.vars[ABOLISH_PREFIX$1 + s]); } /** 检查玩家某栏是否还有未废除的槽(可被决堰废除) */ function canAbolishGroup(state, player, group) { const slots = SLOT_GROUPS[group]; if (!slots) return false; return slots.some((s) => !state.players[player]?.vars[ABOLISH_PREFIX$1 + s]); } function onInit$288(skill, state) { const ownerId = skill.ownerId; const unloadUse = registerAction(state, skill.id, ownerId, "use", (st, params) => { if (st.currentPlayerIndex !== ownerId) return "不是你的回合"; if (st.phase !== "出牌") return "只能在出牌阶段发动"; if (usedThisTurn$3(st, ownerId, SKILL_NAME$6)) return "本回合已使用过决堰"; if (!st.players[ownerId]?.alive) return "玩家不存在或已死亡"; const group = params.slot ?? params.option; if (typeof group !== "string" || !SLOT_CHOICES.includes(group)) return "请选择要废除的装备栏"; if (!canAbolishGroup(st, ownerId, group)) return "该装备栏已全部废除"; return null; }, async (st, params) => { const group = params.slot ?? params.option; await markOncePerTurn(st, ownerId, SKILL_NAME$6); await pushFrame(st, SKILL_NAME$6, ownerId, { group }); const slots = SLOT_GROUPS[group]; const self = st.players[ownerId]; for (const slot of slots) { const equipId = self.equipment[slot]; if (equipId) { await applyAtom(st, { type: "卸下", player: ownerId, slot }); await applyAtom(st, { type: "移动牌", cardId: equipId, from: { zone: "手牌", player: ownerId }, to: { zone: "弃牌堆" } }); } st.players[ownerId].vars[ABOLISH_PREFIX$1 + slot] = true; } if (group === "武器") { st.turn.vars[TURN_WEAPON] = true; await applyAtom(st, { type: "回合用量", player: ownerId, key: slashExtraKey("决堰"), value: 3 }); } else if (group === "防具") { await applyAtom(st, { type: "摸牌", player: ownerId, count: 3 }); const bonusKey = handLimitBonusKey(ownerId); const cur = getHandLimitBonus(st.turn.vars, ownerId) ?? 0; st.turn.vars[bonusKey] = cur + 3; } else if (group === "坐骑") { st.turn.vars[TURN_MOUNT] = true; await applyAtom(st, { type: "回合用量", player: ownerId, key: TURN_MOUNT, value: true }); } else if (group === "宝物") { await applyAtom(st, { type: "添加技能", player: ownerId, skillId: "集智" }); st.players[ownerId].vars[JIZHI_TEMP] = true; } await popFrame(st); }); const unloadExtra = registerSlashExtraProvider(state, ownerId, (st, player) => { if (player !== ownerId) return 0; return st.turn.vars[TURN_WEAPON] ? 3 : 0; }); const unloadDist = registerDistanceExemptor(state, ownerId, (st, from, _to) => { if (from !== ownerId) return false; return !!st.turn.vars[TURN_MOUNT]; }); const unloadTurnEnd = registerAfterHook(state, skill.id, ownerId, "回合结束", async (ctx) => { const atom = ctx.atom; if (atom.type !== "回合结束") return; if (atom.player !== ownerId) return; const self = ctx.state.players[ownerId]; if (!self?.vars[JIZHI_TEMP]) return; delete self.vars[JIZHI_TEMP]; if (self.skills.includes("集智")) await applyAtom(ctx.state, { type: "移除技能", player: ownerId, skillId: "集智" }); }); return () => { unloadUse(); unloadExtra(); unloadDist(); unloadTurnEnd(); }; } function onMount$258(_skill, api) { api.defineAction("use", { label: "决堰", style: "primary", activeWhen: activeUnlessUsedThisTurn(SKILL_NAME$6), prompt: { type: "chooseOption", title: "决堰:选择要废除的装备栏(本回合获得对应效果)", options: [ { value: "武器", label: "武器栏 — 使用杀的限制次数+3" }, { value: "防具", label: "防具栏 — 摸三张牌且手牌上限+3" }, { value: "坐骑", label: "坐骑栏 — 使用牌无距离限制" }, { value: "宝物", label: "宝物栏 — 获得集智" } ] } }); } var SKILL_NAME$6, ABOLISH_PREFIX$1, TURN_WEAPON, TURN_MOUNT, JIZHI_TEMP, SLOT_GROUPS, SLOT_CHOICES, _skillModule$127; var init_决堰 = __esmMin((() => { init_apply(); init_frame(); init_skill(); init_once_per_turn(); init_slash_quota(); init_distance(); init_vars_keys(); SKILL_NAME$6 = "决堰"; ABOLISH_PREFIX$1 = "决堰/废除:"; TURN_WEAPON = "决堰/本回合:武器"; TURN_MOUNT = JUEYAN_MOUNT_VIEW_KEY; JIZHI_TEMP = "决堰/集智临时"; SLOT_GROUPS = { 武器: ["武器"], 防具: ["防具"], 坐骑: ["进攻马", "防御马"], 宝物: ["宝物"] }; SLOT_CHOICES = [ "武器", "防具", "坐骑", "宝物" ]; _skillModule$127 = { createSkill: createSkill$288, onInit: onInit$288, onMount: onMount$258 }; })); //#endregion //#region src/engine/skills/凿险.ts var 凿险_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$287, default: () => 凿险_default, onInit: () => onInit$287, onMount: () => onMount$257 }); function createSkill$287(id, ownerId) { return { id, ownerId, name: "凿险", description: "觉醒技:准备阶段田≥3时,减1体力上限并获得\"急袭\"(田当顺手牵羊)" }; } function onInit$287(skill, state) { const ownerId = skill.ownerId; registerAfterHook(state, skill.id, ownerId, "阶段开始", async (ctx) => { const atom = ctx.atom; if (atom.type !== "阶段开始") return; if (atom.phase !== "准备") return; if (atom.player !== ownerId) return; if (ctx.state.players[ownerId]?.vars[AWAKENED_KEY$9]) return; const self = ctx.state.players[ownerId]; if (!self?.alive) return; if (self.marks.filter((m) => m.id.startsWith(TIAN_PREFIX$4)).length < 3) return; ctx.state.players[ownerId].vars[AWAKENED_KEY$9] = true; const newMax = self.maxHealth - 1; await applyAtom(ctx.state, { type: "设上限", player: ownerId, amount: newMax }); await applyAtom(ctx.state, { type: "添加技能", player: ownerId, skillId: "急袭" }); }); return () => {}; } function onMount$257(_skill, _api) {} var AWAKENED_KEY$9, TIAN_PREFIX$4, 凿险_default; var init_凿险 = __esmMin((() => { init_apply(); init_skill(); AWAKENED_KEY$9 = "凿险/awakened"; TIAN_PREFIX$4 = "屯田/田:"; 凿险_default = { createSkill: createSkill$287, onInit: onInit$287, onMount: onMount$257 }; })); //#endregion //#region src/engine/skills/刚烈.ts var 刚烈_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$286, default: () => 刚烈_default, onInit: () => onInit$286, onMount: () => onMount$256 }); function createSkill$286(id, ownerId) { return { id, ownerId, name: "刚烈", description: "受到伤害后判定,非红桃则来源弃两张手牌或受 1 点伤害" }; } function onInit$286(skill, state) { const ownerId = skill.ownerId; const unloaders = []; for (const p of state.players) { const seatId = p.index; const isOwner = seatId === ownerId; const u = registerAction(state, skill.id, seatId, "respond", (st, _params) => { const slot = st.pendingSlots.get(seatId); if (!slot) return "当前不需要回应"; const atom = slot.atom; if (atom["type"] !== "请求回应") return "当前不需要回应"; const requestType = atom["requestType"]; if (requestType === CHOOSE_REQUEST$3) return null; if (isOwner && requestType === CONFIRM_REQUEST$17) return null; return "当前不是刚烈回应"; }, async (st, params) => { if ((st.pendingSlots.get(seatId)?.atom)?.["requestType"] === CONFIRM_REQUEST$17) st.localVars["刚烈/confirmed"] = params.choice === true; else st.localVars["刚烈/choice"] = params.choice === true ? "discard" : "damage"; }); unloaders.push(u); } registerAfterHook(state, skill.id, ownerId, "判定牌生效后", async (ctx) => { const atom = ctx.atom; if (atom.judgeType !== "刚烈") return; if (atom.player !== ownerId) return; const processing = frameCards(ctx.state); if (processing.length === 0) return; const judgeCardId = processing[processing.length - 1]; const judgeCard = ctx.state.cardMap[judgeCardId]; if (!judgeCard) return; ctx.state.localVars["刚烈/judgeSuit"] = judgeCard.suit; }); registerAfterHook(state, skill.id, ownerId, "受到伤害后", async (ctx) => { const atom = ctx.atom; if (atom.target !== ownerId) return; if ((atom.amount ?? 0) <= 0) return; if (atom.source === void 0 || atom.source === ownerId) return; const sourceIdx = atom.source; if (!ctx.state.players[sourceIdx]?.alive) return; if (ctx.state.zones.deck.length === 0) return; delete ctx.state.localVars["刚烈/confirmed"]; await applyAtom(ctx.state, { type: "请求回应", requestType: CONFIRM_REQUEST$17, target: ownerId, prompt: { type: "confirm", title: "是否发动刚烈?", confirmLabel: "发动", cancelLabel: "不发动" }, defaultChoice: false, timeout: 10 }); if (!ctx.state.localVars["刚烈/confirmed"]) return; delete ctx.state.localVars["刚烈/judgeSuit"]; await runJudgeFlow(ctx.state, ownerId, "刚烈"); const suit = ctx.state.localVars["刚烈/judgeSuit"]; if (suit === void 0) return; if (suit === "♥") return; if (ctx.state.players[sourceIdx].hand.length >= 2) { delete ctx.state.localVars["刚烈/choice"]; await applyAtom(ctx.state, { type: "请求回应", requestType: CHOOSE_REQUEST$3, target: sourceIdx, prompt: { type: "confirm", title: "刚烈:弃置两张手牌,或受到 1 点伤害?", confirmLabel: "弃两张手牌", cancelLabel: "受 1 点伤害" }, defaultChoice: false, timeout: 30 }); if (ctx.state.localVars["刚烈/choice"] === "discard") { const hand = [...ctx.state.players[sourceIdx].hand]; await applyAtom(ctx.state, { type: "弃置", player: sourceIdx, cardIds: hand.slice(0, 2) }); return; } } await runDamageFlow(ctx.state, ownerId, sourceIdx, 1); }); return () => { unloaders.forEach((u) => u()); }; } function onMount$256(_skill, api) { api.defineAction("respond", { label: "刚烈", style: "danger", prompt: { type: "confirm", title: "刚烈:弃置两张手牌,或受到 1 点伤害?", confirmLabel: "弃两张手牌", cancelLabel: "受 1 点伤害" } }); } var CONFIRM_REQUEST$17, CHOOSE_REQUEST$3, 刚烈_default; var init_刚烈 = __esmMin((() => { init_apply(); init_frame(); init_judge(); init_damage(); init_skill(); CONFIRM_REQUEST$17 = "刚烈/confirm"; CHOOSE_REQUEST$3 = "刚烈/choose"; 刚烈_default = { createSkill: createSkill$286, onInit: onInit$286, onMount: onMount$256 }; })); //#endregion //#region src/engine/skills/制衡.ts var 制衡_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$285, onInit: () => onInit$285, onMount: () => onMount$255 }); function createSkill$285(id, ownerId) { return { id, ownerId, name: "制衡", description: "出牌阶段限一次:弃任意张牌(手牌/装备),摸等量的牌" }; } function onInit$285(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "use", (state, params) => { const cardIds = params.cardIds ?? (typeof params.cardId === "string" ? [params.cardId] : void 0); if (!Array.isArray(cardIds) || cardIds.length === 0) return "cardIds required (at least 1)"; if (new Set(cardIds).size !== cardIds.length) return "cardIds 含重复牌"; if (usedThisTurn$3(state, ownerId, "制衡")) return "本回合已使用过制衡"; const self = state.players[ownerId]; if (!self) return "player not found"; for (const cardId of cardIds) { const inHand = self.hand.includes(cardId); const inEquip = Object.values(self.equipment).includes(cardId); if (!inHand && !inEquip) return "牌不在手牌或装备区中"; } return null; }, async (state, params) => { await markOncePerTurn(state, ownerId, "制衡"); const from = ownerId; await pushFrame(state, "制衡", from, { ...params }); const cardIds = params.cardIds ?? (typeof params.cardId === "string" ? [params.cardId] : []); await applyAtom(state, { type: "弃置", player: from, cardIds, voluntary: true }); await applyAtom(state, { type: "摸牌", player: from, count: cardIds.length }); await popFrame(state); }); return () => {}; } function onMount$255(skill, api) { api.defineAction("use", { label: "制衡", style: "primary", prompt: { type: "distribute", mode: "select", title: "制衡:选择要弃置的牌(可多选)", source: "handAndEquip", minTotal: 1, maxTotal: 99 }, activeWhen: (ctx) => activeUnlessUsedThisTurn("制衡")(ctx) }); return () => {}; } var init_制衡 = __esmMin((() => { init_apply(); init_frame(); init_once_per_turn(); init_skill(); })); //#endregion //#region src/engine/skills/制霸.ts var 制霸_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$284, default: () => 制霸_default, onInit: () => onInit$284, onMount: () => onMount$254 }); /** 拼点牌点数:A=1, 2-10=面值, J=11, Q=12, K=13 */ function rankValue$10(rank) { if (rank === "A") return 1; if (rank === "J") return 11; if (rank === "Q") return 12; if (rank === "K") return 13; const n = parseInt(rank, 10); return Number.isFinite(n) ? n : 0; } function createSkill$284(id, ownerId) { return { id, ownerId, name: "制霸", description: "主公技:其他吴势力角色出牌阶段可与你拼点,若其没赢你可获得双方拼点牌;觉醒后可拒绝拼点" }; } function onInit$284(skill, state) { const ownerId = skill.ownerId; const unloaders = []; for (const p of state.players) { const allyIdx = p.index; if (allyIdx === ownerId) continue; if (p.faction !== "吴") continue; const u = registerAction(state, skill.id, allyIdx, "use", (st, params) => { if (ownerId !== 0) return "制霸为主公技,孙策非主公"; const lord = st.players[ownerId]; if (!lord?.alive) return "主公不存在或已死亡"; if (lord.hand.length === 0) return "孙策没有手牌,无法拼点"; const self = st.players[allyIdx]; if (!self?.alive) return "已死亡"; const myTurn = st.currentPlayerIndex === allyIdx; const inActPhase = st.phase === "出牌"; const free = !hasBlockingPending(st); if (!myTurn || !inActPhase || !free) return "现在不能使用制霸"; if (usedThisTurn$3(st, allyIdx, "制霸")) return "本回合已使用过制霸"; const cardId = params.cardId; if (typeof cardId !== "string") return "请选择一张拼点牌"; if (!self.hand.includes(cardId)) return "拼点牌不在手牌中"; return null; }, async (st, params) => { const allyCardId = params.cardId; await markOncePerTurn(st, allyIdx, "制霸"); const lord = st.players[ownerId]; if (getHunziAwakened(lord.vars)) { delete st.localVars[REFUSE_KEY$1]; await applyAtom(st, { type: "请求回应", requestType: REFUSE_RT$1, target: ownerId, prompt: { type: "confirm", title: `${st.players[allyIdx].name} 欲与你制霸拼点,是否接受?`, confirmLabel: "接受", cancelLabel: "拒绝" }, defaultChoice: true, timeout: 20 }); if (st.localVars[REFUSE_KEY$1] === false) return; } await pushFrame(st, "制霸", allyIdx, { ...params }); try { await applyAtom(st, { type: "移动牌", cardId: allyCardId, from: { zone: "手牌", player: allyIdx }, to: { zone: "处理区" } }); delete st.localVars[LORD_CARD_KEY]; await applyAtom(st, { type: "请求回应", requestType: LORD_CARD_RT, target: ownerId, prompt: { type: "useCard", title: `制霸:与 ${st.players[allyIdx].name} 拼点,请出一张手牌`, cardFilter: { filter: () => true, min: 1, max: 1 } }, timeout: 30 }); const lordCardId = st.localVars[LORD_CARD_KEY]; if (!lordCardId || !st.players[ownerId].hand.includes(lordCardId)) { await applyAtom(st, { type: "移动牌", cardId: allyCardId, from: { zone: "处理区" }, to: { zone: "弃牌堆" } }); return; } await applyAtom(st, { type: "移动牌", cardId: lordCardId, from: { zone: "手牌", player: ownerId }, to: { zone: "处理区" } }); if (rankValue$10(st.cardMap[allyCardId]?.rank ?? "") > rankValue$10(st.cardMap[lordCardId]?.rank ?? "")) for (const id of [allyCardId, lordCardId]) await applyAtom(st, { type: "移动牌", cardId: id, from: { zone: "处理区" }, to: { zone: "弃牌堆" } }); else { delete st.localVars[TAKE_KEY$1]; await applyAtom(st, { type: "请求回应", requestType: TAKE_RT$1, target: ownerId, prompt: { type: "confirm", title: "制霸:对方拼点没赢,是否获得双方拼点牌?", confirmLabel: "获得", cancelLabel: "不获得" }, defaultChoice: true, timeout: 20 }); const take = st.localVars[TAKE_KEY$1] !== false; for (const id of [allyCardId, lordCardId]) await applyAtom(st, { type: "移动牌", cardId: id, from: { zone: "处理区" }, to: take ? { zone: "手牌", player: ownerId } : { zone: "弃牌堆" } }); } } finally { await popFrame(st); } }); unloaders.push(u); } for (const p of state.players) { const seatId = p.index; const u = registerAction(state, skill.id, seatId, "respond", (st, params) => { const slot = st.pendingSlots.get(seatId); if (!slot) return "当前不需要回应"; const atom = slot.atom; if (atom["type"] !== "请求回应") return "当前不需要回应"; const rt = atom["requestType"]; if (rt === REFUSE_RT$1 || rt === TAKE_RT$1) { if (seatId !== ownerId) return "只有孙策可以回应"; return null; } if (rt === LORD_CARD_RT) { if (seatId !== ownerId) return "只有孙策可以选择拼点牌"; const cardId = params.cardId; if (typeof cardId !== "string") return "请选择一张拼点牌"; if (!st.players[seatId].hand.includes(cardId)) return "拼点牌不在手牌中"; return null; } return "当前不是制霸回应"; }, async (st, params) => { const slot = st.pendingSlots.get(seatId); if (!slot) return; const rt = slot.atom["requestType"]; if (rt === REFUSE_RT$1) st.localVars[REFUSE_KEY$1] = params.choice === true || params.confirmed === true; else if (rt === LORD_CARD_RT) st.localVars[LORD_CARD_KEY] = params.cardId; else if (rt === TAKE_RT$1) st.localVars[TAKE_KEY$1] = params.choice === true || params.confirmed === true; }); unloaders.push(u); } return () => { unloaders.forEach((u) => u()); }; } function onMount$254(_skill, api) { api.defineAction("respond", { label: "制霸", style: "primary", prompt: { type: "confirm", title: "制霸", confirmLabel: "确认", cancelLabel: "取消" } }); } var REFUSE_RT$1, LORD_CARD_RT, TAKE_RT$1, REFUSE_KEY$1, LORD_CARD_KEY, TAKE_KEY$1, 制霸_default; var init_制霸 = __esmMin((() => { init_apply(); init_frame(); init_once_per_turn(); init_skill(); init_vars_keys(); REFUSE_RT$1 = "制霸/refuse"; LORD_CARD_RT = "制霸/lordCard"; TAKE_RT$1 = "制霸/take"; REFUSE_KEY$1 = "制霸/refuseResult"; LORD_CARD_KEY = "制霸/lordCardId"; TAKE_KEY$1 = "制霸/takeResult"; 制霸_default = { createSkill: createSkill$284, onInit: onInit$284, onMount: onMount$254 }; })); //#endregion //#region src/engine/skills/勤学.ts var 勤学_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$283, default: () => 勤学_default, onInit: () => onInit$283, onMount: () => onMount$253 }); function createSkill$283(id, ownerId) { return { id, ownerId, name: "勤学", description: "觉醒技:准备或结束阶段,若手牌数比体力值多2或更多,减1体力上限,回复1体力或摸2牌,然后获得\"攻心\"" }; } function onInit$283(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "respond", (st, _params) => { const slot = st.pendingSlots.get(ownerId); if (!slot) return "当前不需要回应"; const atom = slot.atom; if (atom["type"] !== "请求回应") return "当前不需要回应"; if (atom["requestType"] !== CHOOSE_RT$23) return "当前不是勤学选择"; return null; }, async (st, params) => { st.localVars[CHOICE_KEY$20] = params.choice === true ? "draw" : "heal"; }); registerAfterHook(state, skill.id, ownerId, "阶段开始", async (ctx) => { const atom = ctx.atom; if (atom.type !== "阶段开始") return; if (atom.player !== ownerId) return; if (atom.phase !== "准备" && atom.phase !== "回合结束") return; if (ctx.state.players[ownerId]?.vars[AWAKENED_KEY$8]) return; const self = ctx.state.players[ownerId]; if (!self?.alive) return; if (self.hand.length - getHealthValue(self) < 2) return; ctx.state.players[ownerId].vars[AWAKENED_KEY$8] = true; const newMax = self.maxHealth - 1; await applyAtom(ctx.state, { type: "设上限", player: ownerId, amount: newMax }); delete ctx.state.localVars[CHOICE_KEY$20]; await applyAtom(ctx.state, { type: "请求回应", requestType: CHOOSE_RT$23, target: ownerId, prompt: { type: "confirm", title: "勤学:摸两张牌,还是回复1点体力?", confirmLabel: "摸两张牌", cancelLabel: "回复1点体力" }, defaultChoice: false, timeout: 30 }); const choice = ctx.state.localVars[CHOICE_KEY$20]; delete ctx.state.localVars[CHOICE_KEY$20]; if (choice === "draw") await applyAtom(ctx.state, { type: "摸牌", player: ownerId, count: 2 }); else { const cur = ctx.state.players[ownerId].health; const max = ctx.state.players[ownerId].maxHealth; const amount = Math.min(1, Math.max(0, max - cur)); if (amount > 0) await applyAtom(ctx.state, { type: "回复体力", target: ownerId, amount }); } await applyAtom(ctx.state, { type: "添加技能", player: ownerId, skillId: "攻心" }); }); return () => {}; } function onMount$253(_skill, api) { api.defineAction("respond", { label: "勤学", style: "primary", prompt: { type: "confirm", title: "勤学:摸两张牌,还是回复1点体力?", confirmLabel: "摸两张牌", cancelLabel: "回复1点体力" } }); } var CHOOSE_RT$23, CHOICE_KEY$20, AWAKENED_KEY$8, 勤学_default; var init_勤学 = __esmMin((() => { init_types(); init_apply(); init_skill(); CHOOSE_RT$23 = "勤学/choose"; CHOICE_KEY$20 = "勤学/choice"; AWAKENED_KEY$8 = "勤学/awakened"; 勤学_default = { createSkill: createSkill$283, onInit: onInit$283, onMount: onMount$253 }; })); //#endregion //#region src/engine/skills/化身.ts var 化身_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$282, default: () => 化身_default, onInit: () => onInit$282, onMount: () => onMount$252 }); /** 是否为界版(界左慈)。 */ function isJie版(state, ownerId) { return state.players[ownerId]?.character === JIE_CHAR; } /** 本局已登场的武将名(state.players 的 character)。化身牌池须排除这些。 */ function debutedCharacters$1(state) { const set = /* @__PURE__ */ new Set(); for (const p of state.players) if (p.character) set.add(p.character); return set; } /** * 从未登场武将中随机抽取 n 张(不重复,排除本局已登场 + 该 owner 化身牌池已有的)。 * 用 state.rngSeed 派生 RNG 并推进写回,保证确定性。 * 池不足时返回所能抽到的全部(不报错——左慈 FAQ 允许池小)。 */ function draw化身Cards(state, ownerId, n) { const debuted = debutedCharacters$1(state); const existingPool = getHuashenPool(state.players[ownerId]?.vars) ?? []; const taken = new Set([...debuted, ...existingPool]); const available = allCharacters.map((c) => c.name).filter((name) => !taken.has(name)); if (available.length === 0) return []; const rng = createRng(state.rngSeed); for (let i = available.length - 1; i > 0; i--) { const j = rng.nextInt(i + 1); const tmp = available[i]; available[i] = available[j]; available[j] = tmp; } state.rngSeed = rng.getState(); return available.slice(0, Math.min(n, available.length)); } /** 武将的可选化身技能:排除 限定/觉醒/主公技。 */ function getUsableSkills(characterName) { const meta = getCharacterMeta(characterName); if (!meta) return []; return meta.skills.filter((s) => !EXCLUDED_SKILLS.has(s)); } function createSkill$282(id, ownerId) { return { id, ownerId, name: "化身", description: "游戏开始时随机获得未登场武将牌作为化身牌(左慈2张/界左慈3张),亮出一张并获得其一个技能(限定/觉醒/主公技除外);回合开始或结束时可更改/替换/移去化身牌" }; } function onInit$282(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "respond", (st, params) => { const slot = st.pendingSlots.get(ownerId); if (!slot) return "当前不需要回应"; const atom = slot.atom; if (atom["type"] !== "请求回应") return "当前不需要回应"; const rt = atom["requestType"]; if (rt === ACTION_REQUEST) { const action = Number(params.option); if (!Number.isInteger(action) || ![ ACTION_REPLACE, ACTION_SWAP, ACTION_SKIP ].includes(action)) return "需要 option(1=替换, 2=移去, 3=不操作)"; const pool = getHuashenPool(st.players[ownerId]?.vars) ?? []; if (action === ACTION_REPLACE && pool.length < 2) return "化身牌不足,无法替换"; if (action === ACTION_SWAP && pool.length < 2) return "化身牌不足,无法移去"; return null; } if (rt === SWAP_COUNT_REQUEST) { const count = Number(params.option); if (!Number.isInteger(count) || ![1, 2].includes(count)) return "需要 option(1 或 2)"; const pool = getHuashenPool(st.players[ownerId]?.vars) ?? []; const maxRemovable = Math.min(2, pool.length - 1); if (count > maxRemovable) return `最多移去 ${maxRemovable} 张`; return null; } if (rt === SWITCH_REQUEST) { if (typeof params.choice !== "boolean") return "需要 choice(布尔)"; return null; } if (rt === CHARACTER_REQUEST) { const pool = getHuashenPool(st.players[ownerId]?.vars) ?? []; const charName = params.option; if (typeof charName !== "string") return "需要 option(武将名)"; if (!pool.includes(charName)) return "该武将不在化身牌池中"; return null; } if (rt === SKILL_REQUEST) { const candidates = st.localVars[CANDIDATES_KEY(ownerId)] ?? []; const selected = params.option; if (typeof selected !== "string") return "需要 option(技能名)"; if (candidates.length > 0 && !candidates.includes(selected)) return "该技能不在候选中"; return null; } return "当前不是化身询问"; }, async (st, params) => { const rt = st.pendingSlots.get(ownerId).atom.requestType; if (rt === ACTION_REQUEST) st.localVars[ACTION_CHOICE_KEY] = Number(params.option); else if (rt === SWAP_COUNT_REQUEST) st.localVars[SWAP_COUNT_KEY] = Number(params.option); else if (rt === SWITCH_REQUEST) st.localVars[SWITCH_CHOICE_KEY] = params.choice === true; else if (rt === CHARACTER_REQUEST) st.localVars[CHARACTER_CHOICE_KEY] = params.option; else if (rt === SKILL_REQUEST) { st.localVars[SELECTED_KEY] = params.option; delete st.localVars[CANDIDATES_KEY(ownerId)]; } }); registerAfterHook(state, skill.id, ownerId, "回合开始", async (ctx) => { const st = ctx.state; if (!st.players[ownerId]?.alive) return; if (!st.localVars[INIT_KEY$5(ownerId)]) { st.localVars[INIT_KEY$5(ownerId)] = true; await initialize化身(st, ownerId); return; } if (ctx.atom.player !== ownerId) return; await offerTurnAction(st, ownerId); }); registerAfterHook(state, skill.id, ownerId, "回合结束后", async (ctx) => { if (ctx.atom.player !== ownerId) return; if (!ctx.state.players[ownerId]?.alive) return; await offerTurnAction(ctx.state, ownerId); }); return () => {}; } /** * 初始化化身:抽 N 张未登场武将牌(标版 N=2,界版 N=3),亮出第一张,获得其一个可选技能。 */ async function initialize化身(state, ownerId) { const player = state.players[ownerId]; if (!player) return; const pool = draw化身Cards(state, ownerId, isJie版(state, ownerId) ? 3 : 2); player.vars[HUASHEN_POOL_KEY] = pool; player.vars[LIT_KEY] = 0; if (pool.length === 0) return; await lightAndGainSkill(state, ownerId); } /** * 亮出一张有可选技能的化身牌,询问并添加其一个可选技能。 * 若该武将无可选技能,自动尝试亮出另一张;都无则放弃。 * excludeIdx 用于替换时排除当前展示牌。 */ async function lightAndGainSkill(state, ownerId, excludeIdx) { const player = state.players[ownerId]; if (!player) return; const pool = getHuashenPool(player.vars); if (!pool || pool.length === 0) return; const usableIndices = pool.map((_, i) => i).filter((i) => getUsableSkills(pool[i]).length > 0).filter((i) => i !== excludeIdx); if (usableIndices.length === 0) return; let chosenIdx; if (usableIndices.length === 1) chosenIdx = usableIndices[0]; else { chosenIdx = await askSelectCharacter(state, ownerId, pool, usableIndices); if (chosenIdx < 0) chosenIdx = usableIndices[0]; } player.vars[LIT_KEY] = chosenIdx; const litChar = pool[chosenIdx]; const selected = await askSelectSkill(state, ownerId, litChar, getUsableSkills(litChar)); if (!selected) return; await applyAtom(state, { type: "添加技能", player: ownerId, skillId: selected }); player.vars[CURRENT_KEY] = selected; } /** * 回合开始/结束行动分派:标版→offerSwitch(是否更换),界版→offerTurnAction(三选一)。 */ async function offerTurnAction(state, ownerId) { if (isJie版(state, ownerId)) await offerJieTurnAction(state, ownerId); else await offerSwitch(state, ownerId); } /** * 标版:询问玩家是否更换化身牌;确认则卸载旧技能,亮出另一张并获得新技能。 */ async function offerSwitch(state, ownerId) { const player = state.players[ownerId]; if (!player) return; const pool = getHuashenPool(player.vars); if (!pool || pool.length < 2) return; delete state.localVars[SWITCH_CHOICE_KEY]; await applyAtom(state, { type: "请求回应", requestType: SWITCH_REQUEST, target: ownerId, prompt: { type: "confirm", title: "化身:是否更改化身牌?", confirmLabel: "更改", cancelLabel: "不更改" }, defaultChoice: false, timeout: 30 }); const choice = state.localVars[SWITCH_CHOICE_KEY]; delete state.localVars[SWITCH_CHOICE_KEY]; if (choice !== true) return; const curLit = player.vars[LIT_KEY] ?? 0; if (getUsableSkills(pool[curLit === 0 ? 1 : 0]).length === 0) return; const oldSkill = player.vars[CURRENT_KEY]; if (oldSkill) { await applyAtom(state, { type: "移除技能", player: ownerId, skillId: oldSkill }); delete player.vars[CURRENT_KEY]; } await lightAndGainSkill(state, ownerId, curLit); } /** * 界版:询问玩家选择 1=替换 / 2=移去 / 3=不操作,并执行。 */ async function offerJieTurnAction(state, ownerId) { const player = state.players[ownerId]; if (!player) return; const pool = getHuashenPool(player.vars); if (!pool || pool.length < 2) return; delete state.localVars[ACTION_CHOICE_KEY]; await applyAtom(state, { type: "请求回应", requestType: ACTION_REQUEST, target: ownerId, prompt: { type: "chooseOption", title: "化身:选择行动", options: [ { value: String(ACTION_REPLACE), label: "替换展示的化身牌及技能" }, { value: String(ACTION_SWAP), label: "移去化身牌并获得等量新的" }, { value: String(ACTION_SKIP), label: "不操作" } ] }, defaultChoice: String(ACTION_SKIP), timeout: 30 }); const action = state.localVars[ACTION_CHOICE_KEY]; delete state.localVars[ACTION_CHOICE_KEY]; if (action === ACTION_REPLACE) await doReplace(state, ownerId); else if (action === ACTION_SWAP) await doSwap(state, ownerId); } /** * 界版行动 1:替换展示的化身牌及因此获得的技能。 * 卸载旧技能,亮出另一张并获得新技能。 */ async function doReplace(state, ownerId) { const player = state.players[ownerId]; if (!player) return; const pool = getHuashenPool(player.vars); if (!pool || pool.length < 2) return; const curLit = player.vars[LIT_KEY] ?? 0; if (!pool.some((name, i) => i !== curLit && getUsableSkills(name).length > 0)) return; const oldSkill = player.vars[CURRENT_KEY]; if (oldSkill) { await applyAtom(state, { type: "移除技能", player: ownerId, skillId: oldSkill }); delete player.vars[CURRENT_KEY]; } await lightAndGainSkill(state, ownerId, curLit); } /** * 界版行动 2:移去至多两张化身牌并获得等量新的化身牌。 * 简化:移去池末尾的卡片,始终保留展示牌(避免歧义)。 * 不改变当前展示的化身牌和技能。 */ async function doSwap(state, ownerId) { const player = state.players[ownerId]; if (!player) return; const pool = getHuashenPool(player.vars); if (!pool || pool.length < 2) return; const litIdx = player.vars[LIT_KEY] ?? 0; const maxRemovable = Math.min(2, pool.length - 1); delete state.localVars[SWAP_COUNT_KEY]; await applyAtom(state, { type: "请求回应", requestType: SWAP_COUNT_REQUEST, target: ownerId, prompt: { type: "chooseOption", title: "化身:移去几张化身牌并获得等量新的?", options: Array.from({ length: maxRemovable }, (_, i) => ({ value: String(i + 1), label: `移去 ${i + 1} 张并获得 ${i + 1} 张新的(展示牌保留)` })) }, defaultChoice: "1", timeout: 30 }); const rawCount = state.localVars[SWAP_COUNT_KEY]; delete state.localVars[SWAP_COUNT_KEY]; if (typeof rawCount !== "number") return; const count = Math.min(rawCount, maxRemovable); if (count < 1) return; const toRemove = []; for (let i = pool.length - 1; i >= 0 && toRemove.length < count; i--) if (i !== litIdx) toRemove.push(i); const removeSet = new Set(toRemove); const kept = []; let newLitIdx = 0; for (let i = 0; i < pool.length; i++) { if (removeSet.has(i)) continue; if (i === litIdx) newLitIdx = kept.length; kept.push(pool[i]); } const drawn = draw化身Cards(state, ownerId, count); kept.push(...drawn); player.vars[HUASHEN_POOL_KEY] = kept; player.vars[LIT_KEY] = newLitIdx; } /** * 请求玩家从牌池中选择一张化身牌(多张有可选技能时)。 * 返回选中的 pool 索引,或 -1(超时/未选)。 */ async function askSelectCharacter(state, ownerId, pool, usableIndices) { delete state.localVars[CHARACTER_CHOICE_KEY]; const characterCards = {}; for (const i of usableIndices) { const meta = getCharacterMeta(pool[i]); characterCards[pool[i]] = { faction: meta?.faction ?? "群", skills: getUsableSkills(pool[i]) }; } await applyAtom(state, { type: "请求回应", requestType: CHARACTER_REQUEST, target: ownerId, prompt: { type: "chooseOption", title: "化身:选择一张化身牌", options: usableIndices.map((i) => ({ value: pool[i], label: pool[i] })), characterCards }, defaultChoice: pool[usableIndices[0]], timeout: 30 }); const chosen = state.localVars[CHARACTER_CHOICE_KEY]; delete state.localVars[CHARACTER_CHOICE_KEY]; return chosen ? pool.indexOf(chosen) : -1; } /** * 请求玩家从 litChar 的 usable 技能中选一个。 * 只有一个可选技能时自动选取(简化);超时/未选 → 第一个。 * 返回选中的技能名,或 undefined(无可选)。 */ async function askSelectSkill(state, ownerId, litChar, usable) { if (usable.length === 0) return void 0; if (usable.length === 1) return usable[0]; delete state.localVars[SELECTED_KEY]; state.localVars[CANDIDATES_KEY(ownerId)] = usable; await applyAtom(state, { type: "请求回应", requestType: SKILL_REQUEST, target: ownerId, prompt: { type: "chooseOption", title: `化身:从「${litChar}」选择一个技能`, options: usable.map((s) => ({ value: s, label: s })) }, defaultChoice: usable[0], timeout: 30 }); let selected = state.localVars[SELECTED_KEY]; delete state.localVars[SELECTED_KEY]; if (!selected || !usable.includes(selected)) selected = usable[0]; return selected; } function onMount$252(_skill, api) { api.defineAction("respond", { label: "化身", style: "primary", prompt: { type: "confirm", title: "化身", confirmLabel: "确定", cancelLabel: "取消" } }); } var JIE_CHAR, LIT_KEY, CURRENT_KEY, INIT_KEY$5, SELECTED_KEY, SWITCH_CHOICE_KEY, ACTION_CHOICE_KEY, SWAP_COUNT_KEY, SKILL_REQUEST, CHARACTER_REQUEST, SWITCH_REQUEST, ACTION_REQUEST, SWAP_COUNT_REQUEST, CANDIDATES_KEY, CHARACTER_CHOICE_KEY, ACTION_REPLACE, ACTION_SWAP, ACTION_SKIP, EXCLUDED_SKILLS, 化身_default; var init_化身 = __esmMin((() => { init_apply(); init_rng(); init_skill(); init_characters(); init_character_meta(); init_vars_keys(); JIE_CHAR = "界左慈"; LIT_KEY = "化身/亮出"; CURRENT_KEY = "化身/当前技能"; INIT_KEY$5 = (ownerId) => `化身/init/${ownerId}`; SELECTED_KEY = "化身/selectedSkill"; SWITCH_CHOICE_KEY = "化身/switchChoice"; ACTION_CHOICE_KEY = "化身/actionChoice"; SWAP_COUNT_KEY = "化身/swapCount"; SKILL_REQUEST = "化身/选技能"; CHARACTER_REQUEST = "化身/选化身牌"; SWITCH_REQUEST = "化身/是否切换"; ACTION_REQUEST = "化身/选择行动"; SWAP_COUNT_REQUEST = "化身/移除数量"; CANDIDATES_KEY = (ownerId) => `化身/candidates/${ownerId}`; CHARACTER_CHOICE_KEY = "化身/characterChoice"; ACTION_REPLACE = 1; ACTION_SWAP = 2; ACTION_SKIP = 3; EXCLUDED_SKILLS = new Set([ "乱武", "涅槃", "凿险", "志继", "若愚", "魂姿", "护驾", "救援", "暴虐", "激将", "颂威", "黄天", "制霸" ]); 化身_default = { createSkill: createSkill$282, onInit: onInit$282, onMount: onMount$252 }; })); //#endregion //#region src/engine/flows/turn.ts /** 亲自启动 player 的一个完整回合(额外回合入口): * 回合开始 → 准备阶段开始 → 准备阶段结束。 * 回合管理的阶段推进 after-hook 据此自动走完该玩家的判定→摸牌→出牌→弃牌→回合结束。 */ async function startTurn(state, player) { await applyAtom(state, { type: "回合开始", player }); await applyAtom(state, { type: "阶段开始", player, phase: "准备" }); await applyAtom(state, { type: "阶段结束", player, phase: "准备" }); } var init_turn = __esmMin((() => { init_apply(); })); //#endregion //#region src/engine/skills/博图.ts var 博图_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$281, default: () => _skillModule$126, onInit: () => onInit$281, onMount: () => onMount$251 }); /** 复刻「回合结束」atom 的 per-turn 清理(幂等防御:博图 cancel 回合结束后 时, * 回合结束 已先行 apply 清理过,此处重复执行无副作用,仅作防御)。 * 与 回合结束.ts apply 保持一致:清空 turn.vars、清所有玩家 duration='turn' 标记、 * 清 /usedThisTurn|/healed|/givenCount|/givenTargets|/basicUsed vars。 */ function clearPerTurnState$3(state) { state.turn.vars = {}; for (const p of state.players) { p.marks = p.marks.filter((m) => m.duration !== "turn"); p.vars = Object.fromEntries(Object.entries(p.vars).filter(([k]) => !k.endsWith("/usedThisTurn") && !k.endsWith("/healed") && !k.endsWith("/givenCount") && !k.endsWith("/givenTargets") && !k.endsWith("/basicUsed"))); } } /** 存活角色数 */ function aliveCount(state) { return state.players.filter((p) => p.alive).length; } function createSkill$281(id, ownerId) { return { id, ownerId, name: "博图", description: "每轮限X次(X为存活角色数至多3),回合结束后若本回合弃牌堆含四种花色,可执行一个额外回合" }; } function onInit$281(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "respond", (st, _params) => { const slot = st.pendingSlots.get(ownerId); if (!slot) return "当前不需要回应"; const atom = slot.atom; if (atom["type"] !== "请求回应") return "当前不需要回应"; if (atom["requestType"] !== CONFIRM_RT$68) return "当前不是博图确认"; return null; }, async (st, params) => { st.localVars[CONFIRMED_KEY$54] = params.choice === true || params.confirmed === true; }); registerAfterHook(state, skill.id, ownerId, "回合开始", async (ctx) => { const atom = ctx.atom; if (atom.type !== "回合开始") return; if (atom.player !== ownerId) return; ctx.state.players[ownerId].vars[BASE_VAR$1] = ctx.state.zones.discardPile.length; }); registerBeforeHook(state, skill.id, ownerId, "回合结束后", async (ctx) => { const atom = ctx.atom; if (atom.type !== "回合结束后") return; if (atom.player !== ownerId) return; const st = ctx.state; const self = st.players[ownerId]; if (!self?.alive) return; const round = st.turn.round; if (self.vars[LAST_ROUND_KEY$2] !== round) { self.vars[LAST_ROUND_KEY$2] = round; self.vars[COUNT_KEY$6] = 0; } const x = Math.min(aliveCount(st), 3); const used = self.vars[COUNT_KEY$6] ?? 0; if (used >= x) return; const base = self.vars[BASE_VAR$1]; if (typeof base !== "number") return; if (st.zones.discardPile.length < base) return; const newCardIds = st.zones.discardPile.slice(base); const suits = new Set(newCardIds.map((id) => st.cardMap[id]?.suit).filter((s) => s === "♠" || s === "♥" || s === "♣" || s === "♦")); if (!(suits.has("♠") && suits.has("♥") && suits.has("♣") && suits.has("♦"))) return; delete st.localVars[CONFIRMED_KEY$54]; await applyAtom(st, { type: "请求回应", requestType: CONFIRM_RT$68, target: ownerId, prompt: { type: "confirm", title: "博图:本回合弃牌堆已含四种花色,是否执行一个额外回合?", description: `每轮限 ${x} 次(已用 ${used} 次)`, confirmLabel: "执行额外回合", cancelLabel: "不执行" }, defaultChoice: false, timeout: 20 }); const confirmed = st.localVars[CONFIRMED_KEY$54]; delete st.localVars[CONFIRMED_KEY$54]; if (!confirmed) return; st.players[ownerId].vars[COUNT_KEY$6] = used + 1; clearPerTurnState$3(st); st.currentPlayerIndex = ownerId; await startTurn(st, ownerId); return { kind: "cancel" }; }); return () => {}; } function onMount$251(_skill, _api) {} var BASE_VAR$1, LAST_ROUND_KEY$2, COUNT_KEY$6, CONFIRM_RT$68, CONFIRMED_KEY$54, _skillModule$126; var init_博图 = __esmMin((() => { init_apply(); init_turn(); init_skill(); BASE_VAR$1 = "博图/base"; LAST_ROUND_KEY$2 = "博图/lastRound"; COUNT_KEY$6 = "博图/count"; CONFIRM_RT$68 = "博图/confirm"; CONFIRMED_KEY$54 = "博图/confirmed"; _skillModule$126 = { createSkill: createSkill$281, onInit: onInit$281, onMount: onMount$251 }; })); //#endregion //#region src/engine/skills/双雄.ts var 双雄_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$280, default: () => 双雄_default, onInit: () => onInit$280, onMount: () => onMount$250 }); function createSkill$280(id, ownerId) { return { id, ownerId, name: "双雄", description: "摸牌阶段,改为进行一次判定,获得判定牌,本回合可将与之颜色不同的手牌当决斗使用" }; } /** 影子卡 id:${原id}#双雄 */ function shadowIdOf$23(cardId) { return `${cardId}#双雄`; } function onInit$280(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "respond", (s, _params) => { const slot = s.pendingSlots.get(ownerId); if (slot?.atom.type !== "请求回应") return "当前不需要回应"; if (slot.atom.requestType !== CONFIRM_RT$67) return "当前不是双雄询问"; return null; }, async (s, params) => { if ((s.pendingSlots.get(ownerId)?.atom)?.requestType === CONFIRM_RT$67) s.localVars[TRIGGERED_KEY$5] = params.choice === true || params.confirmed === true; }); registerBeforeHook(state, skill.id, ownerId, "阶段开始", async (ctx) => { const atom = ctx.atom; if (atom.type !== "阶段开始") return; if (atom.player !== ownerId) return; if (atom.phase !== "摸牌") return; if (ctx.state.currentPlayerIndex !== ownerId) return; if (!ctx.state.players[ownerId]?.alive) return; if (ctx.state.zones.deck.length < 1) return; delete ctx.state.localVars[TRIGGERED_KEY$5]; await applyAtom(ctx.state, { type: "请求回应", requestType: CONFIRM_RT$67, target: ownerId, prompt: { type: "confirm", title: "是否发动双雄?(放弃摸牌,进行判定,获得判定牌,本回合异色手牌当决斗)", confirmLabel: "发动", cancelLabel: "不发动" }, defaultChoice: false, timeout: 10 }); if (ctx.state.localVars[TRIGGERED_KEY$5] !== true) return; await runJudgeFlow(ctx.state, ownerId, "双雄"); delete ctx.state.localVars[TRIGGERED_KEY$5]; return skipPhase(ctx.state, { player: ownerId, phase: "摸牌" }); }); registerAfterHook(state, skill.id, ownerId, "判定牌生效后", async (ctx) => { const atom = ctx.atom; if (atom.player !== ownerId) return; if (atom.judgeType !== "双雄") return; const processing = frameCards(ctx.state); if (processing.length === 0) return; const judgeCardId = processing[processing.length - 1]; const judgeCard = ctx.state.cardMap[judgeCardId]; if (!judgeCard) return; const judgeColor = judgeCard.color; ctx.state.turn.vars[COLOR_KEY$1] = judgeColor; await applyAtom(ctx.state, { type: "回合用量", player: ownerId, key: COLOR_KEY$1, value: judgeColor }); await applyAtom(ctx.state, { type: "移动牌", cardId: judgeCardId, from: { zone: "处理区" }, to: { zone: "手牌", player: ownerId } }); }); registerAction(state, skill.id, ownerId, "transform", (s, params) => { const myTurn = s.currentPlayerIndex === ownerId; const inActPhase = s.phase === "出牌"; const free = !hasBlockingPending(s); const self = s.players[ownerId]; const selfAlive = self.alive === true; const judgeColor = s.turn.vars[COLOR_KEY$1]; const colorSet = typeof judgeColor === "string" && judgeColor !== ""; const cardId = params.cardId; const cardIdOk = typeof cardId === "string"; const cardInHand = cardIdOk && self.hand.includes(cardId); const card = cardIdOk ? s.cardMap[cardId] : void 0; const cardExists = !!card; const colorDiff = !!card && !!judgeColor && card.color !== judgeColor; return myTurn && inActPhase && free && selfAlive && colorSet && cardInHand && cardExists && colorDiff ? null : "双雄:需要一张与本回合判定牌颜色不同的手牌"; }, async (s, params) => { const cardId = params.cardId; const shadowId = shadowIdOf$23(cardId); await applyAtom(s, { type: "当作", player: ownerId, cardIds: [cardId], shadowId, outputName: "决斗" }); }, (s, params) => { const cardId = params.cardId; const sId = shadowIdOf$23(cardId); delete s.cardMap[sId]; const self = s.players[ownerId]; const idx = self.hand.indexOf(sId); if (idx >= 0) self.hand[idx] = cardId; }); return () => {}; } function onMount$250(skill, api) { api.defineAction("respond", { label: "双雄", style: "primary", prompt: { type: "confirm", title: "是否发动双雄?", confirmLabel: "发动", cancelLabel: "不发动" } }); api.defineAction("transform", { label: "双雄", style: "danger", prompt: { type: "useCardAndTarget", title: "选择一张与本回合判定牌颜色不同的手牌当决斗使用", cardFilter: { filter: () => true, min: 1, max: 1 }, targetFilter: { min: 1, max: 1 } }, transform: (card) => ({ name: "决斗", sourceCardId: card.id, fromSkill: skill.id }), activeWhen: (ctx) => { if (!defaultPlayActive(ctx)) return false; const p = ctx.view.players[ctx.perspectiveIdx]; if (!p?.hand) return false; const judgeColor = p.turnUsage?.[COLOR_KEY$1]; if (typeof judgeColor !== "string" || judgeColor === "") return false; return p.hand.some((c) => c.color !== judgeColor); } }); } var CONFIRM_RT$67, TRIGGERED_KEY$5, COLOR_KEY$1, 双雄_default; var init_双雄 = __esmMin((() => { init_apply(); init_frame(); init_judge(); init_skill(); init_skip_phase(); init_action_active(); init_vars_keys(); CONFIRM_RT$67 = "双雄/confirm"; TRIGGERED_KEY$5 = "双雄/triggered"; COLOR_KEY$1 = SHUANGXIONG_COLOR_VIEW_KEY; 双雄_default = { createSkill: createSkill$280, onInit: onInit$280, onMount: onMount$250 }; })); //#endregion //#region src/engine/skills/反间.ts var 反间_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$279, default: () => _skillModule$125, onInit: () => onInit$279, onMount: () => onMount$249 }); function createSkill$279(id, ownerId) { return { id, ownerId, name: "反间", description: "出牌阶段限一次,你可以令一名其他角色选择一种花色,令其获得并展示你的一张手牌,若此牌花色与其选择的花色不同,你对其造成1点伤害" }; } function onInit$279(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "use", (st, params) => { const self = st.players[ownerId]; if (!self) return "player not found"; if (!self.alive) return "你已死亡"; if (st.currentPlayerIndex !== ownerId) return "只能在你的回合使用"; if (st.phase !== "出牌") return "只能在出牌阶段使用"; if (hasBlockingPending(st)) return "当前有未完成的询问"; if (usedThisTurn$3(st, ownerId, "反间")) return "本回合已使用过反间"; if (self.hand.length === 0) return "需要有手牌才能发动反间"; const targets = params.targets; if (!Array.isArray(targets) || targets.length !== 1) return "需要指定一名目标"; const target = targets[0]; if (target === ownerId) return "不能对自己使用反间"; if (!st.players[target]?.alive) return "目标不合法"; return null; }, async (st, params) => { const from = ownerId; const target = params.targets[0]; await markOncePerTurn(st, from, "反间"); await pushFrame(st, "反间", from, { ...params }); delete st.localVars[SUIT_KEY]; await applyAtom(st, { type: "请求回应", requestType: SUIT_REQUEST, target, prompt: { type: "chooseSuit", title: "反间:选择一种花色(猜对则不受伤害,猜错受1点伤害)" }, defaultChoice: TIMEOUT_DEFAULT_SUIT, timeout: 30 }); let chosenSuit = st.localVars[SUIT_KEY]; if (!chosenSuit || !SUITS.includes(chosenSuit)) chosenSuit = TIMEOUT_DEFAULT_SUIT; const self = st.players[from]; if (!self || !self.alive || self.hand.length === 0) { await popFrame(st); return; } const rng = createRng(st.rngSeed); const idx = rng.nextInt(self.hand.length); st.rngSeed = rng.getState(); const cardId = self.hand[idx]; await applyAtom(st, { type: "移动牌", cardId, from: { zone: "手牌", player: from }, to: { zone: "手牌", player: target } }); const card = st.cardMap[cardId]; const targetPlayer = st.players[target]; if (card && card.suit !== chosenSuit && targetPlayer?.alive) await runDamageFlow(st, from, target, 1); await popFrame(st); }); const unloaders = []; for (const p of state.players) { const seatId = p.index; const u = registerAction(state, skill.id, seatId, "respond", (st, params) => { const slot = st.pendingSlots.get(seatId); if (!slot) return "当前不需要回应"; const atom = slot.atom; if (atom["type"] !== "请求回应") return "当前不需要回应"; if (atom["requestType"] !== SUIT_REQUEST) return "当前不是反间选花色"; const suit = params.suit; if (typeof suit !== "string" || !SUITS.includes(suit)) return "需要选择 ♠/♥/♣/♦ 中的一种花色"; return null; }, async (st, params) => { st.localVars[SUIT_KEY] = params.suit; }); unloaders.push(u); } return () => { unloaders.forEach((u) => u()); }; } function onMount$249(skill, api) { api.defineAction("use", { label: "反间", style: "danger", prompt: { type: "selectTarget", title: "反间:令一名其他角色猜花色", targetFilter: { min: 1, max: 1, filter: (_view, t) => t !== skill.ownerId } }, activeWhen: (ctx) => activeUnlessUsedThisTurn("反间")(ctx) && (ctx.view.players[ctx.perspectiveIdx]?.hand?.length ?? 0) > 0 }); api.defineAction("respond", { label: "反间", style: "danger", prompt: { type: "chooseSuit", title: "反间:选择一种花色(猜对则不受伤害,猜错受1点伤害)" } }); return () => {}; } var SUIT_REQUEST, SUIT_KEY, SUITS, TIMEOUT_DEFAULT_SUIT, _skillModule$125; var init_反间 = __esmMin((() => { init_apply(); init_frame(); init_damage(); init_rng(); init_once_per_turn(); init_skill(); SUIT_REQUEST = "反间/选花色"; SUIT_KEY = "反间/suit"; SUITS = [ "♠", "♥", "♣", "♦" ]; TIMEOUT_DEFAULT_SUIT = "♠"; _skillModule$125 = { createSkill: createSkill$279, onInit: onInit$279, onMount: onMount$249 }; })); //#endregion //#region src/engine/skills/反馈.ts var 反馈_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$278, onInit: () => onInit$278, onMount: () => onMount$248 }); function createSkill$278(id, ownerId) { return { id, ownerId, name: "反馈", description: "受到伤害后,你可以获得伤害来源的一张牌" }; } function onInit$278(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "respond", (state, params) => { const slot = state.pendingSlots.get(ownerId); if (!slot) return "当前不需要回应"; if (slot.atom.type !== "请求回应") return "当前不需要回应"; const requestType = slot.atom.requestType; if (requestType === "反馈/confirm") return null; if (requestType === "反馈/选牌") { const zone = params.zone; if (zone === "equipment") { if (typeof params.cardId !== "string") return "cardId required"; } else if (zone === "hand") { if (typeof params.handIndex !== "number") return "handIndex required"; } else return "zone required (equipment|hand)"; return null; } return "当前不是反馈回应"; }, async (state, params) => { const requestType = (state.pendingSlots.get(ownerId)?.atom)?.requestType; if (requestType === "反馈/confirm") state.localVars["反馈/confirmed"] = params.choice === true || params.confirmed === true; else if (requestType === "反馈/选牌") state.localVars[PICK_RESULT_KEY$3] = { zone: params.zone, cardId: params.cardId ?? null, handIndex: params.handIndex ?? null }; }); registerAfterHook(state, skill.id, ownerId, "受到伤害后", async (ctx) => { const atom = ctx.atom; if (atom.target !== ownerId) return; if ((atom.amount ?? 0) <= 0) return; if (atom.source === void 0 || atom.source === ownerId) return; const sourcePlayer = ctx.state.players[atom.source]; if (!sourcePlayer?.alive) return; if (!(sourcePlayer.hand.length > 0 || Object.keys(sourcePlayer.equipment).length > 0)) return; delete ctx.state.localVars["反馈/confirmed"]; await applyAtom(ctx.state, { type: "请求回应", requestType: "反馈/confirm", target: ownerId, prompt: { type: "confirm", title: "是否发动反馈?", confirmLabel: "发动", cancelLabel: "不发动" }, defaultChoice: false, timeout: 10 }); if (!ctx.state.localVars["反馈/confirmed"]) return; const source = ctx.state.players[atom.source]; if (!source) return; await runPickTargetCardPanel(ctx.state, ownerId, atom.source, source, { mode: "obtain", requestType: "反馈/选牌", title: "反馈:选择获得来源的一张牌", includeJudge: false }); }); return () => {}; } function onMount$248(_skill, api) { api.defineAction("respond", { label: "反馈", style: "default", prompt: { type: "confirm", title: "是否发动反馈?", confirmLabel: "发动", cancelLabel: "不发动" } }); } var init_反馈 = __esmMin((() => { init_apply(); init_skill(); init_pick_card_panel(); init_vars_keys(); })); //#endregion //#region src/engine/skills/古锭刀.ts var 古锭刀_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$277, default: () => 古锭刀_default, onInit: () => onInit$277 }); function createSkill$277(id, ownerId) { return { id, ownerId, name: "古锭刀", description: "锁定技,当你使用杀造成伤害时,若目标没有手牌,此伤害+1。", isLocked: true }; } function onInit$277(skill, state) { const ownerId = skill.ownerId; registerBeforeHook(state, skill.id, ownerId, "造成伤害时", async (ctx) => { const atom = ctx.atom; if (atom.source !== ownerId) return; const baseAmount = atom.amount ?? 0; if (baseAmount <= 0) return; const self = ctx.state.players[ownerId]; if (!self) return; const weaponId = self.equipment["武器"]; if (!weaponId) return; if (ctx.state.cardMap[weaponId]?.name !== "古锭刀") return; const damageCardId = atom.cardId; if (!damageCardId) return; if (ctx.state.cardMap[damageCardId]?.name !== "杀") return; const targetIdx = atom.target; if (typeof targetIdx !== "number") return; const target = ctx.state.players[targetIdx]; if (!target) return; if (target.hand.length > 0) return; return { kind: "modify", atom: { ...ctx.atom, amount: baseAmount + 1 } }; }); return () => {}; } var 古锭刀_default; var init_古锭刀 = __esmMin((() => { init_skill(); 古锭刀_default = { createSkill: createSkill$277, onInit: onInit$277 }; })); //#endregion //#region src/engine/skills/咆哮.ts var 咆哮_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$276, onInit: () => onInit$276 }); function createSkill$276(id, ownerId) { return { id, ownerId, name: "咆哮", description: "锁定技:出牌阶段使用【杀】无次数限制", isLocked: true }; } function onInit$276(skill, state) { const ownerId = skill.ownerId; const unregMax = registerSlashUnlimitedProvider(state, ownerId, () => true); const unregHook = registerAfterHook(state, skill.id, ownerId, "阶段开始", async (ctx) => { const atom = ctx.atom; if (atom.type !== "阶段开始") return; if (atom.phase !== "出牌") return; if (atom.player !== ownerId) return; await applyAtom(ctx.state, { type: "回合用量", player: ownerId, key: slashUnlimitedKey("咆哮"), value: true }); }); return () => { unregMax(); unregHook(); }; } var init_咆哮 = __esmMin((() => { init_apply(); init_skill(); init_slash_quota(); init_vars_keys(); })); //#endregion //#region src/engine/skills/回合管理.ts var 回合管理_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$275, onInit: () => onInit$275 }); function nextPhase(current) { const idx = PHASE_ORDER.indexOf(current); if (idx < 0 || idx >= PHASE_ORDER.length - 1) return null; return PHASE_ORDER[idx + 1]; } /** 从 fromIndex 之后找第一个存活玩家的索引;全死亡时返回 fromIndex */ function findNextAlive$2(state, fromIndex) { const n = state.players.length; for (let i = 1; i <= n; i++) { const idx = (fromIndex + i) % n; if (state.players[idx].alive) return idx; } return fromIndex; } /** 启动 player 的回合:回合开始 → 阶段开始/结束(准备),触发阶段推进链。 * 翻面检查集中在此(对齐 rules/flow/game.md「回合开始前」:武将牌背面朝上则翻面后 * 跳过此回合,直接进入下回合的「回合开始前」): * 若 player 背面朝上,先翻回正面(发「翻面后」时机),不 fire 回合开始/回合结束 * atom,直接推进到下家并重复检查——故翻面角色整回合被跳过,不触发 * 「回合开始时 / 回合结束时 / 回合结束后」技能。 * 连续跳过所有背面朝上的玩家,直到遇到正面朝上的玩家;存活玩家数兜底保证终止。 */ async function beginTurn(state, player) { const aliveCount = state.players.filter((p) => p.alive).length; let cur = player; let guard = 0; while (isFaceDown(state, cur) && guard < aliveCount) { await flipFaceUpAll(state, cur); await applyAtom(state, { type: "下一玩家" }); cur = state.currentPlayerIndex; guard++; } await applyAtom(state, { type: "回合开始", player: cur }); await applyAtom(state, { type: "阶段开始", player: cur, phase: "准备" }); await applyAtom(state, { type: "阶段结束", player: cur, phase: "准备" }); } function createSkill$275(id, ownerId) { return { id, ownerId, name: "回合管理", description: "监听上家回合结束,自动开始自己的回合" }; } function onInit$275(skill, state) { const ownerId = skill.ownerId; const me = skill.ownerId; registerAfterHook(state, skill.id, ownerId, "阶段结束", async (ctx) => { if (ctx.atom.type !== "阶段结束") return; const { player, phase } = ctx.atom; if (player !== me) return; const next = nextPhase(phase); if (!next) return; if (!await applyAtom(ctx.state, { type: "阶段间", player, from: phase, to: next })) return; await applyAtom(ctx.state, { type: "阶段开始", player, phase: next }); if (ctx.state.phase !== next) return; if (next === "摸牌") await applyAtom(ctx.state, { type: "摸牌", player, count: 2 }); if (next === "弃牌") { const handCount = ctx.state.players[player].hand.length; const limit = handLimit(ctx.state, player); if (handCount > limit) { const excess = handCount - limit; await applyAtom(ctx.state, { type: "请求回应", requestType: "__弃牌", target: player, prompt: { type: "useCard", title: `弃牌阶段:需弃 ${excess} 张牌`, cardFilter: { filter: () => true, min: excess, max: excess } }, timeout: 30 }); } } if (next === "出牌") { (async () => { while (ctx.state.phase === "出牌" && ctx.state.currentPlayerIndex === player) await applyAtom(ctx.state, { type: "出牌窗口", player, timeout: 50 }); })(); return; } if (next === "回合结束") { await applyAtom(ctx.state, { type: "清过期标记", player }); await applyAtom(ctx.state, { type: "下一玩家" }); if (await applyAtom(ctx.state, { type: "回合结束", player })) await applyAtom(ctx.state, { type: "回合结束后", player }); return; } if (next === "准备" || next === "判定" || next === "摸牌" || next === "弃牌") await applyAtom(ctx.state, { type: "阶段结束", player, phase: next }); }); registerAfterHook(state, skill.id, ownerId, "回合结束后", async (ctx) => { if (ctx.atom.type !== "回合结束后") return; const finishedIndex = ctx.atom.player; const state = ctx.state; if (findNextAlive$2(state, finishedIndex) !== me) return; await beginTurn(ctx.state, me); }); registerAction(state, skill.id, ownerId, "end", (state, _params) => { const myTurn = state.currentPlayerIndex === ownerId; const inActPhase = state.phase === "出牌" || state.phase === "弃牌"; const free = !hasBlockingPending(state); if (myTurn && inActPhase && free) return null; return "现在不能结束回合"; }, async (state, _params) => { await applyAtom(state, { type: "阶段结束", player: ownerId, phase: state.phase }); }); registerAction(state, skill.id, ownerId, "start", (state, _params) => { const isLordSeat = ownerId === 0 && state.currentPlayerIndex === 0; const atInitial = state.phase === "准备" && state.pendingSlots.size === 0; if (isLordSeat && atInitial) return null; return "现在不能开局"; }, async (state, _params) => { const player = ownerId; await applyAtom(state, { type: "回合开始", player }); await applyAtom(state, { type: "阶段开始", player, phase: "准备" }); await applyAtom(state, { type: "阶段结束", player, phase: "准备" }); }); return () => {}; } var PHASE_ORDER; var init_回合管理 = __esmMin((() => { init_apply(); init_skill(); init_hand_limit(); init_face_down(); PHASE_ORDER = [ "准备", "判定", "摸牌", "出牌", "弃牌", "回合结束" ]; })); //#endregion //#region src/engine/skills/固政.ts var 固政_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$274, default: () => _skillModule$124, onInit: () => onInit$274, onMount: () => onMount$247 }); /** localVars key 前缀:固政/弃牌/ = 该玩家本弃牌阶段弃置的 cardIds */ function discardKey$1(player) { return `固政/弃牌/${player}`; } function createSkill$274(id, ownerId) { return { id, ownerId, name: "固政", description: "其他角色弃牌阶段结束时:将其一张弃牌返回其手牌,然后获得其余弃牌" }; } function onInit$274(skill, state) { const ownerId = skill.ownerId; registerAfterHook(state, skill.id, ownerId, "弃置", async (ctx) => { if (ctx.state.phase !== "弃牌") return; const atom = ctx.atom; const key = discardKey$1(atom.player); const existing = ctx.state.localVars[key]; ctx.state.localVars[key] = [...existing ?? [], ...atom.cardIds]; }); registerBeforeHook(state, skill.id, ownerId, "阶段结束", async (ctx) => { const atom = ctx.atom; if (atom.phase !== "弃牌") return; const player = atom.player; if (player === ownerId) return; const st = ctx.state; const cardIds = st.localVars[discardKey$1(player)]; delete st.localVars[discardKey$1(player)]; if (!cardIds || cardIds.length === 0) return; if (!st.players[ownerId]?.alive) return; await pushFrame(st, "固政", ownerId, { target: player, cardIds: [...cardIds] }); delete st.localVars[CONFIRM_KEY$25]; await applyAtom(st, { type: "请求回应", requestType: CONFIRM_REQUEST$16, target: ownerId, prompt: { type: "confirm", title: `是否发动固政?(${st.players[player]?.name ?? ""} 弃置了 ${cardIds.length} 张牌)`, confirmLabel: "发动", cancelLabel: "不发动" }, defaultChoice: false, timeout: 30 }); if (!(st.localVars[CONFIRM_KEY$25] === true)) { await popFrame(st); return; } const available = cardIds.filter((id) => st.zones.discardPile.includes(id)); let chosen; if (available.length <= 1) { if (available.length === 0) { await popFrame(st); return; } chosen = available[0]; } else { delete st.localVars[PICK_KEY$9]; await applyAtom(st, { type: "请求回应", requestType: PICK_REQUEST$5, target: ownerId, prompt: { type: "pickProcessingCard", title: "固政:选择一张牌返回其手牌(其余你将获得)", cards: available.map((id) => { const c = st.cardMap[id]; return { cardId: id, cardName: c?.name ?? "?", suit: c?.suit ?? "", rank: c?.rank ?? "" }; }) }, defaultChoice: available[0], timeout: 30 }); const picked = st.localVars[PICK_KEY$9]; chosen = picked && available.includes(picked) ? picked : available[0]; } const rest = available.filter((id) => id !== chosen); await applyAtom(st, { type: "移动牌", cardId: chosen, from: { zone: "弃牌堆" }, to: { zone: "手牌", player } }); for (const id of rest) await applyAtom(st, { type: "移动牌", cardId: id, from: { zone: "弃牌堆" }, to: { zone: "手牌", player: ownerId } }); await popFrame(st); }); registerAction(state, skill.id, ownerId, "respond", (st, params) => { const slot = st.pendingSlots.get(ownerId); if (!slot) return "当前不需要回应"; if (slot.atom.type !== "请求回应") return "当前不是固政窗口"; const atom = slot.atom; if (atom.requestType === CONFIRM_REQUEST$16) return null; if (atom.requestType === PICK_REQUEST$5) { if (typeof params.cardId !== "string") return "需要选择一张牌"; return null; } return "当前不是固政窗口"; }, async (st, params) => { const atom = st.pendingSlots.get(ownerId).atom; if (atom.requestType === CONFIRM_REQUEST$16) st.localVars[CONFIRM_KEY$25] = params.choice === true || params.confirmed === true; else if (atom.requestType === PICK_REQUEST$5) st.localVars[PICK_KEY$9] = params.cardId; }); return () => {}; } function onMount$247(_skill, _api) { return () => {}; } var CONFIRM_REQUEST$16, PICK_REQUEST$5, CONFIRM_KEY$25, PICK_KEY$9, _skillModule$124; var init_固政 = __esmMin((() => { init_apply(); init_frame(); init_skill(); CONFIRM_REQUEST$16 = "固政/确认"; PICK_REQUEST$5 = "固政/选牌"; CONFIRM_KEY$25 = "固政/确认结果"; PICK_KEY$9 = "固政/所选牌"; _skillModule$124 = { createSkill: createSkill$274, onInit: onInit$274, onMount: onMount$247 }; })); //#endregion //#region src/engine/skills/国色.ts var 国色_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$273, default: () => 国色_default, onInit: () => onInit$273, onMount: () => onMount$246 }); /** 国色可用牌:方块(♦)牌(手牌或装备区) */ function isGuoseCard$1(card) { return !!card && card.suit === "♦"; } function createSkill$273(id, ownerId) { return { id, ownerId, name: "国色", description: "你可以将一张方块牌当【乐不思蜀】使用" }; } function onInit$273(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "use", (state, params) => { const myTurn = state.currentPlayerIndex === ownerId; const inActPhase = state.phase === "出牌"; const free = !hasBlockingPending(state); const self = state.players[ownerId]; if (!self?.alive) return "玩家不存在或已死亡"; if (typeof params.cardId !== "string") return "需要选择一张牌"; const cardId = params.cardId; const cardInHand = self.hand.includes(cardId); const cardInEquip = Object.values(self.equipment).some((id) => id === cardId); if (!cardInHand && !cardInEquip) return "牌不在手牌或装备区"; if (!isGuoseCard$1(state.cardMap[cardId])) return "需要一张方块牌"; const target = params.target ?? params.targets?.[0]; if (typeof target !== "number") return "需要选择目标"; if (target === ownerId) return "不能对自己使用"; const targetPlayer = state.players[target]; if (!targetPlayer?.alive) return "目标不存在或已死亡"; if (targetPlayer.pendingTricks.some((t) => t.name === TRICK_NAME$3)) return "目标判定区已有乐不思蜀"; return myTurn && inActPhase && free ? null : "国色使用条件不满足"; }, async (state, params) => { const from = ownerId; const cardId = params.cardId; const target = params.target ?? params.targets?.[0]; await pushFrame(state, "国色", from, { ...params }); const equipSlotEntry = Object.entries(state.players[from].equipment).find(([, id]) => id === cardId); if (equipSlotEntry) await applyAtom(state, { type: "卸下", player: from, slot: equipSlotEntry[0] }); await applyAtom(state, { type: "移动牌", cardId, from: { zone: "手牌", player: from }, to: { zone: "处理区" } }); await applyAtom(state, { type: "添加延时锦囊", player: target, trick: { name: TRICK_NAME$3, source: from, card: state.cardMap[cardId] ?? { id: cardId, name: TRICK_NAME$3, suit: "♦", color: "红", rank: "A", type: "锦囊牌" } } }); await applyAtom(state, { type: "移动牌", cardId, from: { zone: "处理区" }, to: { zone: "弃牌堆" } }); await popFrame(state); }); return () => {}; } function onMount$246(skill, api) { api.defineAction("use", { label: "国色", style: "danger", prompt: { type: "useCardAndTarget", title: "国色:将一张方块牌当乐不思蜀使用", cardFilter: { filter: (c) => isGuoseCard$1(c), min: 1, max: 1 }, targetFilter: { min: 1, max: 1, filter: (view, t) => { if (t === view.currentPlayerIndex) return false; const tp = view.players[t]; if (!tp) return false; if (tp.alive === false) return false; return true; } } }, activeWhen: (ctx) => { if (!defaultPlayActive(ctx)) return false; const p = ctx.view.players[ctx.perspectiveIdx]; if (!p) return false; if (p.hand?.some((c) => isGuoseCard$1(c)) ?? false) return true; return Object.values(p.equipment ?? {}).some((id) => { return isGuoseCard$1(id ? ctx.view.cardMap[id] : void 0); }); } }); } var TRICK_NAME$3, 国色_default; var init_国色 = __esmMin((() => { init_apply(); init_frame(); init_action_active(); init_skill(); TRICK_NAME$3 = "乐不思蜀"; 国色_default = { createSkill: createSkill$273, onInit: onInit$273, onMount: onMount$246 }; })); //#endregion //#region src/engine/flows/rank.ts /** 拼点牌点数:A=1, 2-10=面值, J=11, Q=12, K=13(与各拼点技的 rankValue 一致)。 * card 为 undefined 时返回 0(编排函数前置保证牌存在,此处仅防御)。 */ function getCardValue(card) { if (!card) return 0; const rank = card.rank; if (rank === "A") return 1; if (rank === "J") return 11; if (rank === "Q") return 12; if (rank === "K") return 13; const n = parseInt(rank, 10); return Number.isFinite(n) ? n : 0; } /** 拼点两步化编排函数——对齐 rankcompare.md 四步。 * * 时机1 拼点扣置:双方同时将一张手牌扣置入处理区(面朝下)。 * apply 把两张牌从手牌移入处理区;toViewEvents 对非扣置者隐藏牌面。 * 时机2 拼点亮出:同时亮出(面朝上),向全员公开牌面。 * 确定结果:initiator 点数严格大于 target = '赢';否则(≤)= '没赢'。 * 牌入弃牌堆:两张拼点牌 处理区→弃牌堆(在 拼点后 之前——钩子读 discardPile 取牌)。 * 时机3 拼点后:纯标记,after-hook 触发拼点后效果(酣战获杀/纵适获牌)。 * * 调用方约定:initiatorCard 须在 initiator 手牌中、targetCard 须在 target 手牌中。 * 返回拼点结果('赢'/'没赢'),供调用方分支结算。 */ async function runRankCompareFlow(state, initiator, target, initiatorCard, targetCard) { await applyAtom(state, { type: "拼点扣置", initiator, target, initiatorCard, targetCard }); await applyAtom(state, { type: "拼点亮出", initiator, target, initiatorCard, targetCard }); const result = getCardValue(state.cardMap[initiatorCard]) > getCardValue(state.cardMap[targetCard]) ? "赢" : "没赢"; await applyAtom(state, { type: "移动牌", cardId: initiatorCard, from: { zone: "处理区" }, to: { zone: "弃牌堆" } }); await applyAtom(state, { type: "移动牌", cardId: targetCard, from: { zone: "处理区" }, to: { zone: "弃牌堆" } }); await applyAtom(state, { type: "拼点后", initiator, target, initiatorCard, targetCard, result }); return result; } var init_rank = __esmMin((() => { init_apply(); })); //#endregion //#region src/engine/skills/天义.ts var 天义_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$272, default: () => 天义_default, onInit: () => onInit$272, onMount: () => onMount$245 }); /** 拼点牌点数:A=1, 2-10=面值, J=11, Q=12, K=13 */ function rankValue$9(rank) { if (rank === "A") return 1; if (rank === "J") return 11; if (rank === "Q") return 12; if (rank === "K") return 13; const n = parseInt(rank, 10); return Number.isFinite(n) ? n : 0; } function createSkill$272(id, ownerId) { return { id, ownerId, name: "天义", description: "出牌阶段限一次,与一名角色拼点:赢则本回合攻击范围无限、可额外使用一张杀、杀可额外指定一个目标;没赢则本回合不能使用杀" }; } function onInit$272(skill, state) { const ownerId = skill.ownerId; const unloadProvider = registerSlashExtraProvider(state, ownerId, (st, player) => st.turn.vars[WIN_VAR$1] === player ? 1 : 0); const unloadBlocker = registerSlashBlocker(state, ownerId, (st, player) => st.turn.vars[LOST_VAR$2] === player); const unloadRangeExemptor = registerAttackRangeExemptor(state, ownerId, (st, from, _to, _cardId) => st.turn.vars[WIN_VAR$1] === from); const unloadTargetProvider = registerSlashTargetProvider(state, ownerId, (st, player) => st.turn.vars[WIN_VAR$1] === player ? 2 : 0); registerAction(state, skill.id, ownerId, "use", (st, params) => { if (st.currentPlayerIndex !== ownerId) return "不是你的回合"; if (st.phase !== "出牌") return "只能在出牌阶段发动"; if (usedThisTurn$3(st, ownerId, "天义")) return "本回合已使用过天义"; const self = st.players[ownerId]; if (!self?.alive) return "玩家不存在或已死亡"; const cardId = params.cardId; if (typeof cardId !== "string") return "需要选择一张拼点牌"; if (!self.hand.includes(cardId)) return "拼点牌不在手牌中"; const target = params.target; if (typeof target !== "number") return "需要选择拼点目标"; if (target === ownerId) return "不能与自己拼点"; const targetPlayer = st.players[target]; if (!targetPlayer?.alive) return "目标不存在或已死亡"; if (targetPlayer.hand.length === 0) return "目标没有手牌,无法拼点"; return null; }, async (st, params) => { const from = ownerId; const initiatorCardId = params.cardId; const target = params.target; await markOncePerTurn(st, from, "天义"); await pushFrame(st, "天义", from, { ...params }); const initiatorCard = st.cardMap[initiatorCardId]; const initiatorValue = initiatorCard ? rankValue$9(initiatorCard.rank) : 0; delete st.localVars[TARGET_CARD_KEY$3]; await applyAtom(st, { type: "请求回应", requestType: PD_RT$2, target, prompt: { type: "useCard", title: `天义:与 ${st.players[from].name} 拼点,请出一张手牌`, cardFilter: { filter: () => true, min: 1, max: 1 } }, timeout: 30 }); const targetCardId = st.localVars[TARGET_CARD_KEY$3]; delete st.localVars[TARGET_CARD_KEY$3]; let win; if (targetCardId && st.players[target].hand.includes(targetCardId)) win = await runRankCompareFlow(st, from, target, initiatorCardId, targetCardId) === "赢"; else { await applyAtom(st, { type: "移动牌", cardId: initiatorCardId, from: { zone: "手牌", player: from }, to: { zone: "弃牌堆" } }); win = initiatorValue > 0; } if (win) { st.turn.vars[WIN_VAR$1] = from; await applyAtom(st, { type: "回合用量", player: from, key: WIN_VAR$1, value: from }); await applyAtom(st, { type: "回合用量", player: from, key: slashExtraKey("天义"), value: 1 }); await applyAtom(st, { type: "回合用量", player: from, key: slashTargetKey("天义"), value: 1 }); } else { st.turn.vars[LOST_VAR$2] = from; await applyAtom(st, { type: "回合用量", player: from, key: slashBlockedKey("天义"), value: true }); } await popFrame(st); }); for (const p of state.players) { const pid = p.index; registerAction(state, skill.id, pid, "respond", (st, params) => { const slot = st.pendingSlots.get(pid); if (!slot) return "当前不需要回应"; const atom = slot.atom; if (atom.type !== "请求回应") return "当前不需要回应"; if (atom.requestType !== PD_RT$2) return "当前不是天义回应"; if (atom.target !== pid) return "不是问你的"; const cardId = params.cardId; if (typeof cardId !== "string") return "请选择一张拼点牌"; if (!st.players[pid].hand.includes(cardId)) return "拼点牌不在手牌中"; return null; }, async (st, params) => { const slot = st.pendingSlots.get(pid); if (!slot) return; const atom = slot.atom; if (atom.type !== "请求回应" || atom.requestType !== PD_RT$2) return; st.localVars[TARGET_CARD_KEY$3] = params.cardId; }); } return () => { unloadProvider(); unloadBlocker(); unloadRangeExemptor(); unloadTargetProvider(); }; } function onMount$245(skill, api) { api.defineAction("use", { label: "天义", style: "primary", prompt: { type: "useCardAndTarget", title: "天义:选择一张拼点牌和一名目标", cardFilter: { filter: () => true, min: 1, max: 1 }, targetFilter: { min: 1, max: 1, filter: (view, t) => { if (t === view.currentPlayerIndex) return false; const tp = view.players[t]; if (!tp) return false; return tp.alive !== false && (tp.handCount ?? 0) > 0; } } }, activeWhen: (ctx) => activeUnlessUsedThisTurn("天义")(ctx) && (ctx.view.players[ctx.perspectiveIdx]?.hand?.length ?? 0) > 0 }); api.defineAction("respond", { label: "天义", style: "primary", prompt: { type: "useCard", title: "天义:请出一张拼点牌", cardFilter: { filter: () => true, min: 1, max: 1 } } }); } var TARGET_CARD_KEY$3, PD_RT$2, WIN_VAR$1, LOST_VAR$2, 天义_default; var init_天义 = __esmMin((() => { init_apply(); init_frame(); init_rank(); init_once_per_turn(); init_skill(); init_slash_quota(); init_slash_target(); init_distance(); init_vars_keys(); TARGET_CARD_KEY$3 = "天义/targetCard"; PD_RT$2 = "天义/拼点"; WIN_VAR$1 = TIANYI_WIN_VIEW_KEY; LOST_VAR$2 = "天义/lost"; 天义_default = { createSkill: createSkill$272, onInit: onInit$272, onMount: onMount$245 }; })); //#endregion //#region src/engine/skills/天妒.ts var 天妒_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$271, onInit: () => onInit$271, onMount: () => onMount$244 }); function createSkill$271(id, ownerId) { return { id, ownerId, name: "天妒", description: "当你的判定牌生效后,你可以获得此判定牌" }; } function onInit$271(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "respond", (s) => { const slot = s.pendingSlots.get(ownerId); if (slot?.atom.type !== "请求回应") return "当前不需要回应"; if (slot.atom.requestType !== CHOOSE_RT$22) return "当前不是天妒选择"; return null; }, async (s, params) => { s.localVars[CHOICE_KEY$19] = params.choice === true; }); registerAfterHook(state, skill.id, ownerId, "判定牌生效后", async (ctx) => { if (ctx.atom.player !== ownerId) return; const processing = frameCards(ctx.state); if (processing.length === 0) return; if (processing.length > 1) return; const judgeCardId = processing[processing.length - 1]; if (!ctx.state.cardMap[judgeCardId]) return; delete ctx.state.localVars[CHOICE_KEY$19]; await applyAtom(ctx.state, { type: "请求回应", requestType: CHOOSE_RT$22, target: ownerId, prompt: { type: "confirm", title: "天妒:获得判定牌?", confirmLabel: "获得", cancelLabel: "不获得" }, defaultChoice: false, timeout: 10 }); const want = ctx.state.localVars[CHOICE_KEY$19] === true; delete ctx.state.localVars[CHOICE_KEY$19]; if (!want) return; await applyAtom(ctx.state, { type: "移动牌", cardId: judgeCardId, from: { zone: "处理区" }, to: { zone: "手牌", player: ownerId } }); }); return () => {}; } function onMount$244(_skill, api) { api.defineAction("respond", { label: "天妒", style: "default", prompt: { type: "confirm", title: "是否发动天妒?", confirmLabel: "获得判定牌", cancelLabel: "不获得" } }); } var CHOOSE_RT$22, CHOICE_KEY$19; var init_天妒 = __esmMin((() => { init_apply(); init_frame(); init_skill(); CHOOSE_RT$22 = "天妒/choose"; CHOICE_KEY$19 = "天妒/choice"; })); //#endregion //#region src/engine/skills/天香.ts var 天香_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$270, default: () => 天香_default, onInit: () => onInit$270, onMount: () => onMount$243 }); /** 判定一张手牌对天香是否合法:红桃;若拥有红颜,黑桃也视为红桃。 */ function isTianxiangCard$1(state, ownerId, cardId) { const card = state.cardMap[cardId]; if (!card) return false; if (card.suit === "♥") return true; if (card.suit === "♠" && state.players[ownerId]?.skills.includes("红颜")) return true; return false; } function currentRequestType$15(state, ownerId) { const slot = state.pendingSlots.get(ownerId); if (!slot) return void 0; return slot.atom.requestType; } function createSkill$270(id, ownerId) { return { id, ownerId, name: "天香", description: "受到伤害时,弃一张红桃手牌防止之,令一名其他角色二选一:①受来源1伤害并摸X(X为已损失体力至多5);②失去1体力并获得弃置的牌" }; } function onInit$270(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "respond", (st, params) => { const rt = currentRequestType$15(st, ownerId); if (rt !== CONFIRM_RT$66 && rt !== CHOOSE_RT$21 && rt !== OPTION_RT$7) return "当前不是天香询问"; if (rt === CONFIRM_RT$66 || rt === OPTION_RT$7) return null; const cardId = params.cardId; const target = params.target; if (typeof cardId !== "string") return "请选择一张红桃手牌"; if (!st.players[ownerId]?.hand.includes(cardId)) return "牌不在手牌中"; if (!isTianxiangCard$1(st, ownerId, cardId)) return "必须选择红桃手牌"; if (typeof target !== "number") return "请选择转移目标"; if (target === ownerId) return "不能转移给自己"; if (!st.players[target]?.alive) return "目标无效"; return null; }, async (st, params) => { const rt = currentRequestType$15(st, ownerId); const confirmed = params.choice === true || params.confirmed === true; if (rt === CONFIRM_RT$66) st.localVars[CONFIRMED_KEY$53] = confirmed; else if (rt === CHOOSE_RT$21) { st.localVars[CARD_KEY$5] = params.cardId ?? null; st.localVars[TARGET_KEY$36] = params.target ?? null; } else if (rt === OPTION_RT$7) st.localVars[OPTION_KEY$7] = confirmed ? "damage" : "lose"; }); registerBeforeHook(state, skill.id, ownerId, "受到伤害时", async (ctx) => { const atom = ctx.atom; if (atom.target !== ownerId) return; const amount = atom.amount ?? 0; if (amount <= 0) return; const self = ctx.state.players[ownerId]; if (!self?.alive) return; if (self.hand.filter((id) => isTianxiangCard$1(ctx.state, ownerId, id)).length === 0) return; if (!ctx.state.players.some((p, i) => i !== ownerId && p.alive)) return; delete ctx.state.localVars[CONFIRMED_KEY$53]; await applyAtom(ctx.state, { type: "请求回应", requestType: CONFIRM_RT$66, target: ownerId, prompt: { type: "confirm", title: "是否发动天香?", confirmLabel: "发动", cancelLabel: "不发动" }, defaultChoice: false, timeout: 10 }); if (!ctx.state.localVars[CONFIRMED_KEY$53]) return; delete ctx.state.localVars[CARD_KEY$5]; delete ctx.state.localVars[TARGET_KEY$36]; const hasHongyan = self.skills.includes("红颜"); await applyAtom(ctx.state, { type: "请求回应", requestType: CHOOSE_RT$21, target: ownerId, prompt: { type: "useCardAndTarget", title: "天香:弃一张红桃手牌,选择一名其他角色", cardFilter: { filter: (c) => c.suit === "♥" || hasHongyan && c.suit === "♠", min: 1, max: 1 }, targetFilter: { min: 1, max: 1, filter: (_view, t) => t !== ownerId && ctx.state.players[t]?.alive === true } }, timeout: 15 }); const cardId = ctx.state.localVars[CARD_KEY$5]; const newTarget = ctx.state.localVars[TARGET_KEY$36]; delete ctx.state.localVars[CONFIRMED_KEY$53]; delete ctx.state.localVars[CARD_KEY$5]; delete ctx.state.localVars[TARGET_KEY$36]; if (typeof cardId !== "string" || typeof newTarget !== "number") return; if (newTarget === ownerId) return; const targetPlayer = ctx.state.players[newTarget]; if (!targetPlayer?.alive) return; if (!self.hand.includes(cardId)) return; if (!isTianxiangCard$1(ctx.state, ownerId, cardId)) return; delete ctx.state.localVars[OPTION_KEY$7]; await applyAtom(ctx.state, { type: "请求回应", requestType: OPTION_RT$7, target: ownerId, prompt: { type: "confirm", title: `天香:令 ${targetPlayer.name} ①受来源1点伤害并摸X张牌?(确认=①,取消=②失去1体力并获得弃牌)`, confirmLabel: "①受伤害+摸牌", cancelLabel: "②失去体力+获牌" }, defaultChoice: false, timeout: 15 }); const chosenOption = ctx.state.localVars[OPTION_KEY$7]; delete ctx.state.localVars[OPTION_KEY$7]; const isDamage = chosenOption === "damage"; await applyAtom(ctx.state, { type: "弃置", player: ownerId, cardIds: [cardId], voluntary: true }); if (isDamage) { const source = atom.source ?? ownerId; await runDamageFlow(ctx.state, source, newTarget, amount, void 0, atom.damageType); if (targetPlayer.alive) { const lostHealth = targetPlayer.maxHealth - getHealthValue(targetPlayer); const x = Math.min(lostHealth, 5); if (x > 0) await applyAtom(ctx.state, { type: "摸牌", player: newTarget, count: x }); } } else { await applyAtom(ctx.state, { type: "失去体力", target: newTarget, amount: 1 }); if (targetPlayer.alive && ctx.state.zones.discardPile.includes(cardId)) await applyAtom(ctx.state, { type: "移动牌", cardId, from: { zone: "弃牌堆" }, to: { zone: "手牌", player: newTarget } }); } return { kind: "cancel" }; }); return () => {}; } function onMount$243(_skill, api) { api.defineAction("respond", { label: "天香", style: "primary", prompt: { type: "confirm", title: "是否发动天香?", confirmLabel: "发动", cancelLabel: "不发动" } }); } var CONFIRM_RT$66, CHOOSE_RT$21, OPTION_RT$7, CONFIRMED_KEY$53, CARD_KEY$5, TARGET_KEY$36, OPTION_KEY$7, 天香_default; var init_天香 = __esmMin((() => { init_types(); init_apply(); init_damage(); init_skill(); CONFIRM_RT$66 = "天香/confirm"; CHOOSE_RT$21 = "天香/choose"; OPTION_RT$7 = "天香/option"; CONFIRMED_KEY$53 = "天香/confirmed"; CARD_KEY$5 = "天香/cardId"; TARGET_KEY$36 = "天香/target"; OPTION_KEY$7 = "天香/option"; 天香_default = { createSkill: createSkill$270, onInit: onInit$270, onMount: onMount$243 }; })); //#endregion //#region src/engine/skills/奇制.ts var 奇制_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$269, default: () => _skillModule$123, onInit: () => onInit$269, onMount: () => onMount$242 }); function createSkill$269(id, ownerId) { return { id, ownerId, name: SKILL_ID$58, description: "回合内使用非装备牌指定目标后,可弃置另一名角色一张牌并令其摸一张牌" }; } /** 玩家区域(手牌+装备)是否有牌可被弃 */ function hasDiscardableCards$2(player) { if (!player) return false; if (player.hand.length > 0) return true; return Object.values(player.equipment).some((id) => typeof id === "string"); } /** 是否存在可被弃牌的其他角色(存活、非自己、有牌) */ function hasValidTargets$2(state, ownerId) { return state.players.some((p) => p.alive && p.index !== ownerId && hasDiscardableCards$2(p)); } /** 当前 pending 的 requestType(类型安全读取) */ function currentRequestType$14(state, ownerId) { const slot = state.pendingSlots.get(ownerId); if (!slot) return void 0; return slot.atom.requestType; } function onInit$269(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "respond", (st, params) => { const rt = currentRequestType$14(st, ownerId); if (rt !== CONFIRM_RT$65 && rt !== TARGET_RT$22 && rt !== PICK_CARD_RT$3) return "当前不是奇制询问"; if (rt === CONFIRM_RT$65) return null; if (rt === TARGET_RT$22) { const t = params.targets?.[0] ?? (typeof params.target === "number" ? params.target : void 0); if (typeof t !== "number") return "请选择一名角色"; if (t === ownerId) return "不能选择自己"; const tp = st.players[t]; if (!tp?.alive) return "目标已死亡"; if (!hasDiscardableCards$2(tp)) return "目标无可弃置的牌"; return null; } const zone = params.zone; if (zone === "equipment") { if (typeof params.cardId !== "string") return "cardId required"; } else if (zone === "hand") { if (typeof params.handIndex !== "number") return "handIndex required"; } else return "zone required (equipment|hand)"; return null; }, async (st, params) => { const rt = currentRequestType$14(st, ownerId); if (rt === CONFIRM_RT$65) { st.localVars[CONFIRM_KEY$24] = params.choice === true || params.confirmed === true; return; } if (rt === TARGET_RT$22) { const t = params.targets?.[0] ?? (typeof params.target === "number" ? params.target : void 0); if (typeof t === "number") st.localVars[TARGET_KEY$35] = t; return; } if (rt === PICK_CARD_RT$3) { st.localVars[PICK_RESULT_KEY$3] = { zone: params.zone, cardId: params.cardId ?? null, handIndex: params.handIndex ?? null }; return; } }); registerAfterHook(state, skill.id, ownerId, "指定目标后", async (ctx) => { const atom = ctx.atom; if (atom.type !== "指定目标后") return; if (atom.source !== ownerId) return; if (ctx.state.currentPlayerIndex !== ownerId) return; if (!ctx.state.players[ownerId]?.alive) return; if ((atom.cardId !== void 0 ? ctx.state.cardMap[atom.cardId] : void 0)?.type === "装备牌") return; if (!hasValidTargets$2(ctx.state, ownerId)) return; delete ctx.state.localVars[CONFIRM_KEY$24]; await applyAtom(ctx.state, { type: "请求回应", requestType: CONFIRM_RT$65, target: ownerId, prompt: { type: "confirm", title: "是否发动奇制?(弃置另一名角色一张牌,然后令其摸一张牌)", confirmLabel: "发动", cancelLabel: "不发动" }, defaultChoice: false, timeout: 10 }); if (!ctx.state.localVars[CONFIRM_KEY$24]) { delete ctx.state.localVars[CONFIRM_KEY$24]; return; } delete ctx.state.localVars[CONFIRM_KEY$24]; if (!ctx.state.players[ownerId]?.alive) return; if (!hasValidTargets$2(ctx.state, ownerId)) return; delete ctx.state.localVars[TARGET_KEY$35]; await applyAtom(ctx.state, { type: "请求回应", requestType: TARGET_RT$22, target: ownerId, prompt: { type: "choosePlayer", title: "奇制:选择要弃置其一张牌的其他角色", min: 1, max: 1, filter: (_view, t) => t !== ownerId && ctx.state.players[t]?.alive === true && hasDiscardableCards$2(ctx.state.players[t]) }, timeout: 20 }); const targetIdx = ctx.state.localVars[TARGET_KEY$35]; delete ctx.state.localVars[TARGET_KEY$35]; if (typeof targetIdx !== "number") return; const target = ctx.state.players[targetIdx]; if (!target?.alive || !hasDiscardableCards$2(target)) return; await runPickTargetCardPanel(ctx.state, ownerId, targetIdx, target, { mode: "discard", requestType: PICK_CARD_RT$3, title: `奇制:选择要从 ${target.name} 弃置的 1 张牌`, includeJudge: false }); if (ctx.state.players[targetIdx]?.alive) await applyAtom(ctx.state, { type: "摸牌", player: targetIdx, count: 1 }); const prev = getQizhiCount(ctx.state.turn.vars) ?? 0; ctx.state.turn.vars[QIZHI_COUNT_KEY] = prev + 1; }); return () => {}; } function onMount$242(_skill, api) { api.defineAction("respond", { label: SKILL_ID$58, style: "default", prompt: { type: "confirm", title: "奇制", confirmLabel: "发动", cancelLabel: "不发动" } }); return () => {}; } var SKILL_ID$58, CONFIRM_RT$65, TARGET_RT$22, PICK_CARD_RT$3, CONFIRM_KEY$24, TARGET_KEY$35, _skillModule$123; var init_奇制 = __esmMin((() => { init_apply(); init_skill(); init_pick_card_panel(); init_vars_keys(); SKILL_ID$58 = "奇制"; CONFIRM_RT$65 = `${SKILL_ID$58}/confirm`; TARGET_RT$22 = `${SKILL_ID$58}/选目标`; PICK_CARD_RT$3 = `${SKILL_ID$58}/选牌`; CONFIRM_KEY$24 = `${SKILL_ID$58}/confirmed`; TARGET_KEY$35 = `${SKILL_ID$58}/target`; _skillModule$123 = { createSkill: createSkill$269, onInit: onInit$269, onMount: onMount$242 }; })); //#endregion //#region src/engine/skills/奇才.ts var 奇才_exports = /* @__PURE__ */ __exportAll({ QICAI_TAG: () => QICAI_TAG$1, createSkill: () => createSkill$268, onInit: () => onInit$268 }); function createSkill$268(id, ownerId) { return { id, ownerId, name: "奇才", description: "锁定技:你使用任何锦囊牌无距离限制", isLocked: true }; } function onInit$268(skill, state) { const ownerId = skill.ownerId; const player = state.players[ownerId]; if (player && !player.tags.includes("奇才/无距离限制")) player.tags.push(QICAI_TAG$1); return () => { const p = state.players[ownerId]; if (p) p.tags = p.tags.filter((t) => t !== QICAI_TAG$1); }; } var QICAI_TAG$1; var init_奇才 = __esmMin((() => { QICAI_TAG$1 = "奇才/无距离限制"; })); //#endregion //#region src/engine/skills/奇袭.ts var 奇袭_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$267, default: () => 奇袭_default, onInit: () => onInit$267, onMount: () => onMount$241 }); function createSkill$267(id, ownerId) { return { id, ownerId, name: "奇袭", description: "你可以将一张黑色牌当【过河拆桥】使用" }; } /** 影子卡 id:${原id}#奇袭 */ function shadowIdOf$22(cardId) { return `${cardId}#奇袭`; } function onInit$267(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "transform", (state, params) => { const myTurn = state.currentPlayerIndex === ownerId; const inActPhase = state.phase === "出牌"; const free = !hasBlockingPending(state); const self = state.players[ownerId]; const selfAlive = self.alive === true; const cardId = params.cardId; const cardIdOk = typeof cardId === "string"; const card = cardIdOk ? state.cardMap[cardId] : void 0; const isBlack = !!card && card.color === "黑"; const cardInHand = cardIdOk && self.hand.includes(cardId); const cardInEquip = cardIdOk && Object.values(self.equipment).some((id) => id === cardId); return myTurn && inActPhase && free && selfAlive && isBlack && (cardInHand || cardInEquip) ? null : "现在不能使用奇袭"; }, async (state, params) => { const cardId = params.cardId; const shadowId = shadowIdOf$22(cardId); const self = state.players[ownerId]; const equipSlotEntry = Object.entries(self.equipment).find(([, id]) => id === cardId); if (equipSlotEntry) { const slot = equipSlotEntry[0]; params["_origSlot"] = slot; await applyAtom(state, { type: "卸下", player: ownerId, slot }); } await applyAtom(state, { type: "当作", player: ownerId, cardIds: [cardId], shadowId, outputName: "过河拆桥" }); }, (state, params) => { const cardId = params.cardId; const sId = shadowIdOf$22(cardId); const self = state.players[ownerId]; const origSlot = params["_origSlot"]; delete state.cardMap[sId]; const idx = self.hand.indexOf(sId); if (idx >= 0) if (origSlot) { self.hand.splice(idx, 1); self.equipment[origSlot] = cardId; if (origSlot === "武器") { const card = state.cardMap[cardId]; self.vars[DISTANCE_ATTACK_RANGE_KEY] = card?.range ?? 1; } } else self.hand[idx] = cardId; }); return () => {}; } function onMount$241(skill, api) { api.defineAction("transform", { label: "奇袭", style: "passive", prompt: { type: "useCardAndTarget", title: "选择一张黑色牌当过河拆桥使用", cardFilter: { filter: (c) => c.color === "黑", min: 1, max: 1 }, targetFilter: { min: 1, max: 1 } }, transform: (card) => ({ name: "过河拆桥", sourceCardId: card.id, fromSkill: skill.id }), activeWhen: (ctx) => { if (!defaultPlayActive(ctx)) return false; const p = ctx.view.players[ctx.perspectiveIdx]; if (!p) return false; if (p.hand?.some((c) => c.color === "黑") ?? false) return true; return Object.values(p.equipment ?? {}).some((id) => { return (id ? ctx.view.cardMap[id] : void 0)?.color === "黑"; }); } }); } var 奇袭_default; var init_奇袭 = __esmMin((() => { init_skill(); init_apply(); init_action_active(); init_vars_keys(); 奇袭_default = { createSkill: createSkill$267, onInit: onInit$267, onMount: onMount$241 }; })); //#endregion //#region src/engine/skills/奇谋.ts var 奇谋_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$266, default: () => 奇谋_default, onInit: () => onInit$266, onMount: () => onMount$240 }); function createSkill$266(id, ownerId) { return { id, ownerId, name: "奇谋", description: "限定技,出牌阶段,你可以失去任意点体力并摸等量的牌,然后本回合你计算与其他角色的距离-X且使用【杀】的限制次数+X(X为失去的体力值)" }; } function onInit$266(skill, state) { const ownerId = skill.ownerId; const unloaders = []; unloaders.push(registerAction(state, skill.id, ownerId, "use", (st, _params) => { if (st.currentPlayerIndex !== ownerId) return "不是你的回合"; if (st.phase !== "出牌") return "只能在出牌阶段发动"; if (hasBlockingPending(st)) return "当前有未完成的询问"; if (!st.players[ownerId]?.alive) return "玩家不存在或已死亡"; if (st.players[ownerId].vars[USED_KEY$19]) return "奇谋已使用过(限定技)"; if (st.players[ownerId].health < 1) return "没有体力可失去"; return null; }, async (st, _params) => { const me = st.players[ownerId]; const hp = me.health; delete st.localVars[X_KEY$1]; await applyAtom(st, { type: "请求回应", requestType: X_REQUEST, target: ownerId, prompt: { type: "chooseOption", title: `奇谋:失去几点体力?(1-${hp})`, description: "失去 X 点体力并摸 X 张牌,然后本回合你计算与其他角色的距离-X且使用【杀】的限制次数+X", options: Array.from({ length: hp }, (_, i) => ({ value: String(i + 1), label: `失去 ${i + 1} 点体力` })) }, defaultChoice: "1", timeout: 30 }); const x = st.localVars[X_KEY$1]; delete st.localVars[X_KEY$1]; if (typeof x !== "number" || x < 1 || x > hp) return; me.vars[USED_KEY$19] = true; await pushFrame(st, "奇谋", ownerId, { x }); try { await applyAtom(st, { type: "失去体力", target: ownerId, amount: x }); if (!st.players[ownerId]?.alive) return; await applyAtom(st, { type: "摸牌", player: ownerId, count: x }); const newMod = (getDistanceAttackMod(me.vars) ?? 0) + x; me.vars[DISTANCE_ATTACK_MOD_KEY] = newMod; me.vars[BOOST_KEY] = x; await applyAtom(st, { type: "加标记", player: ownerId, mark: { id: BOOST_MARK_ID, scope: ownerId, payload: { x } }, distanceVars: { attackMod: newMod } }); st.turn.vars[EXTRA_KEY] = x; await applyAtom(st, { type: "回合用量", player: ownerId, key: slashExtraKey("奇谋"), value: x }); } finally { await popFrame(st); } })); unloaders.push(registerAction(state, skill.id, ownerId, "respond", (st, params) => { const slot = st.pendingSlots.get(ownerId); if (!slot) return "当前不需要回应"; const atom = slot.atom; if (atom.type !== "请求回应" || atom.requestType !== X_REQUEST) return "当前不是奇谋询问"; const hp = st.players[ownerId]?.health ?? 0; const n = Number(params.option); if (!Number.isInteger(n) || n < 1 || n > hp) return `请选择 1-${hp} 之间的整数`; return null; }, async (st, params) => { st.localVars[X_KEY$1] = Number(params.option); })); unloaders.push(registerSlashExtraProvider(state, ownerId, (st, player) => { if (player !== ownerId) return 0; const bonus = st.turn.vars[EXTRA_KEY]; return typeof bonus === "number" ? bonus : 0; })); unloaders.push(registerAfterHook(state, skill.id, ownerId, "回合结束", async (ctx) => { const atom = ctx.atom; if (atom.type !== "回合结束") return; if (atom.player !== ownerId) return; const me = ctx.state.players[ownerId]; if (!me) return; const x = me.vars[BOOST_KEY]; if (typeof x !== "number") return; const restored = (getDistanceAttackMod(me.vars) ?? 0) - x; if (restored > 0) me.vars[DISTANCE_ATTACK_MOD_KEY] = restored; else delete me.vars[DISTANCE_ATTACK_MOD_KEY]; delete me.vars[BOOST_KEY]; if (me.marks.some((m) => m.id === BOOST_MARK_ID)) await applyAtom(ctx.state, { type: "去标记", player: ownerId, markId: BOOST_MARK_ID, distanceVars: { attackMod: restored > 0 ? restored : void 0 } }); })); return () => { for (const u of unloaders) u(); const me = state.players[ownerId]; if (me) { const x = me.vars[BOOST_KEY]; if (typeof x === "number") { const restored = (getDistanceAttackMod(me.vars) ?? 0) - x; if (restored > 0) me.vars[DISTANCE_ATTACK_MOD_KEY] = restored; else delete me.vars[DISTANCE_ATTACK_MOD_KEY]; delete me.vars[BOOST_KEY]; } me.marks = me.marks.filter((m) => m.id !== BOOST_MARK_ID); } }; } function onMount$240(_skill, api) { api.defineAction("use", { label: "奇谋", style: "primary", prompt: { type: "confirm", title: "是否发动奇谋?(限定技)", description: "出牌阶段:失去任意点体力并摸等量的牌,本回合你计算与其他角色的距离-X且使用【杀】的限制次数+X", confirmLabel: "发动", cancelLabel: "不发动" }, activeWhen: (ctx) => defaultPlayActive(ctx) }); api.defineAction("respond", { label: "选择体力值", style: "default", prompt: { type: "confirm", title: "奇谋:选择失去的体力值" } }); return () => {}; } var USED_KEY$19, X_REQUEST, X_KEY$1, EXTRA_KEY, BOOST_KEY, BOOST_MARK_ID, 奇谋_default; var init_奇谋 = __esmMin((() => { init_apply(); init_frame(); init_skill(); init_slash_quota(); init_action_active(); init_vars_keys(); USED_KEY$19 = "奇谋/used"; X_REQUEST = "奇谋/选X"; X_KEY$1 = "奇谋/x"; EXTRA_KEY = "奇谋/extra"; BOOST_KEY = "奇谋/距离加成"; BOOST_MARK_ID = "奇谋/距离"; 奇谋_default = { createSkill: createSkill$266, onInit: onInit$266, onMount: onMount$240 }; })); //#endregion //#region src/engine/skills/奋威.ts var 奋威_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$265, default: () => _skillModule$122, onInit: () => onInit$265, onMount: () => onMount$239 }); /** * 运行奋威询问流程(确认 + 多选目标),返回被选中的无效目标集合。 * - 返回 null:不发动(确认选否)或空选 → 限定技未消耗 * - 返回 number[]:成功,限定技已标记 USED_KEY */ async function runFenweiPanel(st, ownerId, skillName, targets) { delete st.localVars[CONFIRMED_KEY$52]; await applyAtom(st, { type: "请求回应", requestType: CONFIRM_RT$64, target: ownerId, prompt: { type: "confirm", title: `是否发动奋威?(限定技:令${skillName}对任意个目标无效)`, confirmLabel: "发动", cancelLabel: "不发动" }, defaultChoice: false, timeout: 15 }); if (!st.localVars[CONFIRMED_KEY$52]) return null; delete st.localVars[CHOSEN_KEY$2]; await applyAtom(st, { type: "请求回应", requestType: CHOOSE_RT$20, target: ownerId, prompt: { type: "choosePlayer", title: "奋威:选择要令其无效的目标(可多选)", min: 1, max: targets.length, candidates: targets, filter: (_view, t) => targets.includes(t) }, timeout: 30 }); const chosen = st.localVars[CHOSEN_KEY$2]; delete st.localVars[CHOSEN_KEY$2]; if (!chosen || chosen.length === 0) return null; st.players[ownerId].vars[USED_KEY$18] = true; return chosen; } function createSkill$265(id, ownerId) { return { id, ownerId, name: "奋威", description: "限定技,当一张锦囊牌指定多个目标后,你可以令此牌对任意个目标无效" }; } function onInit$265(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "respond", (st, _params) => { const slot = st.pendingSlots.get(ownerId); if (!slot) return "当前不需要回应"; const atom = slot.atom; if (atom["type"] !== "请求回应") return "当前不需要回应"; const rt = atom["requestType"]; if (rt !== CONFIRM_RT$64 && rt !== CHOOSE_RT$20) return "当前不是奋威询问"; return null; }, async (st, params) => { const rt = (st.pendingSlots.get(ownerId)?.atom)?.requestType; if (rt === CONFIRM_RT$64) st.localVars[CONFIRMED_KEY$52] = params.choice === true || params.confirmed === true; else if (rt === CHOOSE_RT$20) { const targets = params.targets; if (Array.isArray(targets)) st.localVars[CHOSEN_KEY$2] = targets; } }); registerBeforeHook(state, skill.id, ownerId, "请求回应", async (ctx) => { const atom = ctx.atom; if (atom.requestType !== "无懈可击") return; const cancelTarget = atom.cancelTarget; if (typeof cancelTarget !== "number") return; const st = ctx.state; const frame = topFrame(st); if (!frame) return; const mode = MULTI_TARGET_SCROLLS[frame.skillId]; const isChain = frame.skillId === "铁索连环"; if (!mode && !isChain) return; const cardId = frame.params?.cardId; const invalidKey = cardId ? `${INVALID_PREFIX$1}${cardId}` : null; const invalidSet = invalidKey ? st.localVars[invalidKey] : void 0; if (mode && invalidSet?.includes(cancelTarget)) { frame.cancelled = true; return { kind: "cancel" }; } if (!cardId) return; if (st.localVars[`${PROCESSED_PREFIX$2}${cardId}`]) return; if (st.players[ownerId]?.vars[USED_KEY$18]) return; if (!st.players[ownerId]?.alive) return; const targets = frame.params?.targets ?? []; if (targets.length <= 1) return; st.localVars[`${PROCESSED_PREFIX$2}${cardId}`] = true; const chosen = await runFenweiPanel(st, ownerId, frame.skillId, targets); if (!chosen) return; st.localVars[`${INVALID_PREFIX$1}${cardId}`] = chosen; if (mode && chosen.includes(cancelTarget)) { frame.cancelled = true; return { kind: "cancel" }; } }); registerBeforeHook(state, skill.id, ownerId, "设横置", async (ctx) => { const st = ctx.state; const frame = topFrame(st); if (frame?.skillId !== "铁索连环") return; const atom = ctx.atom; if (typeof atom.player !== "number") return; const cardId = frame.params?.cardId; if (!cardId) return; if (!st.localVars[`${INVALID_PREFIX$1}${cardId}`]?.includes(atom.player)) return; return { kind: "cancel" }; }); return () => {}; } function onMount$239(_skill, api) { api.defineAction("respond", { label: "奋威", style: "danger", prompt: { type: "confirm", title: "奋威", confirmLabel: "确认", cancelLabel: "取消" } }); return () => {}; } var USED_KEY$18, CONFIRM_RT$64, CHOOSE_RT$20, CONFIRMED_KEY$52, CHOSEN_KEY$2, INVALID_PREFIX$1, PROCESSED_PREFIX$2, MULTI_TARGET_SCROLLS, _skillModule$122; var init_奋威 = __esmMin((() => { init_apply(); init_frame(); init_skill(); USED_KEY$18 = "奋威/used"; CONFIRM_RT$64 = "奋威/confirm"; CHOOSE_RT$20 = "奋威/choose"; CONFIRMED_KEY$52 = "奋威/confirmed"; CHOSEN_KEY$2 = "奋威/chosenTargets"; INVALID_PREFIX$1 = "奋威/无效/"; PROCESSED_PREFIX$2 = "奋威/已处理/"; MULTI_TARGET_SCROLLS = { 南蛮入侵: "allOthers", 万箭齐发: "allOthers", 桃园结义: "allAlive", 五谷丰登: "allAlive" }; _skillModule$122 = { createSkill: createSkill$265, onInit: onInit$265, onMount: onMount$239 }; })); //#endregion //#region src/engine/skills/奋激.ts var 奋激_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$264, default: () => _skillModule$121, onInit: () => onInit$264, onMount: () => onMount$238 }); function createSkill$264(id, ownerId) { return { id, ownerId, name: "奋激", description: "当一名角色的手牌被弃置或获得后,你可以失去1点体力令其摸两张牌" }; } function onInit$264(skill, state) { const ownerId = skill.ownerId; const unloadAction = registerAction(state, skill.id, ownerId, "respond", (st, _params) => { const slot = st.pendingSlots.get(ownerId); if (!slot) return "当前不需要回应"; const atom = slot.atom; if (atom["type"] !== "请求回应") return "当前不需要回应"; if (!atom["requestType"]?.startsWith(CONFIRM_RT_PREFIX)) return "当前不是奋激询问"; return null; }, async (st, params) => { const rt = (st.pendingSlots.get(ownerId)?.atom)?.requestType ?? ""; if (rt.startsWith(CONFIRM_RT_PREFIX)) st.localVars[rt] = params.choice === true || params.confirmed === true; }); async function tryFenji(ctx, target) { const st = ctx.state; if (!st.players[ownerId]?.alive) return; if (!st.players[target]?.alive) return; const rt = `${CONFIRM_RT_PREFIX}/${target}`; delete st.localVars[rt]; await applyAtom(st, { type: "请求回应", requestType: rt, target: ownerId, prompt: { type: "confirm", title: `是否发动奋激?(失去1点体力令 ${st.players[target]?.name ?? `P${target}`} 摸两张牌)`, confirmLabel: "发动", cancelLabel: "不发动" }, defaultChoice: false, timeout: 10 }); const confirmed = st.localVars[rt]; delete st.localVars[rt]; if (!confirmed) return; await applyAtom(st, { type: "失去体力", target: ownerId, amount: 1 }); if (!st.players[ownerId]?.alive) return; if (st.players[target]?.alive) await applyAtom(st, { type: "摸牌", player: target, count: 2 }); } const unloadDiscardBefore = registerBeforeHook(state, skill.id, ownerId, "弃置", async (ctx) => { const atom = ctx.atom; if (atom.type !== "弃置") return; if (typeof atom.player !== "number") return; const player = ctx.state.players[atom.player]; if (!player) return; ctx.state.localVars[DISCARD_HAND_KEY] = atom.cardIds.filter((id) => player.hand.includes(id)); }); const unloadDiscard = registerAfterHook(state, skill.id, ownerId, "弃置", async (ctx) => { const atom = ctx.atom; if (atom.type !== "弃置") return; if (typeof atom.player !== "number") return; if (atom.voluntary === true) { delete ctx.state.localVars[DISCARD_HAND_KEY]; return; } const handCards = ctx.state.localVars[DISCARD_HAND_KEY]; delete ctx.state.localVars[DISCARD_HAND_KEY]; if (!handCards || handCards.length === 0) return; await tryFenji(ctx, atom.player); }); const unloadObtainBefore = registerBeforeHook(state, skill.id, ownerId, "获得", async (ctx) => { const atom = ctx.atom; if (atom.type !== "获得") return; if (atom.from === void 0) return; const fromP = ctx.state.players[atom.from]; ctx.state.localVars[OBTAIN_HAND_KEY] = fromP?.hand.includes(atom.cardId) ?? false; }); const unloadObtain = registerAfterHook(state, skill.id, ownerId, "获得", async (ctx) => { const atom = ctx.atom; if (atom.type !== "获得") return; if (atom.from === void 0 || typeof atom.from !== "number") { delete ctx.state.localVars[OBTAIN_HAND_KEY]; return; } const fromHand = ctx.state.localVars[OBTAIN_HAND_KEY]; delete ctx.state.localVars[OBTAIN_HAND_KEY]; if (!fromHand) return; await tryFenji(ctx, atom.from); }); return () => { unloadAction(); unloadDiscardBefore(); unloadDiscard(); unloadObtainBefore(); unloadObtain(); }; } function onMount$238(_skill, _api) { return () => {}; } var CONFIRM_RT_PREFIX, DISCARD_HAND_KEY, OBTAIN_HAND_KEY, _skillModule$121; var init_奋激 = __esmMin((() => { init_apply(); init_skill(); CONFIRM_RT_PREFIX = "奋激/confirm"; DISCARD_HAND_KEY = "奋激/弃置手牌快照"; OBTAIN_HAND_KEY = "奋激/获得手牌快照"; _skillModule$121 = { createSkill: createSkill$264, onInit: onInit$264, onMount: onMount$238 }; })); //#endregion //#region src/engine/skills/奸雄.ts var 奸雄_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$263, onInit: () => onInit$263, onMount: () => onMount$237 }); function createSkill$263(id, ownerId) { return { id, ownerId, name: "奸雄", description: "当你受到伤害后,你可以获得造成此伤害的牌" }; } function onInit$263(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "respond", (s) => { const slot = s.pendingSlots.get(ownerId); if (slot?.atom.type !== "请求回应") return "当前不需要回应"; if (slot.atom.requestType !== CHOOSE_RT$19) return "当前不是奸雄选择"; return null; }, async (s, params) => { s.localVars[CHOICE_KEY$18] = params.choice === true; }); registerAfterHook(state, skill.id, ownerId, "受到伤害后", async (ctx) => { const atom = ctx.atom; if (atom.target !== ownerId) return; delete ctx.state.localVars[WANTCARD_KEY$1]; if ((atom.amount ?? 0) <= 0) return; const damageCardId = atom.cardId; if (typeof damageCardId !== "string" || !ctx.state.cardMap[damageCardId]) return; const effectiveId = ctx.state.cardMap[damageCardId].shadowOf ?? damageCardId; delete ctx.state.localVars[CHOICE_KEY$18]; await applyAtom(ctx.state, { type: "请求回应", requestType: CHOOSE_RT$19, target: ownerId, prompt: { type: "confirm", title: "奸雄:是否获得造成此伤害的牌?", confirmLabel: "获得", cancelLabel: "不发动" }, defaultChoice: false, timeout: 10 }); if (ctx.state.localVars[CHOICE_KEY$18] === true) ctx.state.localVars[WANTCARD_KEY$1] = effectiveId; delete ctx.state.localVars[CHOICE_KEY$18]; }); registerAfterHook(state, skill.id, ownerId, "移动牌", async (ctx) => { const wantCard = ctx.state.localVars[WANTCARD_KEY$1]; if (typeof wantCard !== "string") return; if (ctx.atom.to?.zone !== "弃牌堆") return; if (!ctx.state.zones.discardPile.includes(wantCard)) return; if (!ctx.state.cardMap[wantCard]) return; await applyAtom(ctx.state, { type: "移动牌", cardId: wantCard, from: { zone: "弃牌堆" }, to: { zone: "手牌", player: ownerId } }); }); return () => {}; } function onMount$237(_skill, api) { api.defineAction("respond", { label: "奸雄", style: "default", prompt: { type: "confirm", title: "是否发动奸雄?", confirmLabel: "发动", cancelLabel: "不发动" } }); } var CHOOSE_RT$19, WANTCARD_KEY$1, CHOICE_KEY$18; var init_奸雄 = __esmMin((() => { init_apply(); init_skill(); CHOOSE_RT$19 = "奸雄/choose"; WANTCARD_KEY$1 = "奸雄/wantCard"; CHOICE_KEY$18 = "奸雄/choice"; })); //#endregion //#region src/engine/skills/好施.ts var 好施_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$262, default: () => _skillModule$120, onInit: () => onInit$262, onMount: () => onMount$236 }); function createSkill$262(id, ownerId) { return { id, ownerId, name: "好施", description: "摸牌阶段,你可以额外摸两张牌,若此时你的手牌数超过五张,你必须将一半(向下取整)的手牌交给除你外手牌数最少的一名角色" }; } function onInit$262(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "respond", (st, _params) => { const slot = st.pendingSlots.get(ownerId); if (!slot) return "当前不需要回应"; const atom = slot.atom; if (atom["type"] !== "请求回应") return "当前不需要回应"; const rt = atom["requestType"]; if (rt !== CONFIRM_RT$63 && rt !== CHOOSE_TARGET_RT$6 && rt !== GIVE_RT$4) return "当前不是好施询问"; return null; }, async (st, params) => { const slot = st.pendingSlots.get(ownerId); const rt = (slot?.atom)?.requestType; if (rt === CONFIRM_RT$63) st.localVars[CONFIRMED_KEY$51] = params.choice === true || params.confirmed === true; else if (rt === CHOOSE_TARGET_RT$6) { const t = params.targets?.[0] ?? (typeof params.target === "number" ? params.target : void 0); if (typeof t === "number") st.localVars[TARGET_KEY$34] = t; } else if (rt === GIVE_RT$4) { const ids = params.cardIds; const need = (slot?.atom)?.prompt?.cardFilter?.min; if (Array.isArray(ids) && typeof need === "number" && ids.length === need && ids.every((id) => st.players[ownerId]?.hand.includes(id))) st.localVars[GIVE_KEY$3] = ids; } }); registerBeforeHook(state, skill.id, ownerId, "摸牌", async (ctx) => { const atom = ctx.atom; if (atom.player !== ownerId) return; if (ctx.state.currentPlayerIndex !== ownerId) return; if (ctx.state.phase !== "摸牌") return; if (!ctx.state.players[ownerId]?.alive) return; if (usedThisTurn$3(ctx.state, ownerId, "好施")) return; delete ctx.state.localVars[CONFIRMED_KEY$51]; await applyAtom(ctx.state, { type: "请求回应", requestType: CONFIRM_RT$63, target: ownerId, prompt: { type: "confirm", title: "是否发动好施?(额外摸两张牌)", confirmLabel: "发动", cancelLabel: "不发动" }, defaultChoice: false, timeout: 15 }); if (!ctx.state.localVars[CONFIRMED_KEY$51]) return; await markOncePerTurn(ctx.state, ownerId, "好施"); ctx.state.localVars[ACTIVE_KEY$5] = true; const count = atom.count ?? 2; return { kind: "modify", atom: { ...ctx.atom, count: count + 2 } }; }); registerAfterHook(state, skill.id, ownerId, "摸牌", async (ctx) => { if (ctx.atom.player !== ownerId) return; if (!ctx.state.localVars[ACTIVE_KEY$5]) return; delete ctx.state.localVars[ACTIVE_KEY$5]; const self = ctx.state.players[ownerId]; if (!self?.alive) return; const handCount = self.hand.length; if (handCount <= 5) return; const giveCount = Math.floor(handCount / 2); let minCount = Infinity; const candidates = []; for (const p of ctx.state.players) { if (p.index === ownerId || !p.alive) continue; if (p.hand.length < minCount) { minCount = p.hand.length; candidates.length = 0; candidates.push(p.index); } else if (p.hand.length === minCount) candidates.push(p.index); } if (candidates.length === 0) return; let target; if (candidates.length === 1) target = candidates[0]; else { delete ctx.state.localVars[TARGET_KEY$34]; await applyAtom(ctx.state, { type: "请求回应", requestType: CHOOSE_TARGET_RT$6, target: ownerId, prompt: { type: "choosePlayer", title: "好施:选择手牌最少的角色(给予手牌)", min: 1, max: 1, filter: (_view, t) => candidates.includes(t) }, timeout: 15 }); const chosen = ctx.state.localVars[TARGET_KEY$34]; delete ctx.state.localVars[TARGET_KEY$34]; if (typeof chosen !== "number") return; target = chosen; } delete ctx.state.localVars[GIVE_KEY$3]; await applyAtom(ctx.state, { type: "请求回应", requestType: GIVE_RT$4, target: ownerId, prompt: { type: "useCard", title: `好施:选择 ${giveCount} 张牌交给 ${ctx.state.players[target].name}`, cardFilter: { filter: () => true, min: giveCount, max: giveCount } }, mandatory: true, timeout: 30 }); let giveCards = ctx.state.localVars[GIVE_KEY$3]; delete ctx.state.localVars[GIVE_KEY$3]; if (!giveCards || giveCards.length === 0) giveCards = ctx.state.players[ownerId]?.hand.slice(0, giveCount) ?? []; if (giveCards && giveCards.length > 0) for (const cardId of giveCards) await applyAtom(ctx.state, { type: "给予", cardId, from: ownerId, to: target }); }); return () => {}; } function onMount$236(_skill, api) { api.defineAction("respond", { label: "好施", style: "primary", prompt: { type: "confirm", title: "好施", confirmLabel: "确认", cancelLabel: "取消" } }); return () => {}; } var CONFIRM_RT$63, CHOOSE_TARGET_RT$6, GIVE_RT$4, CONFIRMED_KEY$51, ACTIVE_KEY$5, TARGET_KEY$34, GIVE_KEY$3, _skillModule$120; var init_好施 = __esmMin((() => { init_apply(); init_once_per_turn(); init_skill(); CONFIRM_RT$63 = "好施/confirm"; CHOOSE_TARGET_RT$6 = "好施/target"; GIVE_RT$4 = "好施/give"; CONFIRMED_KEY$51 = "好施/confirmed"; ACTIVE_KEY$5 = "好施/active"; TARGET_KEY$34 = "好施/chosenTarget"; GIVE_KEY$3 = "好施/giveCards"; _skillModule$120 = { createSkill: createSkill$262, onInit: onInit$262, onMount: onMount$236 }; })); //#endregion //#region src/engine/skills/完杀.ts var 完杀_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$261, default: () => 完杀_default, onInit: () => onInit$261 }); function createSkill$261(id, ownerId) { return { id, ownerId, name: "完杀", description: "锁定技:在你的回合,除你以外,只有处于濒死状态的角色才能使用【桃】", isLocked: true }; } function onInit$261(skill, state) { const ownerId = skill.ownerId; registerBeforeHook(state, skill.id, ownerId, "请求回应", async (ctx) => { const atom = ctx.atom; if (atom.requestType !== "桃/求桃") return; if (ctx.state.currentPlayerIndex !== ownerId) return; const asked = atom.target; if (typeof asked !== "number") return; if (asked === ownerId) return; if (asked === ctx.state.players.findIndex((p) => p.alive && p.health <= 0)) return; return { kind: "cancel" }; }); return () => {}; } var 完杀_default; var init_完杀 = __esmMin((() => { init_skill(); 完杀_default = { createSkill: createSkill$261, onInit: onInit$261 }; })); //#endregion //#region src/engine/skills/审时.ts var 审时_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$260, default: () => _skillModule$119, onInit: () => onInit$260, onMount: () => onMount$235 }); function getState$3(state, ownerId) { return state.players[ownerId]?.vars[STATE_KEY$3] === "阴" ? "阴" : "阳"; } async function syncStateView$3(state, ownerId) { await applyAtom(state, { type: "回合用量", player: ownerId, key: STATE_VIEW_KEY$3, value: getState$3(state, ownerId) }); } /** 手牌数最多的其他存活角色候选 */ function maxHandOthers(state, ownerId) { let maxCount = -1; const candidates = []; for (const p of state.players) { if (p.index === ownerId || !p.alive) continue; if (p.hand.length > maxCount) { maxCount = p.hand.length; candidates.length = 0; candidates.push(p.index); } else if (p.hand.length === maxCount) candidates.push(p.index); } return candidates; } /** 将手牌摸至四张:补到 4 张,已不少于 4 则不摸 */ async function drawToFour(state, player) { const target = state.players[player]; if (!target?.alive) return; const need = 4 - target.hand.length; if (need > 0) await applyAtom(state, { type: "摸牌", player, count: need }); } function createSkill$260(id, ownerId) { return { id, ownerId, name: SKILL_ID$57, description: "转换技。阳:出牌阶段限一次,交给手牌数最多的其他角色一张牌并对其造成1点伤害,若其因此死亡,可令一名角色摸至四张。阴:其他角色对你造成伤害后,观看其手牌并交给其一张牌,当前回合结束阶段若其未失去此牌,你将手牌摸至四张" }; } function onInit$260(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "respond", (st, params) => { const slot = st.pendingSlots.get(ownerId); if (!slot) return "当前不需要回应"; const atom = slot.atom; if (atom["type"] !== "请求回应") return "当前不需要回应"; const rt = atom["requestType"]; if (rt === YIN_CONFIRM_RT$2 || rt === YANG_DEATH_CONFIRM_RT) return null; if (rt === YIN_GIVE_RT) { const raw = params.cardIds ?? params.cardId; const ids = Array.isArray(raw) ? raw : typeof raw === "string" ? [raw] : void 0; if (!ids || ids.length === 0) return null; const self = st.players[ownerId]; if (!ids.every((id) => self.hand.includes(id))) return "牌不在手牌中"; return null; } if (rt === YANG_DEATH_TARGET_RT) { const t = params.targets?.[0] ?? (typeof params.target === "number" ? params.target : void 0); if (typeof t !== "number") return "需要指定一名角色"; if (!st.players[t]?.alive) return "目标不合法"; return null; } return "当前不是审时回应"; }, async (st, params) => { const rt = (st.pendingSlots.get(ownerId)?.atom)?.requestType; if (rt === YIN_CONFIRM_RT$2) st.localVars[YIN_CONFIRMED_KEY$2] = params.choice === true || params.confirmed === true; else if (rt === YIN_GIVE_RT) { const raw = params.cardIds ?? params.cardId; const ids = Array.isArray(raw) ? raw : typeof raw === "string" ? [raw] : void 0; if (ids && ids.length > 0) st.localVars[YIN_GIVE_KEY] = ids; } else if (rt === YANG_DEATH_CONFIRM_RT) st.localVars[YANG_DEATH_CONFIRMED_KEY] = params.choice === true || params.confirmed === true; else if (rt === YANG_DEATH_TARGET_RT) { const t = params.targets?.[0] ?? (typeof params.target === "number" ? params.target : void 0); if (typeof t === "number") st.localVars[YANG_DEATH_TARGET_KEY] = t; } }); registerAction(state, skill.id, ownerId, "use", (st, params) => { const self = st.players[ownerId]; if (!self) return "player not found"; if (!self.alive) return "你已死亡"; if (st.currentPlayerIndex !== ownerId) return "只能在你的回合使用"; if (st.phase !== "出牌") return "只能在出牌阶段使用"; if (hasBlockingPending(st)) return "当前有未完成的询问"; if (usedThisTurn$3(st, ownerId, SKILL_ID$57)) return "本回合已使用过审时"; if (getState$3(st, ownerId) !== "阳") return "当前为阴状态,无法发动阳"; if (self.hand.length === 0) return "需要有手牌才能发动审时"; const target = params.target; if (typeof target !== "number") return "需要指定目标"; if (target === ownerId) return "不能对自己发动审时"; if (!st.players[target]?.alive) return "目标不合法"; const candidates = maxHandOthers(st, ownerId); if (candidates.length === 0) return "没有可选目标"; if (!candidates.includes(target)) return "目标必须是手牌数最多的其他角色"; const cardId = params.cardId; if (typeof cardId !== "string") return "需要选择一张牌"; if (!self.hand.includes(cardId)) return "牌不在手牌中"; return null; }, async (st, params) => { const from = ownerId; const target = params.target; const cardId = params.cardId; await markOncePerTurn(st, from, SKILL_ID$57); st.players[from].vars[STATE_KEY$3] = "阴"; await syncStateView$3(st, from); await pushFrame(st, `${SKILL_ID$57}(阳)`, from, { target, cardId }); if (st.players[from].hand.includes(cardId) && st.players[target]?.alive) await applyAtom(st, { type: "给予", cardId, from, to: target }); if (st.players[target]?.alive) await runDamageFlow(st, from, target, 1); if (!st.players[target]?.alive) { delete st.localVars[YANG_DEATH_CONFIRMED_KEY]; await applyAtom(st, { type: "请求回应", requestType: YANG_DEATH_CONFIRM_RT, target: from, prompt: { type: "confirm", title: "审时:目标因此死亡,是否令一名角色将手牌摸至四张?", confirmLabel: "发动", cancelLabel: "不发动" }, defaultChoice: false, timeout: 20 }); if (st.localVars[YANG_DEATH_CONFIRMED_KEY]) { delete st.localVars[YANG_DEATH_TARGET_KEY]; await applyAtom(st, { type: "请求回应", requestType: YANG_DEATH_TARGET_RT, target: from, prompt: { type: "choosePlayer", title: "审时:选择一名角色(将手牌摸至四张)", min: 1, max: 1, candidates: st.players.filter((p) => p.alive).map((p) => p.index), filter: (_view, t) => !!st.players[t]?.alive }, timeout: 20 }); const chosen = st.localVars[YANG_DEATH_TARGET_KEY]; delete st.localVars[YANG_DEATH_TARGET_KEY]; if (typeof chosen === "number" && st.players[chosen]?.alive) await drawToFour(st, chosen); } delete st.localVars[YANG_DEATH_CONFIRMED_KEY]; } await popFrame(st); }); registerAfterHook(state, skill.id, ownerId, "受到伤害后", async (ctx) => { const atom = ctx.atom; if (atom.target !== ownerId) return; if ((atom.amount ?? 0) <= 0) return; const source = atom.source; if (source === ownerId) return; if (getState$3(ctx.state, ownerId) !== "阴") return; if (!ctx.state.players[ownerId]?.alive) return; const sourcePlayer = ctx.state.players[source]; if (!sourcePlayer?.alive) return; const self = ctx.state.players[ownerId]; if (!self || self.hand.length === 0) return; delete ctx.state.localVars[YIN_CONFIRMED_KEY$2]; await applyAtom(ctx.state, { type: "请求回应", requestType: YIN_CONFIRM_RT$2, target: ownerId, prompt: { type: "confirm", title: "审时(阴):是否观看其手牌并交给其一张牌?", confirmLabel: "发动", cancelLabel: "不发动" }, defaultChoice: false, timeout: 15 }); if (!ctx.state.localVars[YIN_CONFIRMED_KEY$2]) return; delete ctx.state.localVars[YIN_CONFIRMED_KEY$2]; delete ctx.state.localVars[YIN_GIVE_KEY]; await applyAtom(ctx.state, { type: "请求回应", requestType: YIN_GIVE_RT, target: ownerId, prompt: { type: "useCard", title: `审时(阴):选择一张手牌交给 ${sourcePlayer.name}`, cardFilter: { filter: () => true, min: 1, max: 1 } }, timeout: 30 }); const giveCards = ctx.state.localVars[YIN_GIVE_KEY]; delete ctx.state.localVars[YIN_GIVE_KEY]; if (!Array.isArray(giveCards) || giveCards.length === 0) return; const giveCardId = giveCards[0]; if (!self.hand.includes(giveCardId) || !ctx.state.players[source]?.alive) return; await applyAtom(ctx.state, { type: "给予", cardId: giveCardId, from: ownerId, to: source }); ctx.state.players[ownerId].vars[STATE_KEY$3] = "阳"; await syncStateView$3(ctx.state, ownerId); if (ctx.state.players[source]?.hand.includes(giveCardId)) { const track = ctx.state.players[ownerId].vars[YIN_TRACK_KEY] ?? []; track.push({ source, cardId: giveCardId }); ctx.state.players[ownerId].vars[YIN_TRACK_KEY] = track; } }); registerAfterHook(state, skill.id, ownerId, "阶段开始", async (ctx) => { if (ctx.atom.type !== "阶段开始") return; if (ctx.atom.phase !== "回合结束") return; const ownerState = ctx.state.players[ownerId]; const track = ownerState?.vars[YIN_TRACK_KEY]; if (!Array.isArray(track) || track.length === 0) return; ownerState.vars[YIN_TRACK_KEY] = []; if (!ownerState?.alive) return; for (const entry of track) if (ctx.state.players[entry.source]?.hand.includes(entry.cardId)) { await drawToFour(ctx.state, ownerId); break; } }); registerAfterHook(state, skill.id, ownerId, "回合开始", async (ctx) => { if (ctx.atom.type !== "回合开始") return; if (ctx.atom.player !== ownerId) return; await syncStateView$3(ctx.state, ownerId); }); return () => {}; } function onMount$235(_skill, api) { api.defineAction("use", { label: SKILL_ID$57, style: "danger", prompt: { type: "useCardAndTarget", title: "审时(阳):交给手牌数最多的其他角色一张牌,对其造成1点伤害", cardFilter: { filter: () => true, min: 1, max: 1 }, targetFilter: { min: 1, max: 1, filter: (view, t) => { const me = view.currentPlayerIndex; if (t === me) return false; const tp = view.players.find((p) => p.index === t); if (!tp || tp.alive === false) return false; let maxCount = -1; for (const p of view.players) { if (p.index === me || !p.alive) continue; const hc = p.handCount ?? 0; if (hc > maxCount) maxCount = hc; } return (tp.handCount ?? 0) === maxCount; } } }, activeWhen: (ctx) => { if (!defaultPlayActive(ctx)) return false; const p = ctx.view.players[ctx.perspectiveIdx]; if (!p) return false; if (p.turnUsage?.[STATE_VIEW_KEY$3] === "阴") return false; if ((p.handCount ?? 0) === 0) return false; return ctx.view.players.some((other) => other.index !== ctx.perspectiveIdx && other.alive); } }); api.defineAction("respond", { label: SKILL_ID$57, style: "primary", prompt: { type: "confirm", title: "审时", confirmLabel: "确认", cancelLabel: "取消" } }); return () => {}; } var SKILL_ID$57, STATE_KEY$3, STATE_VIEW_KEY$3, YIN_CONFIRM_RT$2, YIN_GIVE_RT, YIN_TRACK_KEY, YANG_DEATH_CONFIRM_RT, YANG_DEATH_TARGET_RT, YIN_CONFIRMED_KEY$2, YIN_GIVE_KEY, YANG_DEATH_CONFIRMED_KEY, YANG_DEATH_TARGET_KEY, _skillModule$119; var init_审时 = __esmMin((() => { init_apply(); init_frame(); init_damage(); init_once_per_turn(); init_action_active(); init_skill(); init_vars_keys(); SKILL_ID$57 = "审时"; STATE_KEY$3 = "审时/态"; STATE_VIEW_KEY$3 = SHENSHI_STATE_VIEW_KEY; YIN_CONFIRM_RT$2 = "审时/阴/confirm"; YIN_GIVE_RT = "审时/阴/give"; YIN_TRACK_KEY = "审时/阴/追踪"; YANG_DEATH_CONFIRM_RT = "审时/阳/致死确认"; YANG_DEATH_TARGET_RT = "审时/阳/致死目标"; YIN_CONFIRMED_KEY$2 = "审时/阴/confirmed"; YIN_GIVE_KEY = "审时/阴/giveCard"; YANG_DEATH_CONFIRMED_KEY = "审时/阳/致死确认结果"; YANG_DEATH_TARGET_KEY = "审时/阳/致死目标结果"; _skillModule$119 = { createSkill: createSkill$260, onInit: onInit$260, onMount: onMount$235 }; })); //#endregion //#region src/engine/skills/寒冰剑.ts var 寒冰剑_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$259, onInit: () => onInit$259, onMount: () => onMount$234 }); function createSkill$259(id, ownerId) { return { id, ownerId, name: "寒冰剑", description: "武器:杀造成伤害时可改为依次弃目标2张牌", isLocked: true }; } function onInit$259(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "respond", (state, params) => { const slot = state.pendingSlots.get(ownerId); if (!slot) return "当前不需要回应"; if (slot.atom.type !== "请求回应") return "当前不需要回应"; const requestType = slot.atom.requestType; if (requestType === "寒冰剑/confirm") return null; if (requestType === "寒冰剑/选牌") { const zone = params.zone; if (zone === "equipment") { if (typeof params.cardId !== "string") return "cardId required"; } else if (zone === "hand") { if (typeof params.handIndex !== "number") return "handIndex required"; } else return "zone required (equipment|hand)"; return null; } return "当前不是寒冰剑回应"; }, async (state, params) => { const requestType = (state.pendingSlots.get(ownerId)?.atom)?.requestType; if (requestType === "寒冰剑/confirm") state.localVars["寒冰剑/confirmed"] = params.choice === true || params.confirmed === true; else if (requestType === "寒冰剑/选牌") state.localVars[PICK_RESULT_KEY$3] = { zone: params.zone, cardId: params.cardId ?? null, handIndex: params.handIndex ?? null }; }); registerBeforeHook(state, skill.id, ownerId, "受到伤害时", async (ctx) => { const atom = ctx.atom; if (atom.source !== ownerId) return; const self = ctx.state.players[ownerId]; if (!self) return; const weaponId = self.equipment["武器"]; if (!weaponId) return; if (ctx.state.cardMap[weaponId]?.name !== "寒冰剑") return; const damageCardId = atom.cardId; if (!damageCardId) return; if (ctx.state.cardMap[damageCardId]?.name !== "杀") return; const target = ctx.state.players[atom.target]; if (!target) return; if (!(target.hand.length > 0 || Object.keys(target.equipment).length > 0)) return; delete ctx.state.localVars["寒冰剑/confirmed"]; await applyAtom(ctx.state, { type: "请求回应", requestType: "寒冰剑/confirm", target: ownerId, prompt: { type: "confirm", title: "寒冰剑:是否改为弃目标2张牌?", confirmLabel: "弃牌", cancelLabel: "正常伤害" }, defaultChoice: false, timeout: 10 }); if (!ctx.state.localVars["寒冰剑/confirmed"]) return; for (let i = 0; i < 2; i++) { const tp = ctx.state.players[atom.target]; if (!tp) break; if (!(tp.hand.length > 0 || Object.keys(tp.equipment).length > 0)) break; await runPickTargetCardPanel(ctx.state, ownerId, atom.target, tp, { mode: "discard", requestType: "寒冰剑/选牌", title: `寒冰剑:选择弃置目标的牌(第${i + 1}张)`, includeJudge: false }); } delete ctx.state.localVars["寒冰剑/confirmed"]; return { kind: "cancel" }; }); return () => {}; } function onMount$234(skill, api) { api.defineAction("respond", { label: "寒冰剑", style: "default", prompt: { type: "confirm", title: "寒冰剑:是否改为弃目标2张?", confirmLabel: "弃牌", cancelLabel: "正常伤害" } }); } var init_寒冰剑 = __esmMin((() => { init_apply(); init_skill(); init_pick_card_panel(); init_vars_keys(); })); //#endregion //#region src/engine/skills/寸目.ts var 寸目_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$258, default: () => _skillModule$118, onInit: () => onInit$258 }); function createSkill$258(id, ownerId) { return { id, ownerId, name: "寸目", description: "锁定技:当你摸牌时,你改为从牌堆底摸牌", isLocked: true }; } function onInit$258(skill, state) { const ownerId = skill.ownerId; registerBeforeHook(state, skill.id, ownerId, "摸牌", async (ctx) => { if (ctx.atom.player !== ownerId) return; if (ctx.atom.fromBottom) return; return { kind: "modify", atom: { ...ctx.atom, fromBottom: true } }; }); return () => {}; } var _skillModule$118; var init_寸目 = __esmMin((() => { init_skill(); _skillModule$118 = { createSkill: createSkill$258, onInit: onInit$258 }; })); //#endregion //#region src/engine/skills/屯田.ts var 屯田_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$257, default: () => 屯田_default, onInit: () => onInit$257, onMount: () => onMount$233 }); function createSkill$257(id, ownerId) { return { id, ownerId, name: "屯田", description: "回合外失去牌时判定,非红桃则置于武将牌上为田,每张田与其他角色距离 -1" }; } /** 数当前玩家的田数量 */ function tianCount$2(state, player) { return state.players[player].marks.filter((m) => m.id.startsWith(TIAN_PREFIX$3)).length; } /** 重新计算并写入距离修正 vars(每张田 = -1 距离 = 进攻修正 +1) */ function syncDistanceMod$2(state, player) { const count = tianCount$2(state, player); if (count > 0) state.players[player].vars[DISTANCE_ATTACK_MOD_KEY] = count; else delete state.players[player].vars[DISTANCE_ATTACK_MOD_KEY]; } function onInit$257(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "respond", (st, _params) => { const slot = st.pendingSlots.get(ownerId); if (!slot) return "当前不需要回应"; const atom = slot.atom; if (atom["type"] !== "请求回应") return "当前不需要回应"; if (atom["requestType"] !== CONFIRM_RT$62) return "当前不是屯田确认"; return null; }, async (st, params) => { st.localVars[CONFIRMED_KEY$50] = params.choice === true || params.confirmed === true; }); registerAfterHook(state, skill.id, ownerId, "判定牌生效后", async (ctx) => { const atom = ctx.atom; if (atom.player !== ownerId) return; if (atom.judgeType !== "屯田") return; if (!ctx.state.localVars[CONFIRMED_KEY$50]) return; const processing = frameCards(ctx.state); if (processing.length === 0) return; const judgeCardId = processing[processing.length - 1]; const judgeCard = ctx.state.cardMap[judgeCardId]; if (!judgeCard) return; ctx.state.localVars[JUDGE_SUIT_KEY$3] = judgeCard.suit; ctx.state.localVars[JUDGE_CARD_KEY$2] = judgeCardId; if (judgeCard.suit === "♥") return; await applyAtom(ctx.state, { type: "收取判定牌", player: ownerId, cardId: judgeCardId }); const newCount = tianCount$2(ctx.state, ownerId) + 1; const tianId = `${TIAN_PREFIX$3}${ctx.state.seq}`; await applyAtom(ctx.state, { type: "加标记", player: ownerId, mark: { id: tianId, scope: ownerId, payload: { cardId: judgeCardId } }, distanceVars: { attackMod: newCount } }); syncDistanceMod$2(ctx.state, ownerId); }); async function maybeTriggerTunTian(ctx, lossAtomType) { if (ctx.state.currentPlayerIndex === ownerId) return; if (!ctx.state.players[ownerId]?.alive) return; if (ctx.state.zones.deck.length === 0) return; delete ctx.state.localVars[CONFIRMED_KEY$50]; await applyAtom(ctx.state, { type: "请求回应", requestType: CONFIRM_RT$62, target: ownerId, prompt: { type: "confirm", title: `是否发动屯田?(回合外因${lossAtomType}失去牌,判定可能获得田)`, confirmLabel: "发动", cancelLabel: "不发动" }, defaultChoice: false, timeout: 10 }); if (!ctx.state.localVars[CONFIRMED_KEY$50]) return; delete ctx.state.localVars[JUDGE_SUIT_KEY$3]; delete ctx.state.localVars[JUDGE_CARD_KEY$2]; await runJudgeFlow(ctx.state, ownerId, "屯田"); delete ctx.state.localVars[JUDGE_SUIT_KEY$3]; delete ctx.state.localVars[JUDGE_CARD_KEY$2]; } registerAfterHook(state, skill.id, ownerId, "获得", async (ctx) => { const atom = ctx.atom; if (atom.type !== "获得") return; if (atom.from !== ownerId) return; if (atom.player === ownerId) return; await maybeTriggerTunTian(ctx, "获得"); }); registerAfterHook(state, skill.id, ownerId, "移动牌", async (ctx) => { const atom = ctx.atom; if (atom.type !== "移动牌") return; if (atom.from.zone !== "手牌") return; if (atom.from.player !== ownerId) return; if (atom.to.zone === "手牌" && atom.to.player === ownerId) return; await maybeTriggerTunTian(ctx, "移动牌"); }); registerAfterHook(state, skill.id, ownerId, "给予", async (ctx) => { const atom = ctx.atom; if (atom.type !== "给予") return; if (atom.from !== ownerId) return; if (atom.to === ownerId) return; await maybeTriggerTunTian(ctx, "给予"); }); registerAfterHook(state, skill.id, ownerId, "弃置", async (ctx) => { const atom = ctx.atom; if (atom.type !== "弃置") return; if (atom.player !== ownerId) return; await maybeTriggerTunTian(ctx, "弃置"); }); return () => {}; } function onMount$233(_skill, api) { api.defineAction("respond", { label: "屯田", style: "primary", prompt: { type: "confirm", title: "是否发动屯田?", confirmLabel: "发动", cancelLabel: "不发动" } }); } var CONFIRM_RT$62, CONFIRMED_KEY$50, JUDGE_SUIT_KEY$3, JUDGE_CARD_KEY$2, TIAN_PREFIX$3, 屯田_default; var init_屯田 = __esmMin((() => { init_apply(); init_frame(); init_judge(); init_skill(); init_vars_keys(); CONFIRM_RT$62 = "屯田/confirm"; CONFIRMED_KEY$50 = "屯田/confirmed"; JUDGE_SUIT_KEY$3 = "屯田/judgeSuit"; JUDGE_CARD_KEY$2 = "屯田/judgeCardId"; TIAN_PREFIX$3 = "屯田/田:"; 屯田_default = { createSkill: createSkill$257, onInit: onInit$257, onMount: onMount$233 }; })); //#endregion //#region src/engine/skills/崩坏.ts var 崩坏_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$256, default: () => 崩坏_default, onInit: () => onInit$256, onMount: () => onMount$232 }); function createSkill$256(id, ownerId) { return { id, ownerId, name: "崩坏", description: "锁定技:回合结束阶段,若你的体力不是全场最少的,须减1点体力或1点体力上限", isLocked: true }; } function onInit$256(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "respond", (s, _params) => { const slot = s.pendingSlots.get(ownerId); if (slot?.atom.type !== "请求回应") return "当前不需要回应"; if (slot.atom.requestType !== CHOOSE_RT$18) return "当前不是崩坏选择"; return null; }, async (s, params) => { s.localVars[CHOICE_KEY$17] = params.choice === true ? "maxHealth" : "health"; }); registerAfterHook(state, skill.id, ownerId, "阶段开始", async (ctx) => { const atom = ctx.atom; if (atom.type !== "阶段开始") return; if (atom.player !== ownerId) return; if (atom.phase !== "回合结束") return; const self = ctx.state.players[ownerId]; if (!self?.alive) return; const minHealth = ctx.state.players.filter((p) => p.alive).reduce((min, p) => Math.min(min, getHealthValue(p)), Number.POSITIVE_INFINITY); if (getHealthValue(self) <= minHealth) return; delete ctx.state.localVars[CHOICE_KEY$17]; await applyAtom(ctx.state, { type: "请求回应", requestType: CHOOSE_RT$18, target: ownerId, prompt: { type: "confirm", title: "崩坏:减1点体力上限,还是减1点体力?", confirmLabel: "减体力上限", cancelLabel: "减体力" }, defaultChoice: false, timeout: 15 }); const choice = ctx.state.localVars[CHOICE_KEY$17]; delete ctx.state.localVars[CHOICE_KEY$17]; if (choice === "maxHealth") await applyAtom(ctx.state, { type: "设上限", player: ownerId, amount: self.maxHealth - 1 }); else await applyAtom(ctx.state, { type: "失去体力", target: ownerId, amount: 1 }); }); return () => {}; } function onMount$232(_skill, api) { api.defineAction("respond", { label: "崩坏", style: "default", prompt: { type: "confirm", title: "崩坏:减1点体力上限,还是减1点体力?", confirmLabel: "减体力上限", cancelLabel: "减体力" } }); } var CHOOSE_RT$18, CHOICE_KEY$17, 崩坏_default; var init_崩坏 = __esmMin((() => { init_types(); init_apply(); init_skill(); CHOOSE_RT$18 = "崩坏/choose"; CHOICE_KEY$17 = "崩坏/choice"; 崩坏_default = { createSkill: createSkill$256, onInit: onInit$256, onMount: onMount$232 }; })); //#endregion //#region src/engine/skills/巧变.ts var 巧变_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$255, default: () => 巧变_default, onInit: () => onInit$255, onMount: () => onMount$231 }); function createSkill$255(id, ownerId) { return { id, ownerId, name: "巧变", description: "弃一张手牌跳过阶段:摸牌阶段可从至多两名其他角色各获得一张手牌;出牌阶段可移动场上的一张牌" }; } /** 取玩家"场上"的牌(手牌+装备+判定区)——用于出牌阶段移动的源牌选择 */ function fieldCardIds$2(state, player) { const p = state.players[player]; if (!p) return []; const hand = [...p.hand]; const equip = Object.values(p.equipment).filter((id) => !!id); const judge = p.pendingTricks.map((t) => t.card.id); return [ ...hand, ...equip, ...judge ]; } function onInit$255(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "respond", (st, _params) => { const slot = st.pendingSlots.get(ownerId); if (!slot) return "当前不需要回应"; const atom = slot.atom; if (atom["type"] !== "请求回应") return "当前不需要回应"; const rt = atom["requestType"]; if (![ CONFIRM_RT$61, DISCARD_RT$22, STEAL_TARGETS_RT$1, MOVE_SOURCE_PLAYER_RT$1, MOVE_SOURCE_CARD_RT$1, MOVE_DEST_PLAYER_RT$1 ].includes(rt)) return "当前不是巧变询问"; return null; }, async (st, params) => { const rt = (st.pendingSlots.get(ownerId)?.atom)?.requestType; if (rt === CONFIRM_RT$61) { st.localVars[CONFIRMED_KEY$49] = params.choice === true; return; } if (rt === DISCARD_RT$22) { const cardIds = params.cardIds; const single = params.cardId; const id = (Array.isArray(cardIds) && cardIds.length > 0 ? cardIds[0] : void 0) ?? (typeof single === "string" ? single : void 0); if (id) st.localVars[DISCARD_KEY$14] = id; return; } if (rt === STEAL_TARGETS_RT$1) { const targets = params.targets; if (Array.isArray(targets)) st.localVars[STEAL_TARGETS_KEY$1] = targets; return; } if (rt === MOVE_SOURCE_PLAYER_RT$1) { const t = params.targets?.[0] ?? (typeof params.target === "number" ? params.target : void 0); if (typeof t === "number") st.localVars[MOVE_SOURCE_PLAYER_KEY$1] = t; return; } if (rt === MOVE_SOURCE_CARD_RT$1) { const cardIds = params.cardIds; const single = params.cardId; const id = (Array.isArray(cardIds) && cardIds.length > 0 ? cardIds[0] : void 0) ?? (typeof single === "string" ? single : void 0); if (id) st.localVars[MOVE_SOURCE_CARD_KEY$1] = id; return; } if (rt === MOVE_DEST_PLAYER_RT$1) { const t = params.targets?.[0] ?? (typeof params.target === "number" ? params.target : void 0); if (typeof t === "number") st.localVars[MOVE_DEST_PLAYER_KEY$1] = t; return; } }); registerBeforeHook(state, skill.id, ownerId, "阶段开始", async (ctx) => { const atom = ctx.atom; if (atom.type !== "阶段开始") return; if (atom.player !== ownerId) return; if (ctx.state.currentPlayerIndex !== ownerId) return; const phase = atom.phase; if (!SKIPPABLE_PHASES$1.includes(phase)) return; const self = ctx.state.players[ownerId]; if (!self?.alive) return; if (self.hand.length === 0) return; if (hasBlockingPending(ctx.state)) return; delete ctx.state.localVars[CONFIRMED_KEY$49]; await applyAtom(ctx.state, { type: "请求回应", requestType: CONFIRM_RT$61, target: ownerId, prompt: { type: "confirm", title: `是否发动巧变?(弃一张手牌跳过${phase}阶段)`, confirmLabel: "发动", cancelLabel: "不发动" }, defaultChoice: false, timeout: 10 }); if (!ctx.state.localVars[CONFIRMED_KEY$49]) return; delete ctx.state.localVars[DISCARD_KEY$14]; await applyAtom(ctx.state, { type: "请求回应", requestType: DISCARD_RT$22, target: ownerId, prompt: { type: "distribute", mode: "select", title: `巧变:选择一张手牌弃置以跳过${phase}阶段`, source: "hand", minTotal: 1, maxTotal: 1 }, timeout: 15 }); const discardCardId = ctx.state.localVars[DISCARD_KEY$14]; delete ctx.state.localVars[DISCARD_KEY$14]; if (!discardCardId || !self.hand.includes(discardCardId)) return; await pushFrame(ctx.state, "巧变", ownerId, { phase, discardCardId }); try { await applyAtom(ctx.state, { type: "弃置", player: ownerId, cardIds: [discardCardId], voluntary: true }); if (phase === "摸牌") { const candidates = ctx.state.players.filter((p) => p.alive && p.index !== ownerId && p.hand.length > 0); if (candidates.length > 0) { delete ctx.state.localVars[STEAL_TARGETS_KEY$1]; await applyAtom(ctx.state, { type: "请求回应", requestType: STEAL_TARGETS_RT$1, target: ownerId, prompt: { type: "choosePlayer", title: "巧变:选择至多两名其他角色(各获得其一张手牌)", min: 0, max: Math.min(2, candidates.length), filter: (_view, t) => t !== ownerId && ctx.state.players[t]?.alive === true && (ctx.state.players[t]?.hand.length ?? 0) > 0 }, timeout: 20 }); const targets = ctx.state.localVars[STEAL_TARGETS_KEY$1] ?? []; delete ctx.state.localVars[STEAL_TARGETS_KEY$1]; for (const t of targets.slice(0, 2)) { const target = ctx.state.players[t]; if (!target?.alive || target.hand.length === 0) continue; const rng = createRng(ctx.state.rngSeed); const idx = rng.nextInt(target.hand.length); ctx.state.rngSeed = rng.getState(); const cardId = target.hand[idx]; await applyAtom(ctx.state, { type: "获得", player: ownerId, cardId, from: t }); } } } else if (phase === "出牌") { if (ctx.state.players.filter((p) => p.alive && fieldCardIds$2(ctx.state, p.index).length > 0).length > 0) { delete ctx.state.localVars[MOVE_SOURCE_PLAYER_KEY$1]; await applyAtom(ctx.state, { type: "请求回应", requestType: MOVE_SOURCE_PLAYER_RT$1, target: ownerId, prompt: { type: "choosePlayer", title: "巧变:选择源玩家(从其场上选一张牌移动)", min: 1, max: 1, filter: (_view, t) => ctx.state.players[t]?.alive === true && fieldCardIds$2(ctx.state, t).length > 0 }, timeout: 15 }); const srcPlayer = ctx.state.localVars[MOVE_SOURCE_PLAYER_KEY$1]; delete ctx.state.localVars[MOVE_SOURCE_PLAYER_KEY$1]; if (typeof srcPlayer !== "number") {} else { const srcCards = fieldCardIds$2(ctx.state, srcPlayer); delete ctx.state.localVars[MOVE_SOURCE_CARD_KEY$1]; await applyAtom(ctx.state, { type: "请求回应", requestType: MOVE_SOURCE_CARD_RT$1, target: ownerId, prompt: { type: "distribute", mode: "select", title: `巧变:选择 ${ctx.state.players[srcPlayer]?.name ?? `P${srcPlayer}`} 场上的一张牌移动`, cardIds: srcCards, minTotal: 1, maxTotal: 1 }, timeout: 15 }); const srcCardId = ctx.state.localVars[MOVE_SOURCE_CARD_KEY$1]; delete ctx.state.localVars[MOVE_SOURCE_CARD_KEY$1]; if (typeof srcCardId === "string" && srcCards.includes(srcCardId)) { delete ctx.state.localVars[MOVE_DEST_PLAYER_KEY$1]; await applyAtom(ctx.state, { type: "请求回应", requestType: MOVE_DEST_PLAYER_RT$1, target: ownerId, prompt: { type: "choosePlayer", title: "巧变:选择目标玩家(将牌移到其场上)", min: 1, max: 1, filter: (_view, t) => ctx.state.players[t]?.alive === true }, timeout: 15 }); const destPlayer = ctx.state.localVars[MOVE_DEST_PLAYER_KEY$1]; delete ctx.state.localVars[MOVE_DEST_PLAYER_KEY$1]; if (typeof destPlayer === "number") await moveFieldCard$2(ctx.state, srcPlayer, srcCardId, destPlayer); } } } } } finally { await popFrame(ctx.state); } return skipPhase(ctx.state, { player: ownerId, phase }); }); return () => {}; } /** * 把 srcPlayer 场上的 srcCardId 移动到 destPlayer 场上。 * 自动按 srcCardId 所在区域 + 目标位置选择合适的转移方式: * - 装备牌且目标装备槽空闲 → 直接移到目标装备区(通过 移动牌 + 装备 atom) * - 否则统一移到目标手牌(包括判定区牌移回手牌) */ async function moveFieldCard$2(state, srcPlayer, srcCardId, destPlayer) { if (srcPlayer === destPlayer) return; const srcP = state.players[srcPlayer]; if (!srcP) return; if (!state.cardMap[srcCardId]) return; const judgeTrick = srcP.pendingTricks.find((t) => t.card.id === srcCardId); if (judgeTrick) { await applyAtom(state, { type: "移除延时锦囊", player: srcPlayer, trickName: judgeTrick.name }); await applyAtom(state, { type: "获得", player: destPlayer, cardId: srcCardId, from: srcPlayer }); return; } await applyAtom(state, { type: "获得", player: destPlayer, cardId: srcCardId, from: srcPlayer }); } function onMount$231(_skill, api) { api.defineAction("respond", { label: "巧变", style: "primary", prompt: { type: "confirm", title: "是否发动巧变?", confirmLabel: "发动", cancelLabel: "不发动" } }); } var CONFIRM_RT$61, DISCARD_RT$22, STEAL_TARGETS_RT$1, MOVE_SOURCE_PLAYER_RT$1, MOVE_SOURCE_CARD_RT$1, MOVE_DEST_PLAYER_RT$1, CONFIRMED_KEY$49, DISCARD_KEY$14, STEAL_TARGETS_KEY$1, MOVE_SOURCE_PLAYER_KEY$1, MOVE_SOURCE_CARD_KEY$1, MOVE_DEST_PLAYER_KEY$1, SKIPPABLE_PHASES$1, 巧变_default; var init_巧变 = __esmMin((() => { init_apply(); init_frame(); init_skill(); init_skip_phase(); init_rng(); CONFIRM_RT$61 = "巧变/confirm"; DISCARD_RT$22 = "巧变/discard"; STEAL_TARGETS_RT$1 = "巧变/steal-targets"; MOVE_SOURCE_PLAYER_RT$1 = "巧变/move-source-player"; MOVE_SOURCE_CARD_RT$1 = "巧变/move-source-card"; MOVE_DEST_PLAYER_RT$1 = "巧变/move-dest-player"; CONFIRMED_KEY$49 = "巧变/confirmed"; DISCARD_KEY$14 = "巧变/discardCardId"; STEAL_TARGETS_KEY$1 = "巧变/stealTargets"; MOVE_SOURCE_PLAYER_KEY$1 = "巧变/moveSourcePlayer"; MOVE_SOURCE_CARD_KEY$1 = "巧变/moveSourceCardId"; MOVE_DEST_PLAYER_KEY$1 = "巧变/moveDestPlayer"; SKIPPABLE_PHASES$1 = [ "判定", "摸牌", "出牌", "弃牌" ]; 巧变_default = { createSkill: createSkill$255, onInit: onInit$255, onMount: onMount$231 }; })); //#endregion //#region src/engine/skills/巨象.ts var 巨象_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$254, default: () => 巨象_default, onInit: () => onInit$254, onMount: () => onMount$230 }); function createSkill$254(id, ownerId) { return { id, ownerId, name: "巨象", description: "锁定技:南蛮入侵对你无效;其他角色使用的南蛮入侵结算后进入弃牌堆时,你获得它", isLocked: true }; } function onInit$254(skill, state) { const ownerId = skill.ownerId; registerBeforeHook(state, skill.id, ownerId, "询问杀", async (ctx) => { if (ctx.atom.target !== ownerId) return; if (topFrame(ctx.state)?.skillId !== "南蛮入侵") return; return { kind: "cancel" }; }); registerBeforeHook(state, skill.id, ownerId, "受到伤害时", async (ctx) => { const atom = ctx.atom; if (atom.target !== ownerId) return; if (!atom.cardId) return; if (ctx.state.cardMap[atom.cardId]?.name !== "南蛮入侵") return; return { kind: "cancel" }; }); registerAfterHook(state, skill.id, ownerId, "移动牌", async (ctx) => { const atom = ctx.atom; if (!atom.cardId) return; if (atom.from?.zone !== "处理区") return; if (atom.to?.zone !== "弃牌堆") return; if (ctx.state.cardMap[atom.cardId]?.name !== "南蛮入侵") return; const frame = topFrame(ctx.state); if (frame?.skillId !== "南蛮入侵") return; if (frame.from === ownerId) return; if (ctx.state.players[ownerId]?.alive !== true) return; await applyAtom(ctx.state, { type: "移动牌", cardId: atom.cardId, from: { zone: "弃牌堆" }, to: { zone: "手牌", player: ownerId } }); }); return () => {}; } function onMount$230(_skill, _api) {} var 巨象_default; var init_巨象 = __esmMin((() => { init_apply(); init_frame(); init_skill(); 巨象_default = { createSkill: createSkill$254, onInit: onInit$254, onMount: onMount$230 }; })); //#endregion //#region src/engine/core/card-invalidation.ts /** 从 cardId 解析 Card(cardId 无效或不在 cardMap 时返回 undefined)。 */ function cardOf(state, cardId) { if (!cardId) return void 0; return state.cardMap[cardId]; } /** 从顶帧 params.cardId 解析 Card(询问杀/获得/弃置/设横置 的触发牌由结算帧携带)。 */ function frameCardOf(state) { const cardId = topFrame(state)?.params?.cardId; return cardOf(state, typeof cardId === "string" ? cardId : void 0); } /** * 注册牌无效化:谓词返回 true 时在所有 7 个拦截点 cancel。返回 unloader。 * * @param state GameState * @param skillId 技能实例 id(hook 归属) * @param ownerId 受保护角色座次 * @param predicate 无效化谓词 */ function registerCardInvalidation(state, skillId, ownerId, predicate) { const unloaders = []; unloaders.push(registerBeforeHook(state, skillId, ownerId, "成为目标", async (ctx) => { const atom = ctx.atom; if (atom.target !== ownerId) return; if (predicate(ctx.state, cardOf(ctx.state, atom.cardId), ownerId)) return { kind: "cancel" }; })); unloaders.push(registerBeforeHook(state, skillId, ownerId, "检测有效性", async (ctx) => { const atom = ctx.atom; if (atom.target !== ownerId) return; if (predicate(ctx.state, cardOf(ctx.state, atom.cardId), ownerId)) return { kind: "cancel" }; })); unloaders.push(registerBeforeHook(state, skillId, ownerId, "询问杀", async (ctx) => { if (ctx.atom.target !== ownerId) return; if (predicate(ctx.state, frameCardOf(ctx.state), ownerId)) return { kind: "cancel" }; })); unloaders.push(registerBeforeHook(state, skillId, ownerId, "受到伤害时", async (ctx) => { const atom = ctx.atom; if (atom.target !== ownerId) return; if (predicate(ctx.state, cardOf(ctx.state, atom.cardId), ownerId)) return { kind: "cancel" }; })); unloaders.push(registerBeforeHook(state, skillId, ownerId, "获得", async (ctx) => { const atom = ctx.atom; if (atom.from !== ownerId) return; if (atom.player === ownerId) return; if (predicate(ctx.state, frameCardOf(ctx.state), ownerId)) return { kind: "cancel" }; })); unloaders.push(registerBeforeHook(state, skillId, ownerId, "弃置", async (ctx) => { if (ctx.atom.player !== ownerId) return; if (predicate(ctx.state, frameCardOf(ctx.state), ownerId)) return { kind: "cancel" }; })); unloaders.push(registerBeforeHook(state, skillId, ownerId, "设横置", async (ctx) => { if (ctx.atom.player !== ownerId) return; if (predicate(ctx.state, frameCardOf(ctx.state), ownerId)) return { kind: "cancel" }; })); return () => { for (const u of unloaders) u(); }; } var init_card_invalidation = __esmMin((() => { init_frame(); init_skill(); })); //#endregion //#region src/engine/skills/帷幕.ts var 帷幕_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$253, default: () => 帷幕_default, onInit: () => onInit$253 }); function createSkill$253(id, ownerId) { return { id, ownerId, name: "帷幕", description: "锁定技:你不能成为黑色锦囊的目标", isLocked: true }; } /** 判定一张卡是否为黑色锦囊牌 */ function isBlackTrick$1(card) { return !!card && card.type === "锦囊牌" && card.color === "黑"; } function onInit$253(skill, state) { const ownerId = skill.ownerId; return registerCardInvalidation(state, skill.id, ownerId, (_st, card) => isBlackTrick$1(card)); } var 帷幕_default; var init_帷幕 = __esmMin((() => { init_card_invalidation(); 帷幕_default = { createSkill: createSkill$253, onInit: onInit$253 }; })); //#endregion //#region src/engine/skills/庸肆.ts var 庸肆_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$252, default: () => 庸肆_default, onInit: () => onInit$252, onMount: () => onMount$229 }); /** 全场存活玩家的不同势力数(X)。袁术本人为群,故下限 1。 */ function aliveFactionCount(state) { const factions = /* @__PURE__ */ new Set(); for (const p of state.players) if (p.alive && p.faction) factions.add(p.faction); return Math.max(1, factions.size); } function createSkill$252(id, ownerId) { return { id, ownerId, name: "庸肆", description: "锁定技,摸牌阶段你多摸X张牌;弃牌阶段开始时你弃置X张牌(X为全场势力数)", isLocked: true }; } function onInit$252(skill, state) { const ownerId = skill.ownerId; const unloadDraw = registerBeforeHook(state, skill.id, ownerId, "摸牌", async (ctx) => { const atom = ctx.atom; if (atom.player !== ownerId) return; if (ctx.state.currentPlayerIndex !== ownerId) return; if (ctx.state.phase !== "摸牌") return; if (!ctx.state.players[ownerId]?.alive) return; const X = aliveFactionCount(ctx.state); const count = atom.count ?? 2; return { kind: "modify", atom: { ...ctx.atom, count: count + X } }; }); const unloadDiscardHook = registerAfterHook(state, skill.id, ownerId, "阶段开始", async (ctx) => { const atom = ctx.atom; if (atom.type !== "阶段开始") return; if (atom.phase !== "弃牌") return; if (atom.player !== ownerId) return; const st = ctx.state; const self = st.players[ownerId]; if (!self?.alive) return; const X = aliveFactionCount(st); const toDiscard = Math.min(X, self.hand.length); if (toDiscard <= 0) return; delete st.localVars[DISCARD_CHOICE]; st.localVars[DISCARD_COUNT] = toDiscard; await applyAtom(st, { type: "请求回应", requestType: DISCARD_RT$21, target: ownerId, prompt: { type: "useCard", title: `庸肆:弃置 ${toDiscard} 张牌`, cardFilter: { filter: () => true, min: toDiscard, max: toDiscard } }, mandatory: true, timeout: 30 }); const me = st.players[ownerId]; const chosen = st.localVars[DISCARD_CHOICE]; delete st.localVars[DISCARD_CHOICE]; delete st.localVars[DISCARD_COUNT]; await applyAtom(st, { type: "弃置", player: ownerId, cardIds: Array.isArray(chosen) && chosen.length === toDiscard && chosen.every((id) => me.hand.includes(id)) ? chosen : me.hand.slice(0, toDiscard) }); }); const unloadRespond = registerAction(state, skill.id, ownerId, "respond", (st, params) => { const slot = st.pendingSlots.get(ownerId); if (!slot) return "当前不需要回应"; if (slot.atom.type !== "请求回应") return "当前不是庸肆弃牌窗口"; const atom = slot.atom; if (atom.requestType !== DISCARD_RT$21) return "当前不是庸肆弃牌窗口"; if (atom.target !== ownerId) return "不是你的庸肆弃牌"; const cardIds = params.cardIds; if (!Array.isArray(cardIds)) return "cardIds required"; const need = st.localVars[DISCARD_COUNT]; if (typeof need === "number" && cardIds.length !== need) return `需弃置 ${need} 张牌`; const player = st.players[ownerId]; if (!player) return "target not found"; for (const id of cardIds) if (typeof id !== "string" || !player.hand.includes(id)) return `card ${id} not in hand`; return null; }, async (st, params) => { st.localVars[DISCARD_CHOICE] = params.cardIds; }); return () => { unloadDraw(); unloadDiscardHook(); unloadRespond(); }; } function onMount$229(_skill, api) { api.defineAction("respond", { label: "庸肆", style: "primary", prompt: { type: "useCard", title: "庸肆:选择弃置的牌", cardFilter: { filter: () => true, min: 1, max: 99 } }, activeWhen: (ctx) => { const slot = ctx.view.pending; if (!slot) return false; if (slot.target !== ctx.perspectiveIdx) return false; const atom = slot.atom; return atom.type === "请求回应" && atom.requestType === DISCARD_RT$21; } }); } var DISCARD_RT$21, DISCARD_CHOICE, DISCARD_COUNT, 庸肆_default; var init_庸肆 = __esmMin((() => { init_apply(); init_skill(); DISCARD_RT$21 = "庸肆/弃牌"; DISCARD_CHOICE = "庸肆/弃牌选择"; DISCARD_COUNT = "庸肆/弃牌数"; 庸肆_default = { createSkill: createSkill$252, onInit: onInit$252, onMount: onMount$229 }; })); //#endregion //#region src/engine/flows/death.ts /** 死亡结算编排函数——对齐 death.md 5 时机。 * * 时机1 亮身份牌前:焚心(转移身份) * 时机2 亮身份牌:揭示身份(view 层;所有视角可见) * 时机3 死亡时:行殇(摸牌)/断肠(移除凶手技能)/界节命(临终摸弃)——系统处理牌之前 * 时机4 系统处理牌:弃手牌+装备入弃牌堆 + alive=false(原 击杀.apply 逻辑) * 奖惩:反贼死→凶手摸3;忠臣被主公杀→主公弃所有牌 * 时机5 死亡后:功獒(摸牌)/界完杀 cleanup * * killer 为致死来源(伤害路径透传;体力致死/自杀为 undefined)。 */ async function runDeathFlow(state, player, killer) { await applyAtom(state, { type: "亮身份牌前", player }); await applyAtom(state, { type: "亮身份牌", player }); await applyAtom(state, { type: "死亡时", player, killer }); await applyAtom(state, { type: "系统处理牌", player }); await applyDeathPenalty(state, player, killer); await applyAtom(state, { type: "死亡后", player, killer }); } /** 死亡奖惩——搬自系统规则的击杀 after-hook。 * 反贼被杀:凶手摸3张;忠臣被主公杀:主公弃所有牌。无来源/自杀/凶手已亡→无奖惩。 */ async function applyDeathPenalty(state, deadIdx, killer) { if (killer === void 0) return; if (killer === deadIdx) return; const killerPlayer = state.players[killer]; if (!killerPlayer?.alive) return; const dead = state.players[deadIdx]; if (dead.identity === "反贼") await applyAtom(state, { type: "摸牌", player: killer, count: 3 }); else if (dead.identity === "忠臣" && killerPlayer.identity === "主公") { const allCards = [...killerPlayer.hand, ...Object.values(killerPlayer.equipment).filter(Boolean)]; if (allCards.length > 0) await applyAtom(state, { type: "弃置", player: killer, cardIds: allCards }); } } var init_death = __esmMin((() => { init_apply(); })); //#endregion //#region src/engine/skills/系统规则.ts var 系统规则_exports = /* @__PURE__ */ __exportAll({ canRescueWith: () => canRescueWith, createSkill: () => createSkill$251, onInit: () => onInit$251, registerSystemRespondActions: () => registerSystemRespondActions }); function createSkill$251(id, ownerId) { return { id, ownerId, name: "系统规则", description: "引擎级规则(判定清理/技能生命周期/濒死)" }; } function registerSystemRespondActions(state, ownerId) { const unloads = []; unloads.push(registerAction(state, "系统规则", ownerId, "选将", (state, params) => { const slot = state.pendingSlots.get(ownerId); if (!slot) return "当前不需要回应"; if (slot.atom.type !== "选将询问") return "当前不是选将窗口"; if (slot.atom.target !== ownerId) return "不是你的选将回合"; const selfPlayer = state.players[ownerId]; if (!selfPlayer) return `player ${ownerId} not found`; if (selfPlayer.character) return "已选择武将,不能重新选将"; const character = params.character; if (typeof character !== "string") return "character required"; if (!slot.atom.candidates.some((c) => c.name === character)) return "选择的武将不在候选人中"; if (state.players.some((p) => p.index !== ownerId && p.character === character)) return "该武将已被其他玩家选择"; return null; }, async (state, params) => { const slot = state.pendingSlots.get(ownerId); const target = slot.atom.target; const character = params.character; const selected = slot.atom.candidates.find((c) => c.name === character); await applyAtom(state, { type: "分配武将", target, character: selected.name, skills: [...DEFAULT_SKILLS, ...selected.skills] }); })); unloads.push(registerAction(state, "系统规则", ownerId, "respond", (state, params) => { const slot = state.pendingSlots.get(ownerId); if (!slot) return "当前不需要回应"; if (slot.atom.type !== "请求回应") return "当前不是弃牌窗口"; const atom = slot.atom; if (atom.requestType !== "__弃牌") return "当前不是弃牌窗口"; if (atom.target !== ownerId) return "不是你的弃牌回合"; const cardIds = params.cardIds; if (!Array.isArray(cardIds)) return "cardIds required"; if (cardIds.length === 0) return "不能弃 0 张牌"; const player = state.players[atom.target]; if (!player) return "target not found"; for (const id of cardIds) if (typeof id !== "string" || !player.hand.includes(id)) return `card ${id} not in hand`; return null; }, async (state, params) => { const target = state.pendingSlots.get(ownerId).atom.target; const cardIds = params.cardIds; await applyAtom(state, { type: "弃置", player: target, cardIds }); })); return () => unloads.forEach((fn) => fn()); } function onInit$251(_skill, state) { registerAfterHook(state, "系统规则", -1, "添加技能", async (ctx) => { const atom = ctx.atom; await instantiateSkill(ctx.state, atom.skillId, atom.player); }); registerAfterHook(state, "系统规则", -1, "移除技能", async (ctx) => { const atom = ctx.atom; unloadSkillInstance(ctx.state, atom.skillId, atom.player); }); registerAfterHook(state, "系统规则", -1, "弃置", async (ctx) => { const atom = ctx.atom; const player = ctx.state.players[atom.player]; if (!player) return; for (const cardId of atom.cardIds) { const card = ctx.state.cardMap[cardId]; if (card?.type === "装备牌" && card?.name && hasSkillModule(card.name) && player.skills.includes(card.name)) await applyAtom(ctx.state, { type: "移除技能", player: atom.player, skillId: card.name }); } }); registerAfterHook(state, "系统规则", -1, "获得", async (ctx) => { const atom = ctx.atom; if (atom.from === void 0) return; const fromPlayer = ctx.state.players[atom.from]; if (!fromPlayer) return; const card = ctx.state.cardMap[atom.cardId]; if (card?.type === "装备牌" && card?.name && hasSkillModule(card.name) && fromPlayer.skills.includes(card.name)) await applyAtom(ctx.state, { type: "移除技能", player: atom.from, skillId: card.name }); }); registerAfterHook(state, "系统规则", -1, "扣减体力", async (ctx) => { const atom = ctx.atom; if (typeof atom.target !== "number") return; const target = ctx.state.players[atom.target]; if (target && target.alive && target.health <= 0) await runDyingFlow(ctx.state, atom.target); }); registerAfterHook(state, "系统规则", -1, "失去体力", async (ctx) => { const atom = ctx.atom; if (typeof atom.target !== "number") return; const target = ctx.state.players[atom.target]; if (target && target.alive && target.health <= 0) { delete ctx.state.localVars["死亡/killer"]; await runDyingFlow(ctx.state, atom.target); } }); registerAfterHook(state, "系统规则", -1, "设上限", async (ctx) => { const atom = ctx.atom; if (typeof atom.player !== "number") return; const target = ctx.state.players[atom.player]; if (target && target.alive && target.maxHealth <= 0) { delete ctx.state.localVars["死亡/killer"]; await runDeathFlow(ctx.state, atom.player); } }); return () => {}; } /** 求桃救援牌判定(字面牌):桃任何人可用;酒仅自救(dyingIdx===playerIdx)。 * * 仅判定手牌中的字面 桃/酒。转化型救援技能(急救/蛊惑/界龙胆 等)不在此判定—— * 它们注册独立的 respond/rescue action,由 hasAlternativeResponse 的 RESCUE_ALTERNATIVE_SKILLS * 在 preResolve 阶段门控(确保持转化牌的玩家也能看到求桃窗口,不被误判 silent/skip)。 * * engine 侧 cardFilter;前端通过 respondFor='桃/求桃' 的 respond action 重建同等语义。 */ function canRescueWith(state, playerIdx, dyingIdx) { const isSelf = playerIdx === dyingIdx; return (card) => { if (card.name === "桃") return true; if (card.name === "酒" && isSelf) return true; return false; }; } /** * 濒死求桃流程(模块 C 修正,对齐 neardeath.md): * 1. applyAtom(陷入濒死)——系统通知,触发不屈/涅槃/伏枥/仁心等救援技 after-hook。 * 2. 不屈存活检查——命中则濒死已化解,跳过求桃与死亡。 * 3. applyAtom(进入濒死状态时)——补益/随势① 独立时机(先于求桃,可能直接回血化解)。 * 4. 求桃循环:从当前回合角色起,逆时针询问每个存活玩家;被救仍濒死则发 * 新的濒死状态时 并从当前响应者重新逆时针。 * 5. 仍濒死 → runDeathFlow(target, killer)。 * * 关键修正(对比旧实现): * - 起点:从濒死玩家 → 当前回合角色(state.currentPlayerIndex)。 * - 方向:顺时针(targetIdx+i) → 逆时针(startIdx-i+n)。 * - 新增时机:进入濒死状态时(补益/随势①)、新的濒死状态时(被救仍濒死重置)。 */ async function runDyingFlow(state, targetIdx) { await applyAtom(state, { type: "陷入濒死", target: targetIdx }); if (state.localVars["不屈/存活"] === targetIdx) { delete state.localVars["不屈/存活"]; return; } await applyAtom(state, { type: "进入濒死状态时", target: targetIdx }); if (state.players[targetIdx].health > 0) return; const n = state.players.length; let startIdx = state.currentPlayerIndex; let asked = /* @__PURE__ */ new Set(); while (state.players[targetIdx].health <= 0) { let found = false; for (let i = 0; i < n; i++) { const playerIdx = (startIdx - i + n) % n; if (asked.has(playerIdx)) continue; if (!state.players[playerIdx].alive) continue; asked.add(playerIdx); found = true; await applyAtom(state, { type: "请求回应", requestType: "桃/求桃", target: playerIdx, prompt: { type: "useCard", title: `${state.players[targetIdx].name} 濒死,使用桃/酒救援`, cardFilter: { filter: canRescueWith(state, playerIdx, targetIdx), min: 1, max: 1 } }, timeout: 15 }); if (state.localVars["求桃/已救"]) { await applyAtom(state, { type: "回复体力", target: targetIdx, amount: 1, source: playerIdx }); delete state.localVars["求桃/已救"]; if (state.players[targetIdx].health > 0) return; await applyAtom(state, { type: "新的濒死状态时", target: targetIdx }); startIdx = playerIdx; asked = /* @__PURE__ */ new Set(); asked.add(playerIdx); break; } break; } if (!found) break; } if (state.players[targetIdx].health <= 0) { const killer = state.localVars["死亡/killer"]; delete state.localVars["死亡/killer"]; await runDeathFlow(state, targetIdx, killer); } } var init_系统规则 = __esmMin((() => { init_apply(); init_skill(); init_lifecycle(); init_选将(); init_registry$1(); init_death(); })); //#endregion //#region src/engine/rules/身份局.ts var 身份局_exports = /* @__PURE__ */ __exportAll({ checkIdentityGameOver: () => checkIdentityGameOver, default: () => ruleset$1 }); /** 身份局胜负判定(纯函数)。 * 结束条件:主公死亡,或主公存活但所有反贼/内奸均已死亡,或存活 ≤ 1 人。 * 胜方判定:winner 为某阵营代表座次,前端按其 identity 推导获胜阵营文案。 * - 主公阵亡:反贼仍存活 → 反贼胜;反贼全灭且内奸存活(内奸清场单挑残局)→ 内奸胜; * 极端(反贼/内奸均无存活)→ 仍判反贼胜。 * - 主公存活但反贼/内奸全灭:主公方(主忠)胜,winner=主公座次。 * - 仅剩一人存活:winner=存活者(主公→主公方,反贼→反贼,内奸→内奸)。 * 1v1(2 人身份局)是本逻辑的特化:主公死=反贼胜,反贼死=主公胜。 */ function checkIdentityGameOver(state) { const lord = state.players.find((p) => p.identity === "主公"); if (lord && !lord.alive) { const aliveRebel = state.players.find((p) => p.alive && p.identity === "反贼"); if (aliveRebel) return { gameOver: true, winner: aliveRebel.index }; const aliveRenegade = state.players.find((p) => p.alive && p.identity === "内奸"); if (aliveRenegade) return { gameOver: true, winner: aliveRenegade.index }; return { gameOver: true, winner: state.players.find((p) => p.identity === "反贼")?.index }; } if (lord?.alive) { if (!state.players.find((p) => p.alive && (p.identity === "反贼" || p.identity === "内奸"))) return { gameOver: true, winner: lord.index }; } if (state.players.filter((p) => p.alive).length <= 1) return { gameOver: true, winner: state.players.find((p) => p.alive)?.index }; return { gameOver: false }; } var ruleset$1; var init_身份局 = __esmMin((() => { init_系统规则(); ruleset$1 = { mode: "身份局", onInit(state) { onInit$251(createSkill$251("系统规则", -1), state); for (const player of state.players) registerSystemRespondActions(state, player.index); return () => {}; }, checkGameOver: checkIdentityGameOver, opening: { candidatesPerIdentity: { 主公: 7, 忠臣: 5, 反贼: 4, 内奸: 5 }, lordPickEnabled: true } }; })); //#endregion //#region src/engine/rules/1v1.ts var _1v1_exports = /* @__PURE__ */ __exportAll({ default: () => ruleset }); var ruleset; var init__1v1 = __esmMin((() => { init_身份局(); ruleset = { mode: "1v1", onInit(state) { return ruleset$1.onInit(state); }, checkGameOver: checkIdentityGameOver, opening: { candidatesPerIdentity: { 主公: 5, 反贼: 5, 忠臣: 5, 内奸: 5 }, lordPickEnabled: false } }; })); //#endregion //#region src/engine/rules/registry.ts /** 加载指定模式的规则包。未知模式(旧快照脏数据/未实现)抛错,调用方以默认模式兜底。 */ function loadRuleset(mode) { const loader = RULESET_LOADERS[mode]; if (!loader) throw new Error(`不支持的游戏模式: ${String(mode)}`); return loader(); } var RULESET_LOADERS; var init_registry = __esmMin((() => { RULESET_LOADERS = { 身份局: async () => (await Promise.resolve().then(() => (init_身份局(), 身份局_exports))).default, "1v1": async () => (await Promise.resolve().then(() => (init__1v1(), _1v1_exports))).default }; })); //#endregion //#region src/engine/skills/开局.ts var 开局_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$250, onInit: () => onInit$250 }); /** 把扁平武将列表按 baseId 分组。 * 打乱后的顺序保留——组内版本按打乱顺序排列,组间顺序由首次出现决定。 */ function groupByBaseId(pool) { const map = /* @__PURE__ */ new Map(); for (const c of pool) { const baseId = getCharacterBaseId(c.name); let g = map.get(baseId); if (!g) { g = { baseId, versions: [] }; map.set(baseId, g); } g.versions.push(c); } return [...map.values()]; } /** 把武将组列表展开为扁平候选列表(带 baseId 字段供前端分组)。 */ function flattenGroups(groups) { return groups.flatMap((g) => g.versions.map((v) => ({ ...v, baseId: g.baseId }))); } /** 从已分组的 charGroups 中拆分主公候选组:常备 5 + 非常备 2(总 7 组)。 * - charGroups 需为 seed 打乱后的顺序,取前 N 即随机抽取。 * - 常备判断基于 baseId(界版主公也正确识别)。 * - 兑底:常备 < 5 时,用非常备补足到 7;总数仍不足则给现有全部。 */ function pickLordCandidateGroups(groups) { const target = 7; const lordPicked = []; const nonLordPicked = []; for (const g of groups) { if (lordPicked.length >= CANDIDATES_LORD && nonLordPicked.length >= CANDIDATES_NON_LORD) break; if (g.versions.some((v) => isLord(v.name))) { if (lordPicked.length < CANDIDATES_LORD) lordPicked.push(g); } else if (nonLordPicked.length < CANDIDATES_NON_LORD) nonLordPicked.push(g); } const result = [...lordPicked, ...nonLordPicked]; if (result.length < target) { const used = new Set(result.map((g) => g.baseId)); for (const g of groups) { if (result.length >= target) break; if (used.has(g.baseId)) continue; used.add(g.baseId); result.push(g); } } return result; } function createSkill$250(id, ownerId) { return { id, ownerId, name: "开局", description: "游戏开局:抽身份、选将、洗牌、发牌、启动第一回合" }; } function onInit$250(_skill, state) { registerActionEntry(state, { skillId: "开局", ownerId: SYSTEM_OWNER, actionType: "start", validate: (state, params) => { if (params.playerCount < 2) return "至少需要2名玩家"; return null; }, execute: async (state, params) => { const { characters, playerCount, seed, handSize = 4 } = params; await applyAtom(state, { type: "抽身份", playerCount, seed }); const { candidatesPerIdentity, lordPickEnabled } = (await loadRuleset(resolveGameMode(state))).opening; const candidatesFor = (identity) => candidatesPerIdentity[identity ?? ""] ?? candidatesPerIdentity["反贼"] ?? 4; const charRng = createRng(seed + 1); const charPool = [...characters].filter((c) => c.name !== "主公"); for (let i = charPool.length - 1; i > 0; i--) { const j = charRng.nextInt(i + 1); const tmp = charPool[i]; charPool[i] = charPool[j]; charPool[j] = tmp; } const charGroups = groupByBaseId(charPool); const lordIdx = state.players.findIndex((p) => p.identity === "主公"); const used = /* @__PURE__ */ new Set(); state.charSelecting = true; if (lordPickEnabled && lordIdx >= 0) { const lordAvail = pickLordCandidateGroups(charGroups); if (lordAvail.length > 0) { await applyAtom(state, { type: "选将询问", target: lordIdx, candidates: flattenGroups(lordAvail) }); const lordChosen = state.players[lordIdx].character; if (lordChosen) used.add(getCharacterBaseId(lordChosen)); } } const pickers = lordPickEnabled ? state.players.map((_, i) => i).filter((i) => i !== lordIdx) : state.players.map((_, i) => i); if (pickers.length > 0) { const candidatePool = charGroups.filter((g) => !used.has(g.baseId)); const wantByPlayer = pickers.map((idx) => candidatesFor(state.players[idx].identity)); const totalWant = wantByPlayer.reduce((a, b) => a + b, 0); if (candidatePool.length < totalWant) throw new Error(`武将池不足: 需要 ${totalWant} 个候选组提供给玩家,当前池中只有 ${candidatePool.length} 个`); const selections = []; let cursor = 0; for (let k = 0; k < pickers.length; k++) { const want = wantByPlayer[k]; const playerGroups = candidatePool.slice(cursor, cursor + want); cursor += want; selections.push({ target: pickers[k], candidates: flattenGroups(playerGroups) }); } if (selections.length > 0) await applyAtom(state, { type: "并行选将", selections }); } state.charSelecting = false; await applyAtom(state, { type: "亮将", assignments: state.players.filter((p) => p.character).map((p) => ({ target: p.index, character: p.character, faction: p.faction, maxHealth: p.maxHealth, health: p.health, skills: [...p.skills] })) }); for (const player of state.players) for (const skillId of player.skills) await instantiateSkill(state, skillId, player.index); await applyAtom(state, { type: "初始化洗牌", seed }); await applyAtom(state, { type: "发牌", handSize }); const lord = state.players.find((p) => p.identity === "主公"); if (lord) { await applyAtom(state, { type: "回合开始", player: lord.index }); await applyAtom(state, { type: "阶段开始", player: lord.index, phase: "准备" }); await applyAtom(state, { type: "阶段结束", player: lord.index, phase: "准备" }); } } }); return () => unregisterActionEntry(state, "开局", SYSTEM_OWNER, "start"); } var SYSTEM_OWNER, CANDIDATES_LORD, CANDIDATES_NON_LORD; var init_开局 = __esmMin((() => { init_apply(); init_rng(); init_skill(); init_lifecycle(); init_character_meta(); init_registry(); init_core(); SYSTEM_OWNER = -1; CANDIDATES_LORD = 5; CANDIDATES_NON_LORD = 2; })); //#endregion //#region src/engine/skills/强袭.ts var 强袭_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$249, default: () => _skillModule$117, onInit: () => onInit$249, onMount: () => onMount$228 }); function createSkill$249(id, ownerId) { return { id, ownerId, name: SKILL_ID$56, description: "出牌阶段限两次:失去1点体力或弃一张武器牌,对一名本回合未以此法指定过的其他角色造成1点伤害" }; } /** 是否为武器牌 */ function isWeaponCard$2(card) { return !!card && card.type === "装备牌" && card.subtype === "武器"; } /** 本回合已发动次数(0/1/2)。 */ function usesThisTurn$3(state, ownerId) { const v = state.players[ownerId]?.vars[COUNT_KEY$5]; return typeof v === "number" ? v : 0; } /** 本回合已以此法指定过的目标列表。 */ function usedTargets$1(state, ownerId) { const v = state.players[ownerId]?.vars[TARGETS_KEY$6]; return Array.isArray(v) ? v : []; } function onInit$249(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "use", (st, params) => { if (st.currentPlayerIndex !== ownerId) return "不是你的回合"; if (st.phase !== "出牌") return "不是出牌阶段"; if (hasBlockingPending(st)) return "当前有未回应的询问"; if (usesThisTurn$3(st, ownerId) >= MAX_USES$3) return "本阶段强袭已达上限(2次)"; const self = st.players[ownerId]; if (!self?.alive) return "已死亡"; const cost = params.cost; if (cost !== "hp" && cost !== "discard") return "cost 必须为 hp 或 discard"; const target = params.target; if (typeof target !== "number") return "需要指定目标"; if (target === ownerId) return "不能以自己为目标"; if (!st.players[target]?.alive) return "目标不合法"; if (usedTargets$1(st, ownerId).includes(target)) return "本回合已对此角色发动过强袭"; if (cost === "discard") { const cardId = params.cardId; if (typeof cardId !== "string") return "弃武器需要 cardId"; const card = st.cardMap[cardId]; if (!isWeaponCard$2(card)) return "不是武器牌"; const inHand = self.hand.includes(cardId); const inEquip = self.equipment["武器"] === cardId; if (!inHand && !inEquip) return "武器不在手牌或装备区"; } return null; }, async (st, params) => { const from = ownerId; const target = params.target; const cost = params.cost; const nextCount = usesThisTurn$3(st, from) + 1; st.players[from].vars[COUNT_KEY$5] = nextCount; await applyAtom(st, { type: "回合用量", player: from, key: COUNT_KEY$5, value: nextCount }); const nextTargets = [...usedTargets$1(st, from), target]; st.players[from].vars[TARGETS_KEY$6] = nextTargets; await applyAtom(st, { type: "回合用量", player: from, key: TARGETS_KEY$6, value: nextTargets }); await pushFrame(st, SKILL_ID$56, from, { ...params }); if (cost === "hp") { await applyAtom(st, { type: "失去体力", target: from, amount: 1 }); if (!st.players[from]?.alive) { await popFrame(st); return; } } else { const cardId = params.cardId; if (st.players[from].equipment["武器"] === cardId) await applyAtom(st, { type: "卸下", player: from, slot: "武器" }); await applyAtom(st, { type: "弃置", player: from, cardIds: [cardId], voluntary: true }); } await runDamageFlow(st, from, target, 1); await popFrame(st); }); return () => {}; } function onMount$228(skill, api) { api.defineAction("use", { label: SKILL_ID$56, style: "danger", prompt: { type: "selectTarget", title: "强袭:选择一名本回合未以此法指定过的角色(失去1点体力或弃武器,对其造成1点伤害)", description: "本阶段限两次;每名角色每回合仅能被指定一次", targetFilter: { min: 1, max: 1, filter: (view, target) => { const me = view.currentPlayerIndex; if (target === me) return false; const tp = view.players.find((pl) => pl.index === target); if (!tp || tp.alive === false) return false; const used = view.players[me]?.turnUsage?.[TARGETS_KEY$6]; if ((Array.isArray(used) ? used : []).includes(target)) return false; return true; } }, paramVariants: [{ label: "失去1点体力", params: { cost: "hp" } }, { label: "弃一张武器牌(先选中武器牌)", params: { cost: "discard" }, cardFilter: (c) => isWeaponCard$2(c) }] }, activeWhen: (ctx) => { if (!defaultPlayActive(ctx)) return false; const used = ctx.view.players[ctx.perspectiveIdx]?.turnUsage?.[COUNT_KEY$5]; return (typeof used === "number" ? used : 0) < MAX_USES$3; } }); return () => {}; } var SKILL_ID$56, COUNT_KEY$5, TARGETS_KEY$6, MAX_USES$3, _skillModule$117; var init_强袭 = __esmMin((() => { init_apply(); init_frame(); init_damage(); init_action_active(); init_skill(); SKILL_ID$56 = "强袭"; COUNT_KEY$5 = `${SKILL_ID$56}/usedThisTurn`; TARGETS_KEY$6 = `${SKILL_ID$56}/targets/usedThisTurn`; MAX_USES$3 = 2; _skillModule$117 = { createSkill: createSkill$249, onInit: onInit$249, onMount: onMount$228 }; })); //#endregion //#region src/engine/skills/往烈.ts var 往烈_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$248, default: () => _skillModule$116, onInit: () => onInit$248, onMount: () => onMount$227 }); /** 判断牌是否为基本牌或普通锦囊牌(延时/响应锦囊排除)。 */ function isBasicOrNormalTrick(card) { if (!card) return false; if (card.type === "基本牌") return true; return card.type === "锦囊牌" && card.trickSubtype !== "延时锦囊" && card.trickSubtype !== "响应锦囊"; } function createSkill$248(id, ownerId) { return { id, ownerId, name: "往烈", description: "你出牌阶段使用的首张牌无距离限制;当你于出牌阶段使用基本牌或普通锦囊牌时,你可以令此牌不能被响应,然后你本阶段不能再使用牌" }; } function onInit$248(skill, state) { const ownerId = skill.ownerId; const unloadDistExemptor = registerDistanceExemptor(state, ownerId, (st, from, _to) => { if (from !== ownerId) return false; if (st.currentPlayerIndex !== ownerId) return false; if (st.phase !== "出牌") return false; if (st.turn.vars[FIRST_USED_VAR]) return false; return true; }); const unloadRespond = registerAction(state, skill.id, ownerId, "respond", (st, _params) => { const slot = st.pendingSlots.get(ownerId); if (slot?.atom.type !== "请求回应") return "当前不需要回应"; if (slot.atom.requestType !== CHOOSE_RT$17) return "当前不是往烈选择"; return null; }, async (st, params) => { st.localVars[CHOICE_KEY$16] = params.choice === true; }); registerAfterHook(state, skill.id, ownerId, "阶段开始", async (ctx) => { const atom = ctx.atom; if (atom.type !== "阶段开始") return; if (atom.phase !== "出牌") return; if (atom.player !== ownerId) return; if (ctx.state.turn.vars[FIRST_USED_VAR]) return; await applyAtom(ctx.state, { type: "回合用量", player: ownerId, key: FIRST_VIEW_VAR, value: true }); }); registerAfterHook(state, skill.id, ownerId, "使用时", async (ctx) => { const atom = ctx.atom; if (atom.type !== "使用时") return; if (atom.source !== ownerId) return; if (ctx.state.currentPlayerIndex !== ownerId) return; if (ctx.state.phase !== "出牌") return; ctx.state.turn.vars[FIRST_USED_VAR] = true; await applyAtom(ctx.state, { type: "回合用量", player: ownerId, key: FIRST_VIEW_VAR, value: false }); const card = ctx.state.cardMap[atom.cardId]; if (!isBasicOrNormalTrick(card)) return; delete ctx.state.localVars[CHOICE_KEY$16]; await applyAtom(ctx.state, { type: "请求回应", requestType: CHOOSE_RT$17, target: ownerId, prompt: { type: "confirm", title: "是否发动往烈,令此牌不能被响应,然后本阶段不能再使用牌?", confirmLabel: "发动", cancelLabel: "不发动" }, defaultChoice: false, timeout: 15 }); if (ctx.state.localVars[CHOICE_KEY$16] === true) { ctx.state.localVars[UNRESPONDABLE_VAR] = atom.cardId; ctx.state.turn.vars[BAN_VAR] = ownerId; } delete ctx.state.localVars[CHOICE_KEY$16]; }); const unloadBanDodge = registerBeforeHook(state, skill.id, ownerId, "询问闪", async (ctx) => { const cardId = ctx.params?.cardId; if (!cardId) return; if (ctx.state.localVars[UNRESPONDABLE_VAR] !== cardId) return; return { kind: "cancel" }; }); const unloadBanNullify = registerBeforeHook(state, skill.id, ownerId, "请求回应", async (ctx) => { if (ctx.atom.requestType !== "无懈可击") return; const cardId = ctx.params?.cardId; if (!cardId) return; if (ctx.state.localVars[UNRESPONDABLE_VAR] !== cardId) return; return { kind: "cancel" }; }); registerAfterHook(state, skill.id, ownerId, "使用结算结束后", async (ctx) => { const atom = ctx.atom; if (atom.type !== "使用结算结束后") return; if (atom.source !== ownerId) return; if (ctx.state.localVars[UNRESPONDABLE_VAR] === atom.cardId) delete ctx.state.localVars[UNRESPONDABLE_VAR]; }); return () => { unloadDistExemptor(); unloadRespond(); unloadBanDodge(); unloadBanNullify(); }; } function onMount$227(_skill, api) { api.defineAction("respond", { label: "往烈", style: "default", prompt: { type: "confirm", title: "是否发动往烈,令此牌不能被响应,然后本阶段不能再使用牌?", confirmLabel: "发动", cancelLabel: "不发动" } }); } var FIRST_USED_VAR, FIRST_VIEW_VAR, BAN_VAR, UNRESPONDABLE_VAR, CHOOSE_RT$17, CHOICE_KEY$16, _skillModule$116; var init_往烈 = __esmMin((() => { init_apply(); init_skill(); init_distance(); init_vars_keys(); FIRST_USED_VAR = "往烈/首张已用"; FIRST_VIEW_VAR = WANGLIE_FIRST_VIEW_KEY; BAN_VAR = "往烈/禁出牌"; UNRESPONDABLE_VAR = "往烈/不可响应"; CHOOSE_RT$17 = "往烈/choose"; CHOICE_KEY$16 = "往烈/choice"; _skillModule$116 = { createSkill: createSkill$248, onInit: onInit$248, onMount: onMount$227 }; })); //#endregion //#region src/engine/skills/征荣.ts var 征荣_exports = /* @__PURE__ */ __exportAll({ RONG_PREFIX: () => RONG_PREFIX, createSkill: () => createSkill$247, default: () => _skillModule$115, onInit: () => onInit$247, onMount: () => onMount$226, rongCount: () => rongCount }); function createSkill$247(id, ownerId) { return { id, ownerId, name: "征荣", description: "使用杀或伤害锦囊牌时,可将一名手牌数不小于你的目标角色的一张牌置于武将牌上为\"荣\"" }; } /** 数玩家当前的荣数量(鸿举 复用) */ function rongCount(state, player) { return state.players[player].marks.filter((m) => m.id.startsWith(RONG_PREFIX)).length; } /** 目标是否有可取的牌(手牌或装备) */ function hasTakeableCard(state, player) { const p = state.players[player]; return p.hand.length > 0 || Object.keys(p.equipment).length > 0; } function onInit$247(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "respond", (st, params) => { const slot = st.pendingSlots.get(ownerId); if (!slot) return "当前不需要回应"; const atom = slot.atom; if (atom.type !== "请求回应") return "当前不需要回应"; const rt = atom.requestType; if (rt === CONFIRM_RT$60) return null; if (rt === PICK_TARGET_RT$1) { if (typeof (params.target ?? params.choice) !== "number") return "target required"; return null; } if (rt === PICK_CARD_RT$2) { const zone = params.zone; if (zone === "equipment") { if (typeof params.cardId !== "string") return "cardId required"; } else if (zone === "hand") { if (typeof params.handIndex !== "number") return "handIndex required"; } else return "zone required (equipment|hand)"; return null; } return "当前不是征荣回应"; }, async (st, params) => { const rt = (st.pendingSlots.get(ownerId)?.atom)?.requestType; if (rt === CONFIRM_RT$60) st.localVars[CONFIRMED_KEY$48] = params.choice === true || params.confirmed === true; else if (rt === PICK_TARGET_RT$1) { const target = params.target ?? params.choice; if (typeof target === "number") st.localVars[TARGET_KEY$33] = target; } else if (rt === PICK_CARD_RT$2) st.localVars[PICK_RESULT_KEY$3] = { zone: params.zone, cardId: params.cardId ?? null, handIndex: params.handIndex ?? null }; }); registerAfterHook(state, skill.id, ownerId, "使用时", async (ctx) => { const atom = ctx.atom; if (atom.type !== "使用时") return; if (atom.source !== ownerId) return; if (!atom.cardId) return; const card = ctx.state.cardMap[atom.cardId]; if (!card) return; if (!(card.name === "杀" || DAMAGE_TRICKS.has(card.name))) return; const self = ctx.state.players[ownerId]; if (!self?.alive) return; const frame = topFrame(ctx.state); const targets = frame?.params.resolvedTargets ?? frame?.params.targets ?? []; const myHand = self.hand.length; const eligible = targets.filter((t) => t !== ownerId && ctx.state.players[t]?.alive && ctx.state.players[t].hand.length >= myHand && hasTakeableCard(ctx.state, t)); if (eligible.length === 0) return; delete ctx.state.localVars[CONFIRMED_KEY$48]; await applyAtom(ctx.state, { type: "请求回应", requestType: CONFIRM_RT$60, target: ownerId, prompt: { type: "confirm", title: "是否发动征荣?(将一名手牌数不小于你的目标的一张牌置为荣)", confirmLabel: "发动", cancelLabel: "不发动" }, defaultChoice: false, timeout: 15 }); if (!ctx.state.localVars[CONFIRMED_KEY$48]) return; let target; if (eligible.length === 1) target = eligible[0]; else { delete ctx.state.localVars[TARGET_KEY$33]; await applyAtom(ctx.state, { type: "请求回应", requestType: PICK_TARGET_RT$1, target: ownerId, prompt: { type: "choosePlayer", title: "征荣:选择一名目标角色", min: 1, max: 1, candidates: [...eligible] }, defaultChoice: eligible[0], timeout: 15 }); const picked = ctx.state.localVars[TARGET_KEY$33]; if (typeof picked !== "number") return; if (!eligible.includes(picked)) return; target = picked; } const targetPlayer = ctx.state.players[target]; if (!targetPlayer?.alive || !hasTakeableCard(ctx.state, target)) return; const handLenBefore = ctx.state.players[ownerId].hand.length; await runPickTargetCardPanel(ctx.state, ownerId, target, targetPlayer, { mode: "obtain", requestType: PICK_CARD_RT$2, title: "征荣:选择该目标的一张牌置为荣", includeJudge: false }); const newCardId = ctx.state.players[ownerId].hand[handLenBefore]; if (!newCardId) return; await applyAtom(ctx.state, { type: "弃置", player: ownerId, cardIds: [newCardId] }); await applyAtom(ctx.state, { type: "加标记", player: ownerId, mark: { id: `${RONG_PREFIX}${ctx.state.seq}`, scope: ownerId, payload: { cardId: newCardId } } }); }); return () => {}; } function onMount$226(_skill, api) { api.defineAction("respond", { label: "征荣", style: "default", prompt: { type: "confirm", title: "是否发动征荣?", confirmLabel: "发动", cancelLabel: "不发动" } }); } var CONFIRM_RT$60, PICK_TARGET_RT$1, PICK_CARD_RT$2, CONFIRMED_KEY$48, TARGET_KEY$33, RONG_PREFIX, DAMAGE_TRICKS, _skillModule$115; var init_征荣 = __esmMin((() => { init_apply(); init_frame(); init_skill(); init_pick_card_panel(); init_vars_keys(); CONFIRM_RT$60 = "征荣/confirm"; PICK_TARGET_RT$1 = "征荣/选目标"; PICK_CARD_RT$2 = "征荣/选牌"; CONFIRMED_KEY$48 = "征荣/confirmed"; TARGET_KEY$33 = "征荣/目标"; RONG_PREFIX = "征荣/荣:"; DAMAGE_TRICKS = new Set([ "南蛮入侵", "万箭齐发", "火攻", "决斗" ]); _skillModule$115 = { createSkill: createSkill$247, onInit: onInit$247, onMount: onMount$226 }; })); //#endregion //#region src/engine/skills/志继.ts var 志继_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$246, default: () => 志继_default, onInit: () => onInit$246, onMount: () => onMount$225 }); function createSkill$246(id, ownerId) { return { id, ownerId, name: "志继", description: "觉醒技:准备阶段且无手牌时,回复1体力或摸2牌,减1体力上限并永久获得\"观星\"" }; } function onInit$246(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "respond", (st, _params) => { const slot = st.pendingSlots.get(ownerId); if (!slot) return "当前不需要回应"; const atom = slot.atom; if (atom["type"] !== "请求回应") return "当前不需要回应"; if (atom["requestType"] !== CHOOSE_RT$16) return "当前不是志继选择"; return null; }, async (st, params) => { st.localVars[CHOICE_KEY$15] = params.choice === true ? "draw" : "heal"; }); registerAfterHook(state, skill.id, ownerId, "阶段开始", async (ctx) => { const atom = ctx.atom; if (atom.type !== "阶段开始") return; if (atom.phase !== "准备") return; if (atom.player !== ownerId) return; if (ctx.state.players[ownerId]?.vars[AWAKENED_KEY$7]) return; const self = ctx.state.players[ownerId]; if (!self?.alive) return; if (self.hand.length > 0) return; ctx.state.players[ownerId].vars[AWAKENED_KEY$7] = true; delete ctx.state.localVars[CHOICE_KEY$15]; await applyAtom(ctx.state, { type: "请求回应", requestType: CHOOSE_RT$16, target: ownerId, prompt: { type: "confirm", title: "志继:摸两张牌,还是回复1点体力?", confirmLabel: "摸两张牌", cancelLabel: "回复1点体力" }, defaultChoice: false, timeout: 30 }); if (ctx.state.localVars[CHOICE_KEY$15] === "draw") await applyAtom(ctx.state, { type: "摸牌", player: ownerId, count: 2 }); else { const cur = ctx.state.players[ownerId].health; const max = ctx.state.players[ownerId].maxHealth; const amount = Math.min(1, Math.max(0, max - cur)); if (amount > 0) await applyAtom(ctx.state, { type: "回复体力", target: ownerId, amount }); } const newMax = ctx.state.players[ownerId].maxHealth - 1; await applyAtom(ctx.state, { type: "设上限", player: ownerId, amount: newMax }); await applyAtom(ctx.state, { type: "添加技能", player: ownerId, skillId: "观星" }); }); return () => {}; } function onMount$225(_skill, api) { api.defineAction("respond", { label: "志继", style: "primary", prompt: { type: "confirm", title: "志继:摸两张牌,还是回复1点体力?", confirmLabel: "摸两张牌", cancelLabel: "回复1点体力" } }); } var CHOOSE_RT$16, CHOICE_KEY$15, AWAKENED_KEY$7, 志继_default; var init_志继 = __esmMin((() => { init_apply(); init_skill(); CHOOSE_RT$16 = "志继/choose"; CHOICE_KEY$15 = "志继/choice"; AWAKENED_KEY$7 = "志继/awakened"; 志继_default = { createSkill: createSkill$246, onInit: onInit$246, onMount: onMount$225 }; })); //#endregion //#region src/engine/skills/怀橘.ts var 怀橘_exports = /* @__PURE__ */ __exportAll({ JU_PREFIX: () => JU_PREFIX, addJu: () => addJu, createSkill: () => createSkill$245, default: () => _skillModule$114, juCount: () => juCount, onInit: () => onInit$245, onMount: () => onMount$224, removeJu: () => removeJu }); /** 数当前玩家的橘标记数 */ function juCount(state, player) { return state.players[player]?.marks.filter((m) => m.id.startsWith(JU_PREFIX)).length ?? 0; } /** 加 1 枚橘 mark(经 加标记 atom,view 自动同步) */ async function addJu(state, player) { await applyAtom(state, { type: "加标记", player, mark: { id: `${JU_PREFIX}${state.seq}`, scope: player } }); } /** 移除 N 枚橘 mark(按 mark 加入顺序依次移除,经 去标记 atom,view 同步) */ async function removeJu(state, player, count = 1) { const marks = state.players[player]?.marks ?? []; const toRemove = []; for (const m of marks) { if (toRemove.length >= count) break; if (m.id.startsWith(JU_PREFIX)) toRemove.push(m.id); } for (const markId of toRemove) await applyAtom(state, { type: "去标记", player, markId }); } function createSkill$245(id, ownerId) { return { id, ownerId, name: SKILL_ID$55, description: "锁定技:游戏开始时获得3枚橘标记;有橘的角色受到伤害时防止伤害并移除1枚橘;有橘的角色摸牌阶段多摸一张牌", isLocked: true }; } function onInit$245(skill, state) { const ownerId = skill.ownerId; registerAfterHook(state, skill.id, ownerId, "回合开始", async (ctx) => { const st = ctx.state; if (!st.players[ownerId]?.alive) return; if (st.localVars[INIT_KEY$4(ownerId)]) return; st.localVars[INIT_KEY$4(ownerId)] = true; await addJu(st, ownerId); await addJu(st, ownerId); await addJu(st, ownerId); }); registerBeforeHook(state, skill.id, ownerId, "受到伤害时", async (ctx) => { const target = ctx.atom.target; if (juCount(ctx.state, target) <= 0) return; await removeJu(ctx.state, target); return { kind: "cancel" }; }); registerBeforeHook(state, skill.id, ownerId, "摸牌", async (ctx) => { const atom = ctx.atom; const player = atom.player; if (ctx.state.currentPlayerIndex !== player) return; if (ctx.state.phase !== "摸牌") return; if (!ctx.state.players[player]?.alive) return; if (juCount(ctx.state, player) <= 0) return; const count = atom.count ?? 2; return { kind: "modify", atom: { ...atom, count: count + 1 } }; }); return () => {}; } function onMount$224(_skill, _api) { return () => {}; } var SKILL_ID$55, JU_PREFIX, INIT_KEY$4, _skillModule$114; var init_怀橘 = __esmMin((() => { init_apply(); init_skill(); SKILL_ID$55 = "怀橘"; JU_PREFIX = `${SKILL_ID$55}/橘:`; INIT_KEY$4 = (ownerId) => `${SKILL_ID$55}/init/${ownerId}`; _skillModule$114 = { createSkill: createSkill$245, onInit: onInit$245, onMount: onMount$224 }; })); //#endregion //#region src/engine/skills/急救.ts var 急救_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$244, onInit: () => onInit$244, onMount: () => onMount$223 }); function createSkill$244(id, ownerId) { return { id, ownerId, name: "急救", description: "在你的回合外,你可以将一张红色牌当【桃】使用。" }; } function onInit$244(skill, state) { const ownerId = skill.ownerId; const unloadDecl = declareAlternativeResponse(state, ownerId, "请求回应", "桃/求桃"); registerAction(state, skill.id, ownerId, "respond", (state, params) => { if (state.currentPlayerIndex === ownerId) return "急救只能在回合外使用"; const slot = state.pendingSlots.get(ownerId); if (!slot) return "当前不需要回应"; if (slot.atom.target !== ownerId) return "不是问你的"; if (slot.atom.type !== "请求回应") return "当前不是求桃"; if (slot.atom.requestType !== "桃/求桃") return "当前不是求桃"; const cardId = params.cardId; if (!cardId) return "cardId required"; if (!state.players[ownerId].hand.includes(cardId)) return "牌不在手牌中"; const card = state.cardMap[cardId]; if (!card) return "牌不存在"; if (card.color !== "红") return "只能使用红色牌"; return null; }, async (state, params) => { const cardId = params.cardId; await applyAtom(state, { type: "移动牌", cardId, from: { zone: "手牌", player: ownerId }, to: { zone: "弃牌堆" } }); state.localVars["求桃/已救"] = true; }); return () => { unloadDecl(); }; } function onMount$223(_skill, api) { api.defineAction("respond", { label: "急救", style: "primary", respondFor: "桃/求桃", prompt: { type: "useCard", title: "急救:将一张红色牌当桃使用", cardFilter: { filter: (c) => c.color === "红", min: 1, max: 1 } } }); } var init_急救 = __esmMin((() => { init_apply(); init_skill(); })); //#endregion //#region src/engine/skills/急袭.ts var 急袭_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$243, default: () => 急袭_default, onInit: () => onInit$243, onMount: () => onMount$222 }); function createSkill$243(id, ownerId) { return { id, ownerId, name: "急袭", description: "将一张\"田\"当【顺手牵羊】使用" }; } /** 数当前玩家的田数量 */ function tianCount$1(state, player) { return state.players[player].marks.filter((m) => m.id.startsWith(TIAN_PREFIX$2)).length; } /** 重新计算并写入距离修正 vars(每张田 = -1 距离 = 进攻修正 +1) */ function syncDistanceMod$1(state, player) { const count = tianCount$1(state, player); if (count > 0) state.players[player].vars[DISTANCE_ATTACK_MOD_KEY] = count; else delete state.players[player].vars[DISTANCE_ATTACK_MOD_KEY]; } /** 影子卡 id:${田markId}#急袭 */ function shadowIdOf$21(markId) { return `${markId}#急袭`; } function onInit$243(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "transform", (st, params) => { if (st.currentPlayerIndex !== ownerId) return "不是你的回合"; if (st.phase !== "出牌") return "只能在出牌阶段发动"; const self = st.players[ownerId]; if (!self?.alive) return "玩家不存在或已死亡"; const markId = params.markId; if (typeof markId !== "string") return "需要选择一张田"; const mark = self.marks.find((m) => m.id === markId); if (!mark?.id.startsWith(TIAN_PREFIX$2)) return "指定的田不存在"; if (!mark.payload?.cardId) return "田缺少卡牌信息"; return null; }, async (st, params) => { const markId = params.markId; const self = st.players[ownerId]; const mark = self.marks.find((m) => m.id === markId); if (!mark) return; const tianCardId = mark.payload?.cardId; const origCard = tianCardId ? st.cardMap[tianCardId] : void 0; const shadowId = shadowIdOf$21(markId); const newCount = tianCount$1(st, ownerId) - 1; await applyAtom(st, { type: "去标记", player: ownerId, markId, distanceVars: { attackMod: newCount > 0 ? newCount : void 0 } }); const shadow = { id: shadowId, name: "顺手牵羊", suit: origCard?.suit ?? "", color: origCard?.color ?? "无色", rank: origCard?.rank ?? "A", type: "锦囊牌", trickSubtype: "普通锦囊", shadowOf: tianCardId }; st.cardMap[shadowId] = shadow; self.hand.push(shadowId); syncDistanceMod$1(st, ownerId); }, (st, params) => { const markId = params.markId; const shadowId = shadowIdOf$21(markId); const self = st.players[ownerId]; const tianCardId = st.cardMap[shadowId]?.shadowOf; delete st.cardMap[shadowId]; self.hand = self.hand.filter((id) => id !== shadowId); self.marks.push({ id: markId, scope: ownerId, payload: { cardId: tianCardId ?? "" } }); syncDistanceMod$1(st, ownerId); }); return () => {}; } function onMount$222(_skill, api) { api.defineAction("transform", { label: "急袭", style: "danger", prompt: { type: "useCardAndTarget", title: "急袭:选择一张田当顺手牵羊使用", cardFilter: { min: 1, max: 1 }, targetFilter: { min: 1, max: 1, filter: () => true } }, activeWhen: (ctx) => { if (!defaultPlayActive(ctx)) return false; const p = ctx.view.players[ctx.perspectiveIdx]; if (!p) return false; return (p.marks ?? []).some((m) => m.id.startsWith(TIAN_PREFIX$2)); } }); } var TIAN_PREFIX$2, 急袭_default; var init_急袭 = __esmMin((() => { init_apply(); init_action_active(); init_skill(); init_vars_keys(); TIAN_PREFIX$2 = "屯田/田:"; 急袭_default = { createSkill: createSkill$243, onInit: onInit$243, onMount: onMount$222 }; })); //#endregion //#region src/engine/skills/恃才.ts var 恃才_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$242, default: () => _skillModule$113, onInit: () => onInit$242, onMount: () => onMount$221 }); /** 计算牌的有效 id(转化牌用原卡 id)。 */ function effectiveIdOf(state, cardId) { return state.cardMap[cardId]?.shadowOf ?? cardId; } /** 在 owner 装备区查找 cardId 所在槽位。 */ function findEquipSlot$1(state, ownerId, cardId) { const equip = state.players[ownerId]?.equipment; if (!equip) return null; for (const [slot, id] of Object.entries(equip)) if (id === cardId) return slot; return null; } function createSkill$242(id, ownerId) { return { id, ownerId, name: SKILL_ID$54, description: "当你每回合首次使用一种类型的牌后,你可以将之置于牌堆顶,然后摸一张牌" }; } function onInit$242(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "respond", (st, _params) => { const slot = st.pendingSlots.get(ownerId); if (!slot) return "当前不需要回应"; const atom = slot.atom; if (atom["type"] !== "请求回应") return "当前不需要回应"; if (atom["requestType"] !== CONFIRM_RT$59) return "当前不是恃才询问"; return null; }, async (st, params) => { st.localVars[CONFIRM_KEY$23] = params.choice === true || params.confirmed === true; }); registerAfterHook(state, skill.id, ownerId, "使用时", async (ctx) => { const atom = ctx.atom; if (atom.type !== "使用时") return; if (atom.source !== ownerId) return; const card = ctx.state.cardMap[atom.cardId]; if (!card) return; const type = card.type; const used = ctx.state.turn.vars[USED_TYPES_VAR] ?? []; if (used.includes(type)) return; used.push(type); ctx.state.turn.vars[USED_TYPES_VAR] = used; const pending = ctx.state.turn.vars[PENDING_VAR] ?? []; pending.push(effectiveIdOf(ctx.state, atom.cardId)); ctx.state.turn.vars[PENDING_VAR] = pending; ctx.frame.params[EFFECTIVE_ID_KEY] = card.shadowOf ?? atom.cardId; }); registerAfterHook(state, skill.id, ownerId, "使用结算结束后", async (ctx) => { const atom = ctx.atom; if (atom.type !== "使用结算结束后") return; if (atom.source !== ownerId) return; if (!ctx.state.players[ownerId]?.alive) return; const effectiveId = ctx.frame.params[EFFECTIVE_ID_KEY] ?? effectiveIdOf(ctx.state, atom.cardId); const pending = ctx.state.turn.vars[PENDING_VAR] ?? []; if (!pending.includes(effectiveId)) return; ctx.state.turn.vars[PENDING_VAR] = pending.filter((id) => id !== effectiveId); delete ctx.state.localVars[CONFIRM_KEY$23]; await applyAtom(ctx.state, { type: "请求回应", requestType: CONFIRM_RT$59, target: ownerId, prompt: { type: "confirm", title: "是否发动恃才?(将此牌置于牌堆顶,然后摸一张牌)", confirmLabel: "发动", cancelLabel: "不发动" }, defaultChoice: false, timeout: 15 }); if (ctx.state.localVars[CONFIRM_KEY$23] !== true) { delete ctx.state.localVars[CONFIRM_KEY$23]; return; } delete ctx.state.localVars[CONFIRM_KEY$23]; const st = ctx.state; if (st.zones.discardPile.includes(effectiveId)) await applyAtom(st, { type: "移动牌", cardId: effectiveId, from: { zone: "弃牌堆" }, to: { zone: "牌堆" } }); else { const slot = findEquipSlot$1(st, ownerId, effectiveId); if (slot) { await applyAtom(st, { type: "卸下", player: ownerId, slot }); if (st.players[ownerId]?.hand.includes(effectiveId)) await applyAtom(st, { type: "移动牌", cardId: effectiveId, from: { zone: "手牌", player: ownerId }, to: { zone: "牌堆" } }); } } await applyAtom(st, { type: "摸牌", player: ownerId, count: 1 }); }); return () => {}; } function onMount$221(_skill, api) { api.defineAction("respond", { label: SKILL_ID$54, style: "primary", prompt: { type: "confirm", title: "恃才", confirmLabel: "发动", cancelLabel: "不发动" } }); return () => {}; } var SKILL_ID$54, USED_TYPES_VAR, PENDING_VAR, CONFIRM_KEY$23, EFFECTIVE_ID_KEY, CONFIRM_RT$59, _skillModule$113; var init_恃才 = __esmMin((() => { init_apply(); init_skill(); SKILL_ID$54 = "恃才"; USED_TYPES_VAR = "恃才/已用类型"; PENDING_VAR = "恃才/待触发"; CONFIRM_KEY$23 = "恃才/confirmed"; EFFECTIVE_ID_KEY = "恃才/effectiveCardId"; CONFIRM_RT$59 = "恃才/confirm"; _skillModule$113 = { createSkill: createSkill$242, onInit: onInit$242, onMount: onMount$221 }; })); //#endregion //#region src/engine/skills/悲歌.ts var 悲歌_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$241, default: () => 悲歌_default, onInit: () => onInit$241, onMount: () => onMount$220 }); function createSkill$241(id, ownerId) { return { id, ownerId, name: "悲歌", description: "一名角色受到杀伤害后,弃一张牌令其判定:♥回血/♦摸二/♣来源弃二/♠来源翻面" }; } function onInit$241(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "respond", (st, params) => { const slot = st.pendingSlots.get(ownerId); if (slot?.atom.type !== "请求回应") return "当前不需要回应"; if (slot.atom.requestType !== DISCARD_RT$20) return "当前不是悲歌询问"; const cardId = params.cardId; if (typeof cardId !== "string") return "请选择一张手牌弃置"; if (!st.players[ownerId].hand.includes(cardId)) return "牌不在手牌中"; return null; }, async (st, params) => { st.localVars[DISCARD_CARD_KEY$3] = params.cardId; }); registerAfterHook(state, skill.id, ownerId, "判定牌生效后", async (ctx) => { if (ctx.atom.judgeType !== "悲歌") return; const processing = frameCards(ctx.state); if (processing.length === 0) return; const judgeCardId = processing[processing.length - 1]; const judgeCard = ctx.state.cardMap[judgeCardId]; if (!judgeCard) return; ctx.state.localVars[JUDGE_SUIT_KEY$2] = judgeCard.suit; }); registerAfterHook(state, skill.id, ownerId, "受到伤害后", async (ctx) => { const atom = ctx.atom; if ((atom.amount ?? 0) <= 0) return; const targetIdx = atom.target; if (targetIdx === void 0) return; if (!ctx.state.players[targetIdx]?.alive) return; const cardId = atom.cardId; if (!cardId) return; if (ctx.state.cardMap[cardId]?.name !== "杀") return; const self = ctx.state.players[ownerId]; if (!self?.alive) return; if (self.hand.length === 0) return; delete ctx.state.localVars[DISCARD_CARD_KEY$3]; await applyAtom(ctx.state, { type: "请求回应", requestType: DISCARD_RT$20, target: ownerId, prompt: { type: "useCard", title: "悲歌:弃置一张牌,令受伤角色判定", cardFilter: { filter: () => true, min: 1, max: 1 } }, timeout: 15 }); const discardCardId = ctx.state.localVars[DISCARD_CARD_KEY$3]; delete ctx.state.localVars[DISCARD_CARD_KEY$3]; if (typeof discardCardId !== "string") return; await applyAtom(ctx.state, { type: "弃置", player: ownerId, cardIds: [discardCardId], voluntary: true }); if (ctx.state.zones.deck.length === 0) return; delete ctx.state.localVars[JUDGE_SUIT_KEY$2]; await runJudgeFlow(ctx.state, targetIdx, "悲歌"); const suit = ctx.state.localVars[JUDGE_SUIT_KEY$2]; delete ctx.state.localVars[JUDGE_SUIT_KEY$2]; if (suit === void 0) return; const sourceIdx = atom.source; if (suit === "♥") { if (ctx.state.players[targetIdx]?.alive) await applyAtom(ctx.state, { type: "回复体力", target: targetIdx, amount: 1 }); } else if (suit === "♦") await applyAtom(ctx.state, { type: "摸牌", player: targetIdx, count: 2 }); else if (suit === "♣") { if (sourceIdx === void 0) return; const source = ctx.state.players[sourceIdx]; if (!source?.alive) return; const toDiscard = source.hand.slice(0, Math.min(2, source.hand.length)); if (toDiscard.length > 0) await applyAtom(ctx.state, { type: "弃置", player: sourceIdx, cardIds: toDiscard }); } else if (suit === "♠") { if (sourceIdx === void 0) return; if (!ctx.state.players[sourceIdx]?.alive) return; await flipFaceDown(ctx.state, sourceIdx, "悲歌"); } }); registerBeforeHook(state, skill.id, ownerId, "阶段开始", async (ctx) => { const atom = ctx.atom; if (atom.type !== "阶段开始") return; const player = atom.player; if (player === void 0) return; const p = ctx.state.players[player]; if (!p) return; if (atom.phase === "准备" && p.tags.includes(SKIP_TAG$4)) { await flipFaceUp(ctx.state, player, "悲歌"); ctx.state.localVars[SKIP_FLAG$8] = player; return { kind: "cancel" }; } if (ctx.state.localVars[SKIP_FLAG$8] === player) return { kind: "cancel" }; }); registerBeforeHook(state, skill.id, ownerId, "阶段结束", async (ctx) => { const atom = ctx.atom; if (atom.type !== "阶段结束") return; const player = atom.player; if (player === void 0) return; if (ctx.state.localVars[SKIP_FLAG$8] !== player) return; delete ctx.state.localVars[SKIP_FLAG$8]; await performSkipTurn(ctx.state, player); return { kind: "cancel" }; }); return () => {}; } function onMount$220(_skill, api) { api.defineAction("respond", { label: "悲歌", style: "primary", prompt: { type: "useCard", title: "悲歌:弃置一张牌发动", cardFilter: { filter: (_c) => true, min: 1, max: 1 } } }); } var DISCARD_RT$20, DISCARD_CARD_KEY$3, JUDGE_SUIT_KEY$2, SKIP_TAG$4, SKIP_FLAG$8, 悲歌_default; var init_悲歌 = __esmMin((() => { init_apply(); init_frame(); init_judge(); init_face_down(); init_skill(); DISCARD_RT$20 = "悲歌/discard"; DISCARD_CARD_KEY$3 = "悲歌/discardCard"; JUDGE_SUIT_KEY$2 = "悲歌/judgeSuit"; SKIP_TAG$4 = "悲歌/翻面"; SKIP_FLAG$8 = "悲歌/skipAll"; 悲歌_default = { createSkill: createSkill$241, onInit: onInit$241, onMount: onMount$220 }; })); //#endregion //#region src/engine/skills/成略.ts var 成略_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$240, default: () => _skillModule$112, onInit: () => onInit$240, onMount: () => onMount$219 }); function getState$2(state, ownerId) { return state.players[ownerId]?.vars[STATE_KEY$2] === "阴" ? "阴" : "阳"; } async function syncStateView$2(state, ownerId) { await applyAtom(state, { type: "回合用量", player: ownerId, key: STATE_VIEW_KEY$2, value: getState$2(state, ownerId) }); } /** 判断 cardId 的花色是否在本回合成略弃置牌花色集合中。 */ function suitInChenglue(state, cardId) { if (!cardId) return false; const card = state.cardMap[cardId]; if (!card) return false; const suits = state.turn.vars[SUITS_VAR]; if (!Array.isArray(suits) || suits.length === 0) return false; return suits.includes(card.suit); } function createSkill$240(id, ownerId) { return { id, ownerId, name: SKILL_ID$53, description: "转换技,出牌阶段限一次。阳:摸一张牌并弃置两张手牌;阴:摸两张牌并弃置一张手牌。然后本阶段使用与弃置牌花色相同的牌无距离和次数限制" }; } function onInit$240(skill, state) { const ownerId = skill.ownerId; const unloadSlashExemptor = registerSlashExemptor(state, ownerId, (st, player, cardId) => player === ownerId && suitInChenglue(st, cardId)); const unloadRangeExemptor = registerAttackRangeExemptor(state, ownerId, (st, from, _to, cardId) => from === ownerId && suitInChenglue(st, cardId)); const unloadDistanceExemptor = registerDistanceExemptor(state, ownerId, (st, from, _to, cardId) => from === ownerId && suitInChenglue(st, cardId)); registerAction(state, skill.id, ownerId, "respond", (st, params) => { const slot = st.pendingSlots.get(ownerId); if (!slot) return "当前不需要回应"; const atom = slot.atom; if (atom["type"] !== "请求回应") return "当前不需要回应"; if (atom["requestType"] !== DISCARD_RT$19) return "当前不是成略弃牌"; const ids = params.cardIds; if (!Array.isArray(ids) || ids.length === 0) return "请选择要弃置的手牌"; const expected = st.localVars[DISCARD_COUNT_KEY]; const self = st.players[ownerId]; if (!self) return "player not found"; if (typeof expected === "number" && ids.length !== expected) return `需弃置 ${expected} 张牌`; if (!ids.every((id) => self.hand.includes(id))) return "牌不在手牌中"; if (new Set(ids).size !== ids.length) return "cardIds 含重复牌"; return null; }, async (st, params) => { const ids = params.cardIds; if (Array.isArray(ids) && ids.length > 0) st.localVars[DISCARD_KEY$13] = ids; }); registerAction(state, skill.id, ownerId, "use", (st, _params) => { const self = st.players[ownerId]; if (!self) return "player not found"; if (!self.alive) return "你已死亡"; if (st.currentPlayerIndex !== ownerId) return "只能在你的回合使用"; if (st.phase !== "出牌") return "只能在出牌阶段使用"; if (hasBlockingPending(st)) return "当前有未完成的询问"; if (usedThisTurn$3(st, ownerId, SKILL_ID$53)) return "本回合已使用过成略"; const stt = getState$2(st, ownerId); const need = stt === "阳" ? 2 : 1; if (self.hand.length + (stt === "阳" ? 1 : 2) < need) return "手牌不足以发动成略"; return null; }, async (st, _params) => { const from = ownerId; await markOncePerTurn(st, from, SKILL_ID$53); const stt = getState$2(st, from); const drawCount = stt === "阳" ? 1 : 2; const discardCount = stt === "阳" ? 2 : 1; await pushFrame(st, `${SKILL_ID$53}(${stt})`, from, { drawCount, discardCount }); await applyAtom(st, { type: "摸牌", player: from, count: drawCount }); delete st.localVars[DISCARD_KEY$13]; st.localVars[DISCARD_COUNT_KEY] = discardCount; const self = st.players[from]; await applyAtom(st, { type: "请求回应", requestType: DISCARD_RT$19, target: from, prompt: { type: "useCard", title: `成略(${stt}):选择 ${discardCount} 张手牌弃置(本阶段同花色牌无距离和次数限制)`, cardFilter: { filter: () => true, min: discardCount, max: discardCount } }, mandatory: true, timeout: 30 }); delete st.localVars[DISCARD_COUNT_KEY]; let discardIds = st.localVars[DISCARD_KEY$13]; delete st.localVars[DISCARD_KEY$13]; if (!Array.isArray(discardIds) || discardIds.length !== discardCount) discardIds = (self?.hand ?? []).slice(0, discardCount); discardIds = discardIds.filter((id) => st.players[from]?.hand.includes(id)); if (discardIds.length === discardCount) { await applyAtom(st, { type: "弃置", player: from, cardIds: discardIds, voluntary: true }); const suits = Array.from(new Set(discardIds.map((id) => st.cardMap[id]?.suit).filter((s) => typeof s === "string" && s !== ""))); if (suits.length > 0) { st.turn.vars[SUITS_VAR] = suits; await applyAtom(st, { type: "回合用量", player: from, key: SUITS_VAR, value: suits }); await applyAtom(st, { type: "回合用量", player: from, key: slashUnlimitedKey("成略"), value: true }); } } st.players[from].vars[STATE_KEY$2] = stt === "阳" ? "阴" : "阳"; await syncStateView$2(st, from); await popFrame(st); }); registerAfterHook(state, skill.id, ownerId, "回合开始", async (ctx) => { if (ctx.atom.type !== "回合开始") return; if (ctx.atom.player !== ownerId) return; await syncStateView$2(ctx.state, ownerId); }); return () => { unloadSlashExemptor(); unloadRangeExemptor(); unloadDistanceExemptor(); }; } function onMount$219(_skill, api) { api.defineAction("use", { label: SKILL_ID$53, style: "primary", prompt: { type: "confirm", title: "发动成略(摸牌并弃置手牌,本阶段同花色牌无距离和次数限制)", confirmLabel: "发动", cancelLabel: "取消" }, activeWhen: (ctx) => { if (!activeUnlessUsedThisTurn(SKILL_ID$53)(ctx)) return false; const p = ctx.view.players[ctx.perspectiveIdx]; if (!p) return false; if (!(p.turnUsage?.[STATE_VIEW_KEY$2] === "阴") && (p.handCount ?? 0) < 1) return false; return defaultPlayActive(ctx); } }); api.defineAction("respond", { label: SKILL_ID$53, style: "primary", prompt: { type: "useCard", title: "成略:选择要弃置的手牌", cardFilter: { filter: () => true, min: 1, max: 99 } } }); return () => {}; } var SKILL_ID$53, STATE_KEY$2, STATE_VIEW_KEY$2, SUITS_VAR, DISCARD_RT$19, DISCARD_KEY$13, DISCARD_COUNT_KEY, _skillModule$112; var init_成略 = __esmMin((() => { init_apply(); init_frame(); init_skill(); init_once_per_turn(); init_action_active(); init_slash_quota(); init_distance(); init_vars_keys(); SKILL_ID$53 = "成略"; STATE_KEY$2 = "成略/态"; STATE_VIEW_KEY$2 = CHENGLUE_STATE_VIEW_KEY; SUITS_VAR = CHENGLUE_SUITS_VIEW_KEY; DISCARD_RT$19 = "成略/discard"; DISCARD_KEY$13 = "成略/discard结果"; DISCARD_COUNT_KEY = "成略/弃牌数"; _skillModule$112 = { createSkill: createSkill$240, onInit: onInit$240, onMount: onMount$219 }; })); //#endregion //#region src/engine/skills/截辎.ts var 截辎_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$239, onInit: () => onInit$239, onMount: () => onMount$218 }); /** 玩家是否拥有"辎"标记 */ function hasZi(state, player) { return state.players[player]?.marks.some((m) => m.id === ZI_MARK_ID) ?? false; } /** 全场存活玩家最小手牌数;无存活玩家时返回 Infinity */ function minHandCount(state) { let min = Infinity; for (const p of state.players) { if (!p.alive) continue; if (p.hand.length < min) min = p.hand.length; } return min; } function createSkill$239(id, ownerId) { return { id, ownerId, name: "截辎", description: "当一名角色跳过摸牌阶段后,你可以选择一名角色:若其手牌数全场最少且没有\"辎\",其获得\"辎\"标记,否则其摸一张牌。有\"辎\"的角色于其摸牌阶段结束时移除\"辎\"并执行一个额外的摸牌阶段" }; } function onInit$239(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "respond", (st, _params) => { const slot = st.pendingSlots.get(ownerId); if (!slot) return "当前不需要回应"; const atom = slot.atom; if (atom["type"] !== "请求回应") return "当前不需要回应"; const rt = atom["requestType"]; if (rt !== CONFIRM_RT$58 && rt !== CHOOSE_TARGET_RT$5) return "当前不是截辎询问"; return null; }, async (st, params) => { const rt = (st.pendingSlots.get(ownerId)?.atom)?.requestType; if (rt === CONFIRM_RT$58) st.localVars[CONFIRM_KEY$22] = params.choice === true || params.confirmed === true; else if (rt === CHOOSE_TARGET_RT$5) { const t = params.targets?.[0] ?? (typeof params.target === "number" ? params.target : void 0); if (typeof t === "number") st.localVars[TARGET_KEY$32] = t; } }); registerAfterHook(state, skill.id, ownerId, "阶段开始", async (ctx) => { const atom = ctx.atom; if (atom.type !== "阶段开始") return; if (atom.phase !== "摸牌") return; ctx.state.localVars[NORMAL_KEY$2] = atom.player; }); registerBeforeHook(state, skill.id, ownerId, "阶段结束", async (ctx) => { const atom = ctx.atom; if (atom.type !== "阶段结束") return; if (atom.phase !== "摸牌") return; const phasePlayer = atom.player; const startedNormally = ctx.state.localVars[NORMAL_KEY$2] === phasePlayer; delete ctx.state.localVars[NORMAL_KEY$2]; if (startedNormally) { if (!hasZi(ctx.state, phasePlayer)) return; if (!ctx.state.players[phasePlayer]?.alive) return; await applyAtom(ctx.state, { type: "去标记", player: phasePlayer, markId: ZI_MARK_ID }); await applyAtom(ctx.state, { type: "阶段开始", player: phasePlayer, phase: "摸牌" }); await applyAtom(ctx.state, { type: "摸牌", player: phasePlayer, count: 2 }); delete ctx.state.localVars[NORMAL_KEY$2]; return; } if (!ctx.state.players[ownerId]?.alive) return; delete ctx.state.localVars[CONFIRM_KEY$22]; await applyAtom(ctx.state, { type: "请求回应", requestType: CONFIRM_RT$58, target: ownerId, prompt: { type: "confirm", title: "是否发动截辎?(选择一名角色:若其手牌数全场最少且没有\"辎\",其获得\"辎\"标记,否则其摸一张牌)", confirmLabel: "发动", cancelLabel: "不发动" }, defaultChoice: false, timeout: 15 }); if (!ctx.state.localVars[CONFIRM_KEY$22]) return; delete ctx.state.localVars[TARGET_KEY$32]; await applyAtom(ctx.state, { type: "请求回应", requestType: CHOOSE_TARGET_RT$5, target: ownerId, prompt: { type: "choosePlayer", title: "截辎:选择一名角色(手牌全场最少且没\"辎\"→获得\"辎\";否则其摸一张牌)", min: 1, max: 1, filter: (view, t) => view.players[t]?.alive === true }, timeout: 20 }); const target = ctx.state.localVars[TARGET_KEY$32]; delete ctx.state.localVars[TARGET_KEY$32]; if (typeof target !== "number") return; if (!ctx.state.players[target]?.alive) return; const minCount = minHandCount(ctx.state); if (ctx.state.players[target].hand.length === minCount && !hasZi(ctx.state, target)) { const ziMark = { id: ZI_MARK_ID, scope: target }; await applyAtom(ctx.state, { type: "加标记", player: target, mark: ziMark }); } else await applyAtom(ctx.state, { type: "摸牌", player: target, count: 1 }); }); return () => {}; } function onMount$218(_skill, api) { api.defineAction("respond", { label: "截辎", style: "primary", prompt: { type: "confirm", title: "截辎", confirmLabel: "确认", cancelLabel: "取消" } }); } var NORMAL_KEY$2, ZI_MARK_ID, CONFIRM_KEY$22, TARGET_KEY$32, CONFIRM_RT$58, CHOOSE_TARGET_RT$5; var init_截辎 = __esmMin((() => { init_apply(); init_skill(); NORMAL_KEY$2 = "截辎/normalDrawPhase"; ZI_MARK_ID = "辎"; CONFIRM_KEY$22 = "截辎/confirmed"; TARGET_KEY$32 = "截辎/target"; CONFIRM_RT$58 = "截辎/confirm"; CHOOSE_TARGET_RT$5 = "截辎/chooseTarget"; })); //#endregion //#region src/engine/skills/护甲.ts var 护甲_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$238, onInit: () => onInit$238 }); function createSkill$238(id, ownerId) { return { id, ownerId, name: "护甲", description: "锁定技:受到【杀】造成的伤害时,若此牌为黑色,伤害 -1", isLocked: true }; } function onInit$238(skill, state) { const ownerId = skill.ownerId; registerBeforeHook(state, skill.id, ownerId, "受到伤害时", async (ctx) => { const atom = ctx.atom; if (atom.target !== ownerId) return; if (typeof atom.cardId !== "string") return; const card = ctx.state.cardMap[atom.cardId]; if (!card) return; if (!card.name.includes("杀")) return; if (card.color !== "黑") return; const baseAmount = atom.amount ?? 0; if (baseAmount <= 0) return; return { kind: "modify", atom: { ...ctx.atom, amount: baseAmount - 1 } }; }); return () => {}; } var init_护甲 = __esmMin((() => { init_skill(); })); //#endregion //#region src/engine/skills/护驾.ts var 护驾_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$237, default: () => 护驾_default, onInit: () => onInit$237, onMount: () => onMount$217 }); function createSkill$237(id, ownerId) { return { id, ownerId, name: "护驾", description: "主公技:需要闪时,令其他魏势力角色替你打出闪" }; } function onInit$237(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "respond", (st, _params) => { const slot = st.pendingSlots.get(ownerId); if (!slot) return "当前不需要回应"; if (slot.atom.type !== "询问闪") return "当前不是出闪窗口"; if (!st.players[ownerId]?.alive) return "玩家不存在或已死亡"; if (!st.players.some((p) => p.alive && p.index !== ownerId && p.faction === "魏" && p.hand.length > 0)) return "没有可出闪的魏势力角色"; return null; }, async (st, _params) => { const numPlayers = st.players.length; for (let offset = 1; offset < numPlayers; offset++) { const allyIdx = (ownerId + offset) % numPlayers; const ally = st.players[allyIdx]; if (!ally?.alive) continue; if (ally.faction !== "魏") continue; if (ally.hand.length === 0) continue; delete st.localVars["护驾/闪出"]; await applyAtom(st, { type: "请求回应", requestType: "护驾/出闪", target: allyIdx, prompt: { type: "useCard", title: `护驾:曹操(${st.players[ownerId]?.name ?? `P${ownerId}`})需要闪,是否打出一张闪?`, cardFilter: { filter: (c) => c.name === "闪", min: 1, max: 1 } }, timeout: 15 }); const dodgeCardId = st.localVars["护驾/闪出"]; delete st.localVars["护驾/闪出"]; if (dodgeCardId && ally.hand.includes(dodgeCardId)) { await applyAtom(st, { type: "移动牌", cardId: dodgeCardId, from: { zone: "手牌", player: allyIdx }, to: { zone: "处理区" } }); return; } } }); for (const p of state.players) { const pid = p.index; if (pid === ownerId) continue; if (p.faction !== "魏") continue; registerAction(state, skill.id, pid, "respond", (st, params) => { const slot = st.pendingSlots.get(pid); if (!slot) return "当前不需要回应"; const atom = slot.atom; if (atom["type"] !== "请求回应") return "当前不需要回应"; if (atom["requestType"] !== "护驾/出闪") return "当前不是护驾询问"; const cardId = params.cardId; if (cardId) { if (!st.players[pid].hand.includes(cardId)) return "牌不在手牌中"; if (st.cardMap[cardId]?.name !== "闪") return "只能打出闪"; } return null; }, async (st, params) => { const cardId = params.cardId; if (cardId) st.localVars["护驾/闪出"] = cardId; }); } const unloadDecl = declareAlternativeResponse(state, ownerId, "询问闪"); return () => { unloadDecl(); }; } function onMount$217(_skill, api) { api.defineAction("respond", { label: "护驾", style: "primary", prompt: { type: "confirm", title: "护驾:令魏势力角色替你出闪?", confirmLabel: "护驾", cancelLabel: "不发动" }, activeWhen: (ctx) => { const slot = ctx.view.pending; if (!slot) return false; if (slot.target !== ctx.perspectiveIdx) return false; const atom = slot.atom; if (atom.type === "询问闪") return true; if (atom.type === "请求回应" && atom.requestType === "护驾/出闪") return true; return false; } }); } var 护驾_default; var init_护驾 = __esmMin((() => { init_apply(); init_skill(); 护驾_default = { createSkill: createSkill$237, onInit: onInit$237, onMount: onMount$217 }; })); //#endregion //#region src/engine/skills/拒战.ts var 拒战_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$236, default: () => _skillModule$111, onInit: () => onInit$236, onMount: () => onMount$216 }); function getState$1(state, ownerId) { return state.players[ownerId]?.vars[STATE_KEY$1] === "阴" ? "阴" : "阳"; } async function syncStateView$1(state, ownerId) { await applyAtom(state, { type: "回合用量", player: ownerId, key: STATE_VIEW_KEY$1, value: getState$1(state, ownerId) }); } function isForbidden(state, from, to) { return !!state.turn.vars[FORBIDDEN_VAR]?.some((p) => p.from === from && p.to === to); } function addForbidden(state, from, to) { const arr = state.turn.vars[FORBIDDEN_VAR] ?? []; if (!arr.some((p) => p.from === from && p.to === to)) { arr.push({ from, to }); state.turn.vars[FORBIDDEN_VAR] = arr; } } /** 判定当前结算牌是否为杀(cardId 对应卡牌名为"杀",含转化/虚拟杀)。 */ function isSlashCard$2(state, cardId) { if (!cardId) return false; return state.cardMap[cardId]?.name === "杀"; } function createSkill$236(id, ownerId) { return { id, ownerId, name: SKILL_ID$52, description: "转换技。阳:成为其他角色杀的目标后,你可与其各摸一张牌,然后其本回合不能再对你使用牌。阴:使用杀指定目标后,你可获得其一张牌,然后你本回合不能再对其使用牌" }; } function onInit$236(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "respond", (st, params) => { const slot = st.pendingSlots.get(ownerId); if (!slot) return "当前不需要回应"; const atom = slot.atom; if (atom.type !== "请求回应") return "当前不需要回应"; const rt = atom.requestType; if (rt === YANG_CONFIRM_RT || rt === YIN_CONFIRM_RT$1) return null; if (rt === YIN_PICK_RT) { const zone = params.zone; if (zone === "equipment") { if (typeof params.cardId !== "string") return "cardId required"; } else if (zone === "hand") { if (typeof params.handIndex !== "number") return "handIndex required"; } else return "zone required (equipment|hand)"; return null; } return "当前不是拒战回应"; }, async (st, params) => { const rt = (st.pendingSlots.get(ownerId)?.atom)?.requestType; if (rt === YANG_CONFIRM_RT) st.localVars[YANG_CONFIRMED_KEY] = params.choice === true || params.confirmed === true; else if (rt === YIN_CONFIRM_RT$1) st.localVars[YIN_CONFIRMED_KEY$1] = params.choice === true || params.confirmed === true; else if (rt === YIN_PICK_RT) st.localVars[PICK_RESULT_KEY$3] = { zone: params.zone, cardId: params.cardId ?? null, handIndex: params.handIndex ?? null }; }); registerBeforeHook(state, skill.id, ownerId, "成为目标", async (ctx) => { const atom = ctx.atom; const source = atom.source; const target = atom.target; if (typeof source !== "number" || typeof target !== "number") return; if (source === target) return; if (!isForbidden(ctx.state, source, target)) return; return { kind: "cancel" }; }); registerAfterHook(state, skill.id, ownerId, "成为目标后", async (ctx) => { const atom = ctx.atom; if (atom.target !== ownerId) return; const source = atom.source; if (source === ownerId) return; if (!isSlashCard$2(ctx.state, atom.cardId)) return; if (getState$1(ctx.state, ownerId) !== "阳") return; if (!ctx.state.players[ownerId]?.alive) return; if (!ctx.state.players[source]?.alive) return; delete ctx.state.localVars[YANG_CONFIRMED_KEY]; await applyAtom(ctx.state, { type: "请求回应", requestType: YANG_CONFIRM_RT, target: ownerId, prompt: { type: "confirm", title: "拒战(阳):是否与其各摸一张牌,然后其本回合不能再对你使用牌?", confirmLabel: "发动", cancelLabel: "不发动" }, defaultChoice: false, timeout: 15 }); if (!ctx.state.localVars[YANG_CONFIRMED_KEY]) return; delete ctx.state.localVars[YANG_CONFIRMED_KEY]; await applyAtom(ctx.state, { type: "摸牌", player: ownerId, count: 1 }); if (ctx.state.players[source]?.alive) await applyAtom(ctx.state, { type: "摸牌", player: source, count: 1 }); addForbidden(ctx.state, source, ownerId); ctx.state.players[ownerId].vars[STATE_KEY$1] = "阴"; await syncStateView$1(ctx.state, ownerId); }); registerAfterHook(state, skill.id, ownerId, "指定目标后", async (ctx) => { const atom = ctx.atom; if (atom.source !== ownerId) return; const target = atom.target; if (typeof target !== "number") return; if (target === ownerId) return; if (!isSlashCard$2(ctx.state, atom.cardId)) return; if (getState$1(ctx.state, ownerId) !== "阴") return; if (!ctx.state.players[ownerId]?.alive) return; const targetPlayer = ctx.state.players[target]; if (!targetPlayer?.alive) return; if (!(targetPlayer.hand.length > 0 || Object.keys(targetPlayer.equipment).length > 0)) return; delete ctx.state.localVars[YIN_CONFIRMED_KEY$1]; await applyAtom(ctx.state, { type: "请求回应", requestType: YIN_CONFIRM_RT$1, target: ownerId, prompt: { type: "confirm", title: "拒战(阴):是否获得其一张牌,然后你本回合不能再对其使用牌?", confirmLabel: "发动", cancelLabel: "不发动" }, defaultChoice: false, timeout: 15 }); if (!ctx.state.localVars[YIN_CONFIRMED_KEY$1]) return; delete ctx.state.localVars[YIN_CONFIRMED_KEY$1]; await runPickTargetCardPanel(ctx.state, ownerId, target, targetPlayer, { mode: "obtain", requestType: YIN_PICK_RT, title: `拒战(阴):选择从 ${targetPlayer.name} 获得的一张牌`, includeJudge: false }); addForbidden(ctx.state, ownerId, target); ctx.state.players[ownerId].vars[STATE_KEY$1] = "阳"; await syncStateView$1(ctx.state, ownerId); }); registerAfterHook(state, skill.id, ownerId, "回合开始", async (ctx) => { if (ctx.atom.player !== ownerId) return; await syncStateView$1(ctx.state, ownerId); }); return () => {}; } function onMount$216(_skill, api) { api.defineAction("respond", { label: SKILL_ID$52, style: "primary", prompt: { type: "confirm", title: "拒战", confirmLabel: "发动", cancelLabel: "不发动" } }); return () => {}; } var SKILL_ID$52, STATE_KEY$1, STATE_VIEW_KEY$1, FORBIDDEN_VAR, YANG_CONFIRM_RT, YIN_CONFIRM_RT$1, YIN_PICK_RT, YANG_CONFIRMED_KEY, YIN_CONFIRMED_KEY$1, _skillModule$111; var init_拒战 = __esmMin((() => { init_apply(); init_skill(); init_pick_card_panel(); init_vars_keys(); SKILL_ID$52 = "拒战"; STATE_KEY$1 = "拒战/态"; STATE_VIEW_KEY$1 = JUZHAN_STATE_VIEW_KEY; FORBIDDEN_VAR = "拒战/禁对"; YANG_CONFIRM_RT = "拒战/阳/confirm"; YIN_CONFIRM_RT$1 = "拒战/阴/confirm"; YIN_PICK_RT = "拒战/阴/选牌"; YANG_CONFIRMED_KEY = "拒战/阳/confirmed"; YIN_CONFIRMED_KEY$1 = "拒战/阴/confirmed"; _skillModule$111 = { createSkill: createSkill$236, onInit: onInit$236, onMount: onMount$216 }; })); //#endregion //#region src/engine/skills/挑衅.ts var 挑衅_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$235, default: () => 挑衅_default, onInit: () => onInit$235, onMount: () => onMount$215 }); function createSkill$235(id, ownerId) { return { id, ownerId, name: "挑衅", description: "出牌阶段限一次:指定一名能用杀攻击到你的角色,其须对你出杀,否则你弃其一张牌" }; } /** 让姜维从被挑衅者区域选一张牌弃置(pickTargetCard:装备明选/手牌盲选)。 */ async function pickAndDiscard$2(state, picker, victim) { const vp = state.players[victim]; if (!vp) return; const equipment = Object.entries(vp.equipment).filter(([, id]) => typeof id === "string").map(([slot, id]) => ({ slot, cardId: id, cardName: state.cardMap[id]?.name ?? "?" })); const handCount = vp.hand.length; if (equipment.length === 0 && handCount === 0) return; const defaultZone = equipment.length > 0 ? { zone: "equipment", cardId: equipment[0].cardId } : { zone: "hand", handIndex: 0 }; state.localVars[DISCARD_TARGET_VAR] = victim; delete state.localVars[PICK_VAR]; const pickAtom = { type: "请求回应", requestType: PICK_REQUEST_TYPE, target: picker, prompt: { type: "pickTargetCard", mode: "discard", title: `挑衅:弃置 ${vp.name} 的一张牌`, target: victim, equipment, judge: [], handCount }, defaultChoice: defaultZone, timeout: 20 }; await applyAtom(state, pickAtom); const result = state.localVars[PICK_VAR]; delete state.localVars[PICK_VAR]; delete state.localVars[DISCARD_TARGET_VAR]; const filteredEquip = pickAtom.prompt.equipment ?? []; let fallback = defaultZone; if (fallback.zone === "equipment") { if (!filteredEquip.some((e) => e.cardId === fallback.cardId)) fallback = filteredEquip.length > 0 ? { zone: "equipment", cardId: filteredEquip[0].cardId } : { zone: "hand", handIndex: 0 }; } const zone = result?.zone ?? fallback.zone; let discardId; if (zone === "equipment") discardId = result?.cardId ?? fallback.cardId ?? void 0; else { const idx = result?.handIndex ?? 0; discardId = vp.hand[idx] ?? vp.hand[0]; } if (discardId) await applyAtom(state, { type: "弃置", player: victim, cardIds: [discardId] }); } function onInit$235(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "use", (state, params) => { if (state.currentPlayerIndex !== ownerId) return "不是你的回合"; if (state.phase !== "出牌") return "只能在出牌阶段发动"; if (hasBlockingPending(state)) return "当前有未完成的询问"; if (usedThisTurn$3(state, ownerId, "挑衅")) return "本回合已使用过挑衅"; if (!state.players[ownerId]?.alive) return "玩家不存在或已死亡"; const target = params.target; if (typeof target !== "number") return "需要选择目标"; if (target === ownerId) return "不能选择自己"; if (!state.players[target]?.alive) return "目标不存在或已死亡"; if (!inAttackRange(state, target, ownerId)) return "目标无法用杀攻击到你"; return null; }, async (state, params) => { const from = ownerId; const target = params.target; await markOncePerTurn(state, from, "挑衅"); await pushFrame(state, "挑衅", from, { ...params }); try { await applyAtom(state, { type: "请求回应", requestType: REQUEST_TYPE$6, target, prompt: { type: "useCardAndTarget", title: `挑衅:对 ${state.players[from].name} 使用一张杀,否则其弃你一张牌`, cardFilter: { filter: (c) => c.name === "杀", min: 1, max: 1 }, targetFilter: { min: 1, max: 1, filter: (_v, t) => t === from } }, timeout: 15 }); const choice = state.localVars[CHOICE_VAR$3]; delete state.localVars[CHOICE_VAR$3]; if (choice?.cardId) await runUseFlow(state, target, choice.cardId, [choice.target], "杀"); else await pickAndDiscard$2(state, from, target); } finally { await popFrame(state); } }); const unloaders = []; for (const pl of state.players) { const seat = pl.index; unloaders.push(registerAction(state, skill.id, seat, "respond", (st, params) => { const slot = st.pendingSlots.get(seat); if (!slot) return "当前不需要回应"; if (slot.atom.type !== "请求回应") return "当前不是挑衅询问"; const requestType = slot.atom.requestType; if (requestType === REQUEST_TYPE$6) { const cardId = params.cardId; const rawTarget = Array.isArray(params.targets) ? params.targets[0] : params.target; const targetIdx = typeof rawTarget === "number" ? rawTarget : void 0; if (typeof cardId !== "string") return "请选择一张杀"; if (typeof targetIdx !== "number") return "请选择目标"; if (!st.players[seat]?.hand.includes(cardId)) return "牌不在手牌中"; if (st.cardMap[cardId]?.name !== "杀") return "只能使用杀"; if (targetIdx !== ownerId) return "只能对姜维使用杀"; if (!inAttackRange(st, seat, ownerId, cardId)) return "目标不在攻击范围内"; return null; } if (requestType === PICK_REQUEST_TYPE) { const victim = st.localVars[DISCARD_TARGET_VAR]; if (typeof victim !== "number") return "无弃牌目标"; const vp = st.players[victim]; if (!vp) return "弃牌目标不存在"; const zone = params.zone; if (zone === "equipment") { if (typeof params.cardId !== "string") return "cardId required"; const promptEquip = slot.atom?.prompt?.equipment; if (promptEquip && !promptEquip.some((e) => e.cardId === params.cardId)) return "该装备不可选"; if (!Object.values(vp.equipment).includes(params.cardId)) return "该牌不在目标装备区"; return null; } if (zone === "hand") { if (typeof params.handIndex !== "number") return "handIndex required"; if (params.handIndex < 0 || params.handIndex >= vp.hand.length) return "handIndex 越界"; return null; } return "zone required (equipment|hand)"; } return "当前不是挑衅询问"; }, async (st, params) => { const requestType = (st.pendingSlots.get(seat)?.atom)?.requestType; if (requestType === REQUEST_TYPE$6) { const rawTarget = Array.isArray(params.targets) ? params.targets[0] : params.target; st.localVars[CHOICE_VAR$3] = { cardId: params.cardId, target: rawTarget }; } else if (requestType === PICK_REQUEST_TYPE) st.localVars[PICK_VAR] = { zone: params.zone, cardId: params.cardId ?? null, handIndex: params.handIndex ?? null }; })); } return () => { for (const u of unloaders) u(); }; } function onMount$215(skill, api) { api.defineAction("use", { label: "挑衅", style: "danger", prompt: { type: "selectTarget", title: "挑衅:选择一名能用杀攻击到你的角色", description: "其须对你出杀,否则你弃其一张牌", targetFilter: { min: 1, max: 1, filter: (view, t) => { const me = view.currentPlayerIndex; if (t === me) return false; const tp = view.players[t]; if (!tp || tp.alive === false) return false; return viewCanAttack(view.players, view.cardMap, t, me); } } }, activeWhen: (ctx) => activeUnlessUsedThisTurn("挑衅")(ctx) }); api.defineAction("respond", { label: "挑衅", style: "primary", prompt: { type: "pickTargetCard", title: "挑衅:选择弃置的牌", target: 0, equipment: [], judge: [], handCount: 0 } }); } var REQUEST_TYPE$6, CHOICE_VAR$3, PICK_REQUEST_TYPE, PICK_VAR, DISCARD_TARGET_VAR, 挑衅_default; var init_挑衅 = __esmMin((() => { init_apply(); init_frame(); init_use_card(); init_once_per_turn(); init_skill(); init_distance(); init_viewDistance(); REQUEST_TYPE$6 = "挑衅/出杀"; CHOICE_VAR$3 = "挑衅/出杀选择"; PICK_REQUEST_TYPE = "挑衅/选牌"; PICK_VAR = "挑衅/选牌"; DISCARD_TARGET_VAR = "挑衅/弃牌目标"; 挑衅_default = { createSkill: createSkill$235, onInit: onInit$235, onMount: onMount$215 }; })); //#endregion //#region src/engine/skills/据守.ts var 据守_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$234, default: () => 据守_default, onInit: () => onInit$234, onMount: () => onMount$214 }); /** 装备牌 subtype → 装备栏位(与 装备 atom 的 inferSlot 一致) */ function slotOf$6(card) { switch (card?.subtype) { case "武器": return "武器"; case "防具": return "防具"; case "进攻马": return "进攻马"; case "防御马": return "防御马"; case "宝物": return "宝物"; default: return null; } } function createSkill$234(id, ownerId) { return { id, ownerId, name: "据守", description: "结束阶段:翻面并摸四张牌,然后弃置一张手牌(装备牌改为使用之)" }; } function onInit$234(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "use", (state, _params) => { const myTurn = state.currentPlayerIndex === ownerId; const inEndPhase = state.phase === "弃牌" || state.phase === "回合结束"; const free = !hasBlockingPending(state); const selfAlive = state.players[ownerId]?.alive === true; const notUsed = !usedThisTurn$3(state, ownerId, "据守"); return myTurn && inEndPhase && free && selfAlive && notUsed ? null : "现在不能发动据守"; }, async (state, _params) => { await markOncePerTurn(state, ownerId, "据守"); await pushFrame(state, "据守", ownerId, {}); try { await applyAtom(state, { type: "摸牌", player: ownerId, count: 4 }); const self = state.players[ownerId]; if (self.hand.length > 0) { delete state.localVars[DISCARD_CHOICE_KEY$1]; await applyAtom(state, { type: "请求回应", requestType: DISCARD_RT$18, target: ownerId, prompt: { type: "useCard", title: "据守:选择一张手牌弃置(装备牌将改为使用)", cardFilter: { filter: () => true, min: 1, max: 1 } }, mandatory: true, timeout: 20 }); let chosenId = state.localVars[DISCARD_CHOICE_KEY$1]; delete state.localVars[DISCARD_CHOICE_KEY$1]; if (!chosenId || !self.hand.includes(chosenId)) chosenId = self.hand[0]; if (chosenId && self.hand.includes(chosenId)) { const card = state.cardMap[chosenId]; const slot = slotOf$6(card); if (card?.type === "装备牌" && slot) { const currentEquip = state.players[ownerId].equipment[slot]; if (currentEquip) { const oldCard = state.cardMap[currentEquip]; if (oldCard?.name && hasSkillModule(oldCard.name)) await applyAtom(state, { type: "移除技能", player: ownerId, skillId: oldCard.name }); await applyAtom(state, { type: "卸下", player: ownerId, slot }); await applyAtom(state, { type: "移动牌", cardId: currentEquip, from: { zone: "手牌", player: ownerId }, to: { zone: "弃牌堆" } }); } await applyAtom(state, { type: "装备", player: ownerId, cardId: chosenId }); if (card?.name && hasSkillModule(card.name)) await applyAtom(state, { type: "添加技能", player: ownerId, skillId: card.name }); } else await applyAtom(state, { type: "弃置", player: ownerId, cardIds: [chosenId], voluntary: true }); } } } finally { await popFrame(state); } }); registerAction(state, skill.id, ownerId, "respond", (s, _params) => { const slot = s.pendingSlots.get(ownerId); if (!slot) return "当前不需要回应"; if (slot.atom.type !== "请求回应") return "当前不是选牌窗口"; if (slot.atom.requestType !== DISCARD_RT$18) return "当前不是据守弃牌窗口"; return null; }, async (s, params) => { const raw = Array.isArray(params.cardIds) ? params.cardIds[0] : params.cardId; const self = s.players[ownerId]; if (typeof raw === "string" && self?.hand.includes(raw)) s.localVars[DISCARD_CHOICE_KEY$1] = raw; }); return () => {}; } function onMount$214(_skill, api) { api.defineAction("use", { label: "据守", style: "primary", prompt: { type: "confirm", title: "是否发动据守?(翻面摸四张,然后弃置一张手牌)", confirmLabel: "发动", cancelLabel: "不发动" }, activeWhen: (ctx) => { if (ctx.view.currentPlayerIndex !== ctx.perspectiveIdx) return false; const phase = ctx.view.phase; if (phase !== "弃牌" && phase !== "回合结束") return false; const p = ctx.view.players[ctx.perspectiveIdx]; if (!p) return false; if (p.turnUsage?.[USED_KEY$17]) return false; return true; } }); } var USED_KEY$17, DISCARD_RT$18, DISCARD_CHOICE_KEY$1, 据守_default; var init_据守 = __esmMin((() => { init_apply(); init_frame(); init_once_per_turn(); init_skill(); init_registry$1(); USED_KEY$17 = "据守/usedThisTurn"; DISCARD_RT$18 = "据守/弃牌"; DISCARD_CHOICE_KEY$1 = "据守/discardChoice"; 据守_default = { createSkill: createSkill$234, onInit: onInit$234, onMount: onMount$214 }; })); //#endregion //#region src/engine/skills/掣政.ts var 掣政_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$233, default: () => _skillModule$110, onInit: () => onInit$233, onMount: () => onMount$213 }); function createSkill$233(id, ownerId) { return { id, ownerId, name: "掣政", description: "锁定技:防止你出牌阶段对攻击范围内不含你的角色造成的伤害;出牌阶段结束时若用牌数少于这些角色数,弃置其中一名角色一张牌", isLocked: true }; } /** 攻击范围内不包含 ownerId 的存活其他角色 */ function outOfRangeChars(state, ownerId) { const result = []; for (const p of state.players) { if (p.index === ownerId || !p.alive) continue; if (!inAttackRange(state, p.index, ownerId)) result.push(p.index); } return result; } /** 角色是否有任意牌(手牌或装备)可供弃置 */ function hasAnyCard(state, idx) { const p = state.players[idx]; if (!p) return false; if (p.hand.length > 0) return true; return Object.values(p.equipment).some((id) => !!id); } function onInit$233(skill, state) { const ownerId = skill.ownerId; registerBeforeHook(state, skill.id, ownerId, "伤害结算开始时", async (ctx) => { const atom = ctx.atom; if (atom.source !== ownerId) return; const st = ctx.state; if (st.phase !== "出牌") return; if (st.currentPlayerIndex !== ownerId) return; if (!inAttackRange(st, atom.target, ownerId)) return { kind: "cancel" }; }); registerAfterHook(state, skill.id, ownerId, "使用时", async (ctx) => { const st = ctx.state; if (st.phase !== "出牌") return; if (ctx.atom.source !== ownerId) return; const prev = st.turn.vars[USED_KEY$16] ?? 0; st.turn.vars[USED_KEY$16] = prev + 1; }); registerBeforeHook(state, skill.id, ownerId, "阶段结束", async (ctx) => { const atom = ctx.atom; if (atom.phase !== "出牌") return; if (atom.player !== ownerId) return; const st = ctx.state; if (!st.players[ownerId]?.alive) return; const used = st.turn.vars[USED_KEY$16] ?? 0; const outRange = outOfRangeChars(st, ownerId); if (used >= outRange.length) return; const targets = outRange.filter((t) => hasAnyCard(st, t)); if (targets.length === 0) return; await pushFrame(st, "掣政", ownerId, {}); let chosen; if (targets.length === 1) chosen = targets[0]; else { delete st.localVars[CHOOSE_KEY$1]; await applyAtom(st, { type: "请求回应", requestType: CHOOSE_RT$15, target: ownerId, prompt: { type: "choosePlayer", title: "掣政:你本阶段用牌数不足,选择一名角色弃置其一张牌", min: 1, max: 1, candidates: targets, filter: (view, t) => t !== ownerId && view.players[t]?.alive === true }, timeout: 30 }); const picked = st.localVars[CHOOSE_KEY$1]; delete st.localVars[CHOOSE_KEY$1]; if (typeof picked !== "number" || !targets.includes(picked)) { await popFrame(st); return; } chosen = picked; } const tp = st.players[chosen]; if (tp?.alive) { let discardId; if (tp.hand.length > 0) { const rng = createRng(st.rngSeed); discardId = tp.hand[rng.nextInt(tp.hand.length)]; st.rngSeed = rng.getState(); } else { const equipIds = Object.values(tp.equipment).filter((id) => !!id); if (equipIds.length > 0) discardId = equipIds[0]; } if (discardId) await applyAtom(st, { type: "弃置", player: chosen, cardIds: [discardId] }); } await popFrame(st); }); registerAction(state, skill.id, ownerId, "respond", (st, params) => { const slot = st.pendingSlots.get(ownerId); if (!slot) return "当前不需要回应"; if (slot.atom.type !== "请求回应") return "当前不是掣政窗口"; if (slot.atom.requestType !== CHOOSE_RT$15) return "当前不是掣政窗口"; const t = params.targets?.[0] ?? (typeof params.target === "number" ? params.target : void 0); if (typeof t !== "number") return "请选择一名角色"; if (t === ownerId) return "不能选择自己"; if (!st.players[t]?.alive) return "目标不合法"; if (inAttackRange(st, t, ownerId)) return "只能选择攻击范围内不包含你的角色"; return null; }, async (st, params) => { const t = params.targets?.[0] ?? (typeof params.target === "number" ? params.target : void 0); if (typeof t === "number") st.localVars[CHOOSE_KEY$1] = t; }); return () => {}; } function onMount$213(_skill, _api) { return () => {}; } var CHOOSE_RT$15, CHOOSE_KEY$1, USED_KEY$16, _skillModule$110; var init_掣政 = __esmMin((() => { init_apply(); init_frame(); init_distance(); init_skill(); init_rng(); CHOOSE_RT$15 = "掣政/选目标"; CHOOSE_KEY$1 = "掣政/所选目标"; USED_KEY$16 = "掣政/出牌数"; _skillModule$110 = { createSkill: createSkill$233, onInit: onInit$233, onMount: onMount$213 }; })); //#endregion //#region src/engine/skills/攻心.ts var 攻心_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$232, default: () => _skillModule$109, onInit: () => onInit$232, onMount: () => onMount$212 }); function createSkill$232(id, ownerId) { return { id, ownerId, name: "攻心", description: "出牌阶段限一次,观看一名其他角色手牌,可展示其中一张♥牌,弃置之或将之置于牌堆顶" }; } function onInit$232(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "use", (st, params) => { const self = st.players[ownerId]; if (!self) return "player not found"; if (!self.alive) return "你已死亡"; if (st.currentPlayerIndex !== ownerId) return "只能在你的回合使用"; if (st.phase !== "出牌") return "只能在出牌阶段使用"; if (hasBlockingPending(st)) return "当前有未完成的询问"; if (usedThisTurn$3(st, ownerId, "攻心")) return "本回合已使用过攻心"; const targets = params.targets; if (!Array.isArray(targets) || targets.length !== 1) return "需要指定一名目标"; const target = targets[0]; if (target === ownerId) return "不能对自己使用攻心"; const targetPlayer = st.players[target]; if (!targetPlayer?.alive) return "目标不合法"; if (targetPlayer.hand.length === 0) return "目标没有手牌"; return null; }, async (st, params) => { const from = ownerId; const target = params.targets[0]; await markOncePerTurn(st, from, "攻心"); await pushFrame(st, "攻心", from, { target }); const hearts = st.players[target].hand.map((id) => { const c = st.cardMap[id]; if (c?.suit !== "♥") return null; return { cardId: id, cardName: c.name, suit: c.suit, rank: c.rank }; }).filter((c) => c !== null); if (hearts.length === 0) { await popFrame(st); return; } delete st.localVars[PICK_KEY$8]; await applyAtom(st, { type: "请求回应", requestType: PICK_REQUEST$4, target: from, prompt: { type: "pickProcessingCard", title: "攻心:选择目标一张♥牌展示(放弃则不展示)", cards: hearts }, timeout: 30 }); const pickedId = st.localVars[PICK_KEY$8]; delete st.localVars[PICK_KEY$8]; if (!pickedId || !st.players[target]?.hand.includes(pickedId)) { await popFrame(st); return; } await applyAtom(st, { type: "展示", player: target, cardId: pickedId }); delete st.localVars[CHOICE_KEY$14]; await applyAtom(st, { type: "请求回应", requestType: CHOOSE_RT$14, target: from, prompt: { type: "confirm", title: "攻心:弃置此♥牌,还是将之置于牌堆顶?", confirmLabel: "弃置", cancelLabel: "置牌堆顶" }, defaultChoice: false, timeout: 30 }); const effect = st.localVars[CHOICE_KEY$14]; delete st.localVars[CHOICE_KEY$14]; if (effect === "discard") { if (st.players[target]?.hand.includes(pickedId)) await applyAtom(st, { type: "弃置", player: target, cardIds: [pickedId] }); } else if (st.players[target]?.hand.includes(pickedId)) await applyAtom(st, { type: "移动牌", cardId: pickedId, from: { zone: "手牌", player: target }, to: { zone: "牌堆" } }); await popFrame(st); }); registerAction(state, skill.id, ownerId, "respond", (st, params) => { const slot = st.pendingSlots.get(ownerId); if (!slot) return "当前不需要回应"; const atom = slot.atom; if (atom["type"] !== "请求回应") return "当前不需要回应"; const rt = atom["requestType"]; if (rt === PICK_REQUEST$4) { const cardId = params.cardId; if (cardId !== void 0) { const target = st.settlementStack[st.settlementStack.length - 1]?.params["target"]; if (target === void 0 || !st.players[target]?.hand.includes(cardId)) return "该牌不在可选范围"; if (st.cardMap[cardId]?.suit !== "♥") return "只能选择♥牌"; } return null; } if (rt === CHOOSE_RT$14) return null; return "当前不是攻心回应"; }, async (st, params) => { const rt = (st.pendingSlots.get(ownerId)?.atom)?.requestType; if (rt === PICK_REQUEST$4) { const cardId = params.cardId; if (typeof cardId === "string") st.localVars[PICK_KEY$8] = cardId; } else if (rt === CHOOSE_RT$14) st.localVars[CHOICE_KEY$14] = params.choice === true ? "discard" : "top"; }); return () => {}; } function onMount$212(skill, api) { api.defineAction("use", { label: "攻心", style: "primary", prompt: { type: "selectTarget", title: "攻心:观看一名其他角色的手牌", targetFilter: { min: 1, max: 1, filter: (view, t) => t !== skill.ownerId && (view.players[t]?.handCount ?? 0) > 0 } }, activeWhen: (ctx) => { if (!activeUnlessUsedThisTurn("攻心")(ctx)) return false; return ctx.view.players.some((other) => other.index !== skill.ownerId && other.alive && (other.handCount ?? 0) > 0); } }); api.defineAction("respond", { label: "攻心", style: "primary", prompt: { type: "pickProcessingCard", title: "攻心:选择目标一张♥牌展示", cards: [] } }); } var PICK_REQUEST$4, PICK_KEY$8, CHOOSE_RT$14, CHOICE_KEY$14, _skillModule$109; var init_攻心 = __esmMin((() => { init_apply(); init_frame(); init_once_per_turn(); init_skill(); PICK_REQUEST$4 = "攻心/选牌"; PICK_KEY$8 = "攻心/cardId"; CHOOSE_RT$14 = "攻心/选效果"; CHOICE_KEY$14 = "攻心/effect"; _skillModule$109 = { createSkill: createSkill$232, onInit: onInit$232, onMount: onMount$212 }; })); //#endregion //#region src/engine/skills/放权.ts var 放权_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$231, default: () => 放权_default, onInit: () => onInit$231, onMount: () => onMount$211 }); /** 复刻「回合结束」atom 的 per-turn 清理(cancel 回合结束后 atom.apply 不执行,需手动清理)。 * 清空 turn.vars、清所有玩家 duration='turn' 标记、清 /usedThisTurn|/healed|/givenCount vars。 * marks 多数由 end action 的 清过期标记 已清(仅结束玩家),此处补全所有玩家以保持与 atom 一致。 */ function clearPerTurnState$2(state) { state.turn.vars = {}; for (const p of state.players) { p.marks = p.marks.filter((m) => m.duration !== "turn"); p.vars = Object.fromEntries(Object.entries(p.vars).filter(([k]) => !k.endsWith("/usedThisTurn") && !k.endsWith("/healed") && !k.endsWith("/givenCount"))); } } function createSkill$231(id, ownerId) { return { id, ownerId, name: "放权", description: "跳过出牌阶段,回合结束时弃一张手牌,令一名其他角色进行一个额外回合" }; } function onInit$231(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "respond", (s, params) => { const slot = s.pendingSlots.get(ownerId); if (slot?.atom.type !== "请求回应") return "当前不需要回应"; const rt = slot.atom.requestType; if (rt === TRIGGER_RT$8) return null; if (rt === DISCARD_RT$17) { const cardId = params.cardId; if (typeof cardId !== "string") return "请选择一张手牌弃置"; if (!s.players[ownerId].hand.includes(cardId)) return "牌不在手牌中"; return null; } if (rt === CHOOSE_TARGET_RT$4) { const target = params.target; if (typeof target !== "number") return "请选择一名其他角色"; if (target === ownerId) return "不能选择自己"; if (!s.players[target]?.alive) return "目标已死亡"; return null; } return "当前不是放权回应"; }, async (s, params) => { const rt = (s.pendingSlots.get(ownerId)?.atom)?.requestType; if (rt === TRIGGER_RT$8) s.localVars[TRIGGERED_KEY$4] = params.choice === true || params.confirmed === true; else if (rt === DISCARD_RT$17) s.localVars[DISCARD_CARD_KEY$2] = params.cardId; else if (rt === CHOOSE_TARGET_RT$4) s.localVars[EXTRA_TARGET_KEY$1] = params.target; }); registerBeforeHook(state, skill.id, ownerId, "阶段开始", async (ctx) => { const atom = ctx.atom; if (atom.type !== "阶段开始") return; if (atom.player !== ownerId) return; if (atom.phase !== "出牌") return; if (ctx.state.currentPlayerIndex !== ownerId) return; if (!ctx.state.players[ownerId]?.alive) return; delete ctx.state.localVars[TRIGGERED_KEY$4]; await applyAtom(ctx.state, { type: "请求回应", requestType: TRIGGER_RT$8, target: ownerId, prompt: { type: "confirm", title: "是否发动放权?(跳过出牌阶段,回合结束时弃一张手牌令其他角色额外回合)", confirmLabel: "发动", cancelLabel: "不发动" }, defaultChoice: false, timeout: 10 }); if (ctx.state.localVars[TRIGGERED_KEY$4] !== true) return; ctx.state.localVars[ACTIVE_KEY$4] = true; await applyAtom(ctx.state, { type: "阶段结束", player: ownerId, phase: "出牌" }); return { kind: "cancel" }; }); registerBeforeHook(state, skill.id, ownerId, "回合结束", async (ctx) => { const atom = ctx.atom; if (atom.type !== "回合结束") return; const st = ctx.state; const player = atom.player; if (typeof player !== "number") return; if (player === ownerId && st.localVars[ACTIVE_KEY$4] === true) { delete st.localVars[ACTIVE_KEY$4]; const self = st.players[ownerId]; if (!self?.alive) return; if (self.hand.length === 0) return; delete st.localVars[DISCARD_CARD_KEY$2]; await applyAtom(st, { type: "请求回应", requestType: DISCARD_RT$17, target: ownerId, prompt: { type: "useCard", title: "放权:弃置一张手牌", cardFilter: { filter: () => true, min: 1, max: 1 } }, timeout: 15 }); const discardCardId = st.localVars[DISCARD_CARD_KEY$2]; delete st.localVars[DISCARD_CARD_KEY$2]; if (!discardCardId || !self.hand.includes(discardCardId)) return; await applyAtom(st, { type: "弃置", player: ownerId, cardIds: [discardCardId], voluntary: true }); delete st.localVars[EXTRA_TARGET_KEY$1]; await applyAtom(st, { type: "请求回应", requestType: CHOOSE_TARGET_RT$4, target: ownerId, prompt: { type: "choosePlayer", title: "放权:选择一名其他角色进行一个额外回合", min: 1, max: 1, filter: (view, target) => target !== ownerId && view.players[target]?.alive === true }, timeout: 15 }); const extraTarget = st.localVars[EXTRA_TARGET_KEY$1]; if (typeof extraTarget !== "number" || extraTarget === ownerId || !st.players[extraTarget]?.alive) return; const originalNext = st.currentPlayerIndex; st.localVars[EXTRA_TARGET_KEY$1] = extraTarget; st.localVars[ORIGINAL_NEXT_KEY$1] = originalNext; clearPerTurnState$2(st); st.currentPlayerIndex = extraTarget; await startTurn(st, extraTarget); return { kind: "cancel" }; } if (player === st.localVars[EXTRA_TARGET_KEY$1]) { const originalNext = st.localVars[ORIGINAL_NEXT_KEY$1]; delete st.localVars[EXTRA_TARGET_KEY$1]; delete st.localVars[ORIGINAL_NEXT_KEY$1]; if (typeof originalNext !== "number" || !st.players[originalNext]?.alive) return; clearPerTurnState$2(st); st.currentPlayerIndex = originalNext; await startTurn(st, originalNext); return { kind: "cancel" }; } }); return () => {}; } function onMount$211(_skill, api) { api.defineAction("respond", { label: "放权", style: "primary", prompt: { type: "confirm", title: "是否发动放权?", confirmLabel: "发动", cancelLabel: "不发动" } }); } var TRIGGER_RT$8, DISCARD_RT$17, CHOOSE_TARGET_RT$4, TRIGGERED_KEY$4, ACTIVE_KEY$4, DISCARD_CARD_KEY$2, EXTRA_TARGET_KEY$1, ORIGINAL_NEXT_KEY$1, 放权_default; var init_放权 = __esmMin((() => { init_apply(); init_turn(); init_skill(); TRIGGER_RT$8 = "放权/trigger"; DISCARD_RT$17 = "放权/discard"; CHOOSE_TARGET_RT$4 = "放权/chooseTarget"; TRIGGERED_KEY$4 = "放权/triggered"; ACTIVE_KEY$4 = "放权/active"; DISCARD_CARD_KEY$2 = "放权/discardCard"; EXTRA_TARGET_KEY$1 = "放权/extraTarget"; ORIGINAL_NEXT_KEY$1 = "放权/originalNext"; 放权_default = { createSkill: createSkill$231, onInit: onInit$231, onMount: onMount$211 }; })); //#endregion //#region src/engine/skills/放逐.ts var 放逐_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$230, default: () => 放逐_default, onInit: () => onInit$230, onMount: () => onMount$210 }); function createSkill$230(id, ownerId) { return { id, ownerId, name: "放逐", description: "受到伤害后,令一名其他角色摸 X 张牌(X=已损失体力)并翻面" }; } function onInit$230(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "respond", (st, _params) => { const slot = st.pendingSlots.get(ownerId); if (!slot) return "当前不需要回应"; const atom = slot.atom; if (atom["type"] !== "请求回应") return "当前不需要回应"; const rt = atom["requestType"]; if (rt !== CONFIRM_RT$57 && rt !== TARGET_RT$21) return "当前不是放逐询问"; return null; }, async (st, params) => { const rt = (st.pendingSlots.get(ownerId)?.atom)?.requestType; if (rt === CONFIRM_RT$57) st.localVars[CONFIRMED_KEY$47] = params.choice === true; else if (rt === TARGET_RT$21) { const t = params.targets?.[0] ?? (typeof params.target === "number" ? params.target : void 0); if (typeof t === "number") st.localVars[TARGET_KEY$31] = t; } }); registerAfterHook(state, skill.id, ownerId, "受到伤害后", async (ctx) => { const atom = ctx.atom; if (atom.target !== ownerId) return; if ((atom.amount ?? 0) <= 0) return; const self = ctx.state.players[ownerId]; if (!self?.alive) return; const lostHealth = self.maxHealth - getHealthValue(self); if (lostHealth <= 0) return; if (!ctx.state.players.some((p) => p.alive && p.index !== ownerId)) return; delete ctx.state.localVars[CONFIRMED_KEY$47]; await applyAtom(ctx.state, { type: "请求回应", requestType: CONFIRM_RT$57, target: ownerId, prompt: { type: "confirm", title: `是否发动放逐?(令一名其他角色摸 ${lostHealth} 张牌并翻面)`, confirmLabel: "发动", cancelLabel: "不发动" }, defaultChoice: false, timeout: 10 }); if (!ctx.state.localVars[CONFIRMED_KEY$47]) return; delete ctx.state.localVars[TARGET_KEY$31]; await applyAtom(ctx.state, { type: "请求回应", requestType: TARGET_RT$21, target: ownerId, prompt: { type: "choosePlayer", title: "放逐:选择一名其他角色(摸牌并翻面)", min: 1, max: 1, filter: (_view, t) => t !== ownerId && ctx.state.players[t]?.alive === true }, timeout: 15 }); const target = ctx.state.localVars[TARGET_KEY$31]; delete ctx.state.localVars[TARGET_KEY$31]; if (typeof target !== "number") return; if (!ctx.state.players[target]?.alive) return; await applyAtom(ctx.state, { type: "摸牌", player: target, count: lostHealth }); await flipFaceDown(ctx.state, target, "放逐"); }); registerBeforeHook(state, skill.id, ownerId, "阶段开始", async (ctx) => { const atom = ctx.atom; if (atom.type !== "阶段开始") return; const player = atom.player; if (player === void 0) return; const p = ctx.state.players[player]; if (!p) return; if (atom.phase === "准备" && p.tags.includes(SKIP_TAG$3)) { await flipFaceUp(ctx.state, player, "放逐"); ctx.state.localVars[SKIP_FLAG$7] = player; return { kind: "cancel" }; } if (ctx.state.localVars[SKIP_FLAG$7] === player) return { kind: "cancel" }; }); registerBeforeHook(state, skill.id, ownerId, "阶段结束", async (ctx) => { const atom = ctx.atom; if (atom.type !== "阶段结束") return; const player = atom.player; if (player === void 0) return; if (ctx.state.localVars[SKIP_FLAG$7] !== player) return; delete ctx.state.localVars[SKIP_FLAG$7]; await performSkipTurn(ctx.state, player); return { kind: "cancel" }; }); return () => {}; } function onMount$210(_skill, api) { api.defineAction("respond", { label: "放逐", style: "primary", prompt: { type: "confirm", title: "是否发动放逐?", confirmLabel: "发动", cancelLabel: "不发动" } }); } var CONFIRM_RT$57, TARGET_RT$21, CONFIRMED_KEY$47, TARGET_KEY$31, SKIP_TAG$3, SKIP_FLAG$7, 放逐_default; var init_放逐 = __esmMin((() => { init_types(); init_apply(); init_face_down(); init_skill(); CONFIRM_RT$57 = "放逐/confirm"; TARGET_RT$21 = "放逐/target"; CONFIRMED_KEY$47 = "放逐/confirmed"; TARGET_KEY$31 = "放逐/target"; SKIP_TAG$3 = "放逐/翻面"; SKIP_FLAG$7 = "放逐/skipAll"; 放逐_default = { createSkill: createSkill$230, onInit: onInit$230, onMount: onMount$210 }; })); //#endregion //#region src/engine/skills/救援.ts var 救援_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$229, default: () => 救援_default, onInit: () => onInit$229 }); function createSkill$229(id, ownerId) { return { id, ownerId, name: "救援", description: "主公技,锁定技:其他吴势力角色对你使用【桃】的回复值+1", isLocked: true }; } function onInit$229(skill, state) { const ownerId = skill.ownerId; registerAfterHook(state, skill.id, ownerId, "回复体力", async (ctx) => { const atom = ctx.atom; if (atom.target !== ownerId) return; if (ownerId !== 0) return; const source = atom.source; if (typeof source !== "number" || source === ownerId) return; if (ctx.state.localVars["求桃/已救"] !== true) return; const rescuer = ctx.state.players[source]; if (!rescuer?.alive) return; if (rescuer.faction !== "吴") return; await applyAtom(ctx.state, { type: "回复体力", target: ownerId, amount: 1 }); }); return () => {}; } var 救援_default; var init_救援 = __esmMin((() => { init_apply(); init_skill(); 救援_default = { createSkill: createSkill$229, onInit: onInit$229 }; })); //#endregion //#region src/engine/skills/整论.ts var 整论_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$228, default: () => _skillModule$108, onInit: () => onInit$228, onMount: () => onMount$209 }); function createSkill$228(id, ownerId) { return { id, ownerId, name: SKILL_ID$51, description: "若你没有橘,你可以跳过摸牌阶段并获得1枚橘" }; } /** 当前 pending 的 requestType(类型安全读取) */ function currentRequestType$13(state, ownerId) { const slot = state.pendingSlots.get(ownerId); if (!slot) return void 0; return slot.atom.requestType; } function onInit$228(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "respond", (st, _params) => { if (currentRequestType$13(st, ownerId) !== CONFIRM_RT$56) return "当前不是整论询问"; return null; }, async (st, params) => { st.localVars[CONFIRM_KEY$21] = params.choice === true || params.confirmed === true; }); registerBeforeHook(state, skill.id, ownerId, "阶段开始", async (ctx) => { const atom = ctx.atom; if (atom.type !== "阶段开始") return; if (atom.phase !== "摸牌") return; if (atom.player !== ownerId) return; const st = ctx.state; if (!st.players[ownerId]?.alive) return; if (juCount(st, ownerId) > 0) return; delete st.localVars[CONFIRM_KEY$21]; await applyAtom(st, { type: "请求回应", requestType: CONFIRM_RT$56, target: ownerId, prompt: { type: "confirm", title: "整论:你没有橘标记,是否跳过摸牌阶段并获得 1 枚橘?", confirmLabel: "跳过并获橘", cancelLabel: "正常摸牌" }, defaultChoice: false, timeout: 20 }); if (!st.localVars[CONFIRM_KEY$21]) { delete st.localVars[CONFIRM_KEY$21]; return; } delete st.localVars[CONFIRM_KEY$21]; await addJu(st, ownerId); return skipPhase(st, { player: ownerId, phase: "摸牌" }); }); return () => {}; } function onMount$209(_skill, _api) { return () => {}; } var SKILL_ID$51, CONFIRM_RT$56, CONFIRM_KEY$21, _skillModule$108; var init_整论 = __esmMin((() => { init_apply(); init_skill(); init_skip_phase(); init_怀橘(); SKILL_ID$51 = "整论"; CONFIRM_RT$56 = `${SKILL_ID$51}/confirm`; CONFIRM_KEY$21 = `${SKILL_ID$51}/confirmed`; _skillModule$108 = { createSkill: createSkill$228, onInit: onInit$228, onMount: onMount$209 }; })); //#endregion //#region src/engine/skills/断粮.ts var 断粮_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$227, onInit: () => onInit$227, onMount: () => onMount$208 }); /** 断粮可用牌:黑色(♠/♣)的基本牌或装备牌 */ function isDuanliangCard$1(card) { if (!card) return false; if (card.color !== "黑") return false; return card.type === "基本牌" || card.type === "装备牌"; } function createSkill$227(id, ownerId) { return { id, ownerId, name: "断粮", description: "将一张黑色基本牌或装备牌当【兵粮寸断】使用;对手牌数不小于你的角色无距离限制" }; } function onInit$227(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "use", (state, params) => { const myTurn = state.currentPlayerIndex === ownerId; const inActPhase = state.phase === "出牌"; const free = !hasBlockingPending(state); const self = state.players[ownerId]; if (!self?.alive) return "玩家不存在或已死亡"; if (typeof params.cardId !== "string") return "需要选择一张牌"; if (typeof params.target !== "number") return "需要选择目标"; if (!self.hand.includes(params.cardId)) return "牌不在手牌中"; if (!isDuanliangCard$1(state.cardMap[params.cardId])) return "需要黑色基本牌或装备牌"; const target = params.target; if (target === ownerId) return "不能对自己使用"; const targetPlayer = state.players[target]; if (!targetPlayer?.alive) return "目标不存在或已死亡"; if (targetPlayer.pendingTricks.some((t) => t.name === TRICK_NAME$2)) return "目标判定区已有兵粮寸断"; const inRange = targetPlayer.hand.length >= self.hand.length || effectiveDistance(state, ownerId, target) <= 1; return myTurn && inActPhase && free && inRange ? null : "断粮使用条件不满足"; }, async (state, params) => { const from = ownerId; const cardId = params.cardId; const target = params.target; await pushFrame(state, "断粮", from, { ...params }); await applyAtom(state, { type: "移动牌", cardId, from: { zone: "手牌", player: from }, to: { zone: "处理区" } }); await applyAtom(state, { type: "添加延时锦囊", player: target, trick: { name: TRICK_NAME$2, source: from, card: state.cardMap[cardId] ?? { id: cardId, name: TRICK_NAME$2, suit: "♣", color: "黑", rank: "A", type: "锦囊牌" } } }); await applyAtom(state, { type: "移动牌", cardId, from: { zone: "处理区" }, to: { zone: "弃牌堆" } }); await popFrame(state); }); return () => {}; } function onMount$208(skill, api) { api.defineAction("use", { label: "断粮", style: "danger", prompt: { type: "useCardAndTarget", title: "断粮:将黑色基本牌/装备牌当兵粮寸断使用", cardFilter: { filter: (c) => isDuanliangCard$1(c), min: 1, max: 1 }, targetFilter: { min: 1, max: 1, filter: (view, t) => { const me = view.currentPlayerIndex; if (t === me) return false; const tp = view.players[t]; if (!tp) return false; if (tp.alive === false) return false; const myHand = view.players[me]?.handCount ?? 0; if ((tp.handCount ?? 0) >= myHand) return true; return viewEffectiveDistance(view.players, me, t) <= 1; } } }, activeWhen: (ctx) => { if (!defaultPlayActive(ctx)) return false; const p = ctx.view.players[ctx.perspectiveIdx]; if (!p) return false; return p.hand?.some((c) => isDuanliangCard$1(c)) ?? false; } }); } var TRICK_NAME$2; var init_断粮 = __esmMin((() => { init_apply(); init_frame(); init_action_active(); init_skill(); init_distance(); init_viewDistance(); TRICK_NAME$2 = "兵粮寸断"; })); //#endregion //#region src/engine/skills/断肠.ts var 断肠_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$226, default: () => 断肠_default, onInit: () => onInit$226 }); function createSkill$226(id, ownerId) { return { id, ownerId, name: "断肠", description: "杀死你的角色立即失去所有技能直到游戏结束(装备仍生效)", isLocked: true }; } function onInit$226(skill, state) { const ownerId = skill.ownerId; registerAfterHook(state, skill.id, ownerId, "死亡时", async (ctx) => { const atom = ctx.atom; if (atom.type !== "死亡时") return; if (atom.player !== ownerId) return; const killer = atom.killer; if (killer === void 0) return; if (killer === ownerId) return; const killerPlayer = ctx.state.players[killer]; if (!killerPlayer?.alive) return; const equippedNames = /* @__PURE__ */ new Set(); for (const id of Object.values(killerPlayer.equipment)) { if (!id) continue; const name = ctx.state.cardMap[id]?.name; if (name) equippedNames.add(name); } const toRemove = killerPlayer.skills.filter((s) => !DEFAULT_SKILLS_SET.has(s) && !equippedNames.has(s)); for (const skillId of toRemove) await applyAtom(ctx.state, { type: "移除技能", player: killer, skillId }); }); return () => {}; } var DEFAULT_SKILLS_SET, 断肠_default; var init_断肠 = __esmMin((() => { init_apply(); init_skill(); init_选将(); DEFAULT_SKILLS_SET = new Set(DEFAULT_SKILLS); 断肠_default = { createSkill: createSkill$226, onInit: onInit$226 }; })); //#endregion //#region src/engine/skills/新生.ts var 新生_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$225, default: () => 新生_default, onInit: () => onInit$225, onMount: () => onMount$207 }); /** 本局已登场武将名集合。 */ function debutedCharacters(state) { const set = /* @__PURE__ */ new Set(); for (const p of state.players) if (p.character) set.add(p.character); return set; } /** * 从未登场武将中随机抽 1 张(排除本局已登场 + owner 化身牌池已有)。 * 用 state.rngSeed 派生 RNG 并推进写回,保证确定性。 * 池为空(所有武将都已登场/在牌池中)时返回 null。 */ function drawNew化身Card(state, ownerId) { const debuted = debutedCharacters(state); const existingPool = getHuashenPool(state.players[ownerId]?.vars) ?? []; const taken = new Set([...debuted, ...existingPool]); const available = allCharacters.map((c) => c.name).filter((name) => !taken.has(name)); if (available.length === 0) return null; const rng = createRng(state.rngSeed); const idx = rng.nextInt(available.length); state.rngSeed = rng.getState(); return available[idx]; } function createSkill$225(id, ownerId) { return { id, ownerId, name: "新生", description: "每当你受到 1 点伤害后,你可以获得一张新的化身牌" }; } function onInit$225(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "respond", (st, params) => { const slot = st.pendingSlots.get(ownerId); if (!slot) return "当前不需要回应"; const atom = slot.atom; if (atom["type"] !== "请求回应") return "当前不需要回应"; if (atom["requestType"] !== CONFIRM_REQUEST$15) return "当前不是新生询问"; if (typeof params.choice !== "boolean") return "需要 choice(布尔)"; return null; }, async (st, params) => { st.localVars[CHOICE_KEY$13] = params.choice === true; }); registerAfterHook(state, skill.id, ownerId, "受到伤害后", async (ctx) => { const atom = ctx.atom; if (atom.target !== ownerId) return; if ((atom.amount ?? 0) <= 0) return; const st = ctx.state; if (!st.players[ownerId]?.alive) return; if (drawNew化身CardSnapshot(st, ownerId) === null) return; delete st.localVars[CHOICE_KEY$13]; await applyAtom(st, { type: "请求回应", requestType: CONFIRM_REQUEST$15, target: ownerId, prompt: { type: "confirm", title: "新生:是否获得一张新的化身牌?", confirmLabel: "获得", cancelLabel: "不获得" }, defaultChoice: false, timeout: 30 }); const choice = st.localVars[CHOICE_KEY$13]; delete st.localVars[CHOICE_KEY$13]; if (choice !== true) return; const drawn = drawNew化身Card(st, ownerId); if (drawn === null) return; const player = st.players[ownerId]; const pool = getHuashenPool(player.vars) ?? []; pool.push(drawn); player.vars[HUASHEN_POOL_KEY] = pool; }); return () => {}; } /** * 只读快照:探测是否还有可抽的未登场武将(不推进 rng)。 * 用于在询问前判断是否值得询问玩家。 */ function drawNew化身CardSnapshot(state, ownerId) { const debuted = debutedCharacters(state); const existingPool = getHuashenPool(state.players[ownerId]?.vars) ?? []; const taken = new Set([...debuted, ...existingPool]); const available = allCharacters.map((c) => c.name).filter((name) => !taken.has(name)); return available.length > 0 ? available[0] : null; } function onMount$207(_skill, api) { api.defineAction("respond", { label: "新生", style: "passive", prompt: { type: "confirm", title: "新生:是否获得一张新的化身牌?", confirmLabel: "获得", cancelLabel: "不获得" } }); } var CONFIRM_REQUEST$15, CHOICE_KEY$13, 新生_default; var init_新生 = __esmMin((() => { init_apply(); init_skill(); init_characters(); init_rng(); init_vars_keys(); CONFIRM_REQUEST$15 = "新生/confirm"; CHOICE_KEY$13 = "新生/choice"; 新生_default = { createSkill: createSkill$225, onInit: onInit$225, onMount: onMount$207 }; })); //#endregion //#region src/engine/skills/方天画戟.ts var 方天画戟_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$224, onInit: () => onInit$224 }); function createSkill$224(id, ownerId) { return { id, ownerId, name: "方天画戟", description: "武器:最后一张手牌为杀时可指定最多3个目标", isLocked: true }; } function onInit$224(skill, state) { const ownerId = skill.ownerId; return registerSlashTargetProvider(state, ownerId, (st, _p, _cardId) => { const weaponId = st.players[ownerId]?.equipment["武器"]; if (!weaponId || st.cardMap[weaponId]?.name !== "方天画戟") return 0; if ((st.players[ownerId]?.hand)?.length !== 1) return 0; return 3; }); } var init_方天画戟 = __esmMin((() => { init_slash_target(); })); //#endregion //#region src/engine/skills/无双.ts var 无双_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$223, default: () => 无双_default, onInit: () => onInit$223 }); function createSkill$223(id, ownerId) { return { id, ownerId, name: "无双", description: "锁定技:你使用【杀】的目标需连续出两张【闪】才能抵消;与你【决斗】的角色每次需连续打出两张【杀」", isLocked: true }; } function dodgeCountKey$1(killCardId, target) { return `无双/dodgeCount/${killCardId}/${target}`; } /** 无双决斗:每轮决斗内、针对某个被询问出杀者的双杀计数键(防第二次询问杀的 after-hook 再追加) */ function duelKillCountKey(duelCardId, target) { return `无双/duelCount/${duelCardId}/${target}`; } function onInit$223(skill, state) { const ownerId = skill.ownerId; registerAfterHook(state, skill.id, ownerId, "询问闪", async (ctx) => { const atom = ctx.atom; if (atom.source !== ownerId) return; if (!ctx.state.players[ownerId]?.skills.includes("无双")) return; const target = atom.target; const killCardId = getKillCardId$1(ctx.state); if (ctx.state.cardMap[killCardId]?.name !== "杀") return; if (!isCancelled(ctx.state, killCardId, target)) return; const countKey = dodgeCountKey$1(killCardId, target); const count = ctx.state.localVars[countKey] ?? 0; if (count >= 1) { delete ctx.state.localVars[countKey]; return; } clearCancelled(ctx.state, killCardId, target); ctx.state.localVars[countKey] = count + 1; await applyAtom(ctx.state, { type: "询问闪", target, source: ownerId }); delete ctx.state.localVars[countKey]; }); registerAfterHook(state, skill.id, ownerId, "询问杀", async (ctx) => { const atom = ctx.atom; if (atom.source !== ownerId) return; if (!ctx.state.players[ownerId]?.skills.includes("无双")) return; const frameCardId = ctx.state.settlementStack[ctx.state.settlementStack.length - 1]?.params?.cardId; if (!frameCardId || ctx.state.cardMap[frameCardId]?.name !== "决斗") return; const target = atom.target; const countKey = duelKillCountKey(frameCardId, target); const count = ctx.state.localVars[countKey] ?? 0; if (count >= 1) { delete ctx.state.localVars[countKey]; return; } if ((await consumePlayedSlashes(ctx.state)).length === 0) return; ctx.state.localVars[countKey] = count + 1; await applyAtom(ctx.state, { type: "询问杀", target, source: ownerId }); delete ctx.state.localVars[countKey]; }); return () => {}; } /** 从结算帧栈顶部找到杀的 cardId */ function getKillCardId$1(state) { return state.settlementStack[state.settlementStack.length - 1]?.params?.cardId ?? ""; } var 无双_default; var init_无双 = __esmMin((() => { init_apply(); init_skill(); init_frame(); init_play_card(); 无双_default = { createSkill: createSkill$223, onInit: onInit$223 }; })); //#endregion //#region src/engine/skills/明任.ts var 明任_exports = /* @__PURE__ */ __exportAll({ REN_MARK_ID: () => REN_MARK_ID, createSkill: () => createSkill$222, default: () => _skillModule$107, getRenCardId: () => getRenCardId, getRenColor: () => getRenColor, onInit: () => onInit$222, onMount: () => onMount$206 }); /** 取玩家当前"任"对应的牌 id(mark.payload.cardId);无任返回 undefined。 */ function getRenCardId(state, player) { const cardId = ((state.players[player]?.marks.find((m) => m.id === REN_MARK_ID))?.payload)?.cardId; return typeof cardId === "string" ? cardId : void 0; } /** 取玩家当前"任"牌的颜色;无任或牌已丢失返回 undefined。 */ function getRenColor(state, player) { const cardId = getRenCardId(state, player); if (!cardId) return void 0; return state.cardMap[cardId]?.color; } /** 询问玩家选 1 张手牌置为"任",完成 弃置 + 加任标记。无手牌则跳过。 */ async function placeRenFromHand(state, player) { const self = state.players[player]; if (!self?.alive || self.hand.length === 0) return; delete state.localVars[PLACE_KEY]; await applyAtom(state, { type: "请求回应", requestType: PLACE_RT, target: player, prompt: { type: "useCard", title: "明任:选择一张手牌置为\"任\"", cardFilter: { filter: () => true, min: 1, max: 1 } }, timeout: 30 }); const cardIds = state.localVars[PLACE_KEY]; delete state.localVars[PLACE_KEY]; if (!Array.isArray(cardIds) || cardIds.length === 0) return; const cardId = cardIds[0]; if (!self.hand.includes(cardId)) return; await applyAtom(state, { type: "弃置", player, cardIds: [cardId] }); await applyAtom(state, { type: "加标记", player, mark: { id: REN_MARK_ID, scope: player, payload: { cardId } } }); } function createSkill$222(id, ownerId) { return { id, ownerId, name: SKILL_ID$50, description: "游戏开始时摸两张牌并将一张手牌置为\"任\";结束阶段可用手牌替换\"任\"" }; } function onInit$222(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "respond", (st, params) => { const slot = st.pendingSlots.get(ownerId); if (!slot) return "当前不需要回应"; const atom = slot.atom; if (atom.type !== "请求回应") return "当前不需要回应"; const rt = atom.requestType; if (rt === CONFIRM_RT$55) return null; if (rt === PLACE_RT) { const raw = params.cardIds ?? params.cardId; const ids = Array.isArray(raw) ? raw : typeof raw === "string" ? [raw] : void 0; if (!ids || ids.length === 0) return null; const self = st.players[ownerId]; if (!ids.every((id) => self.hand.includes(id))) return "牌不在手牌中"; return null; } return "当前不是明任回应"; }, async (st, params) => { const rt = (st.pendingSlots.get(ownerId)?.atom)?.requestType; if (rt === CONFIRM_RT$55) st.localVars[CONFIRMED_KEY$46] = params.choice === true || params.confirmed === true; else if (rt === PLACE_RT) { const raw = params.cardIds ?? params.cardId; const ids = Array.isArray(raw) ? raw : typeof raw === "string" ? [raw] : void 0; if (ids && ids.length > 0) st.localVars[PLACE_KEY] = ids; } }); registerAfterHook(state, skill.id, ownerId, "回合开始", async (ctx) => { const st = ctx.state; if (st.localVars[INIT_KEY$3(ownerId)]) return; if (!st.players[ownerId]?.alive) return; st.localVars[INIT_KEY$3(ownerId)] = true; await applyAtom(st, { type: "摸牌", player: ownerId, count: 2 }); await placeRenFromHand(st, ownerId); }); registerAfterHook(state, skill.id, ownerId, "阶段开始", async (ctx) => { const atom = ctx.atom; if (atom.type !== "阶段开始") return; if (atom.phase !== "回合结束") return; if (atom.player !== ownerId) return; const st = ctx.state; const self = st.players[ownerId]; if (!self?.alive) return; if (!getRenCardId(st, ownerId)) return; if (self.hand.length === 0) return; delete st.localVars[CONFIRMED_KEY$46]; await applyAtom(st, { type: "请求回应", requestType: CONFIRM_RT$55, target: ownerId, prompt: { type: "confirm", title: "明任:是否用手牌替换\"任\"?", confirmLabel: "替换", cancelLabel: "不替换" }, defaultChoice: false, timeout: 15 }); if (!st.localVars[CONFIRMED_KEY$46]) return; delete st.localVars[CONFIRMED_KEY$46]; delete st.localVars[PLACE_KEY]; await applyAtom(st, { type: "请求回应", requestType: PLACE_RT, target: ownerId, prompt: { type: "useCard", title: "明任:选择一张手牌替换\"任\"", cardFilter: { filter: () => true, min: 1, max: 1 } }, timeout: 30 }); const cardIds = st.localVars[PLACE_KEY]; delete st.localVars[PLACE_KEY]; if (!Array.isArray(cardIds) || cardIds.length === 0) return; const cardId = cardIds[0]; if (!self.hand.includes(cardId)) return; await applyAtom(st, { type: "去标记", player: ownerId, markId: REN_MARK_ID }); await applyAtom(st, { type: "弃置", player: ownerId, cardIds: [cardId] }); await applyAtom(st, { type: "加标记", player: ownerId, mark: { id: REN_MARK_ID, scope: ownerId, payload: { cardId } } }); }); return () => {}; } function onMount$206(_skill, api) { api.defineAction("respond", { label: SKILL_ID$50, style: "primary", prompt: { type: "useCard", title: "明任:选择一张手牌", cardFilter: { filter: () => true, min: 1, max: 1 } } }); return () => {}; } var SKILL_ID$50, REN_MARK_ID, INIT_KEY$3, PLACE_RT, CONFIRM_RT$55, PLACE_KEY, CONFIRMED_KEY$46, _skillModule$107; var init_明任 = __esmMin((() => { init_apply(); init_skill(); SKILL_ID$50 = "明任"; REN_MARK_ID = "明任/任"; INIT_KEY$3 = (ownerId) => `${SKILL_ID$50}/init/${ownerId}`; PLACE_RT = `${SKILL_ID$50}/选牌`; CONFIRM_RT$55 = `${SKILL_ID$50}/confirm`; PLACE_KEY = `${SKILL_ID$50}/选牌结果`; CONFIRMED_KEY$46 = `${SKILL_ID$50}/confirmed`; _skillModule$107 = { createSkill: createSkill$222, onInit: onInit$222, onMount: onMount$206 }; })); //#endregion //#region src/engine/skills/暴虐.ts var 暴虐_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$221, default: () => 暴虐_default, onInit: () => onInit$221, onMount: () => onMount$205 }); function createSkill$221(id, ownerId) { return { id, ownerId, name: "暴虐", description: "主公技:其他群雄角色造成伤害后,可判定,黑桃则回复1点体力" }; } function onInit$221(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "respond", (s, _params) => { const slot = s.pendingSlots.get(ownerId); if (slot?.atom.type !== "请求回应") return "当前不需要回应"; if (slot.atom.requestType !== CONFIRM_RT$54) return "当前不是暴虐确认"; return null; }, async (s, params) => { s.localVars[CONFIRMED_KEY$45] = params.choice === true || params.confirmed === true; }); registerAfterHook(state, skill.id, ownerId, "造成伤害后", async (ctx) => { const atom = ctx.atom; const self = ctx.state.players[ownerId]; if (!self?.alive) return; if (self.identity !== "主公") return; if ((atom.amount ?? 0) <= 0) return; const sourceIdx = atom.source; if (typeof sourceIdx !== "number") return; if (sourceIdx === ownerId) return; if (sourceIdx < 0) return; const source = ctx.state.players[sourceIdx]; if (!source?.alive) return; if (source.faction !== "群") return; delete ctx.state.localVars[CONFIRMED_KEY$45]; await applyAtom(ctx.state, { type: "请求回应", requestType: CONFIRM_RT$54, target: ownerId, prompt: { type: "confirm", title: `暴虐:${source.name} 造成伤害,是否进行判定?(黑桃回复1点体力)`, confirmLabel: "判定", cancelLabel: "不判定" }, defaultChoice: false, timeout: 10 }); if (!ctx.state.localVars[CONFIRMED_KEY$45]) return; await runJudgeFlow(ctx.state, ownerId, "暴虐"); }); registerAfterHook(state, skill.id, ownerId, "判定牌生效后", async (ctx) => { const atom = ctx.atom; if (atom.judgeType !== "暴虐") return; if (atom.player !== ownerId) return; const processing = frameCards(ctx.state); if (processing.length === 0) return; const judgeCardId = processing[processing.length - 1]; const judgeCard = ctx.state.cardMap[judgeCardId]; if (!judgeCard) return; if (judgeCard.suit !== "♠") return; if (!ctx.state.players[ownerId]?.alive) return; await applyAtom(ctx.state, { type: "回复体力", target: ownerId, amount: 1 }); }); return () => {}; } function onMount$205(_skill, api) { api.defineAction("respond", { label: "暴虐", style: "default", prompt: { type: "confirm", title: "是否发动暴虐?(判定,黑桃回复1点体力)", confirmLabel: "判定", cancelLabel: "不发动" } }); } var CONFIRM_RT$54, CONFIRMED_KEY$45, 暴虐_default; var init_暴虐 = __esmMin((() => { init_apply(); init_frame(); init_judge(); init_skill(); CONFIRM_RT$54 = "暴虐/confirm"; CONFIRMED_KEY$45 = "暴虐/confirmed"; 暴虐_default = { createSkill: createSkill$221, onInit: onInit$221, onMount: onMount$205 }; })); //#endregion //#region src/engine/skills/朱雀羽扇.ts var 朱雀羽扇_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$220, default: () => 朱雀羽扇_default, onInit: () => onInit$220, onMount: () => onMount$204 }); /** 是否为普通杀(damageType 为空或 '普通',即非火杀/雷杀) */ function isNormalSlash(card) { return !!card && card.name === "杀" && (!card.damageType || card.damageType === "普通"); } /** 影子卡 id:${原id}#朱雀羽扇 */ function shadowIdOf$20(cardId) { return `${cardId}#朱雀羽扇`; } function origTypeKey(cardId) { return `${ORIG_PREFIX}${cardId}`; } /** 还原一张被羽扇临时改性的牌的原属性(幂等;未被改性时无操作)。 */ function restoreOrigType(state, cardId) { const key = origTypeKey(cardId); if (!(key in state.localVars)) return; const orig = state.localVars[key]; delete state.localVars[key]; const card = state.cardMap[cardId]; if (!card) return; if (orig === null) delete card.damageType; else card.damageType = orig; } function createSkill$220(id, ownerId) { return { id, ownerId, name: "朱雀羽扇", description: "你可将一张普通杀当火杀使用或打出。" }; } function onInit$220(skill, state) { const ownerId = skill.ownerId; const unloaders = []; unloaders.push(registerAction(state, skill.id, ownerId, "transform", (state, params) => { const myTurn = state.currentPlayerIndex === ownerId; const inActPhase = state.phase === "出牌"; const free = !hasBlockingPending(state); const self = state.players[ownerId]; const selfAlive = self?.alive === true; const cardId = params.cardId; const cardIdOk = typeof cardId === "string"; const card = cardIdOk ? state.cardMap[cardId] : void 0; const cardInHand = cardIdOk && !!self && self.hand.includes(cardId); return myTurn && inActPhase && free && selfAlive && cardInHand && isNormalSlash(card) ? null : "现在不能使用朱雀羽扇"; }, async (state, params) => { const cardId = params.cardId; const shadowId = shadowIdOf$20(cardId); await applyAtom(state, { type: "当作", player: ownerId, cardIds: [cardId], shadowId, outputName: "杀", outputDamageType: "火焰" }); }, (state, params) => { const cardId = params.cardId; const shadowId = shadowIdOf$20(cardId); delete state.cardMap[shadowId]; state.players[ownerId].hand = state.players[ownerId].hand.map((id) => id === shadowId ? cardId : id); })); unloaders.push(registerAction(state, skill.id, ownerId, "respond", (st, _params) => { const slot = st.pendingSlots.get(ownerId); if (slot?.atom.type !== "请求回应") return "当前不需要回应"; if (slot.atom.requestType !== CONFIRM_REQUEST$14) return "当前不是朱雀羽扇询问"; return null; }, async (st, params) => { st.localVars[CONFIRMED_VAR$7] = params.choice === true; })); unloaders.push(registerAfterHook(state, skill.id, ownerId, "使用时", async (ctx) => { const atom = ctx.atom; if (atom.source !== ownerId) return; const cardId = atom.cardId; if (!cardId) return; const card = ctx.state.cardMap[cardId]; if (!isNormalSlash(card)) return; const self = ctx.state.players[ownerId]; if (!self) return; const weaponId = self.equipment["武器"]; if (!weaponId || ctx.state.cardMap[weaponId]?.name !== "朱雀羽扇") return; delete ctx.state.localVars[CONFIRMED_VAR$7]; await applyAtom(ctx.state, { type: "请求回应", requestType: CONFIRM_REQUEST$14, target: ownerId, prompt: { type: "confirm", title: "朱雀羽扇:是否发动,将此【杀】改为火【杀】?", confirmLabel: "发动", cancelLabel: "不发动" }, defaultChoice: false, timeout: 10 }); const confirmed = ctx.state.localVars[CONFIRMED_VAR$7] === true; delete ctx.state.localVars[CONFIRMED_VAR$7]; if (!confirmed) return; ctx.state.localVars[origTypeKey(cardId)] = card.damageType ?? null; card.damageType = "火焰"; })); unloaders.push(registerAfterHook(state, skill.id, ownerId, "移动牌", async (ctx) => { const atom = ctx.atom; if (atom.from?.zone !== "处理区") return; const cardId = atom.cardId; if (typeof cardId !== "string") return; restoreOrigType(ctx.state, cardId); })); return () => { for (const fn of unloaders) fn(); for (const key of Object.keys(state.localVars)) if (key.startsWith(ORIG_PREFIX)) restoreOrigType(state, key.slice(14)); }; } function onMount$204(_skill, api) { api.defineAction("transform", { label: "朱雀羽扇", style: "danger", prompt: { type: "useCardAndTarget", title: "朱雀羽扇:将普通【杀】当火【杀】使用", cardFilter: { filter: (c) => c.name === "杀" && (!c.damageType || c.damageType === "普通"), min: 1, max: 1 }, targetFilter: { min: 1, max: 1, filter: (view, t) => { return t !== view.currentPlayerIndex; } } }, activeWhen: (ctx) => defaultPlayActive(ctx) && viewCanSlash(ctx.view, ctx.perspectiveIdx) }); api.defineAction("respond", { label: "朱雀羽扇", style: "default", prompt: { type: "confirm", title: "朱雀羽扇:是否发动,将此【杀】改为火【杀】?", confirmLabel: "发动", cancelLabel: "不发动" } }); } var CONFIRM_REQUEST$14, CONFIRMED_VAR$7, ORIG_PREFIX, 朱雀羽扇_default; var init_朱雀羽扇 = __esmMin((() => { init_apply(); init_skill(); init_action_active(); CONFIRM_REQUEST$14 = "朱雀羽扇/confirm"; CONFIRMED_VAR$7 = "朱雀羽扇/confirmed"; ORIG_PREFIX = "朱雀羽扇/origType:"; 朱雀羽扇_default = { createSkill: createSkill$220, onInit: onInit$220, onMount: onMount$204 }; })); //#endregion //#region src/engine/skills/枭姬.ts var 枭姬_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$219, default: () => _skillModule$106, onInit: () => onInit$219, onMount: () => onMount$203 }); function createSkill$219(id, ownerId) { return { id, ownerId, name: "枭姬", description: "失去装备区的牌时,你可以摸两张牌" }; } /** 询问是否发动枭姬,确认则摸 2 张牌(每失去一件装备触发一次)。 * count = 本次 atom 造成的装备流失件数。 */ async function triggerXiaoji(state, ownerId, count) { for (let i = 0; i < count; i++) { if (!state.players[ownerId]?.alive) return; delete state.localVars["枭姬/confirmed"]; await applyAtom(state, { type: "请求回应", requestType: "枭姬/confirm", target: ownerId, prompt: { type: "confirm", title: "是否发动枭姬?(摸两张牌)", confirmLabel: "发动", cancelLabel: "不发动" }, defaultChoice: true, timeout: 10 }); if (state.localVars["枭姬/confirmed"]) await applyAtom(state, { type: "摸牌", player: ownerId, count: 2 }); } } function onInit$219(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "respond", (state, _params) => { const slot = state.pendingSlots.get(ownerId); if (!slot) return "当前不需要回应"; if (slot.atom.type !== "请求回应") return "当前不是枭姬窗口"; if (slot.atom.requestType !== "枭姬/confirm") return "当前不是枭姬窗口"; return null; }, async (state, params) => { state.localVars["枭姬/confirmed"] = params.choice === true || params.confirmed === true; }); registerAfterHook(state, skill.id, ownerId, "卸下", async (ctx) => { if (ctx.atom.player !== ownerId) return; await triggerXiaoji(ctx.state, ownerId, 1); }); registerBeforeHook(state, skill.id, ownerId, "弃置", async (ctx) => { const atom = ctx.atom; if (atom.player !== ownerId) return; const myEquip = new Set(Object.values(ctx.state.players[ownerId].equipment).filter((id) => typeof id === "string")); const lost = (atom.cardIds ?? []).filter((id) => myEquip.has(id)).length; if (lost > 0) ctx.state.localVars["枭姬/弃置loss"] = lost; }); registerAfterHook(state, skill.id, ownerId, "弃置", async (ctx) => { if (ctx.atom.player !== ownerId) return; const count = ctx.state.localVars["枭姬/弃置loss"]; if (!count) return; delete ctx.state.localVars["枭姬/弃置loss"]; await triggerXiaoji(ctx.state, ownerId, count); }); registerBeforeHook(state, skill.id, ownerId, "获得", async (ctx) => { const atom = ctx.atom; if (atom.from !== ownerId) return; const myEquip = new Set(Object.values(ctx.state.players[ownerId].equipment).filter((id) => typeof id === "string")); if (atom.cardId && myEquip.has(atom.cardId)) ctx.state.localVars["枭姬/获得loss"] = 1; }); registerAfterHook(state, skill.id, ownerId, "获得", async (ctx) => { if (ctx.atom.from !== ownerId) return; const count = ctx.state.localVars["枭姬/获得loss"]; if (!count) return; delete ctx.state.localVars["枭姬/获得loss"]; await triggerXiaoji(ctx.state, ownerId, count); }); return () => {}; } function onMount$203(_skill, api) { api.defineAction("respond", { label: "枭姬", style: "default", prompt: { type: "confirm", title: "是否发动枭姬?(摸两张牌)", confirmLabel: "发动", cancelLabel: "不发动" } }); } var _skillModule$106; var init_枭姬 = __esmMin((() => { init_apply(); init_skill(); _skillModule$106 = { createSkill: createSkill$219, onInit: onInit$219, onMount: onMount$203 }; })); //#endregion //#region src/engine/skills/武圣.ts var 武圣_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$218, onInit: () => onInit$218, onMount: () => onMount$202 }); function createSkill$218(id, ownerId) { return { id, ownerId, name: "武圣", description: "你可以将一张红色牌当【杀】使用或打出" }; } /** 影子卡 id:${原id}#武圣 */ function shadowIdOf$19(cardId) { return `${cardId}#武圣`; } function onInit$218(skill, state) { const ownerId = skill.ownerId; const unloadTransform = registerAction(state, skill.id, ownerId, "transform", (state, params) => { const self = state.players[ownerId]; const selfAlive = self.alive === true; const cardId = params.cardId; const cardIdOk = typeof cardId === "string"; const card = cardIdOk ? state.cardMap[cardId] : void 0; const cardInHand = cardIdOk && self.hand.includes(cardId); const isRed = !!card && card.color === "红"; const myTurn = state.currentPlayerIndex === ownerId; const free = !hasBlockingPending(state); const slot = state.pendingSlots.get(ownerId); const atomType = (slot?.atom)?.type; const reqType = (slot?.atom)?.requestType; const atomTarget = (slot?.atom)?.target; return selfAlive && cardInHand && isRed && (myTurn && free || !!slot && atomTarget === ownerId && (atomType === "询问杀" || atomType === "请求回应" && reqType === "杀/respondKill")) ? null : "现在不能使用武圣"; }, async (state, params) => { const cardId = params.cardId; const shadowId = shadowIdOf$19(cardId); await applyAtom(state, { type: "当作", player: ownerId, cardIds: [cardId], shadowId, outputName: "杀" }); }, (state, params) => { const cardId = params.cardId; const sId = shadowIdOf$19(cardId); delete state.cardMap[sId]; const self = state.players[ownerId]; const idx = self.hand.indexOf(sId); if (idx >= 0) self.hand[idx] = cardId; }); const unloadAlt = declareAlternativeResponse(state, ownerId, "询问杀"); return () => { unloadAlt(); unloadTransform(); }; } function onMount$202(skill, api) { api.defineAction("transform", { label: "武圣", style: "passive", prompt: { type: "useCardAndTarget", title: "选择一张红色牌当杀使用", cardFilter: { filter: (c) => c.color === "红", min: 1, max: 1 }, targetFilter: { min: 1, max: 1, filter: (view, t) => viewCanAttack(view.players, view.cardMap, view.currentPlayerIndex, t) } }, transform: (card) => ({ name: "杀", sourceCardId: card.id, fromSkill: skill.id }), activeWhen: (ctx) => { const p = ctx.view.players[ctx.perspectiveIdx]; if (!p?.alive) return false; if (!(p.hand?.some((c) => c.color === "红") ?? false)) return false; const ownTurnCanSlash = defaultPlayActive(ctx) && viewCanSlash(ctx.view, ctx.perspectiveIdx); const slot = ctx.view.pending; const atomType = (slot?.atom)?.type; const reqType = (slot?.atom)?.requestType; const askedKill = !!slot && slot.target === ctx.perspectiveIdx && (atomType === "询问杀" || atomType === "请求回应" && reqType === "杀/respondKill"); return ownTurnCanSlash || askedKill; } }); } var init_武圣 = __esmMin((() => { init_skill(); init_apply(); init_viewDistance(); init_action_active(); })); //#endregion //#region src/engine/skills/武烈.ts var 武烈_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$217, default: () => _skillModule$105, onInit: () => onInit$217, onMount: () => onMount$201 }); function createSkill$217(id, ownerId) { return { id, ownerId, name: "武烈", description: "限定技,结束阶段,你可以失去任意点体力,令等量的其他角色各获得1个「烈」标记。有「烈」的角色受到伤害时,防止此伤害,然后移去「烈」" }; } function onInit$217(skill, state) { const ownerId = skill.ownerId; const unloadAction = registerAction(state, skill.id, ownerId, "respond", (st, params) => { const slot = st.pendingSlots.get(ownerId); if (!slot) return "当前不需要回应"; const atom = slot.atom; if (atom["type"] !== "请求回应") return "当前不需要回应"; const rt = atom["requestType"]; if (rt !== CONFIRM_RT$53 && rt !== HP_RT && rt !== CHOOSE_RT$13) return "当前不是武烈询问"; if (rt === HP_RT) { const raw = typeof params.option === "string" ? Number(params.option) : params.hpCount; const hp = st.players[ownerId]?.health ?? 0; const n = typeof raw === "number" ? raw : NaN; if (!Number.isInteger(n) || n < 1 || n > hp) return `请选择 1-${hp} 之间的整数`; } if (rt === CHOOSE_RT$13) { const expected = st.localVars[HP_KEY]; const targets = params.targets; if (!Array.isArray(targets) || typeof expected !== "number" || targets.length !== expected) return `请选择 ${expected ?? "?"} 名其他角色`; } return null; }, async (st, params) => { const rt = (st.pendingSlots.get(ownerId)?.atom)?.requestType; if (rt === CONFIRM_RT$53) st.localVars[CONFIRMED_KEY$44] = params.choice === true || params.confirmed === true; else if (rt === HP_RT) { const raw = typeof params.option === "string" ? Number(params.option) : params.hpCount; if (typeof raw === "number" && Number.isInteger(raw) && raw >= 1) st.localVars[HP_KEY] = raw; } else if (rt === CHOOSE_RT$13) { const targets = params.targets; if (Array.isArray(targets)) st.localVars[CHOSEN_KEY$1] = targets; } }); const unloadEndHook = registerAfterHook(state, skill.id, ownerId, "阶段开始", async (ctx) => { const atom = ctx.atom; if (atom.type !== "阶段开始") return; if (atom.player !== ownerId) return; if (atom.phase !== "回合结束") return; const st = ctx.state; const self = st.players[ownerId]; if (!self?.alive) return; if (self.vars[USED_KEY$15]) return; if (self.health < 1) return; const otherAlive = st.players.filter((p) => p.alive && p.index !== ownerId); if (otherAlive.length === 0) return; delete st.localVars[CONFIRMED_KEY$44]; await applyAtom(st, { type: "请求回应", requestType: CONFIRM_RT$53, target: ownerId, prompt: { type: "confirm", title: "是否发动武烈?(限定技)", description: "结束阶段:失去任意点体力,令等量其他角色各获得1个「烈」标记", confirmLabel: "发动", cancelLabel: "不发动" }, defaultChoice: false, timeout: 15 }); if (!st.localVars[CONFIRMED_KEY$44]) { delete st.localVars[CONFIRMED_KEY$44]; return; } delete st.localVars[CONFIRMED_KEY$44]; const maxN = Math.min(self.health, otherAlive.length); delete st.localVars[HP_KEY]; await applyAtom(st, { type: "请求回应", requestType: HP_RT, target: ownerId, prompt: { type: "chooseOption", title: `武烈:失去几点体力?(1-${maxN})`, description: "失去 N 点体力,令 N 名其他角色各获得1个「烈」标记", options: Array.from({ length: maxN }, (_, i) => ({ value: String(i + 1), label: `失去 ${i + 1} 点体力` })) }, defaultChoice: false, timeout: 20 }); const n = st.localVars[HP_KEY]; delete st.localVars[HP_KEY]; if (typeof n !== "number" || n < 1 || n > maxN) return; const targetCount = Math.min(n, otherAlive.length); delete st.localVars[CHOSEN_KEY$1]; st.localVars[HP_KEY] = targetCount; await applyAtom(st, { type: "请求回应", requestType: CHOOSE_RT$13, target: ownerId, prompt: { type: "choosePlayer", title: `武烈:选择 ${targetCount} 名其他角色(各获1个「烈」标记)`, min: targetCount, max: targetCount, filter: (_view, t) => t !== ownerId && st.players[t]?.alive === true }, timeout: 30 }); const chosen = st.localVars[CHOSEN_KEY$1]; delete st.localVars[CHOSEN_KEY$1]; delete st.localVars[HP_KEY]; if (!Array.isArray(chosen) || chosen.length !== targetCount) return; st.players[ownerId].vars[USED_KEY$15] = true; await applyAtom(st, { type: "失去体力", target: ownerId, amount: n }); if (!st.players[ownerId]?.alive) return; for (const t of chosen) { if (!st.players[t]?.alive) continue; await applyAtom(st, { type: "加标记", player: t, mark: { id: LIE_MARK_ID, scope: t, payload: { source: ownerId } } }); } }); const unloadDmgHook = registerBeforeHook(state, skill.id, ownerId, "受到伤害时", async (ctx) => { const atom = ctx.atom; if (typeof atom.target !== "number") return; if ((atom.amount ?? 0) <= 0) return; const st = ctx.state; const target = atom.target; const tp = st.players[target]; if (!tp?.alive) return; if (!tp.marks.some((m) => m.id === LIE_MARK_ID)) return; await applyAtom(st, { type: "去标记", player: target, markId: LIE_MARK_ID }); return { kind: "cancel" }; }); return () => { unloadAction(); unloadEndHook(); unloadDmgHook(); }; } function onMount$201(_skill, _api) { return () => {}; } var USED_KEY$15, CONFIRM_RT$53, HP_RT, CHOOSE_RT$13, CONFIRMED_KEY$44, HP_KEY, CHOSEN_KEY$1, LIE_MARK_ID, _skillModule$105; var init_武烈 = __esmMin((() => { init_apply(); init_skill(); USED_KEY$15 = "武烈/used"; CONFIRM_RT$53 = "武烈/confirm"; HP_RT = "武烈/hp"; CHOOSE_RT$13 = "武烈/choose"; CONFIRMED_KEY$44 = "武烈/confirmed"; HP_KEY = "武烈/hpCount"; CHOSEN_KEY$1 = "武烈/targets"; LIE_MARK_ID = "武烈/烈"; _skillModule$105 = { createSkill: createSkill$217, onInit: onInit$217, onMount: onMount$201 }; })); //#endregion //#region src/engine/skills/洛神.ts var 洛神_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$216, onInit: () => onInit$216, onMount: () => onMount$200 }); function createSkill$216(id, ownerId) { return { id, ownerId, name: "洛神", description: "准备阶段判定:黑色获得判定牌并重复,直到红色或停止" }; } /** 判定牌是否为黑色(黑桃 ♠ 或梅花 ♣) */ function isBlack$1(card) { return !!card && (card.suit === "♠" || card.suit === "♣"); } function onInit$216(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "respond", (state, _params) => { if (state.pendingSlots.get(ownerId)?.atom.type !== "请求回应") return "当前不需要回应"; const requestType = state.pendingSlots.get(ownerId).atom.requestType; if (requestType !== "洛神/confirm" && requestType !== "洛神/continue") return "当前不是洛神询问"; return null; }, async (state, params) => { state.localVars["洛神/confirmed"] = params.choice === true || params.confirmed === true; }); registerAfterHook(state, skill.id, ownerId, "判定牌生效后", async (ctx) => { const atom = ctx.atom; if (atom.player !== ownerId) return; if (atom.judgeType !== "洛神") return; const processing = frameCards(ctx.state); if (processing.length === 0) { ctx.state.localVars["洛神/lastResult"] = "red"; return; } const judgeCardId = processing[processing.length - 1]; const judgeCard = ctx.state.cardMap[judgeCardId]; ctx.state.localVars["洛神/lastJudgeCardId"] = judgeCardId; ctx.state.localVars["洛神/lastResult"] = isBlack$1(judgeCard) ? "black" : "red"; }); registerAfterHook(state, skill.id, ownerId, "阶段开始", async (ctx) => { const atom = ctx.atom; if (atom.type !== "阶段开始") return; if (atom.player !== ownerId) return; if (atom.phase !== "准备") return; if (!ctx.state.players[ownerId]?.alive) return; if (ctx.state.zones.deck.length === 0) return; delete ctx.state.localVars["洛神/confirmed"]; await applyAtom(ctx.state, { type: "请求回应", requestType: "洛神/confirm", target: ownerId, prompt: { type: "confirm", title: "是否发动洛神?", confirmLabel: "发动", cancelLabel: "不发动" }, defaultChoice: false, timeout: 10 }); if (!ctx.state.localVars["洛神/confirmed"]) return; let keepGoing = true; while (keepGoing) { if (ctx.state.zones.deck.length === 0) break; await runJudgeFlow(ctx.state, ownerId, "洛神"); const result = ctx.state.localVars["洛神/lastResult"]; const judgeCardId = ctx.state.localVars["洛神/lastJudgeCardId"]; delete ctx.state.localVars["洛神/lastResult"]; delete ctx.state.localVars["洛神/lastJudgeCardId"]; if (result !== "black" || !judgeCardId) break; await applyAtom(ctx.state, { type: "移动牌", cardId: judgeCardId, from: { zone: "弃牌堆" }, to: { zone: "手牌", player: ownerId } }); delete ctx.state.localVars["洛神/confirmed"]; await applyAtom(ctx.state, { type: "请求回应", requestType: "洛神/continue", target: ownerId, prompt: { type: "confirm", title: "判定为黑色,是否继续洛神?", confirmLabel: "继续", cancelLabel: "停止" }, defaultChoice: true, timeout: 10 }); keepGoing = ctx.state.localVars["洛神/confirmed"] !== false; } delete ctx.state.localVars["洛神/confirmed"]; }); return () => {}; } function onMount$200(_skill, api) { api.defineAction("respond", { label: "洛神", style: "default", prompt: { type: "confirm", title: "是否发动洛神?", confirmLabel: "发动", cancelLabel: "不发动" } }); } var init_洛神 = __esmMin((() => { init_apply(); init_frame(); init_judge(); init_skill(); })); //#endregion //#region src/engine/skills/流离.ts var 流离_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$215, onInit: () => onInit$215, onMount: () => onMount$199 }); function createSkill$215(id, ownerId) { return { id, ownerId, name: "流离", description: "当你成为杀的目标时,可弃一张牌,将此杀转移给攻击范围内一名其他角色" }; } function onInit$215(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "respond", (state, _params) => { if (state.pendingSlots.get(ownerId)?.atom.type !== "请求回应") return "当前不需要回应"; const requestType = state.pendingSlots.get(ownerId).atom.requestType; if (requestType !== "流离/confirm" && requestType !== "流离/chooseTarget" && requestType !== "流离/pickDiscard") return "当前不是流离回应"; if (requestType === "流离/chooseTarget") { const t = _params.target; if (typeof t !== "number") return "请选择转移目标"; if (t === ownerId) return "不能转移给自己"; if (t === state.localVars["流离/来源"]) return "不能转移给杀的来源"; if (!state.players[t]?.alive) return "目标不存活"; if (!inAttackRange(state, ownerId, t)) return "目标不在你的攻击范围内"; } if (requestType === "流离/pickDiscard") { const raw = _params.cardIds ?? _params.cardId; const cardIds = Array.isArray(raw) ? raw : typeof raw === "string" ? [raw] : void 0; if (!cardIds || cardIds.length === 0) return null; if (cardIds.length !== 1) return "请选择弃置的 1 张手牌"; const cid = cardIds[0]; if (typeof cid !== "string" || !state.players[ownerId].hand.includes(cid)) return "弃置牌不在你的手牌中"; } return null; }, async (state, params) => { const requestType = (state.pendingSlots.get(ownerId)?.atom)?.requestType; if (requestType === "流离/confirm") state.localVars["流离/confirmed"] = params.choice === true || params.confirmed === true; else if (requestType === "流离/pickDiscard") { const raw = params.cardIds ?? params.cardId; const ids = Array.isArray(raw) ? raw : typeof raw === "string" ? [raw] : void 0; state.localVars["流离/discard"] = ids?.[0]; } else state.localVars["流离/target"] = params.target; }); registerAfterHook(state, skill.id, ownerId, "成为目标", async (ctx) => { const atom = ctx.atom; if (atom.target !== ownerId) return; const triggerCardId = atom.cardId; if (!triggerCardId) return; if (ctx.state.cardMap[triggerCardId]?.name !== "杀") return; const selfPlayer = ctx.state.players[ownerId]; if (!selfPlayer || selfPlayer.hand.length === 0) return; delete ctx.state.localVars["流离/confirmed"]; ctx.state.localVars["流离/来源"] = atom.source; await applyAtom(ctx.state, { type: "请求回应", requestType: "流离/confirm", target: ownerId, prompt: { type: "confirm", title: "是否发动流离?", confirmLabel: "发动", cancelLabel: "不发动" }, defaultChoice: false, timeout: 10 }); if (!ctx.state.localVars["流离/confirmed"]) return; delete ctx.state.localVars["流离/target"]; await applyAtom(ctx.state, { type: "请求回应", requestType: "流离/chooseTarget", target: ownerId, prompt: { type: "choosePlayer", title: "流离:选择转移目标", min: 1, max: 1, filter: (view, target) => target !== ownerId && target !== atom.source && view.players[target]?.alive === true && inAttackRange(ctx.state, ownerId, target) }, timeout: 15 }); const newTarget = ctx.state.localVars["流离/target"]; if (typeof newTarget !== "number" || newTarget === ownerId) return; delete ctx.state.localVars["流离/discard"]; await applyAtom(ctx.state, { type: "请求回应", requestType: "流离/pickDiscard", target: ownerId, prompt: { type: "useCard", title: "流离:弃置 1 张手牌", cardFilter: { filter: () => true, min: 1, max: 1 } }, timeout: 15 }); const payer = ctx.state.players[ownerId]; const picked = ctx.state.localVars["流离/discard"]; delete ctx.state.localVars["流离/discard"]; if (typeof picked !== "string" || !payer.hand.includes(picked)) { delete ctx.state.localVars["流离/target"]; delete ctx.state.localVars["流离/来源"]; return; } const discardCard = picked; await applyAtom(ctx.state, { type: "弃置", player: ownerId, cardIds: [discardCard], voluntary: true }); const resolvedTargets = ctx.frame.params.resolvedTargets; if (resolvedTargets) { const idx = resolvedTargets.indexOf(ownerId); if (idx >= 0) resolvedTargets[idx] = newTarget; } delete ctx.state.localVars["流离/confirmed"]; delete ctx.state.localVars["流离/target"]; delete ctx.state.localVars["流离/来源"]; }); return () => {}; } function onMount$199(_skill, api) { api.defineAction("respond", { label: "流离", style: "default", prompt: { type: "confirm", title: "是否发动流离?", confirmLabel: "发动", cancelLabel: "不发动" } }); } var init_流离 = __esmMin((() => { init_apply(); init_skill(); init_distance(); })); //#endregion //#region src/engine/skills/涅槃.ts var 涅槃_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$214, default: () => 涅槃_default, onInit: () => onInit$214, onMount: () => onMount$198 }); function createSkill$214(id, ownerId) { return { id, ownerId, name: "涅槃", description: "限定技:濒死时弃置所有牌和判定区牌,重置武将牌,摸三张并回复至3点体力" }; } function onInit$214(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "respond", (st, _params) => { const slot = st.pendingSlots.get(ownerId); if (!slot) return "当前不需要回应"; const atom = slot.atom; if (atom["type"] !== "请求回应") return "当前不需要回应"; if (atom["requestType"] !== CONFIRM_RT$52) return "当前不是涅槃确认"; return null; }, async (st, params) => { st.localVars[CONFIRMED_KEY$43] = params.choice === true || params.confirmed === true; }); registerAfterHook(state, skill.id, ownerId, "陷入濒死", async (ctx) => { if (ctx.atom.target !== ownerId) return; if (ctx.state.players[ownerId]?.vars[USED_KEY$14]) return; const self = ctx.state.players[ownerId]; if (!self) return; if (self.health > 0) return; delete ctx.state.localVars[CONFIRMED_KEY$43]; await applyAtom(ctx.state, { type: "请求回应", requestType: CONFIRM_RT$52, target: ownerId, prompt: { type: "confirm", title: "是否发动涅槃?(限定技:弃所有牌,摸3张,回复至3体力)", confirmLabel: "发动", cancelLabel: "不发动" }, defaultChoice: false, timeout: 15 }); if (!ctx.state.localVars[CONFIRMED_KEY$43]) return; ctx.state.players[ownerId].vars[USED_KEY$14] = true; const player = ctx.state.players[ownerId]; const handCards = [...player.hand]; const equipCards = Object.values(player.equipment).filter((id) => typeof id === "string"); const judgeTricks = player.pendingTricks.map((t) => ({ trickName: t.name, cardId: t.card.id })); for (const { trickName } of judgeTricks) await applyAtom(ctx.state, { type: "移除延时锦囊", player: ownerId, trickName }); const orphanJudgeCards = judgeTricks.map((t) => t.cardId).filter((id) => locateCard(ctx.state, id) === null); const allCardIds = [ ...handCards, ...equipCards, ...orphanJudgeCards ]; if (allCardIds.length > 0) await applyAtom(ctx.state, { type: "弃置", player: ownerId, cardIds: allCardIds }); if (ctx.state.players[ownerId].marks.some((m) => m.id === "chained")) await setChain(ctx.state, ownerId, false); await applyAtom(ctx.state, { type: "摸牌", player: ownerId, count: 3 }); const cur = ctx.state.players[ownerId].health; const amount = Math.max(0, TARGET_HEALTH$2 - cur); if (amount > 0) await applyAtom(ctx.state, { type: "回复体力", target: ownerId, amount }); }); return () => {}; } function onMount$198(_skill, api) { api.defineAction("respond", { label: "涅槃", style: "primary", prompt: { type: "confirm", title: "是否发动涅槃?", confirmLabel: "发动", cancelLabel: "不发动" } }); } var CONFIRM_RT$52, CONFIRMED_KEY$43, USED_KEY$14, TARGET_HEALTH$2, 涅槃_default; var init_涅槃 = __esmMin((() => { init_apply(); init_face_down(); init_judge_zone(); init_skill(); CONFIRM_RT$52 = "涅槃/confirm"; CONFIRMED_KEY$43 = "涅槃/confirmed"; USED_KEY$14 = "涅槃/used"; TARGET_HEALTH$2 = 3; 涅槃_default = { createSkill: createSkill$214, onInit: onInit$214, onMount: onMount$198 }; })); //#endregion //#region src/engine/skills/涯角.ts var 涯角_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$213, default: () => 涯角_default, onInit: () => onInit$213, onMount: () => onMount$197 }); /** 读取当前 pending 的 requestType(类型安全) */ function currentRequestType$12(state, ownerId) { const slot = state.pendingSlots.get(ownerId); if (!slot) return void 0; return slot.atom.requestType; } function createSkill$213(id, ownerId) { return { id, ownerId, name: "涯角", description: "每当你于回合外使用或打出手牌时,你可以展示牌堆顶的一张牌,若这两张牌的类别相同,你可以将牌堆顶的一张牌交给一名角色;若不同,你可以弃置攻击范围内包含你的一名角色区域里的一张牌。" }; } function onInit$213(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "respond", (st, params) => { const rt = currentRequestType$12(st, ownerId); if (rt !== CONFIRM_RT$51 && rt !== TARGET_RT$20 && rt !== DISCARD_RT$16) return "当前不是涯角询问"; if (rt === DISCARD_RT$16) { const zone = params.zone; if (zone === "equipment" || zone === "judge") { if (typeof params.cardId !== "string") return "cardId required"; } else if (zone === "hand") { if (typeof params.handIndex !== "number") return "handIndex required"; } else return "zone required (equipment|judge|hand)"; } return null; }, async (st, params) => { const rt = currentRequestType$12(st, ownerId); if (rt === CONFIRM_RT$51) st.localVars[CONFIRMED_KEY$42] = params.choice === true || params.confirmed === true; else if (rt === TARGET_RT$20) st.localVars[TARGET_KEY$30] = params.target ?? null; else if (rt === DISCARD_RT$16) st.localVars[PICK_RESULT_KEY$3] = { zone: params.zone, cardId: params.cardId ?? null, handIndex: params.handIndex ?? null }; }); registerAfterHook(state, skill.id, ownerId, "移动牌", async (ctx) => { const atom = ctx.atom; if (atom.from.zone !== "手牌" || atom.from.player !== ownerId) return; if (atom.to.zone !== "处理区") return; if (ctx.state.currentPlayerIndex === ownerId) return; if (!ctx.state.players[ownerId]?.alive) return; const deck = ctx.state.zones.deck; if (deck.length === 0) return; delete ctx.state.localVars[CONFIRMED_KEY$42]; await applyAtom(ctx.state, { type: "请求回应", requestType: CONFIRM_RT$51, target: ownerId, prompt: { type: "confirm", title: "是否发动涯角?", confirmLabel: "发动", cancelLabel: "不发动" }, defaultChoice: false, timeout: 15 }); if (!ctx.state.localVars[CONFIRMED_KEY$42]) return; const topCardId = deck[deck.length - 1]; await applyAtom(ctx.state, { type: "展示", player: ownerId, cardId: topCardId }); const playedCard = ctx.state.cardMap[atom.cardId]; const topCard = ctx.state.cardMap[topCardId]; if (playedCard?.type === topCard?.type) { delete ctx.state.localVars[TARGET_KEY$30]; await applyAtom(ctx.state, { type: "请求回应", requestType: TARGET_RT$20, target: ownerId, prompt: { type: "choosePlayer", title: "涯角:将牌堆顶牌交给一名角色", min: 1, max: 1, filter: (view, target) => view.players[target]?.alive === true }, timeout: 15 }); const giveTarget = ctx.state.localVars[TARGET_KEY$30]; if (typeof giveTarget === "number") await applyAtom(ctx.state, { type: "摸牌", player: giveTarget, count: 1 }); } else { delete ctx.state.localVars[TARGET_KEY$30]; await applyAtom(ctx.state, { type: "请求回应", requestType: TARGET_RT$20, target: ownerId, prompt: { type: "choosePlayer", title: "涯角:选择攻击范围内包含你的一名角色,弃其一张牌", min: 1, max: 1, filter: (_view, t) => ctx.state.players[t]?.alive === true && inAttackRange(ctx.state, t, ownerId) }, timeout: 20 }); const discardTarget = ctx.state.localVars[TARGET_KEY$30]; delete ctx.state.localVars[TARGET_KEY$30]; if (typeof discardTarget !== "number") return; const target = ctx.state.players[discardTarget]; if (!target?.alive) return; await runPickTargetCardPanel(ctx.state, ownerId, discardTarget, target, { mode: "discard", requestType: DISCARD_RT$16, title: `涯角:选择要从 ${target.name} 区域弃置的一张牌` }); } delete ctx.state.localVars[CONFIRMED_KEY$42]; delete ctx.state.localVars[TARGET_KEY$30]; }); return () => {}; } function onMount$197(_skill, api) { api.defineAction("respond", { label: "涯角", style: "primary", prompt: { type: "confirm", title: "是否发动涯角?", confirmLabel: "发动", cancelLabel: "不发动" } }); } var CONFIRMED_KEY$42, TARGET_KEY$30, CONFIRM_RT$51, TARGET_RT$20, DISCARD_RT$16, 涯角_default; var init_涯角 = __esmMin((() => { init_apply(); init_skill(); init_distance(); init_pick_card_panel(); init_vars_keys(); CONFIRMED_KEY$42 = "涯角/confirmed"; TARGET_KEY$30 = "涯角/target"; CONFIRM_RT$51 = "涯角/confirm"; TARGET_RT$20 = "涯角/target"; DISCARD_RT$16 = "涯角/discard"; 涯角_default = { createSkill: createSkill$213, onInit: onInit$213, onMount: onMount$197 }; })); //#endregion //#region src/engine/skills/溃诛.ts var 溃诛_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$212, default: () => _skillModule$104, onInit: () => onInit$212, onMount: () => onMount$196 }); function createSkill$212(id, ownerId) { return { id, ownerId, name: "溃诛", description: "弃牌阶段结束时,选择一项:令至多X名角色各摸一张牌,或对体力值之和为X的角色各造成1点伤害(X为本阶段弃牌数)" }; } function onInit$212(skill, state) { const ownerId = skill.ownerId; registerAfterHook(state, skill.id, ownerId, "弃置", async (ctx) => { if (ctx.state.phase !== "弃牌") return; if (ctx.atom.player !== ownerId) return; const prev = ctx.state.localVars[COUNT_KEY$4] ?? 0; ctx.state.localVars[COUNT_KEY$4] = prev + ctx.atom.cardIds.length; }); registerBeforeHook(state, skill.id, ownerId, "阶段结束", async (ctx) => { const atom = ctx.atom; if (atom.phase !== "弃牌") return; if (atom.player !== ownerId) return; const st = ctx.state; if (!st.players[ownerId]?.alive) return; const X = st.localVars[COUNT_KEY$4] ?? 0; delete st.localVars[COUNT_KEY$4]; if (X <= 0) return; st.localVars[X_KEY] = X; await pushFrame(st, "溃诛", ownerId, { X }); delete st.localVars[OPTION_KEY$6]; await applyAtom(st, { type: "请求回应", requestType: OPTION_RT$6, target: ownerId, prompt: { type: "chooseOption", title: `溃诛:你本阶段弃置了 ${X} 张牌,选择一项`, options: [ { value: OPT_DRAW$1, label: `令至多 ${X} 名角色各摸一张牌` }, { value: OPT_DAMAGE, label: `对体力值之和为 ${X} 的角色各造成 1 点伤害` }, { value: OPT_SKIP, label: "不发动" } ] }, timeout: 30 }); const option = st.localVars[OPTION_KEY$6]; delete st.localVars[OPTION_KEY$6]; if (option !== OPT_DRAW$1 && option !== OPT_DAMAGE) { delete st.localVars[X_KEY]; await popFrame(st); return; } if (option === OPT_DRAW$1) { delete st.localVars[DRAW_KEY$1]; await applyAtom(st, { type: "请求回应", requestType: DRAW_RT$1, target: ownerId, prompt: { type: "choosePlayer", title: `溃诛:选择 1 至 ${X} 名角色各摸一张牌`, min: 1, max: X, filter: (view, t) => t !== ownerId && view.players[t]?.alive === true }, timeout: 30 }); const targets = st.localVars[DRAW_KEY$1] ?? []; delete st.localVars[DRAW_KEY$1]; for (const t of targets) if (st.players[t]?.alive) await applyAtom(st, { type: "摸牌", player: t, count: 1 }); } else { delete st.localVars[DAMAGE_KEY$1]; await applyAtom(st, { type: "请求回应", requestType: DAMAGE_RT, target: ownerId, prompt: { type: "choosePlayer", title: `溃诛:选择体力值之和为 ${X} 的角色(各造成 1 点伤害)`, min: 1, max: X, filter: (view, t) => t !== ownerId && view.players[t]?.alive === true }, timeout: 30 }); const targets = st.localVars[DAMAGE_KEY$1] ?? []; delete st.localVars[DAMAGE_KEY$1]; for (const t of targets) if (st.players[t]?.alive) await runDamageFlow(st, ownerId, t, 1); } delete st.localVars[X_KEY]; await popFrame(st); }); registerAction(state, skill.id, ownerId, "respond", (st, params) => { const slot = st.pendingSlots.get(ownerId); if (!slot) return "当前不需要回应"; if (slot.atom.type !== "请求回应") return "当前不是溃诛窗口"; const atom = slot.atom; const X = st.localVars[X_KEY] ?? 0; if (atom.requestType === OPTION_RT$6) { const opt = params.option; if (opt !== OPT_DRAW$1 && opt !== OPT_DAMAGE && opt !== OPT_SKIP) return "请选择一项"; return null; } if (atom.requestType === DRAW_RT$1) { const targets = params.targets; if (!Array.isArray(targets) || targets.length === 0) return "请选择至少一名角色"; if (targets.length > X) return `至多选择 ${X} 名角色`; if (new Set(targets).size !== targets.length) return "targets 含重复座次"; for (const t of targets) { if (t === ownerId) return "不能选择自己"; if (!st.players[t]?.alive) return "目标不合法"; } return null; } if (atom.requestType === DAMAGE_RT) { const targets = params.targets; if (!Array.isArray(targets) || targets.length === 0) return "请选择至少一名角色"; if (targets.length > X) return `至多选择 ${X} 名角色`; if (new Set(targets).size !== targets.length) return "targets 含重复座次"; let sum = 0; for (const t of targets) { if (t === ownerId) return "不能选择自己"; if (!st.players[t]?.alive) return "目标不合法"; sum += getHealthValue(st.players[t]); } if (sum !== X) return `所选角色体力值之和(${sum})须等于 ${X}`; return null; } return "当前不是溃诛窗口"; }, async (st, params) => { const atom = st.pendingSlots.get(ownerId).atom; if (atom.requestType === OPTION_RT$6) st.localVars[OPTION_KEY$6] = params.option; else if (atom.requestType === DRAW_RT$1) st.localVars[DRAW_KEY$1] = params.targets; else if (atom.requestType === DAMAGE_RT) st.localVars[DAMAGE_KEY$1] = params.targets; }); return () => {}; } function onMount$196(_skill, _api) { return () => {}; } var OPTION_RT$6, DRAW_RT$1, DAMAGE_RT, OPTION_KEY$6, DRAW_KEY$1, DAMAGE_KEY$1, X_KEY, COUNT_KEY$4, OPT_DRAW$1, OPT_DAMAGE, OPT_SKIP, _skillModule$104; var init_溃诛 = __esmMin((() => { init_apply(); init_frame(); init_damage(); init_skill(); init_types(); OPTION_RT$6 = "溃诛/选择"; DRAW_RT$1 = "溃诛/摸牌选目标"; DAMAGE_RT = "溃诛/伤害选目标"; OPTION_KEY$6 = "溃诛/选项"; DRAW_KEY$1 = "溃诛/摸牌目标"; DAMAGE_KEY$1 = "溃诛/伤害目标"; X_KEY = "溃诛/X"; COUNT_KEY$4 = "溃诛/弃牌数"; OPT_DRAW$1 = "摸牌"; OPT_DAMAGE = "伤害"; OPT_SKIP = "不发动"; _skillModule$104 = { createSkill: createSkill$212, onInit: onInit$212, onMount: onMount$196 }; })); //#endregion //#region src/engine/skills/激将.ts var 激将_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$211, onInit: () => onInit$211, onMount: () => onMount$195 }); function createSkill$211(id, ownerId) { return { id, ownerId, name: "激将", description: "主公技,其他蜀势力角色可以在你需要时代替你使用或打出【杀】(视为由你使用或打出)" }; } function onInit$211(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "use", (state, params) => { const myTurn = state.currentPlayerIndex === ownerId; const inActPhase = state.phase === "出牌"; const free = !hasBlockingPending(state); const selfAlive = state.players[ownerId].alive === true; const isLord = ownerId === 0; const targetIdx = params.target; const targetExists = typeof targetIdx === "number" && !!state.players[targetIdx]; const target = targetExists ? state.players[targetIdx] : null; const targetNotSelf = targetIdx !== ownerId; const targetAlive = target?.alive === true; const targetShu = target?.faction === "蜀"; const killTargetIdx = params.killTarget; const killTargetValid = killTargetIdx === void 0 || state.players[killTargetIdx]?.alive === true && killTargetIdx !== targetIdx; return myTurn && inActPhase && free && selfAlive && isLord && targetExists && targetNotSelf && targetAlive && targetShu && killTargetValid ? null : "现在不能使用激将"; }, async (state, params) => { const from = ownerId; const target = params.target; const killTarget = params.killTarget; await pushFrame(state, "激将", from, { ...params }); if (typeof killTarget === "number") state.localVars[KILL_TARGET_VAR] = killTarget; await applyAtom(state, { type: "请求回应", requestType: REQUEST_TYPE$5, target, prompt: { type: "useCardAndTarget", title: `激将:对 ${typeof killTarget === "number" ? state.players[killTarget]?.name ?? "?" : "?"} 使用一张杀`, cardFilter: { filter: (c) => c.name === "杀", min: 1, max: 1 }, targetFilter: { min: 1, max: 1, filter: (_v, t) => typeof killTarget === "number" ? t === killTarget : t !== target } }, timeout: 15 }); const choice = state.localVars[CHOICE_VAR$2]; delete state.localVars[CHOICE_VAR$2]; delete state.localVars[KILL_TARGET_VAR]; if (choice?.cardId && Array.isArray(choice.targets) && choice.targets.length > 0) await runUseFlow(state, target, choice.cardId, choice.targets, "杀"); await popFrame(state); }); const unloaders = []; unloaders.push(declareAlternativeResponse(state, ownerId, "询问杀")); for (const pl of state.players) { const seat = pl.index; unloaders.push(registerAction(state, skill.id, seat, "respond", (st, params) => { const slot = st.pendingSlots.get(seat); if (!slot) return "当前不需要回应"; const atom = slot.atom; if (atom.type === "询问杀") { if (!st.players[seat]?.alive) return "玩家不存在或已死亡"; if (seat !== 0) return "仅主公可用"; if (!st.players.some((p) => p.alive && p.index !== seat && p.faction === "蜀" && p.hand.length > 0)) return "没有可出杀的蜀势力角色"; return null; } if (atom.requestType === REQUEST_TYPE$5) { const cardId = params.cardId; const targets = params.targets; if (typeof cardId !== "string") return "请选择一张杀"; if (!Array.isArray(targets) || targets.length === 0) return "请选择目标"; if (!st.players[seat]?.hand.includes(cardId)) return "牌不在手牌中"; if (st.cardMap[cardId]?.name !== "杀") return "只能使用杀"; const killTarget = st.localVars[KILL_TARGET_VAR]; if (typeof killTarget === "number" && !targets.includes(killTarget)) return "必须包含激将指定的目标"; for (const t of targets) if (!inAttackRange(st, seat, t, cardId)) return "目标不在攻击范围内"; return null; } return "当前不是激将询问"; }, async (st, params) => { const atom = st.pendingSlots.get(seat)?.atom; if (atom?.type === "询问杀") { const numPlayers = st.players.length; for (let offset = 1; offset < numPlayers; offset++) { const allyIdx = (seat + offset) % numPlayers; const ally = st.players[allyIdx]; if (!ally?.alive) continue; if (ally.faction !== "蜀") continue; if (ally.hand.length === 0) continue; await applyAtom(st, { type: "请求回应", requestType: "杀/respondKill", target: allyIdx, prompt: { type: "useCard", title: `激将:主公(${st.players[seat]?.name ?? `P${seat}`})需要杀,是否打出一张杀?`, cardFilter: { filter: (c) => c.name === "杀", min: 1, max: 1 } }, timeout: 15 }); if (frameCards(st).find((id) => { return st.cardMap[id]?.name === "杀"; })) return; } return; } if (atom?.requestType === REQUEST_TYPE$5) st.localVars[CHOICE_VAR$2] = { cardId: params.cardId, targets: params.targets }; })); } return () => { for (const u of unloaders) u(); }; } function onMount$195(skill, api) { api.defineAction("use", { label: "激将", style: "primary", prompt: { type: "choosePlayer", title: "激将:选择一名蜀势力角色出杀", min: 1, max: 1, filter: (view, t) => t !== skill.ownerId && view.players[t]?.alive === true && view.players[t]?.faction === "蜀" }, activeWhen: (ctx) => defaultPlayActive(ctx) && isLordOwner(skill.ownerId) }); api.defineAction("respond", { label: "激将", style: "primary", prompt: { type: "confirm", title: "激将:令蜀势力角色替你打出杀?", confirmLabel: "激将", cancelLabel: "不发动" }, activeWhen: (ctx) => { const slot = ctx.view.pending; if (!slot) return false; if (slot.target !== ctx.perspectiveIdx) return false; const atom = slot.atom; if (atom.type === "询问杀") return true; if (atom.requestType === REQUEST_TYPE$5) return true; return false; } }); return () => {}; } var REQUEST_TYPE$5, CHOICE_VAR$2, KILL_TARGET_VAR; var init_激将 = __esmMin((() => { init_apply(); init_frame(); init_use_card(); init_skill(); init_distance(); init_action_active(); REQUEST_TYPE$5 = "激将/出杀"; CHOICE_VAR$2 = "激将/出杀选择"; KILL_TARGET_VAR = "激将/killTarget"; })); //#endregion //#region src/engine/skills/激昂.ts var 激昂_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$210, default: () => 激昂_default, onInit: () => onInit$210, onMount: () => onMount$194 }); /** source 情形去重:已为本 cardId 触发过(localVars 标记,键含 cardId) */ function srcDoneKey$1(cardId) { return `激昂/src/${cardId}`; } /** 判定本次「成为目标」是否为激昂可触发场景(决斗或红色杀) */ function isJiangTrigger$1(state, cardId) { const card = cardId ? state.cardMap[cardId] : void 0; if (card?.name === "决斗") return true; if (card?.name === "杀" && card.color === "红") return true; const frameSkill = topFrame(state)?.skillId; if (frameSkill === "决斗" || frameSkill === "离间") return true; return false; } function createSkill$210(id, ownerId) { return { id, ownerId, name: "激昂", description: "使用或被使用【决斗】或红色【杀】时,你可以摸一张牌" }; } function onInit$210(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "respond", (st, _params) => { const slot = st.pendingSlots.get(ownerId); if (!slot) return "当前不需要回应"; const atom = slot.atom; if (atom["type"] !== "请求回应") return "当前不需要回应"; if (atom["requestType"] !== CONFIRM_RT$50) return "当前不是激昂确认"; return null; }, async (st, params) => { st.localVars[CONFIRMED_KEY$41] = params.choice === true || params.confirmed === true; }); registerAfterHook(state, skill.id, ownerId, "成为目标", async (ctx) => { const atom = ctx.atom; if (!(atom.source === ownerId || atom.target === ownerId)) return; if (!ctx.state.players[ownerId]?.alive) return; if (!isJiangTrigger$1(ctx.state, atom.cardId)) return; if (atom.source === ownerId) { const key = srcDoneKey$1(atom.cardId ?? "__virtual__"); if (ctx.state.localVars[key]) return; ctx.state.localVars[key] = true; } delete ctx.state.localVars[CONFIRMED_KEY$41]; await applyAtom(ctx.state, { type: "请求回应", requestType: CONFIRM_RT$50, target: ownerId, prompt: { type: "confirm", title: "是否发动激昂?(摸一张牌)", confirmLabel: "发动", cancelLabel: "不发动" }, defaultChoice: false, timeout: 15 }); if (!ctx.state.localVars[CONFIRMED_KEY$41]) return; await applyAtom(ctx.state, { type: "摸牌", player: ownerId, count: 1 }); }); return () => {}; } function onMount$194(_skill, api) { api.defineAction("respond", { label: "激昂", style: "primary", prompt: { type: "confirm", title: "是否发动激昂?(摸一张牌)", confirmLabel: "发动", cancelLabel: "不发动" } }); } var CONFIRM_RT$50, CONFIRMED_KEY$41, 激昂_default; var init_激昂 = __esmMin((() => { init_apply(); init_frame(); init_skill(); CONFIRM_RT$50 = "激昂/confirm"; CONFIRMED_KEY$41 = "激昂/confirmed"; 激昂_default = { createSkill: createSkill$210, onInit: onInit$210, onMount: onMount$194 }; })); //#endregion //#region src/engine/skills/火计.ts var 火计_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$209, default: () => 火计_default, onInit: () => onInit$209, onMount: () => onMount$193 }); function createSkill$209(id, ownerId) { return { id, ownerId, name: "火计", description: "你可以将一张红色手牌当【火攻】使用" }; } /** 影子卡 id:${原id}#火计 */ function shadowIdOf$18(cardId) { return `${cardId}#火计`; } function onInit$209(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "transform", (state, params) => { const myTurn = state.currentPlayerIndex === ownerId; const inActPhase = state.phase === "出牌"; const free = !hasBlockingPending(state); const self = state.players[ownerId]; const selfAlive = self.alive === true; const cardId = params.cardId; const cardIdOk = typeof cardId === "string"; const card = cardIdOk ? state.cardMap[cardId] : void 0; const cardInHand = cardIdOk && self.hand.includes(cardId); const isRed = !!card && card.color === "红"; return myTurn && inActPhase && free && selfAlive && cardInHand && isRed ? null : "现在不能使用火计"; }, async (state, params) => { const cardId = params.cardId; const shadowId = shadowIdOf$18(cardId); await applyAtom(state, { type: "当作", player: ownerId, cardIds: [cardId], shadowId, outputName: "火攻" }); }, (state, params) => { const cardId = params.cardId; const sId = shadowIdOf$18(cardId); delete state.cardMap[sId]; const self = state.players[ownerId]; const idx = self.hand.indexOf(sId); if (idx >= 0) self.hand[idx] = cardId; }); return () => {}; } function onMount$193(skill, api) { api.defineAction("transform", { label: "火计", style: "passive", prompt: { type: "useCardAndTarget", title: "选择一张红色牌当火攻使用", cardFilter: { filter: (c) => c.color === "红", min: 1, max: 1 }, targetFilter: { min: 1, max: 1, filter: (view, t) => (view.players[t]?.handCount ?? 0) > 0 } }, transform: (card) => ({ name: "火攻", sourceCardId: card.id, fromSkill: skill.id }), activeWhen: (ctx) => { if (!defaultPlayActive(ctx)) return false; const p = ctx.view.players[ctx.perspectiveIdx]; if (!p) return false; return p.hand?.some((c) => c.color === "红") ?? false; } }); } var 火计_default; var init_火计 = __esmMin((() => { init_skill(); init_apply(); init_action_active(); 火计_default = { createSkill: createSkill$209, onInit: onInit$209, onMount: onMount$193 }; })); //#endregion //#region src/engine/skills/烈刃.ts var 烈刃_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$208, default: () => 烈刃_default, onInit: () => onInit$208, onMount: () => onMount$192 }); /** 拼点牌点数:A=1, 2-10=面值, J=11, Q=12, K=13 */ function rankValue$8(rank) { if (rank === "A") return 1; if (rank === "J") return 11; if (rank === "Q") return 12; if (rank === "K") return 13; const n = parseInt(rank, 10); return Number.isFinite(n) ? n : 0; } function createSkill$208(id, ownerId) { return { id, ownerId, name: "烈刃", description: "使用杀造成伤害后,可与受害者拼点,赢则获得对方一张牌" }; } function onInit$208(skill, state) { const ownerId = skill.ownerId; const unloaders = []; for (const p of state.players) { const seatId = p.index; const u = registerAction(state, skill.id, seatId, "respond", (st, params) => { const slot = st.pendingSlots.get(seatId); if (!slot) return "当前不需要回应"; const atom = slot.atom; if (atom["type"] !== "请求回应") return "当前不需要回应"; const reqType = atom["requestType"]; if (reqType === CONFIRM_RT$49) { if (seatId !== ownerId) return "只有祝融可以确认"; return null; } if (reqType === OWNER_PD_RT) { if (seatId !== ownerId) return "只有祝融可以选择拼点牌"; const cardId = params.cardId; if (typeof cardId !== "string") return "请选择一张拼点牌"; if (!st.players[seatId].hand.includes(cardId)) return "拼点牌不在手牌中"; return null; } if (reqType === VICTIM_PD_RT) { const cardId = params.cardId; if (typeof cardId !== "string") return "请选择一张拼点牌"; if (!st.players[seatId].hand.includes(cardId)) return "拼点牌不在手牌中"; return null; } return "当前不是烈刃回应"; }, async (st, params) => { const slot = st.pendingSlots.get(seatId); if (!slot) return; const reqType = slot.atom["requestType"]; if (reqType === CONFIRM_RT$49) st.localVars[CONFIRMED_KEY$40] = params.choice === true || params.confirmed === true; else if (reqType === OWNER_PD_RT) st.localVars[OWNER_CARD_KEY$1] = params.cardId; else if (reqType === VICTIM_PD_RT) st.localVars[VICTIM_CARD_KEY] = params.cardId; }); unloaders.push(u); } registerAfterHook(state, skill.id, ownerId, "造成伤害后", async (ctx) => { const atom = ctx.atom; if (atom.source !== ownerId) return; if ((atom.amount ?? 0) <= 0) return; if (atom.target === void 0 || atom.target === ownerId) return; const dmgCardId = atom.cardId; if (!dmgCardId) return; if (ctx.state.cardMap[dmgCardId]?.name !== "杀") return; const victim = atom.target; const victimPlayer = ctx.state.players[victim]; if (!victimPlayer?.alive) return; const ownerPlayer = ctx.state.players[ownerId]; if (!ownerPlayer || ownerPlayer.hand.length === 0) return; if (victimPlayer.hand.length === 0) return; delete ctx.state.localVars[CONFIRMED_KEY$40]; await applyAtom(ctx.state, { type: "请求回应", requestType: CONFIRM_RT$49, target: ownerId, prompt: { type: "confirm", title: `是否发动烈刃,与 ${victimPlayer.name} 拼点?`, confirmLabel: "发动", cancelLabel: "不发动" }, defaultChoice: false, timeout: 15 }); if (!ctx.state.localVars[CONFIRMED_KEY$40]) return; if (ctx.state.players[ownerId].hand.length === 0) return; if (ctx.state.players[victim].hand.length === 0) return; delete ctx.state.localVars[OWNER_CARD_KEY$1]; await applyAtom(ctx.state, { type: "请求回应", requestType: OWNER_PD_RT, target: ownerId, prompt: { type: "useCard", title: "烈刃:请选择一张拼点牌", cardFilter: { filter: () => true, min: 1, max: 1 } }, timeout: 30 }); const ownerCardId = ctx.state.localVars[OWNER_CARD_KEY$1]; if (!ownerCardId || !ctx.state.players[ownerId].hand.includes(ownerCardId)) return; delete ctx.state.localVars[VICTIM_CARD_KEY]; await applyAtom(ctx.state, { type: "请求回应", requestType: VICTIM_PD_RT, target: victim, prompt: { type: "useCard", title: `烈刃:与 ${ctx.state.players[ownerId].name} 拼点,请出一张手牌`, cardFilter: { filter: () => true, min: 1, max: 1 } }, timeout: 30 }); const victimCardId = ctx.state.localVars[VICTIM_CARD_KEY]; delete ctx.state.localVars[VICTIM_CARD_KEY]; await pushFrame(ctx.state, "烈刃", ownerId, { victim }); const ownerCard = ctx.state.cardMap[ownerCardId]; const ownerValue = ownerCard ? rankValue$8(ownerCard.rank) : 0; let win; if (victimCardId && ctx.state.players[victim].hand.includes(victimCardId)) win = await runRankCompareFlow(ctx.state, ownerId, victim, ownerCardId, victimCardId) === "赢"; else { await applyAtom(ctx.state, { type: "移动牌", cardId: ownerCardId, from: { zone: "手牌", player: ownerId }, to: { zone: "弃牌堆" } }); win = ownerValue > 0; } await popFrame(ctx.state); if (!win) return; const target = ctx.state.players[victim]; if (!target) return; if (target.hand.length > 0) { const rng = createRng(ctx.state.rngSeed); const cardId = target.hand[rng.nextInt(target.hand.length)]; ctx.state.rngSeed = rng.getState(); await applyAtom(ctx.state, { type: "获得", player: ownerId, cardId, from: victim }); } else { const equipSlot = Object.keys(target.equipment)[0]; if (equipSlot) { const cardId = target.equipment[equipSlot]; if (cardId) await applyAtom(ctx.state, { type: "获得", player: ownerId, cardId, from: victim }); } } }); return () => { unloaders.forEach((u) => u()); }; } function onMount$192(_skill, api) { api.defineAction("respond", { label: "烈刃", style: "danger", prompt: { type: "confirm", title: "是否发动烈刃?", confirmLabel: "发动", cancelLabel: "不发动" } }); } var CONFIRM_RT$49, CONFIRMED_KEY$40, OWNER_PD_RT, OWNER_CARD_KEY$1, VICTIM_PD_RT, VICTIM_CARD_KEY, 烈刃_default; var init_烈刃 = __esmMin((() => { init_apply(); init_frame(); init_rank(); init_skill(); init_rng(); CONFIRM_RT$49 = "烈刃/confirm"; CONFIRMED_KEY$40 = "烈刃/confirmed"; OWNER_PD_RT = "烈刃/选牌"; OWNER_CARD_KEY$1 = "烈刃/ownerCard"; VICTIM_PD_RT = "烈刃/拼点"; VICTIM_CARD_KEY = "烈刃/victimCard"; 烈刃_default = { createSkill: createSkill$208, onInit: onInit$208, onMount: onMount$192 }; })); //#endregion //#region src/engine/skills/烈弓.ts var 烈弓_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$207, onInit: () => onInit$207, onMount: () => onMount$191 }); function createSkill$207(id, ownerId) { return { id, ownerId, name: "烈弓", description: "使用杀指定目标后,若其手牌数不小于你的体力值或不大于你的攻击范围,可令其不能抵消此杀" }; } function onInit$207(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "respond", (state, _params) => { const slot = state.pendingSlots.get(ownerId); if (!slot) return "当前不需要回应"; const atom = slot.atom; if (atom.type !== "请求回应") return "当前不是请求回应"; if (atom.requestType !== "烈弓/confirm") return "当前不是烈弓确认"; return null; }, async (state, params) => { state.localVars[CONFIRM$5] = params.choice === true || params.confirmed === true; }); registerAfterHook(state, skill.id, ownerId, "指定目标", async (ctx) => { const atom = ctx.atom; if (atom.source !== ownerId) return; if (atom.target === void 0) return; const target = atom.target; if (atom.cardId !== void 0) { if (ctx.state.cardMap[atom.cardId]?.name !== "杀") return; } const self = ctx.state.players[ownerId]; const targetPlayer = ctx.state.players[target]; if (!self?.alive || !targetPlayer?.alive) return; const attackRange = getDistanceAttackRange(self.vars) ?? 1; if (!(targetPlayer.hand.length >= getHealthValue(self) || targetPlayer.hand.length <= attackRange)) return; delete ctx.state.localVars[CONFIRM$5]; await applyAtom(ctx.state, { type: "请求回应", requestType: "烈弓/confirm", target: ownerId, prompt: { type: "confirm", title: "是否发动烈弓(令目标不能出闪)?", confirmLabel: "发动", cancelLabel: "不发动" }, defaultChoice: false, timeout: 10 }); if (!ctx.state.localVars[CONFIRM$5]) return; await applyAtom(ctx.state, { type: "加标签", player: target, tag: TAG$3 }); }); registerBeforeHook(state, skill.id, ownerId, "询问闪", async (ctx) => { const atom = ctx.atom; if (atom.source !== ownerId) return; const target = atom.target; if (target === void 0) return; if (!ctx.state.players[target]?.tags.includes(TAG$3)) return; await applyAtom(ctx.state, { type: "去标签", player: target, tag: TAG$3 }); return { kind: "cancel" }; }); return () => {}; } function onMount$191(_skill, api) { api.defineAction("respond", { label: "烈弓", style: "default", prompt: { type: "confirm", title: "是否发动烈弓?", confirmLabel: "发动", cancelLabel: "不发动" } }); } var TAG$3, CONFIRM$5; var init_烈弓 = __esmMin((() => { init_types(); init_apply(); init_skill(); init_vars_keys(); TAG$3 = "烈弓/禁闪"; CONFIRM$5 = "烈弓/confirmed"; })); //#endregion //#region src/engine/skills/父荫.ts var 父荫_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$206, default: () => _skillModule$103, onInit: () => onInit$206, onMount: () => onMount$190 }); function createSkill$206(id, ownerId) { return { id, ownerId, name: SKILL_ID$49, description: "锁定技:每回合首次成为其他角色杀或决斗的目标后,若其手牌数不小于你,此牌对你无效", isLocked: true }; } function onInit$206(skill, state) { const ownerId = skill.ownerId; registerBeforeHook(state, skill.id, ownerId, "检测有效性", async (ctx) => { const atom = ctx.atom; if (atom.target !== ownerId) return; const source = atom.source; if (source === void 0 || source === ownerId) return; const cardId = atom.cardId; if (!cardId) return; const card = ctx.state.cardMap[cardId]; if (!card) return; if (card.name !== "杀" && card.name !== "决斗") return; if (usedThisTurn$3(ctx.state, ownerId, SKILL_ID$49)) return; await markOncePerTurn(ctx.state, ownerId, SKILL_ID$49); const sourcePlayer = ctx.state.players[source]; const selfPlayer = ctx.state.players[ownerId]; if (!sourcePlayer || !selfPlayer) return; if (sourcePlayer.hand.length >= selfPlayer.hand.length) return { kind: "cancel" }; }); return () => {}; } function onMount$190(_skill, _api) { return () => {}; } var SKILL_ID$49, _skillModule$103; var init_父荫 = __esmMin((() => { init_skill(); init_once_per_turn(); SKILL_ID$49 = "父荫"; _skillModule$103 = { createSkill: createSkill$206, onInit: onInit$206, onMount: onMount$190 }; })); //#endregion //#region src/engine/skills/狂骨.ts var 狂骨_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$205, default: () => 狂骨_default, onInit: () => onInit$205, onMount: () => onMount$189 }); function createSkill$205(id, ownerId) { return { id, ownerId, name: "狂骨", description: "对距离1以内的角色每造成1点伤害后,你可以回复1点体力或摸一张牌" }; } function onInit$205(skill, state) { const ownerId = skill.ownerId; const offRespond = registerAction(state, skill.id, ownerId, "respond", (st, _params) => { const slot = st.pendingSlots.get(ownerId); if (!slot) return "当前不需要回应"; const atom = slot.atom; if (atom["type"] !== "请求回应") return "当前不需要回应"; const rt = atom["requestType"]; if (rt !== CONFIRM_REQUEST$13 && rt !== CHOOSE_REQUEST$2) return "当前不是狂骨询问"; return null; }, async (st, params) => { const rt = (st.pendingSlots.get(ownerId)?.atom)?.requestType; if (rt === CONFIRM_REQUEST$13) st.localVars[CONFIRMED_KEY$39] = params.choice === true || params.confirmed === true; else if (rt === CHOOSE_REQUEST$2) st.localVars[CHOICE_KEY$12] = params.choice === true ? "heal" : "draw"; }); registerAfterHook(state, skill.id, ownerId, "造成伤害后", async (ctx) => { const atom = ctx.atom; if (atom.source !== ownerId) return; const amount = atom.amount ?? 0; if (amount <= 0) return; if (atom.target === void 0) return; const target = atom.target; if (!ctx.state.players[target]?.alive) return; if (effectiveDistance(ctx.state, ownerId, target) > 1) return; for (let i = 0; i < amount; i++) { if (!ctx.state.players[ownerId]?.alive) return; delete ctx.state.localVars[CONFIRMED_KEY$39]; await applyAtom(ctx.state, { type: "请求回应", requestType: CONFIRM_REQUEST$13, target: ownerId, prompt: { type: "confirm", title: `是否发动狂骨?(第 ${i + 1}/${amount} 点伤害)`, confirmLabel: "发动", cancelLabel: "不发动" }, defaultChoice: false, timeout: 30 }); if (ctx.state.localVars[CONFIRMED_KEY$39] !== true) continue; delete ctx.state.localVars[CHOICE_KEY$12]; await applyAtom(ctx.state, { type: "请求回应", requestType: CHOOSE_REQUEST$2, target: ownerId, prompt: { type: "confirm", title: "狂骨:回复1点体力,或摸一张牌?", confirmLabel: "回复1点体力", cancelLabel: "摸一张牌" }, defaultChoice: false, timeout: 30 }); if (ctx.state.localVars[CHOICE_KEY$12] === "heal") await applyAtom(ctx.state, { type: "回复体力", target: ownerId, amount: 1 }); else await applyAtom(ctx.state, { type: "摸牌", player: ownerId, count: 1 }); } }); return () => { offRespond(); }; } function onMount$189(_skill, _api) {} var CONFIRM_REQUEST$13, CHOOSE_REQUEST$2, CONFIRMED_KEY$39, CHOICE_KEY$12, 狂骨_default; var init_狂骨 = __esmMin((() => { init_apply(); init_skill(); init_distance(); CONFIRM_REQUEST$13 = "狂骨/confirm"; CHOOSE_REQUEST$2 = "狂骨/choose"; CONFIRMED_KEY$39 = "狂骨/confirmed"; CHOICE_KEY$12 = "狂骨/choice"; 狂骨_default = { createSkill: createSkill$205, onInit: onInit$205, onMount: onMount$189 }; })); //#endregion //#region src/engine/skills/界不屈.ts var 界不屈_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$204, default: () => 界不屈_default, onInit: () => onInit$204 }); function createSkill$204(id, ownerId) { return { id, ownerId, name: "界不屈", description: "锁定技:濒死时翻牌堆顶作\"创\"牌置于武将牌上,点数与已有创均不同则回复至1体力,相同则移去此牌;武将牌上有\"创\"时手牌上限为\"创\"的数量", isLocked: true }; } function onInit$204(skill, state) { const ownerId = skill.ownerId; const unloadHook = registerAfterHook(state, skill.id, ownerId, "陷入濒死", async (ctx) => { if (ctx.atom.target !== ownerId) return; if (!ctx.state.players[ownerId]?.alive) return; if (ctx.state.zones.deck.length === 0) return; delete ctx.state.localVars[DUPLICATE_KEY]; await applyAtom(ctx.state, { type: "置创牌", player: ownerId, varsKey: WOUND_KEY, resultKey: DUPLICATE_KEY }); const duplicate = ctx.state.localVars[DUPLICATE_KEY]; delete ctx.state.localVars[DUPLICATE_KEY]; if (duplicate) return; const healthAfter = ctx.state.players[ownerId].health; const amount = Math.max(0, 1 - healthAfter); if (amount > 0) await applyAtom(ctx.state, { type: "回复体力", target: ownerId, amount }); ctx.state.localVars[SURVIVE_KEY] = ownerId; }); const unloadProvider = registerHandLimitProvider(state, ownerId, (st, player) => { if (player !== ownerId) return void 0; const list = st.players[player]?.vars[WOUND_KEY] ?? []; return list.length > 0 ? list.length : void 0; }); return () => { unloadHook(); unloadProvider(); }; } var SURVIVE_KEY, WOUND_KEY, DUPLICATE_KEY, 界不屈_default; var init_界不屈 = __esmMin((() => { init_apply(); init_skill(); init_hand_limit(); SURVIVE_KEY = "不屈/存活"; WOUND_KEY = "不屈/创牌"; DUPLICATE_KEY = "不屈/重复"; 界不屈_default = { createSkill: createSkill$204, onInit: onInit$204 }; })); //#endregion //#region src/engine/skills/界义从.ts var 界义从_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$203, default: () => _skillModule$102, onInit: () => onInit$203, onMount: () => onMount$188 }); function createSkill$203(id, ownerId) { return { id, ownerId, name: "界义从", description: "锁定技:你计算与其他角色的距离-1;若你的体力值不大于2,其他角色计算与你的距离+1", isLocked: true }; } /** 根据当前体力,在 state 上同步防御修正 vars + mark + view。 * 必须在 after hook 中调用(走 atom 管线以同步 view)。 */ async function syncDefenseMod(state, ownerId) { const me = state.players[ownerId]; if (!me) return; const shouldActive = me.alive && me.health > 0 && me.health <= LOW_HEALTH_THRESHOLD; const has = me.marks.some((m) => m.id === LOW_HEALTH_MARK_ID); if (shouldActive && !has) { me.vars[DISTANCE_DEFENSE_MOD_KEY] = 1; await applyAtom(state, { type: "加标记", player: ownerId, mark: { id: LOW_HEALTH_MARK_ID, scope: ownerId, payload: {} }, distanceVars: { defenseMod: 1 } }); } else if (!shouldActive && has) { delete me.vars[DISTANCE_DEFENSE_MOD_KEY]; await applyAtom(state, { type: "去标记", player: ownerId, markId: LOW_HEALTH_MARK_ID, distanceVars: { defenseMod: void 0 } }); } } function onInit$203(skill, state) { const ownerId = skill.ownerId; state.players[ownerId].vars[DISTANCE_ATTACK_MOD_KEY] = 1; const me = state.players[ownerId]; if (me.alive && me.health > 0 && me.health <= LOW_HEALTH_THRESHOLD) { me.vars[DISTANCE_DEFENSE_MOD_KEY] = 1; me.marks.push({ id: LOW_HEALTH_MARK_ID, scope: ownerId, payload: {} }); } registerAfterHook(state, skill.id, ownerId, "扣减体力后", async (ctx) => { if (ctx.atom.target === ownerId) await syncDefenseMod(ctx.state, ownerId); }); registerAfterHook(state, skill.id, ownerId, "回复体力", async (ctx) => { if (ctx.atom.target === ownerId) await syncDefenseMod(ctx.state, ownerId); }); registerAfterHook(state, skill.id, ownerId, "失去体力", async (ctx) => { if (ctx.atom.target === ownerId) await syncDefenseMod(ctx.state, ownerId); }); return () => { delete state.players[ownerId]?.vars[DISTANCE_ATTACK_MOD_KEY]; delete state.players[ownerId]?.vars[DISTANCE_DEFENSE_MOD_KEY]; const me = state.players[ownerId]; if (me) me.marks = me.marks.filter((m) => m.id !== LOW_HEALTH_MARK_ID); }; } function onMount$188(_skill, _api) {} var LOW_HEALTH_THRESHOLD, LOW_HEALTH_MARK_ID, _skillModule$102; var init_界义从 = __esmMin((() => { init_apply(); init_skill(); init_vars_keys(); LOW_HEALTH_THRESHOLD = 2; LOW_HEALTH_MARK_ID = "义从/低血"; _skillModule$102 = { createSkill: createSkill$203, onInit: onInit$203, onMount: onMount$188 }; })); //#endregion //#region src/engine/skills/界乱击.ts var 界乱击_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$202, default: () => _skillModule$101, onInit: () => onInit$202, onMount: () => onMount$187 }); function createSkill$202(id, ownerId) { return { id, ownerId, name: DISPLAY_NAME$74, description: "你可以将两张花色相同的手牌当【万箭齐发】使用;你使用【万箭齐发】可以少选一个目标" }; } /** 影子卡 id:${id1}#${id2}#界乱击 —— 拼接两张原卡 id,'界乱击' 后缀避免与标版冲突 */ function shadowIdOf$17(id1, id2) { return `${id1}#${id2}#界乱击`; } function onInit$202(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "transform", (state, params) => { const myTurn = state.currentPlayerIndex === ownerId; const inActPhase = state.phase === "出牌"; const free = !hasBlockingPending(state); const self = state.players[ownerId]; const selfAlive = self.alive === true; const cardIds = params.cardIds; if (!Array.isArray(cardIds) || cardIds.length !== 2) return "需要选择 2 张手牌"; const [id1, id2] = cardIds; if (typeof id1 !== "string" || typeof id2 !== "string") return "cardIds 必须为字符串"; if (id1 === id2) return "不能选择同一张牌"; const cardInHand = !!self && self.hand.includes(id1) && self.hand.includes(id2); const c1 = state.cardMap[id1]; const c2 = state.cardMap[id2]; const cardsExist = !!c1 && !!c2; const sameSuit = !!c1 && !!c2 && c1.suit !== "" && c1.suit === c2.suit; return myTurn && inActPhase && free && selfAlive && cardInHand && cardsExist && sameSuit ? null : "界乱击需要两张同花色的手牌"; }, async (state, params) => { const cardIds = params.cardIds; const [id1, id2] = cardIds; await applyAtom(state, { type: "当作", player: ownerId, cardIds, shadowId: shadowIdOf$17(id1, id2), outputName: "万箭齐发" }); }, (state, params) => { const cardIds = params.cardIds; const [id1, id2] = Array.isArray(cardIds) ? cardIds : []; const sId = id1 && id2 ? shadowIdOf$17(id1, id2) : void 0; if (sId) { delete state.cardMap[sId]; const self = state.players[ownerId]; const idx = self.hand.indexOf(sId); if (idx >= 0) self.hand.splice(idx, 1); self.hand.push(id1, id2); } }); registerAction(state, "万箭齐发", ownerId, "use", (state, params) => { if (state.currentPlayerIndex !== ownerId) return "不是你的回合"; if (state.phase !== "出牌") return "不是出牌阶段"; if (hasBlockingPending(state)) return "当前有等待响应"; const self = state.players[ownerId]; if (!self?.alive) return "你已死亡"; const cardId = params.cardId; if (typeof cardId !== "string") return "cardId required"; if (!self.hand.includes(cardId)) return "牌不在手牌中"; if (state.cardMap[cardId]?.name !== "万箭齐发") return "不是万箭齐发"; return null; }, async (state, params) => { const from = ownerId; const cardId = params.cardId; const alivePlayers = state.players.filter((p) => p.alive); const n = alivePlayers.length; const defaultTargets = []; if (n > 1) { const fromPos = alivePlayers.findIndex((p) => p.index === from); if (fromPos >= 0) for (let i = 1; i < n; i++) defaultTargets.push(alivePlayers[(fromPos + i) % n].index); } let targets = defaultTargets; if (defaultTargets.length >= 1) { delete state.localVars[SKIP_TARGET_KEY]; await applyAtom(state, { type: "请求回应", requestType: SKIP_TARGET_RT, target: from, prompt: { type: "choosePlayer", title: "界乱击:可少选一个目标(选 0 或 1 名排除)", description: "可放弃(不排除任何目标)", min: 0, max: 1, filter: (_view, t) => defaultTargets.includes(t) }, timeout: 20 }); const skipTarget = state.localVars[SKIP_TARGET_KEY]; delete state.localVars[SKIP_TARGET_KEY]; if (typeof skipTarget === "number") targets = defaultTargets.filter((t) => t !== skipTarget); } await runUseFlow(state, from, cardId, targets, "万箭齐发", { onSettle: chargeOnSettle(state, from, cardId) }); }); registerAction(state, skill.id, ownerId, "respond", (state, params) => { const slot = state.pendingSlots.get(ownerId); if (!slot) return "当前不需要回应"; const atom = slot.atom; if (atom.type !== "请求回应") return "当前不是界乱击询问"; if (atom.requestType !== SKIP_TARGET_RT) return "当前不是界乱击询问"; const target = params.target ?? (Array.isArray(params.targets) ? params.targets[0] : void 0); if (target === void 0 || target === null) return null; if (typeof target !== "number") return "target 必须为数字"; if (!state.players[target]?.alive) return "目标不存在或已死亡"; return null; }, async (state, params) => { const target = params.target ?? (Array.isArray(params.targets) ? params.targets[0] : void 0); state.localVars[SKIP_TARGET_KEY] = typeof target === "number" ? target : null; }); return () => {}; } function onMount$187(skill, api) { api.defineAction("transform", { label: DISPLAY_NAME$74, style: "danger", prompt: { type: "useCardAndTarget", title: "选择 2 张同花色的手牌当万箭齐发使用(界版:可少选一个目标)", cardFilter: { filter: () => true, min: 2, max: 2, comboFilter: (cards) => cards[0]?.suit === cards[1]?.suit }, targetFilter: { min: 0, max: 0 } }, transform: (card) => ({ name: "万箭齐发", sourceCardId: card.id, fromSkill: skill.id }), activeWhen: (ctx) => { if (!defaultPlayActive(ctx)) return false; const p = ctx.view.players[ctx.perspectiveIdx]; if (!p?.hand) return false; const suitCount = {}; for (const c of p.hand) { if (!c.suit) continue; suitCount[c.suit] = (suitCount[c.suit] ?? 0) + 1; if (suitCount[c.suit] >= 2) return true; } return false; } }); api.defineAction("respond", { label: DISPLAY_NAME$74, style: "danger", prompt: { type: "choosePlayer", title: "界乱击:少选一个目标(可选)", min: 0, max: 1 } }); } var SKILL_ID$48, DISPLAY_NAME$74, SKIP_TARGET_RT, SKIP_TARGET_KEY, _skillModule$101; var init_界乱击 = __esmMin((() => { init_skill(); init_apply(); init_use_card(); init_action_active(); SKILL_ID$48 = "界乱击"; DISPLAY_NAME$74 = "乱击"; SKIP_TARGET_RT = `${SKILL_ID$48}/skipTarget`; SKIP_TARGET_KEY = `${SKILL_ID$48}/skipTargetChoice`; _skillModule$101 = { createSkill: createSkill$202, onInit: onInit$202, onMount: onMount$187 }; })); //#endregion //#region src/engine/skills/界乱武.ts var 界乱武_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$201, default: () => _skillModule$100, onInit: () => onInit$201, onMount: () => onMount$186 }); function createSkill$201(id, ownerId) { return { id, ownerId, name: DISPLAY_NAME$73, description: "限定技:出牌阶段令所有其他角色依次对距离最小者出杀,无法如此做者失去1点体力;结算后可视为使用一张无距离限制的【杀】" }; } /** P 的"距离最近的另一名角色"集合(存活,含贾诩) */ function nearestOthers(state, p) { let min = Infinity; const dist = /* @__PURE__ */ new Map(); for (let i = 0; i < state.players.length; i++) { if (i === p) continue; if (!state.players[i]?.alive) continue; const d = effectiveDistance(state, p, i); dist.set(i, d); if (d < min) min = d; } const result = []; for (const [i, d] of dist) if (d === min) result.push(i); return result; } /** * 执行一次"视为出杀"的完整结算(无实体牌)。模型参考 界仁德.virtualKill。 * 无距离限制(调用方已选择目标,不再校验距离)。 */ async function virtualKill$6(state, source, target) { if (!state.players[target]?.alive) return; const cardId = `界乱武:杀:${source}:${target}:${state.seq}`; state.cardMap[cardId] = { id: cardId, name: "杀", suit: "", color: "无色", rank: "A", type: "基本牌" }; await runUseFlow(state, source, cardId, [target], "杀", { virtual: true, onSettle: chargeOnSettle(state, source, cardId) }); delete state.cardMap[cardId]; } function onInit$201(skill, state) { const ownerId = skill.ownerId; const unloaders = []; unloaders.push(registerAction(state, skill.id, ownerId, "use", (st, _params) => { if (st.currentPlayerIndex !== ownerId) return "不是你的回合"; if (st.phase !== "出牌") return "只能在出牌阶段发动"; if (hasBlockingPending(st)) return "当前有未完成的询问"; if (st.players[ownerId]?.vars[USED_KEY$13]) return "乱武已使用过(限定技)"; if (!st.players[ownerId]?.alive) return "玩家不存在或已死亡"; if (st.players.filter((p) => p.alive && p.index !== ownerId).length === 0) return "场上没有其他角色"; return null; }, async (st, _params) => { const from = ownerId; st.players[from].vars[USED_KEY$13] = true; await pushFrame(state, "界乱武", from, {}); try { const n = st.players.length; for (let step = 1; step < n; step++) { const p = (from + step) % n; if (p === from) continue; if (!st.players[p]?.alive) continue; const nearest = nearestOthers(st, p); if (nearest.length === 0) { await applyAtom(st, { type: "失去体力", target: p, amount: 1 }); continue; } delete st.localVars[CHOICE_VAR$1]; await applyAtom(st, { type: "请求回应", requestType: REQUEST_TYPE$4, target: p, prompt: { type: "useCardAndTarget", title: `乱武:对距离最近的角色使用一张【杀】,否则失去1点体力`, cardFilter: { filter: (c) => c.name === "杀", min: 1, max: 1 }, targetFilter: { min: 1, max: 1, filter: (_view, t) => nearest.includes(t) } }, timeout: 20 }); const choice = st.localVars[CHOICE_VAR$1]; delete st.localVars[CHOICE_VAR$1]; if (choice && st.players[p].hand.includes(choice.cardId) && nearestOthers(st, p).includes(choice.target) && st.players[choice.target]?.alive) await runUseFlow(st, p, choice.cardId, [choice.target], "杀"); else await applyAtom(st, { type: "失去体力", target: p, amount: 1 }); } if (st.players[from]?.alive && canSlash(st, from)) { if (st.players.filter((p) => p.alive && p.index !== from).length > 0) { delete st.localVars[FINAL_TARGET_VAR]; await applyAtom(st, { type: "请求回应", requestType: FINAL_TARGET_RT, target: from, prompt: { type: "choosePlayer", title: "界乱武:视为使用一张无距离限制的【杀】,选择目标(pass 则不使用)", min: 1, max: 1, filter: (_view, t) => t !== from && st.players[t]?.alive === true }, timeout: 20 }); const finalTarget = st.localVars[FINAL_TARGET_VAR]; delete st.localVars[FINAL_TARGET_VAR]; if (typeof finalTarget === "number" && st.players[finalTarget]?.alive) await virtualKill$6(st, from, finalTarget); } } } finally { await popFrame(state); } })); for (const pl of state.players) { const seat = pl.index; unloaders.push(registerAction(state, skill.id, seat, "respond", (st, params) => { const slot = st.pendingSlots.get(seat); if (!slot) return "当前不需要回应"; const atom = slot.atom; if (atom.requestType !== REQUEST_TYPE$4 && atom.requestType !== FINAL_TARGET_RT) return "当前不是乱武询问"; if (atom.requestType === FINAL_TARGET_RT) { const t = params.targets?.[0] ?? (typeof params.target === "number" ? params.target : void 0); if (typeof t !== "number") return "请选择目标"; if (t === seat) return "不能以自己为目标"; if (!st.players[t]?.alive) return "目标不存在或已死亡"; return null; } const cardId = params.cardId; const target = params.target; if (typeof cardId !== "string") return "请选择一张杀"; if (typeof target !== "number") return "请选择目标"; if (!st.players[seat]?.hand.includes(cardId)) return "牌不在手牌中"; if (st.cardMap[cardId]?.name !== "杀") return "只能使用杀"; if (!nearestOthers(st, seat).includes(target)) return "目标不是距离最近的角色"; return null; }, async (st, params) => { if ((st.pendingSlots.get(seat)?.atom)?.requestType === FINAL_TARGET_RT) { const t = params.targets?.[0] ?? (typeof params.target === "number" ? params.target : void 0); if (typeof t === "number") st.localVars[FINAL_TARGET_VAR] = t; return; } st.localVars[CHOICE_VAR$1] = { cardId: params.cardId, target: params.target }; })); } return () => { for (const u of unloaders) u(); }; } function onMount$186(skill, api) { api.defineAction("use", { label: "乱武", style: "primary", prompt: { type: "confirm", title: "乱武(限定技):令所有其他角色依次对距离最近者出杀,无法如此做者失去1点体力;结算后可视为使用一张无距离限制的【杀】" }, activeWhen: (ctx) => defaultPlayActive(ctx) }); api.defineAction("respond", { label: "出杀", style: "danger", prompt: { type: "useCardAndTarget", title: "乱武:对距离最近的角色使用一张【杀】,否则失去1点体力", cardFilter: { filter: (c) => c.name === "杀", min: 1, max: 1 }, targetFilter: { min: 1, max: 1 } } }); return () => {}; } var DISPLAY_NAME$73, USED_KEY$13, CHOICE_VAR$1, REQUEST_TYPE$4, FINAL_TARGET_VAR, FINAL_TARGET_RT, _skillModule$100; var init_界乱武 = __esmMin((() => { init_apply(); init_frame(); init_action_active(); init_use_card(); init_skill(); init_distance(); init_slash_quota(); DISPLAY_NAME$73 = "乱武"; USED_KEY$13 = "乱武/used"; CHOICE_VAR$1 = "乱武/出杀"; REQUEST_TYPE$4 = "界乱武/出杀"; FINAL_TARGET_VAR = "界乱武/最终杀目标"; FINAL_TARGET_RT = "界乱武/最终杀目标"; _skillModule$100 = { createSkill: createSkill$201, onInit: onInit$201, onMount: onMount$186 }; })); //#endregion //#region src/engine/skills/界仁德.ts var 界仁德_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$200, default: () => _skillModule$99, onInit: () => onInit$200, onMount: () => onMount$185 }); function createSkill$200(id, ownerId) { return { id, ownerId, name: "界仁德", description: "出牌阶段:每名角色限一次,将任意张手牌交给一名其他角色;本阶段给出第二张时,可视为使用一张基本牌" }; } /** 创建一张虚拟卡(无实体,仅用于结算流程的 cardId 引用) */ function makeVirtualCard(kind, source, target, seq) { return `仁德:${kind}:${source}:${target}:${seq}`; } /** * 执行一次"视为出杀"的完整结算(runUseFlow virtual 模式)。 * 不消耗手牌;走完整时机 atom 序列(选择目标时/使用时/指定目标/成为目标/...), * 保证激昂/集智/界求援等技能事件一致。计入出杀次数:virtual+charge 下 runUseFlow 的 * onSettle 回调(由 chargeOnSettle 传入)触发 杀.onSettle→incSlashUsed(与下方第 284 行内联注释一致)。 */ async function virtualKill$5(state, source, target) { if (!state.players[target]?.alive) return; const cardId = makeVirtualCard("杀", source, target, state.seq); state.cardMap[cardId] = { id: cardId, name: "杀", suit: "", color: "无色", rank: "A", type: "基本牌" }; await runUseFlow(state, source, cardId, [target], "杀", { virtual: true, onSettle: chargeOnSettle(state, source, cardId) }); delete state.cardMap[cardId]; } /** 视为使用一张【桃】:走 runUseFlow virtual(resolve=回复体力) */ async function virtualPeach(state, source, target) { const t = state.players[target]; if (!t?.alive) return; if (t.health >= t.maxHealth) return; const cardId = makeVirtualCard("桃", source, target, state.seq); state.cardMap[cardId] = { id: cardId, name: "桃", suit: "", color: "无色", rank: "A", type: "基本牌" }; await runUseFlow(state, source, cardId, [target], "桃", { virtual: true }); delete state.cardMap[cardId]; } /** 视为使用一张【酒】:走 runUseFlow virtual(resolve=加增伤标记) */ async function virtualWine$1(state, source) { const cardId = makeVirtualCard("酒", source, source, state.seq); state.cardMap[cardId] = { id: cardId, name: "酒", suit: "", color: "无色", rank: "A", type: "基本牌" }; await runUseFlow(state, source, cardId, [source], "酒", { virtual: true }); delete state.cardMap[cardId]; } /** 规范化分配格式(兼容 allocation / targets 分配数组 / 简单格式) */ function normalizeAllocation(params, ownerId) { const allocParam = params.allocation; const allocTargets = params.targets; const simpleCardId = params.cardId; const simpleTargets = params.targets; if (Array.isArray(allocParam) && allocParam.length > 0 && Array.isArray(allocParam[0].cardIds)) return allocParam; if (Array.isArray(allocTargets) && allocTargets.length > 0 && Array.isArray(allocTargets[0].cardIds)) return allocTargets; if (simpleCardId && Array.isArray(simpleTargets) && simpleTargets.length > 0) return [{ target: simpleTargets[0], cardIds: [simpleCardId] }]; return null; } function onInit$200(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "use", (state, params) => { const myTurn = state.currentPlayerIndex === ownerId; const inActPhase = state.phase === "出牌"; const free = !hasBlockingPending(state); const self = state.players[ownerId]; const selfAlive = self.alive === true; const normalized = normalizeAllocation(params, ownerId); if (!normalized) return "需要指定牌和目标"; const hasCards = normalized.reduce((n, t) => n + t.cardIds.length, 0) > 0; const allCardIds = []; let noDuplicates = true; let allInHand = true; if (hasCards) for (const t of normalized) { if (!Array.isArray(t.cardIds)) { allInHand = false; continue; } for (const cardId of t.cardIds) { if (allCardIds.includes(cardId)) noDuplicates = false; allCardIds.push(cardId); if (!self.hand.includes(cardId)) allInHand = false; } } const targetsLegal = hasCards && normalized.every((t) => t.target !== ownerId && state.players[t.target]?.alive === true); const singleTarget = new Set(normalized.map((t) => t.target)).size === 1; const notGivenBefore = !(Array.isArray(self.vars["仁德/givenTargets"]) ? self.vars["仁德/givenTargets"] : []).includes(normalized[0].target); return myTurn && inActPhase && free && selfAlive && hasCards && noDuplicates && allInHand && targetsLegal && singleTarget && notGivenBefore ? null : "现在不能使用界仁德"; }, async (state, params) => { const from = ownerId; const self = state.players[from]; await pushFrame(state, "仁德", from, { ...params }); const targets = normalizeAllocation(params, from); const afterCount = (typeof self.vars["仁德/givenCount"] === "number" ? self.vars["仁德/givenCount"] : 0) + targets.reduce((n, t) => n + t.cardIds.length, 0); const shouldOfferBasic = afterCount >= 2 && !self.vars["仁德/basicUsed"]; for (const t of targets) for (const cardId of t.cardIds) await applyAtom(state, { type: "移动牌", cardId, from: { zone: "手牌", player: from }, to: { zone: "手牌", player: t.target } }); self.vars["仁德/givenCount"] = afterCount; const givenTargets = Array.isArray(self.vars["仁德/givenTargets"]) ? self.vars["仁德/givenTargets"] : []; for (const t of targets) if (!givenTargets.includes(t.target)) givenTargets.push(t.target); self.vars["仁德/givenTargets"] = givenTargets; if (shouldOfferBasic) { self.vars["仁德/basicUsed"] = true; delete state.localVars[BASIC_CHOICE_VAR]; await applyAtom(state, { type: "请求回应", requestType: CHOICE_RT$2, target: from, prompt: { type: "chooseOption", title: "界仁德:视为使用一张基本牌?", options: [ { value: "杀", label: "杀" }, { value: "桃", label: "桃" }, { value: "酒", label: "酒" }, { value: "不使用", label: "不使用" } ] }, defaultChoice: "不使用", timeout: 30 }); const choice = state.localVars[BASIC_CHOICE_VAR]; delete state.localVars[BASIC_CHOICE_VAR]; if (choice === "杀") if (!canSlash(state, from)) {} else { delete state.localVars[BASIC_TARGET_VAR]; await applyAtom(state, { type: "请求回应", requestType: TARGET_RT$19, target: from, prompt: { type: "choosePlayer", title: "界仁德:选择【杀】的目标(攻击范围内一名其他角色)", min: 1, max: 1, filter: (_view, t) => t !== from && state.players[t]?.alive === true && inAttackRange(state, from, t) }, timeout: 30 }); const slashTarget = state.localVars[BASIC_TARGET_VAR]; delete state.localVars[BASIC_TARGET_VAR]; if (typeof slashTarget === "number" && state.players[slashTarget]?.alive) await virtualKill$5(state, from, slashTarget); } else if (choice === "桃") { delete state.localVars[BASIC_TARGET_VAR]; await applyAtom(state, { type: "请求回应", requestType: TARGET_RT$19, target: from, prompt: { type: "choosePlayer", title: "界仁德:选择【桃】的目标(一名已受伤角色,可对自己)", min: 1, max: 1, filter: (_view, t) => { const tp = state.players[t]; return !!tp?.alive && tp.health < tp.maxHealth; } }, timeout: 30 }); const peachTarget = state.localVars[BASIC_TARGET_VAR]; delete state.localVars[BASIC_TARGET_VAR]; if (typeof peachTarget === "number" && state.players[peachTarget]?.alive) await virtualPeach(state, from, peachTarget); } else if (choice === "酒") if (usedThisTurn$3(state, from, "酒") && !state.players[from]?.tags.includes("酒/无次数限制")) {} else await virtualWine$1(state, from); } await popFrame(state); }); registerAction(state, skill.id, ownerId, "respond", (s) => { const slot = s.pendingSlots.get(ownerId); if (slot?.atom.type !== "请求回应") return "当前不需要回应"; const rt = slot.atom.requestType ?? ""; if (rt !== CHOICE_RT$2 && rt !== TARGET_RT$19) return "当前不是仁德询问"; return null; }, async (s, params) => { const rt = (s.pendingSlots.get(ownerId)?.atom)?.requestType ?? ""; if (rt === CHOICE_RT$2) { const c = params.option; if (c === "杀" || c === "桃" || c === "酒") s.localVars[BASIC_CHOICE_VAR] = c; else s.localVars[BASIC_CHOICE_VAR] = "不使用"; } else if (rt === TARGET_RT$19) { const t = params.targets?.[0] ?? (typeof params.target === "number" ? params.target : void 0); if (typeof t === "number") s.localVars[BASIC_TARGET_VAR] = t; } }); return () => {}; } function onMount$185(skill, api) { api.defineAction("use", { label: "界仁德", style: "primary", prompt: { type: "distribute", mode: "allocate", title: "界仁德:选择要送出的手牌和目标角色(每名角色每回合限一次)", source: "hand", minPerTarget: 1, maxPerTarget: 99, minTotal: 1, maxTotal: 99, allowSelf: false } }); api.defineAction("respond", { label: "界仁德", style: "default", prompt: { type: "confirm", title: "界仁德" } }); return () => {}; } var BASIC_CHOICE_VAR, BASIC_TARGET_VAR, CHOICE_RT$2, TARGET_RT$19, _skillModule$99; var init_界仁德 = __esmMin((() => { init_apply(); init_frame(); init_skill(); init_distance(); init_slash_quota(); init_once_per_turn(); init_use_card(); BASIC_CHOICE_VAR = "仁德/basicChoice"; BASIC_TARGET_VAR = "仁德/basicTarget"; CHOICE_RT$2 = "界仁德/basicChoice"; TARGET_RT$19 = "界仁德/basicTarget"; _skillModule$99 = { createSkill: createSkill$200, onInit: onInit$200, onMount: onMount$185 }; })); //#endregion //#region src/engine/skills/界仁心.ts var 界仁心_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$199, default: () => _skillModule$98, onInit: () => onInit$199, onMount: () => onMount$184 }); /** 装备牌判定 */ function isEquipmentCard$1(card) { return !!card && card.type === "装备牌"; } /** 列出 owner 所有可弃置的装备牌 cardId(手牌 + 装备区) */ function listEquipmentChoices(state, ownerId) { const result = []; const player = state.players[ownerId]; if (!player) return result; for (const cid of player.hand) if (isEquipmentCard$1(state.cardMap[cid])) result.push(cid); for (const slot of [ "武器", "防具", "进攻马", "防御马", "宝物" ]) { const cid = player.equipment[slot]; if (cid && isEquipmentCard$1(state.cardMap[cid])) result.push(cid); } return result; } function createSkill$199(id, ownerId) { return { id, ownerId, name: DISPLAY_NAME$72, description: "其他角色进入濒死时,可弃置一张装备牌并翻面,令其回复至1点体力" }; } function onInit$199(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "respond", (st, params) => { const slot = st.pendingSlots.get(ownerId); if (!slot) return "当前不需要回应"; const atom = slot.atom; if (atom["type"] !== "请求回应") return "当前不需要回应"; const rt = atom["requestType"]; if (rt !== "界仁心/confirm" && rt !== "界仁心/selectCard") return "当前不是仁心询问"; if (rt === "界仁心/selectCard") { const cardId = params.cardId; if (typeof cardId !== "string") return "请选择一张装备牌"; if (!listEquipmentChoices(st, ownerId).includes(cardId)) return "该牌不是你可弃置的装备牌"; } return null; }, async (st, params) => { const rt = (st.pendingSlots.get(ownerId)?.atom)?.requestType; if (rt === "界仁心/confirm") st.localVars[CONFIRMED_KEY$38] = params.choice === true || params.confirmed === true; else if (rt === "界仁心/selectCard") { const cid = params.cardId; if (typeof cid === "string") st.localVars[CARD_CHOICE_KEY] = cid; } }); registerAfterHook(state, skill.id, ownerId, "陷入濒死", async (ctx) => { const atom = ctx.atom; if (typeof atom.target !== "number") return; const target = atom.target; if (target === ownerId) return; const targetPlayer = ctx.state.players[target]; if (!targetPlayer?.alive) return; if (targetPlayer.health > 0) return; if (!ctx.state.players[ownerId]?.alive) return; if (listEquipmentChoices(ctx.state, ownerId).length === 0) return; delete ctx.state.localVars[CONFIRMED_KEY$38]; await applyAtom(ctx.state, { type: "请求回应", requestType: "界仁心/confirm", target: ownerId, prompt: { type: "confirm", title: `仁心:是否弃置一张装备牌并翻面,令 ${targetPlayer.name} 回复至1点体力?`, confirmLabel: "发动", cancelLabel: "不发动" }, defaultChoice: false, timeout: 15 }); const confirmed = ctx.state.localVars[CONFIRMED_KEY$38] === true; delete ctx.state.localVars[CONFIRMED_KEY$38]; if (!confirmed) return; if (ctx.state.players[target]?.health > 0) return; const choicesAfter = listEquipmentChoices(ctx.state, ownerId); if (choicesAfter.length === 0) return; let chosenId; if (choicesAfter.length === 1) chosenId = choicesAfter[0]; else { delete ctx.state.localVars[CARD_CHOICE_KEY]; await applyAtom(ctx.state, { type: "请求回应", requestType: "界仁心/selectCard", target: ownerId, prompt: { type: "pickProcessingCard", title: "仁心:选择一张装备牌弃置", cards: choicesAfter.map((cid) => { const c = ctx.state.cardMap[cid]; return { cardId: cid, cardName: c?.name ?? cid, suit: c?.suit ?? "", rank: c?.rank ?? "" }; }) }, defaultChoice: choicesAfter[0], timeout: 20 }); chosenId = ctx.state.localVars[CARD_CHOICE_KEY]; delete ctx.state.localVars[CARD_CHOICE_KEY]; if (typeof chosenId !== "string") chosenId = choicesAfter[0]; if (!listEquipmentChoices(ctx.state, ownerId).includes(chosenId)) return; } await applyAtom(ctx.state, { type: "弃置", player: ownerId, cardIds: [chosenId], voluntary: true }); await flipFaceDown(ctx.state, ownerId, "仁心"); const cur = ctx.state.players[target]?.health ?? 0; const amount = Math.max(1, 1 - cur); await applyAtom(ctx.state, { type: "回复体力", target, amount }); }); registerBeforeHook(state, skill.id, ownerId, "阶段开始", async (ctx) => { const atom = ctx.atom; if (atom.type !== "阶段开始") return; if (atom.player !== ownerId) return; const self = ctx.state.players[ownerId]; if (atom.phase === "准备" && self?.tags.includes(FLIP_TAG$4)) { await flipFaceUp(ctx.state, ownerId, "仁心"); ctx.state.localVars[SKIP_FLAG$6] = ownerId; return { kind: "cancel" }; } if (ctx.state.localVars[SKIP_FLAG$6] === ownerId) return { kind: "cancel" }; }); registerBeforeHook(state, skill.id, ownerId, "阶段结束", async (ctx) => { const atom = ctx.atom; if (atom.type !== "阶段结束") return; if (atom.player !== ownerId) return; if (ctx.state.localVars[SKIP_FLAG$6] !== ownerId) return; delete ctx.state.localVars[SKIP_FLAG$6]; await performSkipTurn(ctx.state, ownerId); return { kind: "cancel" }; }); return () => {}; } function onMount$184(_skill, api) { api.defineAction("respond", { label: DISPLAY_NAME$72, style: "primary", prompt: { type: "confirm", title: "是否发动仁心?", confirmLabel: "发动", cancelLabel: "不发动" } }); } var DISPLAY_NAME$72, CONFIRMED_KEY$38, CARD_CHOICE_KEY, FLIP_TAG$4, SKIP_FLAG$6, _skillModule$98; var init_界仁心 = __esmMin((() => { init_apply(); init_face_down(); init_skill(); DISPLAY_NAME$72 = "仁心"; CONFIRMED_KEY$38 = "仁心/confirmed"; CARD_CHOICE_KEY = "仁心/cardChoice"; FLIP_TAG$4 = "仁心/翻面"; SKIP_FLAG$6 = "仁心/skipAll"; _skillModule$98 = { createSkill: createSkill$199, onInit: onInit$199, onMount: onMount$184 }; })); //#endregion //#region src/engine/skills/界伏枥.ts var 界伏枥_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$198, default: () => 界伏枥_default, onInit: () => onInit$198, onMount: () => onMount$183 }); function createSkill$198(id, ownerId) { return { id, ownerId, name: DISPLAY_NAME$71, description: "限定技:濒死时,将体力回复至X点且手牌摸至X张(X为全场势力数);然后若X大于你造成的伤害数,你翻面" }; } /** 计算全场存活玩家的不同势力数(X)。 */ function countFactions$2(state) { const factions = /* @__PURE__ */ new Set(); for (const p of state.players) { if (!p.alive) continue; if (p.faction) factions.add(p.faction); } return factions.size; } function onInit$198(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "respond", (st, _params) => { const slot = st.pendingSlots.get(ownerId); if (!slot) return "当前不需要回应"; const atom = slot.atom; if (atom.type !== "请求回应") return "当前不需要回应"; if (atom.requestType !== CONFIRM_RT$48) return "当前不是伏枥询问"; return null; }, async (st, params) => { st.localVars[CONFIRMED_KEY$37] = params.choice === true || params.confirmed === true; }); registerAfterHook(state, skill.id, ownerId, "陷入濒死", async (ctx) => { if (ctx.atom.target !== ownerId) return; if (ctx.state.players[ownerId]?.vars[USED_KEY$12]) return; const self = ctx.state.players[ownerId]; if (!self?.alive) return; if (self.health > 0) return; delete ctx.state.localVars[CONFIRMED_KEY$37]; await applyAtom(ctx.state, { type: "请求回应", requestType: CONFIRM_RT$48, target: ownerId, prompt: { type: "confirm", title: "是否发动伏枥?(限定技:回复至X体力并摸至X张手牌;X=全场势力数;若X>你造成的伤害数则翻面)", confirmLabel: "发动", cancelLabel: "不发动" }, defaultChoice: false, timeout: 15 }); if (!ctx.state.localVars[CONFIRMED_KEY$37]) return; ctx.state.players[ownerId].vars[USED_KEY$12] = true; const x = countFactions$2(ctx.state); const curHealth = ctx.state.players[ownerId].health; const healAmount = Math.max(0, x - curHealth); if (healAmount > 0) await applyAtom(ctx.state, { type: "回复体力", target: ownerId, amount: healAmount }); const curHand = ctx.state.players[ownerId].hand.length; const drawCount = Math.max(0, x - curHand); if (drawCount > 0) await applyAtom(ctx.state, { type: "摸牌", player: ownerId, count: drawCount }); if (x > (ctx.state.players[ownerId].vars[DMG_DEALT_KEY] ?? 0)) { if (!isFaceDown(ctx.state, ownerId)) await flipFaceDown(ctx.state, ownerId, "伏枥"); } }); registerAfterHook(state, skill.id, ownerId, "造成伤害后", async (ctx) => { const atom = ctx.atom; if (atom.source !== ownerId) return; if ((atom.amount ?? 0) <= 0) return; const cur = ctx.state.players[ownerId].vars[DMG_DEALT_KEY] ?? 0; ctx.state.players[ownerId].vars[DMG_DEALT_KEY] = cur + (atom.amount ?? 0); }); registerBeforeHook(state, skill.id, ownerId, "阶段开始", async (ctx) => { const atom = ctx.atom; if (atom.type !== "阶段开始") return; if (atom.player !== ownerId) return; const self = ctx.state.players[ownerId]; if (!self) return; if (atom.phase === "准备" && self.tags.includes(FLIP_TAG$3)) { await flipFaceUp(ctx.state, ownerId, "伏枥"); ctx.state.localVars[SKIP_FLAG$5] = ownerId; return { kind: "cancel" }; } if (ctx.state.localVars[SKIP_FLAG$5] === ownerId) return { kind: "cancel" }; }); registerBeforeHook(state, skill.id, ownerId, "阶段结束", async (ctx) => { const atom = ctx.atom; if (atom.type !== "阶段结束") return; if (atom.player !== ownerId) return; if (ctx.state.localVars[SKIP_FLAG$5] !== ownerId) return; delete ctx.state.localVars[SKIP_FLAG$5]; await performSkipTurn(ctx.state, ownerId); return { kind: "cancel" }; }); return () => {}; } function onMount$183(_skill, api) { api.defineAction("respond", { label: DISPLAY_NAME$71, style: "primary", prompt: { type: "confirm", title: "是否发动伏枥?", confirmLabel: "发动", cancelLabel: "不发动" } }); } var DISPLAY_NAME$71, USED_KEY$12, DMG_DEALT_KEY, CONFIRMED_KEY$37, CONFIRM_RT$48, FLIP_TAG$3, SKIP_FLAG$5, 界伏枥_default; var init_界伏枥 = __esmMin((() => { init_apply(); init_face_down(); init_skill(); DISPLAY_NAME$71 = "伏枥"; USED_KEY$12 = "伏枥/used"; DMG_DEALT_KEY = "伏枥/damageDealt"; CONFIRMED_KEY$37 = "伏枥/confirmed"; CONFIRM_RT$48 = "界伏枥/confirm"; FLIP_TAG$3 = "伏枥/翻面"; SKIP_FLAG$5 = "伏枥/skipAll"; 界伏枥_default = { createSkill: createSkill$198, onInit: onInit$198, onMount: onMount$183 }; })); //#endregion //#region src/engine/skills/界伤逝.ts var 界伤逝_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$197, default: () => _skillModule$97, onInit: () => onInit$197, onMount: () => onMount$182 }); /** 已损失体力值 X = max(0, maxHealth - health)。 */ function lostHealth$2(state, ownerId) { const p = state.players[ownerId]; if (!p) return 0; return Math.max(0, p.maxHealth - getHealthValue(p)); } /** 检查并可能触发伤逝:手牌 < X 时询问,确认则摸至 X 张。 */ async function checkTrigger(state, ownerId) { const p = state.players[ownerId]; if (!p?.alive) return; const X = lostHealth$2(state, ownerId); const hand = p.hand.length; if (X <= 0) return; if (hand >= X) return; delete state.localVars[CONFIRM_KEY$20]; await applyAtom(state, { type: "请求回应", requestType: CONFIRM_RT$47, target: ownerId, prompt: { type: "confirm", title: `伤逝:是否将手牌摸至 ${X} 张?(当前 ${hand} 张,已损失 ${X} 体力)`, confirmLabel: "摸牌", cancelLabel: "不发动" }, defaultChoice: false, timeout: 10 }); if (state.localVars[CONFIRM_KEY$20] !== true) return; let count = X - hand; const available = state.zones.deck.length + state.zones.discardPile.length; if (count > available) count = available; if (count > 0) await applyAtom(state, { type: "摸牌", player: ownerId, count }); } function createSkill$197(id, ownerId) { return { id, ownerId, name: "伤逝", description: "当你的手牌数小于X后,你可以将手牌摸至X张(X为你已损失体力值)" }; } function onInit$197(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "respond", (st, _params) => { const slot = st.pendingSlots.get(ownerId); if (!slot) return "当前不需要回应"; const atom = slot.atom; if (atom.type !== "请求回应") return "当前不需要回应"; if (atom.requestType !== CONFIRM_RT$47) return "当前不是伤逝确认"; return null; }, async (st, params) => { st.localVars[CONFIRM_KEY$20] = params.choice === true || params.confirmed === true; }); registerAfterHook(state, skill.id, ownerId, "扣减体力后", async (ctx) => { const atom = ctx.atom; if (atom.target !== ownerId) return; if ((atom.amount ?? 0) <= 0) return; await checkTrigger(ctx.state, ownerId); }); registerAfterHook(state, skill.id, ownerId, "失去体力", async (ctx) => { const atom = ctx.atom; if (atom.target !== ownerId) return; if ((atom.amount ?? 0) <= 0) return; await checkTrigger(ctx.state, ownerId); }); registerAfterHook(state, skill.id, ownerId, "弃置", async (ctx) => { const atom = ctx.atom; if (atom.player !== ownerId) return; if ((atom.cardIds ?? []).length === 0) return; await checkTrigger(ctx.state, ownerId); }); registerAfterHook(state, skill.id, ownerId, "获得", async (ctx) => { if (ctx.atom.from !== ownerId) return; await checkTrigger(ctx.state, ownerId); }); registerAfterHook(state, skill.id, ownerId, "给予", async (ctx) => { const atom = ctx.atom; if (atom.from !== ownerId) return; if (atom.to === ownerId) return; await checkTrigger(ctx.state, ownerId); }); registerAfterHook(state, skill.id, ownerId, "移出至暂存区", async (ctx) => { if (ctx.atom.target !== ownerId) return; await checkTrigger(ctx.state, ownerId); }); registerAfterHook(state, skill.id, ownerId, "拼点扣置", async (ctx) => { const atom = ctx.atom; if (atom.initiator !== ownerId && atom.target !== ownerId) return; await checkTrigger(ctx.state, ownerId); }); registerAfterHook(state, skill.id, ownerId, "装备", async (ctx) => { if (ctx.atom.player !== ownerId) return; await checkTrigger(ctx.state, ownerId); }); registerAfterHook(state, skill.id, ownerId, "移动牌", async (ctx) => { const atom = ctx.atom; if (atom.from?.zone !== "手牌") return; if (atom.from?.player !== ownerId) return; if (atom.to?.zone === "手牌" && atom.to?.player === ownerId) return; if (atom.to?.zone === "处理区" && atom.cardId) { const card = ctx.state.cardMap[atom.cardId]; const effect = card && getCardEffect(card.name); if (effect && !effect.delayed) return; } await checkTrigger(ctx.state, ownerId); }); registerAfterHook(state, skill.id, ownerId, "使用结算结束后", async (ctx) => { if (ctx.atom.source !== ownerId) return; await checkTrigger(ctx.state, ownerId); }); return () => {}; } function onMount$182(_skill, api) { api.defineAction("respond", { label: "伤逝", style: "default", prompt: { type: "confirm", title: "是否发动伤逝?", confirmLabel: "摸牌", cancelLabel: "不发动" } }); } var CONFIRM_RT$47, CONFIRM_KEY$20, _skillModule$97; var init_界伤逝 = __esmMin((() => { init_types(); init_apply(); init_cards(); init_skill(); CONFIRM_RT$47 = "界伤逝/confirm"; CONFIRM_KEY$20 = "伤逝/confirmed"; _skillModule$97 = { createSkill: createSkill$197, onInit: onInit$197, onMount: onMount$182 }; })); //#endregion //#region src/engine/skills/界克己.ts var 界克己_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$196, default: () => 界克己_default, onInit: () => onInit$196, onMount: () => onMount$181 }); function createSkill$196(id, ownerId) { return { id, ownerId, name: "界克己", description: "若你未于本回合出牌阶段使用或打出过【杀】,你可以跳过弃牌阶段" }; } function onInit$196(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "respond", (state, _params) => { const slot = state.pendingSlots.get(ownerId); if (slot?.atom.type !== "请求回应") return "当前不需要回应"; if (slot.atom.requestType !== CONFIRM_REQUEST$12) return "当前不是界克己确认"; return null; }, async (state, params) => { state.localVars[CONFIRMED_VAR$6] = params.choice === true || params.confirmed === true; }); registerAfterHook(state, skill.id, ownerId, "移动牌", async (ctx) => { const atom = ctx.atom; if (atom.from.zone !== "手牌" || atom.from.player !== ownerId) return; if (atom.to.zone !== "处理区") return; if (ctx.state.phase !== "出牌") return; if (ctx.state.cardMap[atom.cardId]?.name === "杀") ctx.state.turn.vars[PLAYED_SLASH_VAR] = true; }); registerBeforeHook(state, skill.id, ownerId, "阶段开始", async (ctx) => { const atom = ctx.atom; if (atom.type !== "阶段开始") return; if (atom.player !== ownerId) return; if (atom.phase !== "弃牌") return; if (ctx.state.turn.vars[PLAYED_SLASH_VAR]) return; delete ctx.state.localVars[CONFIRMED_VAR$6]; await applyAtom(ctx.state, { type: "请求回应", requestType: CONFIRM_REQUEST$12, target: ownerId, prompt: { type: "confirm", title: "界克己:本回合出牌阶段未出杀,是否跳过弃牌阶段?", confirmLabel: "跳过", cancelLabel: "不跳过" }, defaultChoice: false, timeout: 15 }); if (!ctx.state.localVars[CONFIRMED_VAR$6]) return; await applyAtom(ctx.state, { type: "阶段结束", player: ownerId, phase: "弃牌" }); return { kind: "cancel" }; }); return () => {}; } function onMount$181(_skill, api) { api.defineAction("respond", { label: "界克己", style: "primary", prompt: { type: "confirm", title: "界克己:是否跳过弃牌阶段?", confirmLabel: "跳过", cancelLabel: "不跳过" } }); } var PLAYED_SLASH_VAR, CONFIRMED_VAR$6, CONFIRM_REQUEST$12, 界克己_default; var init_界克己 = __esmMin((() => { init_apply(); init_skill(); PLAYED_SLASH_VAR = "界克己/playedSlash"; CONFIRMED_VAR$6 = "界克己/confirmed"; CONFIRM_REQUEST$12 = "界克己/confirm"; 界克己_default = { createSkill: createSkill$196, onInit: onInit$196, onMount: onMount$181 }; })); //#endregion //#region src/engine/skills/界再起.ts var 界再起_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$195, default: () => _skillModule$96, onInit: () => onInit$195, onMount: () => onMount$180 }); function createSkill$195(id, ownerId) { return { id, ownerId, name: "界再起", description: "结束阶段,你可以令至多X名角色各选一项(X=本回合置入弃牌堆的红色牌数):1.摸一张牌;2.令你回复1点体力" }; } function onInit$195(skill, state) { const ownerId = skill.ownerId; const unloadTurnStartHook = registerAfterHook(state, skill.id, ownerId, "回合开始", async (ctx) => { const atom = ctx.atom; if (atom.type !== "回合开始") return; if (atom.player !== ownerId) return; ctx.state.turn.vars[BASE_VAR] = ctx.state.zones.discardPile.length; }); const unloaders = []; for (const p of state.players) { const seatId = p.index; const u = registerAction(state, skill.id, seatId, "respond", (st, params) => { const slot = st.pendingSlots.get(seatId); if (!slot) return "当前不需要回应"; const atom = slot.atom; if (atom["type"] !== "请求回应") return "当前不需要回应"; const rt = atom["requestType"]; if (rt === TRIGGER_RT$7 || rt === CHOOSE_TARGETS_RT) { if (seatId !== ownerId) return "当前不是你的询问"; if (rt === CHOOSE_TARGETS_RT) { const x = st.localVars[X_VAR]; const targets = params.targets; if (!Array.isArray(targets)) return "需选择目标(可为空)"; const maxN = typeof x === "number" ? x : 0; if (targets.length > maxN) return `至多选择 ${maxN} 名角色`; for (const t of targets) { if (typeof t !== "number") return "目标不合法"; if (!st.players[t]?.alive) return "目标不合法"; } } return null; } if (typeof rt === "string" && rt === OPTION_RT_PREFIX + seatId) return null; return "当前不是界再起询问"; }, async (st, params) => { const rt = (st.pendingSlots.get(seatId)?.atom)?.requestType; if (rt === TRIGGER_RT$7) st.localVars[TRIGGER_CONFIRMED_KEY] = params.choice === true || params.confirmed === true; else if (rt === CHOOSE_TARGETS_RT) { const targets = params.targets; st.localVars[TARGETS_KEY$5] = Array.isArray(targets) ? targets.filter((t) => typeof t === "number") : []; } else if (rt === OPTION_RT_PREFIX + seatId) { const option = params.choice === true || params.confirmed === true ? 1 : 2; const results = st.localVars[OPTION_RESULTS_KEY] ?? {}; results[String(seatId)] = option; st.localVars[OPTION_RESULTS_KEY] = results; } }); unloaders.push(u); } const unloadEndHook = registerAfterHook(state, skill.id, ownerId, "阶段开始", async (ctx) => { const atom = ctx.atom; if (atom.type !== "阶段开始") return; if (atom.player !== ownerId) return; if (atom.phase !== "回合结束") return; const st = ctx.state; if (!st.players[ownerId]?.alive) return; const base = st.turn.vars[BASE_VAR]; if (typeof base !== "number") return; if (st.zones.discardPile.length < base) return; const x = st.zones.discardPile.slice(base).filter((id) => st.cardMap[id]?.color === "红").length; if (x <= 0) return; const aliveCount = st.players.filter((p) => p.alive).length; if (aliveCount === 0) return; const maxTargets = Math.min(x, aliveCount); delete st.localVars[TRIGGER_CONFIRMED_KEY]; await applyAtom(st, { type: "请求回应", requestType: TRIGGER_RT$7, target: ownerId, prompt: { type: "confirm", title: `界再起:本回合置入弃牌堆 ${x} 张红色牌。是否令至多 ${maxTargets} 名角色各选一项?`, description: "选项:1.摸一张牌;2.令孟获回复1点体力", confirmLabel: "发动", cancelLabel: "不发动" }, defaultChoice: false, timeout: 20 }); const confirmed = st.localVars[TRIGGER_CONFIRMED_KEY] === true; delete st.localVars[TRIGGER_CONFIRMED_KEY]; if (!confirmed) return; st.localVars[X_VAR] = maxTargets; delete st.localVars[TARGETS_KEY$5]; await applyAtom(st, { type: "请求回应", requestType: CHOOSE_TARGETS_RT, target: ownerId, prompt: { type: "choosePlayer", title: `界再起:选择至多 ${maxTargets} 名角色(各选一项:摸一张牌 或 令你回复1点体力)`, min: 0, max: maxTargets, filter: (_view, t) => st.players[t]?.alive === true }, timeout: 30 }); delete st.localVars[X_VAR]; const rawTargets = st.localVars[TARGETS_KEY$5]; delete st.localVars[TARGETS_KEY$5]; const targets = Array.isArray(rawTargets) ? rawTargets.filter((t, i, arr) => st.players[t]?.alive && arr.indexOf(t) === i).slice(0, maxTargets) : []; if (targets.length === 0) return; for (const t of targets) { if (!st.players[t]?.alive) continue; delete st.localVars[OPTION_RESULTS_KEY]; await applyAtom(st, { type: "请求回应", requestType: OPTION_RT_PREFIX + t, target: t, prompt: { type: "confirm", title: "界再起(孟获):选择一项——1.摸一张牌;2.令孟获回复1点体力", confirmLabel: "1·摸一张牌", cancelLabel: "2·令孟获回复1点体力" }, defaultChoice: true, timeout: 20 }); if ((st.localVars[OPTION_RESULTS_KEY]?.[String(t)] === 2 ? 2 : 1) === 1) await applyAtom(st, { type: "摸牌", player: t, count: 1 }); else { const mh = st.players[ownerId]; if (mh?.alive && mh.health < mh.maxHealth) await applyAtom(st, { type: "回复体力", target: ownerId, amount: 1 }); } } delete st.localVars[OPTION_RESULTS_KEY]; }); return () => { unloadTurnStartHook(); unloaders.forEach((u) => u()); unloadEndHook(); }; } function onMount$180(_skill, _api) {} var BASE_VAR, X_VAR, TRIGGER_RT$7, CHOOSE_TARGETS_RT, OPTION_RT_PREFIX, TRIGGER_CONFIRMED_KEY, TARGETS_KEY$5, OPTION_RESULTS_KEY, _skillModule$96; var init_界再起 = __esmMin((() => { init_apply(); init_skill(); BASE_VAR = "界再起/base"; X_VAR = "界再起/x"; TRIGGER_RT$7 = "界再起/trigger"; CHOOSE_TARGETS_RT = "界再起/chooseTargets"; OPTION_RT_PREFIX = "界再起/option/"; TRIGGER_CONFIRMED_KEY = "界再起/confirmed"; TARGETS_KEY$5 = "界再起/targets"; OPTION_RESULTS_KEY = "界再起/optionResults"; _skillModule$96 = { createSkill: createSkill$195, onInit: onInit$195, onMount: onMount$180 }; })); //#endregion //#region src/engine/skills/界凿险.ts var 界凿险_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$194, default: () => 界凿险_default, onInit: () => onInit$194, onMount: () => onMount$179 }); /** 复刻「回合结束」atom 的 per-turn 清理(cancel 回合结束后 atom.apply 不执行,需手动清理)。 * 与 回合结束.ts apply 保持一致:清空 turn.vars、清所有玩家 duration='turn' 标记、 * 清 /usedThisTurn|/healed|/givenCount|/givenTargets|/basicUsed vars。 */ function clearPerTurnState$1(state) { state.turn.vars = {}; for (const p of state.players) { p.marks = p.marks.filter((m) => m.duration !== "turn"); p.vars = Object.fromEntries(Object.entries(p.vars).filter(([k]) => !k.endsWith("/usedThisTurn") && !k.endsWith("/healed") && !k.endsWith("/givenCount") && !k.endsWith("/givenTargets") && !k.endsWith("/basicUsed"))); } } function createSkill$194(id, ownerId) { return { id, ownerId, name: "界凿险", description: "觉醒技:准备阶段田≥3时,减1体力上限、获得\"急袭\",且本回合结束后进行一个额外回合" }; } function onInit$194(skill, state) { const ownerId = skill.ownerId; registerAfterHook(state, skill.id, ownerId, "阶段开始", async (ctx) => { const atom = ctx.atom; if (atom.type !== "阶段开始") return; if (atom.phase !== "准备") return; if (atom.player !== ownerId) return; if (ctx.state.players[ownerId]?.vars[AWAKENED_KEY$6]) return; const self = ctx.state.players[ownerId]; if (!self?.alive) return; if (self.marks.filter((m) => m.id.startsWith(TIAN_PREFIX$1)).length < 3) return; ctx.state.players[ownerId].vars[AWAKENED_KEY$6] = true; const newMax = self.maxHealth - 1; await applyAtom(ctx.state, { type: "设上限", player: ownerId, amount: newMax }); await applyAtom(ctx.state, { type: "添加技能", player: ownerId, skillId: "急袭" }); ctx.state.localVars[EXTRA_TURN_KEY] = true; }); registerBeforeHook(state, skill.id, ownerId, "回合结束", async (ctx) => { const atom = ctx.atom; if (atom.type !== "回合结束") return; if (atom.player !== ownerId) return; const st = ctx.state; if (st.localVars[EXTRA_TURN_KEY] !== true) return; delete st.localVars[EXTRA_TURN_KEY]; if (!st.players[ownerId]?.alive) return; clearPerTurnState$1(st); st.currentPlayerIndex = ownerId; await startTurn(st, ownerId); return { kind: "cancel" }; }); return () => {}; } function onMount$179(_skill, _api) {} var AWAKENED_KEY$6, EXTRA_TURN_KEY, TIAN_PREFIX$1, 界凿险_default; var init_界凿险 = __esmMin((() => { init_apply(); init_turn(); init_skill(); AWAKENED_KEY$6 = "凿险/awakened"; EXTRA_TURN_KEY = "凿险/extraTurn"; TIAN_PREFIX$1 = "屯田/田:"; 界凿险_default = { createSkill: createSkill$194, onInit: onInit$194, onMount: onMount$179 }; })); //#endregion //#region src/engine/skills/界刚烈.ts var 界刚烈_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$193, default: () => 界刚烈_default, onInit: () => onInit$193, onMount: () => onMount$178 }); function createSkill$193(id, ownerId) { return { id, ownerId, name: "界刚烈", description: "受到伤害后判定,红色对其造成1点伤害,黑色弃置其一张牌" }; } function onInit$193(skill, state) { const ownerId = skill.ownerId; const u = registerAction(state, skill.id, ownerId, "respond", (st, params) => { const slot = st.pendingSlots.get(ownerId); if (!slot) return "当前不需要回应"; const atom = slot.atom; if (atom["type"] !== "请求回应") return "当前不需要回应"; const requestType = atom["requestType"]; if (requestType === CONFIRM_REQUEST$11) return null; if (requestType !== PICK_REQUEST$3) return "当前不是界刚烈回应"; const zone = params.zone; if (zone === "equipment") { if (typeof params.cardId !== "string") return "cardId required"; } else if (zone === "hand") { if (typeof params.handIndex !== "number") return "handIndex required"; } else if (zone === "judge") { if (typeof params.cardId !== "string") return "cardId required"; } else return "zone required (equipment|hand|judge)"; return null; }, async (st, params) => { if ((st.pendingSlots.get(ownerId)?.atom)?.["requestType"] === CONFIRM_REQUEST$11) { st.localVars["界刚烈/confirmed"] = params.choice === true; return; } st.localVars[PICK_RESULT_KEY$3] = { zone: params.zone, cardId: params.cardId ?? null, handIndex: params.handIndex ?? null }; }); registerAfterHook(state, skill.id, ownerId, "判定牌生效后", async (ctx) => { const atom = ctx.atom; if (atom.judgeType !== "界刚烈") return; if (atom.player !== ownerId) return; const processing = frameCards(ctx.state); if (processing.length === 0) return; const judgeCardId = processing[processing.length - 1]; const judgeCard = ctx.state.cardMap[judgeCardId]; if (!judgeCard) return; ctx.state.localVars[JUDGE_COLOR_KEY] = judgeCard.color; }); registerAfterHook(state, skill.id, ownerId, "受到伤害后", async (ctx) => { const atom = ctx.atom; if (atom.target !== ownerId) return; if ((atom.amount ?? 0) <= 0) return; if (atom.source === void 0 || atom.source === ownerId) return; const sourceIdx = atom.source; if (!ctx.state.players[sourceIdx]?.alive) return; if (ctx.state.zones.deck.length === 0) return; delete ctx.state.localVars["界刚烈/confirmed"]; await applyAtom(ctx.state, { type: "请求回应", requestType: CONFIRM_REQUEST$11, target: ownerId, prompt: { type: "confirm", title: "是否发动界刚烈?", confirmLabel: "发动", cancelLabel: "不发动" }, defaultChoice: false, timeout: 10 }); if (!ctx.state.localVars["界刚烈/confirmed"]) return; delete ctx.state.localVars[JUDGE_COLOR_KEY]; await runJudgeFlow(ctx.state, ownerId, "界刚烈"); const color = ctx.state.localVars[JUDGE_COLOR_KEY]; delete ctx.state.localVars[JUDGE_COLOR_KEY]; if (color === void 0) return; if (color === "红") { await runDamageFlow(ctx.state, ownerId, sourceIdx, 1); return; } const source = ctx.state.players[sourceIdx]; if (!source?.alive) return; if (!(source.hand.length > 0 || Object.keys(source.equipment).length > 0 || source.pendingTricks.length > 0)) return; await runPickTargetCardPanel(ctx.state, ownerId, sourceIdx, source, { mode: "discard", requestType: PICK_REQUEST$3, title: "界刚烈:选择弃置来源的一张牌" }); }); return () => { u(); }; } function onMount$178(_skill, api) { api.defineAction("respond", { label: "界刚烈", style: "danger", prompt: { type: "pickTargetCard", title: "界刚烈:选择弃置来源的一张牌", target: -1, equipment: [], judge: [], handCount: 0 } }); } var CONFIRM_REQUEST$11, PICK_REQUEST$3, JUDGE_COLOR_KEY, 界刚烈_default; var init_界刚烈 = __esmMin((() => { init_apply(); init_frame(); init_judge(); init_damage(); init_skill(); init_pick_card_panel(); init_vars_keys(); CONFIRM_REQUEST$11 = "界刚烈/confirm"; PICK_REQUEST$3 = "界刚烈/选牌"; JUDGE_COLOR_KEY = "界刚烈/judgeColor"; 界刚烈_default = { createSkill: createSkill$193, onInit: onInit$193, onMount: onMount$178 }; })); //#endregion //#region src/engine/skills/界利驭.ts var 界利驭_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$192, default: () => _skillModule$95, onInit: () => onInit$192, onMount: () => onMount$177 }); function createSkill$192(id, ownerId) { return { id, ownerId, name: DISPLAY_NAME$70, description: "当你使用【杀】对其他角色造成伤害后,你可以获得其区域里的一张牌;获得的牌不为装备牌,其摸一张牌;为装备牌,你视为对由其指定的另一名角色使用一张【决斗】" }; } function onInit$192(skill, state) { const ownerId = skill.ownerId; const offs = []; for (const player of state.players) { const pid = player.index; offs.push(registerAction(state, skill.id, pid, "respond", (s, params) => { const slot = s.pendingSlots.get(pid); if (!slot) return "当前不需要回应"; if (slot.atom.type !== "请求回应") return "当前不需要回应"; const requestType = slot.atom.requestType; if (requestType === CONFIRM_RT$46) return null; if (requestType === PICK_RT$10) { const zone = params.zone; if (zone === "equipment" || zone === "judge") { if (typeof params.cardId !== "string") return "cardId required"; } else if (zone === "hand") { if (typeof params.handIndex !== "number") return "handIndex required"; } else return "zone required (equipment|hand|judge)"; return null; } if (requestType === CHOOSE_DUEL_RT) { if (typeof params.target !== "number") return "target required"; return null; } return "当前不是利驭回应"; }, async (s, params) => { const requestType = (s.pendingSlots.get(pid)?.atom)?.requestType; if (requestType === CONFIRM_RT$46) s.localVars[CONFIRM_KEY$19] = params.choice === true || params.confirmed === true; else if (requestType === PICK_RT$10) s.localVars[PICK_RESULT_KEY$3] = { zone: params.zone, cardId: params.cardId ?? null, handIndex: params.handIndex ?? null }; else if (requestType === CHOOSE_DUEL_RT) s.localVars[DUEL_TARGET_KEY] = params.target; })); } offs.push(registerAfterHook(state, skill.id, ownerId, "造成伤害后", async (ctx) => { const atom = ctx.atom; if (atom.source !== ownerId) return; if (atom.target === void 0 || atom.target === ownerId) return; if ((atom.amount ?? 0) <= 0) return; if (typeof atom.cardId !== "string") return; if (ctx.state.cardMap[atom.cardId]?.name !== "杀") return; const target = atom.target; const targetPlayer = ctx.state.players[target]; if (!targetPlayer?.alive) return; if (!ctx.state.players[ownerId]?.alive) return; if (!(targetPlayer.hand.length > 0 || Object.keys(targetPlayer.equipment).length > 0 || targetPlayer.pendingTricks.length > 0)) return; delete ctx.state.localVars[CONFIRM_KEY$19]; await applyAtom(ctx.state, { type: "请求回应", requestType: CONFIRM_RT$46, target: ownerId, prompt: { type: "confirm", title: "是否发动利驭?", confirmLabel: "发动", cancelLabel: "不发动" }, defaultChoice: false, timeout: 10 }); if (!ctx.state.localVars[CONFIRM_KEY$19]) return; delete ctx.state.localVars[CONFIRM_KEY$19]; const targetBefore = ctx.state.players[target]; if (!targetBefore) return; await runPickTargetCardPanel(ctx.state, ownerId, target, targetBefore, { mode: "obtain", requestType: PICK_RT$10, title: "利驭:选择获得受伤目标区域里的一张牌", includeJudge: true }); const ownerPlayer = ctx.state.players[ownerId]; if (!ownerPlayer) return; const obtainedId = ownerPlayer.hand[ownerPlayer.hand.length - 1] ?? null; if (obtainedId === null) return; const obtainedCard = ctx.state.cardMap[obtainedId]; if (!obtainedCard) return; if (obtainedCard.type !== "装备牌") { await applyAtom(ctx.state, { type: "摸牌", player: target, count: 1 }); return; } const candidates = ctx.state.players.filter((p) => p.alive && p.index !== ownerId && p.index !== target).map((p) => p.index); if (candidates.length === 0) return; delete ctx.state.localVars[DUEL_TARGET_KEY]; await applyAtom(ctx.state, { type: "请求回应", requestType: CHOOSE_DUEL_RT, target, prompt: { type: "choosePlayer", title: "利驭:选择另一名角色,界吕布视为对其使用决斗", min: 1, max: 1, filter: (view, t) => t !== ownerId && t !== target && view.players[t]?.alive === true }, defaultChoice: candidates[0], timeout: 15 }); const duelTarget = ctx.state.localVars[DUEL_TARGET_KEY]; delete ctx.state.localVars[DUEL_TARGET_KEY]; if (typeof duelTarget !== "number") return; if (duelTarget === ownerId || duelTarget === target) return; if (!ctx.state.players[duelTarget]?.alive) return; await pushFrame(ctx.state, SKILL_ID$47, ownerId, { duelTarget, source: target }); try { if (ctx.state.players[ownerId]?.alive && ctx.state.players[duelTarget]?.alive) { const vId = `${SKILL_ID$47}:决斗:${ownerId}:${duelTarget}:${ctx.state.seq}`; ctx.state.cardMap[vId] = { id: vId, name: "决斗", suit: "", color: "无色", rank: "A", type: "锦囊牌" }; await runUseFlow(ctx.state, ownerId, vId, [duelTarget], "决斗", { virtual: true }); delete ctx.state.cardMap[vId]; } } finally { await popFrame(ctx.state); } })); return () => { for (const off of offs) off(); }; } function onMount$177(_skill, api) { api.defineAction("respond", { label: DISPLAY_NAME$70, style: "default", prompt: { type: "confirm", title: "是否发动利驭?", confirmLabel: "发动", cancelLabel: "不发动" } }); } var SKILL_ID$47, DISPLAY_NAME$70, CONFIRM_KEY$19, DUEL_TARGET_KEY, CONFIRM_RT$46, PICK_RT$10, CHOOSE_DUEL_RT, _skillModule$95; var init_界利驭 = __esmMin((() => { init_apply(); init_frame(); init_skill(); init_pick_card_panel(); init_use_card(); init_vars_keys(); SKILL_ID$47 = "界利驭"; DISPLAY_NAME$70 = "利驭"; CONFIRM_KEY$19 = `${SKILL_ID$47}/confirmed`; DUEL_TARGET_KEY = `${SKILL_ID$47}/duelTarget`; CONFIRM_RT$46 = `${SKILL_ID$47}/confirm`; PICK_RT$10 = `${SKILL_ID$47}/选牌`; CHOOSE_DUEL_RT = `${SKILL_ID$47}/chooseDuelTarget`; _skillModule$95 = { createSkill: createSkill$192, onInit: onInit$192, onMount: onMount$177 }; })); //#endregion //#region src/engine/skills/界制衡.ts var 界制衡_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$191, default: () => 界制衡_default, onInit: () => onInit$191, onMount: () => onMount$176 }); function createSkill$191(id, ownerId) { return { id, ownerId, name: "界制衡", description: "出牌阶段限一次:弃任意张牌(手牌/装备),摸等量张;若弃置了所有手牌,额外摸一张" }; } function onInit$191(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "use", (state, params) => { const cardIds = params.cardIds ?? (typeof params.cardId === "string" ? [params.cardId] : void 0); if (!Array.isArray(cardIds) || cardIds.length === 0) return "cardIds required (at least 1)"; if (usedThisTurn$3(state, ownerId, "界制衡")) return "本回合已使用过界制衡"; const self = state.players[ownerId]; if (!self) return "player not found"; for (const cardId of cardIds) { const inHand = self.hand.includes(cardId); const inEquip = Object.values(self.equipment).includes(cardId); if (!inHand && !inEquip) return "牌不在手牌或装备区中"; } return null; }, async (state, params) => { await markOncePerTurn(state, ownerId, "界制衡"); const from = ownerId; await pushFrame(state, "界制衡", from, { ...params }); const cardIds = params.cardIds ?? (typeof params.cardId === "string" ? [params.cardId] : []); const handBefore = state.players[from].hand; const discardedAllHand = handBefore.length > 0 && handBefore.every((id) => cardIds.includes(id)); await applyAtom(state, { type: "弃置", player: from, cardIds, voluntary: true }); await applyAtom(state, { type: "摸牌", player: from, count: cardIds.length + (discardedAllHand ? 1 : 0) }); await popFrame(state); }); return () => {}; } function onMount$176(skill, api) { api.defineAction("use", { label: "界制衡", style: "primary", prompt: { type: "distribute", mode: "select", title: "界制衡:选择要弃置的牌(手牌+装备均可;若弃置所有手牌,额外多摸一张)", source: "handAndEquip", minTotal: 1, maxTotal: 99 }, activeWhen: (ctx) => activeUnlessUsedThisTurn("界制衡")(ctx) }); return () => {}; } var 界制衡_default; var init_界制衡 = __esmMin((() => { init_apply(); init_frame(); init_once_per_turn(); init_skill(); 界制衡_default = { createSkill: createSkill$191, onInit: onInit$191, onMount: onMount$176 }; })); //#endregion //#region src/engine/skills/界制霸.ts var 界制霸_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$190, default: () => 界制霸_default, onInit: () => onInit$190, onMount: () => onMount$175 }); /** 拼点牌点数:A=1, 2-10=面值, J=11, Q=12, K=13 */ function rankValue$7(rank) { if (rank === "A") return 1; if (rank === "J") return 11; if (rank === "Q") return 12; if (rank === "K") return 13; const n = parseInt(rank, 10); return Number.isFinite(n) ? n : 0; } function createSkill$190(id, ownerId) { return { id, ownerId, name: DISPLAY_NAME$69, description: "主公技:你与其他吴势力角色可互相拼点;若发起方没赢,你可以获得两张拼点牌;觉醒后可拒绝盟友发起的拼点" }; } function onInit$190(skill, state) { const ownerId = skill.ownerId; const unloaders = []; for (const p of state.players) { const allyIdx = p.index; if (allyIdx === ownerId) continue; if (p.faction !== "吴") continue; const u = registerAction(state, skill.id, allyIdx, "use", (st, params) => { if (ownerId !== 0) return "制霸为主公技,孙策非主公"; const lord = st.players[ownerId]; if (!lord?.alive) return "主公不存在或已死亡"; if (lord.hand.length === 0) return "孙策没有手牌,无法拼点"; const self = st.players[allyIdx]; if (!self?.alive) return "已死亡"; const myTurn = st.currentPlayerIndex === allyIdx; const inActPhase = st.phase === "出牌"; const free = !hasBlockingPending(st); if (!myTurn || !inActPhase || !free) return "现在不能使用制霸"; if (usedThisTurn$3(st, allyIdx, ALLY_USED)) return "本回合已使用过制霸"; const cardId = params.cardId; if (typeof cardId !== "string") return "请选择一张拼点牌"; if (!self.hand.includes(cardId)) return "拼点牌不在手牌中"; return null; }, async (st, params) => { const allyCardId = params.cardId; await markOncePerTurn(st, allyIdx, ALLY_USED); const lord = st.players[ownerId]; if (getHunziAwakened(lord.vars)) { delete st.localVars[REFUSE_KEY]; await applyAtom(st, { type: "请求回应", requestType: REFUSE_RT, target: ownerId, prompt: { type: "confirm", title: `${st.players[allyIdx].name} 欲与你制霸拼点,是否接受?`, confirmLabel: "接受", cancelLabel: "拒绝" }, defaultChoice: true, timeout: 20 }); if (st.localVars[REFUSE_KEY] === false) return; } await pushFrame(st, SKILL_ID$46, allyIdx, { ...params }); try { await applyAtom(st, { type: "移动牌", cardId: allyCardId, from: { zone: "手牌", player: allyIdx }, to: { zone: "处理区" } }); delete st.localVars[RESP_CARD_KEY]; await applyAtom(st, { type: "请求回应", requestType: RESP_CARD_RT, target: ownerId, prompt: { type: "useCard", title: `制霸:与 ${st.players[allyIdx].name} 拼点,请出一张手牌`, cardFilter: { filter: () => true, min: 1, max: 1 } }, timeout: 30 }); const lordCardId = st.localVars[RESP_CARD_KEY]; if (!lordCardId || !st.players[ownerId].hand.includes(lordCardId)) { await applyAtom(st, { type: "移动牌", cardId: allyCardId, from: { zone: "处理区" }, to: { zone: "弃牌堆" } }); return; } await applyAtom(st, { type: "移动牌", cardId: lordCardId, from: { zone: "手牌", player: ownerId }, to: { zone: "处理区" } }); await resolveAfterCompare(st, ownerId, allyCardId, lordCardId, rankValue$7(st.cardMap[allyCardId]?.rank ?? "") > rankValue$7(st.cardMap[lordCardId]?.rank ?? "")); } finally { await popFrame(st); } }); unloaders.push(u); } const uLord = registerAction(state, skill.id, ownerId, "use", (st, params) => { if (ownerId !== 0) return "制霸为主公技,孙策非主公"; const self = st.players[ownerId]; if (!self?.alive) return "已死亡"; const myTurn = st.currentPlayerIndex === ownerId; const inActPhase = st.phase === "出牌"; const free = !hasBlockingPending(st); if (!myTurn || !inActPhase || !free) return "现在不能主动使用制霸"; if (usedThisTurn$3(st, ownerId, LORD_USED)) return "本回合已主动使用过制霸"; const cardId = params.cardId; if (typeof cardId !== "string") return "请选择一张拼点牌"; if (!self.hand.includes(cardId)) return "拼点牌不在手牌中"; const target = params.target; if (typeof target !== "number") return "请选择一名其他吴势力角色"; if (target === ownerId) return "不能与自己拼点"; const tp = st.players[target]; if (!tp?.alive) return "目标不存在或已死亡"; if (tp.faction !== "吴") return "目标非吴势力"; if (tp.hand.length === 0) return "目标没有手牌,无法拼点"; return null; }, async (st, params) => { const lordCardId = params.cardId; const targetIdx = params.target; await markOncePerTurn(st, ownerId, LORD_USED); await pushFrame(st, SKILL_ID$46, ownerId, { ...params }); try { await applyAtom(st, { type: "移动牌", cardId: lordCardId, from: { zone: "手牌", player: ownerId }, to: { zone: "处理区" } }); delete st.localVars[RESP_CARD_KEY]; await applyAtom(st, { type: "请求回应", requestType: RESP_CARD_RT, target: targetIdx, prompt: { type: "useCard", title: `制霸:${st.players[ownerId].name} 与你拼点,请出一张手牌`, cardFilter: { filter: () => true, min: 1, max: 1 } }, timeout: 30 }); const targetCardId = st.localVars[RESP_CARD_KEY]; if (!targetCardId || !st.players[targetIdx].hand.includes(targetCardId)) { await applyAtom(st, { type: "移动牌", cardId: lordCardId, from: { zone: "处理区" }, to: { zone: "弃牌堆" } }); return; } await applyAtom(st, { type: "移动牌", cardId: targetCardId, from: { zone: "手牌", player: targetIdx }, to: { zone: "处理区" } }); await resolveAfterCompare(st, ownerId, lordCardId, targetCardId, rankValue$7(st.cardMap[lordCardId]?.rank ?? "") > rankValue$7(st.cardMap[targetCardId]?.rank ?? "")); } finally { await popFrame(st); } }); unloaders.push(uLord); for (const p of state.players) { const seatId = p.index; const u = registerAction(state, skill.id, seatId, "respond", (st, params) => { const slot = st.pendingSlots.get(seatId); if (!slot) return "当前不需要回应"; const atom = slot.atom; if (atom["type"] !== "请求回应") return "当前不需要回应"; const rt = atom["requestType"]; if (rt === REFUSE_RT || rt === TAKE_RT) { if (seatId !== ownerId) return "只有孙策可以回应"; return null; } if (rt === RESP_CARD_RT) { const cardId = params.cardId; if (typeof cardId !== "string") return "请选择一张拼点牌"; if (!st.players[seatId].hand.includes(cardId)) return "拼点牌不在手牌中"; return null; } return "当前不是制霸回应"; }, async (st, params) => { const slot = st.pendingSlots.get(seatId); if (!slot) return; const rt = slot.atom["requestType"]; if (rt === REFUSE_RT) st.localVars[REFUSE_KEY] = params.choice === true || params.confirmed === true; else if (rt === RESP_CARD_RT) st.localVars[RESP_CARD_KEY] = params.cardId; else if (rt === TAKE_RT) st.localVars[TAKE_KEY] = params.choice === true || params.confirmed === true; }); unloaders.push(u); } return () => { unloaders.forEach((u) => u()); }; } /** * 拼点比点后统一结算(两个方向共用): * initiatorWon=true(发起方赢)→ 两张牌进弃牌堆 * initiatorWon=false(发起方没赢)→ 询问孙策是否获得两张拼点牌: * 获得 → 处理区→孙策手牌;不获得 → 处理区→弃牌堆 */ async function resolveAfterCompare(state, ownerId, cardA, cardB, initiatorWon) { if (initiatorWon) { for (const id of [cardA, cardB]) await applyAtom(state, { type: "移动牌", cardId: id, from: { zone: "处理区" }, to: { zone: "弃牌堆" } }); return; } delete state.localVars[TAKE_KEY]; await applyAtom(state, { type: "请求回应", requestType: TAKE_RT, target: ownerId, prompt: { type: "confirm", title: "制霸:对方拼点没赢,是否获得两张拼点牌?", confirmLabel: "获得", cancelLabel: "不获得" }, defaultChoice: true, timeout: 20 }); const take = state.localVars[TAKE_KEY] !== false; for (const id of [cardA, cardB]) await applyAtom(state, { type: "移动牌", cardId: id, from: { zone: "处理区" }, to: take ? { zone: "手牌", player: ownerId } : { zone: "弃牌堆" } }); } function onMount$175(skill, api) { const ownerId = skill.ownerId; api.defineAction("use", { label: DISPLAY_NAME$69, style: "primary", prompt: { type: "useCardAndTarget", title: "制霸:选择一张拼点牌和一名其他吴势力角色", cardFilter: { filter: () => true, min: 1, max: 1 }, targetFilter: { min: 1, max: 1, filter: (view, t) => { if (t === view.currentPlayerIndex) return false; const tp = view.players[t]; if (!tp) return false; return tp.alive !== false && (tp.handCount ?? 0) > 0; } } }, activeWhen: (ctx) => { const me = ctx.perspectiveIdx; if (me === ownerId && !isLordOwner(ownerId)) return false; if ((ctx.view.players[me]?.hand?.length ?? 0) === 0) return false; return activeUnlessUsedThisTurn(me === ownerId ? LORD_USED : ALLY_USED)(ctx); } }); api.defineAction("respond", { label: DISPLAY_NAME$69, style: "primary", prompt: { type: "confirm", title: "制霸", confirmLabel: "确认", cancelLabel: "取消" } }); } var SKILL_ID$46, DISPLAY_NAME$69, ALLY_USED, LORD_USED, REFUSE_RT, RESP_CARD_RT, TAKE_RT, REFUSE_KEY, RESP_CARD_KEY, TAKE_KEY, 界制霸_default; var init_界制霸 = __esmMin((() => { init_apply(); init_frame(); init_once_per_turn(); init_skill(); init_vars_keys(); init_action_active(); SKILL_ID$46 = "界制霸"; DISPLAY_NAME$69 = "制霸"; ALLY_USED = SKILL_ID$46; LORD_USED = `${SKILL_ID$46}/主动`; REFUSE_RT = `${SKILL_ID$46}/refuse`; RESP_CARD_RT = `${SKILL_ID$46}/respCard`; TAKE_RT = `${SKILL_ID$46}/take`; REFUSE_KEY = `${SKILL_ID$46}/refuseResult`; RESP_CARD_KEY = `${SKILL_ID$46}/respCardId`; TAKE_KEY = `${SKILL_ID$46}/takeResult`; 界制霸_default = { createSkill: createSkill$190, onInit: onInit$190, onMount: onMount$175 }; })); //#endregion //#region src/engine/skills/界势斩.ts var 界势斩_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$189, default: () => _skillModule$94, onInit: () => onInit$189, onMount: () => onMount$174 }); function createSkill$189(id, ownerId) { return { id, ownerId, name: DISPLAY_NAME$68, description: "出牌阶段限两次,令一名其他角色视为对你使用一张决斗" }; } /** 本回合已发动次数(0/1/2)。 */ function usesThisTurn$2(state, ownerId) { const v = state.players[ownerId]?.vars[COUNT_KEY$3]; return typeof v === "number" ? v : 0; } function onInit$189(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "use", (st, params) => { if (st.currentPlayerIndex !== ownerId) return "不是你的回合"; if (st.phase !== "出牌") return "只能在出牌阶段发动"; if (hasBlockingPending(st)) return "当前有未完成的询问"; if (usesThisTurn$2(st, ownerId) >= MAX_USES$2) return "本阶段势斩已达上限(2次)"; if (!st.players[ownerId]?.alive) return "玩家不存在或已死亡"; const target = params.target; if (typeof target !== "number") return "需要指定一名目标"; if (target === ownerId) return "不能以自己为目标"; if (!st.players[target]?.alive) return "目标不合法"; return null; }, async (st, params) => { const from = ownerId; const target = params.target; const nextCount = usesThisTurn$2(st, from) + 1; st.players[from].vars[COUNT_KEY$3] = nextCount; await applyAtom(st, { type: "回合用量", player: from, key: COUNT_KEY$3, value: nextCount }); await pushFrame(st, SKILL_ID$45, from, { ...params }); try { if (st.players[from]?.alive && st.players[target]?.alive) { const virtualCardId = `${SKILL_ID$45}:决斗:${target}:${from}:${st.seq}`; st.cardMap[virtualCardId] = { id: virtualCardId, name: "决斗", suit: "", color: "无色", rank: "A", type: "锦囊牌" }; await runUseFlow(st, target, virtualCardId, [from], "决斗", { virtual: true }); delete st.cardMap[virtualCardId]; } } finally { await popFrame(st); } }); return () => {}; } function onMount$174(_skill, api) { api.defineAction("use", { label: DISPLAY_NAME$68, style: "danger", prompt: { type: "selectTarget", title: "势斩:选择一名其他角色,令其视为对你使用一张决斗(限两次/阶段)", description: "本阶段限两次", targetFilter: { min: 1, max: 1, filter: (view, target) => { if (target === view.currentPlayerIndex) return false; const tp = view.players.find((pl) => pl.index === target); if (!tp || tp.alive === false) return false; return true; } } }, activeWhen: (ctx) => { if (!defaultPlayActive(ctx)) return false; const used = ctx.view.players[ctx.perspectiveIdx]?.turnUsage?.[COUNT_KEY$3]; return (typeof used === "number" ? used : 0) < MAX_USES$2; } }); } var SKILL_ID$45, DISPLAY_NAME$68, COUNT_KEY$3, MAX_USES$2, _skillModule$94; var init_界势斩 = __esmMin((() => { init_apply(); init_frame(); init_action_active(); init_skill(); init_use_card(); SKILL_ID$45 = "界势斩"; DISPLAY_NAME$68 = "势斩"; COUNT_KEY$3 = `${SKILL_ID$45}/usedThisTurn`; MAX_USES$2 = 2; _skillModule$94 = { createSkill: createSkill$189, onInit: onInit$189, onMount: onMount$174 }; })); //#endregion //#region src/engine/skills/界双雄.ts var 界双雄_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$188, default: () => _skillModule$93, onInit: () => onInit$188, onMount: () => onMount$173 }); function createSkill$188(id, ownerId) { return { id, ownerId, name: DISPLAY_NAME$67, description: "摸牌阶段结束时弃置一张牌,本回合可将与之颜色不同的牌当决斗使用;结束阶段获得本回合对你造成伤害的牌" }; } /** 影子卡 id:${原id}#界双雄('界双雄' 后缀避免与标版影子卡冲突) */ function shadowIdOf$16(cardId) { return `${cardId}#界双雄`; } function onInit$188(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "respond", (st, params) => { const slot = st.pendingSlots.get(ownerId); if (!slot) return "当前不需要回应"; const atom = slot.atom; if (atom["type"] !== "请求回应") return "当前不需要回应"; const rt = atom["requestType"]; if (rt !== ACTIVATE_RT$2 && rt !== PICK_RT$9) return "当前不是双雄询问"; if (rt === PICK_RT$9) { const cardId = params.cardId; if (typeof cardId !== "string") return "请选择一张手牌"; if (!st.players[ownerId].hand.includes(cardId)) return "牌不在手牌中"; } return null; }, async (st, params) => { const rt = (st.pendingSlots.get(ownerId)?.atom)?.requestType; if (rt === ACTIVATE_RT$2) st.localVars[ACTIVATE_KEY$1] = params.choice === true; else if (rt === PICK_RT$9) { if (typeof params.cardId === "string") st.localVars[DISCARD_KEY$12] = params.cardId; } }); registerAfterHook(state, skill.id, ownerId, "阶段开始", async (ctx) => { const atom = ctx.atom; if (atom.type !== "阶段开始") return; if (atom.phase !== "摸牌") return; if (atom.player !== ownerId) return; ctx.state.localVars[NORMAL_KEY$1] = ownerId; }); registerBeforeHook(state, skill.id, ownerId, "阶段结束", async (ctx) => { const atom = ctx.atom; if (atom.type !== "阶段结束") return; if (atom.phase !== "摸牌") return; if (atom.player !== ownerId) return; if (ctx.state.localVars[NORMAL_KEY$1] !== ownerId) return; delete ctx.state.localVars[NORMAL_KEY$1]; const self = ctx.state.players[ownerId]; if (!self?.alive) return; if (self.hand.length === 0) return; delete ctx.state.localVars[ACTIVATE_KEY$1]; await applyAtom(ctx.state, { type: "请求回应", requestType: ACTIVATE_RT$2, target: ownerId, prompt: { type: "confirm", title: "是否发动双雄?(弃置一张牌,本回合可将与之颜色不同的牌当决斗使用)", confirmLabel: "发动", cancelLabel: "不发动" }, defaultChoice: false, timeout: 10 }); if (ctx.state.localVars[ACTIVATE_KEY$1] !== true) return; delete ctx.state.localVars[DISCARD_KEY$12]; await applyAtom(ctx.state, { type: "请求回应", requestType: PICK_RT$9, target: ownerId, prompt: { type: "useCard", title: "双雄:选择一张手牌弃置(本回合与之颜色不同的牌当决斗)", cardFilter: { filter: () => true, min: 1, max: 1 } }, timeout: 15 }); const cardId = ctx.state.localVars[DISCARD_KEY$12]; delete ctx.state.localVars[DISCARD_KEY$12]; if (!cardId || !self.hand.includes(cardId)) return; const card = ctx.state.cardMap[cardId]; if (!card) return; await applyAtom(ctx.state, { type: "弃置", player: ownerId, cardIds: [cardId], voluntary: true }); const color = card.color; ctx.state.turn.vars[COLOR_KEY] = color; await applyAtom(ctx.state, { type: "回合用量", player: ownerId, key: COLOR_KEY, value: color }); }); registerAfterHook(state, skill.id, ownerId, "受到伤害后", async (ctx) => { const atom = ctx.atom; if (atom.target !== ownerId) return; if ((atom.amount ?? 0) <= 0) return; const cardId = atom.cardId; if (!cardId) return; const card = ctx.state.cardMap[cardId]; if (!card) return; const effectiveId = card.shadowOf ?? cardId; const list = ctx.state.turn.vars[DAMAGE_KEY] ?? []; if (!list.includes(effectiveId)) { list.push(effectiveId); ctx.state.turn.vars[DAMAGE_KEY] = list; } }); registerAfterHook(state, skill.id, ownerId, "阶段开始", async (ctx) => { const atom = ctx.atom; if (atom.type !== "阶段开始") return; if (atom.phase !== "回合结束") return; if (atom.player !== ownerId) return; if (!ctx.state.players[ownerId]?.alive) return; const list = ctx.state.turn.vars[DAMAGE_KEY] ?? []; if (list.length === 0) return; for (const cardId of list) { if (!ctx.state.zones.discardPile.includes(cardId)) continue; if (!ctx.state.cardMap[cardId]) continue; await applyAtom(ctx.state, { type: "移动牌", cardId, from: { zone: "弃牌堆" }, to: { zone: "手牌", player: ownerId } }); } delete ctx.state.turn.vars[DAMAGE_KEY]; }); registerAction(state, skill.id, ownerId, "transform", (s, params) => { const myTurn = s.currentPlayerIndex === ownerId; const inActPhase = s.phase === "出牌"; const free = !hasBlockingPending(s); const self = s.players[ownerId]; const selfAlive = self.alive === true; const discardColor = s.turn.vars[COLOR_KEY]; const colorSet = typeof discardColor === "string" && discardColor !== ""; const cardId = params.cardId; const cardIdOk = typeof cardId === "string"; const cardInHand = cardIdOk && self.hand.includes(cardId); const card = cardIdOk ? s.cardMap[cardId] : void 0; const cardExists = !!card; const colorDiff = !!card && !!discardColor && card.color !== discardColor; return myTurn && inActPhase && free && selfAlive && colorSet && cardInHand && cardExists && colorDiff ? null : "双雄:需要一张与本回合弃置牌颜色不同的手牌"; }, async (s, params) => { const cardId = params.cardId; const shadowId = shadowIdOf$16(cardId); await applyAtom(s, { type: "当作", player: ownerId, cardIds: [cardId], shadowId, outputName: "决斗" }); }, (s, params) => { const cardId = params.cardId; const sId = shadowIdOf$16(cardId); delete s.cardMap[sId]; const self = s.players[ownerId]; const idx = self.hand.indexOf(sId); if (idx >= 0) self.hand[idx] = cardId; }); return () => {}; } function onMount$173(skill, api) { api.defineAction("respond", { label: DISPLAY_NAME$67, style: "primary", prompt: { type: "confirm", title: "是否发动双雄?", confirmLabel: "发动", cancelLabel: "不发动" } }); api.defineAction("transform", { label: DISPLAY_NAME$67, style: "danger", prompt: { type: "useCardAndTarget", title: "选择一张与本回合弃置牌颜色不同的手牌当决斗使用", cardFilter: { filter: () => true, min: 1, max: 1 }, targetFilter: { min: 1, max: 1 } }, transform: (card) => ({ name: "决斗", sourceCardId: card.id, fromSkill: skill.id }), activeWhen: (ctx) => { if (!defaultPlayActive(ctx)) return false; const p = ctx.view.players[ctx.perspectiveIdx]; if (!p?.hand) return false; const discardColor = p.turnUsage?.[COLOR_KEY]; if (typeof discardColor !== "string" || discardColor === "") return false; return p.hand.some((c) => c.color !== discardColor); } }); } var DISPLAY_NAME$67, NORMAL_KEY$1, ACTIVATE_KEY$1, DISCARD_KEY$12, COLOR_KEY, DAMAGE_KEY, ACTIVATE_RT$2, PICK_RT$9, _skillModule$93; var init_界双雄 = __esmMin((() => { init_apply(); init_skill(); init_action_active(); init_vars_keys(); DISPLAY_NAME$67 = "双雄"; NORMAL_KEY$1 = "界双雄/normalDrawPhase"; ACTIVATE_KEY$1 = "界双雄/activate"; DISCARD_KEY$12 = "界双雄/discardCard"; COLOR_KEY = JIESHUANGXIONG_COLOR_VIEW_KEY; DAMAGE_KEY = "界双雄/damageCards"; ACTIVATE_RT$2 = "界双雄/activate"; PICK_RT$9 = "界双雄/pick"; _skillModule$93 = { createSkill: createSkill$188, onInit: onInit$188, onMount: onMount$173 }; })); //#endregion //#region src/engine/skills/界反间.ts var 界反间_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$187, default: () => _skillModule$92, onInit: () => onInit$187, onMount: () => onMount$172 }); function createSkill$187(id, ownerId) { return { id, ownerId, name: "界反间", description: "出牌阶段限一次,展示并交给一名其他角色一张手牌,其选择:弃置同色牌或失去1点体力" }; } function onInit$187(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "use", (st, params) => { const self = st.players[ownerId]; if (!self) return "player not found"; if (!self.alive) return "你已死亡"; if (st.currentPlayerIndex !== ownerId) return "只能在你的回合使用"; if (st.phase !== "出牌") return "只能在出牌阶段使用"; if (hasBlockingPending(st)) return "当前有未完成的询问"; if (usedThisTurn$3(st, ownerId, "反间")) return "本回合已使用过反间"; if (self.hand.length === 0) return "需要有手牌才能发动反间"; const cardId = params.cardId; if (typeof cardId !== "string" || !self.hand.includes(cardId)) return "需要选择一张手牌"; const targets = params.targets; if (!Array.isArray(targets) || targets.length !== 1) return "需要指定一名目标"; const target = targets[0]; if (target === ownerId) return "不能对自己使用反间"; if (!st.players[target]?.alive) return "目标不合法"; return null; }, async (st, params) => { const from = ownerId; const target = params.targets[0]; const cardId = params.cardId; await markOncePerTurn(st, from, "反间"); await pushFrame(st, "界反间", from, { ...params }); await applyAtom(st, { type: "移动牌", cardId, from: { zone: "手牌", player: from }, to: { zone: "手牌", player: target } }); delete st.localVars[CHOICE_KEY$11]; await applyAtom(st, { type: "请求回应", requestType: CHOICE_REQUEST$1, target, prompt: { type: "confirm", title: "反间:展示所有手牌弃置同色牌,或失去1点体力", confirmLabel: "展示并弃置同色牌", cancelLabel: "失去1点体力" }, defaultChoice: false, timeout: 30 }); if (st.localVars[CHOICE_KEY$11] === true) { const givenSuit = st.cardMap[cardId]?.suit; const tp = st.players[target]; if (givenSuit && tp?.alive) { const toDiscard = tp.hand.filter((id) => st.cardMap[id]?.suit === givenSuit); if (toDiscard.length > 0) await applyAtom(st, { type: "弃置", player: target, cardIds: toDiscard }); } } else if (st.players[target]?.alive) await applyAtom(st, { type: "失去体力", target, amount: 1 }); await popFrame(st); }); const unloaders = []; for (const p of state.players) { const seatId = p.index; const u = registerAction(state, skill.id, seatId, "respond", (st, params) => { const slot = st.pendingSlots.get(seatId); if (!slot) return "当前不需要回应"; const atom = slot.atom; if (atom["type"] !== "请求回应") return "当前不需要回应"; if (atom["requestType"] !== CHOICE_REQUEST$1) return "当前不是反间二选一"; if (typeof params.choice !== "boolean") return "需要选择一项"; return null; }, async (st, params) => { st.localVars[CHOICE_KEY$11] = params.choice === true; }); unloaders.push(u); } return () => { unloaders.forEach((u) => u()); }; } function onMount$172(skill, api) { api.defineAction("use", { label: "界反间", style: "danger", prompt: { type: "useCardAndTarget", title: "反间:选一张手牌展示并交给一名其他角色", cardFilter: { filter: () => true, min: 1, max: 1 }, targetFilter: { min: 1, max: 1, filter: (_view, t) => t !== skill.ownerId } }, activeWhen: (ctx) => activeUnlessUsedThisTurn("反间")(ctx) && (ctx.view.players[ctx.perspectiveIdx]?.hand?.length ?? 0) > 0 }); api.defineAction("respond", { label: "界反间", style: "danger", prompt: { type: "confirm", title: "反间:展示所有手牌弃置同色牌,或失去1点体力", confirmLabel: "展示并弃置同色牌", cancelLabel: "失去1点体力" } }); return () => {}; } var CHOICE_REQUEST$1, CHOICE_KEY$11, _skillModule$92; var init_界反间 = __esmMin((() => { init_apply(); init_frame(); init_once_per_turn(); init_skill(); CHOICE_REQUEST$1 = "界反间/choice"; CHOICE_KEY$11 = "反间/choice"; _skillModule$92 = { createSkill: createSkill$187, onInit: onInit$187, onMount: onMount$172 }; })); //#endregion //#region src/engine/skills/界咆哮.ts var 界咆哮_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$186, default: () => 界咆哮_default, onInit: () => onInit$186 }); function createSkill$186(id, ownerId) { return { id, ownerId, name: "界咆哮", description: "锁定技:出牌阶段使用【杀】无次数限制;杀被【闪】抵消时摸一张牌", isLocked: true }; } function onInit$186(skill, state) { const ownerId = skill.ownerId; const unregMax = registerSlashUnlimitedProvider(state, ownerId, () => true); const unregSync = registerAfterHook(state, skill.id, ownerId, "阶段开始", async (ctx) => { const atom = ctx.atom; if (atom.type !== "阶段开始") return; if (atom.phase !== "出牌") return; if (atom.player !== ownerId) return; await applyAtom(ctx.state, { type: "回合用量", player: ownerId, key: slashUnlimitedKey("界咆哮"), value: true }); }); const unregHook = registerAfterHook(state, skill.id, ownerId, "被抵消", async (ctx) => { if (ctx.frame.skillId !== "杀") return; if (ctx.atom.source !== ownerId) return; await applyAtom(ctx.state, { type: "摸牌", player: ownerId, count: 1 }); }); return () => { unregMax(); unregSync(); unregHook(); }; } var 界咆哮_default; var init_界咆哮 = __esmMin((() => { init_apply(); init_skill(); init_slash_quota(); init_vars_keys(); 界咆哮_default = { createSkill: createSkill$186, onInit: onInit$186 }; })); //#endregion //#region src/engine/skills/界固政.ts var 界固政_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$185, default: () => _skillModule$91, onInit: () => onInit$185, onMount: () => onMount$171 }); /** localVars key:界固政/弃牌/ = 该玩家本阶段累计因弃置置入弃牌堆的 cardIds */ function discardKey(player) { return `界固政/弃牌/${player}`; } /** 清空本技能所有 localVars(阶段结束时调用,为下一阶段重置) */ function clearPhaseVars(state) { for (const key of [...Object.keys(state.localVars)]) if (key.startsWith("界固政/")) delete state.localVars[key]; } function createSkill$185(id, ownerId) { return { id, ownerId, name: SKILL_NAME$5, description: "每阶段限一次,其他角色≥2张牌因弃置置入弃牌堆后,可将其中一张交给该角色,然后可获其余" }; } function onInit$185(skill, state) { const ownerId = skill.ownerId; registerAfterHook(state, skill.id, ownerId, "弃置", async (ctx) => { const atom = ctx.atom; if (atom.player === ownerId) return; const st = ctx.state; if (!st.players[ownerId]?.alive) return; const key = discardKey(atom.player); const accumulated = [...st.localVars[key] ?? [], ...atom.cardIds]; st.localVars[key] = accumulated; if (st.localVars[TRIGGERED_KEY$3]) return; if (accumulated.length < 2) return; st.localVars[TRIGGERED_KEY$3] = true; const player = atom.player; const cardIds = [...accumulated]; await pushFrame(st, SKILL_NAME$5, ownerId, { target: player, cardIds }); delete st.localVars[CONFIRM_KEY$18]; await applyAtom(st, { type: "请求回应", requestType: CONFIRM_REQUEST$10, target: ownerId, prompt: { type: "confirm", title: `是否发动界固政?(${st.players[player]?.name ?? ""} 弃置了 ${cardIds.length} 张牌)`, confirmLabel: "发动", cancelLabel: "不发动" }, defaultChoice: false, timeout: 30 }); if (!(st.localVars[CONFIRM_KEY$18] === true)) { await popFrame(st); return; } const available = cardIds.filter((id) => st.zones.discardPile.includes(id)); if (available.length === 0) { await popFrame(st); return; } let chosen; if (available.length === 1) chosen = available[0]; else { delete st.localVars[PICK_KEY$7]; await applyAtom(st, { type: "请求回应", requestType: PICK_REQUEST$2, target: ownerId, prompt: { type: "pickProcessingCard", title: "界固政:选择一张牌交给该角色(其余你可获得)", cards: available.map((id) => { const c = st.cardMap[id]; return { cardId: id, cardName: c?.name ?? "?", suit: c?.suit ?? "", rank: c?.rank ?? "" }; }) }, defaultChoice: available[0], timeout: 30 }); const picked = st.localVars[PICK_KEY$7]; chosen = picked && available.includes(picked) ? picked : available[0]; } await applyAtom(st, { type: "移动牌", cardId: chosen, from: { zone: "弃牌堆" }, to: { zone: "手牌", player } }); const rest = available.filter((id) => id !== chosen); if (rest.length > 0) { delete st.localVars[REST_KEY]; await applyAtom(st, { type: "请求回应", requestType: REST_REQUEST, target: ownerId, prompt: { type: "confirm", title: `是否获得其余 ${rest.length} 张牌?`, confirmLabel: "获得", cancelLabel: "不获得" }, defaultChoice: false, timeout: 30 }); if (st.localVars[REST_KEY] === true) for (const id of rest) await applyAtom(st, { type: "移动牌", cardId: id, from: { zone: "弃牌堆" }, to: { zone: "手牌", player: ownerId } }); } await popFrame(st); }); registerAfterHook(state, skill.id, ownerId, "阶段结束", async (ctx) => { clearPhaseVars(ctx.state); }); registerAction(state, skill.id, ownerId, "respond", (st, params) => { const slot = st.pendingSlots.get(ownerId); if (!slot) return "当前不需要回应"; if (slot.atom.type !== "请求回应") return "当前不是界固政窗口"; const atom = slot.atom; if (atom.requestType === CONFIRM_REQUEST$10 || atom.requestType === REST_REQUEST) return null; if (atom.requestType === PICK_REQUEST$2) { if (typeof params.cardId !== "string") return "需要选择一张牌"; return null; } return "当前不是界固政窗口"; }, async (st, params) => { const atom = st.pendingSlots.get(ownerId).atom; if (atom.requestType === CONFIRM_REQUEST$10) st.localVars[CONFIRM_KEY$18] = params.choice === true || params.confirmed === true; else if (atom.requestType === PICK_REQUEST$2) st.localVars[PICK_KEY$7] = params.cardId; else if (atom.requestType === REST_REQUEST) st.localVars[REST_KEY] = params.choice === true || params.confirmed === true; }); return () => {}; } function onMount$171(_skill, _api) { return () => {}; } var SKILL_NAME$5, CONFIRM_REQUEST$10, PICK_REQUEST$2, REST_REQUEST, CONFIRM_KEY$18, PICK_KEY$7, REST_KEY, TRIGGERED_KEY$3, _skillModule$91; var init_界固政 = __esmMin((() => { init_apply(); init_frame(); init_skill(); SKILL_NAME$5 = "界固政"; CONFIRM_REQUEST$10 = "界固政/确认"; PICK_REQUEST$2 = "界固政/选牌"; REST_REQUEST = "界固政/获其余"; CONFIRM_KEY$18 = "界固政/确认结果"; PICK_KEY$7 = "界固政/所选牌"; REST_KEY = "界固政/获其余结果"; TRIGGERED_KEY$3 = "界固政/已触发"; _skillModule$91 = { createSkill: createSkill$185, onInit: onInit$185, onMount: onMount$171 }; })); //#endregion //#region src/engine/skills/界国色.ts var 界国色_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$184, default: () => _skillModule$90, onInit: () => onInit$184, onMount: () => onMount$170 }); /** 界国色可用牌:方块(♦)牌(手牌或装备区) */ function isGuoseCard(card) { return !!card && card.suit === "♦"; } /** 玩家是否持有某张牌(手牌或装备区)。 */ function ownsCard$1(self, cardId) { if (self.hand.includes(cardId)) return true; return Object.values(self.equipment).some((id) => id === cardId); } function currentRequestType$11(state, ownerId) { const slot = state.pendingSlots.get(ownerId); if (!slot) return void 0; return slot.atom.requestType; } function createSkill$184(id, ownerId) { return { id, ownerId, name: "界国色", description: "出牌阶段限一次,选一项后摸一张牌:①方片牌当乐不思蜀使用;②弃方片牌和场上乐不思蜀" }; } function onInit$184(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "use", (st, _params) => { if (st.currentPlayerIndex !== ownerId) return "只能在你的回合使用"; if (st.phase !== "出牌") return "只能在出牌阶段使用"; if (hasBlockingPending(st)) return "当前有未完成的询问"; if (usedThisTurn$3(st, ownerId, "界国色")) return "本回合已使用过界国色"; const self = st.players[ownerId]; if (!self?.alive) return "玩家不存在或已死亡"; if (![...self.hand, ...Object.values(self.equipment).filter((id) => typeof id === "string")].some((id) => isGuoseCard(st.cardMap[id]))) return "需要一张方片牌"; return null; }, async (st, _params) => { await markOncePerTurn(st, ownerId, "界国色"); await pushFrame(st, "界国色", ownerId, {}); delete st.localVars[OPTION_KEY$5]; await applyAtom(st, { type: "请求回应", requestType: OPTION_RT$5, target: ownerId, prompt: { type: "confirm", title: "界国色:选择一项(然后摸一张牌)", confirmLabel: "①方片当乐使用", cancelLabel: "②弃方片+弃场上乐" }, defaultChoice: false, timeout: 15 }); const option = st.localVars[OPTION_KEY$5]; delete st.localVars[OPTION_KEY$5]; if (option === "use") await executeOptionUse(st, ownerId); else await executeOptionRemove(st, ownerId); if (st.players[ownerId]?.alive) await applyAtom(st, { type: "摸牌", player: ownerId, count: 1 }); await popFrame(st); }); registerAction(state, skill.id, ownerId, "respond", (st, params) => { const rt = currentRequestType$11(st, ownerId); if (rt !== OPTION_RT$5 && rt !== USE_RT$1 && rt !== REMOVE_CARD_RT && rt !== REMOVE_TARGET_RT) return "当前不是界国色询问"; if (rt === OPTION_RT$5) return null; const self = st.players[ownerId]; if (!self?.alive) return "玩家不可用"; if (rt === USE_RT$1) { const cardId = params.cardId; if (typeof cardId !== "string") return "请选择一张方片牌"; if (!ownsCard$1(self, cardId)) return "牌不在手牌或装备区"; if (!isGuoseCard(st.cardMap[cardId])) return "需要一张方片牌"; const target = params.target ?? params.targets?.[0]; if (typeof target !== "number") return "请选择目标"; if (target === ownerId) return "不能对自己使用"; if (!st.players[target]?.alive) return "目标不存在或已死亡"; if (st.players[target].pendingTricks.some((t) => t.name === TRICK_NAME$1)) return "目标判定区已有乐不思蜀"; return null; } if (rt === REMOVE_CARD_RT) { const cardId = params.cardId; if (typeof cardId !== "string") return "请选择一张方片牌弃置"; if (!ownsCard$1(self, cardId)) return "牌不在手牌或装备区"; if (!isGuoseCard(st.cardMap[cardId])) return "需要一张方片牌"; return null; } const trickTarget = params.target; if (typeof trickTarget !== "number") return "请选择一名角色"; const tp = st.players[trickTarget]; if (!tp?.alive) return "目标无效"; if (!tp.pendingTricks.some((t) => t.name === TRICK_NAME$1)) return "该角色判定区没有乐不思蜀"; return null; }, async (st, params) => { const rt = currentRequestType$11(st, ownerId); const confirmed = params.choice === true || params.confirmed === true; if (rt === OPTION_RT$5) st.localVars[OPTION_KEY$5] = confirmed ? "use" : "remove"; else if (rt === USE_RT$1) { st.localVars[CARD_KEY$4] = params.cardId ?? null; st.localVars[TARGET_KEY$29] = params.target ?? params.targets?.[0] ?? null; } else if (rt === REMOVE_CARD_RT) st.localVars[CARD_KEY$4] = params.cardId ?? null; else if (rt === REMOVE_TARGET_RT) st.localVars[TRICK_TARGET_KEY] = params.target ?? null; }); return () => {}; } async function executeOptionUse(st, ownerId) { if (!st.players[ownerId]?.alive) return; delete st.localVars[CARD_KEY$4]; delete st.localVars[TARGET_KEY$29]; await applyAtom(st, { type: "请求回应", requestType: USE_RT$1, target: ownerId, prompt: { type: "useCardAndTarget", title: "界国色:选一张方片牌当乐不思蜀,选一名其他角色(无距离限制)", cardFilter: { filter: (c) => c.suit === "♦", min: 1, max: 1 }, targetFilter: { min: 1, max: 1, filter: (view, t) => t !== ownerId && view.players[t]?.alive === true } }, timeout: 20 }); const cardId = st.localVars[CARD_KEY$4]; const target = st.localVars[TARGET_KEY$29]; delete st.localVars[CARD_KEY$4]; delete st.localVars[TARGET_KEY$29]; if (typeof cardId !== "string" || typeof target !== "number") return; if (!st.players[target]?.alive) return; const equipSlotEntry = Object.entries(st.players[ownerId].equipment).find(([, id]) => id === cardId); if (equipSlotEntry) await applyAtom(st, { type: "卸下", player: ownerId, slot: equipSlotEntry[0] }); await applyAtom(st, { type: "移动牌", cardId, from: { zone: "手牌", player: ownerId }, to: { zone: "处理区" } }); await applyAtom(st, { type: "添加延时锦囊", player: target, trick: { name: TRICK_NAME$1, source: ownerId, card: st.cardMap[cardId] ?? { id: cardId, name: TRICK_NAME$1, suit: "♦", color: "红", rank: "A", type: "锦囊牌" } } }); await applyAtom(st, { type: "移动牌", cardId, from: { zone: "处理区" }, to: { zone: "弃牌堆" } }); } async function executeOptionRemove(st, ownerId) { if (!st.players[ownerId]?.alive) return; delete st.localVars[CARD_KEY$4]; await applyAtom(st, { type: "请求回应", requestType: REMOVE_CARD_RT, target: ownerId, prompt: { type: "useCard", title: "界国色:选一张方片牌弃置", cardFilter: { filter: (c) => c.suit === "♦", min: 1, max: 1 } }, timeout: 20 }); const cardId = st.localVars[CARD_KEY$4]; delete st.localVars[CARD_KEY$4]; if (typeof cardId !== "string") return; delete st.localVars[TRICK_TARGET_KEY]; await applyAtom(st, { type: "请求回应", requestType: REMOVE_TARGET_RT, target: ownerId, prompt: { type: "choosePlayer", title: "界国色:选择要弃置其乐不思蜀的角色", min: 1, max: 1, candidates: st.players.map((p, i) => ({ p, i })).filter(({ p, i }) => i !== ownerId && p.alive && p.pendingTricks.some((t) => t.name === TRICK_NAME$1)).map(({ i }) => i) }, timeout: 20 }); const trickTarget = st.localVars[TRICK_TARGET_KEY]; delete st.localVars[TRICK_TARGET_KEY]; if (typeof trickTarget !== "number") return; const tp = st.players[trickTarget]; if (!tp?.alive) return; if (!tp.pendingTricks.some((t) => t.name === TRICK_NAME$1)) return; await applyAtom(st, { type: "弃置", player: ownerId, cardIds: [cardId], voluntary: true }); await applyAtom(st, { type: "移除延时锦囊", player: trickTarget, trickName: TRICK_NAME$1 }); } function onMount$170(skill, api) { api.defineAction("use", { label: "界国色", style: "primary", prompt: { type: "confirm", title: "界国色:选一项后摸一张牌", confirmLabel: "发动", cancelLabel: "不发动" }, activeWhen: (ctx) => { if (!activeUnlessUsedThisTurn("界国色")(ctx)) return false; const me = ctx.view.players[ctx.perspectiveIdx]; const hasDiamondInHand = (me?.hand ?? []).some((c) => c.suit === "♦"); const hasDiamondInEquip = Object.values(me?.equipment ?? {}).some((id) => { return ctx.view.cardMap[id]?.suit === "♦"; }); return hasDiamondInHand || hasDiamondInEquip; } }); api.defineAction("respond", { label: "界国色", style: "primary", prompt: { type: "confirm", title: "界国色", confirmLabel: "确认", cancelLabel: "取消" } }); return () => {}; } var TRICK_NAME$1, OPTION_RT$5, USE_RT$1, REMOVE_CARD_RT, REMOVE_TARGET_RT, OPTION_KEY$5, CARD_KEY$4, TARGET_KEY$29, TRICK_TARGET_KEY, _skillModule$90; var init_界国色 = __esmMin((() => { init_apply(); init_frame(); init_once_per_turn(); init_skill(); TRICK_NAME$1 = "乐不思蜀"; OPTION_RT$5 = "界国色/option"; USE_RT$1 = "界国色/use"; REMOVE_CARD_RT = "界国色/removeCard"; REMOVE_TARGET_RT = "界国色/removeTarget"; OPTION_KEY$5 = "界国色/option"; CARD_KEY$4 = "界国色/cardId"; TARGET_KEY$29 = "界国色/target"; TRICK_TARGET_KEY = "界国色/trickTarget"; _skillModule$90 = { createSkill: createSkill$184, onInit: onInit$184, onMount: onMount$170 }; })); //#endregion //#region src/engine/skills/界天香.ts var 界天香_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$183, default: () => 界天香_default, onInit: () => onInit$183, onMount: () => onMount$169 }); /** 判定一张牌对界天香是否合法:红桃;若拥有红颜/界红颜,黑桃也视为红桃。 */ function isTianxiangCard(state, ownerId, cardId) { const card = state.cardMap[cardId]; if (!card) return false; if (card.suit === "♥") return true; const skills = state.players[ownerId]?.skills ?? []; if (card.suit === "♠" && (skills.includes("红颜") || skills.includes("界红颜"))) return true; return false; } /** 界版:红桃牌可在手牌或装备区。返回玩家拥有的所有 cardId(手牌 + 装备区)。 */ function getOwnedCardIds(self) { const equipIds = Object.values(self.equipment).filter((id) => typeof id === "string"); return [...self.hand, ...equipIds]; } /** 玩家是否持有某张牌(手牌或装备区)。 */ function ownsCard(self, cardId) { if (self.hand.includes(cardId)) return true; return Object.values(self.equipment).some((id) => id === cardId); } function currentRequestType$10(state, ownerId) { const slot = state.pendingSlots.get(ownerId); if (!slot) return void 0; return slot.atom.requestType; } function createSkill$183(id, ownerId) { return { id, ownerId, name: "界天香", description: "受到伤害时,弃一张红桃牌防止之,令一名其他角色二选一:①受来源1伤害并摸X(X为已损失体力至多5);②失去1体力并获得弃置的牌" }; } function onInit$183(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "respond", (st, params) => { const rt = currentRequestType$10(st, ownerId); if (rt !== CONFIRM_RT$45 && rt !== CHOOSE_RT$12 && rt !== OPTION_RT$4) return "当前不是界天香询问"; if (rt === CONFIRM_RT$45 || rt === OPTION_RT$4) return null; const cardId = params.cardId; const target = params.target; if (typeof cardId !== "string") return "请选择一张红桃牌"; const self = st.players[ownerId]; if (!self?.alive) return "玩家不可用"; if (!ownsCard(self, cardId)) return "牌不在手牌或装备区"; if (!isTianxiangCard(st, ownerId, cardId)) return "必须选择红桃牌"; if (typeof target !== "number") return "请选择一名其他角色"; if (target === ownerId) return "不能选择自己"; if (!st.players[target]?.alive) return "目标无效"; return null; }, async (st, params) => { const rt = currentRequestType$10(st, ownerId); const confirmed = params.choice === true || params.confirmed === true; if (rt === CONFIRM_RT$45) st.localVars[CONFIRMED_KEY$36] = confirmed; else if (rt === CHOOSE_RT$12) { st.localVars[CARD_KEY$3] = params.cardId ?? null; st.localVars[TARGET_KEY$28] = params.target ?? null; } else if (rt === OPTION_RT$4) st.localVars[OPTION_KEY$4] = confirmed ? "damage" : "lose"; }); registerBeforeHook(state, skill.id, ownerId, "受到伤害时", async (ctx) => { const atom = ctx.atom; if (atom.target !== ownerId) return; if ((atom.amount ?? 0) <= 0) return; const self = ctx.state.players[ownerId]; if (!self?.alive) return; const validCards = getOwnedCardIds(self).filter((id) => isTianxiangCard(ctx.state, ownerId, id)); if (validCards.length === 0) return; if (!ctx.state.players.some((p, i) => i !== ownerId && p.alive)) return; delete ctx.state.localVars[CONFIRMED_KEY$36]; await applyAtom(ctx.state, { type: "请求回应", requestType: CONFIRM_RT$45, target: ownerId, prompt: { type: "confirm", title: "是否发动界天香(弃红桃牌防止伤害)?", confirmLabel: "发动", cancelLabel: "不发动" }, defaultChoice: false, timeout: 10 }); if (!ctx.state.localVars[CONFIRMED_KEY$36]) return; delete ctx.state.localVars[CARD_KEY$3]; delete ctx.state.localVars[TARGET_KEY$28]; const skills = self.skills; const hasHongyan = skills.includes("红颜") || skills.includes("界红颜"); await applyAtom(ctx.state, { type: "请求回应", requestType: CHOOSE_RT$12, target: ownerId, prompt: { type: "useCardAndTarget", title: "界天香:弃一张红桃牌(手牌或装备区),选择一名其他角色", cardFilter: { filter: (c) => c.suit === "♥" || hasHongyan && c.suit === "♠", candidates: validCards, min: 1, max: 1 }, targetFilter: { min: 1, max: 1, filter: (_view, t) => t !== ownerId && ctx.state.players[t]?.alive === true } }, timeout: 15 }); const cardId = ctx.state.localVars[CARD_KEY$3]; const newTarget = ctx.state.localVars[TARGET_KEY$28]; delete ctx.state.localVars[CONFIRMED_KEY$36]; delete ctx.state.localVars[CARD_KEY$3]; delete ctx.state.localVars[TARGET_KEY$28]; if (typeof cardId !== "string" || typeof newTarget !== "number") return; if (newTarget === ownerId) return; const targetPlayer = ctx.state.players[newTarget]; if (!targetPlayer?.alive) return; if (!ownsCard(self, cardId)) return; if (!isTianxiangCard(ctx.state, ownerId, cardId)) return; delete ctx.state.localVars[OPTION_KEY$4]; await applyAtom(ctx.state, { type: "请求回应", requestType: OPTION_RT$4, target: ownerId, prompt: { type: "confirm", title: `界天香:令 ${targetPlayer.name} ①受来源1点伤害并摸X张牌?(确认=①,取消=②失去1体力并获得弃牌)`, confirmLabel: "①受伤害+摸牌", cancelLabel: "②失去体力+获牌" }, defaultChoice: false, timeout: 15 }); const chosenOption = ctx.state.localVars[OPTION_KEY$4]; delete ctx.state.localVars[OPTION_KEY$4]; const isDamage = chosenOption === "damage"; await applyAtom(ctx.state, { type: "弃置", player: ownerId, cardIds: [cardId], voluntary: true }); if (isDamage) { const source = atom.source ?? ownerId; await runDamageFlow(ctx.state, source, newTarget, 1, void 0, atom.damageType); if (targetPlayer.alive) { const lostHealth = targetPlayer.maxHealth - getHealthValue(targetPlayer); const x = Math.min(lostHealth, 5); if (x > 0) await applyAtom(ctx.state, { type: "摸牌", player: newTarget, count: x }); } } else { await applyAtom(ctx.state, { type: "失去体力", target: newTarget, amount: 1 }); if (targetPlayer.alive && ctx.state.zones.discardPile.includes(cardId)) await applyAtom(ctx.state, { type: "移动牌", cardId, from: { zone: "弃牌堆" }, to: { zone: "手牌", player: newTarget } }); } return { kind: "cancel" }; }); return () => {}; } function onMount$169(_skill, api) { api.defineAction("respond", { label: "界天香", style: "primary", prompt: { type: "confirm", title: "是否发动界天香?", confirmLabel: "发动", cancelLabel: "不发动" } }); } var CONFIRM_RT$45, CHOOSE_RT$12, OPTION_RT$4, CONFIRMED_KEY$36, CARD_KEY$3, TARGET_KEY$28, OPTION_KEY$4, 界天香_default; var init_界天香 = __esmMin((() => { init_types(); init_apply(); init_damage(); init_skill(); CONFIRM_RT$45 = "界天香/confirm"; CHOOSE_RT$12 = "界天香/choose"; OPTION_RT$4 = "界天香/option"; CONFIRMED_KEY$36 = "界天香/confirmed"; CARD_KEY$3 = "界天香/cardId"; TARGET_KEY$28 = "界天香/target"; OPTION_KEY$4 = "界天香/option"; 界天香_default = { createSkill: createSkill$183, onInit: onInit$183, onMount: onMount$169 }; })); //#endregion //#region src/engine/skills/界奇才.ts var 界奇才_exports = /* @__PURE__ */ __exportAll({ QICAI_ARMOR_TAG: () => QICAI_ARMOR_TAG, QICAI_TAG: () => QICAI_TAG, QICAI_TREASURE_TAG: () => QICAI_TREASURE_TAG, createSkill: () => createSkill$182, default: () => 界奇才_default, onInit: () => onInit$182, onMount: () => onMount$168 }); function createSkill$182(id, ownerId) { return { id, ownerId, name: "界奇才", description: "锁定技:你使用任何锦囊牌无距离限制;其他角色不能弃置你装备区的防具或宝物", isLocked: true }; } function onInit$182(skill, state) { const ownerId = skill.ownerId; const player = state.players[ownerId]; if (player) { if (!player.tags.includes("奇才/无距离限制")) player.tags.push(QICAI_TAG); if (!player.tags.includes("奇才/防具保护")) player.tags.push(QICAI_ARMOR_TAG); if (!player.tags.includes("奇才/宝物保护")) player.tags.push(QICAI_TREASURE_TAG); } const unloadHook = registerBeforeHook(state, skill.id, ownerId, "请求回应", async (ctx) => { const atom = ctx.atom; if (atom.prompt?.type !== "pickTargetCard") return; if (atom.prompt?.mode !== "discard") return; const targetIdx = atom.prompt?.target; if (typeof targetIdx !== "number") return; if (targetIdx !== ownerId) return; if (atom.prompt?.equipment) atom.prompt.equipment = atom.prompt.equipment.filter((e) => { if (e.slot === "防具") return false; if (e.slot === "宝物") return false; return true; }); }); return () => { unloadHook(); const p = state.players[ownerId]; if (p) p.tags = p.tags.filter((t) => t !== "奇才/无距离限制" && t !== "奇才/防具保护" && t !== "奇才/宝物保护"); }; } function onMount$168() {} var QICAI_TAG, QICAI_ARMOR_TAG, QICAI_TREASURE_TAG, 界奇才_default; var init_界奇才 = __esmMin((() => { init_skill(); QICAI_TAG = "奇才/无距离限制"; QICAI_ARMOR_TAG = "奇才/防具保护"; QICAI_TREASURE_TAG = "奇才/宝物保护"; 界奇才_default = { createSkill: createSkill$182, onInit: onInit$182, onMount: onMount$168 }; })); //#endregion //#region src/engine/skills/界奸雄.ts var 界奸雄_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$181, default: () => 界奸雄_default, onInit: () => onInit$181, onMount: () => onMount$167 }); function createSkill$181(id, ownerId) { return { id, ownerId, name: "界奸雄", description: "当你受到伤害后,你可以摸一张牌,并获得造成此伤害的牌" }; } function onInit$181(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "respond", (s) => { const slot = s.pendingSlots.get(ownerId); if (slot?.atom.type !== "请求回应") return "当前不需要回应"; if (slot.atom.requestType !== CHOOSE_RT$11) return "当前不是奸雄选择"; return null; }, async (s, params) => { s.localVars[CHOICE_KEY$10] = params.choice === true; }); registerAfterHook(state, skill.id, ownerId, "受到伤害后", async (ctx) => { const atom = ctx.atom; if (atom.target !== ownerId) return; delete ctx.state.localVars[WANTCARD_KEY]; if ((atom.amount ?? 0) <= 0) return; const damageCardId = atom.cardId; const hasCard = typeof damageCardId === "string" && !!ctx.state.cardMap[damageCardId]; const effectiveId = hasCard ? ctx.state.cardMap[damageCardId].shadowOf ?? damageCardId : damageCardId; delete ctx.state.localVars[CHOICE_KEY$10]; await applyAtom(ctx.state, { type: "请求回应", requestType: CHOOSE_RT$11, target: ownerId, prompt: { type: "confirm", title: hasCard ? "界奸雄:是否发动?(摸一张牌并获得造成此伤害的牌)" : "界奸雄:是否发动?(无伤害牌可获得,仅摸一张牌)", confirmLabel: "发动", cancelLabel: "不发动" }, defaultChoice: false, timeout: 10 }); if (!(ctx.state.localVars[CHOICE_KEY$10] === true)) { delete ctx.state.localVars[CHOICE_KEY$10]; return; } await applyAtom(ctx.state, { type: "摸牌", player: ownerId, count: 1 }); if (hasCard) ctx.state.localVars[WANTCARD_KEY] = effectiveId; delete ctx.state.localVars[CHOICE_KEY$10]; }); registerAfterHook(state, skill.id, ownerId, "移动牌", async (ctx) => { const wantCard = ctx.state.localVars[WANTCARD_KEY]; if (typeof wantCard !== "string") return; if (ctx.atom.to?.zone !== "弃牌堆") return; if (!ctx.state.zones.discardPile.includes(wantCard)) return; if (!ctx.state.cardMap[wantCard]) return; await applyAtom(ctx.state, { type: "移动牌", cardId: wantCard, from: { zone: "弃牌堆" }, to: { zone: "手牌", player: ownerId } }); }); return () => {}; } function onMount$167(_skill, api) { api.defineAction("respond", { label: "界奸雄", style: "default", prompt: { type: "confirm", title: "是否发动奸雄?", confirmLabel: "发动", cancelLabel: "不发动" } }); } var CHOOSE_RT$11, WANTCARD_KEY, CHOICE_KEY$10, 界奸雄_default; var init_界奸雄 = __esmMin((() => { init_apply(); init_skill(); CHOOSE_RT$11 = "界奸雄/choose"; WANTCARD_KEY = "奸雄/wantCard"; CHOICE_KEY$10 = "奸雄/choice"; 界奸雄_default = { createSkill: createSkill$181, onInit: onInit$181, onMount: onMount$167 }; })); //#endregion //#region src/engine/data/card-meta.ts /** 延时锦囊(乐不思蜀/兵粮寸断/闪电) */ function isDelayedTrick(c) { return c.type === "锦囊牌" && c.trickSubtype === "延时锦囊"; } /** 响应型卡牌(闪 / 无懈可击):只能在被指定时打出,不能主动使用 */ function isRespondOnly(c) { return c.name === "闪" || c.trickSubtype === "响应锦囊"; } var init_card_meta = __esmMin((() => { init_equipment(); init_registry$1(); new Set(装备牌列表.map((def) => def.name).filter((name) => hasSkillModule(name))); })); //#endregion //#region src/engine/skills/界好施.ts var 界好施_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$180, default: () => _skillModule$89, onInit: () => onInit$180, onMount: () => onMount$166 }); function createSkill$180(id, ownerId) { return { id, ownerId, name: DISPLAY_NAME$66, description: "摸牌阶段多摸两张,手牌>5时分半给最少者;给牌后直到下回合开始,成为杀/普通锦囊目标时其可给你一张牌" }; } /** 触发被动:询问受益者是否交给鲁肃一张手牌 */ async function triggerPassiveGive(state, owner, recipient) { delete state.localVars[PASSIVE_CARD_KEY]; await applyAtom(state, { type: "请求回应", requestType: PASSIVE_GIVE_RT, target: recipient, prompt: { type: "useCard", title: `好施:是否交给${state.players[owner].name}一张手牌?(可不打=不交)`, cardFilter: { filter: () => true, min: 1, max: 1 } }, timeout: 15 }); const cardId = state.localVars[PASSIVE_CARD_KEY]; delete state.localVars[PASSIVE_CARD_KEY]; if (cardId && state.players[recipient]?.hand.includes(cardId)) await applyAtom(state, { type: "给予", cardId, from: recipient, to: owner }); } function onInit$180(skill, state) { const ownerId = skill.ownerId; const unloaders = []; for (const pl of state.players) { const seat = pl.index; unloaders.push(registerAction(state, skill.id, seat, "respond", (st, _params) => { const slot = st.pendingSlots.get(seat); if (!slot) return "当前不需要回应"; const atom = slot.atom; if (atom["type"] !== "请求回应") return "当前不需要回应"; if (!atom["requestType"]?.startsWith("界好施/")) return "当前不是界好施询问"; return null; }, async (st, params) => { const slot = st.pendingSlots.get(seat); const rt = (slot?.atom)?.requestType; if (rt === CONFIRM_RT$44) st.localVars[CONFIRMED_KEY$35] = params.choice === true || params.confirmed === true; else if (rt === CHOOSE_TARGET_RT$3) { const t = params.targets?.[0] ?? (typeof params.target === "number" ? params.target : void 0); if (typeof t === "number") st.localVars[TARGET_KEY$27] = t; } else if (rt === GIVE_RT$3) { const ids = params.cardIds; const need = (slot?.atom)?.prompt?.cardFilter?.min; if (Array.isArray(ids) && typeof need === "number" && ids.length === need && new Set(ids).size === ids.length && ids.every((id) => st.players[seat]?.hand.includes(id))) st.localVars[GIVE_KEY$2] = ids; } else if (rt === PASSIVE_GIVE_RT) { const raw = params.cardIds ?? params.cardId; const ids = Array.isArray(raw) ? raw : typeof raw === "string" ? [raw] : void 0; if (ids && ids.length > 0) st.localVars[PASSIVE_CARD_KEY] = ids[0]; } })); } unloaders.push(registerBeforeHook(state, skill.id, ownerId, "摸牌", async (ctx) => { const atom = ctx.atom; if (atom.player !== ownerId) return; if (ctx.state.currentPlayerIndex !== ownerId) return; if (ctx.state.phase !== "摸牌") return; if (!ctx.state.players[ownerId]?.alive) return; if (usedThisTurn$3(ctx.state, ownerId, "界好施")) return; delete ctx.state.localVars[CONFIRMED_KEY$35]; await applyAtom(ctx.state, { type: "请求回应", requestType: CONFIRM_RT$44, target: ownerId, prompt: { type: "confirm", title: "是否发动好施?(额外摸两张牌)", confirmLabel: "发动", cancelLabel: "不发动" }, defaultChoice: false, timeout: 15 }); if (!ctx.state.localVars[CONFIRMED_KEY$35]) return; await markOncePerTurn(ctx.state, ownerId, "界好施"); ctx.state.localVars[ACTIVE_KEY$3] = true; const count = atom.count ?? 2; return { kind: "modify", atom: { ...ctx.atom, count: count + 2 } }; })); unloaders.push(registerAfterHook(state, skill.id, ownerId, "摸牌", async (ctx) => { if (ctx.atom.player !== ownerId) return; if (!ctx.state.localVars[ACTIVE_KEY$3]) return; delete ctx.state.localVars[ACTIVE_KEY$3]; const st = ctx.state; const self = st.players[ownerId]; if (!self?.alive) return; const handCount = self.hand.length; if (handCount <= 5) return; const giveCount = Math.floor(handCount / 2); let minCount = Infinity; const candidates = []; for (const p of st.players) { if (p.index === ownerId || !p.alive) continue; if (p.hand.length < minCount) { minCount = p.hand.length; candidates.length = 0; candidates.push(p.index); } else if (p.hand.length === minCount) candidates.push(p.index); } if (candidates.length === 0) return; let target; if (candidates.length === 1) target = candidates[0]; else { delete st.localVars[TARGET_KEY$27]; await applyAtom(st, { type: "请求回应", requestType: CHOOSE_TARGET_RT$3, target: ownerId, prompt: { type: "choosePlayer", title: "好施:选择手牌最少的角色(给予手牌)", min: 1, max: 1, filter: (_view, t) => candidates.includes(t) }, timeout: 15 }); const chosen = st.localVars[TARGET_KEY$27]; delete st.localVars[TARGET_KEY$27]; if (typeof chosen !== "number") return; target = chosen; } delete st.localVars[GIVE_KEY$2]; await applyAtom(st, { type: "请求回应", requestType: GIVE_RT$3, target: ownerId, prompt: { type: "useCard", title: `好施:选择 ${giveCount} 张牌交给 ${st.players[target].name}`, cardFilter: { filter: () => true, min: giveCount, max: giveCount } }, mandatory: true, timeout: 30 }); let giveCards = st.localVars[GIVE_KEY$2]; delete st.localVars[GIVE_KEY$2]; if (!giveCards || giveCards.length === 0) giveCards = st.players[ownerId]?.hand.slice(0, giveCount) ?? []; if (giveCards && giveCards.length > 0) { for (const cardId of giveCards) await applyAtom(st, { type: "给予", cardId, from: ownerId, to: target }); st.localVars[PASSIVE_ACTIVE_KEY] = true; st.localVars[RECIPIENT_KEY] = target; } })); unloaders.push(registerAfterHook(state, skill.id, ownerId, "回合开始", async (ctx) => { if (ctx.atom.player !== ownerId) return; delete ctx.state.localVars[PASSIVE_ACTIVE_KEY]; delete ctx.state.localVars[RECIPIENT_KEY]; })); unloaders.push(registerAfterHook(state, skill.id, ownerId, "成为目标", async (ctx) => { const atom = ctx.atom; if (atom.target !== ownerId) return; const st = ctx.state; if (!st.localVars[PASSIVE_ACTIVE_KEY]) return; const recipient = st.localVars[RECIPIENT_KEY]; if (typeof recipient !== "number") return; if (!st.players[recipient]?.alive) return; if (st.players[recipient].hand.length === 0) return; const cardId = atom.cardId; if (!cardId) return; if (!st.cardMap[cardId]?.name.includes("杀")) return; const processedKey = `${PASSIVE_PROCESSED_PREFIX}${cardId}`; if (st.localVars[processedKey]) return; st.localVars[processedKey] = true; await triggerPassiveGive(st, ownerId, recipient); })); unloaders.push(registerBeforeHook(state, skill.id, ownerId, "请求回应", async (ctx) => { const atom = ctx.atom; if (atom.requestType !== "无懈可击") return; if (atom.cancelTarget !== ownerId) return; const st = ctx.state; if (!st.localVars[PASSIVE_ACTIVE_KEY]) return; const recipient = st.localVars[RECIPIENT_KEY]; if (typeof recipient !== "number") return; if (!st.players[recipient]?.alive) return; if (st.players[recipient].hand.length === 0) return; const frame = topFrame(st); if (!frame) return; const cardId = frame.params?.cardId; if (!cardId) return; const card = st.cardMap[cardId]; if (!card) return; if (isDelayedTrick(card) || isRespondOnly(card)) return; const processedKey = `${PASSIVE_PROCESSED_PREFIX}${cardId}`; if (st.localVars[processedKey]) return; st.localVars[processedKey] = true; await triggerPassiveGive(st, ownerId, recipient); })); return () => unloaders.forEach((fn) => fn()); } function onMount$166(_skill, api) { api.defineAction("respond", { label: DISPLAY_NAME$66, style: "primary", prompt: { type: "confirm", title: DISPLAY_NAME$66, confirmLabel: "确认", cancelLabel: "取消" } }); return () => {}; } var CONFIRM_RT$44, CHOOSE_TARGET_RT$3, GIVE_RT$3, CONFIRMED_KEY$35, ACTIVE_KEY$3, TARGET_KEY$27, GIVE_KEY$2, PASSIVE_GIVE_RT, PASSIVE_CARD_KEY, PASSIVE_ACTIVE_KEY, RECIPIENT_KEY, PASSIVE_PROCESSED_PREFIX, DISPLAY_NAME$66, _skillModule$89; var init_界好施 = __esmMin((() => { init_apply(); init_frame(); init_once_per_turn(); init_card_meta(); init_skill(); CONFIRM_RT$44 = "界好施/confirm"; CHOOSE_TARGET_RT$3 = "界好施/target"; GIVE_RT$3 = "界好施/give"; CONFIRMED_KEY$35 = "界好施/confirmed"; ACTIVE_KEY$3 = "界好施/active"; TARGET_KEY$27 = "界好施/chosenTarget"; GIVE_KEY$2 = "界好施/giveCards"; PASSIVE_GIVE_RT = "界好施/passiveGive"; PASSIVE_CARD_KEY = "界好施/passiveCard"; PASSIVE_ACTIVE_KEY = "界好施/被动激活"; RECIPIENT_KEY = "界好施/受益者"; PASSIVE_PROCESSED_PREFIX = "界好施/被动已触发/"; DISPLAY_NAME$66 = "好施"; _skillModule$89 = { createSkill: createSkill$180, onInit: onInit$180, onMount: onMount$166 }; })); //#endregion //#region src/engine/skills/界完杀.ts var 界完杀_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$179, default: () => _skillModule$88, onInit: () => onInit$179 }); function createSkill$179(id, ownerId) { return { id, ownerId, name: DISPLAY_NAME$65, description: "锁定技,在你的回合内,只有你和处于濒死状态的角色才能使用【桃】;任意濒死结算中,除你和濒死者外其他角色的非锁定技失效", isLocked: true }; } /** 当前是否有任意存活玩家处于濒死(health<=0 且 alive) */ function anyoneDying(state) { return state.players.some((p) => p.alive && p.health <= 0); } /** * 若无人处于濒死,清理所有玩家身上的 '完杀/非锁定技失效' tag。 * 用于 击杀 / 回复体力 等濒死退出时点的防御性扫描。 * 直接 mutate player.tags(tag 是引擎内部状态,无 view 同步需求)。 */ function cleanupIfNoDying(state) { if (anyoneDying(state)) return; for (const p of state.players) { if (!p) continue; if (!p.tags.includes(SUPPRESSION_TAG$1)) continue; p.tags = p.tags.filter((t) => t !== SUPPRESSION_TAG$1); } } function onInit$179(skill, state) { const ownerId = skill.ownerId; const unloaders = []; unloaders.push(registerSuppressionProvider(state, (st, targetOwnerId, _skillId) => st.players[targetOwnerId]?.tags.includes(SUPPRESSION_TAG$1) === true)); unloaders.push(registerBeforeHook(state, skill.id, ownerId, "请求回应", async (ctx) => { const atom = ctx.atom; if (atom.requestType !== "桃/求桃") return; if (ctx.state.currentPlayerIndex !== ownerId) return; const asked = atom.target; if (typeof asked !== "number") return; if (asked === ownerId) return; const askedPlayer = ctx.state.players[asked]; if (askedPlayer?.alive && askedPlayer.health <= 0) return; return { kind: "cancel" }; })); unloaders.push(registerBeforeHook(state, skill.id, ownerId, "陷入濒死", async (ctx) => { if (ctx.state.currentPlayerIndex !== ownerId) return; if (typeof ctx.atom.target !== "number") return; for (let i = 0; i < ctx.state.players.length; i++) { if (i === ownerId) continue; const p = ctx.state.players[i]; if (!p?.alive) continue; if (p.health <= 0) continue; if (!p.tags.includes(SUPPRESSION_TAG$1)) p.tags.push(SUPPRESSION_TAG$1); } })); unloaders.push(registerAfterHook(state, skill.id, ownerId, "死亡后", async (_ctx) => { cleanupIfNoDying(state); })); unloaders.push(registerAfterHook(state, skill.id, ownerId, "回复体力", async (_ctx) => { cleanupIfNoDying(state); })); return () => { for (const u of unloaders) u(); }; } var DISPLAY_NAME$65, SUPPRESSION_TAG$1, _skillModule$88; var init_界完杀 = __esmMin((() => { init_skill(); init_skill_suppression(); DISPLAY_NAME$65 = "完杀"; SUPPRESSION_TAG$1 = "完杀/非锁定技失效"; _skillModule$88 = { createSkill: createSkill$179, onInit: onInit$179 }; })); //#endregion //#region src/engine/skills/界宗室.ts var 界宗室_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$178, default: () => _skillModule$87, onInit: () => onInit$178, onMount: () => onMount$165 }); function createSkill$178(id, ownerId) { return { id, ownerId, name: DISPLAY_NAME$64, description: "锁定技:手牌上限+X(X为全场势力数);其他角色对你造成伤害时,防止此伤害改为令其摸一张牌,每种势力限一次", isLocked: true }; } /** 计算全场存活玩家的不同势力数(X)。 */ function countFactions$1(state) { const factions = /* @__PURE__ */ new Set(); for (const p of state.players) { if (!p.alive) continue; if (p.faction) factions.add(p.faction); } return factions.size; } /** 检查某势力是否已用过本技防伤。 */ function factionUsed(state, ownerId, faction) { return state.players[ownerId]?.vars[USED_PREFIX + faction] === true; } /** 标记某势力已用过本技防伤。 */ function markFactionUsed(state, ownerId, faction) { const p = state.players[ownerId]; if (p) p.vars[USED_PREFIX + faction] = true; } function onInit$178(skill, state) { const ownerId = skill.ownerId; const unloadProvider = registerHandLimitProvider(state, ownerId, (st, player) => { if (player !== ownerId) return void 0; const p = st.players[player]; if (!p) return void 0; const bonus = getHandLimitBonus(st.turn.vars, player) ?? 0; return getHealthValue(p) + bonus + countFactions$1(st); }); const unloadHook = registerBeforeHook(state, skill.id, ownerId, "受到伤害时", async (ctx) => { const atom = ctx.atom; if (atom.target !== ownerId) return; if ((atom.amount ?? 0) <= 0) return; const source = atom.source; if (source === void 0 || source === ownerId) return; const self = ctx.state.players[ownerId]; const sourcePlayer = ctx.state.players[source]; if (!self?.alive) return; if (!sourcePlayer?.alive) return; const faction = sourcePlayer.faction; if (!faction) return; if (factionUsed(ctx.state, ownerId, faction)) return; markFactionUsed(ctx.state, ownerId, faction); await applyAtom(ctx.state, { type: "摸牌", player: source, count: 1 }); return { kind: "cancel" }; }); return () => { unloadProvider(); unloadHook(); }; } function onMount$165(_skill, _api) { return () => {}; } var DISPLAY_NAME$64, USED_PREFIX, _skillModule$87; var init_界宗室 = __esmMin((() => { init_types(); init_apply(); init_skill(); init_hand_limit(); init_vars_keys(); DISPLAY_NAME$64 = "宗室"; USED_PREFIX = "宗室/防伤/"; _skillModule$87 = { createSkill: createSkill$178, onInit: onInit$178, onMount: onMount$165 }; })); //#endregion //#region src/engine/flows/recast.ts /** 重铸一张牌:弃置此牌并摸一张。 */ async function recastCard(state, player, cardId) { await applyAtom(state, { type: "弃置", player, cardIds: [cardId], voluntary: true }); await applyAtom(state, { type: "摸牌", player, count: 1 }); } var init_recast = __esmMin((() => { init_apply(); })); //#endregion //#region src/engine/skills/界将驰.ts var 界将驰_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$177, default: () => _skillModule$86, onInit: () => onInit$177, onMount: () => onMount$164 }); function createSkill$177(id, ownerId) { return { id, ownerId, name: DISPLAY_NAME$63, description: "摸牌阶段结束时选择一项:①摸一张牌,本回合杀次数-1且杀不计入手牌上限;②重铸一张牌,本回合杀次数+1且杀无距离限制" }; } /** 当前 pending 的 requestType(类型安全读取) */ function currentRequestType$9(state, ownerId) { return (state.pendingSlots.get(ownerId)?.atom)?.requestType; } function onInit$177(skill, state) { const ownerId = skill.ownerId; const unloadBlocker = registerSlashBlocker(state, ownerId, (st, player) => st.turn.vars[CHOICE1_VAR] === player); const unloadProvider = registerSlashQuotaProvider(state, ownerId, (st, player) => st.turn.vars[CHOICE2_VAR] === player ? 2 : 0); const unloadRangeExemptor = registerAttackRangeExemptor(state, ownerId, (st, from, _to, _cardId) => st.turn.vars[CHOICE2_VAR] === from); const unloadHandLimit = registerHandLimitProvider(state, ownerId, (st, player) => { if (st.turn.vars[CHOICE1_VAR] !== player) return void 0; const p = st.players[player]; if (!p) return void 0; const slashCount = p.hand.filter((cid) => st.cardMap[cid]?.name === "杀").length; if (slashCount === 0) return void 0; const bonus = getHandLimitBonus(st.turn.vars, player) ?? 0; return getHealthValue(p) + bonus + slashCount; }); registerAction(state, skill.id, ownerId, "respond", (s) => { const rt = currentRequestType$9(s, ownerId); if (rt !== ACTIVATE_RT$1 && rt !== CHOOSE_RT$10 && rt !== PICK_RT$8) return "当前不是将驰询问"; return null; }, async (s, params) => { const rt = currentRequestType$9(s, ownerId); if (rt === ACTIVATE_RT$1) s.localVars[ACTIVATE_KEY] = params.choice === true; else if (rt === CHOOSE_RT$10) s.localVars[CHOICE_KEY$9] = params.choice === true; else if (rt === PICK_RT$8) { if (typeof params.cardId === "string") s.localVars[CARD_KEY$2] = params.cardId; } }); registerAfterHook(state, skill.id, ownerId, "阶段开始", async (ctx) => { const atom = ctx.atom; if (atom.type !== "阶段开始") return; if (atom.phase !== "摸牌") return; if (atom.player !== ownerId) return; ctx.state.localVars[NORMAL_KEY] = ownerId; }); registerBeforeHook(state, skill.id, ownerId, "阶段结束", async (ctx) => { const atom = ctx.atom; if (atom.type !== "阶段结束") return; if (atom.phase !== "摸牌") return; if (atom.player !== ownerId) return; if (ctx.state.localVars[NORMAL_KEY] !== ownerId) return; delete ctx.state.localVars[NORMAL_KEY]; const self = ctx.state.players[ownerId]; if (!self?.alive) return; delete ctx.state.localVars[ACTIVATE_KEY]; await applyAtom(ctx.state, { type: "请求回应", requestType: ACTIVATE_RT$1, target: ownerId, prompt: { type: "confirm", title: "将驰:是否发动?", confirmLabel: "发动", cancelLabel: "不发动" }, defaultChoice: false, timeout: 10 }); if (ctx.state.localVars[ACTIVATE_KEY] !== true) return; let option1 = true; if (self.hand.length > 0) { delete ctx.state.localVars[CHOICE_KEY$9]; await applyAtom(ctx.state, { type: "请求回应", requestType: CHOOSE_RT$10, target: ownerId, prompt: { type: "confirm", title: "将驰:选择一项 ①摸一张(杀次数-1,杀不计入手牌上限) ②重铸一张(杀次数+1,杀无距离限制)", confirmLabel: "①摸一张", cancelLabel: "②重铸一张" }, defaultChoice: true, timeout: 10 }); option1 = ctx.state.localVars[CHOICE_KEY$9] !== false; } if (option1) { await applyAtom(ctx.state, { type: "摸牌", player: ownerId, count: 1 }); ctx.state.turn.vars[CHOICE1_VAR] = ownerId; await applyAtom(ctx.state, { type: "回合用量", player: ownerId, key: slashBlockedKey("界将驰"), value: true }); return; } if (self.hand.length === 0) return; delete ctx.state.localVars[CARD_KEY$2]; await applyAtom(ctx.state, { type: "请求回应", requestType: PICK_RT$8, target: ownerId, prompt: { type: "useCard", title: "将驰:选择一张手牌重铸", cardFilter: { filter: () => true, min: 1, max: 1 } }, timeout: 10 }); const cardId = ctx.state.localVars[CARD_KEY$2]; delete ctx.state.localVars[CARD_KEY$2]; if (!cardId || !self.hand.includes(cardId)) return; await recastCard(ctx.state, ownerId, cardId); ctx.state.turn.vars[CHOICE2_VAR] = ownerId; await applyAtom(ctx.state, { type: "回合用量", player: ownerId, key: CHOICE2_VAR, value: true }); await applyAtom(ctx.state, { type: "回合用量", player: ownerId, key: slashExtraKey("界将驰"), value: 1 }); }); return () => { unloadBlocker(); unloadProvider(); unloadRangeExemptor(); unloadHandLimit(); }; } function onMount$164(_skill, _api) {} var DISPLAY_NAME$63, NORMAL_KEY, ACTIVATE_KEY, CHOICE_KEY$9, CARD_KEY$2, ACTIVATE_RT$1, CHOOSE_RT$10, PICK_RT$8, CHOICE1_VAR, CHOICE2_VAR, _skillModule$86; var init_界将驰 = __esmMin((() => { init_types(); init_apply(); init_recast(); init_skill(); init_slash_quota(); init_hand_limit(); init_distance(); init_vars_keys(); DISPLAY_NAME$63 = "将驰"; NORMAL_KEY = "将驰/normalDrawPhase"; ACTIVATE_KEY = "将驰/activateChoice"; CHOICE_KEY$9 = "将驰/optionChoice"; CARD_KEY$2 = "将驰/cardId"; ACTIVATE_RT$1 = "界将驰/activate"; CHOOSE_RT$10 = "界将驰/choose"; PICK_RT$8 = "界将驰/pick"; CHOICE1_VAR = "将驰/choice1"; CHOICE2_VAR = JIANGCHI_CHOICE2_VIEW_KEY; _skillModule$86 = { createSkill: createSkill$177, onInit: onInit$177, onMount: onMount$164 }; })); //#endregion //#region src/engine/skills/界屯田.ts var 界屯田_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$176, default: () => 界屯田_default, onInit: () => onInit$176, onMount: () => onMount$163 }); function createSkill$176(id, ownerId) { return { id, ownerId, name: "界屯田", description: "回合外失去牌或回合内弃置杀时判定,非红桃则置于武将牌上为田,每张田与其他角色距离 -1" }; } /** 数当前玩家的田数量 */ function tianCount(state, player) { return state.players[player].marks.filter((m) => m.id.startsWith(TIAN_PREFIX)).length; } /** 重新计算并写入距离修正 vars(每张田 = -1 距离 = 进攻修正 +1) */ function syncDistanceMod(state, player) { const count = tianCount(state, player); if (count > 0) state.players[player].vars[DISTANCE_ATTACK_MOD_KEY] = count; else delete state.players[player].vars[DISTANCE_ATTACK_MOD_KEY]; } function onInit$176(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "respond", (st, _params) => { const slot = st.pendingSlots.get(ownerId); if (!slot) return "当前不需要回应"; const atom = slot.atom; if (atom["type"] !== "请求回应") return "当前不需要回应"; if (atom["requestType"] !== CONFIRM_RT$43) return "当前不是屯田确认"; return null; }, async (st, params) => { st.localVars[CONFIRMED_KEY$34] = params.choice === true || params.confirmed === true; }); registerAfterHook(state, skill.id, ownerId, "判定牌生效后", async (ctx) => { const atom = ctx.atom; if (atom.player !== ownerId) return; if (atom.judgeType !== "屯田") return; if (!ctx.state.localVars[CONFIRMED_KEY$34]) return; const processing = frameCards(ctx.state); if (processing.length === 0) return; const judgeCardId = processing[processing.length - 1]; const judgeCard = ctx.state.cardMap[judgeCardId]; if (!judgeCard) return; ctx.state.localVars[JUDGE_SUIT_KEY$1] = judgeCard.suit; ctx.state.localVars[JUDGE_CARD_KEY$1] = judgeCardId; if (judgeCard.suit === "♥") return; await applyAtom(ctx.state, { type: "收取判定牌", player: ownerId, cardId: judgeCardId }); const newCount = tianCount(ctx.state, ownerId) + 1; const tianId = `${TIAN_PREFIX}${ctx.state.seq}`; await applyAtom(ctx.state, { type: "加标记", player: ownerId, mark: { id: tianId, scope: ownerId, payload: { cardId: judgeCardId } }, distanceVars: { attackMod: newCount } }); syncDistanceMod(ctx.state, ownerId); }); async function performTunTian(ctx, lossAtomType) { if (!ctx.state.players[ownerId]?.alive) return; if (ctx.state.zones.deck.length === 0) return; delete ctx.state.localVars[CONFIRMED_KEY$34]; await applyAtom(ctx.state, { type: "请求回应", requestType: CONFIRM_RT$43, target: ownerId, prompt: { type: "confirm", title: `是否发动屯田?(因${lossAtomType}失去牌,判定可能获得田)`, confirmLabel: "发动", cancelLabel: "不发动" }, defaultChoice: false, timeout: 10 }); if (!ctx.state.localVars[CONFIRMED_KEY$34]) return; delete ctx.state.localVars[JUDGE_SUIT_KEY$1]; delete ctx.state.localVars[JUDGE_CARD_KEY$1]; await runJudgeFlow(ctx.state, ownerId, "屯田"); delete ctx.state.localVars[JUDGE_SUIT_KEY$1]; delete ctx.state.localVars[JUDGE_CARD_KEY$1]; } /** 回合外触发(标版行为):currentPlayerIndex !== ownerId */ async function maybeTriggerTunTian(ctx, lossAtomType) { if (ctx.state.currentPlayerIndex === ownerId) return; await performTunTian(ctx, lossAtomType); } registerAfterHook(state, skill.id, ownerId, "获得", async (ctx) => { const atom = ctx.atom; if (atom.type !== "获得") return; if (atom.from !== ownerId) return; if (atom.player === ownerId) return; await maybeTriggerTunTian(ctx, "获得"); }); registerAfterHook(state, skill.id, ownerId, "移动牌", async (ctx) => { const atom = ctx.atom; if (atom.type !== "移动牌") return; if (atom.from.zone !== "手牌") return; if (atom.from.player !== ownerId) return; if (atom.to.zone === "手牌" && atom.to.player === ownerId) return; await maybeTriggerTunTian(ctx, "移动牌"); }); registerAfterHook(state, skill.id, ownerId, "给予", async (ctx) => { const atom = ctx.atom; if (atom.type !== "给予") return; if (atom.from !== ownerId) return; if (atom.to === ownerId) return; await maybeTriggerTunTian(ctx, "给予"); }); registerAfterHook(state, skill.id, ownerId, "弃置", async (ctx) => { const atom = ctx.atom; if (atom.type !== "弃置") return; if (atom.player !== ownerId) return; if (ctx.state.currentPlayerIndex === ownerId) { if (!(atom.cardIds ?? []).some((id) => ctx.state.cardMap[id]?.name === "杀")) return; await performTunTian(ctx, "回合内弃置杀"); } else await maybeTriggerTunTian(ctx, "弃置"); }); return () => {}; } function onMount$163(_skill, api) { api.defineAction("respond", { label: "界屯田", style: "primary", prompt: { type: "confirm", title: "是否发动屯田?", confirmLabel: "发动", cancelLabel: "不发动" } }); } var CONFIRM_RT$43, CONFIRMED_KEY$34, JUDGE_SUIT_KEY$1, JUDGE_CARD_KEY$1, TIAN_PREFIX, 界屯田_default; var init_界屯田 = __esmMin((() => { init_apply(); init_frame(); init_judge(); init_skill(); init_vars_keys(); CONFIRM_RT$43 = "界屯田/confirm"; CONFIRMED_KEY$34 = "屯田/confirmed"; JUDGE_SUIT_KEY$1 = "屯田/judgeSuit"; JUDGE_CARD_KEY$1 = "屯田/judgeCardId"; TIAN_PREFIX = "屯田/田:"; 界屯田_default = { createSkill: createSkill$176, onInit: onInit$176, onMount: onMount$163 }; })); //#endregion //#region src/engine/skills/界崩坏.ts var 界崩坏_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$175, default: () => 界崩坏_default, onInit: () => onInit$175, onMount: () => onMount$162 }); function createSkill$175(id, ownerId) { return { id, ownerId, name: "崩坏", description: "锁定技:回合结束阶段,若你的体力不是全场最少的,须减1点体力或1点体力上限;界酒池酒杀造成伤害后本回合失效", isLocked: true }; } function onInit$175(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "respond", (s, _params) => { const slot = s.pendingSlots.get(ownerId); if (slot?.atom.type !== "请求回应") return "当前不需要回应"; if (slot.atom.requestType !== CHOOSE_RT$9) return "当前不是崩坏选择"; return null; }, async (s, params) => { s.localVars[CHOICE_KEY$8] = params.choice === true ? "maxHealth" : "health"; }); registerAfterHook(state, skill.id, ownerId, "阶段开始", async (ctx) => { const atom = ctx.atom; if (atom.type !== "阶段开始") return; if (atom.player !== ownerId) return; if (atom.phase !== "回合结束") return; const self = ctx.state.players[ownerId]; if (!self?.alive) return; if (ctx.state.turn.vars[DISABLE_VAR] === true) return; const minHealth = ctx.state.players.filter((p) => p.alive).reduce((min, p) => Math.min(min, getHealthValue(p)), Number.POSITIVE_INFINITY); if (getHealthValue(self) <= minHealth) return; delete ctx.state.localVars[CHOICE_KEY$8]; await applyAtom(ctx.state, { type: "请求回应", requestType: CHOOSE_RT$9, target: ownerId, prompt: { type: "confirm", title: "崩坏:减1点体力上限,还是减1点体力?", confirmLabel: "减体力上限", cancelLabel: "减体力" }, defaultChoice: false, timeout: 15 }); const choice = ctx.state.localVars[CHOICE_KEY$8]; delete ctx.state.localVars[CHOICE_KEY$8]; if (choice === "maxHealth") await applyAtom(ctx.state, { type: "设上限", player: ownerId, amount: self.maxHealth - 1 }); else await applyAtom(ctx.state, { type: "失去体力", target: ownerId, amount: 1 }); }); return () => {}; } function onMount$162(_skill, api) { api.defineAction("respond", { label: "崩坏", style: "default", prompt: { type: "confirm", title: "崩坏:减1点体力上限,还是减1点体力?", confirmLabel: "减体力上限", cancelLabel: "减体力" } }); } var CHOOSE_RT$9, CHOICE_KEY$8, DISABLE_VAR, 界崩坏_default; var init_界崩坏 = __esmMin((() => { init_types(); init_apply(); init_skill(); CHOOSE_RT$9 = "界崩坏/choose"; CHOICE_KEY$8 = "崩坏/choice"; DISABLE_VAR = "崩坏/disabled"; 界崩坏_default = { createSkill: createSkill$175, onInit: onInit$175, onMount: onMount$162 }; })); //#endregion //#region src/engine/skills/界巧变.ts var 界巧变_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$174, default: () => 界巧变_default, onInit: () => onInit$174, onMount: () => onMount$161 }); function createSkill$174(id, ownerId) { return { id, ownerId, name: "界巧变", description: "游戏开始获得2枚\"变\"标记;弃一张牌或移除1枚\"变\"跳过阶段(摸牌:至多两名角色各获得一张手牌;出牌:移动场上的一张牌);结束阶段手牌数与历史均不相等则获得1枚\"变\"" }; } function bianCount(state, player) { return state.players[player].marks.filter((m) => m.id.startsWith(BIAN_PREFIX)).length; } async function addBian(state, player) { await applyAtom(state, { type: "加标记", player, mark: { id: `${BIAN_PREFIX}${state.seq}`, scope: player } }); } async function removeBian(state, player) { const target = state.players[player].marks.find((m) => m.id.startsWith(BIAN_PREFIX)); if (!target) return; await applyAtom(state, { type: "去标记", player, markId: target.id }); } function getHistory(state, player) { const v = state.players[player].vars[HISTORY_KEY]; return Array.isArray(v) ? v.filter((n) => typeof n === "number") : []; } function appendHistory(state, player, handCount) { const history = getHistory(state, player); state.players[player].vars[HISTORY_KEY] = [...history, handCount]; } /** 取玩家"场上"的牌(手牌+装备+判定区)——用于出牌阶段移动的源牌选择 */ function fieldCardIds$1(state, player) { const p = state.players[player]; if (!p) return []; const hand = [...p.hand]; const equip = Object.values(p.equipment).filter((id) => !!id); const judge = p.pendingTricks.map((t) => t.card.id); return [ ...hand, ...equip, ...judge ]; } function onInit$174(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "respond", (st, _params) => { const slot = st.pendingSlots.get(ownerId); if (!slot) return "当前不需要回应"; const atom = slot.atom; if (atom["type"] !== "请求回应") return "当前不需要回应"; const rt = atom["requestType"]; if (![ CONFIRM_RT$42, MODE_RT$1, DISCARD_RT$15, STEAL_TARGETS_RT, MOVE_SOURCE_PLAYER_RT, MOVE_SOURCE_CARD_RT, MOVE_DEST_PLAYER_RT ].includes(rt)) return "当前不是界巧变询问"; return null; }, async (st, params) => { const rt = (st.pendingSlots.get(ownerId)?.atom)?.requestType; if (rt === CONFIRM_RT$42) { st.localVars[CONFIRMED_KEY$33] = params.choice === true; return; } if (rt === MODE_RT$1) { st.localVars[MODE_KEY$1] = params.choice === true ? "变" : "discard"; return; } if (rt === DISCARD_RT$15) { const cardIds = params.cardIds; const single = params.cardId; const id = (Array.isArray(cardIds) && cardIds.length > 0 ? cardIds[0] : void 0) ?? (typeof single === "string" ? single : void 0); if (id) st.localVars[DISCARD_KEY$11] = id; return; } if (rt === STEAL_TARGETS_RT) { const targets = params.targets; if (Array.isArray(targets)) st.localVars[STEAL_TARGETS_KEY] = targets; return; } if (rt === MOVE_SOURCE_PLAYER_RT) { const t = params.targets?.[0] ?? (typeof params.target === "number" ? params.target : void 0); if (typeof t === "number") st.localVars[MOVE_SOURCE_PLAYER_KEY] = t; return; } if (rt === MOVE_SOURCE_CARD_RT) { const cardIds = params.cardIds; const single = params.cardId; const id = (Array.isArray(cardIds) && cardIds.length > 0 ? cardIds[0] : void 0) ?? (typeof single === "string" ? single : void 0); if (id) st.localVars[MOVE_SOURCE_CARD_KEY] = id; return; } if (rt === MOVE_DEST_PLAYER_RT) { const t = params.targets?.[0] ?? (typeof params.target === "number" ? params.target : void 0); if (typeof t === "number") st.localVars[MOVE_DEST_PLAYER_KEY] = t; return; } }); registerAfterHook(state, skill.id, ownerId, "回合开始", async (ctx) => { const st = ctx.state; if (!st.players[ownerId]?.alive) return; if (st.localVars[INIT_KEY$2(ownerId)]) return; st.localVars[INIT_KEY$2(ownerId)] = true; await addBian(st, ownerId); await addBian(st, ownerId); }); registerBeforeHook(state, skill.id, ownerId, "阶段开始", async (ctx) => { const atom = ctx.atom; if (atom.type !== "阶段开始") return; if (atom.player !== ownerId) return; if (ctx.state.currentPlayerIndex !== ownerId) return; const phase = atom.phase; if (!SKIPPABLE_PHASES.includes(phase)) return; const self = ctx.state.players[ownerId]; if (!self?.alive) return; if (hasBlockingPending(ctx.state)) return; const canDiscard = self.hand.length > 0; const canUseBian = bianCount(ctx.state, ownerId) > 0; if (!canDiscard && !canUseBian) return; delete ctx.state.localVars[CONFIRMED_KEY$33]; await applyAtom(ctx.state, { type: "请求回应", requestType: CONFIRM_RT$42, target: ownerId, prompt: { type: "confirm", title: `是否发动界巧变?(跳过${phase}阶段)`, confirmLabel: "发动", cancelLabel: "不发动" }, defaultChoice: false, timeout: 10 }); if (!ctx.state.localVars[CONFIRMED_KEY$33]) return; let mode; if (canDiscard && canUseBian) { delete ctx.state.localVars[MODE_KEY$1]; await applyAtom(ctx.state, { type: "请求回应", requestType: MODE_RT$1, target: ownerId, prompt: { type: "confirm", title: `界巧变:以何种方式跳过${phase}阶段?`, description: "确认 = 移除1枚\"变\"标记;取消 = 弃置一张手牌", confirmLabel: "移除1枚变", cancelLabel: "弃置手牌" }, defaultChoice: false, timeout: 10 }); mode = ctx.state.localVars[MODE_KEY$1] === "变" ? "变" : "discard"; delete ctx.state.localVars[MODE_KEY$1]; } else if (canUseBian) mode = "变"; else mode = "discard"; let discardCardId; if (mode === "discard") { delete ctx.state.localVars[DISCARD_KEY$11]; await applyAtom(ctx.state, { type: "请求回应", requestType: DISCARD_RT$15, target: ownerId, prompt: { type: "distribute", mode: "select", title: `界巧变:选择一张手牌弃置以跳过${phase}阶段`, source: "hand", minTotal: 1, maxTotal: 1 }, timeout: 15 }); discardCardId = ctx.state.localVars[DISCARD_KEY$11]; delete ctx.state.localVars[DISCARD_KEY$11]; if (!discardCardId || !self.hand.includes(discardCardId)) return; } await pushFrame(ctx.state, "界巧变", ownerId, { phase, mode, discardCardId: discardCardId ?? null }); try { if (mode === "discard" && discardCardId) await applyAtom(ctx.state, { type: "弃置", player: ownerId, cardIds: [discardCardId], voluntary: true }); else await removeBian(ctx.state, ownerId); if (phase === "摸牌") { const candidates = ctx.state.players.filter((p) => p.alive && p.index !== ownerId && p.hand.length > 0); if (candidates.length > 0) { delete ctx.state.localVars[STEAL_TARGETS_KEY]; await applyAtom(ctx.state, { type: "请求回应", requestType: STEAL_TARGETS_RT, target: ownerId, prompt: { type: "choosePlayer", title: "界巧变:选择至多两名其他角色(各获得其一张手牌)", min: 0, max: Math.min(2, candidates.length), filter: (_view, t) => t !== ownerId && ctx.state.players[t]?.alive === true && (ctx.state.players[t]?.hand.length ?? 0) > 0 }, timeout: 20 }); const targets = ctx.state.localVars[STEAL_TARGETS_KEY] ?? []; delete ctx.state.localVars[STEAL_TARGETS_KEY]; for (const t of targets.slice(0, 2)) { const target = ctx.state.players[t]; if (!target?.alive || target.hand.length === 0) continue; const rng = createRng(ctx.state.rngSeed); const idx = rng.nextInt(target.hand.length); ctx.state.rngSeed = rng.getState(); const cardId = target.hand[idx]; await applyAtom(ctx.state, { type: "获得", player: ownerId, cardId, from: t }); } } } else if (phase === "出牌") { if (ctx.state.players.filter((p) => p.alive && fieldCardIds$1(ctx.state, p.index).length > 0).length > 0) { delete ctx.state.localVars[MOVE_SOURCE_PLAYER_KEY]; await applyAtom(ctx.state, { type: "请求回应", requestType: MOVE_SOURCE_PLAYER_RT, target: ownerId, prompt: { type: "choosePlayer", title: "界巧变:选择源玩家(从其场上选一张牌移动)", min: 1, max: 1, filter: (_view, t) => ctx.state.players[t]?.alive === true && fieldCardIds$1(ctx.state, t).length > 0 }, timeout: 15 }); const srcPlayer = ctx.state.localVars[MOVE_SOURCE_PLAYER_KEY]; delete ctx.state.localVars[MOVE_SOURCE_PLAYER_KEY]; if (typeof srcPlayer !== "number") {} else { const srcCards = fieldCardIds$1(ctx.state, srcPlayer); delete ctx.state.localVars[MOVE_SOURCE_CARD_KEY]; await applyAtom(ctx.state, { type: "请求回应", requestType: MOVE_SOURCE_CARD_RT, target: ownerId, prompt: { type: "distribute", mode: "select", title: `界巧变:选择 ${ctx.state.players[srcPlayer]?.name ?? `P${srcPlayer}`} 场上的一张牌移动`, cardIds: srcCards, minTotal: 1, maxTotal: 1 }, timeout: 15 }); const srcCardId = ctx.state.localVars[MOVE_SOURCE_CARD_KEY]; delete ctx.state.localVars[MOVE_SOURCE_CARD_KEY]; if (typeof srcCardId === "string" && srcCards.includes(srcCardId)) { delete ctx.state.localVars[MOVE_DEST_PLAYER_KEY]; await applyAtom(ctx.state, { type: "请求回应", requestType: MOVE_DEST_PLAYER_RT, target: ownerId, prompt: { type: "choosePlayer", title: "界巧变:选择目标玩家(将牌移到其场上)", min: 1, max: 1, filter: (_view, t) => ctx.state.players[t]?.alive === true }, timeout: 15 }); const destPlayer = ctx.state.localVars[MOVE_DEST_PLAYER_KEY]; delete ctx.state.localVars[MOVE_DEST_PLAYER_KEY]; if (typeof destPlayer === "number") await moveFieldCard$1(ctx.state, srcPlayer, srcCardId, destPlayer); } } } } } finally { await popFrame(ctx.state); } return skipPhase(ctx.state, { player: ownerId, phase }); }); registerAfterHook(state, skill.id, ownerId, "阶段开始", async (ctx) => { const atom = ctx.atom; if (atom.type !== "阶段开始") return; if (atom.player !== ownerId) return; if (atom.phase !== "回合结束") return; const st = ctx.state; const self = st.players[ownerId]; if (!self?.alive) return; const current = self.hand.length; if (getHistory(st, ownerId).includes(current)) return; await addBian(st, ownerId); appendHistory(st, ownerId, current); }); return () => {}; } /** * 把 srcPlayer 场上的 srcCardId 移动到 destPlayer 场上。 * 自动按 srcCardId 所在区域 + 目标位置选择合适的转移方式: * - 装备牌且目标装备槽空闲 → 直接移到目标装备区(通过 移动牌 + 装备 atom) * - 否则统一移到目标手牌(包括判定区牌移回手牌) */ async function moveFieldCard$1(state, srcPlayer, srcCardId, destPlayer) { if (srcPlayer === destPlayer) return; const srcP = state.players[srcPlayer]; if (!srcP) return; if (!state.cardMap[srcCardId]) return; const judgeTrick = srcP.pendingTricks.find((t) => t.card.id === srcCardId); if (judgeTrick) { await applyAtom(state, { type: "移除延时锦囊", player: srcPlayer, trickName: judgeTrick.name }); await applyAtom(state, { type: "获得", player: destPlayer, cardId: srcCardId, from: srcPlayer }); return; } await applyAtom(state, { type: "获得", player: destPlayer, cardId: srcCardId, from: srcPlayer }); } function onMount$161(_skill, api) { api.defineAction("respond", { label: "界巧变", style: "primary", prompt: { type: "confirm", title: "是否发动界巧变?", confirmLabel: "发动", cancelLabel: "不发动" } }); } var CONFIRM_RT$42, MODE_RT$1, DISCARD_RT$15, STEAL_TARGETS_RT, MOVE_SOURCE_PLAYER_RT, MOVE_SOURCE_CARD_RT, MOVE_DEST_PLAYER_RT, CONFIRMED_KEY$33, MODE_KEY$1, DISCARD_KEY$11, STEAL_TARGETS_KEY, MOVE_SOURCE_PLAYER_KEY, MOVE_SOURCE_CARD_KEY, MOVE_DEST_PLAYER_KEY, HISTORY_KEY, BIAN_PREFIX, INIT_KEY$2, SKIPPABLE_PHASES, 界巧变_default; var init_界巧变 = __esmMin((() => { init_apply(); init_frame(); init_skill(); init_skip_phase(); init_rng(); CONFIRM_RT$42 = "界巧变/confirm"; MODE_RT$1 = "界巧变/mode"; DISCARD_RT$15 = "界巧变/discard"; STEAL_TARGETS_RT = "界巧变/steal-targets"; MOVE_SOURCE_PLAYER_RT = "界巧变/move-source-player"; MOVE_SOURCE_CARD_RT = "界巧变/move-source-card"; MOVE_DEST_PLAYER_RT = "界巧变/move-dest-player"; CONFIRMED_KEY$33 = "界巧变/confirmed"; MODE_KEY$1 = "界巧变/mode"; DISCARD_KEY$11 = "界巧变/discardCardId"; STEAL_TARGETS_KEY = "界巧变/stealTargets"; MOVE_SOURCE_PLAYER_KEY = "界巧变/moveSourcePlayer"; MOVE_SOURCE_CARD_KEY = "界巧变/moveSourceCardId"; MOVE_DEST_PLAYER_KEY = "界巧变/moveDestPlayer"; HISTORY_KEY = "界巧变/历史手牌数"; BIAN_PREFIX = "界巧变/变:"; INIT_KEY$2 = (ownerId) => `界巧变/init/${ownerId}`; SKIPPABLE_PHASES = [ "判定", "摸牌", "出牌", "弃牌" ]; 界巧变_default = { createSkill: createSkill$174, onInit: onInit$174, onMount: onMount$161 }; })); //#endregion //#region src/engine/skills/界巧说.ts var 界巧说_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$173, default: () => _skillModule$85, onInit: () => onInit$173, onMount: () => onMount$160 }); /** 拼点牌点数:A=1, 2-10=面值, J=11, Q=12, K=13。 */ function rankValue$6(rank) { if (rank === "A") return 1; if (rank === "J") return 11; if (rank === "Q") return 12; if (rank === "K") return 13; const n = parseInt(rank, 10); return Number.isFinite(n) ? n : 0; } function createSkill$173(id, ownerId) { return { id, ownerId, name: DISPLAY_NAME$62, description: "出牌阶段限一次拼点:赢则本回合下一张牌可多/少指定一个目标;没赢则本回合不能使用锦囊牌" }; } function onInit$173(skill, state) { const ownerId = skill.ownerId; const unloadBlocker = registerTrickBlocker(state, ownerId, (st, player) => st.turn.vars[LOST_VAR$1] === player); const unloadTargetProvider = registerSlashTargetProvider(state, ownerId, (st, player) => st.turn.vars[WIN_NEXT_VAR] === player ? 2 : 0); registerAction(state, skill.id, ownerId, "use", (st, params) => { if (st.currentPlayerIndex !== ownerId) return "不是你的回合"; if (st.phase !== "出牌") return "只能在出牌阶段发动"; if (usedThisTurn$3(st, ownerId, SKILL_ID$44)) return "本回合已使用过巧说"; const self = st.players[ownerId]; if (!self?.alive) return "玩家不存在或已死亡"; const cardId = params.cardId; if (typeof cardId !== "string") return "需要选择一张拼点牌"; if (!self.hand.includes(cardId)) return "拼点牌不在手牌中"; const target = params.target; if (typeof target !== "number") return "需要选择拼点目标"; if (target === ownerId) return "不能与自己拼点"; const targetPlayer = st.players[target]; if (!targetPlayer?.alive) return "目标不存在或已死亡"; if (targetPlayer.hand.length === 0) return "目标没有手牌,无法拼点"; return null; }, async (st, params) => { const from = ownerId; const initiatorCardId = params.cardId; const target = params.target; await markOncePerTurn(st, from, SKILL_ID$44); await pushFrame(st, SKILL_ID$44, from, { ...params }); const initiatorCard = st.cardMap[initiatorCardId]; const initiatorValue = initiatorCard ? rankValue$6(initiatorCard.rank) : 0; delete st.localVars[TARGET_CARD_KEY$2]; await applyAtom(st, { type: "请求回应", requestType: PD_RT$1, target, prompt: { type: "useCard", title: `巧说:与 ${st.players[from].name} 拼点,请出一张手牌`, cardFilter: { filter: () => true, min: 1, max: 1 } }, timeout: 30 }); const targetCardId = st.localVars[TARGET_CARD_KEY$2]; delete st.localVars[TARGET_CARD_KEY$2]; let win; if (targetCardId && st.players[target].hand.includes(targetCardId)) win = await runRankCompareFlow(st, from, target, initiatorCardId, targetCardId) === "赢"; else { await applyAtom(st, { type: "移动牌", cardId: initiatorCardId, from: { zone: "手牌", player: from }, to: { zone: "弃牌堆" } }); win = initiatorValue > 0; } if (win) { st.turn.vars[WIN_NEXT_VAR] = from; await applyAtom(st, { type: "回合用量", player: from, key: SLASH_TARGET_TU, value: 1 }); } else { st.turn.vars[LOST_VAR$1] = from; await applyAtom(st, { type: "回合用量", player: from, key: LOST_VAR$1, value: true }); } await popFrame(st); }); for (const p of state.players) { const pid = p.index; registerAction(state, skill.id, pid, "respond", (st, params) => { const slot = st.pendingSlots.get(pid); if (!slot) return "当前不需要回应"; const atom = slot.atom; if (atom.type !== "请求回应") return "当前不需要回应"; if (atom.requestType !== PD_RT$1) return "当前不是巧说回应"; if (atom.target !== pid) return "不是问你的"; const cardId = params.cardId; if (typeof cardId !== "string") return "请选择一张拼点牌"; if (!st.players[pid].hand.includes(cardId)) return "拼点牌不在手牌中"; return null; }, async (st, params) => { const slot = st.pendingSlots.get(pid); if (!slot) return; const atom = slot.atom; if (atom.type !== "请求回应" || atom.requestType !== PD_RT$1) return; st.localVars[TARGET_CARD_KEY$2] = params.cardId; }); } registerAfterHook(state, skill.id, ownerId, "移动牌", async (ctx) => { const atom = ctx.atom; if (atom.type !== "移动牌") return; if (atom.from.zone !== "手牌") return; if (atom.from.player !== ownerId) return; if (atom.to.zone !== "处理区") return; if (ctx.state.turn.vars[WIN_NEXT_VAR] === ownerId) { delete ctx.state.turn.vars[WIN_NEXT_VAR]; await applyAtom(ctx.state, { type: "回合用量", player: ownerId, key: SLASH_TARGET_TU, value: false }); } }); return () => { unloadBlocker(); unloadTargetProvider(); }; } function onMount$160(skill, api) { api.defineAction("use", { label: DISPLAY_NAME$62, style: "primary", prompt: { type: "useCardAndTarget", title: "巧说:选择一张拼点牌和一名目标", cardFilter: { filter: () => true, min: 1, max: 1 }, targetFilter: { min: 1, max: 1, filter: (view, t) => { if (t === view.currentPlayerIndex) return false; const tp = view.players[t]; if (!tp) return false; return tp.alive !== false && (tp.handCount ?? 0) > 0; } } }, activeWhen: (ctx) => activeUnlessUsedThisTurn(SKILL_ID$44)(ctx) && (ctx.view.players[ctx.perspectiveIdx]?.hand?.length ?? 0) > 0 }); api.defineAction("respond", { label: DISPLAY_NAME$62, style: "primary", prompt: { type: "useCard", title: "巧说:请出一张拼点牌", cardFilter: { filter: () => true, min: 1, max: 1 } } }); } var SKILL_ID$44, DISPLAY_NAME$62, TARGET_CARD_KEY$2, PD_RT$1, WIN_NEXT_VAR, LOST_VAR$1, SLASH_TARGET_TU, _skillModule$85; var init_界巧说 = __esmMin((() => { init_apply(); init_frame(); init_rank(); init_once_per_turn(); init_skill(); init_trick_quota(); init_slash_target(); init_vars_keys(); SKILL_ID$44 = "界巧说"; DISPLAY_NAME$62 = "巧说"; TARGET_CARD_KEY$2 = `${SKILL_ID$44}/targetCard`; PD_RT$1 = `${SKILL_ID$44}/拼点`; WIN_NEXT_VAR = "巧说/winNext"; LOST_VAR$1 = "巧说/lost"; SLASH_TARGET_TU = slashTargetKey("界巧说"); _skillModule$85 = { createSkill: createSkill$173, onInit: onInit$173, onMount: onMount$160 }; })); //#endregion //#region src/engine/skills/界帷幕.ts var 界帷幕_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$172, default: () => _skillModule$84, onInit: () => onInit$172 }); function createSkill$172(id, ownerId) { return { id, ownerId, name: DISPLAY_NAME$61, description: "锁定技,你不能成为黑色锦囊牌的目标;你防止回合内受到的伤害并摸所防止伤害值两倍数量的牌", isLocked: true }; } /** 判定一张卡是否为黑色锦囊牌 */ function isBlackTrick(card) { return !!card && card.type === "锦囊牌" && card.color === "黑"; } /** 从顶帧 params 取锦囊卡 id(各锦囊 use execute pushFrame 时 params 携带 cardId) */ function frameTrickCardId(state) { const cardId = topFrame(state)?.params?.cardId; return typeof cardId === "string" ? cardId : void 0; } /** 顶帧锦囊卡是否为黑色锦囊(获得/弃置/设横置 hook 用) */ function frameIsBlackTrick(state) { const cardId = frameTrickCardId(state); return cardId !== void 0 && isBlackTrick(state.cardMap[cardId]); } function onInit$172(skill, state) { const ownerId = skill.ownerId; const unloaders = []; unloaders.push(registerBeforeHook(state, skill.id, ownerId, "受到伤害时", async (ctx) => { const atom = ctx.atom; if (atom.target !== ownerId) return; if (ctx.state.currentPlayerIndex !== ownerId) return; const amount = typeof atom.amount === "number" ? atom.amount : 0; if (amount <= 0) return; await applyAtom(ctx.state, { type: "摸牌", player: ownerId, count: amount * 2 }); return { kind: "cancel" }; })); unloaders.push(registerBeforeHook(state, skill.id, ownerId, "成为目标", async (ctx) => { const atom = ctx.atom; if (atom.target !== ownerId) return; if (!atom.cardId) return; if (!isBlackTrick(ctx.state.cardMap[atom.cardId])) return; return { kind: "cancel" }; })); unloaders.push(registerBeforeHook(state, skill.id, ownerId, "获得", async (ctx) => { const atom = ctx.atom; if (atom.from !== ownerId) return; if (atom.player === ownerId) return; if (!frameIsBlackTrick(ctx.state)) return; return { kind: "cancel" }; })); unloaders.push(registerBeforeHook(state, skill.id, ownerId, "弃置", async (ctx) => { if (ctx.atom.player !== ownerId) return; if (!frameIsBlackTrick(ctx.state)) return; return { kind: "cancel" }; })); unloaders.push(registerBeforeHook(state, skill.id, ownerId, "设横置", async (ctx) => { if (ctx.atom.player !== ownerId) return; if (!frameIsBlackTrick(ctx.state)) return; return { kind: "cancel" }; })); unloaders.push(registerBeforeHook(state, skill.id, ownerId, "受到伤害时", async (ctx) => { const atom = ctx.atom; if (atom.target !== ownerId) return; if (!atom.cardId) return; if (!isBlackTrick(ctx.state.cardMap[atom.cardId])) return; return { kind: "cancel" }; })); return () => { for (const u of unloaders) u(); }; } var DISPLAY_NAME$61, _skillModule$84; var init_界帷幕 = __esmMin((() => { init_apply(); init_frame(); init_skill(); DISPLAY_NAME$61 = "帷幕"; _skillModule$84 = { createSkill: createSkill$172, onInit: onInit$172 }; })); //#endregion //#region src/engine/skills/界弓骑.ts var 界弓骑_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$171, default: () => _skillModule$83, onInit: () => onInit$171, onMount: () => onMount$159 }); function createSkill$171(id, ownerId) { return { id, ownerId, name: DISPLAY_NAME$60, description: "出牌阶段限一次,弃一张牌使本回合攻击范围无限,且同花色杀无次数限制;弃装备可再弃一名其他角色一张牌" }; } /** 判断玩家本回合是否已发动过弓骑 */ function usedThisTurn$2(state, ownerId) { return state.players[ownerId]?.vars[USED_KEY$11] === true; } /** 玩家区域(手牌+装备)是否有牌可被弃 */ function hasDiscardableCards$1(player) { if (!player) return false; if (player.hand.length > 0) return true; return Object.values(player.equipment).some((id) => typeof id === "string"); } /** 是否存在可被弃牌的其他角色(存活、非自己、有牌) */ function hasValidTargets$1(state, ownerId) { return state.players.some((p) => p.alive && p.index !== ownerId && hasDiscardableCards$1(p)); } function onInit$171(skill, state) { const ownerId = skill.ownerId; const unloadRangeExemptor = registerAttackRangeExemptor(state, ownerId, (st, from, _to, _cardId) => st.turn.vars[ACTIVE_VAR$2] === from); const unloadSlashExemptor = registerSlashExemptor(state, ownerId, (st, player, cardId) => { if (st.turn.vars[ACTIVE_VAR$2] !== player) return false; if (!cardId) return false; const suit = st.turn.vars[SUIT_VAR]; if (typeof suit !== "string") return false; return st.cardMap[cardId]?.suit === suit; }); registerAction(state, skill.id, ownerId, "use", (st, params) => { if (st.currentPlayerIndex !== ownerId) return "不是你的回合"; if (st.phase !== "出牌") return "只能在出牌阶段发动"; if (hasBlockingPending(st)) return "当前有未完成的询问"; if (usedThisTurn$2(st, ownerId)) return "本回合已发动过弓骑"; const self = st.players[ownerId]; if (!self?.alive) return "玩家不存在或已死亡"; const cardId = params.cardId; if (typeof cardId !== "string") return "需要选择一张要弃置的手牌"; if (!self.hand.includes(cardId)) return "牌不在手牌中"; return null; }, async (st, params) => { const from = ownerId; const cardId = params.cardId; const self = st.players[from]; const discardedCard = st.cardMap[cardId]; st.players[from].vars[USED_KEY$11] = true; await applyAtom(st, { type: "回合用量", player: from, key: USED_KEY$11, value: true }); await pushFrame(st, SKILL_ID$43, from, { ...params }); await applyAtom(st, { type: "弃置", player: from, cardIds: [cardId], voluntary: true }); st.turn.vars[ACTIVE_VAR$2] = from; await applyAtom(st, { type: "回合用量", player: from, key: ACTIVE_VAR$2, value: true }); if (discardedCard?.suit) { st.turn.vars[SUIT_VAR] = discardedCard.suit; await applyAtom(st, { type: "回合用量", player: from, key: SLASH_EXEMPT_SUIT_KEY, value: discardedCard.suit }); } if (discardedCard?.type === "装备牌" && self.alive && hasValidTargets$1(st, from)) await maybeDiscardOthersCard(st, from); await popFrame(st); }); registerAction(state, skill.id, ownerId, "respond", (st, _params) => { const slot = st.pendingSlots.get(ownerId); if (!slot) return "当前不需要回应"; const atom = slot.atom; if (atom.type !== "请求回应") return "当前不是请求回应"; const rt = atom.requestType; if (!rt || ![ CONFIRM_RT$41, TARGET_RT$18, PICK_CARD_RT$1 ].includes(rt)) return "当前不是弓骑询问"; return null; }, async (st, params) => { const rt = (st.pendingSlots.get(ownerId)?.atom)?.requestType; if (rt === CONFIRM_RT$41) { st.localVars[CONFIRM_KEY$17] = params.choice === true || params.confirmed === true; return; } if (rt === TARGET_RT$18) { const t = params.targets?.[0] ?? (typeof params.target === "number" ? params.target : void 0); if (typeof t === "number") st.localVars[TARGET_KEY$26] = t; return; } if (rt === PICK_CARD_RT$1) { const zone = params.zone; if (zone === "equipment") { if (typeof params.cardId !== "string") return; } else if (zone === "hand") { if (typeof params.handIndex !== "number") return; } else return; st.localVars[PICK_RESULT_KEY$3] = { zone: params.zone, cardId: params.cardId ?? null, handIndex: params.handIndex ?? null }; return; } }); return () => { unloadRangeExemptor(); unloadSlashExemptor(); }; } /** 弃置代价牌为装备时,询问是否弃一名其他角色一张牌;确认则选目标+弃牌 */ async function maybeDiscardOthersCard(state, ownerId) { delete state.localVars[CONFIRM_KEY$17]; await applyAtom(state, { type: "请求回应", requestType: CONFIRM_RT$41, target: ownerId, prompt: { type: "confirm", title: "弓骑:弃置的牌为装备,是否弃一名其他角色一张牌?", confirmLabel: "弃牌", cancelLabel: "不弃" }, defaultChoice: false, timeout: 10 }); if (!state.localVars[CONFIRM_KEY$17]) { delete state.localVars[CONFIRM_KEY$17]; return; } delete state.localVars[CONFIRM_KEY$17]; if (!state.players[ownerId]?.alive) return; if (!hasValidTargets$1(state, ownerId)) return; delete state.localVars[TARGET_KEY$26]; await applyAtom(state, { type: "请求回应", requestType: TARGET_RT$18, target: ownerId, prompt: { type: "choosePlayer", title: "弓骑:选择要弃置其一张牌的其他角色", min: 1, max: 1, filter: (_view, t) => t !== ownerId && state.players[t]?.alive === true && hasDiscardableCards$1(state.players[t]) }, timeout: 20 }); const targetIdx = state.localVars[TARGET_KEY$26]; delete state.localVars[TARGET_KEY$26]; if (typeof targetIdx !== "number") return; const target = state.players[targetIdx]; if (!target?.alive || !hasDiscardableCards$1(target)) return; await runPickTargetCardPanel(state, ownerId, targetIdx, target, { mode: "discard", requestType: PICK_CARD_RT$1, title: `弓骑:选择要从 ${target.name} 弃置的 1 张牌`, includeJudge: false }); } function onMount$159(_skill, api) { api.defineAction("use", { label: DISPLAY_NAME$60, style: "primary", prompt: { type: "useCard", title: "弓骑:选择一张手牌弃置(本回合攻击范围无限,同花色杀无次数限制;装备牌可再弃他人一张牌)", cardFilter: { filter: () => true, min: 1, max: 1 } }, activeWhen: (ctx) => { if (!defaultPlayActive(ctx)) return false; return ctx.view.players[ctx.perspectiveIdx]?.turnUsage?.[USED_KEY$11] !== true; } }); api.defineAction("respond", { label: DISPLAY_NAME$60, style: "default", prompt: { type: "confirm", title: "弓骑" } }); } var SKILL_ID$43, DISPLAY_NAME$60, USED_KEY$11, ACTIVE_VAR$2, SUIT_VAR, CONFIRM_RT$41, TARGET_RT$18, PICK_CARD_RT$1, CONFIRM_KEY$17, TARGET_KEY$26, _skillModule$83; var init_界弓骑 = __esmMin((() => { init_apply(); init_frame(); init_skill(); init_action_active(); init_pick_card_panel(); init_distance(); init_slash_quota(); init_vars_keys(); SKILL_ID$43 = "界弓骑"; DISPLAY_NAME$60 = "弓骑"; USED_KEY$11 = `${SKILL_ID$43}/usedThisTurn`; ACTIVE_VAR$2 = GONGQI_ACTIVE_VIEW_KEY; SUIT_VAR = `${SKILL_ID$43}/suit`; CONFIRM_RT$41 = `${SKILL_ID$43}/confirm`; TARGET_RT$18 = `${SKILL_ID$43}/选目标`; PICK_CARD_RT$1 = `${SKILL_ID$43}/选牌`; CONFIRM_KEY$17 = `${SKILL_ID$43}/confirmed`; TARGET_KEY$26 = `${SKILL_ID$43}/target`; _skillModule$83 = { createSkill: createSkill$171, onInit: onInit$171, onMount: onMount$159 }; })); //#endregion //#region src/engine/skills/界强袭.ts var 界强袭_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$170, default: () => _skillModule$82, onInit: () => onInit$170, onMount: () => onMount$158 }); function createSkill$170(id, ownerId) { return { id, ownerId, name: DISPLAY_NAME$59, description: "出牌阶段限两次:受到1点伤害或弃一张武器牌,对一名本回合未以此法指定过的其他角色造成1点伤害" }; } /** 是否为武器牌 */ function isWeaponCard$1(card) { return !!card && card.type === "装备牌" && card.subtype === "武器"; } /** 本回合已发动次数(0/1/2)。 */ function usesThisTurn$1(state, ownerId) { const v = state.players[ownerId]?.vars[COUNT_KEY$2]; return typeof v === "number" ? v : 0; } /** 本回合已以此法指定过的目标列表。 */ function usedTargets(state, ownerId) { const v = state.players[ownerId]?.vars[TARGETS_KEY$4]; return Array.isArray(v) ? v : []; } function onInit$170(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "use", (st, params) => { if (st.currentPlayerIndex !== ownerId) return "不是你的回合"; if (st.phase !== "出牌") return "只能在出牌阶段发动"; if (hasBlockingPending(st)) return "当前有未完成的询问"; if (usesThisTurn$1(st, ownerId) >= MAX_USES$1) return "本阶段强袭已达上限(2次)"; const self = st.players[ownerId]; if (!self?.alive) return "玩家不存在或已死亡"; const cost = params.cost; if (cost !== "damage" && cost !== "discard") return "cost 必须为 damage 或 discard"; const target = params.target; if (typeof target !== "number") return "需要指定目标"; if (target === ownerId) return "不能以自己为目标"; if (!st.players[target]?.alive) return "目标不合法"; if (usedTargets(st, ownerId).includes(target)) return "本回合已对此角色发动过强袭"; if (cost === "discard") { const cardId = params.cardId; if (typeof cardId !== "string") return "弃武器需要 cardId"; const card = st.cardMap[cardId]; if (!isWeaponCard$1(card)) return "不是武器牌"; const inHand = self.hand.includes(cardId); const inEquip = self.equipment["武器"] === cardId; if (!inHand && !inEquip) return "武器不在手牌或装备区"; } return null; }, async (st, params) => { const from = ownerId; const target = params.target; const cost = params.cost; const nextCount = usesThisTurn$1(st, from) + 1; st.players[from].vars[COUNT_KEY$2] = nextCount; await applyAtom(st, { type: "回合用量", player: from, key: COUNT_KEY$2, value: nextCount }); const nextTargets = [...usedTargets(st, from), target]; st.players[from].vars[TARGETS_KEY$4] = nextTargets; await applyAtom(st, { type: "回合用量", player: from, key: TARGETS_KEY$4, value: nextTargets }); await pushFrame(st, SKILL_ID$42, from, { ...params }); if (cost === "damage") { await runDamageFlow(st, from, from, 1); if (!st.players[from]?.alive) { await popFrame(st); return; } } else { const cardId = params.cardId; if (st.players[from].equipment["武器"] === cardId) await applyAtom(st, { type: "卸下", player: from, slot: "武器" }); await applyAtom(st, { type: "弃置", player: from, cardIds: [cardId], voluntary: true }); } await runDamageFlow(st, from, target, 1); await popFrame(st); }); return () => {}; } function onMount$158(_skill, api) { api.defineAction("use", { label: DISPLAY_NAME$59, style: "danger", prompt: { type: "selectTarget", title: "强袭:选择一名本回合未以此法指定过的角色(受到1点伤害或弃武器,对其造成1点伤害)", description: "本阶段限两次;每名角色每回合仅能被指定一次", targetFilter: { min: 1, max: 1, filter: (view, target) => { const me = view.currentPlayerIndex; if (target === me) return false; const tp = view.players.find((pl) => pl.index === target); if (!tp || tp.alive === false) return false; const used = view.players[me]?.turnUsage?.[TARGETS_KEY$4]; if ((Array.isArray(used) ? used : []).includes(target)) return false; return true; } }, paramVariants: [{ label: "受到1点伤害", params: { cost: "damage" } }, { label: "弃一张武器牌(先选中武器牌)", params: { cost: "discard" }, cardFilter: (c) => isWeaponCard$1(c) }] }, activeWhen: (ctx) => { if (!defaultPlayActive(ctx)) return false; const used = ctx.view.players[ctx.perspectiveIdx]?.turnUsage?.[COUNT_KEY$2]; return (typeof used === "number" ? used : 0) < MAX_USES$1; } }); } var SKILL_ID$42, DISPLAY_NAME$59, COUNT_KEY$2, TARGETS_KEY$4, MAX_USES$1, _skillModule$82; var init_界强袭 = __esmMin((() => { init_apply(); init_frame(); init_damage(); init_action_active(); init_skill(); SKILL_ID$42 = "界强袭"; DISPLAY_NAME$59 = "强袭"; COUNT_KEY$2 = `${SKILL_ID$42}/usedThisTurn`; TARGETS_KEY$4 = `${SKILL_ID$42}/targets/usedThisTurn`; MAX_USES$1 = 2; _skillModule$82 = { createSkill: createSkill$170, onInit: onInit$170, onMount: onMount$158 }; })); //#endregion //#region src/engine/skills/界强识.ts var 界强识_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$169, default: () => _skillModule$81, onInit: () => onInit$169, onMount: () => onMount$157 }); function createSkill$169(id, ownerId) { return { id, ownerId, name: DISPLAY_NAME$58, description: "出牌阶段开始时,你可观看一名其他角色的手牌并展示其中一张牌;本阶段你使用此类别的牌后可摸一张牌" }; } function onInit$169(skill, state) { const ownerId = skill.ownerId; /** 询问 owner 是否摸一张牌(非锁定技,默认摸)——供 移动牌/装备 hook 复用。 */ const askDraw = async (st) => { delete st.localVars[DRAW_KEY]; await applyAtom(st, { type: "请求回应", requestType: DRAW_RT, target: ownerId, prompt: { type: "confirm", title: "强识:是否摸一张牌?(本阶段使用同类别的牌)", confirmLabel: "摸牌", cancelLabel: "不摸" }, defaultChoice: true, timeout: 15 }); if (st.localVars[DRAW_KEY] === false) { delete st.localVars[DRAW_KEY]; return; } delete st.localVars[DRAW_KEY]; await applyAtom(st, { type: "摸牌", player: ownerId, count: 1 }); }; registerAction(state, skill.id, ownerId, "respond", (st, params) => { const slot = st.pendingSlots.get(ownerId); if (!slot) return "当前不需要回应"; if (slot.atom.type !== "请求回应") return "当前不需要回应"; const rt = slot.atom.requestType; if (rt === CONFIRM_RT$40 || rt === DRAW_RT) return null; if (rt === TARGET_RT$17) { const t = params.target; if (typeof t !== "number") return "需要指定一名目标"; if (t === ownerId) return "不能观看自己的手牌"; if (!st.players[t]?.alive) return "目标不合法"; if (st.players[t].hand.length === 0) return "目标无手牌"; return null; } if (rt === PICK_RT$7) { const cardId = params.cardId; if (typeof cardId !== "string") return "需要选择一张牌展示"; const target = st.localVars[TARGET_KEY$25]; if (typeof target !== "number") return "目标未确定"; if (!st.players[target]?.hand.includes(cardId)) return "该牌不在目标手牌中"; return null; } return "当前不是强识询问"; }, async (st, params) => { const rt = (st.pendingSlots.get(ownerId)?.atom)?.requestType; if (rt === CONFIRM_RT$40) st.localVars[CONFIRM_KEY$16] = params.choice === true || params.confirmed === true; else if (rt === DRAW_RT) st.localVars[DRAW_KEY] = params.choice === true || params.confirmed === true; else if (rt === TARGET_RT$17) st.localVars[TARGET_KEY$25] = params.target; else if (rt === PICK_RT$7) st.localVars[PICK_KEY$6] = params.cardId; }); registerAfterHook(state, skill.id, ownerId, "阶段开始", async (ctx) => { const atom = ctx.atom; if (atom.type !== "阶段开始") return; if (atom.phase !== "出牌") return; if (atom.player !== ownerId) return; const st = ctx.state; if (!st.players[ownerId]?.alive) return; delete st.players[ownerId].vars[CATEGORY_KEY]; delete st.players[ownerId].vars[REVEALED_KEY]; if (!st.players.some((p, i) => i !== ownerId && p.alive && p.hand.length > 0)) return; delete st.localVars[CONFIRM_KEY$16]; await applyAtom(st, { type: "请求回应", requestType: CONFIRM_RT$40, target: ownerId, prompt: { type: "confirm", title: "是否发动强识?(观看一名其他角色的手牌并展示其中一张牌)", confirmLabel: "发动", cancelLabel: "不发动" }, defaultChoice: false, timeout: 15 }); if (!st.localVars[CONFIRM_KEY$16]) { delete st.localVars[CONFIRM_KEY$16]; return; } delete st.localVars[CONFIRM_KEY$16]; await pushFrame(st, SKILL_ID$41, ownerId, {}); delete st.localVars[TARGET_KEY$25]; await applyAtom(st, { type: "请求回应", requestType: TARGET_RT$17, target: ownerId, prompt: { type: "choosePlayer", title: "强识:选择一名其他角色(观看其手牌)", min: 1, max: 1, filter: (_view, t) => t !== ownerId && st.players[t]?.alive === true && (st.players[t]?.hand.length ?? 0) > 0 }, timeout: 20 }); const target = st.localVars[TARGET_KEY$25]; if (typeof target !== "number" || target === ownerId) { delete st.localVars[TARGET_KEY$25]; await popFrame(st); return; } if (!st.players[target]?.alive || st.players[target].hand.length === 0) { delete st.localVars[TARGET_KEY$25]; await popFrame(st); return; } delete st.localVars[PICK_KEY$6]; await applyAtom(st, { type: "请求回应", requestType: PICK_RT$7, target: ownerId, prompt: { type: "pickProcessingCard", title: "强识:选择一张牌展示(本阶段使用此类别的牌可摸一张)", cards: st.players[target].hand.map((id) => { const c = st.cardMap[id]; return { cardId: id, cardName: c?.name ?? "?", suit: c?.suit ?? "", rank: c?.rank ?? "" }; }) }, timeout: 30 }); const pickedId = st.localVars[PICK_KEY$6]; delete st.localVars[PICK_KEY$6]; delete st.localVars[TARGET_KEY$25]; if (typeof pickedId !== "string" || !st.players[target].hand.includes(pickedId)) { await popFrame(st); return; } await applyAtom(st, { type: "展示", player: target, cardId: pickedId }); const card = st.cardMap[pickedId]; if (card) { st.players[ownerId].vars[CATEGORY_KEY] = card.type; st.players[ownerId].vars[REVEALED_KEY] = pickedId; } await popFrame(st); }); registerAfterHook(state, skill.id, ownerId, "移动牌", async (ctx) => { const st = ctx.state; if (st.currentPlayerIndex !== ownerId) return; if (st.phase !== "出牌") return; const category = st.players[ownerId]?.vars[CATEGORY_KEY]; if (typeof category !== "string") return; const atom = ctx.atom; if (!atom.cardId) return; if (atom.from?.zone !== "手牌") return; if (atom.from.player !== ownerId) return; if (!atom.to) return; if (atom.to.zone !== "处理区" && atom.to.zone !== "弃牌堆") return; const card = st.cardMap[atom.cardId]; if (!card) return; if (card.type !== category) return; if (card.type === "装备牌") return; if (!st.players[ownerId]?.alive) return; await askDraw(st); }); registerAfterHook(state, skill.id, ownerId, "装备", async (ctx) => { const st = ctx.state; if (st.currentPlayerIndex !== ownerId) return; if (st.phase !== "出牌") return; const category = st.players[ownerId]?.vars[CATEGORY_KEY]; if (typeof category !== "string") return; const atom = ctx.atom; if (atom.player !== ownerId) return; if (!st.players[ownerId]?.alive) return; const card = st.cardMap[atom.cardId]; if (!card) return; if (card.type !== category) return; await askDraw(st); }); registerAfterHook(state, skill.id, ownerId, "阶段结束", async (ctx) => { const atom = ctx.atom; if (atom.type !== "阶段结束") return; if (atom.phase !== "出牌") return; if (atom.player !== ownerId) return; const st = ctx.state; delete st.players[ownerId]?.vars[CATEGORY_KEY]; delete st.players[ownerId]?.vars[REVEALED_KEY]; }); return () => {}; } function onMount$157(_skill, api) { api.defineAction("respond", { label: DISPLAY_NAME$58, style: "primary", prompt: { type: "confirm", title: "是否发动强识?", confirmLabel: "发动", cancelLabel: "不发动" } }); } var SKILL_ID$41, DISPLAY_NAME$58, CATEGORY_KEY, REVEALED_KEY, CONFIRM_RT$40, TARGET_RT$17, PICK_RT$7, DRAW_RT, CONFIRM_KEY$16, TARGET_KEY$25, PICK_KEY$6, DRAW_KEY, _skillModule$81; var init_界强识 = __esmMin((() => { init_apply(); init_frame(); init_skill(); SKILL_ID$41 = "界强识"; DISPLAY_NAME$58 = "强识"; CATEGORY_KEY = `${SKILL_ID$41}/category`; REVEALED_KEY = `${SKILL_ID$41}/revealedCard`; CONFIRM_RT$40 = `${SKILL_ID$41}/confirm`; TARGET_RT$17 = `${SKILL_ID$41}/target`; PICK_RT$7 = `${SKILL_ID$41}/pick`; DRAW_RT = `${SKILL_ID$41}/draw`; CONFIRM_KEY$16 = `${SKILL_ID$41}/confirmed`; TARGET_KEY$25 = `${SKILL_ID$41}/targetChoice`; PICK_KEY$6 = `${SKILL_ID$41}/pickedCard`; DRAW_KEY = `${SKILL_ID$41}/drawConfirmed`; _skillModule$81 = { createSkill: createSkill$169, onInit: onInit$169, onMount: onMount$157 }; })); //#endregion //#region src/engine/skills/界当先.ts var 界当先_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$168, default: () => 界当先_default, onInit: () => onInit$168, onMount: () => onMount$156 }); function createSkill$168(id, ownerId) { return { id, ownerId, name: DISPLAY_NAME$57, description: "锁定技:回合开始时执行一个额外的出牌阶段;此阶段开始时可获得一张无距离限制的杀;若未于此阶段造成过伤害,阶段结束时受1点伤害", isLocked: true }; } function onInit$168(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "respond", (st, _params) => { const slot = st.pendingSlots.get(ownerId); if (!slot) return "当前不需要回应"; const atom = slot.atom; if (atom.type !== "请求回应") return "当前不需要回应"; const rt = atom.requestType; if (rt !== CONFIRM_RT$39 && rt !== SOURCE_RT) return "当前不是当先询问"; return null; }, async (st, params) => { const rt = (st.pendingSlots.get(ownerId)?.atom)?.requestType; if (rt === CONFIRM_RT$39) st.localVars[CONFIRMED_KEY$32] = params.choice === true || params.confirmed === true; else if (rt === SOURCE_RT) { const fromDeck = params.choice === true || params.confirmed === true; st.localVars[SOURCE_KEY] = fromDeck ? "deck" : "discard"; } }); registerAction(state, skill.id, ownerId, "end", (st, _params) => { if (st.turn.vars[ACTIVE_KEY$2] !== true) return "当前不处于当先额外出牌阶段"; if (st.phase !== "出牌") return "当前不处于当先额外出牌阶段"; if (hasBlockingPending(st)) return "请先处理待响应事件"; if (!st.players[ownerId]?.alive) return "玩家已死亡"; return null; }, async (st, _params) => { st.turn.vars[ACTIVE_KEY$2] = false; }); const unloadRangeExemptor = registerAttackRangeExemptor(state, ownerId, (st, _from, _to, cardId) => { if (!cardId) return false; return cardId === st.turn.vars[NORANGE_KILL_KEY]; }); registerAfterHook(state, skill.id, ownerId, "回合开始", async (ctx) => { if (ctx.atom.player !== ownerId) return; if (!ctx.state.players[ownerId]?.alive) return; ctx.state.turn.vars[ACTIVE_KEY$2] = true; ctx.state.turn.vars[DMG_IN_PHASE_KEY] = 0; delete ctx.state.turn.vars[EXITING_KEY]; await applyAtom(ctx.state, { type: "回合用量", player: ownerId, key: EXTRA_PHASE_TU, value: true }); await applyAtom(ctx.state, { type: "阶段开始", player: ownerId, phase: "出牌" }); delete ctx.state.localVars[CONFIRMED_KEY$32]; await applyAtom(ctx.state, { type: "请求回应", requestType: CONFIRM_RT$39, target: ownerId, prompt: { type: "confirm", title: "当先:是否从牌堆或弃牌堆获得一张无距离限制的杀?", confirmLabel: "获得杀", cancelLabel: "不获得" }, defaultChoice: false, timeout: 15 }); if (ctx.state.localVars[CONFIRMED_KEY$32] === true) { delete ctx.state.localVars[SOURCE_KEY]; await applyAtom(ctx.state, { type: "请求回应", requestType: SOURCE_RT, target: ownerId, prompt: { type: "confirm", title: "当先:从哪个区域获得杀?", confirmLabel: "牌堆", cancelLabel: "弃牌堆" }, defaultChoice: false, timeout: 15 }); const source = ctx.state.localVars[SOURCE_KEY]; const fromZone = source === "discard" ? "弃牌堆" : "牌堆"; const slashIds = (source === "discard" ? ctx.state.zones.discardPile : ctx.state.zones.deck).filter((cid) => ctx.state.cardMap[cid]?.name === "杀"); let killCardId; if (slashIds.length > 0) { const rng = createRng(ctx.state.rngSeed); killCardId = slashIds[rng.nextInt(slashIds.length)]; ctx.state.rngSeed = rng.getState(); } if (killCardId) { await applyAtom(ctx.state, { type: "移动牌", cardId: killCardId, from: { zone: fromZone }, to: { zone: "手牌", player: ownerId } }); ctx.state.turn.vars[NORANGE_KILL_KEY] = killCardId; ctx.state.turn.vars[NORANGE_ACTIVE_KEY] = true; await applyAtom(ctx.state, { type: "回合用量", player: ownerId, key: NORANGE_ACTIVE_KEY, value: true }); } } while (ctx.state.turn.vars[ACTIVE_KEY$2] === true && ctx.state.players[ownerId]?.alive === true) await applyAtom(ctx.state, { type: "出牌窗口", player: ownerId, timeout: 50 }); if (ctx.state.turn.vars[DMG_IN_PHASE_KEY] === 0 && ctx.state.players[ownerId]?.alive === true) await runDamageFlow(ctx.state, ownerId, ownerId, 1); if (ctx.state.turn.vars[NORANGE_ACTIVE_KEY] !== void 0) await applyAtom(ctx.state, { type: "回合用量", player: ownerId, key: NORANGE_ACTIVE_KEY, value: false }); await applyAtom(ctx.state, { type: "回合用量", player: ownerId, key: EXTRA_PHASE_TU, value: false }); delete ctx.state.turn.vars[ACTIVE_KEY$2]; delete ctx.state.turn.vars[DMG_IN_PHASE_KEY]; delete ctx.state.turn.vars[EXITING_KEY]; delete ctx.state.turn.vars[NORANGE_KILL_KEY]; delete ctx.state.turn.vars[NORANGE_ACTIVE_KEY]; }); registerAfterHook(state, skill.id, ownerId, "造成伤害后", async (ctx) => { const atom = ctx.atom; if (atom.source !== ownerId) return; if ((atom.amount ?? 0) <= 0) return; if (ctx.state.turn.vars[ACTIVE_KEY$2] !== true) return; const cur = ctx.state.turn.vars[DMG_IN_PHASE_KEY] ?? 0; ctx.state.turn.vars[DMG_IN_PHASE_KEY] = cur + (atom.amount ?? 0); }); registerAfterHook(state, skill.id, ownerId, "移动牌", async (ctx) => { const atom = ctx.atom; const tracked = ctx.state.turn.vars[NORANGE_KILL_KEY]; if (typeof tracked !== "string") return; if (atom.cardId !== tracked) return; if (atom.from?.zone !== "手牌") return; delete ctx.state.turn.vars[NORANGE_KILL_KEY]; delete ctx.state.turn.vars[NORANGE_ACTIVE_KEY]; await applyAtom(ctx.state, { type: "回合用量", player: ownerId, key: NORANGE_ACTIVE_KEY, value: false }); }); registerBeforeHook(state, skill.id, ownerId, "阶段结束", async (ctx) => { if (ctx.atom.player !== ownerId) return; if (ctx.state.turn.vars[EXITING_KEY] === true) return { kind: "cancel" }; if (ctx.state.turn.vars[ACTIVE_KEY$2] === true) { ctx.state.turn.vars[ACTIVE_KEY$2] = false; ctx.state.turn.vars[EXITING_KEY] = true; return { kind: "cancel" }; } }); registerBeforeHook(state, skill.id, ownerId, "清过期标记", async (ctx) => { if (ctx.atom.player !== ownerId) return; if (ctx.state.turn.vars[EXITING_KEY] !== true) return; return { kind: "cancel" }; }); registerBeforeHook(state, skill.id, ownerId, "下一玩家", async (ctx) => { if (ctx.state.turn.vars[EXITING_KEY] !== true) return; return { kind: "cancel" }; }); registerBeforeHook(state, skill.id, ownerId, "回合结束", async (ctx) => { if (ctx.atom.player !== ownerId) return; if (ctx.state.turn.vars[EXITING_KEY] !== true) return; delete ctx.state.turn.vars[EXITING_KEY]; return { kind: "cancel" }; }); return () => { unloadRangeExemptor(); }; } function onMount$156(_skill, api) { api.defineAction("respond", { label: DISPLAY_NAME$57, style: "passive", prompt: { type: "confirm", title: "当先:是否获得一张无距离限制的杀?", confirmLabel: "获得杀", cancelLabel: "不获得" } }); api.defineAction("end", { label: "当先·结束额外阶段", style: "default", prompt: { type: "confirm", title: "结束当先额外出牌阶段?", confirmLabel: "结束", cancelLabel: "继续出牌" }, activeWhen: (ctx) => ctx.view.currentPlayerIndex === ctx.perspectiveIdx && ctx.view.phase === "出牌" && ctx.view.players[ctx.perspectiveIdx]?.turnUsage?.[EXTRA_PHASE_TU] === true }); } var DISPLAY_NAME$57, ACTIVE_KEY$2, EXITING_KEY, DMG_IN_PHASE_KEY, NORANGE_KILL_KEY, NORANGE_ACTIVE_KEY, EXTRA_PHASE_TU, CONFIRMED_KEY$32, SOURCE_KEY, CONFIRM_RT$39, SOURCE_RT, 界当先_default; var init_界当先 = __esmMin((() => { init_apply(); init_damage(); init_skill(); init_distance(); init_vars_keys(); init_rng(); DISPLAY_NAME$57 = "当先"; ACTIVE_KEY$2 = "当先/active"; EXITING_KEY = "当先/exiting"; DMG_IN_PHASE_KEY = "当先/damageInPhase"; NORANGE_KILL_KEY = "当先/noRangeKillCardId"; NORANGE_ACTIVE_KEY = DANGXIAN_NO_RANGE_VIEW_KEY; EXTRA_PHASE_TU = DANGXIAN_EXTRA_PHASE_VIEW_KEY; CONFIRMED_KEY$32 = "当先/confirmed"; SOURCE_KEY = "当先/sourceChoice"; CONFIRM_RT$39 = "界当先/confirm"; SOURCE_RT = "界当先/source"; 界当先_default = { createSkill: createSkill$168, onInit: onInit$168, onMount: onMount$156 }; })); //#endregion //#region src/engine/skills/界志继.ts var 界志继_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$167, default: () => 界志继_default, onInit: () => onInit$167, onMount: () => onMount$155 }); function createSkill$167(id, ownerId) { return { id, ownerId, name: "志继", description: "觉醒技:准备阶段或结束阶段且无手牌时,回复1体力或摸2牌,减1体力上限并永久获得\"观星\"" }; } /** 界志继觉醒主逻辑。 */ async function awaken$1(state, ownerId) { if (state.players[ownerId]?.vars[AWAKENED_KEY$5]) return; const self = state.players[ownerId]; if (!self?.alive) return; if (self.hand.length > 0) return; state.players[ownerId].vars[AWAKENED_KEY$5] = true; delete state.localVars[CHOICE_KEY$7]; await applyAtom(state, { type: "请求回应", requestType: CHOOSE_RT$8, target: ownerId, prompt: { type: "confirm", title: "志继:摸两张牌,还是回复1点体力?", confirmLabel: "摸两张牌", cancelLabel: "回复1点体力" }, defaultChoice: false, timeout: 30 }); if (state.localVars[CHOICE_KEY$7] === "draw") await applyAtom(state, { type: "摸牌", player: ownerId, count: 2 }); else { const cur = state.players[ownerId].health; const max = state.players[ownerId].maxHealth; const amount = Math.min(1, Math.max(0, max - cur)); if (amount > 0) await applyAtom(state, { type: "回复体力", target: ownerId, amount }); } await applyAtom(state, { type: "设上限", player: ownerId, amount: state.players[ownerId].maxHealth - 1 }); await applyAtom(state, { type: "添加技能", player: ownerId, skillId: "观星" }); } function onInit$167(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "respond", (st, _params) => { const slot = st.pendingSlots.get(ownerId); if (!slot) return "当前不需要回应"; const atom = slot.atom; if (atom["type"] !== "请求回应") return "当前不需要回应"; if (atom["requestType"] !== CHOOSE_RT$8) return "当前不是志继选择"; return null; }, async (st, params) => { st.localVars[CHOICE_KEY$7] = params.choice === true ? "draw" : "heal"; }); registerAfterHook(state, skill.id, ownerId, "阶段开始", async (ctx) => { const atom = ctx.atom; if (atom.type !== "阶段开始") return; if (atom.phase !== "准备" && atom.phase !== "回合结束") return; if (atom.player !== ownerId) return; await awaken$1(ctx.state, ownerId); }); return () => {}; } function onMount$155(_skill, api) { api.defineAction("respond", { label: "志继", style: "primary", prompt: { type: "confirm", title: "志继:摸两张牌,还是回复1点体力?", confirmLabel: "摸两张牌", cancelLabel: "回复1点体力" } }); } var CHOOSE_RT$8, CHOICE_KEY$7, AWAKENED_KEY$5, 界志继_default; var init_界志继 = __esmMin((() => { init_apply(); init_skill(); CHOOSE_RT$8 = "界志继/choose"; CHOICE_KEY$7 = "志继/choice"; AWAKENED_KEY$5 = "志继/awakened"; 界志继_default = { createSkill: createSkill$167, onInit: onInit$167, onMount: onMount$155 }; })); //#endregion //#region src/engine/skills/界忘隙.ts var 界忘隙_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$166, default: () => _skillModule$80, onInit: () => onInit$166, onMount: () => onMount$154 }); function createSkill$166(id, ownerId) { return { id, ownerId, name: DISPLAY_NAME$56, description: "对其他角色造成1点伤害后,或受到其他角色1点伤害后,摸两张牌并交给该角色其中一张" }; } function onInit$166(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "respond", (st, params) => { const slot = st.pendingSlots.get(ownerId); if (!slot) return "当前不需要回应"; const atom = slot.atom; if (atom["type"] !== "请求回应") return "当前不需要回应"; const rt = atom["requestType"]; if (rt !== CONFIRM_RT$38 && rt !== PICK_RT$6) return "当前不是忘隙询问"; if (rt === PICK_RT$6) { const candidates = atom.prompt?.cardIds ?? []; const candidateSet = new Set(candidates); const chosen = params.cardIds ?? []; if (chosen.length !== 1) return "忘隙:必须选择 1 张牌交给该角色"; if (!candidateSet.has(chosen[0])) return "忘隙:所选牌不在刚摸到的牌中"; } return null; }, async (st, params) => { const rt = (st.pendingSlots.get(ownerId)?.atom)?.requestType; if (rt === CONFIRM_RT$38) st.localVars[CONFIRMED_KEY$31] = params.choice === true || params.confirmed === true; else if (rt === PICK_RT$6) { const ids = params.cardIds ?? []; st.localVars[PICK_KEY$5] = ids[0] ?? null; } }); async function forgetGapOnce(state, other) { if (!state.players[ownerId]?.alive) return; if (!state.players[other]?.alive) return; delete state.localVars[CONFIRMED_KEY$31]; await applyAtom(state, { type: "请求回应", requestType: CONFIRM_RT$38, target: ownerId, prompt: { type: "confirm", title: `忘隙:是否摸两张牌并交给 P${other} 一张?`, confirmLabel: "发动", cancelLabel: "不发动" }, defaultChoice: false, timeout: 10 }); if (!state.localVars[CONFIRMED_KEY$31]) { delete state.localVars[CONFIRMED_KEY$31]; return; } delete state.localVars[CONFIRMED_KEY$31]; const totalAvail = state.zones.deck.length + state.zones.discardPile.length; if (totalAvail === 0) return; const handBefore = new Set(state.players[ownerId].hand); await applyAtom(state, { type: "摸牌", player: ownerId, count: Math.min(2, totalAvail) }); const drawn = state.players[ownerId].hand.filter((id) => !handBefore.has(id)); if (drawn.length === 0) return; if (!state.players[other]?.alive) return; if (drawn.length === 1) { await applyAtom(state, { type: "给予", cardId: drawn[0], from: ownerId, to: other }); return; } delete state.localVars[PICK_KEY$5]; await applyAtom(state, { type: "请求回应", requestType: PICK_RT$6, target: ownerId, prompt: { type: "distribute", mode: "select", title: `忘隙:选择 1 张牌交给 P${other}`, cardIds: drawn, minTotal: 1, maxTotal: 1 }, defaultChoice: false, timeout: 15 }); const chosen = state.localVars[PICK_KEY$5]; delete state.localVars[PICK_KEY$5]; const giveCard = typeof chosen === "string" ? chosen : drawn[0]; if (state.players[other]?.alive && state.players[ownerId]?.alive) await applyAtom(state, { type: "给予", cardId: giveCard, from: ownerId, to: other }); } registerAfterHook(state, skill.id, ownerId, "造成伤害后", async (ctx) => { const atom = ctx.atom; if (atom.source !== ownerId) return; if (atom.target === void 0 || atom.target === ownerId) return; const amount = atom.amount ?? 0; if (amount <= 0) return; for (let i = 0; i < amount; i++) { if (!ctx.state.players[atom.target]?.alive) break; await forgetGapOnce(ctx.state, atom.target); } }); registerAfterHook(state, skill.id, ownerId, "伤害结算结束后", async (ctx) => { const atom = ctx.atom; if (atom.target !== ownerId) return; const source = atom.source; if (typeof source !== "number" || source === ownerId || source < 0) return; const amount = atom.amount ?? 0; if (amount <= 0) return; for (let i = 0; i < amount; i++) { if (!ctx.state.players[source]?.alive) break; await forgetGapOnce(ctx.state, source); } }); return () => {}; } function onMount$154(_skill, api) { api.defineAction("respond", { label: DISPLAY_NAME$56, style: "primary", prompt: { type: "confirm", title: "是否发动忘隙?", confirmLabel: "发动", cancelLabel: "不发动" } }); } var DISPLAY_NAME$56, CONFIRM_RT$38, PICK_RT$6, CONFIRMED_KEY$31, PICK_KEY$5, _skillModule$80; var init_界忘隙 = __esmMin((() => { init_apply(); init_skill(); DISPLAY_NAME$56 = "忘隙"; CONFIRM_RT$38 = "界忘隙/confirm"; PICK_RT$6 = "界忘隙/pick"; CONFIRMED_KEY$31 = "忘隙/confirmed"; PICK_KEY$5 = "忘隙/pickChoice"; _skillModule$80 = { createSkill: createSkill$166, onInit: onInit$166, onMount: onMount$154 }; })); //#endregion //#region src/engine/skills/界恂恂.ts var 界恂恂_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$165, default: () => _skillModule$79, onInit: () => onInit$165, onMount: () => onMount$153 }); function createSkill$165(id, ownerId) { return { id, ownerId, name: DISPLAY_NAME$55, description: "摸牌阶段开始时,观看牌堆顶4张牌,将其中2张置于牌堆顶,其余置于牌堆底" }; } function onInit$165(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "respond", (st, params) => { const slot = st.pendingSlots.get(ownerId); if (!slot) return "当前不需要回应"; const atom = slot.atom; if (atom["type"] !== "请求回应") return "当前不需要回应"; const rt = atom["requestType"]; if (rt !== CONFIRM_RT$37 && rt !== ARRANGE_RT) return "当前不是恂恂询问"; if (rt === ARRANGE_RT) { if (!Array.isArray(params.top) || !Array.isArray(params.bottom)) return "恂恂:需要 top/bottom 划分"; const observed = atom.prompt?.cardIds ?? []; const observedSet = new Set(observed); const top = params.top; const bottom = params.bottom; const combined = [...top, ...bottom]; if (combined.length !== observed.length) return "恂恂:必须划分全部观察的牌"; const requiredTop = Math.min(2, observed.length); if (top.length !== requiredTop) return `恂恂:须将 ${requiredTop} 张置于牌堆顶`; const seen = /* @__PURE__ */ new Set(); for (const cid of combined) { if (!observedSet.has(cid)) return "恂恂:牌不在观察范围内"; if (seen.has(cid)) return "恂恂:存在重复的牌"; seen.add(cid); } } return null; }, async (st, params) => { const rt = (st.pendingSlots.get(ownerId)?.atom)?.requestType; if (rt === CONFIRM_RT$37) st.localVars[CONFIRMED_KEY$30] = params.choice === true || params.confirmed === true; else if (rt === ARRANGE_RT) st.localVars[ARRANGE_KEY] = { top: params.top ?? [], bottom: params.bottom ?? [] }; }); registerAfterHook(state, skill.id, ownerId, "阶段开始", async (ctx) => { const atom = ctx.atom; if (atom.type !== "阶段开始") return; if (atom.player !== ownerId) return; if (atom.phase !== "摸牌") return; if (!ctx.state.players[ownerId]?.alive) return; const deck = ctx.state.zones.deck; if (deck.length === 0) return; const drawCount = Math.min(REVEAL_COUNT, deck.length); if (drawCount === 0) return; delete ctx.state.localVars[CONFIRMED_KEY$30]; await applyAtom(ctx.state, { type: "请求回应", requestType: CONFIRM_RT$37, target: ownerId, prompt: { type: "confirm", title: "是否发动恂恂?", description: `观看牌堆顶 ${drawCount} 张牌,选择置顶与置底`, confirmLabel: "发动", cancelLabel: "不发动" }, defaultChoice: false, timeout: 15 }); if (!ctx.state.localVars[CONFIRMED_KEY$30]) { delete ctx.state.localVars[CONFIRMED_KEY$30]; return; } const observed = deck.slice(-drawCount); const requiredTop = Math.min(2, drawCount); delete ctx.state.localVars[ARRANGE_KEY]; await applyAtom(ctx.state, { type: "请求回应", requestType: ARRANGE_RT, target: ownerId, prompt: { type: "distribute", mode: "select", title: `恂恂:选择 ${requiredTop} 张置于牌堆顶(顺序即摸牌顺序),其余置于牌堆底`, cardIds: observed, minTotal: requiredTop, maxTotal: requiredTop }, defaultChoice: false, timeout: 30 }); const arrangement = ctx.state.localVars[ARRANGE_KEY]; delete ctx.state.localVars[ARRANGE_KEY]; delete ctx.state.localVars[CONFIRMED_KEY$30]; const top = arrangement?.top ?? []; const bottom = arrangement?.bottom ?? []; const observedSet = new Set(observed); const combined = [...top, ...bottom]; if (!(top.length === requiredTop && combined.length === observed.length && new Set(combined).size === combined.length && combined.every((id) => observedSet.has(id)))) return; const middle = deck.slice(0, deck.length - drawCount); const newDeck = [ ...bottom, ...middle, ...[...top].reverse() ]; await applyAtom(ctx.state, { type: "整理牌堆", cards: newDeck, topCount: top.length, bottomCount: bottom.length }); }); return () => {}; } function onMount$153(_skill, api) { api.defineAction("respond", { label: DISPLAY_NAME$55, style: "default", prompt: { type: "confirm", title: "是否发动恂恂?", confirmLabel: "发动", cancelLabel: "不发动" } }); } var DISPLAY_NAME$55, CONFIRM_RT$37, ARRANGE_RT, CONFIRMED_KEY$30, ARRANGE_KEY, REVEAL_COUNT, _skillModule$79; var init_界恂恂 = __esmMin((() => { init_apply(); init_skill(); DISPLAY_NAME$55 = "恂恂"; CONFIRM_RT$37 = "界恂恂/confirm"; ARRANGE_RT = "界恂恂/arrange"; CONFIRMED_KEY$30 = "恂恂/confirmed"; ARRANGE_KEY = "恂恂/arrangement"; REVEAL_COUNT = 4; _skillModule$79 = { createSkill: createSkill$165, onInit: onInit$165, onMount: onMount$153 }; })); //#endregion //#region src/engine/skills/界恩怨.ts var 界恩怨_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$164, default: () => _skillModule$78, onInit: () => onInit$164, onMount: () => onMount$152 }); function createSkill$164(id, ownerId) { return { id, ownerId, name: DISPLAY_NAME$54, description: "获得一名其他角色≥2张牌后可令其摸1张;受到1点伤害后令来源选:交红色手牌或失去1点体力" }; } /** 获得计数 key(per source) */ function gainCountKey(from) { return `${SKILL_ID$40}/gainCount/${from}`; } /** 获得批次标识 key(per source)——存栈顶结算帧对象引用(无帧时回退 depth) */ function gainBatchKey(from) { return `${SKILL_ID$40}/gainBatch/${from}`; } /** * 记录一次"法正从 from 获得 1 张牌",返回是否触发 effect A(计数达 2)。 * 批次识别按「栈顶结算帧对象」隔离:同一帧内多次获得=同批次;不同帧(即便 stack 深度 * 相同,例如同一回合内两次独立的顺手牵羊)视为新批次,计数重置。仅在恰好达到 2 时 * 触发一次——之后继续累加但不再触发(避免同批次≥4 张时每 2 张重复触发)。 * * 注意:不能用 settlementStack.length 作批次标识——两次独立出牌(各自 push/pop 一帧) * 的 depth 相同,会被误判为同批次而错误累加触发。帧对象引用天然唯一,每次入栈都是 * 新对象,故以栈顶帧引用区分批次。无帧时(罕见:技能直接 applyAtom 未 pushFrame) * 回退到 depth,保持单帧内多牌仍可累计。 */ function trackGainAndCheck(state, from) { const stack = state.settlementStack; const top = stack[stack.length - 1]; const batchK = gainBatchKey(from); const countK = gainCountKey(from); const marker = top ?? stack.length; if (state.localVars[batchK] !== marker) { state.localVars[batchK] = marker; state.localVars[countK] = 1; return false; } const count = (state.localVars[countK] ?? 0) + 1; state.localVars[countK] = count; return count === 2; } /** 来源手牌中是否有红色牌 */ function hasRedHandCard(state, player) { return (state.players[player]?.hand ?? []).some((id) => state.cardMap[id]?.color === "红"); } /** 检查 atom 是否为" transferring 1 card to ownerId from otherPlayer " */ function extractGainEvent(atom, ownerId) { const a = atom; if (a["type"] === "获得") { const player = a["player"]; const from = a["from"]; if (player === ownerId && typeof from === "number" && from !== ownerId) return { from }; return null; } if (a["type"] === "给予") { const to = a["to"]; const from = a["from"]; if (to === ownerId && typeof from === "number" && from !== ownerId) return { from }; return null; } if (a["type"] === "移动牌") { const to = a["to"]; const from = a["from"]; if (to?.zone === "手牌" && to.player === ownerId && typeof from?.player === "number" && from.player !== ownerId) return { from: from.player }; return null; } return null; } function onInit$164(skill, state) { const ownerId = skill.ownerId; const unloaders = []; for (const p of state.players) { const seatId = p.index; const isOwner = seatId === ownerId; const u = registerAction(state, skill.id, seatId, "respond", (s) => { const slot = s.pendingSlots.get(seatId); if (slot?.atom.type !== "请求回应") return "当前不需要回应"; const rt = slot.atom.requestType ?? ""; const ownerRTs = [RT_A_CONFIRM, RT_B_CONFIRM]; const sourceRTs = [RT_B_SOURCE, RT_B_CARD]; if (isOwner && ownerRTs.includes(rt)) return null; if (sourceRTs.includes(rt)) return null; return "当前不是恩怨询问"; }, async (s, params) => { const rt = (s.pendingSlots.get(seatId)?.atom)?.requestType; if (rt === RT_A_CONFIRM) s.localVars[A_CONFIRM_KEY] = params.choice === true; else if (rt === RT_B_CONFIRM) s.localVars[B_CONFIRM_KEY] = params.choice === true; else if (rt === RT_B_SOURCE) s.localVars[B_SOURCE_CHOICE_KEY] = params.choice === true ? "card" : "lose"; else if (rt === RT_B_CARD) { const cardId = typeof params.cardId === "string" ? params.cardId : ""; s.localVars[B_CARD_KEY] = cardId; } }); unloaders.push(u); } const gainHook = async (ctx) => { const ev = extractGainEvent(ctx.atom, ownerId); if (!ev) return; const from = ev.from; if (!ctx.state.players[from]?.alive) return; if (!trackGainAndCheck(ctx.state, from)) return; delete ctx.state.localVars[A_CONFIRM_KEY]; await applyAtom(ctx.state, { type: "请求回应", requestType: RT_A_CONFIRM, target: ownerId, prompt: { type: "confirm", title: `恩怨:获得 ${ctx.state.players[from].name ?? `P${from}`} 至少 2 张牌,是否令其摸 1 张?`, confirmLabel: "令其摸 1 张", cancelLabel: "不发动" }, defaultChoice: false, timeout: 10 }); if (ctx.state.localVars[A_CONFIRM_KEY] !== true) return; if (ctx.state.players[from]?.alive) await applyAtom(ctx.state, { type: "摸牌", player: from, count: 1 }); }; registerAfterHook(state, skill.id, ownerId, "获得", gainHook); registerAfterHook(state, skill.id, ownerId, "给予", gainHook); registerAfterHook(state, skill.id, ownerId, "移动牌", gainHook); registerAfterHook(state, skill.id, ownerId, "受到伤害后", async (ctx) => { const atom = ctx.atom; if (atom.target !== ownerId) return; if ((atom.amount ?? 0) <= 0) return; if (atom.source === void 0 || atom.source === ownerId) return; const sourceIdx = atom.source; if (!ctx.state.players[sourceIdx]?.alive) return; delete ctx.state.localVars[B_CONFIRM_KEY]; await applyAtom(ctx.state, { type: "请求回应", requestType: RT_B_CONFIRM, target: ownerId, prompt: { type: "confirm", title: "恩怨:受到伤害后,是否令来源选交红牌或失去 1 体力?", confirmLabel: "令来源选择", cancelLabel: "不发动" }, defaultChoice: false, timeout: 10 }); if (ctx.state.localVars[B_CONFIRM_KEY] !== true) return; if (!ctx.state.players[sourceIdx]?.alive) return; await pushFrame(ctx.state, SKILL_ID$40, ownerId, { source: sourceIdx }); try { const hasRed = hasRedHandCard(ctx.state, sourceIdx); let sourceChoseCard = false; if (hasRed) { delete ctx.state.localVars[B_SOURCE_CHOICE_KEY]; await applyAtom(ctx.state, { type: "请求回应", requestType: RT_B_SOURCE, target: sourceIdx, prompt: { type: "confirm", title: "恩怨:选择一项(确认=交给法正一张红色手牌,取消=失去 1 点体力)", confirmLabel: "交红色手牌", cancelLabel: "失去 1 点体力" }, defaultChoice: false, timeout: 15 }); sourceChoseCard = ctx.state.localVars[B_SOURCE_CHOICE_KEY] === "card"; } if (sourceChoseCard) { delete ctx.state.localVars[B_CARD_KEY]; await applyAtom(ctx.state, { type: "请求回应", requestType: RT_B_CARD, target: sourceIdx, prompt: { type: "useCard", title: "恩怨:选择一张红色手牌交给法正", cardFilter: { filter: (c) => c.color === "红", min: 1, max: 1 } }, timeout: 15 }); const cardId = ctx.state.localVars[B_CARD_KEY]; if (typeof cardId === "string" && cardId && ctx.state.players[sourceIdx]?.hand.includes(cardId) && ctx.state.cardMap[cardId]?.color === "红") await applyAtom(ctx.state, { type: "移动牌", cardId, from: { zone: "手牌", player: sourceIdx }, to: { zone: "手牌", player: ownerId } }); else await applyAtom(ctx.state, { type: "失去体力", target: sourceIdx, amount: 1 }); } else await applyAtom(ctx.state, { type: "失去体力", target: sourceIdx, amount: 1 }); } finally { await popFrame(ctx.state); } }); return () => { unloaders.forEach((fn) => fn()); }; } function onMount$152(_skill, api) { api.defineAction("respond", { label: DISPLAY_NAME$54, style: "default", prompt: { type: "confirm", title: "是否发动恩怨?", confirmLabel: "发动", cancelLabel: "不发动" } }); } var SKILL_ID$40, DISPLAY_NAME$54, A_CONFIRM_KEY, B_CONFIRM_KEY, B_SOURCE_CHOICE_KEY, B_CARD_KEY, RT_A_CONFIRM, RT_B_CONFIRM, RT_B_SOURCE, RT_B_CARD, _skillModule$78; var init_界恩怨 = __esmMin((() => { init_apply(); init_frame(); init_skill(); SKILL_ID$40 = "界恩怨"; DISPLAY_NAME$54 = "恩怨"; A_CONFIRM_KEY = `${SKILL_ID$40}/aConfirmed`; B_CONFIRM_KEY = `${SKILL_ID$40}/bConfirmed`; B_SOURCE_CHOICE_KEY = `${SKILL_ID$40}/bSourceChoice`; B_CARD_KEY = `${SKILL_ID$40}/bCardId`; RT_A_CONFIRM = `${SKILL_ID$40}/aConfirm`; RT_B_CONFIRM = `${SKILL_ID$40}/bConfirm`; RT_B_SOURCE = `${SKILL_ID$40}/bSource`; RT_B_CARD = `${SKILL_ID$40}/bCard`; _skillModule$78 = { createSkill: createSkill$164, onInit: onInit$164, onMount: onMount$152 }; })); //#endregion //#region src/engine/skills/界悲歌.ts var 界悲歌_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$163, default: () => 界悲歌_default, onInit: () => onInit$163, onMount: () => onMount$151 }); function createSkill$163(id, ownerId) { return { id, ownerId, name: "悲歌", description: "受杀伤害后令其判定,可弃一张牌按花色执行;花色同获判定牌,点数同获弃置牌" }; } function onInit$163(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "respond", (st, params) => { const slot = st.pendingSlots.get(ownerId); if (slot?.atom.type !== "请求回应") return "当前不需要回应"; const rt = slot.atom.requestType; if (rt !== START_RT$1 && rt !== DISCARD_RT$14) return "当前不是界悲歌询问"; if (rt === DISCARD_RT$14) { const cardId = params.cardId; if (typeof cardId === "string" && !st.players[ownerId].hand.includes(cardId)) return "牌不在手牌中"; } return null; }, async (st, params) => { const rt = (st.pendingSlots.get(ownerId)?.atom).requestType; if (rt === START_RT$1) st.localVars[START_KEY$1] = params.choice === true; else if (rt === DISCARD_RT$14) st.localVars[DISCARD_CARD_KEY$1] = params.cardId; }); registerAfterHook(state, skill.id, ownerId, "判定牌生效后", async (ctx) => { if (ctx.atom.judgeType !== "界悲歌") return; const processing = frameCards(ctx.state); if (processing.length === 0) return; const judgeCardId = processing[processing.length - 1]; const judgeCard = ctx.state.cardMap[judgeCardId]; if (!judgeCard) return; ctx.state.localVars[JUDGE_SUIT_KEY] = judgeCard.suit; ctx.state.localVars[JUDGE_RANK_KEY] = judgeCard.rank; ctx.state.localVars[JUDGE_CARD_KEY] = judgeCardId; }); registerAfterHook(state, skill.id, ownerId, "受到伤害后", async (ctx) => { const atom = ctx.atom; if ((atom.amount ?? 0) <= 0) return; const targetIdx = atom.target; if (targetIdx === void 0) return; if (!ctx.state.players[targetIdx]?.alive) return; const cardId = atom.cardId; if (!cardId) return; if (ctx.state.cardMap[cardId]?.name !== "杀") return; const self = ctx.state.players[ownerId]; if (!self?.alive) return; if (self.hand.length === 0) return; delete ctx.state.localVars[START_KEY$1]; await applyAtom(ctx.state, { type: "请求回应", requestType: START_RT$1, target: ownerId, prompt: { type: "confirm", title: "界悲歌:是否令受伤角色判定?", confirmLabel: "发动(判定)", cancelLabel: "不发动" }, defaultChoice: false, timeout: 15 }); const start = ctx.state.localVars[START_KEY$1] === true; delete ctx.state.localVars[START_KEY$1]; if (!start) return; if (ctx.state.zones.deck.length === 0) return; delete ctx.state.localVars[JUDGE_SUIT_KEY]; delete ctx.state.localVars[JUDGE_RANK_KEY]; delete ctx.state.localVars[JUDGE_CARD_KEY]; await runJudgeFlow(ctx.state, targetIdx, "界悲歌"); const suit = ctx.state.localVars[JUDGE_SUIT_KEY]; ctx.state.localVars[JUDGE_RANK_KEY]; const judgeCardId = ctx.state.localVars[JUDGE_CARD_KEY]; delete ctx.state.localVars[JUDGE_SUIT_KEY]; delete ctx.state.localVars[JUDGE_RANK_KEY]; delete ctx.state.localVars[JUDGE_CARD_KEY]; if (suit === void 0) return; const sourceIdx = atom.source; let discardCardId; if (ctx.state.players[ownerId].hand.length > 0) { delete ctx.state.localVars[DISCARD_CARD_KEY$1]; await applyAtom(ctx.state, { type: "请求回应", requestType: DISCARD_RT$14, target: ownerId, prompt: { type: "useCard", title: "界悲歌:是否弃置一张牌?(花色同获判定牌,点数同获弃置牌)", cardFilter: { filter: () => true, min: 1, max: 1 } }, timeout: 15 }); discardCardId = ctx.state.localVars[DISCARD_CARD_KEY$1]; delete ctx.state.localVars[DISCARD_CARD_KEY$1]; if (typeof discardCardId === "string") await applyAtom(ctx.state, { type: "弃置", player: ownerId, cardIds: [discardCardId], voluntary: true }); } if (suit === "♥") { if (ctx.state.players[targetIdx]?.alive) await applyAtom(ctx.state, { type: "回复体力", target: targetIdx, amount: 1 }); } else if (suit === "♦") await applyAtom(ctx.state, { type: "摸牌", player: targetIdx, count: 2 }); else if (suit === "♣") { if (sourceIdx === void 0) return; const source = ctx.state.players[sourceIdx]; if (!source?.alive) return; const toDiscard = source.hand.slice(0, Math.min(2, source.hand.length)); if (toDiscard.length > 0) await applyAtom(ctx.state, { type: "弃置", player: sourceIdx, cardIds: toDiscard }); } else if (suit === "♠") { if (sourceIdx === void 0) return; if (!ctx.state.players[sourceIdx]?.alive) return; await flipFaceDown(ctx.state, sourceIdx, "界悲歌"); } if (typeof discardCardId === "string" && judgeCardId) { const judgeCard = ctx.state.cardMap[judgeCardId]; const discardCard = ctx.state.cardMap[discardCardId]; if (judgeCard && discardCard) { if (judgeCard.suit === discardCard.suit && ctx.state.zones.discardPile.includes(judgeCardId)) await applyAtom(ctx.state, { type: "移动牌", cardId: judgeCardId, from: { zone: "弃牌堆" }, to: { zone: "手牌", player: ownerId } }); if (judgeCard.rank === discardCard.rank && ctx.state.zones.discardPile.includes(discardCardId)) await applyAtom(ctx.state, { type: "移动牌", cardId: discardCardId, from: { zone: "弃牌堆" }, to: { zone: "手牌", player: ownerId } }); } } }); registerBeforeHook(state, skill.id, ownerId, "阶段开始", async (ctx) => { const atom = ctx.atom; if (atom.type !== "阶段开始") return; const player = atom.player; if (player === void 0) return; const p = ctx.state.players[player]; if (!p) return; if (atom.phase === "准备" && p.tags.includes(SKIP_TAG$2)) { await flipFaceUp(ctx.state, player, "界悲歌"); ctx.state.localVars[SKIP_FLAG$4] = player; return { kind: "cancel" }; } if (ctx.state.localVars[SKIP_FLAG$4] === player) return { kind: "cancel" }; }); registerBeforeHook(state, skill.id, ownerId, "阶段结束", async (ctx) => { const atom = ctx.atom; if (atom.type !== "阶段结束") return; const player = atom.player; if (player === void 0) return; if (ctx.state.localVars[SKIP_FLAG$4] !== player) return; delete ctx.state.localVars[SKIP_FLAG$4]; await applyAtom(ctx.state, { type: "清过期标记", player }); await applyAtom(ctx.state, { type: "下一玩家" }); if (await applyAtom(ctx.state, { type: "回合结束", player })) await applyAtom(ctx.state, { type: "回合结束后", player }); return { kind: "cancel" }; }); return () => {}; } function onMount$151(_skill, api) { api.defineAction("respond", { label: "悲歌", style: "primary", prompt: { type: "useCard", title: "界悲歌:弃置一张牌(可选)", cardFilter: { filter: (_c) => true, min: 1, max: 1 } } }); } var START_RT$1, DISCARD_RT$14, START_KEY$1, DISCARD_CARD_KEY$1, JUDGE_SUIT_KEY, JUDGE_RANK_KEY, JUDGE_CARD_KEY, SKIP_TAG$2, SKIP_FLAG$4, 界悲歌_default; var init_界悲歌 = __esmMin((() => { init_apply(); init_frame(); init_judge(); init_face_down(); init_skill(); START_RT$1 = "界悲歌/chooseStart"; DISCARD_RT$14 = "界悲歌/chooseDiscard"; START_KEY$1 = "界悲歌/start"; DISCARD_CARD_KEY$1 = "界悲歌/discardCard"; JUDGE_SUIT_KEY = "界悲歌/judgeSuit"; JUDGE_RANK_KEY = "界悲歌/judgeRank"; JUDGE_CARD_KEY = "界悲歌/judgeCardId"; SKIP_TAG$2 = "界悲歌/翻面"; SKIP_FLAG$4 = "界悲歌/skipAll"; 界悲歌_default = { createSkill: createSkill$163, onInit: onInit$163, onMount: onMount$151 }; })); //#endregion //#region src/engine/skills/界惴恐.ts var 界惴恐_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$162, default: () => _skillModule$77, onInit: () => onInit$162, onMount: () => onMount$150 }); function createSkill$162(id, ownerId) { return { id, ownerId, name: DISPLAY_NAME$53, description: "其他角色的回合开始时,若你已受伤,你可以与其拼点:赢则其本回合不能对除其以外的角色使用牌;没赢则你获得其拼点牌,然后其视为对你使用一张杀" }; } /** 虚拟杀结算(参考界仁德/界诛害):source → target,无实体卡,不计入出杀次数。 */ async function virtualKill$4(state, source, target) { if (!state.players[target]?.alive) return; const cardId = `${SKILL_ID$39}:杀:${source}:${target}:${state.seq}`; state.cardMap[cardId] = { id: cardId, name: "杀", suit: "", color: "无色", rank: "A", type: "基本牌" }; await runUseFlow(state, source, cardId, [target], "杀", { virtual: true }); delete state.cardMap[cardId]; } function onInit$162(skill, state) { const ownerId = skill.ownerId; const unloaders = []; unloaders.push(registerAction(state, skill.id, ownerId, "respond", (st, params) => { const slot = st.pendingSlots.get(ownerId); if (slot?.atom.type !== "请求回应") return "当前不需要回应"; const rt = slot.atom.requestType; if (rt === CONFIRM_RT$36) return null; if (rt === OWNER_CARD_RT) { const cardId = params.cardId; if (typeof cardId !== "string") return "请选择一张拼点牌"; if (!st.players[ownerId]?.hand.includes(cardId)) return "拼点牌不在你的手牌中"; return null; } return "当前不是惴恐询问"; }, async (st, params) => { const rt = (st.pendingSlots.get(ownerId)?.atom)?.requestType; if (rt === CONFIRM_RT$36) st.localVars[CONFIRM_KEY$15] = params.choice === true; else if (rt === OWNER_CARD_RT) st.localVars[OWNER_CARD_KEY] = params.cardId; })); for (const p of state.players) { if (p.index === ownerId) continue; const pid = p.index; unloaders.push(registerAction(state, skill.id, pid, "respond", (st, params) => { const slot = st.pendingSlots.get(pid); if (slot?.atom.type !== "请求回应") return "当前不需要回应"; if (slot.atom.requestType !== TARGET_CARD_RT) return "当前不是惴恐拼点询问"; const cardId = params.cardId; if (typeof cardId !== "string") return "请选择一张拼点牌"; if (!st.players[pid]?.hand.includes(cardId)) return "拼点牌不在你的手牌中"; return null; }, async (st, params) => { if ((st.pendingSlots.get(pid)?.atom)?.requestType === TARGET_CARD_RT) st.localVars[TARGET_CARD_KEY$1] = params.cardId; })); } unloaders.push(registerBeforeHook(state, skill.id, ownerId, "成为目标", async (ctx) => { const atom = ctx.atom; const source = atom.source; const target = atom.target; if (typeof source !== "number" || typeof target !== "number") return; if (source === target) return; if (!ctx.state.players[source]?.vars[RESTRICTED_KEY]) return; return { kind: "cancel" }; })); unloaders.push(registerAfterHook(state, skill.id, ownerId, "回合开始", async (ctx) => { const st = ctx.state; const turnPlayer = ctx.atom.player; if (typeof turnPlayer !== "number") return; if (turnPlayer === ownerId) return; const self = st.players[ownerId]; if (!self?.alive) return; if (self.health >= self.maxHealth) return; const target = st.players[turnPlayer]; if (!target?.alive) return; if (self.hand.length === 0) return; if (target.hand.length === 0) return; delete st.localVars[CONFIRM_KEY$15]; await applyAtom(st, { type: "请求回应", requestType: CONFIRM_RT$36, target: ownerId, prompt: { type: "confirm", title: `惴恐:是否与 ${target.name} 拼点?`, confirmLabel: "发动", cancelLabel: "不发动" }, defaultChoice: false, timeout: 15 }); if (!st.localVars[CONFIRM_KEY$15]) return; delete st.localVars[CONFIRM_KEY$15]; if (target.hand.length === 0) return; delete st.localVars[OWNER_CARD_KEY]; await applyAtom(st, { type: "请求回应", requestType: OWNER_CARD_RT, target: ownerId, prompt: { type: "useCard", title: "惴恐:选择一张拼点牌", cardFilter: { filter: () => true, min: 1, max: 1 } }, timeout: 20 }); const ownerCardId = st.localVars[OWNER_CARD_KEY]; delete st.localVars[OWNER_CARD_KEY]; if (!ownerCardId || !self.hand.includes(ownerCardId)) return; delete st.localVars[TARGET_CARD_KEY$1]; const fallback = target.hand[0]; await applyAtom(st, { type: "请求回应", requestType: TARGET_CARD_RT, target: turnPlayer, prompt: { type: "useCard", title: `惴恐:与 ${self.name} 拼点,请出一张手牌`, cardFilter: { filter: () => true, min: 1, max: 1 } }, timeout: 20 }); let targetCardId = st.localVars[TARGET_CARD_KEY$1]; delete st.localVars[TARGET_CARD_KEY$1]; if (!targetCardId || !target.hand.includes(targetCardId)) targetCardId = fallback; if (!targetCardId || !target.hand.includes(targetCardId)) return; if (await runRankCompareFlow(st, ownerId, turnPlayer, ownerCardId, targetCardId) === "赢") { st.players[turnPlayer].vars[RESTRICTED_KEY] = true; await applyAtom(st, { type: "回合用量", player: turnPlayer, key: RESTRICTED_KEY, value: true }); } else { if (st.zones.discardPile.includes(targetCardId)) await applyAtom(st, { type: "移动牌", cardId: targetCardId, from: { zone: "弃牌堆" }, to: { zone: "手牌", player: ownerId } }); await virtualKill$4(st, turnPlayer, ownerId); } })); return () => unloaders.forEach((fn) => fn()); } function onMount$150(_skill, api) { api.defineAction("respond", { label: DISPLAY_NAME$53, style: "primary", prompt: { type: "confirm", title: DISPLAY_NAME$53, confirmLabel: "确认", cancelLabel: "取消" } }); return () => {}; } var SKILL_ID$39, DISPLAY_NAME$53, CONFIRM_RT$36, OWNER_CARD_RT, TARGET_CARD_RT, CONFIRM_KEY$15, OWNER_CARD_KEY, TARGET_CARD_KEY$1, RESTRICTED_KEY, _skillModule$77; var init_界惴恐 = __esmMin((() => { init_apply(); init_rank(); init_use_card(); init_skill(); SKILL_ID$39 = "界惴恐"; DISPLAY_NAME$53 = "惴恐"; CONFIRM_RT$36 = `${SKILL_ID$39}/confirm`; OWNER_CARD_RT = `${SKILL_ID$39}/ownerCard`; TARGET_CARD_RT = `${SKILL_ID$39}/targetCard`; CONFIRM_KEY$15 = `${SKILL_ID$39}/confirmed`; OWNER_CARD_KEY = `${SKILL_ID$39}/ownerCardId`; TARGET_CARD_KEY$1 = `${SKILL_ID$39}/targetCardId`; RESTRICTED_KEY = `${SKILL_ID$39}/restricted/usedThisTurn`; _skillModule$77 = { createSkill: createSkill$162, onInit: onInit$162, onMount: onMount$150 }; })); //#endregion //#region src/engine/skills/界护驾.ts var 界护驾_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$161, default: () => _skillModule$76, onInit: () => onInit$161, onMount: () => onMount$149 }); function createSkill$161(id, ownerId) { return { id, ownerId, name: "界护驾", description: "主公技:魏势力角色可代你使用或打出闪;每回合限一次,魏角色回合外用闪时可令你摸1张" }; } function onInit$161(skill, state) { const ownerId = skill.ownerId; const offs = []; offs.push(declareAlternativeResponse(state, ownerId, "询问闪")); offs.push(registerAction(state, skill.id, ownerId, "respond", (st, _params) => { if (ownerId !== 0) return "非主公不能发动护驾"; const slot = st.pendingSlots.get(ownerId); if (!slot) return "当前不需要回应"; if (slot.atom.type !== "询问闪") return "当前不是出闪窗口"; if (!st.players[ownerId]?.alive) return "玩家不存在或已死亡"; if (!st.players.some((p) => p.alive && p.index !== ownerId && p.faction === "魏" && p.hand.length > 0)) return "没有可出闪的魏势力角色"; return null; }, async (st, _params) => { const numPlayers = st.players.length; for (let offset = 1; offset < numPlayers; offset++) { const allyIdx = (ownerId + offset) % numPlayers; const ally = st.players[allyIdx]; if (!ally?.alive) continue; if (ally.faction !== "魏") continue; if (ally.hand.length === 0) continue; delete st.localVars[DODGE_PLAYED_VAR]; await applyAtom(st, { type: "请求回应", requestType: "界护驾/出闪", target: allyIdx, prompt: { type: "useCard", title: `护驾:曹操(${st.players[ownerId]?.name ?? `P${ownerId}`})需要闪,是否打出一张闪?`, cardFilter: { filter: (c) => c.name === "闪", min: 1, max: 1 } }, timeout: 15 }); const dodgeCardId = st.localVars[DODGE_PLAYED_VAR]; delete st.localVars[DODGE_PLAYED_VAR]; if (dodgeCardId && ally.hand.includes(dodgeCardId)) { await applyAtom(st, { type: "移动牌", cardId: dodgeCardId, from: { zone: "手牌", player: allyIdx }, to: { zone: "处理区" } }); return; } } })); offs.push(registerAfterHook(state, skill.id, ownerId, "移动牌", async (ctx) => { if (ownerId !== 0) return; const atom = ctx.atom; if (atom.from.zone !== "手牌") return; if (atom.to.zone !== "处理区") return; const sourceIdx = atom.from.player; if (typeof sourceIdx !== "number") return; if (sourceIdx === ownerId) return; const source = ctx.state.players[sourceIdx]; if (!source?.alive) return; if (source.faction !== "魏") return; if (ctx.state.cardMap[atom.cardId]?.name !== "闪") return; if (ctx.state.currentPlayerIndex === sourceIdx) return; const lord = ctx.state.players[ownerId]; if (!lord?.alive) return; if (ctx.state.turn.vars[PER_TURN_VAR$1] === true) return; ctx.state.turn.vars[PER_TURN_VAR$1] = true; await applyAtom(ctx.state, { type: "回合用量", player: ownerId, key: PER_TURN_VAR$1, value: true }); delete ctx.state.localVars[CONFIRMED_VAR$5]; await applyAtom(ctx.state, { type: "请求回应", requestType: REQUEST_TYPE$3, target: sourceIdx, prompt: { type: "confirm", title: `界护驾:是否令${lord.name}摸一张牌?`, confirmLabel: "令曹操摸牌", cancelLabel: "不发动" }, defaultChoice: false, timeout: 30 }); if (ctx.state.localVars[CONFIRMED_VAR$5] === true) await applyAtom(ctx.state, { type: "摸牌", player: ownerId, count: 1 }); })); for (const p of state.players) { const pid = p.index; if (pid === ownerId) continue; if (p.faction !== "魏") continue; const off = registerAction(state, skill.id, pid, "respond", (st, params) => { const slot = st.pendingSlots.get(pid); if (!slot) return "当前不需要回应"; const a = slot.atom; if (a["type"] !== "请求回应") return "当前不需要回应"; const requestType = a["requestType"]; if (requestType === "界护驾/出闪") { const cardId = params.cardId; if (cardId) { if (!st.players[pid].hand.includes(cardId)) return "牌不在手牌中"; if (st.cardMap[cardId]?.name !== "闪") return "只能打出闪"; } return null; } if (requestType === REQUEST_TYPE$3) return null; return "当前不是界护驾询问"; }, async (st, params) => { const slot = st.pendingSlots.get(pid); if (!slot) return; const requestType = slot.atom["requestType"]; if (requestType === "界护驾/出闪") { const cardId = params.cardId; if (cardId) st.localVars[DODGE_PLAYED_VAR] = cardId; } else if (requestType === REQUEST_TYPE$3) st.localVars[CONFIRMED_VAR$5] = params.choice === true || params.confirmed === true; }); offs.push(off); } return () => { for (const off of offs) off(); }; } function onMount$149(_skill, api) { api.defineAction("respond", { label: "界护驾", style: "primary", prompt: { type: "confirm", title: "护驾:令魏势力角色替你出闪?", confirmLabel: "护驾", cancelLabel: "不发动" }, activeWhen: (ctx) => { const slot = ctx.view.pending; if (!slot) return false; if (slot.target !== ctx.perspectiveIdx) return false; const atom = slot.atom; if (atom.type === "询问闪") return true; if (atom.type === "请求回应" && atom.requestType === "界护驾/出闪") return true; if (atom.type === "请求回应" && atom.requestType === REQUEST_TYPE$3) return true; return false; } }); } var DODGE_PLAYED_VAR, REQUEST_TYPE$3, CONFIRMED_VAR$5, PER_TURN_VAR$1, _skillModule$76; var init_界护驾 = __esmMin((() => { init_apply(); init_skill(); DODGE_PLAYED_VAR = "界护驾/闪出"; REQUEST_TYPE$3 = "界护驾/drawChoice"; CONFIRMED_VAR$5 = "界护驾/confirmed"; PER_TURN_VAR$1 = "界护驾/triggered"; _skillModule$76 = { createSkill: createSkill$161, onInit: onInit$161, onMount: onMount$149 }; })); //#endregion //#region src/engine/skills/界挑衅.ts var 界挑衅_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$160, default: () => _skillModule$75, onInit: () => onInit$160, onMount: () => onMount$148 }); function createSkill$160(id, ownerId) { return { id, ownerId, name: DISPLAY_NAME$52, description: "出牌阶段限两次:指定一名攻击范围包含你的角色,其须对你出杀且造成伤害,否则你弃其一张牌" }; } /** 本回合已发动次数(0/1/2)。 */ function usesThisTurn(state, ownerId) { const v = state.players[ownerId]?.vars[COUNT_KEY$1]; return typeof v === "number" ? v : 0; } /** 让姜维从被挑衅者区域选一张牌弃置(装备明选/手牌盲选)。 */ async function pickAndDiscard$1(state, picker, victim) { const vp = state.players[victim]; if (!vp) return; const equipment = Object.entries(vp.equipment).filter(([, id]) => typeof id === "string").map(([slot, id]) => ({ slot, cardId: id, cardName: state.cardMap[id]?.name ?? "?" })); const handCount = vp.hand.length; if (equipment.length === 0 && handCount === 0) return; const defaultZone = equipment.length > 0 ? { zone: "equipment", cardId: equipment[0].cardId } : { zone: "hand", handIndex: 0 }; state.localVars[PICK_VICTIM_KEY] = victim; delete state.localVars[PICK_RESULT_KEY$2]; const pickAtom = { type: "请求回应", requestType: PICK_REQUEST$1, target: picker, prompt: { type: "pickTargetCard", mode: "discard", title: `挑衅:弃置 ${vp.name} 的一张牌`, target: victim, equipment, judge: [], handCount }, defaultChoice: defaultZone, timeout: 20 }; await applyAtom(state, pickAtom); const result = state.localVars[PICK_RESULT_KEY$2]; delete state.localVars[PICK_RESULT_KEY$2]; delete state.localVars[PICK_VICTIM_KEY]; const filteredEquip = pickAtom.prompt.equipment ?? []; let fallback = defaultZone; if (fallback.zone === "equipment") { if (!filteredEquip.some((e) => e.cardId === fallback.cardId)) fallback = filteredEquip.length > 0 ? { zone: "equipment", cardId: filteredEquip[0].cardId } : { zone: "hand", handIndex: 0 }; } const zone = result?.zone ?? fallback.zone; let discardId; if (zone === "equipment") discardId = result?.cardId ?? fallback.cardId ?? void 0; else { const idx = result?.handIndex ?? 0; discardId = vp.hand[idx] ?? vp.hand[0]; } if (discardId) await applyAtom(state, { type: "弃置", player: victim, cardIds: [discardId] }); } function onInit$160(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "use", (state, params) => { if (state.currentPlayerIndex !== ownerId) return "不是你的回合"; if (state.phase !== "出牌") return "只能在出牌阶段发动"; if (hasBlockingPending(state)) return "当前有未完成的询问"; if (usesThisTurn(state, ownerId) >= MAX_USES) return "本阶段挑衅已达上限(2次)"; if (!state.players[ownerId]?.alive) return "玩家不存在或已死亡"; const target = params.target; if (typeof target !== "number") return "需要选择目标"; if (target === ownerId) return "不能选择自己"; if (!state.players[target]?.alive) return "目标不存在或已死亡"; if (!inAttackRange(state, target, ownerId)) return "目标无法用杀攻击到你"; return null; }, async (state, params) => { const from = ownerId; const target = params.target; const nextCount = usesThisTurn(state, from) + 1; state.players[from].vars[COUNT_KEY$1] = nextCount; await applyAtom(state, { type: "回合用量", player: from, key: COUNT_KEY$1, value: nextCount }); await pushFrame(state, SKILL_ID$38, from, { ...params }); try { await applyAtom(state, { type: "请求回应", requestType: SLASH_REQUEST, target, prompt: { type: "useCardAndTarget", title: `挑衅:对 ${state.players[from].name} 使用一张杀(需造成伤害,否则其弃你一张牌)`, cardFilter: { filter: (c) => c.name === "杀", min: 1, max: 1 }, targetFilter: { min: 1, max: 1, filter: (_v, t) => t === from } }, timeout: 15 }); const choice = state.localVars[SLASH_CHOICE_VAR]; delete state.localVars[SLASH_CHOICE_VAR]; const hpBefore = state.players[from]?.health ?? 0; let slashed = false; if (choice?.cardId) { await runUseFlow(state, target, choice.cardId, [choice.target], "杀"); slashed = true; } const damaged = (state.players[from]?.health ?? hpBefore) < hpBefore; if (!(slashed && damaged) && state.players[target]?.alive) await pickAndDiscard$1(state, from, target); } finally { await popFrame(state); } }); const unloaders = []; for (const pl of state.players) { const seat = pl.index; unloaders.push(registerAction(state, skill.id, seat, "respond", (st, params) => { const slot = st.pendingSlots.get(seat); if (!slot) return "当前不需要回应"; if (slot.atom.type !== "请求回应") return "当前不是挑衅询问"; const requestType = slot.atom.requestType; if (requestType === SLASH_REQUEST) { const cardId = params.cardId; const targetIdx = params.target; if (typeof cardId !== "string") return "请选择一张杀"; if (typeof targetIdx !== "number") return "请选择目标"; if (!st.players[seat]?.hand.includes(cardId)) return "牌不在手牌中"; if (st.cardMap[cardId]?.name !== "杀") return "只能使用杀"; if (targetIdx !== ownerId) return "只能对姜维使用杀"; if (!inAttackRange(st, seat, ownerId, cardId)) return "目标不在攻击范围内"; return null; } if (requestType === PICK_REQUEST$1) { const victim = st.localVars[PICK_VICTIM_KEY]; if (typeof victim !== "number") return "无弃牌目标"; const vp = st.players[victim]; if (!vp) return "弃牌目标不存在"; const zone = params.zone; if (zone === "equipment") { if (typeof params.cardId !== "string") return "cardId required"; const promptEquip = slot.atom?.prompt?.equipment; if (promptEquip && !promptEquip.some((e) => e.cardId === params.cardId)) return "该装备不可选"; if (!Object.values(vp.equipment).includes(params.cardId)) return "该牌不在目标装备区"; return null; } if (zone === "hand") { if (typeof params.handIndex !== "number") return "handIndex required"; if (params.handIndex < 0 || params.handIndex >= vp.hand.length) return "handIndex 越界"; return null; } return "zone required (equipment|hand)"; } return "当前不是挑衅询问"; }, async (st, params) => { const requestType = (st.pendingSlots.get(seat)?.atom)?.requestType; if (requestType === SLASH_REQUEST) st.localVars[SLASH_CHOICE_VAR] = { cardId: params.cardId, target: params.target }; else if (requestType === PICK_REQUEST$1) st.localVars[PICK_RESULT_KEY$2] = { zone: params.zone, cardId: params.cardId ?? null, handIndex: params.handIndex ?? null }; })); } return () => { for (const u of unloaders) u(); }; } function onMount$148(_skill, api) { api.defineAction("use", { label: DISPLAY_NAME$52, style: "danger", prompt: { type: "selectTarget", title: "挑衅:选择一名攻击范围包含你的角色", description: "其须对你出杀且造成伤害,否则你弃其一张牌(本阶段限两次)", targetFilter: { min: 1, max: 1, filter: (view, t) => { const me = view.currentPlayerIndex; if (t === me) return false; const tp = view.players[t]; if (!tp || tp.alive === false) return false; return viewCanAttack(view.players, view.cardMap, t, me); } } }, activeWhen: (ctx) => { if (!defaultPlayActive(ctx)) return false; const used = ctx.view.players[ctx.perspectiveIdx]?.turnUsage?.[COUNT_KEY$1]; return (typeof used === "number" ? used : 0) < MAX_USES; } }); api.defineAction("respond", { label: DISPLAY_NAME$52, style: "primary", prompt: { type: "pickTargetCard", title: "挑衅:选择弃置的牌", target: 0, equipment: [], judge: [], handCount: 0 } }); } var SKILL_ID$38, DISPLAY_NAME$52, COUNT_KEY$1, SLASH_REQUEST, SLASH_CHOICE_VAR, PICK_REQUEST$1, PICK_VICTIM_KEY, PICK_RESULT_KEY$2, MAX_USES, _skillModule$75; var init_界挑衅 = __esmMin((() => { init_apply(); init_frame(); init_use_card(); init_action_active(); init_skill(); init_distance(); init_viewDistance(); SKILL_ID$38 = "界挑衅"; DISPLAY_NAME$52 = "挑衅"; COUNT_KEY$1 = `${SKILL_ID$38}/usedThisTurn`; SLASH_REQUEST = `${SKILL_ID$38}/出杀`; SLASH_CHOICE_VAR = `${SKILL_ID$38}/出杀选择`; PICK_REQUEST$1 = `${SKILL_ID$38}/选牌`; PICK_VICTIM_KEY = `${SKILL_ID$38}/弃牌目标`; PICK_RESULT_KEY$2 = `${SKILL_ID$38}/选牌结果`; MAX_USES = 2; _skillModule$75 = { createSkill: createSkill$160, onInit: onInit$160, onMount: onMount$148 }; })); //#endregion //#region src/engine/skills/界据守.ts var 界据守_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$159, default: () => 界据守_default, onInit: () => onInit$159, onMount: () => onMount$147 }); /** 装备牌 subtype → 装备栏位(与 装备 atom 的 inferSlot 一致) */ function slotOf$5(card) { switch (card?.subtype) { case "武器": return "武器"; case "防具": return "防具"; case "进攻马": return "进攻马"; case "防御马": return "防御马"; case "宝物": return "宝物"; default: return null; } } function createSkill$159(id, ownerId) { return { id, ownerId, name: "界据守", description: "结束阶段:翻面并摸四张牌,然后弃置一张手牌(装备牌改为使用之)" }; } function onInit$159(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "use", (state, _params) => { const myTurn = state.currentPlayerIndex === ownerId; const inEndPhase = state.phase === "弃牌" || state.phase === "回合结束"; const free = !hasBlockingPending(state); const self = state.players[ownerId]; const selfAlive = self?.alive === true; const notSkipped = !self.tags.includes(SKIP_TAG$1); const notUsed = !usedThisTurn$3(state, ownerId, "据守"); return myTurn && inEndPhase && free && selfAlive && notSkipped && notUsed ? null : "现在不能发动据守"; }, async (state, _params) => { await markOncePerTurn(state, ownerId, "据守"); await pushFrame(state, "界据守", ownerId, {}); try { await flipFaceDown(state, ownerId, "据守"); await applyAtom(state, { type: "摸牌", player: ownerId, count: 4 }); const self = state.players[ownerId]; if (self.hand.length > 0) { delete state.localVars[DISCARD_CHOICE_KEY]; await applyAtom(state, { type: "请求回应", requestType: DISCARD_RT$13, target: ownerId, prompt: { type: "useCard", title: "界据守:选择一张手牌弃置(装备牌将改为使用)", cardFilter: { filter: () => true, min: 1, max: 1 } }, mandatory: true, timeout: 20 }); let chosenId = state.localVars[DISCARD_CHOICE_KEY]; delete state.localVars[DISCARD_CHOICE_KEY]; if (!chosenId || !self.hand.includes(chosenId)) chosenId = self.hand[0]; if (chosenId && self.hand.includes(chosenId)) { const card = state.cardMap[chosenId]; const slot = slotOf$5(card); if (card?.type === "装备牌" && slot) { const currentEquip = state.players[ownerId].equipment[slot]; if (currentEquip) { const oldCard = state.cardMap[currentEquip]; if (oldCard?.name && hasSkillModule(oldCard.name)) await applyAtom(state, { type: "移除技能", player: ownerId, skillId: oldCard.name }); await applyAtom(state, { type: "卸下", player: ownerId, slot }); await applyAtom(state, { type: "移动牌", cardId: currentEquip, from: { zone: "手牌", player: ownerId }, to: { zone: "弃牌堆" } }); } await applyAtom(state, { type: "装备", player: ownerId, cardId: chosenId }); if (card?.name && hasSkillModule(card.name)) await applyAtom(state, { type: "添加技能", player: ownerId, skillId: card.name }); } else await applyAtom(state, { type: "弃置", player: ownerId, cardIds: [chosenId], voluntary: true }); } } } finally { await popFrame(state); } }); registerAction(state, skill.id, ownerId, "respond", (s, _params) => { const slot = s.pendingSlots.get(ownerId); if (!slot) return "当前不需要回应"; if (slot.atom.type !== "请求回应") return "当前不是选牌窗口"; if (slot.atom.requestType !== DISCARD_RT$13) return "当前不是据守弃牌窗口"; return null; }, async (s, params) => { const raw = Array.isArray(params.cardIds) ? params.cardIds[0] : params.cardId; const self = s.players[ownerId]; if (typeof raw === "string" && self?.hand.includes(raw)) s.localVars[DISCARD_CHOICE_KEY] = raw; }); registerBeforeHook(state, skill.id, ownerId, "阶段开始", async (ctx) => { const atom = ctx.atom; if (atom.type !== "阶段开始") return; if (atom.player !== ownerId) return; const self = ctx.state.players[ownerId]; if (atom.phase === "准备" && self?.tags.includes(SKIP_TAG$1)) { await flipFaceUp(ctx.state, ownerId, "据守"); ctx.state.localVars[SKIP_FLAG$3] = ownerId; return { kind: "cancel" }; } if (ctx.state.localVars[SKIP_FLAG$3] === ownerId) return { kind: "cancel" }; }); registerBeforeHook(state, skill.id, ownerId, "阶段结束", async (ctx) => { const atom = ctx.atom; if (atom.type !== "阶段结束") return; if (atom.player !== ownerId) return; if (ctx.state.localVars[SKIP_FLAG$3] !== ownerId) return; delete ctx.state.localVars[SKIP_FLAG$3]; await performSkipTurn(ctx.state, ownerId); return { kind: "cancel" }; }); return () => {}; } function onMount$147(_skill, api) { api.defineAction("use", { label: "界据守", style: "primary", prompt: { type: "confirm", title: "是否发动据守?(翻面摸四张,然后弃置一张手牌)", confirmLabel: "发动", cancelLabel: "不发动" }, activeWhen: (ctx) => { if (ctx.view.currentPlayerIndex !== ctx.perspectiveIdx) return false; const phase = ctx.view.phase; if (phase !== "弃牌" && phase !== "回合结束") return false; const p = ctx.view.players[ctx.perspectiveIdx]; if (!p) return false; if (p.turnUsage?.[USED_KEY$10]) return false; return true; } }); } var SKIP_TAG$1, SKIP_FLAG$3, USED_KEY$10, DISCARD_RT$13, DISCARD_CHOICE_KEY, 界据守_default; var init_界据守 = __esmMin((() => { init_apply(); init_frame(); init_face_down(); init_once_per_turn(); init_skill(); init_registry$1(); SKIP_TAG$1 = "据守/翻面"; SKIP_FLAG$3 = "据守/skipAll"; USED_KEY$10 = "据守/usedThisTurn"; DISCARD_RT$13 = "界据守/弃牌"; DISCARD_CHOICE_KEY = "据守/discardChoice"; 界据守_default = { createSkill: createSkill$159, onInit: onInit$159, onMount: onMount$147 }; })); //#endregion //#region src/engine/skills/界放权.ts var 界放权_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$158, default: () => 界放权_default, onInit: () => onInit$158, onMount: () => onMount$146 }); /** 复刻「回合结束」atom 的 per-turn 清理(cancel 回合结束后 atom.apply 不执行,需手动清理)。 * 清空 turn.vars、清所有玩家 duration='turn' 标记、清 /usedThisTurn|/healed|/givenCount|/givenTargets|/basicUsed vars。 * marks 多数由 end action 的 清过期标记 已清(仅结束玩家),此处补全所有玩家以保持与 atom 一致。 * 注意:不清理 state.localVars(放权内部标记 extraTarget/originalNext 须跨清理存活)。 */ function clearPerTurnState(state) { state.turn.vars = {}; for (const p of state.players) { p.marks = p.marks.filter((m) => m.duration !== "turn"); p.vars = Object.fromEntries(Object.entries(p.vars).filter(([k]) => !k.endsWith("/usedThisTurn") && !k.endsWith("/healed") && !k.endsWith("/givenCount") && !k.endsWith("/givenTargets") && !k.endsWith("/basicUsed"))); } } function createSkill$158(id, ownerId) { return { id, ownerId, name: "界放权", description: "跳过出牌阶段,弃牌阶段开始时弃一张手牌,令一名其他角色进行一个额外回合" }; } function onInit$158(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "respond", (s, params) => { const slot = s.pendingSlots.get(ownerId); if (slot?.atom.type !== "请求回应") return "当前不需要回应"; const rt = slot.atom.requestType; if (rt === TRIGGER_RT$6) return null; if (rt === DISCARD_RT$12) { const cardId = params.cardId; if (typeof cardId !== "string") return "请选择一张手牌弃置"; if (!s.players[ownerId].hand.includes(cardId)) return "牌不在手牌中"; return null; } if (rt === CHOOSE_TARGET_RT$2) { const target = params.target; if (typeof target !== "number") return "请选择一名其他角色"; if (target === ownerId) return "不能选择自己"; if (!s.players[target]?.alive) return "目标已死亡"; return null; } return "当前不是放权回应"; }, async (s, params) => { const rt = (s.pendingSlots.get(ownerId)?.atom)?.requestType; if (rt === TRIGGER_RT$6) s.localVars[TRIGGERED_KEY$2] = params.choice === true || params.confirmed === true; else if (rt === DISCARD_RT$12) s.localVars[DISCARD_CARD_KEY] = params.cardId; else if (rt === CHOOSE_TARGET_RT$2) s.localVars[EXTRA_TARGET_KEY] = params.target; }); registerBeforeHook(state, skill.id, ownerId, "阶段开始", async (ctx) => { const atom = ctx.atom; if (atom.type !== "阶段开始") return; if (atom.player !== ownerId) return; if (atom.phase !== "出牌") return; if (ctx.state.currentPlayerIndex !== ownerId) return; if (!ctx.state.players[ownerId]?.alive) return; delete ctx.state.localVars[TRIGGERED_KEY$2]; await applyAtom(ctx.state, { type: "请求回应", requestType: TRIGGER_RT$6, target: ownerId, prompt: { type: "confirm", title: "是否发动放权?(跳过出牌阶段,弃牌阶段开始时弃一张手牌令一名角色额外回合)", confirmLabel: "发动", cancelLabel: "不发动" }, defaultChoice: false, timeout: 10 }); if (ctx.state.localVars[TRIGGERED_KEY$2] !== true) return; ctx.state.localVars[ACTIVE_KEY$1] = true; await applyAtom(ctx.state, { type: "阶段结束", player: ownerId, phase: "出牌" }); return { kind: "cancel" }; }); registerBeforeHook(state, skill.id, ownerId, "阶段开始", async (ctx) => { const atom = ctx.atom; if (atom.type !== "阶段开始") return; if (atom.player !== ownerId) return; if (atom.phase !== "弃牌") return; if (ctx.state.currentPlayerIndex !== ownerId) return; const st = ctx.state; if (st.localVars[ACTIVE_KEY$1] !== true) return; delete st.localVars[ACTIVE_KEY$1]; const self = st.players[ownerId]; if (!self?.alive) return; if (self.hand.length === 0) return; delete st.localVars[DISCARD_CARD_KEY]; await applyAtom(st, { type: "请求回应", requestType: DISCARD_RT$12, target: ownerId, prompt: { type: "useCard", title: "放权:弃置一张手牌", cardFilter: { filter: () => true, min: 1, max: 1 } }, timeout: 15 }); const discardCardId = st.localVars[DISCARD_CARD_KEY]; delete st.localVars[DISCARD_CARD_KEY]; if (!discardCardId || !self.hand.includes(discardCardId)) return; await applyAtom(st, { type: "弃置", player: ownerId, cardIds: [discardCardId], voluntary: true }); delete st.localVars[EXTRA_TARGET_KEY]; await applyAtom(st, { type: "请求回应", requestType: CHOOSE_TARGET_RT$2, target: ownerId, prompt: { type: "choosePlayer", title: "放权:选择一名其他角色进行一个额外回合", min: 1, max: 1, filter: (view, target) => target !== ownerId && view.players[target]?.alive === true }, timeout: 15 }); const extraTarget = st.localVars[EXTRA_TARGET_KEY]; if (typeof extraTarget !== "number" || !st.players[extraTarget]?.alive) delete st.localVars[EXTRA_TARGET_KEY]; }); registerBeforeHook(state, skill.id, ownerId, "回合结束", async (ctx) => { const atom = ctx.atom; if (atom.type !== "回合结束") return; const st = ctx.state; const player = atom.player; if (typeof player !== "number") return; if (player === ownerId) { const extraTarget = st.localVars[EXTRA_TARGET_KEY]; if (typeof extraTarget !== "number" || !st.players[extraTarget]?.alive) return; if (!st.players[ownerId]?.alive) { delete st.localVars[EXTRA_TARGET_KEY]; return; } const originalNext = st.currentPlayerIndex; st.localVars[ORIGINAL_NEXT_KEY] = originalNext; clearPerTurnState(st); st.currentPlayerIndex = extraTarget; await startTurn(st, extraTarget); return { kind: "cancel" }; } if (player === st.localVars[EXTRA_TARGET_KEY]) { const originalNext = st.localVars[ORIGINAL_NEXT_KEY]; delete st.localVars[EXTRA_TARGET_KEY]; delete st.localVars[ORIGINAL_NEXT_KEY]; if (typeof originalNext !== "number" || !st.players[originalNext]?.alive) return; clearPerTurnState(st); st.currentPlayerIndex = originalNext; await startTurn(st, originalNext); return { kind: "cancel" }; } }); return () => {}; } function onMount$146(_skill, api) { api.defineAction("respond", { label: "界放权", style: "primary", prompt: { type: "confirm", title: "是否发动界放权?", confirmLabel: "发动", cancelLabel: "不发动" } }); } var TRIGGER_RT$6, DISCARD_RT$12, CHOOSE_TARGET_RT$2, TRIGGERED_KEY$2, ACTIVE_KEY$1, DISCARD_CARD_KEY, EXTRA_TARGET_KEY, ORIGINAL_NEXT_KEY, 界放权_default; var init_界放权 = __esmMin((() => { init_apply(); init_turn(); init_skill(); TRIGGER_RT$6 = "界放权/trigger"; DISCARD_RT$12 = "界放权/discard"; CHOOSE_TARGET_RT$2 = "界放权/chooseTarget"; TRIGGERED_KEY$2 = "放权/triggered"; ACTIVE_KEY$1 = "放权/active"; DISCARD_CARD_KEY = "放权/discardCard"; EXTRA_TARGET_KEY = "放权/extraTarget"; ORIGINAL_NEXT_KEY = "放权/originalNext"; 界放权_default = { createSkill: createSkill$158, onInit: onInit$158, onMount: onMount$146 }; })); //#endregion //#region src/engine/skills/界放逐.ts var 界放逐_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$157, default: () => 界放逐_default, onInit: () => onInit$157, onMount: () => onMount$145 }); function createSkill$157(id, ownerId) { return { id, ownerId, name: "界放逐", description: "受到伤害后,令一名其他角色翻面,并摸 X 张牌(X=已损失体力)" }; } function onInit$157(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "respond", (st, _params) => { const slot = st.pendingSlots.get(ownerId); if (!slot) return "当前不需要回应"; const atom = slot.atom; if (atom["type"] !== "请求回应") return "当前不需要回应"; const rt = atom["requestType"]; if (rt !== CONFIRM_RT$35 && rt !== TARGET_RT$16) return "当前不是放逐询问"; return null; }, async (st, params) => { const rt = (st.pendingSlots.get(ownerId)?.atom)?.requestType; if (rt === CONFIRM_RT$35) st.localVars[CONFIRMED_KEY$29] = params.choice === true; else if (rt === TARGET_RT$16) { const t = params.targets?.[0] ?? (typeof params.target === "number" ? params.target : void 0); if (typeof t === "number") st.localVars[TARGET_KEY$24] = t; } }); registerAfterHook(state, skill.id, ownerId, "受到伤害后", async (ctx) => { const atom = ctx.atom; if (atom.target !== ownerId) return; if ((atom.amount ?? 0) <= 0) return; const self = ctx.state.players[ownerId]; if (!self?.alive) return; const lostHealth = self.maxHealth - getHealthValue(self); if (lostHealth <= 0) return; if (!ctx.state.players.some((p) => p.alive && p.index !== ownerId)) return; delete ctx.state.localVars[CONFIRMED_KEY$29]; await applyAtom(ctx.state, { type: "请求回应", requestType: CONFIRM_RT$35, target: ownerId, prompt: { type: "confirm", title: `是否发动界放逐?(令一名其他角色翻面并摸 ${lostHealth} 张牌)`, confirmLabel: "发动", cancelLabel: "不发动" }, defaultChoice: false, timeout: 10 }); if (!ctx.state.localVars[CONFIRMED_KEY$29]) return; delete ctx.state.localVars[TARGET_KEY$24]; await applyAtom(ctx.state, { type: "请求回应", requestType: TARGET_RT$16, target: ownerId, prompt: { type: "choosePlayer", title: "界放逐:选择一名其他角色(翻面并摸牌)", min: 1, max: 1, filter: (_view, t) => t !== ownerId && ctx.state.players[t]?.alive === true }, timeout: 15 }); const target = ctx.state.localVars[TARGET_KEY$24]; delete ctx.state.localVars[TARGET_KEY$24]; if (typeof target !== "number") return; if (!ctx.state.players[target]?.alive) return; await flipFaceDown(ctx.state, target, "放逐"); await applyAtom(ctx.state, { type: "摸牌", player: target, count: lostHealth }); }); registerBeforeHook(state, skill.id, ownerId, "阶段开始", async (ctx) => { const atom = ctx.atom; if (atom.type !== "阶段开始") return; const player = atom.player; if (player === void 0) return; const p = ctx.state.players[player]; if (!p) return; if (atom.phase === "准备" && p.tags.includes(SKIP_TAG)) { await flipFaceUp(ctx.state, player, "放逐"); ctx.state.localVars[SKIP_FLAG$2] = player; return { kind: "cancel" }; } if (ctx.state.localVars[SKIP_FLAG$2] === player) return { kind: "cancel" }; }); registerBeforeHook(state, skill.id, ownerId, "阶段结束", async (ctx) => { const atom = ctx.atom; if (atom.type !== "阶段结束") return; const player = atom.player; if (player === void 0) return; if (ctx.state.localVars[SKIP_FLAG$2] !== player) return; delete ctx.state.localVars[SKIP_FLAG$2]; await performSkipTurn(ctx.state, player); return { kind: "cancel" }; }); return () => {}; } function onMount$145(_skill, api) { api.defineAction("respond", { label: "界放逐", style: "primary", prompt: { type: "confirm", title: "是否发动界放逐?", confirmLabel: "发动", cancelLabel: "不发动" } }); } var CONFIRM_RT$35, TARGET_RT$16, CONFIRMED_KEY$29, TARGET_KEY$24, SKIP_TAG, SKIP_FLAG$2, 界放逐_default; var init_界放逐 = __esmMin((() => { init_types(); init_apply(); init_face_down(); init_skill(); CONFIRM_RT$35 = "界放逐/confirm"; TARGET_RT$16 = "界放逐/target"; CONFIRMED_KEY$29 = "放逐/confirmed"; TARGET_KEY$24 = "放逐/target"; SKIP_TAG = "放逐/翻面"; SKIP_FLAG$2 = "放逐/skipAll"; 界放逐_default = { createSkill: createSkill$157, onInit: onInit$157, onMount: onMount$145 }; })); //#endregion //#region src/engine/skills/界救援.ts var 界救援_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$156, default: () => 界救援_default, onInit: () => onInit$156 }); function createSkill$156(id, ownerId) { return { id, ownerId, name: "界救援", description: "主公技:其他吴势力角色于其回合内回复体力时,若其体力≥你,其可改为令你回复1点体力,然后其摸一张牌" }; } function onInit$156(skill, state) { const ownerId = skill.ownerId; registerBeforeHook(state, skill.id, ownerId, "回复体力", async (ctx) => { const atom = ctx.atom; if (ownerId !== 0) return; const target = atom.target; if (typeof target !== "number") return; if (target === ownerId) return; if (ctx.state.currentPlayerIndex !== target) return; const healer = ctx.state.players[target]; const sunquan = ctx.state.players[ownerId]; if (!healer?.alive || !sunquan?.alive) return; if (healer.faction !== "吴") return; if (getHealthValue(healer) < getHealthValue(sunquan)) return; delete ctx.state.localVars[CONFIRMED_VAR$4]; await applyAtom(ctx.state, { type: "请求回应", requestType: REQUEST_TYPE$2, target, prompt: { type: "confirm", title: `界救援:是否改为令${sunquan.name}回复1点体力,然后你摸一张牌?`, confirmLabel: "改为救援", cancelLabel: "不发动" }, defaultChoice: false, timeout: 30 }); if (ctx.state.localVars[CONFIRMED_VAR$4] === true) { await applyAtom(ctx.state, { type: "回复体力", target: ownerId, amount: 1 }); await applyAtom(ctx.state, { type: "摸牌", player: target, count: 1 }); return { kind: "cancel" }; } }); const offs = []; for (const p of state.players) { const pid = p.index; if (pid === ownerId) continue; if (p.faction !== "吴") continue; const off = registerAction(state, skill.id, pid, "respond", (st, _params) => { const slot = st.pendingSlots.get(pid); if (!slot) return "当前不需要回应"; const a = slot.atom; if (a["type"] !== "请求回应") return "当前不需要回应"; if (a["requestType"] !== REQUEST_TYPE$2) return "当前不是界救援询问"; return null; }, async (st, params) => { st.localVars[CONFIRMED_VAR$4] = params.choice === true || params.confirmed === true; }); offs.push(off); } return () => { for (const off of offs) off(); }; } var REQUEST_TYPE$2, CONFIRMED_VAR$4, 界救援_default; var init_界救援 = __esmMin((() => { init_types(); init_apply(); init_skill(); REQUEST_TYPE$2 = "界救援/choose"; CONFIRMED_VAR$4 = "界救援/confirmed"; 界救援_default = { createSkill: createSkill$156, onInit: onInit$156 }; })); //#endregion //#region src/engine/skills/界断粮.ts var 界断粮_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$155, onInit: () => onInit$155, onMount: () => onMount$144 }); /** 界断粮可用牌:黑色基本牌或黑色装备牌(同标版范围) */ function isDuanliangCard(card) { if (!card) return false; if (card.color !== "黑") return false; return card.type === "基本牌" || card.type === "装备牌"; } function createSkill$155(id, ownerId) { return { id, ownerId, name: "界断粮", description: "将一张黑色基本牌或黑色装备牌当【兵粮寸断】使用;若本回合未造成过伤害,使用时无距离限制" }; } function onInit$155(skill, state) { const ownerId = skill.ownerId; registerAfterHook(state, skill.id, ownerId, "造成伤害后", async (ctx) => { const atom = ctx.atom; if (atom.source !== ownerId) return; if ((atom.amount ?? 0) <= 0) return; if (ctx.state.turn.vars[DMG_VAR$1]) return; ctx.state.turn.vars[DMG_VAR$1] = true; await applyAtom(ctx.state, { type: "回合用量", player: ownerId, key: DMG_VAR$1, value: true }); }); registerAction(state, skill.id, ownerId, "use", (state, params) => { const myTurn = state.currentPlayerIndex === ownerId; const inActPhase = state.phase === "出牌"; const free = !hasBlockingPending(state); const self = state.players[ownerId]; if (!self?.alive) return "玩家不存在或已死亡"; if (typeof params.cardId !== "string") return "需要选择一张牌"; if (typeof params.target !== "number") return "需要选择目标"; if (!self.hand.includes(params.cardId)) return "牌不在手牌中"; if (!isDuanliangCard(state.cardMap[params.cardId])) return "需要黑色基本牌或装备牌"; const target = params.target; if (target === ownerId) return "不能对自己使用"; const targetPlayer = state.players[target]; if (!targetPlayer?.alive) return "目标不存在或已死亡"; if (targetPlayer.pendingTricks.some((t) => t.name === TRICK_NAME)) return "目标判定区已有兵粮寸断"; const inRange = !state.turn.vars[DMG_VAR$1] || effectiveDistance(state, ownerId, target) <= 1; return myTurn && inActPhase && free && inRange ? null : "界断粮使用条件不满足"; }, async (state, params) => { const from = ownerId; const cardId = params.cardId; const target = params.target; await pushFrame(state, "界断粮", from, { ...params }); await applyAtom(state, { type: "移动牌", cardId, from: { zone: "手牌", player: from }, to: { zone: "处理区" } }); await applyAtom(state, { type: "添加延时锦囊", player: target, trick: { name: TRICK_NAME, source: from, card: state.cardMap[cardId] ?? { id: cardId, name: TRICK_NAME, suit: "♣", color: "黑", rank: "A", type: "锦囊牌" } } }); await applyAtom(state, { type: "移动牌", cardId, from: { zone: "处理区" }, to: { zone: "弃牌堆" } }); await popFrame(state); }); return () => {}; } function onMount$144(skill, api) { api.defineAction("use", { label: "界断粮", style: "danger", prompt: { type: "useCardAndTarget", title: "界断粮:将黑色基本牌或装备牌当兵粮寸断使用", cardFilter: { filter: (c) => isDuanliangCard(c), min: 1, max: 1 }, targetFilter: { min: 1, max: 1, filter: (view, t) => { const me = view.currentPlayerIndex; if (t === me) return false; const tp = view.players[t]; if (!tp) return false; if (tp.alive === false) return false; if (!view.players[me]?.turnUsage?.[DMG_VAR$1]) return true; return viewEffectiveDistance(view.players, me, t) <= 1; } } }, activeWhen: (ctx) => { if (!defaultPlayActive(ctx)) return false; const p = ctx.view.players[ctx.perspectiveIdx]; if (!p) return false; return p.hand?.some((c) => isDuanliangCard(c)) ?? false; } }); } var TRICK_NAME, DMG_VAR$1; var init_界断粮 = __esmMin((() => { init_apply(); init_frame(); init_action_active(); init_skill(); init_distance(); init_viewDistance(); TRICK_NAME = "兵粮寸断"; DMG_VAR$1 = "界断粮/damageDealt"; })); //#endregion //#region src/engine/skills/界旋风.ts var 界旋风_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$154, default: () => _skillModule$74, onInit: () => onInit$154, onMount: () => onMount$143 }); function createSkill$154(id, ownerId) { return { id, ownerId, name: DISPLAY_NAME$51, description: "失去装备区里的牌,或一次性失去至少两张牌后,可依次弃置至多两名其他角色共计至多两张牌" }; } /** 玩家区域(手牌+装备)是否有牌可被弃 */ function hasDiscardableCards(player) { if (!player) return false; if (player.hand.length > 0) return true; return Object.values(player.equipment).some((id) => typeof id === "string"); } /** 是否存在可被弃牌的其他角色(存活、非自己、有牌) */ function hasValidTargets(state, ownerId) { return state.players.some((p) => p.alive && p.index !== ownerId && hasDiscardableCards(p)); } /** 选一名其他角色作为弃牌目标,并对其弃 1 张牌。 * 返回 true 表示完成一次弃牌,false 表示玩家未选目标或目标无效。 */ async function pickAndDiscard(state, ownerId, step) { delete state.localVars[TARGET_KEY$23]; await applyAtom(state, { type: "请求回应", requestType: PICK_TARGET_RT, target: ownerId, prompt: { type: "choosePlayer", title: `旋风:选择第 ${step} 名要弃置其牌的其他角色`, min: 1, max: 1, filter: (_view, t) => t !== ownerId && state.players[t]?.alive === true && hasDiscardableCards(state.players[t]) }, timeout: 20 }); const targetIdx = state.localVars[TARGET_KEY$23]; delete state.localVars[TARGET_KEY$23]; if (typeof targetIdx !== "number") return false; const target = state.players[targetIdx]; if (!target?.alive || !hasDiscardableCards(target)) return false; await runPickTargetCardPanel(state, ownerId, targetIdx, target, { mode: "discard", requestType: PICK_CARD_RT, title: `旋风:选择要从 ${target.name} 弃置的 1 张牌`, includeJudge: false }); return true; } /** 触发旋风:依次弃置至多 2 名其他角色共计至多 2 张牌 */ async function triggerXuanfeng(state, ownerId) { const me = state.players[ownerId]; if (!me?.alive) return; if (!hasValidTargets(state, ownerId)) return; delete state.localVars[CONFIRM_KEY$14]; await applyAtom(state, { type: "请求回应", requestType: CONFIRM_RT$34, target: ownerId, prompt: { type: "confirm", title: "是否发动旋风?(依次弃至多 2 名角色至多 2 张牌)", confirmLabel: "发动", cancelLabel: "不发动" }, defaultChoice: false, timeout: 10 }); if (!state.localVars[CONFIRM_KEY$14]) return; if (!me.alive || !hasValidTargets(state, ownerId)) return; await pickAndDiscard(state, ownerId, 1); if (!me.alive || !hasValidTargets(state, ownerId)) return; delete state.localVars[CONTINUE_KEY]; await applyAtom(state, { type: "请求回应", requestType: CONTINUE_RT, target: ownerId, prompt: { type: "confirm", title: "是否继续弃置第二张牌?", confirmLabel: "继续", cancelLabel: "结束" }, defaultChoice: false, timeout: 10 }); if (!state.localVars[CONTINUE_KEY]) return; if (!me.alive || !hasValidTargets(state, ownerId)) return; await pickAndDiscard(state, ownerId, 2); } function onInit$154(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "respond", (st, _params) => { const slot = st.pendingSlots.get(ownerId); if (!slot) return "当前不需要回应"; const atom = slot.atom; if (atom.type !== "请求回应") return "当前不是请求回应"; const rt = atom.requestType; if (!rt || ![ CONFIRM_RT$34, CONTINUE_RT, PICK_TARGET_RT, PICK_CARD_RT ].includes(rt)) return "当前不是旋风询问"; return null; }, async (st, params) => { const rt = (st.pendingSlots.get(ownerId)?.atom)?.requestType; if (rt === CONFIRM_RT$34) { st.localVars[CONFIRM_KEY$14] = params.choice === true || params.confirmed === true; return; } if (rt === CONTINUE_RT) { st.localVars[CONTINUE_KEY] = params.choice === true || params.confirmed === true; return; } if (rt === PICK_TARGET_RT) { const t = params.targets?.[0] ?? (typeof params.target === "number" ? params.target : void 0); if (typeof t === "number") st.localVars[TARGET_KEY$23] = t; return; } if (rt === PICK_CARD_RT) { const zone = params.zone; if (zone === "equipment") { if (typeof params.cardId !== "string") return; } else if (zone === "hand") { if (typeof params.handIndex !== "number") return; } else return; st.localVars[PICK_RESULT_KEY$3] = { zone: params.zone, cardId: params.cardId ?? null, handIndex: params.handIndex ?? null }; return; } }); registerAfterHook(state, skill.id, ownerId, "卸下", async (ctx) => { if (ctx.atom.player !== ownerId) return; await triggerXuanfeng(ctx.state, ownerId); }); registerBeforeHook(state, skill.id, ownerId, "弃置", async (ctx) => { const atom = ctx.atom; if (atom.player !== ownerId) return; const myEquip = new Set(Object.values(ctx.state.players[ownerId].equipment).filter((id) => typeof id === "string")); const cardIds = atom.cardIds ?? []; const equipLoss = cardIds.filter((id) => myEquip.has(id)).length; ctx.state.localVars[DISCARD_EQUIP_KEY$1] = equipLoss; ctx.state.localVars[DISCARD_TOTAL_KEY] = cardIds.length; }); registerAfterHook(state, skill.id, ownerId, "弃置", async (ctx) => { if (ctx.atom.player !== ownerId) return; const equipLoss = ctx.state.localVars[DISCARD_EQUIP_KEY$1]; const totalLoss = ctx.state.localVars[DISCARD_TOTAL_KEY]; delete ctx.state.localVars[DISCARD_EQUIP_KEY$1]; delete ctx.state.localVars[DISCARD_TOTAL_KEY]; if (!((equipLoss ?? 0) > 0) && !((totalLoss ?? 0) >= 2)) return; await triggerXuanfeng(ctx.state, ownerId); }); registerBeforeHook(state, skill.id, ownerId, "获得", async (ctx) => { const atom = ctx.atom; if (atom.from !== ownerId) return; const myEquip = new Set(Object.values(ctx.state.players[ownerId].equipment).filter((id) => typeof id === "string")); if (atom.cardId && myEquip.has(atom.cardId)) ctx.state.localVars[OBTAIN_EQUIP_KEY] = 1; }); registerAfterHook(state, skill.id, ownerId, "获得", async (ctx) => { if (ctx.atom.from !== ownerId) return; const lost = ctx.state.localVars[OBTAIN_EQUIP_KEY]; delete ctx.state.localVars[OBTAIN_EQUIP_KEY]; if (!lost) return; await triggerXuanfeng(ctx.state, ownerId); }); return () => {}; } function onMount$143(_skill, api) { api.defineAction("respond", { label: DISPLAY_NAME$51, style: "default", prompt: { type: "confirm", title: "是否发动旋风?", confirmLabel: "发动", cancelLabel: "不发动" } }); } var DISPLAY_NAME$51, CONFIRM_RT$34, CONTINUE_RT, PICK_TARGET_RT, PICK_CARD_RT, CONFIRM_KEY$14, CONTINUE_KEY, TARGET_KEY$23, DISCARD_EQUIP_KEY$1, DISCARD_TOTAL_KEY, OBTAIN_EQUIP_KEY, _skillModule$74; var init_界旋风 = __esmMin((() => { init_apply(); init_skill(); init_pick_card_panel(); init_vars_keys(); DISPLAY_NAME$51 = "旋风"; CONFIRM_RT$34 = "界旋风/confirm"; CONTINUE_RT = "界旋风/continue"; PICK_TARGET_RT = "界旋风/选目标"; PICK_CARD_RT = "界旋风/选牌"; CONFIRM_KEY$14 = "界旋风/confirmed"; CONTINUE_KEY = "界旋风/continue"; TARGET_KEY$23 = "界旋风/target"; DISCARD_EQUIP_KEY$1 = "界旋风/弃置equipLoss"; DISCARD_TOTAL_KEY = "界旋风/弃置totalLoss"; OBTAIN_EQUIP_KEY = "界旋风/获得equipLoss"; _skillModule$74 = { createSkill: createSkill$154, onInit: onInit$154, onMount: onMount$143 }; })); //#endregion //#region src/engine/skills/界明策.ts var 界明策_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$153, default: () => _skillModule$73, onInit: () => onInit$153, onMount: () => onMount$142 }); function createSkill$153(id, ownerId) { return { id, ownerId, name: DISPLAY_NAME$50, description: "出牌阶段限一次:交给一名其他角色一张杀或装备牌,其选择一项:①视为对你指定的另一名角色出杀(若造成伤害,执行另一项);②你与其各摸一张牌" }; } /** 判定一张卡是否为【杀】(含影子杀——name 即为 '杀') */ function isSlashCard$1(card) { return !!card && card.name === "杀"; } /** 判定一张卡是否为装备牌 */ function isEquipmentCard(card) { return !!card && card.type === "装备牌"; } /** 创建一张虚拟杀卡 id(无实体,仅用于结算流程的 cardId 引用) */ function makeVirtualSlashId(source, target, seq) { return `明策:杀:${source}:${target}:${seq}`; } /** * 执行一次"视为出杀"的完整结算(指定目标→成为目标→检测有效性→询问闪→伤害/抵消)。 * 不消耗手牌;模型参考 界仁德.virtualKill / 界乱武.virtualKill。 * * @returns 是否对 target 造成了伤害(用于界明策选项 1 命中后追加执行选项 2) */ async function virtualKill$3(state, source, target) { if (!state.players[target]?.alive) return false; const targetHealthBefore = state.players[target].health; const cardId = makeVirtualSlashId(source, target, state.seq); state.cardMap[cardId] = { id: cardId, name: "杀", suit: "", color: "无色", rank: "A", type: "基本牌" }; await runUseFlow(state, source, cardId, [target], "杀", { virtual: true }); delete state.cardMap[cardId]; return state.players[target].health < targetHealthBefore; } /** 选项 2:owner 与 target 各摸一张牌 */ async function drawBoth(state, owner, target) { if (state.players[owner]?.alive) await applyAtom(state, { type: "摸牌", player: owner, count: 1 }); if (state.players[target]?.alive) await applyAtom(state, { type: "摸牌", player: target, count: 1 }); } function onInit$153(skill, state) { const ownerId = skill.ownerId; const unloaders = []; unloaders.push(registerAction(state, skill.id, ownerId, "use", (st, params) => { if (st.currentPlayerIndex !== ownerId) return "只能在你的回合使用"; if (st.phase !== "出牌") return "只能在出牌阶段使用"; if (hasBlockingPending(st)) return "当前有未完成的询问"; if (usedThisTurn$3(st, ownerId, SKILL_ID$37)) return "本回合已使用过明策"; const self = st.players[ownerId]; if (!self?.alive) return "角色不可用"; const cardId = params.cardId; if (typeof cardId !== "string") return "需要选择一张牌"; const card = st.cardMap[cardId]; if (!isSlashCard$1(card) && !isEquipmentCard(card)) return "只能给出杀或装备牌"; const inHand = self.hand.includes(cardId); const inEquip = Object.values(self.equipment).includes(cardId); if (!inHand && !inEquip) return "牌不在你的手牌或装备区"; const targets = params.targets; if (!Array.isArray(targets) || targets.length !== 2) return "需要选择给牌目标与可能的杀目标"; const [target, killTarget] = targets; if (target === ownerId) return "给牌目标不能是自己"; if (!st.players[target]?.alive) return "给牌目标不合法"; if (killTarget === target) return "杀目标不能与给牌目标相同"; if (!st.players[killTarget]?.alive) return "杀目标不合法"; return null; }, async (st, params) => { const from = ownerId; const cardId = params.cardId; const [target, killTarget] = params.targets; await markOncePerTurn(st, from, SKILL_ID$37); await pushFrame(st, SKILL_ID$37, from, { ...params }); try { if (Object.values(st.players[from].equipment).includes(cardId)) { const slot = Object.entries(st.players[from].equipment).find(([, id]) => id === cardId)?.[0]; if (slot) await applyAtom(st, { type: "卸下", player: from, slot }); } await applyAtom(st, { type: "移动牌", cardId, from: { zone: "手牌", player: from }, to: { zone: "手牌", player: target } }); if (!st.players[target]?.alive) return; delete st.localVars[CHOICE_KEY$6]; await applyAtom(st, { type: "请求回应", requestType: CHOICE_RT$1, target, prompt: { type: "confirm", title: `明策:选择一项(确认=对 ${killTarget + 1} 号视为使用一张杀,若造成伤害再各摸一张;取消=你与陈宫各摸一张)`, confirmLabel: "①视为出杀", cancelLabel: "②双方各摸一张" }, defaultChoice: false, timeout: 20 }); const choseSlash = st.localVars[CHOICE_KEY$6] === true; delete st.localVars[CHOICE_KEY$6]; if (choseSlash) { if (await virtualKill$3(st, target, killTarget)) await drawBoth(st, from, target); } else await drawBoth(st, from, target); } finally { await popFrame(st); } })); for (const pl of state.players) { const seat = pl.index; unloaders.push(registerAction(state, skill.id, seat, "respond", (st, _params) => { const slot = st.pendingSlots.get(seat); if (!slot) return "当前不需要回应"; if (slot.atom.requestType !== CHOICE_RT$1) return "当前不是明策询问"; return null; }, async (st, params) => { if ((st.pendingSlots.get(seat)?.atom)?.requestType !== CHOICE_RT$1) return; st.localVars[CHOICE_KEY$6] = params.choice === true || params.confirmed === true; })); } return () => { for (const u of unloaders) u(); }; } function onMount$142(skill, api) { api.defineAction("use", { label: DISPLAY_NAME$50, style: "primary", prompt: { type: "useCardAndTarget", title: "明策:交给一名其他角色一张杀或装备牌;并选择可能的杀目标", description: "出牌阶段限一次。给牌后该角色选择:①视为对你指定的另一名角色出杀(若造成伤害再各摸一张);②你与其各摸一张", cardFilter: { filter: (c) => isSlashCard$1(c) || isEquipmentCard(c), min: 1, max: 1 }, targetFilter: { min: 2, max: 2, slots: [{ label: "给牌目标", filter: (view, t) => t !== view.currentPlayerIndex && !!view.players[t]?.alive }, { label: "杀目标(选项①)", filter: (view, t, ctx) => !!view.players[t]?.alive && !ctx.selected.includes(t) }] } }, activeWhen: (ctx) => defaultPlayActive(ctx) && activeUnlessUsedThisTurn(SKILL_ID$37)(ctx) }); return () => {}; } var SKILL_ID$37, DISPLAY_NAME$50, CHOICE_KEY$6, CHOICE_RT$1, _skillModule$73; var init_界明策 = __esmMin((() => { init_apply(); init_frame(); init_use_card(); init_skill(); init_action_active(); init_once_per_turn(); SKILL_ID$37 = "界明策"; DISPLAY_NAME$50 = "明策"; CHOICE_KEY$6 = "明策/choice"; CHOICE_RT$1 = "界明策/choice"; _skillModule$73 = { createSkill: createSkill$153, onInit: onInit$153, onMount: onMount$142 }; })); //#endregion //#region src/engine/skills/界智迟.ts var 界智迟_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$152, default: () => _skillModule$72, onInit: () => onInit$152 }); /** 判定一张卡是否为【杀】(含转化杀——影子卡 name 即为 '杀') */ function isSlash(card) { return !!card && card.name === "杀"; } /** 判定一张卡是否为普通锦囊牌(排除延时锦囊与响应锦囊)。 * 响应锦囊(无懈可击)与延时锦囊(乐不思蜀/兵粮寸断/闪电)均不属于"普通锦囊", * 不受智迟影响。与 validateUseCard 的普通锦囊定义一致。 * 关键:无懈可击走 runUseFlow(effect-target)→runSettlementPhase→检测有效性, * 若不排除响应锦囊,智迟激活时 owner 自己打出无懈可击会被自身 检测有效性 hook cancel, * 导致 owner 无法使用无懈可击。 */ function isNormalTrick(card) { return !!card && card.type === "锦囊牌" && card.trickSubtype !== "延时锦囊" && card.trickSubtype !== "响应锦囊"; } /** 判定一张卡是否为智迟影响范围(杀或普通锦囊) */ function isAffectingCard(card) { return isSlash(card) || isNormalTrick(card); } /** 智迟是否对本 owner 已激活(本回合) */ function isActiveFor(state, ownerId) { return state.turn.vars[ACTIVE_KEY] === ownerId; } function createSkill$152(id, ownerId) { return { id, ownerId, name: DISPLAY_NAME$49, description: "锁定技:回合外受到伤害后,本回合【杀】和普通锦囊牌对你无效", isLocked: true }; } function onInit$152(skill, state) { const ownerId = skill.ownerId; const unloaders = []; unloaders.push(registerAfterHook(state, skill.id, ownerId, "受到伤害后", async (ctx) => { if (ctx.atom.target !== ownerId) return; if (ctx.state.currentPlayerIndex === ownerId) return; if (!ctx.state.players[ownerId]?.alive) return; ctx.state.turn.vars[ACTIVE_KEY] = ownerId; })); unloaders.push(registerCardInvalidation(state, skill.id, ownerId, (st, card) => { if (!isActiveFor(st, ownerId)) return false; return isAffectingCard(card); })); return () => { for (const u of unloaders) u(); }; } var DISPLAY_NAME$49, ACTIVE_KEY, _skillModule$72; var init_界智迟 = __esmMin((() => { init_skill(); init_card_invalidation(); DISPLAY_NAME$49 = "智迟"; ACTIVE_KEY = "智迟/active"; _skillModule$72 = { createSkill: createSkill$152, onInit: onInit$152 }; })); //#endregion //#region src/engine/skills/界暴虐.ts var 界暴虐_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$151, default: () => 界暴虐_default, onInit: () => onInit$151, onMount: () => onMount$141 }); function createSkill$151(id, ownerId) { return { id, ownerId, name: "暴虐", description: "主公技:其他群势力角色造成1点伤害后可判定,黑桃则回复1点体力并获得此判定牌" }; } function onInit$151(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "respond", (s, _params) => { const slot = s.pendingSlots.get(ownerId); if (slot?.atom.type !== "请求回应") return "当前不需要回应"; if (slot.atom.requestType !== CONFIRM_RT$33) return "当前不是暴虐确认"; return null; }, async (s, params) => { s.localVars[CONFIRMED_KEY$28] = params.choice === true || params.confirmed === true; }); registerAfterHook(state, skill.id, ownerId, "造成伤害后", async (ctx) => { const atom = ctx.atom; const self = ctx.state.players[ownerId]; if (!self?.alive) return; if (self.identity !== "主公") return; if (atom.amount !== 1) return; const sourceIdx = atom.source; if (typeof sourceIdx !== "number") return; if (sourceIdx === ownerId) return; if (sourceIdx < 0) return; const source = ctx.state.players[sourceIdx]; if (!source?.alive) return; if (source.faction !== "群") return; delete ctx.state.localVars[CONFIRMED_KEY$28]; await applyAtom(ctx.state, { type: "请求回应", requestType: CONFIRM_RT$33, target: ownerId, prompt: { type: "confirm", title: `暴虐:${source.name} 造成 1 点伤害,是否进行判定?(黑桃回复1点体力并获得判定牌)`, confirmLabel: "判定", cancelLabel: "不判定" }, defaultChoice: false, timeout: 10 }); if (!ctx.state.localVars[CONFIRMED_KEY$28]) return; const discardLenBefore = ctx.state.zones.discardPile.length; await runJudgeFlow(ctx.state, ownerId, "暴虐"); const discard = ctx.state.zones.discardPile; if (discard.length <= discardLenBefore) return; const judgeCardId = discard[discard.length - 1]; const judgeCard = ctx.state.cardMap[judgeCardId]; if (!judgeCard) return; if (judgeCard.suit !== "♠") return; await applyAtom(ctx.state, { type: "移动牌", cardId: judgeCardId, from: { zone: "弃牌堆" }, to: { zone: "手牌", player: ownerId } }); await applyAtom(ctx.state, { type: "回复体力", target: ownerId, amount: 1 }); }); return () => {}; } function onMount$141(_skill, api) { api.defineAction("respond", { label: "暴虐", style: "default", prompt: { type: "confirm", title: "是否发动暴虐?(判定,黑桃回复1点体力并获得判定牌)", confirmLabel: "判定", cancelLabel: "不发动" } }); } var CONFIRM_RT$33, CONFIRMED_KEY$28, 界暴虐_default; var init_界暴虐 = __esmMin((() => { init_apply(); init_judge(); init_skill(); CONFIRM_RT$33 = "界暴虐/confirm"; CONFIRMED_KEY$28 = "暴虐/confirmed"; 界暴虐_default = { createSkill: createSkill$151, onInit: onInit$151, onMount: onMount$141 }; })); //#endregion //#region src/engine/skills/界樵拾.ts var 界樵拾_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$150, default: () => _skillModule$71, onInit: () => onInit$150, onMount: () => onMount$140 }); function createSkill$150(id, ownerId) { return { id, ownerId, name: DISPLAY_NAME$48, description: "每个结束阶段,你可以与当前回合角色各摸一张牌;然后若其与你手牌数不相等,此技能本轮失效" }; } /** 本轮是否已失效(同轮返回 true,新轮自动恢复)。 */ function isDisabledThisRound(state, ownerId) { return state.localVars[DISABLED_KEY(ownerId)] === state.turn.round; } function onInit$150(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "respond", (st, _params) => { const slot = st.pendingSlots.get(ownerId); if (!slot) return "当前不需要回应"; const atom = slot.atom; if (atom["type"] !== "请求回应") return "当前不需要回应"; if (atom["requestType"] !== CONFIRM_RT$32) return "当前不是樵拾确认"; return null; }, async (st, params) => { st.localVars[CONFIRMED_KEY$27] = params.choice === true || params.confirmed === true; }); registerAfterHook(state, skill.id, ownerId, "阶段开始", async (ctx) => { const atom = ctx.atom; if (atom.type !== "阶段开始") return; if (atom.phase !== "回合结束") return; const currentPlayer = atom.player; if (typeof currentPlayer !== "number") return; const st = ctx.state; if (!st.players[ownerId]?.alive) return; if (!st.players[currentPlayer]?.alive) return; if (isDisabledThisRound(st, ownerId)) return; delete st.localVars[CONFIRMED_KEY$27]; await applyAtom(st, { type: "请求回应", requestType: CONFIRM_RT$32, target: ownerId, prompt: { type: "confirm", title: "是否发动樵拾?(你与当前回合角色各摸一张牌;若手牌数不相等,本轮失效)", confirmLabel: "发动", cancelLabel: "不发动" }, defaultChoice: false, timeout: 15 }); if (!st.localVars[CONFIRMED_KEY$27]) return; delete st.localVars[CONFIRMED_KEY$27]; await applyAtom(st, { type: "摸牌", player: ownerId, count: 1 }); if (currentPlayer !== ownerId) { if (st.players[currentPlayer]?.alive) await applyAtom(st, { type: "摸牌", player: currentPlayer, count: 1 }); } if ((st.players[ownerId]?.hand.length ?? 0) !== (st.players[currentPlayer]?.hand.length ?? 0)) st.localVars[DISABLED_KEY(ownerId)] = st.turn.round; }); return () => {}; } function onMount$140(_skill, api) { api.defineAction("respond", { label: DISPLAY_NAME$48, style: "primary", prompt: { type: "confirm", title: "是否发动樵拾?(你与当前回合角色各摸一张牌;若手牌数不相等,本轮失效)", confirmLabel: "发动", cancelLabel: "不发动" } }); return () => {}; } var SKILL_ID$36, DISPLAY_NAME$48, CONFIRM_RT$32, CONFIRMED_KEY$27, DISABLED_KEY, _skillModule$71; var init_界樵拾 = __esmMin((() => { init_apply(); init_skill(); SKILL_ID$36 = "界樵拾"; DISPLAY_NAME$48 = "樵拾"; CONFIRM_RT$32 = `${SKILL_ID$36}/confirm`; CONFIRMED_KEY$27 = `${SKILL_ID$36}/confirmed`; DISABLED_KEY = (ownerId) => `${SKILL_ID$36}/disabledRound/${ownerId}`; _skillModule$71 = { createSkill: createSkill$150, onInit: onInit$150, onMount: onMount$140 }; })); //#endregion //#region src/engine/skills/界武圣.ts var 界武圣_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$149, default: () => 界武圣_default, onInit: () => onInit$149, onMount: () => onMount$139 }); function createSkill$149(id, ownerId) { return { id, ownerId, name: "界武圣", description: "你可以将一张红色牌(手牌或装备区)当【杀】使用或打出" }; } /** 影子卡 id:${原id}#界武圣。 * 后缀必须是**技能 id**(界武圣),与客户端约定一致(浏览器 usePlayInteraction 与 * 无头 availableActions 都按 `${选中牌 id 以 # 连接}#${skillId}` 构造主 action 的 cardId)。 * 写成技能名(武圣)会让客户端构造的影子 id 与引擎创建的不一致 → 主 action * validate 读不到影子卡 → 界武圣整类转化在客户端恒被拒。 */ function shadowIdOf$15(cardId) { return `${cardId}#界武圣`; } function onInit$149(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "transform", (state, params) => { const self = state.players[ownerId]; const selfAlive = self.alive === true; const cardId = params.cardId; const cardIdOk = typeof cardId === "string"; const card = cardIdOk ? state.cardMap[cardId] : void 0; const cardInHand = cardIdOk && self.hand.includes(cardId); const cardInEquip = cardIdOk && Object.values(self.equipment).some((id) => id === cardId); const isRed = !!card && card.color === "红"; const myTurn = state.currentPlayerIndex === ownerId; const free = !hasBlockingPending(state); const slot = state.pendingSlots.get(ownerId); const atomType = (slot?.atom)?.type; const reqType = (slot?.atom)?.requestType; const atomTarget = (slot?.atom)?.target; return selfAlive && (cardInHand || cardInEquip) && isRed && (myTurn && free || !!slot && atomTarget === ownerId && (atomType === "询问杀" || atomType === "请求回应" && reqType === "杀/respondKill")) ? null : "现在不能使用武圣"; }, async (state, params) => { const cardId = params.cardId; const shadowId = shadowIdOf$15(cardId); const self = state.players[ownerId]; const equipSlotEntry = Object.entries(self.equipment).find(([, id]) => id === cardId); if (equipSlotEntry) { const slot = equipSlotEntry[0]; params["_origSlot"] = slot; await applyAtom(state, { type: "卸下", player: ownerId, slot }); } await applyAtom(state, { type: "当作", player: ownerId, cardIds: [cardId], shadowId, outputName: "杀" }); }, (state, params) => { const cardId = params.cardId; const sId = shadowIdOf$15(cardId); const self = state.players[ownerId]; const origSlot = params["_origSlot"]; delete state.cardMap[sId]; const idx = self.hand.indexOf(sId); if (idx >= 0) if (origSlot) { self.hand.splice(idx, 1); self.equipment[origSlot] = cardId; if (origSlot === "武器") { const card = state.cardMap[cardId]; self.vars[DISTANCE_ATTACK_RANGE_KEY] = card?.range ?? 1; } } else self.hand[idx] = cardId; }); const unloadAlt = declareAlternativeResponse(state, ownerId, "询问杀"); const unloadRangeExemptor = registerAttackRangeExemptor(state, ownerId, (_st, _from, _to, cardId) => { if (!cardId) return false; return _st.cardMap[cardId]?.suit === "♦"; }); return () => { unloadAlt(); unloadRangeExemptor(); }; } function onMount$139(skill, api) { api.defineAction("transform", { label: "界武圣", style: "passive", prompt: { type: "useCardAndTarget", title: "选择一张红色牌当杀使用", cardFilter: { filter: (c) => c.color === "红", min: 1, max: 1 }, targetFilter: { min: 1, max: 1, filter: (view, t) => viewCanAttack(view.players, view.cardMap, view.currentPlayerIndex, t) } }, transform: (card) => ({ name: "杀", sourceCardId: card.id, fromSkill: skill.id }), activeWhen: (ctx) => { const p = ctx.view.players[ctx.perspectiveIdx]; if (!p?.alive) return false; const hasRedInHand = p.hand?.some((c) => c.color === "红") ?? false; const hasRedEquip = Object.values(p.equipment ?? {}).some((id) => { return (id ? ctx.view.cardMap[id] : void 0)?.color === "红"; }); if (!hasRedInHand && !hasRedEquip) return false; const ownTurnCanSlash = defaultPlayActive(ctx) && viewCanSlash(ctx.view, ctx.perspectiveIdx); const slot = ctx.view.pending; const atomType = (slot?.atom)?.type; const reqType = (slot?.atom)?.requestType; const askedKill = !!slot && slot.target === ctx.perspectiveIdx && (atomType === "询问杀" || atomType === "请求回应" && reqType === "杀/respondKill"); return ownTurnCanSlash || askedKill; } }); } var 界武圣_default; var init_界武圣 = __esmMin((() => { init_skill(); init_apply(); init_viewDistance(); init_action_active(); init_distance(); init_vars_keys(); 界武圣_default = { createSkill: createSkill$149, onInit: onInit$149, onMount: onMount$139 }; })); //#endregion //#region src/engine/skills/界殚心.ts var 界殚心_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$148, default: () => _skillModule$70, onInit: () => onInit$148, onMount: () => onMount$138 }); function createSkill$148(id, ownerId) { return { id, ownerId, name: DISPLAY_NAME$47, description: "当你受到伤害后,你可摸X张牌,然后修改\"矫诏\"(X为你修改\"矫诏\"的次数)" }; } /** 当前修改"矫诏"的累计次数(默认 0)。 */ function modCount(state, ownerId) { const v = state.players[ownerId]?.vars[MOD_COUNT_KEY]; return typeof v === "number" ? v : 0; } function onInit$148(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "respond", (st) => { const slot = st.pendingSlots.get(ownerId); if (!slot) return "当前不需要回应"; const atom = slot.atom; if (atom.type !== "请求回应") return "当前不需要回应"; if (atom.requestType !== CONFIRM_RT$31) return "当前不是殚心询问"; return null; }, async (st, params) => { st.localVars[CONFIRMED_KEY$26] = params.choice === true; }); registerAfterHook(state, skill.id, ownerId, "受到伤害后", async (ctx) => { const atom = ctx.atom; if (atom.target !== ownerId) return; if ((atom.amount ?? 0) <= 0) return; if (!ctx.state.players[ownerId]?.alive) return; delete ctx.state.localVars[CONFIRMED_KEY$26]; await applyAtom(ctx.state, { type: "请求回应", requestType: CONFIRM_RT$31, target: ownerId, prompt: { type: "confirm", title: "殚心:是否发动?(摸X张牌并修改\"矫诏\",X为已修改次数)", confirmLabel: "发动", cancelLabel: "不发动" }, defaultChoice: false, timeout: 15 }); const confirmed = ctx.state.localVars[CONFIRMED_KEY$26] === true; delete ctx.state.localVars[CONFIRMED_KEY$26]; if (!confirmed) return; const x = modCount(ctx.state, ownerId); if (x > 0) await applyAtom(ctx.state, { type: "摸牌", player: ownerId, count: x }); const nextCount = x + 1; ctx.state.players[ownerId].vars[MOD_COUNT_KEY] = nextCount; }); return () => {}; } function onMount$138(_skill, api) { api.defineAction("respond", { label: DISPLAY_NAME$47, style: "primary", prompt: { type: "confirm", title: "是否发动殚心?", confirmLabel: "发动", cancelLabel: "不发动" } }); } var SKILL_ID$35, DISPLAY_NAME$47, CONFIRM_RT$31, CONFIRMED_KEY$26, MOD_COUNT_KEY, _skillModule$70; var init_界殚心 = __esmMin((() => { init_apply(); init_skill(); SKILL_ID$35 = "界殚心"; DISPLAY_NAME$47 = "殚心"; CONFIRM_RT$31 = `${SKILL_ID$35}/confirm`; CONFIRMED_KEY$26 = `${SKILL_ID$35}/confirmed`; MOD_COUNT_KEY = `${SKILL_ID$35}/修改次数`; _skillModule$70 = { createSkill: createSkill$148, onInit: onInit$148, onMount: onMount$138 }; })); //#endregion //#region src/engine/skills/界毅重.ts var 界毅重_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$147, default: () => _skillModule$69, onInit: () => onInit$147, onMount: () => onMount$137 }); function createSkill$147(id, ownerId) { return { id, ownerId, name: DISPLAY_NAME$46, description: "锁定技,体力值大于等于你的角色的黑色【杀】对你无效;手牌数小于等于你的角色无法响应你的黑色【杀】", isLocked: true }; } /** 当前结算帧正在结算的【杀】牌 id(从 frame.params.cardId 读,与杀.execute pushFrame 一致) */ function currentSlashCardId(state) { const frame = state.settlementStack[state.settlementStack.length - 1]; if (!frame) return void 0; const id = frame.params["cardId"]; return typeof id === "string" ? id : void 0; } /** 是否为黑色【杀】(转化杀按影子卡 color 判定;无色不算黑杀) */ function isBlackSlash(state, cardId) { if (!cardId) return false; const card = state.cardMap[cardId]; if (!card) return false; if (card.name !== "杀") return false; return card.color === "黑"; } function onInit$147(skill, state) { const ownerId = skill.ownerId; registerBeforeHook(state, skill.id, ownerId, "检测有效性", async (ctx) => { const atom = ctx.atom; if (atom.target !== ownerId) return; const source = atom.source; if (source === void 0) return; if (getHealthValue(ctx.state.players[source]) < getHealthValue(ctx.state.players[ownerId])) return; if (!isBlackSlash(ctx.state, atom.cardId)) return; return { kind: "cancel" }; }); registerBeforeHook(state, skill.id, ownerId, "询问闪", async (ctx) => { const atom = ctx.atom; if (atom.source !== ownerId) return; const target = atom.target; if (target === void 0) return; if (!isBlackSlash(ctx.state, currentSlashCardId(ctx.state))) return; if ((ctx.state.players[target]?.hand.length ?? 0) > (ctx.state.players[ownerId]?.hand.length ?? 0)) return; return { kind: "cancel" }; }); return () => {}; } function onMount$137(_skill, _api) {} var DISPLAY_NAME$46, _skillModule$69; var init_界毅重 = __esmMin((() => { init_types(); init_skill(); DISPLAY_NAME$46 = "毅重"; _skillModule$69 = { createSkill: createSkill$147, onInit: onInit$147, onMount: onMount$137 }; })); //#endregion //#region src/engine/skills/界求援.ts var 界求援_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$146, default: () => _skillModule$68, onInit: () => onInit$146, onMount: () => onMount$136 }); /** 是否为杀或伤害锦囊(求援触发条件) */ function isKillOrDamageTrick(card) { if (!card) return false; if (card.name === "杀") return true; return DAMAGE_TRICK_NAMES.has(card.name); } function createSkill$146(id, ownerId) { return { id, ownerId, name: DISPLAY_NAME$45, description: "当你成为杀或伤害锦囊的目标时,可令另一名其他角色选一项:1.交给你一张同类型不同牌名的牌;2.成为此牌的额外目标" }; } /** 虚拟杀结算(参考界仁德/界诛害):source → target,无实体卡,不计入出杀次数。 */ async function virtualKill$2(state, source, target) { if (!state.players[target]?.alive) return; const cardId = `${SKILL_ID$34}:杀:${source}:${target}:${state.seq}`; state.cardMap[cardId] = { id: cardId, name: "杀", suit: "", color: "无色", rank: "A", type: "基本牌" }; await runUseFlow(state, source, cardId, [target], "杀", { virtual: true }); delete state.cardMap[cardId]; } /** * 求援主流程:owner 被指定为 cardId 的目标后,询问 owner + helper 执行选项。 * cardId 必须为杀或伤害锦囊;source 为该牌使用者。 */ async function runQiuYuan(state, ownerId, source, cardId) { const self = state.players[ownerId]; if (!self?.alive) return; const card = state.cardMap[cardId]; if (!card) return; if (state.players.filter((p) => p.alive && p.index !== ownerId && p.index !== source).map((p) => p.index).length === 0) return; delete state.localVars[CONFIRM_KEY$13]; await applyAtom(state, { type: "请求回应", requestType: CONFIRM_RT$30, target: ownerId, prompt: { type: "confirm", title: `求援:是否发动?(被 ${card.name} 指定)`, confirmLabel: "发动", cancelLabel: "不发动" }, defaultChoice: false, timeout: 15 }); if (!state.localVars[CONFIRM_KEY$13]) return; delete state.localVars[CONFIRM_KEY$13]; delete state.localVars[TARGET_KEY$22]; await applyAtom(state, { type: "请求回应", requestType: CHOOSE_TARGET_RT$1, target: ownerId, prompt: { type: "choosePlayer", title: "求援:选择一名其他角色", min: 1, max: 1, filter: (_view, idx) => idx !== ownerId && idx !== source && state.players[idx]?.alive === true }, timeout: 15 }); const helperIdx = state.localVars[TARGET_KEY$22]; delete state.localVars[TARGET_KEY$22]; if (typeof helperIdx !== "number") return; if (!state.players[helperIdx]?.alive) return; if (helperIdx === ownerId || helperIdx === source) return; delete state.localVars[OPTION_KEY$3]; await applyAtom(state, { type: "请求回应", requestType: OPTION_RT$3, target: helperIdx, prompt: { type: "confirm", title: `求援:1.交给 ${self.name} 一张同类型不同牌名的牌 2.成为此牌的额外目标`, confirmLabel: "给牌", cancelLabel: "成为额外目标" }, defaultChoice: false, timeout: 15 }); const option = state.localVars[OPTION_KEY$3]; delete state.localVars[OPTION_KEY$3]; if (option === 1) { const matching = state.players[helperIdx].hand.filter((id) => { const c = state.cardMap[id]; return c?.type === card.type && c.name !== card.name; }); if (matching.length === 0) { await virtualKill$2(state, source, helperIdx); return; } delete state.localVars[CARD_KEY$1]; await applyAtom(state, { type: "请求回应", requestType: GIVE_CARD_RT, target: helperIdx, prompt: { type: "useCard", title: `求援:交给 ${self.name} 一张同类型不同牌名的牌`, cardFilter: { filter: (c) => c.type === card.type && c.name !== card.name, min: 1, max: 1 } }, timeout: 20 }); let giveCardId = state.localVars[CARD_KEY$1]; delete state.localVars[CARD_KEY$1]; if (!giveCardId || !matching.includes(giveCardId)) giveCardId = matching[0]; await applyAtom(state, { type: "给予", cardId: giveCardId, from: helperIdx, to: ownerId }); } else await virtualKill$2(state, source, helperIdx); } function onInit$146(skill, state) { const ownerId = skill.ownerId; const unloaders = []; unloaders.push(registerAction(state, skill.id, ownerId, "respond", (st, params) => { const slot = st.pendingSlots.get(ownerId); if (slot?.atom.type !== "请求回应") return "当前不需要回应"; const rt = slot.atom.requestType; if (rt === CONFIRM_RT$30) return null; if (rt === CHOOSE_TARGET_RT$1) { if (typeof params.target !== "number") return "请选择一名角色"; return null; } return "当前不是求援询问"; }, async (st, params) => { const rt = (st.pendingSlots.get(ownerId)?.atom)?.requestType; if (rt === CONFIRM_RT$30) st.localVars[CONFIRM_KEY$13] = params.choice === true; else if (rt === CHOOSE_TARGET_RT$1) st.localVars[TARGET_KEY$22] = params.target; })); for (const p of state.players) { if (p.index === ownerId) continue; const pid = p.index; unloaders.push(registerAction(state, skill.id, pid, "respond", (st, params) => { const slot = st.pendingSlots.get(pid); if (slot?.atom.type !== "请求回应") return "当前不需要回应"; const rt = slot.atom.requestType; if (rt === OPTION_RT$3) return null; if (rt === GIVE_CARD_RT) { const cardId = params.cardId; if (typeof cardId !== "string") return "请选择一张牌"; if (!st.players[pid]?.hand.includes(cardId)) return "牌不在你的手牌中"; return null; } return "当前不是求援询问"; }, async (st, params) => { const rt = (st.pendingSlots.get(pid)?.atom)?.requestType; if (rt === OPTION_RT$3) st.localVars[OPTION_KEY$3] = params.choice === true ? 1 : 2; else if (rt === GIVE_CARD_RT) st.localVars[CARD_KEY$1] = params.cardId; })); } unloaders.push(registerAfterHook(state, skill.id, ownerId, "成为目标", async (ctx) => { const atom = ctx.atom; if (atom.target !== ownerId) return; const st = ctx.state; const cardId = atom.cardId; if (!cardId) return; const card = st.cardMap[cardId]; if (!isKillOrDamageTrick(card)) return; const source = atom.source; if (typeof source !== "number") return; const processedKey = `${PROCESSED_PREFIX$1}${cardId}`; if (st.localVars[processedKey]) return; st.localVars[processedKey] = true; await runQiuYuan(st, ownerId, source, cardId); })); unloaders.push(registerBeforeHook(state, skill.id, ownerId, "请求回应", async (ctx) => { const atom = ctx.atom; if (atom.requestType !== "无懈可击") return; if (atom.cancelTarget !== ownerId) return; const st = ctx.state; const frame = topFrame(st); if (!frame) return; const cardId = frame.params?.cardId; if (!cardId) return; const card = st.cardMap[cardId]; if (!isKillOrDamageTrick(card)) return; if (card.name === "杀" || card.name === "决斗") return; const processedKey = `${PROCESSED_PREFIX$1}${cardId}`; if (st.localVars[processedKey]) return; st.localVars[processedKey] = true; const source = frame.from; await runQiuYuan(st, ownerId, source, cardId); })); return () => unloaders.forEach((fn) => fn()); } function onMount$136(_skill, api) { api.defineAction("respond", { label: DISPLAY_NAME$45, style: "primary", prompt: { type: "confirm", title: DISPLAY_NAME$45, confirmLabel: "确认", cancelLabel: "取消" } }); return () => {}; } var SKILL_ID$34, DISPLAY_NAME$45, CONFIRM_RT$30, CHOOSE_TARGET_RT$1, OPTION_RT$3, GIVE_CARD_RT, CONFIRM_KEY$13, TARGET_KEY$22, OPTION_KEY$3, CARD_KEY$1, PROCESSED_PREFIX$1, DAMAGE_TRICK_NAMES, _skillModule$68; var init_界求援 = __esmMin((() => { init_apply(); init_frame(); init_use_card(); init_skill(); SKILL_ID$34 = "界求援"; DISPLAY_NAME$45 = "求援"; CONFIRM_RT$30 = `${SKILL_ID$34}/confirm`; CHOOSE_TARGET_RT$1 = `${SKILL_ID$34}/chooseTarget`; OPTION_RT$3 = `${SKILL_ID$34}/option`; GIVE_CARD_RT = `${SKILL_ID$34}/giveCard`; CONFIRM_KEY$13 = `${SKILL_ID$34}/confirmed`; TARGET_KEY$22 = `${SKILL_ID$34}/target`; OPTION_KEY$3 = `${SKILL_ID$34}/option`; CARD_KEY$1 = `${SKILL_ID$34}/cardId`; PROCESSED_PREFIX$1 = `${SKILL_ID$34}/processed/`; DAMAGE_TRICK_NAMES = new Set([ "决斗", "南蛮入侵", "万箭齐发", "火攻" ]); _skillModule$68 = { createSkill: createSkill$146, onInit: onInit$146, onMount: onMount$136 }; })); //#endregion //#region src/engine/skills/界洛神.ts var 界洛神_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$145, default: () => 界洛神_default, onInit: () => onInit$145, onMount: () => onMount$135 }); function createSkill$145(id, ownerId) { return { id, ownerId, name: "界洛神", description: "准备阶段判定:黑色获得判定牌并重复;以此法获得的牌本回合不计入手牌上限" }; } /** 判定牌是否为黑色(黑桃 ♠ 或梅花 ♣) */ function isBlack(card) { return !!card && (card.suit === "♠" || card.suit === "♣"); } /** 读本回合洛神豁免牌列表(可能为空) */ function readExemptList(state) { const v = state.turn.vars[EXEMPT_VAR]; return Array.isArray(v) ? v : []; } function onInit$145(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "respond", (state, _params) => { if (state.pendingSlots.get(ownerId)?.atom.type !== "请求回应") return "当前不需要回应"; const requestType = state.pendingSlots.get(ownerId).atom.requestType; if (requestType !== "界洛神/confirm" && requestType !== "界洛神/continue") return "当前不是洛神询问"; return null; }, async (state, params) => { state.localVars["洛神/confirmed"] = params.choice === true || params.confirmed === true; }); registerAfterHook(state, skill.id, ownerId, "判定牌生效后", async (ctx) => { const atom = ctx.atom; if (atom.player !== ownerId) return; if (atom.judgeType !== "洛神") return; const processing = frameCards(ctx.state); if (processing.length === 0) { ctx.state.localVars["洛神/lastResult"] = "red"; return; } const judgeCardId = processing[processing.length - 1]; const judgeCard = ctx.state.cardMap[judgeCardId]; ctx.state.localVars["洛神/lastJudgeCardId"] = judgeCardId; ctx.state.localVars["洛神/lastResult"] = isBlack(judgeCard) ? "black" : "red"; }); const unloadProvider = registerHandLimitProvider(state, ownerId, (st, player) => { if (player !== ownerId) return void 0; const exempt = readExemptList(st); if (exempt.length === 0) return void 0; const hand = st.players[player]?.hand ?? []; const inHand = exempt.filter((id) => hand.includes(id)).length; if (inHand === 0) return void 0; const bonus = getHandLimitBonus(st.turn.vars, player) ?? 0; return getHealthValue(st.players[player]) + bonus + inHand; }); registerAfterHook(state, skill.id, ownerId, "阶段开始", async (ctx) => { const atom = ctx.atom; if (atom.type !== "阶段开始") return; if (atom.player !== ownerId) return; if (atom.phase !== "准备") return; if (!ctx.state.players[ownerId]?.alive) return; if (ctx.state.zones.deck.length === 0) return; delete ctx.state.localVars["洛神/confirmed"]; await applyAtom(ctx.state, { type: "请求回应", requestType: "界洛神/confirm", target: ownerId, prompt: { type: "confirm", title: "是否发动洛神?", confirmLabel: "发动", cancelLabel: "不发动" }, defaultChoice: false, timeout: 10 }); if (!ctx.state.localVars["洛神/confirmed"]) return; let keepGoing = true; while (keepGoing) { if (ctx.state.zones.deck.length === 0) break; await runJudgeFlow(ctx.state, ownerId, "洛神"); const result = ctx.state.localVars["洛神/lastResult"]; const judgeCardId = ctx.state.localVars["洛神/lastJudgeCardId"]; delete ctx.state.localVars["洛神/lastResult"]; delete ctx.state.localVars["洛神/lastJudgeCardId"]; if (result !== "black" || !judgeCardId) break; await applyAtom(ctx.state, { type: "移动牌", cardId: judgeCardId, from: { zone: "弃牌堆" }, to: { zone: "手牌", player: ownerId } }); const list = readExemptList(ctx.state); if (!list.includes(judgeCardId)) { list.push(judgeCardId); ctx.state.turn.vars[EXEMPT_VAR] = list; } delete ctx.state.localVars["洛神/confirmed"]; await applyAtom(ctx.state, { type: "请求回应", requestType: "界洛神/continue", target: ownerId, prompt: { type: "confirm", title: "判定为黑色,是否继续洛神?", confirmLabel: "继续", cancelLabel: "停止" }, defaultChoice: true, timeout: 10 }); keepGoing = ctx.state.localVars["洛神/confirmed"] !== false; } delete ctx.state.localVars["洛神/confirmed"]; }); return () => { unloadProvider(); }; } function onMount$135(_skill, api) { api.defineAction("respond", { label: "界洛神", style: "default", prompt: { type: "confirm", title: "是否发动洛神?", confirmLabel: "发动", cancelLabel: "不发动" } }); } var EXEMPT_VAR, 界洛神_default; var init_界洛神 = __esmMin((() => { init_types(); init_apply(); init_frame(); init_judge(); init_skill(); init_hand_limit(); init_vars_keys(); EXEMPT_VAR = "界洛神/豁免牌"; 界洛神_default = { createSkill: createSkill$145, onInit: onInit$145, onMount: onMount$135 }; })); //#endregion //#region src/engine/skills/界涅槃.ts var 界涅槃_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$144, default: () => 界涅槃_default, onInit: () => onInit$144, onMount: () => onMount$134 }); function createSkill$144(id, ownerId) { return { id, ownerId, name: "界涅槃", description: "限定技:濒死时弃置所有牌,复原武将牌,摸三张并回复至3点体力,然后获得八阵/火计/看破中的一个" }; } function onInit$144(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "respond", (st, _params) => { const slot = st.pendingSlots.get(ownerId); if (!slot) return "当前不需要回应"; const atom = slot.atom; if (atom["type"] !== "请求回应") return "当前不需要回应"; if (atom["requestType"] === CONFIRM_RT$29) return null; return "当前不是界涅槃询问"; }, async (st, params) => { if (st.pendingSlots.get(ownerId).atom.requestType === CONFIRM_RT$29) st.localVars[CONFIRMED_KEY$25] = params.choice === true || params.confirmed === true; }); registerAfterHook(state, skill.id, ownerId, "陷入濒死", async (ctx) => { if (ctx.atom.target !== ownerId) return; if (ctx.state.players[ownerId]?.vars[USED_KEY$9]) return; const self = ctx.state.players[ownerId]; if (!self) return; if (self.health > 0) return; delete ctx.state.localVars[CONFIRMED_KEY$25]; await applyAtom(ctx.state, { type: "请求回应", requestType: CONFIRM_RT$29, target: ownerId, prompt: { type: "confirm", title: "是否发动界涅槃?(限定技:弃所有牌,摸3张,回复至3体力,获八阵/火计/看破之一)", confirmLabel: "发动", cancelLabel: "不发动" }, defaultChoice: false, timeout: 15 }); if (!ctx.state.localVars[CONFIRMED_KEY$25]) return; ctx.state.players[ownerId].vars[USED_KEY$9] = true; const player = ctx.state.players[ownerId]; const handCards = [...player.hand]; const equipCards = Object.values(player.equipment).filter((id) => typeof id === "string"); const judgeTricks = player.pendingTricks.map((t) => ({ trickName: t.name, cardId: t.card.id })); for (const { trickName } of judgeTricks) await applyAtom(ctx.state, { type: "移除延时锦囊", player: ownerId, trickName }); const orphanJudgeCards = judgeTricks.map((t) => t.cardId).filter((id) => locateCard(ctx.state, id) === null); const allCardIds = [ ...handCards, ...equipCards, ...orphanJudgeCards ]; if (allCardIds.length > 0) await applyAtom(ctx.state, { type: "弃置", player: ownerId, cardIds: allCardIds }); if (ctx.state.players[ownerId].marks.some((m) => m.id === "chained")) await setChain(ctx.state, ownerId, false); await applyAtom(ctx.state, { type: "摸牌", player: ownerId, count: 3 }); const cur = ctx.state.players[ownerId].health; const amount = Math.max(0, TARGET_HEALTH$1 - cur); if (amount > 0) await applyAtom(ctx.state, { type: "回复体力", target: ownerId, amount }); let selected; for (const skillName of CHOOSABLE_SKILLS) { delete ctx.state.localVars[CONFIRMED_KEY$25]; await applyAtom(ctx.state, { type: "请求回应", requestType: CONFIRM_RT$29, target: ownerId, prompt: { type: "confirm", title: `界涅槃:是否获得「${skillName}」?`, confirmLabel: "获得", cancelLabel: "不要" }, defaultChoice: false, timeout: 15 }); if (ctx.state.localVars[CONFIRMED_KEY$25]) { selected = skillName; delete ctx.state.localVars[CONFIRMED_KEY$25]; break; } delete ctx.state.localVars[CONFIRMED_KEY$25]; } selected ??= CHOOSABLE_SKILLS[0]; await applyAtom(ctx.state, { type: "添加技能", player: ownerId, skillId: selected }); }); return () => {}; } function onMount$134(_skill, api) { api.defineAction("respond", { label: "界涅槃", style: "primary", prompt: { type: "confirm", title: "是否发动界涅槃?", confirmLabel: "发动", cancelLabel: "不发动" } }); } var CONFIRM_RT$29, CONFIRMED_KEY$25, USED_KEY$9, TARGET_HEALTH$1, CHOOSABLE_SKILLS, 界涅槃_default; var init_界涅槃 = __esmMin((() => { init_apply(); init_face_down(); init_judge_zone(); init_skill(); CONFIRM_RT$29 = "界涅槃/confirm"; CONFIRMED_KEY$25 = "界涅槃/confirmed"; USED_KEY$9 = "界涅槃/used"; TARGET_HEALTH$1 = 3; CHOOSABLE_SKILLS = [ "八阵", "火计", "看破" ]; 界涅槃_default = { createSkill: createSkill$144, onInit: onInit$144, onMount: onMount$134 }; })); //#endregion //#region src/engine/skills/界渐营.ts var 界渐营_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$143, default: () => _skillModule$67, onInit: () => onInit$143, onMount: () => onMount$133 }); function createSkill$143(id, ownerId) { return { id, ownerId, name: DISPLAY_NAME$44, description: "出牌阶段用牌若与上一张花色或点数相同,可摸一张;每阶段限一次,可将一张牌当任意基本牌使用(若有上一张花色则继承)" }; } /** 影子卡 id: ${原id}#界渐营 */ function shadowIdOf$14(cardId) { return `${cardId}#${SKILL_ID$33}`; } /** 上一张牌花色('' / '♠' / '♥' / '♣' / '♦') */ function lastSuit(state) { const v = state.turn.vars[LAST_SUIT_KEY]; return typeof v === "string" ? v : ""; } /** 上一张牌点数 */ function lastRank(state) { const v = state.turn.vars[LAST_RANK_KEY]; return typeof v === "string" ? v : ""; } /** 写入"上一张牌" */ function setLast(state, suit, rank) { state.turn.vars[LAST_SUIT_KEY] = suit; state.turn.vars[LAST_RANK_KEY] = rank; } /** 重置"上一张牌"为空 */ function resetLast(state) { setLast(state, "", ""); } /** 是否处于 owner 的出牌阶段 */ function inMyPlayPhase$1(state, ownerId) { return state.currentPlayerIndex === ownerId && state.phase === "出牌"; } /** 第二段是否已用过(限一次/阶段) */ function transformUsed(state, ownerId) { return !!state.players[ownerId]?.vars[TRANSFORM_USED_KEY]; } function onInit$143(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "respond", (st, _params) => { const slot = st.pendingSlots.get(ownerId); if (!slot) return "当前不需要回应"; const atom = slot.atom; if (atom["type"] !== "请求回应") return "当前不需要回应"; if (atom["requestType"] !== CONFIRM_RT$28) return "当前不是渐营询问"; return null; }, async (st, params) => { st.localVars[CONFIRMED_KEY$24] = params.choice === true || params.confirmed === true; }); const registerTransform = (outputName) => registerAction(state, skill.id, ownerId, `${TRANSFORM_ACTION_PREFIX$1}${outputName}`, (st, params) => { if (!inMyPlayPhase$1(st, ownerId)) return "只能在你的出牌阶段发动"; if (hasBlockingPending(st)) return "当前有未完成的询问"; if (transformUsed(st, ownerId)) return "本阶段渐营转化已用过"; const self = st.players[ownerId]; if (!self?.alive) return "玩家不存在或已死亡"; const cardId = params.cardId; if (typeof cardId !== "string") return "需要选择一张牌"; if (!self.hand.includes(cardId)) return "牌不在手牌中"; return null; }, async (st, params) => { const cardId = params.cardId; const shadowId = shadowIdOf$14(cardId); const prevSuit = lastSuit(st); st.players[ownerId].vars[TRANSFORM_USED_KEY] = true; await applyAtom(st, { type: "回合用量", player: ownerId, key: TRANSFORM_USED_KEY, value: true }); await applyAtom(st, { type: "当作", player: ownerId, cardIds: [cardId], shadowId, outputName }); if (prevSuit !== "") { const shadow = st.cardMap[shadowId]; if (shadow) { shadow.suit = prevSuit; shadow.color = prevSuit === "♥" || prevSuit === "♦" ? "红" : "黑"; } } }, (st, params) => { const cardId = params.cardId; const sId = shadowIdOf$14(cardId); delete st.cardMap[sId]; const self = st.players[ownerId]; const idx = self.hand.indexOf(sId); if (idx >= 0) self.hand[idx] = cardId; delete self.vars[TRANSFORM_USED_KEY]; }); const unregTransforms = [...ALLOWED_NAMES$1].map(registerTransform); registerAfterHook(state, skill.id, ownerId, "阶段开始", async (ctx) => { const atom = ctx.atom; if (atom.type !== "阶段开始") return; if (atom.phase !== "出牌") return; if (atom.player !== ownerId) return; resetLast(ctx.state); }); registerAfterHook(state, skill.id, ownerId, "移动牌", async (ctx) => { const st = ctx.state; if (!inMyPlayPhase$1(st, ownerId)) return; const atom = ctx.atom; if (atom.from?.zone !== "手牌") return; if (atom.from.player !== ownerId) return; if (atom.to?.zone !== "处理区") return; const cardId = atom.cardId; if (typeof cardId !== "string") return; const card = st.cardMap[cardId]; if (!card) return; await checkAndPromptDraw(st, ownerId, card); }); registerAfterHook(state, skill.id, ownerId, "装备", async (ctx) => { const st = ctx.state; if (!inMyPlayPhase$1(st, ownerId)) return; const atom = ctx.atom; if (atom.player !== ownerId) return; const cardId = atom.cardId; if (typeof cardId !== "string") return; const card = st.cardMap[cardId]; if (!card) return; await checkAndPromptDraw(st, ownerId, card); }); return () => { for (const unreg of unregTransforms) unreg(); }; } /** * 渐营第一段核心:比较当前牌与"上一张牌",若花色或点数相同则询问是否摸一张; * 之后无条件将"上一张牌"更新为当前牌。 */ async function checkAndPromptDraw(st, ownerId, card) { const prevSuit = lastSuit(st); const prevRank = lastRank(st); const hasPrev = prevSuit !== "" || prevRank !== ""; const suitMatch = hasPrev && card.suit !== "" && card.suit === prevSuit; const rankMatch = hasPrev && card.rank !== "" && card.rank === prevRank; setLast(st, card.suit, card.rank); if (!suitMatch && !rankMatch) return; delete st.localVars[CONFIRMED_KEY$24]; await applyAtom(st, { type: "请求回应", requestType: CONFIRM_RT$28, target: ownerId, prompt: { type: "confirm", title: "是否发动渐营?(此牌与上一张花色或点数相同,可摸一张)", confirmLabel: "摸一张", cancelLabel: "不发动" }, defaultChoice: false, timeout: 10 }); if (st.localVars[CONFIRMED_KEY$24] === true) await applyAtom(st, { type: "摸牌", player: ownerId, count: 1 }); delete st.localVars[CONFIRMED_KEY$24]; } /** 声明的牌名此刻能否被主动使用(与 使用牌 的 onMount 同源判据): * timing='生效前' 的纯回应牌(闪/无懈可击)无主动 use 入口;牌自身 activeWhen 不满足 * (如 桃 需自己已受伤)时同样不可用 —— 这类声明点了会走到死路(主 action 不存在/被拒)。 */ function declaredUsable$1(ctx, name) { const effect = getCardEffect(name); if (!effect) return true; if (effect.timing === "生效前") return false; return !effect.activeWhen || effect.activeWhen(ctx); } function onMount$133(_skill, api) { const activeWhen = (ctx) => { if (!defaultPlayActive(ctx)) return false; const p = ctx.view.players[ctx.perspectiveIdx]; if (!p) return false; if (p.turnUsage?.[TRANSFORM_USED_KEY]) return false; return (p.handCount ?? 0) > 0; }; const nameActiveWhen = (name) => (ctx) => activeWhen(ctx) && declaredUsable$1(ctx, name); for (const outputName of ALLOWED_NAMES$1) api.defineAction(`${TRANSFORM_ACTION_PREFIX$1}${outputName}`, { label: `${DISPLAY_NAME$44}·${outputName}`, style: "primary", prompt: { type: "useCard", title: `渐营:将一张牌当【${outputName}】使用`, description: "出牌阶段限一次;若上一张牌有花色,此牌视为与之同花色", cardFilter: { filter: () => true, min: 1, max: 1 } }, transform: (card) => ({ name: outputName, sourceCardId: card.id, fromSkill: SKILL_ID$33 }), activeWhen: nameActiveWhen(outputName) }); api.defineAction("respond", { label: DISPLAY_NAME$44, style: "default", prompt: { type: "confirm", title: "是否发动渐营?", confirmLabel: "摸一张", cancelLabel: "不发动" } }); } var SKILL_ID$33, DISPLAY_NAME$44, LAST_SUIT_KEY, LAST_RANK_KEY, TRANSFORM_USED_KEY, CONFIRM_RT$28, CONFIRMED_KEY$24, ALLOWED_NAMES$1, TRANSFORM_ACTION_PREFIX$1, _skillModule$67; var init_界渐营 = __esmMin((() => { init_apply(); init_skill(); init_action_active(); init_cards(); SKILL_ID$33 = "界渐营"; DISPLAY_NAME$44 = "渐营"; LAST_SUIT_KEY = `${SKILL_ID$33}/lastSuit`; LAST_RANK_KEY = `${SKILL_ID$33}/lastRank`; TRANSFORM_USED_KEY = `${SKILL_ID$33}/transform/usedThisTurn`; CONFIRM_RT$28 = `${SKILL_ID$33}/confirm`; CONFIRMED_KEY$24 = `${SKILL_ID$33}/confirmed`; ALLOWED_NAMES$1 = new Set([ "杀", "闪", "桃", "酒" ]); TRANSFORM_ACTION_PREFIX$1 = "transform:"; _skillModule$67 = { createSkill: createSkill$143, onInit: onInit$143, onMount: onMount$133 }; })); //#endregion //#region src/engine/skills/界潜心.ts var 界潜心_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$142, default: () => _skillModule$66, onInit: () => onInit$142, onMount: () => onMount$132 }); function createSkill$142(id, ownerId) { return { id, ownerId, name: DISPLAY_NAME$43, description: "觉醒技:造成伤害后,若你已受伤,你减少1点体力上限并获得\"荐言\"" }; } function onInit$142(skill, state) { const ownerId = skill.ownerId; registerAfterHook(state, skill.id, ownerId, "造成伤害后", async (ctx) => { const atom = ctx.atom; if (atom.source !== ownerId) return; if ((atom.amount ?? 0) <= 0) return; const self = ctx.state.players[ownerId]; if (!self?.alive) return; if (self.vars[AWAKENED_KEY$4]) return; if (self.health >= self.maxHealth) return; ctx.state.players[ownerId].vars[AWAKENED_KEY$4] = true; const newMax = self.maxHealth - 1; await applyAtom(ctx.state, { type: "设上限", player: ownerId, amount: newMax }); await applyAtom(ctx.state, { type: "添加技能", player: ownerId, skillId: GRANT_SKILL_ID }); }); return () => {}; } function onMount$132(_skill, _api) {} var SKILL_ID$32, DISPLAY_NAME$43, AWAKENED_KEY$4, GRANT_SKILL_ID, _skillModule$66; var init_界潜心 = __esmMin((() => { init_apply(); init_skill(); SKILL_ID$32 = "界潜心"; DISPLAY_NAME$43 = "潜心"; AWAKENED_KEY$4 = `${SKILL_ID$32}/awakened`; GRANT_SKILL_ID = "界荐言"; _skillModule$66 = { createSkill: createSkill$142, onInit: onInit$142, onMount: onMount$132 }; })); //#endregion //#region src/engine/skills/界潜袭.ts var 界潜袭_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$141, default: () => _skillModule$65, onInit: () => onInit$141, onMount: () => onMount$131 }); function createSkill$141(id, ownerId) { return { id, ownerId, name: DISPLAY_NAME$42, description: "准备阶段,你可以摸一张牌并展示一张牌;距离为1的其他角色本回合不能使用或打出与\"潜袭\"牌颜色相同的手牌,你本回合使用\"潜袭\"牌造成的伤害+1" }; } /** 取目标当前被禁的颜色(优先红,再黑;界潜袭只设一种)。 */ function bannedColor(tags) { if (tags.includes(TAG_RED)) return "红"; if (tags.includes(TAG_BLACK)) return "黑"; return null; } /** 判断玩家手中是否存在「指定 name 且非禁色」的牌。 */ function hasNonBannedCard(state, player, cardName, ban) { const hand = state.players[player]?.hand ?? []; for (const id of hand) { const c = state.cardMap[id]; if (!c) continue; if (c.name !== cardName) continue; if (c.color === ban) continue; if (c.color === "无色") continue; return true; } return false; } function onInit$141(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "respond", (st, params) => { const slot = st.pendingSlots.get(ownerId); if (!slot) return "当前不需要回应"; const atom = slot.atom; if (atom["type"] !== "请求回应") return "当前不需要回应"; const reqType = atom["requestType"]; if (reqType === CONFIRM_RT$27) { if (typeof params.choice !== "boolean") return "需要选择一项"; return null; } if (reqType === PICK_RT$5) { const cardId = params.cardId; if (typeof cardId !== "string") return "请选择一张手牌展示"; if (!st.players[ownerId]?.hand.includes(cardId)) return "牌不在手牌中"; return null; } return "当前不是界潜袭回应"; }, async (st, params) => { const reqType = (st.pendingSlots.get(ownerId)?.atom)?.requestType; if (reqType === CONFIRM_RT$27) st.localVars[CONFIRM_KEY$12] = params.choice === true; else if (reqType === PICK_RT$5) st.localVars[PICK_KEY$4] = params.cardId; }); registerAfterHook(state, skill.id, ownerId, "阶段开始", async (ctx) => { const atom = ctx.atom; if (atom.type !== "阶段开始") return; if (atom.player !== ownerId) return; if (atom.phase !== "准备") return; if (!ctx.state.players[ownerId]?.alive) return; delete ctx.state.localVars[CONFIRM_KEY$12]; await applyAtom(ctx.state, { type: "请求回应", requestType: CONFIRM_RT$27, target: ownerId, prompt: { type: "confirm", title: "是否发动潜袭?(摸一张并展示一张牌;距离1的其他角色本回合不能使用/打出同色手牌,你用此牌造伤+1)", confirmLabel: "发动", cancelLabel: "不发动" }, defaultChoice: false, timeout: 15 }); if (ctx.state.localVars[CONFIRM_KEY$12] !== true) { delete ctx.state.localVars[CONFIRM_KEY$12]; return; } delete ctx.state.localVars[CONFIRM_KEY$12]; await pushFrame(ctx.state, SKILL_ID$31, ownerId, {}); await applyAtom(ctx.state, { type: "摸牌", player: ownerId, count: 1 }); const self = ctx.state.players[ownerId]; if (!self?.alive || self.hand.length === 0) { await popFrame(ctx.state); return; } const cards = self.hand.map((id) => { const c = ctx.state.cardMap[id]; if (!c) return null; return { cardId: id, cardName: c.name, suit: c.suit, rank: c.rank }; }).filter((c) => c !== null); delete ctx.state.localVars[PICK_KEY$4]; await applyAtom(ctx.state, { type: "请求回应", requestType: PICK_RT$5, target: ownerId, prompt: { type: "pickProcessingCard", title: "潜袭:选择一张手牌展示(距离1的其他角色本回合不能使用/打出同色手牌,你用此牌造伤+1)", cards }, timeout: 30 }); let pickedId = ctx.state.localVars[PICK_KEY$4]; const ownerHand = ctx.state.players[ownerId]?.hand ?? []; if (!pickedId || !ownerHand.includes(pickedId)) pickedId = ownerHand[0]; delete ctx.state.localVars[PICK_KEY$4]; const pickedCard = pickedId ? ctx.state.cardMap[pickedId] : void 0; if (!pickedCard) { await popFrame(ctx.state); return; } await applyAtom(ctx.state, { type: "展示", player: ownerId, cardId: pickedId }); const color = pickedCard.color; if (color !== "红" && color !== "黑") { await popFrame(ctx.state); return; } ctx.state.turn.vars[CARDID_VAR] = pickedId; ctx.state.turn.vars[COLOR_VAR] = color; const tag = color === "红" ? TAG_RED : TAG_BLACK; for (const p of ctx.state.players) { if (p.index === ownerId) continue; if (!p.alive) continue; if (effectiveDistance(ctx.state, ownerId, p.index) !== 1) continue; if (!p.tags.includes(tag)) await applyAtom(ctx.state, { type: "加标签", player: p.index, tag }); } await popFrame(ctx.state); }); registerBeforeHook(state, skill.id, ownerId, "请求回应", async (ctx) => { const atom = ctx.atom; const target = atom.target; if (typeof target !== "number" || target < 0) return; if (target === ownerId) return; const player = ctx.state.players[target]; if (!player?.alive) return; const ban = bannedColor(player.tags); if (!ban) return; const promptType = atom.prompt?.type; if (!promptType || !CARD_PLAY_PROMPTS$1.has(promptType)) return; const prompt = atom.prompt; const cardFilter = prompt?.cardFilter; if (!cardFilter) return; const originalFilter = cardFilter.filter; if (!player.hand.some((id) => { const c = ctx.state.cardMap[id]; if (!c) return false; if (c.color === ban) return false; return originalFilter ? originalFilter(c) : true; })) return { kind: "cancel" }; const wrappedFilter = (c) => { if (c.color === ban) return false; return originalFilter ? originalFilter(c) : true; }; return { kind: "modify", atom: { ...ctx.atom, prompt: { ...prompt, cardFilter: { ...cardFilter, filter: wrappedFilter } } } }; }); const askHandler = (cardName) => async (ctx) => { const target = ctx.atom.target; if (typeof target !== "number") return; if (target === ownerId) return; const player = ctx.state.players[target]; if (!player?.alive) return; const ban = bannedColor(player.tags); if (!ban) return; if (hasNonBannedCard(ctx.state, target, cardName, ban)) return; return { kind: "cancel" }; }; registerBeforeHook(state, skill.id, ownerId, "询问闪", askHandler("闪")); registerBeforeHook(state, skill.id, ownerId, "询问杀", askHandler("杀")); registerBeforeHook(state, skill.id, ownerId, "造成伤害时", async (ctx) => { const atom = ctx.atom; if (atom.source !== ownerId) return; if ((atom.amount ?? 0) <= 0) return; const cardId = atom.cardId; if (typeof cardId !== "string") return; if (ctx.state.turn.vars[CARDID_VAR] !== cardId) return; return { kind: "modify", atom: { ...ctx.atom, amount: (atom.amount ?? 0) + 1 } }; }); registerAfterHook(state, skill.id, ownerId, "回合结束", async (ctx) => { if (ctx.atom.player !== ownerId) return; for (const p of ctx.state.players) for (const tag of ALL_TAGS) if (p.tags.includes(tag)) await applyAtom(ctx.state, { type: "去标签", player: p.index, tag }); }); return () => {}; } function onMount$131(_skill, api) { api.defineAction("respond", { label: DISPLAY_NAME$42, style: "danger", prompt: { type: "confirm", title: "是否发动潜袭?", confirmLabel: "发动", cancelLabel: "不发动" } }); } var SKILL_ID$31, DISPLAY_NAME$42, CONFIRM_KEY$12, PICK_KEY$4, CONFIRM_RT$27, PICK_RT$5, CARDID_VAR, COLOR_VAR, TAG_RED, TAG_BLACK, ALL_TAGS, CARD_PLAY_PROMPTS$1, _skillModule$65; var init_界潜袭 = __esmMin((() => { init_apply(); init_frame(); init_distance(); init_skill(); SKILL_ID$31 = "界潜袭"; DISPLAY_NAME$42 = "潜袭"; CONFIRM_KEY$12 = `${SKILL_ID$31}/confirmed`; PICK_KEY$4 = `${SKILL_ID$31}/pickedCard`; CONFIRM_RT$27 = `${SKILL_ID$31}/confirm`; PICK_RT$5 = `${SKILL_ID$31}/pick`; CARDID_VAR = `${SKILL_ID$31}/cardId`; COLOR_VAR = `${SKILL_ID$31}/color`; TAG_RED = `${SKILL_ID$31}/禁红`; TAG_BLACK = `${SKILL_ID$31}/禁黑`; ALL_TAGS = [TAG_RED, TAG_BLACK]; CARD_PLAY_PROMPTS$1 = new Set([ "useCard", "useCardAndTarget", "pickProcessingCard", "pickTargetCard", "distribute" ]); _skillModule$65 = { createSkill: createSkill$141, onInit: onInit$141, onMount: onMount$131 }; })); //#endregion //#region src/engine/skills/界激将.ts var 界激将_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$140, default: () => _skillModule$64, onInit: () => onInit$140, onMount: () => onMount$130 }); function createSkill$140(id, ownerId) { return { id, ownerId, name: "界激将", description: "主公技:蜀势力角色可代你使用或打出杀;每回合限一次,蜀角色回合外用杀时可令你摸1张" }; } function onInit$140(skill, state) { const ownerId = skill.ownerId; const offs = []; offs.push(declareAlternativeResponse(state, ownerId, "询问杀")); offs.push(registerAction(state, skill.id, ownerId, "use", (state, params) => { const myTurn = state.currentPlayerIndex === ownerId; const inActPhase = state.phase === "出牌"; const free = !hasBlockingPending(state); const selfAlive = state.players[ownerId].alive === true; const isLord = ownerId === 0; const targetIdx = params.target; const targetExists = typeof targetIdx === "number" && !!state.players[targetIdx]; const target = targetExists ? state.players[targetIdx] : null; const targetNotSelf = targetIdx !== ownerId; const targetAlive = target?.alive === true; const targetShu = target?.faction === "蜀"; const killTargetIdx = params.killTarget; const killTargetValid = killTargetIdx === void 0 || state.players[killTargetIdx]?.alive === true && killTargetIdx !== targetIdx; return myTurn && inActPhase && free && selfAlive && isLord && targetExists && targetNotSelf && targetAlive && targetShu && killTargetValid ? null : "现在不能使用界激将"; }, async (state, params) => { const from = ownerId; const target = params.target; const killTarget = params.killTarget; await pushFrame(state, "界激将", from, { ...params }); if (typeof killTarget === "number") state.localVars[USE_KILL_TARGET_VAR] = killTarget; await applyAtom(state, { type: "请求回应", requestType: USE_REQUEST_TYPE, target, prompt: { type: "useCardAndTarget", title: `界激将:对 ${typeof killTarget === "number" ? state.players[killTarget]?.name ?? "?" : "?"} 使用一张杀`, cardFilter: { filter: (c) => c.name === "杀", min: 1, max: 1 }, targetFilter: { min: 1, max: 1, filter: (_v, t) => typeof killTarget === "number" ? t === killTarget : t !== target } }, timeout: 15 }); const choice = state.localVars[USE_CHOICE_VAR]; delete state.localVars[USE_CHOICE_VAR]; delete state.localVars[USE_KILL_TARGET_VAR]; if (choice?.cardId && Array.isArray(choice.targets) && choice.targets.length > 0) await runUseFlow(state, target, choice.cardId, choice.targets, "杀"); await popFrame(state); })); offs.push(registerAfterHook(state, skill.id, ownerId, "指定目标", async (ctx) => { if (ownerId !== 0) return; const atom = ctx.atom; const sourceIdx = atom.source; if (typeof sourceIdx !== "number") return; if (sourceIdx === ownerId) return; const source = ctx.state.players[sourceIdx]; if (!source?.alive) return; if (source.faction !== "蜀") return; const cardId = atom.cardId; if (!cardId) return; if (ctx.state.cardMap[cardId]?.name !== "杀") return; if (ctx.state.currentPlayerIndex === sourceIdx) return; const lord = ctx.state.players[ownerId]; if (!lord?.alive) return; if (ctx.state.turn.vars[PER_TURN_VAR] === true) return; ctx.state.turn.vars[PER_TURN_VAR] = true; await applyAtom(ctx.state, { type: "回合用量", player: ownerId, key: PER_TURN_VAR, value: true }); delete ctx.state.localVars[CONFIRMED_VAR$3]; await applyAtom(ctx.state, { type: "请求回应", requestType: REQUEST_TYPE$1, target: sourceIdx, prompt: { type: "confirm", title: `界激将:是否令${lord.name}摸一张牌?`, confirmLabel: "令主公摸牌", cancelLabel: "不发动" }, defaultChoice: false, timeout: 30 }); if (ctx.state.localVars[CONFIRMED_VAR$3] === true) await applyAtom(ctx.state, { type: "摸牌", player: ownerId, count: 1 }); })); offs.push(registerAfterHook(state, skill.id, ownerId, "询问杀", async (ctx) => { if (ownerId !== 0) return; const askedIdx = ctx.atom.target; if (typeof askedIdx !== "number") return; let triggerIdx; if (askedIdx === ownerId) { const sub = ctx.state.localVars[SUBSTITUTE_PLAYER_VAR]; delete ctx.state.localVars[SUBSTITUTE_PLAYER_VAR]; if (typeof sub !== "number") return; triggerIdx = sub; } else triggerIdx = askedIdx; const trigger = ctx.state.players[triggerIdx]; if (!trigger?.alive) return; if (trigger.faction !== "蜀") return; if (ctx.state.currentPlayerIndex === triggerIdx) return; if (!frameCards(ctx.state).some((id) => ctx.state.cardMap[id]?.name === "杀")) return; const lord = ctx.state.players[ownerId]; if (!lord?.alive) return; if (ctx.state.turn.vars[PER_TURN_VAR] === true) return; ctx.state.turn.vars[PER_TURN_VAR] = true; await applyAtom(ctx.state, { type: "回合用量", player: ownerId, key: PER_TURN_VAR, value: true }); delete ctx.state.localVars[CONFIRMED_VAR$3]; await applyAtom(ctx.state, { type: "请求回应", requestType: REQUEST_TYPE$1, target: triggerIdx, prompt: { type: "confirm", title: `界激将:是否令${lord.name}摸一张牌?`, confirmLabel: "令主公摸牌", cancelLabel: "不发动" }, defaultChoice: false, timeout: 30 }); if (ctx.state.localVars[CONFIRMED_VAR$3] === true) await applyAtom(ctx.state, { type: "摸牌", player: ownerId, count: 1 }); })); for (const pl of state.players) { const seat = pl.index; offs.push(registerAction(state, skill.id, seat, "respond", (st, params) => { const slot = st.pendingSlots.get(seat); if (!slot) return "当前不需要回应"; const atom = slot.atom; if (atom.type === "询问杀") { if (!st.players[seat]?.alive) return "玩家不存在或已死亡"; if (seat !== 0) return "仅主公可用"; if (!st.players.some((p) => p.alive && p.index !== seat && p.faction === "蜀" && p.hand.length > 0)) return "没有可出杀的蜀势力角色"; return null; } if (atom.requestType === USE_REQUEST_TYPE) { const cardId = params.cardId; const targets = params.targets; if (typeof cardId !== "string") return "请选择一张杀"; if (!Array.isArray(targets) || targets.length === 0) return "请选择目标"; if (!st.players[seat]?.hand.includes(cardId)) return "牌不在手牌中"; if (st.cardMap[cardId]?.name !== "杀") return "只能使用杀"; const killTarget = st.localVars[USE_KILL_TARGET_VAR]; if (typeof killTarget === "number" && !targets.includes(killTarget)) return "必须包含激将指定的目标"; for (const t of targets) if (!inAttackRange(st, seat, t, cardId)) return "目标不在攻击范围内"; return null; } if (atom.requestType === REQUEST_TYPE$1) return null; return "当前不是界激将询问"; }, async (st, params) => { const atom = st.pendingSlots.get(seat)?.atom; if (atom?.type === "询问杀") { const numPlayers = st.players.length; for (let offset = 1; offset < numPlayers; offset++) { const allyIdx = (seat + offset) % numPlayers; const ally = st.players[allyIdx]; if (!ally?.alive) continue; if (ally.faction !== "蜀") continue; if (ally.hand.length === 0) continue; await applyAtom(st, { type: "请求回应", requestType: "杀/respondKill", target: allyIdx, prompt: { type: "useCard", title: `界激将:主公(${st.players[seat]?.name ?? `P${seat}`})需要杀,是否打出一张杀?`, cardFilter: { filter: (c) => c.name === "杀", min: 1, max: 1 } }, timeout: 15 }); if (frameCards(st).find((id) => { return st.cardMap[id]?.name === "杀"; })) { st.localVars[SUBSTITUTE_PLAYER_VAR] = allyIdx; return; } } return; } if (atom?.requestType === USE_REQUEST_TYPE) { st.localVars[USE_CHOICE_VAR] = { cardId: params.cardId, targets: params.targets }; return; } if (atom?.requestType === REQUEST_TYPE$1) st.localVars[CONFIRMED_VAR$3] = params.choice === true || params.confirmed === true; })); } return () => { for (const off of offs) off(); }; } function onMount$130(skill, api) { api.defineAction("use", { label: "界激将", style: "primary", prompt: { type: "choosePlayer", title: "界激将:选择一名蜀势力角色出杀", min: 1, max: 1, filter: (view, t) => t !== skill.ownerId && view.players[t]?.alive === true && view.players[t]?.faction === "蜀" }, activeWhen: (ctx) => defaultPlayActive(ctx) && isLordOwner(skill.ownerId) }); api.defineAction("respond", { label: "界激将", style: "primary", prompt: { type: "confirm", title: "界激将:令蜀势力角色替你打出杀?", confirmLabel: "激将", cancelLabel: "不发动" }, activeWhen: (ctx) => { const slot = ctx.view.pending; if (!slot) return false; if (slot.target !== ctx.perspectiveIdx) return false; const atom = slot.atom; if (atom.type === "询问杀") return true; if (atom.requestType === USE_REQUEST_TYPE) return true; if (atom.requestType === REQUEST_TYPE$1) return true; return false; } }); return () => {}; } var REQUEST_TYPE$1, CONFIRMED_VAR$3, PER_TURN_VAR, USE_REQUEST_TYPE, USE_CHOICE_VAR, USE_KILL_TARGET_VAR, SUBSTITUTE_PLAYER_VAR, _skillModule$64; var init_界激将 = __esmMin((() => { init_apply(); init_frame(); init_use_card(); init_skill(); init_distance(); init_action_active(); REQUEST_TYPE$1 = "界激将/drawChoice"; CONFIRMED_VAR$3 = "界激将/confirmed"; PER_TURN_VAR = "界激将/triggered"; USE_REQUEST_TYPE = "界激将/出杀"; USE_CHOICE_VAR = "界激将/出杀选择"; USE_KILL_TARGET_VAR = "界激将/killTarget"; SUBSTITUTE_PLAYER_VAR = "界激将/代打出者"; _skillModule$64 = { createSkill: createSkill$140, onInit: onInit$140, onMount: onMount$130 }; })); //#endregion //#region src/engine/skills/界激昂.ts var 界激昂_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$139, default: () => 界激昂_default, onInit: () => onInit$139, onMount: () => onMount$129 }); /** source 情形去重:已为本 cardId 触发过(localVars 标记,键含 cardId) */ function srcDoneKey(cardId) { return `${SKILL_ID$30}/src/${cardId}`; } /** 判定是否为激昂可触发场景(决斗或红色杀);与标激昂同款判定 * allowVirtualFallback:仅「成为目标」hook 需要——虚拟决斗(离间)无实体牌, * 靠 topFrame.skillId 兜底。「弃置」hook 处理的必是真实弃牌,应按牌本身身份判定, * 不可用帧兜底(否则离间弃置非决斗/红杀的代价牌时会误判为触发)。 */ function isJiangTrigger(state, cardId, allowVirtualFallback = true) { const card = cardId ? state.cardMap[cardId] : void 0; if (card?.name === "决斗") return true; if (card?.name === "杀" && card.color === "红") return true; if (!allowVirtualFallback) return false; const frameSkill = topFrame(state)?.skillId; if (frameSkill === "决斗" || frameSkill === "离间") return true; return false; } function createSkill$139(id, ownerId) { return { id, ownerId, name: DISPLAY_NAME$41, description: "使用/被使用【决斗】或红色【杀】时可摸一张牌;每回合首次此类牌因弃置入弃牌堆时可失去1体力获得之" }; } function onInit$139(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "respond", (st, _params) => { const slot = st.pendingSlots.get(ownerId); if (!slot) return "当前不需要回应"; const atom = slot.atom; if (atom["type"] !== "请求回应") return "当前不需要回应"; const rt = atom["requestType"]; if (rt !== CONFIRM_RT$26 && rt !== RECYCLE_RT) return "当前不是界激昂确认"; return null; }, async (st, params) => { const slot = st.pendingSlots.get(ownerId); if (!slot) return; const rt = slot.atom["requestType"]; const yes = params.choice === true || params.confirmed === true; if (rt === CONFIRM_RT$26) st.localVars[CONFIRMED_KEY$23] = yes; else st.localVars[RECYCLE_CONFIRM_KEY] = yes; }); registerAfterHook(state, skill.id, ownerId, "成为目标", async (ctx) => { const atom = ctx.atom; if (!(atom.source === ownerId || atom.target === ownerId)) return; if (!ctx.state.players[ownerId]?.alive) return; if (!isJiangTrigger(ctx.state, atom.cardId)) return; if (atom.source === ownerId) { const key = srcDoneKey(atom.cardId ?? "__virtual__"); if (ctx.state.localVars[key]) return; ctx.state.localVars[key] = true; } delete ctx.state.localVars[CONFIRMED_KEY$23]; await applyAtom(ctx.state, { type: "请求回应", requestType: CONFIRM_RT$26, target: ownerId, prompt: { type: "confirm", title: "是否发动激昂?(摸一张牌)", confirmLabel: "发动", cancelLabel: "不发动" }, defaultChoice: false, timeout: 15 }); if (!ctx.state.localVars[CONFIRMED_KEY$23]) return; await applyAtom(ctx.state, { type: "摸牌", player: ownerId, count: 1 }); }); registerAfterHook(state, skill.id, ownerId, "弃置", async (ctx) => { const atom = ctx.atom; if (!ctx.state.players[ownerId]?.alive) return; if (ctx.state.players[ownerId].vars[RECYCLE_USED_KEY]) return; const targets = (atom.cardIds ?? []).filter((id) => isJiangTrigger(ctx.state, id, false)); if (targets.length === 0) return; ctx.state.players[ownerId].vars[RECYCLE_USED_KEY] = true; delete ctx.state.localVars[RECYCLE_CONFIRM_KEY]; await applyAtom(ctx.state, { type: "请求回应", requestType: RECYCLE_RT, target: ownerId, prompt: { type: "confirm", title: "是否发动激昂?(失去1点体力获得此牌)", confirmLabel: "发动", cancelLabel: "不发动" }, defaultChoice: false, timeout: 15 }); if (!ctx.state.localVars[RECYCLE_CONFIRM_KEY]) return; await applyAtom(ctx.state, { type: "失去体力", target: ownerId, amount: 1 }); if (!ctx.state.players[ownerId]?.alive) return; for (const cid of targets) { if (!ctx.state.zones.discardPile.includes(cid)) continue; await applyAtom(ctx.state, { type: "移动牌", cardId: cid, from: { zone: "弃牌堆" }, to: { zone: "手牌", player: ownerId } }); } }); return () => {}; } function onMount$129(_skill, api) { api.defineAction("respond", { label: DISPLAY_NAME$41, style: "primary", prompt: { type: "confirm", title: "是否发动激昂?", confirmLabel: "发动", cancelLabel: "不发动" } }); } var SKILL_ID$30, DISPLAY_NAME$41, CONFIRM_RT$26, CONFIRMED_KEY$23, RECYCLE_RT, RECYCLE_CONFIRM_KEY, RECYCLE_USED_KEY, 界激昂_default; var init_界激昂 = __esmMin((() => { init_apply(); init_frame(); init_skill(); SKILL_ID$30 = "界激昂"; DISPLAY_NAME$41 = "激昂"; CONFIRM_RT$26 = `${SKILL_ID$30}/confirm`; CONFIRMED_KEY$23 = `${SKILL_ID$30}/confirmed`; RECYCLE_RT = `${SKILL_ID$30}/recycle`; RECYCLE_CONFIRM_KEY = `${SKILL_ID$30}/recycleConfirm`; RECYCLE_USED_KEY = `${SKILL_ID$30}/recycle/usedThisTurn`; 界激昂_default = { createSkill: createSkill$139, onInit: onInit$139, onMount: onMount$129 }; })); //#endregion //#region src/engine/skills/界火计.ts var 界火计_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$138, default: () => 界火计_default, onInit: () => onInit$138, onMount: () => onMount$128 }); function createSkill$138(id, ownerId) { return { id, ownerId, name: "界火计", description: "你可以将一张红色牌当【火攻】使用;你的【火攻】改为目标展示随机手牌,你弃同颜色牌造伤" }; } /** 影子卡 id:${原id}#界火计 */ function shadowIdOf$13(cardId) { return `${cardId}#界火计`; } function onInit$138(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "transform", (state, params) => { const myTurn = state.currentPlayerIndex === ownerId; const inActPhase = state.phase === "出牌"; const free = !hasBlockingPending(state); const self = state.players[ownerId]; const selfAlive = self.alive === true; const cardId = params.cardId; const cardIdOk = typeof cardId === "string"; const card = cardIdOk ? state.cardMap[cardId] : void 0; const cardInHand = cardIdOk && self.hand.includes(cardId); const cardInEquip = cardIdOk && Object.values(self.equipment).some((id) => id === cardId); const isRed = !!card && card.color === "红"; return myTurn && inActPhase && free && selfAlive && (cardInHand || cardInEquip) && isRed ? null : "现在不能使用界火计"; }, async (state, params) => { const cardId = params.cardId; const shadowId = shadowIdOf$13(cardId); const self = state.players[ownerId]; const equipSlotEntry = Object.entries(self.equipment).find(([, id]) => id === cardId); if (equipSlotEntry) { const slot = equipSlotEntry[0]; params["_origSlot"] = slot; await applyAtom(state, { type: "卸下", player: ownerId, slot }); } await applyAtom(state, { type: "当作", player: ownerId, cardIds: [cardId], shadowId, outputName: "火攻" }); }, (state, params) => { const cardId = params.cardId; const sId = shadowIdOf$13(cardId); const self = state.players[ownerId]; const origSlot = params["_origSlot"]; delete state.cardMap[sId]; const idx = self.hand.indexOf(sId); if (idx >= 0) if (origSlot) { self.hand.splice(idx, 1); self.equipment[origSlot] = cardId; } else self.hand[idx] = cardId; }); registerAction(state, "火攻", ownerId, "use", (state, params) => { const base = validateUseCard(state, ownerId, params, { cardName: "火攻" }); if (base) return base; const targets = params.targets; if (!Array.isArray(targets) || targets.length !== 1) return "火攻只能指定一名目标"; const target = targets[0]; const targetPlayer = state.players[target]; if (!targetPlayer?.alive) return "目标不合法"; if (targetPlayer.hand.length === 0) return "目标必须有手牌"; return null; }, async (state, params) => { const from = ownerId; const cardId = params.cardId; const target = params.targets[0]; await pushFrame(state, "界火计", from, { ...params }); await applyAtom(state, { type: "移动牌", cardId, from: { zone: "手牌", player: from }, to: { zone: "处理区" } }); try { if (!await promptCancel(state, { cardName: "无懈可击", broadcast: true }, from, target)) { const targetPlayer = state.players[target]; if (targetPlayer && targetPlayer.hand.length > 0) { delete state.localVars["界火计/展示"]; delete state.localVars["界火计/展示颜色"]; delete state.localVars["界火计/弃牌"]; const handLen = targetPlayer.hand.length; const rng = createRng(state.rngSeed); const randomIdx = rng.nextInt(handLen); state.rngSeed = rng.getState(); const revealedCardId = targetPlayer.hand[randomIdx]; const revealedColor = state.cardMap[revealedCardId]?.color ?? "无色"; state.localVars["界火计/展示"] = { cardId: revealedCardId, color: revealedColor }; state.localVars["界火计/展示颜色"] = revealedColor; await applyAtom(state, { type: "展示", player: target, cardId: revealedCardId }); const fromPlayer = state.players[from]; if (fromPlayer?.alive) { if (fromPlayer.hand.some((id) => state.cardMap[id]?.color === revealedColor)) { delete state.localVars["界火计/弃牌"]; await applyAtom(state, { type: "请求回应", requestType: "界火计/弃牌", target: from, prompt: { type: "useCard", title: `界火计:弃置一张 ${revealedColor} 手牌对其造成1点火焰伤害(不弃则无效)`, cardFilter: { filter: (c) => c.color === revealedColor, min: 1, max: 1 } }, timeout: 15 }); const discardId = state.localVars["界火计/弃牌"]; if (discardId && state.players[target]?.alive) { await applyAtom(state, { type: "弃置", player: from, cardIds: [discardId], voluntary: true }); await runDamageFlow(state, from, target, 1, cardId, "火焰"); } } } } } await applyAtom(state, { type: "移动牌", cardId, from: { zone: "处理区" }, to: { zone: "弃牌堆" } }); } finally { if (frameCards(state).includes(cardId)) await applyAtom(state, { type: "移动牌", cardId, from: { zone: "处理区" }, to: { zone: "弃牌堆" } }); await popFrame(state); } }); registerAction(state, skill.id, ownerId, "respond", (state, params) => { const slot = state.pendingSlots.get(ownerId); if (!slot) return "当前不需要回应"; if (slot.atom.type !== "请求回应") return "当前不是界火计窗口"; if (slot.atom.requestType !== "界火计/弃牌") return "当前不是界火计窗口"; const cardId = params.cardId; if (typeof cardId !== "string") return "cardId required"; const self = state.players[ownerId]; if (!self?.alive) return "你已死亡"; if (!self.hand.includes(cardId)) return "牌不在手牌中"; const revealedColor = state.localVars["界火计/展示颜色"]; const card = state.cardMap[cardId]; if (!revealedColor || card?.color !== revealedColor) return "必须弃置与展示牌相同颜色的手牌"; return null; }, async (state, params) => { const cardId = params.cardId; state.localVars["界火计/弃牌"] = cardId; }); return () => {}; } function onMount$128(skill, api) { api.defineAction("transform", { label: "界火计", style: "passive", prompt: { type: "useCardAndTarget", title: "选择一张红色牌(手牌或装备)当火攻使用", cardFilter: { filter: (c) => c.color === "红", min: 1, max: 1 }, targetFilter: { min: 1, max: 1, filter: (view, t) => (view.players[t]?.handCount ?? 0) > 0 } }, transform: (card) => ({ name: "火攻", sourceCardId: card.id, fromSkill: skill.id }), activeWhen: (ctx) => { if (!defaultPlayActive(ctx)) return false; const p = ctx.view.players[ctx.perspectiveIdx]; if (!p) return false; const hasRedInHand = p.hand?.some((c) => c.color === "红") ?? false; const hasRedEquip = Object.values(p.equipment ?? {}).some((id) => { return (id ? ctx.view.cardMap[id] : void 0)?.color === "红"; }); return hasRedInHand || hasRedEquip; } }); api.defineAction("respond", { label: "界火计", style: "primary", prompt: { type: "useCard", title: "界火计:弃置一张与展示牌同颜色的手牌", cardFilter: { filter: () => true, min: 1, max: 1 } } }); } var 界火计_default; var init_界火计 = __esmMin((() => { init_skill(); init_apply(); init_frame(); init_damage(); init_cancel(); init_action_active(); init_rng(); 界火计_default = { createSkill: createSkill$138, onInit: onInit$138, onMount: onMount$128 }; })); //#endregion //#region src/engine/skills/界灭计.ts var 界灭计_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$137, default: () => _skillModule$63, onInit: () => onInit$137, onMount: () => onMount$127 }); /** 是否为(可置顶的)锦囊牌:延时/响应锦囊不可置顶(只在手牌中作为锦囊牌判定)。 * 按 OL 灭计惯例,普通锦囊/延时锦囊均算"锦囊牌";响应锦囊(无懈)不算。 */ function isTrickCard(card) { return !!card && card.type === "锦囊牌" && card.trickSubtype !== "响应锦囊"; } function createSkill$137(id, ownerId) { return { id, ownerId, name: DISPLAY_NAME$40, description: "出牌阶段限一次:将一张锦囊牌置于牌堆顶,令一名有手牌的其他角色选择弃一张锦囊或依次弃两张牌" }; } function onInit$137(skill, state) { const ownerId = skill.ownerId; const unloaders = []; unloaders.push(registerAction(state, skill.id, ownerId, "use", (st, params) => { if (st.currentPlayerIndex !== ownerId) return "不是你的回合"; if (st.phase !== "出牌") return "只能在出牌阶段发动"; if (hasBlockingPending(st)) return "当前有未完成的询问"; if (usedThisTurn$3(st, ownerId, SKILL_ID$29)) return "本回合已使用过灭计"; const self = st.players[ownerId]; if (!self?.alive) return "玩家不存在或已死亡"; const cardId = params.cardId; if (typeof cardId !== "string") return "请选择一张锦囊牌"; if (!self.hand.includes(cardId)) return "牌不在手牌中"; if (!isTrickCard(st.cardMap[cardId])) return "只能选择锦囊牌"; const target = params.target ?? params.targets?.[0]; if (typeof target !== "number") return "请指定一名目标"; if (target === ownerId) return "不能以自己为目标"; const tp = st.players[target]; if (!tp?.alive) return "目标不合法"; if (tp.hand.length === 0) return "目标没有手牌"; return null; }, async (st, params) => { const from = ownerId; const target = params.target ?? params.targets?.[0]; const cardId = params.cardId; await markOncePerTurn(st, from, SKILL_ID$29); await pushFrame(st, SKILL_ID$29, from, { target, cardId }); if (st.players[from]?.hand.includes(cardId)) await applyAtom(st, { type: "移动牌", cardId, from: { zone: "手牌", player: from }, to: { zone: "牌堆" } }); const tp = st.players[target]; const hasTrick = tp?.hand.some((id) => isTrickCard(st.cardMap[id])) ?? false; const hasTwo = (tp?.hand.length ?? 0) >= 2; let effect; if (hasTrick && hasTwo) { delete st.localVars[CHOICE_KEY$5]; await applyAtom(st, { type: "请求回应", requestType: CHOOSE_RT$7, target, prompt: { type: "confirm", title: "灭计:选择一项", description: "1.弃置一张锦囊牌;2.依次弃置两张牌", confirmLabel: "弃一张锦囊", cancelLabel: "依次弃两张" }, defaultChoice: false, timeout: 30 }); effect = st.localVars[CHOICE_KEY$5] === "trick" ? "trick" : "twoCards"; delete st.localVars[CHOICE_KEY$5]; } else if (hasTrick) effect = "trick"; else if (hasTwo) effect = "twoCards"; else { await popFrame(st); return; } if (effect === "trick") { delete st.localVars[CARD_KEY]; await applyAtom(st, { type: "请求回应", requestType: PICK_TRICK_RT, target, prompt: { type: "useCard", title: "灭计:弃置一张锦囊牌", cardFilter: { filter: (c) => isTrickCard(c), min: 1, max: 1 } }, mandatory: true, timeout: 30 }); const picked = st.localVars[CARD_KEY]; delete st.localVars[CARD_KEY]; let trickId = picked; const tpTrick = st.players[target]; if ((!trickId || !tpTrick?.hand.includes(trickId) || !isTrickCard(st.cardMap[trickId])) && tpTrick?.alive) trickId = tpTrick.hand.find((id) => isTrickCard(st.cardMap[id])); if (trickId && tpTrick?.hand.includes(trickId)) await applyAtom(st, { type: "弃置", player: target, cardIds: [trickId] }); } else for (let i = 0; i < 2; i++) { const tpNow = st.players[target]; if (!tpNow?.alive || tpNow.hand.length === 0) break; delete st.localVars[CARD_KEY]; await applyAtom(st, { type: "请求回应", requestType: PICK_ONE_RT, target, prompt: { type: "useCard", title: `灭计:依次弃置两张牌(第 ${i + 1}/2 张)`, cardFilter: { filter: () => true, min: 1, max: 1 } }, mandatory: true, timeout: 30 }); const picked = st.localVars[CARD_KEY]; delete st.localVars[CARD_KEY]; let cardId = picked; if ((!cardId || !tpNow.hand.includes(cardId)) && tpNow.alive) cardId = tpNow.hand[0]; if (cardId && st.players[target]?.hand.includes(cardId)) await applyAtom(st, { type: "弃置", player: target, cardIds: [cardId] }); else break; } await popFrame(st); })); for (const pl of state.players) { const seat = pl.index; unloaders.push(registerAction(state, skill.id, seat, "respond", (st, params) => { const slot = st.pendingSlots.get(seat); if (!slot) return "当前不需要回应"; const atom = slot.atom; if (atom["type"] !== "请求回应") return "当前不需要回应"; const rt = atom["requestType"]; if (rt === CHOOSE_RT$7) return null; if (rt === PICK_TRICK_RT || rt === PICK_ONE_RT) { const ids = params.cardIds; const cardId = Array.isArray(ids) ? ids[0] : params.cardId; if (typeof cardId !== "string") return "请选择一张牌"; if (!st.players[seat]?.hand.includes(cardId)) return "牌不在手牌中"; if (rt === PICK_TRICK_RT && !isTrickCard(st.cardMap[cardId])) return "只能选择锦囊牌"; return null; } return "当前不是灭计询问"; }, async (st, params) => { const rt = (st.pendingSlots.get(seat)?.atom)?.requestType; if (rt === CHOOSE_RT$7) st.localVars[CHOICE_KEY$5] = params.choice === true || params.confirmed === true ? "trick" : "twoCards"; else if (rt === PICK_TRICK_RT || rt === PICK_ONE_RT) { const ids = params.cardIds; const cardId = Array.isArray(ids) ? ids[0] : params.cardId; if (typeof cardId === "string") st.localVars[CARD_KEY] = cardId; } })); } return () => { for (const u of unloaders) u(); }; } function onMount$127(_skill, api) { api.defineAction("use", { label: DISPLAY_NAME$40, style: "primary", prompt: { type: "useCardAndTarget", title: "灭计:将一张锦囊牌置于牌堆顶,令一名有手牌的其他角色弃锦囊或弃两张牌", cardFilter: { filter: (c) => isTrickCard(c), min: 1, max: 1 }, targetFilter: { min: 1, max: 1, filter: (view, t) => { if (t === view.currentPlayerIndex) return false; const tp = view.players.find((p) => p.index === t); if (!tp || tp.alive === false) return false; return tp.handCount > 0; } } }, activeWhen: (ctx) => activeUnlessUsedThisTurn(SKILL_ID$29)(ctx) }); api.defineAction("respond", { label: DISPLAY_NAME$40, style: "primary", prompt: { type: "confirm", title: "灭计:选择一项", confirmLabel: "弃一张锦囊", cancelLabel: "依次弃两张" } }); } var SKILL_ID$29, DISPLAY_NAME$40, CHOOSE_RT$7, CHOICE_KEY$5, PICK_TRICK_RT, PICK_ONE_RT, CARD_KEY, _skillModule$63; var init_界灭计 = __esmMin((() => { init_apply(); init_frame(); init_once_per_turn(); init_skill(); SKILL_ID$29 = "界灭计"; DISPLAY_NAME$40 = "灭计"; CHOOSE_RT$7 = "界灭计/选效果"; CHOICE_KEY$5 = "界灭计/effect"; PICK_TRICK_RT = "界灭计/弃锦囊"; PICK_ONE_RT = "界灭计/弃一张"; CARD_KEY = "界灭计/cardId"; _skillModule$63 = { createSkill: createSkill$137, onInit: onInit$137, onMount: onMount$127 }; })); //#endregion //#region src/engine/skills/界烈弓.ts var 界烈弓_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$136, default: () => 界烈弓_default, onInit: () => onInit$136, onMount: () => onMount$126 }); /** 杀点数:A=1, 2-10=面值, J=11, Q=12, K=13 */ function rankValue$5(rank) { if (rank === "A") return 1; if (rank === "J") return 11; if (rank === "Q") return 12; if (rank === "K") return 13; const n = parseInt(rank, 10); return Number.isFinite(n) ? n : 0; } function createSkill$136(id, ownerId) { return { id, ownerId, name: "界烈弓", description: "你的杀攻击范围为此杀点数;指定目标后,若其手牌数不大于你,其不能抵消;若其体力值不小于你,伤害+1" }; } function onInit$136(skill, state) { const ownerId = skill.ownerId; const unloadRangeExemptor = registerAttackRangeExemptor(state, ownerId, (st, from, to, cardId) => { if (!cardId) return false; const card = st.cardMap[cardId]; if (!card) return false; const rank = rankValue$5(card.rank); if (rank <= 0) return false; return effectiveDistance(st, from, to) <= rank; }); registerAction(state, skill.id, ownerId, "respond", (st, _params) => { const slot = st.pendingSlots.get(ownerId); if (!slot) return "当前不需要回应"; const atom = slot.atom; if (atom.type !== "请求回应") return "当前不是请求回应"; if (atom.requestType !== "界烈弓/confirm") return "当前不是界烈弓确认"; return null; }, async (st, params) => { st.localVars[CONFIRM$4] = params.choice === true || params.confirmed === true; }); registerAfterHook(state, skill.id, ownerId, "指定目标", async (ctx) => { const atom = ctx.atom; if (atom.source !== ownerId) return; if (atom.target === void 0) return; const target = atom.target; if (atom.cardId !== void 0) { if (ctx.state.cardMap[atom.cardId]?.name !== "杀") return; } const self = ctx.state.players[ownerId]; const targetPlayer = ctx.state.players[target]; if (!self?.alive || !targetPlayer?.alive) return; if (targetPlayer.tags.includes(TAG_BLOCK)) await applyAtom(ctx.state, { type: "去标签", player: target, tag: TAG_BLOCK }); if (targetPlayer.tags.includes(TAG_BONUS)) await applyAtom(ctx.state, { type: "去标签", player: target, tag: TAG_BONUS }); const condBlock = targetPlayer.hand.length <= self.hand.length; const condBonus = getHealthValue(targetPlayer) >= getHealthValue(self); if (!condBlock && !condBonus) return; delete ctx.state.localVars[CONFIRM$4]; await applyAtom(ctx.state, { type: "请求回应", requestType: "界烈弓/confirm", target: ownerId, prompt: { type: "confirm", title: "是否发动烈弓?", confirmLabel: "发动", cancelLabel: "不发动" }, defaultChoice: false, timeout: 10 }); if (!ctx.state.localVars[CONFIRM$4]) return; if (condBlock) await applyAtom(ctx.state, { type: "加标签", player: target, tag: TAG_BLOCK }); if (condBonus) await applyAtom(ctx.state, { type: "加标签", player: target, tag: TAG_BONUS }); }); registerBeforeHook(state, skill.id, ownerId, "询问闪", async (ctx) => { const atom = ctx.atom; if (atom.source !== ownerId) return; const target = atom.target; if (target === void 0) return; if (!ctx.state.players[target]?.tags.includes(TAG_BLOCK)) return; await applyAtom(ctx.state, { type: "去标签", player: target, tag: TAG_BLOCK }); return { kind: "cancel" }; }); registerBeforeHook(state, skill.id, ownerId, "造成伤害时", async (ctx) => { const atom = ctx.atom; if (atom.source !== ownerId) return; if ((atom.amount ?? 0) <= 0) return; const targetIdx = atom.target; if (targetIdx === void 0) return; const cardId = atom.cardId; if (typeof cardId !== "string") return; if (ctx.state.cardMap[cardId]?.name !== "杀") return; if (!ctx.state.players[targetIdx]?.tags.includes(TAG_BONUS)) return; await applyAtom(ctx.state, { type: "去标签", player: targetIdx, tag: TAG_BONUS }); return { kind: "modify", atom: { ...ctx.atom, amount: (atom.amount ?? 0) + 1 } }; }); return () => { unloadRangeExemptor(); }; } function onMount$126(_skill, api) { api.defineAction("respond", { label: "界烈弓", style: "default", prompt: { type: "confirm", title: "是否发动烈弓?", confirmLabel: "发动", cancelLabel: "不发动" } }); } var TAG_BLOCK, TAG_BONUS, CONFIRM$4, 界烈弓_default; var init_界烈弓 = __esmMin((() => { init_types(); init_apply(); init_skill(); init_distance(); TAG_BLOCK = "界烈弓/禁闪"; TAG_BONUS = "界烈弓/加伤"; CONFIRM$4 = "界烈弓/confirmed"; 界烈弓_default = { createSkill: createSkill$136, onInit: onInit$136, onMount: onMount$126 }; })); //#endregion //#region src/engine/skills/界焚城.ts var 界焚城_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$135, default: () => _skillModule$62, onInit: () => onInit$135, onMount: () => onMount$125 }); function createSkill$135(id, ownerId) { return { id, ownerId, name: DISPLAY_NAME$39, description: "限定技:选一名其他角色开始,所有其他角色依次弃比上家多的牌或受你造成的2点火焰伤害" }; } /** 当前回合的上家弃牌数(默认 0) */ function lastCount(st) { const v = st.turn.vars[LASTCOUNT_KEY]; return typeof v === "number" ? v : 0; } function onInit$135(skill, state) { const ownerId = skill.ownerId; const unloaders = []; unloaders.push(registerAction(state, skill.id, ownerId, "use", (st, _params) => { if (st.currentPlayerIndex !== ownerId) return "不是你的回合"; if (st.phase !== "出牌") return "只能在出牌阶段发动"; if (hasBlockingPending(st)) return "当前有未完成的询问"; if (st.players[ownerId]?.vars[USED_KEY$8]) return "焚城已使用过(限定技)"; if (!st.players[ownerId]?.alive) return "玩家不存在或已死亡"; if (st.players.filter((p) => p.alive && p.index !== ownerId).length === 0) return "场上没有其他存活角色"; return null; }, async (st, _params) => { const from = ownerId; st.players[from].vars[USED_KEY$8] = true; await pushFrame(st, SKILL_ID$28, from, {}); try { delete st.localVars[START_KEY]; await applyAtom(st, { type: "请求回应", requestType: START_RT, target: from, prompt: { type: "choosePlayer", title: "焚城:选择一名其他角色作为起点", description: "所有其他角色从该角色开始依次选择弃牌或受2点火焰伤害", min: 1, max: 1, filter: (_view, t) => t !== from && st.players[t]?.alive === true }, timeout: 30 }); const startArr = st.localVars[START_KEY]; delete st.localVars[START_KEY]; if (!Array.isArray(startArr) || startArr.length !== 1) return; const startTarget = startArr[0]; if (!st.players[startTarget]?.alive) return; st.turn.vars[LASTCOUNT_KEY] = 0; const n = st.players.length; for (let step = 0; step < n; step++) { const p = (startTarget + step) % n; if (p === from) continue; if (!st.players[p]?.alive) continue; const prev = lastCount(st); const handCount = st.players[p].hand.length; const canDiscard = handCount > prev; let effect; if (canDiscard) { delete st.localVars[CHOICE_KEY$4]; await applyAtom(st, { type: "请求回应", requestType: CONFIRM_RT$25, target: p, prompt: { type: "confirm", title: `焚城:选一项(上家弃 ${prev} 张)`, description: `1.弃置 ${prev + 1} 张或更多牌;2.受到李儒造成的 2 点火焰伤害`, confirmLabel: `弃 ≥ ${prev + 1} 张`, cancelLabel: "受 2 点火伤" }, defaultChoice: false, timeout: 30 }); effect = st.localVars[CHOICE_KEY$4] === "discard" ? "discard" : "damage"; delete st.localVars[CHOICE_KEY$4]; } else effect = "damage"; if (effect === "discard") { delete st.localVars[DISCARD_KEY$10]; await applyAtom(st, { type: "请求回应", requestType: DISCARD_RT$11, target: p, prompt: { type: "distribute", mode: "select", title: `焚城:弃置至少 ${prev + 1} 张牌(上家弃 ${prev} 张)`, source: "hand", minTotal: prev + 1, maxTotal: handCount }, timeout: 30 }); const picked = st.localVars[DISCARD_KEY$10]; delete st.localVars[DISCARD_KEY$10]; if (Array.isArray(picked) && picked.length >= prev + 1 && picked.every((id) => st.players[p]?.hand.includes(id))) { await applyAtom(st, { type: "弃置", player: p, cardIds: picked }); st.turn.vars[LASTCOUNT_KEY] = picked.length; } else if (st.players[p]?.alive) await runDamageFlow(st, from, p, 2, void 0, "火焰"); } else if (st.players[p]?.alive) await runDamageFlow(st, from, p, 2, void 0, "火焰"); } delete st.turn.vars[LASTCOUNT_KEY]; } finally { await popFrame(st); } })); for (const pl of state.players) { const seat = pl.index; unloaders.push(registerAction(state, skill.id, seat, "respond", (st, params) => { const slot = st.pendingSlots.get(seat); if (!slot) return "当前不需要回应"; const atom = slot.atom; if (atom["type"] !== "请求回应") return "当前不需要回应"; const rt = atom["requestType"]; if (rt === START_RT) { const targets = params.targets; if (!Array.isArray(targets) || targets.length !== 1) return "请选择一名起点"; const t = targets[0]; if (t === seat) return "不能选自己为起点"; if (!st.players[t]?.alive) return "起点不合法"; return null; } if (rt === CONFIRM_RT$25) return null; if (rt === DISCARD_RT$11) { const cardIds = params.cardIds; if (!Array.isArray(cardIds) || cardIds.length === 0) return "请选择要弃置的牌"; const self = st.players[seat]; if (!self) return "玩家不存在"; const prev = lastCount(st); if (cardIds.length <= prev) return `须弃置至少 ${prev + 1} 张`; if (new Set(cardIds).size !== cardIds.length) return "不能重复选牌"; for (const id of cardIds) if (!self.hand.includes(id)) return "牌不在手牌中"; return null; } return "当前不是焚城询问"; }, async (st, params) => { const rt = (st.pendingSlots.get(seat)?.atom)?.requestType; if (rt === START_RT) { const targets = params.targets; if (Array.isArray(targets)) st.localVars[START_KEY] = targets; } else if (rt === CONFIRM_RT$25) st.localVars[CHOICE_KEY$4] = params.choice === true || params.confirmed === true ? "discard" : "damage"; else if (rt === DISCARD_RT$11) { const cardIds = params.cardIds; if (Array.isArray(cardIds)) st.localVars[DISCARD_KEY$10] = cardIds; } })); } return () => { for (const u of unloaders) u(); }; } function onMount$125(_skill, api) { api.defineAction("use", { label: DISPLAY_NAME$39, style: "danger", prompt: { type: "confirm", title: "焚城(限定技):选一名其他角色开始,所有其他角色依次弃比上家多的牌或受2点火焰伤害" }, activeWhen: (ctx) => defaultPlayActive(ctx) }); api.defineAction("respond", { label: DISPLAY_NAME$39, style: "danger", prompt: { type: "confirm", title: "焚城:选择一项" } }); } var SKILL_ID$28, DISPLAY_NAME$39, USED_KEY$8, START_RT, START_KEY, CONFIRM_RT$25, CHOICE_KEY$4, DISCARD_RT$11, DISCARD_KEY$10, LASTCOUNT_KEY, _skillModule$62; var init_界焚城 = __esmMin((() => { init_apply(); init_frame(); init_damage(); init_action_active(); init_skill(); SKILL_ID$28 = "界焚城"; DISPLAY_NAME$39 = "焚城"; USED_KEY$8 = "界焚城/used"; START_RT = "界焚城/选起点"; START_KEY = "界焚城/start"; CONFIRM_RT$25 = "界焚城/选效果"; CHOICE_KEY$4 = "界焚城/effect"; DISCARD_RT$11 = "界焚城/弃牌"; DISCARD_KEY$10 = "界焚城/discarded"; LASTCOUNT_KEY = "界焚城/lastCount"; _skillModule$62 = { createSkill: createSkill$135, onInit: onInit$135, onMount: onMount$125 }; })); //#endregion //#region src/engine/skills/界燕语.ts var 界燕语_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$134, default: () => _skillModule$61, onInit: () => onInit$134, onMount: () => onMount$124 }); function createSkill$134(id, ownerId) { return { id, ownerId, name: DISPLAY_NAME$38, description: "出牌阶段,你可以重铸【杀】;出牌阶段结束时,若你本阶段失去过至少两张【杀】,可令一名男性角色摸两张牌" }; } function getLostSha(state, ownerId) { return state.players[ownerId]?.vars[LOST_SHA_KEY] ?? 0; } function addLostSha(state, ownerId, n) { const self = state.players[ownerId]; if (!self) return; const cur = getLostSha(state, ownerId); self.vars[LOST_SHA_KEY] = cur + n; } /** 是否处于 owner 的出牌阶段(用于增计数 hooks 的门控) */ function inMyPlayPhase(state, ownerId) { return state.currentPlayerIndex === ownerId && state.phase === "出牌"; } /** 校验某座次是否为存活男性 */ function isMaleAlive$3(state, target) { const p = state.players[target]; if (!p?.alive) return false; return getGender(p.character) === "男"; } function hasMaleAlive(state) { return state.players.some((p) => p.alive && getGender(p.character) === "男"); } function currentRequestType$8(state, ownerId) { const slot = state.pendingSlots.get(ownerId); if (!slot) return void 0; return slot.atom.requestType; } function onInit$134(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "recycle", (st, params) => { const myTurn = st.currentPlayerIndex === ownerId; const inActPhase = st.phase === "出牌"; const free = !hasBlockingPending(st); const self = st.players[ownerId]; const selfAlive = self?.alive === true; const cardId = params.cardId; const cardIdOk = typeof cardId === "string"; const card = cardIdOk ? st.cardMap[cardId] : void 0; const cardInHand = cardIdOk && self.hand.includes(cardId); const isSha = !!card && card.name === "杀"; return myTurn && inActPhase && free && selfAlive && cardInHand && isSha ? null : "现在不能重铸(需出牌阶段、手中【杀】)"; }, async (st, params) => { const from = ownerId; const cardId = params.cardId; await pushFrame(st, SKILL_ID$27, from, { ...params, recycle: true }); await recastCard(st, from, cardId); await popFrame(st); }); registerAction(state, skill.id, ownerId, "respond", (st, params) => { const rt = currentRequestType$8(st, ownerId); if (rt !== CONFIRM_RT$24 && rt !== TARGET_RT$15) return "当前不是燕语询问"; if (rt === TARGET_RT$15) { const t = params.target; if (typeof t !== "number") return "请选择一名男性角色"; if (!isMaleAlive$3(st, t)) return "目标必须是存活的男性角色"; } return null; }, async (st, params) => { const rt = currentRequestType$8(st, ownerId); if (rt === CONFIRM_RT$24) st.localVars[CONFIRMED_KEY$22] = params.choice === true || params.confirmed === true; else if (rt === TARGET_RT$15) st.localVars[TARGET_KEY$21] = params.target; }); registerAfterHook(state, skill.id, ownerId, "阶段开始", async (ctx) => { const atom = ctx.atom; if (atom.type !== "阶段开始") return; if (atom.phase !== "出牌") return; if (atom.player !== ownerId) return; const self = ctx.state.players[ownerId]; if (!self) return; self.vars[LOST_SHA_KEY] = 0; }); registerAfterHook(state, skill.id, ownerId, "移动牌", async (ctx) => { if (!inMyPlayPhase(ctx.state, ownerId)) return; const atom = ctx.atom; if (atom.from?.zone !== "手牌") return; if (atom.from.player !== ownerId) return; const cardId = atom.cardId; if (typeof cardId !== "string") return; if (ctx.state.cardMap[cardId]?.name !== "杀") return; addLostSha(ctx.state, ownerId, 1); }); registerAfterHook(state, skill.id, ownerId, "弃置", async (ctx) => { if (!inMyPlayPhase(ctx.state, ownerId)) return; const atom = ctx.atom; if (atom.player !== ownerId) return; const shaCount = (Array.isArray(atom.cardIds) ? atom.cardIds : []).filter((id) => ctx.state.cardMap[id]?.name === "杀").length; if (shaCount > 0) addLostSha(ctx.state, ownerId, shaCount); }); registerBeforeHook(state, skill.id, ownerId, "阶段结束", async (ctx) => { const atom = ctx.atom; if (atom.type !== "阶段结束") return; if (atom.phase !== "出牌") return; if (atom.player !== ownerId) return; const st = ctx.state; if (!st.players[ownerId]?.alive) return; if (getLostSha(st, ownerId) < 2) return; if (!hasMaleAlive(st)) return; delete st.localVars[CONFIRMED_KEY$22]; await applyAtom(st, { type: "请求回应", requestType: CONFIRM_RT$24, target: ownerId, prompt: { type: "confirm", title: "是否发动燕语?(令一名男性角色摸两张牌)", confirmLabel: "发动", cancelLabel: "不发动" }, defaultChoice: false, timeout: 15 }); if (!st.localVars[CONFIRMED_KEY$22]) return; delete st.localVars[CONFIRMED_KEY$22]; delete st.localVars[TARGET_KEY$21]; await applyAtom(st, { type: "请求回应", requestType: TARGET_RT$15, target: ownerId, prompt: { type: "choosePlayer", title: "燕语:选择一名男性角色(其摸两张牌)", min: 1, max: 1, filter: (view, t) => view.players[t]?.alive === true && getGender(view.players[t].character) === "男" }, timeout: 15 }); const target = st.localVars[TARGET_KEY$21]; delete st.localVars[TARGET_KEY$21]; if (typeof target !== "number") return; if (!st.players[target]?.alive) return; await applyAtom(st, { type: "摸牌", player: target, count: 2 }); }); return () => {}; } function onMount$124(_skill, api) { api.defineAction("recycle", { label: `${DISPLAY_NAME$38}·重铸`, style: "primary", prompt: { type: "useCard", title: "燕语:重铸一张【杀】(弃此牌,摸一张)", cardFilter: { filter: (c) => c.name === "杀", min: 1, max: 1 } } }); api.defineAction("respond", { label: DISPLAY_NAME$38, style: "primary", prompt: { type: "confirm", title: "是否发动燕语?", confirmLabel: "发动", cancelLabel: "不发动" } }); return () => {}; } var SKILL_ID$27, DISPLAY_NAME$38, LOST_SHA_KEY, CONFIRM_RT$24, TARGET_RT$15, CONFIRMED_KEY$22, TARGET_KEY$21, _skillModule$61; var init_界燕语 = __esmMin((() => { init_apply(); init_frame(); init_recast(); init_skill(); init_character_meta(); SKILL_ID$27 = "界燕语"; DISPLAY_NAME$38 = "燕语"; LOST_SHA_KEY = `${SKILL_ID$27}/lostShaThisPhase`; CONFIRM_RT$24 = `${SKILL_ID$27}/confirm`; TARGET_RT$15 = `${SKILL_ID$27}/target`; CONFIRMED_KEY$22 = `${SKILL_ID$27}/confirmed`; TARGET_KEY$21 = `${SKILL_ID$27}/targetChoice`; _skillModule$61 = { createSkill: createSkill$134, onInit: onInit$134, onMount: onMount$124 }; })); //#endregion //#region src/engine/skills/界父魂.ts var 界父魂_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$133, default: () => 界父魂_default, onInit: () => onInit$133, onMount: () => onMount$123 }); function createSkill$133(id, ownerId) { return { id, ownerId, name: "父魂", description: "转化技:两张牌当杀;转化杀目标只能用同色手牌响应;出牌阶段杀造成伤害后本回合获得武圣咆哮" }; } /** 界父魂 影子卡 id:${id1}#${id2}#界父魂(后缀 = 技能 id,与客户端构造约定一致) */ function shadowIdOf$12(id1, id2) { return `${id1}#${id2}#界父魂`; } /** 玩家某张牌是否在自己的可控区域(手牌或装备区) */ function cardInOwnZone(self, cardId) { if (self.hand.includes(cardId)) return true; return Object.values(self.equipment).some((id) => id === cardId); } function grantedShadowIdOf(cardId) { return `${cardId}#界父魂`; } function isGranted(state, ownerId) { return state.turn.vars[GRANT_VAR] === ownerId; } function onInit$133(skill, state) { const ownerId = skill.ownerId; const unregTransform = registerAction(state, skill.id, ownerId, "transform", (state, params) => { const myTurn = state.currentPlayerIndex === ownerId; const inActPhase = state.phase === "出牌"; const free = !hasBlockingPending(state); const self = state.players[ownerId]; const selfAlive = self.alive === true; const cardIds = params.cardIds; if (!Array.isArray(cardIds) || cardIds.length !== 2) return "需要选择 2 张牌"; const [id1, id2] = cardIds; if (typeof id1 !== "string" || typeof id2 !== "string") return "cardIds 必须为字符串"; if (id1 === id2) return "不能选择同一张牌"; const cardsOwned = !!self && cardInOwnZone(self, id1) && cardInOwnZone(self, id2); const c1 = state.cardMap[id1]; const c2 = state.cardMap[id2]; const cardsExist = !!c1 && !!c2; const slot = state.pendingSlots.get(ownerId); const atomType = (slot?.atom)?.type; const reqType = (slot?.atom)?.requestType; const atomTarget = (slot?.atom)?.target; return (myTurn && inActPhase && free || !!slot && atomTarget === ownerId && (atomType === "询问杀" || atomType === "请求回应" && reqType === "杀/respondKill")) && selfAlive && cardsOwned && cardsExist ? null : "父魂转化条件不满足"; }, async (state, params) => { const cardIds = params.cardIds; const [id1, id2] = cardIds; const self = state.players[ownerId]; const equipsToUnequip = []; for (const cid of cardIds) { const entry = Object.entries(self.equipment).find(([, id]) => id === cid); if (entry) equipsToUnequip.push({ slot: entry[0], cardId: cid }); } if (equipsToUnequip.length > 0) { params["_unequipped"] = equipsToUnequip; for (const { slot } of equipsToUnequip) await applyAtom(state, { type: "卸下", player: ownerId, slot }); } await applyAtom(state, { type: "当作", player: ownerId, cardIds, shadowId: shadowIdOf$12(id1, id2), outputName: "杀" }); }, (state, params) => { const cardIds = params.cardIds; const [id1, id2] = Array.isArray(cardIds) ? cardIds : []; const sId = id1 && id2 ? shadowIdOf$12(id1, id2) : void 0; if (!sId) return; delete state.cardMap[sId]; const self = state.players[ownerId]; const idx = self.hand.indexOf(sId); if (idx >= 0) self.hand.splice(idx, 1); const unequipped = params["_unequipped"]; if (unequipped) for (const { slot, cardId } of unequipped) if (self.equipment[slot] === void 0) { self.equipment[slot] = cardId; if (slot === "武器") { const card = state.cardMap[cardId]; self.vars[DISTANCE_ATTACK_RANGE_KEY] = card?.range ?? 1; } } else self.hand.push(cardId); for (const cid of cardIds) if (!self.hand.includes(cid) && !unequipped?.some((u) => u.cardId === cid)) self.hand.push(cid); }); const unregWusheng = registerAction(state, skill.id, ownerId, "武圣transform", (state, params) => { if (!isGranted(state, ownerId)) return "本回合未获得武圣"; const myTurn = state.currentPlayerIndex === ownerId; const inActPhase = state.phase === "出牌"; const free = !hasBlockingPending(state); const self = state.players[ownerId]; const selfAlive = self.alive === true; const cardId = params.cardId; const cardIdOk = typeof cardId === "string"; const card = cardIdOk ? state.cardMap[cardId] : void 0; const cardInHand = cardIdOk && self.hand.includes(cardId); const isRed = !!card && card.color === "红"; const slot = state.pendingSlots.get(ownerId); const atomType = (slot?.atom)?.type; const reqType = (slot?.atom)?.requestType; const atomTarget = (slot?.atom)?.target; return (myTurn && inActPhase && free || !!slot && atomTarget === ownerId && (atomType === "询问杀" || atomType === "请求回应" && reqType === "杀/respondKill")) && selfAlive && cardInHand && isRed ? null : "现在不能使用武圣"; }, async (state, params) => { const cardId = params.cardId; const shadowId = grantedShadowIdOf(cardId); await applyAtom(state, { type: "当作", player: ownerId, cardIds: [cardId], shadowId, outputName: "杀" }); }, (state, params) => { const cardId = params.cardId; const sId = grantedShadowIdOf(cardId); delete state.cardMap[sId]; const self = state.players[ownerId]; const idx = self.hand.indexOf(sId); if (idx >= 0) self.hand[idx] = cardId; }); const unregMax = registerSlashUnlimitedProvider(state, ownerId, (s, p) => isGranted(s, p)); const unregDmgHook = registerAfterHook(state, skill.id, ownerId, "造成伤害后", async (ctx) => { const atom = ctx.atom; if (atom.source !== ownerId) return; if ((atom.amount ?? 0) <= 0) return; if (ctx.state.phase !== "出牌") return; if (ctx.state.currentPlayerIndex !== ownerId) return; const cardId = atom.cardId; if (!((cardId ? ctx.state.cardMap[cardId] : void 0)?.name === "杀" || topFrame(ctx.state)?.skillId === "杀")) return; if (isGranted(ctx.state, ownerId)) return; ctx.state.turn.vars[GRANT_VAR] = ownerId; await applyAtom(ctx.state, { type: "回合用量", player: ownerId, key: slashUnlimitedKey("父魂"), value: true }); }); const unregBeforeDodge = registerBeforeHook(state, skill.id, ownerId, "询问闪", async (ctx) => { if (ctx.atom.source !== ownerId) return; const frame = topFrame(ctx.state); if (frame?.skillId !== "杀") return; const cardId = frame.params["cardId"]; if (typeof cardId !== "string") return; if (!cardId.includes("#")) return; const slashCard = ctx.state.cardMap[cardId]; if (!slashCard) return; const color = slashCard.color; if (color !== "红" && color !== "黑") return; ctx.state.localVars[COLOR_LIMIT_VAR] = color; }); const unregAfterDodge = registerAfterHook(state, skill.id, ownerId, "询问闪", async (ctx) => { delete ctx.state.localVars[COLOR_LIMIT_VAR]; }); const unregAlt = declareAlternativeResponse(state, ownerId, "询问杀"); return () => { unregTransform(); unregWusheng(); unregMax(); unregDmgHook(); unregBeforeDodge(); unregAfterDodge(); unregAlt(); }; } function onMount$123(skill, api) { api.defineAction("transform", { label: "父魂", style: "passive", prompt: { type: "useCardAndTarget", title: "选择 2 张牌当杀使用", cardFilter: { filter: () => true, min: 2, max: 2 }, targetFilter: { min: 1, max: 1, filter: (view, t) => viewCanAttack(view.players, view.cardMap, view.currentPlayerIndex, t) } }, transform: (card) => ({ name: "杀", sourceCardId: card.id, fromSkill: skill.id }), activeWhen: (ctx) => { const p = ctx.view.players[ctx.perspectiveIdx]; if (!p?.alive) return false; const equipCount = Object.values(p.equipment ?? {}).filter(Boolean).length; if ((p.handCount ?? 0) + equipCount < 2) return false; const ownTurnCanSlash = defaultPlayActive(ctx) && viewCanSlash(ctx.view, ctx.perspectiveIdx); const slot = ctx.view.pending; const atomType = (slot?.atom)?.type; const reqType = (slot?.atom)?.requestType; const askedKill = !!slot && slot.target === ctx.perspectiveIdx && (atomType === "询问杀" || atomType === "请求回应" && reqType === "杀/respondKill"); return ownTurnCanSlash || askedKill; } }); api.defineAction("武圣transform", { label: "武圣", style: "passive", prompt: { type: "useCardAndTarget", title: "选择一张红色手牌当杀使用", cardFilter: { filter: (c) => c.color === "红", min: 1, max: 1 }, targetFilter: { min: 1, max: 1, filter: (view, t) => viewCanAttack(view.players, view.cardMap, view.currentPlayerIndex, t) } }, transform: (card) => ({ name: "杀", sourceCardId: card.id, fromSkill: skill.id }), activeWhen: (ctx) => { const p = ctx.view.players[ctx.perspectiveIdx]; if (!p?.alive) return false; if (!p.turnUsage?.[slashUnlimitedKey("父魂")]) return false; if (!(p.hand?.some((c) => c.color === "红") ?? false)) return false; const ownTurnCanSlash = defaultPlayActive(ctx) && viewCanSlash(ctx.view, ctx.perspectiveIdx); const slot = ctx.view.pending; const atomType = (slot?.atom)?.type; const reqType = (slot?.atom)?.requestType; const askedKill = !!slot && slot.target === ctx.perspectiveIdx && (atomType === "询问杀" || atomType === "请求回应" && reqType === "杀/respondKill"); return ownTurnCanSlash || askedKill; } }); } var GRANT_VAR, COLOR_LIMIT_VAR, 界父魂_default; var init_界父魂 = __esmMin((() => { init_skill(); init_apply(); init_frame(); init_slash_quota(); init_viewDistance(); init_action_active(); init_vars_keys(); GRANT_VAR = "父魂/granted"; COLOR_LIMIT_VAR = "闪/色限制"; 界父魂_default = { createSkill: createSkill$133, onInit: onInit$133, onMount: onMount$123 }; })); //#endregion //#region src/engine/skills/界狂骨.ts var 界狂骨_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$132, default: () => 界狂骨_default, onInit: () => onInit$132, onMount: () => onMount$122 }); function createSkill$132(id, ownerId) { return { id, ownerId, name: "界狂骨", description: "对距离1以内的角色每造成1点伤害时,你可以回复1点体力或摸一张牌" }; } function onInit$132(skill, state) { const ownerId = skill.ownerId; const offRespond = registerAction(state, skill.id, ownerId, "respond", (st, _params) => { const slot = st.pendingSlots.get(ownerId); if (!slot) return "当前不需要回应"; const atom = slot.atom; if (atom["type"] !== "请求回应") return "当前不需要回应"; const rt = atom["requestType"]; if (rt !== CONFIRM_REQUEST$9 && rt !== CHOOSE_REQUEST$1) return "当前不是狂骨询问"; return null; }, async (st, params) => { const rt = (st.pendingSlots.get(ownerId)?.atom)?.requestType; if (rt === CONFIRM_REQUEST$9) st.localVars[CONFIRMED_KEY$21] = params.choice === true || params.confirmed === true; else if (rt === CHOOSE_REQUEST$1) st.localVars[CHOICE_KEY$3] = params.choice === true ? "heal" : "draw"; }); registerAfterHook(state, skill.id, ownerId, "造成伤害后", async (ctx) => { const atom = ctx.atom; if (atom.source !== ownerId) return; const amount = atom.amount ?? 0; if (amount <= 0) return; if (atom.target === void 0) return; const target = atom.target; if (!ctx.state.players[target]?.alive) return; if (effectiveDistance(ctx.state, ownerId, target) > 1) return; for (let i = 0; i < amount; i++) { const self = ctx.state.players[ownerId]; if (!self?.alive) return; const fullHealth = self.health >= self.maxHealth; delete ctx.state.localVars[CONFIRMED_KEY$21]; await applyAtom(ctx.state, { type: "请求回应", requestType: CONFIRM_REQUEST$9, target: ownerId, prompt: { type: "confirm", title: `是否发动界狂骨?(第 ${i + 1}/${amount} 点伤害)`, confirmLabel: "发动", cancelLabel: "不发动" }, defaultChoice: false, timeout: 30 }); if (ctx.state.localVars[CONFIRMED_KEY$21] !== true) continue; delete ctx.state.localVars[CHOICE_KEY$3]; await applyAtom(ctx.state, { type: "请求回应", requestType: CHOOSE_REQUEST$1, target: ownerId, prompt: { type: "confirm", title: "界狂骨:回复1点体力,或摸一张牌?", confirmLabel: "回复1点体力", cancelLabel: "摸一张牌", confirmDisabled: fullHealth ? true : void 0 }, defaultChoice: false, timeout: 30 }); if (ctx.state.localVars[CHOICE_KEY$3] === "heal") await applyAtom(ctx.state, { type: "回复体力", target: ownerId, amount: 1 }); else await applyAtom(ctx.state, { type: "摸牌", player: ownerId, count: 1 }); } }); return () => { offRespond(); }; } function onMount$122(_skill, _api) {} var CONFIRM_REQUEST$9, CHOOSE_REQUEST$1, CONFIRMED_KEY$21, CHOICE_KEY$3, 界狂骨_default; var init_界狂骨 = __esmMin((() => { init_apply(); init_skill(); init_distance(); CONFIRM_REQUEST$9 = "界狂骨/confirm"; CHOOSE_REQUEST$1 = "界狂骨/choose"; CONFIRMED_KEY$21 = "界狂骨/confirmed"; CHOICE_KEY$3 = "界狂骨/choice"; 界狂骨_default = { createSkill: createSkill$132, onInit: onInit$132, onMount: onMount$122 }; })); //#endregion //#region src/engine/skills/界献图.ts var 界献图_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$131, default: () => _skillModule$60, onInit: () => onInit$131, onMount: () => onMount$121 }); function createSkill$131(id, ownerId) { return { id, ownerId, name: DISPLAY_NAME$37, description: "其他角色出牌阶段开始时,你可以摸至多两张牌,然后交给其等量牌;此阶段结束时若其造成伤害小于你以此法交给其的牌数,你失去1点体力" }; } function currentRequestType$7(st, ownerId) { const slot = st.pendingSlots.get(ownerId); if (!slot) return void 0; return slot.atom.requestType; } function onInit$131(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "respond", (st, params) => { const rt = currentRequestType$7(st, ownerId); if (!rt) return "当前不需要回应"; if (rt === TRIGGER_RT$5 || rt === DRAW2_RT || rt === DRAW1_RT) return null; if (rt === GIVE_CARDS_RT$1) { const cardIds = params.cardIds; const n = st.localVars[GIVE_N_VAR] ?? 0; if (!Array.isArray(cardIds)) return "需要 cardIds 数组"; if (cardIds.length !== n) return `需要给出 ${n} 张牌`; if (new Set(cardIds).size !== cardIds.length) return "不能重复"; const self = st.players[ownerId]; if (!self) return "玩家不存在"; if (!cardIds.every((id) => typeof id === "string" && self.hand.includes(id))) return "牌不在手牌中"; return null; } return "当前不是献图询问"; }, async (st, params) => { const rt = currentRequestType$7(st, ownerId); if (!rt) return; if (rt === TRIGGER_RT$5 || rt === DRAW2_RT || rt === DRAW1_RT) st.localVars[CONFIRM_KEY$11] = params.choice === true || params.confirmed === true; else if (rt === GIVE_CARDS_RT$1) { const cardIds = params.cardIds; st.localVars[GIVE_CARDS_KEY$1] = Array.isArray(cardIds) ? cardIds.filter((id) => typeof id === "string") : []; } }); registerAfterHook(state, skill.id, ownerId, "阶段开始", async (ctx) => { const atom = ctx.atom; if (atom.type !== "阶段开始") return; if (atom.phase !== "出牌") return; const currentPlayer = atom.player; if (typeof currentPlayer !== "number") return; if (currentPlayer === ownerId) return; const st = ctx.state; if (!st.players[ownerId]?.alive) return; if (!st.players[currentPlayer]?.alive) return; delete st.turn.vars[givenKey(currentPlayer)]; delete st.turn.vars[dmgKey(currentPlayer)]; delete st.localVars[CONFIRM_KEY$11]; await applyAtom(st, { type: "请求回应", requestType: TRIGGER_RT$5, target: ownerId, prompt: { type: "confirm", title: `是否发动献图?(摸至多两张牌,然后交给 ${st.players[currentPlayer]?.name} 等量牌)`, confirmLabel: "发动", cancelLabel: "不发动" }, defaultChoice: false, timeout: 15 }); if (!st.localVars[CONFIRM_KEY$11]) { delete st.localVars[CONFIRM_KEY$11]; return; } delete st.localVars[CONFIRM_KEY$11]; await pushFrame(st, SKILL_ID$26, ownerId, { target: currentPlayer }); let drawN; delete st.localVars[CONFIRM_KEY$11]; await applyAtom(st, { type: "请求回应", requestType: DRAW2_RT, target: ownerId, prompt: { type: "confirm", title: "献图:摸几张牌?(确认=2张,取消=少一些)", confirmLabel: "摸 2 张", cancelLabel: "少一些" }, defaultChoice: true, timeout: 15 }); if (st.localVars[CONFIRM_KEY$11] !== false) drawN = 2; else { delete st.localVars[CONFIRM_KEY$11]; await applyAtom(st, { type: "请求回应", requestType: DRAW1_RT, target: ownerId, prompt: { type: "confirm", title: "献图:摸 1 张牌?(确认=1张,取消=0张/不摸)", confirmLabel: "摸 1 张", cancelLabel: "不摸" }, defaultChoice: true, timeout: 15 }); drawN = st.localVars[CONFIRM_KEY$11] !== false ? 1 : 0; } delete st.localVars[CONFIRM_KEY$11]; if (drawN === 0) { st.turn.vars[givenKey(currentPlayer)] = 0; await popFrame(st); return; } await applyAtom(st, { type: "摸牌", player: ownerId, count: drawN }); if (!st.players[ownerId]?.alive) { st.turn.vars[givenKey(currentPlayer)] = 0; await popFrame(st); return; } const handCount = st.players[ownerId].hand.length; const giveN = Math.min(drawN, handCount); if (giveN === 0) { st.turn.vars[givenKey(currentPlayer)] = 0; await popFrame(st); return; } if (giveN === handCount) { const allCards = [...st.players[ownerId].hand]; let actuallyGiven = 0; for (const cid of allCards) { if (!st.players[ownerId].hand.includes(cid)) continue; await applyAtom(st, { type: "给予", cardId: cid, from: ownerId, to: currentPlayer }); actuallyGiven++; } st.turn.vars[givenKey(currentPlayer)] = actuallyGiven; await popFrame(st); return; } st.localVars[GIVE_N_VAR] = giveN; delete st.localVars[GIVE_CARDS_KEY$1]; await applyAtom(st, { type: "请求回应", requestType: GIVE_CARDS_RT$1, target: ownerId, prompt: { type: "useCard", title: `献图:选择 ${giveN} 张手牌给予 ${st.players[currentPlayer]?.name}`, cardFilter: { filter: () => true, min: giveN, max: giveN } }, mandatory: true, timeout: 30 }); delete st.localVars[GIVE_N_VAR]; const rawCards = st.localVars[GIVE_CARDS_KEY$1]; delete st.localVars[GIVE_CARDS_KEY$1]; const cards = Array.isArray(rawCards) ? rawCards : []; let actuallyGiven = 0; for (const cid of cards) { if (!st.players[ownerId].hand.includes(cid)) continue; await applyAtom(st, { type: "给予", cardId: cid, from: ownerId, to: currentPlayer }); actuallyGiven++; } if (actuallyGiven < giveN) for (const cid of [...st.players[ownerId].hand]) { if (actuallyGiven >= giveN) break; await applyAtom(st, { type: "给予", cardId: cid, from: ownerId, to: currentPlayer }); actuallyGiven++; } st.turn.vars[givenKey(currentPlayer)] = actuallyGiven; await popFrame(st); }); registerAfterHook(state, skill.id, ownerId, "造成伤害后", async (ctx) => { const st = ctx.state; if (st.phase !== "出牌") return; const atom = ctx.atom; if (typeof atom.source !== "number") return; if ((atom.amount ?? 0) <= 0) return; if (atom.source !== st.currentPlayerIndex) return; if (typeof st.turn.vars[givenKey(st.currentPlayerIndex)] !== "number") return; const cur = st.turn.vars[dmgKey(st.currentPlayerIndex)] ?? 0; st.turn.vars[dmgKey(st.currentPlayerIndex)] = cur + (atom.amount ?? 0); }); registerAfterHook(state, skill.id, ownerId, "阶段结束", async (ctx) => { const atom = ctx.atom; if (atom.type !== "阶段结束") return; if (atom.phase !== "出牌") return; const currentPlayer = atom.player; if (typeof currentPlayer !== "number") return; if (currentPlayer === ownerId) return; const st = ctx.state; if (!st.players[ownerId]?.alive) return; const given = st.turn.vars[givenKey(currentPlayer)]; const damage = st.turn.vars[dmgKey(currentPlayer)] ?? 0; delete st.turn.vars[givenKey(currentPlayer)]; delete st.turn.vars[dmgKey(currentPlayer)]; if (typeof given !== "number" || given <= 0) return; if (damage >= given) return; await applyAtom(st, { type: "失去体力", target: ownerId, amount: 1 }); }); return () => {}; } function onMount$121(_skill, api) { api.defineAction("respond", { label: DISPLAY_NAME$37, style: "primary", prompt: { type: "confirm", title: "是否发动献图?", confirmLabel: "发动", cancelLabel: "不发动" } }); } var SKILL_ID$26, DISPLAY_NAME$37, givenKey, dmgKey, GIVE_CARDS_KEY$1, GIVE_N_VAR, CONFIRM_KEY$11, TRIGGER_RT$5, DRAW2_RT, DRAW1_RT, GIVE_CARDS_RT$1, _skillModule$60; var init_界献图 = __esmMin((() => { init_apply(); init_frame(); init_skill(); SKILL_ID$26 = "界献图"; DISPLAY_NAME$37 = "献图"; givenKey = (currentPlayer) => `${SKILL_ID$26}/given/${currentPlayer}`; dmgKey = (currentPlayer) => `${SKILL_ID$26}/damage/${currentPlayer}`; GIVE_CARDS_KEY$1 = `${SKILL_ID$26}/giveCards`; GIVE_N_VAR = `${SKILL_ID$26}/giveN`; CONFIRM_KEY$11 = `${SKILL_ID$26}/confirmed`; TRIGGER_RT$5 = `${SKILL_ID$26}/trigger`; DRAW2_RT = `${SKILL_ID$26}/draw2`; DRAW1_RT = `${SKILL_ID$26}/draw1`; GIVE_CARDS_RT$1 = `${SKILL_ID$26}/giveCards`; _skillModule$60 = { createSkill: createSkill$131, onInit: onInit$131, onMount: onMount$121 }; })); //#endregion //#region src/engine/skills/界献州.ts var 界献州_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$130, default: () => _skillModule$59, onInit: () => onInit$130, onMount: () => onMount$120 }); function createSkill$130(id, ownerId) { return { id, ownerId, name: DISPLAY_NAME$36, description: "限定技:出牌阶段将你装备区所有牌交给一名其他角色,其选择令你回复X点体力或对其攻击范围内至多X名角色各造成1点伤害(X为装备数)" }; } /** 玩家装备区所有 cardId(已填充槽位)。 */ function equippedCardIds(state, playerId) { const eq = state.players[playerId]?.equipment ?? {}; const ids = []; for (const slot of EQUIP_SLOTS$3) { const id = eq[slot]; if (id) ids.push(id); } return ids; } function onInit$130(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "use", (st, params) => { if (st.currentPlayerIndex !== ownerId) return "不是你的回合"; if (st.phase !== "出牌") return "只能在出牌阶段发动"; if (hasBlockingPending(st)) return "当前有未完成的询问"; const self = st.players[ownerId]; if (!self?.alive) return "玩家不存在或已死亡"; if (self.vars[USED_KEY$7]) return "献州已使用过(限定技)"; if (equippedCardIds(st, ownerId).length === 0) return "装备区无牌,无法发动"; const target = params.target; if (typeof target !== "number") return "需要指定目标"; if (target === ownerId) return "不能以自己为目标"; if (!st.players[target]?.alive) return "目标不合法"; return null; }, async (st, params) => { const from = ownerId; const target = params.target; st.players[from].vars[USED_KEY$7] = true; await pushFrame(st, SKILL_ID$25, from, { ...params }); try { const cardIds = equippedCardIds(st, from); const X = cardIds.length; for (const cardId of cardIds) { const card = st.cardMap[cardId]; const slot = EQUIP_SLOTS$3.find((s) => st.players[from].equipment[s] === cardId); if (!slot) continue; if (card?.name && hasSkillModule(card.name)) await applyAtom(st, { type: "移除技能", player: from, skillId: card.name }); await applyAtom(st, { type: "卸下", player: from, slot }); } for (const cardId of cardIds) await applyAtom(st, { type: "给予", cardId, from, to: target }); st.localVars[COUNT_KEY] = X; st.localVars[TARGET_KEY$20] = target; delete st.localVars[OPTION_KEY$2]; await applyAtom(st, { type: "请求回应", requestType: OPTION_RT$2, target, prompt: { type: "confirm", title: `献州:${st.players[from]?.name ?? `P${from}`} 将 ${X} 张装备交给你。选择一项`, description: `确认 = 令其回复 ${X} 点体力;取消 = 对你攻击范围内至多 ${X} 名角色各造成 1 点伤害`, confirmLabel: `令其回复 ${X} 点体力`, cancelLabel: `对至多 ${X} 名角色造伤` }, defaultChoice: false, timeout: 20 }); const heal = st.localVars[OPTION_KEY$2] === true; delete st.localVars[OPTION_KEY$2]; if (heal) { if (st.players[from]?.alive && X > 0) await applyAtom(st, { type: "回复体力", target: from, amount: X, source: target }); } else { delete st.localVars[TARGETS_KEY$3]; await applyAtom(st, { type: "请求回应", requestType: TARGETS_RT, target, prompt: { type: "selectTarget", title: `献州:选择至多 ${X} 名你攻击范围内的角色(对其各造成 1 点伤害)`, description: "可放弃(选 0 名);伤害来源为你", targetFilter: { min: 0, max: X, filter: (view, t) => { if (t === target) return false; const tp = view.players.find((pl) => pl.index === t); if (!tp || tp.alive === false) return false; return viewCanAttack(view.players, view.cardMap, target, t); } } }, defaultChoice: [], timeout: 30 }); const targetsRaw = st.localVars[TARGETS_KEY$3]; delete st.localVars[TARGETS_KEY$3]; const dmgTargets = Array.isArray(targetsRaw) ? targetsRaw : []; for (const t of dmgTargets) { if (!st.players[t]?.alive) continue; if (!inAttackRange(st, target, t)) continue; await runDamageFlow(st, target, t, 1); } } delete st.localVars[COUNT_KEY]; delete st.localVars[TARGET_KEY$20]; } finally { await popFrame(st); } }); const unloaders = []; for (const p of state.players) { const seatId = p.index; const u = registerAction(state, skill.id, seatId, "respond", (st, params) => { const slot = st.pendingSlots.get(seatId); if (!slot) return "当前不需要回应"; const atom = slot.atom; if (atom["type"] !== "请求回应") return "当前不需要回应"; const rt = atom["requestType"]; if (rt !== OPTION_RT$2 && rt !== TARGETS_RT) return "当前不是献州询问"; if (rt === OPTION_RT$2) return null; const raw = params.targets; if (!Array.isArray(raw)) return "需要 targets 数组"; const targets = raw; const X = st.localVars[COUNT_KEY]; if (typeof X !== "number") return "询问状态异常"; if (targets.length > X) return `至多选 ${X} 名目标`; if (new Set(targets).size !== targets.length) return "不能选重复目标"; const targetPlayer = seatId; for (const t of targets) { if (typeof t !== "number") return "目标必须是数字"; if (t === targetPlayer) return "不能选自己"; if (!st.players[t]?.alive) return "目标已死亡"; if (!inAttackRange(st, targetPlayer, t)) return "目标不在你的攻击范围内"; } return null; }, async (st, params) => { const rt = (st.pendingSlots.get(seatId)?.atom)?.["requestType"]; if (rt === OPTION_RT$2) st.localVars[OPTION_KEY$2] = params.choice === true || params.confirmed === true; else if (rt === TARGETS_RT) st.localVars[TARGETS_KEY$3] = params.targets; }); unloaders.push(u); } return () => { unloaders.forEach((u) => u()); }; } function onMount$120(skill, api) { api.defineAction("use", { label: DISPLAY_NAME$36, style: "danger", prompt: { type: "selectTarget", title: "献州:将装备区所有牌交给一名其他角色,其选择回血或造伤", description: "限定技(整局一次);X = 你交出的装备数", targetFilter: { min: 1, max: 1, filter: (_view, t) => t !== skill.ownerId } }, activeWhen: (ctx) => { if (!defaultPlayActive(ctx)) return false; const me = ctx.view.players[ctx.perspectiveIdx]; if (!me) return false; return Object.values(me.equipment).some((id) => !!id); } }); api.defineAction("respond", { label: DISPLAY_NAME$36, style: "danger", prompt: { type: "confirm", title: "献州", confirmLabel: "令其回血", cancelLabel: "造伤" } }); return () => {}; } var SKILL_ID$25, DISPLAY_NAME$36, USED_KEY$7, OPTION_RT$2, TARGETS_RT, OPTION_KEY$2, TARGETS_KEY$3, TARGET_KEY$20, COUNT_KEY, EQUIP_SLOTS$3, _skillModule$59; var init_界献州 = __esmMin((() => { init_apply(); init_frame(); init_damage(); init_skill(); init_registry$1(); init_distance(); init_viewDistance(); init_action_active(); SKILL_ID$25 = "界献州"; DISPLAY_NAME$36 = "献州"; USED_KEY$7 = `${SKILL_ID$25}/used`; OPTION_RT$2 = `${SKILL_ID$25}/option`; TARGETS_RT = `${SKILL_ID$25}/targets`; OPTION_KEY$2 = `${SKILL_ID$25}/optionChoice`; TARGETS_KEY$3 = `${SKILL_ID$25}/damageTargets`; TARGET_KEY$20 = `${SKILL_ID$25}/target`; COUNT_KEY = `${SKILL_ID$25}/X`; EQUIP_SLOTS$3 = [ "武器", "防具", "进攻马", "防御马", "宝物" ]; _skillModule$59 = { createSkill: createSkill$130, onInit: onInit$130, onMount: onMount$120 }; })); //#endregion //#region src/engine/skills/界甘露.ts var 界甘露_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$129, default: () => _skillModule$58, onInit: () => onInit$129, onMount: () => onMount$119 }); /** 玩家装备区牌数(所有 slot 之和)。 */ function equipmentCount(state, idx) { const eq = state.players[idx]?.equipment ?? {}; return EQUIP_SLOTS$2.filter((s) => typeof eq[s] === "string").length; } /** 已损失体力值。 */ function lostHealth$1(state, idx) { const p = state.players[idx]; if (!p) return 0; return Math.max(0, p.maxHealth - getHealthValue(p)); } /** 列出玩家在某 slot 的装备 cardId(若有)。 */ function equippedAt(state, idx, slot) { return state.players[idx]?.equipment[slot]; } /** 卸下指定 slot 装备及其自带技能(若有)。 * 仅移除技能实例(不卸下)。返回后牌仍在 idx 的装备区。 */ async function unloadSkillAt(state, idx, slot) { const cardId = equippedAt(state, idx, slot); if (!cardId) return; const card = state.cardMap[cardId]; if (card?.name && hasSkillModule(card.name) && state.players[idx].skills.includes(card.name)) await applyAtom(state, { type: "移除技能", player: idx, skillId: card.name }); } /** 把指定 cardId 装备到 idx 的对应 slot,并挂载自带技能(若有)。 * cardId 必须已在 idx 的手牌中(由先前 卸下 + 移动牌 完成)。 */ async function equipWithSkill(state, idx, cardId) { await applyAtom(state, { type: "装备", player: idx, cardId }); const card = state.cardMap[cardId]; if (card?.name && hasSkillModule(card.name)) await applyAtom(state, { type: "添加技能", player: idx, skillId: card.name }); } /** 交换 A、B 的装备区:对每个 slot,先卸下双方该 slot,再把对方的装到自己。 * 对每个 slot 独立处理(无跨 slot 依赖)。 */ async function swapEquipment(state, A, B) { for (const slot of EQUIP_SLOTS$2) { const aCard = equippedAt(state, A, slot); const bCard = equippedAt(state, B, slot); if (!aCard && !bCard) continue; if (aCard) await unloadSkillAt(state, A, slot); if (bCard) await unloadSkillAt(state, B, slot); if (aCard) await applyAtom(state, { type: "卸下", player: A, slot }); if (bCard) await applyAtom(state, { type: "卸下", player: B, slot }); if (aCard) { await applyAtom(state, { type: "移动牌", cardId: aCard, from: { zone: "手牌", player: A }, to: { zone: "手牌", player: B } }); await equipWithSkill(state, B, aCard); } if (bCard) { await applyAtom(state, { type: "移动牌", cardId: bCard, from: { zone: "手牌", player: B }, to: { zone: "手牌", player: A } }); await equipWithSkill(state, A, bCard); } } } function createSkill$129(id, ownerId) { return { id, ownerId, name: DISPLAY_NAME$35, description: "出牌阶段限一次:令两名角色交换装备区里的牌;若两人装备数差 X 大于你已损失的体力值,你须弃 X 张牌" }; } function onInit$129(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "use", (st, _params) => { if (st.currentPlayerIndex !== ownerId) return "不是你的回合"; if (st.phase !== "出牌") return "只能在出牌阶段发动"; if (hasBlockingPending(st)) return "当前有未完成的询问"; if (usedThisTurn$3(st, ownerId, SKILL_NAME$4)) return "本回合已使用过甘露"; if (!st.players[ownerId]?.alive) return "角色不可用"; const aliveIdx = st.players.filter((p) => p.alive); if (aliveIdx.length < 2) return "需要至少两名存活角色"; if (aliveIdx.filter((p) => p.index !== ownerId).length === 0) return "需要至少两名存活角色"; return null; }, async (st, _params) => { await markOncePerTurn(st, ownerId, SKILL_NAME$4); await pushFrame(st, SKILL_NAME$4, ownerId, {}); delete st.localVars[TARGET_KEY$19]; await applyAtom(st, { type: "请求回应", requestType: TARGET_RT$14, target: ownerId, prompt: { type: "choosePlayer", title: "甘露:选择两名角色交换装备区里的牌", min: 2, max: 2, filter: (_view, t) => st.players[t]?.alive === true }, timeout: 30 }); const targets = st.localVars[TARGET_KEY$19]; delete st.localVars[TARGET_KEY$19]; if (!Array.isArray(targets) || targets.length !== 2) { await popFrame(st); return; } const [A, B] = targets; if (!st.players[A]?.alive || !st.players[B]?.alive || A === B) { await popFrame(st); return; } const equipA = equipmentCount(st, A); const equipB = equipmentCount(st, B); const X = Math.abs(equipA - equipB); const lostHp = lostHealth$1(st, ownerId); if (X > lostHp) { if (st.players[ownerId].hand.length < X) { await popFrame(st); return; } delete st.localVars[DISCARD_KEY$9]; await applyAtom(st, { type: "请求回应", requestType: DISCARD_RT$10, target: ownerId, prompt: { type: "useCard", title: `甘露:两人装备数差 ${X} 大于你已损失的体力值 ${lostHp},须弃 ${X} 张手牌`, cardFilter: { filter: () => true, min: X, max: X } }, mandatory: true, timeout: 30 }); const discardCards = st.localVars[DISCARD_KEY$9]; delete st.localVars[DISCARD_KEY$9]; await applyAtom(st, { type: "弃置", player: ownerId, cardIds: Array.isArray(discardCards) && discardCards.length === X ? discardCards : st.players[ownerId]?.hand.slice(0, X) ?? [] }); } await swapEquipment(st, A, B); await popFrame(st); }); registerAction(state, skill.id, ownerId, "respond", (st, params) => { const slot = st.pendingSlots.get(ownerId); if (!slot) return "当前不需要回应"; const atom = slot.atom; if (atom["type"] !== "请求回应") return "当前不需要回应"; const rt = atom["requestType"]; if (rt !== TARGET_RT$14 && rt !== DISCARD_RT$10) return "当前不是甘露询问"; if (rt === TARGET_RT$14) { const targets = params.targets; if (!Array.isArray(targets)) return "targets required"; if (targets.length !== 2) return "需要选择两名角色"; for (const t of targets) { if (typeof t !== "number") return "目标必须是数字座次"; if (!st.players[t]?.alive) return "目标已死亡"; } if (targets[0] === targets[1]) return "不能选同一名角色"; const xDiff = Math.abs(equipmentCount(st, targets[0]) - equipmentCount(st, targets[1])); if (xDiff > lostHealth$1(st, ownerId) && (st.players[ownerId]?.hand.length ?? 0) < xDiff) return "手牌不足,无法支付弃牌代价"; } else { const cardIds = params.cardIds; if (!Array.isArray(cardIds)) return "cardIds required"; const self = st.players[ownerId]; const min = slot.atom.prompt?.cardFilter?.min ?? 0; if (cardIds.length !== min) return `需要弃 ${min} 张牌`; if (new Set(cardIds).size !== cardIds.length) return "不能选择重复的牌"; for (const cid of cardIds) if (typeof cid !== "string" || !self.hand.includes(cid)) return "牌不在手牌中"; } return null; }, async (st, params) => { const rt = (st.pendingSlots.get(ownerId)?.atom)?.requestType; if (rt === TARGET_RT$14) st.localVars[TARGET_KEY$19] = params.targets; else if (rt === DISCARD_RT$10) st.localVars[DISCARD_KEY$9] = params.cardIds; }); return () => {}; } function onMount$119(_skill, api) { api.defineAction("use", { label: DISPLAY_NAME$35, style: "primary", prompt: { type: "confirm", title: "甘露", description: "令两名角色交换装备区里的牌(若装备数差>已损失体力值,须弃等量牌)", confirmLabel: "发动" }, activeWhen: (ctx) => activeUnlessUsedThisTurn(SKILL_NAME$4)(ctx) }); return () => {}; } var SKILL_NAME$4, DISPLAY_NAME$35, TARGET_RT$14, DISCARD_RT$10, TARGET_KEY$19, DISCARD_KEY$9, EQUIP_SLOTS$2, _skillModule$58; var init_界甘露 = __esmMin((() => { init_types(); init_apply(); init_frame(); init_once_per_turn(); init_skill(); init_registry$1(); SKILL_NAME$4 = "界甘露"; DISPLAY_NAME$35 = "甘露"; TARGET_RT$14 = "界甘露/target"; DISCARD_RT$10 = "界甘露/discard"; TARGET_KEY$19 = "界甘露/targets"; DISCARD_KEY$9 = "界甘露/discardCards"; EQUIP_SLOTS$2 = [ "武器", "防具", "进攻马", "防御马", "宝物" ]; _skillModule$58 = { createSkill: createSkill$129, onInit: onInit$129, onMount: onMount$119 }; })); //#endregion //#region src/engine/skills/界疠火.ts var 界疠火_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$128, default: () => 界疠火_default, onInit: () => onInit$128, onMount: () => onMount$118 }); function createSkill$128(id, ownerId) { return { id, ownerId, name: DISPLAY_NAME$34, description: "你使用的非火【杀】可以改为火【杀】;此牌造成伤害后,你弃1牌或失去1体力;你使用的火【杀】可多指定一个目标" }; } /** 影子卡 id:${原id}#界疠火(后缀 = 技能 id,须与客户端构造约定一致;写技能名"疠火" * 会让客户端构造的影子 id 与引擎创建的不一致 → 界疠火整类转化在客户端恒被拒)。 */ function shadowIdOf$11(cardId) { return `${cardId}#界疠火`; } /** 标记某影子为疠火转化(在 localVars 里) */ function convertedKey(shadowId) { return `疠火/converted:${shadowId}`; } /** 标记某影子已造成伤害(在 localVars 里) */ function damagedKey(shadowId) { return `疠火/damaged:${shadowId}`; } function onInit$128(skill, state) { const ownerId = skill.ownerId; const owner = state.players[ownerId]; const FIRE_TAG = "杀/火杀多目标"; if (!owner.tags.includes(FIRE_TAG)) owner.tags.push(FIRE_TAG); const unloadTarget = registerSlashTargetProvider(state, ownerId, (st, _player, cardId) => { if (!cardId) return 0; const card = st.cardMap[cardId]; return card?.name === "杀" && card.damageType === "火焰" ? 2 : 0; }); registerAction(state, skill.id, ownerId, "transform", (state, params) => { const myTurn = state.currentPlayerIndex === ownerId; const inActPhase = state.phase === "出牌"; const free = !hasBlockingPending(state); const self = state.players[ownerId]; const selfAlive = self.alive === true; const cardId = params.cardId; const cardIdOk = typeof cardId === "string"; const card = cardIdOk ? state.cardMap[cardId] : void 0; const cardInHand = cardIdOk && self.hand.includes(cardId); const isNonFireSlash = !!card && card.name === "杀" && card.damageType !== "火焰"; return myTurn && inActPhase && free && selfAlive && cardInHand && isNonFireSlash ? null : "现在不能使用疠火"; }, async (state, params) => { const cardId = params.cardId; const shadowId = shadowIdOf$11(cardId); await applyAtom(state, { type: "当作", player: ownerId, cardIds: [cardId], shadowId, outputName: "杀", outputDamageType: "火焰" }); state.localVars[convertedKey(shadowId)] = true; }, (state, params) => { const cardId = params.cardId; const shadowId = shadowIdOf$11(cardId); delete state.cardMap[shadowId]; delete state.localVars[convertedKey(shadowId)]; delete state.localVars[damagedKey(shadowId)]; state.players[ownerId].hand = state.players[ownerId].hand.map((id) => id === shadowId ? cardId : id); }); registerAction(state, skill.id, ownerId, "respond", (s, params) => { const slot = s.pendingSlots.get(ownerId); if (slot?.atom.type !== "请求回应") return "当前不需要回应"; if (slot.atom.requestType !== COST_RT$1) return "当前不是疠火代价选择"; const cardId = params.cardId; if (typeof cardId !== "string") return "cardId required"; if (!s.players[ownerId].hand.includes(cardId)) return "牌不在手牌中"; return null; }, async (s, params) => { s.localVars[COST_CARD_KEY] = params.cardId; }); registerAfterHook(state, skill.id, ownerId, "造成伤害后", async (ctx) => { const atom = ctx.atom; if (atom.source !== ownerId) return; if ((atom.amount ?? 0) <= 0) return; const cardId = atom.cardId; if (typeof cardId !== "string") return; if (!ctx.state.localVars[convertedKey(cardId)]) return; ctx.state.localVars[damagedKey(cardId)] = true; }); registerAfterHook(state, skill.id, ownerId, "移动牌", async (ctx) => { const atom = ctx.atom; if (atom.from?.zone !== "处理区") return; if (atom.to?.zone !== "弃牌堆") return; const cardId = atom.cardId; if (typeof cardId !== "string") return; if (!ctx.state.localVars[convertedKey(cardId)]) return; const damaged = ctx.state.localVars[damagedKey(cardId)] === true; delete ctx.state.localVars[convertedKey(cardId)]; delete ctx.state.localVars[damagedKey(cardId)]; if (!damaged) return; const self = ctx.state.players[ownerId]; if (!self?.alive) return; if (self.hand.length > 0) { delete ctx.state.localVars[COST_CARD_KEY]; await applyAtom(ctx.state, { type: "请求回应", requestType: COST_RT$1, target: ownerId, prompt: { type: "useCard", title: "疠火:此杀造成伤害。弃置 1 张牌,或放弃(失去 1 体力)", cardFilter: { filter: () => true, min: 1, max: 1 } }, defaultChoice: false, timeout: 10 }); const costCard = ctx.state.localVars[COST_CARD_KEY]; delete ctx.state.localVars[COST_CARD_KEY]; if (typeof costCard === "string" && self.hand.includes(costCard)) await applyAtom(ctx.state, { type: "弃置", player: ownerId, cardIds: [costCard], voluntary: true }); else await applyAtom(ctx.state, { type: "失去体力", target: ownerId, amount: 1 }); } else await applyAtom(ctx.state, { type: "失去体力", target: ownerId, amount: 1 }); }); return () => { unloadTarget(); owner.tags = owner.tags.filter((t) => t !== "杀/火杀多目标"); }; } function onMount$118(skill, api) { api.defineAction("transform", { label: DISPLAY_NAME$34, style: "danger", prompt: { type: "useCardAndTarget", title: "疠火:将非火【杀】当火【杀】使用", cardFilter: { filter: (c) => c.name === "杀" && c.damageType !== "火焰", min: 1, max: 1 }, targetFilter: { min: 1, max: 2, filter: (view, t) => { return t !== view.currentPlayerIndex; } } }, transform: (card) => ({ name: "杀", sourceCardId: card.id, fromSkill: skill.id }), activeWhen: (ctx) => defaultPlayActive(ctx) && viewCanSlash(ctx.view, ctx.perspectiveIdx) }); api.defineAction("respond", { label: "弃1张牌", style: "default", prompt: { type: "useCard", title: "疠火:请弃置 1 张牌(或放弃失去 1 体力)", cardFilter: { filter: () => true, min: 1, max: 1 } } }); } var DISPLAY_NAME$34, COST_RT$1, COST_CARD_KEY, 界疠火_default; var init_界疠火 = __esmMin((() => { init_apply(); init_skill(); init_action_active(); init_slash_target(); DISPLAY_NAME$34 = "疠火"; COST_RT$1 = "界疠火/cost"; COST_CARD_KEY = "疠火/costCard"; 界疠火_default = { createSkill: createSkill$128, onInit: onInit$128, onMount: onMount$118 }; })); //#endregion //#region src/engine/skills/界直言.ts var 界直言_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$127, default: () => _skillModule$57, onInit: () => onInit$127, onMount: () => onMount$117 }); /** 从 fromIndex 之后找下一个存活玩家索引;全死亡时返回 fromIndex */ function findNextAlive$1(state, fromIndex) { const n = state.players.length; for (let i = 1; i <= n; i++) { const idx = (fromIndex + i) % n; if (state.players[idx]?.alive) return idx; } return fromIndex; } /** candidate 是否为 target 的上家(下一个存活玩家 === target) */ function isUpstreamOf$1(state, candidate, target) { if (candidate === target) return false; return findNextAlive$1(state, candidate) === target; } /** 装备牌 subtype → 装备栏位(与 装备 atom 的 inferSlot 一致) */ function slotOf$4(card) { switch (card?.subtype) { case "武器": return "武器"; case "防具": return "防具"; case "进攻马": return "进攻马"; case "防御马": return "防御马"; case "宝物": return "宝物"; default: return null; } } function createSkill$127(id, ownerId) { return { id, ownerId, name: DISPLAY_NAME$33, description: "你上家或你的结束阶段,你可令一名角色摸一张牌并展示之;装备牌:其使用之并回复1点体力;非装备且其体力不等于你:其失去1点体力" }; } function onInit$127(skill, state) { const ownerId = skill.ownerId; registerAfterHook(state, skill.id, ownerId, "阶段开始", async (ctx) => { const atom = ctx.atom; if (atom.type !== "阶段开始") return; if (atom.phase !== "回合结束") return; const st = ctx.state; const phasePlayer = atom.player ?? -1; if (phasePlayer < 0) return; const isSelf = phasePlayer === ownerId; const isUpstream = isUpstreamOf$1(st, phasePlayer, ownerId); if (!isSelf && !isUpstream) return; if (!st.players[ownerId]?.alive) return; delete st.localVars[CONFIRM_KEY$10]; await applyAtom(st, { type: "请求回应", requestType: CONFIRM_RT$23, target: ownerId, prompt: { type: "confirm", title: `是否发动直言?(${isSelf ? "你的" : "上家的"}结束阶段)`, description: "令一名角色摸一张牌并展示之:装备牌→其使用之并回复1点体力;非装备牌且其体力不等于你→其失去1点体力", confirmLabel: "发动", cancelLabel: "不发动" }, defaultChoice: false, timeout: 15 }); if (st.localVars[CONFIRM_KEY$10] !== true) { delete st.localVars[CONFIRM_KEY$10]; return; } delete st.localVars[CONFIRM_KEY$10]; delete st.localVars[TARGET_KEY$18]; await applyAtom(st, { type: "请求回应", requestType: TARGET_RT$13, target: ownerId, prompt: { type: "choosePlayer", title: "直言:选择一名角色摸一张牌并展示之", min: 1, max: 1, filter: (_view, t) => st.players[t]?.alive === true }, timeout: 30 }); const target = st.localVars[TARGET_KEY$18]; delete st.localVars[TARGET_KEY$18]; if (typeof target !== "number" || !st.players[target]?.alive) return; const beforeHandLen = st.players[target].hand.length; await applyAtom(st, { type: "摸牌", player: target, count: 1 }); if (st.players[target].hand.length <= beforeHandLen) return; const drawnId = st.players[target].hand[st.players[target].hand.length - 1]; await applyAtom(st, { type: "展示", player: target, cardId: drawnId }); const drawnCard = st.cardMap[drawnId]; if (drawnCard?.type === "装备牌") { const slot = slotOf$4(drawnCard); if (slot) { const currentEquip = st.players[target].equipment[slot]; if (currentEquip) { const oldCard = st.cardMap[currentEquip]; if (oldCard?.name && hasSkillModule(oldCard.name)) await applyAtom(st, { type: "移除技能", player: target, skillId: oldCard.name }); await applyAtom(st, { type: "卸下", player: target, slot }); await applyAtom(st, { type: "移动牌", cardId: currentEquip, from: { zone: "手牌", player: target }, to: { zone: "弃牌堆" } }); } await applyAtom(st, { type: "装备", player: target, cardId: drawnId }); if (drawnCard?.name && hasSkillModule(drawnCard.name)) await applyAtom(st, { type: "添加技能", player: target, skillId: drawnCard.name }); } if (st.players[target].health < st.players[target].maxHealth) await applyAtom(st, { type: "回复体力", target, amount: 1 }); } else if (getHealthValue(st.players[target]) !== getHealthValue(st.players[ownerId]) && st.players[target]?.alive) await applyAtom(st, { type: "失去体力", target, amount: 1 }); }); registerAction(state, skill.id, ownerId, "respond", (st, params) => { const slot = st.pendingSlots.get(ownerId); if (!slot) return "当前不需要回应"; if (slot.atom.type !== "请求回应") return "当前不是直言窗口"; const atom = slot.atom; if (atom.requestType === CONFIRM_RT$23) return null; if (atom.requestType === TARGET_RT$13) { const target = params.target; if (typeof target !== "number") return "需要选择一名角色"; if (!st.players[target]?.alive) return "目标不合法"; return null; } return "当前不是直言窗口"; }, async (st, params) => { const rt = (st.pendingSlots.get(ownerId)?.atom)?.requestType; if (rt === CONFIRM_RT$23) st.localVars[CONFIRM_KEY$10] = params.choice === true || params.confirmed === true; else if (rt === TARGET_RT$13) { const t = params.target; if (typeof t === "number") st.localVars[TARGET_KEY$18] = t; } }); return () => {}; } function onMount$117(_skill, _api) { return () => {}; } var DISPLAY_NAME$33, CONFIRM_RT$23, TARGET_RT$13, CONFIRM_KEY$10, TARGET_KEY$18, _skillModule$57; var init_界直言 = __esmMin((() => { init_types(); init_apply(); init_skill(); init_registry$1(); DISPLAY_NAME$33 = "直言"; CONFIRM_RT$23 = "界直言/confirm"; TARGET_RT$13 = "界直言/target"; CONFIRM_KEY$10 = "界直言/confirmed"; TARGET_KEY$18 = "界直言/target"; _skillModule$57 = { createSkill: createSkill$127, onInit: onInit$127, onMount: onMount$117 }; })); //#endregion //#region src/engine/skills/界直谏.ts var 界直谏_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$126, default: () => _skillModule$56, onInit: () => onInit$126, onMount: () => onMount$116 }); /** 装备牌 subtype → 装备栏位(与 装备 atom 的 inferSlot 一致) */ function slotOf$3(card) { switch (card?.subtype) { case "武器": return "武器"; case "防具": return "防具"; case "进攻马": return "进攻马"; case "防御马": return "防御马"; case "宝物": return "宝物"; default: return null; } } function createSkill$126(id, ownerId) { return { id, ownerId, name: SKILL_NAME$3, description: "出牌阶段:将一张装备牌置入一名其他角色的装备区(可替换原装备),然后摸一张牌" }; } function onInit$126(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "use", (st, params) => { if (st.currentPlayerIndex !== ownerId) return "只能在你的回合使用"; if (st.phase !== "出牌") return "只能在出牌阶段使用"; if (hasBlockingPending(st)) return "当前有未完成的询问"; const self = st.players[ownerId]; if (!self?.alive) return "你已死亡"; if (typeof params.cardId !== "string") return "需要选择一张装备牌"; if (!self.hand.includes(params.cardId)) return "牌不在手牌中"; const card = st.cardMap[params.cardId]; if (card?.type !== "装备牌") return "只能使用装备牌"; if (!slotOf$3(card)) return "无效的装备牌"; const target = Array.isArray(params.targets) && typeof params.targets[0] === "number" ? params.targets[0] : typeof params.target === "number" ? params.target : void 0; if (target === void 0) return "需要指定一名目标"; if (target === ownerId) return "不能对自己使用界直谏"; if (!st.players[target]?.alive) return "目标不合法"; return null; }, async (st, params) => { const cardId = params.cardId; const target = Array.isArray(params.targets) && typeof params.targets[0] === "number" ? params.targets[0] : params.target; const card = st.cardMap[cardId]; const slot = slotOf$3(card); await pushFrame(st, SKILL_NAME$3, ownerId, { ...params }); await applyAtom(st, { type: "移动牌", cardId, from: { zone: "手牌", player: ownerId }, to: { zone: "手牌", player: target } }); const currentEquip = st.players[target].equipment[slot]; if (currentEquip) { const oldCard = st.cardMap[currentEquip]; if (oldCard?.name && hasSkillModule(oldCard.name)) await applyAtom(st, { type: "移除技能", player: target, skillId: oldCard.name }); await applyAtom(st, { type: "卸下", player: target, slot }); await applyAtom(st, { type: "移动牌", cardId: currentEquip, from: { zone: "手牌", player: target }, to: { zone: "弃牌堆" } }); } await applyAtom(st, { type: "装备", player: target, cardId }); if (card?.name && hasSkillModule(card.name)) await applyAtom(st, { type: "添加技能", player: target, skillId: card.name }); await applyAtom(st, { type: "摸牌", player: ownerId, count: 1 }); await popFrame(st); }); return () => {}; } function onMount$116(skill, api) { api.defineAction("use", { label: SKILL_NAME$3, style: "primary", prompt: { type: "useCardAndTarget", title: "界直谏:将一张装备牌置入一名其他角色的装备区(可替换原装备)", cardFilter: { filter: (c) => c.type === "装备牌", min: 1, max: 1 }, targetFilter: { min: 1, max: 1, filter: (view, target) => { if (target === view.viewer) return false; return view.players[target]?.alive === true; } } } }); return () => {}; } var SKILL_NAME$3, _skillModule$56; var init_界直谏 = __esmMin((() => { init_apply(); init_frame(); init_skill(); init_registry$1(); SKILL_NAME$3 = "界直谏"; _skillModule$56 = { createSkill: createSkill$126, onInit: onInit$126, onMount: onMount$116 }; })); //#endregion //#region src/engine/skills/界看破.ts var 界看破_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$125, default: () => 界看破_default, onInit: () => onInit$125, onMount: () => onMount$115 }); function createSkill$125(id, ownerId) { return { id, ownerId, name: "界看破", description: "你可以将一张黑色牌当【无懈可击】使用;你的【无懈可击】不能被响应" }; } /** 影子卡 id:${原id}#界看破 */ function shadowIdOf$10(cardId) { return `${cardId}#界看破`; } /** 当前是否存在无懈可击广播窗口(玩家可回应) */ function hasNullifyWindow$2(state, playerId) { for (const slot of state.pendingSlots.values()) { const atom = slot.atom; if (atom.type !== "请求回应") continue; if (atom.requestType !== "无懈可击") continue; if (typeof atom.target === "number" && (atom.target < 0 || atom.target === playerId)) return true; } return false; } function onInit$125(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "transform", (state, params) => { const self = state.players[ownerId]; if (!self?.alive) return "玩家不存在或已死亡"; const cardId = params.cardId; const cardIdOk = typeof cardId === "string"; const card = cardIdOk ? state.cardMap[cardId] : void 0; const cardInHand = cardIdOk && self.hand.includes(cardId); const cardInEquip = cardIdOk && Object.values(self.equipment).some((id) => id === cardId); const isBlack = !!card && card.color === "黑"; const hasWindow = hasNullifyWindow$2(state, ownerId); const blockedByOther = (() => { for (const slot of state.pendingSlots.values()) { const atom = slot.atom; if (!slot.isBlocking) continue; if (atom.type === "请求回应" && atom.requestType === "无懈可击") continue; return true; } return false; })(); return (cardInHand || cardInEquip) && isBlack && hasWindow && !blockedByOther ? null : "现在不能使用界看破"; }, async (state, params) => { const cardId = params.cardId; const shadowId = shadowIdOf$10(cardId); const self = state.players[ownerId]; const equipSlotEntry = Object.entries(self.equipment).find(([, id]) => id === cardId); if (equipSlotEntry) { const slot = equipSlotEntry[0]; params["_origSlot"] = slot; await applyAtom(state, { type: "卸下", player: ownerId, slot }); } await applyAtom(state, { type: "当作", player: ownerId, cardIds: [cardId], shadowId, outputName: "无懈可击" }); }, (state, params) => { const cardId = params.cardId; const sId = shadowIdOf$10(cardId); const self = state.players[ownerId]; const origSlot = params["_origSlot"]; delete state.cardMap[sId]; const idx = self.hand.indexOf(sId); if (idx >= 0) if (origSlot) { self.hand.splice(idx, 1); self.equipment[origSlot] = cardId; } else self.hand[idx] = cardId; }); registerAction(state, "无懈可击", ownerId, "respond", (state, params) => { const slot = findPendingSlot(state, ownerId); if (!slot) return "当前不需要回应"; if (slot.atom.type !== "请求回应") return "当前不是无懈可击窗口"; if (slot.atom.requestType !== "无懈可击") return "当前不是无懈可击窗口"; const cardId = params.cardId; if (!cardId) return "cardId required"; const self = state.players[ownerId]; if (!self.alive) return "你已死亡"; if (!self.hand.includes(cardId)) return "牌不在手牌中"; if (state.cardMap[cardId].name !== "无懈可击") return "只能打出无懈可击"; return null; }, async (state, params) => { const cardId = params.cardId; state.localVars["抵消/已回应"] = true; await runUseFlow(state, ownerId, cardId, [ownerId], "无懈可击", { skipCancelQuery: true }); }); return () => {}; } function onMount$115(skill, api) { api.defineAction("transform", { label: "界看破", style: "passive", prompt: { type: "useCard", title: "选择一张黑色牌(手牌或装备)当无懈可击使用", cardFilter: { filter: (c) => c.color === "黑", min: 1, max: 1 } }, transform: (card) => ({ name: "无懈可击", sourceCardId: card.id, fromSkill: skill.id }), activeWhen: (ctx) => { const view = ctx.view; const pending = view.pending; if (!pending) return false; const atom = pending.atom; if (atom.type !== "请求回应" || atom.requestType !== "无懈可击") return false; const p = view.players[ctx.perspectiveIdx]; if (!p) return false; const hasBlackInHand = p.hand?.some((c) => c.color === "黑") ?? false; const hasBlackEquip = Object.values(p.equipment ?? {}).some((id) => { return (id ? ctx.view.cardMap[id] : void 0)?.color === "黑"; }); return hasBlackInHand || hasBlackEquip; } }); } var 界看破_default; var init_界看破 = __esmMin((() => { init_skill(); init_apply(); init_use_card(); 界看破_default = { createSkill: createSkill$125, onInit: onInit$125, onMount: onMount$115 }; })); //#endregion //#region src/engine/skills/界眩惑.ts var 界眩惑_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$124, default: () => _skillModule$55, onInit: () => onInit$124, onMount: () => onMount$114 }); function createSkill$124(id, ownerId) { return { id, ownerId, name: DISPLAY_NAME$32, description: "摸牌阶段结束时,交给一名其他角色两张牌,令其选:①对你指定的另一名角色出杀;②你观看其手牌并获得两张" }; } /** X 手牌中是否有【杀】 */ function hasKillInHand$1(state, player) { return (state.players[player]?.hand ?? []).some((id) => state.cardMap[id]?.name === "杀"); } /** X 是否能选 选项 1(出杀):X 手牌有杀 且 存在另一名存活角色 Y(≠X) */ function canOptionKill(state, x) { if (!hasKillInHand$1(state, x)) return false; return state.players.some((p) => p.alive && p.index !== x); } /** X 是否能选 选项 2(被获得):X 手牌至少 2 张 */ function canOptionGain(state, x) { return (state.players[x]?.hand.length ?? 0) >= 2; } /** * 执行一次【杀】的完整结算(镜像 界诛害.runSlashResolution / 借刀杀人 / 乱武 强制出杀): * 走 runUseFlow('杀'),覆盖全部时机(选择目标时→使用时→指定目标→成为目标→指定目标后→ * 成为目标后→检测有效性→生效前[询问闪]→生效时→生效后[伤害]→使用结算结束时→使用结算结束后), * 保证 无双/贞烈/铁骑/肉林/贯石斧/谦逊 等横切技能正确交互。此前手写实现(移动牌+指定目标+ * 检测有效性+询问闪+runDamageFlow)遗漏了 指定目标后/成为目标后/生效前/生效时/生效后 等时机。 * 真实杀牌(从 X 手牌):不计出杀次数(runUseFlow 不累加 quota,仅 杀.use 主动出杀时累加); * 无距离限制(距离由 杀.use validate 校验,眩惑绕过主动出杀入口)。 */ async function runSlashResolution$1(state, source, target, cardId) { if (!state.players[target]?.alive) return; await runUseFlow(state, source, cardId, [target], "杀"); } /** 选项 1 流程:法正指定 Y → X 出杀对 Y */ async function runOptionKill(state, ownerId, X) { delete state.localVars[VICTIM_KEY]; await applyAtom(state, { type: "请求回应", requestType: RT_VICTIM, target: ownerId, prompt: { type: "choosePlayer", title: "眩惑:指定 X 出杀的目标 Y(可为法正自己)", min: 1, max: 1, filter: (view, target) => target !== X && view.players[target]?.alive === true }, timeout: 15 }); const Y = state.localVars[VICTIM_KEY]; if (typeof Y !== "number" || Y < 0 || !state.players[Y]?.alive) return; delete state.localVars[SLASH_KEY]; await applyAtom(state, { type: "请求回应", requestType: RT_SLASH, target: X, prompt: { type: "useCard", title: "眩惑:选择一张【杀】使用", cardFilter: { filter: (c) => c.name === "杀", min: 1, max: 1 } }, timeout: 15 }); const slashId = state.localVars[SLASH_KEY]; if (typeof slashId !== "string" || !state.players[X]?.hand.includes(slashId)) return; await runSlashResolution$1(state, X, Y, slashId); } /** 选项 2 流程:法正观看 X 手牌 → 选 2 张获得 */ async function runOptionGain(state, ownerId, X) { const xp = state.players[X]; if (!xp || xp.hand.length < 2) return; for (let i = 0; i < 2; i++) { if (xp.hand.length === 0) break; const rtKey = `${PICK_KEY$3}${i}`; delete state.localVars[rtKey]; await applyAtom(state, { type: "请求回应", requestType: i === 0 ? RT_PICK0 : RT_PICK1, target: ownerId, prompt: { type: "pickTargetCard", title: `眩惑:观看 X 手牌,选第 ${i + 1} 张获得`, target: X, equipment: xp.hand.map((id, idx) => ({ slot: `hand-${idx}`, cardId: id, cardName: state.cardMap[id]?.name ?? "?" })), judge: [], handCount: 0 }, timeout: 20 }); const picked = state.localVars[rtKey]; if (typeof picked === "string" && xp.hand.includes(picked)) await applyAtom(state, { type: "获得", player: ownerId, cardId: picked, from: X }); } } function onInit$124(skill, state) { const ownerId = skill.ownerId; for (const p of state.players) { const seatId = p.index; const isOwner = seatId === ownerId; registerAction(state, skill.id, seatId, "respond", (s) => { const slot = s.pendingSlots.get(seatId); if (slot?.atom.type !== "请求回应") return "当前不需要回应"; const rt = slot.atom.requestType ?? ""; const ownerRTs = [ RT_TRIGGER, RT_TARGET, RT_CARDS, RT_VICTIM, RT_PICK0, RT_PICK1 ]; const xRTs = [RT_XCHOICE, RT_SLASH]; if (isOwner && ownerRTs.includes(rt)) return null; if (xRTs.includes(rt)) return null; return "当前不是眩惑询问"; }, async (s, params) => { const rt = (s.pendingSlots.get(seatId)?.atom)?.requestType; if (rt === RT_TRIGGER) s.localVars[TRIGGER_KEY] = params.choice === true; else if (rt === RT_TARGET) { const t = typeof params.target === "number" ? params.target : void 0; const arr = Array.isArray(params.targets) ? params.targets : void 0; s.localVars[TARGET_KEY$17] = t ?? arr?.[0] ?? -1; } else if (rt === RT_CARDS) { const ids = params.cardIds; s.localVars[CARDS_KEY] = Array.isArray(ids) ? ids : []; } else if (rt === RT_VICTIM) { const t = typeof params.target === "number" ? params.target : void 0; const arr = Array.isArray(params.targets) ? params.targets : void 0; s.localVars[VICTIM_KEY] = t ?? arr?.[0] ?? -1; } else if (rt === RT_PICK0 || rt === RT_PICK1) { const cardId = typeof params.cardId === "string" ? params.cardId : ""; s.localVars[`${PICK_KEY$3}${rt === RT_PICK0 ? 0 : 1}`] = cardId; } else if (rt === RT_XCHOICE) s.localVars[XCHOICE_KEY] = params.choice === true ? "kill" : "gain"; else if (rt === RT_SLASH) { const cardId = typeof params.cardId === "string" ? params.cardId : ""; s.localVars[SLASH_KEY] = cardId; } }); } registerBeforeHook(state, skill.id, ownerId, "阶段结束", async (ctx) => { const atom = ctx.atom; if (atom.phase !== "摸牌") return; if (atom.player !== ownerId) return; if (ctx.state.currentPlayerIndex !== ownerId) return; const self = ctx.state.players[ownerId]; if (!self?.alive) return; if (self.hand.length < 2) return; if (!ctx.state.players.some((p) => p.alive && p.index !== ownerId)) return; delete ctx.state.localVars[TRIGGER_KEY]; await applyAtom(ctx.state, { type: "请求回应", requestType: RT_TRIGGER, target: ownerId, prompt: { type: "confirm", title: "是否发动眩惑?(交给一名其他角色两张牌)", confirmLabel: "发动", cancelLabel: "不发动" }, defaultChoice: false, timeout: 10 }); if (ctx.state.localVars[TRIGGER_KEY] !== true) return; delete ctx.state.localVars[TARGET_KEY$17]; await applyAtom(ctx.state, { type: "请求回应", requestType: RT_TARGET, target: ownerId, prompt: { type: "choosePlayer", title: "眩惑:选择接收两张牌的角色", min: 1, max: 1, filter: (view, target) => target !== ownerId && view.players[target]?.alive === true }, timeout: 15 }); const X = ctx.state.localVars[TARGET_KEY$17]; if (typeof X !== "number" || X < 0 || !ctx.state.players[X]?.alive) return; delete ctx.state.localVars[CARDS_KEY]; await applyAtom(ctx.state, { type: "请求回应", requestType: RT_CARDS, target: ownerId, prompt: { type: "distribute", mode: "select", title: "眩惑:选择交给对方的 2 张手牌", source: "hand", minTotal: 2, maxTotal: 2 }, timeout: 20 }); const cards = ctx.state.localVars[CARDS_KEY]; if (!Array.isArray(cards) || cards.length !== 2) return; const validCards = cards.filter((id) => self.hand.includes(id)); if (validCards.length !== 2) return; if (new Set(validCards).size !== 2) return; await pushFrame(ctx.state, SKILL_ID$24, ownerId, { target: X, cards: validCards }); try { for (const cardId of validCards) await applyAtom(ctx.state, { type: "移动牌", cardId, from: { zone: "手牌", player: ownerId }, to: { zone: "手牌", player: X } }); const optKill = canOptionKill(ctx.state, X); const optGain = canOptionGain(ctx.state, X); if (!optKill && !optGain) return; let choice; if (optKill && optGain) { delete ctx.state.localVars[XCHOICE_KEY]; await applyAtom(ctx.state, { type: "请求回应", requestType: RT_XCHOICE, target: X, prompt: { type: "confirm", title: "眩惑:选择一项(确认=对法正指定角色出杀,取消=法正观看并取得你两张手牌)", confirmLabel: "对法正指定角色出杀", cancelLabel: "法正取得两张手牌" }, defaultChoice: false, timeout: 15 }); choice = ctx.state.localVars[XCHOICE_KEY] === "kill" ? "kill" : "gain"; } else choice = optKill ? "kill" : "gain"; if (choice === "kill") await runOptionKill(ctx.state, ownerId, X); else await runOptionGain(ctx.state, ownerId, X); } finally { await popFrame(ctx.state); } }); return () => {}; } function onMount$114(_skill, api) { api.defineAction("respond", { label: DISPLAY_NAME$32, style: "default", prompt: { type: "confirm", title: "是否发动眩惑?", confirmLabel: "发动", cancelLabel: "不发动" } }); } var SKILL_ID$24, DISPLAY_NAME$32, TRIGGER_KEY, TARGET_KEY$17, CARDS_KEY, VICTIM_KEY, PICK_KEY$3, XCHOICE_KEY, SLASH_KEY, RT_TRIGGER, RT_TARGET, RT_CARDS, RT_VICTIM, RT_PICK0, RT_PICK1, RT_XCHOICE, RT_SLASH, _skillModule$55; var init_界眩惑 = __esmMin((() => { init_apply(); init_frame(); init_use_card(); init_skill(); SKILL_ID$24 = "界眩惑"; DISPLAY_NAME$32 = "眩惑"; TRIGGER_KEY = `${SKILL_ID$24}/triggered`; TARGET_KEY$17 = `${SKILL_ID$24}/target`; CARDS_KEY = `${SKILL_ID$24}/cards`; VICTIM_KEY = `${SKILL_ID$24}/victim`; PICK_KEY$3 = `${SKILL_ID$24}/pickHand`; XCHOICE_KEY = `${SKILL_ID$24}/xchoice`; SLASH_KEY = `${SKILL_ID$24}/slashCardId`; RT_TRIGGER = `${SKILL_ID$24}/trigger`; RT_TARGET = `${SKILL_ID$24}/pickTarget`; RT_CARDS = `${SKILL_ID$24}/pickCards`; RT_VICTIM = `${SKILL_ID$24}/pickVictim`; RT_PICK0 = `${SKILL_ID$24}/pickHand0`; RT_PICK1 = `${SKILL_ID$24}/pickHand1`; RT_XCHOICE = `${SKILL_ID$24}/xchoice`; RT_SLASH = `${SKILL_ID$24}/pickSlash`; _skillModule$55 = { createSkill: createSkill$124, onInit: onInit$124, onMount: onMount$114 }; })); //#endregion //#region src/engine/skills/界矢北.ts var 界矢北_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$123, default: () => _skillModule$54, onInit: () => onInit$123, onMount: () => onMount$113 }); function createSkill$123(id, ownerId) { return { id, ownerId, name: DISPLAY_NAME$31, description: "锁定技:游戏开始时获得3点护甲;每回合首次受到伤害后回1血,本回合再受到伤害后失1血", isLocked: true }; } /** 数当前玩家的护甲点数 */ function armorCount(state, player) { return state.players[player].marks.filter((m) => m.id.startsWith(ARMOR_PREFIX)).length; } /** 加 1 点护甲(经 加标记 atom,view 自动同步) */ async function addArmor(state, player) { await applyAtom(state, { type: "加标记", player, mark: { id: `${ARMOR_PREFIX}${state.seq}`, scope: player } }); } /** 移除 N 点护甲(按 mark 加入顺序依次移除) */ async function removeArmor(state, player, count) { const marks = state.players[player].marks; const toRemove = []; for (const m of marks) { if (toRemove.length >= count) break; if (m.id.startsWith(ARMOR_PREFIX)) toRemove.push(m.id); } for (const markId of toRemove) await applyAtom(state, { type: "去标记", player, markId }); } function onInit$123(skill, state) { const ownerId = skill.ownerId; registerAfterHook(state, skill.id, ownerId, "回合开始", async (ctx) => { const st = ctx.state; if (!st.players[ownerId]?.alive) return; if (st.localVars[INIT_KEY$1(ownerId)]) return; st.localVars[INIT_KEY$1(ownerId)] = true; await addArmor(st, ownerId); await addArmor(st, ownerId); await addArmor(st, ownerId); }); registerBeforeHook(state, skill.id, ownerId, "受到伤害时", async (ctx) => { const atom = ctx.atom; if (atom.target !== ownerId) return; const amount = atom.amount ?? 0; if (amount <= 0) return; const armor = armorCount(ctx.state, ownerId); if (armor <= 0) return; const absorbed = Math.min(armor, amount); const newAmount = amount - absorbed; await removeArmor(ctx.state, ownerId, absorbed); return { kind: "modify", atom: { ...ctx.atom, amount: newAmount } }; }); registerAfterHook(state, skill.id, ownerId, "受到伤害后", async (ctx) => { const atom = ctx.atom; if (atom.target !== ownerId) return; if ((atom.amount ?? 0) <= 0) return; const self = ctx.state.players[ownerId]; if (!self?.alive) return; const count = self.vars[DAMAGE_COUNT_KEY] ?? 0; if (count === 0) await applyAtom(ctx.state, { type: "回复体力", target: ownerId, amount: 1 }); else await applyAtom(ctx.state, { type: "失去体力", target: ownerId, amount: 1 }); self.vars[DAMAGE_COUNT_KEY] = count + 1; }); return () => {}; } function onMount$113(_skill, _api) {} var SKILL_ID$23, DISPLAY_NAME$31, ARMOR_PREFIX, INIT_KEY$1, DAMAGE_COUNT_KEY, _skillModule$54; var init_界矢北 = __esmMin((() => { init_apply(); init_skill(); SKILL_ID$23 = "界矢北"; DISPLAY_NAME$31 = "矢北"; ARMOR_PREFIX = `${SKILL_ID$23}/护甲:`; INIT_KEY$1 = (ownerId) => `${SKILL_ID$23}/init/${ownerId}`; DAMAGE_COUNT_KEY = `${SKILL_ID$23}/damageCount/usedThisTurn`; _skillModule$54 = { createSkill: createSkill$123, onInit: onInit$123, onMount: onMount$113 }; })); //#endregion //#region src/engine/skills/界矫诏.ts var 界矫诏_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$122, default: () => _skillModule$53, onInit: () => onInit$122, onMount: () => onMount$112 }); function createSkill$122(id, ownerId) { return { id, ownerId, name: DISPLAY_NAME$30, description: "出牌阶段限一次,你可将一张牌当本轮未有角色使用过的基本或普通锦囊牌使用" }; } /** 影子卡 id: ${原id}#界矫诏 */ function shadowIdOf$9(cardId) { return `${cardId}#${SKILL_ID$22}`; } /** 本轮已用牌名集合(读取时自动按 round 重置)。 */ function readUsedNames(state) { const currentRound = state.turn.round; if (state.localVars[LAST_ROUND_KEY$1] !== currentRound) { state.localVars[LAST_ROUND_KEY$1] = currentRound; state.localVars[USED_NAMES_KEY] = []; return []; } const v = state.localVars[USED_NAMES_KEY]; return Array.isArray(v) ? v : []; } /** 标记一个牌名为本轮已用(Set 语义,防重复)。 */ function markUsedName(state, name) { if (!ALLOWED_NAMES.has(name)) return; const list = readUsedNames(state); if (!list.includes(name)) state.localVars[USED_NAMES_KEY] = [...list, name]; } /** 本回合是否已用过矫诏。 */ function usedThisTurn$1(state, ownerId) { return !!state.players[ownerId]?.vars[USED_KEY$6]; } function onInit$122(skill, state) { const ownerId = skill.ownerId; const registerTransform = (outputName) => registerAction(state, skill.id, ownerId, `${TRANSFORM_ACTION_PREFIX}${outputName}`, (st, params) => { if (st.currentPlayerIndex !== ownerId) return "不是你的回合"; if (st.phase !== "出牌") return "只能在出牌阶段发动"; if (hasBlockingPending(st)) return "当前有未完成的询问"; if (usedThisTurn$1(st, ownerId)) return "本回合已使用过矫诏"; const self = st.players[ownerId]; if (!self?.alive) return "玩家不存在或已死亡"; const cardId = params.cardId; if (typeof cardId !== "string") return "需要选择一张牌"; if (!self.hand.includes(cardId)) return "牌不在手牌中"; if (readUsedNames(st).includes(outputName)) return `本轮已有角色使用过【${outputName}】`; return null; }, async (st, params) => { const cardId = params.cardId; const shadowId = shadowIdOf$9(cardId); st.players[ownerId].vars[USED_KEY$6] = true; await applyAtom(st, { type: "回合用量", player: ownerId, key: USED_KEY$6, value: true }); await applyAtom(st, { type: "当作", player: ownerId, cardIds: [cardId], shadowId, outputName }); }, (st, params) => { const cardId = params.cardId; const sId = shadowIdOf$9(cardId); delete st.cardMap[sId]; const self = st.players[ownerId]; const idx = self.hand.indexOf(sId); if (idx >= 0) self.hand[idx] = cardId; delete self.vars[USED_KEY$6]; }); const unregTransforms = [...ALLOWED_NAMES].map(registerTransform); registerAfterHook(state, skill.id, ownerId, "结算帧入栈", async (ctx) => { const name = ctx.atom.skillId; if (typeof name !== "string") return; if (!ALLOWED_NAMES.has(name)) return; markUsedName(ctx.state, name); }); return () => { for (const unreg of unregTransforms) unreg(); }; } /** 声明的牌名此刻能否被主动使用(与 使用牌 的 onMount 同源判据): * timing='生效前' 的纯回应牌(闪/无懈可击)无主动 use 入口;牌自身 activeWhen 不满足 * (如 桃 需自己已受伤)时同样不可用 —— 这类声明点了会走到死路(主 action 不存在/被拒)。 */ function declaredUsable(ctx, name) { const effect = getCardEffect(name); if (!effect) return true; if (effect.timing === "生效前") return false; return !effect.activeWhen || effect.activeWhen(ctx); } function onMount$112(_skill, api) { const activeWhen = (ctx) => { if (!defaultPlayActive(ctx)) return false; const p = ctx.view.players[ctx.perspectiveIdx]; if (!p) return false; if (p.turnUsage?.[USED_KEY$6]) return false; return (p.handCount ?? 0) > 0; }; const nameActiveWhen = (name) => (ctx) => activeWhen(ctx) && declaredUsable(ctx, name); for (const outputName of ALLOWED_NAMES) api.defineAction(`${TRANSFORM_ACTION_PREFIX}${outputName}`, { label: `${DISPLAY_NAME$30}·${outputName}`, style: "primary", prompt: { type: "useCard", title: `矫诏:将一张牌当【${outputName}】使用`, description: "出牌阶段限一次;转化后按声明的牌正常结算", cardFilter: { filter: () => true, min: 1, max: 1 } }, transform: (card) => ({ name: outputName, sourceCardId: card.id, fromSkill: SKILL_ID$22 }), activeWhen: nameActiveWhen(outputName) }); } var SKILL_ID$22, DISPLAY_NAME$30, USED_KEY$6, USED_NAMES_KEY, LAST_ROUND_KEY$1, ALLOWED_NAMES, TRANSFORM_ACTION_PREFIX, _skillModule$53; var init_界矫诏 = __esmMin((() => { init_apply(); init_skill(); init_action_active(); init_cards(); SKILL_ID$22 = "界矫诏"; DISPLAY_NAME$30 = "矫诏"; USED_KEY$6 = `${SKILL_ID$22}/usedThisTurn`; USED_NAMES_KEY = `${SKILL_ID$22}/已用牌名`; LAST_ROUND_KEY$1 = `${SKILL_ID$22}/lastRound`; ALLOWED_NAMES = new Set([ "杀", "桃", "酒", "决斗", "过河拆桥", "顺手牵羊", "无中生有", "借刀杀人", "桃园结义", "五谷丰登", "南蛮入侵", "万箭齐发", "火攻", "铁索连环" ]); TRANSFORM_ACTION_PREFIX = "transform:"; _skillModule$53 = { createSkill: createSkill$122, onInit: onInit$122, onMount: onMount$112 }; })); //#endregion //#region src/engine/skills/界破军.ts var 界破军_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$121, default: () => _skillModule$52, onInit: () => onInit$121, onMount: () => onMount$111 }); function createSkill$121(id, ownerId) { return { id, ownerId, name: DISPLAY_NAME$29, description: "当你使用【杀】指定目标后,你可以将其至多X张牌移出游戏直到回合结束(X为其体力值)。你使用的【杀】对手牌区与装备区内牌数皆不大于你的角色造成的伤害+1" }; } /** 玩家装备区内的牌数 */ function equipCount$1(p) { return Object.values(p.equipment).filter((id) => !!id).length; } /** 玩家"场上"的总牌数(手牌+装备) */ function totalCardCount$2(p) { return p.hand.length + equipCount$1(p); } /** 玩家所有可被破军的牌(手牌+装备),用于选牌面板展示 */ function targetCardIds(p) { return [...p.hand, ...Object.values(p.equipment).filter((id) => !!id)]; } /** 判断 cardId 是否对应"杀"牌(转化杀的影子卡 name 也是'杀') */ function isSlashCard(state, cardId) { if (!cardId) return false; const card = state.cardMap[cardId]; return !!card && card.name === "杀"; } function onInit$121(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "respond", (st, params) => { const slot = st.pendingSlots.get(ownerId); if (!slot) return "当前不需要回应"; const atom = slot.atom; if (atom["type"] !== "请求回应") return "当前不需要回应"; const rt = atom["requestType"]; if (rt !== RT_ASK_USE$1 && rt !== RT_ASK_CARDS$1) return "当前不是界破军回应"; if (rt === RT_ASK_USE$1) return null; const cardIds = params.cardIds; if (!Array.isArray(cardIds)) return "需要选择牌"; const promptTarget = st.localVars[CTX_TARGET$1]; const promptX = st.localVars[CTX_X$1]; if (typeof promptTarget !== "number") return "询问上下文异常"; const targetPlayer = st.players[promptTarget]; if (!targetPlayer) return "目标不存在"; const equipSet = new Set(Object.values(targetPlayer.equipment).filter((id) => !!id)); for (const id of cardIds) { if (typeof id !== "string") return "cardIds 必须为字符串数组"; if (!targetPlayer.hand.includes(id) && !equipSet.has(id)) return "牌不在目标区域"; } if (cardIds.length < 1) return "至少选择 1 张牌"; if (typeof promptX === "number" && cardIds.length > promptX) return `至多选择 ${promptX} 张牌`; if (new Set(cardIds).size !== cardIds.length) return "不能重复选牌"; return null; }, async (st, params) => { const rt = (st.pendingSlots.get(ownerId)?.atom)?.["requestType"]; if (rt === RT_ASK_USE$1) st.localVars[LV_USE$1] = params.choice === true; else if (rt === RT_ASK_CARDS$1) st.localVars[LV_CARDS$1] = params.cardIds; }); registerAfterHook(state, skill.id, ownerId, "指定目标", async (ctx) => { const atom = ctx.atom; if (atom.type !== "指定目标") return; if (atom.source !== ownerId) return; if (!isSlashCard(ctx.state, atom.cardId)) return; const target = atom.target; if (typeof target !== "number") return; const targetPlayer = ctx.state.players[target]; if (!targetPlayer?.alive) return; if (target === ownerId) return; const X = getHealthValue(targetPlayer); if (X <= 0) return; if (totalCardCount$2(targetPlayer) === 0) return; await pushFrame(ctx.state, SKILL_ID$21, ownerId, {}); try { delete ctx.state.localVars[LV_USE$1]; await applyAtom(ctx.state, { type: "请求回应", requestType: RT_ASK_USE$1, target: ownerId, prompt: { type: "confirm", title: `是否发动破军?(将 ${targetPlayer.name} 至多 ${X} 张牌移出游戏直到回合结束)`, confirmLabel: "发动", cancelLabel: "不发动" }, defaultChoice: false, timeout: 15 }); if (ctx.state.localVars[LV_USE$1] !== true) return; const targetNow = ctx.state.players[target]; if (!targetNow?.alive) return; const Xnow = Math.min(X, getHealthValue(targetNow)); if (Xnow <= 0) return; const available = targetCardIds(targetNow); if (available.length === 0) return; delete ctx.state.localVars[LV_CARDS$1]; ctx.state.localVars[CTX_TARGET$1] = target; ctx.state.localVars[CTX_X$1] = Xnow; await applyAtom(ctx.state, { type: "请求回应", requestType: RT_ASK_CARDS$1, target: ownerId, prompt: { type: "distribute", mode: "select", title: `破军:选择 ${targetNow.name} 的 1~${Xnow} 张牌移出游戏(可从手牌或装备区)`, cardIds: available, minTotal: 1, maxTotal: Xnow }, timeout: 30 }); const pickedRaw = ctx.state.localVars[LV_CARDS$1]; const picked = Array.isArray(pickedRaw) ? pickedRaw : []; if (picked.length === 0) return; await applyAtom(ctx.state, { type: "移出至暂存区", source: ownerId, target, cardIds: picked, varsKey: EXILE_VARS_KEY$1 }); ctx.state.players[target].vars[EXILE_TURN_KEY] = ctx.state.currentPlayerIndex; } finally { await popFrame(ctx.state); } }); registerBeforeHook(state, skill.id, ownerId, "造成伤害时", async (ctx) => { const atom = ctx.atom; if (atom.source !== ownerId) return; if (!isSlashCard(ctx.state, atom.cardId)) return; const target = atom.target; if (typeof target !== "number") return; if (target === ownerId) return; const targetPlayer = ctx.state.players[target]; const selfPlayer = ctx.state.players[ownerId]; if (!targetPlayer || !selfPlayer) return; if (targetPlayer.hand.length > selfPlayer.hand.length) return; if (equipCount$1(targetPlayer) > equipCount$1(selfPlayer)) return; const amount = typeof atom.amount === "number" ? atom.amount : 0; return { kind: "modify", atom: { ...ctx.atom, amount: amount + 1 } }; }); registerAfterHook(state, skill.id, ownerId, "回合结束", async (ctx) => { const atom = ctx.atom; if (atom.type !== "回合结束") return; const turnOwner = atom.player; for (let i = 0; i < ctx.state.players.length; i++) { const p = ctx.state.players[i]; if (!p) continue; const exiledTurn = p.vars[EXILE_TURN_KEY]; if (typeof exiledTurn !== "number" || exiledTurn !== turnOwner) continue; const exiled = p.vars[EXILE_VARS_KEY$1]; if (Array.isArray(exiled) && exiled.length > 0) await applyAtom(ctx.state, { type: "归还暂存牌", player: i, varsKey: EXILE_VARS_KEY$1 }); delete p.vars[EXILE_TURN_KEY]; } }); return () => {}; } function onMount$111(_skill, api) { api.defineAction("respond", { label: DISPLAY_NAME$29, style: "primary", prompt: { type: "confirm", title: "破军", confirmLabel: "发动", cancelLabel: "不发动" } }); } var SKILL_ID$21, DISPLAY_NAME$29, EXILE_VARS_KEY$1, EXILE_TURN_KEY, RT_ASK_USE$1, RT_ASK_CARDS$1, LV_USE$1, LV_CARDS$1, CTX_TARGET$1, CTX_X$1, _skillModule$52; var init_界破军 = __esmMin((() => { init_types(); init_apply(); init_frame(); init_skill(); SKILL_ID$21 = "界破军"; DISPLAY_NAME$29 = "破军"; EXILE_VARS_KEY$1 = "界破军/移出"; EXILE_TURN_KEY = "界破军/移出回合"; RT_ASK_USE$1 = `${SKILL_ID$21}/askUse`; RT_ASK_CARDS$1 = `${SKILL_ID$21}/askCards`; LV_USE$1 = `${SKILL_ID$21}/use`; LV_CARDS$1 = `${SKILL_ID$21}/cards`; CTX_TARGET$1 = `${SKILL_ID$21}/ctx/target`; CTX_X$1 = `${SKILL_ID$21}/ctx/x`; _skillModule$52 = { createSkill: createSkill$121, onInit: onInit$121, onMount: onMount$111 }; })); //#endregion //#region src/engine/skills/界神速.ts var 界神速_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$120, default: () => 界神速_default, onInit: () => onInit$120, onMount: () => onMount$110 }); function createSkill$120(id, ownerId) { return { id, ownerId, name: "界神速", description: "选择至多三项:①跳过判定+摸牌;②弃装备+跳过出牌;③翻面+跳过弃牌。每项视为出杀" }; } /** 创建一张虚拟杀卡(无实体,仅用于结算流程的 cardId 引用) */ function makeVirtualKillCard$1(source, target, seq) { return `界神速:杀:${source}:${target}:${seq}`; } /** * 执行一次"视为出杀"的完整结算(runUseFlow virtual + chargeOnSettle:指定目标→成为目标→检测有效性→询问闪→伤害/抵消)。 * 不消耗手牌、计入出杀次数(由 chargeOnSettle 的 onSettle 计数);无距离限制。同标版 virtualKill。 */ async function virtualKill$1(state, source, target) { if (!state.players[target]?.alive) return; const cardId = makeVirtualKillCard$1(source, target, state.seq); state.cardMap[cardId] = { id: cardId, name: "杀", suit: "", color: "无色", rank: "A", type: "基本牌" }; await runUseFlow(state, source, cardId, [target], "杀", { virtual: true, onSettle: chargeOnSettle(state, source, cardId) }); delete state.cardMap[cardId]; } /** 取玩家装备区的所有 cardId */ function equipCardIds(state, player) { const p = state.players[player]; if (!p) return []; return Object.values(p.equipment).filter((id) => !!id); } /** 询问选择一个虚拟杀目标(其他存活角色)。 * 无其他存活角色时直接返回 undefined,避免创建 0 候选的 choosePlayer 询问 * (preResolve 不处理 choosePlayer filter,会导致玩家卡等超时)。 */ async function askKillTarget(state, ownerId, label) { if (!state.players.some((p, i) => i !== ownerId && p.alive)) return void 0; delete state.localVars[TARGET_KEY$16]; await applyAtom(state, { type: "请求回应", requestType: TARGET_RT$12, target: ownerId, prompt: { type: "choosePlayer", title: `${label}:选择一名其他角色(视为出杀,无距离限制)`, min: 1, max: 1, filter: (_view, t) => t !== ownerId && state.players[t]?.alive === true }, timeout: 15 }); const t = state.localVars[TARGET_KEY$16]; delete state.localVars[TARGET_KEY$16]; return t; } function onInit$120(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "respond", (s) => { const slot = s.pendingSlots.get(ownerId); if (slot?.atom.type !== "请求回应") return "当前不需要回应"; const rt = slot.atom.requestType ?? ""; if (!ALL_RTS.includes(rt)) return "当前不是界神速询问"; return null; }, async (s, params) => { const rt = (s.pendingSlots.get(ownerId)?.atom)?.requestType ?? ""; if (rt === OPT1_RT || rt === OPT2_RT || rt === OPT3_RT) s.localVars[CONFIRMED_KEY$20] = params.choice === true; else if (rt === TARGET_RT$12) { const t = params.targets?.[0] ?? (typeof params.target === "number" ? params.target : void 0); if (typeof t === "number") s.localVars[TARGET_KEY$16] = t; } else if (rt === EQUIP_RT) { const cardIds = params.cardIds; const single = params.cardId; const id = (Array.isArray(cardIds) && cardIds.length > 0 ? cardIds[0] : void 0) ?? (typeof single === "string" ? single : void 0); if (id) s.localVars[EQUIP_KEY] = id; } }); registerBeforeHook(state, skill.id, ownerId, "阶段开始", async (ctx) => { const atom = ctx.atom; if (atom.type !== "阶段开始") return; if (atom.player !== ownerId) return; if (atom.phase !== "判定") return; if (ctx.state.currentPlayerIndex !== ownerId) return; const self = ctx.state.players[ownerId]; if (!self?.alive) return; if (self.vars[USED_KEY$5]) return; if (!ctx.state.players.some((p, i) => i !== ownerId && p.alive)) return; delete ctx.state.localVars[CONFIRMED_KEY$20]; await applyAtom(ctx.state, { type: "请求回应", requestType: OPT1_RT, target: ownerId, prompt: { type: "confirm", title: "是否发动界神速①?(跳过判定+摸牌,视为出杀)", confirmLabel: "发动", cancelLabel: "不发动" }, defaultChoice: false, timeout: 10 }); const opt1 = ctx.state.localVars[CONFIRMED_KEY$20] === true; let opt2 = false; if (equipCardIds(ctx.state, ownerId).length > 0) { delete ctx.state.localVars[CONFIRMED_KEY$20]; await applyAtom(ctx.state, { type: "请求回应", requestType: OPT2_RT, target: ownerId, prompt: { type: "confirm", title: "是否发动界神速②?(弃置一张装备牌,跳过出牌,视为出杀)", confirmLabel: "发动", cancelLabel: "不发动" }, defaultChoice: false, timeout: 10 }); opt2 = ctx.state.localVars[CONFIRMED_KEY$20] === true; } delete ctx.state.localVars[CONFIRMED_KEY$20]; await applyAtom(ctx.state, { type: "请求回应", requestType: OPT3_RT, target: ownerId, prompt: { type: "confirm", title: "是否发动界神速③?(翻面,跳过弃牌,视为出杀)", confirmLabel: "发动", cancelLabel: "不发动" }, defaultChoice: false, timeout: 10 }); const opt3 = ctx.state.localVars[CONFIRMED_KEY$20] === true; if (!opt1 && !opt2 && !opt3) return; self.vars[USED_KEY$5] = true; let opt1Settled = false; await pushFrame(ctx.state, "界神速", ownerId, { opt1, opt2, opt3 }); try { if (opt1 && self.alive) { const target = await askKillTarget(ctx.state, ownerId, "界神速①"); if (typeof target === "number" && ctx.state.players[target]?.alive) { await applyAtom(ctx.state, { type: "加标签", player: ownerId, tag: SKIP_MO_TAG$1 }); await virtualKill$1(ctx.state, ownerId, target); opt1Settled = true; } } if (opt2 && self.alive) { delete ctx.state.localVars[EQUIP_KEY]; await applyAtom(ctx.state, { type: "请求回应", requestType: EQUIP_RT, target: ownerId, prompt: { type: "distribute", mode: "select", title: "界神速②:选择一张装备牌弃置", source: "handAndEquip", minTotal: 1, maxTotal: 1 }, timeout: 15 }); const equipCardIdRaw = ctx.state.localVars[EQUIP_KEY]; delete ctx.state.localVars[EQUIP_KEY]; if (equipCardIdRaw && equipCardIds(ctx.state, ownerId).includes(equipCardIdRaw)) { const target = await askKillTarget(ctx.state, ownerId, "界神速②"); if (typeof target === "number" && ctx.state.players[target]?.alive) { await applyAtom(ctx.state, { type: "加标签", player: ownerId, tag: SKIP_PLAY_TAG$1 }); await applyAtom(ctx.state, { type: "弃置", player: ownerId, cardIds: [equipCardIdRaw], voluntary: true }); await virtualKill$1(ctx.state, ownerId, target); } } } if (opt3 && self.alive) { const target = await askKillTarget(ctx.state, ownerId, "界神速③"); if (typeof target === "number" && ctx.state.players[target]?.alive) { await applyAtom(ctx.state, { type: "加标签", player: ownerId, tag: SKIP_DISCARD_TAG }); await applyAtom(ctx.state, { type: "加标签", player: ownerId, tag: FLIP_TAG$2 }); await virtualKill$1(ctx.state, ownerId, target); } } } finally { await popFrame(ctx.state); } if (opt1Settled) return skipPhase(ctx.state, atom); }); registerBeforeHook(state, skill.id, ownerId, "阶段开始", async (ctx) => { const atom = ctx.atom; if (atom.type !== "阶段开始") return; if (atom.player !== ownerId) return; if (atom.phase !== "摸牌") return; if (!ctx.state.players[ownerId]?.tags.includes(SKIP_MO_TAG$1)) return; return skipPhase(ctx.state, atom, SKIP_MO_TAG$1); }); registerBeforeHook(state, skill.id, ownerId, "阶段开始", async (ctx) => { const atom = ctx.atom; if (atom.type !== "阶段开始") return; if (atom.player !== ownerId) return; if (atom.phase !== "出牌") return; if (!ctx.state.players[ownerId]?.tags.includes(SKIP_PLAY_TAG$1)) return; return skipPhase(ctx.state, atom, SKIP_PLAY_TAG$1); }); registerBeforeHook(state, skill.id, ownerId, "阶段开始", async (ctx) => { const atom = ctx.atom; if (atom.type !== "阶段开始") return; if (atom.player !== ownerId) return; if (atom.phase !== "弃牌") return; if (!ctx.state.players[ownerId]?.tags.includes(SKIP_DISCARD_TAG)) return; return skipPhase(ctx.state, atom, SKIP_DISCARD_TAG); }); registerBeforeHook(state, skill.id, ownerId, "阶段开始", async (ctx) => { const atom = ctx.atom; if (atom.type !== "阶段开始") return; if (atom.player !== ownerId) return; const self = ctx.state.players[ownerId]; if (atom.phase === "准备" && self?.tags.includes(FLIP_TAG$2)) { await applyAtom(ctx.state, { type: "去标签", player: ownerId, tag: FLIP_TAG$2 }); ctx.state.localVars[SKIP_FLAG$1] = ownerId; return { kind: "cancel" }; } if (ctx.state.localVars[SKIP_FLAG$1] === ownerId) return { kind: "cancel" }; }); registerBeforeHook(state, skill.id, ownerId, "阶段结束", async (ctx) => { const atom = ctx.atom; if (atom.type !== "阶段结束") return; if (atom.player !== ownerId) return; if (ctx.state.localVars[SKIP_FLAG$1] !== ownerId) return; delete ctx.state.localVars[SKIP_FLAG$1]; await applyAtom(ctx.state, { type: "清过期标记", player: ownerId }); await applyAtom(ctx.state, { type: "下一玩家" }); if (await applyAtom(ctx.state, { type: "回合结束", player: ownerId })) await applyAtom(ctx.state, { type: "回合结束后", player: ownerId }); return { kind: "cancel" }; }); return () => {}; } function onMount$110(_skill, api) { api.defineAction("respond", { label: "界神速", style: "default", prompt: { type: "confirm", title: "是否发动界神速?", confirmLabel: "发动", cancelLabel: "不发动" } }); } var OPT1_RT, OPT2_RT, OPT3_RT, TARGET_RT$12, EQUIP_RT, SKIP_MO_TAG$1, SKIP_PLAY_TAG$1, SKIP_DISCARD_TAG, FLIP_TAG$2, SKIP_FLAG$1, CONFIRMED_KEY$20, TARGET_KEY$16, EQUIP_KEY, USED_KEY$5, ALL_RTS, 界神速_default; var init_界神速 = __esmMin((() => { init_apply(); init_frame(); init_skill(); init_skip_phase(); init_use_card(); OPT1_RT = "界神速/opt1"; OPT2_RT = "界神速/opt2"; OPT3_RT = "界神速/opt3"; TARGET_RT$12 = "界神速/target"; EQUIP_RT = "界神速/equip"; SKIP_MO_TAG$1 = "神速/跳过摸牌"; SKIP_PLAY_TAG$1 = "神速/跳过出牌"; SKIP_DISCARD_TAG = "神速/跳过弃牌"; FLIP_TAG$2 = "神速/翻面"; SKIP_FLAG$1 = "神速/skipAll"; CONFIRMED_KEY$20 = "神速/confirmed"; TARGET_KEY$16 = "神速/target"; EQUIP_KEY = "神速/equipCardId"; USED_KEY$5 = "神速/usedThisTurn"; ALL_RTS = [ OPT1_RT, OPT2_RT, OPT3_RT, TARGET_RT$12, EQUIP_RT ]; 界神速_default = { createSkill: createSkill$120, onInit: onInit$120, onMount: onMount$110 }; })); //#endregion //#region src/engine/skills/界禁酒.ts var 界禁酒_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$119, default: () => _skillModule$51, onInit: () => onInit$119, onMount: () => onMount$109 }); function createSkill$119(id, ownerId) { return { id, ownerId, name: DISPLAY_NAME$28, description: "锁定技:你的【酒】视为点数K的【杀】;其他角色不能于你的回合使用【酒】", isLocked: true }; } /** 影子卡 id:${原id}#界禁酒(与标版禁酒隔离)。 */ function shadowIdOf$8(cardId) { return `${cardId}#界禁酒`; } function getWrappedOriginalsMap(state) { let m = wrappedOriginalsByState.get(state); if (!m) { m = /* @__PURE__ */ new Map(); wrappedOriginalsByState.set(state, m); } return m; } function onInit$119(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "transform", (st, params) => { const myTurn = st.currentPlayerIndex === ownerId; const free = !hasBlockingPending(st); const self = st.players[ownerId]; const selfAlive = self.alive === true; const cardId = params.cardId; const cardIdOk = typeof cardId === "string"; const card = cardIdOk ? st.cardMap[cardId] : void 0; const cardInHand = cardIdOk && self.hand.includes(cardId); const isWine = !!card && card.name === "酒"; return myTurn && free && selfAlive && cardInHand && isWine ? null : "现在不能使用禁酒"; }, async (st, params) => { const cardId = params.cardId; const shadowId = shadowIdOf$8(cardId); await applyAtom(st, { type: "当作", player: ownerId, cardIds: [cardId], shadowId, outputName: "杀" }); const shadow = st.cardMap[shadowId]; if (shadow) shadow.rank = SHADOW_RANK; }, (st, params) => { const cardId = params.cardId; const sId = shadowIdOf$8(cardId); delete st.cardMap[sId]; const self = st.players[ownerId]; const idx = self.hand.indexOf(sId); if (idx >= 0) self.hand[idx] = cardId; }); const unloadHook = registerAfterHook(state, skill.id, ownerId, "回合开始", async () => { wrapWineRespond(state, ownerId); }); return () => { unloadHook(); const map = getWrappedOriginalsMap(state); for (const [, original] of map) registerActionEntry(state, original); map.clear(); }; } /** 遍历所有非 owner 玩家,wrap 其 酒.respond(幂等)。 * wrap 后 validate 头部检查:owner 回合内拒绝他人用酒,否则委托原 validate。 */ function wrapWineRespond(state, ownerId) { for (const p of state.players) { if (p.index === ownerId) continue; const pid = p.index; if (getWrappedOriginalsMap(state).has(pid)) continue; const original = findActionEntry(state, "酒", pid, "respond"); if (!original) continue; getWrappedOriginalsMap(state).set(pid, { ...original }); const originalValidate = original.validate; registerAction(state, "酒", pid, "respond", (st, params) => { if (st.currentPlayerIndex === ownerId) return "禁酒:界高顺的回合内,其他角色不能使用【酒】"; return originalValidate(st, params); }, original.execute, original.rollback); } } function onMount$109(skill, api) { api.defineAction("transform", { label: DISPLAY_NAME$28, style: "passive", prompt: { type: "useCardAndTarget", title: "选择一张【酒】当点数K的【杀】使用", cardFilter: { filter: (c) => c.name === "酒", min: 1, max: 1 }, targetFilter: { min: 1, max: 1, filter: (view, t) => viewCanAttack(view.players, view.cardMap, view.currentPlayerIndex, t) } }, transform: (card) => ({ name: "杀", sourceCardId: card.id, fromSkill: skill.id }), activeWhen: (ctx) => { const p = ctx.view.players[ctx.perspectiveIdx]; if (!p?.alive) return false; if (!(p.hand?.some((c) => c.name === "酒") ?? false)) return false; return defaultPlayActive(ctx) && viewCanSlash(ctx.view, ctx.perspectiveIdx); } }); } var DISPLAY_NAME$28, SHADOW_RANK, wrappedOriginalsByState, _skillModule$51; var init_界禁酒 = __esmMin((() => { init_skill(); init_apply(); init_action_active(); init_viewDistance(); DISPLAY_NAME$28 = "禁酒"; SHADOW_RANK = "K"; wrappedOriginalsByState = /* @__PURE__ */ new WeakMap(); _skillModule$51 = { createSkill: createSkill$119, onInit: onInit$119, onMount: onMount$109 }; })); //#endregion //#region src/engine/skills/界秘计.ts var 界秘计_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$118, default: () => _skillModule$50, onInit: () => onInit$118, onMount: () => onMount$108 }); /** 已损失体力值 = maxHealth - currentHealth(≥0) */ function lostHealth(state, ownerId) { const p = state.players[ownerId]; if (!p) return 0; return Math.max(0, p.maxHealth - getHealthValue(p)); } function createSkill$118(id, ownerId) { return { id, ownerId, name: DISPLAY_NAME$27, description: "结束阶段,你可以摸X张牌(X为你已损失的体力值),然后你可以交给其他角色至多X张牌" }; } function onInit$118(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "respond", (st, params) => { const slot = st.pendingSlots.get(ownerId); if (!slot) return "当前不需要回应"; if (slot.atom.type !== "请求回应") return "当前不需要回应"; const rt = slot.atom.requestType; if (rt === CONFIRM_RT$22 || rt === GIVE_CONFIRM_RT) return null; if (rt === GIVE_TARGET_RT) { const target = params.target; if (typeof target !== "number") return "需要指定目标"; if (target === ownerId) return "不能给自己"; if (!st.players[target]?.alive) return "目标不合法"; return null; } if (rt === GIVE_CARDS_RT) { const raw = params.cardIds ?? params.cardId; const cardIds = Array.isArray(raw) ? raw : typeof raw === "string" ? [raw] : []; const maxN = st.localVars[GIVE_MAX_VAR] ?? 0; if (cardIds.length > maxN) return `至多 ${maxN} 张`; if (new Set(cardIds).size !== cardIds.length) return "不能重复"; const self = st.players[ownerId]; if (!self) return "玩家不存在"; if (!cardIds.every((id) => typeof id === "string" && self.hand.includes(id))) return "牌不在手牌中"; return null; } return "当前不是秘计询问"; }, async (st, params) => { const rt = (st.pendingSlots.get(ownerId)?.atom)?.requestType; if (rt === CONFIRM_RT$22) st.localVars[CONFIRM_KEY$9] = params.choice === true || params.confirmed === true; else if (rt === GIVE_CONFIRM_RT) st.localVars[GIVE_CONFIRM_KEY] = params.choice === true || params.confirmed === true; else if (rt === GIVE_TARGET_RT) st.localVars[GIVE_TARGET_KEY] = params.target; else if (rt === GIVE_CARDS_RT) { const raw = params.cardIds ?? params.cardId; const normalized = Array.isArray(raw) ? raw : typeof raw === "string" ? [raw] : []; st.localVars[GIVE_CARDS_KEY] = normalized.filter((id) => typeof id === "string"); } }); registerAfterHook(state, skill.id, ownerId, "阶段开始", async (ctx) => { const atom = ctx.atom; if (atom.type !== "阶段开始") return; if (atom.player !== ownerId) return; if (atom.phase !== "回合结束") return; const st = ctx.state; if (!st.players[ownerId]?.alive) return; const pendingKey = mijiPendingKey(ownerId); if (getMijiPending(st.players[ownerId].vars, ownerId)) { delete st.players[ownerId].vars[pendingKey]; await runMijiOnce(st, ownerId, false); } await runMijiOnce(st, ownerId, true); }); return () => {}; } /** * 执行一次秘计: * 1. (askActivate=true 时) 询问发动 * 2. 摸 X 张牌(X = 已损失体力) * 3. 询问是否分发 → 选目标 → 选 0..X 张手牌 → 逐张 给予 */ async function runMijiOnce(state, ownerId, askActivate) { const x = lostHealth(state, ownerId); if (x <= 0) return; if (!state.players[ownerId]?.alive) return; if (askActivate) { delete state.localVars[CONFIRM_KEY$9]; await applyAtom(state, { type: "请求回应", requestType: CONFIRM_RT$22, target: ownerId, prompt: { type: "confirm", title: `秘计:是否摸 ${x} 张牌(X=已损失体力)?然后可交给其他角色至多 ${x} 张牌`, confirmLabel: "发动", cancelLabel: "不发动" }, defaultChoice: false, timeout: 15 }); if (state.localVars[CONFIRM_KEY$9] !== true) { delete state.localVars[CONFIRM_KEY$9]; return; } delete state.localVars[CONFIRM_KEY$9]; } await applyAtom(state, { type: "摸牌", player: ownerId, count: x }); if (!state.players[ownerId]?.alive) return; delete state.localVars[GIVE_CONFIRM_KEY]; await applyAtom(state, { type: "请求回应", requestType: GIVE_CONFIRM_RT, target: ownerId, prompt: { type: "confirm", title: `秘计:是否交给其他角色至多 ${x} 张牌?`, confirmLabel: "分发", cancelLabel: "不分发" }, defaultChoice: false, timeout: 15 }); if (state.localVars[GIVE_CONFIRM_KEY] !== true) { delete state.localVars[GIVE_CONFIRM_KEY]; return; } delete state.localVars[GIVE_CONFIRM_KEY]; if (state.players.filter((p) => p.alive && p.index !== ownerId).length === 0) return; delete state.localVars[GIVE_TARGET_KEY]; await applyAtom(state, { type: "请求回应", requestType: GIVE_TARGET_RT, target: ownerId, prompt: { type: "choosePlayer", title: "秘计:选择分发目标(其他角色)", min: 1, max: 1, filter: (_view, t) => t !== ownerId && state.players[t]?.alive === true }, timeout: 20 }); const target = state.localVars[GIVE_TARGET_KEY]; delete state.localVars[GIVE_TARGET_KEY]; if (typeof target !== "number" || target === ownerId) return; if (!state.players[target]?.alive) return; state.localVars[GIVE_MAX_VAR] = x; delete state.localVars[GIVE_CARDS_KEY]; await applyAtom(state, { type: "请求回应", requestType: GIVE_CARDS_RT, target: ownerId, prompt: { type: "useCard", title: `秘计:选择至多 ${x} 张手牌给予 P${target}(可不选)`, cardFilter: { filter: () => true, min: 0, max: x } }, defaultChoice: { cardIds: [] }, timeout: 30 }); delete state.localVars[GIVE_MAX_VAR]; const rawCards = state.localVars[GIVE_CARDS_KEY]; delete state.localVars[GIVE_CARDS_KEY]; const cardIds = Array.isArray(rawCards) ? rawCards.slice(0, x) : []; for (const cardId of cardIds) { if (!state.players[ownerId].hand.includes(cardId)) continue; await applyAtom(state, { type: "给予", cardId, from: ownerId, to: target }); } } function onMount$108(_skill, _api) {} var DISPLAY_NAME$27, GIVE_MAX_VAR, CONFIRM_RT$22, GIVE_CONFIRM_RT, GIVE_TARGET_RT, GIVE_CARDS_RT, CONFIRM_KEY$9, GIVE_CONFIRM_KEY, GIVE_TARGET_KEY, GIVE_CARDS_KEY, _skillModule$50; var init_界秘计 = __esmMin((() => { init_types(); init_apply(); init_skill(); init_vars_keys(); DISPLAY_NAME$27 = "秘计"; GIVE_MAX_VAR = "界秘计/giveMax"; CONFIRM_RT$22 = "界秘计/confirm"; GIVE_CONFIRM_RT = "界秘计/giveConfirm"; GIVE_TARGET_RT = "界秘计/giveTarget"; GIVE_CARDS_RT = "界秘计/giveCards"; CONFIRM_KEY$9 = "界秘计/confirmed"; GIVE_CONFIRM_KEY = "界秘计/giveConfirmed"; GIVE_TARGET_KEY = "界秘计/giveTarget"; GIVE_CARDS_KEY = "界秘计/giveCards"; _skillModule$50 = { createSkill: createSkill$118, onInit: onInit$118, onMount: onMount$108 }; })); //#endregion //#region src/engine/skills/界称象.ts var 界称象_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$117, default: () => _skillModule$49, onInit: () => onInit$117, onMount: () => onMount$107 }); /** A/J/Q/K → 1/11/12/13;数字直接 parse;其他 → 0(忽略,实际不会发生) */ function rankValue$4(rank) { if (!rank) return 0; if (rank === "A") return 1; if (rank === "J") return 11; if (rank === "Q") return 12; if (rank === "K") return 13; const n = parseInt(rank, 10); return Number.isFinite(n) ? n : 0; } /** 计算玩家当前亮出加成(默认 0) */ function currentBonus(state, ownerId) { const v = state.players[ownerId]?.vars[BONUS_KEY]; return typeof v === "number" ? v : 0; } function createSkill$117(id, ownerId) { return { id, ownerId, name: DISPLAY_NAME$26, description: "受到1点伤害后,亮出牌堆顶4张牌,获得其中点数和≤13的若干张;若点数和恰好为13,下次发动多亮出1张" }; } function onInit$117(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "respond", (st, params) => { const slot = st.pendingSlots.get(ownerId); if (!slot) return "当前不需要回应"; const atom = slot.atom; if (atom["type"] !== "请求回应") return "当前不需要回应"; const rt = atom["requestType"]; if (rt !== "界称象/confirm" && rt !== "界称象/select") return "当前不是称象询问"; if (rt === "界称象/select") { const candidateIds = atom.prompt?.cardIds ?? []; const candidateSet = new Set(candidateIds); const chosen = params.cardIds ?? []; const seen = /* @__PURE__ */ new Set(); let sum = 0; for (const cid of chosen) { if (!candidateSet.has(cid)) return "牌不在亮出范围内"; if (seen.has(cid)) return "存在重复的牌"; seen.add(cid); sum += rankValue$4(st.cardMap[cid]?.rank); } if (sum > RANK_LIMIT) return `获得牌点数和(${sum})超过13`; } return null; }, async (st, params) => { const rt = (st.pendingSlots.get(ownerId)?.atom)?.requestType; if (rt === "界称象/confirm") st.localVars[CONFIRMED_KEY$19] = params.choice === true || params.confirmed === true; else if (rt === "界称象/select") { const ids = params.cardIds ?? []; st.localVars[SELECTION_KEY] = ids; } }); registerAfterHook(state, skill.id, ownerId, "受到伤害后", async (ctx) => { const atom = ctx.atom; if (atom.target !== ownerId) return; const amount = atom.amount ?? 0; if (amount <= 0) return; for (let i = 0; i < amount; i++) { if (!ctx.state.players[ownerId]?.alive) break; const revealCount = BASE_REVEAL + currentBonus(ctx.state, ownerId); const deck = ctx.state.zones.deck; if (deck.length === 0) break; const n = Math.min(revealCount, deck.length); const revealed = []; for (let k = 0; k < n; k++) revealed.push(deck[deck.length - 1 - k]); delete ctx.state.localVars[CONFIRMED_KEY$19]; await applyAtom(ctx.state, { type: "请求回应", requestType: "界称象/confirm", target: ownerId, prompt: { type: "confirm", title: `称象:是否亮出牌堆顶 ${n} 张牌并获取点数和≤13的若干张?`, confirmLabel: "发动", cancelLabel: "不发动" }, defaultChoice: false, timeout: 20 }); if (!ctx.state.localVars[CONFIRMED_KEY$19]) { delete ctx.state.localVars[CONFIRMED_KEY$19]; continue; } delete ctx.state.players[ownerId].vars[BONUS_KEY]; delete ctx.state.localVars[SELECTION_KEY]; await applyAtom(ctx.state, { type: "请求回应", requestType: "界称象/select", target: ownerId, prompt: { type: "distribute", mode: "select", title: "称象:选择点数和≤13的若干张(可不选)", cardIds: revealed, minTotal: 0, maxTotal: n }, defaultChoice: false, timeout: 30 }); const chosen = ctx.state.localVars[SELECTION_KEY] ?? []; delete ctx.state.localVars[CONFIRMED_KEY$19]; delete ctx.state.localVars[SELECTION_KEY]; if (chosen.length === 0) continue; let sum = 0; for (const cid of chosen) sum += rankValue$4(ctx.state.cardMap[cid]?.rank); const deckArr = ctx.state.zones.deck; const belowRevealed = deckArr.slice(0, deckArr.length - n); const revealedSet = new Set(revealed); const chosenSet = new Set(chosen); const unpickedRevealedTopFirst = []; for (let k = 0; k < n; k++) { const cid = deckArr[deckArr.length - 1 - k]; if (revealedSet.has(cid) && !chosenSet.has(cid)) unpickedRevealedTopFirst.push(cid); } const unpickedRevealedBottomFirst = [...unpickedRevealedTopFirst].reverse(); const newDeck = [ ...belowRevealed, ...unpickedRevealedBottomFirst, ...[...chosen].reverse() ]; await applyAtom(ctx.state, { type: "整理牌堆", cards: newDeck, topCount: chosen.length, bottomCount: unpickedRevealedBottomFirst.length }); await applyAtom(ctx.state, { type: "摸牌", player: ownerId, count: chosen.length }); if (sum === RANK_LIMIT) ctx.state.players[ownerId].vars[BONUS_KEY] = 1; } }); return () => {}; } function onMount$107(_skill, api) { api.defineAction("respond", { label: DISPLAY_NAME$26, style: "primary", prompt: { type: "confirm", title: "是否发动称象?", confirmLabel: "发动", cancelLabel: "不发动" } }); } var DISPLAY_NAME$26, SELECTION_KEY, CONFIRMED_KEY$19, BONUS_KEY, BASE_REVEAL, RANK_LIMIT, _skillModule$49; var init_界称象 = __esmMin((() => { init_apply(); init_skill(); DISPLAY_NAME$26 = "称象"; SELECTION_KEY = "称象/selection"; CONFIRMED_KEY$19 = "称象/confirmed"; BONUS_KEY = "称象/bonus"; BASE_REVEAL = 4; RANK_LIMIT = 13; _skillModule$49 = { createSkill: createSkill$117, onInit: onInit$117, onMount: onMount$107 }; })); //#endregion //#region src/engine/skills/界空城.ts var 界空城_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$116, default: () => 界空城_default, onInit: () => onInit$116 }); function createSkill$116(id, ownerId) { return { id, ownerId, name: "界空城", description: "锁定技:若你没有手牌,你不是杀和决斗的合法目标;你使用的杀被闪抵消后摸一张牌", isLocked: true }; } function onInit$116(skill, state) { const ownerId = skill.ownerId; const unregBefore = registerBeforeHook(state, skill.id, ownerId, "成为目标", async (ctx) => { const atom = ctx.atom; if (atom.target !== ownerId) return; if (ctx.state.players[ownerId]?.hand.length !== 0) return; let cardName; if (atom.cardId) cardName = ctx.state.cardMap[atom.cardId]?.name; if (!cardName) cardName = topFrame(ctx.state)?.skillId; if (cardName !== "杀" && cardName !== "决斗") return; return { kind: "cancel" }; }); const unregAfter = registerAfterHook(state, skill.id, ownerId, "被抵消", async (ctx) => { if (ctx.frame.skillId !== "杀") return; if (ctx.atom.source !== ownerId) return; await applyAtom(ctx.state, { type: "摸牌", player: ownerId, count: 1 }); }); return () => { unregBefore(); unregAfter(); }; } var 界空城_default; var init_界空城 = __esmMin((() => { init_apply(); init_frame(); init_skill(); 界空城_default = { createSkill: createSkill$116, onInit: onInit$116 }; })); //#endregion //#region src/engine/skills/界突袭.ts var 界突袭_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$115, default: () => 界突袭_default, onInit: () => onInit$115, onMount: () => onMount$106 }); function createSkill$115(id, ownerId) { return { id, ownerId, name: "界突袭", description: "摸牌阶段,你可以少摸任意张牌并获得等量其他角色的各一张手牌" }; } function onInit$115(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "respond", (s) => { const slot = s.pendingSlots.get(ownerId); if (slot?.atom.type !== "请求回应") return "当前不需要回应"; const rt = slot.atom.requestType; if (rt !== TRIGGER_RT$4 && rt !== SELECT_RT$3) return "当前不是突袭回应"; return null; }, async (s, params) => { const rt = (s.pendingSlots.get(ownerId)?.atom)?.requestType; if (rt === TRIGGER_RT$4) s.localVars[TRIGGERED_KEY$1] = params.choice === true; else if (rt === SELECT_RT$3) { const targets = params.targets ?? (typeof params.target === "number" ? [params.target] : void 0); s.localVars[TARGETS_KEY$2] = targets ?? []; } }); registerBeforeHook(state, skill.id, ownerId, "摸牌", async (ctx) => { const atom = ctx.atom; if (atom.player !== ownerId) return; if (ctx.state.currentPlayerIndex !== ownerId) return; if (ctx.state.phase !== "摸牌") return; if (!ctx.state.players[ownerId]?.alive) return; const baseCount = atom.count ?? 2; if (baseCount <= 0) return; const stealablePlayers = ctx.state.players.filter((p, i) => i !== ownerId && p.alive && p.hand.length > 0); if (stealablePlayers.length === 0) return; delete ctx.state.localVars[TRIGGERED_KEY$1]; await applyAtom(ctx.state, { type: "请求回应", requestType: TRIGGER_RT$4, target: ownerId, prompt: { type: "confirm", title: "是否发动界突袭?(少摸任意张牌并获得等量其他角色的各一张手牌)", confirmLabel: "发动", cancelLabel: "不发动" }, defaultChoice: false, timeout: 10 }); if (ctx.state.localVars[TRIGGERED_KEY$1] !== true) return; const maxTargets = Math.min(baseCount, stealablePlayers.length); delete ctx.state.localVars[TARGETS_KEY$2]; await applyAtom(ctx.state, { type: "请求回应", requestType: SELECT_RT$3, target: ownerId, prompt: { type: "choosePlayer", title: `界突袭:选择 1~${maxTargets} 名角色(各获得一张手牌;少摸等量张)`, min: 1, max: maxTargets, filter: (view, target) => target !== ownerId && view.players[target]?.alive === true && (view.players[target]?.handCount ?? 0) > 0 }, timeout: 15 }); const rawTargets = ctx.state.localVars[TARGETS_KEY$2]; const targets = Array.isArray(rawTargets) ? rawTargets.filter((t) => t !== ownerId && ctx.state.players[t]?.alive === true && ctx.state.players[t].hand.length > 0) : []; if (targets.length === 0) return; for (const target of targets) { const tp = ctx.state.players[target]; if (!tp || !tp.alive || tp.hand.length === 0) continue; const rng = createRng(ctx.state.rngSeed); const idx = rng.nextInt(tp.hand.length); ctx.state.rngSeed = rng.getState(); const cardId = tp.hand[idx]; await applyAtom(ctx.state, { type: "获得", player: ownerId, cardId, from: target }); } const newCount = baseCount - targets.length; if (newCount <= 0) return { kind: "cancel" }; return { kind: "modify", atom: { ...ctx.atom, count: newCount } }; }); return () => {}; } function onMount$106(_skill, api) { api.defineAction("respond", { label: "界突袭", style: "primary", prompt: { type: "confirm", title: "是否发动界突袭?", confirmLabel: "发动", cancelLabel: "不发动" } }); } var TRIGGER_RT$4, SELECT_RT$3, TRIGGERED_KEY$1, TARGETS_KEY$2, 界突袭_default; var init_界突袭 = __esmMin((() => { init_apply(); init_skill(); init_rng(); TRIGGER_RT$4 = "界突袭/trigger"; SELECT_RT$3 = "界突袭/select"; TRIGGERED_KEY$1 = "界突袭/triggered"; TARGETS_KEY$2 = "界突袭/targets"; 界突袭_default = { createSkill: createSkill$115, onInit: onInit$115, onMount: onMount$106 }; })); //#endregion //#region src/engine/skills/界窃听.ts var 界窃听_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$114, default: () => _skillModule$48, onInit: () => onInit$114, onMount: () => onMount$105 }); function createSkill$114(id, ownerId) { return { id, ownerId, name: DISPLAY_NAME$25, description: "其他角色的回合结束后,若其本回合未对其他角色造成伤害可选一项;若未使用过牌可选两项:1.夺其一张装备;2.摸一张牌" }; } /** 当前回合玩家是否对其他角色造成过伤害 */ function damagedOtherThisTurn(state) { return state.turn.vars[DAMAGED_OTHER_KEY] === true; } /** 当前回合玩家是否对其他角色使用过牌 */ function usedCardOnOtherThisTurn(state) { return state.turn.vars[USED_CARD_ON_OTHER_KEY] === true; } /** * 执行"夺取 target 一张装备置入 owner 装备区"流程。 * 调用方需保证 target 装备区非空、owner 与 target 均存活。 */ async function executeStealEquip(state, ownerId, turnPlayer) { const targetPlayer = state.players[turnPlayer]; if (!targetPlayer?.alive) return; const equipment = Object.entries(targetPlayer.equipment).filter(([, id]) => typeof id === "string").map(([slot, id]) => ({ slot, cardId: id, cardName: state.cardMap[id]?.name ?? "?" })); if (equipment.length === 0) return; delete state.localVars[PICK_RESULT_KEY$1]; await applyAtom(state, { type: "请求回应", requestType: PICK_EQUIP_RT, target: ownerId, prompt: { type: "pickTargetCard", title: `窃听:选择 ${targetPlayer.name ?? `P${turnPlayer}`} 装备区一张牌置入你的装备区`, target: turnPlayer, equipment, judge: [], handCount: 0 }, defaultChoice: { zone: "equipment", cardId: equipment[0].cardId }, timeout: 20 }); const result = state.localVars[PICK_RESULT_KEY$1]; delete state.localVars[PICK_RESULT_KEY$1]; const cardId = result?.zone === "equipment" && typeof result.cardId === "string" ? result.cardId : equipment[0].cardId; const slotEntry = equipment.find((e) => e.cardId === cardId); if (!slotEntry) return; const slot = slotEntry.slot; const card = state.cardMap[cardId]; if (!card) return; if (card.name && hasSkillModule(card.name)) await applyAtom(state, { type: "移除技能", player: turnPlayer, skillId: card.name }); await applyAtom(state, { type: "获得", player: ownerId, cardId, from: turnPlayer }); const myOld = state.players[ownerId].equipment[slot]; if (myOld) { const oldCard = state.cardMap[myOld]; if (oldCard?.name && hasSkillModule(oldCard.name)) await applyAtom(state, { type: "移除技能", player: ownerId, skillId: oldCard.name }); await applyAtom(state, { type: "卸下", player: ownerId, slot }); await applyAtom(state, { type: "移动牌", cardId: myOld, from: { zone: "手牌", player: ownerId }, to: { zone: "弃牌堆" } }); } await applyAtom(state, { type: "装备", player: ownerId, cardId }); if (card.name && hasSkillModule(card.name)) await applyAtom(state, { type: "添加技能", player: ownerId, skillId: card.name }); } function onInit$114(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "respond", (st, params) => { const slot = st.pendingSlots.get(ownerId); if (!slot) return "当前不需要回应"; if (slot.atom.type !== "请求回应") return "当前不需要回应"; const rt = slot.atom.requestType; if (rt !== TRIGGER_RT$3 && rt !== STEAL_OR_DRAW_RT && rt !== PICK_EQUIP_RT) return "当前不是窃听询问"; if (rt === PICK_EQUIP_RT) { if (params.zone !== "equipment") return "需要选装备"; if (typeof params.cardId !== "string") return "需要 cardId"; const turnPlayer = st.localVars[TURN_PLAYER_KEY]; if (typeof turnPlayer !== "number") return "询问状态异常"; const tp = st.players[turnPlayer]; if (!tp) return "目标不存在"; if (!Object.values(tp.equipment).includes(params.cardId)) return "该牌不在目标装备区"; } return null; }, async (st, params) => { const rt = (st.pendingSlots.get(ownerId)?.atom)?.requestType; if (rt === TRIGGER_RT$3) st.localVars[CONFIRM_KEY$8] = params.choice === true || params.confirmed === true; else if (rt === STEAL_OR_DRAW_RT) st.localVars[STEAL_CHOICE_KEY] = params.choice === true || params.confirmed === true; else if (rt === PICK_EQUIP_RT) st.localVars[PICK_RESULT_KEY$1] = { zone: params.zone, cardId: params.cardId ?? null }; }); registerAfterHook(state, skill.id, ownerId, "造成伤害后", async (ctx) => { const atom = ctx.atom; const cur = ctx.state.currentPlayerIndex; if (atom.source !== cur) return; if (atom.target === cur) return; if ((atom.amount ?? 0) <= 0) return; ctx.state.turn.vars[DAMAGED_OTHER_KEY] = true; }); registerAfterHook(state, skill.id, ownerId, "指定目标", async (ctx) => { const atom = ctx.atom; const cur = ctx.state.currentPlayerIndex; if (atom.source !== cur) return; if (atom.target === cur) return; ctx.state.turn.vars[USED_CARD_ON_OTHER_KEY] = true; }); registerAfterHook(state, skill.id, ownerId, "询问闪", async (ctx) => { const atom = ctx.atom; const cur = ctx.state.currentPlayerIndex; if (atom.source !== cur) return; if (atom.target === cur) return; ctx.state.turn.vars[USED_CARD_ON_OTHER_KEY] = true; }); registerAfterHook(state, skill.id, ownerId, "询问杀", async (ctx) => { const atom = ctx.atom; const cur = ctx.state.currentPlayerIndex; if (atom.source !== cur) return; if (atom.target === cur) return; ctx.state.turn.vars[USED_CARD_ON_OTHER_KEY] = true; }); registerBeforeHook(state, skill.id, ownerId, "回合结束", async (ctx) => { const turnPlayer = ctx.atom.player; if (typeof turnPlayer !== "number") return { kind: "pass" }; if (turnPlayer === ownerId) return { kind: "pass" }; if (!ctx.state.players[ownerId]?.alive) return { kind: "pass" }; const noDamage = !damagedOtherThisTurn(ctx.state); const noCardUse = !usedCardOnOtherThisTurn(ctx.state); const choices = noDamage ? noCardUse ? 2 : 1 : 0; if (choices === 0) return { kind: "pass" }; ctx.state.localVars[TURN_PLAYER_KEY] = turnPlayer; ctx.state.localVars[CHOICES_KEY] = choices; delete ctx.state.localVars[CONFIRM_KEY$8]; await applyAtom(ctx.state, { type: "请求回应", requestType: TRIGGER_RT$3, target: ownerId, prompt: { type: "confirm", title: `窃听:${ctx.state.players[turnPlayer]?.name ?? `P${turnPlayer}`} 本回合${noDamage ? "未" : "已"}对其他角色造成伤害,${noCardUse ? "未" : "已"}使用过牌。是否发动?`, description: choices === 2 ? "可选两项(夺装备+摸牌)" : "可选一项(夺装备或摸牌)", confirmLabel: "发动", cancelLabel: "不发动" }, defaultChoice: false, timeout: 15 }); if (ctx.state.localVars[CONFIRM_KEY$8] !== true) { delete ctx.state.localVars[CONFIRM_KEY$8]; delete ctx.state.localVars[TURN_PLAYER_KEY]; delete ctx.state.localVars[CHOICES_KEY]; return { kind: "pass" }; } delete ctx.state.localVars[CONFIRM_KEY$8]; if (!ctx.state.players[turnPlayer]?.alive || !ctx.state.players[ownerId]?.alive) { delete ctx.state.localVars[TURN_PLAYER_KEY]; delete ctx.state.localVars[CHOICES_KEY]; return { kind: "pass" }; } const hasEquip = Object.values(ctx.state.players[turnPlayer].equipment).some((id) => typeof id === "string"); if (choices === 1) if (hasEquip) { delete ctx.state.localVars[STEAL_CHOICE_KEY]; await applyAtom(ctx.state, { type: "请求回应", requestType: STEAL_OR_DRAW_RT, target: ownerId, prompt: { type: "confirm", title: "窃听:夺一张装备?(确认=夺装备,取消=摸一张牌)", confirmLabel: "夺装备", cancelLabel: "摸一张牌" }, defaultChoice: false, timeout: 15 }); const steal = ctx.state.localVars[STEAL_CHOICE_KEY] === true; delete ctx.state.localVars[STEAL_CHOICE_KEY]; if (steal) await executeStealEquip(ctx.state, ownerId, turnPlayer); else await applyAtom(ctx.state, { type: "摸牌", player: ownerId, count: 1 }); } else await applyAtom(ctx.state, { type: "摸牌", player: ownerId, count: 1 }); else { if (hasEquip) await executeStealEquip(ctx.state, ownerId, turnPlayer); await applyAtom(ctx.state, { type: "摸牌", player: ownerId, count: 1 }); } delete ctx.state.localVars[TURN_PLAYER_KEY]; delete ctx.state.localVars[CHOICES_KEY]; return { kind: "pass" }; }); return () => {}; } function onMount$105(_skill, api) { api.defineAction("respond", { label: DISPLAY_NAME$25, style: "default", prompt: { type: "confirm", title: "是否发动窃听?", confirmLabel: "发动", cancelLabel: "不发动" } }); return () => {}; } var SKILL_ID$20, DISPLAY_NAME$25, DAMAGED_OTHER_KEY, USED_CARD_ON_OTHER_KEY, TRIGGER_RT$3, STEAL_OR_DRAW_RT, PICK_EQUIP_RT, CONFIRM_KEY$8, STEAL_CHOICE_KEY, PICK_RESULT_KEY$1, TURN_PLAYER_KEY, CHOICES_KEY, _skillModule$48; var init_界窃听 = __esmMin((() => { init_apply(); init_skill(); init_registry$1(); SKILL_ID$20 = "界窃听"; DISPLAY_NAME$25 = "窃听"; DAMAGED_OTHER_KEY = `${SKILL_ID$20}/damagedOther`; USED_CARD_ON_OTHER_KEY = `${SKILL_ID$20}/usedCardOnOther`; TRIGGER_RT$3 = `${SKILL_ID$20}/trigger`; STEAL_OR_DRAW_RT = `${SKILL_ID$20}/stealOrDraw`; PICK_EQUIP_RT = `${SKILL_ID$20}/pickEquip`; CONFIRM_KEY$8 = `${SKILL_ID$20}/confirmed`; STEAL_CHOICE_KEY = `${SKILL_ID$20}/stealChoice`; PICK_RESULT_KEY$1 = `${SKILL_ID$20}/pickResult`; TURN_PLAYER_KEY = `${SKILL_ID$20}/turnPlayer`; CHOICES_KEY = `${SKILL_ID$20}/choices`; _skillModule$48 = { createSkill: createSkill$114, onInit: onInit$114, onMount: onMount$105 }; })); //#endregion //#region src/engine/skills/界精策.ts var 界精策_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$113, default: () => _skillModule$47, onInit: () => onInit$113, onMount: () => onMount$104 }); function createSkill$113(id, ownerId) { return { id, ownerId, name: DISPLAY_NAME$24, description: "本回合每使用一种新花色手牌,手牌上限+1;出牌阶段结束时,可以摸X张牌(X为本回合使用过牌的类型数)" }; } function usedSuits(state, ownerId) { const v = state.turn.vars[USED_SUITS_PREFIX + ownerId]; return Array.isArray(v) ? v : []; } function usedTypes(state, ownerId) { const v = state.turn.vars[USED_TYPES_PREFIX + ownerId]; return Array.isArray(v) ? v : []; } /** * 记录一张牌的使用,更新花色与类型集合。 * 返回是否有变化(用于决定是否需要刷新手牌上限加成)。 */ function recordCardUse(state, ownerId, card) { if (!card?.suit || !card?.type) return false; let changed = false; const suits = usedSuits(state, ownerId); if (!suits.includes(card.suit)) { suits.push(card.suit); state.turn.vars[USED_SUITS_PREFIX + ownerId] = suits; changed = true; } const types = usedTypes(state, ownerId); if (!types.includes(card.type)) { types.push(card.type); state.turn.vars[USED_TYPES_PREFIX + ownerId] = types; changed = true; } return changed; } /** * 同步手牌上限加成到 turn.vars['手牌上限/bonus:']。 * hand-limit.ts 的默认公式会消费此 key(health + bonus)。 */ function syncHandLimitBonus(state, ownerId) { const bonus = usedSuits(state, ownerId).length; if (bonus > 0) state.turn.vars[handLimitBonusKey(ownerId)] = bonus; else delete state.turn.vars[handLimitBonusKey(ownerId)]; } function onInit$113(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "respond", (st, _params) => { const slot = st.pendingSlots.get(ownerId); if (slot?.atom.type !== "请求回应") return "当前不需要回应"; if (slot.atom.requestType !== CONFIRM_REQUEST$8) return "当前不是精策确认"; return null; }, async (st, params) => { st.localVars[CONFIRMED_VAR$2] = params.choice === true || params.confirmed === true; }); registerAfterHook(state, skill.id, ownerId, "移动牌", async (ctx) => { const atom = ctx.atom; if (atom.from.zone !== "手牌" || atom.from.player !== ownerId) return; if (atom.to.zone !== "处理区") return; if (ctx.state.currentPlayerIndex !== ownerId) return; const card = ctx.state.cardMap[atom.cardId]; if (!recordCardUse(ctx.state, ownerId, card)) return; syncHandLimitBonus(ctx.state, ownerId); }); registerAfterHook(state, skill.id, ownerId, "装备", async (ctx) => { const atom = ctx.atom; if (atom.player !== ownerId) return; if (ctx.state.currentPlayerIndex !== ownerId) return; const card = ctx.state.cardMap[atom.cardId]; if (!recordCardUse(ctx.state, ownerId, card)) return; syncHandLimitBonus(ctx.state, ownerId); }); registerBeforeHook(state, skill.id, ownerId, "阶段结束", async (ctx) => { const atom = ctx.atom; if (atom.type !== "阶段结束") return; if (atom.phase !== "出牌") return; if (atom.player !== ownerId) return; if (!ctx.state.players[ownerId]?.alive) return; const X = usedTypes(ctx.state, ownerId).length; if (X === 0) return; delete ctx.state.localVars[CONFIRMED_VAR$2]; await applyAtom(ctx.state, { type: "请求回应", requestType: CONFIRM_REQUEST$8, target: ownerId, prompt: { type: "confirm", title: `精策:是否摸 ${X} 张牌?(本回合已使用 ${X} 种类型的牌)`, confirmLabel: "摸牌", cancelLabel: "不摸" }, defaultChoice: false, timeout: 15 }); if (!ctx.state.localVars[CONFIRMED_VAR$2]) return; await applyAtom(ctx.state, { type: "摸牌", player: ownerId, count: X }); }); return () => {}; } function onMount$104(_skill, _api) { return () => {}; } var SKILL_ID$19, DISPLAY_NAME$24, USED_SUITS_PREFIX, USED_TYPES_PREFIX, CONFIRMED_VAR$2, CONFIRM_REQUEST$8, _skillModule$47; var init_界精策 = __esmMin((() => { init_apply(); init_skill(); init_vars_keys(); SKILL_ID$19 = "界精策"; DISPLAY_NAME$24 = "精策"; USED_SUITS_PREFIX = `${SKILL_ID$19}/usedSuits:`; USED_TYPES_PREFIX = `${SKILL_ID$19}/usedTypes:`; CONFIRMED_VAR$2 = `${SKILL_ID$19}/confirmed`; CONFIRM_REQUEST$8 = `${SKILL_ID$19}/confirm`; _skillModule$47 = { createSkill: createSkill$113, onInit: onInit$113, onMount: onMount$104 }; })); //#endregion //#region src/engine/skills/界红颜.ts var 界红颜_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$112, default: () => 界红颜_default, onInit: () => onInit$112, onMount: () => onMount$103 }); function createSkill$112(id, ownerId) { return { id, ownerId, name: "界红颜", description: "锁定技:你的黑桃牌和黑桃判定牌视为红桃牌;装备区有红桃牌时手牌上限等于体力上限", isLocked: true }; } /** 装备区是否存在红桃牌 */ function hasHeartInEquipment(state, player) { const equip = state.players[player]?.equipment ?? {}; for (const id of Object.values(equip)) { if (!id) continue; const card = state.cardMap[id]; if (card?.suit === "♥" || card?.suit === "♠") return true; } return false; } function onInit$112(skill, state) { const ownerId = skill.ownerId; const unloadHook = registerBeforeHook(state, skill.id, ownerId, "判定", async (ctx) => { if (ctx.atom.player !== ownerId) return; const topId = ctx.state.zones.deck[ctx.state.zones.deck.length - 1]; if (!topId) return; const card = ctx.state.cardMap[topId]; if (!card) return; if (card.suit !== "♠") return; card.suit = "♥"; card.color = "红"; }); const unloadProvider = registerHandLimitProvider(state, ownerId, (st, player) => { if (!hasHeartInEquipment(st, player)) return void 0; return st.players[player]?.maxHealth; }); return () => { unloadHook(); unloadProvider(); }; } function onMount$103(_skill, _api) { return () => {}; } var 界红颜_default; var init_界红颜 = __esmMin((() => { init_skill(); init_hand_limit(); 界红颜_default = { createSkill: createSkill$112, onInit: onInit$112, onMount: onMount$103 }; })); //#endregion //#region src/engine/skills/界纵玄.ts var 界纵玄_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$111, default: () => _skillModule$46, onInit: () => onInit$111, onMount: () => onMount$102 }); /** 从 fromIndex 之后找下一个存活玩家索引;全死亡时返回 fromIndex */ function findNextAlive(state, fromIndex) { const n = state.players.length; for (let i = 1; i <= n; i++) { const idx = (fromIndex + i) % n; if (state.players[idx]?.alive) return idx; } return fromIndex; } /** candidate 是否为 target 的上家(下一个存活玩家 === target) */ function isUpstreamOf(state, candidate, target) { if (candidate === target) return false; return findNextAlive(state, candidate) === target; } function createSkill$111(id, ownerId) { return { id, ownerId, name: DISPLAY_NAME$23, description: "当你的牌因弃置置入弃牌堆后,或你上家每回合首次因弃置置入弃牌堆后,你可以将其中任意张牌置于牌堆顶" }; } function onInit$111(skill, state) { const ownerId = skill.ownerId; const upstreamKey = `${UPSTREAM_TRIGGERED_PREFIX}:${ownerId}`; registerAfterHook(state, skill.id, ownerId, "弃置", async (ctx) => { const atom = ctx.atom; const st = ctx.state; const discarder = atom.player; if (!st.players[ownerId]?.alive) return; let isUpstream = false; if (discarder === ownerId) {} else if (isUpstreamOf(st, discarder, ownerId)) { if (st.turn.vars[upstreamKey]) return; isUpstream = true; } else return; const eligible = atom.cardIds.filter((id) => st.zones.discardPile.includes(id)); if (eligible.length === 0) return; if (isUpstream) st.turn.vars[upstreamKey] = true; await pushFrame(st, SKILL_ID$18, ownerId, { cardIds: [...eligible] }); try { delete st.localVars[CONFIRM_KEY$7]; await applyAtom(st, { type: "请求回应", requestType: CONFIRM_RT$21, target: ownerId, prompt: { type: "confirm", title: `是否发动纵玄?(${isUpstream ? "上家" : "你"}弃置了 ${eligible.length} 张牌,可选若干置于牌堆顶)`, confirmLabel: "发动", cancelLabel: "不发动" }, defaultChoice: false, timeout: 30 }); if (st.localVars[CONFIRM_KEY$7] !== true) { delete st.localVars[CONFIRM_KEY$7]; return; } delete st.localVars[CONFIRM_KEY$7]; const remaining = [...eligible]; while (true) { const available = remaining.filter((id) => st.zones.discardPile.includes(id)); if (available.length === 0) break; delete st.localVars[PICK_KEY$2]; await applyAtom(st, { type: "请求回应", requestType: PICK_RT$4, target: ownerId, prompt: { type: "pickProcessingCard", title: "纵玄:选择一张牌置于牌堆顶", cards: available.map((id) => { const c = st.cardMap[id]; return { cardId: id, cardName: c?.name ?? "?", suit: c?.suit ?? "", rank: c?.rank ?? "" }; }) }, defaultChoice: available[0], timeout: 30 }); const pickedId = st.localVars[PICK_KEY$2]; delete st.localVars[PICK_KEY$2]; if (!pickedId || !available.includes(pickedId)) break; await applyAtom(st, { type: "移动牌", cardId: pickedId, from: { zone: "弃牌堆" }, to: { zone: "牌堆" } }); const idx = remaining.indexOf(pickedId); if (idx >= 0) remaining.splice(idx, 1); const rest = remaining.filter((id) => st.zones.discardPile.includes(id)); if (rest.length === 0) break; delete st.localVars[AGAIN_KEY]; await applyAtom(st, { type: "请求回应", requestType: AGAIN_RT, target: ownerId, prompt: { type: "confirm", title: `纵玄:是否继续选牌?(剩余 ${rest.length} 张可选)`, confirmLabel: "继续", cancelLabel: "完成" }, defaultChoice: false, timeout: 30 }); if (st.localVars[AGAIN_KEY] !== true) { delete st.localVars[AGAIN_KEY]; break; } delete st.localVars[AGAIN_KEY]; } } finally { await popFrame(st); } }); registerAction(state, skill.id, ownerId, "respond", (st, params) => { const slot = st.pendingSlots.get(ownerId); if (!slot) return "当前不需要回应"; if (slot.atom.type !== "请求回应") return "当前不是纵玄窗口"; const atom = slot.atom; if (atom.requestType === CONFIRM_RT$21 || atom.requestType === AGAIN_RT) return null; if (atom.requestType === PICK_RT$4) { if (typeof params.cardId !== "string") return "需要选择一张牌"; return null; } return "当前不是纵玄窗口"; }, async (st, params) => { const rt = (st.pendingSlots.get(ownerId)?.atom)?.requestType; if (rt === CONFIRM_RT$21) st.localVars[CONFIRM_KEY$7] = params.choice === true || params.confirmed === true; else if (rt === AGAIN_RT) st.localVars[AGAIN_KEY] = params.choice === true || params.confirmed === true; else if (rt === PICK_RT$4) { if (typeof params.cardId === "string") st.localVars[PICK_KEY$2] = params.cardId; } }); return () => {}; } function onMount$102(_skill, _api) { return () => {}; } var SKILL_ID$18, DISPLAY_NAME$23, CONFIRM_RT$21, PICK_RT$4, AGAIN_RT, CONFIRM_KEY$7, PICK_KEY$2, AGAIN_KEY, UPSTREAM_TRIGGERED_PREFIX, _skillModule$46; var init_界纵玄 = __esmMin((() => { init_apply(); init_frame(); init_skill(); SKILL_ID$18 = "界纵玄"; DISPLAY_NAME$23 = "纵玄"; CONFIRM_RT$21 = "界纵玄/confirm"; PICK_RT$4 = "界纵玄/pick"; AGAIN_RT = "界纵玄/again"; CONFIRM_KEY$7 = "界纵玄/confirmed"; PICK_KEY$2 = "界纵玄/cardId"; AGAIN_KEY = "界纵玄/again"; UPSTREAM_TRIGGERED_PREFIX = "界纵玄/upstreamTriggeredThisTurn"; _skillModule$46 = { createSkill: createSkill$111, onInit: onInit$111, onMount: onMount$102 }; })); //#endregion //#region src/engine/skills/界纵适.ts var 界纵适_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$110, default: () => _skillModule$45, onInit: () => onInit$110, onMount: () => onMount$101 }); /** 拼点牌点数:A=1, 2-10=面值, J=11, Q=12, K=13(与 天义/驱虎/烈刃 等一致)。 */ function rankValue$3(rank) { if (rank === "A") return 1; if (rank === "J") return 11; if (rank === "Q") return 12; if (rank === "K") return 13; const n = parseInt(rank, 10); return Number.isFinite(n) ? n : 0; } function createSkill$110(id, ownerId) { return { id, ownerId, name: DISPLAY_NAME$22, description: "当你拼点赢时,你可以获得点数较小的拼点牌;当你没赢时,你可以获得你的拼点牌" }; } function onInit$110(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "respond", (st, _params) => { const slot = st.pendingSlots.get(ownerId); if (!slot) return "当前不需要回应"; const atom = slot.atom; if (atom.type !== "请求回应") return "当前不需要回应"; if (atom.requestType !== CONFIRM_RT$20) return "当前不是纵适回应"; return null; }, async (st, params) => { const slot = st.pendingSlots.get(ownerId); if (!slot) return; const atom = slot.atom; if (atom.type !== "请求回应" || atom.requestType !== CONFIRM_RT$20) return; st.localVars[CONFIRM_KEY$6] = params.choice === true; }); registerAfterHook(state, skill.id, ownerId, "拼点后", async (ctx) => { const atom = ctx.atom; if (atom.type !== "拼点后") return; const isInitiator = atom.initiator === ownerId; const isTarget = atom.target === ownerId; if (!isInitiator && !isTarget) return; if (!atom.initiatorCard || !atom.targetCard) return; const initiatorCard = ctx.state.cardMap[atom.initiatorCard]; const targetCard = ctx.state.cardMap[atom.targetCard]; if (!initiatorCard || !targetCard) return; const initiatorValue = rankValue$3(initiatorCard.rank); const targetValue = rankValue$3(targetCard.rank); const ownerValue = isInitiator ? initiatorValue : targetValue; const otherValue = isInitiator ? targetValue : initiatorValue; const ownerCardId = isInitiator ? atom.initiatorCard : atom.targetCard; const otherCardId = isInitiator ? atom.targetCard : atom.initiatorCard; const win = ownerValue > otherValue; const cardToGain = win ? otherCardId : ownerCardId; if (!ctx.state.zones.discardPile.includes(cardToGain)) return; delete ctx.state.localVars[CONFIRM_KEY$6]; await applyAtom(ctx.state, { type: "请求回应", requestType: CONFIRM_RT$20, target: ownerId, prompt: { type: "confirm", title: win ? "纵适:是否获得点数较小的拼点牌?" : "纵适:是否获得你的拼点牌?" }, timeout: 30 }); const confirmed = ctx.state.localVars[CONFIRM_KEY$6] === true; delete ctx.state.localVars[CONFIRM_KEY$6]; if (!confirmed) return; if (!ctx.state.zones.discardPile.includes(cardToGain)) return; await applyAtom(ctx.state, { type: "移动牌", cardId: cardToGain, from: { zone: "弃牌堆" }, to: { zone: "手牌", player: ownerId } }); }); return () => {}; } function onMount$101(_skill, api) { api.defineAction("respond", { label: DISPLAY_NAME$22, style: "primary", prompt: { type: "confirm", title: "纵适:是否获得拼点牌?" } }); } var SKILL_ID$17, DISPLAY_NAME$22, CONFIRM_RT$20, CONFIRM_KEY$6, _skillModule$45; var init_界纵适 = __esmMin((() => { init_apply(); init_skill(); SKILL_ID$17 = "界纵适"; DISPLAY_NAME$22 = "纵适"; CONFIRM_RT$20 = `${SKILL_ID$17}/confirm`; CONFIRM_KEY$6 = `${SKILL_ID$17}/confirmed`; _skillModule$45 = { createSkill: createSkill$110, onInit: onInit$110, onMount: onMount$101 }; })); //#endregion //#region src/engine/skills/界结姻.ts var 界结姻_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$109, default: () => _skillModule$44, onInit: () => onInit$109, onMount: () => onMount$100 }); /** 装备牌 subtype → 装备栏位 */ function slotOf$2(card) { switch (card?.subtype) { case "武器": return "武器"; case "防具": return "防具"; case "进攻马": return "进攻马"; case "防御马": return "防御马"; case "宝物": return "宝物"; default: return null; } } function createSkill$109(id, ownerId) { return { id, ownerId, name: SKILL_NAME$2, description: "出牌阶段限一次:选一名男性角色,弃一张手牌或置一张装备牌入其装备区,体力大者摸1张,小者回1点" }; } /** 校验某座次是否为合法界结姻目标:存活 + 男性。 */ function isMaleAlive$2(state, target) { const p = state.players[target]; if (!p?.alive) return false; return getGender(p.character) === "男"; } /** cardId 是否在 owner 装备区,返回所在栏位;不在返回 null。 */ function findEquipSlot(state, player, cardId) { const eq = state.players[player]?.equipment ?? {}; for (const slot of EQUIP_SLOTS$1) if (eq[slot] === cardId) return slot; return null; } /** 从 params 规范化出 { cardId, target }。 * 支持三种格式: * 1. distribute/allocate: params.allocation = [{target, cardIds:[id]}](前端/正式提交) * 2. 简单: params.cardIds = [id] + params.target = idx * 3. 单卡: params.cardId + params.target = idx(测试/headless 直发) */ function resolveParams$1(params) { const allocation = params.allocation; if (Array.isArray(allocation) && allocation.length > 0) { const entry = allocation[0]; const cardIds = entry.cardIds; const target = entry.target; if (Array.isArray(cardIds) && cardIds.length >= 1 && typeof target === "number") return { cardId: String(cardIds[0]), target }; } const target = params.target; if (typeof target !== "number") return null; const cardId = typeof params.cardId === "string" ? params.cardId : Array.isArray(params.cardIds) && typeof params.cardIds[0] === "string" ? params.cardIds[0] : void 0; if (!cardId) return null; return { cardId, target }; } function onInit$109(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "use", (st, params) => { const self = st.players[ownerId]; if (!self?.alive) return "角色不可用"; if (st.currentPlayerIndex !== ownerId) return "不是你的回合"; if (st.phase !== "出牌") return "不是出牌阶段"; if (hasBlockingPending(st)) return "当前有未回应的询问"; if (st.players[ownerId]?.vars[USED_KEY$4]) return "本回合已使用过界结姻"; const resolved = resolveParams$1(params); if (!resolved) return "需要选择一张牌和一名男性目标"; const { cardId, target } = resolved; if (!isMaleAlive$2(st, target)) return "目标必须是男性角色"; const inHand = self.hand.includes(cardId); const ownerSlot = findEquipSlot(st, ownerId, cardId); if (!inHand && ownerSlot === null) return "牌不在手牌或装备区"; const card = st.cardMap[cardId]; if (card?.type === "装备牌" && slotOf$2(card) === null) return "装备牌栏位不合法"; return null; }, async (st, params) => { const resolved = resolveParams$1(params); if (!resolved) return; const { cardId, target } = resolved; const from = ownerId; await markOncePerTurn(st, from, SKILL_NAME$2); await pushFrame(st, SKILL_NAME$2, from, {}); try { const card = st.cardMap[cardId]; if (card?.type === "装备牌") { const slot = slotOf$2(card); const ownerSlot = findEquipSlot(st, from, cardId); if (ownerSlot !== null) { if (card?.name && hasSkillModule(card.name)) await applyAtom(st, { type: "移除技能", player: from, skillId: card.name }); await applyAtom(st, { type: "卸下", player: from, slot: ownerSlot }); } await applyAtom(st, { type: "移动牌", cardId, from: { zone: "手牌", player: from }, to: { zone: "手牌", player: target } }); const currentEquip = st.players[target].equipment[slot]; if (currentEquip) { const oldCard = st.cardMap[currentEquip]; if (oldCard?.name && hasSkillModule(oldCard.name)) await applyAtom(st, { type: "移除技能", player: target, skillId: oldCard.name }); await applyAtom(st, { type: "卸下", player: target, slot }); await applyAtom(st, { type: "移动牌", cardId: currentEquip, from: { zone: "手牌", player: target }, to: { zone: "弃牌堆" } }); } await applyAtom(st, { type: "装备", player: target, cardId }); if (card?.name && hasSkillModule(card.name)) await applyAtom(st, { type: "添加技能", player: target, skillId: card.name }); } else await applyAtom(st, { type: "弃置", player: from, cardIds: [cardId], voluntary: true }); const ownerHealth = getHealthValue(st.players[from]); const targetHealth = getHealthValue(st.players[target]); if (ownerHealth > targetHealth) { await applyAtom(st, { type: "摸牌", player: from, count: 1 }); await applyAtom(st, { type: "回复体力", target, amount: 1, source: from }); } else if (ownerHealth < targetHealth) { await applyAtom(st, { type: "摸牌", player: target, count: 1 }); await applyAtom(st, { type: "回复体力", target: from, amount: 1, source: from }); } } finally { await popFrame(st); } }); return () => {}; } function onMount$100(_skill, api) { api.defineAction("use", { label: SKILL_NAME$2, style: "primary", prompt: { type: "distribute", mode: "allocate", title: "界结姻:选一张手牌弃置,或选一张装备牌置入一名男性角色的装备区", source: "handAndEquip", minTotal: 1, maxTotal: 1, minPerTarget: 1, maxPerTarget: 1, allowSelf: false, targetFilter: (view, target) => { const p = view.players[target]; if (!p || p.alive === false) return false; return getGender(p.character) === "男"; } }, activeWhen: (ctx) => activeUnlessUsedThisTurn(SKILL_NAME$2)(ctx) }); return () => {}; } var SKILL_NAME$2, USED_KEY$4, EQUIP_SLOTS$1, _skillModule$44; var init_界结姻 = __esmMin((() => { init_types(); init_apply(); init_frame(); init_once_per_turn(); init_skill(); init_character_meta(); init_registry$1(); SKILL_NAME$2 = "界结姻"; USED_KEY$4 = `${SKILL_NAME$2}/usedThisTurn`; EQUIP_SLOTS$1 = [ "武器", "防具", "进攻马", "防御马", "宝物" ]; _skillModule$44 = { createSkill: createSkill$109, onInit: onInit$109, onMount: onMount$100 }; })); //#endregion //#region src/engine/skills/界绝情.ts var 界绝情_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$108, default: () => _skillModule$43, onInit: () => onInit$108, onMount: () => onMount$99 }); function createSkill$108(id, ownerId) { return { id, ownerId, name: "绝情", description: "锁定技:你即将造成的伤害视为失去体力", isLocked: true }; } function onInit$108(skill, state) { const ownerId = skill.ownerId; registerBeforeHook(state, skill.id, ownerId, "伤害结算开始时", async (ctx) => { const atom = ctx.atom; if (atom.source !== ownerId) return; if (typeof atom.target !== "number") return; const amount = atom.amount ?? 0; if (amount <= 0) return; await applyAtom(ctx.state, { type: "失去体力", target: atom.target, amount }); return { kind: "cancel" }; }); return () => {}; } function onMount$99(_skill, _api) {} var _skillModule$43; var init_界绝情 = __esmMin((() => { init_apply(); init_skill(); _skillModule$43 = { createSkill: createSkill$108, onInit: onInit$108, onMount: onMount$99 }; })); //#endregion //#region src/engine/skills/界绝策.ts var 界绝策_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$107, default: () => _skillModule$42, onInit: () => onInit$107, onMount: () => onMount$98 }); function createSkill$107(id, ownerId) { return { id, ownerId, name: DISPLAY_NAME$21, description: "结束阶段:对一名手牌数小于等于你的其他角色造成1点伤害" }; } function onInit$107(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "respond", (st, params) => { const slot = st.pendingSlots.get(ownerId); if (!slot) return "当前不需要回应"; const atom = slot.atom; if (atom["type"] !== "请求回应") return "当前不需要回应"; const rt = atom["requestType"]; if (rt === CONFIRM_RT$19) return null; if (rt === CHOOSE_RT$6) { const targets = params.targets; if (!Array.isArray(targets) || targets.length !== 1) return "请选择一名目标"; const t = targets[0]; if (t === ownerId) return "不能以自己为目标"; if (!st.players[t]?.alive) return "目标不合法"; const self = st.players[ownerId]; const tp = st.players[t]; if (!self || !tp) return "目标不合法"; if (tp.hand.length > self.hand.length) return "目标手牌数须小于等于你"; return null; } return "当前不是绝策询问"; }, async (st, params) => { const rt = (st.pendingSlots.get(ownerId)?.atom)?.requestType; if (rt === CONFIRM_RT$19) st.localVars[CONFIRMED_KEY$18] = params.choice === true || params.confirmed === true; else if (rt === CHOOSE_RT$6) { const targets = params.targets; if (Array.isArray(targets) && targets.length === 1) st.localVars[CHOSEN_KEY] = targets[0]; } }); registerAfterHook(state, skill.id, ownerId, "阶段开始", async (ctx) => { const atom = ctx.atom; if (atom.type !== "阶段开始") return; if (atom.phase !== "回合结束") return; if (atom.player !== ownerId) return; const st = ctx.state; const self = st.players[ownerId]; if (!self?.alive) return; const myHand = self.hand.length; if (!st.players.some((p) => p.alive && p.index !== ownerId && p.hand.length <= myHand)) return; delete st.localVars[CONFIRMED_KEY$18]; await applyAtom(st, { type: "请求回应", requestType: CONFIRM_RT$19, target: ownerId, prompt: { type: "confirm", title: "是否发动绝策?(对一名手牌数≤你的其他角色造成1点伤害)", confirmLabel: "发动", cancelLabel: "不发动" }, defaultChoice: false, timeout: 15 }); if (!st.localVars[CONFIRMED_KEY$18]) { delete st.localVars[CONFIRMED_KEY$18]; return; } delete st.localVars[CONFIRMED_KEY$18]; delete st.localVars[CHOSEN_KEY]; await applyAtom(st, { type: "请求回应", requestType: CHOOSE_RT$6, target: ownerId, prompt: { type: "choosePlayer", title: "绝策:选择一名手牌数≤你的其他角色", min: 1, max: 1, filter: (_view, t) => t !== ownerId && st.players[t]?.alive === true && (st.players[t]?.hand.length ?? 0) <= myHand }, timeout: 20 }); const chosen = st.localVars[CHOSEN_KEY]; delete st.localVars[CHOSEN_KEY]; if (typeof chosen !== "number") return; if (!st.players[chosen]?.alive) return; await runDamageFlow(st, ownerId, chosen, 1); }); return () => {}; } function onMount$98(_skill, _api) {} var DISPLAY_NAME$21, CONFIRM_RT$19, CONFIRMED_KEY$18, CHOOSE_RT$6, CHOSEN_KEY, _skillModule$42; var init_界绝策 = __esmMin((() => { init_apply(); init_damage(); init_skill(); DISPLAY_NAME$21 = "绝策"; CONFIRM_RT$19 = "界绝策/confirm"; CONFIRMED_KEY$18 = "界绝策/confirmed"; CHOOSE_RT$6 = "界绝策/choose"; CHOSEN_KEY = "界绝策/target"; _skillModule$42 = { createSkill: createSkill$107, onInit: onInit$107, onMount: onMount$98 }; })); //#endregion //#region src/engine/skills/界缔盟.ts var 界缔盟_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$106, default: () => _skillModule$41, onInit: () => onInit$106, onMount: () => onMount$97 }); /** 鲁肃的总牌数(手牌+装备) */ function totalCardCount$1(state, player) { const p = state.players[player]; if (!p) return 0; return p.hand.length + Object.values(p.equipment).filter((id) => typeof id === "string").length; } function createSkill$106(id, ownerId) { return { id, ownerId, name: DISPLAY_NAME$20, description: "出牌阶段限一次,令两名角色交换手牌(差不大于你的牌数),出牌阶段结束时弃X张牌(X为手牌数差)" }; } function onInit$106(skill, state) { const ownerId = skill.ownerId; const unloaders = []; unloaders.push(registerAction(state, skill.id, ownerId, "use", (st, _params) => { if (st.currentPlayerIndex !== ownerId) return "不是你的回合"; if (st.phase !== "出牌") return "只能在出牌阶段发动"; if (hasBlockingPending(st)) return "当前有未完成的询问"; if (usedThisTurn$3(st, ownerId, "界缔盟")) return "本回合已使用过缔盟"; if (!st.players[ownerId]?.alive) return "角色不可用"; if (st.players.filter((p) => p.alive && p.index !== ownerId).length < 2) return "需要至少两名其他角色"; return null; }, async (st, _params) => { await markOncePerTurn(st, ownerId, "界缔盟"); await pushFrame(st, "界缔盟", ownerId, {}); delete st.localVars[TARGET_KEY$15]; await applyAtom(st, { type: "请求回应", requestType: TARGET_RT$11, target: ownerId, prompt: { type: "choosePlayer", title: "缔盟:选择两名其他角色交换手牌(差不大于你的牌数)", min: 2, max: 2, filter: (_view, t) => t !== ownerId && st.players[t]?.alive === true }, timeout: 30 }); const targets = st.localVars[TARGET_KEY$15]; delete st.localVars[TARGET_KEY$15]; if (!Array.isArray(targets) || targets.length !== 2) { await popFrame(st); return; } const [A, B] = targets; if (!st.players[A]?.alive || !st.players[B]?.alive) { await popFrame(st); return; } const diff = Math.abs(st.players[A].hand.length - st.players[B].hand.length); if (diff > totalCardCount$1(st, ownerId)) { await popFrame(st); return; } const handA = [...st.players[A].hand]; const handB = [...st.players[B].hand]; for (const cardId of handA) await applyAtom(st, { type: "移动牌", cardId, from: { zone: "手牌", player: A }, to: { zone: "手牌", player: B } }); for (const cardId of handB) await applyAtom(st, { type: "移动牌", cardId, from: { zone: "手牌", player: B }, to: { zone: "手牌", player: A } }); if (diff > 0) { st.localVars[PENDING_DISCARD_KEY] = diff; st.localVars[PENDING_OWNER_KEY] = ownerId; } await popFrame(st); })); unloaders.push(registerAction(state, skill.id, ownerId, "respond", (st, _params) => { const slot = st.pendingSlots.get(ownerId); if (!slot) return "当前不需要回应"; const atom = slot.atom; if (atom["type"] !== "请求回应") return "当前不需要回应"; const rt = atom["requestType"]; if (rt !== TARGET_RT$11 && rt !== DISCARD_RT$9) return "当前不是界缔盟询问"; return null; }, async (st, params) => { const rt = (st.pendingSlots.get(ownerId)?.atom)?.requestType; if (rt === TARGET_RT$11) { const targets = params.targets; if (Array.isArray(targets) && targets.length === 2) st.localVars[TARGET_KEY$15] = targets; } else if (rt === DISCARD_RT$9) { const ids = params.cardIds; if (Array.isArray(ids)) st.localVars[DISCARD_KEY$8] = ids; } })); unloaders.push(registerBeforeHook(state, skill.id, ownerId, "阶段结束", async (ctx) => { const atom = ctx.atom; if (atom.phase !== "出牌") return; if (atom.player !== ownerId) return; const st = ctx.state; const pendingX = st.localVars[PENDING_DISCARD_KEY]; const pendingOwner = st.localVars[PENDING_OWNER_KEY]; if (typeof pendingX !== "number" || pendingOwner !== ownerId) return; delete st.localVars[PENDING_DISCARD_KEY]; delete st.localVars[PENDING_OWNER_KEY]; if (!st.players[ownerId]?.alive) return; const actualDiscard = Math.min(pendingX, st.players[ownerId].hand.length); if (actualDiscard <= 0) return; delete st.localVars[DISCARD_KEY$8]; await applyAtom(st, { type: "请求回应", requestType: DISCARD_RT$9, target: ownerId, prompt: { type: "useCard", title: `缔盟:出牌阶段结束,弃置 ${actualDiscard} 张牌`, cardFilter: { filter: () => true, min: actualDiscard, max: actualDiscard } }, mandatory: true, timeout: 30 }); let discardCards = st.localVars[DISCARD_KEY$8]; delete st.localVars[DISCARD_KEY$8]; if ((!discardCards || discardCards.length === 0) && actualDiscard > 0) discardCards = st.players[ownerId]?.hand.slice(0, actualDiscard) ?? []; if (discardCards && discardCards.length > 0) await applyAtom(st, { type: "弃置", player: ownerId, cardIds: discardCards, voluntary: true }); })); return () => unloaders.forEach((fn) => fn()); } function onMount$97(_skill, api) { api.defineAction("use", { label: DISPLAY_NAME$20, style: "primary", prompt: { type: "confirm", title: "缔盟:选择两名其他角色交换手牌", confirmLabel: "发动", cancelLabel: "取消" }, activeWhen: (ctx) => activeCheck$1(ctx) }); api.defineAction("respond", { label: DISPLAY_NAME$20, style: "primary", prompt: { type: "confirm", title: DISPLAY_NAME$20, confirmLabel: "确认", cancelLabel: "取消" } }); return () => {}; } var TARGET_RT$11, DISCARD_RT$9, TARGET_KEY$15, DISCARD_KEY$8, PENDING_DISCARD_KEY, PENDING_OWNER_KEY, DISPLAY_NAME$20, activeCheck$1, _skillModule$41; var init_界缔盟 = __esmMin((() => { init_apply(); init_frame(); init_once_per_turn(); init_skill(); TARGET_RT$11 = "界缔盟/target"; DISCARD_RT$9 = "界缔盟/discard"; TARGET_KEY$15 = "界缔盟/targets"; DISCARD_KEY$8 = "界缔盟/discardCards"; PENDING_DISCARD_KEY = "界缔盟/待弃数"; PENDING_OWNER_KEY = "界缔盟/待弃归属"; DISPLAY_NAME$20 = "缔盟"; activeCheck$1 = activeUnlessUsedThisTurn("界缔盟"); _skillModule$41 = { createSkill: createSkill$106, onInit: onInit$106, onMount: onMount$97 }; })); //#endregion //#region src/engine/skills/界翦灭.ts var 界翦灭_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$105, default: () => _skillModule$40, onInit: () => onInit$105, onMount: () => onMount$96 }); /** 由 confirm 选择推出颜色:true→红,false/超时→黑。 */ function colorFromChoice(v) { return v === true ? "红" : "黑"; } function createSkill$105(id, ownerId) { return { id, ownerId, name: DISPLAY_NAME$19, description: "出牌阶段限一次:与一名其他角色各选一种颜色,各自弃置所选颜色的手牌,弃牌较多者视为对另一方使用决斗" }; } function onInit$105(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "use", (st, params) => { if (st.currentPlayerIndex !== ownerId) return "不是你的回合"; if (st.phase !== "出牌") return "只能在出牌阶段发动"; if (hasBlockingPending(st)) return "当前有未完成的询问"; if (usedThisTurn$3(st, ownerId, SKILL_ID$16)) return "本回合已使用过翦灭"; const self = st.players[ownerId]; if (!self?.alive) return "玩家不存在或已死亡"; if (self.hand.length === 0) return "你需要有手牌才能发动翦灭"; const target = params.target; if (typeof target !== "number") return "需要指定一名目标"; if (target === ownerId) return "不能以自己为目标"; const tp = st.players[target]; if (!tp?.alive) return "目标不存在或已死亡"; if (tp.hand.length === 0) return "目标没有手牌"; return null; }, async (st, params) => { const from = ownerId; const target = params.target; await markOncePerTurn(st, from, SKILL_ID$16); await pushFrame(st, SKILL_ID$16, from, { ...params }); try { delete st.localVars[OWNER_COLOR_KEY]; await applyAtom(st, { type: "请求回应", requestType: OWNER_COLOR_RT, target: from, prompt: { type: "confirm", title: "翦灭:选择你的颜色(确认=红色,取消=黑色)", confirmLabel: "红色", cancelLabel: "黑色" }, defaultChoice: false, timeout: 15 }); const ownerColor = colorFromChoice(st.localVars[OWNER_COLOR_KEY]); delete st.localVars[TARGET_COLOR_KEY]; await applyAtom(st, { type: "请求回应", requestType: TARGET_COLOR_RT, target, prompt: { type: "confirm", title: `翦灭:${st.players[from].name} 与你各选一色,各自弃该色手牌,多者对少者决斗。选择你的颜色(确认=红色,取消=黑色)`, confirmLabel: "红色", cancelLabel: "黑色" }, defaultChoice: false, timeout: 15 }); const targetColor = colorFromChoice(st.localVars[TARGET_COLOR_KEY]); const ownerCards = colorCards(st, from, ownerColor); const targetCards = colorCards(st, target, targetColor); if (ownerCards.length > 0) await applyAtom(st, { type: "弃置", player: from, cardIds: ownerCards }); if (targetCards.length > 0) await applyAtom(st, { type: "弃置", player: target, cardIds: targetCards }); const SKILL = SKILL_ID$16; if (ownerCards.length > targetCards.length) { if (st.players[from]?.alive && st.players[target]?.alive) { const vId = `${SKILL}:决斗:${from}:${target}:${st.seq}`; st.cardMap[vId] = { id: vId, name: "决斗", suit: "", color: "无色", rank: "A", type: "锦囊牌" }; await runUseFlow(st, from, vId, [target], "决斗", { virtual: true }); delete st.cardMap[vId]; } } else if (targetCards.length > ownerCards.length) { if (st.players[from]?.alive && st.players[target]?.alive) { const vId = `${SKILL}:决斗:${target}:${from}:${st.seq}`; st.cardMap[vId] = { id: vId, name: "决斗", suit: "", color: "无色", rank: "A", type: "锦囊牌" }; await runUseFlow(st, target, vId, [from], "决斗", { virtual: true }); delete st.cardMap[vId]; } } } finally { await popFrame(st); } }); const unloaders = []; for (const p of state.players) { const pid = p.index; const u = registerAction(state, skill.id, pid, "respond", (st, _params) => { const slot = st.pendingSlots.get(pid); if (!slot) return "当前不需要回应"; const atom = slot.atom; if (atom.type !== "请求回应") return "当前不需要回应"; if (atom.requestType !== OWNER_COLOR_RT && atom.requestType !== TARGET_COLOR_RT) return "当前不是翦灭选色"; return null; }, async (st, params) => { const slot = st.pendingSlots.get(pid); if (!slot) return; const atom = slot.atom; const choice = params.choice === true || params.confirmed === true; if (atom.requestType === OWNER_COLOR_RT) { if (pid !== ownerId) return; st.localVars[OWNER_COLOR_KEY] = choice; } else if (atom.requestType === TARGET_COLOR_RT) st.localVars[TARGET_COLOR_KEY] = choice; }); unloaders.push(u); } return () => { unloaders.forEach((u) => u()); }; } /** 取某玩家手中指定颜色的所有牌 id。 */ function colorCards(state, playerId, color) { const p = state.players[playerId]; if (!p) return []; const result = []; for (const id of p.hand) if (state.cardMap[id]?.color === color) result.push(id); return result; } function onMount$96(skill, api) { api.defineAction("use", { label: DISPLAY_NAME$19, style: "danger", prompt: { type: "selectTarget", title: "翦灭:选择一名其他角色,各选一色弃牌,多者对少者决斗", description: "出牌阶段限一次;你和目标都需要有手牌", targetFilter: { min: 1, max: 1, filter: (view, t) => { if (t === view.currentPlayerIndex) return false; const tp = view.players[t]; if (!tp || tp.alive === false) return false; return (tp.handCount ?? 0) > 0; } } }, activeWhen: (ctx) => activeUnlessUsedThisTurn(SKILL_ID$16)(ctx) && (ctx.view.players[ctx.perspectiveIdx]?.hand?.length ?? 0) > 0 }); api.defineAction("respond", { label: DISPLAY_NAME$19, style: "primary", prompt: { type: "confirm", title: "翦灭:选择你的颜色(确认=红色,取消=黑色)", confirmLabel: "红色", cancelLabel: "黑色" } }); } var SKILL_ID$16, DISPLAY_NAME$19, OWNER_COLOR_RT, TARGET_COLOR_RT, OWNER_COLOR_KEY, TARGET_COLOR_KEY, _skillModule$40; var init_界翦灭 = __esmMin((() => { init_apply(); init_frame(); init_once_per_turn(); init_skill(); init_use_card(); SKILL_ID$16 = "界翦灭"; DISPLAY_NAME$19 = "翦灭"; OWNER_COLOR_RT = `${SKILL_ID$16}/我选色`; TARGET_COLOR_RT = `${SKILL_ID$16}/敌选色`; OWNER_COLOR_KEY = `${SKILL_ID$16}/ownerColor`; TARGET_COLOR_KEY = `${SKILL_ID$16}/targetColor`; _skillModule$40 = { createSkill: createSkill$105, onInit: onInit$105, onMount: onMount$96 }; })); //#endregion //#region src/engine/skills/界耀武.ts var 界耀武_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$104, default: () => _skillModule$39, onInit: () => onInit$104, onMount: () => onMount$95 }); function createSkill$104(id, ownerId) { return { id, ownerId, name: DISPLAY_NAME$18, description: "锁定技,当你受到伤害时,若造成伤害的牌为红色,来源摸一张牌;否则你摸一张牌", isLocked: true }; } function onInit$104(skill, state) { const ownerId = skill.ownerId; const unloads = []; unloads.push(registerAfterHook(state, skill.id, ownerId, "受到伤害后", async (ctx) => { const atom = ctx.atom; if (atom.target !== ownerId) return; if ((atom.amount ?? 0) <= 0) return; const damageCardId = atom.cardId; const damageCard = damageCardId ? ctx.state.cardMap[damageCardId] : void 0; if (!!damageCard && damageCard.color === "红") { const source = atom.source; const sourceAlive = typeof source === "number" && ctx.state.players[source]?.alive; await applyAtom(ctx.state, { type: "摸牌", player: sourceAlive ? source : ownerId, count: 1 }); } else await applyAtom(ctx.state, { type: "摸牌", player: ownerId, count: 1 }); })); return () => { for (const u of unloads) u(); }; } function onMount$95(_skill, api) {} var DISPLAY_NAME$18, _skillModule$39; var init_界耀武 = __esmMin((() => { init_apply(); init_skill(); DISPLAY_NAME$18 = "耀武"; _skillModule$39 = { createSkill: createSkill$104, onInit: onInit$104, onMount: onMount$95 }; })); //#endregion //#region src/engine/skills/界自守.ts var 界自守_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$103, default: () => _skillModule$38, onInit: () => onInit$103, onMount: () => onMount$94 }); function createSkill$103(id, ownerId) { return { id, ownerId, name: DISPLAY_NAME$17, description: "摸牌阶段可多摸X张(X为全场势力数);若本回合对其他角色造成过伤害,结束阶段弃X张" }; } /** 计算全场存活玩家的不同势力数(X)。 */ function countFactions(state) { const factions = /* @__PURE__ */ new Set(); for (const p of state.players) { if (!p.alive) continue; if (p.faction) factions.add(p.faction); } return factions.size; } /** 当前 pending 的 requestType(类型安全读取) */ function currentRequestType$6(state, ownerId) { const slot = state.pendingSlots.get(ownerId); if (!slot) return void 0; return slot.atom.requestType; } function onInit$103(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "respond", (st, params) => { const rt = currentRequestType$6(st, ownerId); if (rt !== CONFIRM_RT$18 && rt !== DISCARD_RT$8) return "当前不是自守询问"; if (rt === CONFIRM_RT$18) return null; const cardIds = params.cardIds; if (!Array.isArray(cardIds) || cardIds.length === 0) return "请选择要弃置的牌"; const self = st.players[ownerId]; if (!self) return "玩家不存在"; const seen = /* @__PURE__ */ new Set(); for (const id of cardIds) { if (typeof id !== "string" || !self.hand.includes(id)) return `牌 ${id} 不在手牌中`; if (seen.has(id)) return "不能选择重复的牌"; seen.add(id); } return null; }, async (st, params) => { const rt = currentRequestType$6(st, ownerId); if (rt === CONFIRM_RT$18) st.localVars[CONFIRMED_KEY$17] = params.choice === true || params.confirmed === true; else if (rt === DISCARD_RT$8) { const ids = params.cardIds; if (Array.isArray(ids)) st.localVars[DISCARD_KEY$7] = ids; } }); registerBeforeHook(state, skill.id, ownerId, "摸牌", async (ctx) => { const atom = ctx.atom; if (atom.player !== ownerId) return; if (ctx.state.currentPlayerIndex !== ownerId) return; if (ctx.state.phase !== "摸牌") return; if (!ctx.state.players[ownerId]?.alive) return; const x = countFactions(ctx.state); if (x <= 0) return; delete ctx.state.localVars[CONFIRMED_KEY$17]; await applyAtom(ctx.state, { type: "请求回应", requestType: CONFIRM_RT$18, target: ownerId, prompt: { type: "confirm", title: `是否发动自守?(多摸 ${x} 张牌;若本回合对其他角色造成过伤害,结束阶段弃 ${x} 张)`, confirmLabel: "发动", cancelLabel: "不发动" }, defaultChoice: false, timeout: 15 }); if (!ctx.state.localVars[CONFIRMED_KEY$17]) return; ctx.state.turn.vars[ACTIVE_VAR$1] = true; const count = atom.count ?? 2; return { kind: "modify", atom: { ...ctx.atom, count: count + x } }; }); registerAfterHook(state, skill.id, ownerId, "造成伤害后", async (ctx) => { const atom = ctx.atom; if (atom.source !== ownerId) return; if (atom.target === void 0 || atom.target === ownerId) return; if ((atom.amount ?? 0) <= 0) return; ctx.state.turn.vars[DMG_VAR] = true; }); registerAfterHook(state, skill.id, ownerId, "阶段开始", async (ctx) => { const atom = ctx.atom; if (atom.type !== "阶段开始") return; if (atom.phase !== "回合结束") return; if (atom.player !== ownerId) return; if (!ctx.state.turn.vars[ACTIVE_VAR$1]) return; if (!ctx.state.turn.vars[DMG_VAR]) return; const self = ctx.state.players[ownerId]; if (!self?.alive) return; const x = countFactions(ctx.state); if (x <= 0) return; const handCount = self.hand.length; if (handCount === 0) return; const discardCount = Math.min(x, handCount); delete ctx.state.localVars[DISCARD_KEY$7]; await applyAtom(ctx.state, { type: "请求回应", requestType: DISCARD_RT$8, target: ownerId, prompt: { type: "useCard", title: `自守:本回合对其他角色造成过伤害,弃 ${discardCount} 张牌`, cardFilter: { filter: () => true, min: discardCount, max: discardCount } }, mandatory: true, timeout: 30 }); let cardIds = ctx.state.localVars[DISCARD_KEY$7]; delete ctx.state.localVars[DISCARD_KEY$7]; if ((!cardIds || cardIds.length === 0) && discardCount > 0) cardIds = ctx.state.players[ownerId]?.hand.slice(0, discardCount) ?? []; if (cardIds && cardIds.length > 0) await applyAtom(ctx.state, { type: "弃置", player: ownerId, cardIds }); }); return () => {}; } function onMount$94(_skill, api) { api.defineAction("respond", { label: "自守", style: "primary", prompt: { type: "confirm", title: "是否发动自守?", confirmLabel: "发动", cancelLabel: "不发动" } }); return () => {}; } var DISPLAY_NAME$17, CONFIRMED_KEY$17, CONFIRM_RT$18, DISCARD_RT$8, ACTIVE_VAR$1, DMG_VAR, DISCARD_KEY$7, _skillModule$38; var init_界自守 = __esmMin((() => { init_apply(); init_skill(); DISPLAY_NAME$17 = "自守"; CONFIRMED_KEY$17 = "自守/confirmed"; CONFIRM_RT$18 = "界自守/confirm"; DISCARD_RT$8 = "界自守/弃牌"; ACTIVE_VAR$1 = "自守/active"; DMG_VAR = "自守/damageDealt"; DISCARD_KEY$7 = "自守/discardCards"; _skillModule$38 = { createSkill: createSkill$103, onInit: onInit$103, onMount: onMount$94 }; })); //#endregion //#region src/engine/skills/界节命.ts var 界节命_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$102, default: () => 界节命_default, onInit: () => onInit$102, onMount: () => onMount$93 }); function createSkill$102(id, ownerId) { return { id, ownerId, name: "界节命", description: "受到1点伤害后或死亡时,令一名角色摸X张牌,然后将手牌弃至X张(X为其体力上限且至多为5)" }; } /** 执行节命主体:询问发动 → 选目标 → 目标摸 X 张 → 目标弃至 X 张。 */ async function runJieMing(state, ownerId) { delete state.localVars[CONFIRMED_KEY$16]; await applyAtom(state, { type: "请求回应", requestType: CONFIRM_RT$17, target: ownerId, prompt: { type: "confirm", title: "是否发动节命?(令一名角色摸X张牌,然后将手牌弃至X张)", confirmLabel: "发动", cancelLabel: "不发动" }, defaultChoice: false, timeout: 10 }); if (!state.localVars[CONFIRMED_KEY$16]) return; delete state.localVars[TARGET_KEY$14]; await applyAtom(state, { type: "请求回应", requestType: TARGET_RT$10, target: ownerId, prompt: { type: "choosePlayer", title: "节命:选择一名角色(摸X张牌,然后将手牌弃至X张)", min: 1, max: 1, filter: (_view, t) => state.players[t]?.alive === true }, timeout: 15 }); const target = state.localVars[TARGET_KEY$14]; delete state.localVars[TARGET_KEY$14]; if (typeof target !== "number") return; const targetPlayer = state.players[target]; if (!targetPlayer?.alive) return; const x = Math.min(targetPlayer.maxHealth, 5); if (x > 0) await applyAtom(state, { type: "摸牌", player: target, count: x }); const excess = (state.players[target]?.hand ?? []).length - x; if (excess > 0) { delete state.localVars[DISCARD_KEY$6]; await applyAtom(state, { type: "请求回应", requestType: DISCARD_RT$7, target, prompt: { type: "useCard", title: `节命:需弃置 ${excess} 张手牌(将手牌弃至 ${x} 张)`, cardFilter: { filter: () => true, min: excess, max: excess } }, mandatory: true, timeout: 15 }); let discardIds = state.localVars[DISCARD_KEY$6]; delete state.localVars[DISCARD_KEY$6]; if ((!discardIds || discardIds.length === 0) && excess > 0) discardIds = state.players[target]?.hand.slice(0, excess) ?? []; if (discardIds && discardIds.length > 0) await applyAtom(state, { type: "弃置", player: target, cardIds: discardIds }); } } function onInit$102(skill, state) { const ownerId = skill.ownerId; const unloaders = []; for (const p of state.players) { const seatId = p.index; const off = registerAction(state, skill.id, seatId, "respond", (st, params) => { const slot = st.pendingSlots.get(seatId); if (!slot) return "当前不需要回应"; const atom = slot.atom; if (atom["type"] !== "请求回应") return "当前不需要回应"; const rt = atom["requestType"]; if (rt !== CONFIRM_RT$17 && rt !== TARGET_RT$10 && rt !== DISCARD_RT$7) return "当前不是节命询问"; if (rt === DISCARD_RT$7) { const cardIds = Array.isArray(params.cardIds) ? params.cardIds : typeof params.cardId === "string" ? [params.cardId] : []; if (cardIds.length === 0) return "请选择要弃置的牌"; const self = st.players[seatId]; if (!self?.alive) return "你已死亡"; for (const id of cardIds) if (!self.hand.includes(id)) return "弃置的牌必须在手牌中"; const x = Math.min(self.maxHealth, 5); const excess = self.hand.length - x; if (excess <= 0) return "当前不需要弃牌"; if (cardIds.length !== excess) return `必须弃置 ${excess} 张牌`; } return null; }, async (st, params) => { const rt = (st.pendingSlots.get(seatId)?.atom)?.requestType; if (rt === CONFIRM_RT$17) st.localVars[CONFIRMED_KEY$16] = params.choice === true; else if (rt === TARGET_RT$10) { const t = params.targets?.[0] ?? (typeof params.target === "number" ? params.target : void 0); if (typeof t === "number") st.localVars[TARGET_KEY$14] = t; } else if (rt === DISCARD_RT$7) { const cardIds = Array.isArray(params.cardIds) ? params.cardIds : typeof params.cardId === "string" ? [params.cardId] : []; if (cardIds.length > 0) st.localVars[DISCARD_KEY$6] = cardIds; } }); unloaders.push(off); } unloaders.push(registerAfterHook(state, skill.id, ownerId, "受到伤害后", async (ctx) => { const atom = ctx.atom; if (atom.target !== ownerId) return; if ((atom.amount ?? 0) <= 0) return; if (!ctx.state.players[ownerId]?.alive) return; ctx.state.localVars[DAMAGE_HANDLED_KEY] = true; await runJieMing(ctx.state, ownerId); })); unloaders.push(registerAfterHook(state, skill.id, ownerId, "死亡时", async (ctx) => { const atom = ctx.atom; if (atom.type !== "死亡时") return; if (atom.player !== ownerId) return; if (ctx.state.localVars[DAMAGE_HANDLED_KEY]) { delete ctx.state.localVars[DAMAGE_HANDLED_KEY]; return; } await runJieMing(ctx.state, ownerId); })); unloaders.push(registerAfterHook(state, skill.id, ownerId, "伤害结算结束后", async (ctx) => { if (ctx.atom.target !== ownerId) return; delete ctx.state.localVars[DAMAGE_HANDLED_KEY]; })); return () => { for (const off of unloaders) off(); }; } function onMount$93(_skill, api) { api.defineAction("respond", { label: "界节命", style: "primary", prompt: { type: "confirm", title: "是否发动节命?", confirmLabel: "发动", cancelLabel: "不发动" } }); } var CONFIRM_RT$17, TARGET_RT$10, DISCARD_RT$7, CONFIRMED_KEY$16, TARGET_KEY$14, DISCARD_KEY$6, DAMAGE_HANDLED_KEY, 界节命_default; var init_界节命 = __esmMin((() => { init_apply(); init_skill(); CONFIRM_RT$17 = "界节命/confirm"; TARGET_RT$10 = "界节命/target"; DISCARD_RT$7 = "界节命/discard"; CONFIRMED_KEY$16 = "节命/confirmed"; TARGET_KEY$14 = "节命/target"; DISCARD_KEY$6 = "节命/discardCards"; DAMAGE_HANDLED_KEY = "节命/伤害已触发"; 界节命_default = { createSkill: createSkill$102, onInit: onInit$102, onMount: onMount$93 }; })); //#endregion //#region src/engine/skills/界若愚.ts var 界若愚_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$101, default: () => 界若愚_default, onInit: () => onInit$101, onMount: () => onMount$92 }); function createSkill$101(id, ownerId) { return { id, ownerId, name: "界若愚", description: "主公技/觉醒技:回合开始且体力全场最少时,增加1体力上限、回复至3点体力并永久获得\"激将\"" }; } function onInit$101(skill, state) { const ownerId = skill.ownerId; registerAfterHook(state, skill.id, ownerId, "回合开始", async (ctx) => { if (ctx.atom.player !== ownerId) return; if (ctx.state.players[ownerId]?.vars[AWAKENED_KEY$3]) return; const self = ctx.state.players[ownerId]; if (!self?.alive) return; if (ownerId !== 0) return; const alivePlayers = ctx.state.players.filter((p) => p.alive); if (alivePlayers.length === 0) return; const minHealth = Math.min(...alivePlayers.map((p) => getHealthValue(p))); if (getHealthValue(self) > minHealth) return; ctx.state.players[ownerId].vars[AWAKENED_KEY$3] = true; await applyAtom(ctx.state, { type: "设上限", player: ownerId, amount: self.maxHealth + 1 }); const healAmount = Math.max(0, TARGET_HEALTH - self.health); if (healAmount > 0) await applyAtom(ctx.state, { type: "回复体力", target: ownerId, amount: healAmount }); await applyAtom(ctx.state, { type: "添加技能", player: ownerId, skillId: "激将" }); }); return () => {}; } function onMount$92(_skill, _api) {} var AWAKENED_KEY$3, TARGET_HEALTH, 界若愚_default; var init_界若愚 = __esmMin((() => { init_types(); init_apply(); init_skill(); AWAKENED_KEY$3 = "界若愚/awakened"; TARGET_HEALTH = 3; 界若愚_default = { createSkill: createSkill$101, onInit: onInit$101, onMount: onMount$92 }; })); //#endregion //#region src/engine/skills/界苦肉.ts var 界苦肉_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$100, default: () => _skillModule$37, onInit: () => onInit$100, onMount: () => onMount$91 }); function createSkill$100(id, ownerId) { return { id, ownerId, name: DISPLAY_NAME$16, description: "出牌阶段限一次,你可以弃置一张牌,然后失去1点体力。" }; } function onInit$100(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "use", (state, params) => { if (state.currentPlayerIndex !== ownerId) return "不是你的回合"; if (state.phase !== "出牌") return "只能在出牌阶段发动"; if (usedThisTurn$3(state, ownerId, SKILL_ID$15)) return "本回合已使用过苦肉"; const self = state.players[ownerId]; if (!self?.alive) return "玩家不存在或已死亡"; if (self.health <= 0) return "体力不足,无法发动苦肉"; const cardIds = params.cardIds ?? (typeof params.cardId === "string" ? [params.cardId] : void 0); if (!Array.isArray(cardIds) || cardIds.length === 0) return "需选择一张牌弃置"; if (cardIds.length > 1) return "苦肉只能弃置一张牌"; const cardId = cardIds[0]; const inHand = self.hand.includes(cardId); const inEquip = Object.values(self.equipment).includes(cardId); if (!inHand && !inEquip) return "牌不在手牌或装备区中"; return null; }, async (state, params) => { const from = ownerId; await markOncePerTurn(state, from, SKILL_ID$15); const cardIds = params.cardIds ?? (typeof params.cardId === "string" ? [params.cardId] : []); await pushFrame(state, DISPLAY_NAME$16, from, {}); await applyAtom(state, { type: "弃置", player: from, cardIds, voluntary: true }); await applyAtom(state, { type: "失去体力", target: from, amount: 1 }); await popFrame(state); }); return () => {}; } function onMount$91(skill, api) { api.defineAction("use", { label: DISPLAY_NAME$16, style: "primary", prompt: { type: "distribute", mode: "select", title: "苦肉:弃置一张牌,然后失去1点体力(出牌阶段限一次)", source: "handAndEquip", minTotal: 1, maxTotal: 1 }, activeWhen: (ctx) => { const me = ctx.view.players[ctx.perspectiveIdx]; const hasCard = (me?.handCount ?? 0) + Object.values(me?.equipment ?? {}).length > 0; return activeCheck(ctx) && hasCard && (me?.health ?? 0) > 0; } }); return () => {}; } var SKILL_ID$15, DISPLAY_NAME$16, activeCheck, _skillModule$37; var init_界苦肉 = __esmMin((() => { init_apply(); init_frame(); init_once_per_turn(); init_skill(); SKILL_ID$15 = "界苦肉"; DISPLAY_NAME$16 = "苦肉"; activeCheck = activeUnlessUsedThisTurn(SKILL_ID$15); _skillModule$37 = { createSkill: createSkill$100, onInit: onInit$100, onMount: onMount$91 }; })); //#endregion //#region src/engine/skills/界英姿.ts var 界英姿_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$99, default: () => 界英姿_default, onInit: () => onInit$99, onMount: () => onMount$90 }); function createSkill$99(id, ownerId) { return { id, ownerId, name: "界英姿", description: "锁定技:摸牌阶段你多摸一张牌;你的手牌上限为你的体力上限", isLocked: true }; } function onInit$99(skill, state) { const ownerId = skill.ownerId; const unloadHook = registerBeforeHook(state, skill.id, ownerId, "摸牌", async (ctx) => { const atom = ctx.atom; if (atom.player !== ownerId) return; if (ctx.state.currentPlayerIndex !== ownerId) return; if (ctx.state.phase !== "摸牌") return; if (!ctx.state.players[ownerId]?.alive) return; const count = atom.count ?? 2; return { kind: "modify", atom: { ...ctx.atom, count: count + 1 } }; }); const unloadProvider = registerHandLimitProvider(state, ownerId, (st, player) => { return st.players[player]?.maxHealth; }); return () => { unloadHook(); unloadProvider(); }; } function onMount$90(_skill, _api) { return () => {}; } var 界英姿_default; var init_界英姿 = __esmMin((() => { init_skill(); init_hand_limit(); 界英姿_default = { createSkill: createSkill$99, onInit: onInit$99, onMount: onMount$90 }; })); //#endregion //#region src/engine/skills/界荐言.ts var 界荐言_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$98, default: () => _skillModule$36, onInit: () => onInit$98, onMount: () => onMount$89 }); function createSkill$98(id, ownerId) { return { id, ownerId, name: DISPLAY_NAME$15, description: "出牌阶段限一次:声明一种牌的类别或颜色,连续亮出牌堆顶的牌直到亮出符合声明的牌,选择一名男性角色获得此牌,其余置入弃牌堆" }; } /** 是否为合法声明 */ function isValidDeclaration(d) { return typeof d === "string" && VALID_DECLARATIONS.includes(d); } /** 牌是否符合声明 */ function cardMatches(card, declaration) { if (declaration === "基本牌" || declaration === "锦囊牌" || declaration === "装备牌") return card.type === declaration; return card.color === declaration; } /** 校验某座次是否为男性存活角色 */ function isMaleAlive$1(state, target) { const p = state.players[target]; if (!p?.alive) return false; return getGender(p.character) === "男"; } /** 本回合是否已用过荐言 */ function usedThisTurn(state, ownerId) { return !!state.players[ownerId]?.vars[USED_KEY$3]; } function onInit$98(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "respond", (st, params) => { const slot = st.pendingSlots.get(ownerId); if (!slot) return "当前不需要回应"; if (slot.atom.type !== "请求回应") return "当前不需要回应"; const rt = slot.atom.requestType; if (rt !== DECLARE_RT && rt !== TARGET_RT$9) return "当前不是荐言询问"; if (rt === DECLARE_RT) { if (!isValidDeclaration(params.option ?? params.declaration)) return "声明不合法(基本牌/锦囊牌/装备牌/红/黑)"; } else if (rt === TARGET_RT$9) { const t = params.target ?? (Array.isArray(params.targets) ? params.targets[0] : void 0); if (typeof t !== "number") return "需要选择目标"; if (!isMaleAlive$1(st, t)) return "目标必须是男性角色"; } return null; }, async (st, params) => { const rt = (st.pendingSlots.get(ownerId)?.atom)?.requestType; if (rt === DECLARE_RT) { const decl = params.option ?? params.declaration; if (isValidDeclaration(decl)) st.localVars[DECLARATION_KEY] = decl; } else if (rt === TARGET_RT$9) { const t = params.target ?? (Array.isArray(params.targets) ? params.targets[0] : void 0); if (typeof t === "number") st.localVars[TARGET_KEY$13] = t; } }); registerAction(state, skill.id, ownerId, "use", (st, _params) => { if (!st.players[ownerId]?.alive) return "角色不可用"; if (st.currentPlayerIndex !== ownerId) return "只能在你的回合使用"; if (st.phase !== "出牌") return "只能在出牌阶段使用"; if (hasBlockingPending(st)) return "当前有未完成的询问"; if (usedThisTurn(st, ownerId)) return "本回合已使用过荐言"; if (!st.players.some((_p, i) => isMaleAlive$1(st, i))) return "无男性角色可选"; if (st.zones.deck.length === 0 && st.zones.discardPile.length === 0) return "牌堆已空"; return null; }, async (st, _params) => { const from = ownerId; st.players[from].vars[USED_KEY$3] = true; await applyAtom(st, { type: "回合用量", player: from, key: USED_KEY$3, value: true }); await pushFrame(st, SKILL_ID$14, from, {}); try { delete st.localVars[DECLARATION_KEY]; await applyAtom(st, { type: "请求回应", requestType: DECLARE_RT, target: ownerId, prompt: { type: "chooseOption", title: "荐言:声明一种牌的类别(基本牌/锦囊牌/装备牌)或颜色(红/黑)", description: "选择类别:基本牌、锦囊牌、装备牌;或颜色:红、黑。连续亮出牌堆顶直到出现符合声明的牌。", options: VALID_DECLARATIONS.map((d) => ({ value: d, label: d })) }, defaultChoice: "基本牌", timeout: 30 }); const rawDeclaration = st.localVars[DECLARATION_KEY]; const declaration = isValidDeclaration(rawDeclaration) ? rawDeclaration : "基本牌"; delete st.localVars[DECLARATION_KEY]; delete st.localVars[TARGET_KEY$13]; await applyAtom(st, { type: "请求回应", requestType: TARGET_RT$9, target: ownerId, prompt: { type: "choosePlayer", title: "荐言:选择一名男性角色获得匹配的牌", min: 1, max: 1, filter: (_view, t) => isMaleAlive$1(st, t) }, timeout: 20 }); const target = st.localVars[TARGET_KEY$13]; delete st.localVars[TARGET_KEY$13]; if (typeof target !== "number" || !isMaleAlive$1(st, target)) return; const revealed = []; let matchId; while (st.zones.deck.length > 0) { const topId = st.zones.deck[st.zones.deck.length - 1]; await applyAtom(st, { type: "移动牌", cardId: topId, from: { zone: "牌堆" }, to: { zone: "处理区" } }); revealed.push(topId); const card = st.cardMap[topId]; if (card && cardMatches(card, declaration)) { matchId = topId; break; } } if (matchId) await applyAtom(st, { type: "移动牌", cardId: matchId, from: { zone: "处理区" }, to: { zone: "手牌", player: target } }); const leftover = frameCards(st).filter((id) => revealed.includes(id)); for (const id of leftover) await applyAtom(st, { type: "移动牌", cardId: id, from: { zone: "处理区" }, to: { zone: "弃牌堆" } }); } finally { await popFrame(st); } }); return () => {}; } function onMount$89(_skill, api) { api.defineAction("use", { label: DISPLAY_NAME$15, style: "primary", activeWhen: (ctx) => { if (!defaultPlayActive(ctx)) return false; return !ctx.view.players[ctx.perspectiveIdx]?.turnUsage?.[USED_KEY$3]; }, prompt: { type: "confirm", title: "是否发动荐言?", confirmLabel: "发动", cancelLabel: "不发动" } }); } var SKILL_ID$14, DISPLAY_NAME$15, USED_KEY$3, DECLARE_RT, TARGET_RT$9, DECLARATION_KEY, TARGET_KEY$13, VALID_DECLARATIONS, _skillModule$36; var init_界荐言 = __esmMin((() => { init_apply(); init_frame(); init_skill(); init_character_meta(); init_action_active(); SKILL_ID$14 = "界荐言"; DISPLAY_NAME$15 = "荐言"; USED_KEY$3 = `${SKILL_ID$14}/usedThisTurn`; DECLARE_RT = `${SKILL_ID$14}/declare`; TARGET_RT$9 = `${SKILL_ID$14}/target`; DECLARATION_KEY = `${SKILL_ID$14}/declaration`; TARGET_KEY$13 = `${SKILL_ID$14}/target`; VALID_DECLARATIONS = [ "基本牌", "锦囊牌", "装备牌", "红", "黑" ]; _skillModule$36 = { createSkill: createSkill$98, onInit: onInit$98, onMount: onMount$89 }; })); //#endregion //#region src/engine/skills/界落英.ts var 界落英_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$97, default: () => _skillModule$35, onInit: () => onInit$97, onMount: () => onMount$88 }); function createSkill$97(id, ownerId) { return { id, ownerId, name: DISPLAY_NAME$14, description: "其他角色的梅花牌因弃置或判定而置入弃牌堆后,你可以获得其中任意张牌" }; } /** 当前回合外累计获得的"落英"牌数 */ function outsideGainCount(state, ownerId) { const v = state.players[ownerId]?.vars[OUTSIDE_GAIN_COUNT_KEY]; return typeof v === "number" ? v : 0; } /** 判断一张牌是否为梅花 */ function isClubCard(state, cardId) { const card = state.cardMap[cardId]; return !!card && card.suit === "♣"; } /** * 询问并获取一组梅花牌(在弃牌堆或处理区)。每张经 移动牌 atom 入曹植手牌。 * 返回实际获得的张数。 * * 注:不 pushFrame——pushFrame 会令 frameCards() 返回新空帧而非判定牌所在区, * 导致后续 includes 校验失败。天妒/界奸雄 同样不 pushFrame。 */ async function askAndGain(state, ownerId, clubCardIds, fromZone) { if (clubCardIds.length === 0) return 0; if (!state.players[ownerId]?.alive) return 0; delete state.localVars[CHOICE_KEY$2]; await applyAtom(state, { type: "请求回应", requestType: CHOOSE_RT$5, target: ownerId, prompt: { type: "confirm", title: `落英:是否获得 ${clubCardIds.length} 张梅花牌?`, confirmLabel: "获得", cancelLabel: "不获得" }, defaultChoice: false, timeout: 10 }); const want = state.localVars[CHOICE_KEY$2] === true; delete state.localVars[CHOICE_KEY$2]; if (!want) return 0; let gained = 0; for (const cardId of clubCardIds) { if (fromZone === "弃牌堆") { if (!state.zones.discardPile.includes(cardId)) continue; await applyAtom(state, { type: "移动牌", cardId, from: { zone: "弃牌堆" }, to: { zone: "手牌", player: ownerId } }); } else { if (!frameCards(state).includes(cardId)) continue; await applyAtom(state, { type: "移动牌", cardId, from: { zone: "处理区" }, to: { zone: "手牌", player: ownerId } }); } gained += 1; } return gained; } /** * 落英获得后:若在回合外,累加"英"计数;若达到体力上限且武将牌背面朝上, * 触发界酒诗的"翻回正面 2"询问。本函数内联翻回询问,避免界酒诗二次检测。 */ async function recordGainAndMaybeFlipBack(state, ownerId, gainedCount) { if (gainedCount <= 0) return; if (state.currentPlayerIndex === ownerId) return; const self = state.players[ownerId]; if (!self?.alive) return; const newCount = outsideGainCount(state, ownerId) + gainedCount; self.vars[OUTSIDE_GAIN_COUNT_KEY] = newCount; if (!self.tags.some((t) => t.endsWith("/翻面"))) return; if (newCount < self.maxHealth) return; delete state.localVars["酒诗/flipChoice"]; await applyAtom(state, { type: "请求回应", requestType: "界酒诗/flipBack", target: ownerId, prompt: { type: "confirm", title: `酒诗:回合外累计获得 ${newCount} 张"落英"牌(≥体力上限 ${self.maxHealth}),是否翻至正面?`, confirmLabel: "翻至正面", cancelLabel: "保持背面" }, defaultChoice: false, timeout: 10 }); const flip = state.localVars["酒诗/flipChoice"] === true; delete state.localVars["酒诗/flipChoice"]; if (!flip) return; await flipFaceUpAll(state, ownerId); } function onInit$97(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "respond", (s) => { const slot = s.pendingSlots.get(ownerId); if (slot?.atom.type !== "请求回应") return "当前不需要回应"; if (slot.atom.requestType !== CHOOSE_RT$5) return "当前不是落英选择"; return null; }, async (s, params) => { s.localVars[CHOICE_KEY$2] = params.choice === true; }); registerAfterHook(state, skill.id, ownerId, "弃置", async (ctx) => { const atom = ctx.atom; if (atom.type !== "弃置") return; if (atom.player === ownerId) return; if (!atom.cardIds || atom.cardIds.length === 0) return; const clubCardIds = atom.cardIds.filter((id) => isClubCard(ctx.state, id)); if (clubCardIds.length === 0) return; const gained = await askAndGain(ctx.state, ownerId, clubCardIds, "弃牌堆"); await recordGainAndMaybeFlipBack(ctx.state, ownerId, gained); }); registerAfterHook(state, skill.id, ownerId, "判定牌生效后", async (ctx) => { if (ctx.atom.player === ownerId) return; const processing = frameCards(ctx.state); if (processing.length === 0) return; if (processing.length > 1) return; const judgeCardId = processing[processing.length - 1]; if (!isClubCard(ctx.state, judgeCardId)) return; const gained = await askAndGain(ctx.state, ownerId, [judgeCardId], "处理区"); await recordGainAndMaybeFlipBack(ctx.state, ownerId, gained); }); registerBeforeHook(state, skill.id, ownerId, "阶段开始", async (ctx) => { const atom = ctx.atom; if (atom.type !== "阶段开始") return; if (atom.player !== ownerId) return; if (atom.phase !== "准备") return; const self = ctx.state.players[ownerId]; if (!self) return; delete self.vars[OUTSIDE_GAIN_COUNT_KEY]; }); return () => {}; } function onMount$88(_skill, api) { api.defineAction("respond", { label: DISPLAY_NAME$14, style: "default", prompt: { type: "confirm", title: "是否发动落英?", confirmLabel: "获得", cancelLabel: "不获得" } }); } var DISPLAY_NAME$14, CHOOSE_RT$5, CHOICE_KEY$2, OUTSIDE_GAIN_COUNT_KEY, _skillModule$35; var init_界落英 = __esmMin((() => { init_apply(); init_frame(); init_face_down(); init_skill(); DISPLAY_NAME$14 = "落英"; CHOOSE_RT$5 = "界落英/choose"; CHOICE_KEY$2 = "落英/choice"; OUTSIDE_GAIN_COUNT_KEY = "落英/外得计数"; _skillModule$35 = { createSkill: createSkill$97, onInit: onInit$97, onMount: onMount$88 }; })); //#endregion //#region src/engine/skills/界蛊惑.ts var 界蛊惑_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$96, default: () => 界蛊惑_default, onInit: () => onInit$96, onMount: () => onMount$87 }); function createSkill$96(id, ownerId) { return { id, ownerId, name: DISPLAY_NAME$13, description: "每回合限一次,扣置一张手牌声明为基本牌使用/打出;其他角色可同时质疑:真→缠怨+弃牌或失1体力,假→作废+质疑者摸1牌" }; } /** 蛊惑-杀 生效后的杀结算:成为目标→检测有效性→询问闪→(被抵消/伤害)。 * 扣牌留弃牌堆(不进处理区,保隐藏),cardId 仅作 hook 来源标识。 * pushFrame('杀') + 检测有效性期间临时改 cardMap[name]='杀',使武器技/仁王盾生效。 */ async function resolveGuSlash$1(state, source, target, cardId) { const card = state.cardMap[cardId]; const damageType = card?.damageType; await pushFrame(state, "杀", source, { cardId, declaredName: "杀", target }); const origName = card?.name; if (card && origName !== "杀") card.name = "杀"; try { if (!await applyAtom(state, { type: "成为目标", source, target, cardId })) return; if (!await applyAtom(state, { type: "检测有效性", source, target, cardId })) return; await applyAtom(state, { type: "询问闪", target, source }); const slashFrame = state.settlementStack[state.settlementStack.length - 1]; const dodgeIds = frameCards(state).filter((id) => state.cardMap[id]?.name === "闪"); if (slashFrame?.cancelled || dodgeIds.length > 0) { await applyAtom(state, { type: "被抵消", source, target, cardId }); const remaining = frameCards(state).filter((id) => state.cardMap[id]?.name === "闪"); for (const dId of remaining) await applyAtom(state, { type: "移动牌", cardId: dId, from: { zone: "处理区" }, to: { zone: "弃牌堆" } }); } else if (state.players[target]?.alive) await runDamageFlow(state, source, target, 1, cardId, damageType); } finally { for (const id of frameCards(state)) if (state.cardMap[id]?.name === "闪") await applyAtom(state, { type: "移动牌", cardId: id, from: { zone: "处理区" }, to: { zone: "弃牌堆" } }); await popFrame(state); if (card && origName !== void 0 && origName !== "杀") card.name = origName; } incSlashUsed(state); await applyAtom(state, { type: "回合用量", player: source, key: SLASH_USED_COUNT_KEY, value: slashUsed(state) }); } /** 将扣牌(在弃牌堆)经 弃牌堆→手牌→当作(影子 outputName)→手牌→处理区 提供到当前结算帧。 * 供 dodge(闪)在质疑通过后向处理区提供一张"闪"(供杀结算检测处理区有闪 → 抵消)。 * 影子复用 武圣/奇袭 的转化模型:入弃牌堆时按 shadowOf 还原为扣牌。 */ async function provideAsBasicToProcessing$1(state, yuji, concealedId, outputName) { await applyAtom(state, { type: "移动牌", cardId: concealedId, from: { zone: "弃牌堆" }, to: { zone: "手牌", player: yuji } }); const shadowId = `${concealedId}#界蛊惑${outputName}`; await applyAtom(state, { type: "当作", player: yuji, cardIds: [concealedId], shadowId, outputName }); await applyAtom(state, { type: "移动牌", cardId: shadowId, from: { zone: "手牌", player: yuji }, to: { zone: "处理区" } }); } /** 按声明的牌生效(扣牌已在弃牌堆=已使用)。 */ async function applyDeclaredEffect$1(state, source, declaredName, target, cardId) { if (declaredName === "杀") { if (typeof target !== "number" || !state.players[target]?.alive) return; await resolveGuSlash$1(state, source, target, cardId); } else if (declaredName === "桃") { const tgt = typeof target === "number" ? target : source; if (!state.players[tgt]?.alive) return; await applyAtom(state, { type: "回复体力", target: tgt, amount: 1, source }); } else if (declaredName === "酒") await applyAtom(state, { type: "加标记", player: source, mark: { id: "酒/nextKillDamageBonus", scope: -1, payload: 1, duration: "turn" } }); } /** 对单个质疑者结算"真牌"后果:加缠怨标记 + 选弃一张牌或失去1点体力。 * 无手牌时强制失去1点体力(沿用刚烈 FAQ 模式)。 */ async function applyTruthConsequence(state, questioner, yuji) { await applyAtom(state, { type: "加标记", player: questioner, mark: { id: "缠怨", scope: questioner } }); if (state.players[questioner].hand.length >= 1) { delete state.localVars[CHOOSE_VAR_PREFIX + questioner]; await applyAtom(state, { type: "请求回应", requestType: CHOOSE_REQUEST, target: questioner, prompt: { type: "confirm", title: "界蛊惑:弃置一张手牌,或失去1点体力?", confirmLabel: "弃置一张手牌", cancelLabel: "失去1点体力" }, defaultChoice: false, timeout: 30 }); if (state.localVars[CHOOSE_VAR_PREFIX + questioner] === "discard") { await applyAtom(state, { type: "弃置", player: questioner, cardIds: [...state.players[questioner].hand].slice(0, 1) }); return; } } await applyAtom(state, { type: "失去体力", target: questioner, amount: 1 }); } /** 界蛊惑质疑流程(各入口共用):扣牌 → 质疑循环 → (有人质疑)翻牌结算。 * 返回 { voided: 是否作废(假牌被质疑), downCard: 扣牌 id, questioners: 质疑者列表 }。 * * "同时质疑"在引擎中用顺序询问+全面收集实现(不走多目标并行 slot): * 1) 多目标并行 slot 的 toViewEvents.othersView 用 targets[0] 作为 target 标识,导致非 target * viewer 的 view.pending 跟踪 targets[0];当 targets[0] 的 slot resolve 时, * harness 的 pendingResolved 处理会误清所有非 target viewer 的 pending * (即使其他 slot 仍存活)——与 buildView 不一致。改用 请求回应 逐个问询避免此问题。 * 2) 顺序询问全面收集(不随首问者 break)与同时质疑在游戏状态结果上等价: * 规则只依赖"最终质疑者集合",不依赖决策时序/信息暴露。 * (唯一区别是 UX:顺序询问时后问者知道前问者的选择——可在 UI 层面优化为盲选) */ async function runQuestioning$1(state, yuji, cardId, declaredName) { await applyAtom(state, { type: "扣牌", player: yuji, cardId, declaredName }); const n = state.players.length; const questioners = []; for (let step = 1; step < n; step++) { const p = (yuji + step) % n; if (p === yuji) continue; if (!state.players[p]?.alive) continue; delete state.localVars[QUESTIONERS_VAR]; await applyAtom(state, { type: "请求回应", requestType: QUESTION_REQUEST, target: p, prompt: { type: "confirm", title: `是否质疑于吉声明为【${declaredName}】的蛊惑?` }, timeout: 20 }); const q = state.localVars[QUESTIONERS_VAR]; if (q !== void 0 && !questioners.includes(q)) questioners.push(q); } const downCard = state.localVars[DOWNCARD_VAR$1]; if (questioners.length === 0) return { voided: false, downCard, questioners: [] }; await applyAtom(state, { type: "展示", player: yuji, cardId: downCard }); if (state.cardMap[downCard]?.name === declaredName) { for (const q of [...questioners].sort((a, b) => a - b)) { if (!state.players[q]?.alive) continue; await applyTruthConsequence(state, q, yuji); } return { voided: false, downCard, questioners }; } for (const q of [...questioners].sort((a, b) => a - b)) { if (!state.players[q]?.alive) continue; await applyAtom(state, { type: "摸牌", player: q, count: 1 }); } return { voided: true, downCard, questioners }; } /** 清理本次蛊惑临时 localVars。 */ function cleanupVars$1(state) { delete state.localVars[DOWNCARD_VAR$1]; delete state.localVars[QUESTIONERS_VAR]; for (const key of Object.keys(state.localVars)) if (key.startsWith(CHOOSE_VAR_PREFIX)) delete state.localVars[key]; } function onInit$96(skill, state) { const ownerId = skill.ownerId; const unloaders = []; unloaders.push(declareAlternativeResponse(state, ownerId, "询问闪")); unloaders.push(declareAlternativeResponse(state, ownerId, "请求回应", "桃/求桃")); const registerDeclaredUse = (declaredName) => registerAction(state, skill.id, ownerId, `${USE_ACTION_PREFIX$1}${declaredName}`, (st, params) => { if (st.currentPlayerIndex !== ownerId) return "不是你的回合"; if (st.phase !== "出牌") return "只能在出牌阶段发动"; if (hasBlockingPending(st)) return "当前有未完成的询问"; if (usedThisTurn$3(st, ownerId, SKILL_ID$13)) return "本回合已使用过蛊惑"; const self = st.players[ownerId]; if (!self?.alive) return "玩家不存在或已死亡"; const cardId = params.cardId; if (typeof cardId !== "string" || !self.hand.includes(cardId)) return "请选择一张手牌"; if (declaredName === "杀") { const target = params.target; if (typeof target !== "number" || !st.players[target]?.alive) return "请选择合法目标"; if (!inAttackRange(st, ownerId, target)) return "目标不在攻击范围内"; if (!canSlash(st, ownerId, cardId)) return "出杀次数已达上限"; } else if (declaredName === "桃") { const target = params.target ?? ownerId; const tp = st.players[target]; if (!tp?.alive) return "目标不存在"; if (tp.health >= tp.maxHealth) return "桃只能对受伤角色使用"; } return null; }, async (st, params) => { const from = ownerId; const cardId = params.cardId; const target = params.target; await markOncePerTurn(st, from, SKILL_ID$13); await pushFrame(st, "蛊惑", from, { cardId, declaredName }); try { const result = await runQuestioning$1(st, from, cardId, declaredName); if (!result.voided) await applyDeclaredEffect$1(st, from, declaredName, target, result.downCard); } finally { cleanupVars$1(st); await popFrame(st); } }); for (const declaredName of ACTIVE_DECLARATIONS$1) unloaders.push(registerDeclaredUse(declaredName)); unloaders.push(registerAction(state, skill.id, ownerId, "dodge", (st, params) => { const slot = st.pendingSlots.get(ownerId); if (!slot) return "当前不需要回应"; if (slot.atom.type !== "询问闪") return "当前不是出闪的窗口"; if (slot.atom.target !== ownerId) return "不是问你的"; if (!st.players[ownerId]?.alive) return "你已死亡"; if (usedThisTurn$3(st, ownerId, SKILL_ID$13)) return "本回合已使用过蛊惑"; const cardId = params.cardId; if (typeof cardId !== "string" || !st.players[ownerId].hand.includes(cardId)) return "请选择一张手牌"; return null; }, async (st, params) => { const from = ownerId; const cardId = params.cardId; await markOncePerTurn(st, from, SKILL_ID$13); await pushFrame(st, "蛊惑", from, { cardId, declaredName: "闪" }); let notVoided; let downCard; try { const result = await runQuestioning$1(st, from, cardId, "闪"); notVoided = !result.voided; downCard = result.downCard; } finally { cleanupVars$1(st); await popFrame(st); } if (notVoided) await provideAsBasicToProcessing$1(st, from, downCard, "闪"); })); unloaders.push(registerAction(state, skill.id, ownerId, "rescue", (st, params) => { const slot = st.pendingSlots.get(ownerId); if (!slot) return "当前不需要回应"; const atom = slot.atom; if (atom.type !== "请求回应") return "当前不是求桃窗口"; if (atom.requestType !== "桃/求桃") return "当前不是求桃窗口"; if (atom.target !== ownerId) return "不是问你的"; if (!st.players[ownerId]?.alive) return "你已死亡"; if (usedThisTurn$3(st, ownerId, SKILL_ID$13)) return "本回合已使用过蛊惑"; const cardId = params.cardId; if (typeof cardId !== "string" || !st.players[ownerId].hand.includes(cardId)) return "请选择一张手牌"; return null; }, async (st, params) => { const from = ownerId; const cardId = params.cardId; await markOncePerTurn(st, from, SKILL_ID$13); await pushFrame(st, "蛊惑", from, { cardId, declaredName: "桃" }); let notVoided; try { notVoided = !(await runQuestioning$1(st, from, cardId, "桃")).voided; } finally { cleanupVars$1(st); await popFrame(st); } if (notVoided) st.localVars[RESCUED_VAR$1] = true; })); for (const pl of state.players) { const seat = pl.index; unloaders.push(registerAction(state, skill.id, seat, "respond", (st, _params) => { const slot = st.pendingSlots.get(seat); if (!slot) return "当前不需要回应"; const atom = slot.atom; if (atom.type !== "请求回应") return "当前不需要回应"; const rt = atom.requestType; if (rt !== QUESTION_REQUEST && rt !== CHOOSE_REQUEST) return "当前不是界蛊惑问询窗口"; if (!st.players[seat]?.alive) return "你已死亡"; return null; }, async (st, params) => { const rt = (st.pendingSlots.get(seat)?.atom)?.requestType; if (rt === QUESTION_REQUEST) { if (!(params.choice === true || params.confirmed === true)) return; st.localVars[QUESTIONERS_VAR] = seat; } else if (rt === CHOOSE_REQUEST) st.localVars[CHOOSE_VAR_PREFIX + seat] = params.choice === true ? "discard" : "damage"; })); } return () => { for (const u of unloaders) u(); }; } function onMount$87(_skill, api) { api.defineAction(`${USE_ACTION_PREFIX$1}杀`, { label: "蛊惑·杀", style: "danger", prompt: { type: "useCardAndTarget", title: "蛊惑:扣置一张手牌,声明为【杀】使用", cardFilter: { filter: () => true, min: 1, max: 1 }, targetFilter: { min: 1, max: 1, filter: (view, t) => viewCanAttack(view.players, view.cardMap, api.viewer, t) } }, activeWhen: (ctx) => activeUseActive$1(ctx) && viewCanSlash(ctx.view, ctx.perspectiveIdx) }); api.defineAction(`${USE_ACTION_PREFIX$1}桃`, { label: "蛊惑·桃", style: "primary", prompt: { type: "useCardAndTarget", title: "蛊惑:扣置一张手牌,声明为【桃】使用", cardFilter: { filter: () => true, min: 1, max: 1 }, targetFilter: { min: 0, max: 1, filter: (view, t) => { const p = view.players[t]; return p?.alive === true && p.health < p.maxHealth; } }, selfTarget: true }, activeWhen: (ctx) => { if (!activeUseActive$1(ctx)) return false; const p = ctx.view.players[ctx.perspectiveIdx]; return p?.alive === true && p.health < p.maxHealth; } }); api.defineAction(`${USE_ACTION_PREFIX$1}酒`, { label: "蛊惑·酒", style: "primary", prompt: { type: "useCard", title: "蛊惑:扣置一张手牌,声明为【酒】使用", cardFilter: { filter: () => true, min: 1, max: 1 } }, activeWhen: (ctx) => activeUseActive$1(ctx) }); api.defineAction("dodge", { label: "蛊惑·闪", style: "primary", prompt: { type: "useCard", title: "蛊惑:扣置一张手牌,声明为闪打出", cardFilter: { min: 1, max: 1 } }, activeWhen: (ctx) => dodgeActive$1(ctx) }); api.defineAction("rescue", { label: "蛊惑·桃", style: "primary", prompt: { type: "useCard", title: "蛊惑:扣置一张手牌,声明为桃救援", cardFilter: { min: 1, max: 1 } }, activeWhen: (ctx) => rescueActive$1(ctx) }); api.defineAction("respond", { label: "质疑", style: "danger", prompt: { type: "confirm", title: "是否质疑于吉的蛊惑?" } }); return () => {}; } /** use 激活:自己回合出牌阶段 + 无阻塞 pending + 本回合未用过 + 有手牌。 */ function activeUseActive$1(ctx) { const { view, perspectiveIdx } = ctx; const p = view.players[perspectiveIdx]; if (!p) return false; if (view.currentPlayerIndex !== perspectiveIdx) return false; if (view.phase !== "出牌") return false; const pending = view.pending; if (pending != null && pending.isBlocking !== false) return false; if (p.turnUsage?.[usedThisTurnKey(SKILL_ID$13)]) return false; return (p.hand?.length ?? 0) > 0; } /** dodge 激活:询问闪 pending 命中自己 + 本回合未用过 + 有手牌。 */ function dodgeActive$1(ctx) { const { view, perspectiveIdx } = ctx; const p = view.players[perspectiveIdx]; if (!p) return false; if (p.turnUsage?.[usedThisTurnKey(SKILL_ID$13)]) return false; if ((p.hand?.length ?? 0) === 0) return false; const pending = view.pending; return pending?.target === perspectiveIdx && pending.atom?.type === "询问闪"; } /** rescue 激活:桃/求桃 pending 命中自己 + 本回合未用过 + 有手牌。 */ function rescueActive$1(ctx) { const { view, perspectiveIdx } = ctx; const p = view.players[perspectiveIdx]; if (!p) return false; if (p.turnUsage?.[usedThisTurnKey(SKILL_ID$13)]) return false; if ((p.hand?.length ?? 0) === 0) return false; const pending = view.pending; const atom = pending?.atom; return pending?.target === perspectiveIdx && atom?.type === "请求回应" && atom?.requestType === "桃/求桃"; } var SKILL_ID$13, DISPLAY_NAME$13, QUESTION_REQUEST, CHOOSE_REQUEST, QUESTIONERS_VAR, CHOOSE_VAR_PREFIX, DOWNCARD_VAR$1, RESCUED_VAR$1, ACTIVE_DECLARATIONS$1, USE_ACTION_PREFIX$1, 界蛊惑_default; var init_界蛊惑 = __esmMin((() => { init_apply(); init_frame(); init_damage(); init_skill(); init_distance(); init_slash_quota(); init_once_per_turn(); init_vars_keys(); init_viewDistance(); init_action_active(); SKILL_ID$13 = "界蛊惑"; DISPLAY_NAME$13 = "蛊惑"; QUESTION_REQUEST = "界蛊惑/质疑"; CHOOSE_REQUEST = "界蛊惑/choose"; QUESTIONERS_VAR = "界蛊惑/质疑者列表"; CHOOSE_VAR_PREFIX = "界蛊惑/choice/"; DOWNCARD_VAR$1 = "蛊惑/扣牌"; RESCUED_VAR$1 = "求桃/已救"; ACTIVE_DECLARATIONS$1 = [ "杀", "桃", "酒" ]; USE_ACTION_PREFIX$1 = "use:"; 界蛊惑_default = { createSkill: createSkill$96, onInit: onInit$96, onMount: onMount$87 }; })); //#endregion //#region src/engine/skills/界血裔.ts var 界血裔_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$95, default: () => _skillModule$34, onInit: () => onInit$95, onMount: () => onMount$86 }); function createSkill$95(id, ownerId) { return { id, ownerId, name: DISPLAY_NAME$12, description: "主公技:游戏开始时获得X枚裔标记(X为群势力角色数的两倍);出牌阶段开始时可移除1枚裔并摸一张牌;每枚裔手牌上限+1" }; } /** 数当前玩家的裔标记数 */ function yiCount(state, player) { return state.players[player]?.marks.filter((m) => m.id.startsWith(YI_PREFIX)).length ?? 0; } /** 加 1 枚裔 mark(经 加标记 atom,view 自动同步) */ async function addYi(state, player) { await applyAtom(state, { type: "加标记", player, mark: { id: `${YI_PREFIX}${state.seq}`, scope: player } }); } /** 移除 1 枚裔 mark(按加入顺序依次移除) */ async function removeYi(state, player) { const target = (state.players[player]?.marks ?? []).find((m) => m.id.startsWith(YI_PREFIX)); if (!target) return; await applyAtom(state, { type: "去标记", player, markId: target.id }); } /** 全场存活群势力角色数(含主公自己) */ function countQunAlive(state) { let n = 0; for (const p of state.players) if (p.alive && p.faction === "群") n += 1; return n; } function onInit$95(skill, state) { const ownerId = skill.ownerId; const unloadProvider = registerHandLimitProvider(state, ownerId, (st, player) => { if (player !== ownerId) return void 0; if (ownerId !== 0) return void 0; const p = st.players[player]; if (!p) return void 0; const bonus = getHandLimitBonus(st.turn.vars, player) ?? 0; return getHealthValue(p) + bonus + yiCount(st, player); }); const unloadInitHook = registerAfterHook(state, skill.id, ownerId, "回合开始", async (ctx) => { if (ownerId !== 0) return; const st = ctx.state; if (!st.players[ownerId]?.alive) return; if (st.localVars[INIT_KEY(ownerId)]) return; st.localVars[INIT_KEY(ownerId)] = true; const total = countQunAlive(st) * 2; for (let i = 0; i < total; i++) await addYi(st, ownerId); }); const unloadPhaseHook = registerAfterHook(state, skill.id, ownerId, "阶段开始", async (ctx) => { if (ownerId !== 0) return; const atom = ctx.atom; if (atom.type !== "阶段开始") return; if (atom.player !== ownerId) return; if (atom.phase !== "出牌") return; const st = ctx.state; if (!st.players[ownerId]?.alive) return; if (yiCount(st, ownerId) <= 0) return; delete st.localVars[USE_CHOICE_KEY]; await applyAtom(st, { type: "请求回应", requestType: USE_RT, target: ownerId, prompt: { type: "confirm", title: "血裔:是否移除 1 枚裔标记并摸一张牌?", confirmLabel: "移除裔并摸牌", cancelLabel: "不发动" }, defaultChoice: false, timeout: 20 }); const choice = st.localVars[USE_CHOICE_KEY]; delete st.localVars[USE_CHOICE_KEY]; if (choice !== true) return; await removeYi(st, ownerId); if (st.players[ownerId]?.alive) await applyAtom(st, { type: "摸牌", player: ownerId, count: 1 }); }); registerAction(state, skill.id, ownerId, "respond", (st, _params) => { const slot = st.pendingSlots.get(ownerId); if (!slot) return "当前不需要回应"; const atom = slot.atom; if (atom.type !== "请求回应") return "当前不是血裔询问"; if (atom.requestType !== USE_RT) return "当前不是血裔询问"; return null; }, async (st, params) => { st.localVars[USE_CHOICE_KEY] = params.choice === true || params.confirmed === true; }); return () => { unloadProvider(); unloadInitHook(); unloadPhaseHook(); }; } function onMount$86(_skill, _api) { return () => {}; } var SKILL_ID$12, DISPLAY_NAME$12, YI_PREFIX, INIT_KEY, USE_RT, USE_CHOICE_KEY, _skillModule$34; var init_界血裔 = __esmMin((() => { init_types(); init_apply(); init_skill(); init_hand_limit(); init_vars_keys(); SKILL_ID$12 = "界血裔"; DISPLAY_NAME$12 = "血裔"; YI_PREFIX = `${SKILL_ID$12}/裔:`; INIT_KEY = (ownerId) => `${SKILL_ID$12}/init/${ownerId}`; USE_RT = `${SKILL_ID$12}/use`; USE_CHOICE_KEY = `${SKILL_ID$12}/useChoice`; _skillModule$34 = { createSkill: createSkill$95, onInit: onInit$95, onMount: onMount$86 }; })); //#endregion //#region src/engine/skills/界补益.ts var 界补益_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$94, default: () => _skillModule$33, onInit: () => onInit$94, onMount: () => onMount$85 }); /** 列出濒死目标可选的牌(装备区明牌 + 手牌张数)。 */ function listTargetCards(state, target) { const tp = state.players[target]; if (!tp) return { equipment: [], handCount: 0 }; return { equipment: EQUIP_SLOTS.filter((slot) => typeof tp.equipment[slot] === "string").map((slot) => { const cardId = tp.equipment[slot]; return { slot, cardId, cardName: state.cardMap[cardId]?.name ?? "?" }; }), handCount: tp.hand.length }; } function createSkill$94(id, ownerId) { return { id, ownerId, name: DISPLAY_NAME$11, description: "当一名角色进入濒死状态时,可选其一张牌;若非基本牌,弃置之并回复1点体力" }; } function onInit$94(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "respond", (st, params) => { const slot = st.pendingSlots.get(ownerId); if (!slot) return "当前不需要回应"; const atom = slot.atom; if (atom["type"] !== "请求回应") return "当前不需要回应"; const rt = atom["requestType"]; if (rt !== CONFIRM_RT$16 && rt !== PICK_RT$3) return "当前不是补益询问"; if (rt === PICK_RT$3) { const target = atom.prompt?.target; if (typeof target !== "number") return "目标缺失"; const tp = st.players[target]; if (!tp) return "目标不存在"; const zone = params.zone; if (zone === "equipment") { const cid = params.cardId; if (typeof cid !== "string") return "cardId required"; if (!EQUIP_SLOTS.some((s) => tp.equipment[s] === cid)) return "该牌不在目标装备区"; } else if (zone === "hand") { if (typeof params.handIndex !== "number") return "handIndex required"; if (params.handIndex < 0 || params.handIndex >= tp.hand.length) return "handIndex 越界"; } else return "zone required (equipment|hand)"; } return null; }, async (st, params) => { const rt = (st.pendingSlots.get(ownerId)?.atom)?.requestType; if (rt === CONFIRM_RT$16) st.localVars[CONFIRM_KEY$5] = params.choice === true || params.confirmed === true; else if (rt === PICK_RT$3) st.localVars[PICK_RESULT_KEY$3] = { zone: params.zone, cardId: params.cardId ?? null, handIndex: params.handIndex ?? null }; }); registerAfterHook(state, skill.id, ownerId, "进入濒死状态时", async (ctx) => { const atom = ctx.atom; if (typeof atom.target !== "number") return; const target = atom.target; const targetPlayer = ctx.state.players[target]; if (!targetPlayer?.alive) return; if (targetPlayer.health > 0) return; if (!ctx.state.players[ownerId]?.alive) return; const { equipment, handCount } = listTargetCards(ctx.state, target); if (equipment.length === 0 && handCount === 0) return; delete ctx.state.localVars[CONFIRM_KEY$5]; await applyAtom(ctx.state, { type: "请求回应", requestType: CONFIRM_RT$16, target: ownerId, prompt: { type: "confirm", title: `补益:是否选择 ${targetPlayer.name} 的一张牌?若非基本牌,弃置之并回复1点体力`, confirmLabel: "发动", cancelLabel: "不发动" }, defaultChoice: false, timeout: 15 }); const confirmed = ctx.state.localVars[CONFIRM_KEY$5] === true; delete ctx.state.localVars[CONFIRM_KEY$5]; if (!confirmed) return; if (ctx.state.players[target]?.health > 0) return; const { equipment: equipmentAfter, handCount: handCountAfter } = listTargetCards(ctx.state, target); if (equipmentAfter.length === 0 && handCountAfter === 0) return; delete ctx.state.localVars[PICK_RESULT_KEY$3]; if (handCountAfter > 0) spliceHandOrderEntry(ctx.state, target); await applyAtom(ctx.state, { type: "请求回应", requestType: PICK_RT$3, target: ownerId, prompt: { type: "pickTargetCard", title: "补益:选择目标一张牌(装备区明选,手牌盲选)", target, equipment: equipmentAfter, judge: [], handCount: handCountAfter }, defaultChoice: equipmentAfter.length > 0 ? { zone: "equipment", cardId: equipmentAfter[0].cardId } : { zone: "hand", handIndex: 0 }, timeout: 20 }); let result = ctx.state.localVars[PICK_RESULT_KEY$3]; delete ctx.state.localVars[PICK_RESULT_KEY$3]; result ??= equipmentAfter.length > 0 ? { zone: "equipment", cardId: equipmentAfter[0].cardId, handIndex: null } : { zone: "hand", cardId: null, handIndex: 0 }; let pickedCardId; if (result.zone === "equipment") pickedCardId = result.cardId ?? void 0; else if (result.zone === "hand") { const idx = result.handIndex ?? 0; pickedCardId = ctx.state.players[target]?.hand[idx]; } if (!pickedCardId) return; const tp = ctx.state.players[target]; const inEquip = EQUIP_SLOTS.some((s) => tp?.equipment[s] === pickedCardId); const inHand = tp?.hand.includes(pickedCardId) ?? false; if (!inEquip && !inHand) return; if (ctx.state.cardMap[pickedCardId]?.type !== "基本牌") { await applyAtom(ctx.state, { type: "弃置", player: target, cardIds: [pickedCardId] }); await applyAtom(ctx.state, { type: "回复体力", target, amount: 1 }); } }); return () => {}; } function onMount$85(_skill, api) { api.defineAction("respond", { label: DISPLAY_NAME$11, style: "default", prompt: { type: "confirm", title: "补益" } }); return () => {}; } var DISPLAY_NAME$11, CONFIRM_RT$16, PICK_RT$3, CONFIRM_KEY$5, EQUIP_SLOTS, _skillModule$33; var init_界补益 = __esmMin((() => { init_apply(); init_skill(); init_pick_card_panel(); init_vars_keys(); DISPLAY_NAME$11 = "补益"; CONFIRM_RT$16 = "界补益/confirm"; PICK_RT$3 = "界补益/pick"; CONFIRM_KEY$5 = "界补益/confirmed"; EQUIP_SLOTS = [ "武器", "防具", "进攻马", "防御马", "宝物" ]; _skillModule$33 = { createSkill: createSkill$94, onInit: onInit$94, onMount: onMount$85 }; })); //#endregion //#region src/engine/skills/界裸衣.ts var 界裸衣_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$93, default: () => 界裸衣_default, onInit: () => onInit$93, onMount: () => onMount$84 }); function createSkill$93(id, ownerId) { return { id, ownerId, name: "界裸衣", description: "摸牌阶段开始前,可亮出牌堆顶3张;可跳过摸牌阶段并获得其中的基本牌/武器牌/决斗,且直到你的下回合开始,杀/决斗伤害+1" }; } /** 界裸衣"可获得"判定:基本牌 / 武器牌(subtype=武器) / 决斗 */ function isTakeable(card) { if (!card) return false; if (card.name === "决斗") return true; if (card.type === "基本牌") return true; if (card.type === "装备牌" && card.subtype === "武器") return true; return false; } function onInit$93(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "respond", (st, _params) => { const slot = st.pendingSlots.get(ownerId); if (!slot) return "当前不需要回应"; const atom = slot.atom; if (atom["type"] !== "请求回应") return "当前不需要回应"; const rt = atom["requestType"]; if (rt !== ACTIVATE_RT && rt !== SKIP_RT) return "当前不是界裸衣询问"; return null; }, async (st, params) => { const rt = (st.pendingSlots.get(ownerId)?.atom)?.["requestType"]; const yes = params.choice === true || params.confirmed === true; if (rt === ACTIVATE_RT) st.localVars[ACTIVATED_KEY] = yes; else if (rt === SKIP_RT) st.localVars[SKIP_KEY] = yes; }); registerBeforeHook(state, skill.id, ownerId, "阶段开始", async (ctx) => { const atom = ctx.atom; if (atom.type !== "阶段开始") return; if (atom.player !== ownerId) return; if (atom.phase !== "摸牌") return; if (ctx.state.currentPlayerIndex !== ownerId) return; if (!ctx.state.players[ownerId]?.alive) return; if (usedThisTurn$3(ctx.state, ownerId, "界裸衣")) return; if (ctx.state.zones.deck.length < 3) return; delete ctx.state.localVars[ACTIVATED_KEY]; await applyAtom(ctx.state, { type: "请求回应", requestType: ACTIVATE_RT, target: ownerId, prompt: { type: "confirm", title: "是否发动界裸衣?(亮出牌堆顶3张;直到你的下回合开始,你的杀/决斗伤害+1)", confirmLabel: "发动", cancelLabel: "不发动" }, defaultChoice: false, timeout: 15 }); if (ctx.state.localVars[ACTIVATED_KEY] !== true) return; await markOncePerTurn(ctx.state, ownerId, "界裸衣"); const top3 = ctx.state.zones.deck.slice(-3); let shouldSkip; await pushFrame(ctx.state, "界裸衣", ownerId, {}); try { for (let i = top3.length - 1; i >= 0; i--) await applyAtom(ctx.state, { type: "移动牌", cardId: top3[i], from: { zone: "牌堆" }, to: { zone: "处理区" } }); await applyAtom(ctx.state, { type: "加标签", player: ownerId, tag: BONUS_TAG$1 }); delete ctx.state.localVars[SKIP_KEY]; await applyAtom(ctx.state, { type: "请求回应", requestType: SKIP_RT, target: ownerId, prompt: { type: "confirm", title: "界裸衣:是否跳过摸牌阶段并获得亮出牌中的基本牌、武器牌和【决斗】?(其余亮出牌置入弃牌堆)", confirmLabel: "跳过并获得", cancelLabel: "不跳过" }, defaultChoice: false, timeout: 15 }); shouldSkip = ctx.state.localVars[SKIP_KEY] === true; if (shouldSkip) for (const cardId of top3) { const inProc = ctx.state.zones.processing.includes(cardId); const inFrame = ctx.state.settlementStack[ctx.state.settlementStack.length - 1]?.cards.includes(cardId) ?? false; if (!inProc && !inFrame) continue; const card = ctx.state.cardMap[cardId]; const take = isTakeable(card); await applyAtom(ctx.state, { type: "移动牌", cardId, from: { zone: "处理区" }, to: take ? { zone: "手牌", player: ownerId } : { zone: "弃牌堆" } }); } else for (const cardId of top3) { const inProc = ctx.state.zones.processing.includes(cardId); const inFrame = ctx.state.settlementStack[ctx.state.settlementStack.length - 1]?.cards.includes(cardId) ?? false; if (!inProc && !inFrame) continue; await applyAtom(ctx.state, { type: "移动牌", cardId, from: { zone: "处理区" }, to: { zone: "牌堆" } }); } } finally { await popFrame(ctx.state); } delete ctx.state.localVars[ACTIVATED_KEY]; delete ctx.state.localVars[SKIP_KEY]; if (shouldSkip) return skipPhase(ctx.state, { player: ownerId, phase: "摸牌" }); }); registerBeforeHook(state, skill.id, ownerId, "造成伤害时", async (ctx) => { const atom = ctx.atom; if (atom.source !== ownerId) return; if ((atom.amount ?? 0) <= 0) return; if (!ctx.state.players[ownerId]?.tags.includes(BONUS_TAG$1)) return; const cardId = atom.cardId; if (typeof cardId !== "string") return; const card = ctx.state.cardMap[cardId]; if (!card) return; if (card.name !== "杀" && card.name !== "决斗") return; return { kind: "modify", atom: { ...ctx.atom, amount: (atom.amount ?? 0) + 1 } }; }); registerAfterHook(state, skill.id, ownerId, "回合开始", async (ctx) => { if (ctx.atom.player !== ownerId) return; if (ctx.state.players[ownerId]?.tags.includes(BONUS_TAG$1)) await applyAtom(ctx.state, { type: "去标签", player: ownerId, tag: BONUS_TAG$1 }); }); return () => {}; } function onMount$84(_skill, api) { api.defineAction("respond", { label: "界裸衣", style: "primary", prompt: { type: "confirm", title: "是否发动界裸衣?", confirmLabel: "发动", cancelLabel: "不发动" } }); return () => {}; } var BONUS_TAG$1, ACTIVATE_RT, SKIP_RT, ACTIVATED_KEY, SKIP_KEY, 界裸衣_default; var init_界裸衣 = __esmMin((() => { init_apply(); init_frame(); init_once_per_turn(); init_skill(); init_skip_phase(); BONUS_TAG$1 = "界裸衣/bonus"; ACTIVATE_RT = "界裸衣/activate"; SKIP_RT = "界裸衣/skip"; ACTIVATED_KEY = "界裸衣/activated"; SKIP_KEY = "界裸衣/skipChoice"; 界裸衣_default = { createSkill: createSkill$93, onInit: onInit$93, onMount: onMount$84 }; })); //#endregion //#region src/engine/skills/界观星.ts var 界观星_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$92, default: () => 界观星_default, onInit: () => onInit$92, onMount: () => onMount$83 }); function createSkill$92(id, ownerId) { return { id, ownerId, name: "界观星", description: "准备阶段,观看牌堆顶 5 张(存活角色数<4 时为 3 张),任意排序置顶/置底;若皆置底,结束阶段可再次发动" }; } /** * 计算本次观星的观察张数 X(界版规则): * - 存活角色数 < 4 → X = 3(2~3 人残局仍可观 3 张) * - 存活角色数 ≥ 4 → X = 5 */ function calcX(state) { return state.players.filter((p) => p.alive).length < 4 ? 3 : 5; } /** * 观星主流程:询问是否发动 → 询问排列 → 构建新牌堆 → 整理牌堆。 * @returns 是否"皆置于牌堆底"(满足结束阶段再次发动条件);未发动/非法划分时返回 false。 */ async function doGuanxing(state, ownerId) { const deck = state.zones.deck; if (deck.length === 0) return false; const x = calcX(state); const drawCount = Math.min(x, deck.length); if (drawCount === 0) return false; delete state.localVars[CONFIRMED_VAR$1]; await applyAtom(state, { type: "请求回应", requestType: "界观星/confirm", target: ownerId, prompt: { type: "confirm", title: "是否发动界观星?", confirmLabel: "发动", cancelLabel: "不发动" }, defaultChoice: false, timeout: 15 }); if (!state.localVars[CONFIRMED_VAR$1]) return false; const observed = deck.slice(-drawCount); delete state.localVars[ARRANGE_VAR]; await applyAtom(state, { type: "请求回应", requestType: "界观星/arrange", target: ownerId, prompt: { type: "distribute", title: "界观星:排列牌堆顶牌", description: "选择留在牌堆顶的牌(顺序即摸牌顺序)与置于牌堆底的牌", mode: "select", cardIds: observed, minTotal: 0, maxTotal: drawCount }, defaultChoice: false, timeout: 30 }); const arrangement = state.localVars[ARRANGE_VAR]; delete state.localVars[ARRANGE_VAR]; delete state.localVars[CONFIRMED_VAR$1]; const top = arrangement?.top ?? []; const bottom = arrangement?.bottom ?? []; const observedSet = new Set(observed); const combined = [...top, ...bottom]; if (!(combined.length === observed.length && new Set(combined).size === combined.length && combined.every((id) => observedSet.has(id)))) return false; const middle = deck.slice(0, deck.length - drawCount); await applyAtom(state, { type: "整理牌堆", cards: [ ...bottom, ...middle, ...[...top].reverse() ], topCount: top.length, bottomCount: bottom.length }); return top.length === 0; } function onInit$92(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "respond", (state, params) => { const slot = state.pendingSlots.get(ownerId); if (slot?.atom.type !== "请求回应") return "当前不需要回应"; const requestType = slot.atom.requestType; if (requestType !== "界观星/confirm" && requestType !== "界观星/arrange") return "当前不是界观星询问"; if (requestType === "界观星/arrange") { if (!Array.isArray(params.top) || !Array.isArray(params.bottom)) return "界观星:需要 top/bottom 划分"; } return null; }, async (state, params) => { const requestType = (state.pendingSlots.get(ownerId)?.atom).requestType; if (requestType === "界观星/confirm") state.localVars[CONFIRMED_VAR$1] = params.choice === true || params.confirmed === true; else if (requestType === "界观星/arrange") state.localVars[ARRANGE_VAR] = { top: params.top ?? [], bottom: params.bottom ?? [] }; }); registerAfterHook(state, skill.id, ownerId, "阶段开始", async (ctx) => { const atom = ctx.atom; if (atom.type !== "阶段开始") return; if (atom.player !== ownerId) return; if (!ctx.state.players[ownerId]?.alive) return; if (atom.phase === "准备") { delete ctx.state.localVars[ALL_BOTTOM_FLAG]; if (await doGuanxing(ctx.state, ownerId)) ctx.state.localVars[ALL_BOTTOM_FLAG] = true; } else if (atom.phase === "回合结束") { if (!ctx.state.localVars[ALL_BOTTOM_FLAG]) return; delete ctx.state.localVars[ALL_BOTTOM_FLAG]; await doGuanxing(ctx.state, ownerId); } }); return () => {}; } function onMount$83(_skill, api) { api.defineAction("respond", { label: "界观星", style: "default", prompt: { type: "confirm", title: "是否发动界观星?", confirmLabel: "发动", cancelLabel: "不发动" } }); } var ALL_BOTTOM_FLAG, CONFIRMED_VAR$1, ARRANGE_VAR, 界观星_default; var init_界观星 = __esmMin((() => { init_apply(); init_skill(); ALL_BOTTOM_FLAG = "界观星/全置底"; CONFIRMED_VAR$1 = "界观星/confirmed"; ARRANGE_VAR = "界观星/arrangement"; 界观星_default = { createSkill: createSkill$92, onInit: onInit$92, onMount: onMount$83 }; })); //#endregion //#region src/engine/skills/界解烦.ts var 界解烦_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$91, default: () => _skillModule$32, onInit: () => onInit$91, onMount: () => onMount$82 }); function createSkill$91(id, ownerId) { return { id, ownerId, name: DISPLAY_NAME$10, description: "限定技,出牌阶段选择一名角色,令所有攻击范围内包含其的角色各选:弃一张武器牌 或 令该角色摸一张牌。第一轮发动后回合结束视为未发动" }; } /** 是否为武器牌 */ function isWeaponCard(card) { return !!card && card.type === "装备牌" && card.subtype === "武器"; } /** 玩家可弃的武器牌 cardId 列表(手牌中的武器牌 + 装备区已装备的武器) */ function weaponCardsInHand(state, player) { const p = state.players[player]; if (!p) return []; const handWeapons = p.hand.filter((cid) => isWeaponCard(state.cardMap[cid])); const equipWeapon = p.equipment?.["武器"]; if (equipWeapon && typeof equipWeapon === "string") return [...handWeapons, equipWeapon]; return handWeapons; } /** 列出所有攻击范围内包含 target 的存活其他角色(按座次顺序) */ function affectedPlayers(state, target) { const result = []; for (const p of state.players) { if (!p.alive) continue; if (p.index === target) continue; if (inAttackRange(state, p.index, target)) result.push(p.index); } return result; } function onInit$91(skill, state) { const ownerId = skill.ownerId; const unloads = []; unloads.push(registerAction(state, skill.id, ownerId, "use", (st, params) => { if (st.currentPlayerIndex !== ownerId) return "不是你的回合"; if (st.phase !== "出牌") return "只能在出牌阶段发动"; if (hasBlockingPending(st)) return "当前有未完成的询问"; if (st.players[ownerId]?.vars[USED_KEY$2] === true) return "解烦已发动过(限定技)"; if (!st.players[ownerId]?.alive) return "玩家不存在或已死亡"; const target = params.target; if (typeof target !== "number") return "需要指定目标"; if (!st.players[target]?.alive) return "目标不合法"; return null; }, async (st, params) => { const from = ownerId; const target = params.target; st.players[from].vars[USED_KEY$2] = true; if (st.turn.round === 1) { st.turn.vars[RESET_ON_END_VAR] = from; await applyAtom(st, { type: "回合用量", player: from, key: RESET_ON_END_VAR, value: true }); } await pushFrame(st, SKILL_ID$11, from, { ...params, chosenTarget: target }); try { const affected = affectedPlayers(st, target); for (const p of affected) { if (!st.players[target]?.alive) break; if (!st.players[p]?.alive) continue; const weapons = weaponCardsInHand(st, p); const requestType = `${CHOOSE_RT_PREFIX}${p}`; delete st.localVars[choiceKey(p)]; const defaultDiscard = weapons.length > 0; await applyAtom(st, { type: "请求回应", requestType, target: p, prompt: { type: "confirm", title: `解烦(目标 ${st.players[target].name}):弃一张武器牌 或 令 ${st.players[target].name} 摸一张牌?${weapons.length === 0 ? "(你无武器牌,只能选令其摸牌)" : ""}`, description: `confirm=true 弃一张武器牌;confirm=false(choice 取消 / 超时)令其摸一张牌`, confirmLabel: "弃武器牌", cancelLabel: "令其摸牌" }, defaultChoice: defaultDiscard, timeout: 20 }); const stored = st.localVars[choiceKey(p)]; if (stored === void 0 ? defaultDiscard : stored === "discard") { const currentWeapons = weaponCardsInHand(st, p); if (currentWeapons.length > 0) { await applyAtom(st, { type: "弃置", player: p, cardIds: [currentWeapons[0]] }); continue; } } if (st.players[target]?.alive) await applyAtom(st, { type: "摸牌", player: target, count: 1 }); } } finally { for (const key of Object.keys(st.localVars)) if (key.startsWith(`${SKILL_ID$11}/choice/`)) delete st.localVars[key]; await popFrame(st); } })); for (const player of state.players) { const seatId = player.index; unloads.push(registerAction(state, skill.id, seatId, "respond", (st, _params) => { const slot = st.pendingSlots.get(seatId); if (slot?.atom.type !== "请求回应") return "当前不需要回应"; if ((slot.atom.requestType ?? "") !== `${CHOOSE_RT_PREFIX}${seatId}`) return "当前不是解烦询问"; return null; }, async (st, params) => { if (((st.pendingSlots.get(seatId)?.atom)?.requestType ?? "") !== `${CHOOSE_RT_PREFIX}${seatId}`) return; st.localVars[choiceKey(seatId)] = params.choice === true || params.confirmed === true ? "discard" : "draw"; })); } unloads.push(registerBeforeHook(state, skill.id, ownerId, "回合结束", async (ctx) => { const atom = ctx.atom; if (atom.type !== "回合结束") return; if (atom.player !== ownerId) return; if (ctx.state.turn.vars[RESET_ON_END_VAR] !== ownerId) return; delete ctx.state.players[ownerId].vars[USED_KEY$2]; })); return () => { for (const fn of unloads) fn(); }; } function onMount$82(_skill, api) { api.defineAction("use", { label: DISPLAY_NAME$10, style: "danger", prompt: { type: "selectTarget", title: "解烦:选择一名角色(所有攻击范围内包含其的角色各选:弃武器 或 令其摸牌)", targetFilter: { min: 1, max: 1, filter: (_view, _t) => true } }, activeWhen: (ctx) => defaultPlayActive(ctx) }); api.defineAction("respond", { label: DISPLAY_NAME$10, style: "default", prompt: { type: "confirm", title: "解烦" } }); } var SKILL_ID$11, DISPLAY_NAME$10, USED_KEY$2, RESET_ON_END_VAR, CHOOSE_RT_PREFIX, choiceKey, _skillModule$32; var init_界解烦 = __esmMin((() => { init_apply(); init_frame(); init_skill(); init_distance(); init_action_active(); SKILL_ID$11 = "界解烦"; DISPLAY_NAME$10 = "解烦"; USED_KEY$2 = `${SKILL_ID$11}/used`; RESET_ON_END_VAR = `${SKILL_ID$11}/resetOnEnd`; CHOOSE_RT_PREFIX = `${SKILL_ID$11}/choose/`; choiceKey = (p) => `${SKILL_ID$11}/choice/${p}`; _skillModule$32 = { createSkill: createSkill$91, onInit: onInit$91, onMount: onMount$82 }; })); //#endregion //#region src/engine/skills/界诛害.ts var 界诛害_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$90, default: () => _skillModule$31, onInit: () => onInit$90, onMount: () => onMount$81 }); /** 在 turn.vars 上记录某座次本回合是否造成过伤害(key 后缀不含 /usedThisTurn,但 turn.vars 整体在 * 「回合结束」atom apply 时清空,故仍按回合隔离)。 */ function damageDealtKey(player) { return `${SKILL_ID$10}/source/${player}`; } function createSkill$90(id, ownerId) { return { id, ownerId, name: DISPLAY_NAME$9, description: "其他角色的结束阶段,若其本回合造成过伤害,你可以对其使用一张【杀】" }; } /** 检查 ownerId 手牌中是否有【杀】 */ function hasKillInHand(state, ownerId) { return (state.players[ownerId]?.hand ?? []).some((id) => state.cardMap[id]?.name === "杀"); } /** * 执行一次【杀】的完整结算。走 runUseFlow('杀'),与 借刀杀人/乱武 强制出杀一致: * 覆盖全部时机(选择目标时→使用时→指定目标→成为目标→指定目标后→成为目标后→ * 检测有效性→生效前[询问闪]→生效时→生效后[伤害]→使用结算结束时),保证 无双/贞烈/ * 铁骑/肉林/贯石斧/谦逊 等横切技能正确交互。此前手写实现遗漏了 指定目标后/成为目标后/ * 生效前/生效时/生效后 等时机,且 被抵消 后未重新检查 cancelled(贯石斧强命失效)。 * 不计入出杀次数(runUseFlow 不累加 quota,仅 杀.use 主动出杀时累加); * 不受距离限制(距离由 杀.use validate 校验,诛害绕过主动出杀入口)。 * 火杀/雷杀的 damageType 由 杀.resolve 自动传导。 */ async function runSlashResolution(state, source, target, cardId) { if (!state.players[target]?.alive) return; await runUseFlow(state, source, cardId, [target], "杀"); } function onInit$90(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "respond", (st, params) => { const slot = st.pendingSlots.get(ownerId); if (!slot) return "当前不需要回应"; if (slot.atom.type !== "请求回应") return "当前不需要回应"; const rt = slot.atom.requestType; if (rt !== TRIGGER_RT$2 && rt !== PICK_RT$2) return "当前不是诛害询问"; if (rt === PICK_RT$2) { const cardId = params.cardId; if (typeof cardId !== "string") return "需要选择一张杀"; if (st.cardMap[cardId]?.name !== "杀") return "不是杀牌"; if (!st.players[ownerId]?.hand.includes(cardId)) return "杀不在你的手牌中"; } return null; }, async (st, params) => { const rt = (st.pendingSlots.get(ownerId)?.atom)?.requestType; if (rt === TRIGGER_RT$2) st.localVars[CONFIRM_KEY$4] = params.choice === true; else if (rt === PICK_RT$2) { const cardId = params.cardId; st.localVars[PICK_KEY$1] = cardId; } }); registerAfterHook(state, skill.id, ownerId, "造成伤害后", async (ctx) => { const atom = ctx.atom; const source = atom.source; if (typeof source !== "number") return; if ((atom.amount ?? 0) <= 0) return; ctx.state.turn.vars[damageDealtKey(source)] = true; }); registerAfterHook(state, skill.id, ownerId, "阶段开始", async (ctx) => { const atom = ctx.atom; if (atom.type !== "阶段开始") return; if (atom.phase !== "回合结束") return; const player = atom.player; if (typeof player !== "number") return; if (player === ownerId) return; if (!ctx.state.players[ownerId]?.alive) return; if (ctx.state.turn.vars[damageDealtKey(player)] !== true) return; if (!hasKillInHand(ctx.state, ownerId)) return; delete ctx.state.localVars[CONFIRM_KEY$4]; await applyAtom(ctx.state, { type: "请求回应", requestType: TRIGGER_RT$2, target: ownerId, prompt: { type: "confirm", title: `诛害:是否对 ${ctx.state.players[player]?.name ?? `P${player}`} 使用一张杀?`, confirmLabel: "发动", cancelLabel: "不发动" }, defaultChoice: false, timeout: 10 }); if (ctx.state.localVars[CONFIRM_KEY$4] !== true) { delete ctx.state.localVars[CONFIRM_KEY$4]; return; } delete ctx.state.localVars[CONFIRM_KEY$4]; if (!ctx.state.players[player]?.alive) return; delete ctx.state.localVars[PICK_KEY$1]; await applyAtom(ctx.state, { type: "请求回应", requestType: PICK_RT$2, target: ownerId, prompt: { type: "useCard", title: "诛害:选择一张杀", cardFilter: { filter: (c) => c.name === "杀", min: 1, max: 1 } }, timeout: 15 }); const killCardId = ctx.state.localVars[PICK_KEY$1]; delete ctx.state.localVars[PICK_KEY$1]; if (typeof killCardId !== "string") return; if (!ctx.state.players[ownerId]?.hand.includes(killCardId)) return; if (!ctx.state.players[player]?.alive) return; await runSlashResolution(ctx.state, ownerId, player, killCardId); }); return () => {}; } function onMount$81(_skill, api) { api.defineAction("respond", { label: DISPLAY_NAME$9, style: "danger", prompt: { type: "confirm", title: "是否发动诛害?", confirmLabel: "发动", cancelLabel: "不发动" } }); } var SKILL_ID$10, DISPLAY_NAME$9, TRIGGER_RT$2, PICK_RT$2, CONFIRM_KEY$4, PICK_KEY$1, _skillModule$31; var init_界诛害 = __esmMin((() => { init_apply(); init_use_card(); init_skill(); SKILL_ID$10 = "界诛害"; DISPLAY_NAME$9 = "诛害"; TRIGGER_RT$2 = "界诛害/trigger"; PICK_RT$2 = "界诛害/pickKill"; CONFIRM_KEY$4 = "界诛害/confirmed"; PICK_KEY$1 = "界诛害/killCardId"; _skillModule$31 = { createSkill: createSkill$90, onInit: onInit$90, onMount: onMount$81 }; })); //#endregion //#region src/engine/skills/界谦逊.ts var 界谦逊_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$89, default: () => 界谦逊_default, onInit: () => onInit$89, onMount: () => onMount$80 }); function createSkill$89(id, ownerId) { return { id, ownerId, name: "界谦逊", description: "当延时锦囊或他人普通锦囊对你生效且你为唯一目标时,你可以将所有手牌移出游戏直到回合结束" }; } function onInit$89(skill, state) { const ownerId = skill.ownerId; function isNormalTrick(card) { if (!card) return false; if (card.type !== "锦囊牌") return false; return card.trickSubtype !== "延时锦囊" && card.trickSubtype !== "响应锦囊"; } function getScrollTargets(frame) { const params = frame.params ?? {}; if (frame.skillId === "借刀杀人") { const t = typeof params.target === "number" ? params.target : Array.isArray(params.targets) ? params.targets[0] : void 0; return typeof t === "number" ? [t] : []; } if (Array.isArray(params.targets)) return params.targets; if (typeof params.target === "number") return [params.target]; return []; } async function maybeOfferExile(ctx, reason) { const self = ctx.state.players[ownerId]; if (!self?.alive) return; if (self.hand.length === 0) return; delete ctx.state.localVars[CONFIRMED_KEY$15]; await applyAtom(ctx.state, { type: "请求回应", requestType: CONFIRM_RT$15, target: ownerId, prompt: { type: "confirm", title: `是否发动谦逊?(${reason};将所有手牌移出游戏直到回合结束)`, confirmLabel: "发动", cancelLabel: "不发动" }, defaultChoice: false, timeout: 10 }); if (ctx.state.localVars[CONFIRMED_KEY$15] !== true) return; const player = ctx.state.players[ownerId]; if (!player || player.hand.length === 0) return; const handIds = [...player.hand]; await applyAtom(ctx.state, { type: "移出至暂存区", source: ownerId, target: ownerId, cardIds: handIds, varsKey: EXILE_VARS_KEY }); } registerAction(state, skill.id, ownerId, "respond", (st, _params) => { const slot = st.pendingSlots.get(ownerId); if (!slot) return "当前不需要回应"; const atom = slot.atom; if (atom.type !== "请求回应") return "当前不是请求回应"; if (atom.requestType !== CONFIRM_RT$15) return "当前不是谦逊确认"; return null; }, async (st, params) => { st.localVars[CONFIRMED_KEY$15] = params.choice === true || params.confirmed === true; }); registerAfterHook(state, skill.id, ownerId, "添加延时锦囊", async (ctx) => { const atom = ctx.atom; if (atom.type !== "添加延时锦囊") return; if (atom.player !== ownerId) return; await maybeOfferExile(ctx, `延时锦囊${atom.trick?.name ? `「${atom.trick.name}」` : ""}对你生效`); }); registerAfterHook(state, skill.id, ownerId, "生效时", async (ctx) => { const atom = ctx.atom; if (atom.type !== "生效时") return; if (atom.target !== ownerId) return; const frame = topFrame(ctx.state); if (!frame) return; if (frame.from === ownerId) return; const scrollCardId = frame.cards.find((id) => isNormalTrick(ctx.state.cardMap[id])); if (!scrollCardId) return; const targets = getScrollTargets(frame); if (targets.length !== 1 || targets[0] !== ownerId) return; await maybeOfferExile(ctx, `「${ctx.state.cardMap[scrollCardId]?.name ?? "普通锦囊"}」对你生效`); }); registerAfterHook(state, skill.id, ownerId, "回合结束", async (ctx) => { if (ctx.atom.type !== "回合结束") return; const self = ctx.state.players[ownerId]; if (!self) return; const exiled = self.vars[EXILE_VARS_KEY]; if (!Array.isArray(exiled) || exiled.length === 0) return; await applyAtom(ctx.state, { type: "归还暂存牌", player: ownerId, varsKey: EXILE_VARS_KEY }); }); return () => {}; } function onMount$80(_skill, api) { api.defineAction("respond", { label: "谦逊", style: "default", prompt: { type: "confirm", title: "是否发动谦逊?", confirmLabel: "发动(将所有手牌移出游戏)", cancelLabel: "不发动" } }); } var CONFIRM_RT$15, CONFIRMED_KEY$15, EXILE_VARS_KEY, 界谦逊_default; var init_界谦逊 = __esmMin((() => { init_apply(); init_frame(); init_skill(); CONFIRM_RT$15 = "界谦逊/trigger"; CONFIRMED_KEY$15 = "界谦逊/confirmed"; EXILE_VARS_KEY = "界谦逊/移出"; 界谦逊_default = { createSkill: createSkill$89, onInit: onInit$89, onMount: onMount$80 }; })); //#endregion //#region src/engine/skills/界贞烈.ts var 界贞烈_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$88, default: () => _skillModule$30, onInit: () => onInit$88, onMount: () => onMount$79 }); /** 完整无效 key */ function invalidKey(cardId, ownerId) { return `${INVALID_PREFIX}${cardId}/${ownerId}`; } /** 完整已处理 key */ function processedKey(cardId, ownerId) { return `${PROCESSED_PREFIX}${cardId}/${ownerId}`; } /** 判定卡是否为杀或普通锦囊(贞烈可触发的卡类型) */ function isKillOrNormalTrick(card) { if (!card) return false; if (card.name === "杀") return true; if (card.type === "锦囊牌") return !isDelayedTrick(card) && card.trickSubtype !== "响应锦囊"; return false; } /** 是否已标记此牌对此 owner 无效 */ function isInvalidFor(state, cardId, ownerId) { if (!cardId) return false; return state.localVars[invalidKey(cardId, ownerId)] === true; } /** 是否已对此牌触发过贞烈(防重入) */ function isProcessedFor(state, cardId, ownerId) { if (!cardId) return false; return state.localVars[processedKey(cardId, ownerId)] === true; } /** * 贞烈发动主流程(触发点共用): * 1. 询问发动确认 * 2. 若确认:失去1点体力 + 标记此牌无效 * 3. 若失去体力后仍存活:询问选项(① 获得来源一张牌 / ② 结束阶段发动秘计) * 4. 标记本张牌已处理(防同一张牌重复触发) * * @param state GameState * @param ownerId 王异座次 * @param cardId 触发牌 id * @param sourceId 使用者座次(用于选项①) */ async function runZhenlie(state, ownerId, cardId, sourceId) { delete state.localVars[CONFIRM_KEY$3]; await applyAtom(state, { type: "请求回应", requestType: CONFIRM_RT$14, target: ownerId, prompt: { type: "confirm", title: "是否发动贞烈?(失去1点体力令此牌对你无效,然后二选一)", confirmLabel: "发动", cancelLabel: "不发动" }, defaultChoice: false, timeout: 15 }); if (state.localVars[CONFIRM_KEY$3] !== true) return; await applyAtom(state, { type: "失去体力", target: ownerId, amount: 1 }); state.localVars[invalidKey(cardId, ownerId)] = true; if (!state.players[ownerId]?.alive) return; delete state.localVars[CHOOSE_KEY]; await applyAtom(state, { type: "请求回应", requestType: CHOOSE_RT$4, target: ownerId, prompt: { type: "confirm", title: "贞烈:选择一项(确认=获得使用者一张牌;取消=本回合结束阶段发动一次秘计)", confirmLabel: "获得一张牌", cancelLabel: "发动秘计" }, defaultChoice: false, timeout: 15 }); const choice = state.localVars[CHOOSE_KEY]; delete state.localVars[CHOOSE_KEY]; if (choice === "gain") { const source = state.players[sourceId]; if (!source?.alive) return; if (!(source.hand.length > 0 || Object.keys(source.equipment).length > 0)) return; await runPickTargetCardPanel(state, ownerId, sourceId, source, { mode: "obtain", requestType: PICK_RT$1, title: "贞烈:选择获得使用者的一张牌", includeJudge: false }); } else state.players[ownerId].vars[mijiPendingKey(ownerId)] = true; } function createSkill$88(id, ownerId) { return { id, ownerId, name: DISPLAY_NAME$8, description: "成为其他角色杀或普通锦囊的目标后,可失1体力令此牌对你无效,然后二选一:获得使用者一张牌或本回合结束阶段发动一次秘计" }; } function onInit$88(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "respond", (st, params) => { const slot = st.pendingSlots.get(ownerId); if (!slot) return "当前不需要回应"; if (slot.atom.type !== "请求回应") return "当前不需要回应"; const rt = slot.atom.requestType; if (rt === CONFIRM_RT$14 || rt === CHOOSE_RT$4) return null; if (rt === PICK_RT$1) { const zone = params.zone; if (zone === "equipment") { if (typeof params.cardId !== "string") return "cardId required"; } else if (zone === "hand") { if (typeof params.handIndex !== "number") return "handIndex required"; } else return "zone required (equipment|hand)"; return null; } return "当前不是贞烈回应"; }, async (st, params) => { const rt = (st.pendingSlots.get(ownerId)?.atom)?.requestType; if (rt === CONFIRM_RT$14) st.localVars[CONFIRM_KEY$3] = params.choice === true || params.confirmed === true; else if (rt === CHOOSE_RT$4) st.localVars[CHOOSE_KEY] = params.choice === true || params.confirmed === true ? "gain" : "miji"; else if (rt === PICK_RT$1) st.localVars[PICK_RESULT_KEY$3] = { zone: params.zone, cardId: params.cardId ?? null, handIndex: params.handIndex ?? null }; }); registerAfterHook(state, skill.id, ownerId, "成为目标", async (ctx) => { const atom = ctx.atom; if (atom.target !== ownerId) return; const sourceId = atom.source; if (sourceId === void 0 || sourceId === ownerId) return; const cardId = atom.cardId; if (!cardId) return; const card = ctx.state.cardMap[cardId]; if (!isKillOrNormalTrick(card)) return; if (card.name !== "杀" && card.name !== "决斗") return; if (!ctx.state.players[ownerId]?.alive) return; if (isProcessedFor(ctx.state, cardId, ownerId)) return; ctx.state.localVars[processedKey(cardId, ownerId)] = true; await runZhenlie(ctx.state, ownerId, cardId, sourceId); }); registerBeforeHook(state, skill.id, ownerId, "请求回应", async (ctx) => { const atom = ctx.atom; if (atom.requestType !== "无懈可击") return; if (atom.cancelTarget !== ownerId) return; const frame = topFrame(ctx.state); if (!frame) return; if (frame.from === ownerId) return; const cardId = frame.params?.cardId; if (!cardId) return; const card = ctx.state.cardMap[cardId]; if (!isKillOrNormalTrick(card)) return; if (card.name === "杀") return; if (!ctx.state.players[ownerId]?.alive) return; if (isProcessedFor(ctx.state, cardId, ownerId)) return; ctx.state.localVars[processedKey(cardId, ownerId)] = true; await runZhenlie(ctx.state, ownerId, cardId, frame.from); }); registerCardInvalidation(state, skill.id, ownerId, (st, card) => isInvalidFor(st, card?.id, ownerId)); registerBeforeHook(state, skill.id, ownerId, "设横置", async (ctx) => { if (ctx.atom.player !== ownerId) return; const frame = topFrame(ctx.state); if (frame?.skillId !== "铁索连环") return; if (frame.from === ownerId) return; const cardId = frame.params?.cardId; if (!cardId) return; if (isInvalidFor(ctx.state, cardId, ownerId)) return; if (!ctx.state.players[ownerId]?.alive) return; if (isProcessedFor(ctx.state, cardId, ownerId)) return; ctx.state.localVars[processedKey(cardId, ownerId)] = true; await runZhenlie(ctx.state, ownerId, cardId, frame.from); if (isInvalidFor(ctx.state, cardId, ownerId)) return { kind: "cancel" }; }); return () => {}; } function onMount$79(_skill, api) { api.defineAction("respond", { label: DISPLAY_NAME$8, style: "danger", prompt: { type: "confirm", title: "是否发动贞烈?", confirmLabel: "发动", cancelLabel: "不发动" } }); } var DISPLAY_NAME$8, CONFIRM_RT$14, CHOOSE_RT$4, PICK_RT$1, CONFIRM_KEY$3, CHOOSE_KEY, INVALID_PREFIX, PROCESSED_PREFIX, _skillModule$30; var init_界贞烈 = __esmMin((() => { init_apply(); init_frame(); init_card_invalidation(); init_skill(); init_card_meta(); init_pick_card_panel(); init_vars_keys(); DISPLAY_NAME$8 = "贞烈"; CONFIRM_RT$14 = "界贞烈/confirm"; CHOOSE_RT$4 = "界贞烈/choose"; PICK_RT$1 = "界贞烈/选牌"; CONFIRM_KEY$3 = "贞烈/confirmed"; CHOOSE_KEY = "贞烈/choice"; INVALID_PREFIX = "贞烈/无效/"; PROCESSED_PREFIX = "贞烈/已处理/"; _skillModule$30 = { createSkill: createSkill$88, onInit: onInit$88, onMount: onMount$79 }; })); //#endregion //#region src/engine/skills/界趫猛.ts var 界趫猛_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$87, default: () => _skillModule$29, onInit: () => onInit$87, onMount: () => onMount$78 }); function createSkill$87(id, ownerId) { return { id, ownerId, name: "趫猛", description: "当你使用【杀】对一名角色造成伤害后,你可以弃置其区域里的一张牌。若此牌为坐骑牌,你获得之" }; } /** 是否是坐骑牌(进攻马/防御马) */ function isMountCard(card) { return !!card && card.type === "装备牌" && (card.subtype === "进攻马" || card.subtype === "防御马"); } /** 列出目标区域可被选的牌(用于 pickTargetCard prompt 的 equipment/judge/handCount) */ function buildPickOptions(state, target) { const tp = state.players[target]; if (!tp) return { equipment: [], judge: [], handCount: 0, hasCards: false }; const equipment = Object.entries(tp.equipment).filter(([, id]) => typeof id === "string").map(([slot, id]) => ({ slot, cardId: id, cardName: state.cardMap[id]?.name ?? "?" })); const judge = tp.pendingTricks.map((t) => ({ cardId: t.card.id, cardName: t.card.name })); const handCount = tp.hand.length; return { equipment, judge, handCount, hasCards: equipment.length > 0 || judge.length > 0 || handCount > 0 }; } /** 超时缺省选牌:明牌优先(装备第一张→判定第一张),否则 hand[0] */ function defaultPickZone(opts) { if (opts.equipment.length > 0) return { zone: "equipment", cardId: opts.equipment[0].cardId }; if (opts.judge.length > 0) return { zone: "judge", cardId: opts.judge[0].cardId }; return { zone: "hand", handIndex: 0 }; } function onInit$87(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "respond", (st, params) => { const slot = st.pendingSlots.get(ownerId); if (!slot) return "当前不需要回应"; const atom = slot.atom; if (atom.type !== "请求回应") return "当前不需要回应"; const rt = atom.requestType; if (rt !== CONFIRM_REQUEST$7 && rt !== PICK_REQUEST) return "当前不是趫猛询问"; if (rt === PICK_REQUEST) { const zone = params.zone; if (zone === "equipment" || zone === "judge") { if (typeof params.cardId !== "string") return "cardId required"; } else if (zone === "hand") { if (typeof params.handIndex !== "number") return "handIndex required"; } else return "zone required (equipment|judge|hand)"; } return null; }, async (st, params) => { const rt = (st.pendingSlots.get(ownerId)?.atom)?.requestType; if (rt === CONFIRM_REQUEST$7) st.localVars[CONFIRMED_KEY$14] = params.choice === true || params.confirmed === true; else if (rt === PICK_REQUEST) st.localVars[PICK_RESULT_KEY] = { zone: params.zone, cardId: params.cardId ?? null, handIndex: params.handIndex ?? null }; }); registerAfterHook(state, skill.id, ownerId, "造成伤害后", async (ctx) => { const atom = ctx.atom; if (atom.source !== ownerId) return; if ((atom.amount ?? 0) <= 0) return; if (atom.target === void 0) return; const target = atom.target; if (target === ownerId) return; if (!ctx.state.players[target]?.alive) return; const cardId = atom.cardId; if (typeof cardId !== "string") return; if (ctx.state.cardMap[cardId]?.name !== "杀") return; if (!ctx.state.players[ownerId]?.alive) return; const pickOpts = buildPickOptions(ctx.state, target); if (!pickOpts.hasCards) return; delete ctx.state.localVars[CONFIRMED_KEY$14]; await applyAtom(ctx.state, { type: "请求回应", requestType: CONFIRM_REQUEST$7, target: ownerId, prompt: { type: "confirm", title: "趫猛:是否弃置目标一张牌?", confirmLabel: "发动", cancelLabel: "不发动" }, defaultChoice: false, timeout: 10 }); if (ctx.state.localVars[CONFIRMED_KEY$14] !== true) return; const defaultZone = defaultPickZone(pickOpts); delete ctx.state.localVars[PICK_RESULT_KEY]; if (pickOpts.handCount > 0) spliceHandOrderEntry(ctx.state, target); const pickAtom = { type: "请求回应", requestType: PICK_REQUEST, target: ownerId, prompt: { type: "pickTargetCard", mode: "discard", title: "趫猛:选择目标一张牌(若是坐骑,获得之)", target, equipment: pickOpts.equipment, judge: pickOpts.judge, handCount: pickOpts.handCount }, defaultChoice: defaultZone, timeout: 20 }; await applyAtom(ctx.state, pickAtom); const result = ctx.state.localVars[PICK_RESULT_KEY]; delete ctx.state.localVars[PICK_RESULT_KEY]; const filteredEquip = pickAtom.prompt.equipment; let fallback = defaultZone; if (fallback.zone === "equipment") { if (!filteredEquip.some((e) => e.cardId === fallback.cardId)) fallback = filteredEquip.length > 0 ? { zone: "equipment", cardId: filteredEquip[0].cardId } : pickOpts.judge.length > 0 ? { zone: "judge", cardId: pickOpts.judge[0].cardId } : { zone: "hand", handIndex: 0 }; } const zone = result?.zone ?? fallback.zone; let pickedCardId; if (zone === "equipment" || zone === "judge") pickedCardId = result?.cardId ?? fallback.cardId ?? void 0; else { const handIndex = result?.handIndex ?? fallback.handIndex ?? 0; const tp = ctx.state.players[target]; pickedCardId = tp?.hand[handIndex] ?? tp?.hand[0]; } if (!pickedCardId) return; const pickedCard = ctx.state.cardMap[pickedCardId]; const isMount = isMountCard(pickedCard); if (zone === "judge") { const trick = ctx.state.players[target]?.pendingTricks.find((t) => t.card.id === pickedCardId); if (trick) { await applyAtom(ctx.state, { type: "移除延时锦囊", player: target, trickName: trick.name }); await applyAtom(ctx.state, { type: "弃置", player: target, cardIds: [pickedCardId] }); } } else if (isMount) await applyAtom(ctx.state, { type: "获得", player: ownerId, cardId: pickedCardId, from: target }); else await applyAtom(ctx.state, { type: "弃置", player: target, cardIds: [pickedCardId] }); }); return () => {}; } function onMount$78(_skill, _api) {} var CONFIRM_REQUEST$7, PICK_REQUEST, CONFIRMED_KEY$14, PICK_RESULT_KEY, _skillModule$29; var init_界趫猛 = __esmMin((() => { init_apply(); init_skill(); init_pick_card_panel(); CONFIRM_REQUEST$7 = "界趫猛/confirm"; PICK_REQUEST = "界趫猛/pick"; CONFIRMED_KEY$14 = "趫猛/confirmed"; PICK_RESULT_KEY = "趫猛/pickResult"; _skillModule$29 = { createSkill: createSkill$87, onInit: onInit$87, onMount: onMount$78 }; })); //#endregion //#region src/engine/skills/界连环.ts var 界连环_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$86, default: () => 界连环_default, onInit: () => onInit$86, onMount: () => onMount$77 }); /** 影子卡 id:${原id}#界连环(与标连环 ${原id}#连环 隔离) */ function shadowIdOf$7(cardId) { return `${cardId}#界连环`; } function isChained(state, idx) { return state.players[idx]?.marks.some((m) => m.id === CHAIN_MARK) ?? false; } function createSkill$86(id, ownerId) { return { id, ownerId, name: "界连环", description: "你可以将一张梅花牌当【铁索连环】使用或重铸;你使用【铁索连环】可以多指定一个目标(至多3名)" }; } function onInit$86(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "recycle", (state, params) => { const myTurn = state.currentPlayerIndex === ownerId; const inActPhase = state.phase === "出牌"; const free = !hasBlockingPending(state); const self = state.players[ownerId]; const selfAlive = self?.alive === true; const cardId = params.cardId; const cardIdOk = typeof cardId === "string"; const card = cardIdOk ? state.cardMap[cardId] : void 0; const cardInHand = cardIdOk && self.hand.includes(cardId); const isClub = !!card && card.suit === "♣"; return myTurn && inActPhase && free && selfAlive && cardInHand && isClub ? null : "现在不能重铸(需出牌阶段、手中梅花牌)"; }, async (state, params) => { const from = ownerId; const cardId = params.cardId; await pushFrame(state, "界连环", from, { ...params }); await recastCard(state, from, cardId); await popFrame(state); }); registerAction(state, skill.id, ownerId, "transform", (state, params) => { const self = state.players[ownerId]; const selfAlive = self?.alive === true; const cardId = params.cardId; const cardIdOk = typeof cardId === "string"; const card = cardIdOk ? state.cardMap[cardId] : void 0; const cardInHand = cardIdOk && self.hand.includes(cardId); const isClub = !!card && card.suit === "♣"; if (!selfAlive) return "你已死亡"; if (!cardIdOk || !cardInHand) return "牌不在手牌中"; if (!isClub) return "只能将梅花牌当铁索连环"; return null; }, async (state, params) => { const cardId = params.cardId; const shadowId = shadowIdOf$7(cardId); await applyAtom(state, { type: "当作", player: ownerId, cardIds: [cardId], shadowId, outputName: "铁索连环" }); }, (state, params) => { const cardId = params.cardId; const sId = shadowIdOf$7(cardId); delete state.cardMap[sId]; const self = state.players[ownerId]; const idx = self.hand.indexOf(sId); if (idx >= 0) self.hand[idx] = cardId; }); registerAction(state, "铁索连环", ownerId, "use", (state, params) => { const base = validateUseCard(state, ownerId, params, { cardName: "铁索连环" }); if (base) return base; const targets = params.targets; if (!Array.isArray(targets) || targets.length < 1 || targets.length > MAX_TARGETS) return `需选择一至${MAX_TARGETS}名角色`; for (const t of targets) if (!state.players[t]?.alive) return "目标不合法"; return null; }, async (state, params) => { const from = ownerId; const cardId = params.cardId; const targets = params.targets; await pushFrame(state, "铁索连环", from, { ...params }); await applyAtom(state, { type: "移动牌", cardId, from: { zone: "手牌", player: from }, to: { zone: "处理区" } }); try { if (!await promptCancel(state, { cardName: "无懈可击", broadcast: true }, from, from)) for (const t of targets) await setChain(state, t, !isChained(state, t)); await applyAtom(state, { type: "移动牌", cardId, from: { zone: "处理区" }, to: { zone: "弃牌堆" } }); } finally { if (frameCards(state).includes(cardId)) await applyAtom(state, { type: "移动牌", cardId, from: { zone: "处理区" }, to: { zone: "弃牌堆" } }); await popFrame(state); } }); return () => {}; } function onMount$77(skill, api) { api.defineAction("recycle", { label: "界连环·重铸", style: "primary", prompt: { type: "useCard", title: "界连环:弃一张梅花手牌,摸一张牌", cardFilter: { filter: (c) => c.suit === "♣", min: 1, max: 1 } }, activeWhen: (ctx) => { if (!defaultPlayActive(ctx)) return false; const p = ctx.view.players[ctx.perspectiveIdx]; if (!p) return false; return p.hand?.some((c) => c.suit === "♣") ?? false; } }); api.defineAction("transform", { label: "界连环", style: "primary", prompt: { type: "useCardAndTarget", title: "界连环:将一张梅花手牌当铁索连环使用(至多3名目标)", cardFilter: { filter: (c) => c.suit === "♣", min: 1, max: 1 }, targetFilter: { min: 1, max: MAX_TARGETS, allowSelf: true } }, transform: (card) => ({ name: "铁索连环", sourceCardId: card.id, fromSkill: skill.id }), activeWhen: (ctx) => { if (!defaultPlayActive(ctx)) return false; const p = ctx.view.players[ctx.perspectiveIdx]; if (!p) return false; return p.hand?.some((c) => c.suit === "♣") ?? false; } }); } var CHAIN_MARK, MAX_TARGETS, 界连环_default; var init_界连环 = __esmMin((() => { init_apply(); init_frame(); init_recast(); init_face_down(); init_skill(); init_cancel(); init_action_active(); CHAIN_MARK = "chained"; MAX_TARGETS = 3; 界连环_default = { createSkill: createSkill$86, onInit: onInit$86, onMount: onMount$77 }; })); //#endregion //#region src/engine/skills/界连营.ts var 界连营_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$85, default: () => 界连营_default, onInit: () => onInit$85, onMount: () => onMount$76 }); function createSkill$85(id, ownerId) { return { id, ownerId, name: "界连营", description: "被动技:当你失去所有手牌后,你可以令至多X名角色各摸一张牌(X为你失去的手牌数)" }; } function onInit$85(skill, state) { const ownerId = skill.ownerId; async function maybeTriggerLianYing(ctx, lostCount) { const self = ctx.state.players[ownerId]; if (!self?.alive) return; if (self.hand.length !== 0) return; const X = lostCount; if (X < 1) return; if (ctx.state.zones.deck.length === 0 && ctx.state.zones.discardPile.length === 0) return; const aliveCount = ctx.state.players.filter((p) => p.alive).length; const maxTargets = Math.min(X, aliveCount); if (maxTargets < 1) return; delete ctx.state.localVars[CONFIRMED_KEY$13]; await applyAtom(ctx.state, { type: "请求回应", requestType: TRIGGER_RT$1, target: ownerId, prompt: { type: "confirm", title: `是否发动界连营?(失去${X}张手牌,令至多${maxTargets}名角色各摸一张牌)`, confirmLabel: "发动", cancelLabel: "不发动" }, defaultChoice: false, timeout: 10 }); if (ctx.state.localVars[CONFIRMED_KEY$13] !== true) return; delete ctx.state.localVars[TARGETS_KEY$1]; await applyAtom(ctx.state, { type: "请求回应", requestType: SELECT_RT$2, target: ownerId, prompt: { type: "choosePlayer", title: `界连营:选择至多${maxTargets}名角色(各摸一张牌)`, min: 1, max: maxTargets, filter: (view, target) => view.players[target]?.alive === true }, timeout: 20 }); const rawTargets = ctx.state.localVars[TARGETS_KEY$1]; const targets = (Array.isArray(rawTargets) ? rawTargets.filter((t, i, arr) => ctx.state.players[t]?.alive === true && arr.indexOf(t) === i) : []).slice(0, maxTargets); for (const target of targets) await applyAtom(ctx.state, { type: "摸牌", player: target, count: 1 }); } function consumeLostCount(st) { const before = st.localVars[HAND_BEFORE_KEY$1]; delete st.localVars[HAND_BEFORE_KEY$1]; return typeof before === "number" ? before : void 0; } registerAction(state, skill.id, ownerId, "respond", (st, _params) => { const slot = st.pendingSlots.get(ownerId); if (!slot) return "当前不需要回应"; const atom = slot.atom; if (atom.type !== "请求回应") return "当前不是请求回应"; if (atom.requestType !== TRIGGER_RT$1 && atom.requestType !== SELECT_RT$2) return "当前不是界连营回应"; return null; }, async (st, params) => { const rt = (st.pendingSlots.get(ownerId)?.atom)?.requestType; if (rt === TRIGGER_RT$1) st.localVars[CONFIRMED_KEY$13] = params.choice === true || params.confirmed === true; else if (rt === SELECT_RT$2) { const targets = params.targets ?? (typeof params.target === "number" ? [params.target] : void 0); st.localVars[TARGETS_KEY$1] = targets ?? []; } }); registerBeforeHook(state, skill.id, ownerId, "移动牌", async (ctx) => { const atom = ctx.atom; if (atom.type !== "移动牌") return; if (atom.from.zone !== "手牌") return; if (atom.from.player !== ownerId) return; if (atom.to.zone === "手牌" && atom.to.player === ownerId) return; ctx.state.localVars[HAND_BEFORE_KEY$1] = ctx.state.players[ownerId]?.hand.length ?? 0; }); registerAfterHook(state, skill.id, ownerId, "移动牌", async (ctx) => { const atom = ctx.atom; if (atom.type !== "移动牌") return; if (atom.from.zone !== "手牌") return; if (atom.from.player !== ownerId) return; if (atom.to.zone === "手牌" && atom.to.player === ownerId) return; const lost = consumeLostCount(ctx.state); if (lost === void 0) return; await maybeTriggerLianYing(ctx, lost); }); registerBeforeHook(state, skill.id, ownerId, "给予", async (ctx) => { const atom = ctx.atom; if (atom.type !== "给予") return; if (atom.from !== ownerId) return; if (atom.to === ownerId) return; ctx.state.localVars[HAND_BEFORE_KEY$1] = ctx.state.players[ownerId]?.hand.length ?? 0; }); registerAfterHook(state, skill.id, ownerId, "给予", async (ctx) => { const atom = ctx.atom; if (atom.type !== "给予") return; if (atom.from !== ownerId) return; if (atom.to === ownerId) return; const lost = consumeLostCount(ctx.state); if (lost === void 0) return; await maybeTriggerLianYing(ctx, lost); }); registerBeforeHook(state, skill.id, ownerId, "装备", async (ctx) => { const atom = ctx.atom; if (atom.type !== "装备") return; if (atom.player !== ownerId) return; ctx.state.localVars[HAND_BEFORE_KEY$1] = ctx.state.players[ownerId]?.hand.length ?? 0; }); registerAfterHook(state, skill.id, ownerId, "装备", async (ctx) => { const atom = ctx.atom; if (atom.type !== "装备") return; if (atom.player !== ownerId) return; const lost = consumeLostCount(ctx.state); if (lost === void 0) return; await maybeTriggerLianYing(ctx, lost); }); registerBeforeHook(state, skill.id, ownerId, "移出至暂存区", async (ctx) => { const atom = ctx.atom; if (atom.type !== "移出至暂存区") return; if (atom.target !== ownerId) return; ctx.state.localVars[HAND_BEFORE_KEY$1] = ctx.state.players[ownerId]?.hand.length ?? 0; }); registerAfterHook(state, skill.id, ownerId, "移出至暂存区", async (ctx) => { const atom = ctx.atom; if (atom.type !== "移出至暂存区") return; if (atom.target !== ownerId) return; const lost = consumeLostCount(ctx.state); if (lost === void 0) return; await maybeTriggerLianYing(ctx, lost); }); registerBeforeHook(state, skill.id, ownerId, "获得", async (ctx) => { const atom = ctx.atom; if (atom.type !== "获得") return; if (atom.from !== ownerId) return; if (atom.player === ownerId) return; const hand = ctx.state.players[ownerId]?.hand ?? []; if (atom.cardId && hand.includes(atom.cardId)) ctx.state.localVars[HAND_BEFORE_KEY$1] = hand.length; }); registerAfterHook(state, skill.id, ownerId, "获得", async (ctx) => { const atom = ctx.atom; if (atom.type !== "获得") return; if (atom.from !== ownerId) return; if (atom.player === ownerId) return; const lost = consumeLostCount(ctx.state); if (lost === void 0) return; await maybeTriggerLianYing(ctx, lost); }); registerBeforeHook(state, skill.id, ownerId, "弃置", async (ctx) => { const atom = ctx.atom; if (atom.type !== "弃置") return; if (atom.player !== ownerId) return; const hand = ctx.state.players[ownerId]?.hand ?? []; if (atom.cardIds?.some((id) => hand.includes(id))) ctx.state.localVars[HAND_BEFORE_KEY$1] = hand.length; }); registerAfterHook(state, skill.id, ownerId, "弃置", async (ctx) => { const atom = ctx.atom; if (atom.type !== "弃置") return; if (atom.player !== ownerId) return; const lost = consumeLostCount(ctx.state); if (lost === void 0) return; await maybeTriggerLianYing(ctx, lost); }); return () => {}; } function onMount$76(_skill, api) { api.defineAction("respond", { label: "界连营", style: "default", prompt: { type: "confirm", title: "是否发动界连营?", confirmLabel: "发动(令角色摸牌)", cancelLabel: "不发动" } }); } var TRIGGER_RT$1, SELECT_RT$2, CONFIRMED_KEY$13, TARGETS_KEY$1, HAND_BEFORE_KEY$1, 界连营_default; var init_界连营 = __esmMin((() => { init_apply(); init_skill(); TRIGGER_RT$1 = "界连营/trigger"; SELECT_RT$2 = "界连营/select"; CONFIRMED_KEY$13 = "界连营/confirmed"; TARGETS_KEY$1 = "界连营/targets"; HAND_BEFORE_KEY$1 = "界连营/handBefore"; 界连营_default = { createSkill: createSkill$85, onInit: onInit$85, onMount: onMount$76 }; })); //#endregion //#region src/engine/skills/界遗计.ts var 界遗计_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$84, default: () => _skillModule$28, onInit: () => onInit$84, onMount: () => onMount$75 }); function createSkill$84(id, ownerId) { return { id, ownerId, name: "界遗计", description: "受到 1 点伤害后,摸两张牌,然后可以将至多两张手牌交给至多两名其他角色" }; } function onInit$84(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "respond", (st, params) => { const slot = st.pendingSlots.get(ownerId); if (!slot) return "当前不需要回应"; const atom = slot.atom; if (atom["type"] !== "请求回应") return "当前不需要回应"; const rt = atom["requestType"]; if (rt !== CONFIRM_RT$13 && rt !== GIVE_RT$2) return "当前不是遗计询问"; if (rt === CONFIRM_RT$13) return null; const allocation = params.allocation; if (!Array.isArray(allocation) || allocation.length === 0) return "请选择分配方案"; if (allocation.length > 2) return "至多交给两名其他角色"; if (allocation.reduce((n, e) => n + e.cardIds.length, 0) > 2) return "至多交出两张手牌"; const seenCards = /* @__PURE__ */ new Set(); for (const e of allocation) { if (e.cardIds.length === 0) return "分配数量不合法"; if (e.target === ownerId) return "不能交给自己"; if (!st.players[e.target]?.alive) return "目标无效"; for (const cid of e.cardIds) { if (seenCards.has(cid)) return "存在重复的牌"; seenCards.add(cid); if (!st.players[ownerId]?.hand.includes(cid)) return "牌不在手牌中"; } } return null; }, async (st, params) => { const rt = (st.pendingSlots.get(ownerId)?.atom)?.requestType; if (rt === CONFIRM_RT$13) st.localVars[CONFIRMED_KEY$12] = params.choice === true || params.confirmed === true; else if (rt === GIVE_RT$2) st.localVars[ALLOC_KEY$1] = params.allocation ?? null; }); registerAfterHook(state, skill.id, ownerId, "伤害结算结束后", async (ctx) => { const atom = ctx.atom; if (atom.target !== ownerId) return; if (!ctx.state.players[ownerId]?.alive) return; const amount = atom.amount ?? 0; if (amount <= 0) return; for (let i = 0; i < amount; i++) { if (!ctx.state.players[ownerId]?.alive) break; await applyAtom(ctx.state, { type: "摸牌", player: ownerId, count: 2 }); const self = ctx.state.players[ownerId]; if (!self?.alive || self.hand.length === 0) continue; if (!ctx.state.players.some((p, idx) => idx !== ownerId && p.alive)) continue; delete ctx.state.localVars[CONFIRMED_KEY$12]; await applyAtom(ctx.state, { type: "请求回应", requestType: CONFIRM_RT$13, target: ownerId, prompt: { type: "confirm", title: "遗计:是否将至多两张手牌交给至多两名其他角色?", confirmLabel: "交牌", cancelLabel: "不交" }, defaultChoice: false, timeout: 15 }); if (!ctx.state.localVars[CONFIRMED_KEY$12]) { delete ctx.state.localVars[CONFIRMED_KEY$12]; continue; } delete ctx.state.localVars[ALLOC_KEY$1]; await applyAtom(ctx.state, { type: "请求回应", requestType: GIVE_RT$2, target: ownerId, prompt: { type: "distribute", mode: "allocate", title: "遗计:将至多两张手牌交给至多两名其他角色", source: "hand", minPerTarget: 1, maxPerTarget: 2, minTotal: 1, maxTotal: 2, allowSelf: false, targetFilter: (_view, t) => t !== ownerId && ctx.state.players[t]?.alive === true }, timeout: 30 }); const allocation = ctx.state.localVars[ALLOC_KEY$1]; delete ctx.state.localVars[CONFIRMED_KEY$12]; delete ctx.state.localVars[ALLOC_KEY$1]; if (!Array.isArray(allocation) || allocation.length === 0) continue; if (allocation.length > 2) continue; if (allocation.reduce((n, e) => n + e.cardIds.length, 0) > 2) continue; for (const entry of allocation) { if (entry.target === ownerId) continue; if (!ctx.state.players[entry.target]?.alive) continue; for (const cardId of entry.cardIds) { if (!ctx.state.players[ownerId]?.hand.includes(cardId)) continue; await applyAtom(ctx.state, { type: "给予", cardId, from: ownerId, to: entry.target }); } } } }); return () => {}; } function onMount$75(_skill, api) { api.defineAction("respond", { label: "界遗计", style: "primary", prompt: { type: "confirm", title: "是否发动遗计?", confirmLabel: "发动", cancelLabel: "不发动" } }); } var CONFIRM_RT$13, GIVE_RT$2, CONFIRMED_KEY$12, ALLOC_KEY$1, _skillModule$28; var init_界遗计 = __esmMin((() => { init_apply(); init_skill(); CONFIRM_RT$13 = "界遗计/giveConfirm"; GIVE_RT$2 = "界遗计/giveCard"; CONFIRMED_KEY$12 = "遗计/confirmed"; ALLOC_KEY$1 = "遗计/allocation"; _skillModule$28 = { createSkill: createSkill$84, onInit: onInit$84, onMount: onMount$75 }; })); //#endregion //#region src/engine/skills/界酒池.ts var 界酒池_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$83, default: () => 界酒池_default, onInit: () => onInit$83, onMount: () => onMount$74 }); function createSkill$83(id, ownerId) { return { id, ownerId, name: "界酒池", description: "转化技:黑桃手牌当酒使用,无次数限制;使用酒杀造成伤害后本回合崩坏失效" }; } /** 影子卡 id:${原id}#界酒池(与标版酒池隔离) */ function shadowIdOf$6(cardId) { return `${cardId}#界酒池`; } function onInit$83(skill, state) { const ownerId = skill.ownerId; const self0 = state.players[ownerId]; if (self0 && !self0.tags.includes(WINE_UNLIMITED_TAG)) self0.tags.push(WINE_UNLIMITED_TAG); registerAction(state, skill.id, ownerId, "transform", (state, params) => { const myTurn = state.currentPlayerIndex === ownerId; const free = !hasBlockingPending(state); const self = state.players[ownerId]; const selfAlive = self.alive === true; const cardId = params.cardId; const cardIdOk = typeof cardId === "string"; const card = cardIdOk ? state.cardMap[cardId] : void 0; const cardInHand = cardIdOk && self.hand.includes(cardId); const isSpade = !!card && card.suit === "♠"; return myTurn && free && selfAlive && cardInHand && isSpade ? null : "现在不能使用界酒池"; }, async (state, params) => { const cardId = params.cardId; const shadowId = shadowIdOf$6(cardId); await applyAtom(state, { type: "当作", player: ownerId, cardIds: [cardId], shadowId, outputName: "酒" }); }, (state, params) => { const cardId = params.cardId; const sId = shadowIdOf$6(cardId); delete state.cardMap[sId]; const self = state.players[ownerId]; const idx = self.hand.indexOf(sId); if (idx >= 0) self.hand[idx] = cardId; }); registerAfterHook(state, skill.id, ownerId, "去标记", async (ctx) => { const atom = ctx.atom; if (atom.type !== "去标记") return; if (atom.player !== ownerId) return; if (atom.markId !== "酒/nextKillDamageBonus") return; ctx.state.turn.vars[DISABLE_BENGHUAI_VAR] = true; }); const unloadDecl = declareAlternativeResponse(state, ownerId, "请求回应", "桃/求桃"); return () => { const p = state.players[ownerId]; if (p) p.tags = p.tags.filter((t) => t !== WINE_UNLIMITED_TAG); unloadDecl(); }; } function onMount$74(skill, api) { api.defineAction("transform", { label: "界酒池", style: "passive", prompt: { type: "useCard", title: "选择一张黑桃手牌当酒使用(无次数限制;酒杀造成伤害后本回合崩坏失效)", cardFilter: { filter: (c) => c.suit === "♠", min: 1, max: 1 } }, transform: (card) => ({ name: "酒", sourceCardId: card.id, fromSkill: skill.id }), activeWhen: (ctx) => { if (!defaultPlayActive(ctx)) return false; const p = ctx.view.players[ctx.perspectiveIdx]; if (!p) return false; return p.hand?.some((c) => c.suit === "♠") ?? false; } }); } var DISABLE_BENGHUAI_VAR, WINE_UNLIMITED_TAG, 界酒池_default; var init_界酒池 = __esmMin((() => { init_skill(); init_apply(); init_action_active(); DISABLE_BENGHUAI_VAR = "崩坏/disabled"; WINE_UNLIMITED_TAG = "酒/无次数限制"; 界酒池_default = { createSkill: createSkill$83, onInit: onInit$83, onMount: onMount$74 }; })); //#endregion //#region src/engine/skills/界酒诗.ts var 界酒诗_exports = /* @__PURE__ */ __exportAll({ FLIP_BACK_KEY: () => FLIP_BACK_KEY, FLIP_BACK_RT: () => FLIP_BACK_RT, createSkill: () => createSkill$82, default: () => _skillModule$27, onInit: () => onInit$82, onMount: () => onMount$73 }); function createSkill$82(id, ownerId) { return { id, ownerId, name: DISPLAY_NAME$7, description: "翻面视为使用酒;背面朝上时落英牌增强;受伤后或回合外累计获X张落英牌后可翻回正面" }; } /** 武将牌是否背面朝上(存在任意 '/翻面' 后缀标签) */ function isFlipped(tags) { return tags.some((t) => t.endsWith("/翻面")); } /** 翻回正面:清除所有 '/翻面' 后缀标签,并补发「翻面后」时机(与 flipFaceDown 对称, * 供解围等消费者感知翻回正面)。 */ async function flipBackToFaceUp(state, ownerId) { const flipTags = (state.players[ownerId]?.tags ?? []).filter((t) => t.endsWith("/翻面")); for (const tag of flipTags) await applyAtom(state, { type: "去标签", player: ownerId, tag }); if (flipTags.length > 0) await applyAtom(state, { type: "翻面后", player: ownerId, faceDown: false }); } /** 视为使用一张【酒】:走 runUseFlow virtual(resolve=加增伤标记) */ async function virtualWine(state, source) { const cardId = `${SKILL_ID$9}:酒:${source}:${state.seq}`; state.cardMap[cardId] = { id: cardId, name: "酒", suit: "", color: "无色", rank: "A", type: "基本牌" }; await runUseFlow(state, source, cardId, [source], "酒", { virtual: true }); delete state.cardMap[cardId]; } function onInit$82(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "use", (st, _params) => { const myTurn = st.currentPlayerIndex === ownerId; const inPlayPhase = st.phase === "出牌"; const free = !hasBlockingPending(st); const self = st.players[ownerId]; const selfAlive = self?.alive === true; const faceUp = !isFlipped(self?.tags ?? []); return myTurn && inPlayPhase && free && selfAlive && faceUp ? null : "现在不能发动酒诗"; }, async (st, _params) => { await pushFrame(st, SKILL_ID$9, ownerId, {}); try { await applyAtom(st, { type: "加标签", player: ownerId, tag: FLIP_TAG$1 }); await applyAtom(st, { type: "翻面后", player: ownerId, faceDown: true }); await virtualWine(st, ownerId); } finally { await popFrame(st); } }); registerAction(state, skill.id, ownerId, "respond", (s, _params) => { const slot = s.pendingSlots.get(ownerId); if (slot?.atom.type !== "请求回应") return "当前不需要回应"; const rt = slot.atom.requestType; if (rt !== USE_RT_CONFIRM && rt !== DAMAGE_FLIP_RT && rt !== "界酒诗/flipBack") return "当前不是酒诗询问"; return null; }, async (s, params) => { const rt = (s.pendingSlots.get(ownerId)?.atom).requestType; const choice = params.choice === true; if (rt === USE_RT_CONFIRM) s.localVars[USE_CONFIRM_KEY] = choice; else if (rt === DAMAGE_FLIP_RT) s.localVars[DAMAGE_FLIP_KEY] = choice; else if (rt === "界酒诗/flipBack") s.localVars[FLIP_BACK_KEY] = choice; }); registerAfterHook(state, skill.id, ownerId, "受到伤害后", async (ctx) => { const atom = ctx.atom; if (atom.target !== ownerId) return; if ((atom.amount ?? 0) <= 0) return; const self = ctx.state.players[ownerId]; if (!self?.alive) return; if (!isFlipped(self.tags)) return; delete ctx.state.localVars[DAMAGE_FLIP_KEY]; await applyAtom(ctx.state, { type: "请求回应", requestType: DAMAGE_FLIP_RT, target: ownerId, prompt: { type: "confirm", title: "酒诗:受到伤害后,是否将武将牌翻至正面?", confirmLabel: "翻至正面", cancelLabel: "保持背面" }, defaultChoice: false, timeout: 10 }); const flip = ctx.state.localVars[DAMAGE_FLIP_KEY] === true; delete ctx.state.localVars[DAMAGE_FLIP_KEY]; if (!flip) return; await flipBackToFaceUp(ctx.state, ownerId); }); return () => {}; } function onMount$73(_skill, api) { api.defineAction("use", { label: DISPLAY_NAME$7, style: "primary", prompt: { type: "confirm", title: "是否发动酒诗?(翻至背面,视为使用一张酒)", confirmLabel: "发动", cancelLabel: "不发动" }, activeWhen: (ctx) => { if (ctx.view.currentPlayerIndex !== ctx.perspectiveIdx) return false; if (ctx.view.phase !== "出牌") return false; if (!ctx.view.players[ctx.perspectiveIdx]) return false; return true; } }); api.defineAction("respond", { label: DISPLAY_NAME$7, style: "default", prompt: { type: "confirm", title: "酒诗", confirmLabel: "确认", cancelLabel: "取消" } }); } var SKILL_ID$9, DISPLAY_NAME$7, FLIP_TAG$1, USE_RT_CONFIRM, USE_CONFIRM_KEY, DAMAGE_FLIP_RT, DAMAGE_FLIP_KEY, FLIP_BACK_RT, FLIP_BACK_KEY, _skillModule$27; var init_界酒诗 = __esmMin((() => { init_apply(); init_frame(); init_skill(); init_use_card(); SKILL_ID$9 = "界酒诗"; DISPLAY_NAME$7 = "酒诗"; FLIP_TAG$1 = "酒诗/翻面"; USE_RT_CONFIRM = "界酒诗/confirm"; USE_CONFIRM_KEY = "酒诗/useConfirmed"; DAMAGE_FLIP_RT = "界酒诗/damageFlip"; DAMAGE_FLIP_KEY = "酒诗/damageFlipChoice"; FLIP_BACK_RT = "界酒诗/flipBack"; FLIP_BACK_KEY = "酒诗/flipChoice"; _skillModule$27 = { createSkill: createSkill$82, onInit: onInit$82, onMount: onMount$73 }; })); //#endregion //#region src/engine/skills/界醇醪.ts var 界醇醪_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$81, default: () => 界醇醪_default, onInit: () => onInit$81, onMount: () => onMount$72 }); function createSkill$81(id, ownerId) { return { id, ownerId, name: DISPLAY_NAME$6, description: "你或相邻角色的【杀】因弃置置入弃牌堆后,将之置于你的武将牌上为\"醇\";濒死时你可移去X张\"醇\"视为一张【酒】(X为本轮以此法使用酒的次数)" }; } /** 取 owner 当前的醇列表(可变引用,直接修改即可) */ function getAleTokenIds(state, ownerId) { const v = state.players[ownerId].vars[醇_KEY]; return Array.isArray(v) ? v : []; } /** 取 owner 的相邻角色座次列表(存活) */ function getNeighbors(state, ownerId) { const n = state.players.length; if (n <= 1) return []; const prev = (ownerId - 1 + n) % n; const next = (ownerId + 1) % n; const result = []; if (prev !== ownerId && state.players[prev]?.alive) result.push(prev); if (next !== ownerId && next !== prev && state.players[next]?.alive) result.push(next); return result; } /** 计算本次使用的 X 值(并按需重置轮次计数) */ function computeX$1(state, ownerId) { const player = state.players[ownerId]; const lastRound = player.vars[LAST_ROUND_KEY]; const currentRound = state.turn.round; if (lastRound !== currentRound) { player.vars[USED_ROUND_KEY] = 0; player.vars[LAST_ROUND_KEY] = currentRound; } return (player.vars[USED_ROUND_KEY] ?? 0) + 1; } function onInit$81(skill, state) { const ownerId = skill.ownerId; registerAfterHook(state, skill.id, ownerId, "弃置", async (ctx) => { const atom = ctx.atom; const discardPlayer = atom.player; if (discardPlayer === ownerId) {} else if (!getNeighbors(ctx.state, ownerId).includes(discardPlayer)) return; const slashIds = atom.cardIds.filter((id) => { const c = ctx.state.cardMap[id]; return !!c && c.name === "杀"; }); if (slashIds.length === 0) return; const list = getAleTokenIds(ctx.state, ownerId); const existing = new Set(list); for (const id of slashIds) if (!existing.has(id)) { list.push(id); existing.add(id); } ctx.state.players[ownerId].vars[醇_KEY] = list; }); registerAction(state, skill.id, ownerId, "respond", (s) => { const slot = s.pendingSlots.get(ownerId); if (!slot) return "当前不需要回应"; if (slot.atom.target !== ownerId) return "不是问你的"; if (slot.atom.type !== "请求回应") return "当前不是求桃"; if (slot.atom.requestType !== "桃/求桃") return "当前不是求桃"; const x = computeX$1(s, ownerId); if (getAleTokenIds(s, ownerId).length < x) return "醇数不足"; return null; }, async (s) => { const x = computeX$1(s, ownerId); const list = getAleTokenIds(s, ownerId); const removed = list.splice(0, x); s.players[ownerId].vars[醇_KEY] = list; const used = s.players[ownerId].vars[USED_ROUND_KEY] ?? 0; s.players[ownerId].vars[USED_ROUND_KEY] = used + 1; s.localVars["求桃/已救"] = true; s.localVars["醇醪/lastUsed"] = removed; }); const unloadDecl = declareAlternativeResponse(state, ownerId, "请求回应", "桃/求桃"); return () => { unloadDecl(); }; } function onMount$72(_skill, api) { api.defineAction("respond", { label: "醇醪", style: "primary", respondFor: "桃/求桃", prompt: { type: "confirm", title: "醇醪:移去 X 张\"醇\"视为一张【酒】救援?", confirmLabel: "移醇当酒", cancelLabel: "不发动" } }); } var DISPLAY_NAME$6, 醇_KEY, USED_ROUND_KEY, LAST_ROUND_KEY, 界醇醪_default; var init_界醇醪 = __esmMin((() => { init_skill(); DISPLAY_NAME$6 = "醇醪"; 醇_KEY = "醇醪/醇"; USED_ROUND_KEY = "醇醪/usedThisRound"; LAST_ROUND_KEY = "醇醪/lastRound"; 界醇醪_default = { createSkill: createSkill$81, onInit: onInit$81, onMount: onMount$72 }; })); //#endregion //#region src/engine/skills/界铁骑.ts var 界铁骑_exports = /* @__PURE__ */ __exportAll({ SUPPRESSION_TAG: () => SUPPRESSION_TAG, createSkill: () => createSkill$80, default: () => 界铁骑_default, onInit: () => onInit$80, onMount: () => onMount$71 }); function createSkill$80(id, ownerId) { return { id, ownerId, name: "界铁骑", description: "使用杀指定目标后,可令其本回合非锁定技失效并判定;除非其弃一张与判定结果同花色的牌,否则不能抵消此杀" }; } function onInit$80(skill, state) { const ownerId = skill.ownerId; const unloadSuppression = registerSuppressionProvider(state, (st, targetOwnerId, _skillId) => st.players[targetOwnerId]?.tags.includes(SUPPRESSION_TAG) === true); for (const p of state.players) { const pid = p.index; registerAction(state, skill.id, pid, "respond", (st, params) => { const slot = st.pendingSlots.get(pid); if (!slot) return "当前不需要回应"; const atom = slot.atom; if (atom.type !== "请求回应") return "当前不是请求回应"; if (atom.requestType === "界铁骑/confirm") return null; if (atom.requestType === DISCARD_REQUEST$1) { const cardId = params.cardId; if (typeof cardId !== "string") return "请选择一张手牌弃置"; if (!st.players[pid].hand.includes(cardId)) return "牌不在手牌中"; const suit = st.localVars[suitKey(pid)]; const card = st.cardMap[cardId]; if (!suit || !card) return "无法校验花色"; if (card.suit !== suit) return "花色与判定结果不符"; return null; } return "当前不是铁骑回应"; }, async (st, params) => { const reqType = (st.pendingSlots.get(pid)?.atom)?.requestType; if (reqType === "界铁骑/confirm") st.localVars[CONFIRM$3] = params.choice === true || params.confirmed === true; else if (reqType === DISCARD_REQUEST$1) st.localVars[DISCARD_CARD] = params.cardId; }); } registerAfterHook(state, skill.id, ownerId, "指定目标", async (ctx) => { const atom = ctx.atom; if (atom.source !== ownerId) return; if (atom.target === void 0) return; const target = atom.target; if (atom.cardId !== void 0) { if (ctx.state.cardMap[atom.cardId]?.name !== "杀") return; } const targetPlayer = ctx.state.players[target]; if (!targetPlayer?.alive) return; if (ctx.state.zones.deck.length === 0) return; delete ctx.state.localVars[CONFIRM$3]; await applyAtom(ctx.state, { type: "请求回应", requestType: "界铁骑/confirm", target: ownerId, prompt: { type: "confirm", title: "是否发动铁骑(令目标本回合非锁定技失效并判定)?", confirmLabel: "发动", cancelLabel: "不发动" }, defaultChoice: false, timeout: 10 }); if (!ctx.state.localVars[CONFIRM$3]) return; if (!targetPlayer.tags.includes("界铁骑/非锁定技失效")) await applyAtom(ctx.state, { type: "加标签", player: target, tag: SUPPRESSION_TAG }); ctx.state.localVars[TARGET_VAR$2] = target; await runJudgeFlow(ctx.state, ownerId, "铁骑"); }); registerAfterHook(state, skill.id, ownerId, "判定牌生效后", async (ctx) => { const atom = ctx.atom; if (atom.judgeType !== "铁骑") return; if (atom.player !== ownerId) return; const target = ctx.state.localVars[TARGET_VAR$2]; delete ctx.state.localVars[TARGET_VAR$2]; if (target === void 0) return; if (!ctx.state.players[target]?.alive) return; const cards = frameCards(ctx.state); if (cards.length === 0) return; const judgeCardId = cards[cards.length - 1]; const judgeCard = ctx.state.cardMap[judgeCardId]; if (!judgeCard) return; const suit = judgeCard.suit || ""; ctx.state.localVars[suitKey(target)] = suit; }); registerBeforeHook(state, skill.id, ownerId, "询问闪", async (ctx) => { const atom = ctx.atom; if (atom.source !== ownerId) return; const target = atom.target; if (target === void 0) return; const sKey = suitKey(target); const suit = ctx.state.localVars[sKey]; if (suit === void 0) return; const player = ctx.state.players[target]; if (!player?.alive) return; if (!player.hand.some((id) => ctx.state.cardMap[id]?.suit === suit)) { delete ctx.state.localVars[sKey]; return { kind: "cancel" }; } delete ctx.state.localVars[DISCARD_CARD]; await applyAtom(ctx.state, { type: "请求回应", requestType: DISCARD_REQUEST$1, target, prompt: { type: "useCard", title: `界铁骑:弃置一张${suit}花色手牌,否则不能出闪抵消此杀`, cardFilter: { filter: (c) => c.suit === suit, min: 1, max: 1 } }, timeout: 15 }); delete ctx.state.localVars[sKey]; const discardCardId = ctx.state.localVars[DISCARD_CARD]; delete ctx.state.localVars[DISCARD_CARD]; if (discardCardId && player.hand.includes(discardCardId)) { await applyAtom(ctx.state, { type: "弃置", player: target, cardIds: [discardCardId] }); return; } return { kind: "cancel" }; }); registerAfterHook(state, skill.id, ownerId, "回合结束", async (ctx) => { if (ctx.atom.player !== ownerId) return; for (const p of ctx.state.players) if (p.tags.includes("界铁骑/非锁定技失效")) await applyAtom(ctx.state, { type: "去标签", player: p.index, tag: SUPPRESSION_TAG }); }); return () => { unloadSuppression(); }; } function onMount$71(_skill, api) { api.defineAction("respond", { label: "铁骑", style: "default", prompt: { type: "confirm", title: "是否发动铁骑判定?", confirmLabel: "发动", cancelLabel: "不发动" } }); } var SUPPRESSION_TAG, CONFIRM$3, TARGET_VAR$2, suitKey, DISCARD_CARD, DISCARD_REQUEST$1, 界铁骑_default; var init_界铁骑 = __esmMin((() => { init_apply(); init_frame(); init_judge(); init_skill(); init_skill_suppression(); SUPPRESSION_TAG = "界铁骑/非锁定技失效"; CONFIRM$3 = "铁骑/confirmed"; TARGET_VAR$2 = "铁骑/target"; suitKey = (target) => `界铁骑/suit/${target}`; DISCARD_CARD = "铁骑/discardCard"; DISCARD_REQUEST$1 = "界铁骑/discard"; 界铁骑_default = { createSkill: createSkill$80, onInit: onInit$80, onMount: onMount$71 }; })); //#endregion //#region src/engine/skills/界镇军.ts var 界镇军_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$79, default: () => _skillModule$26, onInit: () => onInit$79, onMount: () => onMount$70 }); function createSkill$79(id, ownerId) { return { id, ownerId, name: DISPLAY_NAME$5, description: "准备阶段,弃置一名角色X张牌(X为其手牌数减体力值且至少为1),然后选择:1.你弃置等量非装备牌数的牌;2.结束阶段其摸等量非装备牌数的牌" }; } /** 角色总牌数(手牌+装备区) */ function totalCardCount(p) { const equipCount = Object.values(p.equipment).filter((id) => !!id).length; return p.hand.length + equipCount; } /** 计算镇军 X 值:target.hand.length - target.health,最小 1 */ function computeX(target) { return Math.max(1, target.hand.length - getHealthValue(target)); } /** 目标牌中非装备牌数(即手牌数,因为目标牌来源只能是手牌或装备区) */ function countNonEquipment(state, target, cardIds) { const equipSet = new Set(Object.values(state.players[target]?.equipment ?? {}).filter((id) => !!id)); return cardIds.filter((id) => !equipSet.has(id)).length; } function onInit$79(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "respond", (st, params) => { const slot = st.pendingSlots.get(ownerId); if (!slot) return "当前不需要回应"; const atom = slot.atom; if (atom["type"] !== "请求回应") return "当前不需要回应"; const rt = atom["requestType"]; if (rt !== RT_ASK_USE && rt !== RT_ASK_TARGET && rt !== RT_ASK_CARDS && rt !== RT_ASK_OPTION && rt !== RT_ASK_OWN) return "当前不是界镇军回应"; if (rt === RT_ASK_USE) return null; if (rt === RT_ASK_TARGET) { if (((typeof params.target === "number" ? params.target : void 0) ?? (Array.isArray(params.targets) && typeof params.targets[0] === "number" ? params.targets[0] : void 0)) === void 0) return "需要选择目标"; return null; } if (rt === RT_ASK_CARDS) { const cardIds = params.cardIds; if (!Array.isArray(cardIds)) return "需要选择牌"; const promptTarget = st.localVars[CTX_TARGET]; const promptX = st.localVars[CTX_X]; if (typeof promptTarget !== "number") return "询问上下文异常"; const targetPlayer = st.players[promptTarget]; if (!targetPlayer) return "目标不存在"; const equipSet = new Set(Object.values(targetPlayer.equipment).filter((id) => !!id)); for (const id of cardIds) { if (typeof id !== "string") return "cardIds 必须为字符串数组"; if (!targetPlayer.hand.includes(id) && !equipSet.has(id)) return "牌不在目标区域"; } if (typeof promptX === "number" && cardIds.length !== promptX) return `需要选择 ${promptX} 张牌`; if (new Set(cardIds).size !== cardIds.length) return "不能重复选牌"; return null; } if (rt === RT_ASK_OPTION) { const opt = params.option; if (opt !== "self" && opt !== "defer") { if (typeof params.choice === "boolean") return null; return "option 必须为 'self' 或 'defer'"; } if (opt === "self") { const promptN = st.localVars[CTX_N]; const myHand = st.players[ownerId]?.hand.length ?? 0; if (typeof promptN === "number" && myHand < promptN) return `手牌不足,不能选选项1(需 ${promptN} 张)`; } return null; } const cardIds = params.cardIds; if (!Array.isArray(cardIds)) return "需要选择牌"; const promptN = st.localVars[CTX_N]; const self = st.players[ownerId]; if (!self) return "玩家不存在"; for (const id of cardIds) { if (typeof id !== "string") return "cardIds 必须为字符串数组"; if (!self.hand.includes(id)) return "牌不在自己手牌中"; } if (typeof promptN === "number" && cardIds.length !== promptN) return `需要选择 ${promptN} 张牌`; if (new Set(cardIds).size !== cardIds.length) return "不能重复选牌"; return null; }, async (st, params) => { const rt = (st.pendingSlots.get(ownerId)?.atom)?.["requestType"]; if (rt === RT_ASK_USE) st.localVars[LV_USE] = params.choice === true; else if (rt === RT_ASK_TARGET) { const t = (typeof params.target === "number" ? params.target : void 0) ?? (Array.isArray(params.targets) && typeof params.targets[0] === "number" ? params.targets[0] : void 0); if (typeof t === "number") st.localVars[LV_TARGET] = t; } else if (rt === RT_ASK_CARDS) st.localVars[LV_CARDS] = params.cardIds; else if (rt === RT_ASK_OPTION) { const opt = params.option; if (opt === "self" || opt === "defer") st.localVars[LV_OPTION] = opt; else st.localVars[LV_OPTION] = params.choice === true ? "self" : "defer"; } else if (rt === RT_ASK_OWN) st.localVars[OWN_KEY] = params.cardIds; }); registerAfterHook(state, skill.id, ownerId, "阶段开始", async (ctx) => { const atom = ctx.atom; if (atom.type !== "阶段开始") return; if (atom.phase !== "准备") return; if (atom.player !== ownerId) return; if (ctx.state.currentPlayerIndex !== ownerId) return; if (!ctx.state.players[ownerId]?.alive) return; if (ctx.state.turn.vars[USED_KEY$1]) return; if (!ctx.state.players.some((p, i) => i !== ownerId && p.alive && totalCardCount(p) > 0)) return; await pushFrame(ctx.state, SKILL_ID$8, ownerId, {}); ctx.state.turn.vars[USED_KEY$1] = true; try { delete ctx.state.localVars[LV_USE]; await applyAtom(ctx.state, { type: "请求回应", requestType: RT_ASK_USE, target: ownerId, prompt: { type: "confirm", title: "是否发动镇军?(弃置一名角色X张牌,X为其手牌数减体力值且至少为1)", confirmLabel: "发动", cancelLabel: "不发动" }, defaultChoice: false, timeout: 15 }); if (ctx.state.localVars[LV_USE] !== true) { delete ctx.state.turn.vars[USED_KEY$1]; return; } delete ctx.state.localVars[LV_TARGET]; await applyAtom(ctx.state, { type: "请求回应", requestType: RT_ASK_TARGET, target: ownerId, prompt: { type: "choosePlayer", title: "镇军:选择一名角色(弃置其X张牌,X为其手牌数减体力值且至少为1)", min: 1, max: 1, filter: (_view, target) => { if (target === ownerId) return false; const p = ctx.state.players[target]; if (!p?.alive) return false; const x = computeX(p); return totalCardCount(p) >= x; } }, timeout: 20 }); const target = ctx.state.localVars[LV_TARGET]; if (typeof target !== "number" || !ctx.state.players[target]?.alive) { delete ctx.state.turn.vars[USED_KEY$1]; return; } const targetPlayer = ctx.state.players[target]; const X = computeX(targetPlayer); if (totalCardCount(targetPlayer) < X) { delete ctx.state.turn.vars[USED_KEY$1]; return; } delete ctx.state.localVars[LV_CARDS]; ctx.state.localVars[CTX_TARGET] = target; ctx.state.localVars[CTX_X] = X; await applyAtom(ctx.state, { type: "请求回应", requestType: RT_ASK_CARDS, target: ownerId, prompt: { type: "distribute", mode: "select", title: `镇军:选择 ${targetPlayer.name} 的 ${X} 张牌弃置(可从手牌或装备区)`, cardIds: [...targetPlayer.hand, ...Object.values(targetPlayer.equipment).filter((id) => !!id)], minTotal: X, maxTotal: X }, timeout: 30 }); const pickedRaw = ctx.state.localVars[LV_CARDS]; const picked = Array.isArray(pickedRaw) ? pickedRaw : []; if (picked.length !== X) { delete ctx.state.turn.vars[USED_KEY$1]; return; } const N = countNonEquipment(ctx.state, target, picked); await applyAtom(ctx.state, { type: "弃置", player: target, cardIds: picked }); const canOption1 = ctx.state.players[ownerId].hand.length >= N; delete ctx.state.localVars[LV_OPTION]; ctx.state.localVars[CTX_N] = N; await applyAtom(ctx.state, { type: "请求回应", requestType: RT_ASK_OPTION, target: ownerId, prompt: { type: "confirm", title: N === 0 ? "镇军:其中无非装备牌,两选项均无额外效果" : canOption1 ? `镇军:选项1(你弃置 ${N} 张手牌) 还是 选项2(结束阶段 ${targetPlayer.name} 摸 ${N} 张)?` : `镇军:手牌不足 ${N} 张,只能选选项2(结束阶段 ${targetPlayer.name} 摸 ${N} 张)`, confirmLabel: canOption1 ? `选项1:弃置 ${N} 张` : `(手牌不足)`, cancelLabel: `选项2:${targetPlayer.name} 摸 ${N} 张` }, defaultChoice: false, timeout: 20 }); if ((ctx.state.localVars[LV_OPTION] === "self" && canOption1 ? "self" : "defer") === "self") { if (N > 0) { delete ctx.state.localVars[OWN_KEY]; await applyAtom(ctx.state, { type: "请求回应", requestType: RT_ASK_OWN, target: ownerId, prompt: { type: "distribute", mode: "select", title: `镇军:选择你自己的 ${N} 张手牌弃置`, source: "hand", minTotal: N, maxTotal: N }, timeout: 30 }); const ownRaw = ctx.state.localVars[OWN_KEY]; const ownIds = Array.isArray(ownRaw) ? ownRaw : []; if (ownIds.length > 0) await applyAtom(ctx.state, { type: "弃置", player: ownerId, cardIds: ownIds }); } } else if (N > 0) ctx.state.turn.vars[DEFER_KEY] = { target, count: N }; } finally { await popFrame(ctx.state); } }); registerAfterHook(state, skill.id, ownerId, "阶段开始", async (ctx) => { const atom = ctx.atom; if (atom.type !== "阶段开始") return; if (atom.phase !== "回合结束") return; if (atom.player !== ownerId) return; const defer = ctx.state.turn.vars[DEFER_KEY]; if (!defer) return; delete ctx.state.turn.vars[DEFER_KEY]; if (ctx.state.players[defer.target]?.alive) await applyAtom(ctx.state, { type: "摸牌", player: defer.target, count: defer.count }); }); return () => {}; } function onMount$70(_skill, api) { api.defineAction("respond", { label: DISPLAY_NAME$5, style: "primary", prompt: { type: "confirm", title: "镇军", confirmLabel: "发动", cancelLabel: "不发动" } }); } var SKILL_ID$8, DISPLAY_NAME$5, USED_KEY$1, DEFER_KEY, RT_ASK_USE, RT_ASK_TARGET, RT_ASK_CARDS, RT_ASK_OPTION, RT_ASK_OWN, LV_USE, LV_TARGET, LV_CARDS, LV_OPTION, OWN_KEY, CTX_TARGET, CTX_X, CTX_N, _skillModule$26; var init_界镇军 = __esmMin((() => { init_types(); init_apply(); init_frame(); init_skill(); SKILL_ID$8 = "界镇军"; DISPLAY_NAME$5 = "镇军"; USED_KEY$1 = `${SKILL_ID$8}/usedThisTurn`; DEFER_KEY = `${SKILL_ID$8}/deferDraw`; RT_ASK_USE = `${SKILL_ID$8}/askUse`; RT_ASK_TARGET = `${SKILL_ID$8}/askTarget`; RT_ASK_CARDS = `${SKILL_ID$8}/askCards`; RT_ASK_OPTION = `${SKILL_ID$8}/askOption`; RT_ASK_OWN = `${SKILL_ID$8}/askOwn`; LV_USE = `${SKILL_ID$8}/use`; LV_TARGET = `${SKILL_ID$8}/target`; LV_CARDS = `${SKILL_ID$8}/cards`; LV_OPTION = `${SKILL_ID$8}/option`; OWN_KEY = `${SKILL_ID$8}/ownCards`; CTX_TARGET = `${SKILL_ID$8}/ctx/target`; CTX_X = `${SKILL_ID$8}/ctx/x`; CTX_N = `${SKILL_ID$8}/ctx/n`; _skillModule$26 = { createSkill: createSkill$79, onInit: onInit$79, onMount: onMount$70 }; })); //#endregion //#region src/engine/skills/界闭月.ts var 界闭月_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$78, default: () => _skillModule$25, onInit: () => onInit$78, onMount: () => onMount$69 }); function createSkill$78(id, ownerId) { return { id, ownerId, name: "闭月", description: "结束阶段,若你没有手牌,你可以摸两张牌,否则你可以摸一张牌" }; } function onInit$78(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "respond", (st, _params) => { const slot = st.pendingSlots.get(ownerId); if (!slot) return "当前不需要回应"; const atom = slot.atom; if (atom["type"] !== "请求回应") return "当前不需要回应"; if (atom["requestType"] !== CONFIRM_REQUEST$6) return "当前不是闭月确认"; return null; }, async (st, params) => { st.localVars[CONFIRMED_KEY$11] = params.choice === true || params.confirmed === true; }); registerAfterHook(state, skill.id, ownerId, "阶段开始", async (ctx) => { const atom = ctx.atom; if (atom.type !== "阶段开始") return; if (atom.phase !== "回合结束") return; if (atom.player !== ownerId) return; const self = ctx.state.players[ownerId]; if (!self?.alive) return; const drawCount = self.hand.length === 0 ? 2 : 1; const countLabel = drawCount === 2 ? "两张牌" : "一张牌"; delete ctx.state.localVars[CONFIRMED_KEY$11]; await applyAtom(ctx.state, { type: "请求回应", requestType: CONFIRM_REQUEST$6, target: ownerId, prompt: { type: "confirm", title: `是否发动闭月?(摸${countLabel})`, confirmLabel: "发动", cancelLabel: "不发动" }, defaultChoice: false, timeout: 15 }); if (ctx.state.localVars[CONFIRMED_KEY$11]) await applyAtom(ctx.state, { type: "摸牌", player: ownerId, count: drawCount }); }); return () => {}; } function onMount$69(_skill, api) { api.defineAction("respond", { label: "闭月", style: "primary", prompt: { type: "confirm", title: "是否发动闭月?", confirmLabel: "发动", cancelLabel: "不发动" } }); return () => {}; } var SKILL_ID$7, CONFIRM_REQUEST$6, CONFIRMED_KEY$11, _skillModule$25; var init_界闭月 = __esmMin((() => { init_apply(); init_skill(); SKILL_ID$7 = "界闭月"; CONFIRM_REQUEST$6 = `${SKILL_ID$7}/confirm`; CONFIRMED_KEY$11 = `${SKILL_ID$7}/confirmed`; _skillModule$25 = { createSkill: createSkill$78, onInit: onInit$78, onMount: onMount$69 }; })); //#endregion //#region src/engine/skills/界除疠.ts var 界除疠_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$77, default: () => _skillModule$24, onInit: () => onInit$77, onMount: () => onMount$68 }); function createSkill$77(id, ownerId) { return { id, ownerId, name: DISPLAY_NAME$4, description: "出牌阶段限一次,选择任意名势力各不相同的其他角色,弃置你和这些角色的各一张牌,被弃置黑桃牌的角色各摸一张牌" }; } /** 列出玩家区域内所有可弃置的 cardId(手牌 + 装备区)。 * 不含判定区:弃置 atom 仅从手牌/装备区移除并推入弃牌堆,传入判定区牌会导致 * 牌被加入弃牌堆却未从判定区移除(状态重复)。 */ function listDiscardable(state, player) { const p = state.players[player]; if (!p) return []; const cards = [...p.hand]; for (const id of Object.values(p.equipment)) if (id) cards.push(id); return cards; } /** 用 state.rngSeed 派生 RNG 等概率抽目标区域一张可弃置的牌,推进后写回(保证重放确定性)。 */ function pickRandomCard(state, player) { const cards = listDiscardable(state, player); if (cards.length === 0) return null; const rng = createRng(state.rngSeed); const idx = rng.nextInt(cards.length); state.rngSeed = rng.getState(); return cards[idx]; } function onInit$77(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "use", (st, params) => { if (st.currentPlayerIndex !== ownerId) return "不是你的回合"; if (st.phase !== "出牌") return "只能在出牌阶段发动"; if (hasBlockingPending(st)) return "当前有未完成的询问"; if (usedThisTurn$3(st, ownerId, SKILL_ID$6)) return "本回合已使用过除疠"; const self = st.players[ownerId]; if (!self?.alive) return "角色不可用"; const cardId = params.cardId; if (typeof cardId !== "string") return "cardId required"; const inHand = self.hand.includes(cardId); const inEquip = Object.values(self.equipment).includes(cardId); if (!inHand && !inEquip) return "牌不在手牌或装备区中"; const targets = params.targets; if (!Array.isArray(targets)) return "targets required"; if (targets.length === 0) return "至少选择一名目标"; const seenFactions = /* @__PURE__ */ new Set(); for (const t of targets) { if (typeof t !== "number") return "目标必须是数字"; if (t === ownerId) return "不能以自己为目标"; const tp = st.players[t]; if (!tp?.alive) return "目标已死亡"; if (listDiscardable(st, t).length === 0) return "目标无可弃置的牌"; const faction = tp.faction ?? "群"; if (seenFactions.has(faction)) return `势力 ${faction} 已有目标,势力必须各不相同`; seenFactions.add(faction); } return null; }, async (st, params) => { const from = ownerId; const cardId = params.cardId; const targets = params.targets ?? []; await markOncePerTurn(st, from, SKILL_ID$6); await pushFrame(st, SKILL_ID$6, from, { ...params }); const discarded = []; await applyAtom(st, { type: "弃置", player: from, cardIds: [cardId], voluntary: true }); discarded.push({ cardId, owner: from }); for (const t of targets) { const picked = pickRandomCard(st, t); if (picked === null) continue; await applyAtom(st, { type: "弃置", player: t, cardIds: [picked] }); discarded.push({ cardId: picked, owner: t }); } for (const { cardId: cid, owner } of discarded) if (st.cardMap[cid]?.suit === "♠") await applyAtom(st, { type: "摸牌", player: owner, count: 1 }); await popFrame(st); }); return () => {}; } function onMount$68(_skill, api) { api.defineAction("use", { label: DISPLAY_NAME$4, style: "primary", prompt: { type: "useCardAndTarget", title: "除疠:弃你和势力各不相同的其他角色各一张牌,被弃黑桃者各摸一张", cardFilter: { filter: () => true, min: 1, max: 1 }, targetFilter: { min: 1, max: 99, filter: (view, target) => { if (target === view.currentPlayerIndex) return false; const tp = view.players.find((p) => p.index === target); if (!tp || tp.alive === false) return false; return true; } } }, activeWhen: (ctx) => activeUnlessUsedThisTurn(SKILL_ID$6)(ctx) }); } var SKILL_ID$6, DISPLAY_NAME$4, _skillModule$24; var init_界除疠 = __esmMin((() => { init_apply(); init_frame(); init_rng(); init_once_per_turn(); init_skill(); SKILL_ID$6 = "界除疠"; DISPLAY_NAME$4 = "除疠"; _skillModule$24 = { createSkill: createSkill$77, onInit: onInit$77, onMount: onMount$68 }; })); //#endregion //#region src/engine/skills/界陷阵.ts var 界陷阵_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$76, default: () => _skillModule$23, onInit: () => onInit$76, onMount: () => onMount$67 }); /** 拼点牌点数:A=1, 2-10=面值, J=11, Q=12, K=13。 */ function rankValue$2(rank) { if (rank === "A") return 1; if (rank === "J") return 11; if (rank === "Q") return 12; if (rank === "K") return 13; const n = parseInt(rank, 10); return Number.isFinite(n) ? n : 0; } function createSkill$76(id, ownerId) { return { id, ownerId, name: DISPLAY_NAME$3, description: "出牌阶段限一次,与一名角色拼点:赢则本回合对其使用牌无距离和次数限制且无视防具,可多指定其为目标;没赢则本回合不能对其使用杀,且你的杀不计入手牌上限" }; } function getTempUnloadMap$1(state) { let m = tempUnloadByState$1.get(state); if (!m) { m = /* @__PURE__ */ new Map(); tempUnloadByState$1.set(state, m); } return m; } function onInit$76(skill, state) { const ownerId = skill.ownerId; const unloadProvider = registerSlashUnlimitedProvider(state, ownerId, (st, player) => typeof st.turn.vars[WIN_VAR] === "number" && player === ownerId); const unloadDistExemptor = registerDistanceExemptor(state, ownerId, (st, from, to) => st.turn.vars[WIN_VAR] === to && from === ownerId); const unloadHandLimit = registerHandLimitProvider(state, ownerId, (st, player) => { if (player !== ownerId) return void 0; if (typeof st.turn.vars[LOST_VAR] !== "number") return void 0; const p = st.players[player]; if (!p) return void 0; const slashCount = p.hand.filter((cid) => st.cardMap[cid]?.name === "杀").length; if (slashCount === 0) return void 0; const bonus = getHandLimitBonus(st.turn.vars, player) ?? 0; return getHealthValue(p) + bonus + slashCount; }); registerAction(state, skill.id, ownerId, "use", (st, params) => { if (st.currentPlayerIndex !== ownerId) return "不是你的回合"; if (st.phase !== "出牌") return "只能在出牌阶段发动"; if (usedThisTurn$3(st, ownerId, SKILL_ID$5)) return "本回合已使用过陷阵"; const self = st.players[ownerId]; if (!self?.alive) return "玩家不存在或已死亡"; const cardId = params.cardId; if (typeof cardId !== "string") return "需要选择一张拼点牌"; if (!self.hand.includes(cardId)) return "拼点牌不在手牌中"; const target = params.target; if (typeof target !== "number") return "需要选择拼点目标"; if (target === ownerId) return "不能与自己拼点"; const targetPlayer = st.players[target]; if (!targetPlayer?.alive) return "目标不存在或已死亡"; if (targetPlayer.hand.length === 0) return "目标没有手牌,无法拼点"; return null; }, async (st, params) => { const from = ownerId; const initiatorCardId = params.cardId; const target = params.target; await markOncePerTurn(st, from, SKILL_ID$5); await pushFrame(st, SKILL_ID$5, from, { ...params }); const initiatorCard = st.cardMap[initiatorCardId]; const initiatorValue = initiatorCard ? rankValue$2(initiatorCard.rank) : 0; delete st.localVars[TARGET_CARD_KEY]; await applyAtom(st, { type: "请求回应", requestType: PD_RT, target, prompt: { type: "useCard", title: `陷阵:与 ${st.players[from].name} 拼点,请出一张手牌`, cardFilter: { filter: () => true, min: 1, max: 1 } }, timeout: 30 }); const targetCardId = st.localVars[TARGET_CARD_KEY]; delete st.localVars[TARGET_CARD_KEY]; let win; if (targetCardId && st.players[target].hand.includes(targetCardId)) win = await runRankCompareFlow(st, from, target, initiatorCardId, targetCardId) === "赢"; else { await applyAtom(st, { type: "移动牌", cardId: initiatorCardId, from: { zone: "手牌", player: from }, to: { zone: "弃牌堆" } }); win = initiatorValue > 0; } if (win) { st.turn.vars[WIN_VAR] = target; await applyAtom(st, { type: "回合用量", player: from, key: WIN_VAR, value: target }); await applyAtom(st, { type: "回合用量", player: from, key: slashUnlimitedKey("界陷阵"), value: true }); } else { st.turn.vars[LOST_VAR] = target; await applyAtom(st, { type: "回合用量", player: from, key: LOST_VAR, value: target }); } await popFrame(st); }); for (const p of state.players) { const pid = p.index; registerAction(state, skill.id, pid, "respond", (st, params) => { const slot = st.pendingSlots.get(pid); if (!slot) return "当前不需要回应"; const atom = slot.atom; if (atom.type !== "请求回应") return "当前不需要回应"; if (atom.requestType !== PD_RT) return "当前不是陷阵回应"; if (atom.target !== pid) return "不是问你的"; const cardId = params.cardId; if (typeof cardId !== "string") return "请选择一张拼点牌"; if (!st.players[pid].hand.includes(cardId)) return "拼点牌不在手牌中"; return null; }, async (st, params) => { const slot = st.pendingSlots.get(pid); if (!slot) return; const atom = slot.atom; if (atom.type !== "请求回应" || atom.requestType !== PD_RT) return; st.localVars[TARGET_CARD_KEY] = params.cardId; }); } registerAfterHook(state, skill.id, ownerId, "指定目标", async (ctx) => { const atom = ctx.atom; if (atom.source !== ownerId) return; const winTarget = ctx.state.turn.vars[WIN_VAR]; if (typeof winTarget !== "number") return; if (atom.target !== winTarget) return; const cardId = atom.cardId; if (typeof cardId !== "string") return; if (ctx.state.cardMap[cardId]?.name !== "杀") return; const targetPlayer = ctx.state.players[winTarget]; if (!targetPlayer) return; const armorId = targetPlayer.equipment?.["防具"]; if (!armorId) return; const armorCard = ctx.state.cardMap[armorId]; if (!armorCard) return; const armorSkillId = armorCard.name; if (!armorSkillId || !targetPlayer.skills.includes(armorSkillId)) return; if (getTempUnloadMap$1(ctx.state).has(winTarget)) return; unloadSkillInstance(ctx.state, armorSkillId, winTarget); getTempUnloadMap$1(ctx.state).set(winTarget, { target: winTarget, skillId: armorSkillId }); }); for (const atomType of ["造成伤害后", "被抵消"]) registerAfterHook(state, skill.id, ownerId, atomType, async (ctx) => { const atom = ctx.atom; if (atom.source !== ownerId) return; const winTarget = ctx.state.turn.vars[WIN_VAR]; if (typeof winTarget !== "number") return; if (atom.target !== winTarget) return; const unloaded = getTempUnloadMap$1(ctx.state).get(winTarget); if (!unloaded) return; await instantiateSkill(ctx.state, unloaded.skillId, winTarget); getTempUnloadMap$1(ctx.state).delete(winTarget); }); registerBeforeHook(state, skill.id, ownerId, "成为目标", async (ctx) => { const atom = ctx.atom; if (atom.source !== ownerId) return; const lostTarget = ctx.state.turn.vars[LOST_VAR]; if (typeof lostTarget !== "number") return; if (atom.target !== lostTarget) return; const cardId = atom.cardId; if (typeof cardId !== "string") return; if (ctx.state.cardMap[cardId]?.name !== "杀") return; return { kind: "cancel" }; }); return () => { unloadProvider(); unloadHandLimit(); unloadDistExemptor(); }; } function onMount$67(skill, api) { api.defineAction("use", { label: DISPLAY_NAME$3, style: "danger", prompt: { type: "useCardAndTarget", title: "选择一张手牌与一名其他角色拼点(陷阵)", cardFilter: { filter: () => true, min: 1, max: 1 }, targetFilter: { min: 1, max: 1, filter: (view, t) => { const target = view.players[t]; if (!target?.alive) return false; if (target.index === view.currentPlayerIndex) return false; return (target.handCount ?? 0) > 0; } } }, activeWhen: (ctx) => { if (!defaultPlayActive(ctx)) return false; if (!activeUnlessUsedThisTurn(SKILL_ID$5)(ctx)) return false; const p = ctx.view.players[ctx.perspectiveIdx]; if (!p) return false; return (p.handCount ?? 0) > 0; } }); } var SKILL_ID$5, DISPLAY_NAME$3, TARGET_CARD_KEY, PD_RT, WIN_VAR, LOST_VAR, tempUnloadByState$1, _skillModule$23; var init_界陷阵 = __esmMin((() => { init_types(); init_apply(); init_frame(); init_rank(); init_skill(); init_lifecycle(); init_once_per_turn(); init_slash_quota(); init_vars_keys(); init_hand_limit(); init_distance(); init_action_active(); SKILL_ID$5 = "界陷阵"; DISPLAY_NAME$3 = "陷阵"; TARGET_CARD_KEY = `${SKILL_ID$5}/targetCard`; PD_RT = `${SKILL_ID$5}/拼点`; WIN_VAR = XIANZHEN_WIN_TARGET_VIEW_KEY; LOST_VAR = "陷阵/lostTarget"; tempUnloadByState$1 = /* @__PURE__ */ new WeakMap(); _skillModule$23 = { createSkill: createSkill$76, onInit: onInit$76, onMount: onMount$67 }; })); //#endregion //#region src/engine/skills/界集智.ts var 界集智_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$75, default: () => 界集智_default, onInit: () => onInit$75, onMount: () => onMount$66 }); /** 读取当前 pending 的 requestType(类型安全) */ function currentRequestType$5(state, ownerId) { const slot = state.pendingSlots.get(ownerId); if (!slot) return void 0; return slot.atom.requestType; } /** 判定打出牌是否为「转化牌」(单卡 shadowOf 或多卡 '#' 拼接 id) */ function isTransformedCard(card) { return Boolean(card.shadowOf) || card.id.includes("#"); } function createSkill$75(id, ownerId) { return { id, ownerId, name: "界集智", description: "被动技:使用非转化锦囊牌时你可以摸一张牌;若此牌是基本牌,可弃之令本回合手牌上限+1" }; } function onInit$75(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "respond", (st) => { const rt = currentRequestType$5(st, ownerId); if (rt !== CONFIRM_REQUEST$5 && rt !== DISCARD_REQUEST) return "当前不是集智询问"; return null; }, async (st, params) => { const rt = currentRequestType$5(st, ownerId); if (rt === CONFIRM_REQUEST$5) st.localVars["界集智/confirmed"] = params.choice === true || params.confirmed === true; else if (rt === DISCARD_REQUEST) st.localVars["界集智/discard"] = params.choice === true || params.confirmed === true; }); registerAfterHook(state, skill.id, ownerId, "移动牌", async (ctx) => { const atom = ctx.atom; if (!atom.cardId) return; const from = atom.from; const to = atom.to; if (from?.zone !== "手牌" || from.player !== ownerId) return; if (!to || to.zone !== "处理区" && to.zone !== "弃牌堆") return; const card = ctx.state.cardMap[atom.cardId]; if (!card) return; if (card.type !== "锦囊牌") return; if (!NON_DELAY_TRICKS$1.has(card.name)) return; if (isTransformedCard(card)) return; if (!ctx.state.players[ownerId]?.alive) return; delete ctx.state.localVars["界集智/confirmed"]; await applyAtom(ctx.state, { type: "请求回应", requestType: CONFIRM_REQUEST$5, target: ownerId, prompt: { type: "confirm", title: "是否发动集智摸一张牌?", confirmLabel: "摸牌", cancelLabel: "不摸" }, defaultChoice: true, timeout: 15 }); if (!ctx.state.localVars["界集智/confirmed"]) return; const handLenBefore = ctx.state.players[ownerId].hand.length; await applyAtom(ctx.state, { type: "摸牌", player: ownerId, count: 1 }); const drawnId = ctx.state.players[ownerId].hand[handLenBefore]; if ((drawnId ? ctx.state.cardMap[drawnId] : void 0)?.type !== "基本牌") return; delete ctx.state.localVars["界集智/discard"]; await applyAtom(ctx.state, { type: "请求回应", requestType: DISCARD_REQUEST, target: ownerId, prompt: { type: "confirm", title: "集智:是否弃置此基本牌令本回合手牌上限+1?", confirmLabel: "弃置", cancelLabel: "保留" }, defaultChoice: false, timeout: 15 }); if (!ctx.state.localVars["界集智/discard"]) return; const self = ctx.state.players[ownerId]; if (!self) return; if (self.hand.indexOf(drawnId) < 0) return; await applyAtom(ctx.state, { type: "弃置", player: ownerId, cardIds: [drawnId] }); const bonusKey = handLimitBonusKey(ownerId); const current = getHandLimitBonus(ctx.state.turn.vars, ownerId) ?? 0; ctx.state.turn.vars[bonusKey] = current + 1; }); return () => {}; } function onMount$66(_skill, api) { api.defineAction("respond", { label: "集智", style: "primary", prompt: { type: "confirm", title: "是否发动集智?", confirmLabel: "摸牌", cancelLabel: "不摸" } }); } var NON_DELAY_TRICKS$1, CONFIRM_REQUEST$5, DISCARD_REQUEST, 界集智_default; var init_界集智 = __esmMin((() => { init_apply(); init_skill(); init_vars_keys(); NON_DELAY_TRICKS$1 = new Set([ "过河拆桥", "顺手牵羊", "无中生有", "无懈可击", "借刀杀人", "桃园结义", "五谷丰登", "南蛮入侵", "万箭齐发", "决斗" ]); CONFIRM_REQUEST$5 = "界集智/confirm"; DISCARD_REQUEST = "界集智/discard"; 界集智_default = { createSkill: createSkill$75, onInit: onInit$75, onMount: onMount$66 }; })); //#endregion //#region src/engine/skills/界雷击.ts var 界雷击_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$74, default: () => 界雷击_default, onInit: () => onInit$74, onMount: () => onMount$65 }); function createSkill$74(id, ownerId) { return { id, ownerId, name: DISPLAY_NAME$2, description: "使用/打出【闪】或使用【闪电】时可判定;黑桃可对一名角色造成2点雷电伤害;梅花回复1点体力并可对一名角色造成1点雷电伤害" }; } /** 询问界张角选择雷电伤害目标(或放弃)。返回目标座次,放弃返回 null。 */ async function chooseDamageTarget(state, ownerId) { delete state.localVars[TARGET_KEY$12]; await applyAtom(state, { type: "请求回应", requestType: TARGET_RT$8, target: ownerId, prompt: { type: "choosePlayer", title: "界雷击:选择雷电伤害目标(或放弃)", min: 1, max: 1, filter: (view, t) => view.players[t]?.alive === true }, timeout: 20 }); const t = state.localVars[TARGET_KEY$12]; delete state.localVars[TARGET_KEY$12]; return typeof t === "number" ? t : null; } /** 界雷击完整流程:询问判定 → 判定自己 → 读结果 → 按花色分支造伤/回血。 */ async function run雷击Flow(state, ownerId, triggerDesc) { if (!state.players[ownerId]?.alive) return; delete state.localVars[JUDGE_KEY]; await applyAtom(state, { type: "请求回应", requestType: JUDGE_RT, target: ownerId, prompt: { type: "confirm", title: `界雷击:是否判定?(${triggerDesc})`, confirmLabel: "判定", cancelLabel: "不判定" }, defaultChoice: false, timeout: 20 }); const judgeChoice = state.localVars[JUDGE_KEY]; delete state.localVars[JUDGE_KEY]; if (judgeChoice !== true) return; await runJudgeFlow(state, ownerId, DISPLAY_NAME$2); const dp = state.zones.discardPile; if (dp.length === 0) return; const judgeCardId = dp[dp.length - 1]; const judgeCard = state.cardMap[judgeCardId]; if (!judgeCard) return; if (judgeCard.suit === "♠") { const target = await chooseDamageTarget(state, ownerId); if (target === null) return; if (!state.players[target]?.alive) return; await runDamageFlow(state, ownerId, target, 2, void 0, "雷电"); } else if (judgeCard.suit === "♣") { const self = state.players[ownerId]; if (self && self.health < self.maxHealth) await applyAtom(state, { type: "回复体力", target: ownerId, amount: 1 }); const target = await chooseDamageTarget(state, ownerId); if (target === null) return; if (!state.players[target]?.alive) return; await runDamageFlow(state, ownerId, target, 1, void 0, "雷电"); } } function onInit$74(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "respond", (st, params) => { const slot = st.pendingSlots.get(ownerId); if (!slot) return "当前不需要回应"; const atom = slot.atom; if (atom["type"] !== "请求回应") return "当前不需要回应"; const rt = atom["requestType"]; if (rt === JUDGE_RT) return null; if (rt === TARGET_RT$8) { const target = params.target; if (typeof target === "number") { if (!st.players[target]?.alive) return "目标不存在或已死亡"; } return null; } return "当前不是界雷击询问"; }, async (st, params) => { const slot = st.pendingSlots.get(ownerId); if (!slot) return; const atom = slot.atom; if (atom.requestType === JUDGE_RT) st.localVars[JUDGE_KEY] = params.choice === true || params.confirmed === true; else if (atom.requestType === TARGET_RT$8) { const target = params.target; st.localVars[TARGET_KEY$12] = typeof target === "number" ? target : null; } }); registerAfterHook(state, skill.id, ownerId, "询问闪", async (ctx) => { const atom = ctx.atom; if (atom.type !== "询问闪") return; if (atom.target !== ownerId) return; if (!ctx.state.players[ownerId]?.alive) return; if (!isCancelled(ctx.state, ctx.frame.params.cardId ?? "", ownerId)) return; await run雷击Flow(ctx.state, ownerId, "已使用/打出闪"); }); registerAfterHook(state, skill.id, ownerId, "添加延时锦囊", async (ctx) => { const atom = ctx.atom; if (atom.type !== "添加延时锦囊") return; if (atom.player !== ownerId) return; if (atom.trick?.name !== "闪电") return; if (atom.trick?.source !== ownerId) return; if (!ctx.state.players[ownerId]?.alive) return; await run雷击Flow(ctx.state, ownerId, "已使用闪电"); }); return () => {}; } function onMount$65(_skill, api) { api.defineAction("respond", { label: DISPLAY_NAME$2, style: "danger", prompt: { type: "choosePlayer", title: "界雷击:选择雷电伤害目标(或放弃)", min: 1, max: 1, filter: (view, t) => view.players[t]?.alive === true } }); } var DISPLAY_NAME$2, JUDGE_RT, JUDGE_KEY, TARGET_RT$8, TARGET_KEY$12, 界雷击_default; var init_界雷击 = __esmMin((() => { init_apply(); init_judge(); init_damage(); init_skill(); init_frame(); DISPLAY_NAME$2 = "雷击"; JUDGE_RT = "界雷击/judge"; JUDGE_KEY = "界雷击/judgeChoice"; TARGET_RT$8 = "界雷击/target"; TARGET_KEY$12 = "界雷击/target"; 界雷击_default = { createSkill: createSkill$74, onInit: onInit$74, onMount: onMount$65 }; })); //#endregion //#region src/engine/skills/界鞬出.ts var 界鞬出_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$73, default: () => 界鞬出_default, onInit: () => onInit$73, onMount: () => onMount$64 }); function createSkill$73(id, ownerId) { return { id, ownerId, name: "鞬出", description: "使用杀指定目标后,可弃其一张牌:基本牌则其获得此杀;非基本牌则其不能抵消此杀且本回合出杀次数+1" }; } /** 让目标从自己的手牌/装备区选一张牌弃置(博弈:基本→获得杀不命中,非基本→禁闪命中+出杀+1)。 */ async function askTargetToDiscard$1(state, ownerId, target, killCardId) { const tp = state.players[target]; if (!tp) return; const equipment = Object.entries(tp.equipment).filter(([, id]) => typeof id === "string").map(([slot, id]) => ({ slot, cardId: id, cardName: state.cardMap[id]?.name ?? "?" })); const handCount = tp.hand.length; const defaultZone = equipment.length > 0 ? { zone: "equipment", cardId: equipment[0].cardId } : { zone: "hand", handIndex: 0 }; delete state.localVars[PICK$1]; await applyAtom(state, { type: "请求回应", requestType: "界鞬出/选牌", target, prompt: { type: "pickTargetCard", title: "界鞬出:选择弃置的一张牌(基本牌→获得此杀;非基本牌→不能抵消且对方本回合出杀+1)", target, equipment, judge: [], handCount }, defaultChoice: defaultZone, timeout: 20 }); const result = state.localVars[PICK$1]; delete state.localVars[PICK$1]; const zone = result?.zone ?? defaultZone.zone; let discardId; if (zone === "equipment") discardId = result?.cardId ?? defaultZone.cardId; else { const idx = result?.handIndex ?? 0; discardId = tp.hand[idx] ?? tp.hand[0]; } if (!discardId) return; await applyAtom(state, { type: "弃置", player: target, cardIds: [discardId] }); if (state.cardMap[discardId]?.type === "基本牌") { const gainMap = state.localVars[GAIN$1] ?? {}; gainMap[target] = killCardId; state.localVars[GAIN$1] = gainMap; } else { await applyAtom(state, { type: "加标签", player: target, tag: TAG$2 }); const cur = state.turn.vars[QUOTA_BONUS] ?? 0; state.turn.vars[QUOTA_BONUS] = cur + 1; await applyAtom(state, { type: "回合用量", player: ownerId, key: slashExtraKey("界鞬出"), value: cur + 1 }); } } function onInit$73(skill, state) { const ownerId = skill.ownerId; const unloaders = [registerSlashExtraProvider(state, ownerId, (st, player) => { if (player !== ownerId) return 0; const bonus = st.turn.vars[QUOTA_BONUS]; return typeof bonus === "number" ? bonus : 0; })]; for (const p of state.players) { const seatId = p.index; const u = registerAction(state, skill.id, seatId, "respond", (st, params) => { const slot = st.pendingSlots.get(seatId); if (!slot) return "当前不需要回应"; const atom = slot.atom; if (atom.type !== "请求回应") return "当前不是请求回应"; if (atom.requestType === "界鞬出/confirm") return null; if (atom.requestType === "界鞬出/选牌") { const me = st.players[seatId]; if (!me) return "玩家不存在"; const zone = params.zone; if (zone === "equipment") { if (typeof params.cardId !== "string") return "cardId required"; if (!Object.values(me.equipment).includes(params.cardId)) return "该牌不在你的装备区"; return null; } if (zone === "hand") { if (typeof params.handIndex !== "number") return "handIndex required"; if (params.handIndex < 0 || params.handIndex >= me.hand.length) return "handIndex 越界"; return null; } return "zone required (equipment|hand)"; } return "当前不是界鞬出回应"; }, async (st, params) => { const requestType = (st.pendingSlots.get(seatId)?.atom)?.requestType; if (requestType === "界鞬出/confirm") st.localVars[CONFIRM$2] = params.choice === true || params.confirmed === true; else if (requestType === "界鞬出/选牌") st.localVars[PICK$1] = { zone: params.zone, cardId: params.cardId ?? null, handIndex: params.handIndex ?? null }; }); unloaders.push(u); } registerAfterHook(state, skill.id, ownerId, "指定目标", async (ctx) => { const atom = ctx.atom; if (atom.source !== ownerId) return; if (atom.target === void 0) return; const target = atom.target; if (atom.cardId === void 0) return; if (ctx.state.cardMap[atom.cardId]?.name !== "杀") return; const targetPlayer = ctx.state.players[target]; if (!targetPlayer?.alive) return; if (!(targetPlayer.hand.length > 0 || Object.keys(targetPlayer.equipment).length > 0)) return; delete ctx.state.localVars[CONFIRM$2]; await applyAtom(ctx.state, { type: "请求回应", requestType: "界鞬出/confirm", target: ownerId, prompt: { type: "confirm", title: "鞬出:是否弃置目标一张牌?", confirmLabel: "发动", cancelLabel: "不发动" }, defaultChoice: false, timeout: 15 }); if (!ctx.state.localVars[CONFIRM$2]) return; await askTargetToDiscard$1(ctx.state, ownerId, target, atom.cardId); }); registerBeforeHook(state, skill.id, ownerId, "成为目标", async (ctx) => { const atom = ctx.atom; if (atom.source !== ownerId) return; const target = atom.target; if (target === void 0) return; const gainMap = ctx.state.localVars[GAIN$1]; if (!gainMap || !(target in gainMap)) return; return { kind: "cancel" }; }); registerAfterHook(state, skill.id, ownerId, "移动牌", async (ctx) => { const gainMap = ctx.state.localVars[GAIN$1]; if (!gainMap) return; const atom = ctx.atom; if (atom.to?.zone !== "弃牌堆") return; const targetEntry = Object.entries(gainMap).find(([, cid]) => cid === atom.cardId); if (!targetEntry) return; const target = Number(targetEntry[0]); delete gainMap[target]; if (Object.keys(gainMap).length === 0) delete ctx.state.localVars[GAIN$1]; if (!ctx.state.players[target]?.alive) return; await applyAtom(ctx.state, { type: "移动牌", cardId: atom.cardId, from: { zone: "弃牌堆" }, to: { zone: "手牌", player: target } }); }); registerBeforeHook(state, skill.id, ownerId, "询问闪", async (ctx) => { const atom = ctx.atom; if (atom.source !== ownerId) return; const target = atom.target; if (target === void 0) return; if (!ctx.state.players[target]?.tags.includes(TAG$2)) return; await applyAtom(ctx.state, { type: "去标签", player: target, tag: TAG$2 }); return { kind: "cancel" }; }); return () => { unloaders.forEach((u) => u()); }; } function onMount$64(_skill, api) { api.defineAction("respond", { label: "鞬出", style: "default", prompt: { type: "confirm", title: "鞬出:是否弃置目标一张牌?", confirmLabel: "发动", cancelLabel: "不发动" } }); } var TAG$2, CONFIRM$2, PICK$1, GAIN$1, QUOTA_BONUS, 界鞬出_default; var init_界鞬出 = __esmMin((() => { init_apply(); init_skill(); init_slash_quota(); init_vars_keys(); TAG$2 = "界鞬出/禁闪"; CONFIRM$2 = "界鞬出/confirmed"; PICK$1 = "界鞬出/选牌"; GAIN$1 = "界鞬出/获得杀"; QUOTA_BONUS = "界鞬出/quotaBonus"; 界鞬出_default = { createSkill: createSkill$73, onInit: onInit$73, onMount: onMount$64 }; })); //#endregion //#region src/engine/skills/界鬼才.ts var 界鬼才_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$72, default: () => 界鬼才_default, onInit: () => onInit$72, onMount: () => onMount$63 }); function createSkill$72(id, ownerId) { return { id, ownerId, name: "界鬼才", description: "判定牌生效前,你可以打出一张手牌或装备牌代替之" }; } function onInit$72(skill, state) { const ownerId = skill.ownerId; function findCardLocation(st, cardId) { const me = st.players[ownerId]; if (!me) return null; if (me.hand.includes(cardId)) return "hand"; for (const slot of Object.keys(me.equipment)) if (me.equipment[slot] === cardId) return slot; return null; } function hasReplaceableCards(st) { const me = st.players[ownerId]; if (!me) return false; if (me.hand.length > 0) return true; return Object.values(me.equipment).some((id) => id !== void 0); } registerAction(state, skill.id, ownerId, "respond", (st, params) => { const slot = st.pendingSlots.get(ownerId); if (!slot) return "当前不需要回应"; const atom = slot.atom; if (atom.type !== "请求回应") return "当前不需要回应"; if (atom.requestType !== "界鬼才/replace") return "当前不是界鬼才询问"; if (params.choice === true || params.confirmed === true || typeof params.cardId === "string") { const cardId = params.cardId; if (typeof cardId !== "string") return "请选择一张替换牌"; if (findCardLocation(st, cardId) === null) return "替换牌不在手牌或装备区中"; } return null; }, async (st, params) => { if (!(params.choice === true || params.confirmed === true || typeof params.cardId === "string")) { st.localVars["鬼才/replaceCard"] = null; return; } st.localVars["鬼才/replaceCard"] = params.cardId ?? null; }); registerJudgeModifier(state, skill.id, ownerId, async (ctx) => { if (ctx.atom.type !== "判定牌生效前") return; const me = ctx.state.players[ownerId]; if (!me?.alive) return; if (!hasReplaceableCards(ctx.state)) return; if (frameCards(ctx.state).length === 0) return; delete ctx.state.localVars["鬼才/replaceCard"]; const equipIds = Object.values(me.equipment).filter((id) => typeof id === "string"); await applyAtom(ctx.state, { type: "请求回应", requestType: "界鬼才/replace", target: ownerId, prompt: { type: "useCard", title: "界鬼才:是否打出一张手牌或装备牌代替判定牌?", cardFilter: { filter: () => true, candidates: [...me.hand, ...equipIds], min: 1, max: 1 } }, defaultChoice: false, timeout: 15 }); const replaceCardId = ctx.state.localVars["鬼才/replaceCard"]; delete ctx.state.localVars["鬼才/replaceCard"]; if (!replaceCardId) return; const location = findCardLocation(ctx.state, replaceCardId); if (location === null) return; if (location !== "hand") { const equipSlot = location; const card = ctx.state.cardMap[replaceCardId]; if (card?.name && me.skills.includes(card.name)) await applyAtom(ctx.state, { type: "移除技能", player: ownerId, skillId: card.name }); await applyAtom(ctx.state, { type: "卸下", player: ownerId, slot: equipSlot }); } await replaceJudgeCard(ctx.state, ownerId, replaceCardId); }); return () => {}; } function onMount$63(_skill, api) { api.defineAction("respond", { label: "界鬼才", style: "primary", prompt: { type: "useCard", title: "界鬼才:选择一张手牌或装备牌代替判定牌", cardFilter: { filter: () => true, min: 1, max: 1 } } }); } var 界鬼才_default; var init_界鬼才 = __esmMin((() => { init_apply(); init_frame(); init_skill(); init_judge(); 界鬼才_default = { createSkill: createSkill$72, onInit: onInit$72, onMount: onMount$63 }; })); //#endregion //#region src/engine/skills/界鬼道.ts var 界鬼道_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$71, default: () => 界鬼道_default, onInit: () => onInit$71, onMount: () => onMount$62 }); /** 判断一张牌是否为黑色(♠或♣) */ function isBlackCard$1(state, cardId) { const card = state.cardMap[cardId]; return !!card && card.color === "黑"; } /** 判断一张牌的点数是否为 2~9 */ function isRank2to9(rank) { const n = rank === "A" ? 1 : rank === "J" ? 11 : rank === "Q" ? 12 : rank === "K" ? 13 : parseInt(rank, 10); return !Number.isNaN(n) && n >= 2 && n <= 9; } /** 判断一张牌是否为黑桃2~9(触发界鬼道摸牌效果) */ function isSpade2to9(card) { return !!card && card.suit === "♠" && isRank2to9(card.rank); } function createSkill$71(id, ownerId) { return { id, ownerId, name: DISPLAY_NAME$1, description: "判定牌生效前,你可以用一张黑色牌替换之,若为黑桃2~9,你摸一张牌" }; } function onInit$71(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "respond", (st, params) => { const slot = st.pendingSlots.get(ownerId); if (!slot) return "当前不需要回应"; const atom = slot.atom; if (atom["type"] !== "请求回应") return "当前不需要回应"; if (atom["requestType"] !== REPLACE_RT$2) return "当前不是界鬼道询问"; if (params.choice === true || params.confirmed === true || typeof params.cardId === "string") { const cardId = params.cardId; if (typeof cardId !== "string") return "请选择一张替换牌"; if (!st.players[ownerId].hand.includes(cardId)) return "替换牌不在手牌中"; if (!isBlackCard$1(st, cardId)) return "界鬼道只能用黑色牌替换"; } return null; }, async (st, params) => { if (!(params.choice === true || params.confirmed === true || typeof params.cardId === "string")) { st.localVars[REPLACE_CARD_KEY$1] = null; return; } st.localVars[REPLACE_CARD_KEY$1] = params.cardId ?? null; }); registerJudgeModifier(state, skill.id, ownerId, async (ctx) => { if (ctx.atom.type !== "判定牌生效前") return; const me = ctx.state.players[ownerId]; if (!me?.alive) return; if (!me.hand.some((id) => isBlackCard$1(ctx.state, id))) return; if (frameCards(ctx.state).length === 0) return; delete ctx.state.localVars[REPLACE_CARD_KEY$1]; await applyAtom(ctx.state, { type: "请求回应", requestType: REPLACE_RT$2, target: ownerId, prompt: { type: "useCard", title: "界鬼道:是否打出一张黑色牌代替判定牌?(黑桃2~9摸一张)", cardFilter: { filter: (c) => c.color === "黑", min: 1, max: 1 } }, defaultChoice: false, timeout: 15 }); const replaceCardId = ctx.state.localVars[REPLACE_CARD_KEY$1]; delete ctx.state.localVars[REPLACE_CARD_KEY$1]; if (!replaceCardId) return; if (!me.hand.includes(replaceCardId)) return; if (!isBlackCard$1(ctx.state, replaceCardId)) return; const replaceCard = ctx.state.cardMap[replaceCardId]; await replaceJudgeCard(ctx.state, ownerId, replaceCardId); if (isSpade2to9(replaceCard)) await applyAtom(ctx.state, { type: "摸牌", player: ownerId, count: 1 }); }); return () => {}; } function onMount$62(_skill, api) { api.defineAction("respond", { label: DISPLAY_NAME$1, style: "primary", prompt: { type: "useCard", title: "界鬼道:选择一张黑色牌代替判定牌(黑桃2~9摸一张)", cardFilter: { filter: (c) => c.color === "黑", min: 1, max: 1 } } }); } var DISPLAY_NAME$1, REPLACE_RT$2, REPLACE_CARD_KEY$1, 界鬼道_default; var init_界鬼道 = __esmMin((() => { init_apply(); init_frame(); init_skill(); init_judge(); DISPLAY_NAME$1 = "鬼道"; REPLACE_RT$2 = "界鬼道/replace"; REPLACE_CARD_KEY$1 = "界鬼道/replaceCard"; 界鬼道_default = { createSkill: createSkill$71, onInit: onInit$71, onMount: onMount$62 }; })); //#endregion //#region src/engine/skills/英魂.ts var 英魂_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$70, default: () => 英魂_default, onInit: () => onInit$70, onMount: () => onMount$61, performYinghunPrepare: () => performYinghunPrepare }); function createSkill$70(id, ownerId) { return { id, ownerId, name: "英魂", description: "回合开始阶段,若你已受伤,可令一名其他角色选择一项:摸X张牌再弃一张牌,或摸一张牌再弃X张牌(X为你已损失体力值)" }; } /** * 英魂准备阶段主逻辑:询问发动 → 选目标 → 选方案 → 目标摸牌 → 目标自选弃牌。 * * 抽成独立导出函数,供两处复用: * 1. 英魂自身 before-hook 调用(原行为不变)。 * 2. 魂姿/界魂姿觉醒 after-hook 在 applyAtom(添加技能,'英魂') 之后显式调用—— * 觉醒当回合英魂刚被添加,其 before-hook 已错过本准备阶段的快照收集时机 * (before-hook 在 applyAtom 入口即快照,此时英魂尚未添加), * 故需手动触发,使英魂在觉醒当回合的同一个准备阶段立即发动。 * * 内在门控(防误触,不依赖调用方约定): * - 当前回合角色 === ownerId(英魂只在自己回合的准备阶段发动) * - ownerId 存活 * - 已受伤(health < maxHealth);满血不发动 * * 幂等性:本准备阶段调用方保证仅一次。英魂 before-hook 每回合由「阶段开始」派发一次; * 魂姿觉醒仅觉醒当回合调一次(后续回合 AWAKENED_KEY 早返回)。故无重入、无重复触发。 */ async function performYinghunPrepare(state, ownerId) { if (state.currentPlayerIndex !== ownerId) return; const self = state.players[ownerId]; if (!self?.alive) return; if (self.health >= self.maxHealth) return; const x = self.maxHealth - getHealthValue(self); delete state.localVars[CONFIRMED_KEY$10]; await applyAtom(state, { type: "请求回应", requestType: CONFIRM_RT$12, target: ownerId, prompt: { type: "confirm", title: `是否发动英魂?(令一名其他角色摸弃,X=${x})`, confirmLabel: "发动", cancelLabel: "不发动" }, defaultChoice: false, timeout: 10 }); if (!state.localVars[CONFIRMED_KEY$10]) return; delete state.localVars[TARGET_KEY$11]; await applyAtom(state, { type: "请求回应", requestType: TARGET_RT$7, target: ownerId, prompt: { type: "choosePlayer", title: "英魂:选择一名其他角色", min: 1, max: 1, filter: (_view, t) => t !== ownerId && state.players[t]?.alive === true }, timeout: 15 }); const target = state.localVars[TARGET_KEY$11]; delete state.localVars[TARGET_KEY$11]; if (typeof target !== "number") return; if (!state.players[target]?.alive) return; delete state.localVars[OPTION_KEY$1]; await applyAtom(state, { type: "请求回应", requestType: OPTION_RT$1, target: ownerId, prompt: { type: "confirm", title: `英魂:选择一项(你已损失 ${x} 体力)`, description: `选项1:令其摸${x}张牌再弃1张牌;选项2:令其摸1张牌再弃${x}张牌`, confirmLabel: `摸${x}弃1`, cancelLabel: `摸1弃${x}` }, defaultChoice: true, timeout: 15 }); const option = state.localVars[OPTION_KEY$1] ?? "opt1"; delete state.localVars[OPTION_KEY$1]; const drawCount = option === "opt1" ? x : 1; const discardCount = option === "opt1" ? 1 : x; if (drawCount > 0) await applyAtom(state, { type: "摸牌", player: target, count: drawCount }); if (!state.players[target]?.alive) return; const hand = state.players[target].hand; const actual = Math.min(discardCount, hand.length); if (actual <= 0) return; delete state.localVars[DISCARD_KEY$5]; await applyAtom(state, { type: "请求回应", requestType: DISCARD_RT$6, target, prompt: { type: "useCard", title: `英魂:弃 ${actual} 张牌`, cardFilter: { filter: () => true, min: actual, max: actual } }, mandatory: true, timeout: 20 }); let discardCards = state.localVars[DISCARD_KEY$5]; delete state.localVars[DISCARD_KEY$5]; if ((!discardCards || discardCards.length === 0) && actual > 0) discardCards = state.players[target]?.hand.slice(0, actual) ?? []; if (discardCards && discardCards.length > 0) await applyAtom(state, { type: "弃置", player: target, cardIds: discardCards }); } function onInit$70(skill, state) { const ownerId = skill.ownerId; const unloaders = []; for (const p of state.players) { const seatId = p.index; const u = registerAction(state, skill.id, seatId, "respond", (st, _params) => { const slot = st.pendingSlots.get(seatId); if (!slot) return "当前不需要回应"; const atom = slot.atom; if (atom["type"] !== "请求回应") return "当前不需要回应"; const rt = atom["requestType"]; if (rt !== CONFIRM_RT$12 && rt !== TARGET_RT$7 && rt !== OPTION_RT$1 && rt !== DISCARD_RT$6) return "当前不是英魂询问"; return null; }, async (st, params) => { const slot = st.pendingSlots.get(seatId); const rt = (slot?.atom)?.requestType; if (rt === CONFIRM_RT$12) st.localVars[CONFIRMED_KEY$10] = params.choice === true; else if (rt === TARGET_RT$7) { const t = params.targets?.[0] ?? (typeof params.target === "number" ? params.target : void 0); if (typeof t === "number") st.localVars[TARGET_KEY$11] = t; } else if (rt === OPTION_RT$1) st.localVars[OPTION_KEY$1] = params.choice === true ? "opt1" : "opt2"; else if (rt === DISCARD_RT$6) { const ids = params.cardIds; const need = (slot?.atom)?.prompt?.cardFilter?.min; if (Array.isArray(ids) && typeof need === "number" && ids.length === need && new Set(ids).size === ids.length && ids.every((id) => st.players[seatId]?.hand.includes(id))) st.localVars[DISCARD_KEY$5] = ids; } }); unloaders.push(u); } registerBeforeHook(state, skill.id, ownerId, "阶段开始", async (ctx) => { const atom = ctx.atom; if (atom.type !== "阶段开始") return; if (atom.player !== ownerId) return; if (atom.phase !== "准备") return; await performYinghunPrepare(ctx.state, ownerId); }); return () => { unloaders.forEach((u) => u()); }; } function onMount$61(_skill, api) { api.defineAction("respond", { label: "英魂", style: "primary", prompt: { type: "confirm", title: "英魂", confirmLabel: "确认", cancelLabel: "取消" } }); return () => {}; } var CONFIRM_RT$12, TARGET_RT$7, OPTION_RT$1, DISCARD_RT$6, CONFIRMED_KEY$10, TARGET_KEY$11, OPTION_KEY$1, DISCARD_KEY$5, 英魂_default; var init_英魂 = __esmMin((() => { init_types(); init_apply(); init_skill(); CONFIRM_RT$12 = "英魂/confirm"; TARGET_RT$7 = "英魂/target"; OPTION_RT$1 = "英魂/option"; DISCARD_RT$6 = "英魂/discard"; CONFIRMED_KEY$10 = "英魂/confirmed"; TARGET_KEY$11 = "英魂/target"; OPTION_KEY$1 = "英魂/option"; DISCARD_KEY$5 = "英魂/discardCards"; 英魂_default = { createSkill: createSkill$70, onInit: onInit$70, onMount: onMount$61 }; })); //#endregion //#region src/engine/skills/界魂姿.ts var 界魂姿_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$69, default: () => 界魂姿_default, onInit: () => onInit$69, onMount: () => onMount$60 }); function createSkill$69(id, ownerId) { return { id, ownerId, name: "界魂姿", description: "觉醒技:准备阶段且体力为1时,减1体力上限并永久获得\"界英姿\"\"英魂\";本回合结束阶段摸2牌或回复1体力" }; } /** 界魂姿觉醒主逻辑。 */ async function awaken(state, ownerId) { if (getHunziAwakened(state.players[ownerId]?.vars)) return; const self = state.players[ownerId]; if (!self?.alive) return; if (self.health > 1) return; state.players[ownerId].vars[HUNZI_AWAKENED_KEY] = true; state.players[ownerId].vars[END_BONUS_KEY] = true; await applyAtom(state, { type: "设上限", player: ownerId, amount: self.maxHealth - 1 }); await applyAtom(state, { type: "添加技能", player: ownerId, skillId: "界英姿" }); await applyAtom(state, { type: "添加技能", player: ownerId, skillId: "英魂" }); await performYinghunPrepare(state, ownerId); } /** 界魂姿觉醒当回合结束阶段收益:摸两张牌 或 回复1点体力(强制二选一)。 */ async function endPhaseBonus(state, ownerId) { const self = state.players[ownerId]; if (!self?.alive) return; if (!self.vars[END_BONUS_KEY]) return; delete state.players[ownerId].vars[END_BONUS_KEY]; delete state.localVars[CHOICE_KEY$1]; await applyAtom(state, { type: "请求回应", requestType: CHOOSE_RT$3, target: ownerId, prompt: { type: "confirm", title: "魂姿:摸两张牌,还是回复1点体力?", confirmLabel: "摸两张牌", cancelLabel: "回复1点体力" }, defaultChoice: false, timeout: 30 }); if (state.localVars[CHOICE_KEY$1] === "draw") await applyAtom(state, { type: "摸牌", player: ownerId, count: 2 }); else { const cur = state.players[ownerId].health; const max = state.players[ownerId].maxHealth; const amount = Math.min(1, Math.max(0, max - cur)); if (amount > 0) await applyAtom(state, { type: "回复体力", target: ownerId, amount }); } } function onInit$69(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "respond", (st, _params) => { const slot = st.pendingSlots.get(ownerId); if (!slot) return "当前不需要回应"; const atom = slot.atom; if (atom["type"] !== "请求回应") return "当前不需要回应"; if (atom["requestType"] !== CHOOSE_RT$3) return "当前不是魂姿选择"; return null; }, async (st, params) => { st.localVars[CHOICE_KEY$1] = params.choice === true ? "draw" : "heal"; }); registerAfterHook(state, skill.id, ownerId, "阶段开始", async (ctx) => { const atom = ctx.atom; if (atom.type !== "阶段开始") return; if (atom.player !== ownerId) return; if (atom.phase === "准备") { await awaken(ctx.state, ownerId); return; } if (atom.phase === "回合结束") await endPhaseBonus(ctx.state, ownerId); }); return () => {}; } function onMount$60(_skill, api) { api.defineAction("respond", { label: "界魂姿", style: "primary", prompt: { type: "confirm", title: "魂姿:摸两张牌,还是回复1点体力?", confirmLabel: "摸两张牌", cancelLabel: "回复1点体力" } }); } var END_BONUS_KEY, CHOOSE_RT$3, CHOICE_KEY$1, 界魂姿_default; var init_界魂姿 = __esmMin((() => { init_apply(); init_skill(); init_英魂(); init_vars_keys(); END_BONUS_KEY = "界魂姿/endBonus"; CHOOSE_RT$3 = "界魂姿/choose"; CHOICE_KEY$1 = "界魂姿/choice"; 界魂姿_default = { createSkill: createSkill$69, onInit: onInit$69, onMount: onMount$60 }; })); //#endregion //#region src/engine/skills/界黄天.ts var 界黄天_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$68, default: () => 界黄天_default, onInit: () => onInit$68, onMount: () => onMount$59 }); /** 判断一张牌是否为界黄天可交的牌(闪或黑桃手牌) */ function isGiveableCard$1(state, cardId) { const card = state.cardMap[cardId]; if (!card) return false; return card.name === "闪" || card.suit === "♠"; } function createSkill$68(id, ownerId) { return { id, ownerId, name: DISPLAY_NAME, description: "主公技:其他群势力角色可在出牌阶段将一张【闪】或黑桃手牌交给你" }; } function onInit$68(skill, state) { const ownerId = skill.ownerId; for (const p of state.players) { const allyIdx = p.index; if (allyIdx === ownerId) continue; if (p.faction !== "群") continue; registerAction(state, skill.id, allyIdx, "use", (st, params) => { if (ownerId !== 0) return "界黄天为主公技,界张角非主公"; if (!st.players[ownerId]?.alive) return "主公不存在或已死亡"; const self = st.players[allyIdx]; if (!self?.alive) return "已死亡"; const myTurn = st.currentPlayerIndex === allyIdx; const inActPhase = st.phase === "出牌"; const free = !hasBlockingPending(st); if (!myTurn || !inActPhase || !free) return "现在不能使用界黄天"; if (usedThisTurn$3(st, allyIdx, SKILL_ID$4)) return "本回合已使用过界黄天"; const cardId = params.cardId; if (typeof cardId !== "string") return "请选择一张牌"; if (!self.hand.includes(cardId)) return "牌不在手牌中"; if (!isGiveableCard$1(st, cardId)) return "界黄天只能交出【闪】或黑桃手牌"; return null; }, async (st, params) => { const cardId = params.cardId; await markOncePerTurn(st, allyIdx, SKILL_ID$4); await applyAtom(st, { type: "移动牌", cardId, from: { zone: "手牌", player: allyIdx }, to: { zone: "手牌", player: ownerId } }); }); } return () => {}; } function onMount$59(skill, api) { const ownerId = skill.ownerId; api.defineAction("use", { label: DISPLAY_NAME, style: "primary", prompt: { type: "useCard", title: "界黄天:交出一张【闪】或黑桃手牌给主公", cardFilter: { filter: (c) => c.name === "闪" || c.suit === "♠", min: 1, max: 1 } }, activeWhen: (ctx) => ctx.perspectiveIdx !== ownerId && activeUnlessUsedThisTurn(SKILL_ID$4)(ctx) && ctx.view.players[ctx.perspectiveIdx]?.hand?.some((c) => c.name === "闪" || c.suit === "♠") === true }); } var SKILL_ID$4, DISPLAY_NAME, 界黄天_default; var init_界黄天 = __esmMin((() => { init_apply(); init_once_per_turn(); init_skill(); SKILL_ID$4 = "界黄天"; DISPLAY_NAME = "黄天"; 界黄天_default = { createSkill: createSkill$68, onInit: onInit$68, onMount: onMount$59 }; })); //#endregion //#region src/engine/skills/界龙胆.ts var 界龙胆_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$67, default: () => 界龙胆_default, onInit: () => onInit$67, onMount: () => onMount$58 }); function createSkill$67(id, ownerId) { return { id, ownerId, name: "界龙胆", description: "你可以将一张【杀】当【闪】、【闪】当【杀】、【酒】当【桃】、【桃】当【酒】使用或打出" }; } /** 影子卡 id:${原id}#界龙胆(单卡转化,同一张牌只可能转一个方向) */ function shadowIdOf$5(cardId) { return `${cardId}#界龙胆`; } function onInit$67(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "transform", (state, params) => { const self = state.players[ownerId]; const selfAlive = self?.alive === true; const cardId = params.cardId; const cardIdOk = typeof cardId === "string"; const card = cardIdOk ? state.cardMap[cardId] : void 0; const cardInHand = cardIdOk && self.hand.includes(cardId); if (!selfAlive) return "你已死亡"; if (!cardIdOk || !cardInHand) return "牌不在手牌中"; const expectedTo = card ? TRANSFORM_MAP$1[card.name] : void 0; const to = params.to ?? expectedTo; if (!expectedTo) return "该牌不能被界龙胆转化"; if (to !== expectedTo) return "转化方向与原牌不符"; return null; }, async (state, params) => { const cardId = params.cardId; const card = state.cardMap[cardId]; const to = params.to ?? (card ? TRANSFORM_MAP$1[card.name] : void 0); if (!to) return; const shadowId = shadowIdOf$5(cardId); await applyAtom(state, { type: "当作", player: ownerId, cardIds: [cardId], shadowId, outputName: to }); }, (state, params) => { const cardId = params.cardId; const sId = shadowIdOf$5(cardId); delete state.cardMap[sId]; const self = state.players[ownerId]; const idx = self.hand.indexOf(sId); if (idx >= 0) self.hand[idx] = cardId; }); const unloadDodge = declareAlternativeResponse(state, ownerId, "询问闪"); const unloadSlash = declareAlternativeResponse(state, ownerId, "询问杀"); const unloadRescue = declareAlternativeResponse(state, ownerId, "请求回应", "桃/求桃"); return () => { unloadDodge(); unloadSlash(); unloadRescue(); }; } function onMount$58(skill, api) { api.defineAction("transform", { label: "界龙胆", style: "passive", prompt: { type: "useCardAndTarget", title: "选择一张杀/闪/酒/桃转化", cardFilter: { filter: (c) => c.name === "杀" || c.name === "闪" || c.name === "酒" || c.name === "桃", min: 1, max: 1 }, targetFilter: { min: 1, max: 3, filter: (view, t) => { return t !== view.currentPlayerIndex && view.players[t]?.alive; } } }, transform: (card) => ({ name: TRANSFORM_MAP$1[card.name] ?? card.name, sourceCardId: card.id, fromSkill: skill.id }), activeWhen: (ctx) => { const p = ctx.view.players[ctx.perspectiveIdx]; if (!p?.alive) return false; const hand = p.hand ?? []; const has = (n) => hand.some((c) => c.name === n); if (defaultPlayActive(ctx)) { if (has("闪") && viewCanSlash(ctx.view, ctx.perspectiveIdx)) return true; if (has("酒") && p.health < p.maxHealth) return true; if (has("桃")) return true; } const slot = ctx.view.pending; if (!!slot && slot.atom.type === "询问闪" && slot.target === ctx.perspectiveIdx && has("杀")) return true; if (!!slot && slot.atom.type === "询问杀" && slot.target === ctx.perspectiveIdx && has("闪")) return true; return false; } }); } var TRANSFORM_MAP$1, 界龙胆_default; var init_界龙胆 = __esmMin((() => { init_skill(); init_apply(); init_action_active(); TRANSFORM_MAP$1 = { 杀: "闪", 闪: "杀", 酒: "桃", 桃: "酒" }; 界龙胆_default = { createSkill: createSkill$67, onInit: onInit$67, onMount: onMount$58 }; })); //#endregion //#region src/engine/skills/白银狮子.ts var 白银狮子_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$66, onInit: () => onInit$66 }); function createSkill$66(id, ownerId) { return { id, ownerId, name: "白银狮子", description: "防具:每次受伤最多1点", isLocked: true }; } /** ownerId 装备区的防具是否为白银狮子,返回其 cardId(否则 null)。 * 动态校核装备仍在(陷阱8):装备可能同帧内被换下,触发时按当前装备区实时判定。 */ function equippedSilverLion(state, ownerId) { const armorId = state.players[ownerId]?.equipment["防具"]; if (!armorId) return null; return state.cardMap[armorId]?.name === "白银狮子" ? armorId : null; } function onInit$66(skill, state) { const ownerId = skill.ownerId; const loseKey = `白银狮子/失去/${ownerId}`; registerBeforeHook(state, skill.id, ownerId, "受到伤害时", async (ctx) => { const atom = ctx.atom; if (atom.target !== ownerId) return; if ((atom.amount ?? 0) <= 1) return; if (!equippedSilverLion(ctx.state, ownerId)) return; return { kind: "modify", atom: { ...ctx.atom, amount: 1 } }; }); registerBeforeHook(state, skill.id, ownerId, "卸下", async (ctx) => { const atom = ctx.atom; if (atom.player !== ownerId || atom.slot !== "防具") return; if (equippedSilverLion(ctx.state, ownerId)) ctx.state.localVars[loseKey] = true; }); registerBeforeHook(state, skill.id, ownerId, "弃置", async (ctx) => { const atom = ctx.atom; if (atom.player !== ownerId) return; const armorId = equippedSilverLion(ctx.state, ownerId); if (armorId && (atom.cardIds ?? []).includes(armorId)) ctx.state.localVars[loseKey] = true; }); registerBeforeHook(state, skill.id, ownerId, "获得", async (ctx) => { const atom = ctx.atom; if (atom.from !== ownerId) return; const armorId = equippedSilverLion(ctx.state, ownerId); if (armorId && atom.cardId === armorId) ctx.state.localVars[loseKey] = true; }); registerBeforeHook(state, skill.id, ownerId, "移除技能", async (ctx) => { const atom = ctx.atom; if (atom.player !== ownerId || atom.skillId !== "白银狮子") return; if (equippedSilverLion(ctx.state, ownerId)) ctx.state.localVars[loseKey] = true; }); async function healIfLost(s) { if (!s.localVars[loseKey]) return; delete s.localVars[loseKey]; if (!s.players[ownerId]?.alive) return; await applyAtom(s, { type: "回复体力", target: ownerId, amount: 1 }); } registerAfterHook(state, skill.id, ownerId, "卸下", async (ctx) => { const atom = ctx.atom; if (atom.player !== ownerId || atom.slot !== "防具") return; await healIfLost(ctx.state); }); registerAfterHook(state, skill.id, ownerId, "弃置", async (ctx) => { if (ctx.atom.player !== ownerId) return; await healIfLost(ctx.state); }); registerAfterHook(state, skill.id, ownerId, "获得", async (ctx) => { if (ctx.atom.from !== ownerId) return; await healIfLost(ctx.state); }); registerAfterHook(state, skill.id, ownerId, "移除技能", async (ctx) => { const atom = ctx.atom; if (atom.player !== ownerId || atom.skillId !== "白银狮子") return; await healIfLost(ctx.state); }); return () => {}; } var init_白银狮子 = __esmMin((() => { init_apply(); init_skill(); })); //#endregion //#region src/engine/skills/直谏.ts var 直谏_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$65, default: () => _skillModule$22, onInit: () => onInit$65, onMount: () => onMount$57 }); /** 装备牌 subtype → 装备栏位(与 装备 atom 的 inferSlot 一致) */ function slotOf$1(card) { switch (card?.subtype) { case "武器": return "武器"; case "防具": return "防具"; case "进攻马": return "进攻马"; case "防御马": return "防御马"; case "宝物": return "宝物"; default: return null; } } function createSkill$65(id, ownerId) { return { id, ownerId, name: "直谏", description: "出牌阶段:将一张装备牌置于一名其他角色的空装备区,然后摸一张牌" }; } function onInit$65(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "use", (st, params) => { if (st.currentPlayerIndex !== ownerId) return "只能在你的回合使用"; if (st.phase !== "出牌") return "只能在出牌阶段使用"; if (hasBlockingPending(st)) return "当前有未完成的询问"; const self = st.players[ownerId]; if (!self?.alive) return "你已死亡"; if (typeof params.cardId !== "string") return "需要选择一张装备牌"; if (!self.hand.includes(params.cardId)) return "牌不在手牌中"; const card = st.cardMap[params.cardId]; if (card?.type !== "装备牌") return "只能使用装备牌"; const slot = slotOf$1(card); if (!slot) return "无效的装备牌"; const target = Array.isArray(params.targets) && typeof params.targets[0] === "number" ? params.targets[0] : typeof params.target === "number" ? params.target : void 0; if (target === void 0) return "需要指定一名目标"; if (target === ownerId) return "不能对自己使用直谏"; const targetPlayer = st.players[target]; if (!targetPlayer?.alive) return "目标不合法"; if (targetPlayer.equipment[slot]) return "目标该装备栏位已有装备,不得替换"; return null; }, async (st, params) => { const cardId = params.cardId; const target = Array.isArray(params.targets) && typeof params.targets[0] === "number" ? params.targets[0] : params.target; const card = st.cardMap[cardId]; await pushFrame(st, "直谏", ownerId, { ...params }); await applyAtom(st, { type: "移动牌", cardId, from: { zone: "手牌", player: ownerId }, to: { zone: "手牌", player: target } }); await applyAtom(st, { type: "装备", player: target, cardId }); if (card?.name && hasSkillModule(card.name)) await applyAtom(st, { type: "添加技能", player: target, skillId: card.name }); await applyAtom(st, { type: "摸牌", player: ownerId, count: 1 }); await popFrame(st); }); return () => {}; } function onMount$57(skill, api) { api.defineAction("use", { label: "直谏", style: "primary", prompt: { type: "useCardAndTarget", title: "直谏:将一张装备牌置于一名其他角色的空装备区", cardFilter: { filter: (c) => c.type === "装备牌", min: 1, max: 1 }, targetFilter: { min: 1, max: 1, filter: (view, target) => { if (target === view.viewer) return false; return view.players[target]?.alive === true; } } } }); return () => {}; } var _skillModule$22; var init_直谏 = __esmMin((() => { init_apply(); init_frame(); init_skill(); init_registry$1(); _skillModule$22 = { createSkill: createSkill$65, onInit: onInit$65, onMount: onMount$57 }; })); //#endregion //#region src/engine/skills/看破.ts var 看破_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$64, default: () => 看破_default, onInit: () => onInit$64, onMount: () => onMount$56 }); function createSkill$64(id, ownerId) { return { id, ownerId, name: "看破", description: "你可以将一张黑色手牌当【无懈可击】使用" }; } /** 影子卡 id:${原id}#看破 */ function shadowIdOf$4(cardId) { return `${cardId}#看破`; } /** 当前是否存在无懈可击广播窗口(玩家可回应) */ function hasNullifyWindow$1(state, playerId) { for (const slot of state.pendingSlots.values()) { const atom = slot.atom; if (atom.type !== "请求回应") continue; if (atom.requestType !== "无懈可击") continue; if (typeof atom.target === "number" && (atom.target < 0 || atom.target === playerId)) return true; } return false; } function onInit$64(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "transform", (state, params) => { const self = state.players[ownerId]; if (!self?.alive) return "玩家不存在或已死亡"; const cardId = params.cardId; const cardIdOk = typeof cardId === "string"; const card = cardIdOk ? state.cardMap[cardId] : void 0; const cardInHand = cardIdOk && self.hand.includes(cardId); const isBlack = !!card && card.color === "黑"; const hasWindow = hasNullifyWindow$1(state, ownerId); const blockedByOther = (() => { for (const slot of state.pendingSlots.values()) { const atom = slot.atom; if (!slot.isBlocking) continue; if (atom.type === "请求回应" && atom.requestType === "无懈可击") continue; return true; } return false; })(); return cardInHand && isBlack && hasWindow && !blockedByOther ? null : "现在不能使用看破"; }, async (state, params) => { const cardId = params.cardId; const shadowId = shadowIdOf$4(cardId); await applyAtom(state, { type: "当作", player: ownerId, cardIds: [cardId], shadowId, outputName: "无懈可击" }); }, (state, params) => { const cardId = params.cardId; const sId = shadowIdOf$4(cardId); delete state.cardMap[sId]; const self = state.players[ownerId]; const idx = self.hand.indexOf(sId); if (idx >= 0) self.hand[idx] = cardId; }); return () => {}; } function onMount$56(skill, api) { api.defineAction("transform", { label: "看破", style: "passive", prompt: { type: "useCard", title: "选择一张黑色牌当无懈可击使用", cardFilter: { filter: (c) => c.color === "黑", min: 1, max: 1 } }, transform: (card) => ({ name: "无懈可击", sourceCardId: card.id, fromSkill: skill.id }), activeWhen: (ctx) => { const view = ctx.view; const pending = view.pending; if (!pending) return false; const atom = pending.atom; if (atom.type !== "请求回应" || atom.requestType !== "无懈可击") return false; const p = view.players[ctx.perspectiveIdx]; if (!p) return false; return p.hand?.some((c) => c.color === "黑") ?? false; } }); } var 看破_default; var init_看破 = __esmMin((() => { init_skill(); init_apply(); 看破_default = { createSkill: createSkill$64, onInit: onInit$64, onMount: onMount$56 }; })); //#endregion //#region src/engine/skills/破势.ts var 破势_exports = /* @__PURE__ */ __exportAll({ allSlotsAbolished: () => allSlotsAbolished, createSkill: () => createSkill$63, default: () => _skillModule$21, onInit: () => onInit$63, onMount: () => onMount$55 }); function createSkill$63(id, ownerId) { return { id, ownerId, name: "破势", description: "觉醒技:准备阶段,若装备栏均被废除或体力值为1,减少1点体力上限,将手牌摸至体力上限,失去决堰并获得怀柔" }; } /** 玩家的装备栏是否全部废除(5 个槽均有废除标记) */ function allSlotsAbolished(state, player) { const vars = state.players[player]?.vars; if (!vars) return false; return ALL_SLOTS.every((s) => vars[ABOLISH_PREFIX$1 + s]); } function onInit$63(skill, state) { const ownerId = skill.ownerId; registerAfterHook(state, skill.id, ownerId, "阶段开始", async (ctx) => { const atom = ctx.atom; if (atom.type !== "阶段开始") return; if (atom.player !== ownerId) return; if (atom.phase !== "准备") return; if (ctx.state.players[ownerId]?.vars[AWAKENED_KEY$2]) return; const self = ctx.state.players[ownerId]; if (!self?.alive) return; const abolished = allSlotsAbolished(ctx.state, ownerId); const healthIsOne = getHealthValue(self) === 1; if (!abolished && !healthIsOne) return; ctx.state.players[ownerId].vars[AWAKENED_KEY$2] = true; await applyAtom(ctx.state, { type: "设上限", player: ownerId, amount: self.maxHealth - 1 }); const newMax = ctx.state.players[ownerId].maxHealth; const handCount = ctx.state.players[ownerId].hand.length; const drawCount = Math.max(0, newMax - handCount); if (drawCount > 0) await applyAtom(ctx.state, { type: "摸牌", player: ownerId, count: drawCount }); await applyAtom(ctx.state, { type: "移除技能", player: ownerId, skillId: "决堰" }); await applyAtom(ctx.state, { type: "添加技能", player: ownerId, skillId: "怀柔" }); }); return () => {}; } function onMount$55(_skill, _api) {} var AWAKENED_KEY$2, ALL_SLOTS, _skillModule$21; var init_破势 = __esmMin((() => { init_types(); init_apply(); init_skill(); init_决堰(); AWAKENED_KEY$2 = "破势/awakened"; ALL_SLOTS = [ "武器", "防具", "进攻马", "防御马", "宝物" ]; _skillModule$21 = { createSkill: createSkill$63, onInit: onInit$63, onMount: onMount$55 }; })); //#endregion //#region src/engine/skills/神速.ts var 神速_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$62, onInit: () => onInit$62, onMount: () => onMount$54 }); function createSkill$62(id, ownerId) { return { id, ownerId, name: "神速", description: "可选择至多三项:①跳过判定+摸牌;②弃装备+跳过出牌;③翻面+跳过弃牌。每项视为出杀" }; } /** 创建一张虚拟杀卡(无实体,仅用于结算流程的 cardId 引用) */ function makeVirtualKillCard(source, target, seq) { return `神速:杀:${source}:${target}:${seq}`; } /** * 执行一次"视为出杀"的完整结算(runUseFlow virtual + chargeOnSettle 模式)。 * 不消耗手牌、计入出杀次数(由 chargeOnSettle 的 onSettle 计数);无距离限制。 * 走完整时机 atom 序列,保证激昂/集智等技能事件一致。 */ async function virtualKill(state, source, target) { if (!state.players[target]?.alive) return; const cardId = makeVirtualKillCard(source, target, state.seq); state.cardMap[cardId] = { id: cardId, name: "杀", suit: "", color: "无色", rank: "A", type: "基本牌" }; await runUseFlow(state, source, cardId, [target], "杀", { virtual: true, onSettle: chargeOnSettle(state, source, cardId) }); delete state.cardMap[cardId]; } function onInit$62(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "respond", (s) => { const slot = s.pendingSlots.get(ownerId); if (slot?.atom.type !== "请求回应") return "当前不需要回应"; const rt = slot.atom.requestType ?? ""; if (rt !== OPT1_TRIGGER_RT && rt !== OPT1_TARGET_RT && rt !== OPT2_TRIGGER_RT && rt !== OPT2_TARGET_RT && rt !== OPT2_EQUIP_RT && rt !== OPT3_TRIGGER_RT && rt !== OPT3_TARGET_RT) return "当前不是神速询问"; return null; }, async (s, params) => { const rt = (s.pendingSlots.get(ownerId)?.atom)?.requestType ?? ""; if (rt === OPT1_TRIGGER_RT || rt === OPT2_TRIGGER_RT || rt === OPT3_TRIGGER_RT) s.localVars["神速/confirmed"] = params.choice === true; else if (rt === OPT1_TARGET_RT || rt === OPT2_TARGET_RT || rt === OPT3_TARGET_RT) { const t = params.targets?.[0] ?? (typeof params.target === "number" ? params.target : void 0); if (typeof t === "number") s.localVars["神速/target"] = t; } else if (rt === OPT2_EQUIP_RT) { const cardIds = params.cardIds; const single = params.cardId; s.localVars["神速/equipCardId"] = (Array.isArray(cardIds) && cardIds.length > 0 ? { cardIds: [cardIds[0]] } : null) ?? (typeof single === "string" ? single : null); } }); registerBeforeHook(state, skill.id, ownerId, "阶段开始", async (ctx) => { const atom = ctx.atom; if (atom.type !== "阶段开始") return; if (atom.player !== ownerId) return; if (atom.phase !== "判定") return; if (ctx.state.currentPlayerIndex !== ownerId) return; const self = ctx.state.players[ownerId]; if (!self?.alive) return; if (self.vars[OPT1_USED_KEY]) return; if (!ctx.state.players.some((p, i) => i !== ownerId && p.alive)) return; delete ctx.state.localVars["神速/confirmed"]; await applyAtom(ctx.state, { type: "请求回应", requestType: OPT1_TRIGGER_RT, target: ownerId, prompt: { type: "confirm", title: "是否发动神速①?(跳过判定+摸牌,视为出杀)", confirmLabel: "发动", cancelLabel: "不发动" }, defaultChoice: false, timeout: 10 }); if (ctx.state.localVars["神速/confirmed"] !== true) return; delete ctx.state.localVars["神速/target"]; await applyAtom(ctx.state, { type: "请求回应", requestType: OPT1_TARGET_RT, target: ownerId, prompt: { type: "choosePlayer", title: "神速①:选择一名其他角色(视为出杀,无距离限制)", min: 1, max: 1, filter: (_view, t) => t !== ownerId && ctx.state.players[t]?.alive === true }, timeout: 15 }); const target = ctx.state.localVars["神速/target"]; delete ctx.state.localVars["神速/target"]; self.vars[OPT1_USED_KEY] = true; await applyAtom(ctx.state, { type: "加标签", player: ownerId, tag: SKIP_MO_TAG }); if (typeof target === "number" && ctx.state.players[target]?.alive) await virtualKill(ctx.state, ownerId, target); return skipPhase(ctx.state, atom); }); registerBeforeHook(state, skill.id, ownerId, "阶段开始", async (ctx) => { const atom = ctx.atom; if (atom.type !== "阶段开始") return; if (atom.player !== ownerId) return; if (atom.phase !== "出牌") return; if (ctx.state.currentPlayerIndex !== ownerId) return; const self = ctx.state.players[ownerId]; if (!self?.alive) return; if (self.vars[OPT2_USED_KEY]) return; const equipCount = Object.values(self.equipment).filter(Boolean).length; const hasOtherAlive = ctx.state.players.some((p, i) => i !== ownerId && p.alive); if (equipCount === 0 || !hasOtherAlive) return; delete ctx.state.localVars["神速/confirmed"]; await applyAtom(ctx.state, { type: "请求回应", requestType: OPT2_TRIGGER_RT, target: ownerId, prompt: { type: "confirm", title: "是否发动神速②?(弃置一张装备牌,跳过出牌,视为出杀)", confirmLabel: "发动", cancelLabel: "不发动" }, defaultChoice: false, timeout: 10 }); if (ctx.state.localVars["神速/confirmed"] !== true) return; delete ctx.state.localVars["神速/equipCardId"]; await applyAtom(ctx.state, { type: "请求回应", requestType: OPT2_EQUIP_RT, target: ownerId, prompt: { type: "distribute", mode: "select", title: "神速②:选择一张装备牌弃置", source: "handAndEquip", minTotal: 1, maxTotal: 1 }, timeout: 15 }); const equipCardIdRaw = ctx.state.localVars["神速/equipCardId"]; const equipCardId = (equipCardIdRaw && typeof equipCardIdRaw === "object" ? equipCardIdRaw.cardIds?.[0] : equipCardIdRaw) ?? void 0; delete ctx.state.localVars["神速/equipCardId"]; if (!equipCardId || !Object.values(self.equipment).includes(equipCardId)) return; delete ctx.state.localVars["神速/target"]; await applyAtom(ctx.state, { type: "请求回应", requestType: OPT2_TARGET_RT, target: ownerId, prompt: { type: "choosePlayer", title: "神速②:选择一名其他角色(视为出杀,无距离限制)", min: 1, max: 1, filter: (_view, t) => t !== ownerId && ctx.state.players[t]?.alive === true }, timeout: 15 }); const target = ctx.state.localVars["神速/target"]; delete ctx.state.localVars["神速/target"]; self.vars[OPT2_USED_KEY] = true; await applyAtom(ctx.state, { type: "弃置", player: ownerId, cardIds: [equipCardId], voluntary: true }); if (typeof target === "number" && ctx.state.players[target]?.alive) await virtualKill(ctx.state, ownerId, target); return skipPhase(ctx.state, atom); }); registerBeforeHook(state, skill.id, ownerId, "阶段开始", async (ctx) => { const atom = ctx.atom; if (atom.type !== "阶段开始") return; if (atom.player !== ownerId) return; if (atom.phase !== "摸牌") return; if (!ctx.state.players[ownerId]?.tags.includes(SKIP_MO_TAG)) return; return skipPhase(ctx.state, atom, SKIP_MO_TAG); }); registerBeforeHook(state, skill.id, ownerId, "阶段开始", async (ctx) => { const atom = ctx.atom; if (atom.type !== "阶段开始") return; if (atom.player !== ownerId) return; if (atom.phase !== "出牌") return; if (!ctx.state.players[ownerId]?.tags.includes(SKIP_PLAY_TAG)) return; return skipPhase(ctx.state, atom, SKIP_PLAY_TAG); }); registerBeforeHook(state, skill.id, ownerId, "阶段开始", async (ctx) => { const atom = ctx.atom; if (atom.type !== "阶段开始") return; if (atom.player !== ownerId) return; if (atom.phase !== "弃牌") return; if (ctx.state.currentPlayerIndex !== ownerId) return; const self = ctx.state.players[ownerId]; if (!self?.alive) return; if (self.vars[OPT3_USED_KEY]) return; if (!ctx.state.players.some((p, i) => i !== ownerId && p.alive)) return; delete ctx.state.localVars["神速/confirmed"]; await applyAtom(ctx.state, { type: "请求回应", requestType: OPT3_TRIGGER_RT, target: ownerId, prompt: { type: "confirm", title: "是否发动神速③?(翻面,跳过弃牌,视为出杀)", confirmLabel: "发动", cancelLabel: "不发动" }, defaultChoice: false, timeout: 10 }); if (ctx.state.localVars["神速/confirmed"] !== true) return; delete ctx.state.localVars["神速/target"]; await applyAtom(ctx.state, { type: "请求回应", requestType: OPT3_TARGET_RT, target: ownerId, prompt: { type: "choosePlayer", title: "神速③:选择一名其他角色(视为出杀,无距离限制)", min: 1, max: 1, filter: (_view, t) => t !== ownerId && ctx.state.players[t]?.alive === true }, timeout: 15 }); const target = ctx.state.localVars["神速/target"]; delete ctx.state.localVars["神速/target"]; self.vars[OPT3_USED_KEY] = true; await applyAtom(ctx.state, { type: "加标签", player: ownerId, tag: FLIP_TAG }); if (typeof target === "number" && ctx.state.players[target]?.alive) await virtualKill(ctx.state, ownerId, target); return skipPhase(ctx.state, atom); }); registerBeforeHook(state, skill.id, ownerId, "阶段开始", async (ctx) => { const atom = ctx.atom; if (atom.type !== "阶段开始") return; if (atom.player !== ownerId) return; const self = ctx.state.players[ownerId]; if (atom.phase === "准备" && self?.tags.includes(FLIP_TAG)) { await applyAtom(ctx.state, { type: "去标签", player: ownerId, tag: FLIP_TAG }); ctx.state.localVars[SKIP_FLAG] = ownerId; return { kind: "cancel" }; } if (ctx.state.localVars[SKIP_FLAG] === ownerId) return { kind: "cancel" }; }); registerBeforeHook(state, skill.id, ownerId, "阶段结束", async (ctx) => { const atom = ctx.atom; if (atom.type !== "阶段结束") return; if (atom.player !== ownerId) return; if (ctx.state.localVars[SKIP_FLAG] !== ownerId) return; delete ctx.state.localVars[SKIP_FLAG]; await applyAtom(ctx.state, { type: "清过期标记", player: ownerId }); await applyAtom(ctx.state, { type: "下一玩家" }); if (await applyAtom(ctx.state, { type: "回合结束", player: ownerId })) await applyAtom(ctx.state, { type: "回合结束后", player: ownerId }); return { kind: "cancel" }; }); return () => {}; } function onMount$54(_skill, api) { api.defineAction("respond", { label: "神速", style: "default", prompt: { type: "confirm", title: "是否发动神速?", confirmLabel: "发动", cancelLabel: "不发动" } }); } var OPT1_TRIGGER_RT, OPT1_TARGET_RT, OPT2_TRIGGER_RT, OPT2_TARGET_RT, OPT2_EQUIP_RT, OPT3_TRIGGER_RT, OPT3_TARGET_RT, SKIP_MO_TAG, SKIP_PLAY_TAG, FLIP_TAG, SKIP_FLAG, OPT1_USED_KEY, OPT2_USED_KEY, OPT3_USED_KEY; var init_神速 = __esmMin((() => { init_apply(); init_skill(); init_use_card(); init_skip_phase(); OPT1_TRIGGER_RT = "神速/opt1-trigger"; OPT1_TARGET_RT = "神速/opt1-target"; OPT2_TRIGGER_RT = "神速/opt2-trigger"; OPT2_TARGET_RT = "神速/opt2-target"; OPT2_EQUIP_RT = "神速/opt2-equip"; OPT3_TRIGGER_RT = "神速/opt3-trigger"; OPT3_TARGET_RT = "神速/opt3-target"; SKIP_MO_TAG = "神速/跳过摸牌"; SKIP_PLAY_TAG = "神速/跳过出牌"; FLIP_TAG = "神速/翻面"; SKIP_FLAG = "神速/skipAll"; OPT1_USED_KEY = "神速/opt1/usedThisTurn"; OPT2_USED_KEY = "神速/opt2/usedThisTurn"; OPT3_USED_KEY = "神速/opt3/usedThisTurn"; })); //#endregion //#region src/engine/skills/祸首.ts var 祸首_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$61, default: () => 祸首_default, onInit: () => onInit$61, onMount: () => onMount$53 }); function createSkill$61(id, ownerId) { return { id, ownerId, name: "祸首", description: "锁定技:南蛮入侵对你无效;你是任何南蛮入侵造成伤害的来源", isLocked: true }; } function onInit$61(skill, state) { const ownerId = skill.ownerId; registerBeforeHook(state, skill.id, ownerId, "询问杀", async (ctx) => { if (ctx.atom.target !== ownerId) return; if (topFrame(ctx.state)?.skillId !== "南蛮入侵") return; return { kind: "cancel" }; }); registerBeforeHook(state, skill.id, ownerId, "伤害结算开始时", async (ctx) => { const atom = ctx.atom; if (!atom.cardId) return; if (ctx.state.cardMap[atom.cardId]?.name !== "南蛮入侵") return; const mengAlive = ctx.state.players[ownerId]?.alive === true; if (atom.target === ownerId) return { kind: "cancel" }; if (mengAlive && atom.source !== void 0 && atom.source !== ownerId) return { kind: "modify", atom: { ...ctx.atom, source: ownerId } }; }); return () => {}; } function onMount$53(_skill, _api) {} var 祸首_default; var init_祸首 = __esmMin((() => { init_frame(); init_skill(); 祸首_default = { createSkill: createSkill$61, onInit: onInit$61, onMount: onMount$53 }; })); //#endregion //#region src/engine/skills/离间.ts var 离间_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$60, default: () => _skillModule$20, onInit: () => onInit$60, onMount: () => onMount$52 }); /** 校验某座次是否为男性存活角色 */ function isMaleAlive(state, target) { const p = state.players[target]; if (!p?.alive) return false; return getGender(p.character) === "男"; } function createSkill$60(id, ownerId) { return { id, ownerId, name: "离间", description: "出牌阶段限一次,弃一张牌,令一名男性角色视为对另一名男性角色使用决斗" }; } function onInit$60(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "use", (st, params) => { const self = st.players[ownerId]; if (!self?.alive) return "角色不可用"; if (st.currentPlayerIndex !== ownerId) return "只能在你的回合使用"; if (st.phase !== "出牌") return "只能在出牌阶段使用"; if (hasBlockingPending(st)) return "当前有未完成的询问"; if (usedThisTurn$3(st, ownerId, "离间")) return "本回合已使用过离间"; const cardId = params.cardId; if (typeof cardId !== "string") return "需要选择一张牌弃置"; const inHand = self.hand.includes(cardId); const inEquip = Object.values(self.equipment).includes(cardId); if (!inHand && !inEquip) return "牌不在你的手牌或装备区"; const targets = params.targets; if (!Array.isArray(targets) || targets.length !== 2) return "需要选择两名男性角色"; const [A, B] = targets; if (A === B) return "必须选择两名不同的角色"; if (!isMaleAlive(st, A)) return "决斗发起者必须是男性角色"; if (!isMaleAlive(st, B)) return "决斗目标必须是男性角色"; return null; }, async (st, params) => { const cardId = params.cardId; const [A, B] = params.targets; await markOncePerTurn(st, ownerId, "离间"); await pushFrame(st, "离间", ownerId, { ...params }); await applyAtom(st, { type: "弃置", player: ownerId, cardIds: [cardId], voluntary: true }); const virtualCardId = `离间:决斗:${A}:${B}:${st.seq}`; st.cardMap[virtualCardId] = { id: virtualCardId, name: "决斗", suit: "", color: "无色", rank: "A", type: "锦囊牌" }; await runUseFlow(st, A, virtualCardId, [B], "决斗", { virtual: true }); delete st.cardMap[virtualCardId]; await popFrame(st); }); return () => {}; } function onMount$52(skill, api) { api.defineAction("use", { label: "离间", style: "danger", prompt: { type: "useCardAndTarget", title: "离间:弃一张牌,令一名男性角色对另一名男性角色决斗", cardFilter: { filter: () => true, min: 1, max: 1 }, targetFilter: { min: 2, max: 2, filter: (view, target) => { const p = view.players[target]; if (!p || p.alive === false) return false; return getGender(p.character) === "男"; } } }, activeWhen: (ctx) => activeUnlessUsedThisTurn("离间")(ctx) }); return () => {}; } var _skillModule$20; var init_离间 = __esmMin((() => { init_apply(); init_frame(); init_once_per_turn(); init_skill(); init_character_meta(); init_use_card(); _skillModule$20 = { createSkill: createSkill$60, onInit: onInit$60, onMount: onMount$52 }; })); //#endregion //#region src/engine/skills/空城.ts var 空城_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$59, default: () => 空城_default, onInit: () => onInit$59 }); function createSkill$59(id, ownerId) { return { id, ownerId, name: "空城", description: "锁定技:若你没有手牌,你不是杀和决斗的合法目标", isLocked: true }; } function onInit$59(skill, state) { const ownerId = skill.ownerId; registerBeforeHook(state, skill.id, ownerId, "成为目标", async (ctx) => { const atom = ctx.atom; if (atom.target !== ownerId) return; if (ctx.state.players[ownerId]?.hand.length !== 0) return; let cardName; if (atom.cardId) cardName = ctx.state.cardMap[atom.cardId]?.name; if (!cardName) cardName = topFrame(ctx.state)?.skillId; if (cardName !== "杀" && cardName !== "决斗") return; return { kind: "cancel" }; }); return () => {}; } var 空城_default; var init_空城 = __esmMin((() => { init_frame(); init_skill(); 空城_default = { createSkill: createSkill$59, onInit: onInit$59 }; })); //#endregion //#region src/engine/skills/突袭.ts var 突袭_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$58, onInit: () => onInit$58, onMount: () => onMount$51 }); function createSkill$58(id, ownerId) { return { id, ownerId, name: "突袭", description: "摸牌阶段,你可以改为获得至多两名角色的各一张手牌" }; } function onInit$58(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "respond", (s) => { const slot = s.pendingSlots.get(ownerId); if (slot?.atom.type !== "请求回应") return "当前不需要回应"; const rt = slot.atom.requestType; if (rt !== TRIGGER_RT && rt !== SELECT_RT$1) return "当前不是突袭回应"; return null; }, async (s, params) => { const rt = (s.pendingSlots.get(ownerId)?.atom)?.requestType; if (rt === TRIGGER_RT) s.localVars[TRIGGERED_KEY] = params.choice === true; else if (rt === SELECT_RT$1) { const targets = params.targets ?? (typeof params.target === "number" ? [params.target] : void 0); s.localVars[TARGETS_KEY] = targets ?? []; } }); registerBeforeHook(state, skill.id, ownerId, "阶段开始", async (ctx) => { const atom = ctx.atom; if (atom.type !== "阶段开始") return; if (atom.player !== ownerId) return; if (atom.phase !== "摸牌") return; if (ctx.state.currentPlayerIndex !== ownerId) return; if (!ctx.state.players[ownerId]?.alive) return; if (!ctx.state.players.some((p, i) => i !== ownerId && p.alive && p.hand.length > 0)) return; delete ctx.state.localVars[TRIGGERED_KEY]; await applyAtom(ctx.state, { type: "请求回应", requestType: TRIGGER_RT, target: ownerId, prompt: { type: "confirm", title: "是否发动突袭?(放弃摸牌,获得至多两名角色各一张手牌)", confirmLabel: "发动", cancelLabel: "不发动" }, defaultChoice: false, timeout: 10 }); if (ctx.state.localVars[TRIGGERED_KEY] !== true) return; delete ctx.state.localVars[TARGETS_KEY]; await applyAtom(ctx.state, { type: "请求回应", requestType: SELECT_RT$1, target: ownerId, prompt: { type: "choosePlayer", title: "突袭:选择至多两名角色(各获得一张手牌)", min: 1, max: 2, filter: (view, target) => target !== ownerId && view.players[target]?.alive === true && (view.players[target]?.handCount ?? 0) > 0 }, timeout: 15 }); const rawTargets = ctx.state.localVars[TARGETS_KEY]; const targets = Array.isArray(rawTargets) ? rawTargets.filter((t) => t !== ownerId && ctx.state.players[t]?.alive === true && ctx.state.players[t].hand.length > 0) : []; if (targets.length === 0) return; for (const target of targets) { const tp = ctx.state.players[target]; if (!tp || !tp.alive || tp.hand.length === 0) continue; const rng = createRng(ctx.state.rngSeed); const idx = rng.nextInt(tp.hand.length); ctx.state.rngSeed = rng.getState(); const cardId = tp.hand[idx]; await applyAtom(ctx.state, { type: "获得", player: ownerId, cardId, from: target }); } await applyAtom(ctx.state, { type: "阶段结束", player: ownerId, phase: "摸牌" }); return { kind: "cancel" }; }); return () => {}; } function onMount$51(_skill, api) { api.defineAction("respond", { label: "突袭", style: "primary", prompt: { type: "confirm", title: "是否发动突袭?", confirmLabel: "发动", cancelLabel: "不发动" } }); } var TRIGGER_RT, SELECT_RT$1, TRIGGERED_KEY, TARGETS_KEY; var init_突袭 = __esmMin((() => { init_apply(); init_skill(); init_rng(); TRIGGER_RT = "突袭/trigger"; SELECT_RT$1 = "突袭/select"; TRIGGERED_KEY = "突袭/triggered"; TARGETS_KEY = "突袭/targets"; })); //#endregion //#region src/engine/skills/立军.ts var 立军_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$57, default: () => _skillModule$19, onInit: () => onInit$57, onMount: () => onMount$50 }); /** turn.vars key:本回合该盟友是否已用过立军(限一次) */ function usedKey(ally) { return `立军/used/${ally}`; } /** turn.vars key:本回合该盟友是否已获 +1 杀次 */ function quotaKey(ally) { return `立军/quota/${ally}`; } function createSkill$57(id, ownerId) { return { id, ownerId, name: "立军", description: "主公技:其他吴势力角色用杀后可交给你获得,你可令其摸一张牌且本回合使用杀的限制次数+1" }; } function onInit$57(skill, state) { const ownerId = skill.ownerId; const unloads = []; for (const p of state.players) { if (p.index === ownerId) continue; if (p.faction !== "吴") continue; const allyIdx = p.index; const u = registerSlashExtraProvider(state, allyIdx, (st) => st.turn.vars[quotaKey(allyIdx)] === true ? 1 : 0); unloads.push(u); } unloads.push(registerAfterHook(state, skill.id, ownerId, "使用时", async (ctx) => { const atom = ctx.atom; if (atom.type !== "使用时") return; const card = ctx.state.cardMap[atom.cardId]; if (!card) return; ctx.frame.params[EFFECTIVE_CARD_KEY] = card.shadowOf ?? atom.cardId; })); unloads.push(registerAfterHook(state, skill.id, ownerId, "使用结算结束后", async (ctx) => { const atom = ctx.atom; const st = ctx.state; if (ownerId !== 0) return; if ((st.cardMap[atom.cardId]?.name ?? ctx.frame?.skillId) !== "杀") return; const effectiveCardId = ctx.frame.params[EFFECTIVE_CARD_KEY] ?? atom.cardId; const source = atom.source; if (source === ownerId) return; if (st.currentPlayerIndex !== source) return; const ally = st.players[source]; if (!ally?.alive) return; if (ally.faction !== "吴") return; const lord = st.players[ownerId]; if (!lord?.alive) return; if (st.turn.vars[usedKey(source)] === true) return; st.turn.vars[usedKey(source)] = true; await pushFrame(st, "立军", ownerId, { ally: source, cardId: effectiveCardId }); delete st.localVars[ALLY_CONFIRM_KEY]; await applyAtom(st, { type: "请求回应", requestType: ALLY_CONFIRM_RT, target: source, prompt: { type: "confirm", title: `立军:是否将此【杀】交给 ${lord.name ?? "主公"}?`, confirmLabel: "交给主公", cancelLabel: "不交" }, defaultChoice: false, timeout: 30 }); const allyYes = st.localVars[ALLY_CONFIRM_KEY] === true; delete st.localVars[ALLY_CONFIRM_KEY]; if (!allyYes) { await popFrame(st); return; } if (st.zones.discardPile.includes(effectiveCardId)) await applyAtom(st, { type: "移动牌", cardId: effectiveCardId, from: { zone: "弃牌堆" }, to: { zone: "手牌", player: ownerId } }); delete st.localVars[LORD_CONFIRM_KEY]; await applyAtom(st, { type: "请求回应", requestType: LORD_CONFIRM_RT, target: ownerId, prompt: { type: "confirm", title: `立军:是否令 ${ally.name ?? "该角色"} 摸一张牌且本回合使用杀的限制次数+1?`, confirmLabel: "令其摸牌+杀次+1", cancelLabel: "不发动" }, defaultChoice: false, timeout: 30 }); const lordYes = st.localVars[LORD_CONFIRM_KEY] === true; delete st.localVars[LORD_CONFIRM_KEY]; if (!lordYes) { await popFrame(st); return; } if (st.players[source]?.alive) await applyAtom(st, { type: "摸牌", player: source, count: 1 }); st.turn.vars[quotaKey(source)] = true; await applyAtom(st, { type: "回合用量", player: source, key: slashExtraKey("立军"), value: 1 }); await popFrame(st); })); for (const p of state.players) { const seatId = p.index; const u = registerAction(state, skill.id, seatId, "respond", (st, _params) => { const slot = st.pendingSlots.get(seatId); if (!slot) return "当前不需要回应"; if (slot.atom.type !== "请求回应") return "当前不是立军窗口"; const rt = slot.atom.requestType; if (rt !== ALLY_CONFIRM_RT && rt !== LORD_CONFIRM_RT) return "当前不是立军窗口"; return null; }, async (st, params) => { const rt = st.pendingSlots.get(seatId).atom.requestType; const yes = params.choice === true || params.confirmed === true; if (rt === ALLY_CONFIRM_RT) st.localVars[ALLY_CONFIRM_KEY] = yes; else if (rt === LORD_CONFIRM_RT) st.localVars[LORD_CONFIRM_KEY] = yes; }); unloads.push(u); } return () => { for (const u of unloads) u(); }; } function onMount$50(_skill, _api) { return () => {}; } var ALLY_CONFIRM_RT, LORD_CONFIRM_RT, ALLY_CONFIRM_KEY, LORD_CONFIRM_KEY, EFFECTIVE_CARD_KEY, _skillModule$19; var init_立军 = __esmMin((() => { init_apply(); init_frame(); init_skill(); init_slash_quota(); init_vars_keys(); ALLY_CONFIRM_RT = "立军/盟友确认"; LORD_CONFIRM_RT = "立军/主公确认"; ALLY_CONFIRM_KEY = "立军/盟友确认结果"; LORD_CONFIRM_KEY = "立军/主公确认结果"; EFFECTIVE_CARD_KEY = "立军/effectiveCardId"; _skillModule$19 = { createSkill: createSkill$57, onInit: onInit$57, onMount: onMount$50 }; })); //#endregion //#region src/engine/skills/箜声.ts var 箜声_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$56, default: () => _skillModule$18, onInit: () => onInit$56, onMount: () => onMount$49 }); /** 读取 owner 的箜声牌列表 */ function kongshengCards(state, player) { return state.players[player].vars[KONGSHENG_KEY$1] ?? []; } /** 装备牌 subtype → 装备栏位(与 装备 atom 的 inferSlot 一致) */ function slotOf(card) { switch (card?.subtype) { case "武器": return "武器"; case "防具": return "防具"; case "进攻马": return "进攻马"; case "防御马": return "防御马"; case "宝物": return "宝物"; default: return null; } } function createSkill$56(id, ownerId) { return { id, ownerId, name: "箜声", description: "准备阶段,你可以将任意张牌置于你的武将牌上;结束阶段,你获得其中的非装备牌,然后令一名角色使用剩余牌并失去1点体力" }; } /** 准备阶段:询问置牌 → 选牌 → 移出至暂存区(置于武将牌上)。 */ async function performKongshengPrepare(state, ownerId) { const self = state.players[ownerId]; if (!self?.alive) return; if (self.hand.length === 0) return; delete state.localVars[CONFIRMED_KEY$9]; await applyAtom(state, { type: "请求回应", requestType: CONFIRM_RT$11, target: ownerId, prompt: { type: "confirm", title: "是否发动箜声?(将任意张牌置于武将牌上)", confirmLabel: "发动", cancelLabel: "不发动" }, defaultChoice: false, timeout: 15 }); if (!state.localVars[CONFIRMED_KEY$9]) { delete state.localVars[CONFIRMED_KEY$9]; return; } delete state.localVars[CONFIRMED_KEY$9]; delete state.localVars[SELECT_KEY]; await applyAtom(state, { type: "请求回应", requestType: SELECT_RT, target: ownerId, prompt: { type: "useCard", title: "箜声:选择要置于武将牌上的牌", cardFilter: { filter: () => true, min: 1, max: self.hand.length } }, timeout: 30 }); const raw = state.localVars[SELECT_KEY]; delete state.localVars[SELECT_KEY]; const selected = Array.isArray(raw) && raw.length > 0 && raw.every((id) => self.hand.includes(id)) && new Set(raw).size === raw.length ? raw : []; if (selected.length === 0) return; await applyAtom(state, { type: "移出至暂存区", source: ownerId, target: ownerId, cardIds: selected, varsKey: KONGSHENG_KEY$1 }); } /** 结束阶段:获得非装备牌 → 令一名角色使用装备牌 → 该角色失去1点体力。 */ async function performKongshengEnd(state, ownerId) { if (!state.players[ownerId]?.alive) return; const cards = kongshengCards(state, ownerId); if (cards.length === 0) return; const equipIds = cards.filter((id) => state.cardMap[id]?.type === "装备牌"); await applyAtom(state, { type: "归还暂存牌", player: ownerId, varsKey: KONGSHENG_KEY$1 }); if (equipIds.length === 0) return; if (!state.players[ownerId]?.alive) return; delete state.localVars[TARGET_KEY$10]; await applyAtom(state, { type: "请求回应", requestType: TARGET_RT$6, target: ownerId, prompt: { type: "choosePlayer", title: "箜声:令一名角色使用剩余(装备)牌并失去1点体力", min: 1, max: 1, filter: (_view, t) => state.players[t]?.alive === true }, timeout: 30 }); const target = state.localVars[TARGET_KEY$10]; delete state.localVars[TARGET_KEY$10]; if (typeof target !== "number" || !state.players[target]?.alive) return; for (const cardId of equipIds) { if (!state.players[ownerId]?.alive) break; if (!state.players[ownerId].hand.includes(cardId)) continue; if (target !== ownerId) await applyAtom(state, { type: "给予", cardId, from: ownerId, to: target }); const card = state.cardMap[cardId]; const slot = slotOf(card); if (!slot) continue; const current = state.players[target].equipment[slot]; if (current) { const oldCard = state.cardMap[current]; if (oldCard?.name && hasSkillModule(oldCard.name)) await applyAtom(state, { type: "移除技能", player: target, skillId: oldCard.name }); await applyAtom(state, { type: "卸下", player: target, slot }); await applyAtom(state, { type: "移动牌", cardId: current, from: { zone: "手牌", player: target }, to: { zone: "弃牌堆" } }); } await applyAtom(state, { type: "装备", player: target, cardId }); if (card?.name && hasSkillModule(card.name)) await applyAtom(state, { type: "添加技能", player: target, skillId: card.name }); } if (state.players[target]?.alive) await applyAtom(state, { type: "失去体力", target, amount: 1 }); } function onInit$56(skill, state) { const ownerId = skill.ownerId; registerAfterHook(state, skill.id, ownerId, "阶段开始", async (ctx) => { const atom = ctx.atom; if (atom.type !== "阶段开始") return; if (ctx.state.currentPlayerIndex !== ownerId) return; if (atom.phase === "准备") await performKongshengPrepare(ctx.state, ownerId); else if (atom.phase === "回合结束") await performKongshengEnd(ctx.state, ownerId); }); registerAction(state, skill.id, ownerId, "respond", (st, params) => { const slot = st.pendingSlots.get(ownerId); if (!slot) return "当前不需要回应"; if (slot.atom.type !== "请求回应") return "当前不需要回应"; const rt = slot.atom.requestType; if (rt === CONFIRM_RT$11) return null; if (rt === SELECT_RT) { const raw = params.cardIds ?? params.cardId; const ids = Array.isArray(raw) ? raw : typeof raw === "string" ? [raw] : void 0; if (!ids || ids.length === 0) return null; return null; } if (rt === TARGET_RT$6) { if (typeof params.target !== "number") return "需要选择一名角色"; return null; } return "当前不是箜声询问"; }, async (st, params) => { const rt = (st.pendingSlots.get(ownerId)?.atom)?.requestType; if (rt === CONFIRM_RT$11) st.localVars[CONFIRMED_KEY$9] = params.choice === true || params.confirmed === true; else if (rt === SELECT_RT) { const raw = params.cardIds ?? params.cardId; const ids = Array.isArray(raw) ? raw : typeof raw === "string" ? [raw] : void 0; if (ids && ids.length > 0) st.localVars[SELECT_KEY] = ids; } else if (rt === TARGET_RT$6) { const t = params.target; if (typeof t === "number") st.localVars[TARGET_KEY$10] = t; } }); return () => {}; } function onMount$49(_skill, _api) { return () => {}; } var KONGSHENG_KEY$1, CONFIRM_RT$11, SELECT_RT, TARGET_RT$6, CONFIRMED_KEY$9, SELECT_KEY, TARGET_KEY$10, _skillModule$18; var init_箜声 = __esmMin((() => { init_apply(); init_skill(); init_registry$1(); KONGSHENG_KEY$1 = "箜声/牌"; CONFIRM_RT$11 = "箜声/confirm"; SELECT_RT = "箜声/select"; TARGET_RT$6 = "箜声/target"; CONFIRMED_KEY$9 = "箜声/confirmed"; SELECT_KEY = "箜声/selected"; TARGET_KEY$10 = "箜声/target"; _skillModule$18 = { createSkill: createSkill$56, onInit: onInit$56, onMount: onMount$49 }; })); //#endregion //#region src/engine/skills/红颜.ts var 红颜_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$55, default: () => 红颜_default, onInit: () => onInit$55, onMount: () => onMount$48 }); function createSkill$55(id, ownerId) { return { id, ownerId, name: "红颜", description: "锁定技:你的黑桃牌均视为红桃牌", isLocked: true }; } function onInit$55(skill, state) { const ownerId = skill.ownerId; registerBeforeHook(state, skill.id, ownerId, "判定", async (ctx) => { if (ctx.atom.player !== ownerId) return; const topId = ctx.state.zones.deck[ctx.state.zones.deck.length - 1]; if (!topId) return; const card = ctx.state.cardMap[topId]; if (!card) return; if (card.suit !== "♠") return; card.suit = "♥"; card.color = "红"; }); return () => {}; } function onMount$48(_skill, _api) {} var 红颜_default; var init_红颜 = __esmMin((() => { init_skill(); 红颜_default = { createSkill: createSkill$55, onInit: onInit$55, onMount: onMount$48 }; })); //#endregion //#region src/engine/skills/结姻.ts var 结姻_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$54, default: () => _skillModule$17, onInit: () => onInit$54, onMount: () => onMount$47 }); function createSkill$54(id, ownerId) { return { id, ownerId, name: "结姻", description: "出牌阶段限一次,弃两张手牌,令你与一名已受伤的男性角色各回复1点体力" }; } /** 校验某座次是否为合法结姻目标:存活 + 已受伤 + 男性。 */ function isValidTarget(state, target) { const p = state.players[target]; if (!p?.alive) return false; if (p.health >= p.maxHealth) return false; return getGender(p.character) === "男"; } /** 从 params 规范化出 { cardIds, target }。 * 支持三种格式: * 1. distribute/allocate: params.allocation = [{target, cardIds}] * 2. 简单: params.cardIds = [...] + params.target = idx * 3. 单卡兼容(不适用于结姻,需 2 张): params.cardId + params.target */ function resolveParams(params) { const allocation = params.allocation; if (Array.isArray(allocation) && allocation.length > 0) return { cardIds: allocation.flatMap((a) => a.cardIds), target: allocation[0].target }; const cardIds = params.cardIds; const target = params.target; if (Array.isArray(cardIds) && typeof target === "number") return { cardIds, target }; return null; } function onInit$54(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "use", (state, params) => { const self = state.players[ownerId]; if (!self?.alive) return "角色不可用"; if (state.currentPlayerIndex !== ownerId) return "不是你的回合"; if (state.phase !== "出牌") return "不是出牌阶段"; if (hasBlockingPending(state)) return "当前有未回应的询问"; if (usedThisTurn$3(state, ownerId, "结姻")) return "本回合已使用过结姻"; if (self.hand.length < 2) return "手牌不足两张"; const resolved = resolveParams(params); if (!resolved) return "需要选择两张手牌和一名目标"; const { cardIds, target } = resolved; if (cardIds.length !== 2) return "必须弃置两张手牌"; if (new Set(cardIds).size !== 2) return "不能弃置相同的牌"; if (!cardIds.every((id) => self.hand.includes(id))) return "牌不在手牌中"; if (!isValidTarget(state, target)) return "目标必须是已受伤的男性角色"; return null; }, async (state, params) => { const resolved = resolveParams(params); if (!resolved) return; const { cardIds, target } = resolved; await markOncePerTurn(state, ownerId, "结姻"); await pushFrame(state, "结姻", ownerId, { cardIds, target }); await applyAtom(state, { type: "弃置", player: ownerId, cardIds, voluntary: true }); await applyAtom(state, { type: "回复体力", target: ownerId, amount: 1, source: ownerId }); await applyAtom(state, { type: "回复体力", target, amount: 1, source: ownerId }); await popFrame(state); }); return () => {}; } function onMount$47(skill, api) { api.defineAction("use", { label: "结姻", style: "primary", prompt: { type: "distribute", mode: "allocate", title: "结姻:弃两张手牌,令你与一名已受伤的男性角色各回复1点体力", source: "hand", minTotal: 2, maxTotal: 2, minPerTarget: 2, maxPerTarget: 2, allowSelf: false, targetFilter: (view, target) => { const p = view.players[target]; if (!p || p.alive === false) return false; if ((p.health ?? 0) >= (p.maxHealth ?? 0)) return false; return getGender(p.character) === "男"; } }, activeWhen: (ctx) => activeUnlessUsedThisTurn("结姻")(ctx) }); return () => {}; } var _skillModule$17; var init_结姻 = __esmMin((() => { init_apply(); init_frame(); init_once_per_turn(); init_skill(); init_character_meta(); _skillModule$17 = { createSkill: createSkill$54, onInit: onInit$54, onMount: onMount$47 }; })); //#endregion //#region src/engine/skills/缔盟.ts var 缔盟_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$53, default: () => _skillModule$16, onInit: () => onInit$53, onMount: () => onMount$46 }); function createSkill$53(id, ownerId) { return { id, ownerId, name: "缔盟", description: "出牌阶段限一次,选择两名其他角色,弃置等同于他们手牌数差的牌,然后交换他们的手牌" }; } function onInit$53(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "use", (st, _params) => { if (st.currentPlayerIndex !== ownerId) return "不是你的回合"; if (st.phase !== "出牌") return "只能在出牌阶段发动"; if (hasBlockingPending(st)) return "当前有未完成的询问"; if (usedThisTurn$3(st, ownerId, "缔盟")) return "本回合已使用过缔盟"; if (!st.players[ownerId]?.alive) return "角色不可用"; if (st.players.filter((p) => p.alive && p.index !== ownerId).length < 2) return "需要至少两名其他角色"; return null; }, async (st, _params) => { await markOncePerTurn(st, ownerId, "缔盟"); await pushFrame(st, "缔盟", ownerId, {}); delete st.localVars[TARGET_KEY$9]; await applyAtom(st, { type: "请求回应", requestType: TARGET_RT$5, target: ownerId, prompt: { type: "choosePlayer", title: "缔盟:选择两名其他角色交换手牌", min: 2, max: 2, filter: (_view, t) => t !== ownerId && st.players[t]?.alive === true }, timeout: 30 }); const targets = st.localVars[TARGET_KEY$9]; delete st.localVars[TARGET_KEY$9]; if (!Array.isArray(targets) || targets.length !== 2) { await popFrame(st); return; } const [A, B] = targets; if (!st.players[A]?.alive || !st.players[B]?.alive) { await popFrame(st); return; } const diff = Math.abs(st.players[A].hand.length - st.players[B].hand.length); const actualDiscard = Math.min(diff, st.players[ownerId].hand.length); if (actualDiscard > 0) { delete st.localVars[DISCARD_KEY$4]; await applyAtom(st, { type: "请求回应", requestType: DISCARD_RT$5, target: ownerId, prompt: { type: "useCard", title: `缔盟:弃置 ${actualDiscard} 张牌`, cardFilter: { filter: () => true, min: actualDiscard, max: actualDiscard } }, mandatory: true, timeout: 30 }); let discardCards = st.localVars[DISCARD_KEY$4]; delete st.localVars[DISCARD_KEY$4]; if (!discardCards || discardCards.length === 0) discardCards = st.players[ownerId]?.hand.slice(0, actualDiscard) ?? []; if (discardCards.length > 0) await applyAtom(st, { type: "弃置", player: ownerId, cardIds: discardCards, voluntary: true }); } const handA = [...st.players[A].hand]; const handB = [...st.players[B].hand]; for (const cardId of handA) await applyAtom(st, { type: "移动牌", cardId, from: { zone: "手牌", player: A }, to: { zone: "手牌", player: B } }); for (const cardId of handB) await applyAtom(st, { type: "移动牌", cardId, from: { zone: "手牌", player: B }, to: { zone: "手牌", player: A } }); await popFrame(st); }); registerAction(state, skill.id, ownerId, "respond", (st, _params) => { const slot = st.pendingSlots.get(ownerId); if (!slot) return "当前不需要回应"; const atom = slot.atom; if (atom["type"] !== "请求回应") return "当前不需要回应"; const rt = atom["requestType"]; if (rt !== TARGET_RT$5 && rt !== DISCARD_RT$5) return "当前不是缔盟询问"; return null; }, async (st, params) => { const slot = st.pendingSlots.get(ownerId); const rt = (slot?.atom)?.requestType; if (rt === TARGET_RT$5) { const targets = params.targets; if (Array.isArray(targets) && targets.length === 2) st.localVars[TARGET_KEY$9] = targets; } else if (rt === DISCARD_RT$5) { const ids = params.cardIds; const need = (slot?.atom)?.prompt?.cardFilter?.min; if (Array.isArray(ids) && typeof need === "number" && ids.length === need && ids.every((id) => st.players[ownerId]?.hand.includes(id))) st.localVars[DISCARD_KEY$4] = ids; } }); return () => {}; } function onMount$46(_skill, api) { api.defineAction("use", { label: "缔盟", style: "primary", prompt: { type: "confirm", title: "缔盟:选择两名其他角色交换手牌", confirmLabel: "发动", cancelLabel: "取消" }, activeWhen: (ctx) => activeUnlessUsedThisTurn("缔盟")(ctx) }); api.defineAction("respond", { label: "缔盟", style: "primary", prompt: { type: "confirm", title: "缔盟", confirmLabel: "确认", cancelLabel: "取消" } }); return () => {}; } var TARGET_RT$5, DISCARD_RT$5, TARGET_KEY$9, DISCARD_KEY$4, _skillModule$16; var init_缔盟 = __esmMin((() => { init_apply(); init_frame(); init_once_per_turn(); init_skill(); TARGET_RT$5 = "缔盟/target"; DISCARD_RT$5 = "缔盟/discard"; TARGET_KEY$9 = "缔盟/targets"; DISCARD_KEY$4 = "缔盟/discardCards"; _skillModule$16 = { createSkill: createSkill$53, onInit: onInit$53, onMount: onMount$46 }; })); //#endregion //#region src/engine/skills/罪论.ts var 罪论_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$52, default: () => _skillModule$15, onInit: () => onInit$52, onMount: () => onMount$45 }); function createSkill$52(id, ownerId) { return { id, ownerId, name: SKILL_ID$3, description: "结束阶段可观看牌堆顶三张牌,获得其中X张(X为满足项数:本回合造成过伤害/未弃置过牌/手牌数全场最少),余牌任意顺序置牌堆顶;均不满足则自己与一名其他角色各失去1点体力" }; } /** 当前 pending 的 requestType(类型安全读取) */ function currentRequestType$4(state, ownerId) { const slot = state.pendingSlots.get(ownerId); if (!slot) return void 0; return slot.atom.requestType; } function onInit$52(skill, state) { const ownerId = skill.ownerId; registerAfterHook(state, skill.id, ownerId, "造成伤害后", async (ctx) => { if (ctx.atom.source === ownerId) ctx.state.turn.vars[damageVar(ownerId)] = true; }); registerAfterHook(state, skill.id, ownerId, "弃置", async (ctx) => { if (ctx.atom.player === ownerId) ctx.state.turn.vars[discardVar(ownerId)] = true; }); registerAction(state, skill.id, ownerId, "respond", (st, params) => { const rt = currentRequestType$4(st, ownerId); if (rt !== CONFIRM_RT$10 && rt !== PICK_RT && rt !== CHOOSE_TARGET_RT) return "当前不是罪论询问"; if (rt === CONFIRM_RT$10) return null; if (rt === CHOOSE_TARGET_RT) { const tgt = params.target ?? params.targets?.[0]; if (typeof tgt !== "number") return "请选择一名其他角色"; if (tgt === ownerId) return "不能选择自己"; if (!st.players[tgt]?.alive) return "目标已死亡"; return null; } const observed = st.localVars[OBSERVED_KEY] ?? []; const expectedGain = st.localVars[EXPECTED_GAIN_KEY] ?? 0; const gained = params.gained; const topOrder = params.topOrder; if (!Array.isArray(gained) || !Array.isArray(topOrder)) return "需要 gained/topOrder 划分"; if (gained.length !== expectedGain) return `应获得 ${expectedGain} 张牌`; const observedSet = new Set(observed); const combined = [...gained, ...topOrder]; if (!(combined.length === observed.length && new Set(combined).size === combined.length && combined.every((id) => observedSet.has(id)))) return "划分不合法(必须恰好覆盖观看的牌)"; return null; }, async (st, params) => { const rt = currentRequestType$4(st, ownerId); if (rt === CONFIRM_RT$10) st.localVars[CONFIRM_KEY$2] = params.choice === true || params.confirmed === true; else if (rt === PICK_RT) st.localVars[PICK_KEY] = { gained: params.gained ?? [], topOrder: params.topOrder ?? [] }; else if (rt === CHOOSE_TARGET_RT) { const tgt = params.target ?? params.targets?.[0]; if (typeof tgt === "number") st.localVars[TARGET_KEY$8] = tgt; } }); registerAfterHook(state, skill.id, ownerId, "阶段开始", async (ctx) => { const atom = ctx.atom; if (atom.type !== "阶段开始") return; if (atom.phase !== "回合结束") return; if (atom.player !== ownerId) return; const st = ctx.state; const self = st.players[ownerId]; if (!self?.alive) return; const causedDamage = !!st.turn.vars[damageVar(ownerId)]; const notDiscarded = !st.turn.vars[discardVar(ownerId)]; const alivePlayers = st.players.filter((p) => p.alive); const minHand = alivePlayers.reduce((m, p) => Math.min(m, p.hand.length), Infinity); const fewestHand = alivePlayers.length > 0 && self.hand.length === minHand; const x = (causedDamage ? 1 : 0) + (notDiscarded ? 1 : 0) + (fewestHand ? 1 : 0); if (x === 0) { const others = alivePlayers.filter((p) => p.index !== ownerId); let target; if (others.length > 0) { delete st.localVars[TARGET_KEY$8]; await applyAtom(st, { type: "请求回应", requestType: CHOOSE_TARGET_RT, target: ownerId, prompt: { type: "choosePlayer", title: "罪论:选择一名其他角色与其各失去1点体力", min: 1, max: 1, candidates: others.map((p) => p.index) }, timeout: 20 }); target = st.localVars[TARGET_KEY$8]; delete st.localVars[TARGET_KEY$8]; if (typeof target !== "number" || !st.players[target]?.alive || target === ownerId) target = others[0].index; } await applyAtom(st, { type: "失去体力", target: ownerId, amount: 1 }); if (typeof target === "number" && st.players[target]?.alive) await applyAtom(st, { type: "失去体力", target, amount: 1 }); return; } const deck = st.zones.deck; if (deck.length === 0) return; const drawCount = Math.min(3, deck.length); const observed = deck.slice(-drawCount); const gainCount = Math.min(x, drawCount); delete st.localVars[CONFIRM_KEY$2]; await applyAtom(st, { type: "请求回应", requestType: CONFIRM_RT$10, target: ownerId, prompt: { type: "confirm", title: `是否发动罪论?(满足 ${x} 项,获得 ${gainCount} 张)`, confirmLabel: "发动", cancelLabel: "不发动" }, defaultChoice: false, timeout: 15 }); if (!st.localVars[CONFIRM_KEY$2]) { delete st.localVars[CONFIRM_KEY$2]; return; } delete st.localVars[CONFIRM_KEY$2]; st.localVars[OBSERVED_KEY] = observed; st.localVars[EXPECTED_GAIN_KEY] = gainCount; delete st.localVars[PICK_KEY]; await applyAtom(st, { type: "请求回应", requestType: PICK_RT, target: ownerId, prompt: { type: "distribute", title: `罪论:选择 ${gainCount} 张获得,其余 ${drawCount - gainCount} 张按顺序置于牌堆顶`, description: "respond: { gained: 要获得的牌; topOrder: 留在牌堆顶的牌(顺序即摸牌顺序,首个最先摸到) }", mode: "select", cardIds: observed, minTotal: drawCount, maxTotal: drawCount }, timeout: 30 }); let pick = st.localVars[PICK_KEY]; delete st.localVars[PICK_KEY]; delete st.localVars[OBSERVED_KEY]; delete st.localVars[EXPECTED_GAIN_KEY]; if (!pick || !Array.isArray(pick.gained) || !Array.isArray(pick.topOrder)) pick = { gained: observed.slice(0, gainCount), topOrder: observed.slice(gainCount) }; const gained = pick.gained; const topOrder = pick.topOrder; for (const cardId of gained) if (st.zones.deck.includes(cardId)) await applyAtom(st, { type: "移动牌", cardId, from: { zone: "牌堆" }, to: { zone: "手牌", player: ownerId } }); if (topOrder.length > 0) { const remainingCount = topOrder.length; const newDeck = [...st.zones.deck.slice(0, st.zones.deck.length - remainingCount), ...[...topOrder].reverse()]; if (newDeck.length === st.zones.deck.length) await applyAtom(st, { type: "整理牌堆", cards: newDeck, topCount: remainingCount }); } }); return () => {}; } function onMount$45(_skill, api) { api.defineAction("respond", { label: SKILL_ID$3, style: "primary", prompt: { type: "confirm", title: "是否发动罪论?", confirmLabel: "发动", cancelLabel: "不发动" } }); return () => {}; } var SKILL_ID$3, CONFIRM_RT$10, PICK_RT, CHOOSE_TARGET_RT, CONFIRM_KEY$2, PICK_KEY, TARGET_KEY$8, EXPECTED_GAIN_KEY, OBSERVED_KEY, damageVar, discardVar, _skillModule$15; var init_罪论 = __esmMin((() => { init_apply(); init_skill(); SKILL_ID$3 = "罪论"; CONFIRM_RT$10 = `${SKILL_ID$3}/confirm`; PICK_RT = `${SKILL_ID$3}/pick`; CHOOSE_TARGET_RT = `${SKILL_ID$3}/chooseTarget`; CONFIRM_KEY$2 = `${SKILL_ID$3}/confirmed`; PICK_KEY = `${SKILL_ID$3}/pick`; TARGET_KEY$8 = `${SKILL_ID$3}/target`; EXPECTED_GAIN_KEY = `${SKILL_ID$3}/expectedGain`; OBSERVED_KEY = `${SKILL_ID$3}/observed`; damageVar = (ownerId) => `${SKILL_ID$3}/伤害/${ownerId}`; discardVar = (ownerId) => `${SKILL_ID$3}/弃置/${ownerId}`; _skillModule$15 = { createSkill: createSkill$52, onInit: onInit$52, onMount: onMount$45 }; })); //#endregion //#region src/engine/skills/耀武.ts var 耀武_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$51, default: () => _skillModule$14, onInit: () => onInit$51, onMount: () => onMount$44 }); function createSkill$51(id, ownerId) { return { id, ownerId, name: "耀武", description: "锁定技,当一名角色使用红色杀对你造成伤害时,其选择回复1点体力或摸一张牌", isLocked: true }; } function onInit$51(skill, state) { const ownerId = skill.ownerId; const unloads = []; for (const p of state.players) { const pid = p.index; unloads.push(registerAction(state, skill.id, pid, "respond", (st, params) => { const slot = st.pendingSlots.get(pid); if (!slot) return "当前不需要回应"; if (slot.atom.type !== "请求回应") return "当前不需要回应"; if (slot.atom.requestType !== CHOICE_RT) return "当前不是耀武选择"; const opt = params.option; if (opt !== OPT_RECOVER && opt !== OPT_DRAW) return "请选择回复体力或摸牌"; return null; }, async (st, params) => { st.localVars[CHOICE_KEY] = params.option; })); } unloads.push(registerAfterHook(state, skill.id, ownerId, "造成伤害后", async (ctx) => { const atom = ctx.atom; if (atom.target !== ownerId) return; if ((atom.amount ?? 0) <= 0) return; const source = atom.source; if (typeof source !== "number") return; if (!ctx.state.players[source]?.alive) return; const damageCardId = atom.cardId; const damageCard = damageCardId ? ctx.state.cardMap[damageCardId] : void 0; if (!damageCard) return; if (damageCard.name !== "杀") return; if (damageCard.color !== "红") return; delete ctx.state.localVars[CHOICE_KEY]; await applyAtom(ctx.state, { type: "请求回应", requestType: CHOICE_RT, target: source, prompt: { type: "chooseOption", title: `耀武:你对 ${ctx.state.players[ownerId].name} 造成红色杀伤害,选择一项`, options: [{ value: OPT_RECOVER, label: "回复1点体力" }, { value: OPT_DRAW, label: "摸一张牌" }] }, timeout: 20 }); const choice = ctx.state.localVars[CHOICE_KEY]; delete ctx.state.localVars[CHOICE_KEY]; if (choice === OPT_RECOVER) await applyAtom(ctx.state, { type: "回复体力", target: source, amount: 1, source: ownerId }); else await applyAtom(ctx.state, { type: "摸牌", player: source, count: 1 }); })); return () => { for (const u of unloads) u(); }; } function onMount$44(_skill, api) { api.defineAction("respond", { label: "耀武", style: "primary", prompt: { type: "chooseOption", title: "耀武:选择一项", options: [{ value: OPT_RECOVER, label: "回复1点体力" }, { value: OPT_DRAW, label: "摸一张牌" }] } }); } var CHOICE_RT, CHOICE_KEY, OPT_RECOVER, OPT_DRAW, _skillModule$14; var init_耀武 = __esmMin((() => { init_apply(); init_skill(); CHOICE_RT = "耀武/选择"; CHOICE_KEY = "耀武/选择结果"; OPT_RECOVER = "recover"; OPT_DRAW = "draw"; _skillModule$14 = { createSkill: createSkill$51, onInit: onInit$51, onMount: onMount$44 }; })); //#endregion //#region src/engine/skills/肉林.ts var 肉林_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$50, default: () => 肉林_default, onInit: () => onInit$50 }); function createSkill$50(id, ownerId) { return { id, ownerId, name: "肉林", description: "锁定技:你对女性角色/女性角色对你使用【杀】时,目标需连续出两张闪才能抵消", isLocked: true }; } function roulinApplies(state, source, target) { const src = state.players[source]; const tgt = state.players[target]; if (!src || !tgt) return false; const srcRoulin = src.skills.includes("肉林"); const tgtRoulin = tgt.skills.includes("肉林"); const tgtFemale = getGender(tgt.character) === "女"; const srcFemale = getGender(src.character) === "女"; return srcRoulin && tgtFemale || tgtRoulin && srcFemale; } function dodgeCountKey(killCardId, target) { return `肉林/dodgeCount/${killCardId}/${target}`; } function getKillCardId(state) { return state.settlementStack[state.settlementStack.length - 1]?.params?.cardId ?? ""; } function onInit$50(skill, state) { const ownerId = skill.ownerId; registerAfterHook(state, skill.id, ownerId, "询问闪", async (ctx) => { const atom = ctx.atom; if (!roulinApplies(ctx.state, atom.source, atom.target)) return; const target = atom.target; const killCardId = getKillCardId(ctx.state); if (ctx.state.cardMap[killCardId]?.name !== "杀") return; if (!isCancelled(ctx.state, killCardId, target)) return; const countKey = dodgeCountKey(killCardId, target); const count = ctx.state.localVars[countKey] ?? 0; if (count >= 1) { delete ctx.state.localVars[countKey]; return; } clearCancelled(ctx.state, killCardId, target); ctx.state.localVars[countKey] = count + 1; await applyAtom(ctx.state, { type: "询问闪", target, source: atom.source }); delete ctx.state.localVars[countKey]; }); return () => {}; } var 肉林_default; var init_肉林 = __esmMin((() => { init_apply(); init_skill(); init_frame(); init_character_meta(); 肉林_default = { createSkill: createSkill$50, onInit: onInit$50 }; })); //#endregion //#region src/engine/skills/良姻.ts var 良姻_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$49, default: () => _skillModule$13, onInit: () => onInit$49, onMount: () => onMount$43 }); /** 本回合是否已触发良姻(turn.vars,回合结束自动清空) */ function triggeredKey(ownerId) { return `良姻/已触发/${ownerId}`; } /** 当前箜声牌数(X) */ function kongshengCount(state, player) { return state.players[player].vars[KONGSHENG_KEY]?.length ?? 0; } /** 从 respond params 提取选中的目标座次(targets 数组优先,兼容 target 单值) */ function extractTarget(params) { const arr = params.targets; if (Array.isArray(arr) && arr.length > 0) return arr[0]; const t = params.target; return typeof t === "number" ? t : void 0; } function createSkill$49(id, ownerId) { return { id, ownerId, name: "良姻", description: "每回合首次有牌移出/移入游戏后,你可以与一名其他角色各摸/弃置一张牌,然后可令其中一名手牌数为X的角色回复1点体力(X为箜声牌数)" }; } /** 良姻触发主逻辑:询问发动 → 选目标 → 选摸弃 → 执行 → 选回血。 */ async function performLiangyin(state, ownerId) { if (!state.players[ownerId]?.alive) return; const x = kongshengCount(state, ownerId); delete state.localVars[CONFIRMED_KEY$8]; await applyAtom(state, { type: "请求回应", requestType: CONFIRM_RT$9, target: ownerId, prompt: { type: "confirm", title: `是否发动良姻?(与一名其他角色各摸/弃一张牌,X=${x})`, confirmLabel: "发动", cancelLabel: "不发动" }, defaultChoice: false, timeout: 15 }); if (!state.localVars[CONFIRMED_KEY$8]) { delete state.localVars[CONFIRMED_KEY$8]; return; } delete state.localVars[CONFIRMED_KEY$8]; delete state.localVars[TARGET_KEY$7]; await applyAtom(state, { type: "请求回应", requestType: TARGET_RT$4, target: ownerId, prompt: { type: "choosePlayer", title: "良姻:选择一名其他角色", min: 1, max: 1, filter: (_view, t) => t !== ownerId && state.players[t]?.alive === true }, timeout: 15 }); const target = state.localVars[TARGET_KEY$7]; delete state.localVars[TARGET_KEY$7]; if (typeof target !== "number" || !state.players[target]?.alive) return; delete state.localVars[MODE_KEY]; await applyAtom(state, { type: "请求回应", requestType: MODE_RT, target: ownerId, prompt: { type: "confirm", title: "良姻:各摸一张牌 还是 各弃一张牌?", confirmLabel: "各摸一张", cancelLabel: "各弃一张" }, defaultChoice: true, timeout: 15 }); const mode = state.localVars[MODE_KEY] ?? "摸"; delete state.localVars[MODE_KEY]; if (mode === "摸") { await applyAtom(state, { type: "摸牌", player: ownerId, count: 1 }); if (state.players[target]?.alive) await applyAtom(state, { type: "摸牌", player: target, count: 1 }); } else { await discardOne(state, ownerId); if (state.players[target]?.alive) await discardOne(state, target); } if (!state.players[ownerId]?.alive) return; const candidates = [ownerId, target].filter((p) => { const pl = state.players[p]; if (!pl?.alive) return false; if (pl.hand.length !== x) return false; return getHealthValue(pl) < pl.maxHealth; }); if (candidates.length === 0) return; delete state.localVars[HEAL_KEY]; await applyAtom(state, { type: "请求回应", requestType: HEAL_RT, target: ownerId, prompt: { type: "choosePlayer", title: `良姻:令一名手牌数为${x}的角色回复1点体力(可不选)`, min: 1, max: 1, filter: (_view, t) => candidates.includes(t) }, timeout: 15 }); const healTarget = state.localVars[HEAL_KEY]; delete state.localVars[HEAL_KEY]; if (typeof healTarget === "number" && state.players[healTarget]?.alive) await applyAtom(state, { type: "回复体力", target: healTarget, amount: 1, source: ownerId }); } /** 令 player 弃置一张手牌(强制;超时自动弃首张)。无手牌则跳过。 */ async function discardOne(state, player) { const pl = state.players[player]; if (!pl?.alive || pl.hand.length === 0) return; delete state.localVars[DISCARD_KEY$3]; await applyAtom(state, { type: "请求回应", requestType: DISCARD_RT$4, target: player, prompt: { type: "useCard", title: "良姻:弃置一张牌", cardFilter: { filter: () => true, min: 1, max: 1 } }, mandatory: true, timeout: 20 }); let cards = state.localVars[DISCARD_KEY$3]; delete state.localVars[DISCARD_KEY$3]; if ((!cards || cards.length === 0) && state.players[player]?.hand.length) cards = state.players[player].hand.slice(0, 1); if (cards && cards.length > 0) await applyAtom(state, { type: "弃置", player, cardIds: cards }); } function onInit$49(skill, state) { const ownerId = skill.ownerId; registerAfterHook(state, skill.id, ownerId, "移出至暂存区", async (ctx) => { if (!ctx.state.players[ownerId]?.alive) return; if (ctx.state.turn.vars[triggeredKey(ownerId)]) return; ctx.state.turn.vars[triggeredKey(ownerId)] = true; await performLiangyin(ctx.state, ownerId); }); registerAfterHook(state, skill.id, ownerId, "归还暂存牌", async (ctx) => { if (!ctx.state.players[ownerId]?.alive) return; if (ctx.state.turn.vars[triggeredKey(ownerId)]) return; ctx.state.turn.vars[triggeredKey(ownerId)] = true; await performLiangyin(ctx.state, ownerId); }); for (const p of state.players) { const seatId = p.index; registerAction(state, skill.id, seatId, "respond", (st, params) => { const slot = st.pendingSlots.get(seatId); if (!slot) return "当前不需要回应"; if (slot.atom.type !== "请求回应") return "当前不需要回应"; const rt = slot.atom.requestType; if (rt === CONFIRM_RT$9 || rt === MODE_RT) return null; if (rt === TARGET_RT$4 || rt === HEAL_RT) { if (typeof params.target !== "number" && !params.targets) return "需要选择一名角色"; return null; } if (rt === DISCARD_RT$4) { const cardIds = params.cardIds; if (!Array.isArray(cardIds) || cardIds.length !== 1) return "请选择一张要弃置的手牌"; const tp = st.players[seatId]; if (!tp) return "玩家不存在"; if (!cardIds.every((id) => typeof id === "string" && tp.hand.includes(id))) return "牌不在手牌中"; if (new Set(cardIds).size !== cardIds.length) return "cardIds 含重复牌"; return null; } return "当前不是良姻询问"; }, async (st, params) => { const rt = (st.pendingSlots.get(seatId)?.atom)?.requestType; if (rt === CONFIRM_RT$9) st.localVars[CONFIRMED_KEY$8] = params.choice === true || params.confirmed === true; else if (rt === TARGET_RT$4) { const t = extractTarget(params); if (typeof t === "number") st.localVars[TARGET_KEY$7] = t; } else if (rt === MODE_RT) st.localVars[MODE_KEY] = params.choice === true ? "摸" : "弃"; else if (rt === DISCARD_RT$4) st.localVars[DISCARD_KEY$3] = params.cardIds; else if (rt === HEAL_RT) { const t = extractTarget(params); if (typeof t === "number") st.localVars[HEAL_KEY] = t; } }); } return () => {}; } function onMount$43(_skill, _api) { return () => {}; } var KONGSHENG_KEY, CONFIRM_RT$9, TARGET_RT$4, MODE_RT, DISCARD_RT$4, HEAL_RT, CONFIRMED_KEY$8, TARGET_KEY$7, MODE_KEY, DISCARD_KEY$3, HEAL_KEY, _skillModule$13; var init_良姻 = __esmMin((() => { init_types(); init_apply(); init_skill(); KONGSHENG_KEY = "箜声/牌"; CONFIRM_RT$9 = "良姻/confirm"; TARGET_RT$4 = "良姻/目标"; MODE_RT = "良姻/摸弃"; DISCARD_RT$4 = "良姻/弃牌"; HEAL_RT = "良姻/回血"; CONFIRMED_KEY$8 = "良姻/confirmed"; TARGET_KEY$7 = "良姻/目标"; MODE_KEY = "良姻/摸弃"; DISCARD_KEY$3 = "良姻/弃牌结果"; HEAL_KEY = "良姻/回血目标"; _skillModule$13 = { createSkill: createSkill$49, onInit: onInit$49, onMount: onMount$43 }; })); //#endregion //#region src/engine/skills/节命.ts var 节命_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$48, default: () => 节命_default, onInit: () => onInit$48, onMount: () => onMount$42 }); function createSkill$48(id, ownerId) { return { id, ownerId, name: "节命", description: "受到伤害后,令一名角色将手牌摸至其体力上限(最多5张)" }; } function onInit$48(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "respond", (st, _params) => { const slot = st.pendingSlots.get(ownerId); if (!slot) return "当前不需要回应"; const atom = slot.atom; if (atom["type"] !== "请求回应") return "当前不需要回应"; const rt = atom["requestType"]; if (rt !== CONFIRM_RT$8 && rt !== TARGET_RT$3) return "当前不是节命询问"; return null; }, async (st, params) => { const rt = (st.pendingSlots.get(ownerId)?.atom)?.requestType; if (rt === CONFIRM_RT$8) st.localVars[CONFIRMED_KEY$7] = params.choice === true; else if (rt === TARGET_RT$3) { const t = params.targets?.[0] ?? (typeof params.target === "number" ? params.target : void 0); if (typeof t === "number") st.localVars[TARGET_KEY$6] = t; } }); registerAfterHook(state, skill.id, ownerId, "伤害结算结束后", async (ctx) => { const atom = ctx.atom; if (atom.target !== ownerId) return; const amount = atom.amount ?? 0; if (amount <= 0) return; if (!ctx.state.players[ownerId]?.alive) return; for (let i = 0; i < amount; i++) { if (!ctx.state.players[ownerId]?.alive) break; delete ctx.state.localVars[CONFIRMED_KEY$7]; await applyAtom(ctx.state, { type: "请求回应", requestType: CONFIRM_RT$8, target: ownerId, prompt: { type: "confirm", title: "是否发动节命?(令一名角色将手牌摸至体力上限)", confirmLabel: "发动", cancelLabel: "不发动" }, defaultChoice: false, timeout: 10 }); if (!ctx.state.localVars[CONFIRMED_KEY$7]) continue; delete ctx.state.localVars[TARGET_KEY$6]; await applyAtom(ctx.state, { type: "请求回应", requestType: TARGET_RT$3, target: ownerId, prompt: { type: "choosePlayer", title: "节命:选择一名角色(手牌摸至体力上限)", min: 1, max: 1, filter: (_view, t) => ctx.state.players[t]?.alive === true }, timeout: 15 }); const target = ctx.state.localVars[TARGET_KEY$6]; delete ctx.state.localVars[TARGET_KEY$6]; if (typeof target !== "number") continue; if (!ctx.state.players[target]?.alive) continue; const targetPlayer = ctx.state.players[target]; const drawCount = Math.min(targetPlayer.maxHealth, 5) - targetPlayer.hand.length; if (drawCount > 0) await applyAtom(ctx.state, { type: "摸牌", player: target, count: drawCount }); } }); return () => {}; } function onMount$42(_skill, api) { api.defineAction("respond", { label: "节命", style: "primary", prompt: { type: "confirm", title: "是否发动节命?", confirmLabel: "发动", cancelLabel: "不发动" } }); } var CONFIRM_RT$8, TARGET_RT$3, CONFIRMED_KEY$7, TARGET_KEY$6, 节命_default; var init_节命 = __esmMin((() => { init_apply(); init_skill(); CONFIRM_RT$8 = "节命/confirm"; TARGET_RT$3 = "节命/target"; CONFIRMED_KEY$7 = "节命/confirmed"; TARGET_KEY$6 = "节命/target"; 节命_default = { createSkill: createSkill$48, onInit: onInit$48, onMount: onMount$42 }; })); //#endregion //#region src/engine/skills/若愚.ts var 若愚_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$47, default: () => 若愚_default, onInit: () => onInit$47, onMount: () => onMount$41 }); function createSkill$47(id, ownerId) { return { id, ownerId, name: "若愚", description: "主公技/觉醒技:回合开始且体力全场最少时,增加1体力上限、回复1体力并永久获得\"激将\"" }; } function onInit$47(skill, state) { const ownerId = skill.ownerId; registerAfterHook(state, skill.id, ownerId, "回合开始", async (ctx) => { if (ctx.atom.player !== ownerId) return; if (ctx.state.players[ownerId]?.vars[AWAKENED_KEY$1]) return; const self = ctx.state.players[ownerId]; if (!self?.alive) return; if (ownerId !== 0) return; const alivePlayers = ctx.state.players.filter((p) => p.alive); if (alivePlayers.length === 0) return; const minHealth = Math.min(...alivePlayers.map((p) => getHealthValue(p))); if (getHealthValue(self) > minHealth) return; ctx.state.players[ownerId].vars[AWAKENED_KEY$1] = true; await applyAtom(ctx.state, { type: "设上限", player: ownerId, amount: self.maxHealth + 1 }); await applyAtom(ctx.state, { type: "回复体力", target: ownerId, amount: 1 }); await applyAtom(ctx.state, { type: "添加技能", player: ownerId, skillId: "激将" }); }); return () => {}; } function onMount$41(_skill, _api) {} var AWAKENED_KEY$1, 若愚_default; var init_若愚 = __esmMin((() => { init_types(); init_apply(); init_skill(); AWAKENED_KEY$1 = "若愚/awakened"; 若愚_default = { createSkill: createSkill$47, onInit: onInit$47, onMount: onMount$41 }; })); //#endregion //#region src/engine/skills/苦肉.ts var 苦肉_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$46, default: () => _skillModule$12, onInit: () => onInit$46, onMount: () => onMount$40 }); function createSkill$46(id, ownerId) { return { id, ownerId, name: "苦肉", description: "出牌阶段,你可以失去1点体力,然后摸两张牌。" }; } function onInit$46(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "use", (state, _params) => { const self = state.players[ownerId]; if (!self) return "player not found"; if (!self.alive) return "player is dead"; if (self.health <= 0) return "体力不足,无法发动苦肉"; return null; }, async (state, _params) => { const from = ownerId; await pushFrame(state, "苦肉", from, {}); await applyAtom(state, { type: "失去体力", target: from, amount: 1 }); if (state.players[from].alive) await applyAtom(state, { type: "摸牌", player: from, count: 2 }); await popFrame(state); }); return () => {}; } function onMount$40(skill, api) { api.defineAction("use", { label: "苦肉", style: "primary", prompt: { type: "confirm", title: "苦肉:失去1点体力,然后摸两张牌" }, activeWhen: (ctx) => defaultPlayActive(ctx) && (ctx.view.players[ctx.perspectiveIdx]?.health ?? 0) > 0 }); return () => {}; } var _skillModule$12; var init_苦肉 = __esmMin((() => { init_apply(); init_frame(); init_action_active(); init_skill(); _skillModule$12 = { createSkill: createSkill$46, onInit: onInit$46, onMount: onMount$40 }; })); //#endregion //#region src/engine/skills/英姿.ts var 英姿_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$45, default: () => 英姿_default, onInit: () => onInit$45, onMount: () => onMount$39 }); function createSkill$45(id, ownerId) { return { id, ownerId, name: "英姿", description: "摸牌阶段,你可以额外摸一张牌" }; } function onInit$45(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "respond", (st, _params) => { const slot = st.pendingSlots.get(ownerId); if (!slot) return "当前不需要回应"; const atom = slot.atom; if (atom["type"] !== "请求回应") return "当前不需要回应"; if (atom["requestType"] !== CONFIRM_REQUEST$4) return "当前不是英姿确认"; return null; }, async (st, params) => { st.localVars["英姿/confirmed"] = params.choice === true || params.confirmed === true; }); registerBeforeHook(state, skill.id, ownerId, "摸牌", async (ctx) => { const atom = ctx.atom; if (atom.player !== ownerId) return; if (ctx.state.currentPlayerIndex !== ownerId) return; if (ctx.state.phase !== "摸牌") return; if (!ctx.state.players[ownerId]?.alive) return; if (usedThisTurn$3(ctx.state, ownerId, "英姿")) return; delete ctx.state.localVars["英姿/confirmed"]; await applyAtom(ctx.state, { type: "请求回应", requestType: CONFIRM_REQUEST$4, target: ownerId, prompt: { type: "confirm", title: "是否发动英姿?(额外摸一张牌)", confirmLabel: "发动", cancelLabel: "不发动" }, defaultChoice: false, timeout: 15 }); if (!ctx.state.localVars["英姿/confirmed"]) return; await markOncePerTurn(ctx.state, ownerId, "英姿"); const count = atom.count ?? 2; return { kind: "modify", atom: { ...ctx.atom, count: count + 1 } }; }); return () => {}; } function onMount$39(_skill, api) { api.defineAction("respond", { label: "英姿", style: "primary", prompt: { type: "confirm", title: "是否发动英姿?(额外摸一张牌)", confirmLabel: "发动", cancelLabel: "不发动" } }); } var CONFIRM_REQUEST$4, 英姿_default; var init_英姿 = __esmMin((() => { init_apply(); init_once_per_turn(); init_skill(); CONFIRM_REQUEST$4 = "英姿/confirm"; 英姿_default = { createSkill: createSkill$45, onInit: onInit$45, onMount: onMount$39 }; })); //#endregion //#region src/engine/skills/荐降.ts var 荐降_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$44, default: () => _skillModule$11, onInit: () => onInit$44, onMount: () => onMount$38 }); function createSkill$44(id, ownerId) { return { id, ownerId, name: "荐降", description: "当你成为其他角色使用牌的目标后,你可以令手牌数最少的一名角色摸一张牌" }; } /** 全场存活角色中手牌数最少者(含自己);返回候选座次数组 */ function minHandPlayers(state) { let minCount = Infinity; const candidates = []; for (const p of state.players) { if (!p.alive) continue; if (p.hand.length < minCount) { minCount = p.hand.length; candidates.length = 0; candidates.push(p.index); } else if (p.hand.length === minCount) candidates.push(p.index); } return candidates; } function onInit$44(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "respond", (st, _params) => { const slot = st.pendingSlots.get(ownerId); if (!slot) return "当前不需要回应"; const atom = slot.atom; if (atom["type"] !== "请求回应") return "当前不需要回应"; const rt = atom["requestType"]; if (rt !== CONFIRM_RT$7 && rt !== CHOOSE_RT$2) return "当前不是荐降询问"; return null; }, async (st, params) => { const rt = (st.pendingSlots.get(ownerId)?.atom)?.requestType; if (rt === CONFIRM_RT$7) st.localVars[CONFIRMED_KEY$6] = params.choice === true || params.confirmed === true; else if (rt === CHOOSE_RT$2) { const t = params.targets?.[0] ?? (typeof params.target === "number" ? params.target : void 0); if (typeof t === "number") st.localVars[TARGET_KEY$5] = t; } }); registerAfterHook(state, skill.id, ownerId, "成为目标后", async (ctx) => { const atom = ctx.atom; if (atom.target !== ownerId) return; if (atom.source === ownerId) return; if (!ctx.state.players[ownerId]?.alive) return; delete ctx.state.localVars[CONFIRMED_KEY$6]; await applyAtom(ctx.state, { type: "请求回应", requestType: CONFIRM_RT$7, target: ownerId, prompt: { type: "confirm", title: "荐降:是否令手牌数最少的一名角色摸一张牌?", confirmLabel: "发动", cancelLabel: "不发动" }, defaultChoice: false, timeout: 15 }); if (!ctx.state.localVars[CONFIRMED_KEY$6]) return; delete ctx.state.localVars[CONFIRMED_KEY$6]; const candidates = minHandPlayers(ctx.state); if (candidates.length === 0) return; let target; if (candidates.length === 1) target = candidates[0]; else { delete ctx.state.localVars[TARGET_KEY$5]; await applyAtom(ctx.state, { type: "请求回应", requestType: CHOOSE_RT$2, target: ownerId, prompt: { type: "choosePlayer", title: "荐降:选择手牌数最少的一名角色(摸一张牌)", min: 1, max: 1, candidates, filter: (_view, t) => candidates.includes(t) }, timeout: 15 }); const chosen = ctx.state.localVars[TARGET_KEY$5]; delete ctx.state.localVars[TARGET_KEY$5]; if (typeof chosen !== "number") return; target = chosen; } if (ctx.state.players[target]?.alive) await applyAtom(ctx.state, { type: "摸牌", player: target, count: 1 }); }); return () => {}; } function onMount$38(_skill, api) { api.defineAction("respond", { label: "荐降", style: "primary", prompt: { type: "confirm", title: "荐降", confirmLabel: "确认", cancelLabel: "取消" } }); return () => {}; } var CONFIRM_RT$7, CHOOSE_RT$2, CONFIRMED_KEY$6, TARGET_KEY$5, _skillModule$11; var init_荐降 = __esmMin((() => { init_apply(); init_skill(); CONFIRM_RT$7 = "荐降/confirm"; CHOOSE_RT$2 = "荐降/choose"; CONFIRMED_KEY$6 = "荐降/confirmed"; TARGET_KEY$5 = "荐降/target"; _skillModule$11 = { createSkill: createSkill$44, onInit: onInit$44, onMount: onMount$38 }; })); //#endregion //#region src/engine/skills/藤甲.ts var 藤甲_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$43, onInit: () => onInit$43 }); function createSkill$43(id, ownerId) { return { id, ownerId, name: "藤甲", description: "锁定技,南蛮入侵/万箭齐发/普通杀对你无效;火焰伤害+1", isLocked: true }; } function onInit$43(skill, state) { const ownerId = skill.ownerId; const armorIsTengjia = (st) => { const armorId = st.players[ownerId]?.equipment?.["防具"]; return !!armorId && st.cardMap[armorId]?.name === "藤甲"; }; registerBeforeHook(state, skill.id, ownerId, "检测有效性", async (ctx) => { const atom = ctx.atom; if (atom.target !== ownerId) return; if (!armorIsTengjia(ctx.state)) return; const cardId = atom.cardId; if (!cardId) return; const card = ctx.state.cardMap[cardId]; if (!card) return; const isNormalSlash = card.name === "杀" && card.damageType !== "火焰" && card.damageType !== "雷电"; const isAOE = card.name === "南蛮入侵" || card.name === "万箭齐发"; if (isNormalSlash || isAOE) return { kind: "cancel" }; }); registerBeforeHook(state, skill.id, ownerId, "受到伤害时", async (ctx) => { const atom = ctx.atom; if (atom.target !== ownerId) return; if (!armorIsTengjia(ctx.state)) return; if (atom.damageType !== "火焰") return; const baseAmount = atom.amount ?? 1; return { kind: "modify", atom: { ...ctx.atom, amount: baseAmount + 1 } }; }); return () => {}; } var init_藤甲 = __esmMin((() => { init_skill(); })); //#endregion //#region src/engine/skills/蛊惑.ts var 蛊惑_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$42, default: () => 蛊惑_default, onInit: () => onInit$42, onMount: () => onMount$37 }); function createSkill$42(id, ownerId) { return { id, ownerId, name: "蛊惑", description: "每回合限一次,扣置一张手牌声明为基本牌(杀/闪/桃/酒)使用或打出;其他角色可质疑" }; } /** 蛊惑-杀 生效后的杀结算:成为目标→检测有效性→询问闪→(被抵消/伤害)。 * 扣牌留弃牌堆(不进处理区,保隐藏),cardId 仅作 hook 来源标识。 * pushFrame('杀') + 检测有效性期间临时改 cardMap[name]='杀',使武器技/仁王盾生效。 */ async function resolveGuSlash(state, source, target, cardId) { const card = state.cardMap[cardId]; const damageType = card?.damageType; await pushFrame(state, "杀", source, { cardId, declaredName: "杀", target }); const origName = card?.name; if (card && origName !== "杀") card.name = "杀"; try { if (!await applyAtom(state, { type: "成为目标", source, target, cardId })) return; if (!await applyAtom(state, { type: "检测有效性", source, target, cardId })) return; await applyAtom(state, { type: "询问闪", target, source }); const slashFrame = state.settlementStack[state.settlementStack.length - 1]; const dodgeIds = frameCards(state).filter((id) => state.cardMap[id]?.name === "闪"); if (slashFrame?.cancelled || dodgeIds.length > 0) { await applyAtom(state, { type: "被抵消", source, target, cardId }); const remaining = frameCards(state).filter((id) => state.cardMap[id]?.name === "闪"); for (const dId of remaining) await applyAtom(state, { type: "移动牌", cardId: dId, from: { zone: "处理区" }, to: { zone: "弃牌堆" } }); } else if (state.players[target]?.alive) await runDamageFlow(state, source, target, 1, cardId, damageType); } finally { for (const id of frameCards(state)) if (state.cardMap[id]?.name === "闪") await applyAtom(state, { type: "移动牌", cardId: id, from: { zone: "处理区" }, to: { zone: "弃牌堆" } }); await popFrame(state); if (card && origName !== void 0 && origName !== "杀") card.name = origName; } incSlashUsed(state); await applyAtom(state, { type: "回合用量", player: source, key: SLASH_USED_COUNT_KEY, value: slashUsed(state) }); } /** 将扣牌(在弃牌堆)经 弃牌堆→手牌→当作(影子 outputName)→手牌→处理区 提供到当前结算帧。 * 供 dodge(闪)在质疑通过后向处理区提供一张"闪"(供杀结算检测处理区有闪 → 抵消)。 * 影子复用 武圣/奇袭 的转化模型:入弃牌堆时按 shadowOf 还原为扣牌。 */ async function provideAsBasicToProcessing(state, yuji, concealedId, outputName) { await applyAtom(state, { type: "移动牌", cardId: concealedId, from: { zone: "弃牌堆" }, to: { zone: "手牌", player: yuji } }); const shadowId = `${concealedId}#蛊惑${outputName}`; await applyAtom(state, { type: "当作", player: yuji, cardIds: [concealedId], shadowId, outputName }); await applyAtom(state, { type: "移动牌", cardId: shadowId, from: { zone: "手牌", player: yuji }, to: { zone: "处理区" } }); } /** 按声明的牌生效(扣牌已在弃牌堆=已使用)。 */ async function applyDeclaredEffect(state, source, declaredName, target, cardId) { if (declaredName === "杀") { if (typeof target !== "number" || !state.players[target]?.alive) return; await resolveGuSlash(state, source, target, cardId); } else if (declaredName === "桃") { const tgt = typeof target === "number" ? target : source; if (!state.players[tgt]?.alive) return; await applyAtom(state, { type: "回复体力", target: tgt, amount: 1, source }); } else if (declaredName === "酒") await applyAtom(state, { type: "加标记", player: source, mark: { id: "酒/nextKillDamageBonus", scope: -1, payload: 1, duration: "turn" } }); } /** 蛊惑质疑流程(各入口共用):扣牌 → 质疑循环 → (有人质疑)翻牌结算。 * 返回 { voided: 是否作废(假牌被质疑), downCard: 扣牌 id, questioner?: 质疑者 }。 */ async function runQuestioning(state, yuji, cardId, declaredName) { await applyAtom(state, { type: "扣牌", player: yuji, cardId, declaredName }); const n = state.players.length; let questioner = void 0; for (let step = 1; step < n; step++) { const p = (yuji + step) % n; if (p === yuji) continue; if (!state.players[p]?.alive) continue; delete state.localVars[QUESTIONER_VAR]; await applyAtom(state, { type: "请求回应", requestType: REQUEST_TYPE, target: p, prompt: { type: "confirm", title: `是否质疑于吉声明为【${declaredName}】的蛊惑?` }, timeout: 20 }); const q = state.localVars[QUESTIONER_VAR]; if (q !== void 0) { questioner = q; break; } } const downCard = state.localVars[DOWNCARD_VAR]; if (questioner === void 0) return { voided: false, downCard }; await applyAtom(state, { type: "展示", player: yuji, cardId: downCard }); if (state.cardMap[downCard]?.name === declaredName) { await applyAtom(state, { type: "失去体力", target: questioner, amount: 1 }); return { voided: false, downCard, questioner }; } await applyAtom(state, { type: "移动牌", cardId: downCard, from: { zone: "弃牌堆" }, to: { zone: "手牌", player: questioner } }); return { voided: true, downCard, questioner }; } /** 清理本次蛊惑临时 localVars。 */ function cleanupVars(state) { delete state.localVars[DOWNCARD_VAR]; delete state.localVars[DECLARE_VAR]; delete state.localVars[QUESTIONER_VAR]; } function onInit$42(skill, state) { const ownerId = skill.ownerId; const unloaders = []; unloaders.push(declareAlternativeResponse(state, ownerId, "询问闪")); unloaders.push(declareAlternativeResponse(state, ownerId, "请求回应", "桃/求桃")); const registerDeclaredUse = (declaredName) => registerAction(state, skill.id, ownerId, `${USE_ACTION_PREFIX}${declaredName}`, (st, params) => { if (st.currentPlayerIndex !== ownerId) return "不是你的回合"; if (st.phase !== "出牌") return "只能在出牌阶段发动"; if (hasBlockingPending(st)) return "当前有未完成的询问"; if (usedThisTurn$3(st, ownerId, "蛊惑")) return "本回合已使用过蛊惑"; const self = st.players[ownerId]; if (!self?.alive) return "玩家不存在或已死亡"; const cardId = params.cardId; if (typeof cardId !== "string" || !self.hand.includes(cardId)) return "请选择一张手牌"; if (declaredName === "杀") { const target = params.target; if (typeof target !== "number" || !st.players[target]?.alive) return "请选择合法目标"; if (!inAttackRange(st, ownerId, target)) return "目标不在攻击范围内"; if (!canSlash(st, ownerId, cardId)) return "出杀次数已达上限"; } else if (declaredName === "桃") { const target = params.target ?? ownerId; const tp = st.players[target]; if (!tp?.alive) return "目标不存在"; if (tp.health >= tp.maxHealth) return "桃只能对受伤角色使用"; } return null; }, async (st, params) => { const from = ownerId; const cardId = params.cardId; const target = params.target; await markOncePerTurn(st, from, "蛊惑"); await pushFrame(st, "蛊惑", from, { cardId, declaredName }); try { const result = await runQuestioning(st, from, cardId, declaredName); if (!result.voided) await applyDeclaredEffect(st, from, declaredName, target, result.downCard); } finally { cleanupVars(st); await popFrame(st); } }); for (const declaredName of ACTIVE_DECLARATIONS) unloaders.push(registerDeclaredUse(declaredName)); unloaders.push(registerAction(state, skill.id, ownerId, "dodge", (st, params) => { const slot = st.pendingSlots.get(ownerId); if (!slot) return "当前不需要回应"; if (slot.atom.type !== "询问闪") return "当前不是出闪的窗口"; if (slot.atom.target !== ownerId) return "不是问你的"; if (!st.players[ownerId]?.alive) return "你已死亡"; if (usedThisTurn$3(st, ownerId, "蛊惑")) return "本回合已使用过蛊惑"; const cardId = params.cardId; if (typeof cardId !== "string" || !st.players[ownerId].hand.includes(cardId)) return "请选择一张手牌"; return null; }, async (st, params) => { const from = ownerId; const cardId = params.cardId; await markOncePerTurn(st, from, "蛊惑"); await pushFrame(st, "蛊惑", from, { cardId, declaredName: "闪" }); let notVoided; let downCard; try { const result = await runQuestioning(st, from, cardId, "闪"); notVoided = !result.voided; downCard = result.downCard; } finally { cleanupVars(st); await popFrame(st); } if (notVoided) await provideAsBasicToProcessing(st, from, downCard, "闪"); })); unloaders.push(registerAction(state, skill.id, ownerId, "rescue", (st, params) => { const slot = st.pendingSlots.get(ownerId); if (!slot) return "当前不需要回应"; const atom = slot.atom; if (atom.type !== "请求回应") return "当前不是求桃窗口"; if (atom.requestType !== "桃/求桃") return "当前不是求桃窗口"; if (atom.target !== ownerId) return "不是问你的"; if (!st.players[ownerId]?.alive) return "你已死亡"; if (usedThisTurn$3(st, ownerId, "蛊惑")) return "本回合已使用过蛊惑"; const cardId = params.cardId; if (typeof cardId !== "string" || !st.players[ownerId].hand.includes(cardId)) return "请选择一张手牌"; return null; }, async (st, params) => { const from = ownerId; const cardId = params.cardId; await markOncePerTurn(st, from, "蛊惑"); await pushFrame(st, "蛊惑", from, { cardId, declaredName: "桃" }); let notVoided; try { notVoided = !(await runQuestioning(st, from, cardId, "桃")).voided; } finally { cleanupVars(st); await popFrame(st); } if (notVoided) st.localVars[RESCUED_VAR] = true; })); for (const pl of state.players) { const seat = pl.index; unloaders.push(registerAction(state, skill.id, seat, "respond", (st, _params) => { const slot = st.pendingSlots.get(seat); if (!slot) return "当前不需要回应"; if (slot.atom.requestType !== REQUEST_TYPE) return "当前不是蛊惑质疑窗口"; if (!st.players[seat]?.alive) return "你已死亡"; return null; }, async (st, _params) => { st.localVars[QUESTIONER_VAR] = seat; })); } return () => { for (const u of unloaders) u(); }; } function onMount$37(_skill, api) { api.defineAction(`${USE_ACTION_PREFIX}杀`, { label: "蛊惑·杀", style: "danger", prompt: { type: "useCardAndTarget", title: "蛊惑:扣置一张手牌,声明为【杀】使用", cardFilter: { filter: () => true, min: 1, max: 1 }, targetFilter: { min: 1, max: 1, filter: (view, t) => viewCanAttack(view.players, view.cardMap, api.viewer, t) } }, activeWhen: (ctx) => activeUseActive(ctx) && viewCanSlash(ctx.view, ctx.perspectiveIdx) }); api.defineAction(`${USE_ACTION_PREFIX}桃`, { label: "蛊惑·桃", style: "primary", prompt: { type: "useCardAndTarget", title: "蛊惑:扣置一张手牌,声明为【桃】使用", cardFilter: { filter: () => true, min: 1, max: 1 }, targetFilter: { min: 0, max: 1, filter: (view, t) => { const p = view.players[t]; return p?.alive === true && p.health < p.maxHealth; } }, selfTarget: true }, activeWhen: (ctx) => { if (!activeUseActive(ctx)) return false; const p = ctx.view.players[ctx.perspectiveIdx]; return p?.alive === true && p.health < p.maxHealth; } }); api.defineAction(`${USE_ACTION_PREFIX}酒`, { label: "蛊惑·酒", style: "primary", prompt: { type: "useCard", title: "蛊惑:扣置一张手牌,声明为【酒】使用", cardFilter: { filter: () => true, min: 1, max: 1 } }, activeWhen: (ctx) => activeUseActive(ctx) }); api.defineAction("dodge", { label: "蛊惑·闪", style: "primary", prompt: { type: "useCard", title: "蛊惑:扣置一张手牌,声明为闪打出", cardFilter: { min: 1, max: 1 } }, activeWhen: (ctx) => dodgeActive(ctx) }); api.defineAction("rescue", { label: "蛊惑·桃", style: "primary", prompt: { type: "useCard", title: "蛊惑:扣置一张手牌,声明为桃救援", cardFilter: { min: 1, max: 1 } }, activeWhen: (ctx) => rescueActive(ctx) }); api.defineAction("respond", { label: "质疑", style: "danger", prompt: { type: "confirm", title: "是否质疑于吉的蛊惑?" } }); return () => {}; } /** use 激活:自己回合出牌阶段 + 无阻塞 pending + 本回合未用过 + 有手牌。 */ function activeUseActive(ctx) { const { view, perspectiveIdx } = ctx; const p = view.players[perspectiveIdx]; if (!p) return false; if (view.currentPlayerIndex !== perspectiveIdx) return false; if (view.phase !== "出牌") return false; const pending = view.pending; if (pending != null && pending.isBlocking !== false) return false; if (p.turnUsage?.[usedThisTurnKey("蛊惑")]) return false; return (p.hand?.length ?? 0) > 0; } /** dodge 激活:询问闪 pending 命中自己 + 本回合未用过 + 有手牌。 */ function dodgeActive(ctx) { const { view, perspectiveIdx } = ctx; const p = view.players[perspectiveIdx]; if (!p) return false; if (p.turnUsage?.[usedThisTurnKey("蛊惑")]) return false; if ((p.hand?.length ?? 0) === 0) return false; const pending = view.pending; return pending?.target === perspectiveIdx && pending.atom?.type === "询问闪"; } /** rescue 激活:桃/求桃 pending 命中自己 + 本回合未用过 + 有手牌。 */ function rescueActive(ctx) { const { view, perspectiveIdx } = ctx; const p = view.players[perspectiveIdx]; if (!p) return false; if (p.turnUsage?.[usedThisTurnKey("蛊惑")]) return false; if ((p.hand?.length ?? 0) === 0) return false; const pending = view.pending; const atom = pending?.atom; return pending?.target === perspectiveIdx && atom?.type === "请求回应" && atom?.requestType === "桃/求桃"; } var REQUEST_TYPE, QUESTIONER_VAR, DOWNCARD_VAR, DECLARE_VAR, RESCUED_VAR, ACTIVE_DECLARATIONS, USE_ACTION_PREFIX, 蛊惑_default; var init_蛊惑 = __esmMin((() => { init_apply(); init_frame(); init_damage(); init_skill(); init_distance(); init_slash_quota(); init_once_per_turn(); init_vars_keys(); init_viewDistance(); init_action_active(); REQUEST_TYPE = "蛊惑/质疑"; QUESTIONER_VAR = "蛊惑/质疑者"; DOWNCARD_VAR = "蛊惑/扣牌"; DECLARE_VAR = "蛊惑/声明"; RESCUED_VAR = "求桃/已救"; ACTIVE_DECLARATIONS = [ "杀", "桃", "酒" ]; USE_ACTION_PREFIX = "use:"; 蛊惑_default = { createSkill: createSkill$42, onInit: onInit$42, onMount: onMount$37 }; })); //#endregion //#region src/engine/skills/行殇.ts var 行殇_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$41, default: () => 行殇_default, onInit: () => onInit$41, onMount: () => onMount$36 }); function createSkill$41(id, ownerId) { return { id, ownerId, name: "行殇", description: "当其他角色死亡时,你可以获得其所有牌(手牌/装备/判定区)" }; } function onInit$41(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "respond", (st, _params) => { const slot = st.pendingSlots.get(ownerId); if (!slot) return "当前不需要回应"; const atom = slot.atom; if (atom["type"] !== "请求回应") return "当前不需要回应"; if (atom["requestType"] !== CONFIRM_RT$6) return "当前不是行殇确认"; return null; }, async (st, params) => { st.localVars[CONFIRMED_KEY$5] = params.choice === true || params.confirmed === true; }); registerAfterHook(state, skill.id, ownerId, "死亡时", async (ctx) => { const atom = ctx.atom; if (atom.type !== "死亡时") return; const deadIdx = atom.player; if (deadIdx === void 0) return; if (deadIdx === ownerId) return; if (!ctx.state.players[ownerId]?.alive) return; const dead = ctx.state.players[deadIdx]; if (!dead) return; const handCards = [...dead.hand]; const equipCards = Object.values(dead.equipment).filter((id) => typeof id === "string"); const judgeCards = dead.pendingTricks.map((t) => ({ trickName: t.name, cardId: t.card.id })); const total = handCards.length + equipCards.length + judgeCards.length; if (total === 0) return; delete ctx.state.localVars[CONFIRMED_KEY$5]; await applyAtom(ctx.state, { type: "请求回应", requestType: CONFIRM_RT$6, target: ownerId, prompt: { type: "confirm", title: `是否发动行殇?(从 ${ctx.state.players[deadIdx]?.name ?? `P${deadIdx}`} 获得 ${total} 张牌)`, confirmLabel: "发动", cancelLabel: "不发动" }, defaultChoice: false, timeout: 10 }); if (!ctx.state.localVars[CONFIRMED_KEY$5]) return; for (const cardId of handCards) await applyAtom(ctx.state, { type: "获得", player: ownerId, cardId, from: deadIdx }); for (const cardId of equipCards) await applyAtom(ctx.state, { type: "获得", player: ownerId, cardId, from: deadIdx }); for (const { trickName, cardId } of judgeCards) { await applyAtom(ctx.state, { type: "移除延时锦囊", player: deadIdx, trickName }); await obtainJudgeZoneCard(ctx.state, deadIdx, cardId, ownerId); } }); return () => {}; } function onMount$36(_skill, api) { api.defineAction("respond", { label: "行殇", style: "primary", prompt: { type: "confirm", title: "是否发动行殇?", confirmLabel: "发动", cancelLabel: "不发动" } }); } var CONFIRM_RT$6, CONFIRMED_KEY$5, 行殇_default; var init_行殇 = __esmMin((() => { init_apply(); init_judge_zone(); init_skill(); CONFIRM_RT$6 = "行殇/confirm"; CONFIRMED_KEY$5 = "行殇/confirmed"; 行殇_default = { createSkill: createSkill$41, onInit: onInit$41, onMount: onMount$36 }; })); //#endregion //#region src/engine/skills/装备通用.ts var 装备通用_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$40, onInit: () => onInit$40, onMount: () => onMount$35 }); function createSkill$40(id, ownerId) { return { id, ownerId, name: "装备", description: "装备到对应栏位" }; } function onInit$40(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "use", (state, params) => { const myTurn = state.currentPlayerIndex === ownerId; const inActPhase = state.phase === "出牌"; const free = !hasBlockingPending(state); const self = state.players[ownerId]; const selfAlive = self.alive === true; if (typeof params.cardId !== "string") return "cardId required"; const cardInHand = !!self.hand.includes(params.cardId); const hasSubtype = !!state.cardMap[params.cardId]?.subtype; return myTurn && inActPhase && free && selfAlive && cardInHand && hasSubtype ? null : "装备使用条件不满足"; }, async (state, params) => { const from = ownerId; await pushFrame(state, "装备通用", from, { ...params }); const cardId = params.cardId; const card = state.cardMap[cardId]; if (card.subtype) { const slot = card.subtype; const currentEquip = state.players[from].equipment[slot]; if (currentEquip) { const oldCard = state.cardMap[currentEquip]; if (oldCard?.name && hasSkillModule(oldCard.name)) await applyAtom(state, { type: "移除技能", player: from, skillId: oldCard.name }); await applyAtom(state, { type: "卸下", player: from, slot }); await applyAtom(state, { type: "移动牌", cardId: currentEquip, from: { zone: "手牌", player: from }, to: { zone: "弃牌堆" } }); } } await applyAtom(state, { type: "装备", player: from, cardId }); if (card?.name && hasSkillModule(card.name)) await applyAtom(state, { type: "添加技能", player: from, skillId: card.name }); await popFrame(state); }); return () => {}; } function onMount$35(skill, api) { api.defineAction("use", { label: "装备", style: "default", prompt: { type: "useCard", title: "装备", cardFilter: { filter: (c) => c.type === "装备牌", min: 1, max: 1 } } }); } var init_装备通用 = __esmMin((() => { init_apply(); init_frame(); init_skill(); init_registry$1(); })); //#endregion //#region src/engine/skills/裸衣.ts var 裸衣_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$39, default: () => 裸衣_default, onInit: () => onInit$39, onMount: () => onMount$34 }); function createSkill$39(id, ownerId) { return { id, ownerId, name: "裸衣", description: "摸牌阶段少摸一张牌,本回合使用【杀】或【决斗】造成的伤害+1" }; } function onInit$39(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "respond", (st, _params) => { const slot = st.pendingSlots.get(ownerId); if (!slot) return "当前不需要回应"; const atom = slot.atom; if (atom["type"] !== "请求回应") return "当前不需要回应"; if (atom["requestType"] !== CONFIRM_REQUEST$3) return "当前不是裸衣确认"; return null; }, async (st, params) => { st.localVars["裸衣/confirmed"] = params.choice === true || params.confirmed === true; }); registerBeforeHook(state, skill.id, ownerId, "摸牌", async (ctx) => { const atom = ctx.atom; if (atom.player !== ownerId) return; if (ctx.state.currentPlayerIndex !== ownerId) return; if (ctx.state.phase !== "摸牌") return; if (!ctx.state.players[ownerId]?.alive) return; if (usedThisTurn$3(ctx.state, ownerId, "裸衣")) return; delete ctx.state.localVars["裸衣/confirmed"]; await applyAtom(ctx.state, { type: "请求回应", requestType: CONFIRM_REQUEST$3, target: ownerId, prompt: { type: "confirm", title: "是否发动裸衣?(少摸一张牌,杀/决斗伤害 +1)", confirmLabel: "发动", cancelLabel: "不发动" }, defaultChoice: false, timeout: 15 }); if (!ctx.state.localVars["裸衣/confirmed"]) return; await markOncePerTurn(ctx.state, ownerId, "裸衣"); await applyAtom(ctx.state, { type: "加标签", player: ownerId, tag: BONUS_TAG }); const count = atom.count ?? 2; return { kind: "modify", atom: { ...ctx.atom, count: Math.max(0, count - 1) } }; }); registerBeforeHook(state, skill.id, ownerId, "造成伤害时", async (ctx) => { const atom = ctx.atom; if (atom.source !== ownerId) return; if ((atom.amount ?? 0) <= 0) return; if (!ctx.state.players[ownerId]?.tags.includes(BONUS_TAG)) return; const cardId = atom.cardId; if (typeof cardId !== "string") return; const card = ctx.state.cardMap[cardId]; if (!card) return; if (card.name !== "杀" && card.name !== "决斗") return; return { kind: "modify", atom: { ...ctx.atom, amount: (atom.amount ?? 0) + 1 } }; }); registerAfterHook(state, skill.id, ownerId, "回合结束", async (ctx) => { if (ctx.atom.player !== ownerId) return; if (ctx.state.players[ownerId]?.tags.includes(BONUS_TAG)) await applyAtom(ctx.state, { type: "去标签", player: ownerId, tag: BONUS_TAG }); }); return () => {}; } function onMount$34(_skill, api) { api.defineAction("respond", { label: "裸衣", style: "primary", prompt: { type: "confirm", title: "是否发动裸衣?(少摸一张牌,杀/决斗伤害 +1)", confirmLabel: "发动", cancelLabel: "不发动" } }); } var BONUS_TAG, CONFIRM_REQUEST$3, 裸衣_default; var init_裸衣 = __esmMin((() => { init_apply(); init_once_per_turn(); init_skill(); BONUS_TAG = "裸衣/bonus"; CONFIRM_REQUEST$3 = "裸衣/confirm"; 裸衣_default = { createSkill: createSkill$39, onInit: onInit$39, onMount: onMount$34 }; })); //#endregion //#region src/engine/skills/观星.ts var 观星_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$38, default: () => 观星_default, onInit: () => onInit$38, onMount: () => onMount$33 }); function createSkill$38(id, ownerId) { return { id, ownerId, name: "观星", description: "准备阶段开始时,观看牌堆顶X张牌(X为全场角色数至多5),调整顺序,余者置牌堆底" }; } function onInit$38(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "respond", (state, params) => { const slot = state.pendingSlots.get(ownerId); if (slot?.atom.type !== "请求回应") return "当前不需要回应"; const requestType = slot.atom.requestType; if (requestType !== "观星/confirm" && requestType !== "观星/arrange") return "当前不是观星询问"; if (requestType === "观星/arrange") { if (!Array.isArray(params.top) || !Array.isArray(params.bottom)) return "观星:需要 top/bottom 划分"; } return null; }, async (state, params) => { const requestType = (state.pendingSlots.get(ownerId)?.atom).requestType; if (requestType === "观星/confirm") state.localVars["观星/confirmed"] = params.choice === true || params.confirmed === true; else if (requestType === "观星/arrange") state.localVars["观星/arrangement"] = { top: params.top ?? [], bottom: params.bottom ?? [] }; }); registerAfterHook(state, skill.id, ownerId, "阶段开始", async (ctx) => { const atom = ctx.atom; if (atom.type !== "阶段开始") return; if (atom.player !== ownerId) return; if (atom.phase !== "准备") return; if (!ctx.state.players[ownerId]?.alive) return; const deck = ctx.state.zones.deck; if (deck.length === 0) return; const x = Math.min(ctx.state.players.length, 5); const drawCount = Math.min(x, deck.length); if (drawCount === 0) return; delete ctx.state.localVars["观星/confirmed"]; await applyAtom(ctx.state, { type: "请求回应", requestType: "观星/confirm", target: ownerId, prompt: { type: "confirm", title: "是否发动观星?", confirmLabel: "发动", cancelLabel: "不发动" }, defaultChoice: false, timeout: 15 }); if (!ctx.state.localVars["观星/confirmed"]) return; const observed = deck.slice(-drawCount); delete ctx.state.localVars["观星/arrangement"]; await applyAtom(ctx.state, { type: "请求回应", requestType: "观星/arrange", target: ownerId, prompt: { type: "distribute", title: "观星:排列牌堆顶牌", description: "选择留在牌堆顶的牌(顺序即摸牌顺序)与置于牌堆底的牌", mode: "select", cardIds: observed, minTotal: 0, maxTotal: drawCount }, defaultChoice: false, timeout: 30 }); const arrangement = ctx.state.localVars["观星/arrangement"]; delete ctx.state.localVars["观星/arrangement"]; delete ctx.state.localVars["观星/confirmed"]; const top = arrangement?.top ?? []; const bottom = arrangement?.bottom ?? []; const observedSet = new Set(observed); const combined = [...top, ...bottom]; if (!(combined.length === observed.length && new Set(combined).size === combined.length && combined.every((id) => observedSet.has(id)))) return; const middle = deck.slice(0, deck.length - drawCount); const newDeck = [ ...bottom, ...middle, ...[...top].reverse() ]; await applyAtom(ctx.state, { type: "整理牌堆", cards: newDeck, topCount: top.length, bottomCount: bottom.length }); }); return () => {}; } function onMount$33(_skill, api) { api.defineAction("respond", { label: "观星", style: "default", prompt: { type: "confirm", title: "是否发动观星?", confirmLabel: "发动", cancelLabel: "不发动" } }); } var 观星_default; var init_观星 = __esmMin((() => { init_apply(); init_skill(); 观星_default = { createSkill: createSkill$38, onInit: onInit$38, onMount: onMount$33 }; })); //#endregion //#region src/engine/skills/解围.ts var 解围_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$37, default: () => 解围_default, onInit: () => onInit$37, onMount: () => onMount$32 }); /** 影子卡 id:${原id}#解围 */ function shadowIdOf$3(cardId) { return `${cardId}#${SHADOW_SUFFIX}`; } /** 当前是否存在无懈可击广播窗口(玩家可回应) */ function hasNullifyWindow(state, playerId) { for (const slot of state.pendingSlots.values()) { const atom = slot.atom; if (atom.type !== "请求回应") continue; if (atom.requestType !== "无懈可击") continue; if (typeof atom.target === "number" && (atom.target < 0 || atom.target === playerId)) return true; } return false; } /** 取玩家"场上"的牌(手牌+装备+判定区)——用于源牌选择 */ function fieldCardIds(state, player) { const p = state.players[player]; if (!p) return []; const hand = [...p.hand]; const equip = Object.values(p.equipment).filter((id) => !!id); const judge = p.pendingTricks.map((t) => t.card.id); return [ ...hand, ...equip, ...judge ]; } /** * 把 srcPlayer 场上的 srcCardId 移动到 destPlayer 场上(与巧变 moveFieldCard 一致)。 * 判定区牌:先移除延时锦囊,再获得。 * 装备/手牌:统一获得(获得 atom 自动处理 hand/equip 的 filter)。 */ async function moveFieldCard(state, srcPlayer, srcCardId, destPlayer) { if (srcPlayer === destPlayer) return; const srcP = state.players[srcPlayer]; if (!srcP) return; if (!state.cardMap[srcCardId]) return; const judgeTrick = srcP.pendingTricks.find((t) => t.card.id === srcCardId); if (judgeTrick) { await applyAtom(state, { type: "移除延时锦囊", player: srcPlayer, trickName: judgeTrick.name }); await applyAtom(state, { type: "获得", player: destPlayer, cardId: srcCardId, from: srcPlayer }); return; } await applyAtom(state, { type: "获得", player: destPlayer, cardId: srcCardId, from: srcPlayer }); } function createSkill$37(id, ownerId) { return { id, ownerId, name: "解围", description: "装备区的一张牌当无懈可击使用;武将牌从背面翻至正面时可弃置一张牌移动场上的一张牌" }; } function onInit$37(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "transform", (state, params) => { const self = state.players[ownerId]; if (!self?.alive) return "玩家不存在或已死亡"; const cardId = params.cardId; const cardIdOk = typeof cardId === "string"; const card = cardIdOk ? state.cardMap[cardId] : void 0; const isEquip = !!card && card.type === "装备牌"; const cardInEquip = cardIdOk && Object.values(self.equipment).some((id) => id === cardId); const hasWindow = hasNullifyWindow(state, ownerId); const blockedByOther = (() => { for (const slot of state.pendingSlots.values()) { const atom = slot.atom; if (!slot.isBlocking) continue; if (atom.type === "请求回应" && atom.requestType === "无懈可击") continue; return true; } return false; })(); return isEquip && cardInEquip && hasWindow && !blockedByOther ? null : "现在不能使用解围"; }, async (state, params) => { const cardId = params.cardId; const shadowId = shadowIdOf$3(cardId); const self = state.players[ownerId]; const equipSlotEntry = Object.entries(self.equipment).find(([, id]) => id === cardId); if (equipSlotEntry) { const slot = equipSlotEntry[0]; params["_origSlot"] = slot; const card = state.cardMap[cardId]; if (card?.name && hasSkillModule(card.name)) await applyAtom(state, { type: "移除技能", player: ownerId, skillId: card.name }); await applyAtom(state, { type: "卸下", player: ownerId, slot }); } await applyAtom(state, { type: "当作", player: ownerId, cardIds: [cardId], shadowId, outputName: "无懈可击" }); }, async (state, params) => { const cardId = params.cardId; const sId = shadowIdOf$3(cardId); const self = state.players[ownerId]; const origSlot = params["_origSlot"]; delete state.cardMap[sId]; const idx = self.hand.indexOf(sId); if (idx >= 0) if (origSlot) { self.hand.splice(idx, 1); self.equipment[origSlot] = cardId; if (origSlot === "武器") { const card = state.cardMap[cardId]; self.vars[DISTANCE_ATTACK_RANGE_KEY] = card?.range ?? 1; } const card = state.cardMap[cardId]; if (card?.name && hasSkillModule(card.name)) { if (!self.skills.includes(card.name)) self.skills.push(card.name); await instantiateSkill(state, card.name, ownerId); } } else self.hand[idx] = cardId; }); registerAction(state, skill.id, ownerId, "respond", (st, _params) => { const slot = st.pendingSlots.get(ownerId); if (!slot) return "当前不需要回应"; const atom = slot.atom; if (atom["type"] !== "请求回应") return "当前不需要回应"; const rt = atom["requestType"]; if (![ CONFIRM_RT$5, DISCARD_RT$3, SOURCE_PLAYER_RT, SOURCE_CARD_RT, DEST_PLAYER_RT ].includes(rt)) return "当前不是解围询问"; return null; }, async (st, params) => { const rt = (st.pendingSlots.get(ownerId)?.atom)?.requestType; if (rt === CONFIRM_RT$5) { st.localVars[CONFIRMED_KEY$4] = params.choice === true; return; } if (rt === DISCARD_RT$3) { const cardIds = params.cardIds; const single = params.cardId; const id = (Array.isArray(cardIds) && cardIds.length > 0 ? cardIds[0] : void 0) ?? (typeof single === "string" ? single : void 0); if (id) st.localVars[DISCARD_KEY$2] = id; return; } if (rt === SOURCE_PLAYER_RT) { const t = params.targets?.[0] ?? (typeof params.target === "number" ? params.target : void 0); if (typeof t === "number") st.localVars[SOURCE_PLAYER_KEY] = t; return; } if (rt === SOURCE_CARD_RT) { const zone = params.zone; const cardId = params.cardId; const handIndex = params.handIndex; st.localVars[SOURCE_CARD_KEY] = JSON.stringify({ zone, cardId, handIndex }); return; } if (rt === DEST_PLAYER_RT) { const t = params.targets?.[0] ?? (typeof params.target === "number" ? params.target : void 0); if (typeof t === "number") st.localVars[DEST_PLAYER_KEY] = t; return; } }); registerAfterHook(state, skill.id, ownerId, "翻面后", async (ctx) => { const atom = ctx.atom; if (atom.type !== "翻面后") return; if (atom.player !== ownerId) return; if (atom.faceDown !== false) return; if (ctx.state.localVars[MOVING_FLAG]) return; const self = ctx.state.players[ownerId]; if (!self?.alive) return; if (self.hand.length === 0) return; if (!ctx.state.players.some((p) => p.alive && fieldCardIds(ctx.state, p.index).length > 0)) return; ctx.state.localVars[MOVING_FLAG] = true; try { delete ctx.state.localVars[CONFIRMED_KEY$4]; await applyAtom(ctx.state, { type: "请求回应", requestType: CONFIRM_RT$5, target: ownerId, prompt: { type: "confirm", title: "解围:是否弃置一张牌移动场上的一张牌?", confirmLabel: "发动", cancelLabel: "不发动" }, defaultChoice: false, timeout: 15 }); if (!ctx.state.localVars[CONFIRMED_KEY$4]) return; delete ctx.state.localVars[DISCARD_KEY$2]; await applyAtom(ctx.state, { type: "请求回应", requestType: DISCARD_RT$3, target: ownerId, prompt: { type: "distribute", mode: "select", title: "解围:选择一张手牌弃置", source: "hand", minTotal: 1, maxTotal: 1 }, timeout: 20 }); const discardCardId = ctx.state.localVars[DISCARD_KEY$2]; delete ctx.state.localVars[DISCARD_KEY$2]; if (!discardCardId || !self.hand.includes(discardCardId)) return; await applyAtom(ctx.state, { type: "弃置", player: ownerId, cardIds: [discardCardId], voluntary: true }); if (ctx.state.players.filter((p) => p.alive && fieldCardIds(ctx.state, p.index).length > 0).length === 0) return; delete ctx.state.localVars[SOURCE_PLAYER_KEY]; await applyAtom(ctx.state, { type: "请求回应", requestType: SOURCE_PLAYER_RT, target: ownerId, prompt: { type: "choosePlayer", title: "解围:选择源玩家(从其场上选一张牌移动)", min: 1, max: 1, filter: (_view, t) => ctx.state.players[t]?.alive === true && fieldCardIds(ctx.state, t).length > 0 }, timeout: 15 }); const srcPlayer = ctx.state.localVars[SOURCE_PLAYER_KEY]; delete ctx.state.localVars[SOURCE_PLAYER_KEY]; if (typeof srcPlayer !== "number") return; const srcPlayerState = ctx.state.players[srcPlayer]; if (!srcPlayerState?.alive || fieldCardIds(ctx.state, srcPlayer).length === 0) return; const equipment = Object.entries(srcPlayerState.equipment).filter(([, id]) => typeof id === "string").map(([slot, id]) => ({ slot, cardId: id, cardName: ctx.state.cardMap[id]?.name ?? "?" })); const judge = srcPlayerState.pendingTricks.map((t) => ({ cardId: t.card.id, cardName: t.card.name })); const handCount = srcPlayerState.hand.length; if (equipment.length === 0 && judge.length === 0 && handCount === 0) return; delete ctx.state.localVars[SOURCE_CARD_KEY]; await applyAtom(ctx.state, { type: "请求回应", requestType: SOURCE_CARD_RT, target: ownerId, prompt: { type: "pickTargetCard", title: "解围:选择要移动的场上牌", target: srcPlayer, equipment, judge, handCount }, timeout: 20 }); const sourceCardRaw = ctx.state.localVars[SOURCE_CARD_KEY]; delete ctx.state.localVars[SOURCE_CARD_KEY]; let srcCardId; if (sourceCardRaw) try { const parsed = JSON.parse(sourceCardRaw); if (parsed.zone === "equipment" || parsed.zone === "judge") { if (typeof parsed.cardId === "string") srcCardId = parsed.cardId; } else if (parsed.zone === "hand") { const idx = typeof parsed.handIndex === "number" ? parsed.handIndex : 0; srcCardId = srcPlayerState.hand[idx] ?? srcPlayerState.hand[0]; } } catch {} if (!srcCardId) return; if (!fieldCardIds(ctx.state, srcPlayer).includes(srcCardId)) return; delete ctx.state.localVars[DEST_PLAYER_KEY]; await applyAtom(ctx.state, { type: "请求回应", requestType: DEST_PLAYER_RT, target: ownerId, prompt: { type: "choosePlayer", title: "解围:选择目标玩家(将牌移到其场上)", min: 1, max: 1, filter: (_view, t) => ctx.state.players[t]?.alive === true && t !== srcPlayer }, timeout: 15 }); const destPlayer = ctx.state.localVars[DEST_PLAYER_KEY]; delete ctx.state.localVars[DEST_PLAYER_KEY]; if (typeof destPlayer !== "number") return; await moveFieldCard(ctx.state, srcPlayer, srcCardId, destPlayer); } finally { delete ctx.state.localVars[MOVING_FLAG]; } }); return () => {}; } function onMount$32(skill, api) { api.defineAction("transform", { label: "解围", style: "passive", prompt: { type: "useCard", title: "选择一张装备牌当无懈可击使用", cardFilter: { filter: (c) => c.type === "装备牌", min: 1, max: 1 } }, transform: (card) => ({ name: "无懈可击", sourceCardId: card.id, fromSkill: skill.id }), activeWhen: (ctx) => { const view = ctx.view; const pending = view.pending; if (!pending) return false; const atom = pending.atom; if (atom.type !== "请求回应" || atom.requestType !== "无懈可击") return false; const p = view.players[ctx.perspectiveIdx]; if (!p) return false; return Object.values(p.equipment ?? {}).some((id) => typeof id === "string" && !!id); } }); api.defineAction("respond", { label: "解围", style: "primary", prompt: { type: "confirm", title: "是否发动解围?", confirmLabel: "发动", cancelLabel: "不发动" } }); } var SHADOW_SUFFIX, CONFIRM_RT$5, DISCARD_RT$3, SOURCE_PLAYER_RT, SOURCE_CARD_RT, DEST_PLAYER_RT, CONFIRMED_KEY$4, DISCARD_KEY$2, SOURCE_PLAYER_KEY, SOURCE_CARD_KEY, DEST_PLAYER_KEY, MOVING_FLAG, 解围_default; var init_解围 = __esmMin((() => { init_apply(); init_skill(); init_lifecycle(); init_registry$1(); init_vars_keys(); SHADOW_SUFFIX = "解围"; CONFIRM_RT$5 = "解围/confirm"; DISCARD_RT$3 = "解围/discard"; SOURCE_PLAYER_RT = "解围/source-player"; SOURCE_CARD_RT = "解围/source-card"; DEST_PLAYER_RT = "解围/dest-player"; CONFIRMED_KEY$4 = "解围/confirmed"; DISCARD_KEY$2 = "解围/discardCardId"; SOURCE_PLAYER_KEY = "解围/sourcePlayer"; SOURCE_CARD_KEY = "解围/sourceCard"; DEST_PLAYER_KEY = "解围/destPlayer"; MOVING_FLAG = "解围/moving"; 解围_default = { createSkill: createSkill$37, onInit: onInit$37, onMount: onMount$32 }; })); //#endregion //#region src/engine/skills/诈降.ts var 诈降_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$36, default: () => _skillModule$10, onInit: () => onInit$36, onMount: () => onMount$31 }); /** 失去体力时是否处于 owner 自己的出牌阶段(诈降杀增益的生效条件)。 */ function inOwnerPlayPhase(state, ownerId) { return state.currentPlayerIndex === ownerId && state.phase === "出牌"; } function createSkill$36(id, ownerId) { return { id, ownerId, name: "诈降", description: "锁定技:当你失去1点体力后,你摸三张牌,若在你的出牌阶段,你本回合使用【杀】的限制次数+1、使用红色【杀】无距离限制且不能被抵消。", isLocked: true }; } function onInit$36(skill, state) { const ownerId = skill.ownerId; const unloadProvider = registerSlashExtraProvider(state, ownerId, (st, player) => st.turn.vars[ACTIVE_VAR] === player ? 1 : 0); const unloadRangeExemptor = registerAttackRangeExemptor(state, ownerId, (st, from, to, cardId) => { if (st.turn.vars[ACTIVE_VAR] !== from) return false; if (!cardId) return false; return st.cardMap[cardId]?.color === "红"; }); const unloadBanDodge = registerBeforeHook(state, skill.id, ownerId, "询问闪", async (ctx) => { if (ctx.atom.source !== ownerId) return; if (ctx.state.turn.vars[ACTIVE_VAR] !== ownerId) return; const cardId = ctx.params?.cardId; if (!cardId) return; const card = ctx.state.cardMap[cardId]; if (card?.name !== "杀") return; if (card.color !== "红") return; return { kind: "cancel" }; }); registerAfterHook(state, skill.id, ownerId, "失去体力", async (ctx) => { const atom = ctx.atom; if (atom.target !== ownerId) return; if ((atom.amount ?? 0) <= 0) return; await applyAtom(ctx.state, { type: "摸牌", player: ownerId, count: 3 }); if (inOwnerPlayPhase(ctx.state, ownerId)) { ctx.state.turn.vars[ACTIVE_VAR] = ownerId; await applyAtom(ctx.state, { type: "回合用量", player: ownerId, key: slashExtraKey("诈降"), value: 1 }); } }); return () => { unloadProvider(); unloadRangeExemptor(); unloadBanDodge(); }; } function onMount$31(_skill, _api) { return () => {}; } var ACTIVE_VAR, _skillModule$10; var init_诈降 = __esmMin((() => { init_apply(); init_skill(); init_slash_quota(); init_distance(); init_vars_keys(); ACTIVE_VAR = "诈降/active"; _skillModule$10 = { createSkill: createSkill$36, onInit: onInit$36, onMount: onMount$31 }; })); //#endregion //#region src/engine/skills/诸葛连弩.ts var 诸葛连弩_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$35, onInit: () => onInit$35 }); function createSkill$35(id, ownerId) { return { id, ownerId, name: "诸葛连弩", description: "武器:出牌阶段使用【杀】无次数限制", isLocked: true }; } function onInit$35(skill, state) { const ownerId = skill.ownerId; return registerSlashUnlimitedProvider(state, ownerId, () => true); } var init_诸葛连弩 = __esmMin((() => { init_slash_quota(); })); //#endregion //#region src/engine/skills/谦节.ts var 谦节_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$34, default: () => 谦节_default, onInit: () => onInit$34 }); function createSkill$34(id, ownerId) { return { id, ownerId, name: "谦节", description: "锁定技:当你进入连环状态时防止之;你不能成为延时锦囊牌和其他角色拼点的目标", isLocked: true }; } /** 从 card.subtype 推断装备槽位(镜像 装备.ts 的 inferSlot / 决堰.ts 的 inferEquipSlot) */ function inferEquipSlot$1(subtype) { switch (subtype) { case "武器": case "防具": case "进攻马": case "防御马": case "宝物": return subtype; default: return null; } } function onInit$34(skill, state) { const ownerId = skill.ownerId; registerBeforeHook(state, skill.id, ownerId, "设横置", async (ctx) => { const atom = ctx.atom; if (atom.player !== ownerId) return; if (!atom.chained) return; return { kind: "cancel" }; }); registerBeforeHook(state, skill.id, ownerId, "添加延时锦囊", async (ctx) => { if (ctx.atom.player !== ownerId) return; return { kind: "cancel" }; }); registerBeforeHook(state, skill.id, ownerId, "请求回应", async (ctx) => { const atom = ctx.atom; if (atom.target !== ownerId) return; const rt = atom.requestType; if (typeof rt !== "string" || !rt.endsWith("/拼点")) return; return { kind: "cancel" }; }); registerBeforeHook(state, skill.id, ownerId, "装备", async (ctx) => { const atom = ctx.atom; if (atom.player !== ownerId) return; const card = ctx.state.cardMap[atom.cardId]; if (!card) return; const slot = inferEquipSlot$1(card.subtype); if (!slot) return; if (ctx.state.players[ownerId]?.vars["决堰/废除:" + slot]) return { kind: "cancel" }; }); return () => {}; } var 谦节_default; var init_谦节 = __esmMin((() => { init_skill(); init_决堰(); 谦节_default = { createSkill: createSkill$34, onInit: onInit$34 }; })); //#endregion //#region src/engine/skills/谦逊.ts var 谦逊_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$33, default: () => 谦逊_default, onInit: () => onInit$33 }); function createSkill$33(id, ownerId) { return { id, ownerId, name: "谦逊", description: "锁定技:你不能成为顺手牵羊和乐不思蜀的目标", isLocked: true }; } function onInit$33(skill, state) { const ownerId = skill.ownerId; registerBeforeHook(state, skill.id, ownerId, "添加延时锦囊", async (ctx) => { const atom = ctx.atom; if (atom.player !== ownerId) return; if (atom.trick?.name !== "乐不思蜀") return; return { kind: "cancel" }; }); registerBeforeHook(state, skill.id, ownerId, "获得", async (ctx) => { const atom = ctx.atom; if (atom.from !== ownerId) return; if (atom.player === ownerId) return; if (topFrame(ctx.state)?.skillId !== "顺手牵羊") return; return { kind: "cancel" }; }); return () => {}; } var 谦逊_default; var init_谦逊 = __esmMin((() => { init_frame(); init_skill(); 谦逊_default = { createSkill: createSkill$33, onInit: onInit$33 }; })); //#endregion //#region src/engine/skills/贞良.ts var 贞良_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$32, default: () => _skillModule$9, onInit: () => onInit$32, onMount: () => onMount$30 }); function getState(state, ownerId) { return state.players[ownerId]?.vars[STATE_KEY] === "阴" ? "阴" : "阳"; } async function syncStateView(state, ownerId) { await applyAtom(state, { type: "回合用量", player: ownerId, key: STATE_VIEW_KEY, value: getState(state, ownerId) }); } /** 记录卢植使用/打出的一张牌 id 及其颜色(若已记录则不覆盖)。 */ function recordUsedCard(state, cardId) { const card = state.cardMap[cardId]; if (!card) return; const color = card.color; if (color !== "红" && color !== "黑") return; const map = state.localVars[USED_COLORS_KEY] ?? {}; if (!(cardId in map)) map[cardId] = color; state.localVars[USED_COLORS_KEY] = map; } function createSkill$32(id, ownerId) { return { id, ownerId, name: SKILL_ID$2, description: "转换技。阳:出牌阶段限一次,选攻击范围内一名其他角色,弃一张与\"任\"同色的牌对其造成1点伤害。阴:回合外使用或打出的牌置入弃牌堆时,若与\"任\"同色,可令一名角色摸一张牌" }; } function onInit$32(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "respond", (st, params) => { const slot = st.pendingSlots.get(ownerId); if (!slot) return "当前不需要回应"; const atom = slot.atom; if (atom.type !== "请求回应") return "当前不需要回应"; const rt = atom.requestType; if (rt === YIN_CONFIRM_RT) return null; if (rt === YIN_TARGET_RT) { const t = params.targets?.[0] ?? (typeof params.target === "number" ? params.target : void 0); if (typeof t !== "number") return "需要指定一名角色"; if (!st.players[t]?.alive) return "目标不合法"; return null; } return "当前不是贞良回应"; }, async (st, params) => { const rt = (st.pendingSlots.get(ownerId)?.atom)?.requestType; if (rt === YIN_CONFIRM_RT) st.localVars[YIN_CONFIRMED_KEY] = params.choice === true || params.confirmed === true; else if (rt === YIN_TARGET_RT) { const t = params.targets?.[0] ?? (typeof params.target === "number" ? params.target : void 0); if (typeof t === "number") st.localVars[YIN_TARGET_KEY] = t; } }); registerAction(state, skill.id, ownerId, "use", (st, params) => { const self = st.players[ownerId]; if (!self) return "player not found"; if (!self.alive) return "你已死亡"; if (st.currentPlayerIndex !== ownerId) return "只能在你的回合使用"; if (st.phase !== "出牌") return "只能在出牌阶段使用"; if (hasBlockingPending(st)) return "当前有未完成的询问"; if (usedThisTurn$3(st, ownerId, SKILL_ID$2)) return "本回合已使用过贞良"; if (getState(st, ownerId) !== "阳") return "当前为阴状态,无法发动阳"; const renColor = getRenColor(st, ownerId); if (!renColor) return "没有\"任\",无法发动贞良"; const target = params.target; if (typeof target !== "number") return "需要指定目标"; if (target === ownerId) return "不能对自己发动贞良"; if (!st.players[target]?.alive) return "目标不合法"; if (!inAttackRange(st, ownerId, target)) return "目标不在攻击范围内"; const cardId = params.cardId; if (typeof cardId !== "string") return "需要选择一张牌"; if (!self.hand.includes(cardId)) return "牌不在手牌中"; if (st.cardMap[cardId]?.color !== renColor) return "必须弃置一张与\"任\"颜色相同的牌"; return null; }, async (st, params) => { const from = ownerId; const target = params.target; const cardId = params.cardId; await markOncePerTurn(st, from, SKILL_ID$2); st.players[from].vars[STATE_KEY] = "阴"; await syncStateView(st, from); await pushFrame(st, `${SKILL_ID$2}(阳)`, from, { target, cardId }); if (st.players[from].hand.includes(cardId)) await applyAtom(st, { type: "弃置", player: from, cardIds: [cardId] }); if (st.players[target]?.alive) await runDamageFlow(st, from, target, 1); await popFrame(st); }); registerAfterHook(state, skill.id, ownerId, "使用时", async (ctx) => { const atom = ctx.atom; if (atom.type !== "使用时") return; if (atom.source !== ownerId) return; if (!atom.cardId) return; recordUsedCard(ctx.state, atom.cardId); }); registerAfterHook(state, skill.id, ownerId, "打出牌时", async (ctx) => { const atom = ctx.atom; if (atom.type !== "打出牌时") return; if (atom.player !== ownerId) return; if (!atom.cardId) return; recordUsedCard(ctx.state, atom.cardId); }); registerAfterHook(state, skill.id, ownerId, "移动牌", async (ctx) => { const atom = ctx.atom; if (atom.type !== "移动牌") return; if (!atom.cardId) return; if (atom.from?.zone !== "处理区") return; if (atom.to?.zone !== "弃牌堆") return; const st = ctx.state; const map = st.localVars[USED_COLORS_KEY]; if (!map || !(atom.cardId in map)) return; const usedColor = map[atom.cardId]; delete map[atom.cardId]; if (getState(st, ownerId) !== "阴") return; if (!st.players[ownerId]?.alive) return; if (st.currentPlayerIndex === ownerId) return; const renColor = getRenColor(st, ownerId); if (!renColor || usedColor !== renColor) return; delete st.localVars[YIN_CONFIRMED_KEY]; await applyAtom(st, { type: "请求回应", requestType: YIN_CONFIRM_RT, target: ownerId, prompt: { type: "confirm", title: "贞良(阴):是否令一名角色摸一张牌?", confirmLabel: "发动", cancelLabel: "不发动" }, defaultChoice: false, timeout: 15 }); if (!st.localVars[YIN_CONFIRMED_KEY]) return; delete st.localVars[YIN_CONFIRMED_KEY]; delete st.localVars[YIN_TARGET_KEY]; await applyAtom(st, { type: "请求回应", requestType: YIN_TARGET_RT, target: ownerId, prompt: { type: "choosePlayer", title: "贞良(阴):选择一名角色摸一张牌", min: 1, max: 1, candidates: st.players.filter((p) => p.alive).map((p) => p.index), filter: (_view, t) => !!st.players[t]?.alive }, timeout: 20 }); const chosen = st.localVars[YIN_TARGET_KEY]; delete st.localVars[YIN_TARGET_KEY]; if (typeof chosen !== "number" || !st.players[chosen]?.alive) return; await applyAtom(st, { type: "摸牌", player: chosen, count: 1 }); st.players[ownerId].vars[STATE_KEY] = "阳"; await syncStateView(st, ownerId); }); registerAfterHook(state, skill.id, ownerId, "回合开始", async (ctx) => { if (ctx.atom.type !== "回合开始") return; if (ctx.atom.player !== ownerId) return; await syncStateView(ctx.state, ownerId); }); return () => {}; } function onMount$30(_skill, api) { api.defineAction("use", { label: SKILL_ID$2, style: "danger", prompt: { type: "useCardAndTarget", title: "贞良(阳):弃一张与\"任\"同色的牌,对攻击范围内一名角色造成1点伤害", cardFilter: { filter: () => true, min: 1, max: 1 }, targetFilter: { min: 1, max: 1, filter: (view, t) => viewCanAttack(view.players, view.cardMap, view.currentPlayerIndex, t) } }, activeWhen: (ctx) => { if (!defaultPlayActive(ctx)) return false; const p = ctx.view.players[ctx.perspectiveIdx]; if (!p) return false; if (p.turnUsage?.[STATE_VIEW_KEY] === "阴") return false; if (p.turnUsage?.["贞良/usedThisTurn"]) return false; const renCardId = (p.marks.find((m) => m.id === REN_MARK_ID)?.payload)?.cardId; if (typeof renCardId !== "string") return false; const renColor = ctx.view.cardMap[renCardId]?.color; if (renColor !== "红" && renColor !== "黑") return false; if (!(p.hand ?? []).some((c) => c.color === renColor)) return false; return ctx.view.players.some((other) => other.index !== ctx.perspectiveIdx && other.alive); } }); api.defineAction("respond", { label: SKILL_ID$2, style: "primary", prompt: { type: "confirm", title: "贞良", confirmLabel: "确认", cancelLabel: "取消" } }); return () => {}; } var SKILL_ID$2, STATE_KEY, STATE_VIEW_KEY, YIN_CONFIRM_RT, YIN_TARGET_RT, YIN_CONFIRMED_KEY, YIN_TARGET_KEY, USED_COLORS_KEY, _skillModule$9; var init_贞良 = __esmMin((() => { init_apply(); init_frame(); init_damage(); init_distance(); init_once_per_turn(); init_action_active(); init_viewDistance(); init_skill(); init_明任(); SKILL_ID$2 = "贞良"; STATE_KEY = "贞良/态"; STATE_VIEW_KEY = "贞良/态"; YIN_CONFIRM_RT = "贞良/阴/confirm"; YIN_TARGET_RT = "贞良/阴/选目标"; YIN_CONFIRMED_KEY = "贞良/阴/confirmed"; YIN_TARGET_KEY = "贞良/阴/目标"; USED_COLORS_KEY = "贞良/阴/usedColors"; _skillModule$9 = { createSkill: createSkill$32, onInit: onInit$32, onMount: onMount$30 }; })); //#endregion //#region src/engine/skills/贯石斧.ts var 贯石斧_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$31, onInit: () => onInit$31, onMount: () => onMount$29 }); function createSkill$31(id, ownerId) { return { id, ownerId, name: "贯石斧", description: "武器技:杀被闪抵消后,可弃两张牌令此杀依然造成伤害" }; } function onInit$31(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "respond", (state, params) => { const slot = state.pendingSlots.get(ownerId); if (!slot) return "当前不需要回应"; if (slot.atom.type !== "请求回应") return "当前不需要回应"; if (slot.atom.requestType !== "贯石斧/select") return "当前不是贯石斧询问"; const cardIds = params.cardIds; if (!Array.isArray(cardIds) || cardIds.length !== 2) return "需要选择 2 张牌"; const self = state.players[ownerId]; if (!self) return "player not found"; const [id1, id2] = cardIds; if (id1 === id2) return "不能选择同一张牌"; const equipIds = Object.values(self.equipment).filter((id) => typeof id === "string"); if (![id1, id2].every((id) => self.hand.includes(id) || equipIds.includes(id))) return "所选牌不在你的手牌或装备区"; return null; }, async (state, params) => { state.localVars["贯石斧/selected"] = params.cardIds; }); registerAfterHook(state, skill.id, ownerId, "被抵消", async (ctx) => { if (ctx.frame.skillId !== "杀") return; const atom = ctx.atom; if (atom.source !== ownerId) return; const self = ctx.state.players[ownerId]; if (!self) return; const weaponId = self.equipment["武器"]; if (!weaponId) return; if (ctx.state.cardMap[weaponId]?.name !== "贯石斧") return; if (!isCancelled(ctx.state, atom.cardId, atom.target)) return; const equipIds = Object.values(self.equipment).filter((id) => typeof id === "string"); if (self.hand.length + equipIds.length < 2) return; delete ctx.state.localVars["贯石斧/selected"]; await applyAtom(ctx.state, { type: "请求回应", requestType: "贯石斧/select", target: ownerId, prompt: { type: "distribute", title: "贯石斧:选择 2 张牌弃置强命(不选则不发动)", mode: "select", source: "handAndEquip", minTotal: 2, maxTotal: 2 }, defaultChoice: false, timeout: 30 }); const selectedIds = ctx.state.localVars["贯石斧/selected"]; delete ctx.state.localVars["贯石斧/selected"]; if (selectedIds?.length !== 2) return; await applyAtom(ctx.state, { type: "弃置", player: ownerId, cardIds: selectedIds, voluntary: true }); clearCancelled(ctx.state, atom.cardId, atom.target); }); return () => {}; } function onMount$29(skill, api) { api.defineAction("respond", { label: "贯石斧", style: "danger", prompt: { type: "distribute", title: "贯石斧:选择 2 张牌弃置强命(不选则不发动)", mode: "select", source: "handAndEquip", minTotal: 2, maxTotal: 2 } }); } var init_贯石斧 = __esmMin((() => { init_apply(); init_skill(); init_frame(); })); //#endregion //#region src/engine/skills/进趋.ts var 进趋_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$30, default: () => _skillModule$8, onInit: () => onInit$30, onMount: () => onMount$28 }); function createSkill$30(id, ownerId) { return { id, ownerId, name: SKILL_ID$1, description: "结束阶段可摸两张牌,然后将手牌弃至X张(X为本回合发动奇制的次数)" }; } /** 当前 pending 的 requestType(类型安全读取) */ function currentRequestType$3(state, ownerId) { const slot = state.pendingSlots.get(ownerId); if (!slot) return void 0; return slot.atom.requestType; } function onInit$30(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "respond", (st, params) => { const rt = currentRequestType$3(st, ownerId); if (rt !== CONFIRM_RT$4 && rt !== DISCARD_RT$2) return "当前不是进趋询问"; if (rt === CONFIRM_RT$4) return null; const cardIds = params.cardIds; if (!Array.isArray(cardIds) || cardIds.length === 0) return "请选择要弃置的牌"; const self = st.players[ownerId]; if (!self) return "玩家不存在"; const seen = /* @__PURE__ */ new Set(); for (const id of cardIds) { if (typeof id !== "string" || !self.hand.includes(id)) return `牌 ${id} 不在手牌中`; if (seen.has(id)) return "存在重复的牌"; seen.add(id); } const x = getQizhiCount(st.turn.vars) ?? 0; const excess = self.hand.length - x; if (excess <= 0) return "当前无需弃置"; if (cardIds.length !== excess) return `需要恰好弃置 ${excess} 张手牌`; return null; }, async (st, params) => { const rt = currentRequestType$3(st, ownerId); if (rt === CONFIRM_RT$4) st.localVars[CONFIRM_KEY$1] = params.choice === true || params.confirmed === true; else if (rt === DISCARD_RT$2) { const ids = params.cardIds; if (Array.isArray(ids)) st.localVars[DISCARD_KEY$1] = ids; } }); registerAfterHook(state, skill.id, ownerId, "阶段开始", async (ctx) => { const atom = ctx.atom; if (atom.type !== "阶段开始") return; if (atom.phase !== "回合结束") return; if (atom.player !== ownerId) return; if (!ctx.state.players[ownerId]?.alive) return; const x = getQizhiCount(ctx.state.turn.vars) ?? 0; delete ctx.state.localVars[CONFIRM_KEY$1]; await applyAtom(ctx.state, { type: "请求回应", requestType: CONFIRM_RT$4, target: ownerId, prompt: { type: "confirm", title: `是否发动进趋?(摸两张牌,然后将手牌弃至 ${x} 张)`, confirmLabel: "发动", cancelLabel: "不发动" }, defaultChoice: false, timeout: 15 }); if (!ctx.state.localVars[CONFIRM_KEY$1]) { delete ctx.state.localVars[CONFIRM_KEY$1]; return; } delete ctx.state.localVars[CONFIRM_KEY$1]; await applyAtom(ctx.state, { type: "摸牌", player: ownerId, count: 2 }); const handNow = ctx.state.players[ownerId]?.hand.length ?? 0; if (handNow > x) { const excess = handNow - x; delete ctx.state.localVars[DISCARD_KEY$1]; await applyAtom(ctx.state, { type: "请求回应", requestType: DISCARD_RT$2, target: ownerId, prompt: { type: "useCard", title: `进趋:弃 ${excess} 张手牌(弃至 ${x} 张)`, cardFilter: { filter: () => true, min: excess, max: excess } }, mandatory: true, timeout: 30 }); let cardIds = ctx.state.localVars[DISCARD_KEY$1]; delete ctx.state.localVars[DISCARD_KEY$1]; if ((!cardIds || cardIds.length === 0) && excess > 0) cardIds = ctx.state.players[ownerId]?.hand.slice(0, excess) ?? []; if (cardIds && cardIds.length > 0) await applyAtom(ctx.state, { type: "弃置", player: ownerId, cardIds }); } }); return () => {}; } function onMount$28(_skill, api) { api.defineAction("respond", { label: SKILL_ID$1, style: "primary", prompt: { type: "confirm", title: "是否发动进趋?", confirmLabel: "发动", cancelLabel: "不发动" } }); return () => {}; } var SKILL_ID$1, CONFIRM_RT$4, DISCARD_RT$2, CONFIRM_KEY$1, DISCARD_KEY$1, _skillModule$8; var init_进趋 = __esmMin((() => { init_apply(); init_skill(); init_vars_keys(); SKILL_ID$1 = "进趋"; CONFIRM_RT$4 = `${SKILL_ID$1}/confirm`; DISCARD_RT$2 = `${SKILL_ID$1}/弃牌`; CONFIRM_KEY$1 = `${SKILL_ID$1}/confirmed`; DISCARD_KEY$1 = `${SKILL_ID$1}/discardCards`; _skillModule$8 = { createSkill: createSkill$30, onInit: onInit$30, onMount: onMount$28 }; })); //#endregion //#region src/engine/skills/连环.ts var 连环_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$29, default: () => 连环_default, onInit: () => onInit$29, onMount: () => onMount$27 }); /** 影子卡 id:${原id}#连环 */ function shadowIdOf$2(cardId) { return `${cardId}#连环`; } function createSkill$29(id, ownerId) { return { id, ownerId, name: "连环", description: "你可以将一张梅花手牌当【铁索连环】使用或重铸" }; } function onInit$29(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "recycle", (state, params) => { const myTurn = state.currentPlayerIndex === ownerId; const inActPhase = state.phase === "出牌"; const free = !hasBlockingPending(state); const self = state.players[ownerId]; const selfAlive = self?.alive === true; const cardId = params.cardId; const cardIdOk = typeof cardId === "string"; const card = cardIdOk ? state.cardMap[cardId] : void 0; const cardInHand = cardIdOk && self.hand.includes(cardId); const isClub = !!card && card.suit === "♣"; return myTurn && inActPhase && free && selfAlive && cardInHand && isClub ? null : "现在不能重铸(需出牌阶段、手中梅花牌)"; }, async (state, params) => { const from = ownerId; const cardId = params.cardId; await pushFrame(state, "连环", from, { ...params }); await recastCard(state, from, cardId); await popFrame(state); }); registerAction(state, skill.id, ownerId, "transform", (state, params) => { const self = state.players[ownerId]; const selfAlive = self?.alive === true; const cardId = params.cardId; const cardIdOk = typeof cardId === "string"; const card = cardIdOk ? state.cardMap[cardId] : void 0; const cardInHand = cardIdOk && self.hand.includes(cardId); const isClub = !!card && card.suit === "♣"; if (!selfAlive) return "你已死亡"; if (!cardIdOk || !cardInHand) return "牌不在手牌中"; if (!isClub) return "只能将梅花牌当铁索连环"; return null; }, async (state, params) => { const cardId = params.cardId; const shadowId = shadowIdOf$2(cardId); await applyAtom(state, { type: "当作", player: ownerId, cardIds: [cardId], shadowId, outputName: "铁索连环" }); }, (state, params) => { const cardId = params.cardId; const sId = shadowIdOf$2(cardId); delete state.cardMap[sId]; const self = state.players[ownerId]; const idx = self.hand.indexOf(sId); if (idx >= 0) self.hand[idx] = cardId; }); return () => {}; } function onMount$27(skill, api) { api.defineAction("recycle", { label: "连环·重铸", style: "primary", prompt: { type: "useCard", title: "连环:弃一张梅花手牌,摸一张牌", cardFilter: { filter: (c) => c.suit === "♣", min: 1, max: 1 } }, activeWhen: (ctx) => { if (!defaultPlayActive(ctx)) return false; const p = ctx.view.players[ctx.perspectiveIdx]; if (!p) return false; return p.hand?.some((c) => c.suit === "♣") ?? false; } }); api.defineAction("transform", { label: "连环", style: "primary", prompt: { type: "useCardAndTarget", title: "连环:将一张梅花手牌当铁索连环使用", cardFilter: { filter: (c) => c.suit === "♣", min: 1, max: 1 }, targetFilter: { min: 1, max: 2, allowSelf: true } }, transform: (card) => ({ name: "铁索连环", sourceCardId: card.id, fromSkill: skill.id }), activeWhen: (ctx) => { if (!defaultPlayActive(ctx)) return false; const p = ctx.view.players[ctx.perspectiveIdx]; if (!p) return false; return p.hand?.some((c) => c.suit === "♣") ?? false; } }); } var 连环_default; var init_连环 = __esmMin((() => { init_apply(); init_frame(); init_recast(); init_skill(); init_action_active(); 连环_default = { createSkill: createSkill$29, onInit: onInit$29, onMount: onMount$27 }; })); //#endregion //#region src/engine/skills/连营.ts var 连营_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$28, default: () => 连营_default, onInit: () => onInit$28, onMount: () => onMount$26 }); function createSkill$28(id, ownerId) { return { id, ownerId, name: "连营", description: "被动技:当你失去最后的手牌时,你可以摸一张牌" }; } function onInit$28(skill, state) { const ownerId = skill.ownerId; async function maybeTriggerLianYing(ctx) { const self = ctx.state.players[ownerId]; if (!self?.alive) return; if (self.hand.length !== 0) return; if (ctx.state.zones.deck.length === 0 && ctx.state.zones.discardPile.length === 0) return; delete ctx.state.localVars[CONFIRMED_KEY$3]; await applyAtom(ctx.state, { type: "请求回应", requestType: CONFIRM_RT$3, target: ownerId, prompt: { type: "confirm", title: "是否发动连营?(失去最后的手牌,摸一张牌)", confirmLabel: "发动", cancelLabel: "不发动" }, defaultChoice: false, timeout: 10 }); if (!ctx.state.localVars[CONFIRMED_KEY$3]) return; await applyAtom(ctx.state, { type: "摸牌", player: ownerId, count: 1 }); } registerAction(state, skill.id, ownerId, "respond", (st, _params) => { const slot = st.pendingSlots.get(ownerId); if (!slot) return "当前不需要回应"; const atom = slot.atom; if (atom.type !== "请求回应") return "当前不是请求回应"; if (atom.requestType !== CONFIRM_RT$3) return "当前不是连营确认"; return null; }, async (st, params) => { st.localVars[CONFIRMED_KEY$3] = params.choice === true || params.confirmed === true; }); registerAfterHook(state, skill.id, ownerId, "移动牌", async (ctx) => { const atom = ctx.atom; if (atom.type !== "移动牌") return; if (atom.from.zone !== "手牌") return; if (atom.from.player !== ownerId) return; if (atom.to.zone === "手牌" && atom.to.player === ownerId) return; await maybeTriggerLianYing(ctx); }); registerAfterHook(state, skill.id, ownerId, "给予", async (ctx) => { const atom = ctx.atom; if (atom.type !== "给予") return; if (atom.from !== ownerId) return; if (atom.to === ownerId) return; await maybeTriggerLianYing(ctx); }); registerAfterHook(state, skill.id, ownerId, "装备", async (ctx) => { const atom = ctx.atom; if (atom.type !== "装备") return; if (atom.player !== ownerId) return; await maybeTriggerLianYing(ctx); }); registerBeforeHook(state, skill.id, ownerId, "获得", async (ctx) => { const atom = ctx.atom; if (atom.type !== "获得") return; if (atom.from !== ownerId) return; if (atom.player === ownerId) return; const hand = ctx.state.players[ownerId]?.hand ?? []; if (atom.cardId && hand.includes(atom.cardId)) ctx.state.localVars[HAND_BEFORE_KEY] = hand.length; }); registerAfterHook(state, skill.id, ownerId, "获得", async (ctx) => { const atom = ctx.atom; if (atom.type !== "获得") return; if (atom.from !== ownerId) return; if (atom.player === ownerId) return; const before = ctx.state.localVars[HAND_BEFORE_KEY]; delete ctx.state.localVars[HAND_BEFORE_KEY]; if (typeof before !== "number") return; await maybeTriggerLianYing(ctx); }); registerBeforeHook(state, skill.id, ownerId, "弃置", async (ctx) => { const atom = ctx.atom; if (atom.type !== "弃置") return; if (atom.player !== ownerId) return; const hand = ctx.state.players[ownerId]?.hand ?? []; if (atom.cardIds?.some((id) => hand.includes(id))) ctx.state.localVars[HAND_BEFORE_KEY] = hand.length; }); registerAfterHook(state, skill.id, ownerId, "弃置", async (ctx) => { const atom = ctx.atom; if (atom.type !== "弃置") return; if (atom.player !== ownerId) return; const before = ctx.state.localVars[HAND_BEFORE_KEY]; delete ctx.state.localVars[HAND_BEFORE_KEY]; if (typeof before !== "number") return; await maybeTriggerLianYing(ctx); }); registerBeforeHook(state, skill.id, ownerId, "移出至暂存区", async (ctx) => { const atom = ctx.atom; if (atom.type !== "移出至暂存区") return; if (atom.target !== ownerId) return; const hand = ctx.state.players[ownerId]?.hand ?? []; if (atom.cardIds?.some((id) => hand.includes(id))) ctx.state.localVars[HAND_BEFORE_KEY] = hand.length; }); registerAfterHook(state, skill.id, ownerId, "移出至暂存区", async (ctx) => { const atom = ctx.atom; if (atom.type !== "移出至暂存区") return; if (atom.target !== ownerId) return; const before = ctx.state.localVars[HAND_BEFORE_KEY]; delete ctx.state.localVars[HAND_BEFORE_KEY]; if (typeof before !== "number") return; await maybeTriggerLianYing(ctx); }); return () => {}; } function onMount$26(_skill, api) { api.defineAction("respond", { label: "连营", style: "default", prompt: { type: "confirm", title: "是否发动连营?", confirmLabel: "发动(摸一张牌)", cancelLabel: "不发动" } }); } var CONFIRM_RT$3, CONFIRMED_KEY$3, HAND_BEFORE_KEY, 连营_default; var init_连营 = __esmMin((() => { init_apply(); init_skill(); CONFIRM_RT$3 = "连营/confirm"; CONFIRMED_KEY$3 = "连营/confirmed"; HAND_BEFORE_KEY = "连营/handBefore"; 连营_default = { createSkill: createSkill$28, onInit: onInit$28, onMount: onMount$26 }; })); //#endregion //#region src/engine/skills/遗礼.ts var 遗礼_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$27, default: () => _skillModule$7, onInit: () => onInit$27, onMount: () => onMount$25 }); function createSkill$27(id, ownerId) { return { id, ownerId, name: SKILL_ID, description: "出牌阶段开始时,你可以失去1点体力或移除1枚橘,然后令一名其他角色获得1枚橘" }; } /** 当前 pending 的 requestType(类型安全读取) */ function currentRequestType$2(state, ownerId) { const slot = state.pendingSlots.get(ownerId); if (!slot) return void 0; return slot.atom.requestType; } function onInit$27(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "respond", (st, params) => { const rt = currentRequestType$2(st, ownerId); if (rt !== CONFIRM_RT$2 && rt !== COST_RT && rt !== TARGET_RT$2) return "当前不是遗礼询问"; if (rt === CONFIRM_RT$2) return null; if (rt === COST_RT) { const opt = params.option; if (opt !== "loseHp" && opt !== "removeJu") return "请选择代价(失去体力/移除橘)"; if (opt === "removeJu" && juCount(st, ownerId) <= 0) return "你没有橘标记可移除"; return null; } const tgt = params.target ?? params.targets?.[0]; if (typeof tgt !== "number") return "请选择一名其他角色"; if (tgt === ownerId) return "不能选择自己"; if (!st.players[tgt]?.alive) return "目标已死亡"; return null; }, async (st, params) => { const rt = currentRequestType$2(st, ownerId); if (rt === CONFIRM_RT$2) st.localVars[CONFIRM_KEY] = params.choice === true || params.confirmed === true; else if (rt === COST_RT) st.localVars[COST_KEY] = params.option; else if (rt === TARGET_RT$2) { const tgt = params.target ?? params.targets?.[0]; if (typeof tgt === "number") st.localVars[TARGET_KEY$4] = tgt; } }); registerAfterHook(state, skill.id, ownerId, "阶段开始", async (ctx) => { const atom = ctx.atom; if (atom.type !== "阶段开始") return; if (atom.phase !== "出牌") return; if (atom.player !== ownerId) return; const st = ctx.state; if (!st.players[ownerId]?.alive) return; if (st.players.filter((p) => p.alive && p.index !== ownerId).length === 0) return; delete st.localVars[CONFIRM_KEY]; await applyAtom(st, { type: "请求回应", requestType: CONFIRM_RT$2, target: ownerId, prompt: { type: "confirm", title: "是否发动遗礼?(失去1点体力或移除1枚橘,令一名其他角色获得1枚橘)", confirmLabel: "发动", cancelLabel: "不发动" }, defaultChoice: false, timeout: 20 }); if (!st.localVars[CONFIRM_KEY]) { delete st.localVars[CONFIRM_KEY]; return; } delete st.localVars[CONFIRM_KEY]; const hasJu = juCount(st, ownerId) > 0; let cost; if (hasJu) { delete st.localVars[COST_KEY]; await applyAtom(st, { type: "请求回应", requestType: COST_RT, target: ownerId, prompt: { type: "chooseOption", title: "遗礼:选择代价", options: [{ value: "loseHp", label: "失去 1 点体力" }, { value: "removeJu", label: "移除 1 枚橘" }] }, timeout: 20 }); const picked = st.localVars[COST_KEY]; delete st.localVars[COST_KEY]; cost = picked === "removeJu" ? "removeJu" : "loseHp"; } else cost = "loseHp"; const candidates = st.players.filter((p) => p.alive && p.index !== ownerId); delete st.localVars[TARGET_KEY$4]; await applyAtom(st, { type: "请求回应", requestType: TARGET_RT$2, target: ownerId, prompt: { type: "choosePlayer", title: "遗礼:选择一名其他角色获得 1 枚橘", min: 1, max: 1, candidates: candidates.map((p) => p.index) }, timeout: 20 }); let target = st.localVars[TARGET_KEY$4]; delete st.localVars[TARGET_KEY$4]; if (typeof target !== "number" || !st.players[target]?.alive || target === ownerId) target = candidates[0].index; if (cost === "loseHp") await applyAtom(st, { type: "失去体力", target: ownerId, amount: 1 }); else await removeJu(st, ownerId); if (st.players[target]?.alive) await addJu(st, target); }); return () => {}; } function onMount$25(_skill, _api) { return () => {}; } var SKILL_ID, CONFIRM_RT$2, COST_RT, TARGET_RT$2, CONFIRM_KEY, COST_KEY, TARGET_KEY$4, _skillModule$7; var init_遗礼 = __esmMin((() => { init_apply(); init_skill(); init_怀橘(); SKILL_ID = "遗礼"; CONFIRM_RT$2 = `${SKILL_ID}/confirm`; COST_RT = `${SKILL_ID}/cost`; TARGET_RT$2 = `${SKILL_ID}/target`; CONFIRM_KEY = `${SKILL_ID}/confirmed`; COST_KEY = `${SKILL_ID}/costChoice`; TARGET_KEY$4 = `${SKILL_ID}/chosenTarget`; _skillModule$7 = { createSkill: createSkill$27, onInit: onInit$27, onMount: onMount$25 }; })); //#endregion //#region src/engine/skills/遗计.ts var 遗计_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$26, default: () => _skillModule$6, onInit: () => onInit$26, onMount: () => onMount$24 }); function createSkill$26(id, ownerId) { return { id, ownerId, name: "遗计", description: "受到 1 点伤害后,观看牌堆顶的两张牌,然后将这些牌交给任意角色" }; } function onInit$26(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "respond", (st, params) => { const slot = st.pendingSlots.get(ownerId); if (!slot) return "当前不需要回应"; const atom = slot.atom; if (atom.type !== "请求回应" || atom.requestType !== DISTRIBUTE_RT) return "当前不是遗计分配"; const allocation = params.allocation; if (Array.isArray(allocation) && allocation.length > 0) { const deck = st.zones.deck; const top2 = new Set([deck[deck.length - 1], deck[deck.length - 2]]); const seen = /* @__PURE__ */ new Set(); for (const entry of allocation) { if (!st.players[entry.target]?.alive) return "目标无效"; for (const cid of entry.cardIds) { if (!top2.has(cid)) return "牌不在可分配范围"; if (seen.has(cid)) return "存在重复的牌"; seen.add(cid); } } if (seen.size !== top2.size) return "必须分配全部两张牌"; } return null; }, async (st, params) => { st.localVars[ALLOC_KEY] = params.allocation ?? null; }); registerAfterHook(state, skill.id, ownerId, "伤害结算结束后", async (ctx) => { if (ctx.atom.target !== ownerId) return; if (!ctx.state.players[ownerId]?.alive) return; const amount = ctx.atom.amount ?? 0; if (amount <= 0) return; for (let i = 0; i < amount; i++) { if (!ctx.state.players[ownerId]?.alive) break; if (ctx.state.zones.deck.length < 2) break; const deck = ctx.state.zones.deck; const top2 = [deck[deck.length - 1], deck[deck.length - 2]]; delete ctx.state.localVars[ALLOC_KEY]; await applyAtom(ctx.state, { type: "请求回应", requestType: DISTRIBUTE_RT, target: ownerId, prompt: { type: "distribute", title: "遗计:观看牌堆顶两张牌并分配", cardIds: top2, minPerTarget: 1, maxPerTarget: 2 }, timeout: 30 }); const distribution = ctx.state.localVars[ALLOC_KEY]; if (Array.isArray(distribution)) { const cardToTarget = /* @__PURE__ */ new Map(); for (const entry of distribution) { if (!ctx.state.players[entry.target]?.alive) continue; for (const cardId of entry.cardIds) cardToTarget.set(cardId, entry.target); } for (const cardId of top2) { const target = cardToTarget.get(cardId); if (typeof target === "number") await applyAtom(ctx.state, { type: "摸牌", player: target, count: 1 }); } } delete ctx.state.localVars[ALLOC_KEY]; } }); return () => {}; } function onMount$24(_skill, api) { api.defineAction("respond", { label: "遗计", style: "primary", prompt: { type: "distribute", title: "遗计:观看牌堆顶两张牌并分配", minPerTarget: 1, maxPerTarget: 2 } }); } var DISTRIBUTE_RT, ALLOC_KEY, _skillModule$6; var init_遗计 = __esmMin((() => { init_apply(); init_skill(); DISTRIBUTE_RT = "遗计/distribute"; ALLOC_KEY = "遗计/allocation"; _skillModule$6 = { createSkill: createSkill$26, onInit: onInit$26, onMount: onMount$24 }; })); //#endregion //#region src/engine/skills/酒池.ts var 酒池_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$25, default: () => 酒池_default, onInit: () => onInit$25, onMount: () => onMount$23 }); function createSkill$25(id, ownerId) { return { id, ownerId, name: "酒池", description: "你可以将一张黑桃手牌当【酒】使用" }; } /** 影子卡 id:${原id}#酒池 */ function shadowIdOf$1(cardId) { return `${cardId}#酒池`; } function onInit$25(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "transform", (state, params) => { const myTurn = state.currentPlayerIndex === ownerId; const free = !hasBlockingPending(state); const self = state.players[ownerId]; const selfAlive = self.alive === true; const cardId = params.cardId; const cardIdOk = typeof cardId === "string"; const card = cardIdOk ? state.cardMap[cardId] : void 0; const cardInHand = cardIdOk && self.hand.includes(cardId); const isSpade = !!card && card.suit === "♠"; const slot = state.pendingSlots.get(ownerId); const atomType = (slot?.atom)?.type; const reqType = (slot?.atom)?.requestType; const atomTarget = (slot?.atom)?.target; const askedPeach = !!slot && atomTarget === ownerId && atomType === "请求回应" && reqType === "桃/求桃" && self.health <= 0; return cardInHand && isSpade && (myTurn && free && selfAlive || selfAlive && askedPeach) ? null : "现在不能使用酒池"; }, async (state, params) => { const cardId = params.cardId; const shadowId = shadowIdOf$1(cardId); await applyAtom(state, { type: "当作", player: ownerId, cardIds: [cardId], shadowId, outputName: "酒" }); }, (state, params) => { const cardId = params.cardId; const sId = shadowIdOf$1(cardId); delete state.cardMap[sId]; const self = state.players[ownerId]; const idx = self.hand.indexOf(sId); if (idx >= 0) self.hand[idx] = cardId; }); const unloadDecl = declareAlternativeResponse(state, ownerId, "请求回应", "桃/求桃"); return () => { unloadDecl(); }; } function onMount$23(skill, api) { api.defineAction("transform", { label: "酒池", style: "passive", prompt: { type: "useCard", title: "选择一张黑桃手牌当酒使用", cardFilter: { filter: (c) => c.suit === "♠", min: 1, max: 1 } }, transform: (card) => ({ name: "酒", sourceCardId: card.id, fromSkill: skill.id }), activeWhen: (ctx) => { const p = ctx.view.players[ctx.perspectiveIdx]; if (!p) return false; if (!(p.hand?.some((c) => c.suit === "♠") ?? false)) return false; if (defaultPlayActive(ctx)) return true; const slot = ctx.view.pending; const reqType = (slot?.atom)?.requestType; return !!slot && slot.target === ctx.perspectiveIdx && reqType === "桃/求桃" && p.health <= 0; } }); } var 酒池_default; var init_酒池 = __esmMin((() => { init_skill(); init_apply(); init_action_active(); 酒池_default = { createSkill: createSkill$25, onInit: onInit$25, onMount: onMount$23 }; })); //#endregion //#region src/engine/skills/酣战.ts var 酣战_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$24, default: () => _skillModule$5, onInit: () => onInit$24, onMount: () => onMount$22 }); /** 拼点牌点数:A=1, 2-10=面值, J=11, Q=12, K=13 */ function rankValue$1(rank) { if (rank === "A") return 1; if (rank === "J") return 11; if (rank === "Q") return 12; if (rank === "K") return 13; const n = parseInt(rank, 10); return Number.isFinite(n) ? n : 0; } function createSkill$24(id, ownerId) { return { id, ownerId, name: "酣战", description: "拼点前可令对方用随机手牌拼点;拼点后可获得两张拼点牌中点数最大的杀" }; } function onInit$24(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "respond", (st, _params) => { const slot = st.pendingSlots.get(ownerId); if (!slot) return "当前不需要回应"; const atom = slot.atom; if (atom["type"] !== "请求回应") return "当前不需要回应"; const rt = atom["requestType"]; if (rt !== FORCE_CONFIRM_RT && rt !== GAIN_CONFIRM_RT) return "当前不是酣战询问"; return null; }, async (st, params) => { const rt = (st.pendingSlots.get(ownerId)?.atom)?.requestType; const ok = params.choice === true || params.confirmed === true; if (rt === FORCE_CONFIRM_RT) st.localVars[FORCE_KEY] = ok; else if (rt === GAIN_CONFIRM_RT) st.localVars[GAIN_KEY] = ok; }); registerBeforeHook(state, skill.id, ownerId, "请求回应", async (ctx) => { const atom = ctx.atom; if (atom.type !== "请求回应") return; if (atom.requestType !== TIAN_YI_PD_RT) return; if (ctx.state.currentPlayerIndex !== ownerId) return; const target = atom.target; if (typeof target !== "number") return; const targetPlayer = ctx.state.players[target]; if (!targetPlayer?.alive) return; if (targetPlayer.hand.length === 0) return; if (!ctx.state.players[ownerId]?.alive) return; delete ctx.state.localVars[FORCE_KEY]; await applyAtom(ctx.state, { type: "请求回应", requestType: FORCE_CONFIRM_RT, target: ownerId, prompt: { type: "confirm", title: "酣战:是否令对方用一张随机手牌拼点?", confirmLabel: "随机拼点", cancelLabel: "正常拼点" }, defaultChoice: false, timeout: 20 }); const confirmed = ctx.state.localVars[FORCE_KEY]; delete ctx.state.localVars[FORCE_KEY]; if (!confirmed) return; const rng = createRng(ctx.state.rngSeed); const hand = ctx.state.players[target].hand; const randomCardId = hand[rng.nextInt(hand.length)]; ctx.state.rngSeed = rng.getState(); ctx.state.localVars[TIAN_YI_TARGET_CARD_KEY] = randomCardId; return { kind: "cancel" }; }); registerAfterHook(state, skill.id, ownerId, "拼点后", async (ctx) => { const atom = ctx.atom; if (atom.type !== "拼点后") return; if (atom.initiator !== ownerId) return; const st = ctx.state; if (!st.players[ownerId]?.alive) return; const candidates = []; for (const id of [atom.initiatorCard, atom.targetCard]) { if (!id) continue; const card = st.cardMap[id]; if (card?.name === "杀") candidates.push({ id, value: rankValue$1(card.rank) }); } if (candidates.length === 0) return; delete st.localVars[GAIN_KEY]; await applyAtom(st, { type: "请求回应", requestType: GAIN_CONFIRM_RT, target: ownerId, prompt: { type: "confirm", title: "酣战:是否获得两张拼点牌中点数最大的杀?", confirmLabel: "获得", cancelLabel: "不获得" }, defaultChoice: false, timeout: 20 }); const confirmed = st.localVars[GAIN_KEY]; delete st.localVars[GAIN_KEY]; if (!confirmed) return; candidates.sort((a, b) => b.value - a.value); const bestId = candidates[0].id; if (st.zones.discardPile.includes(bestId)) await applyAtom(st, { type: "移动牌", cardId: bestId, from: { zone: "弃牌堆" }, to: { zone: "手牌", player: ownerId } }); }); return () => {}; } function onMount$22(_skill, _api) {} var TIAN_YI_PD_RT, TIAN_YI_TARGET_CARD_KEY, FORCE_CONFIRM_RT, FORCE_KEY, GAIN_CONFIRM_RT, GAIN_KEY, _skillModule$5; var init_酣战 = __esmMin((() => { init_apply(); init_skill(); init_rng(); TIAN_YI_PD_RT = "天义/拼点"; TIAN_YI_TARGET_CARD_KEY = "天义/targetCard"; FORCE_CONFIRM_RT = "酣战/随机拼点"; FORCE_KEY = "酣战/forceConfirmed"; GAIN_CONFIRM_RT = "酣战/获杀"; GAIN_KEY = "酣战/gainConfirmed"; _skillModule$5 = { createSkill: createSkill$24, onInit: onInit$24, onMount: onMount$22 }; })); //#endregion //#region src/engine/skills/铁索连环.ts var 铁索连环_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$23, default: () => 铁索连环_default, onInit: () => onInit$23, onMount: () => onMount$21 }); function createSkill$23(id, ownerId) { return { id, ownerId, name: "铁索连环", description: "横置/重置一至两名角色;或重铸" }; } function onInit$23(skill, state) { const ownerId = skill.ownerId; const recastUnload = registerAction(state, skill.id, ownerId, "recast", (state, params) => { return validateUseCard(state, ownerId, params, { cardName: "铁索连环" }); }, async (state, params) => { const cardId = params.cardId; await recastCard(state, ownerId, cardId); }); return () => { recastUnload(); }; } function onMount$21(_skill, api) { api.defineAction("recast", { label: "铁索连环·重铸", style: "primary", prompt: { type: "useCard", title: "铁索连环:重铸(弃此牌,摸一张)", cardFilter: { filter: (c) => c.name === "铁索连环", min: 1, max: 1 } } }); } var 铁索连环_default; var init_铁索连环 = __esmMin((() => { init_recast(); init_skill(); 铁索连环_default = { createSkill: createSkill$23, onInit: onInit$23, onMount: onMount$21 }; })); //#endregion //#region src/engine/skills/铁骑.ts var 铁骑_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$22, onInit: () => onInit$22, onMount: () => onMount$20 }); function createSkill$22(id, ownerId) { return { id, ownerId, name: "铁骑", description: "使用杀指定目标后可判定,红色则目标不能出闪抵消" }; } function onInit$22(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "respond", (state, _params) => { const slot = state.pendingSlots.get(ownerId); if (!slot) return "当前不需要回应"; const atom = slot.atom; if (atom.type !== "请求回应") return "当前不是请求回应"; if (atom.requestType !== "铁骑/confirm") return "当前不是铁骑确认"; return null; }, async (state, params) => { state.localVars[CONFIRM$1] = params.choice === true || params.confirmed === true; }); registerAfterHook(state, skill.id, ownerId, "指定目标", async (ctx) => { const atom = ctx.atom; if (atom.source !== ownerId) return; if (atom.target === void 0) return; const target = atom.target; if (atom.cardId !== void 0) { if (ctx.state.cardMap[atom.cardId]?.name !== "杀") return; } if (!ctx.state.players[target]?.alive) return; if (ctx.state.zones.deck.length === 0) return; delete ctx.state.localVars[CONFIRM$1]; await applyAtom(ctx.state, { type: "请求回应", requestType: "铁骑/confirm", target: ownerId, prompt: { type: "confirm", title: "是否发动铁骑判定?", confirmLabel: "发动", cancelLabel: "不发动" }, defaultChoice: false, timeout: 10 }); if (!ctx.state.localVars[CONFIRM$1]) return; ctx.state.localVars[TARGET_VAR$1] = target; await runJudgeFlow(ctx.state, ownerId, "铁骑"); }); registerAfterHook(state, skill.id, ownerId, "判定牌生效后", async (ctx) => { const atom = ctx.atom; if (atom.judgeType !== "铁骑") return; if (atom.player !== ownerId) return; const target = ctx.state.localVars[TARGET_VAR$1]; delete ctx.state.localVars[TARGET_VAR$1]; if (target === void 0) return; if (!ctx.state.players[target]?.alive) return; const cards = frameCards(ctx.state); if (cards.length === 0) return; const judgeCardId = cards[cards.length - 1]; const judgeCard = ctx.state.cardMap[judgeCardId]; if (!judgeCard) return; if (judgeCard.color === "红") await applyAtom(ctx.state, { type: "加标签", player: target, tag: TAG$1 }); }); registerBeforeHook(state, skill.id, ownerId, "询问闪", async (ctx) => { const atom = ctx.atom; if (atom.source !== ownerId) return; const target = atom.target; if (target === void 0) return; if (!ctx.state.players[target]?.tags.includes(TAG$1)) return; await applyAtom(ctx.state, { type: "去标签", player: target, tag: TAG$1 }); return { kind: "cancel" }; }); return () => {}; } function onMount$20(_skill, api) { api.defineAction("respond", { label: "铁骑", style: "default", prompt: { type: "confirm", title: "是否发动铁骑判定?", confirmLabel: "发动", cancelLabel: "不发动" } }); } var TAG$1, CONFIRM$1, TARGET_VAR$1; var init_铁骑 = __esmMin((() => { init_apply(); init_frame(); init_judge(); init_skill(); TAG$1 = "铁骑/禁闪"; CONFIRM$1 = "铁骑/confirmed"; TARGET_VAR$1 = "铁骑/target"; })); //#endregion //#region src/engine/skills/镇骨.ts var 镇骨_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$21, default: () => _skillModule$4, onInit: () => onInit$21, onMount: () => onMount$19 }); function createSkill$21(id, ownerId) { return { id, ownerId, name: SKILL_NAME$1, description: "结束阶段,你可以选择一名其他角色,本回合结束时和其下回合结束时,其将手牌调整至与你手牌数相同(至多摸至五张)" }; } /** 读取当前 pending 的 requestType(类型安全) */ function currentRequestType$1(state, seat) { return (state.pendingSlots.get(seat)?.atom)?.requestType; } /** * 将 target 手牌调整至与 owner 手牌数相同。 * - target 少:摸 min(差额, MAX_DRAW) 张。 * - target 多:强制选 (差额) 张弃置(超时自动补弃)。 * - 相等:无操作。 */ async function adjustHand(state, ownerId, target) { const owner = state.players[ownerId]; const tp = state.players[target]; if (!owner?.alive || !tp?.alive) return; const ownerHand = owner.hand.length; const targetHand = tp.hand.length; if (targetHand < ownerHand) { const draw = Math.min(ownerHand - targetHand, MAX_DRAW); if (draw > 0) await applyAtom(state, { type: "摸牌", player: target, count: draw }); return; } if (targetHand > ownerHand) { const excess = targetHand - ownerHand; delete state.localVars[DISCARD_LV]; await applyAtom(state, { type: "请求回应", requestType: DISCARD_RT$1, target, prompt: { type: "useCard", title: `镇骨:手牌调整,弃 ${excess} 张牌`, cardFilter: { filter: () => true, min: excess, max: excess } }, mandatory: true, timeout: 30 }); let cardIds = state.localVars[DISCARD_LV]; delete state.localVars[DISCARD_LV]; if ((!cardIds || cardIds.length === 0) && excess > 0) cardIds = state.players[target]?.hand.slice(0, excess) ?? []; if (cardIds && cardIds.length > 0) await applyAtom(state, { type: "弃置", player: target, cardIds }); } } function onInit$21(skill, state) { const ownerId = skill.ownerId; const unloaders = []; for (const p of state.players) { const seatId = p.index; const isOwner = seatId === ownerId; const u = registerAction(state, skill.id, seatId, "respond", (st, params) => { const slot = st.pendingSlots.get(seatId); if (!slot) return "当前不需要回应"; const atom = slot.atom; if (atom.type !== "请求回应") return "当前不需要回应"; const rt = atom.requestType; if (rt === CHOOSE_RT$1) { if (!isOwner) return "当前不是你的询问"; return null; } if (rt === DISCARD_RT$1) { const cardIds = params.cardIds; if (!Array.isArray(cardIds) || cardIds.length === 0) return "请选择要弃置的牌"; const tp = st.players[seatId]; if (!tp) return "玩家不存在"; for (const id of cardIds) if (typeof id !== "string" || !tp.hand.includes(id)) return `牌 ${id} 不在手牌中`; if (new Set(cardIds).size !== cardIds.length) return "cardIds 含重复牌"; const excess = slot.atom.prompt?.cardFilter?.min; if (typeof excess === "number" && cardIds.length !== excess) return `需要恰好弃置 ${excess} 张手牌`; return null; } return "当前不是镇骨询问"; }, async (st, params) => { const rt = currentRequestType$1(st, seatId); if (rt === CHOOSE_RT$1) { const t = params.targets?.[0] ?? (typeof params.target === "number" ? params.target : void 0); if (typeof t === "number") st.localVars[CHOOSE_LV] = t; } else if (rt === DISCARD_RT$1) { const ids = params.cardIds; if (Array.isArray(ids)) st.localVars[DISCARD_LV] = ids; } }); unloaders.push(u); } registerAfterHook(state, skill.id, ownerId, "阶段开始", async (ctx) => { const atom = ctx.atom; if (atom.type !== "阶段开始") return; if (atom.phase !== "回合结束") return; if (atom.player !== ownerId) return; const ownerState = ctx.state.players[ownerId]; if (!ownerState?.alive) return; delete ctx.state.localVars[CHOOSE_LV]; await applyAtom(ctx.state, { type: "请求回应", requestType: CHOOSE_RT$1, target: ownerId, prompt: { type: "choosePlayer", title: "镇骨:选择一名其他角色(本回合结束及其下回合结束时,其手牌调整至与你相同)", min: 1, max: 1, filter: (view, t) => t !== ownerId && view.players[t]?.alive === true }, timeout: 20 }); const target = ctx.state.localVars[CHOOSE_LV]; delete ctx.state.localVars[CHOOSE_LV]; if (typeof target !== "number") return; if (!ctx.state.players[target]?.alive) return; ownerState.vars[TARGET_KEY$3] = target; ownerState.vars[PHASE_KEY] = "本回合"; }); registerAfterHook(state, skill.id, ownerId, "回合结束", async (ctx) => { const atom = ctx.atom; if (atom.type !== "回合结束") return; const ownerState = ctx.state.players[ownerId]; if (!ownerState) return; const phase = ownerState.vars[PHASE_KEY]; const target = ownerState.vars[TARGET_KEY$3]; if (phase === "本回合" && atom.player === ownerId) { if (typeof target !== "number") return; await adjustHand(ctx.state, ownerId, target); if (ctx.state.players[ownerId]?.alive) ownerState.vars[PHASE_KEY] = "目标下回合"; else { delete ownerState.vars[PHASE_KEY]; delete ownerState.vars[TARGET_KEY$3]; } return; } if (phase === "目标下回合" && typeof target === "number" && atom.player === target) { await adjustHand(ctx.state, ownerId, target); delete ownerState.vars[PHASE_KEY]; delete ownerState.vars[TARGET_KEY$3]; } }); return () => { unloaders.forEach((u) => u()); }; } function onMount$19(_skill, api) { api.defineAction("respond", { label: "镇骨", style: "primary", prompt: { type: "choosePlayer", title: "镇骨:选择一名其他角色", min: 1, max: 1 } }); } var SKILL_NAME$1, TARGET_KEY$3, PHASE_KEY, CHOOSE_LV, DISCARD_LV, CHOOSE_RT$1, DISCARD_RT$1, MAX_DRAW, _skillModule$4; var init_镇骨 = __esmMin((() => { init_apply(); init_skill(); SKILL_NAME$1 = "镇骨"; TARGET_KEY$3 = "镇骨/目标"; PHASE_KEY = "镇骨/阶段"; CHOOSE_LV = "镇骨/chooseTarget"; DISCARD_LV = "镇骨/discardCards"; CHOOSE_RT$1 = "镇骨/选目标"; DISCARD_RT$1 = "镇骨/弃牌"; MAX_DRAW = 5; _skillModule$4 = { createSkill: createSkill$21, onInit: onInit$21, onMount: onMount$19 }; })); //#endregion //#region src/engine/skills/闭月.ts var 闭月_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$20, default: () => _skillModule$3, onInit: () => onInit$20, onMount: () => onMount$18 }); function createSkill$20(id, ownerId) { return { id, ownerId, name: "闭月", description: "回合结束阶段,你可以摸一张牌" }; } function onInit$20(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "respond", (st, _params) => { const slot = st.pendingSlots.get(ownerId); if (!slot) return "当前不需要回应"; const atom = slot.atom; if (atom["type"] !== "请求回应") return "当前不需要回应"; if (atom["requestType"] !== CONFIRM_REQUEST$2) return "当前不是闭月确认"; return null; }, async (st, params) => { st.localVars[CONFIRMED_KEY$2] = params.choice === true || params.confirmed === true; }); registerAfterHook(state, skill.id, ownerId, "阶段开始", async (ctx) => { const atom = ctx.atom; if (atom.type !== "阶段开始") return; if (atom.phase !== "回合结束") return; if (atom.player !== ownerId) return; if (!ctx.state.players[ownerId]?.alive) return; delete ctx.state.localVars[CONFIRMED_KEY$2]; await applyAtom(ctx.state, { type: "请求回应", requestType: CONFIRM_REQUEST$2, target: ownerId, prompt: { type: "confirm", title: "是否发动闭月?(摸一张牌)", confirmLabel: "发动", cancelLabel: "不发动" }, defaultChoice: false, timeout: 15 }); if (ctx.state.localVars[CONFIRMED_KEY$2]) await applyAtom(ctx.state, { type: "摸牌", player: ownerId, count: 1 }); }); return () => {}; } function onMount$18(_skill, api) { api.defineAction("respond", { label: "闭月", style: "primary", prompt: { type: "confirm", title: "是否发动闭月?(摸一张牌)", confirmLabel: "发动", cancelLabel: "不发动" } }); return () => {}; } var CONFIRM_REQUEST$2, CONFIRMED_KEY$2, _skillModule$3; var init_闭月 = __esmMin((() => { init_apply(); init_skill(); CONFIRM_REQUEST$2 = "闭月/confirm"; CONFIRMED_KEY$2 = "闭月/confirmed"; _skillModule$3 = { createSkill: createSkill$20, onInit: onInit$20, onMount: onMount$18 }; })); //#endregion //#region src/engine/skills/雄乱.ts var 雄乱_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$19, default: () => _skillModule$2, onInit: () => onInit$19, onMount: () => onMount$17 }); /** 从 card.subtype 推断装备槽位(镜像 谦节.ts / 决堰.ts) */ function inferEquipSlot(subtype) { switch (subtype) { case "武器": case "防具": case "进攻马": case "防御马": case "宝物": return subtype; default: return null; } } function createSkill$19(id, ownerId) { return { id, ownerId, name: SKILL_NAME, description: "限定技,出牌阶段,指定一名其他角色并废除你的判定区和装备区,本回合对其使用牌无距离和次数限制,其本回合不能使用和打出手牌" }; } /** 目标座次是否被本回合雄乱禁出牌(turn.vars['雄乱/目标'] 命中) */ function isBannedFromPlaying(state, seat) { return state.turn.vars[TARGET_VAR] === seat; } function onInit$19(skill, state) { const ownerId = skill.ownerId; const unloadUse = registerAction(state, skill.id, ownerId, "use", (st, params) => { if (st.currentPlayerIndex !== ownerId) return "不是你的回合"; if (st.phase !== "出牌") return "只能在出牌阶段发动"; if (hasBlockingPending(st)) return "当前有未完成的询问"; if (st.players[ownerId]?.vars[USED_KEY]) return "雄乱已使用过(限定技)"; if (!st.players[ownerId]?.alive) return "玩家不存在或已死亡"; const target = params.target ?? (Array.isArray(params.targets) ? params.targets[0] : void 0); if (typeof target !== "number") return "请选择一名其他角色"; if (target === ownerId) return "不能选择自己"; if (!st.players[target]?.alive) return "目标不存在或已死亡"; return null; }, async (st, params) => { const target = params.target ?? (Array.isArray(params.targets) ? params.targets[0] : void 0); if (typeof target !== "number") return; st.players[ownerId].vars[USED_KEY] = true; await pushFrame(st, SKILL_NAME, ownerId, { target }); try { const self = st.players[ownerId]; const trickNames = [...new Set(self.pendingTricks.map((t) => t.name))]; for (const name of trickNames) await applyAtom(st, { type: "移除延时锦囊", player: ownerId, trickName: name }); st.players[ownerId].vars[JUDGE_ABOLISHED] = true; for (const slot of ALL_EQUIP_SLOTS) { const equipId = self.equipment[slot]; if (equipId) { await applyAtom(st, { type: "卸下", player: ownerId, slot }); if (st.players[ownerId].hand.includes(equipId)) await applyAtom(st, { type: "移动牌", cardId: equipId, from: { zone: "手牌", player: ownerId }, to: { zone: "弃牌堆" } }); } st.players[ownerId].vars[ABOLISH_PREFIX + slot] = true; } st.turn.vars[TARGET_VAR] = target; await applyAtom(st, { type: "回合用量", player: ownerId, key: slashUnlimitedKey("雄乱"), value: true }); await applyAtom(st, { type: "回合用量", player: ownerId, key: TARGET_VAR, value: target }); } finally { await popFrame(st); } }); const unloadDist = registerDistanceExemptor(state, ownerId, (st, from, to) => { if (from !== ownerId) return false; return st.turn.vars[TARGET_VAR] === to; }); const unloadUnlimited = registerSlashUnlimitedProvider(state, ownerId, (st, player) => { if (player !== ownerId) return false; return typeof st.turn.vars[TARGET_VAR] === "number"; }); const unloadBlockDodge = registerBeforeHook(state, skill.id, ownerId, "询问闪", async (ctx) => { const atom = ctx.atom; if (typeof atom.target !== "number") return; if (!isBannedFromPlaying(ctx.state, atom.target)) return; return { kind: "cancel" }; }); const unloadBlockKill = registerBeforeHook(state, skill.id, ownerId, "询问杀", async (ctx) => { const atom = ctx.atom; if (typeof atom.target !== "number") return; if (!isBannedFromPlaying(ctx.state, atom.target)) return; return { kind: "cancel" }; }); const unloadBlockRespond = registerBeforeHook(state, skill.id, ownerId, "请求回应", async (ctx) => { const atom = ctx.atom; if (typeof atom.target !== "number") return; const promptType = atom.prompt?.type; if (!promptType || !CARD_PLAY_PROMPTS.has(promptType)) return; if (!isBannedFromPlaying(ctx.state, atom.target)) return; return { kind: "cancel" }; }); const unloadBlockTrick = registerBeforeHook(state, skill.id, ownerId, "添加延时锦囊", async (ctx) => { const atom = ctx.atom; if (typeof atom.player !== "number") return; if (atom.player !== ownerId) return; if (!ctx.state.players[ownerId]?.vars[JUDGE_ABOLISHED]) return; return { kind: "cancel" }; }); const unloadBlockEquip = registerBeforeHook(state, skill.id, ownerId, "装备", async (ctx) => { const atom = ctx.atom; if (typeof atom.player !== "number" || atom.player !== ownerId) return; const card = ctx.state.cardMap[atom.cardId ?? ""]; if (!card) return; const slot = inferEquipSlot(card.subtype); if (!slot) return; if (ctx.state.players[ownerId]?.vars[ABOLISH_PREFIX + slot]) return { kind: "cancel" }; }); return () => { unloadUse(); unloadDist(); unloadUnlimited(); unloadBlockDodge(); unloadBlockKill(); unloadBlockRespond(); unloadBlockTrick(); unloadBlockEquip(); }; } function onMount$17(_skill, api) { api.defineAction("use", { label: "雄乱", style: "danger", activeWhen: (ctx) => defaultPlayActive(ctx), prompt: { type: "selectTarget", title: "雄乱(限定技):指定一名其他角色,废除你的判定区和装备区,本回合对其使用牌无距离和次数限制,其本回合不能使用和打出手牌", targetFilter: { min: 1, max: 1, filter: (_view, target) => target !== _view.currentPlayerIndex && !!_view.players[target]?.alive } } }); } var SKILL_NAME, USED_KEY, TARGET_VAR, ABOLISH_PREFIX, JUDGE_ABOLISHED, ALL_EQUIP_SLOTS, CARD_PLAY_PROMPTS, _skillModule$2; var init_雄乱 = __esmMin((() => { init_apply(); init_frame(); init_skill(); init_slash_quota(); init_vars_keys(); init_distance(); init_action_active(); SKILL_NAME = "雄乱"; USED_KEY = "雄乱/used"; TARGET_VAR = XIONGLUAN_TARGET_VIEW_KEY; ABOLISH_PREFIX = "雄乱/废除:"; JUDGE_ABOLISHED = `${ABOLISH_PREFIX}判定`; ALL_EQUIP_SLOTS = [ "武器", "防具", "进攻马", "防御马", "宝物" ]; CARD_PLAY_PROMPTS = new Set([ "useCard", "useCardAndTarget", "pickProcessingCard", "pickTargetCard", "distribute" ]); _skillModule$2 = { createSkill: createSkill$19, onInit: onInit$19, onMount: onMount$17 }; })); //#endregion //#region src/engine/skills/集智.ts var 集智_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$18, onInit: () => onInit$18, onMount: () => onMount$16 }); function createSkill$18(id, ownerId) { return { id, ownerId, name: "集智", description: "被动技:使用非延时锦囊牌时,你可以摸一张牌" }; } function onInit$18(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "respond", (st) => { const slot = st.pendingSlots.get(ownerId); if (!slot) return "当前不需要回应"; if (slot.atom.type !== "请求回应") return "当前不是集智确认窗口"; if (slot.atom.requestType !== CONFIRM_REQUEST$1) return "当前不是集智确认窗口"; return null; }, async (st, params) => { st.localVars["集智/confirmed"] = params.choice === true || params.confirmed === true; }); registerAfterHook(state, skill.id, ownerId, "移动牌", async (ctx) => { const atom = ctx.atom; if (!atom.cardId) return; const from = atom.from; const to = atom.to; if (from?.zone !== "手牌" || from.player !== ownerId) return; if (!to || to.zone !== "处理区" && to.zone !== "弃牌堆") return; const card = ctx.state.cardMap[atom.cardId]; if (!card) return; if (card.type !== "锦囊牌") return; if (!NON_DELAY_TRICKS.has(card.name)) return; if (!ctx.state.players[ownerId]?.alive) return; delete ctx.state.localVars["集智/confirmed"]; await applyAtom(ctx.state, { type: "请求回应", requestType: CONFIRM_REQUEST$1, target: ownerId, prompt: { type: "confirm", title: "是否发动集智摸一张牌?", confirmLabel: "摸牌", cancelLabel: "不摸" }, defaultChoice: true, timeout: 15 }); if (ctx.state.localVars["集智/confirmed"] === false) return; await applyAtom(ctx.state, { type: "摸牌", player: ownerId, count: 1 }); }); return () => {}; } function onMount$16(_skill, api) { api.defineAction("respond", { label: "集智", style: "primary", prompt: { type: "confirm", title: "是否发动集智摸一张牌?", confirmLabel: "摸牌", cancelLabel: "不摸" } }); } var NON_DELAY_TRICKS, CONFIRM_REQUEST$1; var init_集智 = __esmMin((() => { init_apply(); init_skill(); NON_DELAY_TRICKS = new Set([ "过河拆桥", "顺手牵羊", "无中生有", "无懈可击", "借刀杀人", "桃园结义", "五谷丰登", "南蛮入侵", "万箭齐发", "决斗", "火攻", "铁索连环" ]); CONFIRM_REQUEST$1 = "集智/confirm"; })); //#endregion //#region src/engine/skills/雌雄双股剑.ts var 雌雄双股剑_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$17, default: () => 雌雄双股剑_default, onInit: () => onInit$17, onMount: () => onMount$15 }); function createSkill$17(id, ownerId) { return { id, ownerId, name: "雌雄双股剑", description: "武器:你使用杀指定异性角色为目标后,可令其弃1手牌或令你摸1牌", isLocked: true }; } function onInit$17(skill, state) { const ownerId = skill.ownerId; const SKILL_ID = skill.id; const unloaders = []; for (const p of state.players) { const pid = p.index; const unloader = registerAction(state, SKILL_ID, pid, "respond", (st, params) => { const slot = st.pendingSlots.get(pid); if (slot?.atom.type !== "请求回应") return "当前不需要回应"; const reqType = slot.atom.requestType; if (reqType === CONFIRM_REQUEST) return null; if (reqType === CHOICE_REQUEST) { const cardId = params.cardId; if (cardId !== void 0) { if (!st.players[pid].hand.includes(cardId)) return "牌不在手牌中"; } return null; } return "当前不是雌雄双股剑询问"; }, async (st, params) => { const reqType = (st.pendingSlots.get(pid)?.atom)?.requestType; if (reqType === CONFIRM_REQUEST) st.localVars[CONFIRMED_VAR] = params.choice === true; else if (reqType === CHOICE_REQUEST) st.localVars[CHOICE_VAR] = params.cardId ?? null; }); unloaders.push(unloader); } const hookUnloader = registerAfterHook(state, SKILL_ID, ownerId, "指定目标", async (ctx) => { const atom = ctx.atom; if (atom.source !== ownerId) return; if (typeof atom.target !== "number") return; if (!atom.cardId) return; if (!ctx.state.cardMap[atom.cardId]?.name.includes("杀")) return; const self = ctx.state.players[ownerId]; const target = ctx.state.players[atom.target]; if (!self || !target) return; const weaponId = self.equipment["武器"]; if (!weaponId) return; if (ctx.state.cardMap[weaponId]?.name !== "雌雄双股剑") return; if (getGender(self.character) === getGender(target.character)) return; delete ctx.state.localVars[CONFIRMED_VAR]; await applyAtom(ctx.state, { type: "请求回应", requestType: CONFIRM_REQUEST, target: ownerId, prompt: { type: "confirm", title: "雌雄双股剑:是否发动?(令目标弃1手牌,或你摸1张牌)", confirmLabel: "发动", cancelLabel: "不发动" }, defaultChoice: false, timeout: 10 }); if (ctx.state.localVars[CONFIRMED_VAR] !== true) { delete ctx.state.localVars[CONFIRMED_VAR]; return; } delete ctx.state.localVars[CONFIRMED_VAR]; if (target.hand.length === 0) { await applyAtom(ctx.state, { type: "摸牌", player: ownerId, count: 1 }); return; } delete ctx.state.localVars[CHOICE_VAR]; await applyAtom(ctx.state, { type: "请求回应", requestType: CHOICE_REQUEST, target: atom.target, prompt: { type: "useCard", title: `雌雄双股剑:弃置一张手牌,或放弃(令 ${self.name} 摸一张牌)`, cardFilter: { filter: () => true, candidates: target.hand.slice(), min: 1, max: 1 } }, timeout: 15 }); const discardId = ctx.state.localVars[CHOICE_VAR]; delete ctx.state.localVars[CHOICE_VAR]; if (typeof discardId === "string" && target.hand.includes(discardId)) await applyAtom(ctx.state, { type: "弃置", player: atom.target, cardIds: [discardId] }); else await applyAtom(ctx.state, { type: "摸牌", player: ownerId, count: 1 }); }); unloaders.push(hookUnloader); return () => { for (const fn of unloaders) fn(); }; } function onMount$15(_skill, api) { api.defineAction("respond", { label: "雌雄双股剑", style: "default", prompt: { type: "confirm", title: "雌雄双股剑:是否发动?", confirmLabel: "发动", cancelLabel: "不发动" } }); } var CONFIRM_REQUEST, CHOICE_REQUEST, CONFIRMED_VAR, CHOICE_VAR, 雌雄双股剑_default; var init_雌雄双股剑 = __esmMin((() => { init_apply(); init_skill(); init_character_meta(); CONFIRM_REQUEST = "雌雄双股剑/confirm"; CHOICE_REQUEST = "雌雄双股剑/choice"; CONFIRMED_VAR = "雌雄双股剑/confirmed"; CHOICE_VAR = "雌雄双股剑/discardId"; 雌雄双股剑_default = { createSkill: createSkill$17, onInit: onInit$17, onMount: onMount$15 }; })); //#endregion //#region src/engine/skills/雷击.ts var 雷击_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$16, default: () => 雷击_default, onInit: () => onInit$16, onMount: () => onMount$14 }); function createSkill$16(id, ownerId) { return { id, ownerId, name: "雷击", description: "当你使用或打出【闪】时,可令一名其他角色判定,黑桃则造成2点雷电伤害;梅花则你回复1点体力并对其造成1点雷电伤害" }; } function onInit$16(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "respond", (st, params) => { const slot = st.pendingSlots.get(ownerId); if (!slot) return "当前不需要回应"; const atom = slot.atom; if (atom["type"] !== "请求回应") return "当前不需要回应"; if (atom["requestType"] !== CHOOSE_RT) return "当前不是雷击选择"; const target = params.target; if (typeof target === "number") { if (target === ownerId) return "不能选择自己"; if (!st.players[target]?.alive) return "目标不存在或已死亡"; } return null; }, async (st, params) => { const target = params.target; st.localVars[TARGET_KEY$2] = typeof target === "number" ? target : null; }); registerAfterHook(state, skill.id, ownerId, "询问闪", async (ctx) => { const atom = ctx.atom; if (atom.type !== "询问闪") return; if (atom.target !== ownerId) return; const me = ctx.state.players[ownerId]; if (!me?.alive) return; if (!isCancelled(ctx.state, ctx.frame.params.cardId ?? "", ownerId)) return; delete ctx.state.localVars[TARGET_KEY$2]; await applyAtom(ctx.state, { type: "请求回应", requestType: CHOOSE_RT, target: ownerId, prompt: { type: "choosePlayer", title: "雷击:选择一名角色进行判定(或放弃)", min: 1, max: 1, filter: (view, t) => view.players[t]?.alive === true && t !== ownerId }, timeout: 20 }); const target = ctx.state.localVars[TARGET_KEY$2]; delete ctx.state.localVars[TARGET_KEY$2]; if (typeof target !== "number") return; if (!ctx.state.players[target]?.alive) return; await runJudgeFlow(ctx.state, target, "雷击"); const dp = ctx.state.zones.discardPile; if (dp.length === 0) return; const judgeCardId = dp[dp.length - 1]; const judgeCard = ctx.state.cardMap[judgeCardId]; if (!judgeCard) return; if (judgeCard.suit === "♠") await runDamageFlow(ctx.state, ownerId, target, 2, void 0, "雷电"); else if (judgeCard.suit === "♣") { if (me.health < me.maxHealth) await applyAtom(ctx.state, { type: "回复体力", target: ownerId, amount: 1 }); await runDamageFlow(ctx.state, ownerId, target, 1, void 0, "雷电"); } }); return () => {}; } function onMount$14(_skill, api) { api.defineAction("respond", { label: "雷击", style: "danger", prompt: { type: "choosePlayer", title: "雷击:选择判定目标(或放弃)", min: 1, max: 1, filter: (view, t) => view.players[t]?.alive === true && t !== _skill.ownerId } }); } var CHOOSE_RT, TARGET_KEY$2, 雷击_default; var init_雷击 = __esmMin((() => { init_apply(); init_judge(); init_damage(); init_skill(); init_frame(); CHOOSE_RT = "雷击/chooseTarget"; TARGET_KEY$2 = "雷击/target"; 雷击_default = { createSkill: createSkill$16, onInit: onInit$16, onMount: onMount$14 }; })); //#endregion //#region src/engine/skills/青囊.ts var 青囊_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$15, onInit: () => onInit$15, onMount: () => onMount$13 }); function createSkill$15(id, ownerId) { return { id, ownerId, name: "青囊", description: "出牌阶段限一次,弃一张手牌,令一名角色回复 1 点体力" }; } function onInit$15(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "use", (state, params) => { if (state.currentPlayerIndex !== ownerId) return "不是你的回合"; if (state.phase !== "出牌") return "不是出牌阶段"; if (usedThisTurn$3(state, ownerId, "青囊")) return "本回合已使用过青囊"; const self = state.players[ownerId]; if (!self) return "player not found"; if (!self.alive) return "你已死亡"; const cardId = params.cardId; if (!cardId) return "cardId required"; if (!self.hand.includes(cardId)) return "牌不在手牌中"; const target = params.target ?? params.targets?.[0] ?? ownerId; const tp = state.players[target]; if (!tp) return "目标不存在"; if (!tp.alive) return "目标已死亡"; if (tp.health >= tp.maxHealth) return "目标体力已满"; return null; }, async (state, params) => { const from = ownerId; const cardId = params.cardId; const target = params.target ?? params.targets?.[0] ?? ownerId; await markOncePerTurn(state, from, "青囊"); await pushFrame(state, "青囊", from, { ...params }); await applyAtom(state, { type: "弃置", player: from, cardIds: [cardId], voluntary: true }); await applyAtom(state, { type: "回复体力", target, amount: 1, source: from }); await popFrame(state); }); return () => {}; } function onMount$13(skill, api) { api.defineAction("use", { label: "青囊", style: "primary", prompt: { type: "useCardAndTarget", title: "青囊:弃一张手牌,令一名角色回复 1 点体力", cardFilter: { filter: () => true, min: 1, max: 1 }, targetFilter: { min: 1, max: 1, filter: (view, target) => { const p = view.players[target]; return !!p && p.alive !== false && p.health < p.maxHealth; } } }, activeWhen: (ctx) => activeUnlessUsedThisTurn("青囊")(ctx) }); } var init_青囊 = __esmMin((() => { init_apply(); init_frame(); init_once_per_turn(); init_skill(); })); //#endregion //#region src/engine/skills/青釭剑.ts var 青釭剑_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$14, onInit: () => onInit$14 }); function getTempUnloadMap(state) { let m = tempUnloadByState.get(state); if (!m) { m = /* @__PURE__ */ new Map(); tempUnloadByState.set(state, m); } return m; } function createSkill$14(id, ownerId) { return { id, ownerId, name: "青釭剑", description: "武器:杀无视目标防具", isLocked: true }; } function onInit$14(skill, state) { const ownerId = skill.ownerId; registerAfterHook(state, skill.id, ownerId, "指定目标", async (ctx) => { if (ctx.atom.source !== ownerId) return; const usedCardId = ctx.atom.cardId; if (!usedCardId) return; if (ctx.state.cardMap[usedCardId]?.name !== "杀") return; const me = ctx.state.players[ownerId]; if (!me) return; const weaponId = me.equipment["武器"]; if (!weaponId) return; if (ctx.state.cardMap[weaponId]?.name !== "青釭剑") return; const target = ctx.atom.target; const targetPlayer = ctx.state.players[target]; if (!targetPlayer) return; const armorId = targetPlayer.equipment?.["防具"]; if (!armorId) return; const armorCard = ctx.state.cardMap[armorId]; if (!armorCard) return; const unloaded = []; const armorSkillId = armorCard.name; if (armorSkillId && targetPlayer.skills.includes(armorSkillId)) { unloaded.push({ target, skillId: armorSkillId }); unloadSkillInstance(ctx.state, armorSkillId, target); } if (unloaded.length > 0) getTempUnloadMap(ctx.state).set(ownerId, unloaded); }); async function restoreArmor(curState, source) { if (source !== ownerId) return; const unloaded = getTempUnloadMap(curState).get(ownerId); if (!unloaded || unloaded.length === 0) return; for (const { target, skillId } of unloaded) await instantiateSkill(curState, skillId, target); getTempUnloadMap(curState).delete(ownerId); } registerAfterHook(state, skill.id, ownerId, "造成伤害后", async (ctx) => { await restoreArmor(ctx.state, ctx.atom.source); }); registerAfterHook(state, skill.id, ownerId, "被抵消", async (ctx) => { await restoreArmor(ctx.state, ctx.atom.source); }); return () => {}; } var tempUnloadByState; var init_青釭剑 = __esmMin((() => { init_skill(); init_lifecycle(); tempUnloadByState = /* @__PURE__ */ new WeakMap(); })); //#endregion //#region src/engine/skills/青龙偃月刀.ts var 青龙偃月刀_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$13, onInit: () => onInit$13, onMount: () => onMount$12 }); function createSkill$13(id, ownerId) { return { id, ownerId, name: "青龙偃月刀", description: "武器技:杀被闪抵消后,可对相同目标再使用一张杀" }; } function onInit$13(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "respond", (state, params) => { const slot = state.pendingSlots.get(ownerId); if (!slot) return "当前不需要回应"; if (slot.atom.type !== "请求回应") return "当前不需要回应"; const requestType = slot.atom.requestType; if (requestType !== "青龙偃月刀/confirm" && requestType !== "青龙偃月刀/useKill") return "当前不是青龙偃月刀询问"; if (requestType === "青龙偃月刀/useKill") { const cardId = params.cardId; if (!cardId) return "需要选择一张杀"; const self = state.players[ownerId]; if (!self) return "player not found"; if (!self.hand.includes(cardId)) return "牌不在手牌中"; if (state.cardMap[cardId]?.name !== "杀") return "只能使用杀"; } return null; }, async (state, params) => { const requestType = state.pendingSlots.get(ownerId).atom.requestType; if (requestType === "青龙偃月刀/confirm") state.localVars["青龙偃月刀/confirmed"] = params.choice === true || params.confirmed === true; else if (requestType === "青龙偃月刀/useKill") state.localVars["青龙偃月刀/killCardId"] = params.cardId; }); registerAfterHook(state, skill.id, ownerId, "被抵消", async (ctx) => { if (ctx.frame.skillId !== "杀") return; const atom = ctx.atom; if (atom.source !== ownerId) return; const self = ctx.state.players[ownerId]; if (!self) return; const weaponId = self.equipment["武器"]; if (!weaponId) return; if (ctx.state.cardMap[weaponId]?.name !== "青龙偃月刀") return; if (!isCancelled(ctx.state, atom.cardId, atom.target)) return; if (!self.hand.some((id) => ctx.state.cardMap[id]?.name === "杀")) return; delete ctx.state.localVars["青龙偃月刀/confirmed"]; await applyAtom(ctx.state, { type: "请求回应", requestType: "青龙偃月刀/confirm", target: ownerId, prompt: { type: "confirm", title: "青龙偃月刀:是否追杀?", confirmLabel: "追杀", cancelLabel: "不追杀" }, defaultChoice: false, timeout: 10 }); if (!ctx.state.localVars["青龙偃月刀/confirmed"]) return; delete ctx.state.localVars["青龙偃月刀/confirmed"]; delete ctx.state.localVars["青龙偃月刀/killCardId"]; await applyAtom(ctx.state, { type: "请求回应", requestType: "青龙偃月刀/useKill", target: ownerId, prompt: { type: "useCard", title: "青龙偃月刀:选择一张杀追杀", cardFilter: { filter: (c) => c.name === "杀", min: 1, max: 1 } }, defaultChoice: false, timeout: 15 }); const killCardId = ctx.state.localVars["青龙偃月刀/killCardId"]; delete ctx.state.localVars["青龙偃月刀/killCardId"]; if (!killCardId) return; await runUseFlow(ctx.state, ownerId, killCardId, [atom.target], "杀", { onSettle: chargeOnSettle(ctx.state, ownerId, killCardId) }); }); return () => {}; } function onMount$12(skill, api) { api.defineAction("respond", { label: "青龙偃月刀", style: "danger", prompt: { type: "confirm", title: "青龙偃月刀:是否追杀?", confirmLabel: "追杀", cancelLabel: "不追杀" } }); } var init_青龙偃月刀 = __esmMin((() => { init_apply(); init_skill(); init_frame(); init_use_card(); })); //#endregion //#region src/engine/skills/鞬出.ts var 鞬出_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$12, default: () => 鞬出_default, onInit: () => onInit$12, onMount: () => onMount$11 }); function createSkill$12(id, ownerId) { return { id, ownerId, name: "鞬出", description: "使用杀指定目标后,可弃其一张牌:装备牌则其不能出闪,非装备牌则其获得此杀" }; } /** 让目标从自己的手牌/装备区选一张牌弃置(博弈:装备→禁闪命中,非装备→获得杀不命中)。 */ async function askTargetToDiscard(state, target, killCardId) { const tp = state.players[target]; if (!tp) return; const equipment = Object.entries(tp.equipment).filter(([, id]) => typeof id === "string").map(([slot, id]) => ({ slot, cardId: id, cardName: state.cardMap[id]?.name ?? "?" })); const handCount = tp.hand.length; const defaultZone = equipment.length > 0 ? { zone: "equipment", cardId: equipment[0].cardId } : { zone: "hand", handIndex: 0 }; delete state.localVars[PICK]; await applyAtom(state, { type: "请求回应", requestType: "鞬出/选牌", target, prompt: { type: "pickTargetCard", title: "鞬出:选择弃置的一张牌(装备→不能出闪;非装备→获得此杀不命中)", target, equipment, judge: [], handCount }, defaultChoice: defaultZone, timeout: 20 }); const result = state.localVars[PICK]; delete state.localVars[PICK]; const zone = result?.zone ?? defaultZone.zone; let discardId; if (zone === "equipment") discardId = result?.cardId ?? defaultZone.cardId; else { const idx = result?.handIndex ?? 0; discardId = tp.hand[idx] ?? tp.hand[0]; } if (!discardId) return; await applyAtom(state, { type: "弃置", player: target, cardIds: [discardId] }); if (state.cardMap[discardId]?.type === "装备牌") await applyAtom(state, { type: "加标签", player: target, tag: TAG }); else { const gainMap = state.localVars[GAIN] ?? {}; gainMap[target] = killCardId; state.localVars[GAIN] = gainMap; } } function onInit$12(skill, state) { const ownerId = skill.ownerId; const unloaders = []; for (const p of state.players) { const seatId = p.index; const u = registerAction(state, skill.id, seatId, "respond", (st, params) => { const slot = st.pendingSlots.get(seatId); if (!slot) return "当前不需要回应"; const atom = slot.atom; if (atom.type !== "请求回应") return "当前不是请求回应"; if (atom.requestType === "鞬出/confirm") return null; if (atom.requestType === "鞬出/选牌") { const me = st.players[seatId]; if (!me) return "玩家不存在"; const zone = params.zone; if (zone === "equipment") { if (typeof params.cardId !== "string") return "cardId required"; if (!Object.values(me.equipment).includes(params.cardId)) return "该牌不在你的装备区"; return null; } if (zone === "hand") { if (typeof params.handIndex !== "number") return "handIndex required"; if (params.handIndex < 0 || params.handIndex >= me.hand.length) return "handIndex 越界"; return null; } return "zone required (equipment|hand)"; } return "当前不是鞬出回应"; }, async (st, params) => { const requestType = (st.pendingSlots.get(seatId)?.atom)?.requestType; if (requestType === "鞬出/confirm") st.localVars[CONFIRM] = params.choice === true || params.confirmed === true; else if (requestType === "鞬出/选牌") st.localVars[PICK] = { zone: params.zone, cardId: params.cardId ?? null, handIndex: params.handIndex ?? null }; }); unloaders.push(u); } registerAfterHook(state, skill.id, ownerId, "指定目标", async (ctx) => { const atom = ctx.atom; if (atom.source !== ownerId) return; if (atom.target === void 0) return; const target = atom.target; if (atom.cardId === void 0) return; if (ctx.state.cardMap[atom.cardId]?.name !== "杀") return; const targetPlayer = ctx.state.players[target]; if (!targetPlayer?.alive) return; if (!(targetPlayer.hand.length > 0 || Object.keys(targetPlayer.equipment).length > 0)) return; if (targetPlayer.tags.includes(TAG)) await applyAtom(ctx.state, { type: "去标签", player: target, tag: TAG }); delete ctx.state.localVars[CONFIRM]; await applyAtom(ctx.state, { type: "请求回应", requestType: "鞬出/confirm", target: ownerId, prompt: { type: "confirm", title: "鞬出:是否弃置目标一张牌?", confirmLabel: "发动", cancelLabel: "不发动" }, defaultChoice: false, timeout: 15 }); if (!ctx.state.localVars[CONFIRM]) return; await askTargetToDiscard(ctx.state, target, atom.cardId); }); registerBeforeHook(state, skill.id, ownerId, "成为目标", async (ctx) => { const atom = ctx.atom; if (atom.source !== ownerId) return; const target = atom.target; if (target === void 0) return; const gainMap = ctx.state.localVars[GAIN]; if (!gainMap || !(target in gainMap)) return; return { kind: "cancel" }; }); registerAfterHook(state, skill.id, ownerId, "移动牌", async (ctx) => { const gainMap = ctx.state.localVars[GAIN]; if (!gainMap) return; const atom = ctx.atom; if (atom.to?.zone !== "弃牌堆") return; const targetEntry = Object.entries(gainMap).find(([, cid]) => cid === atom.cardId); if (!targetEntry) return; const target = Number(targetEntry[0]); delete gainMap[target]; if (Object.keys(gainMap).length === 0) delete ctx.state.localVars[GAIN]; if (!ctx.state.players[target]?.alive) return; await applyAtom(ctx.state, { type: "移动牌", cardId: atom.cardId, from: { zone: "弃牌堆" }, to: { zone: "手牌", player: target } }); }); registerBeforeHook(state, skill.id, ownerId, "询问闪", async (ctx) => { const atom = ctx.atom; if (atom.source !== ownerId) return; const target = atom.target; if (target === void 0) return; if (!ctx.state.players[target]?.tags.includes(TAG)) return; await applyAtom(ctx.state, { type: "去标签", player: target, tag: TAG }); return { kind: "cancel" }; }); return () => { unloaders.forEach((u) => u()); }; } function onMount$11(_skill, api) { api.defineAction("respond", { label: "鞬出", style: "default", prompt: { type: "confirm", title: "鞬出:是否弃置目标一张牌?", confirmLabel: "发动", cancelLabel: "不发动" } }); } var TAG, CONFIRM, PICK, GAIN, 鞬出_default; var init_鞬出 = __esmMin((() => { init_apply(); init_skill(); TAG = "鞬出/禁闪"; CONFIRM = "鞬出/confirmed"; PICK = "鞬出/选牌"; GAIN = "鞬出/获得杀"; 鞬出_default = { createSkill: createSkill$12, onInit: onInit$12, onMount: onMount$11 }; })); //#endregion //#region src/engine/skills/颂威.ts var 颂威_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$11, default: () => 颂威_default, onInit: () => onInit$11, onMount: () => onMount$10 }); function createSkill$11(id, ownerId) { return { id, ownerId, name: "颂威", description: "主公技:其他魏势力角色判定牌黑色且生效后,你可以摸一张牌" }; } function onInit$11(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "respond", (st, _params) => { const slot = st.pendingSlots.get(ownerId); if (!slot) return "当前不需要回应"; const atom = slot.atom; if (atom["type"] !== "请求回应") return "当前不需要回应"; if (atom["requestType"] !== CONFIRM_RT$1) return "当前不是颂威确认"; return null; }, async (st, params) => { st.localVars[CONFIRMED_KEY$1] = params.choice === true || params.confirmed === true; }); registerAfterHook(state, skill.id, ownerId, "判定牌生效后", async (ctx) => { const atom = ctx.atom; if (atom.player === ownerId) return; const judgePlayer = ctx.state.players[atom.player ?? -1]; if (!judgePlayer?.alive) return; if (judgePlayer.faction !== "魏") return; const processing = frameCards(ctx.state); if (processing.length === 0) return; const judgeCardId = processing[processing.length - 1]; const judgeCard = ctx.state.cardMap[judgeCardId]; if (!judgeCard) return; if (judgeCard.suit !== "♠" && judgeCard.suit !== "♣") return; if (!ctx.state.players[ownerId]?.alive) return; delete ctx.state.localVars[CONFIRMED_KEY$1]; await applyAtom(ctx.state, { type: "请求回应", requestType: CONFIRM_RT$1, target: ownerId, prompt: { type: "confirm", title: `颂威:${judgePlayer.name} 判定牌为${judgeCard.suit}${judgeCard.rank},是否摸一张牌?`, confirmLabel: "摸牌", cancelLabel: "不摸" }, defaultChoice: false, timeout: 10 }); if (!ctx.state.localVars[CONFIRMED_KEY$1]) return; await applyAtom(ctx.state, { type: "摸牌", player: ownerId, count: 1 }); }); return () => {}; } function onMount$10(_skill, api) { api.defineAction("respond", { label: "颂威", style: "primary", prompt: { type: "confirm", title: "颂威:是否摸一张牌?", confirmLabel: "摸牌", cancelLabel: "不摸" } }); } var CONFIRM_RT$1, CONFIRMED_KEY$1, 颂威_default; var init_颂威 = __esmMin((() => { init_apply(); init_frame(); init_skill(); CONFIRM_RT$1 = "颂威/confirm"; CONFIRMED_KEY$1 = "颂威/confirmed"; 颂威_default = { createSkill: createSkill$11, onInit: onInit$11, onMount: onMount$10 }; })); //#endregion //#region src/engine/skills/飘零.ts var 飘零_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$10, default: () => 飘零_default, onInit: () => onInit$10, onMount: () => onMount$9 }); function currentRequestType(state, ownerId) { const slot = state.pendingSlots.get(ownerId); if (!slot) return void 0; return slot.atom.requestType; } function createSkill$10(id, ownerId) { return { id, ownerId, name: "飘零", description: "结束阶段可判定,红桃则将判定牌置牌堆顶或交给一名角色(给自己则弃一张牌)" }; } function onInit$10(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "respond", (st, params) => { const rt = currentRequestType(st, ownerId); if (rt !== CONFIRM_RT && rt !== GIVE_RT$1 && rt !== TARGET_RT$1 && rt !== DISCARD_RT) return "当前不是飘零询问"; if (rt === TARGET_RT$1) { const target = params.target; if (typeof target !== "number") return "请选择一名角色"; if (!st.players[target]?.alive) return "目标无效"; return null; } if (rt === DISCARD_RT) { const cardId = params.cardId; if (typeof cardId !== "string") return "请选择一张手牌弃置"; if (!st.players[ownerId]?.hand.includes(cardId)) return "牌不在手牌中"; } return null; }, async (st, params) => { const rt = currentRequestType(st, ownerId); const confirmed = params.choice === true || params.confirmed === true; if (rt === CONFIRM_RT) st.localVars[CONFIRMED_KEY] = confirmed; else if (rt === GIVE_RT$1) st.localVars[GIVE_KEY$1] = confirmed; else if (rt === TARGET_RT$1) st.localVars[TARGET_KEY$1] = params.target ?? null; else if (rt === DISCARD_RT) st.localVars[DISCARD_KEY] = params.cardId ?? null; }); registerAfterHook(state, skill.id, ownerId, "阶段开始", async (ctx) => { const atom = ctx.atom; if (atom.type !== "阶段开始") return; if (atom.player !== ownerId) return; if (atom.phase !== "回合结束") return; if (!ctx.state.players[ownerId]?.alive) return; if (ctx.state.zones.deck.length === 0) return; delete ctx.state.localVars[CONFIRMED_KEY]; await applyAtom(ctx.state, { type: "请求回应", requestType: CONFIRM_RT, target: ownerId, prompt: { type: "confirm", title: "是否发动飘零判定?", confirmLabel: "判定", cancelLabel: "不判定" }, defaultChoice: false, timeout: 10 }); if (!ctx.state.localVars[CONFIRMED_KEY]) return; await runJudgeFlow(ctx.state, ownerId, "飘零"); const dp = ctx.state.zones.discardPile; if (dp.length === 0) return; const judgeCardId = dp[dp.length - 1]; if (ctx.state.cardMap[judgeCardId]?.suit !== "♥") return; delete ctx.state.localVars[GIVE_KEY$1]; await applyAtom(ctx.state, { type: "请求回应", requestType: GIVE_RT$1, target: ownerId, prompt: { type: "confirm", title: "飘零:将判定牌交给一名角色?(否则置于牌堆顶)", confirmLabel: "交给角色", cancelLabel: "置牌堆顶" }, defaultChoice: false, timeout: 15 }); const wantGive = ctx.state.localVars[GIVE_KEY$1] === true; delete ctx.state.localVars[GIVE_KEY$1]; if (!wantGive) { await applyAtom(ctx.state, { type: "移动牌", cardId: judgeCardId, from: { zone: "弃牌堆" }, to: { zone: "牌堆" } }); return; } delete ctx.state.localVars[TARGET_KEY$1]; await applyAtom(ctx.state, { type: "请求回应", requestType: TARGET_RT$1, target: ownerId, prompt: { type: "choosePlayer", title: "飘零:选择交给哪名角色", min: 1, max: 1, filter: (view, t) => view.players[t]?.alive === true }, timeout: 15 }); const target = ctx.state.localVars[TARGET_KEY$1]; delete ctx.state.localVars[TARGET_KEY$1]; if (typeof target !== "number") { await applyAtom(ctx.state, { type: "移动牌", cardId: judgeCardId, from: { zone: "弃牌堆" }, to: { zone: "牌堆" } }); return; } if (!ctx.state.players[target]?.alive) return; await applyAtom(ctx.state, { type: "移动牌", cardId: judgeCardId, from: { zone: "弃牌堆" }, to: { zone: "手牌", player: target } }); if (target === ownerId) { const self = ctx.state.players[ownerId]; if (!self?.alive || self.hand.length === 0) return; delete ctx.state.localVars[DISCARD_KEY]; await applyAtom(ctx.state, { type: "请求回应", requestType: DISCARD_RT, target: ownerId, prompt: { type: "useCard", title: "飘零:弃置一张手牌", cardFilter: { filter: () => true, min: 1, max: 1 } }, timeout: 15 }); const discardCardId = ctx.state.localVars[DISCARD_KEY]; delete ctx.state.localVars[DISCARD_KEY]; if (typeof discardCardId === "string" && self.hand.includes(discardCardId)) await applyAtom(ctx.state, { type: "弃置", player: ownerId, cardIds: [discardCardId] }); } }); return () => {}; } function onMount$9(_skill, api) { api.defineAction("respond", { label: "飘零", style: "primary", prompt: { type: "confirm", title: "是否发动飘零?", confirmLabel: "发动", cancelLabel: "不发动" } }); } var CONFIRM_RT, GIVE_RT$1, TARGET_RT$1, DISCARD_RT, CONFIRMED_KEY, GIVE_KEY$1, TARGET_KEY$1, DISCARD_KEY, 飘零_default; var init_飘零 = __esmMin((() => { init_apply(); init_judge(); init_skill(); CONFIRM_RT = "飘零/confirm"; GIVE_RT$1 = "飘零/give"; TARGET_RT$1 = "飘零/chooseTarget"; DISCARD_RT = "飘零/discard"; CONFIRMED_KEY = "飘零/confirmed"; GIVE_KEY$1 = "飘零/give"; TARGET_KEY$1 = "飘零/target"; DISCARD_KEY = "飘零/discardCardId"; 飘零_default = { createSkill: createSkill$10, onInit: onInit$10, onMount: onMount$9 }; })); //#endregion //#region src/engine/skills/飞军.ts var 飞军_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$9, default: () => _skillModule$1, onInit: () => onInit$9, onMount: () => onMount$8 }); /** 计算玩家装备区牌数 */ function equipCount(equipment) { return Object.values(equipment).filter((id) => !!id).length; } /** 将手牌 cardId 列表转为 pickProcessingCard 的 cards 字段 */ function handToCards(state, hand) { return hand.map((id) => { const c = state.cardMap[id]; if (!c) return null; return { cardId: id, cardName: c.name, suit: c.suit, rank: c.rank }; }).filter((c) => c !== null); } function createSkill$9(id, ownerId) { return { id, ownerId, name: "飞军", description: "出牌阶段限一次,弃置一张牌,令一名手牌数大于你的角色交给你一张牌,或令一名装备区里牌数大于你的角色弃置一张装备" }; } function onInit$9(skill, state) { const ownerId = skill.ownerId; const unloaders = []; const useUnload = registerAction(state, skill.id, ownerId, "use", (st, params) => { if (st.currentPlayerIndex !== ownerId) return "只能在你的回合使用"; if (st.phase !== "出牌") return "只能在出牌阶段使用"; if (hasBlockingPending(st)) return "当前有未完成的询问"; if (usedThisTurn$3(st, ownerId, "飞军")) return "本回合已使用过飞军"; const self = st.players[ownerId]; if (!self?.alive) return "你已死亡"; const cardId = params.cardId; if (!cardId) return "请选择要弃置的牌"; if (!self.hand.includes(cardId)) return "弃置的牌必须在手牌中"; return null; }, async (st, params) => { const costCardId = params.cardId; await markOncePerTurn(st, ownerId, "飞军"); await pushFrame(st, "飞军", ownerId, { ...params }); await applyAtom(st, { type: "弃置", player: ownerId, cardIds: [costCardId], voluntary: true }); const self = st.players[ownerId]; if (!self?.alive) { await popFrame(st); return; } const ownerHandCount = self.hand.length; const ownerEquipCount = equipCount(self.equipment); const hasHandOption = st.players.some((p) => p.index !== ownerId && p.alive && p.hand.length > ownerHandCount); const hasEquipOption = st.players.some((p) => p.index !== ownerId && p.alive && equipCount(p.equipment) > ownerEquipCount); if (!hasHandOption && !hasEquipOption) { await popFrame(st); return; } let option; if (hasHandOption && hasEquipOption) { delete st.localVars[OPTION_KEY]; await applyAtom(st, { type: "请求回应", requestType: OPTION_RT, target: ownerId, prompt: { type: "chooseOption", title: "飞军:选择一项", options: [{ value: "hand", label: "令一名手牌数大于你的角色交给你一张牌" }, { value: "equip", label: "令一名装备区里牌数大于你的角色弃置一张装备" }] }, timeout: 30 }); option = st.localVars[OPTION_KEY] ?? "hand"; delete st.localVars[OPTION_KEY]; } else option = hasHandOption ? "hand" : "equip"; const candidates = []; for (const p of st.players) { if (p.index === ownerId || !p.alive) continue; if (option === "hand") { if (p.hand.length > ownerHandCount) candidates.push(p.index); } else if (equipCount(p.equipment) > ownerEquipCount) candidates.push(p.index); } if (candidates.length === 0) { await popFrame(st); return; } let target; if (candidates.length === 1) target = candidates[0]; else { delete st.localVars[TARGET_KEY]; await applyAtom(st, { type: "请求回应", requestType: TARGET_RT, target: ownerId, prompt: { type: "choosePlayer", title: option === "hand" ? "飞军:选择一名手牌数大于你的角色(交给你一张牌)" : "飞军:选择一名装备数大于你的角色(弃置一张装备)", min: 1, max: 1, candidates }, timeout: 30 }); target = st.localVars[TARGET_KEY] ?? candidates[0]; delete st.localVars[TARGET_KEY]; } if (st.players[ownerId]?.skills.includes("兵略")) { const binglueTargets = getBinglueTargets(st.players[ownerId].vars) ?? []; if (!binglueTargets.includes(target)) { st.players[ownerId].vars[BINGLUE_TARGETS_KEY] = [...binglueTargets, target]; await applyAtom(st, { type: "摸牌", player: ownerId, count: 2 }); } } const targetPlayer = st.players[target]; if (!targetPlayer?.alive) { await popFrame(st); return; } if (option === "hand") { if (targetPlayer.hand.length === 0) { await popFrame(st); return; } delete st.localVars[GIVE_KEY]; await applyAtom(st, { type: "请求回应", requestType: GIVE_RT, target, prompt: { type: "pickProcessingCard", title: `飞军:选择一张手牌交给 ${st.players[ownerId]?.name ?? "对方"}`, cards: handToCards(st, targetPlayer.hand) }, timeout: 30 }); let giveCardId = st.localVars[GIVE_KEY]; const targetHand = st.players[target]?.hand ?? []; if (!giveCardId || !targetHand.includes(giveCardId)) giveCardId = targetHand[0]; delete st.localVars[GIVE_KEY]; if (giveCardId && targetHand.includes(giveCardId)) await applyAtom(st, { type: "给予", cardId: giveCardId, from: target, to: ownerId }); } else { const equipIds = Object.values(targetPlayer.equipment).filter((id) => !!id); if (equipIds.length === 0) { await popFrame(st); return; } delete st.localVars[DISCARD_EQUIP_KEY]; await applyAtom(st, { type: "请求回应", requestType: DISCARD_EQUIP_RT, target, prompt: { type: "pickProcessingCard", title: "飞军:选择一张装备牌弃置", cards: handToCards(st, equipIds) }, timeout: 30 }); let discardCardId = st.localVars[DISCARD_EQUIP_KEY]; const currentEquips = Object.values(st.players[target]?.equipment ?? {}).filter((id) => !!id); if (!discardCardId || !currentEquips.includes(discardCardId)) discardCardId = currentEquips[0]; delete st.localVars[DISCARD_EQUIP_KEY]; if (discardCardId && currentEquips.includes(discardCardId)) await applyAtom(st, { type: "弃置", player: target, cardIds: [discardCardId], voluntary: true }); } await popFrame(st); }); unloaders.push(useUnload); for (const p of state.players) { const seatId = p.index; const u = registerAction(state, skill.id, seatId, "respond", (st, params) => { const slot = st.pendingSlots.get(seatId); if (!slot) return "当前不需要回应"; const atom = slot.atom; if (atom.type !== "请求回应") return "当前不需要回应"; const rt = atom.requestType; if (rt === OPTION_RT) { if (params.option !== "hand" && params.option !== "equip") return "请选择一项"; return null; } if (rt === TARGET_RT) { if (typeof (params.targets?.[0] ?? (typeof params.target === "number" ? params.target : void 0)) !== "number") return "请选择目标"; return null; } if (rt === GIVE_RT) { const cardId = params.cardId; if (!cardId) return "请选择一张手牌"; if (!st.players[seatId].hand.includes(cardId)) return "牌不在手牌中"; return null; } if (rt === DISCARD_EQUIP_RT) { const cardId = params.cardId; if (!cardId) return "请选择一张装备"; if (!Object.values(st.players[seatId].equipment).includes(cardId)) return "牌不在装备区"; return null; } return "当前不是飞军询问"; }, async (st, params) => { const rt = (st.pendingSlots.get(seatId)?.atom)?.requestType; if (rt === OPTION_RT) st.localVars[OPTION_KEY] = params.option; else if (rt === TARGET_RT) { const t = params.targets?.[0] ?? (typeof params.target === "number" ? params.target : void 0); if (typeof t === "number") st.localVars[TARGET_KEY] = t; } else if (rt === GIVE_RT) st.localVars[GIVE_KEY] = params.cardId; else if (rt === DISCARD_EQUIP_RT) st.localVars[DISCARD_EQUIP_KEY] = params.cardId; }); unloaders.push(u); } return () => { unloaders.forEach((u) => u()); }; } function onMount$8(skill, api) { api.defineAction("use", { label: "飞军", style: "primary", prompt: { type: "useCard", title: "飞军:弃置一张牌,令手牌数或装备数多于你的角色交牌或弃装备", cardFilter: { filter: () => true, min: 1, max: 1 } }, activeWhen: (ctx) => { if (!activeUnlessUsedThisTurn("飞军")(ctx)) return false; const p = ctx.view.players[ctx.perspectiveIdx]; if (!p) return false; if ((p.hand?.length ?? 0) === 0) return false; const ownerHand = p.handCount; const ownerEquip = Object.values(p.equipment ?? {}).filter((id) => !!id).length; const hasOption1 = ctx.view.players.some((other) => other.index !== skill.ownerId && other.alive && (other.handCount ?? 0) > ownerHand); const hasOption2 = ctx.view.players.some((other) => { if (other.index === skill.ownerId || !other.alive) return false; return Object.values(other.equipment ?? {}).filter((id) => !!id).length > ownerEquip; }); return hasOption1 || hasOption2; } }); api.defineAction("respond", { label: "飞军", style: "primary", prompt: { type: "chooseOption", title: "飞军", options: [] } }); return () => {}; } var OPTION_RT, TARGET_RT, GIVE_RT, DISCARD_EQUIP_RT, OPTION_KEY, TARGET_KEY, GIVE_KEY, DISCARD_EQUIP_KEY, _skillModule$1; var init_飞军 = __esmMin((() => { init_apply(); init_frame(); init_once_per_turn(); init_skill(); init_vars_keys(); OPTION_RT = "飞军/option"; TARGET_RT = "飞军/target"; GIVE_RT = "飞军/giveCard"; DISCARD_EQUIP_RT = "飞军/discardEquip"; OPTION_KEY = "飞军/option"; TARGET_KEY = "飞军/target"; GIVE_KEY = "飞军/giveCardId"; DISCARD_EQUIP_KEY = "飞军/discardEquipCardId"; _skillModule$1 = { createSkill: createSkill$9, onInit: onInit$9, onMount: onMount$8 }; })); //#endregion //#region src/engine/skills/马匹技能.ts var 马匹技能_exports = /* @__PURE__ */ __exportAll({ createMountSkill: () => createMountSkill, 大宛: () => 大宛, 爪黄飞电: () => 爪黄飞电, 的卢: () => 的卢, 紫骍: () => 紫骍, 绝影: () => 绝影, 赤兔: () => 赤兔, 骅骝: () => 骅骝 }); /** * 创建一个马匹技能模块(进攻马或防御马)。 * @param name 卡牌名(也是 skillId),如 '赤兔' * @param kind '进攻' | '防御' * @param desc 技能描述 */ function createMountSkill(name, kind, desc) { const key = VAR_KEY[kind]; return { createSkill(id, ownerId) { return { id, ownerId, name, description: desc }; }, onInit(skill, state) { const ownerId = skill.ownerId; state.players[ownerId].vars[key] = 1; return () => { delete state.players[ownerId]?.vars[key]; }; } }; } var VAR_KEY, 赤兔, 紫骍, 大宛, 的卢, 绝影, 爪黄飞电, 骅骝; var init_马匹技能 = __esmMin((() => { init_vars_keys(); VAR_KEY = { 进攻: DISTANCE_ATTACK_MOD_KEY, 防御: DISTANCE_DEFENSE_MOD_KEY }; 赤兔 = createMountSkill("赤兔", "进攻", "进攻马:你与其他角色的距离-1"); 紫骍 = createMountSkill("紫骍", "进攻", "进攻马:你与其他角色的距离-1"); 大宛 = createMountSkill("大宛", "进攻", "进攻马:你与其他角色的距离-1"); 的卢 = createMountSkill("的卢", "防御", "防御马:其他角色与你的距离+1"); 绝影 = createMountSkill("绝影", "防御", "防御马:其他角色与你的距离+1"); 爪黄飞电 = createMountSkill("爪黄飞电", "防御", "防御马:其他角色与你的距离+1"); 骅骝 = createMountSkill("骅骝", "防御", "防御马:其他角色与你的距离+1"); })); //#endregion //#region src/engine/skills/马术.ts var 马术_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$8, onInit: () => onInit$8 }); function createSkill$8(id, ownerId) { return { id, ownerId, name: "马术", description: "锁定技:你与其他角色的距离-1", isLocked: true }; } function onInit$8(skill, state) { const ownerId = skill.ownerId; state.players[ownerId].vars[DISTANCE_ATTACK_MOD_KEY] = 1; return () => { delete state.players[ownerId]?.vars[DISTANCE_ATTACK_MOD_KEY]; }; } var init_马术 = __esmMin((() => { init_vars_keys(); })); //#endregion //#region src/engine/skills/驱虎.ts var 驱虎_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$7, onInit: () => onInit$7, onMount: () => onMount$7 }); /** 拼点牌点数:A=1, 2-10=面值, J=11, Q=12, K=13 */ function rankValue(rank) { if (rank === "A") return 1; if (rank === "J") return 11; if (rank === "Q") return 12; if (rank === "K") return 13; const n = parseInt(rank, 10); return Number.isFinite(n) ? n : 0; } function createSkill$7(id, ownerId) { return { id, ownerId, name: "驱虎", description: "出牌阶段限一次,与一名角色拼点:赢则该角色对其攻击范围内另一角色造成1伤害,没赢则其对你造成1伤害" }; } function onInit$7(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "use", (state, params) => { if (state.currentPlayerIndex !== ownerId) return "不是你的回合"; if (state.phase !== "出牌") return "只能在出牌阶段发动"; if (usedThisTurn$3(state, ownerId, "驱虎")) return "本回合已使用过驱虎"; const self = state.players[ownerId]; if (!self?.alive) return "玩家不存在或已死亡"; const cardId = params.cardId; if (typeof cardId !== "string") return "需要选择拼点牌"; if (!self.hand.includes(cardId)) return "拼点牌不在手牌中"; const target = params.target; if (typeof target !== "number") return "需要选择拼点目标"; if (target === ownerId) return "不能与自己拼点"; const targetPlayer = state.players[target]; if (!targetPlayer?.alive) return "目标不存在或已死亡"; if (targetPlayer.hand.length === 0) return "目标没有手牌,无法拼点"; return null; }, async (state, params) => { const from = ownerId; const initiatorCardId = params.cardId; const target = params.target; await markOncePerTurn(state, from, "驱虎"); await pushFrame(state, "驱虎", from, { ...params }); const initiatorCard = state.cardMap[initiatorCardId]; const initiatorValue = initiatorCard ? rankValue(initiatorCard.rank) : 0; delete state.localVars["驱虎/targetCard"]; await applyAtom(state, { type: "请求回应", requestType: "驱虎/拼点", target, prompt: { type: "useCard", title: `驱虎:与 ${state.players[from].name} 拼点,请出一张手牌`, cardFilter: { filter: () => true, min: 1, max: 1 } }, timeout: 30 }); const targetCardId = state.localVars["驱虎/targetCard"]; delete state.localVars["驱虎/targetCard"]; let win; if (targetCardId && state.players[target].hand.includes(targetCardId)) win = await runRankCompareFlow(state, from, target, initiatorCardId, targetCardId) === "赢"; else { await applyAtom(state, { type: "移动牌", cardId: initiatorCardId, from: { zone: "手牌", player: from }, to: { zone: "弃牌堆" } }); win = initiatorValue > 0; } if (win) { if (state.players.filter((p) => p.alive && p.index !== target && inAttackRange(state, target, p.index)).map((p) => p.index).length === 0) { await popFrame(state); return; } delete state.localVars["驱虎/victim"]; await applyAtom(state, { type: "请求回应", requestType: "驱虎/选目标", target: from, prompt: { type: "choosePlayer", title: `驱虎:选择 ${state.players[target].name} 攻击范围内的一名角色,其将受到1点伤害`, min: 1, max: 1, filter: (view, t) => t !== from && view.players[t]?.alive === true }, timeout: 30 }); const victim = state.localVars["驱虎/victim"]; delete state.localVars["驱虎/victim"]; if (typeof victim === "number" && state.players[victim]?.alive && victim !== target) { if (inAttackRange(state, target, victim)) await runDamageFlow(state, target, victim, 1); } } else await runDamageFlow(state, target, from, 1); await popFrame(state); }); for (const p of state.players) { const pid = p.index; registerAction(state, skill.id, pid, "respond", (st, params) => { const slot = st.pendingSlots.get(pid); if (!slot) return "当前不需要回应"; const atom = slot.atom; if (atom.type !== "请求回应") return "当前不需要回应"; const reqType = atom.requestType; if (reqType === "驱虎/拼点") { const cardId = params.cardId; if (typeof cardId !== "string") return "请选择一张拼点牌"; if (!st.players[pid].hand.includes(cardId)) return "拼点牌不在手牌中"; return null; } if (reqType === "驱虎/选目标") { if (pid !== ownerId) return "只有驱虎发动者可以选择目标"; if (typeof params.target !== "number") return "请选择一名目标"; return null; } return "当前不是驱虎回应"; }, async (st, params) => { const slot = st.pendingSlots.get(pid); if (!slot) return; const reqType = slot.atom.requestType; if (reqType === "驱虎/拼点") st.localVars["驱虎/targetCard"] = params.cardId; else if (reqType === "驱虎/选目标") st.localVars["驱虎/victim"] = params.target; }); } return () => {}; } function onMount$7(skill, api) { api.defineAction("use", { label: "驱虎", style: "danger", prompt: { type: "useCardAndTarget", title: "驱虎:选择一张拼点牌和一名目标", cardFilter: { filter: () => true, min: 1, max: 1 }, targetFilter: { min: 1, max: 1, filter: (view, t) => { if (t === view.currentPlayerIndex) return false; const tp = view.players[t]; if (!tp) return false; return tp.alive !== false && (tp.handCount ?? 0) > 0; } } }, activeWhen: (ctx) => activeUnlessUsedThisTurn("驱虎")(ctx) && (ctx.view.players[ctx.perspectiveIdx]?.hand?.length ?? 0) > 0 }); api.defineAction("respond", { label: "驱虎", style: "primary", prompt: { type: "useCard", title: "驱虎:请出一张拼点牌", cardFilter: { filter: () => true, min: 1, max: 1 } } }); } var init_驱虎 = __esmMin((() => { init_apply(); init_frame(); init_damage(); init_rank(); init_once_per_turn(); init_skill(); init_distance(); })); //#endregion //#region src/engine/skills/鬼才.ts var 鬼才_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$6, onInit: () => onInit$6, onMount: () => onMount$6 }); function createSkill$6(id, ownerId) { return { id, ownerId, name: "鬼才", description: "判定牌生效前,你可以打出一张手牌代替之" }; } function onInit$6(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "respond", (st, params) => { const slot = st.pendingSlots.get(ownerId); if (!slot) return "当前不需要回应"; const atom = slot.atom; if (atom.type !== "请求回应") return "当前不需要回应"; if (atom.requestType !== "鬼才/replace") return "当前不是鬼才询问"; if (params.choice === true || params.confirmed === true || typeof params.cardId === "string") { const cardId = params.cardId; if (typeof cardId !== "string") return "请选择一张替换牌"; if (!st.players[ownerId].hand.includes(cardId)) return "替换牌不在手牌中"; } return null; }, async (st, params) => { if (!(params.choice === true || params.confirmed === true || typeof params.cardId === "string")) { st.localVars["鬼才/replaceCard"] = null; return; } st.localVars["鬼才/replaceCard"] = params.cardId ?? null; }); registerJudgeModifier(state, skill.id, ownerId, async (ctx) => { if (ctx.atom.type !== "判定牌生效前") return; const me = ctx.state.players[ownerId]; if (!me?.alive) return; if (me.hand.length === 0) return; if (frameCards(ctx.state).length === 0) return; delete ctx.state.localVars["鬼才/replaceCard"]; await applyAtom(ctx.state, { type: "请求回应", requestType: "鬼才/replace", target: ownerId, prompt: { type: "useCard", title: "鬼才:是否打出一张手牌代替判定牌?", cardFilter: { filter: () => true, min: 1, max: 1 } }, defaultChoice: false, timeout: 15 }); const replaceCardId = ctx.state.localVars["鬼才/replaceCard"]; delete ctx.state.localVars["鬼才/replaceCard"]; if (!replaceCardId) return; if (!me.hand.includes(replaceCardId)) return; await replaceJudgeCard(ctx.state, ownerId, replaceCardId); }); return () => {}; } function onMount$6(_skill, api) { api.defineAction("respond", { label: "鬼才", style: "primary", prompt: { type: "useCard", title: "鬼才:选择一张手牌代替判定牌", cardFilter: { filter: () => true, min: 1, max: 1 } } }); } var init_鬼才 = __esmMin((() => { init_apply(); init_frame(); init_skill(); init_judge(); })); //#endregion //#region src/engine/skills/鬼道.ts var 鬼道_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$5, default: () => 鬼道_default, onInit: () => onInit$5, onMount: () => onMount$5 }); /** 判断一张牌是否为黑色(♠或♣) */ function isBlackCard(state, cardId) { const card = state.cardMap[cardId]; return !!card && card.color === "黑"; } function createSkill$5(id, ownerId) { return { id, ownerId, name: "鬼道", description: "判定牌生效前,你可以用一张黑色牌替换之" }; } function onInit$5(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "respond", (st, params) => { const slot = st.pendingSlots.get(ownerId); if (!slot) return "当前不需要回应"; const atom = slot.atom; if (atom["type"] !== "请求回应") return "当前不需要回应"; if (atom["requestType"] !== REPLACE_RT$1) return "当前不是鬼道询问"; if (params.choice === true || params.confirmed === true || typeof params.cardId === "string") { const cardId = params.cardId; if (typeof cardId !== "string") return "请选择一张替换牌"; if (!st.players[ownerId].hand.includes(cardId)) return "替换牌不在手牌中"; if (!isBlackCard(st, cardId)) return "鬼道只能用黑色牌替换"; } return null; }, async (st, params) => { if (!(params.choice === true || params.confirmed === true || typeof params.cardId === "string")) { st.localVars[REPLACE_CARD_KEY] = null; return; } st.localVars[REPLACE_CARD_KEY] = params.cardId ?? null; }); registerJudgeModifier(state, skill.id, ownerId, async (ctx) => { if (ctx.atom.type !== "判定牌生效前") return; const me = ctx.state.players[ownerId]; if (!me?.alive) return; if (!me.hand.some((id) => isBlackCard(ctx.state, id))) return; if (frameCards(ctx.state).length === 0) return; delete ctx.state.localVars[REPLACE_CARD_KEY]; await applyAtom(ctx.state, { type: "请求回应", requestType: REPLACE_RT$1, target: ownerId, prompt: { type: "useCard", title: "鬼道:是否打出一张黑色牌代替判定牌?", cardFilter: { filter: (c) => c.color === "黑", min: 1, max: 1 } }, defaultChoice: false, timeout: 15 }); const replaceCardId = ctx.state.localVars[REPLACE_CARD_KEY]; delete ctx.state.localVars[REPLACE_CARD_KEY]; if (!replaceCardId) return; if (!me.hand.includes(replaceCardId)) return; if (!isBlackCard(ctx.state, replaceCardId)) return; await replaceJudgeCard(ctx.state, ownerId, replaceCardId); }); return () => {}; } function onMount$5(_skill, api) { api.defineAction("respond", { label: "鬼道", style: "primary", prompt: { type: "useCard", title: "鬼道:选择一张黑色牌代替判定牌", cardFilter: { filter: (c) => c.color === "黑", min: 1, max: 1 } } }); } var REPLACE_RT$1, REPLACE_CARD_KEY, 鬼道_default; var init_鬼道 = __esmMin((() => { init_apply(); init_frame(); init_skill(); init_judge(); REPLACE_RT$1 = "鬼道/replace"; REPLACE_CARD_KEY = "鬼道/replaceCard"; 鬼道_default = { createSkill: createSkill$5, onInit: onInit$5, onMount: onMount$5 }; })); //#endregion //#region src/engine/skills/魂姿.ts var 魂姿_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$4, default: () => 魂姿_default, onInit: () => onInit$4, onMount: () => onMount$4 }); function createSkill$4(id, ownerId) { return { id, ownerId, name: "魂姿", description: "觉醒技:准备阶段且体力为1时,减1体力上限并永久获得\"英姿\"和\"英魂\"" }; } function onInit$4(skill, state) { const ownerId = skill.ownerId; registerAfterHook(state, skill.id, ownerId, "阶段开始", async (ctx) => { const atom = ctx.atom; if (atom.type !== "阶段开始") return; if (atom.player !== ownerId) return; if (atom.phase !== "准备") return; if (getHunziAwakened(ctx.state.players[ownerId]?.vars)) return; const self = ctx.state.players[ownerId]; if (!self?.alive) return; if (self.health !== 1) return; ctx.state.players[ownerId].vars[HUNZI_AWAKENED_KEY] = true; await applyAtom(ctx.state, { type: "设上限", player: ownerId, amount: self.maxHealth - 1 }); await applyAtom(ctx.state, { type: "添加技能", player: ownerId, skillId: "英姿" }); await applyAtom(ctx.state, { type: "添加技能", player: ownerId, skillId: "英魂" }); await performYinghunPrepare(ctx.state, ownerId); }); return () => {}; } function onMount$4(_skill, _api) {} var 魂姿_default; var init_魂姿 = __esmMin((() => { init_apply(); init_skill(); init_英魂(); init_vars_keys(); 魂姿_default = { createSkill: createSkill$4, onInit: onInit$4, onMount: onMount$4 }; })); //#endregion //#region src/engine/skills/鸿举.ts var 鸿举_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$3, default: () => _skillModule, onInit: () => onInit$3, onMount: () => onMount$3 }); function createSkill$3(id, ownerId) { return { id, ownerId, name: "鸿举", description: "觉醒技:准备阶段若荣≥3,用任意手牌替换等量的荣,减1体力上限并获得\"清侧\"" }; } function onInit$3(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "respond", (st, params) => { const slot = st.pendingSlots.get(ownerId); if (!slot) return "当前不需要回应"; const atom = slot.atom; if (atom.type !== "请求回应" || atom.requestType !== REPLACE_RT) return "当前不是鸿举换荣"; const cardIds = params.cardIds; if (Array.isArray(cardIds)) { const hand = new Set(st.players[ownerId].hand); const seen = /* @__PURE__ */ new Set(); for (const id of cardIds) { if (typeof id !== "string") return "cardId 必须为字符串"; if (!hand.has(id)) return "牌不在手牌中"; if (seen.has(id)) return "存在重复的牌"; seen.add(id); } const rc = st.players[ownerId].marks.filter((m) => m.id.startsWith(RONG_PREFIX)).length; if (cardIds.length > rc) return "替换数量超过荣数"; } return null; }, async (st, params) => { st.localVars[REPLACE_KEY] = params.cardIds ?? []; }); registerAfterHook(state, skill.id, ownerId, "阶段开始", async (ctx) => { const atom = ctx.atom; if (atom.type !== "阶段开始") return; if (atom.player !== ownerId) return; if (atom.phase !== "准备") return; if (ctx.state.players[ownerId]?.vars[AWAKENED_KEY]) return; const self = ctx.state.players[ownerId]; if (!self?.alive) return; const rc = rongCount(ctx.state, ownerId); if (rc < 3) return; ctx.state.players[ownerId].vars[AWAKENED_KEY] = true; const maxReplace = Math.min(self.hand.length, rc); if (maxReplace > 0) { delete ctx.state.localVars[REPLACE_KEY]; await applyAtom(ctx.state, { type: "请求回应", requestType: REPLACE_RT, target: ownerId, prompt: { type: "distribute", mode: "select", title: "鸿举:选择任意手牌替换等量的荣(可不选)", source: "hand", minTotal: 0, maxTotal: maxReplace }, defaultChoice: [], timeout: 20 }); const selected = ctx.state.localVars[REPLACE_KEY] ?? []; const k = Math.min(selected.length, rc); const oldRong = self.marks.filter((m) => m.id.startsWith(RONG_PREFIX)); for (let i = 0; i < k; i++) await applyAtom(ctx.state, { type: "去标记", player: ownerId, markId: oldRong[i].id }); for (const cardId of selected.slice(0, k)) { await applyAtom(ctx.state, { type: "弃置", player: ownerId, cardIds: [cardId] }); await applyAtom(ctx.state, { type: "加标记", player: ownerId, mark: { id: `${RONG_PREFIX}${ctx.state.seq}`, scope: ownerId, payload: { cardId } } }); } } await applyAtom(ctx.state, { type: "设上限", player: ownerId, amount: self.maxHealth - 1 }); await applyAtom(ctx.state, { type: "添加技能", player: ownerId, skillId: "清侧" }); }); return () => {}; } function onMount$3(_skill, _api) {} var AWAKENED_KEY, REPLACE_RT, REPLACE_KEY, _skillModule; var init_鸿举 = __esmMin((() => { init_apply(); init_skill(); init_征荣(); AWAKENED_KEY = "鸿举/awakened"; REPLACE_RT = "鸿举/换荣"; REPLACE_KEY = "鸿举/换荣结果"; _skillModule = { createSkill: createSkill$3, onInit: onInit$3, onMount: onMount$3 }; })); //#endregion //#region src/engine/skills/麒麟弓.ts var 麒麟弓_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$2, onInit: () => onInit$2, onMount: () => onMount$2 }); function createSkill$2(id, ownerId) { return { id, ownerId, name: "麒麟弓", description: "武器:杀造成伤害时可弃目标1匹马", isLocked: true }; } function onInit$2(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "respond", (state, params) => { const slot = state.pendingSlots.get(ownerId); if (!slot) return "当前不需要回应"; if (slot.atom.type !== "请求回应") return "当前不是麒麟弓窗口"; if (slot.atom.requestType !== "麒麟弓/confirm") return "当前不是麒麟弓窗口"; return null; }, async (state, params) => { state.localVars["麒麟弓/confirmed"] = params.choice === true || params.confirmed === true; }); registerBeforeHook(state, skill.id, ownerId, "造成伤害时", async (ctx) => { const atom = ctx.atom; if (atom.source !== ownerId) return; const self = ctx.state.players[ownerId]; if (!self) return; const weaponId = self.equipment["武器"]; if (!weaponId) return; if (ctx.state.cardMap[weaponId]?.name !== "麒麟弓") return; const damageCardId = atom.cardId; if (!damageCardId) return; if (ctx.state.cardMap[damageCardId]?.name !== "杀") return; const targetIdx = atom.target; if (typeof targetIdx !== "number") return; const target = ctx.state.players[targetIdx]; if (!target) return; const ownedMounts = ["进攻马", "防御马"].filter((slot) => !!target.equipment?.[slot]); if (ownedMounts.length === 0) return; delete ctx.state.localVars["麒麟弓/confirmed"]; await applyAtom(ctx.state, { type: "请求回应", requestType: "麒麟弓/confirm", target: ownerId, prompt: { type: "confirm", title: "麒麟弓:是否弃目标1匹马?", confirmLabel: "弃马", cancelLabel: "不发动" }, timeout: 10 }); if (!ctx.state.localVars["麒麟弓/confirmed"]) { delete ctx.state.localVars["麒麟弓/confirmed"]; return; } const mountId = target.equipment[ownedMounts[0]]; if (mountId) await applyAtom(ctx.state, { type: "弃置", player: targetIdx, cardIds: [mountId] }); delete ctx.state.localVars["麒麟弓/confirmed"]; }); return () => {}; } function onMount$2(skill, api) { api.defineAction("respond", { label: "麒麟弓", style: "default", prompt: { type: "confirm", title: "麒麟弓:是否弃目标1匹马?", confirmLabel: "弃马", cancelLabel: "不发动" } }); } var init_麒麟弓 = __esmMin((() => { init_apply(); init_skill(); })); //#endregion //#region src/engine/skills/黄天.ts var 黄天_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill$1, default: () => 黄天_default, onInit: () => onInit$1, onMount: () => onMount$1 }); /** 判断一张牌是否为黄天可交的牌(闪或闪电) */ function isGiveableCard(state, cardId) { const name = state.cardMap[cardId]?.name; return name === "闪" || name === "闪电"; } function createSkill$1(id, ownerId) { return { id, ownerId, name: "黄天", description: "主公技:其他群势力角色可在出牌阶段将一张【闪】或【闪电】交给你" }; } function onInit$1(skill, state) { const ownerId = skill.ownerId; for (const p of state.players) { const allyIdx = p.index; if (allyIdx === ownerId) continue; if (p.faction !== "群") continue; registerAction(state, skill.id, allyIdx, "use", (st, params) => { if (ownerId !== 0) return "黄天为主公技,张角非主公"; if (!st.players[ownerId]?.alive) return "主公不存在或已死亡"; const self = st.players[allyIdx]; if (!self?.alive) return "已死亡"; const myTurn = st.currentPlayerIndex === allyIdx; const inActPhase = st.phase === "出牌"; const free = !hasBlockingPending(st); if (!myTurn || !inActPhase || !free) return "现在不能使用黄天"; if (usedThisTurn$3(st, allyIdx, "黄天")) return "本回合已使用过黄天"; const cardId = params.cardId; if (typeof cardId !== "string") return "请选择一张牌"; if (!self.hand.includes(cardId)) return "牌不在手牌中"; if (!isGiveableCard(st, cardId)) return "黄天只能交出【闪】或【闪电】"; return null; }, async (st, params) => { const cardId = params.cardId; await markOncePerTurn(st, allyIdx, "黄天"); await applyAtom(st, { type: "移动牌", cardId, from: { zone: "手牌", player: allyIdx }, to: { zone: "手牌", player: ownerId } }); }); } return () => {}; } function onMount$1(skill, api) { const ownerId = skill.ownerId; api.defineAction("use", { label: "黄天", style: "primary", prompt: { type: "useCard", title: "黄天:交出一张【闪】或【闪电】给主公", cardFilter: { filter: (c) => c.name === "闪" || c.name === "闪电", min: 1, max: 1 } }, activeWhen: (ctx) => ctx.perspectiveIdx !== ownerId && activeUnlessUsedThisTurn("黄天")(ctx) && ctx.view.players[ctx.perspectiveIdx]?.hand?.some((c) => c.name === "闪" || c.name === "闪电") === true }); } var 黄天_default; var init_黄天 = __esmMin((() => { init_apply(); init_once_per_turn(); init_skill(); 黄天_default = { createSkill: createSkill$1, onInit: onInit$1, onMount: onMount$1 }; })); //#endregion //#region src/engine/skills/龙胆.ts var 龙胆_exports = /* @__PURE__ */ __exportAll({ createSkill: () => createSkill, onInit: () => onInit, onMount: () => onMount }); function createSkill(id, ownerId) { return { id, ownerId, name: "龙胆", description: "你可以将一张【杀】当【闪】使用或打出,或将一张【闪】当【杀】使用或打出" }; } /** 影子卡 id:${原id}#龙胆(单卡转化,同一张牌只可能转一个方向) */ function shadowIdOf(cardId) { return `${cardId}#龙胆`; } function onInit(skill, state) { const ownerId = skill.ownerId; registerAction(state, skill.id, ownerId, "transform", (state, params) => { const self = state.players[ownerId]; const selfAlive = self?.alive === true; const cardId = params.cardId; const cardIdOk = typeof cardId === "string"; const card = cardIdOk ? state.cardMap[cardId] : void 0; const cardInHand = cardIdOk && self.hand.includes(cardId); if (!selfAlive) return "你已死亡"; if (!cardIdOk || !cardInHand) return "牌不在手牌中"; const expectedTo = card ? TRANSFORM_MAP[card.name] : void 0; const to = params.to ?? expectedTo; if (!expectedTo) return "只能将杀当闪或将闪当杀"; if (to !== expectedTo) { if (to === "闪") return "只能将杀当闪"; if (to === "杀") return "只能将闪当杀"; return "无效的转化方向"; } return null; }, async (state, params) => { const cardId = params.cardId; const card = state.cardMap[cardId]; const to = params.to ?? (card ? TRANSFORM_MAP[card.name] : void 0); if (!to) return; const shadowId = shadowIdOf(cardId); await applyAtom(state, { type: "当作", player: ownerId, cardIds: [cardId], shadowId, outputName: to }); }, (state, params) => { const cardId = params.cardId; const sId = shadowIdOf(cardId); delete state.cardMap[sId]; const self = state.players[ownerId]; const idx = self.hand.indexOf(sId); if (idx >= 0) self.hand[idx] = cardId; }); const unloadDodge = declareAlternativeResponse(state, ownerId, "询问闪"); const unloadSlash = declareAlternativeResponse(state, ownerId, "询问杀"); return () => { unloadDodge(); unloadSlash(); }; } function onMount(skill, api) { api.defineAction("transform", { label: "龙胆", style: "passive", prompt: { type: "useCardAndTarget", title: "选择一张杀或闪转化", cardFilter: { filter: (c) => c.name === "杀" || c.name === "闪", min: 1, max: 1 }, targetFilter: { min: 1, max: 3, filter: (view, t) => { const me = view.currentPlayerIndex; return view.players.some((p, i) => i === me) && t !== me && view.players[t]?.alive; } } }, transform: (card) => ({ name: card.name === "杀" ? "闪" : "杀", sourceCardId: card.id, fromSkill: skill.id }), activeWhen: (ctx) => { const p = ctx.view.players[ctx.perspectiveIdx]; if (!p?.alive) return false; if (!(p.hand?.some((c) => c.name === "杀" || c.name === "闪") ?? false)) return false; const ownTurnCanSlash = defaultPlayActive(ctx) && viewCanSlash(ctx.view, ctx.perspectiveIdx); const slot = ctx.view.pending; const askedDodge = !!slot && slot.atom.type === "询问闪" && slot.target === ctx.perspectiveIdx; const askedKill = !!slot && slot.atom.type === "询问杀" && slot.target === ctx.perspectiveIdx; return ownTurnCanSlash || askedDodge || askedKill; } }); } var TRANSFORM_MAP; var init_龙胆 = __esmMin((() => { init_skill(); init_apply(); init_action_active(); TRANSFORM_MAP = { 杀: "闪", 闪: "杀" }; })); //#endregion //#region src/engine/skills/vite-glob.ts var vite_glob_exports = /* @__PURE__ */ __exportAll({ default: () => vite_glob_default }); var vite_glob_default; var init_vite_glob = __esmMin((() => { vite_glob_default = /* @__PURE__ */ Object.assign({ "./cards/index.ts": () => Promise.resolve().then(() => (init_cards(), cards_exports)), "./cards/play-card.ts": () => Promise.resolve().then(() => (init_play_card(), play_card_exports)), "./cards/use-card.ts": () => Promise.resolve().then(() => (init_use_card(), use_card_exports)), "./cards/validate.ts": () => Promise.resolve().then(() => (init_validate(), validate_exports)), "./cards/万箭齐发.ts": () => Promise.resolve().then(() => (init_万箭齐发(), 万箭齐发_exports)), "./cards/乐不思蜀.ts": () => Promise.resolve().then(() => (init_乐不思蜀(), 乐不思蜀_exports)), "./cards/五谷丰登.ts": () => Promise.resolve().then(() => (init_五谷丰登(), 五谷丰登_exports)), "./cards/借刀杀人.ts": () => Promise.resolve().then(() => (init_借刀杀人(), 借刀杀人_exports)), "./cards/兵粮寸断.ts": () => Promise.resolve().then(() => (init_兵粮寸断(), 兵粮寸断_exports)), "./cards/决斗.ts": () => Promise.resolve().then(() => (init_决斗(), 决斗_exports)), "./cards/南蛮入侵.ts": () => Promise.resolve().then(() => (init_南蛮入侵(), 南蛮入侵_exports)), "./cards/无中生有.ts": () => Promise.resolve().then(() => (init_无中生有(), 无中生有_exports)), "./cards/无懈可击.ts": () => Promise.resolve().then(() => (init_无懈可击(), 无懈可击_exports)), "./cards/杀.ts": () => Promise.resolve().then(() => (init_杀(), 杀_exports)), "./cards/桃.ts": () => Promise.resolve().then(() => (init_桃(), 桃_exports)), "./cards/桃园结义.ts": () => Promise.resolve().then(() => (init_桃园结义(), 桃园结义_exports)), "./cards/火攻.ts": () => Promise.resolve().then(() => (init_火攻(), 火攻_exports)), "./cards/过河拆桥.ts": () => Promise.resolve().then(() => (init_过河拆桥(), 过河拆桥_exports)), "./cards/酒.ts": () => Promise.resolve().then(() => (init_酒(), 酒_exports)), "./cards/铁索连环.ts": () => Promise.resolve().then(() => (init_铁索连环$1(), 铁索连环_exports$1)), "./cards/闪.ts": () => Promise.resolve().then(() => (init_闪(), 闪_exports)), "./cards/闪电.ts": () => Promise.resolve().then(() => (init_闪电(), 闪电_exports)), "./cards/顺手牵羊.ts": () => Promise.resolve().then(() => (init_顺手牵羊(), 顺手牵羊_exports)), "./丈八蛇矛.ts": () => Promise.resolve().then(() => (init_丈八蛇矛(), 丈八蛇矛_exports)), "./不屈.ts": () => Promise.resolve().then(() => (init_不屈(), 不屈_exports)), "./义绝.ts": () => Promise.resolve().then(() => (init_义绝(), 义绝_exports)), "./乱击.ts": () => Promise.resolve().then(() => (init_乱击(), 乱击_exports)), "./乱武.ts": () => Promise.resolve().then(() => (init_乱武(), 乱武_exports)), "./享乐.ts": () => Promise.resolve().then(() => (init_享乐(), 享乐_exports)), "./仁德.ts": () => Promise.resolve().then(() => (init_仁德(), 仁德_exports)), "./仁王盾.ts": () => Promise.resolve().then(() => (init_仁王盾(), 仁王盾_exports)), "./从谏.ts": () => Promise.resolve().then(() => (init_从谏(), 从谏_exports)), "./伪帝.ts": () => Promise.resolve().then(() => (init_伪帝(), 伪帝_exports)), "./倾国.ts": () => Promise.resolve().then(() => (init_倾国(), 倾国_exports)), "./克己.ts": () => Promise.resolve().then(() => (init_克己(), 克己_exports)), "./八卦阵.ts": () => Promise.resolve().then(() => (init_八卦阵(), 八卦阵_exports)), "./八阵.ts": () => Promise.resolve().then(() => (init_八阵(), 八阵_exports)), "./兵略.ts": () => Promise.resolve().then(() => (init_兵略(), 兵略_exports)), "./再起.ts": () => Promise.resolve().then(() => (init_再起(), 再起_exports)), "./决堰.ts": () => Promise.resolve().then(() => (init_决堰(), 决堰_exports)), "./凿险.ts": () => Promise.resolve().then(() => (init_凿险(), 凿险_exports)), "./刚烈.ts": () => Promise.resolve().then(() => (init_刚烈(), 刚烈_exports)), "./制衡.ts": () => Promise.resolve().then(() => (init_制衡(), 制衡_exports)), "./制霸.ts": () => Promise.resolve().then(() => (init_制霸(), 制霸_exports)), "./勤学.ts": () => Promise.resolve().then(() => (init_勤学(), 勤学_exports)), "./化身.ts": () => Promise.resolve().then(() => (init_化身(), 化身_exports)), "./博图.ts": () => Promise.resolve().then(() => (init_博图(), 博图_exports)), "./双雄.ts": () => Promise.resolve().then(() => (init_双雄(), 双雄_exports)), "./反间.ts": () => Promise.resolve().then(() => (init_反间(), 反间_exports)), "./反馈.ts": () => Promise.resolve().then(() => (init_反馈(), 反馈_exports)), "./古锭刀.ts": () => Promise.resolve().then(() => (init_古锭刀(), 古锭刀_exports)), "./咆哮.ts": () => Promise.resolve().then(() => (init_咆哮(), 咆哮_exports)), "./回合管理.ts": () => Promise.resolve().then(() => (init_回合管理(), 回合管理_exports)), "./固政.ts": () => Promise.resolve().then(() => (init_固政(), 固政_exports)), "./国色.ts": () => Promise.resolve().then(() => (init_国色(), 国色_exports)), "./天义.ts": () => Promise.resolve().then(() => (init_天义(), 天义_exports)), "./天妒.ts": () => Promise.resolve().then(() => (init_天妒(), 天妒_exports)), "./天香.ts": () => Promise.resolve().then(() => (init_天香(), 天香_exports)), "./奇制.ts": () => Promise.resolve().then(() => (init_奇制(), 奇制_exports)), "./奇才.ts": () => Promise.resolve().then(() => (init_奇才(), 奇才_exports)), "./奇袭.ts": () => Promise.resolve().then(() => (init_奇袭(), 奇袭_exports)), "./奇谋.ts": () => Promise.resolve().then(() => (init_奇谋(), 奇谋_exports)), "./奋威.ts": () => Promise.resolve().then(() => (init_奋威(), 奋威_exports)), "./奋激.ts": () => Promise.resolve().then(() => (init_奋激(), 奋激_exports)), "./奸雄.ts": () => Promise.resolve().then(() => (init_奸雄(), 奸雄_exports)), "./好施.ts": () => Promise.resolve().then(() => (init_好施(), 好施_exports)), "./完杀.ts": () => Promise.resolve().then(() => (init_完杀(), 完杀_exports)), "./审时.ts": () => Promise.resolve().then(() => (init_审时(), 审时_exports)), "./寒冰剑.ts": () => Promise.resolve().then(() => (init_寒冰剑(), 寒冰剑_exports)), "./寸目.ts": () => Promise.resolve().then(() => (init_寸目(), 寸目_exports)), "./屯田.ts": () => Promise.resolve().then(() => (init_屯田(), 屯田_exports)), "./崩坏.ts": () => Promise.resolve().then(() => (init_崩坏(), 崩坏_exports)), "./巧变.ts": () => Promise.resolve().then(() => (init_巧变(), 巧变_exports)), "./巨象.ts": () => Promise.resolve().then(() => (init_巨象(), 巨象_exports)), "./帷幕.ts": () => Promise.resolve().then(() => (init_帷幕(), 帷幕_exports)), "./庸肆.ts": () => Promise.resolve().then(() => (init_庸肆(), 庸肆_exports)), "./开局.ts": () => Promise.resolve().then(() => (init_开局(), 开局_exports)), "./强袭.ts": () => Promise.resolve().then(() => (init_强袭(), 强袭_exports)), "./往烈.ts": () => Promise.resolve().then(() => (init_往烈(), 往烈_exports)), "./征荣.ts": () => Promise.resolve().then(() => (init_征荣(), 征荣_exports)), "./志继.ts": () => Promise.resolve().then(() => (init_志继(), 志继_exports)), "./怀橘.ts": () => Promise.resolve().then(() => (init_怀橘(), 怀橘_exports)), "./急救.ts": () => Promise.resolve().then(() => (init_急救(), 急救_exports)), "./急袭.ts": () => Promise.resolve().then(() => (init_急袭(), 急袭_exports)), "./恃才.ts": () => Promise.resolve().then(() => (init_恃才(), 恃才_exports)), "./悲歌.ts": () => Promise.resolve().then(() => (init_悲歌(), 悲歌_exports)), "./成略.ts": () => Promise.resolve().then(() => (init_成略(), 成略_exports)), "./截辎.ts": () => Promise.resolve().then(() => (init_截辎(), 截辎_exports)), "./护甲.ts": () => Promise.resolve().then(() => (init_护甲(), 护甲_exports)), "./护驾.ts": () => Promise.resolve().then(() => (init_护驾(), 护驾_exports)), "./拒战.ts": () => Promise.resolve().then(() => (init_拒战(), 拒战_exports)), "./挑衅.ts": () => Promise.resolve().then(() => (init_挑衅(), 挑衅_exports)), "./据守.ts": () => Promise.resolve().then(() => (init_据守(), 据守_exports)), "./掣政.ts": () => Promise.resolve().then(() => (init_掣政(), 掣政_exports)), "./攻心.ts": () => Promise.resolve().then(() => (init_攻心(), 攻心_exports)), "./放权.ts": () => Promise.resolve().then(() => (init_放权(), 放权_exports)), "./放逐.ts": () => Promise.resolve().then(() => (init_放逐(), 放逐_exports)), "./救援.ts": () => Promise.resolve().then(() => (init_救援(), 救援_exports)), "./整论.ts": () => Promise.resolve().then(() => (init_整论(), 整论_exports)), "./断粮.ts": () => Promise.resolve().then(() => (init_断粮(), 断粮_exports)), "./断肠.ts": () => Promise.resolve().then(() => (init_断肠(), 断肠_exports)), "./新生.ts": () => Promise.resolve().then(() => (init_新生(), 新生_exports)), "./方天画戟.ts": () => Promise.resolve().then(() => (init_方天画戟(), 方天画戟_exports)), "./无双.ts": () => Promise.resolve().then(() => (init_无双(), 无双_exports)), "./明任.ts": () => Promise.resolve().then(() => (init_明任(), 明任_exports)), "./暴虐.ts": () => Promise.resolve().then(() => (init_暴虐(), 暴虐_exports)), "./朱雀羽扇.ts": () => Promise.resolve().then(() => (init_朱雀羽扇(), 朱雀羽扇_exports)), "./枭姬.ts": () => Promise.resolve().then(() => (init_枭姬(), 枭姬_exports)), "./武圣.ts": () => Promise.resolve().then(() => (init_武圣(), 武圣_exports)), "./武烈.ts": () => Promise.resolve().then(() => (init_武烈(), 武烈_exports)), "./洛神.ts": () => Promise.resolve().then(() => (init_洛神(), 洛神_exports)), "./流离.ts": () => Promise.resolve().then(() => (init_流离(), 流离_exports)), "./涅槃.ts": () => Promise.resolve().then(() => (init_涅槃(), 涅槃_exports)), "./涯角.ts": () => Promise.resolve().then(() => (init_涯角(), 涯角_exports)), "./溃诛.ts": () => Promise.resolve().then(() => (init_溃诛(), 溃诛_exports)), "./激将.ts": () => Promise.resolve().then(() => (init_激将(), 激将_exports)), "./激昂.ts": () => Promise.resolve().then(() => (init_激昂(), 激昂_exports)), "./火计.ts": () => Promise.resolve().then(() => (init_火计(), 火计_exports)), "./烈刃.ts": () => Promise.resolve().then(() => (init_烈刃(), 烈刃_exports)), "./烈弓.ts": () => Promise.resolve().then(() => (init_烈弓(), 烈弓_exports)), "./父荫.ts": () => Promise.resolve().then(() => (init_父荫(), 父荫_exports)), "./狂骨.ts": () => Promise.resolve().then(() => (init_狂骨(), 狂骨_exports)), "./界不屈.ts": () => Promise.resolve().then(() => (init_界不屈(), 界不屈_exports)), "./界义从.ts": () => Promise.resolve().then(() => (init_界义从(), 界义从_exports)), "./界乱击.ts": () => Promise.resolve().then(() => (init_界乱击(), 界乱击_exports)), "./界乱武.ts": () => Promise.resolve().then(() => (init_界乱武(), 界乱武_exports)), "./界仁德.ts": () => Promise.resolve().then(() => (init_界仁德(), 界仁德_exports)), "./界仁心.ts": () => Promise.resolve().then(() => (init_界仁心(), 界仁心_exports)), "./界伏枥.ts": () => Promise.resolve().then(() => (init_界伏枥(), 界伏枥_exports)), "./界伤逝.ts": () => Promise.resolve().then(() => (init_界伤逝(), 界伤逝_exports)), "./界克己.ts": () => Promise.resolve().then(() => (init_界克己(), 界克己_exports)), "./界再起.ts": () => Promise.resolve().then(() => (init_界再起(), 界再起_exports)), "./界凿险.ts": () => Promise.resolve().then(() => (init_界凿险(), 界凿险_exports)), "./界刚烈.ts": () => Promise.resolve().then(() => (init_界刚烈(), 界刚烈_exports)), "./界利驭.ts": () => Promise.resolve().then(() => (init_界利驭(), 界利驭_exports)), "./界制衡.ts": () => Promise.resolve().then(() => (init_界制衡(), 界制衡_exports)), "./界制霸.ts": () => Promise.resolve().then(() => (init_界制霸(), 界制霸_exports)), "./界势斩.ts": () => Promise.resolve().then(() => (init_界势斩(), 界势斩_exports)), "./界双雄.ts": () => Promise.resolve().then(() => (init_界双雄(), 界双雄_exports)), "./界反间.ts": () => Promise.resolve().then(() => (init_界反间(), 界反间_exports)), "./界咆哮.ts": () => Promise.resolve().then(() => (init_界咆哮(), 界咆哮_exports)), "./界固政.ts": () => Promise.resolve().then(() => (init_界固政(), 界固政_exports)), "./界国色.ts": () => Promise.resolve().then(() => (init_界国色(), 界国色_exports)), "./界天香.ts": () => Promise.resolve().then(() => (init_界天香(), 界天香_exports)), "./界奇才.ts": () => Promise.resolve().then(() => (init_界奇才(), 界奇才_exports)), "./界奸雄.ts": () => Promise.resolve().then(() => (init_界奸雄(), 界奸雄_exports)), "./界好施.ts": () => Promise.resolve().then(() => (init_界好施(), 界好施_exports)), "./界完杀.ts": () => Promise.resolve().then(() => (init_界完杀(), 界完杀_exports)), "./界宗室.ts": () => Promise.resolve().then(() => (init_界宗室(), 界宗室_exports)), "./界将驰.ts": () => Promise.resolve().then(() => (init_界将驰(), 界将驰_exports)), "./界屯田.ts": () => Promise.resolve().then(() => (init_界屯田(), 界屯田_exports)), "./界崩坏.ts": () => Promise.resolve().then(() => (init_界崩坏(), 界崩坏_exports)), "./界巧变.ts": () => Promise.resolve().then(() => (init_界巧变(), 界巧变_exports)), "./界巧说.ts": () => Promise.resolve().then(() => (init_界巧说(), 界巧说_exports)), "./界帷幕.ts": () => Promise.resolve().then(() => (init_界帷幕(), 界帷幕_exports)), "./界弓骑.ts": () => Promise.resolve().then(() => (init_界弓骑(), 界弓骑_exports)), "./界强袭.ts": () => Promise.resolve().then(() => (init_界强袭(), 界强袭_exports)), "./界强识.ts": () => Promise.resolve().then(() => (init_界强识(), 界强识_exports)), "./界当先.ts": () => Promise.resolve().then(() => (init_界当先(), 界当先_exports)), "./界志继.ts": () => Promise.resolve().then(() => (init_界志继(), 界志继_exports)), "./界忘隙.ts": () => Promise.resolve().then(() => (init_界忘隙(), 界忘隙_exports)), "./界恂恂.ts": () => Promise.resolve().then(() => (init_界恂恂(), 界恂恂_exports)), "./界恩怨.ts": () => Promise.resolve().then(() => (init_界恩怨(), 界恩怨_exports)), "./界悲歌.ts": () => Promise.resolve().then(() => (init_界悲歌(), 界悲歌_exports)), "./界惴恐.ts": () => Promise.resolve().then(() => (init_界惴恐(), 界惴恐_exports)), "./界护驾.ts": () => Promise.resolve().then(() => (init_界护驾(), 界护驾_exports)), "./界挑衅.ts": () => Promise.resolve().then(() => (init_界挑衅(), 界挑衅_exports)), "./界据守.ts": () => Promise.resolve().then(() => (init_界据守(), 界据守_exports)), "./界放权.ts": () => Promise.resolve().then(() => (init_界放权(), 界放权_exports)), "./界放逐.ts": () => Promise.resolve().then(() => (init_界放逐(), 界放逐_exports)), "./界救援.ts": () => Promise.resolve().then(() => (init_界救援(), 界救援_exports)), "./界断粮.ts": () => Promise.resolve().then(() => (init_界断粮(), 界断粮_exports)), "./界旋风.ts": () => Promise.resolve().then(() => (init_界旋风(), 界旋风_exports)), "./界明策.ts": () => Promise.resolve().then(() => (init_界明策(), 界明策_exports)), "./界智迟.ts": () => Promise.resolve().then(() => (init_界智迟(), 界智迟_exports)), "./界暴虐.ts": () => Promise.resolve().then(() => (init_界暴虐(), 界暴虐_exports)), "./界樵拾.ts": () => Promise.resolve().then(() => (init_界樵拾(), 界樵拾_exports)), "./界武圣.ts": () => Promise.resolve().then(() => (init_界武圣(), 界武圣_exports)), "./界殚心.ts": () => Promise.resolve().then(() => (init_界殚心(), 界殚心_exports)), "./界毅重.ts": () => Promise.resolve().then(() => (init_界毅重(), 界毅重_exports)), "./界求援.ts": () => Promise.resolve().then(() => (init_界求援(), 界求援_exports)), "./界洛神.ts": () => Promise.resolve().then(() => (init_界洛神(), 界洛神_exports)), "./界涅槃.ts": () => Promise.resolve().then(() => (init_界涅槃(), 界涅槃_exports)), "./界渐营.ts": () => Promise.resolve().then(() => (init_界渐营(), 界渐营_exports)), "./界潜心.ts": () => Promise.resolve().then(() => (init_界潜心(), 界潜心_exports)), "./界潜袭.ts": () => Promise.resolve().then(() => (init_界潜袭(), 界潜袭_exports)), "./界激将.ts": () => Promise.resolve().then(() => (init_界激将(), 界激将_exports)), "./界激昂.ts": () => Promise.resolve().then(() => (init_界激昂(), 界激昂_exports)), "./界火计.ts": () => Promise.resolve().then(() => (init_界火计(), 界火计_exports)), "./界灭计.ts": () => Promise.resolve().then(() => (init_界灭计(), 界灭计_exports)), "./界烈弓.ts": () => Promise.resolve().then(() => (init_界烈弓(), 界烈弓_exports)), "./界焚城.ts": () => Promise.resolve().then(() => (init_界焚城(), 界焚城_exports)), "./界燕语.ts": () => Promise.resolve().then(() => (init_界燕语(), 界燕语_exports)), "./界父魂.ts": () => Promise.resolve().then(() => (init_界父魂(), 界父魂_exports)), "./界狂骨.ts": () => Promise.resolve().then(() => (init_界狂骨(), 界狂骨_exports)), "./界献图.ts": () => Promise.resolve().then(() => (init_界献图(), 界献图_exports)), "./界献州.ts": () => Promise.resolve().then(() => (init_界献州(), 界献州_exports)), "./界甘露.ts": () => Promise.resolve().then(() => (init_界甘露(), 界甘露_exports)), "./界疠火.ts": () => Promise.resolve().then(() => (init_界疠火(), 界疠火_exports)), "./界直言.ts": () => Promise.resolve().then(() => (init_界直言(), 界直言_exports)), "./界直谏.ts": () => Promise.resolve().then(() => (init_界直谏(), 界直谏_exports)), "./界看破.ts": () => Promise.resolve().then(() => (init_界看破(), 界看破_exports)), "./界眩惑.ts": () => Promise.resolve().then(() => (init_界眩惑(), 界眩惑_exports)), "./界矢北.ts": () => Promise.resolve().then(() => (init_界矢北(), 界矢北_exports)), "./界矫诏.ts": () => Promise.resolve().then(() => (init_界矫诏(), 界矫诏_exports)), "./界破军.ts": () => Promise.resolve().then(() => (init_界破军(), 界破军_exports)), "./界神速.ts": () => Promise.resolve().then(() => (init_界神速(), 界神速_exports)), "./界禁酒.ts": () => Promise.resolve().then(() => (init_界禁酒(), 界禁酒_exports)), "./界秘计.ts": () => Promise.resolve().then(() => (init_界秘计(), 界秘计_exports)), "./界称象.ts": () => Promise.resolve().then(() => (init_界称象(), 界称象_exports)), "./界空城.ts": () => Promise.resolve().then(() => (init_界空城(), 界空城_exports)), "./界突袭.ts": () => Promise.resolve().then(() => (init_界突袭(), 界突袭_exports)), "./界窃听.ts": () => Promise.resolve().then(() => (init_界窃听(), 界窃听_exports)), "./界精策.ts": () => Promise.resolve().then(() => (init_界精策(), 界精策_exports)), "./界红颜.ts": () => Promise.resolve().then(() => (init_界红颜(), 界红颜_exports)), "./界纵玄.ts": () => Promise.resolve().then(() => (init_界纵玄(), 界纵玄_exports)), "./界纵适.ts": () => Promise.resolve().then(() => (init_界纵适(), 界纵适_exports)), "./界结姻.ts": () => Promise.resolve().then(() => (init_界结姻(), 界结姻_exports)), "./界绝情.ts": () => Promise.resolve().then(() => (init_界绝情(), 界绝情_exports)), "./界绝策.ts": () => Promise.resolve().then(() => (init_界绝策(), 界绝策_exports)), "./界缔盟.ts": () => Promise.resolve().then(() => (init_界缔盟(), 界缔盟_exports)), "./界翦灭.ts": () => Promise.resolve().then(() => (init_界翦灭(), 界翦灭_exports)), "./界耀武.ts": () => Promise.resolve().then(() => (init_界耀武(), 界耀武_exports)), "./界自守.ts": () => Promise.resolve().then(() => (init_界自守(), 界自守_exports)), "./界节命.ts": () => Promise.resolve().then(() => (init_界节命(), 界节命_exports)), "./界若愚.ts": () => Promise.resolve().then(() => (init_界若愚(), 界若愚_exports)), "./界苦肉.ts": () => Promise.resolve().then(() => (init_界苦肉(), 界苦肉_exports)), "./界英姿.ts": () => Promise.resolve().then(() => (init_界英姿(), 界英姿_exports)), "./界荐言.ts": () => Promise.resolve().then(() => (init_界荐言(), 界荐言_exports)), "./界落英.ts": () => Promise.resolve().then(() => (init_界落英(), 界落英_exports)), "./界蛊惑.ts": () => Promise.resolve().then(() => (init_界蛊惑(), 界蛊惑_exports)), "./界血裔.ts": () => Promise.resolve().then(() => (init_界血裔(), 界血裔_exports)), "./界补益.ts": () => Promise.resolve().then(() => (init_界补益(), 界补益_exports)), "./界裸衣.ts": () => Promise.resolve().then(() => (init_界裸衣(), 界裸衣_exports)), "./界观星.ts": () => Promise.resolve().then(() => (init_界观星(), 界观星_exports)), "./界解烦.ts": () => Promise.resolve().then(() => (init_界解烦(), 界解烦_exports)), "./界诛害.ts": () => Promise.resolve().then(() => (init_界诛害(), 界诛害_exports)), "./界谦逊.ts": () => Promise.resolve().then(() => (init_界谦逊(), 界谦逊_exports)), "./界贞烈.ts": () => Promise.resolve().then(() => (init_界贞烈(), 界贞烈_exports)), "./界趫猛.ts": () => Promise.resolve().then(() => (init_界趫猛(), 界趫猛_exports)), "./界连环.ts": () => Promise.resolve().then(() => (init_界连环(), 界连环_exports)), "./界连营.ts": () => Promise.resolve().then(() => (init_界连营(), 界连营_exports)), "./界遗计.ts": () => Promise.resolve().then(() => (init_界遗计(), 界遗计_exports)), "./界酒池.ts": () => Promise.resolve().then(() => (init_界酒池(), 界酒池_exports)), "./界酒诗.ts": () => Promise.resolve().then(() => (init_界酒诗(), 界酒诗_exports)), "./界醇醪.ts": () => Promise.resolve().then(() => (init_界醇醪(), 界醇醪_exports)), "./界铁骑.ts": () => Promise.resolve().then(() => (init_界铁骑(), 界铁骑_exports)), "./界镇军.ts": () => Promise.resolve().then(() => (init_界镇军(), 界镇军_exports)), "./界闭月.ts": () => Promise.resolve().then(() => (init_界闭月(), 界闭月_exports)), "./界除疠.ts": () => Promise.resolve().then(() => (init_界除疠(), 界除疠_exports)), "./界陷阵.ts": () => Promise.resolve().then(() => (init_界陷阵(), 界陷阵_exports)), "./界集智.ts": () => Promise.resolve().then(() => (init_界集智(), 界集智_exports)), "./界雷击.ts": () => Promise.resolve().then(() => (init_界雷击(), 界雷击_exports)), "./界鞬出.ts": () => Promise.resolve().then(() => (init_界鞬出(), 界鞬出_exports)), "./界鬼才.ts": () => Promise.resolve().then(() => (init_界鬼才(), 界鬼才_exports)), "./界鬼道.ts": () => Promise.resolve().then(() => (init_界鬼道(), 界鬼道_exports)), "./界魂姿.ts": () => Promise.resolve().then(() => (init_界魂姿(), 界魂姿_exports)), "./界黄天.ts": () => Promise.resolve().then(() => (init_界黄天(), 界黄天_exports)), "./界龙胆.ts": () => Promise.resolve().then(() => (init_界龙胆(), 界龙胆_exports)), "./白银狮子.ts": () => Promise.resolve().then(() => (init_白银狮子(), 白银狮子_exports)), "./直谏.ts": () => Promise.resolve().then(() => (init_直谏(), 直谏_exports)), "./看破.ts": () => Promise.resolve().then(() => (init_看破(), 看破_exports)), "./破势.ts": () => Promise.resolve().then(() => (init_破势(), 破势_exports)), "./神速.ts": () => Promise.resolve().then(() => (init_神速(), 神速_exports)), "./祸首.ts": () => Promise.resolve().then(() => (init_祸首(), 祸首_exports)), "./离间.ts": () => Promise.resolve().then(() => (init_离间(), 离间_exports)), "./空城.ts": () => Promise.resolve().then(() => (init_空城(), 空城_exports)), "./突袭.ts": () => Promise.resolve().then(() => (init_突袭(), 突袭_exports)), "./立军.ts": () => Promise.resolve().then(() => (init_立军(), 立军_exports)), "./箜声.ts": () => Promise.resolve().then(() => (init_箜声(), 箜声_exports)), "./系统规则.ts": () => Promise.resolve().then(() => (init_系统规则(), 系统规则_exports)), "./红颜.ts": () => Promise.resolve().then(() => (init_红颜(), 红颜_exports)), "./结姻.ts": () => Promise.resolve().then(() => (init_结姻(), 结姻_exports)), "./缔盟.ts": () => Promise.resolve().then(() => (init_缔盟(), 缔盟_exports)), "./罪论.ts": () => Promise.resolve().then(() => (init_罪论(), 罪论_exports)), "./耀武.ts": () => Promise.resolve().then(() => (init_耀武(), 耀武_exports)), "./肉林.ts": () => Promise.resolve().then(() => (init_肉林(), 肉林_exports)), "./良姻.ts": () => Promise.resolve().then(() => (init_良姻(), 良姻_exports)), "./节命.ts": () => Promise.resolve().then(() => (init_节命(), 节命_exports)), "./若愚.ts": () => Promise.resolve().then(() => (init_若愚(), 若愚_exports)), "./苦肉.ts": () => Promise.resolve().then(() => (init_苦肉(), 苦肉_exports)), "./英姿.ts": () => Promise.resolve().then(() => (init_英姿(), 英姿_exports)), "./英魂.ts": () => Promise.resolve().then(() => (init_英魂(), 英魂_exports)), "./荐降.ts": () => Promise.resolve().then(() => (init_荐降(), 荐降_exports)), "./藤甲.ts": () => Promise.resolve().then(() => (init_藤甲(), 藤甲_exports)), "./蛊惑.ts": () => Promise.resolve().then(() => (init_蛊惑(), 蛊惑_exports)), "./行殇.ts": () => Promise.resolve().then(() => (init_行殇(), 行殇_exports)), "./装备通用.ts": () => Promise.resolve().then(() => (init_装备通用(), 装备通用_exports)), "./裸衣.ts": () => Promise.resolve().then(() => (init_裸衣(), 裸衣_exports)), "./观星.ts": () => Promise.resolve().then(() => (init_观星(), 观星_exports)), "./解围.ts": () => Promise.resolve().then(() => (init_解围(), 解围_exports)), "./诈降.ts": () => Promise.resolve().then(() => (init_诈降(), 诈降_exports)), "./诸葛连弩.ts": () => Promise.resolve().then(() => (init_诸葛连弩(), 诸葛连弩_exports)), "./谦节.ts": () => Promise.resolve().then(() => (init_谦节(), 谦节_exports)), "./谦逊.ts": () => Promise.resolve().then(() => (init_谦逊(), 谦逊_exports)), "./贞良.ts": () => Promise.resolve().then(() => (init_贞良(), 贞良_exports)), "./贯石斧.ts": () => Promise.resolve().then(() => (init_贯石斧(), 贯石斧_exports)), "./进趋.ts": () => Promise.resolve().then(() => (init_进趋(), 进趋_exports)), "./连环.ts": () => Promise.resolve().then(() => (init_连环(), 连环_exports)), "./连营.ts": () => Promise.resolve().then(() => (init_连营(), 连营_exports)), "./遗礼.ts": () => Promise.resolve().then(() => (init_遗礼(), 遗礼_exports)), "./遗计.ts": () => Promise.resolve().then(() => (init_遗计(), 遗计_exports)), "./酒池.ts": () => Promise.resolve().then(() => (init_酒池(), 酒池_exports)), "./酣战.ts": () => Promise.resolve().then(() => (init_酣战(), 酣战_exports)), "./铁索连环.ts": () => Promise.resolve().then(() => (init_铁索连环(), 铁索连环_exports)), "./铁骑.ts": () => Promise.resolve().then(() => (init_铁骑(), 铁骑_exports)), "./镇骨.ts": () => Promise.resolve().then(() => (init_镇骨(), 镇骨_exports)), "./闭月.ts": () => Promise.resolve().then(() => (init_闭月(), 闭月_exports)), "./雄乱.ts": () => Promise.resolve().then(() => (init_雄乱(), 雄乱_exports)), "./集智.ts": () => Promise.resolve().then(() => (init_集智(), 集智_exports)), "./雌雄双股剑.ts": () => Promise.resolve().then(() => (init_雌雄双股剑(), 雌雄双股剑_exports)), "./雷击.ts": () => Promise.resolve().then(() => (init_雷击(), 雷击_exports)), "./青囊.ts": () => Promise.resolve().then(() => (init_青囊(), 青囊_exports)), "./青釭剑.ts": () => Promise.resolve().then(() => (init_青釭剑(), 青釭剑_exports)), "./青龙偃月刀.ts": () => Promise.resolve().then(() => (init_青龙偃月刀(), 青龙偃月刀_exports)), "./鞬出.ts": () => Promise.resolve().then(() => (init_鞬出(), 鞬出_exports)), "./颂威.ts": () => Promise.resolve().then(() => (init_颂威(), 颂威_exports)), "./飘零.ts": () => Promise.resolve().then(() => (init_飘零(), 飘零_exports)), "./飞军.ts": () => Promise.resolve().then(() => (init_飞军(), 飞军_exports)), "./马匹技能.ts": () => Promise.resolve().then(() => (init_马匹技能(), 马匹技能_exports)), "./马术.ts": () => Promise.resolve().then(() => (init_马术(), 马术_exports)), "./驱虎.ts": () => Promise.resolve().then(() => (init_驱虎(), 驱虎_exports)), "./鬼才.ts": () => Promise.resolve().then(() => (init_鬼才(), 鬼才_exports)), "./鬼道.ts": () => Promise.resolve().then(() => (init_鬼道(), 鬼道_exports)), "./魂姿.ts": () => Promise.resolve().then(() => (init_魂姿(), 魂姿_exports)), "./鸿举.ts": () => Promise.resolve().then(() => (init_鸿举(), 鸿举_exports)), "./麒麟弓.ts": () => Promise.resolve().then(() => (init_麒麟弓(), 麒麟弓_exports)), "./黄天.ts": () => Promise.resolve().then(() => (init_黄天(), 黄天_exports)), "./龙胆.ts": () => Promise.resolve().then(() => (init_龙胆(), 龙胆_exports)) }); })); //#endregion //#region src/engine/skills/loaders.ts /** spec 'skills/cards/use-card' → vite-glob key './cards/use-card.ts' */ function globKeyOf(spec) { return `./${spec.slice(7)}.ts`; } async function importModuleFile(spec) { let glob; try { glob = (await Promise.resolve().then(() => (init_vite_glob(), vite_glob_exports))).default; } catch { glob = void 0; } if (glob) { const loader = glob[globKeyOf(spec)]; if (!loader) throw new Error(`技能模块 ${spec} 不在 vite-glob 映射中(文件缺失?)`); return loader(); } const urlSpec = "node:url"; const pathSpec = "node:path"; const { fileURLToPath, pathToFileURL } = await import( /* @vite-ignore */ urlSpec ); const { resolve } = await import( /* @vite-ignore */ pathSpec ); return import( /* @vite-ignore */ pathToFileURL(resolve(fileURLToPath(new URL("data:video/mp2t;base64,Ly8gc3JjL2VuZ2luZS9pbmRleC50cyDigJQg5byV5pOO5a+55aSW5ZSv5LiA5YWl5Y+jKOe6r+mXqOmdoiByZS1leHBvcnQp44CCCi8vIHNlc3Npb24g5bGC6YCa6L+H5q2k5paH5Lu26K6/6Zeu5byV5pOOIEFQSTppbXBvcnQgeyBjcmVhdGUgfSBmcm9tICcuL2VuZ2luZSfjgIIKLy8g5YaF6YOo5raI6LS56ICF5LiN5bqU5LuO5q2k5paH5Lu2IGltcG9ydCDigJTigJQg55u05o6l5L2/55SoIGNvcmUvIOWtkOaooeWdl+OAggpleHBvcnQgewogIGNyZWF0ZSwKICBib290c3RyYXAsCiAgZGlzcGF0Y2gsCiAgcmVzdG9yZSwKICByZWdpc3RlclNraWxsc0Zyb21TdGF0ZSwKICBidWlsZFZpZXcsCiAgZmlyZVRpbWVvdXQsCiAgY2hlY2tHYW1lT3ZlciwKfSBmcm9tICcuL2NvcmUnOwpleHBvcnQgdHlwZSB7IEdhbWVDb25maWcgfSBmcm9tICcuL2NvcmUnOwo=", "" + import.meta.url)), `${spec}.ts`)).href ); } /** * 按 skillId 加载技能模块。未声明的 id 抛错(调用方需先用 hasSkillModule 过滤)。 * 模块形状(存在 createSkill 函数 / '#导出名' 命中)在此校验,坏声明立即暴露。 */ async function importSkillModuleById(id) { const spec = getSkillModuleSpec(id); if (!spec) throw new Error(`技能 "${id}" 无模块声明(registry 聚合结果中不存在)`); const [filePath, exportName] = spec.split("#"); const mod = await importModuleFile(filePath); const target = exportName ? mod[exportName] : mod; if (!target || typeof target.createSkill !== "function") throw new Error(`技能模块 ${spec} 形状不符:期望${exportName ? `导出 "${exportName}" 且` : ""}含 createSkill 函数`); return target; } var init_loaders = __esmMin((() => { init_registry$1(); })); //#endregion //#region src/engine/skills/lifecycle.ts async function getSkillModule(id) { const cached = moduleCache.get(id); if (cached) return cached; const override = moduleOverrides.get(id); const mod = override ? await override() : await importSkillModuleById(id); moduleCache.set(id, mod); moduleCacheVersion++; moduleCacheListeners.forEach((cb) => cb()); return mod; } /** 同步检查技能模块是否已声明(registry 聚合结果中存在)。 * 用于跳过未声明的技能 id(如已删除的 per-card 技能),避免 getSkillModule 拑错。 */ function isSkillModuleRegistered(id) { return moduleOverrides.has(id) || hasSkillModule(id); } /** 同步获取技能描述。依赖技能模块已加载(moduleCache 命中);未加载返回 undefined。 * 前端 useSkillActions 在 view 变化时为所有玩家 registerSkillActions → 全量加载技能模块, * 故渲染时基本能命中;首次渲染(effect 未跑完)的极少数情况优雅降级(只显示技能名)。 * 需要确保命中的场景(MCP 工具/服务端)用 getSkillDescriptionAsync。 */ function getSkillDescription(id) { if (descriptionCache.has(id)) return descriptionCache.get(id); const mod = moduleCache.get(id); if (!mod) return void 0; try { const desc = mod.createSkill(id, 0).description; descriptionCache.set(id, desc); return desc; } catch { return; } } /** 异步获取技能描述:先查缓存,未命中则加载模块再取。模块缺失(无对应技能)返回 undefined。 */ async function getSkillDescriptionAsync(id) { if (descriptionCache.has(id)) return descriptionCache.get(id); try { await getSkillModule(id); } catch { return; } return getSkillDescription(id); } function unloadSkillInstance(state, skillId, ownerId) { const unload = getSkillInstanceUnload(state, skillId, ownerId); if (unload) { unload(); deleteSkillInstanceUnload(state, skillId, ownerId); } if (!hasCardEffect(skillId)) unregisterActionsForInstance(state, skillId, ownerId); } /** * 实例化单个 skill(从 index bootstrap / registerSkillsFromState / 添加技能 atom 调用)。 * * 幂等:若 (skillId, ownerId) 已有实例,先卸载旧实例(调其 unload 函数 + 清 action/hook 注册), * 再重新注册。保证 registerSkillsFromState 重入、并发 dispatch、动态 添加技能 等场景不会因 * `registerActionEntry` 的 "already registered" 抛错。 */ async function instantiateSkill(state, skillId, ownerId) { if (!isSkillModuleRegistered(skillId)) return null; unloadSkillInstance(state, skillId, ownerId); const module = await getSkillModule(skillId); const skill = module.createSkill(skillId, ownerId); if (module.onInit) { const unload = module.onInit(skill, state); setSkillInstanceUnload(state, skillId, ownerId, typeof unload === "function" ? unload : () => {}); } setSkillLocked(state, skillId, skill.isLocked ?? false); return skill; } var moduleCache, moduleOverrides, moduleCacheVersion, moduleCacheListeners, descriptionCache; var init_lifecycle = __esmMin((() => { init_skill(); init_loaders(); init_registry$1(); init_cards(); moduleCache = /* @__PURE__ */ new Map(); moduleOverrides = /* @__PURE__ */ new Map(); moduleCacheVersion = 0; moduleCacheListeners = /* @__PURE__ */ new Set(); descriptionCache = /* @__PURE__ */ new Map(); })); //#endregion //#region src/engine/core/index.ts /** 解析 state.config 中持久化的模式,缺省回退 身份局(兼容旧快照)。 */ function resolveGameMode(state) { return state.config?.mode ?? "身份局"; } /** 检查游戏是否结束。基于 state.config.mode 加载对应规则包判定(默认身份局)。 * 异步(动态 import):session 在 onStateChange 中 await。具体判定逻辑见 * rules/身份局.ts(身份局与 1v1 共用)——core 层不再内嵌模式规则(ADR 0029)。 */ async function checkGameOver(state) { return (await loadRuleset(resolveGameMode(state))).checkGameOver(state); } function create(gameConfig) { const playerCount = Math.max(2, Math.min(8, gameConfig.playerCount)); const stubPlayers = Array.from({ length: playerCount }, (_, i) => ({ index: i, name: gameConfig.playerNames?.[i]?.trim() ?? `player-${i}`, character: "", health: 4, maxHealth: 4, alive: true, hand: [], equipment: {}, skills: [], vars: {}, marks: [], pendingTricks: [], tags: [] })); const allCards = createStandardDeck(); const cardMap = {}; for (const c of allCards) cardMap[c.id] = c; const state = createGameState({ players: stubPlayers, cardMap }); state.startedAt = state.clock.now(); state.config = { ...gameConfig.timeoutSec !== void 0 ? { timeoutSec: gameConfig.timeoutSec } : {}, ...gameConfig.mode !== void 0 ? { mode: gameConfig.mode } : {} }; return state; } /** * 异步 bootstrap:在 state 上跑完开局流程。**不可重入**——开局一旦执行, * 抽身份/选将/洗牌/发牌的状态变更就开始了,无法回滚。对已开局的 state 再调 * bootstrap 是调用方 bug,直接抛错暴露,而非"幂等"重跑。 * * 1. 动态 import 开局 skill 模块 * 2. 调 开局.onInit(skill, state) 注册 start action * 3. dispatch 开局 start → 跑完抽身份/选将/洗牌/发牌/启动第一回合 * 4. registerSkillsFromState(state) 给每个 player 的 skills 注册实例 * * restore 路径同样调 bootstrap —— JSON 快照无法恢复运行时内存状态(skill 实例/hooks/ * respond actions/pending slot 函数),必须靠 bootstrap 重建,再由 restore 重放 actionLog * 推进到正确状态。见 ADR 0027 决策 7 修订。 */ async function bootstrap(state, gameConfig) { if (state.players.some((p) => p.hand.length > 0)) throw new Error("bootstrap: state 已开局(玩家已有手牌),不可重复 bootstrap"); const 开局mod = await Promise.resolve().then(() => (init_开局(), 开局_exports)); const syntheticSkill = 开局mod.createSkill("开局", SYSTEM_OWNER$1); unloadSkillInstance(state, "开局", SYSTEM_OWNER$1); const off开局 = 开局mod.onInit(syntheticSkill, state); setSkillInstanceUnload(state, "开局", SYSTEM_OWNER$1, typeof off开局 === "function" ? off开局 : () => {}); const mode = gameConfig.mode ?? resolveGameMode(state); state.config = { ...state.config, mode }; (await loadRuleset(mode)).onInit(state); const { settle } = await dispatch(state, { skillId: "开局", actionType: "start", ownerId: SYSTEM_OWNER$1, params: { ...gameConfig }, baseSeq: 0 }); const settleError = await settle; if (settleError) throw settleError; } /** * 目标形状归一(dispatch 边界)。 * * 客户端对「目标」有两种 wire 形状(见 client/utils/gameViewHelpers.buildPlayParams): * · 普通牌 / 技能代价牌(非延时锦囊)→ { targets: [idx] } * · 延时锦囊 → { target: idx } * 回应路径(useCardAndTarget 型 pending)固定发 targets(usePlayInteraction.handleRespond)。 * * 引擎侧各技能读法不一:使用牌 skill 自带 target→targets 归一,但**自带 use/respond action * 的技能**(断粮/界断粮/审时/驱虎/天义/界巧说/界陷阵/天香/乱武 …)拿不到它 → * · 只读单数 target 的技能对 targets 形状恒拒(「需要选择目标」); * · 只读 targets 的技能(界火计)对延时锦囊代价牌(单数 target)恒拒。 * 两者都让整类技能在浏览器与 AI 客户端不可用,且只有手写另一种形状的测试能过。 * * 在消息入口把两种形状补齐(已有值不覆盖;单数只取 targets[0],多目标牌不受影响), * 技能读任一形状都成立。preceding 的 params 同样归一(转化技的代价/目标形状一致)。 */ function normalizeTargetShape(params) { const target = typeof params.target === "number" ? params.target : void 0; const targets = Array.isArray(params.targets) ? params.targets : void 0; if (target !== void 0 && targets !== void 0) return params; if (target !== void 0) return { ...params, targets: [target] }; const first = targets && typeof targets[0] === "number" ? targets[0] : void 0; if (first !== void 0) return { ...params, target: first }; return params; } function normalizeMessageTargetShape(message) { const params = normalizeTargetShape(message.params); const preceding = message.preceding?.map((p) => ({ ...p, params: normalizeTargetShape(p.params) })); if (params === message.params && preceding === message.preceding) return message; return { ...message, params, ...preceding ? { preceding } : {} }; } async function dispatch(state, rawMessage) { const message = normalizeMessageTargetShape(rawMessage); let settleResolve; const settle = new Promise((r) => { settleResolve = r; }); let settled = false; const signalSettle = (error) => { if (settled) return; settled = true; state.onExecuteSettle = null; settleResolve(error); }; state.onExecuteSettle = signalSettle; const reject = () => { signalSettle(); return { accepted: false, settle }; }; const accept = () => ({ accepted: true, settle }); const drainUntilSettled = async () => { for (let i = 0; i < 1e3 && !settled; i++) await new Promise((r) => setTimeout(r, 0)); }; const rollbacks = []; const hasPreceding = !!message.preceding?.length; const bufferSnapshot = hasPreceding ? state.atomHistory.length : 0; if (hasPreceding) state.viewBuffering = true; const orderedSnapshot = hasPreceding ? state.players.map((p) => ({ hand: [...p.hand], marks: [...p.marks] })) : null; const rollbackPreceding = async () => { for (let i = rollbacks.length - 1; i >= 0; i--) await rollbacks[i].entry.rollback?.(state, rollbacks[i].params); if (orderedSnapshot) for (let i = 0; i < orderedSnapshot.length; i++) { const p = state.players[i]; if (!p) continue; p.hand = [...orderedSnapshot[i].hand]; p.marks = [...orderedSnapshot[i].marks]; } if (hasPreceding) { state.atomHistory.length = bufferSnapshot; state.viewBuffering = false; } }; const cleanupResidualPending = () => { const resolved = []; for (const [k, slot] of state.pendingSlots) { slot.resolve(); state.pendingSlots.delete(k); resolved.push(slot); } for (const slot of resolved) notifyPendingResolved(state, slot); }; if (message.preceding) for (const p of message.preceding) { const pEntry = findActionEntry(state, p.skillId, message.ownerId, p.actionType); if (pEntry?.validate(state, p.params) !== null) { await rollbackPreceding(); cleanupResidualPending(); return reject(); } await pEntry.execute(state, p.params); rollbacks.push({ entry: pEntry, params: p.params }); } if (message.actionType === "skip") { if (hasPreceding) { state.viewBuffering = false; notifyStateChange(state); } const slot = [...state.pendingSlots.values()].find((s) => { const t = s.atom.target; return typeof t === "number" && t < 0; }) ?? findPendingSlot(state, message.ownerId); if (!slot || slot.isTimeout) return reject(); const atomTarget = slot.atom.target; if (typeof atomTarget === "number" && atomTarget < 0) { logAction(state, message); slot.skippedPlayers ??= /* @__PURE__ */ new Set(); slot.skippedPlayers.add(message.ownerId); if (state.players.filter((p) => p.alive).map((p) => p.index).every((idx) => slot.skippedPlayers.has(idx))) { await slot._fireTimeoutNow?.(); await drainUntilSettled(); } signalSettle(); return accept(); } if (slot.isBlocking) { logAction(state, message); await slot._fireTimeoutNow?.(); await drainUntilSettled(); signalSettle(); return accept(); } return reject(); } const entry = findActionEntry(state, message.skillId, message.ownerId, message.actionType); if (entry?.validate(state, message.params) !== null) { await rollbackPreceding(); return reject(); } const targetKey = message.ownerId; const oldSlot = findPendingSlot(state, targetKey); if (oldSlot) { if (oldSlot.isTimeout) { await rollbackPreceding(); return reject(); } if (oldSlot.isBlocking && message.pendingSeq !== void 0 && message.pendingSeq < oldSlot.createdSeq) { await rollbackPreceding(); return reject(); } oldSlot.pause(); } if (hasPreceding) { state.viewBuffering = false; notifyStateChange(state); } const resolve = oldSlot?.resolve ?? (() => {}); logAction(state, message); const cleanupSlot = () => { if (!oldSlot) return; const key = extractPendingTarget(oldSlot.atom); let deleted = false; if (state.pendingSlots.get(key) === oldSlot) { state.pendingSlots.delete(key); deleted = true; } if (!deleted) { for (const [k, v] of state.pendingSlots) if (v === oldSlot) { state.pendingSlots.delete(k); deleted = true; break; } } if (deleted) notifyPendingResolved(state, oldSlot); resolve(); }; let executeError; const settleAfterDrain = async () => { await drainUntilSettled(); signalSettle(executeError); }; entry.execute(state, message.params).catch((err) => { const e = err instanceof Error ? err : new Error(String(err)); state.onError?.(e); executeError = e; }).then(cleanupSlot).finally(cleanupSlot).finally(settleAfterDrain); return accept(); } /** * 构造指定 viewer 视角的 GameView。 */ function buildView(state, viewer, debug = false) { return buildView$1(state, viewer, debug); } var init_core = __esmMin((() => { init_types(); init_buildView(); init_skill(); init_lifecycle(); init_deck(); init_notify(); init_registry(); })); //#endregion //#region src/engine/index.ts init_core(); //#endregion //#region src/client/view/reducer.ts init_atom(); /** * 按 ViewEvent 增量更新 GameView。 * * - 查找 AtomDefinition:优先 event.atomType(转换语义),fallback event.type * - 调用 applyView(view, event) 增量更新 * - 调用 toViewLog(event) 生成日志条目并 push 到 view.log(time 来自 envelope timestamp) * - 未实现 applyView 的 atom:view 不变(调用方可选择 fallback 全量 buildView) * * 注意:本函数原地突变 view(与后端 apply 原地突变 state 对称),不返回新对象。 */ function viewReducer(view, event, time = 0) { const def = getAtomDef(event.atomType ?? event.type); def.applyView?.(view, event); const resolveName = (idx) => { if (idx < 0) return void 0; return view.players.find((q) => q.index === idx)?.name; }; const logEntry = def.toViewLog?.(event, view.viewer, resolveName); if (logEntry) view.log.push({ time, player: logEntry.player, text: logEntry.text }); } //#endregion //#region src/client/headless/viewMaintainer.ts function applyServerMessage(prev, prevSeq, msg) { const base = { view: prev, lastSeq: prevSeq, newEvents: [], phaseChangedTo: null }; switch (msg.type) { case "initialView": { const view = msg.state; return { ...base, view, lastSeq: msg.lastSeq, seatIndex: view.viewer, phaseChangedTo: "playing" }; } case "event": { if (!prev) return base; if (msg.seq <= prevSeq) return base; let view = prev; const newEvents = []; if (msg.notify) { if (msg.notify.eventType === "pendingResolved") { const target = msg.notify.data.target; if (target !== void 0 && (target === view.viewer || target < 0) && view.pending) view = { ...view, pending: null }; } } if (msg.view) { view = { ...view, players: view.players.map((p) => ({ ...p, hand: p.hand ? [...p.hand] : p.hand, skills: [...p.skills ?? []], marks: [...p.marks ?? []], equipment: { ...p.equipment ?? {} }, pendingTricks: [...p.pendingTricks ?? []], distanceVars: p.distanceVars ? { ...p.distanceVars } : p.distanceVars, turnUsage: p.turnUsage ? { ...p.turnUsage } : p.turnUsage })), zones: view.zones ? { ...view.zones, processing: [...view.zones.processing ?? []] } : view.zones, log: [...view.log] }; viewReducer(view, msg.view, msg.timestamp); newEvents.push(msg.view); } if (msg.deadline !== void 0) { if (msg.deadline !== null && view.pending) view = { ...view, pending: { ...view.pending, deadline: msg.deadline.deadline, totalMs: msg.deadline.totalMs } }; view = { ...view, deadline: msg.deadline !== null ? msg.deadline.deadline : null, deadlineTotalMs: msg.deadline !== null ? msg.deadline.totalMs : 0 }; } return { ...base, view, lastSeq: msg.seq, newEvents }; } case "gameOver": return { ...base, phaseChangedTo: "ended", gameOverWinner: msg.winner }; case "game_reset": return { ...base, view: null, lastSeq: 0, resetToLobby: true, phaseChangedTo: "lobby" }; case "room_joined": return { ...base, playerId: msg.playerId, roomId: msg.roomId, seatIndex: typeof msg.seatIndex === "number" ? msg.seatIndex : base.seatIndex }; case "room_state": return { ...base, roomState: { readyPlayers: msg.readyPlayers, playerIds: msg.playerIds, hostId: msg.hostId, maxPlayers: msg.maxPlayers, config: msg.config, spectatorIds: msg.spectatorIds ?? [], viewGrants: msg.viewGrants ?? {}, pendingViewRequests: msg.pendingViewRequests ?? {}, roomType: msg.roomType, seats: msg.seats ?? [], pendingSeatSwaps: msg.pendingSeatSwaps ?? {}, playerNames: msg.playerNames ?? {} } }; case "room_config": return { ...base }; case "player_ready": return { ...base }; case "game_started": return { ...base, phaseChangedTo: "playing" }; case "actionRejected": return { ...base, actionRejected: true }; default: return base; } } /** 合并 room_config 增量到现有 roomState。viewMaintainer 不持有 roomState,供 HeadlessGameClient 用。 */ function mergeRoomConfig(prev, config) { return prev ? { ...prev, config } : prev; } //#endregion //#region src/client/utils/gameViewHelpers.ts init_cards(); init_action_active(); /** 从 use action 的 prompt 中提取 cardFilter 函数(若有) */ function extractCardFilter(prompt) { switch (prompt.type) { case "useCard": case "useCardAndTarget": return prompt.cardFilter.filter ?? null; default: return null; } } /** * 在一组 action 中,找出适用于指定卡牌的 use action(actionType='use' 且 * cardFilter 匹配)。返回第一个匹配项——同一种交互(使用牌)下,每张牌 * 恰好对应一个 use action(装备牌→装备通用,基本牌/锦囊→对应牌名的技能)。 * @param actions 候选 action 集合(通常是当前视角玩家的 skillActions) * @param card 当前选中的卡牌 */ function findUseActionForCard(actions, card) { return actions.find((a) => { if (a.actionType !== "use") return false; const filter = extractCardFilter(a.prompt); return filter ? filter(card) : false; }); } /** 判断一张牌是否有主动 use 入口(可否在出牌阶段主动打出)。 * 基于 CardEffect 注册表(静态,eager load),不依赖动态注册的 skillActions—— * 因此在 useSkillActions 异步注册间隙也能给出稳定答案,避免手牌闪烁。 * * timing='生效前' 的纯回应牌(闪/无懈可击)只有 respond 入口,无主动 use,在出牌阶段 * 不可主动打出;其余基本牌/锦囊(timing='出牌阶段')以及未注册 card-effect 的牌 * (装备牌由「装备通用」注册 use)均有 use 入口。 * 与 use-card onMount 跳过 timing='生效前' 的逻辑同源。 */ function hasUseEntry(card) { const effect = getCardEffect(card.name); return effect ? effect.timing !== "生效前" : true; } /** 不属于"出牌阶段替代出牌方式"的 actionType 判定。 * respond=被动回应(pending 驱动); * transform=转化技(transformMode 入口);distribute=分配(distributeMode 入口)。 * 另有声明型转化技的 actionType 前缀族 `transform:杀`/`transform:桃`…(界矫诏/界渐营 * 每个声明牌名注册一个 action)——同样各有 transformMode 交互入口,不作 altAction。 * 这些均有各自的交互入口,不应在选中牌后作为 altAction 按钮重复出现。 * use 不排除:同一张牌可能被多个 use action 匹配(如黑杀同时匹配"杀"和"断粮"), * 主 use action(findUseActionForCard 返回)作为主按钮,其余 use action 作为 * 替代出牌方式出现——这正是断粮/界断粮等转化类主动技的 UI 入口。 */ var NON_ALT_ACTION_TYPES = new Set([ "respond", "transform", "distribute" ]); /** actionType 是否属于"有独立交互入口、不作 altAction"的类型(集合 + transform: 前缀族)。 */ function isNonAltActionType(actionType) { return NON_ALT_ACTION_TYPES.has(actionType) || actionType.startsWith("transform:"); } /** * 找出适用于指定卡牌的替代出牌动作(如铁索连环·重铸)。 * 仅匹配真正的"出牌阶段替代出牌方式"(recast 等),排除 use/respond/transform/distribute * 及 transform: 前缀族——后者各有独立交互入口。避免选中桃后误出"出桃/respond""火攻/respond"等按钮。 * @param actions 候选 action 集合 * @param card 当前选中的卡牌 */ function findAltActionsForCard(actions, card, primaryAction) { return actions.filter((a) => { if (isNonAltActionType(a.actionType)) return false; if (primaryAction && a === primaryAction) return false; const filter = extractCardFilter(a.prompt); return filter ? filter(card) : false; }); } /** 判断一个 action 在给定上下文下是否激活。 * 优先用 action 声明的 activeWhen;未声明则用 defaultPlayActive(出牌场景默认: * 当前视角回合 + 出牌阶段 + 无阻塞型 pending)。 * 这是“声明时机”原则的落地点:激活条件由 action 自己说,GameView 不再硬编码分支。 * defaultPlayActive 与 engine/action-active 同源(技能 onMount 也复用它)。 */ function isActiveAction(action, ctx) { return action.activeWhen ? action.activeWhen(ctx) : defaultPlayActive(ctx); } /** 从 targetFilter + selfTarget 派生出牌规则 */ function derivePlayRules(targetFilter, selfTarget) { const slots = targetFilter?.slots; const hasSlots = !!slots && slots.length > 1; const slotCount = slots?.length ?? 0; const isSelf = !!selfTarget; return { needsTarget: isSelf ? false : hasSlots || (targetFilter ? targetFilter.max >= 1 : false), hasSlots, slotCount, selfTarget: isSelf, multiTarget: !hasSlots && !isSelf && (targetFilter?.max ?? 0) >= 2, targetFilter: targetFilter ?? null }; } /** * 构造出牌 action 的 params。 * 由出牌规则(PlayRules,从 use action prompt 派生)决定 target/targets/killTarget 字段。 * @param players 全部玩家(nameToIndex 用) * @param perspectiveIdx 当前视角座次(取自己 name) * @param card 要出的牌 * @param rules 出牌规则(从 prompt 派生) * @param selectedTarget 已选目标 name(A 目标) * @param selectedKillTarget 借刀杀人 B 目标 name * @returns params;若不满足出牌条件(需目标未选等)返回 null */ function buildPlayParams(players, perspectiveIdx, card, rules, selectedTarget, selectedKillTarget, selectedMultiTargets = []) { const selfName = players[perspectiveIdx]?.name ?? ""; if (rules.hasSlots) { if (!selectedTarget || !selectedKillTarget) return null; const aIdx = players.findIndex((p) => p.name === selectedTarget); const bIdx = players.findIndex((p) => p.name === selectedKillTarget); if (aIdx < 0 || bIdx < 0) return null; return { cardId: card.id, target: aIdx, killTarget: bIdx }; } if (rules.selfTarget) { const selfIdx = players.findIndex((p) => p.name === selfName); return { cardId: card.id, targets: [selfIdx >= 0 ? selfIdx : perspectiveIdx] }; } if (rules.multiTarget) { const min = rules.targetFilter?.min ?? 1; if (selectedMultiTargets.length < min) return null; const idxs = selectedMultiTargets.map((n) => players.findIndex((p) => p.name === n)).filter((i) => i >= 0); if (idxs.length < min) return null; return { cardId: card.id, targets: idxs }; } if (rules.needsTarget) { if (!selectedTarget) return null; const idx = players.findIndex((p) => p.name === selectedTarget); if (idx < 0) return null; if (card.type === "锦囊牌" && card.trickSubtype === "延时锦囊") return { cardId: card.id, target: idx }; return { cardId: card.id, targets: [idx] }; } return { cardId: card.id }; } /** * 解析 distribute 主动技的可选牌列表。 * 静态 cardIds 优先;其次按 source(hand / handAndEquip)动态取。 */ function resolveDistributeCardIds(prompt, hand, equipment) { if (Array.isArray(prompt.cardIds) && prompt.cardIds.length > 0) return prompt.cardIds; if (prompt.source === "handAndEquip") { const equipIds = Object.values(equipment).filter((id) => typeof id === "string"); return [...hand.map((c) => c.id), ...equipIds]; } return hand.map((c) => c.id); } //#endregion //#region src/client/headless/availableActions.ts init_action_active(); /** 从 use action 的 prompt 取 targetFilter(useCardAndTarget/selectTarget 才有)。 */ function getTargetFilter(prompt) { if (prompt.type === "useCardAndTarget" || prompt.type === "selectTarget") return prompt.targetFilter; return null; } function getSelfTarget(prompt) { return prompt.type === "useCardAndTarget" ? !!prompt.selfTarget : false; } /** 计算 useCardAndTarget 的合法目标列表。 * 默认排除自己;targetFilter.allowSelf=true(铁索连环含自己)时纳入自己。 * 与 engine/card-effect/validate.ts isLegalTarget 语义对齐(allowSelf ⟷ kind='any')。 */ function computeValidTargets(view, seatIndex, targetFilter, rules) { const validTargets = []; if (rules.needsTarget && !rules.hasSlots && !rules.selfTarget) { const allowSelf = !!targetFilter?.allowSelf; for (const p of view.players) { if (!p.alive) continue; if (p.index === seatIndex && !allowSelf) continue; if (targetFilter?.filter && !targetFilter.filter(view, p.index)) continue; validTargets.push(p.index); } } else if (rules.selfTarget) validTargets.push(seatIndex); return validTargets; } /** 目标数上下界(prompt.targetFilter 的投影)。 * 仅在「需要目标且非自动自身目标」时给值:min 缺省 1(不设字段),max 缺省 1(不设字段)。 * 引擎 validate 用同一份 targetFilter 判定,AI 少了这个下界就只会填 1 个目标 → * 离间(两名男性角色)一类多目标技能恒拒。 */ function targetBounds(targetFilter, rules) { if (!rules.needsTarget || rules.selfTarget) return {}; const min = targetFilter?.min ?? 1; const max = targetFilter?.max ?? 1; return { ...min > 1 ? { minTarget: min } : {}, ...max > 1 ? { maxTarget: max } : {} }; } /** 出牌阶段枚举主动可出的牌。 */ function enumeratePlayActions(view, seatIndex, skillActions) { const ctx = { view, perspectiveIdx: seatIndex }; const me = view.players[seatIndex]; if (!me?.hand) return []; const result = []; for (const card of me.hand) { const action = findUseActionForCard(skillActions, card); if (!action) continue; if (!isActiveAction(action, ctx)) continue; const targetFilter = getTargetFilter(action.prompt); const rules = derivePlayRules(targetFilter, getSelfTarget(action.prompt)); const validTargets = computeValidTargets(view, seatIndex, targetFilter, rules); if (rules.needsTarget && !rules.selfTarget && validTargets.length === 0) continue; const sampleParams = rules.selfTarget ? buildPlayParams(view.players, seatIndex, card, rules, null, null) : rules.needsTarget && !rules.hasSlots ? { cardId: card.id } : buildPlayParams(view.players, seatIndex, card, rules, null, null); const message = { skillId: action.skillId, actionType: "use", ownerId: seatIndex, params: sampleParams ?? { cardId: card.id }, baseSeq: 0 }; const cardDesc = `${card.suit}${card.rank}`; const slashMax = card.name === "杀" && rules.needsTarget && !rules.selfTarget ? viewSlashTargetMax(view, seatIndex, card) : void 0; const bounds = targetBounds(targetFilter, rules); result.push({ description: rules.needsTarget && !rules.selfTarget ? slashMax && slashMax > 1 ? `使用【${card.name}】(${cardDesc}) 选择目标(最多${slashMax}个)` : `使用【${card.name}】(${cardDesc}) 选择目标` : `使用【${card.name}】(${cardDesc})`, message, validTargets, category: "play", ...bounds, ...slashMax !== void 0 ? { maxTarget: slashMax } : {} }); } return result; } /** * 枚举转化类技能动作(武圣/丈八蛇矛)。 * transform action 的 prompt.type 是 useCardAndTarget,有 cardFilter + targetFilter。 * 提交格式:主 action(杀.use) + preceding(transform)。 * - 单卡转化(武圣,min=1):每张匹配牌一个 action,cardId 为影子 id `${原id}#skillId`。 * - 多卡转化(丈八蛇矛,min>=2):组合数大,只生成描述性 action 提示 agent。 */ function enumerateTransformActions(view, seatIndex, skillActions) { const ctx = { view, perspectiveIdx: seatIndex }; const me = view.players[seatIndex]; if (!me?.hand) return []; const result = []; const pendingSlot = view.pending; const pendingAtomType = (pendingSlot?.atom)?.type; const pendingReqType = (pendingSlot?.atom)?.requestType; const pendingRequestedName = (() => { if (!pendingSlot) return null; if (pendingSlot.target !== seatIndex && pendingSlot.target >= 0) return null; if (pendingAtomType?.startsWith("询问")) return pendingAtomType.slice(2) || null; if (pendingAtomType === "请求回应" && pendingReqType) { const sep = pendingReqType.search(/[/_]/); return (sep >= 0 ? pendingReqType.slice(0, sep) : pendingReqType) || null; } return null; })(); const isRespondCtx = pendingRequestedName !== null; for (const action of skillActions) { if (!action.transform) continue; if (!isActiveAction(action, ctx)) continue; const filter = extractCardFilter(action.prompt); if (!filter) continue; const cardFilter = action.prompt.type === "useCardAndTarget" || action.prompt.type === "useCard" ? action.prompt.cardFilter : null; if (!cardFilter) continue; if ((cardFilter.min ?? 1) > 1) { const matchingCards = me.hand.filter(filter); const comboFilter = cardFilter.comboFilter; const combosOk = (c1, c2) => !comboFilter || comboFilter([c1, c2]); if (isRespondCtx) { for (let i = 0; i < matchingCards.length; i++) for (let j = i + 1; j < matchingCards.length; j++) { const c1 = matchingCards[i]; const c2 = matchingCards[j]; if (!combosOk(c1, c2)) continue; const wrapperName = action.transform(c1).name; if (wrapperName !== pendingRequestedName) continue; const shadowCardId = `${c1.id}#${c2.id}#${action.skillId}`; const desc = `${c1.suit}${c1.rank}+${c2.suit}${c2.rank}`; result.push({ description: `${action.skillId}转化【${wrapperName}】(${desc})打出`, message: { skillId: wrapperName, actionType: "respond", ownerId: seatIndex, params: { cardId: shadowCardId }, preceding: [{ skillId: action.skillId, actionType: action.actionType, params: { cardIds: [c1.id, c2.id] } }], baseSeq: 0 }, validTargets: [], category: "transform" }); } continue; } const targetFilter = getTargetFilter(action.prompt); const rules = derivePlayRules(targetFilter, getSelfTarget(action.prompt)); for (let i = 0; i < matchingCards.length; i++) for (let j = i + 1; j < matchingCards.length; j++) { const c1 = matchingCards[i]; const c2 = matchingCards[j]; if (!combosOk(c1, c2)) continue; const wrapperName = action.transform(c1).name; const produced = findUseActionForCard(skillActions, { ...c1, name: wrapperName }); if (!hasUseEntry({ name: wrapperName })) continue; if (produced && !isActiveAction(produced, ctx)) continue; const cardTargetFilter = produced ? getTargetFilter(produced.prompt) : targetFilter; const cardRules = produced ? derivePlayRules(cardTargetFilter, getSelfTarget(produced.prompt)) : rules; const validTargets = computeValidTargets(view, seatIndex, cardTargetFilter, cardRules); if (cardRules.needsTarget && !cardRules.selfTarget && validTargets.length === 0) continue; const shadowCardId = `${c1.id}#${c2.id}#${action.skillId}`; const desc = `${c1.suit}${c1.rank}+${c2.suit}${c2.rank}`; const bounds = targetBounds(cardTargetFilter, cardRules); const slashMax = wrapperName === "杀" && cardRules.needsTarget && !cardRules.selfTarget ? viewSlashTargetMax(view, seatIndex, { name: "杀" }) : void 0; const mainParams = cardRules.selfTarget ? { cardId: shadowCardId, targets: [seatIndex] } : { cardId: shadowCardId }; result.push({ description: wrapperName === "杀" && slashMax && slashMax > 1 ? `${action.skillId}转化【${wrapperName}】(${desc}) (最多${slashMax}目标)` : `${action.skillId}转化【${wrapperName}】(${desc})`, message: { skillId: wrapperName, actionType: "use", ownerId: seatIndex, params: mainParams, preceding: [{ skillId: action.skillId, actionType: action.actionType, params: { cardIds: [c1.id, c2.id] } }], baseSeq: 0 }, validTargets, category: "transform", ...bounds, ...slashMax !== void 0 ? { maxTarget: slashMax } : {} }); } continue; } const matchingCards = me.hand.filter(filter); if (isRespondCtx) { for (const card of matchingCards) { const wrapperName = action.transform ? action.transform(card).name : "杀"; if (wrapperName !== pendingRequestedName) continue; const shadowCardId = `${card.id}#${action.skillId}`; const cardDesc = `${card.suit}${card.rank}`; result.push({ description: `${action.skillId}转化【${wrapperName}】(${cardDesc})打出`, message: { skillId: wrapperName, actionType: "respond", ownerId: seatIndex, params: { cardId: shadowCardId }, preceding: [{ skillId: action.skillId, actionType: action.actionType, params: { cardId: card.id } }], baseSeq: 0 }, validTargets: [], category: "transform" }); } continue; } const targetFilter = getTargetFilter(action.prompt); const rules = derivePlayRules(targetFilter, getSelfTarget(action.prompt)); for (const card of matchingCards) { const wrapperName = action.transform ? action.transform(card).name : "杀"; const shadowCardId = `${card.id}#${action.skillId}`; const produced = findUseActionForCard(skillActions, { ...card, name: wrapperName }); if (!hasUseEntry({ name: wrapperName })) continue; if (produced && !isActiveAction(produced, ctx)) continue; const cardTargetFilter = produced ? getTargetFilter(produced.prompt) : targetFilter; const cardRules = produced ? derivePlayRules(cardTargetFilter, getSelfTarget(produced.prompt)) : rules; const validTargets = computeValidTargets(view, seatIndex, cardTargetFilter, cardRules); if (cardRules.needsTarget && !cardRules.selfTarget && validTargets.length === 0) continue; const cardDesc = `${card.suit}${card.rank}`; const slashMax = wrapperName === "杀" && cardRules.needsTarget && !cardRules.selfTarget ? viewSlashTargetMax(view, seatIndex, { name: "杀" }) : void 0; const mainParams = cardRules.selfTarget ? { cardId: shadowCardId, targets: [seatIndex] } : { cardId: shadowCardId }; result.push({ description: wrapperName === "杀" && slashMax && slashMax > 1 ? `${action.skillId}转化【${wrapperName}】(${cardDesc}) (最多${slashMax}目标)` : `${action.skillId}转化【${wrapperName}】(${cardDesc})`, message: { skillId: wrapperName, actionType: "use", ownerId: seatIndex, params: mainParams, preceding: [{ skillId: action.skillId, actionType: action.actionType, params: { cardId: card.id } }], baseSeq: 0 }, validTargets, category: "transform", ...slashMax !== void 0 ? { maxTarget: slashMax } : {} }); } } return result; } /** * 枚举分配类技能动作(制衡/仁德)。 * 这些 action 的 actionType 是 'use',但 prompt.type 是 'distribute', * 被 findUseActionForCard 跳过(它只匹配 useCard/useCardAndTarget)。 * - select 模式(制衡):选牌弃置换牌,无目标,params={cardIds:[]}。 * - allocate 模式(仁德):分配手牌给目标,params={allocation:[]},validTargets 为可选目标。 */ function enumerateDistributeActions(view, seatIndex, skillActions) { const ctx = { view, perspectiveIdx: seatIndex }; const result = []; for (const action of skillActions) { if (action.actionType !== "use") continue; if (action.prompt.type !== "distribute") continue; if (!isActiveAction(action, ctx)) continue; const prompt = action.prompt; if ((prompt.mode ?? "allocate") === "select") { const sourceDesc = prompt.source === "handAndEquip" ? "手牌或装备" : "手牌"; result.push({ description: `发动【${action.skillId}】(选${sourceDesc}弃置换牌)`, message: { skillId: action.skillId, actionType: "use", ownerId: seatIndex, params: { cardIds: [] }, baseSeq: 0 }, validTargets: [], category: "distribute" }); } else { const allowSelf = prompt.allowSelf !== false; const targetFilterFn = prompt.targetFilter; const validTargets = []; for (const p of view.players) { if (!p.alive) continue; if (p.index === seatIndex && !allowSelf) continue; if (targetFilterFn && !targetFilterFn(view, p.index)) continue; validTargets.push(p.index); } result.push({ description: `发动【${action.skillId}】(分配手牌给目标)`, message: { skillId: action.skillId, actionType: "use", ownerId: seatIndex, params: { allocation: [] }, baseSeq: 0 }, validTargets, category: "distribute" }); } } return result; } /** 枚举替代出牌动作(同一张牌的其他出法,如铁索连环·重铸、连环·重铸)。 * 这些 action 不属于 use/respond/transform/distribute(各有独立入口), * 通过 findAltActionsForCard 匹配手牌,生成无目标的 play action(params={cardId})。 * 无头客户端此前缺这一类枚举 → AI 无法重铸铁索连环。 */ function enumerateAltActions(view, seatIndex, skillActions) { const ctx = { view, perspectiveIdx: seatIndex }; const me = view.players[seatIndex]; if (!me?.hand) return []; const result = []; for (const card of me.hand) { const alts = findAltActionsForCard(skillActions, card, findUseActionForCard(skillActions, card)); for (const action of alts) { if (!isActiveAction(action, ctx)) continue; const targetFilter = getTargetFilter(action.prompt); const rules = derivePlayRules(targetFilter, getSelfTarget(action.prompt)); const validTargets = computeValidTargets(view, seatIndex, targetFilter, rules); if (rules.needsTarget && !rules.selfTarget && validTargets.length === 0) continue; const cardDesc = `${card.suit}${card.rank}`; const sampleParams = rules.selfTarget ? buildPlayParams(view.players, seatIndex, card, rules, null, null) : rules.needsTarget && !rules.hasSlots ? { cardId: card.id } : buildPlayParams(view.players, seatIndex, card, rules, null, null); const bounds = targetBounds(targetFilter, rules); result.push({ description: bounds.minTarget && bounds.minTarget > 1 ? `${action.label}(${cardDesc}) 选择目标(需 ${bounds.minTarget} 个)` : `${action.label}(${cardDesc})`, message: { skillId: action.skillId, actionType: action.actionType, ownerId: seatIndex, params: sampleParams ?? { cardId: card.id }, baseSeq: 0 }, validTargets, category: "play", ...bounds }); } } return result; } /** * 枚举 prompt 非 useCard/useCardAndTarget/distribute 的主动技 use action。 * * 这些技能不需要选中手牌,而是「点按钮 → 直接提交 params」(与前端 handleSkillAction * 的 default/selectTarget/choosePlayer 分支同构): * - confirm(苦肉/缔盟/据守/奇谋/成略/界国色/界焚城/界甘露/界酒诗/乱武 等):params={} * - selectTarget(挑衅/强袭/反间/攻心/雄乱/界翦灭/界势斩/界解烦/界献州):params.target * (+ targets 数组,反间/雄乱 读 targets);prompt.paramVariants 声明的额外参数 * (强袭代价 cost)按「变体 × 目标」展开,避免提交缺参被 validate 拒。 * targetFilter.min>1 的多目标技能改为描述性 action(validTargets + minTarget, * agent 填 targets),paramVariants 同样按变体展开。 * - choosePlayer(激将/界激将):每个候选目标一个 action(params.target); * prompt.min>1 时镜像 selectTarget 的描述性多选 action。 * - chooseOption(决堰):每个选项一个 action(params.option) * * 此前这四类 prompt 无任何枚举路径(只有 useCard/useCardAndTarget/distribute 三种), * 无头客户端/AI 永远看不到这些技能 → 整类主动技不可发动。 */ function enumeratePromptActions(view, seatIndex, skillActions) { const ctx = { view, perspectiveIdx: seatIndex }; const me = view.players[seatIndex]; if (!me?.hand) return []; const result = []; for (const action of skillActions) { if (action.actionType !== "use") continue; const prompt = action.prompt; if (prompt.type !== "confirm" && prompt.type !== "selectTarget" && prompt.type !== "choosePlayer" && prompt.type !== "chooseOption") continue; if (!isActiveAction(action, ctx)) continue; const base = { skillId: action.skillId, actionType: "use", ownerId: seatIndex, baseSeq: 0 }; if (prompt.type === "confirm") { result.push({ description: `发动【${action.label}】(${prompt.title})`, message: { ...base, params: {} }, validTargets: [], category: "play" }); continue; } if (prompt.type === "chooseOption") { for (const opt of prompt.options) result.push({ description: `发动【${action.label}】:${opt.label}`, message: { ...base, params: { option: opt.value } }, validTargets: [], category: "play" }); continue; } let validTargets; if (prompt.type === "choosePlayer") validTargets = prompt.candidates ?? view.players.filter((p) => p.alive && (!prompt.filter || prompt.filter(view, p.index))).map((p) => p.index); else { const filter = prompt.targetFilter.filter; validTargets = view.players.filter((p) => p.alive && (!filter || filter(view, p.index))).map((p) => p.index); } if (validTargets.length === 0) continue; const variants = prompt.type === "selectTarget" && prompt.paramVariants?.length ? prompt.paramVariants : [{ label: "", params: {} }]; const equipIds = new Set(Object.values(me.equipment ?? {}).filter((id) => typeof id === "string")); const costCardsOf = (variant) => variant.cardFilter ? [...(me.hand ?? []).filter((c) => variant.cardFilter(c)), ...[...equipIds].map((id) => view.cardMap[id]).filter((c) => !!c && variant.cardFilter(c))] : [void 0]; const multiMin = prompt.type === "selectTarget" ? prompt.targetFilter.min ?? 1 : prompt.type === "choosePlayer" ? prompt.min ?? 1 : 1; if (multiMin > 1) { const max = prompt.type === "selectTarget" ? prompt.targetFilter.max ?? multiMin : prompt.type === "choosePlayer" ? prompt.max ?? multiMin : multiMin; for (const variant of variants) { const suffix = variant.label ? `(${variant.label})` : ""; const costCards = costCardsOf(variant); if (variant.cardFilter && costCards.length === 0) continue; for (const costCard of costCards) { const cardSuffix = costCard ? `(${costCard.suit}${costCard.rank})` : ""; result.push({ description: `发动【${action.label}】${suffix}${cardSuffix}(选 ${multiMin}${max > multiMin ? `-${max}` : ""} 个目标)`, message: { ...base, params: { targets: [], ...variant.params, ...costCard ? { cardId: costCard.id } : {} } }, validTargets, category: "play", minTarget: multiMin, ...max > 1 ? { maxTarget: max } : {} }); } } continue; } for (const t of validTargets) { const targetName = view.players[t]?.name ?? `P${t}`; for (const variant of variants) { const suffix = variant.label ? `(${variant.label})` : ""; const costCards = costCardsOf(variant); if (variant.cardFilter && costCards.length === 0) continue; for (const costCard of costCards) { const cardSuffix = costCard ? `(${costCard.suit}${costCard.rank})` : ""; result.push({ description: `发动【${action.label}】${suffix}${cardSuffix} → ${targetName}`, message: { ...base, params: { target: t, targets: [t], ...variant.params, ...costCard ? { cardId: costCard.id } : {} } }, validTargets: [t], category: "play" }); } } } } return result; } /** 主入口:枚举当前座次可执行的操作(出牌/转化/替代出牌/分配/技能按钮/结束出牌阶段)。 */ function enumerateAvailableActions(view, seatIndex, skillActions) { if (!view) return []; const actions = [ ...enumeratePlayActions(view, seatIndex, skillActions), ...enumerateTransformActions(view, seatIndex, skillActions), ...enumerateAltActions(view, seatIndex, skillActions), ...enumerateDistributeActions(view, seatIndex, skillActions), ...enumeratePromptActions(view, seatIndex, skillActions) ]; if (view.currentPlayerIndex === seatIndex && view.phase === "出牌" && (!view.pending || view.pending.isBlocking === false)) actions.push({ description: "结束出牌阶段", message: { skillId: "回合管理", actionType: "end", ownerId: seatIndex, params: {}, baseSeq: 0 }, validTargets: [], category: "play" }); return actions; } //#endregion //#region src/client/skillActionRegistry.ts init_lifecycle(); var registry = /* @__PURE__ */ new Map(); function actionKey(skillId, ownerId, actionType) { return `${skillId}:${ownerId}:${actionType}`; } /** 创建一个 FrontendAPI 实例,defineAction 调用会注册到全局注册表 */ function makeFrontendAPI(skillId, ownerId) { return { viewer: ownerId, onEvent() { return () => {}; }, defineAction(actionType, opts, skillIdOverride) { const sid = skillIdOverride ?? skillId; registry.set(actionKey(sid, ownerId, actionType), { skillId: sid, ownerId, actionType, label: opts.label, style: opts.style, prompt: opts.prompt, transform: opts.transform, activeWhen: opts.activeWhen, respondFor: opts.respondFor }); }, playEffect() {} }; } /** * 为一个玩家初始化所有技能的前端 action 注册。 * 通过 import() 动态加载技能模块,调用每个技能 module 的 onMount。 * @param playerIndex 玩家座次下标 */ async function registerSkillActions(playerIndex, skillIds) { for (const skillId of skillIds) try { const mod = await getSkillModule(skillId); if (!mod.onMount) continue; const skill = mod.createSkill(skillId, playerIndex); const api = makeFrontendAPI(skillId, playerIndex); mod.onMount(skill, api); } catch {} } /** * 获取某个玩家已注册的所有 action 定义。 * 用于前端渲染技能按钮区。 * @param playerIndex 玩家座次下标 */ function getActionsForPlayer(playerIndex) { const result = []; for (const [, def] of registry) if (def.ownerId === playerIndex) result.push(def); return result; } /** * 跨所有 ownerId 查找指定 skillId + actionType 的 action 定义。 * 用于视角切换的瞬态场景:target 玩家 action 已被清/未注册到当前 perspective 时 * 仍能在 registry 里找到定义。 */ function findActionAcrossOwners(skillId, actionType) { for (const def of registry.values()) if (def.skillId === skillId && def.actionType === actionType) return def; } /** 清空注册表(用于测试隔离或重连) */ function clearRegistry() { registry.clear(); } //#endregion //#region src/client/utils/pendingRespond.ts /** 安全读取 pending.atom.requestType(消除散落各处的 `as { requestType?: string }` 断言)。 * atom 可能为 null 或不含 requestType 字段,统一在此处理。 */ function getPendingRequestType(pending) { const v = pending.atom?.["requestType"]; return typeof v === "string" ? v : ""; } /** 从 pending 读取引擎投影层下发的 cardFilter.candidates(合法手牌 id 列表)。 * pending.prompt 与 pending.atom.prompt 两处均可能携带(applyView 同时写入),取非空者。 */ function readCardFilterCandidates(pending) { const prompts = [pending.prompt, pending.atom?.prompt]; for (const p of prompts) { const c = p?.cardFilter?.candidates; if (Array.isArray(c)) return c; } } /** 从 SkillActionDef 的 prompt 提取 cardFilter 函数(不走 JSON 序列化,函数引用保留) */ function extractCardFilterFromAction(action) { const p = action.prompt; if ((p.type === "useCard" || p.type === "useCardAndTarget") && p.cardFilter?.filter) return p.cardFilter.filter; } /** * 从 atom 类型本地构造 cardFilter 函数(不依赖 registry)。 * 当前所有 respond 提示的 filter 都是 `c => c.name === ''`: * 询问X → c.name==='X';请求回应 R/Y → c.name===R;__弃牌 → ()=>true。 */ function deriveCardFilterFromAtom(atomType, reqType) { if (atomType.startsWith("询问")) { const cardName = atomType.slice(2); if (!cardName) return void 0; return (c) => c.name === cardName; } if (atomType === "请求回应") { if (!reqType) return void 0; if (reqType === "__弃牌") return () => true; if (reqType === "桃/求桃") return (c) => c.name === "桃" || c.name === "酒"; const slashIdx = reqType.indexOf("/"); const cardName = slashIdx >= 0 ? reqType.slice(0, slashIdx) : reqType; if (!cardName) return void 0; return (c) => c.name === cardName; } } /** * 从 skillActionRegistry(已注册的所有玩家 actions)中查找某 skillId 的 respond action。 * 优先当前 perspective 玩家(快路径),退路跨所有 ownerId 扫描(单例,不依赖 React state 时序)。 */ function findRespondAction(skillId, skillActions) { const own = skillActions.find((a) => a.skillId === skillId && a.actionType === "respond"); if (own) return own; return findActionAcrossOwners(skillId, "respond"); } /** * 推导当前 pending 的 respond 信息。 * @param pending 当前 view.pending(可能为 null) * @param skillActions 当前 perspective 玩家的已注册技能 actions * @returns skillId + cardFilter;pending 为 null 或无法推导时返回 null */ function resolvePendingRespond(pending, skillActions) { if (!pending) return null; const atomType = pending.atom?.type ?? ""; const reqType = getPendingRequestType(pending); let skillId = null; if (atomType.startsWith("询问")) skillId = atomType.slice(2); else if (reqType === "__弃牌") skillId = "系统规则"; else if (atomType === "请求回应") { if (!reqType) return null; const sepIdx = reqType.search(/[/_]/); skillId = sepIdx >= 0 ? reqType.slice(0, sepIdx) : reqType || null; } if (!skillId) return null; const PEACH_RESCUE = "桃/求桃"; if (reqType === PEACH_RESCUE) { const transformRescue = skillActions.filter((a) => a.respondFor === PEACH_RESCUE); const candidates = readCardFilterCandidates(pending); const candSet = candidates ? new Set(candidates) : null; const literalMatch = candSet ? (c) => candSet.has(c.id) : (c) => c.name === "桃" || c.name === "酒"; const transformMatch = (c) => transformRescue.some((a) => extractCardFilterFromAction(a)?.(c)); return { skillId, cardFilter: (c) => literalMatch(c) || transformMatch(c), rescueSkillForCard: (c) => { if (c.name === "桃" || c.name === "酒") return c.name; return transformRescue.find((a) => extractCardFilterFromAction(a)?.(c))?.skillId; } }; } const candidates = readCardFilterCandidates(pending); if (candidates) { const set = new Set(candidates); return { skillId, cardFilter: (c) => set.has(c.id) }; } const action = findRespondAction(skillId, skillActions); const registryFilter = action ? extractCardFilterFromAction(action) : void 0; const localFilter = deriveCardFilterFromAtom(atomType, reqType); return { skillId, cardFilter: registryFilter ?? localFilter }; } //#endregion //#region src/client/utils/autoSkip.ts /** 默认延迟范围:500ms–2000ms,模拟人类思考时间,防手牌信息泄露。 */ var DEFAULT_DELAY_RANGE = [500, 2e3]; var DEFAULT_PREFS = { optInSkip: {} }; /** pending 是否可被自动跳过(排除出牌窗口、强制型询问)。 */ function isAutoSkippable(pending) { if (pending.isBlocking === false) return false; if (pending.mandatory === true) return false; return true; } /** 在 [min,max] 范围内取随机延迟毫秒。 */ function randomDelay(range, rng) { const [min, max] = range; return Math.round(min + rng() * (max - min)); } /** * 自动跳过决策(纯函数,不依赖 React/headless)。 * * 优先级(高→低): * 1. pending 为空 / 不可跳过(出牌窗口/强制型) → wait * 2. 用户开启该类型策略跳过 → act-delayed(不暴露能否响应) * 3. 能响应 → wait * 4. 空手牌(公开) → act-now * 5. 有手牌但无法响应(私有) → act-delayed */ function decideAutoSkip(input) { const { pending, canRespond, handCount, prefs } = input; if (!pending || !isAutoSkippable(pending)) return { kind: "wait" }; const range = input.delayRange ?? DEFAULT_DELAY_RANGE; const rng = input.rng ?? Math.random; const reqType = getPendingRequestType(pending); if (reqType && prefs.optInSkip[reqType]) return { kind: "act-delayed", ms: randomDelay(range, rng) }; if (canRespond) return { kind: "wait" }; if (handCount === 0) return { kind: "act-now" }; return { kind: "act-delayed", ms: randomDelay(range, rng) }; } //#endregion //#region node_modules/.pnpm/eventsource-parser@3.1.0/node_modules/eventsource-parser/dist/index.js function noop(_arg) {} function createParser(config) { if (typeof config == "function") throw new TypeError("`config` must be an object, got a function instead. Did you mean `createParser({onEvent: fn})`?"); const { onEvent = noop, onError = noop, onRetry = noop, onComment, maxBufferSize } = config, pendingFragments = []; let pendingFragmentsLength = 0, isFirstChunk = !0, id, data = "", dataLines = 0, eventType, terminated = !1; function feed(chunk) { if (terminated) throw new Error("Cannot feed parser: it was terminated after exceeding the configured max buffer size. Call `reset()` to resume parsing."); if (isFirstChunk && (isFirstChunk = !1, chunk.charCodeAt(0) === 239 && chunk.charCodeAt(1) === 187 && chunk.charCodeAt(2) === 191 && (chunk = chunk.slice(3))), pendingFragments.length === 0) { const trailing2 = processLines(chunk); trailing2 !== "" && (pendingFragments.push(trailing2), pendingFragmentsLength = trailing2.length), checkBufferSize(); return; } if (chunk.indexOf(` `) === -1 && chunk.indexOf("\r") === -1) { pendingFragments.push(chunk), pendingFragmentsLength += chunk.length, checkBufferSize(); return; } pendingFragments.push(chunk); const input = pendingFragments.join(""); pendingFragments.length = 0, pendingFragmentsLength = 0; const trailing = processLines(input); trailing !== "" && (pendingFragments.push(trailing), pendingFragmentsLength = trailing.length), checkBufferSize(); } function checkBufferSize() { maxBufferSize !== void 0 && (pendingFragmentsLength + data.length <= maxBufferSize || (terminated = !0, pendingFragments.length = 0, pendingFragmentsLength = 0, id = void 0, data = "", dataLines = 0, eventType = void 0, onError(new ParseError(`Buffered data exceeded max buffer size of ${maxBufferSize} characters`, { type: "max-buffer-size-exceeded" })))); } function processLines(chunk) { let searchIndex = 0; if (chunk.indexOf("\r") === -1) { let lfIndex = chunk.indexOf(` `, searchIndex); for (; lfIndex !== -1;) { if (searchIndex === lfIndex) { dataLines > 0 && onEvent({ id, event: eventType, data }), id = void 0, data = "", dataLines = 0, eventType = void 0, searchIndex = lfIndex + 1, lfIndex = chunk.indexOf(` `, searchIndex); continue; } const firstCharCode = chunk.charCodeAt(searchIndex); if (isDataPrefix(chunk, searchIndex, firstCharCode)) { const valueStart = chunk.charCodeAt(searchIndex + 5) === SPACE ? searchIndex + 6 : searchIndex + 5, value = chunk.slice(valueStart, lfIndex); if (dataLines === 0 && chunk.charCodeAt(lfIndex + 1) === LF) { onEvent({ id, event: eventType, data: value }), id = void 0, data = "", eventType = void 0, searchIndex = lfIndex + 2, lfIndex = chunk.indexOf(` `, searchIndex); continue; } data = dataLines === 0 ? value : `${data} ${value}`, dataLines++; } else isEventPrefix(chunk, searchIndex, firstCharCode) ? eventType = chunk.slice(chunk.charCodeAt(searchIndex + 6) === SPACE ? searchIndex + 7 : searchIndex + 6, lfIndex) || void 0 : parseLine(chunk, searchIndex, lfIndex); searchIndex = lfIndex + 1, lfIndex = chunk.indexOf(` `, searchIndex); } return chunk.slice(searchIndex); } for (; searchIndex < chunk.length;) { const crIndex = chunk.indexOf("\r", searchIndex), lfIndex = chunk.indexOf(` `, searchIndex); let lineEnd = -1; if (crIndex !== -1 && lfIndex !== -1 ? lineEnd = crIndex < lfIndex ? crIndex : lfIndex : crIndex !== -1 ? crIndex === chunk.length - 1 ? lineEnd = -1 : lineEnd = crIndex : lfIndex !== -1 && (lineEnd = lfIndex), lineEnd === -1) break; parseLine(chunk, searchIndex, lineEnd), searchIndex = lineEnd + 1, chunk.charCodeAt(searchIndex - 1) === CR && chunk.charCodeAt(searchIndex) === LF && searchIndex++; } return chunk.slice(searchIndex); } function parseLine(chunk, start, end) { if (start === end) { dispatchEvent(); return; } const firstCharCode = chunk.charCodeAt(start); if (isDataPrefix(chunk, start, firstCharCode)) { const valueStart = chunk.charCodeAt(start + 5) === SPACE ? start + 6 : start + 5, value2 = chunk.slice(valueStart, end); data = dataLines === 0 ? value2 : `${data} ${value2}`, dataLines++; return; } if (isEventPrefix(chunk, start, firstCharCode)) { eventType = chunk.slice(chunk.charCodeAt(start + 6) === SPACE ? start + 7 : start + 6, end) || void 0; return; } if (firstCharCode === 105 && chunk.charCodeAt(start + 1) === 100 && chunk.charCodeAt(start + 2) === 58) { const value2 = chunk.slice(chunk.charCodeAt(start + 3) === SPACE ? start + 4 : start + 3, end); id = value2.includes("\0") ? void 0 : value2; return; } if (firstCharCode === 58) { if (onComment) onComment(chunk.slice(start, end).slice(chunk.charCodeAt(start + 1) === SPACE ? 2 : 1)); return; } const line = chunk.slice(start, end), fieldSeparatorIndex = line.indexOf(":"); if (fieldSeparatorIndex === -1) { processField(line, "", line); return; } const field = line.slice(0, fieldSeparatorIndex), offset = line.charCodeAt(fieldSeparatorIndex + 1) === SPACE ? 2 : 1; processField(field, line.slice(fieldSeparatorIndex + offset), line); } function processField(field, value, line) { switch (field) { case "event": eventType = value || void 0; break; case "data": data = dataLines === 0 ? value : `${data} ${value}`, dataLines++; break; case "id": id = value.includes("\0") ? void 0 : value; break; case "retry": /^\d+$/.test(value) ? onRetry(parseInt(value, 10)) : onError(new ParseError(`Invalid \`retry\` value: "${value}"`, { type: "invalid-retry", value, line })); break; default: onError(new ParseError(`Unknown field "${field.length > 20 ? `${field.slice(0, 20)}\u2026` : field}"`, { type: "unknown-field", field, value, line })); break; } } function dispatchEvent() { dataLines > 0 && onEvent({ id, event: eventType, data }), id = void 0, data = "", dataLines = 0, eventType = void 0; } function reset(options = {}) { if (options.consume && pendingFragments.length > 0) { const incompleteLine = pendingFragments.join(""); parseLine(incompleteLine, 0, incompleteLine.length); } isFirstChunk = !0, id = void 0, data = "", dataLines = 0, eventType = void 0, pendingFragments.length = 0, pendingFragmentsLength = 0, terminated = !1; } return { feed, reset }; } function isDataPrefix(chunk, i, firstCharCode) { return firstCharCode === 100 && chunk.charCodeAt(i + 1) === 97 && chunk.charCodeAt(i + 2) === 116 && chunk.charCodeAt(i + 3) === 97 && chunk.charCodeAt(i + 4) === 58; } function isEventPrefix(chunk, i, firstCharCode) { return firstCharCode === 101 && chunk.charCodeAt(i + 1) === 118 && chunk.charCodeAt(i + 2) === 101 && chunk.charCodeAt(i + 3) === 110 && chunk.charCodeAt(i + 4) === 116 && chunk.charCodeAt(i + 5) === 58; } var ParseError, LF, CR, SPACE; var init_dist$1 = __esmMin((() => { ParseError = class extends Error { constructor(message, options) { super(message), this.name = "ParseError", this.type = options.type, this.field = options.field, this.value = options.value, this.line = options.line; } }; LF = 10, CR = 13, SPACE = 32; })); //#endregion //#region node_modules/.pnpm/eventsource@4.1.0/node_modules/eventsource/dist/index.js var dist_exports = /* @__PURE__ */ __exportAll({ ErrorEvent: () => ErrorEvent, EventSource: () => EventSource }); function syntaxError(message) { const DomException = globalThis.DOMException; return typeof DomException == "function" ? new DomException(message, "SyntaxError") : new SyntaxError(message); } function flattenError(err) { return err instanceof Error ? "errors" in err && Array.isArray(err.errors) ? err.errors.map(flattenError).join(", ") : "cause" in err && err.cause instanceof Error ? `${err}: ${flattenError(err.cause)}` : err.message : `${err}`; } function inspectableError(err) { return { type: err.type, message: err.message, code: err.code, defaultPrevented: err.defaultPrevented, cancelable: err.cancelable, timeStamp: err.timeStamp }; } function getBaseURL() { const doc = "document" in globalThis ? globalThis.document : void 0; return doc && typeof doc == "object" && "baseURI" in doc && typeof doc.baseURI == "string" ? doc.baseURI : void 0; } var ErrorEvent, __typeError, __accessCheck, __privateGet, __privateAdd, __privateSet, __privateMethod, _readyState, _url, _redirectUrl, _withCredentials, _fetch, _reconnectInterval, _reconnectTimer, _lastEventId, _controller, _parser, _onError, _onMessage, _onOpen, _EventSource_instances, connect_fn, _onFetchResponse, _onFetchError, getRequestOptions_fn, _onEvent, _onRetryChange, failConnection_fn, scheduleReconnect_fn, _reconnect, EventSource; var init_dist = __esmMin((() => { init_dist$1(); ErrorEvent = class extends Event { /** * Constructs a new `ErrorEvent` instance. This is typically not called directly, * but rather emitted by the `EventSource` object when an error occurs. * * @param type - The type of the event (should be "error") * @param errorEventInitDict - Optional properties to include in the error event */ constructor(type, errorEventInitDict) { var _a, _b; super(type), this.code = (_a = errorEventInitDict == null ? void 0 : errorEventInitDict.code) != null ? _a : void 0, this.message = (_b = errorEventInitDict == null ? void 0 : errorEventInitDict.message) != null ? _b : void 0; } /** * Node.js "hides" the `message` and `code` properties of the `ErrorEvent` instance, * when it is `console.log`'ed. This makes it harder to debug errors. To ease debugging, * we explicitly include the properties in the `inspect` method. * * This is automatically called by Node.js when you `console.log` an instance of this class. * * @param _depth - The current depth * @param options - The options passed to `util.inspect` * @param inspect - The inspect function to use (prevents having to import it from `util`) * @returns A string representation of the error */ [Symbol.for("nodejs.util.inspect.custom")](_depth, options, inspect) { return inspect(inspectableError(this), options); } /** * Deno "hides" the `message` and `code` properties of the `ErrorEvent` instance, * when it is `console.log`'ed. This makes it harder to debug errors. To ease debugging, * we explicitly include the properties in the `inspect` method. * * This is automatically called by Deno when you `console.log` an instance of this class. * * @param inspect - The inspect function to use (prevents having to import it from `util`) * @param options - The options passed to `Deno.inspect` * @returns A string representation of the error */ [Symbol.for("Deno.customInspect")](inspect, options) { return inspect(inspectableError(this), options); } }; __typeError = (msg) => { throw TypeError(msg); }, __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg), __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj)), __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value), __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), member.set(obj, value), value), __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method); EventSource = class extends EventTarget { constructor(url, eventSourceInitDict) { var _a, _b; super(), __privateAdd(this, _EventSource_instances), this.CONNECTING = 0, this.OPEN = 1, this.CLOSED = 2, __privateAdd(this, _readyState), __privateAdd(this, _url), __privateAdd(this, _redirectUrl), __privateAdd(this, _withCredentials), __privateAdd(this, _fetch), __privateAdd(this, _reconnectInterval), __privateAdd(this, _reconnectTimer), __privateAdd(this, _lastEventId, null), __privateAdd(this, _controller), __privateAdd(this, _parser), __privateAdd(this, _onError, null), __privateAdd(this, _onMessage, null), __privateAdd(this, _onOpen, null), __privateAdd(this, _onFetchResponse, async (response) => { var _a2; __privateGet(this, _parser).reset(); const { body, redirected, status, headers } = response; if (status === 204) { __privateMethod(this, _EventSource_instances, failConnection_fn).call(this, "Server sent HTTP 204, not reconnecting", 204), this.close(); return; } if (redirected ? __privateSet(this, _redirectUrl, new URL(response.url)) : __privateSet(this, _redirectUrl, void 0), status !== 200) { __privateMethod(this, _EventSource_instances, failConnection_fn).call(this, `Non-200 status code (${status})`, status); return; } if (!(headers.get("content-type") || "").startsWith("text/event-stream")) { __privateMethod(this, _EventSource_instances, failConnection_fn).call(this, "Invalid content type, expected \"text/event-stream\"", status); return; } if (__privateGet(this, _readyState) === this.CLOSED) return; __privateSet(this, _readyState, this.OPEN); const openEvent = new Event("open"); if ((_a2 = __privateGet(this, _onOpen)) == null || _a2.call(this, openEvent), this.dispatchEvent(openEvent), typeof body != "object" || !body || !("getReader" in body)) { __privateMethod(this, _EventSource_instances, failConnection_fn).call(this, "Invalid response body, expected a web ReadableStream", status), this.close(); return; } const decoder = new TextDecoder(), reader = body.getReader(); let open = !0; do { const { done, value } = await reader.read(); value && __privateGet(this, _parser).feed(decoder.decode(value, { stream: !done })), done && (open = !1, __privateGet(this, _parser).reset(), __privateMethod(this, _EventSource_instances, scheduleReconnect_fn).call(this)); } while (open); }), __privateAdd(this, _onFetchError, (err) => { __privateSet(this, _controller, void 0), !(err.name === "AbortError" || err.type === "aborted") && __privateMethod(this, _EventSource_instances, scheduleReconnect_fn).call(this, flattenError(err)); }), __privateAdd(this, _onEvent, (event) => { typeof event.id == "string" && __privateSet(this, _lastEventId, event.id); const messageEvent = new MessageEvent(event.event || "message", { data: event.data, origin: __privateGet(this, _redirectUrl) ? __privateGet(this, _redirectUrl).origin : __privateGet(this, _url).origin, lastEventId: event.id || "" }); __privateGet(this, _onMessage) && (!event.event || event.event === "message") && __privateGet(this, _onMessage).call(this, messageEvent), this.dispatchEvent(messageEvent); }), __privateAdd(this, _onRetryChange, (value) => { __privateSet(this, _reconnectInterval, value); }), __privateAdd(this, _reconnect, () => { __privateSet(this, _reconnectTimer, void 0), __privateGet(this, _readyState) === this.CONNECTING && __privateMethod(this, _EventSource_instances, connect_fn).call(this); }); try { if (url instanceof URL) __privateSet(this, _url, url); else if (typeof url == "string") __privateSet(this, _url, new URL(url, getBaseURL())); else throw new Error("Invalid URL"); } catch { throw syntaxError("An invalid or illegal string was specified"); } __privateSet(this, _parser, createParser({ onEvent: __privateGet(this, _onEvent), onRetry: __privateGet(this, _onRetryChange) })), __privateSet(this, _readyState, this.CONNECTING), __privateSet(this, _reconnectInterval, 3e3), __privateSet(this, _fetch, (_a = eventSourceInitDict == null ? void 0 : eventSourceInitDict.fetch) != null ? _a : globalThis.fetch), __privateSet(this, _withCredentials, (_b = eventSourceInitDict == null ? void 0 : eventSourceInitDict.withCredentials) != null ? _b : !1), __privateMethod(this, _EventSource_instances, connect_fn).call(this); } /** * Returns the state of this EventSource object's connection. It can have the values described below. * * [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/readyState) * * Note: typed as `number` instead of `0 | 1 | 2` for compatibility with the `EventSource` interface, * defined in the TypeScript `dom` library. * * @public */ get readyState() { return __privateGet(this, _readyState); } /** * Returns the URL providing the event stream. * * [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/url) * * @public */ get url() { return __privateGet(this, _url).href; } /** * Returns true if the credentials mode for connection requests to the URL providing the event stream is set to "include", and false otherwise. * * [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/withCredentials) */ get withCredentials() { return __privateGet(this, _withCredentials); } /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/error_event) */ get onerror() { return __privateGet(this, _onError); } set onerror(value) { __privateSet(this, _onError, value); } /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/message_event) */ get onmessage() { return __privateGet(this, _onMessage); } set onmessage(value) { __privateSet(this, _onMessage, value); } /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/open_event) */ get onopen() { return __privateGet(this, _onOpen); } set onopen(value) { __privateSet(this, _onOpen, value); } addEventListener(type, listener, options) { const listen = listener; super.addEventListener(type, listen, options); } removeEventListener(type, listener, options) { const listen = listener; super.removeEventListener(type, listen, options); } /** * Aborts any instances of the fetch algorithm started for this EventSource object, and sets the readyState attribute to CLOSED. * * [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/close) * * @public */ close() { __privateGet(this, _reconnectTimer) && clearTimeout(__privateGet(this, _reconnectTimer)), __privateGet(this, _readyState) !== this.CLOSED && (__privateGet(this, _controller) && __privateGet(this, _controller).abort(), __privateSet(this, _readyState, this.CLOSED), __privateSet(this, _controller, void 0)); } }; _readyState = /* @__PURE__ */ new WeakMap(), _url = /* @__PURE__ */ new WeakMap(), _redirectUrl = /* @__PURE__ */ new WeakMap(), _withCredentials = /* @__PURE__ */ new WeakMap(), _fetch = /* @__PURE__ */ new WeakMap(), _reconnectInterval = /* @__PURE__ */ new WeakMap(), _reconnectTimer = /* @__PURE__ */ new WeakMap(), _lastEventId = /* @__PURE__ */ new WeakMap(), _controller = /* @__PURE__ */ new WeakMap(), _parser = /* @__PURE__ */ new WeakMap(), _onError = /* @__PURE__ */ new WeakMap(), _onMessage = /* @__PURE__ */ new WeakMap(), _onOpen = /* @__PURE__ */ new WeakMap(), _EventSource_instances = /* @__PURE__ */ new WeakSet(), connect_fn = function() { __privateSet(this, _readyState, this.CONNECTING), __privateSet(this, _controller, new AbortController()), __privateGet(this, _fetch)(__privateGet(this, _url), __privateMethod(this, _EventSource_instances, getRequestOptions_fn).call(this)).then(__privateGet(this, _onFetchResponse)).catch(__privateGet(this, _onFetchError)); }, _onFetchResponse = /* @__PURE__ */ new WeakMap(), _onFetchError = /* @__PURE__ */ new WeakMap(), getRequestOptions_fn = function() { var _a; const init = { mode: "cors", redirect: "follow", headers: { Accept: "text/event-stream", ...__privateGet(this, _lastEventId) ? { "Last-Event-ID": __privateGet(this, _lastEventId) } : void 0 }, cache: "no-store", signal: (_a = __privateGet(this, _controller)) == null ? void 0 : _a.signal }; return "window" in globalThis && (init.credentials = this.withCredentials ? "include" : "same-origin"), init; }, _onEvent = /* @__PURE__ */ new WeakMap(), _onRetryChange = /* @__PURE__ */ new WeakMap(), failConnection_fn = function(message, code) { var _a; __privateGet(this, _readyState) !== this.CLOSED && __privateSet(this, _readyState, this.CLOSED); const errorEvent = new ErrorEvent("error", { code, message }); (_a = __privateGet(this, _onError)) == null || _a.call(this, errorEvent), this.dispatchEvent(errorEvent); }, scheduleReconnect_fn = function(message, code) { var _a; if (__privateGet(this, _readyState) === this.CLOSED) return; __privateSet(this, _readyState, this.CONNECTING); const errorEvent = new ErrorEvent("error", { code, message }); (_a = __privateGet(this, _onError)) == null || _a.call(this, errorEvent), this.dispatchEvent(errorEvent), __privateSet(this, _reconnectTimer, setTimeout(__privateGet(this, _reconnect), __privateGet(this, _reconnectInterval))); }, _reconnect = /* @__PURE__ */ new WeakMap(), EventSource.CONNECTING = 0, EventSource.OPEN = 1, EventSource.CLOSED = 2; Object.defineProperty(EventSource, Symbol.for("eventsource.supports-fetch-override"), { value: !0, writable: !1, configurable: !1, enumerable: !1 }); })); //#endregion //#region src/client/headless/HeadlessGameClient.ts var HeadlessGameClient = class { eventSource = null; _view = null; _lastSeq = 0; _phase = "connecting"; _playerId = null; _seatIndex = 0; _roomId = null; _roomState = null; _gameOverWinner = null; _pendingNewEvents = []; /** 最近一次 action 是否被服务端拒(供 runPlay 轮询)。每次 sendAction 重置。 */ _lastActionRejected = false; callbacks; /** REST/SSE base URL,如 'http://localhost:3930'(无 /ws 后缀) */ baseUrl; /** 主动断开标记:disconnect() 设为 true,不触发重连 */ intentionalDisconnect = false; /** 当前重连状态 */ _reconnectState = "idle"; /** 是否为 debug 模式(影响连接方式) */ _debugMode = false; /** 是否为旁观者 */ _isSpectator = false; /** 已收到 room_joined,具备 SSE 连接上下文 */ canReconnect = false; /** 重连状态轮询定时器 */ reconnectStateTimer = null; /** SSE onopen 回调已触发(用于状态映射) */ sseConnected = false; /** 已自动跳过的 pending deadline(去重,避免对同一窗口重复发 skip) */ _autoSkippedDeadline = null; /** 自动跳过用户偏好(策略跳过开关)。AI 座次默认空(仅维度1强制生效)。 */ _autoSkipPrefs = DEFAULT_PREFS; /** 延迟自动跳过定时器(view 推进到新窗口时清理,避免旧 skip 误作用于新 pending)。 */ _autoSkipTimer = null; constructor(serverUrl, callbacks = {}, auth) { let url = serverUrl; if (url.startsWith("ws://")) url = `http://${url.slice(5)}`; else if (url.startsWith("wss://")) url = `https://${url.slice(6)}`; if (url.endsWith("/ws")) url = url.slice(0, -3); this.baseUrl = url; this.callbacks = callbacks; this.auth = auth ?? null; } /** 程序化认证配置(可选;浏览器走 Cookie)。 */ auth = null; /** 已解析的会话 token(ensureAuth 后有值)。 */ authToken = null; authInitPromise = null; get phase() { return this._phase; } get view() { return this._view; } get roomId() { return this._roomId; } get playerId() { return this._playerId; } get seatIndex() { return this._seatIndex; } get lastSeq() { return this._lastSeq; } get roomState() { return this._roomState; } get gameOverWinner() { return this._gameOverWinner; } /** 是否为旁观者 */ get isSpectator() { return this._isSpectator; } /** 当前重连状态(idle/reconnecting/failed) */ get reconnectState() { return this._reconnectState; } /** 当前重连尝试次数(0=未在重连) */ get reconnectAttemptCount() { return this._reconnectState === "reconnecting" ? 1 : 0; } setPhase(p) { if (this._phase !== p) { this._phase = p; this.callbacks.onPhaseChange?.(p); } } /** 统一的身份变更入口:更新 roomId/playerId/isSpectator,任一实际变化时触发 * onIdentityChange 回调(事件驱动,替代宿主轮询 getter)。未传的字段不动。 */ updateIdentity(next) { const changed = next.roomId !== void 0 && next.roomId !== this._roomId || next.playerId !== void 0 && next.playerId !== this._playerId || next.isSpectator !== void 0 && next.isSpectator !== this._isSpectator; if (next.roomId !== void 0) this._roomId = next.roomId; if (next.playerId !== void 0) this._playerId = next.playerId; if (next.isSpectator !== void 0) this._isSpectator = next.isSpectator; if (changed) this.callbacks.onIdentityChange?.({ roomId: this._roomId, playerId: this._playerId, isSpectator: this._isSpectator }); } /** 检查 REST 响应是否成功,失败时抛出带 status 的错误 */ async assertOk(resp, fallbackMsg) { if (resp.ok) return; const message = (await resp.json().catch(() => ({}))).error ?? fallbackMsg; const err = new Error(message); err.status = resp.status; throw err; } /** 程序化认证:换取/复用会话 token(幂等,并发安全)。 * 无 auth 配置时同步短路(不引入微任务,保持浏览器场景 createRoom 同步发出 fetch)。 */ ensureAuth() { if (!this.auth || this.authToken) return Promise.resolve(); this.authInitPromise ??= (async () => { const { username, password, token } = this.auth; if (token) { this.authToken = token; return; } if (!username || !password) throw new Error("auth 配置不完整:需要 token 或 username/password"); const authOnce = async (path) => { const resp = await fetch(`${this.baseUrl}/api/auth/${path}`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ username, password }) }); const body = await resp.json().catch(() => ({})); if (!resp.ok || !body.token) throw new Error(body.error ?? `认证失败(${resp.status})`); this.authToken = body.token; }; try { await authOnce("register"); } catch (err) { const msg = err instanceof Error ? err.message : String(err); if (!/已存在/.test(msg)) throw err; await authOnce("login"); } })().catch((err) => { const e = err instanceof Error ? err : new Error(String(err)); this.callbacks.onError?.(e); this.authInitPromise = null; throw e; }); return this.authInitPromise; } /** 带 auth 头的 fetch(有 token 时)。 */ authFetch(url, init = {}) { const headers = new Headers(init.headers ?? {}); if (this.authToken) headers.set("Authorization", `Bearer ${this.authToken}`); return fetch(url, { ...init, headers }); } /** 创建 debug 房间并自动 join 0 号座。 * playerId 可选:指定则用作本座次身份,否则由服务端自动生成。 */ async createDebugRoom(playerCount, config, playerId) { this._debugMode = true; this.intentionalDisconnect = false; const resp = await fetch(`${this.baseUrl}/api/debug-room`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ playerCount, config, autoJoin: true, playerId }) }); await this.assertOk(resp, "创建调试房间失败"); const data = await resp.json(); this._seatIndex = data.seatIndex; this.updateIdentity({ roomId: data.roomId, playerId: data.playerId }); this.canReconnect = true; this.openStream(); this.setPhase("lobby"); } /** 连接并 join 指定房间。 * playerId 可选:指定则预先设置本连接身份(透传到 join 请求体),否则复用已有或由服务端生成。 * opts.stream 默认 true(join 后立即打开 SSE);debug 单活流模式对非当前视角座次 * 传 false——浏览器对同源 HTTP/1.1 有 6 连接上限,N 条长连 SSE 会饿死页面上的 * 图片/请求,非当前视角座次只做 REST 驱动,不持有流。 */ async connect(roomId, seatIndex, playerId, opts) { this._debugMode = true; this._seatIndex = seatIndex ?? this._seatIndex; this.updateIdentity({ roomId, playerId: playerId ?? void 0 }); this.intentionalDisconnect = false; const resp = await fetch(`${this.baseUrl}/api/debug-room/${roomId}/join`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ playerId: this._playerId ?? void 0, lastSeq: this._lastSeq }) }); await this.assertOk(resp, "加入调试房间失败"); const data = await resp.json(); this._seatIndex = data.seatIndex; this.updateIdentity({ roomId: data.roomId, playerId: data.playerId }); this.canReconnect = true; if (opts?.stream !== false) this.openStream(); this.setPhase("lobby"); } /** 创建普通(多人)房间:本连接成为房主。roomType: 'normal'=持久化, 'quick'=纯内存(默认)。 */ async createRoom(name, maxPlayers, config, playerId, roomType, password) { this._debugMode = false; this.intentionalDisconnect = false; if (this.auth) await this.ensureAuth(); const resp = await this.authFetch(`${this.baseUrl}/api/rooms`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ name, maxPlayers, config, playerId, roomType, password: password ?? void 0 }) }); await this.assertOk(resp, "创建房间失败"); const data = await resp.json(); this.updateIdentity({ roomId: data.roomId, playerId: data.playerId }); this.canReconnect = true; this.openStream(); this.setPhase("lobby"); } /** 加入普通(多人)房间。password: 有密码房间必填(错误时服务端 403 ROOM_PASSWORD_REQUIRED)。 */ async joinRoom(roomId, playerId, password) { this._debugMode = false; this.updateIdentity({ roomId }); this.intentionalDisconnect = false; if (this.auth) await this.ensureAuth(); const resp = await this.authFetch(`${this.baseUrl}/api/rooms/${roomId}/join`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ playerId, password: password ?? void 0 }) }); await this.assertOk(resp, "加入房间失败"); const data = await resp.json(); this.updateIdentity({ roomId: data.roomId, playerId: data.playerId }); this.canReconnect = true; this.openStream(); } /** 以旁观者身份加入房间。不占座次,默认看公开视图。 */ async joinAsSpectator(roomId, playerId, password) { this._debugMode = false; this.updateIdentity({ roomId }); this.intentionalDisconnect = false; if (this.auth) await this.ensureAuth(); const resp = await this.authFetch(`${this.baseUrl}/api/rooms/${roomId}/join-spectator`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ playerId, password: password ?? void 0 }) }); await this.assertOk(resp, "加入旁观失败"); const data = await resp.json(); this.updateIdentity({ roomId: data.roomId, playerId: data.playerId, isSpectator: true }); this.canReconnect = true; this.openStream(); this.setPhase("lobby"); } /** 建立 SSE 流接收服务端推送。 * 程序化客户端(EventSource 无法带自定义头)把会话 token 放 ?sgs_token=; * 浏览器走 Cookie。 */ openStream() { if (this.intentionalDisconnect) return; const generation = ++this.openStreamGeneration; this.ensureEventSource().then((EventSourceImpl) => { if (generation !== this.openStreamGeneration) return; if (this.intentionalDisconnect || !this._roomId || !this._playerId) return; let url = `${this.baseUrl}/api/rooms/${this._roomId}/stream?playerId=${encodeURIComponent(this._playerId)}`; if (this.authToken) url += `&sgs_token=${encodeURIComponent(this.authToken)}`; this.eventSource = new EventSourceImpl(url); this.attachEventSourceHandlers(); }); } /** 开流代数:每次 open/suspend/disconnect 递增,作废在途的异步创建。 */ openStreamGeneration = 0; /** 获取 EventSource 构造器:浏览器用全局,Node 动态 import eventsource 包 */ async ensureEventSource() { if (typeof globalThis.EventSource !== "undefined") return globalThis.EventSource; return (await Promise.resolve().then(() => (init_dist(), dist_exports))).EventSource; } attachEventSourceHandlers() { if (!this.eventSource) return; this.sseConnected = false; this.eventSource.onopen = () => { this.sseConnected = true; if (this.intentionalDisconnect) { this.eventSource?.close(); return; } this.setReconnectState("idle"); }; this.eventSource.onmessage = (ev) => { this.handleRaw(ev.data); }; this.eventSource.onerror = () => { this.sseConnected = false; if (this.intentionalDisconnect) return; if (this.canReconnect) { this.setReconnectState("reconnecting"); this.startReconnectStatePolling(); } }; } /** 轮询 EventSource.readyState 检测重连恢复 */ startReconnectStatePolling() { if (this.reconnectStateTimer) return; this.reconnectStateTimer = setInterval(() => { if (!this.eventSource) { this.stopReconnectStatePolling(); return; } if (this.eventSource.readyState === 1) { this.stopReconnectStatePolling(); this.setReconnectState("idle"); } else if (this.eventSource.readyState === 2) { this.stopReconnectStatePolling(); this.setReconnectState("failed"); } }, 500); } stopReconnectStatePolling() { if (this.reconnectStateTimer) { clearInterval(this.reconnectStateTimer); this.reconnectStateTimer = null; } } setReconnectState(state) { if (this._reconnectState !== state) { this._reconnectState = state; this.callbacks.onReconnectStateChange?.(state, this.reconnectAttemptCount); } } /** 用户手动取消重连。关闭正在重连的 EventSource。 */ cancelReconnect() { this.stopReconnectStatePolling(); if (this._reconnectState === "reconnecting" && this.eventSource) { this.intentionalDisconnect = true; this.eventSource.close(); this.eventSource = null; } this.setReconnectState("idle"); } handleRaw(raw) { let msg; try { msg = JSON.parse(raw); } catch { return; } if (this._reconnectState === "reconnecting") { this.stopReconnectStatePolling(); this.setReconnectState("idle"); } const r = applyServerMessage(this._view, this._lastSeq, msg); const viewChanged = this._view !== r.view; this._view = r.view; this._lastSeq = r.lastSeq; if (viewChanged && this._view) { if (r.newEvents.length) this._pendingNewEvents.push(...r.newEvents); this.callbacks.onView?.(this._view, r.newEvents); } else if (r.newEvents.length) this._pendingNewEvents.push(...r.newEvents); if (r.phaseChangedTo) this.setPhase(r.phaseChangedTo); if (r.gameOverWinner !== void 0) { this._gameOverWinner = r.gameOverWinner; this.callbacks.onGameOver?.(r.gameOverWinner); } if (r.playerId || r.roomId) this.updateIdentity({ playerId: r.playerId ?? void 0, roomId: r.roomId ?? void 0 }); if (r.seatIndex !== void 0) this._seatIndex = r.seatIndex; if (msg.type === "room_joined") this.canReconnect = true; if (r.roomState) { this._roomState = r.roomState; this.callbacks.onRoomState?.(r.roomState); } if (msg.type === "room_config") { this._roomState = mergeRoomConfig(this._roomState, msg.config); this.callbacks.onRoomState?.(this._roomState); } if (r.resetToLobby) { this._view = null; this._lastSeq = 0; this._gameOverWinner = null; this._pendingNewEvents = []; this._autoSkippedDeadline = null; if (this._autoSkipTimer) { clearTimeout(this._autoSkipTimer); this._autoSkipTimer = null; } } if (viewChanged && this._view) this.maybeAutoSkipBroadcast(); if (r.actionRejected) { this._lastActionRejected = true; this.callbacks.onActionRejected?.(); } if (msg.type === "chat") this.callbacks.onChat?.([{ playerId: msg.playerId, seatIndex: msg.seatIndex, text: msg.text, timestamp: msg.timestamp }], "chat"); else if (msg.type === "chat_history") this.callbacks.onChat?.(msg.messages, "history"); if (msg.type === "role_changed" && msg.playerId === this._playerId) { this.updateIdentity({ isSpectator: msg.newRole === "spectator" }); if (this._isSpectator) this._seatIndex = -1; } this.callbacks.onMessage?.(msg); } drainNewEvents() { const e = this._pendingNewEvents; this._pendingNewEvents = []; return e; } needsAction() { const v = this._view; if (!v?.pending) return false; const p = v.pending; if (p.target < 0) return true; if (p.target !== this._seatIndex) return false; if (p.responseMode === "silent") return false; if (p.isBlocking !== false) return true; return v.phase === "出牌"; } getAvailableActions() { const v = this._view; if (!v) return []; const actionSeat = this._debugMode && v.pending && v.pending.target >= 0 ? v.pending.target : this._seatIndex; const skillActions = getActionsForPlayer(actionSeat); const actions = enumerateAvailableActions(v, actionSeat, skillActions); if (v.pending && (v.pending.target === actionSeat || v.pending.target < 0)) this.appendRespondActions(v, skillActions, actions, actionSeat); return actions; } appendRespondActions(view, skillActions, out, actionSeat) { const ownerId = actionSeat ?? this._seatIndex; const pending = view.pending; if (pending.responseMode === "silent" && pending.target === ownerId) return; const atom = pending.atom; if (atom.type === "选将询问" && Array.isArray(atom.candidates)) { for (const c of atom.candidates) out.push({ description: `选择武将【${c.name}】`, message: { skillId: "系统规则", actionType: "选将", ownerId, params: { character: c.name }, baseSeq: 0 }, validTargets: [], category: "selectChar" }); return; } if (pending.target < 0) out.push({ description: "跳过(不打出无懈可击)", message: { skillId: "__skip", actionType: "skip", ownerId, params: {}, baseSeq: 0 }, validTargets: [], category: "skip" }); const reqType = getPendingRequestType(pending); if (pending.prompt?.type === "pickTargetCard") { const pickPrompt = pending.prompt; const targetIdx = pickPrompt.target; const targetPlayer = view.players[targetIdx]; const sep = reqType.search(/[/_]/); const pickSkillId = sep >= 0 ? reqType.slice(0, sep) : reqType; const verb = reqType.startsWith("顺手牵羊") || reqType.startsWith("反馈") ? "获得" : "弃置"; for (const eq of pickPrompt.equipment ?? []) out.push({ description: `${verb}【${eq.cardName}】(装备)`, message: { skillId: pickSkillId, actionType: "respond", ownerId, params: { zone: "equipment", cardId: eq.cardId }, baseSeq: 0 }, validTargets: [], category: "respond" }); for (const j of pickPrompt.judge ?? []) out.push({ description: `${verb}【${j.cardName}】(判定区)`, message: { skillId: pickSkillId, actionType: "respond", ownerId, params: { zone: "judge", cardId: j.cardId }, baseSeq: 0 }, validTargets: [], category: "respond" }); if ((pickPrompt.handCount ?? targetPlayer?.handCount ?? 0) > 0) out.push({ description: `${verb}手牌(第1张)`, message: { skillId: pickSkillId, actionType: "respond", ownerId, params: { zone: "hand", handIndex: 0 }, baseSeq: 0 }, validTargets: [], category: "respond" }); return; } if (pending.prompt?.type === "pickProcessingCard") { const pickPrompt = pending.prompt; const sep = reqType.search(/[/_]/); const pickSkillId = sep >= 0 ? reqType.slice(0, sep) : reqType; for (const c of pickPrompt.cards ?? []) out.push({ description: `选【${c.cardName} ${c.suit}${c.rank}】`, message: { skillId: pickSkillId, actionType: "respond", ownerId, params: { cardId: c.cardId }, baseSeq: 0 }, validTargets: [], category: "respond" }); return; } if (reqType === "__弃牌") { out.push({ description: "弃牌(需选弃牌张数后提交 cardIds)", message: { skillId: "系统规则", actionType: "respond", ownerId, params: { cardIds: [] }, baseSeq: 0 }, validTargets: [], category: "discard" }); return; } if (pending.mandatory === true && pending.prompt?.type === "useCard") { const minfo = resolvePendingRespond(pending, skillActions); const minCount = pending.prompt.cardFilter.min; out.push({ description: `强制弃牌(需选 ${minCount} 张后提交 cardIds)`, message: { skillId: minfo?.skillId ?? "系统规则", actionType: "respond", ownerId, params: { cardIds: [] }, baseSeq: 0 }, validTargets: [], category: "discard" }); return; } const info = resolvePendingRespond(pending, skillActions); if (info?.skillId) { if (pending.prompt?.type === "choosePlayer") { const choosePrompt = pending.prompt; const filter = choosePrompt.filter; const validTargets = choosePrompt.candidates ?? (() => { const arr = []; for (const p of view.players) { if (!p.alive) continue; if (filter && !filter(view, p.index)) continue; arr.push(p.index); } return arr; })(); const max = choosePrompt.max ?? 1; const min = choosePrompt.min ?? 1; if (max <= 1) { for (const t of validTargets) out.push({ description: `${choosePrompt.title ?? info.skillId} → ${view.players[t]?.name ?? `P${t}`}`, message: { skillId: info.skillId, actionType: "respond", ownerId, params: { target: t, targets: [t] }, baseSeq: 0 }, validTargets: [t], category: "respond" }); if (min === 0) out.push({ description: `${choosePrompt.title ?? info.skillId} → 不选择`, message: { skillId: info.skillId, actionType: "respond", ownerId, params: { targets: [] }, baseSeq: 0 }, validTargets: [], category: "skip" }); } else out.push({ description: `${choosePrompt.title ?? info.skillId}(选 ${min}-${max} 个目标)`, message: { skillId: info.skillId, actionType: "respond", ownerId, params: { targets: [] }, baseSeq: 0 }, validTargets, category: "respond", ...min > 1 ? { minTarget: min } : {}, maxTarget: max }); return; } if (pending.prompt?.type === "chooseOption") { const p = pending.prompt; for (const opt of p.options) out.push({ description: opt.label, message: { skillId: info.skillId, actionType: "respond", ownerId, params: { option: opt.value }, baseSeq: 0 }, validTargets: [], category: "respond" }); return; } if (pending.prompt?.type === "distribute") { const distPrompt = pending.prompt; const mode = distPrompt.mode ?? "allocate"; const me = view.players[ownerId]; const equip = me?.equipment ?? {}; if (mode === "select") { const candidateIds = resolveDistributeCardIds(distPrompt, me?.hand ?? [], equip); const minTotal = distPrompt.minTotal ?? 1; if (candidateIds.length >= minTotal) out.push({ description: `发动【${info.skillId}】(选 ${minTotal} 张${distPrompt.source === "handAndEquip" ? "手牌或装备" : "手牌"}弃置,需填 cardIds)`, message: { skillId: info.skillId, actionType: "respond", ownerId, params: { cardIds: [] }, baseSeq: 0 }, validTargets: [], category: "distribute" }); } else out.push({ description: `发动【${info.skillId}】(分配牌给目标,需填 allocation)`, message: { skillId: info.skillId, actionType: "respond", ownerId, params: { allocation: [] }, baseSeq: 0 }, validTargets: [], category: "distribute" }); if (pending.target >= 0 && pending.mandatory !== true) out.push({ description: `不发动【${info.skillId}】`, message: { skillId: "__skip", actionType: "skip", ownerId, params: {}, baseSeq: 0 }, validTargets: [], category: "skip" }); return; } const isAskType = (atom.type ?? "").startsWith("询问"); if (!isAskType && pending.prompt?.type === "useCardAndTarget") { const prompt = pending.prompt; const me = view.players[ownerId]; const candidateIds = prompt.cardFilter.candidates; let candidates; if (candidateIds) candidates = candidateIds.map((id) => view.cardMap[id]).filter((c) => !!c); else if (info.cardFilter) candidates = me?.hand?.filter((c) => info.cardFilter(c)) ?? []; else candidates = me?.hand ?? []; const allowSelf = !!prompt.targetFilter?.allowSelf || !!prompt.selfTarget; const tFilter = prompt.targetFilter?.filter; const validTargets = []; for (const p of view.players) { if (!p.alive) continue; if (p.index === ownerId && !allowSelf) continue; if (tFilter && !tFilter(view, p.index)) continue; validTargets.push(p.index); } for (const card of candidates) out.push({ description: `回应【${info.skillId}】(${card.suit}${card.rank})`, message: { skillId: info.skillId, actionType: "respond", ownerId, params: { cardId: card.id }, baseSeq: 0 }, validTargets, category: "respond" }); if (pending.target >= 0) out.push({ description: candidates.length === 0 ? `无牌可出,跳过` : `不出【${info.skillId}】`, message: { skillId: "__skip", actionType: "skip", ownerId, params: {}, baseSeq: 0 }, validTargets: [], category: "skip" }); return; } if (!isAskType && pending.prompt?.type === "confirm") { const confirmLabel = pending.prompt.confirmLabel ?? "确认"; const cancelLabel = pending.prompt.cancelLabel ?? "取消"; if (pending.prompt.confirmDisabled !== true) out.push({ description: `${confirmLabel}【${info.skillId}】`, message: { skillId: info.skillId, actionType: "respond", ownerId, params: { choice: true }, baseSeq: 0 }, validTargets: [], category: "respond" }); out.push({ description: `${cancelLabel}`, message: { skillId: info.skillId, actionType: "respond", ownerId, params: { choice: false }, baseSeq: 0 }, validTargets: [], category: "skip" }); } else if (info.cardFilter) { const candidates = view.players[ownerId]?.hand?.filter((c) => info.cardFilter(c)) ?? []; for (const card of candidates) { const rescueSkill = info.rescueSkillForCard?.(card); out.push({ description: `回应【${rescueSkill ?? info.skillId}】(${card.suit}${card.rank})`, message: { skillId: rescueSkill ?? info.skillId, actionType: "respond", ownerId, params: { cardId: card.id }, baseSeq: 0 }, validTargets: [], category: "respond" }); } if (pending.target >= 0) out.push({ description: candidates.length === 0 ? `无牌可出,跳过` : `不出【${info.skillId}】`, message: { skillId: "__skip", actionType: "skip", ownerId, params: {}, baseSeq: 0 }, validTargets: [], category: "skip" }); } else out.push({ description: `回应【${info.skillId}】`, message: { skillId: info.skillId, actionType: "respond", ownerId, params: {}, baseSeq: 0 }, validTargets: [], category: "respond" }); } } sendAction(action) { this._lastActionRejected = false; const pendingSeq = (this._view?.pending)?.isBlocking ? this._lastSeq : void 0; const fullAction = { ...action, baseSeq: this._lastSeq, pendingSeq }; this.postAction(fullAction); } /** POST action 到服务端 */ async postAction(action) { if (!this._roomId || !this._playerId) return; try { await fetch(`${this.baseUrl}/api/rooms/${this._roomId}/action`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ playerId: this._playerId, action }) }); } catch (err) { this.callbacks.onError?.(err instanceof Error ? err : new Error(String(err))); } } /** 返回并清除最近一次 action 是否被服务端拒(供 runPlay 轮询,已拒则报告 rejected)。 */ consumeActionRejected() { const r = this._lastActionRejected; this._lastActionRejected = false; return r; } /** 重排手牌 */ async reorderHand(order) { if (!this._roomId || !this._playerId) return; try { await fetch(`${this.baseUrl}/api/rooms/${this._roomId}/reorder`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ playerId: this._playerId, order }) }); } catch (err) { this.callbacks.onError?.(err instanceof Error ? err : new Error(String(err))); } } useCardAndTarget(skillId, cardId, targets) { this.sendAction({ skillId, actionType: "use", ownerId: this._seatIndex, params: { cardId, targets }, baseSeq: 0 }); } useCard(skillId, cardId) { this.sendAction({ skillId, actionType: "use", ownerId: this._seatIndex, params: { cardId }, baseSeq: 0 }); } respond(skillId, params) { this.sendAction({ skillId, actionType: "respond", ownerId: this._seatIndex, params: params ?? {}, baseSeq: 0 }); } /** 选择武将(对应选将询问 pending)。引擎注册 系统规则:选将。 */ selectCharacter(character) { this.sendAction({ skillId: "系统规则", actionType: "选将", ownerId: this._seatIndex, params: { character }, baseSeq: 0 }); } /** 弃牌:提交要弃的 cardIds。引擎注册 系统规则:respond。 */ discard(cardIds) { this.respond("系统规则", { cardIds }); } /** 放弃当前 pending(不回应):广播型/阻塞型发 skip 触发超时,出牌窗口发空 respond。 */ pass() { const pending = this._view?.pending; if (pending && pending.target < 0) { this.sendAction({ skillId: "__skip", actionType: "skip", ownerId: this._seatIndex, params: {}, baseSeq: 0 }); return; } if (pending && pending.isBlocking !== false) { this.sendAction({ skillId: "__skip", actionType: "skip", ownerId: this._seatIndex, params: {}, baseSeq: 0 }); return; } const skillId = (pending ? resolvePendingRespond(pending, getActionsForPlayer(this._seatIndex)) : null)?.skillId ?? "__pass"; this.sendAction({ skillId, actionType: "respond", ownerId: this._seatIndex, params: {}, baseSeq: 0 }); } /** 自动跳过决策:本座次无法响应 pending(或用户开启策略跳过)时代发 skip。 * 维度1(强制):无法响应时自动跳过,防恶意拖延。空手牌立即,有手牌随机延迟防泄露。 * 维度2(可选):用户开启策略跳过时,无论能否响应都延迟跳过。 * 以 pending.deadline 去重,同一窗口只跳一次。仅在非旁观者座次生效。 */ maybeAutoSkipBroadcast() { if (this._isSpectator) return; if (this._autoSkipTimer) { clearTimeout(this._autoSkipTimer); this._autoSkipTimer = null; } const view = this._view; const pending = view?.pending; if (!pending) return; const deadline = pending.deadline ?? 0; if (deadline === this._autoSkippedDeadline) return; const decision = decideAutoSkip({ pending, canRespond: this.getAvailableActions().some((a) => a.category !== "skip"), handCount: view.players[this._seatIndex]?.handCount ?? 0, prefs: this._autoSkipPrefs }); if (decision.kind === "wait") return; this._autoSkippedDeadline = deadline; if (decision.kind === "act-now") this.pass(); else this._autoSkipTimer = setTimeout(() => { this._autoSkipTimer = null; const cur = this._view?.pending; if (!cur || (cur.deadline ?? 0) !== deadline) return; this.pass(); }, decision.ms); } /** 设置自动跳过用户偏好(策略跳过开关)。 */ setAutoSkipPrefs(prefs) { this._autoSkipPrefs = prefs; } async sendReady() { await this.postRoomOp("ready"); } async sendCancelReady() { await this.postRoomOp("cancel-ready"); } async sendStartGame() { await this.postRoomOp("start"); } async sendRestart() { await this.postRoomOp("restart"); } /** 发送聊天消息 */ async sendChat(text) { if (!this._roomId || !this._playerId) return null; try { const resp = await fetch(`${this.baseUrl}/api/rooms/${this._roomId}/chat`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ playerId: this._playerId, text }) }); if (!resp.ok) { const body = await resp.json().catch(() => ({})); this.callbacks.onError?.(new Error(body.error ?? "发送失败")); return null; } return { remaining: (await resp.json().catch(() => ({}))).remaining }; } catch (err) { this.callbacks.onError?.(err instanceof Error ? err : new Error(String(err))); return null; } } async sendUpdateConfig(config, maxPlayers) { if (!this._roomId || !this._playerId) return; try { const body = { playerId: this._playerId, config }; if (maxPlayers !== void 0) body.maxPlayers = maxPlayers; const resp = await fetch(`${this.baseUrl}/api/rooms/${this._roomId}/config`, { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body) }); if (!resp.ok) { const body = await resp.json().catch(() => ({})); this.callbacks.onError?.(new Error(body.error ?? "更新配置失败")); } } catch (err) { this.callbacks.onError?.(err instanceof Error ? err : new Error(String(err))); } } /** POST 通用房间操作(ready/start/restart) */ async postRoomOp(op) { if (!this._roomId || !this._playerId) return; try { const resp = await fetch(`${this.baseUrl}/api/rooms/${this._roomId}/${op}`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ playerId: this._playerId }) }); if (!resp.ok) { const body = await resp.json().catch(() => ({})); this.callbacks.onError?.(new Error(body.error ?? `${op} 失败(${resp.status})`)); } } catch (err) { this.callbacks.onError?.(err instanceof Error ? err : new Error(String(err))); } } /** 挂起 SSE 流(不离开房间、不清理身份/视图状态):debug 单活流模式用。 * 与 disconnect() 的区别:保留 canReconnect/identity/lastSeq,可 resumeStream() 原地恢复。 */ suspendStream() { this.stopReconnectStatePolling(); this.openStreamGeneration++; this.eventSource?.close(); this.eventSource = null; this.sseConnected = false; } /** 恢复 SSE 流:新 EventSource 首次请求无 Last-Event-ID, * 服务端按 lastSeq=0 回 initialView 全量快照,视图自动对齐。 */ resumeStream() { if (this.intentionalDisconnect || !this._roomId || !this._playerId) return; if (this.eventSource) return; this.openStream(); } disconnect() { this.intentionalDisconnect = true; this.stopReconnectStatePolling(); this.canReconnect = false; this.eventSource?.close(); this.eventSource = null; } /** 角色确定后,为本座次注册技能 actions(供 getAvailableActions)。 */ async loadSkillActions(skillIds, seatIndex) { await registerSkillActions(seatIndex ?? this._seatIndex, skillIds); } }; //#endregion //#region src/ai-mcp/viewProjector.ts /** 从 GameView.pending 读取 choosePlayer 候选,映射成可读 {index,name} 列表。 * 仅当 prompt.type==='choosePlayer' 且 prompt.candidates(number[]) 存在时返回。 * engine 投影层已确保 candidates 注入(跑 filter 或技能显式提供),故此处只做读取+映射。 */ function readPlayerCandidates(view) { const prompt = view.pending?.prompt; if (prompt?.type !== "choosePlayer") return void 0; const ids = prompt.candidates; if (!Array.isArray(ids)) return void 0; return ids.map((i) => ({ index: i, name: view.players[i]?.name ?? `P${i}` })); } /** 完整视图投影。getSnapshot 工具调用。 */ function projectView(view) { return { viewer: view.viewer, currentPlayerIndex: view.currentPlayerIndex, phase: view.phase, turn: { round: view.turn.round }, players: view.players.map((p) => ({ index: p.index, name: p.name, character: p.character, health: p.health, maxHealth: p.maxHealth, alive: p.alive, handCount: p.handCount, hand: p.hand, equipment: p.equipment, skills: p.skills, marks: (p.marks ?? []).map((m) => ({ id: m.id, scope: m.scope })), identity: p.identity })), pending: view.pending ? { target: view.pending.target, isBlocking: view.pending.isBlocking !== false, promptTitle: view.pending.prompt.title ?? view.pending.prompt.type, requestType: getPendingRequestType(view.pending), candidates: view.pending.atom.type === "选将询问" ? view.pending.atom.candidates : void 0, playerCandidates: readPlayerCandidates(view) } : null, zones: view.zones ? { deckCount: view.zones.deckCount, discardPileCount: view.zones.discardPileCount } : { deckCount: 0, discardPileCount: 0 }, log: view.log.slice(-20) }; } //#endregion //#region src/ai-mcp/heuristics.ts var SCORE_FLOOR = 0; var SCORE_CEIL = 100; function clamp(n) { if (!Number.isFinite(n)) return SCORE_FLOOR; return Math.max(SCORE_FLOOR, Math.min(SCORE_CEIL, Math.round(n))); } /** 取自身座次玩家视图。 */ function selfOf(view) { return view.players[view.viewer]; } /** 从 action.params.cardId 在自己手牌中查具体 Card(仅 viewer 自己手牌可见)。 */ function findCardInHand(view, cardId) { if (!cardId) return void 0; return selfOf(view)?.hand?.find((c) => c.id === cardId); } /** 从 action 提取要使用的卡牌 id 与名称。 */ function actionCard(action) { const p = action.message.params; return { id: typeof p.cardId === "string" ? p.cardId : void 0, name: action.message.skillId }; } /** 是否有人濒死(存活且体力 ≤ 0)。 */ function someoneDying(view) { return view.players.some((p) => p.alive && p.health <= 0); } /** 自身是否濒死。 */ function selfDying(view) { const me = selfOf(view); return !!me && me.alive && me.health <= 0; } /** 判定是否处于针对自己的阻塞型回应(被杀/被决斗/被求桃等)。 */ function isBlockingRespondToSelf(view) { const p = view.pending; return !!p && p.isBlocking && p.target === view.viewer; } /** 判断目标是否为盟友(基于身份)。 * 身份规则: * - 主公/忠臣互为盟友(反贼/内奸是敌人) * - 反贼互为盟友(主公/忠臣是敌人) * - 内奸:所有人都是敌人(除自己) * - 目标身份未知(undefined)时:保守视为非盟友 * - 自己的身份未知时:保守视为非盟友(即所有人都当敌人处理) */ function isAlly(myIdentity, targetIdentity) { if (!myIdentity || !targetIdentity) return false; if (myIdentity === "内奸") return false; if (myIdentity === targetIdentity) return true; if (myIdentity === "主公" && targetIdentity === "忠臣" || myIdentity === "忠臣" && targetIdentity === "主公") return true; return false; } /** 从 action.validTargets 中筛选敌方玩家(基于身份)。 */ function enemyTargets(view, action) { const myId = selfOf(view)?.identity; return action.validTargets.map((i) => view.players[i]).filter((p) => !!p && !isAlly(myId, p.identity)); } /** 轮次修正:前两轮保守(-5),后期(round≥5)激进(+5),中期不变。 */ function roundModifier(view) { const round = view.turn.round; if (round <= 2) return -5; if (round >= 5) return 5; return 0; } /** 锦囊威胁等级(按牌名分类,不考虑目标状态)。 * - critical:关键单体锦囊(顺手牵羊/过河拆桥/决斗/乐不思蜀)= 90 * - high:借刀杀人/火攻/兵粮寸断 = 75 * - medium:南蛮入侵/万箭齐发(群体,视存活人数)= 60 * - low:桃园结义/五谷丰登/无中生有(一般不无懈)= 40 * - 未知:medium = 60 */ function wuxieThreatLevel(title) { if (/顺手牵羊|过河拆桥|决斗|乐不思蜀/.test(title)) return { level: "critical", score: 90 }; if (/借刀杀人|火攻|兵粮寸断/.test(title)) return { level: "high", score: 75 }; if (/南蛮入侵|万箭齐发/.test(title)) return { level: "medium", score: 60 }; if (/桃园结义|五谷丰登|无中生有/.test(title)) return { level: "low", score: 40 }; return { level: "medium", score: 60 }; } /** 被指定目标的"保护价值"评分(0-100)。 * 综合目标血量(残血更需要保护)、手牌数(多牌被顺拆损失大)、武将强度。 * 注:阵营归属(盟友/敌方)由调用方判断,本函数只评估目标本身的价值。 */ function wuxieTargetValue(view, targetIdx) { const target = view.players[targetIdx]; if (!target) return 50; let value = 50; if (target.health <= 1) value += 20; else if (target.health <= 2) value += 12; if (target.handCount >= 4) value += 10; if (STRONG_CHARACTERS.has(target.character)) value += 10; return clamp(value); } /** 自己手牌中【无懈可击】的数量(仅 viewer 手牌可见)。 */ function countWuxieInHand(view) { return selfOf(view)?.hand?.filter((c) => c.name === "无懈可击").length ?? 0; } /** 无懈可击综合评分。 * 维度:锦囊威胁等级 × 目标保护价值 × 阵营(自己/盟友/敌方)× 手牌无懈数 × 轮次。 */ function scoreWuxie(view, _action) { const title = view.pending?.promptTitle ?? ""; const threat = wuxieThreatLevel(title); const targetIdx = view.pending?.target ?? -1; const myId = selfOf(view)?.identity; const roundMod = roundModifier(view); if (targetIdx < 0 || !view.players[targetIdx]) { let baseScore = threat.score; if (/南蛮|万箭/.test(title)) { if (view.players.filter((p) => p.alive).length <= 2) baseScore -= 15; } const score = clamp(baseScore + roundMod); return { score, reason: `${title || "锦囊"}(广播型,威胁${threat.level})${score >= 60 ? "考虑" : "暂不"}打出【无懈可击】` }; } const target = view.players[targetIdx]; const targetVal = wuxieTargetValue(view, targetIdx); if (targetIdx === view.viewer) return { score: clamp(threat.score + 5 + roundMod), reason: `自己被${title}指定(威胁${threat.level}),打出【无懈可击】自保` }; if (isAlly(myId, target.identity)) { const baseScore = Math.round((threat.score + targetVal) / 2); const handMod = countWuxieInHand(view) <= 1 ? -5 : 0; return { score: clamp(baseScore + roundMod + handMod), reason: `盟友${target.name}被${title}指定(威胁${threat.level}/价值${targetVal}),打出【无懈可击】保护` }; } if (/桃园结义|五谷丰登/.test(title)) return { score: 55, reason: `敌方${target.name}的${title}对我方有利,考虑无懈阻止` }; return { score: 20, reason: `敌方${target.name}被${title}指定,保留【无懈可击】` }; } function scoreRespond(view, action) { const skillId = action.message.skillId; const hp = selfOf(view)?.health ?? 0; if (skillId === "桃" || skillId === "酒") { if (selfDying(view)) return { score: 100, reason: "自己濒死,出【桃】自救(救命)" }; if (someoneDying(view)) return { score: 95, reason: "有人濒死,出【桃】相救" }; return { score: 60, reason: `回应【${skillId}】` }; } if (skillId === "闪") { if (hp <= 1) return { score: 98, reason: "残血被杀,出【闪】保命" }; return { score: 90, reason: "被杀,出【闪】抵消伤害" }; } if (skillId === "杀") { if (hp <= 1) return { score: 92, reason: "残血,出【杀】避免伤害/输掉决斗" }; return { score: 80, reason: "出【杀】回应(决斗/南蛮入侵)" }; } if (skillId === "无懈可击") return scoreWuxie(view, action); return { score: 75, reason: `发动/回应【${skillId}】` }; } function scoreSkip(view, _action) { const me = selfOf(view); const hp = me?.health ?? 0; if (view.pending && !view.pending.isBlocking) { const targetIdx = view.pending.target; const title = view.pending.promptTitle ?? ""; const threat = wuxieThreatLevel(title); if (targetIdx >= 0 && targetIdx < view.players.length) { const myId = me?.identity; if (targetIdx === view.viewer || isAlly(myId, view.players[targetIdx]?.identity)) { if (threat.level === "critical") return { score: 35, reason: `${title}威胁己方${view.players[targetIdx]?.name ?? ""},不宜跳过(强烈建议无懈)` }; return { score: 42, reason: "盟友/自己被锦囊指定,不宜跳过(可能需无懈保护)" }; } if (threat.level === "low") return { score: 65, reason: `敌方被低威胁锦囊(${title})指定,保留【无懈可击】` }; } return { score: 60, reason: "保留【无懈可击】,暂不打出" }; } if (isBlockingRespondToSelf(view)) { if (hp <= 1) return { score: 20, reason: "残血仍不出牌,濒危(建议回应)" }; return { score: 40, reason: "不出牌,承受伤害" }; } return { score: 30, reason: "跳过" }; } function scorePlay(view, action) { const { id, name } = actionCard(action); const card = findCardInHand(view, id); const cardName = card?.name ?? name ?? ""; const me = selfOf(view); if (cardName === "桃") { const hp = me?.health ?? 0; if (hp >= (me?.maxHealth ?? 0)) return { score: 10, reason: "满血,无需出【桃】" }; if (hp <= 1) return { score: 88, reason: "残血,出【桃】回血保命" }; return { score: 55, reason: "不满血,出【桃】回血" }; } if (cardName === "酒") return { score: 35, reason: "出【酒】(杀+1伤害或留作自救)" }; if (cardName === "杀") { if (action.validTargets.length === 0) return { score: 5, reason: "【杀】无合法目标(射程外/无敌人)" }; const myId = me?.identity; const enemies = enemyTargets(view, action); if (enemies.length === 0) return { score: 8, reason: "【杀】无敌方目标(合法目标均为盟友/身份不明)" }; if (myId === "内奸") { const maxHp = Math.max(...enemies.map((p) => p.health)); const prime = enemies.find((p) => p.health === maxHp); if (prime.health <= 1) return { score: 85, reason: `【杀】攻击残血 ${prime.name}(${prime.health}血,可击杀)` }; return { score: 72, reason: `【杀】内奸平衡局势,攻击高血 ${prime.name}(${prime.health}血)` }; } if (myId === "反贼") { const lord = enemies.find((p) => p.identity === "主公"); if (lord) { if (lord.health <= 1) return { score: 90, reason: `【杀】攻击残血主公 ${lord.name}(${lord.health}血,可击杀)` }; return { score: 82, reason: `【杀】反贼优先攻击主公 ${lord.name}(${lord.health}血)` }; } } const minHp = Math.min(...enemies.map((p) => p.health)); const prime = enemies.find((p) => p.health === minHp); if (prime.health <= 1) return { score: 85, reason: `【杀】攻击残血 ${prime.name}(${prime.health}血,可击杀)` }; if (prime.health <= 2) return { score: 78, reason: `【杀】攻击低血 ${prime.name}(${prime.health}血)` }; return { score: 72, reason: `【杀】攻击目标(${enemies.length}个敌方)` }; } if (cardName === "顺手牵羊" || cardName === "过河拆桥") { const enemies = enemyTargets(view, action); if (enemies.length === 0) return { score: 18, reason: `【${cardName}】合法目标均为盟友/身份不明` }; if (!enemies.some((p) => p.handCount > 0 || Object.keys(p.equipment ?? {}).length > 0)) return { score: 8, reason: `${cardName}目标无牌可取` }; return { score: 75, reason: `【${cardName}】夺取敌方目标牌` }; } if (cardName === "无中生有") return { score: 80, reason: "【无中生有】摸2张牌(稳定过牌)" }; if (cardName === "决斗") { if (action.validTargets.length === 0) return { score: 5, reason: "【决斗】无合法目标" }; if (enemyTargets(view, action).length === 0) return { score: 18, reason: "【决斗】合法目标均为盟友/身份不明" }; return { score: 70, reason: "【决斗】敌方目标(注意己方杀数)" }; } if (cardName === "南蛮入侵" || cardName === "万箭齐发") { const myId = me?.identity; const allies = view.players.filter((p) => p.alive && p.index !== view.viewer && isAlly(myId, p.identity)).length; if (allies > 0) return { score: Math.max(20, 68 - allies * 15), reason: `【${cardName}】会伤害${allies}名盟友,降分` }; return { score: 68, reason: `【${cardName}】群体伤害(无盟友受波及)` }; } if (cardName === "借刀杀人") { if (action.validTargets.length === 0) return { score: 5, reason: "【借刀杀人】无合法目标" }; if (enemyTargets(view, action).length === 0) return { score: 18, reason: "【借刀杀人】合法目标均为盟友/身份不明" }; return { score: 72, reason: "【借刀杀人】夺取/借武器" }; } if (cardName === "桃园结义") return view.players.some((p) => p.alive && p.health < p.maxHealth) ? { score: 65, reason: "【桃园结义】群体回血" } : { score: 20, reason: "无人受伤,【桃园结义】收益低" }; if (cardName === "五谷丰登") return { score: 50, reason: "【五谷丰登】(双方都受益)" }; if (cardName === "乐不思蜀" || cardName === "兵粮寸断") { if (action.validTargets.length === 0) return { score: 5, reason: `【${cardName}】无合法目标` }; if (enemyTargets(view, action).length === 0) return { score: 18, reason: `【${cardName}】合法目标均为盟友/身份不明` }; return { score: 73, reason: `【${cardName}】控场敌方目标` }; } if (cardName === "闪电") return { score: 35, reason: "【闪电】高风险延时锦囊(视局势)" }; if (cardName === "铁索连环") return { score: 55, reason: "【铁索连环】(视局势)" }; if (cardName === "火攻") { if (action.validTargets.length === 0) return { score: 5, reason: "【火攻】无合法目标" }; if (enemyTargets(view, action).length === 0) return { score: 18, reason: "【火攻】合法目标均为盟友/身份不明" }; return { score: 65, reason: "【火攻】敌方目标" }; } if (card?.type === "装备牌") { const slot = card.subtype ?? void 0; if (slot ? !!me?.equipment?.[slot] : false) return { score: 15, reason: `${slot}槽已占用,装备【${cardName}】收益低` }; return { score: (me?.handCount ?? 0) > 2 ? 58 : 45, reason: `装备【${cardName}】(${slot ?? "未知槽位"})` }; } return { score: 50, reason: `使用【${cardName}】` }; } /** 卡牌保留优先级(越高越保留,越低越优先弃)。 * 用于弃牌阶段选牌:低分先弃。 * 装备牌(type==='装备牌')最优先弃(多余装备);桃/无懈/闪最该留。 */ function cardKeepPriority(card) { if (card.type === "装备牌") return 30; switch (card.name) { case "桃": return 100; case "无懈可击": return 90; case "闪": return 85; case "无中生有": return 78; case "杀": return 70; case "顺手牵羊": case "过河拆桥": return 65; case "乐不思蜀": case "兵粮寸断": return 60; case "决斗": case "借刀杀人": return 55; case "南蛮入侵": case "万箭齐发": return 50; case "桃园结义": case "五谷丰登": return 45; case "闪电": return 35; default: return 40; } } /** 从手牌中选出建议弃掉的 cardIds(按保留优先级从低到高取前 N 张)。 */ function suggestDiscardCards(hand, count) { if (count <= 0 || hand.length === 0) return []; return [...hand].sort((a, b) => cardKeepPriority(a) - cardKeepPriority(b)).slice(0, count).map((c) => c.id); } function scoreDiscard(view, action) { const me = selfOf(view); const hand = me?.hand ?? []; const handCount = me?.handCount ?? hand.length; const max = me?.maxHealth ?? 0; const overflow = Math.max(0, handCount - max); if (overflow <= 0) return { score: 50, reason: "弃牌阶段(手牌未超限,无需弃牌)" }; const existingCardIds = action.message.params.cardIds; if (existingCardIds && existingCardIds.length > 0) { const selectedCards = existingCardIds.map((id) => hand.find((c) => c.id === id)).filter(Boolean); if (selectedCards.length > 0) { const avgPriority = selectedCards.reduce((s, c) => s + cardKeepPriority(c), 0) / selectedCards.length; if (avgPriority >= 80) return { score: 25, reason: `弃牌选择不佳:弃掉了高价值牌(均保留优先级${avgPriority.toFixed(0)}),建议保留桃/闪/无懈` }; return { score: 55, reason: `弃牌选择合理(均保留优先级${avgPriority.toFixed(0)})` }; } } const suggested = suggestDiscardCards(hand, overflow); if (suggested.length === 0) return { score: 50, reason: `弃牌阶段(手牌${handCount}>体力${max},需弃${overflow}张;优先弃多余装备>无用锦囊,保留桃>闪>杀)` }; return { score: 50, reason: `弃牌阶段:建议弃 ${suggested.map((id) => hand.find((c) => c.id === id)).filter(Boolean).map((c) => `${c.suit}${c.rank}${c.name}`).join("、")}(保留桃>闪>无懈>杀>锦囊>装备)。建议弃牌cardIds: [${suggested.join(", ")}]` }; } var STRONG_CHARACTERS = new Set([ "诸葛亮", "司马懿", "曹操", "刘备", "孙权", "貂蝉", "甄姬", "郭嘉", "陆逊", "黄月英", "孙尚香", "张角", "华佗", "吕布", "夏侯渊", "张飞", "许褚" ]); function scoreSelectChar(view, action) { const params = action.message.params; const name = typeof params.character === "string" ? params.character : ""; const strong = STRONG_CHARACTERS.has(name); return { score: strong ? 60 : 45, reason: strong ? `选择强力武将【${name}】` : `选择武将【${name}】` }; } function scoreTransform(view, action) { const base = scorePlay(view, action); return { score: clamp(base.score - 2), reason: `转化:${base.reason}` }; } function scoreDistribute(_view, action) { return { score: 55, reason: `分配/调度【${action.message.skillId}】(视手牌结构)` }; } /** * 为单个动作评分。 * 纯函数:只读 view 与 action,无副作用。 */ function scoreAction(view, action) { let result; switch (action.category) { case "respond": result = scoreRespond(view, action); break; case "skip": result = scoreSkip(view, action); break; case "play": result = scorePlay(view, action); break; case "discard": result = scoreDiscard(view, action); break; case "selectChar": result = scoreSelectChar(view, action); break; case "transform": result = scoreTransform(view, action); break; case "distribute": result = scoreDistribute(view, action); break; default: result = { score: 30, reason: action.description }; } return { score: clamp(result.score), reason: result.reason }; } /** * 按评分降序排序动作(同分保持稳定顺序)。 * 返回新数组,不修改入参。 */ function rankActions(view, actions) { const scored = actions.map((a, idx) => ({ a, idx, s: scoreAction(view, a).score })); scored.sort((x, y) => y.s - x.s || x.idx - y.idx); return scored.map((x) => x.a); } //#endregion //#region src/server/logger.ts var LEVEL_PRIORITY = { debug: 0, info: 1, warn: 2, error: 3 }; var currentLevel = process.env.LOG_LEVEL ?? "info"; function shouldLog(level) { return LEVEL_PRIORITY[level] >= LEVEL_PRIORITY[currentLevel]; } function formatMessage(level, context, message, data) { const base = `[${(/* @__PURE__ */ new Date()).toISOString()}] [${level.toUpperCase()}] [${context}] ${message}`; if (data !== void 0) return `${base} ${JSON.stringify(data)}`; return base; } function createLogger(context) { return { debug(message, data) { if (shouldLog("debug")) console.log(formatMessage("debug", context, message, data)); }, info(message, data) { if (shouldLog("info")) console.log(formatMessage("info", context, message, data)); }, warn(message, data) { if (shouldLog("warn")) console.warn(formatMessage("warn", context, message, data)); }, error(message, data) { if (shouldLog("error")) console.error(formatMessage("error", context, message, data)); } }; } //#endregion //#region src/server/lifecycles.ts var log$2 = createLogger("lifecycles"); var resources = /* @__PURE__ */ new Map(); /** * 注册一个资源用于统一清理 * @param name 资源唯一名称 * @param ref 资源引用(仅用于标识,不做深拷贝) * @param cleanup 清理函数(可选,默认只从注册表移除) */ function register(name, ref, cleanup) { if (resources.get(name)) log$2.warn(`资源 ${name} 重复注册,覆盖之前的注册`); resources.set(name, { name, ref, cleanup: cleanup ?? (() => { resources.delete(name); }) }); } //#endregion //#region src/client/replay/recorder.ts /** 深拷贝 GameView(剥离函数引用,保证 JSON 可序列化) */ function cloneView(view) { return JSON.parse(JSON.stringify(view)); } /** 从某座次 initialView 提取公共 baseline(剥离 viewer-dependent 字段) */ function extractBaseline(view) { const { cardMap, log, turn, phase, currentPlayerIndex, zones, settlementStack, pending, deadline, deadlineTotalMs, players } = view; return { cardMap, log, turn, phase, currentPlayerIndex, zones, settlementStack, pending, deadline, deadlineTotalMs, players: players.map((p) => { const { hand: _hand, identity: _identity, identityHidden: _hidden, ...pub } = p; return pub; }) }; } /** 从内部座次记录提取私有 delta */ function extractDelta(rec) { const v = rec.initialView; const privateHands = []; const identityView = []; for (const p of v.players) { if (p.hand !== void 0) privateHands.push({ index: p.index, hand: p.hand }); identityView.push({ index: p.index, identity: p.identity, identityHidden: p.identityHidden }); } const events = rec.events.map((e) => ({ time: e.time, event: e.event })); return { viewer: v.viewer, playerName: rec.playerName, privateHands, identityView, events }; } var ReplayRecorder = class { /** seatIndex → 内部录像 */ seats = /* @__PURE__ */ new Map(); /** 各座次内部 seq 计数器 */ seqCounters = /* @__PURE__ */ new Map(); /** 各座次是否已捕获 initialView */ initialized = /* @__PURE__ */ new Set(); /** * 记录某座次收到的事件批次。 * * @param seat 座次下标 * @param view 该座次当前 GameView(用于首次捕获 initialView) * @param events 本次新事件 */ record(seat, view, events, now = Date.now()) { if (view && !this.initialized.has(seat)) { if (view.players.length === 0 || !view.players.every((p) => p.character)) return; this.seats.set(seat, { seatIndex: seat, playerName: view.players[seat]?.name ?? (seat < 0 ? "旁观" : `P${seat}`), initialView: cloneView(view), events: [] }); this.seqCounters.set(seat, 0); this.initialized.add(seat); } const rec = this.seats.get(seat); if (!rec) return; let seq = this.seqCounters.get(seat) ?? 0; for (const event of events) { rec.events.push({ seq, time: now, event }); seq++; } this.seqCounters.set(seat, seq); } /** 是否有可导出的录像数据(至少一个座次已初始化) */ hasData() { return this.initialized.size > 0; } /** 组装最终录像文件(v2: baseline + delta)。游戏结束时调用。 */ finalize(meta) { const entries = [...this.seats.entries()].sort((a, b) => a[0] - b[0]); if (entries.length === 0) return { format: "sanguosha-replay", version: 2, meta, baseline: { cardMap: {}, log: [], turn: { round: 0, phase: "准备", vars: {} }, phase: "准备", currentPlayerIndex: 0, zones: { deckCount: 0, discardPileCount: 0, processing: [] }, settlementStack: [], pending: null, deadline: null, deadlineTotalMs: 0, players: [] }, seats: {} }; const [, base] = entries.find(([seat]) => seat >= 0) ?? entries[0]; const baseline = extractBaseline(base.initialView); const seats = {}; for (const [seat, rec] of entries) seats[seat] = extractDelta(rec); return { format: "sanguosha-replay", version: 2, meta, baseline, seats }; } /** 清空(新一局重置) */ reset() { this.seats.clear(); this.seqCounters.clear(); this.initialized.clear(); } }; //#endregion //#region src/engine/view/events-for-viewer.ts function eventsForViewer(state, viewer, sinceSeq = 0) { const out = []; for (const e of state.atomHistory) { if (e.seq <= sinceSeq) continue; if (e.kind === "atom") { const owner = e.viewEvents.ownerViews.get(viewer); if (owner === null) continue; const viewEvent = owner ?? e.viewEvents.othersView; if (!viewEvent) continue; out.push({ seq: e.seq, timestamp: e.timestamp, view: viewEvent }); } else { const data = e.views ? e.views.get(String(viewer)) ?? null : e.data; if (data !== null) out.push({ seq: e.seq, timestamp: e.timestamp, notify: { skillId: e.skillId, eventType: e.eventType, data } }); } } return out; } process.env.SGS_HISTORY_DIR ? resolve(process.env.SGS_HISTORY_DIR) : join(process.cwd(), "data", "history"); /** roomId → 条目列表(时间升序,最新在末尾)。懒加载缓存 */ var cache = /* @__PURE__ */ new Map(); register("gameHistoryCache", cache, () => { cache.clear(); }); /** 从终局 state + 开局基线视图组装 v2 录像文件。 * baselineViews: 开局(选将完成后)逐座次捕获的完整视图; * baselineSeq: 捕获时的 state.seq,此后的事件逐条追加(与客户端实时录制同构: * initialView + 增量 ViewEvent,notify 事件不进录像)。 * 基线缺失(空数组)返回 null。 */ function buildReplayFile(state, baselineViews, baselineSeq, meta) { if (baselineViews.length === 0) return null; const rec = new ReplayRecorder(); for (const bv of baselineViews) { rec.record(bv.viewer, bv, [], 0); for (const env of eventsForViewer(state, bv.viewer, baselineSeq)) { if (!env.view) continue; rec.record(bv.viewer, null, [env.view], env.timestamp); } } if (!rec.hasData()) return null; return rec.finalize(meta); } //#endregion //#region shoal/table.ts init_skill(); init_clock(); init_characters(); init_lifecycle(); var CAMP = { 主公: "主公", 忠臣: "主公", 反贼: "反贼", 内奸: "内奸" }; var POOL = allCharacters.map((c) => ({ name: c.name, skills: c.skills.map((s) => s.name) })); var TICK_MS = 500; var Table = class Table { state; n; seed; clock = new VirtualClock(); config; hgc = new HeadlessGameClient("http://offline.invalid"); skillsKey = /* @__PURE__ */ new Map(); baseline = []; baselineSeq = 0; over = null; id; static games = 0; constructor(seed, n = 5) { this.seed = seed; this.n = n; this.id = `sgs-${seed}-${++Table.games}`; } async open(names) { clearRegistry(); const config = this.config = { characters: POOL, playerCount: this.n, seed: this.seed, gameId: this.id, handSize: 4, timeoutSec: 30, mode: "身份局", playerNames: names }; this.state = create(config); this.state.clock = this.clock; this.state.startedAt = this.clock.now(); await bootstrap(this.state, config); await this.settle(); } async quiet(ticks) { let last = -1; for (let quiet = 0; quiet < ticks;) { await new Promise((r) => setImmediate(r)); if (this.state.seq === last) quiet++; else quiet = 0, last = this.state.seq; } } /** Let the engine run its fire-and-forget execute chain to the next ask. */ async settle() { await this.quiet(3); if (!this.baseline.length && this.state.players.every((p) => p.character) && !this.state.charSelecting) { this.baseline = [...Array(this.n).keys(), -1].map((v) => JSON.parse(JSON.stringify(buildView(this.state, v)))); this.baselineSeq = this.state.seq; } if (!this.over) { const r = await checkGameOver(this.state); if (r.gameOver) this.over = { winner: r.winner }; } } /** The seat's ask, or null when it has nothing to do (HeadlessGameClient.needsAction, seat by seat). */ async ask(seat) { if (this.over) return null; const view = buildView(this.state, seat); const p = view.pending; if (!p) return null; if (p.target >= 0 && p.target !== seat) return null; if (p.target === seat && p.responseMode === "silent") return null; if (p.target === seat && p.isBlocking === false && view.phase !== "出牌") return null; if (p.target < 0 && this.broadcastSlot()?.skippedPlayers?.has(seat)) return null; const me = view.players[seat]; const key = me?.skills.join(",") ?? ""; if (me?.character && this.skillsKey.get(seat) !== key) { await registerSkillActions(seat, me.skills); this.skillsKey.set(seat, key); } const h = this.hgc; h._view = view; h._seatIndex = seat; const legal = this.hgc.getAvailableActions(); if (!legal.length) return null; const snap = projectView(view); const d = discardRange(view); if (d && snap.pending) Object.assign(snap.pending, { discard: d }); const best = rankActions(snap, legal)[0]; return { seat, view, obs: { ...snap, game: this.id, recommended: best ? legal.indexOf(best) : null }, legal, key: `${(p.target < 0 ? this.broadcastSlot() : this.state.pendingSlots.get(seat))?.createdSeq ?? this.state.seq}:${p.target}:${JSON.stringify(legal.map((a) => a.description))}` }; } broadcastSlot() { return [...this.state.pendingSlots.values()].find((s) => (s.atom.target ?? 0) < 0); } /** Every seat that must or may act now. A broadcast ask (无懈可击) goes to every seat; one that can only pass is * passed for at once, as the upstream client's auto-skip does, so only seats holding an answer are asked. */ async asks() { for (;;) { const out = []; let passed = false; for (let s = 0; s < this.n && !passed; s++) { const a = await this.ask(s); if (a && a.view.pending.target < 0 && a.legal.every((l) => l.category === "skip")) passed = await this.act(s, 0); else if (a) out.push(a); } if (!passed) return out; } } /** A seat's move: an index into its legal list, or a ClientMessage (an MCP action, its params filled in). * The engine is the judge: a move it refuses changes nothing and returns false. */ async act(seat, action) { const a = await this.ask(seat); if (!a) return false; const msg = toMessage(a, action); if (!msg) return false; const again = `${this.state.turn?.round}:${this.state.currentPlayerIndex}:${seat}:${JSON.stringify(msg)}`; if (msg.actionType === "use" && (this.used.get(again) ?? 0) >= 3) return false; const first = msg.preceding?.[0]; if (first && findActionEntry(this.state, first.skillId, seat, first.actionType)?.validate(this.state, first.params) !== null) return false; this.clock.advanceTo(this.clock.now() + TICK_MS); const view = a.view; const r = await dispatch(this.state, { ...msg, ownerId: seat, baseSeq: this.state.seq, pendingSeq: view.pending?.isBlocking ? this.state.seq : void 0 }).catch(() => ({ accepted: false, settle: Promise.resolve(void 0) })); await Promise.race([r.settle, this.quiet(20)]); await this.settle(); if (r.accepted && msg.actionType === "use") this.used.set(again, (this.used.get(again) ?? 0) + 1); return r.accepted; } /** Nobody can act: let the engine's own clock run to its next deadline (a silent ask timing out). With no deadline * either, the engine may still be loading a skill module: wait for it a little before calling the game stuck. */ async idle() { const next = this.clock.events.filter((e) => !e.cancelled).map((e) => e.at).sort((x, y) => x - y)[0]; if (next === void 0) { if (++this.waits > 100) return false; await new Promise((r) => setTimeout(r, 20)); await this.settle(); return true; } this.waits = 0; this.clock.advanceTo(next); await this.settle(); return true; } waits = 0; used = /* @__PURE__ */ new Map(); identity(seat) { return this.state.players[seat]?.identity ?? ""; } /** +1 for every seat of the winning camp, -1 for the others; the engine names one seat of the winners. */ scores() { const camp = this.over?.winner !== void 0 ? CAMP[this.identity(this.over.winner)] : void 0; const out = {}; for (let s = 0; s < this.n; s++) out[String(s)] = camp && CAMP[this.identity(s)] === camp ? 1 : -1; return out; } result() { const w = this.over?.winner; if (w === void 0) return this.over ? "no winner" : "unfinished"; const camp = CAMP[this.identity(w)]; return camp === "主公" ? "主公 and 忠臣 win" : `${camp} wins`; } /** The upstream's replay file (format sanguosha-replay v2): every seat's view and the spectator's. */ replay(title) { return buildReplayFile(this.state, this.baseline, this.baselineSeq, { createdAt: 0, playerCount: this.n, characters: this.state.players.map((p) => p.character ?? ""), roomName: title }); } /** The public view (no one's hand), for spectators. */ publicView() { return buildView(this.state, -1); } }; /** What a move names: an index into legal, one of legal's entries (its message's params filled in or not), or such a * message by itself. The engine judges it; ownerId is always the seat's. */ function toMessage(a, action) { if (typeof action === "number") { const l = a.legal[action]; return l ? l.message : null; } if (!action || typeof action !== "object") return null; if ("message" in action) return toMessage(a, action.message); const m = action; if (typeof m.skillId !== "string" || typeof m.actionType !== "string") return null; return { skillId: m.skillId, actionType: m.actionType, ownerId: a.seat, params: m.params ?? {}, ...m.preceding ? { preceding: m.preceding } : {} }; } /** How many cards a discard ask wants (the browser's usePendingState reads the same prompt). */ function discardRange(view) { const p = view.pending; if (!p) return null; const atom = p.atom; const f = atom.requestType === "__弃牌" ? atom.prompt?.cardFilter : p.mandatory && p.prompt?.type === "useCard" ? p.prompt.cardFilter : null; if (!f) return null; const min = f.min ?? 0; return { min, max: f.max ?? min }; } async function skillInfo(names) { const out = {}; for (const n of names) out[n] = await getSkillDescriptionAsync(n).catch(() => null) ?? null; return out; } var KEEP = { 桃: 100, 闪: 90, 无懈可击: 85, 酒: 70, 杀: 60 }; var keep = (c) => KEEP[c.name] ?? (c.type === "装备牌" ? 30 : 45); function allies(obs, t) { const me = obs.players[obs.viewer]?.identity; const them = obs.players[t]?.identity; if (!me || !them || me === "内奸") return false; return CAMP[me] === CAMP[them]; } /** Fill a move's open parameters, or null when this simple filler cannot. */ function fill(obs, l) { const m = structuredClone(l.message); const params = m.params; const me = obs.players[obs.viewer]; const hand = me?.hand ?? []; if (l.validTargets.length && !Array.isArray(params.targets)) { const min = l.minTarget ?? 1; const max = l.maxTarget ?? 1; const lord = obs.players.findIndex((p) => p.identity === "主公"); const foes = l.validTargets.filter((t) => !allies(obs, t)); const rebel = me?.identity === "反贼"; const order = [...foes.sort((x, y) => Number(rebel && y === lord) - Number(rebel && x === lord)), ...l.validTargets.filter((t) => !foes.includes(t))]; if (order.length < min) return null; if (!foes.length && min > 0 && l.category === "play") return null; params.targets = order.slice(0, Math.max(min, Math.min(max, 1))); params.target = params.targets[0]; } if (Array.isArray(params.cardIds) && !params.cardIds.length) { const d = obs.pending?.discard; const need = Math.max(1, d?.min ?? 1); if (hand.length < need) return null; params.cardIds = [...hand].sort((a, b) => keep(a) - keep(b)).slice(0, need).map((c) => c.id); } if (Array.isArray(params.allocation) && !params.allocation.length) return null; return m; } /** The upstream AI as a seat: the heuristic's ranking, the best move this filler can complete that the engine has not * refused at this ask (obs.refused), else a pass. A move already made twice this turn is dropped, or 激将 that * nobody answers would be asked forever. */ var Heuristic = class { turn = ""; made = /* @__PURE__ */ new Map(); act(obs, legal) { const turn = `${obs.game}:${obs.turn.round}:${obs.currentPlayerIndex}`; if (turn !== this.turn) this.turn = turn, this.made.clear(); const refused = new Set((obs.refused ?? []).map((r) => JSON.stringify(r))); for (const l of rankActions(obs, legal)) { const m = fill(obs, l); const k = JSON.stringify(m); if (m && !refused.has(k) && (this.made.get(k) ?? 0) < 2) { this.made.set(k, (this.made.get(k) ?? 0) + 1); return m; } } return passIndex(legal); } }; /** The move that gives up: a skip, the end of the play phase, else the last listed. */ function passIndex(legal) { const i = legal.findIndex((l) => l.category === "skip" || l.message.actionType === "end"); return i >= 0 ? i : legal.length - 1; } //#endregion //#region shoal/bots.ts var REFEREE = path.dirname(fileURLToPath(import.meta.url)); var Bot = class { dir; limitMs; errors = 0; dead = false; p; n = 0; waiting = /* @__PURE__ */ new Map(); ready; constructor(dir, limitMs = 2e3) { this.dir = dir; this.limitMs = limitMs; if (!existsSync(path.join(dir, "bot.mjs"))) throw new Error(`No bot at ${path.join(dir, "bot.mjs")}`); const env = Object.fromEntries(Object.entries(process.env).filter(([k]) => !/^(SS_|SLURM_)/.test(k))); this.p = spawn(process.execPath, [path.join(REFEREE, "bot.mjs"), path.resolve(dir)], { stdio: [ "pipe", "pipe", process.env.SGS_BOT_STDERR ? "inherit" : "ignore" ], env }); createInterface({ input: this.p.stdout }).on("line", (line) => { try { const m = JSON.parse(line); if (m && typeof m === "object" && "answer" in m) this.waiting.get(m.n)?.(m.answer); } catch {} }); this.p.on("exit", () => { this.dead = true; for (const r of this.waiting.values()) r(void 0); }); this.p.stdin.on("error", () => {}); this.ready = this.ask({ hello: true }, 1e4); } /** The bot's answer, or undefined when it is late, broken or gone. */ async ask(msg, limitMs = this.limitMs) { if (this.dead) return void 0; const n = ++this.n; let timer; const answer = await new Promise((resolve) => { this.waiting.set(n, resolve); timer = setTimeout(() => resolve(void 0), limitMs); this.p.stdin.write(`${JSON.stringify({ ...msg, n })}\n`); }); clearTimeout(timer); this.waiting.delete(n); if (answer === void 0 || answer === null) { if (++this.errors >= 20) this.close(); return; } return answer; } close() { this.dead = true; this.p.kill(); } }; //#endregion //#region shoal/play.ts var ROOT = path.dirname(REFEREE); var SEEDS = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]; var MAX_MOVES = 3e3; var ROLES = [ "主公", "忠臣", "反贼", "内奸" ]; /** One ask to its end: the chooser gets up to three tries (each refused move is shown back to it in obs.refused), * then the seat passes. True when the engine took a move or the ask is gone anyway. */ async function answer(t, a, choose) { const refused = []; for (let k = 0; k < 3; k++) { const move = await choose(refused.length ? { ...a.obs, refused } : a.obs, a.legal); if (move === void 0 || move === null) break; if (await t.act(a.seat, move) || await gone(t, a)) return true; refused.push(move); } return pass(t, a); } /** A refused move can still end the ask: a failed transform (解围 as 无懈可击) clears the engine's open asks. */ async function gone(t, a) { return (await t.ask(a.seat))?.key !== a.key; } /** A move the engine takes for a seat that gave none: a pass, else what the upstream AI would play, else any. */ async function pass(t, a) { const tries = [passIndex(a.legal)]; const h = new Heuristic(); for (let k = 0; k < 4; k++) tries.push(h.act({ ...a.obs, refused: tries }, a.legal)); tries.push(...a.legal.keys()); for (const m of tries) if (await t.act(a.seat, m) || await gone(t, a)) return true; return false; } /** A whole game with every seat a bot process. */ /** A whole game with every seat a bot process; why it stopped when it did not end. */ async function game(t, bots) { for (let moves = 0; !t.over; moves++) { if (moves >= MAX_MOVES) return `${MAX_MOVES} moves`; const asks = await t.asks(); if (!asks.length) { if (!await t.idle()) return "stuck: nobody to move"; continue; } const a = asks[0]; if (!await answer(t, a, (obs, legal) => bots[a.seat].ask({ obs, legal }))) return `stuck: seat ${a.seat} has no move the engine takes (${a.obs.pending?.requestType})`; } return ""; } function routeName() { const d = (process.env.SS_ROUTE_DIR ?? "routes").replace(/\/+$/, ""); return d === "routes" ? "main" : d.split("/").slice(1).join("/"); } function field() { const dir = path.join(ROOT, "field"); return readdirSync(dir).filter((d) => !d.startsWith(".") && statSync(path.join(dir, d)).isDirectory()).sort(); } /** replays/ per game, and replays/matches.json saying who sat where and how it ended (as kit/league.py). */ var Replays = class { root; index = []; constructor(root = "replays") { this.root = root; mkdirSync(root, { recursive: true }); } add(file, data, title, seats, result) { writeFileSync(path.join(this.root, file), JSON.stringify(data)); this.index.push({ file, title, seats, result }); writeFileSync(path.join(this.root, "matches.json"), JSON.stringify(this.index, null, 2)); } }; function writeMetrics(score, subtasks, file = "metrics.json") { const r = (x) => Math.round(x * 1e6) / 1e6; const out = { score: r(score), subtasks: Object.fromEntries(Object.entries(subtasks).map(([k, v]) => [k, r(v)])) }; writeFileSync(file, JSON.stringify(out, null, 2)); console.log(`score ${out.score} over ${Object.keys(subtasks).length}: ${Object.entries(out.subtasks).map(([k, v]) => `${k} ${v}`).join(", ")}`); } async function play(mode, route) { if (mode !== "check" && mode !== "practice") throw new Error("usage: node referee/play.mjs check|practice "); const name = routeName(); const me = new Bot(route); const reps = mode === "practice" ? new Replays() : null; const won = Object.fromEntries(ROLES.map((r) => [r, []])); for (const opp of field()) { const others = [ 0, 1, 2, 3 ].map(() => new Bot(path.join(ROOT, "field", opp))); await Promise.all([me.ready, ...others.map((b) => b.ready)]); for (const seed of mode === "check" ? SEEDS : SEEDS.slice(0, 1)) { const roles = await identities(seed); const mine = mode === "check" ? [ 0, 1, 2, 3, 4 ] : [0, roles.indexOf("反贼")]; for (const seat of mine) { const bots = [ ...others.slice(0, seat), me, ...others.slice(seat) ]; const names = bots.map((b) => b === me ? name : opp); const t = new Table(seed); await t.open(names); const why = await game(t, bots); const role = t.identity(seat); const win = t.scores()[String(seat)] > 0; won[role].push(win ? 1 : 0); console.log(`seed ${seed}, ${name} as ${role} (seat ${seat}) vs ${opp}: ${t.result()}${why ? ` (${why})` : ""}`); reps?.add(`${opp}-${seed}-${seat}.json`, { ...t.replay(`${name} vs ${opp}`), shoal: { seat, names } }, `${name} as ${role}, four ${opp} bots, seed ${seed}`, names, `${t.result()}: ${name} ${win ? "won" : "lost"}`); } } others.forEach((b) => b.close()); } me.close(); const rate = (xs) => xs.length ? xs.reduce((a, b) => a + b, 0) / xs.length : 0; const subtasks = Object.fromEntries(ROLES.filter((r) => won[r].length).map((r) => [r, rate(won[r])])); if (mode === "check") writeMetrics(rate(Object.values(subtasks)), subtasks); } /** Who holds which identity on a seed (the lord is always seat 0; the rest follow the seeded shuffle). */ async function identities(seed) { const t = new Table(seed); await t.open([ "", "", "", "", "" ]); return t.state.players.map((p) => p.identity ?? ""); } //#endregion //#region shoal/room.ts var say = (m) => process.stdout.write(`@shoal ${JSON.stringify(m)}\n`); var log = (s) => process.stdout.write(`${s}\n`); /** stdin lines and timers, one at a time. */ var Inbox = class { items = []; wake = null; closed = false; push(i) { this.items.push(i); this.wake?.(); } async next() { while (!this.items.length) { if (this.closed) return null; await new Promise((r) => this.wake = r); this.wake = null; } return this.items.shift(); } }; async function room() { const cfg = JSON.parse(process.env.SS_ROOM || "{}"); const seed = cfg.seed ?? Math.floor(Math.random() * 1e9); const limitMs = (cfg.limit ?? 30) * 1e3; const seats = [ 0, 1, 2, 3, 4 ].map((i) => ({ kind: "auto", ...cfg.seats?.[String(i)], auto: new Heuristic(), seen: 0 })); for (const s of seats) startBot(s); await Promise.all(seats.map((s) => s.bot?.ready)); const names = seats.map((s) => s.name ?? s.route ?? s.field ?? s.kind); const inbox = new Inbox(); const rl = createInterface({ input: process.stdin }); rl.on("line", (line) => { try { inbox.push(JSON.parse(line)); } catch { log(`not JSON: ${line.slice(0, 80)}`); } }); rl.on("close", () => { inbox.closed = true; inbox.push({}); }); const t = new Table(seed); await t.open(names); say({ event: { type: "start", seed, seats: seats.map((s, i) => ({ seat: String(i), kind: s.kind, name: names[i] })), view: t.publicView(), seq: t.state.seq } }); let publicSeq = t.state.seq; let version = 0; for (const [i, s] of seats.entries()) if (s.kind === "human") baseline(i); function startBot(s) { if (s.kind !== "bot") return; try { s.bot = new Bot(s.field ? path.join(ROOT, "field", s.field) : path.resolve(process.env.SS_INPUT ?? ".", s.dir ?? s.route ?? "routes")); } catch (e) { log(`seat ${s.name}: ${e.message}; auto plays it`); s.kind = "auto"; } } /** A person's viewer needs the seat's whole view once, then its events. */ function baseline(i) { say({ to: String(i), obs: { view: buildView(t.state, i), seq: t.state.seq } }); seats[i].seen = t.state.seq; } function publish() { const pub = eventsForViewer(t.state, -1, publicSeq); if (pub.length) say({ event: { type: "events", events: pub } }); publicSeq = t.state.seq; for (const [i, s] of seats.entries()) { if (s.kind !== "human") continue; const mine = eventsForViewer(t.state, i, s.seen); if (mine.length) say({ to: String(i), obs: { events: mine } }); s.seen = t.state.seq; } } function drop(s) { if (s.out) clearTimeout(s.out.timer); s.out = void 0; } function request(a, refused = []) { const s = seats[a.seat]; const v = ++version; const timer = setTimeout(() => inbox.push({ timeout: { seat: a.seat, version: v } }), limitMs); s.out = { version: v, key: a.key, refused, timer }; say({ to: String(a.seat), version: v, obs: { ...a.obs, limit: limitMs / 1e3, ...refused.length ? { refused } : {} }, legal: a.legal }); } async function auto(a) { const s = seats[a.seat]; if (s.kind === "bot" && s.bot && !s.bot.dead) return answer(t, a, (obs, legal) => s.bot.ask({ obs, legal })); return answer(t, a, async (obs, legal) => s.auto.act(obs, legal)); } let stopped = false; let stuck = false; for (let moves = 0; !t.over && !stopped && moves < 3e3;) { publish(); const asks = await t.asks(); publish(); if (t.over) break; if (!asks.length) { if (!await t.idle()) stuck = true; if (stuck) break; continue; } for (const s of seats) if (s.out && !asks.some((a) => seats[a.seat] === s && a.key === s.out.key)) drop(s); const now = asks.find((a) => seats[a.seat].kind === "bot" || seats[a.seat].kind === "auto"); if (now) { if (!await auto(now)) stuck = true; if (stuck) break; moves++; continue; } for (const a of asks) if (!seats[a.seat].out) request(a); const msg = await inbox.next(); if (!msg) break; if (msg.stop || inbox.closed && !msg.act && !msg.control) stopped = true; else if (msg.act) { const i = Number(msg.act.seat); const s = seats[i]; const a = asks.find((x) => x.seat === i); if (!s?.out || !a || s.out.version !== msg.act.version) { log(`stale move for seat ${msg.act.seat}: version ${msg.act.version}, now ${s?.out?.version ?? "none"}`); continue; } const refused = s.out.refused; const v = s.out.version; drop(s); if (await t.act(i, msg.act.action)) { say({ event: { type: "moved", seat: String(i), version: v, by: s.kind } }); moves++; } else { log(`seat ${i}: the engine refused ${JSON.stringify(msg.act.action).slice(0, 200)}`); if (await gone(t, a)) continue; if (refused.length < 2) request(a, [...refused, msg.act.action]); else if (!await answer(t, a, async (obs, legal) => s.auto.act(obs, legal))) stuck = true; else say({ event: { type: "moved", seat: String(i), version: v, by: "auto" } }); } } else if (msg.control) { const i = Number(msg.control.seat); const s = seats[i]; if (!s) continue; s.bot?.close(); Object.assign(s, { kind: msg.control.kind, name: msg.control.name ?? s.name, dir: msg.control.dir, field: msg.control.field, bot: void 0 }); startBot(s); await s.bot?.ready; drop(s); say({ event: { type: "control", seat: String(i), kind: s.kind, name: s.name } }); if (s.kind === "human") baseline(i); } else if (msg.timeout) { const s = seats[msg.timeout.seat]; const a = asks.find((x) => x.seat === msg.timeout.seat); if (!s?.out || s.out.version !== msg.timeout.version || !a) continue; log(`seat ${msg.timeout.seat}: no move in ${limitMs / 1e3}s, auto plays it`); drop(s); say({ event: { type: "moved", seat: String(msg.timeout.seat), version: msg.timeout.version, by: "auto" } }); if (!await answer(t, a, async (obs, legal) => s.auto.act(obs, legal)) && !await pass(t, a)) stuck = true; moves++; } } publish(); for (const s of seats) drop(s), s.bot?.close(); const result = stopped ? "stopped" : stuck ? "stuck" : t.result(); const identities = Object.fromEntries(t.state.players.map((p, i) => [String(i), p.identity ?? ""])); say({ end: { result, winner: t.over?.winner === void 0 ? "无人" : String(t.over.winner), scores: t.over ? t.scores() : Object.fromEntries(names.map((_, i) => [String(i), 0])), identities } }); const focus = Math.max(0, seats.findIndex((s) => s.kind === "human" || s.kind === "agent")); new Replays().add("room.json", { ...t.replay("Shoal room"), shoal: { seat: focus, names } }, `A room: ${names.join(", ")}`, names, result); rl.close(); process.exit(0); } //#endregion //#region shoal/sgs.ts init_characters(); /** Every general with its faction, health and skills (the upstream's own skill texts): referee/generals.json. */ async function catalog() { const out = {}; for (const c of allCharacters) out[c.name] = { faction: c.faction, maxHealth: c.maxHealth, gender: c.gender, skills: await skillInfo(c.skills.map((s) => s.name)) }; return out; } //#endregion export { Heuristic, Table, catalog, play, room, skillInfo };