public void GetTargets_WithBlockedTargets_ReturnsEmpty(Ability ability, int target, Formation formation) { var entities = FormationTargeter.GetTargets(ability, target, formation); Assert.Empty(entities); }
public void GetTargets_WithPointBlankTargets_ReturnsExpected(Ability ability, int target, Formation formation, IEnumerable <CombatEntity> expectedTargets) { var entities = FormationTargeter.GetTargets(ability, target, formation); // Contains all of the same items var expectations = !expectedTargets.Except(entities).Any() && expectedTargets.Count() == entities.Count(); Assert.True(expectations); }
/// <summary> /// Performs an ability on a target location, applying damage, healing, and status effects to any targets. /// </summary> /// <param name="attacker">The attacker performing the ability.</param> /// <param name="ability">The ability used to attack the enemy.</param> /// <param name="targetPosition">The position to target with the ability being used.</param> /// <param name="targetFormation">The Formation that the CombatEntity is targeting with its action.</param> /// <returns></returns> public AbilityResult PerformAbility(CombatEntity attacker, Ability ability, int targetPosition, Formation targetFormation) { var result = new AbilityResult(); // Target position is out of bounds if (!ability.IsPointBlank && !ability.IsPositionStatic && (targetPosition > 9 || targetPosition < 1)) { result.FailureReason = BattleErrorWriter.WriteTargetPositionOutOfBounds(); return(result); } if (!HasEnoughResources(attacker, ability, out string failureReason)) { result.FailureReason = failureReason; return(result); } if (ability.IsPointBlank) { targetPosition = GetTargetPosition(attacker, targetFormation); } var targets = FormationTargeter.GetTargets(ability, targetPosition, targetFormation).ToList(); if (targets == null || targets.Count == 0) { result.FailureReason = BattleErrorWriter.WriteNoTargets(); return(result); } // Filter out all dead targets targets = targets.Where(entity => entity != null && entity.Resources.CurrentHealth > 0).ToList(); // Will be an invalid Ability usage if all targets are dead (later on will add ability to revive) if (targets.Count == 0) { result.FailureReason = BattleErrorWriter.WriteAllTargetsAreDead(); return(result); } ConsumeResources(attacker, ability); ApplyEffects(attacker, ability, targets); targets.Add(attacker); result.AffectedEntities = targets; return(result); }
/// <summary> /// Activates the effects of a DelayedAbility on its target formation, returning an IEnumerable of /// CombatEntities affected by the DelayedAbility. /// </summary> /// <param name="ability">The DelayedAbility to activate the effects of.</param> /// <returns></returns> public IEnumerable <CombatEntity> PerformDelayedAbility(DelayedAbility ability) { var affectedEntities = new List <CombatEntity>(); var targetPosition = ability.TargetPosition; if (ability.BaseAbility.IsPositionStatic) { targetPosition = ability.BaseAbility.CenterOfTargets; } var targets = FormationTargeter.GetTargets(ability.BaseAbility, targetPosition, ability.TargetFormation); if (targets == null || targets.Count() == 0) { return(affectedEntities); } ApplyEffects(ability, targets); return(targets); }
public void GetTargets_WithSingleTarget_ReturnsNotNull(Ability ability, int target, Formation formation) { var entities = FormationTargeter.GetTargets(ability, target, formation); Assert.NotEmpty(entities); }
/// <summary> /// Returns a FormationEvaluation for the given CombatEntity which will use the given Ability on /// the provided Formation. /// <para>The FormationEvaluation will contain the best target location for the given Ability depending /// on the AiRandomnesFactor for the ai Formation.</para> /// </summary> /// <param name="activeEntity">The CombatEntity that will be acting.</param> /// <param name="ability">The Ability being evaluated.</param> /// <param name="formation">The Formation being targeted.</param> /// <param name="isEnemyFormation">True if the provided Formation is an enemy Formation. False if allied.</param> /// <returns></returns> private FormationEvaluation EvaluateFormation(CombatEntity activeEntity, Ability ability, Formation formation, bool isEnemyFormation) { bool isOffensive = ability.IsOffensive.GetValueOrDefault(); // Only allows offensive abilities against enemy formations and defensive abilities on allied formations if ((!isOffensive && isEnemyFormation) || (isOffensive && !isEnemyFormation)) { return(null); } var comparer = new DuplicateKeyComparer(); var evaluations = new SortedList <int, FormationEvaluation>(comparer); // Calculate total ai weight modifier for this ability int aiWeightModifier = ability.SelfAppliedStatusEffects.Concat(ability.AppliedStatusEffects) .Select(status => status.AiWeightModifier) .Sum(); aiWeightModifier += ability.AiWeightModifier; // Evaluate every target in the formation and record the evaluations in the SortedList for (int i = 0; i < formation.Positions.Length; i++) { for (int j = 0; j < formation.Positions[i].Length; j++) { int newCenter = i * GameplayConstants.MaxFormationRows + j + 1; var targets = FormationTargeter.GetTargets(ability, newCenter, formation); // No valid targets, skip this position if (targets == null || targets.Count() <= 0) { continue; } int totalThreat = targets.Select(entity => entity.Threat) .Sum(); // Calculate total value using damage if using the ability offensively if (isEnemyFormation && isOffensive) { // Ignore dead entities targets = targets.Where(entity => entity.Resources.CurrentHealth > 0).ToList(); if (targets.Count() == 0) { continue; } // Gets total potential damage dealt to each target as a percentage int damagePercentage = targets.Select(entity => { int damage = DamageCalculator.GetTotalDamageAsInt(activeEntity, entity, ability); int damageAsPercent = damage * 100 / entity.Resources.MaxHealth; return(damageAsPercent + DamageCalculator.GetPercentageDamageAsInt(entity, ability)); }).Sum(); int totalValue = totalThreat + damagePercentage + aiWeightModifier; evaluations.Add(totalValue, new FormationEvaluation { TargetLocation = newCenter, TotalValue = totalValue }); } // Calculate total value using healing if using the ability defensively else if (!isEnemyFormation && !isOffensive) { // Ignore dead entities, update later when adding resurrection spells targets = targets.Where(entity => entity.Resources.CurrentHealth > 0).ToList(); if (targets.Count() == 0) { continue; } int healPercentage = targets.Select(entity => { // Value cannot exceed max heal amount, prevents ai from wanting to overheal int maxHealPercent = 100 - (entity.Resources.CurrentHealth * 100 / entity.Resources.MaxHealth); int healPercent = DamageCalculator.GetHeal(activeEntity, ability) * 100 / entity.Resources.MaxHealth; healPercent += ability.PercentHeal; return((healPercent > maxHealPercent) ? maxHealPercent : healPercent); }).Sum(); int totalValue = totalThreat + healPercentage + aiWeightModifier; evaluations.Add(totalValue, new FormationEvaluation { TargetLocation = newCenter, TotalValue = totalValue }); } } } if (evaluations.Count == 0) { return(null); } // Returns best target evaluation if no randomness if (_myFormation.AiRandomness <= 0) { return(evaluations.Last().Value); } // If any ai randomness, choose randomly between the best targets else { var highestValues = evaluations.TakeLast(_myFormation.AiRandomness + 1) .Select(kvp => kvp.Value); var random = _rand.Next(highestValues.Count()); return(highestValues.ElementAt(random)); } }