Open Tleilaxu special Karama throughout Revival
Permit native Advanced quiet any-player declarations alongside the original target-bound pending request. Preserve completed returns and charges. Adopt explicit provisional Ghola inclusion during the remaining Revival and phase expiry; keep printed Emperor special immunity and the existing low-Homeworld guard. Connect persistent controls and minimal legal bots through the shared quote. Validation: 14 related files148/148, types/lint/build, six actual native control/engine/GameTable SSR journeys and four existing-profile continuations. Private-driver-only feedback reuses passed source-identical checks. Native111/113-action setup, paid shipment and battle sources earn originals. No saved-room/D1/restart/deployment operation; full modes and browser/deployed acceptance remain open.
This commit is contained in:
1 parent
6ea691255a
commit
1a505525a8
18 files changed
+977
-59
No files matched your search
@@ -55,6 +55,7 @@ import { CharityClaim } from './charity-claim';
|
||||
import { FremenRevivalGrant } from './fremen-revival-grant';
|
||||
import { AdvisorPreparation } from './advisor-preparation';
|
||||
import { AtreidesFullPlan } from './atreides-full-plan';
|
||||
import { TleilaxuSpecialKarama } from './tleilaxu-special-karama';
|
||||
import { homeworldRevivalActionBlock } from '@/game/homeworld-revival-deployment-options';
|
||||
import { GuildHomeworldShipment } from './guild-homeworld-shipment';
|
||||
import { JunctionTransport } from './junction-transport';
|
||||
@@ -1977,6 +1978,7 @@ export function GameTable({
|
||||
<Recruits game={g} act={act} busy={busy} />
|
||||
<AdvisorPreparation game={g} act={act} busy={transportBusy || !!g.roomControl?.paused || !!g.roomControl?.closed || !!me.autopilot} />
|
||||
{g.decision?.kind !== 'fullPlanOffer' && <AtreidesFullPlan game={g} act={act} busy={transportBusy || !!g.roomControl?.paused || !!g.roomControl?.closed || !!me.autopilot} />}
|
||||
{g.decision?.kind !== 'revivalStop' && <TleilaxuSpecialKarama game={g} act={act} busy={transportBusy || !!g.roomControl?.paused || !!g.roomControl?.closed || !!me.autopilot} />}
|
||||
<NexusTraitors game={g} act={act} busy={transportBusy || (!!g.roomControl?.paused || !!g.roomControl?.closed) || !!me.autopilot} />
|
||||
<DiscoveryPanel game={g} act={act} busy={transportBusy || (!!g.roomControl?.paused || !!g.roomControl?.closed) || !!me.autopilot} />
|
||||
<LeaderSkillsPanel skills={g.leaderSkills} leaders={g.allLeaders} players={g.players} act={act} busy={transportBusy || (!!g.roomControl?.paused || !!g.roomControl?.closed) || !!me.autopilot} />
|
||||
@@ -3161,8 +3163,8 @@ export function GameTable({
|
||||
declared a{' '}
|
||||
{g.decision.revival === 'forces' ? 'force' : 'leader'}{' '}
|
||||
revival. Allow it or use your once-per-game special Karama
|
||||
to prevent their normal force and leader revivals for this
|
||||
turn.
|
||||
to prevent their further force and leader revivals during
|
||||
this Revival Phase, including Ghola.
|
||||
</p>
|
||||
{g.decision.source === 'nexusEmperor' && (
|
||||
<p className="notice">
|
||||
@@ -3171,21 +3173,7 @@ export function GameTable({
|
||||
income; both the Nexus and your special Karama remain spent.
|
||||
</p>
|
||||
)}
|
||||
<HelpTip topic="tleilaxuSpecial" />
|
||||
{g.revival.specialKaramaBlock && (
|
||||
<p role="status">{g.revival.specialKaramaBlock}</p>
|
||||
)}
|
||||
{me.hand
|
||||
?.filter(canActivateKarama)
|
||||
.map((c) => (
|
||||
<div key={c.id}>
|
||||
{actionButton(
|
||||
'Spend special Karama · prevent revival',
|
||||
{ type: 'card', card: c.id, mode: 'special' },
|
||||
!!g.revival.specialKaramaBlock,
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
<TleilaxuSpecialKarama game={g} act={act} busy={busy} />
|
||||
{actionButton('Allow this revival', {
|
||||
type: 'decision',
|
||||
decline: true,
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
'use client';
|
||||
|
||||
import { ArrowRight } from 'lucide-react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import type { Action, GameView } from '@/game/engine';
|
||||
|
||||
export function TleilaxuSpecialKarama({
|
||||
game,
|
||||
act,
|
||||
busy,
|
||||
}: {
|
||||
game: GameView;
|
||||
act: (action: Action) => void;
|
||||
busy: boolean;
|
||||
}) {
|
||||
const offer = game.tleilaxuSpecialKarama;
|
||||
const me = game.players.find((player) => player.id === game.me);
|
||||
if (!offer || game.status !== 'playing' || !game.advanced ||
|
||||
game.phase !== 4 || me?.faction !== 'tleilaxu') return null;
|
||||
const ownStop = offer.pending && game.decision?.kind === 'revivalStop' &&
|
||||
game.decision.player === game.me;
|
||||
const priorityPending = !!game.response || !!game.phaseOpening || !!game.truthtrance ||
|
||||
!!game.nexusTraitors?.pending || (!!game.decision && !ownStop) || (offer.pending && !ownStop);
|
||||
const blocked = offer.blocked || (priorityPending
|
||||
? 'Finish the current response, decision or phase opening before using this special Karama.'
|
||||
: null);
|
||||
return (
|
||||
<section className="notice space-y-3" aria-label="Tleilaxu special Karama revival prevention">
|
||||
<h3 className="font-serif text-xl">Prevent revival</h3>
|
||||
<p className="muted">
|
||||
Spend your once-per-game special Karama to prevent one faction’s force
|
||||
and leader revivals for the rest of this Revival, including returns from
|
||||
Ghola Treachery. You may choose
|
||||
yourself or an ally, even if the chosen faction is ready or has already
|
||||
completed revivals.
|
||||
</p>
|
||||
<p className="fine">
|
||||
Completed returns and payments stay unchanged. This power does not
|
||||
remove returned pieces or refund spice.
|
||||
</p>
|
||||
<p className="fine">
|
||||
Provisional rule: Ghola Treachery is included during the
|
||||
affected Revival, and this prevention expires when the phase ends. It
|
||||
does not stop Ghola outside Revival.
|
||||
</p>
|
||||
<p className="fine">
|
||||
The Emperor’s native special Karama remains protected by the printed
|
||||
immunity of special Karama powers.
|
||||
</p>
|
||||
{offer.pending && (
|
||||
<p className="fine">Only the original recipient can be selected during this pending revival.</p>
|
||||
)}
|
||||
{blocked && <p className="notice" id="tleilaxu-special-karama-unavailable">{blocked}</p>}
|
||||
{!offer.cards.length && (
|
||||
<p className="muted">No spendable Karama is currently available for this special power.</p>
|
||||
)}
|
||||
{!offer.targets.length && (
|
||||
<p className="muted">No faction is currently available for this revival-prevention power.</p>
|
||||
)}
|
||||
{offer.targets.map((target) => {
|
||||
const name = game.players.find((player) => player.id === target.player)?.name ?? 'the selected faction';
|
||||
const targetReasonId = `tleilaxu-special-karama-${target.player}-unavailable`;
|
||||
const describedBy = [
|
||||
blocked ? 'tleilaxu-special-karama-unavailable' : undefined,
|
||||
target.blocked ? targetReasonId : undefined,
|
||||
].filter(Boolean).join(' ') || undefined;
|
||||
const disabled = busy || !!blocked || !!target.blocked;
|
||||
return (
|
||||
<div key={target.player} className="space-y-2">
|
||||
{target.blocked && <p className="notice" id={targetReasonId}>{name}: {target.blocked}</p>}
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{offer.cards.map((card) => (
|
||||
<Button
|
||||
key={card}
|
||||
data-testid={`tleilaxu-special-karama-${card}-${target.player}`}
|
||||
className="game-action min-h-11 whitespace-normal"
|
||||
disabled={disabled}
|
||||
aria-describedby={describedBy}
|
||||
onClick={() => {
|
||||
if (!disabled) act({ type: 'card', mode: 'special', card, target: target.player });
|
||||
}}
|
||||
>
|
||||
Spend special Karama · prevent revival for {name}
|
||||
<ArrowRight size={15} />
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user