Connect optional Smuggler reserve shipment and saved interception
Prototype one free accompanying force into an empty territory with explicit opt-out, shared human/AI pricing, physical elite custody and Guild recipient accounting. Bind pending quotes to their unique saved Guild decision; preserve legacy unstamped full-price declarations. Smuggler battle collection and combined modules remain unfinished and gated. Repair a randomly dealt-out Karama fixture by relocating its actual physical card without changing inventory. Validation: 70 focused tests; typecheck, lint, 4477 offline tests, production build and 43 HTTP tests passed. Independent rules/persistence review completed, including interruption and cancellation. Browser checked the price toggle, three arrivals for two spice, and refresh with unchanged private cards. Two genuine four-profile Basic/Advanced samples completed without rejected candidates but did not use Smuggler. Preserved all 427 opening rooms and original seat/recovery rows (461 rooms after QA/HTTP). SQLite integrity and foreign keys passed. Server remained healthy after the reported power outage; no restart or reset. Verified source tree: bf6e02bf6873ca6d6b01c83481489f55b2c7db0184a7056364343440dae88803
This commit is contained in:
1 parent
06f46c6f79
commit
9381ef140d
18 files changed
+1015
-25
No files matched your search
@@ -124,6 +124,8 @@ import {
|
||||
import { LeaderPortrait } from './leader-portrait';
|
||||
import { ShipmentQuote } from './shipment-quote';
|
||||
import { reserveShipmentCost } from '@/game/shipment-price';
|
||||
import { quoteSmugglerShipment } from '@/game/smuggler-shipment';
|
||||
import { SmugglerShipmentChoice } from './smuggler-shipment';
|
||||
import { nexusGuildSecretAllyAction, nexusGuildSecretAllyQuote } from '@/game/nexus-guild-secret-ally-options';
|
||||
import { nexusRicheseAction, nexusRicheseQuote } from '@/game/nexus-richese-options';
|
||||
import { guildTransportQuote } from '@/game/transport-quote';
|
||||
@@ -304,6 +306,7 @@ export function GameTable({
|
||||
const [amount, setAmount] = useState(1);
|
||||
const [richeseShipmentEvent, setRicheseShipmentEvent] = useState('');
|
||||
const useRicheseShipment = !!g.nexusRichese && richeseShipmentEvent === g.nexusRichese.event;
|
||||
const [useSmuggler, setUseSmuggler] = useState(true);
|
||||
const [bidDraft, setBidDraft] = useState({ auction: '', value: 1 });
|
||||
const auctionKey = `${g.code}/${g.turn}/${g.auction?.remaining ?? 0}`;
|
||||
const minimumBid = (g.auction?.bid ?? 0) + 1;
|
||||
@@ -507,10 +510,13 @@ export function GameTable({
|
||||
g.players.some((p) => p.faction === 'guild' && p.id === me.ally) ||
|
||||
g.karamaShipping?.player === me.id,
|
||||
};
|
||||
const smugglerOffer = validShipmentAmount && !useGuildSecret && !useRicheseShipment
|
||||
? quoteSmugglerShipment(g, me.id, selected, amount) : null;
|
||||
const smugglerShipment = useSmuggler ? smugglerOffer : null;
|
||||
const shipmentCost = validShipmentAmount
|
||||
? useGuildSecret ? (nexusGuildSecretAllyQuote(g, selected, amount)?.cost ?? null) : useRicheseShipment
|
||||
? (nexusRicheseQuote(g, selected, amount)?.cost ?? null)
|
||||
: reserveShipmentCost(shipmentRate, territory(selected).type, amount)
|
||||
: reserveShipmentCost(shipmentRate, territory(selected).type, amount - (smugglerShipment ? 1 : 0))
|
||||
: null;
|
||||
if (useGuildSecret && shipmentCost === null && !g.nexusGuildSecretAlly?.blocked) shipmentProblems.push('Choose a current Guild Secret Ally shipment.');
|
||||
if (useRicheseShipment && shipmentCost === null && !g.nexusRichese?.blocked)
|
||||
@@ -3808,7 +3814,7 @@ export function GameTable({
|
||||
/>
|
||||
<ShipmentQuote
|
||||
id="reserve-shipment-quote"
|
||||
physicalForces={useRicheseShipment ? amount : undefined}
|
||||
physicalForces={useRicheseShipment || smugglerShipment ? amount : undefined}
|
||||
quote={shipmentQuote}
|
||||
funding={{
|
||||
ownSpice: me.spice ?? 0,
|
||||
@@ -3816,6 +3822,7 @@ export function GameTable({
|
||||
}}
|
||||
unavailableReasons={shipmentProblems}
|
||||
/>
|
||||
<SmugglerShipmentChoice quote={smugglerOffer} use={useSmuggler} busy={busy || !!g.truthtrance} onChange={setUseSmuggler} />
|
||||
<Button
|
||||
className="game-action"
|
||||
aria-describedby="reserve-shipment-quote"
|
||||
@@ -3828,6 +3835,7 @@ export function GameTable({
|
||||
const shipment: Action = {
|
||||
type: 'ship', elite: eliteAmount, ...payment,
|
||||
territory: selected, sector, amount,
|
||||
...(smugglerOffer ? { smuggler: useSmuggler } : {}),
|
||||
...(homeworldSources ? { homeworldSources } : {}),
|
||||
};
|
||||
const action = useGuildSecret ? nexusGuildSecretAllyAction(g, guildSecretEvent, shipment) : useRicheseShipment
|
||||
|
||||
@@ -113,7 +113,7 @@ export function LeaderSkillsPanel({
|
||||
modifiers for Warmaster, Master of Assassins, Swordmaster of Ginaz,
|
||||
Killer Medic and Prana-Bindu Adept, plus Planetologist movement and
|
||||
Special-card battle use, Suk Graduate force rescue, Mentat and Bureaucrat
|
||||
battle scores, Sandmaster victory spice and Rihani inspections/exchanges. Other Leader Skill effects are
|
||||
battle scores, Sandmaster victory spice, Rihani inspections/exchanges and optional Smuggler reserve shipping. Other Leader Skill effects are
|
||||
unfinished.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
import type { SmugglerShipment } from '../game/smuggler-shipment';
|
||||
|
||||
export function SmugglerShipmentChoice({
|
||||
quote,
|
||||
use,
|
||||
busy,
|
||||
onChange,
|
||||
}: {
|
||||
quote: SmugglerShipment | null;
|
||||
use: boolean;
|
||||
busy: boolean;
|
||||
onChange: (use: boolean) => void;
|
||||
}) {
|
||||
if (!quote) return null;
|
||||
return (
|
||||
<section aria-label="Smuggler shipment choice" className="notice space-y-2">
|
||||
<label className="decision-checkbox">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={use}
|
||||
disabled={busy}
|
||||
onChange={(event) => onChange(event.target.checked)}
|
||||
/>
|
||||
Use Smuggler for one free accompanying force
|
||||
</label>
|
||||
<output className="fine block">
|
||||
All {quote.amount} selected forces leave reserves.
|
||||
{use
|
||||
? ` The price is for ${quote.amount - 1}; the free force is included in your selected total.`
|
||||
: ' The ordinary price applies to every selected force.'}{' '}
|
||||
The destination is currently empty.
|
||||
</output>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user