v0.13.4 - Round 1 buff-up: cheapest first, channeling on most expensive
- Added castInitialBuffs() that fires when 60%+ buffs are down (first round) - Without channeling: casts cheapest buff first (more casts = more procs) - With channeling: casts most expensive remaining buff (best value for free cast) - Added isFirstRoundBuffs() helper: checks if most buffs are missing - Injected into all three tiers (Novice, Adept, Veteran) before the regular BUFF_PRIORITY loop - Added STATE.battleRound counter for future round tracking
This commit is contained in:
parent
badfcde640
commit
5f61a5c4fc
4 changed files with 180 additions and 0 deletions
|
|
@ -76,6 +76,7 @@ const STATE = {
|
||||||
battleMode: '',
|
battleMode: '',
|
||||||
inBattle: false,
|
inBattle: false,
|
||||||
battleInitialized: false,
|
battleInitialized: false,
|
||||||
|
battleRound: 0, // Incremented each round for first-round logic
|
||||||
|
|
||||||
hoverTarget: -1,
|
hoverTarget: -1,
|
||||||
interruptHover: false,
|
interruptHover: false,
|
||||||
|
|
@ -791,6 +792,50 @@ function getBestDamageSpell() {
|
||||||
// Best trigger spells: Regen/Absorb (17 MP = ~6% proc chance each cast)
|
// Best trigger spells: Regen/Absorb (17 MP = ~6% proc chance each cast)
|
||||||
// Cheaper spells have proportionally lower proc chance.
|
// Cheaper spells have proportionally lower proc chance.
|
||||||
|
|
||||||
|
// Checks if we're in the initial buff-up phase (most buffs are missing)
|
||||||
|
function isFirstRoundBuffs() {
|
||||||
|
let missing = 0, total = 0;
|
||||||
|
for (const b of BUFF_PRIORITY) {
|
||||||
|
if (!hasSpell(b.spell)) continue;
|
||||||
|
total++;
|
||||||
|
if (buffDuration(b.icon) === 0) missing++;
|
||||||
|
}
|
||||||
|
return total > 0 && missing >= total * 0.6; // 60%+ of our buffs are down
|
||||||
|
}
|
||||||
|
|
||||||
|
// Round 1 strategy: cast buffs from cheapest to most expensive.
|
||||||
|
// - Cheaper spells first = more casts before channeling = more proc chances
|
||||||
|
// - If channeling procs mid-sequence, switch to most expensive remaining buff
|
||||||
|
// Returns the next buff to cast, or null if all buffs are up.
|
||||||
|
function castInitialBuffs() {
|
||||||
|
if (!isFirstRoundBuffs()) return null;
|
||||||
|
if (STATE.mp < 0.20) return null;
|
||||||
|
|
||||||
|
// Collect all missing/expiring buffs with their MP cost
|
||||||
|
const candidates = [];
|
||||||
|
for (const b of BUFF_PRIORITY) {
|
||||||
|
if (!hasSpell(b.spell)) continue;
|
||||||
|
const dur = buffDuration(b.icon);
|
||||||
|
if (dur === 0 || dur < 3) {
|
||||||
|
if (b.icon === 'regen' && STATE.hp >= CFG.cureRegenHP) continue;
|
||||||
|
const sp = findSpell(b.spell);
|
||||||
|
candidates.push({ ...b, mpCost: sp ? sp.mp : 999, dur });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (candidates.length === 0) return null;
|
||||||
|
|
||||||
|
if (STATE.channeling) {
|
||||||
|
// Channeling is charged — cast the MOST expensive buff for best value
|
||||||
|
candidates.sort((a, b) => b.mpCost - a.mpCost);
|
||||||
|
} else {
|
||||||
|
// No channeling — cast the CHEAPEST buff first for more proc attempts
|
||||||
|
candidates.sort((a, b) => a.mpCost - b.mpCost);
|
||||||
|
}
|
||||||
|
|
||||||
|
return { type: 'spell', name: candidates[0].spell, selfTarget: true };
|
||||||
|
}
|
||||||
|
|
||||||
function kickstartChanneling() {
|
function kickstartChanneling() {
|
||||||
if (STATE.channeling) return null;
|
if (STATE.channeling) return null;
|
||||||
|
|
||||||
|
|
@ -909,6 +954,11 @@ function strategyNovice() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. Buffs — cast if missing or about to expire
|
// 3. Buffs — cast if missing or about to expire
|
||||||
|
// On first round, cast cheapest-first to proc channeling
|
||||||
|
{
|
||||||
|
const ib = castInitialBuffs();
|
||||||
|
if (ib) return ib;
|
||||||
|
}
|
||||||
for (const b of BUFF_PRIORITY) {
|
for (const b of BUFF_PRIORITY) {
|
||||||
if (shouldBuff(b.icon, b.minTurns) && STATE.mp > 0.2 && hasSpell(b.spell)) {
|
if (shouldBuff(b.icon, b.minTurns) && STATE.mp > 0.2 && hasSpell(b.spell)) {
|
||||||
if (b.icon === 'regen' && STATE.hp >= CFG.cureRegenHP) continue;
|
if (b.icon === 'regen' && STATE.hp >= CFG.cureRegenHP) continue;
|
||||||
|
|
@ -1019,6 +1069,11 @@ function strategyAdept() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. Buffs
|
// 3. Buffs
|
||||||
|
// On first round, cast cheapest-first to proc channeling
|
||||||
|
{
|
||||||
|
const ib = castInitialBuffs();
|
||||||
|
if (ib) return ib;
|
||||||
|
}
|
||||||
for (const b of BUFF_PRIORITY) {
|
for (const b of BUFF_PRIORITY) {
|
||||||
if (shouldBuff(b.icon, b.minTurns) && STATE.mp > 0.2 && hasSpell(b.spell)) {
|
if (shouldBuff(b.icon, b.minTurns) && STATE.mp > 0.2 && hasSpell(b.spell)) {
|
||||||
if (b.icon === 'regen' && STATE.hp >= CFG.cureRegenHP) continue;
|
if (b.icon === 'regen' && STATE.hp >= CFG.cureRegenHP) continue;
|
||||||
|
|
@ -1141,7 +1196,12 @@ function strategyVeteran() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. Buffs
|
// 3. Buffs
|
||||||
|
// On first round, cast cheapest-first to proc channeling
|
||||||
if (CFG.autoBuff) {
|
if (CFG.autoBuff) {
|
||||||
|
{
|
||||||
|
const ib = castInitialBuffs();
|
||||||
|
if (ib) return ib;
|
||||||
|
}
|
||||||
for (const b of BUFF_PRIORITY) {
|
for (const b of BUFF_PRIORITY) {
|
||||||
if (shouldBuff(b.icon, b.minTurns) && STATE.mp > 0.2 && hasSpell(b.spell)) {
|
if (shouldBuff(b.icon, b.minTurns) && STATE.mp > 0.2 && hasSpell(b.spell)) {
|
||||||
if (b.icon === 'regen' && STATE.hp >= CFG.cureRegenHP) continue;
|
if (b.icon === 'regen' && STATE.hp >= CFG.cureRegenHP) continue;
|
||||||
|
|
|
||||||
|
|
@ -76,6 +76,7 @@ const STATE = {
|
||||||
battleMode: '',
|
battleMode: '',
|
||||||
inBattle: false,
|
inBattle: false,
|
||||||
battleInitialized: false,
|
battleInitialized: false,
|
||||||
|
battleRound: 0, // Incremented each round for first-round logic
|
||||||
|
|
||||||
hoverTarget: -1,
|
hoverTarget: -1,
|
||||||
interruptHover: false,
|
interruptHover: false,
|
||||||
|
|
@ -791,6 +792,50 @@ function getBestDamageSpell() {
|
||||||
// Best trigger spells: Regen/Absorb (17 MP = ~6% proc chance each cast)
|
// Best trigger spells: Regen/Absorb (17 MP = ~6% proc chance each cast)
|
||||||
// Cheaper spells have proportionally lower proc chance.
|
// Cheaper spells have proportionally lower proc chance.
|
||||||
|
|
||||||
|
// Checks if we're in the initial buff-up phase (most buffs are missing)
|
||||||
|
function isFirstRoundBuffs() {
|
||||||
|
let missing = 0, total = 0;
|
||||||
|
for (const b of BUFF_PRIORITY) {
|
||||||
|
if (!hasSpell(b.spell)) continue;
|
||||||
|
total++;
|
||||||
|
if (buffDuration(b.icon) === 0) missing++;
|
||||||
|
}
|
||||||
|
return total > 0 && missing >= total * 0.6; // 60%+ of our buffs are down
|
||||||
|
}
|
||||||
|
|
||||||
|
// Round 1 strategy: cast buffs from cheapest to most expensive.
|
||||||
|
// - Cheaper spells first = more casts before channeling = more proc chances
|
||||||
|
// - If channeling procs mid-sequence, switch to most expensive remaining buff
|
||||||
|
// Returns the next buff to cast, or null if all buffs are up.
|
||||||
|
function castInitialBuffs() {
|
||||||
|
if (!isFirstRoundBuffs()) return null;
|
||||||
|
if (STATE.mp < 0.20) return null;
|
||||||
|
|
||||||
|
// Collect all missing/expiring buffs with their MP cost
|
||||||
|
const candidates = [];
|
||||||
|
for (const b of BUFF_PRIORITY) {
|
||||||
|
if (!hasSpell(b.spell)) continue;
|
||||||
|
const dur = buffDuration(b.icon);
|
||||||
|
if (dur === 0 || dur < 3) {
|
||||||
|
if (b.icon === 'regen' && STATE.hp >= CFG.cureRegenHP) continue;
|
||||||
|
const sp = findSpell(b.spell);
|
||||||
|
candidates.push({ ...b, mpCost: sp ? sp.mp : 999, dur });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (candidates.length === 0) return null;
|
||||||
|
|
||||||
|
if (STATE.channeling) {
|
||||||
|
// Channeling is charged — cast the MOST expensive buff for best value
|
||||||
|
candidates.sort((a, b) => b.mpCost - a.mpCost);
|
||||||
|
} else {
|
||||||
|
// No channeling — cast the CHEAPEST buff first for more proc attempts
|
||||||
|
candidates.sort((a, b) => a.mpCost - b.mpCost);
|
||||||
|
}
|
||||||
|
|
||||||
|
return { type: 'spell', name: candidates[0].spell, selfTarget: true };
|
||||||
|
}
|
||||||
|
|
||||||
function kickstartChanneling() {
|
function kickstartChanneling() {
|
||||||
if (STATE.channeling) return null;
|
if (STATE.channeling) return null;
|
||||||
|
|
||||||
|
|
@ -909,6 +954,11 @@ function strategyNovice() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. Buffs — cast if missing or about to expire
|
// 3. Buffs — cast if missing or about to expire
|
||||||
|
// On first round, cast cheapest-first to proc channeling
|
||||||
|
{
|
||||||
|
const ib = castInitialBuffs();
|
||||||
|
if (ib) return ib;
|
||||||
|
}
|
||||||
for (const b of BUFF_PRIORITY) {
|
for (const b of BUFF_PRIORITY) {
|
||||||
if (shouldBuff(b.icon, b.minTurns) && STATE.mp > 0.2 && hasSpell(b.spell)) {
|
if (shouldBuff(b.icon, b.minTurns) && STATE.mp > 0.2 && hasSpell(b.spell)) {
|
||||||
if (b.icon === 'regen' && STATE.hp >= CFG.cureRegenHP) continue;
|
if (b.icon === 'regen' && STATE.hp >= CFG.cureRegenHP) continue;
|
||||||
|
|
@ -1019,6 +1069,11 @@ function strategyAdept() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. Buffs
|
// 3. Buffs
|
||||||
|
// On first round, cast cheapest-first to proc channeling
|
||||||
|
{
|
||||||
|
const ib = castInitialBuffs();
|
||||||
|
if (ib) return ib;
|
||||||
|
}
|
||||||
for (const b of BUFF_PRIORITY) {
|
for (const b of BUFF_PRIORITY) {
|
||||||
if (shouldBuff(b.icon, b.minTurns) && STATE.mp > 0.2 && hasSpell(b.spell)) {
|
if (shouldBuff(b.icon, b.minTurns) && STATE.mp > 0.2 && hasSpell(b.spell)) {
|
||||||
if (b.icon === 'regen' && STATE.hp >= CFG.cureRegenHP) continue;
|
if (b.icon === 'regen' && STATE.hp >= CFG.cureRegenHP) continue;
|
||||||
|
|
@ -1141,7 +1196,12 @@ function strategyVeteran() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. Buffs
|
// 3. Buffs
|
||||||
|
// On first round, cast cheapest-first to proc channeling
|
||||||
if (CFG.autoBuff) {
|
if (CFG.autoBuff) {
|
||||||
|
{
|
||||||
|
const ib = castInitialBuffs();
|
||||||
|
if (ib) return ib;
|
||||||
|
}
|
||||||
for (const b of BUFF_PRIORITY) {
|
for (const b of BUFF_PRIORITY) {
|
||||||
if (shouldBuff(b.icon, b.minTurns) && STATE.mp > 0.2 && hasSpell(b.spell)) {
|
if (shouldBuff(b.icon, b.minTurns) && STATE.mp > 0.2 && hasSpell(b.spell)) {
|
||||||
if (b.icon === 'regen' && STATE.hp >= CFG.cureRegenHP) continue;
|
if (b.icon === 'regen' && STATE.hp >= CFG.cureRegenHP) continue;
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ const STATE = {
|
||||||
battleMode: '',
|
battleMode: '',
|
||||||
inBattle: false,
|
inBattle: false,
|
||||||
battleInitialized: false,
|
battleInitialized: false,
|
||||||
|
battleRound: 0, // Incremented each round for first-round logic
|
||||||
|
|
||||||
hoverTarget: -1,
|
hoverTarget: -1,
|
||||||
interruptHover: false,
|
interruptHover: false,
|
||||||
|
|
|
||||||
|
|
@ -143,6 +143,50 @@ function getBestDamageSpell() {
|
||||||
// Best trigger spells: Regen/Absorb (17 MP = ~6% proc chance each cast)
|
// Best trigger spells: Regen/Absorb (17 MP = ~6% proc chance each cast)
|
||||||
// Cheaper spells have proportionally lower proc chance.
|
// Cheaper spells have proportionally lower proc chance.
|
||||||
|
|
||||||
|
// Checks if we're in the initial buff-up phase (most buffs are missing)
|
||||||
|
function isFirstRoundBuffs() {
|
||||||
|
let missing = 0, total = 0;
|
||||||
|
for (const b of BUFF_PRIORITY) {
|
||||||
|
if (!hasSpell(b.spell)) continue;
|
||||||
|
total++;
|
||||||
|
if (buffDuration(b.icon) === 0) missing++;
|
||||||
|
}
|
||||||
|
return total > 0 && missing >= total * 0.6; // 60%+ of our buffs are down
|
||||||
|
}
|
||||||
|
|
||||||
|
// Round 1 strategy: cast buffs from cheapest to most expensive.
|
||||||
|
// - Cheaper spells first = more casts before channeling = more proc chances
|
||||||
|
// - If channeling procs mid-sequence, switch to most expensive remaining buff
|
||||||
|
// Returns the next buff to cast, or null if all buffs are up.
|
||||||
|
function castInitialBuffs() {
|
||||||
|
if (!isFirstRoundBuffs()) return null;
|
||||||
|
if (STATE.mp < 0.20) return null;
|
||||||
|
|
||||||
|
// Collect all missing/expiring buffs with their MP cost
|
||||||
|
const candidates = [];
|
||||||
|
for (const b of BUFF_PRIORITY) {
|
||||||
|
if (!hasSpell(b.spell)) continue;
|
||||||
|
const dur = buffDuration(b.icon);
|
||||||
|
if (dur === 0 || dur < 3) {
|
||||||
|
if (b.icon === 'regen' && STATE.hp >= CFG.cureRegenHP) continue;
|
||||||
|
const sp = findSpell(b.spell);
|
||||||
|
candidates.push({ ...b, mpCost: sp ? sp.mp : 999, dur });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (candidates.length === 0) return null;
|
||||||
|
|
||||||
|
if (STATE.channeling) {
|
||||||
|
// Channeling is charged — cast the MOST expensive buff for best value
|
||||||
|
candidates.sort((a, b) => b.mpCost - a.mpCost);
|
||||||
|
} else {
|
||||||
|
// No channeling — cast the CHEAPEST buff first for more proc attempts
|
||||||
|
candidates.sort((a, b) => a.mpCost - b.mpCost);
|
||||||
|
}
|
||||||
|
|
||||||
|
return { type: 'spell', name: candidates[0].spell, selfTarget: true };
|
||||||
|
}
|
||||||
|
|
||||||
function kickstartChanneling() {
|
function kickstartChanneling() {
|
||||||
if (STATE.channeling) return null;
|
if (STATE.channeling) return null;
|
||||||
|
|
||||||
|
|
@ -261,6 +305,11 @@ function strategyNovice() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. Buffs — cast if missing or about to expire
|
// 3. Buffs — cast if missing or about to expire
|
||||||
|
// On first round, cast cheapest-first to proc channeling
|
||||||
|
{
|
||||||
|
const ib = castInitialBuffs();
|
||||||
|
if (ib) return ib;
|
||||||
|
}
|
||||||
for (const b of BUFF_PRIORITY) {
|
for (const b of BUFF_PRIORITY) {
|
||||||
if (shouldBuff(b.icon, b.minTurns) && STATE.mp > 0.2 && hasSpell(b.spell)) {
|
if (shouldBuff(b.icon, b.minTurns) && STATE.mp > 0.2 && hasSpell(b.spell)) {
|
||||||
if (b.icon === 'regen' && STATE.hp >= CFG.cureRegenHP) continue;
|
if (b.icon === 'regen' && STATE.hp >= CFG.cureRegenHP) continue;
|
||||||
|
|
@ -371,6 +420,11 @@ function strategyAdept() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. Buffs
|
// 3. Buffs
|
||||||
|
// On first round, cast cheapest-first to proc channeling
|
||||||
|
{
|
||||||
|
const ib = castInitialBuffs();
|
||||||
|
if (ib) return ib;
|
||||||
|
}
|
||||||
for (const b of BUFF_PRIORITY) {
|
for (const b of BUFF_PRIORITY) {
|
||||||
if (shouldBuff(b.icon, b.minTurns) && STATE.mp > 0.2 && hasSpell(b.spell)) {
|
if (shouldBuff(b.icon, b.minTurns) && STATE.mp > 0.2 && hasSpell(b.spell)) {
|
||||||
if (b.icon === 'regen' && STATE.hp >= CFG.cureRegenHP) continue;
|
if (b.icon === 'regen' && STATE.hp >= CFG.cureRegenHP) continue;
|
||||||
|
|
@ -493,7 +547,12 @@ function strategyVeteran() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. Buffs
|
// 3. Buffs
|
||||||
|
// On first round, cast cheapest-first to proc channeling
|
||||||
if (CFG.autoBuff) {
|
if (CFG.autoBuff) {
|
||||||
|
{
|
||||||
|
const ib = castInitialBuffs();
|
||||||
|
if (ib) return ib;
|
||||||
|
}
|
||||||
for (const b of BUFF_PRIORITY) {
|
for (const b of BUFF_PRIORITY) {
|
||||||
if (shouldBuff(b.icon, b.minTurns) && STATE.mp > 0.2 && hasSpell(b.spell)) {
|
if (shouldBuff(b.icon, b.minTurns) && STATE.mp > 0.2 && hasSpell(b.spell)) {
|
||||||
if (b.icon === 'regen' && STATE.hp >= CFG.cureRegenHP) continue;
|
if (b.icon === 'regen' && STATE.hp >= CFG.cureRegenHP) continue;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue