A full-featured bot for the MMORPG Tibia, built in Python using computer vision, a hardware-level anti-detection layer, and real-time telemetry. Originally a study project to deeply learn systems programming, computer vision, and game automation.
This bot was used in production on a global server character. The character was banned after ~30 consecutive hours of hunting.
Even with hardware-level anti-detection (Arduino Leonardo as a USB HID device + capture card), BattleEye can detect non-human behavioral patterns. Running a bot for 30 straight hours is obviously not human. My recommendation: if you use this, use it in short, controlled sessions and add stamina-based breaks.
๐ด Use at your own risk. Botting violates Tibia's Terms of Service.
This project started as a way to learn things I'd been curious about for a long time โ things you can't just read about, you have to build.
| Technique | What I learned |
|---|---|
| Template Matching | Sliding window search comparing saved templates pixel-by-pixel against a screenshot. Grayscale images significantly speed this up โ less color data = fewer comparisons. |
| Image Hashing (FarmHash64) | O(1) creature lookup by hashing the name strip from the BattleList. Way faster than template matching for known creatures. |
| OCR via pixel analysis | Reading numbers (HP, level, staminaโฆ) by matching digit templates against regions of the screen. |
| HSV color filtering | Detecting HP/Mana bar percentages by analyzing specific pixel colors in known positions. |
| Grayscale optimization | Template matching compares pixel by pixel โ reducing 3 channels to 1 gives ~3x speedup. |
- Numba
@njitโ JIT-compiled Python to machine code for hot paths (BFS pathfinding, bar detection, slot counting). Achieved 2โ10x speedups on bottleneck functions. - Adaptive tick rate โ 80ms during combat, 100ms normally, 150ms idle, 500ms paused.
- Middleware frequencies โ not every system runs every tick. HP every tick, creatures every 2 ticks, radar every 3, skills every 10.
- A* for high-level navigation between waypoints (via
tcod). - BFS flood fill (
@njit) for walkable grid analysis inside the game window.
Key concepts I internalized:
- G cost = distance from start
- H cost (heuristic) = estimated distance to goal
- F cost = G + H โ what A* minimizes
- Cross-platform capture pipeline (macOS native vs. Windows capture card โ different pixel pipelines, same interface)
- Hardware abstraction layer โ transparent switching between
pyautogui, Arduino HID, and capture card with zero changes to game logic - Module reload tricks for runtime platform switching in tests
- How
__init__.pyworks as a facade: controls exports and runs setup code on import
The goal was a lightweight bot โ something that could run in the background without hammering the machine. That turned out to be one of the hardest problems to get right.
Computer vision is inherently heavy. Template matching, BFS pathfinding, OCR over multiple screen regions โ all of this runs every tick, multiple times per second. My CPU and memory usage were way higher than I wanted, and the tricky part was that every optimization risked breaking detection accuracy. Lower the confidence threshold to run faster โ more false positives. Skip a frame to save cycles โ miss a creature. It required a lot of careful tuning to find a balance that was both fast enough and reliable enough.
Things I tried that didn't work as expected:
-
Rewriting in Rust โ I spent some time exploring a Rust rewrite hoping for better memory management and lower overhead. The result was actually worse โ higher CPU and memory than the Python version. I didn't invest much time here (rewriting the entire detection pipeline would've taken months), and I may have made mistakes due to inexperience with Rust. But at least for my tests, Python + Numba outperformed my Rust attempt.
-
GPU parallelization โ I explored offloading some of the CPU-bound work to the GPU to parallelize the heavier detection functions. I didn't go deep enough to make it really work โ I managed some small gains but nothing meaningful for the overall loop. This is probably worth revisiting properly, but it would require a more fundamental rethink of the pipeline.
What ended up actually helping: middleware frequency tuning (not every module runs every tick), Numba JIT on the hottest paths, grayscale everywhere, and caching results that don't change frame-to-frame. Small wins that added up.
| Tab | Description |
|---|---|
| Dashboard | Real-time bot health โ ticks, creatures, HP/MP, active pipeline modules, battle list, recent events |
| Settings | Start/Pause/Stop controls, tick rate, window target, module toggles (healing, cavebot, loot, overlay), server save config |
| Cavebot | Load/loop routes, waypoint list, refill thresholds, deposit options |
| Healing | Configure HP/Mana thresholds for potions and spells |
| Targeting | Whitelist or blacklist mode, creature list |
| Spell Attack | AoE vs single-target spell groups, mana reserve protection |
| Hardware | Choose input mode (Software / Capture Card / Arduino / Full Hardware), serial port detection |
| Recorder | Record waypoints live while walking โ Walk, Ladder, Rope, Shovel, Stairs โ exports to JSON route |
| Status | Live character stats: CPU/RAM, HP/MP bars, capacity, food, speed, stamina, coordinates, creatures in range |
| Diagnostics | Subsystem health dots, Arduino/Telemetry connection status, stuck/reconnect/safe-mode flags, recent errors |
| Feature | Description |
|---|---|
| Cavebot | Route-based navigation with looping, depot return, holes/ropes/stairs/ladders |
| Auto-healing | Observer pattern โ reacts to HP/Mana drops. Spells + potions with configurable thresholds |
| Targeting | Whitelist or blacklist. Attacks nearest creature in range |
| Spell attack | Configurable AoE and single-target spell groups with mana reserve |
| Loot system | Opens corpses, collects selected items |
| Auto-refill | Goes to NPC when potions run low, buys stock, deposits gold/loot, returns to hunt |
| Stuck detection | Detects movement freeze and triggers audio alert |
| Anti-trap | Detects when surrounded and attempts escape |
| Server save | Detects save time, gracefully disconnects, reconnects after |
| Auto-reconnect | Logs back in automatically after disconnection |
| Food auto-eat | Keeps character fed |
| Module | What it detects |
|---|---|
| BattleList | Creature names โ FarmHash64 O(1) lookup + template matching fallback for hash collisions |
| GameWindow | Creature positions, HP bars, walkable tiles |
| StatusBar | HP and Mana percentages (pixel color analysis) |
| Skills Panel | Level, experience, HP, mana, capacity, speed, food, stamina (OCR) |
| Radar | Current coordinate (X, Y, Z) from minimap |
| Inventory | Container/backpack slot detection |
| ActionBar | Cooldown state |
| Connection | Login screen and character selection detection |
The GUI exposes four input modes:
| Mode | Description |
|---|---|
| Software | pyautogui + mss. Works on macOS/Linux. Detectable on Windows. |
| Capture Card | pyautogui input + capture card for screenshots. Bypasses Tibia's screenshot block on Windows. |
| Arduino HID + mss | Arduino Leonardo acts as a real USB keyboard/mouse. Tibia never sees Python โ it sees a HID device. |
| Full Hardware | Arduino HID + capture card. Completely external to the PC. |
The Arduino Leonardo is flashed with custom HID firmware (firmware/) and communicates over serial at 115200 baud โ one text command per line.
Despite all of this โ my character got banned. BattleEye doesn't need to see your keyboard driver. It watches behavior. No human hunts for 30 hours straight.
The bot streams data to a companion platform โ tibiaeye-monorepo โ via HTTP + WebSocket:
- Kill events, loot, XP โ batched HTTP
- Live character position โ WebSocket (every tick)
- Livemap โ real-time map of where your character is walking
- XP/h charts, kill counts, session history, browser notifications (death, low HP, stuck)
src/
โโโ repositories/ # Visual data extraction (pure functions + thin facade)
โ โโโ battlelist/ # FarmHash64 + template matching
โ โโโ gamewindow/ # BFS pathfinding, creature bar detection
โ โโโ statusbar/ # HP/Mana % from pixel colors
โ โโโ skills/ # OCR digit recognition
โ โโโ radar/ # Minimap coordinate extraction
โ โโโ inventory/ # Container slot detection
โโโ gameplay/ # Bot logic (functional pipeline)
โ โโโ gameloop.py # Middleware pipeline (Context โ Context)
โ โโโ cavebot/ # Navigation + combat decisions
โ โโโ core/tasks/ # Stateful task system (the only OOP layer)
โโโ healing/ # Auto-heal observers
โโโ hardware/ # Abstraction: software / Arduino / capture card
โโโ telemetry/ # HTTP + WebSocket data streaming
โโโ gui/ # CustomTkinter interface
โโโ wiki/ # Static game data (creatures, spells, cities)
The game loop is a pure functional middleware pipeline โ each step is Context โ Context, inspired by PyTibia:
Screenshot โ StatusBar โ BattleList โ GameWindow โ Radar โ Skills
โ
handle_cavebot()
โ
orchestrator.do()
โ
Healing observers
As the detection modules grew โ BattleList, GameWindow, StatusBar, Skills panel, Radar, ActionBar, Pathfinding โ manually verifying each one after every change became painful and slow. I'd tweak the HP bar detection, run the bot, stare at the screen, and still not be sure if the skills OCR had regressed.
The real challenge wasn't testing a single module in isolation. It was the combinatorial explosion of scenarios that all needed to work correctly at the same time:
- BattleList with 1 monster, 2, 4, 9 โ and with zero monsters
- BattleList while actively attacking vs. not attacking
- GameWindow with a monster visible and a clear HP bar
- GameWindow with multiple monsters overlapping, or a monster at low HP
- GameWindow while running away from a monster vs. engaging it
- StatusBar at 100% HP, at 30% (healing threshold), at critical levels
- Skills panel with different stamina values, different capacity amounts
- Radar identifying the correct floor and coordinate
- The full gameplay decision: given this exact screenshot, does the bot choose to attack, walk, or refill?
Every time I changed something in creature detection, I had to mentally replay all these scenarios and manually confirm nothing broke. It was slow, error-prone, and I kept catching regressions too late.
So I built a custom E2E framework (e2e/) that runs the full detection pipeline against a library of real game screenshots captured on both macOS and Windows.
The problem it solved: instead of launching the bot and eyeballing results, I could run a single command and immediately know which modules were working, which had regressed, and exactly what each one was returning โ with visual annotated output images to inspect.
How it works:
- Each screenshot has a companion
.expected.jsondescribing what the bot should detect in that scene (creature count, names, HP range, skills values, pathfinding decision, etc.) - The runner imports directly from
src/repositories/โ zero reimplementation, tests the real code - Results are compared against expectations and any mismatch is flagged as a regression
- Annotated images are generated with overlays showing detected bars, creature bounding boxes, BFS walkable grid, path to target, and skills values โ making it visual and fast to diagnose
# Run all scenarios
python e2e/main.py
# Filter by OS, image, or module
python e2e/main.py --os macos
python e2e/main.py --image rotworm
python e2e/main.py --module battlelist
# Visual output with annotated overlays
python e2e/main.py --image rotworm --side-by-side
# Update expected values after an intentional change
python e2e/main.py --update-expected
# Capture a new screenshot into the suite
python e2e/capture.py --os macos --name new_scenarioWhat each module validates:
| Module | Checks |
|---|---|
battlelist |
Exact creature count, names (set equality), attack target |
gamewindow |
Creature count, false positives, HP bar noise, attack state |
statusbar |
HP and Mana within expected range |
skills |
Exact values โ level, XP, HP, mana, capacity, speed, stamina |
radar |
Coordinate found |
pathfinding |
Walkable tile count, path exists, path length |
gameplay |
Correct decision (attack/walk/refill), correct target, task sequence |
Screenshots are split by OS (macos/, windows/) because the capture method changes pixel values โ macOS uses the native screen capture API, Windows goes through a capture card.
The framework works well for this use case. There's definitely room to improve it โ better diff output, coverage tracking per creature, parallel execution โ but it already saved a lot of manual testing time and caught several regressions during development.
git clone https://github.com/GGotha/tibiaeye.git
cd tibiaeye
python -m venv venv
source venv/bin/activate # macOS/Linux
# venv\Scripts\activate # Windows
pip install -r requirements.txt
python gui.py # GUI mode
python main.py --help # CLI mode- Flash
firmware/tibiaeye_hid/tibiaeye_hid.inoto an Arduino Leonardo via Arduino IDE. - Connect via USB.
- In the GUI โ Hardware tab, select the mode and serial port.
I genuinely enjoyed building this โ the computer vision pipeline, hardware abstraction, pathfinding, Numba JIT, a real visual testing framework. There's still a lot I wanted to explore: stamina-aware character rotation, smarter anti-detection heuristics, deeper pathfinding algorithms.
But other priorities in life got in the way and I had to step back. I intend to return to this project and the broader study of bot engineering and algorithmic performance in the future.
- Stamina-based auto-logout โ detect when stamina drops below X โ walk to depot โ logout โ switch to alt character. This would've avoided the ban.
- Death detection + recovery flow
- Player detection (log out if another player enters the hunt area)
- Combo spell sequencing
| Layer | Technology |
|---|---|
| Computer vision | OpenCV, NumPy |
| Performance | Numba JIT (@njit) |
| Hashing | FarmHash64 (pyfarmhash) |
| Pathfinding | tcod (A*), custom BFS (Numba) |
| GUI | CustomTkinter |
| Screen capture | mss (macOS/Linux), OpenCV capture card (Windows) |
| Hardware | Arduino Leonardo, pyserial |
| Telemetry | requests, websocket-client |
| Testing | pytest, custom E2E visual framework |
| Project | Description |
|---|---|
| tibiaeye-monorepo | Node.js monorepo โ NestJS API + React dashboard + livemap |
| PyTibia | The original open-source bot this project was inspired by |
![]() Settings |
![]() Live Status |
![]() Cavebot & Routes |
![]() Route Recorder |
![]() Diagnostics |
![]() Hardware Modes |
Built for learning. Used in production. Got banned.






