Version 0.993 BETA (added bot)

This commit is contained in:
OTCv8
2019-10-09 16:49:24 +02:00
parent c5c600e83e
commit c477637a46
15 changed files with 876 additions and 21 deletions

370
modules/game_bot/bot.lua Normal file
View File

@@ -0,0 +1,370 @@
botWindow = nil
botButton = nil
botConfigFile = nil
botConfig = nil
contentsPanel = nil
configWindow = nil
configEditorText = nil
configList = nil
botPanel = nil
local botMessages = nil
local showingDocumentation = false
local configCopy = ""
local enableButton = nil
local executeEvent = nil
local checkMsgsEvent = nil
local errorOccured = false
local statusLabel = nil
local compiledConfig = nil
local configTab = nil
local tabs = {"macros", "hotkeys", "callbacks", "other"}
local mainTab = nil
local activeTab = nil
local editorText = {"", ""}
function init()
dofile("defaultconfig")
dofile("executor")
connect(g_game, { onGameStart = online, onGameEnd = offline, onTalk = botOnTalk})
connect(rootWidget, { onKeyDown = botKeyDown,
onKeyUp = botKeyUp,
onKeyPress = botKeyPress })
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
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.getRightPanel())
botWindow:setup()
contentsPanel = botWindow:getChildById('contentsPanel')
configList = contentsPanel:getChildById('config')
enableButton = contentsPanel:getChildById('enableButton')
statusLabel = contentsPanel:getChildById('statusLabel')
botMessages = contentsPanel:getChildById('messages')
botPanel = contentsPanel:getChildById('botPanel')
configWindow = g_ui.displayUI('config')
configWindow:hide()
configEditorText = configWindow:getChildById('text')
configTab = configWindow:getChildById('configTab')
configTab.onTabChange = editorTabChanged
for i=1,#botConfig.configs do
if botConfig.configs[i].name ~= nil then
configList:addOption(botConfig.configs[i].name)
else
configList:addOption("Config #" .. i)
end
end
if type(botConfig.selectedConfig) == 'number' then
configList:setCurrentIndex(botConfig.selectedConfig)
end
configList.onOptionChange = modules.game_bot.refreshConfig
mainTab = configTab:addTab("all")
for k, v in ipairs(tabs) do
configTab:addTab(v, nil, nil)
end
if g_game.isOnline() then
online()
end
end
function saveConfig()
botConfigFile:set("config", json.encode(botConfig))
botConfigFile:save()
end
function terminate()
saveConfig()
clearConfig()
disconnect(rootWidget, { onKeyDown = botKeyDown,
onKeyUp = botKeyUp,
onKeyPress = botKeyPress })
disconnect(g_game, { onGameStart = online, onGameEnd = offline, onTalk = botOnTalk})
removeEvent(executeEvent)
removeEvent(checkMsgsEvent)
botWindow:destroy()
botButton:destroy()
configWindow:destroy()
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()
updateEnabled()
if botConfig.enabled then
refreshConfig()
else
clearConfig()
end
if executeEvent == nil then
executeEvent = scheduleEvent(executeConfig, 200)
checkMsgsEvent = scheduleEvent(checkMsgs, 200)
end
end
function toggleBot()
botConfig.enabled = not botConfig.enabled
if botConfig.enabled then
refreshConfig()
else
clearConfig()
end
updateEnabled()
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
end
function editConfig()
local config = configList.currentIndex
configWindow:show()
configWindow:raise()
configWindow:focus()
editorText = {botConfig.configs[config].script or "", ""}
configEditorText:setText(botConfig.configs[config].script)
configEditorText:setEditable(true)
activeTab = mainTab
configTab:selectTab(mainTab)
showingDocumentation = false
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
editorText = {editorText[1] .. "--#" .. activeTab:getText():lower() .. "\n" .. configEditorText:getText() .. editorText[2], ""}
configEditorText:setText(editorText[1])
end
function editorTabChanged(holder, tab)
if activeTab == tab then
return
end
restoreMainTab()
activeTab = tab
if tab == mainTab then
return
end
local splitted = split2(editorText[1], "--#" .. activeTab:getText():lower() .. "\n")
local splitted2 = split2(splitted[2], "--#")
if splitted2[2]:len() > 1 then
splitted2[2] = "--#" .. splitted2[2]
end
editorText = {splitted[1], splitted2[2]}
configEditorText:setText(splitted2[1])
end
function saveEditedConfig()
restoreMainTab()
local config = configList.currentIndex
local text = configEditorText:getText()
configWindow:hide()
botConfig.configs[config].script = text
if text:len() > 3 and text:sub(1,2) == '--' and text:sub(3,3) ~= '#' then
local delim_from, delim_to = string.find( text, "\n", 3, true)
if delim_from then
botConfig.configs[config].name = string.sub( text, 3 , delim_from - 1 ):trim()
configList:updateCurrentOption(botConfig.configs[config].name)
end
end
refreshConfig()
end
function clearConfig()
compiledConfig = nil
botPanel:destroyChildren()
botMessages:destroyChildren()
botMessages:updateLayout()
end
function refreshConfig()
clearConfig()
configWindow:hide()
botConfig.selectedConfig = configList.currentIndex
if not botConfig.enabled then
return
end
saveConfig()
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
local status, result = pcall(function() return executeBot(config.script, config.storage, botPanel, botMsgCallback) end)
if not status then
errorOccured = true
statusLabel:setText(tr("Error: " .. tostring(result)))
return
end
compiledConfig = result
statusLabel:setText(tr("Status: working"))
end
function executeConfig()
executeEvent = scheduleEvent(executeConfig, 20)
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(tr("Error: " .. result))
return
end
end
function botMsgCallback(category, msg)
local widget = g_ui.createWidget('BotLabel', botMessages)
widget.added = g_clock.millis()
if category == 'error' then
widget:setText(msg)
widget:setColor("red")
elseif category == 'warn' then
widget:setText(msg)
widget:setColor("yellow")
elseif category == 'info' then
widget:setText(msg)
widget:setColor("white")
end
if botMessages:getChildCount() > 5 then
botMessages:getFirstChild():destroy()
end
end
function checkMsgs()
checkMsgsEvent = scheduleEvent(checkMsgs, 200)
local widget = botMessages:getFirstChild()
if widget and widget.added + 5000 < g_clock.millis() then
widget:destroy()
end
end
function botKeyDown(widget, keyCode, keyboardModifiers)
if keyCode == KeyUnknown or compiledConfig == nil then return false end
local status, result = pcall(function() compiledConfig.callbacks.onKeyDown(keyCode, keyboardModifiers) end)
if not status then
errorOccured = true
statusLabel:setText(tr("Error: " .. result))
end
return false
end
function botKeyUp(widget, keyCode, keyboardModifiers)
if keyCode == KeyUnknown or compiledConfig == nil then return false end
local status, result = pcall(function() compiledConfig.callbacks.onKeyUp(keyCode, keyboardModifiers) end)
if not status then
errorOccured = true
statusLabel:setText(tr("Error: " .. result))
end
return false
end
function botKeyPress(widget, keyCode, keyboardModifiers, autoRepeatTicks)
if keyCode == KeyUnknown or compiledConfig == nil then return false end
local status, result = pcall(function() compiledConfig.callbacks.onKeyPress(keyCode, keyboardModifiers, autoRepeatTicks) end)
if not status then
errorOccured = true
statusLabel:setText(tr("Error: " .. result))
end
return false
end
function botOnTalk(name, level, mode, text, channelId, pos)
if compiledConfig == nil then return false end
local status, result = pcall(function() compiledConfig.callbacks.onTalk(name, level, mode, text, channelId, pos) end)
if not status then
errorOccured = true
statusLabel:setText(tr("Error: " .. result))
end
return false
end
function botOnGet(oprationId, url, err, data)
if compiledConfig == nil then return false end
local status, result = pcall(function() compiledConfig.callbacks.onGet(oprationId, url, err, data) end)
if not status then
errorOccured = true
statusLabel:setText(tr("Error: " .. result))
end
return false
end

