diff --git a/IRTweaks/IRTweaks/IRTweaks.csproj b/IRTweaks/IRTweaks/IRTweaks.csproj
index 028a86c..6d5e7e1 100644
--- a/IRTweaks/IRTweaks/IRTweaks.csproj
+++ b/IRTweaks/IRTweaks/IRTweaks.csproj
@@ -120,6 +120,7 @@
+
diff --git a/IRTweaks/IRTweaks/ModConfig.cs b/IRTweaks/IRTweaks/ModConfig.cs
index c250e54..de0bc37 100644
--- a/IRTweaks/IRTweaks/ModConfig.cs
+++ b/IRTweaks/IRTweaks/ModConfig.cs
@@ -161,6 +161,7 @@ public class FixesFlags
public bool WeakAgainstMeleeFix = true;
// Misc
+ public bool CheatDetection = true;
public bool DisableCampaign = true;
public bool DisableLowFundsNotification = true;
public bool DisableMPHashCalculation = true;
diff --git a/IRTweaks/IRTweaks/ModState.cs b/IRTweaks/IRTweaks/ModState.cs
index 32dcaa0..19b8419 100644
--- a/IRTweaks/IRTweaks/ModState.cs
+++ b/IRTweaks/IRTweaks/ModState.cs
@@ -31,6 +31,13 @@ public static class InstantiatedDifficultySettings
public static bool WasCTDestroyed = false;
+ public static Dictionary UnitStartingArmor = new Dictionary();
+ public static Dictionary UnitCurrentArmor = new Dictionary();
+ public static Dictionary UnitCurrentHeat = new Dictionary();
+ public static Dictionary PilotCurrentFreeXP = new Dictionary();
+ public static Dictionary PilotDefCurrentFreeXP = new Dictionary();
+ public static Dictionary SimGameFunds = new Dictionary();
+
public static void Reset() {
// Reinitialize state
PilotCalledShotModifiers.Clear();
diff --git a/IRTweaks/IRTweaks/Modules/Misc/CheatDetection.cs b/IRTweaks/IRTweaks/Modules/Misc/CheatDetection.cs
new file mode 100644
index 0000000..f48e49f
--- /dev/null
+++ b/IRTweaks/IRTweaks/Modules/Misc/CheatDetection.cs
@@ -0,0 +1,318 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.Remoting.Messaging;
+using System.Text;
+using System.Threading.Tasks;
+using BattleTech;
+using BattleTech.UI;
+using Harmony;
+using HBS;
+using IRTweaks.Helper;
+
+namespace IRTweaks.Modules.Misc
+{
+ [HarmonyPatch(typeof(Team), "AddUnit")]
+ public static class Mech_AddToTeam
+ {
+ static bool Prepare() => Mod.Config.Fixes.CheatDetection && false; //disabled for now
+
+ public static void Postfix(Team __instance, AbstractActor unit)
+ {
+ if (unit is Mech mech)
+ {
+ var startingArmor = mech.SummaryArmorCurrent;
+ ModState.UnitStartingArmor.Add(mech.GUID, startingArmor);
+ ModState.UnitCurrentArmor.Add(mech.GUID, startingArmor);
+ ModState.UnitCurrentHeat.Add(mech.GUID, mech.CurrentHeat);
+ }
+ }
+ }
+
+ [HarmonyPatch(typeof(Mech), "ApplyArmorStatDamage")]
+ public static class Mech_ApplyArmorStatDamage
+ {
+ static bool Prepare() => Mod.Config.Fixes.CheatDetection && false; //disabled for now
+ public static void Postfix(Mech __instance)
+ {
+
+ var sim = UnityGameInstance.BattleTechGame.Simulation;
+ if (!ModState.UnitStartingArmor.ContainsKey(__instance.GUID))
+ {
+ ModState.UnitStartingArmor.Add(__instance.GUID, __instance.CurrentArmor);
+ Mod.Log.Info?.Write($"Added key to UnitStartingArmor with CurrentArmor... this should have been done already though.");
+ }
+
+ if (!ModState.UnitCurrentArmor.ContainsKey(__instance.GUID))
+ {
+ ModState.UnitCurrentArmor.Add(__instance.GUID, __instance.CurrentArmor);
+ Mod.Log.Info?.Write($"Added key to UnitStartingArmor with CurrentArmor... this should have been done already though.");
+ }
+
+ if (__instance.CurrentArmor > ModState.UnitCurrentArmor[__instance.GUID])
+ {
+ sim.CompanyStats.AddStatistic("CheaterCheaterPumpkinEater", true);
+ Mod.Log.Info?.Write($"Caught you, you little shit. Cheated armor.");
+ }
+
+ if (__instance.CurrentArmor > ModState.UnitStartingArmor[__instance.GUID])
+ {
+ sim.CompanyStats.AddStatistic("CheaterCheaterPumpkinEater", true);
+ Mod.Log.Info?.Write($"Caught you, you little shit. Cheated armor.");
+ }
+
+ ModState.UnitCurrentArmor[__instance.GUID] = __instance.CurrentArmor;
+ }
+ }
+
+ [HarmonyPatch(typeof(Mech))]
+ [HarmonyPatch("_heat", MethodType.Setter)]
+ public static class Mech__heat_Setter
+ {
+ static bool Prepare() => Mod.Config.Fixes.CheatDetection && false; //disabled for now
+
+ public static void Postfix(Mech __instance, int value)
+ {
+ if (!ModState.UnitCurrentHeat.ContainsKey(__instance.GUID))
+ {
+ ModState.UnitCurrentHeat.Add(__instance.GUID, __instance.CurrentHeat);
+ Mod.Log.Info?.Write($"Added key to UnitCurrentHeat with CurrentHeat value... this should have been done already though.");
+ }
+ if (ModState.UnitCurrentHeat.ContainsKey(__instance.GUID))
+ {
+ ModState.UnitCurrentHeat[__instance.GUID] = value;
+ }
+ }
+ }
+
+
+ [HarmonyPatch(typeof(Mech))]
+ [HarmonyPatch("CurrentHeat", MethodType.Getter)]
+ public static class Mech_CurrentHeat_Getter
+ {
+ static bool Prepare() => Mod.Config.Fixes.CheatDetection && false; //disabled for now. this is the one that breaks spawning and CTDs with no errors for some reason.
+
+ public static void Postfix(Mech __instance)
+ {
+ var sim = UnityGameInstance.BattleTechGame.Simulation;
+ if (!ModState.UnitCurrentHeat.ContainsKey(__instance.GUID))
+ {
+ ModState.UnitCurrentHeat.Add(__instance.GUID, __instance.CurrentHeat);
+ Mod.Log.Info?.Write($"Added key to UnitCurrentHeat with CurrentHeat value... this should have been done already though.");
+ }
+ if (__instance.CurrentHeat != ModState.UnitCurrentHeat[__instance.GUID])
+ {
+ sim.CompanyStats.AddStatistic("CheaterCheaterPumpkinEater", true);
+ Mod.Log.Info?.Write($"Caught you, you little shit. Cheated heat.");
+ }
+ }
+ }
+
+ [HarmonyPatch(typeof(CombatGameState))]
+ [HarmonyPatch("OnCombatGameDestroyed")]
+ public static class CombatGameState_OnCGSDestroyed
+ {
+ static bool Prepare() => Mod.Config.Fixes.CheatDetection && false; //disabled for now
+ public static void Prefix()
+ {
+ ModState.UnitCurrentArmor.Clear();
+ ModState.UnitCurrentHeat.Clear();
+ ModState.UnitStartingArmor.Clear();
+ }
+ }
+
+
+
+
+
+ [HarmonyPatch(typeof(PilotDef))]
+ [HarmonyPatch("ExperienceUnspent", MethodType.Setter)]
+ public static class Pilot_XPUnspent_Setter
+ {
+ static bool Prepare() => Mod.Config.Fixes.CheatDetection && false;
+
+ public static void Postfix(PilotDef __instance, int value)
+ {
+ if (UnityGameInstance.BattleTechGame.Simulation == null) return;
+ if (String.IsNullOrEmpty(__instance.Description.Id)) return;
+ if (!ModState.PilotDefCurrentFreeXP.ContainsKey(__instance.Description.Id))
+ {
+ ModState.PilotDefCurrentFreeXP.Add(__instance.Description.Id, __instance.ExperienceUnspent);
+ }
+ ModState.PilotDefCurrentFreeXP[__instance.Description.Id] = value;
+ }
+ }
+
+
+ [HarmonyPatch(typeof(PilotDef))]
+ [HarmonyPatch("ExperienceUnspent", MethodType.Getter)]
+ public static class Pilot_XPUnspent_Getter
+ {
+ static bool Prepare() => Mod.Config.Fixes.CheatDetection && false;
+
+ public static void Postfix(PilotDef __instance)
+ {
+ var sim = UnityGameInstance.BattleTechGame.Simulation;
+ if (sim == null) return;
+ if (String.IsNullOrEmpty(__instance.Description.Id)) return;
+ if (!ModState.PilotDefCurrentFreeXP.ContainsKey(__instance.Description.Id))
+ {
+ ModState.PilotDefCurrentFreeXP.Add(__instance.Description.Id, __instance.ExperienceUnspent);
+ Mod.Log.Info?.Write($"Added key to PilotCurrentXP with ExperienceUnspent value.");
+ }
+ }
+ }
+
+
+ [HarmonyPatch(typeof(Pilot))]
+ [HarmonyPatch("FromPilotDef")]
+ public static class Pilot_FromPilotDef
+ {
+ static bool Prepare() => Mod.Config.Fixes.CheatDetection;
+
+ public static void Postfix(Pilot __instance)
+ {
+ var sim = UnityGameInstance.BattleTechGame.Simulation;
+ if (sim == null) return;
+ if (String.IsNullOrEmpty(__instance.GUID)) return;
+ if (!ModState.PilotCurrentFreeXP.ContainsKey(__instance.GUID))
+ {
+ ModState.PilotCurrentFreeXP.Add(__instance.GUID, __instance.UnspentXP);
+ Mod.Log.Info?.Write($"{__instance.Description.Id}: Set ExperienceUnspent to UnspentXP value.");
+ }
+
+ ModState.PilotCurrentFreeXP[__instance.GUID] = __instance.pilotDef.ExperienceUnspent;
+ }
+ }
+
+ [HarmonyPatch(typeof(Pilot))]
+ [HarmonyPatch("AddExperience")]
+ public static class Pilot_AddXP
+ {
+ static bool Prepare() => Mod.Config.Fixes.CheatDetection;
+
+ public static void Postfix(Pilot __instance, int stackID, string sourceID, int value)
+ {
+ var sim = UnityGameInstance.BattleTechGame.Simulation;
+ if (sim == null) return;
+ if (String.IsNullOrEmpty(__instance.GUID)) return;
+ if (!ModState.PilotCurrentFreeXP.ContainsKey(__instance.GUID))
+ {
+ ModState.PilotCurrentFreeXP.Add(__instance.GUID, __instance.UnspentXP);
+ Mod.Log.Info?.Write($"{__instance.Description.Id}: Added key to PilotCurrentXP with UnspentXP value.");
+ }
+
+ ModState.PilotCurrentFreeXP[__instance.GUID] += value;
+ }
+ }
+
+ [HarmonyPatch(typeof(Pilot))]
+ [HarmonyPatch("SpendExperience")]
+ public static class Pilot_SpendXP
+ {
+ static bool Prepare() => Mod.Config.Fixes.CheatDetection;
+
+ public static void Prefix(Pilot __instance)
+ {
+ var sim = UnityGameInstance.BattleTechGame.Simulation;
+ if (sim == null) return;
+ if (String.IsNullOrEmpty(__instance.GUID)) return;
+ if (!ModState.PilotCurrentFreeXP.ContainsKey(__instance.GUID))
+ {
+ ModState.PilotCurrentFreeXP.Add(__instance.GUID, __instance.UnspentXP);
+ Mod.Log.Info?.Write($"{__instance.Description.Id}: Added key to PilotCurrentXP with UnspentXP value.");
+ }
+ }
+
+ public static void Postfix(Pilot __instance, int stackID, string sourceID, uint value)
+ {
+ var sim = UnityGameInstance.BattleTechGame.Simulation;
+ if (sim == null) return;
+ if (String.IsNullOrEmpty(__instance.GUID)) return;
+ if (!ModState.PilotCurrentFreeXP.ContainsKey(__instance.GUID))
+ {
+ ModState.PilotCurrentFreeXP.Add(__instance.GUID, __instance.UnspentXP);
+ Mod.Log.Info?.Write($"{__instance.Description.Id}: Added key to PilotCurrentXP with UnspentXP value.");
+ }
+
+ ModState.PilotCurrentFreeXP[__instance.GUID] -= (int)value;
+ if (__instance.UnspentXP != ModState.PilotCurrentFreeXP[__instance.GUID])
+ {
+ sim.CompanyStats.AddStatistic("CheaterCheaterPumpkinEater", true);
+ Mod.Log.Info?.Write($"Caught you, you little shit. Cheated experience.");
+ GenericPopupBuilder.Create("CHEAT DETECTED!", "t-bone thinks you're cheating. if you aren't, you should let the RT crew know on Discord.").AddButton("Okay", null, true, null).CancelOnEscape().AddFader(new UIColorRef?(LazySingletonBehavior.Instance.UILookAndColorConstants.PopupBackfill), 0f, true).Render();
+ }
+ }
+ }
+
+
+
+ [HarmonyPatch(typeof(SimGameState))]
+ [HarmonyPatch("AddFunds")]
+ public static class SimGameState_AddFunds
+ {
+ static bool Prepare() => Mod.Config.Fixes.CheatDetection;
+
+ public static void Prefix(SimGameState __instance, int val)
+ {
+ if (String.IsNullOrEmpty(__instance.InstanceGUID)) return;
+ if (!ModState.SimGameFunds.ContainsKey(__instance.InstanceGUID))
+ {
+ ModState.SimGameFunds.Add(__instance.InstanceGUID, __instance.Funds);
+ Mod.Log.Info?.Write($"Added key to SimGameFunds with Funds value; this should have been done already...WTF");
+ }
+ if (__instance.Funds != ModState.SimGameFunds[__instance.InstanceGUID])
+ {
+ __instance.CompanyStats.AddStatistic("CheaterCheaterPumpkinEater", true);
+ Mod.Log.Info?.Write($"Caught you, you little shit. Cheated money.");
+ }
+ }
+
+ public static void Postfix(SimGameState __instance, int val)
+ {
+ if (!ModState.SimGameFunds.ContainsKey(__instance.InstanceGUID))
+ {
+ ModState.SimGameFunds.Add(__instance.InstanceGUID, __instance.Funds);
+ Mod.Log.Info?.Write($"Added key to SimGameFunds with Funds value; this should have been done already...WTF");
+ }
+ ModState.SimGameFunds[__instance.InstanceGUID] += val;
+ if (__instance.Funds != ModState.SimGameFunds[__instance.InstanceGUID])
+ {
+ __instance.CompanyStats.AddStatistic("CheaterCheaterPumpkinEater", true);
+ Mod.Log.Info?.Write($"Caught you, you little shit. Cheated money.");
+ GenericPopupBuilder.Create("CHEAT DETECTED!", "t-bone thinks you're cheating. if you aren't, you should let the RT crew know on Discord.").AddButton("Okay", null, true, null).CancelOnEscape().AddFader(new UIColorRef?(LazySingletonBehavior.Instance.UILookAndColorConstants.PopupBackfill), 0f, true).Render();
+ }
+ }
+
+ }
+
+
+ [HarmonyPatch(typeof(SimGameState))]
+ [HarmonyPatch("Rehydrate")]
+ public static class SimGameState_Rehydrate_CH
+ {
+ static bool Prepare() => Mod.Config.Fixes.CheatDetection;
+
+ public static void Postfix(SimGameState __instance)
+ {
+ if (!ModState.SimGameFunds.ContainsKey(__instance.InstanceGUID))
+ {
+ ModState.SimGameFunds.Add(__instance.InstanceGUID, __instance.Funds);
+ Mod.Log.Info?.Write($"Added key to SimGameFunds with Funds value.");
+ }
+
+ var currentPilots = new List(__instance.PilotRoster);
+ currentPilots.Add(__instance.Commander);
+ foreach (var pilot in currentPilots)
+ {
+ if (String.IsNullOrEmpty(pilot.GUID)) return;
+ if (!ModState.PilotCurrentFreeXP.ContainsKey(pilot.GUID))
+ {
+ ModState.PilotCurrentFreeXP.Add(pilot.GUID, pilot.UnspentXP);
+ Mod.Log.Info?.Write($"{pilot.Description.Id}: Added key to PilotCurrentXP with ExperienceUnspent value.");
+ }
+ }
+ }
+ }
+
+}
diff --git a/IRTweaks/IRTweaks/Modules/Misc/PilotDuplicationFix.cs b/IRTweaks/IRTweaks/Modules/Misc/PilotDuplicationFix.cs
new file mode 100644
index 0000000..3a1e7b5
--- /dev/null
+++ b/IRTweaks/IRTweaks/Modules/Misc/PilotDuplicationFix.cs
@@ -0,0 +1,39 @@
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using System.Reflection.Emit;
+using BattleTech;
+using BattleTech.UI;
+using Harmony;
+
+namespace IRTweaks.Modules.Misc
+{
+ [HarmonyPatch(typeof(SimGameState), "AddPilotToRoster")]
+ static class SimGameState_AddPilotToRoster
+ {
+ static bool Prefix(SimGameState __instance, PilotDef pilotDef)
+ {
+ if (__instance.PilotRoster.Any(x=>x.pilotDef.Description.Id == pilotDef.Description.Id))
+ {
+ Mod.Log.Info?.Write($"Pilot {pilotDef.Description.Id} already in Roster! Not adding to Roster!");
+ return false;
+ }
+ return true;
+ }
+ }
+
+
+ [HarmonyPatch(typeof(SimGameState), "AddPilotToHiringHall")]
+ static class SimGameState_AddPilotToHiringHall
+ {
+ static bool Prefix(SimGameState __instance, PilotDef pilotDef)
+ {
+ if (__instance.PilotRoster.Any(x=>x.pilotDef.Description.Id == pilotDef.Description.Id))
+ {
+ Mod.Log.Info?.Write($"Pilot {pilotDef.Description.Id} already in Roster! Not adding to Hiring Hall!");
+ return false;
+ }
+ return true;
+ }
+ }
+}
diff --git a/IRTweaks/IRTweaks/Properties/AssemblyInfo.cs b/IRTweaks/IRTweaks/Properties/AssemblyInfo.cs
index 39fd90f..db33495 100644
--- a/IRTweaks/IRTweaks/Properties/AssemblyInfo.cs
+++ b/IRTweaks/IRTweaks/Properties/AssemblyInfo.cs
@@ -31,5 +31,5 @@
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
-[assembly: AssemblyVersion("0.9.2.2")]
-[assembly: AssemblyFileVersion("0.9.2.2")]
+[assembly: AssemblyVersion("0.9.2.3")]
+[assembly: AssemblyFileVersion("0.9.2.3")]