diff --git a/scripts/hv-unified.user.js b/scripts/hv-unified.user.js index 2c607d0..2929789 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.14.0 +// @version 0.14.1 // @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.14.0'; +const VERSION = '0.14.1'; const CFG = { // — Battle automation @@ -733,6 +733,13 @@ function hasSpell(name) { return STATE.spellsKnown.some(s => s.n === name); } +// Check if a spell is not on cooldown (has onclick in the DOM) +function hasReadySpell(name) { + const el = $$('.btsd, .btqs').find(e => + (e.getAttribute('onmouseover') || '').includes(`set_infopane_spell('${name}'`)); + return el ? el.hasAttribute('onclick') : false; +} + function findSpell(name) { return STATE.spellsKnown.find(s => s.n === name); } @@ -939,7 +946,7 @@ function castInitialBuffs() { // Collect all missing/expiring buffs with their MP cost const candidates = []; for (const b of BUFF_PRIORITY) { - if (!hasSpell(b.spell)) continue; + if (!hasReadySpell(b.spell)) continue; const dur = buffDuration(b.icon); if (dur === 0 || dur < 3) { if (b.icon === 'regen' && STATE.hp >= CFG.cureRegenHP) continue; @@ -1009,7 +1016,7 @@ function findBestChannelingTarget() { const candidates = []; for (const b of BUFF_PRIORITY) { - if (!hasSpell(b.spell)) continue; + if (!hasReadySpell(b.spell)) continue; const dur = buffDuration(b.icon); if (b.icon === 'regen' && STATE.hp >= CFG.cureRegenHP) continue; const sp = findSpell(b.spell); @@ -1126,7 +1133,7 @@ function strategyNovice() { // 2. Cure if (STATE.hp < CFG.cureHP) { - const c = hasSpell('Full-Cure') ? 'Full-Cure' : hasSpell('Cure') ? 'Cure' : null; + const c = hasReadySpell('Full-Cure') ? 'Full-Cure' : hasReadySpell('Cure') ? 'Cure' : null; if (c) return { type: 'spell', name: c, selfTarget: true }; } @@ -1142,7 +1149,7 @@ function strategyNovice() { if (ch) return ch; } for (const b of BUFF_PRIORITY) { - if (shouldBuff(b.icon, b.minTurns) && STATE.mp > 0.10 && hasSpell(b.spell)) { + if (shouldBuff(b.icon, b.minTurns) && STATE.mp > 0.10 && hasReadySpell(b.spell)) { if (b.icon === 'regen' && STATE.hp >= CFG.cureRegenHP) continue; return { type: 'spell', name: b.spell, selfTarget: true }; } @@ -1255,7 +1262,7 @@ function strategyAdept() { // 2. Cure if (STATE.hp < CFG.cureHP) { - const c = hasSpell('Full-Cure') ? 'Full-Cure' : hasSpell('Cure') ? 'Cure' : null; + const c = hasReadySpell('Full-Cure') ? 'Full-Cure' : hasReadySpell('Cure') ? 'Cure' : null; if (c) return { type: 'spell', name: c, selfTarget: true }; } @@ -1283,7 +1290,7 @@ function strategyAdept() { if (ch) return ch; } for (const b of BUFF_PRIORITY) { - if (shouldBuff(b.icon, b.minTurns) && STATE.mp > 0.10 && hasSpell(b.spell)) { + if (shouldBuff(b.icon, b.minTurns) && STATE.mp > 0.10 && hasReadySpell(b.spell)) { if (b.icon === 'regen' && STATE.hp >= CFG.cureRegenHP) continue; return { type: 'spell', name: b.spell, selfTarget: true }; } @@ -1311,12 +1318,12 @@ function strategyAdept() { // 4. Debuffs — Imperil on 2+ mobs or bosses; Weaken on high-threat if (CFG.autoDebuff && STATE.mp > 0.20) { - if (hasSpell('Imperil') && (anyRareMonster() || STATE.monsters.length >= 2)) { + if (hasReadySpell('Imperil') && (anyRareMonster() || STATE.monsters.length >= 2)) { const b = findStrongestMonster(); if (b >= 0 && !checkMonsterDebuff(b, 'imperil')) return { type: 'spell', name: 'Imperil', target: b, _reason: 'imperil' }; } - if (hasSpell('Weaken') && (anyRareMonster() || hasHighThreat(80) || STATE.monsters.length >= 3)) { + if (hasReadySpell('Weaken') && (anyRareMonster() || hasHighThreat(80) || STATE.monsters.length >= 3)) { const b = findDangerousMonster(); if (b >= 0 && !checkMonsterDebuff(b, 'weaken')) return { type: 'spell', name: 'Weaken', target: b, _reason: 'weaken' }; @@ -1407,7 +1414,7 @@ function strategyVeteran() { // 2. Cure if (STATE.hp < CFG.cureHP) { - const c = hasSpell('Full-Cure') ? 'Full-Cure' : hasSpell('Cure') ? 'Cure' : null; + const c = hasReadySpell('Full-Cure') ? 'Full-Cure' : hasReadySpell('Cure') ? 'Cure' : null; if (c) return { type: 'spell', name: c, selfTarget: true, _reason: `hp${(STATE.hp*100).toFixed(0)}` }; } @@ -1434,14 +1441,14 @@ function strategyVeteran() { if (ch) return ch; } for (const b of BUFF_PRIORITY) { - if (shouldBuff(b.icon, b.minTurns) && STATE.mp > 0.10 && hasSpell(b.spell)) { + if (shouldBuff(b.icon, b.minTurns) && STATE.mp > 0.10 && hasReadySpell(b.spell)) { if (b.icon === 'regen' && STATE.hp >= CFG.cureRegenHP) continue; return { type: 'spell', name: b.spell, selfTarget: true, _reason: 'buff' }; } } } - // 4. Mana/spirit items — AFTER buffs (Veteran) + // 4. Mana/spirit items — AFTER buffs (Novice) if (STATE.mp < CFG.manaGemMP) { const gem = hasUsableGem('mana'); if (gem) return { type: 'item', id: gem.id, selfTarget: true }; @@ -1466,13 +1473,13 @@ function strategyVeteran() { if (CFG.autoDebuff && STATE.mp > 0.20) { // Imperil: reduce physical mitigation by 25-50% (stacks with Penetrated Armor) // Use on any tanky enemy when facing 2+ monsters or a rare - if (hasSpell('Imperil') && (anyRareMonster() || STATE.monsters.length >= 2)) { + if (hasReadySpell('Imperil') && (anyRareMonster() || STATE.monsters.length >= 2)) { const b = findStrongestMonster(); if (b >= 0 && !checkMonsterDebuff(b, 'imperil')) return { type: 'spell', name: 'Imperil', target: b, _reason: 'imperil' }; } // Weaken: reduce incoming damage by 50% — use on high-threat or 3+ mobs - if (hasSpell('Weaken') && (anyRareMonster() || hasHighThreat(80) || STATE.monsters.length >= 3)) { + if (hasReadySpell('Weaken') && (anyRareMonster() || hasHighThreat(80) || STATE.monsters.length >= 3)) { const b = findDangerousMonster(); if (b >= 0 && !checkMonsterDebuff(b, 'weaken')) return { type: 'spell', name: 'Weaken', target: b, _reason: 'weaken' }; diff --git a/scripts/latest.user.js b/scripts/latest.user.js index 2c607d0..2929789 100644 --- a/scripts/latest.user.js +++ b/scripts/latest.user.js @@ -1,7 +1,7 @@ // ==UserScript== // @name HV Unified // @namespace hvunified -// @version 0.14.0 +// @version 0.14.1 // @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.14.0'; +const VERSION = '0.14.1'; const CFG = { // — Battle automation @@ -733,6 +733,13 @@ function hasSpell(name) { return STATE.spellsKnown.some(s => s.n === name); } +// Check if a spell is not on cooldown (has onclick in the DOM) +function hasReadySpell(name) { + const el = $$('.btsd, .btqs').find(e => + (e.getAttribute('onmouseover') || '').includes(`set_infopane_spell('${name}'`)); + return el ? el.hasAttribute('onclick') : false; +} + function findSpell(name) { return STATE.spellsKnown.find(s => s.n === name); } @@ -939,7 +946,7 @@ function castInitialBuffs() { // Collect all missing/expiring buffs with their MP cost const candidates = []; for (const b of BUFF_PRIORITY) { - if (!hasSpell(b.spell)) continue; + if (!hasReadySpell(b.spell)) continue; const dur = buffDuration(b.icon); if (dur === 0 || dur < 3) { if (b.icon === 'regen' && STATE.hp >= CFG.cureRegenHP) continue; @@ -1009,7 +1016,7 @@ function findBestChannelingTarget() { const candidates = []; for (const b of BUFF_PRIORITY) { - if (!hasSpell(b.spell)) continue; + if (!hasReadySpell(b.spell)) continue; const dur = buffDuration(b.icon); if (b.icon === 'regen' && STATE.hp >= CFG.cureRegenHP) continue; const sp = findSpell(b.spell); @@ -1126,7 +1133,7 @@ function strategyNovice() { // 2. Cure if (STATE.hp < CFG.cureHP) { - const c = hasSpell('Full-Cure') ? 'Full-Cure' : hasSpell('Cure') ? 'Cure' : null; + const c = hasReadySpell('Full-Cure') ? 'Full-Cure' : hasReadySpell('Cure') ? 'Cure' : null; if (c) return { type: 'spell', name: c, selfTarget: true }; } @@ -1142,7 +1149,7 @@ function strategyNovice() { if (ch) return ch; } for (const b of BUFF_PRIORITY) { - if (shouldBuff(b.icon, b.minTurns) && STATE.mp > 0.10 && hasSpell(b.spell)) { + if (shouldBuff(b.icon, b.minTurns) && STATE.mp > 0.10 && hasReadySpell(b.spell)) { if (b.icon === 'regen' && STATE.hp >= CFG.cureRegenHP) continue; return { type: 'spell', name: b.spell, selfTarget: true }; } @@ -1255,7 +1262,7 @@ function strategyAdept() { // 2. Cure if (STATE.hp < CFG.cureHP) { - const c = hasSpell('Full-Cure') ? 'Full-Cure' : hasSpell('Cure') ? 'Cure' : null; + const c = hasReadySpell('Full-Cure') ? 'Full-Cure' : hasReadySpell('Cure') ? 'Cure' : null; if (c) return { type: 'spell', name: c, selfTarget: true }; } @@ -1283,7 +1290,7 @@ function strategyAdept() { if (ch) return ch; } for (const b of BUFF_PRIORITY) { - if (shouldBuff(b.icon, b.minTurns) && STATE.mp > 0.10 && hasSpell(b.spell)) { + if (shouldBuff(b.icon, b.minTurns) && STATE.mp > 0.10 && hasReadySpell(b.spell)) { if (b.icon === 'regen' && STATE.hp >= CFG.cureRegenHP) continue; return { type: 'spell', name: b.spell, selfTarget: true }; } @@ -1311,12 +1318,12 @@ function strategyAdept() { // 4. Debuffs — Imperil on 2+ mobs or bosses; Weaken on high-threat if (CFG.autoDebuff && STATE.mp > 0.20) { - if (hasSpell('Imperil') && (anyRareMonster() || STATE.monsters.length >= 2)) { + if (hasReadySpell('Imperil') && (anyRareMonster() || STATE.monsters.length >= 2)) { const b = findStrongestMonster(); if (b >= 0 && !checkMonsterDebuff(b, 'imperil')) return { type: 'spell', name: 'Imperil', target: b, _reason: 'imperil' }; } - if (hasSpell('Weaken') && (anyRareMonster() || hasHighThreat(80) || STATE.monsters.length >= 3)) { + if (hasReadySpell('Weaken') && (anyRareMonster() || hasHighThreat(80) || STATE.monsters.length >= 3)) { const b = findDangerousMonster(); if (b >= 0 && !checkMonsterDebuff(b, 'weaken')) return { type: 'spell', name: 'Weaken', target: b, _reason: 'weaken' }; @@ -1407,7 +1414,7 @@ function strategyVeteran() { // 2. Cure if (STATE.hp < CFG.cureHP) { - const c = hasSpell('Full-Cure') ? 'Full-Cure' : hasSpell('Cure') ? 'Cure' : null; + const c = hasReadySpell('Full-Cure') ? 'Full-Cure' : hasReadySpell('Cure') ? 'Cure' : null; if (c) return { type: 'spell', name: c, selfTarget: true, _reason: `hp${(STATE.hp*100).toFixed(0)}` }; } @@ -1434,14 +1441,14 @@ function strategyVeteran() { if (ch) return ch; } for (const b of BUFF_PRIORITY) { - if (shouldBuff(b.icon, b.minTurns) && STATE.mp > 0.10 && hasSpell(b.spell)) { + if (shouldBuff(b.icon, b.minTurns) && STATE.mp > 0.10 && hasReadySpell(b.spell)) { if (b.icon === 'regen' && STATE.hp >= CFG.cureRegenHP) continue; return { type: 'spell', name: b.spell, selfTarget: true, _reason: 'buff' }; } } } - // 4. Mana/spirit items — AFTER buffs (Veteran) + // 4. Mana/spirit items — AFTER buffs (Novice) if (STATE.mp < CFG.manaGemMP) { const gem = hasUsableGem('mana'); if (gem) return { type: 'item', id: gem.id, selfTarget: true }; @@ -1466,13 +1473,13 @@ function strategyVeteran() { if (CFG.autoDebuff && STATE.mp > 0.20) { // Imperil: reduce physical mitigation by 25-50% (stacks with Penetrated Armor) // Use on any tanky enemy when facing 2+ monsters or a rare - if (hasSpell('Imperil') && (anyRareMonster() || STATE.monsters.length >= 2)) { + if (hasReadySpell('Imperil') && (anyRareMonster() || STATE.monsters.length >= 2)) { const b = findStrongestMonster(); if (b >= 0 && !checkMonsterDebuff(b, 'imperil')) return { type: 'spell', name: 'Imperil', target: b, _reason: 'imperil' }; } // Weaken: reduce incoming damage by 50% — use on high-threat or 3+ mobs - if (hasSpell('Weaken') && (anyRareMonster() || hasHighThreat(80) || STATE.monsters.length >= 3)) { + if (hasReadySpell('Weaken') && (anyRareMonster() || hasHighThreat(80) || STATE.monsters.length >= 3)) { const b = findDangerousMonster(); if (b >= 0 && !checkMonsterDebuff(b, 'weaken')) return { type: 'spell', name: 'Weaken', target: b, _reason: 'weaken' }; diff --git a/src/config.js b/src/config.js index 821f130..d048778 100644 --- a/src/config.js +++ b/src/config.js @@ -2,7 +2,7 @@ // CONFIG — default settings // ═══════════════════════════════════════════════════════════════════════ -const VERSION = '0.14.0'; +const VERSION = '0.14.1'; const CFG = { // — Battle automation diff --git a/src/header.user.js b/src/header.user.js index adfd8b0..f632a77 100644 --- a/src/header.user.js +++ b/src/header.user.js @@ -1,7 +1,7 @@ // ==UserScript== // @name HV Unified // @namespace hvunified -// @version 0.14.0 +// @version 0.14.1 // @description HV Unified — battle automation, guidance, and UI enhancements for HentaiVerse // @author GaboGG + Hermes // @match *://*.hentaiverse.org/* diff --git a/src/strategy-engine.js b/src/strategy-engine.js index 8b4404a..e5cf37f 100644 --- a/src/strategy-engine.js +++ b/src/strategy-engine.js @@ -8,6 +8,13 @@ function hasSpell(name) { return STATE.spellsKnown.some(s => s.n === name); } +// Check if a spell is not on cooldown (has onclick in the DOM) +function hasReadySpell(name) { + const el = $$('.btsd, .btqs').find(e => + (e.getAttribute('onmouseover') || '').includes(`set_infopane_spell('${name}'`)); + return el ? el.hasAttribute('onclick') : false; +} + function findSpell(name) { return STATE.spellsKnown.find(s => s.n === name); } @@ -214,7 +221,7 @@ function castInitialBuffs() { // Collect all missing/expiring buffs with their MP cost const candidates = []; for (const b of BUFF_PRIORITY) { - if (!hasSpell(b.spell)) continue; + if (!hasReadySpell(b.spell)) continue; const dur = buffDuration(b.icon); if (dur === 0 || dur < 3) { if (b.icon === 'regen' && STATE.hp >= CFG.cureRegenHP) continue; @@ -284,7 +291,7 @@ function findBestChannelingTarget() { const candidates = []; for (const b of BUFF_PRIORITY) { - if (!hasSpell(b.spell)) continue; + if (!hasReadySpell(b.spell)) continue; const dur = buffDuration(b.icon); if (b.icon === 'regen' && STATE.hp >= CFG.cureRegenHP) continue; const sp = findSpell(b.spell); @@ -401,7 +408,7 @@ function strategyNovice() { // 2. Cure if (STATE.hp < CFG.cureHP) { - const c = hasSpell('Full-Cure') ? 'Full-Cure' : hasSpell('Cure') ? 'Cure' : null; + const c = hasReadySpell('Full-Cure') ? 'Full-Cure' : hasReadySpell('Cure') ? 'Cure' : null; if (c) return { type: 'spell', name: c, selfTarget: true }; } @@ -417,7 +424,7 @@ function strategyNovice() { if (ch) return ch; } for (const b of BUFF_PRIORITY) { - if (shouldBuff(b.icon, b.minTurns) && STATE.mp > 0.10 && hasSpell(b.spell)) { + if (shouldBuff(b.icon, b.minTurns) && STATE.mp > 0.10 && hasReadySpell(b.spell)) { if (b.icon === 'regen' && STATE.hp >= CFG.cureRegenHP) continue; return { type: 'spell', name: b.spell, selfTarget: true }; } @@ -530,7 +537,7 @@ function strategyAdept() { // 2. Cure if (STATE.hp < CFG.cureHP) { - const c = hasSpell('Full-Cure') ? 'Full-Cure' : hasSpell('Cure') ? 'Cure' : null; + const c = hasReadySpell('Full-Cure') ? 'Full-Cure' : hasReadySpell('Cure') ? 'Cure' : null; if (c) return { type: 'spell', name: c, selfTarget: true }; } @@ -558,7 +565,7 @@ function strategyAdept() { if (ch) return ch; } for (const b of BUFF_PRIORITY) { - if (shouldBuff(b.icon, b.minTurns) && STATE.mp > 0.10 && hasSpell(b.spell)) { + if (shouldBuff(b.icon, b.minTurns) && STATE.mp > 0.10 && hasReadySpell(b.spell)) { if (b.icon === 'regen' && STATE.hp >= CFG.cureRegenHP) continue; return { type: 'spell', name: b.spell, selfTarget: true }; } @@ -586,12 +593,12 @@ function strategyAdept() { // 4. Debuffs — Imperil on 2+ mobs or bosses; Weaken on high-threat if (CFG.autoDebuff && STATE.mp > 0.20) { - if (hasSpell('Imperil') && (anyRareMonster() || STATE.monsters.length >= 2)) { + if (hasReadySpell('Imperil') && (anyRareMonster() || STATE.monsters.length >= 2)) { const b = findStrongestMonster(); if (b >= 0 && !checkMonsterDebuff(b, 'imperil')) return { type: 'spell', name: 'Imperil', target: b, _reason: 'imperil' }; } - if (hasSpell('Weaken') && (anyRareMonster() || hasHighThreat(80) || STATE.monsters.length >= 3)) { + if (hasReadySpell('Weaken') && (anyRareMonster() || hasHighThreat(80) || STATE.monsters.length >= 3)) { const b = findDangerousMonster(); if (b >= 0 && !checkMonsterDebuff(b, 'weaken')) return { type: 'spell', name: 'Weaken', target: b, _reason: 'weaken' }; @@ -682,7 +689,7 @@ function strategyVeteran() { // 2. Cure if (STATE.hp < CFG.cureHP) { - const c = hasSpell('Full-Cure') ? 'Full-Cure' : hasSpell('Cure') ? 'Cure' : null; + const c = hasReadySpell('Full-Cure') ? 'Full-Cure' : hasReadySpell('Cure') ? 'Cure' : null; if (c) return { type: 'spell', name: c, selfTarget: true, _reason: `hp${(STATE.hp*100).toFixed(0)}` }; } @@ -709,14 +716,14 @@ function strategyVeteran() { if (ch) return ch; } for (const b of BUFF_PRIORITY) { - if (shouldBuff(b.icon, b.minTurns) && STATE.mp > 0.10 && hasSpell(b.spell)) { + if (shouldBuff(b.icon, b.minTurns) && STATE.mp > 0.10 && hasReadySpell(b.spell)) { if (b.icon === 'regen' && STATE.hp >= CFG.cureRegenHP) continue; return { type: 'spell', name: b.spell, selfTarget: true, _reason: 'buff' }; } } } - // 4. Mana/spirit items — AFTER buffs (Veteran) + // 4. Mana/spirit items — AFTER buffs (Novice) if (STATE.mp < CFG.manaGemMP) { const gem = hasUsableGem('mana'); if (gem) return { type: 'item', id: gem.id, selfTarget: true }; @@ -741,13 +748,13 @@ function strategyVeteran() { if (CFG.autoDebuff && STATE.mp > 0.20) { // Imperil: reduce physical mitigation by 25-50% (stacks with Penetrated Armor) // Use on any tanky enemy when facing 2+ monsters or a rare - if (hasSpell('Imperil') && (anyRareMonster() || STATE.monsters.length >= 2)) { + if (hasReadySpell('Imperil') && (anyRareMonster() || STATE.monsters.length >= 2)) { const b = findStrongestMonster(); if (b >= 0 && !checkMonsterDebuff(b, 'imperil')) return { type: 'spell', name: 'Imperil', target: b, _reason: 'imperil' }; } // Weaken: reduce incoming damage by 50% — use on high-threat or 3+ mobs - if (hasSpell('Weaken') && (anyRareMonster() || hasHighThreat(80) || STATE.monsters.length >= 3)) { + if (hasReadySpell('Weaken') && (anyRareMonster() || hasHighThreat(80) || STATE.monsters.length >= 3)) { const b = findDangerousMonster(); if (b >= 0 && !checkMonsterDebuff(b, 'weaken')) return { type: 'spell', name: 'Weaken', target: b, _reason: 'weaken' };