diff --git a/modules/client_entergame/entergame.lua b/modules/client_entergame/entergame.lua
index ad013c0..9a3bb7e 100644
--- a/modules/client_entergame/entergame.lua
+++ b/modules/client_entergame/entergame.lua
@@ -422,7 +422,9 @@ function EnterGame.doLogin()
   g_game.setProtocolVersion(g_game.getClientProtocolVersion(G.clientVersion))
   g_game.setCustomProtocolVersion(0)
   g_game.chooseRsa(G.host)
-  -- g_game.setCustomOs(2) -- windows, optional
+  if #server_params <= 3 then
+    g_game.setCustomOs(2) -- set os to windows
+  end
 
   -- extra features from init.lua
   for i = 4, #server_params do
diff --git a/modules/game_bot/bot.lua b/modules/game_bot/bot.lua
index 9b01b6a..775b559 100644
--- a/modules/game_bot/bot.lua
+++ b/modules/game_bot/bot.lua
@@ -181,7 +181,13 @@ function editConfig()
   configWindow:raise()
   configWindow:focus()
   editorText = {botConfig.configs[config].script or "", ""}
-  configEditorText:setText(botConfig.configs[config].script)
+  if #editorText[1] <= 2 then
+    editorText[1] = "--config name\n\n"
+    for k, v in ipairs(tabs) do
+      editorText[1] = editorText[1] .. "--#" .. v .. "\n\n"  
+    end    
+  end
+  configEditorText:setText(editorText[1])
   configEditorText:setEditable(true)
   activeTab = mainTab
   configTab:selectTab(mainTab)
@@ -208,8 +214,12 @@ function restoreMainTab()
     editorText = {configEditorText:getText(), ""}
     return
   end
-  editorText = {editorText[1] .. "--#" .. activeTab:getText():lower() .. "\n" .. configEditorText:getText() .. editorText[2], ""}
-  configEditorText:setText(editorText[1])  
+  local currentText = configEditorText:getText()
+  if #currentText > 0 and currentText:sub(#currentText, #currentText) ~= '\n' then
+    currentText = currentText .. '\n'
+  end
+  editorText = {editorText[1] .. "--#" .. activeTab:getText():lower() .. "\n" .. currentText .. editorText[2], ""}
+  configEditorText:setText(editorText[1])
 end
 
 function editorTabChanged(holder, tab)
@@ -279,7 +289,7 @@ function refreshConfig()
   local status, result = pcall(function() return executeBot(config.script, config.storage, botPanel, botMsgCallback) end)
   if not status then    
     errorOccured = true
-    statusLabel:setText(tr("Error: " .. tostring(result)))
+    statusLabel:setText("Error: " .. tostring(result))
     return
   end
   compiledConfig = result
@@ -300,7 +310,7 @@ function executeConfig()
   local status, result = pcall(function() return compiledConfig.script() end)
   if not status then    
     errorOccured = true
-    statusLabel:setText(tr("Error: " .. result))
+    statusLabel:setText("Error: " .. result)
     return
   end 
 end
@@ -337,7 +347,7 @@ function botKeyDown(widget, keyCode, keyboardModifiers)
   local status, result = pcall(function() compiledConfig.callbacks.onKeyDown(keyCode, keyboardModifiers) end)
   if not status then    
     errorOccured = true
-    statusLabel:setText(tr("Error: " .. result))
+    statusLabel:setText("Error: " .. result)
   end
   return false
 end
@@ -347,7 +357,7 @@ function botKeyUp(widget, keyCode, keyboardModifiers)
   local status, result = pcall(function() compiledConfig.callbacks.onKeyUp(keyCode, keyboardModifiers) end)
   if not status then    
     errorOccured = true
-    statusLabel:setText(tr("Error: " .. result))
+    statusLabel:setText("Error: " .. result)
   end
   return false
 end
@@ -357,7 +367,7 @@ function botKeyPress(widget, keyCode, keyboardModifiers, autoRepeatTicks)
   local status, result = pcall(function() compiledConfig.callbacks.onKeyPress(keyCode, keyboardModifiers, autoRepeatTicks) end)
   if not status then    
     errorOccured = true
-    statusLabel:setText(tr("Error: " .. result))
+    statusLabel:setText("Error: " .. result)
   end
   return false
 end
@@ -367,7 +377,7 @@ function botOnTalk(name, level, mode, text, channelId, pos)
   local status, result = pcall(function() compiledConfig.callbacks.onTalk(name, level, mode, text, channelId, pos) end)
   if not status then    
     errorOccured = true
