1159 lines
32 KiB
Lua

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: 450 430
!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
Label
id: info2
anchors.left: parent.left
anchors.right: parent.right
anchors.top: prev.bottom
text-align: center
text: Add number (1-5) at the end of the name to create multiple configs
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: Target 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: maxDistanceText
anchors.left: parent.left
anchors.right: parent.horizontalCenter
anchors.top: prev.bottom
margin-right: 10
margin-top: 10
text: Max distance to target
text-align: center
HorizontalScrollBar
id: maxDistance
anchors.left: prev.left
anchors.right: prev.right
anchors.top: prev.bottom
margin-top: 5
minimum: 1
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: Keep 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
Label
id: attackSpellText
anchors.left: parent.left
anchors.right: parent.horizontalCenter
anchors.top: prev.bottom
margin-right: 10
margin-top: 10
text: Attack spell and attack rune are only used when you have more than 30% health
text-align: center
text-wrap: true
text-auto-resize: true
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: 18
margin-top: 10
width: 55
text: Ignore
BotSwitch
id: avoid
anchors.left: prev.right
anchors.top: name.bottom
margin-left: 18
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 has low health
BotSwitch
id: loot
anchors.left: parent.horizontalCenter
anchors.right: parent.right
anchors.top: prev.bottom
margin-left: 10
margin-top: 10
text: Loot corpse
BotSwitch
id: monstersOnly
anchors.left: parent.horizontalCenter
anchors.right: parent.right
anchors.top: prev.bottom
margin-left: 10
margin-top: 10
text: Only for monsters
BotSwitch
id: dontWalk
anchors.left: parent.horizontalCenter
anchors.right: parent.right
anchors.top: prev.bottom
margin-left: 10
margin-top: 10
text: Don't walk to target
Label
id: attackSpellText
anchors.left: parent.horizontalCenter
anchors.right: parent.right
anchors.top: prev.bottom
margin-left: 10
margin-top: 10
text: Attack Spell:
text-align: center
TextEdit
id: attackSpell
anchors.left: prev.left
anchors.right: prev.right
anchors.top: prev.bottom
margin-top: 2
Label
id: attackItemText
anchors.left: parent.horizontalCenter
anchors.top: prev.bottom
margin-top: 20
margin-left: 20
text: Attack rune:
text-align: left
BotItem
id: attackItem
anchors.right: parent.right
anchors.verticalCenter: prev.verticalCenter
margin-right: 30
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(),
maxDistance = window.maxDistance: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(),
monstersOnly = window.monstersOnly:isOn(),
dontWalk = window.dontWalk:isOn(),
attackItem = window.attackItem:getItemId(),
attackSpell = window.attackSpell:getText()
}
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.maxDistance.onValueChange = function(scroll, value)
window.maxDistanceText:setText("Max distance to target: " .. value)
end
window.distance.onValueChange = function(scroll, value)
window.distanceText:setText("Keep 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.maxDistance:setValue(config.maxDistance or 6)
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.attackSpell:setText(config.attackSpell or "")
window.attackItem:setItemId(config.attackItem or 0)
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.monstersOnly.onClick = function(widget)
widget:setOn(not widget:isOn())
end
window.dontWalk.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.monstersOnly:setOn(config.monstersOnly)
if config.monstersOnly == nil then
window.monstersOnly:setOn(true)
end
window.dontWalk:setOn(config.dontWalk)
window.name:setText(monster)
window:show()
window:raise()
window:focus()
end
Panels.Attacking = function(parent)
local ui = context.setupUI([[
Panel
id: attacking
height: 150
BotLabel
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
text: Attacking
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()
-- processing
local isConfigPassingConditions = function(monster, config)
if not config or type(config.priority) ~= 'number' or type(config.danger) ~= 'number' then
return false
end
if not config.attack then
return false
end
if monster:isPlayer() and config.monstersOnly then
return false
end
local pos = context.player:getPosition()
local mpos = monster:getPosition()
local hp = monster:getHealthPercent()
if config.minHealth > hp or config.maxHealth < hp then
return false
end
local maxDistance = 5
if type(config.maxDistance) == 'number' then
maxDistance = config.maxDistance
end
if config.chase and hp < 25 then
maxDistance = maxDistance + 2
end
local distance = math.max(math.abs(pos.x-mpos.x), math.abs(pos.y-mpos.y))
if distance > maxDistance then
return false
end
local pathTo = context.findPath(context.player:getPosition(), {x=mpos.x, y=mpos.y, z=mpos.z}, maxDistance + 2, { ignoreNonPathable = true, precision=1, allowOnlyVisibleTiles = true })
if not pathTo or #pathTo > maxDistance + 1 then
return false
end
return true
end
local getMonsterConfig = function(monster)
local name = monster:getName():lower()
local hasConfig = false
hasConfig = hasConfig or (monsters[name] ~= nil)
if isConfigPassingConditions(monster, monsters[name]) then
return monsters[name]
end
for i=1, 5 do
hasConfig = hasConfig or (monsters[name .. i] ~= nil)
if isConfigPassingConditions(monster, monsters[name .. i]) then
return monsters[name .. i]
end
end
if not hasConfig and isConfigPassingConditions(monster, monsters["*"]) then
return monsters["*"]
end
return nil
end
local calculatePriority = function(monster)
local priority = 0
local config = getMonsterConfig(monster)
if not config then
return -1
end
local pos = context.player:getPosition()
local mpos = monster:getPosition()
local hp = monster:getHealthPercent()
local pathTo = context.findPath(context.player:getPosition(), {x=mpos.x, y=mpos.y, z=mpos.z}, 10, { ignoreNonPathable = true, ignoreLastCreature = true, precision=0, allowOnlyVisibleTiles = true })
if not pathTo then
pathTo = context.findPath(context.player:getPosition(), {x=mpos.x, y=mpos.y, z=mpos.z}, 10, { ignoreNonPathable = true, precision=1, allowOnlyVisibleTiles = true })
if not pathTo then
return -1
end
end
local distance = #pathTo
if monster == g_game.getAttackingCreature() then
priority = priority + 10
end
if distance <= 4 then
priority = priority + 10
end
if distance <= 2 then
priority = priority + 10
end
if distance <= 1 then
priority = priority + 10
end
if hp <= 25 and config.chase then
priority = priority + 30
end
if hp <= 10 then
priority = priority + 10
end
if hp <= 25 then
priority = priority + 10
end
if hp <= 50 then
priority = priority + 10
end
if hp <= 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 lastAttackSpell = 0
local lastAttackRune = 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
topItem:setMarked('orange')
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 <= 25 then
if context.player:isWalking() then
return true
end
lootTries = lootTries + 1
if context.autoWalk(cpos, 20, { precision = 1}) then
return true
end
if context.autoWalk(cpos, 20, { ignoreNonPathable = true, precision = 1}) then
return true
end
if context.autoWalk(cpos, 20, { ignoreNonPathable = true, precision = 2}) then
return true
end
if context.autoWalk(cpos, 20, { ignoreNonPathable = true, ignoreCreatures = true, precision = 2}) 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
topItem:setMarked('blue')
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() or (spec:isPlayer() and not spec:isLocalPlayer()) 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 or context.isInProtectionZone() 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
local justStartedAttack = false
if target ~= attacking then
g_game.attack(target)
attacking = target
lastAttack = context.now
justStartedAttack = true
end
-- proceed attack
if not target:isPlayer() and 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 not justStartedAttack and config.attackSpell and config.attackSpell:len() > 0 then
if context.now > lastAttackSpell + 1000 and context.player:getHealthPercent() > 30 then
if context.saySpell(config.attackSpell, 1500) then
lastAttackRune = context.now
end
end
end
if not justStartedAttack and config.attackItem and config.attackItem >= 100 then
if context.now > lastAttackRune + 1000 and context.player:getHealthPercent() > 30 then
if context.useRune(config.attackItem, target, 1500) then
lastAttackRune = context.now
end
end
end
if modules.game_walking.lastManualWalk + 500 > context.now then
return
end
if danger < 8 then
-- low danger, go for loot first
if goForLoot() then
return
end
end
target.ignoreByWaypoints = config.dontWalk
if config.dontWalk then
if goForLoot() then
return
end
return
end
local distance = math.max(math.abs(offsetX), math.abs(offsetY))
if config.keepDistance then
local minDistance = config.distance
if target:getHealthPercent() <= 25 and config.chase and danger < 10 then
minDistance = 1
end
if (distance == minDistance or distance == minDistance + 1) then
return
else
local bestDist = 10
local bestPos = pos
if not context.autoWalk(tpos, 10, { minMargin=minDistance, maxMargin=minDistance + 1, allowOnlyVisibleTiles = true}) then
if not context.autoWalk(tpos, 10, { ignoreNonPathable = true, minMargin=minDistance, maxMargin=minDistance + 1, allowOnlyVisibleTiles = true}) then
if not context.autoWalk(tpos, 10, { ignoreNonPathable = true, ignoreCreatures = true, minMargin=minDistance, maxMargin=minDistance + 2, allowOnlyVisibleTiles = true}) then
return
end
end
end
if not target:isPlayer() then
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
if not context.autoWalk(tpos, 10, { precision = 1, allowOnlyVisibleTiles = true}) then
if not context.autoWalk(tpos, 10, { ignoreNonPathable = true, precision = 1, allowOnlyVisibleTiles = true}) then
if not context.autoWalk(tpos, 10, { ignoreNonPathable = true, precision = 2, allowOnlyVisibleTiles = true}) then
return
end
end
end
if not target:isPlayer() then
context.delay(300)
end
end
end)
end