internal static Vector3 SimpleUnstucker() { var myPos = Trinity.Player.Position; float rotation = Trinity.Player.Rotation; const double totalPoints = 2 * Math.PI; const double start = 0; const double step = Math.PI / 4; const float minDistance = 10f; const float maxDistance = 25f; const float stepDistance = 5f; HashSet <GridPoint> gridPoints = new HashSet <GridPoint>(); int navigationObstacleFail = 0; for (double r = start; r <= totalPoints; r += step) { for (float d = minDistance; d <= maxDistance; d += stepDistance) { float newDirection = (float)(rotation + r); Vector3 newPos = MathEx.GetPointAt(myPos, d, newDirection); if (!MainGridProvider.CanStandAt(MainGridProvider.WorldToGrid(newPos.ToVector2()))) { continue; } // If this hits a known navigation obstacle, skip it if (CacheData.NavigationObstacles.Any(o => MathEx.IntersectsPath(o.Position, o.Radius, myPos, newPos))) { navigationObstacleFail++; continue; } // use distance as weight gridPoints.Add(new GridPoint(newPos, (int)d, d)); } } if (!gridPoints.Any()) { Logger.LogDebug(LogCategory.UserInformation, "Unable to generate new unstucker position! navObsticle={0} - trying RANDOM point!", navigationObstacleFail); Random random = new Random(); int distance = random.Next(5, 30); float direction = (float)random.NextDouble(); return(MathEx.GetPointAt(myPos, distance, direction)); } Navigator.Clear(); var bestPoint = gridPoints.OrderByDescending(p => p.Weight).FirstOrDefault(); Logger.LogDebug(LogCategory.UserInformation, "Generated Unstuck position {0} distance={1:0.0} navObsticle={2}", NavHelper.PrettyPrintVector3(bestPoint.Position), bestPoint.Distance, navigationObstacleFail); return(bestPoint.Position); }
public override string ToString() { return (String.Format("power={0} pos={1} acdGuid={2} preWait={3} postWait={4} timeSinceAssigned={5} timeSinceUse={6} range={7}", SNOPower, NavHelper.PrettyPrintVector3(TargetPosition), TargetACDGUID, WaitTicksBeforeUse, WaitTicksAfterUse, TimeSinceAssigned, TimeSinceUse, MinimumRange)); }
/// <summary> /// Finds an optimal position for using Monk Tempest Rush out of combat /// </summary> /// <returns></returns> internal static Vector3 FindTempestRushTarget() { Vector3 target = PlayerMover.LastMoveToTarget; Vector3 myPos = ZetaDia.Me.Position; if (Trinity.CurrentTarget != null && NavHelper.CanRayCast(myPos, target)) { target = Trinity.CurrentTarget.Position; } float distance = target.Distance2D(myPos); if (distance < 30f) { double direction = MathUtil.FindDirectionRadian(myPos, target); target = MathEx.GetPointAt(myPos, 40f, (float)direction); } return(target); }
/// <summary> /// Special handling for whether or not we want to cache an object that's not in LoS /// </summary> /// <param name="c_diaObject"></param> /// <param name="AddToCache"></param> /// <returns></returns> private static bool RefreshStepIgnoreLoS(bool AddToCache = false) { try { if (CurrentCacheObject.Type == GObjectType.Item || CurrentCacheObject.Type == GObjectType.Gold) { return(true); } if (!DataDictionary.AlwaysRaycastWorlds.Contains(Trinity.Player.WorldID)) { // Bounty Objectives should always be on the weight list if (CurrentCacheObject.IsBountyObjective) { return(true); } // Quest Monsters should get LoS white-listed if (CurrentCacheObject.IsQuestMonster) { return(true); } // Always LoS Units during events if (CurrentCacheObject.Type == GObjectType.Unit && Player.InActiveEvent) { return(true); } } // Everything except items and the current target if (CurrentCacheObject.RActorGuid != LastTargetRactorGUID && CurrentCacheObject.Type != GObjectType.Unknown) { if (CurrentCacheObject.Distance < 95) { using (new PerformanceLogger("RefreshLoS.2")) { // Get whether or not this RActor has ever been in a path line with AllowWalk. If it hasn't, don't add to cache and keep rechecking if (!CacheData.HasBeenRayCasted.TryGetValue(CurrentCacheObject.RActorGuid, out c_HasBeenRaycastable) || DataDictionary.AlwaysRaycastWorlds.Contains(Trinity.Player.WorldID)) { if (CurrentCacheObject.Distance >= 1f && CurrentCacheObject.Distance <= 5f) { c_HasBeenRaycastable = true; if (!CacheData.HasBeenRayCasted.ContainsKey(CurrentCacheObject.RActorGuid)) { CacheData.HasBeenRayCasted.Add(CurrentCacheObject.RActorGuid, c_HasBeenRaycastable); } } else if (Settings.Combat.Misc.UseNavMeshTargeting) { Vector3 myPos = new Vector3(Player.Position.X, Player.Position.Y, Player.Position.Z + 8f); Vector3 cPos = new Vector3(CurrentCacheObject.Position.X, CurrentCacheObject.Position.Y, CurrentCacheObject.Position.Z + 8f); cPos = MathEx.CalculatePointFrom(cPos, myPos, CurrentCacheObject.Radius + 1f); if (Single.IsNaN(cPos.X) || Single.IsNaN(cPos.Y) || Single.IsNaN(cPos.Z)) { cPos = CurrentCacheObject.Position; } if (!NavHelper.CanRayCast(myPos, cPos)) { AddToCache = false; c_IgnoreSubStep = "UnableToRayCast"; } else { c_HasBeenRaycastable = true; if (!CacheData.HasBeenRayCasted.ContainsKey(CurrentCacheObject.RActorGuid)) { CacheData.HasBeenRayCasted.Add(CurrentCacheObject.RActorGuid, c_HasBeenRaycastable); } } } else { if (c_ZDiff > 14f) { AddToCache = false; c_IgnoreSubStep = "LoS.ZDiff"; } else { c_HasBeenRaycastable = true; if (!CacheData.HasBeenRayCasted.ContainsKey(CurrentCacheObject.RActorGuid)) { CacheData.HasBeenRayCasted.Add(CurrentCacheObject.RActorGuid, c_HasBeenRaycastable); } } } } } using (new PerformanceLogger("RefreshLoS.3")) { // Get whether or not this RActor has ever been in "Line of Sight" (as determined by Demonbuddy). If it hasn't, don't add to cache and keep rechecking if (!CacheData.HasBeenInLoS.TryGetValue(CurrentCacheObject.RActorGuid, out c_HasBeenInLoS) || DataDictionary.AlwaysRaycastWorlds.Contains(Trinity.Player.WorldID)) { // Ignore units not in LoS except bosses if (!CurrentCacheObject.IsBoss && !c_diaObject.InLineOfSight) { AddToCache = false; c_IgnoreSubStep = "NotInLoS"; } else { c_HasBeenInLoS = true; if (!CacheData.HasBeenInLoS.ContainsKey(CurrentCacheObject.RActorGuid)) { CacheData.HasBeenInLoS.Add(CurrentCacheObject.RActorGuid, c_HasBeenInLoS); } } } } } else { AddToCache = false; c_IgnoreSubStep = "LoS-OutOfRange"; } // always set true for bosses nearby if (CurrentCacheObject.IsBoss || CurrentCacheObject.IsQuestMonster || CurrentCacheObject.IsBountyObjective) { AddToCache = true; c_IgnoreSubStep = ""; } // always take the current target even if not in LoS if (CurrentCacheObject.RActorGuid == LastTargetRactorGUID) { AddToCache = true; c_IgnoreSubStep = ""; } } // Simple whitelist for LoS if (DataDictionary.LineOfSightWhitelist.Contains(CurrentCacheObject.ActorSNO)) { AddToCache = true; c_IgnoreSubStep = ""; } // Always pickup Infernal Keys whether or not in LoS if (DataDictionary.ForceToItemOverrideIds.Contains(CurrentCacheObject.ActorSNO)) { AddToCache = true; c_IgnoreSubStep = ""; } } catch (Exception ex) { AddToCache = true; c_IgnoreSubStep = "IgnoreLoSException"; Logger.Log(TrinityLogLevel.Debug, LogCategory.CacheManagement, "{0}", ex); } return(AddToCache); }
private static void Monk_MaintainTempestRush() { if (!Monk_TempestRushReady()) { return; } if (Player.IsInTown || Zeta.Bot.Logic.BrainBehavior.IsVendoring) { return; } if (TownRun.IsTryingToTownPortal()) { return; } if (TimeSinceUse(SNOPower.Monk_TempestRush) > 150) { return; } bool shouldMaintain = false; bool nullTarget = CurrentTarget == null; if (!nullTarget) { // maintain for everything except items, doors, interactables... stuff we have to "click" on switch (CurrentTarget.Type) { case GObjectType.Unit: case GObjectType.Gold: case GObjectType.Avoidance: case GObjectType.Barricade: case GObjectType.Destructible: case GObjectType.HealthGlobe: case GObjectType.PowerGlobe: { if (Settings.Combat.Monk.TROption == TempestRushOption.TrashOnly && (TargetUtil.AnyElitesInRange(40f) || CurrentTarget.IsBossOrEliteRareUnique)) { shouldMaintain = false; } else { shouldMaintain = true; } } break; } } else { shouldMaintain = true; } if (Settings.Combat.Monk.TROption != TempestRushOption.MovementOnly && SNOPowerUseTimer(SNOPower.Monk_TempestRush) && shouldMaintain) { Vector3 target = LastTempestRushLocation; const string locationSource = "LastLocation"; if (target.Distance2D(ZetaDia.Me.Position) <= 1f) { // rrrix edit: we can't maintain here return; } if (target == Vector3.Zero) { return; } float DestinationDistance = target.Distance2D(ZetaDia.Me.Position); target = TargetUtil.FindTempestRushTarget(); if (DestinationDistance > 10f && NavHelper.CanRayCast(ZetaDia.Me.Position, target)) { Monk_TempestRushStatus(String.Format("Using Tempest Rush to maintain channeling, source={0}, V3={1} dist={2:0}", locationSource, target, DestinationDistance)); var usePowerResult = ZetaDia.Me.UsePower(SNOPower.Monk_TempestRush, target, CurrentWorldDynamicId, -1); if (usePowerResult) { CacheData.AbilityLastUsed[SNOPower.Monk_TempestRush] = DateTime.UtcNow; } } } }
private static TrinityPower GetDemonHunterPower(bool IsCurrentlyAvoiding, bool UseOOCBuff, bool UseDestructiblePower) { // Pick the best destructible power available if (UseDestructiblePower) { return(GetDemonHunterDestroyPower()); } MinEnergyReserve = 25; //Kridershot InternalName=x1_bow_norm_unique_09-137 GameBalanceID=1999595351 ItemLink: {c:ffff8000}Kridershot{/c} bool hasKridershot = ZetaDia.Me.Inventory.Equipped.Any(i => i.GameBalanceId == 1999595351); bool hasPunishment = HotbarSkills.AssignedSkills.Any(s => s.Power == SNOPower.DemonHunter_Preparation && s.RuneIndex == 0); // Spam Shadow Power if (Settings.Combat.DemonHunter.SpamShadowPower && CombatBase.CanCast(SNOPower.DemonHunter_ShadowPower) && !Player.IsIncapacitated && (!GetHasBuff(SNOPower.DemonHunter_ShadowPower) || Player.CurrentHealthPct <= PlayerEmergencyHealthPotionLimit) && // if we don't have the buff or our health is low ((!hasPunishment && Player.SecondaryResource >= 14) || (hasPunishment && Player.SecondaryResource >= 39)) && // Save some Discipline for Preparation (Settings.Combat.DemonHunter.SpamShadowPower && Player.SecondaryResource >= 28)) // When spamming Shadow Power, save some Discipline for emergencies { return(new TrinityPower(SNOPower.DemonHunter_ShadowPower)); } // NotSpam Shadow Power if (!UseOOCBuff && !Settings.Combat.DemonHunter.SpamShadowPower && CombatBase.CanCast(SNOPower.DemonHunter_ShadowPower) && !Player.IsIncapacitated && (!GetHasBuff(SNOPower.DemonHunter_ShadowPower) || Player.CurrentHealthPct <= PlayerEmergencyHealthPotionLimit) && // if we don't have the buff or our health is low ((!hasPunishment && Player.SecondaryResource >= 14) || (hasPunishment && Player.SecondaryResource >= 39)) && // Save some Discipline for Preparation (Player.CurrentHealthPct < 1f || Player.IsRooted || TargetUtil.AnyMobsInRange(15))) { return(new TrinityPower(SNOPower.DemonHunter_ShadowPower)); } // Vengeance if (!IsCurrentlyAvoiding && CombatBase.CanCast(SNOPower.X1_DemonHunter_Vengeance, CombatBase.CanCastFlags.NoTimer) && ((!Settings.Combat.DemonHunter.VengeanceElitesOnly && TargetUtil.AnyMobsInRange(60, 6)) || TargetUtil.IsEliteTargetInRange(60f))) { return(new TrinityPower(SNOPower.X1_DemonHunter_Vengeance)); } // Smoke Screen if ((!UseOOCBuff || Settings.Combat.DemonHunter.SpamSmokeScreen) && CombatBase.CanCast(SNOPower.DemonHunter_SmokeScreen) && !GetHasBuff(SNOPower.DemonHunter_ShadowPower) && Player.SecondaryResource >= 14 && ( (Player.CurrentHealthPct <= 0.50 || Player.IsRooted || TargetUtil.AnyMobsInRange(15) || Player.IsIncapacitated) || Settings.Combat.DemonHunter.SpamSmokeScreen )) { return(new TrinityPower(SNOPower.DemonHunter_SmokeScreen, 0f, Vector3.Zero, CurrentWorldDynamicId, -1, 1, 1)); } int sentryCoolDown = SpellHistory.SpellUseCountInTime(SNOPower.DemonHunter_Sentry, TimeSpan.FromSeconds(24)) >= 4 ? 12 : 6; // Sentry Turret if (!UseOOCBuff && !Player.IsIncapacitated && CombatBase.CanCast(SNOPower.DemonHunter_Sentry, CombatBase.CanCastFlags.NoTimer) && (TargetUtil.AnyElitesInRange(50) || TargetUtil.AnyMobsInRange(50, 2) || TargetUtil.IsEliteTargetInRange(50)) && Player.PrimaryResource >= 30 && (SpellHistory.TimeSinceUse(SNOPower.DemonHunter_Sentry) > TimeSpan.FromSeconds(sentryCoolDown) || SpellHistory.DistanceFromLastUsePosition(SNOPower.DemonHunter_Sentry) > 7.5)) { return(new TrinityPower(SNOPower.DemonHunter_Sentry, 65f, TargetUtil.GetBestClusterPoint())); } // Caltrops if (!UseOOCBuff && !Player.IsIncapacitated && CombatBase.CanCast(SNOPower.DemonHunter_Caltrops) && Player.SecondaryResource >= 6 && TargetUtil.AnyMobsInRange(40)) { return(new TrinityPower(SNOPower.DemonHunter_Caltrops, 0f, Vector3.Zero, CurrentWorldDynamicId, -1, 1, 1)); } // Preperation: restore 30 disc / 45 sec cd // Invigoration = 1 : passive + 15 disc / 45 sec cd // Punishment = 0 : Cost: 25 disc, restore 75 hatred / NO COOLDOWN // Battle Scars = 3 : instantly restore 40% health + restore 30 disc / 45 sec cd // Focused Mind = 2 : restore 45 disc over 15 sec / 45 sec cd // Backup plan = 4 : 30% chance Prep has no cooldown (remove cast timer) // Restore 75 hatred for 25 disc - NO COOLDOWN! //bool hasPunishment = HotbarSkills.AssignedSkills.Any(s => s.Power == SNOPower.DemonHunter_Preparation && s.RuneIndex == 0); bool hasInvigoration = HotbarSkills.AssignedSkills.Any(s => s.Power == SNOPower.DemonHunter_Preparation && s.RuneIndex == 1); bool hasBattleScars = HotbarSkills.AssignedSkills.Any(s => s.Power == SNOPower.DemonHunter_Preparation && s.RuneIndex == 3); bool hasFocusedMind = HotbarSkills.AssignedSkills.Any(s => s.Power == SNOPower.DemonHunter_Preparation && s.RuneIndex == 2); bool hasBackupPlan = HotbarSkills.AssignedSkills.Any(s => s.Power == SNOPower.DemonHunter_Preparation && s.RuneIndex == 4); float preperationTriggerRange = V.F("DemonHunter.PreperationTriggerRange"); if (((!UseOOCBuff && !Player.IsIncapacitated && (TargetUtil.AnyMobsInRange(preperationTriggerRange))) || Settings.Combat.DemonHunter.SpamPreparation || hasPunishment) && Hotbar.Contains(SNOPower.DemonHunter_Preparation)) { // Preperation w/ Punishment if (hasPunishment && CombatBase.CanCast(SNOPower.DemonHunter_Preparation, CombatBase.CanCastFlags.NoTimer) && Player.SecondaryResource >= 25 && Player.PrimaryResourceMissing >= 75 && TimeSinceUse(SNOPower.DemonHunter_Preparation) >= 1000) { return(new TrinityPower(SNOPower.DemonHunter_Preparation)); } // Preperation w/ Battle Scars - check for health only if (hasBattleScars && CombatBase.CanCast(SNOPower.DemonHunter_Preparation) && Player.CurrentHealthPct < 0.6) { return(new TrinityPower(SNOPower.DemonHunter_Preparation)); } // no rune || invigoration || focused mind || Backup Plan || Battle Scars (need Disc) if ((!hasPunishment) && CombatBase.CanCast(SNOPower.DemonHunter_Preparation) && Player.SecondaryResource <= 15 && TimeSinceUse(SNOPower.DemonHunter_Preparation) >= 1000) { return(new TrinityPower(SNOPower.DemonHunter_Preparation)); } } // SOURCE: Stede - http://www.thebuddyforum.com/demonbuddy-forum/plugins/trinity/155398-trinity-dh-companion-use-abilities.html#post1445496 // Companion On-Use Abilities Added in 2.0 bool hasSpider = HotbarSkills.AssignedSkills.Any(s => s.Power == SNOPower.X1_DemonHunter_Companion && s.RuneIndex == 0); bool hasBat = HotbarSkills.AssignedSkills.Any(s => s.Power == SNOPower.X1_DemonHunter_Companion && s.RuneIndex == 3); bool hasBoar = HotbarSkills.AssignedSkills.Any(s => s.Power == SNOPower.X1_DemonHunter_Companion && s.RuneIndex == 1); bool hasFerret = HotbarSkills.AssignedSkills.Any(s => s.Power == SNOPower.X1_DemonHunter_Companion && s.RuneIndex == 4); bool hasWolf = HotbarSkills.AssignedSkills.Any(s => s.Power == SNOPower.X1_DemonHunter_Companion && s.RuneIndex == 2); if (!UseOOCBuff && !Player.IsIncapacitated && Hotbar.Contains(SNOPower.X1_DemonHunter_Companion)) { // Use Spider Slow on 4 or more trash mobs in an area or on Unique/Elite/Champion if (hasSpider && CombatBase.CanCast(SNOPower.X1_DemonHunter_Companion) && TargetUtil.ClusterExists(25f, 4) && TargetUtil.EliteOrTrashInRange(25f)) { return(new TrinityPower(SNOPower.X1_DemonHunter_Companion)); } //Use Bat when Hatred is Needed if (hasBat && CombatBase.CanCast(SNOPower.X1_DemonHunter_Companion) && Player.PrimaryResourceMissing >= 60) { return(new TrinityPower(SNOPower.X1_DemonHunter_Companion)); } // Use Boar Taunt on 3 or more trash mobs in an area or on Unique/Elite/Champion if (hasBoar && CombatBase.CanCast(SNOPower.X1_DemonHunter_Companion) && ((TargetUtil.ClusterExists(20f, 4) && TargetUtil.EliteOrTrashInRange(20f)) || (CurrentTarget.IsBossOrEliteRareUnique && CurrentTarget.Distance <= 20f))) { return(new TrinityPower(SNOPower.X1_DemonHunter_Companion)); } // Ferrets used for picking up Health Globes when low on Health if (hasFerret && ObjectCache.Any(o => o.Type == GObjectType.HealthGlobe && o.Distance < 60f) && Player.CurrentHealthPct < PlayerEmergencyHealthPotionLimit) { return(new TrinityPower(SNOPower.X1_DemonHunter_Companion)); } // Use Wolf Howl on Unique/Elite/Champion - Would help for farming trash, but trash farming should not need this - Used on Elites to reduce Deaths per hour if (hasWolf && CombatBase.CanCast(SNOPower.X1_DemonHunter_Companion) && (TargetUtil.AnyMobsInRange(20) || (CurrentTarget.IsBossOrEliteRareUnique && CurrentTarget.RadiusDistance < 55f))) { return(new TrinityPower(SNOPower.X1_DemonHunter_Companion)); } } //skillDict.Add("EvasiveFire", SNOPower.DemonHunter_EvasiveFire); //runeDict.Add("Hardened", 0); //runeDict.Add("PartingGift", 2); //runeDict.Add("CoveringFire", 1); //runeDict.Add("Displace", 4); //runeDict.Add("Surge", 3); // Companion if (!Player.IsIncapacitated && CombatBase.CanCast(SNOPower.X1_DemonHunter_Companion) && TargetUtil.EliteOrTrashInRange(30f) && Player.SecondaryResource >= 10) { return(new TrinityPower(SNOPower.X1_DemonHunter_Companion, 0f, Vector3.Zero, CurrentWorldDynamicId, -1, 2, 1)); } // Marked for Death if (!UseOOCBuff && !IsCurrentlyAvoiding && CombatBase.CanCast(SNOPower.DemonHunter_MarkedForDeath) && Player.SecondaryResource >= 3 && (TargetUtil.AnyElitesInRange(40) || TargetUtil.AnyMobsInRange(40, 3) || ((CurrentTarget.IsEliteRareUnique || CurrentTarget.IsTreasureGoblin || CurrentTarget.IsBoss) && CurrentTarget.Radius <= 40 && CurrentTarget.RadiusDistance <= 40f))) { return(new TrinityPower(SNOPower.DemonHunter_MarkedForDeath, 40f, Vector3.Zero, CurrentWorldDynamicId, CurrentTarget.ACDGuid, 1, 1)); } // Vault if (!UseOOCBuff && !IsCurrentlyAvoiding && CombatBase.CanCast(SNOPower.DemonHunter_Vault) && !Player.IsRooted && !Player.IsIncapacitated && Settings.Combat.DemonHunter.VaultMode != Config.Combat.DemonHunterVaultMode.MovementOnly && // Only use vault to retreat if < level 60, or if in inferno difficulty for level 60's (Player.Level < 60 || Player.GameDifficulty > GameDifficulty.Master) && (CurrentTarget.RadiusDistance <= 10f || TargetUtil.AnyMobsInRange(10)) && // if we have ShadowPower and Disicpline is >= 16 // or if we don't have ShadoWpower and Discipline is >= 22 (Player.SecondaryResource >= (Hotbar.Contains(SNOPower.DemonHunter_ShadowPower) ? 22 : 16)) && TimeSinceUse(SNOPower.DemonHunter_Vault) >= Trinity.Settings.Combat.DemonHunter.VaultMovementDelay) { //Vector3 vNewTarget = MathEx.CalculatePointFrom(CurrentTarget.Position, Player.Position, -15f); // Lets find a smarter Vault position instead of just "backwards" //Vector3 vNewTarget = NavHelper.FindSafeZone(Trinity.Player.Position, true, false, null, false); Vector3 vNewTarget = NavHelper.MainFindSafeZone(Trinity.Player.Position, true, false, null, false); return(new TrinityPower(SNOPower.DemonHunter_Vault, 20f, vNewTarget, CurrentWorldDynamicId, -1, 1, 2)); } // Rain of Vengeance if (!UseOOCBuff && CombatBase.CanCast(SNOPower.DemonHunter_RainOfVengeance) && !Player.IsIncapacitated && (TargetUtil.ClusterExists(45f, 3) || TargetUtil.EliteOrTrashInRange(45f))) { var bestClusterPoint = TargetUtil.GetBestClusterPoint(45f, 65f, false, true); return(new TrinityPower(SNOPower.DemonHunter_RainOfVengeance, 0f, bestClusterPoint, CurrentWorldDynamicId, -1, 1, 1)); } // Cluster Arrow if (!UseOOCBuff && !IsCurrentlyAvoiding && CombatBase.CanCast(SNOPower.DemonHunter_ClusterArrow) && !Player.IsIncapacitated && Player.PrimaryResource >= 50) { return(new TrinityPower(SNOPower.DemonHunter_ClusterArrow, V.F("DemonHunter.ClusterArrow.UseRange"), CurrentTarget.ACDGuid)); } // Multi Shot if (!UseOOCBuff && !IsCurrentlyAvoiding && CombatBase.CanCast(SNOPower.DemonHunter_Multishot) && !Player.IsIncapacitated && Player.PrimaryResource >= 30 && (TargetUtil.AnyMobsInRange(40, 2) || CurrentTarget.IsBossOrEliteRareUnique || CurrentTarget.IsTreasureGoblin)) { return(new TrinityPower(SNOPower.DemonHunter_Multishot, 30f, CurrentTarget.Position, CurrentWorldDynamicId, -1, 1, 1)); } // Fan of Knives if (!UseOOCBuff && CombatBase.CanCast(SNOPower.DemonHunter_FanOfKnives) && !Player.IsIncapacitated && (TargetUtil.EliteOrTrashInRange(15) || TargetUtil.AnyTrashInRange(15f, 5, false))) { return(new TrinityPower(SNOPower.DemonHunter_FanOfKnives, 0f, Vector3.Zero, CurrentWorldDynamicId, -1, 1, 1)); } // Strafe spam - similar to barbarian whirlwind routine if (!UseOOCBuff && !IsCurrentlyAvoiding && CombatBase.CanCast(SNOPower.DemonHunter_Strafe, CombatBase.CanCastFlags.NoTimer) && !Player.IsIncapacitated && !Player.IsRooted && Player.PrimaryResource >= Settings.Combat.DemonHunter.StrafeMinHatred) { bool shouldGetNewZigZag = (DateTime.UtcNow.Subtract(Trinity.LastChangedZigZag).TotalMilliseconds >= V.I("Barbarian.Whirlwind.ZigZagMaxTime") || CurrentTarget.ACDGuid != Trinity.LastZigZagUnitAcdGuid || CombatBase.ZigZagPosition.Distance2D(Player.Position) <= 5f); if (shouldGetNewZigZag) { var wwdist = V.F("Barbarian.Whirlwind.ZigZagDistance"); CombatBase.ZigZagPosition = TargetUtil.GetZigZagTarget(CurrentTarget.Position, wwdist); Trinity.LastZigZagUnitAcdGuid = CurrentTarget.ACDGuid; Trinity.LastChangedZigZag = DateTime.UtcNow; } int postCastTickDelay = TrinityPower.MillisecondsToTickDelay(250); return(new TrinityPower(SNOPower.DemonHunter_Strafe, 15f, CombatBase.ZigZagPosition, CurrentWorldDynamicId, -1, 0, postCastTickDelay)); } // Spike Trap if (!UseOOCBuff && !Player.IsIncapacitated && CombatBase.CanCast(SNOPower.DemonHunter_SpikeTrap) && LastPowerUsed != SNOPower.DemonHunter_SpikeTrap && Player.PrimaryResource >= 30) { // For distant monsters, try to target a little bit in-front of them (as they run towards us), if it's not a treasure goblin float fExtraDistance = 0f; if (CurrentTarget.Distance > 17f && !CurrentTarget.IsTreasureGoblin) { fExtraDistance = CurrentTarget.Distance - 17f; if (fExtraDistance > 5f) { fExtraDistance = 5f; } if (CurrentTarget.Distance - fExtraDistance < 15f) { fExtraDistance -= 2; } } Vector3 vNewTarget = MathEx.CalculatePointFrom(CurrentTarget.Position, Player.Position, CurrentTarget.Distance - fExtraDistance); return(new TrinityPower(SNOPower.DemonHunter_SpikeTrap, 35f, vNewTarget, CurrentWorldDynamicId, -1, 1, 1)); } //skillDict.Add("ElementalArrow", SNOPower.DemonHunter_ElementalArrow); //runeDict.Add("BallLightning", 1); //runeDict.Add("FrostArrow", 0); //runeDict.Add("ScreamingSkull", 2); //runeDict.Add("LightningBolts", 4); //runeDict.Add("NetherTentacles", 3); var hasBallLightning = HotbarSkills.AssignedSkills.Any(s => s.Power == SNOPower.DemonHunter_ElementalArrow && s.RuneIndex == 1); var hasFrostArrow = HotbarSkills.AssignedSkills.Any(s => s.Power == SNOPower.DemonHunter_ElementalArrow && s.RuneIndex == 0); var hasScreamingSkull = HotbarSkills.AssignedSkills.Any(s => s.Power == SNOPower.DemonHunter_ElementalArrow && s.RuneIndex == 2); var hasLightningBolts = HotbarSkills.AssignedSkills.Any(s => s.Power == SNOPower.DemonHunter_ElementalArrow && s.RuneIndex == 4); var hasNetherTentacles = HotbarSkills.AssignedSkills.Any(s => s.Power == SNOPower.DemonHunter_ElementalArrow && s.RuneIndex == 3); // Elemental Arrow if (!UseOOCBuff && !IsCurrentlyAvoiding && Hotbar.Contains(SNOPower.DemonHunter_ElementalArrow) && SNOPowerUseTimer(SNOPower.DemonHunter_ElementalArrow) && !Player.IsIncapacitated && ((Player.PrimaryResource >= 10 && !Player.WaitingForReserveEnergy) || Player.PrimaryResource >= MinEnergyReserve || hasKridershot)) { return(new TrinityPower(SNOPower.DemonHunter_ElementalArrow, 65f, Vector3.Zero, -1, CurrentTarget.ACDGuid, 0, 1)); } //skillDict.Add("Chakram", SNOPower.DemonHunter_Chakram); //runeDict.Add("TwinChakrams", 0); //runeDict.Add("Serpentine", 2); //runeDict.Add("RazorDisk", 3); //runeDict.Add("Boomerang", 1); //runeDict.Add("ShurikenCloud", 4); bool hasShurikenCloud = HotbarSkills.AssignedSkills.Any(s => s.Power == SNOPower.DemonHunter_Chakram && s.RuneIndex == 4); // Chakram normal attack if (!UseOOCBuff && !IsCurrentlyAvoiding && Hotbar.Contains(SNOPower.DemonHunter_Chakram) && !Player.IsIncapacitated && !hasShurikenCloud && ((Player.PrimaryResource >= 10 && !Player.WaitingForReserveEnergy) || Player.PrimaryResource >= MinEnergyReserve)) { return(new TrinityPower(SNOPower.DemonHunter_Chakram, 50f, Vector3.Zero, -1, CurrentTarget.ACDGuid, 0, 1)); } // Chakram:Shuriken Cloud if (!Player.IsInTown && !IsCurrentlyAvoiding && Hotbar.Contains(SNOPower.DemonHunter_Chakram) && !Player.IsIncapacitated && hasShurikenCloud && TimeSinceUse(SNOPower.DemonHunter_Chakram) >= 110000 && ((Player.PrimaryResource >= 10 && !Player.WaitingForReserveEnergy) || Player.PrimaryResource >= MinEnergyReserve)) { return(new TrinityPower(SNOPower.DemonHunter_Chakram, 0f, Vector3.Zero, CurrentWorldDynamicId, -1, 2, 2)); } // Rapid Fire if (!UseOOCBuff && !IsCurrentlyAvoiding && CombatBase.CanCast(SNOPower.DemonHunter_RapidFire, CombatBase.CanCastFlags.NoTimer) && !Player.IsIncapacitated && Player.PrimaryResource >= 16 && (Player.PrimaryResource >= Settings.Combat.DemonHunter.RapidFireMinHatred || CombatBase.LastPowerUsed == SNOPower.DemonHunter_RapidFire)) { // Players with grenades *AND* rapid fire should spam grenades at close-range instead if (Hotbar.Contains(SNOPower.DemonHunter_Grenades) && CurrentTarget.RadiusDistance <= 18f) { return(new TrinityPower(SNOPower.DemonHunter_Grenades, 18f, Vector3.Zero, -1, CurrentTarget.ACDGuid, 0, 0)); } // Now return rapid fire, if not sending grenades instead return(new TrinityPower(SNOPower.DemonHunter_RapidFire, 40f, CurrentTarget.Position)); } // Impale if (!UseOOCBuff && !IsCurrentlyAvoiding && Hotbar.Contains(SNOPower.DemonHunter_Impale) && !Player.IsIncapacitated && (!TargetUtil.AnyMobsInRange(12, 4)) && ((Player.PrimaryResource >= 25 && !Player.WaitingForReserveEnergy) || Player.PrimaryResource >= MinEnergyReserve) && CurrentTarget.RadiusDistance <= 50f) { return(new TrinityPower(SNOPower.DemonHunter_Impale, 50f, Vector3.Zero, -1, CurrentTarget.ACDGuid, 0, 1)); } // Evasive Fire if (!UseOOCBuff && CombatBase.CanCast(SNOPower.X1_DemonHunter_EvasiveFire) && !Player.IsIncapacitated && (TargetUtil.AnyMobsInRange(10f) || DemonHunter_HasNoPrimary())) { float range = DemonHunter_HasNoPrimary() ? 70f : 0f; return(new TrinityPower(SNOPower.X1_DemonHunter_EvasiveFire, range, Vector3.Zero, -1, CurrentTarget.ACDGuid, 1, 1)); } // Hungering Arrow if (!UseOOCBuff && !IsCurrentlyAvoiding && Hotbar.Contains(SNOPower.DemonHunter_HungeringArrow) && !Player.IsIncapacitated) { return(new TrinityPower(SNOPower.DemonHunter_HungeringArrow, 50f, Vector3.Zero, -1, CurrentTarget.ACDGuid, 0, 0)); } // Entangling shot if (!UseOOCBuff && !IsCurrentlyAvoiding && Hotbar.Contains(SNOPower.X1_DemonHunter_EntanglingShot) && !Player.IsIncapacitated) { return(new TrinityPower(SNOPower.X1_DemonHunter_EntanglingShot, 50f, Vector3.Zero, -1, CurrentTarget.ACDGuid, 0, 0)); } // Bola Shot if (!UseOOCBuff && !IsCurrentlyAvoiding && Hotbar.Contains(SNOPower.DemonHunter_Bolas) && !Player.IsIncapacitated) { return(new TrinityPower(SNOPower.DemonHunter_Bolas, 50f, Vector3.Zero, -1, CurrentTarget.ACDGuid, 0, 1)); } // Grenades if (!UseOOCBuff && !IsCurrentlyAvoiding && Hotbar.Contains(SNOPower.DemonHunter_Grenades) && !Player.IsIncapacitated) { return(new TrinityPower(SNOPower.DemonHunter_Grenades, 40f, Vector3.Zero, -1, CurrentTarget.ACDGuid, 5, 5)); } // Default attacks return(CombatBase.DefaultPower); }
private static void RefreshSetKiting(ref Vector3 vKitePointAvoid, bool NeedToKite) { using (new PerformanceLogger("RefreshDiaObjectCache.Kiting")) { bool TryToKite = false; List <TrinityCacheObject> kiteMonsterList = new List <TrinityCacheObject>(); if (CurrentTarget != null && CurrentTarget.IsUnit) { switch (CombatBase.KiteMode) { case KiteMode.Never: break; case KiteMode.Elites: kiteMonsterList = (from m in ObjectCache where m.IsUnit && m.RadiusDistance > 0 && m.RadiusDistance <= CombatBase.KiteDistance && m.IsBossOrEliteRareUnique select m).ToList(); break; case KiteMode.Bosses: kiteMonsterList = (from m in ObjectCache where m.IsUnit && m.RadiusDistance > 0 && m.RadiusDistance <= CombatBase.KiteDistance && m.IsBoss select m).ToList(); break; case KiteMode.Always: kiteMonsterList = (from m in ObjectCache where m.IsUnit && m.Weight > 0 && m.RadiusDistance > 0 && m.RadiusDistance <= CombatBase.KiteDistance && (m.IsBossOrEliteRareUnique || ((m.HitPointsPct >= .15 || m.MonsterSize != MonsterSize.Swarm) && !m.IsBossOrEliteRareUnique)) select m).ToList(); break; } } if (kiteMonsterList.Any()) { TryToKite = true; vKitePointAvoid = Player.Position; } if (CombatBase.KiteDistance > 0 && kiteMonsterList.Count() > 0 && IsWizardShouldKite()) { TryToKite = true; vKitePointAvoid = Player.Position; } // Avoid Death if (Settings.Combat.Misc.AvoidDeath && Player.CurrentHealthPct <= CombatBase.EmergencyHealthPotionLimit && // health is lower than potion limit !SNOPowerUseTimer(SNOPower.DrinkHealthPotion) && // we can't use a potion anymore TargetUtil.AnyMobsInRange(90f, false)) { Logger.LogNormal("Attempting to avoid death!"); NeedToKite = true; kiteMonsterList = (from m in ObjectCache where m.IsUnit select m).ToList(); } // Note that if treasure goblin level is set to kamikaze, even avoidance moves are disabled to reach the goblin! bool shouldKamikazeTreasureGoblins = (!AnyTreasureGoblinsPresent || Settings.Combat.Misc.GoblinPriority <= GoblinPriority.Prioritize); double msCancelledEmergency = DateTime.UtcNow.Subtract(timeCancelledEmergencyMove).TotalMilliseconds; bool shouldEmergencyMove = msCancelledEmergency >= cancelledEmergencyMoveForMilliseconds && NeedToKite; double msCancelledKite = DateTime.UtcNow.Subtract(timeCancelledKiteMove).TotalMilliseconds; bool shouldKite = msCancelledKite >= cancelledKiteMoveForMilliseconds && TryToKite; if (shouldKamikazeTreasureGoblins && (shouldEmergencyMove || shouldKite)) { Vector3 vAnySafePoint = NavHelper.FindSafeZone(false, 1, vKitePointAvoid, true, kiteMonsterList, shouldEmergencyMove); if (LastKitePosition == null) { LastKitePosition = new KitePosition() { PositionFoundTime = DateTime.UtcNow, Position = vAnySafePoint, Distance = vAnySafePoint.Distance(Player.Position) }; } if (vAnySafePoint != Vector3.Zero && vAnySafePoint.Distance(Player.Position) >= 1) { if ((DateTime.UtcNow.Subtract(LastKitePosition.PositionFoundTime).TotalMilliseconds > 3000 && LastKitePosition.Position == vAnySafePoint) || (CurrentTarget != null && DateTime.UtcNow.Subtract(lastGlobalCooldownUse).TotalMilliseconds > 1500 && TryToKite)) { timeCancelledKiteMove = DateTime.UtcNow; cancelledKiteMoveForMilliseconds = 1500; Logger.Log(TrinityLogLevel.Debug, LogCategory.UserInformation, "Kite movement failed, cancelling for {0:0}ms", cancelledKiteMoveForMilliseconds); return; } else { LastKitePosition = new KitePosition() { PositionFoundTime = DateTime.UtcNow, Position = vAnySafePoint, Distance = vAnySafePoint.Distance(Player.Position) }; } if (Settings.Advanced.LogCategories.HasFlag(LogCategory.Movement)) { Logger.Log(TrinityLogLevel.Verbose, LogCategory.Movement, "Kiting to: {0} Distance: {1:0} Direction: {2:0}, Health%={3:0.00}, KiteDistance: {4:0}, Nearby Monsters: {5:0} NeedToKite: {6} TryToKite: {7}", vAnySafePoint, vAnySafePoint.Distance(Player.Position), MathUtil.GetHeading(MathUtil.FindDirectionDegree(Me.Position, vAnySafePoint)), Player.CurrentHealthPct, CombatBase.KiteDistance, kiteMonsterList.Count(), NeedToKite, TryToKite); } CurrentTarget = new TrinityCacheObject() { Position = vAnySafePoint, Type = TrinityObjectType.Avoidance, Weight = 90000, Distance = Vector3.Distance(Player.Position, vAnySafePoint), Radius = 2f, InternalName = "KitePoint" }; } } else if (!shouldEmergencyMove && NeedToKite) { Logger.Log(TrinityLogLevel.Debug, LogCategory.UserInformation, "Emergency movement cancelled for {0:0}ms", DateTime.UtcNow.Subtract(timeCancelledEmergencyMove).TotalMilliseconds - cancelledKiteMoveForMilliseconds); } else if (!shouldKite && TryToKite) { Logger.Log(TrinityLogLevel.Debug, LogCategory.UserInformation, "Kite movement cancelled for {0:0}ms", DateTime.UtcNow.Subtract(timeCancelledKiteMove).TotalMilliseconds - cancelledKiteMoveForMilliseconds); } } }
// Special Zig-Zag movement for whirlwind/tempest /// <summary> /// Finds an optimal position for Barbarian Whirlwind, Monk Tempest Rush, or Demon Hunter Strafe /// </summary> /// <param name="origin"></param> /// <param name="ringDistance"></param> /// <param name="randomizeDistance"></param> /// <returns></returns> internal static Vector3 GetZigZagTarget(Vector3 origin, float ringDistance, bool randomizeDistance = false) { var minDistance = 20f; Vector3 myPos = Player.Position; float distanceToTarget = origin.Distance2D(myPos); Vector3 zigZagPoint = origin; bool useTargetBasedZigZag = false; float maxDistance = 25f; int minTargets = 2; if (Trinity.Player.ActorClass == ActorClass.Monk) { maxDistance = 20f; minTargets = 3; useTargetBasedZigZag = Trinity.Settings.Combat.Monk.TargetBasedZigZag; } if (Trinity.Player.ActorClass == ActorClass.Barbarian) { useTargetBasedZigZag = Trinity.Settings.Combat.Barbarian.TargetBasedZigZag; } int eliteCount = ObjectCache.Count(u => u.IsUnit && u.IsBossOrEliteRareUnique); bool shouldZigZagElites = ((Trinity.CurrentTarget.IsBossOrEliteRareUnique && eliteCount > 1) || eliteCount == 0); if (useTargetBasedZigZag && shouldZigZagElites && !AnyTreasureGoblinsPresent && ObjectCache.Count(o => o.IsUnit) >= minTargets) { bool attackInAoe = Trinity.Settings.Combat.Misc.KillMonstersInAoE; var clusterPoint = TargetUtil.GetBestClusterPoint(ringDistance, ringDistance, false, attackInAoe); if (clusterPoint.Distance2D(Player.Position) >= minDistance) { Logger.Log(LogCategory.Movement, "Returning ZigZag: BestClusterPoint {0} r-dist={1} t-dist={2}", clusterPoint, ringDistance, clusterPoint.Distance2D(Player.Position)); return(clusterPoint); } var zigZagTargetList = new List <TrinityCacheObject>(); if (attackInAoe) { zigZagTargetList = (from u in ObjectCache where u.IsUnit && u.Distance < maxDistance select u).ToList(); } else { zigZagTargetList = (from u in ObjectCache where u.IsUnit && u.Distance < maxDistance && !UnitOrPathInAoE(u) select u).ToList(); } if (zigZagTargetList.Count() >= minTargets) { zigZagPoint = zigZagTargetList.OrderByDescending(u => u.Distance).FirstOrDefault().Position; if (NavHelper.CanRayCast(zigZagPoint) && zigZagPoint.Distance2D(Player.Position) >= minDistance) { Logger.Log(LogCategory.Movement, "Returning ZigZag: TargetBased {0} r-dist={1} t-dist={2}", zigZagPoint, ringDistance, zigZagPoint.Distance2D(Player.Position)); return(zigZagPoint); } } } Random rndNum = new Random(int.Parse(Guid.NewGuid().ToString().Substring(0, 8), NumberStyles.HexNumber)); float highestWeightFound = float.NegativeInfinity; Vector3 bestLocation = origin; // the unit circle always starts at 0 :) double min = 0; // the maximum size of a unit circle double max = 2 * Math.PI; // the number of times we will iterate around the circle to find points double piSlices = 16; // We will do several "passes" to make sure we can get a point that we can least zig-zag to // The total number of points tested will be piSlices * distancePasses.Count List <float> distancePasses = new List <float>(); distancePasses.Add(ringDistance * 1 / 2); // Do one loop at 1/2 distance distancePasses.Add(ringDistance * 3 / 4); // Do one loop at 3/4 distance distancePasses.Add(ringDistance); // Do one loop at exact distance foreach (float distance in distancePasses) { for (double direction = min; direction < max; direction += (Math.PI / piSlices)) { // Starting weight is 1 float pointWeight = 1f; // Find a new XY zigZagPoint = MathEx.GetPointAt(origin, distance, (float)direction); // Get the Z zigZagPoint.Z = Trinity.MainGridProvider.GetHeight(zigZagPoint.ToVector2()); // Make sure we're actually zig-zagging our target, except if we're kiting float targetCircle = CurrentTarget.Radius; if (targetCircle <= 5f) { targetCircle = 5f; } if (targetCircle > 10f) { targetCircle = 10f; } bool intersectsPath = MathUtil.IntersectsPath(CurrentTarget.Position, targetCircle, myPos, zigZagPoint); if (CombatBase.PlayerKiteDistance <= 0 && !intersectsPath) { continue; } // if we're kiting, lets not actualy run through monsters if (CombatBase.PlayerKiteDistance > 0 && CacheData.MonsterObstacles.Any(m => m.Position.Distance(zigZagPoint) <= CombatBase.PlayerKiteDistance)) { continue; } // Ignore point if any AoE in this point position if (CacheData.TimeBoundAvoidance.Any(m => m.Position.Distance(zigZagPoint) <= m.Radius && Player.CurrentHealthPct <= AvoidanceManager.GetAvoidanceHealthBySNO(m.ActorSNO, 1))) { continue; } // Make sure this point is in LoS/walkable (not around corners or into a wall) bool canRayCast = !Navigator.Raycast(Player.Position, zigZagPoint); if (!canRayCast) { continue; } float distanceToPoint = zigZagPoint.Distance2D(myPos); float distanceFromTargetToPoint = zigZagPoint.Distance2D(origin); // Lots of weight for points further away from us (e.g. behind our CurrentTarget) pointWeight *= distanceToPoint; // Add weight for any units in this point int monsterCount = ObjectCache.Count(u => u.IsUnit && u.Position.Distance2D(zigZagPoint) <= Math.Max(u.Radius, 10f)); if (monsterCount > 0) { pointWeight *= monsterCount; } //Logger.Log(LogCategory.Movement, "ZigZag Point: {0} distance={1:0} distaceFromTarget={2:0} intersectsPath={3} weight={4:0} monsterCount={5}", // zigZagPoint, distanceToPoint, distanceFromTargetToPoint, intersectsPath, pointWeight, monsterCount); // Use this one if it's more weight, or we haven't even found one yet, or if same weight as another with a random chance if (pointWeight > highestWeightFound) { highestWeightFound = pointWeight; if (Trinity.Settings.Combat.Misc.UseNavMeshTargeting) { bestLocation = new Vector3(zigZagPoint.X, zigZagPoint.Y, Trinity.MainGridProvider.GetHeight(zigZagPoint.ToVector2())); } else { bestLocation = new Vector3(zigZagPoint.X, zigZagPoint.Y, zigZagPoint.Z + 4); } } } } Logger.Log(LogCategory.Movement, "Returning ZigZag: RandomXY {0} r-dist={1} t-dist={2}", bestLocation, ringDistance, bestLocation.Distance2D(Player.Position)); return(bestLocation); }
/// <summary> /// This method will add and update necessary information about all available actors. Determines ObjectType, sets ranges, updates blacklists, determines avoidance, kiting, target weighting /// and the result is we will have a new target for the Target Handler. Returns true if the cache was refreshed. /// </summary> /// <returns>True if the cache was updated</returns> public static bool RefreshDiaObjectCache() { if (ZetaDia.Service.Hero == null) { Logger.LogError("Hero is null!"); return(false); } //if (!ZetaDia.Service.Hero.IsValid) //{ // Logger.LogError("Hero is invalid!"); // return false; //} if (!ZetaDia.IsInGame) { return(false); } if (ZetaDia.IsLoadingWorld) { return(false); } if (!ZetaDia.Me.IsValid) { return(false); } if (!ZetaDia.Me.CommonData.IsValid) { return(false); } using (new PerformanceLogger("RefreshDiaObjectCache")) { LastRefreshedCache = DateTime.UtcNow; /* * Refresh the Cache */ using (new PerformanceLogger("RefreshDiaObjectCache.UpdateBlock")) { CacheData.Clear(); GenericCache.MaintainCache(); GenericBlacklist.MaintainBlacklist(); if (Player.CurrentHealthPct <= 0) { return(false); } RefreshCacheInit(); // Now pull up all the data and store anything we want to handle in the super special cache list // Also use many cache dictionaries to minimize DB<->D3 memory hits, and speed everything up a lot RefreshCacheMainLoop(); } /* * Add Legendary & Set Minimap Markers to ObjectCache */ RefreshCacheMarkers(); // Add Team HotSpots to the cache ObjectCache.AddRange(GroupHotSpots.GetCacheObjectHotSpots()); /* Fire Chains Experimental Avoidance */ if (Settings.Combat.Misc.UseExperimentalFireChainsAvoidance) { const float fireChainSize = 5f; foreach (var unit1 in ObjectCache.Where(u => u.MonsterAffixes.HasFlag(MonsterAffixes.FireChains))) { foreach (var unit2 in ObjectCache.Where(u => u.MonsterAffixes.HasFlag(MonsterAffixes.FireChains)).Where(unit2 => unit1.RActorGuid != unit2.RActorGuid)) { for (float i = 0; i <= unit1.Position.Distance2D(unit2.Position); i += (fireChainSize / 4)) { Vector3 fireChainSpot = MathEx.CalculatePointFrom(unit1.Position, unit2.Position, i); if (Player.Position.Distance2D(fireChainSpot) <= fireChainSize) { Logger.Log(TrinityLogLevel.Debug, LogCategory.CacheManagement, "Avoiding Fire Chains!"); _standingInAvoidance = true; } CacheData.TimeBoundAvoidance.Add(new CacheObstacleObject(fireChainSpot, fireChainSize, -2, "FireChains")); } } if (CacheData.TimeBoundAvoidance.Any(aoe => aoe.ActorSNO == -2)) { Logger.Log(TrinityLogLevel.Debug, LogCategory.CacheManagement, "Generated {0} avoidance points for FireChains, minDistance={1} maxDistance={2}", CacheData.TimeBoundAvoidance.Count(aoe => aoe.ActorSNO == -2), CacheData.TimeBoundAvoidance.Where(aoe => aoe.ActorSNO == -2) .Min(aoe => aoe.Position.Distance2D(Player.Position)), CacheData.TimeBoundAvoidance.Where(aoe => aoe.ActorSNO == -2) .Max(aoe => aoe.Position.Distance2D(Player.Position))); } } } /* Beast Charge Experimental Avoidance */ if (Settings.Combat.Misc.UseExperimentalSavageBeastAvoidance) { const float beastChargePathWidth = 10f; List <int> chargerSnoList = new List <int>(); foreach (var unit1 in ObjectCache.Where(u => objectIsCharging(u))) { Vector3 endPoint = MathEx.GetPointAt(unit1.Position, 90f, unit1.Unit.Movement.Rotation); for (float i = 0; i <= unit1.Position.Distance2D(endPoint); i += (beastChargePathWidth / 4)) { Vector3 pathSpot = MathEx.CalculatePointFrom(unit1.Position, endPoint, i); Logger.Log(TrinityLogLevel.Debug, LogCategory.CacheManagement, "Generating Charge Avoidance: {0} dist: {1}", pathSpot, pathSpot.Distance2D(unit1.Position)); var minWidth = Math.Max(beastChargePathWidth, unit1.Radius) + 5f; if (Player.Position.Distance2D(pathSpot) <= minWidth) { Logger.Log(TrinityLogLevel.Debug, LogCategory.CacheManagement, "Avoiding Charger!"); _standingInAvoidance = true; } CacheData.TimeBoundAvoidance.Add(new CacheObstacleObject(pathSpot, minWidth, unit1.ActorSNO, "Charger")); chargerSnoList.Add(unit1.ActorSNO); } if (CacheData.TimeBoundAvoidance.Any(aoe => chargerSnoList.Contains(aoe.ActorSNO))) { Logger.Log(TrinityLogLevel.Debug, LogCategory.CacheManagement, "Generated {0} avoidance points for BeastCharge, minDistance={1} maxDistance={2}", CacheData.TimeBoundAvoidance.Count(aoe => chargerSnoList.Contains(aoe.ActorSNO)), CacheData.TimeBoundAvoidance.Where(aoe => chargerSnoList.Contains(aoe.ActorSNO)) .Min(aoe => aoe.Position.Distance2D(Player.Position)), CacheData.TimeBoundAvoidance.Where(aoe => chargerSnoList.Contains(aoe.ActorSNO)) .Max(aoe => aoe.Position.Distance2D(Player.Position))); } } } // Reduce ignore-for-loops counter if (_ignoreTargetForLoops > 0) { _ignoreTargetForLoops--; } // If we have an avoidance under our feet, then create a new object which contains a safety point to move to // But only if we aren't force-cancelling avoidance for XX time bool hasFoundSafePoint = false; using (new PerformanceLogger("RefreshDiaObjectCache.AvoidanceCheck")) { if (Settings.Combat.Misc.FleeInGhostMode && Player.IsGhosted) { _standingInAvoidance = true; } // Note that if treasure goblin level is set to kamikaze, even avoidance moves are disabled to reach the goblin! if (_standingInAvoidance && !CombatBase.IsDoingGoblinKamakazi && (!AnyTreasureGoblinsPresent || Settings.Combat.Misc.GoblinPriority <= GoblinPriority.Prioritize) && DateTime.UtcNow.Subtract(timeCancelledEmergencyMove).TotalMilliseconds >= cancelledEmergencyMoveForMilliseconds) { Vector3 safePosition = NavHelper.FindSafeZone(false, 1, Player.Position, true, null, true); // Ignore avoidance stuff if we're incapacitated or didn't find a safe spot we could reach if (safePosition != Vector3.Zero) { if (Settings.Advanced.LogCategories.HasFlag(LogCategory.Movement)) { Logger.Log(TrinityLogLevel.Verbose, LogCategory.Movement, "Kiting Avoidance: {5} {0} Distance: {1:0} Direction: {2:0}, Health%={3:0.00}, KiteDistance: {4:0}", safePosition, safePosition.Distance(Me.Position), MathUtil.GetHeading(MathUtil.FindDirectionDegree(Me.Position, safePosition)), Player.CurrentHealthPct, CombatBase.KiteDistance, _currentAvoidanceName); } hasFoundSafePoint = true; var distance = Vector3.Distance(Player.Position, safePosition); Logger.Log(LogCategory.Avoidance, "Found Safe Spot {0}f away", distance); CurrentTarget = new TrinityCacheObject() { Position = safePosition, Type = TrinityObjectType.Avoidance, Weight = 40000, Distance = distance, Radius = 2f, InternalName = "SafePoint", IsSafeSpot = true }; } } } /* * Set Weights, assign CurrentTarget */ if (!hasFoundSafePoint) { RefreshDiaGetWeights(); if (!CombatBase.IsDoingGoblinKamakazi) { RefreshSetKiting(ref KiteAvoidDestination, NeedToKite); } } // Not heading straight for a safe-spot? // No valid targets but we were told to stay put? if (CurrentTarget == null && !CombatBase.IsDoingGoblinKamakazi && _shouldStayPutDuringAvoidance && !_standingInAvoidance) { CurrentTarget = new TrinityCacheObject() { Position = Player.Position, Type = TrinityObjectType.Avoidance, Weight = 45000, Distance = 2f, Radius = 2f, InternalName = "StayPutPoint", IsSafeSpot = true, }; Logger.Log(TrinityLogLevel.Debug, LogCategory.Avoidance, "Staying Put During Avoidance"); } // Pre-townrun is too far away if (!Player.IsInTown && TownRun.PreTownRunPosition != Vector3.Zero && TownRun.PreTownRunWorldId == Player.WorldID && !ForceVendorRunASAP && TownRun.PreTownRunPosition.Distance2D(Player.Position) <= V.F("Cache.PretownRun.MaxDistance")) { Logger.Log(TrinityLogLevel.Debug, LogCategory.UserInformation, "Pre-TownRun position is more than {0} yards away, canceling", V.I("Cache.PretownRun.MaxDistance")); TownRun.PreTownRunPosition = Vector3.Zero; TownRun.PreTownRunWorldId = -1; } // Reached pre-townrun position if (!Player.IsInTown && TownRun.PreTownRunPosition != Vector3.Zero && TownRun.PreTownRunWorldId == Player.WorldID && !ForceVendorRunASAP && TownRun.PreTownRunPosition.Distance2D(Player.Position) <= 15f) { Logger.Log(TrinityLogLevel.Debug, LogCategory.UserInformation, "Successfully returned to Pre-TownRun Position"); TownRun.PreTownRunPosition = Vector3.Zero; TownRun.PreTownRunWorldId = -1; } // After a townrun, make sure to return to original TownRun Location if (!Player.IsInTown && CurrentTarget == null && TownRun.PreTownRunPosition != Vector3.Zero && TownRun.PreTownRunWorldId == Player.WorldID && !ForceVendorRunASAP) { if (TownRun.PreTownRunPosition.Distance2D(Player.Position) > 10f && TownRun.PreTownRunPosition.Distance2D(Player.Position) <= V.F("Cache.PretownRun.MaxDistance")) { CurrentTarget = new TrinityCacheObject() { Position = TownRun.PreTownRunPosition, Type = TrinityObjectType.Avoidance, Weight = 20000, Distance = 2f, Radius = 2f, InternalName = "PreTownRunPosition" }; Logger.Log(TrinityLogLevel.Debug, LogCategory.UserInformation, "Returning to Pre-TownRun Position"); } } using (new PerformanceLogger("RefreshDiaObjectCache.FinalChecks")) { if (Settings.WorldObject.EnableBountyEvents && !CombatBase.IsDoingGoblinKamakazi) { bool eventObjectNear = ObjectCache.Any(o => o.Type == TrinityObjectType.CursedChest || o.Type == TrinityObjectType.CursedShrine); if (!Player.InActiveEvent) { EventStartPosition = Vector3.Zero; EventStartTime = DateTime.MinValue; } // Reset Event time while we have targts if (CurrentTarget != null && Player.InActiveEvent && eventObjectNear) { EventStartTime = DateTime.UtcNow; } if (eventObjectNear) { EventStartPosition = ObjectCache.FirstOrDefault(o => o.Type == TrinityObjectType.CursedChest || o.Type == TrinityObjectType.CursedShrine).Position; } var activeEvent = ZetaDia.ActInfo.ActiveQuests.FirstOrDefault(q => DataDictionary.EventQuests.Contains(q.QuestSNO)); const int waitTimeoutSeconds = 90; if (DateTime.UtcNow.Subtract(EventStartTime).TotalSeconds > waitTimeoutSeconds && activeEvent != null) { CacheData.BlacklistedEvents.Add(activeEvent.QuestSNO); } if (CurrentTarget == null && Player.InActiveEvent && EventStartPosition != Vector3.Zero && DateTime.UtcNow.Subtract(EventStartTime).TotalSeconds < waitTimeoutSeconds && activeEvent != null && !CacheData.BlacklistedEvents.Contains(activeEvent.QuestSNO)) { CurrentTarget = new TrinityCacheObject() { Position = EventStartPosition, Type = TrinityObjectType.Avoidance, Weight = 20000, Distance = 2f, Radius = 2f, InternalName = "WaitForEvent" }; Logger.Log("Waiting for Event {0} - Time Remaining: {1:0} seconds", ZetaDia.ActInfo.ActiveQuests.FirstOrDefault(q => DataDictionary.EventQuests.Contains(q.QuestSNO)).Quest, waitTimeoutSeconds - DateTime.UtcNow.Subtract(EventStartTime).TotalSeconds); } } if (CombatBase.IsDoingGoblinKamakazi && CurrentTarget != null && CurrentTarget.Type != TrinityObjectType.Door && CurrentTarget.Type != TrinityObjectType.Barricade && !CurrentTarget.InternalName.ToLower().Contains("corrupt") && CurrentTarget.Weight != MaxWeight) { Logger.Log("Forcing Target to Goblin '{0} ({1})' Distance={2}", CombatBase.KamakaziGoblin.InternalName, CombatBase.KamakaziGoblin.ActorSNO, CombatBase.KamakaziGoblin.Distance); CurrentTarget = CombatBase.KamakaziGoblin; } if (CombatBase.IsDoingGoblinKamakazi && CurrentTarget == null) { Logger.Log("No Target, Switching to Goblin '{0} ({1})' Distance={2}", CombatBase.KamakaziGoblin.InternalName, CombatBase.KamakaziGoblin.ActorSNO, CombatBase.KamakaziGoblin.Distance); CurrentTarget = CombatBase.KamakaziGoblin; } // Still no target, let's see if we should backtrack or wait for wrath to come off cooldown... if (CurrentTarget == null) { RefreshWaitTimers(); } // Still no target, let's end it all! if (CurrentTarget == null) { Events.OnCacheUpdatedHandler.Invoke(); return(true); } if (CurrentTarget.IsUnit) { lastHadUnitInSights = DateTime.UtcNow; } if (CurrentTarget.IsBossOrEliteRareUnique) { lastHadEliteUnitInSights = DateTime.UtcNow; } if (CurrentTarget.IsBoss || CurrentTarget.IsBountyObjective) { lastHadBossUnitInSights = DateTime.UtcNow; } if (CurrentTarget.Type == TrinityObjectType.Container) { lastHadContainerInSights = DateTime.UtcNow; } // Record the last time our target changed if (LastTargetRactorGUID != CurrentTarget.RActorGuid) { RecordTargetHistory(); Logger.Log(TrinityLogLevel.Verbose, LogCategory.Weight, "Found New Target {0} dist={1:0} IsElite={2} Radius={3:0.0} Weight={4:0} ActorSNO={5} " + "Anim={6} TargetedCount={7} Type={8} ", CurrentTarget.InternalName, CurrentTarget.Distance, CurrentTarget.IsEliteRareUnique, CurrentTarget.Radius, CurrentTarget.Weight, CurrentTarget.ActorSNO, CurrentTarget.Animation, CurrentTarget.TimesBeenPrimaryTarget, CurrentTarget.Type ); _lastPickedTargetTime = DateTime.UtcNow; _targetLastHealth = 0f; } else { // We're sticking to the same target, so update the target's health cache to check for stucks if (CurrentTarget.IsUnit) { // Check if the health has changed, if so update the target-pick time before we blacklist them again if (CurrentTarget.HitPointsPct != _targetLastHealth) { Logger.Log(TrinityLogLevel.Debug, LogCategory.Weight, "Keeping Target {0} - CurrentTarget.HitPoints: {1:0.00} TargetLastHealth: {2:0.00} ", CurrentTarget.RActorGuid, CurrentTarget.HitPointsPct, _targetLastHealth); _lastPickedTargetTime = DateTime.UtcNow; } // Now store the target's last-known health _targetLastHealth = CurrentTarget.HitPointsPct; } } } // Store less important objects. if (ObjectCache.Count > 1) { var setting = Settings.Advanced.CacheWeightThresholdPct; var threshold = setting > 0 && CurrentTarget != null ? CurrentTarget.Weight * ((double)setting / 100) : 0; var lowPriorityObjects = ObjectCache.DistinctBy(c => c.RActorGuid).Where(c => c.Type != TrinityObjectType.Avoidance && c.Type != TrinityObjectType.Unit || c.Weight <threshold && c.Distance> 12f && !c.IsElite ).ToDictionary(x => x.RActorGuid, x => x); Logger.Log(TrinityLogLevel.Debug, LogCategory.CacheManagement, "Cached {0}/{1} ({2:0}%) WeightThreshold={3}", lowPriorityObjects.Count, ObjectCache.Count, lowPriorityObjects.Count > 0 ? ((double)lowPriorityObjects.Count / ObjectCache.Count) * 100 : 0, threshold); CacheData.LowPriorityObjectCache = lowPriorityObjects; } // We have a target and the cached was refreshed Events.OnCacheUpdatedHandler.Invoke(); return(true); } }
/// <summary> /// This method will add and update necessary information about all available actors. Determines ObjectType, sets ranges, updates blacklists, determines avoidance, kiting, target weighting /// and the result is we will have a new target for the Target Handler. Returns true if the cache was refreshed. /// </summary> /// <returns>True if the cache was updated</returns> public static bool RefreshDiaObjectCache(bool forceUpdate = false) { using (new PerformanceLogger("RefreshDiaObjectCache")) { if (DateTime.UtcNow.Subtract(LastRefreshedCache).TotalMilliseconds < Settings.Advanced.CacheRefreshRate && !forceUpdate) { if (!UpdateCurrentTarget()) { return(false); } } LastRefreshedCache = DateTime.UtcNow; using (new PerformanceLogger("RefreshDiaObjectCache.UpdateBlock")) { CacheData.Clear(); GenericCache.MaintainCache(); GenericBlacklist.MaintainBlacklist(); // Update player-data cache, including buffs PlayerInfoCache.UpdateCachedPlayerData(); if (Player.CurrentHealthPct <= 0) { return(false); } RefreshCacheInit(); // Now pull up all the data and store anything we want to handle in the super special cache list // Also use many cache dictionaries to minimize DB<->D3 memory hits, and speed everything up a lot RefreshCacheMainLoop(); } // Add Team HotSpots to the cache ObjectCache.AddRange(GroupHotSpots.GetCacheObjectHotSpots()); /* Fire Chains Experimental Avoidance */ if (Settings.Combat.Misc.UseExperimentalFireChainsAvoidance) { const float fireChainSize = 5f; foreach (var unit1 in ObjectCache.Where(u => u.MonsterAffixes.HasFlag(MonsterAffixes.FireChains))) { foreach (var unit2 in ObjectCache.Where(u => u.MonsterAffixes.HasFlag(MonsterAffixes.FireChains)).Where(unit2 => unit1.RActorGuid != unit2.RActorGuid)) { for (float i = 0; i <= unit1.Position.Distance2D(unit2.Position); i += (fireChainSize / 4)) { Vector3 fireChainSpot = MathEx.CalculatePointFrom(unit1.Position, unit2.Position, i); if (Trinity.Player.Position.Distance2D(fireChainSpot) <= fireChainSize) { Logger.Log(TrinityLogLevel.Debug, LogCategory.CacheManagement, "Avoiding Fire Chains!"); _standingInAvoidance = true; } CacheData.TimeBoundAvoidance.Add(new CacheObstacleObject(fireChainSpot, fireChainSize, -2, "FireChains")); } } if (CacheData.TimeBoundAvoidance.Any(aoe => aoe.ActorSNO == -2)) { Logger.Log(TrinityLogLevel.Debug, LogCategory.CacheManagement, "Generated {0} avoidance points for FireChains, minDistance={1} maxDistance={2}", CacheData.TimeBoundAvoidance.Count(aoe => aoe.ActorSNO == -2), CacheData.TimeBoundAvoidance.Where(aoe => aoe.ActorSNO == -2) .Min(aoe => aoe.Position.Distance2D(Trinity.Player.Position)), CacheData.TimeBoundAvoidance.Where(aoe => aoe.ActorSNO == -2) .Max(aoe => aoe.Position.Distance2D(Trinity.Player.Position))); } } } /* Beast Charge Experimental Avoidance */ if (Settings.Combat.Misc.UseExperimentalSavageBeastAvoidance) { const float beastChargePathWidth = 10f; const int beastChargerSNO = 3337; foreach (var unit1 in ObjectCache.Where(u => u.IsFacingPlayer && u.Animation == SNOAnim.Beast_start_charge_02 || u.Animation == SNOAnim.Beast_charge_02 || u.Animation == SNOAnim.Beast_charge_04)) { Vector3 endPoint = MathEx.GetPointAt(unit1.Position, 90f, unit1.Unit.Movement.Rotation); for (float i = 0; i <= unit1.Position.Distance2D(endPoint); i += (beastChargePathWidth / 4)) { Vector3 pathSpot = MathEx.CalculatePointFrom(unit1.Position, endPoint, i); Logger.Log(TrinityLogLevel.Debug, LogCategory.CacheManagement, "Generating BeastCharge Avoidance: {0} dist: {1}", pathSpot, pathSpot.Distance2D(unit1.Position)); if (Trinity.Player.Position.Distance2D(pathSpot) <= beastChargePathWidth) { Logger.Log(TrinityLogLevel.Debug, LogCategory.CacheManagement, "Avoiding Beast Charger!"); _standingInAvoidance = true; } CacheData.TimeBoundAvoidance.Add(new CacheObstacleObject(pathSpot, beastChargePathWidth, beastChargerSNO, "BeastCharge")); } if (CacheData.TimeBoundAvoidance.Any(aoe => aoe.ActorSNO == beastChargerSNO)) { Logger.Log(TrinityLogLevel.Debug, LogCategory.CacheManagement, "Generated {0} avoidance points for BeastCharge, minDistance={1} maxDistance={2}", CacheData.TimeBoundAvoidance.Count(aoe => aoe.ActorSNO == beastChargerSNO), CacheData.TimeBoundAvoidance.Where(aoe => aoe.ActorSNO == beastChargerSNO) .Min(aoe => aoe.Position.Distance2D(Trinity.Player.Position)), CacheData.TimeBoundAvoidance.Where(aoe => aoe.ActorSNO == beastChargerSNO) .Max(aoe => aoe.Position.Distance2D(Trinity.Player.Position))); } } } /* Poison Experimental Avoidance */ // Reduce ignore-for-loops counter if (_ignoreTargetForLoops > 0) { _ignoreTargetForLoops--; } // If we have an avoidance under our feet, then create a new object which contains a safety point to move to // But only if we aren't force-cancelling avoidance for XX time bool hasFoundSafePoint = false; using (new PerformanceLogger("RefreshDiaObjectCache.AvoidanceCheck")) { if (Player.IsGhosted) { _standingInAvoidance = true; } // Note that if treasure goblin level is set to kamikaze, even avoidance moves are disabled to reach the goblin! if (_standingInAvoidance && (!AnyTreasureGoblinsPresent || Settings.Combat.Misc.GoblinPriority <= GoblinPriority.Prioritize) && DateTime.UtcNow.Subtract(timeCancelledEmergencyMove).TotalMilliseconds >= cancelledEmergencyMoveForMilliseconds) { Vector3 vAnySafePoint = NavHelper.FindSafeZone(false, 1, Player.Position, true, null, true); // Ignore avoidance stuff if we're incapacitated or didn't find a safe spot we could reach if (vAnySafePoint != Vector3.Zero) { if (Settings.Advanced.LogCategories.HasFlag(LogCategory.Movement)) { Logger.Log(TrinityLogLevel.Verbose, LogCategory.Movement, "Kiting Avoidance: {0} Distance: {1:0} Direction: {2:0}, Health%={3:0.00}, KiteDistance: {4:0}", vAnySafePoint, vAnySafePoint.Distance(Me.Position), MathUtil.GetHeading(MathUtil.FindDirectionDegree(Me.Position, vAnySafePoint)), Player.CurrentHealthPct, CombatBase.PlayerKiteDistance); } hasFoundSafePoint = true; CurrentTarget = new TrinityCacheObject() { Position = vAnySafePoint, Type = GObjectType.Avoidance, Weight = 20000, Distance = Vector3.Distance(Player.Position, vAnySafePoint), Radius = 2f, InternalName = "SafePoint" };; } } } /* * Set Weights, assign CurrentTarget */ if (!hasFoundSafePoint) { RefreshDiaGetWeights(); RefreshSetKiting(ref KiteAvoidDestination, NeedToKite); } // Not heading straight for a safe-spot? // No valid targets but we were told to stay put? if (CurrentTarget == null && _shouldStayPutDuringAvoidance && !_standingInAvoidance) { CurrentTarget = new TrinityCacheObject() { Position = Player.Position, Type = GObjectType.Avoidance, Weight = 20000, Distance = 2f, Radius = 2f, InternalName = "StayPutPoint" }; Logger.Log(TrinityLogLevel.Debug, LogCategory.CacheManagement, "Staying Put During Avoidance"); } // Pre-townrun is too far away if (!Player.IsInTown && TownRun.PreTownRunPosition != Vector3.Zero && TownRun.PreTownRunWorldId == Player.WorldID && !ForceVendorRunASAP && TownRun.PreTownRunPosition.Distance2D(Player.Position) <= V.F("Cache.PretownRun.MaxDistance")) { Logger.Log(TrinityLogLevel.Debug, LogCategory.UserInformation, "Pre-TownRun position is more than {0} yards away, canceling", V.I("Cache.PretownRun.MaxDistance")); TownRun.PreTownRunPosition = Vector3.Zero; TownRun.PreTownRunWorldId = -1; } // Reached pre-townrun position if (!Player.IsInTown && TownRun.PreTownRunPosition != Vector3.Zero && TownRun.PreTownRunWorldId == Player.WorldID && !ForceVendorRunASAP && TownRun.PreTownRunPosition.Distance2D(Player.Position) <= 15f) { Logger.Log(TrinityLogLevel.Debug, LogCategory.UserInformation, "Successfully returned to Pre-TownRun Position"); TownRun.PreTownRunPosition = Vector3.Zero; TownRun.PreTownRunWorldId = -1; } // After a townrun, make sure to return to original TownRun Location if (!Player.IsInTown && CurrentTarget == null && TownRun.PreTownRunPosition != Vector3.Zero && TownRun.PreTownRunWorldId == Player.WorldID && !ForceVendorRunASAP) { if (TownRun.PreTownRunPosition.Distance2D(Player.Position) > 10f && TownRun.PreTownRunPosition.Distance2D(Player.Position) <= V.F("Cache.PretownRun.MaxDistance")) { CurrentTarget = new TrinityCacheObject() { Position = TownRun.PreTownRunPosition, Type = GObjectType.Avoidance, Weight = 20000, Distance = 2f, Radius = 2f, InternalName = "PreTownRunPosition" }; Logger.Log(TrinityLogLevel.Debug, LogCategory.UserInformation, "Returning to Pre-TownRun Position"); } } using (new PerformanceLogger("RefreshDiaObjectCache.FinalChecks")) { // force to stay put if we want to town run and there's no target if (CurrentTarget == null && ForceVendorRunASAP) { DontMoveMeIAmDoingShit = true; } if (Settings.WorldObject.EnableBountyEvents) { bool eventObjectNear = ObjectCache.Any(o => o.Type == GObjectType.CursedChest || o.Type == GObjectType.CursedShrine); if (!Player.InActiveEvent) { EventStartPosition = Vector3.Zero; EventStartTime = DateTime.MinValue; } // Reset Event time while we have targts if (CurrentTarget != null && Player.InActiveEvent && eventObjectNear) { EventStartTime = DateTime.UtcNow; } if (eventObjectNear) { EventStartPosition = ObjectCache.FirstOrDefault(o => o.Type == GObjectType.CursedChest || o.Type == GObjectType.CursedShrine).Position; } var activeEvent = ZetaDia.ActInfo.ActiveQuests.FirstOrDefault(q => DataDictionary.EventQuests.Contains(q.QuestSNO)); const int waitTimeoutSeconds = 90; if (DateTime.UtcNow.Subtract(EventStartTime).TotalSeconds > waitTimeoutSeconds && activeEvent != null) { CacheData.BlacklistedEvents.Add(activeEvent.QuestSNO); } if (CurrentTarget == null && Player.InActiveEvent && EventStartPosition != Vector3.Zero && DateTime.UtcNow.Subtract(EventStartTime).TotalSeconds < waitTimeoutSeconds && activeEvent != null && !CacheData.BlacklistedEvents.Contains(activeEvent.QuestSNO)) { CurrentTarget = new TrinityCacheObject() { Position = EventStartPosition, Type = GObjectType.Avoidance, Weight = 20000, Distance = 2f, Radius = 2f, InternalName = "WaitForEvent" }; Logger.Log("Waiting for Event {0} - Time Remaining: {1:0} seconds", ZetaDia.ActInfo.ActiveQuests.FirstOrDefault(q => DataDictionary.EventQuests.Contains(q.QuestSNO)).Quest, waitTimeoutSeconds - DateTime.UtcNow.Subtract(EventStartTime).TotalSeconds); } } // Still no target, let's see if we should backtrack or wait for wrath to come off cooldown... if (CurrentTarget == null) { RefreshDoBackTrack(); } // Still no target, let's end it all! if (CurrentTarget == null) { return(true); } if (CurrentTarget.IsUnit) { lastHadUnitInSights = DateTime.UtcNow; } if (CurrentTarget.IsBossOrEliteRareUnique) { lastHadEliteUnitInSights = DateTime.UtcNow; } if (CurrentTarget.IsBoss || CurrentTarget.IsBountyObjective) { lastHadBossUnitInSights = DateTime.UtcNow; } if (CurrentTarget.Type == GObjectType.Container) { lastHadContainerInSights = DateTime.UtcNow; } // Record the last time our target changed if (LastTargetRactorGUID != CurrentTarget.RActorGuid) { RecordTargetHistory(); Logger.Log(TrinityLogLevel.Verbose, LogCategory.Weight, "Found New Target {0} dist={1:0} IsElite={2} Radius={3:0.0} Weight={4:0} ActorSNO={5} " + "Anim={6} TargetedCount={7} Type={8} ", CurrentTarget.InternalName, CurrentTarget.Distance, CurrentTarget.IsEliteRareUnique, CurrentTarget.Radius, CurrentTarget.Weight, CurrentTarget.ActorSNO, CurrentTarget.Animation, CurrentTarget.TimesBeenPrimaryTarget, CurrentTarget.Type ); _lastPickedTargetTime = DateTime.UtcNow; _targetLastHealth = 0f; } else { // We're sticking to the same target, so update the target's health cache to check for stucks if (CurrentTarget.IsUnit) { // Check if the health has changed, if so update the target-pick time before we blacklist them again if (CurrentTarget.HitPointsPct != _targetLastHealth) { Logger.Log(TrinityLogLevel.Debug, LogCategory.Weight, "Keeping Target {0} - CurrentTarget.HitPoints: {1:0.00} TargetLastHealth: {2:0.00} ", CurrentTarget.RActorGuid, CurrentTarget.HitPointsPct, _targetLastHealth); _lastPickedTargetTime = DateTime.UtcNow; } // Now store the target's last-known health _targetLastHealth = CurrentTarget.HitPointsPct; } } } // We have a target and the cached was refreshed return(true); } }