Files
noname-shuying/mengsan/mode.js
T
Diuse 9e1732d2f8 fixed # 补全部分卡牌
fixed # 修复部分已知BUG
2026-10-04 19:32:38 +08:00

1930 lines
111 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { createScenarioCharacters, scenarioTranslations } from "./content/scenario-characters.js";
import { createMengsanCards } from "./cards/mode-cards.js";
import { createStrikeCounter } from "./cards/strike-counter.js";
import { resolveRetiredCardReward } from "./cards/retired-cards.js";
import { createBlockDrawer } from "./cards/block-draw.js";
import { createHandUpgrader } from "./cards/hand-upgrade.js";
import { createLifeLossBlocker } from "./cards/life-loss-block.js";
import { createRampage } from "./cards/rampage.js";
import { createSpite } from "./cards/spite.js";
import { createWhirlwind } from "./cards/whirlwind.js";
import { createHavocPlayer } from "./cards/havoc-play.js";
import { createHandExhauster } from "./cards/hand-exhaust.js";
import { createCardPackRuntime } from "./cards/packs/index.js";
import { ironcladBlocksDraw, ironcladPreservesBlock, afterIroncladDraw, beforeIroncladBlock } from "./cards/ironclad-hooks.js";
import { afterSharedDraw, beforeSharedBlock, sharedStrengthPenalty } from "./cards/shared-hooks.js";
import { hasCardAffix } from "./cards/intrinsic-affixes.js";
import { createBattleEnergy } from "./cards/battle-energy.js";
import { createRandomHandExhauster } from "./cards/random-hand-exhaust.js";
import { createBattleCardCopier } from "./cards/battle-card-copy.js";
import { createBattleCardReclaimer } from "./cards/battle-card-reclaim.js";
import { createTemporaryStrength, temporaryStrengthAmount, temporaryStrengthExpires, expireTemporaryStrength, clearTemporaryStrength, forgetTemporaryStrength } from "./cards/temporary-strength.js";
import { createRelicBattle, createRelicSkills } from "./relics/battle.js";
import { grantRelic, getRelic, initializeRelicMaxHp }
from "./relics/definitions.js";
import { mountRelics } from "./ui/relics.js";
import { createOpeningEffects, createRelicCombatSkills }
from "./relics/combat.js";
import { applyRoomRelics, restRecovery } from "./relics/rooms.js";
import { buildBattlePlan, consumeSupports, createBattleDirector, grantSupport } from "./battle/battle-director.js";
import { installPersonalPiles } from "./cards/personal-piles.js";
import { createMonster } from "./monsters/monster.js";
import { prepareBondBattle, bondBattleStatus } from "./bonds/state.js";
import { initializeBondUnit, recordBondDeath } from "./bonds/battle.js";
import { bondDefinitions } from "./bonds/definitions.js";
import { createBondIntentActions } from "./bonds/intents.js";
import {
createMonsterIntentActions,
getIntentHostiles,
isFlyconid,
isRaider,
} from "./monsters/actions.js";
import { constrictTotal, resolveConstrict, clearConstrictSource } from "./monsters/strangler-constrict.js";
import { isShrunk, clearShrinkSource, shrinkAttackDamage } from "./monsters/shrinker-status.js";
import { advanceTangled } from "./monsters/vine-tangled.js";
import { isMawler, recordMawlerAction } from "./monsters/mawler-intent.js";
import { isVine, recordVineAction } from "./monsters/vine-intent.js";
import { isCubex, recordCubexAction, initializeCubex } from "./monsters/cubex-intent.js";
import { isEffigy, recordEffigyAction, initializeEffigy } from "./monsters/effigy-intent.js";
import { isVantom, recordVantomAction, initializeVantom } from "./monsters/vantom-intent.js";
import { isBeast, initializeBeast, recoverBeastStun, canBreakPlow, breakPlow, ringingLimited, countRingingCard, clearRinging } from "./monsters/beast-intent.js";
import { KIN_FOLLOWER_CHARACTER, isKinActor, isKinFollower, canActKin, recordKinAction, initializeKin, rollKinFollowerHp, clearKinOwnerIntents } from "./monsters/kin-intent.js";
import { slowPercent, canCountSlowCard, recordSlowCard, resetSlow, slowApplies, slowAttackDamage } from "./monsters/effigy-slow.js";
import { BYRDONIS_CHARACTER, isByrdonis, rollByrdonisHp, recordByrdonisAction, initializeByrdonis, resolveTerritorial } from "./monsters/byrdonis-intent.js";
import { NIBBIT_CHARACTER, isNibbit, rollNibbitHp, recordNibbitAction, bindNibbitOpenings } from "./monsters/nibbit-intent.js";
import { SHRINKER_CHARACTER, isShrinker, rollShrinkerHp, recordShrinkerAction } from "./monsters/shrinker-intent.js";
import { TWIGMEDIUM_CHARACTER, isTwigmedium, rollTwigmediumHp, recordTwigmediumAction } from "./monsters/twigmedium-intent.js";
import { TWIGSLIME_CHARACTER, isTwigslime, rollTwigslimeHp, recordTwigslimeAction } from "./monsters/twigslime-intent.js";
import { LEAFMEDIUM_CHARACTER, isLeafslimeMedium, rollLeafslimeMediumHp, recordLeafslimeMediumAction } from "./monsters/leafmedium-intent.js";
import { LEAFSLIME_CHARACTER, isLeafslime, rollLeafslimeHp, recordLeafslimeAction } from "./monsters/leafslime-intent.js";
import { STRANGLER_CHARACTER, isStrangler, rollStranglerHp, recordStranglerAction } from "./monsters/strangler-intent.js";
import { JAXFRUIT_CHARACTER, isJaxfruit, rollJaxfruitHp, recordJaxfruitAction } from "./monsters/jaxfruit-intent.js";
import { CRAWLER_CHARACTER, isCrawler, rollCrawlerHp, recordCrawlerAction } from "./monsters/crawler-intent.js";
import { INKLET_CHARACTER, isInklet, rollInkletHp, recordInkletAction, capInkletHpLoss } from "./monsters/inklet-intent.js";
import { mountEnemyIntent } from "./ui/intent-display.js";
import { createFogmogBattle } from "./monsters/fogmog-battle.js";
import { createPhrogBattle } from "./monsters/phrog-battle.js";
import { PHROG_CHARACTER, WRIGGLER_CHARACTER, INFESTED_COUNT, isPhrogActor, isWriggler, rollPhrogHp, rollWrigglerHp, recordPhrogAction, initializePhrog, skipSpawnedWriggler } from "./monsters/phrog-intent.js";
import { infectionHand, resolveInfection } from "./cards/infection-card.js";
import { isFogmog, isToothedEye, isFogmogActor, isEncounterEnemy,
recordFogmogAction } from "./monsters/fogmog-intent.js";
import { mountEnergy } from "./ui/energy-display.js";
import { mountHandUI } from "./ui/hand/index.js";
import {
finishDeathBlow,
hasPendingDeathBlow,
} from "./battle/death-blow.js";
import {
STUN_INTENT,
applyStun,
clearStun,
finishStunnedTurn,
isStunned,
skipStunnedAction,
} from "./battle/stun-intent.js";
import { recordFlyconidAction } from "./monsters/flyconid-intent.js";
import { recordRaiderAction } from "./monsters/raider-intent.js";
import {
PLAYER_ENERGY, PLAYER_HAND_LIMIT, cardCost,
canPayCard, payCard, isActiveCardUse, isXCostCard,
} from "./battle/combat-rules.js";
import { mountBattlePiles } from "./ui/card-library.js";
import { mountGMManager } from "./ui/gm-manager.js";
import { createPlayerTeardown } from "./battle/skill-teardown.js";
import { createBattleResources } from "./battle/battle-resources.js";
import { createModeStorage } from "./progression/mode-storage.js";
import { createBattleSession } from "./battle/battle-session.js";
import { observeFreshEvent } from "./battle/engine-session.js";
import { runPhaseLoop, stopBattleTurn } from "./battle/battle-loop.js";
import { createBattleSettlement } from "./battle/battle-settlement.js";
import { createBattleFlow, quiesceEngine } from "./battle/battle-flow.js";
import { lib, game, ui, get, _status } from "../../../noname.js";
import config from "./config.js";
import { showMap as renderMap } from "./ui/map/index.js";
import { getRandomRewardChoices } from "./progression/reward.js";
import { canAcquireCard } from "./cards/card-definitions.js";
import { addCardToDeck, createRandomCardData } from "./cards/card-data.js";
import { hasUpgradeableCard, upgradeRandomCard } from "./cards/upgrades.js";
import { chooseBattleReward } from "./ui/reward-ui.js";
import { chooseRewardPackage } from "./ui/reward-package.js";
import { applyStoryOutcome, getAvailableStoryChoices } from "./progression/story.js";
import { playDialogue } from "./ui/dialogue.js";
import { chooseButtons } from "./ui/flow-ui.js";
import { openModeSelection } from "./ui/navigation.js";
import {
completeNode,
createRun,
enterNextAct,
generateActMap,
getNodeEncounter,
insertStoryNode,
nextRandom,
} from "./progression/state.js";
const MODE_ID = config.modeId;
const STYLE_PATH = `${lib.assetURL}extension/术樱包/mengsan`;
const copy = value => JSON.parse(JSON.stringify(value));
const modeStorage = createModeStorage({lib, game, config, localStorage});
const saveRun = async run => {
const snapshot = copy(run);
while (true) {
try { await modeStorage.saveRun(snapshot); return; }
catch (error) {
await chooseButtons("存档未完成", [{id:"retry",name:"重试保存"}], "尚未提交本次结果;保留旧存档,不会重新计算奖励或扣款。");
}
}
};
const clearRun = modeStorage.clearRun;
const showMap = run => renderMap(run, { saveRun });
let battleSequence = 0;
let activeBattle = null;
const reportFlowError = error => { console.error("梦三流程暂停,未自动清档:", error); };
const chooseRun = async savedRun => {
const available = savedRun?.status === "running";
const act = available ? config.acts[savedRun.actIndex] : null;
return chooseButtons("梦三", [
{ id: "continue", name: "继续征程", disabled: !available,
description: available ? `${get.translation(savedRun.player.character)} · ${act?.name || "未知区域"} · 已完成 ${savedRun.statistics?.completedNodes || 0} 个节点` : "暂无进行中的征程" },
{ id: "new", name: "开始新征程", description: available ? "选择角色,确认后替换当前自动存档" : "选择一名武将,踏入第一关" },
], "单一自动存档 · 完成节点后记录进度", { eyebrow: "水墨行军 · 存档选择" });
};
const chooseCharacter = async (backLabel = "返回存档选择") => {
const choices = config.characters.filter(name => lib.character[name]).map(name => ({
id: name,
name: get.translation(name),
description: lib.translate[`${name}_title`] || "作为本次征程的角色",
}));
return chooseButtons("选择出征武将", [...(choices.length ? choices : [{ id: "mengsan_liubei_shuying", name: "刘备" }]), { id: "back", name: backLabel, description: "不会修改现有征程" }], "此武将将陪伴你完成本次征程。", { back: "back", eyebrow: "出征准备" });
};
const chooseDefeatAction = async run => chooseButtons("角色阵亡", [
{id:"restart", name:"重新开始", description:`以当前角色${get.translation(run.player.character)}从头开始新征程。`},
{id:"menu", name:"返回主菜单", description:"结束本次征程,清除当前存档并返回模式选择。"},
], "选择前保留上一次地图存档,不自动处理。本次阵亡不发放战斗奖励。", {
eyebrow:"征程结束", menu:false,
});
const createCardInstance = (run, name = "sha", upgrade = 0, affixes = []) =>
createRandomCardData(run, name, { random: () => nextRandom(run), upgrade, affixes });
const applyReward = (run, rewardId) => {
rewardId = resolveRetiredCardReward(rewardId);
const player = run.player;
const relicId = config.rewards[rewardId]?.relic;
if (relicId) {
if (grantRelic(run, relicId)) {
game.log(`获得遗物【${getRelic(relicId).name}】`);
}
return;
}
const cardReward = config.rewards[rewardId]?.card;
if (cardReward) {
if (!canAcquireCard(player.character, cardReward.name)) throw new Error(`武将 ${player.character} 无法获得牌:${cardReward.name}`);
addCardToDeck(run, { ...createCardInstance(run, cardReward.name), ...copy(cardReward) });
return;
}
const support = config.rewards[rewardId]?.support;
if (support) { grantSupport(run, rewardId, support); return; }
switch (rewardId) {
case "card_sha":
addCardToDeck(run, createCardInstance(run, "sha"));
break;
case "card_tao":
addCardToDeck(run, createCardInstance(run, "tao"));
break;
case "upgrade": {
const card = upgradeRandomCard(player.deck, () => nextRandom(run));
if (card) game.log("梦三:", get.translation(card.name),
"强化至", card.upgrade, "级");
else game.log("梦三:没有可强化卡牌");
break;
}
case "heal":
player.hp = Math.min(player.maxHp, player.hp + 8);
break;
case "max_hp":
player.maxHp += 5;
player.hp += 5;
break;
case "item_hand":
grantRelic(run, "mengsan_hand_charm_shuying");
break;
case "skill_yingyong":
if (!player.permanentSkills.includes("mengsan_yingyong_shuying")) {
player.permanentSkills.push("mengsan_yingyong_shuying");
}
break;
}
};
const chooseReward = async (run, rewardConfig = {}) => {
const choices = getRandomRewardChoices(run, rewardConfig.rewardPool, 3, {
allowSharedCards: rewardConfig.allowSharedCardRewards === true,
}).filter(choice => choice.name);
const title = rewardConfig.rewardTitle || (rewardConfig.boss ? "极品奖励(三选一)" : "战斗奖励(三选一)");
const rewardId = await chooseButtons(title, choices, rewardConfig.description);
const reward = choices.find(choice => choice.id == rewardId);
if (reward) applyReward(run, reward.effectId || reward.id);
};
const finishStoryNode = async (run, node) => {
const content = config.nodeContents?.[node.contentId];
if (!content || content.kind != "dialogue") {
await setupBattle(run, node);
return false;
}
await playDialogue(content.dialogue, { run, title: content.name });
const choices = getAvailableStoryChoices(run, content);
if (!choices.length) {
choices.push({ id: "continue", name: "继续", description: "当前没有其他可用选项。", outcome: {} });
}
const choiceId = await chooseButtons(content.name, choices, content.description);
const choice = choices.find(current => current.id == choiceId);
const outcome = applyStoryOutcome(run, choice?.outcome);
await playDialogue(outcome.dialogue, { run, title: content.name });
if (outcome.confirmResult && outcome.result) {
await chooseButtons(content.name, [{ id: "continue", name: "继续" }], outcome.result);
}
if (outcome.battle) {
await setupBattle(run, node, getNodeEncounter(run, node, outcome.battle));
return false;
}
if (outcome.rewardPool) {
await chooseReward(run, {
...outcome,
description: outcome.rewardDescription || (!outcome.confirmResult ? outcome.result : ""),
});
}
completeNode(run, node.id);
await saveRun(run);
return true;
};
const finishUtilityNode = async (run, node) => {
if (node.type == "rest") {
const choice = await chooseButtons("休息节点", [
{ id: "heal", name: "休息",
description: `回复 ${restRecovery(run)} 点生命` },
{ id: "upgrade", name: "磨砺",
description: hasUpgradeableCard(run.player.deck)
? "随机强化一张尚未满级的可强化牌"
: "没有可强化卡牌",
disabled: !hasUpgradeableCard(run.player.deck) },
]);
if (choice === "heal") {
const result = applyRoomRelics(run, node, choice);
if (result) game.log(`休息回复${result.recovered}点生命`,
...result.relics.map(relic => relic.name));
} else applyReward(run, choice);
}
else if (node.type == "chest") {
await chooseReward(run, {
rewardPool: config.acts[run.actIndex].chestRewardPool ||
"shared.pool.boss.premium",
rewardTitle: "宝箱奖励(三选一)",
});
}
else if (node.type == "shop") {
const result = applyRoomRelics(run, node, "enter");
if (result) {
game.log(`遗物【餐券】进入商店回复${result.recovered}点生命`);
await saveRun(run);
}
const price = 20;
const choice = await chooseButtons("商店 Demo", [
{ id: "card_sha", name: `购买【杀】(${price}金币)`, description: "加入个人牌组", disabled: run.player.gold < price || !canAcquireCard(run.player.character, "sha") },
{ id: "heal", name: `恢复生命(${price}金币)`, description: "回复 8 点生命", disabled: run.player.gold < price },
{ id: "leave", name: "离开", description: "暂不购买" },
]);
if (choice != "leave" && choice != null) {
if (run.player.gold < price || (choice === "card_sha" && !canAcquireCard(run.player.character, "sha"))) throw new Error("梦三商店购买资格无效");
applyReward(run, choice);
run.player.gold -= price;
}
}
else {
const choices = [
{ id: "gold", name: "收下钱袋", description: "获得 15 金币" },
];
if (run.actIndex !== 0) {
choices.unshift({
id: "branch", name: "调查异象",
description: "在当前路线后插入一个额外剧情战斗节点",
});
}
const choice = await chooseButtons("随机事件", choices);
if (choice == "branch") insertStoryNode(run, node.id);
else if (choice == "gold") {
run.player.gold += 15;
run.statistics.goldEarned += 15;
}
}
completeNode(run, node.id);
await saveRun(run);
return true;
};
const shuffleBattlePile = (run, cards) => {
for (let index = cards.length - 1; index > 0; index--) {
const target = Math.floor(nextRandom(run) * (index + 1));
[cards[index], cards[target]] = [cards[target], cards[index]];
}
};
const setBattleCamp = (player, camp) => {
player.storage.mengsanCamp_shuying = camp;
player.side = camp === "enemy";
player.identity = player === game.me ? "zhu" : camp === "ally" ? "zhong" : "fan";
player.setIdentity(player.identity);
player.identityShown = true;
player.ai.shown = 1;
player.classList.add("mengsan-hide-identity-shuying");
};
const initBattleUnit = (player, spec) => {
// 只在实际入场且未指定生命时,用征程随机源抽取各怪物的普通生命范围;资料列表不消耗随机数。
const monsterSpec = spec.camp === "enemy" && spec.hp == null ?
(spec.character === CRAWLER_CHARACTER ?
{ ...spec, hp: rollCrawlerHp(() => nextRandom(_status.mengsanRun_shuying)) } :
spec.character === JAXFRUIT_CHARACTER ?
{ ...spec, hp: rollJaxfruitHp(() => nextRandom(_status.mengsanRun_shuying)) } :
spec.character === STRANGLER_CHARACTER ?
{ ...spec, hp: rollStranglerHp(() => nextRandom(_status.mengsanRun_shuying)) } :
spec.character === LEAFSLIME_CHARACTER ?
{ ...spec, hp: rollLeafslimeHp(() => nextRandom(_status.mengsanRun_shuying)) } :
spec.character === LEAFMEDIUM_CHARACTER ?
{ ...spec, hp: rollLeafslimeMediumHp(() => nextRandom(_status.mengsanRun_shuying)) } :
spec.character === TWIGSLIME_CHARACTER ?
{ ...spec, hp: rollTwigslimeHp(() => nextRandom(_status.mengsanRun_shuying)) } :
spec.character === TWIGMEDIUM_CHARACTER ?
{ ...spec, hp: rollTwigmediumHp(() => nextRandom(_status.mengsanRun_shuying)) } :
spec.character === SHRINKER_CHARACTER ?
{ ...spec, hp: rollShrinkerHp(() => nextRandom(_status.mengsanRun_shuying)) } :
spec.character === PHROG_CHARACTER ?
{ ...spec, hp: rollPhrogHp(() => nextRandom(_status.mengsanRun_shuying)) } :
spec.character === KIN_FOLLOWER_CHARACTER ?
{ ...spec, hp: rollKinFollowerHp(() => nextRandom(_status.mengsanRun_shuying)) } :
spec.character === WRIGGLER_CHARACTER ?
{ ...spec, hp: rollWrigglerHp(() => nextRandom(_status.mengsanRun_shuying)) } :
spec.character === BYRDONIS_CHARACTER ?
{ ...spec, hp: rollByrdonisHp(() => nextRandom(_status.mengsanRun_shuying)) } :
spec.character === NIBBIT_CHARACTER ?
{ ...spec, hp: rollNibbitHp(() => nextRandom(_status.mengsanRun_shuying)) } :
spec.character === INKLET_CHARACTER ?
{ ...spec, hp: rollInkletHp(() => nextRandom(_status.mengsanRun_shuying)) } : spec) : spec;
const monster = spec.camp === "enemy" ? createMonster(monsterSpec) : null;
player.storage.mengsanUnitId_shuying = spec.id;
setBattleCamp(player, spec.camp);
player.init(spec.character, null, spec.inheritSkills !== false);
setBattleCamp(player, spec.camp);
player.storage.mengsanUnitId_shuying = spec.id;
if (monster) {
player.storage.mengsanMonster_shuying = { tier: monster.tier, draw: monster.draw, handLimit: monster.handLimit };
player.storage.mengsanMaxEnergy_shuying = monster.energy;
player.storage.mengsanEnergy_shuying = monster.energy;
}
if (spec.maxHp != null || monster) player.maxHp = spec.maxHp ?? monster.hp;
player.hp = Math.min(spec.hp ?? monster?.hp ?? player.hp, player.maxHp);
initializeCubex(player, activeBattle);
initializeByrdonis(player, activeBattle);
initializeEffigy(player, activeBattle);
initializeVantom(player, activeBattle);
initializeBeast(player, activeBattle);
initializeKin(player, spec, activeBattle);
initializePhrog(player, spec, activeBattle);
if (isInklet(player)) {
const position = spec.inkletPosition ?? 1;
if (!Number.isInteger(position) || position < 1 || position > 7) throw new RangeError("墨宝站位必须为1~7");
player.storage.mengsanInkletState_shuying = { position, stage: "opening", turnsTaken: 0 };
player.storage.mengsanInkletIntent_shuying = null;
player.storage.mengsanSlippery_shuying = 1;
player.addSkill("mengsan_slippery_shuying");
player.markSkill("mengsan_slippery_shuying");
}
player.storage.mengsanInitialHand_shuying = spec.hand ?? 4;
for (const skill of spec.skills || []) player.addSkill(skill);
player.update();
return monster || initializeBondUnit(player, spec,
_status.mengsanRun_shuying);
};
const installMonsterPile = (player, monster, current) => {
if (!monster) return;
const run = _status.mengsanRun_shuying;
const battle = { drawPile: monster.createDeck(`${run.runId}_${player.playerid}`), discardPile: [], exhaustPile: [] };
for (const card of battle.drawPile) if (!lib.card[card.name]) throw new Error("怪物牌堆中的牌未加载:" + card.name);
shuffleBattlePile(run, battle.drawPile);
current.monsterPiles.set(player, installPersonalPiles(current.session, player, battle, run, current.resources, {
game, ui, get, lib, _status, document, MutationObserver, shuffle: shuffleBattlePile,
strict: false, refresh() {},
}));
};
const {
planEnemyIntent,
executeFlyconidIntent,
executeRaiderIntent,
executeFogmogIntent,
executeMawlerIntent,
executeVineIntent,
executeCubexIntent,
executeByrdonisIntent,
executeVantomIntent,
executeBeastIntent,
executeKinIntent,
executeEffigyIntent,
executePhrogIntent,
executeNibbitIntent,
executeShrinkerIntent,
executeTwigmediumIntent,
executeTwigslimeIntent,
executeLeafslimeMediumIntent,
executeLeafslimeIntent,
executeStranglerIntent,
executeJaxfruitIntent,
executeCrawlerIntent,
executeInkletIntent,
} = createMonsterIntentActions(game, () => activeBattle);
const { planBondIntent, executeBondIntent } =
createBondIntentActions(game, () => activeBattle);
const equipBattleUnit = async (player, spec, resources) => {
for (const info of spec.equipment || []) {
const card = resources.card(game.createCard(info.name, info.suit, info.number, info.nature));
await player.equip(card);
}
};
const spawnBattleUnit = async (current, spec, anchor) => {
const player = game.addPlayer(anchor ? Number(anchor.dataset.position) + 1 : game.players.length + game.dead.length);
registerPlayers(current.session, [player]);
current.resources.bindPlayers([player]);
const join = game.createEvent("mengsanJoinBattle", false);
join.player = player;
join.setContent(async () => {
const monster = initBattleUnit(player, spec);
current.fogmog?.register(player, spec);
installMonsterPile(player, monster, current);
if (monster) {
current.energyUI.set(player,
mountEnergy(player, current.session, document));
current.intentUI.set(player, mountEnemyIntent(player,
current.session, STYLE_PATH, document,
() => getIntentHostiles(game, player)));
}
for (const participant of [...game.players, ...game.dead]) participant.setSeatNum(Number(participant.dataset.position) + 1);
await equipBattleUnit(player, spec, current.resources);
await player.draw(spec.hand ?? 4);
await game.triggerEnter(player);
if (monster && current.session.active) {
planEnemyIntent(player, _status.mengsanRun_shuying);
planBondIntent(player);
}
});
await join;
game.log(player, "作为", spec.camp === "ally" ? "友方支援" : "敌方援军", "加入战斗");
return player;
};
const createScenario = (plan, current) => {
const director = createBattleDirector(plan, {
active: () => current.session.active,
state: player => ({ hp: player.hp, hand: player.countCards("h"), alive: player.isAlive(),
camp: player.storage.mengsanCamp_shuying, linked: player.isLinked(), turnedOver: player.isTurnedOver() }),
dialogue: lines => playDialogue(lines, { run: _status.mengsanRun_shuying, title: "关卡剧情" }),
spawn: (spec, anchor) => spawnBattleUnit(current, spec, anchor),
async effect(player, effect) {
if (effect.type === "draw") await player.draw(effect.amount);
else if (effect.type === "recover") await player.recover(effect.amount);
else if (effect.type === "maxHp") await player.gainMaxHp(effect.amount);
else if (effect.type === "skill") await player.addSkills(effect.skill);
else if (effect.type === "camp") setBattleCamp(player, effect.camp);
else if (effect.type === "discardEquipment") {
const cards = player.getCards("e", card => card.name === effect.name);
if (cards.length) await player.discard(cards);
}
},
log: name => game.log("关卡条件触发:", name),
});
current.session.ownResource(director, () => director.dispose());
return director;
};
const prepareBattle = async (run, node, encounter, session, resources) => {
// Old saves keep their deck instances, but the retired affix no longer exists.
for (const card of run.player.deck) if (Array.isArray(card.affixes)) card.affixes = card.affixes.filter(key => key !== "annihilate");
if (encounter.requiredCharacter && run.player.character !== encounter.requiredCharacter) throw new Error("该关卡仅限指定主角,请开始刘备的新征程");
const bondUnit = prepareBondBattle(run, () => nextRandom(run), node);
const plan = buildBattlePlan(encounter, run, bondUnit);
const scheduledUnits = [...plan.units, ...plan.rules.flatMap(rule =>
rule.effects.filter(effect => effect.type === "spawn").map(effect => effect.unit))];
const fogmogCount = scheduledUnits.filter(spec => spec.character === "mengsan_fogmog_shuying").length;
const phrogCount = scheduledUnits.filter(spec => spec.character === PHROG_CHARACTER).length;
if (scheduledUnits.length + fogmogCount + phrogCount * INFESTED_COUNT > 7) throw new Error("召唤需要预留空席位;请减少本场支援数量");
if (run.bondBattle) {
game.log(`羁绊助战:${bondDefinitions[run.bondBattle.id].name},${bondBattleStatus(run.bondBattle)}`);
}
if (encounter.encounterPool === "weak" || encounter.encounterPool === "strong") {
game.log(`普通战斗第${encounter.normalBattleNumber}场:${encounter.encounterPool === "weak" ? "弱怪池" : "强怪池"},${encounter.name}`);
}
for (const spec of [...plan.units, ...plan.rules.flatMap(r => r.effects.filter(e => e.type === "spawn").map(e => e.unit))]) {
// Bond NPCs may belong to a disabled character pack. Resolve them the
// same way as native Player.init, without enabling the whole pack.
if (spec.bondId && !lib.character[spec.character]) {
const character = get.character(spec.character);
if (character && !character.isNull) lib.character[spec.character] = character;
}
if (!lib.character[spec.character]) throw new Error("关卡武将未加载:" + spec.character);
for (const skill of spec.skills || []) if (!lib.skill[skill]) throw new Error("关卡技能未加载:" + skill);
const slots = new Set();
for (const card of spec.equipment || []) {
const info = lib.card[card.name];
if (!info || info.type !== "equip" || !info.subtype || slots.has(info.subtype)) throw new Error("初始装备不存在、不是装备或槽位重复:" + card.name);
slots.add(info.subtype);
}
}
for (const rule of plan.rules) for (const effect of rule.effects) {
if (effect.type === "skill" && !lib.skill[effect.skill]) throw new Error("关卡增益技能未加载:" + effect.skill);
}
consumeSupports(run);
document.body.classList.add("mengsan-battle-ui-shuying");
session.ownResource({}, () => document.body.classList.remove("mengsan-battle-ui-shuying"));
resources.field(_status, "mengsanRun_shuying", run);
resources.field(_status, "mengsanNode_shuying", node);
resources.field(_status, "mengsanEncounter_shuying", encounter);
resources.field(_status, "mengsanBattle_shuying", {
resolving: false,
drawPile: copy(run.player.deck),
discardPile: [],
exhaustPile: [],
});
shuffleBattlePile(run, _status.mengsanBattle_shuying.drawPile);
game.prepareArena(1 + plan.units.length);
resources.capturePreparedArena();
// Register runtime IDs before card ownership transfers (gain/lose).
for (const current of game.players) current.getId();
registerPlayers(session, game.players.slice());
resources.bindPlayers(game.players);
const currentBattle = activeBattle;
currentBattle.phrog = createPhrogBattle(game, currentBattle,
(spec, anchor) => spawnBattleUnit(currentBattle, spec, anchor));
currentBattle.fogmog = createFogmogBattle(game, currentBattle,
(spec, anchor) => spawnBattleUnit(currentBattle, spec, anchor));
let teardown;
activeBattle.releaseSkills = () => (teardown ||= createPlayerTeardown([...currentBattle.players], {lib,game,get,_status}))();
const me = game.me;
activeBattle.personalPiles = installPersonalPiles(session, me, _status.mengsanBattle_shuying, run, resources, {
game, ui, get, lib, _status, document, MutationObserver, shuffle: shuffleBattlePile,
refresh: () => activeBattle?.session === session && activeBattle.pilesUI?.refresh(),
});
activeBattle.personalPiles.withOwner(() => me.init(run.player.character));
me.storage.mengsanUnitId_shuying = "player";
setBattleCamp(me, "ally");
const participants = game.players.filter(player => player !== me);
const monsters = plan.units.map((spec, index) => initBattleUnit(participants[index], spec));
bindNibbitOpenings(participants);
plan.units.forEach((spec, index) => installMonsterPile(participants[index], monsters[index], currentBattle));
if (run.bondBattle?.arrived) game.log(`羁绊助战:${bondDefinitions[run.bondBattle.id].name}已到场,使用独立牌堆`);
participants.forEach((player, index) => {
if (monsters[index]) currentBattle.intentUI.set(player,
mountEnemyIntent(player, session, STYLE_PATH, document,
() => getIntentHostiles(game, player)));
});
participants.forEach(player => {
if (player.storage.mengsanBond_shuying) {
currentBattle.energyUI.set(player,
mountEnergy(player, session, document));
}
});
activeBattle.director = createScenario(plan, currentBattle);
activeBattle.director.bind("player", me);
plan.units.forEach((spec, index) => activeBattle.director.bind(spec.id, participants[index]));
me.storage.mengsanPlayer_shuying = true;
me.storage.mengsanMaxEnergy_shuying = PLAYER_ENERGY;
me.storage.mengsanEnergy_shuying = PLAYER_ENERGY;
currentBattle.energyUI.set(me, mountEnergy(me, session, document));
currentBattle.handUI = await mountHandUI(me, session, {
game, ui, get, lib, _status, document, window, cardCost,
canPayCard, isActiveCardUse,
styleURL: `${STYLE_PATH}/ui/hand/style.css`,
log: message => game.log(message),
});
if (!session.active) return;
game.zhu = me;
if (run.player.maxHp == null) {
run.player.maxHp = me.maxHp;
run.player.hp = me.hp;
}
delete run.sharedBattleCardEffects;
initializeRelicMaxHp(run);
me.maxHp = run.player.maxHp;
me.hp = Math.max(1, Math.min(run.player.hp, me.maxHp));
me.update();
currentBattle.relics = createRelicBattle(run, {
active: () => session.active && me.hp > 0,
draw: number => game.mengsanDraw_shuying(me, number),
effect: createOpeningEffects(game, me,
() => currentBattle.energyUI.get(me)?.()),
log: (relic, effect) => {
game.log(me, `遗物【${relic.name}】发动:${effect}`);
currentBattle.relicUI?.refresh();
},
});
run.player.permanentSkills.forEach(skill => {
if (lib.skill[skill] && !me.hasSkill(skill)) me.addSkill(skill);
});
for (const skill of [
"mengsan_draw_shuying", "mengsan_card_use_shuying", "mengsan_ic_bridge_shuying", "mengsan_shared_bridge_shuying",
"mengsan_card_affixes_shuying", "mengsan_scenario_shuying",
"mengsan_card_payment_shuying", "mengsan_monster_draw_shuying",
"mengsan_flyconid_action_shuying", "mengsan_raider_action_shuying",
"mengsan_fogmog_action_shuying", "mengsan_mawler_action_shuying",
"mengsan_phrog_action_shuying", "mengsan_wriggler_spawned_shuying", "mengsan_infection_damage_shuying", "mengsan_beast_action_shuying", "mengsan_kin_action_shuying", "mengsan_kin_orphan_shuying", "mengsan_vantom_action_shuying", "mengsan_effigy_action_shuying", "mengsan_byrdonis_action_shuying", "mengsan_cubex_action_shuying", "mengsan_vine_action_shuying", "mengsan_nibbit_action_shuying",
"mengsan_shrinker_action_shuying",
"mengsan_twigmedium_action_shuying",
"mengsan_twigslime_action_shuying",
"mengsan_leafmedium_action_shuying",
"mengsan_leafslime_action_shuying",
"mengsan_strangler_action_shuying",
"mengsan_jaxfruit_action_shuying",
"mengsan_crawler_action_shuying", "mengsan_inklet_action_shuying",
"mengsan_bond_action_shuying",
"mengsan_raider_card_strength_shuying",
"mengsan_death_blow_finish_shuying",
"mengsan_stun_skip_shuying", "mengsan_stun_recover_shuying",
"mengsan_stun_clear_shuying",
"mengsan_relics_shuying",
"mengsan_relic_attack_shuying",
]) {
if (!lib.skill.global.includes(skill)) {
game.addGlobalSkill(skill);
session.ownResource({}, () => game.removeGlobalSkill(skill));
}
}
activeBattle.pilesUI = mountBattlePiles(session, _status.mengsanBattle_shuying);
currentBattle.relicUI = mountRelics(session, run, currentBattle.relics);
activeBattle.gmUI = mountGMManager(session, me, activeBattle.personalPiles, _status.mengsanBattle_shuying, resources, game);
for (let index = 0; index < plan.units.length; index++) await equipBattleUnit(participants[index], plan.units[index], resources);
game.syncState();
_status.event.trigger("gameStart");
for (const participant of participants) await participant.draw(participant.storage.mengsanInitialHand_shuying ?? 4);
for (const participant of participants) currentBattle.monsterPiles.get(participant)?.drawInnate();
if (!session.active) return;
activeBattle.personalPiles.resetOpeningHand();
await game.mengsanDraw_shuying(me, 4);
if (!session.active) return;
activeBattle.personalPiles.drawInnate();
await currentBattle.relics.start();
if (!session.active) return;
await playDialogue(encounter.openingDialogue, { run, title: encounter.name || "开场剧情" });
if (!session.active) return;
await currentBattle.director.start();
if (!session.active) return;
game.checkResult();
const loop = game.createEvent("phaseLoop", false);
loop.player = me;
loop._isStandardLoop = true;
let openingHandChecked = false;
loop.setContent(async event => runPhaseLoop(event, me, session, {game, lib, _status, get, beforeTurn: async player => {
if (player === me && !openingHandChecked) {
openingHandChecked = true;
currentBattle.personalPiles.trimOpeningHand(
4 + currentBattle.relics.openingHandBonus);
}
if (currentBattle.players.has(player) && player.hujia > 0 && !ironcladPreservesBlock(player))
await player.changeHujia(-player.hujia);
if (player.storage.mengsanMaxEnergy_shuying != null) {
player.storage.mengsanEnergy_shuying = player.storage.mengsanMaxEnergy_shuying;
currentBattle.energyUI.get(player)?.();
}
if (player === me) currentBattle.fogmog.beforeRound();
await currentBattle.director.beforeTurn(player);
if (isFogmogActor(player) || isPhrogActor(player)) planEnemyIntent(player, run);
if (player === me && session.active) {
for (const participant of game.players) {
planEnemyIntent(participant, run);
planBondIntent(participant);
}
}
game.checkResult();
}}));
};
const registerPlayers = (session, players) => {
for (const player of players) {
activeBattle.players.add(player);
const id = player.playerid;
session.ownResource(player, () => {
if (game.playerMap[id] === player) delete game.playerMap[id];
for (const list of [game.players, game.dead]) {
const index = list.indexOf(player); if (index >= 0) list.splice(index, 1);
}
if (game.me === player) game.me = null;
if (game.zhu === player) game.zhu = null;
player.remove();
});
}
};
const makeFlow = (releaseSkills = async () => {}) => createBattleFlow({
releaseSkills,
presentVictory: pending => {
if (pending.bondGrowth) {
const growth = pending.bondGrowth;
game.log(`羁绊【${bondDefinitions[growth.id].name}】进度增加${
growth.gain}%,当前${growth.level}级(${growth.progress}%)`);
}
if (pending.relicRecovery > 0) {
game.log(`遗物【燃烧之血】战后回复${pending.relicRecovery}点生命`);
}
return playDialogue(pending.victoryDialogue,
{ run: pending.base, title: "战后剧情" });
},
settlement:createBattleSettlement({store:modeStorage, config,
getRandomRewardChoices, applyReward, completeNode, enterNextAct, createRun,
logRewardPackage: (pack, selection) => {
const card = pack.upgradeChoices.find(card =>
card.id === selection.cardId);
game.log(`梦三:过关奖励 ${pack.gold} 金币,` +
(card ? `强化【${get.translation(card.name)}】` : "未强化卡牌"));
},
}),
chooseReward: (choices, options) => (options.rewardPackage
? chooseRewardPackage : chooseBattleReward)(choices, {
...options,
fixedRewards: options.fixedRewards.map(id => ({
...config.rewards[id], id,
})),
}),
chooseDefeat:chooseDefeatAction,
showMap,
showEnding:async (route, run) => {
if (route === "menu") { await openModeSelection(); return; }
if (route === "victory") {
await playDialogue(config.acts[run?.actIndex]?.completionDialogue,
{ run, title: "未完待续" });
}
game.over(route === "victory");
},
quiesce:() => quiesceEngine(_status),
});
const requestBattleFinish = outcome => {
const current = activeBattle;
if (!current) return false;
// Complete the original card and all queued replays before snapshotting a victory.
if(outcome==="victory" && current.sharedPendingUses?.size){current.sharedDeferredVictory=true;return false;}
for (const player of game.dead) {
recordBondDeath(player, _status.mengsanRun_shuying,
message => game.log(message));
}
const accepted = current.flow.requestFinish({session:current.session, hp:game.me?.hp || 0, outcome, defeatedEnemies:current.director?.defeatedEnemies ?? 1});
if (accepted) {
current.pilesUI?.dispose();
current.gmUI?.dispose();
current.relicUI?.dispose();
current.handUI?.dispose();
stopBattleTurn(current.session, _status.eventManager);
if (_status.mengsanBattle_shuying) _status.mengsanBattle_shuying.resolving = true;
current.finished();
}
return accepted;
};
const awaitSettlement = async (flow, initial = flow.wait()) => {
let operation = initial;
while (true) {
try { return await operation; }
catch (error) {
if (flow.state !== "retryable") throw error;
const action = await chooseButtons("结算未完成", [
{id:"retry",name:"重试保存与结算"},
{id:"exit",name:"保留已保存进度并返回模式选择"},
], "已提交的奖励不会重发。技能释放失败时请返回模式选择,避免重复调用释放回调。");
if (action === "exit") { await openModeSelection(); throw error; }
operation = flow.retry();
}
}
};
const playBattle = async (run, node, encounter) => {
// Wait for engine boot callbacks BEFORE arena creation; cardsAsync then builds synchronously.
if (lib.onfree) await new Promise(resolve => lib.onfree.push(resolve));
await quiesceEngine(_status);
const session = createBattleSession(`${run.runId}:${++battleSequence}`);
const resources = createBattleResources(session, {game,ui,_status});
let current;
const flow = makeFlow(() => current.releaseSkills?.());
let finished;
const signal = new Promise(resolve => { finished = resolve; });
current = {session, flow, finished, resources, players: new Set(), monsterPiles: new Map(), energyUI: new Map(), intentUI: new Map()};
activeBattle = current;
const root = new lib.element.GameEvent("mengsanBattle", false, _status.eventManager);
current.root = root;
const previousRoot = _status.eventManager.rootEvent;
session.ownResource({}, () => {
if (_status.eventManager.rootEvent === root) _status.eventManager.rootEvent = previousRoot;
});
root.setContent(async () => { await prepareBattle(run, node, encounter, session, resources); });
observeFreshEvent(session, root);
await flow.enterBattle({session,run,node,encounter,start() {
root.start().catch(error => { current.engineError = error; finished(); });
}});
await signal; // External controller, never an engine event content await.
if (current.engineError) {
reportFlowError(current.engineError);
session.requestStop();
current.handUI?.dispose();
document.body.classList.remove("mengsan-battle-ui-shuying");
const reason = current.engineError?.message || String(current.engineError);
await chooseButtons("战斗流程异常", [{id:"exit",name:"保留存档并返回模式选择"}],
`错误原因:${reason}。已停止继续开战,上次成功存档仍保留。`);
await openModeSelection(); return;
}
const result = await awaitSettlement(flow);
if (activeBattle === current) activeBattle = null;
return result;
};
const setupBattle = async (run, node, encounter = getNodeEncounter(run, node)) => {
const result = await playBattle(run, node, encounter);
if (!result) return;
if (result.route === "map") await routeRun(result.run, result.nextNode, true);
else if (result.route === "new") await routeRun(result.run);
};
const routeRun = async (run, selected = null, supplied = false) => {
while (true) {
const node = supplied ? selected : await showMap(run);
supplied = false;
if (!node) { await openModeSelection(); return; }
if (["event", "rest", "shop", "chest"].includes(node.type)) {
await finishUtilityNode(run, node);
continue;
}
if (node.type === "story" && config.nodeContents?.[node.contentId]?.kind === "dialogue") {
if (await finishStoryNode(run, node)) continue;
return;
}
await setupBattle(run, node); return;
}
};
const startJourney = async () => {
lib.translate.restart = "返回";
let run = modeStorage.read()[config.saveKey] || null;
while (true) {
const action = await chooseRun(run);
if (action === "continue") break;
const character = await chooseCharacter();
if (character === "back") continue;
if (run) {
const decision = await chooseButtons("替换当前存档?", [
{ id: "cancel", name: "保留原征程", description: "返回存档选择,不作任何修改" },
{ id: "confirm", name: "确认覆盖并出征", danger: true, description: `以${get.translation(character)}开始新征程,原进度无法撤销` },
], "只有确认后才会创建并写入新征程。", { back: "cancel", eyebrow: "覆盖确认" });
if (decision !== "confirm") continue;
}
run = createRun(character);
await saveRun(run);
break;
}
const canRefreshOldMap = config.characters.includes(run.player.character) && !run.map?.completedNodeIds?.length && run.map?.layoutVersion != config.mapLayoutVersion;
if (!run.map || run.map.actIndex != run.actIndex || canRefreshOldMap) {
run.map = generateActMap(run, run.actIndex);
await saveRun(run);
}
await routeRun(run);
};
const createMode = identityMode => {
const identityElement = identityMode.element || {};
const identityPlayer = identityElement.player || {};
const nativeChangeHujia = identityPlayer.changeHujia || lib.element.Player.prototype.changeHujia;
const nativeChangeHp = identityPlayer.changeHp || lib.element.Player.prototype.changeHp;
const cardContext = {
game, get, lib, random:nextRandom, status:_status, getBattle:()=>activeBattle,
copyToDiscard: createBattleCardCopier(game, () => activeBattle),
reclaimFromDiscard: createBattleCardReclaimer(game, () => activeBattle, get),
temporaryStrength: createTemporaryStrength(game, () => activeBattle),
countStrikeCards: createStrikeCounter(game, () => activeBattle, lib),
blockDraw: createBlockDrawer(game, () => activeBattle),
blockUpgrade: createHandUpgrader(game, () => activeBattle, get),
lifeLossBlock: createLifeLossBlocker(() => activeBattle),
whirlwind: createWhirlwind(game, () => activeBattle),
spite: createSpite(game, () => activeBattle),
rampage: createRampage(game, () => activeBattle),
playDrawTop: createHavocPlayer(game, () => activeBattle, _status),
handExhaust: createHandExhauster(game, () => activeBattle, get,
createRandomHandExhauster(game, () => activeBattle, () => _status.mengsanRun_shuying, nextRandom)),
battleEnergy: createBattleEnergy(game, () => activeBattle),
randomHandExhaust: createRandomHandExhauster(game, () => activeBattle, () => _status.mengsanRun_shuying, nextRandom),
isBattleActive: () => Boolean(activeBattle?.session.active),
};
const cardPacks = createCardPackRuntime(cardContext);
const modeCards = createMengsanCards({cardPacks,isBattleActive:cardContext.isBattleActive});
return {
...identityMode,
name: MODE_ID,
character: { ...(identityMode.character || {}), ...createScenarioCharacters(lib.element.Character) },
card: { ...(identityMode.card || {}), ...modeCards.card },
connect: false,
start: [
async () => {
// Let the engine start event finish before starting the external controller.
setTimeout(() => startJourney().catch(reportFlowError), 0);
},
],
element: {
...identityElement,
player: {
...identityPlayer,
async dieAfter() {
// 原生死亡已成立;在战斗结算关闭会话前落实斩杀的永久收益。
if(this.isDead())await cardPacks.onDeath(_status.event);
clearKinOwnerIntents(game, activeBattle, this);
clearConstrictSource(activeBattle, this);
clearShrinkSource(activeBattle, this);
activeBattle?.fogmog?.onDeath(this);
if (activeBattle?.phrog) await activeBattle.phrog.onDeath(this);
recordBondDeath(this, _status.mengsanRun_shuying,
message => game.log(message));
game.checkResult();
},
dieAfter2() {}, // Identity-mode kill rewards/loyalist penalties do not apply here.
isFriendOf(player) { return this === player || (this.storage.mengsanCamp_shuying === player?.storage?.mengsanCamp_shuying); },
isEnemyOf(player) { return Boolean(player && this.storage.mengsanCamp_shuying !== player.storage?.mengsanCamp_shuying); },
getEnemies(filter, includeDie) { return game[includeDie ? "filterPlayer2" : "filterPlayer"](p => this.isEnemyOf(p) && (!filter || filter(p))); },
getFriends(filter, includeDie) { const self = filter === true; return game[includeDie ? "filterPlayer2" : "filterPlayer"](p => (p !== this || self) && this.isFriendOf(p) && (typeof filter !== "function" || filter(p))); },
changeHp(num, popup) {
const next = nativeChangeHp.call(this, num, popup);
if (activeBattle?.session.active) capInkletHpLoss(this, next);
return next;
},
changeHujia(num, type) {
if(activeBattle?.session.active && type!=="damage")num=beforeIroncladBlock(this,beforeSharedBlock(this,num??1,_status.event),_status.event);
// 脆弱只削减正向获得的护甲,不改变受伤时消耗护甲的数值。
if (this.storage?.mengsanFrail_shuying > 0 && (num == null || num > 0) && type !== "damage") {
num = Math.floor((num ?? 1) * 0.75);
}
return nativeChangeHujia.call(this, num, type, false);
},
getHandcardLimit() {
if (this.storage?.mengsanPlayer_shuying) {
const run = _status.mengsanRun_shuying;
const bonus = Number(run?.player?.handLimitBonus || 0);
return PLAYER_HAND_LIMIT + bonus;
}
if (this.storage?.mengsanMonster_shuying) return this.storage.mengsanMonster_shuying.handLimit;
if (this.storage?.mengsanBond_shuying) {
return this.storage.mengsanBond_shuying.handLimit;
}
let number = Math.max(this.hp, 0);
number = game.checkMod(this, number, "maxHandcardBase", this);
number = game.checkMod(this, number, "maxHandcard", this);
number = game.checkMod(this, number, "maxHandcardFinal", this);
return Math.max(0, number);
},
},
},
game: {
...(identityMode.game || {}),
mengsanSetEnemyIntent_shuying(player, intent) {
const update = activeBattle?.intentUI.get(player);
if (!update) return false;
update(intent);
return true;
},
mengsanStunEnemy_shuying(player, options = {}) {
const update = activeBattle?.intentUI.get(player);
if (!activeBattle?.session.active || !update ||
!player?.isAlive() ||
player.storage?.mengsanCamp_shuying !== "enemy") {
return false;
}
if (isStunned(player)) return false;
const { resume = "advance", recover } = options;
const flyconid = isFlyconid(player);
const raider = isRaider(player);
const fogmog = isFogmogActor(player);
const mawler = isMawler(player);
const vine = isVine(player);
const cubex = isCubex(player);
const byrdonis = isByrdonis(player);
const effigy = isEffigy(player);
const vantom = isVantom(player);
const beast = isBeast(player);
const kin = isKinActor(player);
const phrog = isPhrogActor(player);
const nibbit = isNibbit(player);
const shrinker = isShrinker(player);
const twigmedium = isTwigmedium(player);
const twigslime = isTwigslime(player);
const leafmedium = isLeafslimeMedium(player);
const leafslime = isLeafslime(player);
const strangler = isStrangler(player);
const jaxfruit = isJaxfruit(player);
const crawler = isCrawler(player);
const inklet = isInklet(player);
const intent = options.intent ?? (flyconid ?
player.storage.mengsanFlyconidIntent_shuying : raider ?
player.storage.mengsanRaiderIntent_shuying : fogmog ?
player.storage.mengsanFogmogIntent_shuying : mawler ?
player.storage.mengsanMawlerIntent_shuying : crawler ?
player.storage.mengsanCrawlerIntent_shuying : inklet ?
player.storage.mengsanInkletIntent_shuying : jaxfruit ?
player.storage.mengsanJaxfruitIntent_shuying : strangler ?
player.storage.mengsanStranglerIntent_shuying : leafslime ?
player.storage.mengsanLeafslimeIntent_shuying : leafmedium ?
player.storage.mengsanLeafslimeMediumIntent_shuying : twigslime ?
player.storage.mengsanTwigslimeIntent_shuying : twigmedium ?
player.storage.mengsanTwigmediumIntent_shuying : shrinker ?
player.storage.mengsanShrinkerIntent_shuying : vine ?
player.storage.mengsanVineIntent_shuying : nibbit ?
player.storage.mengsanNibbitIntent_shuying : cubex ?
player.storage.mengsanCubexIntent_shuying : byrdonis ?
player.storage.mengsanByrdonisIntent_shuying : effigy ?
player.storage.mengsanEffigyIntent_shuying : phrog ?
player.storage.mengsanPhrogIntent_shuying : vantom ?
player.storage.mengsanVantomIntent_shuying : kin ?
player.storage.mengsanKinIntent_shuying : beast ?
player.storage.mengsanBeastIntent_shuying : null);
const restore = recover || (flyconid ?
(choice, original) => {
const state =
player.storage.mengsanFlyconidState_shuying || {};
player.storage.mengsanFlyconidState_shuying =
recordFlyconidAction(state, null, false);
const next = choice === "retry" ? original : null;
player.storage.mengsanFlyconidIntent_shuying = next;
return next;
} : raider ? (choice, original) => {
player.storage.mengsanRaiderState_shuying = recordRaiderAction(player.storage.mengsanRaiderState_shuying || {});
const next = choice === "retry" ? original : null;
player.storage.mengsanRaiderIntent_shuying = next;
return next;
} : fogmog ? (choice, original) => {
if (isFogmog(player)) player.storage.mengsanFogmogState_shuying =
recordFogmogAction(player.storage.mengsanFogmogState_shuying || {}, null, false);
const next = choice === "retry" ? original : null;
player.storage.mengsanFogmogIntent_shuying = next;
return next;
} : mawler ? (choice, original) => {
player.storage.mengsanMawlerState_shuying =
recordMawlerAction(player.storage.mengsanMawlerState_shuying || {}, null, false);
const next = choice === "retry" ? original : null;
player.storage.mengsanMawlerIntent_shuying = next;
return next;
} : crawler ? (choice, original) => {
player.storage.mengsanCrawlerState_shuying =
recordCrawlerAction(player.storage.mengsanCrawlerState_shuying || {}, choice !== "retry");
const next = choice === "retry" ? original : null;
player.storage.mengsanCrawlerIntent_shuying = next;
return next;
} : inklet ? (choice, original) => {
player.storage.mengsanInkletState_shuying =
recordInkletAction(player.storage.mengsanInkletState_shuying || {}, choice !== "retry");
const next = choice === "retry" ? original : null;
player.storage.mengsanInkletIntent_shuying = next;
return next;
} : jaxfruit ? (choice, original) => {
player.storage.mengsanJaxfruitState_shuying =
recordJaxfruitAction(player.storage.mengsanJaxfruitState_shuying || {}, choice !== "retry");
const next = choice === "retry" ? original : null;
player.storage.mengsanJaxfruitIntent_shuying = next;
return next;
} : strangler ? (choice, original) => {
player.storage.mengsanStranglerState_shuying =
recordStranglerAction(player.storage.mengsanStranglerState_shuying || {}, choice !== "retry");
const next = choice === "retry" ? original : null;
player.storage.mengsanStranglerIntent_shuying = next;
return next;
} : leafslime ? (choice, original) => {
player.storage.mengsanLeafslimeState_shuying =
recordLeafslimeAction(player.storage.mengsanLeafslimeState_shuying || {}, choice !== "retry");
const next = choice === "retry" ? original : null;
player.storage.mengsanLeafslimeIntent_shuying = next;
return next;
} : leafmedium ? (choice, original) => {
player.storage.mengsanLeafslimeMediumState_shuying =
recordLeafslimeMediumAction(player.storage.mengsanLeafslimeMediumState_shuying || {}, choice !== "retry");
const next = choice === "retry" ? original : null;
player.storage.mengsanLeafslimeMediumIntent_shuying = next;
return next;
} : twigslime ? (choice, original) => {
player.storage.mengsanTwigslimeState_shuying =
recordTwigslimeAction(player.storage.mengsanTwigslimeState_shuying || {}, choice !== "retry");
const next = choice === "retry" ? original : null;
player.storage.mengsanTwigslimeIntent_shuying = next;
return next;
} : twigmedium ? (choice, original) => {
player.storage.mengsanTwigmediumState_shuying =
recordTwigmediumAction(player.storage.mengsanTwigmediumState_shuying || {}, original, choice !== "retry");
const next = choice === "retry" ? original : null;
player.storage.mengsanTwigmediumIntent_shuying = next;
return next;
} : shrinker ? (choice, original) => {
player.storage.mengsanShrinkerState_shuying =
recordShrinkerAction(player.storage.mengsanShrinkerState_shuying || {}, choice !== "retry");
const next = choice === "retry" ? original : null;
player.storage.mengsanShrinkerIntent_shuying = next;
return next;
} : vine ? (choice, original) => {
player.storage.mengsanVineState_shuying =
recordVineAction(player.storage.mengsanVineState_shuying || {}, choice !== "retry");
const next = choice === "retry" ? original : null;
player.storage.mengsanVineIntent_shuying = next;
return next;
} : nibbit ? (choice, original) => {
player.storage.mengsanNibbitState_shuying =
recordNibbitAction(player.storage.mengsanNibbitState_shuying || {}, choice !== "retry");
const next = choice === "retry" ? original : null;
player.storage.mengsanNibbitIntent_shuying = next;
return next;
} : cubex ? (choice, original) => {
player.storage.mengsanCubexState_shuying =
recordCubexAction(player.storage.mengsanCubexState_shuying || {}, choice !== "retry");
const next = choice === "retry" ? original : null;
player.storage.mengsanCubexIntent_shuying = next;
return next;
} : byrdonis ? (choice, original) => {
player.storage.mengsanByrdonisState_shuying =
recordByrdonisAction(player.storage.mengsanByrdonisState_shuying || {}, choice !== "retry");
const next = choice === "retry" ? original : null;
player.storage.mengsanByrdonisIntent_shuying = next;
return next;
} : phrog ? (choice, original) => {
player.storage.mengsanPhrogState_shuying =
recordPhrogAction(player.storage.mengsanPhrogState_shuying || {}, choice !== "retry");
const next = choice === "retry" ? original : null;
player.storage.mengsanPhrogIntent_shuying = next;
return next;
} : effigy ? (choice, original) => {
player.storage.mengsanEffigyState_shuying =
recordEffigyAction(player.storage.mengsanEffigyState_shuying || {}, choice !== "retry");
const next = choice === "retry" ? original : null;
player.storage.mengsanEffigyIntent_shuying = next;
return next;
} : vantom ? (choice, original) => {
player.storage.mengsanVantomState_shuying =
recordVantomAction(player.storage.mengsanVantomState_shuying || {}, choice !== "retry");
const next = choice === "retry" ? original : null;
player.storage.mengsanVantomIntent_shuying = next;
return next;
} : kin ? (choice, original) => {
player.storage.mengsanKinState_shuying =
recordKinAction(player.storage.mengsanKinState_shuying || {}, choice !== "retry");
const next = choice === "retry" ? original : null;
player.storage.mengsanKinIntent_shuying = next;
return next;
} : beast ? (choice, original) => recoverBeastStun(player, choice, original) : null);
const onRecover = async (choice, original) => {
const next = await restore(choice, original);
update(next === undefined ?
(choice === "retry" ? original : null) : next);
};
if (typeof restore !== "function") {
throw new TypeError("此怪物需要击晕恢复回调");
}
if (!applyStun(player, intent, { resume, recover: onRecover })) {
return false;
}
update(STUN_INTENT);
return true;
},
async mengsanPersistRun_shuying(run) {
// 战斗中(包括阵亡等待选择期间)的永久成长先留在内存,过关才提交。
if (activeBattle && activeBattle.session.state !== "disposed") return;
await saveRun(run);
},
async mengsanClearRun_shuying() {
await clearRun();
},
mengsanFailRun_shuying(reason = "征程失败") {
game.log(reason);
return requestBattleFinish("defeat");
},
async mengsanDraw_shuying(player, number = 1) {
if (!player?.storage?.mengsanPlayer_shuying || number <= 0) return [];
if(ironcladBlocksDraw(player))return [];
const battle = _status.mengsanBattle_shuying;
const run = _status.mengsanRun_shuying;
if (!battle || battle.drawPile.length + battle.discardPile.length < number) {
await game.mengsanFailRun_shuying("个人牌堆与弃牌堆不足,挑战失败");
return [];
}
const result = activeBattle.personalPiles.take(number);
if (!result.length) return [];
player.directgain(result);
player.$draw(result.length);
game.log(player, "从个人牌堆摸了", get.cnNumber(result.length), "张牌");
await afterSharedDraw(player,result);
await afterIroncladDraw(player,result);
return result;
},
mengsanFinishBattle_shuying() {
return requestBattleFinish("victory");
},
checkResult() {
const current = activeBattle;
if (!current?.session.active || !current.director?.ready || current.director.busy || current.phrog?.pending || current.resultCheckQueued) return;
if(current.sharedPendingUses?.size && game.me?.isAlive())return;
current.resultCheckQueued = true;
const next = game.createEvent("mengsanScenarioResult", false);
next.setContent(async () => {
try {
await current.director.evaluate("state");
if (!current.session.active) return;
if (!game.me || game.me.isDead()) game.mengsanFailRun_shuying("角色死亡,征程结束");
else if (!game.players.some(isEncounterEnemy) && !current.director.pendingEnemies && !current.phrog?.pending) game.mengsanFinishBattle_shuying();
} finally { current.resultCheckQueued = false; }
});
},
},
get: {
...(identityMode.get || {}),
rawAttitude(from, to) { return from === to || from.storage.mengsanCamp_shuying === to.storage.mengsanCamp_shuying ? 8 : -8; },
},
skill: {
...(identityMode.skill || {}),
...cardPacks.skills,
...createRelicSkills(() => activeBattle, () => game.me),
...createRelicCombatSkills(() => activeBattle, () => game.me),
mengsan_taoyuan_bond_shuying: {
locked: true, mark: true, marktext: "义",
intro: { content: "先天结识关羽、张飞,初始羁绊均为8级,默认选择关羽助战。可在行军菜单中更换。" },
},
mengsan_card_payment_shuying: {
forced: true, silent: true, popup: false,
firstDo: true, priority: 10000,
trigger: { global: "useCard0" },
filter(event) { return event.player?.storage?.mengsanMaxEnergy_shuying != null &&
lib.card[event.card?.name]?.mengsanCost_shuying != null &&
(isActiveCardUse(event, event.player) ||
isXCostCard(event.card) && event.name === "useCard"); },
async content(event, trigger) {
if (trigger.mengsanPaid_shuying) return;
if (!payCard(trigger.player, trigger.card, trigger, activeBattle)) {
trigger.cancel();
game.log(trigger.player, "费用不足,不能使用", trigger.card);
return;
}
trigger.mengsanPaid_shuying = true;
activeBattle?.energyUI.get(trigger.player)?.();
},
},
mengsan_monster_draw_shuying: {
trigger: { player: "phaseDrawBegin2" },
forced: true, silent: true, popup: false,
filter(event, player) {
return Boolean(player.storage?.mengsanMonster_shuying ||
player.storage?.mengsanBond_shuying);
},
async content(event, trigger, player) {
trigger.num = isStunned(player) || isToothedEye(player) ||
isKinFollower(player) && !canActKin(game, activeBattle, player) ? 0 :
(player.storage.mengsanMonster_shuying ||
player.storage.mengsanBond_shuying).draw;
},
},
mengsan_flyconid_action_shuying: {
trigger: { global: "phaseUseBefore" },
forced: true, silent: true, popup: false, priority: 100,
filter(event) { return Boolean(activeBattle?.session.active &&
!isStunned(event.player) && isFlyconid(event.player) &&
event.player.storage.mengsanFlyconidIntent_shuying); },
async content(event, trigger) { await executeFlyconidIntent(trigger.player); },
},
mengsan_raider_action_shuying: {
trigger: { global: "phaseUseBefore" },
forced: true, silent: true, popup: false, priority: 100,
filter(event) { return Boolean(activeBattle?.session.active &&
!isStunned(event.player) && isRaider(event.player) &&
event.player.storage.mengsanRaiderIntent_shuying); },
async content(event, trigger) { await executeRaiderIntent(trigger.player); },
},
mengsan_inklet_action_shuying: {
trigger: { global: "phaseUseBefore" },
forced: true, silent: true, popup: false, priority: 100,
filter(event) { return Boolean(activeBattle?.session.active &&
event.player?.isAlive() && !isStunned(event.player) && isInklet(event.player) &&
event.player.storage.mengsanInkletIntent_shuying); },
async content(event, trigger) { await executeInkletIntent(trigger.player); },
},
mengsan_slippery_shuying: {
mark: true, marktext: "滑",
onremove(player) { delete player.storage.mengsanSlippery_shuying; },
intro: { content(storage, player) { return `滑溜${player.storage.mengsanSlippery_shuying || 0}:下一次实际生命损失限制为1,完全格挡不消耗`; } },
trigger: { player: "changeHp" },
forced: true, silent: true, popup: false,
filter(event, player) { return Boolean(activeBattle?.session.active && (isInklet(player) || isVantom(player)) &&
player.storage.mengsanSlippery_shuying > 0 && event.num < 0 && event.changedHp < 0); },
async content(event, trigger, player) {
player.storage.mengsanSlippery_shuying = Math.max(0, player.storage.mengsanSlippery_shuying - 1);
if (player.storage.mengsanSlippery_shuying) player.markSkill("mengsan_slippery_shuying");
else player.removeSkill("mengsan_slippery_shuying");
},
},
mengsan_crawler_action_shuying: {
trigger: { global: "phaseUseBefore" },
forced: true, silent: true, popup: false, priority: 100,
filter(event) { return Boolean(activeBattle?.session.active &&
event.player?.isAlive() && !isStunned(event.player) && isCrawler(event.player) &&
event.player.storage.mengsanCrawlerIntent_shuying); },
async content(event, trigger) { await executeCrawlerIntent(trigger.player); },
},
mengsan_jaxfruit_action_shuying: {
trigger: { global: "phaseUseBefore" },
forced: true, silent: true, popup: false, priority: 100,
filter(event) { return Boolean(activeBattle?.session.active &&
event.player?.isAlive() && !isStunned(event.player) && isJaxfruit(event.player) &&
event.player.storage.mengsanJaxfruitIntent_shuying); },
async content(event, trigger) { await executeJaxfruitIntent(trigger.player); },
},
mengsan_strangler_action_shuying: {
trigger: { global: "phaseUseBefore" },
forced: true, silent: true, popup: false, priority: 100,
filter(event) { return Boolean(activeBattle?.session.active &&
event.player?.isAlive() && !isStunned(event.player) && isStrangler(event.player) &&
event.player.storage.mengsanStranglerIntent_shuying); },
async content(event, trigger) { await executeStranglerIntent(trigger.player); },
},
mengsan_leafslime_action_shuying: {
trigger: { global: "phaseUseBefore" },
forced: true, silent: true, popup: false, priority: 100,
filter(event) { return Boolean(activeBattle?.session.active &&
event.player?.isAlive() && !isStunned(event.player) && isLeafslime(event.player) &&
event.player.storage.mengsanLeafslimeIntent_shuying); },
async content(event, trigger) { await executeLeafslimeIntent(trigger.player); },
},
mengsan_leafmedium_action_shuying: {
trigger: { global: "phaseUseBefore" },
forced: true, silent: true, popup: false, priority: 100,
filter(event) { return Boolean(activeBattle?.session.active &&
event.player?.isAlive() && !isStunned(event.player) && isLeafslimeMedium(event.player) &&
event.player.storage.mengsanLeafslimeMediumIntent_shuying); },
async content(event, trigger) { await executeLeafslimeMediumIntent(trigger.player); },
},
mengsan_twigslime_action_shuying: {
trigger: { global: "phaseUseBefore" },
forced: true, silent: true, popup: false, priority: 100,
filter(event) { return Boolean(activeBattle?.session.active &&
event.player?.isAlive() && !isStunned(event.player) && isTwigslime(event.player) &&
event.player.storage.mengsanTwigslimeIntent_shuying); },
async content(event, trigger) { await executeTwigslimeIntent(trigger.player); },
},
mengsan_twigmedium_action_shuying: {
trigger: { global: "phaseUseBefore" },
forced: true, silent: true, popup: false, priority: 100,
filter(event) { return Boolean(activeBattle?.session.active &&
event.player?.isAlive() && !isStunned(event.player) && isTwigmedium(event.player) &&
event.player.storage.mengsanTwigmediumIntent_shuying); },
async content(event, trigger) { await executeTwigmediumIntent(trigger.player); },
},
mengsan_shrinker_action_shuying: {
trigger: { global: "phaseUseBefore" },
forced: true, silent: true, popup: false, priority: 100,
filter(event) { return Boolean(activeBattle?.session.active &&
event.player?.isAlive() && !isStunned(event.player) && isShrinker(event.player) &&
event.player.storage.mengsanShrinkerIntent_shuying); },
async content(event, trigger) { await executeShrinkerIntent(trigger.player); },
},
mengsan_vine_action_shuying: {
trigger: { global: "phaseUseBefore" },
forced: true, silent: true, popup: false, priority: 100,
filter(event) { return Boolean(activeBattle?.session.active &&
event.player?.isAlive() && !isStunned(event.player) && isVine(event.player) &&
event.player.storage.mengsanVineIntent_shuying); },
async content(event, trigger) { await executeVineIntent(trigger.player); },
},
mengsan_cubex_action_shuying: {
trigger: { global: "phaseUseBefore" },
forced: true, silent: true, popup: false, priority: 100,
filter(event) { return Boolean(activeBattle?.session.active &&
event.player?.isAlive() && !isStunned(event.player) && isCubex(event.player) &&
event.player.storage.mengsanCubexIntent_shuying); },
async content(event, trigger) { await executeCubexIntent(trigger.player); },
},
mengsan_byrdonis_action_shuying: {
trigger: { global: "phaseUseBefore" },
forced: true, silent: true, popup: false, priority: 100,
filter(event) { return Boolean(activeBattle?.session.active &&
event.player?.isAlive() && !isStunned(event.player) && isByrdonis(event.player) &&
event.player.storage.mengsanByrdonisIntent_shuying); },
async content(event, trigger) { await executeByrdonisIntent(trigger.player); },
},
mengsan_vantom_action_shuying: {
trigger: { global: "phaseUseBefore" },
forced: true, silent: true, popup: false, priority: 100,
filter(event) { return Boolean(activeBattle?.session.active &&
event.player?.isAlive() && !isStunned(event.player) && isVantom(event.player) &&
event.player.storage.mengsanVantomIntent_shuying); },
async content(event, trigger) { await executeVantomIntent(trigger.player); },
},
mengsan_beast_action_shuying: {
trigger: { global: "phaseUseBefore" },
forced: true, silent: true, popup: false, priority: 100,
filter(event) { return Boolean(activeBattle?.session.active &&
event.player?.isAlive() && !isStunned(event.player) && isBeast(event.player) &&
event.player.storage.mengsanBeastIntent_shuying); },
async content(event, trigger) { await executeBeastIntent(trigger.player); },
},
mengsan_kin_action_shuying: {
trigger: { global: "phaseUseBefore" },
forced: true, silent: true, popup: false, priority: 100,
filter(event) { return Boolean(activeBattle?.session.active &&
event.player?.isAlive() && !isStunned(event.player) && isKinActor(event.player) &&
event.player.storage.mengsanKinIntent_shuying); },
async content(event, trigger) { await executeKinIntent(trigger.player, trigger); },
},
mengsan_kin_orphan_shuying: {
trigger: { global: "phaseBefore" },
forced: true, silent: true, popup: false, priority: 1000,
filter(event) { return Boolean(activeBattle?.session.active && event.player?.isAlive() &&
isKinFollower(event.player) && !canActKin(game, activeBattle, event.player)); },
async content(event, trigger) { trigger.cancel(); },
},
mengsan_kin_minion_shuying: {
mark: true, marktext: "爪",
intro: { content: "爪牙:击败所属同族神官即可获胜,不需要击败信徒;神官死亡后不再行动。" },
},
mengsan_phrog_action_shuying: {
trigger: { global: "phaseUseBefore" },
forced: true, silent: true, popup: false, priority: 100,
filter(event) { return Boolean(activeBattle?.session.active &&
event.player?.isAlive() && !isStunned(event.player) && !event.player?.storage?.mengsanPhrogState_shuying?.spawned && isPhrogActor(event.player) &&
event.player.storage.mengsanPhrogIntent_shuying); },
async content(event, trigger) { await executePhrogIntent(trigger.player); },
},
mengsan_effigy_action_shuying: {
trigger: { global: "phaseUseBefore" },
forced: true, silent: true, popup: false, priority: 100,
filter(event) { return Boolean(activeBattle?.session.active &&
event.player?.isAlive() && !isStunned(event.player) && isEffigy(event.player) &&
event.player.storage.mengsanEffigyIntent_shuying); },
async content(event, trigger) { await executeEffigyIntent(trigger.player, trigger); },
},
mengsan_nibbit_action_shuying: {
trigger: { global: "phaseUseBefore" },
forced: true, silent: true, popup: false, priority: 100,
filter(event) { return Boolean(activeBattle?.session.active &&
event.player?.isAlive() && !isStunned(event.player) && isNibbit(event.player) &&
event.player.storage.mengsanNibbitIntent_shuying); },
async content(event, trigger) { await executeNibbitIntent(trigger.player); },
},
mengsan_mawler_action_shuying: {
trigger: { global: "phaseUseBefore" },
forced: true, silent: true, popup: false, priority: 100,
filter(event) { return Boolean(activeBattle?.session.active &&
event.player?.isAlive() && !isStunned(event.player) && isMawler(event.player) &&
event.player.storage.mengsanMawlerIntent_shuying); },
async content(event, trigger) { await executeMawlerIntent(trigger.player); },
},
mengsan_fogmog_action_shuying: {
trigger: { global: "phaseUseBefore" },
forced: true, silent: true, popup: false, priority: 100,
filter(event) { return Boolean(activeBattle?.session.active &&
!isStunned(event.player) && isFogmogActor(event.player)); },
async content(event, trigger) {
await executeFogmogIntent(trigger.player);
if (isToothedEye(trigger.player)) trigger.cancel();
},
},
mengsan_fogmog_illusion_shuying: {
mark: true, marktext: "幻",
intro: { content: "爪牙:雾菇存活时,死亡后的下一轮以6点生命复活;不单独阻止战斗胜利。" },
},
mengsan_bond_action_shuying: {
trigger: { global: "phaseUseBefore" },
forced: true, silent: true, popup: false, priority: 100,
filter(event) {
return Boolean(activeBattle?.session.active &&
!isStunned(event.player) &&
event.player.storage?.mengsanBond_shuying?.intent);
},
async content(event, trigger) {
await executeBondIntent(trigger.player);
},
},
mengsan_setup_strength_shuying: {
trigger: { source: "damageBegin1", global: ["phaseAfter", "dieAfter"] },
forced: true, silent: true, popup: false, priority: 85, forceDie: true, forceOut: true,
mark: true, marktext: "力",
intro: { content(storage, player) { return `临时力量:攻击伤害增加${player.storage.mengsanSetupStrength_shuying || 0}点`; } },
onremove(player) { forgetTemporaryStrength(player); },
filter(event, player) {
if (event.name === "damage") {
return Boolean(temporaryStrengthAmount(player, activeBattle) > 0 &&
(event.card || event.mengsanAttack_shuying) &&
!event.mengsanScriptedSkill_shuying && !event.mengsanSetupStrengthApplied_shuying);
}
return event.name === "die" ? event.player === player &&
Boolean(player.storage.mengsanSetupStrength_shuying) : temporaryStrengthExpires(player, event);
},
async content(event, trigger, player) {
if (event.triggername === "damageBegin1") {
trigger.num += temporaryStrengthAmount(player, activeBattle);
trigger.mengsanSetupStrengthApplied_shuying = true;
} else if(trigger.name==="phase")expireTemporaryStrength(player,trigger);
else clearTemporaryStrength(player);
},
},
mengsan_raider_card_strength_shuying: {
trigger: { source: "damageBegin1" },
forced: true, silent: true, popup: false, priority: 90,
filter(event, player) { return Boolean(activeBattle?.session.active &&
(isRaider(player) || isFogmog(player) || isCrawler(player) || isInklet(player) || isJaxfruit(player) || isStrangler(player) || isLeafslime(player) || isLeafslimeMedium(player) || isTwigslime(player) || isTwigmedium(player) || isShrinker(player) || isVine(player) || isNibbit(player) || isCubex(player) || isByrdonis(player) || isEffigy(player) || isPhrogActor(player) || isVantom(player) || isKinActor(player) || isBeast(player) && !isStunned(player) || player.storage.mengsanBond_shuying) &&
(player.storage.mengsanStrength_shuying > 0 || sharedStrengthPenalty(player)>0) &&
event.card && !event.mengsanScriptedSkill_shuying &&
!event.mengsanCardStrengthApplied_shuying); },
async content(event, trigger, player) {
trigger.num = Math.max(0,trigger.num+(player.storage.mengsanStrength_shuying||0)-sharedStrengthPenalty(player));
},
},
mengsan_plow_shuying: {
mark: true, marktext: "撞",
intro: { content(storage, player) { return `横冲直撞${player.storage.mengsanPlow_shuying || 0}层:受到伤害后生命不高于此值,击晕、清零力量并进入第二阶段。`; } },
onremove(player) { delete player.storage.mengsanPlow_shuying; },
trigger: { player: "damageEnd" }, forced: true, silent: true, popup: false, priority: 1000,
filter(event, player) { return canBreakPlow(player, activeBattle, event); },
async content(event, trigger, player) { breakPlow(game, activeBattle, player, trigger); },
},
mengsan_ringing_shuying: {
mark: true, marktext: "眩",
intro: { content: "昏眩:本次自身回合至多主动使用1张牌,回合结束解除;不限制响应。不可叠加。" },
onremove(player) { clearRinging(player); },
mod: {
cardEnabled(card, player) { if (activeBattle?.session.active && ringingLimited(player, _status.event)) return false; },
cardEnabled2(card, player) { if (activeBattle?.session.active && ringingLimited(player, _status.event)) return false; },
},
trigger: { player: ["useCard0", "useCard1", "phaseBefore", "phaseAfter", "dieAfter"] },
forced: true, silent: true, popup: false, firstDo: true, priority: 11000,
filter(event, player) { return Boolean(activeBattle?.session.active && player.storage.mengsanRinging_shuying &&
(event.name !== "useCard" || isActiveCardUse(event, player))); },
async content(event, trigger, player) {
if (event.triggername === "useCard0") { if (ringingLimited(player, trigger)) trigger.cancel(); }
else if (event.triggername === "useCard1") countRingingCard(player, activeBattle, trigger);
else if (event.triggername === "phaseBefore") player.storage.mengsanRingingUsed_shuying = 0;
else { player.removeSkill("mengsan_ringing_shuying"); activeBattle?.handUI?.refresh(); }
},
},
mengsan_raider_strength_shuying: {
mark: true, marktext: "力",
intro: { content(storage, player) { return `攻击伤害增加${player.storage.mengsanStrength_shuying || 0}点`; } },
onremove(player) { delete player.storage.mengsanStrength_shuying; },
},
mengsan_stun_skip_shuying: {
trigger: { global: "phaseUseBefore" },
forced: true, silent: true, popup: false, priority: 200,
filter(event) {
return Boolean(activeBattle?.session.active &&
isStunned(event.player));
},
async content(event, trigger) {
skipStunnedAction(trigger.player, trigger);
},
},
mengsan_stun_recover_shuying: {
trigger: { global: "phaseAfter" },
forced: true, silent: true, popup: false, priority: 200,
filter(event) {
return Boolean(activeBattle?.session.active &&
isStunned(event.player));
},
async content(event, trigger) {
await finishStunnedTurn(trigger.player);
},
},
mengsan_stun_clear_shuying: {
trigger: { global: "dieAfter" },
forced: true, silent: true, popup: false,
filter(event) { return isStunned(event.player); },
async content(event, trigger) {
clearStun(trigger.player);
},
},
mengsan_death_blow_finish_shuying: {
trigger: { global: "phaseUseAfter" },
forced: true, silent: true, popup: false,
filter(event) {
return Boolean(activeBattle?.session.active &&
hasPendingDeathBlow(event.player));
},
async content(event, trigger) {
await finishDeathBlow(trigger.player);
},
},
mengsan_frail_shuying: {
mark: true, marktext: "脆",
onremove(player) { delete player.storage.mengsanFrail_shuying; },
intro: { content(storage, player) { return `剩余${player.storage.mengsanFrail_shuying || 0}回合:获得护甲减少25%(向下取整)`; } },
trigger: { player: "phaseAfter" },
forced: true, silent: true, popup: false,
async content(event, trigger, player) {
const remaining = Math.max(0, (player.storage.mengsanFrail_shuying || 0) - 1);
player.storage.mengsanFrail_shuying = remaining;
if (remaining) player.markSkill("mengsan_frail_shuying");
else player.removeSkill("mengsan_frail_shuying");
},
},
mengsan_constrict_shuying: {
mark: true, marktext: "缠",
intro: { content(storage, player) { return `紧缠${player.storage.mengsanConstrict_shuying || 0}层:自身回合结束时受到等量非攻击伤害,来源死亡解除。`; } },
onremove(player) { delete player.storage.mengsanConstrict_shuying; activeBattle?.stranglerConstrict?.delete(player); },
trigger: { player: "phaseJieshuBegin" },
forced: true, silent: true, popup: false,
filter(event, player) { return constrictTotal(activeBattle, player) > 0; },
async content(event, trigger, player) { await resolveConstrict(activeBattle, player); },
},
mengsan_slow_shuying: {
mark: true, marktext: "缓",
intro: {
markcount(storage, player) { return slowPercent(player); },
content(storage, player) { return `缓慢${player.storage.mengsanSlow_shuying || 0}层:本回合攻击伤害增加${slowPercent(player)}%。友方每使用一张牌增加10%,换回合清零。`; },
},
onremove(player) { delete player.storage.mengsanSlow_shuying; delete player.storage.mengsanSlowCards_shuying; },
trigger: { player: "damageBegin3", global: ["useCardAfter", "phaseBefore", "phaseAfter"] },
forced: true, silent: true, popup: false, priority: 20,
filter(event, player) {
if (!activeBattle?.session.active || !isEffigy(player) || !player.isAlive() || !player.storage.mengsanSlow_shuying) return false;
if (event.name === "damage") return slowApplies(player, event);
if (event.name === "useCard") return canCountSlowCard(player, activeBattle, event, _status.currentPhase);
return event.name === "phase";
},
async content(event, trigger, player) {
if (event.triggername === "damageBegin3") {
trigger.num = slowAttackDamage(trigger.num, player);
trigger.mengsanSlowApplied_shuying = true;
} else if (event.triggername === "useCardAfter") recordSlowCard(player, activeBattle, trigger, _status.currentPhase);
else resetSlow(player);
},
},
mengsan_territorial_shuying: {
mark: true, marktext: "领",
intro: { content(storage, player) { return `多尼斯异鸟的领地意识${player.storage.mengsanTerritorial_shuying || 0}层:自身回合结束时获得等量力量。`; } },
onremove(player) { delete player.storage.mengsanTerritorial_shuying; },
trigger: { player: "phaseAfter" },
forced: true, silent: true, popup: false,
filter(event, player) { return Boolean(activeBattle?.session.active && isByrdonis(player) &&
event.player === player && player.isAlive() && player.storage.mengsanTerritorial_shuying > 0); },
async content(event, trigger, player) { resolveTerritorial(player, activeBattle); },
},
mengsan_artifact_shuying: {
mark: true, marktext: "制",
intro: { content(storage, player) { return `人工制品${player.storage.mengsanArtifact_shuying || 0}层:每层抵消一次梦三负面状态施加,不抵消伤害。`; } },
onremove(player) { delete player.storage.mengsanArtifact_shuying; },
},
mengsan_tangled_shuying: {
mark: true, marktext: "藤",
intro: { content(storage, player) { return `剩余${player.storage.mengsanTangled_shuying || 0}回合:攻击牌费用增加1(持续回合可叠加,加费不叠加)。`; } },
onremove(player) { delete player.storage.mengsanTangled_shuying; activeBattle?.tangledTargets?.delete(player); if (activeBattle?.session.active) activeBattle.handUI?.refresh(); },
trigger: { player: "phaseAfter" },
forced: true, silent: true, popup: false,
filter(event, player) { return Boolean(activeBattle?.session.active && player.storage.mengsanTangled_shuying > 0); },
async content(event, trigger, player) { advanceTangled(activeBattle, player); },
},
mengsan_shrink_shuying: {
mark: true, marktext: "缩",
intro: { content: "攻击伤害减少30%(向下取整),不重复叠加;来源死亡解除。" },
onremove(player) { delete player.storage.mengsanShrink_shuying; activeBattle?.shrinkerSources?.delete(player); },
trigger: { source: "damageBegin1" },
forced: true, silent: true, popup: false, priority: 70,
filter(event, player) {
return Boolean(activeBattle?.session.active && !event.mengsanScriptedSkill_shuying &&
!event.mengsanShrinkApplied_shuying && (event.card || event.mengsanAttack_shuying) &&
isShrunk(activeBattle, player));
},
async content(event, trigger, player) {
trigger.num = shrinkAttackDamage(trigger.num, player);
trigger.mengsanShrinkApplied_shuying = true;
},
},
mengsan_vulnerable_shuying: {
mark: true, marktext: "易",
onremove(player) { delete player.storage.mengsanVulnerable_shuying; },
intro: { content(storage, player) { return `剩余${player.storage.mengsanVulnerable_shuying || 0}回合:受到攻击伤害增加50%(向上取整)`; } },
trigger: { player: ["damageBegin1", "phaseAfter"] },
forced: true, silent: true, popup: false,
filter(event, player) {
if (!player.storage?.mengsanVulnerable_shuying) return false;
return event.name !== "damage" || Boolean(event.card || event.mengsanAttack_shuying);
},
async content(event, trigger, player) {
if (event.triggername === "damageBegin1") {
trigger.num = Math.ceil(trigger.num * (1.5 + cardPacks.vulnerableBonus(trigger.source, player)));
return;
}
const remaining = Math.max(0, (player.storage.mengsanVulnerable_shuying || 0) - 1);
player.storage.mengsanVulnerable_shuying = remaining;
if (remaining) player.markSkill("mengsan_vulnerable_shuying");
else player.removeSkill("mengsan_vulnerable_shuying");
},
},
mengsan_scenario_shuying: {
trigger: { global: ["changeHpAfter", "gainAfter", "loseAfter", "dieAfter", "turnOverAfter", "linkAfter", "phaseAfter"] },
forced: true, silent: true, popup: false, priority: -100,
filter() { return Boolean(activeBattle?.session.active && activeBattle.director?.ready); },
async content(event, trigger) {
if (event.triggername === "phaseAfter") await activeBattle.director.afterTurn(trigger.player, trigger);
else await activeBattle.director.evaluate("state");
game.checkResult();
},
},
mengsan_draw_shuying: {
trigger: { player: "drawBegin" },
firstDo: true,
forced: true,
silent: true,
priority: 10000,
filter(event, player) {
return Boolean(event.num > 0 && (player.storage?.mengsanPlayer_shuying || ironcladBlocksDraw(player)));
},
async content(event, trigger, player) {
const number = trigger.num;
trigger.cancel();
const cards=ironcladBlocksDraw(player)?[]:await game.mengsanDraw_shuying(player, number);
trigger.result={bool:true,cards};trigger.mengsanIcDrawHandled_shuying=true;
},
},
mengsan_card_use_shuying: {
trigger: { global: ["useCard1", "respond"] },
forced: true,
silent: true,
popup: false,
filter(event) {
return Boolean(activeBattle?.session.active && event.cards?.some(card => card.storage?.mengsanCard_shuying));
},
async content(event, trigger) {
trigger.cards.forEach(card => {
if (card.storage?.mengsanCard_shuying) card.storage.mengsanConsumed_shuying = true;
});
},
},
mengsan_infested_shuying: {
mark: true, marktext: "寄",
intro: { markcount(storage, player) { return player.storage.mengsanInfested_shuying || 0; },
content: "死亡后立即召唤4只扭动虫,扭动虫出场的首回合跳过;须消灭扭动虫才能获胜。" },
onremove(player) { delete player.storage.mengsanInfested_shuying; },
},
mengsan_wriggler_spawned_shuying: {
trigger: { global: "phaseBefore" },
forced: true, silent: true, popup: false, priority: 1000,
filter(event) { return Boolean(activeBattle?.session.active && isWriggler(event.player) && event.player.storage.mengsanPhrogState_shuying?.spawned); },
async content(event, trigger) {
if (skipSpawnedWriggler(trigger.player, trigger)) {
clearStun(trigger.player);
game.mengsanSetEnemyIntent_shuying(trigger.player, null);
game.log(trigger.player, "生成眩晕,跳过首回合");
}
},
},
mengsan_infection_damage_shuying: {
trigger: { global: "phaseDiscardBegin" },
forced: true, silent: true, popup: false, priority: 200,
filter(event) { return Boolean(activeBattle?.session.active && event.player?.isAlive() && infectionHand(event.player).length); },
async content(event, trigger) { await resolveInfection(activeBattle, trigger.player, trigger); },
},
mengsan_card_affixes_shuying: {
forced: true, silent: true, popup: false, priority: 100,
trigger: { global: ["phaseDiscardBegin", "cardsDiscardAfter"] },
filter(event) {
if (!activeBattle?.session.active) return false;
if (event.name === "phaseDiscard") return event.player?.getCards("h").some(card => hasCardAffix(card,"void"));
return event.cards?.some(card => card.storage?.mengsanCard_shuying?.affixes?.includes("ingenious"));
},
async content(event, trigger) {
if (event.triggername === "phaseDiscardBegin") {
const cards = trigger.player.getCards("h").filter(card => hasCardAffix(card,"void"));
if (cards.length) {
await trigger.player.lose(cards, ui.special);
for (const card of cards) card.remove();
activeBattle.pilesUI?.refresh();
}
return;
}
if (trigger.getParent("phaseDiscard")) return;
for (const card of trigger.cards || []) {
if (!activeBattle?.session.active || get.position(card, true) !== "d" || !card.storage?.mengsanCard_shuying?.affixes?.includes("ingenious") || card.storage.mengsanIngeniousResolving_shuying) continue;
const use = trigger.getParent("useCard"), respond = trigger.getParent("respond");
if (use?.cards?.includes(card) || respond?.cards?.includes(card)) continue;
const owner = game.playerMap[card.storage.mengsanOwnerId_shuying];
if (!owner?.isAlive() || !lib.filter.cardEnabled(card, owner) || !canPayCard(owner, card) || !owner.hasUseTarget(card)) continue;
card.storage.mengsanIngeniousResolving_shuying = true;
try { await owner.chooseUseTarget({ card, cards: [card], forced: true, prompt: "奇巧:使用此牌" }); }
finally { card.storage.mengsanIngeniousResolving_shuying = false; }
}
},
mod: {
ignoredHandcard(card) { if (hasCardAffix(card,"retain")) return true; },
cardEnabled2(card) { if (hasCardAffix(card,"unplayable")) return false; },
},
},
mengsan_yingyong_shuying: {
trigger: { source: "damageBegin1" },
forced: true,
usable: 1,
filter(event, player) {
return Boolean(event.card && _status.currentPhase == player);
},
async content(event, trigger) {
trigger.num++;
},
},
},
translate: {
...(identityMode.translate || {}),
...modeCards.translate,
...scenarioTranslations,
mengsan_draw_shuying: "梦三牌组",
mengsan_card_use_shuying: "梦三词缀",
mengsan_yingyong_shuying: "英勇",
mengsan_yingyong_shuying_info: "锁定技,每回合限一次,你于自己的回合内使用牌造成的伤害+1。",
mengsan_flyconid_action_shuying: "孢子行动",
mengsan_raider_action_shuying: "劫掠行动",
mengsan_fogmog_action_shuying: "雾菇行动",
mengsan_mawler_action_shuying: "蛮兽行动",
mengsan_vine_action_shuying: "藤蔓蹒跚者行动",
mengsan_nibbit_action_shuying: "小啃兽行动",
mengsan_cubex_action_shuying: "立柱构造体行动",
mengsan_byrdonis_action_shuying: "多尼斯异鸟行动",
mengsan_effigy_action_shuying: "旧日雕像行动",
mengsan_vantom_action_shuying: "墨影幻灵行动",
mengsan_beast_action_shuying: "仪式兽行动",
mengsan_plow_shuying: "横冲直撞",
mengsan_ringing_shuying: "昏眩",
mengsan_kin_action_shuying: "同族行动",
mengsan_kin_orphan_shuying: "爪牙退场",
mengsan_kin_minion_shuying: "爪牙",
mengsan_phrog_action_shuying: "异蛙寄生行动",
mengsan_wriggler_spawned_shuying: "生成眩晕",
mengsan_infection_damage_shuying: "感染结算",
mengsan_infested_shuying: "寄生物",
mengsan_slow_shuying: "缓慢",
mengsan_slow_shuying_info: "本回合友方每使用一张牌,旧日雕像受到的攻击伤害增加10%,回合结束清零。",
mengsan_territorial_shuying: "领地意识",
mengsan_territorial_shuying_info: "自身回合结束时,每层领地意识获得1点力量。",
mengsan_shrinker_action_shuying: "缩小甲虫行动",
mengsan_twigmedium_action_shuying: "树枝史莱姆(中)行动",
mengsan_twigslime_action_shuying: "树枝史莱姆(小)行动",
mengsan_leafmedium_action_shuying: "树叶史莱姆(中)行动",
mengsan_leafslime_action_shuying: "树叶史莱姆(小)行动",
mengsan_strangler_action_shuying: "蛇行扼杀者行动",
mengsan_jaxfruit_action_shuying: "闪光贾克斯果行动",
mengsan_crawler_action_shuying: "毛绒伏地虫行动",
mengsan_inklet_action_shuying: "墨宝行动",
mengsan_slippery_shuying: "滑溜",
mengsan_slippery_shuying_info: "下一次实际失去生命时只失去1点生命;完全格挡不消耗。",
mengsan_fogmog_illusion_shuying: "幻象·爪牙",
mengsan_weak_shuying: "虚弱",
mengsan_weak_shuying_info:
"攻击伤害减少25%(向下取整),自身回合结束减少1层。",
mengsan_relic_attack_shuying: "遗物攻击修正",
mengsan_setup_strength_shuying: "临时力量",
mengsan_setup_strength_shuying_info: "本回合内增加攻击伤害,回合结束时移除。",
mengsan_raider_card_strength_shuying: "力量加成",
mengsan_raider_strength_shuying: "力量",
mengsan_raider_strength_shuying_info: "攻击造成的伤害按力量层数增加。",
mengsan_frail_shuying: "脆弱",
mengsan_frail_shuying_info: "接下来相应回合获得护甲减少25%(向下取整)。",
mengsan_constrict_shuying: "紧缠",
mengsan_constrict_shuying_info: "自身回合结束受到层数对应的非攻击伤害;可叠加,来源死亡解除。",
mengsan_artifact_shuying: "人工制品",
mengsan_artifact_shuying_info: "每层抵消一次梦三负面状态施加,不抵消伤害、正面增益或既有状态的自然结算。",
mengsan_tangled_shuying: "缠结",
mengsan_tangled_shuying_info: "攻击牌费用增加1,持续对应回合,自身回合结束减少1回合;不影响响应。",
mengsan_shrink_shuying: "缩小",
mengsan_shrink_shuying_info: "攻击伤害减少30%(向下取整),不重复叠加;来源死亡解除。",
mengsan_vulnerable_shuying: "易伤",
mengsan_vulnerable_shuying_info: "接下来相应回合受到的攻击伤害增加50%(向上取整)。",
},
config: {},
help: {
梦三: "梦三:玩家、敌方怪物和羁绊助战各有独立牌堆与弃牌堆,其他友方支援使用公共牌堆。主动出牌消耗费用,转化牌按转化后牌名计费;响应不耗费。DIY须在技能所属角色事件内访问牌堆,不可缓存公共牌堆节点。",
},
};
};
export async function createMengsanMode() {
if (!lib.group.includes("han")) {
game.addGroup("han", "汉", "汉", { color: [
[255, 252, 250, 1], [255, 241, 238, 1],
[245, 196, 193, 1], [192, 85, 91, 1],
] });
}
await new Promise(resolve => {
const style = lib.init.css(`${STYLE_PATH}/ui`, "style", resolve);
style.addEventListener("error", resolve, { once: true });
});
const identityMode = await game.loadModeAsync("identity");
const mode = createMode(identityMode);
mode.splash = "ext:术樱包/pve/images/tianshu.jpg";
return mode;
}
export default async function initMengsan() {
if (lib.config.all.mode.includes(MODE_ID)) return;
const mode = await createMengsanMode();
game.addMode(MODE_ID, mode, {
translate: "梦三",
config: {},
extension: "术樱包",
});
}