View File

@@ -0,0 +1,8 @@
Module
name: game_bot
description: Bot
author: otclient@otclient.ovh
sandboxed: true
scripts: [ bot ]
@onLoad: init()
@onUnload: terminate()

104
modules/game_bot/bot.otui Normal file
View File

@@ -0,0 +1,104 @@
BotButton < Button
margin-top: 2
BotSwitch < Button
margin-top: 2
image-color: green
$!on:
image-color: red
BotLabel < Label
margin-top: 2
text-auto-resize: true
text-align: center
text-wrap: true
BotPanel < Panel
margin-top: 2
layout:
type: verticalBox
fit-children: true
BotSeparator < HorizontalSeparator
margin-top: 5
margin-bottom: 3
MiniWindow
id: botWindow
!text: tr('Bot')
height: 200
icon: /images/topbuttons/bot
@onClose: modules.game_bot.onMiniWindowClose()
&save: true
MiniWindowContents
margin-left: 5
margin-right: 5
ComboBox
id: config
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
margin-top: 5
margin-right: 90
text-offset: 3 0
Button
id: editConfig
anchors.top: prev.top
anchors.left: prev.right
anchors.right: parent.right
!text: tr('Edit')
@onClick: modules.game_bot.editConfig()
margin-left: 5
margin-right: 45
Button
id: enableButton
anchors.top: prev.top
anchors.left: prev.right
anchors.right: parent.right
@onClick: modules.game_bot.toggleBot()
margin-left: 5
Label
id: statusLabel
anchors.top: prev.bottom
anchors.left: parent.left
anchors.right: parent.right
margin-top: 5
text-auto-resize: true
!text: tr('Status: waiting')
text-align: center
text-wrap: true
HorizontalSeparator
anchors.left: parent.left
anchors.right: parent.right
anchors.top: prev.bottom
margin-top: 5
Panel
anchors.top: prev.bottom
anchors.left: parent.left
anchors.right: parent.right
id: messages
layout:
type: verticalBox
fit-children: true
HorizontalSeparator
anchors.left: parent.left
anchors.right: parent.right
anchors.top: prev.bottom
margin-top: 5
Panel
anchors.top: prev.bottom
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.right: parent.right
id: botPanel
layout:
type: verticalBox

