124 lines
6.8 KiB
TypeScript
124 lines
6.8 KiB
TypeScript
'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>
|
||
);
|
||
}
|