using System; using System.Collections.Generic; using System.Threading; using robotManager.Helpful; using robotManager.Products; using wManager.Wow.Class; using wManager.Wow.Helpers; using wManager.Wow.ObjectManager; public class Main : ICustomClass { private bool _isLaunched; private bool _healingInProgress; // === Settings === private const int InnervateManaPercentThreshold = 30; private const float MeleeRange = 6f; // hitbox-aware, 5f was too tight // === Debug throttle === private int _lastDebugLog; private Spell MarkOfTheWild = new Spell("Mark of the Wild"); private Spell OmenOfClarity = new Spell("Omen of Clarity"); // manual 30-min buff (your server/version) private Spell CatForm = new Spell("Cat Form"); private Spell Barkskin = new Spell("Barkskin"); private Spell Innervate = new Spell("Innervate"); private Spell HealingTouch = new Spell("Healing Touch"); private Spell FaerieFire = new Spell("Faerie Fire (Feral)"); private Spell Mangle = new Spell("Mangle (Cat)"); private Spell FerociousBite = new Spell("Ferocious Bite"); public float Range { get { return MeleeRange; } } public void Initialize() { _isLaunched = true; _lastDebugLog = 0; Logging.Write("[Feral] Simple TBC Feral Druid (Cat) FC initialized."); Rotation(); } public void Dispose() { _isLaunched = false; Logging.Write("[Feral] Simple TBC Feral Druid (Cat) FC stopped."); } public void ShowConfiguration() { } private void Rotation() { Logging.Write("[Feral] Rotation loop started."); while (_isLaunched) { try { if (!Products.InPause && !ObjectManager.Me.IsDeadMe) { HealRotation(); BuffRotation(); ManaRotation(); if (!_healingInProgress && Fight.InFight && ObjectManager.Target != null && ObjectManager.Target.IsValid && ObjectManager.Target.IsAttackable) { CombatRotation(); } } } catch (Exception ex) { Logging.WriteError("[Feral] ERROR: " + ex); } Thread.Sleep(50); } } // ============================================= // HEAL ROTATION // ============================================= private void HealRotation() { WoWLocalPlayer me = ObjectManager.Me; if (me.IsMounted || me.IsCast) return; if (me.HealthPercent < 50 && HealingTouch.KnownSpell && me.ManaPercentage > 40 && !me.IsCast) { bool inCombat = Fight.InFight || me.InCombat; if (inCombat && ShouldSkipHealBecauseTargetAlmostDead()) { return; } _healingInProgress = true; try { // Barkskin before HT - only in combat if (inCombat && Barkskin.KnownSpell && Barkskin.IsSpellUsable && !me.HaveBuff("Barkskin") && !me.IsCast) { Logging.Write("[Feral] Casting Barkskin before heal"); Barkskin.Launch(); Thread.Sleep(Usefuls.Latency + 150); WaitSpellUsable(HealingTouch, 2000); } // Healing Touch auto-drops Cat Form if (!me.IsCast && HealingTouch.IsSpellUsable) { Logging.Write("[Feral] Casting Healing Touch (HP=" + me.HealthPercent + "%)"); HealingTouch.Launch(); Usefuls.WaitIsCasting(); // Shift back to cat WaitSpellUsable(CatForm, 2000); if (!me.IsDeadMe && !me.IsCast && CatForm.KnownSpell && CatForm.IsSpellUsable && !me.HaveBuff("Cat Form")) { CatForm.Launch(); Thread.Sleep(Usefuls.Latency + 100); } } } finally { _healingInProgress = false; } } } private void WaitSpellUsable(Spell spell, int timeoutMs) { int start = Environment.TickCount; while ((Environment.TickCount - start) < timeoutMs) { if (ObjectManager.Me.IsDeadMe) return; if (!ObjectManager.Me.IsCast && spell.IsSpellUsable) return; Thread.Sleep(50); } } private bool ShouldSkipHealBecauseTargetAlmostDead() { WoWUnit target = ObjectManager.Target; if (target == null) return false; if (!target.IsValid || !target.IsAlive || !target.IsAttackable) return false; if (!target.IsTargetingMeOrMyPet) return false; if (target.HealthPercent >= 10) return false; int attackers = CountAttackersTargetingMeOrMyPet(); return attackers == 1; } private int CountAttackersTargetingMeOrMyPet() { int count = 0; List units = ObjectManager.GetObjectWoWUnit(); for (int i = 0; i < units.Count; i++) { WoWUnit u = units[i]; if (u == null) continue; if (!u.IsAlive) continue; if (!u.IsAttackable) continue; if (!u.InCombat) continue; if (!u.IsTargetingMeOrMyPet) continue; count++; } return count; } // ============================================= // BUFF ROTATION // ============================================= private void BuffRotation() { if (!Fight.InFight && !ObjectManager.Me.InCombat) return; int markTimeLeft = ObjectManager.Me.BuffTimeLeft("Mark of the Wild"); int omenTimeLeft = ObjectManager.Me.BuffTimeLeft("Omen of Clarity"); if (MarkOfTheWild.KnownSpell && MarkOfTheWild.IsSpellUsable && markTimeLeft < 60000 && !ObjectManager.Me.IsCast) { Logging.Write("[Feral] Refreshing Mark of the Wild"); MarkOfTheWild.Launch(); Thread.Sleep(Usefuls.Latency + 100); } if (OmenOfClarity.KnownSpell && OmenOfClarity.IsSpellUsable && omenTimeLeft < 60000 && !ObjectManager.Me.IsCast) { Logging.Write("[Feral] Refreshing Omen of Clarity"); OmenOfClarity.Launch(); Thread.Sleep(Usefuls.Latency + 100); } } // ============================================= // MANA / INNERVATE ROTATION // ============================================= private void ManaRotation() { WoWLocalPlayer me = ObjectManager.Me; if (me == null) return; if (me.IsDeadMe || me.IsCast || Products.InPause) return; if (!Fight.InFight && !me.InCombat) return; if (!Innervate.KnownSpell || !Innervate.IsSpellUsable) return; if (me.ManaPercentage >= InnervateManaPercentThreshold) return; if (me.HaveBuff("Innervate")) return; if (!me.IsCast && Innervate.IsSpellUsable) { Logging.Write("[Feral] Casting Innervate (Mana=" + me.ManaPercentage + "%)"); Innervate.Launch(); Thread.Sleep(Usefuls.Latency + 150); WaitSpellUsable(CatForm, 2000); if ((Fight.InFight || me.InCombat) && !me.IsDeadMe && !me.IsCast && CatForm.KnownSpell && CatForm.IsSpellUsable && !me.HaveBuff("Cat Form") && !_healingInProgress) { CatForm.Launch(); Thread.Sleep(Usefuls.Latency + 100); } } } // ============================================= // COMBAT ROTATION // ============================================= private void CombatRotation() { if (_healingInProgress) return; WoWUnit target = ObjectManager.Target; if (target == null || !target.IsValid || !target.IsAlive) return; // --- Tap denied check --- bool isTapDenied = Lua.LuaDoString("return UnitIsTapDenied('target')"); if (isTapDenied && !target.IsTargetingMeOrMyPet) { Logging.Write("[Feral] Target tagged by other player, skipping"); Fight.StopFight(); Interact.ClearTarget(); return; } // --- Evade check --- if (target.InCombat && !target.IsTargetingMeOrMyPet && target.GetMove && !target.Fleeing) { Logging.Write("[Feral] Target in evade mode, skipping"); Fight.StopFight(); Interact.ClearTarget(); return; } WoWLocalPlayer me = ObjectManager.Me; if (me.IsCast) return; bool inCatForm = me.HaveBuff("Cat Form"); // --- Enter Cat Form if not in it --- if (!inCatForm) { if (CatForm.KnownSpell && CatForm.IsSpellUsable) { Logging.Write("[Feral] Entering Cat Form for combat"); CatForm.Launch(false, true, false); Thread.Sleep(Usefuls.Latency + 200); } return; } // FIX: Use GetDistance (hitbox-aware) instead of Position.DistanceTo (center-to-center) // Position.DistanceTo measured center-to-center, so large mobs read as 5.0+ // even when in melee range. GetDistance accounts for hitbox. float dist = target.GetDistance; uint energy = me.Energy; int comboPoints = me.ComboPoint; // === Periodic debug log (every ~2 seconds) === int now = Environment.TickCount; if ((now - _lastDebugLog) >= 2000) { _lastDebugLog = now; Logging.Write("[Feral] DEBUG: Energy=" + energy + " CP=" + comboPoints + " Dist=" + dist.ToString("F2") + " MangleUsable=" + Mangle.IsSpellUsable + " FBUsable=" + FerociousBite.IsSpellUsable + " FFUsable=" + FaerieFire.IsSpellUsable + " GCD=" + SpellManager.GlobalCooldownTimeLeft() + " TargetHP=" + target.HealthPercent); } // --- Faerie Fire (ranged, instant, 6s CD) --- if (FaerieFire.KnownSpell && FaerieFire.IsSpellUsable && !target.HaveBuff("Faerie Fire (Feral)") && dist <= 30f) { Logging.Write("[Feral] Casting Faerie Fire (Dist=" + dist.ToString("F2") + ")"); FaerieFire.Launch(false, false, true); Thread.Sleep(Usefuls.Latency + 100); return; } // --- Ferocious Bite at 5 CP --- if (FerociousBite.KnownSpell && FerociousBite.IsSpellUsable && comboPoints >= 5 && energy >= 35 && dist <= MeleeRange) { Logging.Write("[Feral] Casting Ferocious Bite (CP=" + comboPoints + " Energy=" + energy + " Dist=" + dist.ToString("F2") + ")"); FerociousBite.Launch(false, false, true); Thread.Sleep(Usefuls.Latency + 100); return; } // --- Mangle (main filler) --- if (Mangle.KnownSpell && Mangle.IsSpellUsable && energy >= 40 && dist <= MeleeRange) { Logging.Write("[Feral] Casting Mangle (Energy=" + energy + " Dist=" + dist.ToString("F2") + ")"); Mangle.Launch(false, false, true); Thread.Sleep(Usefuls.Latency + 100); return; } // --- Debug: why are we idle? --- if ((now - _lastDebugLog) < 200) { if (dist > MeleeRange) { Logging.Write("[Feral] IDLE: Out of melee range (Dist=" + dist.ToString("F2") + ")"); } else if (energy >= 40 && !Mangle.IsSpellUsable) { Logging.Write("[Feral] IDLE: Mangle not usable despite energy=" + energy + " GCD=" + SpellManager.GlobalCooldownTimeLeft() + " IsCast=" + me.IsCast); } } } }