diff --git a/scripts/hv-unified.user.js b/scripts/hv-unified.user.js index 72cf818..daf950f 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.21 +// @version 0.13.22 // @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.21'; +const VERSION = '0.13.22'; const CFG = { // — Battle automation @@ -79,6 +79,8 @@ const STATE = { _mysticUsed: false, // Track if Mystic Gem has been used this battle + _baseMpCosts: {}, // Cached base MP costs for buff spells (read from magic pane at battle start) + maxBuffDur: {}, // Tracks highest duration seen per buff icon hoverTarget: -1, @@ -433,7 +435,13 @@ function parseBattleState() { const omo = el.getAttribute('onmouseover') || ''; if (!omo) return; const m = omo.match(/battle\.set_infopane_spell\('([^']+)',\s*'[^']*',\s*'[^']*',\s*(\d+),\s*(\d+),\s*(\d+)\)/); - if (m) STATE.spellsKnown.push({ n: m[1], mp: parseInt(m[2]), chr: parseInt(m[3]), cd: parseInt(m[4]) }); + if (m) { + STATE.spellsKnown.push({ n: m[1], mp: parseInt(m[2]), chr: parseInt(m[3]), cd: parseInt(m[4]) }); + // Cache the base MP cost (from magic pane, unaffected by channeling/Conservation) + if (!STATE._baseMpCosts[m[1]]) { + STATE._baseMpCosts[m[1]] = parseInt(m[2]); + } + } }); } @@ -915,19 +923,21 @@ function findBestChannelingTarget() { const dur = buffDuration(b.icon); if (b.icon === 'regen' && STATE.hp >= CFG.cureRegenHP) continue; const sp = findSpell(b.spell); - // Read base MP cost from the magic pane tooltip (always the full base cost, - // unaffected by channeling's 1-MP display or Mana Conservation) - let baseCost = 0; - const paneEl = $$('.btsd[onmouseover*="' + b.spell.replace(/'/g, "\\'") + '"]', - document.getElementById('pane_magic'))[0]; - if (paneEl) { - const costM = (paneEl.getAttribute('onmouseover') || '') - .match(/set_infopane_spell\('[^']+',\s*'[^']*',\s*'[^']*',\s*(\d+)/); - if (costM) baseCost = parseInt(costM[1]); + // Use cached base MP cost (snapshotted at battle start from the magic pane) + // This avoids the channeling 1-MP display issue and Mana Conservation fluctuation + let mpCost = STATE._baseMpCosts[b.spell] || 0; + if (mpCost === 0 && sp) { + // Not cached yet — read from magic pane directly + const paneEl = $$('.btsd[onmouseover*="' + b.spell.replace(/'/g, "\\'") + '"]', + document.getElementById('pane_magic'))[0]; + if (paneEl) { + const costM = (paneEl.getAttribute('onmouseover') || '') + .match(/set_infopane_spell\('[^']+',\s*'[^']*',\s*'[^']*',\s*(\d+)/); + if (costM) mpCost = parseInt(costM[1]); + } + if (mpCost === 0) mpCost = sp.mp; // last resort: use what parseBattleState gave us + STATE._baseMpCosts[b.spell] = mpCost; } - let mpCost = sp ? sp.mp : 0; - // Use base cost from DOM instead of Mana Conservation-reduced cost - if (baseCost > 0) mpCost = baseCost; // Skip if we can't afford the base cost — game pre-checks even when // channeling makes the effective cost 1 MP const maxMp = parseInt((document.getElementById('vrhd') || {}).textContent) || 414; @@ -3317,6 +3327,7 @@ function initializeBattle() { STATE.page = 'battle'; STATE.inBattle = true; STATE._mysticUsed = false; // Reset Mystic Gem tracking on new battle + STATE._baseMpCosts = {}; // Reset base cost cache parseBattleState(); // Force tier update from cached level (battle page may not have level_readout) diff --git a/scripts/latest.user.js b/scripts/latest.user.js index 72cf818..daf950f 100644 --- a/scripts/latest.user.js +++ b/scripts/latest.user.js @@ -1,7 +1,7 @@ // ==UserScript== // @name HV Unified // @namespace hvunified -// @version 0.13.21 +// @version 0.13.22 // @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.21'; +const VERSION = '0.13.22'; const CFG = { // — Battle automation @@ -79,6 +79,8 @@ const STATE = { _mysticUsed: false, // Track if Mystic Gem has been used this battle + _baseMpCosts: {}, // Cached base MP costs for buff spells (read from magic pane at battle start) + maxBuffDur: {}, // Tracks highest duration seen per buff icon hoverTarget: -1, @@ -433,7 +435,13 @@ function parseBattleState() { const omo = el.getAttribute('onmouseover') || ''; if (!omo) return; const m = omo.match(/battle\.set_infopane_spell\('([^']+)',\s*'[^']*',\s*'[^']*',\s*(\d+),\s*(\d+),\s*(\d+)\)/); - if (m) STATE.spellsKnown.push({ n: m[1], mp: parseInt(m[2]), chr: parseInt(m[3]), cd: parseInt(m[4]) }); + if (m) { + STATE.spellsKnown.push({ n: m[1], mp: parseInt(m[2]), chr: parseInt(m[3]), cd: parseInt(m[4]) }); + // Cache the base MP cost (from magic pane, unaffected by channeling/Conservation) + if (!STATE._baseMpCosts[m[1]]) { + STATE._baseMpCosts[m[1]] = parseInt(m[2]); + } + } }); } @@ -915,19 +923,21 @@ function findBestChannelingTarget() { const dur = buffDuration(b.icon); if (b.icon === 'regen' && STATE.hp >= CFG.cureRegenHP) continue; const sp = findSpell(b.spell); - // Read base MP cost from the magic pane tooltip (always the full base cost, - // unaffected by channeling's 1-MP display or Mana Conservation) - let baseCost = 0; - const paneEl = $$('.btsd[onmouseover*="' + b.spell.replace(/'/g, "\\'") + '"]', - document.getElementById('pane_magic'))[0]; - if (paneEl) { - const costM = (paneEl.getAttribute('onmouseover') || '') - .match(/set_infopane_spell\('[^']+',\s*'[^']*',\s*'[^']*',\s*(\d+)/); - if (costM) baseCost = parseInt(costM[1]); + // Use cached base MP cost (snapshotted at battle start from the magic pane) + // This avoids the channeling 1-MP display issue and Mana Conservation fluctuation + let mpCost = STATE._baseMpCosts[b.spell] || 0; + if (mpCost === 0 && sp) { + // Not cached yet — read from magic pane directly + const paneEl = $$('.btsd[onmouseover*="' + b.spell.replace(/'/g, "\\'") + '"]', + document.getElementById('pane_magic'))[0]; + if (paneEl) { + const costM = (paneEl.getAttribute('onmouseover') || '') + .match(/set_infopane_spell\('[^']+',\s*'[^']*',\s*'[^']*',\s*(\d+)/); + if (costM) mpCost = parseInt(costM[1]); + } + if (mpCost === 0) mpCost = sp.mp; // last resort: use what parseBattleState gave us + STATE._baseMpCosts[b.spell] = mpCost; } - let mpCost = sp ? sp.mp : 0; - // Use base cost from DOM instead of Mana Conservation-reduced cost - if (baseCost > 0) mpCost = baseCost; // Skip if we can't afford the base cost — game pre-checks even when // channeling makes the effective cost 1 MP const maxMp = parseInt((document.getElementById('vrhd') || {}).textContent) || 414; @@ -3317,6 +3327,7 @@ function initializeBattle() { STATE.page = 'battle'; STATE.inBattle = true; STATE._mysticUsed = false; // Reset Mystic Gem tracking on new battle + STATE._baseMpCosts = {}; // Reset base cost cache parseBattleState(); // Force tier update from cached level (battle page may not have level_readout) diff --git a/src/battle-parser.js b/src/battle-parser.js index a5082b0..aabcacf 100644 --- a/src/battle-parser.js +++ b/src/battle-parser.js @@ -91,7 +91,13 @@ function parseBattleState() { const omo = el.getAttribute('onmouseover') || ''; if (!omo) return; const m = omo.match(/battle\.set_infopane_spell\('([^']+)',\s*'[^']*',\s*'[^']*',\s*(\d+),\s*(\d+),\s*(\d+)\)/); - if (m) STATE.spellsKnown.push({ n: m[1], mp: parseInt(m[2]), chr: parseInt(m[3]), cd: parseInt(m[4]) }); + if (m) { + STATE.spellsKnown.push({ n: m[1], mp: parseInt(m[2]), chr: parseInt(m[3]), cd: parseInt(m[4]) }); + // Cache the base MP cost (from magic pane, unaffected by channeling/Conservation) + if (!STATE._baseMpCosts[m[1]]) { + STATE._baseMpCosts[m[1]] = parseInt(m[2]); + } + } }); } diff --git a/src/config.js b/src/config.js index 89a26f4..3e1b810 100644 --- a/src/config.js +++ b/src/config.js @@ -2,7 +2,7 @@ // CONFIG — default settings // ═══════════════════════════════════════════════════════════════════════ -const VERSION = '0.13.21'; +const VERSION = '0.13.22'; const CFG = { // — Battle automation diff --git a/src/header.user.js b/src/header.user.js index af55819..c352ef9 100644 --- a/src/header.user.js +++ b/src/header.user.js @@ -1,7 +1,7 @@ // ==UserScript== // @name HV Unified // @namespace hvunified -// @version 0.13.21 +// @version 0.13.22 // @description HV Unified — battle automation, guidance, and UI enhancements for HentaiVerse // @author GaboGG + Hermes // @match *://*.hentaiverse.org/* diff --git a/src/init.js b/src/init.js index d86382b..f08bf83 100644 --- a/src/init.js +++ b/src/init.js @@ -50,6 +50,7 @@ function initializeBattle() { STATE.page = 'battle'; STATE.inBattle = true; STATE._mysticUsed = false; // Reset Mystic Gem tracking on new battle + STATE._baseMpCosts = {}; // Reset base cost cache parseBattleState(); // Force tier update from cached level (battle page may not have level_readout) diff --git a/src/state.js b/src/state.js index c60dafb..a35500e 100644 --- a/src/state.js +++ b/src/state.js @@ -15,6 +15,8 @@ const STATE = { _mysticUsed: false, // Track if Mystic Gem has been used this battle + _baseMpCosts: {}, // Cached base MP costs for buff spells (read from magic pane at battle start) + maxBuffDur: {}, // Tracks highest duration seen per buff icon hoverTarget: -1, diff --git a/src/strategy-engine.js b/src/strategy-engine.js index a0dc481..513c687 100644 --- a/src/strategy-engine.js +++ b/src/strategy-engine.js @@ -238,19 +238,21 @@ function findBestChannelingTarget() { const dur = buffDuration(b.icon); if (b.icon === 'regen' && STATE.hp >= CFG.cureRegenHP) continue; const sp = findSpell(b.spell); - // Read base MP cost from the magic pane tooltip (always the full base cost, - // unaffected by channeling's 1-MP display or Mana Conservation) - let baseCost = 0; - const paneEl = $$('.btsd[onmouseover*="' + b.spell.replace(/'/g, "\\'") + '"]', - document.getElementById('pane_magic'))[0]; - if (paneEl) { - const costM = (paneEl.getAttribute('onmouseover') || '') - .match(/set_infopane_spell\('[^']+',\s*'[^']*',\s*'[^']*',\s*(\d+)/); - if (costM) baseCost = parseInt(costM[1]); + // Use cached base MP cost (snapshotted at battle start from the magic pane) + // This avoids the channeling 1-MP display issue and Mana Conservation fluctuation + let mpCost = STATE._baseMpCosts[b.spell] || 0; + if (mpCost === 0 && sp) { + // Not cached yet — read from magic pane directly + const paneEl = $$('.btsd[onmouseover*="' + b.spell.replace(/'/g, "\\'") + '"]', + document.getElementById('pane_magic'))[0]; + if (paneEl) { + const costM = (paneEl.getAttribute('onmouseover') || '') + .match(/set_infopane_spell\('[^']+',\s*'[^']*',\s*'[^']*',\s*(\d+)/); + if (costM) mpCost = parseInt(costM[1]); + } + if (mpCost === 0) mpCost = sp.mp; // last resort: use what parseBattleState gave us + STATE._baseMpCosts[b.spell] = mpCost; } - let mpCost = sp ? sp.mp : 0; - // Use base cost from DOM instead of Mana Conservation-reduced cost - if (baseCost > 0) mpCost = baseCost; // Skip if we can't afford the base cost — game pre-checks even when // channeling makes the effective cost 1 MP const maxMp = parseInt((document.getElementById('vrhd') || {}).textContent) || 414;