コード例 #1
0
            private static void Postfix(AbstractActor __instance, string sourceID, int sequenceID, int stackItemID, AttackDirection attackDirection)
            {
                Mod.Log.Trace("AA:RAS:POST entered");

                AttackDirector.AttackSequence attackSequence = __instance.Combat.AttackDirector.GetAttackSequence(sequenceID);
                if (attackSequence != null)
                {
                    if (!attackSequence.GetAttackDidDamage(__instance.GUID))
                    {
                        return;
                    }
                    List <Effect> list = __instance.Combat.EffectManager
                                         .GetAllEffectsTargeting(__instance)
                                         .FindAll((Effect x) => x.EffectData.targetingData.effectTriggerType == EffectTriggerType.OnDamaged);

                    for (int i = 0; i < list.Count; i++)
                    {
                        list[i].OnEffectTakeDamage(attackSequence.attacker, __instance);
                    }

                    if (attackSequence.isMelee)
                    {
                        int value = attackSequence.attacker.StatCollection.GetValue <int>(ModStats.MeleeHitPushBackPhases);
                        if (value > 0)
                        {
                            for (int j = 0; j < value; j++)
                            {
                                __instance.ForceUnitOnePhaseDown(sourceID, stackItemID, false);
                            }
                        }
                    }
                }
            }
コード例 #2
0
            static void Postfix(AbstractActor __instance, string sourceID, int sequenceID, int stackItemID, AttackDirection attackDirection)
            {
                try
                {
                    AttackDirector.AttackSequence attackSequence = __instance.Combat.AttackDirector.GetAttackSequence(sequenceID);
                    if (attackSequence != null && attackSequence.GetAttackDidDamage(__instance.GUID))
                    {
                        List <Effect> list = __instance.Combat.EffectManager.GetAllEffectsTargeting(__instance).FindAll((Effect x) => x.EffectData.targetingData.effectTriggerType == EffectTriggerType.OnDamaged);
                        for (int i = 0; i < list.Count; i++)
                        {
                            list[i].OnEffectTakeDamage(attackSequence.attacker, __instance);
                        }
                        if (attackSequence.isMelee)
                        {
                            int value = attackSequence.attacker.StatCollection.GetValue <int>("MeleeHitPushBackPhases");
                            if (value > 0)
                            {
                                for (int j = 0; j < value; j++)
                                {
                                    __instance.ForceUnitOnePhaseDown(sourceID, stackItemID, false);
                                }
                            }
                        }
                    }

                    // Make sustainable evasion removable if many shots are directed at target? Or if any location was destroyed?
                    Logger.Info("[AbstractActor_ResolveAttackSequence_POSTFIX] attackSequence.allSelectedWeapons.Count: " + attackSequence.allSelectedWeapons.Count.ToString());
                    Logger.Info("[AbstractActor_ResolveAttackSequence_POSTFIX] attackSequence.attackTotalShotsFired: " + attackSequence.attackTotalShotsFired);
                    //Logger.Info("[AbstractActor_ResolveAttackSequence_POSTFIX] attackSequence.GetAttackDestroyedAnyLocation: " + attackSequence.GetAttackDestroyedAnyLocation(__instance.GUID));

                    int evasivePipsCurrent = __instance.EvasivePipsCurrent;
                    //BEN: Patch
                    if (Fields.LoosePip || attackSequence.isMelee)
                    {
                        __instance.ConsumeEvasivePip(true);
                        Fields.LoosePip = false;
                    }
                    //:NEB
                    int evasivePipsCurrent2 = __instance.EvasivePipsCurrent;
                    if (evasivePipsCurrent2 < evasivePipsCurrent && !__instance.IsDead && !__instance.IsFlaggedForDeath)
                    {
                        __instance.Combat.MessageCenter.PublishMessage(new FloatieMessage(__instance.GUID, __instance.GUID, "-1 EVASION", FloatieMessage.MessageNature.Debuff));
                    }
                    //BEN: Patch
                    else if (evasivePipsCurrent2 > 0 && !__instance.IsDead && !__instance.IsFlaggedForDeath)
                    {
                        __instance.Combat.MessageCenter.PublishMessage(new FloatieMessage(__instance.GUID, __instance.GUID, "SUSTAINED EVASION", FloatieMessage.MessageNature.Neutral));
                    }
                    //:NEB
                }
                catch (Exception e)
                {
                    Logger.Error(e);
                }
            }
