From 30bade19bda4a5c3a63da7a1476ef2cbafb6d3bd Mon Sep 17 00:00:00 2001 From: GaboGG Date: Tue, 4 Aug 2026 15:59:08 -0400 Subject: [PATCH] v0.17.2 - Fix chainState ref, lazy jpx detection, diag visibility - Fix: chainState/chain used renamed var (jpxChainEnabled vs jpxChainStarted) - Fix: jpxChainStart lazy-retries (10x/600ms) since jpx creates #ctrl-widget lazily - Remove duplicate jpxChainStart definition - HV.diag() now reports scripts.jpx + jpxBridge state for field diagnosis --- scripts/hv-unified.user.js | 53 +++++++++++++++++++++++++------------- scripts/latest.user.js | 53 +++++++++++++++++++++++++------------- src/config.js | 2 +- src/header.user.js | 2 +- src/jpx-bridge.js | 37 ++++++++++++++++---------- src/public-api.js | 12 +++++++-- 6 files changed, 105 insertions(+), 54 deletions(-) diff --git a/scripts/hv-unified.user.js b/scripts/hv-unified.user.js index a11cc43..1572815 100644 --- a/scripts/hv-unified.user.js +++ b/scripts/hv-unified.user.js @@ -1,7 +1,7 @@ // ==UserScript== // @name HV Unified // @namespace hvunified -// @version 0.17.1 +// @version 0.17.2 // @description HV Unified — Bridge & Strategy/Gear Advisor for HentaiVerse (complements Monsterbation & HVUtils) // @author GaboGG + Hermes // @match *://*.hentaiverse.org/* @@ -18,7 +18,7 @@ // CONFIG — default settings // ═══════════════════════════════════════════════════════════════════════ -const VERSION = '0.17.1'; +const VERSION = '0.17.2'; const CFG = { // — Battle strategy advisory @@ -1568,6 +1568,29 @@ function jpxPresent() { document.querySelector('#ctrl-widget, .ctrl-widget')); } +// Lazy-start: retry until jpx's widget actually appears (it's created lazily +// by jpx after its init, so our first check may be too early). +function jpxChainStart() { + if (!CFG.autoChainJpx) return; + if (jpxChainEnabled) return; + if (!jpxPresent()) { + // Retry a few times over ~6s before giving up + if (!jpxChainRetryCount) jpxChainRetryCount = 0; + if (jpxChainRetryCount < 10) { + jpxChainRetryCount++; + setTimeout(jpxChainStart, 600); + } + return; + } + jpxChainRetryCount = 0; + jpxChainEnabled = true; + jpxChainRounds = 0; + jpxChainCooldown = Date.now(); + // Start the first auto round (equivalent to pressing M once) + setTimeout(jpxTriggerAuto, 800); + console.log('%c[HV] ⛓ jpx auto-chain engaged (re-triggering M each round)', 'color:#0f0'); +} + // Trigger jpx's auto-battle toggle (equivalent to pressing M). // jpx listens for keydown on document (capture phase) and maps key 'm' to // toggleActive via userKeybinds. A synthetic KeyboardEvent works because @@ -1603,20 +1626,6 @@ function jpxBattleOver() { return !jpxMonstersPresent(); } -// Start the chain (called after battle initializes) -function jpxChainStart() { - if (!CFG.autoChainJpx) return; - if (jpxChainEnabled) return; - if (!jpxPresent()) return; - - jpxChainEnabled = true; - jpxChainRounds = 0; - jpxChainCooldown = Date.now(); - // Start the first auto round (equivalent to pressing M once) - setTimeout(jpxTriggerAuto, 800); - console.log('%c[HV] ⛓ jpx auto-chain engaged (re-triggering M each round)', 'color:#0f0'); -} - // Called when jpx reports a state change via jpx_ctrlWidget_update function jpxChainOnState(detail) { if (!CFG.autoChainJpx) return; @@ -3765,8 +3774,8 @@ window.HV = { stats: () => JSON.parse(localStorage[SP + 'stats'] || '{"battles":0,"credits":0,"drops":0}'), settings: () => toggleSettings(), // jpx bridge control - chain: (on) => { CFG.autoChainJpx = !!on; saveConfig(); if (on) { jpxChainStart(); if (!jpxChainTimer) jpxChainTimer = setInterval(jpxChainTick, 1500); } else { jpxChainStop('manual'); } return 'jpx chain: ' + CFG.autoChainJpx; }, - chainState: () => ({ active: jpxChainStarted, rounds: jpxChainRounds, jpx: jpxPresent(), maxRounds: CFG.chainMaxRounds, minHP: CFG.chainMinHP }), + chain: (on) => { CFG.autoChainJpx = !!on; saveConfig(); if (on) { jpxBridgeWire(); jpxChainStart(); } else { jpxChainStop('manual'); } return 'jpx chain: ' + CFG.autoChainJpx; }, + chainState: () => ({ active: jpxChainEnabled, rounds: jpxChainRounds, jpx: jpxPresent(), maxRounds: CFG.chainMaxRounds, minHP: CFG.chainMinHP }), advice: () => { const s = detectFightingStyle(); return { style: s, attrs: getAttrAdvice(s), spells: getSpellAdvice(), difficulty: getDifficultyAdvice() }; @@ -3805,8 +3814,16 @@ window.HV = { document.getElementById('hvut') || window.HVUT || document.querySelector('[class*="hvut"], [id*="hvut"]')), + jpx: jpxPresent(), unified: true, }, + // jpx bridge state + jpxBridge: { + chainEnabled: jpxChainEnabled, + chainRounds: jpxChainRounds, + jpxLastActive: jpxLastActive, + retryCount: jpxChainRetryCount || 0, + }, // State availability state_ready: !!STATE && !!STATE.page, monsters: STATE.monsters ? STATE.monsters.length : null, diff --git a/scripts/latest.user.js b/scripts/latest.user.js index a11cc43..1572815 100644 --- a/scripts/latest.user.js +++ b/scripts/latest.user.js @@ -1,7 +1,7 @@ // ==UserScript== // @name HV Unified // @namespace hvunified -// @version 0.17.1 +// @version 0.17.2 // @description HV Unified — Bridge & Strategy/Gear Advisor for HentaiVerse (complements Monsterbation & HVUtils) // @author GaboGG + Hermes // @match *://*.hentaiverse.org/* @@ -18,7 +18,7 @@ // CONFIG — default settings // ═══════════════════════════════════════════════════════════════════════ -const VERSION = '0.17.1'; +const VERSION = '0.17.2'; const CFG = { // — Battle strategy advisory @@ -1568,6 +1568,29 @@ function jpxPresent() { document.querySelector('#ctrl-widget, .ctrl-widget')); } +// Lazy-start: retry until jpx's widget actually appears (it's created lazily +// by jpx after its init, so our first check may be too early). +function jpxChainStart() { + if (!CFG.autoChainJpx) return; + if (jpxChainEnabled) return; + if (!jpxPresent()) { + // Retry a few times over ~6s before giving up + if (!jpxChainRetryCount) jpxChainRetryCount = 0; + if (jpxChainRetryCount < 10) { + jpxChainRetryCount++; + setTimeout(jpxChainStart, 600); + } + return; + } + jpxChainRetryCount = 0; + jpxChainEnabled = true; + jpxChainRounds = 0; + jpxChainCooldown = Date.now(); + // Start the first auto round (equivalent to pressing M once) + setTimeout(jpxTriggerAuto, 800); + console.log('%c[HV] ⛓ jpx auto-chain engaged (re-triggering M each round)', 'color:#0f0'); +} + // Trigger jpx's auto-battle toggle (equivalent to pressing M). // jpx listens for keydown on document (capture phase) and maps key 'm' to // toggleActive via userKeybinds. A synthetic KeyboardEvent works because @@ -1603,20 +1626,6 @@ function jpxBattleOver() { return !jpxMonstersPresent(); } -// Start the chain (called after battle initializes) -function jpxChainStart() { - if (!CFG.autoChainJpx) return; - if (jpxChainEnabled) return; - if (!jpxPresent()) return; - - jpxChainEnabled = true; - jpxChainRounds = 0; - jpxChainCooldown = Date.now(); - // Start the first auto round (equivalent to pressing M once) - setTimeout(jpxTriggerAuto, 800); - console.log('%c[HV] ⛓ jpx auto-chain engaged (re-triggering M each round)', 'color:#0f0'); -} - // Called when jpx reports a state change via jpx_ctrlWidget_update function jpxChainOnState(detail) { if (!CFG.autoChainJpx) return; @@ -3765,8 +3774,8 @@ window.HV = { stats: () => JSON.parse(localStorage[SP + 'stats'] || '{"battles":0,"credits":0,"drops":0}'), settings: () => toggleSettings(), // jpx bridge control - chain: (on) => { CFG.autoChainJpx = !!on; saveConfig(); if (on) { jpxChainStart(); if (!jpxChainTimer) jpxChainTimer = setInterval(jpxChainTick, 1500); } else { jpxChainStop('manual'); } return 'jpx chain: ' + CFG.autoChainJpx; }, - chainState: () => ({ active: jpxChainStarted, rounds: jpxChainRounds, jpx: jpxPresent(), maxRounds: CFG.chainMaxRounds, minHP: CFG.chainMinHP }), + chain: (on) => { CFG.autoChainJpx = !!on; saveConfig(); if (on) { jpxBridgeWire(); jpxChainStart(); } else { jpxChainStop('manual'); } return 'jpx chain: ' + CFG.autoChainJpx; }, + chainState: () => ({ active: jpxChainEnabled, rounds: jpxChainRounds, jpx: jpxPresent(), maxRounds: CFG.chainMaxRounds, minHP: CFG.chainMinHP }), advice: () => { const s = detectFightingStyle(); return { style: s, attrs: getAttrAdvice(s), spells: getSpellAdvice(), difficulty: getDifficultyAdvice() }; @@ -3805,8 +3814,16 @@ window.HV = { document.getElementById('hvut') || window.HVUT || document.querySelector('[class*="hvut"], [id*="hvut"]')), + jpx: jpxPresent(), unified: true, }, + // jpx bridge state + jpxBridge: { + chainEnabled: jpxChainEnabled, + chainRounds: jpxChainRounds, + jpxLastActive: jpxLastActive, + retryCount: jpxChainRetryCount || 0, + }, // State availability state_ready: !!STATE && !!STATE.page, monsters: STATE.monsters ? STATE.monsters.length : null, diff --git a/src/config.js b/src/config.js index 34bbb8f..f40e4f4 100644 --- a/src/config.js +++ b/src/config.js @@ -2,7 +2,7 @@ // CONFIG — default settings // ═══════════════════════════════════════════════════════════════════════ -const VERSION = '0.17.1'; +const VERSION = '0.17.2'; const CFG = { // — Battle strategy advisory diff --git a/src/header.user.js b/src/header.user.js index d1c927c..012741e 100644 --- a/src/header.user.js +++ b/src/header.user.js @@ -1,7 +1,7 @@ // ==UserScript== // @name HV Unified // @namespace hvunified -// @version 0.17.1 +// @version 0.17.2 // @description HV Unified — Bridge & Strategy/Gear Advisor for HentaiVerse (complements Monsterbation & HVUtils) // @author GaboGG + Hermes // @match *://*.hentaiverse.org/* diff --git a/src/jpx-bridge.js b/src/jpx-bridge.js index 877242d..88c6ad5 100644 --- a/src/jpx-bridge.js +++ b/src/jpx-bridge.js @@ -21,6 +21,29 @@ function jpxPresent() { document.querySelector('#ctrl-widget, .ctrl-widget')); } +// Lazy-start: retry until jpx's widget actually appears (it's created lazily +// by jpx after its init, so our first check may be too early). +function jpxChainStart() { + if (!CFG.autoChainJpx) return; + if (jpxChainEnabled) return; + if (!jpxPresent()) { + // Retry a few times over ~6s before giving up + if (!jpxChainRetryCount) jpxChainRetryCount = 0; + if (jpxChainRetryCount < 10) { + jpxChainRetryCount++; + setTimeout(jpxChainStart, 600); + } + return; + } + jpxChainRetryCount = 0; + jpxChainEnabled = true; + jpxChainRounds = 0; + jpxChainCooldown = Date.now(); + // Start the first auto round (equivalent to pressing M once) + setTimeout(jpxTriggerAuto, 800); + console.log('%c[HV] ⛓ jpx auto-chain engaged (re-triggering M each round)', 'color:#0f0'); +} + // Trigger jpx's auto-battle toggle (equivalent to pressing M). // jpx listens for keydown on document (capture phase) and maps key 'm' to // toggleActive via userKeybinds. A synthetic KeyboardEvent works because @@ -56,20 +79,6 @@ function jpxBattleOver() { return !jpxMonstersPresent(); } -// Start the chain (called after battle initializes) -function jpxChainStart() { - if (!CFG.autoChainJpx) return; - if (jpxChainEnabled) return; - if (!jpxPresent()) return; - - jpxChainEnabled = true; - jpxChainRounds = 0; - jpxChainCooldown = Date.now(); - // Start the first auto round (equivalent to pressing M once) - setTimeout(jpxTriggerAuto, 800); - console.log('%c[HV] ⛓ jpx auto-chain engaged (re-triggering M each round)', 'color:#0f0'); -} - // Called when jpx reports a state change via jpx_ctrlWidget_update function jpxChainOnState(detail) { if (!CFG.autoChainJpx) return; diff --git a/src/public-api.js b/src/public-api.js index 0d3dc6f..8ad584f 100644 --- a/src/public-api.js +++ b/src/public-api.js @@ -12,8 +12,8 @@ window.HV = { stats: () => JSON.parse(localStorage[SP + 'stats'] || '{"battles":0,"credits":0,"drops":0}'), settings: () => toggleSettings(), // jpx bridge control - chain: (on) => { CFG.autoChainJpx = !!on; saveConfig(); if (on) { jpxChainStart(); if (!jpxChainTimer) jpxChainTimer = setInterval(jpxChainTick, 1500); } else { jpxChainStop('manual'); } return 'jpx chain: ' + CFG.autoChainJpx; }, - chainState: () => ({ active: jpxChainStarted, rounds: jpxChainRounds, jpx: jpxPresent(), maxRounds: CFG.chainMaxRounds, minHP: CFG.chainMinHP }), + chain: (on) => { CFG.autoChainJpx = !!on; saveConfig(); if (on) { jpxBridgeWire(); jpxChainStart(); } else { jpxChainStop('manual'); } return 'jpx chain: ' + CFG.autoChainJpx; }, + chainState: () => ({ active: jpxChainEnabled, rounds: jpxChainRounds, jpx: jpxPresent(), maxRounds: CFG.chainMaxRounds, minHP: CFG.chainMinHP }), advice: () => { const s = detectFightingStyle(); return { style: s, attrs: getAttrAdvice(s), spells: getSpellAdvice(), difficulty: getDifficultyAdvice() }; @@ -52,8 +52,16 @@ window.HV = { document.getElementById('hvut') || window.HVUT || document.querySelector('[class*="hvut"], [id*="hvut"]')), + jpx: jpxPresent(), unified: true, }, + // jpx bridge state + jpxBridge: { + chainEnabled: jpxChainEnabled, + chainRounds: jpxChainRounds, + jpxLastActive: jpxLastActive, + retryCount: jpxChainRetryCount || 0, + }, // State availability state_ready: !!STATE && !!STATE.page, monsters: STATE.monsters ? STATE.monsters.length : null,