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); }