v0.13.21 - Dynamic base cost read + affordable MP check for channeling

- Removed hardcoded SPELL_COSTS table (was brittle)
- Now reads base MP cost directly from the magic pane's
  onmouseover tooltip (always shows true base cost, unaffected
  by channeling's 1-MP display or Mana Conservation)
- Added affordable check: skip spells where base cost exceeds
  current MP, even though channeling would make it cost 1
- The game's native MP check (shown by "You do not have enough")
  checks against base cost, not channeling-reduced cost
This commit is contained in:
GaboGG 2026-07-26 15:05:20 -04:00
parent e20fe0495c
commit 80fc77600a
5 changed files with 60 additions and 30 deletions

View file

@ -1,7 +1,7 @@
// ==UserScript==
// @name HV Unified
// @namespace hvunified
// @version 0.13.20
// @version 0.13.21
// @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.20';
const VERSION = '0.13.21';
const CFG = {
// — Battle automation
@ -915,15 +915,23 @@ function findBestChannelingTarget() {
const dur = buffDuration(b.icon);
if (b.icon === 'regen' && STATE.hp >= CFG.cureRegenHP) continue;
const sp = findSpell(b.spell);
// Use a reasonable MP cost: ignore Mana Conservation-reduced values
// (the DOM may show 1 MP at high proficiency, but the real base cost
// determines the savings from a free channeling cast)
let mpCost = sp ? sp.mp : 0;
if (mpCost > 0 && mpCost < 5 && sp) {
// Try to get base cost from knowledge-base
const kb = SPELL_COSTS[b.spell];
if (kb) mpCost = kb;
// 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]);
}
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;
if (mpCost > 0 && (mpCost / maxMp) > STATE.mp) continue;
// Fill level: how empty is this buff relative to its max observed duration
let maxDur = STATE.maxBuffDur[b.icon] || 0;
// If max matches current (first observation, no learning yet), assume min 30 turns
@ -1141,6 +1149,8 @@ function strategyAdept() {
if (ib) return ib;
}
// If channeling is active, prioritize most expensive buff for best value
// Note: channeling reduces cost to ~1, but the game pre-checks base MP,
// so we skip spells we can't afford at base cost
if (STATE.channeling && STATE.mp > 0.10) {
const ch = findBestChannelingTarget();
if (ch) return ch;

View file

@ -1,7 +1,7 @@
// ==UserScript==
// @name HV Unified
// @namespace hvunified
// @version 0.13.20
// @version 0.13.21
// @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.20';
const VERSION = '0.13.21';
const CFG = {
// — Battle automation
@ -915,15 +915,23 @@ function findBestChannelingTarget() {
const dur = buffDuration(b.icon);
if (b.icon === 'regen' && STATE.hp >= CFG.cureRegenHP) continue;
const sp = findSpell(b.spell);
// Use a reasonable MP cost: ignore Mana Conservation-reduced values
// (the DOM may show 1 MP at high proficiency, but the real base cost
// determines the savings from a free channeling cast)
let mpCost = sp ? sp.mp : 0;
if (mpCost > 0 && mpCost < 5 && sp) {
// Try to get base cost from knowledge-base
const kb = SPELL_COSTS[b.spell];
if (kb) mpCost = kb;
// 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]);
}
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;
if (mpCost > 0 && (mpCost / maxMp) > STATE.mp) continue;
// Fill level: how empty is this buff relative to its max observed duration
let maxDur = STATE.maxBuffDur[b.icon] || 0;
// If max matches current (first observation, no learning yet), assume min 30 turns
@ -1141,6 +1149,8 @@ function strategyAdept() {
if (ib) return ib;
}
// If channeling is active, prioritize most expensive buff for best value
// Note: channeling reduces cost to ~1, but the game pre-checks base MP,
// so we skip spells we can't afford at base cost
if (STATE.channeling && STATE.mp > 0.10) {
const ch = findBestChannelingTarget();
if (ch) return ch;

View file

@ -2,7 +2,7 @@
// CONFIG — default settings
// ═══════════════════════════════════════════════════════════════════════
const VERSION = '0.13.20';
const VERSION = '0.13.21';
const CFG = {
// — Battle automation

View file

@ -1,7 +1,7 @@
// ==UserScript==
// @name HV Unified
// @namespace hvunified
// @version 0.13.20
// @version 0.13.21
// @description HV Unified — battle automation, guidance, and UI enhancements for HentaiVerse
// @author GaboGG + Hermes
// @match *://*.hentaiverse.org/*

View file

@ -238,15 +238,23 @@ function findBestChannelingTarget() {
const dur = buffDuration(b.icon);
if (b.icon === 'regen' && STATE.hp >= CFG.cureRegenHP) continue;
const sp = findSpell(b.spell);
// Use a reasonable MP cost: ignore Mana Conservation-reduced values
// (the DOM may show 1 MP at high proficiency, but the real base cost
// determines the savings from a free channeling cast)
let mpCost = sp ? sp.mp : 0;
if (mpCost > 0 && mpCost < 5 && sp) {
// Try to get base cost from knowledge-base
const kb = SPELL_COSTS[b.spell];
if (kb) mpCost = kb;
// 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]);
}
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;
if (mpCost > 0 && (mpCost / maxMp) > STATE.mp) continue;
// Fill level: how empty is this buff relative to its max observed duration
let maxDur = STATE.maxBuffDur[b.icon] || 0;
// If max matches current (first observation, no learning yet), assume min 30 turns
@ -464,6 +472,8 @@ function strategyAdept() {
if (ib) return ib;
}
// If channeling is active, prioritize most expensive buff for best value
// Note: channeling reduces cost to ~1, but the game pre-checks base MP,
// so we skip spells we can't afford at base cost
if (STATE.channeling && STATE.mp > 0.10) {
const ch = findBestChannelingTarget();
if (ch) return ch;