Implement mixed Planetologist advisor gather continuation

This commit is contained in:
Quintine committed 2026-10-18 00:35:36 +11:00
1 parent bc37cd38fe
commit c265f0638e
16 files changed
+512 -171

No files matched your search

+28 -9
View File
@@ -97,7 +97,7 @@ import { NexusHarkonnenBetrayal } from './nexus-harkonnen-betrayal';
import { OrnithopterMovement } from './ornithopter-movement';
import { DiscoveryOrnithopterMovement } from './discovery-flight-movement';
import { PlanetologistMovement } from './planetologist-movement';
import { selectedOriginElites } from '@/game/planetologist-movement';
import { selectedOriginElites, advisorFlipMovementBlocked } from '@/game/planetologist-movement';
import { SandmasterMovement } from './sandmaster-movement';
import { SandmasterWormChoice } from './sandmaster-worm';
import { sandmasterWormCollection } from '@/game/sandmaster-worm';
@@ -796,6 +796,7 @@ export function GameTable({
sector,
...(includesNoField ? {} : { amount }),
};
const advisorFlipRouteBlocked = ordinaryMovementAction.fighters === true && advisorFlipMovementBlocked(g, me);
const transportAction: Action = {
type: 'guildShip',
...(useGuildSecret ? {nexus: guildSecretEvent} : {}),
@@ -2583,6 +2584,14 @@ export function GameTable({
unchanged.
</p>
)}
{g.response.kind === 'advisorFlip' && g.response.advisorResume === 'planetologistGather' && (
<p className="notice">
Provisional mixed Planetologist gather: allow one common fighter
arrival before either group moves, or cancel the native advisor
flip. Cancellation keeps both groups and the movement allowance
unchanged, with flipping blocked for that movement opportunity.
</p>
)}
{g.response.kind === 'nexusGuildCunning' && <p className="notice">
Allow one second Guild shipment at its normal price, or cancel this extra opportunity.
The completed first shipment and movement stay unchanged; the Nexus is already spent.
@@ -4792,6 +4801,12 @@ export function GameTable({
territory
</label>
)}
{advisorFlipRouteBlocked && (
<p className="notice" id="movement-advisor-flip-unavailable">
Advisor flipping was canceled for this movement. Turn off
the fighter request or choose a group that is already fighting.
</p>
)}
{!g.ornithopter?.active && (
<>
{actionButton(
@@ -4802,18 +4817,21 @@ export function GameTable({
!source ||
canceledFremenRouteBlocked ||
ordinaryMovementRouteBlocked ||
advisorFlipRouteBlocked ||
selectedDestinationInStorm ||
(me.moved ?? 0) >= (me.movesAllowed ?? 1) ||
(includesNoField
? !validMarkerGroup
: (me.forces[source] ?? 0) === 0),
selectedDestinationInStorm
? 'movement-storm-unavailable'
: ordinaryMovementRouteBlocked
? 'movement-route-unavailable'
: canceledFremenRouteBlocked
? 'movement-range-unavailable'
: undefined,
advisorFlipRouteBlocked
? 'movement-advisor-flip-unavailable'
: selectedDestinationInStorm
? 'movement-storm-unavailable'
: ordinaryMovementRouteBlocked
? 'movement-route-unavailable'
: canceledFremenRouteBlocked
? 'movement-range-unavailable'
: undefined,
)}
{canceledFremenRouteBlocked &&
!selectedDestinationInStorm && (
@@ -4879,6 +4897,7 @@ export function GameTable({
move={
!source ||
!movementAvailable ||
advisorFlipRouteBlocked ||
(me.moved ?? 0) >= (me.movesAllowed ?? 1) ||
(includesNoField
? !validMarkerGroup
@@ -4901,7 +4920,7 @@ export function GameTable({
act={act}
busy={busy}
move={
!source || !movementAvailable ||
!source || !movementAvailable || advisorFlipRouteBlocked ||
(includesNoField ? !validMarkerGroup : (me.forces[source] ?? 0) === 0)
? null
: {
+12 -16
View File
@@ -6,6 +6,7 @@ import {
planetologistLeader,
planetologistMovementModeSupported,
selectedOriginElites,
planetologistGatherAdvisorEntry,
type PlanetologistMoveMode,
} from '@/game/planetologist-movement';
import { botGroundMoveAllowed } from '@/game/bot-mobility';
@@ -15,8 +16,6 @@ import {
splitLocation,
territory,
} from '@/game/board';
import { isAdvisor } from '@/game/advisors';
import { presenceAt } from '@/game/force-presence';
import { Button } from './ui/button';
import { Input } from './ui/input';
@@ -76,20 +75,8 @@ export function planetologistMoveDraft(
? 'Select forces from one territory.'
: 'Select forces from exactly two different territories.',
};
if (
game.advanced &&
me.faction === 'beneGesserit' &&
new Set([...origins].map((origin) => isAdvisor(me, origin))).size > 1 &&
!presenceAt(me, destination) &&
game.players.some(
(player) => player.id !== me.id && presenceAt(player, destination),
)
)
return {
action: null,
blocked:
'Gathering advisors and fighters into an enemy-only territory is still being implemented.',
};
const entry = planetologistGatherAdvisorEntry(game, me, [...origins], destination);
if (entry.blocked) return { action: null, blocked: entry.blocked };
const target = location(destination, sector);
if (
selected.some(
@@ -109,6 +96,7 @@ export function planetologistMoveDraft(
action: {
type: 'move',
planetologist: mode,
...(entry.requiresFlip ? { fighters: true } : {}),
forces: Object.fromEntries(selected),
...(me.elites
? {
@@ -196,6 +184,14 @@ export function PlanetologistMovement({
Destination: {territory(destination).name}, sector {sector}. Select the
physical forces to move below.
</p>
{draft.action?.fighters === true && (
<p className="notice">
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.
</p>
)}
{me.faction === 'ixians' && (
<p className="fine">
Each origin uses its own selected cyborgs for movement range. Cyborgs