hv-unified/src/init.js
GaboGG 166539e845 v0.14.33 - In-game gear analysis module
New src/gear-analysis.js — ports analyze_gear.py scoring to JS:
- gearScoreArmor() / gearScoreWeapon() — same weights as Python
- gearDetectSlot() / gearIsTwoHandWeapon() / gearUsable()
- analyzeGear() — scores equipped vs armory vs buy per slot

UI on Armory pages (above equiplist):
- 🔍 Analyze Gear — rescrapes current page, shows per-slot panel
  with top 4 candidates, scores, prices, and upgrade highlights
- 🔄 Clear Buy + Rescan — wipes stale buy data, rescrapes, analyzes

scrapeBuyPage() now also scavenges the full dynjs_eqstore for items
not visible in the current filter tab — one Purchase visit captures
the entire store (429+ items), no need to click through tabs.

Console API: HV.analyzeGear(), HV.gearPanel(), HV.clearBuy()
2026-07-31 07:07:38 -04:00

143 lines
5.4 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// ═══════════════════════════════════════════════════════════════════════
// INIT — bootstrap everything
// ═══════════════════════════════════════════════════════════════════════
function init() {
loadConfig();
detectLevel();
STATE.page = detectPage();
// Always-on features
setupRETimer();
addConfigButton();
renderProgressPanel();
if (STATE.page === 'battle') {
initializeBattle();
autoCheckTask('battle');
} else {
// Non-battle page enhancements
if (STATE.page === 'itemshop') { enhanceItemShop(); autoCheckTask('buy-health'); }
if (STATE.page === 'equipshop') { enhanceEquipShopWithAdvice(); }
if (STATE.page === 'shrine') enhanceShrine();
if (STATE.page === 'training') { enhanceTraining(); autoCheckTask('training'); }
if (STATE.page === 'monsterlab') { enhanceMonsterLab(); autoCheckTask('feed'); }
if (STATE.page === 'arena') { autoCheckTask('arenas'); autoCheckTask('first-blood'); }
if (STATE.page === 'character') showGuidancePanel();
if (STATE.page === 'settings') enhanceSettings();
if (STATE.page === 'armory') { enhanceArmory(); enhanceGearAnalysis(); }
if (STATE.page === 'abilities') enhanceAbilities();
if (STATE.page === 'monster') enhanceMonsterLab();
}
// Dawn initialization
if (!localStorage[SP + 'lastDawn']) {
try { localStorage[SP + 'lastDawn'] = JSON.stringify(Date.now()); } catch (e) {}
}
updateRETimer();
// Watch for dynamic page transitions (arena/grindfest → battle)
const bodyObserver = new MutationObserver(() => {
if (!STATE.inBattle && isBattlePage()) {
initializeBattle();
}
});
bodyObserver.observe(document.body, { childList: true, subtree: true });
}
function initializeBattle() {
if (STATE.battleInitialized && STATE.page === 'battle') return;
STATE.page = 'battle';
STATE.inBattle = true;
STATE._mysticUsed = false; // Reset Mystic Gem tracking on new battle
STATE._spiritForSkill = 0; // Reset spirit burst toggle
STATE._baseMpCosts = {}; // Reset base cost cache
parseBattleState();
// Force tier update from cached level (battle page may not have level_readout)
if (STATE.level > 1) updateTier();
setupHover();
setupKeybindings();
addMonsterNumbers();
refreshUI();
addConfigButton();
showDifficultyBanner();
// Watch battle log
const log = document.getElementById('textlog');
if (log && !log.dataset.hvObserved) {
log.dataset.hvObserved = '1';
const obs = new MutationObserver(() => {
parseBattleState();
saveMonsterData();
logRound(); // Log this round's data for analysis
if (CFG.hoverEnabled && !STATE.interruptHover && !STATE.interruptAlert && STATE.hoverTarget >= 0) {
const a = getSmartAction();
if (a) executeAction(a);
}
refreshUI();
updateConfigButton();
});
obs.observe(log, { childList: true, subtree: true, characterData: true });
}
// Watch vitals pane
const vitals = document.getElementById('pane_vitals');
if (vitals && !vitals.dataset.hvObserved) {
vitals.dataset.hvObserved = '1';
const vobs = new MutationObserver(() => {
parseBattleState();
logRound(); // Also log on vitals changes (catches end-of-round)
refreshUI();
updateConfigButton();
// Check if battle ended (completion pane appeared)
if (document.getElementById('pane_completion')) {
// Battle ended — handled by log observer
}
});
vobs.observe(vitals, { childList: true, subtree: true, attributes: true });
}
// RiddleMaster detection
if (document.getElementById('riddlemaster')) {
STATE.interruptHover = true;
}
// Auto-scrape gear data on character/armory pages (reads from HV's in-memory store)
// Wait for dynjs_equip to load (external script, may load after document-end)
const url = window.location.href || '';
if (url.includes('ss=eq') || url.includes('ss=ch') || url.includes('ss=am')) {
let attempts = 0;
const tryScrape = () => {
const store = getHVEquipStore();
if (store && Object.keys(store).length > 0) {
autoScrapeGear();
return;
}
if (++attempts < 10) {
setTimeout(tryScrape, 500); // retry every 500ms up to 5 seconds
} else {
// Fallback: scrape names/IDs without stats
autoScrapeGear();
}
};
setTimeout(tryScrape, 300);
}
STATE.battleInitialized = true;
}
// ── Bootstrap ──
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
console.log('%c🛡 HV Unified v' + VERSION + '%c | Q=action H=hover C=cure ,=settings',
'color:#fdcb00;font-weight:bold', '');
console.log('%c HV.advice() for guidance | HV.difficulty() for difficulty check',
'color:#888;font-size:10px');
})();