'use client'; import { useState } from 'react'; import type { Action, GameView } from '@/game/engine'; import { specialForceName } from '@/game/combat'; import { homeworldName, homeworldShipmentChoice, richeseHomeworldMarkerChoice, type HomeworldShipmentSources, } from '@/game/homeworld-shipment-options'; import { Button } from './ui/button'; import { Input } from './ui/input'; /** This private control is separate from physical-source shipment: the marker * costs one but does not withdraw a reserve counter until it is revealed. */ export function HomeworldNoFieldShipment({ game, act, busy }: { game: GameView; act: (action: Action) => void; busy: boolean; }) { const me = game.players.find(player => player.id === game.me); const info = game.richeseNoField; const [selectedDestination, setSelectedDestination] = useState(''); const [selectedToken, setSelectedToken] = useState(''); const [allyPayment, setAllyPayment] = useState(0); if (!me || me.faction !== 'richese' || info?.owner !== me.id || !info.private) return null; const targets = (game.homeworlds?.worlds ?? []).filter(world => world.native !== me.id && world.native !== me.ally); const destination = targets.find(world => world.id === selectedDestination) ?? targets[0]; const tokens = info.private.tokens.filter(token => token.id !== info.private!.lastShipped); const token = tokens.find(candidate => candidate.id === selectedToken) ?? tokens[0]; const quote = richeseHomeworldMarkerChoice(game, destination?.id ?? '', token?.id ?? '', allyPayment); return
Ship a concealed No-Field

Provisional Homeworld rule: ship one concealed marker from your supply to another non-allied Homeworld for one spice. Its value stays private and its physical forces remain in your reserves until the marker is revealed. This uses your normal shipment.

{me.ally && } One marker · one spice total · you pay {quote.ownPayment}, ally pays {quote.allyPayment}. {quote.blocked && ` ${quote.blocked}`}
; } export function HomeworldShipment({ game: g, act, busy, }: { game: GameView; act: (action: Action) => void; busy: boolean; }) { const worlds = g.homeworlds?.worlds ?? []; const me = g.players.find((player) => player.id === g.me)!; const available = worlds.filter((world) => { const force = world.forces[g.me]; return force && force.normal + force.elite > 0; }); const targets = worlds.filter( (world) => (world.native !== g.me || me.faction === 'guild') && !(g.guildTransportCanceled && me.faction === 'guild' && world.native === me.id) && world.native !== me.ally, ); const [origin, setOrigin] = useState(available[0]?.id ?? ''); const [destination, setDestination] = useState( targets.find((world) => world.id !== available[0]?.id)?.id ?? '', ); const [sources, setSources] = useState({}); const [allyPayment, setAllyPayment] = useState(0); const [nexusEvent, setNexusEvent] = useState(''); const useNexus = g.karamaShipping?.player !== g.me && !!g.nexusGuildSecretAlly && nexusEvent === g.nexusGuildSecretAlly.event; const option = g.homeworldShipment; if (!option) return null; const imperial = g.advanced && me.faction === 'emperor'; const selectedOrigins = available.filter((world) => origin === 'imperial-pair' ? world.native === me.id : world.id === origin, ); const selected = Object.fromEntries( selectedOrigins.flatMap((world) => { const choice = sources[world.id]; return choice && choice.normal + choice.elite !== 0 ? [[world.id, choice]] : []; }), ); const quote = homeworldShipmentChoice( g, destination, selected, allyPayment, useNexus ? nexusEvent : undefined, ); const special = specialForceName(me.faction); return (
Ship to another Homeworld

Use your shipment to send forces from one Homeworld to another. Native reserves and foreign garrisons have separate source pools.{' '} {me.faction === 'guild' ? g.guildTransportCanceled ? 'The native Guild return permission is canceled: Junction cannot be a destination, even with a purchased price.' : 'Guild may also return a foreign garrison to Junction.' : 'Your own Homeworld cannot be a destination.'}{' '} Allied Homeworlds cannot be destinations.

{useNexus ? 'Selected Guild Nexus price: one spice per two forces, rounded up.' : g.karamaShipping?.player === g.me ? 'Purchased Karama price: one spice per two forces, rounded up, paid entirely to the bank even if the native Guild rate is canceled.' : me.faction === 'guild' ? g.guildRateCanceled ? 'The native Guild rate is canceled: pay one spice per force.' : 'Guild pays one spice per two forces, rounded up.' : 'Pay one spice per force.'}{' '} This uses your shipment; your normal movement remains available.

{g.nexusGuildSecretAlly && (

Spend the Nexus for its independent price of one spice per two forces, rounded up; it does not stack with a purchased Karama price. Existing source and destination restrictions still apply.

{g.nexusGuildSecretAlly.blocked && (

{g.nexusGuildSecretAlly.blocked}

)}
)} {option.blocked && ( {option.blocked} )}
{selectedOrigins.map((world) => { const pool = world.forces[me.id]; const chosen = sources[world.id] ?? { normal: 0, elite: 0 }; return (
{homeworldName(g, world.id)} ·{' '} {world.native === me.id ? 'native reserves' : 'foreign garrison'}

Available: {pool.normal} normal {pool.elite > 0 ? ` + ${pool.elite} ${special}` : ''}.

{(['normal', 'elite'] as const) .filter((kind) => kind === 'normal' || pool.elite > 0) .map((kind) => ( ))}
); })} {me.ally && ( )}

Your available spice: {me.spice ?? 0}. Allied pledge:{' '} {g.aid.available}.

{quote.amount} forces · {quote.cost} spice total {quote.amount > 0 ? ` · you pay ${quote.ownPayment}, ally pays ${quote.allyPayment}` : ''} .{quote.blocked && ` ${quote.blocked}`}
); }