hv-unified/src/keybindings.js
GaboGG 467e844aae v0.13.34 - Explicit action names in log + _reason tags per decision
- Log now shows spell names/skill names/item names explicitly:
  '[HV] ▶ spell → Haste (self)' instead of '▶ spell → monster 0'
  '[HV] ▶ spell → Fiery Blast → monster 1 (ch fallback)'
  '[HV] ▶ item → Spirit Draught (self) (sp55)'
- Added _reason tag to every action return: 'hp60', 'sp55', 'buff',
  'ch score 81', 'threat', 'oc', 'boss', 'stun', 'mana', 'gem',
  'mystic', 'fallback', 'dmg'
- Now you can see exactly WHY each action was chosen on every turn
2026-07-27 13:35:45 -04:00

91 lines
4.5 KiB
JavaScript

// ═══════════════════════════════════════════════════════════════════════
// 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');
}