'use client'; import { useState } from 'react'; import type { Action, GameView } from '@/game/engine'; import { faction } from '@/game/catalog'; import { HOMEWORLD_CARDS } from '@/game/homeworld-cards'; import { isHomeworldNoFieldLocation } from '@/game/richese-no-field'; import type { NativeReserveSelections } from '@/game/homeworld-native-reserves'; import { emperorHomeworldMoveAction } from '@/game/homeworld-options'; import type { EmperorHomeworldMove } from '@/game/homeworld-emperor-move'; import { imperialFlightCohort } from '@/game/ornithopter-homeworld'; import { HomeworldFace } from './homeworld-cards'; import { EcazPoisonIncome } from './ecaz-poison-income'; import { Button } from './ui/button'; import { Input } from './ui/input'; export function HomeworldTable({ game: g }: { game: GameView }) { const worlds = g.homeworlds?.worlds; if (!worlds) return null; return (

Homeworlds

Native forces here are your reserves. Salusa Secundus counts Sardaukar for its population.

{worlds.map((world) => { const card = HOMEWORLD_CARDS.find((c) => c.id === world.card)!; const owner = g.players.find((p) => p.id === world.native)!; return (
{card.name} {world.side} population · {world.population}

{faction(owner.faction).name} · {owner.name}

{Object.entries(world.forces).map(([id, forces]) => (

{id === world.native ? 'Native reserves' : g.players.find((p) => p.id === id)?.name} : {forces.normal} normal {forces.elite > 0 ? ` + ${forces.elite} ${owner.faction === 'emperor' && id === owner.id ? 'Sardaukar' : 'special'}` : ''}

))} {g.players.flatMap(player => { const marker = player.noField?.deployed; if (!marker || !isHomeworldNoFieldLocation(marker.location) || marker.location.homeworld !== world.id) return []; return [

{player.name}: concealed No-Field · counts as one force

]; })}
{card.name} card
); })}
); } /** The complementary Kaitain allocation is explicit in the submitted order. */ export function NativeShipmentChoice({ game: g, amount, elite, sources, onChange, busy, }: { game: GameView; amount: number; elite: number; sources: NativeReserveSelections | null; onChange: (sources: NativeReserveSelections) => void; busy: boolean; }) { const homes = g.homeworlds?.worlds?.filter((w) => w.native === g.me); if (!homes || homes.length !== 2 || !sources) return null; const salusa = homes.find((w) => w.secondary)!; const kaitain = homes.find((w) => !w.secondary)!; const chosen = sources[salusa.id]; const requested = { normal: amount - elite, elite }; return (
Shipment sources

Choose the counters leaving Salusa. The remainder leaves Kaitain in the same shipment.

{(['normal', 'elite'] as const).map((kind) => { const minimum = Math.max( 0, requested[kind] - kaitain.forces[g.me][kind], ); const maximum = Math.min(requested[kind], salusa.forces[g.me][kind]); return ( ); })}

Kaitain: {sources[kaitain.id].normal} normal +{' '} {sources[kaitain.id].elite} Sardaukar. Salusa: {chosen.normal} normal +{' '} {chosen.elite} Sardaukar.

); } export function EmperorHomeworldMovement({ game: g, act, busy, }: { game: GameView; act: (action: Action) => void; busy: boolean; }) { const [origin, setOrigin] = useState('homeworld:emperor'); const [normal, setNormal] = useState(0); const [elite, setElite] = useState(0); const option = g.homeworldMove; const homes = g.homeworlds?.worlds?.filter((w) => w.native === g.me); if (!option || !homes || homes.length !== 2) return null; const source = homes.find((w) => w.id === origin)!; const destination = homes.find((w) => w.id !== origin)!; const active = g.ornithopter?.active; let available = source.forces[g.me]; if (active?.mode === 'twoGroups' && active.completed === 1 && active.cohort) { try { const cohort = imperialFlightCohort(active.cohort, active.homeworldCohort === true, homes, g.me); available = { normal: Math.min(available.normal, (cohort.forces[origin] ?? 0) - (cohort.elites[origin] ?? 0)), elite: Math.min(available.elite, cohort.elites[origin] ?? 0) }; } catch { available = { normal: 0, elite: 0 }; } } const order: EmperorHomeworldMove = { origin, forces: { normal, elite } }; const move = emperorHomeworldMoveAction(g, order); const cardMoves = active ? [] : (['range3', 'twoGroups'] as const).map(mode => ({ mode, action: emperorHomeworldMoveAction(g, order, mode) })); const name = (id: string) => id === 'homeworld:emperor' ? 'Kaitain' : 'Salusa Secundus'; const disabled = busy || !!option.blocked || !!g.players.find(player => player.id === g.me)?.autopilot; const invalid = !Number.isSafeInteger(normal) || !Number.isSafeInteger(elite) || normal < 0 || elite < 0 || normal > available.normal || elite > available.elite || normal + elite === 0; return (
Move between Imperial Homeworlds

Move normal forces or Sardaukar between Kaitain and Salusa Secundus. Costs one movement and no spice. Moving first passes your unused shipment.

{g.ornithopter &&

Provisional Ornithopter composition: the native Kaitain–Salusa route and no-spice cost stay unchanged. Range three gives no extra World range or new route. Two groups must use different original typed counters; a World group may be paired with a ground group. Existing shipment history and completed movement stay recorded. {active ? 'The card is already on the table; continue its current flight.' : 'Play the held physical card with the first group.'}

} {active &&

Ornithopter: {active.completed} group moved · {active.remaining} remaining. Select only original unmoved normal forces or Sardaukar for the next group.

} {option.blocked && ( {option.blocked} )}

{option.remaining} movement{option.remaining === 1 ? '' : 's'}{' '} remaining.

{!active && g.ornithopter && cardMoves.map(({ mode, action }) => )}
); }