fixed # 补全部分卡牌

fixed # 修复部分已知BUG
This commit is contained in:
2026-10-04 19:32:38 +08:00
parent c448da39c2
commit 94a2a6724b
163 changed files with 14008 additions and 1537 deletions
+83
View File
@@ -11,6 +11,10 @@ import { isMawler, selectMawlerMove, recordMawlerAction } from "./mawler-intent.
import { isVine, selectVineMove, recordVineAction } from "./vine-intent.js";
import { isCubex, selectCubexMove, recordCubexAction } from "./cubex-intent.js";
import { isByrdonis, selectByrdonisMove, recordByrdonisAction } from "./byrdonis-intent.js";
import { isVantom, selectVantomMove, recordVantomAction } from "./vantom-intent.js";
import { isBeast, selectBeastMove, recordBeastAction, gainPlow, applyRinging } from "./beast-intent.js";
import { isKinActor, isKinPriest, canActKin, selectKinPriestMove, selectKinFollowerMove, recordKinAction } from "./kin-intent.js";
import { applyWound } from "../cards/wound-card.js";
import { isEffigy, selectEffigyMove, recordEffigyAction } from "./effigy-intent.js";
import { isPhrog, isPhrogActor, selectPhrogMove, selectWrigglerMove, recordPhrogAction } from "./phrog-intent.js";
import { applyInfection } from "../cards/infection-card.js";
@@ -112,6 +116,21 @@ export function createMonsterIntentActions(game, getActiveBattle) {
const move = (isPhrog(player) ? selectPhrogMove : selectWrigglerMove)(player.storage.mengsanPhrogState_shuying || {});
player.storage.mengsanPhrogIntent_shuying = move;
game.mengsanSetEnemyIntent_shuying(player, move);
} else if (isKinActor(player)) {
if (!canActKin(game, getActiveBattle(), player) || player.storage.mengsanKinIntent_shuying) return;
const move = (isKinPriest(player) ? selectKinPriestMove : selectKinFollowerMove)(player.storage.mengsanKinState_shuying || {});
player.storage.mengsanKinIntent_shuying = move;
game.mengsanSetEnemyIntent_shuying(player, move);
} else if (isBeast(player)) {
if (!getActiveBattle()?.session.active || player.storage.mengsanBeastIntent_shuying) return;
const move = selectBeastMove(player.storage.mengsanBeastState_shuying || {});
player.storage.mengsanBeastIntent_shuying = move;
game.mengsanSetEnemyIntent_shuying(player, move);
} else if (isVantom(player)) {
if (!getActiveBattle()?.session.active || player.storage.mengsanVantomIntent_shuying) return;
const move = selectVantomMove(player.storage.mengsanVantomState_shuying || {});
player.storage.mengsanVantomIntent_shuying = move;
game.mengsanSetEnemyIntent_shuying(player, move);
} else if (isByrdonis(player)) {
if (!getActiveBattle()?.session.active || player.storage.mengsanByrdonisIntent_shuying) return;
const move = selectByrdonisMove(player.storage.mengsanByrdonisState_shuying || {});
@@ -412,6 +431,67 @@ export function createMonsterIntentActions(game, getActiveBattle) {
await executeEffects(player, move);
};
const executeKinIntent = async (player, phase) => {
const current = getActiveBattle();
if (!isKinActor(player) || !player.isAlive() || isStunned(player) || !current?.session.active) return;
if (!canActKin(game, current, player)) { phase?.cancel(); return; }
const move = player.storage.mengsanKinIntent_shuying;
if (!move) return;
player.storage.mengsanKinIntent_shuying = null;
game.mengsanSetEnemyIntent_shuying(player, null);
player.storage.mengsanKinState_shuying =
recordKinAction(player.storage.mengsanKinState_shuying || {});
if (player.storage.mengsanEnergy_shuying < 1) {
game.log(player, "费用不足,未发动", move.name); return;
}
player.storage.mengsanEnergy_shuying--;
current.energyUI.get(player)?.();
game.log(player, "消耗1费用发动", move.name);
await createIntentExecutor(game, () => getActiveBattle() === current && canActKin(game, current, player) ? current : null)(player, move);
};
const executeBeastIntent = async player => {
const current = getActiveBattle();
if (!isBeast(player) || !player.isAlive() || isStunned(player) || !current?.session.active) return;
const move = player.storage.mengsanBeastIntent_shuying;
if (!move) return;
player.storage.mengsanBeastIntent_shuying = null;
game.mengsanSetEnemyIntent_shuying(player, null);
player.storage.mengsanBeastState_shuying =
recordBeastAction(player.storage.mengsanBeastState_shuying || {});
if (player.storage.mengsanEnergy_shuying < 1) {
game.log(player, "费用不足,未发动", move.name); return;
}
player.storage.mengsanEnergy_shuying--;
current.energyUI.get(player)?.();
game.log(player, "消耗1费用发动", move.name);
const revision = player.storage.mengsanBeastState_shuying.revision;
const valid = () => getActiveBattle() === current && current.session.active && player.isAlive() &&
!isStunned(player) && player.storage.mengsanBeastState_shuying?.revision === revision;
if (move.plow) gainPlow(player);
else if (move.ringing) { for (const target of getIntentHostiles(game, player)) { if (!valid()) break; applyRinging(current, target); } }
else await createIntentExecutor(game, () => valid() ? current : null)(player, move);
};
const executeVantomIntent = async player => {
const current = getActiveBattle();
if (!isVantom(player) || !player.isAlive() || isStunned(player) || !current?.session.active) return;
const move = player.storage.mengsanVantomIntent_shuying;
if (!move) return;
player.storage.mengsanVantomIntent_shuying = null;
game.mengsanSetEnemyIntent_shuying(player, null);
player.storage.mengsanVantomState_shuying =
recordVantomAction(player.storage.mengsanVantomState_shuying || {});
if (player.storage.mengsanEnergy_shuying < 1) {
game.log(player, "费用不足,未发动", move.name); return;
}
player.storage.mengsanEnergy_shuying--;
current.energyUI.get(player)?.();
game.log(player, "消耗1费用发动", move.name);
await createIntentExecutor(game, () => getActiveBattle() === current ? current : null)(player, move);
if (getActiveBattle() === current && move.wound) applyWound(game, current, player, move.wound);
};
const executeByrdonisIntent = async player => {
const current = getActiveBattle();
if (!isByrdonis(player) || !player.isAlive() || isStunned(player) || !current?.session.active) return;
@@ -508,6 +588,9 @@ export function createMonsterIntentActions(game, getActiveBattle) {
executeVineIntent,
executeCubexIntent,
executeByrdonisIntent,
executeVantomIntent,
executeBeastIntent,
executeKinIntent,
executeEffigyIntent,
executePhrogIntent,
executeNibbitIntent,
+5 -2
View File
@@ -1,3 +1,4 @@
import { afterIroncladVulnerable } from "../cards/ironclad-hooks.js";
const KEY = "mengsanArtifact_shuying", SKILL = "mengsan_artifact_shuying";
const bindings = new WeakMap();
const DEBUFFS = Object.freeze({
@@ -29,11 +30,13 @@ export function consumeArtifact(player) {
return true;
}
// 一次施加抵消一次,不按负面层数逐层消耗;伤害和正面增益不走此入口。
export function applyMengsanDebuff(player, kind, amount) {
export function applyMengsanDebuff(player, kind, amount, source = null) {
if (!Object.hasOwn(DEBUFFS, kind) || !Number.isSafeInteger(amount) || amount < 0) throw new RangeError("负面状态参数无效");
if (!amount || !player?.isAlive()) return false;
const [key, skill] = DEBUFFS[kind], old = player.storage[key] ?? 0;
if (!Number.isSafeInteger(old) || old < 0 || amount > Number.MAX_SAFE_INTEGER - old) throw new RangeError("负面状态层数越界");
if (consumeArtifact(player)) return false;
player.storage[key] = old + amount; player.addSkill(skill); player.markSkill(skill); return true;
player.storage[key] = old + amount; player.addSkill(skill); player.markSkill(skill);
if(kind === "vulnerable" && source)afterIroncladVulnerable(source,player);
return true;
}
+94
View File
@@ -0,0 +1,94 @@
import { consumeArtifact } from "./artifact-status.js";
import { isActiveCardUse } from "../battle/combat-rules.js";
import { skipStunnedAction } from "../battle/stun-intent.js";
export const BEAST_CHARACTER = "mengsan_ceremonial_beast_shuying";
export const BEAST_MOVES = Object.freeze({
stamp: Object.freeze({ id: "stamp", name: "跺地", plow: 150 }),
plow: Object.freeze({ id: "plow", name: "横冲直撞", damage: 18, strength: 2 }),
cry: Object.freeze({ id: "cry", name: "野兽咆哮", ringing: true }),
stomp: Object.freeze({ id: "stomp", name: "踩踏", damage: 15 }),
crush: Object.freeze({ id: "crush", name: "碾碎", damage: 17, strength: 3 }),
});
export const isBeast = player => player?.name === BEAST_CHARACTER && player.storage?.mengsanCamp_shuying === "enemy";
function checked(state) {
const s = { phase: 1, step: 0, turnsTaken: 0, revision: 0, ...state };
if (![1, 2].includes(s.phase) || !Number.isSafeInteger(s.step) || s.step < 0 || s.step > (s.phase === 1 ? 1 : 2) ||
!Number.isSafeInteger(s.turnsTaken) || s.turnsTaken < 0 || !Number.isSafeInteger(s.revision) || s.revision < 0) throw new RangeError("仪式兽行动状态无效");
return s;
}
export function selectBeastMove(state = {}) {
const s = checked(state);
return s.phase === 1 ? (s.step === 0 ? BEAST_MOVES.stamp : BEAST_MOVES.plow) : [BEAST_MOVES.cry, BEAST_MOVES.stomp, BEAST_MOVES.crush][s.step];
}
export function recordBeastAction(state = {}, advance = true) {
const s = checked(state);
if (typeof advance !== "boolean" || advance && s.turnsTaken === Number.MAX_SAFE_INTEGER) throw new RangeError("仪式兽行动推进无效");
return { ...s, step: advance ? (s.phase === 1 ? 1 : (s.step + 1) % 3) : s.step, turnsTaken: s.turnsTaken + (advance ? 1 : 0) };
}
export function recoverBeastStun(player, choice, original) {
const s = checked(player.storage.mengsanBeastState_shuying);
if (s.justBroken) { player.storage.mengsanBeastState_shuying = { ...s, justBroken: false }; player.storage.mengsanBeastIntent_shuying = null; return null; }
player.storage.mengsanBeastState_shuying = recordBeastAction(s, choice !== "retry");
const next = choice === "retry" ? original : null;
player.storage.mengsanBeastIntent_shuying = next; return next;
}
const bindings = new WeakMap();
export function initializeBeast(player, current) {
if (!isBeast(player)) return false;
if (current?.session.active && bindings.get(player)?.battle !== current) {
const entry = { battle: current };
current.session.ownResource(entry, () => {
if (bindings.get(player) !== entry) return;
bindings.delete(player); delete player.storage.mengsanBeastState_shuying; delete player.storage.mengsanBeastIntent_shuying;
player.removeSkill("mengsan_plow_shuying"); player.removeSkill("mengsan_raider_strength_shuying");
}); bindings.set(player, entry);
}
player.storage.mengsanBeastState_shuying = checked({}); player.storage.mengsanBeastIntent_shuying = null;
player.removeSkill("mengsan_plow_shuying"); player.removeSkill("mengsan_raider_strength_shuying"); return true;
}
export function gainPlow(player) {
if (!isBeast(player) || !player.isAlive()) return false;
const max = player.maxHp;
if (!Number.isSafeInteger(max) || max <= 0 || !Number.isSafeInteger(max * 150)) throw new RangeError("仪式兽血量缩放无效");
player.storage.mengsanPlow_shuying = Math.max(1, Math.round(150 * max / 252));
player.addSkill("mengsan_plow_shuying"); player.markSkill("mengsan_plow_shuying"); return true;
}
export const canBreakPlow = (player, current, damage) => Boolean(current?.session.active && isBeast(player) && player.isAlive() &&
damage?.name === "damage" && damage.player === player && damage.num > 0 && player.storage.mengsanPlow_shuying > 0 &&
player.hp <= player.storage.mengsanPlow_shuying && player.storage.mengsanBeastState_shuying?.phase === 1);
export function breakPlow(game, current, player, damage) {
if (!canBreakPlow(player, current, damage)) return false;
const s = checked(player.storage.mengsanBeastState_shuying);
if (s.revision === Number.MAX_SAFE_INTEGER) throw new RangeError("仪式兽阶段版本溢出");
player.removeSkill("mengsan_plow_shuying"); player.removeSkill("mengsan_raider_strength_shuying");
player.storage.mengsanStrength_shuying = 0;
player.storage.mengsanBeastState_shuying = { ...s, phase: 2, step: 0, revision: s.revision + 1, justBroken: true };
player.storage.mengsanBeastIntent_shuying = null;
game.mengsanStunEnemy_shuying(player, { resume: "retry" });
const phaseUse = damage.getParent?.("phaseUse");
if (phaseUse?.player === player) skipStunnedAction(player, phaseUse);
return true;
}
const ringing = new WeakMap();
export function clearRinging(player) {
ringing.delete(player); delete player.storage.mengsanRinging_shuying; delete player.storage.mengsanRingingUsed_shuying;
}
export function applyRinging(current, player, phasePlayer = null) {
if (!current?.session.active || !player?.isAlive() || !current.players?.has(player)) return false;
if (consumeArtifact(player)) return false;
if (ringing.get(player)?.battle === current && player.storage.mengsanRinging_shuying) return true;
const entry = { battle: current, seen: new WeakSet() };
current.session.ownResource(entry, () => { if (ringing.get(player) !== entry) return; player.removeSkill("mengsan_ringing_shuying"); clearRinging(player); });
ringing.set(player, entry); player.storage.mengsanRinging_shuying = 1;
// Mid-turn applications use already played cards; ordinary Beast Cry arrives before the target's next turn.
player.storage.mengsanRingingUsed_shuying = phasePlayer === player ? (player.getHistory?.("useCard", e => !e.cancelled && !e._cancelled && isActiveCardUse(e, player)).length || 0) : 0;
player.addSkill("mengsan_ringing_shuying"); player.markSkill("mengsan_ringing_shuying"); current.handUI?.refresh(); return true;
}
export const ringingLimited = (player, event) => Boolean(player.storage?.mengsanRinging_shuying &&
player.storage.mengsanRingingUsed_shuying >= 1 && isActiveCardUse(event, player));
export function countRingingCard(player, current, event) {
const entry = ringing.get(player);
if (!current?.session.active || entry?.battle !== current || !player.isAlive() || !player.storage.mengsanRinging_shuying ||
event?.name !== "useCard" || !event.card || event.cancelled || event._cancelled || !isActiveCardUse(event, player) || entry.seen.has(event)) return false;
entry.seen.add(event); player.storage.mengsanRingingUsed_shuying = 1; player.markSkill("mengsan_ringing_shuying"); current.handUI?.refresh(); return true;
}
+2 -1
View File
@@ -1,4 +1,5 @@
// 灰机 Wiki 雾菇页的普通难度:74生命,重击8、头槌14。
import { isKinFollower } from "./kin-intent.js";
export const FOGMOG_CHARACTER = "mengsan_fogmog_shuying";
export const EYE_CHARACTER = "mengsan_eye_with_teeth_shuying";
export const FOGMOG_MOVES = Object.freeze({
@@ -32,4 +33,4 @@ export const isToothedEye = player => player?.name === EYE_CHARACTER &&
export const isFogmogActor = player => isFogmog(player) || isToothedEye(player);
// 爪牙不单独构成胜利目标;雾菇死亡后无需继续杀死或等待其幻象。
export const isEncounterEnemy = player => player?.isAlive() &&
player.storage?.mengsanCamp_shuying === "enemy" && !isToothedEye(player);
player.storage?.mengsanCamp_shuying === "enemy" && !isToothedEye(player) && !isKinFollower(player);
+2 -1
View File
@@ -1,3 +1,4 @@
import { isVantom } from "./vantom-intent.js";
// 灰机 Wiki 墨宝:奇数位刺击起手;偶数位旋风起手后刺击;以后随机招与刺击交替。
export const INKLET_CHARACTER = "mengsan_inklet_shuying";
export const INKLET_MOVES = Object.freeze({
@@ -41,7 +42,7 @@ export const isInklet = player => player?.name === INKLET_CHARACTER &&
// 在梦三的原生changeHp事件创建后限幅;保留护甲消耗量,只将穿透后的生命损失限制为1。
// 层数由实际changeHp通知消耗,完全格挡、零伤害及取消事件不消耗。
export function capInkletHpLoss(player, event) {
if (!isInklet(player) || !(player.storage.mengsanSlippery_shuying > 0) ||
if (!(isInklet(player) || isVantom(player)) || !(player.storage.mengsanSlippery_shuying > 0) ||
!Number.isFinite(event.num) || event.num >= 0) return event;
const parent = event.getParent();
if (parent?.unreal) return event;
+76
View File
@@ -0,0 +1,76 @@
export const KIN_PRIEST_CHARACTER = "mengsan_kin_priest_shuying";
export const KIN_FOLLOWER_CHARACTER = "mengsan_kin_follower_shuying";
export const KIN_PRIEST_MOVES = Object.freeze({
frailty: Object.freeze({ id: "frailty", name: "脆弱法球", damage: 8, debuff: "脆弱", stacks: 1 }),
weakness: Object.freeze({ id: "weakness", name: "虚弱法球", damage: 8, debuff: "虚弱", stacks: 1 }),
beam: Object.freeze({ id: "beam", name: "灵魂光束", damage: 3, hits: 3 }),
ritual: Object.freeze({ id: "ritual", name: "黑暗仪式", strength: 2 }),
});
export const KIN_FOLLOWER_MOVES = Object.freeze({
slash: Object.freeze({ id: "slash", name: "快斩", damage: 5 }),
boomerang: Object.freeze({ id: "boomerang", name: "回旋镖", damage: 2, hits: 2 }),
dance: Object.freeze({ id: "dance", name: "力量之舞", strength: 2 }),
});
const priestCycle = Object.freeze(Object.values(KIN_PRIEST_MOVES));
const followerCycle = Object.freeze(Object.values(KIN_FOLLOWER_MOVES));
const turnsOf = state => {
const turns = state.turnsTaken ?? 0;
if (!Number.isSafeInteger(turns) || turns < 0) throw new RangeError("同族小队行动记录无效");
return turns;
};
export const selectKinPriestMove = (state = {}) => priestCycle[turnsOf(state) % priestCycle.length];
export const selectKinFollowerMove = (state = {}) => followerCycle[turnsOf(state) % followerCycle.length];
export function recordKinAction(state = {}, advance = true) {
const turns = turnsOf(state);
if (typeof advance !== "boolean" || advance && turns === Number.MAX_SAFE_INTEGER) throw new RangeError("同族小队行动推进无效");
return { turnsTaken: turns + (advance ? 1 : 0) };
}
export function rollKinFollowerHp(random) {
const value = random();
if (!Number.isFinite(value) || value < 0 || value >= 1) throw new RangeError("同族信徒随机数必须在[0,1)内");
return 58 + Math.floor(value * 2);
}
export const isKinPriest = player => player?.name === KIN_PRIEST_CHARACTER && player.storage?.mengsanCamp_shuying === "enemy";
export const isKinFollower = player => player?.name === KIN_FOLLOWER_CHARACTER && player.storage?.mengsanCamp_shuying === "enemy";
export const isKinActor = player => isKinPriest(player) || isKinFollower(player);
export function canActKin(game, current, player) {
if (!current?.session.active || !player?.isAlive() || !isKinActor(player)) return false;
if (isKinPriest(player)) return true;
const owner = player.storage.mengsanKinOwner_shuying;
return Boolean(owner && game.players.some(p => p.isAlive() && isKinPriest(p) && p.storage.mengsanUnitId_shuying === owner));
}
const bindings = new WeakMap();
export function initializeKin(player, spec = {}, current = null) {
if (!isKinActor(player)) return false;
if (isKinFollower(player) && spec.kinOwner != null &&
(typeof spec.kinOwner !== "string" || !/^[\w.-]+$/.test(spec.kinOwner))) throw new TypeError("同族信徒的神官ID无效");
if (current?.session.active && bindings.get(player)?.battle !== current) {
const entry = { battle: current };
current.session.ownResource(entry, () => {
if (bindings.get(player) !== entry) return;
bindings.delete(player);
delete player.storage.mengsanKinState_shuying;
delete player.storage.mengsanKinIntent_shuying;
delete player.storage.mengsanKinOwner_shuying;
player.removeSkill("mengsan_kin_minion_shuying");
player.removeSkill("mengsan_raider_strength_shuying");
});
bindings.set(player, entry);
}
player.storage.mengsanKinState_shuying = { turnsTaken: 0 };
player.storage.mengsanKinIntent_shuying = null;
if (isKinFollower(player)) {
player.storage.mengsanKinOwner_shuying = spec.kinOwner ?? null;
player.addSkill("mengsan_kin_minion_shuying");
player.markSkill("mengsan_kin_minion_shuying");
}
return true;
}
export function clearKinOwnerIntents(game, current, priest) {
if (!current?.session.active || !isKinPriest(priest)) return;
for (const player of game.players) {
if (!isKinFollower(player) || player.storage.mengsanKinOwner_shuying !== priest.storage.mengsanUnitId_shuying) continue;
player.storage.mengsanKinIntent_shuying = null;
game.mengsanSetEnemyIntent_shuying(player, null);
}
}
+20
View File
@@ -23,6 +23,9 @@ const HEALTH = Object.freeze({
mengsan_cubex_construct_shuying: 65,
mengsan_byrdonis_shuying: 82,
mengsan_bygone_effigy_shuying: 127,
mengsan_vantom_shuying: 173,
mengsan_ceremonial_beast_shuying: 252,
mengsan_kin_priest_shuying: 190, mengsan_kin_follower_shuying: 59,
mengsan_phrog_parasite_shuying: 62,
mengsan_wriggler_shuying: 19,
mengsan_shrinker_beetle_shuying: 39,
@@ -137,6 +140,20 @@ export class PhrogParasite extends Monster {
export class Wriggler extends Monster {
constructor(options = {}) { super({ ...options, character: "mengsan_wriggler_shuying", tier: "elite", hp: options.hp ?? 19 }); }
}
export class CeremonialBeast extends Monster {
constructor(options = {}) { super({ ...options, character: "mengsan_ceremonial_beast_shuying", tier: "boss", hp: options.hp ?? 252 }); }
}
export class KinPriest extends Monster {
constructor(options = {}) { super({ ...options, character: "mengsan_kin_priest_shuying", tier: "boss", hp: options.hp ?? 190 }); }
}
export class KinFollower extends Monster {
constructor(options = {}) { super({ ...options, character: "mengsan_kin_follower_shuying", tier: "normal", hp: options.hp ?? 59 }); }
}
export class Vantom extends Monster {
constructor(options = {}) {
super({ ...options, character: "mengsan_vantom_shuying", tier: "boss", hp: options.hp ?? 173 });
}
}
export class BygoneEffigy extends Monster {
constructor(options = {}) {
super({ ...options, character: "mengsan_bygone_effigy_shuying", tier: "elite", hp: options.hp ?? 127 });
@@ -195,6 +212,9 @@ export const MONSTER_TYPES = Object.freeze({
mengsan_cubex_construct_shuying: CubexConstruct,
mengsan_byrdonis_shuying: Byrdonis,
mengsan_bygone_effigy_shuying: BygoneEffigy,
mengsan_vantom_shuying: Vantom,
mengsan_ceremonial_beast_shuying: CeremonialBeast,
mengsan_kin_priest_shuying: KinPriest, mengsan_kin_follower_shuying: KinFollower,
mengsan_phrog_parasite_shuying: PhrogParasite,
mengsan_wriggler_shuying: Wriggler,
mengsan_shrinker_beetle_shuying: ShrinkerBeetle,
+43
View File
@@ -0,0 +1,43 @@
// 网页普通主表:墨迹→墨水长枪→肢解→准备→墨迹;开场滑溜9。
export const VANTOM_CHARACTER = "mengsan_vantom_shuying";
export const VANTOM_MOVES = Object.freeze({
blot: Object.freeze({ id: "blot", name: "墨迹", damage: 7 }),
lance: Object.freeze({ id: "lance", name: "墨水长枪", damage: 6, hits: 2 }),
dismember: Object.freeze({ id: "dismember", name: "肢解", damage: 26, wound: 3 }),
prepare: Object.freeze({ id: "prepare", name: "准备", strength: 2 }),
});
const cycle = Object.freeze(Object.values(VANTOM_MOVES));
const turnsOf = state => {
const turns = state.turnsTaken ?? 0;
if (!Number.isSafeInteger(turns) || turns < 0) throw new RangeError("墨影幻灵行动记录无效");
return turns;
};
export const selectVantomMove = (state = {}) => cycle[turnsOf(state) % cycle.length];
export function recordVantomAction(state = {}, advance = true) {
const turns = turnsOf(state);
if (typeof advance !== "boolean" || advance && turns === Number.MAX_SAFE_INTEGER) throw new RangeError("墨影幻灵行动推进无效");
return { turnsTaken: turns + (advance ? 1 : 0) };
}
export const isVantom = player => player?.name === VANTOM_CHARACTER && player.storage?.mengsanCamp_shuying === "enemy";
const bindings = new WeakMap();
export function initializeVantom(player, current = null) {
if (!isVantom(player)) return false;
if (current?.session.active && bindings.get(player)?.battle !== current) {
const entry = { battle: current };
current.session.ownResource(entry, () => {
if (bindings.get(player) !== entry) return;
bindings.delete(player);
delete player.storage.mengsanVantomState_shuying;
delete player.storage.mengsanVantomIntent_shuying;
player.removeSkill("mengsan_slippery_shuying");
player.removeSkill("mengsan_raider_strength_shuying");
});
bindings.set(player, entry);
}
player.storage.mengsanVantomState_shuying = { turnsTaken: 0 };
player.storage.mengsanVantomIntent_shuying = null;
player.storage.mengsanSlippery_shuying = 9;
player.addSkill("mengsan_slippery_shuying");
player.markSkill("mengsan_slippery_shuying");
return true;
}
+7 -3
View File
@@ -2,10 +2,14 @@ import { consumeArtifact } from "./artifact-status.js";
import { cardDefinitions } from "../cards/card-definitions.js";
const KEY = "mengsanTangled_shuying";
const SKILL = "mengsan_tangled_shuying";
// 梦三伤害牌类别;未进永久奖励注册表的原生攻击锦囊也适用。
// 原版攻击分类优先;未配置类型的旧牌及原生攻击锦囊保留兼容判断。
const nativeAttacks = new Set(["nanman", "wanjian", "huogong"]);
export const isAttackCard = card => Boolean(card && (nativeAttacks.has(card.name) ||
Object.hasOwn(cardDefinitions, card.name) && cardDefinitions[card.name].category === "damage"));
export const isAttackCard = card => {
if (!card) return false;
const definition = Object.hasOwn(cardDefinitions, card.name) ? cardDefinitions[card.name] : null;
return definition?.cardType ? definition.cardType === "attack" :
nativeAttacks.has(card.name) || definition?.category === "damage";
};
const clear = target => { delete target.storage[KEY]; target.removeSkill(SKILL); };
const turnsOf = target => {
const turns = target.storage?.[KEY] ?? 0;