mirror of
https://github.com/OTCv8/otclientv8.git
synced 2025-06-14 16:54:29 +02:00
Version 2.3 - cooldowns in action bar, more advanced bot, new cavebot, bug fixes
This commit is contained in:
parent
ed8162a9d5
commit
9a4ab2ae3b
@ -1,7 +1,8 @@
|
||||
<?php
|
||||
// set chmod 777 to dir with this file to create checksum files
|
||||
$data = file_get_contents("php://input");
|
||||
$data = json_decode($data);
|
||||
// set write permission or chmod 777 to dir with this file to let it create checksum files
|
||||
$data = json_decode(file_get_contents("php://input"));
|
||||
$platform = "";
|
||||
$version = 0;
|
||||
if(!empty($data)) {
|
||||
$platform = $data->platform;
|
||||
$version = $data->version; // currently not used
|
||||
@ -19,7 +20,6 @@ if($platform == "WIN32-WGL") { // opengl
|
||||
}
|
||||
|
||||
$data_dir = "/var/www/otclient/files";
|
||||
$things_dir = "/data/things"; // files from that dir won't be downloaded automaticly, you can set it to null to download everything automaticly (useful if you have only 1 version of data/sprites)
|
||||
$files_url = "http://otclient.ovh/files";
|
||||
$update_checksum_interval = 60; // caling updater 100x/s would lag disc, we need to cache it
|
||||
$main_files_and_dirs = array("data", "modules", "layouts", "init.lua"); // used to ignore other files/dirs in data_dir
|
||||
@ -48,23 +48,18 @@ function updateChecksums() {
|
||||
global $binary_path;
|
||||
global $checksums_file;
|
||||
global $data;
|
||||
global $things_dir;
|
||||
|
||||
$ret = array();
|
||||
$data_dir_realpath = realpath($data_dir);
|
||||
$files = getDirFiles($data_dir);
|
||||
foreach($files as $file) {
|
||||
$relative_path = str_replace($data_dir_realpath, "", $file);
|
||||
$ps = explode("/", $relative_path);
|
||||
$ps = explode(DIRECTORY_SEPARATOR, $relative_path);
|
||||
if($relative_path == $binary_path || (count($ps) >= 2 && in_array($ps[1], $main_files_and_dirs)))
|
||||
$ret[$relative_path] = md5_file($file);
|
||||
}
|
||||
foreach($ret as $file => $checksum) {
|
||||
if($things_dir != null && !empty($things_dir) && strpos($file, $things_dir) === 0) {
|
||||
$data["things"][$file] = $checksum;
|
||||
} else {
|
||||
$data["files"][$file] = $checksum;
|
||||
}
|
||||
$data["files"][$file] = $checksum;
|
||||
}
|
||||
$ret = json_encode($data);
|
||||
if(file_put_contents($checksums_file, $ret) === FALSE) {
|
||||
@ -84,7 +79,8 @@ if (function_exists('sem_get')) {
|
||||
|
||||
sem_acquire($semaphore);
|
||||
}
|
||||
$ft = filemtime($checksums_file);
|
||||
|
||||
$ft = file_exists($checksums_file) ? filemtime($checksums_file) : false;
|
||||
if($ft === false || $ft + $update_checksum_interval < time()) {
|
||||
echo updateChecksums();
|
||||
} else {
|
||||
|
2
data/things/.gitignore
vendored
Normal file
2
data/things/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
*
|
||||
!.gitignore
|
2
init.lua
2
init.lua
@ -23,6 +23,8 @@ Servers = {
|
||||
|
||||
USE_NEW_ENERGAME = false -- not done yet
|
||||
ALLOW_CUSTOM_SERVERS = true -- if true it shows option ANOTHER on server list
|
||||
|
||||
g_app.setName("OTCv8")
|
||||
-- CONFIG END
|
||||
|
||||
-- print first terminal message
|
||||
|
@ -54,6 +54,8 @@ end
|
||||
function init()
|
||||
connect(g_app, { onRun = startup,
|
||||
onExit = exit })
|
||||
connect(g_game, { onGameStart = onGameStart,
|
||||
onGameEnd = onGameEnd })
|
||||
|
||||
g_window.setMinimumSize({ width = 800, height = 600 })
|
||||
if g_sounds ~= nil then
|
||||
@ -102,6 +104,8 @@ end
|
||||
function terminate()
|
||||
disconnect(g_app, { onRun = startup,
|
||||
onExit = exit })
|
||||
disconnect(g_game, { onGameStart = onGameStart,
|
||||
onGameEnd = onGameEnd })
|
||||
-- save window configs
|
||||
g_settings.set('window-size', g_window.getUnmaximizedSize())
|
||||
g_settings.set('window-pos', g_window.getUnmaximizedPos())
|
||||
@ -111,3 +115,13 @@ end
|
||||
function exit()
|
||||
g_logger.info("Exiting application..")
|
||||
end
|
||||
|
||||
function onGameStart()
|
||||
local player = g_game.getLocalPlayer()
|
||||
if not player then return end
|
||||
g_window.setTitle(g_app.getName() .. " - " .. player:getName())
|
||||
end
|
||||
|
||||
function onGameEnd()
|
||||
g_window.setTitle(g_app.getName())
|
||||
end
|
||||
|
@ -112,7 +112,7 @@ EnterGameWindow
|
||||
|
||||
MenuLabel
|
||||
id: serverLabel
|
||||
!text: tr('IP:PORT or url')
|
||||
!text: tr('IP:PORT')
|
||||
anchors.left: prev.left
|
||||
anchors.top: prev.bottom
|
||||
margin-top: 8
|
||||
|
@ -154,7 +154,7 @@ function sendStats()
|
||||
g_stats.clearSlow(i - 1)
|
||||
end
|
||||
data.widgets = g_stats.getWidgetsInfo(10, false)
|
||||
data = json.encode(data)
|
||||
data = json.encode(data, 1)
|
||||
if Services.stats ~= nil and Services.stats:len() > 3 then
|
||||
g_http.post(Services.stats, data)
|
||||
end
|
||||
|
@ -337,7 +337,7 @@ function executeCommand(command)
|
||||
-- detect and convert commands with simple syntax
|
||||
local realCommand
|
||||
if string.sub(command, 1, 1) == '=' then
|
||||
realCommand = 'print(' .. string.sub(command,2) .. ')'
|
||||
realCommand = 'print(tostring(' .. string.sub(command,2) .. '))'
|
||||
else
|
||||
realCommand = command
|
||||
end
|
||||
@ -363,6 +363,8 @@ function executeCommand(command)
|
||||
addLine('ERROR: incorrect lua syntax: ' .. err:sub(5), 'red')
|
||||
return
|
||||
end
|
||||
|
||||
commandEnv['player'] = g_game.getLocalPlayer()
|
||||
|
||||
-- setup func env to commandEnv
|
||||
setfenv(func, commandEnv)
|
||||
|
@ -1,7 +1,7 @@
|
||||
--
|
||||
-- json.lua
|
||||
--
|
||||
-- Copyright (c) 2018 rxi
|
||||
-- Copyright (c) 2019 rxi
|
||||
--
|
||||
-- Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
-- this software and associated documentation files (the "Software"), to deal in
|
||||
@ -46,26 +46,40 @@ for k, v in pairs(escape_char_map) do
|
||||
end
|
||||
|
||||
|
||||
local function make_indent(state)
|
||||
return string.rep(" ", state.currentIndentLevel * state.indent)
|
||||
end
|
||||
|
||||
|
||||
local function escape_char(c)
|
||||
return escape_char_map[c] or string.format("\\u%04x", c:byte())
|
||||
end
|
||||
|
||||
|
||||
local function encode_nil(val)
|
||||
local function encode_nil()
|
||||
return "null"
|
||||
end
|
||||
|
||||
|
||||
local function encode_table(val, stack)
|
||||
local function encode_table(val, state)
|
||||
local res = {}
|
||||
stack = stack or {}
|
||||
local stack = state.stack
|
||||
local pretty = state.indent > 0
|
||||
|
||||
local close_indent = make_indent(state)
|
||||
local comma = pretty and ",\n" or ","
|
||||
local colon = pretty and ": " or ":"
|
||||
local open_brace = pretty and "{\n" or "{"
|
||||
local close_brace = pretty and ("\n" .. close_indent .. "}") or "}"
|
||||
local open_bracket = pretty and "[\n" or "["
|
||||
local close_bracket = pretty and ("\n" .. close_indent .. "]") or "]"
|
||||
|
||||
-- Circular reference?
|
||||
if stack[val] then error("circular reference") end
|
||||
|
||||
stack[val] = true
|
||||
|
||||
if val[1] ~= nil or next(val) == nil then
|
||||
if rawget(val, 1) ~= nil or next(val) == nil then
|
||||
-- Treat as array -- check keys are valid and it is not sparse
|
||||
local n = 0
|
||||
for k in pairs(val) do
|
||||
@ -78,11 +92,13 @@ local function encode_table(val, stack)
|
||||
error("invalid table: sparse array")
|
||||
end
|
||||
-- Encode
|
||||
for i, v in ipairs(val) do
|
||||
table.insert(res, encode(v, stack))
|
||||
for _, v in ipairs(val) do
|
||||
state.currentIndentLevel = state.currentIndentLevel + 1
|
||||
table.insert(res, make_indent(state) .. encode(v, state))
|
||||
state.currentIndentLevel = state.currentIndentLevel - 1
|
||||
end
|
||||
stack[val] = nil
|
||||
return "[" .. table.concat(res, ",") .. "]"
|
||||
return open_bracket .. table.concat(res, comma) .. close_bracket
|
||||
|
||||
else
|
||||
-- Treat as an object
|
||||
@ -90,10 +106,12 @@ local function encode_table(val, stack)
|
||||
if type(k) ~= "string" then
|
||||
error("invalid table: mixed or invalid key types")
|
||||
end
|
||||
table.insert(res, encode(k, stack) .. ":" .. encode(v, stack))
|
||||
state.currentIndentLevel = state.currentIndentLevel + 1
|
||||
table.insert(res, make_indent(state) .. encode(k, state) .. colon .. encode(v, state))
|
||||
state.currentIndentLevel = state.currentIndentLevel - 1
|
||||
end
|
||||
stack[val] = nil
|
||||
return "{" .. table.concat(res, ",") .. "}"
|
||||
return open_brace .. table.concat(res, comma) .. close_brace
|
||||
end
|
||||
end
|
||||
|
||||
@ -121,18 +139,22 @@ local type_func_map = {
|
||||
}
|
||||
|
||||
|
||||
encode = function(val, stack)
|
||||
encode = function(val, state)
|
||||
local t = type(val)
|
||||
local f = type_func_map[t]
|
||||
if f then
|
||||
return f(val, stack)
|
||||
return f(val, state)
|
||||
end
|
||||
error("unexpected type '" .. t .. "'")
|
||||
end
|
||||
|
||||
|
||||
function json.encode(val)
|
||||
return ( encode(val) )
|
||||
function json.encode(val, indent)
|
||||
local state = {
|
||||
indent = indent or 0,
|
||||
currentIndentLevel = 0,
|
||||
stack = {}
|
||||
}
|
||||
return encode(val, state)
|
||||
end
|
||||
|
||||
|
||||
|
@ -155,3 +155,9 @@ end
|
||||
function UITabBar:getTabsPanel()
|
||||
return table.collect(self.tabs, function(_,tab) return tab.tabPanel end)
|
||||
end
|
||||
|
||||
function UITabBar:clearTabs()
|
||||
while #self.tabs > 0 do
|
||||
self:removeTab(self.tabs[#self.tabs])
|
||||
end
|
||||
end
|
||||
|
@ -34,8 +34,10 @@ function init()
|
||||
|
||||
connect(g_game, {
|
||||
onGameStart = online,
|
||||
onGameEnd = offline
|
||||
})
|
||||
onGameEnd = offline,
|
||||
onSpellGroupCooldown = onSpellGroupCooldown,
|
||||
onSpellCooldown = onSpellCooldown
|
||||
})
|
||||
|
||||
if g_game.isOnline() then
|
||||
online()
|
||||
@ -45,14 +47,16 @@ end
|
||||
function terminate()
|
||||
disconnect(g_game, {
|
||||
onGameStart = online,
|
||||
onGameEnd = offline
|
||||
onGameEnd = offline,
|
||||
onSpellGroupCooldown = onSpellGroupCooldown,
|
||||
onSpellCooldown = onSpellCooldown
|
||||
})
|
||||
|
||||
saveConfig()
|
||||
|
||||
-- remove hotkeys
|
||||
offline()
|
||||
|
||||
-- remove hotkeys, also saves config
|
||||
if actionPanel1.tabBar:getChildCount() > 0 and actionPanel2.tabBar:getChildCount() > 0 then
|
||||
offline()
|
||||
end
|
||||
|
||||
actionPanel1:destroy()
|
||||
actionPanel2:destroy()
|
||||
end
|
||||
@ -90,16 +94,27 @@ function offline()
|
||||
hotkeyAssignWindow:destroy()
|
||||
hotkeyAssignWindow = nil
|
||||
end
|
||||
saveConfig()
|
||||
|
||||
|
||||
local gameRootPanel = modules.game_interface.getRootPanel()
|
||||
for index, panel in ipairs({actionPanel1, actionPanel2}) do
|
||||
local config = {}
|
||||
for i, child in ipairs(panel.tabBar:getChildren()) do
|
||||
local gameRootPanel = modules.game_interface.getRootPanel()
|
||||
if child.hotkey then
|
||||
g_keyboard.unbindKeyPress(child.hotkey, child.callback, gameRootPanel)
|
||||
if child.config then
|
||||
table.insert(config, child.config)
|
||||
if type(child.config.hotkey) == 'string' and child.config.hotkey:len() > 0 then
|
||||
g_keyboard.unbindKeyPress(child.config.hotkey, child.callback, gameRootPanel)
|
||||
end
|
||||
else
|
||||
table.insert(config, {})
|
||||
end
|
||||
if child.cooldownEvent then
|
||||
removeEvent(child.cooldownEvent)
|
||||
end
|
||||
end
|
||||
actionConfig:setNode('actions_' .. index, config)
|
||||
panel.tabBar:destroyChildren()
|
||||
end
|
||||
actionConfig:save()
|
||||
end
|
||||
|
||||
function setupActionPanel(index, panel)
|
||||
@ -108,10 +123,11 @@ function setupActionPanel(index, panel)
|
||||
for i, buttonConfig in pairs(rawConfig) do -- sorting, because key in rawConfig is string
|
||||
config[tonumber(i)] = buttonConfig
|
||||
end
|
||||
panel.tabBar:destroyChildren()
|
||||
|
||||
for i=1,actionButtonsInPanel do
|
||||
local action = g_ui.createWidget('ActionButton', panel.tabBar)
|
||||
setupAction(index, action, config[i])
|
||||
action.config = config[i] or {}
|
||||
setupAction(action)
|
||||
end
|
||||
|
||||
panel.nextButton.onClick = function()
|
||||
@ -122,28 +138,13 @@ function setupActionPanel(index, panel)
|
||||
end
|
||||
end
|
||||
|
||||
function saveConfig()
|
||||
for index, panel in ipairs({actionPanel1, actionPanel2}) do
|
||||
local config = {}
|
||||
for i, child in ipairs(panel.tabBar:getChildren()) do
|
||||
table.insert(config, {
|
||||
text = child.text:getText(),
|
||||
item = child.item:getItemId(),
|
||||
count = child.item:getItemCount(),
|
||||
action = child.actionType,
|
||||
hotkey = child.hotkey
|
||||
})
|
||||
end
|
||||
actionConfig:setNode('actions_' .. index, config)
|
||||
end
|
||||
actionConfig:save()
|
||||
end
|
||||
|
||||
function setupAction(index, action, config)
|
||||
function setupAction(action)
|
||||
local config = action.config
|
||||
action.item:setShowCount(false)
|
||||
action.onMouseRelease = actionOnMouseRelease
|
||||
action.callback = function(k, c, ticks) executeAction(action, ticks) end
|
||||
|
||||
action.item.onItemChange = nil -- disable callbacks for setup
|
||||
|
||||
if config then
|
||||
if type(config.text) == 'number' then
|
||||
config.text = tostring(config.text)
|
||||
@ -151,43 +152,204 @@ function setupAction(index, action, config)
|
||||
if type(config.hotkey) == 'number' then
|
||||
config.hotkey = tostring(config.hotkey)
|
||||
end
|
||||
action.hotkey = config.hotkey
|
||||
if type(action.hotkey) == 'string' and action.hotkey:len() > 0 then
|
||||
if type(config.hotkey) == 'string' and config.hotkey:len() > 0 then
|
||||
local gameRootPanel = modules.game_interface.getRootPanel()
|
||||
g_keyboard.bindKeyPress(action.hotkey, action.callback, gameRootPanel)
|
||||
g_keyboard.bindKeyPress(config.hotkey, action.callback, gameRootPanel)
|
||||
action.hotkeyLabel:setText(config.hotkey)
|
||||
else
|
||||
action.hotkeyLabel:setText("")
|
||||
end
|
||||
action.hotkeyLabel:setText(action.hotkey or "")
|
||||
action.text:setText(config.text)
|
||||
if action.text:getText():len() > 0 then
|
||||
|
||||
action.text:setImageSource("")
|
||||
action.cooldownTill = 0
|
||||
action.cooldownStart = 0
|
||||
if type(config.text) == 'string' and config.text:len() > 0 then
|
||||
action.text:setText(config.text)
|
||||
action:setBorderColor(ActionColors.text)
|
||||
action.item:setOn(true) -- removes background
|
||||
if Spells then
|
||||
local spell, profile = Spells.getSpellByWords(config.text:lower())
|
||||
action.spell = spell
|
||||
if action.spell and action.spell.icon and profile then
|
||||
action.text:setImageSource(SpelllistSettings[profile].iconFile)
|
||||
action.text:setImageClip(Spells.getImageClip(SpellIcons[action.spell.icon][1], profile))
|
||||
action.text:setText("")
|
||||
end
|
||||
end
|
||||
else
|
||||
action.text:setText("")
|
||||
action.spell = nil
|
||||
if type(config.item) == 'number' and config.item > 100 then
|
||||
action.item:setOn(true)
|
||||
action.item:setItemId(config.item)
|
||||
action.item:setItemCount(config.count or 1)
|
||||
setupActionType(action, config.actionType)
|
||||
else
|
||||
action.item:setItemId(0)
|
||||
action.item:setOn(false)
|
||||
action:setBorderColor(ActionColors.empty)
|
||||
end
|
||||
end
|
||||
if config.item > 0 then
|
||||
setupActionType(action, config.action)
|
||||
end
|
||||
action.item:setOn(config.item > 0)
|
||||
action.item:setItemId(config.item)
|
||||
action.item:setItemCount(config.count)
|
||||
end
|
||||
|
||||
action.item.onItemChange = actionOnItemChange
|
||||
end
|
||||
|
||||
function setupActionType(action, actionType)
|
||||
action.actionType = actionType
|
||||
if action.actionType == ActionTypes.USE then
|
||||
local item = action.item:getItem()
|
||||
if action.item:getItem():isMultiUse() then
|
||||
if not actionType or actionType <= ActionTypes.USE then
|
||||
actionType = ActionTypes.USE_WITH
|
||||
end
|
||||
elseif g_game.getClientVersion() >= 910 then
|
||||
if actionType ~= ActionTypes.USE and actionType ~= ActionTypes.EQUIP then
|
||||
actionType = ActionTypes.USE
|
||||
end
|
||||
else
|
||||
actionType = ActionTypes.USE
|
||||
end
|
||||
|
||||
action.config.actionType = actionType
|
||||
if action.config.actionType == ActionTypes.USE then
|
||||
action:setBorderColor(ActionColors.itemUse)
|
||||
elseif action.actionType == ActionTypes.USE_SELF then
|
||||
elseif action.config.actionType == ActionTypes.USE_SELF then
|
||||
action:setBorderColor(ActionColors.itemUseSelf)
|
||||
elseif action.actionType == ActionTypes.USE_TARGET then
|
||||
elseif action.config.actionType == ActionTypes.USE_TARGET then
|
||||
action:setBorderColor(ActionColors.itemUseTarget)
|
||||
elseif action.actionType == ActionTypes.USE_WITH then
|
||||
elseif action.config.actionType == ActionTypes.USE_WITH then
|
||||
action:setBorderColor(ActionColors.itemUseWith)
|
||||
elseif action.actionType == ActionTypes.EQUIP then
|
||||
elseif action.config.actionType == ActionTypes.EQUIP then
|
||||
action:setBorderColor(ActionColors.itemEquip)
|
||||
end
|
||||
end
|
||||
|
||||
function updateAction(action, newConfig)
|
||||
local config = action.config
|
||||
if newConfig.hotkey and type(config.hotkey) == 'string' and config.hotkey:len() > 0 then
|
||||
local gameRootPanel = modules.game_interface.getRootPanel()
|
||||
g_keyboard.unbindKeyPress(config.hotkey, action.callback, gameRootPanel)
|
||||
end
|
||||
for key, val in pairs(newConfig) do
|
||||
action.config[key] = val
|
||||
end
|
||||
setupAction(action)
|
||||
end
|
||||
|
||||
function actionOnMouseRelease(action, mousePosition, mouseButton)
|
||||
if mouseButton == MouseRightButton then
|
||||
local menu = g_ui.createWidget('PopupMenu')
|
||||
menu:setGameMenu(true)
|
||||
if action.item:getItemId() > 0 then
|
||||
if action.item:getItem():isMultiUse() then
|
||||
menu:addOption(tr('Use on yourself'), function() return setupActionType(action, ActionTypes.USE_SELF) end)
|
||||
menu:addOption(tr('Use on target'), function() return setupActionType(action, ActionTypes.USE_TARGET) end)
|
||||
menu:addOption(tr('With crosshair'), function() return setupActionType(action, ActionTypes.USE_WITH) end)
|
||||
end
|
||||
if g_game.getClientVersion() >= 910 then
|
||||
if not action.item:getItem():isMultiUse() then
|
||||
menu:addOption(tr('Use'), function() return setupActionType(action, ActionTypes.USE) end)
|
||||
end
|
||||
menu:addOption(tr('Equip'), function() return setupActionType(action, ActionTypes.EQUIP) end)
|
||||
end
|
||||
else
|
||||
menu:addOption(tr('Select item'), function() return modules.game_itemselector.show(action.item) end)
|
||||
end
|
||||
menu:addSeparator()
|
||||
menu:addOption(tr('Set text'), function()
|
||||
modules.game_textedit.singlelineEditor(action.config.text or "", function(newText)
|
||||
updateAction(action, {text=newText, item=0})
|
||||
end)
|
||||
end)
|
||||
menu:addOption(tr('Set hotkey'), function()
|
||||
if hotkeyAssignWindow then
|
||||
hotkeyAssignWindow:destroy()
|
||||
end
|
||||
local assignWindow = g_ui.createWidget('ActionAssignWindow', rootWidget)
|
||||
assignWindow:grabKeyboard()
|
||||
assignWindow.comboPreview.keyCombo = ''
|
||||
assignWindow.onKeyDown = function(assignWindow, keyCode, keyboardModifiers)
|
||||
local keyCombo = determineKeyComboDesc(keyCode, keyboardModifiers)
|
||||
assignWindow.comboPreview:setText(tr('Current action hotkey: %s', keyCombo))
|
||||
assignWindow.comboPreview.keyCombo = keyCombo
|
||||
assignWindow.comboPreview:resizeToText()
|
||||
return true
|
||||
end
|
||||
assignWindow.onDestroy = function(widget)
|
||||
if widget == hotkeyAssignWindow then
|
||||
hotkeyAssignWindow = nil
|
||||
end
|
||||
end
|
||||
assignWindow.addButton.onClick = function()
|
||||
updateAction(action, {hotkey=tostring(assignWindow.comboPreview.keyCombo)})
|
||||
assignWindow:destroy()
|
||||
end
|
||||
hotkeyAssignWindow = assignWindow
|
||||
end)
|
||||
menu:addSeparator()
|
||||
menu:addOption(tr('Clear'), function()
|
||||
updateAction(action, {hotkey="", text="", item=0, count=1})
|
||||
end)
|
||||
menu:display(mousePosition)
|
||||
return true
|
||||
elseif mouseButton == MouseLeftButton then
|
||||
action.callback()
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
function actionOnItemChange(widget)
|
||||
updateAction(widget:getParent(), {text="", item=widget:getItemId(), count=widget:getItemCount()})
|
||||
end
|
||||
|
||||
function onSpellCooldown(iconId, duration)
|
||||
for index, panel in ipairs({actionPanel1, actionPanel2}) do
|
||||
for i, child in ipairs(panel.tabBar:getChildren()) do
|
||||
if child.spell and child.spell.id == iconId then
|
||||
startCooldown(child, duration)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function onSpellGroupCooldown(groupId, duration)
|
||||
for index, panel in ipairs({actionPanel1, actionPanel2}) do
|
||||
for i, child in ipairs(panel.tabBar:getChildren()) do
|
||||
if child.spell and child.spell.group then
|
||||
for group, duration in pairs(child.spell.group) do
|
||||
if groupId == group then
|
||||
startCooldown(child, duration)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function startCooldown(action, duration)
|
||||
if type(action.cooldownTill) == 'number' and action.cooldownTill > g_clock.millis() + duration then
|
||||
return -- already has cooldown with greater duration
|
||||
end
|
||||
action.cooldownStart = g_clock.millis()
|
||||
action.cooldownTill = g_clock.millis() + duration
|
||||
updateCooldown(action)
|
||||
end
|
||||
|
||||
function updateCooldown(action)
|
||||
if not action or not action.cooldownTill then return end
|
||||
local timeleft = action.cooldownTill - g_clock.millis()
|
||||
if timeleft <= 30 then
|
||||
action.cooldown:setPercent(100)
|
||||
action.cooldownEvent = nil
|
||||
return
|
||||
end
|
||||
local duration = action.cooldownTill - action.cooldownStart
|
||||
action.cooldown:setPercent(100 - math.floor(100 * timeleft / duration))
|
||||
action.cooldownEvent = scheduleEvent(function() updateCooldown(action) end, 30)
|
||||
end
|
||||
|
||||
function executeAction(action, ticks)
|
||||
if not action.config then return end
|
||||
if type(ticks) ~= 'number' then ticks = 0 end
|
||||
|
||||
local actionDelay = 100
|
||||
@ -196,12 +358,14 @@ function executeAction(action, ticks)
|
||||
elseif action.actionDelayTo ~= nil and g_clock.millis() < action.actionDelayTo then
|
||||
return
|
||||
end
|
||||
|
||||
local actionType = action.config.actionType
|
||||
|
||||
if action.text:getText():len() > 0 then
|
||||
modules.game_console.sendMessage(action.text:getText())
|
||||
if type(action.config.text) == 'string' and action.config.text:len() > 0 then
|
||||
modules.game_console.sendMessage(action.config.text)
|
||||
action.actionDelayTo = g_clock.millis() + actionDelay
|
||||
elseif action.item:getItemId() > 0 then
|
||||
if action.actionType == ActionTypes.USE then
|
||||
if actionType == ActionTypes.USE then
|
||||
if g_game.getClientVersion() < 740 then
|
||||
local item = g_game.findPlayerItem(action.item:getItemId(), hotKey.subType or -1)
|
||||
if item then
|
||||
@ -211,7 +375,7 @@ function executeAction(action, ticks)
|
||||
g_game.useInventoryItem(action.item:getItemId())
|
||||
end
|
||||
action.actionDelayTo = g_clock.millis() + actionDelay
|
||||
elseif action.actionType == ActionTypes.USE_SELF then
|
||||
elseif actionType == ActionTypes.USE_SELF then
|
||||
if g_game.getClientVersion() < 740 then
|
||||
local item = g_game.findPlayerItem(action.item:getItemId(), hotKey.subType or -1)
|
||||
if item then
|
||||
@ -221,7 +385,7 @@ function executeAction(action, ticks)
|
||||
g_game.useInventoryItemWith(action.item:getItemId(), g_game.getLocalPlayer(), action.item:getItemSubType() or -1)
|
||||
end
|
||||
action.actionDelayTo = g_clock.millis() + actionDelay
|
||||
elseif action.actionType == ActionTypes.USE_TARGET then
|
||||
elseif actionType == ActionTypes.USE_TARGET then
|
||||
local attackingCreature = g_game.getAttackingCreature()
|
||||
if not attackingCreature then
|
||||
local item = Item.create(action.item:getItemId())
|
||||
@ -245,7 +409,7 @@ function executeAction(action, ticks)
|
||||
g_game.useInventoryItemWith(action.item:getItemId(), attackingCreature, action.item:getItemSubType() or -1)
|
||||
end
|
||||
action.actionDelayTo = g_clock.millis() + actionDelay
|
||||
elseif action.actionType == ActionTypes.USE_WITH then
|
||||
elseif actionType == ActionTypes.USE_WITH then
|
||||
local item = Item.create(action.item:getItemId())
|
||||
if g_game.getClientVersion() < 740 then
|
||||
local tmpItem = g_game.findPlayerItem(action.item:getItemId(), action.item:getItemSubType() or -1)
|
||||
@ -253,7 +417,7 @@ function executeAction(action, ticks)
|
||||
item = tmpItem
|
||||
end
|
||||
modules.game_interface.startUseWith(item, action.item:getItemSubType() or - 1)
|
||||
elseif action.actionType == ActionTypes.EQUIP then
|
||||
elseif actionType == ActionTypes.EQUIP then
|
||||
if g_game.getClientVersion() >= 910 then
|
||||
local item = Item.create(action.item:getItemId())
|
||||
g_game.equipItem(item)
|
||||
@ -261,106 +425,4 @@ function executeAction(action, ticks)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function actionOnMouseRelease(action, mousePosition, mouseButton)
|
||||
if mouseButton == MouseRightButton then
|
||||
local menu = g_ui.createWidget('PopupMenu')
|
||||
menu:setGameMenu(true)
|
||||
if action.item:getItemId() > 0 then
|
||||
if action.item:getItem():isMultiUse() then
|
||||
menu:addOption(tr('Use on yourself'), function() return setupActionType(action, ActionTypes.USE_SELF) end)
|
||||
menu:addOption(tr('Use on target'), function() return setupActionType(action, ActionTypes.USE_TARGET) end)
|
||||
menu:addOption(tr('With crosshair'), function() return setupActionType(action, ActionTypes.USE_WITH) end)
|
||||
end
|
||||
if g_game.getClientVersion() >= 910 then
|
||||
if not action.item:getItem():isMultiUse() then
|
||||
menu:addOption(tr('Use'), function() return setupActionType(action, ActionTypes.USE) end)
|
||||
end
|
||||
menu:addOption(tr('Equip'), function() return setupActionType(action, ActionTypes.EQUIP) end)
|
||||
end
|
||||
end
|
||||
menu:addSeparator()
|
||||
menu:addOption(tr('Set text'), function()
|
||||
modules.game_textedit.singlelineEditor(action.text:getText(), function(newText)
|
||||
action.item:setOn(false)
|
||||
action.item:setItemId(0)
|
||||
action.text:setText(newText)
|
||||
if action.text:getText():len() > 0 then
|
||||
action:setBorderColor(ActionColors.text)
|
||||
end
|
||||
end)
|
||||
end)
|
||||
menu:addOption(tr('Set hotkey'), function()
|
||||
if hotkeyAssignWindow then
|
||||
hotkeyAssignWindow:destroy()
|
||||
end
|
||||
local assignWindow = g_ui.createWidget('ActionAssignWindow', rootWidget)
|
||||
assignWindow:grabKeyboard()
|
||||
assignWindow.comboPreview.keyCombo = ''
|
||||
assignWindow.onKeyDown = function(assignWindow, keyCode, keyboardModifiers)
|
||||
local keyCombo = determineKeyComboDesc(keyCode, keyboardModifiers)
|
||||
assignWindow.comboPreview:setText(tr('Current action hotkey: %s', keyCombo))
|
||||
assignWindow.comboPreview.keyCombo = keyCombo
|
||||
assignWindow.comboPreview:resizeToText()
|
||||
return true
|
||||
end
|
||||
assignWindow.onDestroy = function()
|
||||
hotkeyAssignWindow = nil
|
||||
end
|
||||
assignWindow.addButton.onClick = function()
|
||||
local gameRootPanel = modules.game_interface.getRootPanel()
|
||||
if action.hotkey and action.hotkey:len() > 0 then
|
||||
g_keyboard.unbindKeyPress(action.hotkey, action.callback, gameRootPanel)
|
||||
end
|
||||
action.hotkey = tostring(assignWindow.comboPreview.keyCombo)
|
||||
if action.hotkey and action.hotkey:len() > 0 then
|
||||
g_keyboard.bindKeyPress(action.hotkey, action.callback, gameRootPanel)
|
||||
end
|
||||
action.hotkeyLabel:setText(action.hotkey or "")
|
||||
assignWindow:destroy()
|
||||
end
|
||||
hotkeyAssignWindow = assignWindow
|
||||
end)
|
||||
menu:addSeparator()
|
||||
menu:addOption(tr('Clear'), function()
|
||||
action.item:setItem(nil)
|
||||
action.text:setText("")
|
||||
action.hotkeyLabel:setText("")
|
||||
local gameRootPanel = modules.game_interface.getRootPanel()
|
||||
if action.hotkey and action.hotkey:len() > 0 then
|
||||
g_keyboard.unbindKeyPress(action.hotkey, action.callback, gameRootPanel)
|
||||
end
|
||||
action.hotkey = nil
|
||||
action.actionType = nil
|
||||
action:setBorderColor(ActionColors.empty)
|
||||
end)
|
||||
menu:display(mousePosition)
|
||||
return true
|
||||
elseif mouseButton == MouseLeftButton then
|
||||
action.callback()
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
function actionOnItemChange(widget)
|
||||
local action = widget:getParent()
|
||||
if action.item:getItemId() > 0 then
|
||||
action.text:setText("")
|
||||
action.item:setOn(true)
|
||||
if action.item:getItem():isMultiUse() then
|
||||
if not action.actionType or action.actionType <= 1 then
|
||||
setupActionType(action, ActionTypes.USE_WITH)
|
||||
end
|
||||
else
|
||||
if g_game.getClientVersion() >= 910 then
|
||||
if not action.actionType or action.actionType <= ActionTypes.EQUIP then
|
||||
setupActionType(action, ActionTypes.USE)
|
||||
end
|
||||
else
|
||||
setupActionType(action, ActionTypes.USE)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
@ -26,9 +26,10 @@ ActionButton < Panel
|
||||
Label
|
||||
id: text
|
||||
anchors.fill: parent
|
||||
margin: 3 3 3 3
|
||||
margin: 1 1 1 1
|
||||
text-auto-resize: true
|
||||
text-wrap: true
|
||||
phantom: true
|
||||
text-align: center
|
||||
font: verdana-9px
|
||||
|
||||
@ -39,8 +40,18 @@ ActionButton < Panel
|
||||
margin: 2 3 3 3
|
||||
text-auto-resize: true
|
||||
text-wrap: false
|
||||
phantom: true
|
||||
font: small-9px
|
||||
color: #D3D3D3
|
||||
color: yellow
|
||||
|
||||
UIProgressRect
|
||||
id: cooldown
|
||||
background: #585858AA
|
||||
percent: 100
|
||||
focusable: false
|
||||
phantom: true
|
||||
anchors.fill: parent
|
||||
margin: 1 1 1 1
|
||||
|
||||
Panel
|
||||
id: actionBar
|
||||
|
@ -67,9 +67,6 @@ function terminate()
|
||||
})
|
||||
|
||||
terminateCallbacks()
|
||||
|
||||
removeEvent(checkEvent)
|
||||
|
||||
editWindow:destroy()
|
||||
|
||||
botWindow:destroy()
|
||||
@ -117,6 +114,7 @@ end
|
||||
|
||||
|
||||
function refresh()
|
||||
if not g_game.isOnline() then return end
|
||||
save()
|
||||
clear()
|
||||
|
||||
@ -219,7 +217,7 @@ function save()
|
||||
end
|
||||
|
||||
local status, result = pcall(function()
|
||||
return json.encode(botStorage)
|
||||
return json.encode(botStorage, 2)
|
||||
end)
|
||||
if not status then
|
||||
return onError("Error while saving bot storage. Storage won't be saved. Details: " .. result)
|
||||
@ -284,11 +282,27 @@ function createDefaultConfigs()
|
||||
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/" .. config_name .. "/" .. baseName, contents)
|
||||
if g_resources.directoryExists(file) then
|
||||
g_resources.makeDir("/bot/" .. config_name .. "/" .. baseName)
|
||||
if not g_resources.directoryExists("/bot/" .. config_name .. "/" .. baseName) then
|
||||
return onError("Can't create /bot/" .. config_name .. "/" .. baseName .. " directory in " .. g_resources.getWriteDir())
|
||||
end
|
||||
local defaultConfigFiles2 = g_resources.listDirectoryFiles("default_configs/" .. config_name .. "/" .. baseName, true, false)
|
||||
for i, file in ipairs(defaultConfigFiles2) do
|
||||
local baseName2 = file:split("/")
|
||||
baseName2 = baseName2[#baseName2]
|
||||
local contents = g_resources.fileExists(file) and g_resources.readFileContents(file) or ""
|
||||
if contents:len() > 0 then
|
||||
g_resources.writeFileContents("/bot/" .. config_name .. "/" .. baseName .. "/" .. baseName2, contents)
|
||||
end
|
||||
end
|
||||
else
|
||||
local contents = g_resources.fileExists(file) and g_resources.readFileContents(file) or ""
|
||||
if contents:len() > 0 then
|
||||
g_resources.writeFileContents("/bot/" .. config_name .. "/" .. baseName, contents)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -1,8 +1,7 @@
|
||||
BotTabBar < MoveableTabBar
|
||||
BotTabBar < TabBar
|
||||
tab-spacing: 1
|
||||
margin-left: 1
|
||||
margin-right: 1
|
||||
movable: false
|
||||
height: 20
|
||||
|
||||
$on:
|
||||
@ -13,10 +12,13 @@ BotTabBar < MoveableTabBar
|
||||
visible: false
|
||||
margin-top: -20
|
||||
|
||||
BotTabBarButton < MoveableTabBarButton
|
||||
padding: 3
|
||||
BotTabBarPanel < TabBarPanel
|
||||
BotTabBarButton < TabBarButton
|
||||
padding: 4
|
||||
padding-right: 5
|
||||
text-horizontal-auto-resize: true
|
||||
|
||||
$!first:
|
||||
margin-left: 0
|
||||
|
||||
MiniWindow
|
||||
id: botWindow
|
||||
|
@ -1,4 +0,0 @@
|
||||
-- to keep it up to date, config is loaded from remote server
|
||||
loadScript("http://otclient.ovh/bot/default.php?version=" .. getVersion())
|
||||
|
||||
-- if you want add custom scripts, just add them bellow or create new lua file
|
@ -1,8 +0,0 @@
|
||||
local batTab = addTab("Batt")
|
||||
|
||||
Panels.AttackSpell(batTab)
|
||||
Panels.AttackItem(batTab)
|
||||
|
||||
Panels.AttackLeaderTarget(batTab)
|
||||
Panels.LimitFloor(batTab)
|
||||
Panels.AntiPush(batTab)
|
@ -1,8 +0,0 @@
|
||||
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)
|
@ -1,5 +0,0 @@
|
||||
ExampleLabel2 < Label
|
||||
text: LOL
|
||||
height: 200
|
||||
width: 50
|
||||
|
@ -1,16 +0,0 @@
|
||||
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)
|
||||
|
@ -1,39 +0,0 @@
|
||||
|
||||
|
||||
addIcon("dancing", {outfit={mount=0,feet=0,legs=0,body=176,type=128,auxType=0,addons=3,head=48}, hotkey="F5", text="dance"}, macro(100, "dance", function()
|
||||
turn(math.random(0,3))
|
||||
end))
|
||||
--[[
|
||||
addIcon("ctrl", {outfit={mount=0,feet=10,legs=10,body=176,type=129,auxType=0,addons=3,head=48}, text="press ctrl to move icon"}, function(widget, enabled)
|
||||
if enabled then
|
||||
info("icon on")
|
||||
else
|
||||
info("icon off")
|
||||
end
|
||||
end)
|
||||
|
||||
addIcon("mount", {outfit={mount=848,feet=10,legs=10,body=176,type=129,auxType=0,addons=3,head=48}}, function(widget, enabled)
|
||||
if enabled then
|
||||
info("icon mount on")
|
||||
else
|
||||
info("icon mount off")
|
||||
end
|
||||
end)
|
||||
|
||||
addIcon("mount 2", {outfit={mount=849,feet=10,legs=10,body=176,type=140,auxType=0,addons=3,head=48}, switchable=false}, function(widget, enabled)
|
||||
info("icon mount 2 pressed")
|
||||
end)
|
||||
|
||||
addIcon("item", {item=3380, hotkey="F6", switchable=false}, function(widget, enabled)
|
||||
info("icon clicked")
|
||||
end)
|
||||
|
||||
addIcon("item2", {item={id=3043, count=100}, switchable=true}, function(widget, enabled)
|
||||
info("icon 2 clicked")
|
||||
end)
|
||||
|
||||
addIcon("text", {text="text\nonly\nicon", switchable=true}, function(widget, enabled)
|
||||
info("icon with text clicked")
|
||||
end)
|
||||
|
||||
]]--
|
@ -1,16 +0,0 @@
|
||||
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")
|
@ -1,7 +0,0 @@
|
||||
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)
|
@ -1,84 +0,0 @@
|
||||
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")
|
5
modules/game_bot/default_configs/new_cavebot/README.md
Normal file
5
modules/game_bot/default_configs/new_cavebot/README.md
Normal file
@ -0,0 +1,5 @@
|
||||
# Default config for OTClientV8 bot (min. otcv8 version 2.2.3)
|
||||
|
||||
### Website: http://bot.otclient.ovh/
|
||||
### Forum: https://otland.net/forums/otclient.494/
|
||||
### Discord: https://discord.gg/t4ntS5p
|
36
modules/game_bot/default_configs/new_cavebot/cavebot.lua
Normal file
36
modules/game_bot/default_configs/new_cavebot/cavebot.lua
Normal file
@ -0,0 +1,36 @@
|
||||
-- Cavebot by otclient@otclient.ovh
|
||||
-- visit http://bot.otclient.ovh/
|
||||
|
||||
local cavebotTab = "Cave"
|
||||
local targetingTab = "Target"
|
||||
|
||||
setDefaultTab(cavebotTab)
|
||||
CaveBot = {} -- global namespace
|
||||
CaveBot.Extensions = {}
|
||||
importStyle("/cavebot/cavebot.otui")
|
||||
importStyle("/cavebot/editor.otui")
|
||||
importStyle("/cavebot/supply.otui")
|
||||
dofile("/cavebot/actions.lua")
|
||||
dofile("/cavebot/editor.lua")
|
||||
dofile("/cavebot/example_functions.lua")
|
||||
dofile("/cavebot/recorder.lua")
|
||||
-- in this section you can add extensions, check extension_template.lua
|
||||
--dofile("/cavebot/extension_template.lua")
|
||||
dofile("/cavebot/depositer.lua")
|
||||
dofile("/cavebot/supply.lua")
|
||||
-- main cavebot file, must be last
|
||||
dofile("/cavebot/cavebot.lua")
|
||||
|
||||
setDefaultTab(targetingTab)
|
||||
TargetBot = {} -- global namespace
|
||||
importStyle("/targetbot/looting.otui")
|
||||
importStyle("/targetbot/target.otui")
|
||||
importStyle("/targetbot/creature_editor.otui")
|
||||
dofile("/targetbot/creature.lua")
|
||||
dofile("/targetbot/creature_attack.lua")
|
||||
dofile("/targetbot/creature_editor.lua")
|
||||
dofile("/targetbot/creature_priority.lua")
|
||||
dofile("/targetbot/looting.lua")
|
||||
dofile("/targetbot/walking.lua")
|
||||
-- main targetbot file, must be last
|
||||
dofile("/targetbot/target.lua")
|
248
modules/game_bot/default_configs/new_cavebot/cavebot/actions.lua
Normal file
248
modules/game_bot/default_configs/new_cavebot/cavebot/actions.lua
Normal file
@ -0,0 +1,248 @@
|
||||
CaveBot.Actions = {}
|
||||
|
||||
-- it adds an action widget to list
|
||||
CaveBot.addAction = function(action, value, focus)
|
||||
action = action:lower()
|
||||
local raction = CaveBot.Actions[action]
|
||||
if not raction then
|
||||
return error("Invalid cavebot action: " .. action)
|
||||
end
|
||||
local widget = UI.createWidget("CaveBotAction", CaveBot.actionList)
|
||||
widget:setText(action .. ":" .. value:split("\n")[1])
|
||||
widget.action = action
|
||||
widget.value = value
|
||||
if raction.color then
|
||||
widget:setColor(raction.color)
|
||||
end
|
||||
widget.onDoubleClick = function(cwidget) -- edit on double click
|
||||
if CaveBot.Editor then
|
||||
schedule(20, function() -- schedule to have correct focus
|
||||
CaveBot.Editor.edit(cwidget.action, cwidget.value, function(action, value)
|
||||
CaveBot.editAction(cwidget, action, value)
|
||||
CaveBot.save()
|
||||
end)
|
||||
end)
|
||||
end
|
||||
end
|
||||
if focus then
|
||||
widget:focus()
|
||||
CaveBot.actionList:ensureChildVisible(widget)
|
||||
end
|
||||
return widget
|
||||
end
|
||||
|
||||
-- it updates existing widget, you should call CaveBot.save() later
|
||||
CaveBot.editAction = function(widget, action, value)
|
||||
action = action:lower()
|
||||
local raction = CaveBot.Actions[action]
|
||||
if not raction then
|
||||
return error("Invalid cavebot action: " .. action)
|
||||
end
|
||||
|
||||
if not widget.action or not widget.value then
|
||||
return error("Invalid cavebot action widget, has missing action or value")
|
||||
end
|
||||
|
||||
widget:setText(action .. ":" .. value:split("\n")[1])
|
||||
widget.action = action
|
||||
widget.value = value
|
||||
if raction.color then
|
||||
widget:setColor(raction.color)
|
||||
end
|
||||
return widget
|
||||
end
|
||||
|
||||
--[[
|
||||
registerAction:
|
||||
action - string, color - string, callback = function(value, retries, prev)
|
||||
value is a string value of action, retries is number which will grow by 1 if return is "retry"
|
||||
prev is a true when previuos action was executed succesfully, false otherwise
|
||||
it must return true if executed correctly, false otherwise
|
||||
it can also return string "retry", then the function will be called again in 20 ms
|
||||
]]--
|
||||
CaveBot.registerAction = function(action, color, callback)
|
||||
action = action:lower()
|
||||
if CaveBot.Actions[action] then
|
||||
return error("Duplicated acction: " .. action)
|
||||
end
|
||||
CaveBot.Actions[action] = {
|
||||
color=color,
|
||||
callback=callback
|
||||
}
|
||||
end
|
||||
|
||||
CaveBot.registerAction("label", "yellow", function(value, retries, prev)
|
||||
return true
|
||||
end)
|
||||
|
||||
CaveBot.registerAction("gotolabel", "#FFFF55", function(value, retries, prev)
|
||||
return CaveBot.gotoLabel(value)
|
||||
end)
|
||||
|
||||
CaveBot.registerAction("delay", "#AAAAAA", function(value, retries, prev)
|
||||
if retries == 0 then
|
||||
CaveBot.delay(tonumber(value))
|
||||
return "retry"
|
||||
end
|
||||
return true
|
||||
end)
|
||||
|
||||
CaveBot.registerAction("function", "red", function(value, retries, prev)
|
||||
local prefix = "local retries = " .. retries .. "\nlocal prev = " .. tostring(prev) .. "\nlocal delay = CaveBot.delay\nlocal gotoLabel = CaveBot.gotoLabel\n"
|
||||
prefix = prefix .. "local macro = function() error('Macros inside cavebot functions are not allowed') end\n"
|
||||
for extension, callbacks in pairs(CaveBot.Extensions) do
|
||||
prefix = prefix .. "local " .. extension .. " = CaveBot.Extensions." .. extension .. "\n"
|
||||
end
|
||||
local status, result = pcall(function()
|
||||
return assert(load(prefix .. value, "cavebot_function"))()
|
||||
end)
|
||||
if not status then
|
||||
error("Error in cavebot function:\n" .. result)
|
||||
return false
|
||||
end
|
||||
return result
|
||||
end)
|
||||
|
||||
CaveBot.registerAction("goto", "green", function(value, retries, prev)
|
||||
local pos = regexMatch(value, "\\s*([0-9]+)\\s*,\\s*([0-9]+)\\s*,\\s*([0-9]+)")
|
||||
if not pos[1] then
|
||||
error("Invalid cavebot goto action value. It should be position (x,y,z), is: " .. value)
|
||||
return false
|
||||
end
|
||||
|
||||
if retries >= 5 then
|
||||
return false -- tried 5 times, can't get there
|
||||
end
|
||||
|
||||
pos = {x=tonumber(pos[1][2]), y=tonumber(pos[1][3]), z=tonumber(pos[1][4])}
|
||||
local playerPos = player:getPosition()
|
||||
if pos.z ~= playerPos.z then
|
||||
return false -- different floor
|
||||
end
|
||||
|
||||
if math.abs(pos.x-playerPos.x) + math.abs(pos.y-playerPos.y) > 40 then
|
||||
return false -- too far way
|
||||
end
|
||||
|
||||
local minimapColor = g_map.getMinimapColor(pos)
|
||||
local stairs = (minimapColor >= 210 and minimapColor <= 213)
|
||||
|
||||
if stairs then
|
||||
if math.abs(pos.x-playerPos.x) == 0 and math.abs(pos.y-playerPos.y) <= 0 then
|
||||
return true -- already at position
|
||||
end
|
||||
elseif math.abs(pos.x-playerPos.x) == 0 and math.abs(pos.y-playerPos.y) <= 1 then
|
||||
return true -- already at position
|
||||
end
|
||||
-- check if there's a path to that place, ignore creatures and fields
|
||||
local path = findPath(playerPos, pos, 40, { ignoreNonPathable = true, precision = 1, ignoreCreatures = true })
|
||||
if not path then
|
||||
return false -- there's no way
|
||||
end
|
||||
|
||||
-- walk will be executed, but it will take some time to get response from server, wait 300ms after autowalk
|
||||
CaveBot.delay(300)
|
||||
|
||||
-- try to find path, don't ignore creatures, don't ignore fields
|
||||
if autoWalk(pos, 40) then
|
||||
return "retry"
|
||||
end
|
||||
|
||||
-- try to find path, don't ignore creatures, ignore fields
|
||||
if autoWalk(pos, 40, { ignoreNonPathable = true }) then
|
||||
return "retry"
|
||||
end
|
||||
|
||||
if retries >= 3 then
|
||||
-- try to lower precision, find something close to final position
|
||||
local precison = retries - 1
|
||||
if stairs then
|
||||
precison = 0
|
||||
end
|
||||
if autoWalk(pos, 50, { ignoreNonPathable = true, precision = precison }) then
|
||||
return "retry"
|
||||
end
|
||||
end
|
||||
|
||||
autoWalk(path) -- everything else failed, try to walk ignoring creatures, maybe will work
|
||||
return "retry"
|
||||
end)
|
||||
|
||||
CaveBot.registerAction("use", "#FFB272", function(value, retries, prev)
|
||||
local pos = regexMatch(value, "\\s*([0-9]+)\\s*,\\s*([0-9]+)\\s*,\\s*([0-9]+)")
|
||||
if not pos[1] then
|
||||
local itemid = tonumber(value)
|
||||
if not itemid then
|
||||
error("Invalid cavebot use action value. It should be (x,y,z) or item id, is: " .. value)
|
||||
return false
|
||||
end
|
||||
use(itemid)
|
||||
return true
|
||||
end
|
||||
|
||||
pos = {x=tonumber(pos[1][2]), y=tonumber(pos[1][3]), z=tonumber(pos[1][4])}
|
||||
local playerPos = player:getPosition()
|
||||
if pos.z ~= playerPos.z then
|
||||
return false -- different floor
|
||||
end
|
||||
|
||||
if math.max(math.abs(pos.x-playerPos.x), math.abs(pos.y-playerPos.y)) > 7 then
|
||||
return false -- too far way
|
||||
end
|
||||
|
||||
local tile = g_map.getTile(pos)
|
||||
if not tile then
|
||||
return false
|
||||
end
|
||||
|
||||
local topThing = tile:getTopUseThing()
|
||||
if not topThing then
|
||||
return false
|
||||
end
|
||||
|
||||
use(topThing)
|
||||
CaveBot.delay(400)
|
||||
return true
|
||||
end)
|
||||
|
||||
CaveBot.registerAction("usewith", "#EEB292", function(value, retries, prev)
|
||||
local pos = regexMatch(value, "\\s*([0-9]+)\\s*,\\s*([0-9]+)\\s*,\\s*([0-9]+)\\s*,\\s*([0-9]+)")
|
||||
if not pos[1] then
|
||||
if not itemid then
|
||||
error("Invalid cavebot usewith action value. It should be (itemid,x,y,z) or item id, is: " .. value)
|
||||
return false
|
||||
end
|
||||
use(itemid)
|
||||
return true
|
||||
end
|
||||
|
||||
local itemid = tonumber(pos[1][2])
|
||||
pos = {x=tonumber(pos[1][3]), y=tonumber(pos[1][4]), z=tonumber(pos[1][5])}
|
||||
local playerPos = player:getPosition()
|
||||
if pos.z ~= playerPos.z then
|
||||
return false -- different floor
|
||||
end
|
||||
|
||||
if math.max(math.abs(pos.x-playerPos.x), math.abs(pos.y-playerPos.y)) > 7 then
|
||||
return false -- too far way
|
||||
end
|
||||
|
||||
local tile = g_map.getTile(pos)
|
||||
if not tile then
|
||||
return false
|
||||
end
|
||||
|
||||
local topThing = tile:getTopUseThing()
|
||||
if not topThing then
|
||||
return false
|
||||
end
|
||||
|
||||
usewith(itemid, topThing)
|
||||
CaveBot.delay(400)
|
||||
return true
|
||||
end)
|
||||
|
||||
CaveBot.registerAction("say", "#FF55FF", function(value, retries, prev)
|
||||
say(value)
|
||||
return true
|
||||
end)
|
192
modules/game_bot/default_configs/new_cavebot/cavebot/cavebot.lua
Normal file
192
modules/game_bot/default_configs/new_cavebot/cavebot/cavebot.lua
Normal file
@ -0,0 +1,192 @@
|
||||
local cavebotMacro = nil
|
||||
local config = nil
|
||||
|
||||
-- ui
|
||||
local configWidget = UI.Config()
|
||||
local ui = UI.createWidget("CaveBotPanel")
|
||||
|
||||
ui.list = ui.listPanel.list -- shortcut
|
||||
CaveBot.actionList = ui.list
|
||||
|
||||
if CaveBot.Editor then
|
||||
CaveBot.Editor.setup()
|
||||
end
|
||||
for extension, callbacks in pairs(CaveBot.Extensions) do
|
||||
if callbacks.setup then
|
||||
callbacks.setup()
|
||||
end
|
||||
end
|
||||
|
||||
-- main loop, controlled by config
|
||||
local actionRetries = 0
|
||||
local prevActionResult = true
|
||||
cavebotMacro = macro(20, function()
|
||||
if TargetBot and TargetBot.isActive() then
|
||||
return -- target bot or looting is working, wait
|
||||
end
|
||||
|
||||
local actions = ui.list:getChildCount()
|
||||
if actions == 0 then return end
|
||||
local currentAction = ui.list:getFocusedChild()
|
||||
if not currentAction then
|
||||
currentAction = ui.list:getFirstChild()
|
||||
end
|
||||
local action = CaveBot.Actions[currentAction.action]
|
||||
local value = currentAction.value
|
||||
local retry = false
|
||||
if action then
|
||||
local status, result = pcall(function()
|
||||
return action.callback(value, actionRetries, prevActionResult)
|
||||
end)
|
||||
if status then
|
||||
if result == "retry" then
|
||||
actionRetries = actionRetries + 1
|
||||
retry = true
|
||||
elseif type(result) == 'boolean' then
|
||||
actionRetries = 0
|
||||
prevActionResult = result
|
||||
else
|
||||
error("Invalid return from cavebot action (" .. currentAction.action .. "), should be \"retry\", false or true, is: " .. tostring(result))
|
||||
end
|
||||
else
|
||||
error("Error while executing cavebot action (" .. currentAction.action .. "):\n" .. result)
|
||||
end
|
||||
else
|
||||
error("Invalid cavebot action: " .. currentAction.action)
|
||||
end
|
||||
|
||||
if retry then
|
||||
return
|
||||
end
|
||||
|
||||
if currentAction ~= ui.list:getFocusedChild() then
|
||||
-- focused child can change durring action, get it again and reset state
|
||||
currentAction = ui.list:getFocusedChild() or ui.list:getFirstChild()
|
||||
actionRetries = 0
|
||||
prevActionResult = true
|
||||
end
|
||||
local nextAction = ui.list:getChildIndex(currentAction) + 1
|
||||
if nextAction > actions then
|
||||
nextAction = 1
|
||||
end
|
||||
ui.list:focusChild(ui.list:getChildByIndex(nextAction))
|
||||
end)
|
||||
|
||||
onPlayerPositionChange(function()
|
||||
local delayAfterPositionChange = math.max(player:getStepDuration() + 100, 200)
|
||||
cavebotMacro.delay = math.max(cavebotMacro.delay or 0, now + delayAfterPositionChange)
|
||||
end)
|
||||
|
||||
-- config, its callback is called immediately, data can be nil
|
||||
local lastConfig = ""
|
||||
config = Config.setup("cavebot_configs", configWidget, "cfg", function(name, enabled, data)
|
||||
if enabled and CaveBot.Recorder.isOn() then
|
||||
CaveBot.Recorder.disable()
|
||||
CaveBot.setOff()
|
||||
return
|
||||
end
|
||||
|
||||
local currentActionIndex = ui.list:getChildIndex(ui.list:getFocusedChild())
|
||||
ui.list:destroyChildren()
|
||||
if not data then return cavebotMacro.setOff() end
|
||||
|
||||
for k,v in ipairs(data) do
|
||||
if type(v) == "table" and #v == 2 then
|
||||
if v[1] == "extensions" then
|
||||
local status, result = pcall(function()
|
||||
return json.decode(v[2])
|
||||
end)
|
||||
if not status then
|
||||
error("Error while parsing CaveBot extensions from config:\n" .. result)
|
||||
else
|
||||
for extension, callbacks in pairs(CaveBot.Extensions) do
|
||||
if callbacks.onConfigChange then
|
||||
callbacks.onConfigChange(name, enabled, result[extension])
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
CaveBot.addAction(v[1], v[2])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
actionRetries = 0
|
||||
prevActionResult = true
|
||||
cavebotMacro.setOn(enabled)
|
||||
cavebotMacro.delay = nil
|
||||
if lastConfig == name then
|
||||
-- restore focused child on the action list
|
||||
ui.list:focusChild(ui.list:getChildByIndex(currentActionIndex))
|
||||
end
|
||||
lastConfig = name
|
||||
end)
|
||||
|
||||
-- ui callbacks
|
||||
ui.showEditor.onClick = function()
|
||||
if not CaveBot.Editor then return end
|
||||
if ui.showEditor:isOn() then
|
||||
CaveBot.Editor.hide()
|
||||
ui.showEditor:setOn(false)
|
||||
else
|
||||
CaveBot.Editor.show()
|
||||
ui.showEditor:setOn(true)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- public function, you can use them in your scripts
|
||||
CaveBot.isOn = function()
|
||||
return config.isOn()
|
||||
end
|
||||
|
||||
CaveBot.isOff = function()
|
||||
return config.isOff()
|
||||
end
|
||||
|
||||
CaveBot.setOn = function(val)
|
||||
if val == false then
|
||||
return CaveBot.setOff(true)
|
||||
end
|
||||
config.setOn()
|
||||
end
|
||||
|
||||
CaveBot.setOff = function(val)
|
||||
if val == false then
|
||||
return CaveBot.setOn(true)
|
||||
end
|
||||
config.setOff()
|
||||
end
|
||||
|
||||
CaveBot.delay = function(value)
|
||||
cavebotMacro.delay = now + value
|
||||
end
|
||||
|
||||
CaveBot.gotoLabel = function(label)
|
||||
label = label:lower()
|
||||
for index, child in ipairs(ui.list:getChildren()) do
|
||||
if child.action == "label" and child.value:lower() == label then
|
||||
ui.list:focusChild(child)
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
CaveBot.save = function()
|
||||
local data = {}
|
||||
for index, child in ipairs(ui.list:getChildren()) do
|
||||
table.insert(data, {child.action, child.value})
|
||||
end
|
||||
local extension_data = {}
|
||||
for extension, callbacks in pairs(CaveBot.Extensions) do
|
||||
if callbacks.onSave then
|
||||
local ext_data = callbacks.onSave()
|
||||
if type(ext_data) == "table" then
|
||||
extension_data[extension] = ext_data
|
||||
end
|
||||
end
|
||||
end
|
||||
table.insert(data, {"extensions", json.encode(extension_data, 2)})
|
||||
config.save(data)
|
||||
end
|
@ -0,0 +1,48 @@
|
||||
CaveBotAction < Label
|
||||
background-color: alpha
|
||||
text-offset: 2 0
|
||||
focusable: true
|
||||
|
||||
$focus:
|
||||
background-color: #00000055
|
||||
|
||||
|
||||
CaveBotPanel < Panel
|
||||
layout:
|
||||
type: verticalBox
|
||||
fit-children: true
|
||||
|
||||
HorizontalSeparator
|
||||
margin-top: 2
|
||||
margin-bottom: 5
|
||||
|
||||
Panel
|
||||
id: listPanel
|
||||
height: 100
|
||||
margin-top: 2
|
||||
|
||||
TextList
|
||||
id: list
|
||||
anchors.fill: parent
|
||||
vertical-scrollbar: listScrollbar
|
||||
margin-right: 15
|
||||
focusable: false
|
||||
auto-focus: first
|
||||
|
||||
VerticalScrollBar
|
||||
id: listScrollbar
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.right: parent.right
|
||||
pixels-scroll: true
|
||||
step: 10
|
||||
|
||||
BotSwitch
|
||||
id: showEditor
|
||||
margin-top: 2
|
||||
|
||||
$on:
|
||||
text: Hide waypoints editor
|
||||
|
||||
$!on:
|
||||
text: Show waypoints editor
|
@ -0,0 +1,27 @@
|
||||
CaveBot.Extensions.Depositer = {}
|
||||
|
||||
local ui
|
||||
|
||||
-- first function called, here you should setup your UI
|
||||
CaveBot.Extensions.Depositer.setup = function()
|
||||
--ui = UI.createWidget('Label')
|
||||
--ui:setText("Depositer UI")
|
||||
end
|
||||
|
||||
-- called when cavebot config changes, configData is a table but it can be nil
|
||||
CaveBot.Extensions.Depositer.onConfigChange = function(configName, isEnabled, configData)
|
||||
if not configData then return end
|
||||
|
||||
end
|
||||
|
||||
-- called when cavebot is saving config, should return table or nil
|
||||
CaveBot.Extensions.Depositer.onSave = function()
|
||||
return {}
|
||||
end
|
||||
|
||||
-- bellow add you custom functions
|
||||
-- this function can be used in cavebot function waypoint as: return Depositer.run(retries, prev)
|
||||
-- there are 2 useful parameters - retries (number) and prev (true/false), check actions.lua to learn more
|
||||
CaveBot.Extensions.Depositer.run = function(retries, prev)
|
||||
return true
|
||||
end
|
173
modules/game_bot/default_configs/new_cavebot/cavebot/editor.lua
Normal file
173
modules/game_bot/default_configs/new_cavebot/cavebot/editor.lua
Normal file
@ -0,0 +1,173 @@
|
||||
CaveBot.Editor = {}
|
||||
CaveBot.Editor.Actions = {}
|
||||
|
||||
-- also works as registerAction(action, params), then text == action
|
||||
-- params are options for text editor or function to be executed when clicked
|
||||
-- you have many examples how to use it bellow
|
||||
CaveBot.Editor.registerAction = function(action, text, params)
|
||||
if type(text) ~= 'string' then
|
||||
params = text
|
||||
text = action
|
||||
end
|
||||
|
||||
local color = nil
|
||||
if type(params) ~= 'function' then
|
||||
local raction = CaveBot.Actions[action]
|
||||
if not raction then
|
||||
return error("CaveBot editor error: action " .. action .. " doesn't exist")
|
||||
end
|
||||
CaveBot.Editor.Actions[action] = params
|
||||
color = raction.color
|
||||
end
|
||||
|
||||
local button = UI.createWidget('CaveBotEditorButton', CaveBot.Editor.ui.buttons)
|
||||
button:setText(text)
|
||||
if color then
|
||||
button:setColor(color)
|
||||
end
|
||||
button.onClick = function()
|
||||
if type(params) == 'function' then
|
||||
params()
|
||||
return
|
||||
end
|
||||
CaveBot.Editor.edit(action, nil, function(action, value)
|
||||
local focusedAction = CaveBot.actionList:getFocusedChild()
|
||||
local index = CaveBot.actionList:getChildCount()
|
||||
if focusedAction then
|
||||
index = CaveBot.actionList:getChildIndex(focusedAction)
|
||||
end
|
||||
local widget = CaveBot.addAction(action, value)
|
||||
CaveBot.actionList:moveChildToIndex(widget, index + 1)
|
||||
CaveBot.save()
|
||||
end)
|
||||
end
|
||||
return button
|
||||
end
|
||||
|
||||
CaveBot.Editor.setup = function()
|
||||
CaveBot.Editor.ui = UI.createWidget("CaveBotEditorPanel")
|
||||
local ui = CaveBot.Editor.ui
|
||||
local registerAction = CaveBot.Editor.registerAction
|
||||
|
||||
registerAction("move up", function()
|
||||
local action = CaveBot.actionList:getFocusedChild()
|
||||
if not action then return end
|
||||
local index = CaveBot.actionList:getChildIndex(action)
|
||||
if index < 2 then return end
|
||||
CaveBot.actionList:moveChildToIndex(action, index - 1)
|
||||
CaveBot.actionList:ensureChildVisible(action)
|
||||
CaveBot.save()
|
||||
end)
|
||||
registerAction("move down", function()
|
||||
local action = CaveBot.actionList:getFocusedChild()
|
||||
if not action then return end
|
||||
local index = CaveBot.actionList:getChildIndex(action)
|
||||
if index >= CaveBot.actionList:getChildCount() then return end
|
||||
CaveBot.actionList:moveChildToIndex(action, index + 1)
|
||||
CaveBot.actionList:ensureChildVisible(action)
|
||||
CaveBot.save()
|
||||
end)
|
||||
registerAction("edit", function()
|
||||
local action = CaveBot.actionList:getFocusedChild()
|
||||
if not action or not action.onDoubleClick then return end
|
||||
action.onDoubleClick(action)
|
||||
end)
|
||||
registerAction("remove", function()
|
||||
local action = CaveBot.actionList:getFocusedChild()
|
||||
if not action then return end
|
||||
action:destroy()
|
||||
CaveBot.save()
|
||||
end)
|
||||
|
||||
registerAction("label", {
|
||||
value="labelName",
|
||||
title="Label",
|
||||
description="Add label",
|
||||
multiline=false
|
||||
})
|
||||
registerAction("delay", {
|
||||
value="500",
|
||||
title="Delay",
|
||||
description="Delay next action (in milliseconds)",
|
||||
multiline=false,
|
||||
validation="^\\s*[0-9]{1,10}\\s*$"
|
||||
})
|
||||
registerAction("gotolabel", "go to label", {
|
||||
value="labelName",
|
||||
title="Go to label",
|
||||
description="Go to label",
|
||||
multiline=false
|
||||
})
|
||||
registerAction("goto", "go to", {
|
||||
value=function() return posx() .. "," .. posy() .. "," .. posz() end,
|
||||
title="Go to position",
|
||||
description="Go to position (x,y,z)",
|
||||
multiline=false,
|
||||
validation="^\\s*([0-9]+)\\s*,\\s*([0-9]+)\\s*,\\s*([0-9]+)$"
|
||||
})
|
||||
registerAction("use", {
|
||||
value=function() return posx() .. "," .. posy() .. "," .. posz() end,
|
||||
title="Use",
|
||||
description="Use item from position (x,y,z) or from inventory (itemId)",
|
||||
multiline=false
|
||||
})
|
||||
registerAction("usewith", "use with", {
|
||||
value=function() return "itemId," .. posx() .. "," .. posy() .. "," .. posz() end,
|
||||
title="Use with",
|
||||
description="Use item at position (itemid,x,y,z)",
|
||||
multiline=false,
|
||||
validation="^\\s*([0-9]+)\\s*,\\s*([0-9]+)\\s*,\\s*([0-9]+)\\s*,\\s*([0-9]+)$"
|
||||
})
|
||||
registerAction("say", {
|
||||
value="text",
|
||||
title="Say",
|
||||
description="Enter text to say",
|
||||
multiline=false
|
||||
})
|
||||
registerAction("function", {
|
||||
title="Edit bot function",
|
||||
multiline=true,
|
||||
value=CaveBot.Editor.ExampleFunctions[1][2],
|
||||
examples=CaveBot.Editor.ExampleFunctions,
|
||||
width=650
|
||||
})
|
||||
|
||||
ui.autoRecording.onClick = function()
|
||||
if ui.autoRecording:isOn() then
|
||||
CaveBot.Recorder.disable()
|
||||
else
|
||||
CaveBot.Recorder.enable()
|
||||
end
|
||||
end
|
||||
|
||||
-- callbacks
|
||||
onPlayerPositionChange(function(pos)
|
||||
ui.pos:setText("Position: " .. pos.x .. ", " .. pos.y .. ", " .. pos.z)
|
||||
end)
|
||||
ui.pos:setText("Position: " .. posx() .. ", " .. posy() .. ", " .. posz())
|
||||
end
|
||||
|
||||
CaveBot.Editor.show = function()
|
||||
CaveBot.Editor.ui:show()
|
||||
end
|
||||
|
||||
|
||||
CaveBot.Editor.hide = function()
|
||||
CaveBot.Editor.ui:hide()
|
||||
end
|
||||
|
||||
CaveBot.Editor.edit = function(action, value, callback) -- callback = function(action, value)
|
||||
local params = CaveBot.Editor.Actions[action]
|
||||
if not params then return end
|
||||
if not value then
|
||||
if type(params.value) == 'function' then
|
||||
value = params.value()
|
||||
elseif type(params.value) == 'string' then
|
||||
value = params.value
|
||||
end
|
||||
end
|
||||
|
||||
UI.EditorWindow(value, params, function(newText)
|
||||
callback(action, newText)
|
||||
end)
|
||||
end
|
@ -0,0 +1,44 @@
|
||||
CaveBotEditorButton < Button
|
||||
|
||||
|
||||
CaveBotEditorPanel < Panel
|
||||
id: cavebotEditor
|
||||
visible: false
|
||||
layout:
|
||||
type: verticalBox
|
||||
fit-children: true
|
||||
|
||||
Label
|
||||
id: pos
|
||||
text-align: center
|
||||
text: -
|
||||
|
||||
Panel
|
||||
id: buttons
|
||||
margin-top: 2
|
||||
layout:
|
||||
type: grid
|
||||
cell-size: 86 20
|
||||
cell-spacing: 1
|
||||
flow: true
|
||||
fit-children: true
|
||||
|
||||
Label
|
||||
text: Double click on action from action list to edit it
|
||||
text-align: center
|
||||
text-auto-resize: true
|
||||
text-wrap: true
|
||||
margin-top: 3
|
||||
margin-left: 2
|
||||
margin-right: 2
|
||||
|
||||
BotSwitch
|
||||
id: autoRecording
|
||||
text: Auto Recording
|
||||
margin-top: 3
|
||||
|
||||
BotButton
|
||||
margin-top: 3
|
||||
margin-bottom: 3
|
||||
text: Documentation
|
||||
@onClick: g_platform.openUrl("http://bot.otclient.ovh/")
|
@ -0,0 +1,65 @@
|
||||
CaveBot.Editor.ExampleFunctions = {}
|
||||
|
||||
local function addExampleFunction(title, text)
|
||||
return table.insert(CaveBot.Editor.ExampleFunctions, {title, text:trim()})
|
||||
end
|
||||
|
||||
addExampleFunction("Click to browse example functions", [[
|
||||
-- available functions/variables:
|
||||
-- prev - result of previous action (true or false)
|
||||
-- retries - number of retries of current function, goes up by one when you return "retry"
|
||||
-- delay(number) - delays bot next action, value in milliseconds
|
||||
-- gotoLabel(string) - goes to specific label, return true if label exists
|
||||
-- you can easily access bot extensions, Depositer.run() instead of CaveBot.Extensions.Depositer.run()
|
||||
-- also you can access bot global variables, like CaveBot, TargetBot
|
||||
-- use storage variable to store date between calls
|
||||
|
||||
-- function should return false, true or "retry"
|
||||
-- if "retry" is returned, function will be executed again in 20 ms (so better call delay before)
|
||||
|
||||
return true
|
||||
]])
|
||||
|
||||
addExampleFunction("buy 200 mana potion from npc Eryn", [[
|
||||
--buy 200 mana potions
|
||||
local npc = getCreatureByName("Eryn")
|
||||
if not npc then
|
||||
return false
|
||||
end
|
||||
if retries > 10 then
|
||||
return false
|
||||
end
|
||||
local pos = player:getPosition()
|
||||
local npcPos = npc:getPosition()
|
||||
if math.max(math.abs(pos.x - npcPos.x), math.abs(pos.y - npcPos.y)) > 3 then
|
||||
autoWalk(npcPos, {precision=3})
|
||||
delay(300)
|
||||
return "retry"
|
||||
end
|
||||
if not NPC.isTrading() then
|
||||
NPC.say("hi")
|
||||
NPC.say("trade")
|
||||
delay(200)
|
||||
return "retry"
|
||||
end
|
||||
NPC.buy(268, 100)
|
||||
schedule(1000, function()
|
||||
-- buy again in 1s
|
||||
NPC.buy(268, 100)
|
||||
NPC.closeTrade()
|
||||
NPC.say("bye")
|
||||
end)
|
||||
delay(1200)
|
||||
return true
|
||||
]])
|
||||
|
||||
addExampleFunction("Say hello 5 times with some delay", [[
|
||||
--say hello
|
||||
if retries > 5 then
|
||||
return true -- finish
|
||||
end
|
||||
say("hello")
|
||||
delay(100 + retries * 100)
|
||||
return "retry"
|
||||
]])
|
||||
|
@ -0,0 +1,58 @@
|
||||
-- example cavebot extension (remember to add this file to ../cavebot.lua)
|
||||
CaveBot.Extensions.Example = {}
|
||||
|
||||
local ui
|
||||
|
||||
-- setup is called automaticly when cavebot is ready
|
||||
CaveBot.Extensions.Example.setup = function()
|
||||
ui = UI.createWidget('BotTextEdit')
|
||||
ui:setText("Hello")
|
||||
ui.onTextChange = function()
|
||||
CaveBot.save() -- save new config when you change something
|
||||
end
|
||||
|
||||
-- add custom cavebot action (check out actions.lua)
|
||||
CaveBot.registerAction("sayhello", "orange", function(value, retries, prev)
|
||||
local how_many_times = tonumber(value)
|
||||
if retries >= how_many_times then
|
||||
return true
|
||||
end
|
||||
say("hello " .. (retries + 1))
|
||||
delay(250)
|
||||
return "retry"
|
||||
end)
|
||||
|
||||
-- add this custom action to editor (check out editor.lua)
|
||||
CaveBot.Editor.registerAction("sayhello", "say hello", {
|
||||
value="5",
|
||||
title="Say hello",
|
||||
description="Says hello x times",
|
||||
validation="[0-9]{1,5}" -- regex, optional
|
||||
})
|
||||
end
|
||||
|
||||
-- called when cavebot config changes, configData is a table but it can also be nil
|
||||
CaveBot.Extensions.Example.onConfigChange = function(configName, isEnabled, configData)
|
||||
if not configData then return end
|
||||
if configData["text"] then
|
||||
ui:setText(configData["text"])
|
||||
end
|
||||
end
|
||||
|
||||
-- called when cavebot is saving config (so when CaveBot.save() is called), should return table or nil
|
||||
CaveBot.Extensions.Example.onSave = function()
|
||||
return {text=ui:getText()}
|
||||
end
|
||||
|
||||
-- bellow add you custom functions to be used in cavebot function action
|
||||
-- an example: return Example.run(retries, prev)
|
||||
-- there are 2 useful parameters - retries (number) and prev (true/false), check actions.lua and example_functions.lua to learn more
|
||||
CaveBot.Extensions.Example.run = function(retries, prev)
|
||||
-- it will say text 10 times with some delay and then continue
|
||||
if retries > 10 then
|
||||
return true
|
||||
end
|
||||
say(ui:getText() .. " x" .. retries)
|
||||
delay(100 + retries * 100)
|
||||
return "retry"
|
||||
end
|
@ -0,0 +1,68 @@
|
||||
-- auto recording for cavebot
|
||||
CaveBot.Recorder = {}
|
||||
|
||||
local isEnabled = nil
|
||||
local lastPos = nil
|
||||
|
||||
local function setup()
|
||||
local function addPosition(pos)
|
||||
CaveBot.addAction("goto", pos.x .. "," .. pos.y .. "," .. pos.z, true)
|
||||
lastPos = pos
|
||||
end
|
||||
|
||||
onPlayerPositionChange(function(newPos, oldPos)
|
||||
if CaveBot.isOn() or not isEnabled then return end
|
||||
if not lastPos then
|
||||
-- first step
|
||||
addPosition(oldPos)
|
||||
elseif newPos.z ~= oldPos.z or math.abs(oldPos.x - newPos.x) > 1 or math.abs(oldPos.y - newPos.y) > 1 then
|
||||
-- stairs/teleport
|
||||
addPosition(oldPos)
|
||||
elseif math.max(math.abs(lastPos.x - newPos.x), math.abs(lastPos.y - newPos.y)) > 5 then
|
||||
-- 5 steps from last pos
|
||||
addPosition(newPos)
|
||||
end
|
||||
end)
|
||||
|
||||
onUse(function(pos, itemId, stackPos, subType)
|
||||
if CaveBot.isOn() or not isEnabled then return end
|
||||
if pos.x == 0xFFFF then
|
||||
lastPos = pos
|
||||
CaveBot.addAction("use", itemId, true)
|
||||
else
|
||||
lastPos = pos
|
||||
CaveBot.addAction("use", pos.x .. "," .. pos.y .. "," .. pos.z, true)
|
||||
end
|
||||
end)
|
||||
|
||||
onUseWith(function(pos, itemId, target, subType)
|
||||
if CaveBot.isOn() or not isEnabled then return end
|
||||
if not target:isItem() then return end
|
||||
local targetPos = target:getPosition()
|
||||
if targetPos.x == 0xFFFF then return end
|
||||
lastPos = pos
|
||||
CaveBot.addAction("usewith", itemId .. "," .. pos.x .. "," .. pos.y .. "," .. pos.z, true)
|
||||
end)
|
||||
end
|
||||
|
||||
CaveBot.Recorder.isOn = function()
|
||||
return isEnabled
|
||||
end
|
||||
|
||||
CaveBot.Recorder.enable = function()
|
||||
CaveBot.setOff()
|
||||
if isEnabled == nil then
|
||||
setup()
|
||||
end
|
||||
CaveBot.Editor.ui.autoRecording:setOn(true)
|
||||
isEnabled = true
|
||||
lastPos = nil
|
||||
end
|
||||
|
||||
CaveBot.Recorder.disable = function()
|
||||
if isEnabled == true then
|
||||
isEnabled = false
|
||||
end
|
||||
CaveBot.Editor.ui.autoRecording:setOn(false)
|
||||
CaveBot.save()
|
||||
end
|
@ -0,0 +1,30 @@
|
||||
CaveBot.Extensions.Supply = {}
|
||||
|
||||
local ui
|
||||
|
||||
-- first function called, here you should setup your UI
|
||||
CaveBot.Extensions.Supply.setup = function()
|
||||
--ui = UI.createWidget('SupplyItemList')
|
||||
--local widget = UI.createWidget('SupplyItem', ui.list)
|
||||
--widget.item.onItemChange = function(newItem)
|
||||
--widget.fields.min.onTextChange = function(newText)
|
||||
-- make it similar to UI.Container, so if there are no free slots, add another one, keep min 4 slots, check if value min/max is number after edit
|
||||
end
|
||||
|
||||
-- called when cavebot config changes, configData is a table but it can be nil
|
||||
CaveBot.Extensions.Supply.onConfigChange = function(configName, isEnabled, configData)
|
||||
if not configData then return end
|
||||
|
||||
end
|
||||
|
||||
-- called when cavebot is saving config, should return table or nil
|
||||
CaveBot.Extensions.Supply.onSave = function()
|
||||
return {}
|
||||
end
|
||||
|
||||
-- bellow add you custom functions
|
||||
-- this function can be used in cavebot function waypoint as: return Supply.run(retries, prev)
|
||||
-- there are 2 useful parameters - retries (number) and prev (true/false), check actions.lua to learn more
|
||||
CaveBot.Extensions.Supply.run = function(retries, prev)
|
||||
return true
|
||||
end
|
@ -0,0 +1,72 @@
|
||||
SupplyItem < Panel
|
||||
height: 34
|
||||
|
||||
BotItem
|
||||
id: item
|
||||
size: 32 32
|
||||
anchors.left: parent.left
|
||||
anchors.top: parent.top
|
||||
margin-top: 1
|
||||
|
||||
Panel
|
||||
id: fields
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: prev.right
|
||||
anchors.right: parent.right
|
||||
margin-left: 2
|
||||
margin-right: 2
|
||||
|
||||
Label
|
||||
id: minLabel
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.horizontalCenter
|
||||
margin-right: 2
|
||||
text-align: center
|
||||
text: "Min"
|
||||
|
||||
Label
|
||||
id: maxLabel
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.horizontalCenter
|
||||
anchors.right: parent.right
|
||||
margin-left: 2
|
||||
text-align: center
|
||||
text: "Max"
|
||||
|
||||
BotTextEdit
|
||||
id: min
|
||||
anchors.top: minLabel.bottom
|
||||
anchors.left: minLabel.left
|
||||
anchors.right: minLabel.right
|
||||
text-align: center
|
||||
text: 1
|
||||
|
||||
BotTextEdit
|
||||
id: max
|
||||
anchors.top: maxLabel.bottom
|
||||
anchors.left: maxLabel.left
|
||||
anchors.right: maxLabel.right
|
||||
text-align: center
|
||||
text: 100
|
||||
|
||||
SupplyItemList < Panel
|
||||
height: 102
|
||||
|
||||
ScrollablePanel
|
||||
id: list
|
||||
anchors.fill: parent
|
||||
vertical-scrollbar: scroll
|
||||
margin-right: 7
|
||||
layout:
|
||||
type: verticalBox
|
||||
cell-height: 34
|
||||
|
||||
BotSmallScrollBar
|
||||
id: scroll
|
||||
anchors.top: prev.top
|
||||
anchors.bottom: prev.bottom
|
||||
anchors.right: parent.right
|
||||
step: 10
|
||||
pixels-scroll: true
|
182
modules/game_bot/default_configs/new_cavebot/hp.lua
Normal file
182
modules/game_bot/default_configs/new_cavebot/hp.lua
Normal file
@ -0,0 +1,182 @@
|
||||
setDefaultTab("HP")
|
||||
|
||||
--2x healing spell
|
||||
--2x healing rune
|
||||
--utani hur
|
||||
--mana shield
|
||||
--anti paralyze
|
||||
--4x equip
|
||||
|
||||
UI.Label("Healing spells")
|
||||
|
||||
if type(storage.healing1) ~= "table" then
|
||||
storage.healing1 = {on=false, title="HP%", text="exura", min=51, max=90}
|
||||
end
|
||||
if type(storage.healing2) ~= "table" then
|
||||
storage.healing2 = {on=false, title="HP%", text="exura vita", min=0, max=50}
|
||||
end
|
||||
|
||||
-- create 2 healing widgets
|
||||
for _, healingInfo in ipairs({storage.healing1, storage.healing2}) do
|
||||
local healingmacro = macro(20, function()
|
||||
local hp = player:getHealthPercent()
|
||||
if healingInfo.max >= hp and hp >= healingInfo.min then
|
||||
if TargetBot then
|
||||
TargetBot.saySpell(healingInfo.text) -- sync spell with targetbot if available
|
||||
else
|
||||
say(healingInfo.text)
|
||||
end
|
||||
end
|
||||
end)
|
||||
healingmacro.setOn(healingInfo.on)
|
||||
|
||||
UI.DualScrollPanel(healingInfo, function(widget, newParams)
|
||||
healingInfo = newParams
|
||||
healingmacro.setOn(healingInfo.on)
|
||||
end)
|
||||
end
|
||||
|
||||
UI.Separator()
|
||||
|
||||
UI.Label("Mana & health potions/runes")
|
||||
|
||||
if type(storage.hpitem1) ~= "table" then
|
||||
storage.hpitem1 = {on=false, title="HP%", item=266, min=51, max=90}
|
||||
end
|
||||
if type(storage.hpitem2) ~= "table" then
|
||||
storage.hpitem2 = {on=false, title="HP%", item=3160, min=0, max=50}
|
||||
end
|
||||
if type(storage.manaitem1) ~= "table" then
|
||||
storage.manaitem1 = {on=false, title="MP%", item=268, min=51, max=90}
|
||||
end
|
||||
if type(storage.manaitem2) ~= "table" then
|
||||
storage.manaitem2 = {on=false, title="MP%", item=3157, min=0, max=50}
|
||||
end
|
||||
|
||||
for i, healingInfo in ipairs({storage.hpitem1, storage.hpitem2, storage.manaitem1, storage.manaitem2}) do
|
||||
local healingmacro = macro(20, function()
|
||||
local hp = i <= 2 and player:getHealthPercent() or math.min(100, math.floor(100 * (player:getMana() / player:getMaxMana())))
|
||||
if healingInfo.max >= hp and hp >= healingInfo.min then
|
||||
if TargetBot then
|
||||
TargetBot.useItem(healingInfo.item, player) -- sync spell with targetbot if available
|
||||
else
|
||||
useWith(healingInfo.item, player)
|
||||
end
|
||||
end
|
||||
end)
|
||||
healingmacro.setOn(healingInfo.on)
|
||||
|
||||
UI.DualScrollItemPanel(healingInfo, function(widget, newParams)
|
||||
healingInfo = newParams
|
||||
healingmacro.setOn(healingInfo.on)
|
||||
end)
|
||||
end
|
||||
|
||||
UI.Separator()
|
||||
|
||||
UI.Label("Mana shield spell:")
|
||||
UI.TextEdit(storage.manaShield or "utamo vita", function(widget, newText)
|
||||
storage.manaShield = newText
|
||||
end)
|
||||
|
||||
local lastManaShield = 0
|
||||
macro(20, "mana shield", function()
|
||||
if hasManaShield() or lastManaShield + 90000 > now then return end
|
||||
if TargetBot then
|
||||
TargetBot.saySpell(storage.manaShield) -- sync spell with targetbot if available
|
||||
else
|
||||
say(storage.manaShield)
|
||||
end
|
||||
end)
|
||||
|
||||
UI.Label("Haste spell:")
|
||||
UI.TextEdit(storage.hasteSpell or "utani hur", function(widget, newText)
|
||||
storage.hasteSpell = newText
|
||||
end)
|
||||
|
||||
macro(500, "haste", function()
|
||||
if hasHaste() then return end
|
||||
if TargetBot then
|
||||
TargetBot.saySpell(storage.hasteSpell) -- sync spell with targetbot if available
|
||||
else
|
||||
say(storage.hasteSpell)
|
||||
end
|
||||
end)
|
||||
|
||||
UI.Label("Anti paralyze spell:")
|
||||
UI.TextEdit(storage.antiParalyze or "utani hur", function(widget, newText)
|
||||
storage.antiParalyze = newText
|
||||
end)
|
||||
|
||||
macro(100, "anti paralyze", function()
|
||||
if not isParalyzed() then return end
|
||||
if TargetBot then
|
||||
TargetBot.saySpell(storage.antiParalyze) -- sync spell with targetbot if available
|
||||
else
|
||||
say(storage.antiParalyze)
|
||||
end
|
||||
end)
|
||||
|
||||
UI.Separator()
|
||||
|
||||
UI.Label("Eatable items:")
|
||||
if type(storage.foodItems) ~= "table" then
|
||||
storage.foodItems = {3582, 3577}
|
||||
end
|
||||
|
||||
local foodContainer = UI.Container(function(widget, items)
|
||||
storage.foodItems = items
|
||||
end, true)
|
||||
foodContainer:setHeight(35)
|
||||
foodContainer:setItems(storage.foodItems)
|
||||
|
||||
macro(10000, "eat food", function()
|
||||
if not storage.foodItems[1] then return end
|
||||
-- search for food in containers
|
||||
for _, container in pairs(g_game.getContainers()) do
|
||||
for __, item in ipairs(container:getItems()) do
|
||||
for i, foodItem in ipairs(storage.foodItems) do
|
||||
if item:getId() == foodItem.id then
|
||||
return g_game.use(item)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
-- can't find any food, try to eat random item using hotkey
|
||||
local toEat = storage.foodItems[math.random(1, #storage.foodItems)]
|
||||
if toEat then g_game.useInventoryItem(toEat.id) end
|
||||
end)
|
||||
|
||||
UI.Separator()
|
||||
UI.Label("Auto equip")
|
||||
|
||||
if type(storage.autoEquip) ~= "table" then
|
||||
storage.autoEquip = {}
|
||||
end
|
||||
for i=1,4 do -- if you want more auto equip panels you can change 4 to higher value
|
||||
if not storage.autoEquip[i] then
|
||||
storage.autoEquip[i] = {on=false, title="Auto Equip", item1=i == 1 and 3052 or 0, item2=i == 1 and 3089 or 0, slot=i == 1 and 9 or 0}
|
||||
end
|
||||
UI.TwoItemsAndSlotPanel(storage.autoEquip[i], function(widget, newParams)
|
||||
storage.autoEquip[i] = newParams
|
||||
end)
|
||||
end
|
||||
macro(250, function()
|
||||
local containers = g_game.getContainers()
|
||||
for index, autoEquip in ipairs(storage.autoEquip) do
|
||||
if autoEquip.on then
|
||||
local slotItem = getSlot(autoEquip.slot)
|
||||
if not slotItem or (slotItem:getId() ~= autoEquip.item1 and slotItem:getId() ~= autoEquip.item2) then
|
||||
for _, container in pairs(containers) do
|
||||
for __, item in ipairs(container:getItems()) do
|
||||
if item:getId() == autoEquip.item1 or item:getId() == autoEquip.item2 then
|
||||
g_game.move(item, {x=65535, y=autoEquip.slot, z=0}, item:getCount())
|
||||
delay(1000) -- don't call it to often
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
22
modules/game_bot/default_configs/new_cavebot/main.lua
Normal file
22
modules/game_bot/default_configs/new_cavebot/main.lua
Normal file
@ -0,0 +1,22 @@
|
||||
-- main tab
|
||||
VERSION = "1.0"
|
||||
|
||||
UI.Label("Config version: " .. VERSION)
|
||||
|
||||
UI.Separator()
|
||||
|
||||
|
||||
|
||||
UI.Separator()
|
||||
|
||||
UI.Button("Discord", function()
|
||||
g_platform.openUrl("https://discord.gg/yhqBE4A")
|
||||
end)
|
||||
|
||||
UI.Button("Forum", function()
|
||||
g_platform.openUrl("https://otland.net/forums/otclient.494/")
|
||||
end)
|
||||
|
||||
UI.Button("Help & Tutorials", function()
|
||||
g_platform.openUrl("http://bot.otclient.ovh/")
|
||||
end)
|
41
modules/game_bot/default_configs/new_cavebot/mwall_timer.lua
Normal file
41
modules/game_bot/default_configs/new_cavebot/mwall_timer.lua
Normal file
@ -0,0 +1,41 @@
|
||||
-- Magic wall & Wild growth timer
|
||||
|
||||
-- config
|
||||
local magicWallId = 2129
|
||||
local magicWallTime = 20000
|
||||
local wildGrowthId = 2130
|
||||
local wildGrowthTime = 45000
|
||||
|
||||
-- script
|
||||
local activeTimers = {}
|
||||
|
||||
onAddThing(function(tile, thing)
|
||||
if not thing:isItem() then
|
||||
return
|
||||
end
|
||||
local timer = 0
|
||||
if thing:getId() == magicWallId then
|
||||
timer = magicWallTime
|
||||
elseif thing:getId() == wildGrowthId then
|
||||
timer = wildGrowthTime
|
||||
else
|
||||
return
|
||||
end
|
||||
|
||||
local pos = tile:getPosition().x .. "," .. tile:getPosition().y .. "," .. tile:getPosition().z
|
||||
if not activeTimers[pos] or activeTimers[pos] < now then
|
||||
activeTimers[pos] = now + timer
|
||||
end
|
||||
tile:setTimer(activeTimers[pos] - now)
|
||||
end)
|
||||
|
||||
onRemoveThing(function(tile, thing)
|
||||
if not thing:isItem() then
|
||||
return
|
||||
end
|
||||
if (thing:getId() == magicWallId or thing:getId() == wildGrowthId) and tile:getGround() then
|
||||
local pos = tile:getPosition().x .. "," .. tile:getPosition().y .. "," .. tile:getPosition().z
|
||||
activeTimers[pos] = nil
|
||||
tile:setTimer(0)
|
||||
end
|
||||
end)
|
@ -0,0 +1,93 @@
|
||||
|
||||
TargetBot.Creature = {}
|
||||
TargetBot.Creature.configsCache = {}
|
||||
TargetBot.Creature.cached = 0
|
||||
|
||||
TargetBot.Creature.resetConfigs = function()
|
||||
TargetBot.targetList:destroyChildren()
|
||||
TargetBot.Creature.resetConfigsCache()
|
||||
end
|
||||
|
||||
TargetBot.Creature.resetConfigsCache = function()
|
||||
TargetBot.Creature.configsCache = {}
|
||||
TargetBot.Creature.cached = 0
|
||||
end
|
||||
|
||||
TargetBot.Creature.addConfig = function(config, focus)
|
||||
if type(config) ~= 'table' or type(config.name) ~= 'string' then
|
||||
return error("Invalid targetbot creature config (missing name)")
|
||||
end
|
||||
TargetBot.Creature.resetConfigsCache()
|
||||
|
||||
if not config.regex then
|
||||
config.regex = "^" .. config.name:trim():lower():gsub("%*", ".*"):gsub("%?", ".?") .. "$"
|
||||
end
|
||||
|
||||
local widget = UI.createWidget("TargetBotEntry", TargetBot.targetList)
|
||||
widget:setText(config.name)
|
||||
widget.value = config
|
||||
|
||||
widget.onDoubleClick = function(entry) -- edit on double click
|
||||
schedule(20, function() -- schedule to have correct focus
|
||||
TargetBot.Creature.edit(entry.value, function(newConfig)
|
||||
entry:setText(newConfig.name)
|
||||
entry.value = newConfig
|
||||
TargetBot.Creature.resetConfigsCache()
|
||||
TargetBot.save()
|
||||
end)
|
||||
end)
|
||||
end
|
||||
|
||||
if focus then
|
||||
widget:focus()
|
||||
TargetBot.targetList:ensureChildVisible(widget)
|
||||
end
|
||||
return widget
|
||||
end
|
||||
|
||||
TargetBot.Creature.getConfigs = function(creature)
|
||||
if not creature then return {} end
|
||||
local name = creature:getName():trim():lower()
|
||||
-- this function may be slow, so it will be using cache
|
||||
if TargetBot.Creature.configsCache[name] then
|
||||
return TargetBot.Creature.configsCache[name]
|
||||
end
|
||||
local configs = {}
|
||||
for _, config in ipairs(TargetBot.targetList:getChildren()) do
|
||||
if regexMatch(name, config.value.regex)[1] then
|
||||
table.insert(configs, config.value)
|
||||
end
|
||||
end
|
||||
if TargetBot.Creature.cached > 1000 then
|
||||
TargetBot.Creature.resetConfigsCache() -- too big cache size, reset
|
||||
end
|
||||
TargetBot.Creature.configsCache[name] = configs -- add to cache
|
||||
TargetBot.Creature.cached = TargetBot.Creature.cached + 1
|
||||
return configs
|
||||
end
|
||||
|
||||
TargetBot.Creature.calculateParams = function(creature, path)
|
||||
local configs = TargetBot.Creature.getConfigs(creature)
|
||||
local priority = 0
|
||||
local danger = 0
|
||||
local selectedConfig = nil
|
||||
for _, config in ipairs(configs) do
|
||||
local config_priority = TargetBot.Creature.calculatePriority(creature, config, path)
|
||||
if config_priority > priority then
|
||||
priority = config_priority
|
||||
danger = TargetBot.Creature.calculateDanger(creature, config, path)
|
||||
selectedConfig = config
|
||||
end
|
||||
end
|
||||
return {
|
||||
config = selectedConfig,
|
||||
creature = creature,
|
||||
danger = danger,
|
||||
priority = priority
|
||||
}
|
||||
end
|
||||
|
||||
TargetBot.Creature.calculateDanger = function(creature, config, path)
|
||||
-- config is based on creature_editor
|
||||
return config.danger
|
||||
end
|
@ -0,0 +1,70 @@
|
||||
TargetBot.Creature.attack = function(params, targets, isLooting) -- params {config, creature, danger, priority}
|
||||
if player:isWalking() then
|
||||
lastWalk = now
|
||||
end
|
||||
|
||||
local config = params.config
|
||||
local creature = params.creature
|
||||
|
||||
if g_game.getAttackingCreature() ~= creature then
|
||||
g_game.attack(creature)
|
||||
end
|
||||
|
||||
if not isLooting then -- walk only when not looting
|
||||
TargetBot.Creature.walk(creature, config, targets)
|
||||
end
|
||||
|
||||
-- attacks
|
||||
local mana = player:getMana()
|
||||
if config.useGroupAttack and config.groupAttackSpell:len() > 1 and mana > config.minManaGroup then
|
||||
local creatures = g_map.getSpectatorsInRange(player:getPosition(), false, config.groupAttackRadius, config.groupAttackRadius)
|
||||
local playersAround = false
|
||||
local monsters = 0
|
||||
for _, creature in ipairs(creatures) do
|
||||
if not creature:isLocalPlayer() and creature:isPlayer() then
|
||||
playersAround = true
|
||||
elseif creature:isMonster() then
|
||||
monsters = monsters + 1
|
||||
end
|
||||
end
|
||||
if monsters >= config.groupAttackTargets and (not playersAround or config.groupAttackIgnorePlayers) then
|
||||
if TargetBot.sayAttackSpell(config.groupAttackSpell, config.groupAttackDelay) then
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
if config.useSpellAttack and config.attackSpell:len() > 1 and mana > config.minMana then
|
||||
if TargetBot.sayAttackSpell(config.attackSpell, config.attackSpellDelay) then
|
||||
return
|
||||
end
|
||||
end
|
||||
if config.useRuneAttack and config.attackRune > 100 then
|
||||
if TargetBot.useAttackItem(config.attackRune, creature, config.attackRuneDelay) then
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
TargetBot.Creature.walk = function(creature, config, targets)
|
||||
-- luring
|
||||
if config.lure and not (config.chase and creature:getHealthPercent() < 30) then
|
||||
local monsters = 0
|
||||
if targets < config.lureCount then
|
||||
local path = findPath(player:getPosition(), creature:getPosition(), 5, {ignoreNonPathable=true, precision=2})
|
||||
if path then
|
||||
return TargetBot.walkTo(creature:getPosition(), 10, {marginMin=5, marginMax=6, ignoreNonPathable=true})
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local currentDistance = findPath(player:getPosition(), creature:getPosition(), 10, {ignoreCreatures=true, ignoreNonPathable=true, ignoreCost=true})
|
||||
if config.chase and (creature:getHealthPercent() < 30 or not config.keepDistance) then
|
||||
if #currentDistance > 1 then
|
||||
return TargetBot.walkTo(creature:getPosition(), 10, {ignoreNonPathable=true, precision=1})
|
||||
end
|
||||
elseif config.keepDistance then
|
||||
if #currentDistance ~= config.keepDistanceRange and #currentDistance ~= config.keepDistanceRange + 1 then
|
||||
return TargetBot.walkTo(creature:getPosition(), 10, {ignoreNonPathable=true, marginMin=config.keepDistanceRange, marginMax=config.keepDistanceRange + 1})
|
||||
end
|
||||
end
|
||||
end
|
@ -0,0 +1,99 @@
|
||||
TargetBot.Creature.edit = function(config, callback) -- callback = function(newConfig)
|
||||
config = config or {}
|
||||
|
||||
local editor = UI.createWindow('TargetBotCreatureEditorWindow')
|
||||
local values = {} -- (key, function returning value of key)
|
||||
|
||||
editor.name:setText(config.name or "")
|
||||
table.insert(values, {"name", function() return editor.name:getText() end})
|
||||
|
||||
local addScrollBar = function(id, title, min, max, defaultValue)
|
||||
local widget = UI.createWidget('TargetBotCreatureEditorScrollBar', editor.left)
|
||||
widget.scroll.onValueChange = function(scroll, value)
|
||||
widget.text:setText(title .. ": " .. value)
|
||||
end
|
||||
widget.scroll:setRange(min, max)
|
||||
if max-min > 1000 then
|
||||
widget.scroll:setStep(100)
|
||||
elseif max-min > 100 then
|
||||
widget.scroll:setStep(10)
|
||||
end
|
||||
widget.scroll:setValue(config[id] or defaultValue)
|
||||
widget.scroll.onValueChange(widget.scroll, widget.scroll:getValue())
|
||||
table.insert(values, {id, function() return widget.scroll:getValue() end})
|
||||
end
|
||||
|
||||
local addTextEdit = function(id, title, defaultValue)
|
||||
local widget = UI.createWidget('TargetBotCreatureEditorTextEdit', editor.right)
|
||||
widget.text:setText(title)
|
||||
widget.textEdit:setText(config[id] or defaultValue or "")
|
||||
table.insert(values, {id, function() return widget.textEdit:getText() end})
|
||||
end
|
||||
|
||||
local addCheckBox = function(id, title, defaultValue)
|
||||
local widget = UI.createWidget('TargetBotCreatureEditorCheckBox', editor.right)
|
||||
widget.onClick = function()
|
||||
widget:setOn(not widget:isOn())
|
||||
end
|
||||
widget:setText(title)
|
||||
if config[id] == nil then
|
||||
widget:setOn(defaultValue)
|
||||
else
|
||||
widget:setOn(config[id])
|
||||
end
|
||||
table.insert(values, {id, function() return widget:isOn() end})
|
||||
end
|
||||
|
||||
local addItem = function(id, title, defaultItem)
|
||||
local widget = UI.createWidget('TargetBotCreatureEditorItem', editor.right)
|
||||
widget.text:setText(title)
|
||||
widget.item:setItemId(config[id] or defaultItem)
|
||||
table.insert(values, {id, function() return widget.item:getItemId() end})
|
||||
end
|
||||
|
||||
editor.cancel.onClick = function()
|
||||
editor:destroy()
|
||||
end
|
||||
editor.onEscape = editor.cancel.onClick
|
||||
|
||||
editor.ok.onClick = function()
|
||||
local newConfig = {}
|
||||
for _, value in ipairs(values) do
|
||||
newConfig[value[1]] = value[2]()
|
||||
end
|
||||
if newConfig.name:len() < 1 then return end
|
||||
newConfig.regex = "^" .. newConfig.name:trim():lower():gsub("%*", ".*"):gsub("%?", ".?") .. "$"
|
||||
|
||||
editor:destroy()
|
||||
callback(newConfig)
|
||||
end
|
||||
|
||||
-- values
|
||||
addScrollBar("priority", "Priority", 0, 10, 1)
|
||||
addScrollBar("danger", "Danger", 0, 10, 1)
|
||||
addScrollBar("maxDistance", "Max distance", 1, 10, 10)
|
||||
addScrollBar("keepDistanceRange", "Keep distance", 1, 5, 1)
|
||||
addScrollBar("lureCount", "Lure", 0, 5, 1)
|
||||
|
||||
addScrollBar("minMana", "Min. mana for attack spell", 0, 3000, 200)
|
||||
addScrollBar("attackSpellDelay", "Attack spell delay", 200, 5000, 2500)
|
||||
addScrollBar("minManaGroup", "Min. mana for group attack", 0, 3000, 1500)
|
||||
addScrollBar("groupAttackTargets", "Min. targets for group attack", 1, 10, 2)
|
||||
addScrollBar("groupAttackRadius", "Radius of group attack spell", 1, 7, 1)
|
||||
addScrollBar("groupAttackDelay", "Group attack spell delay", 200, 60000, 5000)
|
||||
addScrollBar("runeAttackDelay", "Rune attack delay", 200, 5000, 2000)
|
||||
|
||||
addCheckBox("chase", "Chase", true)
|
||||
addCheckBox("keepDistance", "Keep Distance", false)
|
||||
addCheckBox("lure", "Lure", false)
|
||||
-- addCheckBox("avoidAttacks", "Avoid attacks", true)
|
||||
|
||||
addCheckBox("useSpellAttack", "Use attack spell", false)
|
||||
addTextEdit("attackSpell", "Attack spell", "")
|
||||
addCheckBox("useGroupAttack", "Use group attack spell", false)
|
||||
addCheckBox("groupAttackIgnorePlayers", "Ignore players in group attack", false)
|
||||
addTextEdit("groupAttackSpell", "Group attack spell", "")
|
||||
addCheckBox("useRuneAttack", "Use attack rune", false)
|
||||
addItem("attackRune", "Attack rune:", 0)
|
||||
|
||||
end
|
@ -0,0 +1,134 @@
|
||||
TargetBotCreatureEditorScrollBar < Panel
|
||||
height: 35
|
||||
margin-top: 7
|
||||
|
||||
Label
|
||||
id: text
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
text-align: center
|
||||
|
||||
HorizontalScrollBar
|
||||
id: scroll
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: prev.bottom
|
||||
margin-top: 5
|
||||
minimum: 0
|
||||
maximum: 10
|
||||
step: 1
|
||||
|
||||
TargetBotCreatureEditorTextEdit < Panel
|
||||
height: 40
|
||||
margin-top: 7
|
||||
|
||||
Label
|
||||
id: text
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
text-align: center
|
||||
|
||||
TextEdit
|
||||
id: textEdit
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: prev.bottom
|
||||
margin-top: 5
|
||||
minimum: 0
|
||||
maximum: 10
|
||||
step: 1
|
||||
|
||||
TargetBotCreatureEditorItem < Panel
|
||||
height: 34
|
||||
margin-top: 7
|
||||
margin-left: 25
|
||||
margin-right: 25
|
||||
|
||||
Label
|
||||
id: text
|
||||
anchors.left: parent.left
|
||||
anchors.verticalCenter: next.verticalCenter
|
||||
|
||||
BotItem
|
||||
id: item
|
||||
anchors.top: parent.top
|
||||
anchors.right: parent.right
|
||||
|
||||
|
||||
TargetBotCreatureEditorCheckBox < BotSwitch
|
||||
height: 20
|
||||
margin-top: 7
|
||||
|
||||
TargetBotCreatureEditorWindow < MainWindow
|
||||
text: TargetBot creature editor
|
||||
width: 500
|
||||
height: 650
|
||||
|
||||
Label
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
text-align: center
|
||||
text: You can use * (any characters) and ? (any character) in target name
|
||||
|
||||
TextEdit
|
||||
id: name
|
||||
anchors.top: prev.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
margin-left: 90
|
||||
margin-top: 5
|
||||
|
||||
Label
|
||||
anchors.verticalCenter: prev.verticalCenter
|
||||
anchors.left: parent.left
|
||||
text: Target name:
|
||||
|
||||
Panel
|
||||
id: left
|
||||
anchors.top: name.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.horizontalCenter
|
||||
margin-top: 5
|
||||
margin-left: 10
|
||||
margin-right: 10
|
||||
layout:
|
||||
type: verticalBox
|
||||
fit-children: true
|
||||
|
||||
Panel
|
||||
id: right
|
||||
anchors.top: name.bottom
|
||||
anchors.left: parent.horizontalCenter
|
||||
anchors.right: parent.right
|
||||
margin-top: 5
|
||||
margin-left: 10
|
||||
margin-right: 10
|
||||
layout:
|
||||
type: verticalBox
|
||||
fit-children: true
|
||||
|
||||
Button
|
||||
id: help
|
||||
!text: tr('Help & Tutorials')
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: parent.left
|
||||
width: 150
|
||||
@onClick: g_platform.openUrl("http://bot.otclient.ovh/")
|
||||
|
||||
Button
|
||||
id: ok
|
||||
!text: tr('Ok')
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.right: next.left
|
||||
margin-right: 10
|
||||
width: 60
|
||||
|
||||
Button
|
||||
id: cancel
|
||||
!text: tr('Cancel')
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.right: parent.right
|
||||
width: 60
|
@ -0,0 +1,37 @@
|
||||
TargetBot.Creature.calculatePriority = function(creature, config, path)
|
||||
-- config is based on creature_editor
|
||||
local priority = config.priority
|
||||
|
||||
-- extra priority if it's current target
|
||||
if g_game.getAttackingCreature() == creature then
|
||||
priority = priority + 1
|
||||
end
|
||||
|
||||
-- check if distance is fine, if not then attack only if already attacked
|
||||
if #path > config.maxDistance then
|
||||
return priority
|
||||
end
|
||||
|
||||
-- extra priority for close distance
|
||||
local path_length = #path
|
||||
if path_length == 1 then
|
||||
priority = priority + 3
|
||||
elseif path_length <= 3 then
|
||||
priority = priority + 1
|
||||
end
|
||||
|
||||
-- extra priority for low health
|
||||
if config.chase and creature:getHealthPercent() < 30 then
|
||||
priority = priority + 5
|
||||
elseif creature:getHealthPercent() < 20 then
|
||||
priority = priority + 2.5
|
||||
elseif creature:getHealthPercent() < 40 then
|
||||
priority = priority + 1.5
|
||||
elseif creature:getHealthPercent() < 60 then
|
||||
priority = priority + 0.5
|
||||
elseif creature:getHealthPercent() < 80 then
|
||||
priority = priority + 0.2
|
||||
end
|
||||
|
||||
return priority
|
||||
end
|
@ -0,0 +1,295 @@
|
||||
TargetBot.Looting = {}
|
||||
TargetBot.Looting.list = {} -- list of containers to loot
|
||||
|
||||
local ui
|
||||
local items = {}
|
||||
local containers = {}
|
||||
local itemsById = {}
|
||||
local containersById = {}
|
||||
local dontSave = false
|
||||
|
||||
TargetBot.Looting.setup = function()
|
||||
ui = UI.createWidget("TargetBotLootingPanel")
|
||||
UI.Container(TargetBot.Looting.onItemsUpdate, true, nil, ui.items)
|
||||
UI.Container(TargetBot.Looting.onContainersUpdate, true, nil, ui.containers)
|
||||
ui.everyItem.onClick = function()
|
||||
ui.everyItem:setOn(not ui.everyItem:isOn())
|
||||
TargetBot.save()
|
||||
end
|
||||
ui.maxDangerPanel.value.onTextChange = function()
|
||||
local value = tonumber(ui.maxDangerPanel.value:getText())
|
||||
if not value then
|
||||
ui.maxDangerPanel.value:setText(0)
|
||||
end
|
||||
if dontSave then return end
|
||||
TargetBot.save()
|
||||
end
|
||||
ui.minCapacityPanel.value.onTextChange = function()
|
||||
local value = tonumber(ui.minCapacityPanel.value:getText())
|
||||
if not value then
|
||||
ui.minCapacityPanel.value:setText(0)
|
||||
end
|
||||
if dontSave then return end
|
||||
TargetBot.save()
|
||||
end
|
||||
end
|
||||
|
||||
TargetBot.Looting.onItemsUpdate = function()
|
||||
if dontSave then return end
|
||||
TargetBot.save()
|
||||
TargetBot.Looting.updateItemsAndContainers()
|
||||
end
|
||||
|
||||
TargetBot.Looting.onContainersUpdate = function()
|
||||
if dontSave then return end
|
||||
TargetBot.save()
|
||||
TargetBot.Looting.updateItemsAndContainers()
|
||||
end
|
||||
|
||||
TargetBot.Looting.update = function(data)
|
||||
dontSave = true
|
||||
TargetBot.Looting.list = {}
|
||||
ui.items:setItems(data['items'] or {})
|
||||
ui.containers:setItems(data['containers'] or {})
|
||||
ui.everyItem:setOn(data['everyItem'])
|
||||
ui.maxDangerPanel.value:setText(data['maxDanger'] or 10)
|
||||
ui.minCapacityPanel.value:setText(data['minCapacity'] or 100)
|
||||
TargetBot.Looting.updateItemsAndContainers()
|
||||
dontSave = false
|
||||
end
|
||||
|
||||
TargetBot.Looting.save = function(data)
|
||||
data['items'] = ui.items:getItems()
|
||||
data['containers'] = ui.containers:getItems()
|
||||
data['maxDanger'] = tonumber(ui.maxDangerPanel.value:getText())
|
||||
data['minCapacity'] = tonumber(ui.minCapacityPanel.value:getText())
|
||||
data['everyItem'] = ui.everyItem:isOn()
|
||||
end
|
||||
|
||||
TargetBot.Looting.updateItemsAndContainers = function()
|
||||
items = ui.items:getItems()
|
||||
containers = ui.containers:getItems()
|
||||
itemsById = {}
|
||||
containersById = {}
|
||||
for i, item in ipairs(items) do
|
||||
itemsById[item.id] = 1
|
||||
end
|
||||
for i, container in ipairs(containers) do
|
||||
containersById[container.id] = 1
|
||||
end
|
||||
end
|
||||
|
||||
local waitTill = 0
|
||||
local waitingForContainer = nil
|
||||
local status = ""
|
||||
local lastFoodConsumption = 0
|
||||
|
||||
TargetBot.Looting.getStatus = function()
|
||||
return status
|
||||
end
|
||||
|
||||
TargetBot.Looting.process = function(targets, dangerLevel)
|
||||
if (not items[1] and not ui.everyItem:isOn()) or not containers[1] then
|
||||
status = ""
|
||||
return false
|
||||
end
|
||||
if dangerLevel > tonumber(ui.maxDangerPanel.value:getText()) then
|
||||
status = "High danger"
|
||||
return false
|
||||
end
|
||||
if player:getFreeCapacity() < tonumber(ui.minCapacityPanel.value:getText()) then
|
||||
status = "No cap"
|
||||
TargetBot.Looting.list = {}
|
||||
return false
|
||||
end
|
||||
local loot = TargetBot.Looting.list[1]
|
||||
if loot == nil then
|
||||
status = ""
|
||||
return false
|
||||
end
|
||||
|
||||
if waitTill > now then
|
||||
return true
|
||||
end
|
||||
local containers = g_game.getContainers()
|
||||
local lootContainers = TargetBot.Looting.getLootContainers(containers)
|
||||
|
||||
-- check if there's container for loot and has empty space for it
|
||||
if not lootContainers[1] then
|
||||
-- there's no space, don't loot
|
||||
status = "No space"
|
||||
return false
|
||||
end
|
||||
|
||||
status = "Looting"
|
||||
|
||||
for index, container in pairs(containers) do
|
||||
if container.lootContainer then
|
||||
TargetBot.Looting.lootContainer(lootContainers, container)
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
local pos = player:getPosition()
|
||||
local dist = math.max(math.abs(pos.x-loot.pos.x), math.abs(pos.y-loot.pos.y))
|
||||
if loot.tries > 30 or loot.pos.z ~= pos.z or dist > 20 then
|
||||
table.remove(TargetBot.Looting.list, 1)
|
||||
return true
|
||||
end
|
||||
|
||||
local tile = g_map.getTile(loot.pos)
|
||||
if dist >= 3 or not tile then
|
||||
loot.tries = loot.tries + 1
|
||||
TargetBot.walkTo(loot.pos, 20, { ignoreNonPathable = true, precision = 2 })
|
||||
return true
|
||||
end
|
||||
|
||||
local container = tile:getTopUseThing()
|
||||
if not container or container:getId() ~= loot.container then
|
||||
table.remove(TargetBot.Looting.list, 1)
|
||||
return true
|
||||
end
|
||||
|
||||
g_game.open(container)
|
||||
waitTill = now + 1000 -- give it 1s to open
|
||||
waitingForContainer = loot.container
|
||||
loot.tries = loot.tries + 10
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
TargetBot.Looting.getLootContainers = function(containers)
|
||||
local lootContainers = {}
|
||||
local openedContainersById = {}
|
||||
local toOpen = nil
|
||||
for index, container in pairs(containers) do
|
||||
openedContainersById[container:getContainerItem():getId()] = 1
|
||||
if containersById[container:getContainerItem():getId()] and not container.lootContainer then
|
||||
if container:getItemsCount() < container:getCapacity() then
|
||||
table.insert(lootContainers, container)
|
||||
else -- it's full, open next container if possible
|
||||
for slot, item in ipairs(container:getItems()) do
|
||||
if item:isContainer() and containersById[item:getId()] then
|
||||
toOpen = {item, container}
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
if not lootContainers[1] then
|
||||
if toOpen then
|
||||
g_game.open(toOpen[1], toOpen[2])
|
||||
waitTill = now + 500 -- wait 0.5s
|
||||
return lootContainers
|
||||
end
|
||||
-- check containers one more time, maybe there's any loot container
|
||||
for index, container in pairs(containers) do
|
||||
if not containersById[container:getContainerItem():getId()] and not container.lootContainer then
|
||||
for slot, item in ipairs(container:getItems()) do
|
||||
if item:isContainer() and containersById[item:getId()] then
|
||||
g_game.open(item)
|
||||
waitTill = now + 500 -- wait 0.5s
|
||||
return lootContainers
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
-- can't find any lootContainer, let's check slots, maybe there's one
|
||||
for slot = InventorySlotFirst, InventorySlotLast do
|
||||
local item = getInventoryItem(slot)
|
||||
if item and item:isContainer() and not openedContainersById[item:getId()] then
|
||||
-- container which is not opened yet, let's open it
|
||||
g_game.open(item)
|
||||
waitTill = now + 500 -- wait 0.5s
|
||||
return lootContainers
|
||||
end
|
||||
end
|
||||
end
|
||||
return lootContainers
|
||||
end
|
||||
|
||||
TargetBot.Looting.lootContainer = function(lootContainers, container)
|
||||
-- loot items
|
||||
local nextContainer = nil
|
||||
for i, item in ipairs(container:getItems()) do
|
||||
if item:isContainer() then
|
||||
nextContainer = item
|
||||
elseif itemsById[item:getId()] or ui.everyItem:isOn() then
|
||||
item.lootTries = (item.lootTries or 0) + 1
|
||||
if item.lootTries < 5 then -- if can't be looted within 0.5s then skip it
|
||||
return TargetBot.Looting.lootItem(lootContainers, item)
|
||||
end
|
||||
elseif storage.foodItems and storage.foodItems[1] and lastFoodConsumption + 5000 < now then
|
||||
for _, food in ipairs(storage.foodItems) do
|
||||
if item:getId() == food.id then
|
||||
g_game.use(item)
|
||||
lastFoodConsumption = now
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- no more items to loot, open next container
|
||||
if nextContainer then
|
||||
nextContainer.lootTries = (nextContainer.lootTries or 0) + 1
|
||||
if nextContainer.lootTries < 2 then -- max 0.6s to open it
|
||||
g_game.open(nextContainer, container)
|
||||
waitTill = now + 300 -- give it 0.3s to open
|
||||
waitingForContainer = loot.container
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
-- looting finished, remove container from list
|
||||
container.lootContainer = false
|
||||
g_game.close(container)
|
||||
table.remove(TargetBot.Looting.list, 1)
|
||||
end
|
||||
|
||||
TargetBot.Looting.lootItem = function(lootContainers, item)
|
||||
if item:isStackable() then
|
||||
local count = item:getCount()
|
||||
for _, container in ipairs(lootContainers) do
|
||||
for slot, citem in ipairs(container:getItems()) do
|
||||
if item:getId() == citem:getId() and citem:getCount() < 100 then
|
||||
g_game.move(item, container:getSlotPosition(slot - 1), count)
|
||||
waitTill = now + 300 -- give it 0.3s to move item
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local container = lootContainers[1]
|
||||
g_game.move(item, container:getSlotPosition(container:getItemsCount()), 1)
|
||||
waitTill = now + 300 -- give it 0.3s to move item
|
||||
end
|
||||
|
||||
onContainerOpen(function(container, previousContainer)
|
||||
if container:getContainerItem():getId() == waitingForContainer then
|
||||
container.lootContainer = true
|
||||
waitingForContainer = nil
|
||||
end
|
||||
end)
|
||||
|
||||
onCreatureDisappear(function(creature)
|
||||
if not TargetBot.isOn() then return end
|
||||
if not creature:isMonster() then return end
|
||||
local pos = player:getPosition()
|
||||
local mpos = creature:getPosition()
|
||||
local name = creature:getName()
|
||||
if pos.z ~= mpos.z or math.max(math.abs(pos.x-mpos.x), math.abs(pos.y-mpos.y)) > 6 then return end
|
||||
schedule(20, function() -- check in 20ms if there's container (dead body) on that tile
|
||||
if not containers[1] then return end
|
||||
if TargetBot.Looting.list[20] then return end -- too many items to loot
|
||||
local tile = g_map.getTile(mpos)
|
||||
if not tile then return end
|
||||
local container = tile:getTopUseThing()
|
||||
if not container or not container:isContainer() then return end
|
||||
if not findPath(player:getPosition(), mpos, 6, {ignoreNonPathable=true, ignoreCreatures=true, ignoreCost=true}) then return end
|
||||
table.insert(TargetBot.Looting.list, {pos=mpos, creature=name, container=container:getId(), added=now, tries=0})
|
||||
container:setMarked('#000088')
|
||||
end)
|
||||
end)
|
@ -0,0 +1,83 @@
|
||||
TargetBotLootingPanel < Panel
|
||||
layout:
|
||||
type: verticalBox
|
||||
fit-children: true
|
||||
|
||||
HorizontalSeparator
|
||||
margin-top: 5
|
||||
|
||||
Label
|
||||
margin-top: 5
|
||||
text: Items to loot
|
||||
text-align: center
|
||||
|
||||
BotContainer
|
||||
id: items
|
||||
margin-top: 3
|
||||
|
||||
BotSwitch
|
||||
id: everyItem
|
||||
!text: tr("Loot every item")
|
||||
margin-top: 2
|
||||
|
||||
Label
|
||||
margin-top: 5
|
||||
text: Containers for loot
|
||||
text-align: center
|
||||
|
||||
BotContainer
|
||||
id: containers
|
||||
margin-top: 3
|
||||
height: 45
|
||||
|
||||
Panel
|
||||
id: maxDangerPanel
|
||||
height: 20
|
||||
margin-top: 5
|
||||
|
||||
BotTextEdit
|
||||
id: value
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: parent.bottom
|
||||
margin-right: 6
|
||||
width: 80
|
||||
|
||||
Label
|
||||
anchors.left: parent.left
|
||||
anchors.verticalCenter: prev.verticalCenter
|
||||
text: Max. danger:
|
||||
margin-left: 5
|
||||
|
||||
Panel
|
||||
id: minCapacityPanel
|
||||
height: 20
|
||||
margin-top: 3
|
||||
|
||||
BotTextEdit
|
||||
id: value
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: parent.bottom
|
||||
margin-right: 6
|
||||
width: 80
|
||||
|
||||
Label
|
||||
anchors.left: parent.left
|
||||
anchors.verticalCenter: prev.verticalCenter
|
||||
text: Min. capacity:
|
||||
margin-left: 5
|
||||
|
||||
Label
|
||||
margin-top: 3
|
||||
margin-left: 20
|
||||
margin-right: 20
|
||||
!text: tr("Drag item or click on any of empty slot")
|
||||
text-align: center
|
||||
text-wrap: true
|
||||
text-auto-resize: true
|
||||
|
||||
BotButton
|
||||
margin-top: 3
|
||||
text: Help & Tutorials
|
||||
@onClick: g_platform.openUrl("http://bot.otclient.ovh/")
|
@ -0,0 +1,231 @@
|
||||
local targetbotMacro = nil
|
||||
local config = nil
|
||||
local lastAction = 0
|
||||
|
||||
-- ui
|
||||
local configWidget = UI.Config()
|
||||
local ui = UI.createWidget("TargetBotPanel")
|
||||
|
||||
ui.list = ui.listPanel.list -- shortcut
|
||||
TargetBot.targetList = ui.list
|
||||
TargetBot.Looting.setup()
|
||||
|
||||
ui.status.left:setText("Status:")
|
||||
ui.status.right:setText("Off")
|
||||
ui.target.left:setText("Target:")
|
||||
ui.target.right:setText("-")
|
||||
ui.config.left:setText("Config:")
|
||||
ui.config.right:setText("-")
|
||||
ui.danger.left:setText("Danger:")
|
||||
ui.danger.right:setText("0")
|
||||
|
||||
ui.editor.debug.onClick = function()
|
||||
local on = ui.editor.debug:isOn()
|
||||
ui.editor.debug:setOn(not on)
|
||||
if on then
|
||||
for _, spec in ipairs(getSpectators()) do
|
||||
spec:clearText()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- main loop, controlled by config
|
||||
targetbotMacro = macro(100, function()
|
||||
local pos = player:getPosition()
|
||||
local creatures = g_map.getSpectatorsInRange(pos, false, 6, 6) -- 12x12 area
|
||||
if #creatures > 10 then -- if there are too many monsters around, limit area
|
||||
creatures = g_map.getSpectatorsInRange(pos, false, 3, 3) -- 6x6 area
|
||||
end
|
||||
local highestPriority = 0
|
||||
local dangerLevel = 0
|
||||
local targets = 0
|
||||
local highestPriorityParams = nil
|
||||
for i, creature in ipairs(creatures) do
|
||||
local path = findPath(player:getPosition(), creature:getPosition(), 7, {ignoreLastCreature=true, ignoreNonPathable=true, ignoreCost=true})
|
||||
if creature:isMonster() and path then
|
||||
local params = TargetBot.Creature.calculateParams(creature, path) -- return {craeture, config, danger, priority}
|
||||
dangerLevel = dangerLevel + params.danger
|
||||
if params.priority > 0 then
|
||||
targets = targets + 1
|
||||
if params.priority > highestPriority then
|
||||
highestPriority = params.priority
|
||||
highestPriorityParams = params
|
||||
end
|
||||
if ui.editor.debug:isOn() then
|
||||
creature:setText(params.config.name .. "\n" .. params.priority)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- reset walking
|
||||
TargetBot.walkTo(nil)
|
||||
|
||||
-- looting
|
||||
local looting = TargetBot.Looting.process(targets, dangerLevel)
|
||||
local lootingStatus = TargetBot.Looting.getStatus()
|
||||
|
||||
ui.danger.right:setText(dangerLevel)
|
||||
if highestPriorityParams and not isInPz() then
|
||||
ui.target.right:setText(highestPriorityParams.creature:getName())
|
||||
ui.config.right:setText(highestPriorityParams.config.name)
|
||||
TargetBot.Creature.attack(highestPriorityParams, targets, looting)
|
||||
if lootingStatus:len() > 0 then
|
||||
TargetBot.setStatus("Attack & " .. lootingStatus)
|
||||
else
|
||||
TargetBot.setStatus("Attacking")
|
||||
end
|
||||
TargetBot.walk()
|
||||
lastAction = now
|
||||
return
|
||||
end
|
||||
|
||||
ui.target.right:setText("-")
|
||||
ui.config.right:setText("-")
|
||||
if looting then
|
||||
TargetBot.walk()
|
||||
lastAction = now
|
||||
end
|
||||
if lootingStatus:len() > 0 then
|
||||
TargetBot.setStatus(lootingStatus)
|
||||
else
|
||||
TargetBot.setStatus("Waiting")
|
||||
end
|
||||
end)
|
||||
|
||||
-- config, its callback is called immediately, data can be nil
|
||||
config = Config.setup("targetbot_configs", configWidget, "json", function(name, enabled, data)
|
||||
if not data then
|
||||
ui.status.right:setText("Off")
|
||||
return targetbotMacro.setOff()
|
||||
end
|
||||
TargetBot.Creature.resetConfigs()
|
||||
for _, value in ipairs(data["targeting"] or {}) do
|
||||
TargetBot.Creature.addConfig(value)
|
||||
end
|
||||
TargetBot.Looting.update(data["looting"] or {})
|
||||
|
||||
-- add configs
|
||||
if enabled then
|
||||
ui.status.right:setText("On")
|
||||
else
|
||||
ui.status.right:setText("Off")
|
||||
end
|
||||
|
||||
targetbotMacro.setOn(enabled)
|
||||
targetbotMacro.delay = nil
|
||||
end)
|
||||
|
||||
-- setup ui
|
||||
ui.editor.buttons.add.onClick = function()
|
||||
TargetBot.Creature.edit(nil, function(newConfig)
|
||||
TargetBot.Creature.addConfig(newConfig, true)
|
||||
TargetBot.save()
|
||||
end)
|
||||
end
|
||||
|
||||
ui.editor.buttons.edit.onClick = function()
|
||||
local entry = ui.list:getFocusedChild()
|
||||
if not entry then return end
|
||||
TargetBot.Creature.edit(entry.value, function(newConfig)
|
||||
entry:setText(newConfig.name)
|
||||
entry.value = newConfig
|
||||
TargetBot.Creature.resetConfigsCache()
|
||||
TargetBot.save()
|
||||
end)
|
||||
end
|
||||
|
||||
ui.editor.buttons.remove.onClick = function()
|
||||
local entry = ui.list:getFocusedChild()
|
||||
if not entry then return end
|
||||
entry:destroy()
|
||||
TargetBot.Creature.resetConfigsCache()
|
||||
TargetBot.save()
|
||||
end
|
||||
|
||||
-- public function, you can use them in your scripts
|
||||
TargetBot.isActive = function() -- return true if attacking or looting takes place
|
||||
return lastAction + 300 > now
|
||||
end
|
||||
|
||||
TargetBot.setStatus = function(text)
|
||||
return ui.status.right:setText(text)
|
||||
end
|
||||
|
||||
TargetBot.isOn = function()
|
||||
return config.isOn()
|
||||
end
|
||||
|
||||
TargetBot.isOff = function()
|
||||
return config.isOff()
|
||||
end
|
||||
|
||||
TargetBot.setOn = function(val)
|
||||
if val == false then
|
||||
return TargetBot.setOff(true)
|
||||
end
|
||||
config.setOn()
|
||||
end
|
||||
|
||||
TargetBot.setOff = function(val)
|
||||
if val == false then
|
||||
return TargetBot.setOn(true)
|
||||
end
|
||||
config.setOff()
|
||||
end
|
||||
|
||||
TargetBot.delay = function(value)
|
||||
targetbotMacro.delay = now + value
|
||||
end
|
||||
|
||||
TargetBot.save = function()
|
||||
local data = {targeting={}, looting={}}
|
||||
for _, entry in ipairs(ui.list:getChildren()) do
|
||||
table.insert(data.targeting, entry.value)
|
||||
end
|
||||
TargetBot.Looting.save(data.looting)
|
||||
config.save(data)
|
||||
end
|
||||
|
||||
-- attacks
|
||||
local lastSpell = 0
|
||||
local lastAttackSpell = 0
|
||||
|
||||
TargetBot.saySpell = function(text, delay)
|
||||
if type(text) ~= 'string' or text:len() < 1 then return end
|
||||
if not delay then delay = 500 end
|
||||
if g_game.getProtocolVersion() < 1090 then
|
||||
lastAttackSpell = now -- pause attack spells, healing spells are more important
|
||||
end
|
||||
if lastSpell + delay < now then
|
||||
say(text)
|
||||
lastSpell = now
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
TargetBot.sayAttackSpell = function(text, delay)
|
||||
if type(text) ~= 'string' or text:len() < 1 then return end
|
||||
if not delay then delay = 2000 end
|
||||
if lastAttackSpell + delay < now then
|
||||
say(text)
|
||||
lastAttackSpell = now
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
local lastRuneAttack = 0
|
||||
|
||||
TargetBot.useItem = function(item, target, delay)
|
||||
useWith(item, target)
|
||||
end
|
||||
|
||||
TargetBot.useAttackItem = function(item, target, delay)
|
||||
if not delay then delay = 2000 end
|
||||
if lastRuneAttack + delay < now then
|
||||
useWith(item, target)
|
||||
lastRuneAttack = now
|
||||
end
|
||||
end
|
@ -0,0 +1,115 @@
|
||||
TargetBotEntry < Label
|
||||
background-color: alpha
|
||||
text-offset: 2 0
|
||||
focusable: true
|
||||
|
||||
$focus:
|
||||
background-color: #00000055
|
||||
|
||||
TargetBotDualLabel < Panel
|
||||
height: 18
|
||||
margin-left: 3
|
||||
margin-right: 4
|
||||
|
||||
Label
|
||||
id: left
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
text-auto-resize: true
|
||||
|
||||
Label
|
||||
id: right
|
||||
anchors.top: parent.top
|
||||
anchors.right: parent.right
|
||||
text-auto-resize: true
|
||||
|
||||
TargetBotPanel < Panel
|
||||
layout:
|
||||
type: verticalBox
|
||||
fit-children: true
|
||||
|
||||
HorizontalSeparator
|
||||
margin-top: 2
|
||||
margin-bottom: 5
|
||||
|
||||
TargetBotDualLabel
|
||||
id: status
|
||||
TargetBotDualLabel
|
||||
id: target
|
||||
TargetBotDualLabel
|
||||
id: config
|
||||
TargetBotDualLabel
|
||||
id: danger
|
||||
|
||||
Panel
|
||||
id: listPanel
|
||||
height: 40
|
||||
|
||||
TextList
|
||||
id: list
|
||||
anchors.fill: parent
|
||||
vertical-scrollbar: listScrollbar
|
||||
margin-right: 15
|
||||
focusable: false
|
||||
auto-focus: first
|
||||
|
||||
VerticalScrollBar
|
||||
id: listScrollbar
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.right: parent.right
|
||||
pixels-scroll: true
|
||||
step: 10
|
||||
|
||||
BotSwitch
|
||||
id: configButton
|
||||
@onClick: |
|
||||
self:setOn(not self:isOn())
|
||||
self:getParent().listPanel:setHeight(self:isOn() and 100 or 40)
|
||||
self:getParent().editor:setVisible(self:isOn())
|
||||
|
||||
$on:
|
||||
text: Hide target editor
|
||||
|
||||
$!on:
|
||||
text: Show target editor
|
||||
|
||||
Panel
|
||||
id: editor
|
||||
visible: false
|
||||
layout:
|
||||
type: verticalBox
|
||||
fit-children: true
|
||||
|
||||
Panel
|
||||
id: buttons
|
||||
height: 20
|
||||
margin-top: 2
|
||||
|
||||
Button
|
||||
id: add
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: parent.left
|
||||
text: Add
|
||||
width: 56
|
||||
|
||||
Button
|
||||
id: edit
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
text: Edit
|
||||
width: 56
|
||||
|
||||
Button
|
||||
id: remove
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.right: parent.right
|
||||
text: Remove
|
||||
width: 56
|
||||
|
||||
BotSwitch
|
||||
id: debug
|
||||
text: Show target priority
|
@ -0,0 +1,28 @@
|
||||
local dest
|
||||
local maxDist
|
||||
local params
|
||||
|
||||
TargetBot.walkTo = function(_dest, _maxDist, _params)
|
||||
dest = _dest
|
||||
maxDist = _maxDist
|
||||
params = _params
|
||||
end
|
||||
|
||||
-- called every 100ms if targeting or looting is active
|
||||
TargetBot.walk = function()
|
||||
if not dest then return end
|
||||
if player:isWalking() then return end
|
||||
local pos = player:getPosition()
|
||||
if pos.z ~= dest.z then return end
|
||||
local dist = math.max(math.abs(pos.x-dest.x), math.abs(pos.y-dest.y))
|
||||
if params.precision and params.precision >= dist then return end
|
||||
if params.marginMin and params.marginMax then
|
||||
if dist >= params.marginMin and dist <= params.marginMax then
|
||||
return
|
||||
end
|
||||
end
|
||||
local path = getPath(pos, dest, maxDist, params)
|
||||
if path then
|
||||
walk(path[1])
|
||||
end
|
||||
end
|
110
modules/game_bot/default_configs/new_cavebot/tools.lua
Normal file
110
modules/game_bot/default_configs/new_cavebot/tools.lua
Normal file
@ -0,0 +1,110 @@
|
||||
-- tools tab
|
||||
setDefaultTab("Tools")
|
||||
|
||||
-- allows to test/edit bot lua scripts ingame, you can have multiple scripts like this, just change storage.ingame_lua
|
||||
UI.Button("Ingame macro editor", function(newText)
|
||||
UI.MultilineEditorWindow(storage.ingame_macros or "", {title="Macro editor", description="You can add your custom macros (or any other lua code) here"}, function(text)
|
||||
storage.ingame_macros = text
|
||||
reload()
|
||||
end)
|
||||
end)
|
||||
UI.Button("Ingame hotkey editor", function(newText)
|
||||
UI.MultilineEditorWindow(storage.ingame_hotkeys or "", {title="Hotkeys editor", description="You can add your custom hotkeys/singlehotkeys here"}, function(text)
|
||||
storage.ingame_hotkeys = text
|
||||
reload()
|
||||
end)
|
||||
end)
|
||||
|
||||
UI.Separator()
|
||||
|
||||
for _, scripts in ipairs({storage.ingame_macros, storage.ingame_hotkeys}) do
|
||||
if type(scripts) == "string" and scripts:len() > 3 then
|
||||
local status, result = pcall(function()
|
||||
assert(load(scripts, "ingame_editor"))()
|
||||
end)
|
||||
if not status then
|
||||
error("Ingame edior error:\n" .. result)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
UI.Separator()
|
||||
|
||||
UI.Button("Zoom In map [ctrl + =]", function() zoomIn() end)
|
||||
UI.Button("Zoom Out map [ctrl + -]", function() zoomOut() end)
|
||||
|
||||
UI.Separator()
|
||||
|
||||
local moneyIds = {3031, 3035} -- gold coin, platinium coin
|
||||
macro(1000, "Exchange money", function()
|
||||
local containers = g_game.getContainers()
|
||||
for index, container in pairs(containers) do
|
||||
if not container.lootContainer then -- ignore monster containers
|
||||
for i, item in ipairs(container:getItems()) do
|
||||
if item:getCount() == 100 then
|
||||
for m, moneyId in ipairs(moneyIds) do
|
||||
if item:getId() == moneyId then
|
||||
return g_game.use(item)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
macro(1000, "Stack items", function()
|
||||
local containers = g_game.getContainers()
|
||||
local toStack = {}
|
||||
for index, container in pairs(containers) do
|
||||
if not container.lootContainer then -- ignore monster containers
|
||||
for i, item in ipairs(container:getItems()) do
|
||||
if item:isStackable() and item:getCount() < 100 then
|
||||
local stackWith = toStack[item:getId()]
|
||||
if stackWith then
|
||||
g_game.move(item, stackWith[1], math.min(stackWith[2], item:getCount()))
|
||||
return
|
||||
end
|
||||
toStack[item:getId()] = {container:getSlotPosition(i - 1), 100 - item:getCount()}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
UI.Separator()
|
||||
|
||||
UI.Label("Mana training")
|
||||
if type(storage.manaTrain) ~= "table" then
|
||||
storage.manaTrain = {on=false, title="MP%", text="utevo lux", min=80, max=100}
|
||||
end
|
||||
|
||||
local manatrainmacro = macro(1000, function()
|
||||
local mana = math.min(100, math.floor(100 * (player:getMana() / player:getMaxMana())))
|
||||
if storage.manaTrain.max >= mana and mana >= storage.manaTrain.min then
|
||||
say(storage.manaTrain.text)
|
||||
end
|
||||
end)
|
||||
manatrainmacro.setOn(storage.manaTrain.on)
|
||||
|
||||
UI.DualScrollPanel(storage.manaTrain, function(widget, newParams)
|
||||
storage.manaTrain = newParams
|
||||
manatrainmacro.setOn(storage.manaTrain.on)
|
||||
end)
|
||||
|
||||
UI.Separator()
|
||||
|
||||
macro(60000, "Send message on trade", function()
|
||||
local trade = getChannelId("advertising")
|
||||
if not trade then
|
||||
trade = getChannelId("trade")
|
||||
end
|
||||
if trade and storage.autoTradeMessage:len() > 0 then
|
||||
sayChannel(trade, storage.autoTradeMessage)
|
||||
end
|
||||
end)
|
||||
UI.TextEdit(storage.autoTradeMessage or "I'm using OTClientV8!", function(widget, text)
|
||||
storage.autoTradeMessage = text
|
||||
end)
|
||||
|
||||
UI.Separator()
|
@ -29,6 +29,20 @@ context.callback = function(callbackType, callback)
|
||||
context._currentExecution = prevExecution
|
||||
end
|
||||
end)
|
||||
local cb = context._callbacks[callbackType]
|
||||
return {
|
||||
remove = function()
|
||||
local index = nil
|
||||
for i, cb2 in ipairs(context._callbacks[callbackType]) do
|
||||
if cb == cb2 then
|
||||
index = i
|
||||
end
|
||||
end
|
||||
if index then
|
||||
table.remove(context._callbacks[callbackType], index)
|
||||
end
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
-- onKeyDown(callback) -- callback = function(keys)
|
||||
@ -158,7 +172,7 @@ end
|
||||
context.listen = function(name, callback)
|
||||
if not name then return context.error("listen: invalid name") end
|
||||
name = name:lower()
|
||||
context.onTalk(function(name2, level, mode, text, channelId, pos)
|
||||
return context.onTalk(function(name2, level, mode, text, channelId, pos)
|
||||
if name == name2:lower() then
|
||||
callback(text, channelId, pos)
|
||||
end
|
||||
@ -167,7 +181,7 @@ end
|
||||
|
||||
-- onPlayerPositionChange(callback) -- callback = function(newPos, oldPos)
|
||||
context.onPlayerPositionChange = function(callback)
|
||||
context.onCreaturePositionChange(function(creature, newPos, oldPos)
|
||||
return context.onCreaturePositionChange(function(creature, newPos, oldPos)
|
||||
if creature == context.player then
|
||||
callback(newPos, oldPos)
|
||||
end
|
||||
@ -176,7 +190,7 @@ end
|
||||
|
||||
-- onPlayerHealthChange(callback) -- callback = function(healthPercent)
|
||||
context.onPlayerHealthChange = function(callback)
|
||||
context.onCreatureHealthPercentChange(function(creature, healthPercent)
|
||||
return context.onCreatureHealthPercentChange(function(creature, healthPercent)
|
||||
if creature == context.player then
|
||||
callback(healthPercent)
|
||||
end
|
||||
|
@ -33,9 +33,10 @@ Config.list = function(dir)
|
||||
return correctList
|
||||
end
|
||||
|
||||
-- load config from string insteaf of dile
|
||||
-- load config from string insteaf of file
|
||||
Config.parse = function(data)
|
||||
local status, result = pcall(function()
|
||||
if data:len() < 2 then return {} end
|
||||
return json.decode(data)
|
||||
end)
|
||||
if status and type(result) == 'table' then
|
||||
@ -51,13 +52,29 @@ Config.parse = function(data)
|
||||
end
|
||||
|
||||
Config.load = function(dir, name)
|
||||
local file = context.configDir .. "/" .. dir .. "/" .. name .. ".json"
|
||||
local file = context.configDir .. "/" .. dir .. "/" .. name .. ".json"
|
||||
if g_resources.fileExists(file) then -- load json
|
||||
return json.decode(g_resources.readFileContents(file))
|
||||
local status, result = pcall(function()
|
||||
local data = g_resources.readFileContents(file)
|
||||
if data:len() < 2 then return {} end
|
||||
return json.decode(data)
|
||||
end)
|
||||
if not status then
|
||||
context.error("Invalid json config (" .. name .. "): " .. result)
|
||||
return {}
|
||||
end
|
||||
return result
|
||||
end
|
||||
file = context.configDir .. "/" .. dir .. "/" .. name .. ".cfg"
|
||||
if g_resources.fileExists(file) then -- load cfg
|
||||
return table.decodeStringPairList(g_resources.readFileContents(file))
|
||||
local status, result = pcall(function()
|
||||
return table.decodeStringPairList(g_resources.readFileContents(file))
|
||||
end)
|
||||
if not status then
|
||||
context.error("Invalid cfg config (" .. name .. "): " .. result)
|
||||
return {}
|
||||
end
|
||||
return result
|
||||
end
|
||||
return context.error("Config " .. file .. " doesn't exist")
|
||||
end
|
||||
@ -87,7 +104,7 @@ Config.save = function(dir, name, value, forcedExtension)
|
||||
if (table.isStringPairList(value) and forcedExtension ~= "json") or forcedExtension == "cfg" then -- cfg
|
||||
g_resources.writeFileContents(file .. ".cfg", table.encodeStringPairList(value))
|
||||
else
|
||||
g_resources.writeFileContents(file .. ".json", json.encode(value))
|
||||
g_resources.writeFileContents(file .. ".json", json.encode(value, 2))
|
||||
end
|
||||
return true
|
||||
end
|
||||
@ -175,7 +192,11 @@ Config.setup = function(dir, widget, configExtension, callback)
|
||||
if g_resources.fileExists(file) then
|
||||
return context.error("Config " .. name .. " already exist")
|
||||
end
|
||||
g_resources.writeFileContents(file, "")
|
||||
if configExtension == "json" then
|
||||
g_resources.writeFileContents(file, json.encode({}))
|
||||
else
|
||||
g_resources.writeFileContents(file, "")
|
||||
end
|
||||
context.storage._configs[dir].selected = name
|
||||
widget.switch:setOn(false)
|
||||
refresh()
|
||||
|
@ -1,6 +1,6 @@
|
||||
local context = G.botContext
|
||||
|
||||
context.encode = function(data) return json.encode(data) end
|
||||
context.encode = function(data, indent) return json.encode(data, indent or 2) end
|
||||
context.decode = function(text) local status, result = pcall(function() return json.decode(text) end) if status then return result end return {} end
|
||||
|
||||
context.displayGeneralBox = function(title, message, buttons, onEnterCallback, onEscapeCallback)
|
||||
|
@ -8,5 +8,17 @@ UI.createWidget = function(name, parent)
|
||||
if parent == nil then
|
||||
parent = context.panel
|
||||
end
|
||||
return g_ui.createWidget(name, parent)
|
||||
local widget = g_ui.createWidget(name, parent)
|
||||
widget.botWidget = true
|
||||
return widget
|
||||
end
|
||||
|
||||
UI.createWindow = function(name)
|
||||
local widget = g_ui.createWidget(name, g_ui.getRootWidget())
|
||||
widget.botWidget = true
|
||||
widget:show()
|
||||
widget:raise()
|
||||
widget:focus()
|
||||
return widget
|
||||
end
|
||||
|
||||
|
@ -4,6 +4,14 @@ if type(context.UI) ~= "table" then
|
||||
end
|
||||
local UI = context.UI
|
||||
|
||||
UI.Button = function(text, callback, parent)
|
||||
local widget = UI.createWidget("BotButton", parent)
|
||||
widget:setText(text)
|
||||
widget.onClick = callback
|
||||
return widget
|
||||
end
|
||||
|
||||
|
||||
UI.Config = function(parent)
|
||||
return UI.createWidget("BotConfig", parent)
|
||||
end
|
||||
@ -11,8 +19,11 @@ end
|
||||
-- call :setItems(table) to set items, call :getItems() to get them
|
||||
-- unique if true, won't allow duplicates
|
||||
-- callback (can be nil) gets table with new item list, eg: {{id=2160, count=1}, {id=268, count=100}, {id=269, count=20}}
|
||||
UI.Container = function(callback, unique, parent)
|
||||
local widget = UI.createWidget("BotContainer", parent)
|
||||
UI.Container = function(callback, unique, parent, widget)
|
||||
if not widget then
|
||||
widget = UI.createWidget("BotContainer", parent)
|
||||
end
|
||||
|
||||
local oldItems = {}
|
||||
|
||||
local updateItems = function()
|
||||
@ -33,7 +44,7 @@ UI.Container = function(callback, unique, parent)
|
||||
|
||||
if somethingNew then
|
||||
oldItems = items
|
||||
callback(items)
|
||||
callback(widget, items)
|
||||
end
|
||||
|
||||
widget:setItems(items)
|
||||
@ -77,6 +88,183 @@ UI.Container = function(callback, unique, parent)
|
||||
return items
|
||||
end
|
||||
|
||||
widget:setItems({})
|
||||
|
||||
return widget
|
||||
end
|
||||
|
||||
UI.DualScrollPanel = function(params, callback, parent) -- callback = function(widget, newParams)
|
||||
--[[ params:
|
||||
on - bool,
|
||||
text - string,
|
||||
title - string,
|
||||
min - number,
|
||||
max - number,
|
||||
]]
|
||||
params.title = params.title or "title"
|
||||
params.text = params.text or ""
|
||||
params.min = params.min or 20
|
||||
params.max = params.max or 80
|
||||
|
||||
local widget = UI.createWidget('DualScrollPanel', parent)
|
||||
|
||||
widget.title:setOn(params.on)
|
||||
widget.title.onClick = function()
|
||||
params.on = not params.on
|
||||
widget.title:setOn(params.on)
|
||||
if callback then
|
||||
callback(widget, params)
|
||||
end
|
||||
end
|
||||
|
||||
widget.text:setText(params.text or "")
|
||||
widget.text.onTextChange = function(widget, text)
|
||||
params.text = text
|
||||
if callback then
|
||||
callback(widget, params)
|
||||
end
|
||||
end
|
||||
|
||||
local update = function(dontSignal)
|
||||
widget.title:setText("" .. params.min .. "% <= " .. params.title .. " <= " .. params.max .. "%")
|
||||
if callback and not dontSignal then
|
||||
callback(widget, params)
|
||||
end
|
||||
end
|
||||
|
||||
widget.scroll1:setValue(params.min)
|
||||
widget.scroll2:setValue(params.max)
|
||||
|
||||
widget.scroll1.onValueChange = function(scroll, value)
|
||||
params.min = value
|
||||
update()
|
||||
end
|
||||
widget.scroll2.onValueChange = function(scroll, value)
|
||||
params.max = value
|
||||
update()
|
||||
end
|
||||
update(true)
|
||||
end
|
||||
|
||||
UI.DualScrollItemPanel = function(params, callback, parent) -- callback = function(widget, newParams)
|
||||
--[[ params:
|
||||
on - bool,
|
||||
item - number,
|
||||
title - string,
|
||||
min - number,
|
||||
max - number,
|
||||
]]
|
||||
params.title = params.title or "title"
|
||||
params.item = params.item or 0
|
||||
params.min = params.min or 20
|
||||
params.max = params.max or 80
|
||||
|
||||
local widget = UI.createWidget('DualScrollItemPanel', parent)
|
||||
|
||||
widget.title:setOn(params.on)
|
||||
widget.title.onClick = function()
|
||||
params.on = not params.on
|
||||
widget.title:setOn(params.on)
|
||||
if callback then
|
||||
callback(widget, params)
|
||||
end
|
||||
end
|
||||
|
||||
widget.item:setItemId(params.item)
|
||||
widget.item.onItemChange = function()
|
||||
params.item = widget.item:getItemId()
|
||||
if callback then
|
||||
callback(widget, params)
|
||||
end
|
||||
end
|
||||
|
||||
local update = function(dontSignal)
|
||||
widget.title:setText("" .. params.min .. "% <= " .. params.title .. " <= " .. params.max .. "%")
|
||||
if callback and not dontSignal then
|
||||
callback(widget, params)
|
||||
end
|
||||
end
|
||||
|
||||
widget.scroll1:setValue(params.min)
|
||||
widget.scroll2:setValue(params.max)
|
||||
|
||||
widget.scroll1.onValueChange = function(scroll, value)
|
||||
params.min = value
|
||||
update()
|
||||
end
|
||||
widget.scroll2.onValueChange = function(scroll, value)
|
||||
params.max = value
|
||||
update()
|
||||
end
|
||||
update(true)
|
||||
end
|
||||
|
||||
UI.Label = function(text, parent)
|
||||
local label = UI.createWidget('BotLabel', parent)
|
||||
label:setText(text)
|
||||
return label
|
||||
end
|
||||
|
||||
UI.Separator = function(parent)
|
||||
local separator = UI.createWidget('BotSeparator', parent)
|
||||
return separator
|
||||
end
|
||||
|
||||
UI.TextEdit = function(text, callback, parent)
|
||||
local widget = UI.createWidget('BotTextEdit', parent)
|
||||
widget.onTextChange = callback
|
||||
widget:setText(text)
|
||||
return widget
|
||||
end
|
||||
|
||||
UI.TwoItemsAndSlotPanel = function(params, callback, parent)
|
||||
--[[ params:
|
||||
on - bool,
|
||||
title - string,
|
||||
item1 - number,
|
||||
item2 - number,
|
||||
slot - number,
|
||||
]]
|
||||
params.title = params.title or "title"
|
||||
params.item1 = params.item1 or 0
|
||||
params.item2 = params.item2 or 0
|
||||
params.slot = params.slot or 1
|
||||
|
||||
local widget = UI.createWidget("TwoItemsAndSlotPanel", parent)
|
||||
|
||||
widget.title:setText(params.title)
|
||||
widget.title:setOn(params.on)
|
||||
widget.title.onClick = function()
|
||||
params.on = not params.on
|
||||
widget.title:setOn(params.on)
|
||||
if callback then
|
||||
callback(widget, params)
|
||||
end
|
||||
end
|
||||
|
||||
widget.slot:setCurrentIndex(params.slot)
|
||||
widget.slot.onOptionChange = function()
|
||||
params.slot = widget.slot.currentIndex
|
||||
if callback then
|
||||
callback(widget, params)
|
||||
end
|
||||
end
|
||||
|
||||
widget.item1:setItemId(params.item1)
|
||||
widget.item1.onItemChange = function()
|
||||
params.item1 = widget.item1:getItemId()
|
||||
if callback then
|
||||
callback(widget, params)
|
||||
end
|
||||
end
|
||||
|
||||
widget.item2:setItemId(params.item2)
|
||||
widget.item2.onItemChange = function()
|
||||
params.item2 = widget.item2:getItemId()
|
||||
if callback then
|
||||
callback(widget, params)
|
||||
end
|
||||
end
|
||||
|
||||
return widget
|
||||
end
|
||||
|
@ -1,5 +1,8 @@
|
||||
local context = G.botContext
|
||||
|
||||
-- DO NOT USE THIS CODE.
|
||||
-- IT'S ONLY HERE FOR BACKWARD COMPATIBILITY, MAY BE REMOVED IN THE FUTURE
|
||||
|
||||
context.createWidget = function(name, parent)
|
||||
if parent == nil then
|
||||
parent = context.panel
|
||||
@ -32,11 +35,11 @@ context.addTab = function(name)
|
||||
return tab.tabPanel.content
|
||||
end
|
||||
|
||||
context.tabs:setOn(true)
|
||||
local smallTabs = #(context.tabs.tabs) >= 5
|
||||
local newTab = context.tabs:addTab(name, g_ui.createWidget('BotPanel')).tabPanel.content
|
||||
if #(context.tabs.tabs) > 5 then
|
||||
context.tabs:setOn(true)
|
||||
if smallTabs then
|
||||
for k,tab in pairs(context.tabs.tabs) do
|
||||
tab:setPadding(3)
|
||||
tab:setFont('small-9px')
|
||||
end
|
||||
end
|
||||
|
3
modules/game_bot/panels/DONT_USE_PANELS.txt
Normal file
3
modules/game_bot/panels/DONT_USE_PANELS.txt
Normal file
@ -0,0 +1,3 @@
|
||||
DONT USE PANELS
|
||||
THEY ONLY HERE FOR BACKWARD COMPATIBILITY
|
||||
MAY BE REMOVED IN THE FUTURE
|
@ -581,7 +581,7 @@ Panel
|
||||
newConfig = "name:" .. configName .. "\n"
|
||||
end
|
||||
for monster, config in pairs(monsters) do
|
||||
newConfig = newConfig .. "\n" .. monster .. ":" .. json.encode(config) .. "\n"
|
||||
newConfig = newConfig .. "\n" .. monster .. ":" .. json.encode(config, 2) .. "\n"
|
||||
end
|
||||
|
||||
context.storage.attacking.configs[context.storage.attacking.activeConfig] = newConfig
|
||||
|
@ -1,6 +1,8 @@
|
||||
BotConfig < Panel
|
||||
id: botConfig
|
||||
height: 45
|
||||
margin-left: 2
|
||||
margin-right: 2
|
||||
|
||||
ComboBox
|
||||
id: list
|
||||
@ -24,26 +26,29 @@ BotConfig < Panel
|
||||
color: #FF0000
|
||||
|
||||
Button
|
||||
margin-top: 1
|
||||
margin-top: 2
|
||||
id: add
|
||||
anchors.top: prev.bottom
|
||||
anchors.left: parent.left
|
||||
text: Add
|
||||
width: 56
|
||||
height: 20
|
||||
|
||||
height: 18
|
||||
text-offet: 0 2
|
||||
|
||||
Button
|
||||
id: edit
|
||||
anchors.top: prev.top
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
text: Edit
|
||||
width: 56
|
||||
height: 20
|
||||
|
||||
height: 18
|
||||
text-offet: 0 2
|
||||
|
||||
Button
|
||||
id: remove
|
||||
anchors.top: prev.top
|
||||
anchors.right: parent.right
|
||||
text: Remove
|
||||
width: 56
|
||||
height: 20
|
||||
height: 18
|
||||
text-offet: 0 2
|
@ -1,6 +1,6 @@
|
||||
DualScrollPanel < Panel
|
||||
height: 55
|
||||
margin-top: 2
|
||||
height: 51
|
||||
margin-top: 3
|
||||
|
||||
SmallBotSwitch
|
||||
id: title
|
||||
@ -36,6 +36,8 @@ DualScrollPanel < Panel
|
||||
anchors.right: parent.right
|
||||
anchors.top: scroll1.bottom
|
||||
margin-top: 3
|
||||
margin-left: 2
|
||||
margin-right: 1
|
||||
|
||||
SingleScrollItemPanel < Panel
|
||||
height: 45
|
||||
@ -66,8 +68,8 @@ SingleScrollItemPanel < Panel
|
||||
step: 1
|
||||
|
||||
DualScrollItemPanel < Panel
|
||||
height: 37
|
||||
margin-top: 2
|
||||
height: 33
|
||||
margin-top: 3
|
||||
|
||||
BotItem
|
||||
id: item
|
||||
@ -197,23 +199,26 @@ ItemAndSlotPanel < Panel
|
||||
height: 20
|
||||
|
||||
TwoItemsAndSlotPanel < Panel
|
||||
height: 37
|
||||
height: 35
|
||||
margin-top: 4
|
||||
|
||||
BotItem
|
||||
id: item1
|
||||
anchors.left: parent.left
|
||||
anchors.top: parent.top
|
||||
margin-top: 1
|
||||
|
||||
BotItem
|
||||
id: item2
|
||||
anchors.left: prev.right
|
||||
anchors.top: prev.top
|
||||
margin-left: 1
|
||||
|
||||
SmallBotSwitch
|
||||
id: title
|
||||
anchors.left: prev.right
|
||||
anchors.right: parent.right
|
||||
anchors.top: prev.top
|
||||
anchors.top: parent.top
|
||||
text-align: center
|
||||
margin-left: 2
|
||||
margin-top: 0
|
||||
|
@ -10,6 +10,7 @@ cooldownPanel = nil
|
||||
lastPlayer = nil
|
||||
|
||||
cooldown = {}
|
||||
cooldowns = {}
|
||||
groupCooldown = {}
|
||||
|
||||
function init()
|
||||
@ -43,6 +44,11 @@ function terminate()
|
||||
disconnect(g_game, { onGameStart = online,
|
||||
onSpellGroupCooldown = onSpellGroupCooldown,
|
||||
onSpellCooldown = onSpellCooldown })
|
||||
|
||||
for key, val in pairs(cooldowns) do
|
||||
removeCooldown(key)
|
||||
end
|
||||
cooldowns = {}
|
||||
|
||||
cooldownWindow:destroy()
|
||||
cooldownButton:destroy()
|
||||
@ -110,6 +116,7 @@ function removeCooldown(progressRect)
|
||||
progressRect.icon:destroy()
|
||||
progressRect.icon = nil
|
||||
end
|
||||
cooldowns[progressRect] = nil
|
||||
progressRect = nil
|
||||
end
|
||||
|
||||
@ -125,6 +132,7 @@ function turnOffCooldown(progressRect)
|
||||
particle:fill('parent')
|
||||
scheduleEvent(function() particle:destroy() end, 1000) -- hack until onEffectEnd]]
|
||||
|
||||
cooldowns[progressRect] = nil
|
||||
progressRect = nil
|
||||
end
|
||||
|
||||
@ -145,6 +153,7 @@ function updateCooldown(progressRect, duration)
|
||||
removeEvent(progressRect.event)
|
||||
|
||||
progressRect.event = scheduleEvent(function()
|
||||
if not progressRect.callback then return end
|
||||
progressRect.callback[ProgressCallback.update]()
|
||||
end, 100)
|
||||
else
|
||||
@ -187,6 +196,7 @@ function onSpellCooldown(iconId, duration)
|
||||
end
|
||||
initCooldown(progressRect, updateFunc, finishFunc)
|
||||
cooldown[iconId] = true
|
||||
cooldowns[progressRect] = true
|
||||
end
|
||||
|
||||
function onSpellGroupCooldown(groupId, duration)
|
||||
|
@ -38,7 +38,13 @@ function show(itemWidget)
|
||||
itemWidget:setItem(Item.create(window.item:getItemId(), window.item:getItemCount()))
|
||||
destroy()
|
||||
end
|
||||
|
||||
local clearFunc = function()
|
||||
window.item:setItemId(0)
|
||||
window.item:setItemCount(0)
|
||||
doneFunc()
|
||||
end
|
||||
|
||||
window.clearButton.onClick = clearFunc
|
||||
window.okButton.onClick = doneFunc
|
||||
window.cancelButton.onClick = destroy
|
||||
window.onEnter = doneFunc
|
||||
|
@ -49,6 +49,13 @@ ItemSelectorWindow < MainWindow
|
||||
text-align: center
|
||||
!text: tr("Count / SubType")
|
||||
|
||||
Button
|
||||
id: clearButton
|
||||
!text: tr('Clear')
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: parent.left
|
||||
width: 60
|
||||
|
||||
Button
|
||||
id: okButton
|
||||
!text: tr('Ok')
|
||||
|
@ -1034,6 +1034,9 @@ function Market.decrementAmount()
|
||||
end
|
||||
|
||||
function Market.updateCurrentItems()
|
||||
if not categoryList or not categoryList:getCurrentOption() then
|
||||
return
|
||||
end
|
||||
local id = getMarketCategoryId(categoryList:getCurrentOption().text)
|
||||
if id == MarketCategory.MetaWeapons then
|
||||
id = getMarketCategoryId(subCategoryList:getCurrentOption().text)
|
||||
|
@ -28,6 +28,7 @@ function show(text, options, callback) -- callback = function(newText)
|
||||
multiline = true / false
|
||||
width = number
|
||||
validation = text (regex)
|
||||
range = {number, number}
|
||||
examples = {{name, text}, {name, text}}
|
||||
]]--
|
||||
if type(text) == 'userdata' then
|
||||
@ -63,10 +64,13 @@ function show(text, options, callback) -- callback = function(newText)
|
||||
end
|
||||
-- functions
|
||||
local validate = function(text)
|
||||
if type(options.validation) ~= 'string' or options.validation:len() == 0 then
|
||||
return true
|
||||
if type(options.range) == 'table' then
|
||||
local value = tonumber(text)
|
||||
return value >= options.range[1] and value <= options.range[2]
|
||||
elseif type(options.validation) == 'string' and options.validation:len() > 0 then
|
||||
return #regexMatch(text, options.validation) == 1
|
||||
end
|
||||
return #regexMatch(text, options.validation) == 1
|
||||
return true
|
||||
end
|
||||
local destroy = function()
|
||||
window:destroy()
|
||||
@ -109,7 +113,7 @@ function show(text, options, callback) -- callback = function(newText)
|
||||
window.text:setCursorPos(-1)
|
||||
end
|
||||
end
|
||||
if type(options.validation) == 'string' and options.validation:len() > 0 then
|
||||
if type(options.range) == 'table' or (type(options.validation) == 'string' and options.validation:len() > 0) then
|
||||
window.buttons.ok:disable()
|
||||
window.text.onTextChange = function(widget, text)
|
||||
if validate(text) then
|
||||
|
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.
BIN
pdb/pdb.7z
BIN
pdb/pdb.7z
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user