From 6dd9a5474999d09af95b0427950e75861d6b4c3f Mon Sep 17 00:00:00 2001 From: OTCv8 Date: Sat, 2 Nov 2019 08:43:11 +0100 Subject: [PATCH] Bot update - waypoints, attacking, looting --- modules/game_bot/bot.lua | 29 +- modules/game_bot/bot.otui | 8 +- modules/game_bot/defaultconfig.lua | 10 +- modules/game_bot/executor.lua | 47 +- modules/game_bot/functions/callbacks.lua | 16 +- modules/game_bot/functions/main.lua | 2 +- modules/game_bot/functions/map.lua | 19 +- modules/game_bot/functions/ui.lua | 1 + modules/game_bot/panels/attacking.lua | 974 +++++++++++++++++++- modules/game_bot/panels/basic.lua | 15 +- modules/game_bot/panels/looting.lua | 438 ++++++++- modules/game_bot/panels/waypoints.lua | 134 ++- modules/game_itemselector/itemselector.lua | 3 - modules/game_itemselector/itemselector.otui | 4 +- modules/gamelib/ui/uiitem.lua | 3 - 15 files changed, 1640 insertions(+), 63 deletions(-) diff --git a/modules/game_bot/bot.lua b/modules/game_bot/bot.lua index 64d8ea8..4493612 100644 --- a/modules/game_bot/bot.lua +++ b/modules/game_bot/bot.lua @@ -42,7 +42,7 @@ function init() connect(Creature, { onAppear = botCreatureAppear, - onDisappear =botCreatureDisappear, + onDisappear = botCreatureDisappear, onPositionChange = botCreaturePositionChange, onHealthPercentChange = botCraetureHealthPercentChange }) @@ -50,6 +50,9 @@ function init() onPositionChange = botCreaturePositionChange, onHealthPercentChange = botCraetureHealthPercentChange }) + connect(Container, { onOpen = botContainerOpen, + onClose = botContainerClose, + onUpdateItem = botContainerUpdateItem }) botConfigFile = g_configs.create("/bot.otml") local config = botConfigFile:get("config") @@ -165,6 +168,9 @@ function terminate() onPositionChange = botCreaturePositionChange, onHealthPercentChange = botCraetureHealthPercentChange }) + disconnect(Container, { onOpen = botContainerOpen, + onClose = botContainerClose, + onUpdateItem = botContainerUpdateItem }) removeEvent(executeEvent) removeEvent(checkMsgsEvent) @@ -324,6 +330,12 @@ function clearConfig() botMessages:destroyChildren() botMessages:updateLayout() + + for i, widget in pairs(g_ui.getRootWidget():getChildren()) do + if widget.botWidget then + widget:destroy() + end + end end function refreshConfig() @@ -479,3 +491,18 @@ function botOnUseWith(pos, itemId, target, subType) if compiledConfig == nil then return false end safeBotCall(function() compiledConfig.callbacks.onUseWith(pos, itemId, target, subType) end) end + +function botContainerOpen(container, previousContainer) + if compiledConfig == nil then return false end + safeBotCall(function() compiledConfig.callbacks.onContainerOpen(container, previousContainer) end) +end + +function botContainerClose(container) + if compiledConfig == nil then return false end + safeBotCall(function() compiledConfig.callbacks.onContainerClose(container) end) +end + +function botContainerUpdateItem(container, slot, item) + if compiledConfig == nil then return false end + safeBotCall(function() compiledConfig.callbacks.onContainerUpdateItem(container, slot, item) end) +end diff --git a/modules/game_bot/bot.otui b/modules/game_bot/bot.otui index 30d4a3f..c62300d 100644 --- a/modules/game_bot/bot.otui +++ b/modules/game_bot/bot.otui @@ -15,16 +15,15 @@ BotLabel < Label text-align: center text-wrap: true -BotItem < UIItem +BotItem < Item virtual: true - size: 32 32 - border: 1 black &selectable: true BotTextEdit < TextEdit @onClick: modules.game_textedit.show(self) text-align: center multiline: false + focusable: false BotSeparator < HorizontalSeparator margin-top: 5 @@ -40,8 +39,7 @@ CaveBotLabel < Label focusable: true $focus: - background-color: #00000033 - color: #ffffff + background-color: #00000055 MiniWindow id: botWindow diff --git a/modules/game_bot/defaultconfig.lua b/modules/game_bot/defaultconfig.lua index d26b58c..3835179 100644 --- a/modules/game_bot/defaultconfig.lua +++ b/modules/game_bot/defaultconfig.lua @@ -15,22 +15,28 @@ local battleTab = addTab("Battle") local caveTab = addTab("Cave") local toolsTab = addTab("Tools") +Panels.Health(battleTab) Panels.HealthItem(battleTab) Panels.ManaItem(battleTab) +Panels.AttackSpell(battleTab) 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) --#macros +addSeparator("sep1") local helloLabel = addLabel("helloLabel", "") macro(1000, "example macro (time)", nil, function() helloLabel:setText("Time from start: " .. now) end) -macro(1000, "this macro does nothing", nil, function() +macro(1000, "this macro does nothing", "f7", function() end, toolsTab) @@ -40,7 +46,7 @@ hotkey("f5", "example hotkey", function() info("Wow, you clicked f5 hotkey") end) -singlehotkey("ctrl+f6", "example hotkey2", function() +singlehotkey("ctrl+f6", "singlehotkey", function() info("Wow, you clicked f6 singlehotkey") end) diff --git a/modules/game_bot/executor.lua b/modules/game_bot/executor.lua index e6c43db..0c8ad02 100644 --- a/modules/game_bot/executor.lua +++ b/modules/game_bot/executor.lua @@ -25,7 +25,10 @@ function executeBot(config, storage, tabs, msgCallback, saveConfigCallback) onCreaturePositionChange = {}, onCreatureHealthPercentChange = {}, onUse = {}, - onUseWith = {} + onUseWith = {}, + onContainerOpen = {}, + onContainerClose = {}, + onContainerUpdateItem = {} } -- basic functions & classes @@ -48,8 +51,13 @@ function executeBot(config, storage, tabs, msgCallback, saveConfigCallback) context.g_game = g_game context.g_map = g_map context.g_ui = g_ui + context.g_platform = g_platform + context.g_sounds = g_sounds + context.g_window = g_window + context.g_mouse = g_mouse + context.StaticText = StaticText - context.Position = Position + context.Config = Config context.HTTP = HTTP -- log functions @@ -80,14 +88,24 @@ function executeBot(config, storage, tabs, msgCallback, saveConfigCallback) for i, macro in ipairs(context._macros) do if macro.lastExecution + macro.timeout <= context.now and (macro.name == nil or macro.name:len() < 1 or context.storage._macros[macro.name]) then - if macro.callback() then - macro.lastExecution = context.now + local status, result = pcall(function() + if macro.callback() then + macro.lastExecution = context.now + end + end) + if not status then + context.error("Macro: " .. macro.name .. " execution error: " .. result) end end end while #context._scheduler > 0 and context._scheduler[1].execution <= g_clock.millis() do - context._scheduler[1].callback() + local status, result = pcall(function() + context._scheduler[1].callback() + end) + if not status then + context.error("Schedule execution error: " .. result) + end table.remove(context._scheduler, 1) end end, @@ -161,7 +179,7 @@ function executeBot(config, storage, tabs, msgCallback, saveConfigCallback) onCreatureDisappear = function(creature) for i, callback in ipairs(context._callbacks.onCreatureDisappear) do callback(creature) - end + end end, onCreaturePositionChange = function(creature, newPos, oldPos) for i, callback in ipairs(context._callbacks.onCreaturePositionChange) do @@ -182,7 +200,22 @@ function executeBot(config, storage, tabs, msgCallback, saveConfigCallback) for i, callback in ipairs(context._callbacks.onUseWith) do callback(pos, itemId, target, subType) end - end + end, + onContainerOpen = function(container, previousContainer) + for i, callback in ipairs(context._callbacks.onContainerOpen) do + callback(container, previousContainer) + end + end, + onContainerClose = function(container) + for i, callback in ipairs(context._callbacks.onContainerClose) do + callback(container) + end + end, + onContainerUpdateItem = function(container, slot, item) + for i, callback in ipairs(context._callbacks.onContainerUpdateItem) do + callback(container, slot, item) + end + end } } end \ No newline at end of file diff --git a/modules/game_bot/functions/callbacks.lua b/modules/game_bot/functions/callbacks.lua index a9ae0b0..1663e40 100644 --- a/modules/game_bot/functions/callbacks.lua +++ b/modules/game_bot/functions/callbacks.lua @@ -79,8 +79,22 @@ context.onUseWith = function(callback) return context.callback("onUseWith", callback) end +-- onContainerOpen -- callback = function(container, previousContainer) +context.onContainerOpen = function(callback) + return context.callback("onContainerOpen", callback) +end --- custom callbacks +-- onContainerUpdateItem -- callback = function(container) +context.onContainerClose = function(callback) + return context.callback("onContainerClose", callback) +end + +-- onContainerUpdateItem -- callback = function(container, slot, item) +context.onContainerUpdateItem = function(callback) + return context.callback("onContainerUpdateItem", callback) +end + +-- CUSTOM CALLBACKS -- listen(name, callback) -- callback = function(text, channelId, pos) context.listen = function(name, callback) diff --git a/modules/game_bot/functions/main.lua b/modules/game_bot/functions/main.lua index a6d7070..4a87464 100644 --- a/modules/game_bot/functions/main.lua +++ b/modules/game_bot/functions/main.lua @@ -36,7 +36,7 @@ context.macro = function(timeout, name, hotkey, callback, parent) local switch = nil if name:len() > 0 then if context.storage._macros[name] == nil then - context.storage._macros[name] = true + context.storage._macros[name] = false end switch = context._addMacroSwitch(name, hotkey, parent) end diff --git a/modules/game_bot/functions/map.lua b/modules/game_bot/functions/map.lua index b1246f6..e8b39d7 100644 --- a/modules/game_bot/functions/map.lua +++ b/modules/game_bot/functions/map.lua @@ -10,6 +10,19 @@ context.getSpectators = function(multifloor) return g_map.getSpectators(context.player:getPosition(), multifloor) end +context.getCreatureById = function(id, multifloor) + if type(id) ~= 'number' then return nil end + if multifloor ~= true then + multifloor = false + end + for i, spec in ipairs(g_map.getSpectators(context.player:getPosition(), multifloor)) do + if spec:getId() == id then + return spec + end + end + return nil +end + context.getCreatureByName = function(name, multifloor) if not name then return nil end name = name:lower() @@ -70,6 +83,10 @@ context.autoWalk = function(destination, maxDist, ignoreFields, ignoreCreatures) if #path < 1 then return false end - g_game.autoWalk(path, context.player:getPosition()) + if g_game.getFeature(GameNewWalking) then + g_game.autoWalk(path, context.player:getPosition()) + else + g_game.autoWalk(path, {x=0,y=0,z=0}) + end return true end \ No newline at end of file diff --git a/modules/game_bot/functions/ui.lua b/modules/game_bot/functions/ui.lua index 7ef0b6c..2120ff6 100644 --- a/modules/game_bot/functions/ui.lua +++ b/modules/game_bot/functions/ui.lua @@ -5,6 +5,7 @@ context.setupUI = function(otml, parent) parent = context.panel end local widget = g_ui.loadUIFromString(otml, parent) + widget.botWidget = true return widget end diff --git a/modules/game_bot/panels/attacking.lua b/modules/game_bot/panels/attacking.lua index d277e69..2cd1a92 100644 --- a/modules/game_bot/panels/attacking.lua +++ b/modules/game_bot/panels/attacking.lua @@ -1,8 +1,334 @@ local context = G.botContext local Panels = context.Panels +Panels.MonsterEditor = function(monster, config, callback, parent) + local otherWindow = g_ui.getRootWidget():getChildById('monsterEditor') + if otherWindow then + otherWindow:destory() + end + + local window = context.setupUI([[ +MainWindow + id: monsterEditor + size: 400 300 + !text: tr("Edit monster") + + Label + id: info + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + text-align: center + text: Use monster name * for any other monster not on the list + + TextEdit + id: name + anchors.top: prev.bottom + anchors.left: parent.left + anchors.right: parent.right + margin-left: 100 + margin-top: 5 + multiline: false + + Label + anchors.verticalCenter: prev.verticalCenter + anchors.left: parent.left + text: Monster name: + + Label + id: priorityText + anchors.left: parent.left + anchors.right: parent.horizontalCenter + anchors.top: prev.bottom + margin-right: 10 + margin-top: 10 + text: Priority + text-align: center + + HorizontalScrollBar + id: priority + anchors.left: prev.left + anchors.right: prev.right + anchors.top: prev.bottom + margin-top: 5 + minimum: 0 + maximum: 10 + step: 1 + + Label + id: dangerText + anchors.left: parent.left + anchors.right: parent.horizontalCenter + anchors.top: prev.bottom + margin-right: 10 + margin-top: 10 + text: Danger + text-align: center + + HorizontalScrollBar + id: danger + anchors.left: prev.left + anchors.right: prev.right + anchors.top: prev.bottom + margin-top: 5 + minimum: 0 + maximum: 10 + step: 1 + + Label + id: distanceText + anchors.left: parent.left + anchors.right: parent.horizontalCenter + anchors.top: prev.bottom + margin-right: 10 + margin-top: 10 + text: Distance + text-align: center + + HorizontalScrollBar + id: distance + anchors.left: prev.left + anchors.right: prev.right + anchors.top: prev.bottom + margin-top: 5 + minimum: 0 + maximum: 5 + step: 1 + + Label + id: minHealthText + anchors.left: parent.left + anchors.right: parent.horizontalCenter + anchors.top: prev.bottom + margin-right: 10 + margin-top: 10 + text: Minimum Health + text-align: center + + HorizontalScrollBar + id: minHealth + anchors.left: prev.left + anchors.right: prev.right + anchors.top: prev.bottom + margin-top: 5 + minimum: 0 + maximum: 100 + step: 1 + + Label + id: maxHealthText + anchors.left: parent.left + anchors.right: parent.horizontalCenter + anchors.top: prev.bottom + margin-right: 10 + margin-top: 10 + text: Maximum Health + text-align: center + + HorizontalScrollBar + id: maxHealth + anchors.left: prev.left + anchors.right: prev.right + anchors.top: prev.bottom + margin-top: 5 + minimum: 0 + maximum: 100 + step: 1 + + BotSwitch + id: attack + anchors.left: parent.horizontalCenter + anchors.top: name.bottom + margin-left: 10 + margin-top: 10 + width: 55 + text: Attack + + BotSwitch + id: ignore + anchors.left: prev.right + anchors.top: name.bottom + margin-left: 5 + margin-top: 10 + width: 55 + text: Ignore + + BotSwitch + id: avoid + anchors.left: prev.right + anchors.top: name.bottom + margin-left: 5 + margin-top: 10 + width: 55 + text: Avoid + + BotSwitch + id: keepDistance + anchors.left: parent.horizontalCenter + anchors.right: parent.right + anchors.top: prev.bottom + margin-left: 10 + margin-top: 10 + text: Keep distance + + BotSwitch + id: avoidAttacks + anchors.left: parent.horizontalCenter + anchors.right: parent.right + anchors.top: prev.bottom + margin-left: 10 + margin-top: 10 + text: Avoid monster attacks + + BotSwitch + id: chase + anchors.left: parent.horizontalCenter + anchors.right: parent.right + anchors.top: prev.bottom + margin-left: 10 + margin-top: 10 + text: Chase when running away + + BotSwitch + id: loot + anchors.left: parent.horizontalCenter + anchors.right: parent.right + anchors.top: prev.bottom + margin-left: 10 + margin-top: 10 + text: Loot corpse + + Button + id: okButton + !text: tr('Ok') + anchors.bottom: parent.bottom + anchors.right: next.left + margin-right: 10 + width: 60 + + Button + id: cancelButton + !text: tr('Cancel') + anchors.bottom: parent.bottom + anchors.right: parent.right + width: 60 + ]], g_ui.getRootWidget()) + + local destroy = function() + window:destroy() + end + local doneFunc = function() + local monster = window.name:getText() + local config = { + priority = window.priority:getValue(), + danger = window.danger:getValue(), + distance = window.distance:getValue(), + minHealth = window.minHealth:getValue(), + maxHealth = window.maxHealth:getValue(), + attack = window.attack:isOn(), + ignore = window.ignore:isOn(), + avoid = window.avoid:isOn(), + keepDistance = window.keepDistance:isOn(), + avoidAttacks = window.avoidAttacks:isOn(), + chase = window.chase:isOn(), + loot = window.loot:isOn() + } + destroy() + callback(monster, config) + end + + window.okButton.onClick = doneFunc + window.cancelButton.onClick = destroy + window.onEnter = doneFunc + window.onEscape = destroy + + + window.priority.onValueChange = function(scroll, value) + window.priorityText:setText("Priority: " .. value) + end + window.danger.onValueChange = function(scroll, value) + window.dangerText:setText("Danger: " .. value) + end + window.distance.onValueChange = function(scroll, value) + window.distanceText:setText("Distance: " .. value) + end + window.minHealth.onValueChange = function(scroll, value) + window.minHealthText:setText("Minimum health: " .. value .. "%") + end + window.maxHealth.onValueChange = function(scroll, value) + window.maxHealthText:setText("Maximum health: " .. value .. "%") + end + + window.priority:setValue(config.priority or 1) + window.danger:setValue(config.danger or 1) + window.distance:setValue(config.distance or 1) + window.minHealth:setValue(1) -- to force onValueChange update + window.maxHealth:setValue(1) -- to force onValueChange update + window.minHealth:setValue(config.minHealth or 0) + window.maxHealth:setValue(config.maxHealth or 100) + + window.attack.onClick = function(widget) + if widget:isOn() then + return + end + widget:setOn(true) + window.ignore:setOn(false) + window.avoid:setOn(false) + end + window.ignore.onClick = function(widget) + if widget:isOn() then + return + end + widget:setOn(true) + window.attack:setOn(false) + window.avoid:setOn(false) + end + window.avoid.onClick = function(widget) + if widget:isOn() then + return + end + widget:setOn(true) + window.attack:setOn(false) + window.ignore:setOn(false) + end + + window.attack:setOn(config.attack) + window.ignore:setOn(config.ignore) + window.avoid:setOn(config.avoid) + if not window.attack:isOn() and not window.ignore:isOn() and not window.avoid:isOn() then + window.attack:setOn(true) + end + + window.keepDistance.onClick = function(widget) + widget:setOn(not widget:isOn()) + end + window.avoidAttacks.onClick = function(widget) + widget:setOn(not widget:isOn()) + end + window.chase.onClick = function(widget) + widget:setOn(not widget:isOn()) + end + window.loot.onClick = function(widget) + widget:setOn(not widget:isOn()) + end + + window.keepDistance:setOn(config.keepDistance) + window.avoidAttacks:setOn(config.avoidAttacks) + window.chase:setOn(config.chase) + window.loot:setOn(config.loot) + if config.loot == nil then + window.loot:setOn(true) + end + + window.name:setText(monster) + + window:show() + window:raise() + window:focus() +end + Panels.Attacking = function(parent) - context.setupUI([[ + local ui = context.setupUI([[ Panel id: attacking height: 150 @@ -12,8 +338,650 @@ Panel anchors.left: parent.left anchors.right: parent.right text: Attacking - -]], parent) + ComboBox + id: config + anchors.top: prev.bottom + anchors.left: parent.left + margin-top: 5 + text-offset: 3 0 + width: 130 + + Button + id: enableButton + anchors.top: prev.top + anchors.left: prev.right + anchors.right: parent.right + margin-left: 5 + + Button + margin-top: 1 + id: add + anchors.top: prev.bottom + anchors.left: parent.left + text: Add + width: 60 + height: 20 + + Button + id: edit + anchors.top: prev.top + anchors.horizontalCenter: parent.horizontalCenter + text: Edit + width: 60 + height: 20 + + Button + id: remove + anchors.top: prev.top + anchors.right: parent.right + text: Remove + width: 60 + height: 20 + + TextList + id: list + anchors.top: prev.bottom + anchors.left: parent.left + anchors.right: parent.right + vertical-scrollbar: listScrollbar + margin-right: 15 + margin-top: 2 + height: 60 + focusable: false + auto-focus: first + + VerticalScrollBar + id: listScrollbar + anchors.top: prev.top + anchors.bottom: prev.bottom + anchors.right: parent.right + pixels-scroll: true + step: 5 + + Button + margin-top: 2 + id: mAdd + anchors.top: prev.bottom + anchors.left: parent.left + text: Add + width: 60 + height: 20 + + Button + id: mEdit + anchors.top: prev.top + anchors.horizontalCenter: parent.horizontalCenter + text: Edit + width: 60 + height: 20 + + Button + id: mRemove + anchors.top: prev.top + anchors.right: parent.right + text: Remove + width: 60 + height: 20 + +]], parent) + + if type(context.storage.attacking) ~= "table" then + context.storage.attacking = {} + end + if type(context.storage.attacking.configs) ~= "table" then + context.storage.attacking.configs = {} + end + + local getConfigName = function(config) + local matches = regexMatch(config, [[name:\s*([^\n]*)$]]) + if matches[1] and matches[1][2] then + return matches[1][2]:trim() + end + return nil + end + + local commands = {} + local monsters = {} + local configName = nil + local refreshConfig = nil -- declared later + + local createNewConfig = function() + if not context.storage.attacking.activeConfig or not context.storage.attacking.configs[context.storage.attacking.activeConfig] then + return + end + + local newConfig = "" + if configName ~= nil then + newConfig = "name:" .. configName .. "\n" + end + for monster, config in pairs(monsters) do + newConfig = newConfig .. "\n" .. monster .. ":" .. json.encode(config) .. "\n" + end + + context.storage.attacking.configs[context.storage.attacking.activeConfig] = newConfig + refreshConfig() + end + + local parseConfig = function(config) + commands = {} + monsters = {} + configName = nil + + local matches = regexMatch(config, [[([^:^\n]+)(:?)([^\n]*)]]) + for i=1,#matches do + local command = matches[i][2] + local validation = (matches[i][3] == ":") + local text = matches[i][4] + if validation then + table.insert(commands, {command=command:lower(), text=text}) + elseif #commands > 0 then + commands[#commands].text = commands[#commands].text .. "\n" .. matches[i][1] + end + end + local labels = {} + for i, command in ipairs(commands) do + if commands[i].command == "name" then + configName = commands[i].text + else + local status, result = pcall(function() return json.decode(command.text) end) + if not status then + context.error("Invalid monster config: " .. commands[i].command .. ", error: " .. result) + print(command.text) + else + monsters[commands[i].command] = result + table.insert(labels, commands[i].command) + end + end + end + table.sort(labels) + for i, text in ipairs(labels) do + local label = g_ui.createWidget("CaveBotLabel", ui.list) + label:setText(text) + end + end + + local ignoreOnOptionChange = true + refreshConfig = function(scrollDown) + ignoreOnOptionChange = true + if context.storage.attacking.enabled then + ui.enableButton:setText("On") + ui.enableButton:setColor('#00AA00FF') + else + ui.enableButton:setText("Off") + ui.enableButton:setColor('#FF0000FF') + end + + ui.config:clear() + for i, config in ipairs(context.storage.attacking.configs) do + local name = getConfigName(config) + if not name then + name = "Unnamed config" + end + ui.config:addOption(name) + end + + if not context.storage.attacking.activeConfig and #context.storage.attacking.configs > 0 then + context.storage.attacking.activeConfig = 1 + end + + ui.list:destroyChildren() + + if context.storage.attacking.activeConfig and context.storage.attacking.configs[context.storage.attacking.activeConfig] then + ui.config:setCurrentIndex(context.storage.attacking.activeConfig) + parseConfig(context.storage.attacking.configs[context.storage.attacking.activeConfig]) + end + + context.saveConfig() + if scrollDown and ui.list:getLastChild() then + ui.list:focusChild(ui.list:getLastChild()) + end + + ignoreOnOptionChange = false + end + + + ui.config.onOptionChange = function(widget) + if not ignoreOnOptionChange then + context.storage.attacking.activeConfig = widget.currentIndex + refreshConfig() + end + end + ui.enableButton.onClick = function() + if not context.storage.attacking.activeConfig or not context.storage.attacking.configs[context.storage.attacking.activeConfig] then + return + end + context.storage.attacking.enabled = not context.storage.attacking.enabled + refreshConfig() + end + ui.add.onClick = function() + modules.game_textedit.multilineEditor("Target list editor", "name:Config name", function(newText) + table.insert(context.storage.attacking.configs, newText) + context.storage.attacking.activeConfig = #context.storage.attacking.configs + refreshConfig() + end) + end + ui.edit.onClick = function() + if not context.storage.attacking.activeConfig or not context.storage.attacking.configs[context.storage.attacking.activeConfig] then + return + end + modules.game_textedit.multilineEditor("Target list editor", context.storage.attacking.configs[context.storage.attacking.activeConfig], function(newText) + context.storage.attacking.configs[context.storage.attacking.activeConfig] = newText + refreshConfig() + end) + end + ui.remove.onClick = function() + if not context.storage.attacking.activeConfig or not context.storage.attacking.configs[context.storage.attacking.activeConfig] then + return + end + context.storage.attacking.enabled = false + table.remove(context.storage.attacking.configs, context.storage.attacking.activeConfig) + context.storage.attacking.activeConfig = 0 + refreshConfig() + end + + + ui.mAdd.onClick = function() + if not context.storage.attacking.activeConfig or not context.storage.attacking.configs[context.storage.attacking.activeConfig] then + return + end + Panels.MonsterEditor("", {}, function(name, config) + if name:len() > 0 then + monsters[name] = config + end + createNewConfig() + end, parent) + end + ui.mEdit.onClick = function() + if not context.storage.attacking.activeConfig or not context.storage.attacking.configs[context.storage.attacking.activeConfig] then + return + end + local monsterWidget = ui.list:getFocusedChild() + if not monsterWidget or not monsters[monsterWidget:getText()] then + return + end + Panels.MonsterEditor(monsterWidget:getText(), monsters[monsterWidget:getText()], function(name, config) + monsters[monsterWidget:getText()] = nil + if name:len() > 0 then + monsters[name] = config + end + createNewConfig() + end, parent) + end + ui.mRemove.onClick = function() + if not context.storage.attacking.activeConfig or not context.storage.attacking.configs[context.storage.attacking.activeConfig] then + return + end + local monsterWidget = ui.list:getFocusedChild() + if not monsterWidget or not monsters[monsterWidget:getText()] then + return + end + monsters[monsterWidget:getText()] = nil + createNewConfig() + end + + refreshConfig() + + local getMonsterConfig = function(monster) + if monsters[monster:getName():lower()] then + return monsters[monster:getName():lower()] + end + return monsters["*"] + end + + local calculatePriority = function(monster) + local priority = 0 + local config = getMonsterConfig(monster) + if not config or type(config.priority) ~= 'number' then + return -1 + end + if not config.attack then + return -1 + end + + local distance = context.getDistanceBetween(context.player:getPosition(), monster:getPosition()) + if distance > 10 then + return -1 + end + + local mpos = monster:getPosition() + local hasPath = false + for x=-1,1 do + for y=-1,1 do + local pathTo = context.findPath(context.player:getPosition(), {x=mpos.x-x, y=mpos.y-y, z=mpos.z}, 100, true, false) + if #pathTo > 0 then + hasPath = true + break + end + end + end + if distance > 2 and not hasPath then + return -1 + end + + if monster == g_game.getAttackingCreature() then + priority = priority + 10 + end + + if distance <= 4 then + priority = priority + 10 + end + if distance <= 2 then + priority = priority + 20 + end + + if monster:getHealthPercent() <= 10 then + priority = priority + 10 + end + if monster:getHealthPercent() <= 25 then + priority = priority + 10 + end + if monster:getHealthPercent() <= 50 then + priority = priority + 10 + end + if monster:getHealthPercent() <= 75 then + priority = priority + 10 + end + + priority = priority + config.priority * 10 + return priority + end + + local calculateMonsterDanger = function(monster) + local danger = 0 + local config = getMonsterConfig(monster) + if not config or type(config.danger) ~= 'number' then + return danger + end + danger = danger + config.danger + return danger + end + + local lastAttack = context.now + local lootContainers = {} + local lootTries = 0 + local openContainerRequest = 0 + local waitForLooting = 0 + + local goForLoot = function() + if #lootContainers == 0 or not context.storage.looting.enabled then + return false + end + + local pos = context.player:getPosition() + table.sort(lootContainers, function(pos1, pos2) + local dist1 = math.max(math.abs(pos.x-pos1.x), math.abs(pos.y-pos1.y)) + local dist2 = math.max(math.abs(pos.x-pos2.x), math.abs(pos.y-pos2.y)) + return dist1 < dist2 + end) + + local cpos = lootContainers[1] + if cpos.z ~= pos.z then + table.remove(lootContainers, 1) + return true + end + + if lootTries >= 5 then + lootTries = 0 + table.remove(lootContainers, 1) + return true + end + local dist = math.max(math.abs(pos.x-cpos.x), math.abs(pos.y-cpos.y)) + if dist <= 5 then + local tile = g_map.getTile(cpos) + if not tile then + table.remove(lootContainers, 1) + return true + end + + local topItem = tile:getTopUseThing() + if not topItem:isContainer() then + table.remove(lootContainers, 1) + return true + end + + if dist <= 1 then + lootTries = lootTries + 1 + openContainerRequest = context.now + g_game.open(topItem) + waitForLooting = math.max(waitForLooting, context.now + 500) + return true + end + end + + if dist <= 20 then + if context.player:isWalking() then + return true + end + + lootTries = lootTries + 1 + if context.autoWalk(cpos, 100 + dist * 2) then + return true + end + + if context.autoWalk(cpos, 100 + dist * 2, true) then + return true + end + + for i=1,5 do + local cpos2 = {x=cpos.x + math.random(-1, 1),y = cpos.y + math.random(-1, 1), z = cpos.z} + if context.autoWalk(cpos2, 100 + dist * 2) then + return true + end + end + -- try again, ignore field + for i=1,5 do + local cpos2 = {x=cpos.x + math.random(-1, 1),y = cpos.y + math.random(-1, 1), z = cpos.z} + if context.autoWalk(cpos2, 100 + dist * 2, true) then + return true + end + end + + -- ignore fields and monsters + if context.autoWalk(cpos, 100 + dist * 2, true, true) then + return true + end + else + table.remove(lootContainers, 1) + return false + end + return true + end + + context.onCreatureDisappear(function(creature) + if not creature:isMonster() then + return + end + local pos = context.player:getPosition() + local tpos = creature:getPosition() + if tpos.z ~= pos.z then + return + end + + local config = getMonsterConfig(creature) + if not config or not config.loot then + return + end + local distance = math.max(math.abs(pos.x-tpos.x), math.abs(pos.y-tpos.y)) + if distance > 6 then + return + end + + local tile = g_map.getTile(tpos) + if not tile then + return + end + + local topItem = tile:getTopUseThing() + if not topItem:isContainer() then + return + end + + table.insert(lootContainers, tpos) + end) + + context.onContainerOpen(function(container, prevContainer) + lootTries = 0 + if not context.storage.attacking.enabled then + return + end + + if openContainerRequest + 500 > context.now and #lootContainers > 0 then + waitForLooting = math.max(waitForLooting, context.now + 1000 + container:getItemsCount() * 100) + table.remove(lootContainers, 1) + end + if prevContainer then + container.autoLooting = prevContainer.autoLooting + else + container.autoLooting = (openContainerRequest + 3000 > context.now) + end + end) + + context.macro(200, function() + if not context.storage.attacking.enabled then + return + end + + local attacking = nil + local following = nil + local attackingCandidate = g_game.getAttackingCreature() + local followingCandidate = g_game.getFollowingCreature() + local spectators = context.getSpectators() + local monsters = {} + local danger = 0 + + for i, spec in ipairs(spectators) do + if attackingCandidate and attackingCandidate:getId() == spec:getId() then + attacking = spec + end + if followingCandidate and followingCandidate:getId() == spec:getId() then + following = spec + end + if spec:isMonster() then + danger = danger + calculateMonsterDanger(spec) + spec.attackingPriority = calculatePriority(spec) + table.insert(monsters, spec) + end + end + + if following then + return + end + + if waitForLooting > context.now then + return + end + + if #monsters == 0 then + goForLoot() + return + end + + table.sort(monsters, function(a, b) + return a.attackingPriority > b.attackingPriority + end) + + local target = monsters[1] + if target.attackingPriority < 0 then + return + end + + local pos = context.player:getPosition() + local tpos = target:getPosition() + local config = getMonsterConfig(target) + local offsetX = pos.x - tpos.x + local offsetY = pos.y - tpos.y + + if target ~= attacking then + g_game.attack(target) + attacking = target + lastAttack = context.now + end + + -- proceed attack + if lastAttack + 15000 < context.now then + -- stop and attack again, just in case + g_game.cancelAttack() + g_game.attack(target) + lastAttack = context.now + return + end + + if danger < 8 then + -- low danger, go for loot first + if goForLoot() then + return + end + end + + local distance = math.max(math.abs(offsetX), math.abs(offsetY)) + if config.keepDistance then + if (distance == config.distance or distance == config.distance + 1) then + return + else + local bestDist = 10 + local bestPos = pos + + for i=1,5 do + local testPos = {x=pos.x + math.random(-3,3), y=pos.y + math.random(-3,3), z=pos.z} + local dist = math.abs(config.distance - math.max(math.abs(tpos.x - testPos.x), math.abs(tpos.y - testPos.y))) + if dist < bestDist then + local path = context.findPath(pos, testPos, 100, false, false) + if #path > 0 then + bestPos = testPos + bestDist = dist + end + end + end + if bestDist > 1 then + for i=1,10 do + local testPos = {x=pos.x + math.random(-4,4), y=pos.y + math.random(-4,4), z=pos.z} + local dist = math.abs(config.distance - math.max(math.abs(tpos.x - testPos.x), math.abs(tpos.y - testPos.y))) + if dist < bestDist then + local path = context.findPath(pos, testPos, 100, true, false) + if #path > 0 then + bestPos = testPos + bestDist = dist + end + end + end + end + if bestDist < 10 then + context.autoWalk(bestPos, 100, true, false) + context.delay(300) + end + end + return + end + + if config.avoidAttacks and distance <= 1 then + if (offsetX == 0 and offsetY ~= 0) then + if context.player:canWalk(Directions.East) then + g_game.walk(Directions.East) + elseif context.player:canWalk(Directions.West) then + g_game.walk(Directions.West) + end + elseif (offsetX ~= 0 and offsetY == 0) then + if context.player:canWalk(Directions.North) then + g_game.walk(Directions.North) + elseif context.player:canWalk(Directions.South) then + g_game.walk(Directions.South) + end + end + end + + if distance > 1 then + for x=-1,1 do + for y=-1,1 do + if context.autoWalk({x=tpos.x-x, y=tpos.y-y, z=tpos.z}, 100, true, false) then + return + end + end + end + if not context.autoWalk(tpos, 100, false, true) then + context.autoWalk(tpos, 100, true, true) + end + end + end) end diff --git a/modules/game_bot/panels/basic.lua b/modules/game_bot/panels/basic.lua index a936781..5571e6e 100644 --- a/modules/game_bot/panels/basic.lua +++ b/modules/game_bot/panels/basic.lua @@ -17,7 +17,7 @@ end Panels.ManaShield = function(parent) context.macro(500, "Auto ManaShield", nil, function() if not context.hasManaShield() then - if context.saySpell("utamo vita", 1000) then + if context.saySpell("utamo vita", 500) then context.delay(5000) end end @@ -323,3 +323,16 @@ Panels.Turning = function(parent) end Panels.AntiIdle = Panels.Turning +Panels.AttackSpell = function(parent) + context.macro(500, "Auto attack spell", nil, function() + local target = g_game.getAttackingCreature() + if target and context.getCreatureById(target:getId()) and context.storage.autoAttackText:len() > 0 then + if context.saySpell(context.storage.autoAttackText, 1000) then + context.delay(1000) + end + end + end, parent) + context.addTextEdit("autoAttackText", context.storage.autoAttackText or "exori vis", function(widget, text) + context.storage.autoAttackText = text + end, parent) +end diff --git a/modules/game_bot/panels/looting.lua b/modules/game_bot/panels/looting.lua index 7794d6f..07219ed 100644 --- a/modules/game_bot/panels/looting.lua +++ b/modules/game_bot/panels/looting.lua @@ -1,20 +1,448 @@ local context = G.botContext local Panels = context.Panels - Panels.Looting = function(parent) - context.setupUI([[ + local ui = context.setupUI([[ Panel id: looting - height: 150 + height: 190 BotLabel anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right text: Looting - -]], parent) + ComboBox + id: config + anchors.top: prev.bottom + anchors.left: parent.left + margin-top: 5 + text-offset: 3 0 + width: 130 + + Button + id: enableButton + anchors.top: prev.top + anchors.left: prev.right + anchors.right: parent.right + margin-left: 5 + + Button + margin-top: 1 + id: add + anchors.top: prev.bottom + anchors.left: parent.left + text: Add + width: 60 + height: 20 + + Button + id: edit + anchors.top: prev.top + anchors.horizontalCenter: parent.horizontalCenter + text: Edit + width: 60 + height: 20 + + Button + id: remove + anchors.top: prev.top + anchors.right: parent.right + text: Remove + width: 60 + height: 20 + + ScrollablePanel + id: items + anchors.top: prev.bottom + anchors.right: parent.right + anchors.left: parent.left + vertical-scrollbar: scrollBar + margin-right: 10 + margin-top: 2 + height: 70 + layout: + type: grid + cell-size: 34 34 + flow: true + + VerticalScrollBar + id: scrollBar + anchors.top: prev.top + anchors.bottom: prev.bottom + anchors.right: parent.right + step: 10 + pixels-scroll: true + + BotLabel + anchors.top: prev.bottom + anchors.left: parent.left + anchors.right: parent.right + margin-top: 4 + text: Loot Containers + + Panel + id: containers + anchors.top: prev.bottom + anchors.left: parent.left + anchors.right: parent.right + height: 33 + margin-top: 2 + + BotItem + id: item1 + anchors.top: parent.top + anchors.left: parent.left + margin-left: 3 + + BotItem + id: item2 + anchors.top: prev.top + anchors.left: prev.right + margin-left: 2 + + BotItem + id: item3 + anchors.top: prev.top + anchors.left: prev.right + margin-left: 2 + + BotItem + id: item4 + anchors.top: prev.top + anchors.left: prev.right + margin-left: 2 + + BotItem + id: item5 + anchors.top: prev.top + anchors.left: prev.right + margin-left: 2 + +]], parent) + + local lootContainers = { ui.containers.item1, ui.containers.item2, ui.containers.item3, ui.containers.item4, ui.containers.item5 } + + if type(context.storage.looting) ~= "table" then + context.storage.looting = {} + end + if type(context.storage.looting.configs) ~= "table" then + context.storage.looting.configs = {} + end + + local getConfigName = function(config) + local matches = regexMatch(config, [[name:\s*([^\n]*)$]]) + if matches[1] and matches[1][2] then + return matches[1][2]:trim() + end + return nil + end + + local items = {} + local itemsByKey = {} + local containers = {} + local commands = {} + local refreshConfig = nil -- declared later + + local createNewConfig = function(focusedWidget) + if not context.storage.looting.activeConfig or not context.storage.looting.configs[context.storage.looting.activeConfig] then + return + end + + local tmpItems = {} + local tmpContainers = {} + local focusIndex = 0 + + local newConfig = "" + for i, text in ipairs(commands) do + newConfig = newConfig .. text .. "\n" + end + for i=1,ui.items:getChildCount() do + local widget = ui.items:getChildByIndex(i) + if widget and widget:getItemId() >= 100 then + if tmpItems[widget:getItemId()] == nil then + tmpItems[widget:getItemId()] = 1 + newConfig = newConfig .. "\n" .. widget:getItemId() + end + end + if widget == focusedWidget then + focusIndex = i + end + end + for i, widget in ipairs(lootContainers) do + if widget:getItemId() >= 100 then + if tmpContainers[widget:getItemId()] == nil then + tmpContainers[widget:getItemId()] = 1 -- remove duplicates + newConfig = newConfig .. "\ncontainer:" .. widget:getItemId() + end + end + end + + context.storage.looting.configs[context.storage.looting.activeConfig] = newConfig + refreshConfig(focusIndex) + end + + local parseConfig = function(config) + items = {} + itemsByKey = {} + containers = {} + commands = {} + local matches = regexMatch(config, [[([^:^\n^\s]+)(:?)([^\n]*)]]) + for i=1,#matches do + local command = matches[i][2] + local validation = (matches[i][3] == ":") + local text = matches[i][4] + local commandAsNumber = tonumber(command) + local textAsNumber = tonumber(text) + if commandAsNumber and commandAsNumber >= 100 then + table.insert(items, commandAsNumber) + itemsByKey[commandAsNumber] = 1 + elseif command == "container" and validation and textAsNumber and textAsNumber >= 100 then + containers[textAsNumber] = 1 + elseif validation then + table.insert(commands, command .. ":" .. text) + end + end + + local itemsToShow = #items + 2 + if itemsToShow % 5 ~= 0 then + itemsToShow = itemsToShow + 5 - itemsToShow % 5 + end + if itemsToShow < 10 then + itemsToShow = 10 + end + + for i=1,itemsToShow do + local widget = g_ui.createWidget("BotItem", ui.items) + local itemId = 0 + if i <= #items then + itemId = items[i] + end + widget:setItemId(itemId) + widget.onItemChange = createNewConfig + end + + for i, widget in ipairs(lootContainers) do + widget:setItemId(0) + end + local containerIndex = 1 + for containerId, i in pairs(containers) do + if lootContainers[containerIndex] then + lootContainers[containerIndex]:setItemId(containerId) + end + containerIndex = containerIndex + 1 + end + for i, widget in ipairs(lootContainers) do + widget.onItemChange = createNewConfig + end + end + + local ignoreOnOptionChange = true + refreshConfig = function(focusIndex) + ignoreOnOptionChange = true + if context.storage.looting.enabled then + ui.enableButton:setText("On") + ui.enableButton:setColor('#00AA00FF') + else + ui.enableButton:setText("Off") + ui.enableButton:setColor('#FF0000FF') + end + + ui.config:clear() + for i, config in ipairs(context.storage.looting.configs) do + local name = getConfigName(config) + if not name then + name = "Unnamed config" + end + ui.config:addOption(name) + end + + if not context.storage.looting.activeConfig and #context.storage.looting.configs > 0 then + context.storage.looting.activeConfig = 1 + end + + ui.items:destroyChildren() + for i, widget in ipairs(lootContainers) do + widget.onItemChange = nil + widget:setItemId(0) + widget:setItemCount(0) + end + + if context.storage.looting.activeConfig and context.storage.looting.configs[context.storage.looting.activeConfig] then + ui.config:setCurrentIndex(context.storage.looting.activeConfig) + parseConfig(context.storage.looting.configs[context.storage.looting.activeConfig]) + end + + context.saveConfig() + if focusIndex and focusIndex > 0 and ui.items:getChildByIndex(focusIndex) then + ui.items:focusChild(ui.items:getChildByIndex(focusIndex)) + end + + ignoreOnOptionChange = false + end + + ui.config.onOptionChange = function(widget) + if not ignoreOnOptionChange then + context.storage.looting.activeConfig = widget.currentIndex + refreshConfig() + end + end + ui.enableButton.onClick = function() + if not context.storage.looting.activeConfig or not context.storage.looting.configs[context.storage.looting.activeConfig] then + return + end + context.storage.looting.enabled = not context.storage.looting.enabled + refreshConfig() + end + ui.add.onClick = function() + modules.game_textedit.multilineEditor("Looting editor", "name:Config name", function(newText) + table.insert(context.storage.looting.configs, newText) + context.storage.looting.activeConfig = #context.storage.looting.configs + refreshConfig() + end) + end + ui.edit.onClick = function() + if not context.storage.looting.activeConfig or not context.storage.looting.configs[context.storage.looting.activeConfig] then + return + end + modules.game_textedit.multilineEditor("Looting editor", context.storage.looting.configs[context.storage.looting.activeConfig], function(newText) + context.storage.looting.configs[context.storage.looting.activeConfig] = newText + refreshConfig() + end) + end + ui.remove.onClick = function() + if not context.storage.looting.activeConfig or not context.storage.looting.configs[context.storage.looting.activeConfig] then + return + end + context.storage.looting.enabled = false + table.remove(context.storage.looting.configs, context.storage.looting.activeConfig) + context.storage.looting.activeConfig = 0 + refreshConfig() + end + + refreshConfig() + + context.onContainerOpen(function(container, prevContainer) + if context.storage.attacking.enabled then + return + end + if prevContainer then + container.autoLooting = prevContainer.autoLooting + else + container.autoLooting = true + end + end) + + context.macro(200, function() + if not context.storage.looting.enabled then + return + end + local candidates = {} + local lootContainersCandidates = {} + for containerId, container in pairs(g_game.getContainers()) do + local containerItem = container:getContainerItem() + if container.autoLooting and container:getItemsCount() > 0 and (not containerItem or containers[containerItem:getId()] == nil) then + table.insert(candidates, container) + elseif containerItem and containers[containerItem:getId()] ~= nil then + table.insert(lootContainersCandidates, container) + end + end + if #lootContainersCandidates == 0 then + for slot = InventorySlotFirst, InventorySlotLast do + local item = context.getInventoryItem(slot) + if item and item:isContainer() and containers[item:getId()] ~= nil then + table.insert(lootContainersCandidates, item) + end + end + if #lootContainersCandidates > 0 then + -- try to open inventory backpack + local target = lootContainersCandidates[math.random(1,#lootContainersCandidates)] + g_game.open(target, nil) + context.delay(200) + end + return + end + + if #candidates == 0 then + return + end + + local container = candidates[math.random(1,#candidates)] + local nextContainers = {} + local foundItem = nil + for i, item in ipairs(container:getItems()) do + if item:isContainer() then + table.insert(nextContainers, item) + elseif itemsByKey[item:getId()] ~= nil then + foundItem = item + break + end + end + + -- found item to loot + if foundItem then + -- find backpack for it, first backpack with same items + for i, container in ipairs(lootContainersCandidates) do + if container:getItemsCount() < container:getCapacity() or foundItem:isStackable() then -- has space + for j, item in ipairs(container:getItems()) do + if item:getId() == foundItem:getId() then + if foundItem:isStackable() then + if item:getCount() ~= 100 then + g_game.move(foundItem, container:getSlotPosition(j), foundItem:getCount()) + return + end + else + g_game.move(foundItem, container:getSlotPosition(container:getItemsCount()), foundItem:getCount()) + return + end + end + end + end + end + -- now any backpack with empty slot + for i, container in ipairs(lootContainersCandidates) do + if container:getItemsCount() < container:getCapacity() then -- has space + g_game.move(foundItem, container:getSlotPosition(container:getItemsCount()), foundItem:getCount()) + return + end + end + + -- can't find backpack, try to open new + for i, container in ipairs(lootContainersCandidates) do + local candidates = {} + for j, item in ipairs(container:getItems()) do + if item:isContainer() and containers[item:getId()] ~= nil then + table.insert(candidates, item) + end + end + if #candidates > 0 then + g_game.open(candidates[math.random(1,#candidates)], container) + return + end + -- full, close it + g_game.close(container) + return + end + return + end + + -- open remaining containers + if #nextContainers == 0 then + return + end + local delay = 1 + for i=2,#nextContainers do + -- if more than 1 container, open them in new window + context.schedule(delay, function() + g_game.open(nextContainers[i], nil) + end) + delay = delay + 250 + end + context.schedule(delay, function() + g_game.open(nextContainers[1], container) + end) + context.delay(150 + delay) + end) end diff --git a/modules/game_bot/panels/waypoints.lua b/modules/game_bot/panels/waypoints.lua index 1af947c..09552d9 100644 --- a/modules/game_bot/panels/waypoints.lua +++ b/modules/game_bot/panels/waypoints.lua @@ -5,7 +5,7 @@ Panels.Waypoints = function(parent) local ui = context.setupUI([[ Panel id: waypoints - height: 213 + height: 203 BotLabel anchors.top: parent.top @@ -35,6 +35,7 @@ Panel anchors.left: parent.left text: Add width: 60 + height: 20 Button id: edit @@ -42,6 +43,7 @@ Panel anchors.horizontalCenter: parent.horizontalCenter text: Edit width: 60 + height: 20 Button id: remove @@ -49,6 +51,7 @@ Panel anchors.right: parent.right text: Remove width: 60 + height: 20 TextList id: list @@ -85,6 +88,7 @@ Panel text: Goto width: 61 margin-top: 1 + height: 20 Button id: wUse @@ -92,6 +96,7 @@ Panel anchors.left: prev.right text: Use width: 61 + height: 20 Button id: wUseWith @@ -99,6 +104,7 @@ Panel anchors.left: prev.right text: UseWith width: 61 + height: 20 Button id: wWait @@ -107,6 +113,7 @@ Panel text: Wait width: 61 margin-top: 1 + height: 20 Button id: wSay @@ -114,6 +121,7 @@ Panel anchors.left: prev.right text: Say width: 61 + height: 20 Button id: wFunction @@ -121,6 +129,7 @@ Panel anchors.left: prev.right text: Function width: 61 + height: 20 BotSwitch id: recording @@ -128,6 +137,7 @@ Panel anchors.left: parent.left anchors.right: parent.right text: Auto Recording + height: 20 ]], parent) @@ -157,6 +167,12 @@ Panel return true elseif command == "say" then return true + elseif command == "label" then + return true + elseif command == "gotolabel" then + return true + elseif command == "comment" then + return true elseif command == "function" then return true end @@ -169,7 +185,7 @@ Panel local parseConfig = function(config) commands = {} - local matches = regexMatch(config, [[\s*([^:^\n]+)(:?)([^\n]*)]]) + local matches = regexMatch(config, [[([^:^\n^\s]+)(:?)([^\n]*)]]) for i=1,#matches do local command = matches[i][2] local validation = (matches[i][3] == ":") @@ -178,7 +194,7 @@ Panel if validation then table.insert(commands, {command=command:lower(), text=text}) elseif #commands > 0 then - commands[#commands].text = commands[#commands].text .. "\n" .. command + commands[#commands].text = commands[#commands].text .. "\n" .. matches[i][1] end end end @@ -186,6 +202,18 @@ Panel for i=1,#commands do local label = g_ui.createWidget("CaveBotLabel", ui.list) label:setText(commands[i].command .. ":" .. commands[i].text) + if commands[i].command == "goto" then + label:setColor("green") + elseif commands[i].command == "label" then + label:setColor("yellow") + elseif commands[i].command == "comment" then + label:setText(commands[i].text) + label:setColor("white") + elseif commands[i].command == "use" or commands[i].command == "usewith" then + label:setColor("orange") + elseif commands[i].command == "gotolabel" then + label:setColor("red") + end end end @@ -218,7 +246,7 @@ Panel ui.list:destroyChildren() - if context.storage.cavebot.activeConfig then + if context.storage.cavebot.activeConfig and context.storage.cavebot.configs[context.storage.cavebot.activeConfig] then ui.config:setCurrentIndex(context.storage.cavebot.activeConfig) parseConfig(context.storage.cavebot.configs[context.storage.cavebot.activeConfig]) end @@ -247,7 +275,7 @@ Panel refreshConfig() end ui.add.onClick = function() - modules.game_textedit.multilineEditor("Waypoints editor", "name:Config name\n", function(newText) + modules.game_textedit.multilineEditor("Waypoints editor", "name:Config name\nlabel:start\n", function(newText) table.insert(context.storage.cavebot.configs, newText) context.storage.cavebot.activeConfig = #context.storage.cavebot.configs refreshConfig() @@ -287,12 +315,7 @@ Panel local newText = "" if newPos.z ~= oldPos.z then newText = "goto:" .. oldPos.x .. "," .. oldPos.y .. "," .. oldPos.z - if #commands > 0 then - local lastCommand = commands[#commands].command .. ":" .. commands[#commands].text - if lastCommand == newText then - return - end - end + newText = newText .. "\ngoto:" .. newPos.x .. "," .. newPos.y .. "," .. newPos.z stepsSincleLastPos = 0 else stepsSincleLastPos = stepsSincleLastPos + 1 @@ -319,7 +342,8 @@ Panel return end stepsSincleLastPos = 0 - newText = "use:" .. pos.x .. "," .. pos.y .. "," .. pos.z + local playerPos = context.player:getPosition() + newText = "goto:" .. playerPos.x .. "," .. playerPos.y .. "," .. playerPos.z .. "\nuse:" .. pos.x .. "," .. pos.y .. "," .. pos.z context.storage.cavebot.configs[context.storage.cavebot.activeConfig] = context.storage.cavebot.configs[context.storage.cavebot.activeConfig] .. "\n" .. newText refreshConfig(true) end) @@ -338,7 +362,8 @@ Panel return end stepsSincleLastPos = 0 - newText = "usewith:" .. itemId .. "," .. targetPos.x .. "," .. targetPos.y .. "," .. targetPos.z + local playerPos = context.player:getPosition() + newText = "goto:" .. playerPos.x .. "," .. playerPos.y .. "," .. playerPos.z .. "\nusewith:" .. itemId .. "," .. targetPos.x .. "," .. targetPos.y .. "," .. targetPos.z context.storage.cavebot.configs[context.storage.cavebot.activeConfig] = context.storage.cavebot.configs[context.storage.cavebot.activeConfig] .. "\n" .. newText refreshConfig(true) end) @@ -404,7 +429,7 @@ Panel if not context.storage.cavebot.activeConfig or not context.storage.cavebot.configs[context.storage.cavebot.activeConfig] then return end - modules.game_textedit.multilineEditor("Add function", "function(waypoints)\n --your lua code\n\n -- must return true to execute next command, othwerwise will run in loop till correct return\n return true\nend", function(newText) + modules.game_textedit.multilineEditor("Add function", "function(waypoints)\n -- your lua code, function is executed if previous goto was successful or is just after label\n\n -- must return true to execute next command, otherwise will run in loop till correct return\n return true\nend", function(newText) context.storage.cavebot.configs[context.storage.cavebot.activeConfig] = context.storage.cavebot.configs[context.storage.cavebot.activeConfig] .. "\nfunction:" .. newText refreshConfig(true) end) @@ -423,6 +448,12 @@ Panel end refreshConfig() + + local usedGotoLabel = false + local executeNextMacroCall = false + local commandExecutionNo = 0 + local lastGotoSuccesful = true + local lastOpenedContainer = 0 local functions = { enable = function() @@ -435,23 +466,52 @@ Panel end, refresh = function() refreshConfig() - end + end, + wait = function(peroid) + waitTo = context.now + peroid + end, + waitTo = function(timepoint) + waitTo = timepoint + end, + gotoLabel = function(name) + for i=1,ui.list:getChildCount() do + local command = commands[i] + if command and command.command == "label" and command.text == name then + ui.list:focusChild(ui.list:getChildByIndex(i)) + usedGotoLabel = true + lastGotoSuccesful = true + return true + end + end + end } - local executeNextMacroCall = false - local commandExecutionNo = 0 - local lastGotoSuccesful = true + context.onContainerOpen(function(container) + if container:getItemsCount() > 0 then + lastOpenedContainer = context.now + end + end) + context.macro(250, function() if not context.storage.cavebot.enabled then return end - if context.player:isWalking() then + -- wait if walked or opened container recently + if context.player:isWalking() or lastOpenedContainer + 1000 > context.now then executeNextMacroCall = false return end + -- wait if attacking/following creature + local attacking = g_game.getAttackingCreature() + local following = g_game.getFollowingCreature() + if (attacking and context.getCreatureById(attacking:getId())) or (following and context.getCreatureById(following:getId())) then + executeNextMacroCall = false + return + end + if not executeNextMacroCall then executeNextMacroCall = true return @@ -474,32 +534,45 @@ Panel end return end + + if commandIndex == 1 then + lastGotoSuccesful = true + end + if command.command == "goto" then local matches = regexMatch(command.text, [[([0-9]+)[^0-9]+([0-9]+)[^0-9]+([0-9]+)]]) if #matches == 1 and #matches[1] == 4 then local position = {x=tonumber(matches[1][2]), y=tonumber(matches[1][3]), z=tonumber(matches[1][4])} local distance = context.getDistanceBetween(position, context.player:getPosition()) - if distance > 0 and position.z == context.player:getPosition().z then + if distance > 100 or position.z ~= context.player:getPosition().z then + lastGotoSuccesful = false + elseif distance > 0 then commandExecutionNo = commandExecutionNo + 1 lastGotoSuccesful = false if commandExecutionNo <= 3 then -- try max 3 times if not context.autoWalk(position, 100 + distance * 2, commandExecutionNo > 1, false) then - context.autoWalk(position, 100 + distance * 2, true, true) + if commandExecutionNo > 1 then + context.autoWalk(position, 100 + distance * 2, true, true) -- ignore creatures + end context.delay(500) return end return elseif commandExecutionNo == 4 then -- try last time, location close to destination - position.x = position.x + math.random(-1, 1) - position.y = position.y + math.random(-1, 1) - if context.autoWalk(position, 100 + distance * 2, true) then - return + for i=1,3 do + position.x = position.x + math.random(-1, 1) + position.y = position.y + math.random(-1, 1) + if context.autoWalk(position, 100 + distance * 2, true) then + return + end end - elseif distance < 2 then + elseif distance <= 2 then lastGotoSuccesful = true + executeNextMacroCall = lastGotoSuccesful end else - lastGotoSuccesful = (position.z == context.player:getPosition().z) + lastGotoSuccesful = true + executeNextMacroCall = lastGotoSuccesful end else context.error("Waypoints: invalid use of goto function") @@ -550,6 +623,7 @@ Panel elseif command.command == "say" and lastGotoSuccesful then context.say(command.text) elseif command.command == "function" and lastGotoSuccesful then + usedGotoLabel = false local status, result = pcall(function() return assert(load("return " .. command.text, nil, nil, context))()(functions) end) @@ -557,7 +631,11 @@ Panel context.error("Waypoints function execution error:\n" .. result) context.delay(2500) end - if not result then + if not result or usedGotoLabel then + return + end + elseif command.command == "gotolabel" then + if functions.gotoLabel(command.text) then return end end diff --git a/modules/game_itemselector/itemselector.lua b/modules/game_itemselector/itemselector.lua index 2c79a6f..a3e6a5d 100644 --- a/modules/game_itemselector/itemselector.lua +++ b/modules/game_itemselector/itemselector.lua @@ -37,9 +37,6 @@ function show(itemWidget) local doneFunc = function() itemWidget:setItemId(window.item:getItemId()) itemWidget:setItemCount(window.item:getItemCount()) - if itemWidget.onItemChange then - itemWidget:onItemChange() - end destroy() end diff --git a/modules/game_itemselector/itemselector.otui b/modules/game_itemselector/itemselector.otui index bb257ba..ab733ba 100644 --- a/modules/game_itemselector/itemselector.otui +++ b/modules/game_itemselector/itemselector.otui @@ -19,8 +19,8 @@ ItemSelectorWindow < MainWindow margin-left: 5 padding-left: 5 width: 70 - minimum: 1 - maximum: 999999999 + minimum: 0 + maximum: 999999 focusable: true Label diff --git a/modules/gamelib/ui/uiitem.lua b/modules/gamelib/ui/uiitem.lua index d1cfc64..cbcab3a 100644 --- a/modules/gamelib/ui/uiitem.lua +++ b/modules/gamelib/ui/uiitem.lua @@ -31,9 +31,6 @@ function UIItem:onDrop(widget, mousePos, forced) if item:getSubType() > 1 then self:setItemSubType(item:getSubType()) end - if self.onItemChange then - self:onItemChange() - end return end