'use client'; import { useId, useState } from 'react'; import type { Action, GameView } from '@/game/engine'; import { planetologistLeader, type PlanetologistMoveMode, } from '@/game/planetologist-movement'; import { gameTerritories, splitLocation, territory, } from '@/game/board'; import { PlanetologistMovementAction, planetologistMoveDraft, planetologistMovementBlocked, planetologistNoFieldMarker, } from './planetologist-movement-action'; import { Input } from './ui/input'; export { planetologistMoveDraft } from './planetologist-movement-action'; type Counts = Record; type Props = { game: GameView; act: (action: Action) => void; busy: boolean; destination: string; sector: number; unavailableReason?: string | null; }; export function PlanetologistMovement(props: Props) { const marker = planetologistNoFieldMarker(props.game); const me = props.game.players.find(player => player.id === props.game.me); return ; } function PlanetologistMovementDraft({ game, act, busy, destination, sector, unavailableReason, }: Props) { const id = useId(); const [mode, setMode] = useState('range'); const [forces, setForces] = useState({}); const [elites, setElites] = useState({}); const [includeNoField, setIncludeNoField] = useState(false); const me = game.players.find((player) => player.id === game.me); const leader = me && planetologistLeader(game, me.id); if (!me || !leader) return null; const sources = Object.entries(me.forces).filter(([, count]) => count > 0); const marker = planetologistNoFieldMarker(game); const draft = planetologistMoveDraft( game, mode, destination, sector, forces, elites, includeNoField, ); const blocked = planetologistMovementBlocked(game, draft, unavailableReason); const leaderName = game.allLeaders.find((candidate) => candidate.id === leader)?.name ?? leader; return (

Planetologist movement

{leaderName} may add one territory to one movement, up to three, or gather forces from exactly two territories into the selected destination.

{game.advanced&&me.faction==='beneGesserit'&&mode==='gather'&&(

When joining your residents, each incoming origin that changes type has its own matching response. Both groups wait for every required allowance. Prevention leaves the movement unused and keeps both groups in place.

)}

Destination: {territory(destination).name}, sector {sector}. Select the physical forces to move below.

{marker && ( <>

Provisional range composition: the concealed marker moves as one force with Planetologist’s extra range, up to three territories. Move it alone or with physical forces from its own territory; its value stays concealed. No direct joint FAQ settles this combination.

{game.homeworldMobility?.noFieldMovementBlocked && (

{game.homeworldMobility.noFieldMovementBlocked}

)} )} {draft.action?.fighters === true && (

Provisional mixed-gather policy: both groups arrive as fighters. The advisor flip is declared before either group moves. If canceled, both groups stay in place and no movement is spent; flipping remains blocked for that movement opportunity.

)} {me.faction === 'ixians' && (

Each origin uses its own selected cyborgs for movement range. Cyborgs from one territory cannot carry suboids from the other territory farther.

)} {sources.length ? ( sources.map(([from, owned]) => { const source = splitLocation(from); const sourceName = gameTerritories(game).find( (candidate) => candidate.id === source.territory, )?.name ?? source.territory; const selected = forces[from] ?? 0; const ownedElite = me.elites?.forces[from] ?? 0; const minimumElite = Math.max(0, selected - (owned - ownedElite)); return (
{me.elites && ( )}
); }) ) : (

No physical board forces are available to move.

)} {blocked && ( {blocked} )}

Concealed No-Fields cannot join gather, either Ornithopter card movement or Discovery flight. Normal movement and arrival rules still apply.

); }