Skip to content

Instantly share code, notes, and snippets.

@divinity76
Last active August 20, 2026 21:10
Show Gist options
  • Select an option

  • Save divinity76/17166b2fb2bd9f8967c042e4ceb75ae1 to your computer and use it in GitHub Desktop.

Select an option

Save divinity76/17166b2fb2bd9f8967c042e4ceb75ae1 to your computer and use it in GitHub Desktop.
otclient scripting cheat sheet
function getManaPercent()
return (player:getMana() / player:getMaxMana()) * 100
end
function getHealthPercent()
return (player:getHealth() / player:getMaxHealth()) * 100
end
function hasManashield()
-- return (player:getStates() & (1 << 4)) ~= 0
return player:hasState(16)
end
local function findAllItemsInContainers(itemId, subType, tier)
local found = {}
for _, container in pairs(g_game.getContainers()) do
for slot, item in ipairs(container:getItems()) do
if item:getId() == itemId and (subType == nil or item:getSubType() == subType) and
(tier == nil or (item.getTier and item:getTier() == tier)) then
table.insert(found, item)
end
end
end
return found
end
local function countItemsInContainers(itemId, subType, tier)
return #findAllItemsInContainers(itemId, subType, tier)
end
-- Finds the nearest tile around `position` (excluding the center) with no top creature.
-- Searches in concentric "circles" up to maxRadius (default 6) on the same z-level.
function findFreeTileNearPosition(position, maxRadius)
maxRadius = maxRadius or 6
local cx, cy, cz = position.x, position.y, position.z
local p = {
x = cx,
y = cy,
z = cz
}
local function try(dx, dy)
if dx == 0 and dy == 0 then
return nil
end -- skip center
p.x = cx + dx;
p.y = cy + dy
local tile = g_map.getTile(p)
if tile and not tile:getTopCreature() then
-- If you also need walkability, add: tile:isWalkable()
-- return { x = p.x, y = p.y, z = cz }
return tile
end
return nil
end
for r = 1, maxRadius do
local r2, prev2 = r * r, (r - 1) * (r - 1)
-- Helper to guard “circle” annulus without atan2.
local function in_annulus(dx, dy)
local d2 = dx * dx + dy * dy
return d2 > prev2 and d2 <= r2
end
-- Start at (r, 0) and sweep CCW around the square perimeter at Chebyshev radius r.
-- Right edge: ( r, 0 .. r ) -- up
do
local dx = r
for dy = 0, r do
if in_annulus(dx, dy) then
local hit = try(dx, dy)
if hit then
return hit
end
end
end
end
-- Top edge: ( r .. -r, r ) -- left
do
local dy = r
for dx = r, -r, -1 do
if in_annulus(dx, dy) then
local hit = try(dx, dy)
if hit then
return hit
end
end
end
end
-- Left edge: ( -r, r .. -r ) -- down
do
local dx = -r
for dy = r, -r, -1 do
if in_annulus(dx, dy) then
local hit = try(dx, dy)
if hit then
return hit
end
end
end
end
-- Bottom edge: ( -r .. r, -r ) -- right, stopping before (r,0) to avoid duplicate
do
local dy = -r
for dx = -r, r - 1 do
if in_annulus(dx, dy) then
local hit = try(dx, dy)
if hit then
return hit
end
end
end
end
end
return nil
end
macro(10000, "auto manashield", function()
if player:getMana() < 50 then
return
end
-- if hasManashield() then
-- return
-- end
say("utamo vita")
end)
macro(100, "auto exura vita", function()
if getHealthPercent() > 80 then
return
end
if player:getMana() < 100 then
return
end
if not hasManashield() then
return
end
say "exura vita"
end)
macro(200, "manarune heal", function()
if not hasManashield() and player:getMana() > 50 then
-- we need utamo vita asap
say("utamo vita")
return
end
if getManaPercent() >= 70 then
return
end
local idManaRune = 3201
local idManaruneBp = 2868
local manaruneList = findAllItemsInContainers(idManaRune, 1)
local manaruneCount = #manaruneList
local manaruneItem = manaruneList[1] or nil
if manaruneCount == 0 then
local manaruneBpItem = g_game.findPlayerItem(idManaruneBp, 1)
if manaruneBpItem then
g_game.open(manaruneBpItem, manaruneBpItem:getParentContainer())
end
return
end
local nearbyFreePos = findFreeTileNearPosition(player:getPosition(), 6)
if not nearbyFreePos then
-- need to find a nearby tile with no creature..
return
end
local targetThing = nearbyFreePos:getTopUseThing();
if not targetThing then
targetThing = nearbyFreePos:getTopThing();
if not targetThing then
return
end
end
g_game.useWith(manaruneItem, targetThing, 1)
if manaruneCount < 2 then
local manaruneBpItem = g_game.findPlayerItem(idManaruneBp, 1)
if manaruneBpItem then
g_game.open(manaruneBpItem, manaruneBpItem:getParentContainer())
end
end
end)
-- function g_game.getAttackingCreature() end
macro(500, "SD rune attack", function()
if not hasManashield() then
return
end
if getManaPercent() < 50 then
return
end
local target = g_game.getAttackingCreature()
if not target then
return
end
local idSDRune = 3155
local sdruneItem = g_game.findPlayerItem(idSDRune, 1)
if not sdruneItem then
return
end
g_game.useWith(sdruneItem, target, 1)
end)
macro(1000, "Auto UE", function()
if not hasManashield() then
say("utamo vita")
return
end
if getManaPercent() < 30 then
return
end
local target = g_game.getAttackingCreature()
if not target then
return
end
say("exevo gran mas mort")
end)
local function stackOnce()
local containers = g_game.getContainers()
local toStack = {}
for _, container in pairs(containers) do
for slot, item in ipairs(container:getItems()) do
if item:isStackable() and item:getCount() ~= 100 then
local targetPos = toStack[item:getId()]
if targetPos then
g_game.move(item, targetPos, item:getCount())
return true -- moved something
end
toStack[item:getId()] = container:getSlotPosition(slot - 1)
end
end
end
return false -- nothing to stack
end
macro(5000, "Auto stacking items", function(m)
if stackOnce() then
m.timeout = 1000
else
m.timeout = 5000 -- idle when done
end
end)
local NORMAL_DELAY = 5000
local FAST_DELAY = 800
local VERY_FAST_DELAY = 250
macro(VERY_FAST_DELAY, "Sell", function(m)
-- First pass: stack everything
if stackOnce() then
return
end
-- Nothing left to stack: say the sell lines
say('exura "hi')
say('exura "sell all golden boots')
say('exura "sell all golden legs')
say('exura "sell all magic longsword')
say('exura "sell all demon shield')
say('exura "sell all great shield')
say('exura "sell all mpa')
say('exura "sell all dsm')
say('exura "sell all royal helmet')
m.setOff()
end)
macro(3600000, "Auto task maxpoints", function()
say("!task maxpoints")
end)
macro(500, "turn itself off", function(m)
m.setOff()
end)

