public static List <DamagePredictRecord> getWeaponDamagePredict(AbstractActor unit, ICombatant target, Weapon weapon)
        {
            List <DamagePredictRecord> result = new List <DamagePredictRecord>();
            ExtWeaponDef extWeapon            = CustomAmmoCategories.getExtWeaponDef(weapon.defId);

            if (extWeapon.Modes.Count < 1)
            {
                CustomAmmoCategoriesLog.Log.LogWrite("WARNING! " + weapon.defId + " has no modes. Even base mode. This means something is very very wrong\n", true);
                return(result);
            }
            string currentMode = extWeapon.baseModeId;

            if (CustomAmmoCategories.checkExistance(weapon.StatCollection, CustomAmmoCategories.WeaponModeStatisticName) == true)
            {
                currentMode = weapon.StatCollection.GetStatistic(CustomAmmoCategories.WeaponModeStatisticName).Value <string>();
            }
            string currentAmmo = "";

            if (CustomAmmoCategories.checkExistance(weapon.StatCollection, CustomAmmoCategories.AmmoIdStatName) == true)
            {
                currentAmmo = weapon.StatCollection.GetStatistic(CustomAmmoCategories.AmmoIdStatName).Value <string>();
            }
            foreach (var mode in extWeapon.Modes)
            {
                HashSet <string> ammos        = CustomAmmoCategories.getWeaponAvaibleAmmoForMode(weapon, mode.Value.Id);
                List <int>       hitLocations = null;
                float            AverageArmor = float.NaN;
                foreach (var ammo in ammos)
                {
                    DamagePredictRecord record = new DamagePredictRecord(ammo, mode.Value.Id);
                    CustomAmmoCategories.fillWeaponPredictRecord(ref record, unit, target, weapon, ref hitLocations, ref AverageArmor);
                    result.Add(record);
                }
            }
            CustomAmmoCategories.applyWeaponAmmoMode(weapon, currentMode, currentAmmo);
            return(result);
        }
        public static void ChooseBestWeaponForTarget(AbstractActor unit, ICombatant target, bool isStationary)
        {
            Stopwatch stopWatch = new Stopwatch();
            Dictionary <string, Weapon> weapons = new Dictionary <string, Weapon>();
            Dictionary <string, List <DamagePredictRecord> > damagePredict = new Dictionary <string, List <DamagePredictRecord> >();

            foreach (Weapon weapon in unit.Weapons)
            {
                weapons.Add(weapon.uid, weapon);
                damagePredict.Add(weapon.uid, CustomAmmoCategories.getWeaponDamagePredict(unit, target, weapon));
            }
            foreach (var weapon in weapons)
            {
                CustomAmmoCategoriesLog.Log.LogWrite("Weapon " + weapon.Key + " " + weapon.Value.defId + "\n");
                foreach (var fireType in damagePredict[weapon.Key])
                {
                    CustomAmmoCategoriesLog.Log.LogWrite(" mode:" + fireType.Id.modeId + " ammo:" + fireType.Id.ammoId + " heat:" + fireType.HeatDamageCoeff + " dmg:" + fireType.NormDamageCoeff + "\n");
                }
            }
            Mech targetMech = target as Mech;

            if (targetMech != null)
            {
                CustomAmmoCategoriesLog.Log.LogWrite("Try overheat\n");
                float overallPredictHeatDamage = 0f;
                Dictionary <string, int> weaponsWithHeatFireMode = new Dictionary <string, int>();
                foreach (var weapon in weapons)
                {
                    if (damagePredict.ContainsKey(weapon.Key) == false)
                    {
                        CustomAmmoCategoriesLog.Log.LogWrite("WARNING! " + weapon.Value.defId + " has no predict damage record something is very very wrong\n", true);
                        continue;
                    }
                    if (damagePredict[weapon.Key].Count <= 0)
                    {
                        CustomAmmoCategoriesLog.Log.LogWrite("WARNING! " + weapon.Value.defId + " has empty predict damage record something is very very wrong\n", true);
                        continue;
                    }
                    float HeatDamageCoeff  = damagePredict[weapon.Key][0].HeatDamageCoeff;
                    int   heatDamageIndex  = 0;
                    bool  haveDiffHeatMode = false;
                    for (int index = 1; index < damagePredict[weapon.Key].Count; ++index)
                    {
                        DamagePredictRecord fireMode = damagePredict[weapon.Key][index];
                        if (HeatDamageCoeff < fireMode.HeatDamageCoeff)
                        {
                            HeatDamageCoeff = fireMode.HeatDamageCoeff; heatDamageIndex = index; haveDiffHeatMode = true;
                        }
                        ;
                    }
                    overallPredictHeatDamage += damagePredict[weapon.Key][heatDamageIndex].PredictHeatDamage;
                    if (haveDiffHeatMode)
                    {
                        weaponsWithHeatFireMode.Add(weapon.Key, heatDamageIndex);
                    }
                }
                CustomAmmoCategoriesLog.Log.LogWrite(" Current target heat:" + targetMech.CurrentHeat + " predicted:" + overallPredictHeatDamage + "\n");
                if ((targetMech.CurrentHeat + overallPredictHeatDamage) > targetMech.OverheatLevel)
                {
                    CustomAmmoCategoriesLog.Log.LogWrite(" worth it\n");
                    foreach (var weapon in weaponsWithHeatFireMode)
                    {
                        CustomAmmoCategories.applyWeaponAmmoMode(weapons[weapon.Key], damagePredict[weapon.Key][weapon.Value].Id.modeId, damagePredict[weapon.Key][weapon.Value].Id.ammoId);
                        weapons.Remove(weapon.Key);
                        damagePredict.Remove(weapon.Key);
                    }
                }
                else
                {
                    CustomAmmoCategoriesLog.Log.LogWrite(" not worth it\n");
                }
            }
            CustomAmmoCategoriesLog.Log.LogWrite("Normal damage\n");
            foreach (var weapon in weapons)
            {
                CustomAmmoCategoriesLog.Log.LogWrite("Weapon " + weapon.Key + " " + weapon.Value.defId + "\n");
                foreach (var fireType in damagePredict[weapon.Key])
                {
                    CustomAmmoCategoriesLog.Log.LogWrite(" mode:" + fireType.Id.modeId + " ammo:" + fireType.Id.ammoId + " heat:" + fireType.HeatDamageCoeff + " dmg:" + fireType.NormDamageCoeff + "\n");
                }
            }
            foreach (var weapon in weapons)
            {
                if (damagePredict.ContainsKey(weapon.Key) == false)
                {
                    CustomAmmoCategoriesLog.Log.LogWrite("WARNING! " + weapon.Value.defId + " has no predict damage record something is very very wrong\n", true);
                    continue;
                }
                if (damagePredict[weapon.Key].Count <= 0)
                {
                    CustomAmmoCategoriesLog.Log.LogWrite("WARNING! " + weapon.Value.defId + " has empty predict damage record something is very very wrong\n", true);
                    continue;
                }
                float DamageCoeff = damagePredict[weapon.Key][0].HeatDamageCoeff;
                int   DamageIndex = 0;
                for (int index = 1; index < damagePredict[weapon.Key].Count; ++index)
                {
                    DamagePredictRecord fireMode = damagePredict[weapon.Key][index];
                    if (DamageCoeff < fireMode.NormDamageCoeff)
                    {
                        DamageCoeff = fireMode.NormDamageCoeff; DamageIndex = index;
                    }
                    ;
                }
                CustomAmmoCategories.applyWeaponAmmoMode(weapons[weapon.Key], damagePredict[weapon.Key][DamageIndex].Id.modeId, damagePredict[weapon.Key][DamageIndex].Id.ammoId);
            }
        }
        public static void fillWeaponPredictRecord(ref DamagePredictRecord record, AbstractActor unit, ICombatant target, Weapon weapon, ref List <int> hitLocations, ref float AverageArmor)
        {
            CustomAmmoCategoriesLog.Log.LogWrite("fillWeaponPredictRecord " + unit.DisplayName + " target " + target.DisplayName + " weapon " + weapon.defId + "\n");
            CustomAmmoCategories.applyWeaponAmmoMode(weapon, record.Id.modeId, record.Id.ammoId);
            AbstractActor targetActor = target as AbstractActor;

            if (hitLocations == null)
            {
                hitLocations = target.GetPossibleHitLocations(unit);
                foreach (int hitLocation in hitLocations)
                {
                    CustomAmmoCategoriesLog.Log.LogWrite("Hit Location " + hitLocation + "\n");
                }
            }
            if (float.IsNaN(AverageArmor))
            {
                AverageArmor = CustomAmmoCategories.getTargetAvarageArmor(hitLocations, target);
            }
            float toHit = 0;

            if (weapon.WillFireAtTargetFromPosition(target, unit.CurrentPosition) == true)
            {
                toHit = weapon.GetToHitFromPosition(target, 1, unit.CurrentPosition, target.CurrentPosition, true, (targetActor != null) ? targetActor.IsEvasive : false, false);
            }
            if (toHit < Epsilon)
            {
                record.HeatDamageCoeff = 0f; record.NormDamageCoeff = 0f; record.PredictHeatDamage = 0f;
            }
            ;
            float coolDownCoeff    = 1.0f / (1.0f + CustomAmmoCategories.getWeaponCooldown(weapon));
            float jammCoeff        = 1.0f - CustomAmmoCategories.getWeaponFlatJammingChance(weapon);
            float damageJammCoeff  = CustomAmmoCategories.getWeaponDamageOnJamming(weapon) ? 0.5f : 1.0f;
            float damageShotsCount = (float)weapon.ShotsWhenFired;
            float damagePerShot    = weapon.DamagePerShot;
            float heatPerShot      = weapon.HeatDamagePerShot;

            if (weapon.componentDef.ComponentTags.Contains("wr-clustered_shots") || (CustomAmmoCategories.getWeaponDisabledClustering(weapon) == false))
            {
                damageShotsCount *= (float)weapon.ProjectilesPerShot;
            }
            float piercedLocationsCount = (float)CustomAmmoCategories.getWeaponPierceLocations(hitLocations, target, damagePerShot);
            float hitLocationsCount     = (hitLocations.Count > 0) ? (float)hitLocations.Count : 1.0f;
            float clusterCoeff          = 1.0f + ((piercedLocationsCount / hitLocationsCount) * damageShotsCount) * CustomAmmoCategories.Settings.ClusterAIMult;
            float pierceCoeff           = 1.0f;

            if (AverageArmor > damagePerShot)
            {
                pierceCoeff += (damagePerShot / AverageArmor) * CustomAmmoCategories.Settings.PenetrateAIMult;
            }
            record.NormDamageCoeff   = damagePerShot * damageShotsCount * toHit * coolDownCoeff * jammCoeff * damageJammCoeff * clusterCoeff * pierceCoeff;
            record.HeatDamageCoeff   = heatPerShot * damageShotsCount * toHit * jammCoeff * damageJammCoeff;
            record.PredictHeatDamage = heatPerShot * damageShotsCount * toHit;
            CustomAmmoCategoriesLog.Log.LogWrite(" toHit = " + toHit + "\n");
            CustomAmmoCategoriesLog.Log.LogWrite(" coolDownCoeff = " + coolDownCoeff + "\n");
            CustomAmmoCategoriesLog.Log.LogWrite(" jammCoeff = " + jammCoeff + "\n");
            CustomAmmoCategoriesLog.Log.LogWrite(" damageJammCoeff = " + damageJammCoeff + "\n");
            CustomAmmoCategoriesLog.Log.LogWrite(" damageShotsCount = " + damageShotsCount + "\n");
            CustomAmmoCategoriesLog.Log.LogWrite(" damagePerShot = " + damagePerShot + "\n");
            CustomAmmoCategoriesLog.Log.LogWrite(" heatPerShot = " + heatPerShot + "\n");
            CustomAmmoCategoriesLog.Log.LogWrite(" piercedLocationsCount = " + piercedLocationsCount + "\n");
            CustomAmmoCategoriesLog.Log.LogWrite(" hitLocationsCount = " + hitLocationsCount + "\n");
            CustomAmmoCategoriesLog.Log.LogWrite(" AverageArmor = " + AverageArmor + "\n");
            CustomAmmoCategoriesLog.Log.LogWrite(" clusterCoeff = " + clusterCoeff + "\n");
            CustomAmmoCategoriesLog.Log.LogWrite(" pierceCoeff = " + pierceCoeff + "\n");
            CustomAmmoCategoriesLog.Log.LogWrite(" NormDamageCoeff = " + record.NormDamageCoeff + "\n");
            CustomAmmoCategoriesLog.Log.LogWrite(" HeatDamageCoeff = " + record.HeatDamageCoeff + "\n");
        }