2020-01-08 00:24:01 +01:00

541 lines
14 KiB
Lua

botWindow = nil
botButton = nil
contentsPanel = nil
editWindow = nil
local checkEvent = nil
local botStorage = {}
local botStorageFile = nil
local botWebSockets = {}
local botMessages = nil
local botTabs = nil
local botExecutor = nil
local configList = nil
local enableButton = nil
local executeEvent = nil
local statusLabel = nil
function init()
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,
})
initCallbacks()
botButton = modules.client_topmenu.addRightGameToggleButton('botButton', tr('Bot'), '/images/topbuttons/bot', toggle)
botButton:setOn(false)
botButton:hide()
botWindow = g_ui.loadUI('bot', modules.game_interface.getLeftPanel())
botWindow:setup()
contentsPanel = botWindow.contentsPanel
configList = contentsPanel.config
enableButton = contentsPanel.enableButton
statusLabel = contentsPanel.statusLabel
botMessages = contentsPanel.messages
botTabs = contentsPanel.botTabs
botTabs:setContentWidget(contentsPanel.botPanel)
editWindow = g_ui.displayUI('edit')
editWindow:hide()
if g_game.isOnline() then
clear()
online()
end
end
function terminate()
save()
clear()
disconnect(g_game, {
onGameStart = online,
onGameEnd = offline,
})
terminateCallbacks()
removeEvent(checkEvent)
editWindow:destroy()
botWindow:destroy()
botButton: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()
botButton:setOn(false)
end
function toggle()
if botButton:isOn() then
botWindow:close()
botButton:setOn(false)
else
botWindow:open()
botButton:setOn(true)
end
end
function online()
botButton:show()
scheduleEvent(refresh, 20)
end
function offline()
save()
clear()
botButton:hide()
editWindow:hide()
end
function onError(message)
statusLabel:setOn(true)
statusLabel:setText("Error:\n" .. message)
g_logger.error("[BOT] " .. message)
end
function edit()
editWindow:show()
editWindow:focus()
editWindow:raise()
end
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
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
end
-- 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
botMessages:getFirstChild():destroy()
end
end
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
onError(result)
end
end
function botKeyDown(widget, keyCode, keyboardModifiers)
if botExecutor == nil then return false end
if keyCode == KeyUnknown then return end
safeBotCall(function() botExecutor.callbacks.onKeyDown(keyCode, keyboardModifiers) end)
end
function botKeyUp(widget, keyCode, keyboardModifiers)
if botExecutor == nil then return false end
if keyCode == KeyUnknown then return end
safeBotCall(function() botExecutor.callbacks.onKeyUp(keyCode, keyboardModifiers) end)
end
function botKeyPress(widget, keyCode, keyboardModifiers, autoRepeatTicks)
if botExecutor == nil then return false end
if keyCode == KeyUnknown then return end
safeBotCall(function() botExecutor.callbacks.onKeyPress(keyCode, keyboardModifiers, autoRepeatTicks) end)
end
function botOnTalk(name, level, mode, text, channelId, pos)
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 botExecutor == nil then return false end
safeBotCall(function() botExecutor.callbacks.onTextMessage(mode, text) end)
end
function botAddThing(tile, thing)
if botExecutor == nil then return false end
safeBotCall(function() botExecutor.callbacks.onAddThing(tile, thing) end)
end
function botRemoveThing(tile, thing)
if botExecutor == nil then return false end
safeBotCall(function() botExecutor.callbacks.onRemoveThing(tile, thing) end)
end
function botCreatureAppear(creature)
if botExecutor == nil then return false end
safeBotCall(function() botExecutor.callbacks.onCreatureAppear(creature) end)
end
function botCreatureDisappear(creature)
if botExecutor == nil then return false end
safeBotCall(function() botExecutor.callbacks.onCreatureDisappear(creature) end)
end
function botCreaturePositionChange(creature, newPos, oldPos)
if botExecutor == nil then return false end
safeBotCall(function() botExecutor.callbacks.onCreaturePositionChange(creature, newPos, oldPos) end)
end
function botCraetureHealthPercentChange(creature, healthPercent)
if botExecutor == nil then return false end
safeBotCall(function() botExecutor.callbacks.onCreatureHealthPercentChange(creature, healthPercent) end)
end
function botOnUse(pos, itemId, stackPos, subType)
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 botExecutor == nil then return false end
safeBotCall(function() botExecutor.callbacks.onUseWith(pos, itemId, target, subType) end)
end
function botContainerOpen(container, previousContainer)
if botExecutor == nil then return false end
safeBotCall(function() botExecutor.callbacks.onContainerOpen(container, previousContainer) end)
end
function botContainerClose(container)
if botExecutor == nil then return false end
safeBotCall(function() botExecutor.callbacks.onContainerClose(container) end)
end
function botContainerUpdateItem(container, slot, item)
if botExecutor == nil then return false end
safeBotCall(function() botExecutor.callbacks.onContainerUpdateItem(container, slot, item) end)
end
function botOnMissle(missle)
if botExecutor == nil then return false end
safeBotCall(function() botExecutor.callbacks.onMissle(missle) end)
end
function botChannelList(channels)
if botExecutor == nil then return false end
safeBotCall(function() botExecutor.callbacks.onChannelList(channels) end)
end
function botOpenChannel(channelId, name)
if botExecutor == nil then return false end
safeBotCall(function() botExecutor.callbacks.onOpenChannel(channelId, name) end)
end
function botCloseChannel(channelId)
if botExecutor == nil then return false end
safeBotCall(function() botExecutor.callbacks.onCloseChannel(channelId) end)
end
function botChannelEvent(channelId, name, event)
if botExecutor == nil then return false end
safeBotCall(function() botExecutor.callbacks.onChannelEvent(channelId, name, event) end)
end