View File

@@ -0,0 +1,75 @@
ConfigTabBar < MoveableTabBar
height: 22
ConfigTabBarButton < MoveableTabBarButton
height: 22
padding: 15
ConfigTabBarPanel < MoveableTabBarPanel
MainWindow
id: configWindow
size: 650 500
!text: tr("Config editor")
Label
id: description
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
text-auto-resize: true
text-align: left
text-wrap: true
!text: tr("For more informations how to edit config, click 'Documentation' button")
ConfigTabBar
id: configTab
anchors.left: parent.left
anchors.top: prev.bottom
anchors.right: parent.right
margin-top: 5
tab-spacing: 2
movable: false
MultilineTextEdit
id: text
anchors.top: textScroll.top
anchors.left: parent.left
anchors.right: textScroll.left
anchors.bottom: textScroll.bottom
vertical-scrollbar: textScroll
text-wrap: true
VerticalScrollBar
id: textScroll
anchors.top: configTab.bottom
anchors.bottom: okButton.top
anchors.right: parent.right
margin-bottom: 10
step: 16
pixels-scroll: true
Button
id: documentationButton
!text: tr('Documentation')
anchors.bottom: parent.bottom
anchors.left: parent.left
width: 150
@onClick: scheduleEvent(function() g_platform.openUrl("https://github.com/OTCv8/otclient_bot") end, 50)
Button
id: okButton
!text: tr('Ok')
anchors.bottom: parent.bottom
anchors.right: next.left
margin-right: 10
width: 60
@onClick: modules.game_bot.saveEditedConfig()
Button
id: cancelButton
!text: tr('Cancel')
anchors.bottom: parent.bottom
anchors.right: parent.right
width: 60
@onClick: self:getParent():hide()

View File

