Version 1.7

This commit is contained in:
OTCv8 2020-01-08 00:24:01 +01:00
parent 1d2bdf855d
commit 2a10e65ec0
42 changed files with 1157 additions and 563 deletions

View File

@ -1,6 +1,6 @@
# OTClientV8 # OTClientV8
Tibia client design for versions 7.40 - 11.00 Tibia client designed for versions 7.40 - 11.00.
It's based on https://github.com/edubart/otclient and it's not backward compatible. It's based on https://github.com/edubart/otclient and it's not backward compatible.
## DISCORD: https://discord.gg/feySup6 ## DISCORD: https://discord.gg/feySup6

View File

@ -5,7 +5,7 @@ APP_VERSION = 1337 -- client version for updater and login to identify outd
-- If you don't use updater or other service, set it to updater = "" -- If you don't use updater or other service, set it to updater = ""
Services = { Services = {
website = "http://otclient.ovh", -- currently not used website = "http://otclient.ovh", -- currently not used
updater = "http://otclient.ovh/api/updater.php", updater = "",
news = "http://otclient.ovh/api/news.php", news = "http://otclient.ovh/api/news.php",
stats = "", stats = "",
crash = "http://otclient.ovh/api/crash.php", crash = "http://otclient.ovh/api/crash.php",

View File

@ -235,11 +235,8 @@ function CharacterList.terminate()
CharacterList = nil CharacterList = nil
end end
function CharacterList.create(characters, account, otui, websocket) function CharacterList.create(characters, account, otui)
if not otui then otui = 'characterlist' end if not otui then otui = 'characterlist' end
if websocket then
websocket:close()
end
if charactersWindow then if charactersWindow then
charactersWindow:destroy() charactersWindow:destroy()
end end

View File

@ -17,9 +17,6 @@ local serverHostTextEdit
local rememberPasswordBox local rememberPasswordBox
local protos = {"740", "760", "772", "792", "800", "810", "854", "860", "1077", "1090", "1096", "1098", "1099", "1100"} local protos = {"740", "760", "772", "792", "800", "810", "854", "860", "1077", "1090", "1096", "1098", "1099", "1100"}
local webSocket
local webSocketLoginPacket
-- private functions -- private functions
local function onProtocolError(protocol, message, errorCode) local function onProtocolError(protocol, message, errorCode)
if errorCode then if errorCode then
@ -54,13 +51,9 @@ local function onCharacterList(protocol, characters, account, otui)
loadBox = nil loadBox = nil
end end
CharacterList.create(characters, account, otui, webSocket) CharacterList.create(characters, account, otui)
CharacterList.show() CharacterList.show()
if webSocket then
webSocket = nil
end
g_settings.save() g_settings.save()
end end
@ -137,10 +130,6 @@ local function onHTTPResult(data, err)
if #incorrectThings > 0 then if #incorrectThings > 0 then
g_logger.info(incorrectThings) g_logger.info(incorrectThings)
if Updater then if Updater then
if webSocket then
webSocket:close()
webSocket = nil
end
return Updater.updateThings(things, incorrectThings) return Updater.updateThings(things, incorrectThings)
else else
return EnterGame.onError(incorrectThings) return EnterGame.onError(incorrectThings)
@ -186,7 +175,7 @@ local function onHTTPResult(data, err)
g_proxy.clear() g_proxy.clear()
if proxies then if proxies then
for i, proxy in ipairs(proxies) do for i, proxy in ipairs(proxies) do
g_proxy.addProxy(tonumber(proxy["localPort"]), proxy["host"], tonumber(proxy["port"]), tonumber(proxy["priority"])) g_proxy.addProxy(proxy["host"], tonumber(proxy["port"]), tonumber(proxy["priority"]))
end end
end end
end end
@ -198,7 +187,6 @@ end
-- public functions -- public functions
function EnterGame.init() function EnterGame.init()
enterGame = g_ui.displayUI('entergame') enterGame = g_ui.displayUI('entergame')
newLogin = g_ui.displayUI('entergame_new')
serverSelectorPanel = enterGame:getChildById('serverSelectorPanel') serverSelectorPanel = enterGame:getChildById('serverSelectorPanel')
customServerSelectorPanel = enterGame:getChildById('customServerSelectorPanel') customServerSelectorPanel = enterGame:getChildById('customServerSelectorPanel')
@ -260,15 +248,7 @@ end
function EnterGame.terminate() function EnterGame.terminate()
g_keyboard.unbindKeyDown('Ctrl+G') g_keyboard.unbindKeyDown('Ctrl+G')
if webSocket then
webSocket.close()
webSocket = nil
end
enterGame:destroy() enterGame:destroy()
if newLogin then
newLogin:destroy()
end
if loadBox then if loadBox then
loadBox:destroy() loadBox:destroy()
loadBox = nil loadBox = nil
@ -288,12 +268,10 @@ function EnterGame.show()
enterGame:raise() enterGame:raise()
enterGame:focus() enterGame:focus()
enterGame:getChildById('accountNameTextEdit'):focus() enterGame:getChildById('accountNameTextEdit'):focus()
EnterGame.checkWebsocket()
end end
function EnterGame.hide() function EnterGame.hide()
enterGame:hide() enterGame:hide()
newLogin:hide()
end end
function EnterGame.openWindow() function EnterGame.openWindow()
@ -313,80 +291,8 @@ function EnterGame.clearAccountFields()
g_settings.remove('password') g_settings.remove('password')
end end
function EnterGame.checkWebsocket()
if enterGame:isHidden() then return end
local url = serverHostTextEdit:getText()
if url:find("ws://") == nil and url:find("wss://") == nil then
if webSocket then
webSocket:close()
webSocket = nil
end
return
end
if webSocket then
if webSocket.url == url then
return
end
webSocket:close()
webSocket = nil
end
webSocket = HTTP.WebSocketJSON(url, {
onOpen = function(message, webSocketId)
if webSocket and webSocket.id == webSocketId then
webSocket.send({type="init", uid=G.UUID, version=APP_VERSION})
end
end,
onMessage = function(message, webSocketId)
if webSocket and webSocket.id == webSocketId then
if message.type == "login" then
webSocketLoginPacket = nil
EnterGame.hide()
onHTTPResult(message, nil)
elseif message.type == "quick_login" and message.qrcode then
EnterGame.showNewLogin(message.qrcode)
end
end
end,
onClose = function(message, webSocketId)
if webSocket and webSocket.id == webSocketId then
webSocket = nil
if webSocketLoginPacket then
webSocketLoginPacket = nil
onHTTPResult(nil, "WebSocket disconnected")
end
EnterGame.checkWebsocket() -- reconnect
end
end,
onError = function(message, webSocketId)
if webSocket and webSocket.id == webSocketId then
-- handle error
end
end
})
end
function EnterGame.hideNewLogin()
newLogin:hide()
end
function EnterGame.showNewLogin(qrcode)
if enterGame:isHidden() then return end
newLogin.qrcode:setQRCode("https://quath.co/0/" .. qrcode, 1)
newLogin.qrcode:setEnabled(true)
local clickFunction = function()
g_platform.openUrl("qauth://" .. qrcode)
end
newLogin.qrcode.onClick = clickFunction
newLogin.quathlogo.onClick = clickFunction
if newLogin:isHidden() then
newLogin:show()
newLogin:raise()
end
end
function EnterGame.onServerChange() function EnterGame.onServerChange()
server = serverSelector:getText() server = serverSelector:getText()
EnterGame.hideNewLogin()
if server == tr("Another") then if server == tr("Another") then
if not customServerSelectorPanel:isOn() then if not customServerSelectorPanel:isOn() then
serverHostTextEdit:setText("") serverHostTextEdit:setText("")
@ -399,7 +305,6 @@ function EnterGame.onServerChange()
end end
if Servers and Servers[server] ~= nil then if Servers and Servers[server] ~= nil then
serverHostTextEdit:setText(Servers[server]) serverHostTextEdit:setText(Servers[server])
EnterGame.checkWebsocket()
end end
end end
@ -509,36 +414,6 @@ function EnterGame.doLogin()
end end
end end
function EnterGame.doLoginWs()
-- PREVIEW, need to implement websocket reconnect and error handling
if G.host == nil or G.host:len() < 10 then
return EnterGame.onError("Invalid server url: " .. G.host)
end
if not webSocket then
return EnterGame.onError("There's no websocket connection to: " .. G.host)
end
loadBox = displayCancelBox(tr('Please wait'), tr('Connecting to login server...'))
connect(loadBox, { onCancel = function(msgbox)
loadBox = nil
webSocketLoginPacket = nil
EnterGame.show()
end })
local data = {
type = "login",
account = G.account,
password = G.password,
token = G.authenticatorToken,
version = APP_VERSION,
uid = G.UUID
}
webSocketLoginPacket = data
webSocket.send(data)
EnterGame.hide()
end
function EnterGame.doLoginHttp() function EnterGame.doLoginHttp()
if G.host == nil or G.host:len() < 10 then if G.host == nil or G.host:len() < 10 then
return EnterGame.onError("Invalid server url: " .. G.host) return EnterGame.onError("Invalid server url: " .. G.host)

View File

@ -6,6 +6,10 @@ local installedLocales
local currentLocale local currentLocale
function sendLocale(localeName) function sendLocale(localeName)
if not g_game.getFeature(GameExtendedOpcode) then
return
end
local protocolGame = g_game.getProtocolGame() local protocolGame = g_game.getProtocolGame()
if protocolGame then if protocolGame then
protocolGame:sendExtendedOpcode(ExtendedIds.Locale, localeName) protocolGame:sendExtendedOpcode(ExtendedIds.Locale, localeName)

View File

@ -84,6 +84,25 @@ Panel
minimum: 0 minimum: 0
maximum: 300 maximum: 300
Label
anchors.top: prev.bottom
anchors.left: parent.left
anchors.right: parent.right
id: walkCtrlTurnDelayLabel
margin-top: 10
@onSetup: |
local value = modules.client_options.getOption('walkTurnDelay')
self:setText(tr('Walk delay after ctrl turn: %s ms', value))
OptionScrollbar
id: walkCtrlTurnDelay
anchors.top: prev.bottom
anchors.left: parent.left
anchors.right: parent.right
margin-top: 3
minimum: 0
maximum: 300
Label Label
anchors.top: prev.bottom anchors.top: prev.bottom
anchors.left: parent.left anchors.left: parent.left

View File

@ -48,7 +48,8 @@ local defaultOptions = {
walkFirstStepDelay = 200, walkFirstStepDelay = 200,
walkTurnDelay = 100, walkTurnDelay = 100,
walkStairsDelay = 50, walkStairsDelay = 50,
walkTeleportDelay = 200 walkTeleportDelay = 200,
walkCtrlTurnDelay = 150
} }
local optionsWindow local optionsWindow
@ -315,6 +316,8 @@ function setOption(key, value, force)
generalPanel:getChildById('walkStairsDelayLabel'):setText(tr('Walk delay after floor change: %s ms', value)) generalPanel:getChildById('walkStairsDelayLabel'):setText(tr('Walk delay after floor change: %s ms', value))
elseif key == 'walkTeleportDelay' then elseif key == 'walkTeleportDelay' then
generalPanel:getChildById('walkTeleportDelayLabel'):setText(tr('Walk delay after teleport: %s ms', value)) generalPanel:getChildById('walkTeleportDelayLabel'):setText(tr('Walk delay after teleport: %s ms', value))
elseif key == 'walkCtrlTurnDelay' then
generalPanel:getChildById('walkCtrlTurnDelayLabel'):setText(tr('Walk delay after ctrl turn: %s ms', value))
end end
-- change value for keybind updates -- change value for keybind updates

View File

@ -134,6 +134,7 @@ function sendStats()
mem = g_platform.getTotalSystemMemory(), mem = g_platform.getTotalSystemMemory(),
mem_usage = g_platform.getMemoryUsage(), mem_usage = g_platform.getMemoryUsage(),
os_name = g_platform.getOSName(), os_name = g_platform.getOSName(),
platform = g_window.getPlatformType(),
uptime = g_clock.seconds() uptime = g_clock.seconds()
} }
} }

