protected virtual bool ShouldExplodingPalm(out TrinityActor target) { target = null; if (!Skills.Monk.ExplodingPalm.CanCast()) { return(false); } if (Player.PrimaryResource < PrimaryEnergyReserve) { return(false); } var isBigCluster = TargetUtil.ClusterExists(WaveOfLightRange, 3); var isEliteInRange = TargetUtil.AnyElitesInRange(WaveOfLightRange); var isFarTooMuchResource = Player.PrimaryResourcePct > 0.8f; if (isBigCluster || isEliteInRange || isFarTooMuchResource) { target = TargetUtil.BestExplodingPalmTarget(MeleeAttackRange); return(target != null); } return(false); }
/// <summary> /// Gets the best (non-movement related) avoidance power /// </summary> /// <returns></returns> private static TrinityPower GetCombatAvoidancePower() { // Defensive Teleport: SafePassage if (CanCast(SNOPower.Wizard_Teleport, CanCastFlags.NoTimer) && Runes.Wizard.SafePassage.IsActive && Player.CurrentHealthPct <= 0.50) { var target = NavHelper.FindSafeZone(false, 1, CurrentTarget.Position, true); return(new TrinityPower(SNOPower.Wizard_Teleport, 65f, target)); } // Diamond Skin: Tank mode if (CanCast(SNOPower.Wizard_DiamondSkin) && LastPowerUsed != SNOPower.Wizard_DiamondSkin && !GetHasBuff(SNOPower.Wizard_DiamondSkin) && (TargetUtil.AnyElitesInRange(25, 1) || TargetUtil.AnyMobsInRange(25, 1) || Player.CurrentHealthPct <= 0.90 || Player.IsIncapacitated || Player.IsRooted || CurrentTarget.RadiusDistance <= 40f)) { return(new TrinityPower(SNOPower.Wizard_DiamondSkin)); } // Explosive Blast if (CanCast(SNOPower.Wizard_ExplosiveBlast, CanCastFlags.NoTimer) && !Player.IsIncapacitated && !Player.IsInTown) { return(new TrinityPower(SNOPower.Wizard_ExplosiveBlast, 10f)); } // Frost Nova if (CanCast(SNOPower.Wizard_FrostNova) && !Player.IsIncapacitated && ((Runes.Wizard.DeepFreeze.IsActive && TargetUtil.AnyMobsInRange(25, 5)) || TargetUtil.AnyMobsInRange(25, 1) || Player.CurrentHealthPct <= 0.7) && CurrentTarget.RadiusDistance <= 25f) { return(new TrinityPower(SNOPower.Wizard_FrostNova, 20f)); } return(null); }
protected override bool ShouldWaveOfLight(out TrinityActor target) { target = null; if (!Skills.Monk.WaveOfLight.CanCast()) { return(false); } if (Player.PrimaryResource < PrimaryEnergyReserve) { return(false); } var isBigCluster = TargetUtil.ClusterExists(Settings.WoLRange, Settings.ClusterSize); var isEliteInRange = TargetUtil.AnyElitesInRange(Settings.WoLEliteRange); var isFarTooMuchResource = Player.PrimaryResourcePct > 0.8f; if (isBigCluster || isEliteInRange || isFarTooMuchResource) { target = TargetUtil.GetBestClusterUnit(); return(target != null); } if (IsBlocked || IsStuck) { target = TargetUtil.GetClosestUnit(Settings.WoLRange) ?? CurrentTarget; } else { target = TargetUtil.GetBestClusterUnit(Settings.WoLRange) ?? CurrentTarget; } return(true); }
protected virtual bool ShouldInnerSanctuary() { if (!Skills.Monk.InnerSanctuary.CanCast()) { return(false); } if (HasInstantCooldowns && !Skills.Monk.InnerSanctuary.IsLastUsed) { return(true); } if (Core.Buffs.HasBuff(SNOPower.X1_Monk_InnerSanctuary)) { return(false); } if (Player.CurrentHealthPct > 0.9 && !TargetUtil.AnyElitesInRange(10f)) { return(false); } if (TargetUtil.BestAoeUnit(25, Player.IsInParty).Distance > 20) { return(false); } return(true); }
protected virtual bool ShouldShadowPower() { if (!Skills.DemonHunter.ShadowPower.CanCast()) { return(false); } if (Player.HasBuff(SNOPower.DemonHunter_ShadowPower)) { return(false); } if (Skills.DemonHunter.ShadowPower.TimeSinceUse < 4500) { return(false); } if (TargetUtil.AnyElitesInRange(40f)) { return(true); } if (Player.CurrentHealthPct < 0.7f) { return(true); } if (TargetUtil.NumMobsInRange() > 5) { return(true); } return(false); }
protected virtual bool ShouldLeap(out Vector3 position) { position = Vector3.Zero; if (!Skills.Barbarian.Leap.CanCast()) { return(false); } position = TargetUtil.GetBestClusterPoint(); if (Legendary.LutSocks.IsEquipped && Skills.Barbarian.Leap.TimeSinceUse < 2000) { return(true); } if (Sets.MightOfTheEarth.IsFullyEquipped) { return(true); } if (!TargetUtil.ClusterExists(15f, 50f, 5) && !TargetUtil.AnyElitesInRange(50f)) { return(false); } return(position != Vector3.Zero); }
protected override bool ShouldTeleport(out Vector3 position) { position = Vector3.Zero; var skill = Skills.Wizard.Teleport; var affixOnPlayer = Core.Avoidance.InAvoidance(ZetaDia.Me.Position); var healthIsLow = Player.CurrentHealthPct < Settings.TeleportHealthEmergency; var archonHealthIsLow = Player.CurrentHealthPct < Settings.ArchonTeleportHealthEmergency; var anyElitesinRange = TargetUtil.AnyElitesInRange(Settings.TeleportEliteKiteRange); var anyMobsInRange = TargetUtil.AnyMobsInRangeOfPosition(Player.Position, Settings.TeleportTrashKiteRange, Settings.TeleportTrashInRangeCount); if (!skill.CanCast()) { return(false); } if (Player.IsChannelling || !Player.IsChannelling) { if (anyElitesinRange || anyMobsInRange || healthIsLow || affixOnPlayer) { Core.Logger.Log(LogCategory.Routine, $"Close Elites: {anyElitesinRange}, Mobs: {anyMobsInRange}, Health: {healthIsLow}, Affix: {affixOnPlayer}"); } return(true); } Avoider.TryGetSafeSpot(out position, Settings.TeleportKiteMinDistance, Settings.TeleportKiteMaxDistance, ZetaDia.Me.Position, node => !HostileMonsters.Any(m => m.Position.Distance(node.NavigableCenter) < 15f)); return(position != Vector3.Zero); }
protected bool IsReasonToUse(SkillSettings settings, Skill skill) { var routine = Core.Routines.CurrentRoutine; if (settings.Reasons.HasFlag(UseReasons.Elites) && TargetUtil.AnyElitesInRange(40f)) { return(true); } if (settings.Reasons.HasFlag(UseReasons.Trash) && TargetUtil.ClusterExists(routine.TrashRange, routine.TrashRange, routine.ClusterSize)) { return(true); } if (settings.Reasons.HasFlag(UseReasons.Surrounded) && TargetUtil.NumMobsInRange(25f) >= Math.Max(ClusterSize, 5)) { return(true); } if (settings.Reasons.HasFlag(UseReasons.Avoiding) && IsCurrentlyAvoiding) { return(true); } if (settings.Reasons.HasFlag(UseReasons.Blocked) && PlayerMover.IsBlocked) { return(true); } if (settings.Reasons.HasFlag(UseReasons.DumpResource) && Player.PrimaryResourcePct < 0.8f) { return(true); } if (settings.Reasons.HasFlag(UseReasons.Goblins) && WeightedUnits.Any(u => u.IsTreasureGoblin)) { return(true); } if (settings.Reasons.HasFlag(UseReasons.HealthEmergency) && Player.CurrentHealthPct < TrinityCombat.Routines.Current.EmergencyHealthPct) { return(true); } if (settings.Reasons.HasFlag(UseReasons.Buff) && settings.BuffCondition != null && settings.BuffCondition()) { return(true); } return(false); }
protected override bool ShouldLandOfTheDead(out TrinityActor target) { target = null; if (!Skills.Necromancer.LandOfTheDead.CanCast()) { return(false); } if (!TargetUtil.AnyElitesInRange(LotDRange) && !HasInstantCooldowns) { return(false); } target = FindBestTarget(); return(IsValidTarget(target)); }
protected virtual bool ShouldAvalanche(out Vector3 position) { position = Vector3.Zero; if (!Skills.Barbarian.Avalanche.CanCast()) { return(false); } if (!TargetUtil.ClusterExists(15f, 50f, 5) && !TargetUtil.AnyElitesInRange(50f)) { return(false); } position = TargetUtil.GetBestClusterPoint(); return(position != Vector3.Zero); }
protected override bool ShouldBlindingFlash() { if (!Skills.Monk.BlindingFlash.CanCast()) { return(false); } if (HasInstantCooldowns && Skills.Monk.BlindingFlash.TimeSinceUse > 2500) //Overlap them by half a second { return(true); } var lowHealth = Player.CurrentHealthPct <= Settings.EmergencyHealthPct; var enoughStuffToBlind = TargetUtil.AnyElitesInRange(20, 1) || TargetUtil.AnyMobsInRange(20, 3); var blindCurrentTarget = CurrentTarget != null && CurrentTarget.IsElite && CurrentTarget.RadiusDistance <= 15f; return(lowHealth || enoughStuffToBlind || blindCurrentTarget); }
protected virtual bool ShouldBlindingFlash() { if (!Skills.Monk.BlindingFlash.CanCast()) { return(false); } if (HasInstantCooldowns && !Skills.Monk.Epiphany.IsLastUsed) { return(true); } var lowHealth = Player.CurrentHealthPct <= 0.4; var enoughStuffToBlind = TargetUtil.AnyElitesInRange(15, 1) || TargetUtil.AnyMobsInRange(15, 3); var blindCurrentTarget = CurrentTarget != null && CurrentTarget.IsElite && CurrentTarget.RadiusDistance <= 15f; return(lowHealth || enoughStuffToBlind || blindCurrentTarget); }
protected virtual bool ShouldFanOfKnives() { if (!Skills.DemonHunter.FanOfKnives.CanCast()) { return(false); } if (Runes.DemonHunter.FanOfDaggers.IsActive && Player.CurrentHealthPct < 0.65 && TargetUtil.AnyMobsInRange(16f)) { return(true); } if (TargetUtil.NumMobsInRange(20f) >= 4 || TargetUtil.AnyElitesInRange(15f)) { return(true); } return(false); }
/// <summary> /// When Companion should be cast /// </summary> private static bool CompanionCondition(SkillMeta meta) { meta.CastFlags = CanCastFlags.NoTimer; // Use Spider Slow on 4 or more trash mobs in an area or on Unique/Elite/Champion if (Runes.DemonHunter.SpiderCompanion.IsActive && TargetUtil.ClusterExists(25f, 4) && TargetUtil.EliteOrTrashInRange(25f)) { return(true); } //Use Bat when Hatred is Needed if (Runes.DemonHunter.BatCompanion.IsActive && Player.PrimaryResourceMissing >= 60) { return(true); } // Use Boar Taunt on 3 or more trash mobs in an area or on Unique/Elite/Champion if (Runes.DemonHunter.BoarCompanion.IsActive && ((TargetUtil.ClusterExists(20f, 4) && TargetUtil.EliteOrTrashInRange(20f)) || (CurrentTarget.IsBossOrEliteRareUnique && CurrentTarget.Distance <= 20f))) { return(true); } // Ferrets used for picking up Health Globes when low on Health if (Runes.DemonHunter.FerretCompanion.IsActive && Trinity.ObjectCache.Any(o => o.Type == TrinityObjectType.HealthGlobe && o.Distance < 60f) && Player.CurrentHealthPct < EmergencyHealthPotionLimit) { return(true); } // 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 (Runes.DemonHunter.WolfCompanion.IsActive && (TargetUtil.AnyElitesInRange(100f) || TargetUtil.AnyMobsInRange(40, 8))) { return(true); } // Companion off CD if (Settings.Combat.DemonHunter.CompanionOffCooldown && TargetUtil.AnyMobsInRange(60)) { return(true); } return(false); }
protected override bool ShouldBoneSpear(out TrinityActor target) { target = null; if (!Skills.Necromancer.BoneSpear.CanCast()) { return(false); } if (Skills.Necromancer.LandOfTheDead.IsBuffActive) { return(false); } var boneSpearTarget = TargetUtil.AnyElitesInRange(60f) ? TargetUtil.BestEliteInRange(60f, true) : TargetUtil.LowestHealthTarget(60f); target = boneSpearTarget ?? CurrentTarget; return(target != null); }
/// <summary> /// Checks for all necessary buffs and combat conditions for starting Archon /// </summary> /// <returns></returns> private static bool ShouldStartArchon() { bool canCastArchon = ( CheckAbilityAndBuff(SNOPower.Wizard_MagicWeapon) && (!Hotbar.Contains(SNOPower.Wizard_Familiar) || IsFamiliarActive) && CheckAbilityAndBuff(SNOPower.Wizard_EnergyArmor) && CheckAbilityAndBuff(SNOPower.Wizard_IceArmor) && CheckAbilityAndBuff(SNOPower.Wizard_StormArmor) ); var elitesOnly = Settings.Combat.Wizard.ArchonElitesOnly && TargetUtil.AnyElitesInRange(Settings.Combat.Wizard.ArchonEliteDistance); var trashInRange = !Settings.Combat.Wizard.ArchonElitesOnly && TargetUtil.AnyMobsInRange(Settings.Combat.Wizard.ArchonMobDistance, Settings.Combat.Wizard.ArchonMobCount); // With Chantodos set wait until max stacks before using archon if (Sets.ChantodosResolve.IsFullyEquipped && CacheData.Buffs.HasBuff(SNOPower.P3_ItemPassive_Unique_Ring_021) && CacheData.Buffs.GetBuff(SNOPower.P3_ItemPassive_Unique_Ring_021).StackCount < 20) { return(false); } return(canCastArchon && (elitesOnly || trashInRange || CurrentTarget.IsBoss)); }
protected virtual bool ShouldCompanion() { if (!Skills.DemonHunter.Companion.CanCast()) { return(false); } // Use Spider Slow on 4 or more trash mobs in an area or on Unique/Elite/Champion if (Runes.DemonHunter.SpiderCompanion.IsActive && TargetUtil.ClusterExists(25f, 4) && TargetUtil.EliteOrTrashInRange(25f)) { return(true); } // Use Bat when Hatred is Needed if (Runes.DemonHunter.BatCompanion.IsActive && Player.PrimaryResourceMissing >= 60) { return(true); } // Use Boar Taunt on 3 or more trash mobs in an area or on Unique/Elite/Champion if (Runes.DemonHunter.BoarCompanion.IsActive && (TargetUtil.ClusterExists(20f, 4) && TargetUtil.EliteOrTrashInRange(20f) || CurrentTarget != null && CurrentTarget.IsElite && CurrentTarget.Distance <= 20f)) { return(true); } // Ferrets used for picking up Health Globes when low on Health if (Runes.DemonHunter.FerretCompanion.IsActive && Core.Targets.Entries.Any(o => o.Type == TrinityObjectType.HealthGlobe && o.Distance < 60f) && Player.CurrentHealthPct < EmergencyHealthPct) { return(true); } // Use Wolf Howl on Unique/Elite/Champion if (Runes.DemonHunter.WolfCompanion.IsActive && (TargetUtil.AnyElitesInRange(80f) || TargetUtil.AnyMobsInRange(40, 5))) { return(true); } return(false); }
/// <summary> /// When Rain of Vengeance should be cast /// </summary> private static bool RainOfVengeanceCondition(SkillMeta meta) { meta.CastRange = 90f; meta.CastFlags = CanCastFlags.NoTimer; if (Legendary.CrashingRain.IsEquipped) { meta.TargetPositionSelector = skillMeta => TargetUtil.GetBestClusterPoint(30f, 80f); } if (Settings.Combat.DemonHunter.RainOfVengeanceOffCD || Sets.NatalyasVengeance.IsEquipped) { return(true); } if (TargetUtil.ClusterExists(45f, 4) || TargetUtil.AnyElitesInRange(90f)) { return(true); } return(false); }
protected virtual bool ShouldSevenSidedStrike(out TrinityActor target) { target = null; if (!Skills.Monk.SevenSidedStrike.CanCast()) { return(false); } if (!TargetUtil.AnyMobsInRange(45f)) { return(false); } if (Player.PrimaryResource < PrimaryEnergyReserve) { return(false); } if (Skills.Monk.ExplodingPalm.IsActive && !WeightedUnits.Any(u => u.IsUnit && u.Distance < 35f && u.HasDebuff(SNOPower.Monk_ExplodingPalm))) { return(false); } var isElitesInRange = TargetUtil.AnyElitesInRange(15, 1); var isSpamRelatedItems = Legendary.Madstone.IsEquipped || Sets.UlianasStratagem.IsMaxBonusActive; var isLowHealth = Player.CurrentHealthPct < 0.55f; var isLargerCluster = TargetUtil.ClusterExists(20f, 20f, LargeClusterSize); if (isElitesInRange || isSpamRelatedItems || isLowHealth || isLargerCluster) { target = TargetUtil.GetBestClusterUnit(); return(target != null); } return(false); }
protected virtual bool ShouldEpiphany() { if (!Skills.Monk.Epiphany.CanCast()) { return(false); } if (HasInstantCooldowns && !Skills.Monk.Epiphany.IsLastUsed) { return(true); } if (Player.CurrentHealthPct > 0.9 && !TargetUtil.AnyElitesInRange(30f)) { return(false); } if (TargetUtil.BestAoeUnit(25, Player.IsInParty)?.Distance > 30 && !TargetUtil.AnyElitesInRange(20)) { return(false); } return(true); }
protected override bool ShouldSimulacrum(out Vector3 position) { position = Vector3.Zero; TrinityActor target; if (!Skills.Necromancer.Simulacrum.CanCast()) { return(false); } if (!TargetUtil.AnyElitesInRange(LotDRange) && !AlwaysSimulacrum && !HasInstantCooldowns) { return(false); } target = FindBestTarget(); if (!IsValidTarget(target)) { return(false); } position = target.Position; return(true); }
public TrinityPower GetBuffPower() { // ty RobbenHoschi if (Skills.Wizard.ExplosiveBlast.CanCast()) { return(ExplosiveBlast()); } if (Skills.Wizard.DiamondSkin.CanCast()) { return(DiamondSkin()); } if (Skills.Wizard.FrostNova.CanCast() && (TargetUtil.ClusterExists(25f, ClusterSize) || TargetUtil.AnyElitesInRange(40f))) { return(FrostNova()); } return(DefaultBuffPower()); }
public TrinityPower GetOffensivePower() { var position = Vector3.Zero; TrinityActor target; if (IsArchonActive) { target = TargetUtil.SafeList(true) .FirstOrDefault(a => a.IsBoss && !a.IsShadowClone && a.Distance < 125f) ?? TargetUtil.SafeList(true) .Where(a => a.IsElite && a.EliteType != EliteTypes.Minion && !a.IsIllusion && a.Distance < 125f) .OrderBy(a => a.NearbyUnitsWithinDistance(6f)) .FirstOrDefault() ?? TargetUtil.SafeList(true) .Where(a => a.IsTrashMob && a.IsInLineOfSight && !a.IsSummoner && !a.IsSummoned && a.Distance < 50f) .OrderByDescending(a => a.NearbyUnitsWithinDistance(6f)) .FirstOrDefault() ?? CurrentTarget; } else { target = TargetUtil.SafeList(true) .FirstOrDefault(a => a.IsBoss && !a.IsShadowClone && a.Distance < 125f) ?? TargetUtil.SafeList(true) .Where(a => a.IsMonster && a.Distance < 50f && a.IsInLineOfSight) .OrderBy(a => a.Distance) .FirstOrDefault(a => TrinityGrid.Instance.CanRayWalk(Player.Position, a.Position)) ?? CurrentTarget; } if (target == null) { return(null); } if (position != Vector3.Zero) { return(Teleport(position)); } Skill teleport = Skills.Wizard.Teleport; if (Skills.Wizard.ArchonTeleport.CanCast() || Skills.Wizard.Teleport.CanCast()) { teleport = IsArchonActive ? Skills.Wizard.ArchonTeleport : Skills.Wizard.Teleport; var needTeleport = !Core.Buffs.HasInvulnerableShrine && Core.Avoidance.InAvoidance(Player.Position) || !Core.Buffs.HasInvulnerableShrine && Player.CurrentHealthPct < 1 && teleport.TimeSinceUse > 1500 || TargetUtil.AnyElitesInRange(15) && teleport.TimeSinceUse > 2000 || TargetUtil.AnyBossesInRange(30) || TargetUtil.NumMobsInRange() > 3 || teleport.TimeSinceUse > 3000; if (needTeleport) { if (position == Vector3.Zero) { Core.Avoidance.Avoider.TryGetSafeSpot(out position, 30, 45); } return(Teleport(position)); } } if (IsArchonActive) { return(ArchonDisintegrationWave(target)); } if (Skills.Wizard.Archon.CanCast() && TalRashaStacks >= 3 && Player.Summons.HydraCount >= 2) { return(Archon()); } var hydra = Skills.Wizard.Hydra; var rayOfFrost = Skills.Wizard.RayOfFrost; var elite = TargetUtil.SafeList(true) .Where(a => a.IsElite && a.IsInLineOfSight && a.EliteType != EliteTypes.Minion && !a.IsIllusion && a.Distance < 50f) .OrderByDescending(a => a.Distance) .FirstOrDefault(); if (Skills.Wizard.Archon.CanCast() && TalRashaStacks < 3) { if (Player.Summons.HydraCount < 2 || hydra.TimeSinceUse > 6000) { return(Hydra(elite?.Position ?? target.Position)); } if (rayOfFrost.TimeSinceUse > 6000) { return(RayOfFrost(target)); } if (teleport.TimeSinceUse > 6000 && Core.Avoidance.Avoider.TryGetSafeSpot(out position, 20, 50)) { return(Teleport(position)); } } if (Skills.Wizard.Hydra.CanCast() && Player.Summons.HydraCount < 2 && IsInCombat) { return(new TrinityPower(Skills.Wizard.Hydra, 50, elite ?? target)); } if (Skills.Wizard.RayOfFrost.CanCast() && Player.PrimaryResource > 15) { return(RayOfFrost(target)); } return(Walk(Player.Position)); }
public TrinityPower GetOffensivePower() { Vector3 position; TrinityPower power; var allUnits = Core.Targets.ByType[TrinityObjectType.Unit].Where(u => u.IsUnit && u.RadiusDistance <= 50f).ToList(); var clusterUnits = (from u in allUnits where u.IsUnit && u.Weight > 0 && !u.IsPlayer orderby u.NearbyUnitsWithinDistance(15f) descending, u.Distance, u.HitPointsPct descending select u).ToList(); var bestClusterUnit = clusterUnits.FirstOrDefault(); //10 second 60% damage reduction should always be on to survive if (!HasJeramsRevengeBuff && Player.CurrentHealthPct > 0.4 && !Core.Avoidance.InCriticalAvoidance(Player.Position) && (ZetaDia.Me.IsInCombat || Player.CurrentHealthPct < 0.4) && bestClusterUnit != null && Skills.WitchDoctor.WallOfDeath.CanCast()) { Core.Logger.Log(LogCategory.Routine, $"Casting Wall of Death on {allUnits.FirstOrDefault()}"); return(WallOfDeath(allUnits.FirstOrDefault())); } if (bestClusterUnit != null) { if (Player.HasBuff(SNOPower.Witchdoctor_Hex)) { Core.Logger.Log(LogCategory.Routine, $"Casting Explode Chicken"); Vector3 explodePos = PlayerMover.IsBlocked ? Player.Position : bestClusterUnit.Position; return(ExplodeChicken(explodePos)); } if (!HasJeramsRevengeBuff && ZetaDia.Me.IsInCombat && Skills.WitchDoctor.WallOfDeath.CanCast()) { Core.Logger.Log(LogCategory.Routine, $"Casting Wall of Death on {allUnits.FirstOrDefault()}"); return(WallOfDeath(allUnits.FirstOrDefault())); } if (!Player.HasBuff(SNOPower.Witchdoctor_Hex) && Skills.WitchDoctor.Hex.CanCast()) { Core.Logger.Log(LogCategory.Routine, $"Casting Hex"); return(Hex(CurrentTarget.Position)); } var targetsWithoutLocust = clusterUnits.Where(u => !u.HasDebuff(SNOPower.Witchdoctor_Locust_Swarm)).OrderBy(u => u.Distance); var isAnyTargetWithLocust = clusterUnits.Any(u => u.HasDebuff(SNOPower.Witchdoctor_Locust_Swarm) && u.Distance < 45f); var percentTargetsWithHaunt = TargetUtil.DebuffedPercent(SNOPower.Witchdoctor_Haunt, 8f); var percentTargetsWithLocust = TargetUtil.DebuffedPercent(SNOPower.Witchdoctor_Locust_Swarm, 12f); var isEliteWithoutHaunt = clusterUnits.Any(u => u.IsElite && !u.HasDebuff(SNOPower.Witchdoctor_Haunt)); var isElitewithoutLocust = clusterUnits.Any(u => u.IsElite && !u.HasDebuff(SNOPower.Witchdoctor_Locust_Swarm)); var harvestStacks = Skills.WitchDoctor.SoulHarvest.BuffStacks; var harvestBuffCooldown = Core.Cooldowns.GetBuffCooldown(SNOPower.Witchdoctor_SoulHarvest); var harvestPossibleStackGain = 10 - harvestStacks; var harvestUnitsInRange = allUnits.Count(u => u.Distance < 12f); var interruptForHarvest = Skills.WitchDoctor.SoulHarvest.CanCast() && harvestPossibleStackGain >= harvestUnitsInRange && harvestBuffCooldown?.Remaining.TotalMilliseconds < 500; var interruptForHaunt = percentTargetsWithHaunt < 0.2f || isEliteWithoutHaunt; var needToSwarmElite = isElitewithoutLocust && !((Legendary.VileHive.IsEquipped || Runes.WitchDoctor.Pestilence.IsActive) && isAnyTargetWithLocust); var interruptForLocust = (percentTargetsWithLocust < 0.1f || needToSwarmElite) && Player.PrimaryResource > 300 && Skills.WitchDoctor.LocustSwarm.CanCast(); // continue channelling firebats? if (Player.IsChannelling) { if (!interruptForHaunt && !interruptForLocust && !interruptForHarvest) { Core.Logger.Log(LogCategory.Routine, "Continuation of Firebats."); return(new TrinityPower(SNOPower.Witchdoctor_Firebats, 30f, Player.Position, 75, 250)); } if (interruptForHaunt) { Core.Logger.Log(LogCategory.Routine, "Interrupted Firebats to haunt"); } if (interruptForLocust) { Core.Logger.Log(LogCategory.Routine, "Interrupted Firebats to locust"); } if (interruptForHarvest) { Core.Logger.Log(LogCategory.Routine, "Interrupted Firebats to harvest"); } } // Emergency health situation if (Player.CurrentHealthPct < 0.35) { if (Skills.WitchDoctor.SpiritWalk.CanCast()) { Core.Logger.Log(LogCategory.Routine, $"Defensive Spirit Walking"); return(SpiritWalk()); } if (TargetUtil.AnyMobsInRange(12f) && Skills.WitchDoctor.SoulHarvest.CanCast()) { Core.Logger.Log(LogCategory.Routine, "Emergency Harvest"); return(SoulHarvest()); } if (!HasJeramsRevengeBuff && Skills.WitchDoctor.WallOfDeath.CanCast() && allUnits.Any()) { Core.Logger.Log(LogCategory.Routine, $"Casting Defensive WallOfDeath on {allUnits.FirstOrDefault()}"); return(WallOfDeath(allUnits.FirstOrDefault())); } } // Locust if (Skills.WitchDoctor.LocustSwarm.CanCast() && Skills.WitchDoctor.LocustSwarm.TimeSinceUse > 1000 && targetsWithoutLocust.Any() && (!Runes.WitchDoctor.Pestilence.IsActive || !isAnyTargetWithLocust)) { if ((percentTargetsWithLocust < Settings.LocustPct || needToSwarmElite) && Player.PrimaryResource > 300 && targetsWithoutLocust.Any()) { Core.Logger.Log(LogCategory.Routine, "Locust"); return(new TrinityPower(SNOPower.Witchdoctor_Locust_Swarm, 10f, targetsWithoutLocust.First().Position, 0, 0)); } } // Soul harvest for the damage reduction of Okumbas Ornament if (Skills.WitchDoctor.SoulHarvest.CanCast() && (bestClusterUnit.Distance < 12f || harvestStacks < 4 && TargetUtil.AnyMobsInRange(10f)) && harvestStacks < 10) { Core.Logger.Log(LogCategory.Routine, $"Harvest State: StackGainPossible={harvestPossibleStackGain} Units={harvestUnitsInRange} BuffRemainingSecs:{harvestBuffCooldown?.Remaining.TotalSeconds:N2}"); if (harvestPossibleStackGain <= harvestUnitsInRange) { Core.Logger.Log(LogCategory.Routine, $"Soul Harvest."); return(SoulHarvest()); } } if (ShouldBigBadVoodoo(out position)) { return(BigBadVoodoo(position)); } // Piranhas if (Skills.WitchDoctor.Piranhas.CanCast() && Player.PrimaryResource >= 250 && (TargetUtil.ClusterExists(15f, 40f) || TargetUtil.AnyElitesInRange(40f)) && Player.PrimaryResource >= 250) { return(Piranhas(TargetUtil.GetBestClusterUnit())); } // .80 of mobs give or take. Spelltracker check is to prevent repeat casts ont he same target before the projectile arrives. var targetsWithoutHaunt = clusterUnits.Where(u => !u.HasDebuff(SNOPower.Witchdoctor_Haunt) && !SpellTracker.IsUnitTracked(u, SNOPower.Witchdoctor_Haunt)).OrderBy(u => u.Distance); if ((percentTargetsWithHaunt < Settings.HauntPct || isEliteWithoutHaunt) && targetsWithoutHaunt.Any() && Player.PrimaryResource > 100) { var target = targetsWithoutHaunt.First(); Core.Logger.Log(LogCategory.Routine, $"Haunt on {target}"); return(Haunt(target)); } Vector3 bestBuffedPosition; TargetUtil.BestBuffPosition(16f, bestClusterUnit.Position, true, out bestBuffedPosition); var bestClusterUnitRadiusPosition = MathEx.GetPointAt(bestClusterUnit.Position, bestClusterUnit.CollisionRadius * 1.1f, bestClusterUnit.Rotation); var bestFirebatsPosition = bestBuffedPosition != Vector3.Zero ? bestBuffedPosition : bestClusterUnitRadiusPosition; var distance = bestFirebatsPosition.Distance(Player.Position); // Walk into cluster or buffed location. if (distance > 10f && !PlayerMover.IsBlocked) { if (distance > 20f && Skills.WitchDoctor.SpiritWalk.CanCast()) { Core.Logger.Log(LogCategory.Routine, $"Spirit Walking"); return(SpiritWalk()); } Core.Logger.Warn($"Walking to cluster position. Dist: {bestFirebatsPosition.Distance(Player.Position)}"); return(new TrinityPower(SNOPower.Walk, 3f, bestFirebatsPosition, 0, 0)); } if (Skills.WitchDoctor.Firebats.CanCast()) { var closestUnit = allUnits.OrderBy(u => u.Distance).FirstOrDefault(); if (closestUnit != null) { Core.Logger.Log(LogCategory.Routine, $"Casting Firebats"); return(Firebats(closestUnit)); } } } //if (IsChannellingFirebats && Player.CurrentHealthPct > 0.5f && TargetUtil.AnyMobsInRange(FireBatsRange)) // return Firebats(); //if (TrySpecialPower(out power)) // return power; //if (TrySecondaryPower(out power)) // return power; //if (TryPrimaryPower(out power)) // return power; return(Walk(TargetUtil.GetLoiterPosition(CurrentTarget, 15f))); }
public void MoveTowards(Vector3 vMoveToTarget) { if (!ZetaDia.IsInGame || !ZetaDia.Me.IsValid || ZetaDia.Me.IsDead || ZetaDia.IsLoadingWorld) { return; } if (UISafetyCheck()) { return; } TimeLastUsedPlayerMover = DateTime.Now; vMyCurrentPosition = GilesTrinity.PlayerStatus.CurrentPosition; LastMoveToTarget = vMoveToTarget; // record speed once per second if (DateTime.Now.Subtract(lastRecordedPosition).TotalMilliseconds >= 1000) { // Record our current location and time if (!SpeedSensors.Any()) { SpeedSensors.Add(new SpeedSensor() { Location = vMyCurrentPosition, TimeSinceLastMove = new TimeSpan(0), Distance = 0f, WorldID = GilesTrinity.CurrentWorldDynamicId }); } else { SpeedSensor lastSensor = SpeedSensors.OrderByDescending(s => s.Timestamp).FirstOrDefault(); SpeedSensors.Add(new SpeedSensor() { Location = vMyCurrentPosition, TimeSinceLastMove = new TimeSpan(DateTime.Now.Subtract(lastSensor.TimeSinceLastMove).Ticks), Distance = Vector3.Distance(vMyCurrentPosition, lastSensor.Location), WorldID = GilesTrinity.CurrentWorldDynamicId }); } lastRecordedPosition = DateTime.Now; } // Set the public variable MovementSpeed = GetMovementSpeed(); vMoveToTarget = WarnAndLogLongPath(vMoveToTarget); // Make sure GilesTrinity doesn't want us to avoid routine-movement //if (GilesTrinity.bDontMoveMeIAmDoingShit) // return; // Store player current position // Store distance to current moveto target float DestinationDistance; DestinationDistance = vMyCurrentPosition.Distance2D(vMoveToTarget); // Do unstuckery things if (GilesTrinity.Settings.Advanced.UnstuckerEnabled) { // See if we can reset the 10-limit unstuck counter, if >120 seconds since we last generated an unstuck location // this is used if we're NOT stuck... if (iTotalAntiStuckAttempts > 1 && DateTime.Now.Subtract(LastGeneratedStuckPosition).TotalSeconds >= 120) { iTotalAntiStuckAttempts = 1; iTimesReachedStuckPoint = 0; vSafeMovementLocation = Vector3.Zero; NavHelper.UsedStuckSpots = new List <GridPoint>(); DbHelper.Log(TrinityLogLevel.Normal, LogCategory.Movement, "Resetting unstuck timers", true); } // See if we need to, and can, generate unstuck actions // check if we're stuck bool isStuck = UnstuckChecker(vMyCurrentPosition); if (DateTime.Now.Subtract(_lastCancelledUnstucker).TotalSeconds > iCancelUnstuckerForSeconds && isStuck) { // Record the time we last apparently couldn't move for a brief period of time _lastRecordedAnyStuck = DateTime.Now; // See if there's any stuck position to try and navigate to generated by random mover vSafeMovementLocation = UnstuckHandler(vMyCurrentPosition, LastMoveToTarget); DbHelper.Log(TrinityLogLevel.Normal, LogCategory.Movement, "SafeMovement Location set to {0}", vSafeMovementLocation); if (vSafeMovementLocation == Vector3.Zero) { return; } } // See if we can clear the total unstuckattempts if we haven't been stuck in over 6 minutes. if (DateTime.Now.Subtract(_lastRecordedAnyStuck).TotalSeconds >= 360) { iTimesReachedMaxUnstucks = 0; } // Did we have a safe point already generated (eg from last loop through), if so use it as our current location instead if (vSafeMovementLocation != Vector3.Zero) { // Set our current movement target to the safe point we generated last cycle vMoveToTarget = vSafeMovementLocation; DestinationDistance = vMyCurrentPosition.Distance2D(vMoveToTarget); } // Get distance to current destination // Remove the stuck position if it's been reached, this bit of code also creates multiple stuck-patterns in an ever increasing amount if (vSafeMovementLocation != Vector3.Zero && DestinationDistance <= 3f) { vSafeMovementLocation = Vector3.Zero; iTimesReachedStuckPoint++; // Do we want to immediately generate a 2nd waypoint to "chain" anti-stucks in an ever-increasing path-length? if (iTimesReachedStuckPoint <= iTotalAntiStuckAttempts) { //GilesTrinity.PlayerStatus.CurrentPosition = vMyCurrentPosition; vSafeMovementLocation = NavHelper.FindSafeZone(true, iTotalAntiStuckAttempts, vMyCurrentPosition); vMoveToTarget = vSafeMovementLocation; } else { if (GilesTrinity.Settings.Advanced.LogCategories.HasFlag(LogCategory.Movement)) { DbHelper.Log(TrinityLogLevel.Verbose, LogCategory.Movement, "Clearing old route and trying new path find to: " + LastMoveToTarget.ToString()); } // Reset the path and allow a whole "New" unstuck generation next cycle iTimesReachedStuckPoint = 0; // And cancel unstucking for 9 seconds so DB can try to navigate iCancelUnstuckerForSeconds = (9 * iTotalAntiStuckAttempts); if (iCancelUnstuckerForSeconds < 20) { iCancelUnstuckerForSeconds = 20; } _lastCancelledUnstucker = DateTime.Now; Navigator.Clear(); Navigator.MoveTo(LastMoveToTarget, "original destination", false); return; } } } // Is the built-in unstucker enabled or not? // if (GilesTrinity.Settings.Advanced.DebugInStatusBar) // { // Logging.WriteDiagnostic("[Trinity] Moving toward <{0:0},{1:0},{2:0}> distance: {3:0}", vMoveToTarget.X, vMoveToTarget.Y, vMoveToTarget.Z, fDistanceFromTarget); // } // See if there's an obstacle in our way, if so try to navigate around it Vector3 point = vMoveToTarget; foreach (GilesObstacle obstacle in GilesTrinity.hashNavigationObstacleCache.Where(o => vMoveToTarget.Distance2D(o.Location) <= o.Radius)) { if (vShiftedPosition == Vector3.Zero) { // Make sure we only shift max once every 6 seconds if (DateTime.Now.Subtract(lastShiftedPosition).TotalMilliseconds >= 6000) { DbHelper.Log(TrinityLogLevel.Debug, LogCategory.Movement, "Shifting position for Navigation Obstacle {0} {1} at {2}", obstacle.ActorSNO, obstacle.Name, obstacle.Location); GetShiftedPosition(ref vMoveToTarget, ref point, obstacle.Radius + 5f); } } else { if (DateTime.Now.Subtract(lastShiftedPosition).TotalMilliseconds <= iShiftPositionFor) { vMoveToTarget = vShiftedPosition; } else { vShiftedPosition = Vector3.Zero; } } } // don't use special movement within 10 seconds of being stuck bool cancelSpecialMovementAfterStuck = DateTime.Now.Subtract(LastGeneratedStuckPosition).TotalMilliseconds > 10000; // See if we can use abilities like leap etc. for movement out of combat, but not in town if (GilesTrinity.Settings.Combat.Misc.AllowOOCMovement && !GilesTrinity.PlayerStatus.IsInTown && !GilesTrinity.bDontMoveMeIAmDoingShit && cancelSpecialMovementAfterStuck) { bool bTooMuchZChange = (Math.Abs(vMyCurrentPosition.Z - vMoveToTarget.Z) >= 4f); // Whirlwind for a barb, special context only if (GilesTrinity.Hotbar.Contains(SNOPower.Barbarian_Whirlwind) && GilesTrinity.GilesObjectCache.Count(u => u.Type == GObjectType.Unit && u.RadiusDistance <= 10f) >= 1 && GilesTrinity.PlayerStatus.PrimaryResource >= 10) { ZetaDia.Me.UsePower(SNOPower.Barbarian_Whirlwind, vMoveToTarget, GilesTrinity.CurrentWorldDynamicId, -1); if (GilesTrinity.Settings.Advanced.LogCategories.HasFlag(LogCategory.Movement)) { DbHelper.Log(TrinityLogLevel.Debug, LogCategory.Movement, "Using Whirlwind for OOC movement, distance={0}", DestinationDistance); } return; } // Leap movement for a barb if (GilesTrinity.Hotbar.Contains(SNOPower.Barbarian_Leap) && DateTime.Now.Subtract(GilesTrinity.dictAbilityLastUse[SNOPower.Barbarian_Leap]).TotalMilliseconds >= GilesTrinity.dictAbilityRepeatDelay[SNOPower.Barbarian_Leap] && DestinationDistance >= 20f && PowerManager.CanCast(SNOPower.Barbarian_Leap) && !ShrinesInArea(vMoveToTarget)) { Vector3 vThisTarget = vMoveToTarget; if (DestinationDistance > 35f) { vThisTarget = MathEx.CalculatePointFrom(vMoveToTarget, vMyCurrentPosition, 35f); } ZetaDia.Me.UsePower(SNOPower.Barbarian_Leap, vThisTarget, GilesTrinity.CurrentWorldDynamicId, -1); GilesTrinity.dictAbilityLastUse[SNOPower.Barbarian_Leap] = DateTime.Now; if (GilesTrinity.Settings.Advanced.LogCategories.HasFlag(LogCategory.Movement)) { DbHelper.Log(TrinityLogLevel.Debug, LogCategory.Movement, "Using Leap for OOC movement, distance={0}", DestinationDistance); } return; } // Furious Charge movement for a barb if (GilesTrinity.Hotbar.Contains(SNOPower.Barbarian_FuriousCharge) && !bTooMuchZChange && DateTime.Now.Subtract(GilesTrinity.dictAbilityLastUse[SNOPower.Barbarian_FuriousCharge]).TotalMilliseconds >= GilesTrinity.dictAbilityRepeatDelay[SNOPower.Barbarian_FuriousCharge] && DestinationDistance >= 20f && PowerManager.CanCast(SNOPower.Barbarian_FuriousCharge) && !ShrinesInArea(vMoveToTarget)) { Vector3 vThisTarget = vMoveToTarget; if (DestinationDistance > 35f) { vThisTarget = MathEx.CalculatePointFrom(vMoveToTarget, vMyCurrentPosition, 35f); } ZetaDia.Me.UsePower(SNOPower.Barbarian_FuriousCharge, vThisTarget, GilesTrinity.CurrentWorldDynamicId, -1); GilesTrinity.dictAbilityLastUse[SNOPower.Barbarian_FuriousCharge] = DateTime.Now; if (GilesTrinity.Settings.Advanced.LogCategories.HasFlag(LogCategory.Movement)) { DbHelper.Log(TrinityLogLevel.Debug, LogCategory.Movement, "Using Furious Charge for OOC movement, distance={0}", DestinationDistance); } return; } // Vault for a DH - maximum set by user-defined setting if (GilesTrinity.Hotbar.Contains(SNOPower.DemonHunter_Vault) && !bTooMuchZChange && DateTime.Now.Subtract(GilesTrinity.dictAbilityLastUse[SNOPower.DemonHunter_Vault]).TotalMilliseconds >= GilesTrinity.Settings.Combat.DemonHunter.VaultMovementDelay && DestinationDistance >= 18f && PowerManager.CanCast(SNOPower.DemonHunter_Vault) && !ShrinesInArea(vMoveToTarget) && // Don't Vault into avoidance/monsters if we're kiting (GilesTrinity.PlayerKiteDistance <= 0 || (GilesTrinity.PlayerKiteDistance > 0 && (!GilesTrinity.hashAvoidanceObstacleCache.Any(a => a.Location.Distance(vMoveToTarget) <= GilesTrinity.PlayerKiteDistance) || (!GilesTrinity.hashAvoidanceObstacleCache.Any(a => MathEx.IntersectsPath(a.Location, a.Radius, GilesTrinity.PlayerStatus.CurrentPosition, vMoveToTarget))) || !GilesTrinity.hashMonsterObstacleCache.Any(a => a.Location.Distance(vMoveToTarget) <= GilesTrinity.PlayerKiteDistance)))) ) { Vector3 vThisTarget = vMoveToTarget; if (DestinationDistance > 35f) { vThisTarget = MathEx.CalculatePointFrom(vMoveToTarget, vMyCurrentPosition, 35f); } ZetaDia.Me.UsePower(SNOPower.DemonHunter_Vault, vThisTarget, GilesTrinity.CurrentWorldDynamicId, -1); GilesTrinity.dictAbilityLastUse[SNOPower.DemonHunter_Vault] = DateTime.Now; if (GilesTrinity.Settings.Advanced.LogCategories.HasFlag(LogCategory.Movement)) { DbHelper.Log(TrinityLogLevel.Debug, LogCategory.Movement, "Using Vault for OOC movement, distance={0}", DestinationDistance); } return; } // Tempest rush for a monk if (GilesTrinity.Hotbar.Contains(SNOPower.Monk_TempestRush) && (GilesTrinity.Settings.Combat.Monk.TROption == TempestRushOption.MovementOnly || GilesTrinity.Settings.Combat.Monk.TROption == TempestRushOption.Always || (GilesTrinity.Settings.Combat.Monk.TROption == TempestRushOption.TrashOnly && !TargetUtil.AnyElitesInRange(40f)))) { Vector3 vTargetAimPoint = vMoveToTarget; //vTargetAimPoint = MathEx.CalculatePointFrom(vMoveToTarget, vMyCurrentPosition, aimPointDistance); //vTargetAimPoint = MathEx.CalculatePointFrom(vMyCurrentPosition, vMoveToTarget, aimPointDistance); //bool canRayCastTarget = GilesTrinity.NavHelper.CanRayCast(vMyCurrentPosition, vTargetAimPoint); bool canRayCastTarget = true; vTargetAimPoint = TargetUtil.FindTempestRushTarget(); if (!CanChannelTempestRush && ((GilesTrinity.PlayerStatus.PrimaryResource >= GilesTrinity.Settings.Combat.Monk.TR_MinSpirit && DestinationDistance >= GilesTrinity.Settings.Combat.Monk.TR_MinDist) || DateTime.Now.Subtract(GilesTrinity.dictAbilityLastUse[SNOPower.Monk_TempestRush]).TotalMilliseconds <= 150) && canRayCastTarget && PowerManager.CanCast(SNOPower.Monk_TempestRush)) { CanChannelTempestRush = true; } else if ((CanChannelTempestRush && (GilesTrinity.PlayerStatus.PrimaryResource < 10f)) || !canRayCastTarget) { CanChannelTempestRush = false; } double lastUse = DateTime.Now.Subtract(GilesTrinity.dictAbilityLastUse[SNOPower.Monk_TempestRush]).TotalMilliseconds; if (CanChannelTempestRush) { if (GilesTrinity.GilesUseTimer(SNOPower.Monk_TempestRush)) { LastTempestRushPosition = vTargetAimPoint; ZetaDia.Me.UsePower(SNOPower.Monk_TempestRush, vTargetAimPoint, GilesTrinity.CurrentWorldDynamicId, -1); GilesTrinity.dictAbilityLastUse[SNOPower.Monk_TempestRush] = DateTime.Now; GilesTrinity.LastPowerUsed = SNOPower.Monk_TempestRush; // simulate movement speed of 30 SpeedSensor lastSensor = SpeedSensors.OrderByDescending(s => s.Timestamp).FirstOrDefault(); SpeedSensors.Add(new SpeedSensor() { Location = vMyCurrentPosition, TimeSinceLastMove = new TimeSpan(0, 0, 0, 0, 1000), Distance = 5f, WorldID = GilesTrinity.CurrentWorldDynamicId }); if (GilesTrinity.Settings.Advanced.LogCategories.HasFlag(LogCategory.Movement)) { DbHelper.Log(TrinityLogLevel.Debug, LogCategory.Movement, "Using Tempest Rush for OOC movement, distance={0:0} spirit={1:0} cd={2} lastUse={3:0} V3={4} vAim={5}", DestinationDistance, GilesTrinity.PlayerStatus.PrimaryResource, PowerManager.CanCast(SNOPower.Monk_TempestRush), lastUse, vMoveToTarget, vTargetAimPoint); } return; } else { return; } } else { if (GilesTrinity.Settings.Advanced.LogCategories.HasFlag(LogCategory.Movement)) { DbHelper.Log(TrinityLogLevel.Debug, LogCategory.Movement, "Tempest rush failed!: {0:00.0} / {1} distance: {2:00.0} / {3} Raycast: {4} MS: {5:0.0} lastUse={6:0}", GilesTrinity.PlayerStatus.PrimaryResource, GilesTrinity.Settings.Combat.Monk.TR_MinSpirit, DestinationDistance, GilesTrinity.Settings.Combat.Monk.TR_MinDist, canRayCastTarget, GetMovementSpeed(), lastUse); } GilesTrinity.MaintainTempestRush = false; } // Always set this from PlayerMover GilesTrinity.LastTempestRushLocation = vTargetAimPoint; } bool hasWormHole = HotbarSkills.AssignedSkills.Any(s => s.Power == SNOPower.Wizard_Teleport && s.RuneIndex == 4); // Teleport for a wizard (need to be able to check skill rune in DB for a 3-4 teleport spam in a row) if (GilesTrinity.Hotbar.Contains(SNOPower.Wizard_Teleport) && ((PowerManager.CanCast(SNOPower.Wizard_Teleport) && DateTime.Now.Subtract(GilesTrinity.dictAbilityLastUse[SNOPower.Wizard_Teleport]).TotalMilliseconds >= GilesTrinity.dictAbilityRepeatDelay[SNOPower.Wizard_Teleport]) || (hasWormHole && WizardTeleportCount < 3 && DateTime.Now.Subtract(GilesTrinity.dictAbilityLastUse[SNOPower.Wizard_Teleport]).TotalMilliseconds >= 250)) && DestinationDistance >= 10f && !ShrinesInArea(vMoveToTarget)) { // Reset teleport count if we've already hit the max if (WizardTeleportCount >= 3) { WizardTeleportCount = 0; } // increment the teleport count for wormhole rune WizardTeleportCount++; var maxTeleportRange = 75f; Vector3 vThisTarget = vMoveToTarget; if (DestinationDistance > maxTeleportRange) { vThisTarget = MathEx.CalculatePointFrom(vMoveToTarget, vMyCurrentPosition, maxTeleportRange); } ZetaDia.Me.UsePower(SNOPower.Wizard_Teleport, vThisTarget, GilesTrinity.CurrentWorldDynamicId, -1); GilesTrinity.dictAbilityLastUse[SNOPower.Wizard_Teleport] = DateTime.Now; if (GilesTrinity.Settings.Advanced.LogCategories.HasFlag(LogCategory.Movement)) { DbHelper.Log(TrinityLogLevel.Debug, LogCategory.Movement, "Using Teleport for OOC movement, distance={0}", DestinationDistance); } return; } // Archon Teleport for a wizard if (GilesTrinity.Hotbar.Contains(SNOPower.Wizard_Archon_Teleport) && DateTime.Now.Subtract(GilesTrinity.dictAbilityLastUse[SNOPower.Wizard_Archon_Teleport]).TotalMilliseconds >= GilesTrinity.dictAbilityRepeatDelay[SNOPower.Wizard_Archon_Teleport] && DestinationDistance >= 20f && PowerManager.CanCast(SNOPower.Wizard_Archon_Teleport) && !ShrinesInArea(vMoveToTarget)) { Vector3 vThisTarget = vMoveToTarget; if (DestinationDistance > 35f) { vThisTarget = MathEx.CalculatePointFrom(vMoveToTarget, vMyCurrentPosition, 35f); } ZetaDia.Me.UsePower(SNOPower.Wizard_Archon_Teleport, vThisTarget, GilesTrinity.CurrentWorldDynamicId, -1); GilesTrinity.dictAbilityLastUse[SNOPower.Wizard_Archon_Teleport] = DateTime.Now; if (GilesTrinity.Settings.Advanced.LogCategories.HasFlag(LogCategory.Movement)) { DbHelper.Log(TrinityLogLevel.Debug, LogCategory.Movement, "Using Archon Teleport for OOC movement, distance={0}", DestinationDistance); } return; } } if (vMyCurrentPosition.Distance2D(vMoveToTarget) > 1f) { // Default movement ZetaDia.Me.UsePower(SNOPower.Walk, vMoveToTarget, GilesTrinity.CurrentWorldDynamicId, -1); if (GilesTrinity.Settings.Advanced.LogCategories.HasFlag(LogCategory.Movement)) { DbHelper.Log(TrinityLogLevel.Debug, LogCategory.Movement, "Moved to:{0} dir: {1} Speed:{2:0.00} Dist:{3:0} ZDiff:{4:0} Nav:{5} LoS:{6}", vMoveToTarget, MathUtil.GetHeadingToPoint(vMoveToTarget), MovementSpeed, vMyCurrentPosition.Distance2D(vMoveToTarget), Math.Abs(vMyCurrentPosition.Z - vMoveToTarget.Z), GilesTrinity.gp.CanStandAt(GilesTrinity.gp.WorldToGrid(vMoveToTarget.ToVector2())), !Navigator.Raycast(vMyCurrentPosition, vMoveToTarget) ); } } else { if (GilesTrinity.Settings.Advanced.LogCategories.HasFlag(LogCategory.Movement)) { DbHelper.Log(TrinityLogLevel.Debug, LogCategory.Movement, "Reached MoveTowards Destination {0} Current Speed: {1:0.0}", vMoveToTarget, MovementSpeed); } } }
public static TrinityPower GetPower() { TrinityPower power = null; // Spirit Walk, always! if (CanCast(SNOPower.Witchdoctor_SpiritWalk)) { return(new TrinityPower(SNOPower.Witchdoctor_SpiritWalk)); } var hasAngryChicken = (Skills.WitchDoctor.Hex.IsActive && Runes.WitchDoctor.AngryChicken.IsActive) || CacheData.Hotbar.ActivePowers.Contains(SNOPower.Witchdoctor_Hex_ChickenWalk); if (hasAngryChicken && Sets.ManajumasWay.IsEquipped && CanCast(SNOPower.Witchdoctor_Hex)) { return(new TrinityPower(SNOPower.Witchdoctor_Hex)); } // Combat Avoidance Spells if (!UseOOCBuff && IsCurrentlyAvoiding) { // Spirit Walk out of AoE if (CanCast(SNOPower.Witchdoctor_SpiritWalk)) { return(new TrinityPower(SNOPower.Witchdoctor_SpiritWalk)); } // Soul harvest at current location while avoiding if (Sets.RaimentOfTheJadeHarvester.IsMaxBonusActive && MinimumSoulHarvestCriteria(Enemies.BestCluster)) { Skills.WitchDoctor.SoulHarvest.Cast(); } } // Incapacitated or Rooted if (!UseOOCBuff && (Player.IsIncapacitated || Player.IsRooted)) { // Spirit Walk if (CanCast(SNOPower.Witchdoctor_SpiritWalk)) { return(new TrinityPower(SNOPower.Witchdoctor_SpiritWalk)); } } // Combat Spells with a Target if (!UseOOCBuff && !IsCurrentlyAvoiding && CurrentTarget != null) { if (_bastianGeneratorWaitTimer.IsFinished && ShouldRefreshBastiansGeneratorBuff) { if (Hotbar.Contains(SNOPower.Witchdoctor_CorpseSpider)) { return(new TrinityPower(SNOPower.Witchdoctor_CorpseSpider, 50f, CurrentTarget.ACDGuid)); } if (Hotbar.Contains(SNOPower.Witchdoctor_PoisonDart)) { return(new TrinityPower(SNOPower.Witchdoctor_PoisonDart, 50f, CurrentTarget.ACDGuid)); } if (Hotbar.Contains(SNOPower.Witchdoctor_PlagueOfToads)) { return(new TrinityPower(SNOPower.Witchdoctor_PlagueOfToads, 50f, CurrentTarget.ACDGuid)); } if (Hotbar.Contains(SNOPower.Witchdoctor_Firebomb)) { return(new TrinityPower(SNOPower.Witchdoctor_Firebomb, 50f, CurrentTarget.ACDGuid)); } _bastianGeneratorWaitTimer.Reset(); } bool hasGraveInjustice = CacheData.Hotbar.PassiveSkills.Contains(SNOPower.Witchdoctor_Passive_GraveInjustice); // Debug.Print(CacheData.Hotbar.GetSkill(SNOPower.Witchdoctor_Hex).RuneIndex.ToString()); var isChicken = CacheData.Hotbar.ActivePowers.Contains(SNOPower.Witchdoctor_Hex_ChickenWalk); bool hasVisionQuest = CacheData.Hotbar.PassiveSkills.Any(s => s == SNOPower.Witchdoctor_Passive_VisionQuest); // Set max ranged attack range, based on Grave Injustice, and current target NOT standing in avoidance, and health > 25% float rangedAttackMaxRange = 30f; if (hasGraveInjustice && !CurrentTarget.IsStandingInAvoidance && Player.CurrentHealthPct > 0.25) { rangedAttackMaxRange = Math.Min(Player.GoldPickupRadius + 8f, 30f); } // Set basic attack range, depending on whether or not we have Bears and whether or not we are a tik tank float basicAttackRange = 35f; if (hasGraveInjustice) { basicAttackRange = rangedAttackMaxRange; } else if (Hotbar.Contains(SNOPower.Witchdoctor_ZombieCharger) && Player.PrimaryResource >= 150) { basicAttackRange = 30f; } else if (Legendary.TiklandianVisage.IsEquipped && !TikHorrifyCriteria(Enemies.BestLargeCluster)) { basicAttackRange = 25f; } else if (Legendary.TiklandianVisage.IsEquipped) { basicAttackRange = 1f; } // Summon Pets ----------------------------------------------------------------------- // Hex with angry chicken, is chicken, explode! if (isChicken && (TargetUtil.AnyMobsInRange(12f, 1, false) || CurrentTarget.RadiusDistance <= 10f || UseDestructiblePower)) // && CanCast(SNOPower.Witchdoctor_Hex_Explode) { //Debug.Print("Attempting to cast HEx Explosion {0}", ZetaDia.Me.UsePower(SNOPower.Witchdoctor_Hex_Explode, ZetaDia.Me.Position, // ZetaDia.CurrentWorldDynamicId)); return(new TrinityPower(SNOPower.Witchdoctor_Hex_Explode)); } bool hasJaunt = CacheData.Hotbar.ActiveSkills.Any(s => s.Power == SNOPower.Witchdoctor_SpiritWalk && s.RuneIndex == 1); bool hasHonoredGuest = CacheData.Hotbar.ActiveSkills.Any(s => s.Power == SNOPower.Witchdoctor_SpiritWalk && s.RuneIndex == 3); bool hasUmbralShock = CacheData.Hotbar.ActiveSkills.Any(s => s.Power == SNOPower.Witchdoctor_SpiritWalk && s.RuneIndex == 2); bool hasSeverance = CacheData.Hotbar.ActiveSkills.Any(s => s.Power == SNOPower.Witchdoctor_SpiritWalk && s.RuneIndex == 0); bool hasHealingJourney = CacheData.Hotbar.ActiveSkills.Any(s => s.Power == SNOPower.Witchdoctor_SpiritWalk && s.RuneIndex == 4); // Spirit Walk for Goblins chasing if (CanCast(SNOPower.Witchdoctor_SpiritWalk) && CurrentTarget.IsTreasureGoblin && CurrentTarget.HitPointsPct < 0.90 && CurrentTarget.RadiusDistance <= 40f) { return(new TrinityPower(SNOPower.Witchdoctor_SpiritWalk)); } // Spirit Walk < 65% Health: Healing Journey if (CanCast(SNOPower.Witchdoctor_SpiritWalk) && hasHealingJourney && Player.CurrentHealthPct <= V.F("WitchDoctor.SpiritWalk.HealingJourneyHealth")) { return(new TrinityPower(SNOPower.Witchdoctor_SpiritWalk)); } // Spirit Walk < 50% Mana: Honored Guest if (CanCast(SNOPower.Witchdoctor_SpiritWalk) && hasHonoredGuest && Player.PrimaryResourcePct <= V.F("WitchDoctor.SpiritWalk.HonoredGuestMana")) { return(new TrinityPower(SNOPower.Witchdoctor_SpiritWalk)); } //bool shouldRefreshVisionQuest = GetTimeSinceLastVisionQuestRefresh() > 4000; bool shouldRefreshVisionQuest = !GetHasBuff(SNOPower.Witchdoctor_Passive_VisionQuest) || GetTimeSinceLastVisionQuestRefresh() > 3800; // Vision Quest Passive if (hasVisionQuest && shouldRefreshVisionQuest) { // Poison Darts if (CanCast(SNOPower.Witchdoctor_PoisonDart)) { VisionQuestRefreshTimer.Restart(); return(new TrinityPower(SNOPower.Witchdoctor_PoisonDart, basicAttackRange, CurrentTarget.ACDGuid)); } // Corpse Spiders if (CanCast(SNOPower.Witchdoctor_CorpseSpider)) { VisionQuestRefreshTimer.Restart(); return(new TrinityPower(SNOPower.Witchdoctor_CorpseSpider, basicAttackRange, CurrentTarget.ACDGuid)); } // Plague Of Toads if (CanCast(SNOPower.Witchdoctor_PlagueOfToads)) { VisionQuestRefreshTimer.Restart(); return(new TrinityPower(SNOPower.Witchdoctor_PlagueOfToads, basicAttackRange, CurrentTarget.ACDGuid)); } // Fire Bomb if (CanCast(SNOPower.Witchdoctor_Firebomb)) { VisionQuestRefreshTimer.Restart(); return(new TrinityPower(SNOPower.Witchdoctor_Firebomb, basicAttackRange, CurrentTarget.ACDGuid));; } } bool hasVengefulSpirit = CacheData.Hotbar.ActiveSkills.Any(s => s.Power == SNOPower.Witchdoctor_SoulHarvest && s.RuneIndex == 4); bool hasSwallowYourSoul = CacheData.Hotbar.ActiveSkills.Any(s => s.Power == SNOPower.Witchdoctor_SoulHarvest && s.RuneIndex == 3); // START Jade Harvester ----------------------------------------------------------------------- if (Sets.RaimentOfTheJadeHarvester.IsMaxBonusActive) { //LogTargetArea("BestLargeCluster", Enemies.BestLargeCluster); //LogTargetArea("BestCluster", Enemies.BestCluster); //LogTargetArea("Nearby", Enemies.Nearby); //LogTargetArea("CloseNearby", Enemies.CloseNearby); // Piranhas if (CanCast(SNOPower.Witchdoctor_Piranhas) && Player.PrimaryResource >= 250 && (TargetUtil.ClusterExists(15f, 45f) || TargetUtil.AnyElitesInRange(45f)) && LastPowerUsed != SNOPower.Witchdoctor_Piranhas && Player.PrimaryResource >= 250) { return(new TrinityPower(SNOPower.Witchdoctor_Piranhas, 25f, Enemies.BestCluster.Position)); } // Should we move to cluster for harvest if (IdealSoulHarvestCriteria(Enemies.BestLargeCluster)) { //LogTargetArea("--- Found a good harvest location...", Enemies.BestLargeCluster); MoveToSoulHarvestPoint(Enemies.BestLargeCluster); } // Is there a slightly better position than right here if (MinimumSoulHarvestCriteria(Enemies.BestCluster) && (Enemies.BestCluster.EliteCount >= 2 || Enemies.BestCluster.UnitCount > 4)) { //LogTargetArea("--- Found an average harvest location...", Enemies.BestCluster); MoveToSoulHarvestPoint(Enemies.BestCluster); } // Should we harvest right here? if (MinimumSoulHarvestCriteria(Enemies.CloseNearby)) { //LogTargetArea("--- Harvesting (CurrentPosition)", Enemies.CloseNearby); return(new TrinityPower(SNOPower.Witchdoctor_SoulHarvest)); } // Locust Swarm if (CanCast(SNOPower.Witchdoctor_Locust_Swarm) && Player.PrimaryResource >= 300 && !CurrentTarget.HasDebuff(SNOPower.Witchdoctor_Locust_Swarm)) { return(new TrinityPower(SNOPower.Witchdoctor_Locust_Swarm, 20f, CurrentTarget.ACDGuid)); } // Haunt if (Skills.WitchDoctor.Haunt.CanCast() && Player.PrimaryResource >= 50 && !CurrentTarget.HasDebuff(SNOPower.Witchdoctor_Haunt)) { return(new TrinityPower(SNOPower.Witchdoctor_Haunt, 45f, CurrentTarget.ACDGuid)); } // Acid Cloud if (Skills.WitchDoctor.AcidCloud.CanCast() && Player.PrimaryResource >= 325 && LastPowerUsed != SNOPower.Witchdoctor_AcidCloud) { Vector3 bestClusterPoint; if (Passives.WitchDoctor.GraveInjustice.IsActive) { bestClusterPoint = TargetUtil.GetBestClusterPoint(15f, Math.Min(Player.GoldPickupRadius + 8f, 30f)); } else { bestClusterPoint = TargetUtil.GetBestClusterPoint(15f, 30f); } return(new TrinityPower(SNOPower.Witchdoctor_AcidCloud, rangedAttackMaxRange, bestClusterPoint)); } // Spread the love around if (!CurrentTarget.IsTreasureGoblin && CurrentTarget.HasDebuff(SNOPower.Witchdoctor_Locust_Swarm) && CurrentTarget.HasDebuff(SNOPower.Witchdoctor_Haunt) && Enemies.Nearby.UnitCount > 3 && Enemies.Nearby.DebuffedPercent(HarvesterCoreDebuffs) < 0.5) { //var oldTarget = Trinity.CurrentTarget; Trinity.Blacklist3Seconds.Add(CurrentTarget.RActorGuid); Trinity.CurrentTarget = Enemies.BestCluster.GetTargetWithoutDebuffs(HarvesterCoreDebuffs); //Logger.LogNormal("{0} {1} is fully debuffed, switched to {2} {3}", oldTarget.InternalName, oldTarget.ACDGuid, CurrentTarget.InternalName, CurrentTarget.ACDGuid); } // Save mana for locust swarm || piranhas if (!CurrentTarget.HasDebuff(SNOPower.Witchdoctor_Locust_Swarm) && Player.PrimaryResource < 300) { //Logger.LogNormal("Saving mana"); return(DefaultPower); } } // END Jade Harvester ----------------------------------------------------------------------- // Tiklandian Visage ---------------------------------------------------------------------- // Constantly casts Horrify and moves the middle of clusters if (Legendary.TiklandianVisage.IsEquipped) { // Piranhas if (CanCast(SNOPower.Witchdoctor_Piranhas) && Player.PrimaryResource >= 250 && (TargetUtil.ClusterExists(15f, 45f) || TargetUtil.AnyElitesInRange(45f)) && LastPowerUsed != SNOPower.Witchdoctor_Piranhas && Player.PrimaryResource >= 250) { return(new TrinityPower(SNOPower.Witchdoctor_Piranhas, 25f, Enemies.BestCluster.Position)); } //Cast Horrify before we go into the fray if (CanCast(SNOPower.Witchdoctor_Horrify)) { return(new TrinityPower(SNOPower.Witchdoctor_Horrify)); } // Should we move to a better position to fear people if (TikHorrifyCriteria(Enemies.BestLargeCluster)) { MoveToHorrifyPoint(Enemies.BestLargeCluster); } } // END Tiklandian Visage ---------------------------------------------------------------------- // Sacrifice if (CanCast(SNOPower.Witchdoctor_Sacrifice) && Trinity.PlayerOwnedZombieDogCount > 0 && (TargetUtil.AnyElitesInRange(15, 1) || (CurrentTarget.IsBossOrEliteRareUnique && CurrentTarget.RadiusDistance <= 9f))) { return(new TrinityPower(SNOPower.Witchdoctor_Sacrifice)); } // Sacrifice for Circle of Life bool hasCircleofLife = CacheData.Hotbar.PassiveSkills.Any(s => s == SNOPower.Witchdoctor_Passive_CircleOfLife); if (CanCast(SNOPower.Witchdoctor_Sacrifice) && Trinity.PlayerOwnedZombieDogCount > 0 && hasCircleofLife && TargetUtil.AnyMobsInRange(15f)) { return(new TrinityPower(SNOPower.Witchdoctor_Sacrifice)); } // Wall of Zombies if (CanCast(SNOPower.Witchdoctor_WallOfZombies) && (TargetUtil.AnyElitesInRange(15, 1) || TargetUtil.AnyMobsInRange(15, 1) || ((CurrentTarget.IsEliteRareUnique || CurrentTarget.IsTreasureGoblin || CurrentTarget.IsBoss) && CurrentTarget.RadiusDistance <= 25f))) { return(new TrinityPower(SNOPower.Witchdoctor_WallOfZombies, 25f, CurrentTarget.Position)); } bool hasRestlessGiant = CacheData.Hotbar.ActiveSkills.Any(s => s.Power == SNOPower.Witchdoctor_Gargantuan && s.RuneIndex == 0); bool hasWrathfulProtector = CacheData.Hotbar.ActiveSkills.Any(s => s.Power == SNOPower.Witchdoctor_Gargantuan && s.RuneIndex == 3); if (CanCast(SNOPower.Witchdoctor_Gargantuan)) { // Gargantuan, Recast on Elites or Bosses to trigger Restless Giant if (hasRestlessGiant && (TargetUtil.IsEliteTargetInRange(30f) || Trinity.PlayerOwnedGargantuanCount == 0)) { return(new TrinityPower(SNOPower.Witchdoctor_Gargantuan)); } // Gargantuan Wrathful Protector, 15 seconds of smash, use sparingly! if (hasWrathfulProtector && TargetUtil.IsEliteTargetInRange(30f)) { return(new TrinityPower(SNOPower.Witchdoctor_Gargantuan)); } // Gargantuan regular if (!hasRestlessGiant && !hasWrathfulProtector && Trinity.PlayerOwnedGargantuanCount == 0) { return(new TrinityPower(SNOPower.Witchdoctor_Gargantuan)); } } bool hasSacrifice = Hotbar.Contains(SNOPower.Witchdoctor_Sacrifice); // Zombie Dogs for Sacrifice if (hasSacrifice && CanCast(SNOPower.Witchdoctor_SummonZombieDog) && (LastPowerUsed == SNOPower.Witchdoctor_Sacrifice || Trinity.PlayerOwnedZombieDogCount <= 2) && LastPowerUsed != SNOPower.Witchdoctor_SummonZombieDog) { return(new TrinityPower(SNOPower.Witchdoctor_SummonZombieDog)); } // Hex with angry chicken, check if we want to shape shift and explode if (CanCast(SNOPower.Witchdoctor_Hex) && (TargetUtil.AnyMobsInRange(12f, 1, false) || CurrentTarget.RadiusDistance <= 10f) && hasAngryChicken) { return(new TrinityPower(SNOPower.Witchdoctor_Hex)); } // Hex Spam Cast without angry chicken if (CanCast(SNOPower.Witchdoctor_Hex) && !hasAngryChicken && (TargetUtil.AnyElitesInRange(12) || TargetUtil.AnyMobsInRange(12, 2) || TargetUtil.IsEliteTargetInRange(18f))) { return(new TrinityPower(SNOPower.Witchdoctor_Hex)); } if (CanCast(SNOPower.Witchdoctor_SoulHarvest) && (TargetUtil.AnyElitesInRange(16) || TargetUtil.AnyMobsInRange(16, 2) || TargetUtil.IsEliteTargetInRange(16f))) { return(new TrinityPower(SNOPower.Witchdoctor_SoulHarvest)); } // Mass Confuse, elites only or big mobs or to escape on low health if (CanCast(SNOPower.Witchdoctor_MassConfusion) && (TargetUtil.AnyElitesInRange(12, 1) || TargetUtil.AnyMobsInRange(12, 6) || Player.CurrentHealthPct <= 0.25 || (CurrentTarget.IsBossOrEliteRareUnique && CurrentTarget.RadiusDistance <= 12f)) && !CurrentTarget.IsTreasureGoblin) { return(new TrinityPower(SNOPower.Witchdoctor_MassConfusion, 0f, CurrentTarget.ACDGuid)); } if (!Settings.Combat.WitchDoctor.UseBigBadVoodooOffCooldown) { // Big Bad Voodoo, elites and bosses only if (CanCast(SNOPower.Witchdoctor_BigBadVoodoo) && (TargetUtil.EliteOrTrashInRange(25f) || (CurrentTarget.IsBoss && CurrentTarget.Distance <= 30f))) { return(new TrinityPower(SNOPower.Witchdoctor_BigBadVoodoo)); } } else { // Big Bad Voodo, cast whenever available if (!UseOOCBuff && !Player.IsIncapacitated && CanCast(SNOPower.Witchdoctor_BigBadVoodoo)) { return(new TrinityPower(SNOPower.Witchdoctor_BigBadVoodoo)); } } // Grasp of the Dead if (CanCast(SNOPower.Witchdoctor_GraspOfTheDead) && (TargetUtil.AnyMobsInRange(30, 2) || TargetUtil.EliteOrTrashInRange(30f)) && Player.PrimaryResource >= 150) { var bestClusterPoint = TargetUtil.GetBestClusterPoint(); return(new TrinityPower(SNOPower.Witchdoctor_GraspOfTheDead, 25f, bestClusterPoint)); } // Piranhas if (CanCast(SNOPower.Witchdoctor_Piranhas) && Player.PrimaryResource >= 250 && (TargetUtil.ClusterExists(15f, 45f) || TargetUtil.AnyElitesInRange(45f)) && Player.PrimaryResource >= 250) { var bestClusterPoint = TargetUtil.GetBestClusterPoint(); return(new TrinityPower(SNOPower.Witchdoctor_Piranhas, 25f, bestClusterPoint)); } bool hasPhobia = CacheData.Hotbar.ActiveSkills.Any(s => s.Power == SNOPower.Witchdoctor_Horrify && s.RuneIndex == 2); bool hasStalker = CacheData.Hotbar.ActiveSkills.Any(s => s.Power == SNOPower.Witchdoctor_Horrify && s.RuneIndex == 4); bool hasFaceOfDeath = CacheData.Hotbar.ActiveSkills.Any(s => s.Power == SNOPower.Witchdoctor_Horrify && s.RuneIndex == 1); bool hasFrighteningAspect = CacheData.Hotbar.ActiveSkills.Any(s => s.Power == SNOPower.Witchdoctor_Horrify && s.RuneIndex == 0); bool hasRuthlessTerror = CacheData.Hotbar.ActiveSkills.Any(s => s.Power == SNOPower.Witchdoctor_Horrify && s.RuneIndex == 3); float horrifyRadius = hasFaceOfDeath ? 24f : 12f; // Horrify when low on health if (CanCast(SNOPower.Witchdoctor_Horrify) && Player.CurrentHealthPct <= EmergencyHealthPotionLimit && TargetUtil.AnyMobsInRange(horrifyRadius, 3)) { return(new TrinityPower(SNOPower.Witchdoctor_Horrify)); } // Horrify Buff at 35% health -- Freightening Aspect if (CanCast(SNOPower.Witchdoctor_Horrify) && Player.CurrentHealthPct <= 0.35 && hasFrighteningAspect) { return(new TrinityPower(SNOPower.Witchdoctor_Horrify)); } // Spam Horrify if (CanCast(SNOPower.Witchdoctor_Horrify) && Settings.Combat.WitchDoctor.SpamHorrify) { return(new TrinityPower(SNOPower.Witchdoctor_Horrify)); } // Fetish Army, elites only if (CanCast(SNOPower.Witchdoctor_FetishArmy) && (TargetUtil.EliteOrTrashInRange(30f) || TargetUtil.IsEliteTargetInRange(30f) || Settings.Combat.WitchDoctor.UseFetishArmyOffCooldown)) { return(new TrinityPower(SNOPower.Witchdoctor_FetishArmy)); } bool hasManitou = Runes.WitchDoctor.Manitou.IsActive; // Spirit Barrage Manitou if (CanCast(SNOPower.Witchdoctor_SpiritBarrage) && Player.PrimaryResource >= 100 && TimeSincePowerUse(SNOPower.Witchdoctor_SpiritBarrage) > 18000 && hasManitou) { return(new TrinityPower(SNOPower.Witchdoctor_SpiritBarrage)); } bool hasResentfulSpirit = Runes.WitchDoctor.ResentfulSpirits.IsActive; // Haunt if (CanCast(SNOPower.Witchdoctor_Haunt) && Player.PrimaryResource >= 50 && !SpellTracker.IsUnitTracked(CurrentTarget, SNOPower.Witchdoctor_Haunt) && LastPowerUsed != SNOPower.Witchdoctor_Haunt) { return(new TrinityPower(SNOPower.Witchdoctor_Haunt, 21f, CurrentTarget.ACDGuid)); } //skillDict.Add("LocustSwarm", SNOPower.Witchdoctor_Locust_Swarm); // Locust Swarm if (CanCast(SNOPower.Witchdoctor_Locust_Swarm) && Player.PrimaryResource >= 300 && !SpellTracker.IsUnitTracked(CurrentTarget, SNOPower.Witchdoctor_Locust_Swarm) && LastPowerUsed != SNOPower.Witchdoctor_Locust_Swarm) { return(new TrinityPower(SNOPower.Witchdoctor_Locust_Swarm, 12f, CurrentTarget.ACDGuid)); } // Sacrifice for 0 Dogs if (CanCast(SNOPower.Witchdoctor_Sacrifice) && (Settings.Combat.WitchDoctor.ZeroDogs || !WitchDoctorHasPrimaryAttack)) { return(new TrinityPower(SNOPower.Witchdoctor_Sacrifice, 9f)); } var zombieChargerRange = hasGraveInjustice ? Math.Min(Player.GoldPickupRadius + 8f, 11f) : 11f; // Zombie Charger aka Zombie bears Spams Bears @ Everything from 11feet away if (CanCast(SNOPower.Witchdoctor_ZombieCharger) && Player.PrimaryResource >= 150) { return(new TrinityPower(SNOPower.Witchdoctor_ZombieCharger, zombieChargerRange, CurrentTarget.Position)); } // Soul Harvest Any Elites or to increase buff stacks if (!Sets.RaimentOfTheJadeHarvester.IsMaxBonusActive && CanCast(SNOPower.Witchdoctor_SoulHarvest) && (TargetUtil.AnyMobsInRange(16f, GetBuffStacks(SNOPower.Witchdoctor_SoulHarvest) + 1, false) || (hasSwallowYourSoul && Player.PrimaryResourcePct <= 0.50) || TargetUtil.IsEliteTargetInRange(16f))) { return(new TrinityPower(SNOPower.Witchdoctor_SoulHarvest)); } // Soul Harvest with VengefulSpirit if (!Sets.RaimentOfTheJadeHarvester.IsMaxBonusActive && CanCast(SNOPower.Witchdoctor_SoulHarvest) && hasVengefulSpirit && TargetUtil.AnyMobsInRange(16, 3)) { return(new TrinityPower(SNOPower.Witchdoctor_SoulHarvest)); } bool hasDireBats = CacheData.Hotbar.ActiveSkills.Any(s => s.Power == SNOPower.Witchdoctor_Firebats && s.RuneIndex == 0); bool hasVampireBats = CacheData.Hotbar.ActiveSkills.Any(s => s.Power == SNOPower.Witchdoctor_Firebats && s.RuneIndex == 3); bool hasPlagueBats = CacheData.Hotbar.ActiveSkills.Any(s => s.Power == SNOPower.Witchdoctor_Firebats && s.RuneIndex == 2); bool hasHungryBats = CacheData.Hotbar.ActiveSkills.Any(s => s.Power == SNOPower.Witchdoctor_Firebats && s.RuneIndex == 1); bool hasCloudOfBats = CacheData.Hotbar.ActiveSkills.Any(s => s.Power == SNOPower.Witchdoctor_Firebats && s.RuneIndex == 4); int fireBatsChannelCost = hasVampireBats ? 0 : 75; int fireBatsMana = TimeSincePowerUse(SNOPower.Witchdoctor_Firebats) < 125 ? fireBatsChannelCost : 225; bool firebatsMaintain = Trinity.ObjectCache.Any(u => u.IsUnit && u.IsPlayerFacing(70f) && u.Weight > 0 && u.Distance <= V.F("WitchDoctor.Firebats.MaintainRange") && SpellHistory.TimeSinceUse(SNOPower.Witchdoctor_Firebats) <= TimeSpan.FromMilliseconds(250d)); // Fire Bats:Cloud of bats if (hasCloudOfBats && (TargetUtil.AnyMobsInRange(8f) || firebatsMaintain) && CanCast(SNOPower.Witchdoctor_Firebats) && Player.PrimaryResource >= fireBatsMana) { var range = Settings.Combat.WitchDoctor.FirebatsRange > 12f ? 12f : Settings.Combat.WitchDoctor.FirebatsRange; return(new TrinityPower(SNOPower.Witchdoctor_Firebats, range, CurrentTarget.ACDGuid)); } // Fire Bats fast-attack if (CanCast(SNOPower.Witchdoctor_Firebats) && Player.PrimaryResource >= fireBatsMana && (TargetUtil.AnyMobsInRange(Settings.Combat.WitchDoctor.FirebatsRange) || firebatsMaintain) && !hasCloudOfBats) { return(new TrinityPower(SNOPower.Witchdoctor_Firebats, Settings.Combat.WitchDoctor.FirebatsRange, CurrentTarget.Position)); } // Acid Cloud if (CanCast(SNOPower.Witchdoctor_AcidCloud) && Player.PrimaryResource >= 175) { Vector3 bestClusterPoint; if (hasGraveInjustice) { bestClusterPoint = TargetUtil.GetBestClusterPoint(15f, Math.Min(Player.GoldPickupRadius + 8f, 30f)); } else { bestClusterPoint = TargetUtil.GetBestClusterPoint(15f, 30f); } return(new TrinityPower(SNOPower.Witchdoctor_AcidCloud, rangedAttackMaxRange, bestClusterPoint)); } bool hasWellOfSouls = CacheData.Hotbar.ActiveSkills.Any(s => s.Power == SNOPower.Witchdoctor_SpiritBarrage && s.RuneIndex == 1); bool hasRushOfEssence = CacheData.Hotbar.PassiveSkills.Any(s => s == SNOPower.Witchdoctor_Passive_RushOfEssence); // Spirit Barrage + Rush of Essence if (CanCast(SNOPower.Witchdoctor_SpiritBarrage) && Player.PrimaryResource >= 100 && hasRushOfEssence && !hasManitou) { if (hasWellOfSouls) { return(new TrinityPower(SNOPower.Witchdoctor_SpiritBarrage, 21f, CurrentTarget.ACDGuid)); } return(new TrinityPower(SNOPower.Witchdoctor_SpiritBarrage, 21f, CurrentTarget.ACDGuid)); } // Zombie Charger backup if (CanCast(SNOPower.Witchdoctor_ZombieCharger) && Player.PrimaryResource >= 150) { return(new TrinityPower(SNOPower.Witchdoctor_ZombieCharger, zombieChargerRange, CurrentTarget.Position)); } // Regular spirit barage if (CanCast(SNOPower.Witchdoctor_SpiritBarrage) && Player.PrimaryResource >= 100 && !hasManitou) { return(new TrinityPower(SNOPower.Witchdoctor_SpiritBarrage, basicAttackRange, CurrentTarget.ACDGuid)); } // Poison Darts fast-attack Spams Darts when mana is too low (to cast bears) @12yds or @10yds if Bears avialable if (CanCast(SNOPower.Witchdoctor_PoisonDart)) { VisionQuestRefreshTimer.Restart(); return(new TrinityPower(SNOPower.Witchdoctor_PoisonDart, basicAttackRange, CurrentTarget.ACDGuid)); } // Corpse Spiders fast-attacks Spams Spiders when mana is too low (to cast bears) @12yds or @10yds if Bears avialable if (CanCast(SNOPower.Witchdoctor_CorpseSpider)) { VisionQuestRefreshTimer.Restart(); return(new TrinityPower(SNOPower.Witchdoctor_CorpseSpider, basicAttackRange, CurrentTarget.ACDGuid)); } // Toads fast-attacks Spams Toads when mana is too low (to cast bears) @12yds or @10yds if Bears avialable if (CanCast(SNOPower.Witchdoctor_PlagueOfToads)) { VisionQuestRefreshTimer.Restart(); return(new TrinityPower(SNOPower.Witchdoctor_PlagueOfToads, basicAttackRange, CurrentTarget.ACDGuid)); } // Fire Bomb fast-attacks Spams Bomb when mana is too low (to cast bears) @12yds or @10yds if Bears avialable if (CanCast(SNOPower.Witchdoctor_Firebomb)) { VisionQuestRefreshTimer.Restart(); return(new TrinityPower(SNOPower.Witchdoctor_Firebomb, basicAttackRange, CurrentTarget.ACDGuid)); } //Hexing Pants Mod if (Legendary.HexingPantsOfMrYan.IsEquipped && CurrentTarget.IsUnit && //!CanCast(SNOPower.Witchdoctor_Piranhas) && CurrentTarget.RadiusDistance > 10f) { return(new TrinityPower(SNOPower.Walk, 10f, CurrentTarget.Position)); } if (Legendary.HexingPantsOfMrYan.IsEquipped && CurrentTarget.IsUnit && //!CanCast(SNOPower.Witchdoctor_Piranhas) && CurrentTarget.RadiusDistance < 10f) { Vector3 vNewTarget = MathEx.CalculatePointFrom(CurrentTarget.Position, Player.Position, -10f); return(new TrinityPower(SNOPower.Walk, 10f, vNewTarget)); } } // Buffs if (UseOOCBuff) { // Spirit Walk OOC if (CanCast(SNOPower.Witchdoctor_SpiritWalk) && Settings.Combat.Misc.AllowOOCMovement) { return(new TrinityPower(SNOPower.Witchdoctor_SpiritWalk)); } //Spam fear at all times if Tiklandian Visage is ewquipped and fear spam is selected to keep fear buff active if (CanCast(SNOPower.Witchdoctor_Horrify) && Settings.Combat.WitchDoctor.SpamHorrify && Legendary.TiklandianVisage.IsEquipped) { return(new TrinityPower(SNOPower.Witchdoctor_Horrify)); } bool hasStalker = CacheData.Hotbar.ActiveSkills.Any(s => s.Power == SNOPower.Witchdoctor_Horrify && s.RuneIndex == 4); // Horrify Buff When not in combat for movement speed -- Stalker if (CanCast(SNOPower.Witchdoctor_Horrify) && hasStalker) { return(new TrinityPower(SNOPower.Witchdoctor_Horrify)); } // Zombie Dogs non-sacrifice build if (CanCast(SNOPower.Witchdoctor_SummonZombieDog) && ((Legendary.TheTallMansFinger.IsEquipped && Trinity.PlayerOwnedZombieDogCount < 1) || (!Legendary.TheTallMansFinger.IsEquipped && Trinity.PlayerOwnedZombieDogCount <= 2))) { return(new TrinityPower(SNOPower.Witchdoctor_SummonZombieDog)); } bool hasRestlessGiant = CacheData.Hotbar.ActiveSkills.Any(s => s.Power == SNOPower.Witchdoctor_Gargantuan && s.RuneIndex == 0); bool hasWrathfulProtector = CacheData.Hotbar.ActiveSkills.Any(s => s.Power == SNOPower.Witchdoctor_Gargantuan && s.RuneIndex == 3); if (CanCast(SNOPower.Witchdoctor_Gargantuan) && !hasRestlessGiant && !hasWrathfulProtector && Trinity.PlayerOwnedGargantuanCount == 0) { return(new TrinityPower(SNOPower.Witchdoctor_Gargantuan)); } } // Default Attacks if (IsNull(power)) { power = DefaultPower; } return(power); }
public TrinityPower GetOffensivePower() { // 锁定奥拉什 TrinityActor target; Vector3 position = Vector3.Zero; if (IsArchonActive) { target = TargetUtil.BestRangedAoeUnit(75, 75) ?? CurrentTarget; if (Skills.Wizard.ArchonTeleport.CanCast()) { if (!Core.Buffs.HasInvulnerableShrine && Skills.Wizard.Archon.TimeSinceUse > 19000) { Core.Avoidance.Avoider.TryGetSafeSpot(out position, 30, 50, Player.Position); } if (!Core.Buffs.HasInvulnerableShrine && Core.Avoidance.InAvoidance(Core.Player.Position)) { Core.Avoidance.Avoider.TryGetSafeSpot(out position, 25, 45, target.Position); if (position == Vector3.Zero) { Core.Avoidance.Avoider.TryGetSafeSpot(out position, 15, 40, Player.Position); } } if ((!Core.Buffs.HasInvulnerableShrine && (Player.CurrentHealthPct >= 1 && Player.ShieldHitpoints < Player.CurrentHealth) && Skills.Wizard.ArchonTeleport.TimeSinceUse > 3000) || (!Core.Buffs.HasInvulnerableShrine && Player.CurrentHealthPct < 1 && Skills.Wizard.ArchonTeleport.TimeSinceUse > 1500) || TargetUtil.AnyElitesInRange(10)) { Core.Avoidance.Avoider.TryGetSafeSpot(out position, 30, 45, target.Position); } if (target.IsBoss && target.Distance < 30) { Core.Avoidance.Avoider.TryGetSafeSpot(out position, 35, 50, target.Position); } if (position != Vector3.Zero) { return(Teleport(position)); } } if (Skills.Wizard.ArchonDisintegrationWave.CanCast()) { return(ArchonDisintegrationWave(target)); } return(Walk(Avoider.SafeSpot)); } target = TargetUtil.BestRangedAoeUnit(50) ?? CurrentTarget; if (Skills.Wizard.Archon.CanCast()) { if (!HasTalRashaStacks) { if (TalRashaStacks == 2 && Skills.Wizard.ArcaneTorrent.TimeSinceUse < 6000) { if (Skills.Wizard.Blizzard.TimeSinceUse < 6000) { return(BlackHole(target)); } if (Skills.Wizard.BlackHole.TimeSinceUse < 6000) { return(Blizzard(target)); } } } else { return(Archon()); } } var blizzard = Skills.Wizard.Blizzard; if (blizzard.CanCast() && blizzard.TimeSinceUse > 6000) { return(Blizzard(target)); } var blackhole = Skills.Wizard.BlackHole; if ((blackhole.CanCast() && blackhole.TimeSinceUse > 6000)) { return(BlackHole(target)); } if (TalRashaStacks >= 2) { return(ArcaneTorrent(target)); } return(Walk(Avoider.SafeSpot)); }
public TrinityPower GetOffensivePower() { Vector3 position; var allUnits = Core.Targets.ByType[TrinityObjectType.Unit].Where(u => u.IsUnit && u.RadiusDistance <= 50f).ToList(); var clusterUnits = (from u in allUnits where u.IsUnit && u.Weight > 0 && !u.IsPlayer orderby u.NearbyUnitsWithinDistance(15f) descending, u.Distance, u.HitPointsPct descending select u).ToList(); var bestClusterUnit = clusterUnits.FirstOrDefault(); //10 second 60% damage reduction should always be on to survive if (!HasJeramsRevengeBuff && Player.CurrentHealthPct > 0.4 && !Core.Avoidance.InCriticalAvoidance(Player.Position) && (ZetaDia.Me.IsInCombat || Player.CurrentHealthPct < 0.4) && bestClusterUnit != null && Skills.WitchDoctor.WallOfDeath.CanCast()) { return(WallOfDeath(allUnits.FirstOrDefault())); } if (bestClusterUnit != null) { if (Player.HasBuff(SNOPower.Witchdoctor_Hex)) { Vector3 explodePos = PlayerMover.IsBlocked ? Player.Position : bestClusterUnit.Position; return(ExplodeChicken(explodePos)); } if (!HasJeramsRevengeBuff && ZetaDia.Me.IsInCombat && Skills.WitchDoctor.WallOfDeath.CanCast()) { var target = allUnits.FirstOrDefault(); if (target != null) { return(WallOfDeath(target)); } } // Make sure we approach carefully when our defenses are down var harvestStacks = Skills.WitchDoctor.SoulHarvest.BuffStacks; var bestHarvestCluster = TargetUtil.GetBestClusterPoint(18f, 50f); if (Core.Rift.IsGreaterRift && harvestStacks < 10 && Legendary.LakumbasOrnament.IsEquipped) { if (Skills.WitchDoctor.SpiritWalk.CanCast() && TargetUtil.ClusterExists(18f, 50f, 2)) { return(SpiritWalk()); } if (Skills.WitchDoctor.SpiritWalk.IsBuffActive && bestHarvestCluster != null) { return(Walk(bestHarvestCluster)); } } // SpiritWalk for the invulnerability var shouldBecomeInvulnerable = Core.Avoidance.Avoider.ShouldAvoid || Core.Avoidance.Avoider.ShouldKite || (Player.CurrentHealthPct < 0.9f && Core.Rift.IsGreaterRift); if (Skills.WitchDoctor.SpiritWalk.CanCast() && Settings.SpiritWalk.UseMode == UseTime.Default && TargetUtil.AnyMobsInRange(20f)) { return(SpiritWalk()); } // Spam hex for the 50% damage reduction var closeUnit = HostileMonsters.FirstOrDefault(u => u.Distance < 40f); if (!Player.HasBuff(SNOPower.Witchdoctor_Hex) && Skills.WitchDoctor.Hex.CanCast() && closeUnit != null) { return(Hex(TargetUtil.GetBestClusterPoint(15f, 20f))); } var targetsWithoutLocust = clusterUnits.Where(u => !u.HasDebuff(SNOPower.Witchdoctor_Locust_Swarm)).OrderBy(u => u.Distance); var isAnyTargetWithLocust = clusterUnits.Any(u => u.HasDebuff(SNOPower.Witchdoctor_Locust_Swarm) && u.Distance < 45f); var percentTargetsWithHaunt = TargetUtil.DebuffedPercent(SNOPower.Witchdoctor_Haunt, 8f); var percentTargetsWithLocust = TargetUtil.DebuffedPercent(SNOPower.Witchdoctor_Locust_Swarm, 12f); var isEliteWithoutHaunt = clusterUnits.Any(u => u.IsElite && !u.HasDebuff(SNOPower.Witchdoctor_Haunt) && u.Distance <= 20f); var isElitewithoutLocust = clusterUnits.Any(u => u.IsElite && !u.HasDebuff(SNOPower.Witchdoctor_Locust_Swarm) && u.Distance <= 20f); var harvestBuffCooldown = Core.Cooldowns.GetBuffCooldown(SNOPower.Witchdoctor_SoulHarvest); var harvestPossibleStackGain = 10 - harvestStacks; var harvestUnitsInRange = allUnits.Count(u => u.Distance < 12f); var interruptForHarvest = Skills.WitchDoctor.SoulHarvest.CanCast() && harvestPossibleStackGain >= harvestUnitsInRange && harvestBuffCooldown?.Remaining.TotalMilliseconds < 500; var interruptForHaunt = percentTargetsWithHaunt < 0.2f || isEliteWithoutHaunt; var needToSwarmElite = isElitewithoutLocust && !((Legendary.VileHive.IsEquipped || Runes.WitchDoctor.Pestilence.IsActive) && isAnyTargetWithLocust); var interruptForLocust = (percentTargetsWithLocust < 0.1f || needToSwarmElite) && Player.PrimaryResource > 300 && Skills.WitchDoctor.LocustSwarm.CanCast(); var interruptForHex = Skills.WitchDoctor.Hex.CanCast(); var interruptForSpiritWalk = Skills.WitchDoctor.SpiritWalk.CanCast() && Settings.SpiritWalk.UseMode == UseTime.Default && shouldBecomeInvulnerable; // continue channelling firebats? if (Player.IsChannelling) { if (!interruptForHaunt && !interruptForLocust && !interruptForHarvest && !interruptForHex && !interruptForSpiritWalk) { return(new TrinityPower(SNOPower.Witchdoctor_Firebats, 30f, Player.Position, 75, 250)); } } // Emergency health situation if (Player.CurrentHealthPct < 0.35) { if (Skills.WitchDoctor.SpiritWalk.CanCast()) { return(SpiritWalk()); } if (TargetUtil.AnyMobsInRange(12f) && Skills.WitchDoctor.SoulHarvest.CanCast()) { return(SoulHarvest()); } if (!HasJeramsRevengeBuff && Skills.WitchDoctor.WallOfDeath.CanCast() && allUnits.Any()) { return(WallOfDeath(allUnits.FirstOrDefault())); } } // Soul harvest for the damage reduction of Okumbas Ornament if (Skills.WitchDoctor.SoulHarvest.CanCast() && (bestClusterUnit.Distance < 12f || harvestStacks < 4 && TargetUtil.AnyMobsInRange(10f)) && harvestStacks < 10) { if (harvestPossibleStackGain <= harvestUnitsInRange) { return(SoulHarvest()); } } // Locust if (Skills.WitchDoctor.LocustSwarm.CanCast() && Skills.WitchDoctor.LocustSwarm.TimeSinceUse > 1000 && targetsWithoutLocust.Any() && (!Runes.WitchDoctor.Pestilence.IsActive || !isAnyTargetWithLocust)) { if ((percentTargetsWithLocust < Settings.LocustPct || needToSwarmElite) && Player.PrimaryResource > 300 && targetsWithoutLocust.Any()) { return(new TrinityPower(SNOPower.Witchdoctor_Locust_Swarm, 10f, targetsWithoutLocust.First().AcdId, 0, 0)); } } if (ShouldBigBadVoodoo(out position)) { return(BigBadVoodoo(position)); } // Piranhas if (Skills.WitchDoctor.Piranhas.CanCast() && Player.PrimaryResource >= 250 && (TargetUtil.ClusterExists(15f, 40f) || TargetUtil.AnyElitesInRange(40f)) && Player.PrimaryResource >= 250) { return(Piranhas(TargetUtil.GetBestClusterUnit())); } // .80 of mobs give or take. Spelltracker check is to prevent repeat casts ont he same target before the projectile arrives. var targetsWithoutHaunt = clusterUnits.Where(u => !u.HasDebuff(SNOPower.Witchdoctor_Haunt) && !SpellTracker.IsUnitTracked(u, SNOPower.Witchdoctor_Haunt)).OrderBy(u => u.Distance); if ((percentTargetsWithHaunt < Settings.HauntPct || isEliteWithoutHaunt) && targetsWithoutHaunt.Any() && Player.PrimaryResource > 100) { var target = targetsWithoutHaunt.First(); return(Haunt(target)); } Vector3 bestBuffedPosition; TargetUtil.BestBuffPosition(16f, bestClusterUnit.Position, true, out bestBuffedPosition); var bestClusterUnitRadiusPosition = MathEx.GetPointAt(bestClusterUnit.Position, bestClusterUnit.CollisionRadius * 1.1f, bestClusterUnit.Rotation); var bestFirebatsPosition = bestBuffedPosition != Vector3.Zero ? bestBuffedPosition : bestClusterUnitRadiusPosition; var distance = bestFirebatsPosition.Distance2D(Player.Position); // Walk into cluster or buffed location. if (distance > 10f && distance < 35f && !PlayerMover.IsBlocked) { if (distance > 20f && Skills.WitchDoctor.SpiritWalk.CanCast()) { return(SpiritWalk()); } //Core.Logger.Warn($"Walking to cluster position. Dist: {bestFirebatsPosition.Distance(Player.Position)}"); return(new TrinityPower(SNOPower.Walk, 3f, bestFirebatsPosition, 0, 0)); } if (Skills.WitchDoctor.Firebats.CanCast()) { var closestUnit = allUnits.OrderBy(u => u.Distance).FirstOrDefault(); if (closestUnit != null) { return(Firebats(closestUnit)); } } } return(Walk(TargetUtil.GetLoiterPosition(CurrentTarget, 15f))); }
public void MoveTowards(Vector3 vMoveToTarget) { if (Trinity.Settings.Advanced.DisableAllMovement) { return; } if (!ZetaDia.IsInGame || !ZetaDia.Me.IsValid || ZetaDia.Me.IsDead || ZetaDia.IsLoadingWorld) { return; } if (UISafetyCheck()) { return; } TimeLastUsedPlayerMover = DateTime.UtcNow; LastMoveToTarget = vMoveToTarget; // Set the public variable vMoveToTarget = WarnAndLogLongPath(vMoveToTarget); // Store player current position // Store distance to current moveto target float destinationDistance = MyPosition.Distance2D(vMoveToTarget); // Do unstuckery things if (Trinity.Settings.Advanced.UnstuckerEnabled) { // See if we can reset the 10-limit unstuck counter, if >120 seconds since we last generated an unstuck location // this is used if we're NOT stuck... if (TotalAntiStuckAttempts > 1 && DateTime.UtcNow.Subtract(LastGeneratedStuckPosition).TotalSeconds >= 120) { TotalAntiStuckAttempts = 1; TimesReachedStuckPoint = 0; vSafeMovementLocation = Vector3.Zero; NavHelper.UsedStuckSpots = new List <GridPoint>(); Logger.Log(TrinityLogLevel.Info, LogCategory.Movement, "Resetting unstuck timers", true); } // See if we need to, and can, generate unstuck actions // check if we're stuck bool isStuck = UnstuckChecker(); if (isStuck) { // Record the time we last apparently couldn't move for a brief period of time LastRecordedAnyStuck = DateTime.UtcNow; // See if there's any stuck position to try and navigate to generated by random mover vSafeMovementLocation = UnstuckHandler(MyPosition, vMoveToTarget); if (vSafeMovementLocation == Vector3.Zero) { Logger.Log(TrinityLogLevel.Info, LogCategory.Movement, "Unable to find Unstuck point!", vSafeMovementLocation); return; } Logger.Log(TrinityLogLevel.Verbose, LogCategory.Movement, "SafeMovement Location set to {0}", vSafeMovementLocation); } // See if we can clear the total unstuckattempts if we haven't been stuck in over 6 minutes. if (DateTime.UtcNow.Subtract(LastRecordedAnyStuck).TotalSeconds >= 360) { TimesReachedMaxUnstucks = 0; } // Did we have a safe point already generated (eg from last loop through), if so use it as our current location instead if (vSafeMovementLocation != Vector3.Zero) { // Set our current movement target to the safe point we generated last cycle vMoveToTarget = vSafeMovementLocation; destinationDistance = MyPosition.Distance2D(vMoveToTarget); } // Get distance to current destination // Remove the stuck position if it's been reached, this bit of code also creates multiple stuck-patterns in an ever increasing amount if (vSafeMovementLocation != Vector3.Zero && destinationDistance <= 3f) { vSafeMovementLocation = Vector3.Zero; TimesReachedStuckPoint++; // Do we want to immediately generate a 2nd waypoint to "chain" anti-stucks in an ever-increasing path-length? if (TimesReachedStuckPoint <= TotalAntiStuckAttempts) { vSafeMovementLocation = NavHelper.FindSafeZone(true, TotalAntiStuckAttempts, MyPosition); vMoveToTarget = vSafeMovementLocation; } else { if (Trinity.Settings.Advanced.LogCategories.HasFlag(LogCategory.Movement)) { Logger.Log(TrinityLogLevel.Info, LogCategory.UserInformation, "Clearing old route and trying new path find to: " + LastMoveToTarget.ToString()); } // Reset the path and allow a whole "New" unstuck generation next cycle TimesReachedStuckPoint = 0; // And cancel unstucking for 9 seconds so DB can try to navigate CancelUnstuckerForSeconds = (9 * TotalAntiStuckAttempts); if (CancelUnstuckerForSeconds < 20) { CancelUnstuckerForSeconds = 20; } LastCancelledUnstucker = DateTime.UtcNow; Navigator.Clear(); Navigator.MoveTo(LastMoveToTarget, "original destination", false); return; } } } // don't use special movement within 10 seconds of being stuck bool cancelSpecialMovementAfterStuck = DateTime.UtcNow.Subtract(LastGeneratedStuckPosition).TotalMilliseconds > 10000; // See if we can use abilities like leap etc. for movement out of combat, but not in town if (Trinity.Settings.Combat.Misc.AllowOOCMovement && !Trinity.Player.IsInTown && !Trinity.DontMoveMeIAmDoingShit && cancelSpecialMovementAfterStuck) { bool bTooMuchZChange = (Math.Abs(MyPosition.Z - vMoveToTarget.Z) >= 4f); // Whirlwind for a barb, special context only if (Trinity.Settings.Combat.Barbarian.SprintMode != BarbarianSprintMode.CombatOnly && Trinity.Hotbar.Contains(SNOPower.Barbarian_Whirlwind) && Trinity.ObjectCache.Any(u => u.IsUnit && MathUtil.IntersectsPath(u.Position, u.Radius + 5f, Trinity.Player.Position, vMoveToTarget)) && Trinity.Player.PrimaryResource >= V.F("Barbarian.Whirlwind.MinFury") && !Trinity.IsWaitingForSpecial && V.B("Barbarian.Whirlwind.UseForMovement")) { ZetaDia.Me.UsePower(SNOPower.Barbarian_Whirlwind, vMoveToTarget, Trinity.CurrentWorldDynamicId, -1); SpellHistory.RecordSpell(SNOPower.Barbarian_Whirlwind); if (Trinity.Settings.Advanced.LogCategories.HasFlag(LogCategory.Movement)) { Logger.Log(TrinityLogLevel.Debug, LogCategory.Movement, "Using Whirlwind for OOC movement, distance={0}", destinationDistance); } return; } // Leap movement for a barb if (Trinity.Settings.Combat.Barbarian.UseLeapOOC && Trinity.Hotbar.Contains(SNOPower.Barbarian_Leap) && PowerManager.CanCast(SNOPower.Barbarian_Leap) && !ShrinesInArea(vMoveToTarget)) { Vector3 vThisTarget = vMoveToTarget; if (destinationDistance > 35f) { vThisTarget = MathEx.CalculatePointFrom(vMoveToTarget, MyPosition, 35f); } ZetaDia.Me.UsePower(SNOPower.Barbarian_Leap, vThisTarget, Trinity.CurrentWorldDynamicId, -1); SpellHistory.RecordSpell(SNOPower.Barbarian_Leap); if (Trinity.Settings.Advanced.LogCategories.HasFlag(LogCategory.Movement)) { Logger.Log(TrinityLogLevel.Debug, LogCategory.Movement, "Using Leap for OOC movement, distance={0}", destinationDistance); } return; } // Furious Charge movement for a barb if (Trinity.Settings.Combat.Barbarian.UseChargeOOC && Trinity.Hotbar.Contains(SNOPower.Barbarian_FuriousCharge) && !bTooMuchZChange && destinationDistance >= 20f && PowerManager.CanCast(SNOPower.Barbarian_FuriousCharge) && !ShrinesInArea(vMoveToTarget)) { Vector3 vThisTarget = vMoveToTarget; if (destinationDistance > 35f) { vThisTarget = MathEx.CalculatePointFrom(vMoveToTarget, MyPosition, 35f); } ZetaDia.Me.UsePower(SNOPower.Barbarian_FuriousCharge, vThisTarget, Trinity.CurrentWorldDynamicId, -1); SpellHistory.RecordSpell(SNOPower.Barbarian_FuriousCharge); if (Trinity.Settings.Advanced.LogCategories.HasFlag(LogCategory.Movement)) { Logger.Log(TrinityLogLevel.Debug, LogCategory.Movement, "Using Furious Charge for OOC movement, distance={0}", destinationDistance); } return; } bool hasTacticalAdvantage = HotbarSkills.PassiveSkills.Any(s => s == SNOPower.DemonHunter_Passive_TacticalAdvantage); int vaultDelay = hasTacticalAdvantage ? 2000 : Trinity.Settings.Combat.DemonHunter.VaultMovementDelay; // DemonHunter Vault if (Trinity.Hotbar.Contains(SNOPower.DemonHunter_Vault) && !bTooMuchZChange && Trinity.Settings.Combat.DemonHunter.VaultMode != DemonHunterVaultMode.CombatOnly && CombatBase.TimeSincePowerUse(SNOPower.DemonHunter_Vault) > vaultDelay && destinationDistance >= 18f && PowerManager.CanCast(SNOPower.DemonHunter_Vault) && !ShrinesInArea(vMoveToTarget) && // Don't Vault into avoidance/monsters if we're kiting (CombatBase.PlayerKiteDistance <= 0 || (CombatBase.PlayerKiteDistance > 0 && (!CacheData.TimeBoundAvoidance.Any(a => a.Position.Distance(vMoveToTarget) <= CombatBase.PlayerKiteDistance) || (!CacheData.TimeBoundAvoidance.Any(a => MathEx.IntersectsPath(a.Position, a.Radius, Trinity.Player.Position, vMoveToTarget))) || !CacheData.MonsterObstacles.Any(a => a.Position.Distance(vMoveToTarget) <= CombatBase.PlayerKiteDistance)))) ) { Vector3 vThisTarget = vMoveToTarget; if (destinationDistance > 35f) { vThisTarget = MathEx.CalculatePointFrom(vMoveToTarget, MyPosition, 35f); } ZetaDia.Me.UsePower(SNOPower.DemonHunter_Vault, vThisTarget, Trinity.CurrentWorldDynamicId, -1); SpellHistory.RecordSpell(SNOPower.DemonHunter_Vault); if (Trinity.Settings.Advanced.LogCategories.HasFlag(LogCategory.Movement)) { Logger.Log(TrinityLogLevel.Debug, LogCategory.Movement, "Using Vault for OOC movement, distance={0}", destinationDistance); } return; } // Tempest rush for a monk if (Trinity.Hotbar.Contains(SNOPower.Monk_TempestRush) && (Trinity.Settings.Combat.Monk.TROption == TempestRushOption.MovementOnly || Trinity.Settings.Combat.Monk.TROption == TempestRushOption.Always || (Trinity.Settings.Combat.Monk.TROption == TempestRushOption.TrashOnly && !TargetUtil.AnyElitesInRange(40f)))) { Vector3 vTargetAimPoint = vMoveToTarget; bool canRayCastTarget = true; vTargetAimPoint = TargetUtil.FindTempestRushTarget(); if (!CanChannelTempestRush && ((Trinity.Player.PrimaryResource >= Trinity.Settings.Combat.Monk.TR_MinSpirit && destinationDistance >= Trinity.Settings.Combat.Monk.TR_MinDist) || DateTime.UtcNow.Subtract(CacheData.AbilityLastUsed[SNOPower.Monk_TempestRush]).TotalMilliseconds <= 150) && canRayCastTarget && PowerManager.CanCast(SNOPower.Monk_TempestRush)) { CanChannelTempestRush = true; } else if ((CanChannelTempestRush && (Trinity.Player.PrimaryResource < 10f)) || !canRayCastTarget) { CanChannelTempestRush = false; } double lastUse = DateTime.UtcNow.Subtract(CacheData.AbilityLastUsed[SNOPower.Monk_TempestRush]).TotalMilliseconds; if (CanChannelTempestRush) { if (Trinity.SNOPowerUseTimer(SNOPower.Monk_TempestRush)) { LastTempestRushPosition = vTargetAimPoint; ZetaDia.Me.UsePower(SNOPower.Monk_TempestRush, vTargetAimPoint, Trinity.CurrentWorldDynamicId, -1); SpellHistory.RecordSpell(SNOPower.Monk_TempestRush); // simulate movement speed of 30 SpeedSensor lastSensor = SpeedSensors.OrderByDescending(s => s.Timestamp).FirstOrDefault(); SpeedSensors.Add(new SpeedSensor() { Location = MyPosition, TimeSinceLastMove = new TimeSpan(0, 0, 0, 0, 1000), Distance = 5f, WorldID = Trinity.CurrentWorldDynamicId }); if (Trinity.Settings.Advanced.LogCategories.HasFlag(LogCategory.Movement)) { Logger.Log(TrinityLogLevel.Debug, LogCategory.Movement, "Using Tempest Rush for OOC movement, distance={0:0} spirit={1:0} cd={2} lastUse={3:0} V3={4} vAim={5}", destinationDistance, Trinity.Player.PrimaryResource, PowerManager.CanCast(SNOPower.Monk_TempestRush), lastUse, vMoveToTarget, vTargetAimPoint); } return; } else { return; } } else { if (Trinity.Settings.Advanced.LogCategories.HasFlag(LogCategory.Movement)) { Logger.Log(TrinityLogLevel.Debug, LogCategory.Movement, "Tempest rush failed!: {0:00.0} / {1} distance: {2:00.0} / {3} Raycast: {4} MS: {5:0.0} lastUse={6:0}", Trinity.Player.PrimaryResource, Trinity.Settings.Combat.Monk.TR_MinSpirit, destinationDistance, Trinity.Settings.Combat.Monk.TR_MinDist, canRayCastTarget, GetMovementSpeed(), lastUse); } Trinity.MaintainTempestRush = false; } // Always set this from PlayerMover Trinity.LastTempestRushLocation = vTargetAimPoint; } // Dashing Strike OOC if (CombatBase.CanCast(SNOPower.X1_Monk_DashingStrike) && Trinity.Settings.Combat.Monk.UseDashingStrikeOOC && destinationDistance > 15f) { ZetaDia.Me.UsePower(SNOPower.X1_Monk_DashingStrike, vMoveToTarget, Trinity.CurrentWorldDynamicId, -1); SpellHistory.RecordSpell(SNOPower.X1_Monk_DashingStrike); if (Trinity.Settings.Advanced.LogCategories.HasFlag(LogCategory.Movement)) { Logger.Log(TrinityLogLevel.Debug, LogCategory.Movement, "Using Dashing Strike for OOC movement, distance={0}", destinationDistance); } } bool hasWormHole = HotbarSkills.AssignedSkills.Any(s => s.Power == SNOPower.Wizard_Teleport && s.RuneIndex == 4); bool hasCalamity = HotbarSkills.AssignedSkills.Any(s => s.Power == SNOPower.Wizard_Teleport && s.RuneIndex == 0); // Teleport for a wizard if (!hasCalamity && CombatBase.CanCast(SNOPower.Wizard_Teleport, CombatBase.CanCastFlags.NoTimer) && CombatBase.TimeSincePowerUse(SNOPower.Wizard_Teleport) > 250 && destinationDistance >= 10f && !ShrinesInArea(vMoveToTarget)) { var maxTeleportRange = 75f; Vector3 vThisTarget = vMoveToTarget; if (destinationDistance > maxTeleportRange) { vThisTarget = MathEx.CalculatePointFrom(vMoveToTarget, MyPosition, maxTeleportRange); } ZetaDia.Me.UsePower(SNOPower.Wizard_Teleport, vThisTarget, Trinity.CurrentWorldDynamicId, -1); SpellHistory.RecordSpell(SNOPower.Wizard_Teleport); if (Trinity.Settings.Advanced.LogCategories.HasFlag(LogCategory.Movement)) { Logger.Log(TrinityLogLevel.Debug, LogCategory.Movement, "Using Teleport for OOC movement, distance={0}", destinationDistance); } return; } // Archon Teleport for a wizard if (Trinity.Hotbar.Contains(SNOPower.Wizard_Archon_Teleport) && DateTime.UtcNow.Subtract(CacheData.AbilityLastUsed[SNOPower.Wizard_Archon_Teleport]).TotalMilliseconds >= CombatBase.GetSNOPowerUseDelay(SNOPower.Wizard_Archon_Teleport) && destinationDistance >= 20f && PowerManager.CanCast(SNOPower.Wizard_Archon_Teleport) && !ShrinesInArea(vMoveToTarget)) { Vector3 vThisTarget = vMoveToTarget; if (destinationDistance > 35f) { vThisTarget = MathEx.CalculatePointFrom(vMoveToTarget, MyPosition, 35f); } ZetaDia.Me.UsePower(SNOPower.Wizard_Archon_Teleport, vThisTarget, Trinity.CurrentWorldDynamicId, -1); SpellHistory.RecordSpell(SNOPower.Wizard_Archon_Teleport); if (Trinity.Settings.Advanced.LogCategories.HasFlag(LogCategory.Movement)) { Logger.Log(TrinityLogLevel.Debug, LogCategory.Movement, "Using Archon Teleport for OOC movement, distance={0}", destinationDistance); } return; } } if (MyPosition.Distance2D(vMoveToTarget) > 1f) { // Default movement ZetaDia.Me.UsePower(SNOPower.Walk, vMoveToTarget, Trinity.CurrentWorldDynamicId, -1); if (Trinity.Settings.Advanced.LogCategories.HasFlag(LogCategory.Movement)) { Logger.Log(TrinityLogLevel.Debug, LogCategory.Movement, "Moved to:{0} dir:{1} Speed:{2:0.00} Dist:{3:0} ZDiff:{4:0} CanStand:{5} Raycast:{6}", NavHelper.PrettyPrintVector3(vMoveToTarget), MathUtil.GetHeadingToPoint(vMoveToTarget), MovementSpeed, MyPosition.Distance2D(vMoveToTarget), Math.Abs(MyPosition.Z - vMoveToTarget.Z), Trinity.MainGridProvider.CanStandAt(Trinity.MainGridProvider.WorldToGrid(vMoveToTarget.ToVector2())), !Navigator.Raycast(MyPosition, vMoveToTarget) ); } } else { if (Trinity.Settings.Advanced.LogCategories.HasFlag(LogCategory.Movement)) { Logger.Log(TrinityLogLevel.Debug, LogCategory.Movement, "Reached MoveTowards Destination {0} Current Speed: {1:0.0}", vMoveToTarget, MovementSpeed); } } }
public TrinityPower GetOffensivePower() { TrinityPower power; var target = CurrentTarget; Vector3 position = Vector3.Zero; if (Skills.Wizard.ArchonTeleport.CanCast() || Skills.Wizard.Teleport.CanCast()) { if (!Core.Buffs.HasInvulnerableShrine && Core.Avoidance.InAvoidance(Core.Player.Position)) { Core.Avoidance.Avoider.TryGetSafeSpot(out position, 35, 50, Player.Position); if (position == Vector3.Zero) { Core.Avoidance.Avoider.TryGetSafeSpot(out position, 15, 50, Player.Position); } } if (!Core.Buffs.HasInvulnerableShrine) { if (IsArchonActive) { if (TargetUtil.AnyElitesInRange(10) && Skills.Wizard.ArchonTeleport.TimeSinceUse > 2000) { Core.Avoidance.Avoider.TryGetSafeSpot(out position, 35, 50, CurrentTarget.Position); if (position == Vector3.Zero) { Core.Avoidance.Avoider.TryGetSafeSpot(out position, 20, 50, CurrentTarget.Position); } } if (TargetUtil.AnyMobsInRange(15, 3) && Skills.Wizard.ArchonTeleport.TimeSinceUse > 3000) { Core.Avoidance.Avoider.TryGetSafeSpot(out position, 35, 50, CurrentTarget.Position); if (position == Vector3.Zero) { Core.Avoidance.Avoider.TryGetSafeSpot(out position, 20, 50, CurrentTarget.Position); } } if (Player.CurrentHealthPct < 1 && Skills.Wizard.ArchonTeleport.TimeSinceUse > 2000) { Core.Avoidance.Avoider.TryGetSafeSpot(out position, 35, 50, Player.Position); if (position == Vector3.Zero) { Core.Avoidance.Avoider.TryGetSafeSpot(out position, 15, 50, Player.Position); } } } if (!IsArchonActive) { if (TargetUtil.AnyElitesInRange(10) && Skills.Wizard.Teleport.TimeSinceUse > 1200) { Core.Avoidance.Avoider.TryGetSafeSpot(out position, 35, 50, CurrentTarget.Position); if (position == Vector3.Zero) { Core.Avoidance.Avoider.TryGetSafeSpot(out position, 20, 50, CurrentTarget.Position); } } if (TargetUtil.AnyMobsInRange(15, 3) && Skills.Wizard.Teleport.TimeSinceUse > 1200) { Core.Avoidance.Avoider.TryGetSafeSpot(out position, 35, 50, CurrentTarget.Position); if (position == Vector3.Zero) { Core.Avoidance.Avoider.TryGetSafeSpot(out position, 20, 50, CurrentTarget.Position); } } if (Player.CurrentHealthPct < 1 && Skills.Wizard.Teleport.TimeSinceUse > 1200) { Core.Avoidance.Avoider.TryGetSafeSpot(out position, 35, 50, Player.Position); if (position == Vector3.Zero) { Core.Avoidance.Avoider.TryGetSafeSpot(out position, 15, 50, Player.Position); } } } } if (target.IsBoss && target.Distance < 25 && (Skills.Wizard.ArchonTeleport.TimeSinceUse > 2000 || Skills.Wizard.Teleport.TimeSinceUse > 2000)) { Core.Avoidance.Avoider.TryGetSafeSpot(out position, 40, 50, CurrentTarget.Position); } if (position != Vector3.Zero && !TrinityGrid.Instance.IsIntersectedByFlags(ZetaDia.Me.Position, position, AvoidanceFlags.Combat, AvoidanceFlags.CriticalAvoidance)) { return(Teleport(position)); } } if (!IsArchonActive) { if (Player.CurrentHealthPct <= 0.7f && Skills.Wizard.Archon.CanCast()) { Core.Logger.Log(LogCategory.Spells, $"Emergency Archon, LOW ON HP {Player.CurrentHealthPct}%!"); return(Archon()); } if (Legendary.ConventionOfElements.IsEquipped) { if (Skills.Wizard.Archon.CanCast()) { if (Core.Buffs.ConventionElement == Element.Fire && TalRashaStacks >= 3) { Core.Logger.Log(LogCategory.Spells, $"Archon conditions met (TalRasha {TalRashaStacks} stacks && We are on {Core.Buffs.ConventionElement}!)"); return(Archon()); } if (Core.Buffs.ConventionElement == Element.Fire && TalRashaStacks >= 2) { Core.Logger.Log(LogCategory.Spells, $"Archon conditions met (TalRasha {TalRashaStacks} stacks && We are on {Core.Buffs.ConventionElement}!)"); return(Archon()); } } } if (!Legendary.ConventionOfElements.IsEquipped) { if (Skills.Wizard.Archon.CanCast() && TalRashaStacks >= 2) { Core.Logger.Log(LogCategory.Spells, $"Archon conditions met (TalRasha {TalRashaStacks} stacks!)"); return(Archon()); } } } if (IsArchonActive) { target = null; var targetElite = TargetUtil.BestEliteInRange(75); var targett = TargetUtil.BestRangedAoeUnit(30, 75, Settings.ClusterSize, true, true); var targettt = TargetUtil.GetBestClusterUnit(30, 75, true, true, false, true) ?? CurrentTarget; if (targetElite != null) { Core.Logger.Log(LogCategory.Targetting, $"Ladies first!"); return(ArchonDisintegrationWave(targetElite)); } else if (targett != null) { Core.Logger.Log(LogCategory.Targetting, $"Good hunts are second!"); return(ArchonDisintegrationWave(targett)); } else { Core.Logger.Log(LogCategory.Targetting, $"Archon Disintegration: Pew Pew Pew!"); return(ArchonDisintegrationWave(targettt)); } } var lastBlackHolePosition = Vector3.Zero; power = null; if (Skills.Wizard.BlackHole.CanCast() && Core.Player.PrimaryResource > 18) { if (Skills.Wizard.BlackHole.CurrentRune == Runes.Wizard.Spellsteal || Skills.Wizard.BlackHole.CurrentRune == Runes.Wizard.AbsoluteZero) { if (TargetUtil.NumMobsInRangeOfPosition(target.Position, 15) > Skills.Wizard.BlackHole.BuffStacks) { lastBlackHolePosition = target.Position; { Core.Logger.Log(LogCategory.Targetting, $"Placing BlackHole: Good place for stacks!"); return(BlackHole(target)); } } } if (target.Position.Distance2D(lastBlackHolePosition) > 10) { lastBlackHolePosition = target.Position; { Core.Logger.Log(LogCategory.Targetting, $"Placing BlackHole: The bomb has been planted!"); return(BlackHole(target)); } } if (Skills.Wizard.BlackHole.TimeSinceUse > 2000) { lastBlackHolePosition = target.Position; { Core.Logger.Log(LogCategory.Targetting, $"Placing BlackHole: Well placed."); return(BlackHole(target)); } } } if (TrySecondaryPower(out power)) { Core.Logger.Log(LogCategory.Targetting, $"OffensivePower SecondaryPower"); return(power); } if (TryPrimaryPower(out power)) { Core.Logger.Log(LogCategory.Targetting, $"OffensivePower PrimaryPower"); return(power); } return(Walk(CurrentTarget.Position)); }