/// <summary> /// Get a SkillMeta object /// </summary> /// <param name="skill"></param> /// <returns></returns> public static SkillMeta GetSkillMeta(Skill skill) { SkillMeta s; if (_skillMetas.TryGetValue(skill, out s)) return s; Logger.LogVerbose("GetSkillInfo found no SkillMeta for {0}", skill.Name); var newMeta = new SkillMeta(skill); SetSkillMeta(newMeta); return newMeta; }
public SkillMeta(Skill skill) { Skill = skill; }
/// <summary> /// Finds a target location using skill metadata. /// </summary> /// <param name="skill">skill to be used</param> /// <returns>target position</returns> public static TrinityCacheObject GetBestAreaEffectTarget(Skill skill) { // Avoid bot choosing a target that is too far away (and potentially running towards it) when there is danger close by. var searchRange = (float)(skill.IsGeneratorOrPrimary && Enemies.CloseNearby.Units.Any() ? skill.Meta.CastRange * 0.5 : skill.Meta.CastRange); TrinityCacheObject target; switch (skill.Meta.AreaEffectShape) { case AreaEffectShapeType.Beam: target = TargetUtil.GetBestPierceTarget(searchRange); break; case AreaEffectShapeType.Circle: target = TargetUtil.GetBestClusterUnit(skill.AreaEffectRadius, searchRange); break; case AreaEffectShapeType.Cone: target = TargetUtil.GetBestArcTarget(searchRange, skill.AreaEffectRadius); break; default: target = TargetUtil.GetBestClusterUnit(skill.AreaEffectRadius, searchRange); break; } return target ?? CurrentTarget; }
/// <summary> /// Converts a skill into a TrinityPower for casting /// </summary> /// <returns></returns> public static TrinityPower GetTrinityPower(Skill skill) { var ticksBefore = skill.Meta.BeforeUseDelay == 0 ? 0 : (int)Math.Round(BotMain.TicksPerSecond * (skill.Meta.BeforeUseDelay / 1000)); var ticksAfter = skill.Meta.AfterUseDelay == 0 ? 0 : (int)Math.Round(BotMain.TicksPerSecond * (skill.Meta.AfterUseDelay / 1000)); if (skill.Meta.IsCastOnSelf) { Logger.Log(LogCategory.Targetting, "Calculating TargetPosition for {0} as Self. CurrentTarget={1}", skill.Name, CurrentTarget != null ? CurrentTarget.InternalName : ""); return skill.ToPower(ticksBefore, ticksAfter); } var castRange = (skill.Meta.CastRange <= 0) ? (int)Math.Round(skill.Meta.MaxTargetDistance * 0.5) : skill.Meta.CastRange; if (skill.Meta.TargetPositionSelector != null) { var targetPosition = skill.Meta.TargetPositionSelector(skill.Meta); Logger.Log(LogCategory.Targetting, "Calculating TargetPosition for {0} using TargetPositionSelector at {1} Dist={2} PlayerIsFacing(CastPosition={3} CurrentTarget={4}) CurrentTarget={5}", skill.Name, targetPosition, Player.Position.Distance(targetPosition), Player.IsFacing(targetPosition), Player.IsFacing(CurrentTarget.Position), CurrentTarget.InternalName ); return skill.ToPower(castRange, targetPosition, ticksBefore, ticksAfter); } if (skill.Meta.TargetUnitSelector != null) { var targetUnit = skill.Meta.TargetUnitSelector(skill.Meta); Logger.Log(LogCategory.Targetting, "Calculating TargetPosition for {0} using TargetUnitSelector at {1} Dist={2} PlayerIsFacing(CastPosition={3} CurrentTarget={4}) CurrentTarget={5}", skill.Name, targetUnit.Position, Player.Position.Distance(targetUnit.Position), Player.IsFacing(targetUnit.Position), Player.IsFacing(CurrentTarget.Position), CurrentTarget.InternalName ); return skill.ToPower(castRange, targetUnit.Position, targetUnit.ACDGuid, ticksBefore, ticksAfter); } if (skill.Meta.IsAreaEffectSkill) { var target = GetBestAreaEffectTarget(skill); Logger.Log(LogCategory.Targetting, "Calculating TargetPosition for {0} using AreaEffectTargetting at {1} Dist={2} PlayerIsFacing(CastPosition={3} CurrentTarget={4}) CurrentTarget={5} AreaShape={6} AreaRadius={7} ", skill.Name, target, Player.Position.Distance(target.Position), Player.IsFacing(target.Position), Player.IsFacing(CurrentTarget.Position), CurrentTarget.InternalName, skill.Meta.AreaEffectShape, skill.AreaEffectRadius ); return skill.ToPower(castRange, target.Position, target.ACDGuid, ticksBefore, ticksAfter); } return skill.ToPower(castRange, CurrentTarget.Position); }
/// <summary> /// Checks if a skill can and should be cast. /// </summary> /// <param name="skill">the Skill to check</param> /// <param name="condition">function to test against</param> //public static bool CanCast(Skill skill, Func<SkillMeta, bool> condition) //{ // return CanCast(skill, null, condition); //} /// <summary> /// Checks if a skill can and should be cast. /// </summary> /// <param name="skill">the Skill to check</param> /// <param name="changes">action to modify existing skill data</param> //public static bool CanCast(Skill skill, Action<SkillMeta> changes) //{ // return CanCast(skill, null, c => { changes(c); return true; }); //} /// <summary> /// Checks if a skill can and should be cast. /// </summary> /// <param name="skill">the Skill to check</param> /// <param name="cd">Optional combat data to use</param> /// <param name="adhocCondition">Optional function to test against</param> public static bool CanCast(Skill skill, SkillMeta sm = null) { try { var meta = (sm != null) ? skill.Meta.Apply(sm) : skill.Meta; Func<string> check = () => { if (!Hotbar.Contains(skill.SNOPower)) return "NotOnHotbar"; if (Player.IsIncapacitated) return "IsIncapacitated"; //var adhocConditionResult = (adhocCondition == null) || adhocCondition(meta); var metaConditionResult = (meta.CastCondition == null) || meta.CastCondition(meta); if (!meta.CastFlags.HasFlag(CanCastFlags.NoTimer) && !SNOPowerUseTimer(skill.SNOPower)) return "PowerUseTimer"; if (!meta.CastFlags.HasFlag(CanCastFlags.NoPowerManager) && !PowerManager.CanCast(skill.SNOPower)) return "PowerManager"; // Note: ZetaDia.Me.IsInCombat is unrealiable and only kicks in after an ability has hit a monster if (meta.IsCombatOnly && CurrentTarget == null) return "IsInCombat"; // This is already checked above...? //if (meta.ReUseDelay > 0 && TimeSincePowerUse(skill.SNOPower) < meta.ReUseDelay) // return "ReUseDelay"; //if (meta.IsEliteOnly && Enemies.Nearby.EliteCount == 0) // return false; //if (meta.MaxTargetDistance > CurrentTarget.Distance) // return false; var resourceCost = (meta.RequiredResource > 0) ? meta.RequiredResource : skill.Cost; if (resourceCost > 0 && !skill.IsGeneratorOrPrimary) { var actualResource = (skill.Resource == Resource.Discipline) ? Player.SecondaryResource : Player.PrimaryResource; if (actualResource < resourceCost) return string.Format("NotEnoughResource({0}/{1})", Math.Round(actualResource), resourceCost); } //if (meta.IsEliteOnly && !CurrentTarget.IsBossOrEliteRareUnique) // return false; //if (!adhocConditionResult) // return "AdHocConditionFailure"; if (!metaConditionResult) return "ConditionFailure"; return string.Empty; }; var failReason = check(); if (!string.IsNullOrEmpty(failReason)) { Logger.Log(TrinityLogLevel.Verbose, LogCategory.SkillSelection, " >> CanCast Failed: {0} ({1}) Reason={2}", skill.Name, (int)skill.SNOPower, failReason); return false; } return true; } catch (Exception ex) { Logger.Log("Exception in CanCast for {0}. {1} {2}", skill.Name, ex.Message, ex.InnerException); } return false; }
/// <summary> /// Checks a skill against the convention of elements ring /// </summary> internal static bool ShouldWaitForConventionElement(Skill skill) { if (!Settings.Combat.Misc.UseConventionElementOnly) return false; return Legendary.ConventionOfElements.IsEquipped && CacheData.Buffs.ConventionElement != skill.Element; }