// ═══════════════════════════════════════════════════════════════════════ // KEYBINDINGS — keyboard shortcuts for battle // ═══════════════════════════════════════════════════════════════════════ let keybindingsSetup = false; const Q_DEBOUNCE_MS = 300; // Ignore Q repeats faster than this function setupKeybindings() { if (keybindingsSetup) return; keybindingsSetup = true; document.addEventListener('keydown', function(e) { // Settings — comma key if (e.keyCode === KEYS.COMMA) { if (isBattlePage()) { e.preventDefault(); STATE.settingsVisible = !STATE.settingsVisible; toggleSettings(); return; } } // Main action hotkey (default: Q) if (e.code === CFG.hotkey && !e.ctrlKey && !e.altKey && !e.metaKey) { if (!isInputFocused() && isBattlePage()) { // Debounce: ignore key-repeats until game processes the action const now = Date.now(); if (now - (STATE._lastActionTime || 0) < Q_DEBOUNCE_MS) return; STATE._lastActionTime = now; console.log('%c[HV] Q pressed — checking action...', 'color:#888'); parseBattleState(); // Debug: show full battle state const activeBuffs = Object.entries(STATE.buffs) .filter(([k, v]) => v > 0 && k !== 'regen' || v > 0) .map(([k, v]) => `${k}=${v}`).join(' '); // Monster SP bars (threat levels) const threatLevels = STATE.monsters.map((m, i) => { const sp = STATE.monsterSp[i] || 0; return `${i}:${sp}`; }).join(' '); const curMp = parseInt((document.getElementById('vrm') || {}).textContent) || 0; const curHp = parseInt((document.getElementById('vrhd') || {}).textContent) || 0; console.log( `%c[HV] ch=${STATE.channeling} hp=${curHp} mp=${curMp}(${(STATE.mp*100).toFixed(0)}%) ` + `sp=${(STATE.sp*100).toFixed(0)}% oc=${STATE.oc.toFixed(0)} ` + `mon:${STATE.monsters.length} ` + `buffs: ${activeBuffs} | spBars: ${threatLevels}`, 'color:#888' ); const a = getRecommendedAction(); if (a) { e.preventDefault(); // Show explicit action details let label = ''; if (a.type === 'spell') { label = a.name + (a.target >= 0 ? ' → monster ' + a.target : ' (self)'); } else if (a.type === 'skill') { label = a.name + ' → monster ' + a.target; } else if (a.type === 'item') { const itemDef = ITEMS[parseInt(STATE.itemsKnown[a.id] || '0')]; label = (itemDef ? itemDef.n : a.id) + (a.selfTarget ? ' (self)' : ''); } else if (a.type === 'attack') { label = 'monster ' + a.target; } else { label = a.target >= 0 ? 'monster ' + a.target : a.name || a.id; } console.log(`%c[HV] ▶ ${a.type} → ${label}${a._reason ? ' (' + a._reason + ')' : ''}`, 'color:#0f0'); executeAction(a); } } } // Hover toggle (H) if (e.code === 'KeyH' && !e.ctrlKey && !e.altKey && !e.metaKey && !isInputFocused()) { STATE.interruptHover = !STATE.interruptHover; console.log(`%c[HV] Hover ${STATE.interruptHover ? 'OFF' : 'ON'}`, 'color:#f80'); } // Emergency heal (C) — always cast Cure/Full-Cure regardless of strategy if (e.code === 'KeyC' && !e.ctrlKey && !e.altKey && !e.metaKey && !isInputFocused()) { const c = hasSpell('Full-Cure') ? 'Full-Cure' : hasSpell('Cure') ? 'Cure' : null; if (c) { console.log(`%c[HV] 🚑 Emergency: casting ${c}`, 'color:#f44'); castSelfSpell(c); } } }); console.log('%c⌨ [HV] Keys: Q=action H=hover C=cure ,=settings', 'color:#0f0;font-size:11px'); }