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()
143 lines
5.4 KiB
JavaScript
143 lines
5.4 KiB
JavaScript
// ═══════════════════════════════════════════════════════════════════════
|
||
// 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');
|
||
|
||
})();
|