Files
Dune/components/fremen-revival-grant.tsx
T
Quintine 220d78c779
Verify and publish checkpoint container / container (push) Canceled after 0s
Coordinate Fremen grants in ordered revival
2026-10-29 09:19:17 +11:00

124 lines
6.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use client';
import { ArrowRight } from 'lucide-react';
import { Button } from '@/components/ui/button';
import type { Action, GameView } from '@/game/engine';
export function FremenRevivalGrant({
game,
act,
busy,
}: {
game: GameView;
act: (action: Action) => void;
busy: boolean;
}) {
const grant = game.fremenRevivalGrant;
const me = game.players.find((player) => player.id === game.me);
const group = game.fremenGroupRevivalGrant;
const orderedAutomatedAlly = (recipient: string) =>
game.advanced && !!game.revivalOrder && game.revivalOrder.remaining.includes(recipient) &&
game.players.some(player => player.id === recipient && (player.bot || player.autopilot));
if (group && game.status === 'playing' && game.phase === 4 && me?.faction === 'fremen') {
const busyWindow = busy || !!game.truthtrance || !!game.nexusTraitors?.pending;
return (
<section className="notice space-y-3" aria-label="Fremen group ally free revival">
<h3 className="font-serif text-xl">Fremen group revival</h3>
<p className="muted">Choose an ally to receive the ordinary Fremen free-revival rate of three for this phase. This replaces their native rate; normal revival limits still apply.</p>
<p className="fine">The grant to each ally is a provisional interpretation of the larger-alliance rule. Each declaration opens a Karama response.</p>
{!game.advanced && !game.revivalOrder && group.some(option => !option.granted && !option.blocked &&
game.players.some(player => player.id === option.recipient && (player.bot || player.autopilot))) &&
<p className="fine">When a grant would help an automated ally, it waits to revive forces until you grant it or select Ready for next phase.</p>}
{group.map(option => {
const name = game.players.find(player => player.id === option.recipient)?.name ?? 'Ally';
return <div key={option.recipient}>
{option.granted && <p className="fine">Free revival has been granted to {name}.</p>}
{option.blocked && !option.granted && <p className="fine">{name}: {option.blocked}</p>}
<Button className="game-action" disabled={busyWindow || option.granted || !!option.blocked}
onClick={() => act({type:'grantRevival', target:option.recipient})}>
Grant {name} free revival <ArrowRight size={15} />
</Button>
{orderedAutomatedAlly(option.recipient) && !option.granted && !option.blocked && <>
<p className="fine">An automated ally may wait for your grant when it would help. You can let it continue without waiting and may still grant later while Revival remains open.</p>
<Button data-testid={`fremen-revival-wait-release-${option.recipient}`}
className="game-action" disabled={busyWindow || !!option.waitReleased}
onClick={() => {
if (!busyWindow && !option.waitReleased)
act({type:'releaseFremenRevivalWait', target:option.recipient});
}}>
Continue without waiting <ArrowRight size={15} />
</Button>
{option.waitReleased && <p className="fine">{name} can continue without waiting for your grant.</p>}
</>}
</div>;
})}
</section>
);
}
if (!grant || game.status !== 'playing' || game.phase !== 4 ||
me?.faction !== 'fremen' || me.ally !== grant.recipient) return null;
const recipient = game.players.find((player) => player.id === grant.recipient)?.name ?? 'your ally';
const automatedAlly = game.players.find(player => player.id === grant.recipient);
const disabled = busy || !!game.truthtrance || !!game.nexusTraitors?.pending ||
!!grant.blocked || grant.granted;
return (
<section className="notice space-y-3" aria-label="Fremen ally free revival">
<h3 className="font-serif text-xl">Fremen ally revival</h3>
<p className="muted">
Grant {recipient} an ordinary free force revival rate of three this phase. This
replaces their native ordinary rate rather than adding three. Ordinary total
revival and special-force limits still apply.
</p>
{!game.advanced && !game.revivalOrder && !group &&
(automatedAlly?.bot || automatedAlly?.autopilot) && !grant.granted && !grant.blocked && (
<p className="fine">When this grant would help your automated ally, it waits to revive forces until you grant it or select Ready for next phase.</p>
)}
{orderedAutomatedAlly(grant.recipient) && !grant.granted && !grant.blocked && <>
<p className="fine">An automated ally may wait for your grant when it would help. You can let it continue without waiting and may still grant later while Revival remains open.</p>
<Button data-testid={`fremen-revival-wait-release-${grant.recipient}`}
className="game-action" disabled={disabled || !!grant.waitReleased}
onClick={() => {
if (!disabled && !grant.waitReleased)
act({type:'releaseFremenRevivalWait', target:grant.recipient});
}}>
Continue without waiting <ArrowRight size={15} />
</Button>
{grant.waitReleased && <p className="fine">{recipient} can continue without waiting for your grant.</p>}
</>}
<p className="fine">
Provisional current-rate policy: Recruits doubles the current granted rate, including applicable bonuses,
before caps. Without other bonuses, three becomes six.
Later grants use the same current-rate calculation;
your ally’s revival controls show the actual remaining allowance.
Previously recorded free returns use the allowance. Earlier paid returns keep their payments and count
toward the total revival limit; there is no refund or retroactive
reclassification.
</p>
<p className="fine">
Another player may use Karama before the original alliance grant takes effect.
Canceling blocks this Fremen grant to the current ally for this Revival;
the ally retains its intrinsic rate and Recruits. Completed revivals and
payments stay unchanged. This response timing and prevention scope are provisional.
</p>
{grant.granted && (
<p className="muted">Free revival has been granted to {recipient} for this phase.</p>
)}
{grant.blocked && (
<p className="notice" id="fremen-revival-grant-unavailable">{grant.blocked}</p>
)}
<Button
data-testid="fremen-revival-grant-submit"
className="game-action"
disabled={disabled}
aria-describedby={grant.blocked ? 'fremen-revival-grant-unavailable' : undefined}
onClick={() => {
if (!disabled) act({ type: 'grantRevival' });
}}
>
Grant ally free revival
<ArrowRight size={15} />
</Button>
</section>
);
}