-    statusLabel:setText(tr("Error: " .. result))
+    statusLabel:setText("Error: " .. result)
   end
   return false  
 end
@@ -377,7 +387,7 @@ function botAddThing(tile, thing, asd)
   local status, result = pcall(function() compiledConfig.callbacks.onAddThing(tile, thing) end)
   if not status then    
     errorOccured = true
-    statusLabel:setText(tr("Error: " .. result))
+    statusLabel:setText("Error: " .. result)
   end
   return false 
 end
@@ -387,7 +397,7 @@ function botRemoveThing(tile, thing)
   local status, result = pcall(function() compiledConfig.callbacks.onRemoveThing(tile, thing) end)
   if not status then    
     errorOccured = true
-    statusLabel:setText(tr("Error: " .. result))
+    statusLabel:setText("Error: " .. result)
   end
   return false
 end
\ No newline at end of file
diff --git a/modules/game_bot/bot.otui b/modules/game_bot/bot.otui
index 3f82b90..411add3 100644
--- a/modules/game_bot/bot.otui
+++ b/modules/game_bot/bot.otui
@@ -13,15 +13,21 @@ BotLabel < Label
   text-align: center
   text-wrap: true
     
+BotItem < UIItem  
+  virtual: true
+  size: 32 32
+  border: 1 black
+  &selectable: true
+  
+BotSeparator < HorizontalSeparator
+  margin-top: 5
+  margin-bottom: 3
+  
 BotPanel < Panel
   margin-top: 2
   layout:
     type: verticalBox
     fit-children: true
-
-BotSeparator < HorizontalSeparator
-  margin-top: 5
-  margin-bottom: 3
     
 MiniWindow
   id: botWindow
@@ -33,7 +39,7 @@ MiniWindow
 
   MiniWindowContents   
     margin-left: 5
-    margin-right: 5
+    margin-right: 3
 
     ComboBox
       id: config
diff --git a/modules/game_bot/config.otui b/modules/game_bot/config.otui
index f2bccd6..5c7736a 100644
--- a/modules/game_bot/config.otui
+++ b/modules/game_bot/config.otui
@@ -55,7 +55,7 @@ MainWindow
     anchors.bottom: parent.bottom
     anchors.left: parent.left
     width: 150
-    @onClick: scheduleEvent(function() g_platform.openUrl("https://github.com/OTCv8/otclient_bot") end, 50)
+    @onClick: scheduleEvent(function() g_platform.openUrl("https://github.com/OTCv8/otclientv8_bot") end, 50)
 
   Button
     id: okButton
