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:
parent
48e5103655
commit
0a41f9d7c5
5 changed files with 84 additions and 18 deletions
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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/*
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue