using System; using System.Collections; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; using System.Reflection.Emit; using System.Resources; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Text; using System.Text.RegularExpressions; using HarmonyLib; using Il2CppFishNet; using Il2CppFishNet.Object; using Il2CppInterop.Runtime.Injection; using Il2CppInterop.Runtime.InteropTypes; using Il2CppInterop.Runtime.InteropTypes.Arrays; using Il2CppScheduleOne; using Il2CppScheduleOne.Core.Items.Framework; using Il2CppScheduleOne.DevUtilities; using Il2CppScheduleOne.Dialogue; using Il2CppScheduleOne.Economy; using Il2CppScheduleOne.Effects; using Il2CppScheduleOne.Employees; using Il2CppScheduleOne.Equipping; using Il2CppScheduleOne.GameTime; using Il2CppScheduleOne.Growing; using Il2CppScheduleOne.ItemFramework; using Il2CppScheduleOne.Law; using Il2CppScheduleOne.Levelling; using Il2CppScheduleOne.Management; using Il2CppScheduleOne.Messaging; using Il2CppScheduleOne.Money; using Il2CppScheduleOne.NPCs; using Il2CppScheduleOne.NPCs.Behaviour; using Il2CppScheduleOne.NPCs.CharacterClasses; using Il2CppScheduleOne.ObjectScripts; using Il2CppScheduleOne.PlayerScripts; using Il2CppScheduleOne.Product; using Il2CppScheduleOne.Property; using Il2CppScheduleOne.Quests; using Il2CppScheduleOne.StationFramework; using Il2CppScheduleOne.UI; using Il2CppScheduleOne.UI.Handover; using Il2CppScheduleOne.UI.Items; using Il2CppScheduleOne.UI.Phone; using Il2CppScheduleOne.UI.Phone.Delivery; using Il2CppScheduleOne.UI.Phone.Messages; using Il2CppScheduleOne.UI.Shop; using Il2CppScheduleOne.UI.Stations; using Il2CppScheduleOne.UI.Stations.Drying_rack; using Il2CppScheduleOne.Variables; using Il2CppSystem.Collections.Generic; using Il2CppTMPro; using Lithium; using Lithium.Helper; using Lithium.Modules; using Lithium.Modules.CauldronCustom; using Lithium.Modules.ChemistryStation; using Lithium.Modules.Customers; using Lithium.Modules.Customers.Architecture; using Lithium.Modules.Customers.Behaviours; using Lithium.Modules.Customers.BonusPayments; using Lithium.Modules.DryingRacks; using Lithium.Modules.DynamicsOrders; using Lithium.Modules.EffectCombos; using Lithium.Modules.EffectCombos.BonusPayments; using Lithium.Modules.Employees; using Lithium.Modules.Employees.Patches; using Lithium.Modules.LabOven; using Lithium.Modules.MixingStations; using Lithium.Modules.MixingStations.Patches; using Lithium.Modules.PlantGrowth; using Lithium.Modules.PlantGrowth.Behaviours; using Lithium.Modules.PlantGrowth.Patches; using Lithium.Modules.PropertyPrices; using Lithium.Modules.Shops; using Lithium.Modules.Shops.Patches; using Lithium.Modules.StackSizes; using Lithium.Modules.Storyline; using Lithium.Modules.TrashGrabber; using Lithium.Modules.WateringCans; using Lithium.Util; using MelonLoader; using MelonLoader.Utils; using Microsoft.CodeAnalysis; using Newtonsoft.Json; using Newtonsoft.Json.Converters; using Newtonsoft.Json.Linq; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: MelonInfo(typeof(Core), "Lithium_fork", "1.1.1", "DerTomDerTwitch (original Lithium) & fork maintainer", null)] [assembly: MelonGame("TVGS", "Schedule I")] [assembly: MelonPlatformDomain(/*Could not decode attribute arguments.*/)] [assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")] [assembly: AssemblyCompany("Lithium_fork")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.1.1.0")] [assembly: AssemblyInformationalVersion("1.0.0")] [assembly: AssemblyProduct("Lithium_fork")] [assembly: AssemblyTitle("Lithium_fork")] [assembly: NeutralResourcesLanguage("en-US")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.1.1.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace Lithium { public class Core : MelonMod { public static readonly List Modules = new List(16) { new ModPropertyPrices(), new ModPlants(), new ModDryingRacks(), new ModCustomers(), new ModDynamicsOrders(), new ModStackSizes(), new ModLabOven(), new ModTrashGrabber(), new ModMixingStations(), new ModStoryline(), new ModShops(), new ModEmployees(), new ModCauldronCustom(), new ModChemistryStation(), new ModWateringCan(), new ModEffectCombos() }; private const string ForkCredit = "Lithium_fork: fork maintained for Schedule I updates. Original mod \"Lithium\" by DerTomDerTwitch; development continued here due to lack of time on the author's side."; private bool _isFirstStart = true; public static Instance Logger { get; set; } public static T Get() where T : ModuleBase { return Modules.OfType().FirstOrDefault(); } public override void OnInitializeMelon() { //IL_00cd: Unknown result type (might be due to invalid IL or missing references) //IL_00d3: Expected O, but got Unknown Logger = ((MelonBase)this).LoggerInstance; ((MelonBase)this).LoggerInstance.Msg("Lithium_fork: fork maintained for Schedule I updates. Original mod \"Lithium\" by DerTomDerTwitch; development continued here due to lack of time on the author's side."); foreach (ModuleBase module in Modules) { try { ((MelonBase)this).LoggerInstance.Msg("Loading " + module.GetType().Name); module.Load(); } catch (Exception value) { ((MelonBase)this).LoggerInstance.Error($"Failed to load module {module.GetType().Name}: {value}"); } } Harmony val = new Harmony("com.lithium_fork"); try { val.PatchAll(); } catch (Exception value2) { ((MelonBase)this).LoggerInstance.Error($"Harmony PatchAll failed: {value2}"); } finally { try { BotanistDynamicTimingPatches.Register(val); } catch (Exception value3) { ((MelonBase)this).LoggerInstance.Error($"BotanistDynamicTimingPatches.Register failed: {value3}"); } try { GrowContainerShroomHarmony.Apply(val); } catch (Exception value4) { ((MelonBase)this).LoggerInstance.Error($"GrowContainerShroomHarmony.Apply failed: {value4}"); } } ((MelonBase)this).LoggerInstance.Msg("Lithium_fork initialized"); } public override void OnSceneWasInitialized(int buildIndex, string sceneName) { if (sceneName == "Main") { foreach (ModuleBase module in Modules) { try { ((MelonBase)this).LoggerInstance.Msg("Apply " + module.GetType().Name); module.Apply(); } catch (Exception value) { ((MelonBase)this).LoggerInstance.Error($"Failed to apply module {module.GetType().Name}: {value}"); } } _isFirstStart = false; } else if (sceneName.Equals("Menu", StringComparison.OrdinalIgnoreCase)) { SupplierStartPatch.ClearListingStockCache(); if (!_isFirstStart) { _isFirstStart = true; } } } public override void OnUpdate() { ((MelonBase)this).OnUpdate(); if (Input.GetKeyDown((KeyCode)286)) { _ = Customer.UnlockedCustomers.ToList()[0]; } } public override void OnLateUpdate() { ((MelonBase)this).OnLateUpdate(); ModMixingStations modMixingStations = Get(); if (modMixingStations?.Configuration != null) { MixingStationMk2Registry.RefreshAllMixing(modMixingStations.Configuration); } } } } namespace Lithium.Util { public static class DeliveryUtils { public static readonly Dictionary DeliveryFeeOverrides = new Dictionary(); private static void SetDeliveryShopOpen(DeliveryShop shop, bool open) { Traverse.Create((object)shop).Property("IsOpen", (object[])null).SetValue((object)open); } private static bool GetDeliveryShopOpen(DeliveryShop shop) { return Traverse.Create((object)shop).Property("IsOpen", (object[])null).GetValue(); } public static void ApplyDeliveryOverrides() { DeliveryFeeOverrides.Clear(); Dictionary dictionary = (from s in ((IEnumerable)Object.FindObjectsOfType(true)).ToList() where (Object)(object)s != (Object)null && (Object)(object)s.MatchingShop != (Object)null select s).ToDictionary((DeliveryShop s) => s.MatchingShop.ShopName, (DeliveryShop s) => s); foreach (KeyValuePair delivery in Core.Get().Configuration.Deliveries) { if (!dictionary.TryGetValue(delivery.Key, out var value)) { continue; } switch (delivery.Value.Availability) { case DeliveryAvailabilitySettings.Unchanged: { switch (value.MatchingShop.ShopName) { case "Albert Hoover": { Albert val2 = Object.FindObjectOfType(); SetDeliveryShopOpen(value, ((NPC)val2).RelationData.RelationDelta > Supplier.DELIVERY_RELATIONSHIP_REQUIREMENT); continue; } case "Shirley Watts": { Shirley val = Object.FindObjectOfType(); SetDeliveryShopOpen(value, ((NPC)val).RelationData.RelationDelta > Supplier.DELIVERY_RELATIONSHIP_REQUIREMENT); continue; } case "Salvador Moreno": { Salvador val3 = Object.FindObjectOfType(); SetDeliveryShopOpen(value, ((NPC)val3).RelationData.RelationDelta > Supplier.DELIVERY_RELATIONSHIP_REQUIREMENT); continue; } } string shopName = value.MatchingShop.ShopName; if (string.IsNullOrEmpty(shopName) || shopName.IndexOf("Fungal", StringComparison.OrdinalIgnoreCase) < 0) { continue; } foreach (Supplier item in Object.FindObjectsOfType(true)) { if (!((Object)(object)item == (Object)null)) { string name = ((object)item).GetType().Name; if (name != null && name.IndexOf("Fungal", StringComparison.OrdinalIgnoreCase) >= 0) { SetDeliveryShopOpen(value, ((NPC)item).RelationData.RelationDelta > Supplier.DELIVERY_RELATIONSHIP_REQUIREMENT); break; } } } continue; } case DeliveryAvailabilitySettings.Never: SetDeliveryShopOpen(value, open: false); break; case DeliveryAvailabilitySettings.Always: SetDeliveryShopOpen(value, open: true); break; case DeliveryAvailabilitySettings.AfterReachingXP: SetDeliveryShopOpen(value, NetworkSingleton.Instance.TotalXP >= delivery.Value.XPRequirement); break; default: throw new ArgumentOutOfRangeException(); } DeliveryFeeOverrides[delivery.Key] = delivery.Value.DeliveryFee; if ((Object)(object)value.DeliveryFeeLabel != (Object)null) { value.DeliveryFeeLabel.text = $"${delivery.Value.DeliveryFee:0}"; } ((Component)value).gameObject.SetActive(GetDeliveryShopOpen(value)); } } } public static class SuccessChanceCalculator { public static float CalculateSuccess(EDrugType drugType, EQuality quality, float qualityLevelModifier, ECustomerStandard standard, string[] desires, string[] effects, CustomerAffinityData affinities, bool includeDrugPreference, float baseAcceptance) { //IL_00dd: Unknown result type (might be due to invalid IL or missing references) //IL_00de: Unknown result type (might be due to invalid IL or missing references) //IL_00df: Unknown result type (might be due to invalid IL or missing references) //IL_00e1: Expected I4, but got Unknown //IL_018b: Unknown result type (might be due to invalid IL or missing references) //IL_0190: Unknown result type (might be due to invalid IL or missing references) float num2; if (desires.Length != 0) { int num = 0; foreach (string item in desires.Where((string d) => !string.IsNullOrEmpty(d))) { num += (effects.Contains(item) ? 1 : 0); } num2 = (float)num / (float)desires.Length; Core.Logger.Msg($"Sample offering: Covered {num} desires. Base acceptance {num2 * 100f:F1}%"); } else { Core.Logger.Msg("Sample offering: No desired. Base acceptance 100%"); num2 = 1f; } int num3 = quality - standard; Core.Logger.Msg($"Sample offering: Quality difference {num3} levels"); num2 += qualityLevelModifier * (float)num3; Core.Logger.Msg($"Adjusted acceptance: {num2 * 100f:F1}%"); if (includeDrugPreference) { Enumerator enumerator2 = affinities.ProductAffinities.GetEnumerator(); while (enumerator2.MoveNext()) { ProductTypeAffinity current2 = enumerator2.Current; if (current2.DrugType == drugType) { Core.Logger.Msg($"Sample offering: Product affinity modifier: {current2.Affinity * 100f:F1}%"); num2 *= current2.Affinity; break; } } } num2 += baseAcceptance; Core.Logger.Msg($"Sample offering: Final acceptance is {Mathf.Clamp01(num2):F1}%"); return Mathf.Clamp01(num2); } } public class WeightedNormalizer { private readonly List _weights = new List(); private readonly List _values = new List(); private readonly List _cdf = new List(); private bool _isInitialized; public void Add(float weight, float value) { if (weight < 0f) { throw new ArgumentOutOfRangeException("weight", "Weight must be non-negative."); } _weights.Add(weight); _values.Add(value); _isInitialized = false; } private void Initialize() { _cdf.Clear(); float num = _weights.Sum(); if (num == 0f) { _cdf.Clear(); _cdf.Add(1f); _isInitialized = true; return; } float num2 = 0f; foreach (float weight in _weights) { num2 += weight / num; _cdf.Add(num2); } _isInitialized = true; } public float Evaluate(float n) { if (!_isInitialized) { Initialize(); } if (_values.Count == 0) { throw new InvalidOperationException("No values added to normalize."); } if (!(n <= 0f)) { if (n >= 1f) { List values = _values; return values[values.Count - 1]; } int num = _cdf.FindIndex((float x) => x > n); switch (num) { case -1: { List values2 = _values; return values2[values2.Count - 1]; } case 0: return _values[0]; default: { int index = num - 1; float num2 = _cdf[index]; float num3 = _cdf[num]; float num4 = (n - num2) / (num3 - num2); float num5 = _values[index]; float num6 = _values[num]; return num5 + (num6 - num5) * num4; } } } return _values[0]; } } public class PickerEmptyException : Exception { } public class WeightedPicker { private readonly Random _random; private bool _hasChanged; private readonly Dictionary _dictionary = new Dictionary(); private double _totalWeight; public int Count => _dictionary.Count; public float this[T key] { get { return _dictionary[key]; } set { _dictionary[key] = value; _hasChanged = true; } } public WeightedPicker(Random random) { _random = random; } public WeightedPicker() : this(new Random(Guid.NewGuid().GetHashCode())) { } public T Pick() { if (_hasChanged) { Rebuild(); } float value = (float)_random.Next((int)(_totalWeight * 100.0)) / 100f; return PickManually(value); } public T PickManually(float value) { if (_hasChanged) { Rebuild(); } value = Math.Min((float)_totalWeight, Math.Max(0f, value)); float num = 0f; KeyValuePair? keyValuePair = null; foreach (KeyValuePair item in _dictionary) { if (num > value) { break; } keyValuePair = item; num += item.Value; } if (!keyValuePair.HasValue) { throw new PickerEmptyException(); } return keyValuePair.Value.Key; } private void Rebuild() { _totalWeight = _dictionary.Values.Sum(); _hasChanged = false; } public void Add(T value, float weight) { _dictionary.Add(value, weight); _hasChanged = true; } public void AddRange(IEnumerable> entries) { foreach (KeyValuePair entry in entries) { _dictionary.Add(entry.Key, entry.Value); } _hasChanged = true; } public bool Remove(T key) { _hasChanged = true; return _dictionary.Remove(key); } public void Clear() { _dictionary.Clear(); _hasChanged = true; } } } namespace Lithium.Modules { public abstract class ModuleBase { public abstract void Load(); public abstract void Apply(); } public abstract class ModuleBase : ModuleBase where TConfiguration : ModuleConfiguration { public TConfiguration Configuration { get; private set; } protected virtual void OnBeforeConfigurationLoaded() { } public override void Load() { Configuration = Activator.CreateInstance(); OnBeforeConfigurationLoaded(); Configuration.LoadConfiguration(); } } public abstract class ModuleConfiguration { private static readonly string ConfigFolder = Path.Combine(MelonEnvironment.UserDataDirectory, "Lithium"); [JsonProperty(Order = -500)] public bool Enabled; [JsonIgnore] public abstract string Name { get; } public string GetConfigFile() { return Path.Combine(ConfigFolder, Name + ".json"); } public void SaveConfiguration() { if (!Directory.Exists(ConfigFolder)) { Directory.CreateDirectory(ConfigFolder); } string configFile = GetConfigFile(); string contents = JsonConvert.SerializeObject((object)this, (Formatting)1); File.WriteAllText(configFile, contents); } public void LoadConfiguration() { //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Expected O, but got Unknown string configFile = GetConfigFile(); if (File.Exists(configFile)) { string text = File.ReadAllText(configFile); JsonSerializerSettings val = new JsonSerializerSettings { DefaultValueHandling = (DefaultValueHandling)2 }; JsonConvert.PopulateObject(text, (object)this, val); } else { SaveConfiguration(); } } } } namespace Lithium.Modules.MixingStations { public class ModMixingStationsConfiguration : ModuleConfiguration { public override string Name => "MixingStation"; public int InputCapacity { get; set; } = 20; public float MixSpeed { get; set; } = 1f; [JsonProperty(/*Could not decode attribute arguments.*/)] public int? MixStepsPerSecond { get; set; } internal float GetEffectiveMixSpeed() { if (MixStepsPerSecond.HasValue && Mathf.Approximately(MixSpeed, 1f)) { return Mathf.Max(0.01f, (float)MixStepsPerSecond.Value); } return Mathf.Max(0.01f, MixSpeed); } } public class ModMixingStations : ModuleBase { public override void Apply() { _ = base.Configuration.Enabled; } } } namespace Lithium.Modules.MixingStations.Patches { internal static class MixingStationClockReflection { internal static object ResolveClock(MixingStation station) { if ((Object)(object)station.Clock != (Object)null) { return station.Clock; } if (!(station is MixingStationMk2)) { return null; } string[] array = new string[8] { "Clock", "clock", "Alarm", "alarm", "MixingStationCanvas", "mixingStationCanvas", "Canvas", "canvas" }; foreach (string text in array) { try { object obj = ResolveTraversedToAlarm(Traverse.Create((object)station).Field(text).GetValue()); if (obj != null) { return obj; } } catch { } try { object obj3 = ResolveTraversedToAlarm(Traverse.Create((object)station).Property(text, (object[])null).GetValue()); if (obj3 != null) { return obj3; } } catch { } } return FindDigitalAlarmUnderTransform(((Component)station).transform); } private static object ResolveTraversedToAlarm(object v) { if (v == null) { return null; } if (LooksLikeDigitalAlarm(v)) { return v; } GameObject val = (GameObject)((v is GameObject) ? v : null); if (val != null) { return FindDigitalAlarmUnderTransform(val.transform); } Component val2 = (Component)((v is Component) ? v : null); if (val2 != null) { return FindDigitalAlarmUnderTransform(val2.transform); } return null; } private static bool LooksLikeDigitalAlarm(object o) { string name = o.GetType().Name; if (name != null) { return name.IndexOf("DigitalAlarm", StringComparison.OrdinalIgnoreCase) >= 0; } return false; } private static object FindDigitalAlarmUnderTransform(Transform t) { if ((Object)(object)t == (Object)null) { return null; } foreach (MonoBehaviour componentsInChild in ((Component)t).GetComponentsInChildren(true)) { if ((Object)(object)componentsInChild != (Object)null && LooksLikeDigitalAlarm(componentsInChild)) { return componentsInChild; } } return null; } internal static void SetScreenLit(object clock, bool lit) { clock?.GetType().GetMethod("SetScreenLit", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.Invoke(clock, new object[1] { lit }); } internal static void DisplayMinutes(object clock, int minutes) { clock?.GetType().GetMethod("DisplayMinutes", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.Invoke(clock, new object[1] { minutes }); } internal static void DisplayText(object clock, string text) { clock?.GetType().GetMethod("DisplayText", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.Invoke(clock, new object[1] { text }); } } [HarmonyPatch(typeof(MixingStation), "Start")] public class MixingStationCapacityPatch { [HarmonyPostfix] public static void MixingStationCapacity(MixingStation __instance) { MixingStationPatchLogic.StartPostfix(__instance); MixingStationMk2 val = __instance.AsMk2(); if ((Object)(object)val != (Object)null) { MixingStationMk2Registry.Register(val); } } } [HarmonyPatch(typeof(MixingStation), "GetMixTimeForCurrentOperation")] internal static class MixingStationGetMixTimePatch { [HarmonyPostfix] public static void Postfix(ref int __result) { if (!MixingStationPatchLogic.SuppressGetMixTimePostfix) { MixingStationPatchLogic.GetMixTimePostfix(ref __result); } } } internal static class MixingStationMk2UiBroadcast { private sealed class CachedRefs { internal object Clock; internal List TimerTmps; internal int LastWritten = int.MinValue; internal bool LastWasIdle; } private static readonly Dictionary CacheByStationId = new Dictionary(); private static readonly Regex RxMinsRemaining = new Regex("\\d+(\\.\\d+)?\\s*mins?\\s*remaining", RegexOptions.IgnoreCase | RegexOptions.Compiled); private static readonly Regex RxMinutesRestantes = new Regex("\\d+(\\.\\d+)?\\s*minutes?\\s*restantes?", RegexOptions.IgnoreCase | RegexOptions.Compiled); private static readonly Regex RxLeadingNumber = new Regex("^\\s*\\d+(\\.\\d+)?", RegexOptions.Compiled); internal static void ClearStationCaches(int stationId) { CacheByStationId.Remove(stationId); } internal static void WriteRemainingMinutesToMk2Ui(MixingStationMk2 mk2, int remainingMinutes) { if ((Object)(object)mk2 == (Object)null) { return; } int instanceID = ((Object)mk2).GetInstanceID(); CachedRefs orBuildCache = GetOrBuildCache(mk2, instanceID); if (orBuildCache == null || (!orBuildCache.LastWasIdle && orBuildCache.LastWritten == remainingMinutes)) { return; } orBuildCache.LastWritten = remainingMinutes; orBuildCache.LastWasIdle = false; if (orBuildCache.Clock != null) { MixingStationClockReflection.SetScreenLit(orBuildCache.Clock, lit: true); MixingStationClockReflection.DisplayMinutes(orBuildCache.Clock, remainingMinutes); } if (orBuildCache.TimerTmps == null) { return; } for (int i = 0; i < orBuildCache.TimerTmps.Count; i++) { TMP_Text val = orBuildCache.TimerTmps[i]; if ((Object)(object)val == (Object)null) { continue; } string text = val.text; if (!string.IsNullOrEmpty(text)) { string text2 = RxMinsRemaining.Replace(text, $"{remainingMinutes} mins remaining"); if (text2 == text) { text2 = RxMinutesRestantes.Replace(text, $"{remainingMinutes} minutes restantes"); } if (text2 == text) { text2 = RxLeadingNumber.Replace(text, remainingMinutes.ToString()); } if (text2 != text) { val.text = text2; } } } } internal static void BlankMk2Ui(MixingStationMk2 mk2) { if ((Object)(object)mk2 == (Object)null) { return; } int instanceID = ((Object)mk2).GetInstanceID(); if (CacheByStationId.TryGetValue(instanceID, out var value) && !value.LastWasIdle) { value.LastWasIdle = true; value.LastWritten = int.MinValue; if (value.Clock != null) { MixingStationClockReflection.SetScreenLit(value.Clock, lit: false); MixingStationClockReflection.DisplayText(value.Clock, string.Empty); } } } private static CachedRefs GetOrBuildCache(MixingStationMk2 mk2, int sid) { if (CacheByStationId.TryGetValue(sid, out var value) && value != null) { return value; } CachedRefs cachedRefs = new CachedRefs(); try { cachedRefs.Clock = MixingStationClockReflection.ResolveClock((MixingStation)(object)mk2); } catch { } try { Transform transform = ((Component)mk2).transform; if ((Object)(object)transform != (Object)null) { cachedRefs.TimerTmps = new List(4); foreach (TMP_Text componentsInChild in ((Component)transform).GetComponentsInChildren(true)) { if (!((Object)(object)componentsInChild == (Object)null)) { string text = componentsInChild.text; if (!string.IsNullOrEmpty(text) && LooksLikeMinutesRemainingLabel(text)) { cachedRefs.TimerTmps.Add(componentsInChild); } } } } } catch { } CacheByStationId[sid] = cachedRefs; return cachedRefs; } private static bool LooksLikeMinutesRemainingLabel(string s) { if (s.IndexOf("min", StringComparison.OrdinalIgnoreCase) < 0 && s.IndexOf("remaining", StringComparison.OrdinalIgnoreCase) < 0) { return s.IndexOf("restant", StringComparison.OrdinalIgnoreCase) >= 0; } return true; } } [HarmonyPatch] internal static class MixingStationMk2UpdateDisplayPatch { private static MethodBase TargetMethod() { MethodInfo methodInfo = AccessTools.DeclaredMethod(typeof(MixingStationMk2), "LateUpdate", (Type[])null, (Type[])null); if (methodInfo != null) { return methodInfo; } methodInfo = AccessTools.DeclaredMethod(typeof(MixingStation), "LateUpdate", (Type[])null, (Type[])null); if (methodInfo != null) { return methodInfo; } methodInfo = AccessTools.DeclaredMethod(typeof(MixingStationMk2), "Update", (Type[])null, (Type[])null); if (methodInfo != null) { return methodInfo; } return AccessTools.DeclaredMethod(typeof(MixingStation), "Update", (Type[])null, (Type[])null); } private static bool Prepare() { return TargetMethod() != null; } [HarmonyPostfix] [HarmonyPriority(0)] private static void Postfix(MixingStation __instance) { MixingStationMk2 val = __instance.AsMk2(); if ((Object)(object)val == (Object)null) { return; } ModMixingStations modMixingStations = Core.Get(); if (modMixingStations?.Configuration == null || !modMixingStations.Configuration.Enabled) { return; } try { if (((MixingStation)val).CurrentMixOperation == null) { MixingStationMk2UiBroadcast.BlankMk2Ui(val); return; } int scaledRequiredMinutes = MixingStationPatchLogic.GetScaledRequiredMinutes((MixingStation)(object)val, modMixingStations.Configuration); int remainingMinutes = Mathf.Max(0, scaledRequiredMinutes - ((MixingStation)val).CurrentMixTime); MixingStationMk2UiBroadcast.WriteRemainingMinutesToMk2Ui(val, remainingMinutes); } catch { } } } internal static class MixingStationMk2Registry { private static readonly List Stations = new List(); internal static void Register(MixingStationMk2 mk2) { if (!((Object)(object)mk2 == (Object)null) && !Stations.Contains(mk2)) { Stations.Add(mk2); } } internal static void Unregister(MixingStationMk2 mk2) { if ((Object)(object)mk2 != (Object)null) { Stations.Remove(mk2); } if ((Object)(object)mk2 != (Object)null) { MixingStationMk2UiBroadcast.ClearStationCaches(((Object)mk2).GetInstanceID()); } } internal static void RefreshAllMixing(ModMixingStationsConfiguration config) { if (config == null || !config.Enabled) { return; } for (int num = Stations.Count - 1; num >= 0; num--) { MixingStationMk2 val = Stations[num]; if ((Object)(object)val == (Object)null) { Stations.RemoveAt(num); } else { try { if (((MixingStation)val).CurrentMixOperation == null) { MixingStationMk2UiBroadcast.BlankMk2Ui(val); } else { int scaledRequiredMinutes = MixingStationPatchLogic.GetScaledRequiredMinutes((MixingStation)(object)val, config); int remainingMinutes = Mathf.Max(0, scaledRequiredMinutes - ((MixingStation)val).CurrentMixTime); MixingStationMk2UiBroadcast.WriteRemainingMinutesToMk2Ui(val, remainingMinutes); } } catch { } } } } } [HarmonyPatch(typeof(MixingStation), "OnDestroy")] internal static class MixingStationMk2LifecyclePatch { private static bool Prepare() { return AccessTools.DeclaredMethod(typeof(MixingStation), "OnDestroy", (Type[])null, (Type[])null) != null; } [HarmonyPostfix] private static void Postfix(MixingStation __instance) { MixingStationMk2 val = __instance.AsMk2(); if ((Object)(object)val != (Object)null) { MixingStationMk2Registry.Unregister(val); } } } internal static class MixingStationPatchLogic { [ThreadStatic] internal static bool SuppressGetMixTimePostfix; private static readonly Dictionary LastMixProgressDailyMinSum = new Dictionary(); internal static TimeManager ResolveTimeManager() { try { TimeManager instance = NetworkSingleton.Instance; if ((Object)(object)instance != (Object)null) { return instance; } } catch { } try { TimeManager instance2 = NetworkSingleton.Instance; if ((Object)(object)instance2 != (Object)null) { return instance2; } } catch { } return null; } internal static int GetDailyMinSumSafe() { int num = -1; try { TimeManager instance = NetworkSingleton.Instance; if ((Object)(object)instance != (Object)null) { num = Mathf.Max(num, instance.DailyMinSum); } } catch { } try { TimeManager instance2 = NetworkSingleton.Instance; if ((Object)(object)instance2 != (Object)null) { num = Mathf.Max(num, instance2.DailyMinSum); } } catch { } if (num < 0) { return 0; } return num; } internal static bool CanAdvanceMixTimeAsAuthority() { try { if (InstanceFinder.IsServer) { return true; } } catch { } try { if (InstanceFinder.IsClient && !InstanceFinder.IsServer) { return false; } } catch { } return true; } internal static bool OnMinPassPrefix(MixingStation __instance) { if ((Object)(object)__instance == (Object)null) { return false; } ModMixingStationsConfiguration configuration = Core.Get().Configuration; if (configuration == null) { return false; } bool flag = false; try { flag = __instance.CurrentMixOperation != null; } catch { } int num = 0; try { num = ((__instance.OutputSlot != null) ? __instance.OutputSlot.Quantity : 0); } catch { } if (flag || num > 0) { int num2 = 0; int num3 = 0; try { num3 = __instance.CurrentMixTime; } catch { } if (flag) { num2 = SafeGetScaledRequired(__instance, configuration); int num4 = num3; if (CanAdvanceMixTimeAsAuthority()) { int instanceID = ((Object)__instance).GetInstanceID(); int dailyMinSumSafe = GetDailyMinSumSafe(); if (num4 <= 0) { LastMixProgressDailyMinSum.Remove(instanceID); } if (!LastMixProgressDailyMinSum.TryGetValue(instanceID, out var value) || value != dailyMinSumSafe) { LastMixProgressDailyMinSum[instanceID] = dailyMinSumSafe; int num5 = Mathf.Min(num4 + 1, num2); try { __instance.CurrentMixTime = num5; } catch { } num3 = num5; if (num4 < num2 && num5 >= num2) { TryIncrementMixingCompletedVariable(); try { __instance.MixingDone_Networked(); } catch { } } } } } int num6 = Mathf.Max(0, num2 - num3); MixingStationMk2 val = __instance.AsMk2(); if ((Object)(object)val != (Object)null) { MixingStationMk2UiBroadcast.WriteRemainingMinutesToMk2Ui(val, num6); } else { object obj6 = MixingStationClockReflection.ResolveClock(__instance); if (obj6 != null) { MixingStationClockReflection.SetScreenLit(obj6, lit: true); MixingStationClockReflection.DisplayMinutes(obj6, num6); } } try { if ((Object)(object)__instance.Light != (Object)null) { if (__instance.IsMixingDone) { __instance.Light.isOn = GetDailyMinSumSafe() % 2 == 0; } else { __instance.Light.isOn = true; } } } catch { } } else { MixingStationMk2 val2 = __instance.AsMk2(); if ((Object)(object)val2 != (Object)null) { MixingStationMk2UiBroadcast.BlankMk2Ui(val2); } else { object obj8 = MixingStationClockReflection.ResolveClock(__instance); if (obj8 != null) { MixingStationClockReflection.SetScreenLit(obj8, lit: false); MixingStationClockReflection.DisplayText(obj8, string.Empty); } } try { if ((Object)(object)__instance.Light != (Object)null && __instance.IsMixingDone) { __instance.Light.isOn = false; } } catch { } } return false; } private static int SafeGetScaledRequired(MixingStation station, ModMixingStationsConfiguration config) { SuppressGetMixTimePostfix = true; try { int num = 0; try { num = station.GetMixTimeForCurrentOperation(); } catch { return 1; } if (!config.Enabled) { return Mathf.Max(1, num); } float num2 = config.GetEffectiveMixSpeed(); if (num2 <= 0f) { num2 = 1f; } return Mathf.Max(1, Mathf.FloorToInt((float)num / num2)); } finally { SuppressGetMixTimePostfix = false; } } private static void TryIncrementMixingCompletedVariable() { try { VariableDatabase instance = NetworkSingleton.Instance; if (!((Object)(object)instance == (Object)null)) { float value = instance.GetValue("Mixing_Operations_Completed"); instance.SetVariableValue("Mixing_Operations_Completed", (value + 1f).ToString(), true); } } catch { } } internal static int GetScaledRequiredMinutes(MixingStation station, ModMixingStationsConfiguration config) { SuppressGetMixTimePostfix = true; try { int mixTimeForCurrentOperation = station.GetMixTimeForCurrentOperation(); if (!config.Enabled) { return mixTimeForCurrentOperation; } float effectiveMixSpeed = config.GetEffectiveMixSpeed(); return Mathf.Max(1, Mathf.FloorToInt((float)mixTimeForCurrentOperation / effectiveMixSpeed)); } finally { SuppressGetMixTimePostfix = false; } } internal static void GetMixTimePostfix(ref int __result) { ModMixingStationsConfiguration configuration = Core.Get().Configuration; if (configuration.Enabled) { float effectiveMixSpeed = configuration.GetEffectiveMixSpeed(); __result = Mathf.Max(1, Mathf.FloorToInt((float)__result / effectiveMixSpeed)); } } internal static void StartPostfix(MixingStation __instance) { ModMixingStationsConfiguration configuration = Core.Get().Configuration; if (configuration.Enabled) { __instance.MaxMixQuantity = configuration.InputCapacity; } } } [HarmonyPatch(typeof(MixingStation), "OnMinPass")] internal static class MixingStationOnMinPassPatch { [HarmonyPrefix] public static bool Prefix(MixingStation __instance) { return MixingStationPatchLogic.OnMinPassPrefix(__instance); } } [HarmonyPatch] internal static class MixingStationMk2OnMinPassPatch { private static bool Prepare() { MethodInfo methodInfo = AccessTools.Method(typeof(MixingStationMk2), "OnMinPass", (Type[])null, (Type[])null); MethodInfo methodInfo2 = AccessTools.Method(typeof(MixingStation), "OnMinPass", (Type[])null, (Type[])null); if (methodInfo != null && methodInfo2 != null) { return (object)methodInfo != methodInfo2; } return false; } private static MethodBase TargetMethod() { return AccessTools.Method(typeof(MixingStationMk2), "OnMinPass", (Type[])null, (Type[])null); } [HarmonyPrefix] public static bool Prefix(MixingStationMk2 __instance) { return MixingStationPatchLogic.OnMinPassPrefix((MixingStation)(object)__instance); } } [HarmonyPatch] internal static class MixingStationCanvasUpdatePatch { private sealed class CanvasCache { internal MixingStation Station; internal List Tmps; internal int LastWritten = int.MinValue; internal bool LastWasIdle; } private static readonly Dictionary CacheByCanvasId = new Dictionary(); private static readonly Regex RxMinsRemaining = new Regex("\\d+(\\.\\d+)?\\s*mins?\\s*remaining", RegexOptions.IgnoreCase | RegexOptions.Compiled); private static readonly Regex RxLeadingMins = new Regex("^\\s*\\d+(\\.\\d+)?\\s*mins?", RegexOptions.IgnoreCase | RegexOptions.Compiled); private static MethodBase TargetMethod() { return AccessTools.DeclaredMethod(typeof(MixingStationCanvas), "LateUpdate", (Type[])null, (Type[])null) ?? AccessTools.DeclaredMethod(typeof(MixingStationCanvas), "Update", (Type[])null, (Type[])null); } private static bool Prepare() { return TargetMethod() != null; } [HarmonyPostfix] [HarmonyPriority(0)] private static void Postfix(MixingStationCanvas __instance) { if ((Object)(object)__instance == (Object)null) { return; } ModMixingStations modMixingStations = Core.Get(); if (modMixingStations?.Configuration == null || !modMixingStations.Configuration.Enabled) { return; } int instanceID = ((Object)__instance).GetInstanceID(); if (!CacheByCanvasId.TryGetValue(instanceID, out var value) || value == null) { value = BuildCache(__instance); CacheByCanvasId[instanceID] = value; } MixingStation station = value.Station; if ((Object)(object)station == (Object)null) { return; } bool flag; try { flag = station.CurrentMixOperation != null; } catch { return; } if (!flag) { value.LastWasIdle = true; value.LastWritten = int.MinValue; return; } int scaledRequiredMinutes; try { scaledRequiredMinutes = MixingStationPatchLogic.GetScaledRequiredMinutes(station, modMixingStations.Configuration); } catch { return; } int num; try { num = Mathf.Max(0, scaledRequiredMinutes - station.CurrentMixTime); } catch { return; } if (!value.LastWasIdle && value.LastWritten == num) { return; } value.LastWasIdle = false; value.LastWritten = num; if (value.Tmps == null) { return; } for (int i = 0; i < value.Tmps.Count; i++) { TMP_Text val = value.Tmps[i]; if ((Object)(object)val == (Object)null) { continue; } string text = val.text; if (!string.IsNullOrEmpty(text)) { string text2 = RxMinsRemaining.Replace(text, $"{num} mins remaining"); if (text2 == text) { text2 = RxLeadingMins.Replace(text, $"{num} mins"); } if (text2 != text) { val.text = text2; } } } } private static CanvasCache BuildCache(MixingStationCanvas canvas) { CanvasCache canvasCache = new CanvasCache { Station = ResolveStation(canvas), Tmps = new List(4) }; try { if ((Object)(object)((Component)canvas).transform != (Object)null) { foreach (TMP_Text componentsInChild in ((Component)canvas).GetComponentsInChildren(true)) { if (!((Object)(object)componentsInChild == (Object)null)) { string text = componentsInChild.text; if (!string.IsNullOrEmpty(text) && (text.IndexOf("min", StringComparison.OrdinalIgnoreCase) >= 0 || text.IndexOf("remaining", StringComparison.OrdinalIgnoreCase) >= 0)) { canvasCache.Tmps.Add(componentsInChild); } } } } } catch { } return canvasCache; } private static MixingStation ResolveStation(MixingStationCanvas canvas) { Type typeFromHandle = typeof(MixingStationCanvas); string[] array = new string[5] { "MixingStation", "mixingStation", "Station", "station", "ParentStation" }; foreach (string name in array) { try { PropertyInfo property = typeFromHandle.GetProperty(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (property != null) { object? value = property.GetValue(canvas); MixingStation val = (MixingStation)((value is MixingStation) ? value : null); if (val != null) { return val; } } } catch { } try { FieldInfo field = typeFromHandle.GetField(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (field != null) { object? value2 = field.GetValue(canvas); MixingStation val2 = (MixingStation)((value2 is MixingStation) ? value2 : null); if (val2 != null) { return val2; } } } catch { } } try { return ((Component)canvas).GetComponentInParent(); } catch { return null; } } } internal static class MixingStationMk2Check { internal static MixingStationMk2 AsMk2(this MixingStation station) { if ((Object)(object)station == (Object)null) { return null; } try { return ((Il2CppObjectBase)station).TryCast(); } catch { return null; } } internal static bool IsMk2(this MixingStation station) { return (Object)(object)station.AsMk2() != (Object)null; } internal static Transform ResolveStationRoot(MixingStation station) { if ((Object)(object)station == (Object)null) { return null; } try { MixingStationMk2 val = station.AsMk2(); if ((Object)(object)val != (Object)null && (Object)(object)((Component)val).transform != (Object)null) { return ((Component)val).transform; } } catch { } try { return ((Component)station).transform; } catch { return null; } } } } namespace Lithium.Modules.TrashGrabber { public class ModTrashGrabberConfiguration : ModuleConfiguration { public override string Name => "TrashGrabber"; public int CustomCapacity { get; set; } = 20; } public class ModTrashGrabber : ModuleBase { public override void Apply() { _ = base.Configuration.Enabled; } } } namespace Lithium.Modules.TrashGrabber.Patches { [HarmonyPatch(typeof(Equippable_TrashGrabber), "GetCapacity")] public static class EquippableTrashGrabberGetCapacityPatch { [HarmonyPostfix] public static void Postfix(ref int __result, Equippable_TrashGrabber __instance) { ModTrashGrabberConfiguration configuration = Core.Get().Configuration; if (configuration.Enabled) { __result = configuration.CustomCapacity - __instance.trashGrabberInstance.GetTotalSize(); } } } } namespace Lithium.Modules.StackSizes { public class ModStackSizesConfiguration : ModuleConfiguration { public override string Name => "StackSizes"; public Dictionary CategorySizes { get; set; } = new Dictionary { { (EItemCategory)0, 20 }, { (EItemCategory)1, 20 }, { (EItemCategory)2, 20 }, { (EItemCategory)3, 10 }, { (EItemCategory)4, 10 }, { (EItemCategory)5, 10 }, { (EItemCategory)6, 1000 }, { (EItemCategory)7, 20 }, { (EItemCategory)8, 20 }, { (EItemCategory)9, 20 }, { (EItemCategory)10, 10 }, { (EItemCategory)11, 10 }, { (EItemCategory)12, 10 } }; public Dictionary ItemOverrides { get; set; } = new Dictionary(); public List IgnoredItems { get; set; } = new List(); } public class ModStackSizes : ModuleBase { public override void Apply() { _ = base.Configuration.Enabled; } } public static class ItemRegistry { public static List AllItemDefinitions = new List(); public static void UpdateEntireRegistry() { foreach (ItemDefinition allItemDefinition in AllItemDefinitions) { UpdateItemDefinition(allItemDefinition); } } public static void UpdateItemDefinition(ItemDefinition itemDefinition) { //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_007b: Unknown result type (might be due to invalid IL or missing references) ModStackSizesConfiguration configuration = Core.Get().Configuration; if (configuration.Enabled && !configuration.IgnoredItems.Contains(((BaseItemDefinition)itemDefinition).ID)) { if (configuration.ItemOverrides.TryGetValue(((BaseItemDefinition)itemDefinition).ID, out var value)) { ((BaseItemDefinition)itemDefinition).StackLimit = value; return; } if (configuration.CategorySizes.TryGetValue(((BaseItemDefinition)itemDefinition).Category, out value)) { ((BaseItemDefinition)itemDefinition).StackLimit = value; return; } MelonLogger.Warning($"Found item for category {((BaseItemDefinition)itemDefinition).Category}, but no category value"); } } } } namespace Lithium.Modules.StackSizes.Patches { [HarmonyPatch(typeof(ItemUIManager), "UpdateCashDragAmount")] [HarmonyPatch(typeof(ItemUIManager), "StartDragCash")] [HarmonyPatch(typeof(ItemUIManager), "EndCashDrag")] public class CashDragPatch { [HarmonyTranspiler] public static IEnumerable TranspilerPatch(IEnumerable instructions) { foreach (CodeInstruction instruction in instructions) { if (instruction.opcode == OpCodes.Ldc_R4 && (float)instruction.operand == 1000f) { yield return new CodeInstruction(OpCodes.Ldc_R4, (object)5000f); } else { yield return instruction; } } } } [HarmonyPatch(typeof(Registry), "AddToRegistry")] public class RegistryAddToRegistryPatch { [HarmonyPostfix] public static void RegistryAddToRegistry(Registry __instance, ItemDefinition item) { CollectionExtensions.AddItem((IEnumerable)ItemRegistry.AllItemDefinitions, item); ItemRegistry.UpdateItemDefinition(item); } } [HarmonyPatch(typeof(Registry), "Start")] public class RegistryStartPatch { [HarmonyPostfix] public static void RegistryStart(Registry __instance) { if (Core.Get().Configuration.Enabled) { List list = new List(); Enumerator enumerator = __instance.ItemRegistry.GetEnumerator(); while (enumerator.MoveNext()) { ItemRegister current = enumerator.Current; list.Add(current); } ItemRegistry.AllItemDefinitions = list.Select((ItemRegister itemRegister) => itemRegister.Definition).ToList(); ItemRegistry.UpdateEntireRegistry(); } } } } namespace Lithium.Modules.PlantGrowth { public class WeightedFloat { public float Weight; public float Value; [JsonConstructor] public WeightedFloat(float weight, float value) { Weight = weight; Value = value; } } public class PlantsPotsSection { [JsonProperty(Order = -10)] public string _Note = "POT-BASED PLANTS (Weed/Coca/Meth). GrowthSpeed: 1.0 = normal speed, 2.0 = 2x faster, 0.5 = 2x slower. WaterDrainModifier: soil drying speed — 1.0 = normal, 0.5 = dries 2x slower."; public float? GrowthSpeed = 1f; public float? WaterDrainModifier = 1f; } public class PlantsShroomsSection { [JsonProperty(Order = -10)] public string _Note = "MUSHROOMS (Mushroom Bed). GrowthSpeed: 1.0 = normal speed, 2.0 = 2x faster, 0.5 = 2x slower. WaterDrainModifier: substrate drying speed — 1.0 = normal, 0.5 = dries 2x slower."; public float? GrowthSpeed = 1f; public float? GrowthModifier; public float? WaterDrainModifier = 1f; } public class ModPlantsConfiguration : ModuleConfiguration { [JsonProperty(Order = -100)] public string _Note = "Lithium 'Plants' module — configure PLANTS (section 'Plants') and MUSHROOMS (section 'Shrooms') separately. The legacy GrowthModifier / WaterDrainModifier fields are used as a fallback when an explicit section value is not set."; public PlantsPotsSection Plants = new PlantsPotsSection(); public PlantsShroomsSection Shrooms = new PlantsShroomsSection(); public float GrowthModifier = 1f; public float WaterDrainModifier = 1f; public bool ShroomGrowContainersMatchNameOnly = true; public static readonly List RandomYieldsPerBudModifierDefaults = new List(3) { new WeightedFloat(7f, 1f), new WeightedFloat(2f, 2f), new WeightedFloat(1f, 3f) }; public static readonly List RandomYieldModifiersDefaults = new List(4) { new WeightedFloat(7.5f, 1f), new WeightedFloat(1f, 0.25f), new WeightedFloat(1f, 1.5f), new WeightedFloat(0.5f, 3f) }; public static readonly List RandomQualityModifiersDefaults = new List(5) { new WeightedFloat(0f, -0.5f), new WeightedFloat(0.5f, 0f), new WeightedFloat(0.75f, 0f), new WeightedFloat(0.2f, 0.4f), new WeightedFloat(0.01f, 0.5f) }; public List RandomYieldsPerBudModifier = RandomYieldsPerBudModifierDefaults; public List RandomYieldModifiers = RandomYieldModifiersDefaults; public List RandomQualityModifiers = RandomQualityModifiersDefaults; [JsonIgnore] public WeightedPicker RandomYieldPerBudPicker; [JsonIgnore] public WeightedNormalizer RandomYieldModifierPicker; [JsonIgnore] public WeightedNormalizer RandomYieldQualityPicker; public override string Name => "Plants"; internal float EffectivePotsGrowthModifier => Plants?.GrowthSpeed ?? GrowthModifier; internal float EffectivePotsWaterDrainModifier => Plants?.WaterDrainModifier ?? WaterDrainModifier; internal float EffectiveShroomGrowthModifier => Shrooms?.GrowthSpeed ?? Shrooms?.GrowthModifier ?? GrowthModifier; internal float EffectiveShroomWaterDrainModifier => Shrooms?.WaterDrainModifier ?? WaterDrainModifier; } public class ModPlants : ModuleBase { public ModPlants() { ClassInjector.RegisterTypeInIl2Cpp(); ClassInjector.RegisterTypeInIl2Cpp(); ClassInjector.RegisterTypeInIl2Cpp(); ClassInjector.RegisterTypeInIl2Cpp(); } protected override void OnBeforeConfigurationLoaded() { base.OnBeforeConfigurationLoaded(); base.Configuration.RandomYieldsPerBudModifier.Clear(); base.Configuration.RandomYieldModifiers.Clear(); base.Configuration.RandomQualityModifiers.Clear(); } public override void Load() { base.Load(); EnsureWeightedListsFromDefaults(); RebuildWeightedPickers(); } private void EnsureWeightedListsFromDefaults() { if (base.Configuration.RandomYieldsPerBudModifier.Count == 0) { foreach (WeightedFloat randomYieldsPerBudModifierDefault in ModPlantsConfiguration.RandomYieldsPerBudModifierDefaults) { base.Configuration.RandomYieldsPerBudModifier.Add(new WeightedFloat(randomYieldsPerBudModifierDefault.Weight, randomYieldsPerBudModifierDefault.Value)); } } if (base.Configuration.RandomYieldModifiers.Count == 0 || base.Configuration.RandomYieldModifiers.Sum((WeightedFloat e) => e.Weight) <= 0f) { base.Configuration.RandomYieldModifiers.Clear(); foreach (WeightedFloat randomYieldModifiersDefault in ModPlantsConfiguration.RandomYieldModifiersDefaults) { base.Configuration.RandomYieldModifiers.Add(new WeightedFloat(randomYieldModifiersDefault.Weight, randomYieldModifiersDefault.Value)); } } if (base.Configuration.RandomQualityModifiers.Count != 0 && !(base.Configuration.RandomQualityModifiers.Sum((WeightedFloat e) => e.Weight) <= 0f)) { return; } base.Configuration.RandomQualityModifiers.Clear(); foreach (WeightedFloat randomQualityModifiersDefault in ModPlantsConfiguration.RandomQualityModifiersDefaults) { base.Configuration.RandomQualityModifiers.Add(new WeightedFloat(randomQualityModifiersDefault.Weight, randomQualityModifiersDefault.Value)); } } private void RebuildWeightedPickers() { base.Configuration.RandomYieldPerBudPicker = new WeightedPicker(); base.Configuration.RandomYieldPerBudPicker.AddRange(base.Configuration.RandomYieldsPerBudModifier.Select((WeightedFloat p) => new KeyValuePair(p.Value, p.Weight))); base.Configuration.RandomYieldModifierPicker = new WeightedNormalizer(); foreach (WeightedFloat randomYieldModifier in base.Configuration.RandomYieldModifiers) { base.Configuration.RandomYieldModifierPicker.Add(randomYieldModifier.Weight, randomYieldModifier.Value); } base.Configuration.RandomYieldQualityPicker = new WeightedNormalizer(); foreach (WeightedFloat randomQualityModifier in base.Configuration.RandomQualityModifiers) { base.Configuration.RandomYieldQualityPicker.Add(randomQualityModifier.Weight, randomQualityModifier.Value); } } public override void Apply() { _ = base.Configuration.Enabled; } } } namespace Lithium.Modules.PlantGrowth.Patches { [HarmonyPatch(typeof(HarvestPotBehaviour), "OnActionSuccess")] public class HarvestPotBehaviourPatch { public static void Postfix(HarvestPotBehaviour __instance, ItemInstance usedItem) { ModPlantsConfiguration configuration = Core.Get().Configuration; if (!configuration.Enabled) { return; } Botanist botanist = ((GrowContainerBehaviour)__instance)._botanist; if (!((Object)(object)botanist == (Object)null) && !((Object)(object)((NPC)botanist).Inventory == (Object)null)) { ItemInstance itemToRetrieveTemplate = ((Employee)botanist).MoveItemBehaviour.itemToRetrieveTemplate; if (itemToRetrieveTemplate != null) { ((BaseItemInstance)itemToRetrieveTemplate).Quantity = (int)Math.Min((float)((BaseItemInstance)itemToRetrieveTemplate).Quantity * configuration.RandomYieldPerBudPicker.Pick(), ((BaseItemInstance)itemToRetrieveTemplate).StackLimit); } } } } [HarmonyPatch(typeof(Plant), "GrowthDone")] public class PlantGrowthDonePatch { [HarmonyPrefix] public static void Prefix(Plant __instance) { ModPlantsConfiguration configuration = Core.Get().Configuration; if (configuration.Enabled && !((Object)(object)__instance == (Object)null) && !((Object)(object)((Component)__instance).GetComponent() != (Object)null)) { PlantModified plantModified = ((Component)__instance).gameObject.AddComponent(); plantModified.OriginalYieldLevel = __instance.YieldMultiplier; Plant val = ((Component)__instance).GetComponentInParent(); if ((Object)(object)val == (Object)null) { val = ((Component)__instance).GetComponent(); } if ((Object)(object)val != (Object)null) { plantModified.QualityLevel = val.QualityLevel; } __instance.YieldMultiplier *= configuration.RandomYieldModifierPicker.Evaluate(Random.value); } } } [HarmonyPatch(typeof(PlantHarvestable), "Harvest")] public class PlantHarvestablePatch { private static readonly Dictionary SkipFlags = new Dictionary(); private static readonly Dictionary GenerateFlags = new Dictionary(); [HarmonyPrefix] public static bool Prefix(PlantHarvestable __instance, bool giveProduct) { //IL_009d: Unknown result type (might be due to invalid IL or missing references) //IL_00a2: Unknown result type (might be due to invalid IL or missing references) //IL_00a8: Expected O, but got Unknown ModPlantsConfiguration configuration = Core.Get().Configuration; if (!configuration.Enabled) { return true; } Plant componentInParent = ((Component)__instance).GetComponentInParent(); PlantBaseQuality plantBaseQuality = default(PlantBaseQuality); if (!((Component)componentInParent).TryGetComponent(ref plantBaseQuality)) { PlantBaseQuality plantBaseQuality2 = ((Component)componentInParent).gameObject.AddComponent(); plantBaseQuality2.Quality = componentInParent.QualityLevel; plantBaseQuality2.NeedsNotification = true; } if (!GenerateFlags.ContainsKey(__instance)) { componentInParent.QualityLevel += configuration.RandomYieldQualityPicker.Evaluate(Random.value); __instance.ProductQuantity = (int)configuration.RandomYieldPerBudPicker.Pick(); GenerateFlags[__instance] = true; } QualityItemInstance val = new QualityItemInstance((ItemDefinition)(object)__instance.Product, __instance.ProductQuantity, ItemQuality.GetQuality(componentInParent.QualityLevel)); if (!PlayerSingleton.Instance.CanItemFitInInventory((ItemInstance)(object)val, 1)) { SkipFlags[__instance] = true; return false; } SkipFlags.Remove(__instance); return true; } [HarmonyPostfix] public static void Postfix(PlantHarvestable __instance) { //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_00a4: Unknown result type (might be due to invalid IL or missing references) if (Core.Get().Configuration.Enabled && !SkipFlags.ContainsKey(__instance) && GenerateFlags.Remove(__instance)) { Plant componentInParent = ((Component)__instance).GetComponentInParent(); PlantBaseQuality plantBaseQuality = default(PlantBaseQuality); if (((Component)componentInParent).TryGetComponent(ref plantBaseQuality) && plantBaseQuality.NeedsNotification) { EQuality quality = ItemQuality.GetQuality(componentInParent.QualityLevel); Singleton.Instance.SendNotification($"{__instance.ProductQuantity}x {((BaseItemDefinition)componentInParent.SeedDefinition).Name}", $"{quality:G} quality", ((BaseItemDefinition)componentInParent.SeedDefinition).Icon, 2f, false); componentInParent.QualityLevel = plantBaseQuality.Quality; plantBaseQuality.NeedsNotification = false; Object.Destroy((Object)(object)plantBaseQuality); } } } } [HarmonyPatch(typeof(Pot), "GetTemperatureGrowthMultiplier")] public class PotPatch { [HarmonyPostfix] public static void Postfix(ref float __result) { ModPlantsConfiguration configuration = Core.Get().Configuration; if (configuration.Enabled) { float effectivePotsGrowthModifier = configuration.EffectivePotsGrowthModifier; if (effectivePotsGrowthModifier <= 0.001f) { MelonLogger.Error("[Lithium_fork][Plants] Plants.GrowthSpeed (or root GrowthModifier) is invalid (<= 0). Skipping patch."); } else { __result *= Mathf.Max(0.001f, effectivePotsGrowthModifier); } } } } [HarmonyPatch(typeof(Pot), "Start")] public class PotStartPatch { [HarmonyPostfix] public static void Postfix(Pot __instance) { if (Core.Get().Configuration.Enabled) { ((Component)__instance).gameObject.AddComponent().Init(__instance); } } } [HarmonyPatch(typeof(Pot), "OnMinPass")] public class PotMinPassPatch { [HarmonyPrefix] public static void Prefix(Pot __instance) { if (Core.Get().Configuration.Enabled && !((Object)(object)__instance == (Object)null)) { PotBaseValues component = ((Component)__instance).gameObject.GetComponent(); if (!((Object)(object)component == (Object)null)) { float baseWaterDrainPerHour = component.BaseWaterDrainPerHour; ((GrowContainer)__instance)._moistureDrainPerHour = baseWaterDrainPerHour * Core.Get().Configuration.EffectivePotsWaterDrainModifier; } } } } internal static class GrowContainerShroomHarmony { internal static void Apply(Harmony harmony) { //IL_01ff: Unknown result type (might be due to invalid IL or missing references) //IL_020d: Expected O, but got Unknown ModPlants modPlants = Core.Get(); if (modPlants?.Configuration == null || !modPlants.Configuration.Enabled) { MelonLogger.Msg("[Lithium_fork][Plants] Plants module disabled — shroom hooks skipped (set Plants.json Enabled=true to activate)."); return; } Type typeFromHandle = typeof(GrowContainer); Type typeFromHandle2 = typeof(Pot); Assembly assembly = typeFromHandle.Assembly; Type[] array; try { array = assembly.GetTypes(); } catch (ReflectionTypeLoadException ex) { array = ex.Types?.Where((Type t) => t != null).Cast().ToArray() ?? Array.Empty(); } List list = new List(); Type[] array2 = array; foreach (Type type in array2) { if (!(type == null) && !type.IsAbstract && typeFromHandle.IsAssignableFrom(type) && !(type == typeFromHandle2) && (!modPlants.Configuration.ShroomGrowContainersMatchNameOnly || LikelyShroomRelatedTypeName(type.Name))) { list.Add(type); } } MelonLogger.Msg($"[Lithium_fork][Plants] Shroom GrowContainer scan: {list.Count} candidate type(s) found."); int num = 0; int num2 = 0; int num3 = 0; foreach (Type item in list) { num += TryPatchDeclared(harmony, item, "GetTemperatureGrowthMultiplier", "GrowthPostfix", requireFloatNoArgs: true, null); num2 += TryPatchDeclared(harmony, item, "GetCurrentGrowthRate", "GrowthRatePostfix", requireFloatNoArgs: true, null); num3 += TryPatchDeclared(harmony, item, "OnMinPass", null, requireFloatNoArgs: false, "OnMinPassPrefix"); } int value = 0; try { MethodInfo methodInfo = AccessTools.DeclaredMethod(typeof(GrowContainer), "OnMinPass", (Type[])null, (Type[])null); if (methodInfo != null) { harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(GrowContainerShroomHarmony), "OnMinPassPrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); value = 1; } } catch (Exception ex2) { MelonLogger.Warning("[Lithium_fork][Plants] Could not patch GrowContainer.OnMinPass (base): " + ex2.Message); } MelonLogger.Msg($"[Lithium_fork][Plants] Shroom patches applied: GetTemperatureGrowthMultiplier={num}, GetCurrentGrowthRate={num2}, OnMinPass(declared)={num3}, OnMinPass(base)={value}."); MelonLogger.Msg($"[Lithium_fork][Plants] Shroom config in use: GrowthSpeed={modPlants.Configuration.EffectiveShroomGrowthModifier:F3}, WaterDrain={modPlants.Configuration.EffectiveShroomWaterDrainModifier:F3}."); } private static int TryPatchDeclared(Harmony harmony, Type t, string methodName, string postfix, bool requireFloatNoArgs, string prefix) { //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Unknown result type (might be due to invalid IL or missing references) MethodInfo methodInfo = AccessTools.DeclaredMethod(t, methodName, (Type[])null, (Type[])null); if (methodInfo == null) { return 0; } if (requireFloatNoArgs && (methodInfo.ReturnType != typeof(float) || methodInfo.GetParameters().Length != 0)) { return 0; } try { HarmonyMethod val = ((prefix == null) ? ((HarmonyMethod)null) : new HarmonyMethod(typeof(GrowContainerShroomHarmony), prefix, (Type[])null)); HarmonyMethod val2 = ((postfix == null) ? ((HarmonyMethod)null) : new HarmonyMethod(typeof(GrowContainerShroomHarmony), postfix, (Type[])null)); harmony.Patch((MethodBase)methodInfo, val, val2, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); MelonLogger.Msg($"[Lithium_fork][Plants] patched {t.FullName}.{methodName} ({((val != null) ? "prefix" : "")}{((val != null && val2 != null) ? "+" : "")}{((val2 != null) ? "postfix" : "")})."); return 1; } catch (Exception ex) { MelonLogger.Warning($"[Lithium_fork][Plants] patch failed: {t.FullName}.{methodName}: {ex.Message}"); return 0; } } private static bool LikelyShroomRelatedTypeName(string typeName) { if (string.IsNullOrEmpty(typeName)) { return false; } if (typeName.IndexOf("Mushroom", StringComparison.OrdinalIgnoreCase) < 0 && typeName.IndexOf("Substrate", StringComparison.OrdinalIgnoreCase) < 0 && typeName.IndexOf("Fungal", StringComparison.OrdinalIgnoreCase) < 0 && typeName.IndexOf("Shroom", StringComparison.OrdinalIgnoreCase) < 0 && typeName.IndexOf("Mycelium", StringComparison.OrdinalIgnoreCase) < 0 && typeName.IndexOf("MonoTub", StringComparison.OrdinalIgnoreCase) < 0 && typeName.IndexOf("SpawnStation", StringComparison.OrdinalIgnoreCase) < 0) { return typeName.IndexOf("Colony", StringComparison.OrdinalIgnoreCase) >= 0; } return true; } private static void GrowthPostfix(GrowContainer __instance, ref float __result) { ScaleGrowthResult(__instance, ref __result); } private static void GrowthRatePostfix(GrowContainer __instance, ref float __result) { ScaleGrowthResult(__instance, ref __result); } private static void ScaleGrowthResult(GrowContainer __instance, ref float __result) { ModPlantsConfiguration modPlantsConfiguration = Core.Get()?.Configuration; if (modPlantsConfiguration == null || !modPlantsConfiguration.Enabled) { return; } try { if (__instance is Pot) { return; } } catch { } if (!modPlantsConfiguration.ShroomGrowContainersMatchNameOnly || LikelyShroomRelatedTypeName(((object)__instance).GetType().Name)) { float effectiveShroomGrowthModifier = modPlantsConfiguration.EffectiveShroomGrowthModifier; if (effectiveShroomGrowthModifier <= 0.001f) { MelonLogger.Error("[Lithium_fork][Plants] Invalid Shroom growth modifier. Skipping (set Plants.Shrooms.GrowthSpeed > 0)."); } else { __result *= Mathf.Max(0.001f, effectiveShroomGrowthModifier); } } } private static void OnMinPassPrefix(GrowContainer __instance) { ModPlantsConfiguration modPlantsConfiguration = Core.Get()?.Configuration; if (modPlantsConfiguration == null || !modPlantsConfiguration.Enabled) { return; } try { if (__instance is Pot) { return; } } catch { } if (!modPlantsConfiguration.ShroomGrowContainersMatchNameOnly || LikelyShroomRelatedTypeName(((object)__instance).GetType().Name)) { GrowContainerMoistureTracker growContainerMoistureTracker = ((Component)__instance).gameObject.GetComponent(); if ((Object)(object)growContainerMoistureTracker == (Object)null) { growContainerMoistureTracker = ((Component)__instance).gameObject.AddComponent(); growContainerMoistureTracker.BaseDrainPerHour = __instance._moistureDrainPerHour; } float effectiveShroomWaterDrainModifier = modPlantsConfiguration.EffectiveShroomWaterDrainModifier; if (!(effectiveShroomWaterDrainModifier <= 0.001f)) { __instance._moistureDrainPerHour = growContainerMoistureTracker.BaseDrainPerHour * Mathf.Max(0.001f, effectiveShroomWaterDrainModifier); } } } } } namespace Lithium.Modules.PlantGrowth.Behaviours { public class PlantBaseQuality : MonoBehaviour { public float Quality; public bool NeedsNotification; } public class PlantModified : MonoBehaviour { public float OriginalYieldLevel; public float QualityLevel; } public class PotBaseValues : MonoBehaviour { public float BaseWaterDrainPerHour; public void Init(Pot pot) { BaseWaterDrainPerHour = ((GrowContainer)pot)._moistureDrainPerHour; } } public class GrowContainerMoistureTracker : MonoBehaviour { public float BaseDrainPerHour; } } namespace Lithium.Modules.Storyline { public class ModStorylineConfiguration : ModuleConfiguration { public override string Name => "Storyline"; public bool PreventRVExplosion { get; set; } = true; } public class ModStoryline : ModuleBase { public override void Apply() { _ = base.Configuration.Enabled; } } } namespace Lithium.Modules.Storyline.Patches { [HarmonyPatch(typeof(RV), "SetDestroyed")] public class RVSetExplodedPatch { [HarmonyPrefix] public static bool DisableRVExplosion(RV __instance) { ModStorylineConfiguration configuration = Core.Get().Configuration; if (!configuration.Enabled || !configuration.PreventRVExplosion) { return true; } GameObject childGameObject = GetChildGameObject(((Component)__instance).gameObject, "Destroyed RV"); if ((Object)(object)childGameObject != (Object)null) { childGameObject.SetActive(true); GameObject childGameObject2 = GetChildGameObject(childGameObject, "CartelNote"); if ((Object)(object)childGameObject2 != (Object)null) { childGameObject2.SetActive(true); } GameObject childGameObject3 = GetChildGameObject(childGameObject, "destroyed rv"); if ((Object)(object)childGameObject3 != (Object)null) { childGameObject3.SetActive(false); } } else { MelonLogger.Msg("Destroyed RV not found"); } return false; } private static GameObject GetChildGameObject(GameObject obj, string childName) { Transform val = obj.transform.Find(childName); if (!((Object)(object)val != (Object)null)) { return null; } return ((Component)val).gameObject; } } [HarmonyPatch(typeof(Quest_WelcomeToHylandPoint), "BlowupRV")] public class QuestWelcomeToHylandPointPatch { [HarmonyPrefix] public static bool DisableRVExplosion(Quest_WelcomeToHylandPoint __instance) { ModStorylineConfiguration configuration = Core.Get().Configuration; if (!configuration.Enabled || !configuration.PreventRVExplosion) { return true; } return false; } } } namespace Lithium.Modules.Shops { internal static class SupplierPhoneListingPriceHelper { private const BindingFlags InstanceAny = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic; internal static void ApplyListingPriceFieldsOnly(Listing listing, float price) { if (listing == null) { return; } Type type = ((object)listing).GetType(); while (type != null && type != typeof(object)) { string[] array = new string[4] { "Price", "price", "_price", "k__BackingField" }; foreach (string name in array) { FieldInfo field = type.GetField(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (!(field == null) && !(field.FieldType != typeof(float))) { try { field.SetValue(listing, price); } catch { } } } type = type.BaseType; } } } internal static class PhoneShopInterfaceListingsHelper { private static readonly string[] PreferredMemberNames = new string[8] { "Listings", "listings", "ShopListings", "shopListings", "OnlineShopListings", "onlineShopListings", "ActiveListings", "activeListings" }; private const BindingFlags InstanceAny = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic; internal static IEnumerable EnumerateListings(PhoneShopInterface psi) { if ((Object)(object)psi == (Object)null) { yield break; } Type t = typeof(PhoneShopInterface); string[] preferredMemberNames = PreferredMemberNames; foreach (string name in preferredMemberNames) { PropertyInfo property = t.GetProperty(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (property != null && property.GetIndexParameters().Length == 0) { object obj; try { obj = property.GetValue(psi); } catch { obj = null; } if (obj != null) { foreach (Listing item in EnumerateFromValue(obj)) { yield return item; } yield break; } } FieldInfo field = t.GetField(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (!(field != null)) { continue; } object obj3; try { obj3 = field.GetValue(psi); } catch { obj3 = null; } if (obj3 == null) { continue; } foreach (Listing item2 in EnumerateFromValue(obj3)) { yield return item2; } yield break; } PropertyInfo[] properties = t.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (PropertyInfo propertyInfo in properties) { if (propertyInfo.GetIndexParameters().Length != 0 || !LooksLikeListingCollectionName(propertyInfo.Name)) { continue; } foreach (Listing item3 in EnumerateFromMember(psi, propertyInfo)) { yield return item3; } } FieldInfo[] fields = t.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (FieldInfo fieldInfo in fields) { if (!LooksLikeListingCollectionName(fieldInfo.Name)) { continue; } foreach (Listing item4 in EnumerateFromField(psi, fieldInfo)) { yield return item4; } } } private static bool LooksLikeListingCollectionName(string name) { if (string.IsNullOrEmpty(name)) { return false; } return name.IndexOf("listing", StringComparison.OrdinalIgnoreCase) >= 0; } private static IEnumerable EnumerateFromMember(PhoneShopInterface psi, PropertyInfo pi) { object value; try { value = pi.GetValue(psi); } catch { yield break; } foreach (Listing item in EnumerateFromValue(value)) { yield return item; } } private static IEnumerable EnumerateFromField(PhoneShopInterface psi, FieldInfo fi) { object value; try { value = fi.GetValue(psi); } catch { yield break; } foreach (Listing item in EnumerateFromValue(value)) { yield return item; } } private static IEnumerable EnumerateFromValue(object raw) { if (raw == null) { yield break; } if (raw is Array array) { foreach (object item in array) { Listing val = (Listing)((item is Listing) ? item : null); if (val != null) { yield return val; } } } else { if (!(raw is IEnumerable enumerable)) { yield break; } foreach (object item2 in enumerable) { Listing val2 = (Listing)((item2 is Listing) ? item2 : null); if (val2 != null) { yield return val2; } } } } internal static void TryInvokeListingOrPriceRefresh(PhoneShopInterface psi) { if ((Object)(object)psi == (Object)null) { return; } Type typeFromHandle = typeof(PhoneShopInterface); string[] array = new string[6] { "Refresh", "RefreshListings", "RefreshPrices", "UpdatePrices", "RebuildListings", "UpdateListings" }; foreach (string name in array) { MethodInfo method = typeFromHandle.GetMethod(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null); if (!(method == null)) { try { method.Invoke(psi, null); break; } catch { break; } } } } } public class SupplierListingOverride { public Dictionary PriceOverrides { get; set; } = new Dictionary(); } public class ItemListingOverride { public float Price { get; set; } [JsonConverter(typeof(StringEnumConverter))] public ERestockRate RestockRate { get; set; } public int Stock { get; set; } = -1; } public class ShopListingSettings { [JsonProperty(Order = 6)] public Dictionary ItemOverrides = new Dictionary(); [JsonProperty(Order = 1)] public bool Override { get; set; } [JsonProperty(Order = 2)] public int DefaultStock { get; set; } = -1; [JsonConverter(typeof(StringEnumConverter))] [JsonProperty(Order = 3)] public EPaymentType PaymentType { get; set; } } public enum DeliveryAvailabilitySettings { Unchanged, Never, Always, AfterReachingXP } public class DeliverySettings { [JsonConverter(typeof(StringEnumConverter))] public DeliveryAvailabilitySettings Availability { get; set; } public float DeliveryFee { get; set; } = 200f; public int XPRequirement { get; set; } } public class ModShopsConfiguration : ModuleConfiguration { public ShopListingSettings ThriftyThreads; public ShopListingSettings CokeSupplier; public ShopListingSettings MethSupplier; public ShopListingSettings WeedSupplier; public ShopListingSettings ShroomsSupplier; public ShopListingSettings Boutique; public ShopListingSettings DarkMarket; public ShopListingSettings GasStation; public ShopListingSettings CentralGasStation; public ShopListingSettings DansHardware; public ShopListingSettings HandyHanks; public ShopListingSettings ArmsDealer; public SupplierListingOverride Albert; public SupplierListingOverride Shirley; public SupplierListingOverride Salvador; public SupplierListingOverride Fungal; public override string Name => "Shops"; [JsonProperty(Order = -450)] public bool PhoneMirrorListingPriceFields { get; set; } public Dictionary Deliveries { get; set; } } public class ModShops : ModuleBase { public override void Apply() { } } } namespace Lithium.Modules.Shops.Patches { [HarmonyPatch(typeof(ShopInterface), "SetIsOpen")] public class ForceUpdateShopPrices { [HarmonyPostfix] public static void ShopInterfaceSetIsOpen(ShopInterface __instance, bool isOpen) { if (isOpen && Core.Get().Configuration.Enabled) { SupplierStartPatch.ProcessShopInterface(__instance); } Enumerator enumerator = __instance.listingUI.GetEnumerator(); while (enumerator.MoveNext()) { ListingUI current = enumerator.Current; current.UpdateLockStatus(); current.Update(); current.UpdatePrice(); current.UpdateStock(); current.UpdateButtons(); } } } [HarmonyPatch(typeof(Player), "NetworkInitialize__Late")] public class SupplierStartPatch { private static bool _deferredUiSupplierApplyScheduled; private static readonly Dictionary LastAppliedStockByListing = new Dictionary(); [HarmonyPrefix] public static void PatchPrices() { ModShopsConfiguration configuration = Core.Get().Configuration; if (configuration.Enabled) { ApplyShopOverrides(); ApplySupplierOverrides(); configuration.SaveConfiguration(); } } public static void ApplySupplierOverridesFromUiHooks() { ModShops modShops = Core.Get(); if (modShops != null && modShops.Configuration.Enabled && !_deferredUiSupplierApplyScheduled) { _deferredUiSupplierApplyScheduled = true; MelonCoroutines.Start(DeferredUiSupplierApplyCoroutine()); } } private static IEnumerator DeferredUiSupplierApplyCoroutine() { yield return null; _deferredUiSupplierApplyScheduled = false; ApplySupplierOverrides(); yield return (object)new WaitForEndOfFrame(); ApplySupplierOverrides(); } public static void ApplySupplierOverrides() { ModShopsConfiguration configuration = Core.Get().Configuration; if (!configuration.Enabled) { return; } Albert val = ((IEnumerable)Object.FindObjectsOfType(true)).FirstOrDefault(); if ((Object)(object)val != (Object)null) { AssertSupplierConfigEntryExists(ref configuration.Albert, (Supplier)(object)val); if (configuration.Albert != null) { ApplySupplierConfigValues(configuration.Albert, (Supplier)(object)val); } } Shirley val2 = ((IEnumerable)Object.FindObjectsOfType(true)).FirstOrDefault(); if ((Object)(object)val2 != (Object)null) { AssertSupplierConfigEntryExists(ref configuration.Shirley, (Supplier)(object)val2); if (configuration.Shirley != null) { ApplySupplierConfigValues(configuration.Shirley, (Supplier)(object)val2); } } Salvador val3 = ((IEnumerable)Object.FindObjectsOfType(true)).FirstOrDefault(); if ((Object)(object)val3 != (Object)null) { AssertSupplierConfigEntryExists(ref configuration.Salvador, (Supplier)(object)val3); if (configuration.Salvador != null) { ApplySupplierConfigValues(configuration.Salvador, (Supplier)(object)val3); } } TryApplyFungalPhoneSupplier(configuration); TryApplyFungalPhoneShopInterfaceModals(configuration); } private static void AssertSupplierConfigEntryExists(ref SupplierListingOverride configuration, Supplier supplier) { if (supplier.OnlineShopItems != null && configuration == null) { configuration = new SupplierListingOverride { PriceOverrides = ((IEnumerable)supplier.OnlineShopItems).Where((Listing listing) => (Object)(object)((listing != null) ? listing.Item : null) != (Object)null).ToDictionary((Listing listing) => ((BaseItemDefinition)listing.Item).ID, (Listing listing) => listing.Price) }; } } private static void ApplySupplierConfigValues(SupplierListingOverride configuration, Supplier supplier) { if (configuration?.PriceOverrides == null || ((supplier != null) ? supplier.OnlineShopItems : null) == null) { return; } foreach (Listing item in (Il2CppArrayBase)(object)supplier.OnlineShopItems) { if (!((Object)(object)((item != null) ? item.Item : null) == (Object)null) && configuration.PriceOverrides.TryGetValue(((BaseItemDefinition)item.Item).ID, out var value)) { item.Item.BasePurchasePrice = value; ModShops modShops = Core.Get(); if (modShops != null && modShops.Configuration.PhoneMirrorListingPriceFields) { SupplierPhoneListingPriceHelper.ApplyListingPriceFieldsOnly(item, value); } } } } private static void TryApplyFungalPhoneSupplier(ModShopsConfiguration configuration) { Type typeFromHandle = typeof(Albert); Type typeFromHandle2 = typeof(Shirley); Type typeFromHandle3 = typeof(Salvador); foreach (Supplier item in Object.FindObjectsOfType(true)) { if ((Object)(object)item == (Object)null) { continue; } Type type = ((object)item).GetType(); if (!(type == typeFromHandle) && !(type == typeFromHandle2) && !(type == typeFromHandle3) && (SupplierNameLooksLikeFungal(item) || SupplierCatalogMatchesFungalJson(configuration.Fungal, item))) { AssertSupplierConfigEntryExists(ref configuration.Fungal, item); if (configuration.Fungal != null) { ApplySupplierConfigValues(configuration.Fungal, item); } } } } private static bool SupplierNameLooksLikeFungal(Supplier supplier) { string obj = ((object)supplier).GetType().Name ?? string.Empty; string text = (((Object)(object)((Component)supplier).gameObject != (Object)null) ? ((Object)((Component)supplier).gameObject).name : string.Empty); if (obj.IndexOf("Fungal", StringComparison.OrdinalIgnoreCase) < 0) { return text.IndexOf("Fungal", StringComparison.OrdinalIgnoreCase) >= 0; } return true; } private static bool SupplierCatalogMatchesFungalJson(SupplierListingOverride fungal, Supplier supplier) { if (fungal?.PriceOverrides == null || fungal.PriceOverrides.Count == 0) { return false; } if (((supplier != null) ? supplier.OnlineShopItems : null) == null) { return false; } List list = (from l in (IEnumerable)supplier.OnlineShopItems where (Object)(object)((l != null) ? l.Item : null) != (Object)null select ((BaseItemDefinition)l.Item).ID).ToList(); if (list.Count == 0) { return false; } int num = list.Count((string id) => fungal.PriceOverrides.ContainsKey(id)); if (num >= 2) { return true; } if (num >= 1) { return num == list.Count; } return false; } private static void TryApplyFungalPhoneShopInterfaceModals(ModShopsConfiguration configuration) { SupplierListingOverride fungal = configuration.Fungal; if (fungal?.PriceOverrides == null || fungal.PriceOverrides.Count == 0) { return; } foreach (PhoneShopInterface item in Object.FindObjectsOfType(true)) { if ((Object)(object)item == (Object)null || !PhoneShopInterfaceLooksLikeFungalModal(fungal, item)) { continue; } foreach (Listing item2 in PhoneShopInterfaceListingsHelper.EnumerateListings(item)) { if (!((Object)(object)((item2 != null) ? item2.Item : null) == (Object)null) && fungal.PriceOverrides.TryGetValue(((BaseItemDefinition)item2.Item).ID, out var value)) { item2.Item.BasePurchasePrice = value; if (configuration.PhoneMirrorListingPriceFields) { SupplierPhoneListingPriceHelper.ApplyListingPriceFieldsOnly(item2, value); } } } PhoneShopInterfaceListingsHelper.TryInvokeListingOrPriceRefresh(item); } } private static bool PhoneShopInterfaceLooksLikeFungalModal(SupplierListingOverride fungal, PhoneShopInterface psi) { List list = new List(); foreach (Listing item in PhoneShopInterfaceListingsHelper.EnumerateListings(psi)) { if ((Object)(object)((item != null) ? item.Item : null) != (Object)null) { list.Add(((BaseItemDefinition)item.Item).ID); } } if (list.Count == 0) { return false; } int num = list.Count((string id) => fungal.PriceOverrides.ContainsKey(id)); if (num >= 2) { return true; } if (num >= 1) { return num == list.Count; } return false; } private static void ApplyShopOverrides() { List list = ((IEnumerable)Object.FindObjectsOfType()).ToList(); MelonLogger.Msg($"[Lithium_fork][Shops] ApplyShopOverrides: {list.Count} ShopInterface(s): " + string.Join(", ", from s in list where (Object)(object)s != (Object)null select s.ShopCode ?? "?")); foreach (ShopInterface item in list) { ProcessShopInterface(item); } } public static void ProcessShopInterface(ShopInterface shopInterface) { if ((Object)(object)shopInterface == (Object)null) { return; } ModShopsConfiguration configuration = Core.Get().Configuration; List list = shopInterface.Listings?.ToList(); if (list == null) { return; } string shopCode = shopInterface.ShopCode; if (shopCode == null) { return; } switch (shopCode.Length) { case 9: switch (shopCode[0]) { case 'c': if (shopCode == "coke_shop") { AssertConfigurationEntries(ref configuration.CokeSupplier, shopInterface, list); if (configuration.Enabled) { ApplyShopSettings(list, shopInterface, configuration.CokeSupplier); } } break; case 'm': if (shopCode == "meth_shop") { AssertConfigurationEntries(ref configuration.MethSupplier, shopInterface, list); if (configuration.Enabled) { ApplyShopSettings(list, shopInterface, configuration.MethSupplier); } } break; case 'w': if (shopCode == "weed_shop") { AssertConfigurationEntries(ref configuration.WeedSupplier, shopInterface, list); if (configuration.Enabled) { ApplyShopSettings(list, shopInterface, configuration.WeedSupplier); } } break; } break; case 16: switch (shopCode[0]) { case 'd': if (shopCode == "dark_market_shop") { ApplyDarkMarketFromConfig(configuration, shopInterface, list); } break; case 'g': if (shopCode == "gas_mart_central") { AssertConfigurationEntries(ref configuration.CentralGasStation, shopInterface, list); if (configuration.Enabled) { ApplyShopSettings(list, shopInterface, configuration.CentralGasStation); } } break; } break; case 13: switch (shopCode[0]) { case 'g': if (shopCode == "gas_mart_west") { AssertConfigurationEntries(ref configuration.GasStation, shopInterface, list); if (configuration.Enabled) { ApplyShopSettings(list, shopInterface, configuration.GasStation); } } break; case 'd': if (shopCode == "dans_hardware") { AssertConfigurationEntries(ref configuration.DansHardware, shopInterface, list); if (configuration.Enabled) { ApplyShopSettings(list, shopInterface, configuration.DansHardware); } } break; } break; case 15: if (shopCode == "thrifty_threads") { AssertConfigurationEntries(ref configuration.ThriftyThreads, shopInterface, list); if (configuration.Enabled) { ApplyShopSettings(list, shopInterface, configuration.ThriftyThreads); } } break; case 12: if (shopCode == "shrooms_shop") { AssertConfigurationEntries(ref configuration.ShroomsSupplier, shopInterface, list); if (configuration.Enabled) { ApplyShopSettings(list, shopInterface, configuration.ShroomsSupplier); } } break; case 8: if (shopCode == "boutique") { AssertConfigurationEntries(ref configuration.Boutique, shopInterface, list); if (configuration.Enabled) { ApplyShopSettings(list, shopInterface, configuration.Boutique); } } break; case 4: if (shopCode == "shop" && IsDarkMarketShopInterface(shopInterface)) { ApplyDarkMarketFromConfig(configuration, shopInterface, list); } break; case 11: if (shopCode == "handy_hanks") { AssertConfigurationEntries(ref configuration.HandyHanks, shopInterface, list); if (configuration.Enabled) { ApplyShopSettings(list, shopInterface, configuration.HandyHanks); } } break; case 10: if (shopCode == "armsdealer") { AssertConfigurationEntries(ref configuration.ArmsDealer, shopInterface, list); if (configuration.Enabled) { ApplyShopSettings(list, shopInterface, configuration.ArmsDealer); } } break; case 5: case 6: case 7: case 14: break; } } private static bool IsDarkMarketShopInterface(ShopInterface shopInterface) { if ((Object)(object)((Component)shopInterface).gameObject != (Object)null) { return ((Object)((Component)shopInterface).gameObject).name.IndexOf("DarkMarket", StringComparison.OrdinalIgnoreCase) >= 0; } return false; } private static void ApplyDarkMarketFromConfig(ModShopsConfiguration configuration, ShopInterface shopInterface, List listings) { AssertConfigurationEntries(ref configuration.DarkMarket, shopInterface, listings); if (configuration.Enabled) { ApplyShopSettings(listings, shopInterface, configuration.DarkMarket); } } private static void AssertConfigurationEntries(ref ShopListingSettings configSetting, ShopInterface shopInterface, List listings) { //IL_001a: Unknown result type (might be due to invalid IL or missing references) if (configSetting == null) { configSetting = new ShopListingSettings { Override = false, DefaultStock = -1, PaymentType = shopInterface.PaymentType, ItemOverrides = listings.ToDictionary((ShopListing listing) => ((BaseItemDefinition)listing.Item).ID, (ShopListing listing) => new ItemListingOverride { Price = listing.Price, Stock = (listing.LimitedStock ? listing.DefaultStock : (-1)), RestockRate = listing.RestockRate }) }; } shopInterface.RefreshShownItems(); shopInterface.RefreshUnlockStatus(); } private static string ListingStockKey(ShopInterface shopInterface, ShopListing listing) { string obj = (((Object)(object)shopInterface != (Object)null && shopInterface.ShopCode != null) ? shopInterface.ShopCode : "?"); object obj2; if (listing != null) { StorableItemDefinition item = listing.Item; if (((item != null) ? ((BaseItemDefinition)item).ID : null) != null) { obj2 = ((BaseItemDefinition)listing.Item).ID; goto IL_0047; } } obj2 = "?"; goto IL_0047; IL_0047: string text = (string)obj2; return obj + "|" + text; } public static void ClearListingStockCache() { LastAppliedStockByListing.Clear(); } private static void ApplyShopSettings(List listings, ShopInterface shopInterface, ShopListingSettings shopSettings) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0112: Unknown result type (might be due to invalid IL or missing references) if (!shopSettings.Override) { return; } shopInterface.PaymentType = shopSettings.PaymentType; foreach (ShopListing listing in listings) { if (!shopSettings.ItemOverrides.TryGetValue(((BaseItemDefinition)listing.Item).ID, out var value)) { continue; } int currentStock = listing.CurrentStock; int defaultStock = listing.DefaultStock; bool limitedStock = listing.LimitedStock; listing.LimitedStock = value.Stock >= 0; listing.DefaultStock = ((value.Stock >= 0) ? value.Stock : shopSettings.DefaultStock); if (value.Stock >= 0) { int stock = value.Stock; string key = ListingStockKey(shopInterface, listing); if (currentStock > 0) { listing.CurrentStock = Mathf.Min(currentStock, stock); } else if (defaultStock == 0 || !limitedStock) { listing.CurrentStock = stock; } else if (!LastAppliedStockByListing.ContainsKey(key)) { listing.CurrentStock = stock; } else { listing.CurrentStock = 0; } LastAppliedStockByListing[key] = listing.CurrentStock; } listing.OverridePrice = true; listing.OverriddenPrice = value.Price; listing.RestockRate = value.RestockRate; } } } [HarmonyPatch] internal static class SupplierPhonePricesReapplyPatches { private static bool _loggedOnce; private static int _hookCount; private static bool IsRealMethodForPatch(MethodInfo m) { if (m.IsSpecialName) { return false; } string name = m.Name; if (name.StartsWith("set_", StringComparison.Ordinal) || name.StartsWith("get_", StringComparison.Ordinal)) { return false; } if (name.StartsWith("add_", StringComparison.Ordinal) || name.StartsWith("remove_", StringComparison.Ordinal)) { return false; } return true; } private static bool IsFishNetOrNetworkGeneratedMethod(MethodInfo m) { string name = m.Name; if (name.IndexOf("RpcWriter", StringComparison.OrdinalIgnoreCase) >= 0) { return true; } if (name.IndexOf("RpcReader", StringComparison.OrdinalIgnoreCase) >= 0) { return true; } if (name.IndexOf("UserCode_", StringComparison.OrdinalIgnoreCase) >= 0) { return true; } if (name.IndexOf("InvokeUserCode_", StringComparison.OrdinalIgnoreCase) >= 0) { return true; } return false; } [HarmonyPrepare] private static bool Prepare() { try { _hookCount = TargetMethods().Count(); return _hookCount > 0; } catch { return false; } } private static IEnumerable SupplierDeaddropHooks() { MethodInfo[] methods = typeof(Supplier).GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (MethodInfo methodInfo in methods) { if (!methodInfo.IsStatic && !(methodInfo.ReturnType != typeof(void)) && methodInfo.GetParameters().Length <= 8 && IsRealMethodForPatch(methodInfo) && !IsFishNetOrNetworkGeneratedMethod(methodInfo) && methodInfo.Name.IndexOf("deaddrop", StringComparison.OrdinalIgnoreCase) >= 0) { yield return methodInfo; } } } private static IEnumerable SupplierPhoneHooks() { MethodInfo[] methods = typeof(Supplier).GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (MethodInfo i in methods) { if (!i.IsStatic && !(i.ReturnType != typeof(void)) && i.GetParameters().Length <= 8 && IsRealMethodForPatch(i) && !IsFishNetOrNetworkGeneratedMethod(i)) { string j = i.Name; if (j.IndexOf("phone", StringComparison.OrdinalIgnoreCase) >= 0) { yield return i; } if (j.IndexOf("onlineshop", StringComparison.OrdinalIgnoreCase) >= 0 && (j.IndexOf("refresh", StringComparison.OrdinalIgnoreCase) >= 0 || j.IndexOf("open", StringComparison.OrdinalIgnoreCase) >= 0 || j.IndexOf("show", StringComparison.OrdinalIgnoreCase) >= 0)) { yield return i; } } } } private static IEnumerable PhoneShopOpenHooks() { MethodInfo[] methods = typeof(PhoneShopInterface).GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (MethodInfo methodInfo in methods) { if (!methodInfo.IsStatic && !(methodInfo.ReturnType != typeof(void)) && methodInfo.Name.Equals("Open", StringComparison.OrdinalIgnoreCase)) { yield return methodInfo; } } } private static IEnumerable MessageUiPhoneHooks() { Assembly assembly = typeof(Supplier).Assembly; Type[] array; try { array = assembly.GetTypes(); } catch (ReflectionTypeLoadException ex) { array = ex.Types?.Where((Type t) => t != null).Cast().ToArray() ?? Array.Empty(); } Type[] array2 = array; foreach (Type type in array2) { if (type == null || type.Namespace == null || !type.Namespace.Contains("UI.Phone", StringComparison.Ordinal) || (type.Name.IndexOf("Message", StringComparison.OrdinalIgnoreCase) < 0 && type.Name.IndexOf("Conversation", StringComparison.OrdinalIgnoreCase) < 0 && type.Name.IndexOf("DeadDrop", StringComparison.OrdinalIgnoreCase) < 0 && type.Name.IndexOf("Deaddrop", StringComparison.OrdinalIgnoreCase) < 0)) { continue; } MethodInfo[] methods = type.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (MethodInfo methodInfo in methods) { if (!methodInfo.IsStatic && !(methodInfo.ReturnType != typeof(void)) && methodInfo.GetParameters().Length <= 8 && IsRealMethodForPatch(methodInfo) && !IsFishNetOrNetworkGeneratedMethod(methodInfo)) { string name = methodInfo.Name; if (name.Equals("Open", StringComparison.OrdinalIgnoreCase) || name.Equals("SetOpen", StringComparison.OrdinalIgnoreCase) || name.Equals("Show", StringComparison.OrdinalIgnoreCase) || (name.StartsWith("Open", StringComparison.OrdinalIgnoreCase) && methodInfo.GetParameters().Length <= 4)) { yield return methodInfo; } } } } } internal static IEnumerable TargetMethods() { HashSet seen = new HashSet(StringComparer.Ordinal); foreach (MethodBase item2 in EnumerateRaw()) { if (!(item2 == null)) { string item = item2.DeclaringType?.FullName + "::" + item2; if (seen.Add(item)) { yield return item2; } } } } private static IEnumerable EnumerateRaw() { foreach (MethodBase item in SupplierDeaddropHooks()) { yield return item; } foreach (MethodBase item2 in SupplierPhoneHooks()) { yield return item2; } foreach (MethodBase item3 in PhoneShopOpenHooks()) { yield return item3; } foreach (MethodBase item4 in MessageUiPhoneHooks()) { yield return item4; } } [HarmonyPostfix] private static void Postfix() { ModShops modShops = Core.Get(); if (modShops != null && modShops.Configuration.Enabled) { SupplierStartPatch.ApplySupplierOverridesFromUiHooks(); if (!_loggedOnce) { _loggedOnce = true; MelonLogger.Msg($"[Lithium_fork][Shops] Phone supplier price reapply: {_hookCount} Harmony hook(s) (Supplier + PhoneShop + message UI)."); } } } } } namespace Lithium.Modules.LabOven { public class ModLabOvenConfiguration : ModuleConfiguration { public float Speed = 1f; public override string Name => "LabOven"; } public class ModLabOven : ModuleBase { public override void Apply() { _ = base.Configuration.Enabled; } } } namespace Lithium.Modules.LabOven.Patches { [HarmonyPatch(typeof(OvenCookOperation), "IsReady")] public class OvenCookOperationIsReadyPatch { [HarmonyPostfix] public static void Postfix(OvenCookOperation __instance, ref bool __result) { if (Core.Get().Configuration.Enabled) { __result = __instance.CookProgress >= __instance.GetCookDuration(); } } } [HarmonyPatch(typeof(OvenCookOperation), "GetCookDuration")] public class OvenCookOperationGetCookDurationPatch { [HarmonyPostfix] public static void Postfix(ref int __result) { if (Core.Get().Configuration.Enabled) { float num = Mathf.Max(0.01f, Core.Get().Configuration.Speed); __result = Mathf.Max(1, Mathf.FloorToInt((float)__result / num)); } } } } namespace Lithium.Modules.DryingRacks { public class ModDryingRacksConfiguration : ModuleConfiguration { public Dictionary PerQualityDryTimes = new Dictionary { { "Trash", 720 }, { "Poor", 720 }, { "Standard", 720 }, { "Premium", 720 }, { "Heavenly", 720 } }; public override string Name => "DryingRacks"; public int Capacity { get; set; } = 20; } public class ModDryingRacks : ModuleBase { public override void Apply() { _ = base.Configuration.Enabled; } } } namespace Lithium.Modules.DryingRacks.Patches { [HarmonyPatch(typeof(DryingRack), "OnMinPass")] public class DryingRackPatch { [HarmonyPrefix] private static bool Prefix(DryingRack __instance) { //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_0082: Unknown result type (might be due to invalid IL or missing references) //IL_0088: Invalid comparison between Unknown and I4 if (!Core.Get().Configuration.Enabled) { return true; } foreach (DryingOperation item in __instance.DryingOperations.ToArray()) { float time = item.Time; item.Time = time + 1f; Dictionary perQualityDryTimes = Core.Get().Configuration.PerQualityDryTimes; EQuality quality = item.GetQuality(); string key = ((object)(EQuality)(ref quality)).ToString(); int valueOrDefault = perQualityDryTimes.GetValueOrDefault(key, 720); if (!(item.Time >= (float)valueOrDefault)) { continue; } if ((int)item.StartQuality >= 3) { if (InstanceFinder.IsServer && __instance.GetOutputCapacityForOperation(item, (EQuality)4) >= item.Quantity) { __instance.TryEndOperation(__instance.DryingOperations.IndexOf(item), false, (EQuality)4, Random.Range(int.MinValue, int.MaxValue)); } } else { item.IncreaseQuality(); } } return false; } } [HarmonyPatch(typeof(DryingOperationUI), "UpdatePosition")] public class DryingOperationUIPatch { [HarmonyPrefix] private static bool Prefix(DryingOperationUI __instance) { //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_00e6: Unknown result type (might be due to invalid IL or missing references) if (!Core.Get().Configuration.Enabled) { return true; } Dictionary perQualityDryTimes = Core.Get().Configuration.PerQualityDryTimes; EQuality startQuality = __instance.AssignedOperation.StartQuality; string key = ((object)(EQuality)(ref startQuality)).ToString(); int valueOrDefault = perQualityDryTimes.GetValueOrDefault(key, 720); DryingOperation assignedOperation = __instance.AssignedOperation; float num = Mathf.Clamp01(assignedOperation.Time / (float)valueOrDefault); int num2 = Mathf.Clamp(Mathf.RoundToInt((float)valueOrDefault - assignedOperation.Time), 0, valueOrDefault); int value = num2 / 60; int value2 = num2 % 60; __instance.Tooltip.text = $"{value}h {value2}m until next tier"; float num3 = -62.5f; float num4 = 0f - num3; __instance.Rect.anchoredPosition = new Vector2(Mathf.Lerp(num3, num4, num), 0f); return false; } } [HarmonyPatch(typeof(DryingOperation), "GetQuality")] public class DryingOperationPatch { [HarmonyPrefix] private static bool Prefix(DryingOperation __instance, ref EQuality __result) { //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Expected I4, but got Unknown //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Expected I4, but got Unknown if (!Core.Get().Configuration.Enabled) { return true; } Dictionary perQualityDryTimes = Core.Get().Configuration.PerQualityDryTimes; EQuality startQuality = __instance.StartQuality; string key = ((object)(EQuality)(ref startQuality)).ToString(); if (__instance.Time >= (float)perQualityDryTimes.GetValueOrDefault(key, 720)) { __result = (EQuality)(__instance.StartQuality + 1); } else { __result = (EQuality)(int)__instance.StartQuality; } return false; } } [HarmonyPatch(typeof(DryingRackCanvas), "SetIsOpen")] public class DryingRackCapacityPatch { [HarmonyPostfix] public static void DryingRackCapacity(DryingRackCanvas __instance, DryingRack rack, bool open) { ModDryingRacksConfiguration configuration = Core.Get().Configuration; if (configuration.Enabled && rack != null) { rack.ItemCapacity = configuration.Capacity; } } } } namespace Lithium.Modules.WateringCans { public class ModWateringCanConfiguration : ModuleConfiguration { public float DrainModifier = 1f; public override string Name => "WateringCan"; } public class ModWateringCan : ModuleBase { public override void Apply() { _ = base.Configuration.Enabled; } } } namespace Lithium.Modules.WateringCans.Patches { [HarmonyPatch(typeof(WaterContainerPourable), "PourAmount")] public static class WaterContainerPourablePourAmountPatch { [HarmonyPrefix] public static void Prefix(ref float amount) { ModWateringCanConfiguration configuration = Core.Get().Configuration; if (configuration.Enabled) { amount *= configuration.DrainModifier; } } } } namespace Lithium.Modules.PropertyPrices { public class ModPropertyPricesConfiguration : ModuleConfiguration { public override string Name => "Property Prices"; public Dictionary PropertyPrices { get; set; } = new Dictionary { { "RV", 0 }, { "Motel Room", 75 }, { "Sweatshop", 800 }, { "Storage Unit", 5000 }, { "Bungalow", 6000 }, { "Barn", 25000 }, { "Docks Warehouse", 50000 }, { "Laundromat", 4000 }, { "Post Office", 10000 }, { "Car Wash", 20000 }, { "Taco Ticklers", 50000 }, { "Manor", 100000 } }; } public class ModPropertyPrices : ModuleBase { public override void Apply() { _ = base.Configuration.Enabled; } } } namespace Lithium.Modules.PropertyPrices.Patches { [HarmonyPatch(typeof(Player), "NetworkInitialize___Early")] public class ForcePropertyPrices { [HarmonyPrefix] public static void PatchPrices() { ModPropertyPricesConfiguration configuration = Core.Get().Configuration; if (configuration.Enabled) { ChangePropertyPrices(configuration); UpdateMissMingDialog(configuration); configuration.SaveConfiguration(); } } private static void ChangePropertyPrices(ModPropertyPricesConfiguration configuration) { Property[] array = Il2CppArrayBase.op_Implicit(Object.FindObjectsOfType()); foreach (Property val in array) { if (!configuration.PropertyPrices.TryGetValue(val.PropertyName, out var value)) { MelonLogger.Warning("Property " + val.PropertyName + " not found in configuration. Skipping."); continue; } val.Price = value; if ((Object)(object)val.ForSaleSign != (Object)null) { ((TMP_Text)((Component)val.ForSaleSign.transform.Find("Price")).GetComponent()).text = MoneyManager.FormatAmount((float)value, false, false); } if ((Object)(object)val.ListingPoster != (Object)null) { ((TMP_Text)((Component)val.ListingPoster.Find("Price")).GetComponent()).text = MoneyManager.FormatAmount((float)value, false, false); } } } private static void UpdateMissMingDialog(ModPropertyPricesConfiguration configuration) { DialogueController_Ming[] array = Il2CppArrayBase.op_Implicit(Object.FindObjectsOfType()); foreach (DialogueController_Ming val in array) { DialogueController_Ming val2 = val; Transform parent = ((Component)val).gameObject.transform.parent; string text = ((parent != null) ? ((Object)parent).name : null); float price = ((text == "Ming") ? ((float)configuration.PropertyPrices["Sweatshop"]) : ((!(text == "Donna")) ? val.Price : ((float)configuration.PropertyPrices["Motel Room"]))); val2.Price = price; } } } } namespace Lithium.Modules.Employees { public class BotanistsConfiguration { public int MaxAssignedPots = 8; public float WalkSpeed = 1.2f; public int DailyWage = 200; public float SoilPourTime = 10f; public float WaterPourTime = 10f; public float AdditivePourTime = 10f; public float SeedSowTime = 15f; public float HarvestTime = 15f; } public class ChemistConfiguration { public int MaxStations = 6; public int DailyWage = 300; public float WalkSpeed = 1.2f; } public class PackagerConfiguration { public int MaxStations = 3; public int MaxRoutes = 10; public float PackagingSpeedMultiplier = 2f; public int DailyWage = 200; public float WalkSpeed = 1.2f; } public class CleanerConfiguration { public int MaxBins = 3; public int DailyWage = 100; public float WalkSpeed = 1.2f; } public class ModEmployeesConfiguration : ModuleConfiguration { public BotanistsConfiguration Botanists = new BotanistsConfiguration(); public ChemistConfiguration Chemists = new ChemistConfiguration(); public PackagerConfiguration Packagers = new PackagerConfiguration(); public CleanerConfiguration Cleaners = new CleanerConfiguration(); public override string Name => "Employees"; } public class ModEmployees : ModuleBase { public static readonly HashSet ConfiguredEmployees = new HashSet(); public override void Apply() { _ = base.Configuration.Enabled; } } } namespace Lithium.Modules.Employees.Patches { [HarmonyPatch(typeof(Cleaner), "NetworkInitialize___Early")] public class CleanerPatch { [HarmonyPostfix] private static void Postfix(Cleaner __instance) { ModEmployeesConfiguration configuration = Core.Get().Configuration; if (configuration.Enabled && !((Object)(object)__instance == (Object)null) && __instance.configuration != null && __instance.configuration.Bins != null && !((Object)(object)((NPC)__instance).Movement == (Object)null)) { __instance.configuration.Bins.MaxItems = configuration.Cleaners.MaxBins; ((NPC)__instance).Movement.WalkSpeed = configuration.Cleaners.WalkSpeed; ((Employee)__instance).DailyWage = configuration.Cleaners.DailyWage; ModEmployees.ConfiguredEmployees.Add((Employee)(object)__instance); } } } [HarmonyPatch(typeof(Cleaner), "NetworkInitialize__Late")] public class CleanerNetworkLatePatch { [HarmonyPostfix] private static void Postfix(Cleaner __instance) { ModEmployeesConfiguration configuration = Core.Get().Configuration; if (configuration.Enabled && !((Object)(object)__instance == (Object)null)) { CleanerConfiguration configuration2 = __instance.configuration; if (((configuration2 != null) ? configuration2.Bins : null) != null && (Object)(object)((NPC)__instance).Movement != (Object)null) { __instance.configuration.Bins.MaxItems = configuration.Cleaners.MaxBins; ((NPC)__instance).Movement.WalkSpeed = configuration.Cleaners.WalkSpeed; } ((Employee)__instance).DailyWage = configuration.Cleaners.DailyWage; EmployeeLimitsDelayedReapply.ScheduleCleaner(__instance); } } } [HarmonyPatch(typeof(Packager), "NetworkInitialize__Late")] public class PackagerPatch { [HarmonyPostfix] private static void Postfix(Packager __instance) { ModEmployeesConfiguration configuration = Core.Get().Configuration; if (configuration.Enabled && !((Object)(object)__instance == (Object)null)) { if ((Object)(object)((NPC)__instance).Movement != (Object)null) { ((NPC)__instance).Movement.WalkSpeed = configuration.Packagers.WalkSpeed; ((Employee)__instance).DailyWage = configuration.Packagers.DailyWage; } PackagerConfiguration configuration2 = __instance.configuration; if (((configuration2 != null) ? configuration2.Stations : null) != null) { __instance.configuration.Stations.MaxItems = configuration.Packagers.MaxStations; } PackagerConfiguration configuration3 = __instance.configuration; if (((configuration3 != null) ? configuration3.Routes : null) != null) { __instance.configuration.Routes.MaxRoutes = configuration.Packagers.MaxRoutes; } __instance.PackagingSpeedMultiplier = configuration.Packagers.PackagingSpeedMultiplier; EmployeeLimitsDelayedReapply.SchedulePackager(__instance); } } } [HarmonyPatch(typeof(Chemist), "NetworkInitialize__Late")] public class ChemistValuesPatch { [HarmonyPostfix] private static void Postfix(Chemist __instance) { ModEmployeesConfiguration configuration = Core.Get().Configuration; if (configuration.Enabled && !((Object)(object)__instance == (Object)null)) { if ((Object)(object)((NPC)__instance).Movement != (Object)null) { ((NPC)__instance).Movement.WalkSpeed = configuration.Chemists.WalkSpeed; ((Employee)__instance).DailyWage = configuration.Chemists.DailyWage; } ChemistConfiguration configuration2 = __instance.configuration; if (((configuration2 != null) ? configuration2.Stations : null) != null) { __instance.configuration.Stations.MaxItems = configuration.Chemists.MaxStations; } EmployeeLimitsDelayedReapply.ScheduleChemist(__instance); } } } internal static class BotanistTimingHelper { private static readonly string[] DurationFieldCandidates = new string[12] { "actionDuration", "ActionDuration", "timeRemaining", "TimeRemaining", "activeDuration", "ActiveDuration", "_timeRemaining", "_actionDuration", "Action_Time", "actionTime", "Duration", "duration" }; internal static void TryApplyDuration(object behaviour, float seconds) { if (behaviour == null || seconds <= 0f || !Core.Get().Configuration.Enabled) { return; } Traverse val = Traverse.Create(behaviour); string[] durationFieldCandidates = DurationFieldCandidates; foreach (string text in durationFieldCandidates) { try { Traverse val2 = val.Field(text); if (!val2.FieldExists()) { continue; } val2.SetValue((object)seconds); break; } catch { } } } internal static float ResolveDurationForBehaviourTypeName(string typeName, BotanistsConfiguration cfg) { if (string.IsNullOrEmpty(typeName)) { return -1f; } if (typeName.IndexOf("Harvest", StringComparison.OrdinalIgnoreCase) >= 0) { return cfg.HarvestTime; } if (typeName.IndexOf("Soil", StringComparison.OrdinalIgnoreCase) >= 0) { return cfg.SoilPourTime; } if (typeName.IndexOf("Water", StringComparison.OrdinalIgnoreCase) >= 0) { return cfg.WaterPourTime; } if (typeName.IndexOf("Seed", StringComparison.OrdinalIgnoreCase) >= 0 || typeName.IndexOf("Sow", StringComparison.OrdinalIgnoreCase) >= 0) { return cfg.SeedSowTime; } if (typeName.IndexOf("Additive", StringComparison.OrdinalIgnoreCase) >= 0 || typeName.IndexOf("Fertil", StringComparison.OrdinalIgnoreCase) >= 0) { return cfg.AdditivePourTime; } return -1f; } } public static class BotanistDynamicTimingPatches { private const string BehaviourNamespace = "Il2CppScheduleOne.NPCs.Behaviour"; private static bool _registered; public static void Register(Harmony harmony) { //IL_00ea: Unknown result type (might be due to invalid IL or missing references) //IL_00f1: Expected O, but got Unknown if (harmony == null || _registered) { return; } Assembly assembly; try { assembly = typeof(HarvestPotBehaviour).Assembly; } catch { return; } Type[] array; try { array = assembly.GetTypes(); } catch (ReflectionTypeLoadException ex) { array = ex.Types?.Where((Type x) => x != null).Cast().ToArray() ?? Array.Empty(); } Type[] array2 = array; foreach (Type type in array2) { if (type == null || type.Namespace != "Il2CppScheduleOne.NPCs.Behaviour" || !type.Name.EndsWith("PotBehaviour", StringComparison.Ordinal)) { continue; } MethodInfo methodInfo = AccessTools.Method(type, "StartAction", (Type[])null, (Type[])null); if (methodInfo == null) { continue; } try { HarmonyMethod val = new HarmonyMethod(typeof(BotanistDynamicTimingPatches), "DynamicPotBehaviourStartActionPostfix", (Type[])null); harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, val, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Instance logger = Core.Logger; if (logger != null) { logger.Msg("[Lithium_fork][Employees] Botanist timings: patched " + type.Name + ".StartAction"); } } catch (Exception ex2) { Instance logger2 = Core.Logger; if (logger2 != null) { logger2.Warning("[Lithium_fork][Employees] Botanist timings: skip " + type.Name + ".StartAction — " + ex2.Message); } } } _registered = true; } public static void DynamicPotBehaviourStartActionPostfix(object __instance) { if (__instance == null) { return; } ModEmployeesConfiguration configuration = Core.Get().Configuration; if (configuration.Enabled) { float num = BotanistTimingHelper.ResolveDurationForBehaviourTypeName(__instance.GetType().Name, configuration.Botanists); if (!(num <= 0f)) { BotanistTimingHelper.TryApplyDuration(__instance, num); } } } } internal static class EmployeeLimitsDelayedReapply { private const int FrameRetries = 45; internal static void ScheduleChemist(Chemist instance) { if ((Object)(object)instance != (Object)null) { MelonCoroutines.Start(ReapplyChemistCoroutine(instance)); } } internal static void SchedulePackager(Packager instance) { if ((Object)(object)instance != (Object)null) { MelonCoroutines.Start(ReapplyPackagerCoroutine(instance)); } } internal static void ScheduleCleaner(Cleaner instance) { if ((Object)(object)instance != (Object)null) { MelonCoroutines.Start(ReapplyCleanerCoroutine(instance)); } } private static IEnumerator ReapplyChemistCoroutine(Chemist chemist) { for (int i = 0; i < 45; i++) { yield return null; if ((Object)(object)chemist == (Object)null) { break; } ModEmployeesConfiguration configuration = Core.Get().Configuration; if (!configuration.Enabled) { break; } ChemistConfiguration configuration2 = chemist.configuration; if (((configuration2 != null) ? configuration2.Stations : null) != null) { chemist.configuration.Stations.MaxItems = configuration.Chemists.MaxStations; } } } private static IEnumerator ReapplyPackagerCoroutine(Packager packager) { for (int i = 0; i < 45; i++) { yield return null; if ((Object)(object)packager == (Object)null) { break; } ModEmployeesConfiguration configuration = Core.Get().Configuration; if (!configuration.Enabled) { break; } PackagerConfiguration configuration2 = packager.configuration; if (((configuration2 != null) ? configuration2.Stations : null) != null) { packager.configuration.Stations.MaxItems = configuration.Packagers.MaxStations; } PackagerConfiguration configuration3 = packager.configuration; if (((configuration3 != null) ? configuration3.Routes : null) != null) { packager.configuration.Routes.MaxRoutes = configuration.Packagers.MaxRoutes; } packager.PackagingSpeedMultiplier = configuration.Packagers.PackagingSpeedMultiplier; } } private static IEnumerator ReapplyCleanerCoroutine(Cleaner cleaner) { for (int i = 0; i < 45; i++) { yield return null; if ((Object)(object)cleaner == (Object)null) { break; } ModEmployeesConfiguration configuration = Core.Get().Configuration; if (!configuration.Enabled) { break; } ((Employee)cleaner).DailyWage = configuration.Cleaners.DailyWage; if ((Object)(object)((NPC)cleaner).Movement != (Object)null) { ((NPC)cleaner).Movement.WalkSpeed = configuration.Cleaners.WalkSpeed; } CleanerConfiguration configuration2 = cleaner.configuration; if (((configuration2 != null) ? configuration2.Bins : null) != null) { cleaner.configuration.Bins.MaxItems = configuration.Cleaners.MaxBins; } } } } [HarmonyPatch(typeof(Botanist), "Awake")] public class BotanistPatch { [HarmonyPostfix] private static void Postfix(Botanist __instance) { ModEmployeesConfiguration configuration = Core.Get().Configuration; if (configuration.Enabled && !((Object)(object)__instance == (Object)null) && !((Object)(object)((NPC)__instance).Movement == (Object)null)) { __instance.MaxAssignedPots = configuration.Botanists.MaxAssignedPots; ((NPC)__instance).Movement.WalkSpeed = configuration.Botanists.WalkSpeed; ((Employee)__instance).DailyWage = configuration.Botanists.DailyWage; } } } } namespace Lithium.Modules.EffectCombos { public class EffectCombo { public string Name { get; set; } public int FixedBonus { get; set; } public float PercentageBonus { get; set; } public string[] Effects { get; set; } = Array.Empty(); } public class ModEffectCombosConfiguration : ModuleConfiguration { public override string Name => "EffectCombos"; public bool AffectsDealers { get; set; } = true; public EffectCombo[] Combos { get; set; } = Array.Empty(); } public class ModEffectCombos : ModuleBase { public override void Apply() { if (base.Configuration.Enabled) { Core.Get().RegisterBonusPaymentHandler(new EffectComboBonus()); } } } } namespace Lithium.Modules.EffectCombos.BonusPayments { public class EffectComboBonus : IBonusPaymentHandler { public bool BonusPaymentHandler(Customer customer, Contract contract, List items, out List boni) { //IL_013c: Unknown result type (might be due to invalid IL or missing references) //IL_0146: Expected O, but got Unknown boni = new List(); ModEffectCombosConfiguration configuration = Core.Get().Configuration; if (!configuration.Enabled) { return false; } if (!configuration.AffectsDealers && (Object)(object)contract.Dealer != (Object)null) { return false; } int num = items.ToList().Sum((ItemInstance i) => (i != null) ? ((BaseItemInstance)i).Quantity : 0); foreach (ItemInstance item in items) { if (item == null) { continue; } string[] array = ProductHelper.GetNormalizedEffectNamesForMatchingItem(item).ToArray(); if (array.Length == 0) { continue; } EffectCombo[] combos = configuration.Combos; foreach (EffectCombo effectCombo in combos) { if (effectCombo.Effects.Select((string s) => s.ToLowerInvariant()).Intersect(array).Count() == effectCombo.Effects.Length) { float num2 = effectCombo.FixedBonus * ((BaseItemInstance)item).Quantity; float num3 = (float)((BaseItemInstance)item).Quantity / (float)num; float num4 = contract.Payment * (effectCombo.PercentageBonus / 100f) * num3; float num5 = num2 + num4; boni.Add(new BonusPayment("\"" + effectCombo.Name + "\" Combo Bonus", num5)); } } } return boni.Any(); } } } namespace Lithium.Modules.DynamicsOrders { public class XpPaymentBracket { public int MinXp { get; set; } public float PaymentMin { get; set; } public float PaymentMax { get; set; } public float OrderQuantityMin { get; set; } public float OrderQuantityMax { get; set; } } public class NpcDynamicsRule { [JsonProperty(Order = -1)] public bool Enabled { get; set; } public float MinQuantity { get; set; } = 1f; public float MaxQuantity { get; set; } = 6f; public int UltimateQuantityCap { get; set; } = 300; public float BudgetScalePercent { get; set; } public float QuantityGrowthExponent { get; set; } = 1.12f; public float XpExponentK { get; set; } = 0.35f; public bool AugmentPerContract { get; set; } } public class DynamicsOrdersGlobal { public List XpPaymentBrackets { get; set; } = new List(); [JsonProperty(/*Could not decode attribute arguments.*/)] [DefaultValue(true)] public bool EnableOrderQuantityVariance { get; set; } = true; [JsonProperty(/*Could not decode attribute arguments.*/)] [DefaultValue(50f)] public float FallbackUnitPrice { get; set; } = 50f; public NpcDynamicsRule DefaultNpcRule { get; set; } } public class DynamicsOrdersConfiguration : ModuleConfiguration { public override string Name => "DynamicsOrders"; [JsonProperty(Order = 2)] public DynamicsOrdersGlobal Global { get; set; } = new DynamicsOrdersGlobal(); [JsonProperty(Order = 3)] public Dictionary Rules { get; set; } = new Dictionary(); internal bool EnsureDefaults() { bool result = false; if (Global.XpPaymentBrackets == null || Global.XpPaymentBrackets.Count == 0) { Global.XpPaymentBrackets = DynamicsOrdersDefaults.Nexus799Brackets(); result = true; } if (Global.DefaultNpcRule == null) { Global.DefaultNpcRule = DynamicsOrdersDefaults.DefaultNpcRule(); result = true; } List xpPaymentBrackets = Global.XpPaymentBrackets; if (xpPaymentBrackets != null && xpPaymentBrackets.Count > 0) { bool flag = false; foreach (XpPaymentBracket xpPaymentBracket3 in Global.XpPaymentBrackets) { if (xpPaymentBracket3.OrderQuantityMax <= 0f) { flag = true; break; } } if (flag) { List list = DynamicsOrdersDefaults.Nexus799Brackets(); for (int i = 0; i < Global.XpPaymentBrackets.Count; i++) { XpPaymentBracket xpPaymentBracket = Global.XpPaymentBrackets[i]; if (!(xpPaymentBracket.OrderQuantityMax > 0f)) { if (i < list.Count) { xpPaymentBracket.OrderQuantityMin = list[i].OrderQuantityMin; xpPaymentBracket.OrderQuantityMax = list[i].OrderQuantityMax; } else { XpPaymentBracket xpPaymentBracket2 = list[list.Count - 1]; xpPaymentBracket.OrderQuantityMin = xpPaymentBracket2.OrderQuantityMin; xpPaymentBracket.OrderQuantityMax = xpPaymentBracket2.OrderQuantityMax; } result = true; } } } } if (Rules == null || Rules.Count == 0) { Rules = DynamicsOrdersDefaults.CustomerRulesCatalog(); result = true; } return result; } } internal static class DynamicsOrdersDefaults { private static readonly string[] IgnCustomerNpcIds = new string[66] { "Kyle_Cooley", "Austin_Steiner", "Jessi_Waters", "Kathy_Henderson", "Mick_Lubbin", "Sam_Thompson", "Peter_File", "Donna_Martin", "Geraldine_Poon", "Chloe_Bowers", "Peggy_Myers", "Ludwig_Meyer", "Beth_Penn", "Mrs._Ming", "Trent_Sherman", "Meg_Cooley", "Joyce_Ball", "Keith_Wagner", "Jerry_Montero", "Doris_Lubbin", "Kim_Delaney", "Dean_Webster", "Charles_Rowland", "George_Greene", "Elizabeth_Homley", "Jennifer_Rivera", "Kevin_Oakley", "Louis_Fourier", "Lucy_Pennington", "Randy_Caulfield", "Eugene_Buckley", "Greg_Figgle", "Jeff_Gilmore", "Phillip_Wentworth", "Bruce_Norton", "Anna_Chesterfield", "Lisa_Gardener", "Kelly_Reynolds", "Genghis_Barn", "Cranky_Frank", "Javier_Perez", "Marco_Barone", "Melissa_Wood", "Mac_Cooper", "Sherman_Giles", "Billy_Kramer", "Chris_Sullivan", "Hank_Stevenson", "Karen_Kennedy", "Alison_Knight", "Jeremy_Wilkinson", "Carl_Bundy", "Jackie_Stevenson", "Jack_Knight", "Harold_Colt", "Dennis_Kennedy", "Fiona_Hancock", "Lily_Turner", "Ray_Hoffman", "Jen_Heard", "Walter_Cussler", "Irene_Meadows", "Herbet_Bleuball", "Michael_Boog", "Tobias_Wentworth", "Pearl_Moore" }; internal static List Nexus799Brackets() { return new List(8) { new XpPaymentBracket { MinXp = 0, PaymentMin = 15f, PaymentMax = 80f, OrderQuantityMin = 1f, OrderQuantityMax = 8f }, new XpPaymentBracket { MinXp = 1000, PaymentMin = 25f, PaymentMax = 200f, OrderQuantityMin = 2f, OrderQuantityMax = 14f }, new XpPaymentBracket { MinXp = 5000, PaymentMin = 125f, PaymentMax = 1000f, OrderQuantityMin = 4f, OrderQuantityMax = 24f }, new XpPaymentBracket { MinXp = 10000, PaymentMin = 250f, PaymentMax = 2000f, OrderQuantityMin = 6f, OrderQuantityMax = 35f }, new XpPaymentBracket { MinXp = 25000, PaymentMin = 625f, PaymentMax = 5000f, OrderQuantityMin = 10f, OrderQuantityMax = 55f }, new XpPaymentBracket { MinXp = 50000, PaymentMin = 1250f, PaymentMax = 10000f, OrderQuantityMin = 15f, OrderQuantityMax = 80f }, new XpPaymentBracket { MinXp = 75000, PaymentMin = 1875f, PaymentMax = 15000f, OrderQuantityMin = 20f, OrderQuantityMax = 100f }, new XpPaymentBracket { MinXp = 100000, PaymentMin = 2500f, PaymentMax = 20000f, OrderQuantityMin = 25f, OrderQuantityMax = 130f } }; } internal static NpcDynamicsRule DefaultNpcRule() { return new NpcDynamicsRule { MinQuantity = 1f, MaxQuantity = 6f, UltimateQuantityCap = 300, BudgetScalePercent = 0f, QuantityGrowthExponent = 1.12f, XpExponentK = 0.35f, AugmentPerContract = false }; } internal static Dictionary CustomerRulesCatalog() { Dictionary dictionary = new Dictionary(IgnCustomerNpcIds.Length); string[] ignCustomerNpcIds = IgnCustomerNpcIds; foreach (string key in ignCustomerNpcIds) { dictionary[key] = new NpcDynamicsRule { Enabled = false }; } return dictionary; } } internal static class DynamicsOrderLogic { private const float DefaultNpcRefMaxQuantity = 6f; public static void Apply(ref ContractInfo contract, Customer customer, DynamicsOrdersConfiguration config) { if (config == null || !config.Enabled || contract == null || (Object)(object)customer == (Object)null || (Object)(object)NetworkSingleton.Instance == (Object)null) { return; } config.EnsureDefaults(); int totalXP = NetworkSingleton.Instance.TotalXP; string npcId = string.Empty; try { if ((Object)(object)customer.NPC != (Object)null && customer.NPC.ID != null) { npcId = customer.NPC.ID; } } catch { } NpcDynamicsRule npcDynamicsRule = ResolveRule(config, npcId); List xpPaymentBrackets = config.Global.XpPaymentBrackets; if (xpPaymentBrackets == null || xpPaymentBrackets.Count == 0) { return; } xpPaymentBrackets.Sort((XpPaymentBracket a, XpPaymentBracket b) => a.MinXp.CompareTo(b.MinXp)); GetSmoothedXpBand(totalXP, xpPaymentBrackets, out var qMin, out var qMax, out var payMin, out var payMax); float num = Mathf.Clamp01((float)totalXP / 100000f); qMin *= Mathf.Max(0.01f, npcDynamicsRule.MinQuantity); qMax *= Mathf.Max(0.01f, npcDynamicsRule.MaxQuantity) / 6f; float num2 = Mathf.Pow(Mathf.Max(npcDynamicsRule.QuantityGrowthExponent, 1.001f), num); float num3 = Mathf.Exp(npcDynamicsRule.XpExponentK * num); qMin *= num2 * num3; qMax *= num2 * num3; int num4 = Mathf.Max(1, Mathf.RoundToInt(qMin)); int num5 = Mathf.Max(num4, Mathf.RoundToInt(qMax)); num5 = Mathf.Min(num5, npcDynamicsRule.UltimateQuantityCap); num4 = Mathf.Min(num4, num5); int num6 = ((!config.Global.EnableOrderQuantityVariance) ? Mathf.Clamp(Mathf.RoundToInt(Mathf.Lerp((float)num4, (float)num5, 0.97f)), num4, num5) : Random.Range(num4, num5 + 1)); if (contract.Products != null && contract.Products.entries != null && contract.Products.entries.Count != 0) { num6 = Mathf.Clamp(num6, 1, npcDynamicsRule.UltimateQuantityCap); ReplaceFirstLineQuantity(contract, num6); string productID = contract.Products.entries[0].ProductID; float fallbackIfUnavailable = DeriveUnitPriceFromBand(payMin, payMax, qMin, qMax); float num7 = ProductHelper.TryGetMarketUnitPrice(productID, fallbackIfUnavailable); if (num7 <= 0f) { num7 = Mathf.Max(0.01f, config.Global.FallbackUnitPrice); } float num8 = 1f + npcDynamicsRule.BudgetScalePercent / 100f; float num9 = (float)num6 * num7 * num8; num9 = Mathf.Max(num9, 1f); TrySetContractPayment(contract, num9); } } private static float DeriveUnitPriceFromBand(float payMin, float payMax, float qMin, float qMax) { float num = (payMin + payMax) * 0.5f; float num2 = Mathf.Max(1f, (qMin + qMax) * 0.5f); return num / num2; } private static void GetSmoothedXpBand(int totalXp, List b, out float qMin, out float qMax, out float payMin, out float payMax) { qMin = (qMax = 1f); payMin = (payMax = 1f); if (b == null || b.Count == 0) { return; } if (totalXp <= b[0].MinXp) { qMin = b[0].OrderQuantityMin; qMax = b[0].OrderQuantityMax; payMin = b[0].PaymentMin; payMax = b[0].PaymentMax; return; } if (totalXp >= b[b.Count - 1].MinXp) { XpPaymentBracket xpPaymentBracket = b[b.Count - 1]; qMin = xpPaymentBracket.OrderQuantityMin; qMax = xpPaymentBracket.OrderQuantityMax; payMin = xpPaymentBracket.PaymentMin; payMax = xpPaymentBracket.PaymentMax; return; } for (int i = 0; i < b.Count - 1; i++) { if (totalXp >= b[i].MinXp && totalXp < b[i + 1].MinXp) { float num = b[i + 1].MinXp - b[i].MinXp; float num2 = ((num > 0f) ? ((float)(totalXp - b[i].MinXp) / num) : 0f); num2 = Mathf.SmoothStep(0f, 1f, Mathf.Clamp01(num2)); qMin = Mathf.Lerp(b[i].OrderQuantityMin, b[i + 1].OrderQuantityMin, num2); qMax = Mathf.Lerp(b[i].OrderQuantityMax, b[i + 1].OrderQuantityMax, num2); payMin = Mathf.Lerp(b[i].PaymentMin, b[i + 1].PaymentMin, num2); payMax = Mathf.Lerp(b[i].PaymentMax, b[i + 1].PaymentMax, num2); return; } } XpPaymentBracket xpPaymentBracket2 = b[b.Count - 1]; qMin = xpPaymentBracket2.OrderQuantityMin; qMax = xpPaymentBracket2.OrderQuantityMax; payMin = xpPaymentBracket2.PaymentMin; payMax = xpPaymentBracket2.PaymentMax; } private static void ReplaceFirstLineQuantity(ContractInfo contract, int quantity) { //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Expected O, but got Unknown //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Expected O, but got Unknown List entries = contract.Products.entries; ProductList val = new ProductList(); for (int i = 0; i < entries.Count; i++) { Entry val2 = entries[i]; val.entries.Add(new Entry { ProductID = val2.ProductID, Quality = val2.Quality, Quantity = ((i == 0) ? quantity : val2.Quantity) }); } contract.Products = val; } private static NpcDynamicsRule ResolveRule(DynamicsOrdersConfiguration config, string npcId) { NpcDynamicsRule npcDynamicsRule = config.Global.DefaultNpcRule ?? DynamicsOrdersDefaults.DefaultNpcRule(); if (string.IsNullOrEmpty(npcId) || config.Rules == null || !config.Rules.TryGetValue(npcId, out var value) || value == null) { return npcDynamicsRule; } if (!value.Enabled) { return npcDynamicsRule; } return Merge(npcDynamicsRule, value); } private static NpcDynamicsRule Merge(NpcDynamicsRule baseRule, NpcDynamicsRule ov) { return new NpcDynamicsRule { MinQuantity = ((ov.MinQuantity > 0f) ? ov.MinQuantity : baseRule.MinQuantity), MaxQuantity = ((ov.MaxQuantity > 0f) ? ov.MaxQuantity : baseRule.MaxQuantity), UltimateQuantityCap = ((ov.UltimateQuantityCap > 0) ? ov.UltimateQuantityCap : baseRule.UltimateQuantityCap), BudgetScalePercent = ov.BudgetScalePercent, QuantityGrowthExponent = ((ov.QuantityGrowthExponent > 0f) ? ov.QuantityGrowthExponent : baseRule.QuantityGrowthExponent), XpExponentK = ((ov.XpExponentK >= 0f) ? ov.XpExponentK : baseRule.XpExponentK), AugmentPerContract = ov.AugmentPerContract }; } private static void TrySetContractPayment(ContractInfo contract, float payment) { if (contract == null) { return; } try { Type typeFromHandle = typeof(ContractInfo); string[] array = new string[3] { "Payment", "TotalPayment", "Reward" }; foreach (string name in array) { PropertyInfo property = typeFromHandle.GetProperty(name, BindingFlags.Instance | BindingFlags.Public); if (property != null && property.CanWrite && property.PropertyType == typeof(float)) { property.SetValue(contract, payment); break; } FieldInfo field = typeFromHandle.GetField(name, BindingFlags.Instance | BindingFlags.Public); if (field != null && field.FieldType == typeof(float)) { field.SetValue(contract, payment); break; } } } catch { } } internal static float GetContractPayment(ContractInfo contract) { if (contract == null) { return 0f; } try { Type typeFromHandle = typeof(ContractInfo); string[] array = new string[3] { "Payment", "TotalPayment", "Reward" }; foreach (string name in array) { PropertyInfo property = typeFromHandle.GetProperty(name, BindingFlags.Instance | BindingFlags.Public); if (property != null && property.PropertyType == typeof(float)) { return (float)property.GetValue(contract); } FieldInfo field = typeFromHandle.GetField(name, BindingFlags.Instance | BindingFlags.Public); if (field != null && field.FieldType == typeof(float)) { return (float)field.GetValue(contract); } } } catch { } return 0f; } } public class ModDynamicsOrders : ModuleBase { public override void Load() { base.Load(); if (base.Configuration.EnsureDefaults()) { base.Configuration.SaveConfiguration(); } } public override void Apply() { if (!base.Configuration.Enabled) { MelonLogger.Msg("[Lithium_fork][DynamicsOrders] Module disabled."); } else { MelonLogger.Msg("[Lithium_fork][DynamicsOrders] Enabled — contract payment/quantity scaled by player XP (see DynamicsOrders.json)."); } } } } namespace Lithium.Modules.DynamicsOrders.Patches { [HarmonyPatch(typeof(CounterofferInterface), "Open")] public static class CounterofferOpenPriceSyncPatch { [HarmonyPostfix] [HarmonyPriority(0)] public static void Postfix(MSGConversation _conversation) { DynamicsOrdersConfiguration dynamicsOrdersConfiguration = Core.Get()?.Configuration; if (dynamicsOrdersConfiguration == null || !dynamicsOrdersConfiguration.Enabled || _conversation == null) { return; } Customer customerFromConversation = GetCustomerFromConversation(_conversation); if (((customerFromConversation != null) ? customerFromConversation.OfferedContractInfo : null) == null) { return; } float contractPayment = DynamicsOrderLogic.GetContractPayment(customerFromConversation.OfferedContractInfo); if (!(contractPayment <= 0f)) { MessagesApp instance = PlayerSingleton.Instance; object obj; if (instance == null) { obj = null; } else { CounterofferInterface counterofferInterface = instance.CounterofferInterface; obj = ((counterofferInterface != null) ? counterofferInterface.PriceInput : null); } if (!((Object)obj == (Object)null)) { instance.CounterofferInterface.PriceInput.text = Mathf.RoundToInt(contractPayment).ToString(); } } } private static Customer GetCustomerFromConversation(MSGConversation conversation) { string contactName = conversation.contactName; for (int i = 0; i < Customer.UnlockedCustomers.Count; i++) { Customer val = Customer.UnlockedCustomers[i]; if ((Object)(object)((val != null) ? val.NPC : null) != (Object)null && val.NPC.fullName == contactName) { return val; } } return null; } } } namespace Lithium.Modules.Customers { public class EffectMatchBonus { public float PercentageBonusMin { get; set; } public float PercentageBonusMax { get; set; } public int FixedBonusMin { get; set; } public int FixedBonusMax { get; set; } } public class EffectBonus { public bool Enabled { get; set; } public bool AffectsDealers { get; set; } = true; public EffectMatchBonus OneCoveredEffect { get; set; } = new EffectMatchBonus(); public EffectMatchBonus TwoCoveredEffects { get; set; } = new EffectMatchBonus(); public EffectMatchBonus ThreeCoveredEffects { get; set; } = new EffectMatchBonus(); } public class QualityBonus { public bool Enabled { get; set; } public bool AffectsDealers { get; set; } = true; public float BonusPercentage { get; set; } } public class SampleOffering { public bool Enabled { get; set; } public float QualityLevelModifier { get; set; } = 0.2f; public bool IncludeDrugPreference { get; set; } = true; public float BaseAcceptance { get; set; } } public class Contracts { [JsonProperty(/*Could not decode attribute arguments.*/)] [DefaultValue(true)] public bool Enabled { get; set; } = true; [JsonProperty(/*Could not decode attribute arguments.*/)] [DefaultValue(true)] public bool RejectHandoverWithoutPreferredEffects { get; set; } = true; public int XPRequired { get; set; } [JsonProperty(/*Could not decode attribute arguments.*/)] [DefaultValue(false)] public bool RequireXPForStrictHandover { get; set; } public bool SendNotification { get; set; } = true; public bool SendNotificationForDealers { get; set; } = true; public int NotificationCooldownInMinutes { get; set; } = 5; public string[] MessageTemplates { get; set; } = new string[4] { "Hey, I wanted to get fresh stuff, but you don't offer good stuff. I prefer ##DESIRES##", "I was looking for something ##DESIRES##, but you don't have any. Please improve your offering.", "Yo you still haven't got anything ##DESIRES##? Dang!", "##DESIRES## ... come on, can't be that hard to find, right?" }; public string[] DealerTemplates { get; set; } = new string[4] { "Hey, I wanted to get fresh stuff, but ##DEALER## doesn't offer good stuff. I prefer ##DESIRES##", "I was looking for something ##DESIRES##, but ##DEALER## doesn't have any. Could you help him out?", "Yo ##DEALER## still hasn't got anything ##DESIRES##? Dang!", "##DESIRES## ... come on, can't be that hard for ##DEALER## to find, right?" }; } public class ModCustomersConfiguration : ModuleConfiguration { public override string Name => "Customers"; public SampleOffering SampleOffering { get; set; } = new SampleOffering(); public Contracts Contracts { get; set; } = new Contracts(); public QualityBonus QualityBonus { get; set; } = new QualityBonus(); public EffectBonus EffectBonus { get; set; } = new EffectBonus(); } public class ModCustomers : ModuleBase { internal readonly List BonusPaymentHandlers = new List(); public ModCustomers() { ClassInjector.RegisterTypeInIl2Cpp(); RegisterBonusPaymentHandler(new EffectCoverageBonus()); RegisterBonusPaymentHandler(new Lithium.Modules.Customers.BonusPayments.QualityBonus()); } public override void Apply() { if (!base.Configuration.Enabled) { MelonLogger.Msg("[Lithium_fork][Customers] Module Disabled — no contract rewrite, no handover extras."); return; } Contracts contracts = base.Configuration.Contracts; MelonLogger.Msg($"[Lithium_fork][Customers] Config: Contracts.Enabled={contracts.Enabled}, RejectHandoverWithoutPreferredEffects={contracts.RejectHandoverWithoutPreferredEffects}, EffectBonus.Enabled={base.Configuration.EffectBonus.Enabled}, RequireXPForStrictHandover={contracts.RequireXPForStrictHandover}, XPRequired={contracts.XPRequired} " + "(XP only used when RequireXPForStrictHandover is true). Wrong product in messages: check Contracts.Enabled and Harmony errors on TryGenerateContract."); if (!contracts.Enabled) { MelonLogger.Warning("[Lithium_fork][Customers] Contracts.Enabled is false — vanilla contract products will NOT be rewritten for favourite effects."); } CustomerContractHookProbe.LogContractRelatedCustomerMethods(); } public void RegisterBonusPaymentHandler(IBonusPaymentHandler handler) { if (BonusPaymentHandlers.All((IBonusPaymentHandler h) => h.GetType() != handler.GetType())) { BonusPaymentHandlers.Add(handler); } } } public static class CustomerEffectNotify { private const string FallbackPlayerMessage = "Your product doesn't match what this customer wants (preferred effects)."; private const string FallbackDealerMessage = "This delivery doesn't match what the customer wants (preferred effects)."; public static void NotifyPlayerProductsNotSuitable(Customer customer) { //IL_00e8: Unknown result type (might be due to invalid IL or missing references) //IL_00fe: Expected O, but got Unknown ModCustomersConfiguration configuration = Core.Get().Configuration; if (!configuration.Contracts.SendNotification || (Object)(object)customer == (Object)null) { return; } CustomerNotificationState customerNotificationState = default(CustomerNotificationState); if (((Component)customer).TryGetComponent(ref customerNotificationState)) { if (NetworkSingleton.Instance.Playtime - customerNotificationState.LastNotification < (float)(60 * configuration.Contracts.NotificationCooldownInMinutes)) { return; } } else { customerNotificationState = ((Component)customer).gameObject.AddComponent(); customerNotificationState.LastNotification = NetworkSingleton.Instance.Playtime; } string text = ((configuration.Contracts.MessageTemplates != null && configuration.Contracts.MessageTemplates.Length != 0) ? configuration.Contracts.MessageTemplates.OrderBy((string _) => Random.value).FirstOrDefault() : null); string text2 = (string.IsNullOrEmpty(text) ? "Your product doesn't match what this customer wants (preferred effects)." : text).Replace("##DESIRES##", ProductHelper.FormatDesires(customer)); NetworkSingleton.Instance.ReceiveMessage(new Message(text2, (ESenderType)1, false, -1), true, customer.NPC.ID); customerNotificationState.LastNotification = NetworkSingleton.Instance.Playtime; } public static void NotifyDealerNotSuitable(Customer customer) { //IL_0112: Unknown result type (might be due to invalid IL or missing references) //IL_0128: Expected O, but got Unknown ModCustomersConfiguration configuration = Core.Get().Configuration; if (!configuration.Contracts.SendNotificationForDealers || (Object)(object)customer == (Object)null) { return; } if ((Object)(object)customer.AssignedDealer == (Object)null) { NotifyPlayerProductsNotSuitable(customer); return; } CustomerNotificationState customerNotificationState = default(CustomerNotificationState); if (((Component)customer).TryGetComponent(ref customerNotificationState)) { if (NetworkSingleton.Instance.Playtime - customerNotificationState.LastNotification < (float)(60 * configuration.Contracts.NotificationCooldownInMinutes)) { return; } } else { customerNotificationState = ((Component)customer).gameObject.AddComponent(); customerNotificationState.LastNotification = NetworkSingleton.Instance.Playtime; } string text = ((configuration.Contracts.DealerTemplates != null && configuration.Contracts.DealerTemplates.Length != 0) ? configuration.Contracts.DealerTemplates.OrderBy((string _) => Random.value).FirstOrDefault() : null); string text2 = (string.IsNullOrEmpty(text) ? "This delivery doesn't match what the customer wants (preferred effects)." : text).Replace("##DEALER##", ((NPC)customer.AssignedDealer).FirstName).Replace("##DESIRES##", ProductHelper.FormatDesires(customer)); NetworkSingleton.Instance.ReceiveMessage(new Message(text2, (ESenderType)1, false, -1), true, customer.NPC.ID); customerNotificationState.LastNotification = NetworkSingleton.Instance.Playtime; } public static void NotifyHandoverRejectedWrongEffects(Customer customer, bool handoverByPlayer) { if (handoverByPlayer) { NotifyPlayerProductsNotSuitable(customer); } else { NotifyDealerNotSuitable(customer); } } } public static class CustomerContractHookProbe { public static void LogContractRelatedCustomerMethods() { try { MethodBase[] array = (from m in (from m in typeof(Customer).GetMethods(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) where m.Name.Contains("Contract", StringComparison.OrdinalIgnoreCase) || m.Name.Contains("Generate", StringComparison.OrdinalIgnoreCase) || m.Name.Contains("Deal", StringComparison.OrdinalIgnoreCase) || m.Name == "TryGenerateContract" select m).Cast().Distinct() orderby m.Name select m).ToArray(); MelonLogger.Msg($"[Lithium_fork][Customers] Il2Cpp Customer methods (contract/deal/generate hints): {array.Length} found"); foreach (MethodBase item in array.Take(40)) { string value = string.Join(", ", from p in item.GetParameters() select (p.ParameterType.IsByRef ? "ref " : "") + p.ParameterType.Name + " " + p.Name); MelonLogger.Msg($"[Lithium_fork][Customers] {item.Name}({value})"); } if (array.Length > 40) { MelonLogger.Msg($"[Lithium_fork][Customers] ... and {array.Length - 40} more (truncated)"); } } catch (Exception ex) { MelonLogger.Warning("[Lithium_fork][Customers] Contract hook probe failed: " + ex.Message); } } } public static class CustomerContractRewriter { public static void TryRewrite(ref ContractInfo __result, Customer __instance, ModCustomersConfiguration config) { //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_028f: Unknown result type (might be due to invalid IL or missing references) //IL_020e: Unknown result type (might be due to invalid IL or missing references) if (!config.Enabled || !config.Contracts.Enabled || __result == null) { return; } HashSet desireSet = ProductHelper.GetNormalizedDesires(__instance).ToHashSet(); if (desireSet.Count == 0) { return; } Entry obj = __result.Products.entries.ToList()[0]; EQuality quality = obj.Quality; int quantity = obj.Quantity; if ((Object)(object)__instance.AssignedDealer != (Object)null) { if (__instance.DealerHasSuitableProduct(out var dealerItems)) { List list = dealerItems.Select((ItemInstance d) => ProductManager.DiscoveredProducts.ToList().Single((ProductDefinition p) => ((BaseItemDefinition)p).ID.Equals(((BaseItemInstance)d).ID))).Distinct().ToList(); if (list.Count == 0) { CustomerEffectNotify.NotifyDealerNotSuitable(__instance); __result = null; return; } Dictionary dictionary = new Dictionary(); foreach (ProductDefinition product in list) { if (!((Object)(object)product == (Object)null)) { int num = dealerItems.Where((ItemInstance i) => ((BaseItemInstance)i).ID == ((BaseItemDefinition)product).ID).Sum((ItemInstance i) => ((BaseItemInstance)i).Quantity); if (num > 0) { dictionary[((BaseItemDefinition)product).ID] = num; } } } WeightedPicker weightedPicker = new WeightedPicker(); int num2 = 0; foreach (KeyValuePair entry in dictionary) { int matchCount = ProductHelper.GetMatchCount(dealerItems.First((ItemInstance i) => ((BaseItemInstance)i).ID.Equals(entry.Key)), desireSet); num2 += matchCount; weightedPicker.Add(entry.Key, matchCount); } if (num2 <= 0) { CustomerEffectNotify.NotifyDealerNotSuitable(__instance); __result = null; } else { string text = weightedPicker.Pick(); quantity = dictionary[text]; RewireOrderedProduct(__result, text, quality, Mathf.Clamp(quantity, 1, quantity)); } } else { CustomerEffectNotify.NotifyDealerNotSuitable(__instance); __result = null; } } else { List list2 = (from p in ProductManager.ListedProducts.ToList() where ProductHelper.ProductMatchesDesires(p, desireSet) select p into x orderby Random.value select x).ToList(); if (list2.Count == 0) { CustomerEffectNotify.NotifyPlayerProductsNotSuitable(__instance); __result = null; } else { RewireOrderedProduct(__result, ((BaseItemDefinition)list2[0]).ID, quality, quantity); } } } private static void RewireOrderedProduct(ContractInfo __result, string id, EQuality quality, int maxAvailableQuantity) { //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Expected O, but got Unknown //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Expected O, but got Unknown int num = __result.Products.entries.ToList().Sum((Entry e) => e.Quantity); ProductList val = new ProductList(); val.entries.Add(new Entry { ProductID = id, Quality = quality, Quantity = Mathf.Min(maxAvailableQuantity, num) }); __result.Products = val; } } public static class CustomerPreferredEffectHandoverGate { public static bool StrictPreferredEffectEnforcementActive(ModCustomersConfiguration config) { if (!config.Enabled || !config.Contracts.RejectHandoverWithoutPreferredEffects) { return false; } if (config.Contracts.RequireXPForStrictHandover) { int xPRequired = config.Contracts.XPRequired; if (xPRequired > 0 && (Object)(object)NetworkSingleton.Instance != (Object)null && NetworkSingleton.Instance.TotalXP < xPRequired) { return false; } } return true; } public static bool ViolatesOnFinalize(EHandoverOutcome outcome, Customer customer, List items) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Invalid comparison between Unknown and I4 if ((int)outcome == 1) { return !ProductHelper.ItemsSatisfyPreferredEffects(customer, items); } return false; } public static bool ShouldBlockFinalizeForPreferredEffects(ModCustomersConfiguration config, EHandoverOutcome outcome, Customer customer, List items) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) if (StrictPreferredEffectEnforcementActive(config)) { return ViolatesOnFinalize(outcome, customer, items); } return false; } } } namespace Lithium.Modules.Customers.Patches { [HarmonyPatch(typeof(Customer), "TryGenerateContract")] public class CustomerContractGenerationPatch { [HarmonyPostfix] public static void Postfix(ref ContractInfo __result, Customer __instance, Dealer dealer) { ModCustomersConfiguration configuration = Core.Get().Configuration; CustomerContractRewriter.TryRewrite(ref __result, __instance, configuration); ModDynamicsOrders modDynamicsOrders = Core.Get(); if (modDynamicsOrders != null && modDynamicsOrders.Configuration.Enabled) { DynamicsOrderLogic.Apply(ref __result, __instance, modDynamicsOrders.Configuration); } } } [HarmonyPatch(typeof(Customer), "ProcessHandover")] [HarmonyPriority(0)] public static class CustomerProcessHandoverPatch { public static bool Prefix(Customer __instance, EHandoverOutcome outcome, Contract contract, List items, bool handoverByPlayer, bool giveBonuses) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_00f8: Unknown result type (might be due to invalid IL or missing references) //IL_0139: Unknown result type (might be due to invalid IL or missing references) //IL_0143: Expected O, but got Unknown //IL_0179: Unknown result type (might be due to invalid IL or missing references) //IL_017e: Unknown result type (might be due to invalid IL or missing references) //IL_0182: Unknown result type (might be due to invalid IL or missing references) //IL_01a5: Unknown result type (might be due to invalid IL or missing references) //IL_01a7: Unknown result type (might be due to invalid IL or missing references) //IL_016e: Unknown result type (might be due to invalid IL or missing references) //IL_0178: Expected O, but got Unknown //IL_01c3: Unknown result type (might be due to invalid IL or missing references) //IL_01cd: Expected O, but got Unknown //IL_02ec: Unknown result type (might be due to invalid IL or missing references) //IL_02ee: Invalid comparison between Unknown and I4 //IL_03e2: Unknown result type (might be due to invalid IL or missing references) ModCustomersConfiguration configuration = Core.Get().Configuration; if (!configuration.Enabled) { return true; } if (CustomerPreferredEffectHandoverGate.ShouldBlockFinalizeForPreferredEffects(configuration, outcome, __instance, items)) { MelonLogger.Warning("[Lithium_fork] Handover rejected (effect-bonus prefix): products do not match customer's preferred effects."); try { if ((Object)(object)Singleton.Instance != (Object)null) { Singleton.Instance.ClearCustomerSlots(false); } CustomerEffectNotify.NotifyHandoverRejectedWrongEffects(__instance, handoverByPlayer); } catch (Exception value) { MelonLogger.Error($"Handover reject cleanup failed: {value}"); } return false; } if (!configuration.EffectBonus.Enabled) { return true; } if (!handoverByPlayer && !configuration.EffectBonus.AffectsDealers) { return true; } float num = default(float); EDrugType val = default(EDrugType); int num2 = default(int); float num3 = default(float); float num4 = Mathf.Clamp01(__instance.EvaluateDelivery(contract, items, ref num, ref val, ref num2, ref num3)); __instance.ChangeAddiction(num4 / 5f); float relationDelta = __instance.NPC.RelationData.RelationDelta; float relationshipChange = CustomerSatisfaction.GetRelationshipChange(num4); float num5 = relationshipChange * 0.2f * Mathf.Lerp(0.75f, 1.5f, num); __instance.AdjustAffinity(val, num5); __instance.NPC.RelationData.ChangeRelationship(relationshipChange, true); List list = new List(); if (NetworkSingleton.Instance.IsCurrentlyActive) { list.Add(new BonusPayment("Curfew Bonus", contract.Payment * 0.2f)); } if (num2 > contract.ProductList.GetTotalQuantity()) { list.Add(new BonusPayment("Generosity Bonus", 10f * (float)(num2 - contract.ProductList.GetTotalQuantity()))); } GameDateTime acceptTime = contract.AcceptTime; GameDateTime val2 = default(GameDateTime); ((GameDateTime)(ref val2))..ctor(acceptTime.elapsedDays, TimeManager.AddMinutesTo24HourTime(contract.DeliveryWindow.WindowStartTime, 60)); if (NetworkSingleton.Instance.IsCurrentDateWithinRange(acceptTime, val2)) { list.Add(new BonusPayment("Quick Delivery Bonus", contract.Payment * 0.1f)); } List items2 = items.ToList(); foreach (IBonusPaymentHandler bonusPaymentHandler in Core.Get().BonusPaymentHandlers) { if (bonusPaymentHandler.BonusPaymentHandler(__instance, contract, items2, out var boni) && boni.Sum((BonusPayment b) => b.Amount) > 0f) { list.AddRange(boni); } } float num6 = 0f; foreach (BonusPayment item in list) { MelonLogger.Msg($"Bonus: {item.Title} Amount: {item.Amount}"); num6 += item.Amount; } if (handoverByPlayer) { Singleton.Instance.ClearCustomerSlots(false); contract.SubmitPayment(num6); } if ((int)outcome == 1 && handoverByPlayer) { Singleton.Instance.PlayPopup(__instance, num4, relationDelta, contract.Payment, list.ToIL2CPPList()); } __instance.TimeSinceLastDealCompleted = 0; __instance.NPC.SendAnimationTrigger("GrabItem"); NetworkObject val3 = null; if ((Object)(object)contract.Dealer != (Object)null) { val3 = ((NetworkBehaviour)contract.Dealer).NetworkObject; } MelonLogger.Msg($"Base payment: {contract.Payment} Total bonus: {num6} Satisfaction: {num4} Dealer: {((val3 != null) ? ((Object)val3).name : null) ?? "none"}"); float num7 = Mathf.Clamp(contract.Payment + num6, 0f, float.MaxValue); __instance.ProcessHandoverServerSide(outcome, items, handoverByPlayer, num7, contract.ProductList, num4, val3); return false; } } [HarmonyPatch(typeof(Customer), "ProcessHandoverServerSide")] [HarmonyPriority(800)] public static class CustomerProcessHandoverServerSidePatch { public static bool Prefix(Customer __instance, EHandoverOutcome outcome, List items, bool handoverByPlayer, float totalPayment, ProductList productList, float satisfaction, NetworkObject dealerObject) { //IL_0013: Unknown result type (might be due to invalid IL or missing references) if (!CustomerPreferredEffectHandoverGate.StrictPreferredEffectEnforcementActive(Core.Get().Configuration)) { return true; } if (!CustomerPreferredEffectHandoverGate.ViolatesOnFinalize(outcome, __instance, items)) { return true; } MelonLogger.Warning("[Lithium_fork] Handover rejected (server-side): products do not match customer's preferred effects."); try { if ((Object)(object)Singleton.Instance != (Object)null) { Singleton.Instance.ClearCustomerSlots(false); } CustomerEffectNotify.NotifyHandoverRejectedWrongEffects(__instance, handoverByPlayer); } catch (Exception value) { MelonLogger.Error($"Server-side handover UI cleanup failed: {value}"); } return false; } } [HarmonyPatch(typeof(Customer), "GetSampleSuccess")] public class CustomerSampleSuccessPatch { [HarmonyPrefix] private static bool Prefix(Customer __instance, List items, float price, ref float __result) { //IL_00b0: Unknown result type (might be due to invalid IL or missing references) //IL_00b7: Unknown result type (might be due to invalid IL or missing references) //IL_00c8: Unknown result type (might be due to invalid IL or missing references) ModCustomersConfiguration configuration = Core.Get().Configuration; if (!configuration.Enabled || !configuration.SampleOffering.Enabled) { return true; } float num = 0f; int num2 = 0; CustomerData effectiveCustomerData = ProductHelper.GetEffectiveCustomerData(__instance); if ((Object)(object)effectiveCustomerData == (Object)null) { return true; } Enumerator enumerator = items.GetEnumerator(); while (enumerator.MoveNext()) { ItemInstance current = enumerator.Current; ProductDefinition val = ProductHelper.ResolveProductDefinition(current); ProductItemInstance val2 = ((Il2CppObjectBase)current).TryCast(); if (!((Object)(object)val == (Object)null) && val2 != null) { string[] desires = ProductHelper.GetNormalizedDesires(__instance).ToArray(); List list = ProductHelper.GetNormalizedEffectNamesForMatchingItem(current).ToList(); string[] effects = ((list.Count > 0) ? list.ToArray() : Array.Empty()); num += SuccessChanceCalculator.CalculateSuccess(val.DrugType, ((QualityItemInstance)val2).Quality, configuration.SampleOffering.QualityLevelModifier, effectiveCustomerData.Standards, desires, effects, effectiveCustomerData.DefaultAffinityData, configuration.SampleOffering.IncludeDrugPreference, configuration.SampleOffering.BaseAcceptance); num2++; } } if (num2 == 0) { return true; } __result = num / (float)num2; return false; } } [HarmonyPatch(typeof(Customer), "ProcessHandover")] [HarmonyPriority(800)] public static class CustomerStrictHandoverPatch { public static bool Prefix(Customer __instance, EHandoverOutcome outcome, Contract contract, List items, bool handoverByPlayer, bool giveBonuses) { //IL_0013: Unknown result type (might be due to invalid IL or missing references) if (!CustomerPreferredEffectHandoverGate.StrictPreferredEffectEnforcementActive(Core.Get().Configuration)) { return true; } if (!CustomerPreferredEffectHandoverGate.ViolatesOnFinalize(outcome, __instance, items)) { return true; } MelonLogger.Warning("[Lithium_fork] Handover rejected: products do not match customer's preferred effects."); try { if ((Object)(object)Singleton.Instance != (Object)null) { Singleton.Instance.ClearCustomerSlots(false); } CustomerEffectNotify.NotifyHandoverRejectedWrongEffects(__instance, handoverByPlayer); } catch (Exception value) { MelonLogger.Error($"Strict handover UI cleanup failed: {value}"); } return false; } } [HarmonyPatch(typeof(DeliveryApp), "SetOpen")] public class DeliveryAppPatch { [HarmonyPrefix] public static void Prefix(ref bool open, DeliveryApp __instance) { ModShops modShops = Core.Get(); if (modShops == null || !modShops.Configuration.Enabled) { return; } ModShopsConfiguration configuration = Core.Get().Configuration; if (configuration.Deliveries == null) { configuration.Deliveries = PlayerSingleton.Instance.deliveryShops.ToList().ToDictionary((DeliveryShop d) => d.MatchingShop.ShopName, (DeliveryShop _) => new DeliverySettings { Availability = DeliveryAvailabilitySettings.Unchanged, DeliveryFee = 0f, XPRequirement = 0 }); configuration.SaveConfiguration(); } DeliveryUtils.ApplyDeliveryOverrides(); } [HarmonyPostfix] public static void Postfix(bool open) { if (open) { ModShops modShops = Core.Get(); if (modShops != null && modShops.Configuration.Enabled) { SupplierStartPatch.ApplySupplierOverridesFromUiHooks(); } } } } } namespace Lithium.Modules.Customers.BonusPayments { public class EffectCoverageBonus : IBonusPaymentHandler { public bool BonusPaymentHandler(Customer customer, Contract contract, List items, out List boni) { //IL_029e: Unknown result type (might be due to invalid IL or missing references) //IL_02a8: Expected O, but got Unknown boni = new List(); ModCustomersConfiguration configuration = Core.Get().Configuration; if (!configuration.EffectBonus.Enabled) { return false; } if (!configuration.EffectBonus.AffectsDealers && (Object)(object)contract.Dealer != (Object)null) { return false; } HashSet hashSet = ProductHelper.GetNormalizedDesires(customer).ToHashSet(); if (hashSet.Count == 0 || items == null || items.Count == 0) { return false; } int num = items.ToList().Sum((ItemInstance i) => (i != null) ? ((BaseItemInstance)i).Quantity : 0); float num2 = 0f; foreach (ItemInstance item in items) { if (item != null) { int matchCount = ProductHelper.GetMatchCount(item, hashSet); if (matchCount > 0) { ProductDefinition val = ProductHelper.ResolveProductDefinition(item); string value = (((Object)(object)val != (Object)null) ? ((BaseItemDefinition)val).Name : ((BaseItemInstance)item).ID); EffectMatchBonus effectMatchBonus = matchCount switch { 1 => configuration.EffectBonus.OneCoveredEffect, 2 => configuration.EffectBonus.TwoCoveredEffects, _ => configuration.EffectBonus.ThreeCoveredEffects, }; int num3 = Mathf.Max(1, ((BaseItemInstance)item).Quantity); int num4 = Mathf.Min(effectMatchBonus.FixedBonusMin, effectMatchBonus.FixedBonusMax); int num5 = Mathf.Max(effectMatchBonus.FixedBonusMin, effectMatchBonus.FixedBonusMax); float num6 = Mathf.Min(effectMatchBonus.PercentageBonusMin, effectMatchBonus.PercentageBonusMax); float num7 = Mathf.Max(effectMatchBonus.PercentageBonusMin, effectMatchBonus.PercentageBonusMax); int num8 = ((num5 > num4) ? Random.Range(num4, num5 + 1) : num4); float num9 = ((num7 > num6) ? Random.Range(num6, num7) : num6); float num10 = num8 * num3; float num11 = (float)num3 / (float)num; float num12 = contract.Payment * (num9 / 100f) * num11; float num13 = num10 + num12; num2 += num13; MelonLogger.Msg($"Effect match boni for item {value}: x{num3} (matches: {matchCount}) Fixed: {num10} Percent: {num12} Total: {num13}"); } } } if (num2 <= 0f) { return false; } boni.Add(new BonusPayment("Effect Match Bonus", num2)); return true; } } public class QualityBonus : IBonusPaymentHandler { public bool BonusPaymentHandler(Customer customer, Contract contract, List items, out List boni) { //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_0086: Unknown result type (might be due to invalid IL or missing references) //IL_0104: Unknown result type (might be due to invalid IL or missing references) //IL_010e: Expected O, but got Unknown boni = new List(); ModCustomersConfiguration configuration = Core.Get().Configuration; if (!configuration.QualityBonus.Enabled) { return false; } if (!configuration.QualityBonus.AffectsDealers && (Object)(object)contract.Dealer != (Object)null) { return false; } CustomerData effectiveCustomerData = ProductHelper.GetEffectiveCustomerData(customer); if ((Object)(object)effectiveCustomerData == (Object)null) { return false; } float qualityScalar = CustomerData.GetQualityScalar(StandardsMethod.GetCorrespondingQuality(effectiveCustomerData.Standards)); float num = 1f; foreach (ItemInstance item in items) { ProductItemInstance val = ((Il2CppObjectBase)item).TryCast(); if (val != null) { float qualityScalar2 = CustomerData.GetQualityScalar(((QualityItemInstance)val).Quality); if (qualityScalar2 < num) { num = qualityScalar2; } } else { MelonLogger.Msg("Found null product item! Please report this bug."); } } float num2 = num - qualityScalar; if (num2 > 0f) { int num3 = Mathf.RoundToInt(num2 / 0.25f); float num4 = contract.Payment * ((float)num3 * configuration.QualityBonus.BonusPercentage / 100f); boni.Add(new BonusPayment("Quality Bonus", num4)); return true; } return false; } } } namespace Lithium.Modules.Customers.Behaviours { public class CustomerNotificationState : MonoBehaviour { public float LastNotification; } } namespace Lithium.Modules.Customers.Architecture { public interface IBonusPaymentHandler { bool BonusPaymentHandler(Customer customer, Contract contract, List items, out List boni); } } namespace Lithium.Modules.ChemistryStation { internal static class ChemistryStationTimeScaling { internal static int GetScaledRequiredMinutes(ChemistryCookOperation op, float speed) { if (op == null) { return 1; } StationRecipe recipe = op.Recipe; if ((Object)(object)recipe == (Object)null) { return 1; } int cookTime_Mins = recipe.CookTime_Mins; float num = Mathf.Max(0.01f, speed); return Mathf.Max(1, Mathf.FloorToInt((float)cookTime_Mins / num)); } } public class ModChemistryStationConfiguration : ModuleConfiguration { public float Speed = 1f; public override string Name => "ChemistryStation"; } public class ModChemistryStation : ModuleBase { public override void Apply() { _ = base.Configuration.Enabled; } } } namespace Lithium.Modules.ChemistryStation.Patches { [HarmonyPatch(typeof(ChemistryCookOperation), "IsComplete")] public class ChemistryCookOperationIsCompletePatch { [HarmonyPostfix] public static void Postfix(ChemistryCookOperation __instance, ref bool __result) { ModChemistryStationConfiguration configuration = Core.Get().Configuration; if (configuration.Enabled) { int scaledRequiredMinutes = ChemistryStationTimeScaling.GetScaledRequiredMinutes(__instance, configuration.Speed); __result = __instance.CurrentTime >= scaledRequiredMinutes; } } } [HarmonyPatch(typeof(ChemistryStation), "UpdateClock")] public class ChemistryStationUpdateClockPatch { [HarmonyPostfix] public static void Postfix(ChemistryStation __instance) { ModChemistryStationConfiguration configuration = Core.Get().Configuration; if (configuration.Enabled) { ChemistryCookOperation currentCookOperation = __instance.CurrentCookOperation; if (currentCookOperation != null && !((Object)(object)__instance.Alarm == (Object)null)) { int scaledRequiredMinutes = ChemistryStationTimeScaling.GetScaledRequiredMinutes(currentCookOperation, configuration.Speed); int num = Mathf.Max(0, scaledRequiredMinutes - currentCookOperation.CurrentTime); __instance.Alarm.DisplayCurrentTime = false; __instance.Alarm.SetScreenLit(true); __instance.Alarm.DisplayMinutes(num); } } } } } namespace Lithium.Modules.CauldronCustom { public class CauldronCustomConfiguration : ModuleConfiguration { [JsonProperty(Order = 1)] public float ManualMinutesPerBatch; [JsonProperty(Order = 2)] public float NpcMinutesPerBatch; [JsonProperty(Order = 3)] public float ManualCookSpeed = 1f; [JsonProperty(Order = 4)] public float NpcCookSpeed = 1f; [JsonProperty(Order = 10)] public bool DebugLogOnce; [JsonProperty(Order = 11)] public bool LogEverySendCookOperation; public override string Name => "CauldronCustom"; internal void NormalizeLegacySpeedFieldsFromJson() { string configFile = GetConfigFile(); if (File.Exists(configFile)) { JObject val; try { val = JObject.Parse(File.ReadAllText(configFile)); } catch { return; } if (val["ManualCookSpeed"] == null && val["ManualDurationMultiplier"] != null) { ManualCookSpeed = Extensions.Value((IEnumerable)val["ManualDurationMultiplier"]); } if (val["NpcCookSpeed"] == null && val["NpcDurationMultiplier"] != null) { NpcCookSpeed = Extensions.Value((IEnumerable)val["NpcDurationMultiplier"]); } } } } public class ModCauldronCustom : ModuleBase { public override void Load() { base.Load(); base.Configuration.NormalizeLegacySpeedFieldsFromJson(); if (base.Configuration.LogEverySendCookOperation) { MelonLogger.Msg("[Lithium_fork][CauldronCustom] Verbose SendCookOperation logging is ON; path=" + base.Configuration.GetConfigFile() + " Enabled=" + base.Configuration.Enabled); } } public override void Apply() { _ = base.Configuration.Enabled; } } internal static class CauldronCustomTimeLogic { private static bool _debugLoggedOnce; private static readonly Dictionary _sessionFirstInput = new Dictionary(); private const float SessionIdleForgetSeconds = 120f; private static int _sendCookReentrancyDepth; private const ulong NetworkObjectIdPrefix = 5620492334958379008uL; private const ulong NativePointerPrefix = 5764607523034234880uL; private const ulong NativePointerMask = 281474976710655uL; private const ulong InstanceIdPrefix = 6917529027641081856uL; internal static string LastDuplicateSkipReason { get; set; } = string.Empty; internal static ulong GetCauldronSessionKey(Cauldron cauldron) { if ((Object)(object)cauldron == (Object)null) { return 0uL; } try { NetworkBehaviour val = ((Il2CppObjectBase)cauldron).TryCast(); if ((Object)(object)val != (Object)null && (Object)(object)val.NetworkObject != (Object)null) { int objectId = val.NetworkObject.ObjectId; return 0x4E00000000000000uL | (uint)objectId; } } catch { } try { IntPtr pointer = ((Il2CppObjectBase)cauldron).Pointer; if (pointer != IntPtr.Zero) { return 0x5000000000000000uL | ((ulong)pointer.ToInt64() & 0xFFFFFFFFFFFFuL); } } catch { } return 0x6000000000000000uL | (uint)((Object)cauldron).GetInstanceID(); } internal static void EnterSendCookOperation() { _sendCookReentrancyDepth++; } internal static void LeaveSendCookOperation() { if (_sendCookReentrancyDepth > 0) { _sendCookReentrancyDepth--; } } internal static bool ShouldSkipSendCookOperationDueToReentrancy() { return _sendCookReentrancyDepth > 1; } internal static void ApplyBatchTime(Cauldron cauldron, ref int remainingCookTime) { LastDuplicateSkipReason = string.Empty; CauldronCustomConfiguration configuration = Core.Get().Configuration; if (!configuration.Enabled) { return; } int num = remainingCookTime; bool flag = IsNpcCauldron(cauldron); if (flag) { if (configuration.NpcMinutesPerBatch > 0f) { remainingCookTime = Mathf.Max(1, Mathf.RoundToInt(configuration.NpcMinutesPerBatch)); } else if (configuration.NpcCookSpeed > 0f && !Mathf.Approximately(configuration.NpcCookSpeed, 1f)) { if (ShouldSkipDuplicateSpeedScale(cauldron, num, configuration.NpcCookSpeed)) { return; } remainingCookTime = Mathf.Max(1, Mathf.RoundToInt((float)remainingCookTime / configuration.NpcCookSpeed)); NotifySpeedScaleApplied(cauldron, remainingCookTime); } } else if (configuration.ManualMinutesPerBatch > 0f) { remainingCookTime = Mathf.Max(1, Mathf.RoundToInt(configuration.ManualMinutesPerBatch)); } else if (configuration.ManualCookSpeed > 0f && !Mathf.Approximately(configuration.ManualCookSpeed, 1f)) { if (ShouldSkipDuplicateSpeedScale(cauldron, num, configuration.ManualCookSpeed)) { return; } remainingCookTime = Mathf.Max(1, Mathf.RoundToInt((float)remainingCookTime / configuration.ManualCookSpeed)); NotifySpeedScaleApplied(cauldron, remainingCookTime); } if (configuration.DebugLogOnce && !_debugLoggedOnce) { _debugLoggedOnce = true; MelonLogger.Msg($"[Lithium_fork][CauldronCustom] Debug (once): npc={flag}, remainingCookTime {num} -> {remainingCookTime} (ManualMinutes={configuration.ManualMinutesPerBatch}, ManualCookSpeed={configuration.ManualCookSpeed}, NpcMinutes={configuration.NpcMinutesPerBatch}, NpcCookSpeed={configuration.NpcCookSpeed})"); } } private static bool ShouldSkipDuplicateSpeedScale(Cauldron cauldron, int before, float speed) { if ((Object)(object)cauldron == (Object)null || speed <= 0f) { return false; } ulong cauldronSessionKey = GetCauldronSessionKey(cauldron); float realtimeSinceStartup = Time.realtimeSinceStartup; if (_sessionFirstInput.TryGetValue(cauldronSessionKey, out (int, int, float) value) && realtimeSinceStartup - value.Item3 > 120f) { _sessionFirstInput.Remove(cauldronSessionKey); } if (!_sessionFirstInput.TryGetValue(cauldronSessionKey, out value)) { _sessionFirstInput[cauldronSessionKey] = (before, 0, realtimeSinceStartup); LastDuplicateSkipReason = string.Empty; return false; } var (num, num2, _) = value; if (num2 != 0 && before != num && Mathf.Abs(before - num2) <= 4) { _sessionFirstInput[cauldronSessionKey] = (num, num2, realtimeSinceStartup); LastDuplicateSkipReason = "duplicate_output_match"; return true; } if (num != before) { float num3 = (float)before * speed; if (Mathf.Abs(num3 - (float)num) <= Mathf.Max(2f, (float)Mathf.Abs(num) * 0.02f)) { _sessionFirstInput[cauldronSessionKey] = (num, num2, realtimeSinceStartup); LastDuplicateSkipReason = "duplicate_roundtrip_float"; return true; } if (Mathf.Abs(Mathf.RoundToInt(num3) - num) <= 3) { _sessionFirstInput[cauldronSessionKey] = (num, num2, realtimeSinceStartup); LastDuplicateSkipReason = "duplicate_roundtrip_int"; return true; } } if (before > num) { _sessionFirstInput[cauldronSessionKey] = (before, 0, realtimeSinceStartup); LastDuplicateSkipReason = string.Empty; return false; } _sessionFirstInput[cauldronSessionKey] = (num, num2, realtimeSinceStartup); LastDuplicateSkipReason = string.Empty; return false; } private static void NotifySpeedScaleApplied(Cauldron cauldron, int scaledRemainingCookTime) { if (!((Object)(object)cauldron == (Object)null)) { ulong cauldronSessionKey = GetCauldronSessionKey(cauldron); float realtimeSinceStartup = Time.realtimeSinceStartup; if (_sessionFirstInput.TryGetValue(cauldronSessionKey, out (int, int, float) value)) { _sessionFirstInput[cauldronSessionKey] = (value.Item1, scaledRemainingCookTime, realtimeSinceStartup); } } } private static bool IsNpcCauldron(Cauldron cauldron) { if ((Object)(object)cauldron == (Object)null) { return false; } try { return (Object)(object)cauldron.NPCUserObject != (Object)null; } catch { return false; } } } } namespace Lithium.Modules.CauldronCustom.Patches { [HarmonyPatch(typeof(Cauldron), "SendCookOperation")] internal static class CauldronSendCookOperationPatch { [HarmonyPrefix] private static void Prefix(Cauldron __instance, ref int remainingCookTime, EQuality quality) { bool flag = Core.Get()?.Configuration.LogEverySendCookOperation ?? false; int value = remainingCookTime; ulong cauldronSessionKey = CauldronCustomTimeLogic.GetCauldronSessionKey(__instance); CauldronCustomTimeLogic.LastDuplicateSkipReason = string.Empty; CauldronCustomTimeLogic.EnterSendCookOperation(); try { if (CauldronCustomTimeLogic.ShouldSkipSendCookOperationDueToReentrancy()) { CauldronCustomTimeLogic.LastDuplicateSkipReason = "reentrancy"; if (flag) { MelonLogger.Msg($"[Lithium_fork][CauldronCustom] SendCookOperation key=0x{cauldronSessionKey:X} before={value} after={remainingCookTime} skip=reentrancy isServer={InstanceFinder.IsServer}"); } return; } CauldronCustomTimeLogic.ApplyBatchTime(__instance, ref remainingCookTime); if (flag) { string lastDuplicateSkipReason = CauldronCustomTimeLogic.LastDuplicateSkipReason; string value2 = (string.IsNullOrEmpty(lastDuplicateSkipReason) ? "apply" : ("skip:" + lastDuplicateSkipReason)); MelonLogger.Msg($"[Lithium_fork][CauldronCustom] SendCookOperation key=0x{cauldronSessionKey:X} before={value} after={remainingCookTime} {value2} isServer={InstanceFinder.IsServer}"); } } finally { CauldronCustomTimeLogic.LeaveSendCookOperation(); } } } } namespace Lithium.Helper { public static class CollectionConverter { public static T[] ToArray(this List list) { if (list == null) { return Array.Empty(); } T[] array = new T[list.Count]; int num = 0; Enumerator enumerator = list.GetEnumerator(); while (enumerator.MoveNext()) { T current = enumerator.Current; array[num] = current; num++; } return array; } public static List ToList(this List list) { if (list == null) { return new List(); } List list2 = new List(list.Count); Enumerator enumerator = list.GetEnumerator(); while (enumerator.MoveNext()) { T current = enumerator.Current; list2.Add(current); } return list2; } public static List ToIL2CPPList(this List list) { List val = new List(list?.Count ?? 0); if (list == null) { return val; } foreach (T item in list) { val.Add(item); } return val; } } public static class ProductHelper { public static float TryGetMarketUnitPrice(string productId, float fallbackIfUnavailable) { if (string.IsNullOrEmpty(productId)) { return fallbackIfUnavailable; } try { ProductDefinition val = ((IEnumerable)ProductManager.DiscoveredProducts.ToList()).SingleOrDefault((Func)((ProductDefinition p) => (Object)(object)p != (Object)null && ((BaseItemDefinition)p).ID != null && ((BaseItemDefinition)p).ID.Equals(productId))); if ((Object)(object)val == (Object)null) { return fallbackIfUnavailable; } float? num = TryGetMarketValueFloat(val); return (num.HasValue && num.GetValueOrDefault() > 0f) ? num.Value : fallbackIfUnavailable; } catch { return fallbackIfUnavailable; } } private static float? TryGetMarketValueFloat(ProductDefinition pd) { if ((Object)(object)pd == (Object)null) { return null; } Type typeFromHandle = typeof(ProductDefinition); string[] array = new string[7] { "MarketValue", "BasePrice", "MarketPrice", "Price", "Value", "SellPrice", "UnitPrice" }; foreach (string name in array) { PropertyInfo property = typeFromHandle.GetProperty(name, BindingFlags.Instance | BindingFlags.Public); if (property != null) { try { object value = property.GetValue(pd); if (value is float value2) { return value2; } if (value is int) { int num = (int)value; return num; } } catch { } } FieldInfo field = typeFromHandle.GetField(name, BindingFlags.Instance | BindingFlags.Public); if (!(field != null)) { continue; } try { object value3 = field.GetValue(pd); if (value3 is float value4) { return value4; } if (value3 is int) { int num2 = (int)value3; return num2; } } catch { } } return null; } public static CustomerData GetEffectiveCustomerData(Customer customer) { if ((Object)(object)customer == (Object)null) { return null; } CustomerData customerData = customer.CustomerData; if ((Object)(object)customerData != (Object)null) { return customerData; } return customer.customerData; } public static string NormalizeEffectName(string name) { if (!string.IsNullOrEmpty(name)) { return name.Trim().ToLowerInvariant(); } return string.Empty; } public static HashSet GetNormalizedEffectNames(ProductDefinition pd) { if (((pd != null) ? ((PropertyItemDefinition)pd).Properties : null) == null) { return new HashSet(); } HashSet hashSet = new HashSet(); foreach (Effect item in ((PropertyItemDefinition)pd).Properties.ToList()) { if (!((Object)(object)item == (Object)null)) { string text = NormalizeEffectName(item.Name); if (text.Length > 0) { hashSet.Add(text); } } } return hashSet; } public static List GetNormalizedDesires(CustomerData customerData) { if (((customerData != null) ? customerData.PreferredProperties : null) == null) { return new List(); } return (from e in customerData.PreferredProperties.ToList() select NormalizeEffectName(e.Name) into s where s.Length > 0 select s).ToList(); } public static List GetNormalizedDesires(Customer customer) { return GetNormalizedDesires(GetEffectiveCustomerData(customer)); } public static ProductDefinition ResolveProductDefinition(ItemInstance item) { if (item == null) { return null; } ItemDefinition definition = item.Definition; ProductDefinition val = ((definition != null) ? ((Il2CppObjectBase)definition).TryCast() : null); if ((Object)(object)val != (Object)null) { return val; } return ((IEnumerable)ProductManager.DiscoveredProducts.ToList()).SingleOrDefault((Func)((ProductDefinition p) => (Object)(object)p != (Object)null && ((BaseItemDefinition)p).ID != null && ((BaseItemInstance)item).ID != null && ((BaseItemDefinition)p).ID.Equals(((BaseItemInstance)item).ID))); } public static HashSet GetNormalizedEffectNamesForMatchingItem(ItemInstance item) { if (item == null) { return new HashSet(); } HashSet hashSet = new HashSet(); foreach (string normalizedEffectName in GetNormalizedEffectNames(((IEnumerable)ProductManager.DiscoveredProducts.ToList()).SingleOrDefault((Func)((ProductDefinition p) => (Object)(object)p != (Object)null && ((BaseItemDefinition)p).ID != null && ((BaseItemInstance)item).ID != null && ((BaseItemDefinition)p).ID.Equals(((BaseItemInstance)item).ID))))) { hashSet.Add(normalizedEffectName); } ItemDefinition definition = item.Definition; foreach (string normalizedEffectName2 in GetNormalizedEffectNames((definition != null) ? ((Il2CppObjectBase)definition).TryCast() : null)) { hashSet.Add(normalizedEffectName2); } return hashSet; } public static int GetMatchCount(ItemInstance item, HashSet normalizedDesires) { if (item == null || normalizedDesires == null || normalizedDesires.Count == 0) { return 0; } return GetNormalizedEffectNamesForMatchingItem(item).Count(normalizedDesires.Contains); } public static bool ProductMatchesDesires(ProductDefinition pd, HashSet normalizedDesires) { if ((Object)(object)pd == (Object)null || normalizedDesires == null || normalizedDesires.Count == 0) { return false; } return GetNormalizedEffectNames(pd).Overlaps(normalizedDesires); } public static bool ProductMatchesDesires(ProductDefinition pd, List normalizedDesireList) { return ProductMatchesDesires(pd, normalizedDesireList.ToHashSet()); } public static int GetMatchCount(ProductDefinition pd, HashSet normalizedDesires) { if ((Object)(object)pd == (Object)null || normalizedDesires == null || normalizedDesires.Count == 0) { return 0; } return GetNormalizedEffectNames(pd).Count(normalizedDesires.Contains); } public static int GetMatchCount(IList source, HashSet normalizedDesires) { if (source == null || normalizedDesires == null || normalizedDesires.Count == 0) { return 0; } int num = 0; foreach (Effect item in source) { if (!((Object)(object)item == (Object)null)) { string text = NormalizeEffectName(item.Name); if (text.Length > 0 && normalizedDesires.Contains(text)) { num++; } } } return num; } public static bool PlayerHasSuitableProduct(this Customer customer) { HashSet desires = GetNormalizedDesires(customer).ToHashSet(); if (desires.Count == 0) { return true; } return ProductManager.DiscoveredProducts.ToList().Count((ProductDefinition pd) => GetMatchCount(pd, desires) > 0) > 0; } public static bool DealerHasSuitableProduct(this Customer customer, out List dealerItems) { HashSet hashSet = GetNormalizedDesires(customer).ToHashSet(); if (hashSet.Count == 0) { dealerItems = new List(); return true; } dealerItems = (from i in ((NPC)customer.AssignedDealer).Inventory.ItemSlots.ToList() where i.ItemInstance != null select i.ItemInstance).ToList(); HashSet hashSet2 = new HashSet(); foreach (ItemInstance dealerItem in dealerItems) { foreach (string item in GetNormalizedEffectNamesForMatchingItem(dealerItem)) { hashSet2.Add(item); } } return hashSet.Overlaps(hashSet2); } public static string FormatDesires(CustomerData customerData) { if (((customerData != null) ? customerData.PreferredProperties : null) == null) { return string.Empty; } return (from p in customerData.PreferredProperties.ToList() select p.Name).SmartJoin(", ", " or "); } public static string FormatDesires(Customer customer) { return FormatDesires(GetEffectiveCustomerData(customer)); } public static bool ItemsSatisfyPreferredEffects(Customer customer, List items) { if ((Object)(object)customer == (Object)null) { return true; } HashSet hashSet = GetNormalizedDesires(customer).ToHashSet(); if (hashSet.Count == 0) { return true; } if (items == null) { return false; } bool result = false; Enumerator enumerator = items.GetEnumerator(); while (enumerator.MoveNext()) { ItemInstance current = enumerator.Current; if (current != null && ((BaseItemInstance)current).Quantity > 0) { result = true; HashSet normalizedEffectNamesForMatchingItem = GetNormalizedEffectNamesForMatchingItem(current); if (normalizedEffectNamesForMatchingItem.Count == 0 || !hashSet.Overlaps(normalizedEffectNamesForMatchingItem)) { return false; } } } return result; } } public static class StringExtensions { public static string SmartJoin(this IEnumerable source, string glue, string lastGlue) { T[] array = source.ToArray(); switch (array.Length) { case 0: return string.Empty; case 1: return array[0].ToString(); default: { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.Append(string.Join(glue, array[..^1])); StringBuilder stringBuilder2 = stringBuilder; StringBuilder.AppendInterpolatedStringHandler handler = new StringBuilder.AppendInterpolatedStringHandler(0, 2, stringBuilder2); handler.AppendFormatted(lastGlue); handler.AppendFormatted(array.Last()); stringBuilder2.Append(ref handler); return stringBuilder.ToString(); } } } } }