@@ -0,0 +1,62 @@
botDefaultConfig = {
configs = {
{name = "Example", script = [[
--#Example config
--#macros
macro(5000, "macro send link", "f5", function()
g_game.talk("macro test - https://github.com/OTCv8/otclient_bot")
end)
macro(1000, "flag tiles", function()
local staticText = StaticText.create()
staticText:addMessage("t", 9, "xDDD")
local tile = player:getTile()
tile:clearTexts()
tile:addText(staticText)
for i = 1, 10 do
schedule(1000 * i, function()
staticText:setText(i)
end)
end
schedule(11000, function()
tile:clearTexts()
end)
end)
addSeparator("spe0")
--#hotkeys
hotkey('y', 'test hotkey', function() g_game.talk('hotkey elo') end)
--#callbacks
--#other
addLabel("label1", "Test label 1")
addSeparator("sep1")
addLabel("label2", "Test label 2")
storage.clicks = 0
addButton("button1", "Click me", function()
storage.clicks = storage.clicks + 1
ui.button1:setText("Clicks: " .. storage.clicks)
end)
HTTP.getJSON("https://api.ipify.org/?format=json", function(data, err)
if err then
warn("Whoops! Error occured: " .. err)
return
end
info("HTTP: My IP is: " .. tostring(data['ip']))
end)
info("Bot started")
]]},
{}, {}, {}, {}
},
enabled = false,
selectedConfig = 1
}

View File