コード例 #3
0
        /// <summary>
        ///     returns true if 10% armor damage was incurred or any structure damage
        /// </summary>
        /// <param name="attackSequence"></param>
        /// <returns></returns>
        private static bool SufficientDamageWasDone(AttackDirector.AttackSequence attackSequence)
        {
            if (attackSequence == null)
            {
                return(false);
            }

            var id = attackSequence.chosenTarget.GUID;

            if (!attackSequence.GetAttackDidDamage(id))
            {
                LogDebug("No damage");
                return(false);
            }

            var previousArmor     = Patches.mechArmorBeforeAttack;
            var previousStructure = Patches.mechStructureBeforeAttack;

            LogDebug($"Damage >>> A: {attackSequence.GetArmorDamageDealt(id):#.###}" +
                     $" S: {attackSequence.GetStructureDamageDealt(id):#.###}" +
                     $" ({(attackSequence.GetArmorDamageDealt(id) + attackSequence.GetStructureDamageDealt(id)) / (previousArmor + previousStructure) * 100:#.##}%)  H: {Patches.heatDamage}");

            if (attackSequence.GetStructureDamageDealt(id) >= modSettings.MinimumStructureDamageRequired)
            {
                LogDebug("Structure damage requires panic save");
                return(true);
            }

            float heatTaken = 0;

            if (attackSequence.chosenTarget is Mech defender)
            {
                heatTaken = defender.CurrentHeat - Patches.mechHeatBeforeAttack;
                // LogDebug($"B {Patches.mechHeatBeforeAttack} A {defender.CurrentHeat}");
                LogDebug($"Took {Patches.heatDamage} heat");
            }

            LogDebug($"attackSequence.GetArmorDamageDealt(id) {attackSequence.GetArmorDamageDealt(id)}\nattackSequence.GetStructureDamageDealt(id) {attackSequence.GetStructureDamageDealt(id)}\nPatches.heatDamage * modSettings.HeatDamageModifier {Patches.heatDamage * modSettings.HeatDamageModifier}");
            if (attackSequence.GetArmorDamageDealt(id) + attackSequence.GetStructureDamageDealt(id) + Patches.heatDamage * modSettings.HeatDamageModifier /
                (previousArmor + previousStructure) +
                100 <= modSettings.MinimumDamagePercentageRequired)
            {
                LogDebug("Not enough damage");
                return(false);
            }

            LogDebug("Total damage requires a panic save");
            return(true);
        }
コード例 #4
0
        // returns true if enough damage was inflicted to trigger a panic save
        private static bool SufficientDamageWasDone(AttackDirector.AttackSequence attackSequence)
        {
            if (attackSequence == null)
            {
                return(false);
            }

            var id = attackSequence.chosenTarget.GUID;

            if (!attackSequence.GetAttackDidDamage(id))
            {
                LogReport("No damage");
                return(false);
            }

            var previousArmor     = AttackStackSequence_OnAttackBegin_Patch.armorBeforeAttack;
            var previousStructure = AttackStackSequence_OnAttackBegin_Patch.structureBeforeAttack;
            var armorDamage       = attackSequence.GetArmorDamageDealt(id);
            var structureDamage   = attackSequence.GetStructureDamageDealt(id);
            var percentDamageDone = (attackSequence.GetArmorDamageDealt(id) + attackSequence.GetStructureDamageDealt(id)) / (previousArmor + previousStructure) * 100;

            damageWithHeatDamage = percentDamageDone + Mech_AddExternalHeat_Patch.heatDamage * modSettings.HeatDamageFactor;

            // have to check structure here AFTER armor, despite it being the priority, because we need to set the global
            LogReport($"Damage >>> A: {armorDamage:F3} S: {structureDamage:F3} ({percentDamageDone:F2}%) H: {Mech_AddExternalHeat_Patch.heatDamage}");
            if (attackSequence.chosenTarget is Mech &&
                attackSequence.GetStructureDamageDealt(id) >= modSettings.MinimumMechStructureDamageRequired ||
                modSettings.VehiclesCanPanic &&
                attackSequence.chosenTarget is Vehicle &&
                attackSequence.GetStructureDamageDealt(id) >= modSettings.MinimumVehicleStructureDamageRequired)
            {
                LogReport("Structure damage requires panic save");
                return(true);
            }

            if (damageWithHeatDamage <= modSettings.MinimumDamagePercentageRequired)
            {
                LogReport("Not enough damage");
                Mech_AddExternalHeat_Patch.heatDamage = 0;
                return(false);
            }

            LogReport("Total damage requires a panic save");
            return(true);
        }
