mirror of
https://github.com/OTCv8/otclientv8.git
synced 2025-04-29 18:59:20 +02:00
Version 1.7
This commit is contained in:
parent
1d2bdf855d
commit
2a10e65ec0
@ -1,6 +1,6 @@
|
||||
# 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.
|
||||
|
||||
## DISCORD: https://discord.gg/feySup6
|
||||
|
2
init.lua
2
init.lua
@ -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 = ""
|
||||
Services = {
|
||||
website = "http://otclient.ovh", -- currently not used
|
||||
updater = "http://otclient.ovh/api/updater.php",
|
||||
updater = "",
|
||||
news = "http://otclient.ovh/api/news.php",
|
||||
stats = "",
|
||||
crash = "http://otclient.ovh/api/crash.php",
|
||||
|
@ -235,11 +235,8 @@ function CharacterList.terminate()
|
||||
CharacterList = nil
|
||||
end
|
||||
|
||||
function CharacterList.create(characters, account, otui, websocket)
|
||||
function CharacterList.create(characters, account, otui)
|
||||
if not otui then otui = 'characterlist' end
|
||||
if websocket then
|
||||
websocket:close()
|
||||
end
|
||||
if charactersWindow then
|
||||
charactersWindow:destroy()
|
||||
end
|
||||
|
@ -17,9 +17,6 @@ local serverHostTextEdit
|
||||
local rememberPasswordBox
|
||||
local protos = {"740", "760", "772", "792", "800", "810", "854", "860", "1077", "1090", "1096", "1098", "1099", "1100"}
|
||||
|
||||
local webSocket
|
||||
local webSocketLoginPacket
|
||||
|
||||
-- private functions
|
||||
local function onProtocolError(protocol, message, errorCode)
|
||||
if errorCode then
|
||||
@ -54,13 +51,9 @@ local function onCharacterList(protocol, characters, account, otui)
|
||||
loadBox = nil
|
||||
end
|
||||
|
||||
CharacterList.create(characters, account, otui, webSocket)
|
||||
CharacterList.create(characters, account, otui)
|
||||
CharacterList.show()
|
||||
|
||||
if webSocket then
|
||||
webSocket = nil
|
||||
end
|
||||
|
||||
g_settings.save()
|
||||
end
|
||||
|
||||
@ -137,10 +130,6 @@ local function onHTTPResult(data, err)
|
||||
if #incorrectThings > 0 then
|
||||
g_logger.info(incorrectThings)
|
||||
if Updater then
|
||||
if webSocket then
|
||||
webSocket:close()
|
||||
webSocket = nil
|
||||
end
|
||||
return Updater.updateThings(things, incorrectThings)
|
||||
else
|
||||
return EnterGame.onError(incorrectThings)
|
||||
@ -186,7 +175,7 @@ local function onHTTPResult(data, err)
|
||||
g_proxy.clear()
|
||||
if proxies then
|
||||
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
|
||||
@ -198,7 +187,6 @@ end
|
||||
-- public functions
|
||||
function EnterGame.init()
|
||||
enterGame = g_ui.displayUI('entergame')
|
||||
newLogin = g_ui.displayUI('entergame_new')
|
||||
|
||||
serverSelectorPanel = enterGame:getChildById('serverSelectorPanel')
|
||||
customServerSelectorPanel = enterGame:getChildById('customServerSelectorPanel')
|
||||
@ -260,15 +248,7 @@ end
|
||||
function EnterGame.terminate()
|
||||
g_keyboard.unbindKeyDown('Ctrl+G')
|
||||
|
||||
if webSocket then
|
||||
webSocket.close()
|
||||
webSocket = nil
|
||||
end
|
||||
|
||||
enterGame:destroy()
|
||||
if newLogin then
|
||||
newLogin:destroy()
|
||||
end
|
||||
if loadBox then
|
||||
loadBox:destroy()
|
||||
loadBox = nil
|
||||
@ -288,12 +268,10 @@ function EnterGame.show()
|
||||
enterGame:raise()
|
||||
enterGame:focus()
|
||||
enterGame:getChildById('accountNameTextEdit'):focus()
|
||||
EnterGame.checkWebsocket()
|
||||
end
|
||||
|
||||
function EnterGame.hide()
|
||||
enterGame:hide()
|
||||
newLogin:hide()
|
||||
end
|
||||
|
||||
function EnterGame.openWindow()
|
||||
@ -313,80 +291,8 @@ function EnterGame.clearAccountFields()
|
||||
g_settings.remove('password')
|
||||
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()
|
||||
server = serverSelector:getText()
|
||||
EnterGame.hideNewLogin()
|
||||
if server == tr("Another") then
|
||||
if not customServerSelectorPanel:isOn() then
|
||||
serverHostTextEdit:setText("")
|
||||
@ -399,7 +305,6 @@ function EnterGame.onServerChange()
|
||||
end
|
||||
if Servers and Servers[server] ~= nil then
|
||||
serverHostTextEdit:setText(Servers[server])
|
||||
EnterGame.checkWebsocket()
|
||||
end
|
||||
end
|
||||
|
||||
@ -509,36 +414,6 @@ function EnterGame.doLogin()
|
||||
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()
|
||||
if G.host == nil or G.host:len() < 10 then
|
||||
return EnterGame.onError("Invalid server url: " .. G.host)
|
||||
|
@ -6,6 +6,10 @@ local installedLocales
|
||||
local currentLocale
|
||||
|
||||
function sendLocale(localeName)
|
||||
if not g_game.getFeature(GameExtendedOpcode) then
|
||||
return
|
||||
end
|
||||
|
||||
local protocolGame = g_game.getProtocolGame()
|
||||
if protocolGame then
|
||||
protocolGame:sendExtendedOpcode(ExtendedIds.Locale, localeName)
|
||||
|
@ -84,6 +84,25 @@ Panel
|
||||
minimum: 0
|
||||
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
|
||||
anchors.top: prev.bottom
|
||||
anchors.left: parent.left
|
||||
|
@ -48,7 +48,8 @@ local defaultOptions = {
|
||||
walkFirstStepDelay = 200,
|
||||
walkTurnDelay = 100,
|
||||
walkStairsDelay = 50,
|
||||
walkTeleportDelay = 200
|
||||
walkTeleportDelay = 200,
|
||||
walkCtrlTurnDelay = 150
|
||||
}
|
||||
|
||||
local optionsWindow
|
||||
@ -315,6 +316,8 @@ function setOption(key, value, force)
|
||||
generalPanel:getChildById('walkStairsDelayLabel'):setText(tr('Walk delay after floor change: %s ms', value))
|
||||
elseif key == 'walkTeleportDelay' then
|
||||
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
|
||||
|
||||
-- change value for keybind updates
|
||||
|
@ -134,6 +134,7 @@ function sendStats()
|
||||
mem = g_platform.getTotalSystemMemory(),
|
||||
mem_usage = g_platform.getMemoryUsage(),
|
||||
os_name = g_platform.getOSName(),
|
||||
platform = g_window.getPlatformType(),
|
||||
uptime = g_clock.seconds()
|
||||
}
|
||||
}
|
||||
|
@ -140,7 +140,7 @@ function init()
|
||||
|
||||
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)
|
||||
|
||||
commandHistory = g_settings.getList('terminal-history')
|
||||
|
@ -7,18 +7,27 @@ HTTP = {
|
||||
}
|
||||
|
||||
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)
|
||||
HTTP.operations[operation] = {type="get", url=url, callback=callback}
|
||||
return operation
|
||||
end
|
||||
|
||||
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)
|
||||
HTTP.operations[operation] = {type="get", json=true, url=url, callback=callback}
|
||||
return operation
|
||||
end
|
||||
|
||||
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
|
||||
data = json.encode(data)
|
||||
end
|
||||
@ -28,6 +37,9 @@ function HTTP.post(url, data, callback)
|
||||
end
|
||||
|
||||
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
|
||||
data = json.encode(data)
|
||||
end
|
||||
@ -37,12 +49,18 @@ function HTTP.postJSON(url, data, callback)
|
||||
end
|
||||
|
||||
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)
|
||||
HTTP.operations[operation] = {type="download", url=url, file=file, callback=callback, progressCallback=progressCallback}
|
||||
return operation
|
||||
end
|
||||
|
||||
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 callback then
|
||||
callback('/downloads/' .. HTTP.images[url], nil)
|
||||
@ -57,6 +75,9 @@ function HTTP.downloadImage(url, callback)
|
||||
end
|
||||
|
||||
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
|
||||
timeout = HTTP.websocketTimeout
|
||||
end
|
||||
@ -84,6 +105,9 @@ end
|
||||
HTTP.WebSocketJSON = HTTP.webSocketJSON
|
||||
|
||||
function HTTP.cancel(operationId)
|
||||
if not g_http or not g_http.cancel then
|
||||
return
|
||||
end
|
||||
return g_http.cancel(operationId)
|
||||
end
|
||||
|
||||
|
@ -19,6 +19,10 @@ function UIComboBox:clearOptions()
|
||||
self:clearText()
|
||||
end
|
||||
|
||||
function UIComboBox:clear()
|
||||
return self:clearOptions()
|
||||
end
|
||||
|
||||
function UIComboBox:getOptionsCount()
|
||||
return #self.options
|
||||
end
|
||||
@ -108,12 +112,6 @@ function UIComboBox:removeOption(text)
|
||||
end
|
||||
end
|
||||
|
||||
function UIComboBox:clear()
|
||||
self.options = {}
|
||||
self.currentIndex = -1
|
||||
self:setText("")
|
||||
end
|
||||
|
||||
function UIComboBox:onMousePress(mousePos, mouseButton)
|
||||
local menu
|
||||
if self.menuScroll then
|
||||
|
@ -1,86 +1,37 @@
|
||||
botWindow = nil
|
||||
botButton = nil
|
||||
botConfigFile = nil
|
||||
botConfig = nil
|
||||
contentsPanel = nil
|
||||
configWindow = nil
|
||||
configEditorText = nil
|
||||
configList = nil
|
||||
botTabs = nil
|
||||
botPanel = nil
|
||||
editWindow = nil
|
||||
|
||||
local checkEvent = nil
|
||||
|
||||
local botStorage = {}
|
||||
local botStorageFile = nil
|
||||
local botWebSockets = {}
|
||||
local botMessages = nil
|
||||
local configCopy = ""
|
||||
local botTabs = nil
|
||||
local botExecutor = nil
|
||||
|
||||
local configList = nil
|
||||
local enableButton = nil
|
||||
local executeEvent = nil
|
||||
local checkMsgsEvent = nil
|
||||
local errorOccured = false
|
||||
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()
|
||||
dofile("defaultconfig")
|
||||
dofile("executor")
|
||||
|
||||
g_ui.importStyle("ui/basic.otui")
|
||||
g_ui.importStyle("ui/panels.otui")
|
||||
g_ui.importStyle("ui/config.otui")
|
||||
|
||||
connect(g_game, {
|
||||
onGameStart = online,
|
||||
onGameEnd = offline,
|
||||
onTalk = botOnTalk,
|
||||
onTextMessage = botOnTextMessage,
|
||||
onUse = botOnUse,
|
||||
onUseWith = botOnUseWith,
|
||||
onChannelList = botChannelList,
|
||||
onOpenChannel = botOpenChannel,
|
||||
onCloseChannel = botCloseChannel,
|
||||
onChannelEvent = botChannelEvent
|
||||
})
|
||||
|
||||
connect(rootWidget, { onKeyDown = botKeyDown,
|
||||
onKeyUp = botKeyUp,
|
||||
onKeyPress = botKeyPress })
|
||||
initCallbacks()
|
||||
|
||||
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 })
|
||||
|
||||
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 = modules.client_topmenu.addRightGameToggleButton('botButton', tr('Bot'), '/images/topbuttons/bot', toggle)
|
||||
botButton:setOn(false)
|
||||
botButton:hide()
|
||||
|
||||
@ -93,107 +44,188 @@ function init()
|
||||
statusLabel = contentsPanel.statusLabel
|
||||
botMessages = contentsPanel.messages
|
||||
botTabs = contentsPanel.botTabs
|
||||
botPanel = contentsPanel.botPanel
|
||||
botTabs:setContentWidget(botPanel)
|
||||
botTabs:setContentWidget(contentsPanel.botPanel)
|
||||
|
||||
configWindow = g_ui.displayUI('config')
|
||||
configWindow: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
|
||||
editWindow = g_ui.displayUI('edit')
|
||||
editWindow:hide()
|
||||
|
||||
if g_game.isOnline() then
|
||||
clear()
|
||||
online()
|
||||
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()
|
||||
saveConfig()
|
||||
clearConfig()
|
||||
|
||||
disconnect(rootWidget, { onKeyDown = botKeyDown,
|
||||
onKeyUp = botKeyUp,
|
||||
onKeyPress = botKeyPress })
|
||||
save()
|
||||
clear()
|
||||
|
||||
disconnect(g_game, {
|
||||
onGameStart = online,
|
||||
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, {
|
||||
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(checkEvent)
|
||||
|
||||
removeEvent(executeEvent)
|
||||
removeEvent(checkMsgsEvent)
|
||||
editWindow:destroy()
|
||||
|
||||
botWindow: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
|
||||
|
||||
function onMiniWindowClose()
|
||||
@ -212,228 +244,63 @@ end
|
||||
|
||||
function online()
|
||||
botButton:show()
|
||||
updateEnabled()
|
||||
if botConfig.enabled then
|
||||
scheduleEvent(refreshConfig, 20)
|
||||
else
|
||||
clearConfig()
|
||||
end
|
||||
if executeEvent == nil then
|
||||
executeEvent = scheduleEvent(executeConfig, 200)
|
||||
checkMsgsEvent = scheduleEvent(checkMsgs, 200)
|
||||
end
|
||||
scheduleEvent(refresh, 20)
|
||||
end
|
||||
|
||||
function offline()
|
||||
save()
|
||||
clear()
|
||||
botButton:hide()
|
||||
configWindow:hide()
|
||||
clearConfig()
|
||||
removeEvent(executeEvent)
|
||||
removeEvent(checkMsgsEvent)
|
||||
executeEvent = nil
|
||||
checkMsgsEvent = nil
|
||||
editWindow:hide()
|
||||
end
|
||||
|
||||
function toggleBot()
|
||||
botConfig.enabled = not botConfig.enabled
|
||||
if botConfig.enabled then
|
||||
refreshConfig()
|
||||
else
|
||||
clearConfig()
|
||||
end
|
||||
updateEnabled()
|
||||
function onError(message)
|
||||
statusLabel:setOn(true)
|
||||
statusLabel:setText("Error:\n" .. message)
|
||||
g_logger.error("[BOT] " .. message)
|
||||
end
|
||||
|
||||
function updateEnabled()
|
||||
if botConfig.enabled then
|
||||
enableButton:setText(tr('On'))
|
||||
enableButton:setColor('#00AA00FF')
|
||||
else
|
||||
enableButton:setText(tr('Off'))
|
||||
enableButton:setColor('#FF0000FF')
|
||||
statusLabel:setText(tr("Status: disabled"))
|
||||
end
|
||||
errorOccured = false
|
||||
function edit()
|
||||
editWindow:show()
|
||||
editWindow:focus()
|
||||
editWindow:raise()
|
||||
end
|
||||
|
||||
function editConfig()
|
||||
local config = configList.currentIndex
|
||||
configWindow:show()
|
||||
configWindow:raise()
|
||||
configWindow:focus()
|
||||
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"
|
||||
function createDefaultConfig()
|
||||
if not g_resources.directoryExists("/bot/default_config") then
|
||||
g_resources.makeDir("/bot/default_config")
|
||||
if not g_resources.directoryExists("/bot/default_config") then
|
||||
return onError("Can't create default_config directory in " .. g_resources.getWriteDir())
|
||||
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)
|
||||
local defaultConfigFiles = g_resources.listDirectoryFiles("default_config", true, false)
|
||||
for i, file in ipairs(defaultConfigFiles) do
|
||||
local baseName = file:split("/")
|
||||
baseName = baseName[#baseName]
|
||||
local contents = g_resources.readFileContents(file)
|
||||
if contents:len() > 0 then
|
||||
g_resources.writeFileContents("/bot/default_config/" .. baseName, contents)
|
||||
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
|
||||
|
||||
function refreshConfig()
|
||||
configWindow:hide()
|
||||
|
||||
botConfig.selectedConfig = configList.currentIndex
|
||||
if not botConfig.enabled then
|
||||
return
|
||||
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
|
||||
|
||||
function botMsgCallback(category, msg)
|
||||
-- Executor
|
||||
function message(category, msg)
|
||||
local widget = g_ui.createWidget('BotLabel', botMessages)
|
||||
widget.added = g_clock.millis()
|
||||
if category == 'error' then
|
||||
widget:setText(msg)
|
||||
widget:setColor("red")
|
||||
g_logger.error("[BOT] " .. msg)
|
||||
elseif category == 'warn' then
|
||||
widget:setText(msg)
|
||||
widget:setColor("yellow")
|
||||
g_logger.warning("[BOT] " .. msg)
|
||||
elseif category == 'info' then
|
||||
widget:setText(msg)
|
||||
widget:setColor("white")
|
||||
g_logger.info("[BOT] " .. msg)
|
||||
end
|
||||
|
||||
if botMessages:getChildCount() > 5 then
|
||||
@ -441,132 +308,233 @@ function botMsgCallback(category, msg)
|
||||
end
|
||||
end
|
||||
|
||||
function checkMsgs()
|
||||
checkMsgsEvent = scheduleEvent(checkMsgs, 200)
|
||||
function check()
|
||||
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()
|
||||
if widget and widget.added + 5000 < g_clock.millis() then
|
||||
widget:destroy()
|
||||
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)
|
||||
local status, result = pcall(func)
|
||||
if not status then
|
||||
errorOccured = true
|
||||
statusLabel:setText("Error: " .. result)
|
||||
onError(result)
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
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
|
||||
safeBotCall(function() compiledConfig.callbacks.onKeyDown(keyCode, keyboardModifiers) end)
|
||||
safeBotCall(function() botExecutor.callbacks.onKeyDown(keyCode, keyboardModifiers) end)
|
||||
end
|
||||
|
||||
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
|
||||
safeBotCall(function() compiledConfig.callbacks.onKeyUp(keyCode, keyboardModifiers) end)
|
||||
safeBotCall(function() botExecutor.callbacks.onKeyUp(keyCode, keyboardModifiers) end)
|
||||
end
|
||||
|
||||
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
|
||||
safeBotCall(function() compiledConfig.callbacks.onKeyPress(keyCode, keyboardModifiers, autoRepeatTicks) end)
|
||||
safeBotCall(function() botExecutor.callbacks.onKeyPress(keyCode, keyboardModifiers, autoRepeatTicks) end)
|
||||
end
|
||||
|
||||
function botOnTalk(name, level, mode, text, channelId, pos)
|
||||
if compiledConfig == nil then return false end
|
||||
safeBotCall(function() compiledConfig.callbacks.onTalk(name, level, mode, text, channelId, pos) end)
|
||||
if botExecutor == nil then return false end
|
||||
safeBotCall(function() botExecutor.callbacks.onTalk(name, level, mode, text, channelId, pos) end)
|
||||
end
|
||||
|
||||
function botOnTextMessage(mode, text)
|
||||
if compiledConfig == nil then return false end
|
||||
safeBotCall(function() compiledConfig.callbacks.onTextMessage(mode, text) end)
|
||||
if botExecutor == nil then return false end
|
||||
safeBotCall(function() botExecutor.callbacks.onTextMessage(mode, text) end)
|
||||
end
|
||||
|
||||
function botAddThing(tile, thing)
|
||||
if compiledConfig == nil then return false end
|
||||
safeBotCall(function() compiledConfig.callbacks.onAddThing(tile, thing) end)
|
||||
if botExecutor == nil then return false end
|
||||
safeBotCall(function() botExecutor.callbacks.onAddThing(tile, thing) end)
|
||||
end
|
||||
|
||||
function botRemoveThing(tile, thing)
|
||||
if compiledConfig == nil then return false end
|
||||
safeBotCall(function() compiledConfig.callbacks.onRemoveThing(tile, thing) end)
|
||||
if botExecutor == nil then return false end
|
||||
safeBotCall(function() botExecutor.callbacks.onRemoveThing(tile, thing) end)
|
||||
end
|
||||
|
||||
function botCreatureAppear(creature)
|
||||
if compiledConfig == nil then return false end
|
||||
safeBotCall(function() compiledConfig.callbacks.onCreatureAppear(creature) end)
|
||||
if botExecutor == nil then return false end
|
||||
safeBotCall(function() botExecutor.callbacks.onCreatureAppear(creature) end)
|
||||
end
|
||||
|
||||
function botCreatureDisappear(creature)
|
||||
if compiledConfig == nil then return false end
|
||||
safeBotCall(function() compiledConfig.callbacks.onCreatureDisappear(creature) end)
|
||||
if botExecutor == nil then return false end
|
||||
safeBotCall(function() botExecutor.callbacks.onCreatureDisappear(creature) end)
|
||||
end
|
||||
|
||||
function botCreaturePositionChange(creature, newPos, oldPos)
|
||||
if compiledConfig == nil then return false end
|
||||
safeBotCall(function() compiledConfig.callbacks.onCreaturePositionChange(creature, newPos, oldPos) end)
|
||||
if botExecutor == nil then return false end
|
||||
safeBotCall(function() botExecutor.callbacks.onCreaturePositionChange(creature, newPos, oldPos) end)
|
||||
end
|
||||
|
||||
function botCraetureHealthPercentChange(creature, healthPercent)
|
||||
if compiledConfig == nil then return false end
|
||||
safeBotCall(function() compiledConfig.callbacks.onCreatureHealthPercentChange(creature, healthPercent) end)
|
||||
if botExecutor == nil then return false end
|
||||
safeBotCall(function() botExecutor.callbacks.onCreatureHealthPercentChange(creature, healthPercent) end)
|
||||
end
|
||||
|
||||
function botOnUse(pos, itemId, stackPos, subType)
|
||||
if compiledConfig == nil then return false end
|
||||
safeBotCall(function() compiledConfig.callbacks.onUse(pos, itemId, stackPos, subType) end)
|
||||
if botExecutor == nil then return false end
|
||||
safeBotCall(function() botExecutor.callbacks.onUse(pos, itemId, stackPos, subType) end)
|
||||
end
|
||||
|
||||
function botOnUseWith(pos, itemId, target, subType)
|
||||
if compiledConfig == nil then return false end
|
||||
safeBotCall(function() compiledConfig.callbacks.onUseWith(pos, itemId, target, subType) end)
|
||||
if botExecutor == nil then return false end
|
||||
safeBotCall(function() botExecutor.callbacks.onUseWith(pos, itemId, target, subType) end)
|
||||
end
|
||||
|
||||
function botContainerOpen(container, previousContainer)
|
||||
if compiledConfig == nil then return false end
|
||||
safeBotCall(function() compiledConfig.callbacks.onContainerOpen(container, previousContainer) end)
|
||||
if botExecutor == nil then return false end
|
||||
safeBotCall(function() botExecutor.callbacks.onContainerOpen(container, previousContainer) end)
|
||||
end
|
||||
|
||||
function botContainerClose(container)
|
||||
if compiledConfig == nil then return false end
|
||||
safeBotCall(function() compiledConfig.callbacks.onContainerClose(container) end)
|
||||
if botExecutor == nil then return false end
|
||||
safeBotCall(function() botExecutor.callbacks.onContainerClose(container) end)
|
||||
end
|
||||
|
||||
function botContainerUpdateItem(container, slot, item)
|
||||
if compiledConfig == nil then return false end
|
||||
safeBotCall(function() compiledConfig.callbacks.onContainerUpdateItem(container, slot, item) end)
|
||||
if botExecutor == nil then return false end
|
||||
safeBotCall(function() botExecutor.callbacks.onContainerUpdateItem(container, slot, item) end)
|
||||
end
|
||||
|
||||
function botOnMissle(missle)
|
||||
if compiledConfig == nil then return false end
|
||||
safeBotCall(function() compiledConfig.callbacks.onMissle(missle) end)
|
||||
end
|
||||
|
||||
function botOnMissle(missle)
|
||||
if compiledConfig == nil then return false end
|
||||
safeBotCall(function() compiledConfig.callbacks.onMissle(missle) end)
|
||||
if botExecutor == nil then return false end
|
||||
safeBotCall(function() botExecutor.callbacks.onMissle(missle) end)
|
||||
end
|
||||
|
||||
function botChannelList(channels)
|
||||
if compiledConfig == nil then return false end
|
||||
safeBotCall(function() compiledConfig.callbacks.onChannelList(channels) end)
|
||||
if botExecutor == nil then return false end
|
||||
safeBotCall(function() botExecutor.callbacks.onChannelList(channels) end)
|
||||
end
|
||||
|
||||
function botOpenChannel(channelId, name)
|
||||
if compiledConfig == nil then return false end
|
||||
safeBotCall(function() compiledConfig.callbacks.onOpenChannel(channelId, name) end)
|
||||
if botExecutor == nil then return false end
|
||||
safeBotCall(function() botExecutor.callbacks.onOpenChannel(channelId, name) end)
|
||||
end
|
||||
|
||||
function botCloseChannel(channelId)
|
||||
if compiledConfig == nil then return false end
|
||||
safeBotCall(function() compiledConfig.callbacks.onCloseChannel(channelId) end)
|
||||
if botExecutor == nil then return false end
|
||||
safeBotCall(function() botExecutor.callbacks.onCloseChannel(channelId) end)
|
||||
end
|
||||
|
||||
function botChannelEvent(channelId, name, event)
|
||||
if compiledConfig == nil then return false end
|
||||
safeBotCall(function() compiledConfig.callbacks.onChannelEvent(channelId, name, event) end)
|
||||
if botExecutor == nil then return false end
|
||||
safeBotCall(function() botExecutor.callbacks.onChannelEvent(channelId, name, event) end)
|
||||
end
|
||||
|
@ -8,16 +8,14 @@ MiniWindow
|
||||
&autoOpen: 10
|
||||
|
||||
MiniWindowContents
|
||||
margin-left: 5
|
||||
margin-right: 3
|
||||
|
||||
ComboBox
|
||||
id: config
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
margin-top: 2
|
||||
margin-right: 90
|
||||
margin-left: 2
|
||||
margin-right: 75
|
||||
text-offset: 3 0
|
||||
|
||||
Button
|
||||
@ -26,34 +24,52 @@ MiniWindow
|
||||
anchors.left: prev.right
|
||||
anchors.right: parent.right
|
||||
!text: tr('Edit')
|
||||
@onClick: modules.game_bot.editConfig()
|
||||
margin-left: 5
|
||||
margin-right: 45
|
||||
@onClick: modules.game_bot.edit()
|
||||
margin-left: 3
|
||||
margin-right: 37
|
||||
|
||||
Button
|
||||
id: enableButton
|
||||
anchors.top: prev.top
|
||||
anchors.left: prev.right
|
||||
anchors.right: parent.right
|
||||
@onClick: modules.game_bot.toggleBot()
|
||||
margin-left: 5
|
||||
margin-left: 3
|
||||
margin-right: 2
|
||||
|
||||
$on:
|
||||
text: On
|
||||
color: #00AA00
|
||||
|
||||
$!on:
|
||||
text: Off
|
||||
color: #FF0000
|
||||
|
||||
Label
|
||||
id: statusLabel
|
||||
anchors.top: prev.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
margin-top: 3
|
||||
text-auto-resize: true
|
||||
!text: tr('Status: waiting')
|
||||
text-align: center
|
||||
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
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: prev.bottom
|
||||
margin-top: 3
|
||||
margin-left: 2
|
||||
margin-right: 2
|
||||
|
||||
Panel
|
||||
anchors.top: prev.bottom
|
||||
@ -69,6 +85,8 @@ MiniWindow
|
||||
anchors.right: parent.right
|
||||
anchors.top: prev.bottom
|
||||
margin-top: 5
|
||||
margin-left: 2
|
||||
margin-right: 2
|
||||
|
||||
MoveableTabBar
|
||||
id: botTabs
|
||||
@ -76,6 +94,8 @@ MiniWindow
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
tab-spacing: 1
|
||||
margin-left: 1
|
||||
margin-right: 1
|
||||
height: 20
|
||||
movable: false
|
||||
|
||||
|
BIN
modules/game_bot/configs.png
Normal file
BIN
modules/game_bot/configs.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 20 KiB |
8
modules/game_bot/default_config/battle.lua
Normal file
8
modules/game_bot/default_config/battle.lua
Normal file
@ -0,0 +1,8 @@
|
||||
local batTab = addTab("Batt")
|
||||
|
||||
Panels.AttackSpell(batTab)
|
||||
Panels.AttackItem(batTab)
|
||||
|
||||
Panels.AttackLeaderTarget(batTab)
|
||||
Panels.LimitFloor(batTab)
|
||||
Panels.AntiPush(batTab)
|
8
modules/game_bot/default_config/cavebot.lua
Normal file
8
modules/game_bot/default_config/cavebot.lua
Normal 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)
|
5
modules/game_bot/default_config/example.otui
Normal file
5
modules/game_bot/default_config/example.otui
Normal file
@ -0,0 +1,5 @@
|
||||
ExampleLabel2 < Label
|
||||
text: LOL
|
||||
height: 200
|
||||
width: 50
|
||||
|
16
modules/game_bot/default_config/hp.lua
Normal file
16
modules/game_bot/default_config/hp.lua
Normal 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)
|
||||
|
16
modules/game_bot/default_config/main.lua
Normal file
16
modules/game_bot/default_config/main.lua
Normal 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")
|
7
modules/game_bot/default_config/npc.lua
Normal file
7
modules/game_bot/default_config/npc.lua
Normal 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)
|
84
modules/game_bot/default_config/tools.lua
Normal file
84
modules/game_bot/default_config/tools.lua
Normal 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
121
modules/game_bot/edit.otui
Normal 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()
|
@ -1,7 +1,27 @@
|
||||
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 = {}
|
||||
context.configDir = "/bot/".. config
|
||||
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._websockets = websockets
|
||||
|
||||
@ -10,7 +30,7 @@ function executeBot(config, storage, tabs, msgCallback, saveConfigCallback, webs
|
||||
context.storage._macros = {} -- active macros
|
||||
end
|
||||
|
||||
--
|
||||
-- macros, hotkeys, scheduler, callbacks
|
||||
context._macros = {}
|
||||
context._hotkeys = {}
|
||||
context._scheduler = {}
|
||||
@ -87,8 +107,15 @@ function executeBot(config, storage, tabs, msgCallback, saveConfigCallback, webs
|
||||
dofiles("panels")
|
||||
G.botContext = nil
|
||||
|
||||
-- run script
|
||||
assert(load(config, nil, nil, context))()
|
||||
-- run ui scripts
|
||||
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 {
|
||||
script = function()
|
||||
|
113
modules/game_bot/functions/config.lua
Normal file
113
modules/game_bot/functions/config.lua
Normal 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
|
111
modules/game_bot/functions/npc.lua
Normal file
111
modules/game_bot/functions/npc.lua
Normal 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
|
@ -11,7 +11,7 @@ end
|
||||
|
||||
context.addTab = function(name)
|
||||
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
|
||||
|
||||
context.addSwitch = function(id, text, onClickCallback, parent)
|
||||
|
BIN
modules/game_bot/scripts.png
Normal file
BIN
modules/game_bot/scripts.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 14 KiB |
@ -39,8 +39,80 @@ BotSeparator < HorizontalSeparator
|
||||
margin-bottom: 3
|
||||
|
||||
BotPanel < Panel
|
||||
layout:
|
||||
type: verticalBox
|
||||
ScrollablePanel
|
||||
id: content
|
||||
anchors.fill: parent
|
||||
margin-right: 8
|
||||
margin-left: 1
|
||||
margin-bottom: 5
|
||||
vertical-scrollbar: botPanelScroll
|
||||
layout:
|
||||
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
|
||||
background-color: alpha
|
||||
|
51
modules/game_bot/ui/config.otui
Normal file
51
modules/game_bot/ui/config.otui
Normal 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
|
@ -112,7 +112,6 @@ ItemsRow < Panel
|
||||
id: item1
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
margin-left: 3
|
||||
|
||||
BotItem
|
||||
id: item2
|
||||
|
@ -898,6 +898,7 @@ end
|
||||
function sendCurrentMessage()
|
||||
local message = consoleTextEdit:getText()
|
||||
if #message == 0 then return end
|
||||
if not isChatEnabled() then return end
|
||||
consoleTextEdit:clearText()
|
||||
|
||||
-- send message
|
||||
@ -1071,6 +1072,10 @@ function setIgnoreNpcMessages(ignore)
|
||||
end
|
||||
|
||||
function navigateMessageHistory(step)
|
||||
if not isChatEnabled() then
|
||||
return
|
||||
end
|
||||
|
||||
local numCommands = #messageHistory
|
||||
if numCommands > 0 then
|
||||
currentMessageIndex = math.min(math.max(currentMessageIndex + step, 0), numCommands)
|
||||
|
@ -95,6 +95,9 @@ function onContainerOpen(container, previousContainer)
|
||||
containerWindow:hide()
|
||||
end
|
||||
containerWindow.onDrop = function(container, widget, mousePos)
|
||||
if containerPanel:getChildByPos(mousePos) then
|
||||
return false
|
||||
end
|
||||
local child = containerPanel:getChildByIndex(-1)
|
||||
if child then
|
||||
child:onDrop(widget, mousePos, true)
|
||||
|
@ -286,9 +286,13 @@ function onUseWith(clickedWidget, mousePosition)
|
||||
local tile = clickedWidget:getTile(mousePosition)
|
||||
if tile 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
|
||||
print("normal")
|
||||
g_game.useWith(selectedThing, tile:getTopUseThing(), selectedSubtype)
|
||||
end
|
||||
end
|
||||
|
@ -13,6 +13,7 @@ searchText = nil
|
||||
setupPanel = nil
|
||||
quantity = nil
|
||||
quantityScroll = nil
|
||||
idLabel = nil
|
||||
nameLabel = nil
|
||||
priceLabel = nil
|
||||
moneyLabel = nil
|
||||
@ -49,6 +50,7 @@ function init()
|
||||
|
||||
setupPanel = npcWindow:recursiveGetChildById('setupPanel')
|
||||
quantityScroll = setupPanel:getChildById('quantityScroll')
|
||||
idLabel = setupPanel:getChildById('id')
|
||||
nameLabel = setupPanel:getChildById('name')
|
||||
priceLabel = setupPanel:getChildById('price')
|
||||
moneyLabel = setupPanel:getChildById('money')
|
||||
@ -119,6 +121,23 @@ end
|
||||
|
||||
function 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
|
||||
|
||||
function onItemBoxChecked(widget)
|
||||
@ -226,6 +245,7 @@ function setShowYourCapacity(state)
|
||||
end
|
||||
|
||||
function clearSelectedItem()
|
||||
idLabel:clearText()
|
||||
nameLabel:clearText()
|
||||
weightLabel:clearText()
|
||||
priceLabel:clearText()
|
||||
@ -288,6 +308,7 @@ function canTradeItem(item)
|
||||
end
|
||||
|
||||
function refreshItem(item)
|
||||
idLabel:setText(item.ptr:getId())
|
||||
nameLabel:setText(item.name)
|
||||
weightLabel:setText(string.format('%.2f', item.weight) .. ' ' .. WEIGHT_UNIT)
|
||||
priceLabel:setText(formatCurrency(getItemPrice(item)))
|
||||
@ -420,11 +441,11 @@ end
|
||||
|
||||
function closeNpcTrade()
|
||||
g_game.closeNpcTrade()
|
||||
hide()
|
||||
addEvent(hide)
|
||||
end
|
||||
|
||||
function onCloseNpcTrade()
|
||||
hide()
|
||||
addEvent(hide)
|
||||
end
|
||||
|
||||
function onPlayerGoods(money, items)
|
||||
|
@ -101,13 +101,24 @@ MainWindow
|
||||
image-color: #ffffff88
|
||||
|
||||
Label
|
||||
!text: tr('Name') .. ':'
|
||||
!text: tr('Id') .. ':'
|
||||
anchors.left: parent.left
|
||||
anchors.top: parent.top
|
||||
margin-top: 5
|
||||
margin-left: 5
|
||||
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
|
||||
id: name
|
||||
|
||||
|
@ -244,8 +244,11 @@ function sortBy(state)
|
||||
end
|
||||
|
||||
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()
|
||||
for i=1,childrenCount do
|
||||
local child = vipList:getChildByIndex(i)
|
||||
|
@ -383,7 +383,7 @@ function turn(dir, repeated)
|
||||
end
|
||||
lastTurnDirection = dir
|
||||
nextWalkDir = nil
|
||||
player:lockWalk(150)
|
||||
player:lockWalk(g_settings.getNumber('walkCtrlTurnDelay'))
|
||||
end
|
||||
end
|
||||
|
||||
|
BIN
otclient_dx.exe
BIN
otclient_dx.exe
Binary file not shown.
BIN
otclient_gl.exe
BIN
otclient_gl.exe
Binary file not shown.
BIN
otclient_linux
BIN
otclient_linux
Binary file not shown.
Binary file not shown.
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user