Fix channeling: consume charge on useful spell, don't let it wear off

Channeling works: proc on mana-costing spell -> next spell costs 1 MP
and is 50% stronger/longer. The charge lasts 5 ticks (15 with Mystic
Gem) then wears off and is wasted.

Previously the script would basic attack with channeling charged,
wasting the bonus. Now:
- If channeling is active and nothing more urgent, still cast a buff
  (refresh anything under 10 turns) or a cheap damage spell
- Never basic attack while channeling is charged
- Novice/Adept/Veteran all have this protection
This commit is contained in:
GaboGG 2026-07-21 14:45:23 -04:00
parent 7093409580
commit d9fc1e547c
3 changed files with 189 additions and 12 deletions

View file

@ -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 };

View file

@ -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 };

View file

@ -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 };