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:
parent
7093409580
commit
d9fc1e547c
3 changed files with 189 additions and 12 deletions
|
|
@ -915,11 +915,31 @@ function strategyNovice() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 7. Channeling kickstart or basic attack — builds OC naturally
|
// 7. Channeling kickstart or basic attack
|
||||||
{
|
{
|
||||||
const k = kickstartChanneling();
|
const k = kickstartChanneling();
|
||||||
if (k) return k;
|
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();
|
const t = findWeakestMonster();
|
||||||
if (t >= 0 && STATE.monsters[t] && STATE.monsters[t].hasAttribute('onclick'))
|
if (t >= 0 && STATE.monsters[t] && STATE.monsters[t].hasAttribute('onclick'))
|
||||||
return { type: 'attack', target: t };
|
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
|
// 5. Weapon skills — before spirit stance
|
||||||
// and miss the skill window. This is the highest-value use of OC.
|
const skillOC_N = CFG.skillOC || 75;
|
||||||
const skillOC_N = CFG.skillOC || 80;
|
|
||||||
if (STATE.oc >= skillOC_N) {
|
if (STATE.oc >= skillOC_N) {
|
||||||
// Rending Blow: AoE armor pen vs 5+ enemies (highest priority in Grindfest)
|
// Rending Blow: AoE armor pen vs 5+ enemies (highest priority in Grindfest)
|
||||||
if (STATE.monsters.length >= 5 && STATE.skillsKnown.includes('Rending Blow')) {
|
if (STATE.monsters.length >= 5 && STATE.skillsKnown.includes('Rending Blow')) {
|
||||||
|
|
@ -1030,6 +1049,26 @@ function strategyAdept() {
|
||||||
const k = kickstartChanneling();
|
const k = kickstartChanneling();
|
||||||
if (k) return k;
|
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();
|
const t = findWeakestMonster();
|
||||||
if (t >= 0 && STATE.monsters[t] && STATE.monsters[t].hasAttribute('onclick'))
|
if (t >= 0 && STATE.monsters[t] && STATE.monsters[t].hasAttribute('onclick'))
|
||||||
return { type: 'attack', target: t };
|
return { type: 'attack', target: t };
|
||||||
|
|
@ -1142,6 +1181,26 @@ function strategyVeteran() {
|
||||||
const k = kickstartChanneling();
|
const k = kickstartChanneling();
|
||||||
if (k) return k;
|
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();
|
const t = findWeakestMonster();
|
||||||
if (t >= 0 && STATE.monsters[t] && STATE.monsters[t].hasAttribute('onclick'))
|
if (t >= 0 && STATE.monsters[t] && STATE.monsters[t].hasAttribute('onclick'))
|
||||||
return { type: 'attack', target: t };
|
return { type: 'attack', target: t };
|
||||||
|
|
|
||||||
|
|
@ -915,11 +915,31 @@ function strategyNovice() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 7. Channeling kickstart or basic attack — builds OC naturally
|
// 7. Channeling kickstart or basic attack
|
||||||
{
|
{
|
||||||
const k = kickstartChanneling();
|
const k = kickstartChanneling();
|
||||||
if (k) return k;
|
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();
|
const t = findWeakestMonster();
|
||||||
if (t >= 0 && STATE.monsters[t] && STATE.monsters[t].hasAttribute('onclick'))
|
if (t >= 0 && STATE.monsters[t] && STATE.monsters[t].hasAttribute('onclick'))
|
||||||
return { type: 'attack', target: t };
|
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
|
// 5. Weapon skills — before spirit stance
|
||||||
// and miss the skill window. This is the highest-value use of OC.
|
const skillOC_N = CFG.skillOC || 75;
|
||||||
const skillOC_N = CFG.skillOC || 80;
|
|
||||||
if (STATE.oc >= skillOC_N) {
|
if (STATE.oc >= skillOC_N) {
|
||||||
// Rending Blow: AoE armor pen vs 5+ enemies (highest priority in Grindfest)
|
// Rending Blow: AoE armor pen vs 5+ enemies (highest priority in Grindfest)
|
||||||
if (STATE.monsters.length >= 5 && STATE.skillsKnown.includes('Rending Blow')) {
|
if (STATE.monsters.length >= 5 && STATE.skillsKnown.includes('Rending Blow')) {
|
||||||
|
|
@ -1030,6 +1049,26 @@ function strategyAdept() {
|
||||||
const k = kickstartChanneling();
|
const k = kickstartChanneling();
|
||||||
if (k) return k;
|
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();
|
const t = findWeakestMonster();
|
||||||
if (t >= 0 && STATE.monsters[t] && STATE.monsters[t].hasAttribute('onclick'))
|
if (t >= 0 && STATE.monsters[t] && STATE.monsters[t].hasAttribute('onclick'))
|
||||||
return { type: 'attack', target: t };
|
return { type: 'attack', target: t };
|
||||||
|
|
@ -1142,6 +1181,26 @@ function strategyVeteran() {
|
||||||
const k = kickstartChanneling();
|
const k = kickstartChanneling();
|
||||||
if (k) return k;
|
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();
|
const t = findWeakestMonster();
|
||||||
if (t >= 0 && STATE.monsters[t] && STATE.monsters[t].hasAttribute('onclick'))
|
if (t >= 0 && STATE.monsters[t] && STATE.monsters[t].hasAttribute('onclick'))
|
||||||
return { type: 'attack', target: t };
|
return { type: 'attack', target: t };
|
||||||
|
|
|
||||||
|
|
@ -270,11 +270,31 @@ function strategyNovice() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 7. Channeling kickstart or basic attack — builds OC naturally
|
// 7. Channeling kickstart or basic attack
|
||||||
{
|
{
|
||||||
const k = kickstartChanneling();
|
const k = kickstartChanneling();
|
||||||
if (k) return k;
|
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();
|
const t = findWeakestMonster();
|
||||||
if (t >= 0 && STATE.monsters[t] && STATE.monsters[t].hasAttribute('onclick'))
|
if (t >= 0 && STATE.monsters[t] && STATE.monsters[t].hasAttribute('onclick'))
|
||||||
return { type: 'attack', target: t };
|
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
|
// 5. Weapon skills — before spirit stance
|
||||||
// and miss the skill window. This is the highest-value use of OC.
|
const skillOC_N = CFG.skillOC || 75;
|
||||||
const skillOC_N = CFG.skillOC || 80;
|
|
||||||
if (STATE.oc >= skillOC_N) {
|
if (STATE.oc >= skillOC_N) {
|
||||||
// Rending Blow: AoE armor pen vs 5+ enemies (highest priority in Grindfest)
|
// Rending Blow: AoE armor pen vs 5+ enemies (highest priority in Grindfest)
|
||||||
if (STATE.monsters.length >= 5 && STATE.skillsKnown.includes('Rending Blow')) {
|
if (STATE.monsters.length >= 5 && STATE.skillsKnown.includes('Rending Blow')) {
|
||||||
|
|
@ -385,6 +404,26 @@ function strategyAdept() {
|
||||||
const k = kickstartChanneling();
|
const k = kickstartChanneling();
|
||||||
if (k) return k;
|
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();
|
const t = findWeakestMonster();
|
||||||
if (t >= 0 && STATE.monsters[t] && STATE.monsters[t].hasAttribute('onclick'))
|
if (t >= 0 && STATE.monsters[t] && STATE.monsters[t].hasAttribute('onclick'))
|
||||||
return { type: 'attack', target: t };
|
return { type: 'attack', target: t };
|
||||||
|
|
@ -497,6 +536,26 @@ function strategyVeteran() {
|
||||||
const k = kickstartChanneling();
|
const k = kickstartChanneling();
|
||||||
if (k) return k;
|
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();
|
const t = findWeakestMonster();
|
||||||
if (t >= 0 && STATE.monsters[t] && STATE.monsters[t].hasAttribute('onclick'))
|
if (t >= 0 && STATE.monsters[t] && STATE.monsters[t].hasAttribute('onclick'))
|
||||||
return { type: 'attack', target: t };
|
return { type: 'attack', target: t };
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue