| @@ -0,0 +1,155 @@ | |||
| 1 | + | BattleIcon < UICheckBox | |
| 2 | + | size: 20 20 | |
| 3 | + | image-color: white | |
| 4 | + | image-rect: 0 0 20 20 | |
| 5 | + | ||
| 6 | + | $hover !disabled: | |
| 7 | + | color: #cccccc | |
| 8 | + | ||
| 9 | + | $!checked: | |
| 10 | + | image-clip: 0 0 20 20 | |
| 11 | + | ||
| 12 | + | $hover !checked: | |
| 13 | + | image-clip: 0 40 20 20 | |
| 14 | + | ||
| 15 | + | $checked: | |
| 16 | + | image-clip: 0 20 20 20 | |
| 17 | + | ||
| 18 | + | $hover checked: | |
| 19 | + | image-clip: 0 60 20 20 | |
| 20 | + | ||
| 21 | + | $disabled: | |
| 22 | + | image-color: #ffffff88 | |
| 23 | + | ||
| 24 | + | BattlePlayers < BattleIcon | |
| 25 | + | image-source: /images/game/battle/battle_players | |
| 26 | + | ||
| 27 | + | BattleNPCs < BattleIcon | |
| 28 | + | image-source: /images/game/battle/battle_npcs | |
| 29 | + | ||
| 30 | + | BattleMonsters < BattleIcon | |
| 31 | + | image-source: /images/game/battle/battle_monsters | |
| 32 | + | ||
| 33 | + | BattleSkulls < BattleIcon | |
| 34 | + | image-source: /images/game/battle/battle_skulls | |
| 35 | + | ||
| 36 | + | BattleParty < BattleIcon | |
| 37 | + | image-source: /images/game/battle/battle_party | |
| 38 | + | ||
| 39 | + | MiniWindow | |
| 40 | + | id: battleWindow | |
| 41 | + | !text: tr('Battle') | |
| 42 | + | height: 166 | |
| 43 | + | icon: /images/topbuttons/battle | |
| 44 | + | @onClose: modules.game_battle.onMiniWindowClose() | |
| 45 | + | &save: true | |
| 46 | + | &autoOpen: false | |
| 47 | + | ||
| 48 | + | Panel | |
| 49 | + | id: filterPanel | |
| 50 | + | margin-top: 26 | |
| 51 | + | anchors.top: parent.top | |
| 52 | + | anchors.left: parent.left | |
| 53 | + | anchors.right: miniwindowScrollBar.left | |
| 54 | + | height: 45 | |
| 55 | + | ||
| 56 | + | Panel | |
| 57 | + | id: buttons | |
| 58 | + | anchors.top: parent.top | |
| 59 | + | anchors.horizontalCenter: parent.horizontalCenter | |
| 60 | + | height: 20 | |
| 61 | + | width: 120 | |
| 62 | + | layout: | |
| 63 | + | type: horizontalBox | |
| 64 | + | spacing: 5 | |
| 65 | + | ||
| 66 | + | BattlePlayers | |
| 67 | + | id: hidePlayers | |
| 68 | + | !tooltip: tr('Hide players') | |
| 69 | + | @onSetup: if g_app.isMobile() then self:setChecked(true) end | |
| 70 | + | @onCheckChange: modules.game_battle.checkCreatures() | |
| 71 | + | ||
| 72 | + | BattleNPCs | |
| 73 | + | id: hideNPCs | |
| 74 | + | !tooltip: tr('Hide Npcs') | |
| 75 | + | @onSetup: if g_app.isMobile() then self:setChecked(true) end | |
| 76 | + | @onCheckChange: modules.game_battle.checkCreatures() | |
| 77 | + | ||
| 78 | + | BattleMonsters | |
| 79 | + | id: hideMonsters | |
| 80 | + | !tooltip: tr('Hide monsters') | |
| 81 | + | @onCheckChange: modules.game_battle.checkCreatures() | |
| 82 | + | ||
| 83 | + | BattleSkulls | |
| 84 | + | id: hideSkulls | |
| 85 | + | !tooltip: tr('Hide non-skull players') | |
| 86 | + | @onCheckChange: modules.game_battle.checkCreatures() | |
| 87 | + | ||
| 88 | + | BattleParty | |
| 89 | + | id: hideParty | |
| 90 | + | !tooltip: tr('Hide party members') | |
| 91 | + | @onSetup: if g_app.isMobile() then self:setChecked(true) end | |
| 92 | + | @onCheckChange: modules.game_battle.checkCreatures() | |
| 93 | + | ||
| 94 | + | Panel | |
| 95 | + | id: sortPanel | |
| 96 | + | anchors.top: prev.bottom | |
| 97 | + | anchors.left: parent.left | |
| 98 | + | anchors.right: parent.right | |
| 99 | + | height: 20 | |
| 100 | + | margin-top: 6 | |
| 101 | + | ||
| 102 | + | ComboBox | |
| 103 | + | id: sortTypeBox | |
| 104 | + | width: 90 | |
| 105 | + | anchors.top: parent.top | |
| 106 | + | anchors.left: prev.right | |
| 107 | + | anchors.horizontalCenter: parent.horizontalCenter | |
| 108 | + | margin-left: -31 | |
| 109 | + | ||
| 110 | + | ComboBox | |
| 111 | + | id: sortOrderBox | |
| 112 | + | width: 60 | |
| 113 | + | anchors.top: parent.top | |
| 114 | + | anchors.left: prev.right | |
| 115 | + | margin-left: 4 | |
| 116 | + | ||
| 117 | + | Panel | |
| 118 | + | height: 18 | |
| 119 | + | anchors.top: prev.bottom | |
| 120 | + | anchors.left: parent.left | |
| 121 | + | anchors.right: miniwindowScrollBar.left | |
| 122 | + | margin-top: 4 | |
| 123 | + | ||
| 124 | + | UIWidget | |
| 125 | + | id: toggleFilterButton | |
| 126 | + | anchors.top: prev.top | |
| 127 | + | width: 21 | |
| 128 | + | anchors.horizontalCenter: parent.horizontalCenter | |
| 129 | + | image-source: /images/ui/arrow_vertical | |
| 130 | + | image-rect: 0 0 21 12 | |
| 131 | + | image-clip: 21 0 21 12 | |
| 132 | + | @onClick: modules.game_battle.toggleFilterPanel() | |
| 133 | + | phantom: false | |
| 134 | + | ||
| 135 | + | HorizontalSeparator | |
| 136 | + | anchors.top: prev.top | |
| 137 | + | anchors.left: parent.left | |
| 138 | + | anchors.right: miniwindowScrollBar.left | |
| 139 | + | margin-right: 1 | |
| 140 | + | margin-top: 11 | |
| 141 | + | ||
| 142 | + | MiniWindowContents | |
| 143 | + | anchors.top: prev.bottom | |
| 144 | + | margin-top: 6 | |
| 145 | + | ||
| 146 | + | Panel | |
| 147 | + | id: battlePanel | |
| 148 | + | anchors.left: parent.left | |
| 149 | + | anchors.right: parent.right | |
| 150 | + | anchors.top: parent.top | |
| 151 | + | margin-top: 5 | |
| 152 | + | padding-right: 5 | |
| 153 | + | layout: | |
| 154 | + | type: verticalBox | |
| 155 | + | fit-children: true |
| @@ -0,0 +1,2 @@ | |||
| 1 | + | BattleButton < CreatureButton | |
| 2 | + | &isBattleButton: true |
| @@ -0,0 +1,685 @@ | |||
| 1 | + | botWindow = nil | |
| 2 | + | botButton = nil | |
| 3 | + | contentsPanel = nil | |
| 4 | + | editWindow = nil | |
| 5 | + | ||
| 6 | + | local checkEvent = nil | |
| 7 | + | ||
| 8 | + | local botStorage = {} | |
| 9 | + | local botStorageFile = nil | |
| 10 | + | local botWebSockets = {} | |
| 11 | + | local botMessages = nil | |
| 12 | + | local botTabs = nil | |
| 13 | + | local botExecutor = nil | |
| 14 | + | ||
| 15 | + | local configList = nil | |
| 16 | + | local enableButton = nil | |
| 17 | + | local executeEvent = nil | |
| 18 | + | local statusLabel = nil | |
| 19 | + | ||
| 20 | + | local configManagerUrl = "http://otclient.ovh/configs.php" | |
| 21 | + | ||
| 22 | + | function init() | |
| 23 | + | dofile("executor") | |
| 24 | + | ||
| 25 | + | g_ui.importStyle("ui/basic.otui") | |
| 26 | + | g_ui.importStyle("ui/panels.otui") | |
| 27 | + | g_ui.importStyle("ui/config.otui") | |
| 28 | + | g_ui.importStyle("ui/icons.otui") | |
| 29 | + | g_ui.importStyle("ui/container.otui") | |
| 30 | + | ||
| 31 | + | connect(g_game, { | |
| 32 | + | onGameStart = online, | |
| 33 | + | onGameEnd = offline, | |
| 34 | + | }) | |
| 35 | + | ||
| 36 | + | initCallbacks() | |
| 37 | + | ||
| 38 | + | botButton = modules.client_topmenu.addRightGameToggleButton('botButton', tr('Bot'), '/images/topbuttons/bot', toggle, false, 99999) | |
| 39 | + | ||
| 40 | + | botWindow = g_ui.loadUI('bot', modules.game_interface.getLeftPanel()) | |
| 41 | + | botWindow:setup() | |
| 42 | + | ||
| 43 | + | contentsPanel = botWindow.contentsPanel | |
| 44 | + | configList = contentsPanel.config | |
| 45 | + | enableButton = contentsPanel.enableButton | |
| 46 | + | statusLabel = contentsPanel.statusLabel | |
| 47 | + | botMessages = contentsPanel.messages | |
| 48 | + | botTabs = contentsPanel.botTabs | |
| 49 | + | botTabs:setContentWidget(contentsPanel.botPanel) | |
| 50 | + | ||
| 51 | + | editWindow = g_ui.displayUI('edit') | |
| 52 | + | editWindow:hide() | |
| 53 | + | ||
| 54 | + | if g_game.isOnline() then | |
| 55 | + | clear() | |
| 56 | + | online() | |
| 57 | + | end | |
| 58 | + | end | |
| 59 | + | ||
| 60 | + | function terminate() | |
| 61 | + | save() | |
| 62 | + | clear() | |
| 63 | + | ||
| 64 | + | disconnect(g_game, { | |
| 65 | + | onGameStart = online, | |
| 66 | + | onGameEnd = offline, | |
| 67 | + | }) | |
| 68 | + | ||
| 69 | + | terminateCallbacks() | |
| 70 | + | editWindow:destroy() | |
| 71 | + | ||
| 72 | + | botWindow:destroy() | |
| 73 | + | --botButton:destroy() | |
| 74 | + | end | |
| 75 | + | ||
| 76 | + | function clear() | |
| 77 | + | botExecutor = nil | |
| 78 | + | removeEvent(checkEvent) | |
| 79 | + | ||
| 80 | + | -- optimization, callback is not used when not needed | |
| 81 | + | g_game.enableTileThingLuaCallback(false) | |
| 82 | + | ||
| 83 | + | botTabs:clearTabs() | |
| 84 | + | botTabs:setOn(false) | |
| 85 | + | ||
| 86 | + | botMessages:destroyChildren() | |
| 87 | + | botMessages:updateLayout() | |
| 88 | + | ||
| 89 | + | for i, socket in pairs(botWebSockets) do | |
| 90 | + | g_http.cancel(socket) | |
| 91 | + | botWebSockets[i] = nil | |
| 92 | + | end | |
| 93 | + | ||
| 94 | + | for i, widget in pairs(g_ui.getRootWidget():getChildren()) do | |
| 95 | + | if widget.botWidget then | |
| 96 | + | widget:destroy() | |
| 97 | + | end | |
| 98 | + | end | |
| 99 | + | for i, widget in pairs(modules.game_interface.gameMapPanel:getChildren()) do | |
| 100 | + | if widget.botWidget then | |
| 101 | + | widget:destroy() | |
| 102 | + | end | |
| 103 | + | end | |
| 104 | + | ||
| 105 | + | local gameMapPanel = modules.game_interface.getMapPanel() | |
| 106 | + | if gameMapPanel then | |
| 107 | + | gameMapPanel:unlockVisibleFloor() | |
| 108 | + | end | |
| 109 | + | ||
| 110 | + | if g_sounds then | |
| 111 | + | g_sounds.getChannel(SoundChannels.Bot):stop() | |
| 112 | + | end | |
| 113 | + | end | |
| 114 | + | ||
| 115 | + | ||
| 116 | + | function refresh() | |
| 117 | + | if not g_game.isOnline() then return end | |
| 118 | + | save() | |
| 119 | + | clear() | |
| 120 | + | ||
| 121 | + | -- create bot dir | |
| 122 | + | if not g_resources.directoryExists("/bot") then | |
| 123 | + | g_resources.makeDir("/bot") | |
| 124 | + | if not g_resources.directoryExists("/bot") then | |
| 125 | + | return onError("Can't create bot directory in " .. g_resources.getWriteDir()) | |
| 126 | + | end | |
| 127 | + | end | |
| 128 | + | ||
| 129 | + | -- get list of configs | |
| 130 | + | createDefaultConfigs() | |
| 131 | + | local configs = g_resources.listDirectoryFiles("/bot", false, false) | |
| 132 | + | ||
| 133 | + | -- clean | |
| 134 | + | configList.onOptionChange = nil | |
| 135 | + | enableButton.onClick = nil | |
| 136 | + | configList:clearOptions() | |
| 137 | + | ||
| 138 | + | -- select active config based on settings | |
| 139 | + | local settings = g_settings.getNode('bot') or {} | |
| 140 | + | local index = g_game.getCharacterName() .. "_" .. g_game.getClientVersion() | |
| 141 | + | if settings[index] == nil then | |
| 142 | + | settings[index] = { | |
| 143 | + | enabled=false, | |
| 144 | + | config="" | |
| 145 | + | } | |
| 146 | + | end | |
| 147 | + | ||
| 148 | + | -- init list and buttons | |
| 149 | + | for i=1,#configs do | |
| 150 | + | configList:addOption(configs[i]) | |
| 151 | + | end | |
| 152 | + | configList:setCurrentOption(settings[index].config) | |
| 153 | + | if configList:getCurrentOption().text ~= settings[index].config then | |
| 154 | + | settings[index].config = configList:getCurrentOption().text | |
| 155 | + | settings[index].enabled = false | |
| 156 | + | end | |
| 157 | + | ||
| 158 | + | enableButton:setOn(settings[index].enabled) | |
| 159 | + | ||
| 160 | + | configList.onOptionChange = function(widget) | |
| 161 | + | settings[index].config = widget:getCurrentOption().text | |
| 162 | + | g_settings.setNode('bot', settings) | |
| 163 | + | g_settings.save() | |
| 164 | + | refresh() | |
| 165 | + | end | |
| 166 | + | ||
| 167 | + | enableButton.onClick = function(widget) | |
| 168 | + | settings[index].enabled = not settings[index].enabled | |
| 169 | + | g_settings.setNode('bot', settings) | |
| 170 | + | g_settings.save() | |
| 171 | + | refresh() | |
| 172 | + | end | |
| 173 | + | ||
| 174 | + | if not g_game.isOnline() or not settings[index].enabled then | |
| 175 | + | statusLabel:setOn(true) | |
| 176 | + | statusLabel:setText("Status: disabled\nPress off button to enable") | |
| 177 | + | return | |
| 178 | + | end | |
| 179 | + | ||
| 180 | + | local configName = settings[index].config | |
| 181 | + | ||
| 182 | + | -- storage | |
| 183 | + | botStorage = {} | |
| 184 | + | botStorageFile = "/bot/" .. configName .. "/storage.json" | |
| 185 | + | if g_resources.fileExists(botStorageFile) then | |
| 186 | + | local status, result = pcall(function() | |
| 187 | + | return json.decode(g_resources.readFileContents(botStorageFile)) | |
| 188 | + | end) | |
| 189 | + | if not status then | |
| 190 | + | return onError("Error while reading storage (" .. botStorageFile .. "). To fix this problem you can delete storage.json. Details: " .. result) | |
| 191 | + | end | |
| 192 | + | botStorage = result | |
| 193 | + | end | |
| 194 | + | ||
| 195 | + | -- run script | |
| 196 | + | local status, result = pcall(function() | |
| 197 | + | return executeBot(configName, botStorage, botTabs, message, save, refresh, botWebSockets) end | |
| 198 | + | ) | |
| 199 | + | if not status then | |
| 200 | + | return onError(result) | |
| 201 | + | end | |
| 202 | + | ||
| 203 | + | statusLabel:setOn(false) | |
| 204 | + | botExecutor = result | |
| 205 | + | check() | |
| 206 | + | end | |
| 207 | + | ||
| 208 | + | function save() | |
| 209 | + | if not botExecutor then | |
| 210 | + | return | |
| 211 | + | end | |
| 212 | + | ||
| 213 | + | local settings = g_settings.getNode('bot') or {} | |
| 214 | + | local index = g_game.getCharacterName() .. "_" .. g_game.getClientVersion() | |
| 215 | + | if settings[index] == nil then | |
| 216 | + | return | |
| 217 | + | end | |
| 218 | + | ||
| 219 | + | local status, result = pcall(function() | |
| 220 | + | return json.encode(botStorage, 2) | |
| 221 | + | end) | |
| 222 | + | if not status then | |
| 223 | + | return onError("Error while saving bot storage. Storage won't be saved. Details: " .. result) | |
| 224 | + | end | |
| 225 | + | ||
| 226 | + | if result:len() > 100 * 1024 * 1024 then | |
| 227 | + | return onError("Storage file is too big, above 100MB, it won't be saved") | |
| 228 | + | end | |
| 229 | + | ||
| 230 | + | g_resources.writeFileContents(botStorageFile, result) | |
| 231 | + | end | |
| 232 | + | ||
| 233 | + | function onMiniWindowClose() | |
| 234 | + | --botButton:setOn(false) | |
| 235 | + | end | |
| 236 | + | ||
| 237 | + | function toggle() | |
| 238 | + | if botButton:isOn() then | |
| 239 | + | botWindow:close() | |
| 240 | + | botButton:setOn(false) | |
| 241 | + | else | |
| 242 | + | botWindow:open() | |
| 243 | + | botButton:setOn(true) | |
| 244 | + | end | |
| 245 | + | end | |
| 246 | + | ||
| 247 | + | function online() | |
| 248 | + | --botButton:show() | |
| 249 | + | scheduleEvent(refresh, 20) | |
| 250 | + | end | |
| 251 | + | ||
| 252 | + | function offline() | |
| 253 | + | save() | |
| 254 | + | clear() | |
| 255 | + | --botButton:hide() | |
| 256 | + | editWindow:hide() | |
| 257 | + | end | |
| 258 | + | ||
| 259 | + | function onError(message) | |
| 260 | + | statusLabel:setOn(true) | |
| 261 | + | statusLabel:setText("Error:\n" .. message) | |
| 262 | + | g_logger.error("[BOT] " .. message) | |
| 263 | + | end | |
| 264 | + | ||
| 265 | + | function edit() | |
| 266 | + | local configs = g_resources.listDirectoryFiles("/bot", false, false) | |
| 267 | + | editWindow.manager.upload.config:clearOptions() | |
| 268 | + | for i=1,#configs do | |
| 269 | + | editWindow.manager.upload.config:addOption(configs[i]) | |
| 270 | + | end | |
| 271 | + | editWindow.manager.download.config:setText("") | |
| 272 | + | ||
| 273 | + | editWindow:show() | |
| 274 | + | editWindow:focus() | |
| 275 | + | editWindow:raise() | |
| 276 | + | end | |
| 277 | + | ||
| 278 | + | function createDefaultConfigs() | |
| 279 | + | local defaultConfigFiles = g_resources.listDirectoryFiles("default_configs", false, false) | |
| 280 | + | for i, config_name in ipairs(defaultConfigFiles) do | |
| 281 | + | if not g_resources.directoryExists("/bot/" .. config_name) then | |
| 282 | + | g_resources.makeDir("/bot/" .. config_name) | |
| 283 | + | if not g_resources.directoryExists("/bot/" .. config_name) then | |
| 284 | + | return onError("Can't create /bot/" .. config_name .. " directory in " .. g_resources.getWriteDir()) | |
| 285 | + | end | |
| 286 | + | end | |
| 287 | + | ||
| 288 | + | local defaultConfigFiles = g_resources.listDirectoryFiles("default_configs/" .. config_name, true, false) | |
| 289 | + | for i, file in ipairs(defaultConfigFiles) do | |
| 290 | + | local baseName = file:split("/") | |
| 291 | + | baseName = baseName[#baseName] | |
| 292 | + | if g_resources.directoryExists(file) then | |
| 293 | + | g_resources.makeDir("/bot/" .. config_name .. "/" .. baseName) | |
| 294 | + | if not g_resources.directoryExists("/bot/" .. config_name .. "/" .. baseName) then | |
| 295 | + | return onError("Can't create /bot/" .. config_name .. "/" .. baseName .. " directory in " .. g_resources.getWriteDir()) | |
| 296 | + | end | |
| 297 | + | local defaultConfigFiles2 = g_resources.listDirectoryFiles("default_configs/" .. config_name .. "/" .. baseName, true, false) | |
| 298 | + | for i, file in ipairs(defaultConfigFiles2) do | |
| 299 | + | local baseName2 = file:split("/") | |
| 300 | + | baseName2 = baseName2[#baseName2] | |
| 301 | + | local contents = g_resources.fileExists(file) and g_resources.readFileContents(file) or "" | |
| 302 | + | if contents:len() > 0 then | |
| 303 | + | g_resources.writeFileContents("/bot/" .. config_name .. "/" .. baseName .. "/" .. baseName2, contents) | |
| 304 | + | end | |
| 305 | + | end | |
| 306 | + | else | |
| 307 | + | local contents = g_resources.fileExists(file) and g_resources.readFileContents(file) or "" | |
| 308 | + | if contents:len() > 0 then | |
| 309 | + | g_resources.writeFileContents("/bot/" .. config_name .. "/" .. baseName, contents) | |
| 310 | + | end | |
| 311 | + | end | |
| 312 | + | end | |
| 313 | + | end | |
| 314 | + | end | |
| 315 | + | ||
| 316 | + | function uploadConfig() | |
| 317 | + | local config = editWindow.manager.upload.config:getCurrentOption().text | |
| 318 | + | local archive = compressConfig(config) | |
| 319 | + | if not archive then | |
| 320 | + | return displayErrorBox(tr("Config upload failed"), tr("Config %s is invalid (can't be compressed)", config)) | |
| 321 | + | end | |
| 322 | + | if archive:len() > 64 * 1024 then | |
| 323 | + | return displayErrorBox(tr("Config upload failed"), tr("Config %s is too big, maximum size is 64KB. Now it has %s KB.", config, math.floor(archive / 1024))) | |
| 324 | + | end | |
| 325 | + | ||
| 326 | + | local infoBox = displayInfoBox(tr("Uploading config"), tr("Uploading config %s. Please wait.", config)) | |
| 327 | + | ||
| 328 | + | HTTP.postJSON(configManagerUrl .. "?config=" .. config:gsub("%s+", "_"), archive, function(data, err) | |
| 329 | + | if infoBox then | |
| 330 | + | infoBox:destroy() | |
| 331 | + | end | |
| 332 | + | if err or data["error"] then | |
| 333 | + | return displayErrorBox(tr("Config upload failed"), tr("Error while upload config %s:\n%s", config, err or data["error"])) | |
| 334 | + | end | |
| 335 | + | displayInfoBox(tr("Succesful config upload"), tr("Config %s has been uploaded.\n%s", config, data["message"])) | |
| 336 | + | end) | |
| 337 | + | end | |
| 338 | + | ||
| 339 | + | function downloadConfig() | |
| 340 | + | local hash = editWindow.manager.download.config:getText() | |
| 341 | + | if hash:len() == 0 then | |
| 342 | + | return displayErrorBox(tr("Config download error"), tr("Enter correct config hash")) | |
| 343 | + | end | |
| 344 | + | local infoBox = displayInfoBox(tr("Downloading config"), tr("Downloading config with hash %s. Please wait.", hash)) | |
| 345 | + | HTTP.download(configManagerUrl .. "?hash=" .. hash, hash .. ".zip", function(path, checksum, err) | |
| 346 | + | if infoBox then | |
| 347 | + | infoBox:destroy() | |
| 348 | + | end | |
| 349 | + | if err then | |
| 350 | + | return displayErrorBox(tr("Config download error"), tr("Config with hash %s cannot be downloaded", hash)) | |
| 351 | + | end | |
| 352 | + | modules.client_textedit.show("", { | |
| 353 | + | title="Enter name for downloaded config", | |
| 354 | + | description="Config with hash " .. hash .. " has been downloaded. Enter name for new config.\nWarning: if config with same name already exist, it will be overwritten!", | |
| 355 | + | width=500 | |
| 356 | + | }, function(configName) | |
| 357 | + | decompressConfig(configName, "/downloads/" .. path) | |
| 358 | + | refresh() | |
| 359 | + | edit() | |
| 360 | + | end) | |
| 361 | + | end) | |
| 362 | + | end | |
| 363 | + | ||
| 364 | + | function compressConfig(configName) | |
| 365 | + | if not g_resources.directoryExists("/bot/" .. configName) then | |
| 366 | + | return onError("Config " .. configName .. " doesn't exist") | |
| 367 | + | end | |
| 368 | + | local forArchive = {} | |
| 369 | + | for _, file in ipairs(g_resources.listDirectoryFiles("/bot/" .. configName)) do | |
| 370 | + | local fullPath = "/bot/" .. configName .. "/" .. file | |
| 371 | + | if g_resources.fileExists(fullPath) then -- regular file | |
| 372 | + | forArchive[file] = g_resources.readFileContents(fullPath) | |
| 373 | + | else -- dir | |
| 374 | + | for __, file2 in ipairs(g_resources.listDirectoryFiles(fullPath)) do | |
| 375 | + | local fullPath2 = fullPath .. "/" .. file2 | |
| 376 | + | if g_resources.fileExists(fullPath2) then -- regular file | |
| 377 | + | forArchive[file .. "/" .. file2] = g_resources.readFileContents(fullPath2) | |
| 378 | + | end | |
| 379 | + | end | |
| 380 | + | end | |
| 381 | + | end | |
| 382 | + | return g_resources.createArchive(forArchive) | |
| 383 | + | end | |
| 384 | + | ||
| 385 | + | function decompressConfig(configName, archive) | |
| 386 | + | if g_resources.directoryExists("/bot/" .. configName) then | |
| 387 | + | g_resources.deleteFile("/bot/" .. configName) -- also delete dirs | |
| 388 | + | end | |
| 389 | + | local files = g_resources.decompressArchive(archive) | |
| 390 | + | g_resources.makeDir("/bot/" .. configName) | |
| 391 | + | if not g_resources.directoryExists("/bot/" .. configName) then | |
| 392 | + | return onError("Can't create /bot/" .. configName .. " directory in " .. g_resources.getWriteDir()) | |
| 393 | + | end | |
| 394 | + | ||
| 395 | + | for file, contents in pairs(files) do | |
| 396 | + | local split = file:split("/") | |
| 397 | + | split[#split] = nil -- remove file name | |
| 398 | + | local dirPath = "/bot/" .. configName | |
| 399 | + | for _, s in ipairs(split) do | |
| 400 | + | dirPath = dirPath .. "/" .. s | |
| 401 | + | if not g_resources.directoryExists(dirPath) then | |
| 402 | + | g_resources.makeDir(dirPath) | |
| 403 | + | if not g_resources.directoryExists(dirPath) then | |
| 404 | + | return onError("Can't create " .. dirPath .. " directory in " .. g_resources.getWriteDir()) | |
| 405 | + | end | |
| 406 | + | end | |
| 407 | + | end | |
| 408 | + | g_resources.writeFileContents("/bot/" .. configName .. file, contents) | |
| 409 | + | end | |
| 410 | + | end | |
| 411 | + | ||
| 412 | + | -- Executor | |
| 413 | + | function message(category, msg) | |
| 414 | + | local widget = g_ui.createWidget('BotLabel', botMessages) | |
| 415 | + | widget.added = g_clock.millis() | |
| 416 | + | if category == 'error' then | |
| 417 | + | widget:setText(msg) | |
| 418 | + | widget:setColor("red") | |
| 419 | + | g_logger.error("[BOT] " .. msg) | |
| 420 | + | elseif category == 'warn' then | |
| 421 | + | widget:setText(msg) | |
| 422 | + | widget:setColor("yellow") | |
| 423 | + | g_logger.warning("[BOT] " .. msg) | |
| 424 | + | elseif category == 'info' then | |
| 425 | + | widget:setText(msg) | |
| 426 | + | widget:setColor("white") | |
| 427 | + | g_logger.info("[BOT] " .. msg) | |
| 428 | + | end | |
| 429 | + | ||
| 430 | + | if botMessages:getChildCount() > 5 then | |
| 431 | + | botMessages:getFirstChild():destroy() | |
| 432 | + | end | |
| 433 | + | end | |
| 434 | + | ||
| 435 | + | function check() | |
| 436 | + | removeEvent(checkEvent) | |
| 437 | + | if not botExecutor then | |
| 438 | + | return | |
| 439 | + | end | |
| 440 | + | ||
| 441 | + | checkEvent = scheduleEvent(check, 10) | |
| 442 | + | ||
| 443 | + | local status, result = pcall(function() | |
| 444 | + | return botExecutor.script() | |
| 445 | + | end) | |
| 446 | + | if not status then | |
| 447 | + | botExecutor = nil -- critical | |
| 448 | + | return onError(result) | |
| 449 | + | end | |
| 450 | + | ||
| 451 | + | -- remove old messages | |
| 452 | + | local widget = botMessages:getFirstChild() | |
| 453 | + | if widget and widget.added + 5000 < g_clock.millis() then | |
| 454 | + | widget:destroy() | |
| 455 | + | end | |
| 456 | + | end | |
| 457 | + | ||
| 458 | + | -- Callbacks | |
| 459 | + | function initCallbacks() | |
| 460 | + | connect(rootWidget, { | |
| 461 | + | onKeyDown = botKeyDown, | |
| 462 | + | onKeyUp = botKeyUp, | |
| 463 | + | onKeyPress = botKeyPress | |
| 464 | + | }) | |
| 465 | + | ||
| 466 | + | connect(g_game, { | |
| 467 | + | onTalk = botOnTalk, | |
| 468 | + | onTextMessage = botOnTextMessage, | |
| 469 | + | onUse = botOnUse, | |
| 470 | + | onUseWith = botOnUseWith, | |
| 471 | + | onChannelList = botChannelList, | |
| 472 | + | onOpenChannel = botOpenChannel, | |
| 473 | + | onCloseChannel = botCloseChannel, | |
| 474 | + | onChannelEvent = botChannelEvent | |
| 475 | + | }) | |
| 476 | + | ||
| 477 | + | connect(Tile, { | |
| 478 | + | onAddThing = botAddThing, | |
| 479 | + | onRemoveThing = botRemoveThing | |
| 480 | + | }) | |
| 481 | + | ||
| 482 | + | connect(Creature, { | |
| 483 | + | onAppear = botCreatureAppear, | |
| 484 | + | onDisappear = botCreatureDisappear, | |
| 485 | + | onPositionChange = botCreaturePositionChange, | |
| 486 | + | onHealthPercentChange = botCraetureHealthPercentChange, | |
| 487 | + | onTurn = botCreatureTurn | |
| 488 | + | }) | |
| 489 | + | ||
| 490 | + | connect(LocalPlayer, { | |
| 491 | + | onPositionChange = botCreaturePositionChange, | |
| 492 | + | onHealthPercentChange = botCraetureHealthPercentChange | |
| 493 | + | }) | |
| 494 | + | ||
| 495 | + | connect(Container, { | |
| 496 | + | onOpen = botContainerOpen, | |
| 497 | + | onClose = botContainerClose, | |
| 498 | + | onUpdateItem = botContainerUpdateItem | |
| 499 | + | }) | |
| 500 | + | ||
| 501 | + | connect(g_map, { | |
| 502 | + | onMissle = botOnMissle, | |
| 503 | + | onAnimatedText = botOnAnimatedText, | |
| 504 | + | onStaticText = botOnStaticText | |
| 505 | + | }) | |
| 506 | + | end | |
| 507 | + | ||
| 508 | + | function terminateCallbacks() | |
| 509 | + | disconnect(rootWidget, { | |
| 510 | + | onKeyDown = botKeyDown, | |
| 511 | + | onKeyUp = botKeyUp, | |
| 512 | + | onKeyPress = botKeyPress | |
| 513 | + | }) | |
| 514 | + | ||
| 515 | + | disconnect(g_game, { | |
| 516 | + | onTalk = botOnTalk, | |
| 517 | + | onTextMessage = botOnTextMessage, | |
| 518 | + | onUse = botOnUse, | |
| 519 | + | onUseWith = botOnUseWith, | |
| 520 | + | onChannelList = botChannelList, | |
| 521 | + | onOpenChannel = botOpenChannel, | |
| 522 | + | onCloseChannel = botCloseChannel, | |
| 523 | + | onChannelEvent = botChannelEvent | |
| 524 | + | }) | |
| 525 | + | ||
| 526 | + | disconnect(Tile, { | |
| 527 | + | onAddThing = botAddThing, | |
| 528 | + | onRemoveThing = botRemoveThing | |
| 529 | + | }) | |
| 530 | + | ||
| 531 | + | disconnect(Creature, { | |
| 532 | + | onAppear = botCreatureAppear, | |
| 533 | + | onDisappear = botCreatureDisappear, | |
| 534 | + | onPositionChange = botCreaturePositionChange, | |
| 535 | + | onHealthPercentChange = botCraetureHealthPercentChange, | |
| 536 | + | onTurn = botCreatureTurn | |
| 537 | + | }) | |
| 538 | + | ||
| 539 | + | disconnect(LocalPlayer, { | |
| 540 | + | onPositionChange = botCreaturePositionChange, | |
| 541 | + | onHealthPercentChange = botCraetureHealthPercentChange | |
| 542 | + | }) | |
| 543 | + | ||
| 544 | + | disconnect(Container, { | |
| 545 | + | onOpen = botContainerOpen, | |
| 546 | + | onClose = botContainerClose, | |
| 547 | + | onUpdateItem = botContainerUpdateItem | |
| 548 | + | }) | |
| 549 | + | ||
| 550 | + | disconnect(g_map, { | |
| 551 | + | onMissle = botOnMissle, | |
| 552 | + | onAnimatedText = botOnAnimatedText, | |
| 553 | + | onStaticText = botOnStaticText | |
| 554 | + | }) | |
| 555 | + | end | |
| 556 | + | ||
| 557 | + | function safeBotCall(func) | |
| 558 | + | local status, result = pcall(func) | |
| 559 | + | if not status then | |
| 560 | + | onError(result) | |
| 561 | + | end | |
| 562 | + | end | |
| 563 | + | ||
| 564 | + | function botKeyDown(widget, keyCode, keyboardModifiers) | |
| 565 | + | if botExecutor == nil then return false end | |
| 566 | + | if keyCode == KeyUnknown then return end | |
| 567 | + | safeBotCall(function() botExecutor.callbacks.onKeyDown(keyCode, keyboardModifiers) end) | |
| 568 | + | end | |
| 569 | + | ||
| 570 | + | function botKeyUp(widget, keyCode, keyboardModifiers) | |
| 571 | + | if botExecutor == nil then return false end | |
| 572 | + | if keyCode == KeyUnknown then return end | |
| 573 | + | safeBotCall(function() botExecutor.callbacks.onKeyUp(keyCode, keyboardModifiers) end) | |
| 574 | + | end | |
| 575 | + | ||
| 576 | + | function botKeyPress(widget, keyCode, keyboardModifiers, autoRepeatTicks) | |
| 577 | + | if botExecutor == nil then return false end | |
| 578 | + | if keyCode == KeyUnknown then return end | |
| 579 | + | safeBotCall(function() botExecutor.callbacks.onKeyPress(keyCode, keyboardModifiers, autoRepeatTicks) end) | |
| 580 | + | end | |
| 581 | + | ||
| 582 | + | function botOnTalk(name, level, mode, text, channelId, pos) | |
| 583 | + | if botExecutor == nil then return false end | |
| 584 | + | safeBotCall(function() botExecutor.callbacks.onTalk(name, level, mode, text, channelId, pos) end) | |
| 585 | + | end | |
| 586 | + | ||
| 587 | + | function botOnTextMessage(mode, text) | |
| 588 | + | if botExecutor == nil then return false end | |
| 589 | + | safeBotCall(function() botExecutor.callbacks.onTextMessage(mode, text) end) | |
| 590 | + | end | |
| 591 | + | ||
| 592 | + | function botAddThing(tile, thing) | |
| 593 | + | if botExecutor == nil then return false end | |
| 594 | + | safeBotCall(function() botExecutor.callbacks.onAddThing(tile, thing) end) | |
| 595 | + | end | |
| 596 | + | ||
| 597 | + | function botRemoveThing(tile, thing) | |
| 598 | + | if botExecutor == nil then return false end | |
| 599 | + | safeBotCall(function() botExecutor.callbacks.onRemoveThing(tile, thing) end) | |
| 600 | + | end | |
| 601 | + | ||
| 602 | + | function botCreatureAppear(creature) | |
| 603 | + | if botExecutor == nil then return false end | |
| 604 | + | safeBotCall(function() botExecutor.callbacks.onCreatureAppear(creature) end) | |
| 605 | + | end | |
| 606 | + | ||
| 607 | + | function botCreatureDisappear(creature) | |
| 608 | + | if botExecutor == nil then return false end | |
| 609 | + | safeBotCall(function() botExecutor.callbacks.onCreatureDisappear(creature) end) | |
| 610 | + | end | |
| 611 | + | ||
| 612 | + | function botCreaturePositionChange(creature, newPos, oldPos) | |
| 613 | + | if botExecutor == nil then return false end | |
| 614 | + | safeBotCall(function() botExecutor.callbacks.onCreaturePositionChange(creature, newPos, oldPos) end) | |
| 615 | + | end | |
| 616 | + | ||
| 617 | + | function botCraetureHealthPercentChange(creature, healthPercent) | |
| 618 | + | if botExecutor == nil then return false end | |
| 619 | + | safeBotCall(function() botExecutor.callbacks.onCreatureHealthPercentChange(creature, healthPercent) end) | |
| 620 | + | end | |
| 621 | + | ||
| 622 | + | function botOnUse(pos, itemId, stackPos, subType) | |
| 623 | + | if botExecutor == nil then return false end | |
| 624 | + | safeBotCall(function() botExecutor.callbacks.onUse(pos, itemId, stackPos, subType) end) | |
| 625 | + | end | |
| 626 | + | ||
| 627 | + | function botOnUseWith(pos, itemId, target, subType) | |
| 628 | + | if botExecutor == nil then return false end | |
| 629 | + | safeBotCall(function() botExecutor.callbacks.onUseWith(pos, itemId, target, subType) end) | |
| 630 | + | end | |
| 631 | + | ||
| 632 | + | function botContainerOpen(container, previousContainer) | |
| 633 | + | if botExecutor == nil then return false end | |
| 634 | + | safeBotCall(function() botExecutor.callbacks.onContainerOpen(container, previousContainer) end) | |
| 635 | + | end | |
| 636 | + | ||
| 637 | + | function botContainerClose(container) | |
| 638 | + | if botExecutor == nil then return false end | |
| 639 | + | safeBotCall(function() botExecutor.callbacks.onContainerClose(container) end) | |
| 640 | + | end | |
| 641 | + | ||
| 642 | + | function botContainerUpdateItem(container, slot, item) | |
| 643 | + | if botExecutor == nil then return false end | |
| 644 | + | safeBotCall(function() botExecutor.callbacks.onContainerUpdateItem(container, slot, item) end) | |
| 645 | + | end | |
| 646 | + | ||
| 647 | + | function botOnMissle(missle) | |
| 648 | + | if botExecutor == nil then return false end | |
| 649 | + | safeBotCall(function() botExecutor.callbacks.onMissle(missle) end) | |
| 650 | + | end | |
| 651 | + | ||
| 652 | + | function botOnAnimatedText(thing, text) | |
| 653 | + | if botExecutor == nil then return false end | |
| 654 | + | safeBotCall(function() botExecutor.callbacks.onAnimatedText(thing, text) end) | |
| 655 | + | end | |
| 656 | + | ||
| 657 | + | function botOnStaticText(thing, text) | |
| 658 | + | if botExecutor == nil then return false end | |
| 659 | + | safeBotCall(function() botExecutor.callbacks.onStaticText(thing, text) end) | |
| 660 | + | end | |
| 661 | + | ||
| 662 | + | function botChannelList(channels) | |
| 663 | + | if botExecutor == nil then return false end | |
| 664 | + | safeBotCall(function() botExecutor.callbacks.onChannelList(channels) end) | |
| 665 | + | end | |
| 666 | + | ||
| 667 | + | function botOpenChannel(channelId, name) | |
| 668 | + | if botExecutor == nil then return false end | |
| 669 | + | safeBotCall(function() botExecutor.callbacks.onOpenChannel(channelId, name) end) | |
| 670 | + | end | |
| 671 | + | ||
| 672 | + | function botCloseChannel(channelId) | |
| 673 | + | if botExecutor == nil then return false end | |
| 674 | + | safeBotCall(function() botExecutor.callbacks.onCloseChannel(channelId) end) | |
| 675 | + | end | |
| 676 | + | ||
| 677 | + | function botChannelEvent(channelId, name, event) | |
| 678 | + | if botExecutor == nil then return false end | |
| 679 | + | safeBotCall(function() botExecutor.callbacks.onChannelEvent(channelId, name, event) end) | |
| 680 | + | end | |
| 681 | + | ||
| 682 | + | function botCreatureTurn(creature, direction) | |
| 683 | + | if botExecutor == nil then return false end | |
| 684 | + | safeBotCall(function() botExecutor.callbacks.onTurn(creature, direction) end) | |
| 685 | + | end |
| @@ -0,0 +1,8 @@ | |||
| 1 | + | Module | |
| 2 | + | name: game_bot | |
| 3 | + | description: Advanced OTClientV8 Bot | |
| 4 | + | author: otclient@otclient.ovh | |
| 5 | + | sandboxed: true | |
| 6 | + | scripts: [ bot ] | |
| 7 | + | @onLoad: init() | |
| 8 | + | @onUnload: terminate() |
| @@ -0,0 +1,126 @@ | |||
| 1 | + | BotTabBar < TabBar | |
| 2 | + | tab-spacing: 1 | |
| 3 | + | margin-left: 1 | |
| 4 | + | margin-right: 1 | |
| 5 | + | height: 20 | |
| 6 | + | ||
| 7 | + | $on: | |
| 8 | + | visible: true | |
| 9 | + | margin-top: 2 | |
| 10 | + | ||
| 11 | + | $!on: | |
| 12 | + | visible: false | |
| 13 | + | margin-top: -20 | |
| 14 | + | ||
| 15 | + | BotTabBarPanel < TabBarPanel | |
| 16 | + | BotTabBarButton < TabBarButton | |
| 17 | + | padding: 4 | |
| 18 | + | padding-right: 5 | |
| 19 | + | text-horizontal-auto-resize: true | |
| 20 | + | $!first: | |
| 21 | + | margin-left: 0 | |
| 22 | + | ||
| 23 | + | MiniWindow | |
| 24 | + | id: botWindow | |
| 25 | + | visible: false | |
| 26 | + | !text: tr('Bot') | |
| 27 | + | height: 600 | |
| 28 | + | icon: /images/topbuttons/bot | |
| 29 | + | @onClose: modules.game_bot.onMiniWindowClose() | |
| 30 | + | &save: true | |
| 31 | + | ||
| 32 | + | MiniWindowContents | |
| 33 | + | ComboBox | |
| 34 | + | id: config | |
| 35 | + | anchors.top: parent.top | |
| 36 | + | anchors.left: parent.left | |
| 37 | + | anchors.right: parent.right | |
| 38 | + | margin-top: 2 | |
| 39 | + | margin-left: 2 | |
| 40 | + | margin-right: 75 | |
| 41 | + | text-offset: 3 0 | |
| 42 | + | ||
| 43 | + | Button | |
| 44 | + | id: editConfig | |
| 45 | + | anchors.top: prev.top | |
| 46 | + | anchors.left: prev.right | |
| 47 | + | anchors.right: parent.right | |
| 48 | + | !text: tr('Edit') | |
| 49 | + | @onClick: modules.game_bot.edit() | |
| 50 | + | margin-left: 3 | |
| 51 | + | margin-right: 37 | |
| 52 | + | ||
| 53 | + | Button | |
| 54 | + | id: enableButton | |
| 55 | + | anchors.top: prev.top | |
| 56 | + | anchors.left: prev.right | |
| 57 | + | anchors.right: parent.right | |
| 58 | + | margin-left: 3 | |
| 59 | + | margin-right: 2 | |
| 60 | + | ||
| 61 | + | $on: | |
| 62 | + | text: On | |
| 63 | + | color: #00AA00 | |
| 64 | + | ||
| 65 | + | $!on: | |
| 66 | + | text: Off | |
| 67 | + | color: #FF0000 | |
| 68 | + | ||
| 69 | + | Label | |
| 70 | + | id: statusLabel | |
| 71 | + | anchors.top: prev.bottom | |
| 72 | + | anchors.left: parent.left | |
| 73 | + | anchors.right: parent.right | |
| 74 | + | text-wrap: true | |
| 75 | + | text-auto-resize: true | |
| 76 | + | text-align: center | |
| 77 | + | !text: tr('Status: waiting') | |
| 78 | + | margin-left: 3 | |
| 79 | + | margin-right: 3 | |
| 80 | + | ||
| 81 | + | $on: | |
| 82 | + | margin-top: 3 | |
| 83 | + | ||
| 84 | + | $!on: | |
| 85 | + | text: | |
| 86 | + | margin-top: -13 | |
| 87 | + | ||
| 88 | + | HorizontalSeparator | |
| 89 | + | anchors.left: parent.left | |
| 90 | + | anchors.right: parent.right | |
| 91 | + | anchors.top: prev.bottom | |
| 92 | + | margin-top: 3 | |
| 93 | + | margin-left: 2 | |
| 94 | + | margin-right: 2 | |
| 95 | + | ||
| 96 | + | Panel | |
| 97 | + | anchors.top: prev.bottom | |
| 98 | + | anchors.left: parent.left | |
| 99 | + | anchors.right: parent.right | |
| 100 | + | id: messages | |
| 101 | + | layout: | |
| 102 | + | type: verticalBox | |
| 103 | + | fit-children: true | |
| 104 | + | ||
| 105 | + | HorizontalSeparator | |
| 106 | + | anchors.left: parent.left | |
| 107 | + | anchors.right: parent.right | |
| 108 | + | anchors.top: prev.bottom | |
| 109 | + | margin-top: 5 | |
| 110 | + | margin-left: 2 | |
| 111 | + | margin-right: 2 | |
| 112 | + | ||
| 113 | + | BotTabBar | |
| 114 | + | id: botTabs | |
| 115 | + | anchors.top: prev.bottom | |
| 116 | + | anchors.left: parent.left | |
| 117 | + | anchors.right: parent.right | |
| 118 | + | margin-right: -20 | |
| 119 | + | ||
| 120 | + | Panel | |
| 121 | + | id: botPanel | |
| 122 | + | margin-top: 2 | |
| 123 | + | anchors.top: prev.bottom | |
| 124 | + | anchors.bottom: parent.bottom | |
| 125 | + | anchors.left: parent.left | |
| 126 | + | anchors.right: parent.right |
| @@ -0,0 +1,36 @@ | |||
| 1 | + | -- Cavebot by otclient@otclient.ovh | |
| 2 | + | -- visit http://bot.otclient.ovh/ | |
| 3 | + | ||
| 4 | + | local cavebotTab = "Cave" | |
| 5 | + | local targetingTab = "Target" | |
| 6 | + | ||
| 7 | + | setDefaultTab(cavebotTab) | |
| 8 | + | CaveBot = {} -- global namespace | |
| 9 | + | CaveBot.Extensions = {} | |
| 10 | + | importStyle("/cavebot/cavebot.otui") | |
| 11 | + | importStyle("/cavebot/editor.otui") | |
| 12 | + | importStyle("/cavebot/supply.otui") | |
| 13 | + | dofile("/cavebot/actions.lua") | |
| 14 | + | dofile("/cavebot/editor.lua") | |
| 15 | + | dofile("/cavebot/example_functions.lua") | |
| 16 | + | dofile("/cavebot/recorder.lua") | |
| 17 | + | -- in this section you can add extensions, check extension_template.lua | |
| 18 | + | --dofile("/cavebot/extension_template.lua") | |
| 19 | + | dofile("/cavebot/depositer.lua") | |
| 20 | + | dofile("/cavebot/supply.lua") | |
| 21 | + | -- main cavebot file, must be last | |
| 22 | + | dofile("/cavebot/cavebot.lua") | |
| 23 | + | ||
| 24 | + | setDefaultTab(targetingTab) | |
| 25 | + | TargetBot = {} -- global namespace | |
| 26 | + | importStyle("/targetbot/looting.otui") | |
| 27 | + | importStyle("/targetbot/target.otui") | |
| 28 | + | importStyle("/targetbot/creature_editor.otui") | |
| 29 | + | dofile("/targetbot/creature.lua") | |
| 30 | + | dofile("/targetbot/creature_attack.lua") | |
| 31 | + | dofile("/targetbot/creature_editor.lua") | |
| 32 | + | dofile("/targetbot/creature_priority.lua") | |
| 33 | + | dofile("/targetbot/looting.lua") | |
| 34 | + | dofile("/targetbot/walking.lua") | |
| 35 | + | -- main targetbot file, must be last | |
| 36 | + | dofile("/targetbot/target.lua") |
| @@ -0,0 +1,252 @@ | |||
| 1 | + | CaveBot.Actions = {} | |
| 2 | + | ||
| 3 | + | -- it adds an action widget to list | |
| 4 | + | CaveBot.addAction = function(action, value, focus) | |
| 5 | + | action = action:lower() | |
| 6 | + | local raction = CaveBot.Actions[action] | |
| 7 | + | if not raction then | |
| 8 | + | return error("Invalid cavebot action: " .. action) | |
| 9 | + | end | |
| 10 | + | if type(value) == 'number' then | |
| 11 | + | value = tostring(value) | |
| 12 | + | end | |
| 13 | + | local widget = UI.createWidget("CaveBotAction", CaveBot.actionList) | |
| 14 | + | widget:setText(action .. ":" .. value:split("\n")[1]) | |
| 15 | + | widget.action = action | |
| 16 | + | widget.value = value | |
| 17 | + | if raction.color then | |
| 18 | + | widget:setColor(raction.color) | |
| 19 | + | end | |
| 20 | + | widget.onDoubleClick = function(cwidget) -- edit on double click | |
| 21 | + | if CaveBot.Editor then | |
| 22 | + | schedule(20, function() -- schedule to have correct focus | |
| 23 | + | CaveBot.Editor.edit(cwidget.action, cwidget.value, function(action, value) | |
| 24 | + | CaveBot.editAction(cwidget, action, value) | |
| 25 | + | CaveBot.save() | |
| 26 | + | end) | |
| 27 | + | end) | |
| 28 | + | end | |
| 29 | + | end | |
| 30 | + | if focus then | |
| 31 | + | widget:focus() | |
| 32 | + | CaveBot.actionList:ensureChildVisible(widget) | |
| 33 | + | end | |
| 34 | + | return widget | |
| 35 | + | end | |
| 36 | + | ||
| 37 | + | -- it updates existing widget, you should call CaveBot.save() later | |
| 38 | + | CaveBot.editAction = function(widget, action, value) | |
| 39 | + | action = action:lower() | |
| 40 | + | local raction = CaveBot.Actions[action] | |
| 41 | + | if not raction then | |
| 42 | + | return error("Invalid cavebot action: " .. action) | |
| 43 | + | end | |
| 44 | + | ||
| 45 | + | if not widget.action or not widget.value then | |
| 46 | + | return error("Invalid cavebot action widget, has missing action or value") | |
| 47 | + | end | |
| 48 | + | ||
| 49 | + | widget:setText(action .. ":" .. value:split("\n")[1]) | |
| 50 | + | widget.action = action | |
| 51 | + | widget.value = value | |
| 52 | + | if raction.color then | |
| 53 | + | widget:setColor(raction.color) | |
| 54 | + | end | |
| 55 | + | return widget | |
| 56 | + | end | |
| 57 | + | ||
| 58 | + | --[[ | |
| 59 | + | registerAction: | |
| 60 | + | action - string, color - string, callback = function(value, retries, prev) | |
| 61 | + | value is a string value of action, retries is number which will grow by 1 if return is "retry" | |
| 62 | + | prev is a true when previuos action was executed succesfully, false otherwise | |
| 63 | + | it must return true if executed correctly, false otherwise | |
| 64 | + | it can also return string "retry", then the function will be called again in 20 ms | |
| 65 | + | ]]-- | |
| 66 | + | CaveBot.registerAction = function(action, color, callback) | |
| 67 | + | action = action:lower() | |
| 68 | + | if CaveBot.Actions[action] then | |
| 69 | + | return error("Duplicated acction: " .. action) | |
| 70 | + | end | |
| 71 | + | CaveBot.Actions[action] = { | |
| 72 | + | color=color, | |
| 73 | + | callback=callback | |
| 74 | + | } | |
| 75 | + | end | |
| 76 | + | ||
| 77 | + | CaveBot.registerAction("label", "yellow", function(value, retries, prev) | |
| 78 | + | return true | |
| 79 | + | end) | |
| 80 | + | ||
| 81 | + | CaveBot.registerAction("gotolabel", "#FFFF55", function(value, retries, prev) | |
| 82 | + | return CaveBot.gotoLabel(value) | |
| 83 | + | end) | |
| 84 | + | ||
| 85 | + | CaveBot.registerAction("delay", "#AAAAAA", function(value, retries, prev) | |
| 86 | + | if retries == 0 then | |
| 87 | + | CaveBot.delay(tonumber(value)) | |
| 88 | + | return "retry" | |
| 89 | + | end | |
| 90 | + | return true | |
| 91 | + | end) | |
| 92 | + | ||
| 93 | + | CaveBot.registerAction("function", "red", function(value, retries, prev) | |
| 94 | + | local prefix = "local retries = " .. retries .. "\nlocal prev = " .. tostring(prev) .. "\nlocal delay = CaveBot.delay\nlocal gotoLabel = CaveBot.gotoLabel\n" | |
| 95 | + | prefix = prefix .. "local macro = function() error('Macros inside cavebot functions are not allowed') end\n" | |
| 96 | + | for extension, callbacks in pairs(CaveBot.Extensions) do | |
| 97 | + | prefix = prefix .. "local " .. extension .. " = CaveBot.Extensions." .. extension .. "\n" | |
| 98 | + | end | |
| 99 | + | local status, result = pcall(function() | |
| 100 | + | return assert(load(prefix .. value, "cavebot_function"))() | |
| 101 | + | end) | |
| 102 | + | if not status then | |
| 103 | + | error("Error in cavebot function:\n" .. result) | |
| 104 | + | return false | |
| 105 | + | end | |
| 106 | + | return result | |
| 107 | + | end) | |
| 108 | + | ||
| 109 | + | CaveBot.registerAction("goto", "green", function(value, retries, prev) | |
| 110 | + | local pos = regexMatch(value, "\\s*([0-9]+)\\s*,\\s*([0-9]+)\\s*,\\s*([0-9]+)") | |
| 111 | + | print("Bot Walk Path") | |
| 112 | + | if not pos[1] then | |
| 113 | + | error("Invalid cavebot goto action value. It should be position (x,y,z), is: " .. value) | |
| 114 | + | return false | |
| 115 | + | end | |
| 116 | + | ||
| 117 | + | if retries >= 5 then | |
| 118 | + | return false -- tried 5 times, can't get there | |
| 119 | + | end | |
| 120 | + | ||
| 121 | + | pos = {x=tonumber(pos[1][2]), y=tonumber(pos[1][3]), z=tonumber(pos[1][4])} | |
| 122 | + | local playerPos = player:getPosition() | |
| 123 | + | if pos.z ~= playerPos.z then | |
| 124 | + | return false -- different floor | |
| 125 | + | end | |
| 126 | + | ||
| 127 | + | if math.abs(pos.x-playerPos.x) + math.abs(pos.y-playerPos.y) > 40 then | |
| 128 | + | return false -- too far way | |
| 129 | + | end | |
| 130 | + | ||
| 131 | + | local minimapColor = g_map.getMinimapColor(pos) | |
| 132 | + | local stairs = (minimapColor >= 210 and minimapColor <= 213) | |
| 133 | + | ||
| 134 | + | if stairs then | |
| 135 | + | if math.abs(pos.x-playerPos.x) == 0 and math.abs(pos.y-playerPos.y) <= 0 then | |
| 136 | + | return true -- already at position | |
| 137 | + | end | |
| 138 | + | elseif math.abs(pos.x-playerPos.x) == 0 and math.abs(pos.y-playerPos.y) <= 1 then | |
| 139 | + | return true -- already at position | |
| 140 | + | end | |
| 141 | + | -- check if there's a path to that place, ignore creatures and fields | |
| 142 | + | local path = findPath(playerPos, pos, 40, { ignoreNonPathable = true, precision = 1, ignoreCreatures = true }) | |
| 143 | + | if not path then | |
| 144 | + | return false -- there's no way | |
| 145 | + | end | |
| 146 | + | ||
| 147 | + | -- walk will be executed, but it will take some time to get response from server, wait 300ms after autowalk | |
| 148 | + | CaveBot.delay(300) | |
| 149 | + | ||
| 150 | + | -- try to find path, don't ignore creatures, don't ignore fields | |
| 151 | + | if autoWalk(pos, 40) then | |
| 152 | + | return "retry" | |
| 153 | + | end | |
| 154 | + | ||
| 155 | + | -- try to find path, don't ignore creatures, ignore fields | |
| 156 | + | if autoWalk(pos, 40, { ignoreNonPathable = true }) then | |
| 157 | + | return "retry" | |
| 158 | + | end | |
| 159 | + | ||
| 160 | + | if retries >= 3 then | |
| 161 | + | -- try to lower precision, find something close to final position | |
| 162 | + | local precison = retries - 1 | |
| 163 | + | if stairs then | |
| 164 | + | precison = 0 | |
| 165 | + | end | |
| 166 | + | if autoWalk(pos, 50, { ignoreNonPathable = true, precision = precison }) then | |
| 167 | + | return "retry" | |
| 168 | + | end | |
| 169 | + | end | |
| 170 | + | ||
| 171 | + | autoWalk(path) -- everything else failed, try to walk ignoring creatures, maybe will work | |
| 172 | + | return "retry" | |
| 173 | + | end) | |
| 174 | + | ||
| 175 | + | CaveBot.registerAction("use", "#FFB272", function(value, retries, prev) | |
| 176 | + | local pos = regexMatch(value, "\\s*([0-9]+)\\s*,\\s*([0-9]+)\\s*,\\s*([0-9]+)") | |
| 177 | + | if not pos[1] then | |
| 178 | + | local itemid = tonumber(value) | |
| 179 | + | if not itemid then | |
| 180 | + | error("Invalid cavebot use action value. It should be (x,y,z) or item id, is: " .. value) | |
| 181 | + | return false | |
| 182 | + | end | |
| 183 | + | use(itemid) | |
| 184 | + | return true | |
| 185 | + | end | |
| 186 | + | ||
| 187 | + | pos = {x=tonumber(pos[1][2]), y=tonumber(pos[1][3]), z=tonumber(pos[1][4])} | |
| 188 | + | local playerPos = player:getPosition() | |
| 189 | + | if pos.z ~= playerPos.z then | |
| 190 | + | return false -- different floor | |
| 191 | + | end | |
| 192 | + | ||
| 193 | + | if math.max(math.abs(pos.x-playerPos.x), math.abs(pos.y-playerPos.y)) > 7 then | |
| 194 | + | return false -- too far way | |
| 195 | + | end | |
| 196 | + | ||
| 197 | + | local tile = g_map.getTile(pos) | |
| 198 | + | if not tile then | |
| 199 | + | return false | |
| 200 | + | end | |
| 201 | + | ||
| 202 | + | local topThing = tile:getTopUseThing() | |
| 203 | + | if not topThing then | |
| 204 | + | return false | |
| 205 | + | end | |
| 206 | + | ||
| 207 | + | use(topThing) | |
| 208 | + | CaveBot.delay(400) | |
| 209 | + | return true | |
| 210 | + | end) | |
| 211 | + | ||
| 212 | + | CaveBot.registerAction("usewith", "#EEB292", function(value, retries, prev) | |
| 213 | + | local pos = regexMatch(value, "\\s*([0-9]+)\\s*,\\s*([0-9]+)\\s*,\\s*([0-9]+)\\s*,\\s*([0-9]+)") | |
| 214 | + | if not pos[1] then | |
| 215 | + | if not itemid then | |
| 216 | + | error("Invalid cavebot usewith action value. It should be (itemid,x,y,z) or item id, is: " .. value) | |
| 217 | + | return false | |
| 218 | + | end | |
| 219 | + | use(itemid) | |
| 220 | + | return true | |
| 221 | + | end | |
| 222 | + | ||
| 223 | + | local itemid = tonumber(pos[1][2]) | |
| 224 | + | pos = {x=tonumber(pos[1][3]), y=tonumber(pos[1][4]), z=tonumber(pos[1][5])} | |
| 225 | + | local playerPos = player:getPosition() | |
| 226 | + | if pos.z ~= playerPos.z then | |
| 227 | + | return false -- different floor | |
| 228 | + | end | |
| 229 | + | ||
| 230 | + | if math.max(math.abs(pos.x-playerPos.x), math.abs(pos.y-playerPos.y)) > 7 then | |
| 231 | + | return false -- too far way | |
| 232 | + | end | |
| 233 | + | ||
| 234 | + | local tile = g_map.getTile(pos) | |
| 235 | + | if not tile then | |
| 236 | + | return false | |
| 237 | + | end | |
| 238 | + | ||
| 239 | + | local topThing = tile:getTopUseThing() | |
| 240 | + | if not topThing then | |
| 241 | + | return false | |
| 242 | + | end | |
| 243 | + | ||
| 244 | + | usewith(itemid, topThing) | |
| 245 | + | CaveBot.delay(400) | |
| 246 | + | return true | |
| 247 | + | end) | |
| 248 | + | ||
| 249 | + | CaveBot.registerAction("say", "#FF55FF", function(value, retries, prev) | |
| 250 | + | say(value) | |
| 251 | + | return true | |
| 252 | + | end) |
| @@ -0,0 +1,192 @@ | |||
| 1 | + | local cavebotMacro = nil | |
| 2 | + | local config = nil | |
| 3 | + | ||
| 4 | + | -- ui | |
| 5 | + | local configWidget = UI.Config() | |
| 6 | + | local ui = UI.createWidget("CaveBotPanel") | |
| 7 | + | ||
| 8 | + | ui.list = ui.listPanel.list -- shortcut | |
| 9 | + | CaveBot.actionList = ui.list | |
| 10 | + | ||
| 11 | + | if CaveBot.Editor then | |
| 12 | + | CaveBot.Editor.setup() | |
| 13 | + | end | |
| 14 | + | for extension, callbacks in pairs(CaveBot.Extensions) do | |
| 15 | + | if callbacks.setup then | |
| 16 | + | callbacks.setup() | |
| 17 | + | end | |
| 18 | + | end | |
| 19 | + | ||
| 20 | + | -- main loop, controlled by config | |
| 21 | + | local actionRetries = 0 | |
| 22 | + | local prevActionResult = true | |
| 23 | + | cavebotMacro = macro(20, function() | |
| 24 | + | if TargetBot and TargetBot.isActive() and not TargetBot.isCaveBotActionAllowed() then | |
| 25 | + | return -- target bot or looting is working, wait | |
| 26 | + | end | |
| 27 | + | ||
| 28 | + | local actions = ui.list:getChildCount() | |
| 29 | + | if actions == 0 then return end | |
| 30 | + | local currentAction = ui.list:getFocusedChild() | |
| 31 | + | if not currentAction then | |
| 32 | + | currentAction = ui.list:getFirstChild() | |
| 33 | + | end | |
| 34 | + | local action = CaveBot.Actions[currentAction.action] | |
| 35 | + | local value = currentAction.value | |
| 36 | + | local retry = false | |
| 37 | + | if action then | |
| 38 | + | local status, result = pcall(function() | |
| 39 | + | return action.callback(value, actionRetries, prevActionResult) | |
| 40 | + | end) | |
| 41 | + | if status then | |
| 42 | + | if result == "retry" then | |
| 43 | + | actionRetries = actionRetries + 1 | |
| 44 | + | retry = true | |
| 45 | + | elseif type(result) == 'boolean' then | |
| 46 | + | actionRetries = 0 | |
| 47 | + | prevActionResult = result | |
| 48 | + | else | |
| 49 | + | error("Invalid return from cavebot action (" .. currentAction.action .. "), should be \"retry\", false or true, is: " .. tostring(result)) | |
| 50 | + | end | |
| 51 | + | else | |
| 52 | + | error("Error while executing cavebot action (" .. currentAction.action .. "):\n" .. result) | |
| 53 | + | end | |
| 54 | + | else | |
| 55 | + | error("Invalid cavebot action: " .. currentAction.action) | |
| 56 | + | end | |
| 57 | + | ||
| 58 | + | if retry then | |
| 59 | + | return | |
| 60 | + | end | |
| 61 | + | ||
| 62 | + | if currentAction ~= ui.list:getFocusedChild() then | |
| 63 | + | -- focused child can change durring action, get it again and reset state | |
| 64 | + | currentAction = ui.list:getFocusedChild() or ui.list:getFirstChild() | |
| 65 | + | actionRetries = 0 | |
| 66 | + | prevActionResult = true | |
| 67 | + | end | |
| 68 | + | local nextAction = ui.list:getChildIndex(currentAction) + 1 | |
| 69 | + | if nextAction > actions then | |
| 70 | + | nextAction = 1 | |
| 71 | + | end | |
| 72 | + | ui.list:focusChild(ui.list:getChildByIndex(nextAction)) | |
| 73 | + | end) | |
| 74 | + | ||
| 75 | + | onPlayerPositionChange(function() | |
| 76 | + | local delayAfterPositionChange = math.max(player:getStepDuration() + 100, 200) | |
| 77 | + | cavebotMacro.delay = math.max(cavebotMacro.delay or 0, now + delayAfterPositionChange) | |
| 78 | + | end) | |
| 79 | + | ||
| 80 | + | -- config, its callback is called immediately, data can be nil | |
| 81 | + | local lastConfig = "" | |
| 82 | + | config = Config.setup("cavebot_configs", configWidget, "cfg", function(name, enabled, data) | |
| 83 | + | if enabled and CaveBot.Recorder.isOn() then | |
| 84 | + | CaveBot.Recorder.disable() | |
| 85 | + | CaveBot.setOff() | |
| 86 | + | return | |
| 87 | + | end | |
| 88 | + | ||
| 89 | + | local currentActionIndex = ui.list:getChildIndex(ui.list:getFocusedChild()) | |
| 90 | + | ui.list:destroyChildren() | |
| 91 | + | if not data then return cavebotMacro.setOff() end | |
| 92 | + | ||
| 93 | + | for k,v in ipairs(data) do | |
| 94 | + | if type(v) == "table" and #v == 2 then | |
| 95 | + | if v[1] == "extensions" then | |
| 96 | + | local status, result = pcall(function() | |
| 97 | + | return json.decode(v[2]) | |
| 98 | + | end) | |
| 99 | + | if not status then | |
| 100 | + | error("Error while parsing CaveBot extensions from config:\n" .. result) | |
| 101 | + | else | |
| 102 | + | for extension, callbacks in pairs(CaveBot.Extensions) do | |
| 103 | + | if callbacks.onConfigChange then | |
| 104 | + | callbacks.onConfigChange(name, enabled, result[extension]) | |
| 105 | + | end | |
| 106 | + | end | |
| 107 | + | end | |
| 108 | + | else | |
| 109 | + | CaveBot.addAction(v[1], v[2]) | |
| 110 | + | end | |
| 111 | + | end | |
| 112 | + | end | |
| 113 | + | ||
| 114 | + | actionRetries = 0 | |
| 115 | + | prevActionResult = true | |
| 116 | + | cavebotMacro.setOn(enabled) | |
| 117 | + | cavebotMacro.delay = nil | |
| 118 | + | if lastConfig == name then | |
| 119 | + | -- restore focused child on the action list | |
| 120 | + | ui.list:focusChild(ui.list:getChildByIndex(currentActionIndex)) | |
| 121 | + | end | |
| 122 | + | lastConfig = name | |
| 123 | + | end) | |
| 124 | + | ||
| 125 | + | -- ui callbacks | |
| 126 | + | ui.showEditor.onClick = function() | |
| 127 | + | if not CaveBot.Editor then return end | |
| 128 | + | if ui.showEditor:isOn() then | |
| 129 | + | CaveBot.Editor.hide() | |
| 130 | + | ui.showEditor:setOn(false) | |
| 131 | + | else | |
| 132 | + | CaveBot.Editor.show() | |
| 133 | + | ui.showEditor:setOn(true) | |
| 134 | + | end | |
| 135 | + | end | |
| 136 | + | ||
| 137 | + | ||
| 138 | + | -- public function, you can use them in your scripts | |
| 139 | + | CaveBot.isOn = function() | |
| 140 | + | return config.isOn() | |
| 141 | + | end | |
| 142 | + | ||
| 143 | + | CaveBot.isOff = function() | |
| 144 | + | return config.isOff() | |
| 145 | + | end | |
| 146 | + | ||
| 147 | + | CaveBot.setOn = function(val) | |
| 148 | + | if val == false then | |
| 149 | + | return CaveBot.setOff(true) | |
| 150 | + | end | |
| 151 | + | config.setOn() | |
| 152 | + | end | |
| 153 | + | ||
| 154 | + | CaveBot.setOff = function(val) | |
| 155 | + | if val == false then | |
| 156 | + | return CaveBot.setOn(true) | |
| 157 | + | end | |
| 158 | + | config.setOff() | |
| 159 | + | end | |
| 160 | + | ||
| 161 | + | CaveBot.delay = function(value) | |
| 162 | + | cavebotMacro.delay = now + value | |
| 163 | + | end | |
| 164 | + | ||
| 165 | + | CaveBot.gotoLabel = function(label) | |
| 166 | + | label = label:lower() | |
| 167 | + | for index, child in ipairs(ui.list:getChildren()) do | |
| 168 | + | if child.action == "label" and child.value:lower() == label then | |
| 169 | + | ui.list:focusChild(child) | |
| 170 | + | return true | |
| 171 | + | end | |
| 172 | + | end | |
| 173 | + | return false | |
| 174 | + | end | |
| 175 | + | ||
| 176 | + | CaveBot.save = function() | |
| 177 | + | local data = {} | |
| 178 | + | for index, child in ipairs(ui.list:getChildren()) do | |
| 179 | + | table.insert(data, {child.action, child.value}) | |
| 180 | + | end | |
| 181 | + | local extension_data = {} | |
| 182 | + | for extension, callbacks in pairs(CaveBot.Extensions) do | |
| 183 | + | if callbacks.onSave then | |
| 184 | + | local ext_data = callbacks.onSave() | |
| 185 | + | if type(ext_data) == "table" then | |
| 186 | + | extension_data[extension] = ext_data | |
| 187 | + | end | |
| 188 | + | end | |
| 189 | + | end | |
| 190 | + | table.insert(data, {"extensions", json.encode(extension_data, 2)}) | |
| 191 | + | config.save(data) | |
| 192 | + | end |
| @@ -0,0 +1,48 @@ | |||
| 1 | + | CaveBotAction < Label | |
| 2 | + | background-color: alpha | |
| 3 | + | text-offset: 2 0 | |
| 4 | + | focusable: true | |
| 5 | + | ||
| 6 | + | $focus: | |
| 7 | + | background-color: #00000055 | |
| 8 | + | ||
| 9 | + | ||
| 10 | + | CaveBotPanel < Panel | |
| 11 | + | layout: | |
| 12 | + | type: verticalBox | |
| 13 | + | fit-children: true | |
| 14 | + | ||
| 15 | + | HorizontalSeparator | |
| 16 | + | margin-top: 2 | |
| 17 | + | margin-bottom: 5 | |
| 18 | + | ||
| 19 | + | Panel | |
| 20 | + | id: listPanel | |
| 21 | + | height: 100 | |
| 22 | + | margin-top: 2 | |
| 23 | + | ||
| 24 | + | TextList | |
| 25 | + | id: list | |
| 26 | + | anchors.fill: parent | |
| 27 | + | vertical-scrollbar: listScrollbar | |
| 28 | + | margin-right: 15 | |
| 29 | + | focusable: false | |
| 30 | + | auto-focus: first | |
| 31 | + | ||
| 32 | + | VerticalScrollBar | |
| 33 | + | id: listScrollbar | |
| 34 | + | anchors.top: parent.top | |
| 35 | + | anchors.bottom: parent.bottom | |
| 36 | + | anchors.right: parent.right | |
| 37 | + | pixels-scroll: true | |
| 38 | + | step: 10 | |
| 39 | + | ||
| 40 | + | BotSwitch | |
| 41 | + | id: showEditor | |
| 42 | + | margin-top: 2 | |
| 43 | + | ||
| 44 | + | $on: | |
| 45 | + | text: Hide waypoints editor | |
| 46 | + | ||
| 47 | + | $!on: | |
| 48 | + | text: Show waypoints editor |
| @@ -0,0 +1,27 @@ | |||
| 1 | + | CaveBot.Extensions.Depositer = {} | |
| 2 | + | ||
| 3 | + | local ui | |
| 4 | + | ||
| 5 | + | -- first function called, here you should setup your UI | |
| 6 | + | CaveBot.Extensions.Depositer.setup = function() | |
| 7 | + | --ui = UI.createWidget('Label') | |
| 8 | + | --ui:setText("Depositer UI") | |
| 9 | + | end | |
| 10 | + | ||
| 11 | + | -- called when cavebot config changes, configData is a table but it can be nil | |
| 12 | + | CaveBot.Extensions.Depositer.onConfigChange = function(configName, isEnabled, configData) | |
| 13 | + | if not configData then return end | |
| 14 | + | ||
| 15 | + | end | |
| 16 | + | ||
| 17 | + | -- called when cavebot is saving config, should return table or nil | |
| 18 | + | CaveBot.Extensions.Depositer.onSave = function() | |
| 19 | + | return {} | |
| 20 | + | end | |
| 21 | + | ||
| 22 | + | -- bellow add you custom functions | |
| 23 | + | -- this function can be used in cavebot function waypoint as: return Depositer.run(retries, prev) | |
| 24 | + | -- there are 2 useful parameters - retries (number) and prev (true/false), check actions.lua to learn more | |
| 25 | + | CaveBot.Extensions.Depositer.run = function(retries, prev) | |
| 26 | + | return true | |
| 27 | + | end |
| @@ -0,0 +1,174 @@ | |||
| 1 | + | CaveBot.Editor = {} | |
| 2 | + | CaveBot.Editor.Actions = {} | |
| 3 | + | ||
| 4 | + | -- also works as registerAction(action, params), then text == action | |
| 5 | + | -- params are options for text editor or function to be executed when clicked | |
| 6 | + | -- you have many examples how to use it bellow | |
| 7 | + | CaveBot.Editor.registerAction = function(action, text, params) | |
| 8 | + | if type(text) ~= 'string' then | |
| 9 | + | params = text | |
| 10 | + | text = action | |
| 11 | + | end | |
| 12 | + | ||
| 13 | + | local color = nil | |
| 14 | + | if type(params) ~= 'function' then | |
| 15 | + | local raction = CaveBot.Actions[action] | |
| 16 | + | if not raction then | |
| 17 | + | return error("CaveBot editor error: action " .. action .. " doesn't exist") | |
| 18 | + | end | |
| 19 | + | CaveBot.Editor.Actions[action] = params | |
| 20 | + | color = raction.color | |
| 21 | + | end | |
| 22 | + | ||
| 23 | + | local button = UI.createWidget('CaveBotEditorButton', CaveBot.Editor.ui.buttons) | |
| 24 | + | button:setText(text) | |
| 25 | + | if color then | |
| 26 | + | button:setColor(color) | |
| 27 | + | end | |
| 28 | + | button.onClick = function() | |
| 29 | + | if type(params) == 'function' then | |
| 30 | + | params() | |
| 31 | + | return | |
| 32 | + | end | |
| 33 | + | CaveBot.Editor.edit(action, nil, function(action, value) | |
| 34 | + | local focusedAction = CaveBot.actionList:getFocusedChild() | |
| 35 | + | local index = CaveBot.actionList:getChildCount() | |
| 36 | + | if focusedAction then | |
| 37 | + | index = CaveBot.actionList:getChildIndex(focusedAction) | |
| 38 | + | end | |
| 39 | + | local widget = CaveBot.addAction(action, value) | |
| 40 | + | CaveBot.actionList:moveChildToIndex(widget, index + 1) | |
| 41 | + | CaveBot.actionList:focusChild(widget) | |
| 42 | + | CaveBot.save() | |
| 43 | + | end) | |
| 44 | + | end | |
| 45 | + | return button | |
| 46 | + | end | |
| 47 | + | ||
| 48 | + | CaveBot.Editor.setup = function() | |
| 49 | + | CaveBot.Editor.ui = UI.createWidget("CaveBotEditorPanel") | |
| 50 | + | local ui = CaveBot.Editor.ui | |
| 51 | + | local registerAction = CaveBot.Editor.registerAction | |
| 52 | + | ||
| 53 | + | registerAction("move up", function() | |
| 54 | + | local action = CaveBot.actionList:getFocusedChild() | |
| 55 | + | if not action then return end | |
| 56 | + | local index = CaveBot.actionList:getChildIndex(action) | |
| 57 | + | if index < 2 then return end | |
| 58 | + | CaveBot.actionList:moveChildToIndex(action, index - 1) | |
| 59 | + | CaveBot.actionList:ensureChildVisible(action) | |
| 60 | + | CaveBot.save() | |
| 61 | + | end) | |
| 62 | + | registerAction("edit", function() | |
| 63 | + | local action = CaveBot.actionList:getFocusedChild() | |
| 64 | + | if not action or not action.onDoubleClick then return end | |
| 65 | + | action.onDoubleClick(action) | |
| 66 | + | end) | |
| 67 | + | registerAction("move down", function() | |
| 68 | + | local action = CaveBot.actionList:getFocusedChild() | |
| 69 | + | if not action then return end | |
| 70 | + | local index = CaveBot.actionList:getChildIndex(action) | |
| 71 | + | if index >= CaveBot.actionList:getChildCount() then return end | |
| 72 | + | CaveBot.actionList:moveChildToIndex(action, index + 1) | |
| 73 | + | CaveBot.actionList:ensureChildVisible(action) | |
| 74 | + | CaveBot.save() | |
| 75 | + | end) | |
| 76 | + | registerAction("remove", function() | |
| 77 | + | local action = CaveBot.actionList:getFocusedChild() | |
| 78 | + | if not action then return end | |
| 79 | + | action:destroy() | |
| 80 | + | CaveBot.save() | |
| 81 | + | end) | |
| 82 | + | ||
| 83 | + | registerAction("label", { | |
| 84 | + | value="labelName", | |
| 85 | + | title="Label", | |
| 86 | + | description="Add label", | |
| 87 | + | multiline=false | |
| 88 | + | }) | |
| 89 | + | registerAction("delay", { | |
| 90 | + | value="500", | |
| 91 | + | title="Delay", | |
| 92 | + | description="Delay next action (in milliseconds)", | |
| 93 | + | multiline=false, | |
| 94 | + | validation="^\\s*[0-9]{1,10}\\s*$" | |
| 95 | + | }) | |
| 96 | + | registerAction("gotolabel", "go to label", { | |
| 97 | + | value="labelName", | |
| 98 | + | title="Go to label", | |
| 99 | + | description="Go to label", | |
| 100 | + | multiline=false | |
| 101 | + | }) | |
| 102 | + | registerAction("goto", "go to", { | |
| 103 | + | value=function() return posx() .. "," .. posy() .. "," .. posz() end, | |
| 104 | + | title="Go to position", | |
| 105 | + | description="Go to position (x,y,z)", | |
| 106 | + | multiline=false, | |
| 107 | + | validation="^\\s*([0-9]+)\\s*,\\s*([0-9]+)\\s*,\\s*([0-9]+)$" | |
| 108 | + | }) | |
| 109 | + | registerAction("use", { | |
| 110 | + | value=function() return posx() .. "," .. posy() .. "," .. posz() end, | |
| 111 | + | title="Use", | |
| 112 | + | description="Use item from position (x,y,z) or from inventory (itemId)", | |
| 113 | + | multiline=false | |
| 114 | + | }) | |
| 115 | + | registerAction("usewith", "use with", { | |
| 116 | + | value=function() return "itemId," .. posx() .. "," .. posy() .. "," .. posz() end, | |
| 117 | + | title="Use with", | |
| 118 | + | description="Use item at position (itemid,x,y,z)", | |
| 119 | + | multiline=false, | |
| 120 | + | validation="^\\s*([0-9]+)\\s*,\\s*([0-9]+)\\s*,\\s*([0-9]+)\\s*,\\s*([0-9]+)$" | |
| 121 | + | }) | |
| 122 | + | registerAction("say", { | |
| 123 | + | value="text", | |
| 124 | + | title="Say", | |
| 125 | + | description="Enter text to say", | |
| 126 | + | multiline=false | |
| 127 | + | }) | |
| 128 | + | registerAction("function", { | |
| 129 | + | title="Edit bot function", | |
| 130 | + | multiline=true, | |
| 131 | + | value=CaveBot.Editor.ExampleFunctions[1][2], | |
| 132 | + | examples=CaveBot.Editor.ExampleFunctions, | |
| 133 | + | width=650 | |
| 134 | + | }) | |
| 135 | + | ||
| 136 | + | ui.autoRecording.onClick = function() | |
| 137 | + | if ui.autoRecording:isOn() then | |
| 138 | + | CaveBot.Recorder.disable() | |
| 139 | + | else | |
| 140 | + | CaveBot.Recorder.enable() | |
| 141 | + | end | |
| 142 | + | end | |
| 143 | + | ||
| 144 | + | -- callbacks | |
| 145 | + | onPlayerPositionChange(function(pos) | |
| 146 | + | ui.pos:setText("Position: " .. pos.x .. ", " .. pos.y .. ", " .. pos.z) | |
| 147 | + | end) | |
| 148 | + | ui.pos:setText("Position: " .. posx() .. ", " .. posy() .. ", " .. posz()) | |
| 149 | + | end | |
| 150 | + | ||
| 151 | + | CaveBot.Editor.show = function() | |
| 152 | + | CaveBot.Editor.ui:show() | |
| 153 | + | end | |
| 154 | + | ||
| 155 | + | ||
| 156 | + | CaveBot.Editor.hide = function() | |
| 157 | + | CaveBot.Editor.ui:hide() | |
| 158 | + | end | |
| 159 | + | ||
| 160 | + | CaveBot.Editor.edit = function(action, value, callback) -- callback = function(action, value) | |
| 161 | + | local params = CaveBot.Editor.Actions[action] | |
| 162 | + | if not params then return end | |
| 163 | + | if not value then | |
| 164 | + | if type(params.value) == 'function' then | |
| 165 | + | value = params.value() | |
| 166 | + | elseif type(params.value) == 'string' then | |
| 167 | + | value = params.value | |
| 168 | + | end | |
| 169 | + | end | |
| 170 | + | ||
| 171 | + | UI.EditorWindow(value, params, function(newText) | |
| 172 | + | callback(action, newText) | |
| 173 | + | end) | |
| 174 | + | end |
| @@ -0,0 +1,44 @@ | |||
| 1 | + | CaveBotEditorButton < Button | |
| 2 | + | ||
| 3 | + | ||
| 4 | + | CaveBotEditorPanel < Panel | |
| 5 | + | id: cavebotEditor | |
| 6 | + | visible: false | |
| 7 | + | layout: | |
| 8 | + | type: verticalBox | |
| 9 | + | fit-children: true | |
| 10 | + | ||
| 11 | + | Label | |
| 12 | + | id: pos | |
| 13 | + | text-align: center | |
| 14 | + | text: - | |
| 15 | + | ||
| 16 | + | Panel | |
| 17 | + | id: buttons | |
| 18 | + | margin-top: 2 | |
| 19 | + | layout: | |
| 20 | + | type: grid | |
| 21 | + | cell-size: 86 20 | |
| 22 | + | cell-spacing: 1 | |
| 23 | + | flow: true | |
| 24 | + | fit-children: true | |
| 25 | + | ||
| 26 | + | Label | |
| 27 | + | text: Double click on action from action list to edit it | |
| 28 | + | text-align: center | |
| 29 | + | text-auto-resize: true | |
| 30 | + | text-wrap: true | |
| 31 | + | margin-top: 3 | |
| 32 | + | margin-left: 2 | |
| 33 | + | margin-right: 2 | |
| 34 | + | ||
| 35 | + | BotSwitch | |
| 36 | + | id: autoRecording | |
| 37 | + | text: Auto Recording | |
| 38 | + | margin-top: 3 | |
| 39 | + | ||
| 40 | + | BotButton | |
| 41 | + | margin-top: 3 | |
| 42 | + | margin-bottom: 3 | |
| 43 | + | text: Documentation | |
| 44 | + | @onClick: g_platform.openUrl("http://bot.otclient.ovh/") |
| @@ -0,0 +1,65 @@ | |||
| 1 | + | CaveBot.Editor.ExampleFunctions = {} | |
| 2 | + | ||
| 3 | + | local function addExampleFunction(title, text) | |
| 4 | + | return table.insert(CaveBot.Editor.ExampleFunctions, {title, text:trim()}) | |
| 5 | + | end | |
| 6 | + | ||
| 7 | + | addExampleFunction("Click to browse example functions", [[ | |
| 8 | + | -- available functions/variables: | |
| 9 | + | -- prev - result of previous action (true or false) | |
| 10 | + | -- retries - number of retries of current function, goes up by one when you return "retry" | |
| 11 | + | -- delay(number) - delays bot next action, value in milliseconds | |
| 12 | + | -- gotoLabel(string) - goes to specific label, return true if label exists | |
| 13 | + | -- you can easily access bot extensions, Depositer.run() instead of CaveBot.Extensions.Depositer.run() | |
| 14 | + | -- also you can access bot global variables, like CaveBot, TargetBot | |
| 15 | + | -- use storage variable to store date between calls | |
| 16 | + | ||
| 17 | + | -- function should return false, true or "retry" | |
| 18 | + | -- if "retry" is returned, function will be executed again in 20 ms (so better call delay before) | |
| 19 | + | ||
| 20 | + | return true | |
| 21 | + | ]]) | |
| 22 | + | ||
| 23 | + | addExampleFunction("buy 200 mana potion from npc Eryn", [[ | |
| 24 | + | --buy 200 mana potions | |
| 25 | + | local npc = getCreatureByName("Eryn") | |
| 26 | + | if not npc then | |
| 27 | + | return false | |
| 28 | + | end | |
| 29 | + | if retries > 10 then | |
| 30 | + | return false | |
| 31 | + | end | |
| 32 | + | local pos = player:getPosition() | |
| 33 | + | local npcPos = npc:getPosition() | |
| 34 | + | if math.max(math.abs(pos.x - npcPos.x), math.abs(pos.y - npcPos.y)) > 3 then | |
| 35 | + | autoWalk(npcPos, {precision=3}) | |
| 36 | + | delay(300) | |
| 37 | + | return "retry" | |
| 38 | + | end | |
| 39 | + | if not NPC.isTrading() then | |
| 40 | + | NPC.say("hi") | |
| 41 | + | NPC.say("trade") | |
| 42 | + | delay(200) | |
| 43 | + | return "retry" | |
| 44 | + | end | |
| 45 | + | NPC.buy(268, 100) | |
| 46 | + | schedule(1000, function() | |
| 47 | + | -- buy again in 1s | |
| 48 | + | NPC.buy(268, 100) | |
| 49 | + | NPC.closeTrade() | |
| 50 | + | NPC.say("bye") | |
| 51 | + | end) | |
| 52 | + | delay(1200) | |
| 53 | + | return true | |
| 54 | + | ]]) | |
| 55 | + | ||
| 56 | + | addExampleFunction("Say hello 5 times with some delay", [[ | |
| 57 | + | --say hello | |
| 58 | + | if retries > 5 then | |
| 59 | + | return true -- finish | |
| 60 | + | end | |
| 61 | + | say("hello") | |
| 62 | + | delay(100 + retries * 100) | |
| 63 | + | return "retry" | |
| 64 | + | ]]) | |
| 65 | + |
| @@ -0,0 +1,58 @@ | |||
| 1 | + | -- example cavebot extension (remember to add this file to ../cavebot.lua) | |
| 2 | + | CaveBot.Extensions.Example = {} | |
| 3 | + | ||
| 4 | + | local ui | |
| 5 | + | ||
| 6 | + | -- setup is called automaticly when cavebot is ready | |
| 7 | + | CaveBot.Extensions.Example.setup = function() | |
| 8 | + | ui = UI.createWidget('BotTextEdit') | |
| 9 | + | ui:setText("Hello") | |
| 10 | + | ui.onTextChange = function() | |
| 11 | + | CaveBot.save() -- save new config when you change something | |
| 12 | + | end | |
| 13 | + | ||
| 14 | + | -- add custom cavebot action (check out actions.lua) | |
| 15 | + | CaveBot.registerAction("sayhello", "orange", function(value, retries, prev) | |
| 16 | + | local how_many_times = tonumber(value) | |
| 17 | + | if retries >= how_many_times then | |
| 18 | + | return true | |
| 19 | + | end | |
| 20 | + | say("hello " .. (retries + 1)) | |
| 21 | + | delay(250) | |
| 22 | + | return "retry" | |
| 23 | + | end) | |
| 24 | + | ||
| 25 | + | -- add this custom action to editor (check out editor.lua) | |
| 26 | + | CaveBot.Editor.registerAction("sayhello", "say hello", { | |
| 27 | + | value="5", | |
| 28 | + | title="Say hello", | |
| 29 | + | description="Says hello x times", | |
| 30 | + | validation="[0-9]{1,5}" -- regex, optional | |
| 31 | + | }) | |
| 32 | + | end | |
| 33 | + | ||
| 34 | + | -- called when cavebot config changes, configData is a table but it can also be nil | |
| 35 | + | CaveBot.Extensions.Example.onConfigChange = function(configName, isEnabled, configData) | |
| 36 | + | if not configData then return end | |
| 37 | + | if configData["text"] then | |
| 38 | + | ui:setText(configData["text"]) | |
| 39 | + | end | |
| 40 | + | end | |
| 41 | + | ||
| 42 | + | -- called when cavebot is saving config (so when CaveBot.save() is called), should return table or nil | |
| 43 | + | CaveBot.Extensions.Example.onSave = function() | |
| 44 | + | return {text=ui:getText()} | |
| 45 | + | end | |
| 46 | + | ||
| 47 | + | -- bellow add you custom functions to be used in cavebot function action | |
| 48 | + | -- an example: return Example.run(retries, prev) | |
| 49 | + | -- there are 2 useful parameters - retries (number) and prev (true/false), check actions.lua and example_functions.lua to learn more | |
| 50 | + | CaveBot.Extensions.Example.run = function(retries, prev) | |
| 51 | + | -- it will say text 10 times with some delay and then continue | |
| 52 | + | if retries > 10 then | |
| 53 | + | return true | |
| 54 | + | end | |
| 55 | + | say(ui:getText() .. " x" .. retries) | |
| 56 | + | delay(100 + retries * 100) | |
| 57 | + | return "retry" | |
| 58 | + | end |
| @@ -0,0 +1,65 @@ | |||
| 1 | + | -- auto recording for cavebot | |
| 2 | + | CaveBot.Recorder = {} | |
| 3 | + | ||
| 4 | + | local isEnabled = nil | |
| 5 | + | local lastPos = nil | |
| 6 | + | ||
| 7 | + | local function setup() | |
| 8 | + | local function addPosition(pos) | |
| 9 | + | CaveBot.addAction("goto", pos.x .. "," .. pos.y .. "," .. pos.z, true) | |
| 10 | + | lastPos = pos | |
| 11 | + | end | |
| 12 | + | ||
| 13 | + | onPlayerPositionChange(function(newPos, oldPos) | |
| 14 | + | if CaveBot.isOn() or not isEnabled then return end | |
| 15 | + | if not lastPos then | |
| 16 | + | -- first step | |
| 17 | + | addPosition(oldPos) | |
| 18 | + | elseif newPos.z ~= oldPos.z or math.abs(oldPos.x - newPos.x) > 1 or math.abs(oldPos.y - newPos.y) > 1 then | |
| 19 | + | -- stairs/teleport | |
| 20 | + | addPosition(oldPos) | |
| 21 | + | elseif math.max(math.abs(lastPos.x - newPos.x), math.abs(lastPos.y - newPos.y)) > 5 then | |
| 22 | + | -- 5 steps from last pos | |
| 23 | + | addPosition(newPos) | |
| 24 | + | end | |
| 25 | + | end) | |
| 26 | + | ||
| 27 | + | onUse(function(pos, itemId, stackPos, subType) | |
| 28 | + | if CaveBot.isOn() or not isEnabled then return end | |
| 29 | + | if pos.x ~= 0xFFFF then | |
| 30 | + | lastPos = pos | |
| 31 | + | CaveBot.addAction("use", pos.x .. "," .. pos.y .. "," .. pos.z, true) | |
| 32 | + | end | |
| 33 | + | end) | |
| 34 | + | ||
| 35 | + | onUseWith(function(pos, itemId, target, subType) | |
| 36 | + | if CaveBot.isOn() or not isEnabled then return end | |
| 37 | + | if not target:isItem() then return end | |
| 38 | + | local targetPos = target:getPosition() | |
| 39 | + | if targetPos.x == 0xFFFF then return end | |
| 40 | + | lastPos = pos | |
| 41 | + | CaveBot.addAction("usewith", itemId .. "," .. targetPos.x .. "," .. targetPos.y .. "," .. targetPos.z, true) | |
| 42 | + | end) | |
| 43 | + | end | |
| 44 | + | ||
| 45 | + | CaveBot.Recorder.isOn = function() | |
| 46 | + | return isEnabled | |
| 47 | + | end | |
| 48 | + | ||
| 49 | + | CaveBot.Recorder.enable = function() | |
| 50 | + | CaveBot.setOff() | |
| 51 | + | if isEnabled == nil then | |
| 52 | + | setup() | |
| 53 | + | end | |
| 54 | + | CaveBot.Editor.ui.autoRecording:setOn(true) | |
| 55 | + | isEnabled = true | |
| 56 | + | lastPos = nil | |
| 57 | + | end | |
| 58 | + | ||
| 59 | + | CaveBot.Recorder.disable = function() | |
| 60 | + | if isEnabled == true then | |
| 61 | + | isEnabled = false | |
| 62 | + | end | |
| 63 | + | CaveBot.Editor.ui.autoRecording:setOn(false) | |
| 64 | + | CaveBot.save() | |
| 65 | + | end |
| @@ -0,0 +1,30 @@ | |||
| 1 | + | CaveBot.Extensions.Supply = {} | |
| 2 | + | ||
| 3 | + | local ui | |
| 4 | + | ||
| 5 | + | -- first function called, here you should setup your UI | |
| 6 | + | CaveBot.Extensions.Supply.setup = function() | |
| 7 | + | --ui = UI.createWidget('SupplyItemList') | |
| 8 | + | --local widget = UI.createWidget('SupplyItem', ui.list) | |
| 9 | + | --widget.item.onItemChange = function(newItem) | |
| 10 | + | --widget.fields.min.onTextChange = function(newText) | |
| 11 | + | -- 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 | |
| 12 | + | end | |
| 13 | + | ||
| 14 | + | -- called when cavebot config changes, configData is a table but it can be nil | |
| 15 | + | CaveBot.Extensions.Supply.onConfigChange = function(configName, isEnabled, configData) | |
| 16 | + | if not configData then return end | |
| 17 | + | ||
| 18 | + | end | |
| 19 | + | ||
| 20 | + | -- called when cavebot is saving config, should return table or nil | |
| 21 | + | CaveBot.Extensions.Supply.onSave = function() | |
| 22 | + | return {} | |
| 23 | + | end | |
| 24 | + | ||
| 25 | + | -- bellow add you custom functions | |
| 26 | + | -- this function can be used in cavebot function waypoint as: return Supply.run(retries, prev) | |
| 27 | + | -- there are 2 useful parameters - retries (number) and prev (true/false), check actions.lua to learn more | |
| 28 | + | CaveBot.Extensions.Supply.run = function(retries, prev) | |
| 29 | + | return true | |
| 30 | + | end |
| @@ -0,0 +1,72 @@ | |||
| 1 | + | SupplyItem < Panel | |
| 2 | + | height: 34 | |
| 3 | + | ||
| 4 | + | BotItem | |
| 5 | + | id: item | |
| 6 | + | size: 32 32 | |
| 7 | + | anchors.left: parent.left | |
| 8 | + | anchors.top: parent.top | |
| 9 | + | margin-top: 1 | |
| 10 | + | ||
| 11 | + | Panel | |
| 12 | + | id: fields | |
| 13 | + | anchors.top: parent.top | |
| 14 | + | anchors.bottom: parent.bottom | |
| 15 | + | anchors.left: prev.right | |
| 16 | + | anchors.right: parent.right | |
| 17 | + | margin-left: 2 | |
| 18 | + | margin-right: 2 | |
| 19 | + | ||
| 20 | + | Label | |
| 21 | + | id: minLabel | |
| 22 | + | anchors.top: parent.top | |
| 23 | + | anchors.left: parent.left | |
| 24 | + | anchors.right: parent.horizontalCenter | |
| 25 | + | margin-right: 2 | |
| 26 | + | text-align: center | |
| 27 | + | text: "Min" | |
| 28 | + | ||
| 29 | + | Label | |
| 30 | + | id: maxLabel | |
| 31 | + | anchors.top: parent.top | |
| 32 | + | anchors.left: parent.horizontalCenter | |
| 33 | + | anchors.right: parent.right | |
| 34 | + | margin-left: 2 | |
| 35 | + | text-align: center | |
| 36 | + | text: "Max" | |
| 37 | + | ||
| 38 | + | BotTextEdit | |
| 39 | + | id: min | |
| 40 | + | anchors.top: minLabel.bottom | |
| 41 | + | anchors.left: minLabel.left | |
| 42 | + | anchors.right: minLabel.right | |
| 43 | + | text-align: center | |
| 44 | + | text: 1 | |
| 45 | + | ||
| 46 | + | BotTextEdit | |
| 47 | + | id: max | |
| 48 | + | anchors.top: maxLabel.bottom | |
| 49 | + | anchors.left: maxLabel.left | |
| 50 | + | anchors.right: maxLabel.right | |
| 51 | + | text-align: center | |
| 52 | + | text: 100 | |
| 53 | + | ||
| 54 | + | SupplyItemList < Panel | |
| 55 | + | height: 102 | |
| 56 | + | ||
| 57 | + | ScrollablePanel | |
| 58 | + | id: list | |
| 59 | + | anchors.fill: parent | |
| 60 | + | vertical-scrollbar: scroll | |
| 61 | + | margin-right: 7 | |
| 62 | + | layout: | |
| 63 | + | type: verticalBox | |
| 64 | + | cell-height: 34 | |
| 65 | + | ||
| 66 | + | BotSmallScrollBar | |
| 67 | + | id: scroll | |
| 68 | + | anchors.top: prev.top | |
| 69 | + | anchors.bottom: prev.bottom | |
| 70 | + | anchors.right: parent.right | |
| 71 | + | step: 10 | |
| 72 | + | pixels-scroll: true |
| @@ -0,0 +1,192 @@ | |||
| 1 | + | setDefaultTab("HP") | |
| 2 | + | ||
| 3 | + | --2x healing spell | |
| 4 | + | --2x healing rune | |
| 5 | + | --utani hur | |
| 6 | + | --mana shield | |
| 7 | + | --anti paralyze | |
| 8 | + | --4x equip | |
| 9 | + | ||
| 10 | + | UI.Label("Healing spells") | |
| 11 | + | ||
| 12 | + | if type(storage.healing1) ~= "table" then | |
| 13 | + | storage.healing1 = {on=false, title="HP%", text="exura", min=51, max=90} | |
| 14 | + | end | |
| 15 | + | if type(storage.healing2) ~= "table" then | |
| 16 | + | storage.healing2 = {on=false, title="HP%", text="exura vita", min=0, max=50} | |
| 17 | + | end | |
| 18 | + | ||
| 19 | + | -- create 2 healing widgets | |
| 20 | + | for _, healingInfo in ipairs({storage.healing1, storage.healing2}) do | |
| 21 | + | local healingmacro = macro(20, function() | |
| 22 | + | local hp = player:getHealthPercent() | |
| 23 | + | if healingInfo.max >= hp and hp >= healingInfo.min then | |
| 24 | + | if TargetBot then | |
| 25 | + | TargetBot.saySpell(healingInfo.text) -- sync spell with targetbot if available | |
| 26 | + | else | |
| 27 | + | say(healingInfo.text) | |
| 28 | + | end | |
| 29 | + | end | |
| 30 | + | end) | |
| 31 | + | healingmacro.setOn(healingInfo.on) | |
| 32 | + | ||
| 33 | + | UI.DualScrollPanel(healingInfo, function(widget, newParams) | |
| 34 | + | healingInfo = newParams | |
| 35 | + | healingmacro.setOn(healingInfo.on) | |
| 36 | + | end) | |
| 37 | + | end | |
| 38 | + | ||
| 39 | + | UI.Separator() | |
| 40 | + | ||
| 41 | + | UI.Label("Mana & health potions/runes") | |
| 42 | + | ||
| 43 | + | if type(storage.hpitem1) ~= "table" then | |
| 44 | + | storage.hpitem1 = {on=false, title="HP%", item=266, min=51, max=90} | |
| 45 | + | end | |
| 46 | + | if type(storage.hpitem2) ~= "table" then | |
| 47 | + | storage.hpitem2 = {on=false, title="HP%", item=3160, min=0, max=50} | |
| 48 | + | end | |
| 49 | + | if type(storage.manaitem1) ~= "table" then | |
| 50 | + | storage.manaitem1 = {on=false, title="MP%", item=268, min=51, max=90} | |
| 51 | + | end | |
| 52 | + | if type(storage.manaitem2) ~= "table" then | |
| 53 | + | storage.manaitem2 = {on=false, title="MP%", item=3157, min=0, max=50} | |
| 54 | + | end | |
| 55 | + | ||
| 56 | + | for i, healingInfo in ipairs({storage.hpitem1, storage.hpitem2, storage.manaitem1, storage.manaitem2}) do | |
| 57 | + | local healingmacro = macro(20, function() | |
| 58 | + | local hp = i <= 2 and player:getHealthPercent() or math.min(100, math.floor(100 * (player:getMana() / player:getMaxMana()))) | |
| 59 | + | if healingInfo.max >= hp and hp >= healingInfo.min then | |
| 60 | + | if TargetBot then | |
| 61 | + | TargetBot.useItem(healingInfo.item, healingInfo.subType, player) -- sync spell with targetbot if available | |
| 62 | + | else | |
| 63 | + | local thing = g_things.getThingType(healingInfo.item) | |
| 64 | + | local subType = g_game.getClientVersion() >= 860 and 0 or 1 | |
| 65 | + | if thing and thing:isFluidContainer() then | |
| 66 | + | subType = healingInfo.subType | |
| 67 | + | end | |
| 68 | + | g_game.useInventoryItemWith(healingInfo.item, player, subType) | |
| 69 | + | end | |
| 70 | + | end | |
| 71 | + | end) | |
| 72 | + | healingmacro.setOn(healingInfo.on) | |
| 73 | + | ||
| 74 | + | UI.DualScrollItemPanel(healingInfo, function(widget, newParams) | |
| 75 | + | healingInfo = newParams | |
| 76 | + | healingmacro.setOn(healingInfo.on and healingInfo.item > 100) | |
| 77 | + | end) | |
| 78 | + | end | |
| 79 | + | ||
| 80 | + | if g_game.getClientVersion() < 780 then | |
| 81 | + | UI.Label("In old tibia potions & runes work only when you have backpack with them opened") | |
| 82 | + | end | |
| 83 | + | ||
| 84 | + | UI.Separator() | |
| 85 | + | ||
| 86 | + | UI.Label("Mana shield spell:") | |
| 87 | + | UI.TextEdit(storage.manaShield or "utamo vita", function(widget, newText) | |
| 88 | + | storage.manaShield = newText | |
| 89 | + | end) | |
| 90 | + | ||
| 91 | + | local lastManaShield = 0 | |
| 92 | + | macro(20, "mana shield", function() | |
| 93 | + | if hasManaShield() or lastManaShield + 90000 > now then return end | |
| 94 | + | if TargetBot then | |
| 95 | + | TargetBot.saySpell(storage.manaShield) -- sync spell with targetbot if available | |
| 96 | + | else | |
| 97 | + | say(storage.manaShield) | |
| 98 | + | end | |
| 99 | + | end) | |
| 100 | + | ||
| 101 | + | UI.Label("Haste spell:") | |
| 102 | + | UI.TextEdit(storage.hasteSpell or "utani hur", function(widget, newText) | |
| 103 | + | storage.hasteSpell = newText | |
| 104 | + | end) | |
| 105 | + | ||
| 106 | + | macro(500, "haste", function() | |
| 107 | + | if hasHaste() then return end | |
| 108 | + | if TargetBot then | |
| 109 | + | TargetBot.saySpell(storage.hasteSpell) -- sync spell with targetbot if available | |
| 110 | + | else | |
| 111 | + | say(storage.hasteSpell) | |
| 112 | + | end | |
| 113 | + | end) | |
| 114 | + | ||
| 115 | + | UI.Label("Anti paralyze spell:") | |
| 116 | + | UI.TextEdit(storage.antiParalyze or "utani hur", function(widget, newText) | |
| 117 | + | storage.antiParalyze = newText | |
| 118 | + | end) | |
| 119 | + | ||
| 120 | + | macro(100, "anti paralyze", function() | |
| 121 | + | if not isParalyzed() then return end | |
| 122 | + | if TargetBot then | |
| 123 | + | TargetBot.saySpell(storage.antiParalyze) -- sync spell with targetbot if available | |
| 124 | + | else | |
| 125 | + | say(storage.antiParalyze) | |
| 126 | + | end | |
| 127 | + | end) | |
| 128 | + | ||
| 129 | + | UI.Separator() | |
| 130 | + | ||
| 131 | + | UI.Label("Eatable items:") | |
| 132 | + | if type(storage.foodItems) ~= "table" then | |
| 133 | + | storage.foodItems = {3582, 3577} | |
| 134 | + | end | |
| 135 | + | ||
| 136 | + | local foodContainer = UI.Container(function(widget, items) | |
| 137 | + | storage.foodItems = items | |
| 138 | + | end, true) | |
| 139 | + | foodContainer:setHeight(35) | |
| 140 | + | foodContainer:setItems(storage.foodItems) | |
| 141 | + | ||
| 142 | + | macro(10000, "eat food", function() | |
| 143 | + | if not storage.foodItems[1] then return end | |
| 144 | + | -- search for food in containers | |
| 145 | + | for _, container in pairs(g_game.getContainers()) do | |
| 146 | + | for __, item in ipairs(container:getItems()) do | |
| 147 | + | for i, foodItem in ipairs(storage.foodItems) do | |
| 148 | + | if item:getId() == foodItem.id then | |
| 149 | + | return g_game.use(item) | |
| 150 | + | end | |
| 151 | + | end | |
| 152 | + | end | |
| 153 | + | end | |
| 154 | + | -- can't find any food, try to eat random item using hotkey | |
| 155 | + | if g_game.getClientVersion() < 780 then return end -- hotkey's dont work on old tibia | |
| 156 | + | local toEat = storage.foodItems[math.random(1, #storage.foodItems)] | |
| 157 | + | if toEat then g_game.useInventoryItem(toEat.id) end | |
| 158 | + | end) | |
| 159 | + | ||
| 160 | + | UI.Separator() | |
| 161 | + | UI.Label("Auto equip") | |
| 162 | + | ||
| 163 | + | if type(storage.autoEquip) ~= "table" then | |
| 164 | + | storage.autoEquip = {} | |
| 165 | + | end | |
| 166 | + | for i=1,4 do -- if you want more auto equip panels you can change 4 to higher value | |
| 167 | + | if not storage.autoEquip[i] then | |
| 168 | + | 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} | |
| 169 | + | end | |
| 170 | + | UI.TwoItemsAndSlotPanel(storage.autoEquip[i], function(widget, newParams) | |
| 171 | + | storage.autoEquip[i] = newParams | |
| 172 | + | end) | |
| 173 | + | end | |
| 174 | + | macro(250, function() | |
| 175 | + | local containers = g_game.getContainers() | |
| 176 | + | for index, autoEquip in ipairs(storage.autoEquip) do | |
| 177 | + | if autoEquip.on then | |
| 178 | + | local slotItem = getSlot(autoEquip.slot) | |
| 179 | + | if not slotItem or (slotItem:getId() ~= autoEquip.item1 and slotItem:getId() ~= autoEquip.item2) then | |
| 180 | + | for _, container in pairs(containers) do | |
| 181 | + | for __, item in ipairs(container:getItems()) do | |
| 182 | + | if item:getId() == autoEquip.item1 or item:getId() == autoEquip.item2 then | |
| 183 | + | g_game.move(item, {x=65535, y=autoEquip.slot, z=0}, item:getCount()) | |
| 184 | + | delay(1000) -- don't call it too often | |
| 185 | + | return | |
| 186 | + | end | |
| 187 | + | end | |
| 188 | + | end | |
| 189 | + | end | |
| 190 | + | end | |
| 191 | + | end | |
| 192 | + | end) |
| @@ -0,0 +1,22 @@ | |||
| 1 | + | -- main tab | |
| 2 | + | VERSION = "1.2" | |
| 3 | + | ||
| 4 | + | UI.Label("Config version: " .. VERSION) | |
| 5 | + | ||
| 6 | + | UI.Separator() | |
| 7 | + | ||
| 8 | + | ||
| 9 | + | ||
| 10 | + | UI.Separator() | |
| 11 | + | ||
| 12 | + | UI.Button("Discord", function() | |
| 13 | + | g_platform.openUrl("https://discord.gg/yhqBE4A") | |
| 14 | + | end) | |
| 15 | + | ||
| 16 | + | UI.Button("Forum", function() | |
| 17 | + | g_platform.openUrl("http://otclient.net/") | |
| 18 | + | end) | |
| 19 | + | ||
| 20 | + | UI.Button("Help & Tutorials", function() | |
| 21 | + | g_platform.openUrl("http://bot.otclient.ovh/") | |
| 22 | + | end) |
| @@ -0,0 +1,41 @@ | |||
| 1 | + | -- Magic wall & Wild growth timer | |
| 2 | + | ||
| 3 | + | -- config | |
| 4 | + | local magicWallId = 2129 | |
| 5 | + | local magicWallTime = 20000 | |
| 6 | + | local wildGrowthId = 2130 | |
| 7 | + | local wildGrowthTime = 45000 | |
| 8 | + | ||
| 9 | + | -- script | |
| 10 | + | local activeTimers = {} | |
| 11 | + | ||
| 12 | + | onAddThing(function(tile, thing) | |
| 13 | + | if not thing:isItem() then | |
| 14 | + | return | |
| 15 | + | end | |
| 16 | + | local timer = 0 | |
| 17 | + | if thing:getId() == magicWallId then | |
| 18 | + | timer = magicWallTime | |
| 19 | + | elseif thing:getId() == wildGrowthId then | |
| 20 | + | timer = wildGrowthTime | |
| 21 | + | else | |
| 22 | + | return | |
| 23 | + | end | |
| 24 | + | ||
| 25 | + | local pos = tile:getPosition().x .. "," .. tile:getPosition().y .. "," .. tile:getPosition().z | |
| 26 | + | if not activeTimers[pos] or activeTimers[pos] < now then | |
| 27 | + | activeTimers[pos] = now + timer | |
| 28 | + | end | |
| 29 | + | tile:setTimer(activeTimers[pos] - now) | |
| 30 | + | end) | |
| 31 | + | ||
| 32 | + | onRemoveThing(function(tile, thing) | |
| 33 | + | if not thing:isItem() then | |
| 34 | + | return | |
| 35 | + | end | |
| 36 | + | if (thing:getId() == magicWallId or thing:getId() == wildGrowthId) and tile:getGround() then | |
| 37 | + | local pos = tile:getPosition().x .. "," .. tile:getPosition().y .. "," .. tile:getPosition().z | |
| 38 | + | activeTimers[pos] = nil | |
| 39 | + | tile:setTimer(0) | |
| 40 | + | end | |
| 41 | + | end) |
| @@ -0,0 +1,93 @@ | |||
| 1 | + | ||
| 2 | + | TargetBot.Creature = {} | |
| 3 | + | TargetBot.Creature.configsCache = {} | |
| 4 | + | TargetBot.Creature.cached = 0 | |
| 5 | + | ||
| 6 | + | TargetBot.Creature.resetConfigs = function() | |
| 7 | + | TargetBot.targetList:destroyChildren() | |
| 8 | + | TargetBot.Creature.resetConfigsCache() | |
| 9 | + | end | |
| 10 | + | ||
| 11 | + | TargetBot.Creature.resetConfigsCache = function() | |
| 12 | + | TargetBot.Creature.configsCache = {} | |
| 13 | + | TargetBot.Creature.cached = 0 | |
| 14 | + | end | |
| 15 | + | ||
| 16 | + | TargetBot.Creature.addConfig = function(config, focus) | |
| 17 | + | if type(config) ~= 'table' or type(config.name) ~= 'string' then | |
| 18 | + | return error("Invalid targetbot creature config (missing name)") | |
| 19 | + | end | |
| 20 | + | TargetBot.Creature.resetConfigsCache() | |
| 21 | + | ||
| 22 | + | if not config.regex then | |
| 23 | + | config.regex = "^" .. config.name:trim():lower():gsub("%*", ".*"):gsub("%?", ".?") .. "$" | |
| 24 | + | end | |
| 25 | + | ||
| 26 | + | local widget = UI.createWidget("TargetBotEntry", TargetBot.targetList) | |
| 27 | + | widget:setText(config.name) | |
| 28 | + | widget.value = config | |
| 29 | + | ||
| 30 | + | widget.onDoubleClick = function(entry) -- edit on double click | |
| 31 | + | schedule(20, function() -- schedule to have correct focus | |
| 32 | + | TargetBot.Creature.edit(entry.value, function(newConfig) | |
| 33 | + | entry:setText(newConfig.name) | |
| 34 | + | entry.value = newConfig | |
| 35 | + | TargetBot.Creature.resetConfigsCache() | |
| 36 | + | TargetBot.save() | |
| 37 | + | end) | |
| 38 | + | end) | |
| 39 | + | end | |
| 40 | + | ||
| 41 | + | if focus then | |
| 42 | + | widget:focus() | |
| 43 | + | TargetBot.targetList:ensureChildVisible(widget) | |
| 44 | + | end | |
| 45 | + | return widget | |
| 46 | + | end | |
| 47 | + | ||
| 48 | + | TargetBot.Creature.getConfigs = function(creature) | |
| 49 | + | if not creature then return {} end | |
| 50 | + | local name = creature:getName():trim():lower() | |
| 51 | + | -- this function may be slow, so it will be using cache | |
| 52 | + | if TargetBot.Creature.configsCache[name] then | |
| 53 | + | return TargetBot.Creature.configsCache[name] | |
| 54 | + | end | |
| 55 | + | local configs = {} | |
| 56 | + | for _, config in ipairs(TargetBot.targetList:getChildren()) do | |
| 57 | + | if regexMatch(name, config.value.regex)[1] then | |
| 58 | + | table.insert(configs, config.value) | |
| 59 | + | end | |
| 60 | + | end | |
| 61 | + | if TargetBot.Creature.cached > 1000 then | |
| 62 | + | TargetBot.Creature.resetConfigsCache() -- too big cache size, reset | |
| 63 | + | end | |
| 64 | + | TargetBot.Creature.configsCache[name] = configs -- add to cache | |
| 65 | + | TargetBot.Creature.cached = TargetBot.Creature.cached + 1 | |
| 66 | + | return configs | |
| 67 | + | end | |
| 68 | + | ||
| 69 | + | TargetBot.Creature.calculateParams = function(creature, path) | |
| 70 | + | local configs = TargetBot.Creature.getConfigs(creature) | |
| 71 | + | local priority = 0 | |
| 72 | + | local danger = 0 | |
| 73 | + | local selectedConfig = nil | |
| 74 | + | for _, config in ipairs(configs) do | |
| 75 | + | local config_priority = TargetBot.Creature.calculatePriority(creature, config, path) | |
| 76 | + | if config_priority > priority then | |
| 77 | + | priority = config_priority | |
| 78 | + | danger = TargetBot.Creature.calculateDanger(creature, config, path) | |
| 79 | + | selectedConfig = config | |
| 80 | + | end | |
| 81 | + | end | |
| 82 | + | return { | |
| 83 | + | config = selectedConfig, | |
| 84 | + | creature = creature, | |
| 85 | + | danger = danger, | |
| 86 | + | priority = priority | |
| 87 | + | } | |
| 88 | + | end | |
| 89 | + | ||
| 90 | + | TargetBot.Creature.calculateDanger = function(creature, config, path) | |
| 91 | + | -- config is based on creature_editor | |
| 92 | + | return config.danger | |
| 93 | + | end |
| @@ -0,0 +1,112 @@ | |||
| 1 | + | TargetBot.Creature.attack = function(params, targets, isLooting) -- params {config, creature, danger, priority} | |
| 2 | + | if player:isWalking() then | |
| 3 | + | lastWalk = now | |
| 4 | + | end | |
| 5 | + | ||
| 6 | + | local config = params.config | |
| 7 | + | local creature = params.creature | |
| 8 | + | ||
| 9 | + | if g_game.getAttackingCreature() ~= creature then | |
| 10 | + | g_game.attack(creature) | |
| 11 | + | end | |
| 12 | + | ||
| 13 | + | if not isLooting then -- walk only when not looting | |
| 14 | + | TargetBot.Creature.walk(creature, config, targets) | |
| 15 | + | end | |
| 16 | + | ||
| 17 | + | -- attacks | |
| 18 | + | local mana = player:getMana() | |
| 19 | + | if config.useGroupAttack and config.groupAttackSpell:len() > 1 and mana > config.minManaGroup then | |
| 20 | + | local creatures = g_map.getSpectatorsInRange(player:getPosition(), false, config.groupAttackRadius, config.groupAttackRadius) | |
| 21 | + | local playersAround = false | |
| 22 | + | local monsters = 0 | |
| 23 | + | for _, creature in ipairs(creatures) do | |
| 24 | + | if not creature:isLocalPlayer() and creature:isPlayer() and (not config.groupAttackIgnoreParty or creature:getShield() <= 2) then | |
| 25 | + | playersAround = true | |
| 26 | + | elseif creature:isMonster() then | |
| 27 | + | monsters = monsters + 1 | |
| 28 | + | end | |
| 29 | + | end | |
| 30 | + | if monsters >= config.groupAttackTargets and (not playersAround or config.groupAttackIgnorePlayers) then | |
| 31 | + | if TargetBot.sayAttackSpell(config.groupAttackSpell, config.groupAttackDelay) then | |
| 32 | + | return | |
| 33 | + | end | |
| 34 | + | end | |
| 35 | + | end | |
| 36 | + | ||
| 37 | + | if config.useGroupAttackRune and config.groupAttackRune > 100 then | |
| 38 | + | local creatures = g_map.getSpectatorsInRange(creature:getPosition(), false, config.groupRuneAttackRadius, config.groupRuneAttackRadius) | |
| 39 | + | local playersAround = false | |
| 40 | + | local monsters = 0 | |
| 41 | + | for _, creature in ipairs(creatures) do | |
| 42 | + | if not creature:isLocalPlayer() and creature:isPlayer() and (not config.groupAttackIgnoreParty or creature:getShield() <= 2) then | |
| 43 | + | playersAround = true | |
| 44 | + | elseif creature:isMonster() then | |
| 45 | + | monsters = monsters + 1 | |
| 46 | + | end | |
| 47 | + | end | |
| 48 | + | if monsters >= config.groupRuneAttackTargets and (not playersAround or config.groupAttackIgnorePlayers) then | |
| 49 | + | if TargetBot.useAttackItem(config.groupAttackRune, 0, creature, config.groupRuneAttackDelay) then | |
| 50 | + | return | |
| 51 | + | end | |
| 52 | + | end | |
| 53 | + | end | |
| 54 | + | if config.useSpellAttack and config.attackSpell:len() > 1 and mana > config.minMana then | |
| 55 | + | if TargetBot.sayAttackSpell(config.attackSpell, config.attackSpellDelay) then | |
| 56 | + | return | |
| 57 | + | end | |
| 58 | + | end | |
| 59 | + | if config.useRuneAttack and config.attackRune > 100 then | |
| 60 | + | if TargetBot.useAttackItem(config.attackRune, 0, creature, config.attackRuneDelay) then | |
| 61 | + | return | |
| 62 | + | end | |
| 63 | + | end | |
| 64 | + | end | |
| 65 | + | ||
| 66 | + | TargetBot.Creature.walk = function(creature, config, targets) | |
| 67 | + | local cpos = creature:getPosition() | |
| 68 | + | local pos = player:getPosition() | |
| 69 | + | ||
| 70 | + | -- luring | |
| 71 | + | if (config.lure or config.lureCavebot) and not (config.chase and creature:getHealthPercent() < 30) then | |
| 72 | + | local monsters = 0 | |
| 73 | + | if targets < config.lureCount then | |
| 74 | + | if config.lureCavebot then | |
| 75 | + | return TargetBot.allowCaveBot(200) | |
| 76 | + | else | |
| 77 | + | local path = findPath(pos, cpos, 5, {ignoreNonPathable=true, precision=2}) | |
| 78 | + | if path then | |
| 79 | + | return TargetBot.walkTo(cpos, 10, {marginMin=5, marginMax=6, ignoreNonPathable=true}) | |
| 80 | + | end | |
| 81 | + | end | |
| 82 | + | end | |
| 83 | + | end | |
| 84 | + | ||
| 85 | + | local currentDistance = findPath(pos, cpos, 10, {ignoreCreatures=true, ignoreNonPathable=true, ignoreCost=true}) | |
| 86 | + | if config.chase and (creature:getHealthPercent() < 30 or not config.keepDistance) then | |
| 87 | + | if #currentDistance > 1 then | |
| 88 | + | return TargetBot.walkTo(cpos, 10, {ignoreNonPathable=true, precision=1}) | |
| 89 | + | end | |
| 90 | + | elseif config.keepDistance then | |
| 91 | + | if #currentDistance ~= config.keepDistanceRange and #currentDistance ~= config.keepDistanceRange + 1 then | |
| 92 | + | return TargetBot.walkTo(cpos, 10, {ignoreNonPathable=true, marginMin=config.keepDistanceRange, marginMax=config.keepDistanceRange + 1}) | |
| 93 | + | end | |
| 94 | + | end | |
| 95 | + | ||
| 96 | + | if config.avoidAttacks then | |
| 97 | + | local diffx = cpos.x - pos.x | |
| 98 | + | local diffy = cpos.y - pos.y | |
| 99 | + | local candidates = {} | |
| 100 | + | if math.abs(diffx) == 1 and diffy == 0 then | |
| 101 | + | candidates = {{x=pos.x, y=pos.y-1, z=pos.z}, {x=pos.x, y=pos.y+1, z=pos.z}} | |
| 102 | + | elseif diffx == 0 and math.abs(diffy) == 1 then | |
| 103 | + | candidates = {{x=pos.x-1, y=pos.y, z=pos.z}, {x=pos.x+1, y=pos.y, z=pos.z}} | |
| 104 | + | end | |
| 105 | + | for _, candidate in ipairs(candidates) do | |
| 106 | + | local tile = g_map.getTile(candidate) | |
| 107 | + | if tile and tile:isWalkable() then | |
| 108 | + | return TargetBot.walkTo(candidate, 2, {ignoreNonPathable=true}) | |
| 109 | + | end | |
| 110 | + | end | |
| 111 | + | end | |
| 112 | + | end |
| @@ -0,0 +1,105 @@ | |||
| 1 | + | TargetBot.Creature.edit = function(config, callback) -- callback = function(newConfig) | |
| 2 | + | config = config or {} | |
| 3 | + | ||
| 4 | + | local editor = UI.createWindow('TargetBotCreatureEditorWindow') | |
| 5 | + | local values = {} -- (key, function returning value of key) | |
| 6 | + | ||
| 7 | + | editor.name:setText(config.name or "") | |
| 8 | + | table.insert(values, {"name", function() return editor.name:getText() end}) | |
| 9 | + | ||
| 10 | + | local addScrollBar = function(id, title, min, max, defaultValue) | |
| 11 | + | local widget = UI.createWidget('TargetBotCreatureEditorScrollBar', editor.left) | |
| 12 | + | widget.scroll.onValueChange = function(scroll, value) | |
| 13 | + | widget.text:setText(title .. ": " .. value) | |
| 14 | + | end | |
| 15 | + | widget.scroll:setRange(min, max) | |
| 16 | + | if max-min > 1000 then | |
| 17 | + | widget.scroll:setStep(100) | |
| 18 | + | elseif max-min > 100 then | |
| 19 | + | widget.scroll:setStep(10) | |
| 20 | + | end | |
| 21 | + | widget.scroll:setValue(config[id] or defaultValue) | |
| 22 | + | widget.scroll.onValueChange(widget.scroll, widget.scroll:getValue()) | |
| 23 | + | table.insert(values, {id, function() return widget.scroll:getValue() end}) | |
| 24 | + | end | |
| 25 | + | ||
| 26 | + | local addTextEdit = function(id, title, defaultValue) | |
| 27 | + | local widget = UI.createWidget('TargetBotCreatureEditorTextEdit', editor.right) | |
| 28 | + | widget.text:setText(title) | |
| 29 | + | widget.textEdit:setText(config[id] or defaultValue or "") | |
| 30 | + | table.insert(values, {id, function() return widget.textEdit:getText() end}) | |
| 31 | + | end | |
| 32 | + | ||
| 33 | + | local addCheckBox = function(id, title, defaultValue) | |
| 34 | + | local widget = UI.createWidget('TargetBotCreatureEditorCheckBox', editor.right) | |
| 35 | + | widget.onClick = function() | |
| 36 | + | widget:setOn(not widget:isOn()) | |
| 37 | + | end | |
| 38 | + | widget:setText(title) | |
| 39 | + | if config[id] == nil then | |
| 40 | + | widget:setOn(defaultValue) | |
| 41 | + | else | |
| 42 | + | widget:setOn(config[id]) | |
| 43 | + | end | |
| 44 | + | table.insert(values, {id, function() return widget:isOn() end}) | |
| 45 | + | end | |
| 46 | + | ||
| 47 | + | local addItem = function(id, title, defaultItem) | |
| 48 | + | local widget = UI.createWidget('TargetBotCreatureEditorItem', editor.right) | |
| 49 | + | widget.text:setText(title) | |
| 50 | + | widget.item:setItemId(config[id] or defaultItem) | |
| 51 | + | table.insert(values, {id, function() return widget.item:getItemId() end}) | |
| 52 | + | end | |
| 53 | + | ||
| 54 | + | editor.cancel.onClick = function() | |
| 55 | + | editor:destroy() | |
| 56 | + | end | |
| 57 | + | editor.onEscape = editor.cancel.onClick | |
| 58 | + | ||
| 59 | + | editor.ok.onClick = function() | |
| 60 | + | local newConfig = {} | |
| 61 | + | for _, value in ipairs(values) do | |
| 62 | + | newConfig[value[1]] = value[2]() | |
| 63 | + | end | |
| 64 | + | if newConfig.name:len() < 1 then return end | |
| 65 | + | newConfig.regex = "^" .. newConfig.name:trim():lower():gsub("%*", ".*"):gsub("%?", ".?") .. "$" | |
| 66 | + | ||
| 67 | + | editor:destroy() | |
| 68 | + | callback(newConfig) | |
| 69 | + | end | |
| 70 | + | ||
| 71 | + | -- values | |
| 72 | + | addScrollBar("priority", "Priority", 0, 10, 1) | |
| 73 | + | addScrollBar("danger", "Danger", 0, 10, 1) | |
| 74 | + | addScrollBar("maxDistance", "Max distance", 1, 10, 10) | |
| 75 | + | addScrollBar("keepDistanceRange", "Keep distance", 1, 5, 1) | |
| 76 | + | addScrollBar("lureCount", "Lure", 0, 5, 1) | |
| 77 | + | ||
| 78 | + | addScrollBar("minMana", "Min. mana for attack spell", 0, 3000, 200) | |
| 79 | + | addScrollBar("attackSpellDelay", "Attack spell delay", 200, 5000, 2500) | |
| 80 | + | addScrollBar("minManaGroup", "Min. mana for group attack", 0, 3000, 1500) | |
| 81 | + | addScrollBar("groupAttackTargets", "Min. targets for group attack", 1, 10, 2) | |
| 82 | + | addScrollBar("groupAttackRadius", "Radius of group attack spell", 1, 7, 1) | |
| 83 | + | addScrollBar("groupAttackDelay", "Group attack spell delay", 200, 60000, 5000) | |
| 84 | + | addScrollBar("runeAttackDelay", "Rune attack delay", 200, 5000, 2000) | |
| 85 | + | addScrollBar("groupRuneAttackTargets", "Min. targets for group rune attack", 1, 10, 2) | |
| 86 | + | addScrollBar("groupRuneAttackRadius", "Radius of group rune attack", 1, 7, 1) | |
| 87 | + | addScrollBar("groupRuneAttackDelay", "Group rune attack delay", 200, 60000, 5000) | |
| 88 | + | ||
| 89 | + | addCheckBox("chase", "Chase", true) | |
| 90 | + | addCheckBox("keepDistance", "Keep Distance", false) | |
| 91 | + | addCheckBox("lure", "Lure", false) | |
| 92 | + | addCheckBox("lureCavebot", "Lure using cavebot", false) | |
| 93 | + | addCheckBox("avoidAttacks", "Avoid wave attacks", false) | |
| 94 | + | ||
| 95 | + | addCheckBox("useSpellAttack", "Use attack spell", false) | |
| 96 | + | addTextEdit("attackSpell", "Attack spell", "") | |
| 97 | + | addCheckBox("useRuneAttack", "Use attack rune", false) | |
| 98 | + | addItem("attackRune", "Attack rune:", 0) | |
| 99 | + | addCheckBox("useGroupAttack", "Use group attack spell", false) | |
| 100 | + | addTextEdit("groupAttackSpell", "Group attack spell", "") | |
| 101 | + | addCheckBox("useGroupAttackRune", "Use group attack rune", false) | |
| 102 | + | addItem("groupAttackRune", "Group attack rune:", 0) | |
| 103 | + | addCheckBox("groupAttackIgnorePlayers", "Ignore players in group attack", false) | |
| 104 | + | addCheckBox("groupAttackIgnoreParty", "Ignore party in group attack", false) | |
| 105 | + | end |
| @@ -0,0 +1,134 @@ | |||
| 1 | + | TargetBotCreatureEditorScrollBar < Panel | |
| 2 | + | height: 28 | |
| 3 | + | margin-top: 3 | |
| 4 | + | ||
| 5 | + | Label | |
| 6 | + | id: text | |
| 7 | + | anchors.left: parent.left | |
| 8 | + | anchors.right: parent.right | |
| 9 | + | anchors.top: parent.top | |
| 10 | + | text-align: center | |
| 11 | + | ||
| 12 | + | HorizontalScrollBar | |
| 13 | + | id: scroll | |
| 14 | + | anchors.left: parent.left | |
| 15 | + | anchors.right: parent.right | |
| 16 | + | anchors.top: prev.bottom | |
| 17 | + | margin-top: 3 | |
| 18 | + | minimum: 0 | |
| 19 | + | maximum: 10 | |
| 20 | + | step: 1 | |
| 21 | + | ||
| 22 | + | TargetBotCreatureEditorTextEdit < Panel | |
| 23 | + | height: 40 | |
| 24 | + | margin-top: 7 | |
| 25 | + | ||
| 26 | + | Label | |
| 27 | + | id: text | |
| 28 | + | anchors.left: parent.left | |
| 29 | + | anchors.right: parent.right | |
| 30 | + | anchors.top: parent.top | |
| 31 | + | text-align: center | |
| 32 | + | ||
| 33 | + | TextEdit | |
| 34 | + | id: textEdit | |
| 35 | + | anchors.left: parent.left | |
| 36 | + | anchors.right: parent.right | |
| 37 | + | anchors.top: prev.bottom | |
| 38 | + | margin-top: 5 | |
| 39 | + | minimum: 0 | |
| 40 | + | maximum: 10 | |
| 41 | + | step: 1 | |
| 42 | + | ||
| 43 | + | TargetBotCreatureEditorItem < Panel | |
| 44 | + | height: 34 | |
| 45 | + | margin-top: 7 | |
| 46 | + | margin-left: 25 | |
| 47 | + | margin-right: 25 | |
| 48 | + | ||
| 49 | + | Label | |
| 50 | + | id: text | |
| 51 | + | anchors.left: parent.left | |
| 52 | + | anchors.verticalCenter: next.verticalCenter | |
| 53 | + | ||
| 54 | + | BotItem | |
| 55 | + | id: item | |
| 56 | + | anchors.top: parent.top | |
| 57 | + | anchors.right: parent.right | |
| 58 | + | ||
| 59 | + | ||
| 60 | + | TargetBotCreatureEditorCheckBox < BotSwitch | |
| 61 | + | height: 20 | |
| 62 | + | margin-top: 7 | |
| 63 | + | ||
| 64 | + | TargetBotCreatureEditorWindow < MainWindow | |
| 65 | + | text: TargetBot creature editor | |
| 66 | + | width: 500 | |
| 67 | + | height: 600 | |
| 68 | + | ||
| 69 | + | Label | |
| 70 | + | anchors.left: parent.left | |
| 71 | + | anchors.right: parent.right | |
| 72 | + | anchors.top: parent.top | |
| 73 | + | text-align: center | |
| 74 | + | text: You can use * (any characters) and ? (any character) in target name | |
| 75 | + | ||
| 76 | + | TextEdit | |
| 77 | + | id: name | |
| 78 | + | anchors.top: prev.bottom | |
| 79 | + | anchors.left: parent.left | |
| 80 | + | anchors.right: parent.right | |
| 81 | + | margin-left: 90 | |
| 82 | + | margin-top: 5 | |
| 83 | + | ||
| 84 | + | Label | |
| 85 | + | anchors.verticalCenter: prev.verticalCenter | |
| 86 | + | anchors.left: parent.left | |
| 87 | + | text: Target name: | |
| 88 | + | ||
| 89 | + | Panel | |
| 90 | + | id: left | |
| 91 | + | anchors.top: name.bottom | |
| 92 | + | anchors.left: parent.left | |
| 93 | + | anchors.right: parent.horizontalCenter | |
| 94 | + | margin-top: 5 | |
| 95 | + | margin-left: 10 | |
| 96 | + | margin-right: 10 | |
| 97 | + | layout: | |
| 98 | + | type: verticalBox | |
| 99 | + | fit-children: true | |
| 100 | + | ||
| 101 | + | Panel | |
| 102 | + | id: right | |
| 103 | + | anchors.top: name.bottom | |
| 104 | + | anchors.left: parent.horizontalCenter | |
| 105 | + | anchors.right: parent.right | |
| 106 | + | margin-top: 5 | |
| 107 | + | margin-left: 10 | |
| 108 | + | margin-right: 10 | |
| 109 | + | layout: | |
| 110 | + | type: verticalBox | |
| 111 | + | fit-children: true | |
| 112 | + | ||
| 113 | + | Button | |
| 114 | + | id: help | |
| 115 | + | !text: tr('Help & Tutorials') | |
| 116 | + | anchors.bottom: parent.bottom | |
| 117 | + | anchors.left: parent.left | |
| 118 | + | width: 150 | |
| 119 | + | @onClick: g_platform.openUrl("http://bot.otclient.ovh/") | |
| 120 | + | ||
| 121 | + | Button | |
| 122 | + | id: ok | |
| 123 | + | !text: tr('Ok') | |
| 124 | + | anchors.bottom: parent.bottom | |
| 125 | + | anchors.right: next.left | |
| 126 | + | margin-right: 10 | |
| 127 | + | width: 60 | |
| 128 | + | ||
| 129 | + | Button | |
| 130 | + | id: cancel | |
| 131 | + | !text: tr('Cancel') | |
| 132 | + | anchors.bottom: parent.bottom | |
| 133 | + | anchors.right: parent.right | |
| 134 | + | width: 60 |