v0.13.2 - Rewrite armory.js with actual page DOM support
- Scrap the old floating panel approach (wrong DOM selectors) - Now injects a toolbar directly into the equipment list on the Armory page - Parses equipment from native #equiplist table rows using correct DOM selectors (hover_equip, select_equip, eqids[] checkboxes) - Detects current screen (organize/sell/salvage/modify) to show relevant bulk-select buttons per tab - Sell tab: select Crude, select <=Fair - Salvage tab: select salvage-grade, select <=Average - Organize tab: select equipped (for pinning) - Plus: Select All, Invert, Clear on every screen - Grades by quality: Keep (Magnificent+), Keep (Superior), Salvage (Average-Fair), Sell (Crude) - Resets properly across page navigations (single-use id guard)
This commit is contained in:
parent
1669167373
commit
2f680a630d
3 changed files with 456 additions and 477 deletions
|
|
@ -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 (<input name="eqids[]">). 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 = `<div style="display:flex;gap:4px;margin-bottom:6px">
|
||||
<input type="button" value="💰 Sell Crude (${sellCount})" style="padding:3px 8px;cursor:pointer;background:#d50c2d;color:#fff;border:none;border-radius:3px;font-size:10px" id="hv-armory-bulk-sell">
|
||||
<input type="button" value="♻ Salvage ≤Fair (${salvCount})" style="padding:3px 8px;cursor:pointer;background:#555;color:#fff;border:none;border-radius:3px;font-size:10px" id="hv-armory-bulk-salvage">
|
||||
</div>`;
|
||||
|
||||
body += `<div style="border-top:1px solid #374151;margin:4px 0;padding:4px 0;font-size:9px;color:#888">
|
||||
✅ Keep: ${keepCount} | ♻ Salvage: ${salvCount} | 💰 Sell: ${sellCount} | 🗡 Equipped: ${equippedCount}
|
||||
</div>`;
|
||||
|
||||
// ── Table ──
|
||||
body += '<table style="width:100%;font-size:9px;border-collapse:collapse">';
|
||||
body += '<tr style="color:#666"><th style="text-align:left;padding:1px 2px">Q</th>' +
|
||||
'<th style="text-align:left;padding:1px 2px">Item</th>' +
|
||||
'<th style="text-align:center;padding:1px 2px">Lv</th>' +
|
||||
'<th style="text-align:right;padding:1px 2px">Action</th></tr>';
|
||||
|
||||
let lastSlot = '';
|
||||
for (const item of graded) {
|
||||
// Slot separator
|
||||
if (item.slot && item.slot !== lastSlot) {
|
||||
lastSlot = item.slot;
|
||||
body += `<tr><td colspan="4" style="padding:4px 2px 1px;color:#555;font-size:8px;border-bottom:1px solid #333">${item.slot}</td></tr>`;
|
||||
}
|
||||
|
||||
const statusIcon = item.equipped ? '🗡' : item.locked ? '🔒' : item.stored ? '📦' : item.pinned ? '📌' : '';
|
||||
const nameShort = item.name.length > 28 ? item.name.slice(0, 27) + '…' : item.name;
|
||||
body += `<tr><td style="padding:1px 2px;color:${qColor(item.quality)}">${item.quality[0]}</td>
|
||||
<td style="padding:1px 2px">${statusIcon} ${nameShort}</td>
|
||||
<td style="padding:1px 2px;text-align:center;color:#888">${item.level || '?'}</td>
|
||||
<td style="padding:1px 2px;text-align:right;color:${item.grade.color}">${item.grade.label}</td></tr>`;
|
||||
}
|
||||
body += '</table>';
|
||||
|
||||
body += '<div style="border-top:1px solid #374151;margin:6px 0;padding:4px 0;font-size:9px;color:#555">' +
|
||||
'Status: 🗡Equipped 📌Pinned 🔒Locked 📦Stored</div>';
|
||||
|
||||
// ── 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 = `<div style="display:flex;justify-content:space-between;align-items:center;padding:6px 8px;cursor:pointer" id="hv-armory-header">
|
||||
<b style="color:#fdcb00;font-size:10px">🛡️ Armory (${items.length})</b>
|
||||
<span id="hv-armory-toggle" style="color:#888;font-size:12px">${collapsed ? '▶' : '▼'}</span>
|
||||
</div>
|
||||
<div id="hv-armory-body" style="padding:0 8px 6px;display:${collapsed ? 'none' : 'block'}">${body}</div>`;
|
||||
// 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);
|
||||
}
|
||||
|
||||
document.body.appendChild(panel);
|
||||
// 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);
|
||||
|
||||
// ── 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);
|
||||
}
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════
|
||||
|
|
|
|||
|
|
@ -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 (<input name="eqids[]">). 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 = `<div style="display:flex;gap:4px;margin-bottom:6px">
|
||||
<input type="button" value="💰 Sell Crude (${sellCount})" style="padding:3px 8px;cursor:pointer;background:#d50c2d;color:#fff;border:none;border-radius:3px;font-size:10px" id="hv-armory-bulk-sell">
|
||||
<input type="button" value="♻ Salvage ≤Fair (${salvCount})" style="padding:3px 8px;cursor:pointer;background:#555;color:#fff;border:none;border-radius:3px;font-size:10px" id="hv-armory-bulk-salvage">
|
||||
</div>`;
|
||||
|
||||
body += `<div style="border-top:1px solid #374151;margin:4px 0;padding:4px 0;font-size:9px;color:#888">
|
||||
✅ Keep: ${keepCount} | ♻ Salvage: ${salvCount} | 💰 Sell: ${sellCount} | 🗡 Equipped: ${equippedCount}
|
||||
</div>`;
|
||||
|
||||
// ── Table ──
|
||||
body += '<table style="width:100%;font-size:9px;border-collapse:collapse">';
|
||||
body += '<tr style="color:#666"><th style="text-align:left;padding:1px 2px">Q</th>' +
|
||||
'<th style="text-align:left;padding:1px 2px">Item</th>' +
|
||||
'<th style="text-align:center;padding:1px 2px">Lv</th>' +
|
||||
'<th style="text-align:right;padding:1px 2px">Action</th></tr>';
|
||||
|
||||
let lastSlot = '';
|
||||
for (const item of graded) {
|
||||
// Slot separator
|
||||
if (item.slot && item.slot !== lastSlot) {
|
||||
lastSlot = item.slot;
|
||||
body += `<tr><td colspan="4" style="padding:4px 2px 1px;color:#555;font-size:8px;border-bottom:1px solid #333">${item.slot}</td></tr>`;
|
||||
}
|
||||
|
||||
const statusIcon = item.equipped ? '🗡' : item.locked ? '🔒' : item.stored ? '📦' : item.pinned ? '📌' : '';
|
||||
const nameShort = item.name.length > 28 ? item.name.slice(0, 27) + '…' : item.name;
|
||||
body += `<tr><td style="padding:1px 2px;color:${qColor(item.quality)}">${item.quality[0]}</td>
|
||||
<td style="padding:1px 2px">${statusIcon} ${nameShort}</td>
|
||||
<td style="padding:1px 2px;text-align:center;color:#888">${item.level || '?'}</td>
|
||||
<td style="padding:1px 2px;text-align:right;color:${item.grade.color}">${item.grade.label}</td></tr>`;
|
||||
}
|
||||
body += '</table>';
|
||||
|
||||
body += '<div style="border-top:1px solid #374151;margin:6px 0;padding:4px 0;font-size:9px;color:#555">' +
|
||||
'Status: 🗡Equipped 📌Pinned 🔒Locked 📦Stored</div>';
|
||||
|
||||
// ── 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 = `<div style="display:flex;justify-content:space-between;align-items:center;padding:6px 8px;cursor:pointer" id="hv-armory-header">
|
||||
<b style="color:#fdcb00;font-size:10px">🛡️ Armory (${items.length})</b>
|
||||
<span id="hv-armory-toggle" style="color:#888;font-size:12px">${collapsed ? '▶' : '▼'}</span>
|
||||
</div>
|
||||
<div id="hv-armory-body" style="padding:0 8px 6px;display:${collapsed ? 'none' : 'block'}">${body}</div>`;
|
||||
// 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);
|
||||
}
|
||||
|
||||
document.body.appendChild(panel);
|
||||
// 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);
|
||||
|
||||
// ── 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);
|
||||
}
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════
|
||||
|
|
|
|||
323
src/armory.js
323
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 (<input name="eqids[]">). 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 = `<div style="display:flex;gap:4px;margin-bottom:6px">
|
||||
<input type="button" value="💰 Sell Crude (${sellCount})" style="padding:3px 8px;cursor:pointer;background:#d50c2d;color:#fff;border:none;border-radius:3px;font-size:10px" id="hv-armory-bulk-sell">
|
||||
<input type="button" value="♻ Salvage ≤Fair (${salvCount})" style="padding:3px 8px;cursor:pointer;background:#555;color:#fff;border:none;border-radius:3px;font-size:10px" id="hv-armory-bulk-salvage">
|
||||
</div>`;
|
||||
|
||||
body += `<div style="border-top:1px solid #374151;margin:4px 0;padding:4px 0;font-size:9px;color:#888">
|
||||
✅ Keep: ${keepCount} | ♻ Salvage: ${salvCount} | 💰 Sell: ${sellCount} | 🗡 Equipped: ${equippedCount}
|
||||
</div>`;
|
||||
|
||||
// ── Table ──
|
||||
body += '<table style="width:100%;font-size:9px;border-collapse:collapse">';
|
||||
body += '<tr style="color:#666"><th style="text-align:left;padding:1px 2px">Q</th>' +
|
||||
'<th style="text-align:left;padding:1px 2px">Item</th>' +
|
||||
'<th style="text-align:center;padding:1px 2px">Lv</th>' +
|
||||
'<th style="text-align:right;padding:1px 2px">Action</th></tr>';
|
||||
|
||||
let lastSlot = '';
|
||||
for (const item of graded) {
|
||||
// Slot separator
|
||||
if (item.slot && item.slot !== lastSlot) {
|
||||
lastSlot = item.slot;
|
||||
body += `<tr><td colspan="4" style="padding:4px 2px 1px;color:#555;font-size:8px;border-bottom:1px solid #333">${item.slot}</td></tr>`;
|
||||
}
|
||||
|
||||
const statusIcon = item.equipped ? '🗡' : item.locked ? '🔒' : item.stored ? '📦' : item.pinned ? '📌' : '';
|
||||
const nameShort = item.name.length > 28 ? item.name.slice(0, 27) + '…' : item.name;
|
||||
body += `<tr><td style="padding:1px 2px;color:${qColor(item.quality)}">${item.quality[0]}</td>
|
||||
<td style="padding:1px 2px">${statusIcon} ${nameShort}</td>
|
||||
<td style="padding:1px 2px;text-align:center;color:#888">${item.level || '?'}</td>
|
||||
<td style="padding:1px 2px;text-align:right;color:${item.grade.color}">${item.grade.label}</td></tr>`;
|
||||
}
|
||||
body += '</table>';
|
||||
|
||||
body += '<div style="border-top:1px solid #374151;margin:6px 0;padding:4px 0;font-size:9px;color:#555">' +
|
||||
'Status: 🗡Equipped 📌Pinned 🔒Locked 📦Stored</div>';
|
||||
|
||||
// ── 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 = `<div style="display:flex;justify-content:space-between;align-items:center;padding:6px 8px;cursor:pointer" id="hv-armory-header">
|
||||
<b style="color:#fdcb00;font-size:10px">🛡️ Armory (${items.length})</b>
|
||||
<span id="hv-armory-toggle" style="color:#888;font-size:12px">${collapsed ? '▶' : '▼'}</span>
|
||||
</div>
|
||||
<div id="hv-armory-body" style="padding:0 8px 6px;display:${collapsed ? 'none' : 'block'}">${body}</div>`;
|
||||
// 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);
|
||||
}
|
||||
|
||||
document.body.appendChild(panel);
|
||||
// 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);
|
||||
|
||||
// ── Wire bulk buttons ──
|
||||
const sellBulk = document.getElementById('hv-armory-bulk-sell');
|
||||
const salvBulk = document.getElementById('hv-armory-bulk-salvage');
|
||||
// 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);
|
||||
|
||||
if (sellBulk) sellBulk.onclick = bulkSell;
|
||||
if (salvBulk) salvBulk.onclick = bulkSalvage;
|
||||
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);
|
||||
|
||||
// ── 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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue