/// <summary> /// Applies the effects of the ability being used on all of it's targets. /// </summary> /// <param name="attacker">The entity performing the ability.</param> /// <param name="ability">The ability to apply the effects of.</param> /// <param name="targets">The targets of the ability.</param> private void ApplyEffects(CombatEntity attacker, Ability ability, IEnumerable <CombatEntity> targets) { bool isCrit = IsCritical(attacker, ability); foreach (var target in targets) { var damage = DamageCalculator.GetTotalDamage(attacker, target, ability, isCrit); var healing = DamageCalculator.GetHeal(attacker, ability, isCrit); healing += target.Resources.MaxHealth * ability.PercentHeal / 100; target.Resources.CurrentHealth += healing; target.Resources.CurrentHealth -= DamageCalculator.GetDamageTypesAsInt(damage); if (target.Resources.CurrentHealth > 0) { _statusEffectManager.Apply(target, attacker, ability.AppliedStatusEffects, isCrit); if (target.Resources.CurrentHealth > target.Resources.MaxHealth) { target.Resources.CurrentHealth = target.Resources.MaxHealth; } } else { _statusEffectManager.RemoveAll(target); target.Resources.CurrentHealth = 0; } } if (ability.SelfAppliedStatusEffects.Any()) { _statusEffectManager.Apply(attacker, attacker, ability.SelfAppliedStatusEffects, isCrit); } }
/// <summary> /// Applies a StatusEffect onto a CombatEntity. /// </summary> /// <param name="recipient">The CombatEntity who is receiving the StatusEffect.</param> /// <param name="applicator">The CombatEntity applying the StatusEffect on the receiver.</param> /// <param name="statusEffect">The StatusEffect to apply onto the receiver.</param> /// <param name="isCrit">If true, will include critical damage in the calculations.</param> public void Apply(CombatEntity recipient, CombatEntity applicator, StatusEffect statusEffect, bool isCrit) { var status = recipient.StatusEffects.FirstOrDefault(se => se.BaseStatus.Id == statusEffect.Id); if (status == null) { var appliedStatusEffect = CreateAppliedStatus(applicator, recipient, statusEffect); ApplyStatEffects(recipient, statusEffect); recipient.StatusEffects.Add(appliedStatusEffect); } else { // Apply another stack of StatusEffect if (statusEffect.StackSize > 1 && status.CurrentStacks < statusEffect.StackSize) { status.CumulativeDamage += DamageCalculator.GetDamage(applicator, statusEffect, isCrit); status.CumulativeHeal += DamageCalculator.GetHeal(applicator, statusEffect, isCrit); status.CurrentStacks++; status.Duration = statusEffect.Duration; } // Can't apply another stack, refresh duration instead else { status.Duration = statusEffect.Duration; } } }
/// <summary> /// Creates an instance of an AppliedStatusEffect from the given StatusEffect. /// </summary> /// <param name="applicator">The CombatEntity applying this instance of the AppliedStatusEffect.</param> /// <param name="statusEffect">The base to use for the AppliedStatusEffect.</param> /// <returns>Returns an instance of an AppliedStatusEffect that keeps track of a StatusEffect on a CombatEntity.</returns> private AppliedStatusEffect CreateAppliedStatus(CombatEntity applicator, CombatEntity recipient, StatusEffect statusEffect) { var appliedStatus = new AppliedStatusEffect(); appliedStatus.BaseStatus = statusEffect; appliedStatus.CumulativeDamage = DamageCalculator.GetDamage(applicator, statusEffect); appliedStatus.CumulativeHeal = DamageCalculator.GetHeal(applicator, statusEffect); appliedStatus.CumulativeHeal += DamageCalculator.GetPercentageHeal(recipient.Resources.MaxHealth, statusEffect.PercentHealPerTurn); appliedStatus.CurrentStacks = 1; appliedStatus.Duration = statusEffect.Duration; return(appliedStatus); }
/// <summary> /// Creates an instance of a DelayedAbility. /// </summary> /// <param name="attacker">The CombatEntity starting the effect of a DelayedAbility.</param> /// <param name="ability">The Ability to convert into a DelayedAbility.</param> /// <param name="action">The object containing details about the action being performed.</param> /// <param name="targetFormation">The Formation that the CombatEntity is targeting with its action.</param> /// <returns></returns> public DelayedAbilityResult CreateDelayedAbility(CombatEntity attacker, Ability ability, BattleAction action, Formation targetFormation) { var result = new DelayedAbilityResult(); // Target out of bounds if (!ability.IsPointBlank && !ability.IsPositionStatic && (action.TargetPosition > 9 || action.TargetPosition < 1)) { result.FailureReason = BattleErrorWriter.WriteTargetPositionOutOfBounds(); return(result); } if (!HasEnoughResources(attacker, ability, out string failureReason)) { result.FailureReason = failureReason; return(result); } int targetPosition = action.TargetPosition; if (ability.IsPointBlank) { targetPosition = GetTargetPosition(attacker, targetFormation); } ConsumeResources(attacker, ability); bool isCrit = IsCritical(attacker, ability); result.DelayedAbility = new DelayedAbility { Actor = attacker, BaseAbility = ability, StoredDamage = DamageCalculator.GetDamage(attacker, ability, isCrit), StoredHealing = DamageCalculator.GetHeal(attacker, ability, isCrit), TargetFormation = targetFormation, TargetPosition = action.TargetPosition, IsCrit = isCrit, TurnsLeft = ability.DelayedTurns }; return(result); }
/// <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)); } }