how to open main container:

local containers = getContainers()
if not containers[0] and getBack() then
  g_game.open(getBack())
end

how to get the target bot's "Danger" count in Lua:

TargetBot.Danger() -- returns int

how to move an item to under my feet:

interestingItem=findItem(1337);
g_game.move(interestingItem, player:getPosition(), 1)

how to move an item to a container:

bp0=getContainer(0);
interestingItem=findItem(1337);
g_game.move(interestingItem, bp0:getSlotPosition(0), 1)

how to open container in current window, rather than a new window:

g_game.open(container, container:getParentContainer())

how to send a custom packet, like "\x01\x00\x65":

  local protocol = g_game.getProtocolGame()
  local msg = OutputMessage.create()
  msg:addU8(1)
  msg:addU8(0)
  msg:addU8(113)
  protocol:send(msg)

how to schedule something in the future:

schedule(1000, function()
-- this will execute in 1000 milliseconds
end)

misc functions:

function getManaPercent()
  return (player:getMana() / player:getMaxMana()) *100
end

function getHealthPercent()
  return (player:getHealth() / player:getMaxHealth()) *100
end

function hasManashield()
    -- return (player:getStates() & (1 << 4)) ~= 0
    return player:hasState(PlayerStates.ManaShield)
end
macro(3000, "manarune heal", function()
  if getManaPercent() >= 70 then
    say("more than 70%")
    return
  end
    local idManaRune = 3201
    local manaruneItem = g_game.findPlayerItem(idManaRune, 1)
    if not manaruneItem then
      say("no manarune")
      return
    end
    say("using manarune")
    g_game.useWith(manaruneItem, g_game.getLocalPlayer(), 1)
end)



macro(100, "manarune heal in front of you", function()
  if getManaPercent() >= 70 then
    return
  end
    local idManaRune = 3201
    local manaruneItem = g_game.findPlayerItem(idManaRune, 1)
    if not manaruneItem then
      return
    end
    local pos = player:getPosition()
    pos.x = pos.x + 2
    local tile = g_map.getTile(pos)
    if not tile then
      return
    end
    local targetThing = tile:getTopUseThing()
    if not targetThing then
        targething = tile:getTopThing()
        if not targetThing then
            return
        end
    end
    g_game.useWith(manaruneItem, targetThing, 1)
end)

converting 100gp to 1plat, 100plat to 1 cc, 100 cc to something:

for i, container in pairs(getContainers()) do
    for j, item in ipairs(container:getItems()) do
      if item:getCount() == 100 and (item:getId() == 3031 or item:getId() == 3035 or item:getId() == 3043) then
        g_game.use(item)
        delay(100)
        return "retry"
      end
    end
  end

turn off cavebot:

CaveBot.setOn(false)

Stacking itmes:

macro(5000, "Auto stacking items", function()
    local containers = g_game.getContainers()
    for i, container in pairs(containers) do
        local toStack = {}
        for j, item in ipairs(container:getItems()) do
            if item:isStackable() and item:getCount() ~= 100 then
                local otherItem = toStack[item:getId()]
                if otherItem then
                    g_game.move(item, otherItem, item:getCount())
                end
                toStack[item:getId()] = container:getSlotPosition(j - 1)
            end
        end
    end
end)
@divinity76

divinity76 commented Oct 5, 2024

Copy link
Copy Markdown
Author

https://pastebin.com/u/Zeroun_Scripts

tibiafun.zapto.org custom.lua

local questing = false
local doTimestampedAttack = false
local function getManaPercent()
    return (player:getMana() / player:getMaxMana()) * 100
end

local function getHealthPercent()
    return (player:getHealth() / player:getMaxHealth()) * 100
end

local function hasManashield()
    -- return (player:getStates() & (1 << 4)) ~= 0
    return player:hasState(16)
end
local function findAllItemsInContainers(itemId, subType, tier)
    local found = {}
    for _, container in pairs(g_game.getContainers()) do
        for slot, item in ipairs(container:getItems()) do
            if item:getId() == itemId and
                (subType == nil or item:getSubType() == subType) and
                (tier == nil or (item.getTier and item:getTier() == tier)) then
                table.insert(found, item)
            end
        end
    end
    return found
