🎉 2.0.1.0版本发布

✨ 重构活动BOSS武将包并新增独立PVP武将包

✨ 完善天书动态难度、过关奖励与羁绊系统

🎨 新增自定义技能选择、羁绊谱册及移动端适配

🔊 整理BOSS配音、图片与模块资源路径

🐛 修复天书关卡、伤害保护及武将包加载问题
This commit is contained in:
2026-08-13 17:28:42 +08:00
parent 49cb75d813
commit 02e7a642ea
54 changed files with 8509 additions and 952 deletions
File diff suppressed because it is too large Load Diff
+525 -60
View File
@@ -1,41 +1,41 @@
const difficultyOrder = [normal, hard, nightmare];
const difficultyOrder = ["normal", "hard", "nightmare"];
const stages = [
{
id: stage1,
name: 第一关,
// bosses: [caocao_boss_shuying, dongzhuo_boss_shuying],
bosses: [zishu_boss_shuying, chouniu_boss_shuying, yinhu_boss_shuying, maotu_boss_shuying, chenlong_boss_shuying, sishe_boss_shuying, wuma_boss_shuying, weiyang_boss_shuying, shenhou_boss_shuying, youji_boss_shuying, xvgou_boss_shuying, haizhu_boss_shuying],
id: "stage1",
name: "第一关",
// bosses: ["zishu_boss_shuying", "yanluowang_boss_shuying"],
bosses: ["zishu_boss_shuying", "chouniu_boss_shuying", "yinhu_boss_shuying", "maotu_boss_shuying", "chenlong_boss_shuying", "sishe_boss_shuying", "wuma_boss_shuying", "weiyang_boss_shuying", "shenhou_boss_shuying", "youji_boss_shuying", "xvgou_boss_shuying", "haizhu_boss_shuying"],
},
{
id: stage2,
name: 第二关,
bosses: [mengpo_boss_shuying, baowei_boss_shuying, niaozui_boss_shuying, heibaiwuchang_boss_shuying, niutoumamian_boss_shuying, yvsai_boss_shuying, huangfeng_boss_shuying, riyeyoushen_boss_shuying, guiwang_boss_shuying, yanluowang_boss_shuying],
id: "stage2",
name: "第二关",
bosses: ["mengpo_boss_shuying", "baowei_boss_shuying", "niaozui_boss_shuying", "heibaiwuchang_boss_shuying", "niutoumamian_boss_shuying", "yvsai_boss_shuying", "huangfeng_boss_shuying", "riyeyoushen_boss_shuying", "guiwang_boss_shuying", "yanluowang_boss_shuying"],
},
{
id: stage3,
name: 第三关,
bosses: [caocao_boss_shuying, simayi_boss_shuying, lvbu_boss_shuying, dongzhuo_boss_shuying],
id: "stage3",
name: "第三关",
bosses: ["caocao_boss_shuying", "simayi_boss_shuying", "lvbu_boss_shuying", "dongzhuo_boss_shuying", "zhangjiao_boss_shuying", "yuanshu_boss_shuying"],
},
{
id: stage4,
name: 第四关,
bosses: [shuishengonggong_boss_shuying, shaohao_boss_shuying, xuannv_boss_shuying, hanba_boss_shuying],
id: "stage4",
name: "第四关",
bosses: ["shuishengonggong_boss_shuying", "huoshenzhurong_boss_shuying", "baiqi_boss_shuying", "kuafu_boss_shuying", "shaohao_boss_shuying", "xuannv_boss_shuying", "hanba_boss_shuying"],
},
];
const bossList = Object.fromEntries(stages.map(stage => [stage.id, stage.bosses]));
const virtualIdols = {
Xiaotao: vtb_xiaotao,
Xiaosha: vtb_xiaosha,
Xiaoshan: vtb_xiaoshan,
Xiaole: vtb_xiaole,
Xiaojiu: vtb_xiaojiu,
Xiaotao: "vtb_xiaotao",
Xiaosha: "vtb_xiaosha",
Xiaoshan: "vtb_xiaoshan",
Xiaole: "vtb_xiaole",
Xiaojiu: "vtb_xiaojiu",
};
const virtualIdolList = Object.values(virtualIdols);
// Boss 的体力与体力上限始终使用相同加成,只需在配置中填写一次。
// Boss 的体力与体力上限始终使用相同加成
const createDifficultyConfig = (startCards, hpBonus = 0) => ({
startCards,
hpBonus,
@@ -88,56 +88,75 @@ const bossDifficulty = {
bosses: {
...createBossGroup(stages[0].bosses, bossDifficultyTemplates.shiershengxiao),
...createBossGroup([yvsai_boss_shuying, huangfeng_boss_shuying], bossDifficultyTemplates.zhuoguiquxie),
...createBossGroup(["yvsai_boss_shuying", "huangfeng_boss_shuying"], bossDifficultyTemplates.zhuoguiquxie),
mengpo_boss_shuying: createBossConfig(bossDifficultyTemplates.zhuoguiquxie, {
nightmare: [guimei_shuying],
nightmare: ["guimei_shuying"],
}),
baowei_boss_shuying: createBossConfig(bossDifficultyTemplates.zhuoguiquxie, addSkillFromHard(yinsha_shuying)),
niaozui_boss_shuying: createBossConfig(bossDifficultyTemplates.zhuoguiquxie, addSkillFromHard(bingyi_shuying)),
baowei_boss_shuying: createBossConfig(bossDifficultyTemplates.zhuoguiquxie, addSkillFromHard("yinsha_shuying")),
niaozui_boss_shuying: createBossConfig(bossDifficultyTemplates.zhuoguiquxie, addSkillFromHard("bingyi_shuying")),
heibaiwuchang_boss_shuying: createBossConfig(bossDifficultyTemplates.zhuoguiquxie, {
hard: [xixing_shuying],
nightmare: [xixing_shuying, taiping_shuying],
hard: ["xixing_shuying"],
nightmare: ["xixing_shuying", "taiping_shuying"],
}),
riyeyoushen_boss_shuying: createBossConfig(bossDifficultyTemplates.zhuoguiquxie, {
nightmare: [duane_shuying],
nightmare: ["duane_shuying"],
}),
niutoumamian_boss_shuying: createBossConfig(bossDifficultyTemplates.zhuoguiquxie, {
hard: [xiaoshou_shuying],
nightmare: [xiaoshou_shuying, guizhao_shuying],
hard: ["xiaoshou_shuying"],
nightmare: ["xiaoshou_shuying", "guizhao_shuying"],
}),
guiwang_boss_shuying: createBossConfig(bossDifficultyTemplates.zhuoguiquxie, {
nightmare: [chihu_shuying],
nightmare: ["chihu_shuying"],
}),
yanluowang_boss_shuying: createBossConfig(bossDifficultyTemplates.zhuoguiquxie, {
hard: [zhennu_shuying],
nightmare: [zhennu_shuying, xuanpan_shuying],
hard: ["zhennu_shuying"],
nightmare: ["zhennu_shuying", "xuanpan_shuying"],
}),
caocao_boss_shuying: createBossConfig(bossDifficultyTemplates.qingqingzijin, addSkillFromHard(yishen_shuying)),
simayi_boss_shuying: createBossConfig(bossDifficultyTemplates.qingqingzijin, addSkillFromHard(yuanlv_shuying)),
lvbu_boss_shuying: createBossConfig(bossDifficultyTemplates.qingqingzijin, addSkillFromHard(zhanjia_shuying)),
dongzhuo_boss_shuying: createBossConfig(bossDifficultyTemplates.qingqingzijin, addSkillFromHard(qubu_shuying)),
caocao_boss_shuying: createBossConfig(bossDifficultyTemplates.qingqingzijin, addSkillFromHard("yishen_shuying")),
simayi_boss_shuying: createBossConfig(bossDifficultyTemplates.qingqingzijin, addSkillFromHard("yuanlv_shuying")),
lvbu_boss_shuying: createBossConfig(bossDifficultyTemplates.qingqingzijin, addSkillFromHard("zhanjia_shuying")),
dongzhuo_boss_shuying: createBossConfig(bossDifficultyTemplates.qingqingzijin, addSkillFromHard("qubu_shuying")),
yuanshu_boss_shuying: createBossConfig(bossDifficultyTemplates.qingqingzijin, addSkillFromHard("duoxi_shuying")),
zhangjiao_boss_shuying: createBossConfig(bossDifficultyTemplates.qingqingzijin, addSkillFromHard("yinlei_shuying")),
...createBossGroup([shaohao_boss_shuying, xuannv_boss_shuying, hanba_boss_shuying], bossDifficultyTemplates.tianshuluandou),
shuishengonggong_boss_shuying: createBossConfig(bossDifficultyTemplates.tianshuluandou, addSkillFromHard(shuishen_shuying)),
baiqi_boss_shuying: {},
pangu_boss_shuying: {},
...createBossGroup(["shaohao_boss_shuying", "xuannv_boss_shuying", "hanba_boss_shuying"], bossDifficultyTemplates.tianshuluandou),
shuishengonggong_boss_shuying: createBossConfig(bossDifficultyTemplates.tianshuluandou, {
nightmare: ["shuishen_shuying"],
}),
huoshenzhurong_boss_shuying: createBossConfig(bossDifficultyTemplates.tianshuluandou, {
nightmare: ["huoshen_shuying"],
}),
baiqi_boss_shuying: createBossConfig(bossDifficultyTemplates.tianshuluandou, {
normal: ["changsheng_shuying"],
hard: ["shashen_shuying"],
nightmare: ["changsheng_shuying", "shashen_shuying"],
}),
kuafu_boss_shuying: createBossConfig(bossDifficultyTemplates.tianshuluandou, {
nightmare: ["shenqu_shuying"],
}),
},
};
const tianshuConfig = {
bossList,
stages,
difficulties: {
normal: {
name: 普通,
name: "普通",
tag: "初入天书",
description: "较低的起始手牌与体力加成,适合熟悉四关流程与奖励机制。",
},
hard: {
name: 困难,
name: "困难",
tag: "渐入险境",
description: "Boss拥有更多起始手牌、额外体力,部分Boss会取得追加技能。",
},
nightmare: {
name: 阴间,
name: "阴间",
tag: "九死一生",
description: "Boss获得最高体力和手牌强化,并启用当前配置中的完整追加技能。",
},
},
difficultyOrder,
@@ -152,40 +171,486 @@ const tianshuConfig = {
killDrawIfFullHp: 2,
stageRecover: 1,
stageDraw: 2,
skillChoiceCount: 5,
skillChoiceCount: 6,
},
settings: {
virtualIdolConfigKey: extension_术樱包_tianShu_Xvni,
virtualIdolRandomPoolConfigKey: extension_术樱包_tianShu_XvniRandomPool,
virtualIdolReviveConfigKey: extension_术樱包_tianShu_dead,
addBossConfigKey: extension_术樱包_tianShu_addBoss,
revivePlayersConfigKey: extension_术樱包_tianShu_revivePlayers,
difficultyStatusKey: shuYing_tianshuDifficulty,
virtualIdolConfigKey: "extension_术樱包_tianShu_Xvni",
virtualIdolRandomPoolConfigKey: "extension_术樱包_tianShu_XvniRandomPool",
virtualIdolReviveConfigKey: "extension_术樱包_tianShu_dead",
addBossConfigKey: "extension_术樱包_tianShu_addBoss",
revivePlayersConfigKey: "extension_术樱包_tianShu_revivePlayers",
difficultyStatusKey: "shuYing_tianshuDifficulty",
},
skillPool: {
banned: [huoxin, jueqing, qinqing, beige, huashen, drlt_zhiti, olzhiti, xinfu_pdgyingshi, rebeige],
additional: [
"reguanxing", "rehuoji", "kongcheng", //蜀中无大将
"rezhiheng", "sbjiang", "sbyingzi", "kurou", "repojun", //江东基业
"xinshenxing", "rezhijian", "pothaoshi", "xinzhiyan", //舌战群儒
"sbwushuang", "sbliyu", //天下无双
"ollongdan", "xinliegong", "new_rewusheng", "new_repaoxiao", "new_yajiao", "sbtieji", //五虎上将
"relieren", "decadezhennan", "gzjili", "spjiedao", "rezhiman", "mansi", "spxizhan", //南蛮入侵
"olchengxiang", "sbjizhi", "sbyiji", "zhiyu", //奇策智囊
"rejieming", "kaikang", "new_rejianxiong", "repindi", "xinwangxi", //天下归心
"shencai", "wuling", "reshuishi", "mbtiantao", "mbxinghun", "qiexie", "yuli", "tingwei", //神武再世
"nzry_shicai", "remingce", "mizhao", "xinfu_tushe", "rejigong", //群雄逐鹿
"xiaoji", "liangzhu", "mozhi", "olzhenlie", "zhiren", //乱世佳人
],
banned: ["huoxin", "jueqing", "qinqing", "beige", "huashen", "drlt_zhiti", "olzhiti", "xinfu_pdgyingshi", "rebeige"],
},
keywordBuffs: [
bonds: [
{
keyword: 杀,
id: "shafa",
name: "杀伐",
keyword: "杀",
levels: [
{ count: 2, damageBonus: 1 },
{ count: 3, damageBonus: 2 },
{
level: 1,
count: 2,
condition: { type: "keyword", keyword: "杀", count: 2 },
skills: ["shuYing_Tianshu_BondSha1"],
text: "使用【杀】的次数上限+1",
},
{
level: 2,
count: 3,
condition: { type: "keyword", keyword: "杀", count: 3 },
skills: ["shuYing_Tianshu_BondSha2"],
text: "使用【杀】的次数上限+2",
},
],
},
{
keyword: 闪,
id: "skill_01",
name: "蜀中无大将",
condition: {
type: "skillNames",
any: ["观星", "火计", "空城"],
},
levels: [
{ count: 2, drawAfterShan: 1 },
{
level: 1,
count: 1,
skills: ["dcjincui", "dcqingshi"],
text: "你获得“尽瘁”和“情势”",
},
{
level: 2,
count: 3,
skills: ["dcjincui", "dcqingshi", "dczhizhe", "shuYing_Tianshu_Skill_01"],
text: "你获得“尽瘁”、“情势”和“智哲”,防止你受到的所有无属性伤害,你造成的伤害增加你体力值点",
},
],
},
{
keyword: 桃,
id: "skill_02",
name: "江东基业",
condition: {
type: "skillNames",
any: ["制衡", "激昂", "英姿", "锐军", "英魂", "反间", "诈降", "弓骑", "苦肉", "旋风", "破军", "奇袭"],
},
levels: [
{ count: 2, recoverBonus: 1 },
{
level: 1,
count: 1,
skills: ["shuYing_Tianshu_Skill_02_Skill1"],
text: "你的摸牌阶段额外已损失体力值张牌",
},
{
level: 2,
count: 2,
skills: ["shuYing_Tianshu_Skill_02_Skill2"],
text: "你的摸牌阶段额外已损失体力值张牌,你弃置敌方牌时对其造成1点伤害",
},
{
level: 3,
count: 3,
skills: ["shuYing_Tianshu_Skill_02_Skill3"],
text: "你的摸牌阶段额外已损失体力值张牌,你弃置敌方牌时对其造成等量点伤害,敌方获得你的牌时对其造成等量点伤害",
},
],
},
{
id: "skill_03",
name: "舌战群儒",
condition: {
type: "skillNames",
any: ["慎行", "直谏", "好施", "直言"],
},
levels: [
{
level: 1,
count: 1,
skills: ["shuYing_Tianshu_Skill_03_Skill1"],
text: "你使用牌指定敌方时,随机弃置其1张牌",
},
{
level: 2,
count: 2,
skills: ["shuYing_Tianshu_Skill_03_Skill2"],
text: "你使用牌指定敌方时,随机弃置其2张牌;你使用牌指定友方时其摸1张牌",
}
],
},
{
id: "skill_04",
name: "天下无双",
condition: {
type: "skillNames",
any: ["无双", "利驭"],
},
levels: [
{
level: 1,
count: 1,
skills: ["shuYing_Tianshu_Skill_04_Skill1"],
text: "你使用的【决斗】伤害增加你体力值点",
},
{
level: 2,
count: 2,
skills: ["shuYing_Tianshu_Skill_04_Skill2"],
text: "你使用的【杀】和【决斗】伤害增加你体力值点",
}
],
},
{
id: "skill_05",
name: "五虎上将",
condition: {
type: "skillNames",
any: ["龙胆", "烈弓", "武圣", "咆哮", "涯角", "铁骑"],
},
levels: [
{
level: 1,
count: 1,
skills: ["shuYing_Tianshu_Skill_05_Skill1"],
text: "你使用【杀】伤害+1",
},
{
level: 2,
count: 2,
skills: ["shuYing_Tianshu_Skill_05_Skill2"],
text: "你使用【杀】伤害+1且不限距离和次数",
},
{
level: 3,
count: 3,
skills: ["shuYing_Tianshu_Skill_05_Skill3"],
text: "你使用【杀】伤害+1且不限距离和次数,你的手牌均视为【杀】",
},
],
},
{
id: "skill_06",
name: "南蛮入侵",
condition: {
type: "skillNames",
any: ["烈刃", "镇南", "蒺藜", "截刀", "制蛮", "蛮嗣", "嬉战"],
},
levels: [
{
level: 1,
count: 1,
skills: ["shuYing_Tianshu_Skill_06_Skill1"],
text: "你使用【南蛮入侵】伤害增加你已损失的体力值点且对友方无效",
},
{
level: 2,
count: 2,
skills: ["shuYing_Tianshu_Skill_06_Skill2"],
text: "你使用【南蛮入侵】伤害增加你已损失的体力值点且对友方无效,你的锦囊牌均视为【南蛮入侵】",
},
{
level: 3,
count: 3,
skills: ["shuYing_Tianshu_Skill_06_Skill3"],
text: "你使用【南蛮入侵】伤害增加你已损失的体力值点且对友方无效,你的手牌牌均视为【南蛮入侵】",
},
],
},
{
id: "skill_07",
name: "奇策智囊",
condition: {
type: "skillNames",
any: ["驱虎", "连环", "奇策", "称象", "集智", "鬼才", "遗计", "智愚"],
},
levels: [
{
level: 1,
count: 1,
skills: ["shuYing_Tianshu_Skill_07_Skill1"],
text: "你的锦囊牌无法被响应",
},
{
level: 2,
count: 2,
skills: ["shuYing_Tianshu_Skill_07_Skill2"],
text: "你的锦囊牌无法被响应,且伤害值+1",
},
{
level: 3,
count: 3,
skills: ["shuYing_Tianshu_Skill_07_Skill3"],
text: "你的锦囊牌无法被响应,且伤害值+1,你使用的锦囊牌造成伤害后你摸1张牌。",
},
],
},
{
id: "skill_08",
name: "天下归心",
condition: {
type: "skillNames",
any: ["节命", "慷忾", "贲育", "归心", "据守", "解围", "奸雄", "刚烈", "品第", "忘隙"],
},
levels: [
{
level: 1,
count: 2,
skills: ["shuYing_Tianshu_Skill_08_Skill1"],
text: "你受到敌方造成伤害后,随机获得所有敌方1张手牌,然后重置你的武将牌",
},
{
level: 2,
count: 3,
skills: ["shuYing_Tianshu_Skill_08_Skill2"],
text: "你受到敌方造成的伤害后,随机获得所有敌方伤害值张手牌并对伤害来源造成2点伤害,然后重置你的武将牌",
},
],
},
{
id: "skill_09",
name: "神武再世",
condition: {
type: "skillNames",
any: ["魄袭", "涉猎", "攻心", "龙魂", "连破", "横骛", "神裁", "三首", "斩决", "凤燎", "武神", "五灵", "慧识", "天涛", "星魂", "挈挟", "驭雳", "霆威"],
},
levels: [
{
level: 1,
count: 1,
skills: ["shuYing_Tianshu_Skill_09_Skill1"],
text: "你对敌方造成伤害后其流失1点体力",
},
{
level: 2,
count: 2,
skills: ["shuYing_Tianshu_Skill_09_Skill2"],
text: "你对敌方造成伤害后其流失等量点体力",
},
{
level: 3,
count: 3,
skills: ["shuYing_Tianshu_Skill_09_Skill3"],
text: "你对敌方造成伤害后其流失等量点体力,若有敌方一回合内流失的体力值大于你的体力值,其失去所有体力",
},
],
},
{
id: "skill_10",
name: "群雄逐鹿",
condition: {
type: "skillNames",
any: ["乱击", "酒池", "恃才", "从谏", "庸肆", "明策", "天命", "密诏", "饰非", "绝策", "图射", "狼袭", "自守", "宗室", "急攻", "矢北"],
},
levels: [
{
level: 1,
count: 2,
skills: ["shuYing_Tianshu_Skill_10_Skill1"],
text: "你的锦囊牌均视为【万箭齐发】且对友方无效",
},
{
level: 2,
count: 3,
skills: ["shuYing_Tianshu_Skill_10_Skill2"],
text: "你的锦囊牌均视为【万箭齐发】且对友方无效,你使用【万箭齐发】时随机获得每名敌方目标的各1张手牌",
},
],
},
{
id: "skill_11",
name: "乱世佳人",
condition: {
type: "skillNames",
any: ["枭姬", "倾国", "流离", "离间", "悲歌", "良助", "陈情", "默识", "贞烈", "秘计", "结姻", "闭月", "怠宴", "织纴", "己诫"],
},
levels: [
{
level: 1,
count: 2,
skills: ["shuYing_Tianshu_Skill_11_Skill1"],
text: "你每回合首次使用牌指定异性敌方时,其不能响应此牌;你每回合首对异性敌方造成的伤害+1",
},
{
level: 2,
count: 3,
skills: ["shuYing_Tianshu_Skill_11_Skill2"],
text: "你使用牌指定异性敌方时,其不能响应此牌;你对异性敌方造成的伤害+1",
},
],
},
],
};
// // 羁绊配置模板,仅供复制参考,不会被系统加载。
// // 所有 condition 都只检查玩家过关选择且当前仍持有的技能。
// const bondTemplates = {
// // 技能描述关键词计数,并以最高等级覆盖低等级。
// keywordLevels: {
// id: "keyword_levels_example",
// name: "关键词多等级模板",
// keyword: "杀",
// levels: [
// {
// level: 1,
// count: 2,
// condition: { type: "keyword", keyword: "杀", count: 2 },
// skills: ["reward_skill_level1"],
// text: "两个包含【杀】的过关技能时获得一级效果",
// },
// {
// level: 2,
// count: 3,
// condition: { type: "keyword", keyword: "杀", count: 3 },
// skills: ["reward_skill_level2"],
// text: "三个包含【杀】的过关技能时以二级效果替换一级效果",
// },
// ],
// },
// // 选择一个指定技能后,发放一个羁绊奖励技能。
// singleSkill: {
// id: "single_skill_example",
// name: "单技能模板",
// levels: [{
// level: 1,
// condition: { type: "skill", skill: "guanxing" },
// skill: "reward_skill",
// text: "选择【观星】后获得一个奖励技能",
// }],
// },
// // 按技能译名匹配:guanxing、reguanxing、sbguanxing 等译名均为“观星”时都会满足。
// translatedSkillName: {
// id: "translated_skill_name_example",
// name: "同名技能模板",
// levels: [{
// level: 1,
// condition: { type: "skillName", name: "观星" },
// skill: "reward_skill",
// text: "选择任意版本的【观星】后获得奖励技能",
// }],
// },
// // 多个译名组合:各名称只需命中任意一个同名技能 ID。
// translatedSkillNames: {
// id: "translated_skill_names_example",
// name: "同名技能组合模板",
// levels: [{
// level: 1,
// condition: { type: "skillNames", all: ["观星", "空城"] },
// skills: ["reward_skill_a", "reward_skill_b"],
// text: "选择任意版本的【观星】和【空城】后获得奖励",
// }],
// },
// // 按不同译名计数:观星、界观星等同译名版本合计只算一种,不能重复推进等级。
// translatedSkillNameLevels: {
// id: "translated_skill_name_levels_example",
// name: "蜀中无大将",
// condition: { type: "skillNames", any: ["观星", "火计", "空城"] },
// levels: [
// { level: 1, count: 1, skill: "reward_skill_level1", text: "具有其中一种不同译名时获得一级效果" },
// { level: 2, count: 3, skill: "reward_skill_level2", text: "集齐观星、火计、空城时以二级效果覆盖一级效果" },
// ],
// },
// // 按武将译名关键词检索:配置“甘宁”可匹配☆甘宁、界甘宁、谋甘宁、极·甘宁等名称。
// translatedCharacterSingleSkill: {
// id: "translated_character_single_skill_example",
// name: "武将名称检索单技能模板",
// levels: [{
// level: 1,
// condition: { type: "characterSkillNames", character: "甘宁", all: ["奇袭"] },
// skill: "reward_skill",
// text: "武将译名包含“甘宁”且取得任意版本的【奇袭】后获得奖励技能",
// }],
// },
// // 名称匹配武将取得多个指定译名技能;双将时主将、副将任一名称命中即可。
// translatedCharacterMultipleSkills: {
// id: "translated_character_multiple_skills_example",
// name: "武将名称检索多技能模板",
// levels: [{
// level: 1,
// condition: { type: "characterSkillNames", character: "甘宁", all: ["奇袭", "奋威"] },
// skills: ["reward_skill_a", "reward_skill_b"],
// text: "任意名称包含“甘宁”的武将取得【奇袭】和【奋威】后获得两个奖励技能",
// }],
// },
// // characters 可填写多个名称关键词;any 表示取得列出的任意一个技能即可触发。
// translatedCharacterAnySkill: {
// id: "translated_character_any_skill_example",
// name: "多武将关键词任选技能模板",
// levels: [{
// level: 1,
// condition: { type: "characterSkillNames", characters: ["甘宁", "吕蒙"], any: ["奇袭", "克己"] },
// skill: "reward_skill",
// text: "武将名称包含“甘宁”或“吕蒙”,并取得【奇袭】或【克己】后获得奖励技能",
// }],
// },
// // 必须同时选择全部指定技能;可一次发放多个羁绊奖励技能。
// allSkills: {
// id: "all_skills_example",
// name: "诸葛亮羁绊模板",
// levels: [{
// level: 1,
// condition: { type: "skills", all: ["guanxing", "kongcheng"] },
// skills: ["reward_skill_a", "reward_skill_b"],
// text: "同时拥有过关选择的【观星】和【空城】时获得两个奖励技能",
// }],
// },
// // 任意选择一个指定技能即可满足,适合原版/界限突破等同类技能。
// anySkill: {
// id: "any_skill_example",
// name: "任一技能模板",
// levels: [{
// level: 1,
// condition: { type: "skills", any: ["guanxing", "reguanxing"] },
// skills: ["reward_skill"],
// text: "选择【观星】或【界观星】中的任意一个时获得奖励技能",
// }],
// },
// // all/any 可以嵌套,适合多个技能组共同组成羁绊。
// nestedCombination: {
// id: "nested_combination_example",
// name: "嵌套组合模板",
// levels: [{
// level: 1,
// condition: {
// all: [
// { any: [{ type: "skill", skill: "guanxing" }, { type: "skill", skill: "reguanxing" }] },
// { type: "skill", skill: "kongcheng" },
// { type: "keyword", keyword: "锦囊", count: 2 },
// ],
// },
// skills: ["reward_skill"],
// text: "满足任一观星、空城和两个【锦囊】关键词技能时获得奖励",
// }],
// },
// // not 用于排除指定技能;需要与其他条件组合时将其放进 all 数组。
// excludedSkill: {
// id: "excluded_skill_example",
// name: "排除条件模板",
// levels: [{
// level: 1,
// condition: {
// all: [
// { type: "keyword", keyword: "桃", count: 2 },
// { not: { type: "skill", skill: "forbidden_skill" } },
// ],
// },
// skills: ["reward_skill"],
// text: "有两个【桃】关键词技能且未选择排除技能时获得奖励",
// }],
// },
// };
export default tianshuConfig;
+384 -60
View File
@@ -1,15 +1,15 @@
import tianshuConfig from "./config.js";
import bondSkillPack from "./bondSkills.js";
import { lib, game, ui, get, ai, _status } from "../../../noname.js";
export default function initTianshu(lib, game, ui, get, ai, _status, shuYing) {
if (!lib.config.extension_术樱包_tianShuOff) return;
if (!lib.config.extension_术樱包_tianShu_Off) return;
let tianshu = new Object();
const state = {
difficulty: "normal",
stageIndex: 0,
selectedSkills: {},
selectedSkillSet: {},
difficultyBossMap: {},
originalBossMap: {},
@@ -20,6 +20,7 @@ export default function initTianshu(lib, game, ui, get, ai, _status, shuYing) {
roundFilterInstalled: false,
originalGameCheck: null,
checkHookInstalled: false,
bondHooksInstalled: false,
corpseChooseCleanup: null,
corpseChooseEvent: null,
initialBoss: null,
@@ -27,6 +28,41 @@ export default function initTianshu(lib, game, ui, get, ai, _status, shuYing) {
running: false,
pendingSpawn: false,
clearingStage: false,
bondOverviewButton: null,
bondOverviewDialog: null,
};
const rewardSkillSource = "shuYing_tianshu_reward";
const selectedSkillStorage = "shuYing_tianshu_selectedSkills";
const bondSkillSourcePrefix = "shuYing_tianshu_bond_";
const bondSyncingPlayers = new WeakSet();
const bondQueuedPlayers = new WeakSet();
// 读取玩家当前仍持有的天书过关选择技能;羁绊只能使用此列表判定。
const getTianshuRewardSkills = player => {
const skills = player?.additionalSkills?.[rewardSkillSource];
if (Array.isArray(skills)) return skills.filter(skill => typeof skill == "string" && skill);
return typeof skills == "string" && skills ? [skills] : [];
};
// 读取天书过关选择历史;技能永久失去后仍保留,用于识别是否需要刷新羁绊。
const getTianshuRewardSkillHistory = player => {
const skills = player?.storage?.[selectedSkillStorage];
return Array.isArray(skills) ? skills.filter(skill => typeof skill == "string" && skill) : [];
};
// 捕获一次过关技能选择,同时登记实际技能来源和不可见的选择历史。
const captureTianshuRewardSkill = async (player, skill) => {
if (!player || typeof skill != "string" || !skill || !lib.skill[skill]) return false;
if (!getTianshuRewardSkills(player).includes(skill)) {
await player.addAdditionalSkills(rewardSkillSource, skill, true);
}
const history = getTianshuRewardSkillHistory(player);
if (!history.includes(skill)) {
history.push(skill);
player.storage[selectedSkillStorage] = history;
game.broadcast((current, key, value) => current.storage[key] = value, player, selectedSkillStorage, history);
player.syncStorage?.(selectedSkillStorage);
}
return true;
};
// 生成玩家的稳定标识,用于跨死亡/复活状态追踪玩家方成员。
@@ -223,9 +259,20 @@ export default function initTianshu(lib, game, ui, get, ai, _status, shuYing) {
// 开局弹出难度选择,并把难度写入运行状态与全局状态。
const chooseDifficulty = async (player = game.me) => {
const keys = Object.keys(tianshuConfig.difficulties);
const controls = keys.map(key => tianshuConfig.difficulties[key].name);
const result = await player.chooseControl(controls).set("prompt", "请选择天书乱斗难度").set("ai", () => 0).forResult();
state.difficulty = keys[Math.max(0, controls.indexOf(result.control))] || "normal";
const canChoose = player == game.me || player?.isUnderControl?.();
if (canChoose && typeof shuYing.chooseSingleOptionDialog == "function") {
state.difficulty = await shuYing.chooseSingleOptionDialog({
title: "请选择天书乱斗难度",
intro: "难度将改变Boss的起始手牌、体力上限与追加技能,本局确定后不可更改。",
defaultKey: "normal",
options: keys.map(key => ({ key, tone: key, ...tianshuConfig.difficulties[key] })),
}) || "normal";
}
else {
const controls = keys.map(key => tianshuConfig.difficulties[key].name);
const result = await player.chooseControl(controls).set("prompt", "请选择天书乱斗难度").set("ai", () => 0).forResult();
state.difficulty = keys[Math.max(0, controls.indexOf(result.control))] || "normal";
}
_status[tianshuConfig.settings.difficultyStatusKey] = state.difficulty;
};
// 在指定座位创建当前难度的动态 Boss;初始主 Boss 已静默死亡,所以关卡 Boss 全部使用忠臣身份。
@@ -528,11 +575,23 @@ export default function initTianshu(lib, game, ui, get, ai, _status, shuYing) {
}
return skills;
};
// 从全武将技能中构建可供指定玩家过关选择的技能池。
// 检查技能能否进入指定玩家的过关奖励池,并统一处理去重与规则过滤。
const canAddRewardSkill = (skill, skills, ownCharacterSkills) => {
if (typeof skill != "string" || !skill || skills.includes(skill)) return false;
const banned = tianshuConfig.skillPool.banned || [];
if (banned.includes(skill) || state.selectedSkillSet[skill] || ownCharacterSkills.includes(skill)) return false;
const info = get.info(skill);
if (!info || info.charlotte || info.equipSkill || info.cardSkill || info.ruleSkill || info.temp || info.sub || info.silent) return false;
if (info.unique || info.juexingji || info.limited || info.zhuSkill || info.hiddenSkill || info.dutySkill || info.boss || info.superCharlotte) return false;
return !skill.includes("_boss") && !skill.startsWith("boss_") && !skill.startsWith("_") && !skill.includes("_sub");
};
// 每次过关同步合并 config 额外奖励池和当前已加载的全武将技能池。
const getAllSkillPool = player => {
const skills = [];
const banned = tianshuConfig.skillPool.banned || [];
const ownCharacterSkills = getCharacterCardSkills(player);
for (const skill of tianshuConfig.skillPool.additional || []) {
if (canAddRewardSkill(skill, skills, ownCharacterSkills)) skills.push(skill);
}
for (const name in lib.character) {
const character = lib.character[name];
if (!character || lib.filter.characterDisabled(name) || name.indexOf("boss_") == 0) continue;
@@ -546,12 +605,7 @@ export default function initTianshu(lib, game, ui, get, ai, _status, shuYing) {
else if (Array.isArray(info.derivation)) list.addArray(info.derivation);
}
for (const skill of list) {
if (skills.includes(skill) || banned.includes(skill) || state.selectedSkillSet[skill] || ownCharacterSkills.includes(skill)) continue;
const info = get.info(skill);
if (!info || info.charlotte || info.equipSkill || info.cardSkill || info.ruleSkill || info.temp || info.sub || info.silent) continue;
if (info.unique || info.juexingji || info.limited || info.zhuSkill || info.hiddenSkill || info.dutySkill || info.boss || info.superCharlotte) continue;
if (skill.includes("_boss") || skill.startsWith("boss_") || skill.startsWith("_") || skill.includes("_sub")) continue;
skills.push(skill);
if (canAddRewardSkill(skill, skills, ownCharacterSkills)) skills.push(skill);
}
}
return skills;
@@ -565,47 +619,303 @@ export default function initTianshu(lib, game, ui, get, ai, _status, shuYing) {
if (lib.config.extension_术樱包_giveAiSkill && main && player != main && (underControl || tianshuSingleControl)) chooser = main;
return chooser;
};
// 让指定玩家从 UI 中选择一个奖励技能。
const chooseRewardSkill = async (player, choices) => {
// 读取指定 additionalSkills 来源当前持有的技能。
const getAdditionalSourceSkills = (player, source) => {
const skills = player?.additionalSkills?.[source];
if (Array.isArray(skills)) return skills.filter(skill => typeof skill == "string" && skill);
return typeof skills == "string" && skills ? [skills] : [];
};
// 羁绊只读取玩家当前仍持有的过关选择技能,不读取武将牌或其他来源技能。
const getBondConditionSkills = player => getTianshuRewardSkills(player);
// 将过关技能按译名归一化;同译名的不同技能 ID 只贡献一次羁绊进度。
const getUniqueBondSkillNames = skills => Array.from(new Set(
skills.map(skill => lib.translate[skill]).filter(name => typeof name == "string" && name)
));
// 获取玩家主副将译名,供武将名称关键词条件检索不同前后缀版本。
const getBondCharacterNames = player => Array.from(new Set(
[player?.name, player?.name1, player?.name2]
.filter(name => typeof name == "string" && name)
.map(name => lib.translate[name] || get.translation(name))
.filter(name => typeof name == "string" && name)
));
// 通用羁绊条件解释器,支持关键词、指定技能以及 all/any/not 组合。
const matchesBondCondition = (player, condition) => {
if (!condition || typeof condition != "object") return false;
if (!condition.type && Array.isArray(condition.all)) return condition.all.every(item => matchesBondCondition(player, item));
if (!condition.type && Array.isArray(condition.any)) return condition.any.some(item => matchesBondCondition(player, item));
if (!condition.type && condition.not) return !matchesBondCondition(player, condition.not);
const owned = getBondConditionSkills(player);
if (condition.type == "keyword") {
if (!condition.keyword) return false;
const marker = `【${condition.keyword}】`;
const matchedNames = new Set();
owned.forEach(skill => {
const info = typeof get.skillInfoTranslation == "function" ? get.skillInfoTranslation(skill, player) : lib.translate[`${skill}_info`];
if (String(info || "").includes(marker)) matchedNames.add(lib.translate[skill] || skill);
});
return matchedNames.size >= Math.max(1, Number(condition.count) || 1);
}
if (condition.type == "skill") return typeof condition.skill == "string" && owned.includes(condition.skill);
if (condition.type == "skillName") {
return typeof condition.name == "string" && owned.some(skill => lib.translate[skill] == condition.name);
}
if (condition.type == "skills") {
const all = Array.isArray(condition.all) ? condition.all : [];
const any = Array.isArray(condition.any) ? condition.any : [];
if (all.length && !all.every(skill => owned.includes(skill))) return false;
if (any.length && !any.some(skill => owned.includes(skill))) return false;
return Boolean(all.length || any.length);
}
if (condition.type == "skillNames") {
const names = new Set(getUniqueBondSkillNames(owned));
const all = Array.from(new Set(Array.isArray(condition.all) ? condition.all.filter(Boolean) : []));
const any = Array.from(new Set(Array.isArray(condition.any) ? condition.any.filter(Boolean) : []));
if (all.length && !all.every(name => names.has(name))) return false;
const matchedAny = any.filter(name => names.has(name));
const need = Number(condition.count);
if (Number.isFinite(need) && need > 0) {
const matched = new Set(all.filter(name => names.has(name)).concat(matchedAny));
return matched.size >= Math.max(1, Math.floor(need));
}
if (any.length && !matchedAny.length) return false;
return Boolean(all.length || any.length);
}
if (condition.type == "characterSkillNames") {
const characters = Array.from(new Set(
(Array.isArray(condition.characters) ? condition.characters : [condition.character]).filter(Boolean)
));
if (!characters.length || !characters.some(keyword => getBondCharacterNames(player).some(name => name.includes(keyword)))) return false;
const names = new Set(getUniqueBondSkillNames(owned));
const all = Array.from(new Set(Array.isArray(condition.all) ? condition.all.filter(Boolean) : []));
const any = Array.from(new Set(Array.isArray(condition.any) ? condition.any.filter(Boolean) : []));
if (all.length && !all.every(name => names.has(name))) return false;
if (any.length && !any.some(name => names.has(name))) return false;
return Boolean(all.length || any.length);
}
return false;
};
// 兼容当前关键词配置,同时允许以后直接为等级填写任意 condition。
const getBondLevelCondition = (bond, level) => {
const source = level?.condition || bond?.condition;
if (source) {
const condition = { ...source };
if (condition.count == null && level?.count != null) condition.count = level.count;
return condition;
}
if (bond?.keyword) {
return {
type: "keyword",
keyword: bond.keyword,
count: level?.count,
};
}
return null;
};
// 只选取条件成立且等级最高的一档,低等级效果不会与高等级叠加。
const getActiveBondLevel = (player, bond) => {
let active = null;
(bond.levels || []).forEach((level, index) => {
if (!matchesBondCondition(player, getBondLevelCondition(bond, level))) return;
const rank = Number(level.level ?? level.count ?? index + 1) || index + 1;
if (!active || rank > active.rank) active = { config: level, rank };
});
return active;
};
const getBondLevelSkills = level => {
if (Array.isArray(level?.skills)) return level.skills.filter(Boolean);
return level?.skill ? [level.skill] : [];
};
// 打开当前玩家方羁绊概览;只展示名称、等级和该等级的实际效果。
const openBondOverview = () => {
state.bondOverviewDialog?.remove();
const createNode = (tag, className, text, parent) => {
const node = document.createElement(tag);
if (className) node.className = className;
if (text !== undefined && text !== null) node.textContent = text;
parent?.appendChild(node);
return node;
};
const mask = createNode("div", "shuYing-bond-overview-mask", null, document.body);
const dialog = createNode("section", "shuYing-bond-overview", null, mask);
state.bondOverviewDialog = mask;
dialog.setAttribute("role", "dialog");
dialog.setAttribute("aria-modal", "true");
createNode("h2", "shuYing-bond-overview-title", "已激活羁绊", dialog);
const content = createNode("div", "shuYing-bond-overview-content", null, dialog);
let total = 0;
for (const current of getPlayerSidePlayers({ aliveOnly: false, includeVirtual: false })) {
const levels = current.storage?.shuYing_tianshu_bondLevels || {};
const activeBonds = (tianshuConfig.bonds || []).map(bond => {
const rank = Number(levels[bond.id]) || 0;
const level = (bond.levels || []).find((item, index) => (Number(item.level ?? item.count ?? index + 1) || index + 1) == rank);
return rank && level ? { bond, level, rank } : null;
}).filter(Boolean);
if (!activeBonds.length) continue;
total += activeBonds.length;
const group = createNode("article", "shuYing-bond-overview-player", null, content);
createNode("h3", "shuYing-bond-overview-player-name", get.translation(current), group);
activeBonds.forEach(({ bond, level, rank }) => {
const row = createNode("div", "shuYing-bond-overview-item", null, group);
const head = createNode("div", "shuYing-bond-overview-item-head", null, row);
createNode("span", "shuYing-bond-overview-name", bond.name || "未命名羁绊", head);
createNode("span", "shuYing-bond-overview-level", `第 ${rank} 级`, head);
createNode("div", "shuYing-bond-overview-effect", level.text || "该等级暂未配置效果说明", row);
});
}
if (!total) createNode("div", "shuYing-bond-overview-empty", "当前玩家方尚未激活羁绊", content);
const close = createNode("button", "shuYing-bond-overview-close", "关闭", dialog);
close.type = "button";
const finish = () => {
mask.remove();
if (state.bondOverviewDialog == mask) state.bondOverviewDialog = null;
};
close.addEventListener("click", finish);
mask.addEventListener("click", event => {
if (event.target == mask) finish();
});
close.focus({ preventScroll: true });
};
// 创建独立于具体 UI 扩展的羁绊入口按钮。
const installBondOverviewButton = () => {
state.bondOverviewButton?.remove();
const button = document.createElement("button");
button.className = "shuYing-bond-overview-button";
button.type = "button";
button.textContent = "羁绊";
button.title = "查看玩家方当前激活的羁绊效果";
button.addEventListener("click", openBondOverview);
document.body.appendChild(button);
state.bondOverviewButton = button;
lib.onover.push(() => {
button.remove();
state.bondOverviewDialog?.remove();
if (state.bondOverviewButton == button) state.bondOverviewButton = null;
state.bondOverviewDialog = null;
});
};
const sameSkillList = (first, second) => first.length == second.length && first.every(skill => second.includes(skill));
// 将一个羁绊来源精确同步为目标技能,保留武将牌或其他来源持有的同名技能。
const syncAdditionalSkillSource = (player, source, desiredSkills) => {
const desired = Array.from(new Set(desiredSkills)).filter(skill => {
if (lib.skill[skill]) return true;
console.warn(`[天书羁绊] 未找到奖励技能:${skill}`);
return false;
});
const current = getAdditionalSourceSkills(player, source);
if (sameSkillList(current, desired)) return false;
const removable = new Set(player.getRemovableAdditionalSkills(source));
for (const skill of current.filter(skill => !desired.includes(skill))) {
if (removable.has(skill)) player.removeSkill(skill);
else player.$removeAdditionalSkills(source, skill);
}
for (const skill of desired.filter(skill => !current.includes(skill))) {
if (!player.hasSkill(skill, true, false, false)) player.addSkill(skill, null, true, true);
}
if (desired.length) player.additionalSkills[source] = desired.slice();
else delete player.additionalSkills[source];
game.broadcast((current, map) => current.additionalSkills = map, player, player.additionalSkills);
player.checkConflict();
_status.event?.clearStepCache?.();
return true;
};
// 依据当前真实技能所有权重算一名玩家的全部羁绊来源和隐藏等级缓存。
const refreshTianshuBonds = player => {
if (!state.running || !player || !isCapturedPlayerSide(player) || isVirtualIdol(player) || bondSyncingPlayers.has(player)) return;
bondSyncingPlayers.add(player);
try {
delete player.storage.shuYing_tianshu_keywordBondLevels;
const levelCache = {};
const activeSources = new Set();
for (const bond of tianshuConfig.bonds || []) {
if (!bond?.id) continue;
const source = `${bondSkillSourcePrefix}${bond.id}`;
const active = getActiveBondLevel(player, bond);
const skills = active ? getBondLevelSkills(active.config) : [];
activeSources.add(source);
syncAdditionalSkillSource(player, source, skills);
if (active) levelCache[bond.id] = active.rank;
}
for (const source of Object.keys(player.additionalSkills || {})) {
if (source.startsWith(bondSkillSourcePrefix) && !activeSources.has(source)) syncAdditionalSkillSource(player, source, []);
}
if (Object.keys(levelCache).length) player.storage.shuYing_tianshu_bondLevels = levelCache;
else delete player.storage.shuYing_tianshu_bondLevels;
}
finally {
bondSyncingPlayers.delete(player);
}
};
// 合并同一调用栈内的技能变化,确保 additionalSkills 映射更新完成后再重算。
const queueTianshuBondRefresh = player => {
if (!state.running || !player || bondSyncingPlayers.has(player) || bondQueuedPlayers.has(player)) return;
bondQueuedPlayers.add(player);
Promise.resolve().then(() => {
bondQueuedPlayers.delete(player);
refreshTianshuBonds(player);
});
};
// 兜底监听直接 removeSkill,但只响应曾被捕获的天书过关选择技能。
const installTianshuBondHooks = () => {
if (state.bondHooksInstalled) return;
state.bondHooksInstalled = true;
const hook = (skill, player) => {
if (getTianshuRewardSkillHistory(player).includes(skill)) queueTianshuBondRefresh(player);
};
(lib.hooks.removeSkillCheck ||= []).push(hook);
};
// 从当前奖励池随机生成候选;刷新时优先排除上一组技能。
const createRewardSkillChoices = (player, excluded = []) => {
const excludedSet = new Set(excluded);
const pool = getAllSkillPool(player).filter(skill => !excludedSet.has(skill));
const choices = [];
while (choices.length < tianshuConfig.reward.skillChoiceCount && pool.length) {
const choice = pool.randomRemove();
if (choice) choices.push(choice);
}
return choices;
};
// 让指定玩家从 UI 中选择一个奖励技能,并返回刷新请求或最终技能。
const chooseRewardSkill = async (player, choices, canRefresh) => {
const chooser = getRewardSkillChooser(player);
const controls = choices.map(skill => get.translation(skill));
const result = await chooser.chooseControl(controls)
.set("prompt", `<span style="color:#f6d365">${get.translation(player)}</span> 请选择获得一个技能`)
.set("choiceList", choices.map(skill => `<div class="skill" style="white-space:normal;word-break:break-all;line-height:22px;">【${get.translation(skill)}】</div><div class="popup text" style="width:calc(100% - 10px);display:inline-block;white-space:normal;word-break:break-all;">${lib.translate[`${skill}_info`] || "暂无技能描述"}</div>`))
.set("displayIndex", false)
.set("ai", () => controls[0])
.forResult();
const index = controls.indexOf(result.control);
return choices[index] || null;
const canChoose = chooser == game.me || chooser?.isUnderControl?.();
if (!canChoose || typeof shuYing.chooseSkillRewardDialog != "function") return choices[0] || null;
const playerName = get.translation(player);
return shuYing.chooseSkillRewardDialog({
title: `第${state.stageIndex + 1}关 ${playerName}选择技能`,
highlightText: playerName,
choices,
selectedSkills: getBondConditionSkills(player),
bonds: tianshuConfig.bonds || [],
player,
canRefresh,
});
};
// 过关后让每个存活玩家方角色依次选择奖励技能。
const chooseSkillsForStageClear = async () => {
for (const player of getPlayerSidePlayers({ aliveOnly: true, includeVirtual: false })) {
const pool = getAllSkillPool(player);
const choices = [];
while (choices.length < tianshuConfig.reward.skillChoiceCount && pool.length) {
const choice = pool.randomRemove();
if (choice) choices.push(choice);
}
let choices = createRewardSkillChoices(player);
if (!choices.length) continue;
const skill = await chooseRewardSkill(player, choices);
let refreshed = false;
let skill = await chooseRewardSkill(player, choices, true);
if (skill?.refresh) {
refreshed = true;
const oldChoices = choices;
const handCards = player.getCards("h");
const discardCount = Math.ceil(handCards.length / 2);
if (discardCount) await player.discard(handCards.randomGets(discardCount));
choices = createRewardSkillChoices(player, oldChoices);
if (!choices.length) choices = createRewardSkillChoices(player);
if (!choices.length) continue;
skill = await chooseRewardSkill(player, choices, !refreshed);
}
if (!skill || !choices.includes(skill)) continue;
await player.addSkill(skill);
const id = getPlayerKey(player);
state.selectedSkills[id] ??= [];
state.selectedSkills[id].push(skill);
if (!await captureTianshuRewardSkill(player, skill)) continue;
state.selectedSkillSet[skill] = true;
player.storage.shuYing_tianshu_selectedSkills = state.selectedSkills[id].slice();
game.log(player, "获得了技能", `#g【${get.translation(skill)}】`);
refreshTianshuBonds(player);
}
};
// 获取 1 号位上家的最终 Boss,用于让 Boss 回合结束后立刻回到 1 号位玩家。
const getFinalBossBeforeSeatOne = () => {
const firstPlayer = getPlayerSeatOne();
if (!firstPlayer) return game.boss || null;
const target = firstPlayer.previous || game.boss || null;
return target;
};
// 完成关卡清算:发奖励、选技能、进入下一关或结束游戏。
const clearStage = async () => {
if (state.clearingStage) return;
@@ -641,17 +951,8 @@ export default function initTianshu(lib, game, ui, get, ai, _status, shuYing) {
const currentTurnPlayer = getParentEvent("phase")?.player || _status.currentPhase;
state.stageIndex++;
await spawnStage(state.stageIndex);
let nextTurnTarget = null;
if (currentTurnPlayer && isCapturedPlayerSide(currentTurnPlayer)) {
if (isVirtualIdol(currentTurnPlayer)) {
nextTurnTarget = getFinalBossBeforeSeatOne();
}
else {
const next = currentTurnPlayer.next;
nextTurnTarget = next && !next.isDead() && isCapturedPlayerSide(next) && !isVirtualIdol(next) ? next : getFinalBossBeforeSeatOne();
}
}
if (nextTurnTarget) finishCurrentPhaseTo(nextTurnTarget);
// 过关后立即结束当前回合,并将新关的首个行动权交给当前角色的实际下家。
if (currentTurnPlayer?.next) finishCurrentPhaseTo(currentTurnPlayer.next);
};
// 处理 Boss 死亡事件:击杀奖励、隐藏尸体、判断是否清关。
const handleBossDie = async event => {
@@ -676,6 +977,8 @@ export default function initTianshu(lib, game, ui, get, ai, _status, shuYing) {
installHiddenBossCorpseCheckHook();
spawnVirtualIdol();
capturePlayerSide();
installTianshuBondHooks();
installBondOverviewButton();
state.pendingSpawn = true;
await chooseDifficulty(player);
prepareDifficultyBossCharacters();
@@ -716,6 +1019,8 @@ export default function initTianshu(lib, game, ui, get, ai, _status, shuYing) {
checkPlayerDefeat() {
if (this.isPlayerSideDefeated()) game.over(game.me === game.boss);
},
refreshBonds: refreshTianshuBonds,
queueBondRefresh: queueTianshuBondRefresh,
getPlayerSeatOne,
getVirtualIdolSeat,
};
@@ -753,6 +1058,7 @@ export default function initTianshu(lib, game, ui, get, ai, _status, shuYing) {
},
};
tianshu.skill = {
...bondSkillPack.skill,
shuYing_Skill_Tianshu_Go:
{
mode: ["boss"],
@@ -768,6 +1074,25 @@ export default function initTianshu(lib, game, ui, get, ai, _status, shuYing) {
game.addGlobalSkill("shuYing_Tianshu_NewSpawnStage");
game.addGlobalSkill("shuYing_Tianshu_NewBossDie");
game.addGlobalSkill("shuYing_Tianshu_NewPlayerDie");
game.addGlobalSkill("shuYing_Tianshu_BondManager");
},
},
shuYing_Tianshu_BondManager: {
mode: ["boss"],
trigger: { global: "changeSkillsAfter" },
forced: true,
silent: true,
popup: false,
charlotte: true,
firstDo: true,
filter(event) {
if (!state.running || !event.player || !isCapturedPlayerSide(event.player) || isVirtualIdol(event.player)) return false;
const removed = Array.isArray(event.removeSkill) ? event.removeSkill : [];
const history = getTianshuRewardSkillHistory(event.player);
return removed.some(skill => history.includes(skill));
},
content(event, trigger) {
refreshTianshuBonds(trigger.player);
},
},
shuYing_Tianshu_NewNoStartCards: {
@@ -831,16 +1156,14 @@ export default function initTianshu(lib, game, ui, get, ai, _status, shuYing) {
},
shuYing_Tianshu_Protection: {
mode: ["boss"],
trigger: { player: "damageBefore" },
trigger: { player: "damageBegin4" },
forced: true,
priority: 100,
priority: -1000,
charlotte: true,
popup: false,
// 仅玩家方/虚拟偶像可触发一次性保护。
filter(event, player) {
return Boolean(player && player.isIn() && !isCurrentStageBoss(player) && (isCapturedPlayerSide(player) || isVirtualIdol(player)));
return Boolean(event.num > 0 && player && player.isIn() && !isCurrentStageBoss(player) && (isCapturedPlayerSide(player) || isVirtualIdol(player)));
},
// 防止本次伤害并移除保护技能。
async content(event, trigger, player) {
trigger.cancel();
player.removeSkill("shuYing_Tianshu_Protection");
@@ -867,6 +1190,7 @@ export default function initTianshu(lib, game, ui, get, ai, _status, shuYing) {
shuYing_Skill_Tianshu_intro5_info: "共四关。击杀Boss获得奖励;过关后玩家方回复、摸牌并选择技能。",
shuYing_Tianshu_Protection: "保护",
shuYing_Tianshu_Protection_info: "锁定技。防止你受到的一次伤害,然后移除此技能。",
shuYing_Tianshu_BondManager: "天书羁绊",
};
tianshu.perfectPair = {};
tianshu.characterTitle = {};