示例#1
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);
        }
示例#2
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);
        }
示例#3
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);
        }
 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);
     }
 }
        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);
        }
        public static void Postfix(AttackDirector __instance, MessageCenterMessage message)
        {
            Mod.Log.Debug?.Write("AD:OASE - entered.");

            // Nothing to do
            if (ModState.BreachAttackId == ModState.NO_ATTACK_SEQUENCE_ID)
            {
                return;
            }

            AttackSequenceEndMessage attackSequenceEndMessage = message as AttackSequenceEndMessage;

            if (__instance == null || attackSequenceEndMessage == null)
            {
                return;
            }

            int sequenceId = attackSequenceEndMessage.sequenceId;

            AttackDirector.AttackSequence attackSequence = __instance.GetAttackSequence(sequenceId);

            if (attackSequence == null)
            {
                Mod.Log.Info?.Write($"Could not find attack sequence with id: {sequenceId}. CAC may have killed it, or there's an error in processing. Skipping hull breach checks.");
                return;
            }

            // No chosen target, nothing to damage
            if (attackSequence.chosenTarget == null)
            {
                return;
            }

            if (ModState.BreachAttackId != attackSequence.id)
            {
                Mod.Log.Debug?.Write($"Attack sequence ID {attackSequence.id} does not match Hull Breach Attack Id - skipping hull breach resolution.");
                return;
            }

            float structureDamage = attackSequence.GetStructureDamageDealt(attackSequence.chosenTarget.GUID);

            Mod.Log.Debug?.Write($"Attack sequence {sequenceId} did {structureDamage} structure damage to target: {attackSequence.chosenTarget.GUID}");
            if (structureDamage == 0f)
            {
                Mod.Log.Debug?.Write($"Attack did no structure damage, skipping.");
                return;
            }

            if (attackSequence.chosenTarget is Mech targetMech)
            {
                Mod.Log.Debug?.Write($"Checking hull breaches for targetMech: {CombatantUtils.Label(targetMech)}");
                ResolveMechHullBreaches(targetMech);
            }
            if (attackSequence.chosenTarget is Turret targetTurret)
            {
                Mod.Log.Debug?.Write($"Checking hull breaches for targetTurret: {CombatantUtils.Label(targetTurret)}");
                ResolveTurretHullBreaches(targetTurret);
            }
            if (attackSequence.chosenTarget is Vehicle targetVehicle)
            {
                Mod.Log.Debug?.Write($"Checking hull breaches for targetVehicle: {CombatantUtils.Label(targetVehicle)}");
                ResolveVehicleHullBreaches(targetVehicle);
            }

            // Reset state
            ModState.BreachAttackId = ModState.NO_ATTACK_SEQUENCE_ID;
            ModState.BreachHitsMech.Clear();
            ModState.BreachHitsTurret.Clear();
            ModState.BreachHitsVehicle.Clear();

            Mod.Log.Debug?.Write("AD:OASE - exiting.");
        }