Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ nExBot is a modular Tibia bot that automates hunting, healing, navigation, and a
| **TargetBot** | AI combat with 9-stage priority scoring, behavior learning, wave prediction, and movement coordination |
| **Hunt Analyzer** | Real-time session analytics — kills/hour, XP/hour, profit, Hunt Score, efficiency insights |
| **Containers** | Auto-open, quiver management, and container role assignments |
| **Follow Player** | Party hunt companion — stays near leader while attacking monsters |
| **Extras** | Anti-RS, alarms, equipment swapping, conditions, combo system, push max |

---
Expand Down Expand Up @@ -96,6 +97,11 @@ Enable CaveBot and TargetBot, press **Start** (`Ctrl+Z`), and monitor progress i
- **Monster Insights** — 12 SRP modules that learn monster behavior in real-time
- **Movement coordination** — intent-based voting resolves wave avoidance, keep-distance, AoE positioning, and chase

### 👥 Follow Player — Party Hunt
- **Stay near leader** — attacks monsters but never walks past the party leader
- **Parallel mode** — walks toward leader while attacking (ASM stays active via forceWalk)
- **Lost leader recovery** — walks to last known position for up to 10 seconds

### 🧭 CaveBot — Navigation
- **Walking engine v4.0** — smooth autoWalk pipelining (5+ tiles), step pipelining (2-step lookahead), PathCursor preservation, adaptive recovery with path validation and exponential-decay blacklists
- **15+ waypoint types** — goto, label, action, buy, sell, lure, standLure, depositor, travel, imbuing, tasker, withdraw
Expand Down Expand Up @@ -158,6 +164,7 @@ _Loader.lua (entry point)
| ⚔️ [AttackBot](docs/ATTACKBOT.md) | Attack spells, runes, AoE optimization |
| 🧭 [CaveBot](docs/CAVEBOT.md) | Navigation, waypoints, supply management |
| 🎯 [TargetBot](docs/TARGETBOT.md) | Combat AI, Monster Insights, movement |
| 👥 [Follow Player](docs/FOLLOW.md) | Party hunt companion — stay near leader |
| 📦 [Containers](docs/CONTAINERS.md) | Container management, quiver system |
| 📊 [Hunt Analyzer](docs/SMARTHUNT.md) | Session analytics and insights (SmartHunt) |
| 🛠️ [Extras & Tools](docs/EXTRAS.md) | Safety, equipment, utilities |
Expand Down
7 changes: 2 additions & 5 deletions _Loader.lua
Original file line number Diff line number Diff line change
Expand Up @@ -329,7 +329,6 @@ do

if not nExBot._clientPrinted then
nExBot._clientPrinted = true
print("[nExBot] Client detected: " .. tostring(nExBot.clientName) .. " (" .. tostring(nExBot.clientType) .. ")")
end
end

Expand All @@ -349,7 +348,6 @@ local function autoDetectClient(attempt, maxAttempts)
nExBot.isOpenTibiaBR = acl.isOpenTibiaBR()

if newType ~= prevType or nExBot.clientName ~= prevName then
print("[nExBot] Client detected (late): " .. tostring(nExBot.clientName) .. " (" .. tostring(newType) .. ")")
end

