/// <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> /// Applies healing then damage from a delayed action. /// </summary> /// <param name="action">The delayed action causing the healing and damage.</param> private void ApplyHealingAndDamage(DelayedAction action) { var targets = new List <Character>(AllCharacters.Where( character => IsTargetableCharacter(character, action.BaseAction, action.Targets))); 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 startingHealth = targets[i].CurrentHealth; preHealthChangedDict.Add(targets[i].Id, startingHealth); int totalDamage = DamageCalculator.GetTotalDamage(action.TotalDamage, targets[i]); int percentHealAmount = targets[i].CurrentMaxHealth * action.HealPercentage; _characterController.ModifyCurrentHealth(targets[i], percentHealAmount); _characterController.ModifyCurrentHealth(targets[i], action.HealAmount); _characterController.ModifyCurrentHealth(targets[i], totalDamage); _threatController.ApplyThreat(action.Actor, targets[i], percentHealAmount + action.HealAmount + totalDamage, action.BaseAction.Threat, action.BaseAction.ThreatMultiplier); int modifiedHealth = targets[i].CurrentHealth - startingHealth; changeAmount.Add(targets[i].Id, modifiedHealth); loggableData.Add(new KeyValuePair <string, int>(targets[i].Name, modifiedHealth)); postHealthChangedDict.Add(targets[i].Id, targets[i].CurrentHealth); } if (changeAmount.Values.Any(val => val != 0)) { string logMessage = CombatMessenger.GetHealthChangedMessage(action.Actor.Name, action.BaseAction.Name, loggableData); CharactersHealthChanged?.Invoke(this, new CharactersHealthChangedEventArgs() { PostCharactersChanged = postHealthChangedDict, PreCharactersChanged = preHealthChangedDict, ChangeAmount = changeAmount, LogMessage = logMessage }); } CheckForDeadTargets(targets); }
private void OnCharactersHealthChanged(object sender, CharactersHealthChangedEventArgs args) { CharactersHealthChanged?.Invoke(sender, args); }
/// <summary> /// Applies damage and healing from status effects affecting a character at the start of its turn. /// If there are no turns remaining on the status, the status is removed. If the character dies from /// status effect damage, the character died event is invoked. /// </summary> /// <param name="character">The character starting its turn.</param> private void StartOfTurnEffects(Character character) { var removeStatuses = new List <AppliedStatus>(); int startingHealth = character.CurrentHealth; int totalDamage = 0; // Calculate damage and apply healing from each status effect foreach (var status in _appliedStatuses[character]) { int damage = DamageCalculator.GetTotalDamage(status.TotalDamage, character); totalDamage += damage; int healAmount = status.HealAmount; int percentHeal = character.CurrentMaxHealth * status.HealPercentage / 100; if (_random.Next(1, 101) <= status.CritChance) { totalDamage = totalDamage * status.CritMultiplier / 100; healAmount = healAmount * status.CritMultiplier / 100; } character.CurrentHealth += percentHeal; character.CurrentHealth += healAmount; _threatController.ApplyThreat(status.Applicator, character, damage + healAmount + percentHeal, status.BaseStatus.Threat, status.BaseStatus.ThreatMultiplier); if (character.CurrentHealth > character.CurrentMaxHealth) { character.CurrentHealth = character.CurrentMaxHealth; } status.TurnsRemaining--; if (status.TurnsRemaining == 0) { removeStatuses.Add(status); } } character.CurrentHealth += totalDamage; int modifiedHealth = character.CurrentHealth - startingHealth; var names = _appliedStatuses[character].Select(status => status.BaseStatus.Name).ToList(); if (modifiedHealth != 0) { CharactersHealthChanged?.Invoke(this, new CharactersHealthChangedEventArgs() { PostCharactersChanged = new Dictionary <int, int>() { { character.Id, character.CurrentHealth } }, PreCharactersChanged = new Dictionary <int, int>() { { character.Id, startingHealth } }, ChangeAmount = new Dictionary <int, int>() { { character.Id, modifiedHealth } }, LogMessage = CombatMessenger.GetHealthChangedByStatusMessage(names, character.Name, modifiedHealth) }); } var statusNames = removeStatuses.Select(st => st.BaseStatus.Name).ToList(); // If a status is queued for removal, remove from the character's buff and debuff lists foreach (var status in removeStatuses) { if (status.BaseStatus.IsDebuff) { character.Debuffs.Remove(status.BaseStatus); } else { character.Buffs.Remove(status.BaseStatus); } RemoveStatusEffects(status, character); } _appliedStatuses[character].RemoveAll(status => removeStatuses.Contains(status)); if (removeStatuses.Any()) { StatusEffectsRemoved?.Invoke(this, new CombatLoggableEventArgs() { LogMessage = CombatMessenger.GetRemoveStatusMessage(statusNames, character.Name) }); } // Invoke characters dying event if a character died as a result of this status effect. if (character.CurrentHealth <= 0) { character.CurrentHealth = 0; RemoveAllStatuses(character); var characters = new List <Character>() { character }; CharactersDied?.Invoke(this, new CharactersDiedEventArgs() { DyingCharacters = characters, LogMessage = CombatMessenger.GetCharactersDiedMessage(characters.Select(chr => chr.Name).ToList()) }); } }