fixed # 修复部分天书乱斗的问题

This commit is contained in:
2026-10-12 15:10:31 +08:00
parent 0205bbebc6
commit 279f2d9b0a
19 changed files with 888 additions and 103 deletions
+9 -19
View File
@@ -1,4 +1,6 @@
// 天书羁绊只通过技能表达具体效果;羁绊管理器负责决定当前应持有哪一级技能。
import { lib, game, ui, get, ai, _status } from "../../../noname.js";
const skill = {
shuYing_Tianshu_BondSha1: {
charlotte: true,
@@ -109,8 +111,8 @@ const skill = {
locked: true,
popup: false,
group: [
"shuYing_Tianshu_Skill_02_Count2_draw",
"shuYing_Tianshu_Skill_02_Count2_discard",
"shuYing_Tianshu_Skill_02_Skill2_draw",
"shuYing_Tianshu_Skill_02_Skill2_discard",
],
subSkill: {
draw: {
@@ -181,9 +183,9 @@ const skill = {
locked: true,
popup: false,
group: [
"shuYing_Tianshu_Skill_02_Count3_draw",
"shuYing_Tianshu_Skill_02_Count3_discard",
"shuYing_Tianshu_Skill_02_Count3_gain",
"shuYing_Tianshu_Skill_02_Skill3_draw",
"shuYing_Tianshu_Skill_02_Skill3_discard",
"shuYing_Tianshu_Skill_02_Skill3_gain",
],
subSkill: {
draw: {
@@ -334,13 +336,7 @@ const skill = {
async content(event, trigger, player) {
const target = trigger.target;
const cards = target
.getCards("he", card => {
return lib.filter.cardDiscardable(
card,
target,
event.name
);
})
.getDiscardableCards(player, "he")
.randomGets(1);
if (!cards.length) return;
@@ -389,13 +385,7 @@ const skill = {
}
const cards = target
.getCards("he", card => {
return lib.filter.cardDiscardable(
card,
target,
event.name
);
})
.getDiscardableCards(player, "he")
.randomGets(2);
if (!cards.length) return;
+59 -1
View File
@@ -118,7 +118,11 @@ const bossDifficulty = {
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")),
zhangjiao_boss_shuying: createBossConfig({
normal: createDifficultyConfig(6, 4),
hard: createDifficultyConfig(8, 12),
nightmare: createDifficultyConfig(10, 20),
}, addSkillFromHard("yinlei_shuying")),
...createBossGroup(["shaohao_boss_shuying", "xuannv_boss_shuying", "hanba_boss_shuying"], bossDifficultyTemplates.tianshuluandou),
shuishengonggong_boss_shuying: createBossConfig(bossDifficultyTemplates.tianshuluandou, {
@@ -210,6 +214,60 @@ const tianshuConfig = {
banned: ["huoxin", "jueqing", "qinqing", "beige", "huashen", "drlt_zhiti", "olzhiti", "xinfu_pdgyingshi", "rebeige"],
},
bonds: [
{
id: "taoyuan",
name: "桃园",
condition: { type: "skillRecover" },
levels: [
{
level: 1,
count: 1,
skills: ["shuYing_Tianshu_BondTaoyuan1"],
text: "你通过技能回复体力时,回复值+1",
},
{
level: 2,
count: 2,
skills: ["shuYing_Tianshu_BondTaoyuan2"],
text: "你通过技能回复体力时,回复值+1,"
+ "并可以令一名其他角色回复或失去1点体力",
},
{
level: 3,
count: 3,
skills: ["shuYing_Tianshu_BondTaoyuan3"],
text: "你通过技能回复体力时,回复值+1,"
+ "并可以令一名其他角色回复或失去1点体力;"
+ "按原体力上限结算回复后,将溢出回复转为受回复角色的体力上限",
},
],
},
{
id: "directDamage",
name: "直伤",
condition: { type: "directDamage" },
levels: [
{
level: 1,
count: 1,
skills: ["shuYing_Tianshu_BondDirectDamage1"],
text: "你通过技能直接造成的伤害+1",
},
{
level: 2,
count: 2,
skills: ["shuYing_Tianshu_BondDirectDamage2"],
text: "你通过技能直接造成的伤害+1,并摸等同于最终伤害值的牌",
},
{
level: 3,
count: 3,
skills: ["shuYing_Tianshu_BondDirectDamage3"],
text: "你通过技能直接造成的伤害+1,并摸等同于最终伤害值的牌;"
+ "你可以令此次伤害改为无来源,仍结算增伤和摸牌",
},
],
},
{
id: "shafa",
name: "杀伐",
+28
View File
@@ -0,0 +1,28 @@
export const directDamageStorage = "shuYing_tianshu_directDamageSkills";
export function getDirectDamageOrigin(event, lib, get) {
if (!event || event.num <= 0 || event.unreal) return null;
if (typeof event.notLink == "function" && !event.notLink()) return null;
let current = event.getParent?.(1, true);
const visited = new Set();
while (current && !visited.has(current)) {
visited.add(current);
if (["useCard", "respond", "damage", "_lianhuan", "_lianhuan2"]
.includes(current.name) || current.name?.endsWith("_cost")) {
return null;
}
const skill = lib.skill[current.name] ? current.name : current.skill;
if (skill && lib.skill[skill]) {
return { skill: get.sourceSkillFor(skill), player: current.player };
}
current = current.getParent?.(1, true);
}
return null;
}
export function getDirectDamageSkillCount(player, skills, lib) {
const recorded = player?.storage?.[directDamageStorage];
if (!Array.isArray(recorded)) return 0;
return new Set(skills.filter(skill => recorded.includes(skill))
.map(skill => lib.translate[skill] || skill)).size;
}
+235 -5
View File
@@ -1,5 +1,9 @@
import tianshuConfig from "./config.js";
import bondSkillPack from "./bondSkills.js";
import { directDamageStorage, getDirectDamageOrigin,
getDirectDamageSkillCount } from "./directDamage.js";
import { skillRecoverStorage, getSkillRecoverOrigin, getSkillRecoverCount,
wrapSkillRecoverEvent } from "./skillRecover.js";
import { lib, game, ui, get, ai, _status } from "../../../noname.js";
export default function initTianshu(lib, game, ui, get, ai, _status, shuYing) {
@@ -22,6 +26,7 @@ export default function initTianshu(lib, game, ui, get, ai, _status, shuYing) {
originalGameCheck: null,
checkHookInstalled: false,
bondHooksInstalled: false,
recoverHookInstalled: false,
corpseChooseCleanup: null,
corpseChooseEvent: null,
initialBoss: null,
@@ -274,10 +279,15 @@ export default function initTianshu(lib, game, ui, get, ai, _status, shuYing) {
}
};
// 新 Boss 入场并重排座位后,若当前行动者属于玩家方(含虚拟偶像)且实际下家为 Boss 或虚拟偶像,则启用旧轮次。
const shouldUseLegacyBossTurn = current => {
const shouldUseLegacyBossTurn = (current, stageDeadPlayers) => {
if (!lib.config[tianshuConfig.settings.legacyRoundConfigKey]) return false;
if (!current || !current.isIn?.() || !isCapturedPlayerSide(current)) return false;
const next = current.next || current.getNext?.();
let next = current.next || current.getNext?.();
const visited = new Set([current]);
while (next && stageDeadPlayers?.has(next) && !visited.has(next)) {
visited.add(next);
next = next.next || next.getNext?.();
}
return Boolean(next && (isCurrentStageBoss(next) || isVirtualIdol(next)));
};
// 从玩家方1号位沿上家方向查找当前关 Boss,作为旧轮次的行动者。
@@ -293,9 +303,9 @@ export default function initTianshu(lib, game, ui, get, ai, _status, shuYing) {
}
return null;
};
const getNextStageTurnPlayer = current => {
const getNextStageTurnPlayer = (current, stageDeadPlayers) => {
const next = current?.next || null;
if (!shouldUseLegacyBossTurn(current)) return next;
if (!shouldUseLegacyBossTurn(current, stageDeadPlayers)) return next;
return getLegacyBossTurnPlayer() || next;
};
// 开局捕获玩家方成员,避免后续 game.boss 变化导致阵营误判。
@@ -738,6 +748,14 @@ export default function initTianshu(lib, game, ui, get, ai, _status, shuYing) {
if (!condition.type && condition.not) return !matchesBondCondition(player, condition.not);
const owned = getBondConditionSkills(player);
if (condition.type == "directDamage") {
return getDirectDamageSkillCount(player, owned, lib)
>= Math.max(1, Number(condition.count) || 1);
}
if (condition.type == "skillRecover") {
return getSkillRecoverCount(player, owned, lib)
>= Math.max(1, Number(condition.count) || 1);
}
if (condition.type == "keyword") {
if (!condition.keyword) return false;
const marker = `【${condition.keyword}】`;
@@ -812,6 +830,12 @@ export default function initTianshu(lib, game, ui, get, ai, _status, shuYing) {
if (!condition.type && condition.not) return false;
const skillName = lib.translate[skill] || "";
if (condition.type == "directDamage") {
return getDirectDamageSkillCount(player, [skill], lib) > 0;
}
if (condition.type == "skillRecover") {
return getSkillRecoverCount(player, [skill], lib) > 0;
}
if (condition.type == "keyword") {
if (!condition.keyword) return false;
const info = typeof get.skillInfoTranslation == "function" ? get.skillInfoTranslation(skill, player) : lib.translate[`${skill}_info`];
@@ -1088,6 +1112,9 @@ export default function initTianshu(lib, game, ui, get, ai, _status, shuYing) {
game.over(!state.defending);
return;
}
const stageDeadPlayers = new Set(getPlayerSidePlayers({
aliveOnly: false, includeVirtual: true,
}).filter(player => player.isDead()));
await reviveVirtualIdolIfNeeded();
if (lib.config[tianshuConfig.settings.revivePlayersConfigKey]) {
const deadPlayers = getPlayerSidePlayers({ aliveOnly: false, includeVirtual: false }).filter(player => player.isDead());
@@ -1116,7 +1143,8 @@ export default function initTianshu(lib, game, ui, get, ai, _status, shuYing) {
state.stageIndex++;
await spawnStage(state.stageIndex);
// 必须在新 Boss 召唤并完成座位重排后,再按新座位链决定控制权归属。
const nextTurnPlayer = getNextStageTurnPlayer(currentTurnPlayer);
const nextTurnPlayer = getNextStageTurnPlayer(
currentTurnPlayer, stageDeadPlayers);
if (nextTurnPlayer) finishCurrentPhaseTo(nextTurnPlayer);
};
// 处理 Boss 死亡事件:击杀奖励、隐藏尸体、判断是否清关。
@@ -1135,6 +1163,79 @@ export default function initTianshu(lib, game, ui, get, ai, _status, shuYing) {
if (!stageCleared) return;
await clearStage();
};
const settleSkillRecover = async (event, record, overflow) => {
const { player, skill, level } = record;
const rewardSkill = getTianshuRewardSkills(player)
.find(current => get.sourceSkillFor(current) == skill);
if (rewardSkill) {
const recorded = player.storage[skillRecoverStorage] || [];
if (!recorded.includes(rewardSkill)) {
recorded.push(rewardSkill);
player.storage[skillRecoverStorage] = recorded;
game.broadcast((current, key, value) => {
current.storage[key] = value;
}, player, skillRecoverStorage, recorded);
player.syncStorage?.(skillRecoverStorage);
refreshTianshuBonds(player);
game.log(player, "登记桃园技能",
`【${get.translation(rewardSkill)}】`);
}
}
if (overflow > 0) game.log(player, "将", overflow,
"点溢出回复转为", event.player, "的体力上限");
if (level < 2 || !player.isIn()) return;
const result = await player.chooseTarget(
"桃园:可以令一名其他角色回复或失去1点体力",
(card, current, target) => target != current && target.isIn())
.set("ai", target => Math.abs(get.attitude(player, target)))
.forResult();
if (!result.bool || !result.targets?.[0]?.isIn()) return;
const target = result.targets[0];
const choice = await player.chooseControl("回复体力", "失去体力")
.set("prompt", `桃园:令${get.translation(target)}执行哪项?`)
.set("ai", () => get.attitude(player, target) > 0 ? 0 : 1)
.forResult();
if (!target.isIn()) return;
player.line(target);
if (choice.control == "回复体力") {
const next = target.recover(1, player);
next.shuYing_tianshu_taoyuanSecondary = true;
await next;
} else if (choice.control == "失去体力") {
await target.loseHp(1);
}
};
const installSkillRecoverHook = () => {
if (state.recoverHookInstalled) return;
const prototype = lib.element.Player?.prototype || lib.element.player;
const original = prototype.recover.shuYing_taoyuanBase
|| prototype.recover;
const recover = function () {
const next = original.apply(this, arguments);
if (!state.running || _status.over || !this.isIn()) return next;
const origin = getSkillRecoverOrigin(next, lib, get);
const player = origin?.player;
if (!player?.isIn() || player != next.source
|| !isCapturedPlayerSide(player)
|| (isVirtualIdol(player)
&& !canVirtualIdolReceiveRewards())) return next;
const level = player.storage.shuYing_tianshu_bondLevels
?.taoyuan || 0;
const originalContent = next.content.original;
const nativeContent = typeof originalContent == "string"
? lib.element.content[originalContent]
|| lib.element.contents?.[originalContent]
: originalContent || next.content;
wrapSkillRecoverEvent(next, {
player, skill: origin.skill, level,
}, settleSkillRecover, nativeContent,
() => player.storage.shuYing_tianshu_bondLevels?.taoyuan || 0);
return next;
};
recover.shuYing_taoyuanBase = original;
prototype.recover = recover;
state.recoverHookInstalled = true;
};
// gameStart 时启动天书乱斗流程,完成难度选择并等待 gameDrawAfter 召唤第一关。
const startRun = async (player = game.me) => {
if (state.running) return;
@@ -1159,6 +1260,7 @@ export default function initTianshu(lib, game, ui, get, ai, _status, shuYing) {
installHiddenBossCorpseCheckHook();
spawnVirtualIdol();
capturePlayerSide();
installSkillRecoverHook();
installTianshuBondHooks();
installBondOverviewButton();
state.pendingSpawn = true;
@@ -1259,6 +1361,9 @@ export default function initTianshu(lib, game, ui, get, ai, _status, shuYing) {
game.addGlobalSkill("shuYing_Tianshu_DefendingControl");
game.addGlobalSkill("shuYing_Tianshu_StageRoundHistory");
game.addGlobalSkill("shuYing_Tianshu_BondManager");
game.addGlobalSkill("shuYing_Tianshu_DirectDamageCapture");
game.addGlobalSkill("shuYing_Tianshu_DirectDamageSettle");
game.addGlobalSkill("shuYing_Tianshu_SkillRecoverCapture");
},
},
shuYing_Tianshu_DefendingControl: {
@@ -1297,6 +1402,113 @@ export default function initTianshu(lib, game, ui, get, ai, _status, shuYing) {
game.getGlobalHistory().isRound = true;
},
},
shuYing_Tianshu_BondDirectDamage1: { charlotte: true, popup: false },
shuYing_Tianshu_BondDirectDamage2: { charlotte: true, popup: false },
shuYing_Tianshu_BondDirectDamage3: { charlotte: true, popup: false },
shuYing_Tianshu_DirectDamageCapture: {
mode: ["boss"],
trigger: { global: "damageBegin1" },
forced: true,
silent: true,
charlotte: true,
firstDo: true,
priority: 1000,
filter(event) {
const player = event.source;
if (!state.running || event.shuYing_tianshu_directDamage
|| !player?.isIn()
|| !isCapturedPlayerSide(player)
|| (isVirtualIdol(player)
&& !canVirtualIdolReceiveRewards())) return false;
const origin = getDirectDamageOrigin(event, lib, get);
return Boolean(origin && origin.player == player);
},
async content(event, trigger) {
if (trigger.shuYing_tianshu_directDamage) return;
const origin = getDirectDamageOrigin(trigger, lib, get);
if (!origin) return;
const player = trigger.source;
const level = player.storage.shuYing_tianshu_bondLevels
?.directDamage || 0;
trigger.shuYing_tianshu_directDamage = {
player, skill: origin.skill, level,
};
if (!level) return;
trigger.num++;
if (level < 3) return;
const result = await player.chooseBool(
"直伤:是否令此次伤害改为无来源?",
"仍会增加1点伤害,并按最终伤害值摸牌。")
.set("ai", () => false).forResult();
if (result.bool) {
delete trigger.source;
game.log(player, "令此次技能伤害改为无来源");
}
},
},
shuYing_Tianshu_DirectDamageSettle: {
mode: ["boss"],
trigger: { global: "damage" },
forced: true,
silent: true,
charlotte: true,
firstDo: true,
priority: 1000,
filter(event) {
return Boolean(state.running && event.num > 0
&& !event.unreal && event.shuYing_tianshu_directDamage
&& !event.shuYing_tianshu_directDamage.settled);
},
async content(event, trigger) {
const record = trigger.shuYing_tianshu_directDamage;
record.settled = true;
const { player, skill, level } = record;
const num = trigger.num;
const rewardSkill = getTianshuRewardSkills(player)
.find(current => get.sourceSkillFor(current) == skill);
if (rewardSkill) {
const recorded = player.storage[directDamageStorage] || [];
if (!recorded.includes(rewardSkill)) {
recorded.push(rewardSkill);
player.storage[directDamageStorage] = recorded;
game.broadcast((current, key, value) => {
current.storage[key] = value;
}, player, directDamageStorage, recorded);
player.syncStorage?.(directDamageStorage);
refreshTianshuBonds(player);
game.log(player, "登记直伤技能",
`【${get.translation(rewardSkill)}】`);
}
}
if (level >= 2 && player.isIn()) await player.draw(num);
},
},
shuYing_Tianshu_BondTaoyuan1: { charlotte: true, popup: false },
shuYing_Tianshu_BondTaoyuan2: { charlotte: true, popup: false },
shuYing_Tianshu_BondTaoyuan3: { charlotte: true, popup: false },
shuYing_Tianshu_SkillRecoverCapture: {
mode: ["boss"],
trigger: { global: "recoverBegin" },
forced: true,
silent: true,
charlotte: true,
firstDo: true,
priority: 1000,
filter(event) {
const record = event.shuYing_tianshu_skillRecover;
return Boolean(state.running && event.num > 0 && record
&& !record.boosted
&& !event.shuYing_tianshu_taoyuanSecondary);
},
async content(event, trigger) {
const record = trigger.shuYing_tianshu_skillRecover;
if (record.boosted || trigger.shuYing_tianshu_taoyuanSecondary) {
return;
}
record.boosted = true;
if (record.level >= 1) trigger.num++;
},
},
shuYing_Tianshu_BondManager: {
mode: ["boss"],
trigger: { global: "changeSkillsAfter" },
@@ -1397,6 +1609,24 @@ export default function initTianshu(lib, game, ui, get, ai, _status, shuYing) {
shuYing_Skill_Tianshu_intro5: { nobracket: true },
};
tianshu.translate = {
shuYing_Tianshu_BondTaoyuan1: "桃园·壹",
shuYing_Tianshu_BondTaoyuan1_info: "你通过技能回复体力时,回复值+1。",
shuYing_Tianshu_BondTaoyuan2: "桃园·贰",
shuYing_Tianshu_BondTaoyuan2_info:
"你通过技能回复体力时,回复值+1,并可以令一名其他角色回复或失去1点体力。",
shuYing_Tianshu_BondTaoyuan3: "桃园·叁",
shuYing_Tianshu_BondTaoyuan3_info:
"你通过技能回复体力时,回复值+1,并可以令一名其他角色回复或失去1点体力;"
+ "按原体力上限结算回复后,将溢出回复转为受回复角色的体力上限。",
shuYing_Tianshu_BondDirectDamage1: "直伤·壹",
shuYing_Tianshu_BondDirectDamage1_info: "你通过技能直接造成的伤害+1。",
shuYing_Tianshu_BondDirectDamage2: "直伤·贰",
shuYing_Tianshu_BondDirectDamage2_info:
"你通过技能直接造成的伤害+1,并摸等同于最终伤害值的牌。",
shuYing_Tianshu_BondDirectDamage3: "直伤·叁",
shuYing_Tianshu_BondDirectDamage3_info:
"你通过技能直接造成的伤害+1,并摸等同于最终伤害值的牌;"
+ "你可以令此次伤害改为无来源,仍结算增伤和摸牌。",
tianshu_boss_shuYing: "天书乱斗",
shuYing_Skill_Tianshu_Go: "天书乱斗",
shuYing_Skill_Tianshu_intro1: "&nbsp;第一关",
+61
View File
@@ -0,0 +1,61 @@
export const skillRecoverStorage = "shuYing_tianshu_skillRecoverSkills";
export function getSkillRecoverOrigin(event, lib, get) {
if (!event || event.num <= 0 || event.shuYing_tianshu_taoyuanSecondary) {
return null;
}
let current = event.getParent?.(1, true);
const visited = new Set();
while (current && !visited.has(current)) {
visited.add(current);
if (["useCard", "respond", "recover", "damage"]
.includes(current.name) || current.name?.endsWith("_cost")) {
return null;
}
const skill = lib.skill[current.name] ? current.name : current.skill;
if (skill && lib.skill[skill]) {
return { skill: get.sourceSkillFor(skill), player: current.player };
}
current = current.getParent?.(1, true);
}
return null;
}
export function getSkillRecoverCount(player, skills, lib) {
const recorded = player?.storage?.[skillRecoverStorage];
if (!Array.isArray(recorded)) return 0;
return new Set(skills.filter(skill => recorded.includes(skill))
.map(skill => lib.translate[skill] || skill)).size;
}
export function wrapSkillRecoverEvent(event, record, onSettle, nativeContent,
getLevel) {
const originalFilter = event.filterStop;
const originalContent = nativeContent
|| (typeof event.content.original == "function"
? event.content.original : event.content);
event.shuYing_tianshu_skillRecover = record;
event.filterStop = function () {
if (!record.boosted && getLevel) record.level = getLevel();
if (record.level >= 3 && this.num > 0 && this.player.isHealthy()
&& !this.shuYing_tianshu_taoyuanSecondary) return false;
return originalFilter?.call(this) || false;
};
event.setContent(async current => {
if (record.settled) return;
if (current.shuYing_tianshu_taoyuanSecondary) {
await originalContent(current, current._trigger, current.player);
return;
}
const missing = Math.max(0, current.player.maxHp - current.player.hp);
const overflow = record.level >= 3
? Math.max(0, current.num - missing) : 0;
if (record.level >= 3) current.num = Math.min(current.num, missing);
await originalContent(current, current._trigger, current.player);
if (overflow > 0) await current.player.gainMaxHp(overflow);
record.settled = true;
if (current.num > 0 || overflow > 0) {
await onSettle(current, record, overflow);
}
});
}