end

local function countItemsInContainers(itemId, subType, tier)
    return #findAllItemsInContainers(itemId, subType, tier)
end
local function manaIsPercentMode() return player:getMaxMana() == 100 end
local function hasMana(amount)
    if manaIsPercentMode() then
        return player:getMana() * 1000 >= amount
    else
        return player:getMana() >= amount
    end
end

macro(20, "fast^", function(m)
    -- ^north=0 >east=1 vsouth=2 <west=3
    g_game.walk(Directions.East)
    g_game.walk(Directions.South)
    m.setOff()
end)
macro(1000, "Hold Target", function(m)
    local target = g_game.getAttackingCreature()

    if target then
        m.lastTargetName = target:getName()
    elseif m.lastTargetName then
        local spectators = g_map.getSpectators(player:getPosition(), true)

        for _, creature in ipairs(spectators) do
            if creature:getName() == m.lastTargetName then
                g_game.attack(creature)
                return
            end
        end
    end
end)

macro(60000, "auto-eat", function()
    local foods = {
        3577, -- meat
        3582, -- ham
        3581, -- shrimp
        3583, -- Dragon Ham
        3725 -- brown mushroom
    }
    for _, id in ipairs(foods) do
        local item = findItem(id)
        if item then
            use(item)
            return
        end
    end
end)

-- macro(500, "ultra manatrain", function()
--     maximizeManalevelTraining = false
--     -- if not hasManashield() and hasMana(50) then
--     --     -- we need utamo vita asap
--     --     g_game.talk("utamo vita")
--     --     return
--     -- end
--     if getManaPercent() < 75 then
--         local idManaRune = 3201
--         local manaRuneItem = g_game.findPlayerItem(idManaRune, 1)
--         if manaRuneItem then g_game.use(manaRuneItem) end
--     else
--         local target = g_game.getAttackingCreature()
--         if target and not target:isDead() and not target:isRemoved() and
--             target:getName() == "Venom Sniper" then return end
--         g_game.talk('mana waste "140000')
--     end
-- end)

-- macro(7000, "auto manashield", -- 8s confirmed too slow. (lost at gambon)
-- function() g_game.talk("utamo vita") end)

-- macro(100, "auto exura vita", function()
--     if getHealthPercent() > 80 then return end
--     if not hasMana(100) then
--         local idUH = 3160
--         local uhItem = g_game.findPlayerItem(idUH, 1)
--         if uhItem then
--             -- g_game.use(uhItem)
--             g_game.talk("!uh") -- wtf..
--         end
--         return
--     end
--     if not hasManashield() then return end
--     g_game.talk("exura vita")
-- end)
macro(200, "auto manarune", function()
    local manaruneHealThreshold = manaruneHealThreshold or 70
    local haveInfinityManarune = true
    -- if not hasManashield() then
    --     -- we need utamo vita asap
    --     g_game.talk("utamo vita")
    --     return
    -- end
    if getManaPercent() >= manaruneHealThreshold then return end
    if haveInfinityManarune then
        local manarune = g_game.findPlayerItem(3201, 1)
        if manarune then
            g_game.use(manarune)
            return
        else
            g_game.talk('exura "Error: Infinity manarune not found!!!!')
        end
        return
    end
    local idManaRune = 3201
    local idManaruneBp = 2868
    local manaruneList = findAllItemsInContainers(idManaRune, 1)
    local manaruneCount = #manaruneList
    local manaruneItem = manaruneList[1] or nil
    if manaruneCount == 0 then
        local manaruneBpItem = g_game.findPlayerItem(idManaruneBp, 1)
        if manaruneBpItem then
            g_game.open(manaruneBpItem, manaruneBpItem:getParentContainer())
        end
        return
    end
    g_game.use(manaruneItem)
    if manaruneCount < 2 then
        local manaruneBpItem = g_game.findPlayerItem(idManaruneBp, 1)
        if manaruneBpItem then
            g_game.open(manaruneBpItem, manaruneBpItem:getParentContainer())
        end
    end
end)
-- function g_game.getAttackingCreature() end
macro(500, "Auto SD rune attack", function()
    -- if not hasManashield() then return end
    if getManaPercent() < 50 then return end
    local target = g_game.getAttackingCreature()
    if not target then return end
    local idSDRune = 3155
    local sdruneItem = g_game.findPlayerItem(idSDRune, 1)
    if not sdruneItem then return end
    g_game.useWith(sdruneItem, target, 1)
end)

