Compose Harass withdrawal with provisional Stone Burner timing
This commit is contained in:
1 parent
5d13374f7e
commit
feb2b03251
23 files changed
+1362
-124
No files matched your search
@@ -163,7 +163,7 @@ import {
|
||||
isMirrorWeapon,
|
||||
} from '@/game/battle-cards';
|
||||
import { validBattleSlotPair } from '@/game/battle-card-slots';
|
||||
import { stoneBurnerPlanBlock } from '@/game/stone-burner';
|
||||
import { stoneBurnerPlanBlock, stoneBurnerWithdrawalContext } from '@/game/stone-burner';
|
||||
import { mirrorWeaponModeBlock } from '@/game/mirror-weapon-mode';
|
||||
import { TECH_TOKENS, ownedTech } from '@/game/tech-tokens';
|
||||
import {
|
||||
@@ -577,6 +577,11 @@ export function GameTable({
|
||||
);
|
||||
const mirrorSelected = isMirrorWeapon(selectedSpecialWeapon);
|
||||
const stoneContext = g.battle?.stoneBurnerContext;
|
||||
const stoneCandidateContext = selectedSpecialWeapon &&
|
||||
(selectedWeaponId === 'ecaz-harass-withdraw' || selectedDefenseId === 'ecaz-harass-withdraw')
|
||||
? stoneBurnerWithdrawalContext(stoneContext?.occupy,
|
||||
g.battle!.attacker === me.id ? 'attacker' : 'defender', g.battle?.harassWithdraw?.occupy)
|
||||
: stoneContext?.occupy;
|
||||
const stonePlanReason = !selectedSpecialWeapon
|
||||
? null
|
||||
: ((mirrorSelected ? mirrorWeaponModeBlock(g) : null) ?? stoneContext?.blocked ??
|
||||
@@ -595,7 +600,7 @@ export function GameTable({
|
||||
g.battle!.tieWinner === g.battle!.attacker
|
||||
? 'attacker'
|
||||
: 'defender',
|
||||
stoneContext.occupy,
|
||||
stoneCandidateContext,
|
||||
),
|
||||
)
|
||||
.find((reason) => reason !== null) ?? null)));
|
||||
@@ -3083,6 +3088,11 @@ export function GameTable({
|
||||
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>}
|
||||
<p className="fine">
|
||||
Provisional Harass & Withdraw timing: compare undialed
|
||||
forces remaining after a successful withdrawal. An opposing
|
||||
successful Traitor cancels that withdrawal.
|
||||
</p>
|
||||
<a href="/rules?topic=stone-burner">
|
||||
Read Stone Burner rules
|
||||
</a>
|
||||
|
||||
@@ -81,6 +81,11 @@ export function HarassWithdrawGuide({
|
||||
Your leader can still die. An opponent’s successful traitor call cancels
|
||||
the withdrawal. Discard this card after use.
|
||||
</p>
|
||||
<p className="fine">
|
||||
Provisional Stone Burner timing: compare undialed forces remaining after
|
||||
a successful withdrawal, not the forces already returned to reserves.
|
||||
In Occupy, the other ally’s undialed forces still count.
|
||||
</p>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -2,7 +2,7 @@ import type { GameView } from '@/game/engine';
|
||||
import { faction } from '@/game/catalog';
|
||||
import { leaderStrengthLabel } from '@/game/cards';
|
||||
import { isStoneBurner } from '@/game/battle-cards';
|
||||
import { stoneBurnerComparison } from '@/game/stone-burner';
|
||||
import { stoneBurnerComparison, stoneBurnerWithdrawalContext } from '@/game/stone-burner';
|
||||
import { RevealedPlanPieces } from './revealed-plan-pieces';
|
||||
import { KwisatzInspector } from './kwisatz-inspector';
|
||||
export function RevealedBattle({ game }: { game: GameView }) {
|
||||
@@ -10,6 +10,18 @@ export function RevealedBattle({ game }: { game: GameView }) {
|
||||
if (!battle?.revealed) return null;
|
||||
const attackerPlan = battle.plans[battle.attacker];
|
||||
const defenderPlan = battle.plans[battle.defender];
|
||||
let stoneContext = battle.stoneBurnerContext?.occupy;
|
||||
const withdrawing = [battle.attacker, battle.defender].filter(id => {
|
||||
const plan = battle.plans[id];
|
||||
return plan && [plan.weapon, plan.defense].includes('ecaz-harass-withdraw');
|
||||
});
|
||||
for (const id of withdrawing) {
|
||||
const occupy = battle.ecazOccupy?.lead === id ? {
|
||||
cardUser: id, ecaz: battle.ecazOccupy.ecaz, canceled: battle.ecazOccupy.canceled,
|
||||
} : undefined;
|
||||
stoneContext = stoneBurnerWithdrawalContext(stoneContext,
|
||||
id === battle.attacker ? 'attacker' : 'defender', occupy);
|
||||
}
|
||||
const comparisons =
|
||||
battle.cards.some(isStoneBurner) &&
|
||||
battle.ownForces &&
|
||||
@@ -23,6 +35,8 @@ export function RevealedBattle({ game }: { game: GameView }) {
|
||||
game.me === battle.defender ? battle.ownForces! : opponent,
|
||||
defenderPlan.dial,
|
||||
defenderPlan.support,
|
||||
battle.tieWinner === battle.attacker ? 'attacker' : 'defender',
|
||||
stoneContext,
|
||||
),
|
||||
)
|
||||
: [];
|
||||
@@ -37,6 +51,13 @@ export function RevealedBattle({ game }: { game: GameView }) {
|
||||
<h3>Revealed battle plans</h3>
|
||||
<a className="battle-display-link" href="#table-decisions">Return to battle decisions</a>
|
||||
<p className="revealed-plans-help">Both plans are public. Inspect any played component; use the decision controls to resolve the battle. <a href="/rules?topic=revealed-battle-components#revealed-battle-components">Component guide</a> · <a href="/rules?topic=battle#battle">Battle rules</a></p>
|
||||
{comparisons.length > 0 && withdrawing.length > 0 && (
|
||||
<p className="fine">
|
||||
Provisional Stone Burner counts below assume Harass & Withdraw
|
||||
succeeds. An opposing successful Traitor cancels withdrawal and decides
|
||||
the battle instead.
|
||||
</p>
|
||||
)}
|
||||
<div className="revealed-plan-pair">
|
||||
{[battle.attacker, battle.defender].map((id) => {
|
||||
const plan = battle.plans[id];
|
||||
|
||||
Reference in new issue
Block a user