コード例 #5
0
            private static void Postfix(AbstractActor __instance, string sourceID, int sequenceID, int stackItemID, AttackDirection attackDirection)
            {
                Mod.Log.Trace("AA:RAS:POST entered");

                //int evasivePipsCurrent = __instance.EvasivePipsCurrent;
                //__instance.ConsumeEvasivePip(true);
                //int evasivePipsCurrent2 = __instance.EvasivePipsCurrent;
                //if (evasivePipsCurrent2 < evasivePipsCurrent && !__instance.IsDead && !__instance.IsFlaggedForDeath) {
                //    __instance.Combat.MessageCenter.PublishMessage(new FloatieMessage(__instance.GUID, __instance.GUID, "-1 EVASION", FloatieMessage.MessageNature.Debuff));
                //}

                AttackDirector.AttackSequence attackSequence = __instance.Combat.AttackDirector.GetAttackSequence(sequenceID);
                if (attackSequence != null)
                {
                    if (!attackSequence.GetAttackDidDamage(__instance.GUID))
                    {
                        return;
                    }
                    List <Effect> list = __instance.Combat.EffectManager.GetAllEffectsTargeting(__instance).FindAll((Effect x) => x.EffectData.targetingData.effectTriggerType == EffectTriggerType.OnDamaged);
                    for (int i = 0; i < list.Count; i++)
                    {
                        list[i].OnEffectTakeDamage(attackSequence.attacker, __instance);
                    }
                    if (attackSequence.isMelee)
                    {
                        int value = attackSequence.attacker.StatCollection.GetValue <int>(ModStats.MeleeHitPushBackPhases);
                        if (value > 0)
                        {
                            for (int j = 0; j < value; j++)
                            {
                                __instance.ForceUnitOnePhaseDown(sourceID, stackItemID, false);
                            }
                        }
                    }
                }
            }
コード例 #6
0
        public static bool TryPenetrateStressResistance(Mech mech, AttackDirector.AttackSequence attackSequence, out int stressLevel, out float ejectionChance)
        {
            Pilot pilot = mech.GetPilot();

            stressLevel    = 0;
            ejectionChance = 0;

            // No brainers
            if (pilot == null || mech == null || mech.IsDead || (mech.IsFlaggedForDeath && !mech.HasHandledDeath))
            {
                Logger.Info($"[Attack_TryPenetrateStressResistance] ({mech?.DisplayName}) EXITING: No pilot, no mech or mech is already dead");
                return(false);
            }

            // Elite pilots always resist
            if (pilot.IsElite && SimpleEjectionSystem.Settings.ElitePilotsAlwaysResists)
            {
                Logger.Info($"[Attack_TryPenetrateStressResistance] ({mech.DisplayName}) EXITING: Pilot is elite");
                return(false);
            }

            // Don't take control from human player
            if (pilot.IsPlayerCharacter && SimpleEjectionSystem.Settings.PlayerCharacterAlwaysResists)
            {
                Logger.Info($"[Attack_TryPenetrateStressResistance] ({mech.DisplayName}) EXITING: Is commander");
                return(false);
            }

            // No damage
            if (!attackSequence.GetAttackDidDamage(mech.GUID))
            {
                Logger.Info($"[Attack_TryPenetrateStressResistance] ({mech.DisplayName}) EXITING: Took no damage");
                return(false);
            }

            // Ejection modifiers
            float ejectModifiers = 0;

            ejectModifiers += Assess.GetEjectionModifiersFromState(mech, pilot, out bool isGoingToDie);

            // Shortcutting
            if (isGoingToDie)
            {
                stressLevel    = pilot.SetStressLevel(4).GetStressLevel();
                ejectionChance = SimpleEjectionSystem.Settings.PointlessEjectionChance;
                return(true);
            }

            ejectModifiers += Assess.GetEjectionModifiersFromAttack(mech, attackSequence);

            // Resistance modifiers
            float resistModifiers = 0;

            resistModifiers += Assess.GetResistanceModifiers(mech);


            // Difficulty

            /*
             * if (mech.team.IsLocalPlayer)
             * {
             *  float difficultyMultiplier = Miscellaneous.GetMultiplierForDifficulty(SimpleEjectionSystem.Settings.Difficulty);
             *  Logger.Debug($"[Attack_TryPenetrateStressResistance] ({mech.DisplayName}) difficultyMultiplier: {difficultyMultiplier}");
             *
             *  ejectModifiers *= difficultyMultiplier;
             *  Logger.Debug($"[Attack_TryPenetrateStressResistance] ({mech.DisplayName}) ejectModifiers: {ejectModifiers}");
             *
             *  resistModifiers /= difficultyMultiplier;
             *  Logger.Debug($"[Attack_TryPenetrateStressResistance] ({mech.DisplayName}) resistModifiers: {resistModifiers}");
             * }
             */

            // Evaluate
            float finalModifiers = (ejectModifiers - resistModifiers) * 5;

            Logger.Debug($"[Attack_TryPenetrateStressResistance] ({mech.DisplayName}) finalModifiers: {finalModifiers}");

            if (finalModifiers <= 0)
            {
                Logger.Debug($"[Attack_TryPenetrateStressResistance] ({mech.DisplayName}) RESISTED!");
                return(false);
            }
            Logger.Debug($"[Attack_TryPenetrateStressResistance] ({mech.DisplayName}) Resistances BREACHED!");
            pilot.AddHardship();

            // Raise pilot's stresslevel
            stressLevel = pilot.IncreaseStressLevel(1).GetStressLevel();
            Logger.Debug($"[Attack_TryPenetrateStressResistance] ({mech.DisplayName}) stressLevel: {stressLevel}");

            // Sanitize ejection chance
            //ejectionChance = Math.Min(95, finalModifiers);
            ejectionChance = Mathf.Clamp(finalModifiers, 0, SimpleEjectionSystem.Settings.EjectionChanceMax);
            Logger.Debug($"[Attack_TryPenetrateStressResistance] ({mech.DisplayName}) ejectionChance: {ejectionChance}");

            // Save ejection chance in pilot's StatCollection to potentially use it on next activation
            pilot.SetLastEjectionChance(ejectionChance);



            return(true);
        }