if nExBot.isOpenTibiaBR then
Expand All @@ -366,7 +364,6 @@ local function autoDetectClient(attempt, maxAttempts)
table.insert(keys, k)
end
end
print("[nExBot] Client signals: signals=" .. table.concat(keys, ","))
end
end
return
Expand All @@ -393,6 +390,8 @@ loadCategory("constants", {
-- ============================================================================
loadCategory("utils", {
"utils/shared",
"utils/shared_helpers",
"utils/storage_engine",
"utils/ring_buffer",
"utils/client_helper",
"utils/safe_creature",
Expand Down Expand Up @@ -444,7 +443,6 @@ loadCategory("features_legacy", {
"pushmax",
"combo",
"HealBot",
"new_healer",
"AttackBot",
})

Expand All @@ -455,7 +453,6 @@ loadCategory("tools_legacy", {
"ingame_editor",
"Dropper",
"Containers",
"container_opener",
"quiver_manager",
"quiver_label",
"tools",
Expand Down
62 changes: 25 additions & 37 deletions cavebot/actions.lua
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ local getClient = nExBot.Shared.getClient
local getClientVersion = nExBot.Shared.getClientVersion

local oldTibia = getClientVersion() < 960
local nextTile = nil

-- Throttle table for unknown floor-change minimap color warnings (once per tile+color)
local warnedUnknownFloor = {}
Expand All @@ -14,8 +13,6 @@ local warnedUnknownFloor = {}
local DIR_MOD_LOOKUP = Directions.DIR_TO_OFFSET

-- Direction-offset helper using Directions module
local nextPos = nil -- creature
local nextPosF = nil -- furniture
local function modPos(dir)
local mod = DIR_MOD_LOOKUP[dir]
if mod then
Expand Down Expand Up @@ -262,7 +259,6 @@ CaveBot.registerAction("delay", "#AAAAAA", function(value, retries, prev)
local random
local final


if #data == 2 then
random = tonumber(data[2]:trim())
end
Expand Down Expand Up @@ -334,16 +330,6 @@ end)
The walkTo function now handles path caching internally.
]]

-- Walk strategy enum
local WALK_STRATEGY = {
DIRECT = 1,
ATTACK_BLOCKER = 2,
FAILED = 3
}

-- Direction offset lookup (reuse canonical table)
local DIR_OFFSET = DIR_MOD_LOOKUP

-- Check if path is blocked by attackable monster
local function getBlockingMonster(playerPos, destPos, maxDist)
-- Only check if we're close to destination
Expand All @@ -361,7 +347,7 @@ local function getBlockingMonster(playerPos, destPos, maxDist)

-- Check first step for blocking monster
local dir = path[1]
local offset = DIR_OFFSET[dir]
local offset = DIR_MOD_LOOKUP[dir]
if not offset then return nil end

local checkPos = {
Expand Down Expand Up @@ -468,25 +454,10 @@ CaveBot.registerAction("goto", "green", function(value, retries, prev)
CaveBot.ensureNavigatorRoute(playerPos.z)
end

-- ========== FLOOR CHECK ==========
if destPos.z ~= playerPos.z then
return false, true
end

-- ========== FORWARD PASS CHECK ==========
-- If the navigator confirms the player has already passed this WP on the route,
-- advance immediately. This handles smooth walk-through transitions where A* paths
-- carry the player past a WP before the goto action's arrival check fires.
if WaypointNavigator and WaypointNavigator.hasPassedWaypoint then
local currentAction = ui and ui.list and ui.list:getFocusedChild()
local waypointIdx = currentAction and ui.list:getChildIndex(currentAction) or nil
if waypointIdx and WaypointNavigator.hasPassedWaypoint(playerPos, waypointIdx, destPos) then
CaveBot.clearWaypointTarget()
return true
end
end

-- ========== FLOOR-CHANGE TILE DETECTION ==========
-- ========== FLOOR-CHANGE TILE DETECTION (before floor check) ==========
-- Allow goto to target floor-change tiles on different floors — the bot walks
-- there, steps on the tile, and waits for the Z transition. This is how the bot
-- recovers when stuck on the wrong floor (e.g. fell down a hole).
local Client = getClient()
local minimapColor = (Client and Client.getMinimapColor) and Client.getMinimapColor(destPos) or (g_map and g_map.getMinimapColor(destPos)) or 0
local isFloorChange = (FloorItems and FloorItems.isFloorChangeTile) and FloorItems.isFloorChangeTile(destPos) or false
Expand All @@ -499,8 +470,6 @@ CaveBot.registerAction("goto", "green", function(value, retries, prev)
elseif minimapColor == 212 or minimapColor == 213 then
expectedFloorAfterChange = destPos.z + 1
end
-- Fallback: if minimap color didn't match known floor-change colors,
-- default to destPos.z so downstream logic always has a value
if expectedFloorAfterChange == nil then
expectedFloorAfterChange = destPos.z
local warnKey = destPos.x .. "," .. destPos.y .. "," .. destPos.z .. ":" .. tostring(minimapColor)
Expand All @@ -511,6 +480,26 @@ CaveBot.registerAction("goto", "green", function(value, retries, prev)
end
end

-- ========== FLOOR CHECK ==========
-- Non-floor-change WPs on a different floor → instantFail.
-- Floor-change WPs on a different floor → allowed (walk there, wait for Z change).
if destPos.z ~= playerPos.z and not isFloorChange then
return false, true
end

-- ========== FORWARD PASS CHECK ==========
-- If the navigator confirms the player has already passed this WP on the route,
-- advance immediately. This handles smooth walk-through transitions where A* paths
-- carry the player past a WP before the goto action's arrival check fires.
if WaypointNavigator and WaypointNavigator.hasPassedWaypoint then
local currentAction = ui and ui.list and ui.list:getFocusedChild()
local waypointIdx = currentAction and ui.list:getChildIndex(currentAction) or nil
if waypointIdx and WaypointNavigator.hasPassedWaypoint(playerPos, waypointIdx, destPos) then
CaveBot.clearWaypointTarget()
return true
end
end

-- ========== ARRIVAL PRECISION ==========
-- Adaptive: scale precision by distance to next goto WP to prevent zone overlap.
-- Floor-change WPs keep precision=0 (must step on the exact tile).
Expand Down Expand Up @@ -599,7 +588,6 @@ CaveBot.registerAction("goto", "green", function(value, retries, prev)
-- Walk precision matches arrival precision minus 1: A* stops at the zone
-- boundary rather than overshooting to the center.
local walkParams = {
ignoreNonPathable = true,
precision = isFloorChange and 0 or math.max(0, precision - 1),
allowFloorChange = isFloorChange
}
Expand Down
3 changes: 1 addition & 2 deletions cavebot/bank.lua
Original file line number Diff line number Diff line change
Expand Up @@ -84,9 +84,8 @@ CaveBot.Extensions.Bank.setup = function()
})
end


onTalk(function(name, level, mode, text, channelId, pos)
if CaveBot.isOff() then return end
if not CaveBot or not CaveBot.isOff or CaveBot.isOff() then return end
if mode == 51 and text:find("Your account balance is") then
balance = getFirstNumberInText(text)
end
Expand Down
4 changes: 2 additions & 2 deletions cavebot/buy_supplies.lua
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,8 @@ CaveBot.Extensions.BuySupplies.setup = function()
table.insert(possibleItems, v.id)
end

for id, values in pairs(Supplies.getItemsData()) do
id = tonumber(id)
for key, values in pairs(Supplies.getItemsData()) do
local id = tonumber(key)
if table.find(possibleItems, id) then
local max = values.max
local current = player:getItemsCount(id)
Expand Down
Loading