ui: real SKILL TREE tab (research) + SHIP SYSTEMS buttons gated by researched techs
This commit is contained in:
@@ -4,21 +4,24 @@ import { useState } from 'react'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Card, CardContent, CardFooter, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
import { BUILD_PART_COST, MAX_CAPACITY, MAX_LEVEL, expandCost, totalQty, upgradeCost } from '@/lib/game/ship-costs'
|
||||
import { BUILD_PART_COST, expandCost, totalQty, upgradeCost } from '@/lib/game/ship-costs'
|
||||
import type { ShipState, ShipSystemDto } from '@/lib/game/ship'
|
||||
import { buildGate, effectiveCapacityCap, effectiveLevelCap } from '@/lib/game/tech'
|
||||
|
||||
// SHIP SYSTEMS tab — live data. The parent (MissionControl) owns the ship state so the left
|
||||
// column mirrors actions immediately; this panel applies each action's response directly via
|
||||
// onChanged (no polling). Buttons are disabled when unaffordable or capped, using the same
|
||||
// cost constants the API enforces (lib/game/ship-costs.ts).
|
||||
// onChanged (no polling). Buttons are disabled when unaffordable, capped or gated by an
|
||||
// unresearched tech, using the same pure helpers the API enforces (lib/game/ship-costs.ts +
|
||||
// lib/game/tech.ts).
|
||||
|
||||
interface ShipSystemsPanelProps {
|
||||
ship: ShipState | null
|
||||
narrow?: boolean
|
||||
onChanged: (next: ShipState) => void
|
||||
researchedSlugs: Set<string> // slugs of researched techs — raises caps, lifts build gates
|
||||
}
|
||||
|
||||
export function ShipSystemsPanel({ ship, narrow, onChanged }: ShipSystemsPanelProps) {
|
||||
export function ShipSystemsPanel({ ship, narrow, onChanged, researchedSlugs }: ShipSystemsPanelProps) {
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
const [busy, setBusy] = useState(false) // one action at a time — turn-based game
|
||||
|
||||
@@ -57,7 +60,7 @@ export function ShipSystemsPanel({ ship, narrow, onChanged }: ShipSystemsPanelPr
|
||||
)}
|
||||
<div style={{ display: 'grid', gridTemplateColumns: narrow ? '1fr' : 'repeat(2, 1fr)', gap: '0.75rem' }}>
|
||||
{ship.systems.map((system) => (
|
||||
<SystemCard key={system.id} system={system} resources={resources} parts={parts} busy={busy} onAct={act} />
|
||||
<SystemCard key={system.id} system={system} resources={resources} parts={parts} busy={busy} researchedSlugs={researchedSlugs} onAct={act} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
@@ -69,17 +72,23 @@ function SystemCard({
|
||||
resources,
|
||||
parts,
|
||||
busy,
|
||||
researchedSlugs,
|
||||
onAct,
|
||||
}: {
|
||||
system: ShipSystemDto
|
||||
resources: number
|
||||
parts: number
|
||||
busy: boolean
|
||||
researchedSlugs: Set<string>
|
||||
onAct: (url: string, body: unknown) => void
|
||||
}) {
|
||||
const upgradeDisabled = busy || !system.built || system.level >= MAX_LEVEL || resources < upgradeCost(system.level)
|
||||
const expandDisabled = busy || !system.built || system.capacity >= MAX_CAPACITY || resources < expandCost(system.capacity)
|
||||
const buildDisabled = busy || system.built || parts < BUILD_PART_COST
|
||||
const maxLevel = effectiveLevelCap(system.type, researchedSlugs)
|
||||
const maxCapacity = effectiveCapacityCap(system.type, researchedSlugs)
|
||||
const gate = buildGate(system.type, researchedSlugs) // name of the required tech, or null when ungated/researched
|
||||
|
||||
const upgradeDisabled = busy || !system.built || system.level >= maxLevel || resources < upgradeCost(system.level)
|
||||
const expandDisabled = busy || !system.built || system.capacity >= maxCapacity || resources < expandCost(system.capacity)
|
||||
const buildDisabled = busy || system.built || gate !== null || parts < BUILD_PART_COST
|
||||
|
||||
return (
|
||||
<Card>
|
||||
@@ -100,6 +109,9 @@ function SystemCard({
|
||||
? `UPGRADE ${upgradeCost(system.level)}× RES · EXPAND ${expandCost(system.capacity)}× RES`
|
||||
: `BUILD ${BUILD_PART_COST}× PARTS`}
|
||||
</p>
|
||||
{!system.built && gate !== null && (
|
||||
<p style={{ fontSize: '0.62rem', color: 'var(--color-red)', letterSpacing: '0.08em' }}>REQUIRES // {gate}</p>
|
||||
)}
|
||||
</CardContent>
|
||||
<CardFooter className="gap-2">
|
||||
{system.built ? (
|
||||
|
||||
Reference in New Issue
Block a user