コード例 #7
0
        // returns true if enough damage was inflicted to trigger a panic save
        private static bool SufficientDamageWasDone(AttackDirector.AttackSequence attackSequence)
        {
            if (attackSequence == null)
            {
                return(false);
            }

            var id = attackSequence.chosenTarget.GUID;

            if (!attackSequence.GetAttackDidDamage(id))
            {
                LogReport("No damage");
                return(false);
            }

            // Account for melee attacks so separate panics are not triggered.
            if (attackSequence.isMelee && MechMeleeSequence_FireWeapons_Patch.meleeHasSupportWeapons)
            {
                initialArmorMelee     = AttackStackSequence_OnAttackBegin_Patch.armorBeforeAttack;
                initialStructureMelee = AttackStackSequence_OnAttackBegin_Patch.structureBeforeAttack;
                armorDamageMelee      = attackSequence.GetArmorDamageDealt(id);
                structureDamageMelee  = attackSequence.GetStructureDamageDealt(id);
                hadMeleeAttack        = true;
                LogReport("Stashing melee damage for support weapon firing");
                return(false);
            }

            var previousArmor     = AttackStackSequence_OnAttackBegin_Patch.armorBeforeAttack;
            var previousStructure = AttackStackSequence_OnAttackBegin_Patch.structureBeforeAttack;

            if (hadMeleeAttack)
            {
                LogReport("Adding stashed melee damage");
                previousArmor     = initialArmorMelee;
                previousStructure = initialStructureMelee;
            }
            else
            {
                armorDamageMelee     = 0;
                structureDamageMelee = 0;
            }

            var armorDamage     = attackSequence.GetArmorDamageDealt(id) + armorDamageMelee;
            var structureDamage = attackSequence.GetStructureDamageDealt(id) + structureDamageMelee;
            var heatDamage      = Mech_AddExternalHeat_Patch.heatDamage * modSettings.HeatDamageFactor;

            // used in SavingThrows.cs
            damageIncludingHeatDamage = armorDamage + structureDamage + heatDamage;
            var percentDamageDone =
                damageIncludingHeatDamage / (previousArmor + previousStructure) * 100;

            // clear melee values
            initialArmorMelee     = 0;
            initialStructureMelee = 0;
            armorDamageMelee      = 0;
            structureDamageMelee  = 0;
            hadMeleeAttack        = false;

            // have to check structure here AFTER armor, despite it being the priority, because we need to set the global
            LogReport($"Damage >>> A: {armorDamage:F3} S: {structureDamage:F3} ({percentDamageDone:F2}%) H: {Mech_AddExternalHeat_Patch.heatDamage}");
            if (modSettings.AlwaysPanic)
            {
                LogReport("AlwaysPanic");
                return(true);
            }

            if (attackSequence.chosenTarget is Mech &&
                attackSequence.GetStructureDamageDealt(id) > modSettings.MinimumMechStructureDamageRequired ||
                modSettings.VehiclesCanPanic &&
                attackSequence.chosenTarget is Vehicle &&
                attackSequence.GetStructureDamageDealt(id) > modSettings.MinimumVehicleStructureDamageRequired)
            {
                LogReport("Structure damage requires panic save");
                return(true);
            }

            if (percentDamageDone <= modSettings.MinimumDamagePercentageRequired)
            {
                LogReport("Not enough damage");
                Mech_AddExternalHeat_Patch.heatDamage = 0;
                return(false);
            }

            LogReport("Total damage requires a panic save");
            return(true);
        }
