window.GDD = window.GDD || {}; const { useState, useEffect, useCallback, useMemo } = React; function FittingDemo() { const [ship, setShip] = useState(null); const [ships, setShips] = useState([]); const [availableModules, setAvailableModules] = useState([]); const [fittedModules, setFittedModules] = useState({ high: [], med: [], low: [] }); const [selectedModule, setSelectedModule] = useState(null); const [filterSlot, setFilterSlot] = useState('all'); const [notifications, setNotifications] = useState([]); useEffect(() => { window.GDD.api.getPlayerShips().then(s => { setShips(s); if (s.length > 0) setShip(s[0]); }); window.GDD.api.getAvailableModules().then(m => setAvailableModules(m)); }, []); useEffect(() => { if (!ship) return; window.GDD.api.getShipFittings(ship.id).then(fitted => { const slots = { high: [], med: [], low: [] }; fitted.forEach(m => { if (m.slot === 'high') slots.high.push(m); else if (m.slot === 'med') slots.med.push(m); else if (m.slot === 'low') slots.low.push(m); }); setFittedModules(slots); }); }, [ship]); const addNotif = useCallback((msg, color) => { const id = Date.now(); setNotifications(prev => [...prev, { id, msg, color }]); setTimeout(() => setNotifications(prev => prev.filter(n => n.id !== id)), 3000); }, []); const cpuUsage = useMemo(() => { let total = 0; Object.values(fittedModules).flat().forEach(m => total += m.cpu); return total; }, [fittedModules]); const gridUsage = useMemo(() => { let total = 0; Object.values(fittedModules).flat().forEach(m => total += m.power); return total; }, [fittedModules]); const cpuMax = ship ? ship.cpu : 0; const gridMax = ship ? ship.powerGrid : 0; const cpuOver = cpuUsage > cpuMax; const gridOver = gridUsage > gridMax; const handleFit = useCallback((mod) => { if (!ship) return; const slot = mod.slot; const maxSlots = slot === 'high' ? ship.highSlots : slot === 'med' ? ship.medSlots : ship.lowSlots; const currentCount = fittedModules[slot].length; if (currentCount >= maxSlots) { addNotif(`No empty ${slot} slots available.`, 'var(--red)'); return; } const newCpu = cpuUsage + mod.cpu; const newGrid = gridUsage + mod.power; if (newCpu > cpuMax) { addNotif(`CPU exceeded: ${newCpu}/${cpuMax}. Remove a module first.`, 'var(--red)'); return; } if (newGrid > gridMax) { addNotif(`Power Grid exceeded: ${newGrid}/${gridMax}. Remove a module first.`, 'var(--red)'); return; } setFittedModules(prev => ({ ...prev, [slot]: [...prev[slot], { ...mod, uid: Date.now() + Math.random() }], })); addNotif(`${mod.name} fitted to ${slot} slot.`, 'var(--green)'); }, [ship, fittedModules, cpuUsage, gridUsage, cpuMax, gridMax, addNotif]); const handleUnfit = useCallback((slot, index) => { const mod = fittedModules[slot][index]; setFittedModules(prev => ({ ...prev, [slot]: prev[slot].filter((_, i) => i !== index), })); addNotif(`${mod.name} removed from ${slot} slot.`, 'var(--muted)'); }, [fittedModules, addNotif]); const filteredModules = filterSlot === 'all' ? availableModules : availableModules.filter(m => m.slot === filterSlot); const slotConfig = [ { key: 'high', label: 'High Slots', color: 'var(--red)', icon: '◆', max: ship?.highSlots || 0 }, { key: 'med', label: 'Medium Slots', color: 'var(--cyan)', icon: '◇', max: ship?.medSlots || 0 }, { key: 'low', label: 'Low Slots', color: 'var(--green)', icon: '○', max: ship?.lowSlots || 0 }, ]; const moduleTypeIcon = (type) => { switch(type) { case 'weapon': return '⊕'; case 'shield': return '◎'; case 'mining': return '⛏'; case 'propulsion': return '»'; case 'ewar': return '◎'; case 'armor': return '◼'; case 'damage_mod': return '↯'; case 'cargo': return '□'; default: return '•'; } }; if (!ship) return
Loading ship data...
Drag modules into slot bays. CPU and Power Grid are hard limits — overfitting is blocked. Select a ship and build your loadout.
{/* Notifications */}