local function performSmartUE()
    local planGrid = {
        {0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0},
        {0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0},
        {0, 0, 0, 0, 2, 1, 2, 1, 2, 1, 2, 0, 0, 0, 0},
        {0, 0, 0, 2, 2, 2, 1, 1, 1, 2, 2, 2, 0, 0, 0},
        {0, 0, 0, 2, 2, 1, 1, 1, 1, 1, 2, 2, 0, 0, 0},
        {0, 0, 2, 2, 2, 1, 1, 0, 1, 1, 2, 2, 2, 0, 0},
        {0, 0, 0, 2, 2, 1, 1, 1, 1, 1, 2, 2, 0, 0, 0},
        {0, 0, 0, 2, 2, 2, 1, 1, 1, 2, 2, 2, 0, 0, 0},
        {0, 0, 0, 0, 2, 1, 2, 1, 2, 1, 2, 0, 0, 0, 0},
        {0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0},
        {0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0}
    }
    local target = g_game.getAttackingCreature()
    if not target or target:isDead() or target:isRemoved() or
        target:getHealthPercent() <= 0 then return end
    local targetName = target:getName()
    if targetName == "Venom Sniper" or targetName == "Ghost" or targetName ==
        "Spirit" then
        -- notoriously immune to conventional spells
        local idGFB = 3191
        local gfbItem = g_game.findPlayerItem(idGFB, 1)
        if gfbItem then g_game.useWith(gfbItem, target, 1) end
        return
    end
    local plan = 0

    local p = player:getPosition()
    local t = target:getPosition()
    local dx = t.x - p.x
    local dy = t.y - p.y

    if dx >= -7 and dx <= 7 and dy >= -5 and dy <= 5 then
        local row = dy + 6
        local col = dx + 8
        plan = planGrid[row][col]
    end
    if plan == 1 and monsterName == "Wolf Boss" then plan = 2 end
    if plan == 1 then
        -- gambon is very resistant to SD, so we use mas vis instead when possible
        g_game.talk("exevo mas vis" .. " \"" .. targetName .. " " ..
                        target:getHealthPercent())
    elseif plan == 2 then
        g_game.talk("exevo gran mas pox" .. " \"" .. targetName .. " " ..
                        target:getHealthPercent())
    else
        -- g_game.talk("no plan fits!")
        local idSD = 3155
        local sdItem = g_game.findPlayerItem(idSD, 1)
        if sdItem then g_game.useWith(sdItem, target, 1) end
    end
end
-- macro(500, "Smart UE+MasVis+SD", function()
--     local autoUEThreshold = autoUEThreshold or 20
--     autoUEThreshold = 11 -- todo fixme...
--     if getManaPercent() < autoUEThreshold then
--         --  plan = -1 will get u killed.
--         return
--     end
--     performSmartUE()
-- end)
-- macro(1000, "Auto UE", function()
--     local autoUEThreshold = autoUEThreshold or 30
--     local maximizeManalevelTraining = maximizeManalevelTraining or false
--     if not hasManashield() then
--         g_game.talk("utamo vita")
--         return
--     end
--     if getManaPercent() < autoUEThreshold then return end

--     local target = g_game.getAttackingCreature()
--     if not target or target:isRemoved() or target:isDead() then
--         if maximizeManalevelTraining and getManaPercent() >= 60 then
--             -- g_game.talk('mana waste "20000')
--         end
--         return
--     end

--     local dist = distanceFromPlayer(target:getPosition())
--     local spell = (dist < 3) and "exevo mas vis" or "exevo gran mas pox"

--     g_game.talk(spell .. " \"" .. target:getName() .. " " ..
--                     target:getHealthPercent())
-- end)
local function stackOnce()
    local containers = g_game.getContainers()
    local toStack = {}
    local specialIds = {3031, 3035, 3043, 3042}
    local firstContainer

    for _, container in pairs(containers) do
        if not firstContainer then firstContainer = container end
        for slot, item in ipairs(container:getItems()) do
            if item:isStackable() and item:getCount() ~= 100 then
                local targetPos = toStack[item:getId()]
                if targetPos then
                    g_game.move(item, targetPos, item:getCount())
                    return true -- moved something
                end
                if container == firstContainer then
                    toStack[item:getId()] = container:getSlotPosition(slot - 1)
                else
                    if table.contains(specialIds, item:getId()) then
                        toStack[item:getId()] =
                            firstContainer:getSlotPosition(
                                firstContainer:getItemsCount())
                    else
                        toStack[item:getId()] =
                            container:getSlotPosition(slot - 1)
                    end
                end
            end
        end
    end
    return false -- nothing to stack
end

local function stackOnceFancy()
    local containers = g_game.getContainers()
    local toStack = {}
    local specialIds = {3031, 3035, 3043, 3042} -- gp, platinum, cc, scarab coins
    local firstContainer

    for _, container in pairs(containers) do
        if not firstContainer then firstContainer = container end
        for slot, item in ipairs(container:getItems()) do
            if item:isStackable() then
                if item:getCount() == 100 then
                    -- if its a special id, use it
                    if table.contains(specialIds, item:getId()) then
                        g_game.use(item)
                        return true -- converted coins
                    end
                else
                    local targetPos = toStack[item:getId()]
                    if targetPos then
                        g_game.move(item, targetPos, item:getCount())
                        return true -- moved something
                    end
                    if container == firstContainer then
                        toStack[item:getId()] =
                            container:getSlotPosition(slot - 1)
                    else
                        if table.contains(specialIds, item:getId()) then
                            g_game.move(item, firstContainer:getSlotPosition(
                                            firstContainer:getItemsCount()),
                                        item:getCount())
                            return true -- moved coins
                        else
                            toStack[item:getId()] =
                                container:getSlotPosition(slot - 1)
                        end
                    end
                end
            end
        end
    end
    return false -- nothing to stack
