// Resolve turret hits - any hull breach kill the unit private static void ResolveTurretHullBreaches(Turret targetTurret) { bool needsQuip = false; // Check for immunity List <MechComponent> components = targetTurret.allComponents.Where(mc => mc.DamageLevel == ComponentDamageLevel.Functional).ToList(); bool hasImmunity = false; foreach (MechComponent mc in components) { if (mc.StatCollection.ContainsStatistic(ModStats.HullBreachImmunity)) { Mod.Log.Debug?.Write($" Component: {mc.UIName} grants hull breach immunity, skipping!"); hasImmunity = true; break; } } if (hasImmunity) { return; } foreach (BuildingLocation hitLocation in ModState.BreachHitsTurret.Keys) { // If no immunity, sum the breach check across all trials float passChance = 1f - ModState.BreachCheck; //float sequencePassChance = Mathf.Pow(passChance, ModState.BreachHitsTurret[hitLocation]); // TODO: Number of trials is way too rough, and can make breaches extremely common. Weakening to flat percentage chance. float sequencePassChance = Mathf.Pow(passChance, 1); float sequenceThreshold = 1f - sequencePassChance; Mod.Log.Debug?.Write($" For pass chance: {passChance} with n trials: {ModState.BreachHitsTurret[hitLocation]} has sequencePassChance: {sequencePassChance} => sequenceThreshold: {sequenceThreshold}"); // Check for failure bool passedCheck = CheckHelper.DidCheckPassThreshold(sequenceThreshold, targetTurret, 0f, Mod.LocalizedText.Floaties[ModText.FT_Hull_Breach]); Mod.Log.Debug?.Write($"Actor: {CombatantUtils.Label(targetTurret)} HULL BREACH check: {passedCheck} for location: {hitLocation}"); if (!passedCheck) { Mod.Log.Info?.Write($" Turret {CombatantUtils.Label(targetTurret)} suffers a hull breach in location: {hitLocation}"); string floatieText = new Text(Mod.LocalizedText.Floaties[ModText.FT_Hull_Breach]).ToString(); MultiSequence showInfoSequence = new ShowActorInfoSequence(targetTurret, floatieText, FloatieMessage.MessageNature.Debuff, false); targetTurret.Combat.MessageCenter.PublishMessage(new AddSequenceToStackMessage(showInfoSequence)); needsQuip = true; if (targetTurret.GetPilot() != null) { targetTurret.GetPilot().KillPilot(targetTurret.Combat.Constants, "", 0, DamageType.Unknown, null, null); } targetTurret.FlagForDeath("Dead from hull breach!", DeathMethod.Unknown, DamageType.Unknown, -1, -1, "", false); targetTurret.HandleDeath("0"); } } if (needsQuip) { QuipHelper.PublishQuip(targetTurret, Mod.LocalizedText.Quips.Breach); } }
private static void ResolveVehicleHullBreaches(Vehicle targetVehicle) { bool needsQuip = false; foreach (VehicleChassisLocations hitLocation in ModState.BreachHitsVehicle.Keys) { List <MechComponent> componentsInLocation = targetVehicle.allComponents.Where(mc => mc.mechComponentRef.DamageLevel == ComponentDamageLevel.Functional && mc.mechComponentRef.MountedLocation == (ChassisLocations)hitLocation).ToList(); // Check for immunity in this location bool hasImmunity = false; foreach (MechComponent mc in componentsInLocation) { if (mc.StatCollection.ContainsStatistic(ModStats.HullBreachImmunity)) { Mod.Log.Debug($" Component: {mc.UIName} grants hull breach immunity, skipping!"); hasImmunity = true; break; } } if (hasImmunity) { continue; } // If no immunity, sum the breach check across all trials float passChance = 1f - ModState.BreachCheck; float sequencePassChance = Mathf.Pow(passChance, ModState.BreachHitsVehicle[hitLocation]); float sequenceThreshold = 1f - sequencePassChance; Mod.Log.Debug($" For pass chance: {passChance} with n trials: {ModState.BreachHitsVehicle[hitLocation]} has sequencePassChance: {sequencePassChance} => sequenceThreshold: {sequenceThreshold}"); // Check for failure bool passedCheck = CheckHelper.DidCheckPassThreshold(sequenceThreshold, targetVehicle, 0f, Mod.Config.LocalizedFloaties[ModConfig.FT_Hull_Breach]); Mod.Log.Debug($"Actor: {CombatantUtils.Label(targetVehicle)} HULL BREACH check: {passedCheck} for location: {hitLocation}"); if (!passedCheck) { string floatieText = new Text(Mod.Config.LocalizedFloaties[ModConfig.FT_Hull_Breach]).ToString(); MultiSequence showInfoSequence = new ShowActorInfoSequence(targetVehicle, floatieText, FloatieMessage.MessageNature.Debuff, false); targetVehicle.Combat.MessageCenter.PublishMessage(new AddSequenceToStackMessage(showInfoSequence)); needsQuip = true; if (targetVehicle.GetPilot() != null) { targetVehicle.GetPilot().KillPilot(targetVehicle.Combat.Constants, "", 0, DamageType.Unknown, null, null); } targetVehicle.FlagForDeath("Dead from hull breach!", DeathMethod.Unknown, DamageType.Unknown, -1, -1, "", false); targetVehicle.HandleDeath("0"); break; } } if (needsQuip) { QuipHelper.PublishQuip(targetVehicle, Mod.Config.Qips.Breach); } }
// Create a falling sequence, publish a floatie with the error public static void AddFallingSequence(Mech mech, MultiSequence parentSequence, string floatieText) { MechFallSequence mechFallSequence = new MechFallSequence(mech, floatieText, new Vector2(0f, -1f)); string fallDebuffText = new Text(Mod.Config.LocalizedFloaties[floatieText]).ToString(); MultiSequence showInfoSequence = new ShowActorInfoSequence(mech, fallDebuffText, FloatieMessage.MessageNature.Debuff, false) { RootSequenceGUID = mechFallSequence.SequenceGUID }; mechFallSequence.AddChildSequence(showInfoSequence, mechFallSequence.MessageIndex); mech.Combat.MessageCenter.PublishMessage(new AddSequenceToStackMessage(mechFallSequence)); }
static void Prefix(MechMeleeSequence __instance) { (MeleeAttack meleeAttack, Weapon fakeWeapon)seqState = ModState.GetMeleeSequenceState(__instance.SequenceGUID); if (seqState.meleeAttack != null && seqState.meleeAttack.AttackerInstability != 0) { Mod.MeleeLog.Info?.Write($" -- Adding {seqState.meleeAttack.AttackerInstability} absolute instability to attacker."); __instance.OwningMech.AddAbsoluteInstability(seqState.meleeAttack.AttackerInstability, StabilityChangeSource.Attack, "-1"); } // Publish a floatie warning of the swarm attack on the target if (ModState.ForceDamageTable == DamageTable.SWARM) { string swarmAttackText = new Text(Mod.LocalizedText.Floaties[ModText.FT_Swarm_Attack]).ToString(); MultiSequence showInfoSequence = new ShowActorInfoSequence(__instance.MeleeTarget, swarmAttackText, FloatieMessage.MessageNature.Debuff, false) { RootSequenceGUID = __instance.SequenceGUID }; SharedState.Combat.MessageCenter.PublishMessage(new AddSequenceToStackMessage(showInfoSequence)); Mod.Log.Info?.Write(" -- published fall sequence."); } }
// Create a falling sequence, publish a floatie with the error public static void AddFallingSequence(Mech mech, MultiSequence parentSequence, string floatieText) { Mod.Log.Info?.Write($"Adding falling sequence for mech: {mech.DistinctId()}"); MechFallSequence mechFallSequence = new MechFallSequence(mech, floatieText, new Vector2(0f, -1f)); string fallDebuffText = new Text(Mod.LocalizedText.Floaties[floatieText]).ToString(); MultiSequence showInfoSequence = new ShowActorInfoSequence(mech, fallDebuffText, FloatieMessage.MessageNature.Debuff, false) { RootSequenceGUID = mechFallSequence.SequenceGUID }; mechFallSequence.AddChildSequence(showInfoSequence, mechFallSequence.MessageIndex); mech.Combat.MessageCenter.PublishMessage(new AddSequenceToStackMessage(mechFallSequence)); Mod.Log.Info?.Write(" -- published fall sequence."); IStackSequence doneWithActorSequence = mech.DoneWithActor(); mech.Combat.MessageCenter.PublishMessage(new AddSequenceToStackMessage(doneWithActorSequence)); Mod.Log.Info?.Write(" -- published doneWithActor sequence."); }
// Resolve mech hits - mark components invalid, but kill the pilot on a head-hit private static void ResolveMechHullBreaches(Mech targetMech) { bool needsQuip = false; foreach (ChassisLocations hitLocation in ModState.BreachHitsMech.Keys) { List <MechComponent> componentsInLocation = targetMech.allComponents.Where(mc => mc.mechComponentRef.DamageLevel == ComponentDamageLevel.Functional && mc.mechComponentRef.MountedLocation == hitLocation).ToList(); // Check for immunity in this location bool hasImmunity = false; foreach (MechComponent mc in componentsInLocation) { if (mc.StatCollection.ContainsStatistic(ModStats.HullBreachImmunity)) { Mod.Log.Debug($" Component: {mc.UIName} grants hull breach immunity, skipping!"); hasImmunity = true; break; } } if (hasImmunity) { continue; } // If no immunity, sum the breach check across all trials float passChance = 1f - ModState.BreachCheck; float sequencePassChance = Mathf.Pow(passChance, ModState.BreachHitsMech[hitLocation]); float sequenceThreshold = 1f - sequencePassChance; Mod.Log.Debug($" For pass chance: {passChance} with n trials: {ModState.BreachHitsMech[hitLocation]} has sequencePassChance: {sequencePassChance} => sequenceThreshold: {sequenceThreshold}"); // Check for failure bool passedCheck = CheckHelper.DidCheckPassThreshold(sequenceThreshold, targetMech, 0f, Mod.Config.LocalizedFloaties[ModConfig.FT_Hull_Breach]); Mod.Log.Debug($"Actor: {CombatantUtils.Label(targetMech)} HULL BREACH check: {passedCheck} for location: {hitLocation}"); if (!passedCheck) { string floatieText = new Text(Mod.Config.LocalizedFloaties[ModConfig.FT_Hull_Breach]).ToString(); MultiSequence showInfoSequence = new ShowActorInfoSequence(targetMech, floatieText, FloatieMessage.MessageNature.Debuff, false); targetMech.Combat.MessageCenter.PublishMessage(new AddSequenceToStackMessage(showInfoSequence)); needsQuip = true; if (hitLocation <= ChassisLocations.RightTorso) { switch (hitLocation) { case ChassisLocations.Head: Mod.Log.Debug($" Head structure damage taken, killing pilot!"); targetMech.GetPilot().KillPilot(targetMech.Combat.Constants, "", 0, DamageType.Enemy, null, null); break; case ChassisLocations.CenterTorso: default: if (hitLocation == ChassisLocations.CenterTorso) { Mod.Log.Debug($" Center Torso hull breach!"); } // Walk the location and disable every component in it foreach (MechComponent mc in componentsInLocation) { Mod.Log.Debug($" Marking component: {mc.defId} of type: {mc.componentDef.Description.Name} nonfunctional"); mc.DamageComponent(default(WeaponHitInfo), ComponentDamageLevel.NonFunctional, true); } break; } } } } if (needsQuip) { QuipHelper.PublishQuip(targetMech, Mod.Config.Qips.Breach); } }
static bool Prefix(Pilot __instance, DamageType damageType, ref bool ___needsInjury, ref InjuryReason ___injuryReason) { Mod.Log.Trace?.Write("P:SNI - entered"); // DEBUG Line here: Someone is emitting an injuryReason of 101. Try to identify them by emitting a stack trace when this happens. if ((int)___injuryReason > 6) { Mod.Log.Warn?.Write($"PainTolerance intercepted injuryReason with value of: {(int)___injuryReason} and desc: {___injuryReason}"); Mod.Log.Info?.Write($" -- injured actor was: {__instance.ParentActor.DistinctId()}"); System.Diagnostics.StackTrace t = new System.Diagnostics.StackTrace(); Mod.Log.Info?.Write($" -- PainTolerance:InjurePilot intercepted call stack:"); Mod.Log.Info?.Write($" --\n\n{t}"); Mod.Log.Info?.Write($" -- Skipping pain tolerance check!"); return(true); } if (__instance.ParentActor == null) { return(true); } Mod.Log.Info?.Write($"Checking pilot: {__instance.ParentActor.DistinctId()} to resist injury of type: {___injuryReason}"); // Compute the resist penalty for each damage type that we support if (damageType == DamageType.HeadShot) { Mod.Log.Info?.Write($" Actor suffered a headshot, injury resist was set to: {ModState.InjuryResistPenalty}"); } else if (damageType == DamageType.AmmoExplosion) { Mod.Log.Info?.Write($" Actor suffered an ammo explosion, injury resist was set to: {ModState.InjuryResistPenalty}"); } else if (damageType == DamageType.Knockdown || damageType == DamageType.KnockdownSelf) { ModState.InjuryResistPenalty = Mod.Config.Combat.PainTolerance.KnockdownResistPenalty; Mod.Log.Info?.Write($" Actor was knocked down, setting injury resist to: {ModState.InjuryResistPenalty}"); } else if (damageType == DamageType.SideTorso) { ModState.InjuryResistPenalty = Mod.Config.Combat.PainTolerance.SideLocationDestroyedResistPenalty; Mod.Log.Info?.Write($" Actor torso/side destroyed, setting injury resist to: {ModState.InjuryResistPenalty}"); } else if (damageType == DamageType.Overheat || damageType == DamageType.OverheatSelf || "OVERHEATED".Equals(__instance.InjuryReasonDescription, StringComparison.InvariantCultureIgnoreCase)) { // comparison string must match label in https://github.com/BattletechModders/MechEngineer/blob/master/source/Features/ShutdownInjuryProtection/Patches/Pilot_InjuryReasonDescription_Patch.cs Mod.Log.Debug?.Write($" Actor damage from overheating or ME heatDamage injury, computing overheat ratio."); float overheatRatio = PainHelper.CalculateOverheatRatio(__instance.ParentActor as Mech); int overheatPenalty = (int)Math.Floor(overheatRatio * Mod.Config.Combat.PainTolerance.OverheatResistPenaltyPerHeatPercentile); Mod.Log.Debug?.Write($" overheatPenalty:{overheatPenalty} = " + $"Floor( overheatRatio:{overheatRatio} * penaltyPerOverheatDamage{Mod.Config.Combat.PainTolerance.OverheatResistPenaltyPerHeatPercentile} )"); ModState.InjuryResistPenalty = overheatPenalty; Mod.Log.Info?.Write($" Actor overheated, setting injury resist to: {ModState.InjuryResistPenalty}"); } // Check head injury if (__instance.ParentActor.ImmuneToHeadInjuries()) { Mod.Log.Info?.Write($"Ignoring head injury on actor: {__instance.ParentActor.DistinctId()} due to stat"); return(false); } // Need default resistance? if (ModState.InjuryResistPenalty != -1) { bool success = PainHelper.MakeResistCheck(__instance); if (success) { Mod.Log.Info?.Write($"Ignoring {___injuryReason} injury on pilot."); // Publish a floatie string localText = new Text(Mod.LocalizedText.Floaties[ModText.FT_InjuryResist], new object[] { }).ToString(); IStackSequence stackSequence = new ShowActorInfoSequence(__instance.ParentActor, localText, FloatieMessage.MessageNature.PilotInjury, useCamera: false); SharedState.Combat.MessageCenter.PublishMessage(new AddSequenceToStackMessage(stackSequence)); return(false); } else { Mod.Log.Info?.Write($"Pilot will suffer injury type: {___injuryReason}."); } ModState.InjuryResistPenalty = -1; } return(true); }
internal static void ShowInjuryMessage(this Pilot pilot, string message) { var sequence = new ShowActorInfoSequence(pilot.ParentActor, message, FloatieMessage.MessageNature.PilotInjury, true); pilot.ParentActor.Combat.MessageCenter.PublishMessage(new AddSequenceToStackMessage(sequence)); }