/// <summary> /// Calculates healing and damage, then checks and applies any critical strikes, then applies healing and damage. /// </summary> /// <param name="actor">The character applying the healing and damage.</param> /// <param name="action">The action being performed.</param> /// <param name="targets">The list of characters the action is targeting.</param> private void ApplyHealingAndDamage(Character actor, ActionBase action, IReadOnlyList <Character> targets) { var damageTypes = DamageCalculator.GetDamage(actor, action); int[] damage = damageTypes.AsArray(); var postHealthChangedDict = new Dictionary <int, int>(); var changeAmount = new Dictionary <int, int>(); var loggableData = new List <KeyValuePair <string, int> >(); var preHealthChangedDict = new Dictionary <int, int>(); for (int i = 0; i < targets.Count(); i++) { int startHealth = targets[i].CurrentHealth; preHealthChangedDict.Add(targets[i].Id, startHealth); // Calculate and apply healing int percentHealAmount = targets[i].CurrentMaxHealth * DamageCalculator.GetHealingPercentage(actor, action); int modifiedHealAmount = DamageCalculator.GetHealing(actor, action); int totalDamage = DamageCalculator.GetTotalDamage(damageTypes, targets[i]); // Determine if a critical happened int totalCritChance = action.CritChance > 0 ? action.CritChance + actor.CritChance : 0; int rand = _random.Next(1, 101); if (totalCritChance >= rand) { int multiplier = action.CritMultiplier + actor.CritMultiplier + 100; modifiedHealAmount = modifiedHealAmount * multiplier / 100; totalDamage = totalDamage * multiplier / 100; } _characterController.ModifyCurrentHealth(targets[i], percentHealAmount); _characterController.ModifyCurrentHealth(targets[i], modifiedHealAmount); _characterController.ModifyCurrentHealth(targets[i], totalDamage); _threatController.ApplyThreat(actor, targets[i], percentHealAmount + modifiedHealAmount + totalDamage, action.Threat, action.ThreatMultiplier); // Prepare event data int healthChange = targets[i].CurrentHealth - startHealth; changeAmount.Add(targets[i].Id, healthChange); loggableData.Add(new KeyValuePair <string, int>(targets[i].Name, healthChange)); postHealthChangedDict.Add(targets[i].Id, targets[i].CurrentHealth); } if (changeAmount.Values.Any(val => val != 0)) { string logMessage = CombatMessenger.GetHealthChangedMessage(actor.Name, action.Name, loggableData); CharactersHealthChanged?.Invoke(this, new CharactersHealthChangedEventArgs() { PostCharactersChanged = postHealthChangedDict, PreCharactersChanged = preHealthChangedDict, ChangeAmount = changeAmount, LogMessage = logMessage }); } }
/// <summary> /// Creates and returns an instance of an AppliedStatus. /// </summary> /// <param name="applicator">The character from which the status originated.</param> /// <param name="statusBase">The status which the AppliedStatus is based off.</param> /// <returns>A wrapper around a status effect, containing modified values and a turn counter.</returns> private AppliedStatus CreateAppliedStatus(Character applicator, StatusEffect statusBase) { return(new AppliedStatus() { Applicator = applicator, BaseStatus = statusBase, TotalDamage = DamageCalculator.GetDamage(applicator, statusBase), HealAmount = DamageCalculator.GetHealing(applicator, statusBase), HealPercentage = DamageCalculator.GetHealingPercentage(applicator, statusBase), TurnsRemaining = statusBase.Duration, CritChance = applicator.CritChance + statusBase.CritChance, CritMultiplier = applicator.CritMultiplier + statusBase.CritMultiplier, StackCount = 1 }); }
/// <summary> /// Creates a delayed status instance, which will apply a status effect after a set amount of turns has passed. /// </summary> /// <param name="applicator">The character from which the spell originated.</param> /// <param name="statusBase">The status effect to use as the base for the delayed status.</param> /// <param name="target">A list of characters to apply to status effects to.</param> /// <param name="spellDelay">How many rounds until the buffs activate.</param> public void CreateDelayedStatus(Character applicator, StatusEffect statusBase, IReadOnlyList <int> targets, int spellDelay) { var delayedStatus = new DelayedStatus() { Applicator = applicator, BaseStatus = statusBase, TotalDamage = DamageCalculator.GetDamage(applicator, statusBase), HealAmount = DamageCalculator.GetHealing(applicator, statusBase), HealPercentage = DamageCalculator.GetHealingPercentage(applicator, statusBase), SpellDelay = spellDelay, CritChance = applicator.CritChance + statusBase.CritChance, CritMultiplier = applicator.CritMultiplier + statusBase.CritMultiplier, Targets = targets }; if (!_delayedStatuses.ContainsKey(applicator)) { _delayedStatuses[applicator] = new List <DelayedStatus>(); } _delayedStatuses[applicator].Add(delayedStatus); }
/// <summary> /// Creates a delayed action that has it's effects applied after an amount of rounds has passed. /// </summary> /// <param name="actor">The character performing the action.</param> /// <param name="action">The action being performed.</param> /// <param name="targets">The list of target positions the action targets.</param> private void CreateDelayedAction(Character actor, ActionBase action, IReadOnlyList <int> targets) { if (!_delayedActions.ContainsKey(actor)) { _delayedActions[actor] = new List <DelayedAction>(); } DamageTypes totalDamage = DamageCalculator.GetDamage(actor, action); int totalHeal = DamageCalculator.GetHealing(actor, action); int percentageHealing = DamageCalculator.GetHealingPercentage(actor, action); int rand = _random.Next(1, 101); int totalCritChance = action.CritChance > 0 ? action.CritChance + actor.CritChance : 0; if (totalCritChance > rand) { int critMultiplier = actor.CritMultiplier + action.CritMultiplier + 100; totalDamage = totalDamage * critMultiplier / 100; totalHeal = totalHeal * critMultiplier / 100; } _delayedActions[actor].Add(new DelayedAction() { Actor = actor, BaseAction = action, TotalDamage = totalDamage, HealAmount = totalHeal, HealPercentage = percentageHealing, TurnsRemaining = action.Delay, Targets = targets }); DelayedActionBeginChannel?.Invoke(this, new CombatLoggableEventArgs() { LogMessage = CombatMessenger.GetBeginChannelMessage(actor.Name, action.Name) }); }
/// <summary> /// Applies a status effect on a character. /// </summary> /// <param name="applicator">The character that is applying the status effect.</param> /// <param name="status">The status effect being applied.</param> /// <param name="character">The character the status effect is being applied on.</param> public void ApplyStatus(Character applicator, StatusEffect status, Character character, bool invokeAppliedStatusEvent = true) { if (!_appliedStatuses.ContainsKey(character)) { _appliedStatuses[character] = new List <AppliedStatus>(); } // If the the same type of status is already on a character if (_appliedStatuses[character].Any(applied => applied.BaseStatus == status)) { var matchingStatus = _appliedStatuses[character].First(applied => applied.BaseStatus == status); // If the status is stackable, refresh the duration and apply another layer of effects if (status.Stackable && matchingStatus.StackCount < status.StackSize) { matchingStatus.TurnsRemaining = status.Duration; matchingStatus.TotalDamage += DamageCalculator.GetDamage(applicator, status); matchingStatus.HealAmount += DamageCalculator.GetHealing(applicator, status); matchingStatus.HealPercentage += DamageCalculator.GetHealingPercentage(applicator, status); matchingStatus.CritChance = status.CritChance + character.CritChance; matchingStatus.CritMultiplier = status.CritMultiplier + character.CritMultiplier; ApplyStatusEffects(status, character); matchingStatus.StackCount++; } // If the status is stackable but has reached its stack limit, refresh the duration only else if (status.Stackable) { matchingStatus.TurnsRemaining = status.Duration; } // If the status isn't stackable, refresh the duration and reset the damage else { matchingStatus.TurnsRemaining = status.Duration; matchingStatus.TotalDamage = DamageCalculator.GetDamage(applicator, status); matchingStatus.HealAmount = DamageCalculator.GetHealing(applicator, status); matchingStatus.HealPercentage = DamageCalculator.GetHealingPercentage(applicator, status); matchingStatus.CritChance = status.CritChance + character.CritChance; matchingStatus.CritMultiplier = status.CritMultiplier + character.CritMultiplier; } } // Create and apply a new status effect on a character else { ApplyStatusEffects(status, character); _appliedStatuses[character].Add(CreateAppliedStatus(applicator, status)); if (status.IsDebuff) { character.Debuffs.Add(status); } else { character.Buffs.Add(status); } } if (invokeAppliedStatusEvent) { StatusEffectApplied?.Invoke(this, new StatusEffectAppliedEventArgs() { AffectedCharacterIds = new List <int>() { character.Id }, LogMessage = CombatMessenger.GetAffectedByStatusMessage(status.Name, character.Name) }); } }