Integrate Ecaz Duke revival and bounded Mirror Weapon battles
This commit is contained in:
1 parent
68d782ca82
commit
3b118dd845
39 files changed
+1377
-533
No files matched your search
@@ -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) => (
|
||||
<option key={k} value={k}>
|
||||
{battleCardLabel(k)}
|
||||
</option>
|
||||
|
||||
+29
-18
@@ -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' ? (
|
||||
<>
|
||||
<h3>Choose Stone Burner’s effect</h3>
|
||||
<h3>Choose {g.decision.copiedFrom ? 'Mirror Weapon’s copied Stone Burner' : 'Stone Burner'} effect</h3>
|
||||
<p className="muted">
|
||||
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.
|
||||
</p>
|
||||
{g.decision.copiedFrom && <p className="fine">Your physical Mirror Weapon copied the opposing Stone Burner. Your choice is independent of its owner’s; winning retains Mirror as Mirror.</p>}
|
||||
<a href="/rules?topic=stone-burner">
|
||||
Read Stone Burner rules
|
||||
</a>
|
||||
@@ -2729,6 +2731,7 @@ export function GameTable({
|
||||
</>
|
||||
) : g.decision.kind === 'poisonTooth' ? (
|
||||
<>
|
||||
{g.decision.copiedFrom && <p className="fine">Your physical Mirror Weapon copied the opposing Poison Tooth. Choose its activation independently; a winning Mirror remains Mirror.</p>}
|
||||
<p className="muted">
|
||||
Choose after reviewing both plans. Activating attacks both
|
||||
leaders, including yours. Chemistry as a defense protects;
|
||||
@@ -2736,10 +2739,12 @@ export function GameTable({
|
||||
</p>
|
||||
{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) => (
|
||||
<option value={choice.id} key={choice.id}>
|
||||
{choice.name} · {choice.cost} spice
|
||||
{choice.id === 'duke-vidal' ? ' · strength 6, set aside after revival' : ''}
|
||||
{choice.early ? ' · early revival' : ''}
|
||||
{!choice.affordable
|
||||
? ' · insufficient spice'
|
||||
@@ -3972,13 +3978,18 @@ export function GameTable({
|
||||
</>
|
||||
);
|
||||
})()
|
||||
: me.leaders.some(
|
||||
(l) => l.dead && l.faction === me.faction,
|
||||
) && (
|
||||
: (me.leaders.some((l) => l.dead && l.faction === me.faction) ||
|
||||
(me.faction === 'ecaz' && g.dukeVidal?.leader.dead)) && (
|
||||
<p className="fine">
|
||||
{me.leaderRevived && me.faction !== 'tleilaxu'
|
||||
? 'You have used your one leader revival for this phase.'
|
||||
: 'No native leader is currently eligible for normal revival.'}
|
||||
: g.revival.prevented
|
||||
? 'Tleilaxu prevented your normal revivals this turn.'
|
||||
: g.revival.dukeBlocked && me.faction === 'ecaz' && g.dukeVidal?.leader.dead
|
||||
? g.revival.dukeBlocked
|
||||
: g.revival.cycleBlock
|
||||
? g.revival.cycleBlock
|
||||
: 'No leader is currently eligible for normal revival.'}
|
||||
</p>
|
||||
)}
|
||||
</>
|
||||
@@ -4849,14 +4860,14 @@ export function GameTable({
|
||||
Discard it after this battle, even if you win.
|
||||
</p>
|
||||
)}
|
||||
{selectedStone && (
|
||||
{selectedSpecialWeapon && (
|
||||
<div className="my-3 flex flex-col gap-3">
|
||||
<p>
|
||||
Stone Burner compares undialed physical
|
||||
tokens, not dialed strength. Its leader effect
|
||||
is chosen after both plans are revealed.
|
||||
{mirrorSelected
|
||||
? 'Mirror Weapon copies the opposing revealed weapon attack, not its physical card. If the copied attack is Stone Burner, it uses undialed physical tokens; any copied post-reveal choice is yours. A winning Mirror remains physical Mirror, even if it copies activated Poison Tooth or Artillery.'
|
||||
: 'Stone Burner compares undialed physical tokens, not dialed strength. Its leader effect is chosen after both plans are revealed.'}
|
||||
</p>
|
||||
<CardInspector card={selectedStone} />
|
||||
<CardInspector card={selectedSpecialWeapon} />
|
||||
{stonePlanReason && (
|
||||
<p className="notice" role="status">
|
||||
{stonePlanReason}
|
||||
@@ -5290,7 +5301,7 @@ export function GameTable({
|
||||
<option value="">Forces</option>
|
||||
{g.ghola.leaders.map((l) => (
|
||||
<option key={l.id} value={l.id}>
|
||||
{l.name}
|
||||
{l.name}{l.id === 'duke-vidal' ? ' · set aside after revival' : ''}
|
||||
</option>
|
||||
))}
|
||||
{g.ghola.kwisatz && (
|
||||
|
||||
Reference in new issue
Block a user