end
macro(3000, "Auto stacking items", function(m)
    if stackOnceFancy() then
        m.timeout = 500 -- fast when there is shit to stack
    else
        m.timeout = 3000 -- idle when done
    end
end)
macro(200, "Sell", function(m)
    -- First pass: stack everything
    if stackOnceFancy() then return end
    -- Nothing left to stack: say the sell lines
    g_game.talk('exura "hi')
    g_game.talk('exura "sell all dsm')
    g_game.talk('exura "sell all mpa')
    g_game.talk('exura "sell all great shield')
    g_game.talk('exura "sell all golden boots')
    g_game.talk('exura "sell all golden legs')
    g_game.talk('exura "sell all royal helmet')
    g_game.talk('exura "sell all crystal arrow')
    g_game.talk('exura "sell all winged helmet')
    g_game.talk('exura "sell all horned helmet')
    g_game.talk('exura "sell all hidden turbant')
    g_game.talk('exura "sell all magic sword')
    g_game.talk('exura "sell all plasma shield')
    g_game.talk('exura "sell all thunder hammer')
    g_game.talk('exura "sell all demon armor')
    g_game.talk('exura "sell all demon legs')
    g_game.talk('exura "sell all divine helmet')
    g_game.talk('exura "sell all divine armor')
    g_game.talk('exura "sell all divine legs')
    g_game.talk('exura "sell all hammer of wrath')
    g_game.talk('exura "sell all hunting spear')
    g_game.talk('exura "sell all the pharao sword')
    g_game.talk('exura "sell all soft boots')
    g_game.talk('exura "sell all crystal wand')
    g_game.talk('exura "sell all ruthless axe')
    g_game.talk('exura "sell all golden helmet')
    local newCap = g_game.getLocalPlayer():getFreeCapacity()
    m.lastCap = m.lastCap or nil
    if newCap ~= m.lastCap then
        m.lastCap = newCap
    else
        m.retryCounter = (m.retryCounter or 0) + 1
        if m.retryCounter < 5 then return end
        m.retryCounter = nil
        m.lastCap = nil
        m.setOff()
    end
end)

macro(1000, "turn itself off", function(m)
    --    m.setOff() 
    g_game.talk("exevo gran mas pox")
end)
-- macro(30000, "auto-AOL",
--       function() if not getNeck() then g_game.talk("!buyaol") end end)
function getManaPercent() return (player:getMana() / player:getMaxMana()) * 100 end

function getHealthPercent()
    return (player:getHealth() / player:getMaxHealth()) * 100
end

function hasManashield()
    -- return (player:getStates() & (1 << 4)) ~= 0
    return player:hasState(16)
end

