Files
siege-protocol/js/renderer-inventory.js
T
44r0n7 626879ed0c Add freshness bar, enhance overlays and renderers
- Add enemy freshness tracking (novelty bonus for repeated deploys)
- Add freshness bar to sidepanel enemy cards with penalty indicator
- Major overhaul of renderer-overlays.js (790+ lines for UI polish)
- Enhanced combat log, shop overlays, and inventory UI
- Improved weapon/upgrade display with partial ownership colors
- Added element icons and weakness/resistance indicators to cards
- Enhanced radial menu and tooltip system
- Add "stale/%" penalty text when freshness depleted
- Update play link to ffazeshift.net in index.html
2026-06-17 11:58:17 -04:00

256 lines
9.0 KiB
JavaScript

// ═══ 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();
}