// ═══ renderer-inventory.js ═══ // ============================================================ // RENDERER INVENTORY — bottom weapon drawer // ============================================================ const _PK_X = 16, _PK_Y = 642, _PK_W = 1298, _PK_H = 244; const _PK_PAD = 16; const _PK_TITLE_H = 24; const _PK_CLOSE_H = 30; const _PK_BODY_Y = _PK_Y + _PK_PAD + _PK_TITLE_H; const _PK_BODY_H = _PK_H - _PK_PAD * 2 - _PK_TITLE_H - _PK_CLOSE_H; const _PK_DETAIL_W = 246; const _PK_GAP = 12; const _PK_LIST_X = _PK_X + _PK_PAD; const _PK_LIST_W = _PK_W - _PK_PAD * 2 - _PK_GAP - _PK_DETAIL_W; const _PK_DET_X = _PK_LIST_X + _PK_LIST_W + _PK_GAP; const _PK_CARD_W = 92, _PK_CARD_H = 70, _PK_CARD_GAP = 8; const _PK_SECT_H = 18, _PK_DROP_H = 32; const _PK_CPR = Math.floor((_PK_LIST_W + _PK_CARD_GAP) / (_PK_CARD_W + _PK_CARD_GAP)); let _pickerHoverWeapon = null; let _pickerHoverDef = null; function drawInventoryOverlay() { if (!document.body.classList.contains('inventory-open')) return; const W = canvas.width, H = canvas.height; const equipMode = _pickerSlot >= 0; ctx.fillStyle = 'rgba(0,0,0,0.42)'; ctx.fillRect(0, 0, W, H); ctx.fillStyle = '#060e16'; ctx.strokeStyle = '#1a3048'; ctx.lineWidth = 1; ctx.fillRect(_PK_X, _PK_Y, _PK_W, _PK_H); ctx.strokeRect(_PK_X, _PK_Y, _PK_W, _PK_H); ctx.save(); const titleSuffix = equipMode ? `- SLOT ${_pickerSlot + 1}` : ''; ctx.font = '11px Orbitron, "Share Tech Mono", monospace'; ctx.letterSpacing = '3px'; ctx.textAlign = 'left'; ctx.textBaseline = 'top'; ctx.fillStyle = '#00d4ff'; ctx.fillText('WEAPON INVENTORY ' + titleSuffix, _PK_LIST_X, _PK_Y + _PK_PAD); ctx.letterSpacing = '0px'; const invCount = (G.weaponInventory || []).length; ctx.font = '10px "Share Tech Mono", monospace'; ctx.fillStyle = '#3a6080'; ctx.fillText(invCount + ' in inventory', _PK_LIST_X + 270, _PK_Y + _PK_PAD + 1); ctx.strokeStyle = '#1a3048'; ctx.lineWidth = 1; ctx.beginPath(); ctx.moveTo(_PK_X, _PK_BODY_Y - 8); ctx.lineTo(_PK_X + _PK_W, _PK_BODY_Y - 8); ctx.stroke(); const CB_W = 110, CB_H = 26; const CB_X = _PK_X + _PK_W - _PK_PAD - CB_W; const CB_Y = _PK_Y + 7; const cbHov = isHovered(CB_X, CB_Y, CB_W, CB_H); ctx.fillStyle = cbHov ? '#1a0808' : 'transparent'; ctx.strokeStyle = cbHov ? '#ff4444' : '#3a6080'; ctx.lineWidth = 1; ctx.fillRect(CB_X, CB_Y, CB_W, CB_H); ctx.strokeRect(CB_X, CB_Y, CB_W, CB_H); ctx.font = '10px Orbitron, monospace'; ctx.letterSpacing = '2px'; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; ctx.fillStyle = cbHov ? '#ff4444' : '#3a6080'; ctx.fillText('CLOSE', CB_X + CB_W / 2, CB_Y + CB_H / 2); ctx.letterSpacing = '0px'; addHitRegion(CB_X, CB_Y, CB_W, CB_H, closeWeaponPicker); _bagDropZones.push({ x: _PK_X, y: _PK_Y, w: _PK_W, h: _PK_H }); let yOff = 0, col = 0; _pickerHoverWeapon = null; _pickerHoverDef = null; ctx.save(); ctx.beginPath(); ctx.rect(_PK_LIST_X, _PK_BODY_Y, _PK_LIST_W, _PK_BODY_H); ctx.clip(); function flushRow() { if (col > 0) { yOff += _PK_CARD_H + _PK_CARD_GAP; col = 0; } } function drawCard(weapon, def, opts) { const { isRemove, isEquipped, cantAfford, isBuy, location, action, source } = opts || {}; const cx = _PK_LIST_X + col * (_PK_CARD_W + _PK_CARD_GAP); const cy = _PK_BODY_Y + yOff - _pickerScrollY; col++; if (col >= _PK_CPR) { yOff += _PK_CARD_H + _PK_CARD_GAP; col = 0; } const visible = cy + _PK_CARD_H > _PK_BODY_Y && cy < _PK_BODY_Y + _PK_BODY_H; if (!visible) return; const hov = isHovered(cx, cy, _PK_CARD_W, _PK_CARD_H); if (hov && !cantAfford) { _pickerHoverWeapon = weapon; _pickerHoverDef = def; } const border = isRemove ? '#ff4444' : isEquipped ? '#00d4ff99' : (hov && !cantAfford) ? '#ffd700' : '#1a3048'; const bg = isRemove ? '#150808' : isEquipped ? '#07101a' : (hov && !cantAfford) ? '#0c1820' : '#080f18'; ctx.save(); if (cantAfford) ctx.globalAlpha = 0.4; else if (isEquipped) ctx.globalAlpha = 0.72; ctx.fillStyle = bg; ctx.strokeStyle = border; ctx.lineWidth = 1; ctx.fillRect(cx, cy, _PK_CARD_W, _PK_CARD_H); ctx.strokeRect(cx, cy, _PK_CARD_W, _PK_CARD_H); ctx.font = '22px monospace'; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; ctx.fillStyle = isRemove ? '#ff4444' : '#ffffff'; ctx.fillText(def?.icon || (isRemove ? 'X' : '?'), cx + _PK_CARD_W / 2, cy + 21); ctx.save(); ctx.beginPath(); ctx.rect(cx + 4, cy + 36, _PK_CARD_W - 8, 16); ctx.clip(); ctx.font = '9px Orbitron, "Share Tech Mono", monospace'; ctx.letterSpacing = '1px'; ctx.textBaseline = 'middle'; ctx.fillStyle = isRemove ? '#ff4444' : '#b8d8e8'; ctx.fillText(isRemove ? 'REMOVE' : (def?.name || ''), cx + _PK_CARD_W / 2, cy + 44); ctx.restore(); if (isEquipped) { ctx.save(); ctx.fillStyle = 'rgba(0,212,255,0.18)'; ctx.fillRect(cx + 4, cy + 4, _PK_CARD_W - 8, 14); ctx.font = '8px Orbitron, monospace'; ctx.letterSpacing = '1px'; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; ctx.fillStyle = '#7ecfff'; ctx.fillText('EQUIPPED', cx + _PK_CARD_W / 2, cy + 11); ctx.restore(); } const sub = isRemove ? 'free' : isBuy ? (cantAfford ? `Need ${def.cost - spendableCredits()}c` : `${def.cost}c`) : (location || ''); ctx.save(); ctx.beginPath(); ctx.rect(cx + 2, cy + 54, _PK_CARD_W - 4, 14); ctx.clip(); ctx.font = '9px "Share Tech Mono", monospace'; ctx.letterSpacing = '0px'; ctx.textBaseline = 'middle'; ctx.fillStyle = isBuy ? '#ffd700' : '#3a6080'; ctx.fillText(sub, cx + _PK_CARD_W / 2, cy + 61); ctx.restore(); ctx.restore(); if (!cantAfford && action) addHitRegion(cx, cy, _PK_CARD_W, _PK_CARD_H, action); if (!cantAfford && weapon && !isRemove && !isBuy) _dragRegions.push({ x: cx, y: cy, w: _PK_CARD_W, h: _PK_CARD_H, weapon, source: source || null }); } for (let i = 0; i < G.tower.weaponSlots; i++) { const w = G.weapons[i]; if (!w) continue; const ii = i; drawCard(w, getWeaponDef(w), { location: `SOCKET ${i + 1}`, isEquipped: true, source: { type: 'slot', slotIndex: i }, action: (equipMode && i !== _pickerSlot) ? () => { equipWeaponInstanceToSlot(_pickerSlot, G.weapons[ii].instanceId); } : null }); } for (const w of (G.weaponInventory || [])) { const wRef = w; drawCard(w, getWeaponDef(w), { location: 'INVENTORY', source: { type: 'bag' }, action: equipMode ? () => { equipWeaponInstanceToSlot(_pickerSlot, wRef.instanceId); } : null }); } flushRow(); if (equipMode) { for (const def of WEAPON_DEFS) { const ownedCount = countOwnedWeaponType(def.id); if (ownedCount >= MAX_WEAPONS_PER_TYPE) continue; const cantAfford = spendableCredits() < def.cost; const dRef = def; drawCard(null, def, { location: 'BUY', isBuy: true, cantAfford, ownedCount, action: cantAfford ? null : () => { buyAndEquipWeapon(_pickerSlot, dRef.id); } }); } flushRow(); } _pickerScrollMax = Math.max(0, yOff - _PK_BODY_H + 20); ctx.restore(); const DX = _PK_DET_X, DY = _PK_BODY_Y, DH = _PK_BODY_H; ctx.fillStyle = '#050a10'; ctx.strokeStyle = '#122030'; ctx.lineWidth = 1; ctx.fillRect(DX, DY, _PK_DETAIL_W, DH); ctx.strokeRect(DX, DY, _PK_DETAIL_W, DH); if (_pickerHoverWeapon || _pickerHoverDef) { const hw = _pickerHoverWeapon, hd = _pickerHoverDef; let dy = DY + 10; ctx.font = '12px Orbitron, "Share Tech Mono", monospace'; ctx.letterSpacing = '2px'; ctx.textAlign = 'left'; ctx.textBaseline = 'top'; ctx.fillStyle = '#00d4ff'; ctx.fillText((hd?.icon || '') + ' ' + (hd?.name || ''), DX + 10, dy); ctx.letterSpacing = '0px'; dy += 28; ctx.font = '10px "Share Tech Mono", monospace'; const elLabel = hw ? weaponElementLabel(hw) : ''; if (elLabel) { ctx.fillStyle = '#3a6080'; ctx.textAlign = 'left'; ctx.fillText('ELEMENTS', DX + 10, dy); ctx.fillStyle = '#b8d8e8'; ctx.textAlign = 'right'; ctx.fillText(elLabel, DX + _PK_DETAIL_W - 10, dy); ctx.textAlign = 'left'; dy += 18; } for (const [k, v] of (hw ? weaponStatRows(hw) : [])) { if (dy > DY + DH - 18) break; ctx.strokeStyle = '#102030'; ctx.lineWidth = 1; ctx.beginPath(); ctx.moveTo(DX + 8, dy + 16); ctx.lineTo(DX + _PK_DETAIL_W - 8, dy + 16); ctx.stroke(); ctx.fillStyle = '#3a6080'; ctx.textAlign = 'left'; ctx.fillText(k, DX + 10, dy); ctx.fillStyle = '#b8d8e8'; ctx.textAlign = 'right'; ctx.fillText(String(v), DX + _PK_DETAIL_W - 10, dy); dy += 18; } } else { ctx.font = '11px Orbitron, monospace'; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; ctx.fillStyle = '#1a3048'; ctx.fillText('Drag weapons into sockets', DX + _PK_DETAIL_W / 2, DY + DH / 2 - 10); ctx.fillText('or back into the bag', DX + _PK_DETAIL_W / 2, DY + DH / 2 + 10); } ctx.restore(); }