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
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

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 = ""
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",

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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()
}
}

View File

@ -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')

View File

@ -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

View File

@ -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

View File

@ -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]
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
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
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

View File

@ -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

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)
-- 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()

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.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)

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

@ -39,9 +39,81 @@ BotSeparator < HorizontalSeparator
margin-bottom: 3
BotPanel < Panel
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
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
anchors.top: parent.top
anchors.left: parent.left
margin-left: 3
BotItem
id: item2

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

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

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.