v0.13.30 - Fix spirit item detection, add threat-based targeting

- Fix: items on cooldown (no id attr) were invisible to parser
  Spirit Draught/Potion on cooldown couldn't be auto-consumed
- Fix: spiritPotionSP raised from 30% to 60%
- New: parse monster SP bar widths into STATE.monsterSp[]
- New: findDangerousMonster() — targets highest SP bar monster
- New: hasHighThreat() — true if 2+ monsters >85% SP (about to special)
- Weapon skills now fire on high-threat even without full OC
- Basic attacks prefer dangerous monsters in all tiers
This commit is contained in:
GaboGG 2026-07-27 13:07:39 -04:00
parent b6d78067ce
commit b5a5c3df54
7 changed files with 246 additions and 108 deletions

View file

@ -1,7 +1,7 @@
// ==UserScript==
// @name HV Unified
// @namespace hvunified
// @version 0.13.29
// @version 0.13.30
// @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.29';
const VERSION = '0.13.30';
const CFG = {
// — Battle automation
@ -89,6 +89,7 @@ const STATE = {
interruptAlert: false,
monsters: [],
monsterSp: [], // SP/OC bar levels per monster (0-120, high = about to special)
hp: 1,
mp: 1,
sp: 1,
@ -426,6 +427,13 @@ function parseBattleState() {
// ── Monsters ──
STATE.monsters = $$('.btm1[onclick]');
// Parse each monster's SP/OC bar level (the red bar = special attack charge)
STATE.monsterSp = [];
$$('.btm1').forEach(m => {
const spBar = m.querySelector('.btm5 img[src$="barred.png"]');
const spPct = spBar ? (parseInt(spBar.style.width) || 0) : 0;
STATE.monsterSp.push(spPct);
});
// ── Spells from spell pane ──
STATE.spellsKnown = [];
@ -485,12 +493,18 @@ function parseBattleState() {
// ── Items ──
STATE.itemsKnown = {};
$$('[id^="ikey_"]').forEach(el => {
const id = el.id.replace('ikey_', '');
const omo = el.getAttribute('onmouseover') || '';
const m = omo.match(/battle\.set_infopane_item\((\d+)\)/);
if (m) STATE.itemsKnown[id] = m[1];
});
// Read all item slots, including on-cooldown ones (which lose their id)
const itemPane = document.getElementById('pane_item');
if (itemPane) {
$$('.bti3 > div[onmouseover], .bti3 div[id^="ikey_"]', itemPane).forEach(el => {
const omo = el.getAttribute('onmouseover') || '';
const m = omo.match(/set_infopane_item\((\d+)\)/);
if (m) {
const slot = el.id ? el.id.replace('ikey_', '') : ('auto_' + m[1]);
STATE.itemsKnown[slot] = m[1];
}
});
}
// Buffs
STATE.buffs = {};
@ -717,6 +731,10 @@ function shouldBuff(name, minTurns) {
}
// Known buffs with their priority, icon name, spell name, and refresh threshold
// Threat detection: SP bar max is 120px, monsters >85% are about to special
const THREAT_PX = Math.round(120 * 85 / 100); // ~102px
// NOTE: Heartseeker and Arcane Focus are mutually exclusive. For a 2H (physical) build,
// Heartseeker is preferred (+25% phys dmg, +10% crit). Arcane Focus is for magic builds.
const BUFF_PRIORITY = [
@ -795,6 +813,30 @@ function findStrongestMonster() {
return best >= 0 ? best : 0;
}
// Find the monster with the highest SP/OC bar (most dangerous — about to special)
// The red bar (nbarred.png) has a max width of 120px
// Returns the index, or 0 if none found
function findDangerousMonster() {
let best = -1, bestSP = -1;
STATE.monsters.forEach((m, i) => {
if (!m || !m.hasAttribute || !m.hasAttribute('onclick')) return;
const sp = STATE.monsterSp[i] || 0;
if (sp > bestSP) { bestSP = sp; best = i; }
});
return best >= 0 ? best : 0;
}
// Check if there are 2+ monsters with SP bar > threshold (imminent special attacks)
function hasHighThreat(threshold) {
let count = 0;
STATE.monsters.forEach((m, i) => {
if (!m || !m.hasAttribute || !m.hasAttribute('onclick')) return;
const sp = STATE.monsterSp[i] || 0;
if (sp > threshold) count++;
});
return count >= 2;
}
function checkMonsterDebuff(idx, db) {
const m = STATE.monsters[idx];
if (!m) return false;
@ -1124,7 +1166,7 @@ function strategyNovice() {
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 null;
@ -1216,25 +1258,26 @@ function strategyAdept() {
}
}
// 5. Weapon skills — before spirit stance
// 5. Weapon skills — prioritize high-threat monsters (high SP/OC bar)
const isHighThreat = hasHighThreat(THREAT_PX);
const skillOC_N = CFG.skillOC || 75;
if (STATE.oc >= skillOC_N) {
if (STATE.oc >= skillOC_N || isHighThreat) {
// Rending Blow: AoE armor pen vs 5+ enemies (highest priority in Grindfest)
if (STATE.monsters.length >= 5 && STATE.skillsKnown.includes('Rending Blow')) {
const t = findStrongestMonster();
const t = isHighThreat ? findDangerousMonster() : findStrongestMonster();
if (t >= 0) return { type: 'skill', name: 'Rending Blow', target: t };
}
// Shatter Strike: AoE stun vs 5+ enemies (needs Penetrated Armor from Rending Blow)
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 = findStrongestMonster();
const t = isHighThreat ? findDangerousMonster() : findStrongestMonster();
if (t >= 0) return { type: 'skill', name: 'Shatter Strike', target: t };
}
}
// Great Cleave: boss/rare fights only (single target, lower priority than AoE)
if (anyRareMonster() && STATE.skillsKnown.includes('Great Cleave')) {
const t = findStrongestMonster();
const t = isHighThreat ? findDangerousMonster() : findStrongestMonster();
if (t >= 0) return { type: 'skill', name: 'Great Cleave', target: t };
}
}
@ -1259,7 +1302,7 @@ function strategyAdept() {
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 null;
@ -1349,35 +1392,38 @@ function strategyVeteran() {
}
}
// 6. Weapon skills only at OC thresholds (let OC build via basic attacks)
// 5. Weapon skills — prioritize high-threat monsters (high SP/OC bar)
// If 2+ enemies have >85% SP bar, they're about to special — emergency
const isHighThreat = hasHighThreat(THREAT_PX);
const askillOC3 = CFG.skillOC || 80;
if (STATE.oc >= askillOC3) {
// Great Cleave: boss/rare fights only
if (STATE.oc >= askillOC3 || isHighThreat) {
// Emergency: 2+ monsters about to special — spend OC to kill them fast
// 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 };
}
// 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 };
}
}
// Great Cleave: boss/rare fights or single-target threat
if (anyRareMonster() && STATE.skillsKnown.includes('Great Cleave')) {
const t = findStrongestMonster();
const t = isHighThreat ? findDangerousMonster() : findStrongestMonster();
if (t >= 0) return { type: 'skill', name: 'Great Cleave', target: t };
}
// Rending Blow + Shatter Strike: AoE vs 5+ enemies
if (STATE.monsters.length >= 5) {
if (STATE.skillsKnown.includes('Rending Blow')) {
const t = findStrongestMonster();
if (t >= 0) return { type: 'skill', name: 'Rending Blow', target: t };
}
if (STATE.skillsKnown.includes('Shatter Strike')) {
const hasArmorBreak = STATE.monsters.some((m, i) => checkMonsterDebuff(i, 'penetrated_armor'));
if (hasArmorBreak) {
const t = findStrongestMonster();
if (t >= 0) return { type: 'skill', name: 'Shatter Strike', target: t };
}
}
}
// Other weapon skills (non-2H): generic use
if (!STATE.skillsKnown.includes('Great Cleave')) {
// If high threat and no AoE skill available, use any weapon skill on the most dangerous
if (isHighThreat && STATE.oc >= (CFG.skillOC || 80)) {
const ps = ['Frenzied Blows', 'Skyward Sword', 'Iris Strike', 'Merciful Blow',
'Vital Strike', 'Shield Bash', 'Concussive Strike'];
for (const sk of ps) {
if (STATE.skillsKnown.includes(sk)) {
const t = findStrongestMonster();
const t = findDangerousMonster();
if (t >= 0) return { type: 'skill', name: sk, target: t };
}
}

View file

@ -1,7 +1,7 @@
// ==UserScript==
// @name HV Unified
// @namespace hvunified
// @version 0.13.29
// @version 0.13.30
// @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.29';
const VERSION = '0.13.30';
const CFG = {
// — Battle automation
@ -89,6 +89,7 @@ const STATE = {
interruptAlert: false,
monsters: [],
monsterSp: [], // SP/OC bar levels per monster (0-120, high = about to special)
hp: 1,
mp: 1,
sp: 1,
@ -426,6 +427,13 @@ function parseBattleState() {
// ── Monsters ──
STATE.monsters = $$('.btm1[onclick]');
// Parse each monster's SP/OC bar level (the red bar = special attack charge)
STATE.monsterSp = [];
$$('.btm1').forEach(m => {
const spBar = m.querySelector('.btm5 img[src$="barred.png"]');
const spPct = spBar ? (parseInt(spBar.style.width) || 0) : 0;
STATE.monsterSp.push(spPct);
});
// ── Spells from spell pane ──
STATE.spellsKnown = [];
@ -485,12 +493,18 @@ function parseBattleState() {
// ── Items ──
STATE.itemsKnown = {};
$$('[id^="ikey_"]').forEach(el => {
const id = el.id.replace('ikey_', '');
const omo = el.getAttribute('onmouseover') || '';
const m = omo.match(/battle\.set_infopane_item\((\d+)\)/);
if (m) STATE.itemsKnown[id] = m[1];
});
// Read all item slots, including on-cooldown ones (which lose their id)
const itemPane = document.getElementById('pane_item');
if (itemPane) {
$$('.bti3 > div[onmouseover], .bti3 div[id^="ikey_"]', itemPane).forEach(el => {
const omo = el.getAttribute('onmouseover') || '';
const m = omo.match(/set_infopane_item\((\d+)\)/);
if (m) {
const slot = el.id ? el.id.replace('ikey_', '') : ('auto_' + m[1]);
STATE.itemsKnown[slot] = m[1];
}
});
}
// Buffs
STATE.buffs = {};
@ -717,6 +731,10 @@ function shouldBuff(name, minTurns) {
}
// Known buffs with their priority, icon name, spell name, and refresh threshold
// Threat detection: SP bar max is 120px, monsters >85% are about to special
const THREAT_PX = Math.round(120 * 85 / 100); // ~102px
// NOTE: Heartseeker and Arcane Focus are mutually exclusive. For a 2H (physical) build,
// Heartseeker is preferred (+25% phys dmg, +10% crit). Arcane Focus is for magic builds.
const BUFF_PRIORITY = [
@ -795,6 +813,30 @@ function findStrongestMonster() {
return best >= 0 ? best : 0;
}
// Find the monster with the highest SP/OC bar (most dangerous — about to special)
// The red bar (nbarred.png) has a max width of 120px
// Returns the index, or 0 if none found
function findDangerousMonster() {
let best = -1, bestSP = -1;
STATE.monsters.forEach((m, i) => {
if (!m || !m.hasAttribute || !m.hasAttribute('onclick')) return;
const sp = STATE.monsterSp[i] || 0;
if (sp > bestSP) { bestSP = sp; best = i; }
});
return best >= 0 ? best : 0;
}
// Check if there are 2+ monsters with SP bar > threshold (imminent special attacks)
function hasHighThreat(threshold) {
let count = 0;
STATE.monsters.forEach((m, i) => {
if (!m || !m.hasAttribute || !m.hasAttribute('onclick')) return;
const sp = STATE.monsterSp[i] || 0;
if (sp > threshold) count++;
});
return count >= 2;
}
function checkMonsterDebuff(idx, db) {
const m = STATE.monsters[idx];
if (!m) return false;
@ -1124,7 +1166,7 @@ function strategyNovice() {
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 null;
@ -1216,25 +1258,26 @@ function strategyAdept() {
}
}
// 5. Weapon skills — before spirit stance
// 5. Weapon skills — prioritize high-threat monsters (high SP/OC bar)
const isHighThreat = hasHighThreat(THREAT_PX);
const skillOC_N = CFG.skillOC || 75;
if (STATE.oc >= skillOC_N) {
if (STATE.oc >= skillOC_N || isHighThreat) {
// Rending Blow: AoE armor pen vs 5+ enemies (highest priority in Grindfest)
if (STATE.monsters.length >= 5 && STATE.skillsKnown.includes('Rending Blow')) {
const t = findStrongestMonster();
const t = isHighThreat ? findDangerousMonster() : findStrongestMonster();
if (t >= 0) return { type: 'skill', name: 'Rending Blow', target: t };
}
// Shatter Strike: AoE stun vs 5+ enemies (needs Penetrated Armor from Rending Blow)
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 = findStrongestMonster();
const t = isHighThreat ? findDangerousMonster() : findStrongestMonster();
if (t >= 0) return { type: 'skill', name: 'Shatter Strike', target: t };
}
}
// Great Cleave: boss/rare fights only (single target, lower priority than AoE)
if (anyRareMonster() && STATE.skillsKnown.includes('Great Cleave')) {
const t = findStrongestMonster();
const t = isHighThreat ? findDangerousMonster() : findStrongestMonster();
if (t >= 0) return { type: 'skill', name: 'Great Cleave', target: t };
}
}
@ -1259,7 +1302,7 @@ function strategyAdept() {
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 null;
@ -1349,35 +1392,38 @@ function strategyVeteran() {
}
}
// 6. Weapon skills only at OC thresholds (let OC build via basic attacks)
// 5. Weapon skills — prioritize high-threat monsters (high SP/OC bar)
// If 2+ enemies have >85% SP bar, they're about to special — emergency
const isHighThreat = hasHighThreat(THREAT_PX);
const askillOC3 = CFG.skillOC || 80;
if (STATE.oc >= askillOC3) {
// Great Cleave: boss/rare fights only
if (STATE.oc >= askillOC3 || isHighThreat) {
// Emergency: 2+ monsters about to special — spend OC to kill them fast
// 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 };
}
// 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 };
}
}
// Great Cleave: boss/rare fights or single-target threat
if (anyRareMonster() && STATE.skillsKnown.includes('Great Cleave')) {
const t = findStrongestMonster();
const t = isHighThreat ? findDangerousMonster() : findStrongestMonster();
if (t >= 0) return { type: 'skill', name: 'Great Cleave', target: t };
}
// Rending Blow + Shatter Strike: AoE vs 5+ enemies
if (STATE.monsters.length >= 5) {
if (STATE.skillsKnown.includes('Rending Blow')) {
const t = findStrongestMonster();
if (t >= 0) return { type: 'skill', name: 'Rending Blow', target: t };
}
if (STATE.skillsKnown.includes('Shatter Strike')) {
const hasArmorBreak = STATE.monsters.some((m, i) => checkMonsterDebuff(i, 'penetrated_armor'));
if (hasArmorBreak) {
const t = findStrongestMonster();
if (t >= 0) return { type: 'skill', name: 'Shatter Strike', target: t };
}
}
}
// Other weapon skills (non-2H): generic use
if (!STATE.skillsKnown.includes('Great Cleave')) {
// If high threat and no AoE skill available, use any weapon skill on the most dangerous
if (isHighThreat && STATE.oc >= (CFG.skillOC || 80)) {
const ps = ['Frenzied Blows', 'Skyward Sword', 'Iris Strike', 'Merciful Blow',
'Vital Strike', 'Shield Bash', 'Concussive Strike'];
for (const sk of ps) {
if (STATE.skillsKnown.includes(sk)) {
const t = findStrongestMonster();
const t = findDangerousMonster();
if (t >= 0) return { type: 'skill', name: sk, target: t };
}
}

View file

@ -81,6 +81,13 @@ function parseBattleState() {
// ── Monsters ──
STATE.monsters = $$('.btm1[onclick]');
// Parse each monster's SP/OC bar level (the red bar = special attack charge)
STATE.monsterSp = [];
$$('.btm1').forEach(m => {
const spBar = m.querySelector('.btm5 img[src$="barred.png"]');
const spPct = spBar ? (parseInt(spBar.style.width) || 0) : 0;
STATE.monsterSp.push(spPct);
});
// ── Spells from spell pane ──
STATE.spellsKnown = [];
@ -140,12 +147,18 @@ function parseBattleState() {
// ── Items ──
STATE.itemsKnown = {};
$$('[id^="ikey_"]').forEach(el => {
const id = el.id.replace('ikey_', '');
const omo = el.getAttribute('onmouseover') || '';
const m = omo.match(/battle\.set_infopane_item\((\d+)\)/);
if (m) STATE.itemsKnown[id] = m[1];
});
// Read all item slots, including on-cooldown ones (which lose their id)
const itemPane = document.getElementById('pane_item');
if (itemPane) {
$$('.bti3 > div[onmouseover], .bti3 div[id^="ikey_"]', itemPane).forEach(el => {
const omo = el.getAttribute('onmouseover') || '';
const m = omo.match(/set_infopane_item\((\d+)\)/);
if (m) {
const slot = el.id ? el.id.replace('ikey_', '') : ('auto_' + m[1]);
STATE.itemsKnown[slot] = m[1];
}
});
}
// Buffs
STATE.buffs = {};

View file

@ -2,7 +2,7 @@
// CONFIG — default settings
// ═══════════════════════════════════════════════════════════════════════
const VERSION = '0.13.29';
const VERSION = '0.13.30';
const CFG = {
// — Battle automation

View file

@ -1,7 +1,7 @@
// ==UserScript==
// @name HV Unified
// @namespace hvunified
// @version 0.13.29
// @version 0.13.30
// @description HV Unified — battle automation, guidance, and UI enhancements for HentaiVerse
// @author GaboGG + Hermes
// @match *://*.hentaiverse.org/*

View file

@ -25,6 +25,7 @@ const STATE = {
interruptAlert: false,
monsters: [],
monsterSp: [], // SP/OC bar levels per monster (0-120, high = about to special)
hp: 1,
mp: 1,
sp: 1,

View file

@ -31,6 +31,10 @@ function shouldBuff(name, minTurns) {
}
// Known buffs with their priority, icon name, spell name, and refresh threshold
// Threat detection: SP bar max is 120px, monsters >85% are about to special
const THREAT_PX = Math.round(120 * 85 / 100); // ~102px
// NOTE: Heartseeker and Arcane Focus are mutually exclusive. For a 2H (physical) build,
// Heartseeker is preferred (+25% phys dmg, +10% crit). Arcane Focus is for magic builds.
const BUFF_PRIORITY = [
@ -109,6 +113,30 @@ function findStrongestMonster() {
return best >= 0 ? best : 0;
}
// Find the monster with the highest SP/OC bar (most dangerous — about to special)
// The red bar (nbarred.png) has a max width of 120px
// Returns the index, or 0 if none found
function findDangerousMonster() {
let best = -1, bestSP = -1;
STATE.monsters.forEach((m, i) => {
if (!m || !m.hasAttribute || !m.hasAttribute('onclick')) return;
const sp = STATE.monsterSp[i] || 0;
if (sp > bestSP) { bestSP = sp; best = i; }
});
return best >= 0 ? best : 0;
}
// Check if there are 2+ monsters with SP bar > threshold (imminent special attacks)
function hasHighThreat(threshold) {
let count = 0;
STATE.monsters.forEach((m, i) => {
if (!m || !m.hasAttribute || !m.hasAttribute('onclick')) return;
const sp = STATE.monsterSp[i] || 0;
if (sp > threshold) count++;
});
return count >= 2;
}
function checkMonsterDebuff(idx, db) {
const m = STATE.monsters[idx];
if (!m) return false;
@ -438,7 +466,7 @@ function strategyNovice() {
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 null;
@ -530,25 +558,26 @@ function strategyAdept() {
}
}
// 5. Weapon skills — before spirit stance
// 5. Weapon skills — prioritize high-threat monsters (high SP/OC bar)
const isHighThreat = hasHighThreat(THREAT_PX);
const skillOC_N = CFG.skillOC || 75;
if (STATE.oc >= skillOC_N) {
if (STATE.oc >= skillOC_N || isHighThreat) {
// Rending Blow: AoE armor pen vs 5+ enemies (highest priority in Grindfest)
if (STATE.monsters.length >= 5 && STATE.skillsKnown.includes('Rending Blow')) {
const t = findStrongestMonster();
const t = isHighThreat ? findDangerousMonster() : findStrongestMonster();
if (t >= 0) return { type: 'skill', name: 'Rending Blow', target: t };
}
// Shatter Strike: AoE stun vs 5+ enemies (needs Penetrated Armor from Rending Blow)
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 = findStrongestMonster();
const t = isHighThreat ? findDangerousMonster() : findStrongestMonster();
if (t >= 0) return { type: 'skill', name: 'Shatter Strike', target: t };
}
}
// Great Cleave: boss/rare fights only (single target, lower priority than AoE)
if (anyRareMonster() && STATE.skillsKnown.includes('Great Cleave')) {
const t = findStrongestMonster();
const t = isHighThreat ? findDangerousMonster() : findStrongestMonster();
if (t >= 0) return { type: 'skill', name: 'Great Cleave', target: t };
}
}
@ -573,7 +602,7 @@ function strategyAdept() {
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 null;
@ -663,35 +692,38 @@ function strategyVeteran() {
}
}
// 6. Weapon skills only at OC thresholds (let OC build via basic attacks)
// 5. Weapon skills — prioritize high-threat monsters (high SP/OC bar)
// If 2+ enemies have >85% SP bar, they're about to special — emergency
const isHighThreat = hasHighThreat(THREAT_PX);
const askillOC3 = CFG.skillOC || 80;
if (STATE.oc >= askillOC3) {
// Great Cleave: boss/rare fights only
if (STATE.oc >= askillOC3 || isHighThreat) {
// Emergency: 2+ monsters about to special — spend OC to kill them fast
// 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 };
}
// 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 };
}
}
// Great Cleave: boss/rare fights or single-target threat
if (anyRareMonster() && STATE.skillsKnown.includes('Great Cleave')) {
const t = findStrongestMonster();
const t = isHighThreat ? findDangerousMonster() : findStrongestMonster();
if (t >= 0) return { type: 'skill', name: 'Great Cleave', target: t };
}
// Rending Blow + Shatter Strike: AoE vs 5+ enemies
if (STATE.monsters.length >= 5) {
if (STATE.skillsKnown.includes('Rending Blow')) {
const t = findStrongestMonster();
if (t >= 0) return { type: 'skill', name: 'Rending Blow', target: t };
}
if (STATE.skillsKnown.includes('Shatter Strike')) {
const hasArmorBreak = STATE.monsters.some((m, i) => checkMonsterDebuff(i, 'penetrated_armor'));
if (hasArmorBreak) {
const t = findStrongestMonster();
if (t >= 0) return { type: 'skill', name: 'Shatter Strike', target: t };
}
}
}
// Other weapon skills (non-2H): generic use
if (!STATE.skillsKnown.includes('Great Cleave')) {
// If high threat and no AoE skill available, use any weapon skill on the most dangerous
if (isHighThreat && STATE.oc >= (CFG.skillOC || 80)) {
const ps = ['Frenzied Blows', 'Skyward Sword', 'Iris Strike', 'Merciful Blow',
'Vital Strike', 'Shield Bash', 'Concussive Strike'];
for (const sk of ps) {
if (STATE.skillsKnown.includes(sk)) {
const t = findStrongestMonster();
const t = findDangerousMonster();
if (t >= 0) return { type: 'skill', name: sk, target: t };
}
}