Some mods target the Mono version of the game, which is available by opting into the Steam beta branch "alternate"
Decompiled source of BetterFiends v1.1.1
BetterFiends - Il2Cpp.dll
Decompiled 3 weeks agousing System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Text.RegularExpressions; using BetterFiends; using BetterFiends.Configuration; using BetterFiends.Services; using ExampleMod.Objects; using ExampleMod.Services; using HarmonyLib; using Il2CppFishNet.Connection; using Il2CppFishNet.Object; using Il2CppScheduleOne.DevUtilities; using Il2CppScheduleOne.Dialogue; using Il2CppScheduleOne.Economy; using Il2CppScheduleOne.ItemFramework; using Il2CppScheduleOne.Law; using Il2CppScheduleOne.NPCs; using Il2CppScheduleOne.NPCs.Behaviour; using Il2CppScheduleOne.PlayerScripts; using Il2CppScheduleOne.Product; using Il2CppScheduleOne.UI; using Il2CppScheduleOne.UI.Handover; using Il2CppScheduleOne.VoiceOver; using Il2CppSystem.Collections.Generic; using MelonLoader; using MelonLoader.Utils; using Newtonsoft.Json; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)] [assembly: MelonInfo(typeof(global::BetterFiends.BetterFiends), "BetterFiends", "1.0", "EndureBlackout", "https://thunderstore.io/c/schedule-i/p/EndureBlackout/BetterFiends/")] [assembly: MelonColor] [assembly: MelonGame("TVGS", "Schedule I")] [assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")] [assembly: AssemblyCompany("BetterFiends")] [assembly: AssemblyConfiguration("Il2Cpp")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+aa2b1bc4fe173ba1028a6e35360273f49e46ced2")] [assembly: AssemblyProduct("BetterFiends")] [assembly: AssemblyTitle("BetterFiends")] [assembly: AssemblyVersion("1.0.0.0")] namespace ExampleMod.Services { public class JsonDataStoreService<T> where T : class { private readonly string filePath; private List<T> items; public JsonDataStoreService(string filePath) { this.filePath = filePath; LoadData(); } private void LoadData() { try { if (File.Exists(filePath)) { string text = File.ReadAllText(filePath); items = JsonConvert.DeserializeObject<List<T>>(text); } else { items = new List<T>(); SaveData(); } } catch (Exception ex) { MelonLogger.Error("Error loading data from " + filePath + ": " + ex.Message); items = new List<T>(); } } public void SaveData() { try { string directoryName = Path.GetDirectoryName(filePath); if (!Directory.Exists(directoryName)) { Directory.CreateDirectory(directoryName); } string contents = JsonConvert.SerializeObject((object)items, (Formatting)1); File.WriteAllText(filePath, contents); } catch (Exception ex) { MelonLogger.Error("Failed to save data: " + ex.Message); } } public void Add(T item) { items.Add(item); SaveData(); } public void AddRange(IEnumerable<T> newItems) { items.AddRange(newItems); SaveData(); } public bool Remove(T item) { bool flag = items.Remove(item); if (flag) { SaveData(); } return flag; } public void Clear() { items.Clear(); SaveData(); } public List<T> GetAll() { return new List<T>(items); } public T FindFirst(Func<T, bool> predicate) { return items.FirstOrDefault(predicate); } public List<T> FindAll(Func<T, bool> predicate) { return items.Where(predicate).ToList(); } public void Update(Func<T, bool> predicate, Action<T> updateAction) { foreach (T item in items.Where(predicate)) { updateAction(item); } SaveData(); } } } namespace ExampleMod.Objects { public class Fiend { public string Id { get; set; } public string Name { get; set; } public Product LastConsumed { get; set; } } public class Product { public string Id { get; set; } public string Name { get; set; } public float Addictiveness { get; set; } public int Quality { get; set; } } } namespace BetterFiends { public static class BuildInfo { public const string Name = "BetterFiends"; public const string Description = "Makes NPCs fiend for your products or become narcs"; public const string Author = "EndureBlackout"; public const string Company = null; public const string Version = "1.0"; public const string DownloadLink = "https://thunderstore.io/c/schedule-i/p/EndureBlackout/BetterFiends/"; } public class BetterFiends : MelonMod { public static Harmony harmony; public static bool appUpdate = false; public static AssetBundle testAppBundle; public static Object test; public static GameObject mainCanvasPrefab; private string fiendDataPath; public static JsonDataStoreService<Fiend> fiendData; private static string configPath; public static Config config; public static List<NPC> fiendList = new List<NPC>(); public override void OnInitializeMelon() { //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Expected O, but got Unknown ((MelonBase)this).OnInitializeMelon(); if (harmony == null) { harmony = new Harmony("com.endureblackout.betterfiends"); try { fiendDataPath = Path.Combine(MelonEnvironment.UserDataDirectory, "fiend_data.json"); fiendData = new JsonDataStoreService<Fiend>(fiendDataPath); LoadConfig(); MelonLogger.Msg("BetterFiends: Patches applied successfully."); } catch (Exception arg) { MelonLogger.Error($"BetterFiends: Failed to apply patches. Error: {arg}"); } } } private void LoadConfig() { configPath = Path.Combine(MelonEnvironment.UserDataDirectory, "better-fiends-config.json"); if (File.Exists(configPath)) { string text = File.ReadAllText(configPath); config = JsonConvert.DeserializeObject<Config>(text); MelonLogger.Msg("[BetterFiends]: Config loaded successfully."); } else { Config newConfig = new Config(); SaveConfig(newConfig); } } private static void SaveConfig(Config newConfig) { try { string contents = JsonConvert.SerializeObject((object)newConfig, (Formatting)1); File.WriteAllText(configPath, contents); MelonLogger.Msg("[BetterFiends]: New config filed created!"); config = newConfig; } catch (Exception ex) { MelonLogger.Error("[BetterFiends]: There was an issue saving the config file: " + ex.Message); } } } } namespace BetterFiends.Services { public static class RenderService { public static void SetWorldspaceDialogueRenderer(NPC npc, string message) { Transform val = FindChildRecursive(((Component)npc.Avatar.BodyContainer).transform, "mixamorig:Spine2"); if ((Object)(object)val != (Object)null) { WorldspaceDialogueRenderer worldspaceDialogueRenderer = GetWorldspaceDialogueRenderer(npc); if ((Object)(object)worldspaceDialogueRenderer != (Object)null) { Object.Destroy((Object)(object)worldspaceDialogueRenderer); } } } public static Transform FindChildRecursive(Transform parent, string name) { for (int i = 0; i < parent.childCount; i++) { Transform child = parent.GetChild(i); if (((Object)child).name == name) { return child; } Transform val = FindChildRecursive(child, name); if ((Object)(object)val != (Object)null) { return val; } } return null; } public static WorldspaceDialogueRenderer GetWorldspaceDialogueRenderer(NPC npc) { Transform val = FindChildRecursive(((Component)npc.Avatar.BodyContainer).transform, "mixamorig:Spine2"); if ((Object)(object)val != (Object)null) { WorldspaceDialogueRenderer componentInChildren = ((Component)val).GetComponentInChildren<WorldspaceDialogueRenderer>(); if ((Object)(object)componentInChildren != (Object)null) { return componentInChildren; } MelonLogger.Warning("No WorldspaceDialogueRenderer found under Spine2 on " + ((Object)npc).name); } return null; } } } namespace BetterFiends.Patching { [HarmonyPatch(typeof(RequestProductBehaviour))] public static class NPC_Request_Product_Patch { [HarmonyPatch("Begin")] [HarmonyPostfix] public static void BeginPostfix(RequestProductBehaviour __instance) { try { if (BetterFiends.fiendList.Contains(((Behaviour)__instance).Npc)) { GreetingOverride requestGreeting = __instance.requestGreeting; string text = BetterFiends.fiendData.FindFirst((Fiend x) => x.Id == ((Behaviour)__instance).Npc.BakedGUID)?.LastConsumed?.Name ?? null; if (requestGreeting != null) { requestGreeting.Greeting = ((text != null) ? ("Yo I need more of that " + text + "... NOW!") : "Yo I need more of your shit... NOW!"); requestGreeting.PlayVO = true; requestGreeting.VOType = (EVOLineType)4; } } } catch (Exception ex) { MelonLogger.Error("Error in Begin Postfix: " + ex.Message); } } [HarmonyPatch("RequestRejected")] [HarmonyPostfix] public static void RequestRejectedPostFix(RequestProductBehaviour __instance) { NPC npc = ((Behaviour)__instance).Npc; if (BetterFiends.fiendList.Contains(npc)) { Customer component = ((Component)npc).GetComponent<Customer>(); float num = CalculateNarcProbability(component, npc); if (Random.value < num) { DoNarcBehavior(npc, __instance.TargetPlayer, "Better get me that product next time!"); } else { DoRejectBehavior(npc, "I really needed that shit!"); } BetterFiends.fiendList.Remove(npc); } } [HarmonyPatch("RequestAccepted")] [HarmonyPostfix] public static void RequestAcceptedPostFix(RequestProductBehaviour __instance) { NPC npc = ((Behaviour)__instance).Npc; if (BetterFiends.fiendList.Contains(npc)) { Customer component = ((Component)npc).GetComponent<Customer>(); float num = CalculateNarcProbability(component, npc); if (Random.value < num) { DoNarcBehavior(npc, __instance.TargetPlayer, "No hard feelings man, they said I would do hard time!"); } BetterFiends.fiendList.Remove(npc); } } [HarmonyPatch("HandoverClosed")] [HarmonyPrefix] public static bool HandoverClosedPrefix(RequestProductBehaviour __instance, EHandoverOutcome outcome, List<ItemInstance> items, float askingPrice) { if (BetterFiends.fiendList.Contains(((Behaviour)__instance).Npc)) { NPC npc = ((Behaviour)__instance).Npc; Customer component = ((Component)npc).GetComponent<Customer>(); Fiend fiend = BetterFiends.fiendData.FindFirst((Fiend x) => x.Id == npc.BakedGUID); if (fiend != null && fiend.LastConsumed != null) { ItemInstance val = null; Enumerator<ItemInstance> enumerator = items.GetEnumerator(); while (enumerator.MoveNext()) { ItemInstance current = enumerator.Current; if (current.ID == fiend.LastConsumed.Id) { val = current; } } if (val == null) { DoRejectBehavior(npc, "This ain't the shit I was looking for!"); Singleton<HandoverScreen>.Instance.ClearCustomerSlots(true); return false; } } } return true; } private static float CalculateNarcProbability(Customer customer, NPC npc) { float num = 0.05f; float num2 = Mathf.Clamp01(customer.CurrentAddiction); float num3 = 1f - num2 * 0.8f; float num4 = 100f / (100f + customer.CustomerData.MaxWeeklySpend); float num5 = num * num3 * num4; MelonLogger.Msg($"Narc chance: {num5}"); return Mathf.Clamp01(num5 * BetterFiends.config.FiendProbabilityMultiplier); } private static void DoNarcBehavior(NPC npc, Player player, string message) { //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Expected O, but got Unknown npc.PlayVO((EVOLineType)10); npc.dialogueHandler.ShowWorldspaceDialogue(npc.dialogueHandler.Database.GetLine((EDialogueModule)3, "sample_offer_rejected_police"), 5f); npc.actions.SetCallPoliceBehaviourCrime((Crime)new AttemptingToSell()); npc.actions.CallPolice_Networked(player); WorldspaceDialogueRenderer worldspaceDialogueRenderer = RenderService.GetWorldspaceDialogueRenderer(npc); worldspaceDialogueRenderer.ShowText(message, 0f); try { npc.PlayVO((EVOLineType)14); } catch { } } private static void DoRejectBehavior(NPC npc, string message) { //IL_0022: Unknown result type (might be due to invalid IL or missing references) if (BetterFiends.config.EnableAggressiveBehavior) { float num = default(float); npc.behaviour.CombatBehaviour.SetTarget((NetworkConnection)null, ((NetworkBehaviour)Player.GetClosestPlayer(((Component)npc).transform.position, ref num, (List<Player>)null)).NetworkObject); ((Behaviour)npc.behaviour.CombatBehaviour).Enable_Networked((NetworkConnection)null); } WorldspaceDialogueRenderer worldspaceDialogueRenderer = RenderService.GetWorldspaceDialogueRenderer(npc); worldspaceDialogueRenderer.ShowText(message, 0f); } } [HarmonyPatch(typeof(ConsumeProductBehaviour), "TryConsume")] public static class NPC_ConsumeProduct_Patch { [CompilerGenerated] private sealed class <HandlePlayerInteraction>d__1 : IEnumerator<object>, IDisposable, IEnumerator { private int <>1__state; private object <>2__current; public NPC npc; public Player player; private List<Behaviour> <existing>5__1; private Enumerator<Behaviour> <>s__2; private Behaviour <behaviour>5__3; private Enumerator<Behaviour> <>s__4; private Behaviour <beh>5__5; object IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <HandlePlayerInteraction>d__1(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <existing>5__1 = null; <>s__2 = null; <behaviour>5__3 = null; <>s__4 = null; <beh>5__5 = null; <>1__state = -2; } private bool MoveNext() { //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Expected O, but got Unknown switch (<>1__state) { default: return false; case 0: <>1__state = -1; <>2__current = (object)new WaitForSeconds(90f); <>1__state = 1; return true; case 1: <>1__state = -1; PauseBehaviors(npc); ((Behaviour)npc.behaviour.RequestProductBehaviour).Enabled = true; ((Behaviour)npc.behaviour.RequestProductBehaviour).SendEnable(); npc.behaviour.RequestProductBehaviour.TargetPlayer = player; npc.behaviour.AddEnabledBehaviour((Behaviour)(object)npc.behaviour.RequestProductBehaviour); <existing>5__1 = npc.behaviour.enabledBehaviours; <>s__2 = <existing>5__1.GetEnumerator(); while (<>s__2.MoveNext()) { <behaviour>5__3 = <>s__2.Current; if (!<behaviour>5__3.Active) { <behaviour>5__3.Active = true; if (((NetworkBehaviour)npc).LocalConnection != (NetworkConnection)null) { <behaviour>5__3.Enable_Networked(((NetworkBehaviour)npc).LocalConnection); <behaviour>5__3.Begin_Networked(((NetworkBehaviour)npc).LocalConnection); BetterFiends.fiendList.Add(npc); } } <behaviour>5__3 = null; } <>s__2 = null; if (<existing>5__1.Count > 0) { <>s__4 = <existing>5__1.GetEnumerator(); while (<>s__4.MoveNext()) { <beh>5__5 = <>s__4.Current; if (<beh>5__5 is RequestProductBehaviour) { npc.behaviour.activeBehaviour = <beh>5__5; } <beh>5__5 = null; } <>s__4 = null; } return false; } } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } public static void Postfix(ConsumeProductBehaviour __instance) { //IL_013c: Unknown result type (might be due to invalid IL or missing references) //IL_0146: Expected I4, but got Unknown //IL_00d2: Unknown result type (might be due to invalid IL or missing references) //IL_00dc: Expected I4, but got Unknown try { ProductItemInstance product = __instance.product; string pattern = "\\s*\\(.*[uU]npackaged\\)"; if (product == null) { return; } Player local = Player.Local; NPC npc = ((Behaviour)__instance).Npc; Fiend fiendData = BetterFiends.fiendData.FindFirst((Fiend x) => x.Id == npc.BakedGUID); if (fiendData == null) { MelonLogger.Msg("[BetterFiends]: Fiend data not found, creating new fiend data"); fiendData = new Fiend { Id = npc.BakedGUID, Name = ((Object)npc).name, LastConsumed = new Product { Id = ((ItemInstance)product).ID, Name = Regex.Replace(((ItemInstance)product).Name, pattern, ""), Addictiveness = product.GetAddictiveness(), Quality = (int)((QualityItemInstance)product).Quality } }; BetterFiends.fiendData.Add(fiendData); } else { fiendData.LastConsumed = new Product { Id = ((ItemInstance)product).ID, Name = Regex.Replace(((ItemInstance)product).Name, pattern, ""), Addictiveness = product.GetAddictiveness(), Quality = (int)((QualityItemInstance)product).Quality }; BetterFiends.fiendData.Update((Fiend x) => x.Id == npc.BakedGUID, delegate(Fiend y) { y.LastConsumed = fiendData.LastConsumed; }); } if (local.IsLocalPlayer) { Customer component = ((Component)npc).GetComponent<Customer>(); float currentAddiction = component.CurrentAddiction; float addictiveness = product.GetAddictiveness(); float num = CalculateFiendProbability(currentAddiction, addictiveness); MelonLogger.Msg($"[BetterFiends]: {((Object)npc).name} has a {num} chance to fiend for more product."); if (Random.value < num) { MelonLogger.Msg("[BetterFiends]: " + ((Object)npc).name + " is fiending for more product. If denied, they will act out in violence or narc on you."); MelonCoroutines.Start(HandlePlayerInteraction(npc, local)); } } } catch (Exception ex) { MelonLogger.Error("[BetterFiends]: Error in TryConsume Postfix: " + ex.Message); } } [IteratorStateMachine(typeof(<HandlePlayerInteraction>d__1))] private static IEnumerator HandlePlayerInteraction(NPC npc, Player player) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <HandlePlayerInteraction>d__1(0) { npc = npc, player = player }; } private static void PauseBehaviors(NPC npc) { List<Behaviour> enabledBehaviours = npc.behaviour.enabledBehaviours; Enumerator<Behaviour> enumerator = enabledBehaviours.GetEnumerator(); while (enumerator.MoveNext()) { Behaviour current = enumerator.Current; if (current.Active) { current.Active = false; current.BehaviourUpdate(); if (((NetworkBehaviour)npc).LocalConnection != (NetworkConnection)null) { current.End_Networked(((NetworkBehaviour)npc).LocalConnection); } } } } private static float CalculateFiendProbability(float currentAddiction, float productAddictiveness) { float num = Mathf.Pow(currentAddiction, 1.5f) * (1f + Random.Range(-0.1f, 0.1f)); float num2 = productAddictiveness * (1f + num); float num3 = 1f / (1f + Mathf.Exp(0f - num2 + 3f)) * 0.5f; return Mathf.Clamp01(num3 * BetterFiends.config.FiendProbabilityMultiplier); } } } namespace BetterFiends.Configuration { public class Config { public bool EnableAggressiveBehavior { get; set; } = true; public float FiendProbabilityMultiplier { get; set; } = 1f; public float NarcProbabilityMultiplier { get; set; } = 1f; } }
BetterFiends - Mono.dll
Decompiled 3 weeks agousing System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Text.RegularExpressions; using BetterFiends; using BetterFiends.Configuration; using BetterFiends.Services; using ExampleMod.Objects; using ExampleMod.Services; using FishNet.Connection; using FishNet.Object; using HarmonyLib; using MelonLoader; using MelonLoader.Utils; using Newtonsoft.Json; using ScheduleOne.DevUtilities; using ScheduleOne.Dialogue; using ScheduleOne.Economy; using ScheduleOne.ItemFramework; using ScheduleOne.Law; using ScheduleOne.NPCs; using ScheduleOne.NPCs.Behaviour; using ScheduleOne.PlayerScripts; using ScheduleOne.Product; using ScheduleOne.UI; using ScheduleOne.UI.Handover; using ScheduleOne.VoiceOver; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)] [assembly: MelonInfo(typeof(global::BetterFiends.BetterFiends), "BetterFiends", "1.0", "EndureBlackout", "https://thunderstore.io/c/schedule-i/p/EndureBlackout/BetterFiends/")] [assembly: MelonColor] [assembly: MelonGame("TVGS", "Schedule I")] [assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")] [assembly: AssemblyCompany("BetterFiends")] [assembly: AssemblyConfiguration("Mono")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+aa2b1bc4fe173ba1028a6e35360273f49e46ced2")] [assembly: AssemblyProduct("BetterFiends")] [assembly: AssemblyTitle("BetterFiends")] [assembly: AssemblyVersion("1.0.0.0")] namespace ExampleMod.Services { public class JsonDataStoreService<T> where T : class { private readonly string filePath; private List<T> items; public JsonDataStoreService(string filePath) { this.filePath = filePath; LoadData(); } private void LoadData() { try { if (File.Exists(filePath)) { string text = File.ReadAllText(filePath); items = JsonConvert.DeserializeObject<List<T>>(text); } else { items = new List<T>(); SaveData(); } } catch (Exception ex) { MelonLogger.Error("Error loading data from " + filePath + ": " + ex.Message); items = new List<T>(); } } public void SaveData() { try { string directoryName = Path.GetDirectoryName(filePath); if (!Directory.Exists(directoryName)) { Directory.CreateDirectory(directoryName); } string contents = JsonConvert.SerializeObject((object)items, (Formatting)1); File.WriteAllText(filePath, contents); } catch (Exception ex) { MelonLogger.Error("Failed to save data: " + ex.Message); } } public void Add(T item) { items.Add(item); SaveData(); } public void AddRange(IEnumerable<T> newItems) { items.AddRange(newItems); SaveData(); } public bool Remove(T item) { bool flag = items.Remove(item); if (flag) { SaveData(); } return flag; } public void Clear() { items.Clear(); SaveData(); } public List<T> GetAll() { return new List<T>(items); } public T FindFirst(Func<T, bool> predicate) { return items.FirstOrDefault(predicate); } public List<T> FindAll(Func<T, bool> predicate) { return items.Where(predicate).ToList(); } public void Update(Func<T, bool> predicate, Action<T> updateAction) { foreach (T item in items.Where(predicate)) { updateAction(item); } SaveData(); } } } namespace ExampleMod.Objects { public class Fiend { public string Id { get; set; } public string Name { get; set; } public Product LastConsumed { get; set; } } public class Product { public string Id { get; set; } public string Name { get; set; } public float Addictiveness { get; set; } public int Quality { get; set; } } } namespace BetterFiends { public static class BuildInfo { public const string Name = "BetterFiends"; public const string Description = "Makes NPCs fiend for your products or become narcs"; public const string Author = "EndureBlackout"; public const string Company = null; public const string Version = "1.0"; public const string DownloadLink = "https://thunderstore.io/c/schedule-i/p/EndureBlackout/BetterFiends/"; } public class BetterFiends : MelonMod { public static Harmony harmony; public static bool appUpdate = false; public static AssetBundle testAppBundle; public static Object test; public static GameObject mainCanvasPrefab; private string fiendDataPath; public static JsonDataStoreService<Fiend> fiendData; private static string configPath; public static Config config; public static List<NPC> fiendList = new List<NPC>(); public override void OnInitializeMelon() { //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Expected O, but got Unknown ((MelonBase)this).OnInitializeMelon(); if (harmony == null) { harmony = new Harmony("com.endureblackout.betterfiends"); try { fiendDataPath = Path.Combine(MelonEnvironment.UserDataDirectory, "fiend_data.json"); fiendData = new JsonDataStoreService<Fiend>(fiendDataPath); LoadConfig(); MelonLogger.Msg("BetterFiends: Patches applied successfully."); } catch (Exception arg) { MelonLogger.Error($"BetterFiends: Failed to apply patches. Error: {arg}"); } } } private void LoadConfig() { configPath = Path.Combine(MelonEnvironment.UserDataDirectory, "better-fiends-config.json"); if (File.Exists(configPath)) { string text = File.ReadAllText(configPath); config = JsonConvert.DeserializeObject<Config>(text); MelonLogger.Msg("[BetterFiends]: Config loaded successfully."); } else { Config newConfig = new Config(); SaveConfig(newConfig); } } private static void SaveConfig(Config newConfig) { try { string contents = JsonConvert.SerializeObject((object)newConfig, (Formatting)1); File.WriteAllText(configPath, contents); MelonLogger.Msg("[BetterFiends]: New config filed created!"); config = newConfig; } catch (Exception ex) { MelonLogger.Error("[BetterFiends]: There was an issue saving the config file: " + ex.Message); } } } } namespace BetterFiends.Services { public static class RenderService { public static void SetWorldspaceDialogueRenderer(NPC npc, string message) { Transform val = FindChildRecursive(((Component)npc.Avatar.BodyContainer).transform, "mixamorig:Spine2"); if ((Object)(object)val != (Object)null) { WorldspaceDialogueRenderer worldspaceDialogueRenderer = GetWorldspaceDialogueRenderer(npc); if ((Object)(object)worldspaceDialogueRenderer != (Object)null) { Object.Destroy((Object)(object)worldspaceDialogueRenderer); } } } public static Transform FindChildRecursive(Transform parent, string name) { for (int i = 0; i < parent.childCount; i++) { Transform child = parent.GetChild(i); if (((Object)child).name == name) { return child; } Transform val = FindChildRecursive(child, name); if ((Object)(object)val != (Object)null) { return val; } } return null; } public static WorldspaceDialogueRenderer GetWorldspaceDialogueRenderer(NPC npc) { Transform val = FindChildRecursive(((Component)npc.Avatar.BodyContainer).transform, "mixamorig:Spine2"); if ((Object)(object)val != (Object)null) { WorldspaceDialogueRenderer componentInChildren = ((Component)val).GetComponentInChildren<WorldspaceDialogueRenderer>(); if ((Object)(object)componentInChildren != (Object)null) { MelonLogger.Msg("Found WorldspaceDialogueRenderer on " + npc.fullName); return componentInChildren; } MelonLogger.Warning("No WorldspaceDialogueRenderer found under Spine2 on " + npc.fullName); } return null; } } } namespace BetterFiends.Patching { [HarmonyPatch(typeof(RequestProductBehaviour))] public static class NPC_Request_Product_Patch { [HarmonyPatch("Begin")] [HarmonyPostfix] public static void BeginPostfix(RequestProductBehaviour __instance) { //IL_00c2: Unknown result type (might be due to invalid IL or missing references) try { if (BetterFiends.fiendList.Contains(((Behaviour)__instance).Npc)) { FieldInfo fieldInfo = AccessTools.Field(typeof(RequestProductBehaviour), "requestGreeting"); object? value = fieldInfo.GetValue(__instance); GreetingOverride val = (GreetingOverride)((value is GreetingOverride) ? value : null); string text = BetterFiends.fiendData.FindFirst((Fiend x) => x.Id == ((Behaviour)__instance).Npc.BakedGUID)?.LastConsumed?.Name ?? null; if (val != null) { val.Greeting = ((text != null) ? ("Yo I need more of that " + text + "... NOW!") : "Yo I need more of your shit... NOW!"); val.PlayVO = true; val.VOType = (EVOLineType)4; } } } catch (Exception ex) { MelonLogger.Error("Error in Begin Postfix: " + ex.Message); } } [HarmonyPatch("RequestRejected")] [HarmonyPostfix] public static void RequestRejectedPostFix(RequestProductBehaviour __instance) { NPC npc = ((Behaviour)__instance).Npc; if (BetterFiends.fiendList.Contains(npc)) { Customer component = ((Component)npc).GetComponent<Customer>(); float num = CalculateNarcProbability(component, npc); if (Random.value < num) { DoNarcBehavior(npc, __instance.TargetPlayer, "Better get me that product next time!"); } else { DoRejectBehavior(npc, "I really needed that shit!"); } BetterFiends.fiendList.Remove(npc); } } [HarmonyPatch("RequestAccepted")] [HarmonyPostfix] public static void RequestAcceptedPostFix(RequestProductBehaviour __instance) { NPC npc = ((Behaviour)__instance).Npc; if (BetterFiends.fiendList.Contains(npc)) { Customer component = ((Component)npc).GetComponent<Customer>(); float num = CalculateNarcProbability(component, npc); if (Random.value < num) { DoNarcBehavior(npc, __instance.TargetPlayer, "No hard feelings man, they said I would do hard time!"); } BetterFiends.fiendList.Remove(npc); } } [HarmonyPatch("HandoverClosed")] [HarmonyPrefix] public static bool HandoverClosedPrefix(RequestProductBehaviour __instance, EHandoverOutcome outcome, List<ItemInstance> items, float askingPrice) { if (BetterFiends.fiendList.Contains(((Behaviour)__instance).Npc)) { NPC npc = ((Behaviour)__instance).Npc; Customer component = ((Component)npc).GetComponent<Customer>(); Fiend fiend = BetterFiends.fiendData.FindFirst((Fiend x) => x.Id == npc.BakedGUID); if (fiend != null && fiend.LastConsumed != null) { ItemInstance val = ((IEnumerable<ItemInstance>)items).FirstOrDefault((Func<ItemInstance, bool>)((ItemInstance x) => x.ID == fiend.LastConsumed.Id)); if (val == null) { DoRejectBehavior(npc, "This ain't the shit I was looking for!"); Singleton<HandoverScreen>.Instance.ClearCustomerSlots(true); return false; } } } return true; } private static float CalculateNarcProbability(Customer customer, NPC npc) { float num = 0.05f; float num2 = Mathf.Clamp01(customer.CurrentAddiction); float num3 = 1f - num2 * 0.8f; float num4 = 100f / (100f + customer.CustomerData.MaxWeeklySpend); float num5 = num * num3 * num4; MelonLogger.Msg($"Narc chance: {num5}"); return Mathf.Clamp01(num5 * BetterFiends.config.FiendProbabilityMultiplier); } private static void DoNarcBehavior(NPC npc, Player player, string message) { //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Expected O, but got Unknown npc.PlayVO((EVOLineType)10); npc.dialogueHandler.ShowWorldspaceDialogue(npc.dialogueHandler.Database.GetLine((EDialogueModule)3, "sample_offer_rejected_police"), 5f); npc.actions.SetCallPoliceBehaviourCrime((Crime)new AttemptingToSell()); npc.actions.CallPolice_Networked(player); WorldspaceDialogueRenderer worldspaceDialogueRenderer = RenderService.GetWorldspaceDialogueRenderer(npc); worldspaceDialogueRenderer.ShowText(message, 0f); try { npc.PlayVO((EVOLineType)14); } catch { } } private static void DoRejectBehavior(NPC npc, string message) { //IL_0022: Unknown result type (might be due to invalid IL or missing references) if (BetterFiends.config.EnableAggressiveBehavior) { float num = default(float); npc.behaviour.CombatBehaviour.SetTarget((NetworkConnection)null, ((NetworkBehaviour)Player.GetClosestPlayer(((Component)npc).transform.position, ref num, (List<Player>)null)).NetworkObject); ((Behaviour)npc.behaviour.CombatBehaviour).Enable_Networked((NetworkConnection)null); } WorldspaceDialogueRenderer worldspaceDialogueRenderer = RenderService.GetWorldspaceDialogueRenderer(npc); worldspaceDialogueRenderer.ShowText(message, 0f); try { npc.PlayVO((EVOLineType)14); } catch { } } } [HarmonyPatch(typeof(ConsumeProductBehaviour), "TryConsume")] public static class NPC_ConsumeProduct_Patch { [CompilerGenerated] private sealed class <HandlePlayerInteraction>d__1 : IEnumerator<object>, IDisposable, IEnumerator { private int <>1__state; private object <>2__current; public NPC npc; public Player player; private PropertyInfo <enabledProp>5__1; private PropertyInfo <targetProp>5__2; private MethodInfo <addEnabled>5__3; private FieldInfo <field>5__4; private List<Behaviour> <existing>5__5; private List<Behaviour>.Enumerator <>s__6; private Behaviour <behaviour>5__7; private PropertyInfo <activeField>5__8; object IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <HandlePlayerInteraction>d__1(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <enabledProp>5__1 = null; <targetProp>5__2 = null; <addEnabled>5__3 = null; <field>5__4 = null; <existing>5__5 = null; <>s__6 = default(List<Behaviour>.Enumerator); <behaviour>5__7 = null; <activeField>5__8 = null; <>1__state = -2; } private bool MoveNext() { //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Expected O, but got Unknown switch (<>1__state) { default: return false; case 0: <>1__state = -1; <>2__current = (object)new WaitForSeconds(90f); <>1__state = 1; return true; case 1: <>1__state = -1; PauseBehaviors(npc); <enabledProp>5__1 = AccessTools.Property(typeof(RequestProductBehaviour), "Enabled"); <enabledProp>5__1.SetValue(npc.behaviour.RequestProductBehaviour, true); ((Behaviour)npc.behaviour.RequestProductBehaviour).SendEnable(); <targetProp>5__2 = AccessTools.Property(typeof(RequestProductBehaviour), "TargetPlayer"); <targetProp>5__2.SetValue(npc.behaviour.RequestProductBehaviour, player); <addEnabled>5__3 = AccessTools.Method(typeof(NPCBehaviour), "AddEnabledBehaviour", (Type[])null, (Type[])null); <addEnabled>5__3.Invoke(npc.behaviour, new object[1] { npc.behaviour.RequestProductBehaviour }); <field>5__4 = AccessTools.Field(typeof(NPCBehaviour), "enabledBehaviours"); <existing>5__5 = <field>5__4.GetValue(npc.behaviour) as List<Behaviour>; <>s__6 = <existing>5__5.GetEnumerator(); try { while (<>s__6.MoveNext()) { <behaviour>5__7 = <>s__6.Current; if (!<behaviour>5__7.Active) { <activeField>5__8 = AccessTools.Property(typeof(Behaviour), "Active"); <activeField>5__8.SetValue(<behaviour>5__7, true); if (((NetworkBehaviour)npc).LocalConnection != (NetworkConnection)null) { <behaviour>5__7.Enable_Networked(((NetworkBehaviour)npc).LocalConnection); <behaviour>5__7.Begin_Networked(((NetworkBehaviour)npc).LocalConnection); BetterFiends.fiendList.Add(npc); } <activeField>5__8 = null; } <behaviour>5__7 = null; } } finally { ((IDisposable)<>s__6).Dispose(); } <>s__6 = default(List<Behaviour>.Enumerator); if (<existing>5__5.Count > 0) { npc.behaviour.activeBehaviour = <existing>5__5.Where((Behaviour x) => x is RequestProductBehaviour).First(); } return false; } } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } public static void Postfix(ConsumeProductBehaviour __instance) { //IL_0161: Unknown result type (might be due to invalid IL or missing references) //IL_016b: Expected I4, but got Unknown //IL_00f5: Unknown result type (might be due to invalid IL or missing references) //IL_00ff: Expected I4, but got Unknown try { FieldInfo fieldInfo = AccessTools.Field(typeof(ConsumeProductBehaviour), "product"); object? value = fieldInfo.GetValue(__instance); ProductItemInstance val = (ProductItemInstance)((value is ProductItemInstance) ? value : null); string pattern = "\\s*\\(.*[uU]npackaged\\)"; if (val == null) { return; } Player local = Player.Local; NPC npc = ((Behaviour)__instance).Npc; Fiend fiendData = BetterFiends.fiendData.FindFirst((Fiend x) => x.Id == npc.BakedGUID); if (fiendData == null) { MelonLogger.Msg("[BetterFiends]: Fiend data not found, creating new fiend data"); fiendData = new Fiend { Id = npc.BakedGUID, Name = ((Object)npc).name, LastConsumed = new Product { Id = ((ItemInstance)val).ID, Name = Regex.Replace(((ItemInstance)val).Name, pattern, ""), Addictiveness = val.GetAddictiveness(), Quality = (int)((QualityItemInstance)val).Quality } }; BetterFiends.fiendData.Add(fiendData); } else { fiendData.LastConsumed = new Product { Id = ((ItemInstance)val).ID, Name = Regex.Replace(((ItemInstance)val).Name, pattern, ""), Addictiveness = val.GetAddictiveness(), Quality = (int)((QualityItemInstance)val).Quality }; BetterFiends.fiendData.Update((Fiend x) => x.Id == npc.BakedGUID, delegate(Fiend y) { y.LastConsumed = fiendData.LastConsumed; }); } if (local.IsLocalPlayer) { Customer component = ((Component)npc).GetComponent<Customer>(); float currentAddiction = component.CurrentAddiction; float addictiveness = val.GetAddictiveness(); float num = CalculateFiendProbability(currentAddiction, addictiveness); MelonLogger.Msg($"[BetterFiends]: {((Object)npc).name} has a {num} chance to fiend for more product."); if (Random.value < num) { MelonLogger.Msg("[BetterFiends]: " + ((Object)npc).name + " is fiending for more product. If denied, they will act out in violence or narc on you."); MelonCoroutines.Start(HandlePlayerInteraction(npc, local)); } } } catch (Exception ex) { MelonLogger.Error("[BetterFiends]: Error in TryConsume Postfix: " + ex.Message); } } [IteratorStateMachine(typeof(<HandlePlayerInteraction>d__1))] private static IEnumerator HandlePlayerInteraction(NPC npc, Player player) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <HandlePlayerInteraction>d__1(0) { npc = npc, player = player }; } private static void PauseBehaviors(NPC npc) { FieldInfo fieldInfo = AccessTools.Field(typeof(NPCBehaviour), "enabledBehaviours"); List<Behaviour> list = fieldInfo.GetValue(npc.behaviour) as List<Behaviour>; foreach (Behaviour item in list) { if (item.Active) { PropertyInfo propertyInfo = AccessTools.Property(typeof(Behaviour), "Active"); propertyInfo.SetValue(item, false); item.BehaviourUpdate(); if (((NetworkBehaviour)npc).LocalConnection != (NetworkConnection)null) { item.End_Networked(((NetworkBehaviour)npc).LocalConnection); } } } } private static float CalculateFiendProbability(float currentAddiction, float productAddictiveness) { float num = Mathf.Pow(currentAddiction, 1.5f) * (1f + Random.Range(-0.1f, 0.1f)); float num2 = productAddictiveness * (1f + num); float num3 = 1f / (1f + Mathf.Exp(0f - num2 + 3f)) * 0.5f; return Mathf.Clamp01(num3 * BetterFiends.config.FiendProbabilityMultiplier); } } } namespace BetterFiends.Configuration { public class Config { public bool EnableAggressiveBehavior { get; set; } = true; public float FiendProbabilityMultiplier { get; set; } = 1f; public float NarcProbabilityMultiplier { get; set; } = 1f; } }