private double EstimateUnitDamage(Unit attacker, Unit defender) { Combat.TurnPhase phase = Combat.TurnPhase.MAGIC; double epsilon = 0.05; while (phase < Combat.TurnPhase.CLEANUP) { UnitStack attackerStack = new UnitStack(new Unit(attacker.GetUnitType(), 1), null); UnitStack defenderStack = new UnitStack(new Unit(defender.GetUnitType(), 1), null); double attackerDamage = EstimateStackAttacksDamage(attackerStack, defenderStack, phase); attackerStack = new UnitStack(new Unit(attacker.GetUnitType(), 1), null); defenderStack = new UnitStack(new Unit(defender.GetUnitType(), 1), null); double defenderDamage = EstimateStackAttacksDamage(defenderStack, attackerStack, phase); phase++; // read as "attacker deals significantly less damage than defender // during this phase" if (attackerDamage + epsilon < defenderDamage) { return(0); } // "defender deals significantly less damage than attacker // during this phase" if (attackerDamage > defenderDamage + epsilon) { return(attackerDamage); } } return(0); }
/// <summary> /// Select a target for the spell from a list of candidates and cast the spell on it /// </summary> /// <param name="potentialTargets">The list of potential targets</param> public override void CastOn(List <UnitStack> potentialTargets) { if (potentialTargets.Count > 0) { // note: the spell doesn't work on holy units UnitStack toTarget = potentialTargets[0]; int qty = toTarget.GetTotalQty(); int candidateQty; for (int i = 1; i < potentialTargets.Count; i++) { candidateQty = potentialTargets[i].GetTotalQty(); if (toTarget.IsAffectedBy(this) || toTarget.GetUnitType().IsHoly() || (candidateQty > qty && !potentialTargets[i].IsAffectedBy(this) && !potentialTargets[i].GetUnitType().IsHoly())) { toTarget = potentialTargets[i]; qty = candidateQty; } } if (!toTarget.IsAffectedBy(this) && !toTarget.GetUnitType().IsHoly()) { toTarget.AffectBySpell(this); } } }
/// <summary> /// Select a unit stack from a list of existing ones and make an illusory clone of it /// </summary> /// <param name="existing">The list of existing unit stacks</param> public override UnitStack Create(List <UnitStack> existing) { if (existing.Count > 0) { UnitStack toClone = existing[0]; int qty = toClone.GetTotalQty(); int candidateQty; for (int i = 1; i < existing.Count; i++) { candidateQty = existing[i].GetTotalQty(); if (candidateQty > qty) { toClone = existing[i]; qty = candidateQty; } } UnitType illusion = new UnitType(toClone.GetUnitType()); illusion.SetShield(0); illusion.SetArmor(0); illusion.SetHitPoints(1); illusion.AddAttackQuality(AttackData.Quality.ILLUSORY); Unit mirrorImage = new Unit(illusion, toClone.GetTotalQty()); UnitStack stack = new UnitStack(mirrorImage, toClone.GetProvinceToRetreat()); stack.AffectBySpell(this); return(stack); } return(null); }
/// <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 CreateUnitStackViewsForSide(GameObject[] spawnPoints) { bool areProcessingAttackers = spawnPoints == _attackersSpawnPoints; List <UnitStack> stacks = _model.GetUnitStacks(areProcessingAttackers); Dictionary <UnitStack, UnitStackView> stackViews = areProcessingAttackers ? _attackerStackViews : _defenderStackViews; int offset = areProcessingAttackers ? _attackerStackViewsOffset : _defenderStackViewsOffset; int count = Mathf.Min(stacks.Count - offset, spawnPoints.Length); for (int i = 0; i < count; i++) { UnitStack currentStack = stacks[i + offset]; UnitStackView view = (UnitStackView)Instantiate(_unitStackViewPrefab, spawnPoints[i].transform.position, Quaternion.identity); view.transform.parent = spawnPoints[i].transform; view.SetModel(currentStack); if (currentStack.GetProvinceToRetreat().GetOwnersFaction().IsPC()) { view.StackSelected += OnPlayerSelectedDefendingUnitStack; } view.ExplosionAnimationCompleted += OnExplosionAnimationCompleted; view.StackInspected += OnStackInspected; view.StackInspectionEnded += OnUnitTypeInspectionEnded; stackViews[currentStack] = view; } }
/// <summary> /// Create a new unit stack with one of the chaos spawn version /// </summary> /// <param name="summoners">List of unit stacks capable of summoning chaos spawns</param> /// <returns>Unit stack created by the spell</returns> public override UnitStack Create(List <UnitStack> summoners) { if (_totalWeight == 0) { return(null); } int randomNumber = Dice.RollDie(_totalWeight) - 1; UnitType unitType = null; for (int i = 0; i < _convertedOutcomes.Count; i++) { if (randomNumber <= _convertedOutcomes[i].Value) { unitType = _convertedOutcomes[i].Key; break; } else { randomNumber -= _convertedOutcomes[i].Value; } } if (unitType != null) { UnitStack stack = new UnitStack(new Unit(unitType, 1), null); stack.AffectBySpell(this); FileLogger.Trace("SPAWN", unitType.GetName() + " is spawned."); return(stack); } return(null); }
/// <summary> /// Calculate defender's armor value against the attack /// </summary> /// <param name="attack">Attack under consideration</param> /// <param name="defender">Defending unit stack</param> /// <param name="isCritical">Whether the attack was a critical success</param> /// <returns>Defender's armor value modified by attack's qualities</returns> public int CalculateArmorValue(Attack attack, UnitStack defender, bool isCritical) { // AP (armor piercing), GUNPOWDER and LIGHTNING attacks halve armor rating // FIRE attacks ignore armor // Critical hits ignore armor // STONE SKIN spell adds 2 to the armor value after the above rules were applied // ILLUSORY attacks ignore armor and ignore effects of STONE SKIN spell int result = defender.GetUnitType().GetArmor(); if (attack.IsArmorPiercing() || attack.IsGunpowderAttack() || attack.HasQuality(AttackData.Quality.LIGHTNING)) { result /= 2; } if (isCritical && IsUsingCriticals()) { result = 0; } if (attack.IsFireAttack()) { result = 0; } if (defender.IsAffectedBy("Stone Skin")) { result += 2; } if (attack.HasQuality(AttackData.Quality.ILLUSORY)) { result = 0; } return(result); }
private double GetEstimatedDamage(UnitStack attacker, UnitStack defender, Combat.TurnPhase phase) { int intPhase = (int)phase; if (attacker.GetAffectingSpells().Count == 0 && defender.GetAffectingSpells().Count == 0) { int attackerTypeId = attacker.GetUnitType().GetId(); if (_expectedDamage.ContainsKey(attackerTypeId)) { Dictionary <int, Dictionary <int, double> > attackerRecords = _expectedDamage[attackerTypeId]; int defenderTypeId = defender.GetUnitType().GetId(); if (attackerRecords.ContainsKey(defenderTypeId)) { Dictionary <int, double> defenderRecords = attackerRecords[defenderTypeId]; if (defenderRecords.ContainsKey(intPhase)) { return(defenderRecords[intPhase]); } } } } // "not found" return(-1); }
public BattleUnitStack(UnitStack stack) { Unit = stack.Unit; LastStackHp = stack.Unit.HitPoints; Number = stack.Number; Effects = new List <Effect>(); _startNumber = stack.Number; }
public void UnitStack_ValidArgs_Creates() { var unit = new Unit("Test", 100, 10, 5, (7, 13), 1.0); var stack = new UnitStack(unit, 100); Assert.IsNotNull(stack); Assert.IsInstanceOfType(stack, typeof(UnitStack)); }
public void UnitStack_ValidArgs_Values() { var unit = new Unit("Test", 100, 10, 5, (7, 13), 1.0); var stack = new UnitStack(unit, 100); Assert.AreEqual(stack.Type, unit); Assert.AreEqual(stack.Count, (uint)100); }
//battle stuff public BattleUnitStack(UnitStack CrusadeStack) { BasicAmount = CrusadeStack.Amount; // max possible amount of minions this.minion = CrusadeStack.minion; curHitPoints = CrusadeStack.minion.HitPoints * CrusadeStack.Amount; curAttack = CrusadeStack.minion.Attack; curDefence = CrusadeStack.minion.Defence; curInitiative = CrusadeStack.minion.Initiative; queueInitiative = curInitiative; }
/// <summary> /// Copy constructor /// </summary> /// <param name="stack">The unit stack to clone</param> public UnitStack(UnitStack stack) { _baseUnit = new Unit(stack.GetBaseUnit()); _provinceToRetreat = stack.GetProvinceToRetreat(); _hitPointsDistribution = new int[_baseUnit.GetUnitType().GetHitPoints()]; for (int i = 0; i < _hitPointsDistribution.Length; i++) { _hitPointsDistribution[i] = stack._hitPointsDistribution[i]; } _affectingSpells = new List <Spell>(); }
private static (UnitSpecification specification, UnitStack stack) SetUp(uint count) { var unitSpecification = new SlaveSpecification(); var stackSpecification = new UnitStackSpecification(unitSpecification, count); var stack = new UnitStack(stackSpecification); var resources = count * unitSpecification.HireCost; stack.Consume(resources); return(unitSpecification, stack); }
public void SetModel(UnitStack unitStack) { _model = unitStack; _model.WoundCheckMade += OnWoundCheckMade; _unitImage.material.mainTexture = SpriteCollectionManager.GetTextureByName(_model.GetUnitType().GetName()); _mouseListener.MouseClickDetected += OnStackSelected; _mouseOverListener.MouseOverDetected += OnStackInspected; _mouseOverListener.MouseExitDetected += OnStackInspectionEnd; _explosion.gameObject.SetActive(false); _healing.gameObject.SetActive(false); UpdateView(); }
private void DestroyUnitStackView(UnitStack stack) { FileLogger.Trace("VIEW", "Destroying " + stack.GetUnitType().GetName() + "'s stack view"); bool isAttackerStackDestroyed = _attackerStackViews.ContainsKey(stack); Dictionary <UnitStack, UnitStackView> stackViews = isAttackerStackDestroyed ? _attackerStackViews : _defenderStackViews; stackViews[stack].ExplosionAnimationCompleted -= OnExplosionAnimationCompleted; stackViews[stack].StackSelected -= OnPlayerSelectedDefendingUnitStack; stackViews[stack].StackInspected -= OnStackInspected; stackViews[stack].StackInspectionEnded -= OnUnitTypeInspectionEnded; Destroy(stackViews[stack].gameObject); stackViews.Remove(stack); }
/// <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> /// Select the unit stack which is going to soak enemy's attacks /// </summary> /// <param name="stacks">Available unit stacks</param> /// <param name="attacker">Attacking enemy unit stack</param> /// <param name="phase">Turn phase of the combat</param> /// <returns>Unit stack selected</returns> public UnitStack SelectDefendingUnitStack(List <UnitStack> stacks, UnitStack attacker, Combat.TurnPhase phase) { switch (_level) { case 1: // general-level AI takes into account successful defense odds // and selects the stack that has the lowest exectated replacement cost return(SelectMinReplacementCostUnit(stacks, attacker, phase)); default: // captain-level AI selects the cheapest unit stack return(SelectCheapestUnit(stacks)); } }
/// <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> /// Handles defender selection and related animations. /// </summary> private void ProcessPhaseWithAttacks() { FileLogger.Trace("COMBAT VIEW", "ProcessPhaseWithAttacks"); // unit stacks could be created or destroyed - update the stack views UpdateUnitStackViews(); if (!_model.AreThereUnresolvedAttacks()) { _skipPhase = false; ProcessCombatTurn(); } else { // show all attacks UpdateAttackViews(); AttackRollResultsCollection currentAttackBatch = _model.SelectAttackRollResultsCollection(); if (currentAttackBatch != null) { FileLogger.Trace("COMBAT", "Selected attacks by " + currentAttackBatch.GetUnitStack().GetUnitType().GetName()); if (currentAttackBatch.GetUnitStack() != _currentAttacker) { SetNewStackForCurrentAttackView(currentAttackBatch.GetUnitStack()); // a new defender will be selected against a new attack _currentDefender = null; _skipStack = false; FileLogger.Trace("COMBAT VIEW", "ProcessPhaseWithAttacks: Setting skip stack to false"); } if (!_model.IsPlayerTurn()) { SelectAnNPCStackAsTarget(); } else { // if it's the player's turn // and he already selected a defending stack, resolve the attack if (_currentDefender != null && _currentDefender.GetTotalQty() > 0) { FileLogger.Trace("COMBAT", "Resolving an attack against " + _currentDefender.GetUnitType().GetName()); _model.ResolveCurrentAttack(false); } // else wait until the player will click on a stack view } } // else panic - there should be no case when there are unresolved attacks // but the model can't return an attack collection } }
/// <summary> /// Stops "object selected" animation and probably destroys the current attacks view. /// Starts "object selected" animation on the new one. /// </summary> private void SetNewStackForCurrentAttackView(UnitStack stack) { // handle the attacks view related to the current attacker if (_currentAttacker != null && _attackViews.ContainsKey(_currentAttacker)) { // stop blinking _attackViews[_currentAttacker].GetComponent <Selectable>().IsSelected = false; // most likely, we exhausted attacks in this bunch - delete the view if (_attackViews[_currentAttacker].GetModel().Count == 0) { Destroy(_attackViews[_currentAttacker].gameObject); } } _currentAttacker = stack; // start blinking _attackViews[_currentAttacker].GetComponent <Selectable>().IsSelected = true; }
/// <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> /// Class constructor /// </summary> /// <param name="unitStack">Attacking unit stack</param> /// <param name="attack">Attack object</param> /// <param name="positiveDieRoll">Value of the attack's positive roll</param> /// <param name="negativeDieRoll">Value of the attack's negative roll</param> /// <param name="attackSkill">Attack skill value</param> /// <param name="baseDamage">Based damage value</param> /// <param name="bonusDamage">Bonus damage value</param> /// <param name="isCritical">Whether the attack is a critical hit</param> public AttackRollResult(UnitStack unitStack, Attack attack, int positiveDieRoll, int negativeDieRoll, int attackSkill, int baseDamage, int bonusDamage, bool isCritical) { UnitStack = unitStack; Attack = attack; PositiveDieRoll = positiveDieRoll; NegativeDieRoll = negativeDieRoll; AttackSkill = attackSkill; BaseDamage = baseDamage; BonusDamage = bonusDamage; IsCritical = isCritical; }
/// <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> /// Cleans up results of resolution /// </summary> private void ProcessAttackResolutionResults() { FileLogger.Trace("COMBAT VIEW", "ProcessAttackResolutionResults"); if (_attackViews[_currentAttacker].GetModel().Count == 0) { FileLogger.Trace("COMBAT VIEW", "Destroying " + _currentAttacker.GetUnitType().GetName() + "'s attacks view"); Destroy(_attackViews[_currentAttacker].gameObject); _attackViews.Remove(_currentAttacker); _currentAttacker = null; } else { FileLogger.Trace("COMBAT VIEW", "Updating " + _currentAttacker.GetUnitType().GetName() + "'s attacks view"); _attackViews[_currentAttacker].UpdateView(); } if (_currentDefender.GetTotalQty() == 0) { DestroyUnitStackView(_currentDefender); UpdateUnitStackViews(); _currentDefender = null; } else { FileLogger.Trace("COMBAT VIEW", "Updating " + _currentDefender.GetUnitType().GetName() + "'s stack view"); Dictionary <UnitStack, UnitStackView> stackViews = _attackerStackViews.ContainsKey(_currentDefender) ? _attackerStackViews : _defenderStackViews; stackViews[_currentDefender].UpdateView(); } // if the attacker is reset, reset the defender, too // because a different defender will likely to be selected // against a new attack if (_currentAttacker == null) { _currentDefender = null; } if (_attackResolutionView.gameObject.activeSelf) { _attackResolutionView.gameObject.SetActive(false); } _model.PerformEndOfCombatCheck(); if (!_model.IsCombatOver()) { ProcessCombatTurn(); } }
/// <summary> /// Calculate defender's skill value against the attack /// </summary> /// <param name="attack">Attack under consideration</param> /// <param name="defender">Defending unit stack</param> /// <returns>Defensive skill modified by attack's qualities</returns> public int CalculateDefensiveSkill(Attack attack, UnitStack defender) { // RANGED attacks ignore defense skill // SKIRMISH or MAGIC attacks halve it int result = defender.GetUnitType().GetDefense(); if (attack.IsRangedAttack()) { result = 0; } else { if (attack.IsSkirmishAttack() || attack.HasQuality(AttackData.Quality.MAGIC)) { result /= 2; } } return(result); }
/// <summary> /// Select a target for the spell from a list of candidates and cast the spell on it /// </summary> /// <param name="potentialTargets">The list of potential targets</param> public override void CastOn(List <UnitStack> potentialTargets) { if (potentialTargets.Count > 0) { UnitStack toTarget = potentialTargets[0]; int qty = toTarget.GetTotalQty(); int candidateQty; for (int i = 1; i < potentialTargets.Count; i++) { candidateQty = potentialTargets[i].GetTotalQty(); if (toTarget.IsAffectedBy(this) || (candidateQty > qty && !potentialTargets[i].IsAffectedBy(this))) { toTarget = potentialTargets[i]; qty = candidateQty; } } toTarget.AffectBySpell(this); } }
private void OnPlayerSelectedDefendingUnitStack(object sender, EventArgs args) { if (_model.IsPlayerTurn()) { UnitStack stack = ((UnitStackView)sender).GetModel(); if (_attackViews.Count > 0) { _currentDefender = stack; _model.SetDefendingStack(stack); FileLogger.Trace("COMBAT VIEW", "Player selected " + _currentDefender.GetUnitType().GetName() + " as a defender"); Dictionary <UnitStack, UnitStackView> stackViews = _attackerStackViews.ContainsKey(_currentDefender) ? _attackerStackViews : _defenderStackViews; stackViews[_currentDefender].PlayExplosionAnimation(); } else { FileLogger.Error("COMBAT", "A defending unit stack is chosen, but there are no attacks to resolve."); } } }
/// <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); }