From 1a4f9a80a31f22d5567267cffed6487ca28a3453 Mon Sep 17 00:00:00 2001 From: GaboGG Date: Sun, 26 Jul 2026 15:23:04 -0400 Subject: [PATCH] v0.13.26 - Stricter Q debounce: reset timer after each successful action - executeAction now sets STATE._lastActionTime after dispatching - Q handler checks this instead of a local var - When an action executes, the Q debounce resets so the next Q press waits the full 300ms before attempting again - Prevents stale-state queueing when game hasn't processed the previous action yet (was sending 12+ 'skill' commands to the game, though only 1 actually ran) --- scripts/hv-unified.user.js | 28 +++++++++++++++++----------- scripts/latest.user.js | 28 +++++++++++++++++----------- src/action-executor.js | 18 ++++++++++++------ src/config.js | 2 +- src/header.user.js | 2 +- src/keybindings.js | 5 ++--- src/state.js | 1 + 7 files changed, 51 insertions(+), 33 deletions(-) diff --git a/scripts/hv-unified.user.js b/scripts/hv-unified.user.js index d444f87..9baa45b 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.13.25 +// @version 0.13.26 // @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.13.25'; +const VERSION = '0.13.26'; const CFG = { // — Battle automation @@ -78,6 +78,7 @@ const STATE = { battleInitialized: false, _mysticUsed: false, // Track if Mystic Gem has been used this battle + _lastActionTime: 0, // Timestamp of last dispatched action (for Q debounce) _baseMpCosts: {}, // Cached base MP costs for buff spells (read from magic pane at battle start) @@ -1418,14 +1419,20 @@ function strategyVeteran() { function executeAction(a) { if (!a) return false; + let result = false; switch (a.type) { - case 'attack': return attackMonster(a.target); - case 'spell': return a.selfTarget ? castSelfSpell(a.name) : castTargetSpell(a.name, a.target); - case 'skill': return useSkill(a.name, a.target); - case 'item': return useItem(a.id); - case 'toggle_spirit': return toggleSpiritStance(); + case 'attack': result = attackMonster(a.target); break; + case 'spell': result = a.selfTarget ? castSelfSpell(a.name) : castTargetSpell(a.name, a.target); break; + case 'skill': result = useSkill(a.name, a.target); break; + case 'item': result = useItem(a.id); break; + case 'toggle_spirit': result = toggleSpiritStance(); break; } - return false; + if (result) { + // After dispatching an action, stagger the debounce so we don't queue + // another action while the game processes this one (typically ~500-1000ms) + STATE._lastActionTime = Date.now(); + } + return result; } function attackMonster(i) { @@ -1549,7 +1556,6 @@ function setupHover() { // ═══════════════════════════════════════════════════════════════════════ let keybindingsSetup = false; -let _lastQTime = 0; const Q_DEBOUNCE_MS = 300; // Ignore Q repeats faster than this function setupKeybindings() { @@ -1572,8 +1578,8 @@ function setupKeybindings() { if (!isInputFocused() && isBattlePage()) { // Debounce: ignore key-repeats until game processes the action const now = Date.now(); - if (now - _lastQTime < Q_DEBOUNCE_MS) return; - _lastQTime = now; + if (now - (STATE._lastActionTime || 0) < Q_DEBOUNCE_MS) return; + STATE._lastActionTime = now; console.log('%c[HV] Q pressed — checking action...', 'color:#888'); parseBattleState(); diff --git a/scripts/latest.user.js b/scripts/latest.user.js index d444f87..9baa45b 100644 --- a/scripts/latest.user.js +++ b/scripts/latest.user.js @@ -1,7 +1,7 @@ // ==UserScript== // @name HV Unified // @namespace hvunified -// @version 0.13.25 +// @version 0.13.26 // @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.13.25'; +const VERSION = '0.13.26'; const CFG = { // — Battle automation @@ -78,6 +78,7 @@ const STATE = { battleInitialized: false, _mysticUsed: false, // Track if Mystic Gem has been used this battle + _lastActionTime: 0, // Timestamp of last dispatched action (for Q debounce) _baseMpCosts: {}, // Cached base MP costs for buff spells (read from magic pane at battle start) @@ -1418,14 +1419,20 @@ function strategyVeteran() { function executeAction(a) { if (!a) return false; + let result = false; switch (a.type) { - case 'attack': return attackMonster(a.target); - case 'spell': return a.selfTarget ? castSelfSpell(a.name) : castTargetSpell(a.name, a.target); - case 'skill': return useSkill(a.name, a.target); - case 'item': return useItem(a.id); - case 'toggle_spirit': return toggleSpiritStance(); + case 'attack': result = attackMonster(a.target); break; + case 'spell': result = a.selfTarget ? castSelfSpell(a.name) : castTargetSpell(a.name, a.target); break; + case 'skill': result = useSkill(a.name, a.target); break; + case 'item': result = useItem(a.id); break; + case 'toggle_spirit': result = toggleSpiritStance(); break; } - return false; + if (result) { + // After dispatching an action, stagger the debounce so we don't queue + // another action while the game processes this one (typically ~500-1000ms) + STATE._lastActionTime = Date.now(); + } + return result; } function attackMonster(i) { @@ -1549,7 +1556,6 @@ function setupHover() { // ═══════════════════════════════════════════════════════════════════════ let keybindingsSetup = false; -let _lastQTime = 0; const Q_DEBOUNCE_MS = 300; // Ignore Q repeats faster than this function setupKeybindings() { @@ -1572,8 +1578,8 @@ function setupKeybindings() { if (!isInputFocused() && isBattlePage()) { // Debounce: ignore key-repeats until game processes the action const now = Date.now(); - if (now - _lastQTime < Q_DEBOUNCE_MS) return; - _lastQTime = now; + if (now - (STATE._lastActionTime || 0) < Q_DEBOUNCE_MS) return; + STATE._lastActionTime = now; console.log('%c[HV] Q pressed — checking action...', 'color:#888'); parseBattleState(); diff --git a/src/action-executor.js b/src/action-executor.js index 2628b5f..8463892 100644 --- a/src/action-executor.js +++ b/src/action-executor.js @@ -4,14 +4,20 @@ function executeAction(a) { if (!a) return false; + let result = false; switch (a.type) { - case 'attack': return attackMonster(a.target); - case 'spell': return a.selfTarget ? castSelfSpell(a.name) : castTargetSpell(a.name, a.target); - case 'skill': return useSkill(a.name, a.target); - case 'item': return useItem(a.id); - case 'toggle_spirit': return toggleSpiritStance(); + case 'attack': result = attackMonster(a.target); break; + case 'spell': result = a.selfTarget ? castSelfSpell(a.name) : castTargetSpell(a.name, a.target); break; + case 'skill': result = useSkill(a.name, a.target); break; + case 'item': result = useItem(a.id); break; + case 'toggle_spirit': result = toggleSpiritStance(); break; } - return false; + if (result) { + // After dispatching an action, stagger the debounce so we don't queue + // another action while the game processes this one (typically ~500-1000ms) + STATE._lastActionTime = Date.now(); + } + return result; } function attackMonster(i) { diff --git a/src/config.js b/src/config.js index 75ad03b..19605e8 100644 --- a/src/config.js +++ b/src/config.js @@ -2,7 +2,7 @@ // CONFIG — default settings // ═══════════════════════════════════════════════════════════════════════ -const VERSION = '0.13.25'; +const VERSION = '0.13.26'; const CFG = { // — Battle automation diff --git a/src/header.user.js b/src/header.user.js index bdfda2b..37374f0 100644 --- a/src/header.user.js +++ b/src/header.user.js @@ -1,7 +1,7 @@ // ==UserScript== // @name HV Unified // @namespace hvunified -// @version 0.13.25 +// @version 0.13.26 // @description HV Unified — battle automation, guidance, and UI enhancements for HentaiVerse // @author GaboGG + Hermes // @match *://*.hentaiverse.org/* diff --git a/src/keybindings.js b/src/keybindings.js index 2ab4da5..9c8a13f 100644 --- a/src/keybindings.js +++ b/src/keybindings.js @@ -3,7 +3,6 @@ // ═══════════════════════════════════════════════════════════════════════ let keybindingsSetup = false; -let _lastQTime = 0; const Q_DEBOUNCE_MS = 300; // Ignore Q repeats faster than this function setupKeybindings() { @@ -26,8 +25,8 @@ function setupKeybindings() { if (!isInputFocused() && isBattlePage()) { // Debounce: ignore key-repeats until game processes the action const now = Date.now(); - if (now - _lastQTime < Q_DEBOUNCE_MS) return; - _lastQTime = now; + if (now - (STATE._lastActionTime || 0) < Q_DEBOUNCE_MS) return; + STATE._lastActionTime = now; console.log('%c[HV] Q pressed — checking action...', 'color:#888'); parseBattleState(); diff --git a/src/state.js b/src/state.js index a35500e..784c07d 100644 --- a/src/state.js +++ b/src/state.js @@ -14,6 +14,7 @@ const STATE = { battleInitialized: false, _mysticUsed: false, // Track if Mystic Gem has been used this battle + _lastActionTime: 0, // Timestamp of last dispatched action (for Q debounce) _baseMpCosts: {}, // Cached base MP costs for buff spells (read from magic pane at battle start)