macro(100, "stairjumper", function(m)
    -- method 1: walk-attack-walk
    -- method 2: use door-attack-use door
    -- method 3: use door-wait, wait for <=40% mana, use door, wait for >=90% mana, repeat
    local method = 3
    if method ~= 3 and
        (manapercent() < 70 or not hasManashield() or hppercent() < 90) then
        delay(100)
        return
    end
    if method == 1 then
        -- north south
        g_game.walk(Directions.South)
        g_game.talk("exevo gran mas pox")
        -- g_game.talk("exevo mas vis")
        g_game.walk(Directions.North)
    elseif method == 2 then
        -- use door...
        local playerPos = player:getPosition()
        local door = g_map.getTile({
            x = playerPos.x + 0,
            y = playerPos.y - 1,
            z = playerPos.z
        }):getTopUseThing()
        if not door then
            delay(100)
            return
        end
        g_game.use(door)
        g_game.talk("exevo gran mas pox")
        g_game.use(door)
    elseif method == 3 then
        m.stairjumper = m.stairjumper or {state = "use1"}
        local s = m.stairjumper
        local playerPos = player:getPosition()
        s.targetPos = s.targetPos or
                          {
                x = playerPos.x,
                y = playerPos.y - 1,
                z = playerPos.z
            }
        local doorTile = g_map.getTile(s.targetPos)
        local door = doorTile and doorTile:getTopUseThing()
        if not door then
            delay(100)
            g_game.talk('exura "error: no door found')
            return
        end
        if s.state == "use1" then
            g_game.use(door)
            s.state = "waitLow"
            delay(200)
            return
        end
        if s.state == "waitLow" then
            if manapercent() > 30 then return end
            g_game.use(door)
            s.state = "waitHigh"
            delay(200)
            return
        end
        if s.state == "waitHigh" then
            if manapercent() < 90 then return end
            s.state = "use1"
            delay(200)
            return
        end
    end
    delay(1000)
end)
macro(60000, "anti-afk-kick", function(m)
    local order = {
        Directions.North, Directions.East, Directions.South, Directions.West
    }
    local last = m.lastDir or player:getDirection()
    local idx = 1
    for i, d in ipairs(order) do
        if d == last then
            idx = i
            break
        end
    end
    local nextDir = order[(idx % #order) + 1]
    g_game.turn(nextDir)
    m.lastDir = nextDir
end)
macro(1000, "perma expring", function()
    local target = g_game.getAttackingCreature()
    if not target or target:isRemoved() or target:isDead() or
        target:getHealthPercent() <= 0 then return end
    local ring = player:getInventoryItem(InventorySlotFinger)
    if ring then
        local id = ring:getId()
        if id == 3006 then return end
        if id == 3100 then
            local containers = g_game.getContainers()
            if not containers then return end
            local ordered = {}
            for _, c in pairs(containers) do table.insert(ordered, c) end
            table.sort(ordered, function(a, b)
                return a:getId() < b:getId()
            end)
            local c = ordered[1]
            if not c then return end
            local cap = c:getCapacity()
            local count = c:getItemsCount()
            if count < cap then
                g_game.move(ring, c:getSlotPosition(count), 1)
            end
            return
        end
        return
    end

    local item = findItem(3006)
    if not item then item = findItem(3098) end
    if not item then return end
    moveToSlot(item, SlotFinger, 1)
end)

macro(1000, "smart expring", function(m)
    local currentTarget = g_game.getAttackingCreature()
    local monsterName = "Maousy"
    if currentTarget then monsterName = currentTarget:getName() end
    if monsterName == "Ankarah Boss" or monsterName == "Santa Claus" then
        -- impossible to time expring on these, hp readout is completely unreliable.
        return
    end
    local expring = 3006
    local roh = 3098
    local moveDelay = 1
    local hpTriggerPercent = 5
    if monsterName == "Gambon" or monsterName == "Undead Grorlam" or monsterName ==
        "Immortal" then
        hpTriggerPercent = 2
    else
        if monsterName == "Mutated Scarab" then hpTriggerPercent = 5 end
        if monsterName == "Bone Wolf" or monsterName == "Dworc Destroyer" or
            monsterName == "Necropharus" then hpTriggerPercent = 10 end
    end
    local function getMainContainer()
        local containers = g_game.getContainers()
        local first, firstId
        for id, c in pairs(containers) do
            if not firstId or id < firstId then
                firstId = id
                first = c
            end
        end
        return first
    end

    local function moveToMainBp(item, delayMs)
        local main = getMainContainer()
        if not main or not item then return end
        schedule(delayMs, function()
            g_game.move(item, main:getSlotPosition(main:getItemsCount()), 1)
        end)
    end

    local function hasMaousy()
        for _, c in ipairs(getSpectators()) do
            if c:getName() == monsterName and not c:isDead() and
                not c:isRemoved() and c:getHealthPercent() > 0 then
                return true
            end
        end
        return false
    end

    local function hasLowMaousy(multiplier)
        multiplier = multiplier or 1
        for _, c in ipairs(getSpectators()) do
            if c:getName() == monsterName and not c:isDead() and
                not c:isRemoved() and c:getHealthPercent() > 0 then
                if c:getHealthPercent() <= (hpTriggerPercent * multiplier) then
                    return true
                end
            end
        end
        return false
    end

    local function equipRing(id, delayMs)
        local item = findItem(id)
        if item then
            schedule(delayMs, function()
                moveToSlot(item, SlotFinger, 1)
            end)
        end
    end

    local ring = getFinger()

    if hasLowMaousy() then
        if not ring or ring:getId() ~= expring then
            if ring then
                moveToMainBp(ring, moveDelay)
                moveDelay = moveDelay + 300
            end
            equipRing(expring, moveDelay)
            moveDelay = moveDelay + 300
        end
        return
    end

    if ring and ring:getId() == expring and hasLowMaousy(3) then return end

    if not ring or ring:getId() == expring then
        if ring then
            moveToMainBp(ring, moveDelay)
            moveDelay = moveDelay + 300
        end
        equipRing(roh, moveDelay)
        moveDelay = moveDelay + 300
    end
end)

-- macro(1, "takeoded Quest UH script", function()
--     -- if player:getHealth() == player:getMaxHealth() then return end
--     if getManaPercent() >= 12 and player:getHealth() == player:getMaxHealth() then
--         return
--     end
--     g_game.talk("!uh")
-- end)

-- macro(100, "auto uh simple", function(m)
--     -- if player:getHealthPercent() >= 70 then return end
--     g_game.talk("!uh")
-- end)
-- macro(20000, "close npc chat", function()
--     modules.game_console.removeTab("Jordan")
--     modules.game_console.removeTab("Edalla")
--     modules.game_console.removeTab("Dino")
--     modules.game_console.removeTab("Rino")
--     modules.game_console.removeTab("Djinn Hakwer")
--     modules.game_console.removeTab("Dufi")
--     modules.game_console.removeTab("Mad")
--     modules.game_console.removeTab("Mery")
--     modules.game_console.removeTab("Euregio")
--     modules.game_console.removeTab("Tharr")
-- end)
macro(100, "heal Cash", function()
    if 1 == 1 then
        -- g_game.talk('exura sio "Reaper')
        local uh = 3160 -- ID Runy
        local uhItem = g_game.findPlayerItem(uh, 1)
        if not uhItem then return end
        local target = getCreatureByName("Reaper")
        if not target then return end
        if target:getHealthPercent() < 99 then
            g_game.useWith(uhItem, target, 1)
        end
        return
    end
    local cash = getCreatureByName("Reaper")
    if not cash then return end
    if 1 == 2 or cash:getHealthPercent() < 90 then say('exura sio "Reaper') end
end)

macro(10000, "alert target eliminated", function(m)
    local target = g_game.getAttackingCreature()
    local dead = (not target) or target:isDead() or target:isRemoved() or
                     target:getHealthPercent() < 1
    if not dead then return end
    playSound("/sounds/Low_Health.ogg")
    -- playAlarm()
    --    g_game.talk("Target eliminated.")
end)
macro(1000, "auto open bps", function(m)
    local function cleanup()
        m.step = nil;
        m.queue = nil
        m.timeout = 1000
    end
    local function countTable(t)
        local n = 0
        for _ in pairs(t or {}) do n = n + 1 end
        return n
    end
    local function xdelay(ms) m.timeout = ms end

    local containers = g_game.getContainers()
    local function minimizeAllContainers()
        for _, c in pairs(g_game.getContainers()) do
            local w = c.window
            if w then
                if w.minimize and false then
                    w:minimize()
                else
                    w:setContentHeight(34) -- fallback minimize look
                end
            end
        end
    end
    m.step = m.step or 1

    if m.step == 1 and containers and countTable(containers) > 0 then
        local ordered = {}
        for id, c in pairs(containers) do
            table.insert(ordered, {id = id, c = c})
        end
        table.sort(ordered, function(a, b) return a.id < b.id end)

        -- container #2 = loot bp (id 2865)
        local loot = ordered[2] and ordered[2].c
        if loot then
            local free = loot:getCapacity() - loot:getItemsCount()
            if free <= 2 then
                for _, it in ipairs(loot:getItems()) do
                    if it:getId() == 2865 then
                        g_game.open(it, it:getParentContainer())
                        break
                    end
                end
            end
        end

        -- -- container #5 = uh bp (id 2869)
        -- local uhbp = ordered[5] and ordered[5].c
        -- if uhbp and uhbp:getItemsCount() < 2 then
        --     for _, it in ipairs(uhbp:getItems()) do
        --         if it:getId() == 2869 then
        --             g_game.open(it, it:getParentContainer())
        --             break
        --         end
        --     end
        -- end

        cleanup()
        return
    end

    if m.step == 1 then
        local bp = player:getInventoryItem(InventorySlotBack)
        if bp then g_game.open(bp) end
        xdelay(300)
        m.step = 2
        return
    end

    if m.step == 2 then
        m.queue = m.queue or {
            2865, -- loot bp
            -- 2872, --mr  bp
            2871 -- stuff bp
        }
        local id = table.remove(m.queue, 1)
        if id then
            local item = findItem(id)
            if item then g_game.open(item) end
            xdelay(300)
            return
        end
        m.step = 3
        xdelay(500)
        minimizeAllContainers()
        return
    end

    if m.step == 3 then
        -- local uhbp = 2869
        -- local containersNow = g_game.getContainers()
        -- local last = containersNow and containersNow[#containersNow]
        -- local foundUHBP = false
        -- if last then
        --     for _, it in ipairs(last:getItems()) do
        --         if it:getId() == uhbp then
        --             foundUHBP = true
        --             g_game.open(it)
        --             break
        --         end
        --     end
        -- end
        -- if not foundUHBP then g_game.talk('exura "no uh bp') end
        -- xdelay(300)
        m.step = 4
        -- minimizeAllContainers()
        -- return
    end

    if m.step == 4 then
        local containers = g_game.getContainers()
        for _, c in pairs(containers) do
            local w = c.window
            if w then
                if w.minimize and false then
                    w:minimize()
                else
                    w:setContentHeight(34) -- fallback minimize look
                end
            end
        end
        minimizeAllContainers()
        cleanup()
        return
    end
end)
macro(200, "debug", function(m)
    if 1 == 1 then
        if getManaPercent() > 50 then g_game.talk('mana waste "140000') end
        return
    end
    local containers = g_game.getContainers()
    local ordered = {}
    for id, c in pairs(containers) do table.insert(ordered, {id = id, c = c}) end
    table.sort(ordered, function(a, b) return a.id < b.id end)

    local first = ordered[1] and ordered[1].c
    if not first then return end

    local items = first:getItems()
    local dest = player:getPosition()
    for i = #items, 1, -1 do
        local it = items[i]
        if it:getId() ~= 2865 then
            local count = it:isStackable() and it:getCount() or 1
            g_game.move(it, dest, count)
        end
    end
    m.setOff()
end)

-- <smartlooter>
-- local smartLooterTempUntil = 0
-- local smartLooterLastSet = nil
-- local smartLooterRequested = nil

-- local triggerWords = {
--     "amulet of life", "golden helmet", "crystal wand", "soft boots",
--     "magic sword"
-- }

-- local function setMinCap(value)
--     if smartLooterLastSet == value then return end
--     local sel = storage and storage._configs and
--                     storage._configs.targetbot_configs and
--                     storage._configs.targetbot_configs.selected
--     if not sel or sel == "" then return end

--     local data = Config.load("targetbot_configs", sel)
--     data.looting = data.looting or {}
--     data.looting.minCapacity = value
--     Config.save("targetbot_configs", sel, data, "json")

--     TargetBot.setOff()
--     TargetBot.setOn()
--     smartLooterLastSet = value
-- end

-- local function hasTriggerWord(text, caseSensitive)
--     if caseSensitive == nil then caseSensitive = true end
--     local hay = caseSensitive and text or text:lower()
--     for _, word in ipairs(triggerWords) do
--         local needle = caseSensitive and word or word:lower()
--         if hay:find(needle, 1, true) then return true end
--     end
--     return false
-- end

-- onTalk(function(name, level, mode, text, channelId, pos)
--     if mode ~= 8 or channelId ~= 8 then return end
--     if name ~= "System" then return end
--     if not text then return end
--     if not hasTriggerWord(text) then return end

--     smartLooterTempUntil = now + 3000
--     smartLooterRequested = 2
--     -- too slow to run here: setMinCap(smartLooterRequested)
-- end)

-- macro(100, "smartlooter", function()
--     if smartLooterTempUntil > now then
--         if smartLooterRequested ~= 2 then smartLooterRequested = 2 end
--     else
--         smartLooterRequested = 999
--     end

--     if smartLooterRequested ~= nil then setMinCap(smartLooterRequested) end
-- end)
-- </smartlooter>
if 1 == 2 then
    local function getManaPercent()
        return (player:getMana() / player:getMaxMana()) * 100
    end

    local function getHealthPercent()
        return (player:getHealth() / player:getMaxHealth()) * 100
    end

    local function hasManashield()
        -- return (player:getStates() & (1 << 4)) ~= 0
        return player:hasState(16)
    end
    macro(1000, "manatrain", function()
        local hp = getHealthPercent and getHealthPercent() or 0
        local mana = getManaPercent and getManaPercent() or 0
        g_game.talk("hp " .. hp .. " mana " .. mana .. " manashield " ..
                        tostring(hasManashield()))

        if hp > 90 and mana > 90 then g_game.talk('mana waste "10000') end
    end)
end
macro(100, "blueaura", function() g_game.talk("!uh") end)
macro(100, "bump target", function()
    local target = g_game.getAttackingCreature()
    if not target or target:isDead() then
        print("no target or target dead")
        return true
    end

    local ppos = player:getPosition()
    local tpos = target:getPosition()
    if not tpos then return end
    if ppos.z ~= tpos.z then
        print("different floor")
        return true
    end

    local dx = math.abs(ppos.x - tpos.x)
    local dy = math.abs(ppos.y - tpos.y)
    if math.max(dx, dy) ~= 1 then
        print("target not adjacent")
        return true
    end

    local candidates = {}
    for ox = -1, 1 do
        for oy = -1, 1 do
            if not (ox == 0 and oy == 0) then
                local pos = {x = tpos.x + ox, y = tpos.y + oy, z = tpos.z}
                if math.max(math.abs(pos.x - ppos.x), math.abs(pos.y - ppos.y)) ==
                    1 then
                    local tile = g_map.getTile(pos)
                    if tile and tile:isWalkable(false) then
                        table.insert(candidates, pos)
                    end
                end
            end
        end
    end

    if #candidates == 0 then
        print("no reachable adjacent squares")
        return true
    end

    local dest = candidates[math.random(#candidates)]
    local ok, err = pcall(function() g_game.move(target, dest, 1) end)
    if not ok then print("move failed: " .. tostring(err)) end

    return true
end)

function performTimestampAttack()
    local currentAttackTime = (g_glock and g_glock.realMillis and
                                  g_glock.realMillis()) or time
    storage.performTimestampAttack = storage.performTimestampAttack or {}

    currentAttackTime = math.floor(currentAttackTime / 1000)
    if storage.performTimestampAttack.lastAttackTime == currentAttackTime then
        return
    end
    local target = g_game.getAttackingCreature()
    if not target then return end
    storage.performTimestampAttack.lastAttackTime = currentAttackTime
    performSmartUE()
end

macro(1, "Timestamp Attack", function(m) performTimestampAttack() end)
macro(1, "Takeoded-Smart", function(m)

    local hpPercent = getHealthPercent()
    local manaPercent = getManaPercent()
    local isInDanger = false
    if (hpPercent <= 90 or manaPercent < 12) then
        isInDanger = true
        g_game.talk("!uh")
        g_game.talk("!uh")
    end
    local timestamp = time -- g_clock.realMillis()
    local lastMRTime = m.lastMRTime or 0
    if (manaPercent < 90 and (timestamp - lastMRTime) > 300) then
        -- use manarune.
        local idMR = 3201
        local mrItem = g_game.findPlayerItem(idMR, 1)
        if mrItem then
            g_game.use(mrItem)
            m.lastMRTime = timestamp
        end
    end
    if (isInDanger) then
        -- hmmm do nothing?
        return
    end
    local lastUETime = m.lastUETime or 0
    lastUETime = timestamp - lastUETime
    local target = g_game.getAttackingCreature()
    if not target or target:isRemoved() or target:isDead() then
        if (not questing) and lastUETime > 500 and manaPercent >= 80 then
            g_game.talk('mana waste "140000')
            m.lastUETime = timestamp
        end
        return
    end
    if doTimestampedAttack then
        performTimestampAttack()
        return
    end
    if (lastUETime > 500 and manaPercent >= 12) then
        if (not questing) and manaPercent >= 90 then
            g_game.talk("mana waste \"140000")
        else
            performSmartUE()
        end
        m.lastUETime = timestamp
    end
end)
macro(2000, "switch to higher hp", function()
    local target = g_game.getAttackingCreature()
    if not target or target:isDead() then return true end

    local name = target:getName()
    local best = target
    local bestHp = target:getHealthPercent() or 0

    local pos = player:getPosition()
    local specs = g_map.getSpectatorsInRange(pos, false, 3, 3)
    for _, c in ipairs(specs) do
        if c:isMonster() and not c:isDead() and c:getName() == name then
            local hp = c:getHealthPercent() or 0
            if hp > bestHp then
                bestHp = hp
                best = c
            end
        end
    end

    if best ~= target then g_game.attack(best) end

    return true
end)

-- <AUTOFOLLOW>
followName = "autofollow"
if not storage[followName] then storage[followName] = {player = 'name'} end
local toFollowPos = {}

UI.Separator()
UI.Label("Auto Follow")

followTE = UI.TextEdit(storage[followName].player or "name", function(widget,
                                                                      newText)
    storage[followName].player = newText
end)

local followChange = macro(200, "Follow Change", function() end)

local followMacro = macro(20, "Follow", function()
    local target = getCreatureByName(storage[followName].player)
    if target then
        local tpos = target:getPosition()
        toFollowPos[tpos.z] = tpos
    end
    if player:isWalking() then return end
    local p = toFollowPos[posz()]
    if not p then return end
    if autoWalk(p, 20, {ignoreNonPathable = true, precision = 1}) then
        delay(100)
    end
end)
UI.Separator()
onPlayerPositionChange(function(newPos, oldPos)
    if followChange:isOff() then return end
    if (g_game.isFollowing()) then
        tfollow = g_game.getFollowingCreature()

        if tfollow then
            if tfollow:getName() ~= storage[followName].player then
                followTE:setText(tfollow:getName())
                storage[followName].player = tfollow:getName()
            end
        end
    end
end)

onCreaturePositionChange(function(creature, newPos, oldPos)
    if creature:getName() == storage[followName].player and newPos then
        toFollowPos[newPos.z] = newPos
    end
end)
-- </AUTOFOLLOW>

questing = true
-- questing = false
doTimestampedAttack = false
if questing then
    --
else
    --
end

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment