Bot update - waypoints, attacking, looting

This commit is contained in:
OTCv8 2019-11-02 08:43:11 +01:00
parent 62ff2b1cf5
commit 6dd9a54749
15 changed files with 1640 additions and 63 deletions

View File

@ -42,7 +42,7 @@ function init()
connect(Creature, {
onAppear = botCreatureAppear,
onDisappear =botCreatureDisappear,
onDisappear = botCreatureDisappear,
onPositionChange = botCreaturePositionChange,
onHealthPercentChange = botCraetureHealthPercentChange
})
@ -50,6 +50,9 @@ function init()
onPositionChange = botCreaturePositionChange,
onHealthPercentChange = botCraetureHealthPercentChange
})
connect(Container, { onOpen = botContainerOpen,
onClose = botContainerClose,
onUpdateItem = botContainerUpdateItem })
botConfigFile = g_configs.create("/bot.otml")
local config = botConfigFile:get("config")
@ -165,6 +168,9 @@ function terminate()
onPositionChange = botCreaturePositionChange,
onHealthPercentChange = botCraetureHealthPercentChange
})
disconnect(Container, { onOpen = botContainerOpen,
onClose = botContainerClose,
onUpdateItem = botContainerUpdateItem })
removeEvent(executeEvent)
removeEvent(checkMsgsEvent)
@ -324,6 +330,12 @@ function clearConfig()
botMessages:destroyChildren()
botMessages:updateLayout()
for i, widget in pairs(g_ui.getRootWidget():getChildren()) do
if widget.botWidget then
widget:destroy()
end
end
end
function refreshConfig()
@ -479,3 +491,18 @@ function botOnUseWith(pos, itemId, target, subType)
if compiledConfig == nil then return false end
safeBotCall(function() compiledConfig.callbacks.onUseWith(pos, itemId, target, subType) end)
end
function botContainerOpen(container, previousContainer)
if compiledConfig == nil then return false end
safeBotCall(function() compiledConfig.callbacks.onContainerOpen(container, previousContainer) end)
end
function botContainerClose(container)
if compiledConfig == nil then return false end
safeBotCall(function() compiledConfig.callbacks.onContainerClose(container) end)
end
function botContainerUpdateItem(container, slot, item)
if compiledConfig == nil then return false end
safeBotCall(function() compiledConfig.callbacks.onContainerUpdateItem(container, slot, item) end)
end

View File

@ -15,16 +15,15 @@ BotLabel < Label
text-align: center
text-wrap: true
BotItem < UIItem
BotItem < Item
virtual: true
size: 32 32
border: 1 black
&selectable: true
BotTextEdit < TextEdit
@onClick: modules.game_textedit.show(self)
text-align: center
multiline: false
focusable: false
BotSeparator < HorizontalSeparator
margin-top: 5
@ -40,8 +39,7 @@ CaveBotLabel < Label
focusable: true
$focus:
background-color: #00000033
color: #ffffff
background-color: #00000055
MiniWindow
id: botWindow

View File

@ -15,22 +15,28 @@ local battleTab = addTab("Battle")
local caveTab = addTab("Cave")
local toolsTab = addTab("Tools")
Panels.Health(battleTab)
Panels.HealthItem(battleTab)
Panels.ManaItem(battleTab)
Panels.AttackSpell(battleTab)
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)
--#macros
addSeparator("sep1")
local helloLabel = addLabel("helloLabel", "")
macro(1000, "example macro (time)", nil, function()
helloLabel:setText("Time from start: " .. now)
end)
macro(1000, "this macro does nothing", nil, function()
macro(1000, "this macro does nothing", "f7", function()
end, toolsTab)
@ -40,7 +46,7 @@ hotkey("f5", "example hotkey", function()
info("Wow, you clicked f5 hotkey")
end)
singlehotkey("ctrl+f6", "example hotkey2", function()
singlehotkey("ctrl+f6", "singlehotkey", function()
info("Wow, you clicked f6 singlehotkey")
end)

View File

@ -25,7 +25,10 @@ function executeBot(config, storage, tabs, msgCallback, saveConfigCallback)
onCreaturePositionChange = {},
onCreatureHealthPercentChange = {},
onUse = {},
onUseWith = {}
onUseWith = {},
onContainerOpen = {},
onContainerClose = {},
onContainerUpdateItem = {}
}
-- basic functions & classes
@ -48,8 +51,13 @@ function executeBot(config, storage, tabs, msgCallback, saveConfigCallback)
context.g_game = g_game
context.g_map = g_map
context.g_ui = g_ui
context.g_platform = g_platform
context.g_sounds = g_sounds
context.g_window = g_window
context.g_mouse = g_mouse
context.StaticText = StaticText
context.Position = Position
context.Config = Config
context.HTTP = HTTP
-- log functions
@ -80,14 +88,24 @@ function executeBot(config, storage, tabs, msgCallback, saveConfigCallback)
for i, macro in ipairs(context._macros) do
if macro.lastExecution + macro.timeout <= context.now and (macro.name == nil or macro.name:len() < 1 or context.storage._macros[macro.name]) then
if macro.callback() then
macro.lastExecution = context.now
local status, result = pcall(function()
if macro.callback() then
macro.lastExecution = context.now
end
end)
if not status then
context.error("Macro: " .. macro.name .. " execution error: " .. result)
end
end
end
while #context._scheduler > 0 and context._scheduler[1].execution <= g_clock.millis() do
context._scheduler[1].callback()
local status, result = pcall(function()
context._scheduler[1].callback()
end)
if not status then
context.error("Schedule execution error: " .. result)
end
table.remove(context._scheduler, 1)
end
end,
@ -161,7 +179,7 @@ function executeBot(config, storage, tabs, msgCallback, saveConfigCallback)
onCreatureDisappear = function(creature)
for i, callback in ipairs(context._callbacks.onCreatureDisappear) do
callback(creature)
end
end
end,
onCreaturePositionChange = function(creature, newPos, oldPos)
for i, callback in ipairs(context._callbacks.onCreaturePositionChange) do
@ -182,7 +200,22 @@ function executeBot(config, storage, tabs, msgCallback, saveConfigCallback)
for i, callback in ipairs(context._callbacks.onUseWith) do
callback(pos, itemId, target, subType)
end
end
end,
onContainerOpen = function(container, previousContainer)
for i, callback in ipairs(context._callbacks.onContainerOpen) do
callback(container, previousContainer)
end
end,
onContainerClose = function(container)
for i, callback in ipairs(context._callbacks.onContainerClose) do
callback(container)
end
end,
onContainerUpdateItem = function(container, slot, item)
for i, callback in ipairs(context._callbacks.onContainerUpdateItem) do
callback(container, slot, item)
end
end
}
}
end

View File

@ -79,8 +79,22 @@ context.onUseWith = function(callback)
return context.callback("onUseWith", callback)
end
-- onContainerOpen -- callback = function(container, previousContainer)
context.onContainerOpen = function(callback)
return context.callback("onContainerOpen", callback)
end
-- custom callbacks
-- onContainerUpdateItem -- callback = function(container)
context.onContainerClose = function(callback)
return context.callback("onContainerClose", callback)
end
-- onContainerUpdateItem -- callback = function(container, slot, item)
context.onContainerUpdateItem = function(callback)
return context.callback("onContainerUpdateItem", callback)
end
-- CUSTOM CALLBACKS
-- listen(name, callback) -- callback = function(text, channelId, pos)
context.listen = function(name, callback)

View File

@ -36,7 +36,7 @@ context.macro = function(timeout, name, hotkey, callback, parent)
local switch = nil
if name:len() > 0 then
if context.storage._macros[name] == nil then
context.storage._macros[name] = true
context.storage._macros[name] = false
end
switch = context._addMacroSwitch(name, hotkey, parent)
end

View File

@ -10,6 +10,19 @@ context.getSpectators = function(multifloor)
return g_map.getSpectators(context.player:getPosition(), multifloor)
end
context.getCreatureById = function(id, multifloor)
if type(id) ~= 'number' then return nil end
if multifloor ~= true then
multifloor = false
end
for i, spec in ipairs(g_map.getSpectators(context.player:getPosition(), multifloor)) do
if spec:getId() == id then
return spec
end
end
return nil
end
context.getCreatureByName = function(name, multifloor)
if not name then return nil end
name = name:lower()
@ -70,6 +83,10 @@ context.autoWalk = function(destination, maxDist, ignoreFields, ignoreCreatures)
if #path < 1 then
return false
end
g_game.autoWalk(path, context.player:getPosition())
if g_game.getFeature(GameNewWalking) then
g_game.autoWalk(path, context.player:getPosition())
else
g_game.autoWalk(path, {x=0,y=0,z=0})
end
return true
end

View File

@ -5,6 +5,7 @@ context.setupUI = function(otml, parent)
parent = context.panel
end
local widget = g_ui.loadUIFromString(otml, parent)
widget.botWidget = true
return widget
end

View File

@ -1,8 +1,334 @@
local context = G.botContext
local Panels = context.Panels
Panels.MonsterEditor = function(monster, config, callback, parent)
local otherWindow = g_ui.getRootWidget():getChildById('monsterEditor')
if otherWindow then
otherWindow:destory()
end
local window = context.setupUI([[
MainWindow
id: monsterEditor
size: 400 300
!text: tr("Edit monster")
Label
id: info
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
text-align: center
text: Use monster name * for any other monster not on the list
TextEdit
id: name
anchors.top: prev.bottom
anchors.left: parent.left
anchors.right: parent.right
margin-left: 100
margin-top: 5
multiline: false
Label
anchors.verticalCenter: prev.verticalCenter
anchors.left: parent.left
text: Monster name:
Label
id: priorityText
anchors.left: parent.left
anchors.right: parent.horizontalCenter
anchors.top: prev.bottom
margin-right: 10
margin-top: 10
text: Priority
text-align: center
HorizontalScrollBar
id: priority
anchors.left: prev.left
anchors.right: prev.right
anchors.top: prev.bottom
margin-top: 5
minimum: 0
maximum: 10
step: 1
Label
id: dangerText
anchors.left: parent.left
anchors.right: parent.horizontalCenter
anchors.top: prev.bottom
margin-right: 10
margin-top: 10
text: Danger
text-align: center
HorizontalScrollBar
id: danger
anchors.left: prev.left
anchors.right: prev.right
anchors.top: prev.bottom
margin-top: 5
minimum: 0
maximum: 10
step: 1
Label
id: distanceText
anchors.left: parent.left
anchors.right: parent.horizontalCenter
anchors.top: prev.bottom
margin-right: 10
margin-top: 10
text: Distance
text-align: center
HorizontalScrollBar
id: distance
anchors.left: prev.left
anchors.right: prev.right
anchors.top: prev.bottom
margin-top: 5
minimum: 0
maximum: 5
step: 1
Label
id: minHealthText
anchors.left: parent.left
anchors.right: parent.horizontalCenter
anchors.top: prev.bottom
margin-right: 10
margin-top: 10
text: Minimum Health
text-align: center
HorizontalScrollBar
id: minHealth
anchors.left: prev.left
anchors.right: prev.right
anchors.top: prev.bottom
margin-top: 5
minimum: 0
maximum: 100
step: 1
Label
id: maxHealthText
anchors.left: parent.left
anchors.right: parent.horizontalCenter
anchors.top: prev.bottom
margin-right: 10
margin-top: 10
text: Maximum Health
text-align: center
HorizontalScrollBar
id: maxHealth
anchors.left: prev.left
anchors.right: prev.right
anchors.top: prev.bottom
margin-top: 5
minimum: 0
maximum: 100
step: 1
BotSwitch
id: attack
anchors.left: parent.horizontalCenter
anchors.top: name.bottom
margin-left: 10
margin-top: 10
width: 55
text: Attack
BotSwitch
id: ignore
anchors.left: prev.right
anchors.top: name.bottom
margin-left: 5
margin-top: 10
width: 55
text: Ignore
BotSwitch
id: avoid
anchors.left: prev.right
anchors.top: name.bottom
margin-left: 5
margin-top: 10
width: 55
text: Avoid
BotSwitch
id: keepDistance
anchors.left: parent.horizontalCenter
anchors.right: parent.right
anchors.top: prev.bottom
margin-left: 10
margin-top: 10
text: Keep distance
BotSwitch
id: avoidAttacks
anchors.left: parent.horizontalCenter
anchors.right: parent.right
anchors.top: prev.bottom
margin-left: 10
margin-top: 10
text: Avoid monster attacks
BotSwitch
id: chase
anchors.left: parent.horizontalCenter
anchors.right: parent.right
anchors.top: prev.bottom
margin-left: 10
margin-top: 10
text: Chase when running away
BotSwitch
id: loot
anchors.left: parent.horizontalCenter
anchors.right: parent.right
anchors.top: prev.bottom
margin-left: 10
margin-top: 10
text: Loot corpse
Button
id: okButton
!text: tr('Ok')
anchors.bottom: parent.bottom
anchors.right: next.left
margin-right: 10
width: 60
Button
id: cancelButton
!text: tr('Cancel')
anchors.bottom: parent.bottom
anchors.right: parent.right
width: 60
]], g_ui.getRootWidget())
local destroy = function()
window:destroy()
end
local doneFunc = function()
local monster = window.name:getText()
local config = {
priority = window.priority:getValue(),
danger = window.danger:getValue(),
distance = window.distance:getValue(),
minHealth = window.minHealth:getValue(),
maxHealth = window.maxHealth:getValue(),
attack = window.attack:isOn(),
ignore = window.ignore:isOn(),
avoid = window.avoid:isOn(),
keepDistance = window.keepDistance:isOn(),
avoidAttacks = window.avoidAttacks:isOn(),
chase = window.chase:isOn(),
loot = window.loot:isOn()
}
destroy()
callback(monster, config)
end
window.okButton.onClick = doneFunc
window.cancelButton.onClick = destroy
window.onEnter = doneFunc
window.onEscape = destroy
window.priority.onValueChange = function(scroll, value)
window.priorityText:setText("Priority: " .. value)
end
window.danger.onValueChange = function(scroll, value)
window.dangerText:setText("Danger: " .. value)
end
window.distance.onValueChange = function(scroll, value)
window.distanceText:setText("Distance: " .. value)
end
window.minHealth.onValueChange = function(scroll, value)
window.minHealthText:setText("Minimum health: " .. value .. "%")
end
window.maxHealth.onValueChange = function(scroll, value)
window.maxHealthText:setText("Maximum health: " .. value .. "%")
end
window.priority:setValue(config.priority or 1)
window.danger:setValue(config.danger or 1)
window.distance:setValue(config.distance or 1)
window.minHealth:setValue(1) -- to force onValueChange update
window.maxHealth:setValue(1) -- to force onValueChange update
window.minHealth:setValue(config.minHealth or 0)
window.maxHealth:setValue(config.maxHealth or 100)
window.attack.onClick = function(widget)
if widget:isOn() then
return
end
widget:setOn(true)
window.ignore:setOn(false)
window.avoid:setOn(false)
end
window.ignore.onClick = function(widget)
if widget:isOn() then
return
end
widget:setOn(true)
window.attack:setOn(false)
window.avoid:setOn(false)
end
window.avoid.onClick = function(widget)
if widget:isOn() then
return
end
widget:setOn(true)
window.attack:setOn(false)
window.ignore:setOn(false)
end
window.attack:setOn(config.attack)
window.ignore:setOn(config.ignore)
window.avoid:setOn(config.avoid)
if not window.attack:isOn() and not window.ignore:isOn() and not window.avoid:isOn() then
window.attack:setOn(true)
end
window.keepDistance.onClick = function(widget)
widget:setOn(not widget:isOn())
end
window.avoidAttacks.onClick = function(widget)
widget:setOn(not widget:isOn())
end
window.chase.onClick = function(widget)
widget:setOn(not widget:isOn())
end
window.loot.onClick = function(widget)
widget:setOn(not widget:isOn())
end
window.keepDistance:setOn(config.keepDistance)
window.avoidAttacks:setOn(config.avoidAttacks)
window.chase:setOn(config.chase)
window.loot:setOn(config.loot)
if config.loot == nil then
window.loot:setOn(true)
end
window.name:setText(monster)
window:show()
window:raise()
window:focus()
end
Panels.Attacking = function(parent)
context.setupUI([[
local ui = context.setupUI([[
Panel
id: attacking
height: 150
@ -12,8 +338,650 @@ Panel
anchors.left: parent.left
anchors.right: parent.right
text: Attacking
]], parent)
ComboBox
id: config
anchors.top: prev.bottom
anchors.left: parent.left
margin-top: 5
text-offset: 3 0
width: 130
Button
id: enableButton
anchors.top: prev.top
anchors.left: prev.right
anchors.right: parent.right
margin-left: 5
Button
margin-top: 1
id: add
anchors.top: prev.bottom
anchors.left: parent.left
text: Add
width: 60
height: 20
Button
id: edit
anchors.top: prev.top
anchors.horizontalCenter: parent.horizontalCenter
text: Edit
width: 60
height: 20
Button
id: remove
anchors.top: prev.top
anchors.right: parent.right
text: Remove
width: 60
height: 20
TextList
id: list
anchors.top: prev.bottom
anchors.left: parent.left
anchors.right: parent.right
vertical-scrollbar: listScrollbar
margin-right: 15
margin-top: 2
height: 60
focusable: false
auto-focus: first
VerticalScrollBar
id: listScrollbar
anchors.top: prev.top
anchors.bottom: prev.bottom
anchors.right: parent.right
pixels-scroll: true
step: 5
Button
margin-top: 2
id: mAdd
anchors.top: prev.bottom
anchors.left: parent.left
text: Add
width: 60
height: 20
Button
id: mEdit
anchors.top: prev.top
anchors.horizontalCenter: parent.horizontalCenter
text: Edit
width: 60
height: 20
Button
id: mRemove
anchors.top: prev.top
anchors.right: parent.right
text: Remove
width: 60
height: 20
]], parent)
if type(context.storage.attacking) ~= "table" then
context.storage.attacking = {}
end
if type(context.storage.attacking.configs) ~= "table" then
context.storage.attacking.configs = {}
end
local getConfigName = function(config)
local matches = regexMatch(config, [[name:\s*([^\n]*)$]])
if matches[1] and matches[1][2] then
return matches[1][2]:trim()
end
return nil
end
local commands = {}
local monsters = {}
local configName = nil
local refreshConfig = nil -- declared later
local createNewConfig = function()
if not context.storage.attacking.activeConfig or not context.storage.attacking.configs[context.storage.attacking.activeConfig] then
return
end
local newConfig = ""
if configName ~= nil then
newConfig = "name:" .. configName .. "\n"
end
for monster, config in pairs(monsters) do
newConfig = newConfig .. "\n" .. monster .. ":" .. json.encode(config) .. "\n"
end
context.storage.attacking.configs[context.storage.attacking.activeConfig] = newConfig
refreshConfig()
end
local parseConfig = function(config)
commands = {}
monsters = {}
configName = nil
local matches = regexMatch(config, [[([^:^\n]+)(:?)([^\n]*)]])
for i=1,#matches do
local command = matches[i][2]
local validation = (matches[i][3] == ":")
local text = matches[i][4]
if validation then
table.insert(commands, {command=command:lower(), text=text})
elseif #commands > 0 then
commands[#commands].text = commands[#commands].text .. "\n" .. matches[i][1]
end
end
local labels = {}
for i, command in ipairs(commands) do
if commands[i].command == "name" then
configName = commands[i].text
else
local status, result = pcall(function() return json.decode(command.text) end)
if not status then
context.error("Invalid monster config: " .. commands[i].command .. ", error: " .. result)
print(command.text)
else
monsters[commands[i].command] = result
table.insert(labels, commands[i].command)
end
end
end
table.sort(labels)
for i, text in ipairs(labels) do
local label = g_ui.createWidget("CaveBotLabel", ui.list)
label:setText(text)
end
end
local ignoreOnOptionChange = true
refreshConfig = function(scrollDown)
ignoreOnOptionChange = true
if context.storage.attacking.enabled then
ui.enableButton:setText("On")
ui.enableButton:setColor('#00AA00FF')
else
ui.enableButton:setText("Off")
ui.enableButton:setColor('#FF0000FF')
end
ui.config:clear()
for i, config in ipairs(context.storage.attacking.configs) do
local name = getConfigName(config)
if not name then
name = "Unnamed config"
end
ui.config:addOption(name)
end
if not context.storage.attacking.activeConfig and #context.storage.attacking.configs > 0 then
context.storage.attacking.activeConfig = 1
end
ui.list:destroyChildren()
if context.storage.attacking.activeConfig and context.storage.attacking.configs[context.storage.attacking.activeConfig] then
ui.config:setCurrentIndex(context.storage.attacking.activeConfig)
parseConfig(context.storage.attacking.configs[context.storage.attacking.activeConfig])
end
context.saveConfig()
if scrollDown and ui.list:getLastChild() then
ui.list:focusChild(ui.list:getLastChild())
end
ignoreOnOptionChange = false
end
ui.config.onOptionChange = function(widget)
if not ignoreOnOptionChange then
context.storage.attacking.activeConfig = widget.currentIndex
refreshConfig()
end
end
ui.enableButton.onClick = function()
if not context.storage.attacking.activeConfig or not context.storage.attacking.configs[context.storage.attacking.activeConfig] then
return
end
context.storage.attacking.enabled = not context.storage.attacking.enabled
refreshConfig()
end
ui.add.onClick = function()
modules.game_textedit.multilineEditor("Target list editor", "name:Config name", function(newText)
table.insert(context.storage.attacking.configs, newText)
context.storage.attacking.activeConfig = #context.storage.attacking.configs
refreshConfig()
end)
end
ui.edit.onClick = function()
if not context.storage.attacking.activeConfig or not context.storage.attacking.configs[context.storage.attacking.activeConfig] then
return
end
modules.game_textedit.multilineEditor("Target list editor", context.storage.attacking.configs[context.storage.attacking.activeConfig], function(newText)
context.storage.attacking.configs[context.storage.attacking.activeConfig] = newText
refreshConfig()
end)
end
ui.remove.onClick = function()
if not context.storage.attacking.activeConfig or not context.storage.attacking.configs[context.storage.attacking.activeConfig] then
return
end
context.storage.attacking.enabled = false
table.remove(context.storage.attacking.configs, context.storage.attacking.activeConfig)
context.storage.attacking.activeConfig = 0
refreshConfig()
end
ui.mAdd.onClick = function()
if not context.storage.attacking.activeConfig or not context.storage.attacking.configs[context.storage.attacking.activeConfig] then
return
end
Panels.MonsterEditor("", {}, function(name, config)
if name:len() > 0 then
monsters[name] = config
end
createNewConfig()
end, parent)
end
ui.mEdit.onClick = function()
if not context.storage.attacking.activeConfig or not context.storage.attacking.configs[context.storage.attacking.activeConfig] then
return
end
local monsterWidget = ui.list:getFocusedChild()
if not monsterWidget or not monsters[monsterWidget:getText()] then
return
end
Panels.MonsterEditor(monsterWidget:getText(), monsters[monsterWidget:getText()], function(name, config)
monsters[monsterWidget:getText()] = nil
if name:len() > 0 then
monsters[name] = config
end
createNewConfig()
end, parent)
end
ui.mRemove.onClick = function()
if not context.storage.attacking.activeConfig or not context.storage.attacking.configs[context.storage.attacking.activeConfig] then
return
end
local monsterWidget = ui.list:getFocusedChild()
if not monsterWidget or not monsters[monsterWidget:getText()] then
return
end
monsters[monsterWidget:getText()] = nil
createNewConfig()
end
refreshConfig()
local getMonsterConfig = function(monster)
if monsters[monster:getName():lower()] then
return monsters[monster:getName():lower()]
end
return monsters["*"]
end
local calculatePriority = function(monster)
local priority = 0
local config = getMonsterConfig(monster)
if not config or type(config.priority) ~= 'number' then
return -1
end
if not config.attack then
return -1
end
local distance = context.getDistanceBetween(context.player:getPosition(), monster:getPosition())
if distance > 10 then
return -1
end
local mpos = monster:getPosition()
local hasPath = false
for x=-1,1 do
for y=-1,1 do
local pathTo = context.findPath(context.player:getPosition(), {x=mpos.x-x, y=mpos.y-y, z=mpos.z}, 100, true, false)
if #pathTo > 0 then
hasPath = true
break
end
end
end
if distance > 2 and not hasPath then
return -1
end
if monster == g_game.getAttackingCreature() then
priority = priority + 10
end
if distance <= 4 then
priority = priority + 10
end
if distance <= 2 then
priority = priority + 20
end
if monster:getHealthPercent() <= 10 then
priority = priority + 10
end
if monster:getHealthPercent() <= 25 then
priority = priority + 10
end
if monster:getHealthPercent() <= 50 then
priority = priority + 10
end
if monster:getHealthPercent() <= 75 then
priority = priority + 10
end
priority = priority + config.priority * 10
return priority
end
local calculateMonsterDanger = function(monster)
local danger = 0
local config = getMonsterConfig(monster)
if not config or type(config.danger) ~= 'number' then
return danger
end
danger = danger + config.danger
return danger
end
local lastAttack = context.now
local lootContainers = {}
local lootTries = 0
local openContainerRequest = 0
local waitForLooting = 0
local goForLoot = function()
if #lootContainers == 0 or not context.storage.looting.enabled then
return false
end
local pos = context.player:getPosition()
table.sort(lootContainers, function(pos1, pos2)
local dist1 = math.max(math.abs(pos.x-pos1.x), math.abs(pos.y-pos1.y))
local dist2 = math.max(math.abs(pos.x-pos2.x), math.abs(pos.y-pos2.y))
return dist1 < dist2
end)
local cpos = lootContainers[1]
if cpos.z ~= pos.z then
table.remove(lootContainers, 1)
return true
end
if lootTries >= 5 then
lootTries = 0
table.remove(lootContainers, 1)
return true
end
local dist = math.max(math.abs(pos.x-cpos.x), math.abs(pos.y-cpos.y))
if dist <= 5 then
local tile = g_map.getTile(cpos)
if not tile then
table.remove(lootContainers, 1)
return true
end
local topItem = tile:getTopUseThing()
if not topItem:isContainer() then
table.remove(lootContainers, 1)
return true
end
if dist <= 1 then
lootTries = lootTries + 1
openContainerRequest = context.now
g_game.open(topItem)
waitForLooting = math.max(waitForLooting, context.now + 500)
return true
end
end
if dist <= 20 then
if context.player:isWalking() then
return true
end
lootTries = lootTries + 1
if context.autoWalk(cpos, 100 + dist * 2) then
return true
end
if context.autoWalk(cpos, 100 + dist * 2, true) then
return true
end
for i=1,5 do
local cpos2 = {x=cpos.x + math.random(-1, 1),y = cpos.y + math.random(-1, 1), z = cpos.z}
if context.autoWalk(cpos2, 100 + dist * 2) then
return true
end
end
-- try again, ignore field
for i=1,5 do
local cpos2 = {x=cpos.x + math.random(-1, 1),y = cpos.y + math.random(-1, 1), z = cpos.z}
if context.autoWalk(cpos2, 100 + dist * 2, true) then
return true
end
end
-- ignore fields and monsters
if context.autoWalk(cpos, 100 + dist * 2, true, true) then
return true
end
else
table.remove(lootContainers, 1)
return false
end
return true
end
context.onCreatureDisappear(function(creature)
if not creature:isMonster() then
return
end
local pos = context.player:getPosition()
local tpos = creature:getPosition()
if tpos.z ~= pos.z then
return
end
local config = getMonsterConfig(creature)
if not config or not config.loot then
return
end
local distance = math.max(math.abs(pos.x-tpos.x), math.abs(pos.y-tpos.y))
if distance > 6 then
return
end
local tile = g_map.getTile(tpos)
if not tile then
return
end
local topItem = tile:getTopUseThing()
if not topItem:isContainer() then
return
end
table.insert(lootContainers, tpos)
end)
context.onContainerOpen(function(container, prevContainer)
lootTries = 0
if not context.storage.attacking.enabled then
return
end
if openContainerRequest + 500 > context.now and #lootContainers > 0 then
waitForLooting = math.max(waitForLooting, context.now + 1000 + container:getItemsCount() * 100)
table.remove(lootContainers, 1)
end
if prevContainer then
container.autoLooting = prevContainer.autoLooting
else
container.autoLooting = (openContainerRequest + 3000 > context.now)
end
end)
context.macro(200, function()
if not context.storage.attacking.enabled then
return
end
local attacking = nil
local following = nil
local attackingCandidate = g_game.getAttackingCreature()
local followingCandidate = g_game.getFollowingCreature()
local spectators = context.getSpectators()
local monsters = {}
local danger = 0
for i, spec in ipairs(spectators) do
if attackingCandidate and attackingCandidate:getId() == spec:getId() then
attacking = spec
end
if followingCandidate and followingCandidate:getId() == spec:getId() then
following = spec
end
if spec:isMonster() then
danger = danger + calculateMonsterDanger(spec)
spec.attackingPriority = calculatePriority(spec)
table.insert(monsters, spec)
end
end
if following then
return
end
if waitForLooting > context.now then
return
end
if #monsters == 0 then
goForLoot()
return
end
table.sort(monsters, function(a, b)
return a.attackingPriority > b.attackingPriority
end)
local target = monsters[1]
if target.attackingPriority < 0 then
return
end
local pos = context.player:getPosition()
local tpos = target:getPosition()
local config = getMonsterConfig(target)
local offsetX = pos.x - tpos.x
local offsetY = pos.y - tpos.y
if target ~= attacking then
g_game.attack(target)
attacking = target
lastAttack = context.now
end
-- proceed attack
if lastAttack + 15000 < context.now then
-- stop and attack again, just in case
g_game.cancelAttack()
g_game.attack(target)
lastAttack = context.now
return
end
if danger < 8 then
-- low danger, go for loot first
if goForLoot() then
return
end
end
local distance = math.max(math.abs(offsetX), math.abs(offsetY))
if config.keepDistance then
if (distance == config.distance or distance == config.distance + 1) then
return
else
local bestDist = 10
local bestPos = pos
for i=1,5 do
local testPos = {x=pos.x + math.random(-3,3), y=pos.y + math.random(-3,3), z=pos.z}
local dist = math.abs(config.distance - math.max(math.abs(tpos.x - testPos.x), math.abs(tpos.y - testPos.y)))
if dist < bestDist then
local path = context.findPath(pos, testPos, 100, false, false)
if #path > 0 then
bestPos = testPos
bestDist = dist
end
end
end
if bestDist > 1 then
for i=1,10 do
local testPos = {x=pos.x + math.random(-4,4), y=pos.y + math.random(-4,4), z=pos.z}
local dist = math.abs(config.distance - math.max(math.abs(tpos.x - testPos.x), math.abs(tpos.y - testPos.y)))
if dist < bestDist then
local path = context.findPath(pos, testPos, 100, true, false)
if #path > 0 then
bestPos = testPos
bestDist = dist
end
end
end
end
if bestDist < 10 then
context.autoWalk(bestPos, 100, true, false)
context.delay(300)
end
end
return
end
if config.avoidAttacks and distance <= 1 then
if (offsetX == 0 and offsetY ~= 0) then
if context.player:canWalk(Directions.East) then
g_game.walk(Directions.East)
elseif context.player:canWalk(Directions.West) then
g_game.walk(Directions.West)
end
elseif (offsetX ~= 0 and offsetY == 0) then
if context.player:canWalk(Directions.North) then
g_game.walk(Directions.North)
elseif context.player:canWalk(Directions.South) then
g_game.walk(Directions.South)
end
end
end
if distance > 1 then
for x=-1,1 do
for y=-1,1 do
if context.autoWalk({x=tpos.x-x, y=tpos.y-y, z=tpos.z}, 100, true, false) then
return
end
end
end
if not context.autoWalk(tpos, 100, false, true) then
context.autoWalk(tpos, 100, true, true)
end
end
end)
end

View File

@ -17,7 +17,7 @@ end
Panels.ManaShield = function(parent)
context.macro(500, "Auto ManaShield", nil, function()
if not context.hasManaShield() then
if context.saySpell("utamo vita", 1000) then
if context.saySpell("utamo vita", 500) then
context.delay(5000)
end
end
@ -323,3 +323,16 @@ Panels.Turning = function(parent)
end
Panels.AntiIdle = Panels.Turning
Panels.AttackSpell = function(parent)
context.macro(500, "Auto attack spell", nil, function()
local target = g_game.getAttackingCreature()
if target and context.getCreatureById(target:getId()) and context.storage.autoAttackText:len() > 0 then
if context.saySpell(context.storage.autoAttackText, 1000) then
context.delay(1000)
end
end
end, parent)
context.addTextEdit("autoAttackText", context.storage.autoAttackText or "exori vis", function(widget, text)
context.storage.autoAttackText = text
end, parent)
end

View File

@ -1,20 +1,448 @@
local context = G.botContext
local Panels = context.Panels
Panels.Looting = function(parent)
context.setupUI([[
local ui = context.setupUI([[
Panel
id: looting
height: 150
height: 190
BotLabel
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
text: Looting
]], parent)
ComboBox
id: config
anchors.top: prev.bottom
anchors.left: parent.left
margin-top: 5
text-offset: 3 0
width: 130
Button
id: enableButton
anchors.top: prev.top
anchors.left: prev.right
anchors.right: parent.right
margin-left: 5
Button
margin-top: 1
id: add
anchors.top: prev.bottom
anchors.left: parent.left
text: Add
width: 60
height: 20
Button
id: edit
anchors.top: prev.top
anchors.horizontalCenter: parent.horizontalCenter
text: Edit
width: 60
height: 20
Button
id: remove
anchors.top: prev.top
anchors.right: parent.right
text: Remove
width: 60
height: 20
ScrollablePanel
id: items
anchors.top: prev.bottom
anchors.right: parent.right
anchors.left: parent.left
vertical-scrollbar: scrollBar
margin-right: 10
margin-top: 2
height: 70
layout:
type: grid
cell-size: 34 34
flow: true
VerticalScrollBar
id: scrollBar
anchors.top: prev.top
anchors.bottom: prev.bottom
anchors.right: parent.right
step: 10
pixels-scroll: true
BotLabel
anchors.top: prev.bottom
anchors.left: parent.left
anchors.right: parent.right
margin-top: 4
text: Loot Containers
Panel
id: containers
anchors.top: prev.bottom
anchors.left: parent.left
anchors.right: parent.right
height: 33
margin-top: 2
BotItem
id: item1
anchors.top: parent.top
anchors.left: parent.left
margin-left: 3
BotItem
id: item2
anchors.top: prev.top
anchors.left: prev.right
margin-left: 2
BotItem
id: item3
anchors.top: prev.top
anchors.left: prev.right
margin-left: 2
BotItem
id: item4
anchors.top: prev.top
anchors.left: prev.right
margin-left: 2
BotItem
id: item5
anchors.top: prev.top
anchors.left: prev.right
margin-left: 2
]], parent)
local lootContainers = { ui.containers.item1, ui.containers.item2, ui.containers.item3, ui.containers.item4, ui.containers.item5 }
if type(context.storage.looting) ~= "table" then
context.storage.looting = {}
end
if type(context.storage.looting.configs) ~= "table" then
context.storage.looting.configs = {}
end
local getConfigName = function(config)
local matches = regexMatch(config, [[name:\s*([^\n]*)$]])
if matches[1] and matches[1][2] then
return matches[1][2]:trim()
end
return nil
end
local items = {}
local itemsByKey = {}
local containers = {}
local commands = {}
local refreshConfig = nil -- declared later
local createNewConfig = function(focusedWidget)
if not context.storage.looting.activeConfig or not context.storage.looting.configs[context.storage.looting.activeConfig] then
return
end
local tmpItems = {}
local tmpContainers = {}
local focusIndex = 0
local newConfig = ""
for i, text in ipairs(commands) do
newConfig = newConfig .. text .. "\n"
end
for i=1,ui.items:getChildCount() do
local widget = ui.items:getChildByIndex(i)
if widget and widget:getItemId() >= 100 then
if tmpItems[widget:getItemId()] == nil then
tmpItems[widget:getItemId()] = 1
newConfig = newConfig .. "\n" .. widget:getItemId()
end
end
if widget == focusedWidget then
focusIndex = i
end
end
for i, widget in ipairs(lootContainers) do
if widget:getItemId() >= 100 then
if tmpContainers[widget:getItemId()] == nil then
tmpContainers[widget:getItemId()] = 1 -- remove duplicates
newConfig = newConfig .. "\ncontainer:" .. widget:getItemId()
end
end
end
context.storage.looting.configs[context.storage.looting.activeConfig] = newConfig
refreshConfig(focusIndex)
end
local parseConfig = function(config)
items = {}
itemsByKey = {}
containers = {}
commands = {}
local matches = regexMatch(config, [[([^:^\n^\s]+)(:?)([^\n]*)]])
for i=1,#matches do
local command = matches[i][2]
local validation = (matches[i][3] == ":")
local text = matches[i][4]
local commandAsNumber = tonumber(command)
local textAsNumber = tonumber(text)
if commandAsNumber and commandAsNumber >= 100 then
table.insert(items, commandAsNumber)
itemsByKey[commandAsNumber] = 1
elseif command == "container" and validation and textAsNumber and textAsNumber >= 100 then
containers[textAsNumber] = 1
elseif validation then
table.insert(commands, command .. ":" .. text)
end
end
local itemsToShow = #items + 2
if itemsToShow % 5 ~= 0 then
itemsToShow = itemsToShow + 5 - itemsToShow % 5
end
if itemsToShow < 10 then
itemsToShow = 10
end
for i=1,itemsToShow do
local widget = g_ui.createWidget("BotItem", ui.items)
local itemId = 0
if i <= #items then
itemId = items[i]
end
widget:setItemId(itemId)
widget.onItemChange = createNewConfig
end
for i, widget in ipairs(lootContainers) do
widget:setItemId(0)
end
local containerIndex = 1
for containerId, i in pairs(containers) do
if lootContainers[containerIndex] then
lootContainers[containerIndex]:setItemId(containerId)
end
containerIndex = containerIndex + 1
end
for i, widget in ipairs(lootContainers) do
widget.onItemChange = createNewConfig
end
end
local ignoreOnOptionChange = true
refreshConfig = function(focusIndex)
ignoreOnOptionChange = true
if context.storage.looting.enabled then
ui.enableButton:setText("On")
ui.enableButton:setColor('#00AA00FF')
else
ui.enableButton:setText("Off")
ui.enableButton:setColor('#FF0000FF')
end
ui.config:clear()
for i, config in ipairs(context.storage.looting.configs) do
local name = getConfigName(config)
if not name then
name = "Unnamed config"
end
ui.config:addOption(name)
end
if not context.storage.looting.activeConfig and #context.storage.looting.configs > 0 then
context.storage.looting.activeConfig = 1
end
ui.items:destroyChildren()
for i, widget in ipairs(lootContainers) do
widget.onItemChange = nil
widget:setItemId(0)
widget:setItemCount(0)
end
if context.storage.looting.activeConfig and context.storage.looting.configs[context.storage.looting.activeConfig] then
ui.config:setCurrentIndex(context.storage.looting.activeConfig)
parseConfig(context.storage.looting.configs[context.storage.looting.activeConfig])
end
context.saveConfig()
if focusIndex and focusIndex > 0 and ui.items:getChildByIndex(focusIndex) then
ui.items:focusChild(ui.items:getChildByIndex(focusIndex))
end
ignoreOnOptionChange = false
end
ui.config.onOptionChange = function(widget)
if not ignoreOnOptionChange then
context.storage.looting.activeConfig = widget.currentIndex
refreshConfig()
end
end
ui.enableButton.onClick = function()
if not context.storage.looting.activeConfig or not context.storage.looting.configs[context.storage.looting.activeConfig] then
return
end
context.storage.looting.enabled = not context.storage.looting.enabled
refreshConfig()
end
ui.add.onClick = function()
modules.game_textedit.multilineEditor("Looting editor", "name:Config name", function(newText)
table.insert(context.storage.looting.configs, newText)
context.storage.looting.activeConfig = #context.storage.looting.configs
refreshConfig()
end)
end
ui.edit.onClick = function()
if not context.storage.looting.activeConfig or not context.storage.looting.configs[context.storage.looting.activeConfig] then
return
end
modules.game_textedit.multilineEditor("Looting editor", context.storage.looting.configs[context.storage.looting.activeConfig], function(newText)
context.storage.looting.configs[context.storage.looting.activeConfig] = newText
refreshConfig()
end)
end
ui.remove.onClick = function()
if not context.storage.looting.activeConfig or not context.storage.looting.configs[context.storage.looting.activeConfig] then
return
end
context.storage.looting.enabled = false
table.remove(context.storage.looting.configs, context.storage.looting.activeConfig)
context.storage.looting.activeConfig = 0
refreshConfig()
end
refreshConfig()
context.onContainerOpen(function(container, prevContainer)
if context.storage.attacking.enabled then
return
end
if prevContainer then
container.autoLooting = prevContainer.autoLooting
else
container.autoLooting = true
end
end)
context.macro(200, function()
if not context.storage.looting.enabled then
return
end
local candidates = {}
local lootContainersCandidates = {}
for containerId, container in pairs(g_game.getContainers()) do
local containerItem = container:getContainerItem()
if container.autoLooting and container:getItemsCount() > 0 and (not containerItem or containers[containerItem:getId()] == nil) then
table.insert(candidates, container)
elseif containerItem and containers[containerItem:getId()] ~= nil then
table.insert(lootContainersCandidates, container)
end
end
if #lootContainersCandidates == 0 then
for slot = InventorySlotFirst, InventorySlotLast do
local item = context.getInventoryItem(slot)
if item and item:isContainer() and containers[item:getId()] ~= nil then
table.insert(lootContainersCandidates, item)
end
end
if #lootContainersCandidates > 0 then
-- try to open inventory backpack
local target = lootContainersCandidates[math.random(1,#lootContainersCandidates)]
g_game.open(target, nil)
context.delay(200)
end
return
end
if #candidates == 0 then
return
end
local container = candidates[math.random(1,#candidates)]
local nextContainers = {}
local foundItem = nil
for i, item in ipairs(container:getItems()) do
if item:isContainer() then
table.insert(nextContainers, item)
elseif itemsByKey[item:getId()] ~= nil then
foundItem = item
break
end
end
-- found item to loot
if foundItem then
-- find backpack for it, first backpack with same items
for i, container in ipairs(lootContainersCandidates) do
if container:getItemsCount() < container:getCapacity() or foundItem:isStackable() then -- has space
for j, item in ipairs(container:getItems()) do
if item:getId() == foundItem:getId() then
if foundItem:isStackable() then
if item:getCount() ~= 100 then
g_game.move(foundItem, container:getSlotPosition(j), foundItem:getCount())
return
end
else
g_game.move(foundItem, container:getSlotPosition(container:getItemsCount()), foundItem:getCount())
return
end
end
end
end
end
-- now any backpack with empty slot
for i, container in ipairs(lootContainersCandidates) do
if container:getItemsCount() < container:getCapacity() then -- has space
g_game.move(foundItem, container:getSlotPosition(container:getItemsCount()), foundItem:getCount())
return
end
end
-- can't find backpack, try to open new
for i, container in ipairs(lootContainersCandidates) do
local candidates = {}
for j, item in ipairs(container:getItems()) do
if item:isContainer() and containers[item:getId()] ~= nil then
table.insert(candidates, item)
end
end
if #candidates > 0 then
g_game.open(candidates[math.random(1,#candidates)], container)
return
end
-- full, close it
g_game.close(container)
return
end
return
end
-- open remaining containers
if #nextContainers == 0 then
return
end
local delay = 1
for i=2,#nextContainers do
-- if more than 1 container, open them in new window
context.schedule(delay, function()
g_game.open(nextContainers[i], nil)
end)
delay = delay + 250
end
context.schedule(delay, function()
g_game.open(nextContainers[1], container)
end)
context.delay(150 + delay)
end)
end

View File

@ -5,7 +5,7 @@ Panels.Waypoints = function(parent)
local ui = context.setupUI([[
Panel
id: waypoints
height: 213
height: 203
BotLabel
anchors.top: parent.top
@ -35,6 +35,7 @@ Panel
anchors.left: parent.left
text: Add
width: 60
height: 20
Button
id: edit
@ -42,6 +43,7 @@ Panel
anchors.horizontalCenter: parent.horizontalCenter
text: Edit
width: 60
height: 20
Button
id: remove
@ -49,6 +51,7 @@ Panel
anchors.right: parent.right
text: Remove
width: 60
height: 20
TextList
id: list
@ -85,6 +88,7 @@ Panel
text: Goto
width: 61
margin-top: 1
height: 20
Button
id: wUse
@ -92,6 +96,7 @@ Panel
anchors.left: prev.right
text: Use
width: 61
height: 20
Button
id: wUseWith
@ -99,6 +104,7 @@ Panel
anchors.left: prev.right
text: UseWith
width: 61
height: 20
Button
id: wWait
@ -107,6 +113,7 @@ Panel
text: Wait
width: 61
margin-top: 1
height: 20
Button
id: wSay
@ -114,6 +121,7 @@ Panel
anchors.left: prev.right
text: Say
width: 61
height: 20
Button
id: wFunction
@ -121,6 +129,7 @@ Panel
anchors.left: prev.right
text: Function
width: 61
height: 20
BotSwitch
id: recording
@ -128,6 +137,7 @@ Panel
anchors.left: parent.left
anchors.right: parent.right
text: Auto Recording
height: 20
]], parent)
@ -157,6 +167,12 @@ Panel
return true
elseif command == "say" then
return true
elseif command == "label" then
return true
elseif command == "gotolabel" then
return true
elseif command == "comment" then
return true
elseif command == "function" then
return true
end
@ -169,7 +185,7 @@ Panel
local parseConfig = function(config)
commands = {}
local matches = regexMatch(config, [[\s*([^:^\n]+)(:?)([^\n]*)]])
local matches = regexMatch(config, [[([^:^\n^\s]+)(:?)([^\n]*)]])
for i=1,#matches do
local command = matches[i][2]
local validation = (matches[i][3] == ":")
@ -178,7 +194,7 @@ Panel
if validation then
table.insert(commands, {command=command:lower(), text=text})
elseif #commands > 0 then
commands[#commands].text = commands[#commands].text .. "\n" .. command
commands[#commands].text = commands[#commands].text .. "\n" .. matches[i][1]
end
end
end
@ -186,6 +202,18 @@ Panel
for i=1,#commands do
local label = g_ui.createWidget("CaveBotLabel", ui.list)
label:setText(commands[i].command .. ":" .. commands[i].text)
if commands[i].command == "goto" then
label:setColor("green")
elseif commands[i].command == "label" then
label:setColor("yellow")
elseif commands[i].command == "comment" then
label:setText(commands[i].text)
label:setColor("white")
elseif commands[i].command == "use" or commands[i].command == "usewith" then
label:setColor("orange")
elseif commands[i].command == "gotolabel" then
label:setColor("red")
end
end
end
@ -218,7 +246,7 @@ Panel
ui.list:destroyChildren()
if context.storage.cavebot.activeConfig then
if context.storage.cavebot.activeConfig and context.storage.cavebot.configs[context.storage.cavebot.activeConfig] then
ui.config:setCurrentIndex(context.storage.cavebot.activeConfig)
parseConfig(context.storage.cavebot.configs[context.storage.cavebot.activeConfig])
end
@ -247,7 +275,7 @@ Panel
refreshConfig()
end
ui.add.onClick = function()
modules.game_textedit.multilineEditor("Waypoints editor", "name:Config name\n", function(newText)
modules.game_textedit.multilineEditor("Waypoints editor", "name:Config name\nlabel:start\n", function(newText)
table.insert(context.storage.cavebot.configs, newText)
context.storage.cavebot.activeConfig = #context.storage.cavebot.configs
refreshConfig()
@ -287,12 +315,7 @@ Panel
local newText = ""
if newPos.z ~= oldPos.z then
newText = "goto:" .. oldPos.x .. "," .. oldPos.y .. "," .. oldPos.z
if #commands > 0 then
local lastCommand = commands[#commands].command .. ":" .. commands[#commands].text
if lastCommand == newText then
return
end
end
newText = newText .. "\ngoto:" .. newPos.x .. "," .. newPos.y .. "," .. newPos.z
stepsSincleLastPos = 0
else
stepsSincleLastPos = stepsSincleLastPos + 1
@ -319,7 +342,8 @@ Panel
return
end
stepsSincleLastPos = 0
newText = "use:" .. pos.x .. "," .. pos.y .. "," .. pos.z
local playerPos = context.player:getPosition()
newText = "goto:" .. playerPos.x .. "," .. playerPos.y .. "," .. playerPos.z .. "\nuse:" .. pos.x .. "," .. pos.y .. "," .. pos.z
context.storage.cavebot.configs[context.storage.cavebot.activeConfig] = context.storage.cavebot.configs[context.storage.cavebot.activeConfig] .. "\n" .. newText
refreshConfig(true)
end)
@ -338,7 +362,8 @@ Panel
return
end
stepsSincleLastPos = 0
newText = "usewith:" .. itemId .. "," .. targetPos.x .. "," .. targetPos.y .. "," .. targetPos.z
local playerPos = context.player:getPosition()
newText = "goto:" .. playerPos.x .. "," .. playerPos.y .. "," .. playerPos.z .. "\nusewith:" .. itemId .. "," .. targetPos.x .. "," .. targetPos.y .. "," .. targetPos.z
context.storage.cavebot.configs[context.storage.cavebot.activeConfig] = context.storage.cavebot.configs[context.storage.cavebot.activeConfig] .. "\n" .. newText
refreshConfig(true)
end)
@ -404,7 +429,7 @@ Panel
if not context.storage.cavebot.activeConfig or not context.storage.cavebot.configs[context.storage.cavebot.activeConfig] then
return
end
modules.game_textedit.multilineEditor("Add function", "function(waypoints)\n --your lua code\n\n -- must return true to execute next command, othwerwise will run in loop till correct return\n return true\nend", function(newText)
modules.game_textedit.multilineEditor("Add function", "function(waypoints)\n -- your lua code, function is executed if previous goto was successful or is just after label\n\n -- must return true to execute next command, otherwise will run in loop till correct return\n return true\nend", function(newText)
context.storage.cavebot.configs[context.storage.cavebot.activeConfig] = context.storage.cavebot.configs[context.storage.cavebot.activeConfig] .. "\nfunction:" .. newText
refreshConfig(true)
end)
@ -423,6 +448,12 @@ Panel
end
refreshConfig()
local usedGotoLabel = false
local executeNextMacroCall = false
local commandExecutionNo = 0
local lastGotoSuccesful = true
local lastOpenedContainer = 0
local functions = {
enable = function()
@ -435,23 +466,52 @@ Panel
end,
refresh = function()
refreshConfig()
end
end,
wait = function(peroid)
waitTo = context.now + peroid
end,
waitTo = function(timepoint)
waitTo = timepoint
end,
gotoLabel = function(name)
for i=1,ui.list:getChildCount() do
local command = commands[i]
if command and command.command == "label" and command.text == name then
ui.list:focusChild(ui.list:getChildByIndex(i))
usedGotoLabel = true
lastGotoSuccesful = true
return true
end
end
end
}
local executeNextMacroCall = false
local commandExecutionNo = 0
local lastGotoSuccesful = true
context.onContainerOpen(function(container)
if container:getItemsCount() > 0 then
lastOpenedContainer = context.now
end
end)
context.macro(250, function()
if not context.storage.cavebot.enabled then
return
end
if context.player:isWalking() then
-- wait if walked or opened container recently
if context.player:isWalking() or lastOpenedContainer + 1000 > context.now then
executeNextMacroCall = false
return
end
-- wait if attacking/following creature
local attacking = g_game.getAttackingCreature()
local following = g_game.getFollowingCreature()
if (attacking and context.getCreatureById(attacking:getId())) or (following and context.getCreatureById(following:getId())) then
executeNextMacroCall = false
return
end
if not executeNextMacroCall then
executeNextMacroCall = true
return
@ -474,32 +534,45 @@ Panel
end
return
end
if commandIndex == 1 then
lastGotoSuccesful = true
end
if command.command == "goto" then
local matches = regexMatch(command.text, [[([0-9]+)[^0-9]+([0-9]+)[^0-9]+([0-9]+)]])
if #matches == 1 and #matches[1] == 4 then
local position = {x=tonumber(matches[1][2]), y=tonumber(matches[1][3]), z=tonumber(matches[1][4])}
local distance = context.getDistanceBetween(position, context.player:getPosition())
if distance > 0 and position.z == context.player:getPosition().z then
if distance > 100 or position.z ~= context.player:getPosition().z then
lastGotoSuccesful = false
elseif distance > 0 then
commandExecutionNo = commandExecutionNo + 1
lastGotoSuccesful = false
if commandExecutionNo <= 3 then -- try max 3 times
if not context.autoWalk(position, 100 + distance * 2, commandExecutionNo > 1, false) then
context.autoWalk(position, 100 + distance * 2, true, true)
if commandExecutionNo > 1 then
context.autoWalk(position, 100 + distance * 2, true, true) -- ignore creatures
end
context.delay(500)
return
end
return
elseif commandExecutionNo == 4 then -- try last time, location close to destination
position.x = position.x + math.random(-1, 1)
position.y = position.y + math.random(-1, 1)
if context.autoWalk(position, 100 + distance * 2, true) then
return
for i=1,3 do
position.x = position.x + math.random(-1, 1)
position.y = position.y + math.random(-1, 1)
if context.autoWalk(position, 100 + distance * 2, true) then
return
end
end
elseif distance < 2 then
elseif distance <= 2 then
lastGotoSuccesful = true
executeNextMacroCall = lastGotoSuccesful
end
else
lastGotoSuccesful = (position.z == context.player:getPosition().z)
lastGotoSuccesful = true
executeNextMacroCall = lastGotoSuccesful
end
else
context.error("Waypoints: invalid use of goto function")
@ -550,6 +623,7 @@ Panel
elseif command.command == "say" and lastGotoSuccesful then
context.say(command.text)
elseif command.command == "function" and lastGotoSuccesful then
usedGotoLabel = false
local status, result = pcall(function()
return assert(load("return " .. command.text, nil, nil, context))()(functions)
end)
@ -557,7 +631,11 @@ Panel
context.error("Waypoints function execution error:\n" .. result)
context.delay(2500)
end
if not result then
if not result or usedGotoLabel then
return
end
elseif command.command == "gotolabel" then
if functions.gotoLabel(command.text) then
return
end
end

View File

@ -37,9 +37,6 @@ function show(itemWidget)
local doneFunc = function()
itemWidget:setItemId(window.item:getItemId())
itemWidget:setItemCount(window.item:getItemCount())
if itemWidget.onItemChange then
itemWidget:onItemChange()
end
destroy()
end

View File

@ -19,8 +19,8 @@ ItemSelectorWindow < MainWindow
margin-left: 5
padding-left: 5
width: 70
minimum: 1
maximum: 999999999
minimum: 0
maximum: 999999
focusable: true
Label

View File

@ -31,9 +31,6 @@ function UIItem:onDrop(widget, mousePos, forced)
if item:getSubType() > 1 then
self:setItemSubType(item:getSubType())
end
if self.onItemChange then
self:onItemChange()
end
return
end