diff --git a/modules/game_bot/defaultconfig.lua b/modules/game_bot/defaultconfig.lua
index ae10d1b..b3e0806 100644
--- a/modules/game_bot/defaultconfig.lua
+++ b/modules/game_bot/defaultconfig.lua
@@ -1,10 +1,11 @@
 botDefaultConfig = {
   configs = {
-    {name = "Example", script = [[
---#Example config
+    {name = "Example", script = [=[
+--#Example
+info("Tested on 10.99")
 
 --#main
-local widget = setupUI(%[%[
+local widget = setupUI([[
 Panel
   id: redPanel
   background: red
@@ -16,7 +17,7 @@ Panel
     anchors.fill: parent
     text: custom ui, otml based
     text-align: center
-%]%])
+]])
 
 --#macros
 macro(5000, "macro send link", "f5", function()
@@ -25,7 +26,7 @@ macro(5000, "macro send link", "f5", function()
 end)
 
 macro(1000, "flag tiles", function()
-  tile:setText("Hello =)", "red")
+  player:getTile():setText("Hello =)", "red")
 end)
 
 macro(25, "auto healing", function()
@@ -85,8 +86,154 @@ HTTP.getJSON("https://api.ipify.org/?format=json", function(data, err)
     info("HTTP: My IP is: " .. tostring(data['ip']))
 end)
 

-]]},
-  {}, {}, {}, {}
+]=]},
+  {name = "UI & Healing", script = [=[
+-- UI & healing
+info("Tested on 10.99")
+
+--#main
+local healthPanel = setupUI([[
+Panel
+  id: healingPanel
+  height: 150
+  margin-top: 3
+  
+  Label
+    anchors.top: parent.top
+    anchors.left: parent.left
+    anchors.right: parent.right
+    text: Use item if
+    text-align: center
+  
+  BotItem
+    id: item1
+    anchors.left: parent.left
+    anchors.top: prev.bottom
+
+  Label
+    id: label1  
+    anchors.left: prev.right
+    anchors.right: parent.right
+    anchors.top: prev.top
+    margin: 0 5 0 5
+    text-align: center
+    
+  HorizontalScrollBar
+    id: scroll1
+    anchors.left: prev.left
+    anchors.right: prev.right
+    anchors.top: prev.bottom
+    margin-top: 5
+    minimum: 0
+    maximum: 100
+    step: 1
+    
+  BotItem
+    id: item2
+    anchors.left: parent.left
+    anchors.top: item1.bottom
+    margin-top: 3
+
+  Label
+    id: label2
+    anchors.left: prev.right
+    anchors.right: parent.right
+    anchors.top: prev.top
+    margin: 0 5 0 5
+    text-align: center
+    
+  HorizontalScrollBar
+    id: scroll2
+    anchors.left: label2.left
+    anchors.right: label2.horizontalCenter
+    anchors.top: label2.bottom
+    margin-top: 5
+    minimum: 0
+    maximum: 100
+    step: 1
+    
+  HorizontalScrollBar
+    id: scroll3
+    anchors.left: label2.horizontalCenter
+    anchors.right: label2.right
+    anchors.top: label2.bottom
+    margin-top: 5
+    minimum: 0
+    maximum: 100
+    step: 1
+    
+  Label
+    anchors.top: item2.bottom
+    anchors.left: parent.left
+    anchors.right: parent.right
+    margin-top: 3
+    text: Drag item to change it
+    text-align: center
+    
+  HorizontalSeparator
+    anchors.top: prev.bottom
+    anchors.left: parent.left
+    anchors.right: parent.right
+    margin-top: 3
+]])
+
+healthPanel.item1:setItemId(storage.healItem1 or 266)
+healthPanel.item1.onItemChange = function(widget, item)
+  storage.healItem1 = item:getId()
+  widget:setItemId(storage.healItem1)
+end
+
+healthPanel.item2:setItemId(storage.healItem2 or 268)
+healthPanel.item2.onItemChange = function(widget, item)
+  storage.healItem2 = item:getId()
+  widget:setItemId(storage.healItem2)
+end
+
+healthPanel.scroll1.onValueChange = function(scroll, value)
+  storage.healPercent1 = value
+  healthPanel.label1:setText("0% <= hp <= " .. storage.healPercent1 .. "%")
+end
+healthPanel.scroll1:setValue(storage.healPercent1 or 50)
+
+healthPanel.scroll2.onValueChange = function(scroll, value)
+  storage.healPercent2 = value
+  healthPanel.label2:setText("" .. storage.healPercent2 .. "% <= mana <= " .. storage.healPercent3 .. "%")
+end
+healthPanel.scroll3.onValueChange = function(scroll, value)
+  storage.healPercent3 = value
+  healthPanel.label2:setText("" .. storage.healPercent2 .. "% <= mana <= " .. storage.healPercent3 .. "%")
+end
+healthPanel.scroll2:setValue(storage.healPercent2 or 40)
+healthPanel.scroll3:setValue(storage.healPercent3 or 60)
+
+macro(25, function()
+  if not storage.healItem1 then
+    return
+  end
+  if healthPanel.scroll1:getValue() >= hppercent() then
+    useWith(storage.healItem1, player)      
+    delay(500)
+  end
+end)
+macro(25, function()
+  if not storage.healItem2 then
+    return
+  end
+  if storage.healPercent2 <= manapercent() and manapercent() <= storage.healPercent3 then
+    useWith(storage.healItem2, player)      
+    delay(500)
+  end  
+end)
+
+--#macros
+
+--#hotkeys
+
+--#callbacks
+
+--#other
+]=]},
+  {}, {}, {}
   },
   enabled = false,
   selectedConfig = 1
diff --git a/modules/game_bot/functions.lua b/modules/game_bot/functions.lua
index 03851e4..eb39033 100644
--- a/modules/game_bot/functions.lua
+++ b/modules/game_bot/functions.lua
@@ -8,7 +8,7 @@ function setupFunctions(context)
   context.hp = function() return context.player:getHealth() end
   context.mana = function() return context.player:getMana() end
   context.hppercent = function() return context.player:getHealthPercent() end
-  context.manapercent = function() return context.player:getManaPercent() end
+  context.manapercent = function() if context.player:getMaxMana() <= 1 then return 100 else return math.floor(context.player:getMana() * 100 / context.player:getMaxMana()) end end
   context.maxhp = function() return context.player:getMaxHealth() end
   context.maxmana = function() return context.player:getMaxMana() end
   context.hpmax = function() return context.player:getMaxHealth() end
diff --git a/modules/game_bugreport/bugreport.lua b/modules/game_bugreport/bugreport.lua
index 5308d7f..51d631a 100644
--- a/modules/game_bugreport/bugreport.lua
+++ b/modules/game_bugreport/bugreport.lua
@@ -12,11 +12,11 @@ function init()
 
   bugTextEdit = bugReportWindow:getChildById('bugTextEdit')
 
-  g_keyboard.bindKeyDown(HOTKEY, show)
+  g_keyboard.bindKeyDown(HOTKEY, show, modules.game_interface.getRootPanel())
 end
 
 function terminate()
-  g_keyboard.unbindKeyDown(HOTKEY)
+  g_keyboard.unbindKeyDown(HOTKEY, modules.game_interface.getRootPanel())
   bugReportWindow:destroy()
 end
 
diff --git a/modules/game_console/console.lua b/modules/game_console/console.lua
index aad711a..8dc36aa 100644
--- a/modules/game_console/console.lua
+++ b/modules/game_console/console.lua
@@ -151,9 +151,10 @@ function init()
   consoleTabBar.onTabChange = onTabChange
 
   -- tibia like hotkeys
-  g_keyboard.bindKeyDown('Ctrl+O', g_game.requestChannels)
-  g_keyboard.bindKeyDown('Ctrl+E', removeCurrentTab)
-  g_keyboard.bindKeyDown('Ctrl+H', openHelp)
+  local gameRootPanel = modules.game_interface.getRootPanel()
+  g_keyboard.bindKeyDown('Ctrl+O', g_game.requestChannels, gameRootPanel)
+  g_keyboard.bindKeyDown('Ctrl+E', removeCurrentTab, gameRootPanel)
+  g_keyboard.bindKeyDown('Ctrl+H', openHelp, gameRootPanel)
 
   consoleToggleChat = consolePanel:getChildById('toggleChat')
   load()
@@ -204,18 +205,19 @@ function enableChat(temporarily)
   consoleTextEdit:setText("")
   consoleTextEdit:focus()
 
-  g_keyboard.unbindKeyDown("Space")
-  g_keyboard.unbindKeyDown("Enter")
+  local gameRootPanel = modules.game_interface.getRootPanel()
+  g_keyboard.unbindKeyDown("Space", gameRootPanel)
+  g_keyboard.unbindKeyDown("Enter", gameRootPanel)
   
   if temporarily then
     local quickFunc = function()
       if not g_game.isOnline() then return end
-      g_keyboard.unbindKeyDown("Enter")
-      g_keyboard.unbindKeyDown("Escape")
+      g_keyboard.unbindKeyDown("Enter", gameRootPanel)
+      g_keyboard.unbindKeyDown("Escape", gameRootPanel)
       disableChat(temporarily)
     end
-    g_keyboard.bindKeyDown("Enter", quickFunc)
-    g_keyboard.bindKeyDown("Escape", quickFunc)  
+    g_keyboard.bindKeyDown("Enter", quickFunc, gameRootPanel)
+    g_keyboard.bindKeyDown("Escape", quickFunc, gameRootPanel)  
   end
 
   modules.game_walking.disableWSAD()
@@ -241,8 +243,10 @@ function disableChat()
     end
     enableChat(true)
   end
-  g_keyboard.bindKeyDown("Space", quickFunc)
-  g_keyboard.bindKeyDown("Enter", quickFunc)
+  
+  local gameRootPanel = modules.game_interface.getRootPanel()
+  g_keyboard.bindKeyDown("Space", quickFunc, gameRootPanel)
+  g_keyboard.bindKeyDown("Enter", quickFunc, gameRootPanel)
 
   modules.game_walking.enableWSAD()
 
@@ -273,9 +277,10 @@ function terminate()
 
   if g_game.isOnline() then clear() end
 
-  g_keyboard.unbindKeyDown('Ctrl+O')
-  g_keyboard.unbindKeyDown('Ctrl+E')
-  g_keyboard.unbindKeyDown('Ctrl+H')
+  local gameRootPanel = modules.game_interface.getRootPanel()
+  g_keyboard.unbindKeyDown('Ctrl+O', gameRootPanel)
+  g_keyboard.unbindKeyDown('Ctrl+E', gameRootPanel)
+  g_keyboard.unbindKeyDown('Ctrl+H', gameRootPanel)
 
   saveCommunicationSettings()
 
@@ -1495,7 +1500,8 @@ function online()
   serverTab = addTab(tr('Server Log'), false)
 
   if g_game.getClientVersion() < 862 then
-    g_keyboard.bindKeyDown('Ctrl+R', openPlayerReportRuleViolationWindow)
+    local gameRootPanel = modules.game_interface.getRootPanel()
+    g_keyboard.bindKeyDown('Ctrl+R', openPlayerReportRuleViolationWindow, gameRootPanel)
   end
   -- open last channels
   local lastChannelsOpen = g_settings.getNode('lastChannelsOpen')
@@ -1519,7 +1525,8 @@ end
 
 function offline()
   if g_game.getClientVersion() < 862 then
-    g_keyboard.unbindKeyDown('Ctrl+R')
+    local gameRootPanel = modules.game_interface.getRootPanel()
+    g_keyboard.unbindKeyDown('Ctrl+R', gameRootPanel)
   end
   clear()
 end
diff --git a/modules/game_hotkeys/hotkeys_manager.lua b/modules/game_hotkeys/hotkeys_manager.lua
index 262703e..e1b653a 100644
--- a/modules/game_hotkeys/hotkeys_manager.lua
+++ b/modules/game_hotkeys/hotkeys_manager.lua
@@ -36,7 +36,6 @@ useRadioGroup = nil
 currentHotkeys = nil
 boundCombosCallback = {}
 hotkeysList = {}
-lastHotkeyTime = g_clock.millis()
 hotkeyConfigs = {}
 currentConfig = 1
 configValueChanged = false
@@ -182,8 +181,9 @@ function load(forceDefaults)
 end
 
 function unload()
+  local gameRootPanel = modules.game_interface.getRootPanel()
   for keyCombo,callback in pairs(boundCombosCallback) do
-    g_keyboard.unbindKeyPress(keyCombo, callback)
+    g_keyboard.unbindKeyPress(keyCombo, callback, gameRootPanel)
   end
   boundCombosCallback = {}
   currentHotkeys:destroyChildren()
@@ -373,21 +373,21 @@ function addKeyCombo(keyCombo, keySettings, focus)
 
     updateHotkeyLabel(hotkeyLabel)
 
-    
+    local gameRootPanel = modules.game_interface.getRootPanel()
     if keyCombo:lower():find("ctrl") then
       if boundCombosCallback[keyCombo] then
-        g_keyboard.unbindKeyPress(keyCombo, boundCombosCallback[keyCombo])      
+        g_keyboard.unbindKeyPress(keyCombo, boundCombosCallback[keyCombo], gameRootPanel)      
       end
     end
 
     boundCombosCallback[keyCombo] = function() prepareKeyCombo(keyCombo) end
-    g_keyboard.bindKeyPress(keyCombo, boundCombosCallback[keyCombo])
+    g_keyboard.bindKeyPress(keyCombo, boundCombosCallback[keyCombo], gameRootPanel)
         
     if not keyCombo:lower():find("ctrl") then
       local keyComboCtrl = "Ctrl+" .. keyCombo
       if not boundCombosCallback[keyComboCtrl] then
         boundCombosCallback[keyComboCtrl] = function() prepareKeyCombo(keyComboCtrl) end
-        g_keyboard.bindKeyPress(keyComboCtrl, boundCombosCallback[keyComboCtrl])      
+        g_keyboard.bindKeyPress(keyComboCtrl, boundCombosCallback[keyComboCtrl], gameRootPanel)   
       end
     end
   end
@@ -400,7 +400,7 @@ function addKeyCombo(keyCombo, keySettings, focus)
   configValueChanged = true
 end
 
-function prepareKeyCombo(keyCombo)
+function prepareKeyCombo(keyCombo, repeated)
     local hotKey = hotkeyList[keyCombo]
     if keyCombo:lower():find("ctrl") or not hotKey or (hotKey.itemId == nil and (not hotKey.value or #hotKey.value == 0)) then
       keyCombo = keyCombo:gsub("Ctrl%+", "")
@@ -432,7 +432,11 @@ function doKeyCombo(keyCombo)
   local hotKey = hotkeyList[keyCombo]
   if not hotKey then return end
 
-  if hotKey.lastHotkeyTime ~= nil and g_clock.millis() - hotKey.lastHotkeyTime < 100 then
+  local hotkeyDelay = 100  
+  if hotKey.hotkeyDelayTo == nil or g_clock.millis() > hotKey.hotkeyDelayTo + hotkeyDelay then
+    hotkeyDelay = 200 -- for first use
+  end
+  if hotKey.hotkeyDelayTo ~= nil and g_clock.millis() < hotKey.hotkeyDelayTo then
     return
   end
 	
@@ -443,7 +447,7 @@ function doKeyCombo(keyCombo)
     else
       modules.game_console.setTextEditText(hotKey.value)
     end
-    hotKey.lastHotkeyTime = g_clock.millis()
+    hotKey.hotkeyDelayTo = g_clock.millis() + hotkeyDelay
   elseif hotKey.useType == HOTKEY_MANAGER_USE then
     if g_game.getClientVersion() < 740 then
       local item = g_game.findPlayerItem(hotKey.itemId, hotKey.subType or -1)
@@ -453,7 +457,7 @@ function doKeyCombo(keyCombo)
     else
       g_game.useInventoryItem(hotKey.itemId)
     end
-    hotKey.lastHotkeyTime = g_clock.millis()
+    hotKey.hotkeyDelayTo = g_clock.millis() + hotkeyDelay
   elseif hotKey.useType == HOTKEY_MANAGER_USEONSELF then
     if g_game.getClientVersion() < 740 then
       local item = g_game.findPlayerItem(hotKey.itemId, hotKey.subType or -1)
@@ -463,7 +467,7 @@ function doKeyCombo(keyCombo)
     else
       g_game.useInventoryItemWith(hotKey.itemId, g_game.getLocalPlayer(), hotKey.subType or -1)
     end
-    hotKey.lastHotkeyTime = g_clock.millis()
+    hotKey.hotkeyDelayTo = g_clock.millis() + hotkeyDelay
   elseif hotKey.useType == HOTKEY_MANAGER_USEONTARGET then
     local attackingCreature = g_game.getAttackingCreature()
     if not attackingCreature then
@@ -487,7 +491,7 @@ function doKeyCombo(keyCombo)
     else
       g_game.useInventoryItemWith(hotKey.itemId, attackingCreature, hotKey.subType or -1)
     end
-    hotKey.lastHotkeyTime = g_clock.millis()
+    hotKey.hotkeyDelayTo = g_clock.millis() + hotkeyDelay
   elseif hotKey.useType == HOTKEY_MANAGER_USEWITH then
     local item = Item.create(hotKey.itemId)
     if g_game.getClientVersion() < 740 then
@@ -596,7 +600,8 @@ end
 
 function removeHotkey()
   if currentHotkeyLabel == nil then return end
-  g_keyboard.unbindKeyPress(currentHotkeyLabel.keyCombo, boundCombosCallback[currentHotkeyLabel.keyCombo])
+  local gameRootPanel = modules.game_interface.getRootPanel()
+  g_keyboard.unbindKeyPress(currentHotkeyLabel.keyCombo, boundCombosCallback[currentHotkeyLabel.keyCombo], gameRootPanel)
   boundCombosCallback[currentHotkeyLabel.keyCombo] = nil
   currentHotkeyLabel:destroy()
   currentHotkeyLabel = nil
diff --git a/modules/game_interface/widgets/uiitem.lua b/modules/game_interface/widgets/uiitem.lua
index 4e8941c..c9f3db8 100644
--- a/modules/game_interface/widgets/uiitem.lua
+++ b/modules/game_interface/widgets/uiitem.lua
@@ -24,6 +24,13 @@ function UIItem:onDrop(widget, mousePos, forced)
 
   local item = widget.currentDragThing
   if not item or not item:isItem() then return false end
+  
+  if self.selectable then
+    if self.onItemChange then
+      self.onItemChange(self, item)
+    end
+    return
+  end
 
   local toPos = self.position
 
@@ -93,7 +100,7 @@ function UIItem:onMouseRelease(mousePosition, mouseButton)
 end
 
 function UIItem:canAcceptDrop(widget, mousePos)
-  if self:isVirtual() or not self:isDraggable() then return false end
+  if not self.selectable and (self:isVirtual() or not self:isDraggable()) then return false end
   if not widget or not widget.currentDragThing then return false end
 
   local children = rootWidget:recursiveGetChildrenByPos(mousePos)