hv-unified/src/action-executor.js
GaboGG f2fc00965e v0.16.0 - Complement community scripts (AGY refactor)
REMOVED (owned by MB/HVUT):
- RE timer (HVUtils has one)
- Battle keybinds Q/H/C (Monsterbation owns battle keys; kept , for settings)
- Hover-attack battle UI hooks (Monsterbation)
- Battle logger (Monsterbation combat log)
- Spirit/potion/scroll auto-use in battle (Monsterbation)
- Armory bulk sell/salvage/shrine UI + Keep Top 10% button (HVUtils bulk tools)

RESOLVED:
- Q keybind collision: yielded to Monsterbation
- DOM double-injection: removed duplicate overlays
- Storage: hvunified_ namespace isolated from HVUT GM_* storage

KEPT (gaps community doesn't cover):
- Gear analysis/scoring (equipped vs store, 19-category advisory)
- Strategy guidance as advisory layer (HV.advice/HV.action)
- Guidance/abilities/difficulty advisor
- window.HV public API bridge
2026-08-04 14:03:34 -04:00

111 lines
3.9 KiB
JavaScript

// ═══════════════════════════════════════════════════════════════════════
// ACTION EXECUTOR — carry out recommended actions
// ═══════════════════════════════════════════════════════════════════════
function executeAction(a) {
if (!a) return false;
let result = false;
switch (a.type) {
case 'attack': result = attackMonster(a.target); break;
case 'spell': result = a.selfTarget ? castSelfSpell(a.name) : castTargetSpell(a.name, a.target); break;
case 'skill': result = useSkill(a.name, a.target); break;
case 'item': result = useItem(a.id); break;
}
if (result) {
STATE._lastActionTime = Date.now();
}
return result;
}
function attackMonster(i) {
if (i < 0 || i >= STATE.monsters.length) return false;
const m = STATE.monsters[i];
if (!m || !m.hasAttribute || !m.hasAttribute('onclick')) return false;
m.click();
return true;
}
function castSelfSpell(n) {
const s = $$('.btsd[onclick]').find(el =>
(el.getAttribute('onmouseover') || '').includes(`set_infopane_spell('${n}'`));
if (!s) return false;
s.click();
return true;
}
function castTargetSpell(n, i) {
const s = $$('.btsd[onclick]').find(el =>
(el.getAttribute('onmouseover') || '').includes(`set_infopane_spell('${n}'`));
if (!s) return false;
s.click();
// Target selection: click immediately, no setTimeout delay needed.
// HV's native lock_action handles the state transition synchronously.
if (i >= 0 && i < STATE.monsters.length) {
const m = STATE.monsters[i];
if (m && m.hasAttribute && m.hasAttribute('onclick')) m.click();
}
return true;
}
function useItem(id) {
let it = document.getElementById('ikey_' + id);
// Fallback for auto_ slot IDs (on-cooldown items without explicit DOM id):
// search for element with matching onmouseover item ID
if (!it && typeof id === 'string' && id.startsWith('auto_')) {
const rawId = id.replace('auto_', '');
const itemPane = document.getElementById('pane_item');
if (itemPane) {
it = $$('div[onmouseover*="set_infopane_item(' + rawId + ')"]', itemPane)[0];
}
}
if (!it) return false;
// Track that we used the P-slot gem so we don't retry stale state
if (id === 'p') STATE._mysticUsed = true;
dummy.setAttribute('onclick', it.getAttribute('onmouseover'));
dummy.click();
it.click();
return true;
}
function useSkill(n, i) {
let el = null;
// Try quickbar first
for (const e of $$('#quickbar > .btqs[onclick]')) {
if ((e.getAttribute('onmouseover') || '').includes(`set_infopane_spell('${n}'`)) {
el = e; break;
}
}
// Try spellbook
if (!el) {
el = $$('.btsd[onclick]').find(e =>
(e.getAttribute('onmouseover') || '').includes(`set_infopane_spell('${n}'`));
}
// Fallback: onclick match
if (!el) {
for (const e of $$('.btqb[onclick]')) {
if ((e.getAttribute('onclick') || '').includes(n)) { el = e; break; }
}
}
// Fallback: image match
if (!el) {
for (const e of $$('.btqb img')) {
if ((e.src || '').toLowerCase().includes(n.toLowerCase().replace(/\s+/g, '_'))) {
el = e.parentElement; break;
}
}
}
if (!el) return false;
el.click();
// Click target immediately — no setTimeout needed
if (i >= 0 && i < STATE.monsters.length) {
const m = STATE.monsters[i];
if (m && m.hasAttribute && m.hasAttribute('onclick')) m.click();
}
return true;
}