/// <summary> /// Rolls attacks for either attackers or defenders /// </summary> /// <param name="units">Unit stacks making attacks</param> /// <param name="results">List storing attack roll results</param> private void RollAttacks(List <UnitStack> units, List <AttackRollResultsCollection> results) { for (int i = 0; i < units.Count; i++) { if (!units[i].IsAffectedBy("Confusion")) { List <Attack> phaseAttacks = units[i].GetUnitType().GetAttacksForPhase(_currentPhase); if (phaseAttacks.Count > 0) { AttackRollResultsCollection unitStackAttackRolls = new AttackRollResultsCollection(); for (int j = 0; j < units[i].GetTotalQty(); j++) { for (int k = 0; k < phaseAttacks.Count; k++) { for (int l = 0; l < phaseAttacks[k].GetNumberOfAttacks(); l++) { AttackRollResult result = CombatHelper.Instance.CreateAnAttackRollResult(units[i], phaseAttacks[k]); unitStackAttackRolls.AddAttackRollResult(result); } } } results.Add(unitStackAttackRolls); } } } results.Sort((x, y) => x.GetAt(0).AttackSkill.CompareTo(y.GetAt(0).AttackSkill)); }
/// <summary> /// Calculate mathematical expectation of the weapon damage value /// </summary> /// <param name="attackRollResult">Result of the attack roll</param> /// <param name="defender">Defending unit stack</param> /// <param name="totalDefense">Defender's total defense against the attack</param> /// <param name="armor">Defender's armor value against the attack</param> /// <param name="maxValue">Max value the damage can reach</param> /// <returns>Mathematical expectation of the weapon damage value</returns> public double EstimateWeaponDamage(AttackRollResult attackRollResult, UnitStack defender, int totalDefense, int armor, int maxValue) { double result = 0; if ((IsUsingCriticals() && attackRollResult.IsCritical) || (attackRollResult.AttackRoll + attackRollResult.AttackSkill > totalDefense)) { result = 0.5 * (attackRollResult.Attack.GetMinDamage() + attackRollResult.Attack.GetMaxDamage()); if (IsUsingCriticals() && attackRollResult.IsCritical) { result *= 2; } if (armor > 0) { result -= armor; } if (result < 0) { result = 0; } if (result > maxValue) { result = maxValue; } } return(result); }
private void RenderAttackRollResult(StringBuilder resHtml, AttackRollResult ar) { if (ar.Character != null) { AppendCharacterHeader(resHtml, ar.Character); } resHtml.AppendOpenTagWithClass("span", "weaponheader"); if (ar.Attack.Weapon == null) { resHtml.AppendSmallIcon("sword"); } else if (ar.Attack.Weapon.Ranged) { resHtml.AppendSmallIcon("bow"); } else if (ar.Attack.Weapon.Natural) { resHtml.AppendSmallIcon("claw"); } else { resHtml.AppendSmallIcon("sword"); } resHtml.AppendHtml(ar.Name.Capitalize()); resHtml.AppendLineBreak(); resHtml.AppendCloseTag("span"); foreach (SingleAttackRoll res in ar.Rolls) { RenderRollResult(resHtml, res.Result, false, ar.Attack.CritRange); resHtml.AppendHtml(" Dmg: "); RenderRollResult(resHtml, res.Damage); foreach (BonusDamage bd in res.BonusDamage) { resHtml.Append(" + "); RenderRollResult(resHtml, bd.Damage); resHtml.Append(" " + bd.DamageType); } if (res.CritResult != null) { resHtml.AppendHtml(" Crit: "); RenderRollResult(resHtml, res.CritResult, res.CritResult.Rolls[0].Result == 1); if (res.CritResult.Rolls[0].Result != 1) { resHtml.AppendHtml(" Dmg: "); } RenderRollResult(resHtml, res.CritDamage); } } }
/// <summary> /// Remove an attack roll result from the collection /// </summary> /// <param name="result">Attack roll result to remove</param> /// <returns>Whether the operation was successful</returns> public bool RemoveAttackRollResult(AttackRollResult result) { bool success = _model.Remove(result); if (success && Updated != null) { Updated(this, EventArgs.Empty); } return(success); }
public void RollAttack(Attack atk, Character ch) { AttackRollResult res = new AttackRollResult(atk); res.Character = ch; _Results.Insert(0, res); TrimList(); RenderResults(); }
/// <summary> /// Class constructor /// </summary> /// <param name="attack">Attack roll result</param> /// <param name="target">Target unit defending against the attack</param> /// <param name="defensePositiveDieRoll">Value of the defense's positive roll</param> /// <param name="defenseNegativeDieRoll">Value of the defense's negative roll</param> /// <param name="defenseSkill">Value of the skill used in defense</param> /// <param name="shield">Shield value used in the defense roll</param> public AttackResolutionEvent(AttackRollResult attack, UnitStack target, int defensePositiveDieRoll, int defenseNegativeDieRoll, int defenseSkill, int shield) { _attack = attack; _target = target; _defensePositiveDieRoll = defensePositiveDieRoll; _defenseNegativeDieRoll = defenseNegativeDieRoll; _defenseSkill = defenseSkill; _shield = shield; }
/// <summary> /// Add another attack roll result to the collection /// </summary> /// <param name="result">Attack roll result to add</param> /// <returns>Whether the operation was successful</returns> public bool AddAttackRollResult(AttackRollResult result) { if (_model.Count > 0 && result.UnitStack != _unitStack) { return(false); } _model.Add(result); _unitStack = result.UnitStack; if (Updated != null) { Updated(this, EventArgs.Empty); } return(true); }
/// <summary> /// Calculate damage inflicted by an attack /// </summary> /// <param name="attackRollResult">Attack roll result</param> /// <param name="defender">Defending unit stack</param> /// <param name="totalDefense">Defender's total defense value</param> /// <param name="armor">Defender's armor value</param> /// <returns>Damage inflicted by the attack to the defender</returns> public int CalculateDamage(AttackRollResult attackRollResult, UnitStack defender, int totalDefense, int armor) { int result = 0; if ((IsUsingCriticals() && attackRollResult.IsCritical) || (attackRollResult.AttackRoll + attackRollResult.AttackSkill > totalDefense)) { result = attackRollResult.FullDamage; if (armor > 0) { result = Mathf.Max(0, result - armor); } } return(result); }
/// <summary> /// Generate an attack roll result /// </summary> /// <param name="attacker">Unit stack representing the attacker</param> /// <param name="attack">Attack object for roll result generation</param> /// <param name="plusDie">Value of the positive die's roll (pass zero to actually roll)</param> /// <param name="minusDie">Value of the negative die's roll (pass zero to actually roll)</param> /// <returns>Whether the die roll represents a critical success</returns> public AttackRollResult CreateAnAttackRollResult(UnitStack attacker, Attack attack, int plusDie = 0, int minusDie = 0) { bool isCritical = false; int positiveDieRoll = plusDie == 0 ? Dice.RollDie(_diceSides) : plusDie; int negativeDieRoll = minusDie == 0 ? Dice.RollDie(_diceSides) : minusDie; int dieRoll = positiveDieRoll - negativeDieRoll; int bonusDamage = 0; if (IsCritical(dieRoll)) { isCritical = true; bonusDamage = attack.RollDamage(); } AttackRollResult result = new AttackRollResult(attacker, attack, positiveDieRoll, negativeDieRoll, attack.GetSkill(), attack.RollDamage(), bonusDamage, isCritical); return(result); }
/// <summary> /// Estimate results of a unit stack attacking another during a specified turn phase /// </summary> /// <param name="attacker">Attacking unit stack</param> /// <param name="defender">Defending unit stack</param> /// <param name="phase">Turn phase</param> /// <returns>Mathematical expectation of the attack damage value</returns> public double EstimateStackAttacksDamage(UnitStack attacker, UnitStack defender, Combat.TurnPhase phase) { double result = GetEstimatedDamage(attacker, defender, phase); if (result >= 0) { return(result); } result = 0; int maxHP = defender.GetUnitType().GetHitPoints(); List <Attack> attacks = attacker.GetUnitType().GetAttacksForPhase(phase); for (int i = 0; i < attacks.Count; i++) { int numberOfAttacks = attacks[i].GetNumberOfAttacks(); for (int attackerPlusDie = 0; attackerPlusDie < _diceSides; attackerPlusDie++) { for (int attackerMinusDie = 0; attackerMinusDie < _diceSides; attackerMinusDie++) { AttackRollResult attackRollResult = CreateAnAttackRollResult(attacker, attacks[i], attackerPlusDie + 1, attackerMinusDie + 1); int defensiveSkill = CalculateDefensiveSkill(attackRollResult.Attack, defender); int shield = CalculateShieldValue(attackRollResult.Attack, defender); int armor = CalculateArmorValue(attackRollResult.Attack, defender, attackRollResult.IsCritical); for (int defenderPlusDie = 0; defenderPlusDie < _diceSides; defenderPlusDie++) { for (int defenderMinusDie = 0; defenderMinusDie < _diceSides; defenderMinusDie++) { // both defenderPlusDie and defenderMinusDie would add +1 to them, // so the +1s would cancel each other int defenseRoll = defenderPlusDie - defenderMinusDie; int totalDefense = defenseRoll + defensiveSkill + shield; result += numberOfAttacks * EstimateWeaponDamage(attackRollResult, defender, totalDefense, armor, maxHP); } } } } } result *= _damageScale; SetEstimatedDamage(attacker, defender, phase, result); return(result); }
/// <summary> /// Calculate mathematical expectation of the attack damage value /// </summary> /// <param name="attackRollResult">Attack roll result object representing the attack</param> /// <param name="defender">Defending unit stack</param> /// <returns>Mathematical expectation of the attack damage value</returns> public int EstimateAttackDamage(AttackRollResult attackRoll, UnitStack defender) { int result = 0; double totalDamage = 0; int maxHP = defender.GetUnitType().GetHitPoints(); for (int attackerPlusDie = 0; attackerPlusDie < _diceSides; attackerPlusDie++) { for (int attackerMinusDie = 0; attackerMinusDie < _diceSides; attackerMinusDie++) { AttackRollResult attackRollResult = CreateAnAttackRollResult(attackRoll.UnitStack, attackRoll.Attack, attackerPlusDie + 1, attackerMinusDie + 1); int defensiveSkill = CalculateDefensiveSkill(attackRollResult.Attack, defender); int shield = CalculateShieldValue(attackRollResult.Attack, defender); int armor = CalculateArmorValue(attackRollResult.Attack, defender, attackRollResult.IsCritical); for (int defenderPlusDie = 0; defenderPlusDie < _diceSides; defenderPlusDie++) { for (int defenderMinusDie = 0; defenderMinusDie < _diceSides; defenderMinusDie++) { // both defenderPlusDie and defenderMinusDie would add +1 to them, // so the +1s would cancel each other int defenseRoll = defenderPlusDie - defenderMinusDie; int totalDefense = defenseRoll + defensiveSkill + shield; totalDamage += EstimateWeaponDamage(attackRollResult, defender, totalDefense, armor, maxHP); } } } } totalDamage *= _damageScale; result = Convert.ToInt32(Math.Floor(totalDamage)); totalDamage -= result; System.Random rando = new System.Random(); if (totalDamage > rando.NextDouble()) { result++; } return(result); }
/// <summary> /// Is the unit stack a valid target for the attack? /// </summary> /// <param name="target">Unit stack which is a potential target for the attack</param> /// <param name="attackRollResult">Attack roll result representing the attack</param> /// <returns>Whether the unit stack is a valid target for the attack</returns> private bool IsValidAttackTarget(UnitStack target, AttackRollResult attackRollResult) { if (_attackers.Contains(target) && _attackers.Contains(attackRollResult.UnitStack)) { FileLogger.Trace("COMBAT", "Both " + attackRollResult.UnitStack.GetUnitType().GetName() + " and " + target.GetUnitType().GetName() + " belong to the attacker"); return(false); } if (_defenders.Contains(target) && _defenders.Contains(attackRollResult.UnitStack)) { FileLogger.Trace("COMBAT", "Both " + attackRollResult.UnitStack.GetUnitType().GetName() + " and " + target.GetUnitType().GetName() + " belong to the defender"); return(false); } if (target.GetTotalQty() == 0) { FileLogger.Trace("COMBAT", "No defenders left in this stack"); return(false); } return(true); }
public void RollAttackSet(AttackSet atkSet, Character ch) { AttackSetResult res = new AttackSetResult(); res.Character = ch; foreach (Attack at in atkSet.WeaponAttacks) { AttackRollResult ares = new AttackRollResult(at); res.Results.Add(ares); } foreach (Attack at in atkSet.NaturalAttacks) { AttackRollResult ares = new AttackRollResult(at); res.Results.Add(ares); } _Results.Insert(0, res); TrimList(); RenderResults(); }
public AttackResult(AttackRollResult result, List <int> rngs, int attackDamage) { Result = result; RandNumbersUsed = rngs; BaseDamage = attackDamage; }
/// <summary> /// Check if the collection contains an attack roll result /// </summary> /// <param name="attackRoll">Attack roll result to check</param> /// <returns>Whether the collection contains this attack roll result</returns> public bool Contains(AttackRollResult attackRoll) { return(_model.Contains(attackRoll)); }
// TODO: handle the situation when one side is eliminated completely (end the combat) public bool ResolveAttack(UnitGroup targetGroup, int index, AttackRollResult attack) { Unit target; if (index > 0) { target = targetGroup.GetUnit(index); if (target == null) { Debug.Log("Invalid attack resolution target!"); return false; } if (target.Quantity <= 0) { Debug.Log("Zero Quantity!"); return false; } } else { target = targetGroup.GetRandomUnit(); } if (attackers.Contains(targetGroup) && attackers.Contains(attack.UnitGroup)) { Debug.Log("Invalid attack resolution target!"); return false; } if (defenders.Contains(targetGroup) && defenders.Contains(attack.UnitGroup)) { Debug.Log("Invalid attack resolution target!"); return false; } int defenseRoll = Dice.RollDie(10) + Dice.RollDie(10) + target.UnitType.Defense; if ((useCriticals && attack.IsCritical) || (attack.Attack > defenseRoll)) { Debug.Log(attack.Attack.ToString() + " attack vs " + defenseRoll + " defense: hit!"); int damage = attack.FullDamage; if (!attack.UnitGroup.UnitType.Attack.IsAP) { damage -= target.UnitType.Armor; } Debug.Log(attack.FullDamage.ToString() + " damage vs " + target.UnitType.Armor + " armor, result = " + damage); if (damage > 0) { target.Quantity -= 1; int newHP = target.CurrentHealth - damage; Debug.Log("HP: " + target.CurrentHealth + " - " + damage + " = " + newHP); if (newHP > 0) { Unit reinforced = targetGroup.GetUnitWithHealth(newHP); if (reinforced == null) { Debug.Log("Attempted to reinforce a null unit"); return false; } reinforced.Quantity += 1; } } } else { Debug.Log(attack.Attack.ToString() + " attack vs " + defenseRoll + " defense: miss."); } if (attackerRollResults.Contains(attack)) { attackerRollResults.Remove(attack); } else { defenderRollResults.Remove(attack); } /* if (attackerRollResults.Count == 0 && defenderRollResults.Count == 0) { if (CurrentPhase == attackPhase.CHARGE) { CurrentPhase = attackPhase.MELEE; } else { CurrentPhase = attackPhase.CHARGE; } } */ return true; }
/// <summary> /// Resolve an attack against the target unit stack /// </summary> /// <param name="target">Target unit stack</param> /// <param name="attackRollResult">Attack roll result representing the attack</param> /// <param name="useEstimates">Whether estimated results will be used or honest rolls will be made</param> /// <returns>Whether the attack was successfully resolved</returns> private bool ResolveAnAttackAgainstTarget(UnitStack target, AttackRollResult attackRollResult, bool useEstimates) { if (!IsValidAttackTarget(target, attackRollResult)) { return(false); } int positiveDieRoll = CombatHelper.Instance.RollDie(); int negativeDieRoll = CombatHelper.Instance.RollDie(); int defenseRoll = positiveDieRoll - negativeDieRoll; int defensiveSkill = CombatHelper.Instance.CalculateDefensiveSkill(attackRollResult.Attack, target); int shield = CombatHelper.Instance.CalculateShieldValue(attackRollResult.Attack, target); AttackResolutionEvent data = new AttackResolutionEvent(attackRollResult, target, positiveDieRoll, negativeDieRoll, defensiveSkill, shield); int totalDefense = defenseRoll + defensiveSkill + shield; int armor = CombatHelper.Instance.CalculateArmorValue(attackRollResult.Attack, target, attackRollResult.IsCritical); data.SetArmor(armor); int damage = 0; if (useEstimates) { damage = CombatHelper.Instance.EstimateAttackDamage(attackRollResult, target); } else { damage = CombatHelper.Instance.CalculateDamage(attackRollResult, target, totalDefense, armor); } data.SetDamage(damage); target.TakeDamage(damage); int totalAttack = attackRollResult.AttackRoll + attackRollResult.AttackSkill; if (useEstimates) { FileLogger.Trace("COMBAT", "Simulation: estimated damage = " + damage); } else { FileLogger.Trace("COMBAT", "Attack: " + totalAttack + ", defense: " + totalDefense + ", weapon damage: " + attackRollResult.FullDamage + ", armor: " + armor + ", resulting damage: " + damage); } bool deleted = false; for (int i = 0; i < _attackerRollResults.Count; i++) { if (_attackerRollResults[i].Contains(attackRollResult)) { bool removed = _attackerRollResults[i].RemoveAttackRollResult(attackRollResult); if (!removed) { FileLogger.Trace("COMBAT", "Failed to remove an attack roll result from a collection!"); } if (_attackerRollResults[i].Count == 0) { _attackerRollResults.RemoveAt(i); } deleted = true; break; } } if (!deleted) { for (int i = 0; i < _defenderRollResults.Count; i++) { if (_defenderRollResults[i].Contains(attackRollResult)) { bool removed = _defenderRollResults[i].RemoveAttackRollResult(attackRollResult); if (!removed) { FileLogger.Trace("COMBAT", "Failed to remove an attack roll result from a collection!"); } if (_defenderRollResults[i].Count == 0) { _defenderRollResults.RemoveAt(i); } break; } } } if (AttackResolved != null) { AttackResolved(this, data); } return(true); }
private AttackRollResult GenerateAttackRollResult(UnitGroup attacker) { bool isCritical = false; int dieRoll = Dice.RollDie(10) + Dice.RollDie(10); int bonusDamage = 0; if (useCriticals && dieRoll >= 18) { isCritical = true; bonusDamage = attacker.UnitType.Attack.RollDamage(); } AttackRollResult result = new AttackRollResult(attacker, dieRoll + attacker.UnitType.Attack.Skill, attacker.UnitType.Attack.RollDamage(), bonusDamage, isCritical); return result; }