otclient-redemption/modules/corelib/keybind.lua
Luan Santos f5299139c7 refactor(platform): restructure keyboard modifiers for cross-platform consistency
Replace deprecated KeyboardControlModifier with platform-aware primary modifier
handling. Introduces KeyboardMetaModifier for native modifier keys (Cmd on macOS,
Win on Windows, Super on X11) and KeyboardPrimaryModifier to abstract the
platform-specific primary modifier key. Adds isPrimaryModifierOnly() helper
to normalize platform differences in keyboard shortcuts.

Extract Cocoa view handling into separate OTOpenGLView and OTWindowDelegate
classes for improved code organization and maintainability.
2026-01-10 10:06:53 -08:00

1058 lines
32 KiB
Lua

CHAT_MODE = {
ON = 1,
OFF = 2
}
Keybind = {
presets = {},
presetToIndex = {},
currentPreset = nil,
configs = {
keybinds = {},
hotkeys = {}
},
defaultKeys = {
[CHAT_MODE.ON] = {},
[CHAT_MODE.OFF] = {}
},
defaultKeybinds = {},
hotkeys = {
[CHAT_MODE.ON] = {},
[CHAT_MODE.OFF] = {}
},
chatMode = CHAT_MODE.ON,
reservedKeys = {
["Up"] = true,
["Down"] = true,
["Left"] = true,
["Right"] = true
}
}
KEY_UP = 1
KEY_DOWN = 2
KEY_PRESS = 3
HOTKEY_ACTION = {
USE_YOURSELF = 1,
USE_CROSSHAIR = 2,
USE_TARGET = 3,
EQUIP = 4,
USE = 5,
TEXT = 6,
TEXT_AUTO = 7,
SPELL = 8
}
function Keybind.init()
connect(g_game, { onGameStart = Keybind.online, onGameEnd = Keybind.offline })
Keybind.presets = g_settings.getList("controls-presets")
if #Keybind.presets == 0 then
Keybind.presets = { "Druid", "Knight", "Paladin", "Sorcerer", "Monk" }
Keybind.currentPreset = "Druid"
else
Keybind.currentPreset = g_settings.getValue("controls-preset-current")
end
for index, preset in ipairs(Keybind.presets) do
Keybind.presetToIndex[preset] = index
end
if not g_resources.directoryExists("/controls") then
g_resources.makeDir("/controls")
end
if not g_resources.directoryExists("/controls/keybinds") then
g_resources.makeDir("/controls/keybinds")
end
if not g_resources.directoryExists("/controls/hotkeys") then
g_resources.makeDir("/controls/hotkeys")
end
for _, preset in ipairs(Keybind.presets) do
Keybind.configs.keybinds[preset] = g_configs.create("/controls/keybinds/" .. preset .. ".otml")
Keybind.configs.hotkeys[preset] = g_configs.create("/controls/hotkeys/" .. preset .. ".otml")
end
for preset, config in pairs(Keybind.configs.hotkeys) do
for chatMode = CHAT_MODE.ON, CHAT_MODE.OFF do
Keybind.hotkeys[chatMode][preset] = {}
local hotkeyId = 1
local hotkeys = config:getNode(chatMode)
if hotkeys then
local hotkey = hotkeys[tostring(hotkeyId)]
while hotkey do
if hotkey.data.parameter then
hotkey.data.parameter = "\"" .. hotkey.data.parameter .. "\"" -- forcing quotes cause OTML is not saving them, just wow
end
table.insert(Keybind.hotkeys[chatMode][preset], hotkey)
hotkeyId = hotkeyId + 1
hotkey = hotkeys[tostring(hotkeyId)]
end
end
end
end
end
function Keybind.terminate()
disconnect(g_game, { onGameStart = Keybind.online, onGameEnd = Keybind.offline })
for _, preset in ipairs(Keybind.presets) do
Keybind.configs.keybinds[preset]:save()
Keybind.configs.hotkeys[preset]:save()
end
g_settings.setList("controls-presets", Keybind.presets)
g_settings.setValue("controls-preset-current", Keybind.currentPreset)
g_settings.save()
end
function Keybind.online()
for _, hotkey in ipairs(Keybind.hotkeys[Keybind.chatMode][Keybind.currentPreset]) do
Keybind.bindHotkey(hotkey.hotkeyId, Keybind.chatMode)
end
end
function Keybind.offline()
for _, hotkey in ipairs(Keybind.hotkeys[Keybind.chatMode][Keybind.currentPreset]) do
Keybind.unbindHotkey(hotkey.hotkeyId, Keybind.chatMode)
end
end
function Keybind.new(category, action, primary, secondary, alone)
local index = category .. '_' .. action
if Keybind.defaultKeybinds[index] then
pwarning(string.format("Keybind for [%s: %s] is already in use", category, action))
return
end
local keys = {}
if type(primary) == "string" then
keys[CHAT_MODE.ON] = { primary = primary }
keys[CHAT_MODE.OFF] = { primary = primary }
else
keys[CHAT_MODE.ON] = { primary = primary[CHAT_MODE.ON] }
keys[CHAT_MODE.OFF] = { primary = primary[CHAT_MODE.OFF] }
end
if type(secondary) == "string" then
keys[CHAT_MODE.ON].secondary = secondary
keys[CHAT_MODE.OFF].secondary = secondary
else
keys[CHAT_MODE.ON].secondary = secondary[CHAT_MODE.ON]
keys[CHAT_MODE.OFF].secondary = secondary[CHAT_MODE.OFF]
end
keys[CHAT_MODE.ON].primary = retranslateKeyComboDesc(keys[CHAT_MODE.ON].primary)
if keys[CHAT_MODE.ON].secondary then
keys[CHAT_MODE.ON].secondary = retranslateKeyComboDesc(keys[CHAT_MODE.ON].secondary)
end
keys[CHAT_MODE.OFF].primary = retranslateKeyComboDesc(keys[CHAT_MODE.OFF].primary)
if keys[CHAT_MODE.OFF].secondary then
keys[CHAT_MODE.OFF].secondary = retranslateKeyComboDesc(keys[CHAT_MODE.OFF].secondary)
end
if Keybind.defaultKeys[CHAT_MODE.ON][keys[CHAT_MODE.ON].primary] then
local primaryIndex = Keybind.defaultKeys[CHAT_MODE.ON][keys[CHAT_MODE.ON].primary]
local primaryKeybind = Keybind.defaultKeybinds[primaryIndex]
perror(string.format("Default primary key (Chat Mode On) assigned to [%s: %s] is already in use by [%s: %s]",
category, action, primaryKeybind.category, primaryKeybind.action))
return
end
if Keybind.defaultKeys[CHAT_MODE.OFF][keys[CHAT_MODE.OFF].primary] then
local primaryIndex = Keybind.defaultKeys[CHAT_MODE.OFF][keys[CHAT_MODE.OFF].primary]
local primaryKeybind = Keybind.defaultKeybinds[primaryIndex]
perror(string.format("Default primary key (Chat Mode Off) assigned to [%s: %s] is already in use by [%s: %s]",
category, action, primaryKeybind.category, primaryKeybind.action))
return
end
if keys[CHAT_MODE.ON].secondary and Keybind.defaultKeys[CHAT_MODE.ON][keys[CHAT_MODE.ON].secondary] then
local secondaryIndex = Keybind.defaultKeys[CHAT_MODE.ON][keys[CHAT_MODE.ON].secondary]
local secondaryKeybind = Keybind.defaultKeybinds[secondaryIndex]
perror(string.format("Default secondary key (Chat Mode On) assigned to [%s: %s] is already in use by [%s: %s]",
category, action, secondaryKeybind.category, secondaryKeybind.action))
return
end
if keys[CHAT_MODE.OFF].secondary and Keybind.defaultKeys[CHAT_MODE.OFF][keys[CHAT_MODE.OFF].secondary] then
local secondaryIndex = Keybind.defaultKeys[CHAT_MODE.OFF][keys[CHAT_MODE.OFF].secondary]
local secondaryKeybind = Keybind.defaultKeybinds[secondaryIndex]
perror(string.format("Default secondary key (Chat Mode Off) assigned to [%s: %s] is already in use by [%s: %s]",
category, action, secondaryKeybind.category, secondaryKeybind.action))
return
end
if keys[CHAT_MODE.ON].primary then
Keybind.defaultKeys[CHAT_MODE.ON][keys[CHAT_MODE.ON].primary] = index
end
if keys[CHAT_MODE.OFF].primary then
Keybind.defaultKeys[CHAT_MODE.OFF][keys[CHAT_MODE.OFF].primary] = index
end
Keybind.defaultKeybinds[index] = {
category = category,
action = action,
keys = keys,
alone = alone
}
if keys[CHAT_MODE.ON].secondary then
Keybind.defaultKeys[CHAT_MODE.ON][keys[CHAT_MODE.ON].secondary] = index
end
if keys[CHAT_MODE.OFF].secondary then
Keybind.defaultKeys[CHAT_MODE.OFF][keys[CHAT_MODE.OFF].secondary] = index
end
end
function Keybind.delete(category, action)
local index = category .. '_' .. action
local keybind = Keybind.defaultKeybinds[index]
if not keybind then
return
end
Keybind.unbind(category, action)
local keysOn = keybind.keys[CHAT_MODE.ON]
local keysOff = keybind.keys[CHAT_MODE.OFF]
local primaryOn = keysOn.primary and tostring(keysOn.primary) or nil
local primaryOff = keysOff.primary and tostring(keysOff.primary) or nil
local secondaryOn = keysOn.secondary and tostring(keysOn.secondary) or nil
local secondaryOff = keysOff.secondary and tostring(keysOff.secondary) or nil
if primaryOn and primaryOn:len() > 0 then
Keybind.defaultKeys[CHAT_MODE.ON][primaryOn] = nil
end
if secondaryOn and secondaryOn:len() > 0 then
Keybind.defaultKeys[CHAT_MODE.ON][secondaryOn] = nil
end
if primaryOff and primaryOff:len() > 0 then
Keybind.defaultKeys[CHAT_MODE.OFF][primaryOff] = nil
end
if secondaryOff and secondaryOff:len() > 0 then
Keybind.defaultKeys[CHAT_MODE.OFF][secondaryOff] = nil
end
Keybind.defaultKeybinds[index] = nil
end
function Keybind.bind(category, action, callbacks, widget)
local index = category .. '_' .. action
local keybind = Keybind.defaultKeybinds[index]
if not keybind then
return
end
keybind.callbacks = callbacks
keybind.widget = widget
local keys = Keybind.getKeybindKeys(category, action)
for _, callback in ipairs(keybind.callbacks) do
if callback.type == KEY_UP then
if keys.primary then
keys.primary = tostring(keys.primary)
if keys.primary:len() > 0 then
g_keyboard.bindKeyUp(keys.primary, callback.callback, keybind.widget, callback.alone)
end
end
if keys.secondary then
keys.secondary = tostring(keys.secondary)
if keys.secondary:len() > 0 then
g_keyboard.bindKeyUp(keys.secondary, callback.callback, keybind.widget, callback.alone)
end
end
elseif callback.type == KEY_DOWN then
if keys.primary then
keys.primary = tostring(keys.primary)
if keys.primary:len() > 0 then
g_keyboard.bindKeyDown(keys.primary, callback.callback, keybind.widget, callback.alone)
end
end
if keys.secondary then
keys.secondary = tostring(keys.secondary)
if keys.secondary:len() > 0 then
g_keyboard.bindKeyDown(keys.secondary, callback.callback, keybind.widget, callback.alone)
end
end
elseif callback.type == KEY_PRESS then
if keys.primary then
keys.primary = tostring(keys.primary)
if keys.primary:len() > 0 then
g_keyboard.bindKeyPress(keys.primary, callback.callback, keybind.widget)
end
end
if keys.secondary then
keys.secondary = tostring(keys.secondary)
if keys.secondary:len() > 0 then
g_keyboard.bindKeyPress(keys.secondary, callback.callback, keybind.widget)
end
end
end
end
end
function Keybind.unbind(category, action)
local index = category .. '_' .. action
local keybind = Keybind.defaultKeybinds[index]
if not keybind or not keybind.callbacks then
return
end
local keys = Keybind.getKeybindKeys(category, action)
for _, callback in ipairs(keybind.callbacks) do
if callback.type == KEY_UP then
if keys.primary then
keys.primary = tostring(keys.primary)
if keys.primary:len() > 0 then
g_keyboard.unbindKeyUp(keys.primary, callback.callback, keybind.widget)
end
end
if keys.secondary then
keys.secondary = tostring(keys.secondary)
if keys.secondary:len() > 0 then
g_keyboard.unbindKeyUp(keys.secondary, callback.callback, keybind.widget)
end
end
elseif callback.type == KEY_DOWN then
if keys.primary then
keys.primary = tostring(keys.primary)
if keys.primary:len() > 0 then
g_keyboard.unbindKeyDown(keys.primary, callback.callback, keybind.widget)
end
end
if keys.secondary then
keys.secondary = tostring(keys.secondary)
if keys.secondary:len() > 0 then
g_keyboard.unbindKeyDown(keys.secondary, callback.callback, keybind.widget)
end
end
elseif callback.type == KEY_PRESS then
if keys.primary then
keys.primary = tostring(keys.primary)
if keys.primary:len() > 0 then
g_keyboard.unbindKeyPress(keys.primary, callback.callback, keybind.widget)
end
end
if keys.secondary then
keys.secondary = tostring(keys.secondary)
if keys.secondary:len() > 0 then
g_keyboard.unbindKeyPress(keys.secondary, callback.callback, keybind.widget)
end
end
end
end
end
function Keybind.newPreset(presetName)
if Keybind.presetToIndex[presetName] then
return
end
table.insert(Keybind.presets, presetName)
Keybind.presetToIndex[presetName] = #Keybind.presets
Keybind.configs.keybinds[presetName] = g_configs.create("/controls/keybinds/" .. presetName .. ".otml")
Keybind.configs.hotkeys[presetName] = g_configs.create("/controls/hotkeys/" .. presetName .. ".otml")
Keybind.hotkeys[CHAT_MODE.ON][presetName] = {}
Keybind.hotkeys[CHAT_MODE.OFF][presetName] = {}
g_settings.setList("controls-presets", Keybind.presets)
g_settings.save()
end
function Keybind.copyPreset(fromPreset, toPreset)
if Keybind.presetToIndex[toPreset] then
return false
end
table.insert(Keybind.presets, toPreset)
Keybind.presetToIndex[toPreset] = #Keybind.presets
Keybind.configs.keybinds[fromPreset]:save()
Keybind.configs.hotkeys[fromPreset]:save()
local keybindsConfigPath = Keybind.configs.keybinds[fromPreset]:getFileName()
local keybindsConfigContent = g_resources.readFileContents(keybindsConfigPath)
g_resources.writeFileContents("/controls/keybinds/" .. toPreset .. ".otml", keybindsConfigContent)
Keybind.configs.keybinds[toPreset] = g_configs.create("/controls/keybinds/" .. toPreset .. ".otml")
local hotkeysConfigPath = Keybind.configs.hotkeys[fromPreset]:getFileName()
local hotkeysConfigContent = g_resources.readFileContents(hotkeysConfigPath)
g_resources.writeFileContents("/controls/hotkeys/" .. toPreset .. ".otml", hotkeysConfigContent)
Keybind.configs.hotkeys[toPreset] = g_configs.create("/controls/hotkeys/" .. toPreset .. ".otml")
for chatMode = CHAT_MODE.ON, CHAT_MODE.OFF do
Keybind.hotkeys[chatMode][toPreset] = {}
local hotkeyId = 1
local hotkeys = Keybind.configs.hotkeys[toPreset]:getNode(chatMode)
if hotkeys then
local hotkey = hotkeys[tostring(hotkeyId)]
while hotkey do
if hotkey.data.parameter then
hotkey.data.parameter = "\"" .. hotkey.data.parameter .. "\"" -- forcing quotes cause OTML is not saving them, just wow
end
table.insert(Keybind.hotkeys[chatMode][toPreset], hotkey)
hotkeyId = hotkeyId + 1
hotkey = hotkeys[tostring(hotkeyId)]
end
end
end
g_settings.setList("controls-presets", Keybind.presets)
g_settings.save()
return true
end
function Keybind.renamePreset(oldPresetName, newPresetName)
if Keybind.currentPreset == oldPresetName then
Keybind.currentPreset = newPresetName
end
local index = Keybind.presetToIndex[oldPresetName]
Keybind.presetToIndex[oldPresetName] = nil
Keybind.presetToIndex[newPresetName] = index
Keybind.presets[index] = newPresetName
local keybindsConfigPath = Keybind.configs.keybinds[oldPresetName]:getFileName()
Keybind.configs.keybinds[oldPresetName]:save()
Keybind.configs.keybinds[oldPresetName] = nil
local keybindsConfigContent = g_resources.readFileContents(keybindsConfigPath)
g_resources.deleteFile(keybindsConfigPath)
g_resources.writeFileContents("/controls/keybinds/" .. newPresetName .. ".otml", keybindsConfigContent)
Keybind.configs.keybinds[newPresetName] = g_configs.create("/controls/keybinds/" .. newPresetName .. ".otml")
local hotkeysConfigPath = Keybind.configs.hotkeys[oldPresetName]:getFileName()
Keybind.configs.hotkeys[oldPresetName]:save()
Keybind.configs.hotkeys[oldPresetName] = nil
local hotkeysConfigContent = g_resources.readFileContents(hotkeysConfigPath)
g_resources.deleteFile(hotkeysConfigPath)
g_resources.writeFileContents("/controls/hotkeys/" .. newPresetName .. ".otml", hotkeysConfigContent)
Keybind.configs.hotkeys[newPresetName] = g_configs.create("/controls/hotkeys/" .. newPresetName .. ".otml")
Keybind.hotkeys[CHAT_MODE.ON][newPresetName] = Keybind.hotkeys[CHAT_MODE.ON][oldPresetName]
Keybind.hotkeys[CHAT_MODE.OFF][newPresetName] = Keybind.hotkeys[CHAT_MODE.OFF][oldPresetName]
g_settings.setList("controls-presets", Keybind.presets)
g_settings.save()
end
function Keybind.removePreset(presetName)
if #Keybind.presets == 1 then
return false
end
table.remove(Keybind.presets, Keybind.presetToIndex[presetName])
Keybind.presetToIndex[presetName] = nil
Keybind.configs.keybinds[presetName] = nil
g_configs.unload("/controls/keybinds/" .. presetName .. ".otml")
g_resources.deleteFile("/controls/keybinds/" .. presetName .. ".otml")
Keybind.configs.hotkeys[presetName] = nil
g_configs.unload("/controls/hotkeys/" .. presetName .. ".otml")
g_resources.deleteFile("/controls/hotkeys/" .. presetName .. ".otml")
if Keybind.currentPreset == presetName then
Keybind.currentPreset = Keybind.presets[1]
end
g_settings.setList("controls-presets", Keybind.presets)
g_settings.save()
return true
end
function Keybind.selectPreset(presetName)
if Keybind.currentPreset == presetName then
return false
end
if not Keybind.presetToIndex[presetName] then
return false
end
for _, keybind in pairs(Keybind.defaultKeybinds) do
if keybind.callbacks then
Keybind.unbind(keybind.category, keybind.action)
end
end
for _, hotkey in ipairs(Keybind.hotkeys[Keybind.chatMode][Keybind.currentPreset]) do
Keybind.unbindHotkey(hotkey.hotkeyId, Keybind.chatMode)
end
Keybind.currentPreset = presetName
for _, keybind in pairs(Keybind.defaultKeybinds) do
if keybind.callbacks then
Keybind.bind(keybind.category, keybind.action, keybind.callbacks, keybind.widget)
end
end
for _, hotkey in ipairs(Keybind.hotkeys[Keybind.chatMode][Keybind.currentPreset]) do
Keybind.bindHotkey(hotkey.hotkeyId, Keybind.chatMode)
end
return true
end
function Keybind.getAction(category, action)
local index = category .. '_' .. action
return Keybind.defaultKeybinds[index]
end
function Keybind.setPrimaryActionKey(category, action, preset, keyCombo, chatMode)
local index = category .. '_' .. action
local keybind = Keybind.defaultKeybinds[index]
if keyCombo and type(keyCombo) == "string" and keyCombo:len() > 0 then
keyCombo = retranslateKeyComboDesc(keyCombo)
end
local numericChatMode = chatMode
chatMode = tostring(chatMode)
local keys = Keybind.configs.keybinds[preset]:getNode(index)
if not keys then
keys = {}
for k, v in pairs(keybind.keys) do
keys[tostring(k)] = table.recursivecopy(v)
end
end
if keybind.callbacks then
Keybind.unbind(category, action)
end
if not keys[chatMode] then
keys[chatMode] = { primary = keyCombo, secondary = keybind.keys[numericChatMode].secondary }
end
keys[chatMode].primary = keyCombo
local ret = false
if keys[chatMode].secondary == keyCombo then
keys[chatMode].secondary = nil
ret = true
end
Keybind.configs.keybinds[preset]:setNode(index, keys)
if keybind.callbacks then
Keybind.bind(category, action, keybind.callbacks, keybind.widget)
end
return ret
end
function Keybind.setSecondaryActionKey(category, action, preset, keyCombo, chatMode)
local index = category .. '_' .. action
local keybind = Keybind.defaultKeybinds[index]
if keyCombo and type(keyCombo) == "string" and keyCombo:len() > 0 then
keyCombo = retranslateKeyComboDesc(keyCombo)
end
local numericChatMode = chatMode
chatMode = tostring(chatMode)
local keys = Keybind.configs.keybinds[preset]:getNode(index)
if not keys then
keys = {}
for k, v in pairs(keybind.keys) do
keys[tostring(k)] = table.recursivecopy(v)
end
end
if keybind.callbacks then
Keybind.unbind(category, action)
end
if not keys[chatMode] then
keys[chatMode] = { primary = keybind.keys[numericChatMode].primary, secondary = keyCombo }
end
keys[chatMode].secondary = keyCombo
local ret = false
if keys[chatMode].primary == keyCombo then
keys[chatMode].primary = nil
ret = true
end
Keybind.configs.keybinds[preset]:setNode(index, keys)
if keybind.callbacks then
Keybind.bind(category, action, keybind.callbacks, keybind.widget)
end
return ret
end
function Keybind.resetKeybindsToDefault(presetName, chatMode)
if not chatMode then
chatMode = Keybind.chatMode
end
for _, keybind in pairs(Keybind.defaultKeybinds) do
if keybind.callbacks then
Keybind.unbind(keybind.category, keybind.action)
end
end
for _, keybind in pairs(Keybind.defaultKeybinds) do
local index = keybind.category .. '_' .. keybind.action
Keybind.configs.keybinds[presetName]:setNode(index, keybind.keys)
end
for _, keybind in pairs(Keybind.defaultKeybinds) do
if keybind.callbacks then
Keybind.bind(keybind.category, keybind.action, keybind.callbacks, keybind.widget)
end
end
end
function Keybind.getKeybindKeys(category, action, chatMode, preset, forceDefault)
if not chatMode then
chatMode = Keybind.chatMode
end
local index = category .. '_' .. action
local keybind = Keybind.defaultKeybinds[index]
local keys = Keybind.configs.keybinds[preset or Keybind.currentPreset]:getNode(index)
if not keys or forceDefault then
keys = {
primary = keybind.keys[chatMode].primary,
secondary = keybind.keys[chatMode].secondary
}
else
keys = keys[tostring(chatMode)] or keys[chatMode]
end
if not keys then
keys = {
primary = "",
secondary = ""
}
end
return keys
end
function Keybind.isKeyComboUsed(keyCombo, category, action, chatMode)
if not chatMode then
chatMode = Keybind.chatMode
end
if Keybind.reservedKeys[keyCombo] then
return true
end
if category and action then
local targetKeys = Keybind.getKeybindKeys(category, action, chatMode, Keybind.currentPreset)
for _, keybind in pairs(Keybind.defaultKeybinds) do
local keys = Keybind.getKeybindKeys(keybind.category, keybind.action, chatMode, Keybind.currentPreset)
if (keys.primary == keyCombo and targetKeys.primary ~= keyCombo) or (keys.secondary == keyCombo and targetKeys.secondary ~= keyCombo) then
return true
end
end
else
for _, keybind in pairs(Keybind.defaultKeybinds) do
local keys = Keybind.getKeybindKeys(keybind.category, keybind.action, chatMode, Keybind.currentPreset)
if keys.primary == keyCombo or keys.secondary == keyCombo then
return true
end
end
if Keybind.hotkeys[chatMode][Keybind.currentPreset] then
for _, hotkey in ipairs(Keybind.hotkeys[chatMode][Keybind.currentPreset]) do
if hotkey.primary == keyCombo or hotkey.secondary == keyCombo then
return true
end
end
end
end
return false
end
function Keybind.newHotkey(action, data, primary, secondary, chatMode)
if not chatMode then
chatMode = Keybind.chatMode
end
local hotkey = {
action = action,
data = data,
primary = primary or "",
secondary = secondary or ""
}
if not Keybind.hotkeys[chatMode][Keybind.currentPreset] then
Keybind.hotkeys[chatMode][Keybind.currentPreset] = {}
end
table.insert(Keybind.hotkeys[chatMode][Keybind.currentPreset], hotkey)
local hotkeyId = #Keybind.hotkeys[chatMode][Keybind.currentPreset]
hotkey.hotkeyId = hotkeyId
Keybind.configs.hotkeys[Keybind.currentPreset]:setNode(chatMode, Keybind.hotkeys[chatMode][Keybind.currentPreset])
Keybind.configs.hotkeys[Keybind.currentPreset]:save()
Keybind.bindHotkey(hotkeyId, chatMode)
end
function Keybind.removeHotkey(hotkeyId, chatMode)
if not chatMode then
chatMode = Keybind.chatMode
end
if not Keybind.hotkeys[chatMode][Keybind.currentPreset] then
return
end
Keybind.unbindHotkey(hotkeyId, chatMode)
table.remove(Keybind.hotkeys[chatMode][Keybind.currentPreset], hotkeyId)
Keybind.configs.hotkeys[Keybind.currentPreset]:clear()
for id, hotkey in ipairs(Keybind.hotkeys[chatMode][Keybind.currentPreset]) do
hotkey.hotkeyId = id
Keybind.configs.hotkeys[Keybind.currentPreset]:setNode(id, hotkey)
end
Keybind.configs.hotkeys[Keybind.currentPreset]:save()
end
function Keybind.editHotkey(hotkeyId, action, data, chatMode)
if not chatMode then
chatMode = Keybind.chatMode
end
Keybind.unbindHotkey(hotkeyId, chatMode)
local hotkey = Keybind.hotkeys[chatMode][Keybind.currentPreset][hotkeyId]
hotkey.action = action
hotkey.data = data
Keybind.configs.hotkeys[Keybind.currentPreset]:setNode(chatMode, Keybind.hotkeys[chatMode][Keybind.currentPreset])
Keybind.configs.hotkeys[Keybind.currentPreset]:save()
Keybind.bindHotkey(hotkeyId, chatMode)
end
function Keybind.editHotkeyKeys(hotkeyId, primary, secondary, chatMode)
if not chatMode then
chatMode = Keybind.chatMode
end
Keybind.unbindHotkey(hotkeyId, chatMode)
local hotkey = Keybind.hotkeys[chatMode][Keybind.currentPreset][hotkeyId]
if primary and primary:len() > 0 then
primary = retranslateKeyComboDesc(primary)
end
if secondary and secondary:len() > 0 then
secondary = retranslateKeyComboDesc(secondary)
end
hotkey.primary = primary or ""
hotkey.secondary = secondary or ""
Keybind.configs.hotkeys[Keybind.currentPreset]:setNode(chatMode, Keybind.hotkeys[chatMode][Keybind.currentPreset])
Keybind.configs.hotkeys[Keybind.currentPreset]:save()
Keybind.bindHotkey(hotkeyId, chatMode)
end
function Keybind.removeAllHotkeys(chatMode)
if not chatMode then
chatMode = Keybind.chatMode
end
for _, hotkey in ipairs(Keybind.hotkeys[chatMode][Keybind.currentPreset]) do
Keybind.unbindHotkey(hotkey.hotkeyId)
end
Keybind.hotkeys[chatMode][Keybind.currentPreset] = {}
Keybind.configs.hotkeys[Keybind.currentPreset]:remove(chatMode)
Keybind.configs.hotkeys[Keybind.currentPreset]:save()
end
function Keybind.getHotkeyKeys(hotkeyId, preset, chatMode)
if not chatMode then
chatMode = Keybind.chatMode
end
if not preset then
preset = Keybind.currentPreset
end
local keys = { primary = "", secondary = "" }
if not Keybind.hotkeys[chatMode][preset] then
return keys
end
local hotkey = Keybind.hotkeys[chatMode][preset][hotkeyId]
if not hotkey then
return keys
end
local config = Keybind.configs.hotkeys[preset]:getNode(chatMode)
if not config then
return keys
end
return config[tostring(hotkeyId)] or keys
end
function Keybind.hotkeyCallback(hotkeyId, chatMode)
if not chatMode then
chatMode = Keybind.chatMode
end
local hotkey = Keybind.hotkeys[chatMode][Keybind.currentPreset][hotkeyId]
if not hotkey then
return
end
local action = hotkey.action
local data = hotkey.data
if action == HOTKEY_ACTION.USE_YOURSELF then
if g_game.getClientVersion() < 780 then
local item = g_game.findPlayerItem(data.itemId, data.subType or -1)
if item then
g_game.useWith(item, g_game.getLocalPlayer())
end
else
g_game.useInventoryItemWith(data.itemId, g_game.getLocalPlayer(), data.subType or -1)
end
elseif action == HOTKEY_ACTION.USE_CROSSHAIR then
local item = Item.create(data.itemId)
if g_game.getClientVersion() < 780 then
item = g_game.findPlayerItem(data.itemId, data.subType or -1)
end
if item then
modules.game_interface.startUseWith(item, data.subType or -1)
end
elseif action == HOTKEY_ACTION.USE_TARGET then
local attackingCreature = g_game.getAttackingCreature()
if not attackingCreature then
local item = Item.create(data.itemId)
if g_game.getClientVersion() < 780 then
item = g_game.findPlayerItem(data.itemId, data.subType or -1)
end
if item then
modules.game_interface.startUseWith(item, data.subType or -1)
end
return
end
if attackingCreature:getTile() then
if g_game.getClientVersion() < 780 then
local item = g_game.findPlayerItem(data.itemId, data.subType or -1)
if item then
g_game.useWith(item, attackingCreature, data.subType or -1)
end
else
g_game.useInventoryItemWith(data.itemId, attackingCreature, data.subType or -1)
end
end
elseif action == HOTKEY_ACTION.EQUIP then
if g_game.getClientVersion() >= 910 then
local item = Item.create(data.itemId)
g_game.equipItem(item)
end
elseif action == HOTKEY_ACTION.USE then
if g_game.getClientVersion() < 780 then
local item = g_game.findPlayerItem(data.itemId, data.subType or -1)
if item then
g_game.use(item)
end
else
g_game.useInventoryItem(data.itemId)
end
elseif action == HOTKEY_ACTION.TEXT then
if modules.game_interface.isChatVisible() then
modules.game_console.setTextEditText(hotkey.data.text)
end
elseif action == HOTKEY_ACTION.TEXT_AUTO then
if modules.game_interface.isChatVisible() then
modules.game_console.sendMessage(hotkey.data.text)
else
g_game.talk(hotkey.data.text)
end
elseif action == HOTKEY_ACTION.SPELL then
local text = data.words
if data.parameter then
text = text .. " " .. data.parameter
end
if modules.game_interface.isChatVisible() then
modules.game_console.sendMessage(text)
else
g_game.talk(text)
end
end
end
function Keybind.bindHotkey(hotkeyId, chatMode)
if not chatMode or chatMode ~= Keybind.chatMode then
return
end
if not modules.game_interface then
return
end
local hotkey = Keybind.hotkeys[chatMode][Keybind.currentPreset][hotkeyId]
if not hotkey then
return
end
local keys = Keybind.getHotkeyKeys(hotkeyId, Keybind.currentPreset, chatMode)
local gameRootPanel = modules.game_interface.getRootPanel()
local action = hotkey.action
hotkey.callback = function() Keybind.hotkeyCallback(hotkeyId, chatMode) end
if keys.primary then
keys.primary = tostring(keys.primary)
if keys.primary:len() > 0 then
if action == HOTKEY_ACTION.EQUIP or action == HOTKEY_ACTION.USE or action == HOTKEY_ACTION.TEXT or action == HOTKEY_ACTION.TEXT_AUTO then
g_keyboard.bindKeyDown(keys.primary, hotkey.callback, gameRootPanel)
else
g_keyboard.bindKeyPress(keys.primary, hotkey.callback, gameRootPanel)
end
end
end
if keys.secondary then
keys.secondary = tostring(keys.secondary)
if keys.secondary:len() > 0 then
if action == HOTKEY_ACTION.EQUIP or action == HOTKEY_ACTION.USE or action == HOTKEY_ACTION.TEXT or action == HOTKEY_ACTION.TEXT_AUTO then
g_keyboard.bindKeyDown(keys.secondary, hotkey.callback, gameRootPanel)
else
g_keyboard.bindKeyPress(keys.secondary, hotkey.callback, gameRootPanel)
end
end
end
end
function Keybind.unbindHotkey(hotkeyId, chatMode)
if not chatMode or chatMode ~= Keybind.chatMode then
return
end
if not modules.game_interface then
return
end
local hotkey = Keybind.hotkeys[chatMode][Keybind.currentPreset][hotkeyId]
if not hotkey then
return
end
local keys = Keybind.getHotkeyKeys(hotkeyId, Keybind.currentPreset, chatMode)
local gameRootPanel = modules.game_interface.getRootPanel()
local action = hotkey.action
if keys.primary then
keys.primary = tostring(keys.primary)
if keys.primary:len() > 0 then
if action == HOTKEY_ACTION.EQUIP or action == HOTKEY_ACTION.USE or action == HOTKEY_ACTION.TEXT or action == HOTKEY_ACTION.TEXT_AUTO then
g_keyboard.unbindKeyDown(keys.primary, hotkey.callback, gameRootPanel)
else
g_keyboard.unbindKeyPress(keys.primary, hotkey.callback, gameRootPanel)
end
end
end
if keys.secondary then
keys.secondary = tostring(keys.secondary)
if keys.secondary:len() > 0 then
if action == HOTKEY_ACTION.EQUIP or action == HOTKEY_ACTION.USE or action == HOTKEY_ACTION.TEXT or action == HOTKEY_ACTION.TEXT_AUTO then
g_keyboard.unbindKeyDown(keys.secondary, hotkey.callback, gameRootPanel)
else
g_keyboard.unbindKeyPress(keys.secondary, hotkey.callback, gameRootPanel)
end
end
end
end
function Keybind.setChatMode(chatMode)
if Keybind.chatMode == chatMode then
return
end
for _, keybind in pairs(Keybind.defaultKeybinds) do
if keybind.callbacks then
Keybind.unbind(keybind.category, keybind.action)
end
end
for _, hotkey in ipairs(Keybind.hotkeys[Keybind.chatMode][Keybind.currentPreset]) do
Keybind.unbindHotkey(hotkey.hotkeyId, Keybind.chatMode)
end
if modules.game_walking then
modules.game_walking.unbindTurnKeys()
end
Keybind.chatMode = chatMode
for _, keybind in pairs(Keybind.defaultKeybinds) do
if keybind.callbacks then
Keybind.bind(keybind.category, keybind.action, keybind.callbacks, keybind.widget)
end
end
for _, hotkey in ipairs(Keybind.hotkeys[chatMode][Keybind.currentPreset]) do
Keybind.bindHotkey(hotkey.hotkeyId, chatMode)
end
if modules.game_walking then
modules.game_walking.bindTurnKeys()
end
end