- 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
91 lines
4.5 KiB
JavaScript
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');
|
|
}
|