private static bool StrikeOnce(MapEvent mapEvent,
                                       IBattleObserver battleObserver,
                                       MapEventSide strikerSide,
                                       MapEventSide strikedSide,
                                       PartyAttackComposition attack,
                                       out float totalDamageDone)
        {
            int strikerNumber = strikerSide.NumRemainingSimulationTroops;
            int strikedNumber = strikedSide.NumRemainingSimulationTroops;

            totalDamageDone = 0;
            if (strikerNumber == 0 || strikedNumber == 0)
            {
                return(true);
            }

            MapEventState mapEventState  = MapEventState.GetMapEventState(mapEvent);
            bool          finishedAnyone = false;

            for (int index = strikedNumber - 1; index >= 0; index--)
            {
                UniqueTroopDescriptor strikerTroopDescriptor = strikerSide.SelectRandomSimulationTroop();
                CharacterObject       strikerTroop           = strikerSide.GetAllocatedTroop(strikerTroopDescriptor);
                PartyBase             strikerTroopParty      = strikerSide.GetAllocatedTroopParty(strikerTroopDescriptor);

                UniqueTroopDescriptor strikedTroopDescriptor = MapEventSideHelper.SelectSimulationTroopAtIndex(strikedSide, index, out List <UniqueTroopDescriptor> strikedTroopList);
                CharacterObject       strikedTroop           = strikedSide.GetAllocatedTroop(strikedTroopDescriptor);
                PartyBase             strikedTroopParty      = strikedSide.GetAllocatedTroopParty(strikedTroopDescriptor);

                // MapEvents.GetSimulatedDamage and CombatSimulationModel.SimulateHit
                if (mapEvent.IsPlayerSimulation && strikedTroopParty == PartyBase.MainParty)
                {
                    float damageMultiplier = Campaign.Current.Models.DifficultyModel.GetPlayerTroopsReceivedDamageMultiplier();
                    attack *= damageMultiplier;
                }
                DamageTypes damageType = (double)MBRandom.RandomFloat < 0.15 ? DamageTypes.Blunt : DamageTypes.Cut;

                bool isFinishingStrike = MapEventSideHelper.ApplySimulationDamageToSelectedTroop(
                    strikedSide, strikedTroop, strikedTroopParty, strikedTroopDescriptor, index, strikedTroopList,
                    attack, damageType, strikerTroopParty, mapEventState, battleObserver, out float damage);
                totalDamageDone += damage;

                strikerSide.ApplySimulatedHitRewardToSelectedTroop(strikedTroop, 0, isFinishingStrike);
                finishedAnyone = finishedAnyone || isFinishingStrike;
            }

            return(finishedAnyone);
        }
 public bool Equals(PartyAttackComposition b)
 {
     return(Infantry.Equals(b.Infantry) && Mounted.Equals(b.Mounted));
 }
        public static bool Prefix(ref bool __result,
                                  MapEvent __instance,
                                  float strikerAdvantage)
        {
            MapEventSide    attackerSide   = __instance.AttackerSide;
            MapEventSide    defenderSide   = __instance.DefenderSide;
            MapEventState   mapEventState  = MapEventState.GetMapEventState(__instance);
            IBattleObserver battleObserver = (IBattleObserver)MapEvent_BattleObserver.GetValue(__instance);

            int attackerNumber = attackerSide.NumRemainingSimulationTroops;
            int defenderNumber = defenderSide.NumRemainingSimulationTroops;

            float strengthOfNumber;

            if (mapEventState.IsSiege)
            {
                strengthOfNumber = SubModule.Settings.Battle_SendAllTroops_SiegeStrengthOfNumber;
                //if (SubModule.Settings.Battle_SendAllTroops_DetailedCombatModel)
                //{
                //    if (mapEventState.GateBreached)
                //    {
                //        strengthOfNumber = (SubModule.Settings.Battle_SendAllTroops_SiegeStrengthOfNumber
                //            + SubModule.Settings.Battle_SendAllTroops_StrengthOfNumber) / 2;
                //    }
                //    else
                //    {
                //        strengthOfNumber = SubModule.Settings.Battle_SendAllTroops_SiegeStrengthOfNumber;
                //    }
                //}
                //else
                //{
                //    strengthOfNumber = SubModule.Settings.Battle_SendAllTroops_SiegeStrengthOfNumber;
                //}
            }
            else
            {
                strengthOfNumber = SubModule.Settings.Battle_SendAllTroops_StrengthOfNumber;
            }
            float battleSpeedMultiplier = DamageMultiplier;

            if (strengthOfNumber != 0.6f)
            {
                // Normalized battle speed to that of 0.6
                double biggerPartyNumber = Math.Max(attackerNumber, defenderNumber);
                battleSpeedMultiplier *= (float)Math.Pow(biggerPartyNumber, 0.6 - strengthOfNumber);
            }

            float attackerNumberPenalty = (float)Math.Pow((double)attackerNumber, strengthOfNumber - 1.0);
            float defenderNumberPenalty = (float)Math.Pow((double)defenderNumber, strengthOfNumber - 1.0);

            PartyAttackComposition attackerTotalAttack = new PartyAttackComposition();
            PartyAttackComposition defenderTotalAttack = new PartyAttackComposition();

            foreach (var party in attackerSide.Parties)
            {
                attackerTotalAttack += mapEventState.MakePartyAttack(party, battleSpeedMultiplier * attackerNumberPenalty / RangedAverageDamagePerHit);
            }
            foreach (var party in defenderSide.Parties)
            {
                defenderTotalAttack += mapEventState.MakePartyAttack(party, battleSpeedMultiplier * defenderNumberPenalty / RangedAverageDamagePerHit);
            }

            float attackerAdvantage = BattleAdvantageModel.PartyBattleAdvantage(attackerSide.LeaderParty);
            float defenderAdvantage = BattleAdvantageModel.PartyBattleAdvantage(defenderSide.LeaderParty);

            PartyAttackComposition attackerDistributedAttack = attackerTotalAttack
                                                               * (battleSpeedMultiplier * attackerNumberPenalty * attackerAdvantage * mapEventState.SettlementPenalty / defenderNumber);
            PartyAttackComposition defenderDistributedAttack = defenderTotalAttack
                                                               * (battleSpeedMultiplier * defenderNumberPenalty * defenderAdvantage / attackerNumber);

            bool finishedAnyone = false;

            finishedAnyone |= StrikeOnce(__instance, battleObserver, attackerSide, defenderSide, attackerDistributedAttack, out float attackerTotalDamageDone);
            finishedAnyone |= StrikeOnce(__instance, battleObserver, defenderSide, attackerSide, defenderDistributedAttack, out float defenderTotalDamageDone);

            // Distribute XP among all living
            int AttackerAverageDamageDone = (int)Math.Round(Math.Min(attackerTotalDamageDone / attackerNumber, 1));

            try
            {
                for (int index = 0; index < attackerSide.NumRemainingSimulationTroops; index++)
                {
                    MapEventSideHelper.SelectSimulationTroopAtIndex(attackerSide, index, out _);
                    attackerSide.ApplySimulatedHitRewardToSelectedTroop(null, AttackerAverageDamageDone, false);
                }
                int DefenderAverageDamageDone = (int)Math.Round(Math.Min(defenderTotalDamageDone / defenderNumber, 1));
                for (int index = 0; index < attackerSide.NumRemainingSimulationTroops; index++)
                {
                    MapEventSideHelper.SelectSimulationTroopAtIndex(attackerSide, index, out _);
                    attackerSide.ApplySimulatedHitRewardToSelectedTroop(null, DefenderAverageDamageDone, false);
                }
            }
            catch (NullReferenceException)
            {
                // CombatXpModel.GetXpFromHit is changed by other mod and not accepting attackedTroop == null
            }

            __result = finishedAnyone;

            return(false);
        }