@@ -0,0 +1,247 @@
function executeBot(config, storage, panel, msg)
local context = {}
context.panel = panel
context.storage = storage
if context.storage.macros == nil then
context.storage.macros = {} -- active macros
end
--
context.macros = {}
context.hotkeys = {}
context.scheduler = {}
context.callbacks = {
onKeyDown = {},
onKeyUp = {},
onKeyPress = {},
onTalk = {},
}
context.ui = {}
-- basic functions
context.print = print
context.pairs = pairs
context.ipairs = ipairs
context.tostring = tostring
context.math = math
context.table = table
context.string = string
context.tr = tr
context.json = json
context.regexMatch = regexMatch
-- game functions
context.say = g_game.talk
context.talk = g_game.talk
context.talkPrivate = context.talkPrivate
context.sayPrivate = context.talkPrivate
context.use = g_game.useInventoryItem
context.usewith = g_game.useInventoryItemWith
context.useWith = g_game.useInventoryItemWith
context.findItem = g_game.findItemInContainers
-- classes
context.g_game = g_game
context.g_map = g_map
context.StaticText = StaticText
context.HTTP = HTTP
-- log functions
context.info = function(text) return msg("info", text) end
context.warn = function(text) return msg("warn", text) end
context.error = function(text) return msg("error", text) end
context.warning = context.warn
-- UI
context.addSwitch = function(id, text, onClickCallback)
local switch = g_ui.createWidget('BotSwitch', context.panel)
switch:setId(id)
switch:setText(text)
switch.onClick = onClickCallback
context.ui[id] = switch
return switch
end
context.addButton = function(id, text, onClickCallback)
local button = g_ui.createWidget('BotButton', context.panel)
button:setId(id)
button:setText(text)
button.onClick = onClickCallback
context.ui[id] = button
return button
end
context.addLabel = function(id, text)
local label = g_ui.createWidget('BotLabel', context.panel)
label:setId(id)
label:setText(text)
context.ui[id] = label
return label
end
context.addSeparator = function(id)
local separator = g_ui.createWidget('BotSeparator', context.panel)
separator:setId(id)
context.ui[id] = separator
return separator
end
context.addMacroSwitch = function(name, keys)
local text = name
if keys:len() > 0 then
text = name .. " [" .. keys .. "]"
end
local switch = context.addSwitch("macro_" .. #context.macros, text, function(widget)
context.storage.macros[name] = not context.storage.macros[name]
widget:setOn(context.storage.macros[name])
end)
switch:setOn(context.storage.macros[name])
return switch
end
context.addHotkeySwitch = function(name, keys)
local text = name
if keys:len() > 0 then
text = name .. " [" .. keys .. "]"
end
local switch = context.addSwitch("hotkey_" .. #context.hotkeys, text, nil)
switch:setOn(false)
return switch
end
-- MAIN BOT FUNCTION
-- macro(timeout, callback)
-- macro(timeout, name, callback)
-- macro(timeout, name, hotkey, callback)
context.macro = function(timeout, name, hotkey, callback)
if type(timeout) ~= 'number' or timeout < 1 then
error("Invalid timeout for macro: " .. tostring(timeout))
end
if type(name) == 'function' then
callback = name
name = ""
hotkey = ""
elseif type(hotkey) == 'function' then
callback = hotkey
hotkey = ""
elseif type(callback) ~= 'function' then
error("Invalid callback for macro: " .. tostring(callback))
end
if type(name) ~= 'string' or type(hotkey) ~= 'string' then
error("Invalid name or hotkey for macro")
end
if hotkey:len() > 0 then
hotkey = retranslateKeyComboDesc(hotkey)
end
local switch = nil
if name:len() > 0 then
if context.storage.macros[name] == nil then
context.storage.macros[name] = true
end
switch = context.addMacroSwitch(name, hotkey)
end
table.insert(context.macros, {
timeout = timeout,
name = name,
callback = callback,
lastExecution = context.now,
hotkey = hotkey,
switch = switch
})
end
-- hotkey(keys, callback)
-- hotkey(keys, name, callback)
context.hotkey = function(keys, name, callback)
if type(name) == 'function' then
callback = name
name = ""
end
keys = retranslateKeyComboDesc(keys)
local switch = nil
if name:len() > 0 then
switch = context.addHotkeySwitch(name, keys)
end
context.hotkeys[keys] = {
name = name,
callback = callback,
lastExecution = context.now,
switch = switch
}
end
-- schedule(timeout, callback)
context.schedule = function(timeout, callback)
local extecute_time = g_clock.millis() + timeout
table.insert(context.scheduler, {
execution = extecute_time,
callback = callback
})
table.sort(context.scheduler, function(a, b) return a.execution < b.execution end)
end
-- init context
context.now = g_clock.millis()
context.time = g_clock.millis()
context.player = g_game.getLocalPlayer()
-- run script
assert(load(config, nil, nil, context))()
return {
script = function()
context.now = g_clock.millis()
context.time = g_clock.millis()
for i, macro in ipairs(context.macros) do
if macro.lastExecution + macro.timeout <= context.now and (macro.name == nil or macro.name:len() < 1 or context.storage.macros[macro.name]) then
macro.lastExecution = context.now
macro.callback()
end
end
while #context.scheduler > 0 and context.scheduler[1].execution <= g_clock.millis() do
context.scheduler[1].callback()
table.remove(context.scheduler, 1)
end
end,
callbacks = {
onKeyDown = function(keyCode, keyboardModifiers)
local keyDesc = determineKeyComboDesc(keyCode, keyboardModifiers)
for i, macro in ipairs(context.macros) do
if macro.switch and macro.hotkey == keyDesc then
macro.switch:onClick()
end
end
local hotkey = context.hotkeys[keyDesc]
if hotkey and hotkey.switch then
hotkey.switch:setOn(true)
end
end,
onKeyUp = function(keyCode, keyboardModifiers)
local keyDesc = determineKeyComboDesc(keyCode, keyboardModifiers)
local hotkey = context.hotkeys[keyDesc]
if hotkey and hotkey.switch then
hotkey.switch:setOn(false)
end
end,
onKeyPress = function(keyCode, keyboardModifiers, autoRepeatTicks)
local keyDesc = determineKeyComboDesc(keyCode, keyboardModifiers)
local hotkey = context.hotkeys[keyDesc]
if hotkey then
hotkey.lastExecution = context.now
hotkey.callback()
end
end,
onTalk = function(name, level, mode, text, channelId, pos)
end
}
}
end

View File

@@ -9,6 +9,9 @@ end
function updateFeatures(version)
g_game.resetFeatures()
-- you can add custom features here, list of them in modules\gamelib\const.lua
g_game.getFeature(GameBot)
if(version >= 770) then
g_game.enableFeature(GameLooktypeU16);
g_game.enableFeature(GameMessageStatements);

View File

@@ -32,5 +32,6 @@ Module
- game_unjustifiedpoints
- game_walking
- game_shop
- game_bot
@onLoad: init()
@onUnload: terminate()

View File

@@ -156,6 +156,7 @@ GameIngameStore = 73
GameIngameStoreHighlights = 74
GameIngameStoreServiceType = 75
GameAdditionalSkills = 76
GameDistanceEffectU16 = 77
GameExtendedOpcode = 80