コード例 #8
0
 static void Postfix(AbstractActor __instance, string sourceID, int sequenceID, int stackItemID, AttackDirection attackDirection)
 {
     try
     {
         AttackDirector.AttackSequence attackSequence = __instance.Combat.AttackDirector.GetAttackSequence(sequenceID);
         if (attackSequence != null && attackSequence.GetAttackDidDamage(__instance.GUID))
         {
             List <Effect> list = __instance.Combat.EffectManager.GetAllEffectsTargeting(__instance).FindAll((Effect x) => x.EffectData.targetingData.effectTriggerType == EffectTriggerType.OnDamaged);
             for (int i = 0; i < list.Count; i++)
             {
                 list[i].OnEffectTakeDamage(attackSequence.attacker, __instance);
             }
             if (attackSequence.isMelee)
             {
                 int value = attackSequence.attacker.StatCollection.GetValue <int>("MeleeHitPushBackPhases");
                 if (value > 0)
                 {
                     for (int j = 0; j < value; j++)
                     {
                         __instance.ForceUnitOnePhaseDown(sourceID, stackItemID, false);
                     }
                 }
             }
         }
         int   evasivePipsCurrent  = __instance.EvasivePipsCurrent;
         var   settings            = PermanentEvasion.Settings;
         float totalDamageReceived = 1;
         if (attackSequence.GetAttackDidDamage(__instance.GUID))
         {
             totalDamageReceived += attackSequence.GetArmorDamageDealt(__instance.GUID) + attackSequence.GetStructureDamageDealt(__instance.GUID);
             if ((totalDamageReceived > settings.MinDamageForEvasionStrip) && settings.AllowHitStrip)
             {
                 __instance.ConsumeEvasivePip(true);
                 Fields.LoosePip = false;
             }
             else if (Fields.LoosePip)
             {
                 __instance.ConsumeEvasivePip(true);
                 Fields.LoosePip = false;
             }
         }
         else if (Fields.LoosePip)
         {
             __instance.ConsumeEvasivePip(true);
             Fields.LoosePip = false;
         }
         int evasivePipsCurrent2 = __instance.EvasivePipsCurrent;
         if (evasivePipsCurrent2 < evasivePipsCurrent && (totalDamageReceived > settings.MinDamageForEvasionStrip) && settings.AllowHitStrip && !__instance.IsDead && !__instance.IsFlaggedForDeath)
         {
             __instance.Combat.MessageCenter.PublishMessage(new FloatieMessage(__instance.GUID, __instance.GUID, "HIT: -1 EVASION", FloatieMessage.MessageNature.Debuff));
         }
         else if (evasivePipsCurrent2 < evasivePipsCurrent && !__instance.IsDead && !__instance.IsFlaggedForDeath)
         {
             __instance.Combat.MessageCenter.PublishMessage(new FloatieMessage(__instance.GUID, __instance.GUID, "-1 EVASION", FloatieMessage.MessageNature.Debuff));
         }
         else if (evasivePipsCurrent2 > 0 && Fields.KeptPip && !__instance.IsDead && !__instance.IsFlaggedForDeath)
         {
             __instance.Combat.MessageCenter.PublishMessage(new FloatieMessage(__instance.GUID, __instance.GUID, "EVASION KEPT", FloatieMessage.MessageNature.Buff));
             Fields.KeptPip = false;
         }
     }
     catch (Exception e)
     {
         Logger.Error(e);
     }
 }
