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 += '| Q | ' +
- 'Item | ' +
- 'Lv | ' +
- 'Action |
';
-
- let lastSlot = '';
- for (const item of graded) {
- // Slot separator
- if (item.slot && item.slot !== lastSlot) {
- lastSlot = item.slot;
- body += `| ${item.slot} |
`;
- }
-
- const statusIcon = item.equipped ? '🗡' : item.locked ? '🔒' : item.stored ? '📦' : item.pinned ? '📌' : '';
- const nameShort = item.name.length > 28 ? item.name.slice(0, 27) + '…' : item.name;
- body += `| ${item.quality[0]} |
- ${statusIcon} ${nameShort} |
- ${item.level || '?'} |
- ${item.grade.label} |
`;
- }
- body += '
';
-
- 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 = `
- ${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 += '| Q | ' +
- 'Item | ' +
- 'Lv | ' +
- 'Action |
';
-
- let lastSlot = '';
- for (const item of graded) {
- // Slot separator
- if (item.slot && item.slot !== lastSlot) {
- lastSlot = item.slot;
- body += `| ${item.slot} |
`;
- }
-
- const statusIcon = item.equipped ? '🗡' : item.locked ? '🔒' : item.stored ? '📦' : item.pinned ? '📌' : '';
- const nameShort = item.name.length > 28 ? item.name.slice(0, 27) + '…' : item.name;
- body += `| ${item.quality[0]} |
- ${statusIcon} ${nameShort} |
- ${item.level || '?'} |
- ${item.grade.label} |
`;
- }
- body += '
';
-
- 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 = `
- ${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 += '| Q | ' +
- 'Item | ' +
- 'Lv | ' +
- 'Action |
';
-
- let lastSlot = '';
- for (const item of graded) {
- // Slot separator
- if (item.slot && item.slot !== lastSlot) {
- lastSlot = item.slot;
- body += `| ${item.slot} |
`;
- }
-
- const statusIcon = item.equipped ? '🗡' : item.locked ? '🔒' : item.stored ? '📦' : item.pinned ? '📌' : '';
- const nameShort = item.name.length > 28 ? item.name.slice(0, 27) + '…' : item.name;
- body += `| ${item.quality[0]} |
- ${statusIcon} ${nameShort} |
- ${item.level || '?'} |
- ${item.grade.label} |
`;
- }
- body += '
';
-
- 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 = `
- ${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);
+ }
}