keen to have a look thanks very muchCopilotBuddy — HonorBuddy for WotLK 3.3.5a
Five months in. Reverse-engineering HB 4.3.4 and adapting it for WoW Wrath of the Lich King 3.3.5a (build 12340).
People have been asking for a working HB on WotLK for years. So I built one.
View attachment 5400
View attachment 5399
What's done:
- Navigation — based on HB WoD/Legion navigation.
- Bot API — 98% parity with HB 4.3.4. Some parts had to be adapted or stubbed since they simply don't exist in WotLK.
- UI — HB WoD version.
- Singular — ported from HB 4.3.4.
If you know XML and a bit of C#, and you want to help — test, write profiles, report bugs or nav blackspots — DM me.
WotLK original only. Build 12340. No Warmane, no custom servers.
Discord has a dedicated section for discussion and bug reports.
[Hidden content]
dm me in discordthanks! currently working on a 1.12 bot, will use your tips on navigation to improve it
UPDATE 8
==========================
New:
--------------
* Overlay system (port from HB 6.2.3)
- OverlayManager, OverlayWindowBase, OverlayControl ported from HB 6.2.3
- ToastOverlayWindow and WidgetOverlayWindow implemented; replaces the
old toast-only notification path
- UIOverlay: Overlay_ControlBar and Overlay_StatusDisplay — in-game
overlay displaying bot status, current action and context without
switching focus to the main window
- CapabilityManager / CapabilityFlags / CapabilityState / CapabilityStateChangedArgs
ported from HB 6.2.3; bot capabilities (movement, combat, loot, etc.)
now managed via named flags rather than ad-hoc booleans
- BotEvents start/stop wired to CapabilityManager; TreeRoot.Shutdown
integrated; overlay lifecycle tied to bot start/stop
- Requires the BuddyControlPanel plugin to be active — the overlay is
not created automatically; StyxWoW.IsOverlayActive (safe, non-creating
check) is used throughout the bot to guard all overlay calls when the
plugin is absent
* Unified WoW Data Extractor (standalone tool — separate workspace)
- New standalone C# / WPF application replacing the previous collection
of separate extraction utilities
- Combines Map, Mmap, Vmap and Road extractors in a single pipeline;
all four extraction passes run from one UI without requiring a running
server or build-time dependencies
- MPQ archive reading (StormLib via P/Invoke); ADT, WDT, DBC, WMO,
M2 parsing fully implemented in managed C#
- Navmesh generation via the native RecastBuilderDll (Recast/Detour C++);
outputs .mmap / .map / .vmap / .roadmap files compatible with
Navigation.dll at runtime
- WPF UI with per-map progress reporting, cancellation, and structured
log output
- CLI mode available for scripted / headless batch extraction
- Output format matches the 4×4 sub-tile layout (see Navigation section
below); tileWidth = 133.333 yards (ADT / 4), maxTiles derived from
ADT count × 16
* DataBinImporter utility (Tools/DataBinImporter)
- Standalone CLI tool that supplements data.bin with NPC entries from a
TrinityCore 3.3.5a world database
- Conservative strategy: only inserts entry IDs not already present in
data.bin; preserves all existing HB-sourced data
- data.bin NPC database updated with TrinityCore 3.3.5a world import:
Category Before After Delta
─────────────────────────────────────────────
Vendor 2,016 2,919 +903
Repair 645 1,436 +791
ReagentVendor 118 879 +761
ClassTrainer 308 521 +213
FoodVendor 324 353 +29
ProfTrainer 473 482 +9
Flightmaster 150 156 +6
Innkeeper 123 125 +2
─────────────────────────────────────────────
Total 8,440 9,573 +1,133
* DataBinInspector utility (Tools/DataBinInspector)
- Read-only diagnostic tool; dumps data.bin content with NPC flag
breakdown (vendor, trainer, flight master, repair, etc.)
- Useful for verifying DataBinImporter output without modifying the DB
Navigation — 4×4 sub-tile system:
--------------
* Tile format migrated from 1 ADT = 1 Detour tile → 1 ADT = 4×4 = 16
Detour tiles (MMAP_MULTI_TILE_VERSION = 5, matching original HB and
HB 6.2.3 Tripper)
- Sub-tile size: 133.333 yards (533.333 / 4)
- loadMap(mapId, x, y) loads all 16 sub-tiles for an ADT in a single
call; unload removes all 16
- MeshMapCalculator added (Tripper/Navigation): converts between WoW ADT
coordinates and Detour sub-tile coordinates; origin at ADT [32, 32]
(world 0, 0), formula: detourX = (adtX - 32) × 4 + subX
- TileIdentifier improved: sub-tile awareness, correct bounds calculation
- WorldMeshManager updated for sub-tile load/unload lifecycle
- Navigator query filters refactored into WowQueryFilter helpers —
avoids duplicated filter setup across query sites
* Navigation.dll (C++) — major overhaul
- MAX_POLYS_BUFFER and MAX_PATH_LENGTH raised from 740 → 8192
(matches HB 6.2.3 Tripper values)
- Navmesh query pool size raised from 65 535 → 748 983
(matches HB 6.2.3 WorldMeshManager)
- dtNodeIndex widened from unsigned short → unsigned int to support
meshes with more than 65 535 nodes
- Area costs updated to full HB 6.2.3 set (Ground=1.66, Water=3.33,
Lava=55, Road=1.0, Fall=1.7, Fly=1.0, Swim=2.5) — replaces the
placeholder costs used in prior builds
- NavTerrain enum migrated from bitmask to HB 6.2.3 sequential area IDs
- NavLog callback added (SetNavLogCallback_C): CalculatePathEx reports
FAILED / DT_PARTIAL_RESULT results back to the managed C# logger
- Corridor length exposed in NavStats and surfaced to managed code
- Movemap grid handling updated to match sub-tile layout
- Null guards added after fopen() in loadMapData / loadMap to prevent
crash on missing tile files
* Flightor
- CanFly: explicit map guard added — flying only attempted on Outland
(530) and Northrend (571); prevents aerial path attempts on private
servers where IsFlyableArea() incorrectly returns true in old-world
zones
- Dalaran removed from the hardcoded no-fly zone list in Navigator;
flying was legal in Dalaran in 3.3.5a (restriction introduced in
Cataclysm); IsFlyableArea() via Lua is now the authority
- Ground nav fallback (all three sites in MoveTo): Z-projection removed;
destination passed as-is to Navigator.MoveTo() — projecting to player
Z was causing CalculatePathEx FAILED results on sloped terrain
* MeshNavigator
- EnsureTiles timing probe: Stopwatch around EnsureTilesAroundPosition;
logs to diagnostic channel if > 2 ms
- PathRegen: diagnostic log at regen trigger (destChanged / needsRegen /
destination)
- GeneratePath: Stopwatch added; logs map, distance and elapsed ms to
diagnostic channel when path generation exceeds 5 ms
- Intermediate diagnostics (EnsureTiles probe, PathRegen trigger) removed
in final pathing adjust pass — only the GeneratePath timing kept
* Navigator (Styx)
- No-fly zone list corrected: Dalaran (4395) removed (was wrongly listed);
only true indoor dungeon areas retained (Pit of Saron 4613, Halls of
Reflection 4820)
Bug fixes — Core:
--------------
* Flight path system
- TaxiNodeInfo: new class reading current taxi node from CGTaxiMap memory
(dword_C0D7EC); used to correlate the player's current taxi position
with the DBC node table
- FlightPaths: rewritten to use TaxiNodeInfo DBC for node and connection
discovery; previously relied on incomplete static data
- GlobalOffsets: taxi node base and count offsets added
* LevelBot
- Flight master blacklisted if TaxiFrame fails to open after approach;
prevents the bot from approaching the same flight master repeatedly
when the NPC is not responding
- Vendor and mailbox interaction log messages corrected (wrong NPC type
was printed in several paths)
* TreeRoot
- Frame lock removed from the no-lock execution path; was acquiring the
lock unnecessarily when LockType.None was selected
- HonorbuddyUnableToStartException used consistently at startup failure
sites instead of plain Exception
* WoWPulsator
- Per-stage timing diagnostics removed from the pulse loop; the overhead
was measurable on lower-end hardware and the data was not acted on
* KillBetweenHotspots
- Ignored when bot was unmounted — mobs between hotspots were skipped
even with KillBetweenHotspots=true; now evaluated regardless of mount
state
* Blackspot
- Marking a blackspot in Eastern Kingdoms (MapId = 0) failed silently
due to an integer zero-check treating MapId=0 as "not set"; fixed
* WoWUnit
- Attackable: flag changed from UnitFlags.NotAttackable → UnitFlags.ImmuneToPC
(0x100); matches HB 4.3.4 flag_6 — critters and immune mobs were
incorrectly considered attackable with the old flag
* StyxWoW
- IsOverlayActive property added: returns true only if the overlay has
already been created by a plugin; does not trigger lazy creation,
safe to call when BuddyControlPanel is not loaded
* ProfileManager
- LoadProfileForLevel: logs selected sub-profile name and level range
at debug level
- GetProfileForLevel: logs each skipped sub-profile (name, level range,
continent, current level/map) at debug level — aids profile debugging
- LoadProfile: logs the file path being loaded at debug level
* Profile
- ProfileException during XML parse: was silently written to debug log;
now written to the main log at error level with "Profile parse error:"
prefix
* Blacklist
- Add(): logs the GUID (hex) and duration at debug level every time an
entry is blacklisted
* Settings
- Save/Load: XElement root name now encoded via XmlConvert.EncodeName;
fixes crash when the settings class name contains characters invalid
in XML element names
* ToastNotifier
- OnLevelUp / OnPlayerDied: replaced direct StyxWoW.Overlay access with
StyxWoW.IsOverlayActive guard — the old code lazily created and
activated the overlay even when BuddyControlPanel was not loaded
* GameStats
- Stale comment referencing ToastNotifier initialization removed
* BotManager
- Logs bot switch ("Changing current bot to: {name}") before assigning
the new current bot
* ActionMoveToPoi
- Movement delegated to Flightor.MoveTo() (HB 6.2.3 pattern); removed
the duplicate Mount.ShouldMount / Navigator.MoveTo path — Flightor
already dispatches to Navigator internally for ground navigation
* MainWindow
- Log dispatch uses DispatcherPriority.Background; heavy navigation
debug logging no longer starves UI input events (clicks, keyboard)
* Mount
- CancelShapeshift: logs the form being cancelled at debug level before
calling CancelShapeshiftForm()
Bug fixes — Singular wotlk:
--------------
* Druid
- Feral: rotation priority order corrected; several edge cases in cat
form energy management fixed
- Lowbie: WaitForCast guard added (was missing for Druid; introduced in
Update 7 for Priest only)
* Paladin
- Retribution: rotation updated, priority order corrected
- Common: shared paladin logic updated (seals, aura management)
* Priest
- Shadow: DoT refresh logic corrected; Mind Flay clipping fixed
* Warlock
- Demonology: rotation updated
- Common: shared warlock helpers updated (pet management, shard handling)
- Destruction / Affliction: minor fixes carried forward from Update 7
Shaman pass
* Hunter
- Beast Mastery, Marksmanship, Survival: all three specs refreshed;
pet ability usage and trap timing corrected
- Common: ranged attack range guards unified across specs
* Mage
- Common: shared mage logic updated (polymorph targeting, blink safety)
* Warrior
- Lowbie: melee chase logic added when target kites out of melee range
(same fix applied to Paladin/Hunter lowbie in Update 7; Warrior was
missed)
* Helpers
- CompositeBuilder: minor improvements to composite construction helpers
- Spell.cs: minor corrections
Version numbering:
--------------
This release: v1.3.0 (Update 8).
Previous: v1.2.0 (Update 7).
==========================
UPDATE 9
==========================
Bug fixes — Core:
--------------
* SourceCompiler (Styx/Loaders)
- Added System.Text.Json.dll, System.Text.Encodings.Web.dll and
System.Buffers.dll to the essential Roslyn reference list — modern
.NET plugins (JsonSerializer, JsonPropertyName, ArrayPool) failed to
compile at runtime with the previous reference set
* TreeRoot
- AFK handling rewritten to match HB 4.3.4 ns3/Class3.cs pattern:
ResetAfk() is now called proactively on every tick so the
LastHardwareAction never goes stale during idle periods (waiting
for respawn, standing, etc.)
- Reactive AFK check every 30 s: if WoW still reports the player as
AFK (e.g. after reconnect or login), a spacebar KeyUpDown is sent
to dismiss the AFK dialog — guarded by UnitIsAFK("player") Lua call
* WoWClient
- PerformanceCounter: replaced the hardcoded struct offsets at
0x00D417AC with a pointer-based read of dword_D4159C (IDA-verified
3.3.5a 12340: PerformanceCounter() reads sub_86ADC0((double*)dword_D4159C))
- Struct layout: +0 multiplier (double), +8 timer type (uint; 2=QPC,
else GetTickCount), +24 base (double) — null-pointer fallback to
GetTickCount() if WoW has not initialized the timer struct yet
* WoWMovement
- CtmTurnSpeed property added: overrides the CTM turn-speed field
at CTM_Base + 0x4 (0xCA11DC) after every native CTM call. The
game normally overwrites this with CMovementData.TurnSpeed (or π)
on each invocation, so the override must be applied *after* the
call. Null = use game default
* LocalPlayer
- IsGhost: removed the && CorpsePoint != WoWPoint.Empty guard. The
corpse-point field can read as zero right after release
(timing/offset race), causing IsGhost=false during ghost state
and bypassing the MountUp dead/ghost guard — a mount-while-dead
bug. Now returns base.IsGhost unconditionally
* Navigator (Styx/Logic/Pathing)
- MoveTo: returns MoveResult.Moved early when the player is casting,
so a mount cast in progress is not interrupted by an in-flight
movement request
* StuckHandler
- Stuck detection now follows the HB 6.2.3 Class469 pattern: only
evaluate "we are stuck" when Navigator.PathDistance returns a
value. Treating null (Detour partial path / off-mesh terrain) as
zero caused false stuck detections during normal looting and
micro-adjust movement
* MailFrame (Styx/Logic/Inventory/Frames/MailBox)
- Replaced the hand-rolled Lua string-builder + GetReturnValues
bag/slot lookup with WoWItem.UseContainerItem() on each
attachment — eliminates the per-mail Lua heap allocation and the
fragile string-find on item links (which broke on localized item
names)
- Per-attachment sleep reduced 250 ms → 100 ms
- SendMailWithManyAttachments batch size raised 11 → 12
- Added "Something is messed up, we didn't attach all of our items."
warning when SendMailItems.Length != attachments.Length after the
UseContainerItem loop
- Take(N) + RemoveRange batch slicing replaces the TakeWhile/Count
11 pattern, fixing the off-by-one when the remaining itemList
count was exactly 11
* GatherBuddy
- GatherBuddySettings: BlacklistTimer default 20 s → 45 s, and
MinFreeBagSlots default 2 → 1 — gives the bot a longer retry
window on contested nodes and a tighter bag-full signal
- GatherbuddyBot: Pulse() override added; sets
Navigator.PathPrecision = Clamp(Me.MovementInfo.CurrentSpeed * 0.15f,
1.5, 10) every pulse so arrival tolerance scales with current
movement speed (mounted vs on-foot)
- NeedsMailing: removed the "mailboxClose && bagsNearlyFull" double
condition; now trips solely on FreeBagSlots <= MinFreeBagSlots
(matches MailToAlt semantics: go to mailbox only when bags hit
the configured threshold)
- Mailbox approach: uses Flightor.MoveTo(distance 10) when
Flightor.CanFly, falls back to Navigator.MoveTo on the ground
- Node blacklist: only blacklists a node for 2 s when the loot
frame was actually visible — was previously blacklisting on the
!Combat branch, which fired even when the gather attempt was
aborted before any loot interaction
* LevelBot (Bots/Grind)
- Ghost-corpse retrieval: replaced the
Levelbot.Decorators.Death.DecoratorNeedToTakeCorpse wrapper with a
direct Decorator(ctx => IsGhost && Distance(CorpsePoint) < 40f)
around CreateCorpseRetrievalBehavior() — matches HB 4.3.4
LevelBot.smethod_89 verbatim and skips the now-misnamed decorator
class
Bug fixes — Singular wotlk:
--------------
* Druid
- Balance: removed the Nature's Grace TalentManager.GetCount(1, 7)
branch from the Moonfire refresh condition. The check was
causing the spell to refresh far more often than the aura
duration warranted; the NG→haste proc is already handled by the
rotation's normal timing. Applied in all three rotation blocks
(single-target, AOE, Moonfire refresh)
- Feral: Prowl is now gated on SpellManager.HasSpell("Pounce") so
low-level druids (< 30, no Pounce) don't enter stealth only to
be unable to follow up with the Pounce opener — Prowl was
slowing approach speed without providing a payoff
- Common (precombat): added Thorns cast on self when the aura is
not present (xyFaded contribution)
* Paladin
- Retribution: CreateRetributionPaladinHeal() rewritten
- Holy Light no longer gated on !HasSpell(Flash of Light); was
never being cast once FoL was trained regardless of the health
threshold
- Both heals now use their dedicated settings
(Paladin.HolyLightHealth / Paladin.FlashOfLightHealth) instead
of the shared RetributionHealHealth value
- Lay on Hands wired up at last: emergency heal cast when
HealthPercent <= LayOnHandsHealth and Forbearance is not
active (LoH would otherwise be wasted on the debuff)
- Priority order: Lay on Hands (emergency) → Holy Light → Flash
of Light
- Common: Blessing of Wisdom selection implemented in
CreatePaladinBlessBehavior(). Wisdom was previously a TODO; the
Kings guard only excluded Might (so picking Wisdom still cast
Kings) and the Might branch had no Wisdom exclusion either, so
selecting Wisdom in settings silently kept casting Kings. Now:
Wisdom branch casts Blessing of Wisdom on all party/raid
members within 40 yd missing the aura, and both Kings and
Might branches correctly bail out when Wisdom is selected
* Mage
- Common: Conjure Food no longer spins in an infinite loop for
low-level mages — item 5349 (Conjured Muffin) was missing from
MageFoodIds, so Gotfood always returned false until the first
cast actually landed
- Common: Conjure Water added (was completely absent) with a full
MageWaterIds table covering levels 5/15/25/35/45/55/60/65 plus
the Conjured Water 5350 fallback
- Common: Conjure Food and Conjure Water replaced Spell.BuffSelf
with an explicit Decorator + Sequence + WaitContinue pattern
(MoveStop → Cast → IsCasting → !IsCasting → Gotfood/Gotwater
gate), matching the existing Ritual of Refreshment sequence in
the same file and reliably waiting for the cast + bag refresh
before re-evaluating
* Priest
- Shadow: added Smite filler below Mind Flay for low-level
shadow priests without Mind Flay trained (< 11 Shadow talent
points). Previously these characters fell through to the wand
fallback even at full mana
* Warlock
- Common: NeedToCreateHealthStone rewritten — ActualSpell on
item-use spells is null when the spell is not in the player's
spellbook, so the previous
CarriedItems.Any(... ActualSpell.Name.Contains("Healthstone"))
check missed every healthstone the player actually owned. Now
matches against a static HealthstoneEntries table (10 entries)
covering all WotLK ranks: Minor (5512), Lesser (5511),
Healthstone (5509), Greater (5510), Major (9421), Fel (22103),
Demonic (36892/36893/36894/36895)
- Demonology: PreCombatBuffs now falls back to Pet.CreateSummonPet
("Imp") when Summon Felguard is not available (Demonology
sub-30), instead of failing the summoning branch entirely
* Hunter
- Common: added PetManager.PetTimer.Reset() inside the
!GotAlivePet branch of the pet-revive flow so the pet-respawn
timer doesn't carry over from a previous death and force an
immediate re-attempt
* Rogue
- All three specs (Assassination / Combat / Subtlety): Slice and
Dice infinite-spam fix
- Spell.BuffSelf replaced with Spell.Cast("Slice and Dice",
ret => StyxWoW.Me, ...): BuffSelf blocks refresh when the
buff exists; Cast bypasses the auto-HasAura short-circuit
- Condition now uses RawComboPoints and gates GetAuraTimeLeft
on !HasAura("Slice and Dice") so the rotation only
re-evaluates timing when the buff is actually present
- Removed the redundant !HasAura check from the original
condition
- All three specs: Rupture Spell.Buff checks now null-guard
CurrentTarget (was causing a NullReferenceException on target
swap / death mid-rotation)
- All three specs: stealth-pull openers now require
CurrentTarget.Distance < Spell.MeleeRange
- Garrote: added MeleeRange check on top of MeIsBehind
(Combat, Subtlety)
- Cheap Shot: added MeleeRange check (Combat, Subtlety)
- Affects all 9 stealth branches across the three specs
Bug fixes — WoW Data Extractor (WMO / GameObject placement):
--------------
* RecastBuilderDll (native)
- Detour tile bounds now use coreBmin/coreBmax (the true sub-tile
bbox) instead of bmin/bmax (the Recast border-expanded
rasterization bbox). The previous code shifted each tile's
Detour origin by ~borderMeters (1.33 m on default settings),
causing every WMO / M2 / doodad embedded in the tile to render
at the wrong position. Matches the rcVcopy(params.bmin, bmin)
step from the original MapBuilder.cpp
* AdtParser (src/Formats/Adt/Parsing)
- ParseMcnkPrimaryTexture renamed to
ParseMcnkRoadOrPrimaryTexture; now iterates all MCLY layers of
an MCNK, not just layer 0. Roads in WotLK are routinely painted
as a secondary or blended layer, so the previous single-layer
read was assigning the wrong texture ID to road-capable chunks
- Added IsRoadTexture(string) matcher: returns true for any
texture whose name contains "road", "cobblestone",
"path_stone" or "bridgefloor"
- When a road-capable texture is found in any layer, that ID
wins over the layer-0 primary texture
* MmapExtractorService (src/MmapExtractor)
- WMO (MODF) and M2 (MDDF) rotation matrix construction fixed.
Previous code built Rx(ax) * Ry(ay) * Rz(az) and used positive
angles, which mismatched the C++ G3D fromEulerAnglesXYZ(...)
convention and the G3D column-vector / row-vector multiply
inversion. Result: every WMO and every M2 was rotated by the
wrong set of Euler angles, producing the "WMO/GameObject mal
placé" symptom (geometry present but offset/rotated)
- New construction: Rz(az) * Ry(ay) * Rx(ax) with the correct
angle assignment (ax = RotZ, ay = RotX/RotY per binary order,
az = RotY/RotX per binary order). Applied identically to both
MODF (WMO) and MDDF (M2) code paths
* offmesh.txt
- Map 1, tile 39,33: added Durotar Valley of Trials tunnel (orc
starting zone) — two-way off-mesh link between the cave
entrance coords, radius 2.0
- Map 571, tile 20,26: Borean Tundra elevator (Horde side, near
Warsong Hold) — the previous entry was a Storm Peaks label
(GO 152614) with copy-paste X,Y on both endpoints and an
incorrect bottom Z. Replaced with the correct Borean Tundra
coords (top 2891.7466 6236.0654 208.91, bottom 2877.88 6223.36
105.38), radius 3.0
Version numbering:
--------------
This release: v1.4.0 (Update 9).
Previous: v1.3.0 (Update 8).
==========================