v0.15.0 - Keep Top 5 smart-selector for sell/salvage screens

New button on Sell and Salvage tabs: ' Keep Top 5 / Select Sell/Salvage Rest'

- Groups every un-equipped, unlocked, unpinned item into 19 categories:
  1H, 2H, Staff, Shield + 5 armor slots (Head/Body/Hands/Legs/Feet)
  × armor type (Cloth/Light/Heavy)
- Scores items using the same gear-analysis weights (quality mult,
  weapon type bonus, PMit/Evade/STR etc.)
- Keeps the 5 highest-scoring items per category unchecked
- Checks everything else for selling or salvaging
- Console logs per-category breakdown (e.g. '🗂 Light Head: 12 items
  → keep 5 (top 161/149/141/138/131), select 7')

Bump: 0.14.41 → 0.15.0
This commit is contained in:
GaboGG 2026-07-31 14:24:25 -04:00
parent e935e34ae1
commit 1c14e9b2a4
5 changed files with 228 additions and 6 deletions

View file

@ -1,7 +1,7 @@
// ==UserScript==
// @name HV Unified
// @namespace hvunified
// @version 0.14.41
// @version 0.15.0
// @description HV Unified — battle automation, guidance, and UI enhancements for HentaiVerse
// @author GaboGG + Hermes
// @match *://*.hentaiverse.org/*
@ -17,7 +17,7 @@
// CONFIG — default settings
// ═══════════════════════════════════════════════════════════════════════
const VERSION = '0.14.41';
const VERSION = '0.15.0';
const CFG = {
// — Battle automation
@ -3280,6 +3280,80 @@ function enhanceArmory() {
toolbar.appendChild(b);
}
// ── Keep Top 5 per category (score-based), select the rest ──
// Categories: 1H, 2H, Staff, Shield + 5 armor slots × (Cloth, Light, Heavy)
function qualityIndex(name) {
const q = KB.qualities.find(qq => name.includes(qq));
return q ? KB.qualities.indexOf(q) : 0;
}
function keepTop5AndSelect(action) {
const catMap = new Map();
items.forEach(item => {
if (item.equipped || item.locked || item.pinned) return;
const data = getHVEquipData(item.id);
const stats = data && data.d ? parseEquipHTML(data.d) : null;
if (!stats) return; // skip unscraped items — leave untouched
const type = (stats.type || '');
let cat = '?';
if (type.includes('Two-handed')) cat = '2H';
else if (type.includes('One-handed')) cat = '1H';
else if (type.includes('Staff')) cat = 'Staff';
else if (type.includes('Shield')) cat = 'Shield';
else if (type.includes('Cloth')) cat = 'Cloth';
else if (type.includes('Light')) cat = 'Light';
else if (type.includes('Heavy')) cat = 'Heavy';
// For armor, append the slot (Head/Body/Hands/Legs/Feet)
if (['Cloth', 'Light', 'Heavy'].includes(cat)) {
const slot = gearDetectSlot({ name: item.name, category: cat });
if (!slot) return;
cat += ' ' + slot;
}
const isWeaponCat = cat.startsWith('1H') || cat.startsWith('2H') || cat === 'Staff' || cat === 'Shield';
const quality = qualityIndex(item.name);
const score = isWeaponCat
? gearScoreWeapon({ name: item.name, stats, quality })
: gearScoreArmor({ name: item.name, stats, quality });
if (!catMap.has(cat)) catMap.set(cat, []);
catMap.get(cat).push({ item, score });
});
// Keep top 5 per category, select the rest
let selected = 0;
catMap.forEach((list, cat) => {
list.sort((a, b) => b.score - a.score);
const keep = list.slice(0, 5);
const dump = list.slice(5);
dump.forEach(({ item }) => {
item.checkbox.checked = true;
selected++;
});
console.log(`%c[HV] 🗂 ${cat}: ${list.length} items → keep ${keep.length} (top ${keep.map(k => k.score.toFixed(0)).join('/')}), select ${dump.length}`, 'color:#0f0');
});
if (typeof update_selected_count === 'function') update_selected_count();
return selected;
}
// ── Keep Top 5 button — available on sell and salvage screens ──
if (screen === 'sell' || screen === 'salvage') {
const b3 = document.createElement('input');
b3.type = 'button';
b3.value = `⭐ Keep Top 5 / Select ${screen === 'sell' ? 'Sell' : 'Salvage'} Rest`;
b3.style.cssText = 'padding:2px 6px;cursor:pointer;background:#7a5a2a;color:#fff;border:none;border-radius:3px;font-size:10px';
b3.title = 'Keeps the 5 highest-scoring items in each category (1H, 2H, Staff, Shield, and each armor slot per Cloth/Light/Heavy). Selects everything else.';
b3.onclick = () => {
const n = keepTop5AndSelect(screen);
console.log(`%c[HV] ⭐ Keep Top 5: ${n} items selected for ${screen}`, 'color:#0f0');
};
toolbar.appendChild(b3);
}
// Clear button always available
const clear = document.createElement('input');
clear.type = 'button';

View file

@ -1,7 +1,7 @@
// ==UserScript==
// @name HV Unified
// @namespace hvunified
// @version 0.14.41
// @version 0.15.0
// @description HV Unified — battle automation, guidance, and UI enhancements for HentaiVerse
// @author GaboGG + Hermes
// @match *://*.hentaiverse.org/*
@ -17,7 +17,7 @@
// CONFIG — default settings
// ═══════════════════════════════════════════════════════════════════════
const VERSION = '0.14.41';
const VERSION = '0.15.0';
const CFG = {
// — Battle automation
@ -3280,6 +3280,80 @@ function enhanceArmory() {
toolbar.appendChild(b);
}
// ── Keep Top 5 per category (score-based), select the rest ──
// Categories: 1H, 2H, Staff, Shield + 5 armor slots × (Cloth, Light, Heavy)
function qualityIndex(name) {
const q = KB.qualities.find(qq => name.includes(qq));
return q ? KB.qualities.indexOf(q) : 0;
}
function keepTop5AndSelect(action) {
const catMap = new Map();
items.forEach(item => {
if (item.equipped || item.locked || item.pinned) return;
const data = getHVEquipData(item.id);
const stats = data && data.d ? parseEquipHTML(data.d) : null;
if (!stats) return; // skip unscraped items — leave untouched
const type = (stats.type || '');
let cat = '?';
if (type.includes('Two-handed')) cat = '2H';
else if (type.includes('One-handed')) cat = '1H';
else if (type.includes('Staff')) cat = 'Staff';
else if (type.includes('Shield')) cat = 'Shield';
else if (type.includes('Cloth')) cat = 'Cloth';
else if (type.includes('Light')) cat = 'Light';
else if (type.includes('Heavy')) cat = 'Heavy';
// For armor, append the slot (Head/Body/Hands/Legs/Feet)
if (['Cloth', 'Light', 'Heavy'].includes(cat)) {
const slot = gearDetectSlot({ name: item.name, category: cat });
if (!slot) return;
cat += ' ' + slot;
}
const isWeaponCat = cat.startsWith('1H') || cat.startsWith('2H') || cat === 'Staff' || cat === 'Shield';
const quality = qualityIndex(item.name);
const score = isWeaponCat
? gearScoreWeapon({ name: item.name, stats, quality })
: gearScoreArmor({ name: item.name, stats, quality });
if (!catMap.has(cat)) catMap.set(cat, []);
catMap.get(cat).push({ item, score });
});
// Keep top 5 per category, select the rest
let selected = 0;
catMap.forEach((list, cat) => {
list.sort((a, b) => b.score - a.score);
const keep = list.slice(0, 5);
const dump = list.slice(5);
dump.forEach(({ item }) => {
item.checkbox.checked = true;
selected++;
});
console.log(`%c[HV] 🗂 ${cat}: ${list.length} items → keep ${keep.length} (top ${keep.map(k => k.score.toFixed(0)).join('/')}), select ${dump.length}`, 'color:#0f0');
});
if (typeof update_selected_count === 'function') update_selected_count();
return selected;
}
// ── Keep Top 5 button — available on sell and salvage screens ──
if (screen === 'sell' || screen === 'salvage') {
const b3 = document.createElement('input');
b3.type = 'button';
b3.value = `⭐ Keep Top 5 / Select ${screen === 'sell' ? 'Sell' : 'Salvage'} Rest`;
b3.style.cssText = 'padding:2px 6px;cursor:pointer;background:#7a5a2a;color:#fff;border:none;border-radius:3px;font-size:10px';
b3.title = 'Keeps the 5 highest-scoring items in each category (1H, 2H, Staff, Shield, and each armor slot per Cloth/Light/Heavy). Selects everything else.';
b3.onclick = () => {
const n = keepTop5AndSelect(screen);
console.log(`%c[HV] ⭐ Keep Top 5: ${n} items selected for ${screen}`, 'color:#0f0');
};
toolbar.appendChild(b3);
}
// Clear button always available
const clear = document.createElement('input');
clear.type = 'button';

View file

@ -174,6 +174,80 @@ function enhanceArmory() {
toolbar.appendChild(b);
}
// ── Keep Top 5 per category (score-based), select the rest ──
// Categories: 1H, 2H, Staff, Shield + 5 armor slots × (Cloth, Light, Heavy)
function qualityIndex(name) {
const q = KB.qualities.find(qq => name.includes(qq));
return q ? KB.qualities.indexOf(q) : 0;
}
function keepTop5AndSelect(action) {
const catMap = new Map();
items.forEach(item => {
if (item.equipped || item.locked || item.pinned) return;
const data = getHVEquipData(item.id);
const stats = data && data.d ? parseEquipHTML(data.d) : null;
if (!stats) return; // skip unscraped items — leave untouched
const type = (stats.type || '');
let cat = '?';
if (type.includes('Two-handed')) cat = '2H';
else if (type.includes('One-handed')) cat = '1H';
else if (type.includes('Staff')) cat = 'Staff';
else if (type.includes('Shield')) cat = 'Shield';
else if (type.includes('Cloth')) cat = 'Cloth';
else if (type.includes('Light')) cat = 'Light';
else if (type.includes('Heavy')) cat = 'Heavy';
// For armor, append the slot (Head/Body/Hands/Legs/Feet)
if (['Cloth', 'Light', 'Heavy'].includes(cat)) {
const slot = gearDetectSlot({ name: item.name, category: cat });
if (!slot) return;
cat += ' ' + slot;
}
const isWeaponCat = cat.startsWith('1H') || cat.startsWith('2H') || cat === 'Staff' || cat === 'Shield';
const quality = qualityIndex(item.name);
const score = isWeaponCat
? gearScoreWeapon({ name: item.name, stats, quality })
: gearScoreArmor({ name: item.name, stats, quality });
if (!catMap.has(cat)) catMap.set(cat, []);
catMap.get(cat).push({ item, score });
});
// Keep top 5 per category, select the rest
let selected = 0;
catMap.forEach((list, cat) => {
list.sort((a, b) => b.score - a.score);
const keep = list.slice(0, 5);
const dump = list.slice(5);
dump.forEach(({ item }) => {
item.checkbox.checked = true;
selected++;
});
console.log(`%c[HV] 🗂 ${cat}: ${list.length} items → keep ${keep.length} (top ${keep.map(k => k.score.toFixed(0)).join('/')}), select ${dump.length}`, 'color:#0f0');
});
if (typeof update_selected_count === 'function') update_selected_count();
return selected;
}
// ── Keep Top 5 button — available on sell and salvage screens ──
if (screen === 'sell' || screen === 'salvage') {
const b3 = document.createElement('input');
b3.type = 'button';
b3.value = `⭐ Keep Top 5 / Select ${screen === 'sell' ? 'Sell' : 'Salvage'} Rest`;
b3.style.cssText = 'padding:2px 6px;cursor:pointer;background:#7a5a2a;color:#fff;border:none;border-radius:3px;font-size:10px';
b3.title = 'Keeps the 5 highest-scoring items in each category (1H, 2H, Staff, Shield, and each armor slot per Cloth/Light/Heavy). Selects everything else.';
b3.onclick = () => {
const n = keepTop5AndSelect(screen);
console.log(`%c[HV] ⭐ Keep Top 5: ${n} items selected for ${screen}`, 'color:#0f0');
};
toolbar.appendChild(b3);
}
// Clear button always available
const clear = document.createElement('input');
clear.type = 'button';

View file

@ -2,7 +2,7 @@
// CONFIG — default settings
// ═══════════════════════════════════════════════════════════════════════
const VERSION = '0.14.41';
const VERSION = '0.15.0';
const CFG = {
// — Battle automation

View file

@ -1,7 +1,7 @@
// ==UserScript==
// @name HV Unified
// @namespace hvunified
// @version 0.14.41
// @version 0.15.0
// @description HV Unified — battle automation, guidance, and UI enhancements for HentaiVerse
// @author GaboGG + Hermes
// @match *://*.hentaiverse.org/*