mirror of
https://github.com/mehah/otclient
synced 2026-08-15 16:29:06 -04:00
New Features: - Native macOS app with Cocoa windowing, OpenGL view, improved input, clipboard and cursor handling. - Platform-aware keyboard and shortcut behavior with clearer primary/meta/control semantics and normalized key combos. Bug Fixes: - Expanded detection for "connection refused" network errors. Chores: - macOS build/bundle and packaging improvements; new dependency overlay added; CI now runs macOS build when relevant. Tests: - AddressSanitizer support added for test builds.
1030 lines
31 KiB
Lua
1030 lines
31 KiB
Lua
HOTKEY_MANAGER_USE = nil
|
|
HOTKEY_MANAGER_USEONSELF = 1
|
|
HOTKEY_MANAGER_USEONTARGET = 2
|
|
HOTKEY_MANAGER_USEWITH = 3
|
|
HOTKEY_MANAGER_USEATCURSOR = 4
|
|
|
|
HOTKEY_ACTION_TOGGLE_WASD = 1
|
|
HOTKEY_ACTION_ATTACK_NEXT = 2
|
|
HOTKEY_ACTION_ATTACK_PREV = 3
|
|
HOTKEY_ACTION_TOGGLE_CHASE = 4
|
|
|
|
HotkeyActions = {{
|
|
id = HOTKEY_ACTION_TOGGLE_WASD,
|
|
text = tr('Toggle WASD chat mode')
|
|
}, {
|
|
id = HOTKEY_ACTION_ATTACK_NEXT,
|
|
text = tr('Attack next creature in battle list')
|
|
}, {
|
|
id = HOTKEY_ACTION_ATTACK_PREV,
|
|
text = tr('Attack previous creature in battle list')
|
|
}, {
|
|
id = HOTKEY_ACTION_TOGGLE_CHASE,
|
|
text = tr('Toggle chase mode')
|
|
}}
|
|
|
|
HotkeyColors = {
|
|
text = '#888888',
|
|
textAutoSend = '#FFFFFF',
|
|
itemUse = '#8888FF',
|
|
itemUseSelf = '#00FF00',
|
|
itemUseTarget = '#FF0000',
|
|
itemUseWith = '#F5B325',
|
|
action = '#F97ACD'
|
|
}
|
|
|
|
hotkeysManagerLoaded = false
|
|
hotkeysWindow = nil
|
|
|
|
currentHotkeyLabel = nil
|
|
currentItemPreview = nil
|
|
itemWidget = nil
|
|
addHotkeyButton = nil
|
|
removeHotkeyButton = nil
|
|
hotkeyActionCombo = nil
|
|
hotkeyText = nil
|
|
hotKeyTextLabel = nil
|
|
sendAutomatically = nil
|
|
selectObjectButton = nil
|
|
clearObjectButton = nil
|
|
useOnSelf = nil
|
|
useOnTarget = nil
|
|
useWith = nil
|
|
useAtCursor = nil
|
|
defaultComboKeys = nil
|
|
perServer = true
|
|
perCharacter = true
|
|
mouseGrabberWidget = nil
|
|
useRadioGroup = nil
|
|
currentHotkeys = nil
|
|
boundCombosCallback = {}
|
|
hotkeysList = {}
|
|
local hotkeyBlockingSources = {}
|
|
local nextSourceId = 1
|
|
lastHotkeyTime = g_clock.millis()
|
|
local hotkeysWindowButton = nil
|
|
|
|
-- public functions
|
|
function init()
|
|
|
|
Keybind.new("Windows", "Show/hide Hotkeys", "Ctrl+K", "")
|
|
Keybind.bind("Windows", "Show/hide Hotkeys", {
|
|
{
|
|
type = KEY_DOWN,
|
|
callback = toggle,
|
|
}
|
|
})
|
|
hotkeysWindow = g_ui.displayUI('hotkeys_manager')
|
|
hotkeysWindow:setVisible(false)
|
|
hotkeysWindowButton = modules.client_topmenu.addRightGameToggleButton('hotkeysWindowButton', tr('Hotkeys'), '/images/options/hotkeys', toggle)
|
|
|
|
currentHotkeys = hotkeysWindow:getChildById('currentHotkeys')
|
|
currentItemPreview = hotkeysWindow:getChildById('itemPreview')
|
|
addHotkeyButton = hotkeysWindow:getChildById('addHotkeyButton')
|
|
removeHotkeyButton = hotkeysWindow:getChildById('removeHotkeyButton')
|
|
hotkeyText = hotkeysWindow:getChildById('hotkeyText')
|
|
hotKeyTextLabel = hotkeysWindow:getChildById('hotKeyTextLabel')
|
|
sendAutomatically = hotkeysWindow:getChildById('sendAutomatically')
|
|
selectObjectButton = hotkeysWindow:getChildById('selectObjectButton')
|
|
clearObjectButton = hotkeysWindow:getChildById('clearObjectButton')
|
|
useOnSelf = hotkeysWindow:getChildById('useOnSelf')
|
|
useOnTarget = hotkeysWindow:getChildById('useOnTarget')
|
|
useWith = hotkeysWindow:getChildById('useWith')
|
|
useAtCursor = hotkeysWindow:getChildById('useAtCursor')
|
|
|
|
useRadioGroup = UIRadioGroup.create()
|
|
useRadioGroup:addWidget(useOnSelf)
|
|
useRadioGroup:addWidget(useOnTarget)
|
|
useRadioGroup:addWidget(useWith)
|
|
useRadioGroup:addWidget(useAtCursor)
|
|
useRadioGroup.onSelectionChange = function(self, selected)
|
|
onChangeUseType(selected)
|
|
end
|
|
|
|
hotkeyActionCombo = hotkeysWindow:getChildById('hotkeyActionCombo')
|
|
|
|
hotkeyActionCombo:addOption('None', 0)
|
|
for _, action in pairs(HotkeyActions) do
|
|
hotkeyActionCombo:addOption(action.text, action.id)
|
|
end
|
|
|
|
hotkeyActionCombo.onOptionChange = onActionChange
|
|
|
|
mouseGrabberWidget = g_ui.createWidget('UIWidget')
|
|
mouseGrabberWidget:setVisible(false)
|
|
mouseGrabberWidget:setFocusable(false)
|
|
mouseGrabberWidget.onMouseRelease = onChooseItemMouseRelease
|
|
|
|
currentHotkeys.onChildFocusChange = function(self, hotkeyLabel)
|
|
onSelectHotkeyLabel(hotkeyLabel)
|
|
end
|
|
g_keyboard.bindKeyPress('Down', function()
|
|
currentHotkeys:focusNextChild(KeyboardFocusReason)
|
|
end, hotkeysWindow)
|
|
g_keyboard.bindKeyPress('Up', function()
|
|
currentHotkeys:focusPreviousChild(KeyboardFocusReason)
|
|
end, hotkeysWindow)
|
|
|
|
connect(g_game, {
|
|
onGameStart = online,
|
|
onGameEnd = offline
|
|
})
|
|
|
|
load()
|
|
end
|
|
|
|
function terminate()
|
|
disconnect(g_game, {
|
|
onGameStart = online,
|
|
onGameEnd = offline
|
|
})
|
|
|
|
Keybind.delete("Windows", "Show/hide Hotkeys")
|
|
|
|
unload()
|
|
|
|
hotkeysWindow:destroy()
|
|
|
|
mouseGrabberWidget:destroy()
|
|
hotkeysWindow = nil
|
|
|
|
hotkeyActionCombo = nil
|
|
hotKeyTextLabel = nil
|
|
hotkeyText = nil
|
|
sendAutomatically = nil
|
|
selectObjectButton = nil
|
|
clearObjectButton = nil
|
|
mouseGrabberWidget = nil
|
|
addHotkeyButton = nil
|
|
removeHotkeyButton = nil
|
|
itemPreview = nil
|
|
useOnSelf = nil
|
|
useOnTarget = nil
|
|
useWith = nil
|
|
useAtCursor = nil
|
|
currentHotkeys = nil
|
|
hotkeysWindowButton = nil
|
|
end
|
|
|
|
function configure(savePerServer, savePerCharacter)
|
|
perServer = savePerServer
|
|
perCharacter = savePerCharacter
|
|
reload()
|
|
end
|
|
|
|
function online()
|
|
reload()
|
|
hide()
|
|
end
|
|
|
|
function offline()
|
|
unload()
|
|
hide()
|
|
end
|
|
|
|
function show()
|
|
if not g_game.isOnline() then
|
|
return
|
|
end
|
|
hotkeysWindow:show()
|
|
hotkeysWindow:raise()
|
|
hotkeysWindow:focus()
|
|
end
|
|
|
|
function hide()
|
|
hotkeysWindow:hide()
|
|
end
|
|
|
|
function toggle()
|
|
if not hotkeysWindow:isVisible() then
|
|
show()
|
|
else
|
|
hide()
|
|
end
|
|
end
|
|
|
|
function ok()
|
|
save()
|
|
hide()
|
|
end
|
|
|
|
function cancel()
|
|
reload()
|
|
hide()
|
|
end
|
|
|
|
function load(forceDefaults)
|
|
local serverHost = nil
|
|
hotkeysManagerLoaded = false
|
|
|
|
local hotkeySettings = g_settings.getNode('game_hotkeys')
|
|
local hotkeys = {}
|
|
|
|
if not table.empty(hotkeySettings) then
|
|
hotkeys = hotkeySettings
|
|
end
|
|
if perServer and not table.empty(hotkeys) then
|
|
if G.host ~= nil then
|
|
serverHost = string.gsub(G.host, "^https?://", "")
|
|
hotkeys = hotkeys[serverHost]
|
|
end
|
|
end
|
|
if perCharacter and not table.empty(hotkeys) then
|
|
hotkeys = hotkeys[g_game.getCharacterName()]
|
|
end
|
|
|
|
hotkeyList = {}
|
|
if not forceDefaults then
|
|
if not table.empty(hotkeys) then
|
|
for keyCombo, setting in pairs(hotkeys) do
|
|
keyCombo = tostring(keyCombo)
|
|
addKeyCombo(keyCombo, setting)
|
|
hotkeyList[keyCombo] = setting
|
|
end
|
|
end
|
|
end
|
|
|
|
if currentHotkeys:getChildCount() == 0 then
|
|
loadDefautComboKeys()
|
|
end
|
|
|
|
hotkeysManagerLoaded = true
|
|
end
|
|
|
|
function unload()
|
|
for keyCombo, callback in pairs(boundCombosCallback) do
|
|
g_keyboard.unbindKeyPress(keyCombo, callback)
|
|
end
|
|
boundCombosCallback = {}
|
|
currentHotkeys:destroyChildren()
|
|
currentHotkeyLabel = nil
|
|
updateHotkeyForm(true)
|
|
hotkeyList = {}
|
|
end
|
|
|
|
function reset()
|
|
unload()
|
|
load(true)
|
|
end
|
|
|
|
function reload()
|
|
unload()
|
|
load()
|
|
end
|
|
|
|
function save()
|
|
local serverHost = string.gsub(G.host, "^https?://", "")
|
|
local hotkeySettings = g_settings.getNode('game_hotkeys') or {}
|
|
local hotkeys = hotkeySettings
|
|
|
|
if perServer then
|
|
if not hotkeys[serverHost] then
|
|
hotkeys[serverHost] = {}
|
|
end
|
|
hotkeys = hotkeys[serverHost]
|
|
end
|
|
|
|
if perCharacter then
|
|
local char = g_game.getCharacterName()
|
|
if not hotkeys[char] then
|
|
hotkeys[char] = {}
|
|
end
|
|
hotkeys = hotkeys[char]
|
|
end
|
|
|
|
table.clear(hotkeys)
|
|
|
|
for _, child in pairs(currentHotkeys:getChildren()) do
|
|
hotkeys[child.keyCombo] = {
|
|
autoSend = child.autoSend,
|
|
itemId = child.itemId,
|
|
subType = child.subType,
|
|
useType = child.useType,
|
|
value = child.value,
|
|
action = child.action
|
|
}
|
|
end
|
|
|
|
hotkeyList = hotkeys
|
|
g_settings.setNode('game_hotkeys', hotkeySettings)
|
|
g_settings.save()
|
|
end
|
|
|
|
function loadDefautComboKeys()
|
|
if not defaultComboKeys then
|
|
for i = 1, 12 do
|
|
addKeyCombo('F' .. i)
|
|
end
|
|
for i = 1, 4 do
|
|
addKeyCombo('Shift+F' .. i)
|
|
end
|
|
else
|
|
for keyCombo, keySettings in pairs(defaultComboKeys) do
|
|
addKeyCombo(keyCombo, keySettings)
|
|
end
|
|
end
|
|
end
|
|
|
|
function setDefaultComboKeys(combo)
|
|
defaultComboKeys = combo
|
|
end
|
|
|
|
function onActionChange(comboBox, option)
|
|
local action = comboBox:getCurrentOption().data
|
|
if currentHotkeyLabel then
|
|
if action > 0 then
|
|
currentHotkeyLabel.action = action
|
|
currentHotkeyLabel.itemId = nil
|
|
currentHotkeyLabel.value = nil
|
|
currentHotkeyLabel.autoSend = nil
|
|
else
|
|
currentHotkeyLabel.action = nil
|
|
end
|
|
updateHotkeyLabel(currentHotkeyLabel)
|
|
updateHotkeyForm(true, true)
|
|
end
|
|
end
|
|
|
|
function onChooseItemMouseRelease(self, mousePosition, mouseButton)
|
|
local item = nil
|
|
if mouseButton == MouseLeftButton then
|
|
local clickedWidget = modules.game_interface.getRootPanel():recursiveGetChildByPos(mousePosition, false)
|
|
if clickedWidget then
|
|
if clickedWidget:getClassName() == 'UIGameMap' then
|
|
local tile = clickedWidget:getTile(mousePosition)
|
|
if tile then
|
|
local thing = tile:getTopMoveThing()
|
|
if thing and thing:isItem() then
|
|
item = thing
|
|
end
|
|
end
|
|
elseif clickedWidget:getClassName() == 'UIItem' and not clickedWidget:isVirtual() then
|
|
item = clickedWidget:getItem()
|
|
end
|
|
end
|
|
end
|
|
|
|
if item and currentHotkeyLabel then
|
|
currentHotkeyLabel.itemId = item:getId()
|
|
if item:isFluidContainer() then
|
|
currentHotkeyLabel.subType = item:getSubType()
|
|
end
|
|
if item:isMultiUse() then
|
|
currentHotkeyLabel.useType = HOTKEY_MANAGER_USEWITH
|
|
else
|
|
currentHotkeyLabel.useType = HOTKEY_MANAGER_USE
|
|
end
|
|
currentHotkeyLabel.value = nil
|
|
currentHotkeyLabel.autoSend = false
|
|
updateHotkeyLabel(currentHotkeyLabel)
|
|
updateHotkeyForm(true)
|
|
end
|
|
|
|
show()
|
|
|
|
-- Restore cursor
|
|
if modules.client_options and modules.client_options.getOption('nativeCursor') then
|
|
g_window.restoreMouseCursor()
|
|
else
|
|
g_mouse.popCursor('target')
|
|
end
|
|
self:ungrabMouse()
|
|
return true
|
|
end
|
|
|
|
function startChooseItem()
|
|
if g_ui.isMouseGrabbed() then
|
|
return
|
|
end
|
|
mouseGrabberWidget:grabMouse()
|
|
-- Use native cursor when enabled, otherwise use custom cursor
|
|
if modules.client_options and modules.client_options.getOption('nativeCursor') then
|
|
g_window.setSystemCursor('cross')
|
|
else
|
|
g_mouse.pushCursor('target')
|
|
end
|
|
hide()
|
|
end
|
|
|
|
function clearObject()
|
|
currentHotkeyLabel.itemId = nil
|
|
currentHotkeyLabel.subType = nil
|
|
currentHotkeyLabel.useType = nil
|
|
currentHotkeyLabel.autoSend = nil
|
|
currentHotkeyLabel.value = nil
|
|
currentHotkeyLabel.action = nil
|
|
updateHotkeyLabel(currentHotkeyLabel)
|
|
updateHotkeyForm(true)
|
|
end
|
|
|
|
function addHotkey()
|
|
local assignWindow = g_ui.createWidget('HotkeyAssignWindow', rootWidget)
|
|
assignWindow:grabKeyboard()
|
|
|
|
local comboLabel = assignWindow:getChildById('comboPreview')
|
|
comboLabel.keyCombo = ''
|
|
assignWindow.onKeyDown = hotkeyCapture
|
|
end
|
|
|
|
function addKeyCombo(keyCombo, keySettings, focus)
|
|
if keyCombo == nil or #keyCombo == 0 then
|
|
return
|
|
end
|
|
if not keyCombo then
|
|
return
|
|
end
|
|
if modules.game_actionbar and modules.game_actionbar.removeHotkeyFromActionBar then
|
|
modules.game_actionbar.removeHotkeyFromActionBar(keyCombo)
|
|
end
|
|
local hotkeyLabel = currentHotkeys:getChildById(keyCombo)
|
|
if not hotkeyLabel then
|
|
hotkeyLabel = g_ui.createWidget('HotkeyListLabel')
|
|
hotkeyLabel:setId(keyCombo)
|
|
|
|
local children = currentHotkeys:getChildren()
|
|
children[#children + 1] = hotkeyLabel
|
|
table.sort(children, function(a, b)
|
|
if a:getId():len() < b:getId():len() then
|
|
return true
|
|
elseif a:getId():len() == b:getId():len() then
|
|
return a:getId() < b:getId()
|
|
else
|
|
return false
|
|
end
|
|
end)
|
|
for i = 1, #children do
|
|
if children[i] == hotkeyLabel then
|
|
currentHotkeys:insertChild(i, hotkeyLabel)
|
|
break
|
|
end
|
|
end
|
|
|
|
if keySettings then
|
|
currentHotkeyLabel = hotkeyLabel
|
|
hotkeyLabel.keyCombo = keyCombo
|
|
hotkeyLabel.autoSend = toboolean(keySettings.autoSend)
|
|
hotkeyLabel.itemId = tonumber(keySettings.itemId)
|
|
hotkeyLabel.subType = tonumber(keySettings.subType)
|
|
hotkeyLabel.useType = tonumber(keySettings.useType)
|
|
hotkeyLabel.action = tonumber(keySettings.action)
|
|
if keySettings.value then
|
|
hotkeyLabel.value = tostring(keySettings.value)
|
|
end
|
|
else
|
|
hotkeyLabel.keyCombo = keyCombo
|
|
hotkeyLabel.autoSend = false
|
|
hotkeyLabel.itemId = nil
|
|
hotkeyLabel.subType = nil
|
|
hotkeyLabel.useType = nil
|
|
hotkeyLabel.action = nil
|
|
hotkeyLabel.value = ''
|
|
end
|
|
|
|
updateHotkeyLabel(hotkeyLabel)
|
|
|
|
boundCombosCallback[keyCombo] = function()
|
|
doKeyCombo(keyCombo)
|
|
end
|
|
g_keyboard.bindKeyPress(keyCombo, boundCombosCallback[keyCombo])
|
|
end
|
|
|
|
if focus then
|
|
currentHotkeys:focusChild(hotkeyLabel)
|
|
currentHotkeys:ensureChildVisible(hotkeyLabel)
|
|
updateHotkeyForm(true)
|
|
end
|
|
end
|
|
|
|
function doKeyCombo(keyCombo)
|
|
if not g_game.isOnline() then
|
|
return
|
|
end
|
|
if not canPerformKeyCombo(keyCombo) then
|
|
return
|
|
end
|
|
local hotKey = hotkeyList[keyCombo]
|
|
if not hotKey then
|
|
return
|
|
end
|
|
|
|
if g_clock.millis() - lastHotkeyTime < modules.client_options.getOption('hotkeyDelay') then
|
|
return
|
|
end
|
|
lastHotkeyTime = g_clock.millis()
|
|
|
|
if hotKey.action then
|
|
if hotKey.action == HOTKEY_ACTION_TOGGLE_WASD then
|
|
modules.game_console.toggleChat()
|
|
elseif hotKey.action == HOTKEY_ACTION_ATTACK_NEXT then
|
|
modules.game_battle.attackNext()
|
|
elseif hotKey.action == HOTKEY_ACTION_ATTACK_PREV then
|
|
modules.game_battle.attackNext(true)
|
|
elseif hotKey.action == HOTKEY_ACTION_TOGGLE_CHASE then
|
|
toggleChaseMode()
|
|
end
|
|
|
|
elseif hotKey.itemId == nil then
|
|
if not hotKey.value or #hotKey.value == 0 then
|
|
return
|
|
end
|
|
if hotKey.autoSend then
|
|
modules.game_console.sendMessage(hotKey.value)
|
|
else
|
|
scheduleEvent(function()
|
|
if not modules.game_console.isChatEnabled() then
|
|
modules.game_console.switchChatOnCall()
|
|
end
|
|
modules.game_console.setTextEditText(hotKey.value)
|
|
end, 1)
|
|
end
|
|
else
|
|
executeHotkeyItem(hotKey.useType, hotKey.itemId, hotKey.subType)
|
|
end
|
|
end
|
|
|
|
function toggleChaseMode()
|
|
local currentMode = g_game.getChaseMode()
|
|
local nextMode = currentMode == ChaseOpponent and DontChase or ChaseOpponent
|
|
g_game.setChaseMode(nextMode)
|
|
end
|
|
|
|
function executeHotkeyItem(action, itemId, subType)
|
|
local function get_use_thing_under_cursor()
|
|
local mapPanel = modules.game_interface and modules.game_interface.getMapPanel and modules.game_interface.getMapPanel()
|
|
if not mapPanel then
|
|
return nil
|
|
end
|
|
|
|
local mousePosition = g_window.getMousePosition()
|
|
if not mapPanel:containsPoint(mousePosition) then
|
|
return nil
|
|
end
|
|
|
|
local mapPosition = mapPanel:getPosition(mousePosition)
|
|
if not mapPosition then
|
|
return nil
|
|
end
|
|
|
|
local localPlayer = g_game.getLocalPlayer()
|
|
if localPlayer and mapPosition.z ~= localPlayer:getPosition().z then
|
|
local dz = mapPosition.z - localPlayer:getPosition().z
|
|
mapPosition.x = mapPosition.x + dz
|
|
mapPosition.y = mapPosition.y + dz
|
|
mapPosition.z = localPlayer:getPosition().z
|
|
end
|
|
|
|
local tile = g_map.getTile(mapPosition)
|
|
if not tile then
|
|
return nil
|
|
end
|
|
|
|
local virtualItem = Item.create(itemId)
|
|
if virtualItem:isFluidContainer() or virtualItem:isMultiUse() then
|
|
return tile:getTopMultiUseThing()
|
|
end
|
|
return tile:getTopUseThing()
|
|
end
|
|
|
|
if action == HOTKEY_MANAGER_USE then
|
|
if g_game.getClientVersion() < 780 or subType then
|
|
local item = g_game.findPlayerItem(itemId, subType or -1)
|
|
if item then
|
|
g_game.use(item)
|
|
end
|
|
else
|
|
g_game.useInventoryItem(itemId)
|
|
end
|
|
elseif action == HOTKEY_MANAGER_USEONSELF then
|
|
if g_game.getClientVersion() < 780 or subType then
|
|
local item = g_game.findPlayerItem(itemId, subType or -1)
|
|
if item then
|
|
g_game.useWith(item, g_game.getLocalPlayer())
|
|
end
|
|
else
|
|
g_game.useInventoryItemWith(itemId, g_game.getLocalPlayer())
|
|
end
|
|
elseif action == HOTKEY_MANAGER_USEONTARGET then
|
|
local attackingCreature = g_game.getAttackingCreature()
|
|
if not attackingCreature then
|
|
local item = Item.create(itemId)
|
|
if g_game.getClientVersion() < 780 or subType then
|
|
local tmpItem = g_game.findPlayerItem(itemId, subType or -1)
|
|
if not tmpItem then
|
|
return
|
|
end
|
|
item = tmpItem
|
|
end
|
|
|
|
modules.game_interface.startUseWith(item)
|
|
return
|
|
end
|
|
|
|
if not attackingCreature:getTile() then
|
|
return
|
|
end
|
|
if g_game.getClientVersion() < 780 or subType then
|
|
local item = g_game.findPlayerItem(itemId, subType or -1)
|
|
if item then
|
|
g_game.useWith(item, attackingCreature)
|
|
end
|
|
else
|
|
g_game.useInventoryItemWith(itemId, attackingCreature)
|
|
end
|
|
elseif action == HOTKEY_MANAGER_USEWITH then
|
|
local item = Item.create(itemId)
|
|
if g_game.getClientVersion() < 780 or subType then
|
|
local tmpItem = g_game.findPlayerItem(itemId, subType or -1)
|
|
if not tmpItem then
|
|
return true
|
|
end
|
|
item = tmpItem
|
|
end
|
|
modules.game_interface.startUseWith(item)
|
|
elseif action == HOTKEY_MANAGER_USEATCURSOR then
|
|
local useThing = get_use_thing_under_cursor()
|
|
if not useThing then
|
|
local item = Item.create(itemId)
|
|
if g_game.getClientVersion() < 780 or subType then
|
|
local tmpItem = g_game.findPlayerItem(itemId, subType or -1)
|
|
if not tmpItem then
|
|
return
|
|
end
|
|
item = tmpItem
|
|
end
|
|
modules.game_interface.startUseWith(item)
|
|
return
|
|
end
|
|
|
|
if g_game.getClientVersion() < 780 or subType then
|
|
local item = g_game.findPlayerItem(itemId, subType or -1)
|
|
if item then
|
|
g_game.useWith(item, useThing)
|
|
end
|
|
else
|
|
g_game.useInventoryItemWith(itemId, useThing)
|
|
end
|
|
end
|
|
end
|
|
|
|
function updateHotkeyLabel(hotkeyLabel)
|
|
if not hotkeyLabel then
|
|
return
|
|
end
|
|
if hotkeyLabel.useType == HOTKEY_MANAGER_USEONSELF then
|
|
hotkeyLabel:setText(tr('%s: (use object on yourself)', hotkeyLabel.keyCombo))
|
|
hotkeyLabel:setColor(HotkeyColors.itemUseSelf)
|
|
elseif hotkeyLabel.useType == HOTKEY_MANAGER_USEONTARGET then
|
|
hotkeyLabel:setText(tr('%s: (use object on target)', hotkeyLabel.keyCombo))
|
|
hotkeyLabel:setColor(HotkeyColors.itemUseTarget)
|
|
elseif hotkeyLabel.useType == HOTKEY_MANAGER_USEWITH then
|
|
hotkeyLabel:setText(tr('%s: (use object with crosshair)', hotkeyLabel.keyCombo))
|
|
hotkeyLabel:setColor(HotkeyColors.itemUseWith)
|
|
elseif hotkeyLabel.useType == HOTKEY_MANAGER_USEATCURSOR then
|
|
hotkeyLabel:setText(tr('%s: (use object at cursor position)', hotkeyLabel.keyCombo))
|
|
hotkeyLabel:setColor(HotkeyColors.itemUseWith)
|
|
elseif hotkeyLabel.itemId ~= nil then
|
|
hotkeyLabel:setText(tr('%s: (use object)', hotkeyLabel.keyCombo))
|
|
hotkeyLabel:setColor(HotkeyColors.itemUse)
|
|
elseif hotkeyLabel.action then
|
|
for _, action in pairs(HotkeyActions) do
|
|
if action.id == hotkeyLabel.action then
|
|
hotkeyLabel:setText(tr('%s: ' .. action.text, hotkeyLabel.keyCombo))
|
|
break
|
|
end
|
|
end
|
|
hotkeyLabel:setColor(HotkeyColors.action)
|
|
else
|
|
local text = hotkeyLabel.keyCombo .. ': '
|
|
if hotkeyLabel.value then
|
|
text = text .. hotkeyLabel.value
|
|
end
|
|
hotkeyLabel:setText(text)
|
|
if hotkeyLabel.autoSend then
|
|
hotkeyLabel:setColor(HotkeyColors.autoSend)
|
|
else
|
|
hotkeyLabel:setColor(HotkeyColors.text)
|
|
end
|
|
end
|
|
end
|
|
|
|
function updateHotkeyForm(reset, dontUpdateCombo)
|
|
if currentHotkeyLabel then
|
|
removeHotkeyButton:enable()
|
|
if currentHotkeyLabel.itemId ~= nil then
|
|
if not dontUpdateCombo then
|
|
hotkeyActionCombo:setCurrentIndex(1)
|
|
end
|
|
hotkeyActionCombo:disable()
|
|
hotkeyText:clearText()
|
|
hotkeyText:disable()
|
|
hotKeyTextLabel:disable()
|
|
sendAutomatically:setChecked(false)
|
|
sendAutomatically:disable()
|
|
selectObjectButton:disable()
|
|
clearObjectButton:enable()
|
|
currentItemPreview:setItemId(currentHotkeyLabel.itemId)
|
|
if currentHotkeyLabel.subType then
|
|
currentItemPreview:setItemSubType(currentHotkeyLabel.subType)
|
|
end
|
|
if currentItemPreview:getItem():isMultiUse() then
|
|
useOnSelf:enable()
|
|
useOnTarget:enable()
|
|
useWith:enable()
|
|
useAtCursor:enable()
|
|
if currentHotkeyLabel.useType == HOTKEY_MANAGER_USEONSELF then
|
|
useRadioGroup:selectWidget(useOnSelf)
|
|
elseif currentHotkeyLabel.useType == HOTKEY_MANAGER_USEONTARGET then
|
|
useRadioGroup:selectWidget(useOnTarget)
|
|
elseif currentHotkeyLabel.useType == HOTKEY_MANAGER_USEWITH then
|
|
useRadioGroup:selectWidget(useWith)
|
|
elseif currentHotkeyLabel.useType == HOTKEY_MANAGER_USEATCURSOR then
|
|
useRadioGroup:selectWidget(useAtCursor)
|
|
end
|
|
else
|
|
useOnSelf:disable()
|
|
useOnTarget:disable()
|
|
useWith:disable()
|
|
useAtCursor:disable()
|
|
useRadioGroup:clearSelected()
|
|
end
|
|
elseif currentHotkeyLabel.action then
|
|
if not dontUpdateCombo then
|
|
hotkeyActionCombo:setCurrentOptionByData(currentHotkeyLabel.action)
|
|
end
|
|
hotkeyActionCombo:enable()
|
|
hotkeyText:clearText()
|
|
hotkeyText:disable()
|
|
hotKeyTextLabel:disable()
|
|
sendAutomatically:setChecked(false)
|
|
sendAutomatically:disable()
|
|
selectObjectButton:disable()
|
|
useOnSelf:disable()
|
|
useOnTarget:disable()
|
|
useWith:disable()
|
|
useAtCursor:disable()
|
|
useRadioGroup:clearSelected()
|
|
selectObjectButton:disable()
|
|
clearObjectButton:disable()
|
|
else
|
|
if not dontUpdateCombo then
|
|
hotkeyActionCombo:setCurrentIndex(1)
|
|
end
|
|
hotkeyActionCombo:enable()
|
|
useOnSelf:disable()
|
|
useOnTarget:disable()
|
|
useWith:disable()
|
|
useAtCursor:disable()
|
|
useRadioGroup:clearSelected()
|
|
hotkeyText:enable()
|
|
hotkeyText:focus()
|
|
hotKeyTextLabel:enable()
|
|
hotkeyText:setText(currentHotkeyLabel.value)
|
|
if reset then
|
|
hotkeyText:setCursorPos(-1)
|
|
end
|
|
sendAutomatically:setChecked(currentHotkeyLabel.autoSend)
|
|
sendAutomatically:setEnabled(currentHotkeyLabel.value and #currentHotkeyLabel.value > 0)
|
|
selectObjectButton:enable()
|
|
clearObjectButton:disable()
|
|
currentItemPreview:clearItem()
|
|
end
|
|
else
|
|
if not dontUpdateCombo then
|
|
hotkeyActionCombo:setCurrentIndex(1)
|
|
end
|
|
hotkeyActionCombo:disable()
|
|
removeHotkeyButton:disable()
|
|
hotkeyText:disable()
|
|
sendAutomatically:disable()
|
|
selectObjectButton:disable()
|
|
clearObjectButton:disable()
|
|
useOnSelf:disable()
|
|
useOnTarget:disable()
|
|
useWith:disable()
|
|
useAtCursor:disable()
|
|
hotkeyText:clearText()
|
|
useRadioGroup:clearSelected()
|
|
sendAutomatically:setChecked(false)
|
|
currentItemPreview:clearItem()
|
|
end
|
|
end
|
|
|
|
function removeHotkey()
|
|
if currentHotkeyLabel == nil then
|
|
return
|
|
end
|
|
g_keyboard.unbindKeyPress(currentHotkeyLabel.keyCombo, boundCombosCallback[currentHotkeyLabel.keyCombo])
|
|
boundCombosCallback[currentHotkeyLabel.keyCombo] = nil
|
|
currentHotkeyLabel:destroy()
|
|
currentHotkeyLabel = nil
|
|
end
|
|
|
|
function onHotkeyTextChange(value)
|
|
if not hotkeysManagerLoaded then
|
|
return
|
|
end
|
|
if currentHotkeyLabel == nil then
|
|
return
|
|
end
|
|
currentHotkeyLabel.value = value
|
|
if value == '' then
|
|
currentHotkeyLabel.autoSend = false
|
|
end
|
|
updateHotkeyLabel(currentHotkeyLabel)
|
|
updateHotkeyForm(false, true)
|
|
end
|
|
|
|
function onSendAutomaticallyChange(autoSend)
|
|
if not hotkeysManagerLoaded then
|
|
return
|
|
end
|
|
if currentHotkeyLabel == nil then
|
|
return
|
|
end
|
|
if not currentHotkeyLabel.value or #currentHotkeyLabel.value == 0 then
|
|
return
|
|
end
|
|
currentHotkeyLabel.autoSend = autoSend
|
|
updateHotkeyLabel(currentHotkeyLabel)
|
|
updateHotkeyForm(false, true)
|
|
end
|
|
|
|
function onChangeUseType(useTypeWidget)
|
|
if not hotkeysManagerLoaded then
|
|
return
|
|
end
|
|
if currentHotkeyLabel == nil then
|
|
return
|
|
end
|
|
if useTypeWidget == useOnSelf then
|
|
currentHotkeyLabel.useType = HOTKEY_MANAGER_USEONSELF
|
|
elseif useTypeWidget == useOnTarget then
|
|
currentHotkeyLabel.useType = HOTKEY_MANAGER_USEONTARGET
|
|
elseif useTypeWidget == useWith then
|
|
currentHotkeyLabel.useType = HOTKEY_MANAGER_USEWITH
|
|
elseif useTypeWidget == useAtCursor then
|
|
currentHotkeyLabel.useType = HOTKEY_MANAGER_USEATCURSOR
|
|
else
|
|
currentHotkeyLabel.useType = HOTKEY_MANAGER_USE
|
|
end
|
|
updateHotkeyLabel(currentHotkeyLabel)
|
|
updateHotkeyForm()
|
|
end
|
|
|
|
function onSelectHotkeyLabel(hotkeyLabel)
|
|
currentHotkeyLabel = hotkeyLabel
|
|
updateHotkeyForm(true)
|
|
end
|
|
|
|
function hotkeyCapture(assignWindow, keyCode, keyboardModifiers)
|
|
local keyCombo = determineKeyComboDesc(keyCode, keyboardModifiers)
|
|
local comboPreview = assignWindow:getChildById('comboPreview')
|
|
comboPreview:setText(tr('Current hotkey to add: %s', keyCombo))
|
|
comboPreview.keyCombo = keyCombo
|
|
comboPreview:resizeToText()
|
|
assignWindow:getChildById('addButton'):enable()
|
|
return true
|
|
end
|
|
|
|
function hotkeyCaptureOk(assignWindow, keyCombo)
|
|
addKeyCombo(keyCombo, nil, true)
|
|
assignWindow:destroy()
|
|
end
|
|
|
|
function enableHotkeys(sourceId)
|
|
if sourceId then
|
|
hotkeyBlockingSources[sourceId] = nil
|
|
end
|
|
end
|
|
|
|
function disableHotkeys(sourceIdentifier)
|
|
local sourceId = sourceIdentifier or ("auto_" .. nextSourceId)
|
|
nextSourceId = nextSourceId + 1
|
|
hotkeyBlockingSources[sourceId] = true
|
|
return sourceId
|
|
end
|
|
|
|
local function getCallerModule()
|
|
local info = debug.getinfo(3, "S")
|
|
if info and info.source then
|
|
local source = info.source:gsub("@", "")
|
|
local moduleName = source:match("/modules/([^/]+)/") or
|
|
source:match("\\modules\\([^\\]+)\\") or
|
|
source:match("([^/\\]+)%.lua$") or
|
|
"unknown"
|
|
return moduleName:gsub("_", "")
|
|
end
|
|
return "unknown"
|
|
end
|
|
|
|
function createHotkeyBlock(sourceIdentifier)
|
|
local callerModule = getCallerModule()
|
|
local fullId = sourceIdentifier and
|
|
(sourceIdentifier .. "_" .. callerModule) or
|
|
("auto_" .. callerModule .. "_" .. nextSourceId)
|
|
local blockId = disableHotkeys(fullId)
|
|
return {
|
|
release = function()
|
|
enableHotkeys(blockId)
|
|
end,
|
|
getId = function()
|
|
return fullId
|
|
end
|
|
}
|
|
end
|
|
|
|
function areHotkeysDisabled()
|
|
for _ in pairs(hotkeyBlockingSources) do
|
|
return true
|
|
end
|
|
return false
|
|
end
|
|
|
|
function clearAllHotkeyBlocks()
|
|
hotkeyBlockingSources = {}
|
|
end
|
|
function getHotkeyBlockingInfo()
|
|
local count = 0
|
|
local sources = {}
|
|
for sourceId in pairs(hotkeyBlockingSources) do
|
|
count = count + 1
|
|
table.insert(sources, sourceId)
|
|
end
|
|
table.sort(sources)
|
|
return count, sources
|
|
end
|
|
|
|
function printHotkeyBlockingInfo()
|
|
local count, sources = getHotkeyBlockingInfo()
|
|
print("=== Hotkey Blocking Info ===")
|
|
print("Total blocks: " .. count)
|
|
if count > 0 then
|
|
print("Active sources:")
|
|
for i, source in ipairs(sources) do
|
|
print(" " .. i .. ". " .. source)
|
|
end
|
|
else
|
|
print("No active blocks")
|
|
end
|
|
print("===========================")
|
|
end
|
|
|
|
-- Even if hotkeys are enabled, only the hotkeys containing Ctrl or Alt or F1-F12 will be enabled when
|
|
-- chat is opened (no WASD mode). This is made to prevent executing hotkeys while typing...
|
|
function canPerformKeyCombo(keyCombo)
|
|
if areHotkeysDisabled() then
|
|
return false
|
|
end
|
|
if not modules.game_console.isChatEnabled() then
|
|
return true
|
|
end
|
|
local platformType = g_window.getPlatformType() or ""
|
|
local isMacOS = platformType:find("MACOS") ~= nil
|
|
if isMacOS then
|
|
return string.match(keyCombo, "Cmd%+") or
|
|
string.match(keyCombo, "Ctrl%+") or
|
|
string.match(keyCombo, "Alt%+") or
|
|
string.match(keyCombo, "Option%+") or
|
|
string.match(keyCombo, "F%d+")
|
|
end
|
|
return string.match(keyCombo, "Ctrl%+") or
|
|
string.match(keyCombo, "Alt%+") or
|
|
string.match(keyCombo, "F%d+")
|
|
end
|
|
|
|
-- Actionbar
|
|
function removeHotkeyByCombo(keyCombo)
|
|
if not keyCombo or keyCombo == "" then
|
|
return false
|
|
end
|
|
local hotkeyLabel = currentHotkeys and currentHotkeys:getChildById(keyCombo)
|
|
if hotkeyLabel then
|
|
if boundCombosCallback[keyCombo] then
|
|
g_keyboard.unbindKeyPress(keyCombo, boundCombosCallback[keyCombo])
|
|
boundCombosCallback[keyCombo] = nil
|
|
end
|
|
if currentHotkeyLabel == hotkeyLabel then
|
|
currentHotkeyLabel = nil
|
|
end
|
|
hotkeyLabel:destroy()
|
|
updateHotkeyForm(true)
|
|
return true
|
|
end
|
|
return false
|
|
end
|
|
|
|
function isHotkeyUsedByManager(keyCombo)
|
|
if not keyCombo or keyCombo == "" then
|
|
return false
|
|
end
|
|
if boundCombosCallback[keyCombo] then
|
|
return true
|
|
end
|
|
if currentHotkeys then
|
|
local hotkeyLabel = currentHotkeys:getChildById(keyCombo)
|
|
if hotkeyLabel then
|
|
return true
|
|
end
|
|
end
|
|
return false
|
|
end
|