v0.13.22 - Cache base MP costs at battle start (dynamic, not hardcoded)
- Removed SPELL_COSTS hardcoded table - Base MP costs are now read from the magic pane's onmouseover tooltip at battle init and cached in STATE._baseMpCosts - Cache is populated during parseBattleState() and persists for the entire battle, avoiding channeling's 1-MP display - Cache resets on new battle, so level-ups or stat changes are picked up when starting a new arena
This commit is contained in:
parent
80fc77600a
commit
31799a7261
8 changed files with 78 additions and 45 deletions
|
|
@ -1,7 +1,7 @@
|
||||||
// ==UserScript==
|
// ==UserScript==
|
||||||
// @name HV Unified
|
// @name HV Unified
|
||||||
// @namespace hvunified
|
// @namespace hvunified
|
||||||
// @version 0.13.21
|
// @version 0.13.22
|
||||||
// @description HV Unified — battle automation, guidance, and UI enhancements for HentaiVerse
|
// @description HV Unified — battle automation, guidance, and UI enhancements for HentaiVerse
|
||||||
// @author GaboGG + Hermes
|
// @author GaboGG + Hermes
|
||||||
// @match *://*.hentaiverse.org/*
|
// @match *://*.hentaiverse.org/*
|
||||||
|
|
@ -17,7 +17,7 @@
|
||||||
// CONFIG — default settings
|
// CONFIG — default settings
|
||||||
// ═══════════════════════════════════════════════════════════════════════
|
// ═══════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
const VERSION = '0.13.21';
|
const VERSION = '0.13.22';
|
||||||
|
|
||||||
const CFG = {
|
const CFG = {
|
||||||
// — Battle automation
|
// — Battle automation
|
||||||
|
|
@ -79,6 +79,8 @@ const STATE = {
|
||||||
|
|
||||||
_mysticUsed: false, // Track if Mystic Gem has been used this battle
|
_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
|
maxBuffDur: {}, // Tracks highest duration seen per buff icon
|
||||||
|
|
||||||
hoverTarget: -1,
|
hoverTarget: -1,
|
||||||
|
|
@ -433,7 +435,13 @@ function parseBattleState() {
|
||||||
const omo = el.getAttribute('onmouseover') || '';
|
const omo = el.getAttribute('onmouseover') || '';
|
||||||
if (!omo) return;
|
if (!omo) return;
|
||||||
const m = omo.match(/battle\.set_infopane_spell\('([^']+)',\s*'[^']*',\s*'[^']*',\s*(\d+),\s*(\d+),\s*(\d+)\)/);
|
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);
|
const dur = buffDuration(b.icon);
|
||||||
if (b.icon === 'regen' && STATE.hp >= CFG.cureRegenHP) continue;
|
if (b.icon === 'regen' && STATE.hp >= CFG.cureRegenHP) continue;
|
||||||
const sp = findSpell(b.spell);
|
const sp = findSpell(b.spell);
|
||||||
// Read base MP cost from the magic pane tooltip (always the full base cost,
|
// Use cached base MP cost (snapshotted at battle start from the magic pane)
|
||||||
// unaffected by channeling's 1-MP display or Mana Conservation)
|
// This avoids the channeling 1-MP display issue and Mana Conservation fluctuation
|
||||||
let baseCost = 0;
|
let mpCost = STATE._baseMpCosts[b.spell] || 0;
|
||||||
const paneEl = $$('.btsd[onmouseover*="' + b.spell.replace(/'/g, "\\'") + '"]',
|
if (mpCost === 0 && sp) {
|
||||||
document.getElementById('pane_magic'))[0];
|
// Not cached yet — read from magic pane directly
|
||||||
if (paneEl) {
|
const paneEl = $$('.btsd[onmouseover*="' + b.spell.replace(/'/g, "\\'") + '"]',
|
||||||
const costM = (paneEl.getAttribute('onmouseover') || '')
|
document.getElementById('pane_magic'))[0];
|
||||||
.match(/set_infopane_spell\('[^']+',\s*'[^']*',\s*'[^']*',\s*(\d+)/);
|
if (paneEl) {
|
||||||
if (costM) baseCost = parseInt(costM[1]);
|
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
|
// Skip if we can't afford the base cost — game pre-checks even when
|
||||||
// channeling makes the effective cost 1 MP
|
// channeling makes the effective cost 1 MP
|
||||||
const maxMp = parseInt((document.getElementById('vrhd') || {}).textContent) || 414;
|
const maxMp = parseInt((document.getElementById('vrhd') || {}).textContent) || 414;
|
||||||
|
|
@ -3317,6 +3327,7 @@ function initializeBattle() {
|
||||||
STATE.page = 'battle';
|
STATE.page = 'battle';
|
||||||
STATE.inBattle = true;
|
STATE.inBattle = true;
|
||||||
STATE._mysticUsed = false; // Reset Mystic Gem tracking on new battle
|
STATE._mysticUsed = false; // Reset Mystic Gem tracking on new battle
|
||||||
|
STATE._baseMpCosts = {}; // Reset base cost cache
|
||||||
|
|
||||||
parseBattleState();
|
parseBattleState();
|
||||||
// Force tier update from cached level (battle page may not have level_readout)
|
// Force tier update from cached level (battle page may not have level_readout)
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
// ==UserScript==
|
// ==UserScript==
|
||||||
// @name HV Unified
|
// @name HV Unified
|
||||||
// @namespace hvunified
|
// @namespace hvunified
|
||||||
// @version 0.13.21
|
// @version 0.13.22
|
||||||
// @description HV Unified — battle automation, guidance, and UI enhancements for HentaiVerse
|
// @description HV Unified — battle automation, guidance, and UI enhancements for HentaiVerse
|
||||||
// @author GaboGG + Hermes
|
// @author GaboGG + Hermes
|
||||||
// @match *://*.hentaiverse.org/*
|
// @match *://*.hentaiverse.org/*
|
||||||
|
|
@ -17,7 +17,7 @@
|
||||||
// CONFIG — default settings
|
// CONFIG — default settings
|
||||||
// ═══════════════════════════════════════════════════════════════════════
|
// ═══════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
const VERSION = '0.13.21';
|
const VERSION = '0.13.22';
|
||||||
|
|
||||||
const CFG = {
|
const CFG = {
|
||||||
// — Battle automation
|
// — Battle automation
|
||||||
|
|
@ -79,6 +79,8 @@ const STATE = {
|
||||||
|
|
||||||
_mysticUsed: false, // Track if Mystic Gem has been used this battle
|
_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
|
maxBuffDur: {}, // Tracks highest duration seen per buff icon
|
||||||
|
|
||||||
hoverTarget: -1,
|
hoverTarget: -1,
|
||||||
|
|
@ -433,7 +435,13 @@ function parseBattleState() {
|
||||||
const omo = el.getAttribute('onmouseover') || '';
|
const omo = el.getAttribute('onmouseover') || '';
|
||||||
if (!omo) return;
|
if (!omo) return;
|
||||||
const m = omo.match(/battle\.set_infopane_spell\('([^']+)',\s*'[^']*',\s*'[^']*',\s*(\d+),\s*(\d+),\s*(\d+)\)/);
|
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);
|
const dur = buffDuration(b.icon);
|
||||||
if (b.icon === 'regen' && STATE.hp >= CFG.cureRegenHP) continue;
|
if (b.icon === 'regen' && STATE.hp >= CFG.cureRegenHP) continue;
|
||||||
const sp = findSpell(b.spell);
|
const sp = findSpell(b.spell);
|
||||||
// Read base MP cost from the magic pane tooltip (always the full base cost,
|
// Use cached base MP cost (snapshotted at battle start from the magic pane)
|
||||||
// unaffected by channeling's 1-MP display or Mana Conservation)
|
// This avoids the channeling 1-MP display issue and Mana Conservation fluctuation
|
||||||
let baseCost = 0;
|
let mpCost = STATE._baseMpCosts[b.spell] || 0;
|
||||||
const paneEl = $$('.btsd[onmouseover*="' + b.spell.replace(/'/g, "\\'") + '"]',
|
if (mpCost === 0 && sp) {
|
||||||
document.getElementById('pane_magic'))[0];
|
// Not cached yet — read from magic pane directly
|
||||||
if (paneEl) {
|
const paneEl = $$('.btsd[onmouseover*="' + b.spell.replace(/'/g, "\\'") + '"]',
|
||||||
const costM = (paneEl.getAttribute('onmouseover') || '')
|
document.getElementById('pane_magic'))[0];
|
||||||
.match(/set_infopane_spell\('[^']+',\s*'[^']*',\s*'[^']*',\s*(\d+)/);
|
if (paneEl) {
|
||||||
if (costM) baseCost = parseInt(costM[1]);
|
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
|
// Skip if we can't afford the base cost — game pre-checks even when
|
||||||
// channeling makes the effective cost 1 MP
|
// channeling makes the effective cost 1 MP
|
||||||
const maxMp = parseInt((document.getElementById('vrhd') || {}).textContent) || 414;
|
const maxMp = parseInt((document.getElementById('vrhd') || {}).textContent) || 414;
|
||||||
|
|
@ -3317,6 +3327,7 @@ function initializeBattle() {
|
||||||
STATE.page = 'battle';
|
STATE.page = 'battle';
|
||||||
STATE.inBattle = true;
|
STATE.inBattle = true;
|
||||||
STATE._mysticUsed = false; // Reset Mystic Gem tracking on new battle
|
STATE._mysticUsed = false; // Reset Mystic Gem tracking on new battle
|
||||||
|
STATE._baseMpCosts = {}; // Reset base cost cache
|
||||||
|
|
||||||
parseBattleState();
|
parseBattleState();
|
||||||
// Force tier update from cached level (battle page may not have level_readout)
|
// Force tier update from cached level (battle page may not have level_readout)
|
||||||
|
|
|
||||||
|
|
@ -91,7 +91,13 @@ function parseBattleState() {
|
||||||
const omo = el.getAttribute('onmouseover') || '';
|
const omo = el.getAttribute('onmouseover') || '';
|
||||||
if (!omo) return;
|
if (!omo) return;
|
||||||
const m = omo.match(/battle\.set_infopane_spell\('([^']+)',\s*'[^']*',\s*'[^']*',\s*(\d+),\s*(\d+),\s*(\d+)\)/);
|
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]);
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
// CONFIG — default settings
|
// CONFIG — default settings
|
||||||
// ═══════════════════════════════════════════════════════════════════════
|
// ═══════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
const VERSION = '0.13.21';
|
const VERSION = '0.13.22';
|
||||||
|
|
||||||
const CFG = {
|
const CFG = {
|
||||||
// — Battle automation
|
// — Battle automation
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
// ==UserScript==
|
// ==UserScript==
|
||||||
// @name HV Unified
|
// @name HV Unified
|
||||||
// @namespace hvunified
|
// @namespace hvunified
|
||||||
// @version 0.13.21
|
// @version 0.13.22
|
||||||
// @description HV Unified — battle automation, guidance, and UI enhancements for HentaiVerse
|
// @description HV Unified — battle automation, guidance, and UI enhancements for HentaiVerse
|
||||||
// @author GaboGG + Hermes
|
// @author GaboGG + Hermes
|
||||||
// @match *://*.hentaiverse.org/*
|
// @match *://*.hentaiverse.org/*
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,7 @@ function initializeBattle() {
|
||||||
STATE.page = 'battle';
|
STATE.page = 'battle';
|
||||||
STATE.inBattle = true;
|
STATE.inBattle = true;
|
||||||
STATE._mysticUsed = false; // Reset Mystic Gem tracking on new battle
|
STATE._mysticUsed = false; // Reset Mystic Gem tracking on new battle
|
||||||
|
STATE._baseMpCosts = {}; // Reset base cost cache
|
||||||
|
|
||||||
parseBattleState();
|
parseBattleState();
|
||||||
// Force tier update from cached level (battle page may not have level_readout)
|
// Force tier update from cached level (battle page may not have level_readout)
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,8 @@ const STATE = {
|
||||||
|
|
||||||
_mysticUsed: false, // Track if Mystic Gem has been used this battle
|
_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
|
maxBuffDur: {}, // Tracks highest duration seen per buff icon
|
||||||
|
|
||||||
hoverTarget: -1,
|
hoverTarget: -1,
|
||||||
|
|
|
||||||
|
|
@ -238,19 +238,21 @@ function findBestChannelingTarget() {
|
||||||
const dur = buffDuration(b.icon);
|
const dur = buffDuration(b.icon);
|
||||||
if (b.icon === 'regen' && STATE.hp >= CFG.cureRegenHP) continue;
|
if (b.icon === 'regen' && STATE.hp >= CFG.cureRegenHP) continue;
|
||||||
const sp = findSpell(b.spell);
|
const sp = findSpell(b.spell);
|
||||||
// Read base MP cost from the magic pane tooltip (always the full base cost,
|
// Use cached base MP cost (snapshotted at battle start from the magic pane)
|
||||||
// unaffected by channeling's 1-MP display or Mana Conservation)
|
// This avoids the channeling 1-MP display issue and Mana Conservation fluctuation
|
||||||
let baseCost = 0;
|
let mpCost = STATE._baseMpCosts[b.spell] || 0;
|
||||||
const paneEl = $$('.btsd[onmouseover*="' + b.spell.replace(/'/g, "\\'") + '"]',
|
if (mpCost === 0 && sp) {
|
||||||
document.getElementById('pane_magic'))[0];
|
// Not cached yet — read from magic pane directly
|
||||||
if (paneEl) {
|
const paneEl = $$('.btsd[onmouseover*="' + b.spell.replace(/'/g, "\\'") + '"]',
|
||||||
const costM = (paneEl.getAttribute('onmouseover') || '')
|
document.getElementById('pane_magic'))[0];
|
||||||
.match(/set_infopane_spell\('[^']+',\s*'[^']*',\s*'[^']*',\s*(\d+)/);
|
if (paneEl) {
|
||||||
if (costM) baseCost = parseInt(costM[1]);
|
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
|
// Skip if we can't afford the base cost — game pre-checks even when
|
||||||
// channeling makes the effective cost 1 MP
|
// channeling makes the effective cost 1 MP
|
||||||
const maxMp = parseInt((document.getElementById('vrhd') || {}).textContent) || 414;
|
const maxMp = parseInt((document.getElementById('vrhd') || {}).textContent) || 414;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue