context: #region
  
  using System;
  using System.Linq;
  using Game.Battle.CombatObjects;
  using Game.Battle.StatsModCalculator;
  using Game.Data;
  using Game.Data.BarbarianTribe;
  using Game.Data.Stats;
  using Game.Data.Stronghold;
  using Game.Data.Troop;
  using Game.Setup;
  
  #endregion
  
  namespace Game.Battle
  {
      public class BattleFormulas : IBattleFormulas
      {
          private readonly UnitFactory unitFactory;
  
          private readonly IObjectTypeFactory objectTypeFactory;
  
          private readonly UnitModFactory unitModFactory;
  
          public BattleFormulas(UnitModFactory unitModFactory, UnitFactory unitFactory, IObjectTypeFactory objectTypeFactory)
          {
              this.unitModFactory = unitModFactory;
              this.unitFactory = unitFactory;
              this.objectTypeFactory = objectTypeFactory;
          }        
  
          public virtual decimal GetDmgWithMissChance(int attackersUpkeep, int defendersUpkeep, decimal dmg, IBattleRandom random)
          {
              double delta = Math.Max(0, (double)attackersUpkeep / defendersUpkeep);
              double effectiveness = attackersUpkeep > 200 ? 1 : (double)attackersUpkeep / 200;
              
              int missChance;
              if (delta < 1)
              {
                  missChance = (int)(0 * effectiveness);
              }
              else if (delta < 1.25)
              {
                  missChance = (int)(10 * effectiveness);
              }
              else if (delta < 1.5)
              {
                  missChance = (int)(17 * effectiveness);
              }
              else if (delta < 2)
              {
                  missChance = (int)(22 * effectiveness);
              }
              else if (delta < 3.5)
              {
                  missChance = (int)(30 * effectiveness);
              }
              else if (delta < 5)
              {
                  missChance = (int)(40 * effectiveness);
              }
              else if (delta < 7)
              {
                  missChance = (int)(48 * effectiveness);
              }
              else if (delta < 10)
              {
                  missChance = (int)(55 * effectiveness);
              }
              else
              {
                  missChance = (int)(60 * effectiveness);
              }
  
              var rand = (int)(random.NextDouble() * 100);
  
              if (missChance <= 0 || rand > missChance)
              {
                  return dmg;
              }
              
              return dmg / 2m;
          }
  
          public virtual int GetUnitsPerStructure(byte level)
          {
              int[] units = new[] {20, 20, 23, 28, 34, 39, 45, 52, 59, 67, 76, 85, 95, 106, 117, 130};
  
              return units[Math.Min(level, units.Length - 1)];
          }
  
          public virtual decimal GetAttackScore(ICombatObject attacker, ICombatObject target, uint round)
          {
              decimal attackBonus = attacker.AttackBonus(target);
              decimal modifier = (decimal)GetDmgModifier(attacker, target, round);
  
              return modifier * (1 + attackBonus);
          }
  
          public virtual decimal GetAttackerDmgToDefender(ICombatObject attacker, ICombatObject target, uint round)
          {
              decimal atk = attacker.Stats.Atk;
              decimal rawDmg = (atk * attacker.Count);
              decimal attackBonus = attacker.AttackBonus(target);
              decimal defenseBonus = target.DefenseBonus(attacker);
              decimal modifier = (decimal)GetDmgModifier(attacker, target, round);
              rawDmg = modifier * rawDmg * (1 + attackBonus) / (1 + defenseBonus);
  
              return rawDmg > ushort.MaxValue ? ushort.MaxValue : rawDmg;
          }
  
          public virtual double GetDmgModifier(ICombatObject attacker, ICombatObject target, uint round)
          {
              switch(attacker.Stats.Base.Weapon)
              {
                  case WeaponType.Tower:
                      switch(target.Stats.Base.Armor)
                      {
                          case ArmorType.Building1:
                              return round < 5 ? unitModFactory.GetModifier(1, 1) * .2 : unitModFactory.GetModifier(1, 1);
                          case ArmorType.Building2:
                              return round < 5 ? unitModFactory.GetModifier(1, 2) * .2 : unitModFactory.GetModifier(1, 2);;
                          case ArmorType.Building3:
                              return unitModFactory.GetModifier(1, 3);
                          case ArmorType.Gate:
                              return unitModFactory.GetModifier(1, 4);
                          default:
                              return unitModFactory.GetModifier(1, target.Type);
                      }
                  case WeaponType.Cannon:
                      switch(target.Stats.Base.Armor)
                      {
                          case ArmorType.Building1:
                              return round < 5 ? unitModFactory.GetModifier(2, 1) * .2 : unitModFactory.GetModifier(2, 1);
                          case ArmorType.Building2:
                              return round < 5 ? unitModFactory.GetModifier(2, 2) * .2 : unitModFactory.GetModifier(2, 2); ;
                          case ArmorType.Building3:
                              return unitModFactory.GetModifier(2, 3);
                          case ArmorType.Gate:
                              return unitModFactory.GetModifier(2, 4);
                          default:
                              return unitModFactory.GetModifier(2, target.Type);
                      }
                  case WeaponType.Barricade:
                      switch(target.Stats.Base.Armor)
                      {
                          case ArmorType.Building1:
                              return round < 5 ? unitModFactory.GetModifier(3, 1) * .2 : unitModFactory.GetModifier(3, 1);
                          case ArmorType.Building2:
                              return round < 5 ? unitModFactory.GetModifier(3, 2) * .2 : unitModFactory.GetModifier(3, 2); ;
                          case ArmorType.Building3:
                              return unitModFactory.GetModifier(3, 3);
                          case ArmorType.Gate:
                              return unitModFactory.GetModifier(3, 4);
                          default:
                              return unitModFactory.GetModifier(3, target.Type);
                      }
                  default:
                      switch(target.Stats.Base.Armor)
                      {
                          case ArmorType.Building1:
                              return round < 5 ? unitModFactory.GetModifier(attacker.Type, 1) * .2 : unitModFactory.GetModifier(attacker.Type, 1);
                          case ArmorType.Building2:
                              return round < 5 ? unitModFactory.GetModifier(attacker.Type, 2) * .2 : unitModFactory.GetModifier(attacker.Type, 2); ;
                          case ArmorType.Building3:
                              return unitModFactory.GetModifier(attacker.Type, 3);
                          case ArmorType.Gate:
                              return unitModFactory.GetModifier(attacker.Type, 4);
                          default:
                              return unitModFactory.GetModifier(attacker.Type, target.Type);
                      }
              }
          }
  
          public virtual int GetLootPerRoundForCity(ICity city)
          {
              double roundsRequired = Math.Max(5,
                                               Config.battle_loot_till_full -
                                               city.Technologies.GetEffects(EffectCode.LootLoadMod)
                                                   .DefaultIfEmpty()
                                                   .Sum(x => x == null ? 0 : (int)x.Value[0]));
              return (int)Math.Ceiling(100 / roundsRequired);
          }
  
          public virtual Resource GetRewardResource(ICombatObject attacker, ICombatObject defender)
          {
              // calculate total carry, if 10 units with 10 carry, which should be 100
              int totalCarry = attacker.Stats.Carry * attacker.Count;
  
              // if carry is 100 and % is 5, then count = 5;
              int lootPerRound = attacker.LootPerRound();
              int count = Math.Max(1, totalCarry * lootPerRound / 100);
  
              // spaceleft is the maxcarry.
              var spaceLeft = new Resource(totalCarry / 1,
                                           totalCarry / 2,
                                           totalCarry / Config.battle_loot_resource_iron_ratio,
                                           totalCarry / 1,
                                           totalCarry / Config.battle_loot_resource_labor_ratio);
  
              // maxcarry minus current resource is the empty space left.
              spaceLeft.Subtract(attacker.Loot);
  
              // returning lesser value between the count and the empty space.
              return new Resource(Math.Min(count / 1, spaceLeft.Crop),
                                  Math.Min(count / 2, spaceLeft.Gold),
                                  Math.Min(count / Config.battle_loot_resource_iron_ratio, spaceLeft.Iron),
                                  Math.Min(count / 1, spaceLeft.Wood));
          }
  
          public virtual short GetStamina(ITroopStub stub, ICity city)
          {
              return (short)Config.battle_stamina_initial;
          }
  
          public virtual short GetStamina(ITroopStub stub, IStronghold targetStronghold)
          {
              return (short)(Config.battle_stamina_initial * Config.battle_stamina_gate_multiplier);
          }
  
          public virtual short GetStamina(ITroopStub stub, IBarbarianTribe barbarianTribe)
          {
              return (short)Config.battle_stamina_initial;
          }
  
          public virtual ushort GetStaminaReinforced(ICity city, ushort stamina, uint round)
          {
              return stamina;
          }
  
          public virtual ushort GetStaminaRoundEnded(ICity city, ushort stamina, uint round)
          {
  
gt:             if (stamina == 0)