diff --git a/scripts/hv-unified.user.js b/scripts/hv-unified.user.js index c6ad7da..500caa2 100644 --- a/scripts/hv-unified.user.js +++ b/scripts/hv-unified.user.js @@ -915,11 +915,31 @@ function strategyNovice() { } } - // 7. Channeling kickstart or basic attack — builds OC naturally + // 7. Channeling kickstart or basic attack { const k = kickstartChanneling(); if (k) return k; } + + // If channeling is charged, consume it on a useful spell + if (STATE.channeling && STATE.mp > 0.10) { + for (const b of BUFF_PRIORITY) { + if (hasSpell(b.spell)) { + const dur = buffDuration(b.icon); + if (dur === 0 || dur < 10) { + if (b.icon === 'regen' && STATE.hp >= CFG.cureRegenHP) continue; + return { type: 'spell', name: b.spell, selfTarget: true }; + } + } + } + const cheap = findDmgSpell(['Fiery Blast', 'Freeze', 'Shockblast', 'Gale']); + if (cheap) { + const t = findWeakestMonster(); + if (t >= 0 && STATE.monsters[t] && STATE.monsters[t].hasAttribute('onclick')) + return { type: 'spell', name: cheap, target: t }; + } + } + const t = findWeakestMonster(); if (t >= 0 && STATE.monsters[t] && STATE.monsters[t].hasAttribute('onclick')) return { type: 'attack', target: t }; @@ -978,9 +998,8 @@ function strategyAdept() { } } - // 5. Weapon skills — before spirit stance, so we don't burn OC on spirit - // and miss the skill window. This is the highest-value use of OC. - const skillOC_N = CFG.skillOC || 80; + // 5. Weapon skills — before spirit stance + const skillOC_N = CFG.skillOC || 75; if (STATE.oc >= skillOC_N) { // Rending Blow: AoE armor pen vs 5+ enemies (highest priority in Grindfest) if (STATE.monsters.length >= 5 && STATE.skillsKnown.includes('Rending Blow')) { @@ -1030,6 +1049,26 @@ function strategyAdept() { const k = kickstartChanneling(); if (k) return k; } + + // Use channeling charge on a spell + if (STATE.channeling && STATE.mp > 0.10) { + for (const b of BUFF_PRIORITY) { + if (hasSpell(b.spell)) { + const dur = buffDuration(b.icon); + if (dur === 0 || dur < 10) { + if (b.icon === 'regen' && STATE.hp >= CFG.cureRegenHP) continue; + return { type: 'spell', name: b.spell, selfTarget: true }; + } + } + } + const cheap = findDmgSpell(['Fiery Blast', 'Freeze', 'Shockblast', 'Gale']); + if (cheap) { + const t = findWeakestMonster(); + if (t >= 0 && STATE.monsters[t] && STATE.monsters[t].hasAttribute('onclick')) + return { type: 'spell', name: cheap, target: t }; + } + } + const t = findWeakestMonster(); if (t >= 0 && STATE.monsters[t] && STATE.monsters[t].hasAttribute('onclick')) return { type: 'attack', target: t }; @@ -1142,6 +1181,26 @@ function strategyVeteran() { const k = kickstartChanneling(); if (k) return k; } + + // Consume channeling on a spell + if (STATE.channeling && STATE.mp > 0.10) { + for (const b of BUFF_PRIORITY) { + if (hasSpell(b.spell)) { + const dur = buffDuration(b.icon); + if (dur === 0 || dur < 10) { + if (b.icon === 'regen' && STATE.hp >= CFG.cureRegenHP) continue; + return { type: 'spell', name: b.spell, selfTarget: true }; + } + } + } + const cheap = findDmgSpell(['Fiery Blast', 'Freeze', 'Shockblast', 'Gale']); + if (cheap) { + const t = findWeakestMonster(); + if (t >= 0 && STATE.monsters[t] && STATE.monsters[t].hasAttribute('onclick')) + return { type: 'spell', name: cheap, target: t }; + } + } + const t = findWeakestMonster(); if (t >= 0 && STATE.monsters[t] && STATE.monsters[t].hasAttribute('onclick')) return { type: 'attack', target: t }; diff --git a/scripts/latest.user.js b/scripts/latest.user.js index c6ad7da..500caa2 100644 --- a/scripts/latest.user.js +++ b/scripts/latest.user.js @@ -915,11 +915,31 @@ function strategyNovice() { } } - // 7. Channeling kickstart or basic attack — builds OC naturally + // 7. Channeling kickstart or basic attack { const k = kickstartChanneling(); if (k) return k; } + + // If channeling is charged, consume it on a useful spell + if (STATE.channeling && STATE.mp > 0.10) { + for (const b of BUFF_PRIORITY) { + if (hasSpell(b.spell)) { + const dur = buffDuration(b.icon); + if (dur === 0 || dur < 10) { + if (b.icon === 'regen' && STATE.hp >= CFG.cureRegenHP) continue; + return { type: 'spell', name: b.spell, selfTarget: true }; + } + } + } + const cheap = findDmgSpell(['Fiery Blast', 'Freeze', 'Shockblast', 'Gale']); + if (cheap) { + const t = findWeakestMonster(); + if (t >= 0 && STATE.monsters[t] && STATE.monsters[t].hasAttribute('onclick')) + return { type: 'spell', name: cheap, target: t }; + } + } + const t = findWeakestMonster(); if (t >= 0 && STATE.monsters[t] && STATE.monsters[t].hasAttribute('onclick')) return { type: 'attack', target: t }; @@ -978,9 +998,8 @@ function strategyAdept() { } } - // 5. Weapon skills — before spirit stance, so we don't burn OC on spirit - // and miss the skill window. This is the highest-value use of OC. - const skillOC_N = CFG.skillOC || 80; + // 5. Weapon skills — before spirit stance + const skillOC_N = CFG.skillOC || 75; if (STATE.oc >= skillOC_N) { // Rending Blow: AoE armor pen vs 5+ enemies (highest priority in Grindfest) if (STATE.monsters.length >= 5 && STATE.skillsKnown.includes('Rending Blow')) { @@ -1030,6 +1049,26 @@ function strategyAdept() { const k = kickstartChanneling(); if (k) return k; } + + // Use channeling charge on a spell + if (STATE.channeling && STATE.mp > 0.10) { + for (const b of BUFF_PRIORITY) { + if (hasSpell(b.spell)) { + const dur = buffDuration(b.icon); + if (dur === 0 || dur < 10) { + if (b.icon === 'regen' && STATE.hp >= CFG.cureRegenHP) continue; + return { type: 'spell', name: b.spell, selfTarget: true }; + } + } + } + const cheap = findDmgSpell(['Fiery Blast', 'Freeze', 'Shockblast', 'Gale']); + if (cheap) { + const t = findWeakestMonster(); + if (t >= 0 && STATE.monsters[t] && STATE.monsters[t].hasAttribute('onclick')) + return { type: 'spell', name: cheap, target: t }; + } + } + const t = findWeakestMonster(); if (t >= 0 && STATE.monsters[t] && STATE.monsters[t].hasAttribute('onclick')) return { type: 'attack', target: t }; @@ -1142,6 +1181,26 @@ function strategyVeteran() { const k = kickstartChanneling(); if (k) return k; } + + // Consume channeling on a spell + if (STATE.channeling && STATE.mp > 0.10) { + for (const b of BUFF_PRIORITY) { + if (hasSpell(b.spell)) { + const dur = buffDuration(b.icon); + if (dur === 0 || dur < 10) { + if (b.icon === 'regen' && STATE.hp >= CFG.cureRegenHP) continue; + return { type: 'spell', name: b.spell, selfTarget: true }; + } + } + } + const cheap = findDmgSpell(['Fiery Blast', 'Freeze', 'Shockblast', 'Gale']); + if (cheap) { + const t = findWeakestMonster(); + if (t >= 0 && STATE.monsters[t] && STATE.monsters[t].hasAttribute('onclick')) + return { type: 'spell', name: cheap, target: t }; + } + } + const t = findWeakestMonster(); if (t >= 0 && STATE.monsters[t] && STATE.monsters[t].hasAttribute('onclick')) return { type: 'attack', target: t }; diff --git a/src/strategy-engine.js b/src/strategy-engine.js index bda2152..b066b98 100644 --- a/src/strategy-engine.js +++ b/src/strategy-engine.js @@ -270,11 +270,31 @@ function strategyNovice() { } } - // 7. Channeling kickstart or basic attack — builds OC naturally + // 7. Channeling kickstart or basic attack { const k = kickstartChanneling(); if (k) return k; } + + // If channeling is charged, consume it on a useful spell + if (STATE.channeling && STATE.mp > 0.10) { + for (const b of BUFF_PRIORITY) { + if (hasSpell(b.spell)) { + const dur = buffDuration(b.icon); + if (dur === 0 || dur < 10) { + if (b.icon === 'regen' && STATE.hp >= CFG.cureRegenHP) continue; + return { type: 'spell', name: b.spell, selfTarget: true }; + } + } + } + const cheap = findDmgSpell(['Fiery Blast', 'Freeze', 'Shockblast', 'Gale']); + if (cheap) { + const t = findWeakestMonster(); + if (t >= 0 && STATE.monsters[t] && STATE.monsters[t].hasAttribute('onclick')) + return { type: 'spell', name: cheap, target: t }; + } + } + const t = findWeakestMonster(); if (t >= 0 && STATE.monsters[t] && STATE.monsters[t].hasAttribute('onclick')) return { type: 'attack', target: t }; @@ -333,9 +353,8 @@ function strategyAdept() { } } - // 5. Weapon skills — before spirit stance, so we don't burn OC on spirit - // and miss the skill window. This is the highest-value use of OC. - const skillOC_N = CFG.skillOC || 80; + // 5. Weapon skills — before spirit stance + const skillOC_N = CFG.skillOC || 75; if (STATE.oc >= skillOC_N) { // Rending Blow: AoE armor pen vs 5+ enemies (highest priority in Grindfest) if (STATE.monsters.length >= 5 && STATE.skillsKnown.includes('Rending Blow')) { @@ -385,6 +404,26 @@ function strategyAdept() { const k = kickstartChanneling(); if (k) return k; } + + // Use channeling charge on a spell + if (STATE.channeling && STATE.mp > 0.10) { + for (const b of BUFF_PRIORITY) { + if (hasSpell(b.spell)) { + const dur = buffDuration(b.icon); + if (dur === 0 || dur < 10) { + if (b.icon === 'regen' && STATE.hp >= CFG.cureRegenHP) continue; + return { type: 'spell', name: b.spell, selfTarget: true }; + } + } + } + const cheap = findDmgSpell(['Fiery Blast', 'Freeze', 'Shockblast', 'Gale']); + if (cheap) { + const t = findWeakestMonster(); + if (t >= 0 && STATE.monsters[t] && STATE.monsters[t].hasAttribute('onclick')) + return { type: 'spell', name: cheap, target: t }; + } + } + const t = findWeakestMonster(); if (t >= 0 && STATE.monsters[t] && STATE.monsters[t].hasAttribute('onclick')) return { type: 'attack', target: t }; @@ -497,6 +536,26 @@ function strategyVeteran() { const k = kickstartChanneling(); if (k) return k; } + + // Consume channeling on a spell + if (STATE.channeling && STATE.mp > 0.10) { + for (const b of BUFF_PRIORITY) { + if (hasSpell(b.spell)) { + const dur = buffDuration(b.icon); + if (dur === 0 || dur < 10) { + if (b.icon === 'regen' && STATE.hp >= CFG.cureRegenHP) continue; + return { type: 'spell', name: b.spell, selfTarget: true }; + } + } + } + const cheap = findDmgSpell(['Fiery Blast', 'Freeze', 'Shockblast', 'Gale']); + if (cheap) { + const t = findWeakestMonster(); + if (t >= 0 && STATE.monsters[t] && STATE.monsters[t].hasAttribute('onclick')) + return { type: 'spell', name: cheap, target: t }; + } + } + const t = findWeakestMonster(); if (t >= 0 && STATE.monsters[t] && STATE.monsters[t].hasAttribute('onclick')) return { type: 'attack', target: t };