/// <summary> /// Gets the amount of damage, healing, and crit chance/damage a status effect does as a list of key value pairs. /// </summary> /// <param name="character">The character to use to calculate the amount of damage and healing a status effect does.</param> /// <param name="status">The status effect to get data for.</param> /// <returns>A list of key value pairs containing the name of the stat and the value of that stat.</returns> private List <KeyValuePair <string, int> > GetStatusDamageData(Character character, StatusEffect status) { var data = new List <KeyValuePair <string, int> >(); var damage = GetElementalData(DamageCalculator.GetDamage(character, status), "Damage/Turn"); // Calculate heals int healAmount = DamageCalculator.GetHealing(character, status); int healPercent = DamageCalculator.GetHealingPercentage(character, status); bool healsOrDamage = damage.Any() || healAmount != 0 || healPercent != 0; // Add damage first data.AddRange(damage); if (healsOrDamage) { data.Add(new KeyValuePair <string, int>("Crit Chance", status.DamageCritChance + character.CritChance)); data.Add(new KeyValuePair <string, int>("Crit Damage", status.DamageCritMultiplier + character.CritMultiplier)); } // Add any healing amounts if (healAmount != 0) { data.Add(new KeyValuePair <string, int>("Health Restored/Turn", healAmount)); } if (healPercent != 0) { data.Add(new KeyValuePair <string, int>("% Health Restored/Turn", healPercent)); } return(data); }
/// <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; } } }
public virtual void Attack(Character target) { if (CanHit(this, target)) { DamageCalculator dc = new DamageCalculator(); int damage = dc.GetDamage(this, target); target.TakeDamage(damage); } }
/// <summary> /// Creates ActionData using data from a character and that character's action. /// </summary> /// <param name="character">The character to use data from.</param> /// <param name="action">The action to use data from.</param> /// <returns>A struct containing display data about the action.</returns> private ActionData CreateActionData(Character character, ActionBase action) { var data = new ActionData(); var damage = DamageCalculator.GetDamage(character, action); data.Damage = action.Damage; data.ModifiedDamage = damage; data.Heal = action.HealAmount; data.StatusEffects = new List <string>(action.BuffsToApply.Select(status => status.Name)); return(data); }
/// <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); }
private static void ReportHit( string hitDescription, int attack, int defense, int health, float attackMultiplier = 1f, int numAttacks = 1 ) { var attackWithMultiplier = (int)Math.Round(attack * attackMultiplier); var damageDone = DamageCalculator.GetDamage(attackWithMultiplier, defense); var totalDamageDone = damageDone * numAttacks; var damageDonePercentage = 1 - (health - totalDamageDone) / (float)health; var hitsToKill = CalculateHitsToKill(totalDamageDone, health); if (numAttacks == 1) { System.Console.WriteLine($"{damageDone} dmg, {hitDescription} ({damageDonePercentage:P}, htk: {hitsToKill})"); } else if (numAttacks > 1) { System.Console.WriteLine($"{totalDamageDone} dmg ({numAttacks}x {damageDone}), {hitDescription} ({damageDonePercentage:P}, htk: {hitsToKill})"); } }
public void UpdateForecast(Vector2Int attackingUnitNewPosition, Unit playerUnit, Unit enemyUnit) { if (playerUnit.PlayerNumber != enemyUnit.PlayerNumber) { targetUnitInformationBackground.color = ENEMY_COLOR; var defenderPosition = gameViewModel.GetPositionOfUnit(enemyUnit); var defenderAttackPositions = gameViewModel.GetSurroundingLocationsAtPoint(defenderPosition, enemyUnit.MainWeapon.Range); playerNameLabel.text = playerUnit.Name; playerAttackMethodLabel.text = playerUnit.MainWeapon.Name; playerCurrentHpLabel.text = $"CUR {playerUnit.HealthPoints}"; playerMaxHpLabel.text = $"MAX {playerUnit.MaxHealthPoints}"; playerHitLabel.text = $"HIT {DamageCalculator.GetHitChance(playerUnit, enemyUnit)}"; playerOffensiveLabel.text = $"OFF {DamageCalculator.GetOffensive(playerUnit)}"; enemyDefensiveLabel.text = $"DEF {DamageCalculator.GetDefensive(playerUnit, enemyUnit)}"; enemyPotentialHpLabel.gameObject.SetActive(true); int enemyHealthLost = 0; if (playerUnit.Speed.Value > enemyUnit.Speed.Value) { enemyHealthLost = 2 * DamageCalculator.GetDamage(playerUnit, enemyUnit); playerFollowupPossibleLabel.gameObject.SetActive(true); } else { enemyHealthLost = DamageCalculator.GetDamage(playerUnit, enemyUnit); playerFollowupPossibleLabel.gameObject.SetActive(false); } if (enemyUnit.HealthPoints - enemyHealthLost <= 0) { enemyPotentialHpLabel.text = $"0"; } else { enemyPotentialHpLabel.text = $"{enemyUnit.HealthPoints - enemyHealthLost}"; } playerDefensiveLabel.text = $"DEF {DamageCalculator.GetDefensive(enemyUnit, playerUnit)}"; playerCritLabel.text = $"CRT {DamageCalculator.GetCritRate(playerUnit, enemyUnit)}"; enemyNameLabel.text = enemyUnit.Name; enemyCurrentHpLabel.text = $"CUR {enemyUnit.HealthPoints}"; enemyMaxHpLabel.text = $"MAX {enemyUnit.MaxHealthPoints}"; playerPotentialHpLabel.gameObject.SetActive(true); if (defenderAttackPositions.Contains(attackingUnitNewPosition)) { var playerHealthLost = 0; if (enemyUnit.Speed.Value > playerUnit.Speed.Value) { playerHealthLost = 2 * DamageCalculator.GetDamage(enemyUnit, playerUnit); enemyFollowupPossibleLabel.gameObject.SetActive(true); } else { playerHealthLost = DamageCalculator.GetDamage(enemyUnit, playerUnit); enemyFollowupPossibleLabel.gameObject.SetActive(false); } if (playerUnit.HealthPoints - playerHealthLost <= 0) { playerPotentialHpLabel.text = "0"; } else { playerPotentialHpLabel.text = $"{playerUnit.HealthPoints - playerHealthLost}"; } enemyAttackMethodLabel.text = playerUnit.MainWeapon.Name; enemyHitLabel.text = $"HIT {DamageCalculator.GetHitChance(enemyUnit, playerUnit)}"; enemyOffensiveLabel.text = $"OFF {DamageCalculator.GetOffensive(enemyUnit)}"; enemyDefensiveLabel.text = $"DEF {DamageCalculator.GetDefensive(playerUnit, enemyUnit)}"; enemyCritLabel.text = $"CRT {DamageCalculator.GetCritRate(enemyUnit, playerUnit)}"; } else { playerPotentialHpLabel.text = $"{playerUnit.HealthPoints}"; enemyAttackMethodLabel.text = ""; enemyHitLabel.text = $"HIT 0"; enemyOffensiveLabel.text = $"OFF 0"; enemyCritLabel.text = $"CRT 0"; } } }
public void UpdateForecastWithSkill(Vector2Int attackingUnitNewPosition, Unit playerUnit, Unit targetUnit, SingleTargetSkill skill) { if (playerUnit.PlayerNumber != targetUnit.PlayerNumber) { targetUnitInformationBackground.color = ENEMY_COLOR; var defenderPosition = gameViewModel.GetPositionOfUnit(targetUnit); var defenderAttackPositions = gameViewModel.GetSurroundingLocationsAtPoint(defenderPosition, targetUnit.MainWeapon.Range); var damageSkill = skill as SingleDamageSkill; playerNameLabel.text = playerUnit.Name; playerAttackMethodLabel.text = $"{damageSkill.SkillName}"; playerCurrentHpLabel.text = $"CUR {playerUnit.HealthPoints}"; playerMaxHpLabel.text = $"MAX {playerUnit.MaxHealthPoints}"; playerHitLabel.text = $"HIT {damageSkill.GetHitChance(playerUnit, targetUnit)}"; playerOffensiveLabel.text = $"OFF {damageSkill.GetOffensive(playerUnit)}"; playerDefensiveLabel.text = $"DEF {DamageCalculator.GetDefensive(targetUnit, playerUnit)}"; playerCritLabel.text = $"CRT {damageSkill.GetCritRate(playerUnit, targetUnit)}"; playerPotentialHpLabel.gameObject.SetActive(true); if (defenderAttackPositions.Contains(attackingUnitNewPosition)) { var playerHealthLost = 0; if (targetUnit.Speed.Value > playerUnit.Speed.Value) { playerHealthLost = damageSkill.SkillCost + (2 * DamageCalculator.GetDamage(targetUnit, playerUnit)); enemyFollowupPossibleLabel.gameObject.SetActive(true); } else { playerHealthLost = damageSkill.SkillCost + DamageCalculator.GetDamage(targetUnit, playerUnit); enemyFollowupPossibleLabel.gameObject.SetActive(false); } if (playerUnit.HealthPoints - playerHealthLost <= 0) { playerPotentialHpLabel.text = "0"; } else { playerPotentialHpLabel.text = $"{playerUnit.HealthPoints - playerHealthLost}"; } enemyAttackMethodLabel.text = targetUnit.MainWeapon.Name; enemyHitLabel.text = $"HIT {DamageCalculator.GetHitChance(targetUnit, playerUnit)}"; enemyOffensiveLabel.text = $"OFF {DamageCalculator.GetOffensive(targetUnit)}"; enemyCritLabel.text = $"CRT {DamageCalculator.GetCritRate(targetUnit, playerUnit)}"; } else { playerPotentialHpLabel.text = $"{playerUnit.HealthPoints - skill.SkillCost}"; enemyAttackMethodLabel.text = ""; enemyHitLabel.text = $"HIT 0"; enemyOffensiveLabel.text = $"OFF 0"; enemyCritLabel.text = $"CRT 0"; } enemyNameLabel.text = targetUnit.Name; enemyCurrentHpLabel.text = $"CUR {targetUnit.HealthPoints}"; enemyPotentialHpLabel.gameObject.SetActive(true); var enemyHealthLost = damageSkill.GetDamage(playerUnit, targetUnit); if (targetUnit.HealthPoints - enemyHealthLost <= 0) { enemyPotentialHpLabel.text = $"0"; } else { enemyPotentialHpLabel.text = $"{targetUnit.HealthPoints - damageSkill.GetDamage(playerUnit, targetUnit)}"; } enemyMaxHpLabel.text = $"MAX {targetUnit.MaxHealthPoints}"; enemyDefensiveLabel.text = $"DEF {damageSkill.GetDefensive(playerUnit, targetUnit)}"; } else { targetUnitInformationBackground.color = ALLY_COLOR; var defenderPosition = gameViewModel.GetPositionOfUnit(targetUnit); var defenderAttackPositions = gameViewModel.GetSurroundingLocationsAtPoint(defenderPosition, targetUnit.MainWeapon.Range); var supportSkill = skill as SingleSupportSkill; playerNameLabel.text = playerUnit.Name; playerAttackMethodLabel.text = $"{supportSkill.SkillName}"; playerCurrentHpLabel.text = $"CUR {playerUnit.HealthPoints}"; playerMaxHpLabel.text = $"MAX {playerUnit.MaxHealthPoints}"; playerHitLabel.text = $"HIT "; playerOffensiveLabel.text = $"OFF "; playerDefensiveLabel.text = $"DEF "; playerCritLabel.text = $"CRT "; playerPotentialHpLabel.gameObject.SetActive(true); playerPotentialHpLabel.text = $"{playerUnit.HealthPoints - skill.SkillCost}"; enemyNameLabel.text = targetUnit.Name; enemyAttackMethodLabel.text = ""; enemyCurrentHpLabel.text = $"CUR {targetUnit.HealthPoints}"; enemyPotentialHpLabel.gameObject.SetActive(true); var targetUnitHealthGain = supportSkill.GetHealAmount(playerUnit, targetUnit); if (targetUnitHealthGain == 0) { enemyPotentialHpLabel.gameObject.SetActive(false); enemyPotentialHpLabel.text = $"0"; } else { enemyPotentialHpLabel.gameObject.SetActive(true); if (targetUnit.HealthPoints + targetUnitHealthGain > targetUnit.MaxHealthPoints.Value) { enemyPotentialHpLabel.text = $"{targetUnit.MaxHealthPoints.Value}"; } else { enemyPotentialHpLabel.text = $"{targetUnit.HealthPoints + targetUnitHealthGain}"; } } enemyMaxHpLabel.text = $"MAX {targetUnit.MaxHealthPoints}"; enemyDefensiveLabel.text = $"DEF "; } }
private static void Main() { var builder = new MonsterSpeciesBuilder(); var director = new MonsterSpeciesDirector(builder); var slimeSpecies = director.CreateSlime(); var wolfSpecies = director.CreateWolf(); // System.Console.WriteLine(); // foreach (var level in new[] {/*1, 5, 10, 20, 30, */40}) // { // var attacker = new Monster(slimeSpecies, level); // var defender = new Monster(slimeSpecies, level); // // DamageReporter.ReportDamage(attacker, defender); // // System.Console.WriteLine(); // } // return; var playerTeam = new List <Monster> { new(wolfSpecies, 40), // new(slimeSpecies, 40), }; var enemyTeam = new List <Monster> { new(slimeSpecies, 40), // new(slimeSpecies, 40), }; var localPlayerController = new LocalPlayerController(); var simpleAIController = new LocalPlayerController(); // var simpleAIController = new SimpleAIController(); var combatSystem = new CombatSystem(playerTeam, localPlayerController); combatSystem.InitiateCombat(enemyTeam, simpleAIController); while (combatSystem.IsInProgress) { // check for all monster deaths on one side var activeMonster = combatSystem.GetNextActiveMonster(); if (activeMonster == null) { combatSystem.Tick(); continue; } combatSystem.CurrentTurn++; System.Console.WriteLine($"Turn {combatSystem.CurrentTurn}"); var controller = activeMonster.Controller; var skill = controller.GetSkill(activeMonster); CombatMonster target; if (skill.Target == Target.Enemy) { var eligibleTargets = combatSystem.AllMonsters .Where(m => m.Controller != controller) .Where(m => m.IsAlive) .ToList(); target = controller.GetTarget(eligibleTargets); foreach (var component in skill.Components) { if (component is DamageComponent damageComponent) { float damage = DamageCalculator.GetDamage( activeMonster.CurrentStats[Stat.Attack], target.CurrentStats[Stat.Defense] ); damage *= damageComponent.DamageMultiplier; var realDamage = (int)Math.Round(damage); var targetMaxHealth = target.Monster.Stats[Stat.Health]; var damagePercentage = (1 - (float)(targetMaxHealth - realDamage) / targetMaxHealth) * 100; target.CurrentStats[Stat.Health] -= realDamage; var sb = new StringBuilder(); sb.Append(activeMonster.Monster.Species.Name); sb.Append($" (Lv {activeMonster.Monster.Level})"); sb.Append(" uses "); sb.Append(skill.Name); sb.Append(" on "); sb.Append(target.Monster.Species.Name); sb.Append($" (Lv {target.Monster.Level})"); sb.Append(" for "); sb.Append(realDamage); sb.Append($" ({damagePercentage:F}%) damage"); System.Console.WriteLine(sb); } else if (component is StatusEffectComponent) { // todo } else { // todo } } } else if (skill.Target == Target.Friendly) { var eligibleTargets = combatSystem.AllMonsters .Where(m => m.Controller == controller) .Where(m => m.IsAlive) .ToList(); target = controller.GetTarget(eligibleTargets); // todo } else { // todo } System.Console.WriteLine(); activeMonster.AttackBar = 0; } } } }