v0.14.4 - Sort buff loop by cost (cheapest-first when no channeling)

- Regular buff loop in all 3 tiers now collects candidates then sorts
  by MP cost ascending (cheapest first) when no channeling, or
  descending (most expensive first) when channeling is active
- Previously just iterated BUFF_PRIORITY declaration order (Haste →
  Spark → Heartseeker → Regen → Absorb → Shadow Veil) which wasn't
  cost-ordered
- _reason now shows 'buff' (cheapest first) or 'chBuff' (most expensive)
This commit is contained in:
GaboGG 2026-07-27 14:45:00 -04:00
parent 48e5103655
commit 0a41f9d7c5
5 changed files with 84 additions and 18 deletions

View file

@ -1,7 +1,7 @@
// ==UserScript== // ==UserScript==
// @name HV Unified // @name HV Unified
// @namespace hvunified // @namespace hvunified
// @version 0.14.3 // @version 0.14.4
// @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.14.3'; const VERSION = '0.14.4';
const CFG = { const CFG = {
// — Battle automation // — Battle automation
@ -1155,12 +1155,19 @@ function strategyNovice() {
const ch = findBestChannelingTarget(); const ch = findBestChannelingTarget();
if (ch) return ch; if (ch) return ch;
} }
// Collect available buff candidates and sort by cost
const buffCandidates = [];
for (const b of BUFF_PRIORITY) { for (const b of BUFF_PRIORITY) {
if (shouldBuff(b.icon, b.minTurns) && STATE.mp > 0.10 && hasReadySpell(b.spell)) { if (shouldBuff(b.icon, b.minTurns) && STATE.mp > 0.10 && hasReadySpell(b.spell)) {
if (b.icon === 'regen' && STATE.hp >= CFG.cureRegenHP) continue; if (b.icon === 'regen' && STATE.hp >= CFG.cureRegenHP) continue;
return { type: 'spell', name: b.spell, selfTarget: true }; const sp = findSpell(b.spell);
buffCandidates.push({ ...b, mpCost: sp ? sp.mp : 0 });
} }
} }
if (buffCandidates.length > 0) {
buffCandidates.sort((a, b) => STATE.channeling ? b.mpCost - a.mpCost : a.mpCost - b.mpCost);
return { type: 'spell', name: buffCandidates[0].spell, selfTarget: true, _reason: STATE.channeling ? 'chBuff' : 'buff' };
}
// 4. Mana/spirit items — AFTER buffs (Novice) // 4. Mana/spirit items — AFTER buffs (Novice)
if (STATE.mp < CFG.manaGemMP) { if (STATE.mp < CFG.manaGemMP) {
@ -1291,17 +1298,24 @@ function strategyAdept() {
} }
// If channeling is active, prioritize most expensive buff for best value // If channeling is active, prioritize most expensive buff for best value
// Note: channeling reduces cost to ~1, but the game pre-checks base MP, // 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 channeling is active, prioritize most expensive buff for best value
if (STATE.channeling && STATE.mp > 0.10) { if (STATE.channeling && STATE.mp > 0.10) {
const ch = findBestChannelingTarget(); const ch = findBestChannelingTarget();
if (ch) return ch; if (ch) return ch;
} }
// Collect available buff candidates and sort by cost
const buffCandidates = [];
for (const b of BUFF_PRIORITY) { for (const b of BUFF_PRIORITY) {
if (shouldBuff(b.icon, b.minTurns) && STATE.mp > 0.10 && hasReadySpell(b.spell)) { if (shouldBuff(b.icon, b.minTurns) && STATE.mp > 0.10 && hasReadySpell(b.spell)) {
if (b.icon === 'regen' && STATE.hp >= CFG.cureRegenHP) continue; if (b.icon === 'regen' && STATE.hp >= CFG.cureRegenHP) continue;
return { type: 'spell', name: b.spell, selfTarget: true }; const sp = findSpell(b.spell);
buffCandidates.push({ ...b, mpCost: sp ? sp.mp : 0 });
} }
} }
if (buffCandidates.length > 0) {
buffCandidates.sort((a, b) => STATE.channeling ? b.mpCost - a.mpCost : a.mpCost - b.mpCost);
return { type: 'spell', name: buffCandidates[0].spell, selfTarget: true, _reason: STATE.channeling ? 'chBuff' : 'buff' };
}
// 4. Mana/spirit items — AFTER buffs (Adept) // 4. Mana/spirit items — AFTER buffs (Adept)
if (STATE.mp < CFG.manaGemMP) { if (STATE.mp < CFG.manaGemMP) {
@ -1447,12 +1461,20 @@ function strategyVeteran() {
const ch = findBestChannelingTarget(); const ch = findBestChannelingTarget();
if (ch) return ch; if (ch) return ch;
} }
// Collect available buff candidates and sort by cost
// No channeling → cheapest first (proc chance), channeling → most expensive (MP value)
const buffCandidates = [];
for (const b of BUFF_PRIORITY) { for (const b of BUFF_PRIORITY) {
if (shouldBuff(b.icon, b.minTurns) && STATE.mp > 0.10 && hasReadySpell(b.spell)) { if (shouldBuff(b.icon, b.minTurns) && STATE.mp > 0.10 && hasReadySpell(b.spell)) {
if (b.icon === 'regen' && STATE.hp >= CFG.cureRegenHP) continue; if (b.icon === 'regen' && STATE.hp >= CFG.cureRegenHP) continue;
return { type: 'spell', name: b.spell, selfTarget: true, _reason: 'buff' }; const sp = findSpell(b.spell);
buffCandidates.push({ ...b, mpCost: sp ? sp.mp : 0 });
} }
} }
if (buffCandidates.length > 0) {
buffCandidates.sort((a, b) => STATE.channeling ? b.mpCost - a.mpCost : a.mpCost - b.mpCost);
return { type: 'spell', name: buffCandidates[0].spell, selfTarget: true, _reason: STATE.channeling ? 'chBuff' : 'buff' };
}
} }
// 4. Mana/spirit items — AFTER buffs (Novice) // 4. Mana/spirit items — AFTER buffs (Novice)

View file

@ -1,7 +1,7 @@
// ==UserScript== // ==UserScript==
// @name HV Unified // @name HV Unified
// @namespace hvunified // @namespace hvunified
// @version 0.14.3 // @version 0.14.4
// @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.14.3'; const VERSION = '0.14.4';
const CFG = { const CFG = {
// — Battle automation // — Battle automation
@ -1155,12 +1155,19 @@ function strategyNovice() {
const ch = findBestChannelingTarget(); const ch = findBestChannelingTarget();
if (ch) return ch; if (ch) return ch;
} }
// Collect available buff candidates and sort by cost
const buffCandidates = [];
for (const b of BUFF_PRIORITY) { for (const b of BUFF_PRIORITY) {
if (shouldBuff(b.icon, b.minTurns) && STATE.mp > 0.10 && hasReadySpell(b.spell)) { if (shouldBuff(b.icon, b.minTurns) && STATE.mp > 0.10 && hasReadySpell(b.spell)) {
if (b.icon === 'regen' && STATE.hp >= CFG.cureRegenHP) continue; if (b.icon === 'regen' && STATE.hp >= CFG.cureRegenHP) continue;
return { type: 'spell', name: b.spell, selfTarget: true }; const sp = findSpell(b.spell);
buffCandidates.push({ ...b, mpCost: sp ? sp.mp : 0 });
} }
} }
if (buffCandidates.length > 0) {
buffCandidates.sort((a, b) => STATE.channeling ? b.mpCost - a.mpCost : a.mpCost - b.mpCost);
return { type: 'spell', name: buffCandidates[0].spell, selfTarget: true, _reason: STATE.channeling ? 'chBuff' : 'buff' };
}
// 4. Mana/spirit items — AFTER buffs (Novice) // 4. Mana/spirit items — AFTER buffs (Novice)
if (STATE.mp < CFG.manaGemMP) { if (STATE.mp < CFG.manaGemMP) {
@ -1291,17 +1298,24 @@ function strategyAdept() {
} }
// If channeling is active, prioritize most expensive buff for best value // If channeling is active, prioritize most expensive buff for best value
// Note: channeling reduces cost to ~1, but the game pre-checks base MP, // 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 channeling is active, prioritize most expensive buff for best value
if (STATE.channeling && STATE.mp > 0.10) { if (STATE.channeling && STATE.mp > 0.10) {
const ch = findBestChannelingTarget(); const ch = findBestChannelingTarget();
if (ch) return ch; if (ch) return ch;
} }
// Collect available buff candidates and sort by cost
const buffCandidates = [];
for (const b of BUFF_PRIORITY) { for (const b of BUFF_PRIORITY) {
if (shouldBuff(b.icon, b.minTurns) && STATE.mp > 0.10 && hasReadySpell(b.spell)) { if (shouldBuff(b.icon, b.minTurns) && STATE.mp > 0.10 && hasReadySpell(b.spell)) {
if (b.icon === 'regen' && STATE.hp >= CFG.cureRegenHP) continue; if (b.icon === 'regen' && STATE.hp >= CFG.cureRegenHP) continue;
return { type: 'spell', name: b.spell, selfTarget: true }; const sp = findSpell(b.spell);
buffCandidates.push({ ...b, mpCost: sp ? sp.mp : 0 });
} }
} }
if (buffCandidates.length > 0) {
buffCandidates.sort((a, b) => STATE.channeling ? b.mpCost - a.mpCost : a.mpCost - b.mpCost);
return { type: 'spell', name: buffCandidates[0].spell, selfTarget: true, _reason: STATE.channeling ? 'chBuff' : 'buff' };
}
// 4. Mana/spirit items — AFTER buffs (Adept) // 4. Mana/spirit items — AFTER buffs (Adept)
if (STATE.mp < CFG.manaGemMP) { if (STATE.mp < CFG.manaGemMP) {
@ -1447,12 +1461,20 @@ function strategyVeteran() {
const ch = findBestChannelingTarget(); const ch = findBestChannelingTarget();
if (ch) return ch; if (ch) return ch;
} }
// Collect available buff candidates and sort by cost
// No channeling → cheapest first (proc chance), channeling → most expensive (MP value)
const buffCandidates = [];
for (const b of BUFF_PRIORITY) { for (const b of BUFF_PRIORITY) {
if (shouldBuff(b.icon, b.minTurns) && STATE.mp > 0.10 && hasReadySpell(b.spell)) { if (shouldBuff(b.icon, b.minTurns) && STATE.mp > 0.10 && hasReadySpell(b.spell)) {
if (b.icon === 'regen' && STATE.hp >= CFG.cureRegenHP) continue; if (b.icon === 'regen' && STATE.hp >= CFG.cureRegenHP) continue;
return { type: 'spell', name: b.spell, selfTarget: true, _reason: 'buff' }; const sp = findSpell(b.spell);
buffCandidates.push({ ...b, mpCost: sp ? sp.mp : 0 });
} }
} }
if (buffCandidates.length > 0) {
buffCandidates.sort((a, b) => STATE.channeling ? b.mpCost - a.mpCost : a.mpCost - b.mpCost);
return { type: 'spell', name: buffCandidates[0].spell, selfTarget: true, _reason: STATE.channeling ? 'chBuff' : 'buff' };
}
} }
// 4. Mana/spirit items — AFTER buffs (Novice) // 4. Mana/spirit items — AFTER buffs (Novice)

View file

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

View file

@ -1,7 +1,7 @@
// ==UserScript== // ==UserScript==
// @name HV Unified // @name HV Unified
// @namespace hvunified // @namespace hvunified
// @version 0.14.3 // @version 0.14.4
// @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/*

View file

@ -430,12 +430,19 @@ function strategyNovice() {
const ch = findBestChannelingTarget(); const ch = findBestChannelingTarget();
if (ch) return ch; if (ch) return ch;
} }
// Collect available buff candidates and sort by cost
const buffCandidates = [];
for (const b of BUFF_PRIORITY) { for (const b of BUFF_PRIORITY) {
if (shouldBuff(b.icon, b.minTurns) && STATE.mp > 0.10 && hasReadySpell(b.spell)) { if (shouldBuff(b.icon, b.minTurns) && STATE.mp > 0.10 && hasReadySpell(b.spell)) {
if (b.icon === 'regen' && STATE.hp >= CFG.cureRegenHP) continue; if (b.icon === 'regen' && STATE.hp >= CFG.cureRegenHP) continue;
return { type: 'spell', name: b.spell, selfTarget: true }; const sp = findSpell(b.spell);
buffCandidates.push({ ...b, mpCost: sp ? sp.mp : 0 });
} }
} }
if (buffCandidates.length > 0) {
buffCandidates.sort((a, b) => STATE.channeling ? b.mpCost - a.mpCost : a.mpCost - b.mpCost);
return { type: 'spell', name: buffCandidates[0].spell, selfTarget: true, _reason: STATE.channeling ? 'chBuff' : 'buff' };
}
// 4. Mana/spirit items — AFTER buffs (Novice) // 4. Mana/spirit items — AFTER buffs (Novice)
if (STATE.mp < CFG.manaGemMP) { if (STATE.mp < CFG.manaGemMP) {
@ -566,17 +573,24 @@ function strategyAdept() {
} }
// If channeling is active, prioritize most expensive buff for best value // If channeling is active, prioritize most expensive buff for best value
// Note: channeling reduces cost to ~1, but the game pre-checks base MP, // 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 channeling is active, prioritize most expensive buff for best value
if (STATE.channeling && STATE.mp > 0.10) { if (STATE.channeling && STATE.mp > 0.10) {
const ch = findBestChannelingTarget(); const ch = findBestChannelingTarget();
if (ch) return ch; if (ch) return ch;
} }
// Collect available buff candidates and sort by cost
const buffCandidates = [];
for (const b of BUFF_PRIORITY) { for (const b of BUFF_PRIORITY) {
if (shouldBuff(b.icon, b.minTurns) && STATE.mp > 0.10 && hasReadySpell(b.spell)) { if (shouldBuff(b.icon, b.minTurns) && STATE.mp > 0.10 && hasReadySpell(b.spell)) {
if (b.icon === 'regen' && STATE.hp >= CFG.cureRegenHP) continue; if (b.icon === 'regen' && STATE.hp >= CFG.cureRegenHP) continue;
return { type: 'spell', name: b.spell, selfTarget: true }; const sp = findSpell(b.spell);
buffCandidates.push({ ...b, mpCost: sp ? sp.mp : 0 });
} }
} }
if (buffCandidates.length > 0) {
buffCandidates.sort((a, b) => STATE.channeling ? b.mpCost - a.mpCost : a.mpCost - b.mpCost);
return { type: 'spell', name: buffCandidates[0].spell, selfTarget: true, _reason: STATE.channeling ? 'chBuff' : 'buff' };
}
// 4. Mana/spirit items — AFTER buffs (Adept) // 4. Mana/spirit items — AFTER buffs (Adept)
if (STATE.mp < CFG.manaGemMP) { if (STATE.mp < CFG.manaGemMP) {
@ -722,12 +736,20 @@ function strategyVeteran() {
const ch = findBestChannelingTarget(); const ch = findBestChannelingTarget();
if (ch) return ch; if (ch) return ch;
} }
// Collect available buff candidates and sort by cost
// No channeling → cheapest first (proc chance), channeling → most expensive (MP value)
const buffCandidates = [];
for (const b of BUFF_PRIORITY) { for (const b of BUFF_PRIORITY) {
if (shouldBuff(b.icon, b.minTurns) && STATE.mp > 0.10 && hasReadySpell(b.spell)) { if (shouldBuff(b.icon, b.minTurns) && STATE.mp > 0.10 && hasReadySpell(b.spell)) {
if (b.icon === 'regen' && STATE.hp >= CFG.cureRegenHP) continue; if (b.icon === 'regen' && STATE.hp >= CFG.cureRegenHP) continue;
return { type: 'spell', name: b.spell, selfTarget: true, _reason: 'buff' }; const sp = findSpell(b.spell);
buffCandidates.push({ ...b, mpCost: sp ? sp.mp : 0 });
} }
} }
if (buffCandidates.length > 0) {
buffCandidates.sort((a, b) => STATE.channeling ? b.mpCost - a.mpCost : a.mpCost - b.mpCost);
return { type: 'spell', name: buffCandidates[0].spell, selfTarget: true, _reason: STATE.channeling ? 'chBuff' : 'buff' };
}
} }
// 4. Mana/spirit items — AFTER buffs (Novice) // 4. Mana/spirit items — AFTER buffs (Novice)