diff --git a/components/battle-preparation.tsx b/components/battle-preparation.tsx index 71d5a379..9f366084 100644 --- a/components/battle-preparation.tsx +++ b/components/battle-preparation.tsx @@ -5,6 +5,7 @@ import { battleCardLabel, isWeaponCard, isStoneBurner, + isMirrorWeapon, isDefenseCard, VOICE_KINDS, } from '@/game/battle-cards'; @@ -20,6 +21,7 @@ import { type BattleCardSlot, } from '@/game/battle-card-slots'; import { canUsePlanetologistBattleSpecial } from '@/game/leader-skill-combat'; +import { mirrorWeaponModeBlock } from '@/game/mirror-weapon-mode'; /** Both selectors evaluate the complete physical pair, in either selection order. */ export function battlePlanCardOptions(game: GameView, slot: BattleCardSlot, leader: string, weapon: string, defense: string) { @@ -34,6 +36,7 @@ export function battlePlanCardOptions(game: GameView, slot: BattleCardSlot, lead }); return (card.id !== 'ecaz-reinforcements' || (!!game.battle?.reinforcements && !game.battle.reinforcements.blocked)) && (card.id !== 'ecaz-harass-withdraw' || (!!game.battle?.harassWithdraw && !game.battle.harassWithdraw.blocked)) && + (!isMirrorWeapon(card) || !mirrorWeaponModeBlock(game)) && battleCardSlotEligible(slot, card, { planetologistWeapon }) && (!fixed || fixedBattleInspectionMatches(slot, fixed.value, card.id, card)) && validBattleSlotPair(w, d, planetologistWeapon); @@ -154,7 +157,7 @@ export function BattlePreparation({ value={kind} onChange={(e) => setKind(e.target.value)} > - {VOICE_KINDS.map((k) => ( + {VOICE_KINDS.filter((name) => name !== 'mirrorWeapon' || !mirrorWeaponModeBlock(game)).map((k) => ( diff --git a/components/game-table.tsx b/components/game-table.tsx index 3b7b4363..4017e0dd 100644 --- a/components/game-table.tsx +++ b/components/game-table.tsx @@ -141,9 +141,11 @@ import { CHEAP_HERO_TRAITOR, matchingTraitor } from '@/game/traitors'; import { battleCardLabel, isStoneBurner, + isMirrorWeapon, } from '@/game/battle-cards'; import { validBattleSlotPair } from '@/game/battle-card-slots'; import { stoneBurnerPlanBlock } from '@/game/stone-burner'; +import { mirrorWeaponModeBlock } from '@/game/mirror-weapon-mode'; import { TECH_TOKENS, ownedTech } from '@/game/tech-tokens'; import { canUseAsKaramaRole, @@ -518,15 +520,14 @@ export function GameTable({ selectedWeaponId === 'ecaz-harass-withdraw' || selectedDefenseId === 'ecaz-harass-withdraw', committed.dial ? Number(committed.dial.value) : battleDial, me.faction === 'fremen' && !g.battle?.fremenSupportBlocked ? 0 : battleSupport); - const selectedStone = me.hand?.find( - (c) => - c.id === selectedWeaponId && - isStoneBurner(c), + const selectedSpecialWeapon = me.hand?.find( + (c) => c.id === selectedWeaponId && (isStoneBurner(c) || isMirrorWeapon(c)), ); + const mirrorSelected = isMirrorWeapon(selectedSpecialWeapon); const stoneContext = g.battle?.stoneBurnerContext; - const stonePlanReason = !selectedStone + const stonePlanReason = !selectedSpecialWeapon ? null - : (stoneContext?.blocked ?? + : ((mirrorSelected ? mirrorWeaponModeBlock(g) : null) ?? stoneContext?.blocked ?? (!ownBattleForces || !stoneContext?.opponentPools.length ? 'Stone Burner needs a supported physical force pool.' : (stoneContext.opponentPools @@ -2296,7 +2297,7 @@ export function GameTable({ : g.decision.kind === 'ecazSpice' ? 'Shared spice collection' : g.decision.kind === 'stoneBurner' - ? 'Stone Burner' + ? g.decision.copiedFrom ? 'Mirror Weapon copying Stone Burner' : 'Stone Burner' : g.decision.kind === 'nullentropy' ? 'Private Nullentropy Box search' : g.decision.kind === 'richeseAllyOpportunity' @@ -2695,7 +2696,7 @@ export function GameTable({ /> ) : g.decision.kind === 'stoneBurner' ? ( <> -

Choose Stone Burner’s effect

+

Choose {g.decision.copiedFrom ? 'Mirror Weapon’s copied Stone Burner' : 'Stone Burner'} effect

Choose whether both leaders die or surviving leaders contribute no strength. Both choices compare undialed @@ -2703,6 +2704,7 @@ export function GameTable({ comparison. Other weapon effects and traitor precedence still apply.

+ {g.decision.copiedFrom &&

Your physical Mirror Weapon copied the opposing Stone Burner. Your choice is independent of its owner’s; winning retains Mirror as Mirror.

} Read Stone Burner rules @@ -2729,6 +2731,7 @@ export function GameTable({ ) : g.decision.kind === 'poisonTooth' ? ( <> + {g.decision.copiedFrom &&

Your physical Mirror Weapon copied the opposing Poison Tooth. Choose its activation independently; a winning Mirror remains Mirror.

}

Choose after reviewing both plans. Activating attacks both leaders, including yours. Chemistry as a defense protects; @@ -2736,10 +2739,12 @@ export function GameTable({

{actionButton('Activate Poison Tooth', { type: 'decision', + event: g.decision.event, activate: true, })} {actionButton('Leave it unused', { type: 'decision', + event: g.decision.event, activate: false, })} @@ -3953,6 +3958,7 @@ export function GameTable({ {g.revival.leaders.map((choice) => (