using System; using System.Collections.Generic; using System.ComponentModel; using System.IO; using System.Threading; using robotManager.Helpful; using robotManager.Products; using robotManager.FiniteStateMachine; using wManager; using wManager.Events; using wManager.Wow; using wManager.Wow.Class; using wManager.Wow.Helpers; using wManager.Wow.ObjectManager; using MemoryRobot; // ============================================================ // SETTINGS CLASS // ============================================================ [Serializable] public class HunterBMSettings_TBCBM : Settings { [Category("Feign Death")] public bool UseFeignDeath { get; set; } [Category("Aspects")] public bool UseAspectOfHawk { get; set; } [Category("Aspects")] public bool UseAspectOfViper { get; set; } [Category("Aspects")] public int ViperManaThreshold { get; set; } [Category("Aspects")] public int HawkManaThreshold { get; set; } [Category("Core abilities")] public bool UseHuntersMark { get; set; } [Category("Core abilities")] public int HuntersMarkManaMin { get; set; } [Category("Core abilities")] public bool UseMendPet { get; set; } [Category("Core abilities")] public int MendPetThreshold { get; set; } [Category("Attack abilities")] public bool UseSerpentSting { get; set; } [Category("Attack abilities")] public bool UseArcaneShot { get; set; } [Category("Attack abilities")] public bool UseSteadyShot { get; set; } [Category("Attack abilities")] public bool UseRaptorStrike { get; set; } [Category("Attack abilities")] public bool UseMongooseBite { get; set; } [Category("Attack abilities")] public bool UseKillCommand { get; set; } [Category("Cooldowns")] public bool UseBestialWrath { get; set; } [Category("Cooldowns")] [Description("Use Bestial Wrath only on elite targets")] public bool BestialWrathOnElite { get; set; } [Category("Cooldowns")] [Description("Minimum number of enemies attacking to use Bestial Wrath (0 = ignore)")] public int BestialWrathEnemiesMin { get; set; } [Category("Cooldowns")] [Description("Use Bestial Wrath when target level >= player level + this value (0 = ignore)")] public int BestialWrathLevelDiff { get; set; } [Category("Cooldowns")] public int BestialWrathManaMin { get; set; } [Category("Cooldowns")] public bool UseIntimidation { get; set; } [Category("Cooldowns")] public bool UseRapidFire { get; set; } [Category("Auto Shot low mana mode")] public bool UseAutoShotLowMana { get; set; } [Category("Auto Shot low mana mode")] public int AutoShotManaThreshold { get; set; } [Category("Aggro management")] public bool UseAggroManagement { get; set; } [Category("Aggro management")] public bool UseLowestHpTargeting { get; set; } [Category("MULTIPULL")] public bool UseMultiPull { get; set; } [Category("MULTIPULL")] public int MultiPullMaxMobs { get; set; } [Category("MULTIPULL")] public int MultiPullRange { get; set; } [Category("MULTIPULL")] [Description("Disable multipull when HP % below this value")] public int MultiPullMinHpPercent { get; set; } [Category("FAST LOOTING")] public bool UseFastLooting { get; set; } [Category("FAST LOOTING")] public int FastLootMaxDistance { get; set; } [Category("FAST LOOTING")] public float FastLootInteractDistance { get; set; } [Category("Attack abilities")] [Description("Use Multi-Shot when fighting 2+ mobs")] public bool UseMultiShot { get; set; } [Category("Attack abilities")] [Description("Minimum mana % to use Multi-Shot")] public int MultiShotManaMin { get; set; } [Category("Ability thresholds (HP % of target)")] public int SerpentStingHpMin { get; set; } [Category("Ability thresholds (HP % of target)")] public int SerpentStingManaMin { get; set; } [Category("Ability thresholds (HP % of target)")] public int BestialWrathHpMin { get; set; } [Category("Ability thresholds (HP % of target)")] public int IntimidationHpMin { get; set; } [Category("Ability thresholds (HP % of target)")] public int RapidFireHpMin { get; set; } [Category("Ability thresholds (HP % of target)")] public int ArcaneHpMin { get; set; } [Category("Ability thresholds (HP % of target)")] public int ArcaneManaMin { get; set; } [Category("Ability thresholds (HP % of target)")] public int SteadyShotHpMin { get; set; } [Category("Ability thresholds (HP % of target)")] public int SteadyShotManaMin { get; set; } public HunterBMSettings_TBCBM() { UseFeignDeath = true; UseAspectOfHawk = true; UseAspectOfViper = true; ViperManaThreshold = 60; HawkManaThreshold = 80; UseHuntersMark = true; HuntersMarkManaMin = 20; UseMendPet = true; MendPetThreshold = 80; UseSerpentSting = true; UseArcaneShot = false; UseSteadyShot = true; UseMultiShot = false; MultiShotManaMin = 40; UseRaptorStrike = false; UseMongooseBite = false; UseKillCommand = true; UseBestialWrath = true; UseIntimidation = true; UseRapidFire = true; UseAutoShotLowMana = true; AutoShotManaThreshold = 10; UseAggroManagement = true; UseLowestHpTargeting = true; UseMultiPull = false; MultiPullMaxMobs = 2; MultiPullRange = 30; MultiPullMinHpPercent = 50; UseFastLooting = false; FastLootMaxDistance = 15; FastLootInteractDistance = 4.5f; SerpentStingHpMin = 70; SerpentStingManaMin = 50; BestialWrathOnElite = false; BestialWrathEnemiesMin = 2; BestialWrathLevelDiff = 4; BestialWrathHpMin = 60; BestialWrathManaMin = 10; IntimidationHpMin = 60; RapidFireHpMin = 60; ArcaneHpMin = 30; ArcaneManaMin = 70; SteadyShotHpMin = 20; SteadyShotManaMin = 20; } public static HunterBMSettings_TBCBM CurrentSetting { get; set; } private static string GetSettingsFilePath() { // WRobot обычно хранит настройки в корневой папке в директории "settings". // GetSettingsFilePath() /*deprecated*/ может отсутствовать в некоторых сборках. string baseDir = Others.GetCurrentDirectory; if (string.IsNullOrEmpty(baseDir)) baseDir = Environment.CurrentDirectory; string dir = baseDir + "\\settings"; try { if (!Directory.Exists(dir)) Directory.CreateDirectory(dir); } catch { // fallback: если не удалось создать папку, пишем в temp dir = Others.GetTempFolder; if (string.IsNullOrEmpty(dir)) dir = Path.GetTempPath(); } return dir + "\\HunterBM_TBC.xml"; }public static HunterBMSettings_TBCBM Load() { string path = GetSettingsFilePath(); if (File.Exists(path)) { CurrentSetting = Load(path); } else { CurrentSetting = new HunterBMSettings_TBCBM(); } return CurrentSetting; } public void Save() { string path = GetSettingsFilePath(); Save(path); } } // ============================================================ // MAIN FIGHTCLASS // ============================================================ public class Main : ICustomClass { private HunterBMSettings_TBCBM Settings; private bool _isLaunched; private readonly Random _humanRnd = new Random(); private Spell RevivePet = new Spell("Revive Pet"); private Spell CallPet = new Spell("Call Pet"); public Spell MendPet = new Spell("Mend Pet"); public Spell AspectHawk = new Spell("Aspect of the Hawk"); public Spell AspectViper = new Spell("Aspect of the Viper"); public Spell HuntersMark = new Spell("Hunter's Mark"); public Spell RaptorStrike = new Spell("Raptor Strike"); public Spell MongooseBite = new Spell("Mongoose Bite"); public Spell SerpentSting = new Spell("Serpent Sting"); public Spell ArcaneShot = new Spell("Arcane Shot"); public Spell SteadyShot = new Spell("Steady Shot"); public Spell MultiShot = new Spell("Multi-Shot"); public Spell RapidFire = new Spell("Rapid Fire"); public Spell Intimidation = new Spell("Intimidation"); public Spell BestialWrath = new Spell("Bestial Wrath"); private Spell FeignDeath = new Spell("Feign Death"); public Spell KillCommand = new Spell("Kill Command"); public Spell AutoShot = new Spell("Auto Shot"); private bool _rangeEventRegistered = false; // Raptor Strike timeout tracking private DateTime _raptorStrikeActivatedTime = DateTime.MinValue; private DateTime _lastHappinessCheck = DateTime.MinValue; private bool _petIsHungry = false; private DateTime _lastFeedAttempt = DateTime.MinValue; private DateTime _lastPetAttackTick = DateTime.MinValue; private ulong _lastPetAttackTarget = 0; // Aggro management private HashSet _engagedEnemyGuids = new HashSet(); private DateTime _lastAggroCheck = DateTime.MinValue; private DateTime _lastTargetSwitch = DateTime.MinValue; private ulong _priorityTargetGuid = 0; private WoWUnit _priorityTargetUnit = null; // Fast looting private ulong _lastLootedGuid = 0; private DateTime _lastPetCommandTime = DateTime.MinValue; private ulong _lastPetCommandTarget = 0; // Pet send-on-target-change (fast + reliable) private string _lastTargetGuidForPet = ""; private DateTime _petLastSendTime = DateTime.MinValue; private DateTime _petRetryUntil = DateTime.MinValue; private int _petRetryCount = 0; private ulong _fastLootTargetGuid = 0; private DateTime _fastLootMoveStart = DateTime.MinValue; // Swimming range control private DateTime _lastSwimBackward = DateTime.MinValue; private bool _isSwimmingCombat = false; // Anti-jitter: swimming distance control without Thread.Sleep private bool _swimBackingAway = false; private DateTime _swimBackingAwayUntil = DateTime.MinValue; private DateTime _swimBackingAwayLastStart = DateTime.MinValue; private bool _swimForwarding = false; private volatile bool _swimImpulseActive = false; private DateTime _swimForwardUntil = DateTime.MinValue; private DateTime _swimForwardLastStart = DateTime.MinValue; // Distance window underwater private float _swimRetreatStartDist = 7f; // start backing away when closer than this private float _swimRetreatStopDist = 8f; // stop backing away when farther than this private float _swimApproachStartDist = 30f; // start moving forward when farther than this private float _swimApproachStopDist = 25f; // stop moving forward when at/below this private float RANGED_MIN_DISTANCE = 2f; // Минимальная реальная дистанция для ranged атак при 5f в игре, получено опытным путём public float Range { get { // Под водой навмеш/MoveTo часто ломается (фолбэк на ground). // Если дать малый Range (5f), бот будет пытаться подплыть в мили и начнётся петля "вперёд-назад". // Поэтому под водой ставим ОЧЕНЬ большой Range, чтобы WRobot не делал MoveTo вообще. if (_isSwimmingCombat) return 100f; return 33f; } } private void RangeManager() { if (!_rangeEventRegistered) { FightEvents.OnFightLoop += delegate { if (ObjectManager.Target == null || !ObjectManager.Target.IsValid || !ObjectManager.Target.IsAlive) return; // SWIMMING COMBAT: движение вперёд/назад импульсами (как стрейф на суше) if (_isSwimmingCombat) { // Блокируем WRobot навигацию if (MovementManager.InMoveTo) { MovementManager.StopMoveTo(true, 0); } float dist = ObjectManager.Target.GetDistance; // Фейсим для стрельбы if (dist <= 35f && dist >= 5f) MovementManager.Face(ObjectManager.Target); // Слишком далеко — вперёд if (dist > _swimApproachStartDist) { MovementManager.Face(ObjectManager.Target); _swimImpulseActive = true; Move.Forward(Move.MoveAction.DownKey, 0); Thread.Sleep(_humanRnd.Next(800, 1500)); Move.Forward(Move.MoveAction.UpKey, 0); _swimImpulseActive = false; } // Слишком близко — назад else if (dist < _swimRetreatStartDist) { _swimImpulseActive = true; Move.Backward(Move.MoveAction.DownKey, 0); Thread.Sleep(_humanRnd.Next(600, 1200)); Move.Backward(Move.MoveAction.UpKey, 0); _swimImpulseActive = false; } return; } // Raptor Strike timeout (3 секунды) - сбрасываем Auto Shot'ом if (ObjectManager.Me.HaveBuff("Raptor Strike")) { if (_raptorStrikeActivatedTime == DateTime.MinValue) { _raptorStrikeActivatedTime = DateTime.Now; } else if ((DateTime.Now - _raptorStrikeActivatedTime).TotalSeconds > 3) { // Raptor Strike висит больше 3 секунд - сбрасываем Auto Shot StartAttackWithPet(); _raptorStrikeActivatedTime = DateTime.MinValue; Logging.Write("[Hunter] Raptor Strike timeout - cancelled via AutoShot"); } else { // Raptor Strike активен меньше 3 сек - не отходим return; } } else { _raptorStrikeActivatedTime = DateTime.MinValue; } // Strafe вбок если моб в мили и на пете (НЕ под водой) // if (!ObjectManager.Me.IsSwimming && ObjectManager.Target.GetDistance < 5f && ObjectManager.Target.IsTargetingMyPet) // { // if (DateTime.Now.Millisecond % 2 == 0) // { // Move.StrafeLeft(Move.MoveAction.DownKey, 0); // Thread.Sleep(GetHumanStrafeHoldMs()); // Move.StrafeLeft(Move.MoveAction.UpKey, 0); // } // else // { // Move.StrafeRight(Move.MoveAction.DownKey, 0); // Thread.Sleep(GetHumanStrafeHoldMs()); // Move.StrafeRight(Move.MoveAction.UpKey, 0); // } // } float realDist = ObjectManager.Target.GetDistance - ObjectManager.Target.CombatReach - ObjectManager.Me.CombatReach; if (realDist < RANGED_MIN_DISTANCE && ObjectManager.Target.GetDistance < 20f && ObjectManager.Target.IsTargetingMyPet) { Move.Backward(Move.MoveAction.DownKey); Thread.Sleep(_humanRnd.Next(400, 600)); MovementManager.StopMove(); } }; _rangeEventRegistered = true; } } private int GetHumanStrafeHoldMs() { // Было фиксированное 500ms -> делаем более "human-like" около 700ms // Диапазон: 620..900 (можно подправить) return _humanRnd.Next(720, 991); } private void StartAttackWithPet() { // Старт автоатаки + команда пету. Привязка к моменту начала атаки (самое "честное" место). // Важно: не спамим каждую итерацию, но при смене цели отправляем сразу. if (ObjectManager.Target == null || !ObjectManager.Target.IsValid || !ObjectManager.Target.IsAlive) return; ulong tGuid = 0; try { tGuid = (ulong)ObjectManager.Target.Guid.GetHashCode(); } catch { tGuid = 0; } bool targetChanged = tGuid != 0 && tGuid != _lastPetAttackTarget; if (targetChanged) { _lastPetAttackTarget = tGuid; _lastPetAttackTick = DateTime.MinValue; } if (!targetChanged && _lastPetAttackTick != DateTime.MinValue && (DateTime.Now - _lastPetAttackTick).TotalMilliseconds < 400) { // мягкий анти-спам } else { // petattack if (ObjectManager.Pet != null && ObjectManager.Pet.IsValid && ObjectManager.Pet.IsAlive) { Lua.LuaDoString("PetAttack();"); } _lastPetAttackTick = DateTime.Now; } // автоатака (для ханта это старт стрельбы/автошота) Lua.LuaDoString("AttackTarget();"); } private void EnsurePetAttack() { // Send pet instantly on target acquire/change + 1-2 retries. if (ObjectManager.Pet == null || !ObjectManager.Pet.IsValid || !ObjectManager.Pet.IsAlive) return; if (ObjectManager.Target == null || !ObjectManager.Target.IsValid || !ObjectManager.Target.IsAlive) return; string guid = ""; try { guid = ObjectManager.Target.Guid.ToString(); } catch { guid = ""; } if (string.IsNullOrEmpty(guid)) return; // If target changed -> send immediately and open short retry window if (guid != _lastTargetGuidForPet) { _lastTargetGuidForPet = guid; SendPetAttackLuaSafe(); _petLastSendTime = DateTime.Now; _petRetryUntil = DateTime.Now.AddSeconds(2.0); _petRetryCount = 0; // keep old vars for compatibility/debug _lastPetCommandTime = _petLastSendTime; _lastPetCommandTarget = 0; return; } // Retries (covers cases when client hasn't "latched" target yet) if (_petRetryCount < 2 && DateTime.Now < _petRetryUntil) { if ((DateTime.Now - _petLastSendTime).TotalMilliseconds >= 350) { SendPetAttackLuaSafe(); _petLastSendTime = DateTime.Now; _petRetryCount++; _lastPetCommandTime = _petLastSendTime; } } } private void SendPetAttackLuaSafe() { try { // Avoid "There is nothing to attack" and similar client-side rejects string lua = "if UnitExists('target') and UnitCanAttack('player','target') and (not UnitIsDead('target')) then " + " PetFollow(); " + " PetAttack(); " + "end"; Lua.LuaDoString(lua); } catch { // no spam } } private bool Canpoison(WoWUnit unit) { return unit.CreatureTypeTarget != "Elemental" && unit.CreatureTypeTarget != "Mechanical"; } public void Initialize() { Settings = HunterBMSettings_TBCBM.Load(); _isLaunched = true; RangeManager(); Logging.Write("[Hunter BM] Initialized - v3 with Settings, FastLoot, MultiPull, SwimCombat"); Rotation(); } public void Dispose() { _isLaunched = false; _isSwimmingCombat = false; _engagedEnemyGuids.Clear(); Logging.Write("[Hunter BM] Stopped."); } public void ShowConfiguration() { Settings = HunterBMSettings_TBCBM.Load(); var settingsWindow = new System.Windows.Forms.Form(); settingsWindow.Text = "Hunter BM Settings"; settingsWindow.Width = 400; settingsWindow.Height = 500; settingsWindow.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; settingsWindow.MaximizeBox = false; settingsWindow.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen; var propertyGrid = new System.Windows.Forms.PropertyGrid(); propertyGrid.SelectedObject = Settings; propertyGrid.Dock = System.Windows.Forms.DockStyle.Fill; var saveButton = new System.Windows.Forms.Button(); saveButton.Text = "Save && Close"; saveButton.Dock = System.Windows.Forms.DockStyle.Bottom; saveButton.Height = 30; saveButton.Click += (sender, e) => { Settings.Save(); settingsWindow.Close(); }; settingsWindow.Controls.Add(propertyGrid); settingsWindow.Controls.Add(saveButton); settingsWindow.ShowDialog(); } // ============================================================ // EVADE DETECTION // ============================================================ private bool IsTargetEvading(WoWUnit target) { if (target == null || !target.IsValid || !target.IsAlive) return false; if (target.HealthPercent < 40) return false; if (target.HealthPercent > 95 && target.InCombat && !target.IsTargetingMeOrMyPet && target.GetMove) { double hpBefore = target.HealthPercent; Thread.Sleep(300); double hpAfter = target.HealthPercent; if (hpAfter > hpBefore || hpAfter >= 99) { Logging.Write("[Hunter] Target EVADING (HP: " + hpBefore.ToString("F0") + "% -> " + hpAfter.ToString("F0") + "%)"); return true; } } return false; } private bool IsTargetFleeing(WoWUnit target) { if (target == null || !target.IsValid || !target.IsAlive) return false; bool isMoving = target.GetMove; bool isDamaged = target.HealthPercent < 90; bool targetingUs = target.IsTargetingMeOrMyPet; return isMoving && (isDamaged || targetingUs); } // ============================================================ // AGGRO MANAGEMENT // ============================================================ private List GetMobsAttackingMe() { List result = new List(); try { List allUnits = ObjectManager.GetObjectWoWUnit(); for (int i = 0; i < allUnits.Count; i++) { WoWUnit u = allUnits[i]; if (u == null || !u.IsValid) continue; if (!u.IsAlive || !u.IsAttackable) continue; if (!u.IsTargetingMe || u.IsTargetingMyPet) continue; if (u.GetDistance > 45f) continue; if (!u.InCombat) continue; result.Add(u); } } catch (Exception ex) { Logging.Write("[Hunter] GetMobsAttackingMe error: " + ex.Message); } return result; } private List GetAllEngagedMobs() { List result = new List(); try { List allUnits = ObjectManager.GetObjectWoWUnit(); for (int i = 0; i < allUnits.Count; i++) { WoWUnit u = allUnits[i]; if (u == null || !u.IsValid) continue; if (!u.IsAlive || !u.IsAttackable) continue; if (!u.IsTargetingMeOrMyPet) continue; if (u.GetDistance > 45f) continue; if (!u.InCombat) continue; result.Add(u); } } catch (Exception ex) { Logging.Write("[Hunter] GetAllEngagedMobs error: " + ex.Message); } return result; } private bool IsPetTankingMob(WoWUnit mob) { if (mob == null || !mob.IsValid || ObjectManager.Pet == null || !ObjectManager.Pet.IsValid) return false; return mob.IsTargetingMyPet; } private WoWUnit GetLowestHpTarget(List mobs) { if (mobs == null || mobs.Count == 0) return null; WoWUnit lowest = null; double lowestHp = 101; for (int i = 0; i < mobs.Count; i++) { WoWUnit m = mobs[i]; if (m != null && m.IsValid && m.IsAlive) { if (m.HealthPercent < lowestHp) { lowestHp = m.HealthPercent; lowest = m; } } } return lowest; } private bool HandleAggroManagement() { if (!Settings.UseAggroManagement) return false; if (ObjectManager.Pet == null || !ObjectManager.Pet.IsValid || !ObjectManager.Pet.IsAlive) return false; var mobsAttackingMe = GetMobsAttackingMe(); if (mobsAttackingMe.Count > 0) { var priorityTarget = mobsAttackingMe[0]; _priorityTargetGuid = GetUnitGuid(priorityTarget); _priorityTargetUnit = priorityTarget; ulong currentGuid = GetUnitGuid(ObjectManager.Target); if (currentGuid != _priorityTargetGuid) { Logging.Write("[Hunter] AGGRO: Switching to " + priorityTarget.Name); ObjectManager.Me.Target = priorityTarget.Guid; } Lua.LuaDoString("PetAttack();"); if (Settings.UseIntimidation && Intimidation.KnownSpell && Intimidation.IsSpellUsable && priorityTarget.GetDistance <= 35f && ObjectManager.Pet.Position.DistanceTo(priorityTarget.Position) <= 10f) { Intimidation.Launch(); Logging.Write("[Hunter] Intimidation!"); } _lastTargetSwitch = DateTime.Now; return true; } if (_priorityTargetGuid != 0) { if (_priorityTargetUnit == null || !_priorityTargetUnit.IsValid || !_priorityTargetUnit.IsAlive || !_priorityTargetUnit.IsTargetingMe) { _priorityTargetGuid = 0; _priorityTargetUnit = null; } else { ulong currentGuid = GetUnitGuid(ObjectManager.Target); if (currentGuid != _priorityTargetGuid) { ObjectManager.Me.Target = _priorityTargetUnit.Guid; } Lua.LuaDoString("PetAttack();"); return true; } } if (!Settings.UseLowestHpTargeting) return false; if ((DateTime.Now - _lastTargetSwitch).TotalSeconds < 3) return false; var allEngaged = GetAllEngagedMobs(); if (allEngaged.Count <= 1) return false; bool allOnPet = true; for (int i = 0; i < allEngaged.Count; i++) { if (!IsPetTankingMob(allEngaged[i])) { allOnPet = false; break; } } if (!allOnPet) return false; var lowestHp = GetLowestHpTarget(allEngaged); if (lowestHp == null || !ObjectManager.Target.IsValid) return false; ulong myCurrentGuid = GetUnitGuid(ObjectManager.Target); ulong lowestGuid = GetUnitGuid(lowestHp); if (myCurrentGuid == lowestGuid) return false; double currentTargetHp = ObjectManager.Target.HealthPercent; double lowestHpPercent = lowestHp.HealthPercent; if (currentTargetHp - lowestHpPercent > 25 || lowestHpPercent < 10) { ObjectManager.Me.Target = lowestHp.Guid; _lastTargetSwitch = DateTime.Now; Logging.Write("[Hunter] Lowest HP: " + lowestHp.Name + " (" + lowestHp.HealthPercent.ToString("F0") + "%)"); } return false; } private ulong GetUnitGuid(WoWUnit unit) { if (unit == null || !unit.IsValid) return 0; try { return (ulong)unit.Guid.GetHashCode(); } catch { return 0; } } // ============================================================ // FAST LOOTING // ============================================================ private void TryFastLoot() { if (!Settings.UseFastLooting) return; if (Fight.InFight || ObjectManager.Me.InCombat) return; // чтобы не дергать лут во время каста/еды и т.п. if (ObjectManager.Me.IsCast) return; int maxDist = Settings.FastLootMaxDistance; if (maxDist < 6) maxDist = 6; float interactDist = Settings.FastLootInteractDistance; if (interactDist < 3.5f) interactDist = 3.5f; WoWUnit lootable = null; float closestDist = 999f; try { List allUnits = ObjectManager.GetObjectWoWUnit(); for (int i = 0; i < allUnits.Count; i++) { WoWUnit u = allUnits[i]; if (u == null || !u.IsValid) continue; if (!u.IsDead || !u.IsLootable) continue; float dist = u.GetDistance; if (dist > maxDist) continue; ulong guid = (ulong)u.Guid.GetHashCode(); if (guid == _lastLootedGuid) continue; if (dist < closestDist) { closestDist = dist; lootable = u; } } } catch (Exception ex) { Logging.Write("[Hunter] TryFastLoot error: " + ex.Message); return; } if (lootable == null) { _fastLootTargetGuid = 0; _fastLootMoveStart = DateTime.MinValue; return; } ulong lootGuid = (ulong)lootable.Guid.GetHashCode(); // Если до моба надо добежать - добегаем if (lootable.GetDistance > interactDist) { if (_fastLootTargetGuid != lootGuid) { _fastLootTargetGuid = lootGuid; _fastLootMoveStart = DateTime.Now; } // Таймаут на добегание (чтобы не зависнуть) if (_fastLootMoveStart != DateTime.MinValue && (DateTime.Now - _fastLootMoveStart).TotalSeconds > 6) { _fastLootTargetGuid = 0; _fastLootMoveStart = DateTime.MinValue; return; } MovementManager.MoveTo(lootable); return; } // Уже рядом - лутаем _lastLootedGuid = lootGuid; _fastLootTargetGuid = 0; _fastLootMoveStart = DateTime.MinValue; MovementManager.StopMove(); MovementManager.StopMoveTo(); Interact.InteractGameObject(lootable.GetBaseAddress); Thread.Sleep(Usefuls.Latency + 200); // Закрываем лут окно если открыто if (Lua.LuaDoString("return LootFrame:IsVisible()")) { Lua.LuaDoString("for i=1,GetNumLootItems() do LootSlot(i) end"); Thread.Sleep(100); Lua.LuaDoString("CloseLoot()"); } } // ============================================================ // MULTIPULL // ============================================================ // MULTIPULL - NEW VERSION (based on MultiPull2 plugin) // ============================================================ private void TryMultiPull() { if (!Settings.UseMultiPull) return; if (!Fight.InFight || ObjectManager.Target == null || !ObjectManager.Target.IsValid) return; if (ObjectManager.Me.IsCast) return; if (ObjectManager.Me.HaveBuff("Feign Death")) return; // Проверяем HP ханта if (ObjectManager.Me.HealthPercent < Settings.MultiPullMinHpPercent) return; // Не пулим если мало маны if (ObjectManager.Me.ManaPercentage < 20) return; // Проверяем текущее количество атакующих мобов (API WRobot) int currentlyAttacking = ObjectManager.GetNumberAttackPlayer(); if (currentlyAttacking >= Settings.MultiPullMaxMobs) return; // Получаем список GUID мобов, которые УЖЕ атакуют нас HashSet attackingGuids = new HashSet(); try { List attackingUnits = ObjectManager.GetUnitAttackPlayer(); for (int i = 0; i < attackingUnits.Count; i++) { if (attackingUnits[i] != null && attackingUnits[i].IsValid) { attackingGuids.Add(attackingUnits[i].Guid); } } } catch (Exception ex) { Logging.Write("[Hunter] TryMultiPull GetUnitAttackPlayer error: " + ex.Message); } // Ищем нового моба для пула WoWUnit targetMob = null; float closestDist = 999f; try { List allUnits = ObjectManager.GetObjectWoWUnit(); for (int i = 0; i < allUnits.Count; i++) { WoWUnit u = allUnits[i]; // Базовые проверки if (u == null || !u.IsValid) continue; if (!u.IsAlive || !u.IsAttackable) continue; // Пропускаем мобов, которые УЖЕ атакуют нас if (attackingGuids.Contains(u.Guid)) continue; // Пропускаем текущую цель if (ObjectManager.Target != null && u.Guid == ObjectManager.Target.Guid) continue; // Пропускаем пета if (ObjectManager.Pet != null && ObjectManager.Pet.IsValid && u.Guid == ObjectManager.Pet.Guid) continue; // Пропускаем мобов в бою с другими игроками if (u.InCombat && !u.IsTargetingMe && !u.IsTargetingMyPet) continue; // Пропускаем мобов, которые нас уже атакуют (дополнительная проверка) if (u.IsTargetingMeOrMyPet) continue; // Проверка дистанции float dist = u.GetDistance; if (dist > Settings.MultiPullRange || dist < 10f) continue; // Пропускаем если tap denied if (u.IsTapDenied) continue; // Пропускаем мобов в блэклисте WRobot if (wManagerSetting.IsBlackListedAllConditions(u)) continue; // Выбираем ближайшего if (dist < closestDist) { closestDist = dist; targetMob = u; } } } catch (Exception ex) { Logging.Write("[Hunter] TryMultiPull search error: " + ex.Message); return; } if (targetMob == null || !targetMob.IsValid) return; // Проверяем что можем использовать Serpent Sting if (!SerpentSting.KnownSpell || !SerpentSting.IsSpellUsable) return; // Проверяем что моб может быть отравлен if (!Canpoison(targetMob)) return; // Запоминаем текущую цель WoWUnit originalTarget = ObjectManager.Target; try { // КЛЮЧЕВОЙ МОМЕНТ: Переключаемся на нового моба ObjectManager.Me.Target = targetMob.Guid; Thread.Sleep(Usefuls.Latency + 100); // Проверяем что таргет переключился if (ObjectManager.Target == null || !ObjectManager.Target.IsValid || ObjectManager.Target.Guid != targetMob.Guid) { // Не получилось переключиться - возвращаем старый таргет if (originalTarget != null && originalTarget.IsValid) ObjectManager.Me.Target = originalTarget.Guid; return; } // Разворачиваемся к цели if (!ObjectManager.Me.IsCast) { MovementManager.Face(ObjectManager.Target); Thread.Sleep(Usefuls.Latency + 50); } // Проверяем дистанцию перед кастом if (ObjectManager.Target.GetDistance > 35f) { if (originalTarget != null && originalTarget.IsValid) ObjectManager.Me.Target = originalTarget.Guid; return; } // Кастуем Serpent Sting если нет дебаффа if (!ObjectManager.Target.HaveBuff("Serpent Sting")) { // Ждём пока GCD пройдёт (макс ~2 сек) int gcdWait = 0; while (gcdWait < 2000 && ObjectManager.Me.GlobalCooldownTimeLeft > 0) { Thread.Sleep(50); gcdWait += 50; } // Ждём завершения Auto Shot каста если идёт int castWait = 0; while (castWait < 1000 && ObjectManager.Me.IsCast) { Thread.Sleep(50); castWait += 50; } // Повторно проверяем что цель ещё валидна if (ObjectManager.Target == null || !ObjectManager.Target.IsValid || ObjectManager.Target.Guid != targetMob.Guid) { if (originalTarget != null && originalTarget.IsValid) ObjectManager.Me.Target = originalTarget.Guid; return; } // Кастуем if (SerpentSting.IsSpellUsable) { SerpentSting.Launch(); // Ждём подтверждения каста (стинг появится как дебафф когда стрела долетит) // Даём время на GCD + полёт стрелы (~1.5-2 сек на 30 ярдов) int flightWait = 0; int maxFlightWait = 1800 + (int)(targetMob.GetDistance * 20); // базовые 1.8с + ~20мс/ярд while (flightWait < maxFlightWait) { Thread.Sleep(100); flightWait += 100; // Если дебафф появился - стрела долетела try { if (targetMob.IsValid && targetMob.HaveBuff("Serpent Sting")) break; } catch { break; } } Logging.Write("[Hunter] MultiPull: " + targetMob.Name + " (dist: " + targetMob.GetDistance.ToString("F1") + ", waited: " + flightWait + "ms)"); } else { Logging.Write("[Hunter] MultiPull: Serpent Sting not usable after GCD wait"); } } } catch (Exception ex) { Logging.Write("[Hunter] TryMultiPull cast error: " + ex.Message); } finally { // ВСЕГДА возвращаем таргет на оригинальную цель try { if (originalTarget != null && originalTarget.IsValid && originalTarget.IsAlive) { ObjectManager.Me.Target = originalTarget.Guid; Thread.Sleep(50); } } catch { // Ignore } } } // ============================================================ // MAIN ROTATION // ============================================================ // SWIMMING COMBAT // ============================================================ // Под водой навмеш WRobot'а часто ломается и бот фолбечит на ground-movement. // Если Range маленький, WRobot пытается подплыть в мили, затем наша логика отходит назад, // и получается дребезг "вперёд-назад". Решение: // 1) Динамический Range — большой (100f) под водой, чтобы WRobot НЕ делал MoveTo // 2) OnFightLoop — StopMove/StopMoveTo каждый тик в swim-combat // 3) Дистанцию держим сами короткими импульсами без Thread.Sleep // Вызывается из Rotation loop — обновляет флаг + backward private void SwimmingRangeControl() { bool swimming = ObjectManager.Me.IsSwimming; bool inFight = Fight.InFight; // Только обновляем флаг — движение теперь в OnFightLoop if (swimming && inFight && ObjectManager.Target != null && ObjectManager.Target.IsValid && ObjectManager.Target.IsAlive) _isSwimmingCombat = true; else _isSwimmingCombat = false; // Каждый тик превентивно блокируем WRobot навигацию // НО не трогаем когда наш импульс активен if (_isSwimmingCombat && !_swimImpulseActive) { MovementManager.StopMoveTo(true, 3000); if (MovementManager.InMovement) { MovementManager.StopMove(); } } } internal void Rotation() { Logging.Write("[Hunter BM] Rotation started"); while (_isLaunched) { try { if (!Products.InPause && !ObjectManager.Me.IsDeadMe) { if (ObjectManager.Me.HaveBuff("Feign Death")) { Thread.Sleep(100); continue; } // Pet attack instantly on target acquire/change (before rotation) EnsurePetAttack(); if (!ObjectManager.Me.InCombat) { _engagedEnemyGuids.Clear(); _priorityTargetGuid = 0; _priorityTargetUnit = null; _isSwimmingCombat = false; } // Pet management PetManager(); // Buffs BuffRotation(); // Pet happiness if ((DateTime.Now - _lastHappinessCheck).TotalSeconds >= 60) { int happiness = Lua.LuaDoString("happiness, damagePercentage, loyaltyRate = GetPetHappiness() return happiness"); _petIsHungry = happiness < 3; _lastHappinessCheck = DateTime.Now; } if (_petIsHungry && (DateTime.Now - _lastFeedAttempt).TotalSeconds >= 1) { Feed(); _lastFeedAttempt = DateTime.Now; if (ObjectManager.Pet.HaveBuff("Feed Pet Effect")) _petIsHungry = false; } // Fast looting if (!Fight.InFight) { TryFastLoot(); } // Aggro management bool holdingAggroTarget = false; if (ObjectManager.Me.InCombat) { holdingAggroTarget = HandleAggroManagement(); } // Combat rotation if (Fight.InFight && ObjectManager.Target.IsValid && ObjectManager.Target.IsAttackable) { // Под водой: перехватываем движение чтобы бот не плыл в мили SwimmingRangeControl(); CombatRotation(); // Multipull TryMultiPull(); } if (ObjectManager.Me.InCombat && holdingAggroTarget) { HandleAggroManagement(); } } } catch (Exception ex) { Logging.WriteError("[Hunter] ERROR: " + ex.Message); } Thread.Sleep(100); } } // ============================================================ // PET FOOD LISTS // ============================================================ private string PetFoodType() { return Lua.LuaDoString("return GetPetFoodTypes();"); } private List FoodList() { return new List { "Tough Jerky", "Haunch of Meat", "Mutton Chop", "Wild Hog Shank", "Cured Ham Steak", "Roasted Quail", "Smoked Talbuk Venison", "Clefthoof Ribs" }; } private List Fungus() { return new List { "Raw Black Truffle", "Dried King Bolete", "Spongy Morel", "Delicious Cave Mold", "Shroomcap Supreme" }; } private List FishList() { return new List { "Slitherskin Mackerel", "Longjaw Mud Snapper", "Bristle Whisker Catfish", "Rockscale Cod", "Striped Yellowtail", "Spinefin Halibut", "Figluster's Mudfish" }; } private List FruitList() { return new List { "Shiny Red Apple", "Tel'Abim Banana", "Snapvine Watermelon", "Goldenbark Apple", "Heaven Peach", "Telaari Grapes" }; } private List BreadList() { return new List { "Tough Hunk of Bread", "Freshly Baked Bread", "Moist Cornbread", "Soft Banana Bread", "Homemade Cherry Pie", "Mag'har Grainbread" }; } private bool FeedByType(List list) { foreach (string item in list) { if (ItemsManager.GetItemCountByNameLUA(item) > 0) { Lua.LuaDoString("CastSpellByName('Feed Pet')"); Thread.Sleep(Usefuls.Latency + 100); Lua.LuaDoString("UseItemByName('" + item + "')"); return true; } } return false; } private void FeedPet() { string foodType = PetFoodType(); if (foodType.Contains("Meat") && FeedByType(FoodList())) return; if (foodType.Contains("Fungus") && FeedByType(Fungus())) return; if (foodType.Contains("Fish") && FeedByType(FishList())) return; if (foodType.Contains("Fruit") && FeedByType(FruitList())) return; if (foodType.Contains("Bread") && FeedByType(BreadList())) return; } internal void BuffRotation() { if (ObjectManager.Me.IsDeadMe || ObjectManager.Me.IsMounted) return; uint manaPercent = ObjectManager.Me.ManaPercentage; bool hasViper = ObjectManager.Me.HaveBuff("Aspect of the Viper"); bool hasHawk = ObjectManager.Me.HaveBuff("Aspect of the Hawk"); if (Fight.InFight || ObjectManager.Me.InCombat) { if (Settings.UseAspectOfViper && AspectViper.KnownSpell && !hasViper && manaPercent <= Settings.ViperManaThreshold && AspectViper.IsSpellUsable) { AspectViper.Launch(); } else if (hasViper && manaPercent >= Settings.HawkManaThreshold && Settings.UseAspectOfHawk && AspectHawk.KnownSpell && AspectHawk.IsSpellUsable) { AspectHawk.Launch(); } else if (!hasHawk && !hasViper) { if (Settings.UseAspectOfViper && AspectViper.KnownSpell && manaPercent <= Settings.ViperManaThreshold && AspectViper.IsSpellUsable) { AspectViper.Launch(); } else if (Settings.UseAspectOfHawk && AspectHawk.KnownSpell && AspectHawk.IsSpellUsable) { AspectHawk.Launch(); } } } else { if (Settings.UseAspectOfHawk && !hasHawk && !hasViper && AspectHawk.KnownSpell && AspectHawk.IsSpellUsable) { AspectHawk.Launch(); } } } internal void PetManager() { if (!ObjectManager.Me.IsDeadMe && !ObjectManager.Me.IsMounted && !Usefuls.IsLoadingOrConnecting) { if (!ObjectManager.Pet.IsValid && CallPet.KnownSpell) { CallPet.Launch(); Thread.Sleep(Usefuls.Latency + 1000); } if (ObjectManager.Pet.IsDead && RevivePet.KnownSpell) { RevivePet.Launch(); Thread.Sleep(Usefuls.Latency + 1000); Usefuls.WaitIsCasting(); } if (Settings.UseMendPet && ObjectManager.Pet.IsValid && ObjectManager.Pet.IsAlive && !ObjectManager.Pet.HaveBuff("Mend Pet") && MendPet.KnownSpell && ObjectManager.Pet.HealthPercent <= Settings.MendPetThreshold) { MendPet.Launch(); } } } public void Feed() { if (ObjectManager.Pet.IsAlive && !ObjectManager.Me.IsCast && !ObjectManager.Pet.HaveBuff("Feed Pet Effect")) { FeedPet(); Thread.Sleep(Usefuls.Latency + 400); } } private void TryFeignDeath() { if (!Settings.UseFeignDeath) return; if (!FeignDeath.KnownSpell || !FeignDeath.IsSpellUsable) return; if (ObjectManager.Pet == null || !ObjectManager.Pet.IsValid || !ObjectManager.Pet.IsAlive) return; if (ObjectManager.Target == null || !ObjectManager.Target.IsValid || !ObjectManager.Target.IsAlive) return; if (!ObjectManager.Target.IsTargetingMe || ObjectManager.Target.IsTargetingMyPet) return; if (ObjectManager.Me.HealthPercent >= 50) return; Logging.Write("[Hunter] FD - mob targeting me, HP low, pet alive"); FeignDeath.Launch(); Thread.Sleep(Usefuls.Latency + 300); if (ObjectManager.Me.HaveBuff("Feign Death")) { Thread.Sleep(_humanRnd.Next(800, 1200)); Lua.RunMacroText("/cancelaura Feign Death"); Thread.Sleep(Usefuls.Latency + 100); if (ObjectManager.Pet != null && ObjectManager.Pet.IsValid && ObjectManager.Pet.IsAlive && ObjectManager.Pet.TargetObject != null && ObjectManager.Pet.TargetObject.IsAlive) { ObjectManager.Me.Target = ObjectManager.Pet.TargetObject.Guid; Thread.Sleep(Usefuls.Latency + 100); } StartAttackWithPet(); } } internal void CombatRotation() { if (ObjectManager.Me.HaveBuff("Feign Death")) return; TryFeignDeath(); if (!ObjectManager.Target.IsValid) return; EnsurePetAttack(); if (ObjectManager.Target.IsTapDenied) { Lua.LuaDoString("PetFollow()"); Fight.StopFight(); Interact.ClearTarget(); Logging.Write("[Hunter] Target is tap denied, recalling pet and stopping fight"); return; } if (IsTargetEvading(ObjectManager.Target)) { Fight.StopFight(); Interact.ClearTarget(); return; } float distance = ObjectManager.Target.GetDistance; double targetHealth = ObjectManager.Target.HealthPercent; uint manaPercent = ObjectManager.Me.ManaPercentage; bool inMeleeRange = distance < 5f; bool inRangedRange = distance >= 5f && distance <= 35f; if (!ObjectManager.Me.IsCast && !ObjectManager.Me.IsMounted && !ObjectManager.Me.HaveBuff("Feign Death")) { MovementManager.Face(ObjectManager.Target); } // Auto Shot low mana mode if (Settings.UseAutoShotLowMana && manaPercent <= Settings.AutoShotManaThreshold) { Lua.LuaDoString("PetAttack();"); StartAttackWithPet(); return; } // Hunter's Mark (pet уже отправлен выше через SendPetAttackIfNeeded) if (Settings.UseHuntersMark && HuntersMark.KnownSpell && !HuntersMark.TargetHaveBuff && HuntersMark.IsSpellUsable && manaPercent >= (uint)Settings.HuntersMarkManaMin) { HuntersMark.Launch(); } // Kill Command if (Settings.UseKillCommand && KillCommand.KnownSpell && KillCommand.IsSpellUsable && ObjectManager.Pet.IsValid && ObjectManager.Pet.IsAlive && ObjectManager.Pet.TargetObject != null && ObjectManager.Pet.TargetObject.IsAlive) { KillCommand.Launch(); } // Serpent Sting if (Settings.UseSerpentSting && SerpentSting.KnownSpell && !ObjectManager.Target.HaveBuff("Serpent Sting") && inRangedRange && Canpoison(ObjectManager.Me.TargetObject) && targetHealth >= Settings.SerpentStingHpMin && manaPercent > Settings.SerpentStingManaMin && SerpentSting.IsSpellUsable) { SerpentSting.Launch(); } // Bestial Wrath if (Settings.UseBestialWrath && BestialWrath.KnownSpell && targetHealth >= Settings.BestialWrathHpMin && manaPercent > Settings.BestialWrathManaMin && BestialWrath.IsSpellUsable) { bool canUse = true; if (Settings.BestialWrathOnElite || Settings.BestialWrathEnemiesMin > 0 || Settings.BestialWrathLevelDiff > 0) { canUse = false; if (Settings.BestialWrathOnElite && ObjectManager.Target.IsElite) canUse = true; if (Settings.BestialWrathEnemiesMin > 0 && GetAllEngagedMobs().Count >= Settings.BestialWrathEnemiesMin) canUse = true; if (Settings.BestialWrathLevelDiff > 0 && ObjectManager.Target.Level >= ObjectManager.Me.Level + Settings.BestialWrathLevelDiff) canUse = true; } if (canUse) { BestialWrath.Launch(); Logging.Write("[Hunter] Bestial Wrath"); } } // Intimidation if (Settings.UseIntimidation && Intimidation.KnownSpell && targetHealth >= Settings.IntimidationHpMin && Intimidation.IsSpellUsable) { Intimidation.Launch(); } // Rapid Fire if (Settings.UseRapidFire && RapidFire.KnownSpell && inRangedRange && targetHealth >= Settings.RapidFireHpMin && RapidFire.IsSpellUsable) { RapidFire.Launch(); } // Multi-Shot (при 2+ мобах в бою) if (Settings.UseMultiShot && MultiShot.KnownSpell && inRangedRange && manaPercent > Settings.MultiShotManaMin && MultiShot.IsSpellUsable) { int engagedCount = GetAllEngagedMobs().Count; if (engagedCount >= 2) { MultiShot.Launch(); Logging.Write("[Hunter] Multi-Shot (" + engagedCount + " mobs engaged)"); } } // Steady Shot (основной филлер) if (Settings.UseSteadyShot && SteadyShot.KnownSpell && inRangedRange && targetHealth >= Settings.SteadyShotHpMin && manaPercent > Settings.SteadyShotManaMin && SteadyShot.IsSpellUsable) { SteadyShot.Launch(); } // Arcane Shot if (Settings.UseArcaneShot && ArcaneShot.KnownSpell && inRangedRange && targetHealth >= Settings.ArcaneHpMin && manaPercent > Settings.ArcaneManaMin && ArcaneShot.IsSpellUsable) { ArcaneShot.Launch(); } // Mongoose Bite if (Settings.UseMongooseBite && inMeleeRange && MongooseBite.KnownSpell && MongooseBite.IsSpellUsable) { MongooseBite.Launch(); } // Raptor Strike else if (Settings.UseRaptorStrike && inMeleeRange && RaptorStrike.KnownSpell && RaptorStrike.IsSpellUsable) { RaptorStrike.Launch(); } }}