diff --git a/scripts/hv-unified.user.js b/scripts/hv-unified.user.js index 32e16de..e67d3b9 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.13.33 +// @version 0.13.34 // @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.33'; +const VERSION = '0.13.34'; const CFG = { // — Battle automation @@ -1017,7 +1017,7 @@ function findBestChannelingTarget() { // Only use a buff if it actually needs refreshing (score >= 30 means // e.g. 30% emptiness on a 100-MP spell, or 60% on a 50-MP spell) if (candidates[0].score >= 30) { - return { type: 'spell', name: candidates[0].spell, selfTarget: true }; + return { type: 'spell', name: candidates[0].spell, selfTarget: true, _reason: `ch score ${candidates[0].score.toFixed(0)}` }; } } @@ -1330,18 +1330,18 @@ function strategyVeteran() { // 0. Mystic Gem for channeling — only use once per battle const gem = hasUsableGem('channel'); if (gem && !STATE.channeling && !STATE._mysticUsed) - return { type: 'item', id: gem.id, selfTarget: true }; + return { type: 'item', id: gem.id, selfTarget: true, _reason: 'mystic' }; // 1. Health items — safe to use early - if (STATE.hp < 0.60) { + if (STATE.hp < CFG.cureItemHP) { const gem = hasUsableGem('heal'); - if (gem) return { type: 'item', id: gem.id, selfTarget: true }; + if (gem) return { type: 'item', id: gem.id, selfTarget: true, _reason: `hp${(STATE.hp*100).toFixed(0)}` }; } // 2. Cure if (STATE.hp < CFG.cureHP) { const c = hasSpell('Full-Cure') ? 'Full-Cure' : hasSpell('Cure') ? 'Cure' : null; - if (c) return { type: 'spell', name: c, selfTarget: true }; + if (c) return { type: 'spell', name: c, selfTarget: true, _reason: `hp${(STATE.hp*100).toFixed(0)}` }; } // 2b. Spirit items — Spark of Life costs 50% SP. Keep SP above 55% so @@ -1350,7 +1350,7 @@ function strategyVeteran() { for (const [slot, info] of Object.entries(STATE.itemsKnown)) { if (slot === 'p') continue; const def = ITEMS[parseInt(info)]; - if (def && def.t === 'spirit') return { type: 'item', id: slot, selfTarget: true }; + if (def && def.t === 'spirit') return { type: 'item', id: slot, selfTarget: true, _reason: `sp${(STATE.sp*100).toFixed(0)}` }; } } @@ -1369,7 +1369,7 @@ function strategyVeteran() { for (const b of BUFF_PRIORITY) { if (shouldBuff(b.icon, b.minTurns) && STATE.mp > 0.10 && hasSpell(b.spell)) { if (b.icon === 'regen' && STATE.hp >= CFG.cureRegenHP) continue; - return { type: 'spell', name: b.spell, selfTarget: true }; + return { type: 'spell', name: b.spell, selfTarget: true, _reason: 'buff' }; } } } @@ -1420,20 +1420,20 @@ function strategyVeteran() { // Rending Blow: AoE armor pen vs 5+ enemies (highest priority in Grindfest) if (STATE.monsters.length >= 5 && STATE.skillsKnown.includes('Rending Blow')) { const t = isHighThreat ? findDangerousMonster() : findStrongestMonster(); - if (t >= 0) return { type: 'skill', name: 'Rending Blow', target: t }; + if (t >= 0) return { type: 'skill', name: 'Rending Blow', target: t, _reason: isHighThreat ? 'threat' : 'oc' }; } // Shatter Strike: AoE stun vs 5+ enemies (needs Penetrated Armor) if (STATE.monsters.length >= 5 && STATE.skillsKnown.includes('Shatter Strike')) { const hasArmorBreak = STATE.monsters.some((m, i) => checkMonsterDebuff(i, 'penetrated_armor')); if (hasArmorBreak) { const t = isHighThreat ? findDangerousMonster() : findStrongestMonster(); - if (t >= 0) return { type: 'skill', name: 'Shatter Strike', target: t }; + if (t >= 0) return { type: 'skill', name: 'Shatter Strike', target: t, _reason: 'stun' }; } } // Great Cleave: boss/rare fights or single-target threat if (anyRareMonster() && STATE.skillsKnown.includes('Great Cleave')) { const t = isHighThreat ? findDangerousMonster() : findStrongestMonster(); - if (t >= 0) return { type: 'skill', name: 'Great Cleave', target: t }; + if (t >= 0) return { type: 'skill', name: 'Great Cleave', target: t, _reason: isHighThreat ? 'threat' : 'boss' }; } // If high threat and no AoE skill available, use any weapon skill on the most dangerous if (isHighThreat && STATE.oc >= (CFG.skillOC || 80)) { @@ -1442,21 +1442,36 @@ function strategyVeteran() { for (const sk of ps) { if (STATE.skillsKnown.includes(sk)) { const t = findDangerousMonster(); - if (t >= 0) return { type: 'skill', name: sk, target: t }; + if (t >= 0) return { type: 'skill', name: sk, target: t, _reason: 'threat' }; } } } } - // 7. Damage spells + // 5. Spirit gem auto-consume + const spGem = document.getElementById('ikey_p'); + const spGemId = spGem ? (spGem.getAttribute('onmouseover') || '').match(/set_infopane_item\((\d+)\)/) : null; + if (spGemId && spGemId[1] === '10007') { + if (hasUsableGem('spirit')) return { type: 'item', id: 'p', selfTarget: true, _reason: 'gem' }; + } + // Spirit potions — only at SP < threshold (Spark needs 50%) + if (STATE.sp < CFG.spiritPotionSP) { + for (const [slot, info] of Object.entries(STATE.itemsKnown)) { + if (slot === 'p') continue; + const def = ITEMS[parseInt(info)]; + if (def && def.t === 'spirit') return { type: 'item', id: slot, selfTarget: true, _reason: `sp${(STATE.sp*100).toFixed(0)}` }; + } + } + + // 6. Damage spells const dmg = CFG.useAttackSpells ? getBestDamageSpell() : null; if (dmg && STATE.mp > 0.15) { const t = STATE.monsters.length > 1 ? findWeakestMonster() : 0; if (t >= 0 && STATE.monsters[t] && STATE.monsters[t].hasAttribute('onclick')) - return { type: 'spell', name: dmg, target: t }; + return { type: 'spell', name: dmg, target: t, _reason: 'dmg' }; } - // 8. Channeling kickstart or basic attack + // 7. Channeling kickstart or basic attack { const k = kickstartChanneling(); if (k) return k; @@ -1468,9 +1483,9 @@ function strategyVeteran() { if (ch) return ch; } - const t = findWeakestMonster(); + const t = hasHighThreat(THREAT_PX) ? findDangerousMonster() : findWeakestMonster(); if (t >= 0 && STATE.monsters[t] && STATE.monsters[t].hasAttribute('onclick')) - return { type: 'attack', target: t }; + return { type: 'attack', target: t, _reason: hasHighThreat(THREAT_PX) ? 'threat' : 'fallback' }; return null; } @@ -1665,8 +1680,21 @@ function setupKeybindings() { const a = getRecommendedAction(); if (a) { e.preventDefault(); - const label = a.target >= 0 ? 'monster ' + a.target : a.name || a.id; - console.log(`%c[HV] ▶ ${a.type} → ${label}`, 'color:#0f0'); + // Show explicit action details + let label = ''; + if (a.type === 'spell') { + label = a.name + (a.target >= 0 ? ' → monster ' + a.target : ' (self)'); + } else if (a.type === 'skill') { + label = a.name + ' → monster ' + a.target; + } else if (a.type === 'item') { + const itemDef = ITEMS[parseInt(STATE.itemsKnown[a.id] || '0')]; + label = (itemDef ? itemDef.n : a.id) + (a.selfTarget ? ' (self)' : ''); + } else if (a.type === 'attack') { + label = 'monster ' + a.target; + } else { + label = a.target >= 0 ? 'monster ' + a.target : a.name || a.id; + } + console.log(`%c[HV] ▶ ${a.type} → ${label}${a._reason ? ' (' + a._reason + ')' : ''}`, 'color:#0f0'); executeAction(a); } } diff --git a/scripts/latest.user.js b/scripts/latest.user.js index 32e16de..e67d3b9 100644 --- a/scripts/latest.user.js +++ b/scripts/latest.user.js @@ -1,7 +1,7 @@ // ==UserScript== // @name HV Unified // @namespace hvunified -// @version 0.13.33 +// @version 0.13.34 // @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.33'; +const VERSION = '0.13.34'; const CFG = { // — Battle automation @@ -1017,7 +1017,7 @@ function findBestChannelingTarget() { // Only use a buff if it actually needs refreshing (score >= 30 means // e.g. 30% emptiness on a 100-MP spell, or 60% on a 50-MP spell) if (candidates[0].score >= 30) { - return { type: 'spell', name: candidates[0].spell, selfTarget: true }; + return { type: 'spell', name: candidates[0].spell, selfTarget: true, _reason: `ch score ${candidates[0].score.toFixed(0)}` }; } } @@ -1330,18 +1330,18 @@ function strategyVeteran() { // 0. Mystic Gem for channeling — only use once per battle const gem = hasUsableGem('channel'); if (gem && !STATE.channeling && !STATE._mysticUsed) - return { type: 'item', id: gem.id, selfTarget: true }; + return { type: 'item', id: gem.id, selfTarget: true, _reason: 'mystic' }; // 1. Health items — safe to use early - if (STATE.hp < 0.60) { + if (STATE.hp < CFG.cureItemHP) { const gem = hasUsableGem('heal'); - if (gem) return { type: 'item', id: gem.id, selfTarget: true }; + if (gem) return { type: 'item', id: gem.id, selfTarget: true, _reason: `hp${(STATE.hp*100).toFixed(0)}` }; } // 2. Cure if (STATE.hp < CFG.cureHP) { const c = hasSpell('Full-Cure') ? 'Full-Cure' : hasSpell('Cure') ? 'Cure' : null; - if (c) return { type: 'spell', name: c, selfTarget: true }; + if (c) return { type: 'spell', name: c, selfTarget: true, _reason: `hp${(STATE.hp*100).toFixed(0)}` }; } // 2b. Spirit items — Spark of Life costs 50% SP. Keep SP above 55% so @@ -1350,7 +1350,7 @@ function strategyVeteran() { for (const [slot, info] of Object.entries(STATE.itemsKnown)) { if (slot === 'p') continue; const def = ITEMS[parseInt(info)]; - if (def && def.t === 'spirit') return { type: 'item', id: slot, selfTarget: true }; + if (def && def.t === 'spirit') return { type: 'item', id: slot, selfTarget: true, _reason: `sp${(STATE.sp*100).toFixed(0)}` }; } } @@ -1369,7 +1369,7 @@ function strategyVeteran() { for (const b of BUFF_PRIORITY) { if (shouldBuff(b.icon, b.minTurns) && STATE.mp > 0.10 && hasSpell(b.spell)) { if (b.icon === 'regen' && STATE.hp >= CFG.cureRegenHP) continue; - return { type: 'spell', name: b.spell, selfTarget: true }; + return { type: 'spell', name: b.spell, selfTarget: true, _reason: 'buff' }; } } } @@ -1420,20 +1420,20 @@ function strategyVeteran() { // Rending Blow: AoE armor pen vs 5+ enemies (highest priority in Grindfest) if (STATE.monsters.length >= 5 && STATE.skillsKnown.includes('Rending Blow')) { const t = isHighThreat ? findDangerousMonster() : findStrongestMonster(); - if (t >= 0) return { type: 'skill', name: 'Rending Blow', target: t }; + if (t >= 0) return { type: 'skill', name: 'Rending Blow', target: t, _reason: isHighThreat ? 'threat' : 'oc' }; } // Shatter Strike: AoE stun vs 5+ enemies (needs Penetrated Armor) if (STATE.monsters.length >= 5 && STATE.skillsKnown.includes('Shatter Strike')) { const hasArmorBreak = STATE.monsters.some((m, i) => checkMonsterDebuff(i, 'penetrated_armor')); if (hasArmorBreak) { const t = isHighThreat ? findDangerousMonster() : findStrongestMonster(); - if (t >= 0) return { type: 'skill', name: 'Shatter Strike', target: t }; + if (t >= 0) return { type: 'skill', name: 'Shatter Strike', target: t, _reason: 'stun' }; } } // Great Cleave: boss/rare fights or single-target threat if (anyRareMonster() && STATE.skillsKnown.includes('Great Cleave')) { const t = isHighThreat ? findDangerousMonster() : findStrongestMonster(); - if (t >= 0) return { type: 'skill', name: 'Great Cleave', target: t }; + if (t >= 0) return { type: 'skill', name: 'Great Cleave', target: t, _reason: isHighThreat ? 'threat' : 'boss' }; } // If high threat and no AoE skill available, use any weapon skill on the most dangerous if (isHighThreat && STATE.oc >= (CFG.skillOC || 80)) { @@ -1442,21 +1442,36 @@ function strategyVeteran() { for (const sk of ps) { if (STATE.skillsKnown.includes(sk)) { const t = findDangerousMonster(); - if (t >= 0) return { type: 'skill', name: sk, target: t }; + if (t >= 0) return { type: 'skill', name: sk, target: t, _reason: 'threat' }; } } } } - // 7. Damage spells + // 5. Spirit gem auto-consume + const spGem = document.getElementById('ikey_p'); + const spGemId = spGem ? (spGem.getAttribute('onmouseover') || '').match(/set_infopane_item\((\d+)\)/) : null; + if (spGemId && spGemId[1] === '10007') { + if (hasUsableGem('spirit')) return { type: 'item', id: 'p', selfTarget: true, _reason: 'gem' }; + } + // Spirit potions — only at SP < threshold (Spark needs 50%) + if (STATE.sp < CFG.spiritPotionSP) { + for (const [slot, info] of Object.entries(STATE.itemsKnown)) { + if (slot === 'p') continue; + const def = ITEMS[parseInt(info)]; + if (def && def.t === 'spirit') return { type: 'item', id: slot, selfTarget: true, _reason: `sp${(STATE.sp*100).toFixed(0)}` }; + } + } + + // 6. Damage spells const dmg = CFG.useAttackSpells ? getBestDamageSpell() : null; if (dmg && STATE.mp > 0.15) { const t = STATE.monsters.length > 1 ? findWeakestMonster() : 0; if (t >= 0 && STATE.monsters[t] && STATE.monsters[t].hasAttribute('onclick')) - return { type: 'spell', name: dmg, target: t }; + return { type: 'spell', name: dmg, target: t, _reason: 'dmg' }; } - // 8. Channeling kickstart or basic attack + // 7. Channeling kickstart or basic attack { const k = kickstartChanneling(); if (k) return k; @@ -1468,9 +1483,9 @@ function strategyVeteran() { if (ch) return ch; } - const t = findWeakestMonster(); + const t = hasHighThreat(THREAT_PX) ? findDangerousMonster() : findWeakestMonster(); if (t >= 0 && STATE.monsters[t] && STATE.monsters[t].hasAttribute('onclick')) - return { type: 'attack', target: t }; + return { type: 'attack', target: t, _reason: hasHighThreat(THREAT_PX) ? 'threat' : 'fallback' }; return null; } @@ -1665,8 +1680,21 @@ function setupKeybindings() { const a = getRecommendedAction(); if (a) { e.preventDefault(); - const label = a.target >= 0 ? 'monster ' + a.target : a.name || a.id; - console.log(`%c[HV] ▶ ${a.type} → ${label}`, 'color:#0f0'); + // Show explicit action details + let label = ''; + if (a.type === 'spell') { + label = a.name + (a.target >= 0 ? ' → monster ' + a.target : ' (self)'); + } else if (a.type === 'skill') { + label = a.name + ' → monster ' + a.target; + } else if (a.type === 'item') { + const itemDef = ITEMS[parseInt(STATE.itemsKnown[a.id] || '0')]; + label = (itemDef ? itemDef.n : a.id) + (a.selfTarget ? ' (self)' : ''); + } else if (a.type === 'attack') { + label = 'monster ' + a.target; + } else { + label = a.target >= 0 ? 'monster ' + a.target : a.name || a.id; + } + console.log(`%c[HV] ▶ ${a.type} → ${label}${a._reason ? ' (' + a._reason + ')' : ''}`, 'color:#0f0'); executeAction(a); } } diff --git a/src/config.js b/src/config.js index 992db18..57094ca 100644 --- a/src/config.js +++ b/src/config.js @@ -2,7 +2,7 @@ // CONFIG — default settings // ═══════════════════════════════════════════════════════════════════════ -const VERSION = '0.13.33'; +const VERSION = '0.13.34'; const CFG = { // — Battle automation diff --git a/src/header.user.js b/src/header.user.js index 374906f..194c0e6 100644 --- a/src/header.user.js +++ b/src/header.user.js @@ -1,7 +1,7 @@ // ==UserScript== // @name HV Unified // @namespace hvunified -// @version 0.13.33 +// @version 0.13.34 // @description HV Unified — battle automation, guidance, and UI enhancements for HentaiVerse // @author GaboGG + Hermes // @match *://*.hentaiverse.org/* diff --git a/src/keybindings.js b/src/keybindings.js index 78e3919..62dc299 100644 --- a/src/keybindings.js +++ b/src/keybindings.js @@ -51,8 +51,21 @@ function setupKeybindings() { const a = getRecommendedAction(); if (a) { e.preventDefault(); - const label = a.target >= 0 ? 'monster ' + a.target : a.name || a.id; - console.log(`%c[HV] ▶ ${a.type} → ${label}`, 'color:#0f0'); + // Show explicit action details + let label = ''; + if (a.type === 'spell') { + label = a.name + (a.target >= 0 ? ' → monster ' + a.target : ' (self)'); + } else if (a.type === 'skill') { + label = a.name + ' → monster ' + a.target; + } else if (a.type === 'item') { + const itemDef = ITEMS[parseInt(STATE.itemsKnown[a.id] || '0')]; + label = (itemDef ? itemDef.n : a.id) + (a.selfTarget ? ' (self)' : ''); + } else if (a.type === 'attack') { + label = 'monster ' + a.target; + } else { + label = a.target >= 0 ? 'monster ' + a.target : a.name || a.id; + } + console.log(`%c[HV] ▶ ${a.type} → ${label}${a._reason ? ' (' + a._reason + ')' : ''}`, 'color:#0f0'); executeAction(a); } } diff --git a/src/strategy-engine.js b/src/strategy-engine.js index 19c77ec..43c6b4d 100644 --- a/src/strategy-engine.js +++ b/src/strategy-engine.js @@ -303,7 +303,7 @@ function findBestChannelingTarget() { // Only use a buff if it actually needs refreshing (score >= 30 means // e.g. 30% emptiness on a 100-MP spell, or 60% on a 50-MP spell) if (candidates[0].score >= 30) { - return { type: 'spell', name: candidates[0].spell, selfTarget: true }; + return { type: 'spell', name: candidates[0].spell, selfTarget: true, _reason: `ch score ${candidates[0].score.toFixed(0)}` }; } } @@ -616,18 +616,18 @@ function strategyVeteran() { // 0. Mystic Gem for channeling — only use once per battle const gem = hasUsableGem('channel'); if (gem && !STATE.channeling && !STATE._mysticUsed) - return { type: 'item', id: gem.id, selfTarget: true }; + return { type: 'item', id: gem.id, selfTarget: true, _reason: 'mystic' }; // 1. Health items — safe to use early - if (STATE.hp < 0.60) { + if (STATE.hp < CFG.cureItemHP) { const gem = hasUsableGem('heal'); - if (gem) return { type: 'item', id: gem.id, selfTarget: true }; + if (gem) return { type: 'item', id: gem.id, selfTarget: true, _reason: `hp${(STATE.hp*100).toFixed(0)}` }; } // 2. Cure if (STATE.hp < CFG.cureHP) { const c = hasSpell('Full-Cure') ? 'Full-Cure' : hasSpell('Cure') ? 'Cure' : null; - if (c) return { type: 'spell', name: c, selfTarget: true }; + if (c) return { type: 'spell', name: c, selfTarget: true, _reason: `hp${(STATE.hp*100).toFixed(0)}` }; } // 2b. Spirit items — Spark of Life costs 50% SP. Keep SP above 55% so @@ -636,7 +636,7 @@ function strategyVeteran() { for (const [slot, info] of Object.entries(STATE.itemsKnown)) { if (slot === 'p') continue; const def = ITEMS[parseInt(info)]; - if (def && def.t === 'spirit') return { type: 'item', id: slot, selfTarget: true }; + if (def && def.t === 'spirit') return { type: 'item', id: slot, selfTarget: true, _reason: `sp${(STATE.sp*100).toFixed(0)}` }; } } @@ -655,7 +655,7 @@ function strategyVeteran() { for (const b of BUFF_PRIORITY) { if (shouldBuff(b.icon, b.minTurns) && STATE.mp > 0.10 && hasSpell(b.spell)) { if (b.icon === 'regen' && STATE.hp >= CFG.cureRegenHP) continue; - return { type: 'spell', name: b.spell, selfTarget: true }; + return { type: 'spell', name: b.spell, selfTarget: true, _reason: 'buff' }; } } } @@ -706,20 +706,20 @@ function strategyVeteran() { // Rending Blow: AoE armor pen vs 5+ enemies (highest priority in Grindfest) if (STATE.monsters.length >= 5 && STATE.skillsKnown.includes('Rending Blow')) { const t = isHighThreat ? findDangerousMonster() : findStrongestMonster(); - if (t >= 0) return { type: 'skill', name: 'Rending Blow', target: t }; + if (t >= 0) return { type: 'skill', name: 'Rending Blow', target: t, _reason: isHighThreat ? 'threat' : 'oc' }; } // Shatter Strike: AoE stun vs 5+ enemies (needs Penetrated Armor) if (STATE.monsters.length >= 5 && STATE.skillsKnown.includes('Shatter Strike')) { const hasArmorBreak = STATE.monsters.some((m, i) => checkMonsterDebuff(i, 'penetrated_armor')); if (hasArmorBreak) { const t = isHighThreat ? findDangerousMonster() : findStrongestMonster(); - if (t >= 0) return { type: 'skill', name: 'Shatter Strike', target: t }; + if (t >= 0) return { type: 'skill', name: 'Shatter Strike', target: t, _reason: 'stun' }; } } // Great Cleave: boss/rare fights or single-target threat if (anyRareMonster() && STATE.skillsKnown.includes('Great Cleave')) { const t = isHighThreat ? findDangerousMonster() : findStrongestMonster(); - if (t >= 0) return { type: 'skill', name: 'Great Cleave', target: t }; + if (t >= 0) return { type: 'skill', name: 'Great Cleave', target: t, _reason: isHighThreat ? 'threat' : 'boss' }; } // If high threat and no AoE skill available, use any weapon skill on the most dangerous if (isHighThreat && STATE.oc >= (CFG.skillOC || 80)) { @@ -728,21 +728,36 @@ function strategyVeteran() { for (const sk of ps) { if (STATE.skillsKnown.includes(sk)) { const t = findDangerousMonster(); - if (t >= 0) return { type: 'skill', name: sk, target: t }; + if (t >= 0) return { type: 'skill', name: sk, target: t, _reason: 'threat' }; } } } } - // 7. Damage spells + // 5. Spirit gem auto-consume + const spGem = document.getElementById('ikey_p'); + const spGemId = spGem ? (spGem.getAttribute('onmouseover') || '').match(/set_infopane_item\((\d+)\)/) : null; + if (spGemId && spGemId[1] === '10007') { + if (hasUsableGem('spirit')) return { type: 'item', id: 'p', selfTarget: true, _reason: 'gem' }; + } + // Spirit potions — only at SP < threshold (Spark needs 50%) + if (STATE.sp < CFG.spiritPotionSP) { + for (const [slot, info] of Object.entries(STATE.itemsKnown)) { + if (slot === 'p') continue; + const def = ITEMS[parseInt(info)]; + if (def && def.t === 'spirit') return { type: 'item', id: slot, selfTarget: true, _reason: `sp${(STATE.sp*100).toFixed(0)}` }; + } + } + + // 6. Damage spells const dmg = CFG.useAttackSpells ? getBestDamageSpell() : null; if (dmg && STATE.mp > 0.15) { const t = STATE.monsters.length > 1 ? findWeakestMonster() : 0; if (t >= 0 && STATE.monsters[t] && STATE.monsters[t].hasAttribute('onclick')) - return { type: 'spell', name: dmg, target: t }; + return { type: 'spell', name: dmg, target: t, _reason: 'dmg' }; } - // 8. Channeling kickstart or basic attack + // 7. Channeling kickstart or basic attack { const k = kickstartChanneling(); if (k) return k; @@ -754,8 +769,8 @@ function strategyVeteran() { if (ch) return ch; } - const t = findWeakestMonster(); + const t = hasHighThreat(THREAT_PX) ? findDangerousMonster() : findWeakestMonster(); if (t >= 0 && STATE.monsters[t] && STATE.monsters[t].hasAttribute('onclick')) - return { type: 'attack', target: t }; + return { type: 'attack', target: t, _reason: hasHighThreat(THREAT_PX) ? 'threat' : 'fallback' }; return null; }