diff --git a/api/updater.php b/api/updater.php index 0d8c8dd..e2da2e2 100644 --- a/api/updater.php +++ b/api/updater.php @@ -1,7 +1,8 @@ platform; $version = $data->version; // currently not used @@ -19,7 +20,6 @@ if($platform == "WIN32-WGL") { // opengl } $data_dir = "/var/www/otclient/files"; -$things_dir = "/data/things"; // files from that dir won't be downloaded automaticly, you can set it to null to download everything automaticly (useful if you have only 1 version of data/sprites) $files_url = "http://otclient.ovh/files"; $update_checksum_interval = 60; // caling updater 100x/s would lag disc, we need to cache it $main_files_and_dirs = array("data", "modules", "layouts", "init.lua"); // used to ignore other files/dirs in data_dir @@ -48,23 +48,18 @@ function updateChecksums() { global $binary_path; global $checksums_file; global $data; - global $things_dir; $ret = array(); $data_dir_realpath = realpath($data_dir); $files = getDirFiles($data_dir); foreach($files as $file) { $relative_path = str_replace($data_dir_realpath, "", $file); - $ps = explode("/", $relative_path); + $ps = explode(DIRECTORY_SEPARATOR, $relative_path); if($relative_path == $binary_path || (count($ps) >= 2 && in_array($ps[1], $main_files_and_dirs))) $ret[$relative_path] = md5_file($file); } foreach($ret as $file => $checksum) { - if($things_dir != null && !empty($things_dir) && strpos($file, $things_dir) === 0) { - $data["things"][$file] = $checksum; - } else { - $data["files"][$file] = $checksum; - } + $data["files"][$file] = $checksum; } $ret = json_encode($data); if(file_put_contents($checksums_file, $ret) === FALSE) { @@ -84,7 +79,8 @@ if (function_exists('sem_get')) { sem_acquire($semaphore); } -$ft = filemtime($checksums_file); + +$ft = file_exists($checksums_file) ? filemtime($checksums_file) : false; if($ft === false || $ft + $update_checksum_interval < time()) { echo updateChecksums(); } else { diff --git a/data/things/.gitignore b/data/things/.gitignore new file mode 100644 index 0000000..c96a04f --- /dev/null +++ b/data/things/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore \ No newline at end of file diff --git a/init.lua b/init.lua index 658b487..fcaebf6 100644 --- a/init.lua +++ b/init.lua @@ -23,6 +23,8 @@ Servers = { USE_NEW_ENERGAME = false -- not done yet ALLOW_CUSTOM_SERVERS = true -- if true it shows option ANOTHER on server list + +g_app.setName("OTCv8") -- CONFIG END -- print first terminal message diff --git a/modules/client/client.lua b/modules/client/client.lua index 4b3afbc..a051bb0 100644 --- a/modules/client/client.lua +++ b/modules/client/client.lua @@ -54,6 +54,8 @@ end function init() connect(g_app, { onRun = startup, onExit = exit }) + connect(g_game, { onGameStart = onGameStart, + onGameEnd = onGameEnd }) g_window.setMinimumSize({ width = 800, height = 600 }) if g_sounds ~= nil then @@ -102,6 +104,8 @@ end function terminate() disconnect(g_app, { onRun = startup, onExit = exit }) + disconnect(g_game, { onGameStart = onGameStart, + onGameEnd = onGameEnd }) -- save window configs g_settings.set('window-size', g_window.getUnmaximizedSize()) g_settings.set('window-pos', g_window.getUnmaximizedPos()) @@ -111,3 +115,13 @@ end function exit() g_logger.info("Exiting application..") end + +function onGameStart() + local player = g_game.getLocalPlayer() + if not player then return end + g_window.setTitle(g_app.getName() .. " - " .. player:getName()) +end + +function onGameEnd() + g_window.setTitle(g_app.getName()) +end diff --git a/modules/client_entergame/entergame.otui b/modules/client_entergame/entergame.otui index a425aa9..50bb7e2 100644 --- a/modules/client_entergame/entergame.otui +++ b/modules/client_entergame/entergame.otui @@ -112,7 +112,7 @@ EnterGameWindow MenuLabel id: serverLabel - !text: tr('IP:PORT or url') + !text: tr('IP:PORT') anchors.left: prev.left anchors.top: prev.bottom margin-top: 8 diff --git a/modules/client_stats/stats.lua b/modules/client_stats/stats.lua index 7040206..5587e2c 100644 --- a/modules/client_stats/stats.lua +++ b/modules/client_stats/stats.lua @@ -154,7 +154,7 @@ function sendStats() g_stats.clearSlow(i - 1) end data.widgets = g_stats.getWidgetsInfo(10, false) - data = json.encode(data) + data = json.encode(data, 1) if Services.stats ~= nil and Services.stats:len() > 3 then g_http.post(Services.stats, data) end diff --git a/modules/client_terminal/terminal.lua b/modules/client_terminal/terminal.lua index 46994c6..693b62f 100644 --- a/modules/client_terminal/terminal.lua +++ b/modules/client_terminal/terminal.lua @@ -337,7 +337,7 @@ function executeCommand(command) -- detect and convert commands with simple syntax local realCommand if string.sub(command, 1, 1) == '=' then - realCommand = 'print(' .. string.sub(command,2) .. ')' + realCommand = 'print(tostring(' .. string.sub(command,2) .. '))' else realCommand = command end @@ -363,6 +363,8 @@ function executeCommand(command) addLine('ERROR: incorrect lua syntax: ' .. err:sub(5), 'red') return end + + commandEnv['player'] = g_game.getLocalPlayer() -- setup func env to commandEnv setfenv(func, commandEnv) diff --git a/modules/corelib/json.lua b/modules/corelib/json.lua index 21f6f89..40cd68d 100644 --- a/modules/corelib/json.lua +++ b/modules/corelib/json.lua @@ -1,7 +1,7 @@ -- -- json.lua -- --- Copyright (c) 2018 rxi +-- Copyright (c) 2019 rxi -- -- Permission is hereby granted, free of charge, to any person obtaining a copy of -- this software and associated documentation files (the "Software"), to deal in @@ -46,26 +46,40 @@ for k, v in pairs(escape_char_map) do end +local function make_indent(state) + return string.rep(" ", state.currentIndentLevel * state.indent) +end + + local function escape_char(c) return escape_char_map[c] or string.format("\\u%04x", c:byte()) end -local function encode_nil(val) +local function encode_nil() return "null" end -local function encode_table(val, stack) +local function encode_table(val, state) local res = {} - stack = stack or {} + local stack = state.stack + local pretty = state.indent > 0 + + local close_indent = make_indent(state) + local comma = pretty and ",\n" or "," + local colon = pretty and ": " or ":" + local open_brace = pretty and "{\n" or "{" + local close_brace = pretty and ("\n" .. close_indent .. "}") or "}" + local open_bracket = pretty and "[\n" or "[" + local close_bracket = pretty and ("\n" .. close_indent .. "]") or "]" -- Circular reference? if stack[val] then error("circular reference") end stack[val] = true - if val[1] ~= nil or next(val) == nil then + if rawget(val, 1) ~= nil or next(val) == nil then -- Treat as array -- check keys are valid and it is not sparse local n = 0 for k in pairs(val) do @@ -78,11 +92,13 @@ local function encode_table(val, stack) error("invalid table: sparse array") end -- Encode - for i, v in ipairs(val) do - table.insert(res, encode(v, stack)) + for _, v in ipairs(val) do + state.currentIndentLevel = state.currentIndentLevel + 1 + table.insert(res, make_indent(state) .. encode(v, state)) + state.currentIndentLevel = state.currentIndentLevel - 1 end stack[val] = nil - return "[" .. table.concat(res, ",") .. "]" + return open_bracket .. table.concat(res, comma) .. close_bracket else -- Treat as an object @@ -90,10 +106,12 @@ local function encode_table(val, stack) if type(k) ~= "string" then error("invalid table: mixed or invalid key types") end - table.insert(res, encode(k, stack) .. ":" .. encode(v, stack)) + state.currentIndentLevel = state.currentIndentLevel + 1 + table.insert(res, make_indent(state) .. encode(k, state) .. colon .. encode(v, state)) + state.currentIndentLevel = state.currentIndentLevel - 1 end stack[val] = nil - return "{" .. table.concat(res, ",") .. "}" + return open_brace .. table.concat(res, comma) .. close_brace end end @@ -121,18 +139,22 @@ local type_func_map = { } -encode = function(val, stack) +encode = function(val, state) local t = type(val) local f = type_func_map[t] if f then - return f(val, stack) + return f(val, state) end error("unexpected type '" .. t .. "'") end - -function json.encode(val) - return ( encode(val) ) +function json.encode(val, indent) + local state = { + indent = indent or 0, + currentIndentLevel = 0, + stack = {} + } + return encode(val, state) end diff --git a/modules/corelib/ui/uitabbar.lua b/modules/corelib/ui/uitabbar.lua index 951692f..9840c53 100644 --- a/modules/corelib/ui/uitabbar.lua +++ b/modules/corelib/ui/uitabbar.lua @@ -155,3 +155,9 @@ end function UITabBar:getTabsPanel() return table.collect(self.tabs, function(_,tab) return tab.tabPanel end) end + +function UITabBar:clearTabs() + while #self.tabs > 0 do + self:removeTab(self.tabs[#self.tabs]) + end +end diff --git a/modules/game_actionbar/actionbar.lua b/modules/game_actionbar/actionbar.lua index c1692d7..c1b4e4c 100644 --- a/modules/game_actionbar/actionbar.lua +++ b/modules/game_actionbar/actionbar.lua @@ -34,8 +34,10 @@ function init() connect(g_game, { onGameStart = online, - onGameEnd = offline - }) + onGameEnd = offline, + onSpellGroupCooldown = onSpellGroupCooldown, + onSpellCooldown = onSpellCooldown + }) if g_game.isOnline() then online() @@ -45,14 +47,16 @@ end function terminate() disconnect(g_game, { onGameStart = online, - onGameEnd = offline + onGameEnd = offline, + onSpellGroupCooldown = onSpellGroupCooldown, + onSpellCooldown = onSpellCooldown }) - saveConfig() - - -- remove hotkeys - offline() - + -- remove hotkeys, also saves config + if actionPanel1.tabBar:getChildCount() > 0 and actionPanel2.tabBar:getChildCount() > 0 then + offline() + end + actionPanel1:destroy() actionPanel2:destroy() end @@ -90,16 +94,27 @@ function offline() hotkeyAssignWindow:destroy() hotkeyAssignWindow = nil end - saveConfig() - + + local gameRootPanel = modules.game_interface.getRootPanel() for index, panel in ipairs({actionPanel1, actionPanel2}) do + local config = {} for i, child in ipairs(panel.tabBar:getChildren()) do - local gameRootPanel = modules.game_interface.getRootPanel() - if child.hotkey then - g_keyboard.unbindKeyPress(child.hotkey, child.callback, gameRootPanel) + if child.config then + table.insert(config, child.config) + if type(child.config.hotkey) == 'string' and child.config.hotkey:len() > 0 then + g_keyboard.unbindKeyPress(child.config.hotkey, child.callback, gameRootPanel) + end + else + table.insert(config, {}) + end + if child.cooldownEvent then + removeEvent(child.cooldownEvent) end end + actionConfig:setNode('actions_' .. index, config) + panel.tabBar:destroyChildren() end + actionConfig:save() end function setupActionPanel(index, panel) @@ -108,10 +123,11 @@ function setupActionPanel(index, panel) for i, buttonConfig in pairs(rawConfig) do -- sorting, because key in rawConfig is string config[tonumber(i)] = buttonConfig end - panel.tabBar:destroyChildren() + for i=1,actionButtonsInPanel do local action = g_ui.createWidget('ActionButton', panel.tabBar) - setupAction(index, action, config[i]) + action.config = config[i] or {} + setupAction(action) end panel.nextButton.onClick = function() @@ -122,28 +138,13 @@ function setupActionPanel(index, panel) end end -function saveConfig() - for index, panel in ipairs({actionPanel1, actionPanel2}) do - local config = {} - for i, child in ipairs(panel.tabBar:getChildren()) do - table.insert(config, { - text = child.text:getText(), - item = child.item:getItemId(), - count = child.item:getItemCount(), - action = child.actionType, - hotkey = child.hotkey - }) - end - actionConfig:setNode('actions_' .. index, config) - end - actionConfig:save() -end - -function setupAction(index, action, config) +function setupAction(action) + local config = action.config action.item:setShowCount(false) action.onMouseRelease = actionOnMouseRelease action.callback = function(k, c, ticks) executeAction(action, ticks) end - + action.item.onItemChange = nil -- disable callbacks for setup + if config then if type(config.text) == 'number' then config.text = tostring(config.text) @@ -151,43 +152,204 @@ function setupAction(index, action, config) if type(config.hotkey) == 'number' then config.hotkey = tostring(config.hotkey) end - action.hotkey = config.hotkey - if type(action.hotkey) == 'string' and action.hotkey:len() > 0 then + if type(config.hotkey) == 'string' and config.hotkey:len() > 0 then local gameRootPanel = modules.game_interface.getRootPanel() - g_keyboard.bindKeyPress(action.hotkey, action.callback, gameRootPanel) + g_keyboard.bindKeyPress(config.hotkey, action.callback, gameRootPanel) + action.hotkeyLabel:setText(config.hotkey) + else + action.hotkeyLabel:setText("") end - action.hotkeyLabel:setText(action.hotkey or "") - action.text:setText(config.text) - if action.text:getText():len() > 0 then + + action.text:setImageSource("") + action.cooldownTill = 0 + action.cooldownStart = 0 + if type(config.text) == 'string' and config.text:len() > 0 then + action.text:setText(config.text) action:setBorderColor(ActionColors.text) + action.item:setOn(true) -- removes background + if Spells then + local spell, profile = Spells.getSpellByWords(config.text:lower()) + action.spell = spell + if action.spell and action.spell.icon and profile then + action.text:setImageSource(SpelllistSettings[profile].iconFile) + action.text:setImageClip(Spells.getImageClip(SpellIcons[action.spell.icon][1], profile)) + action.text:setText("") + end + end + else + action.text:setText("") + action.spell = nil + if type(config.item) == 'number' and config.item > 100 then + action.item:setOn(true) + action.item:setItemId(config.item) + action.item:setItemCount(config.count or 1) + setupActionType(action, config.actionType) + else + action.item:setItemId(0) + action.item:setOn(false) + action:setBorderColor(ActionColors.empty) + end end - if config.item > 0 then - setupActionType(action, config.action) - end - action.item:setOn(config.item > 0) - action.item:setItemId(config.item) - action.item:setItemCount(config.count) end action.item.onItemChange = actionOnItemChange end function setupActionType(action, actionType) - action.actionType = actionType - if action.actionType == ActionTypes.USE then + local item = action.item:getItem() + if action.item:getItem():isMultiUse() then + if not actionType or actionType <= ActionTypes.USE then + actionType = ActionTypes.USE_WITH + end + elseif g_game.getClientVersion() >= 910 then + if actionType ~= ActionTypes.USE and actionType ~= ActionTypes.EQUIP then + actionType = ActionTypes.USE + end + else + actionType = ActionTypes.USE + end + + action.config.actionType = actionType + if action.config.actionType == ActionTypes.USE then action:setBorderColor(ActionColors.itemUse) - elseif action.actionType == ActionTypes.USE_SELF then + elseif action.config.actionType == ActionTypes.USE_SELF then action:setBorderColor(ActionColors.itemUseSelf) - elseif action.actionType == ActionTypes.USE_TARGET then + elseif action.config.actionType == ActionTypes.USE_TARGET then action:setBorderColor(ActionColors.itemUseTarget) - elseif action.actionType == ActionTypes.USE_WITH then + elseif action.config.actionType == ActionTypes.USE_WITH then action:setBorderColor(ActionColors.itemUseWith) - elseif action.actionType == ActionTypes.EQUIP then + elseif action.config.actionType == ActionTypes.EQUIP then action:setBorderColor(ActionColors.itemEquip) end end +function updateAction(action, newConfig) + local config = action.config + if newConfig.hotkey and type(config.hotkey) == 'string' and config.hotkey:len() > 0 then + local gameRootPanel = modules.game_interface.getRootPanel() + g_keyboard.unbindKeyPress(config.hotkey, action.callback, gameRootPanel) + end + for key, val in pairs(newConfig) do + action.config[key] = val + end + setupAction(action) +end + +function actionOnMouseRelease(action, mousePosition, mouseButton) + if mouseButton == MouseRightButton then + local menu = g_ui.createWidget('PopupMenu') + menu:setGameMenu(true) + if action.item:getItemId() > 0 then + if action.item:getItem():isMultiUse() then + menu:addOption(tr('Use on yourself'), function() return setupActionType(action, ActionTypes.USE_SELF) end) + menu:addOption(tr('Use on target'), function() return setupActionType(action, ActionTypes.USE_TARGET) end) + menu:addOption(tr('With crosshair'), function() return setupActionType(action, ActionTypes.USE_WITH) end) + end + if g_game.getClientVersion() >= 910 then + if not action.item:getItem():isMultiUse() then + menu:addOption(tr('Use'), function() return setupActionType(action, ActionTypes.USE) end) + end + menu:addOption(tr('Equip'), function() return setupActionType(action, ActionTypes.EQUIP) end) + end + else + menu:addOption(tr('Select item'), function() return modules.game_itemselector.show(action.item) end) + end + menu:addSeparator() + menu:addOption(tr('Set text'), function() + modules.game_textedit.singlelineEditor(action.config.text or "", function(newText) + updateAction(action, {text=newText, item=0}) + end) + end) + menu:addOption(tr('Set hotkey'), function() + if hotkeyAssignWindow then + hotkeyAssignWindow:destroy() + end + local assignWindow = g_ui.createWidget('ActionAssignWindow', rootWidget) + assignWindow:grabKeyboard() + assignWindow.comboPreview.keyCombo = '' + assignWindow.onKeyDown = function(assignWindow, keyCode, keyboardModifiers) + local keyCombo = determineKeyComboDesc(keyCode, keyboardModifiers) + assignWindow.comboPreview:setText(tr('Current action hotkey: %s', keyCombo)) + assignWindow.comboPreview.keyCombo = keyCombo + assignWindow.comboPreview:resizeToText() + return true + end + assignWindow.onDestroy = function(widget) + if widget == hotkeyAssignWindow then + hotkeyAssignWindow = nil + end + end + assignWindow.addButton.onClick = function() + updateAction(action, {hotkey=tostring(assignWindow.comboPreview.keyCombo)}) + assignWindow:destroy() + end + hotkeyAssignWindow = assignWindow + end) + menu:addSeparator() + menu:addOption(tr('Clear'), function() + updateAction(action, {hotkey="", text="", item=0, count=1}) + end) + menu:display(mousePosition) + return true + elseif mouseButton == MouseLeftButton then + action.callback() + return true + end + return false +end + +function actionOnItemChange(widget) + updateAction(widget:getParent(), {text="", item=widget:getItemId(), count=widget:getItemCount()}) +end + +function onSpellCooldown(iconId, duration) + for index, panel in ipairs({actionPanel1, actionPanel2}) do + for i, child in ipairs(panel.tabBar:getChildren()) do + if child.spell and child.spell.id == iconId then + startCooldown(child, duration) + end + end + end +end + +function onSpellGroupCooldown(groupId, duration) + for index, panel in ipairs({actionPanel1, actionPanel2}) do + for i, child in ipairs(panel.tabBar:getChildren()) do + if child.spell and child.spell.group then + for group, duration in pairs(child.spell.group) do + if groupId == group then + startCooldown(child, duration) + end + end + end + end + end +end + +function startCooldown(action, duration) + if type(action.cooldownTill) == 'number' and action.cooldownTill > g_clock.millis() + duration then + return -- already has cooldown with greater duration + end + action.cooldownStart = g_clock.millis() + action.cooldownTill = g_clock.millis() + duration + updateCooldown(action) +end + +function updateCooldown(action) + if not action or not action.cooldownTill then return end + local timeleft = action.cooldownTill - g_clock.millis() + if timeleft <= 30 then + action.cooldown:setPercent(100) + action.cooldownEvent = nil + return + end + local duration = action.cooldownTill - action.cooldownStart + action.cooldown:setPercent(100 - math.floor(100 * timeleft / duration)) + action.cooldownEvent = scheduleEvent(function() updateCooldown(action) end, 30) +end + function executeAction(action, ticks) + if not action.config then return end if type(ticks) ~= 'number' then ticks = 0 end local actionDelay = 100 @@ -196,12 +358,14 @@ function executeAction(action, ticks) elseif action.actionDelayTo ~= nil and g_clock.millis() < action.actionDelayTo then return end + + local actionType = action.config.actionType - if action.text:getText():len() > 0 then - modules.game_console.sendMessage(action.text:getText()) + if type(action.config.text) == 'string' and action.config.text:len() > 0 then + modules.game_console.sendMessage(action.config.text) action.actionDelayTo = g_clock.millis() + actionDelay elseif action.item:getItemId() > 0 then - if action.actionType == ActionTypes.USE then + if actionType == ActionTypes.USE then if g_game.getClientVersion() < 740 then local item = g_game.findPlayerItem(action.item:getItemId(), hotKey.subType or -1) if item then @@ -211,7 +375,7 @@ function executeAction(action, ticks) g_game.useInventoryItem(action.item:getItemId()) end action.actionDelayTo = g_clock.millis() + actionDelay - elseif action.actionType == ActionTypes.USE_SELF then + elseif actionType == ActionTypes.USE_SELF then if g_game.getClientVersion() < 740 then local item = g_game.findPlayerItem(action.item:getItemId(), hotKey.subType or -1) if item then @@ -221,7 +385,7 @@ function executeAction(action, ticks) g_game.useInventoryItemWith(action.item:getItemId(), g_game.getLocalPlayer(), action.item:getItemSubType() or -1) end action.actionDelayTo = g_clock.millis() + actionDelay - elseif action.actionType == ActionTypes.USE_TARGET then + elseif actionType == ActionTypes.USE_TARGET then local attackingCreature = g_game.getAttackingCreature() if not attackingCreature then local item = Item.create(action.item:getItemId()) @@ -245,7 +409,7 @@ function executeAction(action, ticks) g_game.useInventoryItemWith(action.item:getItemId(), attackingCreature, action.item:getItemSubType() or -1) end action.actionDelayTo = g_clock.millis() + actionDelay - elseif action.actionType == ActionTypes.USE_WITH then + elseif actionType == ActionTypes.USE_WITH then local item = Item.create(action.item:getItemId()) if g_game.getClientVersion() < 740 then local tmpItem = g_game.findPlayerItem(action.item:getItemId(), action.item:getItemSubType() or -1) @@ -253,7 +417,7 @@ function executeAction(action, ticks) item = tmpItem end modules.game_interface.startUseWith(item, action.item:getItemSubType() or - 1) - elseif action.actionType == ActionTypes.EQUIP then + elseif actionType == ActionTypes.EQUIP then if g_game.getClientVersion() >= 910 then local item = Item.create(action.item:getItemId()) g_game.equipItem(item) @@ -261,106 +425,4 @@ function executeAction(action, ticks) end end end -end - -function actionOnMouseRelease(action, mousePosition, mouseButton) - if mouseButton == MouseRightButton then - local menu = g_ui.createWidget('PopupMenu') - menu:setGameMenu(true) - if action.item:getItemId() > 0 then - if action.item:getItem():isMultiUse() then - menu:addOption(tr('Use on yourself'), function() return setupActionType(action, ActionTypes.USE_SELF) end) - menu:addOption(tr('Use on target'), function() return setupActionType(action, ActionTypes.USE_TARGET) end) - menu:addOption(tr('With crosshair'), function() return setupActionType(action, ActionTypes.USE_WITH) end) - end - if g_game.getClientVersion() >= 910 then - if not action.item:getItem():isMultiUse() then - menu:addOption(tr('Use'), function() return setupActionType(action, ActionTypes.USE) end) - end - menu:addOption(tr('Equip'), function() return setupActionType(action, ActionTypes.EQUIP) end) - end - end - menu:addSeparator() - menu:addOption(tr('Set text'), function() - modules.game_textedit.singlelineEditor(action.text:getText(), function(newText) - action.item:setOn(false) - action.item:setItemId(0) - action.text:setText(newText) - if action.text:getText():len() > 0 then - action:setBorderColor(ActionColors.text) - end - end) - end) - menu:addOption(tr('Set hotkey'), function() - if hotkeyAssignWindow then - hotkeyAssignWindow:destroy() - end - local assignWindow = g_ui.createWidget('ActionAssignWindow', rootWidget) - assignWindow:grabKeyboard() - assignWindow.comboPreview.keyCombo = '' - assignWindow.onKeyDown = function(assignWindow, keyCode, keyboardModifiers) - local keyCombo = determineKeyComboDesc(keyCode, keyboardModifiers) - assignWindow.comboPreview:setText(tr('Current action hotkey: %s', keyCombo)) - assignWindow.comboPreview.keyCombo = keyCombo - assignWindow.comboPreview:resizeToText() - return true - end - assignWindow.onDestroy = function() - hotkeyAssignWindow = nil - end - assignWindow.addButton.onClick = function() - local gameRootPanel = modules.game_interface.getRootPanel() - if action.hotkey and action.hotkey:len() > 0 then - g_keyboard.unbindKeyPress(action.hotkey, action.callback, gameRootPanel) - end - action.hotkey = tostring(assignWindow.comboPreview.keyCombo) - if action.hotkey and action.hotkey:len() > 0 then - g_keyboard.bindKeyPress(action.hotkey, action.callback, gameRootPanel) - end - action.hotkeyLabel:setText(action.hotkey or "") - assignWindow:destroy() - end - hotkeyAssignWindow = assignWindow - end) - menu:addSeparator() - menu:addOption(tr('Clear'), function() - action.item:setItem(nil) - action.text:setText("") - action.hotkeyLabel:setText("") - local gameRootPanel = modules.game_interface.getRootPanel() - if action.hotkey and action.hotkey:len() > 0 then - g_keyboard.unbindKeyPress(action.hotkey, action.callback, gameRootPanel) - end - action.hotkey = nil - action.actionType = nil - action:setBorderColor(ActionColors.empty) - end) - menu:display(mousePosition) - return true - elseif mouseButton == MouseLeftButton then - action.callback() - return true - end - return false -end - -function actionOnItemChange(widget) - local action = widget:getParent() - if action.item:getItemId() > 0 then - action.text:setText("") - action.item:setOn(true) - if action.item:getItem():isMultiUse() then - if not action.actionType or action.actionType <= 1 then - setupActionType(action, ActionTypes.USE_WITH) - end - else - if g_game.getClientVersion() >= 910 then - if not action.actionType or action.actionType <= ActionTypes.EQUIP then - setupActionType(action, ActionTypes.USE) - end - else - setupActionType(action, ActionTypes.USE) - end - end - end end \ No newline at end of file diff --git a/modules/game_actionbar/actionbar.otui b/modules/game_actionbar/actionbar.otui index 50e5e8a..ab66a82 100644 --- a/modules/game_actionbar/actionbar.otui +++ b/modules/game_actionbar/actionbar.otui @@ -26,9 +26,10 @@ ActionButton < Panel Label id: text anchors.fill: parent - margin: 3 3 3 3 + margin: 1 1 1 1 text-auto-resize: true text-wrap: true + phantom: true text-align: center font: verdana-9px @@ -39,8 +40,18 @@ ActionButton < Panel margin: 2 3 3 3 text-auto-resize: true text-wrap: false + phantom: true font: small-9px - color: #D3D3D3 + color: yellow + + UIProgressRect + id: cooldown + background: #585858AA + percent: 100 + focusable: false + phantom: true + anchors.fill: parent + margin: 1 1 1 1 Panel id: actionBar diff --git a/modules/game_bot/bot.lua b/modules/game_bot/bot.lua index 415d56d..3d64c88 100644 --- a/modules/game_bot/bot.lua +++ b/modules/game_bot/bot.lua @@ -67,9 +67,6 @@ function terminate() }) terminateCallbacks() - - removeEvent(checkEvent) - editWindow:destroy() botWindow:destroy() @@ -117,6 +114,7 @@ end function refresh() + if not g_game.isOnline() then return end save() clear() @@ -219,7 +217,7 @@ function save() end local status, result = pcall(function() - return json.encode(botStorage) + return json.encode(botStorage, 2) end) if not status then return onError("Error while saving bot storage. Storage won't be saved. Details: " .. result) @@ -284,11 +282,27 @@ function createDefaultConfigs() for i, file in ipairs(defaultConfigFiles) do local baseName = file:split("/") baseName = baseName[#baseName] - local contents = g_resources.readFileContents(file) - if contents:len() > 0 then - g_resources.writeFileContents("/bot/" .. config_name .. "/" .. baseName, contents) + if g_resources.directoryExists(file) then + g_resources.makeDir("/bot/" .. config_name .. "/" .. baseName) + if not g_resources.directoryExists("/bot/" .. config_name .. "/" .. baseName) then + return onError("Can't create /bot/" .. config_name .. "/" .. baseName .. " directory in " .. g_resources.getWriteDir()) + end + local defaultConfigFiles2 = g_resources.listDirectoryFiles("default_configs/" .. config_name .. "/" .. baseName, true, false) + for i, file in ipairs(defaultConfigFiles2) do + local baseName2 = file:split("/") + baseName2 = baseName2[#baseName2] + local contents = g_resources.fileExists(file) and g_resources.readFileContents(file) or "" + if contents:len() > 0 then + g_resources.writeFileContents("/bot/" .. config_name .. "/" .. baseName .. "/" .. baseName2, contents) + end + end + else + local contents = g_resources.fileExists(file) and g_resources.readFileContents(file) or "" + if contents:len() > 0 then + g_resources.writeFileContents("/bot/" .. config_name .. "/" .. baseName, contents) + end end - end + end end end diff --git a/modules/game_bot/bot.otui b/modules/game_bot/bot.otui index 046b2e6..c1f6061 100644 --- a/modules/game_bot/bot.otui +++ b/modules/game_bot/bot.otui @@ -1,8 +1,7 @@ -BotTabBar < MoveableTabBar +BotTabBar < TabBar tab-spacing: 1 margin-left: 1 margin-right: 1 - movable: false height: 20 $on: @@ -13,10 +12,13 @@ BotTabBar < MoveableTabBar visible: false margin-top: -20 -BotTabBarButton < MoveableTabBarButton - padding: 3 +BotTabBarPanel < TabBarPanel +BotTabBarButton < TabBarButton + padding: 4 + padding-right: 5 text-horizontal-auto-resize: true - + $!first: + margin-left: 0 MiniWindow id: botWindow diff --git a/modules/game_bot/default_configs/default/default.lua b/modules/game_bot/default_configs/default/default.lua deleted file mode 100644 index 53f9307..0000000 --- a/modules/game_bot/default_configs/default/default.lua +++ /dev/null @@ -1,4 +0,0 @@ --- to keep it up to date, config is loaded from remote server -loadScript("http://otclient.ovh/bot/default.php?version=" .. getVersion()) - --- if you want add custom scripts, just add them bellow or create new lua file diff --git a/modules/game_bot/default_configs/examples/battle.lua b/modules/game_bot/default_configs/examples/battle.lua deleted file mode 100644 index e4ebc2b..0000000 --- a/modules/game_bot/default_configs/examples/battle.lua +++ /dev/null @@ -1,8 +0,0 @@ -local batTab = addTab("Batt") - -Panels.AttackSpell(batTab) -Panels.AttackItem(batTab) - -Panels.AttackLeaderTarget(batTab) -Panels.LimitFloor(batTab) -Panels.AntiPush(batTab) diff --git a/modules/game_bot/default_configs/examples/cavebot.lua b/modules/game_bot/default_configs/examples/cavebot.lua deleted file mode 100644 index f24a93f..0000000 --- a/modules/game_bot/default_configs/examples/cavebot.lua +++ /dev/null @@ -1,8 +0,0 @@ -local caveTab = addTab("Cave") - -local waypoints = Panels.Waypoints(caveTab) -local attacking = Panels.Attacking(caveTab) -local looting = Panels.Looting(caveTab) -addButton("tutorial", "Help & Tutorials", function() - g_platform.openUrl("https://github.com/OTCv8/otclientv8_bot") -end, caveTab) diff --git a/modules/game_bot/default_configs/examples/example.otui b/modules/game_bot/default_configs/examples/example.otui deleted file mode 100644 index 541f98b..0000000 --- a/modules/game_bot/default_configs/examples/example.otui +++ /dev/null @@ -1,5 +0,0 @@ -ExampleLabel2 < Label - text: LOL - height: 200 - width: 50 - \ No newline at end of file diff --git a/modules/game_bot/default_configs/examples/hp.lua b/modules/game_bot/default_configs/examples/hp.lua deleted file mode 100644 index beb9db8..0000000 --- a/modules/game_bot/default_configs/examples/hp.lua +++ /dev/null @@ -1,16 +0,0 @@ -local healTab = addTab("HP") - -Panels.Haste(healTab) -Panels.ManaShield(healTab) -Panels.AntiParalyze(healTab) -Panels.Health(healTab) -Panels.Health(healTab) -Panels.HealthItem(healTab) -Panels.HealthItem(healTab) -Panels.ManaItem(healTab) -Panels.ManaItem(healTab) -Panels.Equip(healTab) -Panels.Equip(healTab) -Panels.Equip(healTab) -Panels.Eating(healTab) - diff --git a/modules/game_bot/default_configs/examples/icons.lua b/modules/game_bot/default_configs/examples/icons.lua deleted file mode 100644 index a0e7857..0000000 --- a/modules/game_bot/default_configs/examples/icons.lua +++ /dev/null @@ -1,39 +0,0 @@ - - -addIcon("dancing", {outfit={mount=0,feet=0,legs=0,body=176,type=128,auxType=0,addons=3,head=48}, hotkey="F5", text="dance"}, macro(100, "dance", function() - turn(math.random(0,3)) -end)) ---[[ -addIcon("ctrl", {outfit={mount=0,feet=10,legs=10,body=176,type=129,auxType=0,addons=3,head=48}, text="press ctrl to move icon"}, function(widget, enabled) - if enabled then - info("icon on") - else - info("icon off") - end -end) - -addIcon("mount", {outfit={mount=848,feet=10,legs=10,body=176,type=129,auxType=0,addons=3,head=48}}, function(widget, enabled) - if enabled then - info("icon mount on") - else - info("icon mount off") - end -end) - -addIcon("mount 2", {outfit={mount=849,feet=10,legs=10,body=176,type=140,auxType=0,addons=3,head=48}, switchable=false}, function(widget, enabled) - info("icon mount 2 pressed") -end) - -addIcon("item", {item=3380, hotkey="F6", switchable=false}, function(widget, enabled) - info("icon clicked") -end) - -addIcon("item2", {item={id=3043, count=100}, switchable=true}, function(widget, enabled) - info("icon 2 clicked") -end) - -addIcon("text", {text="text\nonly\nicon", switchable=true}, function(widget, enabled) - info("icon with text clicked") -end) - -]]-- \ No newline at end of file diff --git a/modules/game_bot/default_configs/examples/main.lua b/modules/game_bot/default_configs/examples/main.lua deleted file mode 100644 index 9bb12d1..0000000 --- a/modules/game_bot/default_configs/examples/main.lua +++ /dev/null @@ -1,16 +0,0 @@ -Panels.TradeMessage() -Panels.AutoStackItems() - -addButton("discord", "Discord & Help", function() - g_platform.openUrl("https://discord.gg/yhqBE4A") -end) - -addButton("forum", "Forum", function() - g_platform.openUrl("https://otland.net/forums/otclient.494/") -end) - -addButton("github", "Documentation", function() - g_platform.openUrl("https://github.com/OTCv8/otclientv8_bot") -end) - -addSeparator("sep") diff --git a/modules/game_bot/default_configs/examples/npc.lua b/modules/game_bot/default_configs/examples/npc.lua deleted file mode 100644 index ff76c60..0000000 --- a/modules/game_bot/default_configs/examples/npc.lua +++ /dev/null @@ -1,7 +0,0 @@ -singlehotkey("f10", "npc buy and sell", function() - NPC.say("hi") - NPC.say("trade") - NPC.buy(3074, 2) -- wand of vortex - NPC.sell(3074, 1) - NPC.closeTrade() -end) \ No newline at end of file diff --git a/modules/game_bot/default_configs/examples/tools.lua b/modules/game_bot/default_configs/examples/tools.lua deleted file mode 100644 index 326b16e..0000000 --- a/modules/game_bot/default_configs/examples/tools.lua +++ /dev/null @@ -1,84 +0,0 @@ -local toolsTab = addTab("Tools") - -macro(1000, "exchange money", function() - local containers = getContainers() - for i, container in pairs(containers) do - for j, item in ipairs(container:getItems()) do - if item:isStackable() and (item:getId() == 3035 or item:getId() == 3031) and item:getCount() == 100 then - g_game.use(item) - return - end - end - end -end) - -macro(1000, "this macro does nothing", "f7", function() - -end) - -macro(100, "debug pathfinding", nil, function() - for i, tile in ipairs(g_map.getTiles(posz())) do - tile:setText("") - end - local path = findEveryPath(pos(), 20, { - ignoreNonPathable = false - }) - local total = 0 - for i, p in pairs(path) do - local s = i:split(",") - local pos = {x=tonumber(s[1]), y=tonumber(s[2]), z=tonumber(s[3])} - local tile = g_map.getTile(pos) - if tile then - tile:setText(p[2]) - end - total = total + 1 - end -end) - -macro(1000, "speed hack", nil, function() - player:setSpeed(1000) -end) - -hotkey("f5", "example hotkey", function() - info("Wow, you clicked f5 hotkey") -end) - -singlehotkey("ctrl+f6", "singlehotkey", function() - info("Wow, you clicked f6 singlehotkey") - usewith(268, player) -end) - -singlehotkey("ctrl+f8", "play alarm", function() - playAlarm() -end) - -singlehotkey("ctrl+f9", "stop alarm", function() - stopSound() -end) - -local positionLabel = addLabel("positionLabel", "") -onPlayerPositionChange(function() - positionLabel:setText("Pos: " .. posx() .. "," .. posy() .. "," .. posz()) -end) - -local s = addSwitch("sdSound", "Play sound when using sd", function(widget) - storage.sdSound = not storage.sdSound - widget:setOn(storage.sdSound) -end) -s:setOn(storage.sdSound) - -onUseWith(function(pos, itemId) - if storage.sdSound and itemId == 3155 then - playSound("/sounds/magnum.ogg") - end -end) - -macro(100, "hide useless tiles", "", function() - for i, tile in ipairs(g_map.getTiles(posz())) do - if not tile:isWalkable(true) then - tile:setFill('black') - end - end -end) - -addLabel("mapinfo", "You can use ctrl + plus and ctrl + minus to zoom in / zoom out map") diff --git a/modules/game_bot/default_configs/new_cavebot/README.md b/modules/game_bot/default_configs/new_cavebot/README.md new file mode 100644 index 0000000..5f3946e --- /dev/null +++ b/modules/game_bot/default_configs/new_cavebot/README.md @@ -0,0 +1,5 @@ +# Default config for OTClientV8 bot (min. otcv8 version 2.2.3) + +### Website: http://bot.otclient.ovh/ +### Forum: https://otland.net/forums/otclient.494/ +### Discord: https://discord.gg/t4ntS5p diff --git a/modules/game_bot/default_configs/new_cavebot/cavebot.lua b/modules/game_bot/default_configs/new_cavebot/cavebot.lua new file mode 100644 index 0000000..c395ef4 --- /dev/null +++ b/modules/game_bot/default_configs/new_cavebot/cavebot.lua @@ -0,0 +1,36 @@ +-- Cavebot by otclient@otclient.ovh +-- visit http://bot.otclient.ovh/ + +local cavebotTab = "Cave" +local targetingTab = "Target" + +setDefaultTab(cavebotTab) +CaveBot = {} -- global namespace +CaveBot.Extensions = {} +importStyle("/cavebot/cavebot.otui") +importStyle("/cavebot/editor.otui") +importStyle("/cavebot/supply.otui") +dofile("/cavebot/actions.lua") +dofile("/cavebot/editor.lua") +dofile("/cavebot/example_functions.lua") +dofile("/cavebot/recorder.lua") +-- in this section you can add extensions, check extension_template.lua +--dofile("/cavebot/extension_template.lua") +dofile("/cavebot/depositer.lua") +dofile("/cavebot/supply.lua") +-- main cavebot file, must be last +dofile("/cavebot/cavebot.lua") + +setDefaultTab(targetingTab) +TargetBot = {} -- global namespace +importStyle("/targetbot/looting.otui") +importStyle("/targetbot/target.otui") +importStyle("/targetbot/creature_editor.otui") +dofile("/targetbot/creature.lua") +dofile("/targetbot/creature_attack.lua") +dofile("/targetbot/creature_editor.lua") +dofile("/targetbot/creature_priority.lua") +dofile("/targetbot/looting.lua") +dofile("/targetbot/walking.lua") +-- main targetbot file, must be last +dofile("/targetbot/target.lua") diff --git a/modules/game_bot/default_configs/new_cavebot/cavebot/actions.lua b/modules/game_bot/default_configs/new_cavebot/cavebot/actions.lua new file mode 100644 index 0000000..d4a86aa --- /dev/null +++ b/modules/game_bot/default_configs/new_cavebot/cavebot/actions.lua @@ -0,0 +1,248 @@ +CaveBot.Actions = {} + +-- it adds an action widget to list +CaveBot.addAction = function(action, value, focus) + action = action:lower() + local raction = CaveBot.Actions[action] + if not raction then + return error("Invalid cavebot action: " .. action) + end + local widget = UI.createWidget("CaveBotAction", CaveBot.actionList) + widget:setText(action .. ":" .. value:split("\n")[1]) + widget.action = action + widget.value = value + if raction.color then + widget:setColor(raction.color) + end + widget.onDoubleClick = function(cwidget) -- edit on double click + if CaveBot.Editor then + schedule(20, function() -- schedule to have correct focus + CaveBot.Editor.edit(cwidget.action, cwidget.value, function(action, value) + CaveBot.editAction(cwidget, action, value) + CaveBot.save() + end) + end) + end + end + if focus then + widget:focus() + CaveBot.actionList:ensureChildVisible(widget) + end + return widget +end + +-- it updates existing widget, you should call CaveBot.save() later +CaveBot.editAction = function(widget, action, value) + action = action:lower() + local raction = CaveBot.Actions[action] + if not raction then + return error("Invalid cavebot action: " .. action) + end + + if not widget.action or not widget.value then + return error("Invalid cavebot action widget, has missing action or value") + end + + widget:setText(action .. ":" .. value:split("\n")[1]) + widget.action = action + widget.value = value + if raction.color then + widget:setColor(raction.color) + end + return widget +end + +--[[ +registerAction: +action - string, color - string, callback = function(value, retries, prev) +value is a string value of action, retries is number which will grow by 1 if return is "retry" +prev is a true when previuos action was executed succesfully, false otherwise +it must return true if executed correctly, false otherwise +it can also return string "retry", then the function will be called again in 20 ms +]]-- +CaveBot.registerAction = function(action, color, callback) + action = action:lower() + if CaveBot.Actions[action] then + return error("Duplicated acction: " .. action) + end + CaveBot.Actions[action] = { + color=color, + callback=callback + } +end + +CaveBot.registerAction("label", "yellow", function(value, retries, prev) + return true +end) + +CaveBot.registerAction("gotolabel", "#FFFF55", function(value, retries, prev) + return CaveBot.gotoLabel(value) +end) + +CaveBot.registerAction("delay", "#AAAAAA", function(value, retries, prev) + if retries == 0 then + CaveBot.delay(tonumber(value)) + return "retry" + end + return true +end) + +CaveBot.registerAction("function", "red", function(value, retries, prev) + local prefix = "local retries = " .. retries .. "\nlocal prev = " .. tostring(prev) .. "\nlocal delay = CaveBot.delay\nlocal gotoLabel = CaveBot.gotoLabel\n" + prefix = prefix .. "local macro = function() error('Macros inside cavebot functions are not allowed') end\n" + for extension, callbacks in pairs(CaveBot.Extensions) do + prefix = prefix .. "local " .. extension .. " = CaveBot.Extensions." .. extension .. "\n" + end + local status, result = pcall(function() + return assert(load(prefix .. value, "cavebot_function"))() + end) + if not status then + error("Error in cavebot function:\n" .. result) + return false + end + return result +end) + +CaveBot.registerAction("goto", "green", function(value, retries, prev) + local pos = regexMatch(value, "\\s*([0-9]+)\\s*,\\s*([0-9]+)\\s*,\\s*([0-9]+)") + if not pos[1] then + error("Invalid cavebot goto action value. It should be position (x,y,z), is: " .. value) + return false + end + + if retries >= 5 then + return false -- tried 5 times, can't get there + end + + pos = {x=tonumber(pos[1][2]), y=tonumber(pos[1][3]), z=tonumber(pos[1][4])} + local playerPos = player:getPosition() + if pos.z ~= playerPos.z then + return false -- different floor + end + + if math.abs(pos.x-playerPos.x) + math.abs(pos.y-playerPos.y) > 40 then + return false -- too far way + end + + local minimapColor = g_map.getMinimapColor(pos) + local stairs = (minimapColor >= 210 and minimapColor <= 213) + + if stairs then + if math.abs(pos.x-playerPos.x) == 0 and math.abs(pos.y-playerPos.y) <= 0 then + return true -- already at position + end + elseif math.abs(pos.x-playerPos.x) == 0 and math.abs(pos.y-playerPos.y) <= 1 then + return true -- already at position + end + -- check if there's a path to that place, ignore creatures and fields + local path = findPath(playerPos, pos, 40, { ignoreNonPathable = true, precision = 1, ignoreCreatures = true }) + if not path then + return false -- there's no way + end + + -- walk will be executed, but it will take some time to get response from server, wait 300ms after autowalk + CaveBot.delay(300) + + -- try to find path, don't ignore creatures, don't ignore fields + if autoWalk(pos, 40) then + return "retry" + end + + -- try to find path, don't ignore creatures, ignore fields + if autoWalk(pos, 40, { ignoreNonPathable = true }) then + return "retry" + end + + if retries >= 3 then + -- try to lower precision, find something close to final position + local precison = retries - 1 + if stairs then + precison = 0 + end + if autoWalk(pos, 50, { ignoreNonPathable = true, precision = precison }) then + return "retry" + end + end + + autoWalk(path) -- everything else failed, try to walk ignoring creatures, maybe will work + return "retry" +end) + +CaveBot.registerAction("use", "#FFB272", function(value, retries, prev) + local pos = regexMatch(value, "\\s*([0-9]+)\\s*,\\s*([0-9]+)\\s*,\\s*([0-9]+)") + if not pos[1] then + local itemid = tonumber(value) + if not itemid then + error("Invalid cavebot use action value. It should be (x,y,z) or item id, is: " .. value) + return false + end + use(itemid) + return true + end + + pos = {x=tonumber(pos[1][2]), y=tonumber(pos[1][3]), z=tonumber(pos[1][4])} + local playerPos = player:getPosition() + if pos.z ~= playerPos.z then + return false -- different floor + end + + if math.max(math.abs(pos.x-playerPos.x), math.abs(pos.y-playerPos.y)) > 7 then + return false -- too far way + end + + local tile = g_map.getTile(pos) + if not tile then + return false + end + + local topThing = tile:getTopUseThing() + if not topThing then + return false + end + + use(topThing) + CaveBot.delay(400) + return true +end) + +CaveBot.registerAction("usewith", "#EEB292", function(value, retries, prev) + local pos = regexMatch(value, "\\s*([0-9]+)\\s*,\\s*([0-9]+)\\s*,\\s*([0-9]+)\\s*,\\s*([0-9]+)") + if not pos[1] then + if not itemid then + error("Invalid cavebot usewith action value. It should be (itemid,x,y,z) or item id, is: " .. value) + return false + end + use(itemid) + return true + end + + local itemid = tonumber(pos[1][2]) + pos = {x=tonumber(pos[1][3]), y=tonumber(pos[1][4]), z=tonumber(pos[1][5])} + local playerPos = player:getPosition() + if pos.z ~= playerPos.z then + return false -- different floor + end + + if math.max(math.abs(pos.x-playerPos.x), math.abs(pos.y-playerPos.y)) > 7 then + return false -- too far way + end + + local tile = g_map.getTile(pos) + if not tile then + return false + end + + local topThing = tile:getTopUseThing() + if not topThing then + return false + end + + usewith(itemid, topThing) + CaveBot.delay(400) + return true +end) + +CaveBot.registerAction("say", "#FF55FF", function(value, retries, prev) + say(value) + return true +end) \ No newline at end of file diff --git a/modules/game_bot/default_configs/new_cavebot/cavebot/cavebot.lua b/modules/game_bot/default_configs/new_cavebot/cavebot/cavebot.lua new file mode 100644 index 0000000..89db97b --- /dev/null +++ b/modules/game_bot/default_configs/new_cavebot/cavebot/cavebot.lua @@ -0,0 +1,192 @@ +local cavebotMacro = nil +local config = nil + +-- ui +local configWidget = UI.Config() +local ui = UI.createWidget("CaveBotPanel") + +ui.list = ui.listPanel.list -- shortcut +CaveBot.actionList = ui.list + +if CaveBot.Editor then + CaveBot.Editor.setup() +end +for extension, callbacks in pairs(CaveBot.Extensions) do + if callbacks.setup then + callbacks.setup() + end +end + +-- main loop, controlled by config +local actionRetries = 0 +local prevActionResult = true +cavebotMacro = macro(20, function() + if TargetBot and TargetBot.isActive() then + return -- target bot or looting is working, wait + end + + local actions = ui.list:getChildCount() + if actions == 0 then return end + local currentAction = ui.list:getFocusedChild() + if not currentAction then + currentAction = ui.list:getFirstChild() + end + local action = CaveBot.Actions[currentAction.action] + local value = currentAction.value + local retry = false + if action then + local status, result = pcall(function() + return action.callback(value, actionRetries, prevActionResult) + end) + if status then + if result == "retry" then + actionRetries = actionRetries + 1 + retry = true + elseif type(result) == 'boolean' then + actionRetries = 0 + prevActionResult = result + else + error("Invalid return from cavebot action (" .. currentAction.action .. "), should be \"retry\", false or true, is: " .. tostring(result)) + end + else + error("Error while executing cavebot action (" .. currentAction.action .. "):\n" .. result) + end + else + error("Invalid cavebot action: " .. currentAction.action) + end + + if retry then + return + end + + if currentAction ~= ui.list:getFocusedChild() then + -- focused child can change durring action, get it again and reset state + currentAction = ui.list:getFocusedChild() or ui.list:getFirstChild() + actionRetries = 0 + prevActionResult = true + end + local nextAction = ui.list:getChildIndex(currentAction) + 1 + if nextAction > actions then + nextAction = 1 + end + ui.list:focusChild(ui.list:getChildByIndex(nextAction)) +end) + +onPlayerPositionChange(function() + local delayAfterPositionChange = math.max(player:getStepDuration() + 100, 200) + cavebotMacro.delay = math.max(cavebotMacro.delay or 0, now + delayAfterPositionChange) +end) + +-- config, its callback is called immediately, data can be nil +local lastConfig = "" +config = Config.setup("cavebot_configs", configWidget, "cfg", function(name, enabled, data) + if enabled and CaveBot.Recorder.isOn() then + CaveBot.Recorder.disable() + CaveBot.setOff() + return + end + + local currentActionIndex = ui.list:getChildIndex(ui.list:getFocusedChild()) + ui.list:destroyChildren() + if not data then return cavebotMacro.setOff() end + + for k,v in ipairs(data) do + if type(v) == "table" and #v == 2 then + if v[1] == "extensions" then + local status, result = pcall(function() + return json.decode(v[2]) + end) + if not status then + error("Error while parsing CaveBot extensions from config:\n" .. result) + else + for extension, callbacks in pairs(CaveBot.Extensions) do + if callbacks.onConfigChange then + callbacks.onConfigChange(name, enabled, result[extension]) + end + end + end + else + CaveBot.addAction(v[1], v[2]) + end + end + end + + actionRetries = 0 + prevActionResult = true + cavebotMacro.setOn(enabled) + cavebotMacro.delay = nil + if lastConfig == name then + -- restore focused child on the action list + ui.list:focusChild(ui.list:getChildByIndex(currentActionIndex)) + end + lastConfig = name +end) + +-- ui callbacks +ui.showEditor.onClick = function() + if not CaveBot.Editor then return end + if ui.showEditor:isOn() then + CaveBot.Editor.hide() + ui.showEditor:setOn(false) + else + CaveBot.Editor.show() + ui.showEditor:setOn(true) + end +end + + +-- public function, you can use them in your scripts +CaveBot.isOn = function() + return config.isOn() +end + +CaveBot.isOff = function() + return config.isOff() +end + +CaveBot.setOn = function(val) + if val == false then + return CaveBot.setOff(true) + end + config.setOn() +end + +CaveBot.setOff = function(val) + if val == false then + return CaveBot.setOn(true) + end + config.setOff() +end + +CaveBot.delay = function(value) + cavebotMacro.delay = now + value +end + +CaveBot.gotoLabel = function(label) + label = label:lower() + for index, child in ipairs(ui.list:getChildren()) do + if child.action == "label" and child.value:lower() == label then + ui.list:focusChild(child) + return true + end + end + return false +end + +CaveBot.save = function() + local data = {} + for index, child in ipairs(ui.list:getChildren()) do + table.insert(data, {child.action, child.value}) + end + local extension_data = {} + for extension, callbacks in pairs(CaveBot.Extensions) do + if callbacks.onSave then + local ext_data = callbacks.onSave() + if type(ext_data) == "table" then + extension_data[extension] = ext_data + end + end + end + table.insert(data, {"extensions", json.encode(extension_data, 2)}) + config.save(data) +end diff --git a/modules/game_bot/default_configs/new_cavebot/cavebot/cavebot.otui b/modules/game_bot/default_configs/new_cavebot/cavebot/cavebot.otui new file mode 100644 index 0000000..12e54d4 --- /dev/null +++ b/modules/game_bot/default_configs/new_cavebot/cavebot/cavebot.otui @@ -0,0 +1,48 @@ +CaveBotAction < Label + background-color: alpha + text-offset: 2 0 + focusable: true + + $focus: + background-color: #00000055 + + +CaveBotPanel < Panel + layout: + type: verticalBox + fit-children: true + + HorizontalSeparator + margin-top: 2 + margin-bottom: 5 + + Panel + id: listPanel + height: 100 + margin-top: 2 + + TextList + id: list + anchors.fill: parent + vertical-scrollbar: listScrollbar + margin-right: 15 + focusable: false + auto-focus: first + + VerticalScrollBar + id: listScrollbar + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.right: parent.right + pixels-scroll: true + step: 10 + + BotSwitch + id: showEditor + margin-top: 2 + + $on: + text: Hide waypoints editor + + $!on: + text: Show waypoints editor diff --git a/modules/game_bot/default_configs/new_cavebot/cavebot/depositer.lua b/modules/game_bot/default_configs/new_cavebot/cavebot/depositer.lua new file mode 100644 index 0000000..d397c47 --- /dev/null +++ b/modules/game_bot/default_configs/new_cavebot/cavebot/depositer.lua @@ -0,0 +1,27 @@ +CaveBot.Extensions.Depositer = {} + +local ui + +-- first function called, here you should setup your UI +CaveBot.Extensions.Depositer.setup = function() + --ui = UI.createWidget('Label') + --ui:setText("Depositer UI") +end + +-- called when cavebot config changes, configData is a table but it can be nil +CaveBot.Extensions.Depositer.onConfigChange = function(configName, isEnabled, configData) + if not configData then return end + +end + +-- called when cavebot is saving config, should return table or nil +CaveBot.Extensions.Depositer.onSave = function() + return {} +end + +-- bellow add you custom functions +-- this function can be used in cavebot function waypoint as: return Depositer.run(retries, prev) +-- there are 2 useful parameters - retries (number) and prev (true/false), check actions.lua to learn more +CaveBot.Extensions.Depositer.run = function(retries, prev) + return true +end diff --git a/modules/game_bot/default_configs/new_cavebot/cavebot/editor.lua b/modules/game_bot/default_configs/new_cavebot/cavebot/editor.lua new file mode 100644 index 0000000..e27b6cb --- /dev/null +++ b/modules/game_bot/default_configs/new_cavebot/cavebot/editor.lua @@ -0,0 +1,173 @@ +CaveBot.Editor = {} +CaveBot.Editor.Actions = {} + +-- also works as registerAction(action, params), then text == action +-- params are options for text editor or function to be executed when clicked +-- you have many examples how to use it bellow +CaveBot.Editor.registerAction = function(action, text, params) + if type(text) ~= 'string' then + params = text + text = action + end + + local color = nil + if type(params) ~= 'function' then + local raction = CaveBot.Actions[action] + if not raction then + return error("CaveBot editor error: action " .. action .. " doesn't exist") + end + CaveBot.Editor.Actions[action] = params + color = raction.color + end + + local button = UI.createWidget('CaveBotEditorButton', CaveBot.Editor.ui.buttons) + button:setText(text) + if color then + button:setColor(color) + end + button.onClick = function() + if type(params) == 'function' then + params() + return + end + CaveBot.Editor.edit(action, nil, function(action, value) + local focusedAction = CaveBot.actionList:getFocusedChild() + local index = CaveBot.actionList:getChildCount() + if focusedAction then + index = CaveBot.actionList:getChildIndex(focusedAction) + end + local widget = CaveBot.addAction(action, value) + CaveBot.actionList:moveChildToIndex(widget, index + 1) + CaveBot.save() + end) + end + return button +end + +CaveBot.Editor.setup = function() + CaveBot.Editor.ui = UI.createWidget("CaveBotEditorPanel") + local ui = CaveBot.Editor.ui + local registerAction = CaveBot.Editor.registerAction + + registerAction("move up", function() + local action = CaveBot.actionList:getFocusedChild() + if not action then return end + local index = CaveBot.actionList:getChildIndex(action) + if index < 2 then return end + CaveBot.actionList:moveChildToIndex(action, index - 1) + CaveBot.actionList:ensureChildVisible(action) + CaveBot.save() + end) + registerAction("move down", function() + local action = CaveBot.actionList:getFocusedChild() + if not action then return end + local index = CaveBot.actionList:getChildIndex(action) + if index >= CaveBot.actionList:getChildCount() then return end + CaveBot.actionList:moveChildToIndex(action, index + 1) + CaveBot.actionList:ensureChildVisible(action) + CaveBot.save() + end) + registerAction("edit", function() + local action = CaveBot.actionList:getFocusedChild() + if not action or not action.onDoubleClick then return end + action.onDoubleClick(action) + end) + registerAction("remove", function() + local action = CaveBot.actionList:getFocusedChild() + if not action then return end + action:destroy() + CaveBot.save() + end) + + registerAction("label", { + value="labelName", + title="Label", + description="Add label", + multiline=false + }) + registerAction("delay", { + value="500", + title="Delay", + description="Delay next action (in milliseconds)", + multiline=false, + validation="^\\s*[0-9]{1,10}\\s*$" + }) + registerAction("gotolabel", "go to label", { + value="labelName", + title="Go to label", + description="Go to label", + multiline=false + }) + registerAction("goto", "go to", { + value=function() return posx() .. "," .. posy() .. "," .. posz() end, + title="Go to position", + description="Go to position (x,y,z)", + multiline=false, + validation="^\\s*([0-9]+)\\s*,\\s*([0-9]+)\\s*,\\s*([0-9]+)$" + }) + registerAction("use", { + value=function() return posx() .. "," .. posy() .. "," .. posz() end, + title="Use", + description="Use item from position (x,y,z) or from inventory (itemId)", + multiline=false + }) + registerAction("usewith", "use with", { + value=function() return "itemId," .. posx() .. "," .. posy() .. "," .. posz() end, + title="Use with", + description="Use item at position (itemid,x,y,z)", + multiline=false, + validation="^\\s*([0-9]+)\\s*,\\s*([0-9]+)\\s*,\\s*([0-9]+)\\s*,\\s*([0-9]+)$" + }) + registerAction("say", { + value="text", + title="Say", + description="Enter text to say", + multiline=false + }) + registerAction("function", { + title="Edit bot function", + multiline=true, + value=CaveBot.Editor.ExampleFunctions[1][2], + examples=CaveBot.Editor.ExampleFunctions, + width=650 + }) + + ui.autoRecording.onClick = function() + if ui.autoRecording:isOn() then + CaveBot.Recorder.disable() + else + CaveBot.Recorder.enable() + end + end + + -- callbacks + onPlayerPositionChange(function(pos) + ui.pos:setText("Position: " .. pos.x .. ", " .. pos.y .. ", " .. pos.z) + end) + ui.pos:setText("Position: " .. posx() .. ", " .. posy() .. ", " .. posz()) +end + +CaveBot.Editor.show = function() + CaveBot.Editor.ui:show() +end + + +CaveBot.Editor.hide = function() + CaveBot.Editor.ui:hide() +end + +CaveBot.Editor.edit = function(action, value, callback) -- callback = function(action, value) + local params = CaveBot.Editor.Actions[action] + if not params then return end + if not value then + if type(params.value) == 'function' then + value = params.value() + elseif type(params.value) == 'string' then + value = params.value + end + end + + UI.EditorWindow(value, params, function(newText) + callback(action, newText) + end) +end diff --git a/modules/game_bot/default_configs/new_cavebot/cavebot/editor.otui b/modules/game_bot/default_configs/new_cavebot/cavebot/editor.otui new file mode 100644 index 0000000..d11288c --- /dev/null +++ b/modules/game_bot/default_configs/new_cavebot/cavebot/editor.otui @@ -0,0 +1,44 @@ +CaveBotEditorButton < Button + + +CaveBotEditorPanel < Panel + id: cavebotEditor + visible: false + layout: + type: verticalBox + fit-children: true + + Label + id: pos + text-align: center + text: - + + Panel + id: buttons + margin-top: 2 + layout: + type: grid + cell-size: 86 20 + cell-spacing: 1 + flow: true + fit-children: true + + Label + text: Double click on action from action list to edit it + text-align: center + text-auto-resize: true + text-wrap: true + margin-top: 3 + margin-left: 2 + margin-right: 2 + + BotSwitch + id: autoRecording + text: Auto Recording + margin-top: 3 + + BotButton + margin-top: 3 + margin-bottom: 3 + text: Documentation + @onClick: g_platform.openUrl("http://bot.otclient.ovh/") diff --git a/modules/game_bot/default_configs/new_cavebot/cavebot/example_functions.lua b/modules/game_bot/default_configs/new_cavebot/cavebot/example_functions.lua new file mode 100644 index 0000000..2e5b0fb --- /dev/null +++ b/modules/game_bot/default_configs/new_cavebot/cavebot/example_functions.lua @@ -0,0 +1,65 @@ +CaveBot.Editor.ExampleFunctions = {} + +local function addExampleFunction(title, text) + return table.insert(CaveBot.Editor.ExampleFunctions, {title, text:trim()}) +end + +addExampleFunction("Click to browse example functions", [[ +-- available functions/variables: +-- prev - result of previous action (true or false) +-- retries - number of retries of current function, goes up by one when you return "retry" +-- delay(number) - delays bot next action, value in milliseconds +-- gotoLabel(string) - goes to specific label, return true if label exists +-- you can easily access bot extensions, Depositer.run() instead of CaveBot.Extensions.Depositer.run() +-- also you can access bot global variables, like CaveBot, TargetBot +-- use storage variable to store date between calls + +-- function should return false, true or "retry" +-- if "retry" is returned, function will be executed again in 20 ms (so better call delay before) + +return true +]]) + +addExampleFunction("buy 200 mana potion from npc Eryn", [[ +--buy 200 mana potions +local npc = getCreatureByName("Eryn") +if not npc then + return false +end +if retries > 10 then + return false +end +local pos = player:getPosition() +local npcPos = npc:getPosition() +if math.max(math.abs(pos.x - npcPos.x), math.abs(pos.y - npcPos.y)) > 3 then + autoWalk(npcPos, {precision=3}) + delay(300) + return "retry" +end +if not NPC.isTrading() then + NPC.say("hi") + NPC.say("trade") + delay(200) + return "retry" +end +NPC.buy(268, 100) +schedule(1000, function() + -- buy again in 1s + NPC.buy(268, 100) + NPC.closeTrade() + NPC.say("bye") +end) +delay(1200) +return true +]]) + +addExampleFunction("Say hello 5 times with some delay", [[ +--say hello +if retries > 5 then + return true -- finish +end +say("hello") +delay(100 + retries * 100) +return "retry" +]]) + diff --git a/modules/game_bot/default_configs/new_cavebot/cavebot/extension_template.lua b/modules/game_bot/default_configs/new_cavebot/cavebot/extension_template.lua new file mode 100644 index 0000000..d015f11 --- /dev/null +++ b/modules/game_bot/default_configs/new_cavebot/cavebot/extension_template.lua @@ -0,0 +1,58 @@ +-- example cavebot extension (remember to add this file to ../cavebot.lua) +CaveBot.Extensions.Example = {} + +local ui + +-- setup is called automaticly when cavebot is ready +CaveBot.Extensions.Example.setup = function() + ui = UI.createWidget('BotTextEdit') + ui:setText("Hello") + ui.onTextChange = function() + CaveBot.save() -- save new config when you change something + end + + -- add custom cavebot action (check out actions.lua) + CaveBot.registerAction("sayhello", "orange", function(value, retries, prev) + local how_many_times = tonumber(value) + if retries >= how_many_times then + return true + end + say("hello " .. (retries + 1)) + delay(250) + return "retry" + end) + + -- add this custom action to editor (check out editor.lua) + CaveBot.Editor.registerAction("sayhello", "say hello", { + value="5", + title="Say hello", + description="Says hello x times", + validation="[0-9]{1,5}" -- regex, optional + }) +end + +-- called when cavebot config changes, configData is a table but it can also be nil +CaveBot.Extensions.Example.onConfigChange = function(configName, isEnabled, configData) + if not configData then return end + if configData["text"] then + ui:setText(configData["text"]) + end +end + +-- called when cavebot is saving config (so when CaveBot.save() is called), should return table or nil +CaveBot.Extensions.Example.onSave = function() + return {text=ui:getText()} +end + +-- bellow add you custom functions to be used in cavebot function action +-- an example: return Example.run(retries, prev) +-- there are 2 useful parameters - retries (number) and prev (true/false), check actions.lua and example_functions.lua to learn more +CaveBot.Extensions.Example.run = function(retries, prev) + -- it will say text 10 times with some delay and then continue + if retries > 10 then + return true + end + say(ui:getText() .. " x" .. retries) + delay(100 + retries * 100) + return "retry" +end diff --git a/modules/game_bot/default_configs/new_cavebot/cavebot/recorder.lua b/modules/game_bot/default_configs/new_cavebot/cavebot/recorder.lua new file mode 100644 index 0000000..f0fffc1 --- /dev/null +++ b/modules/game_bot/default_configs/new_cavebot/cavebot/recorder.lua @@ -0,0 +1,68 @@ +-- auto recording for cavebot +CaveBot.Recorder = {} + +local isEnabled = nil +local lastPos = nil + +local function setup() + local function addPosition(pos) + CaveBot.addAction("goto", pos.x .. "," .. pos.y .. "," .. pos.z, true) + lastPos = pos + end + + onPlayerPositionChange(function(newPos, oldPos) + if CaveBot.isOn() or not isEnabled then return end + if not lastPos then + -- first step + addPosition(oldPos) + elseif newPos.z ~= oldPos.z or math.abs(oldPos.x - newPos.x) > 1 or math.abs(oldPos.y - newPos.y) > 1 then + -- stairs/teleport + addPosition(oldPos) + elseif math.max(math.abs(lastPos.x - newPos.x), math.abs(lastPos.y - newPos.y)) > 5 then + -- 5 steps from last pos + addPosition(newPos) + end + end) + + onUse(function(pos, itemId, stackPos, subType) + if CaveBot.isOn() or not isEnabled then return end + if pos.x == 0xFFFF then + lastPos = pos + CaveBot.addAction("use", itemId, true) + else + lastPos = pos + CaveBot.addAction("use", pos.x .. "," .. pos.y .. "," .. pos.z, true) + end + end) + + onUseWith(function(pos, itemId, target, subType) + if CaveBot.isOn() or not isEnabled then return end + if not target:isItem() then return end + local targetPos = target:getPosition() + if targetPos.x == 0xFFFF then return end + lastPos = pos + CaveBot.addAction("usewith", itemId .. "," .. pos.x .. "," .. pos.y .. "," .. pos.z, true) + end) +end + +CaveBot.Recorder.isOn = function() + return isEnabled +end + +CaveBot.Recorder.enable = function() + CaveBot.setOff() + if isEnabled == nil then + setup() + end + CaveBot.Editor.ui.autoRecording:setOn(true) + isEnabled = true + lastPos = nil +end + +CaveBot.Recorder.disable = function() + if isEnabled == true then + isEnabled = false + end + CaveBot.Editor.ui.autoRecording:setOn(false) + CaveBot.save() +end \ No newline at end of file diff --git a/modules/game_bot/default_configs/new_cavebot/cavebot/supply.lua b/modules/game_bot/default_configs/new_cavebot/cavebot/supply.lua new file mode 100644 index 0000000..b3cd4ca --- /dev/null +++ b/modules/game_bot/default_configs/new_cavebot/cavebot/supply.lua @@ -0,0 +1,30 @@ +CaveBot.Extensions.Supply = {} + +local ui + +-- first function called, here you should setup your UI +CaveBot.Extensions.Supply.setup = function() + --ui = UI.createWidget('SupplyItemList') + --local widget = UI.createWidget('SupplyItem', ui.list) + --widget.item.onItemChange = function(newItem) + --widget.fields.min.onTextChange = function(newText) + -- make it similar to UI.Container, so if there are no free slots, add another one, keep min 4 slots, check if value min/max is number after edit +end + +-- called when cavebot config changes, configData is a table but it can be nil +CaveBot.Extensions.Supply.onConfigChange = function(configName, isEnabled, configData) + if not configData then return end + +end + +-- called when cavebot is saving config, should return table or nil +CaveBot.Extensions.Supply.onSave = function() + return {} +end + +-- bellow add you custom functions +-- this function can be used in cavebot function waypoint as: return Supply.run(retries, prev) +-- there are 2 useful parameters - retries (number) and prev (true/false), check actions.lua to learn more +CaveBot.Extensions.Supply.run = function(retries, prev) + return true +end diff --git a/modules/game_bot/default_configs/new_cavebot/cavebot/supply.otui b/modules/game_bot/default_configs/new_cavebot/cavebot/supply.otui new file mode 100644 index 0000000..83c76ac --- /dev/null +++ b/modules/game_bot/default_configs/new_cavebot/cavebot/supply.otui @@ -0,0 +1,72 @@ +SupplyItem < Panel + height: 34 + + BotItem + id: item + size: 32 32 + anchors.left: parent.left + anchors.top: parent.top + margin-top: 1 + + Panel + id: fields + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.left: prev.right + anchors.right: parent.right + margin-left: 2 + margin-right: 2 + + Label + id: minLabel + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.horizontalCenter + margin-right: 2 + text-align: center + text: "Min" + + Label + id: maxLabel + anchors.top: parent.top + anchors.left: parent.horizontalCenter + anchors.right: parent.right + margin-left: 2 + text-align: center + text: "Max" + + BotTextEdit + id: min + anchors.top: minLabel.bottom + anchors.left: minLabel.left + anchors.right: minLabel.right + text-align: center + text: 1 + + BotTextEdit + id: max + anchors.top: maxLabel.bottom + anchors.left: maxLabel.left + anchors.right: maxLabel.right + text-align: center + text: 100 + +SupplyItemList < Panel + height: 102 + + ScrollablePanel + id: list + anchors.fill: parent + vertical-scrollbar: scroll + margin-right: 7 + layout: + type: verticalBox + cell-height: 34 + + BotSmallScrollBar + id: scroll + anchors.top: prev.top + anchors.bottom: prev.bottom + anchors.right: parent.right + step: 10 + pixels-scroll: true diff --git a/modules/game_bot/default_configs/new_cavebot/hp.lua b/modules/game_bot/default_configs/new_cavebot/hp.lua new file mode 100644 index 0000000..1ae7027 --- /dev/null +++ b/modules/game_bot/default_configs/new_cavebot/hp.lua @@ -0,0 +1,182 @@ +setDefaultTab("HP") + +--2x healing spell +--2x healing rune +--utani hur +--mana shield +--anti paralyze +--4x equip + +UI.Label("Healing spells") + +if type(storage.healing1) ~= "table" then + storage.healing1 = {on=false, title="HP%", text="exura", min=51, max=90} +end +if type(storage.healing2) ~= "table" then + storage.healing2 = {on=false, title="HP%", text="exura vita", min=0, max=50} +end + +-- create 2 healing widgets +for _, healingInfo in ipairs({storage.healing1, storage.healing2}) do + local healingmacro = macro(20, function() + local hp = player:getHealthPercent() + if healingInfo.max >= hp and hp >= healingInfo.min then + if TargetBot then + TargetBot.saySpell(healingInfo.text) -- sync spell with targetbot if available + else + say(healingInfo.text) + end + end + end) + healingmacro.setOn(healingInfo.on) + + UI.DualScrollPanel(healingInfo, function(widget, newParams) + healingInfo = newParams + healingmacro.setOn(healingInfo.on) + end) +end + +UI.Separator() + +UI.Label("Mana & health potions/runes") + +if type(storage.hpitem1) ~= "table" then + storage.hpitem1 = {on=false, title="HP%", item=266, min=51, max=90} +end +if type(storage.hpitem2) ~= "table" then + storage.hpitem2 = {on=false, title="HP%", item=3160, min=0, max=50} +end +if type(storage.manaitem1) ~= "table" then + storage.manaitem1 = {on=false, title="MP%", item=268, min=51, max=90} +end +if type(storage.manaitem2) ~= "table" then + storage.manaitem2 = {on=false, title="MP%", item=3157, min=0, max=50} +end + +for i, healingInfo in ipairs({storage.hpitem1, storage.hpitem2, storage.manaitem1, storage.manaitem2}) do + local healingmacro = macro(20, function() + local hp = i <= 2 and player:getHealthPercent() or math.min(100, math.floor(100 * (player:getMana() / player:getMaxMana()))) + if healingInfo.max >= hp and hp >= healingInfo.min then + if TargetBot then + TargetBot.useItem(healingInfo.item, player) -- sync spell with targetbot if available + else + useWith(healingInfo.item, player) + end + end + end) + healingmacro.setOn(healingInfo.on) + + UI.DualScrollItemPanel(healingInfo, function(widget, newParams) + healingInfo = newParams + healingmacro.setOn(healingInfo.on) + end) +end + +UI.Separator() + +UI.Label("Mana shield spell:") +UI.TextEdit(storage.manaShield or "utamo vita", function(widget, newText) + storage.manaShield = newText +end) + +local lastManaShield = 0 +macro(20, "mana shield", function() + if hasManaShield() or lastManaShield + 90000 > now then return end + if TargetBot then + TargetBot.saySpell(storage.manaShield) -- sync spell with targetbot if available + else + say(storage.manaShield) + end +end) + +UI.Label("Haste spell:") +UI.TextEdit(storage.hasteSpell or "utani hur", function(widget, newText) + storage.hasteSpell = newText +end) + +macro(500, "haste", function() + if hasHaste() then return end + if TargetBot then + TargetBot.saySpell(storage.hasteSpell) -- sync spell with targetbot if available + else + say(storage.hasteSpell) + end +end) + +UI.Label("Anti paralyze spell:") +UI.TextEdit(storage.antiParalyze or "utani hur", function(widget, newText) + storage.antiParalyze = newText +end) + +macro(100, "anti paralyze", function() + if not isParalyzed() then return end + if TargetBot then + TargetBot.saySpell(storage.antiParalyze) -- sync spell with targetbot if available + else + say(storage.antiParalyze) + end +end) + +UI.Separator() + +UI.Label("Eatable items:") +if type(storage.foodItems) ~= "table" then + storage.foodItems = {3582, 3577} +end + +local foodContainer = UI.Container(function(widget, items) + storage.foodItems = items +end, true) +foodContainer:setHeight(35) +foodContainer:setItems(storage.foodItems) + +macro(10000, "eat food", function() + if not storage.foodItems[1] then return end + -- search for food in containers + for _, container in pairs(g_game.getContainers()) do + for __, item in ipairs(container:getItems()) do + for i, foodItem in ipairs(storage.foodItems) do + if item:getId() == foodItem.id then + return g_game.use(item) + end + end + end + end + -- can't find any food, try to eat random item using hotkey + local toEat = storage.foodItems[math.random(1, #storage.foodItems)] + if toEat then g_game.useInventoryItem(toEat.id) end +end) + +UI.Separator() +UI.Label("Auto equip") + +if type(storage.autoEquip) ~= "table" then + storage.autoEquip = {} +end +for i=1,4 do -- if you want more auto equip panels you can change 4 to higher value + if not storage.autoEquip[i] then + storage.autoEquip[i] = {on=false, title="Auto Equip", item1=i == 1 and 3052 or 0, item2=i == 1 and 3089 or 0, slot=i == 1 and 9 or 0} + end + UI.TwoItemsAndSlotPanel(storage.autoEquip[i], function(widget, newParams) + storage.autoEquip[i] = newParams + end) +end +macro(250, function() + local containers = g_game.getContainers() + for index, autoEquip in ipairs(storage.autoEquip) do + if autoEquip.on then + local slotItem = getSlot(autoEquip.slot) + if not slotItem or (slotItem:getId() ~= autoEquip.item1 and slotItem:getId() ~= autoEquip.item2) then + for _, container in pairs(containers) do + for __, item in ipairs(container:getItems()) do + if item:getId() == autoEquip.item1 or item:getId() == autoEquip.item2 then + g_game.move(item, {x=65535, y=autoEquip.slot, z=0}, item:getCount()) + delay(1000) -- don't call it to often + return + end + end + end + end + end + end +end) \ No newline at end of file diff --git a/modules/game_bot/default_configs/new_cavebot/main.lua b/modules/game_bot/default_configs/new_cavebot/main.lua new file mode 100644 index 0000000..9f6b6e9 --- /dev/null +++ b/modules/game_bot/default_configs/new_cavebot/main.lua @@ -0,0 +1,22 @@ +-- main tab +VERSION = "1.0" + +UI.Label("Config version: " .. VERSION) + +UI.Separator() + + + +UI.Separator() + +UI.Button("Discord", function() + g_platform.openUrl("https://discord.gg/yhqBE4A") +end) + +UI.Button("Forum", function() + g_platform.openUrl("https://otland.net/forums/otclient.494/") +end) + +UI.Button("Help & Tutorials", function() + g_platform.openUrl("http://bot.otclient.ovh/") +end) diff --git a/modules/game_bot/default_configs/new_cavebot/mwall_timer.lua b/modules/game_bot/default_configs/new_cavebot/mwall_timer.lua new file mode 100644 index 0000000..6dc1ec4 --- /dev/null +++ b/modules/game_bot/default_configs/new_cavebot/mwall_timer.lua @@ -0,0 +1,41 @@ +-- Magic wall & Wild growth timer + +-- config +local magicWallId = 2129 +local magicWallTime = 20000 +local wildGrowthId = 2130 +local wildGrowthTime = 45000 + +-- script +local activeTimers = {} + +onAddThing(function(tile, thing) + if not thing:isItem() then + return + end + local timer = 0 + if thing:getId() == magicWallId then + timer = magicWallTime + elseif thing:getId() == wildGrowthId then + timer = wildGrowthTime + else + return + end + + local pos = tile:getPosition().x .. "," .. tile:getPosition().y .. "," .. tile:getPosition().z + if not activeTimers[pos] or activeTimers[pos] < now then + activeTimers[pos] = now + timer + end + tile:setTimer(activeTimers[pos] - now) +end) + +onRemoveThing(function(tile, thing) + if not thing:isItem() then + return + end + if (thing:getId() == magicWallId or thing:getId() == wildGrowthId) and tile:getGround() then + local pos = tile:getPosition().x .. "," .. tile:getPosition().y .. "," .. tile:getPosition().z + activeTimers[pos] = nil + tile:setTimer(0) + end +end) diff --git a/modules/game_bot/default_configs/new_cavebot/targetbot/creature.lua b/modules/game_bot/default_configs/new_cavebot/targetbot/creature.lua new file mode 100644 index 0000000..84a8836 --- /dev/null +++ b/modules/game_bot/default_configs/new_cavebot/targetbot/creature.lua @@ -0,0 +1,93 @@ + +TargetBot.Creature = {} +TargetBot.Creature.configsCache = {} +TargetBot.Creature.cached = 0 + +TargetBot.Creature.resetConfigs = function() + TargetBot.targetList:destroyChildren() + TargetBot.Creature.resetConfigsCache() +end + +TargetBot.Creature.resetConfigsCache = function() + TargetBot.Creature.configsCache = {} + TargetBot.Creature.cached = 0 +end + +TargetBot.Creature.addConfig = function(config, focus) + if type(config) ~= 'table' or type(config.name) ~= 'string' then + return error("Invalid targetbot creature config (missing name)") + end + TargetBot.Creature.resetConfigsCache() + + if not config.regex then + config.regex = "^" .. config.name:trim():lower():gsub("%*", ".*"):gsub("%?", ".?") .. "$" + end + + local widget = UI.createWidget("TargetBotEntry", TargetBot.targetList) + widget:setText(config.name) + widget.value = config + + widget.onDoubleClick = function(entry) -- edit on double click + schedule(20, function() -- schedule to have correct focus + TargetBot.Creature.edit(entry.value, function(newConfig) + entry:setText(newConfig.name) + entry.value = newConfig + TargetBot.Creature.resetConfigsCache() + TargetBot.save() + end) + end) + end + + if focus then + widget:focus() + TargetBot.targetList:ensureChildVisible(widget) + end + return widget +end + +TargetBot.Creature.getConfigs = function(creature) + if not creature then return {} end + local name = creature:getName():trim():lower() + -- this function may be slow, so it will be using cache + if TargetBot.Creature.configsCache[name] then + return TargetBot.Creature.configsCache[name] + end + local configs = {} + for _, config in ipairs(TargetBot.targetList:getChildren()) do + if regexMatch(name, config.value.regex)[1] then + table.insert(configs, config.value) + end + end + if TargetBot.Creature.cached > 1000 then + TargetBot.Creature.resetConfigsCache() -- too big cache size, reset + end + TargetBot.Creature.configsCache[name] = configs -- add to cache + TargetBot.Creature.cached = TargetBot.Creature.cached + 1 + return configs +end + +TargetBot.Creature.calculateParams = function(creature, path) + local configs = TargetBot.Creature.getConfigs(creature) + local priority = 0 + local danger = 0 + local selectedConfig = nil + for _, config in ipairs(configs) do + local config_priority = TargetBot.Creature.calculatePriority(creature, config, path) + if config_priority > priority then + priority = config_priority + danger = TargetBot.Creature.calculateDanger(creature, config, path) + selectedConfig = config + end + end + return { + config = selectedConfig, + creature = creature, + danger = danger, + priority = priority + } +end + +TargetBot.Creature.calculateDanger = function(creature, config, path) + -- config is based on creature_editor + return config.danger +end diff --git a/modules/game_bot/default_configs/new_cavebot/targetbot/creature_attack.lua b/modules/game_bot/default_configs/new_cavebot/targetbot/creature_attack.lua new file mode 100644 index 0000000..e01b9e8 --- /dev/null +++ b/modules/game_bot/default_configs/new_cavebot/targetbot/creature_attack.lua @@ -0,0 +1,70 @@ +TargetBot.Creature.attack = function(params, targets, isLooting) -- params {config, creature, danger, priority} + if player:isWalking() then + lastWalk = now + end + + local config = params.config + local creature = params.creature + + if g_game.getAttackingCreature() ~= creature then + g_game.attack(creature) + end + + if not isLooting then -- walk only when not looting + TargetBot.Creature.walk(creature, config, targets) + end + + -- attacks + local mana = player:getMana() + if config.useGroupAttack and config.groupAttackSpell:len() > 1 and mana > config.minManaGroup then + local creatures = g_map.getSpectatorsInRange(player:getPosition(), false, config.groupAttackRadius, config.groupAttackRadius) + local playersAround = false + local monsters = 0 + for _, creature in ipairs(creatures) do + if not creature:isLocalPlayer() and creature:isPlayer() then + playersAround = true + elseif creature:isMonster() then + monsters = monsters + 1 + end + end + if monsters >= config.groupAttackTargets and (not playersAround or config.groupAttackIgnorePlayers) then + if TargetBot.sayAttackSpell(config.groupAttackSpell, config.groupAttackDelay) then + return + end + end + end + if config.useSpellAttack and config.attackSpell:len() > 1 and mana > config.minMana then + if TargetBot.sayAttackSpell(config.attackSpell, config.attackSpellDelay) then + return + end + end + if config.useRuneAttack and config.attackRune > 100 then + if TargetBot.useAttackItem(config.attackRune, creature, config.attackRuneDelay) then + return + end + end +end + +TargetBot.Creature.walk = function(creature, config, targets) + -- luring + if config.lure and not (config.chase and creature:getHealthPercent() < 30) then + local monsters = 0 + if targets < config.lureCount then + local path = findPath(player:getPosition(), creature:getPosition(), 5, {ignoreNonPathable=true, precision=2}) + if path then + return TargetBot.walkTo(creature:getPosition(), 10, {marginMin=5, marginMax=6, ignoreNonPathable=true}) + end + end + end + + local currentDistance = findPath(player:getPosition(), creature:getPosition(), 10, {ignoreCreatures=true, ignoreNonPathable=true, ignoreCost=true}) + if config.chase and (creature:getHealthPercent() < 30 or not config.keepDistance) then + if #currentDistance > 1 then + return TargetBot.walkTo(creature:getPosition(), 10, {ignoreNonPathable=true, precision=1}) + end + elseif config.keepDistance then + if #currentDistance ~= config.keepDistanceRange and #currentDistance ~= config.keepDistanceRange + 1 then + return TargetBot.walkTo(creature:getPosition(), 10, {ignoreNonPathable=true, marginMin=config.keepDistanceRange, marginMax=config.keepDistanceRange + 1}) + end + end +end diff --git a/modules/game_bot/default_configs/new_cavebot/targetbot/creature_editor.lua b/modules/game_bot/default_configs/new_cavebot/targetbot/creature_editor.lua new file mode 100644 index 0000000..cbacc66 --- /dev/null +++ b/modules/game_bot/default_configs/new_cavebot/targetbot/creature_editor.lua @@ -0,0 +1,99 @@ +TargetBot.Creature.edit = function(config, callback) -- callback = function(newConfig) + config = config or {} + + local editor = UI.createWindow('TargetBotCreatureEditorWindow') + local values = {} -- (key, function returning value of key) + + editor.name:setText(config.name or "") + table.insert(values, {"name", function() return editor.name:getText() end}) + + local addScrollBar = function(id, title, min, max, defaultValue) + local widget = UI.createWidget('TargetBotCreatureEditorScrollBar', editor.left) + widget.scroll.onValueChange = function(scroll, value) + widget.text:setText(title .. ": " .. value) + end + widget.scroll:setRange(min, max) + if max-min > 1000 then + widget.scroll:setStep(100) + elseif max-min > 100 then + widget.scroll:setStep(10) + end + widget.scroll:setValue(config[id] or defaultValue) + widget.scroll.onValueChange(widget.scroll, widget.scroll:getValue()) + table.insert(values, {id, function() return widget.scroll:getValue() end}) + end + + local addTextEdit = function(id, title, defaultValue) + local widget = UI.createWidget('TargetBotCreatureEditorTextEdit', editor.right) + widget.text:setText(title) + widget.textEdit:setText(config[id] or defaultValue or "") + table.insert(values, {id, function() return widget.textEdit:getText() end}) + end + + local addCheckBox = function(id, title, defaultValue) + local widget = UI.createWidget('TargetBotCreatureEditorCheckBox', editor.right) + widget.onClick = function() + widget:setOn(not widget:isOn()) + end + widget:setText(title) + if config[id] == nil then + widget:setOn(defaultValue) + else + widget:setOn(config[id]) + end + table.insert(values, {id, function() return widget:isOn() end}) + end + + local addItem = function(id, title, defaultItem) + local widget = UI.createWidget('TargetBotCreatureEditorItem', editor.right) + widget.text:setText(title) + widget.item:setItemId(config[id] or defaultItem) + table.insert(values, {id, function() return widget.item:getItemId() end}) + end + + editor.cancel.onClick = function() + editor:destroy() + end + editor.onEscape = editor.cancel.onClick + + editor.ok.onClick = function() + local newConfig = {} + for _, value in ipairs(values) do + newConfig[value[1]] = value[2]() + end + if newConfig.name:len() < 1 then return end + newConfig.regex = "^" .. newConfig.name:trim():lower():gsub("%*", ".*"):gsub("%?", ".?") .. "$" + + editor:destroy() + callback(newConfig) + end + + -- values + addScrollBar("priority", "Priority", 0, 10, 1) + addScrollBar("danger", "Danger", 0, 10, 1) + addScrollBar("maxDistance", "Max distance", 1, 10, 10) + addScrollBar("keepDistanceRange", "Keep distance", 1, 5, 1) + addScrollBar("lureCount", "Lure", 0, 5, 1) + + addScrollBar("minMana", "Min. mana for attack spell", 0, 3000, 200) + addScrollBar("attackSpellDelay", "Attack spell delay", 200, 5000, 2500) + addScrollBar("minManaGroup", "Min. mana for group attack", 0, 3000, 1500) + addScrollBar("groupAttackTargets", "Min. targets for group attack", 1, 10, 2) + addScrollBar("groupAttackRadius", "Radius of group attack spell", 1, 7, 1) + addScrollBar("groupAttackDelay", "Group attack spell delay", 200, 60000, 5000) + addScrollBar("runeAttackDelay", "Rune attack delay", 200, 5000, 2000) + + addCheckBox("chase", "Chase", true) + addCheckBox("keepDistance", "Keep Distance", false) + addCheckBox("lure", "Lure", false) +-- addCheckBox("avoidAttacks", "Avoid attacks", true) + + addCheckBox("useSpellAttack", "Use attack spell", false) + addTextEdit("attackSpell", "Attack spell", "") + addCheckBox("useGroupAttack", "Use group attack spell", false) + addCheckBox("groupAttackIgnorePlayers", "Ignore players in group attack", false) + addTextEdit("groupAttackSpell", "Group attack spell", "") + addCheckBox("useRuneAttack", "Use attack rune", false) + addItem("attackRune", "Attack rune:", 0) + +end diff --git a/modules/game_bot/default_configs/new_cavebot/targetbot/creature_editor.otui b/modules/game_bot/default_configs/new_cavebot/targetbot/creature_editor.otui new file mode 100644 index 0000000..5f45b73 --- /dev/null +++ b/modules/game_bot/default_configs/new_cavebot/targetbot/creature_editor.otui @@ -0,0 +1,134 @@ +TargetBotCreatureEditorScrollBar < Panel + height: 35 + margin-top: 7 + + Label + id: text + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + text-align: center + + HorizontalScrollBar + id: scroll + anchors.left: parent.left + anchors.right: parent.right + anchors.top: prev.bottom + margin-top: 5 + minimum: 0 + maximum: 10 + step: 1 + +TargetBotCreatureEditorTextEdit < Panel + height: 40 + margin-top: 7 + + Label + id: text + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + text-align: center + + TextEdit + id: textEdit + anchors.left: parent.left + anchors.right: parent.right + anchors.top: prev.bottom + margin-top: 5 + minimum: 0 + maximum: 10 + step: 1 + +TargetBotCreatureEditorItem < Panel + height: 34 + margin-top: 7 + margin-left: 25 + margin-right: 25 + + Label + id: text + anchors.left: parent.left + anchors.verticalCenter: next.verticalCenter + + BotItem + id: item + anchors.top: parent.top + anchors.right: parent.right + + +TargetBotCreatureEditorCheckBox < BotSwitch + height: 20 + margin-top: 7 + +TargetBotCreatureEditorWindow < MainWindow + text: TargetBot creature editor + width: 500 + height: 650 + + Label + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + text-align: center + text: You can use * (any characters) and ? (any character) in target name + + TextEdit + id: name + anchors.top: prev.bottom + anchors.left: parent.left + anchors.right: parent.right + margin-left: 90 + margin-top: 5 + + Label + anchors.verticalCenter: prev.verticalCenter + anchors.left: parent.left + text: Target name: + + Panel + id: left + anchors.top: name.bottom + anchors.left: parent.left + anchors.right: parent.horizontalCenter + margin-top: 5 + margin-left: 10 + margin-right: 10 + layout: + type: verticalBox + fit-children: true + + Panel + id: right + anchors.top: name.bottom + anchors.left: parent.horizontalCenter + anchors.right: parent.right + margin-top: 5 + margin-left: 10 + margin-right: 10 + layout: + type: verticalBox + fit-children: true + + Button + id: help + !text: tr('Help & Tutorials') + anchors.bottom: parent.bottom + anchors.left: parent.left + width: 150 + @onClick: g_platform.openUrl("http://bot.otclient.ovh/") + + Button + id: ok + !text: tr('Ok') + anchors.bottom: parent.bottom + anchors.right: next.left + margin-right: 10 + width: 60 + + Button + id: cancel + !text: tr('Cancel') + anchors.bottom: parent.bottom + anchors.right: parent.right + width: 60 diff --git a/modules/game_bot/default_configs/new_cavebot/targetbot/creature_priority.lua b/modules/game_bot/default_configs/new_cavebot/targetbot/creature_priority.lua new file mode 100644 index 0000000..7a72c69 --- /dev/null +++ b/modules/game_bot/default_configs/new_cavebot/targetbot/creature_priority.lua @@ -0,0 +1,37 @@ +TargetBot.Creature.calculatePriority = function(creature, config, path) + -- config is based on creature_editor + local priority = config.priority + + -- extra priority if it's current target + if g_game.getAttackingCreature() == creature then + priority = priority + 1 + end + + -- check if distance is fine, if not then attack only if already attacked + if #path > config.maxDistance then + return priority + end + + -- extra priority for close distance + local path_length = #path + if path_length == 1 then + priority = priority + 3 + elseif path_length <= 3 then + priority = priority + 1 + end + + -- extra priority for low health + if config.chase and creature:getHealthPercent() < 30 then + priority = priority + 5 + elseif creature:getHealthPercent() < 20 then + priority = priority + 2.5 + elseif creature:getHealthPercent() < 40 then + priority = priority + 1.5 + elseif creature:getHealthPercent() < 60 then + priority = priority + 0.5 + elseif creature:getHealthPercent() < 80 then + priority = priority + 0.2 + end + + return priority +end \ No newline at end of file diff --git a/modules/game_bot/default_configs/new_cavebot/targetbot/looting.lua b/modules/game_bot/default_configs/new_cavebot/targetbot/looting.lua new file mode 100644 index 0000000..b855aa6 --- /dev/null +++ b/modules/game_bot/default_configs/new_cavebot/targetbot/looting.lua @@ -0,0 +1,295 @@ +TargetBot.Looting = {} +TargetBot.Looting.list = {} -- list of containers to loot + +local ui +local items = {} +local containers = {} +local itemsById = {} +local containersById = {} +local dontSave = false + +TargetBot.Looting.setup = function() + ui = UI.createWidget("TargetBotLootingPanel") + UI.Container(TargetBot.Looting.onItemsUpdate, true, nil, ui.items) + UI.Container(TargetBot.Looting.onContainersUpdate, true, nil, ui.containers) + ui.everyItem.onClick = function() + ui.everyItem:setOn(not ui.everyItem:isOn()) + TargetBot.save() + end + ui.maxDangerPanel.value.onTextChange = function() + local value = tonumber(ui.maxDangerPanel.value:getText()) + if not value then + ui.maxDangerPanel.value:setText(0) + end + if dontSave then return end + TargetBot.save() + end + ui.minCapacityPanel.value.onTextChange = function() + local value = tonumber(ui.minCapacityPanel.value:getText()) + if not value then + ui.minCapacityPanel.value:setText(0) + end + if dontSave then return end + TargetBot.save() + end +end + +TargetBot.Looting.onItemsUpdate = function() + if dontSave then return end + TargetBot.save() + TargetBot.Looting.updateItemsAndContainers() +end + +TargetBot.Looting.onContainersUpdate = function() + if dontSave then return end + TargetBot.save() + TargetBot.Looting.updateItemsAndContainers() +end + +TargetBot.Looting.update = function(data) + dontSave = true + TargetBot.Looting.list = {} + ui.items:setItems(data['items'] or {}) + ui.containers:setItems(data['containers'] or {}) + ui.everyItem:setOn(data['everyItem']) + ui.maxDangerPanel.value:setText(data['maxDanger'] or 10) + ui.minCapacityPanel.value:setText(data['minCapacity'] or 100) + TargetBot.Looting.updateItemsAndContainers() + dontSave = false +end + +TargetBot.Looting.save = function(data) + data['items'] = ui.items:getItems() + data['containers'] = ui.containers:getItems() + data['maxDanger'] = tonumber(ui.maxDangerPanel.value:getText()) + data['minCapacity'] = tonumber(ui.minCapacityPanel.value:getText()) + data['everyItem'] = ui.everyItem:isOn() +end + +TargetBot.Looting.updateItemsAndContainers = function() + items = ui.items:getItems() + containers = ui.containers:getItems() + itemsById = {} + containersById = {} + for i, item in ipairs(items) do + itemsById[item.id] = 1 + end + for i, container in ipairs(containers) do + containersById[container.id] = 1 + end +end + +local waitTill = 0 +local waitingForContainer = nil +local status = "" +local lastFoodConsumption = 0 + +TargetBot.Looting.getStatus = function() + return status +end + +TargetBot.Looting.process = function(targets, dangerLevel) + if (not items[1] and not ui.everyItem:isOn()) or not containers[1] then + status = "" + return false + end + if dangerLevel > tonumber(ui.maxDangerPanel.value:getText()) then + status = "High danger" + return false + end + if player:getFreeCapacity() < tonumber(ui.minCapacityPanel.value:getText()) then + status = "No cap" + TargetBot.Looting.list = {} + return false + end + local loot = TargetBot.Looting.list[1] + if loot == nil then + status = "" + return false + end + + if waitTill > now then + return true + end + local containers = g_game.getContainers() + local lootContainers = TargetBot.Looting.getLootContainers(containers) + + -- check if there's container for loot and has empty space for it + if not lootContainers[1] then + -- there's no space, don't loot + status = "No space" + return false + end + + status = "Looting" + + for index, container in pairs(containers) do + if container.lootContainer then + TargetBot.Looting.lootContainer(lootContainers, container) + return true + end + end + + local pos = player:getPosition() + local dist = math.max(math.abs(pos.x-loot.pos.x), math.abs(pos.y-loot.pos.y)) + if loot.tries > 30 or loot.pos.z ~= pos.z or dist > 20 then + table.remove(TargetBot.Looting.list, 1) + return true + end + + local tile = g_map.getTile(loot.pos) + if dist >= 3 or not tile then + loot.tries = loot.tries + 1 + TargetBot.walkTo(loot.pos, 20, { ignoreNonPathable = true, precision = 2 }) + return true + end + + local container = tile:getTopUseThing() + if not container or container:getId() ~= loot.container then + table.remove(TargetBot.Looting.list, 1) + return true + end + + g_game.open(container) + waitTill = now + 1000 -- give it 1s to open + waitingForContainer = loot.container + loot.tries = loot.tries + 10 + + return true +end + +TargetBot.Looting.getLootContainers = function(containers) + local lootContainers = {} + local openedContainersById = {} + local toOpen = nil + for index, container in pairs(containers) do + openedContainersById[container:getContainerItem():getId()] = 1 + if containersById[container:getContainerItem():getId()] and not container.lootContainer then + if container:getItemsCount() < container:getCapacity() then + table.insert(lootContainers, container) + else -- it's full, open next container if possible + for slot, item in ipairs(container:getItems()) do + if item:isContainer() and containersById[item:getId()] then + toOpen = {item, container} + break + end + end + end + end + end + if not lootContainers[1] then + if toOpen then + g_game.open(toOpen[1], toOpen[2]) + waitTill = now + 500 -- wait 0.5s + return lootContainers + end + -- check containers one more time, maybe there's any loot container + for index, container in pairs(containers) do + if not containersById[container:getContainerItem():getId()] and not container.lootContainer then + for slot, item in ipairs(container:getItems()) do + if item:isContainer() and containersById[item:getId()] then + g_game.open(item) + waitTill = now + 500 -- wait 0.5s + return lootContainers + end + end + end + end + -- can't find any lootContainer, let's check slots, maybe there's one + for slot = InventorySlotFirst, InventorySlotLast do + local item = getInventoryItem(slot) + if item and item:isContainer() and not openedContainersById[item:getId()] then + -- container which is not opened yet, let's open it + g_game.open(item) + waitTill = now + 500 -- wait 0.5s + return lootContainers + end + end + end + return lootContainers +end + +TargetBot.Looting.lootContainer = function(lootContainers, container) + -- loot items + local nextContainer = nil + for i, item in ipairs(container:getItems()) do + if item:isContainer() then + nextContainer = item + elseif itemsById[item:getId()] or ui.everyItem:isOn() then + item.lootTries = (item.lootTries or 0) + 1 + if item.lootTries < 5 then -- if can't be looted within 0.5s then skip it + return TargetBot.Looting.lootItem(lootContainers, item) + end + elseif storage.foodItems and storage.foodItems[1] and lastFoodConsumption + 5000 < now then + for _, food in ipairs(storage.foodItems) do + if item:getId() == food.id then + g_game.use(item) + lastFoodConsumption = now + return + end + end + end + end + + -- no more items to loot, open next container + if nextContainer then + nextContainer.lootTries = (nextContainer.lootTries or 0) + 1 + if nextContainer.lootTries < 2 then -- max 0.6s to open it + g_game.open(nextContainer, container) + waitTill = now + 300 -- give it 0.3s to open + waitingForContainer = loot.container + return + end + end + + -- looting finished, remove container from list + container.lootContainer = false + g_game.close(container) + table.remove(TargetBot.Looting.list, 1) +end + +TargetBot.Looting.lootItem = function(lootContainers, item) + if item:isStackable() then + local count = item:getCount() + for _, container in ipairs(lootContainers) do + for slot, citem in ipairs(container:getItems()) do + if item:getId() == citem:getId() and citem:getCount() < 100 then + g_game.move(item, container:getSlotPosition(slot - 1), count) + waitTill = now + 300 -- give it 0.3s to move item + return + end + end + end + end + + local container = lootContainers[1] + g_game.move(item, container:getSlotPosition(container:getItemsCount()), 1) + waitTill = now + 300 -- give it 0.3s to move item +end + +onContainerOpen(function(container, previousContainer) + if container:getContainerItem():getId() == waitingForContainer then + container.lootContainer = true + waitingForContainer = nil + end +end) + +onCreatureDisappear(function(creature) + if not TargetBot.isOn() then return end + if not creature:isMonster() then return end + local pos = player:getPosition() + local mpos = creature:getPosition() + local name = creature:getName() + if pos.z ~= mpos.z or math.max(math.abs(pos.x-mpos.x), math.abs(pos.y-mpos.y)) > 6 then return end + schedule(20, function() -- check in 20ms if there's container (dead body) on that tile + if not containers[1] then return end + if TargetBot.Looting.list[20] then return end -- too many items to loot + local tile = g_map.getTile(mpos) + if not tile then return end + local container = tile:getTopUseThing() + if not container or not container:isContainer() then return end + if not findPath(player:getPosition(), mpos, 6, {ignoreNonPathable=true, ignoreCreatures=true, ignoreCost=true}) then return end + table.insert(TargetBot.Looting.list, {pos=mpos, creature=name, container=container:getId(), added=now, tries=0}) + container:setMarked('#000088') + end) +end) diff --git a/modules/game_bot/default_configs/new_cavebot/targetbot/looting.otui b/modules/game_bot/default_configs/new_cavebot/targetbot/looting.otui new file mode 100644 index 0000000..97cb351 --- /dev/null +++ b/modules/game_bot/default_configs/new_cavebot/targetbot/looting.otui @@ -0,0 +1,83 @@ +TargetBotLootingPanel < Panel + layout: + type: verticalBox + fit-children: true + + HorizontalSeparator + margin-top: 5 + + Label + margin-top: 5 + text: Items to loot + text-align: center + + BotContainer + id: items + margin-top: 3 + + BotSwitch + id: everyItem + !text: tr("Loot every item") + margin-top: 2 + + Label + margin-top: 5 + text: Containers for loot + text-align: center + + BotContainer + id: containers + margin-top: 3 + height: 45 + + Panel + id: maxDangerPanel + height: 20 + margin-top: 5 + + BotTextEdit + id: value + anchors.right: parent.right + anchors.top: parent.top + anchors.bottom: parent.bottom + margin-right: 6 + width: 80 + + Label + anchors.left: parent.left + anchors.verticalCenter: prev.verticalCenter + text: Max. danger: + margin-left: 5 + + Panel + id: minCapacityPanel + height: 20 + margin-top: 3 + + BotTextEdit + id: value + anchors.right: parent.right + anchors.top: parent.top + anchors.bottom: parent.bottom + margin-right: 6 + width: 80 + + Label + anchors.left: parent.left + anchors.verticalCenter: prev.verticalCenter + text: Min. capacity: + margin-left: 5 + + Label + margin-top: 3 + margin-left: 20 + margin-right: 20 + !text: tr("Drag item or click on any of empty slot") + text-align: center + text-wrap: true + text-auto-resize: true + + BotButton + margin-top: 3 + text: Help & Tutorials + @onClick: g_platform.openUrl("http://bot.otclient.ovh/") diff --git a/modules/game_bot/default_configs/new_cavebot/targetbot/target.lua b/modules/game_bot/default_configs/new_cavebot/targetbot/target.lua new file mode 100644 index 0000000..495d9ec --- /dev/null +++ b/modules/game_bot/default_configs/new_cavebot/targetbot/target.lua @@ -0,0 +1,231 @@ +local targetbotMacro = nil +local config = nil +local lastAction = 0 + +-- ui +local configWidget = UI.Config() +local ui = UI.createWidget("TargetBotPanel") + +ui.list = ui.listPanel.list -- shortcut +TargetBot.targetList = ui.list +TargetBot.Looting.setup() + +ui.status.left:setText("Status:") +ui.status.right:setText("Off") +ui.target.left:setText("Target:") +ui.target.right:setText("-") +ui.config.left:setText("Config:") +ui.config.right:setText("-") +ui.danger.left:setText("Danger:") +ui.danger.right:setText("0") + +ui.editor.debug.onClick = function() + local on = ui.editor.debug:isOn() + ui.editor.debug:setOn(not on) + if on then + for _, spec in ipairs(getSpectators()) do + spec:clearText() + end + end +end + +-- main loop, controlled by config +targetbotMacro = macro(100, function() + local pos = player:getPosition() + local creatures = g_map.getSpectatorsInRange(pos, false, 6, 6) -- 12x12 area + if #creatures > 10 then -- if there are too many monsters around, limit area + creatures = g_map.getSpectatorsInRange(pos, false, 3, 3) -- 6x6 area + end + local highestPriority = 0 + local dangerLevel = 0 + local targets = 0 + local highestPriorityParams = nil + for i, creature in ipairs(creatures) do + local path = findPath(player:getPosition(), creature:getPosition(), 7, {ignoreLastCreature=true, ignoreNonPathable=true, ignoreCost=true}) + if creature:isMonster() and path then + local params = TargetBot.Creature.calculateParams(creature, path) -- return {craeture, config, danger, priority} + dangerLevel = dangerLevel + params.danger + if params.priority > 0 then + targets = targets + 1 + if params.priority > highestPriority then + highestPriority = params.priority + highestPriorityParams = params + end + if ui.editor.debug:isOn() then + creature:setText(params.config.name .. "\n" .. params.priority) + end + end + end + end + + -- reset walking + TargetBot.walkTo(nil) + + -- looting + local looting = TargetBot.Looting.process(targets, dangerLevel) + local lootingStatus = TargetBot.Looting.getStatus() + + ui.danger.right:setText(dangerLevel) + if highestPriorityParams and not isInPz() then + ui.target.right:setText(highestPriorityParams.creature:getName()) + ui.config.right:setText(highestPriorityParams.config.name) + TargetBot.Creature.attack(highestPriorityParams, targets, looting) + if lootingStatus:len() > 0 then + TargetBot.setStatus("Attack & " .. lootingStatus) + else + TargetBot.setStatus("Attacking") + end + TargetBot.walk() + lastAction = now + return + end + + ui.target.right:setText("-") + ui.config.right:setText("-") + if looting then + TargetBot.walk() + lastAction = now + end + if lootingStatus:len() > 0 then + TargetBot.setStatus(lootingStatus) + else + TargetBot.setStatus("Waiting") + end +end) + +-- config, its callback is called immediately, data can be nil +config = Config.setup("targetbot_configs", configWidget, "json", function(name, enabled, data) + if not data then + ui.status.right:setText("Off") + return targetbotMacro.setOff() + end + TargetBot.Creature.resetConfigs() + for _, value in ipairs(data["targeting"] or {}) do + TargetBot.Creature.addConfig(value) + end + TargetBot.Looting.update(data["looting"] or {}) + + -- add configs + if enabled then + ui.status.right:setText("On") + else + ui.status.right:setText("Off") + end + + targetbotMacro.setOn(enabled) + targetbotMacro.delay = nil +end) + +-- setup ui +ui.editor.buttons.add.onClick = function() + TargetBot.Creature.edit(nil, function(newConfig) + TargetBot.Creature.addConfig(newConfig, true) + TargetBot.save() + end) +end + +ui.editor.buttons.edit.onClick = function() + local entry = ui.list:getFocusedChild() + if not entry then return end + TargetBot.Creature.edit(entry.value, function(newConfig) + entry:setText(newConfig.name) + entry.value = newConfig + TargetBot.Creature.resetConfigsCache() + TargetBot.save() + end) +end + +ui.editor.buttons.remove.onClick = function() + local entry = ui.list:getFocusedChild() + if not entry then return end + entry:destroy() + TargetBot.Creature.resetConfigsCache() + TargetBot.save() +end + +-- public function, you can use them in your scripts +TargetBot.isActive = function() -- return true if attacking or looting takes place + return lastAction + 300 > now +end + +TargetBot.setStatus = function(text) + return ui.status.right:setText(text) +end + +TargetBot.isOn = function() + return config.isOn() +end + +TargetBot.isOff = function() + return config.isOff() +end + +TargetBot.setOn = function(val) + if val == false then + return TargetBot.setOff(true) + end + config.setOn() +end + +TargetBot.setOff = function(val) + if val == false then + return TargetBot.setOn(true) + end + config.setOff() +end + +TargetBot.delay = function(value) + targetbotMacro.delay = now + value +end + +TargetBot.save = function() + local data = {targeting={}, looting={}} + for _, entry in ipairs(ui.list:getChildren()) do + table.insert(data.targeting, entry.value) + end + TargetBot.Looting.save(data.looting) + config.save(data) +end + +-- attacks +local lastSpell = 0 +local lastAttackSpell = 0 + +TargetBot.saySpell = function(text, delay) + if type(text) ~= 'string' or text:len() < 1 then return end + if not delay then delay = 500 end + if g_game.getProtocolVersion() < 1090 then + lastAttackSpell = now -- pause attack spells, healing spells are more important + end + if lastSpell + delay < now then + say(text) + lastSpell = now + return true + end + return false +end + +TargetBot.sayAttackSpell = function(text, delay) + if type(text) ~= 'string' or text:len() < 1 then return end + if not delay then delay = 2000 end + if lastAttackSpell + delay < now then + say(text) + lastAttackSpell = now + return true + end + return false +end + +local lastRuneAttack = 0 + +TargetBot.useItem = function(item, target, delay) + useWith(item, target) +end + +TargetBot.useAttackItem = function(item, target, delay) + if not delay then delay = 2000 end + if lastRuneAttack + delay < now then + useWith(item, target) + lastRuneAttack = now + end +end diff --git a/modules/game_bot/default_configs/new_cavebot/targetbot/target.otui b/modules/game_bot/default_configs/new_cavebot/targetbot/target.otui new file mode 100644 index 0000000..6e0e4ea --- /dev/null +++ b/modules/game_bot/default_configs/new_cavebot/targetbot/target.otui @@ -0,0 +1,115 @@ +TargetBotEntry < Label + background-color: alpha + text-offset: 2 0 + focusable: true + + $focus: + background-color: #00000055 + +TargetBotDualLabel < Panel + height: 18 + margin-left: 3 + margin-right: 4 + + Label + id: left + anchors.top: parent.top + anchors.left: parent.left + text-auto-resize: true + + Label + id: right + anchors.top: parent.top + anchors.right: parent.right + text-auto-resize: true + +TargetBotPanel < Panel + layout: + type: verticalBox + fit-children: true + + HorizontalSeparator + margin-top: 2 + margin-bottom: 5 + + TargetBotDualLabel + id: status + TargetBotDualLabel + id: target + TargetBotDualLabel + id: config + TargetBotDualLabel + id: danger + + Panel + id: listPanel + height: 40 + + TextList + id: list + anchors.fill: parent + vertical-scrollbar: listScrollbar + margin-right: 15 + focusable: false + auto-focus: first + + VerticalScrollBar + id: listScrollbar + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.right: parent.right + pixels-scroll: true + step: 10 + + BotSwitch + id: configButton + @onClick: | + self:setOn(not self:isOn()) + self:getParent().listPanel:setHeight(self:isOn() and 100 or 40) + self:getParent().editor:setVisible(self:isOn()) + + $on: + text: Hide target editor + + $!on: + text: Show target editor + + Panel + id: editor + visible: false + layout: + type: verticalBox + fit-children: true + + Panel + id: buttons + height: 20 + margin-top: 2 + + Button + id: add + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.left: parent.left + text: Add + width: 56 + + Button + id: edit + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.horizontalCenter: parent.horizontalCenter + text: Edit + width: 56 + + Button + id: remove + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.right: parent.right + text: Remove + width: 56 + + BotSwitch + id: debug + text: Show target priority diff --git a/modules/game_bot/default_configs/new_cavebot/targetbot/walking.lua b/modules/game_bot/default_configs/new_cavebot/targetbot/walking.lua new file mode 100644 index 0000000..b256d6a --- /dev/null +++ b/modules/game_bot/default_configs/new_cavebot/targetbot/walking.lua @@ -0,0 +1,28 @@ +local dest +local maxDist +local params + +TargetBot.walkTo = function(_dest, _maxDist, _params) + dest = _dest + maxDist = _maxDist + params = _params +end + +-- called every 100ms if targeting or looting is active +TargetBot.walk = function() + if not dest then return end + if player:isWalking() then return end + local pos = player:getPosition() + if pos.z ~= dest.z then return end + local dist = math.max(math.abs(pos.x-dest.x), math.abs(pos.y-dest.y)) + if params.precision and params.precision >= dist then return end + if params.marginMin and params.marginMax then + if dist >= params.marginMin and dist <= params.marginMax then + return + end + end + local path = getPath(pos, dest, maxDist, params) + if path then + walk(path[1]) + end +end diff --git a/modules/game_bot/default_configs/new_cavebot/tools.lua b/modules/game_bot/default_configs/new_cavebot/tools.lua new file mode 100644 index 0000000..383b25f --- /dev/null +++ b/modules/game_bot/default_configs/new_cavebot/tools.lua @@ -0,0 +1,110 @@ +-- tools tab +setDefaultTab("Tools") + +-- allows to test/edit bot lua scripts ingame, you can have multiple scripts like this, just change storage.ingame_lua +UI.Button("Ingame macro editor", function(newText) + UI.MultilineEditorWindow(storage.ingame_macros or "", {title="Macro editor", description="You can add your custom macros (or any other lua code) here"}, function(text) + storage.ingame_macros = text + reload() + end) +end) +UI.Button("Ingame hotkey editor", function(newText) + UI.MultilineEditorWindow(storage.ingame_hotkeys or "", {title="Hotkeys editor", description="You can add your custom hotkeys/singlehotkeys here"}, function(text) + storage.ingame_hotkeys = text + reload() + end) +end) + +UI.Separator() + +for _, scripts in ipairs({storage.ingame_macros, storage.ingame_hotkeys}) do + if type(scripts) == "string" and scripts:len() > 3 then + local status, result = pcall(function() + assert(load(scripts, "ingame_editor"))() + end) + if not status then + error("Ingame edior error:\n" .. result) + end + end +end + +UI.Separator() + +UI.Button("Zoom In map [ctrl + =]", function() zoomIn() end) +UI.Button("Zoom Out map [ctrl + -]", function() zoomOut() end) + +UI.Separator() + +local moneyIds = {3031, 3035} -- gold coin, platinium coin +macro(1000, "Exchange money", function() + local containers = g_game.getContainers() + for index, container in pairs(containers) do + if not container.lootContainer then -- ignore monster containers + for i, item in ipairs(container:getItems()) do + if item:getCount() == 100 then + for m, moneyId in ipairs(moneyIds) do + if item:getId() == moneyId then + return g_game.use(item) + end + end + end + end + end + end +end) + +macro(1000, "Stack items", function() + local containers = g_game.getContainers() + local toStack = {} + for index, container in pairs(containers) do + if not container.lootContainer then -- ignore monster containers + for i, item in ipairs(container:getItems()) do + if item:isStackable() and item:getCount() < 100 then + local stackWith = toStack[item:getId()] + if stackWith then + g_game.move(item, stackWith[1], math.min(stackWith[2], item:getCount())) + return + end + toStack[item:getId()] = {container:getSlotPosition(i - 1), 100 - item:getCount()} + end + end + end + end +end) + +UI.Separator() + +UI.Label("Mana training") +if type(storage.manaTrain) ~= "table" then + storage.manaTrain = {on=false, title="MP%", text="utevo lux", min=80, max=100} +end + +local manatrainmacro = macro(1000, function() + local mana = math.min(100, math.floor(100 * (player:getMana() / player:getMaxMana()))) + if storage.manaTrain.max >= mana and mana >= storage.manaTrain.min then + say(storage.manaTrain.text) + end +end) +manatrainmacro.setOn(storage.manaTrain.on) + +UI.DualScrollPanel(storage.manaTrain, function(widget, newParams) + storage.manaTrain = newParams + manatrainmacro.setOn(storage.manaTrain.on) +end) + +UI.Separator() + +macro(60000, "Send message on trade", function() + local trade = getChannelId("advertising") + if not trade then + trade = getChannelId("trade") + end + if trade and storage.autoTradeMessage:len() > 0 then + sayChannel(trade, storage.autoTradeMessage) + end +end) +UI.TextEdit(storage.autoTradeMessage or "I'm using OTClientV8!", function(widget, text) + storage.autoTradeMessage = text +end) + +UI.Separator() diff --git a/modules/game_bot/functions/callbacks.lua b/modules/game_bot/functions/callbacks.lua index 846d445..f76440f 100644 --- a/modules/game_bot/functions/callbacks.lua +++ b/modules/game_bot/functions/callbacks.lua @@ -29,6 +29,20 @@ context.callback = function(callbackType, callback) context._currentExecution = prevExecution end end) + local cb = context._callbacks[callbackType] + return { + remove = function() + local index = nil + for i, cb2 in ipairs(context._callbacks[callbackType]) do + if cb == cb2 then + index = i + end + end + if index then + table.remove(context._callbacks[callbackType], index) + end + end + } end -- onKeyDown(callback) -- callback = function(keys) @@ -158,7 +172,7 @@ end context.listen = function(name, callback) if not name then return context.error("listen: invalid name") end name = name:lower() - context.onTalk(function(name2, level, mode, text, channelId, pos) + return context.onTalk(function(name2, level, mode, text, channelId, pos) if name == name2:lower() then callback(text, channelId, pos) end @@ -167,7 +181,7 @@ end -- onPlayerPositionChange(callback) -- callback = function(newPos, oldPos) context.onPlayerPositionChange = function(callback) - context.onCreaturePositionChange(function(creature, newPos, oldPos) + return context.onCreaturePositionChange(function(creature, newPos, oldPos) if creature == context.player then callback(newPos, oldPos) end @@ -176,7 +190,7 @@ end -- onPlayerHealthChange(callback) -- callback = function(healthPercent) context.onPlayerHealthChange = function(callback) - context.onCreatureHealthPercentChange(function(creature, healthPercent) + return context.onCreatureHealthPercentChange(function(creature, healthPercent) if creature == context.player then callback(healthPercent) end diff --git a/modules/game_bot/functions/config.lua b/modules/game_bot/functions/config.lua index 2775dc7..a9764b1 100644 --- a/modules/game_bot/functions/config.lua +++ b/modules/game_bot/functions/config.lua @@ -33,9 +33,10 @@ Config.list = function(dir) return correctList end --- load config from string insteaf of dile +-- load config from string insteaf of file Config.parse = function(data) local status, result = pcall(function() + if data:len() < 2 then return {} end return json.decode(data) end) if status and type(result) == 'table' then @@ -51,13 +52,29 @@ Config.parse = function(data) end Config.load = function(dir, name) - local file = context.configDir .. "/" .. dir .. "/" .. name .. ".json" + local file = context.configDir .. "/" .. dir .. "/" .. name .. ".json" if g_resources.fileExists(file) then -- load json - return json.decode(g_resources.readFileContents(file)) + local status, result = pcall(function() + local data = g_resources.readFileContents(file) + if data:len() < 2 then return {} end + return json.decode(data) + end) + if not status then + context.error("Invalid json config (" .. name .. "): " .. result) + return {} + end + return result end file = context.configDir .. "/" .. dir .. "/" .. name .. ".cfg" if g_resources.fileExists(file) then -- load cfg - return table.decodeStringPairList(g_resources.readFileContents(file)) + local status, result = pcall(function() + return table.decodeStringPairList(g_resources.readFileContents(file)) + end) + if not status then + context.error("Invalid cfg config (" .. name .. "): " .. result) + return {} + end + return result end return context.error("Config " .. file .. " doesn't exist") end @@ -87,7 +104,7 @@ Config.save = function(dir, name, value, forcedExtension) if (table.isStringPairList(value) and forcedExtension ~= "json") or forcedExtension == "cfg" then -- cfg g_resources.writeFileContents(file .. ".cfg", table.encodeStringPairList(value)) else - g_resources.writeFileContents(file .. ".json", json.encode(value)) + g_resources.writeFileContents(file .. ".json", json.encode(value, 2)) end return true end @@ -175,7 +192,11 @@ Config.setup = function(dir, widget, configExtension, callback) if g_resources.fileExists(file) then return context.error("Config " .. name .. " already exist") end - g_resources.writeFileContents(file, "") + if configExtension == "json" then + g_resources.writeFileContents(file, json.encode({})) + else + g_resources.writeFileContents(file, "") + end context.storage._configs[dir].selected = name widget.switch:setOn(false) refresh() diff --git a/modules/game_bot/functions/tools.lua b/modules/game_bot/functions/tools.lua index dda3cf4..9a5e344 100644 --- a/modules/game_bot/functions/tools.lua +++ b/modules/game_bot/functions/tools.lua @@ -1,6 +1,6 @@ local context = G.botContext -context.encode = function(data) return json.encode(data) end +context.encode = function(data, indent) return json.encode(data, indent or 2) end context.decode = function(text) local status, result = pcall(function() return json.decode(text) end) if status then return result end return {} end context.displayGeneralBox = function(title, message, buttons, onEnterCallback, onEscapeCallback) diff --git a/modules/game_bot/functions/ui.lua b/modules/game_bot/functions/ui.lua index 759305f..46d70c0 100644 --- a/modules/game_bot/functions/ui.lua +++ b/modules/game_bot/functions/ui.lua @@ -8,5 +8,17 @@ UI.createWidget = function(name, parent) if parent == nil then parent = context.panel end - return g_ui.createWidget(name, parent) + local widget = g_ui.createWidget(name, parent) + widget.botWidget = true + return widget end + +UI.createWindow = function(name) + local widget = g_ui.createWidget(name, g_ui.getRootWidget()) + widget.botWidget = true + widget:show() + widget:raise() + widget:focus() + return widget +end + diff --git a/modules/game_bot/functions/ui_elements.lua b/modules/game_bot/functions/ui_elements.lua index 85115ed..5065feb 100644 --- a/modules/game_bot/functions/ui_elements.lua +++ b/modules/game_bot/functions/ui_elements.lua @@ -4,6 +4,14 @@ if type(context.UI) ~= "table" then end local UI = context.UI +UI.Button = function(text, callback, parent) + local widget = UI.createWidget("BotButton", parent) + widget:setText(text) + widget.onClick = callback + return widget +end + + UI.Config = function(parent) return UI.createWidget("BotConfig", parent) end @@ -11,8 +19,11 @@ end -- call :setItems(table) to set items, call :getItems() to get them -- unique if true, won't allow duplicates -- callback (can be nil) gets table with new item list, eg: {{id=2160, count=1}, {id=268, count=100}, {id=269, count=20}} -UI.Container = function(callback, unique, parent) - local widget = UI.createWidget("BotContainer", parent) +UI.Container = function(callback, unique, parent, widget) + if not widget then + widget = UI.createWidget("BotContainer", parent) + end + local oldItems = {} local updateItems = function() @@ -33,7 +44,7 @@ UI.Container = function(callback, unique, parent) if somethingNew then oldItems = items - callback(items) + callback(widget, items) end widget:setItems(items) @@ -77,6 +88,183 @@ UI.Container = function(callback, unique, parent) return items end + widget:setItems({}) + return widget end +UI.DualScrollPanel = function(params, callback, parent) -- callback = function(widget, newParams) + --[[ params: + on - bool, + text - string, + title - string, + min - number, + max - number, + ]] + params.title = params.title or "title" + params.text = params.text or "" + params.min = params.min or 20 + params.max = params.max or 80 + + local widget = UI.createWidget('DualScrollPanel', parent) + + widget.title:setOn(params.on) + widget.title.onClick = function() + params.on = not params.on + widget.title:setOn(params.on) + if callback then + callback(widget, params) + end + end + + widget.text:setText(params.text or "") + widget.text.onTextChange = function(widget, text) + params.text = text + if callback then + callback(widget, params) + end + end + + local update = function(dontSignal) + widget.title:setText("" .. params.min .. "% <= " .. params.title .. " <= " .. params.max .. "%") + if callback and not dontSignal then + callback(widget, params) + end + end + + widget.scroll1:setValue(params.min) + widget.scroll2:setValue(params.max) + + widget.scroll1.onValueChange = function(scroll, value) + params.min = value + update() + end + widget.scroll2.onValueChange = function(scroll, value) + params.max = value + update() + end + update(true) +end + +UI.DualScrollItemPanel = function(params, callback, parent) -- callback = function(widget, newParams) + --[[ params: + on - bool, + item - number, + title - string, + min - number, + max - number, + ]] + params.title = params.title or "title" + params.item = params.item or 0 + params.min = params.min or 20 + params.max = params.max or 80 + + local widget = UI.createWidget('DualScrollItemPanel', parent) + + widget.title:setOn(params.on) + widget.title.onClick = function() + params.on = not params.on + widget.title:setOn(params.on) + if callback then + callback(widget, params) + end + end + + widget.item:setItemId(params.item) + widget.item.onItemChange = function() + params.item = widget.item:getItemId() + if callback then + callback(widget, params) + end + end + + local update = function(dontSignal) + widget.title:setText("" .. params.min .. "% <= " .. params.title .. " <= " .. params.max .. "%") + if callback and not dontSignal then + callback(widget, params) + end + end + + widget.scroll1:setValue(params.min) + widget.scroll2:setValue(params.max) + + widget.scroll1.onValueChange = function(scroll, value) + params.min = value + update() + end + widget.scroll2.onValueChange = function(scroll, value) + params.max = value + update() + end + update(true) +end + +UI.Label = function(text, parent) + local label = UI.createWidget('BotLabel', parent) + label:setText(text) + return label +end + +UI.Separator = function(parent) + local separator = UI.createWidget('BotSeparator', parent) + return separator +end + +UI.TextEdit = function(text, callback, parent) + local widget = UI.createWidget('BotTextEdit', parent) + widget.onTextChange = callback + widget:setText(text) + return widget +end + +UI.TwoItemsAndSlotPanel = function(params, callback, parent) + --[[ params: + on - bool, + title - string, + item1 - number, + item2 - number, + slot - number, + ]] + params.title = params.title or "title" + params.item1 = params.item1 or 0 + params.item2 = params.item2 or 0 + params.slot = params.slot or 1 + + local widget = UI.createWidget("TwoItemsAndSlotPanel", parent) + + widget.title:setText(params.title) + widget.title:setOn(params.on) + widget.title.onClick = function() + params.on = not params.on + widget.title:setOn(params.on) + if callback then + callback(widget, params) + end + end + + widget.slot:setCurrentIndex(params.slot) + widget.slot.onOptionChange = function() + params.slot = widget.slot.currentIndex + if callback then + callback(widget, params) + end + end + + widget.item1:setItemId(params.item1) + widget.item1.onItemChange = function() + params.item1 = widget.item1:getItemId() + if callback then + callback(widget, params) + end + end + + widget.item2:setItemId(params.item2) + widget.item2.onItemChange = function() + params.item2 = widget.item2:getItemId() + if callback then + callback(widget, params) + end + end + + return widget +end diff --git a/modules/game_bot/functions/ui_legacy.lua b/modules/game_bot/functions/ui_legacy.lua index 5d77098..4dbaccf 100644 --- a/modules/game_bot/functions/ui_legacy.lua +++ b/modules/game_bot/functions/ui_legacy.lua @@ -1,5 +1,8 @@ local context = G.botContext +-- DO NOT USE THIS CODE. +-- IT'S ONLY HERE FOR BACKWARD COMPATIBILITY, MAY BE REMOVED IN THE FUTURE + context.createWidget = function(name, parent) if parent == nil then parent = context.panel @@ -32,11 +35,11 @@ context.addTab = function(name) return tab.tabPanel.content end - context.tabs:setOn(true) + local smallTabs = #(context.tabs.tabs) >= 5 local newTab = context.tabs:addTab(name, g_ui.createWidget('BotPanel')).tabPanel.content - if #(context.tabs.tabs) > 5 then + context.tabs:setOn(true) + if smallTabs then for k,tab in pairs(context.tabs.tabs) do - tab:setPadding(3) tab:setFont('small-9px') end end diff --git a/modules/game_bot/panels/DONT_USE_PANELS.txt b/modules/game_bot/panels/DONT_USE_PANELS.txt new file mode 100644 index 0000000..a9c2c18 --- /dev/null +++ b/modules/game_bot/panels/DONT_USE_PANELS.txt @@ -0,0 +1,3 @@ +DONT USE PANELS +THEY ONLY HERE FOR BACKWARD COMPATIBILITY +MAY BE REMOVED IN THE FUTURE \ No newline at end of file diff --git a/modules/game_bot/panels/attacking.lua b/modules/game_bot/panels/attacking.lua index d348f83..3833364 100644 --- a/modules/game_bot/panels/attacking.lua +++ b/modules/game_bot/panels/attacking.lua @@ -581,7 +581,7 @@ Panel newConfig = "name:" .. configName .. "\n" end for monster, config in pairs(monsters) do - newConfig = newConfig .. "\n" .. monster .. ":" .. json.encode(config) .. "\n" + newConfig = newConfig .. "\n" .. monster .. ":" .. json.encode(config, 2) .. "\n" end context.storage.attacking.configs[context.storage.attacking.activeConfig] = newConfig diff --git a/modules/game_bot/ui/config.otui b/modules/game_bot/ui/config.otui index ab7fdca..eecea23 100644 --- a/modules/game_bot/ui/config.otui +++ b/modules/game_bot/ui/config.otui @@ -1,6 +1,8 @@ BotConfig < Panel id: botConfig height: 45 + margin-left: 2 + margin-right: 2 ComboBox id: list @@ -24,26 +26,29 @@ BotConfig < Panel color: #FF0000 Button - margin-top: 1 + margin-top: 2 id: add anchors.top: prev.bottom anchors.left: parent.left text: Add width: 56 - height: 20 - + height: 18 + text-offet: 0 2 + Button id: edit anchors.top: prev.top anchors.horizontalCenter: parent.horizontalCenter text: Edit width: 56 - height: 20 - + height: 18 + text-offet: 0 2 + Button id: remove anchors.top: prev.top anchors.right: parent.right text: Remove width: 56 - height: 20 \ No newline at end of file + height: 18 + text-offet: 0 2 \ No newline at end of file diff --git a/modules/game_bot/ui/panels.otui b/modules/game_bot/ui/panels.otui index e4cdc9b..e113947 100644 --- a/modules/game_bot/ui/panels.otui +++ b/modules/game_bot/ui/panels.otui @@ -1,6 +1,6 @@ DualScrollPanel < Panel - height: 55 - margin-top: 2 + height: 51 + margin-top: 3 SmallBotSwitch id: title @@ -36,6 +36,8 @@ DualScrollPanel < Panel anchors.right: parent.right anchors.top: scroll1.bottom margin-top: 3 + margin-left: 2 + margin-right: 1 SingleScrollItemPanel < Panel height: 45 @@ -66,8 +68,8 @@ SingleScrollItemPanel < Panel step: 1 DualScrollItemPanel < Panel - height: 37 - margin-top: 2 + height: 33 + margin-top: 3 BotItem id: item @@ -197,23 +199,26 @@ ItemAndSlotPanel < Panel height: 20 TwoItemsAndSlotPanel < Panel - height: 37 + height: 35 + margin-top: 4 BotItem id: item1 anchors.left: parent.left anchors.top: parent.top + margin-top: 1 BotItem id: item2 anchors.left: prev.right anchors.top: prev.top + margin-left: 1 SmallBotSwitch id: title anchors.left: prev.right anchors.right: parent.right - anchors.top: prev.top + anchors.top: parent.top text-align: center margin-left: 2 margin-top: 0 diff --git a/modules/game_cooldown/cooldown.lua b/modules/game_cooldown/cooldown.lua index bcfe909..374d1af 100644 --- a/modules/game_cooldown/cooldown.lua +++ b/modules/game_cooldown/cooldown.lua @@ -10,6 +10,7 @@ cooldownPanel = nil lastPlayer = nil cooldown = {} +cooldowns = {} groupCooldown = {} function init() @@ -43,6 +44,11 @@ function terminate() disconnect(g_game, { onGameStart = online, onSpellGroupCooldown = onSpellGroupCooldown, onSpellCooldown = onSpellCooldown }) + + for key, val in pairs(cooldowns) do + removeCooldown(key) + end + cooldowns = {} cooldownWindow:destroy() cooldownButton:destroy() @@ -110,6 +116,7 @@ function removeCooldown(progressRect) progressRect.icon:destroy() progressRect.icon = nil end + cooldowns[progressRect] = nil progressRect = nil end @@ -125,6 +132,7 @@ function turnOffCooldown(progressRect) particle:fill('parent') scheduleEvent(function() particle:destroy() end, 1000) -- hack until onEffectEnd]] + cooldowns[progressRect] = nil progressRect = nil end @@ -145,6 +153,7 @@ function updateCooldown(progressRect, duration) removeEvent(progressRect.event) progressRect.event = scheduleEvent(function() + if not progressRect.callback then return end progressRect.callback[ProgressCallback.update]() end, 100) else @@ -187,6 +196,7 @@ function onSpellCooldown(iconId, duration) end initCooldown(progressRect, updateFunc, finishFunc) cooldown[iconId] = true + cooldowns[progressRect] = true end function onSpellGroupCooldown(groupId, duration) diff --git a/modules/game_itemselector/itemselector.lua b/modules/game_itemselector/itemselector.lua index 80892d1..33f9437 100644 --- a/modules/game_itemselector/itemselector.lua +++ b/modules/game_itemselector/itemselector.lua @@ -38,7 +38,13 @@ function show(itemWidget) itemWidget:setItem(Item.create(window.item:getItemId(), window.item:getItemCount())) destroy() end - + local clearFunc = function() + window.item:setItemId(0) + window.item:setItemCount(0) + doneFunc() + end + + window.clearButton.onClick = clearFunc window.okButton.onClick = doneFunc window.cancelButton.onClick = destroy window.onEnter = doneFunc diff --git a/modules/game_itemselector/itemselector.otui b/modules/game_itemselector/itemselector.otui index ab733ba..0e114c6 100644 --- a/modules/game_itemselector/itemselector.otui +++ b/modules/game_itemselector/itemselector.otui @@ -49,6 +49,13 @@ ItemSelectorWindow < MainWindow text-align: center !text: tr("Count / SubType") + Button + id: clearButton + !text: tr('Clear') + anchors.bottom: parent.bottom + anchors.left: parent.left + width: 60 + Button id: okButton !text: tr('Ok') diff --git a/modules/game_market/market.lua b/modules/game_market/market.lua index ff9d7b5..1d2eb2f 100644 --- a/modules/game_market/market.lua +++ b/modules/game_market/market.lua @@ -1034,6 +1034,9 @@ function Market.decrementAmount() end function Market.updateCurrentItems() + if not categoryList or not categoryList:getCurrentOption() then + return + end local id = getMarketCategoryId(categoryList:getCurrentOption().text) if id == MarketCategory.MetaWeapons then id = getMarketCategoryId(subCategoryList:getCurrentOption().text) diff --git a/modules/game_textedit/textedit.lua b/modules/game_textedit/textedit.lua index 7e8cc25..67cbc51 100644 --- a/modules/game_textedit/textedit.lua +++ b/modules/game_textedit/textedit.lua @@ -28,6 +28,7 @@ function show(text, options, callback) -- callback = function(newText) multiline = true / false width = number validation = text (regex) + range = {number, number} examples = {{name, text}, {name, text}} ]]-- if type(text) == 'userdata' then @@ -63,10 +64,13 @@ function show(text, options, callback) -- callback = function(newText) end -- functions local validate = function(text) - if type(options.validation) ~= 'string' or options.validation:len() == 0 then - return true + if type(options.range) == 'table' then + local value = tonumber(text) + return value >= options.range[1] and value <= options.range[2] + elseif type(options.validation) == 'string' and options.validation:len() > 0 then + return #regexMatch(text, options.validation) == 1 end - return #regexMatch(text, options.validation) == 1 + return true end local destroy = function() window:destroy() @@ -109,7 +113,7 @@ function show(text, options, callback) -- callback = function(newText) window.text:setCursorPos(-1) end end - if type(options.validation) == 'string' and options.validation:len() > 0 then + if type(options.range) == 'table' or (type(options.validation) == 'string' and options.validation:len() > 0) then window.buttons.ok:disable() window.text.onTextChange = function(widget, text) if validate(text) then diff --git a/otclient_dx.exe b/otclient_dx.exe index 8acb6fd..ed312f2 100644 Binary files a/otclient_dx.exe and b/otclient_dx.exe differ diff --git a/otclient_gl.exe b/otclient_gl.exe index 59cc173..280c5b2 100644 Binary files a/otclient_gl.exe and b/otclient_gl.exe differ diff --git a/otclient_linux b/otclient_linux index 5a9d8f1..dcb0c0c 100644 Binary files a/otclient_linux and b/otclient_linux differ diff --git a/pdb/pdb.7z b/pdb/pdb.7z index 40ff037..f2fac5b 100644 Binary files a/pdb/pdb.7z and b/pdb/pdb.7z differ