View File

@ -140,7 +140,7 @@ function init()
terminalWindow.onDoubleClick = popWindow terminalWindow.onDoubleClick = popWindow
--terminalButton = modules.client_topmenu.addLeftButton('terminalButton', tr('Terminal') .. ' (Ctrl + T)', '/images/topbuttons/terminal', toggle) terminalButton = modules.client_topmenu.addLeftButton('terminalButton', tr('Terminal') .. ' (Ctrl + T)', '/images/topbuttons/terminal', toggle)
g_keyboard.bindKeyDown('Ctrl+T', toggle) g_keyboard.bindKeyDown('Ctrl+T', toggle)
commandHistory = g_settings.getList('terminal-history') commandHistory = g_settings.getList('terminal-history')

View File

@ -7,18 +7,27 @@ HTTP = {
} }
function HTTP.get(url, callback) function HTTP.get(url, callback)
if not g_http or not g_http.get then
return error("HTTP.get is not supported")
end
local operation = g_http.get(url, HTTP.timeout) local operation = g_http.get(url, HTTP.timeout)
HTTP.operations[operation] = {type="get", url=url, callback=callback} HTTP.operations[operation] = {type="get", url=url, callback=callback}
return operation return operation
end end
function HTTP.getJSON(url, callback) function HTTP.getJSON(url, callback)
if not g_http or not g_http.get then
return error("HTTP.getJSON is not supported")
end
local operation = g_http.get(url, HTTP.timeout) local operation = g_http.get(url, HTTP.timeout)
HTTP.operations[operation] = {type="get", json=true, url=url, callback=callback} HTTP.operations[operation] = {type="get", json=true, url=url, callback=callback}
return operation return operation
end end
function HTTP.post(url, data, callback) function HTTP.post(url, data, callback)
if not g_http or not g_http.post then
return error("HTTP.post is not supported")
end
if type(data) == "table" then if type(data) == "table" then
data = json.encode(data) data = json.encode(data)
end end
@ -28,6 +37,9 @@ function HTTP.post(url, data, callback)
end end
function HTTP.postJSON(url, data, callback) function HTTP.postJSON(url, data, callback)
if not g_http or not g_http.post then
return error("HTTP.postJSON is not supported")
end
if type(data) == "table" then if type(data) == "table" then
data = json.encode(data) data = json.encode(data)
end end
@ -37,12 +49,18 @@ function HTTP.postJSON(url, data, callback)
end end
function HTTP.download(url, file, callback, progressCallback) function HTTP.download(url, file, callback, progressCallback)
if not g_http or not g_http.download then
return error("HTTP.download is not supported")
end
local operation = g_http.download(url, file, HTTP.timeout) local operation = g_http.download(url, file, HTTP.timeout)
HTTP.operations[operation] = {type="download", url=url, file=file, callback=callback, progressCallback=progressCallback} HTTP.operations[operation] = {type="download", url=url, file=file, callback=callback, progressCallback=progressCallback}
return operation return operation
end end
function HTTP.downloadImage(url, callback) function HTTP.downloadImage(url, callback)
if not g_http or not g_http.download then
return error("HTTP.downloadImage is not supported")
end
if HTTP.images[url] ~= nil then if HTTP.images[url] ~= nil then
if callback then if callback then
callback('/downloads/' .. HTTP.images[url], nil) callback('/downloads/' .. HTTP.images[url], nil)
@ -57,6 +75,9 @@ function HTTP.downloadImage(url, callback)
end end
function HTTP.webSocket(url, callbacks, timeout, jsonWebsocket) function HTTP.webSocket(url, callbacks, timeout, jsonWebsocket)
if not g_http or not g_http.ws then
return error("WebSocket is not supported")
end
if not timeout or timeout < 1 then if not timeout or timeout < 1 then
timeout = HTTP.websocketTimeout timeout = HTTP.websocketTimeout
end end
@ -84,6 +105,9 @@ end
HTTP.WebSocketJSON = HTTP.webSocketJSON HTTP.WebSocketJSON = HTTP.webSocketJSON
function HTTP.cancel(operationId) function HTTP.cancel(operationId)
if not g_http or not g_http.cancel then
return
end
return g_http.cancel(operationId) return g_http.cancel(operationId)
end end

View File

@ -19,6 +19,10 @@ function UIComboBox:clearOptions()
self:clearText() self:clearText()
end end
function UIComboBox:clear()
return self:clearOptions()
end
function UIComboBox:getOptionsCount() function UIComboBox:getOptionsCount()
return #self.options return #self.options
end end
@ -108,12 +112,6 @@ function UIComboBox:removeOption(text)
end end
end end
function UIComboBox:clear()
self.options = {}
self.currentIndex = -1
self:setText("")
end
function UIComboBox:onMousePress(mousePos, mouseButton) function UIComboBox:onMousePress(mousePos, mouseButton)
local menu local menu
if self.menuScroll then if self.menuScroll then

View File

