Apply ruled Grumman return and Cunning placement
This commit is contained in:
1 parent
7994026a70
commit
2efa69d10f
24 files changed
+1246
-219
No files matched your search
@@ -31,79 +31,92 @@ export function GrummanCollection({ game, act, busy }: Props) {
|
||||
|
||||
function CollectionChoice({ game, act, busy }: Props) {
|
||||
const offer = game.grummanCollection!;
|
||||
const [token, setToken] = useState(offer.tokens[0]?.id ?? '');
|
||||
const [destination, setDestination] = useState(
|
||||
offer.destinations[0]?.id ?? '',
|
||||
const initialMode = offer.blocked && !offer.removeBlocked ? 'remove' : 'add';
|
||||
const [mode, setMode] = useState<'add' | 'remove'>(initialMode);
|
||||
const [token, setToken] = useState(
|
||||
(initialMode === 'remove' ? offer.removable : offer.tokens)[0]?.id ?? '',
|
||||
);
|
||||
const [destination, setDestination] = useState(offer.destinations[0]?.id ?? '');
|
||||
const [useCunning, setUseCunning] = useState(false);
|
||||
const canAct = grummanCollectionCanAct(game);
|
||||
const choice = grummanCollectionChoice(game, token, destination);
|
||||
const disabled = busy || !canAct || !!offer.blocked;
|
||||
const tokens = mode === 'remove' ? offer.removable : offer.tokens;
|
||||
const destinations = useCunning && offer.cunning ? offer.cunning.destinations : offer.destinations;
|
||||
const choice = grummanCollectionChoice(game, token, mode === 'add' ? destination : '', mode, useCunning);
|
||||
const disabled = busy || !canAct;
|
||||
return (
|
||||
<section aria-label="Grumman collection" className="space-y-3">
|
||||
<p>
|
||||
Add one available Terror token to a stronghold to collect 4 spice. You
|
||||
receive the spice only after the addition resolves.
|
||||
Add one available Terror token or return one unrevealed placed token to
|
||||
hidden supply to collect 4 spice. Only one completed operation pays this
|
||||
Collection; your separate Mentat placement remains unchanged.
|
||||
</p>
|
||||
<label className="block space-y-1">
|
||||
<span>Your Terror token</span>
|
||||
<select
|
||||
aria-label="Your Terror token"
|
||||
className="w-full"
|
||||
value={token}
|
||||
disabled={disabled || !offer.tokens.length}
|
||||
onChange={(event) => setToken(event.target.value)}
|
||||
>
|
||||
{!offer.tokens.length && <option value="">No available token</option>}
|
||||
{offer.tokens.map((candidate) => (
|
||||
<option key={candidate.id} value={candidate.id}>
|
||||
{TERROR_DEFINITIONS[candidate.kind].name}
|
||||
</option>
|
||||
))}
|
||||
<span>Grumman operation</span>
|
||||
<select aria-label="Grumman operation" className="w-full" value={mode} disabled={disabled}
|
||||
onChange={event => {
|
||||
const next = event.target.value === 'remove' ? 'remove' : 'add';
|
||||
setMode(next);
|
||||
setUseCunning(false);
|
||||
setToken((next === 'remove' ? offer.removable : offer.tokens)[0]?.id ?? '');
|
||||
setDestination(offer.destinations[0]?.id ?? '');
|
||||
}}>
|
||||
<option value="add">Add from supply</option>
|
||||
<option value="remove">Return placed token to hidden supply</option>
|
||||
</select>
|
||||
</label>
|
||||
<label className="block space-y-1">
|
||||
<span>Destination stronghold</span>
|
||||
<select
|
||||
aria-label="Destination stronghold"
|
||||
className="w-full"
|
||||
value={destination}
|
||||
disabled={disabled || !offer.destinations.length}
|
||||
onChange={(event) => setDestination(event.target.value)}
|
||||
>
|
||||
{!offer.destinations.length && (
|
||||
<option value="">No available stronghold</option>
|
||||
)}
|
||||
{offer.destinations.map((target) => (
|
||||
<option key={target.id} value={target.id}>
|
||||
{target.name}
|
||||
<span>Your Terror token</span>
|
||||
<select aria-label="Your Terror token" className="w-full" value={token}
|
||||
disabled={disabled || !tokens.length} onChange={event => setToken(event.target.value)}>
|
||||
{!tokens.length && <option value="">No eligible token</option>}
|
||||
{tokens.map(candidate => (
|
||||
<option key={candidate.id} value={candidate.id}>
|
||||
{TERROR_DEFINITIONS[candidate.kind].name}
|
||||
{'name' in candidate && typeof candidate.name === 'string' ? ` · ${candidate.name}` : ''}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</label>
|
||||
{choice.blocked && (
|
||||
<p className="fine" aria-live="polite">
|
||||
{choice.blocked}
|
||||
</p>
|
||||
)}
|
||||
<p className="fine">Removal unavailable: {offer.removeBlocked}</p>
|
||||
{mode === 'add' && <>
|
||||
{offer.cunning && <label className="decision-checkbox min-h-11">
|
||||
<input type="checkbox" checked={useCunning} disabled={disabled || !!offer.cunning.blocked}
|
||||
onChange={event => {
|
||||
const use = event.target.checked;
|
||||
setUseCunning(use);
|
||||
const choices = use ? offer.cunning!.destinations : offer.destinations;
|
||||
if (!choices.some(place => place.id === destination))
|
||||
setDestination(choices[0]?.id ?? '');
|
||||
}} />
|
||||
<span>Use Moritani Nexus Cunning for this eligible addition</span>
|
||||
</label>}
|
||||
<label className="block space-y-1">
|
||||
<span>{useCunning ? 'Destination Arrakis territory' : 'Destination stronghold'}</span>
|
||||
<select aria-label={useCunning ? 'Destination Arrakis territory' : 'Destination stronghold'}
|
||||
className="w-full" value={destination} disabled={disabled || !destinations.length}
|
||||
onChange={event => setDestination(event.target.value)}>
|
||||
{!destinations.length && <option value="">No eligible destination</option>}
|
||||
{destinations.map(target => <option key={target.id} value={target.id}>{target.name}</option>)}
|
||||
</select>
|
||||
</label>
|
||||
{offer.cunning && <p className="fine">
|
||||
Cunning requires an ordinary eligible supply addition at a stronghold
|
||||
already holding Terror. It spends the Nexus card, not your Mentat
|
||||
placement, and never permits relocation, Homeworlds or the Hidden
|
||||
Mobile Stronghold.
|
||||
</p>}
|
||||
</>}
|
||||
{mode === 'remove' && <p className="fine">
|
||||
The token stays unrevealed and becomes reusable in hidden supply.
|
||||
Hidden supply identities rotate; other placed tokens remain where they are.
|
||||
</p>}
|
||||
{choice.blocked && <p className="fine" aria-live="polite">{choice.blocked}</p>}
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<Button
|
||||
className="game-action"
|
||||
disabled={busy || !choice.action}
|
||||
onClick={() => {
|
||||
if (choice.action) act(choice.action);
|
||||
}}
|
||||
>
|
||||
Add token and collect 4 spice
|
||||
<Button className="game-action" disabled={busy || !choice.action}
|
||||
onClick={() => { if (choice.action) act(choice.action); }}>
|
||||
{mode === 'remove' ? 'Return token and collect 4 spice' : 'Add token and collect 4 spice'}
|
||||
</Button>
|
||||
<Button
|
||||
className="game-action"
|
||||
variant="outline"
|
||||
disabled={busy || !canAct}
|
||||
onClick={() =>
|
||||
act({ type: 'decision', event: offer.event, decline: true })
|
||||
}
|
||||
>
|
||||
<Button className="game-action" variant="outline" disabled={disabled}
|
||||
onClick={() => act({ type: 'decision', event: offer.event, decline: true })}>
|
||||
Pass Grumman collection
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -324,7 +324,7 @@ export function MoritaniPlacement({
|
||||
onChange={(event) => setUseNexus(event.target.checked)} />
|
||||
Use Moritani Nexus Cunning
|
||||
</label>
|
||||
<p className="fine">Cunning places one available supply token in an Arrakis territory, including one with Terror already present. It spends the Nexus card and this Mentat placement opportunity. Relocation, Grumman use, Homeworlds and the Hidden Mobile Stronghold are unavailable.</p>
|
||||
<p className="fine">Cunning places one available supply token in an Arrakis territory, including one with Terror already present. It spends the Nexus card and this Mentat placement opportunity. It never enhances relocation or permits Homeworlds or the Hidden Mobile Stronghold. High-Grumman Cunning additions use their separate Collection controls and require an ordinary eligible addition.</p>
|
||||
{game.nexusMoritani.blocked && <p className="fine">{game.nexusMoritani.blocked}</p>}
|
||||
</>}
|
||||
<div className="flex min-w-0 flex-col gap-3">
|
||||
|
||||
Reference in new issue
Block a user