コード例 #9
0
        public static bool RollForEjectionResult(Mech mech, AttackDirector.AttackSequence attackSequence, bool IsEarlyPanic)
        {
            if (mech == null || mech.IsDead || (mech.IsFlaggedForDeath && !mech.HasHandledDeath) || attackSequence == null)
            {
                return(false);
            }

            // knocked down mechs cannot eject
            if (mech.IsProne && Settings.KnockedDownCannotEject)
            {
                return(false);
            }
            var id = attackSequence.chosenTarget.GUID;

            // have to do damage
            if (!attackSequence.GetAttackDidDamage(id))
            {
                return(false);
            }

            Pilot pilot   = mech.GetPilot();
            var   weapons = mech.Weapons;
            var   guts    = mech.SkillGuts;
            var   tactics = mech.SkillTactics;
            var   total   = guts + tactics;

            float lowestRemaining = mech.CenterTorsoStructure + mech.CenterTorsoFrontArmor;
            float ejectModifiers  = 0;

            // guts 10 makes you immune, player character cannot be forced to eject
            if ((guts >= 10 && Settings.GutsTenAlwaysResists) || (pilot != null && pilot.IsPlayerCharacter && Settings.PlayerCharacterAlwaysResists))
            {
                return(false);
            }

            // tactics 10 makes you immune, or combination of guts and tactics makes you immune.
            if ((tactics >= 10 && Settings.TacticsTenAlwaysResists) || (total >= 10 && Settings.ComboTenAlwaysResists))
            {
                return(false);
            }

            // pilots that cannot eject or be headshot shouldn't eject
            if (!mech.CanBeHeadShot || (pilot != null && !pilot.CanEject))
            {
                return(false);
            }

            // pilot health
            if (pilot != null)
            {
                float pilotHealthPercent = 1 - ((float)pilot.Injuries / pilot.Health);

                if (pilotHealthPercent < 1)
                {
                    ejectModifiers += Settings.PilotHealthMaxModifier * (1 - pilotHealthPercent);
                }
            }

            if (mech.IsUnsteady)
            {
                ejectModifiers += Settings.UnsteadyModifier;
            }

            // Head
            var headHealthPercent = (mech.HeadArmor + mech.HeadStructure) / (mech.GetMaxArmor(ArmorLocation.Head) + mech.GetMaxStructure(ChassisLocations.Head));

            if (headHealthPercent < 1)
            {
                ejectModifiers += Settings.HeadDamageMaxModifier * (1 - headHealthPercent);
            }

            // CT
            var ctPercent = (mech.CenterTorsoFrontArmor + mech.CenterTorsoStructure) / (mech.GetMaxArmor(ArmorLocation.CenterTorso) + mech.GetMaxStructure(ChassisLocations.CenterTorso));

            if (ctPercent < 1)
            {
                ejectModifiers += Settings.CTDamageMaxModifier * (1 - ctPercent);
                lowestRemaining = Math.Min(mech.CenterTorsoStructure, lowestRemaining);
            }

            // side torsos
            var ltStructurePercent = mech.LeftTorsoStructure / mech.GetMaxStructure(ChassisLocations.LeftTorso);

            if (ltStructurePercent < 1)
            {
                ejectModifiers += Settings.SideTorsoInternalDamageMaxModifier * (1 - ltStructurePercent);
            }

            var rtStructurePercent = mech.RightTorsoStructure / mech.GetMaxStructure(ChassisLocations.RightTorso);

            if (rtStructurePercent < 1)
            {
                ejectModifiers += Settings.SideTorsoInternalDamageMaxModifier * (1 - rtStructurePercent);
            }

            // legs
            if (mech.RightLegDamageLevel == LocationDamageLevel.Destroyed || mech.LeftLegDamageLevel == LocationDamageLevel.Destroyed)
            {
                float legPercent;

                if (mech.LeftLegDamageLevel == LocationDamageLevel.Destroyed)
                {
                    legPercent = (mech.RightLegStructure + mech.RightLegArmor) / (mech.GetMaxStructure(ChassisLocations.RightLeg) + mech.GetMaxArmor(ArmorLocation.RightLeg));
                }
                else
                {
                    legPercent = (mech.LeftLegStructure + mech.LeftLegArmor) / (mech.GetMaxStructure(ChassisLocations.LeftLeg) + mech.GetMaxArmor(ArmorLocation.LeftLeg));
                }

                if (legPercent < 1)
                {
                    lowestRemaining = Math.Min(legPercent * (mech.GetMaxStructure(ChassisLocations.LeftLeg) + mech.GetMaxArmor(ArmorLocation.LeftLeg)), lowestRemaining);
                    ejectModifiers += Settings.LeggedMaxModifier * (1 - legPercent);
                }
            }

            // next shot could kill
            if (lowestRemaining <= attackSequence.cumulativeDamage)
            {
                ejectModifiers += Settings.NextShotLikeThatCouldKill;
            }

            // weaponless
            if (weapons.TrueForAll(w =>
                                   w.DamageLevel == ComponentDamageLevel.Destroyed || w.DamageLevel == ComponentDamageLevel.NonFunctional))
            {
                ejectModifiers += Settings.WeaponlessModifier;
            }

            // alone
            if (mech.Combat.GetAllAlliesOf(mech).TrueForAll(m => m.IsDead || m == mech as AbstractActor))
            {
                ejectModifiers += Settings.AloneModifier;
            }

            var modifiers = (ejectModifiers - Settings.BaseEjectionResist - (Settings.GutsEjectionResistPerPoint * guts) - (Settings.TacticsEjectionResistPerPoint * tactics)) * 5;

            if (mech.team == mech.Combat.LocalPlayerTeam)
            {
                MoraleConstantsDef moraleDef = mech.Combat.Constants.GetActiveMoraleDef(mech.Combat);
                modifiers -= Math.Max(mech.Combat.LocalPlayerTeam.Morale - moraleDef.CanUseInspireLevel, 0);
            }

            if (modifiers < 0)
            {
                return(false);
            }

            var   rng = (new System.Random()).Next(100);
            float rollToBeat;

            if (!IsEarlyPanic)
            {
                rollToBeat = Math.Min(modifiers, Settings.MaxEjectChance);
            }
            else
            {
                rollToBeat = Math.Min(modifiers, Settings.MaxEjectChanceWhenEarly);
            }

            mech.Combat.MessageCenter.PublishMessage(!(rng < rollToBeat)
                ? new AddSequenceToStackMessage(new ShowActorInfoSequence(mech, $"Guts/Tactics Check Passed {Math.Floor(rollToBeat)}%", FloatieMessage.MessageNature.Buff, true))
                : new AddSequenceToStackMessage(new ShowActorInfoSequence(mech, $"Punchin' Out! {Math.Floor(rollToBeat)}%", FloatieMessage.MessageNature.Debuff, true)));

            return(rng < rollToBeat);
        }