@ -1,86 +1,37 @@
botWindow = nil botWindow = nil
botButton = nil botButton = nil
botConfigFile = nil
botConfig = nil
contentsPanel = nil contentsPanel = nil
configWindow = nil editWindow = nil
configEditorText = nil
configList = nil local checkEvent = nil
botTabs = nil
botPanel = nil local botStorage = {}
local botStorageFile = nil
local botWebSockets = {} local botWebSockets = {}
local botMessages = nil local botMessages = nil
local configCopy = "" local botTabs = nil
local botExecutor = nil
local configList = nil
local enableButton = nil local enableButton = nil
local executeEvent = nil local executeEvent = nil
local checkMsgsEvent = nil
local errorOccured = false
local statusLabel = nil local statusLabel = nil
local compiledConfig = nil
local configTab = nil
local tabs = {"main", "panels", "macros", "hotkeys", "callbacks", "other"}
local mainTab = nil
local activeTab = nil
local editorText = {"", ""}
function init() function init()
dofile("defaultconfig")
dofile("executor") dofile("executor")
g_ui.importStyle("ui/basic.otui") g_ui.importStyle("ui/basic.otui")
g_ui.importStyle("ui/panels.otui") g_ui.importStyle("ui/panels.otui")
g_ui.importStyle("ui/config.otui")
connect(g_game, { connect(g_game, {
onGameStart = online, onGameStart = online,
onGameEnd = offline, onGameEnd = offline,
onTalk = botOnTalk,
onTextMessage = botOnTextMessage,
onUse = botOnUse,
onUseWith = botOnUseWith,
onChannelList = botChannelList,
onOpenChannel = botOpenChannel,
onCloseChannel = botCloseChannel,
onChannelEvent = botChannelEvent
}) })
connect(rootWidget, { onKeyDown = botKeyDown, initCallbacks()
onKeyUp = botKeyUp,
onKeyPress = botKeyPress })
connect(Tile, { onAddThing = botAddThing, onRemoveThing = botRemoveThing }) botButton = modules.client_topmenu.addRightGameToggleButton('botButton', tr('Bot'), '/images/topbuttons/bot', toggle)
connect(Creature, {
onAppear = botCreatureAppear,
onDisappear = botCreatureDisappear,
onPositionChange = botCreaturePositionChange,
onHealthPercentChange = botCraetureHealthPercentChange
})
connect(LocalPlayer, {
onPositionChange = botCreaturePositionChange,
onHealthPercentChange = botCraetureHealthPercentChange
})
connect(Container, { onOpen = botContainerOpen,
onClose = botContainerClose,
onUpdateItem = botContainerUpdateItem })
connect(g_map, { onMissle = botOnMissle })
botConfigFile = g_configs.create("/bot.otml")
local config = botConfigFile:get("config")
if config ~= nil and config:len() > 10 then
local status, result = pcall(function() return json.decode(config) end)
if not status then
g_logger.error("Error: bot config parse error: " .. result .. "\n" .. config)
end
botConfig = result
else
botConfig = botDefaultConfig
end
botConfig.configs[1].name = botDefaultConfig.configs[1].name
botConfig.configs[1].script = botDefaultConfig.configs[1].script
botButton = modules.client_topmenu.addRightGameToggleButton('botButton',
tr('Bot'), '/images/topbuttons/bot', toggle)
botButton:setOn(false) botButton:setOn(false)
botButton:hide() botButton:hide()
@ -93,107 +44,188 @@ function init()
statusLabel = contentsPanel.statusLabel statusLabel = contentsPanel.statusLabel
botMessages = contentsPanel.messages botMessages = contentsPanel.messages
botTabs = contentsPanel.botTabs botTabs = contentsPanel.botTabs
botPanel = contentsPanel.botPanel botTabs:setContentWidget(contentsPanel.botPanel)
botTabs:setContentWidget(botPanel)
configWindow = g_ui.displayUI('config') editWindow = g_ui.displayUI('edit')
configWindow:hide() editWindow:hide()
configEditorText = configWindow.text
configTab = configWindow.configTab
configTab.onTabChange = editorTabChanged
for i=1,#botConfig.configs do
if botConfig.configs[i].name ~= nil then
configList:addOption(botConfig.configs[i].name)
else
configList:addOption("Config #" .. i)
end
end
if type(botConfig.selectedConfig) == 'number' then
configList:setCurrentIndex(botConfig.selectedConfig)
end
configList.onOptionChange = modules.game_bot.refreshConfig
mainTab = configTab:addTab("all")
for k, v in ipairs(tabs) do
configTab:addTab(v, nil, nil)
end
if g_game.isOnline() then if g_game.isOnline() then
clear()
online() online()
end end
end end
function saveConfig()
local status, result = pcall(function()
botConfigFile:set("config", json.encode(botConfig))
botConfigFile:save()
end)
if not status then
errorOccured = true
-- try to fix it
local extraInfo = ""
for i = 1, #botConfig.configs do
if botConfig.configs[i].storage then
local status, result = pcall(function() json.encode(botConfig.configs[i].storage) end)
if not status then
botConfig.configs[i].storage = nil
extraInfo = extraInfo .. "\nLocal storage of config " .. i .. " has been erased due to invalid data"
end
end
end
statusLabel:setText("Error while saving config and user storage:\n" .. result .. extraInfo .. "\n\n" .. "Try to restart bot")
return false
end
return true
end
function terminate() function terminate()
saveConfig() save()
clearConfig() clear()
disconnect(rootWidget, { onKeyDown = botKeyDown,
onKeyUp = botKeyUp,
onKeyPress = botKeyPress })
disconnect(g_game, { disconnect(g_game, {
onGameStart = online, onGameStart = online,
onGameEnd = offline, onGameEnd = offline,
onTalk = botOnTalk,
onTextMessage = botOnTextMessage,
onUse = botOnUse,
onUseWith = botOnUseWith,
onChannelList = botChannelList,
onOpenChannel = botOpenChannel,
onCloseChannel = botCloseChannel,
onChannelEvent = botChannelEvent
}) })
disconnect(Tile, { onAddThing = botAddThing, onRemoveThing = botRemoveThing }) terminateCallbacks()
disconnect(Creature, { removeEvent(checkEvent)
onAppear = botCreatureAppear,
onDisappear =botCreatureDisappear,
onPositionChange = botCreaturePositionChange,
onHealthPercentChange = botCraetureHealthPercentChange
})
disconnect(LocalPlayer, {
onPositionChange = botCreaturePositionChange,
onHealthPercentChange = botCraetureHealthPercentChange
})
disconnect(Container, { onOpen = botContainerOpen,
onClose = botContainerClose,
onUpdateItem = botContainerUpdateItem })
disconnect(g_map, { onMissle = botOnMissle })
removeEvent(executeEvent) editWindow:destroy()
removeEvent(checkMsgsEvent)
botWindow:destroy() botWindow:destroy()
botButton:destroy() botButton:destroy()
configWindow:destroy() end
function clear()
botExecutor = nil
removeEvent(checkEvent)
-- optimization, callback is not used when not needed
g_game.enableTileThingLuaCallback(false)
botTabs:clearTabs()
botTabs:setOn(false)
botMessages:destroyChildren()
botMessages:updateLayout()
for i, socket in pairs(botWebSockets) do
g_http.cancel(socket)
end
botWebSockets = {}
for i, widget in pairs(g_ui.getRootWidget():getChildren()) do
if widget.botWidget then
widget:destroy()
end
end
local gameMapPanel = modules.game_interface.getMapPanel()
if gameMapPanel then
gameMapPanel:unlockVisibleFloor()
end
if g_sounds then
g_sounds.getChannel(SoundChannels.Bot):stop()
end
end
function refresh()
save()
clear()
-- create bot dir
if not g_resources.directoryExists("/bot") then
g_resources.makeDir("/bot")
if not g_resources.directoryExists("/bot") then
return onError("Can't create bot directory in " .. g_resources.getWriteDir())
end
end
-- get list of configs
local configs = g_resources.listDirectoryFiles("/bot", false, false)
if #configs == 0 then
createDefaultConfig()
configs = g_resources.listDirectoryFiles("/bot", false, false)
end
-- clean
configList.onOptionChange = nil
enableButton.onClick = nil
configList:clearOptions()
-- select active config based on settings
local settings = g_settings.getNode('bot') or {}
local index = g_game.getCharacterName() .. "_" .. g_game.getClientVersion()
if settings[index] == nil then
settings[index] = {
enabled=false,
config=""
}
end
-- init list and buttons
for i=1,#configs do
configList:addOption(configs[i])
end
configList:setCurrentOption(settings[index].config)
if configList:getCurrentOption().text ~= settings[index].config then
settings[index].enabled = false
end
enableButton:setOn(settings[index].enabled)
configList.onOptionChange = function(widget)
settings[index].config = widget:getCurrentOption().text
settings[index].enabled = false
g_settings.setNode('bot', settings)
g_settings.save()
refresh()
end
enableButton.onClick = function(widget)
settings[index].enabled = not settings[index].enabled
g_settings.setNode('bot', settings)
g_settings.save()
refresh()
end
if not g_game.isOnline() or not settings[index].enabled then
statusLabel:setOn(true)
statusLabel:setText("Status: disabled")
return
end
local configName = settings[index].config
-- storage
botStorage = {}
botStorageFile = "/bot/" .. configName .. "/storage.json"
if g_resources.fileExists(botStorageFile) then
local status, result = pcall(function()
return json.decode(g_resources.readFileContents(botStorageFile))
end)
if not status then
return onError("Error while reading storage (" .. botStorageFile .. "). To fix this problem you can delete storage.json. Details: " .. result)
end
botStorage = result
end
-- run script
local status, result = pcall(function()
return executeBot(configName, botStorage, botTabs, message, save, botWebSockets) end
)
if not status then
return onError(result)
end
statusLabel:setOn(false)
botExecutor = result
check()
end
function save()
if not botExecutor then
return
end
local settings = g_settings.getNode('bot') or {}
local index = g_game.getCharacterName() .. "_" .. g_game.getClientVersion()
if settings[index] == nil then
return
end
local status, result = pcall(function()
return json.encode(botStorage)
end)
if not status then
return onError("Error while saving bot storage. Storage won't be saved. Details: " .. result)
end
if result:len() > 100 * 1024 * 1024 then
return onError("Storage file is too big, above 100MB, it won't be saved")
end
g_resources.writeFileContents(botStorageFile, result)
end end
function onMiniWindowClose() function onMiniWindowClose()
@ -212,228 +244,63 @@ end
function online() function online()
botButton:show() botButton:show()
updateEnabled() scheduleEvent(refresh, 20)
if botConfig.enabled then
scheduleEvent(refreshConfig, 20)
else
clearConfig()
end
if executeEvent == nil then
executeEvent = scheduleEvent(executeConfig, 200)
checkMsgsEvent = scheduleEvent(checkMsgs, 200)
end
end end
function offline() function offline()
save()
clear()
botButton:hide() botButton:hide()
configWindow:hide() editWindow:hide()
clearConfig()
removeEvent(executeEvent)
removeEvent(checkMsgsEvent)
executeEvent = nil
checkMsgsEvent = nil
end end
function toggleBot() function onError(message)
botConfig.enabled = not botConfig.enabled statusLabel:setOn(true)
if botConfig.enabled then statusLabel:setText("Error:\n" .. message)
refreshConfig() g_logger.error("[BOT] " .. message)
else
clearConfig()
end
updateEnabled()
end end
function updateEnabled() function edit()
if botConfig.enabled then editWindow:show()
enableButton:setText(tr('On')) editWindow:focus()
enableButton:setColor('#00AA00FF') editWindow:raise()
else
enableButton:setText(tr('Off'))
enableButton:setColor('#FF0000FF')
statusLabel:setText(tr("Status: disabled"))
end
errorOccured = false
end end
function editConfig() function createDefaultConfig()
local config = configList.currentIndex if not g_resources.directoryExists("/bot/default_config") then
configWindow:show() g_resources.makeDir("/bot/default_config")
configWindow:raise() if not g_resources.directoryExists("/bot/default_config") then
configWindow:focus() return onError("Can't create default_config directory in " .. g_resources.getWriteDir())
editorText = {botConfig.configs[config].script or "", ""}
if #editorText[1] <= 2 then
editorText[1] = "--config name\n\n"
for k, v in ipairs(tabs) do
editorText[1] = editorText[1] .. "--#" .. v .. "\n\n"
end
end
configEditorText:setText(editorText[1])
configEditorText:setEditable(true)
activeTab = mainTab
configTab:selectTab(mainTab)
end
local function split2(str, delimiter)
local result = { }
local from = 1
local delim_from, delim_to = string.find( str, delimiter, from, true)
if delim_from then
table.insert( result, string.sub( str, from , delim_from - 1 ) )
from = delim_to + 1
delim_from, delim_to = string.find( str, delimiter, from )
table.insert( result, string.sub( str, from ) )
else
table.insert(result, str)
table.insert(result, "")
end
return result
end
function restoreMainTab()
if activeTab == mainTab then
editorText = {configEditorText:getText(), ""}
return
end
local currentText = configEditorText:getText()
if #currentText > 0 and currentText:sub(#currentText, #currentText) ~= '\n' then
currentText = currentText .. '\n'
end
editorText = {editorText[1] .. "--#" .. activeTab:getText():lower() .. "\n" .. currentText .. editorText[2], ""}
configEditorText:setText(editorText[1])
end
function editorTabChanged(holder, tab)
if activeTab == tab then
return
end
restoreMainTab()
activeTab = tab
if tab == mainTab then
return
end
local splitted = split2(editorText[1], "--#" .. activeTab:getText():lower() .. "\n")
local splitted2 = split2(splitted[2], "--#")
if splitted2[2]:len() > 1 then
splitted2[2] = "--#" .. splitted2[2]
end
editorText = {splitted[1], splitted2[2]}
configEditorText:setText(splitted2[1])
end
function saveEditedConfig()
restoreMainTab()
local config = configList.currentIndex
local text = configEditorText:getText()
configWindow:hide()
botConfig.configs[config].script = text
if text:len() > 3 and text:sub(1,2) == '--' and text:sub(3,3) ~= '#' then
local delim_from, delim_to = string.find( text, "\n", 3, true)
if delim_from then
botConfig.configs[config].name = string.sub( text, 3 , delim_from - 1 ):trim()
configList:updateCurrentOption(botConfig.configs[config].name)
end
end
refreshConfig()
end
function clearConfig()
compiledConfig = nil
botTabs:clearTabs()
botTabs:setOn(false)
botMessages:destroyChildren()
botMessages:updateLayout()
for socket in pairs(botWebSockets) do
g_http.cancel(socket)
botWebSockets[socket] = nil
end
for i, widget in pairs(g_ui.getRootWidget():getChildren()) do
if widget.botWidget then
widget:destroy()
end
end
local gameMapPanel = modules.game_interface.getMapPanel()
if gameMapPanel then
gameMapPanel:unlockVisibleFloor()
end
if g_sounds then
local botSoundChannel = g_sounds.getChannel(SoundChannels.Bot)
botSoundChannel:stop()
end end
end end
function refreshConfig() local defaultConfigFiles = g_resources.listDirectoryFiles("default_config", true, false)
configWindow:hide() for i, file in ipairs(defaultConfigFiles) do
local baseName = file:split("/")
botConfig.selectedConfig = configList.currentIndex baseName = baseName[#baseName]
if not botConfig.enabled then local contents = g_resources.readFileContents(file)
return if contents:len() > 0 then
g_resources.writeFileContents("/bot/default_config/" .. baseName, contents)
end end
if not saveConfig() then
clearConfig()
return
end
clearConfig()
local config = botConfig.configs[configList.currentIndex]
if not config.storage then
config.storage = {}
end
if config.script == nil or config.script:len() < 5 then
errorOccured = true
statusLabel:setText(tr("Error: empty config"))
return
end
errorOccured = false
g_game.enableTileThingLuaCallback(false)
local status, result = pcall(function() return executeBot(config.script, config.storage, botTabs, botMsgCallback, saveConfig, botWebSockets) end)
if not status then
errorOccured = true
statusLabel:setText("Error: " .. tostring(result))
return
end
compiledConfig = result
statusLabel:setText(tr("Status: working"))
end
function executeConfig()
executeEvent = scheduleEvent(executeConfig, 25)
if compiledConfig == nil then
return
end
if not botConfig.enabled or errorOccured then
if not errorOccured then
statusLabel:setText(tr("Status: disabled"))
end
return
end
local status, result = pcall(function() return compiledConfig.script() end)
if not status then
errorOccured = true
statusLabel:setText("Error: " .. result)
return
end end
end end
function botMsgCallback(category, msg) -- Executor
function message(category, msg)
local widget = g_ui.createWidget('BotLabel', botMessages) local widget = g_ui.createWidget('BotLabel', botMessages)
widget.added = g_clock.millis() widget.added = g_clock.millis()
if category == 'error' then if category == 'error' then
widget:setText(msg) widget:setText(msg)
widget:setColor("red") widget:setColor("red")
g_logger.error("[BOT] " .. msg)
elseif category == 'warn' then elseif category == 'warn' then
widget:setText(msg) widget:setText(msg)
widget:setColor("yellow") widget:setColor("yellow")
g_logger.warning("[BOT] " .. msg)
elseif category == 'info' then elseif category == 'info' then
widget:setText(msg) widget:setText(msg)
widget:setColor("white") widget:setColor("white")
g_logger.info("[BOT] " .. msg)
end end
if botMessages:getChildCount() > 5 then if botMessages:getChildCount() > 5 then
@ -441,132 +308,233 @@ function botMsgCallback(category, msg)
end end
end end
function checkMsgs() function check()
checkMsgsEvent = scheduleEvent(checkMsgs, 200) removeEvent(checkEvent)
if not botExecutor then
return
end
checkEvent = scheduleEvent(check, 25)
local status, result = pcall(function()
return botExecutor.script()
end)
if not status then
botExecutor = nil -- critical
return onError(result)
end
-- remove old messages
local widget = botMessages:getFirstChild() local widget = botMessages:getFirstChild()
if widget and widget.added + 5000 < g_clock.millis() then if widget and widget.added + 5000 < g_clock.millis() then
widget:destroy() widget:destroy()
end end
end end
-- Callbacks
function initCallbacks()
connect(rootWidget, {
onKeyDown = botKeyDown,
onKeyUp = botKeyUp,
onKeyPress = botKeyPress
})
connect(g_game, {
onTalk = botOnTalk,
onTextMessage = botOnTextMessage,
onUse = botOnUse,
onUseWith = botOnUseWith,
onChannelList = botChannelList,
onOpenChannel = botOpenChannel,
onCloseChannel = botCloseChannel,
onChannelEvent = botChannelEvent
})
connect(Tile, {
onAddThing = botAddThing,
onRemoveThing = botRemoveThing
})
connect(Creature, {
onAppear = botCreatureAppear,
onDisappear = botCreatureDisappear,
onPositionChange = botCreaturePositionChange,
onHealthPercentChange = botCraetureHealthPercentChange
})
connect(LocalPlayer, {
onPositionChange = botCreaturePositionChange,
onHealthPercentChange = botCraetureHealthPercentChange
})
connect(Container, {
onOpen = botContainerOpen,
onClose = botContainerClose,
onUpdateItem = botContainerUpdateItem
})
connect(g_map, {
onMissle = botOnMissle
})
end
function terminateCallbacks()
disconnect(rootWidget, {
onKeyDown = botKeyDown,
onKeyUp = botKeyUp,
onKeyPress = botKeyPress
})
disconnect(g_game, {
onTalk = botOnTalk,
onTextMessage = botOnTextMessage,
onUse = botOnUse,
onUseWith = botOnUseWith,
onChannelList = botChannelList,
onOpenChannel = botOpenChannel,
onCloseChannel = botCloseChannel,
onChannelEvent = botChannelEvent
})
disconnect(Tile, {
onAddThing = botAddThing,
onRemoveThing = botRemoveThing
})
disconnect(Creature, {
onAppear = botCreatureAppear,
onDisappear = botCreatureDisappear,
onPositionChange = botCreaturePositionChange,
onHealthPercentChange = botCraetureHealthPercentChange
})
disconnect(LocalPlayer, {
onPositionChange = botCreaturePositionChange,
onHealthPercentChange = botCraetureHealthPercentChange
})
disconnect(Container, {
onOpen = botContainerOpen,
onClose = botContainerClose,
onUpdateItem = botContainerUpdateItem
})
disconnect(g_map, {
onMissle = botOnMissle
})
end
function safeBotCall(func) function safeBotCall(func)
local status, result = pcall(func) local status, result = pcall(func)
if not status then if not status then
errorOccured = true onError(result)
statusLabel:setText("Error: " .. result)
end end
return false
end end
function botKeyDown(widget, keyCode, keyboardModifiers) function botKeyDown(widget, keyCode, keyboardModifiers)
if compiledConfig == nil then return false end if botExecutor == nil then return false end
if keyCode == KeyUnknown then return end if keyCode == KeyUnknown then return end
safeBotCall(function() compiledConfig.callbacks.onKeyDown(keyCode, keyboardModifiers) end) safeBotCall(function() botExecutor.callbacks.onKeyDown(keyCode, keyboardModifiers) end)
end end
function botKeyUp(widget, keyCode, keyboardModifiers) function botKeyUp(widget, keyCode, keyboardModifiers)
if compiledConfig == nil then return false end if botExecutor == nil then return false end
if keyCode == KeyUnknown then return end if keyCode == KeyUnknown then return end
safeBotCall(function() compiledConfig.callbacks.onKeyUp(keyCode, keyboardModifiers) end) safeBotCall(function() botExecutor.callbacks.onKeyUp(keyCode, keyboardModifiers) end)
end end
function botKeyPress(widget, keyCode, keyboardModifiers, autoRepeatTicks) function botKeyPress(widget, keyCode, keyboardModifiers, autoRepeatTicks)
if compiledConfig == nil then return false end if botExecutor == nil then return false end
if keyCode == KeyUnknown then return end if keyCode == KeyUnknown then return end
safeBotCall(function() compiledConfig.callbacks.onKeyPress(keyCode, keyboardModifiers, autoRepeatTicks) end) safeBotCall(function() botExecutor.callbacks.onKeyPress(keyCode, keyboardModifiers, autoRepeatTicks) end)
end end
function botOnTalk(name, level, mode, text, channelId, pos) function botOnTalk(name, level, mode, text, channelId, pos)
if compiledConfig == nil then return false end if botExecutor == nil then return false end
safeBotCall(function() compiledConfig.callbacks.onTalk(name, level, mode, text, channelId, pos) end) safeBotCall(function() botExecutor.callbacks.onTalk(name, level, mode, text, channelId, pos) end)
end end
function botOnTextMessage(mode, text) function botOnTextMessage(mode, text)
if compiledConfig == nil then return false end if botExecutor == nil then return false end
safeBotCall(function() compiledConfig.callbacks.onTextMessage(mode, text) end) safeBotCall(function() botExecutor.callbacks.onTextMessage(mode, text) end)
end end
function botAddThing(tile, thing) function botAddThing(tile, thing)
if compiledConfig == nil then return false end if botExecutor == nil then return false end
safeBotCall(function() compiledConfig.callbacks.onAddThing(tile, thing) end) safeBotCall(function() botExecutor.callbacks.onAddThing(tile, thing) end)
end end
function botRemoveThing(tile, thing) function botRemoveThing(tile, thing)
if compiledConfig == nil then return false end if botExecutor == nil then return false end
safeBotCall(function() compiledConfig.callbacks.onRemoveThing(tile, thing) end) safeBotCall(function() botExecutor.callbacks.onRemoveThing(tile, thing) end)
end end
function botCreatureAppear(creature) function botCreatureAppear(creature)
if compiledConfig == nil then return false end if botExecutor == nil then return false end
safeBotCall(function() compiledConfig.callbacks.onCreatureAppear(creature) end) safeBotCall(function() botExecutor.callbacks.onCreatureAppear(creature) end)
end end
function botCreatureDisappear(creature) function botCreatureDisappear(creature)
if compiledConfig == nil then return false end if botExecutor == nil then return false end
safeBotCall(function() compiledConfig.callbacks.onCreatureDisappear(creature) end) safeBotCall(function() botExecutor.callbacks.onCreatureDisappear(creature) end)
end end
function botCreaturePositionChange(creature, newPos, oldPos) function botCreaturePositionChange(creature, newPos, oldPos)
if compiledConfig == nil then return false end if botExecutor == nil then return false end
safeBotCall(function() compiledConfig.callbacks.onCreaturePositionChange(creature, newPos, oldPos) end) safeBotCall(function() botExecutor.callbacks.onCreaturePositionChange(creature, newPos, oldPos) end)
end end
function botCraetureHealthPercentChange(creature, healthPercent) function botCraetureHealthPercentChange(creature, healthPercent)
if compiledConfig == nil then return false end if botExecutor == nil then return false end
safeBotCall(function() compiledConfig.callbacks.onCreatureHealthPercentChange(creature, healthPercent) end) safeBotCall(function() botExecutor.callbacks.onCreatureHealthPercentChange(creature, healthPercent) end)
end end
function botOnUse(pos, itemId, stackPos, subType) function botOnUse(pos, itemId, stackPos, subType)
if compiledConfig == nil then return false end if botExecutor == nil then return false end
safeBotCall(function() compiledConfig.callbacks.onUse(pos, itemId, stackPos, subType) end) safeBotCall(function() botExecutor.callbacks.onUse(pos, itemId, stackPos, subType) end)
end end
function botOnUseWith(pos, itemId, target, subType) function botOnUseWith(pos, itemId, target, subType)
if compiledConfig == nil then return false end if botExecutor == nil then return false end
safeBotCall(function() compiledConfig.callbacks.onUseWith(pos, itemId, target, subType) end) safeBotCall(function() botExecutor.callbacks.onUseWith(pos, itemId, target, subType) end)
end end
function botContainerOpen(container, previousContainer) function botContainerOpen(container, previousContainer)
if compiledConfig == nil then return false end if botExecutor == nil then return false end
safeBotCall(function() compiledConfig.callbacks.onContainerOpen(container, previousContainer) end) safeBotCall(function() botExecutor.callbacks.onContainerOpen(container, previousContainer) end)
end end
function botContainerClose(container) function botContainerClose(container)
if compiledConfig == nil then return false end if botExecutor == nil then return false end
safeBotCall(function() compiledConfig.callbacks.onContainerClose(container) end) safeBotCall(function() botExecutor.callbacks.onContainerClose(container) end)
end end
function botContainerUpdateItem(container, slot, item) function botContainerUpdateItem(container, slot, item)
if compiledConfig == nil then return false end if botExecutor == nil then return false end
safeBotCall(function() compiledConfig.callbacks.onContainerUpdateItem(container, slot, item) end) safeBotCall(function() botExecutor.callbacks.onContainerUpdateItem(container, slot, item) end)
end end
function botOnMissle(missle) function botOnMissle(missle)
if compiledConfig == nil then return false end if botExecutor == nil then return false end
safeBotCall(function() compiledConfig.callbacks.onMissle(missle) end) safeBotCall(function() botExecutor.callbacks.onMissle(missle) end)
end
function botOnMissle(missle)
if compiledConfig == nil then return false end
safeBotCall(function() compiledConfig.callbacks.onMissle(missle) end)
end end
function botChannelList(channels) function botChannelList(channels)
if compiledConfig == nil then return false end if botExecutor == nil then return false end
safeBotCall(function() compiledConfig.callbacks.onChannelList(channels) end) safeBotCall(function() botExecutor.callbacks.onChannelList(channels) end)
end end
function botOpenChannel(channelId, name) function botOpenChannel(channelId, name)
if compiledConfig == nil then return false end if botExecutor == nil then return false end
safeBotCall(function() compiledConfig.callbacks.onOpenChannel(channelId, name) end) safeBotCall(function() botExecutor.callbacks.onOpenChannel(channelId, name) end)
end end
function botCloseChannel(channelId) function botCloseChannel(channelId)
if compiledConfig == nil then return false end if botExecutor == nil then return false end
safeBotCall(function() compiledConfig.callbacks.onCloseChannel(channelId) end) safeBotCall(function() botExecutor.callbacks.onCloseChannel(channelId) end)
end end
function botChannelEvent(channelId, name, event) function botChannelEvent(channelId, name, event)
if compiledConfig == nil then return false end if botExecutor == nil then return false end
safeBotCall(function() compiledConfig.callbacks.onChannelEvent(channelId, name, event) end) safeBotCall(function() botExecutor.callbacks.onChannelEvent(channelId, name, event) end)
end end

View File

@ -8,16 +8,14 @@ MiniWindow
&autoOpen: 10 &autoOpen: 10
MiniWindowContents MiniWindowContents
margin-left: 5
margin-right: 3
ComboBox ComboBox
id: config id: config
anchors.top: parent.top anchors.top: parent.top
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
margin-top: 2 margin-top: 2
margin-right: 90 margin-left: 2
margin-right: 75
text-offset: 3 0 text-offset: 3 0
Button Button
@ -26,34 +24,52 @@ MiniWindow
anchors.left: prev.right anchors.left: prev.right
anchors.right: parent.right anchors.right: parent.right
!text: tr('Edit') !text: tr('Edit')
@onClick: modules.game_bot.editConfig() @onClick: modules.game_bot.edit()
margin-left: 5 margin-left: 3
margin-right: 45 margin-right: 37
Button Button
id: enableButton id: enableButton
anchors.top: prev.top anchors.top: prev.top
anchors.left: prev.right anchors.left: prev.right
anchors.right: parent.right anchors.right: parent.right
@onClick: modules.game_bot.toggleBot() margin-left: 3
margin-left: 5 margin-right: 2
$on:
text: On
color: #00AA00
$!on:
text: Off
color: #FF0000
Label Label
id: statusLabel id: statusLabel
anchors.top: prev.bottom anchors.top: prev.bottom
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
margin-top: 3
text-auto-resize: true
!text: tr('Status: waiting')
text-align: center
text-wrap: true text-wrap: true
text-auto-resize: true
text-align: center
!text: tr('Status: waiting')
margin-left: 3
margin-right: 3
$on:
margin-top: 3
$!on:
text:
margin-top: -13
HorizontalSeparator HorizontalSeparator
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
anchors.top: prev.bottom anchors.top: prev.bottom
margin-top: 3 margin-top: 3
margin-left: 2
margin-right: 2
Panel Panel
anchors.top: prev.bottom anchors.top: prev.bottom
@ -69,6 +85,8 @@ MiniWindow
anchors.right: parent.right anchors.right: parent.right
anchors.top: prev.bottom anchors.top: prev.bottom
margin-top: 5 margin-top: 5
margin-left: 2
margin-right: 2
MoveableTabBar MoveableTabBar
id: botTabs id: botTabs
@ -76,6 +94,8 @@ MiniWindow
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
tab-spacing: 1 tab-spacing: 1
margin-left: 1
margin-right: 1
height: 20 height: 20
movable: false movable: false

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

View File

@ -0,0 +1,8 @@
local batTab = addTab("Batt")
Panels.AttackSpell(batTab)
Panels.AttackItem(batTab)
Panels.AttackLeaderTarget(batTab)
Panels.LimitFloor(batTab)
Panels.AntiPush(batTab)

View File

@ -0,0 +1,8 @@
local caveTab = addTab("Cave")
local waypoints = Panels.Waypoints(caveTab)
local attacking = Panels.Attacking(caveTab)
local looting = Panels.Looting(caveTab)
addButton("tutorial", "Help & Tutorials", function()
g_platform.openUrl("https://github.com/OTCv8/otclientv8_bot")
end, caveTab)

View File

@ -0,0 +1,5 @@
ExampleLabel2 < Label
text: LOL
height: 200
width: 50

View File

@ -0,0 +1,16 @@
local healTab = addTab("HP")
Panels.Haste(healTab)
Panels.ManaShield(healTab)
Panels.AntiParalyze(healTab)
Panels.Health(healTab)
Panels.Health(healTab)
Panels.HealthItem(healTab)
Panels.HealthItem(healTab)
Panels.ManaItem(healTab)
Panels.ManaItem(healTab)
Panels.Equip(healTab)
Panels.Equip(healTab)
Panels.Equip(healTab)
Panels.Eating(healTab)

View File

@ -0,0 +1,16 @@
Panels.TradeMessage()
Panels.AutoStackItems()
addButton("discord", "Discord & Help", function()
g_platform.openUrl("https://discord.gg/yhqBE4A")
end)
addButton("forum", "Forum", function()
g_platform.openUrl("https://otland.net/forums/otclient.494/")
end)
addButton("github", "Documentation", function()
g_platform.openUrl("https://github.com/OTCv8/otclientv8_bot")
end)
addSeparator("sep")

View File

@ -0,0 +1,7 @@
singlehotkey("f10", "npc buy and sell", function()
NPC.say("hi")
NPC.say("trade")
NPC.buy(3074, 2) -- wand of vortex
NPC.sell(3074, 1)
NPC.closeTrade()
end)

View File

@ -0,0 +1,84 @@
local toolsTab = addTab("Tools")
macro(1000, "exchange money", function()
local containers = getContainers()
for i, container in pairs(containers) do
for j, item in ipairs(container:getItems()) do
if item:isStackable() and (item:getId() == 3035 or item:getId() == 3031) and item:getCount() == 100 then
g_game.use(item)
return
end
end
end
end)
macro(1000, "this macro does nothing", "f7", function()
end)
macro(100, "debug pathfinding", nil, function()
for i, tile in ipairs(g_map.getTiles(posz())) do
tile:setText("")
end
local path = findEveryPath(pos(), 20, {
ignoreNonPathable = false
})
local total = 0
for i, p in pairs(path) do
local s = i:split(",")
local pos = {x=tonumber(s[1]), y=tonumber(s[2]), z=tonumber(s[3])}
local tile = g_map.getTile(pos)
if tile then
tile:setText(p[2])
end
total = total + 1
end
end)
macro(1000, "speed hack", nil, function()
player:setSpeed(1000)
end)
hotkey("f5", "example hotkey", function()
info("Wow, you clicked f5 hotkey")
end)
singlehotkey("ctrl+f6", "singlehotkey", function()
info("Wow, you clicked f6 singlehotkey")
usewith(268, player)
end)
singlehotkey("ctrl+f8", "play alarm", function()
playAlarm()
end)
singlehotkey("ctrl+f9", "stop alarm", function()
stopSound()
end)
local positionLabel = addLabel("positionLabel", "")
onPlayerPositionChange(function()
positionLabel:setText("Pos: " .. posx() .. "," .. posy() .. "," .. posz())
end)
local s = addSwitch("sdSound", "Play sound when using sd", function(widget)
storage.sdSound = not storage.sdSound
widget:setOn(storage.sdSound)
end)
s:setOn(storage.sdSound)
onUseWith(function(pos, itemId)
if storage.sdSound and itemId == 3155 then
playSound("/sounds/magnum.ogg")
end
end)
macro(100, "hide useless tiles", "", function()
for i, tile in ipairs(g_map.getTiles(posz())) do
if not tile:isWalkable(true) then
tile:setFill('black')
end
end
end)
addLabel("mapinfo", "You can use ctrl + plus and ctrl + minus to zoom in / zoom out map")

121
modules/game_bot/edit.otui Normal file
View File

@ -0,0 +1,121 @@
MainWindow
id: editWindow
size: 550 580
!text: tr("Config editor")
@onEscape: self:hide()
@onEnter: self:hide()
Label
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
text-auto-resize: true
text-align: center
text-wrap: true
!text: tr("Bot configs are stored in:")
TextEdit
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: prev.bottom
height: 20
width: 400
margin-top: 5
editable: false
!text: g_resources.getWriteDir() .. "bot"
text-align: center
Button
id: documentationButton
!text: tr('Click here to open bot directory')
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: prev.bottom
margin-top: 5
width: 250
@onClick: g_platform.openDir(g_resources.getWriteDir() .. "bot")
Label
margin-top: 5
anchors.top: prev.bottom
anchors.left: parent.left
anchors.right: parent.right
text-auto-resize: true
text-align: center
text-wrap: true
!text: tr("Every directory in bot directory is treated as different config.\nTo create new config just create new directory.")
Label
margin-top: 5
anchors.top: prev.bottom
anchors.horizontalCenter: parent.horizontalCenter
height: 175
image-source: configs.png
image-fixed-ratio: true
image-size: 500 175
Label
margin-top: 5
anchors.top: prev.bottom
anchors.left: parent.left
anchors.right: parent.right
text-auto-resize: true
text-align: center
text-wrap: true
!text: tr("Inside config directory put .lua and .otui files.\nEvery file will be loaded and executed in alphabetical order, .otui first and then .lua.")
Label
margin-top: 5
anchors.top: prev.bottom
anchors.horizontalCenter: parent.horizontalCenter
height: 150
image-source: scripts.png
image-fixed-ratio: true
image-size: 500 150
Label
margin-top: 5
anchors.top: prev.bottom
anchors.left: parent.left
anchors.right: parent.right
text-auto-resize: true
text-align: center
text-wrap: true
!text: tr("To reload configs just press On and Off in bot window.\nTo learn more about bot click Tutorials button.")
Button
!text: tr('Tutorials')
anchors.bottom: parent.bottom
anchors.left: parent.left
width: 100
@onClick: g_platform.openUrl("http://otclient.ovh/bot.php?tutorials")
Button
!text: tr('Scripts')
anchors.bottom: parent.bottom
anchors.left: prev.right
margin-left: 10
width: 100
@onClick: g_platform.openUrl("http://otclient.ovh/bot.php?scripts")
Button
!text: tr('Forum')
anchors.bottom: parent.bottom
anchors.left: prev.right
margin-left: 10
width: 100
@onClick: g_platform.openUrl("http://otclient.ovh/bot.php?forum")
Button
!text: tr('Discord')
anchors.bottom: parent.bottom
anchors.left: prev.right
margin-left: 10
width: 100
@onClick: g_platform.openUrl("http://otclient.ovh/bot.php?discord")
Button
id: cancelButton
!text: tr('Close')
anchors.bottom: parent.bottom
anchors.right: parent.right
width: 60
@onClick: self:getParent():hide()

View File

@ -1,7 +1,27 @@
function executeBot(config, storage, tabs, msgCallback, saveConfigCallback, websockets) function executeBot(config, storage, tabs, msgCallback, saveConfigCallback, websockets)
-- load lua and otui files
local configFiles = g_resources.listDirectoryFiles("/bot/" .. config, true, false)
local luaFiles = {}
local uiFiles = {}
for i, file in ipairs(configFiles) do
local ext = file:split(".")
if ext[#ext]:lower() == "lua" then
table.insert(luaFiles, file)
end
if ext[#ext]:lower() == "ui" or ext[#ext]:lower() == "otui" then
table.insert(uiFiles, file)
end
end
if #luaFiles == 0 then
return error("Config (/bot/" .. config .. ") doesn't have lua files")
end
-- init bot variables
local context = {} local context = {}
context.configDir = "/bot/".. config
context.tabs = tabs context.tabs = tabs
context.panel = context.tabs:addTab("Main", g_ui.createWidget('BotPanel')).tabPanel context.panel = context.tabs:addTab("Main", g_ui.createWidget('BotPanel')).tabPanel.content
context.saveConfig = saveConfigCallback context.saveConfig = saveConfigCallback
context._websockets = websockets context._websockets = websockets
@ -10,7 +30,7 @@ function executeBot(config, storage, tabs, msgCallback, saveConfigCallback, webs
context.storage._macros = {} -- active macros context.storage._macros = {} -- active macros
end end
-- -- macros, hotkeys, scheduler, callbacks
context._macros = {} context._macros = {}
context._hotkeys = {} context._hotkeys = {}
context._scheduler = {} context._scheduler = {}
@ -87,8 +107,15 @@ function executeBot(config, storage, tabs, msgCallback, saveConfigCallback, webs
dofiles("panels") dofiles("panels")
G.botContext = nil G.botContext = nil
-- run script -- run ui scripts
assert(load(config, nil, nil, context))() for i, file in ipairs(uiFiles) do
g_ui.importStyle(file)
end
-- run lua script
for i, file in ipairs(luaFiles) do
assert(load(g_resources.readFileContents(file), file, nil, context))()
end
return { return {
script = function() script = function()

View File

@ -0,0 +1,113 @@
--[[
Config. create. load and save config file (.json)
Used by cavebot and other things
]]--
local context = G.botContext
context.Config = {}
local Config = context.Config
Config.exist = function(dir)
return g_resources.directoryExists(context.configDir .. "/" .. dir)
end
Config.create = function(dir)
g_resources.makeDir(context.configDir .. "/" .. dir)
return Config.exist(dir)
end
Config.list = function(dir)
if not Config.exist(dir) then
if not Config.create(dir) then
return contex.error("Can't create config dir: " .. context.configDir .. "/" .. dir)
end
end
return g_resources.listDirectoryFiles(context.configDir .. "/" .. dir)
end
Config.load = function(dir, name)
local file = context.configDir .. "/" .. dir .. "/" .. name .. ".json"
if g_resources.fileExists(file) then -- load json
return json.decode(g_resources.readFileContents(file))
end
file = context.configDir .. "/" .. dir .. "/" .. name .. ".cfg"
if g_resources.fileExists(file) then -- load cfg
return g_resources.readFileContents(file)
end
return context.error("Config " .. file .. " doesn't exist")
end
Config.save = function(dir, name, value)
if not Config.exist(dir) then
if not Config.create(dir) then
return contex.error("Can't create config dir: " .. context.configDir .. "/" .. dir)
end
end
local file = context.configDir .. "/" .. dir .. "/" .. name
if type(value) == 'string' then -- cfg
g_resources.writeFileContents(file .. ".cfg", value)
elseif type(value) == 'table' then -- json
g_resources.writeFileContents(file .. ".json", json.encode(value))
end
return context.error("Invalid config value type: " .. type(value))
end
Config.remove = function(dir, name)
local file = context.configDir .. "/" .. dir .. "/" .. name .. ".json"
if g_resources.fileExists(file) then
return g_resources.deleteFile(file)
end
file = context.configDir .. "/" .. dir .. "/" .. name .. ".cfg"
if g_resources.fileExists(file) then
return g_resources.deleteFile(file)
end
end
-- setup is used for BotConfig widget
-- not done yet
Config.setup = function(dir, widget, callback)
local refresh = function()
--
end
widget.switch.onClick = function()
widget.switch:setOn(not widget.switch:isOn())
end
widget.add = function()
end
widget.edit = function()
end
widget.remove = function()
end
--local configs = Config.list(dir)
--widget.list.
return {
isOn = function()
return widget.switch:isOn()
end,
isOff = function()
return not widget.switch:isOn()
end,
enable = function()
if not widget.switch:isOn() then
widget.switch:onClick()
end
end,
disable = function()
if widget.switch:isOn() then
widget.switch:onClick()
end
end,
save = function()
end
}
end

View File

@ -0,0 +1,111 @@
local context = G.botContext
context.NPC = {}
context.NPC.talk = function(text)
if g_game.getClientVersion() >= 810 then
g_game.talkChannel(11, 0, text)
else
return context.say(text)
end
end
context.NPC.say = context.NPC.talk
context.NPC.isTrading = function()
return modules.game_npctrade.npcWindow and modules.game_npctrade.npcWindow:isVisible()
end
context.NPC.hasTrade = context.NPC.isTrading
context.NPC.hasTradeWindow = context.NPC.isTrading
context.NPC.getSellItems = function()
if not context.NPC.isTrading() then return {} end
local items = {}
for i, item in ipairs(modules.game_npctrade.tradeItems[modules.game_npctrade.SELL]) do
table.insert(items, {
id = item.ptr:getId(),
name = item.name,
count = item.ptr:getCount(),
subType = item.ptr:getSubType(),
weight = item.weight / 100,
price = item.price
})
end
return items
end
context.NPC.getBuyItems = function()
if not context.NPC.isTrading() then return {} end
for i, item in ipairs(modules.game_npctrade.tradeItems[modules.game_npctrade.BUY]) do
table.insert(items, {
id = item.ptr:getId(),
name = item.name,
count = item.ptr:getCount(),
subType = item.ptr:getSubType(),
weight = item.weight / 100,
price = item.price
})
end
return items
end
context.NPC.getSellQuantity = function(item)
if not context.NPC.isTrading() then return 0 end
if type(item) == 'number' then
item = Item.create(item)
end
return modules.game_npctrade.getSellQuantity(item)
end
context.NPC.canTradeItem = function(item)
if not context.NPC.isTrading() then return false end
if type(item) == 'number' then
item = Item.create(item)
end
return modules.game_npctrade.canTradeItem(item)
end
context.NPC.sell = function(item, count, ignoreEquipped)
if type(item) == 'number' then
item = Item.create(item)
end
if count == 0 then
count = 1
end
if count == nil or count == -1 then
count = context.NPC.getSellQuantity(item)
end
if ignoreEquipped == nil then
ignoreEquipped = true
end
g_game.sellItem(item, count, ignoreEquipped)
end
context.NPC.buy = function(item, count, ignoreCapacity, withBackpack)
if type(item) == 'number' then
item = Item.create(item)
end
if count == nil or count <= 0 then
count = 1
end
if ignoreCapacity == nil then
ignoreCapacity = false
end
if withBackpack == nil then
withBackpack = false
end
g_game.buyItem(item, count, ignoreCapacity, withBackpack)
end
context.NPC.sellAll = function()
if not context.NPC.isTrading() then return false end
modules.game_npctrade.sellAll()
end
context.NPC.closeTrade = function()
modules.game_npctrade.closeNpcTrade()
end
context.NPC.close = context.NPC.closeTrade
context.NPC.finish = context.NPC.closeTrade
context.NPC.endTrade = context.NPC.closeTrade
context.NPC.finishTrade = context.NPC.closeTrade

View File

@ -11,7 +11,7 @@ end
context.addTab = function(name) context.addTab = function(name)
context.tabs:setOn(true) context.tabs:setOn(true)
return context.tabs:addTab(name, g_ui.createWidget('BotPanel')).tabPanel return context.tabs:addTab(name, g_ui.createWidget('BotPanel')).tabPanel.content
end end
context.addSwitch = function(id, text, onClickCallback, parent) context.addSwitch = function(id, text, onClickCallback, parent)

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

@ -39,9 +39,81 @@ BotSeparator < HorizontalSeparator
margin-bottom: 3 margin-bottom: 3
BotPanel < Panel BotPanel < Panel
ScrollablePanel
id: content
anchors.fill: parent
margin-right: 8
margin-left: 1
margin-bottom: 5
vertical-scrollbar: botPanelScroll
layout: layout:
type: verticalBox type: verticalBox
UIScrollBar
id: botPanelScroll
orientation: vertical
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.right: parent.right
margin-bottom: 1
step: 20
width: 8
image-source: /images/ui/scrollbar
image-clip: 39 0 13 65
image-border: 1
pixels-scroll: true
UIButton
id: decrementButton
anchors.top: parent.top
anchors.left: parent.left
image-source: /images/ui/scrollbar
image-clip: 0 0 13 13
image-color: #ffffffff
size: 8 8
$hover:
image-clip: 13 0 13 13
$pressed:
image-clip: 26 0 13 13
$disabled:
image-color: #ffffff66
UIButton
id: incrementButton
anchors.bottom: parent.bottom
anchors.right: parent.right
size: 8 8
image-source: /images/ui/scrollbar
image-clip: 0 13 13 13
image-color: #ffffffff
$hover:
image-clip: 13 13 13 13
$pressed:
image-clip: 26 13 13 13
$disabled:
image-color: #ffffff66
UIButton
id: sliderButton
anchors.centerIn: parent
size: 8 11
image-source: /images/ui/scrollbar
image-clip: 0 26 13 13
image-border: 2
image-color: #ffffffff
$hover:
image-clip: 13 26 13 13
$pressed:
image-clip: 26 26 13 13
$disabled:
image-color: #ffffff66
Label
id: valueLabel
anchors.fill: parent
color: white
text-align: center
CaveBotLabel < Label CaveBotLabel < Label
background-color: alpha background-color: alpha
text-offset: 2 0 text-offset: 2 0

View File

@ -0,0 +1,51 @@
BotConfig < Panel
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
height: 40
ComboBox
id: list
anchors.top: parent.top
anchors.left: parent.left
text-offset: 3 0
width: 130
Button
id: switch
anchors.top: prev.top
anchors.left: prev.right
anchors.right: parent.right
margin-left: 5
$on:
text: On
color: #00AA00
$!on:
text: Off
color: #FF0000
Button
margin-top: 1
id: add
anchors.top: prev.bottom
anchors.left: parent.left
text: Add
width: 58
height: 17
Button
id: edit
anchors.top: prev.top
anchors.horizontalCenter: parent.horizontalCenter
text: Edit
width: 58
height: 17
Button
id: remove
anchors.top: prev.top
anchors.right: parent.right
text: Remove
width: 58
height: 17

View File

@ -112,7 +112,6 @@ ItemsRow < Panel
id: item1 id: item1
anchors.top: parent.top anchors.top: parent.top
anchors.left: parent.left anchors.left: parent.left
margin-left: 3
BotItem BotItem
id: item2 id: item2

View File

@ -898,6 +898,7 @@ end
function sendCurrentMessage() function sendCurrentMessage()
local message = consoleTextEdit:getText() local message = consoleTextEdit:getText()
if #message == 0 then return end if #message == 0 then return end
if not isChatEnabled() then return end
consoleTextEdit:clearText() consoleTextEdit:clearText()
-- send message -- send message
@ -1071,6 +1072,10 @@ function setIgnoreNpcMessages(ignore)
end end
function navigateMessageHistory(step) function navigateMessageHistory(step)
if not isChatEnabled() then
return
end
local numCommands = #messageHistory local numCommands = #messageHistory
if numCommands > 0 then if numCommands > 0 then
currentMessageIndex = math.min(math.max(currentMessageIndex + step, 0), numCommands) currentMessageIndex = math.min(math.max(currentMessageIndex + step, 0), numCommands)

View File

@ -95,6 +95,9 @@ function onContainerOpen(container, previousContainer)
containerWindow:hide() containerWindow:hide()
end end
containerWindow.onDrop = function(container, widget, mousePos) containerWindow.onDrop = function(container, widget, mousePos)
if containerPanel:getChildByPos(mousePos) then
return false
end
local child = containerPanel:getChildByIndex(-1) local child = containerPanel:getChildByIndex(-1)
if child then if child then
child:onDrop(widget, mousePos, true) child:onDrop(widget, mousePos, true)

View File

@ -286,9 +286,13 @@ function onUseWith(clickedWidget, mousePosition)
local tile = clickedWidget:getTile(mousePosition) local tile = clickedWidget:getTile(mousePosition)
if tile then if tile then
if selectedThing:isFluidContainer() or selectedThing:isMultiUse() then if selectedThing:isFluidContainer() or selectedThing:isMultiUse() then
g_game.useWith(selectedThing, tile:getTopMultiUseThing(), selectedSubtype) if selectedThing:getId() == 3180 or selectedThing:getId() == 3156 then
-- special version for mwall
g_game.useWith(selectedThing, tile:getTopUseThing(), selectedSubtype)
else
g_game.useWith(selectedThing, tile:getTopMultiUseThingEx(clickedWidget:getPositionOffset(mousePosition)), selectedSubtype)
end
else else
print("normal")
g_game.useWith(selectedThing, tile:getTopUseThing(), selectedSubtype) g_game.useWith(selectedThing, tile:getTopUseThing(), selectedSubtype)
end end
end end

View File

@ -13,6 +13,7 @@ searchText = nil
setupPanel = nil setupPanel = nil
quantity = nil quantity = nil
quantityScroll = nil quantityScroll = nil
idLabel = nil
nameLabel = nil nameLabel = nil
priceLabel = nil priceLabel = nil
moneyLabel = nil moneyLabel = nil
@ -49,6 +50,7 @@ function init()
setupPanel = npcWindow:recursiveGetChildById('setupPanel') setupPanel = npcWindow:recursiveGetChildById('setupPanel')
quantityScroll = setupPanel:getChildById('quantityScroll') quantityScroll = setupPanel:getChildById('quantityScroll')
idLabel = setupPanel:getChildById('id')
nameLabel = setupPanel:getChildById('name') nameLabel = setupPanel:getChildById('name')
priceLabel = setupPanel:getChildById('price') priceLabel = setupPanel:getChildById('price')
moneyLabel = setupPanel:getChildById('money') moneyLabel = setupPanel:getChildById('money')
@ -119,6 +121,23 @@ end
function hide() function hide()
npcWindow:hide() npcWindow:hide()
local layout = itemsPanel:getLayout()
layout:disableUpdates()
clearSelectedItem()
searchText:clearText()
setupPanel:disable()
itemsPanel:destroyChildren()
if radioItems then
radioItems:destroy()
radioItems = nil
end
layout:enableUpdates()
layout:update()
end end
function onItemBoxChecked(widget) function onItemBoxChecked(widget)
@ -226,6 +245,7 @@ function setShowYourCapacity(state)
end end
function clearSelectedItem() function clearSelectedItem()
idLabel:clearText()
nameLabel:clearText() nameLabel:clearText()
weightLabel:clearText() weightLabel:clearText()
priceLabel:clearText() priceLabel:clearText()
@ -288,6 +308,7 @@ function canTradeItem(item)
end end
function refreshItem(item) function refreshItem(item)
idLabel:setText(item.ptr:getId())
nameLabel:setText(item.name) nameLabel:setText(item.name)
weightLabel:setText(string.format('%.2f', item.weight) .. ' ' .. WEIGHT_UNIT) weightLabel:setText(string.format('%.2f', item.weight) .. ' ' .. WEIGHT_UNIT)
priceLabel:setText(formatCurrency(getItemPrice(item))) priceLabel:setText(formatCurrency(getItemPrice(item)))
@ -420,11 +441,11 @@ end
function closeNpcTrade() function closeNpcTrade()
g_game.closeNpcTrade() g_game.closeNpcTrade()
hide() addEvent(hide)
end end
function onCloseNpcTrade() function onCloseNpcTrade()
hide() addEvent(hide)
end end
function onPlayerGoods(money, items) function onPlayerGoods(money, items)

View File

@ -101,13 +101,24 @@ MainWindow
image-color: #ffffff88 image-color: #ffffff88
Label Label
!text: tr('Name') .. ':' !text: tr('Id') .. ':'
anchors.left: parent.left anchors.left: parent.left
anchors.top: parent.top anchors.top: parent.top
margin-top: 5 margin-top: 5
margin-left: 5 margin-left: 5
width: 85 width: 85
NPCOfferLabel
id: id
Label
!text: tr('Name') .. ':'
anchors.left: parent.left
anchors.top: prev.bottom
margin-top: 5
margin-left: 5
width: 85
NPCOfferLabel NPCOfferLabel
id: name id: name

View File

@ -244,8 +244,11 @@ function sortBy(state)
end end
function onAddVip(id, name, state, description, iconId, notify) function onAddVip(id, name, state, description, iconId, notify)
local vipList = vipWindow:getChildById('contentsPanel') if not name or name:len() == 0 then
return
end
local vipList = vipWindow:getChildById('contentsPanel')
local childrenCount = vipList:getChildCount() local childrenCount = vipList:getChildCount()
for i=1,childrenCount do for i=1,childrenCount do
local child = vipList:getChildByIndex(i) local child = vipList:getChildByIndex(i)

View File

@ -383,7 +383,7 @@ function turn(dir, repeated)
end end
lastTurnDirection = dir lastTurnDirection = dir
nextWalkDir = nil nextWalkDir = nil
player:lockWalk(150) player:lockWalk(g_settings.getNumber('walkCtrlTurnDelay'))
end end
end end

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.