diff --git a/scripts/hv-unified.user.js b/scripts/hv-unified.user.js index ac32581..366cec5 100644 --- a/scripts/hv-unified.user.js +++ b/scripts/hv-unified.user.js @@ -2608,207 +2608,193 @@ function enhanceAbilities() { // ═══════════════════════════════════════════════════════════════════════ // ARMORY — smart equipment inventory management // ═══════════════════════════════════════════════════════════════════════ +// +// The Armory (ss=am) has native tabs: Organize, Modify, Repair, Soulbind, +// Purchase, Sell, Salvage. Each tab has its own equipment list with +// checkboxes (). The page has native submit buttons +// for each action (Sell Equipment, Salvage Equipment, etc.). +// +// Our enhancements: +// - Add bulk-select buttons above the equipment list +// - Auto-check items based on quality thresholds +// - Show quick summary of what's worth keeping vs discarding +// ─────────────────────────────────────────────────────────────────────── -// Analyse equipment quality and assign a grade function gradeEquipment(name) { const q = KB.qualities.find(q => name.includes(q)); if (!q) return { action: 'keep', label: '❓', color: '#888', reason: 'Unknown' }; const qi = KB.qualities.indexOf(q); - // Magnificent+ (index >= 5): always keep if (qi >= 5) return { action: 'keep', label: '✅ Keep', color: '#0f0', reason: `${q}+` }; - // Superior (index 3): borderline — keep if it's a weapon/armor we need - if (qi === 3) return { action: 'keep', label: '⬆ Keep', color: '#fdcb00', reason: 'Superior' }; - // Average/Fair (1-2): salvage for materials + if (qi >= 3) return { action: 'keep', label: '⬆ Keep', color: '#fdcb00', reason: q }; if (qi >= 1) return { action: 'salvage', label: '♻ Salvage', color: '#888', reason: q }; - // Crude (0): sell return { action: 'sell', label: '💰 Sell', color: '#f80', reason: 'Crude' }; } function enhanceArmory() { - const main = document.getElementById('mainpane'); - if (!main || document.getElementById('hv-armory-panel')) return; + const equipList = document.getElementById('equiplist'); + if (!equipList || document.getElementById('hv-armory-bar')) return; - // ── Parse equipment from the page ── + // ── Parse current equipment in the list ── const items = []; let currentCategory = ''; - $$('tr', main).forEach(row => { - // Category label rows (Weapon, Armor sections) + $$('tr', equipList).forEach(row => { if (row.className === 'eqtplabel') { currentCategory = (row.textContent || '').trim(); return; } - // Find the equip item link/ID - const links = row.querySelectorAll('a[href*="set_equip"]'); - if (links.length === 0) return; + const cb = row.querySelector('input[name="eqids[]"]'); + if (!cb) return; - // Item has an onmouseover for tooltip - const omo = row.getAttribute('onmouseover') || ''; - const idMatch = omo.match(/hover_equip\((\d+)\)/); - if (!idMatch) return; - - const id = idMatch[1]; + const id = cb.value; const label = row.querySelector('label'); const name = label ? (label.textContent || '').trim() : ''; - // Detect level — HV renders Lv with CSS font in a .btm1 inside the row - const levelDiv = row.querySelector('.btm1 label, .btm1'); - const levelText = levelDiv ? readCSSText(levelDiv) : ''; - const lvMatch = levelText.match(/lv\.?\s*(\d+)/i); - const itemLevel = lvMatch ? parseInt(lvMatch[1]) : 0; + // Status icons in the label text + const equipped = name.includes('🗡'); + const locked = name.includes('🔒'); + const pinned = name.includes('📌'); + const stored = name.includes('📦'); + const protected_ = name.includes('🛡'); - // Detect status indicators - const rowHtml = row.innerHTML || ''; - const isLocked = rowHtml.includes('lock.png') || rowHtml.includes('lck.png'); - const isEquipped = rowHtml.includes('eqp.png') || rowHtml.includes('Equipped'); - const isPinned = rowHtml.includes('pin.png'); - const isStored = rowHtml.includes('store.png'); + // Strip status icons for clean name + const cleanName = name.replace(/[🗡🔒📌📦🛡️]/g, '').trim(); - // Check for Sell/Salvage buttons in this row - const sellBtn = row.querySelector('input[value="Sell"], a[onclick*="sell_equip"]'); - const salvBtn = row.querySelector('input[value="Salvage"], a[onclick*="salvage_equip"]'); - - const quality = KB.qualities.find(q => name.includes(q)) || '?'; items.push({ - id, name, quality, slot: currentCategory, - level: itemLevel, - locked: isLocked, equipped: isEquipped, - pinned: isPinned, stored: isStored, - canSell: !!sellBtn, canSalvage: !!salvBtn, - row, + id, name: cleanName, slot: currentCategory, + equipped, locked, pinned, stored, protected: protected_, + checkbox: cb, row, + grade: gradeEquipment(cleanName), }); }); if (items.length === 0) return; - // ── Grade each item ── - const graded = items.map(item => ({ - ...item, - grade: gradeEquipment(item.name, item.slot), - })); + // ── Detect which armory tab we're on ── + let screen = 'organize'; + const url = window.location.href || ''; + const sm = url.match(/screen=(\w+)/); + if (sm) screen = sm[1]; - // ── Sorting: equipped/pinned first, then by quality descending, then by level ── - const qwords = KB.qualities; - graded.sort((a, b) => { - if (a.equipped && !b.equipped) return -1; - if (!a.equipped && b.equipped) return 1; - if (a.pinned && !b.pinned) return -1; - if (!a.pinned && b.pinned) return 1; - const qDiff = (qwords.indexOf(b.quality) - qwords.indexOf(a.quality)); - if (qDiff !== 0) return qDiff; - return b.level - a.level; - }); + // ── Counts ── + const sellCount = items.filter(i => i.grade.action === 'sell' && !i.equipped && !i.locked).length; + const salvCount = items.filter(i => i.grade.action === 'salvage' && !i.equipped && !i.locked).length; + const keepCount = items.filter(i => i.grade.action === 'keep').length; - // ── Summary counts ── - const keepCount = graded.filter(i => i.grade.action === 'keep').length; - const sellCount = graded.filter(i => i.grade.action === 'sell').length; - const salvCount = graded.filter(i => i.grade.action === 'salvage').length; - const equippedCount = graded.filter(i => i.equipped).length; - - // ── Build bulk action buttons ── - function bulkSell() { - const toSell = graded.filter(i => i.grade.action === 'sell' && i.canSell && !i.equipped && !i.locked); - if (toSell.length === 0) return; - toSell.forEach(item => { - const btn = item.row.querySelector('input[value="Sell"], a[onclick*="sell_equip"]'); - if (btn) btn.click(); - }); - } - - function bulkSalvage() { - const toSalv = graded.filter(i => i.grade.action === 'salvage' && i.canSalvage && !i.equipped && !i.locked); - if (toSalv.length === 0) return; - toSalv.forEach(item => { - const btn = item.row.querySelector('input[value="Salvage"], a[onclick*="salvage_equip"]'); - if (btn) btn.click(); - }); - } - - // ── Build the panel ── - const qColor = (q) => { const i = qwords.indexOf(q); return i >= 5 ? '#0f0' : i >= 3 ? '#fdcb00' : i >= 1 ? '#888' : '#f80'; }; - - let body = `
- - -
`; - - body += `
- ✅ Keep: ${keepCount} | ♻ Salvage: ${salvCount} | 💰 Sell: ${sellCount} | 🗡 Equipped: ${equippedCount} -
`; - - // ── Table ── - body += ''; - body += '' + - '' + - '' + - ''; - - let lastSlot = ''; - for (const item of graded) { - // Slot separator - if (item.slot && item.slot !== lastSlot) { - lastSlot = item.slot; - body += ``; - } - - const statusIcon = item.equipped ? '🗡' : item.locked ? '🔒' : item.stored ? '📦' : item.pinned ? '📌' : ''; - const nameShort = item.name.length > 28 ? item.name.slice(0, 27) + '…' : item.name; - body += ` - - - `; - } - body += '
QItemLvAction
${item.slot}
${item.quality[0]}${statusIcon} ${nameShort}${item.level || '?'}${item.grade.label}
'; - - body += '
' + - 'Status: 🗡Equipped 📌Pinned 🔒Locked 📦Stored
'; - - // ── Build panel container ── - const collapsed = localStorage[SP + 'armoryCollapsed'] === '1'; - const panel = document.createElement('div'); - panel.id = 'hv-armory-panel'; - panel.style.cssText = css({ - position: 'fixed', - top: '60px', - right: '4px', - zIndex: '9995', - background: '#111827', - color: '#d1d5db', - padding: '0', - borderRadius: '8px', + // ── Build the toolbar ── + const toolbar = document.createElement('div'); + toolbar.id = 'hv-armory-bar'; + toolbar.style.cssText = css({ + display: 'flex', + gap: '4px', + flexWrap: 'wrap', + padding: '4px 6px', + marginBottom: '2px', + background: '#1a1a2e', + borderRadius: '4px', fontSize: '10px', fontFamily: 'monospace', - maxWidth: '350px', - maxHeight: '80vh', - overflowY: 'auto', - boxShadow: '0 0 15px rgba(0,0,0,0.6)', - border: '1px solid #374151', + alignItems: 'center', }); - panel.innerHTML = `
- 🛡️ Armory (${items.length}) - ${collapsed ? '▶' : '▼'} -
-
${body}
`; + // Summary + const summary = document.createElement('span'); + summary.style.cssText = 'color:#888;margin-right:6px'; + summary.textContent = `✅${keepCount} ♻${salvCount} 💰${sellCount}`; + toolbar.appendChild(summary); - panel.querySelector('#hv-armory-header').onclick = () => { - const b = document.getElementById('hv-armory-body'); - const t = document.getElementById('hv-armory-toggle'); - const h = b.style.display === 'none'; - b.style.display = h ? 'block' : 'none'; - t.textContent = h ? '▼' : '▶'; - localStorage[SP + 'armoryCollapsed'] = h ? '0' : '1'; - }; + // ── Smart auto-select: check boxes based on grade ── + function autoSelect(action) { + items.forEach(item => { + if (item.grade.action !== action) return; + if (item.equipped || item.locked) return; + item.checkbox.checked = true; + }); + // Trigger the game's update function if available + if (typeof update_selected_count === 'function') update_selected_count(); + } - document.body.appendChild(panel); + // ── Select by quality ── + function selectByQuality(below) { + const idx = KB.qualities.indexOf(below); + items.forEach(item => { + const qi = KB.qualities.indexOf(KB.qualities.find(q => item.name.includes(q))); + if (qi < 0) return; + if (qi <= idx && !item.equipped && !item.locked) { + item.checkbox.checked = true; + } + }); + if (typeof update_selected_count === 'function') update_selected_count(); + } - // ── Wire bulk buttons ── - const sellBulk = document.getElementById('hv-armory-bulk-sell'); - const salvBulk = document.getElementById('hv-armory-bulk-salvage'); + function clearAll() { + items.forEach(item => item.checkbox.checked = false); + if (typeof update_selected_count === 'function') update_selected_count(); + } - if (sellBulk) sellBulk.onclick = bulkSell; - if (salvBulk) salvBulk.onclick = bulkSalvage; + // Buttons — only show relevant ones for current screen + if (screen === 'sell') { + const b = document.createElement('input'); + b.type = 'button'; + b.value = `💰 Select Crude (${sellCount})`; + b.style.cssText = 'padding:2px 6px;cursor:pointer;background:#d50c2d;color:#fff;border:none;border-radius:3px;font-size:10px'; + b.onclick = () => autoSelect('sell'); + toolbar.appendChild(b); + + const b2 = document.createElement('input'); + b2.type = 'button'; + b2.value = '💰 Select ≤Fair'; + b2.style.cssText = 'padding:2px 6px;cursor:pointer;background:#a00;color:#fff;border:none;border-radius:3px;font-size:10px'; + b2.onclick = () => selectByQuality('Fair'); + toolbar.appendChild(b2); + } + + if (screen === 'salvage') { + const b = document.createElement('input'); + b.type = 'button'; + b.value = `♻ Select Salvage (${salvCount})`; + b.style.cssText = 'padding:2px 6px;cursor:pointer;background:#555;color:#fff;border:none;border-radius:3px;font-size:10px'; + b.onclick = () => autoSelect('salvage'); + toolbar.appendChild(b); + + const b2 = document.createElement('input'); + b2.type = 'button'; + b2.value = '♻ Select ≤Average'; + b2.style.cssText = 'padding:2px 6px;cursor:pointer;background:#444;color:#fff;border:none;border-radius:3px;font-size:10px'; + b2.onclick = () => selectByQuality('Average'); + toolbar.appendChild(b2); + + // "Sell Salvaged Equipment" toggle is already native + } + + if (screen === 'organize') { + const b = document.createElement('input'); + b.type = 'button'; + b.value = '📌 Pin Equipped'; + b.style.cssText = 'padding:2px 6px;cursor:pointer;background:#2a5a2a;color:#fff;border:none;border-radius:3px;font-size:10px'; + b.onclick = () => { + // Select all equipped items so they can be pinned + items.forEach(item => { if (item.equipped) item.checkbox.checked = true; }); + if (typeof update_selected_count === 'function') update_selected_count(); + }; + toolbar.appendChild(b); + } + + // Clear button always available + const clear = document.createElement('input'); + clear.type = 'button'; + clear.value = '✗ Clear'; + clear.style.cssText = 'padding:2px 6px;cursor:pointer;background:#555;color:#fff;border:none;border-radius:3px;font-size:10px'; + clear.onclick = clearAll; + toolbar.appendChild(clear); + + // ── Insert into page ── + const eqSelect = document.getElementById('equipselect_outer') || + document.querySelector('#equipselect_left, #armory_right > div'); + if (equipList && equipList.parentNode) { + equipList.parentNode.insertBefore(toolbar, equipList); + } } // ═══════════════════════════════════════════════════════════════════════ diff --git a/scripts/latest.user.js b/scripts/latest.user.js index ac32581..366cec5 100644 --- a/scripts/latest.user.js +++ b/scripts/latest.user.js @@ -2608,207 +2608,193 @@ function enhanceAbilities() { // ═══════════════════════════════════════════════════════════════════════ // ARMORY — smart equipment inventory management // ═══════════════════════════════════════════════════════════════════════ +// +// The Armory (ss=am) has native tabs: Organize, Modify, Repair, Soulbind, +// Purchase, Sell, Salvage. Each tab has its own equipment list with +// checkboxes (). The page has native submit buttons +// for each action (Sell Equipment, Salvage Equipment, etc.). +// +// Our enhancements: +// - Add bulk-select buttons above the equipment list +// - Auto-check items based on quality thresholds +// - Show quick summary of what's worth keeping vs discarding +// ─────────────────────────────────────────────────────────────────────── -// Analyse equipment quality and assign a grade function gradeEquipment(name) { const q = KB.qualities.find(q => name.includes(q)); if (!q) return { action: 'keep', label: '❓', color: '#888', reason: 'Unknown' }; const qi = KB.qualities.indexOf(q); - // Magnificent+ (index >= 5): always keep if (qi >= 5) return { action: 'keep', label: '✅ Keep', color: '#0f0', reason: `${q}+` }; - // Superior (index 3): borderline — keep if it's a weapon/armor we need - if (qi === 3) return { action: 'keep', label: '⬆ Keep', color: '#fdcb00', reason: 'Superior' }; - // Average/Fair (1-2): salvage for materials + if (qi >= 3) return { action: 'keep', label: '⬆ Keep', color: '#fdcb00', reason: q }; if (qi >= 1) return { action: 'salvage', label: '♻ Salvage', color: '#888', reason: q }; - // Crude (0): sell return { action: 'sell', label: '💰 Sell', color: '#f80', reason: 'Crude' }; } function enhanceArmory() { - const main = document.getElementById('mainpane'); - if (!main || document.getElementById('hv-armory-panel')) return; + const equipList = document.getElementById('equiplist'); + if (!equipList || document.getElementById('hv-armory-bar')) return; - // ── Parse equipment from the page ── + // ── Parse current equipment in the list ── const items = []; let currentCategory = ''; - $$('tr', main).forEach(row => { - // Category label rows (Weapon, Armor sections) + $$('tr', equipList).forEach(row => { if (row.className === 'eqtplabel') { currentCategory = (row.textContent || '').trim(); return; } - // Find the equip item link/ID - const links = row.querySelectorAll('a[href*="set_equip"]'); - if (links.length === 0) return; + const cb = row.querySelector('input[name="eqids[]"]'); + if (!cb) return; - // Item has an onmouseover for tooltip - const omo = row.getAttribute('onmouseover') || ''; - const idMatch = omo.match(/hover_equip\((\d+)\)/); - if (!idMatch) return; - - const id = idMatch[1]; + const id = cb.value; const label = row.querySelector('label'); const name = label ? (label.textContent || '').trim() : ''; - // Detect level — HV renders Lv with CSS font in a .btm1 inside the row - const levelDiv = row.querySelector('.btm1 label, .btm1'); - const levelText = levelDiv ? readCSSText(levelDiv) : ''; - const lvMatch = levelText.match(/lv\.?\s*(\d+)/i); - const itemLevel = lvMatch ? parseInt(lvMatch[1]) : 0; + // Status icons in the label text + const equipped = name.includes('🗡'); + const locked = name.includes('🔒'); + const pinned = name.includes('📌'); + const stored = name.includes('📦'); + const protected_ = name.includes('🛡'); - // Detect status indicators - const rowHtml = row.innerHTML || ''; - const isLocked = rowHtml.includes('lock.png') || rowHtml.includes('lck.png'); - const isEquipped = rowHtml.includes('eqp.png') || rowHtml.includes('Equipped'); - const isPinned = rowHtml.includes('pin.png'); - const isStored = rowHtml.includes('store.png'); + // Strip status icons for clean name + const cleanName = name.replace(/[🗡🔒📌📦🛡️]/g, '').trim(); - // Check for Sell/Salvage buttons in this row - const sellBtn = row.querySelector('input[value="Sell"], a[onclick*="sell_equip"]'); - const salvBtn = row.querySelector('input[value="Salvage"], a[onclick*="salvage_equip"]'); - - const quality = KB.qualities.find(q => name.includes(q)) || '?'; items.push({ - id, name, quality, slot: currentCategory, - level: itemLevel, - locked: isLocked, equipped: isEquipped, - pinned: isPinned, stored: isStored, - canSell: !!sellBtn, canSalvage: !!salvBtn, - row, + id, name: cleanName, slot: currentCategory, + equipped, locked, pinned, stored, protected: protected_, + checkbox: cb, row, + grade: gradeEquipment(cleanName), }); }); if (items.length === 0) return; - // ── Grade each item ── - const graded = items.map(item => ({ - ...item, - grade: gradeEquipment(item.name, item.slot), - })); + // ── Detect which armory tab we're on ── + let screen = 'organize'; + const url = window.location.href || ''; + const sm = url.match(/screen=(\w+)/); + if (sm) screen = sm[1]; - // ── Sorting: equipped/pinned first, then by quality descending, then by level ── - const qwords = KB.qualities; - graded.sort((a, b) => { - if (a.equipped && !b.equipped) return -1; - if (!a.equipped && b.equipped) return 1; - if (a.pinned && !b.pinned) return -1; - if (!a.pinned && b.pinned) return 1; - const qDiff = (qwords.indexOf(b.quality) - qwords.indexOf(a.quality)); - if (qDiff !== 0) return qDiff; - return b.level - a.level; - }); + // ── Counts ── + const sellCount = items.filter(i => i.grade.action === 'sell' && !i.equipped && !i.locked).length; + const salvCount = items.filter(i => i.grade.action === 'salvage' && !i.equipped && !i.locked).length; + const keepCount = items.filter(i => i.grade.action === 'keep').length; - // ── Summary counts ── - const keepCount = graded.filter(i => i.grade.action === 'keep').length; - const sellCount = graded.filter(i => i.grade.action === 'sell').length; - const salvCount = graded.filter(i => i.grade.action === 'salvage').length; - const equippedCount = graded.filter(i => i.equipped).length; - - // ── Build bulk action buttons ── - function bulkSell() { - const toSell = graded.filter(i => i.grade.action === 'sell' && i.canSell && !i.equipped && !i.locked); - if (toSell.length === 0) return; - toSell.forEach(item => { - const btn = item.row.querySelector('input[value="Sell"], a[onclick*="sell_equip"]'); - if (btn) btn.click(); - }); - } - - function bulkSalvage() { - const toSalv = graded.filter(i => i.grade.action === 'salvage' && i.canSalvage && !i.equipped && !i.locked); - if (toSalv.length === 0) return; - toSalv.forEach(item => { - const btn = item.row.querySelector('input[value="Salvage"], a[onclick*="salvage_equip"]'); - if (btn) btn.click(); - }); - } - - // ── Build the panel ── - const qColor = (q) => { const i = qwords.indexOf(q); return i >= 5 ? '#0f0' : i >= 3 ? '#fdcb00' : i >= 1 ? '#888' : '#f80'; }; - - let body = `
- - -
`; - - body += `
- ✅ Keep: ${keepCount} | ♻ Salvage: ${salvCount} | 💰 Sell: ${sellCount} | 🗡 Equipped: ${equippedCount} -
`; - - // ── Table ── - body += ''; - body += '' + - '' + - '' + - ''; - - let lastSlot = ''; - for (const item of graded) { - // Slot separator - if (item.slot && item.slot !== lastSlot) { - lastSlot = item.slot; - body += ``; - } - - const statusIcon = item.equipped ? '🗡' : item.locked ? '🔒' : item.stored ? '📦' : item.pinned ? '📌' : ''; - const nameShort = item.name.length > 28 ? item.name.slice(0, 27) + '…' : item.name; - body += ` - - - `; - } - body += '
QItemLvAction
${item.slot}
${item.quality[0]}${statusIcon} ${nameShort}${item.level || '?'}${item.grade.label}
'; - - body += '
' + - 'Status: 🗡Equipped 📌Pinned 🔒Locked 📦Stored
'; - - // ── Build panel container ── - const collapsed = localStorage[SP + 'armoryCollapsed'] === '1'; - const panel = document.createElement('div'); - panel.id = 'hv-armory-panel'; - panel.style.cssText = css({ - position: 'fixed', - top: '60px', - right: '4px', - zIndex: '9995', - background: '#111827', - color: '#d1d5db', - padding: '0', - borderRadius: '8px', + // ── Build the toolbar ── + const toolbar = document.createElement('div'); + toolbar.id = 'hv-armory-bar'; + toolbar.style.cssText = css({ + display: 'flex', + gap: '4px', + flexWrap: 'wrap', + padding: '4px 6px', + marginBottom: '2px', + background: '#1a1a2e', + borderRadius: '4px', fontSize: '10px', fontFamily: 'monospace', - maxWidth: '350px', - maxHeight: '80vh', - overflowY: 'auto', - boxShadow: '0 0 15px rgba(0,0,0,0.6)', - border: '1px solid #374151', + alignItems: 'center', }); - panel.innerHTML = `
- 🛡️ Armory (${items.length}) - ${collapsed ? '▶' : '▼'} -
-
${body}
`; + // Summary + const summary = document.createElement('span'); + summary.style.cssText = 'color:#888;margin-right:6px'; + summary.textContent = `✅${keepCount} ♻${salvCount} 💰${sellCount}`; + toolbar.appendChild(summary); - panel.querySelector('#hv-armory-header').onclick = () => { - const b = document.getElementById('hv-armory-body'); - const t = document.getElementById('hv-armory-toggle'); - const h = b.style.display === 'none'; - b.style.display = h ? 'block' : 'none'; - t.textContent = h ? '▼' : '▶'; - localStorage[SP + 'armoryCollapsed'] = h ? '0' : '1'; - }; + // ── Smart auto-select: check boxes based on grade ── + function autoSelect(action) { + items.forEach(item => { + if (item.grade.action !== action) return; + if (item.equipped || item.locked) return; + item.checkbox.checked = true; + }); + // Trigger the game's update function if available + if (typeof update_selected_count === 'function') update_selected_count(); + } - document.body.appendChild(panel); + // ── Select by quality ── + function selectByQuality(below) { + const idx = KB.qualities.indexOf(below); + items.forEach(item => { + const qi = KB.qualities.indexOf(KB.qualities.find(q => item.name.includes(q))); + if (qi < 0) return; + if (qi <= idx && !item.equipped && !item.locked) { + item.checkbox.checked = true; + } + }); + if (typeof update_selected_count === 'function') update_selected_count(); + } - // ── Wire bulk buttons ── - const sellBulk = document.getElementById('hv-armory-bulk-sell'); - const salvBulk = document.getElementById('hv-armory-bulk-salvage'); + function clearAll() { + items.forEach(item => item.checkbox.checked = false); + if (typeof update_selected_count === 'function') update_selected_count(); + } - if (sellBulk) sellBulk.onclick = bulkSell; - if (salvBulk) salvBulk.onclick = bulkSalvage; + // Buttons — only show relevant ones for current screen + if (screen === 'sell') { + const b = document.createElement('input'); + b.type = 'button'; + b.value = `💰 Select Crude (${sellCount})`; + b.style.cssText = 'padding:2px 6px;cursor:pointer;background:#d50c2d;color:#fff;border:none;border-radius:3px;font-size:10px'; + b.onclick = () => autoSelect('sell'); + toolbar.appendChild(b); + + const b2 = document.createElement('input'); + b2.type = 'button'; + b2.value = '💰 Select ≤Fair'; + b2.style.cssText = 'padding:2px 6px;cursor:pointer;background:#a00;color:#fff;border:none;border-radius:3px;font-size:10px'; + b2.onclick = () => selectByQuality('Fair'); + toolbar.appendChild(b2); + } + + if (screen === 'salvage') { + const b = document.createElement('input'); + b.type = 'button'; + b.value = `♻ Select Salvage (${salvCount})`; + b.style.cssText = 'padding:2px 6px;cursor:pointer;background:#555;color:#fff;border:none;border-radius:3px;font-size:10px'; + b.onclick = () => autoSelect('salvage'); + toolbar.appendChild(b); + + const b2 = document.createElement('input'); + b2.type = 'button'; + b2.value = '♻ Select ≤Average'; + b2.style.cssText = 'padding:2px 6px;cursor:pointer;background:#444;color:#fff;border:none;border-radius:3px;font-size:10px'; + b2.onclick = () => selectByQuality('Average'); + toolbar.appendChild(b2); + + // "Sell Salvaged Equipment" toggle is already native + } + + if (screen === 'organize') { + const b = document.createElement('input'); + b.type = 'button'; + b.value = '📌 Pin Equipped'; + b.style.cssText = 'padding:2px 6px;cursor:pointer;background:#2a5a2a;color:#fff;border:none;border-radius:3px;font-size:10px'; + b.onclick = () => { + // Select all equipped items so they can be pinned + items.forEach(item => { if (item.equipped) item.checkbox.checked = true; }); + if (typeof update_selected_count === 'function') update_selected_count(); + }; + toolbar.appendChild(b); + } + + // Clear button always available + const clear = document.createElement('input'); + clear.type = 'button'; + clear.value = '✗ Clear'; + clear.style.cssText = 'padding:2px 6px;cursor:pointer;background:#555;color:#fff;border:none;border-radius:3px;font-size:10px'; + clear.onclick = clearAll; + toolbar.appendChild(clear); + + // ── Insert into page ── + const eqSelect = document.getElementById('equipselect_outer') || + document.querySelector('#equipselect_left, #armory_right > div'); + if (equipList && equipList.parentNode) { + equipList.parentNode.insertBefore(toolbar, equipList); + } } // ═══════════════════════════════════════════════════════════════════════ diff --git a/src/armory.js b/src/armory.js index a794e15..433619a 100644 --- a/src/armory.js +++ b/src/armory.js @@ -1,205 +1,212 @@ // ═══════════════════════════════════════════════════════════════════════ // ARMORY — smart equipment inventory management // ═══════════════════════════════════════════════════════════════════════ +// +// The Armory (ss=am) has native tabs: Organize, Modify, Repair, Soulbind, +// Purchase, Sell, Salvage. Each tab has its own equipment list with +// checkboxes (). The page has native submit buttons +// for each action (Sell Equipment, Salvage Equipment, etc.). +// +// Our enhancements: +// - Add bulk-select buttons above the equipment list +// - Auto-check items based on quality thresholds +// - Show quick summary of what's worth keeping vs discarding +// ─────────────────────────────────────────────────────────────────────── -// Analyse equipment quality and assign a grade function gradeEquipment(name) { const q = KB.qualities.find(q => name.includes(q)); if (!q) return { action: 'keep', label: '❓', color: '#888', reason: 'Unknown' }; const qi = KB.qualities.indexOf(q); - // Magnificent+ (index >= 5): always keep if (qi >= 5) return { action: 'keep', label: '✅ Keep', color: '#0f0', reason: `${q}+` }; - // Superior (index 3): borderline — keep if it's a weapon/armor we need - if (qi === 3) return { action: 'keep', label: '⬆ Keep', color: '#fdcb00', reason: 'Superior' }; - // Average/Fair (1-2): salvage for materials + if (qi >= 3) return { action: 'keep', label: '⬆ Keep', color: '#fdcb00', reason: q }; if (qi >= 1) return { action: 'salvage', label: '♻ Salvage', color: '#888', reason: q }; - // Crude (0): sell return { action: 'sell', label: '💰 Sell', color: '#f80', reason: 'Crude' }; } function enhanceArmory() { - const main = document.getElementById('mainpane'); - if (!main || document.getElementById('hv-armory-panel')) return; + const equipList = document.getElementById('equiplist'); + if (!equipList || document.getElementById('hv-armory-bar')) return; - // ── Parse equipment from the page ── + // ── Parse current equipment in the list ── const items = []; let currentCategory = ''; - $$('tr', main).forEach(row => { - // Category label rows (Weapon, Armor sections) + $$('tr', equipList).forEach(row => { if (row.className === 'eqtplabel') { currentCategory = (row.textContent || '').trim(); return; } - // Find the equip item link/ID - const links = row.querySelectorAll('a[href*="set_equip"]'); - if (links.length === 0) return; + const cb = row.querySelector('input[name="eqids[]"]'); + if (!cb) return; - // Item has an onmouseover for tooltip - const omo = row.getAttribute('onmouseover') || ''; - const idMatch = omo.match(/hover_equip\((\d+)\)/); - if (!idMatch) return; - - const id = idMatch[1]; + const id = cb.value; const label = row.querySelector('label'); const name = label ? (label.textContent || '').trim() : ''; - // Detect level — HV renders Lv with CSS font in a .btm1 inside the row - const levelDiv = row.querySelector('.btm1 label, .btm1'); - const levelText = levelDiv ? readCSSText(levelDiv) : ''; - const lvMatch = levelText.match(/lv\.?\s*(\d+)/i); - const itemLevel = lvMatch ? parseInt(lvMatch[1]) : 0; + // Status icons in the label text + const equipped = name.includes('🗡'); + const locked = name.includes('🔒'); + const pinned = name.includes('📌'); + const stored = name.includes('📦'); + const protected_ = name.includes('🛡'); - // Detect status indicators - const rowHtml = row.innerHTML || ''; - const isLocked = rowHtml.includes('lock.png') || rowHtml.includes('lck.png'); - const isEquipped = rowHtml.includes('eqp.png') || rowHtml.includes('Equipped'); - const isPinned = rowHtml.includes('pin.png'); - const isStored = rowHtml.includes('store.png'); + // Strip status icons for clean name + const cleanName = name.replace(/[🗡🔒📌📦🛡️]/g, '').trim(); - // Check for Sell/Salvage buttons in this row - const sellBtn = row.querySelector('input[value="Sell"], a[onclick*="sell_equip"]'); - const salvBtn = row.querySelector('input[value="Salvage"], a[onclick*="salvage_equip"]'); - - const quality = KB.qualities.find(q => name.includes(q)) || '?'; items.push({ - id, name, quality, slot: currentCategory, - level: itemLevel, - locked: isLocked, equipped: isEquipped, - pinned: isPinned, stored: isStored, - canSell: !!sellBtn, canSalvage: !!salvBtn, - row, + id, name: cleanName, slot: currentCategory, + equipped, locked, pinned, stored, protected: protected_, + checkbox: cb, row, + grade: gradeEquipment(cleanName), }); }); if (items.length === 0) return; - // ── Grade each item ── - const graded = items.map(item => ({ - ...item, - grade: gradeEquipment(item.name, item.slot), - })); + // ── Detect which armory tab we're on ── + let screen = 'organize'; + const url = window.location.href || ''; + const sm = url.match(/screen=(\w+)/); + if (sm) screen = sm[1]; - // ── Sorting: equipped/pinned first, then by quality descending, then by level ── - const qwords = KB.qualities; - graded.sort((a, b) => { - if (a.equipped && !b.equipped) return -1; - if (!a.equipped && b.equipped) return 1; - if (a.pinned && !b.pinned) return -1; - if (!a.pinned && b.pinned) return 1; - const qDiff = (qwords.indexOf(b.quality) - qwords.indexOf(a.quality)); - if (qDiff !== 0) return qDiff; - return b.level - a.level; - }); + // ── Counts ── + const sellCount = items.filter(i => i.grade.action === 'sell' && !i.equipped && !i.locked).length; + const salvCount = items.filter(i => i.grade.action === 'salvage' && !i.equipped && !i.locked).length; + const keepCount = items.filter(i => i.grade.action === 'keep').length; - // ── Summary counts ── - const keepCount = graded.filter(i => i.grade.action === 'keep').length; - const sellCount = graded.filter(i => i.grade.action === 'sell').length; - const salvCount = graded.filter(i => i.grade.action === 'salvage').length; - const equippedCount = graded.filter(i => i.equipped).length; - - // ── Build bulk action buttons ── - function bulkSell() { - const toSell = graded.filter(i => i.grade.action === 'sell' && i.canSell && !i.equipped && !i.locked); - if (toSell.length === 0) return; - toSell.forEach(item => { - const btn = item.row.querySelector('input[value="Sell"], a[onclick*="sell_equip"]'); - if (btn) btn.click(); - }); - } - - function bulkSalvage() { - const toSalv = graded.filter(i => i.grade.action === 'salvage' && i.canSalvage && !i.equipped && !i.locked); - if (toSalv.length === 0) return; - toSalv.forEach(item => { - const btn = item.row.querySelector('input[value="Salvage"], a[onclick*="salvage_equip"]'); - if (btn) btn.click(); - }); - } - - // ── Build the panel ── - const qColor = (q) => { const i = qwords.indexOf(q); return i >= 5 ? '#0f0' : i >= 3 ? '#fdcb00' : i >= 1 ? '#888' : '#f80'; }; - - let body = `
- - -
`; - - body += `
- ✅ Keep: ${keepCount} | ♻ Salvage: ${salvCount} | 💰 Sell: ${sellCount} | 🗡 Equipped: ${equippedCount} -
`; - - // ── Table ── - body += ''; - body += '' + - '' + - '' + - ''; - - let lastSlot = ''; - for (const item of graded) { - // Slot separator - if (item.slot && item.slot !== lastSlot) { - lastSlot = item.slot; - body += ``; - } - - const statusIcon = item.equipped ? '🗡' : item.locked ? '🔒' : item.stored ? '📦' : item.pinned ? '📌' : ''; - const nameShort = item.name.length > 28 ? item.name.slice(0, 27) + '…' : item.name; - body += ` - - - `; - } - body += '
QItemLvAction
${item.slot}
${item.quality[0]}${statusIcon} ${nameShort}${item.level || '?'}${item.grade.label}
'; - - body += '
' + - 'Status: 🗡Equipped 📌Pinned 🔒Locked 📦Stored
'; - - // ── Build panel container ── - const collapsed = localStorage[SP + 'armoryCollapsed'] === '1'; - const panel = document.createElement('div'); - panel.id = 'hv-armory-panel'; - panel.style.cssText = css({ - position: 'fixed', - top: '60px', - right: '4px', - zIndex: '9995', - background: '#111827', - color: '#d1d5db', - padding: '0', - borderRadius: '8px', + // ── Build the toolbar ── + const toolbar = document.createElement('div'); + toolbar.id = 'hv-armory-bar'; + toolbar.style.cssText = css({ + display: 'flex', + gap: '4px', + flexWrap: 'wrap', + padding: '4px 6px', + marginBottom: '2px', + background: '#1a1a2e', + borderRadius: '4px', fontSize: '10px', fontFamily: 'monospace', - maxWidth: '350px', - maxHeight: '80vh', - overflowY: 'auto', - boxShadow: '0 0 15px rgba(0,0,0,0.6)', - border: '1px solid #374151', + alignItems: 'center', }); - panel.innerHTML = `
- 🛡️ Armory (${items.length}) - ${collapsed ? '▶' : '▼'} -
-
${body}
`; + // Summary + const summary = document.createElement('span'); + summary.style.cssText = 'color:#888;margin-right:6px'; + summary.textContent = `✅${keepCount} ♻${salvCount} 💰${sellCount}`; + toolbar.appendChild(summary); - panel.querySelector('#hv-armory-header').onclick = () => { - const b = document.getElementById('hv-armory-body'); - const t = document.getElementById('hv-armory-toggle'); - const h = b.style.display === 'none'; - b.style.display = h ? 'block' : 'none'; - t.textContent = h ? '▼' : '▶'; - localStorage[SP + 'armoryCollapsed'] = h ? '0' : '1'; + // ── Smart auto-select: check boxes based on grade ── + function autoSelect(action) { + items.forEach(item => { + if (item.grade.action !== action) return; + if (item.equipped || item.locked) return; + item.checkbox.checked = true; + }); + // Trigger the game's update function if available + if (typeof update_selected_count === 'function') update_selected_count(); + } + + // ── Select by quality ── + function selectByQuality(below) { + const idx = KB.qualities.indexOf(below); + items.forEach(item => { + const qi = KB.qualities.indexOf(KB.qualities.find(q => item.name.includes(q))); + if (qi < 0) return; + if (qi <= idx && !item.equipped && !item.locked) { + item.checkbox.checked = true; + } + }); + if (typeof update_selected_count === 'function') update_selected_count(); + } + + function clearAll() { + items.forEach(item => item.checkbox.checked = false); + if (typeof update_selected_count === 'function') update_selected_count(); + } + + // Buttons — only show relevant ones for current screen + if (screen === 'sell') { + const b = document.createElement('input'); + b.type = 'button'; + b.value = `💰 Select Crude (${sellCount})`; + b.style.cssText = 'padding:2px 6px;cursor:pointer;background:#d50c2d;color:#fff;border:none;border-radius:3px;font-size:10px'; + b.onclick = () => autoSelect('sell'); + toolbar.appendChild(b); + + const b2 = document.createElement('input'); + b2.type = 'button'; + b2.value = '💰 Select ≤Fair'; + b2.style.cssText = 'padding:2px 6px;cursor:pointer;background:#a00;color:#fff;border:none;border-radius:3px;font-size:10px'; + b2.onclick = () => selectByQuality('Fair'); + toolbar.appendChild(b2); + } + + if (screen === 'salvage') { + const b = document.createElement('input'); + b.type = 'button'; + b.value = `♻ Select Salvage (${salvCount})`; + b.style.cssText = 'padding:2px 6px;cursor:pointer;background:#555;color:#fff;border:none;border-radius:3px;font-size:10px'; + b.onclick = () => autoSelect('salvage'); + toolbar.appendChild(b); + + const b2 = document.createElement('input'); + b2.type = 'button'; + b2.value = '♻ Select ≤Average'; + b2.style.cssText = 'padding:2px 6px;cursor:pointer;background:#444;color:#fff;border:none;border-radius:3px;font-size:10px'; + b2.onclick = () => selectByQuality('Average'); + toolbar.appendChild(b2); + + // "Sell Salvaged Equipment" toggle is already native + } + + if (screen === 'organize') { + const b = document.createElement('input'); + b.type = 'button'; + b.value = '📌 Pin Equipped'; + b.style.cssText = 'padding:2px 6px;cursor:pointer;background:#2a5a2a;color:#fff;border:none;border-radius:3px;font-size:10px'; + b.onclick = () => { + // Select all equipped items so they can be pinned + items.forEach(item => { if (item.equipped) item.checkbox.checked = true; }); + if (typeof update_selected_count === 'function') update_selected_count(); + }; + toolbar.appendChild(b); + } + + // Clear button always available + const clear = document.createElement('input'); + clear.type = 'button'; + clear.value = '✗ Clear'; + clear.style.cssText = 'padding:2px 6px;cursor:pointer;background:#555;color:#fff;border:none;border-radius:3px;font-size:10px'; + clear.onclick = clearAll; + toolbar.appendChild(clear); + + // Select All / Invert Selection (useful on any screen) + const selAll = document.createElement('input'); + selAll.type = 'button'; + selAll.value = '☐ All'; + selAll.style.cssText = 'padding:2px 6px;cursor:pointer;background:#3a4a6a;color:#fff;border:none;border-radius:3px;font-size:10px'; + selAll.onclick = () => { + items.forEach(item => { item.checkbox.checked = true; }); + if (typeof update_selected_count === 'function') update_selected_count(); }; + toolbar.appendChild(selAll); - document.body.appendChild(panel); + const invert = document.createElement('input'); + invert.type = 'button'; + invert.value = '⊞ Invert'; + invert.style.cssText = 'padding:2px 6px;cursor:pointer;background:#444;color:#fff;border:none;border-radius:3px;font-size:10px'; + invert.onclick = () => { + items.forEach(item => { item.checkbox.checked = !item.checkbox.checked; }); + if (typeof update_selected_count === 'function') update_selected_count(); + }; + toolbar.appendChild(invert); - // ── Wire bulk buttons ── - const sellBulk = document.getElementById('hv-armory-bulk-sell'); - const salvBulk = document.getElementById('hv-armory-bulk-salvage'); - - if (sellBulk) sellBulk.onclick = bulkSell; - if (salvBulk) salvBulk.onclick = bulkSalvage; + // ── Insert into page ── + const eqSelect = document.getElementById('equipselect_outer') || + document.querySelector('#equipselect_left, #armory_right > div'); + if (equipList && equipList.parentNode) { + equipList.parentNode.insertBefore(toolbar, equipList); + } }