コード例 #10
0
        public static bool ShouldPanic(Mech mech, AttackDirector.AttackSequence attackSequence)
        {
            if (mech == null || attackSequence == null)
            {
                return(false);
            }

            if (mech.IsDead || (mech.IsFlaggedForDeath && mech.HasHandledDeath) || mech.team == null)
            {
                return(false);
            }
            var id = attackSequence.chosenTarget.GUID;

            if (!attackSequence.GetAttackDidDamage(id)) //no point in panicking over nothing
            {
                return(false);
            }

            if (attackSequence.GetStructureDamageDealt(id) < 1 && !attackSequence.GetLowArmorStruck(id)) //no structure damage and didn't strike low armour
            {
                float totalArmor = 0, maxArmor = 0;

                maxArmor = GetTotalMechArmour(mech, maxArmor);

                totalArmor = GetCurrentMechArmour(mech, totalArmor);

                if ((totalArmor / maxArmor * 100) + ((BasicPanic.Settings.MinimumArmourDamagePercentageRequired * maxArmor / 100) / maxArmor * 100) >= 100)  //basically if this equals to 100%, mech didn't lose enough armour
                {
                    return(false);
                }
            }


            if (mech.team == mech.Combat.LocalPlayerTeam && !BasicPanic.Settings.PlayerTeamCanPanic)
            {
                return(false);
            }
            else if (mech.team != mech.Combat.LocalPlayerTeam && !BasicPanic.Settings.EnemiesCanPanic)
            {
                return(false);
            }

            int PanicRoll = 0;

            Pilot pilot   = mech.GetPilot();
            var   weapons = mech.Weapons;
            var   guts    = mech.SkillGuts;
            var   tactics = mech.SkillTactics;
            var   total   = guts + tactics;
            int   index   = -1;

            index = PanicHelpers.GetTrackedPilotIndex(mech);


            float lowestRemaining = mech.CenterTorsoStructure + mech.CenterTorsoFrontArmor;
            float panicModifiers  = 0;

            if (index < 0)
            {
                Holder.TrackedPilots.Add(new PanicTracker(mech)); //add a new tracker to tracked pilot, then we run it all over again;

                index = PanicHelpers.GetTrackedPilotIndex(mech);
                if (index < 0)
                {
                    return(false);
                }
            }

            if (Holder.TrackedPilots[index].trackedMech != mech.GUID)
            {
                return(false);
            }

            if (Holder.TrackedPilots[index].trackedMech == mech.GUID &&
                Holder.TrackedPilots[index].ChangedRecently && BasicPanic.Settings.AlwaysGatedChanges)
            {
                return(false);
            }

            // pilot health
            if (pilot != null)
            {
                float pilotHealthPercent = 1 - ((float)pilot.Injuries / pilot.Health);

                if (pilotHealthPercent < 1)
                {
                    panicModifiers += BasicPanic.Settings.PilotHealthMaxModifier * (1 - pilotHealthPercent);
                }
            }

            if (mech.IsUnsteady)
            {
                panicModifiers += BasicPanic.Settings.UnsteadyModifier;
            }

            // Head
            var headHealthPercent = (mech.HeadArmor + mech.HeadStructure) / (mech.GetMaxArmor(ArmorLocation.Head) + mech.GetMaxStructure(ChassisLocations.Head));

            if (headHealthPercent < 1)
            {
                panicModifiers += BasicPanic.Settings.HeadDamageMaxModifier * (1 - headHealthPercent);
            }

            // CT
            var ctPercent = (mech.CenterTorsoFrontArmor + mech.CenterTorsoStructure) / (mech.GetMaxArmor(ArmorLocation.CenterTorso) + mech.GetMaxStructure(ChassisLocations.CenterTorso));

            if (ctPercent < 1)
            {
                panicModifiers += BasicPanic.Settings.CTDamageMaxModifier * (1 - ctPercent);
                lowestRemaining = Math.Min(mech.CenterTorsoStructure, lowestRemaining);
            }

            // side torsos
            var ltStructurePercent = mech.LeftTorsoStructure / mech.GetMaxStructure(ChassisLocations.LeftTorso);

            if (ltStructurePercent < 1)
            {
                panicModifiers += BasicPanic.Settings.SideTorsoInternalDamageMaxModifier * (1 - ltStructurePercent);
            }

            var rtStructurePercent = mech.RightTorsoStructure / mech.GetMaxStructure(ChassisLocations.RightTorso);

            if (rtStructurePercent < 1)
            {
                panicModifiers += BasicPanic.Settings.SideTorsoInternalDamageMaxModifier * (1 - rtStructurePercent);
            }

            // legs
            if (mech.RightLegDamageLevel == LocationDamageLevel.Destroyed || mech.LeftLegDamageLevel == LocationDamageLevel.Destroyed)
            {
                float legPercent;

                if (mech.LeftLegDamageLevel == LocationDamageLevel.Destroyed)
                {
                    legPercent = (mech.RightLegStructure + mech.RightLegArmor) / (mech.GetMaxStructure(ChassisLocations.RightLeg) + mech.GetMaxArmor(ArmorLocation.RightLeg));
                }
                else
                {
                    legPercent = (mech.LeftLegStructure + mech.LeftLegArmor) / (mech.GetMaxStructure(ChassisLocations.LeftLeg) + mech.GetMaxArmor(ArmorLocation.LeftLeg));
                }

                if (legPercent < 1)
                {
                    lowestRemaining = Math.Min(legPercent * (mech.GetMaxStructure(ChassisLocations.LeftLeg) + mech.GetMaxArmor(ArmorLocation.LeftLeg)), lowestRemaining);
                    panicModifiers += BasicPanic.Settings.LeggedMaxModifier * (1 - legPercent);
                }
            }

            // next shot could kill
            if (lowestRemaining <= attackSequence.cumulativeDamage)
            {
                panicModifiers += BasicPanic.Settings.NextShotLikeThatCouldKill;
            }

            // weaponless
            if (weapons.TrueForAll(w =>
                                   w.DamageLevel == ComponentDamageLevel.Destroyed || w.DamageLevel == ComponentDamageLevel.NonFunctional))
            {
                panicModifiers += BasicPanic.Settings.WeaponlessModifier;
            }

            // alone
            if (mech.Combat.GetAllAlliesOf(mech).TrueForAll(m => m.IsDead || m == mech as AbstractActor))
            {
                panicModifiers += BasicPanic.Settings.AloneModifier;
            }
            //straight up add guts, tactics, and morale to this as negative values
            panicModifiers -= total;
            if (mech.team == mech.Combat.LocalPlayerTeam)
            {
                MoraleConstantsDef moraleDef = mech.Combat.Constants.GetActiveMoraleDef(mech.Combat);
                panicModifiers -= Math.Max(mech.Combat.LocalPlayerTeam.Morale - moraleDef.CanUseInspireLevel, 0) / (float)2;
            }

            //reduce modifiers by 5 to account change to D20 roll instead of D100 roll, then min it t0 20 or modified floor
            panicModifiers /= 5;

            PanicRoll = PanicRoll + (int)panicModifiers;

            if ((total >= 20 || PanicRoll <= 0) && !BasicPanic.Settings.AtLeastOneChanceToPanic)
            {
                return(false);
            }

            PanicRoll = Math.Min(PanicRoll, 20);

            if (PanicRoll < 0)
            {
                PanicRoll = 0;                                   //make this have some kind of chance to happen
            }
            PanicRoll = UnityEngine.Random.Range(PanicRoll, 20); // actual roll
            //we get this far, we reduce total to under the max panic chance
            total = Math.Min(total, (int)BasicPanic.Settings.MaxPanicResistTotal);

            int rngRoll = UnityEngine.Random.Range(total, 20);

            if (rngRoll <= PanicRoll)
            {
                ApplyPanicDebuff(mech, index);
                return(true);
            }
            mech.Combat.MessageCenter.PublishMessage(new AddSequenceToStackMessage(new ShowActorInfoSequence(mech, $"Resisted Morale Check!", FloatieMessage.MessageNature.Buff, true)));
            return(false);
        }