This commit is contained in:
2026-09-28 20:58:29 +08:00
parent c3e83a700a
commit 8045e3c63f
49 changed files with 4582 additions and 441 deletions
+7 -8
View File
@@ -1015,18 +1015,17 @@ const skill = {
},
async content(event, trigger, player) {
const target = trigger.player;
const loseEvent = target.loseHp(trigger.num);
loseEvent.shuYing_Tianshu_Skill_09_Skill3 = true;
loseEvent.shuYing_source = player;
await loseEvent;
await target.loseHp(trigger.num);
if (!target.isIn()) return;
const loseHpNum = target
.getHistory("loseHp", evt => {
// 体力变化记录在全局历史中,只累计本回合该目标流失的体力。
const loseHpNum = game
.getGlobalHistory("changeHp", evt => {
return (
evt.shuYing_Tianshu_Skill_09_Skill3 &&
evt.shuYing_source == player
evt.player == target &&
evt.getParent().name == "loseHp" &&
evt.num < 0
);
})
.reduce((sum, evt) => {
+84 -2
View File
@@ -24,6 +24,7 @@ export default function initTianshu(lib, game, ui, get, ai, _status, shuYing) {
corpseChooseCleanup: null,
corpseChooseEvent: null,
initialBoss: null,
defending: false,
virtualIdol: null,
running: false,
pendingSpawn: false,
@@ -100,6 +101,27 @@ export default function initTianshu(lib, game, ui, get, ai, _status, shuYing) {
const isVirtualIdol = player => Boolean(player && (getVirtualIdolIds().includes(player.name1) || getVirtualIdolIds().includes(player.name)));
// 判断指定角色是否为当前关卡实际召唤的 Boss;玩家变身为同名动态武将时不会被误判。
const isCurrentStageBoss = player => Boolean(player?.storage?.shuYing_tianshu_stageBoss && player.storage.shuYing_tianshu_stageIndex == state.stageIndex);
// 应战身份独立于 game.me/game.boss,避免动态换控后误判阵营和胜负。
const swapDefendingBoss = target => {
if (!state.defending || !target || !game.players.includes(target) || !isCurrentStageBoss(target)) return;
game.swapControl(target);
game.onSwapControl?.();
// 应战开局沿用主 Boss UI,会隐藏 fakeme;换成动态 Boss 后必须恢复代理头像。
// 直接复用已经显示正确的角色头像,不按克隆 ID 拼接图片路径。
if (ui.fakeme) {
game.singleHandcard = true;
ui.arena?.classList.add("single-handcard");
ui.window?.classList.add("single-handcard");
ui.fakeme.style.display = "";
ui.fakeme.current = target.name;
ui.fakeme.style.backgroundImage = target.node.avatar.style.backgroundImage;
ui.updatehl?.();
}
};
const getNextDefendingBoss = dead => {
const bosses = game.players.filter(isCurrentStageBoss).sort((a, b) => Number(a.dataset.position) - Number(b.dataset.position));
return bosses.find(boss => Number(boss.dataset.position) > Number(dead?.dataset.position ?? -1)) || bosses[0];
};
// 判断指定角色是否属于开局捕获的玩家方。
const isCapturedPlayerSide = player => Boolean(getPlayerKey(player) && state.playerSideIds[getPlayerKey(player)]);
// 获取玩家方角色列表,可按是否存活、是否包含虚拟偶像过滤。
@@ -188,6 +210,20 @@ export default function initTianshu(lib, game, ui, get, ai, _status, shuYing) {
// 结束当前 phase 事件,把后续行动权交给指定角色;保留旧版重置轮数与阶段的做法。
const finishCurrentPhaseTo = target => {
if (!target) return;
// 清剿把弃牌事件挂在上层队列;finish 不会清空队列,不能让旧关结算弃掉新关奖励。
let pendingParent = _status.event;
const pendingVisited = new Set();
while (pendingParent && !pendingVisited.has(pendingParent)) {
pendingVisited.add(pendingParent);
for (const queue of [pendingParent.next, pendingParent.after]) {
if (!Array.isArray(queue)) continue;
for (let i = queue.length - 1; i >= 0; i--) {
if (queue[i]?.name == "qingjiao_discard") queue.splice(i, 1);
}
}
if (pendingParent.name == "phaseLoop") break;
pendingParent = pendingParent.getParent ? pendingParent.getParent() : pendingParent.parent;
}
const phase = _status.event?.getParent ? _status.event.getParent("phase") : getParentEvent("phase");
if (phase) {
if (typeof game.resetSkills == "function") game.resetSkills();
@@ -314,6 +350,16 @@ export default function initTianshu(lib, game, ui, get, ai, _status, shuYing) {
boss.storage.shuYing_tianshu_stageBoss = true;
boss.storage.shuYing_tianshu_stageIndex = state.stageIndex;
boss.storage.shuYing_tianshu_originalBossName = originalName;
if (state.defending) {
const originalIsUnderControl = boss.isUnderControl;
boss.isUnderControl = function (self, me = game.me) {
if (state.running && state.defending && isCurrentStageBoss(this) && isCurrentStageBoss(me)
&& !_status.connectMode && !game.notMe && !this.isMad() && get.config("single_control")) {
return this == me ? Boolean(self) : true;
}
return originalIsUnderControl.call(this, self, me);
};
}
game.addVideo("setIdentity", boss, boss.identity);
if (game.playerMap) game.playerMap[boss.dataset.position] = boss;
game.arrangePlayers();
@@ -591,6 +637,7 @@ export default function initTianshu(lib, game, ui, get, ai, _status, shuYing) {
player._start_cards.addArray(cards);
}
}
if (state.defending) swapDefendingBoss(getNextDefendingBoss());
};
// 读取玩家当前武将牌上自带的技能,避免过关奖励重复刷出本体技能。
const getCharacterCardSkills = player => {
@@ -976,6 +1023,8 @@ export default function initTianshu(lib, game, ui, get, ai, _status, shuYing) {
};
// 让指定玩家从 UI 中选择一个奖励技能,并返回刷新请求或最终技能。
const chooseRewardSkill = async (player, choices, canRefresh) => {
// 应战者不能代替敌方选奖励;沿用无人类操作时的自动选择路径。
if (state.defending) return choices[0] || null;
const chooser = getRewardSkillChooser(player);
const canChoose = chooser == game.me || chooser?.isUnderControl?.();
if (!canChoose || typeof shuYing.chooseSkillRewardDialog != "function") return choices[0] || null;
@@ -1019,7 +1068,7 @@ export default function initTianshu(lib, game, ui, get, ai, _status, shuYing) {
if (state.clearingStage) return;
state.clearingStage = true;
if (state.stageIndex >= tianshuConfig.stages.length - 1) {
game.over(game.me !== game.boss);
game.over(!state.defending);
return;
}
await reviveVirtualIdolIfNeeded();
@@ -1059,6 +1108,7 @@ export default function initTianshu(lib, game, ui, get, ai, _status, shuYing) {
if (!isCurrentStageBoss(deadBoss)) return;
if (deadBoss.storage.shuYing_tianshu_bossDieHandled) return;
deadBoss.storage.shuYing_tianshu_bossDieHandled = true;
if (state.defending && game.me == deadBoss) swapDefendingBoss(getNextDefendingBoss(deadBoss));
const stageCleared = !game.players.some(current => isCurrentStageBoss(current));
const finalCleared = stageCleared && state.stageIndex >= tianshuConfig.stages.length - 1;
if (!finalCleared && event.source && event.source.isIn()) {
@@ -1073,6 +1123,22 @@ export default function initTianshu(lib, game, ui, get, ai, _status, shuYing) {
if (state.running) return;
state.running = true;
state.initialBoss = game.boss || null;
state.defending = Boolean(state.initialBoss && game.me == state.initialBoss);
if (state.defending) {
// 应战原生布局是 0/2/4/6;先恢复挑战者 1/2/3,再安排 Boss 和偶像,避免重叠。
game.players.filter(current => current != state.initialBoss && current.side === false)
.sort((a, b) => Number(a.dataset.position) - Number(b.dataset.position))
.forEach((current, index) => { current.dataset.position = index + 1; });
game.arrangePlayers();
const originalModeSwapPlayer = game.modeSwapPlayer;
game.modeSwapPlayer = function (target) {
if (state.running && state.defending && isCurrentStageBoss(target)) {
swapDefendingBoss(target);
return;
}
return originalModeSwapPlayer.apply(this, arguments);
};
}
installHiddenBossCorpseCheckHook();
spawnVirtualIdol();
capturePlayerSide();
@@ -1116,7 +1182,7 @@ export default function initTianshu(lib, game, ui, get, ai, _status, shuYing) {
canReceiveOpeningProtection: player => Boolean(player && player.isIn() && !isCurrentStageBoss(player) && (isCapturedPlayerSide(player) || isVirtualIdol(player))),
// 玩家死亡后检查是否达到失败条件。
checkPlayerDefeat() {
if (this.isPlayerSideDefeated()) game.over(game.me === game.boss);
if (this.isPlayerSideDefeated()) game.over(state.defending);
},
refreshBonds: refreshTianshuBonds,
queueBondRefresh: queueTianshuBondRefresh,
@@ -1173,9 +1239,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_DefendingControl");
game.addGlobalSkill("shuYing_Tianshu_BondManager");
},
},
shuYing_Tianshu_DefendingControl: {
trigger: { global: "phaseBefore" },
forced: true,
silent: true,
charlotte: true,
forceDie: true,
firstDo: true,
filter(event) {
return state.running && state.defending && !_status.connectMode
&& get.config("single_control") && isCurrentStageBoss(event.player);
},
async content(event, trigger) {
swapDefendingBoss(trigger.player);
},
},
shuYing_Tianshu_BondManager: {
mode: ["boss"],
trigger: { global: "changeSkillsAfter" },