Some mods target the Mono version of the game, which is available by opting into the Steam beta branch "alternate"
Decompiled source of DealersSendTexts v2.2.0
Main-DealersSendTexts-2.2.0.dll
Decompiled a week agousing System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using DealersSendTexts; using HarmonyLib; using Il2CppInterop.Runtime.InteropTypes; using Il2CppScheduleOne; using Il2CppScheduleOne.DevUtilities; using Il2CppScheduleOne.Economy; using Il2CppScheduleOne.GameTime; using Il2CppScheduleOne.ItemFramework; using Il2CppScheduleOne.Map; using Il2CppScheduleOne.Messaging; using Il2CppScheduleOne.Money; using Il2CppScheduleOne.NPCs; using Il2CppScheduleOne.Persistence; using Il2CppScheduleOne.Persistence.Datas; using Il2CppScheduleOne.PlayerScripts; using Il2CppScheduleOne.Product; using Il2CppScheduleOne.Quests; using Il2CppSystem.Collections.Generic; using MelonLoader; using MelonLoader.Preferences; using Newtonsoft.Json; using UnityEngine; using UnityEngine.Events; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: MelonInfo(typeof(DealerText), "Dealers Send Texts", "2.1.0", "GuysWeForgotDre", null)] [assembly: MelonGame("TVGS", "Schedule I")] [assembly: AssemblyTitle("Dealers Send Texts")] [assembly: AssemblyDescription("Dealers text updates on deals and daily summary in Schedule One")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("Dealers Send Texts")] [assembly: AssemblyCopyright("Copyright © 2025")] [assembly: AssemblyTrademark("")] [assembly: ComVisible(false)] [assembly: Guid("51af033c-2fba-4fd9-950e-a5fb45e211c4")] [assembly: AssemblyFileVersion("2.1.0.0")] [assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")] [assembly: AssemblyVersion("2.1.0.0")] namespace DealersSendTexts; public static class AlertManager { public static void DailySummary(DealerManager stats) { if (!((NPC)stats.Dealer).RelationData.Unlocked) { return; } DealerPrefs dealerPrefs = DealerPrefs.Prefs(((NPC)stats.Dealer).FirstName); DealerState state = stats.State; Dictionary<string, int> dictionary = new Dictionary<string, int>(); HashSet<string> hashSet = new HashSet<string>(); float num = 0f; foreach (SaleData dailySale in state.DailySales) { num += dailySale.Payment; if (!dictionary.ContainsKey(dailySale.Product)) { dictionary.Add(dailySale.Product, dailySale.Quantity); } else { dictionary[dailySale.Product] += dailySale.Quantity; } hashSet.Add(dailySale.Location); } if (dealerPrefs.GetShowSummary()) { string text = MoneyManager.FormatAmount(num, false, true); string text2 = ""; foreach (string key in dictionary.Keys) { text2 += $"\n{Util.GetName(key)} ({dictionary[key]})"; } string text3 = $"DAILY SUMMARY\nDid {state.DailySales.Count} deals in {hashSet.Count} locations; {state.Failures.Count} failed.\nMade {text} from:{text2}"; if (stats.IsDealerHurt(out var status)) { text3 = text3 + "\nDealer " + status; } MessageManager.Send(stats.Dealer, EIcon.Summary, text3, notify: false); } if (dealerPrefs.GetSendCustomers()) { string text4 = $"CUSTOMER LOG ({state.TodaysSales.Count})"; foreach (string key2 in state.TodaysSales.Keys) { stats.IsCustomerHurt(key2, out var status2); text4 = text4 + "\n" + key2 + ": " + state.TodaysSales[key2] + " " + status2; } MessageManager.Send(stats.Dealer, EIcon.Customer, text4, notify: false); } if (dealerPrefs.GetSendLocations()) { string text5 = $"LOCATIONS ({hashSet.Count})"; foreach (string item in hashSet) { text5 = text5 + "\n" + item; } MessageManager.Send(stats.Dealer, EIcon.Location, text5, notify: false); } if (dealerPrefs.GetSendFailures() && state.Failures.Count > 0) { string text6 = $"FAILURES ({state.Failures.Count})"; foreach (FailureKey failure in state.Failures) { text6 = text6 + "\n" + failure.Customer + " " + Util.Prefix(failure.Location); } MessageManager.Send(stats.Dealer, EIcon.Failure, text6, notify: false); } state.ClearAll(daily: true); } public static void NPCInjuryAlert(NPC npc, EIcon type) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_008a: Unknown result type (might be due to invalid IL or missing references) //IL_015b: Unknown result type (might be due to invalid IL or missing references) string text = LocationManager.Describe(((Component)npc).transform.position, "at"); string text2 = Vector3.Distance(Player.Local.PlayerBasePosition, ((Component)npc).transform.position).ToString("#.#"); string text3 = type switch { EIcon.HasDied => "died", EIcon.KnockOut => "been knocked out", _ => "an unknown alert", }; Dealer val = (Dealer)(object)((npc is Dealer) ? npc : null); if (val != null) { EMsg dealerInjury = DealerPrefs.Prefs(((NPC)val).FirstName).GetDealerInjury(); string message = $"I have {text3} in {npc.Region} {text}, {text2} meters from you."; if (dealerInjury != EMsg.Disable) { MessageManager.Send(val, type, message, dealerInjury == EMsg.Notify); } return; } foreach (DealerManager value2 in DealerManager.Dealers.Values) { foreach (string key in value2.Customers.Keys) { if (key == npc.fullName) { EMsg customerInjury = DealerPrefs.Prefs(((NPC)value2.Dealer).FirstName).GetCustomerInjury(); if (!value2.State.RecentSale.TryGetValue(key, out var value)) { value = "Never"; } string message2 = $"{npc.fullName} has {text3} in {npc.Region} {text}, {text2} meters from you. Last sale was {value}."; if (customerInjury != EMsg.Disable) { MessageManager.Send(value2.Dealer, type, message2, customerInjury == EMsg.Notify); } } } } } public static void CheckProductAlert(Dealer dealer) { //IL_002c: 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_0056: Unknown result type (might be due to invalid IL or missing references) DealerPrefs dealerPrefs = DealerPrefs.Prefs(((NPC)dealer).FirstName); DealerManager stats = DealerManager.GetStats(dealer); stats.RefreshInventory(); string text = MoneyManager.FormatAmount(dealer.Cash, false, false); string text2 = Vector3.Distance(Player.Local.PlayerBasePosition, ((Component)dealer).transform.position).ToString("#.#"); string text3 = LocationManager.Describe(((Component)dealer).transform.position, "at"); float num = 0f; foreach (string key in stats.State.Products.Keys) { num += (float)stats.State.Products[key]; } if (dealerPrefs.GetCheckCash() != EMsg.Disable && dealer.Cash >= dealerPrefs.GetCashAlert()) { MessageManager.Send(dealer, EIcon.ProdAlert, $"Cash threshold reached: {text}.\n{num} products left; I'm {text2} meters from you, {text3}.", dealerPrefs.GetCheckCash() == EMsg.Notify); } if (dealerPrefs.GetCheckProduct() != EMsg.Disable && num <= dealerPrefs.GetProductAlert()) { MessageManager.Send(dealer, EIcon.ProdAlert, $"Product threshold reached: {num}.\n{text} cash; I'm {text2} meters from you, {text3}.", dealerPrefs.GetCheckProduct() == EMsg.Notify); } } } public class DealerPrefs { public static DealerPrefs Master; private static bool MasterOnly; private static bool Initialized = false; private const string MasterName = "_Master"; private static readonly Dictionary<string, DealerPrefs> _dealerSettings = new Dictionary<string, DealerPrefs>(); private MelonPreferences_Category DealerGroup; private MelonPreferences_Entry<bool> OverrideMaster; private MelonPreferences_Entry<bool> CreateDealers; private MelonPreferences_Entry<EMsg> ShowStarted; private MelonPreferences_Entry<EMsg> ShowSuccess; private MelonPreferences_Entry<EMsg> ShowFailure; private MelonPreferences_Entry<bool> ShowSummary; private MelonPreferences_Entry<bool> SendCustomers; private MelonPreferences_Entry<bool> SendLocations; private MelonPreferences_Entry<bool> SendFailures; private MelonPreferences_Entry<EMsg> CheckProduct; private MelonPreferences_Entry<float> ProductAlert; private MelonPreferences_Entry<EMsg> CheckCash; private MelonPreferences_Entry<float> CashAlert; private MelonPreferences_Entry<EMsg> CustomerInjury; private MelonPreferences_Entry<EMsg> DealerInjury; private MelonPreferences_Entry<EMsg> IsStuckAlert; private MelonPreferences_Entry<int> IsStuckCount; private MelonPreferences_Entry<int> IsStuckRadius; private MelonPreferences_Entry<EMsg> Navigation; private MelonPreferences_Entry<int> NavDeltaTime; public static void Initialize() { if (!Initialized) { Master = AddDealer("_Master"); MasterOnly = !Master.CreateDealers.Value; Initialized = true; } } public EMsg GetShowStarted() { return GetValue<EMsg>(ShowStarted, Master.ShowStarted); } public EMsg GetShowSuccess() { return GetValue<EMsg>(ShowSuccess, Master.ShowSuccess); } public EMsg GetShowFailure() { return GetValue<EMsg>(ShowFailure, Master.ShowFailure); } public bool GetShowSummary() { return GetValue<bool>(ShowSummary, Master.ShowSummary); } public bool GetSendCustomers() { return GetValue<bool>(SendCustomers, Master.SendCustomers); } public bool GetSendLocations() { return GetValue<bool>(SendLocations, Master.SendLocations); } public bool GetSendFailures() { return GetValue<bool>(SendFailures, Master.SendFailures); } public EMsg GetCheckProduct() { return GetValue<EMsg>(CheckProduct, Master.CheckProduct); } public float GetProductAlert() { return GetValue<float>(ProductAlert, Master.ProductAlert); } public EMsg GetCheckCash() { return GetValue<EMsg>(CheckCash, Master.CheckCash); } public float GetCashAlert() { return GetValue<float>(CashAlert, Master.CashAlert); } public EMsg GetCustomerInjury() { return GetValue<EMsg>(CustomerInjury, Master.CustomerInjury); } public EMsg GetDealerInjury() { return GetValue<EMsg>(DealerInjury, Master.DealerInjury); } public EMsg GetIsStuckAlert() { return GetValue<EMsg>(IsStuckAlert, Master.IsStuckAlert); } public int GetIsStuckCount() { return GetValue<int>(IsStuckCount, Master.IsStuckCount); } public int GetIsStuckRadius() { return GetValue<int>(IsStuckRadius, Master.IsStuckRadius); } public EMsg GetNavigation() { return GetValue<EMsg>(Navigation, Master.Navigation); } public int GetINavDeltaTime() { return GetValue<int>(NavDeltaTime, Master.NavDeltaTime); } public static DealerPrefs Prefs(string firstName) { if (!MasterOnly) { if (!_dealerSettings.TryGetValue(firstName, out var value)) { return AddDealer(firstName); } return value; } return Master; } private static DealerPrefs AddDealer(string name) { DealerPrefs dealerPrefs = new DealerPrefs { DealerGroup = MelonPreferences.CreateCategory("DealersSendTexts_" + name, name + " Preferences") }; if (name == "_Master") { dealerPrefs.CreateDealers = dealerPrefs.DealerGroup.CreateEntry<bool>(name + "00_CreateDealers", false, "Create settings per dealer [requires restart]", (string)null, false, false, (ValueValidator)null, (string)null); ((MelonPreferences_Entry)dealerPrefs.CreateDealers).Comment = "Create separate settings panel for each dealer. Requires restart. (\"default\" false but will not auto delete if reset)"; } else { dealerPrefs.OverrideMaster = dealerPrefs.DealerGroup.CreateEntry<bool>(name + "00_OverrideMaster", false, "[Override Master]: Use these settings instead [false]", (string)null, false, false, (ValueValidator)null, (string)null); ((MelonPreferences_Entry)dealerPrefs.OverrideMaster).Comment = "Use individual dealer profile settings instead of generic master settings (default false)"; } dealerPrefs.ShowStarted = dealerPrefs.DealerGroup.CreateEntry<EMsg>(name + "01_ShowStarted", EMsg.Silent, "Text: When a new deal is started [default silent]", (string)null, false, false, (ValueValidator)null, (string)null); dealerPrefs.ShowSuccess = dealerPrefs.DealerGroup.CreateEntry<EMsg>(name + "02_ShowSuccess", EMsg.Silent, "Text: When deal successfully completed [silent]", (string)null, false, false, (ValueValidator)null, (string)null); dealerPrefs.ShowFailure = dealerPrefs.DealerGroup.CreateEntry<EMsg>(name + "03_ShowFailure", EMsg.Notify, "Text: When deal failed / timed out [notify]", (string)null, false, false, (ValueValidator)null, (string)null); dealerPrefs.ShowSummary = dealerPrefs.DealerGroup.CreateEntry<bool>(name + "04_ShowSummary", true, "Daily: Summary of deals completed that day [true]", (string)null, false, false, (ValueValidator)null, (string)null); dealerPrefs.SendCustomers = dealerPrefs.DealerGroup.CreateEntry<bool>(name + "05_SendCustomers", false, "Daily: Time of today's deal, per customer [false]", (string)null, false, false, (ValueValidator)null, (string)null); dealerPrefs.SendLocations = dealerPrefs.DealerGroup.CreateEntry<bool>(name + "06_SendLocations", false, "Daily: List of locations visited today [false]", (string)null, false, false, (ValueValidator)null, (string)null); dealerPrefs.SendFailures = dealerPrefs.DealerGroup.CreateEntry<bool>(name + "07_SendFailures", true, "Daily: List of failed customers / locations [true]", (string)null, false, false, (ValueValidator)null, (string)null); dealerPrefs.CheckProduct = dealerPrefs.DealerGroup.CreateEntry<EMsg>(name + "08_CheckProduct", EMsg.Silent, "Alert: When product count below threshold [silent]", (string)null, false, false, (ValueValidator)null, (string)null); dealerPrefs.ProductAlert = dealerPrefs.DealerGroup.CreateEntry<float>(name + "09_ProductAlert", 20f, "--Product count (total) to trigger alert [20]", (string)null, false, false, (ValueValidator)null, (string)null); dealerPrefs.CheckCash = dealerPrefs.DealerGroup.CreateEntry<EMsg>(name + "10_CheckCashLevel", EMsg.Disable, "Alert: When cash is above threshold [disable]", (string)null, false, false, (ValueValidator)null, (string)null); dealerPrefs.CashAlert = dealerPrefs.DealerGroup.CreateEntry<float>(name + "11_CashAlert", 50000f, "--Minimum cash amount to trigger alert [50,000]", (string)null, false, false, (ValueValidator)null, (string)null); dealerPrefs.CustomerInjury = dealerPrefs.DealerGroup.CreateEntry<EMsg>(name + "12_CustomerInjury", EMsg.Silent, "Alert: When customer knocked out or dies [silent]", (string)null, false, false, (ValueValidator)null, (string)null); dealerPrefs.DealerInjury = dealerPrefs.DealerGroup.CreateEntry<EMsg>(name + "13_DealerInjury", EMsg.Notify, "Alert: When dealer is knocked out or dies [notify]", (string)null, false, false, (ValueValidator)null, (string)null); dealerPrefs.IsStuckAlert = dealerPrefs.DealerGroup.CreateEntry<EMsg>(name + "14_IsStuckAlert", EMsg.Notify, "Alert: When dealer hasn't moved in too long [notify]", (string)null, false, false, (ValueValidator)null, (string)null); dealerPrefs.IsStuckCount = dealerPrefs.DealerGroup.CreateEntry<int>(name + "15_IsStuckCount", 4, "Failed move checks before sending stuck alert [4]", (string)null, false, false, (ValueValidator)null, (string)null); dealerPrefs.IsStuckRadius = dealerPrefs.DealerGroup.CreateEntry<int>(name + "16_IsStuckRadius", 5, "Min. move distance to not be considered stuck [5]", (string)null, false, false, (ValueValidator)null, (string)null); dealerPrefs.Navigation = dealerPrefs.DealerGroup.CreateEntry<EMsg>(name + "17_Navigation", EMsg.Notify, "Text: When new navigation target set [notify]", (string)null, false, false, (ValueValidator)null, (string)null); dealerPrefs.NavDeltaTime = dealerPrefs.DealerGroup.CreateEntry<int>(name + "18_NavDeltaTime", 5, "Min. time between navigation messages (minutes) [5]", (string)null, false, false, (ValueValidator)null, (string)null); ((MelonPreferences_Entry)dealerPrefs.ShowStarted).Comment = "Send a text message when a new deal is started (default silent)"; ((MelonPreferences_Entry)dealerPrefs.ShowSuccess).Comment = "Send a text message when a deal is successfully completed (default silent)"; ((MelonPreferences_Entry)dealerPrefs.ShowFailure).Comment = "Send a text message when a deal is failed or timed out (default notify)"; ((MelonPreferences_Entry)dealerPrefs.ShowSummary).Comment = "Send a summary each night of deals completed / failed and money made (default true)"; ((MelonPreferences_Entry)dealerPrefs.SendCustomers).Comment = "Send a summary each night time of deal with each customer (default false)"; ((MelonPreferences_Entry)dealerPrefs.SendLocations).Comment = "Send a summary each night of locations visited that day for successful deals (default false)"; ((MelonPreferences_Entry)dealerPrefs.SendFailures).Comment = "Send a summary each night of failed customers / locations (default true)"; ((MelonPreferences_Entry)dealerPrefs.CheckProduct).Comment = "Send a text message when product count (any type) dips below a threshold (default silent)"; ((MelonPreferences_Entry)dealerPrefs.ProductAlert).Comment = "Threshold to send low product alert (total count of product, e.g. brick = 20) (default 20)"; ((MelonPreferences_Entry)dealerPrefs.CheckCash).Comment = "Send a text message when cash on hand is above a threshold (default disable)"; ((MelonPreferences_Entry)dealerPrefs.CashAlert).Comment = "Amount of cash on hand to send cash alert (default 50,000)"; ((MelonPreferences_Entry)dealerPrefs.CustomerInjury).Comment = "Send a text message when a customer is knocked out or is killed (default silent)"; ((MelonPreferences_Entry)dealerPrefs.DealerInjury).Comment = "Send a text message when a dealer is knocked out or is killed (default notify)"; ((MelonPreferences_Entry)dealerPrefs.IsStuckAlert).Comment = "Send a text message when dealer has not moved from one spot for too long (default notify)"; ((MelonPreferences_Entry)dealerPrefs.IsStuckCount).Comment = "Number of failed checks to send stuck alert (every 20 min) (default 4)"; ((MelonPreferences_Entry)dealerPrefs.IsStuckRadius).Comment = "Minimum distance dealer must move to not be considered stuck (default 5)"; ((MelonPreferences_Entry)dealerPrefs.Navigation).Comment = "Send a message when setting a new movement destination (default notify)"; ((MelonPreferences_Entry)dealerPrefs.NavDeltaTime).Comment = "Minimum cooldown minutes between navigation messages in minutes (default 5)"; dealerPrefs.DealerGroup.SetFilePath(ModSaveData.PrefsPath); _dealerSettings[name] = dealerPrefs; return dealerPrefs; } public static void ClearAll() { foreach (DealerPrefs value in _dealerSettings.Values) { value.DealerGroup.Entries.Clear(); value.DealerGroup = null; } _dealerSettings.Clear(); Initialized = false; } private T GetValue<T>(MelonPreferences_Entry<T> local, MelonPreferences_Entry<T> master) { if (!MasterOnly && OverrideMaster.Value) { return local.Value; } return master.Value; } } [Serializable] public class SaleData { public string Product; public string Description; public string Customer; public string Location; public string Window; public string Started; public string Cost; public string Status; public string Date; public float Payment; public int Quantity; public int StartTime; [JsonIgnore] public DealerManager Stats; public SaleData() { } public SaleData(Contract contract, DealerManager stats) { //IL_0089: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Unknown result type (might be due to invalid IL or missing references) //IL_00a3: Unknown result type (might be due to invalid IL or missing references) //IL_0125: Unknown result type (might be due to invalid IL or missing references) Stats = stats; Product = contract.ProductList.entries[0].ProductID; Description = contract.ProductList.GetCommaSeperatedString(); Customer component = ((Component)contract.Customer).GetComponent<Customer>(); object obj; if (component == null) { obj = null; } else { NPC nPC = component.NPC; obj = ((nPC != null) ? nPC.fullName : null); } if (obj == null) { obj = "Unknown"; } Customer = (string)obj; Location = contract.DeliveryLocation.LocationName; EDealWindow window = DealWindowInfo.GetWindow(contract.DeliveryWindow.WindowStartTime); Window = ((object)(EDealWindow)(ref window)).ToString(); Started = TimeManager.Get12HourTime((float)contract.AcceptTime.time, true); Cost = MoneyManager.FormatAmount(contract.Payment * (1f - stats.Dealer.Cut), false, false); Status = "Accepted"; Date = Util.DayDate(); Payment = contract.Payment * (1f - stats.Dealer.Cut); Quantity = contract.ProductList.GetTotalQuantity(); StartTime = contract.AcceptTime.time; } } [Serializable] public class ModSaveData : SaveData { public const string PATHBASE = "UserData\\DealersSendTexts"; public const string DATAFILE = "Data.json"; public const string PREFFILE = "Config.cfg"; private static string _activeSaveDir; private static string _activeSavePath; public Dictionary<string, DealerState> DealerStates = new Dictionary<string, DealerState>(); public HashSet<string> Completed = new HashSet<string>(); public List<Location> Locations = new List<Location>(); public static string DataPath => Path.Combine(_activeSavePath ?? "UserData\\DealersSendTexts", "Data.json"); public static string PrefsPath => Path.Combine(_activeSavePath ?? "UserData\\DealersSendTexts", "Config.cfg"); public static void SaveData(string pathFromSaver) { SetActiveSave(pathFromSaver); MelonLogger.Msg("Attempting to save " + DataPath); if (!Directory.Exists(Path.GetDirectoryName(DataPath))) { Directory.CreateDirectory(DataPath); } ModSaveData modSaveData = new ModSaveData { DealerStates = DealerManager.Dealers.ToDictionary((KeyValuePair<string, DealerManager> kvp) => kvp.Key, (KeyValuePair<string, DealerManager> kvp) => kvp.Value.State), Completed = ContractManager.Completed, Locations = LocationManager.All() }; MelonLogger.Msg("Serializing:"); try { string contents = JsonConvert.SerializeObject((object)modSaveData, (Formatting)1); File.WriteAllText(DataPath, contents); } catch (Exception arg) { MelonLogger.Error($"[DealersSendTexts] Failed to save to {DataPath}: {arg}"); } MelonLogger.Msg("[DealersSendTexts] Successfully saved " + DataPath); MelonLogger.Msg(Application.version); } public static void LoadData(string pathFromLoader) { SetActiveSave(pathFromLoader); if (!File.Exists(DataPath)) { DealerText.ClearAll(); return; } try { MelonLogger.Msg("Loading " + DataPath + ":"); ModSaveData modSaveData = JsonConvert.DeserializeObject<ModSaveData>(File.ReadAllText(DataPath)); ContractManager.Completed = modSaveData.Completed; foreach (Location location in modSaveData.Locations) { LocationManager.Add(location); } foreach (KeyValuePair<string, DealerState> dealerState in modSaveData.DealerStates) { if (DealerManager.Dealers.TryGetValue(dealerState.Key, out var value)) { value.State = dealerState.Value; } else { DealerManager.StateCache.Add(dealerState.Key, dealerState.Value); } } } catch (Exception arg) { MelonLogger.Error($"[DealersSendTexts] Failed to load from {DataPath}: {arg}"); } MelonLogger.Msg("[DealersSendTexts] Successfully loaded " + DataPath); } private static void SetActiveSave(string gamePath) { string text = gamePath; if (string.IsNullOrEmpty(text)) { text = Singleton<SaveManager>.Instance.IndividualSavesContainerPath; } string text2 = (Directory.Exists(text) ? text : Path.GetDirectoryName(text)); _activeSaveDir = (string.IsNullOrEmpty(text2) ? "Unknown Save" : new DirectoryInfo(text2).Name); _activeSavePath = Path.Combine("UserData\\DealersSendTexts", _activeSaveDir); Directory.CreateDirectory(_activeSavePath); } } public class Location { public SerialVector3 Position; public string Name; public string Region; public int StartedCount; public int SuccessCount; public int FailureCount; public Dictionary<string, int> CustomerCount { get; } = new Dictionary<string, int>(); public Dictionary<string, int> DealerCount { get; } = new Dictionary<string, int>(); public void RecordStarted() { StartedCount++; } public void RecordSuccess() { SuccessCount++; } public void RecordFailure() { FailureCount++; } public void RecordCustomer(string name) { CustomerCount[name] = ((!CustomerCount.TryGetValue(name, out var value)) ? 1 : (value + 1)); } public void RecordDealer(string name) { DealerCount[name] = ((!DealerCount.TryGetValue(name, out var value)) ? 1 : (value + 1)); } public float DistanceTo(Vector3 location) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) return Vector3.Distance(location, Position.ToVector3()); } } public static class LocationManager { private static readonly List<Location> Locations = new List<Location>(); public static void Register(string name, Vector3 position) { //IL_003c: Unknown result type (might be due to invalid IL or missing references) if (!Locations.Any((Location l) => l.Name == name)) { Locations.Add(new Location { Name = name, Position = new SerialVector3(position) }); } } public static void Add(Location location) { if (!Locations.Contains(location)) { Locations.Add(location); } } public static Location GetNearest(Vector3 position) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) return Locations.OrderBy((Location l) => l.DistanceTo(position)).FirstOrDefault(); } public static Location GetNearest(Vector3 position, out float distance) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) Location nearest = GetNearest(position); distance = Vector3.Distance(nearest.Position.ToVector3(), position); return nearest; } public static string Describe(Vector3 position, string noDistPrefix = "to") { //IL_0000: Unknown result type (might be due to invalid IL or missing references) float distance; Location nearest = GetNearest(position, out distance); if (!(distance > 1.5f)) { return noDistPrefix + " " + nearest.Name; } return $"{distance:#.#} meters from {nearest.Name}"; } public static List<Location> All() { return Locations; } public static void ClearAll() { foreach (Location location in Locations) { location.CustomerCount.Clear(); location.DealerCount.Clear(); } Locations.Clear(); } } public enum EIcon { None = -1, Started, Success, Failure, Summary, Customer, Location, ProdAlert, HurtAlert, KnockOut, HasDied, Navigate } public static class MessageManager { public static string Create(EContract state, Contract contract, SaleData sale) { //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_00fe: Unknown result type (might be due to invalid IL or missing references) //IL_011c: Unknown result type (might be due to invalid IL or missing references) //IL_0121: Unknown result type (might be due to invalid IL or missing references) //IL_025a: Unknown result type (might be due to invalid IL or missing references) Vector3 position = ((Component)contract.DeliveryLocation.CustomerStandPoint).transform.position; Vector3 position2 = ((Component)contract.Dealer).transform.position; string text = Vector3.Distance(position, position2).ToString("#.#"); int num = contract.Dealer.ActiveContracts.Count - 1; switch (state) { case EContract.Started: { string text3 = Util.Prefix(sale.Location); return sale.Customer + " at " + sale.Started + ": " + sale.Description + " for " + sale.Cost + ", " + text3 + " (" + text + " meters), " + sale.Window + "."; } case EContract.Success: { int num3 = TimeManager.AddMinutesTo24HourTime(contract.DeliveryWindow.WindowStartTime, 60); GameDateTime val = default(GameDateTime); ((GameDateTime)(ref val))..ctor(contract.AcceptTime.elapsedDays, num3); float num4 = 0f; if (NetworkSingleton<TimeManager>.Instance.IsCurrentDateWithinRange(contract.AcceptTime, val)) { num4 = sale.Payment * 0.1f; sale.Payment += num4; sale.Cost = MoneyManager.FormatAmount(sale.Payment, false, false); } string text4 = Util.HoursMins(Util.TimeDiff(sale.StartTime, Util.IntTime())); string text5 = ((num4 > 0f) ? (" (" + MoneyManager.FormatAmount(num4, false, true) + " bonus incl.)") : ""); return $"Completed deal for {sale.Customer} in {text4}, made {sale.Cost}{text5}; {num} active deals"; } case EContract.Failure: { string text2 = "Failed deal: "; int num2 = 5; text2 = (sale.Stats.IsDealerHurt(out var status) ? (text2 + "Dealer " + status) : (sale.Stats.IsCustomerHurt(sale.Customer, out status) ? (text2 + "Customer " + status) : (Pathing.IsStuck(sale.Stats.Dealer) ? (text2 + "Stuck " + LocationManager.Describe(((Component)sale.Stats.Dealer).transform.position, "at")) : ((Util.TimeDiff(contract.DeliveryWindow.WindowEndTime, Util.IntTime(), is24hour: false) < num2) ? (text2 + "Ran out of time") : ((Math.Abs(Util.IntTime() - 700) >= num2) ? (text2 + "Unknown reason") : (text2 + "Went to bed too early")))))); return $"{text2} for {sale.Customer} from {text} meters, lost potential {sale.Cost}; {num} active deals."; } default: return ""; } } public static void Send(Dealer dealer, EIcon icon, string message, bool notify) { //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Expected O, but got Unknown if (((NPC)dealer).MSGConversation != null) { Message val = new Message(GetIcon(icon) + message, (ESenderType)1, false, -1); ((NPC)dealer).MSGConversation.SendMessage(val, notify, false); } } private static string GetIcon(EIcon state) { return state switch { EIcon.Started => " <color=blue><b>▶</b></color> ", EIcon.Success => " <color=green><b>✔</b></color> ", EIcon.Failure => " <color=red><b>✖</b></color> ", EIcon.Summary => " <color=brown><b>☰</b></color> ", EIcon.Customer => " <color=darkblue><b>✪</b></color> ", EIcon.Location => " <color=teal><b>✦</b></color> ", EIcon.ProdAlert => " <color=purple><b>⁉</b></color> ", EIcon.HurtAlert => " <color=maroon><b>‼</b></color> ", EIcon.KnockOut => " <color=orange><b>⁂</b></color> ", EIcon.HasDied => " <color=red><b>☠</b></color> ", EIcon.Navigate => " <color=blue>∇</color> ", _ => "", }; } } [Serializable] public class DealerState { public string MostRecent = "Never"; public int SaleCount; public Dictionary<string, string> TodaysSales = new Dictionary<string, string>(); public Dictionary<string, string> RecentSale = new Dictionary<string, string>(); public Dictionary<string, int> Products = new Dictionary<string, int>(); public HashSet<FailureKey> Failures = new HashSet<FailureKey>(); public List<SaleData> DailySales = new List<SaleData>(); public List<SaleData> TotalSales = new List<SaleData>(); public void ClearAll(bool daily = false) { DailySales.Clear(); TodaysSales.Clear(); Failures.Clear(); if (!daily) { Products.Clear(); RecentSale.Clear(); TotalSales.Clear(); } } } [Serializable] public struct SerialVector3 { public float x; public float y; public float z; public SerialVector3(float x, float y, float z) { this.x = x; this.y = y; this.z = z; } public SerialVector3(Vector3 v) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) x = v.x; y = v.y; z = v.z; } public readonly Vector3 ToVector3() { //IL_0012: Unknown result type (might be due to invalid IL or missing references) return new Vector3(x, y, z); } } [Serializable] public struct FailureKey { public string Customer; public string Location; public FailureKey(string customer, string location) { Customer = customer; Location = location; } public override readonly bool Equals(object obj) { if (obj is FailureKey failureKey && Customer == failureKey.Customer) { return Location == failureKey.Location; } return false; } public override readonly int GetHashCode() { return Customer.GetHashCode() ^ Location.GetHashCode(); } } [HarmonyPatch(typeof(Contract), "InitializeContract")] public class ContractInitializeContractPatch { private static void Postfix(Contract __instance) { if (__instance != null && __instance.Dealer != null && !DealerManager.IsCartel(__instance.Dealer)) { ContractManager.ProcessContract(__instance, EContract.Started); } } } [HarmonyPatch(typeof(Contract), "End")] public class ContractEndPatch { private static void Prefix(Contract __instance) { if (__instance != null && __instance.Dealer != null && !DealerManager.IsCartel(__instance.Dealer)) { ContractManager.ProcessContract(__instance, EContract.Failure); } } } [HarmonyPatch(typeof(Contract), "Complete")] public class ContractCompletePatch { private static void Prefix(Contract __instance) { if (__instance != null && __instance.Dealer != null && !DealerManager.IsCartel(__instance.Dealer)) { HashSet<string> completed = ContractManager.Completed; Customer component = ((Component)__instance.Customer).GetComponent<Customer>(); object obj; if (component == null) { obj = null; } else { NPC nPC = component.NPC; obj = ((nPC != null) ? nPC.fullName : null); } if (obj == null) { obj = "Unknown"; } completed.Add((string)obj); } } } [HarmonyPatch(typeof(Dealer), "Start")] public class DealerStartPatch { private static void Postfix(Dealer __instance) { if (__instance != null && !DealerManager.IsCartel(__instance)) { DealerPrefs.Prefs(((NPC)__instance).FirstName); } } } [HarmonyPatch(typeof(LoadManager), "StartGame")] public class LoadManagerStartGamePatch { private static void Postfix(SaveInfo info) { ModSaveData.LoadData(info.SavePath); } } [HarmonyPatch(typeof(NPC), "Start")] public class NPCStartPatch { private static void Postfix(NPC __instance) { if (__instance.Health != null) { __instance.Health.onDie.AddListener(UnityAction.op_Implicit((Action)delegate { AlertManager.NPCInjuryAlert(__instance, EIcon.HasDied); })); __instance.Health.onKnockedOut.AddListener(UnityAction.op_Implicit((Action)delegate { AlertManager.NPCInjuryAlert(__instance, EIcon.KnockOut); })); } } } [HarmonyPatch(typeof(NPCMovement))] public class NPCMovementSetDestinationPatch { private static MethodBase TargetMethod() { return AccessTools.GetDeclaredMethods(typeof(NPCMovement)).FirstOrDefault((MethodInfo method) => method.Name == "SetDestination" && method.GetParameters().Length == 5); } private static void Postfix(NPCMovement __instance, Vector3 pos, Action<WalkResult> callback, bool interruptExistingCallback, float successThreshold, float cacheMaxDistSqr) { //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0056: 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) if (__instance == null || __instance.npc == null) { return; } NPC npc = __instance.npc; Dealer val = (Dealer)(object)((npc is Dealer) ? npc : null); if (val != null && !DealerManager.IsCartel(val)) { EMsg navigation = DealerPrefs.Prefs(((NPC)val).FirstName).GetNavigation(); if (navigation != EMsg.Disable && DealerManager.CheckPing(val)) { DealerManager.SetPing(val, Util.AbsTime()); float num = Vector3.Distance(((Component)val).transform.position, pos); string arg = LocationManager.Describe(pos); string message = $"Headed {arg}, {num:#.#} meters away."; MessageManager.Send(val, EIcon.Navigate, message, navigation == EMsg.Notify); } } } } [HarmonyPatch(typeof(Player), "SleepStart")] public class PlayerSleepStartPatch { private static void Prefix() { ContractManager.SendSummary(); } } [HarmonyPatch(typeof(SaveManager), "Save", new Type[] { typeof(string) })] public class SaveManagerSavePatch { private static void Prefix(string saveFolderPath) { ModSaveData.SaveData(saveFolderPath); } } [HarmonyPatch(typeof(TimeManager), "Update")] public class TimeManagerUpdatePatch { [HarmonyPostfix] public static void PostFix() { int num = Util.AbsTime() % 20; if (num == 0 && DealerManager.CheckStuck) { DealerManager.CheckStuck = false; foreach (DealerManager value in DealerManager.Dealers.Values) { Pathing.CheckStuck(value); } } if (num == 1) { DealerManager.CheckStuck = true; } } } public class DealerManager { public static Dictionary<string, DealerManager> Dealers = new Dictionary<string, DealerManager>(); public static Dictionary<string, DealerState> StateCache = new Dictionary<string, DealerState>(); public static bool CheckStuck; public Dictionary<string, NPC> Customers = new Dictionary<string, NPC>(); public Dealer Dealer; public DealerState State; public Vector3 Home; private int LastPing = -1; public DealerManager(Dealer dealer) { //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_0069: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Unknown result type (might be due to invalid IL or missing references) Dealer = dealer; State = (StateCache.TryGetValue(((NPC)dealer).fullName, out var value) ? value : new DealerState()); NPCEnterableBuilding home = dealer.Home; Transform transform = ((Component)dealer).transform; Home = ((Component)home.GetClosestDoor((transform != null) ? transform.position : Vector3.zero, true)).transform.position; LocationManager.Register(dealer.HomeName, Home); Enumerator<Customer> enumerator = dealer.AssignedCustomers.GetEnumerator(); while (enumerator.MoveNext()) { Customer current = enumerator.Current; string fullName = current.NPC.fullName; if (!State.TodaysSales.ContainsKey(fullName)) { State.TodaysSales[fullName] = "Never"; } if (!State.RecentSale.ContainsKey(fullName)) { State.RecentSale[fullName] = "Never"; } AddCustomer(fullName, current.NPC); } } public static DealerManager GetStats(Dealer dealer) { if (!Dealers.TryGetValue(((NPC)dealer).fullName, out var value)) { value = new DealerManager(dealer); Dealers.Add(((NPC)dealer).fullName, value); } return value; } public void RefreshInventory() { List<ItemSlot> allSlots = Dealer.GetAllSlots(); if (allSlots == null || allSlots.Count == 0) { return; } List<string> list = new List<string>(); Dictionary<string, int> dictionary = new Dictionary<string, int>(); Enumerator<ItemSlot> enumerator = allSlots.GetEnumerator(); while (enumerator.MoveNext()) { ItemSlot current = enumerator.Current; if (current.Quantity != 0) { int num = current.Quantity; ProductItemInstance val = ((Il2CppObjectBase)current.ItemInstance).TryCast<ProductItemInstance>(); if (val != null) { num *= val.Amount; } if (list.Contains(current.ItemInstance.ID)) { dictionary[current.ItemInstance.ID] += num; continue; } list.Add(current.ItemInstance.ID); dictionary.Add(current.ItemInstance.ID, num); } } State.Products = dictionary; } public void AddCustomer(string fullName, NPC npc) { if (!Customers.ContainsKey(fullName)) { Customers.Add(fullName, npc); } } public bool IsCustomerHurt(string name, out string status) { status = ""; if (!Customers.TryGetValue(name, out var value)) { return false; } if (value.Health.IsDead) { status = $"<color=red>(Dead {value.Health.DaysPassedSinceDeath} days)</color>"; } if (value.Health.IsKnockedOut) { status = "<color=orange>(Knocked Out)</color>"; } return !string.IsNullOrEmpty(status); } public bool IsDealerHurt(out string status) { status = ""; if (((NPC)Dealer).Health.IsDead) { status = $"<color=red>Dead ({((NPC)Dealer).Health.DaysPassedSinceDeath} days)</color>"; } if (((NPC)Dealer).Health.IsKnockedOut) { status = "<color=orange>(Knocked Out)</color>"; } return !string.IsNullOrEmpty(status); } public static bool IsCartel(Dealer dealer) { if (Application.version.CompareTo("0.4.0") < 0) { return false; } return Type.GetType("CartelDealer")?.IsInstanceOfType(dealer) ?? false; } public static bool CheckPing(Dealer dealer) { int iNavDeltaTime = DealerPrefs.Prefs(((NPC)dealer).FirstName).GetINavDeltaTime(); if (iNavDeltaTime > -1) { return Util.AbsTime() - GetStats(dealer).LastPing > iNavDeltaTime; } return false; } public static void SetPing(Dealer dealer, int ping) { GetStats(dealer).LastPing = ping; } public static void ClearAll() { foreach (DealerManager value in Dealers.Values) { value.Customers.Clear(); value.State.ClearAll(); } Dealers.Clear(); StateCache.Clear(); } public Vector3 DistanceToHome() { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) return Dealer.Home.GetClosestDoor(((Component)Dealer).transform.position, true).AccessPoint.position; } } public enum EMsg { Notify, Silent, Disable } public class DealerText : MelonMod { public const string ModName = "Dealers Send Texts"; public const string Version = "2.1.0"; public const string ModDesc = "Dealers text updates on deals and daily summary in Schedule One"; public override void OnSceneWasLoaded(int buildIndex, string sceneName) { if (sceneName.Equals("main", StringComparison.OrdinalIgnoreCase)) { DealerPrefs.Initialize(); } } public override void OnSceneWasUnloaded(int buildIndex, string sceneName) { if (sceneName.Equals("main", StringComparison.OrdinalIgnoreCase)) { ClearAll(); } } public static void ClearAll() { ContractManager.ClearAll(); LocationManager.ClearAll(); DealerManager.ClearAll(); DealerPrefs.ClearAll(); Pathing.ClearAll(); } } public static class Pathing { private const int MINTIME = 5; private const int MAXHISTORY = 10; private static readonly Dictionary<string, Queue<(Vector3, int)>> PositionHistory = new Dictionary<string, Queue<(Vector3, int)>>(); public static void RecordPosition(Dealer dealer) { //IL_0063: Unknown result type (might be due to invalid IL or missing references) if (dealer != null && dealer.ActiveContracts.Count != 0) { string fullName = ((NPC)dealer).fullName; int num = Util.AbsTime(); if (!PositionHistory.TryGetValue(fullName, out var value)) { value = (PositionHistory[fullName] = new Queue<(Vector3, int)>()); } if (value.Count == 0 || Math.Abs(value.Peek().Item2 - num) >= 5) { value.Enqueue((((Component)dealer).transform.position, num)); } if (value.Count > 10) { value.Dequeue(); } } } public static bool IsStuck(Dealer dealer) { //IL_004f: 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_0065: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Unknown result type (might be due to invalid IL or missing references) if (dealer == null || !PositionHistory.TryGetValue(((NPC)dealer).fullName, out var value)) { return false; } DealerPrefs dealerPrefs = DealerPrefs.Prefs(((NPC)dealer).FirstName); int isStuckCount = dealerPrefs.GetIsStuckCount(); if (value.Count < isStuckCount) { return false; } (Vector3, int)[] array = value.Reverse().Take(isStuckCount).ToArray(); Vector3 item = array[0].Item1; (Vector3, int)[] array2 = array; for (int i = 0; i < array2.Length; i++) { if (Vector3.Distance(array2[i].Item1, item) > (float)dealerPrefs.GetIsStuckRadius()) { return false; } } return true; } public static void CheckStuck(DealerManager stats) { //IL_0047: 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) Dealer dealer = stats.Dealer; RecordPosition(dealer); if (IsStuck(dealer) && !stats.IsDealerHurt(out var _) && dealer.ActiveContracts.Count != 0) { bool notify = DealerPrefs.Prefs(((NPC)dealer).FirstName).GetIsStuckAlert() == EMsg.Notify; string arg = LocationManager.Describe(((Component)dealer).transform.position, "at"); string message = $"I may be stuck in {((NPC)dealer).Region} {arg}. Most recent deal was {stats.State.MostRecent}."; MessageManager.Send(dealer, EIcon.HurtAlert, message, notify); } } public static void ClearAll() { PositionHistory.Clear(); } } public enum EContract { Started, Success, Failure } public static class ContractManager { public static HashSet<string> Completed = new HashSet<string>(); public static void ProcessContract(Contract contract, EContract state) { //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Unknown result type (might be due to invalid IL or missing references) if (contract != null && contract.Dealer != null) { DealerPrefs prefs = DealerPrefs.Prefs(((NPC)contract.Dealer).FirstName); DealerManager stats = DealerManager.GetStats(contract.Dealer); SaleData saleData = new SaleData(contract, stats); NPC nPC = ((Component)contract.Customer).GetComponent<Customer>().NPC; Vector3 position = contract.DeliveryLocation.CustomerStandPoint.position; stats.AddCustomer(nPC.fullName, nPC); LocationManager.Register(saleData.Location, position); Location nearest = LocationManager.GetNearest(position); if (state == EContract.Failure && Completed.Contains(saleData.Customer)) { state = EContract.Success; } if (state == EContract.Started) { ContractStarted(contract, saleData, nearest, prefs); } if (state == EContract.Success) { ContractSuccess(contract, saleData, nearest, prefs); } if (state == EContract.Failure) { ContractFailure(contract, saleData, nearest, prefs); } } } public static void SendSummary() { Dictionary<string, DealerManager> dealers = DealerManager.Dealers; if (dealers == null || dealers.Count == 0) { return; } foreach (DealerManager value in dealers.Values) { AlertManager.DailySummary(value); } } private static void ContractStarted(Contract contract, SaleData sale, Location location, DealerPrefs prefs) { location.RecordStarted(); location.RecordCustomer(sale.Customer); location.RecordDealer(((NPC)contract.Dealer).fullName); sale.Stats.State.TotalSales.Add(sale); sale.Stats.State.SaleCount++; string message = MessageManager.Create(EContract.Started, contract, sale); if (prefs.GetShowStarted() != EMsg.Disable) { MessageManager.Send(contract.Dealer, EIcon.Started, message, prefs.GetShowStarted() == EMsg.Notify); } } private static void ContractSuccess(Contract contract, SaleData sale, Location location, DealerPrefs prefs) { sale.Status = "Success"; DealerState state = sale.Stats.State; Completed.Remove(sale.Customer); Dictionary<string, string> todaysSales = state.TodaysSales; string customer = sale.Customer; string value = (state.RecentSale[sale.Customer] = (state.MostRecent = Util.Time())); todaysSales[customer] = value; state.DailySales.Add(sale); location.RecordSuccess(); string message = MessageManager.Create(EContract.Success, contract, sale); if (prefs.GetShowSuccess() != EMsg.Disable) { MessageManager.Send(contract.Dealer, EIcon.Success, message, prefs.GetShowSuccess() == EMsg.Notify); } AlertManager.CheckProductAlert(contract.Dealer); } private static void ContractFailure(Contract contract, SaleData sale, Location location, DealerPrefs prefs) { sale.Status = "Failure"; sale.Stats.State.Failures.Add(new FailureKey(sale.Customer, sale.Location)); location.RecordFailure(); string message = MessageManager.Create(EContract.Failure, contract, sale); if (prefs.GetShowFailure() != EMsg.Disable) { MessageManager.Send(contract.Dealer, EIcon.Failure, message, prefs.GetShowFailure() == EMsg.Notify); } } public static void ClearAll() { Completed.Clear(); } } public static class Util { public static int TimeDiff(int start, int end, bool is24hour = true) { start = (is24hour ? TimeManager.GetMinSumFrom24HourTime(start) : start); end = (is24hour ? TimeManager.GetMinSumFrom24HourTime(end) : end); int num = end - start; if (num < 0) { num += 1440; } return num; } public static string GetName(string input) { ItemDefinition item = Registry.GetItem(input); string text = ((item != null) ? item.Name : null); if (string.IsNullOrEmpty(text)) { return input; } return text; } public static string Prefix(string input) { if (input.ToLower().StartsWith("outside") || input.ToLower().StartsWith("next") || input.ToLower().StartsWith("under") || input.ToLower().StartsWith("behind") || input.ToLower().StartsWith("in ")) { return input; } return "at the " + input; } public static string DayDate() { //IL_000a: Unknown result type (might be due to invalid IL or missing references) return $"{NetworkSingleton<TimeManager>.Instance.CurrentDay}, Day {NetworkSingleton<TimeManager>.Instance.ElapsedDays}"; } public static int IntTime() { return NetworkSingleton<TimeManager>.Instance.CurrentTime; } public static int AbsTime() { return NetworkSingleton<TimeManager>.Instance.GetTotalMinSum(); } public static string Time() { return TimeManager.Get12HourTime((float)IntTime(), true); } public static string HoursMins(int time) { return ((time / 60 > 0) ? (time / 60 + "hr ") : "") + time % 60 + "min"; } }
Alt-DealersSendTexts-2.2.0.dll
Decompiled a week agousing System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using DealersSendTexts; using HarmonyLib; using MelonLoader; using MelonLoader.Preferences; using Newtonsoft.Json; using ScheduleOne; using ScheduleOne.DevUtilities; using ScheduleOne.Economy; using ScheduleOne.GameTime; using ScheduleOne.ItemFramework; using ScheduleOne.Map; using ScheduleOne.Messaging; using ScheduleOne.Money; using ScheduleOne.NPCs; using ScheduleOne.Persistence; using ScheduleOne.Persistence.Datas; using ScheduleOne.PlayerScripts; using ScheduleOne.Product; using ScheduleOne.Quests; using UnityEngine; using UnityEngine.Events; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: MelonInfo(typeof(DealerText), "Dealers Send Texts", "2.1.0", "GuysWeForgotDre", null)] [assembly: MelonGame("TVGS", "Schedule I")] [assembly: AssemblyTitle("Dealers Send Texts")] [assembly: AssemblyDescription("Dealers text updates on deals and daily summary in Schedule One")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("Dealers Send Texts")] [assembly: AssemblyCopyright("Copyright © 2025")] [assembly: AssemblyTrademark("")] [assembly: ComVisible(false)] [assembly: Guid("51af033c-2fba-4fd9-950e-a5fb45e211c4")] [assembly: AssemblyFileVersion("2.1.0.0")] [assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")] [assembly: AssemblyVersion("2.1.0.0")] namespace DealersSendTexts; public static class AlertManager { public static void DailySummary(DealerManager stats) { if (!((NPC)stats.Dealer).RelationData.Unlocked) { return; } DealerPrefs dealerPrefs = DealerPrefs.Prefs(((NPC)stats.Dealer).FirstName); DealerState state = stats.State; Dictionary<string, int> dictionary = new Dictionary<string, int>(); HashSet<string> hashSet = new HashSet<string>(); float num = 0f; foreach (SaleData dailySale in state.DailySales) { num += dailySale.Payment; if (!dictionary.ContainsKey(dailySale.Product)) { dictionary.Add(dailySale.Product, dailySale.Quantity); } else { dictionary[dailySale.Product] += dailySale.Quantity; } hashSet.Add(dailySale.Location); } if (dealerPrefs.GetShowSummary()) { string text = MoneyManager.FormatAmount(num, false, true); string text2 = ""; foreach (string key in dictionary.Keys) { text2 += $"\n{Util.GetName(key)} ({dictionary[key]})"; } string text3 = $"DAILY SUMMARY\nDid {state.DailySales.Count} deals in {hashSet.Count} locations; {state.Failures.Count} failed.\nMade {text} from:{text2}"; if (stats.IsDealerHurt(out var status)) { text3 = text3 + "\nDealer " + status; } MessageManager.Send(stats.Dealer, EIcon.Summary, text3, notify: false); } if (dealerPrefs.GetSendCustomers()) { string text4 = $"CUSTOMER LOG ({state.TodaysSales.Count})"; foreach (string key2 in state.TodaysSales.Keys) { stats.IsCustomerHurt(key2, out var status2); text4 = text4 + "\n" + key2 + ": " + state.TodaysSales[key2] + " " + status2; } MessageManager.Send(stats.Dealer, EIcon.Customer, text4, notify: false); } if (dealerPrefs.GetSendLocations()) { string text5 = $"LOCATIONS ({hashSet.Count})"; foreach (string item in hashSet) { text5 = text5 + "\n" + item; } MessageManager.Send(stats.Dealer, EIcon.Location, text5, notify: false); } if (dealerPrefs.GetSendFailures() && state.Failures.Count > 0) { string text6 = $"FAILURES ({state.Failures.Count})"; foreach (FailureKey failure in state.Failures) { text6 = text6 + "\n" + failure.Customer + " " + Util.Prefix(failure.Location); } MessageManager.Send(stats.Dealer, EIcon.Failure, text6, notify: false); } state.ClearAll(daily: true); } public static void NPCInjuryAlert(NPC npc, EIcon type) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_008a: Unknown result type (might be due to invalid IL or missing references) //IL_015b: Unknown result type (might be due to invalid IL or missing references) string text = LocationManager.Describe(((Component)npc).transform.position, "at"); string text2 = Vector3.Distance(Player.Local.PlayerBasePosition, ((Component)npc).transform.position).ToString("#.#"); string text3 = type switch { EIcon.HasDied => "died", EIcon.KnockOut => "been knocked out", _ => "an unknown alert", }; Dealer val = (Dealer)(object)((npc is Dealer) ? npc : null); if (val != null) { EMsg dealerInjury = DealerPrefs.Prefs(((NPC)val).FirstName).GetDealerInjury(); string message = $"I have {text3} in {npc.Region} {text}, {text2} meters from you."; if (dealerInjury != EMsg.Disable) { MessageManager.Send(val, type, message, dealerInjury == EMsg.Notify); } return; } foreach (DealerManager value2 in DealerManager.Dealers.Values) { foreach (string key in value2.Customers.Keys) { if (key == npc.fullName) { EMsg customerInjury = DealerPrefs.Prefs(((NPC)value2.Dealer).FirstName).GetCustomerInjury(); if (!value2.State.RecentSale.TryGetValue(key, out var value)) { value = "Never"; } string message2 = $"{npc.fullName} has {text3} in {npc.Region} {text}, {text2} meters from you. Last sale was {value}."; if (customerInjury != EMsg.Disable) { MessageManager.Send(value2.Dealer, type, message2, customerInjury == EMsg.Notify); } } } } } public static void CheckProductAlert(Dealer dealer) { //IL_002c: 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_0056: Unknown result type (might be due to invalid IL or missing references) DealerPrefs dealerPrefs = DealerPrefs.Prefs(((NPC)dealer).FirstName); DealerManager stats = DealerManager.GetStats(dealer); stats.RefreshInventory(); string text = MoneyManager.FormatAmount(dealer.Cash, false, false); string text2 = Vector3.Distance(Player.Local.PlayerBasePosition, ((Component)dealer).transform.position).ToString("#.#"); string text3 = LocationManager.Describe(((Component)dealer).transform.position, "at"); float num = 0f; foreach (string key in stats.State.Products.Keys) { num += (float)stats.State.Products[key]; } if (dealerPrefs.GetCheckCash() != EMsg.Disable && dealer.Cash >= dealerPrefs.GetCashAlert()) { MessageManager.Send(dealer, EIcon.ProdAlert, $"Cash threshold reached: {text}.\n{num} products left; I'm {text2} meters from you, {text3}.", dealerPrefs.GetCheckCash() == EMsg.Notify); } if (dealerPrefs.GetCheckProduct() != EMsg.Disable && num <= dealerPrefs.GetProductAlert()) { MessageManager.Send(dealer, EIcon.ProdAlert, $"Product threshold reached: {num}.\n{text} cash; I'm {text2} meters from you, {text3}.", dealerPrefs.GetCheckProduct() == EMsg.Notify); } } } public class DealerPrefs { public static DealerPrefs Master; private static bool MasterOnly; private static bool Initialized = false; private const string MasterName = "_Master"; private static readonly Dictionary<string, DealerPrefs> _dealerSettings = new Dictionary<string, DealerPrefs>(); private MelonPreferences_Category DealerGroup; private MelonPreferences_Entry<bool> OverrideMaster; private MelonPreferences_Entry<bool> CreateDealers; private MelonPreferences_Entry<EMsg> ShowStarted; private MelonPreferences_Entry<EMsg> ShowSuccess; private MelonPreferences_Entry<EMsg> ShowFailure; private MelonPreferences_Entry<bool> ShowSummary; private MelonPreferences_Entry<bool> SendCustomers; private MelonPreferences_Entry<bool> SendLocations; private MelonPreferences_Entry<bool> SendFailures; private MelonPreferences_Entry<EMsg> CheckProduct; private MelonPreferences_Entry<float> ProductAlert; private MelonPreferences_Entry<EMsg> CheckCash; private MelonPreferences_Entry<float> CashAlert; private MelonPreferences_Entry<EMsg> CustomerInjury; private MelonPreferences_Entry<EMsg> DealerInjury; private MelonPreferences_Entry<EMsg> IsStuckAlert; private MelonPreferences_Entry<int> IsStuckCount; private MelonPreferences_Entry<int> IsStuckRadius; private MelonPreferences_Entry<EMsg> Navigation; private MelonPreferences_Entry<int> NavDeltaTime; public static void Initialize() { if (!Initialized) { Master = AddDealer("_Master"); MasterOnly = !Master.CreateDealers.Value; Initialized = true; } } public EMsg GetShowStarted() { return GetValue<EMsg>(ShowStarted, Master.ShowStarted); } public EMsg GetShowSuccess() { return GetValue<EMsg>(ShowSuccess, Master.ShowSuccess); } public EMsg GetShowFailure() { return GetValue<EMsg>(ShowFailure, Master.ShowFailure); } public bool GetShowSummary() { return GetValue<bool>(ShowSummary, Master.ShowSummary); } public bool GetSendCustomers() { return GetValue<bool>(SendCustomers, Master.SendCustomers); } public bool GetSendLocations() { return GetValue<bool>(SendLocations, Master.SendLocations); } public bool GetSendFailures() { return GetValue<bool>(SendFailures, Master.SendFailures); } public EMsg GetCheckProduct() { return GetValue<EMsg>(CheckProduct, Master.CheckProduct); } public float GetProductAlert() { return GetValue<float>(ProductAlert, Master.ProductAlert); } public EMsg GetCheckCash() { return GetValue<EMsg>(CheckCash, Master.CheckCash); } public float GetCashAlert() { return GetValue<float>(CashAlert, Master.CashAlert); } public EMsg GetCustomerInjury() { return GetValue<EMsg>(CustomerInjury, Master.CustomerInjury); } public EMsg GetDealerInjury() { return GetValue<EMsg>(DealerInjury, Master.DealerInjury); } public EMsg GetIsStuckAlert() { return GetValue<EMsg>(IsStuckAlert, Master.IsStuckAlert); } public int GetIsStuckCount() { return GetValue<int>(IsStuckCount, Master.IsStuckCount); } public int GetIsStuckRadius() { return GetValue<int>(IsStuckRadius, Master.IsStuckRadius); } public EMsg GetNavigation() { return GetValue<EMsg>(Navigation, Master.Navigation); } public int GetINavDeltaTime() { return GetValue<int>(NavDeltaTime, Master.NavDeltaTime); } public static DealerPrefs Prefs(string firstName) { if (!MasterOnly) { if (!_dealerSettings.TryGetValue(firstName, out var value)) { return AddDealer(firstName); } return value; } return Master; } private static DealerPrefs AddDealer(string name) { DealerPrefs dealerPrefs = new DealerPrefs { DealerGroup = MelonPreferences.CreateCategory("DealersSendTexts_" + name, name + " Preferences") }; if (name == "_Master") { dealerPrefs.CreateDealers = dealerPrefs.DealerGroup.CreateEntry<bool>(name + "00_CreateDealers", false, "Create settings per dealer [requires restart]", (string)null, false, false, (ValueValidator)null, (string)null); ((MelonPreferences_Entry)dealerPrefs.CreateDealers).Comment = "Create separate settings panel for each dealer. Requires restart. (\"default\" false but will not auto delete if reset)"; } else { dealerPrefs.OverrideMaster = dealerPrefs.DealerGroup.CreateEntry<bool>(name + "00_OverrideMaster", false, "[Override Master]: Use these settings instead [false]", (string)null, false, false, (ValueValidator)null, (string)null); ((MelonPreferences_Entry)dealerPrefs.OverrideMaster).Comment = "Use individual dealer profile settings instead of generic master settings (default false)"; } dealerPrefs.ShowStarted = dealerPrefs.DealerGroup.CreateEntry<EMsg>(name + "01_ShowStarted", EMsg.Silent, "Text: When a new deal is started [default silent]", (string)null, false, false, (ValueValidator)null, (string)null); dealerPrefs.ShowSuccess = dealerPrefs.DealerGroup.CreateEntry<EMsg>(name + "02_ShowSuccess", EMsg.Silent, "Text: When deal successfully completed [silent]", (string)null, false, false, (ValueValidator)null, (string)null); dealerPrefs.ShowFailure = dealerPrefs.DealerGroup.CreateEntry<EMsg>(name + "03_ShowFailure", EMsg.Notify, "Text: When deal failed / timed out [notify]", (string)null, false, false, (ValueValidator)null, (string)null); dealerPrefs.ShowSummary = dealerPrefs.DealerGroup.CreateEntry<bool>(name + "04_ShowSummary", true, "Daily: Summary of deals completed that day [true]", (string)null, false, false, (ValueValidator)null, (string)null); dealerPrefs.SendCustomers = dealerPrefs.DealerGroup.CreateEntry<bool>(name + "05_SendCustomers", false, "Daily: Time of today's deal, per customer [false]", (string)null, false, false, (ValueValidator)null, (string)null); dealerPrefs.SendLocations = dealerPrefs.DealerGroup.CreateEntry<bool>(name + "06_SendLocations", false, "Daily: List of locations visited today [false]", (string)null, false, false, (ValueValidator)null, (string)null); dealerPrefs.SendFailures = dealerPrefs.DealerGroup.CreateEntry<bool>(name + "07_SendFailures", true, "Daily: List of failed customers / locations [true]", (string)null, false, false, (ValueValidator)null, (string)null); dealerPrefs.CheckProduct = dealerPrefs.DealerGroup.CreateEntry<EMsg>(name + "08_CheckProduct", EMsg.Silent, "Alert: When product count below threshold [silent]", (string)null, false, false, (ValueValidator)null, (string)null); dealerPrefs.ProductAlert = dealerPrefs.DealerGroup.CreateEntry<float>(name + "09_ProductAlert", 20f, "--Product count (total) to trigger alert [20]", (string)null, false, false, (ValueValidator)null, (string)null); dealerPrefs.CheckCash = dealerPrefs.DealerGroup.CreateEntry<EMsg>(name + "10_CheckCashLevel", EMsg.Disable, "Alert: When cash is above threshold [disable]", (string)null, false, false, (ValueValidator)null, (string)null); dealerPrefs.CashAlert = dealerPrefs.DealerGroup.CreateEntry<float>(name + "11_CashAlert", 50000f, "--Minimum cash amount to trigger alert [50,000]", (string)null, false, false, (ValueValidator)null, (string)null); dealerPrefs.CustomerInjury = dealerPrefs.DealerGroup.CreateEntry<EMsg>(name + "12_CustomerInjury", EMsg.Silent, "Alert: When customer knocked out or dies [silent]", (string)null, false, false, (ValueValidator)null, (string)null); dealerPrefs.DealerInjury = dealerPrefs.DealerGroup.CreateEntry<EMsg>(name + "13_DealerInjury", EMsg.Notify, "Alert: When dealer is knocked out or dies [notify]", (string)null, false, false, (ValueValidator)null, (string)null); dealerPrefs.IsStuckAlert = dealerPrefs.DealerGroup.CreateEntry<EMsg>(name + "14_IsStuckAlert", EMsg.Notify, "Alert: When dealer hasn't moved in too long [notify]", (string)null, false, false, (ValueValidator)null, (string)null); dealerPrefs.IsStuckCount = dealerPrefs.DealerGroup.CreateEntry<int>(name + "15_IsStuckCount", 4, "Failed move checks before sending stuck alert [4]", (string)null, false, false, (ValueValidator)null, (string)null); dealerPrefs.IsStuckRadius = dealerPrefs.DealerGroup.CreateEntry<int>(name + "16_IsStuckRadius", 5, "Min. move distance to not be considered stuck [5]", (string)null, false, false, (ValueValidator)null, (string)null); dealerPrefs.Navigation = dealerPrefs.DealerGroup.CreateEntry<EMsg>(name + "17_Navigation", EMsg.Notify, "Text: When new navigation target set [notify]", (string)null, false, false, (ValueValidator)null, (string)null); dealerPrefs.NavDeltaTime = dealerPrefs.DealerGroup.CreateEntry<int>(name + "18_NavDeltaTime", 5, "Min. time between navigation messages (minutes) [5]", (string)null, false, false, (ValueValidator)null, (string)null); ((MelonPreferences_Entry)dealerPrefs.ShowStarted).Comment = "Send a text message when a new deal is started (default silent)"; ((MelonPreferences_Entry)dealerPrefs.ShowSuccess).Comment = "Send a text message when a deal is successfully completed (default silent)"; ((MelonPreferences_Entry)dealerPrefs.ShowFailure).Comment = "Send a text message when a deal is failed or timed out (default notify)"; ((MelonPreferences_Entry)dealerPrefs.ShowSummary).Comment = "Send a summary each night of deals completed / failed and money made (default true)"; ((MelonPreferences_Entry)dealerPrefs.SendCustomers).Comment = "Send a summary each night time of deal with each customer (default false)"; ((MelonPreferences_Entry)dealerPrefs.SendLocations).Comment = "Send a summary each night of locations visited that day for successful deals (default false)"; ((MelonPreferences_Entry)dealerPrefs.SendFailures).Comment = "Send a summary each night of failed customers / locations (default true)"; ((MelonPreferences_Entry)dealerPrefs.CheckProduct).Comment = "Send a text message when product count (any type) dips below a threshold (default silent)"; ((MelonPreferences_Entry)dealerPrefs.ProductAlert).Comment = "Threshold to send low product alert (total count of product, e.g. brick = 20) (default 20)"; ((MelonPreferences_Entry)dealerPrefs.CheckCash).Comment = "Send a text message when cash on hand is above a threshold (default disable)"; ((MelonPreferences_Entry)dealerPrefs.CashAlert).Comment = "Amount of cash on hand to send cash alert (default 50,000)"; ((MelonPreferences_Entry)dealerPrefs.CustomerInjury).Comment = "Send a text message when a customer is knocked out or is killed (default silent)"; ((MelonPreferences_Entry)dealerPrefs.DealerInjury).Comment = "Send a text message when a dealer is knocked out or is killed (default notify)"; ((MelonPreferences_Entry)dealerPrefs.IsStuckAlert).Comment = "Send a text message when dealer has not moved from one spot for too long (default notify)"; ((MelonPreferences_Entry)dealerPrefs.IsStuckCount).Comment = "Number of failed checks to send stuck alert (every 20 min) (default 4)"; ((MelonPreferences_Entry)dealerPrefs.IsStuckRadius).Comment = "Minimum distance dealer must move to not be considered stuck (default 5)"; ((MelonPreferences_Entry)dealerPrefs.Navigation).Comment = "Send a message when setting a new movement destination (default notify)"; ((MelonPreferences_Entry)dealerPrefs.NavDeltaTime).Comment = "Minimum cooldown minutes between navigation messages in minutes (default 5)"; dealerPrefs.DealerGroup.SetFilePath(ModSaveData.PrefsPath); _dealerSettings[name] = dealerPrefs; return dealerPrefs; } public static void ClearAll() { foreach (DealerPrefs value in _dealerSettings.Values) { value.DealerGroup.Entries.Clear(); value.DealerGroup = null; } _dealerSettings.Clear(); Initialized = false; } private T GetValue<T>(MelonPreferences_Entry<T> local, MelonPreferences_Entry<T> master) { if (!MasterOnly && OverrideMaster.Value) { return local.Value; } return master.Value; } } [Serializable] public class SaleData { public string Product; public string Description; public string Customer; public string Location; public string Window; public string Started; public string Cost; public string Status; public string Date; public float Payment; public int Quantity; public int StartTime; [JsonIgnore] public DealerManager Stats; public SaleData() { } public SaleData(Contract contract, DealerManager stats) { //IL_0089: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Unknown result type (might be due to invalid IL or missing references) //IL_00a3: Unknown result type (might be due to invalid IL or missing references) //IL_0125: Unknown result type (might be due to invalid IL or missing references) Stats = stats; Product = contract.ProductList.entries[0].ProductID; Description = contract.ProductList.GetCommaSeperatedString(); Customer component = ((Component)contract.Customer).GetComponent<Customer>(); object obj; if (component == null) { obj = null; } else { NPC nPC = component.NPC; obj = ((nPC != null) ? nPC.fullName : null); } if (obj == null) { obj = "Unknown"; } Customer = (string)obj; Location = contract.DeliveryLocation.LocationName; EDealWindow window = DealWindowInfo.GetWindow(contract.DeliveryWindow.WindowStartTime); Window = ((object)(EDealWindow)(ref window)).ToString(); Started = TimeManager.Get12HourTime((float)contract.AcceptTime.time, true); Cost = MoneyManager.FormatAmount(contract.Payment * (1f - stats.Dealer.Cut), false, false); Status = "Accepted"; Date = Util.DayDate(); Payment = contract.Payment * (1f - stats.Dealer.Cut); Quantity = contract.ProductList.GetTotalQuantity(); StartTime = contract.AcceptTime.time; } } [Serializable] public class ModSaveData : SaveData { public const string PATHBASE = "UserData\\DealersSendTexts"; public const string DATAFILE = "Data.json"; public const string PREFFILE = "Config.cfg"; private static string _activeSaveDir; private static string _activeSavePath; public Dictionary<string, DealerState> DealerStates = new Dictionary<string, DealerState>(); public HashSet<string> Completed = new HashSet<string>(); public List<Location> Locations = new List<Location>(); public static string DataPath => Path.Combine(_activeSavePath ?? "UserData\\DealersSendTexts", "Data.json"); public static string PrefsPath => Path.Combine(_activeSavePath ?? "UserData\\DealersSendTexts", "Config.cfg"); public static void SaveData(string pathFromSaver) { SetActiveSave(pathFromSaver); MelonLogger.Msg("Attempting to save " + DataPath); if (!Directory.Exists(Path.GetDirectoryName(DataPath))) { Directory.CreateDirectory(DataPath); } ModSaveData modSaveData = new ModSaveData { DealerStates = DealerManager.Dealers.ToDictionary((KeyValuePair<string, DealerManager> kvp) => kvp.Key, (KeyValuePair<string, DealerManager> kvp) => kvp.Value.State), Completed = ContractManager.Completed, Locations = LocationManager.All() }; MelonLogger.Msg("Serializing:"); try { string contents = JsonConvert.SerializeObject((object)modSaveData, (Formatting)1); File.WriteAllText(DataPath, contents); } catch (Exception arg) { MelonLogger.Error($"[DealersSendTexts] Failed to save to {DataPath}: {arg}"); } MelonLogger.Msg("[DealersSendTexts] Successfully saved " + DataPath); MelonLogger.Msg(Application.version); } public static void LoadData(string pathFromLoader) { SetActiveSave(pathFromLoader); if (!File.Exists(DataPath)) { DealerText.ClearAll(); return; } try { MelonLogger.Msg("Loading " + DataPath + ":"); ModSaveData modSaveData = JsonConvert.DeserializeObject<ModSaveData>(File.ReadAllText(DataPath)); ContractManager.Completed = modSaveData.Completed; foreach (Location location in modSaveData.Locations) { LocationManager.Add(location); } foreach (KeyValuePair<string, DealerState> dealerState in modSaveData.DealerStates) { if (DealerManager.Dealers.TryGetValue(dealerState.Key, out var value)) { value.State = dealerState.Value; } else { DealerManager.StateCache.Add(dealerState.Key, dealerState.Value); } } } catch (Exception arg) { MelonLogger.Error($"[DealersSendTexts] Failed to load from {DataPath}: {arg}"); } MelonLogger.Msg("[DealersSendTexts] Successfully loaded " + DataPath); } private static void SetActiveSave(string gamePath) { string text = gamePath; if (string.IsNullOrEmpty(text)) { text = Singleton<SaveManager>.Instance.IndividualSavesContainerPath; } string text2 = (Directory.Exists(text) ? text : Path.GetDirectoryName(text)); _activeSaveDir = (string.IsNullOrEmpty(text2) ? "Unknown Save" : new DirectoryInfo(text2).Name); _activeSavePath = Path.Combine("UserData\\DealersSendTexts", _activeSaveDir); Directory.CreateDirectory(_activeSavePath); } } public class Location { public SerialVector3 Position; public string Name; public string Region; public int StartedCount; public int SuccessCount; public int FailureCount; public Dictionary<string, int> CustomerCount { get; } = new Dictionary<string, int>(); public Dictionary<string, int> DealerCount { get; } = new Dictionary<string, int>(); public void RecordStarted() { StartedCount++; } public void RecordSuccess() { SuccessCount++; } public void RecordFailure() { FailureCount++; } public void RecordCustomer(string name) { CustomerCount[name] = ((!CustomerCount.TryGetValue(name, out var value)) ? 1 : (value + 1)); } public void RecordDealer(string name) { DealerCount[name] = ((!DealerCount.TryGetValue(name, out var value)) ? 1 : (value + 1)); } public float DistanceTo(Vector3 location) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) return Vector3.Distance(location, Position.ToVector3()); } } public static class LocationManager { private static readonly List<Location> Locations = new List<Location>(); public static void Register(string name, Vector3 position) { //IL_003c: Unknown result type (might be due to invalid IL or missing references) if (!Locations.Any((Location l) => l.Name == name)) { Locations.Add(new Location { Name = name, Position = new SerialVector3(position) }); } } public static void Add(Location location) { if (!Locations.Contains(location)) { Locations.Add(location); } } public static Location GetNearest(Vector3 position) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) return Locations.OrderBy((Location l) => l.DistanceTo(position)).FirstOrDefault(); } public static Location GetNearest(Vector3 position, out float distance) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) Location nearest = GetNearest(position); distance = Vector3.Distance(nearest.Position.ToVector3(), position); return nearest; } public static string Describe(Vector3 position, string noDistPrefix = "to") { //IL_0000: Unknown result type (might be due to invalid IL or missing references) float distance; Location nearest = GetNearest(position, out distance); if (!(distance > 1.5f)) { return noDistPrefix + " " + nearest.Name; } return $"{distance:#.#} meters from {nearest.Name}"; } public static List<Location> All() { return Locations; } public static void ClearAll() { foreach (Location location in Locations) { location.CustomerCount.Clear(); location.DealerCount.Clear(); } Locations.Clear(); } } public enum EIcon { None = -1, Started, Success, Failure, Summary, Customer, Location, ProdAlert, HurtAlert, KnockOut, HasDied, Navigate } public static class MessageManager { public static string Create(EContract state, Contract contract, SaleData sale) { //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_00fe: Unknown result type (might be due to invalid IL or missing references) //IL_011c: Unknown result type (might be due to invalid IL or missing references) //IL_0121: Unknown result type (might be due to invalid IL or missing references) //IL_025a: Unknown result type (might be due to invalid IL or missing references) Vector3 position = ((Component)contract.DeliveryLocation.CustomerStandPoint).transform.position; Vector3 position2 = ((Component)contract.Dealer).transform.position; string text = Vector3.Distance(position, position2).ToString("#.#"); int num = contract.Dealer.ActiveContracts.Count - 1; switch (state) { case EContract.Started: { string text3 = Util.Prefix(sale.Location); return sale.Customer + " at " + sale.Started + ": " + sale.Description + " for " + sale.Cost + ", " + text3 + " (" + text + " meters), " + sale.Window + "."; } case EContract.Success: { int num3 = TimeManager.AddMinutesTo24HourTime(contract.DeliveryWindow.WindowStartTime, 60); GameDateTime val = default(GameDateTime); ((GameDateTime)(ref val))..ctor(contract.AcceptTime.elapsedDays, num3); float num4 = 0f; if (NetworkSingleton<TimeManager>.Instance.IsCurrentDateWithinRange(contract.AcceptTime, val)) { num4 = sale.Payment * 0.1f; sale.Payment += num4; sale.Cost = MoneyManager.FormatAmount(sale.Payment, false, false); } string text4 = Util.HoursMins(Util.TimeDiff(sale.StartTime, Util.IntTime())); string text5 = ((num4 > 0f) ? (" (" + MoneyManager.FormatAmount(num4, false, true) + " bonus incl.)") : ""); return $"Completed deal for {sale.Customer} in {text4}, made {sale.Cost}{text5}; {num} active deals"; } case EContract.Failure: { string text2 = "Failed deal: "; int num2 = 5; text2 = (sale.Stats.IsDealerHurt(out var status) ? (text2 + "Dealer " + status) : (sale.Stats.IsCustomerHurt(sale.Customer, out status) ? (text2 + "Customer " + status) : (Pathing.IsStuck(sale.Stats.Dealer) ? (text2 + "Stuck " + LocationManager.Describe(((Component)sale.Stats.Dealer).transform.position, "at")) : ((Util.TimeDiff(contract.DeliveryWindow.WindowEndTime, Util.IntTime(), is24hour: false) < num2) ? (text2 + "Ran out of time") : ((Math.Abs(Util.IntTime() - 700) >= num2) ? (text2 + "Unknown reason") : (text2 + "Went to bed too early")))))); return $"{text2} for {sale.Customer} from {text} meters, lost potential {sale.Cost}; {num} active deals."; } default: return ""; } } public static void Send(Dealer dealer, EIcon icon, string message, bool notify) { //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Expected O, but got Unknown if (((NPC)dealer).MSGConversation != null) { Message val = new Message(GetIcon(icon) + message, (ESenderType)1, false, -1); ((NPC)dealer).MSGConversation.SendMessage(val, notify, false); } } private static string GetIcon(EIcon state) { return state switch { EIcon.Started => " <color=blue><b>▶</b></color> ", EIcon.Success => " <color=green><b>✔</b></color> ", EIcon.Failure => " <color=red><b>✖</b></color> ", EIcon.Summary => " <color=brown><b>☰</b></color> ", EIcon.Customer => " <color=darkblue><b>✪</b></color> ", EIcon.Location => " <color=teal><b>✦</b></color> ", EIcon.ProdAlert => " <color=purple><b>⁉</b></color> ", EIcon.HurtAlert => " <color=maroon><b>‼</b></color> ", EIcon.KnockOut => " <color=orange><b>⁂</b></color> ", EIcon.HasDied => " <color=red><b>☠</b></color> ", EIcon.Navigate => " <color=blue>∇</color> ", _ => "", }; } } [Serializable] public class DealerState { public string MostRecent = "Never"; public int SaleCount; public Dictionary<string, string> TodaysSales = new Dictionary<string, string>(); public Dictionary<string, string> RecentSale = new Dictionary<string, string>(); public Dictionary<string, int> Products = new Dictionary<string, int>(); public HashSet<FailureKey> Failures = new HashSet<FailureKey>(); public List<SaleData> DailySales = new List<SaleData>(); public List<SaleData> TotalSales = new List<SaleData>(); public void ClearAll(bool daily = false) { DailySales.Clear(); TodaysSales.Clear(); Failures.Clear(); if (!daily) { Products.Clear(); RecentSale.Clear(); TotalSales.Clear(); } } } [Serializable] public struct SerialVector3 { public float x; public float y; public float z; public SerialVector3(float x, float y, float z) { this.x = x; this.y = y; this.z = z; } public SerialVector3(Vector3 v) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) x = v.x; y = v.y; z = v.z; } public readonly Vector3 ToVector3() { //IL_0012: Unknown result type (might be due to invalid IL or missing references) return new Vector3(x, y, z); } } [Serializable] public struct FailureKey { public string Customer; public string Location; public FailureKey(string customer, string location) { Customer = customer; Location = location; } public override readonly bool Equals(object obj) { if (obj is FailureKey failureKey && Customer == failureKey.Customer) { return Location == failureKey.Location; } return false; } public override readonly int GetHashCode() { return Customer.GetHashCode() ^ Location.GetHashCode(); } } [HarmonyPatch(typeof(Contract), "InitializeContract")] public class ContractInitializeContractPatch { private static void Postfix(Contract __instance) { if (__instance != null && __instance.Dealer != null && !DealerManager.IsCartel(__instance.Dealer)) { ContractManager.ProcessContract(__instance, EContract.Started); } } } [HarmonyPatch(typeof(Contract), "End")] public class ContractEndPatch { private static void Prefix(Contract __instance) { if (__instance != null && __instance.Dealer != null && !DealerManager.IsCartel(__instance.Dealer)) { ContractManager.ProcessContract(__instance, EContract.Failure); } } } [HarmonyPatch(typeof(Contract), "Complete")] public class ContractCompletePatch { private static void Prefix(Contract __instance) { if (__instance != null && __instance.Dealer != null && !DealerManager.IsCartel(__instance.Dealer)) { HashSet<string> completed = ContractManager.Completed; Customer component = ((Component)__instance.Customer).GetComponent<Customer>(); object obj; if (component == null) { obj = null; } else { NPC nPC = component.NPC; obj = ((nPC != null) ? nPC.fullName : null); } if (obj == null) { obj = "Unknown"; } completed.Add((string)obj); } } } [HarmonyPatch(typeof(Dealer), "Start")] public class DealerStartPatch { private static void Postfix(Dealer __instance) { if (__instance != null && !DealerManager.IsCartel(__instance)) { DealerPrefs.Prefs(((NPC)__instance).FirstName); } } } [HarmonyPatch(typeof(LoadManager), "StartGame")] public class LoadManagerStartGamePatch { private static void Postfix(SaveInfo info) { ModSaveData.LoadData(info.SavePath); } } [HarmonyPatch(typeof(NPC), "Start")] public class NPCStartPatch { private static void Postfix(NPC __instance) { //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Expected O, but got Unknown //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Expected O, but got Unknown if (__instance.Health != null) { __instance.Health.onDie.AddListener((UnityAction)delegate { AlertManager.NPCInjuryAlert(__instance, EIcon.HasDied); }); __instance.Health.onKnockedOut.AddListener((UnityAction)delegate { AlertManager.NPCInjuryAlert(__instance, EIcon.KnockOut); }); } } } [HarmonyPatch(typeof(NPCMovement))] public class NPCMovementSetDestinationPatch { private static MethodBase TargetMethod() { return AccessTools.GetDeclaredMethods(typeof(NPCMovement)).FirstOrDefault((MethodInfo method) => method.Name == "SetDestination" && method.GetParameters().Length == 5); } private static void Postfix(NPCMovement __instance, Vector3 pos, Action<WalkResult> callback, bool interruptExistingCallback, float successThreshold, float cacheMaxDistSqr) { //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_0077: Unknown result type (might be due to invalid IL or missing references) if (__instance == null) { return; } FieldInfo fieldInfo = AccessTools.Field(typeof(NPCMovement), "npc"); if (!(fieldInfo != null)) { return; } object? value = fieldInfo.GetValue(__instance); Dealer val = (Dealer)((value is Dealer) ? value : null); if (val != null && !DealerManager.IsCartel(val)) { EMsg navigation = DealerPrefs.Prefs(((NPC)val).FirstName).GetNavigation(); if (navigation != EMsg.Disable && DealerManager.CheckPing(val)) { DealerManager.SetPing(val, Util.AbsTime()); float num = Vector3.Distance(((Component)val).transform.position, pos); string arg = LocationManager.Describe(pos); string message = $"Headed {arg}, {num:#.#} meters away."; MessageManager.Send(val, EIcon.Navigate, message, navigation == EMsg.Notify); } } } } [HarmonyPatch(typeof(Player), "SleepStart")] public class PlayerSleepStartPatch { private static void Prefix() { ContractManager.SendSummary(); } } [HarmonyPatch(typeof(SaveManager), "Save", new Type[] { typeof(string) })] public class SaveManagerSavePatch { private static void Prefix(string saveFolderPath) { ModSaveData.SaveData(saveFolderPath); } } [HarmonyPatch(typeof(TimeManager), "Update")] public class TimeManagerUpdatePatch { [HarmonyPostfix] public static void PostFix() { int num = Util.AbsTime() % 20; if (num == 0 && DealerManager.CheckStuck) { DealerManager.CheckStuck = false; foreach (DealerManager value in DealerManager.Dealers.Values) { Pathing.CheckStuck(value); } } if (num == 1) { DealerManager.CheckStuck = true; } } } public class DealerManager { public static Dictionary<string, DealerManager> Dealers = new Dictionary<string, DealerManager>(); public static Dictionary<string, DealerState> StateCache = new Dictionary<string, DealerState>(); public static bool CheckStuck; public Dictionary<string, NPC> Customers = new Dictionary<string, NPC>(); public Dealer Dealer; public DealerState State; public Vector3 Home; private int LastPing = -1; public DealerManager(Dealer dealer) { //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_0069: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Unknown result type (might be due to invalid IL or missing references) Dealer = dealer; State = (StateCache.TryGetValue(((NPC)dealer).fullName, out var value) ? value : new DealerState()); NPCEnterableBuilding home = dealer.Home; Transform transform = ((Component)dealer).transform; Home = ((Component)home.GetClosestDoor((transform != null) ? transform.position : Vector3.zero, true)).transform.position; LocationManager.Register(dealer.HomeName, Home); foreach (Customer assignedCustomer in dealer.AssignedCustomers) { string fullName = assignedCustomer.NPC.fullName; if (!State.TodaysSales.ContainsKey(fullName)) { State.TodaysSales[fullName] = "Never"; } if (!State.RecentSale.ContainsKey(fullName)) { State.RecentSale[fullName] = "Never"; } AddCustomer(fullName, assignedCustomer.NPC); } } public static DealerManager GetStats(Dealer dealer) { if (!Dealers.TryGetValue(((NPC)dealer).fullName, out var value)) { value = new DealerManager(dealer); Dealers.Add(((NPC)dealer).fullName, value); } return value; } public void RefreshInventory() { List<ItemSlot> allSlots = Dealer.GetAllSlots(); if (allSlots == null || allSlots.Count == 0) { return; } List<string> list = new List<string>(); Dictionary<string, int> dictionary = new Dictionary<string, int>(); foreach (ItemSlot item in allSlots) { if (item.Quantity != 0) { int num = item.Quantity; ItemInstance itemInstance = item.ItemInstance; ProductItemInstance val = (ProductItemInstance)(object)((itemInstance is ProductItemInstance) ? itemInstance : null); if (val != null) { num *= val.Amount; } if (list.Contains(item.ItemInstance.ID)) { dictionary[item.ItemInstance.ID] += num; continue; } list.Add(item.ItemInstance.ID); dictionary.Add(item.ItemInstance.ID, num); } } State.Products = dictionary; } public void AddCustomer(string fullName, NPC npc) { if (!Customers.ContainsKey(fullName)) { Customers.Add(fullName, npc); } } public bool IsCustomerHurt(string name, out string status) { status = ""; if (!Customers.TryGetValue(name, out var value)) { return false; } if (value.Health.IsDead) { status = $"<color=red>(Dead {value.Health.DaysPassedSinceDeath} days)</color>"; } if (value.Health.IsKnockedOut) { status = "<color=orange>(Knocked Out)</color>"; } return !string.IsNullOrEmpty(status); } public bool IsDealerHurt(out string status) { status = ""; if (((NPC)Dealer).Health.IsDead) { status = $"<color=red>Dead ({((NPC)Dealer).Health.DaysPassedSinceDeath} days)</color>"; } if (((NPC)Dealer).Health.IsKnockedOut) { status = "<color=orange>(Knocked Out)</color>"; } return !string.IsNullOrEmpty(status); } public static bool IsCartel(Dealer dealer) { if (Application.version.CompareTo("0.4.0") < 0) { return false; } return Type.GetType("CartelDealer")?.IsInstanceOfType(dealer) ?? false; } public static bool CheckPing(Dealer dealer) { int iNavDeltaTime = DealerPrefs.Prefs(((NPC)dealer).FirstName).GetINavDeltaTime(); if (iNavDeltaTime > -1) { return Util.AbsTime() - GetStats(dealer).LastPing > iNavDeltaTime; } return false; } public static void SetPing(Dealer dealer, int ping) { GetStats(dealer).LastPing = ping; } public static void ClearAll() { foreach (DealerManager value in Dealers.Values) { value.Customers.Clear(); value.State.ClearAll(); } Dealers.Clear(); StateCache.Clear(); } public Vector3 DistanceToHome() { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) return Dealer.Home.GetClosestDoor(((Component)Dealer).transform.position, true).AccessPoint.position; } } public enum EMsg { Notify, Silent, Disable } public class DealerText : MelonMod { public const string ModName = "Dealers Send Texts"; public const string Version = "2.1.0"; public const string ModDesc = "Dealers text updates on deals and daily summary in Schedule One"; public override void OnSceneWasLoaded(int buildIndex, string sceneName) { if (sceneName.Equals("main", StringComparison.OrdinalIgnoreCase)) { DealerPrefs.Initialize(); } } public override void OnSceneWasUnloaded(int buildIndex, string sceneName) { if (sceneName.Equals("main", StringComparison.OrdinalIgnoreCase)) { ClearAll(); } } public static void ClearAll() { ContractManager.ClearAll(); LocationManager.ClearAll(); DealerManager.ClearAll(); DealerPrefs.ClearAll(); Pathing.ClearAll(); } } public static class Pathing { private const int MINTIME = 5; private const int MAXHISTORY = 10; private static readonly Dictionary<string, Queue<(Vector3, int)>> PositionHistory = new Dictionary<string, Queue<(Vector3, int)>>(); public static void RecordPosition(Dealer dealer) { //IL_0063: Unknown result type (might be due to invalid IL or missing references) if (dealer != null && dealer.ActiveContracts.Count != 0) { string fullName = ((NPC)dealer).fullName; int num = Util.AbsTime(); if (!PositionHistory.TryGetValue(fullName, out var value)) { value = (PositionHistory[fullName] = new Queue<(Vector3, int)>()); } if (value.Count == 0 || Math.Abs(value.Peek().Item2 - num) >= 5) { value.Enqueue((((Component)dealer).transform.position, num)); } if (value.Count > 10) { value.Dequeue(); } } } public static bool IsStuck(Dealer dealer) { //IL_004f: 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_0065: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Unknown result type (might be due to invalid IL or missing references) if (dealer == null || !PositionHistory.TryGetValue(((NPC)dealer).fullName, out var value)) { return false; } DealerPrefs dealerPrefs = DealerPrefs.Prefs(((NPC)dealer).FirstName); int isStuckCount = dealerPrefs.GetIsStuckCount(); if (value.Count < isStuckCount) { return false; } (Vector3, int)[] array = value.Reverse().Take(isStuckCount).ToArray(); Vector3 item = array[0].Item1; (Vector3, int)[] array2 = array; for (int i = 0; i < array2.Length; i++) { if (Vector3.Distance(array2[i].Item1, item) > (float)dealerPrefs.GetIsStuckRadius()) { return false; } } return true; } public static void CheckStuck(DealerManager stats) { //IL_0047: 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) Dealer dealer = stats.Dealer; RecordPosition(dealer); if (IsStuck(dealer) && !stats.IsDealerHurt(out var _) && dealer.ActiveContracts.Count != 0) { bool notify = DealerPrefs.Prefs(((NPC)dealer).FirstName).GetIsStuckAlert() == EMsg.Notify; string arg = LocationManager.Describe(((Component)dealer).transform.position, "at"); string message = $"I may be stuck in {((NPC)dealer).Region} {arg}. Most recent deal was {stats.State.MostRecent}."; MessageManager.Send(dealer, EIcon.HurtAlert, message, notify); } } public static void ClearAll() { PositionHistory.Clear(); } } public enum EContract { Started, Success, Failure } public static class ContractManager { public static HashSet<string> Completed = new HashSet<string>(); public static void ProcessContract(Contract contract, EContract state) { //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Unknown result type (might be due to invalid IL or missing references) if (contract != null && contract.Dealer != null) { DealerPrefs prefs = DealerPrefs.Prefs(((NPC)contract.Dealer).FirstName); DealerManager stats = DealerManager.GetStats(contract.Dealer); SaleData saleData = new SaleData(contract, stats); NPC nPC = ((Component)contract.Customer).GetComponent<Customer>().NPC; Vector3 position = contract.DeliveryLocation.CustomerStandPoint.position; stats.AddCustomer(nPC.fullName, nPC); LocationManager.Register(saleData.Location, position); Location nearest = LocationManager.GetNearest(position); if (state == EContract.Failure && Completed.Contains(saleData.Customer)) { state = EContract.Success; } if (state == EContract.Started) { ContractStarted(contract, saleData, nearest, prefs); } if (state == EContract.Success) { ContractSuccess(contract, saleData, nearest, prefs); } if (state == EContract.Failure) { ContractFailure(contract, saleData, nearest, prefs); } } } public static void SendSummary() { Dictionary<string, DealerManager> dealers = DealerManager.Dealers; if (dealers == null || dealers.Count == 0) { return; } foreach (DealerManager value in dealers.Values) { AlertManager.DailySummary(value); } } private static void ContractStarted(Contract contract, SaleData sale, Location location, DealerPrefs prefs) { location.RecordStarted(); location.RecordCustomer(sale.Customer); location.RecordDealer(((NPC)contract.Dealer).fullName); sale.Stats.State.TotalSales.Add(sale); sale.Stats.State.SaleCount++; string message = MessageManager.Create(EContract.Started, contract, sale); if (prefs.GetShowStarted() != EMsg.Disable) { MessageManager.Send(contract.Dealer, EIcon.Started, message, prefs.GetShowStarted() == EMsg.Notify); } } private static void ContractSuccess(Contract contract, SaleData sale, Location location, DealerPrefs prefs) { sale.Status = "Success"; DealerState state = sale.Stats.State; Completed.Remove(sale.Customer); Dictionary<string, string> todaysSales = state.TodaysSales; string customer = sale.Customer; string value = (state.RecentSale[sale.Customer] = (state.MostRecent = Util.Time())); todaysSales[customer] = value; state.DailySales.Add(sale); location.RecordSuccess(); string message = MessageManager.Create(EContract.Success, contract, sale); if (prefs.GetShowSuccess() != EMsg.Disable) { MessageManager.Send(contract.Dealer, EIcon.Success, message, prefs.GetShowSuccess() == EMsg.Notify); } AlertManager.CheckProductAlert(contract.Dealer); } private static void ContractFailure(Contract contract, SaleData sale, Location location, DealerPrefs prefs) { sale.Status = "Failure"; sale.Stats.State.Failures.Add(new FailureKey(sale.Customer, sale.Location)); location.RecordFailure(); string message = MessageManager.Create(EContract.Failure, contract, sale); if (prefs.GetShowFailure() != EMsg.Disable) { MessageManager.Send(contract.Dealer, EIcon.Failure, message, prefs.GetShowFailure() == EMsg.Notify); } } public static void ClearAll() { Completed.Clear(); } } public static class Util { public static int TimeDiff(int start, int end, bool is24hour = true) { start = (is24hour ? TimeManager.GetMinSumFrom24HourTime(start) : start); end = (is24hour ? TimeManager.GetMinSumFrom24HourTime(end) : end); int num = end - start; if (num < 0) { num += 1440; } return num; } public static string GetName(string input) { string text = Registry.GetItem(input)?.Name; if (string.IsNullOrEmpty(text)) { return input; } return text; } public static string Prefix(string input) { if (input.ToLower().StartsWith("outside") || input.ToLower().StartsWith("next") || input.ToLower().StartsWith("under") || input.ToLower().StartsWith("behind") || input.ToLower().StartsWith("in ")) { return input; } return "at the " + input; } public static string DayDate() { //IL_000a: Unknown result type (might be due to invalid IL or missing references) return $"{NetworkSingleton<TimeManager>.Instance.CurrentDay}, Day {NetworkSingleton<TimeManager>.Instance.ElapsedDays}"; } public static int IntTime() { return NetworkSingleton<TimeManager>.Instance.CurrentTime; } public static int AbsTime() { return NetworkSingleton<TimeManager>.Instance.GetTotalMinSum(); } public static string Time() { return TimeManager.Get12HourTime((float)IntTime(), true); } public static string HoursMins(int time) { return ((time / 60 > 0) ? (time / 60 + "hr ") : "") + time % 60 + "min"; } }