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
@@ -0,0 +1,35 @@
// 2026-10-03 灰机Wiki详情页主表。巨石为原始力量的衍生牌。
export const ironcladAdvancedEntries = [
["relentless_assault", "无情猛攻", 2, {damage:14, nextFreeAttack:true}, {damage:20}],
["howl_from_beyond", "彼岸咆哮", 3, {damage:16, all:true, exhaustReplay:true}, {damage:21}],
["stomp", "踩踏", 3, {damage:12, all:true, attackDiscount:true}, {damage:15}],
["battle_trance", "战斗专注", 0, {draw:3, stopDraw:true}, {draw:4}],
["infernal_blade", "地狱之刃", 1, {generateAttack:true, exhaust:true}, {cost:0}],
["drum_of_battle", "战鼓", 1, {draw:2, exhaustEnergy:2}, {exhaustEnergy:3}],
["eager", "跃跃欲试", 2, {energyPerAttack:true, stopEnergy:true}, {cost:1}],
["feel_no_pain", "无惧疼痛", 1, {power:"feelNoPain", exhaustBlock:3}, {exhaustBlock:4}],
["ferocity", "凶恶", 1, {power:"ferocity", vulnerableDraw:1}, {vulnerableDraw:2}],
["stone_armor", "岩石铠甲", 1, {power:"stoneArmor", plating:4}, {plating:6}],
["juggle", "杂耍", 1, {power:"juggle", juggle:1}, {innate:true}],
["stampede", "惊逃", 2, {power:"stampede", endAttack:1}, {cost:1}],
["feed", "狂宴", 1, {damage:10, feed:3, exhaust:true}, {damage:12, feed:4}],
["thrash", "痛殴", 1, {damage:4, hits:2, absorbAttack:true}, {damage:6}],
["bully", "凌虐", 3, {damage:15, negativeStrength:10}, {damage:20, negativeStrength:15}],
["unleash", "倾泻", "X", {topPlays:true, xBonus:0}, {xBonus:1}],
["primal_force", "原始力量", 0, {transformAttacks:true}, {boulderUpgrade:1}],
["twin_strike", "连环拳", 1, {doubleTap:1}, {doubleTap:2}],
["stoke", "添柴", 1, {stoke:true}, {generatedUpgrade:1}],
["battle_ready", "好勇斗狠", 1, {power:"battleReady", retrieveAttack:1}, {innate:true}],
["bodyguard", "肉盾", 1, {power:"bodyguard", bodyguard:1}, {cost:0}],
["hellraiser", "地狱狂徒", 2, {power:"hellraiser", autoStrike:1}, {cost:1}],
["dark_embrace", "黑暗之拥", 2, {power:"darkEmbrace", exhaustDraw:1}, {cost:1}],
["unshakable", "坚定不移", 2, {power:"unshakable", firstBlock:1}, {cost:1}],
["juggernaut", "势不可当", 2, {power:"juggernaut", blockDamage:6}, {blockDamage:8}],
["barricade", "壁垒", 3, {power:"barricade", barricade:1}, {cost:2}],
["dual_wield", "双持", 1, {copies:1}, {copies:2}],
["corruption", "腐化", 3, {power:"corruption", corruption:1}, {cost:2}],
["midnight", "午夜", 12, {damage:99, exhaustDiscount:true}, {damage:120}],
["rally", "群情激愤", 0, {damage:9, partyCopies:true}, {damage:13}],
["grapple", "擒拿", 1, {damage:7, grapple:5}, {damage:9, grapple:7}],
["boulder", "巨石", 1, {damage:16, generatedOnly:true}, {damage:20}],
].map(([id,title,cost,base,upgrade]) => [id,title,cost,{...base,advanced:true},{...upgrade}]);
+481
View File
@@ -0,0 +1,481 @@
import { ironcladByName } from "./data.js";
import { cardDefinitions } from "../../card-definitions.js";
import { generationPool, legacyIroncladNames } from "../../card-pools.js";
import { cardUpgradeLevel, cardUpgradeRule, canUpgradeCard } from "../../upgrades.js";
import { createCardData } from "../../card-data.js";
import { isStrikeCard } from "../../strike-counter.js";
import { snapshotRampage, copyRampageGrowth } from "../../rampage.js";
import { isAttackCard } from "../../../monsters/vine-tangled.js";
import { authorizeFreeCardUse, takeXCardUse, cardCost } from "../../../battle/combat-rules.js";
import { bindIroncladHooks } from "../../ironclad-hooks.js";
import { sharedHooks } from "../../shared-hooks.js";
import { temporaryStrengthAmount } from "../../temporary-strength.js";
import { shrinkAttackDamage } from "../../../monsters/shrinker-status.js";
const MARK = "mengsan_ic_advanced_shuying";
const BRIDGE = "mengsan_ic_bridge_shuying";
const powerKeys = ["exhaustBlock","exhaustDraw","vulnerableDraw","plating","juggle","endAttack",
"retrieveAttack","bodyguard","autoStrike","firstBlock","blockDamage","barricade","corruption"];
const safe = n => Number.isSafeInteger(n) && n >= 0;
function belongs(event,root) {
const seen=new Set();
for(let current=event;current&&!seen.has(current);current=current.parent){
if(current===root)return true; seen.add(current);
}
return false;
}
function nearestUse(event) {
const seen=new Set();
for(let current=event;current&&!seen.has(current);current=current.parent){
if(current.name==="useCard")return current; seen.add(current);
}
return null;
}
const originalOf = event => {
const use=nearestUse(event);
return use?.mengsanIcRepeatOriginal_shuying || use?.cards?.find(c=>c.name===use.card?.name) || event.card;
};
export function createAdvancedIronclad({game,get,lib,status,getBattle,battleEnergy,random}) {
const states=new WeakMap(), uses=new WeakMap(), hits=new WeakMap(), growth=new WeakMap();
const seenUses=new WeakSet(), seenBlocks=new WeakSet(), seenDraws=new WeakSet();
const valid=(player,token)=>Boolean(token && getBattle()===token.battle && token.session.active &&
token.battle.session===token.session && token.battle.root===token.root &&
token.battle.players.has(player) && player?.isAlive?.());
const tokenOf=player=>{
const battle=getBattle();
if(!battle?.session?.active || !battle.root || !battle.players.has(player) || !player?.isAlive?.())return null;
return {battle,session:battle.session,root:battle.root,run:status.mengsanRun_shuying};
};
const pileOf=(player,token)=>player===game.me ? token.battle.personalPiles : token.battle.monsterPiles?.get(player);
const stateOf=player=>{
const s=states.get(player); return s && valid(player,s.token) ? s : null;
};
const add=(object,key,value)=>{
const next=(object[key]||0)+value;
if(!safe(next))throw new RangeError("刘备牌组状态数值越界");
object[key]=next;
};
const rule=card=>{
const spec=ironcladByName[card?.name];
return spec ? cardUpgradeLevel(card) ? spec.upgraded : spec.base : null;
};
const skillCard=card=>{
const r=rule(card);
const info=lib.card[card?.name],definition=cardDefinitions[card?.name];
if(definition?.cardType)return definition.cardType==="skill";
return r ? !r.damage && !r.power : !isAttackCard(card) && !info?.mengsanPower_shuying &&
(legacyIroncladNames.includes(card?.name) || definition?.deck==="colorless" &&
info?.type!=="equip" && info?.type!=="status");
};
const refresh=s=>{if(s&&valid(s.player,s.token)){s.token.battle.handUI?.refresh();s.token.battle.pilesUI?.refresh();}};
function ensure(player,{requiresPile=true}={}) {
const token=tokenOf(player); if(!token)return null;
// 全局钩子也接收公共牌堆友军的事件;他们没有个人消耗牌堆。
// 被卡牌施加的临时状态可单独登记,不将目标当成个人牌堆使用者。
if(requiresPile && !pileOf(player,token))return null;
let s=stateOf(player); if(s)return s;
s={player,token,powers:Object.create(null),turn:Object.create(null),queue:[],flushing:false,
autoQueue:[],autoBusy:false,freeCards:new WeakSet(),negativeStrength:0,autoCount:0};
states.set(player,s); player.addSkill(MARK);
token.session.ownResource(s,()=>{if(states.get(player)===s){states.delete(player);player.removeSkill(MARK);s.queue.length=0;s.autoQueue.length=0;}});
bindIroncladHooks(player,token.session,{
valid:()=>valid(player,token),
blocksDraw:()=>Boolean(s.turn.noDraw), blocksEnergy:()=>Boolean(s.turn.noEnergy),
preservesBlock:()=>Boolean(s.powers.barricade),
skillExhausts:card=>Boolean(s.powers.corruption && skillCard(card)),
strengthPenalty:()=>s.negativeStrength,
isFree:card=>{
const node=card?.cards?.length===1 ? card.cards[0] : card;
return Boolean(s.freeCards.has(node) || s.powers.freeAttack && isAttackCard(card) || s.powers.corruption && skillCard(card));
},
cost:(card,cost)=>{
const r=rule(card);
if(r?.attackDiscount)cost=Math.max(0,cost-(s.turn.attacks||0));
if(r?.exhaustDiscount)cost=Math.max(0,cost-totalExhaustions(token));
const node=card?.cards?.length===1 ? card.cards[0] : card;
if(s.freeCards.has(node) || s.powers.freeAttack && isAttackCard(card) || s.powers.corruption && skillCard(card))return 0;
return cost;
},
queueExhaust:card=>s.queue.push({type:"exhaust",card,energy:rule(card)?.exhaustEnergy||0,
block:s.powers.exhaustBlock||0,draw:s.powers.exhaustDraw||0}),
flushExhaust:()=>flush(s), afterDraw:cards=>afterDraw(s,cards),
snapshotCard:card=>card.name==="mengsan_ic_thrash" ? {
mengsanThrashBonus_shuying:growthOf(card,s),mengsanThrashSession_shuying:token.session.id} : {},
queueVulnerable:target=>{if(s.powers.vulnerableDraw && valid(target,token))s.queue.push({type:"vulnerable",draw:s.powers.vulnerableDraw});},
blockAmount:(num,event)=>{
if(!(num>0) || !s.powers.firstBlock)return num;
let current=event;const visited=new Set();
while(current&&!visited.has(current)){
visited.add(current);
// 技能/遗物获得的格挡不借用其外层卡牌的来源。
if(current.skill || lib.skill[current.name])return num;
if(current.name==="useCard" && current.player===player){
const uses=s.turn.blockUses ||= new Set();
if(uses.has(current))return num*2;
if(uses.size<s.powers.firstBlock){uses.add(current);return num*2;}
return num;
}
current=current.parent;
}
return num;
},
});
return s;
}
function totalExhaustions(token) {
let total=0;
for(const player of token.battle.players)total+=pileOf(player,token)?.exhaustTotal?.()||0;
return total;
}
function dataOf(card) {
const physical=card?.cards?.length===1 ? card.cards[0] : card;
const data=physical?.storage?.mengsanCard_shuying || card?.storage?.mengsanCard_shuying || {};
return {...data,name:card.name,suit:card.suit||data.suit||"spade",number:card.number||data.number||1,
nature:card.nature||data.nature||null,upgrade:cardUpgradeLevel(card),affixes:[...(data.affixes||[])]};
}
function generatedData(s,name,source=null,upgrade=0) {
const battle=s.token.battle,serial=(battle.generatedCardSerial||0)+1;
if(!safe(serial))throw new RangeError("生成牌序号越界");
battle.generatedCardSerial=serial;
const raw=source ? dataOf(source) : {suit:"spade",number:1,nature:null,affixes:[],upgrade};
return createCardData({...raw,id:`mengsan_generated_${s.token.session.id}_${serial}`,name,
upgrade:Math.min(raw.upgrade||0,cardUpgradeRule(name)?.maxLevel||0)}, {enemy:true});
}
const randomItem=(s,list)=>{
if(!list.length)return null;
const value=random(s.token.run);
if(!Number.isFinite(value)||value<0||value>=1)throw new RangeError("战斗随机数无效");
return list[Math.floor(value*list.length)];
};
function rememberGrowth(s,card,bonus) {
if(!safe(bonus))throw new RangeError("痛殴累积伤害越界");
const entry={token:s.token,bonus};growth.set(card,entry);
s.token.session.ownResource(entry,()=>{if(growth.get(card)===entry)growth.delete(card);});
}
function growthOf(card,s=null) {
if(card?.cards?.length===1 && card.cards[0].name===card.name)card=card.cards[0];
const entry=growth.get(card);
return entry?.token.session.active && (!s || entry.token.battle===s.token.battle) ? entry.bonus :
!card?.storage && card?.mengsanThrashSession_shuying===getBattle()?.session.id ? card.mengsanThrashBonus_shuying||0 : 0;
}
async function copyToHand(s,source) {
if(!valid(s.player,s.token))return null;
const pile=pileOf(s.player,s.token);if(!pile)return null;
const copy=await pile.addToHand(generatedData(s,source.name,source));
if(!copy || !valid(s.player,s.token))return copy;
copyRampageGrowth(source,copy,s.token.battle);
sharedHooks(s.player)?.copyCard?.(source,copy);
if(source.name==="mengsan_ic_thrash")rememberGrowth(s,copy,growthOf(source,s));
// 实体牌副本继承永久词缀与当前强化,不继承本回合免费、已使用或消耗标记。
return copy;
}
async function flush(s) {
if(!s || s.flushing)return;
s.flushing=true;
try {
while(s.queue.length && valid(s.player,s.token)){
const entry=s.queue.shift();
if(entry.energy)battleEnergy.grant(s.player,entry.energy,s.token.battle);
if(entry.block){await s.player.changeHujia(entry.block);if(!valid(s.player,s.token))break;}
if(entry.draw){await s.player.draw(entry.draw);if(!valid(s.player,s.token))break;}
}
refresh(s);
} finally {s.flushing=false;}
}
async function flushAll() {
const battle=getBattle();if(!battle?.session.active)return;
for(const player of [...battle.players])await flush(stateOf(player));
}
function legalTargets(s,card) {
const info=lib.card[card.name];if(!info)return [];
return [...s.token.battle.players].filter(target=>valid(target,s.token) &&
(typeof info.filterTarget!=="function" || info.filterTarget(card,s.player,target)));
}
async function autoPlay(s,card,{fromExhaust=false,fromDraw=false,echo=null,targets=null}={}) {
if(!valid(s.player,s.token))return false;
const info=lib.card[card.name],pile=pileOf(s.player,s.token);
if((fromExhaust||fromDraw) && !pile)return false;
if(++s.autoCount>1024)throw new Error("自动打牌超过安全结算上限,请检查递归组合");
const skip=()=>{if(fromDraw && pile.beginAutoPlay(card,false))pile.finishAutoPlay(card);return false;};
if(!info || info.enable===false || card.storage?.mengsanCard_shuying?.affixes?.includes("unplayable"))return skip();
let selected=info.notarget ? [] : targets?.filter(target=>valid(target,s.token)) || null;
if(!selected){
const candidates=legalTargets(s,card);
if(!candidates.length)return skip();
selected=info.toself ? [s.player] : info.selectTarget===-1 ? candidates : [randomItem(s,candidates)];
}
if(!selected.length && !info.notarget)return skip();
if((fromExhaust||fromDraw) && !pile.beginAutoPlay(card,fromExhaust))return false;
let use,release=()=>{};
try {
const played=echo ? {...echo.card,storage:{mengsanCard_shuying:{...echo.data,affixes:echo.data.affixes.slice()}}} : card;
use=s.player.useCard({card:played,cards:echo?[]:[card],targets:selected,addCount:false});
if(echo){use.mengsanIcEcho_shuying=true;use.mengsanIcRepeatOriginal_shuying=echo.original;}
use.mengsanIcAuto_shuying=true;use.addCount=false;
release=authorizeFreeCardUse(s.player,use.card,use,()=>valid(s.player,s.token));
// 在该原生 useCard 成为当前事件时校验,临时免费授权才能匹配正确父链。
use.oncard=()=>{
if(!valid(s.player,s.token) || !lib.filter.cardEnabled(use.card,s.player) ||
selected.some(target=>!s.player.canUse(use.card,target,false)))use.cancel();
};
await use;
return !use.cancelled;
} finally {release();if(fromExhaust||fromDraw)pile.finishAutoPlay(card);}
}
async function afterDraw(s,cards) {
if(!s?.powers.autoStrike || !valid(s.player,s.token))return;
for(const card of cards||[])if(isAttackCard(card) && isStrikeCard(card,lib))s.autoQueue.push(card);
if(s.autoBusy)return;
s.autoBusy=true;
try {
while(s.autoQueue.length && valid(s.player,s.token)){
const card=s.autoQueue.shift();
if(s.player.getCards("h").includes(card))await autoPlay(s,card);
}
} finally {s.autoBusy=false;}
}
function baseDamage(card,player,event=null) {
const s=stateOf(player),r=rule(card),level=cardUpgradeLevel(card),upgrade=level?cardUpgradeRule(card.name):null;
if(r){
let value=r.damage||0;
if(r.perExhaust)value+=r.perExhaust*(s ? pileOf(player,s.token)?.exhaustedCards().length||0 : 0);
if(card.name==="mengsan_ic_thrash")value+=growthOf(card,s);
return value;
}
const values={sha:level?9:6,mengsan_zhongsha:level?10:8,mengsan_fennu:level?8:6,
mengsan_feijianhuixuanbiao:3,mengsan_jianbingdaji:level?10:9,mengsan_rongrongzhiquan:level?14:10,
mengsan_shandianpili:level?7:4,mengsan_shuangchongdaji:level?7:5,mengsan_tiezhanbo:level?7:5,
mengsan_touchui:level?12:9,mengsan_tupo:level?13:9,mengsan_yubeidaji:level?9:7,
mengsan_yujin:level?24:18,mengsan_yuanhen:5,mengsan_xuanfengzhan:level?8:5};
if(card.name==="mengsan_quanshenzhuangji")return player.hujia||0;
if(card.name==="mengsan_baozou")return 9+(snapshotRampage(card).mengsanRampageBonus_shuying||0);
if(card.name==="mengsan_qiling")return 4; // 无指定目标时,目标易伤项为0。
if(card.name==="mengsan_wanmeidaji"){
const all=s ? pileOf(player,s.token)?.battleCards(event?.cards||[])||[] : [];
return 6+all.filter(c=>isStrikeCard(c,lib)).length*(level?3:2);
}
return values[card.name]??upgrade?.damage??0;
}
async function play(spec,event,player) {
const s=ensure(player);if(!s || !belongs(event,s.token.root))return;
const r=cardUpgradeLevel(event.card)?spec.upgraded:spec.base,pile=pileOf(player,s.token);
if(r.damage){
const source=originalOf(event),amount=r.damage+(r.absorbAttack?growthOf(source,s):0),hitsCount=r.hits||1;
for(let i=0;i<hitsCount && valid(player,s.token);i++){
const targets=r.all ? [...s.token.battle.players].filter(t=>valid(t,s.token)&&player.isEnemyOf(t)) : [event.target];
for(const target of targets){
if(!valid(player,s.token))return;
if(!valid(target,s.token)||!player.isEnemyOf(target))continue;
const hit=target.damage(amount,player);hit.card=event.card;hit.mengsanAttack_shuying=true;
if(r.feed)hits.set(hit,{s,amount:r.feed,claimed:false});
await hit;
}
}
}
if(!valid(player,s.token))return;
if(r.nextFreeAttack)add(s.powers,"freeAttack",1);
if(r.negativeStrength && valid(event.target,s.token)){
const targetState=ensure(event.target,{requiresPile:false});
if(targetState)add(targetState,"negativeStrength",r.negativeStrength);
}
if(r.absorbAttack){
const candidate=randomItem(s,player.getCards("h").filter(c=>isAttackCard(c)));
if(candidate){
let amount=Math.max(0,baseDamage(candidate,player,event)+(player.storage.mengsanStrength_shuying||0)+temporaryStrengthAmount(player,s.token.battle)-s.negativeStrength);
if(player.storage.mengsanWeak_shuying>0)amount=Math.floor(amount*0.75);
amount=shrinkAttackDamage(amount,player);
if(await pile.exhaustFromHand(candidate)){
if(valid(player,s.token)){const source=originalOf(event);rememberGrowth(s,source,growthOf(source,s)+amount);}
}
}
}
if(r.draw){await player.draw(r.draw);if(!valid(player,s.token))return;}
if(r.stopDraw)s.turn.noDraw=true;
if(r.energyPerAttack){const count=player.getCards("h").filter(isAttackCard).length;if(count)battleEnergy.grant(player,count,s.token.battle);}
if(r.stopEnergy)s.turn.noEnergy=true;
if(r.doubleTap)add(s.turn,"doubleTap",r.doubleTap);
if(r.grapple){s.turn.grapple||=new Map();if(valid(event.target,s.token))addMap(s.turn.grapple,event.target,r.grapple);}
if(r.generateAttack){
const name=randomItem(s,generationPool(cardDefinitions,["liubei"],{attacksOnly:true}));
if(name){const card=await pile.addToHand(generatedData(s,name));if(card&&valid(player,s.token))s.freeCards.add(card);}
}
if(r.copies){
const candidates=player.getCards("h").filter(c=>isAttackCard(c)||
(cardDefinitions[c.name]?.cardType ? cardDefinitions[c.name].cardType==="power" : lib.card[c.name]?.mengsanPower_shuying));
let chosen=candidates[0];
if(candidates.length>1){const result=await player.chooseButton({createDialog:["双持:选择一张攻击或能力牌",candidates],forced:true,selectButton:1,ai:button=>get.value(button.link,player)}).forResult();chosen=result.links?.[0];}
if(valid(player,s.token)&&candidates.includes(chosen)&&player.getCards("h").includes(chosen))for(let i=0;i<r.copies&&valid(player,s.token);i++)await copyToHand(s,chosen);
}
if(r.partyCopies)for(const participant of [...s.token.battle.players]){
if(!valid(player,s.token))return;
if(!valid(participant,s.token))continue;
const targetPile=pileOf(participant,s.token);if(targetPile)targetPile.addToDiscard(generatedData(s,spec.name,event.card));
}
if(r.topPlays){
const count=takeXCardUse(player,event,s.token.battle);
if(count!==null)for(let i=0;i<count+r.xBonus&&valid(player,s.token);i++){
const card=pile.drawTop();if(!card)break;await autoPlay(s,card,{fromDraw:true});
}
}
if(r.transformAttacks)for(const card of player.getCards("h").filter(isAttackCard)){
if(!valid(player,s.token))break;
const data=dataOf(card);pile.transformHand(card,{...data,name:"mengsan_ic_boulder",upgrade:r.boulderUpgrade||0});
}
if(r.stoke){
const candidates=player.getCards("h").slice();
const pool=generationPool(cardDefinitions,["liubei","colorless"]);
let count=0;
for(const card of candidates){if(!valid(player,s.token))return;if(await pile.exhaustFromHand(card))count++;}
for(let i=0;i<count&&valid(player,s.token);i++){
const name=randomItem(s,pool);if(!name)break;
await pile.addToHand(generatedData(s,name,null,r.generatedUpgrade||0));
}
}
if(r.power)for(const key of powerKeys)if(r[key]){
if(["bodyguard","autoStrike","barricade","corruption"].includes(key))s.powers[key]=1;
else add(s.powers,key,r[key]);
}
await flush(s);refresh(s);
}
function addMap(map,key,value){const next=(map.get(key)||0)+value;if(!safe(next))throw new RangeError("擒拿数值越界");map.set(key,next);}
async function onUse(use) {
const s=ensure(use.player);if(!s || seenUses.has(use))return;seenUses.add(use);
if(!isAttackCard(use.card))return;
add(s.turn,"attacks",1);
if(s.powers.freeAttack)s.powers.freeAttack--;
const source=originalOf(use),snapshot={card:{name:use.card.name,suit:use.card.suit,number:use.card.number,nature:use.card.nature},data:dataOf(use.card),original:source,targets:[...(use.targets||[])]};
if(s.turn.doubleTap&&!use.mengsanIcEcho_shuying){s.turn.doubleTap--;uses.set(use,snapshot);}
// 第三张的副本在原牌效果之前加入手牌,复制此刻的累积数据。
if(s.turn.attacks===3 && s.powers.juggle)for(let i=0;i<s.powers.juggle&&valid(s.player,s.token);i++)await copyToHand(s,source);
refresh(s);
}
async function onUseAfter(use) {
await flushAll();const s=stateOf(use.player),echo=uses.get(use);uses.delete(use);
if(s&&echo&&valid(s.player,s.token))await autoPlay(s,echo.original,{echo,targets:echo.targets});
refresh(s);
}
async function turnBegin(player) {
const s=ensure(player);if(!s)return;
s.turn=Object.create(null);s.freeCards=new WeakSet();s.autoCount=0;
if(s.powers.plating)s.powers.plating=Math.max(0,s.powers.plating-1);
const pile=pileOf(player,s.token);
if(!pile)return;
for(let i=0;i<(s.powers.retrieveAttack||0)&&valid(player,s.token);i++){
const card=randomItem(s,pile.discardCards().filter(isAttackCard));if(!card)break;
if(await pile.gainToHand(card)){
if(!valid(player,s.token))break;
const data=dataOf(card);if(canUpgradeCard(data))card.storage.mengsanCard_shuying={...data,upgrade:data.upgrade+1};
}
}
refresh(s);
}
async function turnEnd(player) {
const s=ensure(player);if(!s)return;
if(s.powers.plating){await player.changeHujia(s.powers.plating);if(!valid(player,s.token))return;}
const pile=pileOf(player,s.token);if(!pile)return;
const howls=pile.exhaustedCards().filter(c=>rule(c)?.exhaustReplay);
for(const card of howls){if(!valid(player,s.token))return;await autoPlay(s,card,{fromExhaust:true});}
for(let i=0;i<(s.powers.endAttack||0)&&valid(player,s.token);i++){
const card=randomItem(s,player.getCards("h").filter(isAttackCard));if(!card)break;await autoPlay(s,card);
}
}
async function afterBlock(event) {
if(seenBlocks.has(event)||!(event.num>0)||event.type==="damage")return;seenBlocks.add(event);
const s=stateOf(event.player);if(!s)return;
if(s.powers.blockDamage){const target=randomItem(s,[...s.token.battle.players].filter(t=>valid(t,s.token)&&s.player.isEnemyOf(t)));
if(target){const damage=target.damage(s.powers.blockDamage,s.player,"nocard");damage.mengsanScriptedSkill_shuying=true;await damage;}}
for(const [target,amount] of s.turn.grapple||[]){
if(!valid(s.player,s.token))return;
if(valid(target,s.token)){const damage=target.damage(amount,s.player,"nocard");damage.mengsanScriptedSkill_shuying=true;await damage;}
}
}
async function onDeath(event) {
let current=event;const visited=new Set();
while(current&&!visited.has(current)){
visited.add(current);const record=hits.get(current);
if(record){
const {s,amount}=record;
if(record.claimed || !valid(s.player,s.token) || status.mengsanRun_shuying!==s.token.run)return;
record.claimed=true;
await s.player.gainMaxHp(amount);
if(s.player===game.me && status.mengsanRun_shuying===s.token.run){
s.token.run.player.maxHp=s.player.maxHp;s.token.run.player.hp=s.player.hp;
await game.mengsanPersistRun_shuying(s.token.run);
}
return;
}
current=current.parent;
}
}
const skills={
// 本场内部状态技能:保留触发与清理,不在武将状态栏显示。
[MARK]:{charlotte:true,mark:false,
trigger:{source:"damageBegin1"},forced:true,silent:true,popup:false,priority:82,
filter(event,player){return Boolean(player!==game.me && !event.mengsanScriptedSkill_shuying && stateOf(player)?.negativeStrength && (event.mengsanAttack_shuying||isAttackCard(event.card)));},
async content(event,trigger,player){trigger.num=Math.max(0,trigger.num-stateOf(player).negativeStrength);}},
[BRIDGE]:{trigger:{global:["useCard1","useCardAfter","cardsDiscardAfter","loseAfter","phaseBefore","phaseJieshuBegin","phaseAfter","drawAfter","changeHujiaAfter","damageBegin2"]},
forced:true,silent:true,popup:false,priority:60,
filter(event){const battle=getBattle();return Boolean(battle?.session.active&&belongs(event,battle.root));},
async content(event,trigger){
const name=event.triggername;
if(name==="useCard1")await onUse(trigger);
else if(name==="useCardAfter")await onUseAfter(trigger);
else if(name==="phaseBefore")await turnBegin(trigger.player);
else if(name==="phaseJieshuBegin")await turnEnd(trigger.player);
else if(name==="phaseAfter"){
for(const player of getBattle().players){const s=stateOf(player);if(s){s.negativeStrength=0;s.freeCards=new WeakSet();s.turn=Object.create(null);refresh(s);}}
} else if(name==="drawAfter"){
if(!seenDraws.has(trigger)&&!trigger.mengsanIcDrawHandled_shuying){seenDraws.add(trigger);await afterDraw(ensure(trigger.player),trigger.result?.cards||[]);}
} else if(name==="changeHujiaAfter")await afterBlock(trigger);
else if(name==="damageBegin2" && (trigger.mengsanAttack_shuying||isAttackCard(trigger.card))){
let multiplier=1;
for(const holder of getBattle().players){const s=stateOf(holder);if(s?.powers.bodyguard&&valid(holder,s.token)&&holder.isFriendOf(trigger.player)&&holder.isEnemyOf(trigger.source))
multiplier*=holder===trigger.player?2:0.5;}
if(multiplier!==1)trigger.num=Math.floor(trigger.num*multiplier);
} else await flushAll();
}},
};
const describe=(spec,card)=>{
const r=cardUpgradeLevel(card)?spec.upgraded:spec.base,parts=[];
if(r.damage)parts.push(`对${r.all?"所有敌人":"一名敌人"}造成${r.damage+(r.absorbAttack?growthOf(card):0)}点伤害${r.hits?`${r.hits}次`:""}。`);
if(r.nextFreeAttack)parts.push("下一张攻击牌费用变为0。");
if(r.exhaustReplay)parts.push("回合结束时,此牌在消耗牌堆中则免费打出。");
if(r.attackDiscount)parts.push("本回合每打出一张攻击牌,费用减少1。");
if(r.draw)parts.push(`抽${r.draw}张牌。`);
if(r.stopDraw)parts.push("本回合不能再抽牌。");
if(r.generateAttack)parts.push("随机生成一张铁甲战士攻击牌至手牌,本回合费用为0。");
if(r.exhaustEnergy)parts.push(`被消耗时获得${r.exhaustEnergy}费用。`);
if(r.energyPerAttack)parts.push("每张攻击手牌获得1费用。");
if(r.stopEnergy)parts.push("本回合不能再获得费用。");
if(r.exhaustBlock)parts.push(`每张牌被消耗时获得${r.exhaustBlock}点格挡。`);
if(r.exhaustDraw)parts.push(`每张牌被消耗时抽${r.exhaustDraw}张牌。`);
if(r.vulnerableDraw)parts.push(`每次成功施加易伤抽${r.vulnerableDraw}张牌。`);
if(r.plating)parts.push(`获得${r.plating}层覆甲:回合末获得等量格挡,下个自身回合开始减少1层。`);
if(r.juggle)parts.push("每个自身回合的第三张攻击牌复制一张至手牌。");
if(r.innate)parts.push("固有。");
if(r.endAttack)parts.push("回合结束时,随机免费打出一张攻击手牌,目标随机。");
if(r.feed)parts.push(`斩杀敌人时永久增加${r.feed}点最大生命。`);
if(r.absorbAttack)parts.push("消耗随机一张攻击手牌,将其当前伤害(计入你的力量及攻击减益)累积给此牌,本场战斗保留。");
if(r.negativeStrength)parts.push(`目标本回合失去${r.negativeStrength}点力量。`);
if(r.topPlays)parts.push(`免费打出抽牌堆顶的X${r.xBonus?"+1":""}张牌。`);
if(r.transformAttacks)parts.push(`所有攻击手牌变为巨石${r.boulderUpgrade?"+":""},仅本场战斗。`);
if(r.doubleTap)parts.push(`本回合接下来的${r.doubleTap}张攻击牌额外打出一次。`);
if(r.stoke)parts.push(`消耗所有手牌,每张随机生成一张${r.generatedUpgrade?"已强化":""}铁甲战士或无色牌至手牌。`);
if(r.retrieveAttack)parts.push("自身回合开始,从弃牌堆随机获得一张攻击牌并强化。");
if(r.bodyguard)parts.push("你承受敌人双倍伤害,敌人对其他友方的伤害减半。");
if(r.autoStrike)parts.push("抽到【杀】或名称含“打击”的攻击牌时,免费向随机敌人打出。");
if(r.firstBlock)parts.push("每个自身回合首张获得格挡的卡牌,其获得的格挡翻倍;叠加时增加可翻倍的牌数。");
if(r.blockDamage)parts.push(`每次获得格挡,对随机敌人造成${r.blockDamage}点非攻击伤害。`);
if(r.barricade)parts.push("自身回合开始时不清空格挡。");
if(r.copies)parts.push(`选择一张攻击或能力手牌,复制${r.copies}张至手牌。`);
if(r.corruption)parts.push("技能牌费用变为0,打出后被消耗。");
if(r.exhaustDiscount)parts.push("本场任意角色每消耗一张牌,费用减少1。");
if(r.partyCopies)parts.push("在所有存活角色的个人弃牌堆加入此牌副本。");
if(r.grapple)parts.push(`本回合每次获得格挡,对此目标造成${r.grapple}点非攻击伤害。`);
if(r.exhaust)parts.push("消耗。");
if(r.power)parts.push("能力持续本场战斗,打出后退出牌堆。");
return `梦三:主动使用消耗${spec.cost==="X"?"X":cardCost(card||{name:spec.name})}费用。`+parts.join("");
};
return {ensure,play,describe,skills,flushAll,baseDamage,onDeath};
}
+52
View File
@@ -0,0 +1,52 @@
// 2026-10-03 灰机Wiki铁甲战士详情页主表;所有值均为当前状态。
// 纯数据模块可被费用、升级、注册表及运行时共同导入,不依赖引擎。
import { ironcladAdvancedEntries } from "./advanced-data.js";
import { liubeiCardTraits } from "./traits.js";
const entries = [
["dismantle", "拆卸", 1, { damage: 8, vulnerableHits: 2 }, { damage: 10 }],
["ashen_strike", "灰烬打击", 1, { damage: 6, perExhaust: 3 }, { perExhaust: 4 }],
["pillage", "劫掠", 1, { damage: 6, drawUntilSkill: true }, { damage: 9 }],
["hemokinesis", "御血术", 1, { damage: 15, loseHp: 2 }, { damage: 20 }],
["uppercut", "上勾拳", 2, { damage: 13, weak: 1, vulnerable: 1 }, { weak: 2, vulnerable: 2 }],
["fight_me", "与我一战!", 2, { damage: 5, hits: 2, strength: 3, enemyStrength: 1 }, { damage: 6, strength: 4 }],
["bludgeon", "重锤", 3, { damage: 32 }, { damage: 42 }],
["demonic_shield", "恶魔护盾", 0, { loseHp: 1, allyBlock: true, exhaust: true }, { exhaust: false }],
["forgotten_ritual", "被遗忘的仪式", 1, { exhaustTurnEnergy: 3, exhaust: true }, { exhaustTurnEnergy: 4 }],
["burning_pact", "燃烧契约", 1, { chooseExhaust: true, draw: 2 }, { draw: 3 }],
["taunt", "挑衅", 1, { block: 7, vulnerable: 1 }, { block: 8, vulnerable: 2 }],
["evil_eye", "邪眼", 1, { block: 8, exhaustTurnBlock: true }, { block: 11 }],
["second_wind", "重振精神", 1, { exhaustNonAttacks: true, blockPerExhaust: 5 }, { blockPerExhaust: 7 }],
["dominate", "主宰", 1, { vulnerable: 1, strengthFromVulnerable: true, exhaust: true }, { vulnerable: 2 }],
["pacts_end", "契约终结", 0, { damage: 17, all: true, minExhaust: 3 }, { damage: 23 }],
["conflagration", "焚烧", 1, { damage: 2, all: true, hits: 4 }, { hits: 5 }],
["fiend_fire", "恶魔之焰", 2, { damage: 7, hitsPerExhaust: true, exhaust: true }, { damage: 10 }],
["offering", "祭品", 0, { loseHp: 6, energy: 2, draw: 3, exhaust: true }, { draw: 5 }],
["brand", "烙印", 0, { loseHp: 1, chooseExhaust: true, strength: 1 }, { strength: 2 }],
["impervious", "岿然不动", 2, { block: 30, exhaust: true }, { block: 40 }],
["not_yet", "时候未到", 2, { heal: 10, exhaust: true }, { heal: 13 }],
["clash", "交锋", 0, { damage: 14, onlyAttacks: true }, { damage: 18 }],
["entrench", "巩固", 2, { doubleBlock: true }, { cost: 1 }],
["break", "破击", 1, { damage: 20, vulnerable: 5 }, { damage: 30, vulnerable: 7 }],
["blaze", "炽焰", 2, { allyStrength: 5 }, { allyStrength: 7 }],
["inflame", "燃烧", 1, { power: "inflame", strength: 2 }, { strength: 3 }],
["demon_form", "恶魔形态", 3, { power: "demonForm", turnStrength: 2 }, { turnStrength: 3 }],
["pyre", "薪火之源", 2, { power: "pyre", turnEnergy: 1 }, { turnEnergy: 2 }],
["crimson_mantle", "绯红披风", 1, { power: "crimsonMantle", turnLoseHp: 1, turnBlock: 8 }, { turnBlock: 10 }],
["rage", "狂怒", 0, { turnPower: "rage", attackBlock: 3 }, { attackBlock: 5 }],
["rupture", "撕裂", 1, { power: "rupture", lossStrength: 1 }, { lossStrength: 2 }],
["flame_barrier", "火焰屏障", 2, { block: 12, turnPower: "flameBarrier", retaliation: 4 }, { block: 16, retaliation: 6 }],
["colossus", "巨像", 1, { block: 5, turnPower: "colossus", vulnerableReduction: 0.5 }, { block: 8 }],
["cruelty", "残酷", 1, { power: "cruelty", vulnerableBonus: 0.25 }, { vulnerableBonus: 0.5 }],
["inferno", "狱火", 1, { power: "inferno", infernoLoseHp: 1, lossDamage: 6 }, { lossDamage: 9 }],
["tear_asunder", "扯碎", 2, { damage: 5, hitsPerHpLoss: true }, { damage: 7 }],
...ironcladAdvancedEntries,
];
export const ironcladCards = Object.freeze(entries.map(([id, title, cost, base, upgraded]) =>
Object.freeze({ id, title, name: `mengsan_ic_${id}`, cost, ...liubeiCardTraits[`mengsan_ic_${id}`],
base: Object.freeze(base), upgraded: Object.freeze({ ...base, ...upgraded }),
category: base.damage ? "damage" : base.heal ? "recovery" : "utility",
wiki: `https://sts2.huijiwiki.com/wiki/${encodeURIComponent(id === "corruption" ? "腐化(卡牌)" : title)}` })));
export const ironcladByName = Object.freeze(Object.fromEntries(ironcladCards.map(c => [c.name, c])));
export const ironcladUpgradeRules = Object.freeze(Object.fromEntries(ironcladCards.map(c =>
[c.name, Object.freeze({ maxLevel: 1, ...c.upgraded })])));
+15
View File
@@ -0,0 +1,15 @@
import { createIroncladRuntime } from "./runtime.js";
import { createLegacyLiuBeiCards } from "./legacy.js";
export class LiuBeiCardPack {
static id = "liubei";
static owner = "mengsan_liubei_shuying";
constructor(context) { this.context = context; }
createRuntime() {
const legacy = createLegacyLiuBeiCards(this.context);
const current = createIroncladRuntime(this.context);
return { ...current, cards:{...legacy.cards,...current.cards},
translate:{...legacy.translate,...current.translate},
names:[...legacy.names,...current.names] };
}
}
+910
View File
@@ -0,0 +1,910 @@
// 刘备牌组沿用的 25 张铁甲牌及兼容重杀;保留原有卡名与效果接口。
import standard from "../../../../../../card/standard.js";
import { _status, game, get } from "../../../../../../noname.js";
import { cardCost, canPayCard, isActiveCardUse } from "../../../battle/combat-rules.js";
import { cardUpgradeLevel, cardUpgradeRule } from "../../upgrades.js";
import { applyMengsanDebuff } from "../../../monsters/artifact-status.js";
import { isAttackCard } from "../../../monsters/vine-tangled.js";
export function createLegacyLiuBeiCards({ copyToDiscard, reclaimFromDiscard, temporaryStrength, countStrikeCards, randomHandExhaust, battleEnergy, handExhaust, playDrawTop, blockDraw, blockUpgrade, lifeLossBlock, whirlwind, spite, rampage, isBattleActive = () => true } = {}) {
const card = {}, translate = {}, nativeSha = standard.card.sha;
card.mengsan_zhongsha = {
type: "basic",
fullimage: true,
image: "ext:术樱包/mengsan/assets/cards/mengsan_zhongsha.png",
mengsanCost_shuying: cardCost({ name: "mengsan_zhongsha" }),
enable(used, player, event) {
return !isActiveCardUse(event || _status.event, player) ||
canPayCard(player, used);
},
usable: Infinity,
selectTarget: 1,
range: nativeSha.range,
filterTarget: nativeSha.filterTarget,
async content(event, trigger, player) {
const target = event.target;
const effect = cardUpgradeLevel(event.card)
? cardUpgradeRule("mengsan_zhongsha")
: { damage: 8, vulnerable: 2 };
const damage = target.damage(effect.damage, player);
damage.mengsanAttack_shuying = true;
await damage;
if (!target.isAlive()) return;
if (applyMengsanDebuff(target, "vulnerable", effect.vulnerable, player))
game.log(target, `获得${effect.vulnerable}层易伤`);
},
ai: { order: 4, result: { target: -1.5 },
tag: { damage: 1 } },
cardPrompt(used) {
const effect = cardUpgradeLevel(used)
? cardUpgradeRule("mengsan_zhongsha")
: { damage: 8, vulnerable: 2 };
return "梦三:主动使用消耗2费用。对攻击范围内的" +
`一名其他角色造成${effect.damage}点伤害,然后给予其` +
`${effect.vulnerable}层易伤。受到攻击伤害增加50%` +
"(向上取整),每个自身回合结束减少1层。" +
"每回合使用次数不限。";
},
};
translate.mengsan_zhongsha = "重杀";
translate.mengsan_zhongsha_info =
"梦三:主动使用消耗2费用。出牌阶段,对攻击范围内的" +
"一名其他角色造成8点伤害,然后给予其2层易伤。" +
"易伤使受到的攻击伤害增加50%(向上取整)," +
"每个自身回合结束减少1层。每回合使用次数不限。";
// STS2 Setup Strike: the new temporary strength does not enhance its own hit.
// STS2 Cinder: damage first, then exhaust one random remaining hand card.
card.mengsan_yujin = {
type: "basic", fullimage: true,
image: "ext:术樱包/mengsan/assets/cards/mengsan_yujin.png",
mengsanCost_shuying: cardCost({ name: "mengsan_yujin" }),
usable: Infinity, selectTarget: 1,
enable(used, player, event) {
if (!isBattleActive() || !player.isAlive()) return false;
return !isActiveCardUse(event || _status.event, player) || canPayCard(player, used);
},
filterTarget(used, player, target) {
return target !== player && target.isAlive() && player.isEnemyOf(target);
},
async content(event, trigger, player) {
const target = event.target;
if (!isBattleActive() || !player.isAlive() || !target?.isAlive() ||
target === player || !player.isEnemyOf(target)) return;
if (!randomHandExhaust?.capture || !randomHandExhaust?.exhaust) throw new Error("余烬缺少随机消耗接口");
const battle = randomHandExhaust.capture();
if (!battle) return;
const rule = cardUpgradeRule("mengsan_yujin");
const damage = target.damage(cardUpgradeLevel(event.card) ? rule.damage : rule.baseDamage, player);
damage.mengsanAttack_shuying = true;
await damage;
if (isBattleActive() && player.isAlive()) await randomHandExhaust.exhaust(player, event, battle);
},
ai: { order: 6, result: { target: -2 }, tag: { damage: 1 } },
cardPrompt(used) {
const rule = cardUpgradeRule("mengsan_yujin");
const amount = cardUpgradeLevel(used) ? rule.damage : rule.baseDamage;
return `梦三:主动使用消耗2费用。对一名敌方角色造成${amount}点伤害,然后随机消耗一张你的手牌(退出本场战斗,不进入弃牌堆或洗牌)。没有手牌时不消耗牌。每回合使用次数不限。`;
},
};
translate.mengsan_yujin = "余烬";
translate.mengsan_yujin_info = card.mengsan_yujin.cardPrompt({ name: "mengsan_yujin" });
// STS2 Perfected Strike counts current physical combat cards, including itself/exhaust.
card.mengsan_wanmeidaji = {
type: "basic", fullimage: true,
image: "ext:术樱包/mengsan/assets/cards/mengsan_wanmeidaji.png",
mengsanCost_shuying: cardCost({ name: "mengsan_wanmeidaji" }),
usable: Infinity, selectTarget: 1,
enable(used, player, event) {
if (!isBattleActive() || !player.isAlive()) return false;
return !isActiveCardUse(event || _status.event, player) || canPayCard(player, used);
},
filterTarget(used, player, target) {
return target !== player && target.isAlive() && player.isEnemyOf(target);
},
async content(event, trigger, player) {
const target = event.target;
if (!isBattleActive() || !player.isAlive() || !target?.isAlive() ||
target === player || !player.isEnemyOf(target)) return;
if (typeof countStrikeCards !== "function") throw new Error("完美打击缺少打击牌计数接口");
const count = countStrikeCards(player, event);
if (!Number.isSafeInteger(count) || count < 0) throw new RangeError("打击牌数量无效");
const rule = cardUpgradeRule("mengsan_wanmeidaji");
const amount = rule.baseDamage + count * (cardUpgradeLevel(event.card) ? rule.perStrike : rule.basePerStrike);
if (!Number.isSafeInteger(amount) || amount < 0) throw new RangeError("完美打击伤害无效");
const damage = target.damage(amount, player);
damage.mengsanAttack_shuying = true;
damage.mengsanStrikeCount_shuying = count;
await damage;
},
ai: { order: 7, result: { target: -2 }, tag: { damage: 1 } },
cardPrompt(used) {
const rule = cardUpgradeRule("mengsan_wanmeidaji");
const bonus = cardUpgradeLevel(used) ? rule.perStrike : rule.basePerStrike;
return `梦三:主动使用消耗2费用。对一名敌方角色造成6点伤害,本场战斗的牌中每有一张【杀】或名称含“打击”的牌,伤害增加${bonus}点(包括手牌、个人抽牌堆、个人弃牌堆、消耗牌及打出区)。每回合使用次数不限。`;
},
};
translate.mengsan_wanmeidaji = "完美打击";
translate.mengsan_wanmeidaji_info = card.mengsan_wanmeidaji.cardPrompt({ name: "mengsan_wanmeidaji" });
card.mengsan_yubeidaji = {
type: "basic", fullimage: true,
image: "ext:术樱包/mengsan/assets/cards/mengsan_yubeidaji.png",
mengsanCost_shuying: cardCost({ name: "mengsan_yubeidaji" }),
usable: Infinity, selectTarget: 1,
enable(used, player, event) {
if (!isBattleActive() || !player.isAlive()) return false;
return !isActiveCardUse(event || _status.event, player) || canPayCard(player, used);
},
filterTarget(used, player, target) {
return target !== player && target.isAlive() && player.isEnemyOf(target);
},
async content(event, trigger, player) {
const target = event.target;
if (!isBattleActive() || !player.isAlive() || !target?.isAlive() ||
target === player || !player.isEnemyOf(target)) return;
if (!temporaryStrength?.capture || !temporaryStrength?.grant) throw new Error("预备打击缺少临时力量接口");
const battle = temporaryStrength.capture();
if (!battle) return;
const rule = cardUpgradeRule("mengsan_yubeidaji");
const upgraded = cardUpgradeLevel(event.card);
const damage = target.damage(upgraded ? rule.damage : rule.baseDamage, player);
damage.mengsanAttack_shuying = true;
await damage;
if (isBattleActive() && player.isAlive()) {
temporaryStrength.grant(player, upgraded ? rule.strength : rule.baseStrength, event, battle);
}
},
ai: { order: 8, result: { player: 0.8, target: -1.5 }, tag: { damage: 1 } },
cardPrompt(used) {
const rule = cardUpgradeRule("mengsan_yubeidaji");
const upgraded = cardUpgradeLevel(used);
return `梦三:主动使用消耗1费用。对一名敌方角色造成${upgraded ? rule.damage : rule.baseDamage}点伤害,然后在本回合内获得${upgraded ? rule.strength : rule.baseStrength}点力量。每回合使用次数不限。`;
},
};
translate.mengsan_yubeidaji = "预备打击";
translate.mengsan_yubeidaji_info = "梦三:主动使用消耗1费用。对一名敌方角色造成7点伤害,然后在本回合内获得2点力量。每回合使用次数不限。";
// STS2 Breakthrough: native life loss is paid once before the all-enemy attack.
card.mengsan_tupo = {
type: "basic", fullimage: true,
image: "ext:术樱包/mengsan/assets/cards/mengsan_tupo.png",
mengsanCost_shuying: cardCost({ name: "mengsan_tupo" }),
notarget: true, usable: Infinity,
enable(used, player, event) {
if (!isBattleActive() || !player.isAlive()) return false;
if (!game.filterPlayer(target => target !== player &&
target.isAlive() && player.isEnemyOf(target)).length) return false;
return !isActiveCardUse(event || _status.event, player) || canPayCard(player, used);
},
async content(event, trigger, player) {
if (!isBattleActive() || !player.isAlive()) return;
if (!game.filterPlayer(target => target !== player &&
target.isAlive() && player.isEnemyOf(target)).length) return;
// loseHp bypasses block and awaits the normal dying/rescue process.
await player.loseHp(1);
if (!isBattleActive() || !player.isAlive()) return;
const rule = cardUpgradeRule("mengsan_tupo");
const amount = cardUpgradeLevel(event.card) ? rule.damage : rule.baseDamage;
const targets = game.filterPlayer(target => target !== player &&
target.isAlive() && player.isEnemyOf(target));
for (const target of targets) {
if (!isBattleActive() || !player.isAlive()) break;
if (!target.isAlive() || !player.isEnemyOf(target)) continue;
player.line(target, "green");
const damage = target.damage(amount, player);
damage.mengsanAttack_shuying = true;
await damage;
}
},
ai: { order: 7, result: { player(player) { return player.hp <= 1 ? -10 : 1; } }, tag: { damage: 1 } },
cardPrompt(used) {
const rule = cardUpgradeRule("mengsan_tupo");
const amount = cardUpgradeLevel(used) ? rule.damage : rule.baseDamage;
return `梦三:主动使用消耗1费用。失去1点生命,然后对所有敌方角色造成${amount}点伤害。每回合使用次数不限。`;
},
};
translate.mengsan_tupo = "突破";
translate.mengsan_tupo_info = "梦三:主动使用消耗1费用。失去1点生命,然后对所有敌方角色造成9点伤害。每回合使用次数不限。";
// STS2 Headbutt: damage first, then choose from the actor's own real discard pile.
card.mengsan_touchui = {
type: "basic", fullimage: true,
image: "ext:术樱包/mengsan/assets/cards/mengsan_touchui.png",
mengsanCost_shuying: cardCost({ name: "mengsan_touchui" }),
usable: Infinity, selectTarget: 1,
enable(used, player, event) {
if (!isBattleActive() || !player.isAlive()) return false;
return !isActiveCardUse(event || _status.event, player) || canPayCard(player, used);
},
filterTarget(used, player, target) {
return target !== player && target.isAlive() && player.isEnemyOf(target);
},
async content(event, trigger, player) {
const target = event.target;
if (!isBattleActive() || !player.isAlive() || !target?.isAlive() ||
target === player || !player.isEnemyOf(target)) return;
if (typeof reclaimFromDiscard !== "function") throw new Error("头槌缺少个人弃牌回收接口");
const rule = cardUpgradeRule("mengsan_touchui");
const amount = cardUpgradeLevel(event.card) ? rule.damage : rule.baseDamage;
const damage = target.damage(amount, player);
damage.mengsanAttack_shuying = true;
await damage;
// Defeating one enemy does not prevent recovery unless combat actually ends.
if (isBattleActive() && player.isAlive()) await reclaimFromDiscard(player);
},
ai: { order: 6, result: { player: 0.4, target: -1.5 }, tag: { damage: 1 } },
cardPrompt(used) {
const rule = cardUpgradeRule("mengsan_touchui");
const amount = cardUpgradeLevel(used) ? rule.damage : rule.baseDamage;
return '梦三:主动使用消耗1费用。对一名敌方角色造成' + amount +
'点伤害,然后选择个人弃牌堆中的一张牌放到个人抽牌堆顶部。每回合使用次数不限。';
},
};
translate.mengsan_touchui = "头槌";
translate.mengsan_touchui_info = "梦三:主动使用消耗1费用。对一名敌方角色造成9点伤害," +
"然后选择个人弃牌堆中的一张牌放到个人抽牌堆顶部。每回合使用次数不限。";
// STS2 Iron Wave: obtain block before resolving the attack.
card.mengsan_tiezhanbo = {
type: "basic", fullimage: true,
image: "ext:术樱包/mengsan/assets/cards/mengsan_tiezhanbo.png",
mengsanCost_shuying: cardCost({ name: "mengsan_tiezhanbo" }),
usable: Infinity, selectTarget: 1,
enable(used, player, event) {
if (!isBattleActive() || !player.isAlive()) return false;
return !isActiveCardUse(event || _status.event, player) || canPayCard(player, used);
},
filterTarget(used, player, target) {
return target !== player && target.isAlive() && player.isEnemyOf(target);
},
async content(event, trigger, player) {
const target = event.target;
const canResolve = () => isBattleActive() && player.isAlive() &&
target?.isAlive() && target !== player && player.isEnemyOf(target);
if (!canResolve()) return;
const rule = cardUpgradeRule("mengsan_tiezhanbo");
const upgraded = cardUpgradeLevel(event.card) > 0;
await player.changeHujia(upgraded ? rule.block : rule.baseBlock);
// Block triggers may end combat or remove the acting player/target.
if (!canResolve()) return;
const damage = target.damage(upgraded ? rule.damage : rule.baseDamage, player);
damage.mengsanAttack_shuying = true;
await damage;
},
ai: { order: 7, result: { player: 0.6, target: -1.2 }, tag: { damage: 1 } },
cardPrompt(used) {
const rule = cardUpgradeRule("mengsan_tiezhanbo");
const upgraded = cardUpgradeLevel(used) > 0;
const block = upgraded ? rule.block : rule.baseBlock;
const damage = upgraded ? rule.damage : rule.baseDamage;
return '梦三:主动使用消耗1费用。获得' + block +
'点格挡(护甲),然后对一名敌方角色造成' + damage +
'点伤害。每回合使用次数不限。';
},
};
translate.mengsan_tiezhanbo = "铁斩波";
translate.mengsan_tiezhanbo_info = "梦三:主动使用消耗1费用。获得5点格挡(护甲)," +
"然后对一名敌方角色造成5点伤害。每回合使用次数不限。";
// STS2 Twin Strike: each hit resolves on the same target separately.
card.mengsan_shuangchongdaji = {
type: "basic", fullimage: true,
image: "ext:术樱包/mengsan/assets/cards/mengsan_shuangchongdaji.png",
mengsanCost_shuying: cardCost({ name: "mengsan_shuangchongdaji" }),
usable: Infinity, selectTarget: 1,
enable(used, player, event) {
if (!isBattleActive() || !player.isAlive()) return false;
return !isActiveCardUse(event || _status.event, player) || canPayCard(player, used);
},
filterTarget(used, player, target) {
return target !== player && target.isAlive() && player.isEnemyOf(target);
},
async content(event, trigger, player) {
const target = event.target;
const rule = cardUpgradeRule("mengsan_shuangchongdaji");
const amount = cardUpgradeLevel(event.card) ? rule.damage : rule.baseDamage;
for (let hitIndex = 0; hitIndex < rule.hits; hitIndex++) {
if (!isBattleActive() || !player.isAlive() || !target?.isAlive() ||
target === player || !player.isEnemyOf(target)) break;
const damage = target.damage(amount, player);
damage.mengsanAttack_shuying = true;
await damage;
}
},
ai: { order: 6, result: { target: -1.5 }, tag: { damage: 1 } },
cardPrompt(used) {
const rule = cardUpgradeRule("mengsan_shuangchongdaji");
const amount = cardUpgradeLevel(used) ? rule.damage : rule.baseDamage;
return '梦三:主动使用消耗1费用。对一名敌方角色造成' +
amount + '点伤害两次,两次伤害分别结算。每回合使用次数不限。';
},
};
translate.mengsan_shuangchongdaji = "双重打击";
translate.mengsan_shuangchongdaji_info = "梦三:主动使用消耗1费用。对一名敌方角色造成5点伤害两次," +
"两次伤害分别结算。每回合使用次数不限。";
// STS2 Thunderclap: hit each living enemy before adding new vulnerable.
card.mengsan_shandianpili = {
type: "basic", fullimage: true,
image: "ext:术樱包/mengsan/assets/cards/mengsan_shandianpili.png",
mengsanCost_shuying: cardCost({ name: "mengsan_shandianpili" }),
notarget: true, usable: Infinity,
enable(used, player, event) {
if (!isBattleActive() || !player.isAlive()) return false;
if (!game.filterPlayer(target => target !== player &&
target.isAlive() && player.isEnemyOf(target)).length) return false;
return !isActiveCardUse(event || _status.event, player) || canPayCard(player, used);
},
async content(event, trigger, player) {
if (!isBattleActive() || !player.isAlive()) return;
const rule = cardUpgradeRule("mengsan_shandianpili");
const amount = cardUpgradeLevel(event.card) ? rule.damage : rule.baseDamage;
const targets = game.filterPlayer(target => target !== player &&
target.isAlive() && player.isEnemyOf(target));
for (const target of targets) {
if (!isBattleActive() || !player.isAlive()) break;
if (!target.isAlive() || !player.isEnemyOf(target)) continue;
player.line(target, "green");
const damage = target.damage(amount, player);
damage.mengsanAttack_shuying = true;
await damage;
}
// 伤害结束后重新取得存活敌人,包含异蛙亡语刚生成的扭动虫。
for (const target of game.filterPlayer(target => target !== player && target.isAlive() && player.isEnemyOf(target))) {
if (!isBattleActive() || !player.isAlive()) break;
if (applyMengsanDebuff(target, "vulnerable", rule.vulnerable, player))
game.log(target, `获得${rule.vulnerable}层易伤`);
}
},
ai: { order: 7, result: { player: 1 }, tag: { damage: 1 } },
cardPrompt(used) {
const rule = cardUpgradeRule("mengsan_shandianpili");
const amount = cardUpgradeLevel(used) ? rule.damage : rule.baseDamage;
return `梦三:主动使用消耗1费用。对所有敌方角色造成${amount}点伤害,然后给予其1层易伤。` +
"易伤使受到的攻击伤害增加50%(向上取整),每个自身回合结束减少1层。每回合使用次数不限。";
},
};
translate.mengsan_shandianpili = "闪电霹雳";
translate.mengsan_shandianpili_info = "梦三:主动使用消耗1费用。对所有敌方角色造成4点伤害,然后给予其1层易伤。" +
"易伤使受到的攻击伤害增加50%(向上取整),每个自身回合结束减少1层。每回合使用次数不限。";
// Each throw resolves separately and samples the still-living enemies again.
card.mengsan_feijianhuixuanbiao = {
type: "basic", fullimage: true,
image: "ext:术樱包/mengsan/assets/cards/mengsan_feijianhuixuanbiao.png",
mengsanCost_shuying: cardCost({ name: "mengsan_feijianhuixuanbiao" }),
notarget: true, usable: Infinity,
enable(used, player, event) {
if (!isBattleActive() || !player.isAlive()) return false;
if (!game.filterPlayer(target => target !== player &&
target.isAlive() && player.isEnemyOf(target)).length) return false;
return !isActiveCardUse(event || _status.event, player) || canPayCard(player, used);
},
async content(event, trigger, player) {
const rule = cardUpgradeRule("mengsan_feijianhuixuanbiao");
const count = cardUpgradeLevel(event.card) ? rule.hits : rule.baseHits;
for (let hitIndex = 0; hitIndex < count; hitIndex++) {
if (!isBattleActive() || !player.isAlive()) break;
const enemies = game.filterPlayer(target => target !== player &&
target.isAlive() && player.isEnemyOf(target));
if (!enemies.length) break;
// Use the engine helper with an inclusive index range.
const target = enemies[get.rand(0, enemies.length - 1)];
player.line(target, "green");
const damage = target.damage(rule.damage, player);
damage.mengsanAttack_shuying = true;
await damage;
}
},
ai: { order: 6, result: { player: 1 }, tag: { damage: 1 } },
cardPrompt(used) {
const rule = cardUpgradeRule("mengsan_feijianhuixuanbiao");
const count = cardUpgradeLevel(used) ? rule.hits : rule.baseHits;
return `梦三:主动使用消耗1费用。每次对随机一名敌方角色造成${rule.damage}点伤害,共${count}次。` +
"每次攻击重新随机选择存活的敌人,可以重复命中同一敌人。" +
"每回合使用次数不限。";
},
};
translate.mengsan_feijianhuixuanbiao = "飞剑回旋镖";
translate.mengsan_feijianhuixuanbiao_info = "梦三:主动使用消耗1费用。随机对敌人造成3点伤害3次。" +
"每次攻击重新随机选择存活的敌人,可以重复命中同一敌人。每回合使用次数不限。";
// Resolve damage before drawing from the acting player's mode-owned pile.
card.mengsan_jianbingdaji = {
type: "basic", fullimage: true,
image: "ext:术樱包/mengsan/assets/cards/mengsan_jianbingdaji.png",
mengsanCost_shuying: cardCost({ name: "mengsan_jianbingdaji" }),
usable: Infinity, selectTarget: 1,
enable(used, player, event) {
if (!isBattleActive() || !player.isAlive()) return false;
return !isActiveCardUse(event || _status.event, player) || canPayCard(player, used);
},
filterTarget(used, player, target) {
return target !== player && target.isAlive() && player.isEnemyOf(target);
},
async content(event, trigger, player) {
if (!isBattleActive() || !player.isAlive()) return;
const rule = cardUpgradeRule("mengsan_jianbingdaji");
const upgraded = cardUpgradeLevel(event.card) > 0;
const damage = event.target.damage(upgraded ? rule.damage : rule.baseDamage, player);
damage.mengsanAttack_shuying = true;
await damage;
// Defeating the last enemy may dispose the personal pile during damage.
if (isBattleActive() && player.isAlive()) {
await player.draw(upgraded ? rule.draw : rule.baseDraw);
}
},
ai: { order: 6, result: { player: 0.6, target: -1.5 }, tag: { damage: 1, draw: 1 } },
cardPrompt(used) {
const rule = cardUpgradeRule("mengsan_jianbingdaji");
const upgraded = cardUpgradeLevel(used) > 0;
const damage = upgraded ? rule.damage : rule.baseDamage;
const draw = upgraded ? rule.draw : rule.baseDraw;
return `梦三:主动使用消耗1费用。对一名敌方角色造成${damage}点伤害,然后抽${draw}张牌。` +
"每回合使用次数不限。";
},
};
translate.mengsan_jianbingdaji = "剑柄打击";
translate.mengsan_jianbingdaji_info = "梦三:主动使用消耗1费用。对一名敌方角色造成9点伤害,然后抽1张牌。" +
"每回合使用次数不限。";
// Block determines base damage without spending the acting player's block.
card.mengsan_quanshenzhuangji = {
type: "basic", fullimage: true,
image: "ext:术樱包/mengsan/assets/cards/mengsan_quanshenzhuangji.png",
mengsanCost_shuying: cardCost({ name: "mengsan_quanshenzhuangji" }),
usable: Infinity, selectTarget: 1,
enable(used, player, event) {
if (!isBattleActive() || !player.isAlive()) return false;
return !isActiveCardUse(event || _status.event, player) || canPayCard(player, used);
},
filterTarget(used, player, target) {
return target !== player && target.isAlive() && player.isEnemyOf(target);
},
async content(event, trigger, player) {
if (!isBattleActive() || !player.isAlive()) return;
const block = Number.isFinite(player.hujia) ? Math.max(0, player.hujia) : 0;
const storedStrength = player.storage?.mengsanStrength_shuying;
const strength = Number.isInteger(storedStrength) ? storedStrength : 0;
// Native damage(0) stops before damageBegin1, so strength must be included here.
const temporary = temporaryStrength?.amount(player) || 0;
const damage = event.target.damage(Math.max(0, block + strength + temporary), player);
damage.mengsanAttack_shuying = true;
damage.mengsanCardStrengthApplied_shuying = true;
damage.mengsanSetupStrengthApplied_shuying = true;
await damage;
},
ai: {
order: 5,
result: { target(used, player) { return player.hujia > 0 ? -1.5 : 0; } },
tag: { damage: 1 },
},
cardPrompt(used) {
const cost = cardCost(used || { name: "mengsan_quanshenzhuangji" });
return `梦三:主动使用消耗${cost}费用。对一名敌方角色造成等同于你当前格挡(护甲)值的伤害。` +
"不消耗格挡,可以受到力量加成。" +
"每回合使用次数不限。";
},
};
translate.mengsan_quanshenzhuangji = "全身撞击";
translate.mengsan_quanshenzhuangji_info = "梦三:主动使用消耗1费用。对一名敌方角色造成等同于你当前格挡(护甲)值的伤害。" +
"不消耗格挡,可以受到力量加成。每回合使用次数不限。";
// Double only the surviving target's existing vulnerable after damage resolves.
card.mengsan_rongrongzhiquan = {
type: "basic", fullimage: true,
image: "ext:术樱包/mengsan/assets/cards/mengsan_rongrongzhiquan.png",
mengsanCost_shuying: cardCost({ name: "mengsan_rongrongzhiquan" }),
mengsanExhaust_shuying: true,
usable: Infinity, selectTarget: 1,
enable(used, player, event) {
if (!isBattleActive() || !player.isAlive()) return false;
return !isActiveCardUse(event || _status.event, player) || canPayCard(player, used);
},
filterTarget(used, player, target) {
return target !== player && target.isAlive() && player.isEnemyOf(target);
},
async content(event, trigger, player) {
if (!isBattleActive() || !player.isAlive()) return;
const rule = cardUpgradeRule("mengsan_rongrongzhiquan");
const target = event.target;
const damage = target.damage(cardUpgradeLevel(event.card) ? rule.damage : rule.baseDamage, player);
damage.mengsanAttack_shuying = true;
await damage;
if (!target.isAlive() || !isBattleActive()) return;
const stacks = target.storage?.mengsanVulnerable_shuying;
if (!Number.isSafeInteger(stacks) || stacks <= 0) return;
const doubled = stacks * 2;
if (!Number.isSafeInteger(doubled)) throw new RangeError("易伤层数越界");
if (applyMengsanDebuff(target, "vulnerable", stacks, player))
game.log(target, `易伤层数从${stacks}翻倍为${doubled}`);
},
ai: { order: 6, result: { target: -1.5 }, tag: { damage: 1 } },
cardPrompt(used) {
const rule = cardUpgradeRule("mengsan_rongrongzhiquan");
const damage = cardUpgradeLevel(used) ? rule.damage : rule.baseDamage;
return `梦三:主动使用消耗1费用。对一名敌方角色造成${damage}点伤害,然后将该目标已有的易伤层数翻倍。` +
"没有易伤时不会新增易伤。消耗:使用后退出本场战斗,不进入弃牌堆或洗牌。";
},
};
translate.mengsan_rongrongzhiquan = "熔融之拳";
translate.mengsan_rongrongzhiquan_info = "梦三:主动使用消耗1费用。对一名敌方角色造成10点伤害,然后将该目标已有的易伤层数翻倍。" +
"消耗:使用后退出本场战斗。";
// STS2【愤怒】:攻击后生成独立的战斗复制品,不扩充永久牌组。
card.mengsan_fennu = {
type: "basic", fullimage: true,
image: "ext:术樱包/mengsan/assets/cards/mengsan_fennu.png",
mengsanCost_shuying: cardCost({ name: "mengsan_fennu" }),
enable(used, player, event) {
return !isActiveCardUse(event || _status.event, player) ||
canPayCard(player, used);
},
usable: Infinity, selectTarget: 1,
filterTarget(used, player, target) {
return target !== player && player.isEnemyOf(target);
},
async content(event, trigger, player) {
if (typeof copyToDiscard !== "function") throw new Error("愤怒缺少战斗复制接口");
const amount = cardUpgradeLevel(event.card)
? cardUpgradeRule("mengsan_fennu").damage : 6;
const damage = event.target.damage(amount, player);
damage.mengsanAttack_shuying = true;
await damage;
if (copyToDiscard(player, event.card, event.cards)) {
game.log(player, "将一张【愤怒】的复制品加入个人弃牌堆");
}
},
ai: { order: 6, result: { target: -1 }, tag: { damage: 1 } },
cardPrompt(used) {
const amount = cardUpgradeLevel(used)
? cardUpgradeRule("mengsan_fennu").damage : 6;
return `梦三:主动使用消耗0费用。对一名敌方角色造成${amount}点伤害,` +
"然后将一张此牌的复制品加入你的个人弃牌堆。" +
"复制品保留原牌状态和词缀,仅存在于本场战斗。";
},
};
translate.mengsan_fennu = "愤怒";
translate.mengsan_fennu_info = "梦三:主动使用消耗0费用。对一名敌方角色造成6点伤害," +
"然后将一张此牌的复制品加入你的个人弃牌堆。复制品保留原牌状态和词缀,仅存在于本场战斗。" +
"每回合使用次数不限。";
// STS2【放血】:先等待原生生命流失及濒死救援,再增加当前战斗费用。
card.mengsan_fangxue = {
type: "basic", fullimage: true,
image: "ext:术樱包/mengsan/assets/cards/mengsan_fangxue.png",
mengsanCost_shuying: cardCost({ name: "mengsan_fangxue" }),
enable(used, player, event) {
return isBattleActive() && Boolean(battleEnergy?.canUse(player)) &&
(!isActiveCardUse(event || _status.event, player) || canPayCard(player, used));
},
usable: Infinity, toself: true, selectTarget: -1,
filterTarget(used, player, target) { return target === player; },
async content(event, trigger, player) {
if (typeof battleEnergy?.capture !== "function" || typeof battleEnergy?.grant !== "function") {
throw new Error("放血缺少战斗费用接口");
}
const rule = cardUpgradeRule("mengsan_fangxue");
const amount = cardUpgradeLevel(event.card) ? rule.energy : rule.baseEnergy;
const battle = battleEnergy.capture(player, amount);
if (!battle) return;
await player.loseHp(rule.loseHp);
battleEnergy.grant(player, amount, battle);
},
ai: { order: 9, result: { player: player => player.hp > 3 ? 1 : -1 } },
cardPrompt(used) {
const rule = cardUpgradeRule("mengsan_fangxue");
const amount = cardUpgradeLevel(used) ? rule.energy : rule.baseEnergy;
return "梦三:主动使用消耗0费用。失去" + rule.loseHp + "点生命,然后获得" + amount + "点费用。";
},
};
translate.mengsan_fangxue = "放血";
translate.mengsan_fangxue_info = "梦三:主动使用消耗0费用。失去3点生命,然后获得2点费用。";
// STS2【坚毅】:先获得格挡,再消耗一张自身当时的手牌。
card.mengsan_jianyi = {
type: "basic", fullimage: true,
image: "ext:术樱包/mengsan/assets/cards/mengsan_jianyi.png",
mengsanCost_shuying: cardCost({ name: "mengsan_jianyi" }),
enable(used, player, event) {
return isBattleActive() && Boolean(player?.isAlive?.()) &&
(!isActiveCardUse(event || _status.event, player) || canPayCard(player, used));
},
usable: Infinity, toself: true, selectTarget: -1,
filterTarget(used, player, target) { return target === player; },
async content(event, trigger, player) {
if (typeof handExhaust?.capture !== "function" || typeof handExhaust?.exhaust !== "function") {
throw new Error("坚毅缺少手牌消耗接口");
}
const battle = handExhaust.capture(player);
if (!battle) return;
const rule = cardUpgradeRule("mengsan_jianyi");
const current = cardUpgradeLevel(event.card);
await player.changeHujia(current ? rule.block : rule.baseBlock);
await handExhaust.exhaust(player, event, battle, Boolean(current && rule.chooseExhaust));
},
ai: { order: 7, result: { player: 1 } },
cardPrompt(used) {
const rule = cardUpgradeRule("mengsan_jianyi");
const current = cardUpgradeLevel(used);
const block = current ? rule.block : rule.baseBlock;
return "梦三:主动使用消耗1费用。获得" + block + "点格挡(护甲),然后" +
(current && rule.chooseExhaust ? "选择消耗" : "随机消耗") +
"一张你的手牌(退出本场战斗,不进入弃牌堆或洗牌)。没有手牌时不消耗牌。";
},
};
translate.mengsan_jianyi = "坚毅";
translate.mengsan_jianyi_info = "梦三:主动使用消耗1费用。获得7点格挡(护甲),然后随机消耗一张你的手牌(退出本场战斗,不进入弃牌堆或洗牌)。没有手牌时不消耗牌。";
// STS2【破灭】:免费使用自己的堆顶原牌,再将这一原牌消耗。
card.mengsan_pomie = {
type: "basic", fullimage: true,
image: "ext:术樱包/mengsan/assets/cards/mengsan_pomie.png",
mengsanCost_shuying: cardCost({ name: "mengsan_pomie" }),
enable(used, player, event) {
return isBattleActive() && Boolean(player?.isAlive?.()) &&
(!isActiveCardUse(event || _status.event, player) || canPayCard(player, used));
},
usable: Infinity, toself: true, selectTarget: -1,
filterTarget(used, player, target) { return target === player; },
async content(event, trigger, player) {
if (typeof playDrawTop !== "function") throw new Error("破灭缺少堆顶使用接口");
await playDrawTop(player);
},
ai: { order: 6, result: { player: 1 } },
cardPrompt(used) {
return "梦三:主动使用消耗" + cardCost(used) +
"费用。免费使用你抽牌堆顶部的牌,然后将该原牌消耗(退出本场战斗,不进入弃牌堆或洗牌)。抽牌堆为空时不生效。";
},
};
translate.mengsan_pomie = "破灭";
translate.mengsan_pomie_info = "梦三:主动使用消耗1费用。免费使用你抽牌堆顶部的牌,然后将该原牌消耗(退出本场战斗,不进入弃牌堆或洗牌)。抽牌堆为空时不生效。";
// STS2【耸肩无视】:先获得格挡,再抽一张牌。
card.mengsan_songjianwushi = {
type: "basic", fullimage: true,
image: "ext:术樱包/mengsan/assets/cards/mengsan_songjianwushi.png",
mengsanCost_shuying: cardCost({ name: "mengsan_songjianwushi" }),
enable(used, player, event) {
return isBattleActive() && Boolean(player?.isAlive?.()) &&
(!isActiveCardUse(event || _status.event, player) || canPayCard(player, used));
},
usable: Infinity, toself: true, selectTarget: -1,
filterTarget(used, player, target) { return target === player; },
async content(event, trigger, player) {
if (typeof blockDraw !== "function") throw new Error("耸肩无视缺少格挡抽牌接口");
const rule = cardUpgradeRule("mengsan_songjianwushi");
await blockDraw(player, cardUpgradeLevel(event.card) ? rule.block : rule.baseBlock, rule.draw);
},
ai: { order: 7, result: { player: 1 }, tag: { draw: 1 } },
cardPrompt(used) {
const rule = cardUpgradeRule("mengsan_songjianwushi");
const block = cardUpgradeLevel(used) ? rule.block : rule.baseBlock;
return "梦三:主动使用消耗1费用。获得" + block + "点格挡(护甲),然后抽" + rule.draw + "张牌。";
},
};
translate.mengsan_songjianwushi = "耸肩无视";
translate.mengsan_songjianwushi_info = "梦三:主动使用消耗1费用。获得8点格挡(护甲),然后抽1张牌。";
// STS2【暴走】:先攻击,再仅增加本场这张实体牌的伤害。
card.mengsan_baozou = {
type: "basic", fullimage: true,
image: "ext:术樱包/mengsan/assets/cards/mengsan_baozou.png",
mengsanCost_shuying: cardCost({ name: "mengsan_baozou" }),
usable: Infinity, selectTarget: 1,
enable(used, player, event) {
return isBattleActive() && Boolean(player?.isAlive?.()) &&
(!isActiveCardUse(event || _status.event, player) || canPayCard(player, used));
},
filterTarget(used, player, target) {
return target !== player && target.isAlive() && player.isEnemyOf(target);
},
async content(event, trigger, player) {
if (typeof rampage?.apply !== "function") throw new Error("暴走缺少战斗累积接口");
const rule = cardUpgradeRule("mengsan_baozou");
const original = rampage.original(event) || event.card;
await rampage.apply(player, event, rule.damage,
cardUpgradeLevel(original) ? rule.increase : rule.baseIncrease);
},
ai: { order: 5, result: { target: -1.5 }, tag: { damage: 1 } },
cardPrompt(used) {
const rule = cardUpgradeRule("mengsan_baozou");
const source = rampage?.card(used) || used;
const increase = cardUpgradeLevel(source) ? rule.increase : rule.baseIncrease;
const damage = rampage?.damage(used) ?? rule.damage;
return "梦三:主动使用消耗1费用。对一名敌方角色造成" + damage +
"点伤害,然后将这张牌在本场战斗中的伤害增加" + increase + "点。";
},
};
translate.mengsan_baozou = "暴走";
translate.mengsan_baozou_info = card.mengsan_baozou.cardPrompt({ name: "mengsan_baozou" });
// STS2【怨恨】:读取本回合实际生命损失,固定次数后逐次原生攻击。
card.mengsan_yuanhen = {
type: "basic", fullimage: true,
image: "ext:术樱包/mengsan/assets/cards/mengsan_yuanhen.png",
mengsanCost_shuying: cardCost({ name: "mengsan_yuanhen" }),
usable: Infinity, selectTarget: 1,
enable(used, player, event) {
return isBattleActive() && Boolean(player?.isAlive?.()) &&
(!isActiveCardUse(event || _status.event, player) || canPayCard(player, used));
},
filterTarget(used, player, target) {
return target !== player && target.isAlive() && player.isEnemyOf(target);
},
async content(event, trigger, player) {
if (typeof spite !== "function") throw new Error("怨恨缺少战斗攻击接口");
const rule = cardUpgradeRule("mengsan_yuanhen");
await spite(player, event, rule.damage,
cardUpgradeLevel(event.card) ? rule.hits : rule.baseHits);
},
ai: { order: 4, result: { target: -1.5 }, tag: { damage: 1 } },
cardPrompt(used) {
const rule = cardUpgradeRule("mengsan_yuanhen");
const hits = cardUpgradeLevel(used) ? rule.hits : rule.baseHits;
return "梦三:主动使用消耗0费用。对一名敌方角色造成5点伤害。" +
"如果你在本回合失去过生命值,则改为连续攻击" + hits + "次,每次造成5点伤害,分别结算。";
},
};
translate.mengsan_yuanhen = "怨恨";
translate.mengsan_yuanhen_info = card.mengsan_yuanhen.cardPrompt({ name: "mengsan_yuanhen" });
// STS2【旋风斩】:X 在原生用牌支付时固定,逐次重取存活敌人。
card.mengsan_xuanfengzhan = {
type: "basic", fullimage: true,
image: "ext:术樱包/mengsan/assets/cards/mengsan_xuanfengzhan.png",
mengsanCost_shuying: "X", mengsanXCost_shuying: true,
notarget: true, usable: Infinity,
enable(used, player, event) {
return isBattleActive() && Boolean(player?.isAlive?.()) &&
game.filterPlayer(target => target !== player && target.isAlive() && player.isEnemyOf(target)).length > 0 &&
(!isActiveCardUse(event || _status.event, player) || canPayCard(player, used, event || _status.event));
},
async content(event, trigger, player) {
if (typeof whirlwind !== "function") throw new Error("旋风斩缺少X费用攻击接口");
const rule = cardUpgradeRule("mengsan_xuanfengzhan");
await whirlwind(player, event, cardUpgradeLevel(event.card) ? rule.damage : rule.baseDamage);
},
ai: { order: 3, result: { player: 1 }, tag: { damage: 1, multitarget: 1 } },
cardPrompt(used) {
const rule = cardUpgradeRule("mengsan_xuanfengzhan");
const amount = cardUpgradeLevel(used) ? rule.damage : rule.baseDamage;
return "梦三:X费用。消耗当前剩余费用,对所有敌方角色各造成" + amount +
"点伤害X次,每次分别结算。X为本次用于攻击的费用;缠结时先扣除1点附加费用。X为0时不造成伤害。";
},
};
translate.mengsan_xuanfengzhan = "旋风斩";
translate.mengsan_xuanfengzhan_info = card.mengsan_xuanfengzhan.cardPrompt({ name: "mengsan_xuanfengzhan" });
// STS2【欺凌】:按本次目标当前易伤层数计算一次基础攻击,不消耗易伤。
card.mengsan_qiling = {
type: "basic", fullimage: true,
image: "ext:术樱包/mengsan/assets/cards/mengsan_qiling.png",
mengsanCost_shuying: cardCost({ name: "mengsan_qiling" }),
usable: Infinity, selectTarget: 1,
enable(used, player, event) {
return isBattleActive() && Boolean(player?.isAlive?.()) &&
(!isActiveCardUse(event || _status.event, player) || canPayCard(player, used));
},
filterTarget(used, player, target) {
return target !== player && target.isAlive() && player.isEnemyOf(target);
},
async content(event, trigger, player) {
const target = event.target;
if (!isBattleActive() || !player.isAlive() || !target?.isAlive() ||
target === player || !player.isEnemyOf(target)) return;
const layers = target.storage?.mengsanVulnerable_shuying ?? 0;
if (!Number.isSafeInteger(layers) || layers < 0) throw new RangeError("欺凌易伤层数无效");
const rule = cardUpgradeRule("mengsan_qiling");
const perLayer = cardUpgradeLevel(event.card) ? rule.perVulnerable : rule.basePerVulnerable;
const amount = rule.baseDamage + layers * perLayer;
if (!Number.isSafeInteger(amount) || amount < 0) throw new RangeError("欺凌伤害越界");
const damage = target.damage(amount, player);
damage.mengsanAttack_shuying = true;
await damage;
},
ai: { order: 6, result: { target: -1.5 }, tag: { damage: 1 } },
cardPrompt(used) {
const rule = cardUpgradeRule("mengsan_qiling");
const perLayer = cardUpgradeLevel(used) ? rule.perVulnerable : rule.basePerVulnerable;
return "梦三:主动使用消耗0费用。对一名敌方角色造成4点伤害," +
"该目标每有1层易伤,额外增加" + perLayer + "点伤害。不消耗易伤层数。每回合使用次数不限。";
},
};
translate.mengsan_qiling = "欺凌";
translate.mengsan_qiling_info = "梦三:主动使用消耗0费用。对一名敌方角色造成4点伤害," +
"该目标每有1层易伤,额外增加2点伤害。不消耗易伤层数。每回合使用次数不限。";
// STS2【血墙】:等待原生生命流失及濒死救援,再给同一战斗中的使用者格挡。
card.mengsan_xueqiang = {
type: "basic", fullimage: true,
image: "ext:术樱包/mengsan/assets/cards/mengsan_xueqiang.png",
mengsanCost_shuying: cardCost({ name: "mengsan_xueqiang" }),
enable(used, player, event) {
return isBattleActive() && Boolean(lifeLossBlock?.canUse(player)) &&
(!isActiveCardUse(event || _status.event, player) || canPayCard(player, used));
},
usable: Infinity, toself: true, selectTarget: -1,
filterTarget(used, player, target) { return target === player; },
async content(event, trigger, player) {
if (typeof lifeLossBlock?.apply !== "function") throw new Error("血墙缺少生命流失格挡接口");
const rule = cardUpgradeRule("mengsan_xueqiang");
await lifeLossBlock.apply(player, rule.loseHp,
cardUpgradeLevel(event.card) ? rule.block : rule.baseBlock);
},
ai: { order: 7, result: { player: player => player.hp > 2 ? 1 : -1 } },
cardPrompt(used) {
const rule = cardUpgradeRule("mengsan_xueqiang");
const block = cardUpgradeLevel(used) ? rule.block : rule.baseBlock;
return "梦三:主动使用消耗2费用。失去2点生命,然后获得" + block + "点格挡(护甲)。";
},
};
translate.mengsan_xueqiang = "血墙";
translate.mengsan_xueqiang_info = "梦三:主动使用消耗2费用。失去2点生命,然后获得16点格挡(护甲)。";
// STS2【战栗】:施加易伤走既有人工制品入口,消耗走原生用牌及个人牌堆。
card.mengsan_zhanli = {
type: "basic", fullimage: true,
image: "ext:术樱包/mengsan/assets/cards/mengsan_zhanli.png",
mengsanCost_shuying: cardCost({ name: "mengsan_zhanli" }),
mengsanExhaust_shuying: true,
usable: Infinity, selectTarget: 1,
enable(used, player, event) {
return isBattleActive() && Boolean(player?.isAlive?.()) &&
(!isActiveCardUse(event || _status.event, player) || canPayCard(player, used));
},
filterTarget(used, player, target) {
return target !== player && target.isAlive() && player.isEnemyOf(target);
},
async content(event, trigger, player) {
const target = event.target;
if (!isBattleActive() || !player.isAlive() || !target?.isAlive() ||
target === player || !player.isEnemyOf(target)) return;
const rule = cardUpgradeRule("mengsan_zhanli");
const amount = cardUpgradeLevel(event.card) ? rule.vulnerable : rule.baseVulnerable;
if (applyMengsanDebuff(target, "vulnerable", amount, player))
game.log(target, `获得${amount}层易伤`);
},
ai: { order: 9, result: { target: -1.2 } },
cardPrompt(used) {
const rule = cardUpgradeRule("mengsan_zhanli");
const amount = cardUpgradeLevel(used) ? rule.vulnerable : rule.baseVulnerable;
return "梦三:主动使用消耗1费用。给予一名敌方角色" + amount + "层易伤。" +
"易伤使受到的攻击伤害增加50%(向上取整),每个自身回合结束减少1层。" +
"消耗(使用后退出本场战斗,不进入弃牌堆或洗牌)。";
},
};
translate.mengsan_zhanli = "战栗";
translate.mengsan_zhanli_info = "梦三:主动使用消耗1费用。给予一名敌方角色3层易伤。" +
"易伤使受到的攻击伤害增加50%(向上取整),每个自身回合结束减少1层。" +
"消耗(使用后退出本场战斗,不进入弃牌堆或洗牌)。";
// STS2【武装】:格挡结算后只强化当前战斗的真实手牌。
card.mengsan_wuzhuang = {
type: "basic", fullimage: true,
image: "ext:术樱包/mengsan/assets/cards/mengsan_wuzhuang.png",
mengsanCost_shuying: cardCost({ name: "mengsan_wuzhuang" }),
enable(used, player, event) {
return isBattleActive() && Boolean(player?.isAlive?.()) &&
(!isActiveCardUse(event || _status.event, player) || canPayCard(player, used));
},
usable: Infinity, toself: true, selectTarget: -1,
filterTarget(used, player, target) { return target === player; },
async content(event, trigger, player) {
if (typeof blockUpgrade !== "function") throw new Error("武装缺少格挡强化接口");
await blockUpgrade(player, event, cardUpgradeRule("mengsan_wuzhuang").block,
Boolean(cardUpgradeLevel(event.card)));
},
ai: { order: 8, result: { player: 1 } },
cardPrompt(used) {
const selection = cardUpgradeLevel(used) ? "所有牌" : "一张牌";
return "梦三:主动使用消耗1费用。获得5点格挡(护甲),然后强化你手牌中的" + selection + "(仅本场战斗)。";
},
};
translate.mengsan_wuzhuang = "武装";
translate.mengsan_wuzhuang_info = "梦三:主动使用消耗1费用。获得5点格挡(护甲),然后强化你手牌中的一张牌(仅本场战斗)。";
// 【防御】沿用梦三护甲事件,保留脆弱与伤害抵扣等既有规则。
card.mengsan_fangyu = {
type: "basic",
fullimage: true,
image: "ext:术樱包/mengsan/assets/cards/mengsan_fangyu.png",
mengsanCost_shuying: cardCost({ name: "mengsan_fangyu" }),
enable(used, player, event) {
return !isActiveCardUse(event || _status.event, player) ||
canPayCard(player, used);
},
usable: Infinity,
toself: true,
selectTarget: -1,
filterTarget(used, player, target) { return target === player; },
async content(event, trigger, player) {
await player.changeHujia(cardUpgradeLevel(event.card) ? 8 : 5);
},
ai: { order: 7, result: { target: 1 } },
cardPrompt(used) { return `梦三:主动使用消耗1费用。获得${cardUpgradeLevel(used) ? 8 : 5}点格挡(护甲)。`; },
};
translate.mengsan_fangyu = "防御";
translate.mengsan_fangyu_info =
"梦三:主动使用消耗1费用。出牌阶段,对自己使用,获得5点格挡(护甲)。" +
"每回合使用次数不限。";
// 【桃】的回复量也由梦三牌本体提供;救援仍不消耗费用。
return { cards:card, translate, names:Object.keys(card) };
}
+140
View File
@@ -0,0 +1,140 @@
import { ironcladCards, ironcladUpgradeRules } from "./data.js";
import { LIUBEI_CARD_OWNER } from "./names.js";
import { liubeiCardTraits } from "./traits.js";
const legacyDefinitions = [
{
"name": "mengsan_baozou",
"category": "damage"
},
{
"name": "mengsan_yuanhen",
"category": "damage"
},
{
"name": "mengsan_xuanfengzhan",
"category": "damage"
},
{
"name": "mengsan_qiling",
"category": "damage"
},
{
"name": "mengsan_xueqiang",
"category": "utility"
},
{
"name": "mengsan_zhanli",
"category": "utility"
},
{
"name": "mengsan_wuzhuang",
"category": "utility"
},
{
"name": "mengsan_songjianwushi",
"category": "utility"
},
{
"name": "mengsan_pomie",
"category": "utility"
},
{
"name": "mengsan_jianyi",
"category": "utility"
},
{
"name": "mengsan_fangxue",
"category": "utility"
},
{
"name": "mengsan_yujin",
"category": "damage"
},
{
"name": "mengsan_wanmeidaji",
"category": "damage"
},
{
"name": "mengsan_yubeidaji",
"category": "damage"
},
{
"name": "mengsan_tupo",
"category": "damage"
},
{
"name": "mengsan_touchui",
"category": "damage"
},
{
"name": "mengsan_tiezhanbo",
"category": "damage"
},
{
"name": "mengsan_shuangchongdaji",
"category": "damage"
},
{
"name": "mengsan_shandianpili",
"category": "damage"
},
{
"name": "mengsan_fennu",
"category": "damage"
},
{
"name": "mengsan_rongrongzhiquan",
"category": "damage"
},
{
"name": "mengsan_quanshenzhuangji",
"category": "damage"
},
{
"name": "mengsan_jianbingdaji",
"category": "damage"
},
{
"name": "mengsan_feijianhuixuanbiao",
"category": "damage"
},
{
"name": "mengsan_fangyu",
"category": "utility"
}
];
export const liubeiDefinitions = Object.freeze([
...legacyDefinitions,...ironcladCards.map(({name,category,base})=>({name,category,generatedOnly:Boolean(base.generatedOnly)})),
].map(definition=>Object.freeze({...definition,...liubeiCardTraits[definition.name],owner:LIUBEI_CARD_OWNER,deck:"liubei"})));
export const liubeiCosts = Object.freeze({
...{"mengsan_fangyu": 1, "mengsan_fennu": 0, "mengsan_feijianhuixuanbiao": 1, "mengsan_jianbingdaji": 1, "mengsan_quanshenzhuangji": 1, "mengsan_rongrongzhiquan": 1, "mengsan_shandianpili": 1, "mengsan_shuangchongdaji": 1, "mengsan_tiezhanbo": 1, "mengsan_touchui": 1, "mengsan_tupo": 1, "mengsan_yubeidaji": 1, "mengsan_wanmeidaji": 2, "mengsan_yujin": 2, "mengsan_fangxue": 0, "mengsan_jianyi": 1, "mengsan_pomie": 1, "mengsan_songjianwushi": 1, "mengsan_wuzhuang": 1, "mengsan_zhanli": 1, "mengsan_xueqiang": 2, "mengsan_qiling": 0, "mengsan_yuanhen": 0, "mengsan_baozou": 1, "mengsan_xuanfengzhan": "X"},
...Object.fromEntries(ironcladCards.map(card=>[card.name,card.cost])),
});
export const liubeiUpgradeRules = Object.freeze({
...ironcladUpgradeRules,
mengsan_fangyu: Object.freeze({ maxLevel: 1, baseBlock: 5, block: 8 }),
mengsan_baozou: Object.freeze({ maxLevel: 1, damage: 9, baseIncrease: 5, increase: 9 }),
mengsan_yuanhen: Object.freeze({ maxLevel: 1, damage: 5, baseHits: 2, hits: 3 }),
mengsan_xuanfengzhan: Object.freeze({ maxLevel: 1, baseDamage: 5, damage: 8 }),
mengsan_qiling: Object.freeze({ maxLevel: 1, baseDamage: 4, basePerVulnerable: 2, perVulnerable: 3 }),
mengsan_xueqiang: Object.freeze({ maxLevel: 1, loseHp: 2, baseBlock: 16, block: 20 }),
mengsan_zhanli: Object.freeze({ maxLevel: 1, baseVulnerable: 3, vulnerable: 4 }),
mengsan_wuzhuang: Object.freeze({ maxLevel: 1, block: 5 }),
mengsan_songjianwushi: Object.freeze({ maxLevel: 1, baseBlock: 8, block: 11, draw: 1 }),
mengsan_pomie: Object.freeze({ maxLevel: 1, baseCost: 1, cost: 0 }),
mengsan_jianyi: Object.freeze({ maxLevel: 1, baseBlock: 7, block: 9, chooseExhaust: true }),
mengsan_fangxue: Object.freeze({ maxLevel: 1, loseHp: 3, baseEnergy: 2, energy: 3 }),
mengsan_yujin: Object.freeze({ maxLevel: 1, baseDamage: 18, damage: 24, exhaust: 1 }),
mengsan_wanmeidaji: Object.freeze({ maxLevel: 1, baseDamage: 6, damage: 6, basePerStrike: 2, perStrike: 3 }),
mengsan_yubeidaji: Object.freeze({ maxLevel: 1, baseDamage: 7, damage: 9, baseStrength: 2, strength: 3 }),
mengsan_tupo: Object.freeze({ maxLevel: 1, baseDamage: 9, damage: 13, loseHp: 1 }),
mengsan_touchui: Object.freeze({ maxLevel: 1, baseDamage: 9, damage: 12 }),
mengsan_tiezhanbo: Object.freeze({ maxLevel: 1, baseBlock: 5, block: 7, baseDamage: 5, damage: 7 }),
mengsan_shuangchongdaji: Object.freeze({ maxLevel: 1, baseDamage: 5, damage: 7, hits: 2 }),
mengsan_shandianpili: Object.freeze({ maxLevel: 1, baseDamage: 4, damage: 7, vulnerable: 1 }),
mengsan_rongrongzhiquan: Object.freeze({ maxLevel: 1, baseDamage: 10, damage: 14, exhaust: true }),
mengsan_quanshenzhuangji: Object.freeze({ maxLevel: 1, baseCost: 1, cost: 0, damageFromBlock: true }),
mengsan_jianbingdaji: Object.freeze({ maxLevel: 1, baseDamage: 9, damage: 10, baseDraw: 1, draw: 2 }),
mengsan_feijianhuixuanbiao: Object.freeze({ maxLevel: 1, damage: 3, baseHits: 3, hits: 4 }),
mengsan_fennu: Object.freeze({ maxLevel: 1, baseDamage: 6, damage: 8 }),
});
+12
View File
@@ -0,0 +1,12 @@
export const LIUBEI_CARD_OWNER = "mengsan_liubei_shuying";
// STS2 的基础打击/痛击分别沿用杀/重杀;保留原有通用牌归属。
export const liubeiBaseCardNames = Object.freeze(["sha", "mengsan_zhongsha"]);
export const legacyIroncladNames = Object.freeze([
"mengsan_fangyu","mengsan_fennu","mengsan_feijianhuixuanbiao","mengsan_jianbingdaji",
"mengsan_quanshenzhuangji","mengsan_rongrongzhiquan","mengsan_shandianpili",
"mengsan_shuangchongdaji","mengsan_tiezhanbo","mengsan_touchui","mengsan_tupo",
"mengsan_yubeidaji","mengsan_wanmeidaji","mengsan_yujin","mengsan_fangxue",
"mengsan_jianyi","mengsan_pomie","mengsan_songjianwushi","mengsan_wuzhuang",
"mengsan_zhanli","mengsan_xueqiang","mengsan_qiling","mengsan_xuanfengzhan",
"mengsan_yuanhen","mengsan_baozou",
]);
+71
View File
@@ -0,0 +1,71 @@
export const liubeiRewards = {
"shared.reward.card.mengsan_ic_dismantle": {card:{name:"mengsan_ic_dismantle"},name:"获得一张【拆卸】",description:"1费:8伤害;目标已有易伤时攻击2次。"},
"shared.reward.card.mengsan_ic_ashen_strike": {card:{name:"mengsan_ic_ashen_strike"},name:"获得一张【灰烬打击】",description:"1费:6伤害,每张消耗牌增加3伤害。"},
"shared.reward.card.mengsan_ic_pillage": {card:{name:"mengsan_ic_pillage"},name:"获得一张【劫掠】",description:"1费:6伤害,抽牌至抽到非攻击牌。"},
"shared.reward.card.mengsan_ic_hemokinesis": {card:{name:"mengsan_ic_hemokinesis"},name:"获得一张【御血术】",description:"1费:失去2生命,15伤害。"},
"shared.reward.card.mengsan_ic_uppercut": {card:{name:"mengsan_ic_uppercut"},name:"获得一张【上勾拳】",description:"2费:13伤害,1虚弱,1易伤。"},
"shared.reward.card.mengsan_ic_relentless_assault": {card:{name:"mengsan_ic_relentless_assault"},name:"获得一张【无情猛攻】",description:"2费:14伤害,下一张攻击费用变为0。"},
"shared.reward.card.mengsan_ic_fight_me": {card:{name:"mengsan_ic_fight_me"},name:"获得一张【与我一战!】",description:"2费:5伤害2次,获得3力量,敌人获得1力量。"},
"shared.reward.card.mengsan_ic_howl_from_beyond": {card:{name:"mengsan_ic_howl_from_beyond"},name:"获得一张【彼岸咆哮】",description:"3费:全体16伤害;回合末在消耗堆中则打出。"},
"shared.reward.card.mengsan_ic_stomp": {card:{name:"mengsan_ic_stomp"},name:"获得一张【踩踏】",description:"3费:全体12伤害,本回合每打出过一张攻击费用减少1。"},
"shared.reward.card.mengsan_ic_bludgeon": {card:{name:"mengsan_ic_bludgeon"},name:"获得一张【重锤】",description:"3费:32伤害。"},
"shared.reward.card.mengsan_ic_demonic_shield": {card:{name:"mengsan_ic_demonic_shield"},name:"获得一张【恶魔护盾】",description:"0费:失去1生命,给予另一玩家等于自身当前格挡的格挡,消耗。"},
"shared.reward.card.mengsan_ic_rage": {card:{name:"mengsan_ic_rage"},name:"获得一张【狂怒】",description:"0费:本回合每打出攻击获得3格挡。"},
"shared.reward.card.mengsan_ic_battle_trance": {card:{name:"mengsan_ic_battle_trance"},name:"获得一张【战斗专注】",description:"0费:抽3,本回合不能再抽牌。"},
"shared.reward.card.mengsan_ic_forgotten_ritual": {card:{name:"mengsan_ic_forgotten_ritual"},name:"获得一张【被遗忘的仪式】",description:"1费:本回合消耗过牌时获得3费用,消耗。"},
"shared.reward.card.mengsan_ic_infernal_blade": {card:{name:"mengsan_ic_infernal_blade"},name:"获得一张【地狱之刃】",description:"1费:随机攻击加入手牌,本回合免费,消耗。"},
"shared.reward.card.mengsan_ic_colossus": {card:{name:"mengsan_ic_colossus"},name:"获得一张【巨像】",description:"1费:5格挡,本回合易伤敌人的伤害降低50%。"},
"shared.reward.card.mengsan_ic_burning_pact": {card:{name:"mengsan_ic_burning_pact"},name:"获得一张【燃烧契约】",description:"1费:消耗一张牌,抽2。"},
"shared.reward.card.mengsan_ic_taunt": {card:{name:"mengsan_ic_taunt"},name:"获得一张【挑衅】",description:"1费:7格挡,1易伤。"},
"shared.reward.card.mengsan_ic_evil_eye": {card:{name:"mengsan_ic_evil_eye"},name:"获得一张【邪眼】",description:"1费:8格挡;本回合消耗过牌时额外8格挡。"},
"shared.reward.card.mengsan_ic_drum_of_battle": {card:{name:"mengsan_ic_drum_of_battle"},name:"获得一张【战鼓】",description:"1费:抽2;被消耗时获得2费用。"},
"shared.reward.card.mengsan_ic_second_wind": {card:{name:"mengsan_ic_second_wind"},name:"获得一张【重振精神】",description:"1费:消耗所有非攻击手牌,每张获得5格挡。"},
"shared.reward.card.mengsan_ic_dominate": {card:{name:"mengsan_ic_dominate"},name:"获得一张【主宰】",description:"1费:1易伤,目标每层易伤获得1力量,消耗。"},
"shared.reward.card.mengsan_ic_flame_barrier": {card:{name:"mengsan_ic_flame_barrier"},name:"获得一张【火焰屏障】",description:"2费:12格挡,每受到一次攻击反击4伤害。"},
"shared.reward.card.mengsan_ic_eager": {card:{name:"mengsan_ic_eager"},name:"获得一张【跃跃欲试】",description:"2费:每张攻击手牌获得1费用,本回合不能再获得费用。"},
"shared.reward.card.mengsan_ic_inflame": {card:{name:"mengsan_ic_inflame"},name:"获得一张【燃烧】",description:"1费:获得2力量。"},
"shared.reward.card.mengsan_ic_rupture": {card:{name:"mengsan_ic_rupture"},name:"获得一张【撕裂】",description:"1费:自身回合每次失血获得1力量。"},
"shared.reward.card.mengsan_ic_feel_no_pain": {card:{name:"mengsan_ic_feel_no_pain"},name:"获得一张【无惧疼痛】",description:"1费:每张牌被消耗时获得3格挡。"},
"shared.reward.card.mengsan_ic_ferocity": {card:{name:"mengsan_ic_ferocity"},name:"获得一张【凶恶】",description:"1费:每次给予易伤抽1。"},
"shared.reward.card.mengsan_ic_stone_armor": {card:{name:"mengsan_ic_stone_armor"},name:"获得一张【岩石铠甲】",description:"1费:获得4覆甲。"},
"shared.reward.card.mengsan_ic_inferno": {card:{name:"mengsan_ic_inferno"},name:"获得一张【狱火】",description:"1费:回合开始失去1生命,自身回合每次失血全体6伤害。"},
"shared.reward.card.mengsan_ic_juggle": {card:{name:"mengsan_ic_juggle"},name:"获得一张【杂耍】",description:"1费:每回合第三张攻击复制至手牌。"},
"shared.reward.card.mengsan_ic_stampede": {card:{name:"mengsan_ic_stampede"},name:"获得一张【惊逃】",description:"2费:回合末随机打出手牌攻击,目标随机。"},
"shared.reward.card.mengsan_ic_pacts_end": {card:{name:"mengsan_ic_pacts_end"},name:"获得一张【契约终结】",description:"0费:消耗堆至少3张时全体17伤害。"},
"shared.reward.card.mengsan_ic_conflagration": {card:{name:"mengsan_ic_conflagration"},name:"获得一张【焚烧】",description:"1费:全体2伤害4次。"},
"shared.reward.card.mengsan_ic_feed": {card:{name:"mengsan_ic_feed"},name:"获得一张【狂宴】",description:"1费:10伤害,斩杀永久获得3最大生命,消耗。"},
"shared.reward.card.mengsan_ic_thrash": {card:{name:"mengsan_ic_thrash"},name:"获得一张【痛殴】",description:"1费:4伤害2次,随机消耗攻击手牌并继承其伤害。"},
"shared.reward.card.mengsan_ic_tear_asunder": {card:{name:"mengsan_ic_tear_asunder"},name:"获得一张【扯碎】",description:"2费:5伤害,本场每次失血增加一次攻击。"},
"shared.reward.card.mengsan_ic_fiend_fire": {card:{name:"mengsan_ic_fiend_fire"},name:"获得一张【恶魔之焰】",description:"2费:消耗所有手牌,每张造成7伤害,消耗。"},
"shared.reward.card.mengsan_ic_bully": {card:{name:"mengsan_ic_bully"},name:"获得一张【凌虐】",description:"3费:15伤害,敌人本回合失去10力量。"},
"shared.reward.card.mengsan_ic_offering": {card:{name:"mengsan_ic_offering"},name:"获得一张【祭品】",description:"0费:失去6生命,获得2费用,抽3,消耗。"},
"shared.reward.card.mengsan_ic_brand": {card:{name:"mengsan_ic_brand"},name:"获得一张【烙印】",description:"0费:失去1生命,消耗一张牌,获得1力量。"},
"shared.reward.card.mengsan_ic_unleash": {card:{name:"mengsan_ic_unleash"},name:"获得一张【倾泻】",description:"X费:打出抽牌堆顶X张牌。"},
"shared.reward.card.mengsan_ic_primal_force": {card:{name:"mengsan_ic_primal_force"},name:"获得一张【原始力量】",description:"0费:所有攻击手牌变为巨石。"},
"shared.reward.card.mengsan_ic_twin_strike": {card:{name:"mengsan_ic_twin_strike"},name:"获得一张【连环拳】",description:"1费:下1张攻击额外打出一次。"},
"shared.reward.card.mengsan_ic_stoke": {card:{name:"mengsan_ic_stoke"},name:"获得一张【添柴】",description:"1费:消耗所有手牌,每张生成一张随机牌至手牌。"},
"shared.reward.card.mengsan_ic_impervious": {card:{name:"mengsan_ic_impervious"},name:"获得一张【岿然不动】",description:"2费:30格挡,消耗。"},
"shared.reward.card.mengsan_ic_not_yet": {card:{name:"mengsan_ic_not_yet"},name:"获得一张【时候未到】",description:"2费:回复10生命,消耗。"},
"shared.reward.card.mengsan_ic_cruelty": {card:{name:"mengsan_ic_cruelty"},name:"获得一张【残酷】",description:"1费:易伤敌人额外受到25%伤害。"},
"shared.reward.card.mengsan_ic_crimson_mantle": {card:{name:"mengsan_ic_crimson_mantle"},name:"获得一张【绯红披风】",description:"1费:回合开始失去1生命并获得8格挡。"},
"shared.reward.card.mengsan_ic_battle_ready": {card:{name:"mengsan_ic_battle_ready"},name:"获得一张【好勇斗狠】",description:"1费:回合开始从弃牌堆随机取一张攻击至手牌并升级。"},
"shared.reward.card.mengsan_ic_bodyguard": {card:{name:"mengsan_ic_bodyguard"},name:"获得一张【肉盾】",description:"1费:承受敌人双倍伤害,敌人对盟友伤害减半。"},
"shared.reward.card.mengsan_ic_hellraiser": {card:{name:"mengsan_ic_hellraiser"},name:"获得一张【地狱狂徒】",description:"2费:抽到名字含打击的牌时向随机敌人打出。"},
"shared.reward.card.mengsan_ic_dark_embrace": {card:{name:"mengsan_ic_dark_embrace"},name:"获得一张【黑暗之拥】",description:"2费:每张牌被消耗时抽1。"},
"shared.reward.card.mengsan_ic_unshakable": {card:{name:"mengsan_ic_unshakable"},name:"获得一张【坚定不移】",description:"2费:每回合第一次从卡牌获得的格挡翻倍。"},
"shared.reward.card.mengsan_ic_juggernaut": {card:{name:"mengsan_ic_juggernaut"},name:"获得一张【势不可当】",description:"2费:每次获得格挡,对随机敌人造成6伤害。"},
"shared.reward.card.mengsan_ic_pyre": {card:{name:"mengsan_ic_pyre"},name:"获得一张【薪火之源】",description:"2费:回合开始获得1费用。"},
"shared.reward.card.mengsan_ic_barricade": {card:{name:"mengsan_ic_barricade"},name:"获得一张【壁垒】",description:"3费:回合开始不清空格挡。"},
"shared.reward.card.mengsan_ic_demon_form": {card:{name:"mengsan_ic_demon_form"},name:"获得一张【恶魔形态】",description:"3费:回合开始获得2力量。"},
"shared.reward.card.mengsan_ic_clash": {card:{name:"mengsan_ic_clash"},name:"获得一张【交锋】",description:"0费:手牌全部是攻击时可用,14伤害。"},
"shared.reward.card.mengsan_ic_dual_wield": {card:{name:"mengsan_ic_dual_wield"},name:"获得一张【双持】",description:"1费:复制一张攻击或能力手牌至手牌。"},
"shared.reward.card.mengsan_ic_entrench": {card:{name:"mengsan_ic_entrench"},name:"获得一张【巩固】",description:"2费:当前格挡翻倍。"},
"shared.reward.card.mengsan_ic_break": {card:{name:"mengsan_ic_break"},name:"获得一张【破击】",description:"1费:20伤害,5易伤。"},
"shared.reward.card.mengsan_ic_corruption": {card:{name:"mengsan_ic_corruption"},name:"获得一张【腐化】",description:"3费:技能费用变为0,打出的技能被消耗。"},
"shared.reward.card.mengsan_ic_midnight": {card:{name:"mengsan_ic_midnight"},name:"获得一张【午夜】",description:"12费:99伤害;本场任何人每消耗一张牌费用减少1。"},
"shared.reward.card.mengsan_ic_blaze": {card:{name:"mengsan_ic_blaze"},name:"获得一张【炽焰】",description:"2费:给予另一玩家5力量。"},
"shared.reward.card.mengsan_ic_rally": {card:{name:"mengsan_ic_rally"},name:"获得一张【群情激愤】",description:"0费:9伤害,在所有人弃牌堆中加入本牌副本。"},
"shared.reward.card.mengsan_ic_grapple": {card:{name:"mengsan_ic_grapple"},name:"获得一张【擒拿】",description:"1费:7伤害,本回合获得格挡时对该目标5伤害。"},
};
export const liubeiRewardIds = Object.freeze(Object.keys(liubeiRewards));
+320
View File
@@ -0,0 +1,320 @@
import { ironcladCards } from "./data.js";
import { cardUpgradeLevel } from "../../upgrades.js";
import { canPayCard, cardCost, isActiveCardUse } from "../../../battle/combat-rules.js";
import { isAttackCard } from "../../../monsters/vine-tangled.js";
import { applyMengsanDebuff } from "../../../monsters/artifact-status.js";
import { createAdvancedIronclad } from "./advanced.js";
const POWER_SKILL = "mengsan_ic_powers_shuying";
const belongsTo = (event, root) => {
const seen = new Set();
for (let e = event; e && !seen.has(e); e = e.parent) {
if (e === root) return true;
seen.add(e);
}
return false;
};
const positive = n => Number.isSafeInteger(n) && n >= 0;
export function createIroncladRuntime({ game, get, lib, status, getBattle, handExhaust, battleEnergy, random }) {
const advanced=createAdvancedIronclad({game,get,lib,status,getBattle,battleEnergy,random});
const states = new WeakMap();
const capture = player => {
const battle = getBattle();
if (!battle?.session?.active || !battle.root || !battle.players?.has(player) || !player?.isAlive?.()) return null;
return { battle, session: battle.session, root: battle.root };
};
const valid = (player, token) => Boolean(token && getBattle() === token.battle &&
token.battle.session === token.session && token.session.active && token.battle.root === token.root &&
token.battle.players.has(player) && player?.isAlive?.());
const pileOf = (player, token) => player === game.me ? token.battle.personalPiles : token.battle.monsterPiles?.get(player);
const stateOf = player => {
const s = states.get(player);
return s && valid(player, s.token) ? s : null;
};
const ensure = (player, token) => {
let s = stateOf(player);
if (s) return s;
s = { token, powers: Object.create(null), turn: Object.create(null), defense: Object.create(null) };
token.session.ownResource(s, () => {
if (states.get(player) !== s) return;
states.delete(player);
player.removeSkill(POWER_SKILL);
});
states.set(player, s);
player.addSkill(POWER_SKILL);
return s;
};
const add = (object, key, n) => {
const next = (object[key] || 0) + n;
if (!Number.isFinite(next) || next < 0 || next > Number.MAX_SAFE_INTEGER) throw new RangeError("铁甲战士效果数值越界");
object[key] = next;
};
const strength = (player, n, token) => {
if (!valid(player, token) || !positive(n)) return false;
const next = (player.storage.mengsanStrength_shuying || 0) + n;
if (!positive(next)) throw new RangeError("力量数值越界");
player.storage.mengsanStrength_shuying = next;
player.addSkill("mengsan_raider_strength_shuying");
player.markSkill("mengsan_raider_strength_shuying");
return true;
};
const exhaustCount = (player, token) => {
const cards = pileOf(player, token)?.battleCards?.() || [];
return cards.filter(c => c.storage?.mengsanExhausted_shuying).length;
};
const exhaustedThisTurn = (player, token) => {
const lost = game.getGlobalHistory("cardMove");
if (Array.isArray(lost) && lost.some(e => e.player === player && e.type === "mengsanExhaust" &&
belongsTo(e, token.root) && e.cards?.some(c => c.storage?.mengsanExhausted_shuying))) return true;
return player.getHistory("useCard").some(e => belongsTo(e, token.root) &&
e.cards?.some(c => c.storage?.mengsanExhausted_shuying));
};
const chooseExhaust = async (player, event, token) => {
const choice = handExhaust.capture(player);
if (!choice || choice.battle !== token.battle) return null;
return await handExhaust.exhaust(player, event, choice, true, "选择消耗一张你的手牌");
};
const exhaustHands = async (player, event, token, filter = () => true, afterEach = null) => {
const pile = pileOf(player, token);
if (typeof pile?.exhaustFromHand !== "function") throw new Error("铁甲战士缺少个人消耗接口");
const playing = new Set(event.cards || []);
const cards = player.getCards("h").filter(c => !playing.has(c) && filter(c));
let count = 0;
for (const c of cards) {
if (!valid(player, token) || pileOf(player, token) !== pile) break;
if (await pile.exhaustFromHand(c)) {
count++;
if (afterEach && valid(player, token)) await afterEach();
}
}
return count;
};
const ruleOf = (spec, card) => cardUpgradeLevel(card) ? spec.upgraded : spec.base;
const prompt = (spec, card) => {
if(spec.base.advanced)return advanced.describe(spec,card);
const r = ruleOf(spec, card), out = [];
if (r.onlyAttacks) out.push("只有手牌全部为攻击牌时才能打出。");
if (r.loseHp) out.push(`失去${r.loseHp}点生命。`);
if (r.block) out.push(`获得${r.block}点格挡。`);
if (r.exhaustTurnBlock) out.push(`本回合消耗过卡牌时,再获得${r.block}点格挡。`);
if (r.doubleBlock) out.push("将当前格挡翻倍。");
if (r.allyBlock) out.push("给予另一名友方角色等于自己当前格挡值的格挡。");
if (r.chooseExhaust) out.push("消耗一张手牌。");
if (r.exhaustNonAttacks) out.push(`消耗所有非攻击手牌,每张获得${r.blockPerExhaust}点格挡。`);
if (r.hitsPerExhaust) out.push(`消耗所有手牌,每张对一名敌人造成${r.damage}点伤害。`);
else if (r.damage) {
let text = `${r.all ? "对所有敌人" : "对一名敌人"}造成${r.damage}点伤害`;
if (r.hits) text += `${r.hits}次`;
out.push(text + "。");
}
if (r.minExhaust) out.unshift(`仅当个人消耗牌堆至少${r.minExhaust}张时造成伤害。`);
if (r.vulnerableHits) out.push(`目标已有易伤时,攻击${r.vulnerableHits}次。`);
if (r.hitsPerHpLoss) out.push("本场战斗每次实际失去生命,额外攻击一次。");
if (r.perExhaust) out.push(`每张个人消耗牌额外增加${r.perExhaust}点基础伤害。`);
if (r.drawUntilSkill) out.push("抽牌直到抽到一张非攻击牌。");
if (r.vulnerable) out.push(`给予目标${r.vulnerable}层易伤。`);
if (r.weak) out.push(`给予目标${r.weak}层虚弱。`);
if (r.strengthFromVulnerable) out.push("目标每有一层易伤,获得1点力量。");
if (r.strength) out.push(`获得${r.strength}点力量。`);
if (r.enemyStrength) out.push(`目标获得${r.enemyStrength}点力量。`);
if (r.allyStrength) out.push(`给予另一名友方角色${r.allyStrength}点力量。`);
if (r.energy) out.push(`获得${r.energy}费用。`);
if (r.exhaustTurnEnergy) out.push(`本回合消耗过卡牌时,获得${r.exhaustTurnEnergy}费用。`);
if (r.draw) out.push(`抽${r.draw}张牌。`);
if (r.heal) out.push(`回复${r.heal}点生命。`);
if (r.turnStrength) out.push(`每个自身回合开始时获得${r.turnStrength}点力量。`);
if (r.turnEnergy) out.push(`每个自身回合开始时获得${r.turnEnergy}费用。`);
if (r.turnBlock) out.push(`每个自身回合开始时失去${r.turnLoseHp}点生命,再获得${r.turnBlock}点格挡。`);
if (r.attackBlock) out.push(`本次自身回合内,每打出一张攻击牌获得${r.attackBlock}点格挡。`);
if (r.lossStrength) out.push(`在自身回合内每次实际失去生命,获得${r.lossStrength}点力量。`);
if (r.lossDamage) out.push(`自身回合开始时失去${r.infernoLoseHp}点生命;自身回合每次实际失血,对全部敌人造成${r.lossDamage}点非攻击伤害。`);
if (r.retaliation) out.push(`至下个自身回合开始,每受到一次攻击,对攻击者造成${r.retaliation}点伤害。`);
if (r.vulnerableReduction) out.push("至下个自身回合开始,易伤敌人对你的攻击伤害降低50%。");
if (r.vulnerableBonus) out.push(`你攻击易伤敌人的易伤伤害倍率额外增加${r.vulnerableBonus * 100}%。`);
if (r.exhaust) out.push("消耗。");
if (r.power) out.push("能力持续本场战斗,打出后退出牌堆。");
return `梦三:主动使用消耗${cardCost(card || { name: spec.name })}费用。` + out.join("");
};
const cards = {}, translate = {};
for (const spec of ironcladCards) {
const base = spec.base;
const enemyTarget = Boolean(base.damage && !base.all || base.vulnerable);
const allyTarget = Boolean(base.allyStrength || base.allyBlock);
cards[spec.name] = {
type: "basic", fullimage: true,
image: `ext:术樱包/mengsan/assets/cards/${spec.name}.png`,
mengsanCost_shuying: spec.cost, usable: Infinity,
mengsanXCost_shuying: spec.cost === "X",
mengsanInnate_shuying: card => Boolean(ruleOf(spec,card).innate),
mengsanExhaust_shuying: base.exhaust ? card => Boolean(ruleOf(spec, card).exhaust) : false,
mengsanPower_shuying: Boolean(base.power),
selectTarget: enemyTarget || allyTarget ? 1 : -1,
toself: !enemyTarget && !allyTarget,
enable(card, player, event) {
const token = capture(player);
if (!token || isActiveCardUse(event || status.event, player) && !canPayCard(player, card)) return false;
if (base.onlyAttacks && player.getCards("h").some(c => !isAttackCard(c))) return false;
return true;
},
filterTarget(card, player, target) {
if (!target?.isAlive?.()) return false;
if (enemyTarget) return target !== player && player.isEnemyOf(target);
if (allyTarget) return target !== player && player.isFriendOf(target);
return target === player;
},
async content(event, trigger, player) {
const token = capture(player), r = ruleOf(spec, event.card), target = event.target;
if (!token || !belongsTo(event, token.root)) return;
advanced.ensure(player);
if(r.advanced){await advanced.play(spec,event,player);return;}
const targetValid = () => valid(player, token) && valid(target, token) &&
(enemyTarget ? player.isEnemyOf(target) : allyTarget ? target !== player && player.isFriendOf(target) : target === player);
if (!targetValid()) return;
if (r.minExhaust && exhaustCount(player, token) < r.minExhaust) return;
if (r.loseHp) { await player.loseHp(r.loseHp); if (!targetValid()) return; }
if (r.allyBlock && player.hujia > 0) { await target.changeHujia(player.hujia); if (!targetValid()) return; }
if (r.block) { await player.changeHujia(r.block); if (!targetValid()) return; }
if (r.exhaustTurnBlock && exhaustedThisTurn(player, token)) {
await player.changeHujia(r.block); if (!targetValid()) return;
}
if (r.doubleBlock && player.hujia > 0) { await player.changeHujia(player.hujia); if (!targetValid()) return; }
if (r.chooseExhaust) { await chooseExhaust(player, event, token); if (!targetValid()) return; }
if (r.exhaustNonAttacks) {
await exhaustHands(player, event, token, c => !isAttackCard(c),
() => player.changeHujia(r.blockPerExhaust));
return;
}
let hits = r.hits || 1;
if (r.hitsPerHpLoss) hits += game.getAllGlobalHistory("changeHp").filter(e =>
e.player === player && Number.isFinite(e.changedHp) && e.changedHp < 0 && belongsTo(e, token.root)).length;
if (r.hitsPerExhaust) hits = await exhaustHands(player, event, token);
else if (r.vulnerableHits && target.storage?.mengsanVulnerable_shuying > 0) hits = r.vulnerableHits;
const amount = (r.damage || 0) + (r.perExhaust ? r.perExhaust * exhaustCount(player, token) : 0);
if (!positive(amount) || !positive(hits)) throw new RangeError("铁甲战士攻击数值越界");
if (amount > 0) for (let i = 0; i < hits && valid(player, token); i++) {
const targets = r.all ? [...token.battle.players].filter(p => p !== player && p.isAlive() && player.isEnemyOf(p)) : [target];
for (const t of targets) {
if (!valid(player, token)) return;
if (!valid(t, token) || !player.isEnemyOf(t)) continue;
const damage = t.damage(amount, player);
damage.card = event.card;
damage.mengsanAttack_shuying = true;
await damage;
}
}
if (!valid(player, token)) return;
// 自身力量与抽牌不因目标被本牌击杀而丢失;目标状态只施加给存活目标。
if (targetValid()) {
if (r.weak) applyMengsanDebuff(target, "weak", r.weak, player);
if (r.vulnerable) applyMengsanDebuff(target, "vulnerable", r.vulnerable, player);
if (r.enemyStrength) strength(target, r.enemyStrength, token);
if (r.allyStrength) strength(target, r.allyStrength, token);
if (r.strengthFromVulnerable) strength(player, target.storage?.mengsanVulnerable_shuying || 0, token);
}
await advanced.flushAll();
if(!valid(player,token))return;
if (r.strength) strength(player, r.strength, token);
if (r.heal) { await player.recover(r.heal); if (!valid(player, token)) return; }
if (r.energy) battleEnergy.grant(player, r.energy, token.battle);
if (r.exhaustTurnEnergy && exhaustedThisTurn(player, token)) battleEnergy.grant(player, r.exhaustTurnEnergy, token.battle);
if (r.draw) { await player.draw(r.draw); if (!valid(player, token)) return; }
if (r.drawUntilSkill) {
const pile = pileOf(player, token);
if (typeof pile?.battleCards !== "function") throw new Error("劫掠缺少个人牌堆接口");
// 固定当前牌数作为安全上界,抽牌过程不重复消耗空牌堆。
const limit = pile.battleCards(event.cards).length;
for (let i = 0; i < limit && valid(player, token) && pileOf(player, token) === pile; i++) {
const draw = player.draw(1);
await draw;
const drawn = draw.result?.cards;
if (!Array.isArray(drawn) || !drawn.length || drawn.some(c => !isAttackCard(c))) break;
}
}
if ((r.power || r.turnPower) && valid(player, token)) {
const s = ensure(player, token);
if (r.turnStrength) add(s.powers, "turnStrength", r.turnStrength);
if (r.turnEnergy) add(s.powers, "turnEnergy", r.turnEnergy);
if (r.turnBlock) { add(s.powers, "turnBlock", r.turnBlock); add(s.powers, "turnLoseHp", r.turnLoseHp); }
if (r.lossStrength) add(s.powers, "lossStrength", r.lossStrength);
if (r.lossDamage) { add(s.powers, "lossDamage", r.lossDamage); add(s.powers, "infernoLoseHp", r.infernoLoseHp); }
if (r.vulnerableBonus) add(s.powers, "vulnerableBonus", r.vulnerableBonus);
if (r.attackBlock) add(s.turn, "attackBlock", r.attackBlock);
if (r.retaliation) add(s.defense, "retaliation", r.retaliation);
if (r.vulnerableReduction) s.defense.colossus = true;
player.markSkill(POWER_SKILL);
}
},
cardPrompt(card) { return prompt(spec, card); },
ai: { order: base.power ? 8 : 6, result: { target: enemyTarget ? -1 : 1 },
tag: { damage: base.damage ? 1 : 0, recover: base.heal ? 1 : 0 } },
};
translate[spec.name] = spec.title;
translate[`${spec.name}_info`] = prompt(spec, { name: spec.name });
}
const skills = {
...advanced.skills,
[POWER_SKILL]: {
trigger: { player: ["phaseBegin", "phaseAfter", "changeHpAfter", "damageBegin3", "damageAfter", "useCardAfter"] },
forced: true, silent: true, popup: false, priority: 50,
mark: true, marktext: "战",
intro: { content(storage, player) {
const s = stateOf(player);
if (!s) return "本场战斗的铁甲战士效果";
const names = { turnStrength: "每回合力量", turnEnergy: "每回合费用", turnBlock: "每回合格挡", turnLoseHp: "披风每回合失血", infernoLoseHp: "狱火每回合失血", lossDamage: "失血全体伤害", lossStrength: "失血获得力量", vulnerableBonus: "易伤倍率加成", attackBlock: "攻击获得格挡", retaliation: "反击伤害", colossus: "巨像减伤" };
return Object.entries({ ...s.powers, ...s.turn, ...s.defense }).map(([k, v]) => `${names[k] || k}:${v === true ? "50%" : k === "vulnerableBonus" ? `${v * 100}%` : v}`).join("<br>");
} },
filter(event, player) { const s = stateOf(player); return Boolean(s && belongsTo(event, s.token.root)); },
async content(event, trigger, player) {
const s = stateOf(player);
if (!s) return;
const token = s.token;
if (event.triggername === "phaseBegin") {
s.defense = Object.create(null);
s.turn = Object.create(null);
if (s.powers.turnStrength) strength(player, s.powers.turnStrength, token);
if (s.powers.turnEnergy) battleEnergy.grant(player, s.powers.turnEnergy, token.battle);
if (s.powers.turnLoseHp) { await player.loseHp(s.powers.turnLoseHp); if (!valid(player, token)) return; }
if (s.powers.turnBlock) await player.changeHujia(s.powers.turnBlock);
if (valid(player, token) && s.powers.infernoLoseHp) await player.loseHp(s.powers.infernoLoseHp);
} else if (event.triggername === "phaseAfter") {
s.turn = Object.create(null);
} else if (event.triggername === "useCardAfter") {
if (s.turn.attackBlock && status.currentPhase === player && isAttackCard(trigger.card)) await player.changeHujia(s.turn.attackBlock);
} else if (event.triggername === "changeHpAfter") {
const lostHp = status.currentPhase === player && Number.isFinite(trigger.changedHp) && trigger.changedHp < 0;
if (s.powers.lossStrength && lostHp) strength(player, s.powers.lossStrength, token);
if (s.powers.lossDamage && lostHp) {
for (const target of [...token.battle.players]) {
if (!valid(player, token)) return;
if (!valid(target, token) || !player.isEnemyOf(target)) continue;
const damage = target.damage(s.powers.lossDamage, player, "nocard");
damage.mengsanScriptedSkill_shuying = true;
await damage;
}
}
} else if (event.triggername === "damageBegin3") {
if (s.defense.colossus && trigger.source?.storage?.mengsanVulnerable_shuying > 0 &&
(trigger.mengsanAttack_shuying || isAttackCard(trigger.card))) trigger.num = Math.floor(trigger.num * 0.5);
} else if (event.triggername === "damageAfter") {
const attacker = trigger.source;
if (s.defense.retaliation && valid(attacker, token) && attacker !== player &&
(trigger.mengsanAttack_shuying || isAttackCard(trigger.card))) {
const damage = attacker.damage(s.defense.retaliation, player, "nocard");
damage.mengsanScriptedSkill_shuying = true;
await damage;
}
}
if (valid(player, token)) player.markSkill(POWER_SKILL);
},
},
};
translate[POWER_SKILL] = "铁甲战意";
translate.mengsan_ic_advanced_shuying = "刘备牌组状态";
translate.mengsan_ic_bridge_shuying = "刘备牌组结算";
return { cards, translate, skills, names: ironcladCards.map(c => c.name),
onDeath:advanced.onDeath,
vulnerableBonus(source, target) {
return source !== target && source?.isEnemyOf?.(target) ? stateOf(source)?.powers.vulnerableBonus || 0 : 0;
} };
}
+99
View File
@@ -0,0 +1,99 @@
// 原版分类;Wiki正式版优先于较新测试分支。来源见卡牌分类快照.json。
// type 留给无名引擎使用;cardType 才是攻击/技能/能力分类。
export const liubeiCardTraits = Object.freeze({
sha: Object.freeze({"cardType": "attack", "rarity": "basic"}),
mengsan_zhongsha: Object.freeze({"cardType": "attack", "rarity": "basic"}),
mengsan_fangyu: Object.freeze({"cardType": "skill", "rarity": "basic"}),
mengsan_fennu: Object.freeze({"cardType": "attack", "rarity": "common"}),
mengsan_feijianhuixuanbiao: Object.freeze({"cardType": "attack", "rarity": "common"}),
mengsan_jianbingdaji: Object.freeze({"cardType": "attack", "rarity": "common"}),
mengsan_quanshenzhuangji: Object.freeze({"cardType": "attack", "rarity": "common"}),
mengsan_rongrongzhiquan: Object.freeze({"cardType": "attack", "rarity": "common"}),
mengsan_shandianpili: Object.freeze({"cardType": "attack", "rarity": "common"}),
mengsan_shuangchongdaji: Object.freeze({"cardType": "attack", "rarity": "common"}),
mengsan_tiezhanbo: Object.freeze({"cardType": "attack", "rarity": "common"}),
mengsan_touchui: Object.freeze({"cardType": "attack", "rarity": "common"}),
mengsan_tupo: Object.freeze({"cardType": "attack", "rarity": "common"}),
mengsan_yubeidaji: Object.freeze({"cardType": "attack", "rarity": "common"}),
mengsan_wanmeidaji: Object.freeze({"cardType": "attack", "rarity": "common"}),
mengsan_yujin: Object.freeze({"cardType": "attack", "rarity": "common"}),
mengsan_fangxue: Object.freeze({"cardType": "skill", "rarity": "common"}),
mengsan_jianyi: Object.freeze({"cardType": "skill", "rarity": "common"}),
mengsan_pomie: Object.freeze({"cardType": "skill", "rarity": "common"}),
mengsan_songjianwushi: Object.freeze({"cardType": "skill", "rarity": "common"}),
mengsan_wuzhuang: Object.freeze({"cardType": "skill", "rarity": "common"}),
mengsan_zhanli: Object.freeze({"cardType": "skill", "rarity": "common"}),
mengsan_xueqiang: Object.freeze({"cardType": "skill", "rarity": "common"}),
mengsan_qiling: Object.freeze({"cardType": "attack", "rarity": "uncommon"}),
mengsan_xuanfengzhan: Object.freeze({"cardType": "attack", "rarity": "uncommon"}),
mengsan_yuanhen: Object.freeze({"cardType": "attack", "rarity": "uncommon"}),
mengsan_baozou: Object.freeze({"cardType": "attack", "rarity": "uncommon"}),
mengsan_ic_dismantle: Object.freeze({"cardType": "attack", "rarity": "uncommon"}),
mengsan_ic_ashen_strike: Object.freeze({"cardType": "attack", "rarity": "uncommon"}),
mengsan_ic_pillage: Object.freeze({"cardType": "attack", "rarity": "uncommon"}),
mengsan_ic_hemokinesis: Object.freeze({"cardType": "attack", "rarity": "uncommon"}),
mengsan_ic_uppercut: Object.freeze({"cardType": "attack", "rarity": "uncommon"}),
mengsan_ic_relentless_assault: Object.freeze({"cardType": "attack", "rarity": "uncommon"}),
mengsan_ic_fight_me: Object.freeze({"cardType": "attack", "rarity": "uncommon"}),
mengsan_ic_howl_from_beyond: Object.freeze({"cardType": "attack", "rarity": "uncommon"}),
mengsan_ic_stomp: Object.freeze({"cardType": "attack", "rarity": "uncommon"}),
mengsan_ic_bludgeon: Object.freeze({"cardType": "attack", "rarity": "uncommon"}),
mengsan_ic_demonic_shield: Object.freeze({"cardType": "skill", "rarity": "uncommon"}),
mengsan_ic_rage: Object.freeze({"cardType": "skill", "rarity": "uncommon"}),
mengsan_ic_battle_trance: Object.freeze({"cardType": "skill", "rarity": "uncommon"}),
mengsan_ic_forgotten_ritual: Object.freeze({"cardType": "skill", "rarity": "uncommon"}),
mengsan_ic_infernal_blade: Object.freeze({"cardType": "skill", "rarity": "uncommon"}),
mengsan_ic_colossus: Object.freeze({"cardType": "skill", "rarity": "uncommon"}),
mengsan_ic_burning_pact: Object.freeze({"cardType": "skill", "rarity": "uncommon"}),
mengsan_ic_taunt: Object.freeze({"cardType": "skill", "rarity": "uncommon"}),
mengsan_ic_evil_eye: Object.freeze({"cardType": "skill", "rarity": "uncommon"}),
mengsan_ic_drum_of_battle: Object.freeze({"cardType": "skill", "rarity": "uncommon"}),
mengsan_ic_second_wind: Object.freeze({"cardType": "skill", "rarity": "uncommon"}),
mengsan_ic_dominate: Object.freeze({"cardType": "skill", "rarity": "uncommon"}),
mengsan_ic_flame_barrier: Object.freeze({"cardType": "skill", "rarity": "uncommon"}),
mengsan_ic_eager: Object.freeze({"cardType": "skill", "rarity": "uncommon"}),
mengsan_ic_inflame: Object.freeze({"cardType": "power", "rarity": "uncommon"}),
mengsan_ic_rupture: Object.freeze({"cardType": "power", "rarity": "uncommon"}),
mengsan_ic_feel_no_pain: Object.freeze({"cardType": "power", "rarity": "uncommon"}),
mengsan_ic_ferocity: Object.freeze({"cardType": "power", "rarity": "uncommon"}),
mengsan_ic_stone_armor: Object.freeze({"cardType": "power", "rarity": "uncommon"}),
mengsan_ic_inferno: Object.freeze({"cardType": "power", "rarity": "uncommon"}),
mengsan_ic_juggle: Object.freeze({"cardType": "power", "rarity": "uncommon"}),
mengsan_ic_stampede: Object.freeze({"cardType": "power", "rarity": "uncommon"}),
mengsan_ic_pacts_end: Object.freeze({"cardType": "attack", "rarity": "rare"}),
mengsan_ic_conflagration: Object.freeze({"cardType": "attack", "rarity": "rare"}),
mengsan_ic_feed: Object.freeze({"cardType": "attack", "rarity": "rare"}),
mengsan_ic_thrash: Object.freeze({"cardType": "attack", "rarity": "rare"}),
mengsan_ic_tear_asunder: Object.freeze({"cardType": "attack", "rarity": "rare"}),
mengsan_ic_fiend_fire: Object.freeze({"cardType": "attack", "rarity": "rare"}),
mengsan_ic_bully: Object.freeze({"cardType": "attack", "rarity": "rare"}),
mengsan_ic_offering: Object.freeze({"cardType": "skill", "rarity": "rare"}),
mengsan_ic_brand: Object.freeze({"cardType": "skill", "rarity": "rare"}),
mengsan_ic_unleash: Object.freeze({"cardType": "skill", "rarity": "rare"}),
mengsan_ic_primal_force: Object.freeze({"cardType": "skill", "rarity": "rare"}),
mengsan_ic_twin_strike: Object.freeze({"cardType": "skill", "rarity": "rare"}),
mengsan_ic_stoke: Object.freeze({"cardType": "skill", "rarity": "rare"}),
mengsan_ic_impervious: Object.freeze({"cardType": "skill", "rarity": "rare"}),
mengsan_ic_not_yet: Object.freeze({"cardType": "skill", "rarity": "rare"}),
mengsan_ic_cruelty: Object.freeze({"cardType": "power", "rarity": "rare"}),
mengsan_ic_crimson_mantle: Object.freeze({"cardType": "power", "rarity": "rare"}),
mengsan_ic_battle_ready: Object.freeze({"cardType": "power", "rarity": "rare"}),
mengsan_ic_bodyguard: Object.freeze({"cardType": "power", "rarity": "rare"}),
mengsan_ic_hellraiser: Object.freeze({"cardType": "power", "rarity": "rare"}),
mengsan_ic_dark_embrace: Object.freeze({"cardType": "power", "rarity": "rare"}),
mengsan_ic_unshakable: Object.freeze({"cardType": "power", "rarity": "rare"}),
mengsan_ic_juggernaut: Object.freeze({"cardType": "power", "rarity": "rare"}),
mengsan_ic_pyre: Object.freeze({"cardType": "power", "rarity": "rare"}),
mengsan_ic_barricade: Object.freeze({"cardType": "power", "rarity": "rare"}),
mengsan_ic_demon_form: Object.freeze({"cardType": "power", "rarity": "rare"}),
mengsan_ic_clash: Object.freeze({"cardType": "attack", "rarity": "event"}),
mengsan_ic_dual_wield: Object.freeze({"cardType": "skill", "rarity": "event"}),
mengsan_ic_entrench: Object.freeze({"cardType": "skill", "rarity": "event"}),
mengsan_ic_break: Object.freeze({"cardType": "attack", "rarity": "ancient"}),
mengsan_ic_corruption: Object.freeze({"cardType": "power", "rarity": "ancient"}),
mengsan_ic_midnight: Object.freeze({"cardType": "attack", "rarity": "rare"}),
mengsan_ic_blaze: Object.freeze({"cardType": "skill", "rarity": "uncommon"}),
mengsan_ic_rally: Object.freeze({"cardType": "attack", "rarity": "uncommon"}),
mengsan_ic_grapple: Object.freeze({"cardType": "attack", "rarity": "uncommon"}),
mengsan_ic_boulder: Object.freeze({"cardType": "attack", "rarity": "token"}),
});