Files
Dune/components/coalition-emperor-aid.tsx
Quintine 9081249574
Verify and publish checkpoint container / container (push) Successful in 10m13s
Support staged group Emperor aid
2026-10-28 23:33:37 +11:00

104 lines
5.7 KiB
TypeScript
Raw Permalink 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 { useId, useState } from 'react';
import type { Player } from '@/game/engine';
import { eliteRevivalRemaining, paidForceRevivalCost } from '@/game/revival';
/** Public alliance members only; the engine remains the legality authority. */
export type CoalitionEmperorAlly = Pick<Player, 'id' | 'name' | 'faction' | 'tanks' | 'elites'>;
export type CoalitionEmperorAidAction =
| { type: 'emperorGift'; target: string; amount: number }
| { type: 'emperorRevival'; target: string; amount: number; elite: number };
function positiveInteger(raw: string): number | null {
if (!/^[1-9]\d*$/.test(raw)) return null;
const value = Number(raw);
return Number.isSafeInteger(value) ? value : null;
}
/** Render for an Emperor in a staged group alliance; pair controls stay separate. */
export function CoalitionEmperorAid({ allies, phase, advanced, availableSpice,
emperorExtra, revivalOrderHead, busy = false, act }: {
allies: readonly CoalitionEmperorAlly[];
phase: number;
advanced: boolean;
/** Spice not committed to coalition pledges or other obligations. */
availableSpice: number;
emperorExtra: Readonly<Record<string, number>>;
revivalOrderHead?: string | null;
busy?: boolean;
act: (action: CoalitionEmperorAidAction) => void;
}) {
const id = useId();
const [giftTarget, setGiftTarget] = useState('');
const [giftRaw, setGiftRaw] = useState('1');
const [revivalTarget, setRevivalTarget] = useState('');
const [revivalRaw, setRevivalRaw] = useState('1');
const [eliteRaw, setEliteRaw] = useState('0');
const giftAlly = allies.find(ally => ally.id === giftTarget);
const revivalAlly = allies.find(ally => ally.id === revivalTarget);
const gift = positiveInteger(giftRaw);
const amount = positiveInteger(revivalRaw);
const elite = /^(0|[1-9]\d*)$/.test(eliteRaw) && Number.isSafeInteger(Number(eliteRaw))
? Number(eliteRaw) : null;
const maximum = revivalAlly ? Math.max(0, Math.min(
3 - (emperorExtra[revivalAlly.id] ?? 0), revivalAlly.tanks,
Math.floor(availableSpice / 2),
)) : 0;
const cost = revivalAlly && amount && elite !== null
? paidForceRevivalCost(revivalAlly, amount, elite) : null;
const eliteTanks = revivalAlly?.elites?.tanks ?? 0;
const legalElite = revivalAlly && amount && elite !== null &&
elite <= Math.min(amount, eliteRevivalRemaining(revivalAlly, advanced)) &&
amount - elite <= revivalAlly.tanks - eliteTanks;
const canGift = !!giftAlly && gift !== null && gift <= availableSpice && !busy;
const canRevive = phase === 4 && !!revivalAlly && amount !== null && amount <= maximum &&
legalElite && cost !== null && cost <= availableSpice &&
(!revivalOrderHead || revivalOrderHead === revivalAlly.id) && !busy;
return <section className="notice min-w-0 space-y-3 break-words" aria-label="Emperor group alliance aid">
<h3>Emperor alliance aid</h3>
<p className="fine">Choose which ally receives your aid. Spice and revivals take effect after the Karama response.</p>
<label htmlFor={`${id}-gift-target`}>Ally to receive spice</label>
<select id={`${id}-gift-target`} value={giftAlly?.id ?? ''} disabled={busy}
onChange={event => setGiftTarget(event.target.value)}>
<option value="">Choose an ally</option>
{allies.map(ally => <option key={ally.id} value={ally.id}>{ally.name}</option>)}
</select>
<label htmlFor={`${id}-gift-amount`}>Spice to share</label>
<input id={`${id}-gift-amount`} type="number" inputMode="numeric" min={1} max={availableSpice}
step={1} value={giftRaw} disabled={busy} onChange={event => setGiftRaw(event.target.value)} />
<button type="button" className="game-action" disabled={!canGift}
onClick={() => { if (giftAlly && gift !== null && canGift) act({ type: 'emperorGift', target: giftAlly.id, amount: gift }); }}>
Share spice
</button>
{phase === 4 && <div className="space-y-2">
<label htmlFor={`${id}-revival-target`}>Ally to receive extra revivals</label>
<select id={`${id}-revival-target`} value={revivalAlly?.id ?? ''} disabled={busy}
onChange={event => { setRevivalTarget(event.target.value); setEliteRaw('0'); }}>
<option value="">Choose an ally</option>
{allies.map(ally => <option key={ally.id} value={ally.id}>{ally.name}</option>)}
</select>
{revivalAlly && <>
<p className="fine">{revivalAlly.name} has {revivalAlly.tanks} forces in the tanks and can receive up to {maximum} extra revivals now.</p>
{revivalOrderHead && revivalOrderHead !== revivalAlly.id && <p className="fine">Wait for this ally’s revival turn.</p>}
<label htmlFor={`${id}-revival-amount`}>Forces to revive</label>
<input id={`${id}-revival-amount`} type="number" inputMode="numeric" min={1} max={maximum}
step={1} value={revivalRaw} disabled={busy} onChange={event => setRevivalRaw(event.target.value)} />
{revivalAlly.elites && <>
<label htmlFor={`${id}-revival-elite`}>{revivalAlly.faction === 'ixians' ? 'Cyborgs' : 'Elite forces'}</label>
<input id={`${id}-revival-elite`} type="number" inputMode="numeric" min={0}
max={Math.min(amount ?? 0, eliteRevivalRemaining(revivalAlly, advanced))} step={1}
value={eliteRaw} disabled={busy} onChange={event => setEliteRaw(event.target.value)} />
</>}
{cost !== null && <p className="fine">You pay {cost} spice. This uses {revivalAlly.name}’s extra-revival allowance.</p>}
</>}
<button type="button" className="game-action" disabled={!canRevive}
onClick={() => { if (revivalAlly && amount !== null && elite !== null && canRevive)
act({ type: 'emperorRevival', target: revivalAlly.id, amount, elite }); }}>
Pay for extra ally revivals
</button>
</div>}
</section>;
}