Some mods target the Mono version of the game, which is available by opting into the Steam beta branch "alternate"
Decompiled source of MultiDelivery v1.0.5
MultiDelivery-IL2CPP.dll
Decompiled 2 days ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Text; using System.Threading.Tasks; using HarmonyLib; using Il2Cpp; using Il2CppFishNet; using Il2CppFishNet.Connection; using Il2CppFishNet.Object; using Il2CppInterop.Runtime; using Il2CppInterop.Runtime.Attributes; using Il2CppInterop.Runtime.InteropTypes; using Il2CppInterop.Runtime.InteropTypes.Arrays; using Il2CppScheduleOne; using Il2CppScheduleOne.Core.Items.Framework; using Il2CppScheduleOne.Core.Weather; using Il2CppScheduleOne.Delivery; using Il2CppScheduleOne.DevUtilities; using Il2CppScheduleOne.Graffiti; using Il2CppScheduleOne.ItemFramework; using Il2CppScheduleOne.Map; using Il2CppScheduleOne.Money; using Il2CppScheduleOne.NPCs.CharacterClasses; using Il2CppScheduleOne.Persistence.Datas; using Il2CppScheduleOne.Persistence.Loaders; using Il2CppScheduleOne.PlayerScripts; using Il2CppScheduleOne.UI; using Il2CppScheduleOne.UI.Phone.Delivery; using Il2CppScheduleOne.UI.Shop; using Il2CppScheduleOne.Vehicles; using Il2CppScheduleOne.Vehicles.Modification; using Il2CppSteamworks; using Il2CppSystem; using Il2CppSystem.Collections.Generic; using Il2CppSystem.IO; using MelonLoader; using MelonLoader.Preferences; using MelonLoader.Utils; using Microsoft.CodeAnalysis; using MultiDelivery; using MultiDelivery.Builders; using MultiDelivery.GameConsole.Commands; using MultiDelivery.GameConsole.Commands.Help; using MultiDelivery.GameConsole.Commands.Pool; using MultiDelivery.GameConsole.Commands.Quest; using MultiDelivery.GameConsole.Core; using MultiDelivery.Helpers; using MultiDelivery.Network; using MultiDelivery.Persistence; using MultiDelivery.Pool; using MultiDelivery.Quest; using S1API.Console; using S1API.Dialogues; using S1API.Entities; using S1API.Entities.Dialogue; using S1API.Entities.NPCs.Suburbia; using S1API.Internal.Abstraction; using S1API.Leveling; using S1API.Messaging; using S1API.Money; using S1API.Quests; using S1API.Quests.Constants; using S1API.Saveables; using S1API.Utils; using Semver; using SteamNetworkLib; using SteamNetworkLib.Core; using SteamNetworkLib.Events; using SteamNetworkLib.Models; using UnityEngine; using UnityEngine.Events; using UnityEngine.SceneManagement; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)] [assembly: MelonInfo(typeof(global::MultiDelivery.MultiDelivery), "MultiDelivery", "1.0.5", "k073l", null)] [assembly: MelonColor(1, 0, 255, 0)] [assembly: MelonGame("TVGS", "Schedule I")] [assembly: MelonPlatformDomain(/*Could not decode attribute arguments.*/)] [assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")] [assembly: AssemblyCompany("MultiDelivery-IL2CPP")] [assembly: AssemblyConfiguration("IL2CPP")] [assembly: AssemblyDescription("Add vehicles, order multiple deliveries from the same place!")] [assembly: AssemblyFileVersion("1.0.5.0")] [assembly: AssemblyInformationalVersion("1.0.5+1e3711e3b7b873b5063b983a5dfae1d74542cb89")] [assembly: AssemblyProduct("MultiDelivery-IL2CPP")] [assembly: AssemblyTitle("MultiDelivery-IL2CPP")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.0.5.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace MultiDelivery { public class Logger { private Instance? _melonLogger; private static bool _initialized; private static readonly object InitLock = new object(); private static ConstructorInfo? _melonLoggerCtor; private static object? _defaultTextColor; private static Type? _defaultTextColorType; private static object? _melonNsColor; private static MethodInfo? _msgMethod; public string? Namesection { get; set; } public bool RaiseTrace { get; set; } public bool RaiseDebug { get; set; } private static string? RootNamespace => typeof(Logger).Namespace?.Split('.').FirstOrDefault(); public Logger(string? namesection = null) { if (namesection == null) { Namesection = typeof(Logger).Namespace?.Split('.').FirstOrDefault(); } else if (!string.IsNullOrEmpty(RootNamespace) && !namesection.StartsWith(RootNamespace)) { Namesection = RootNamespace + "." + namesection; } else { Namesection = namesection; } SetupInstance(); } public Logger(Instance melonLogger) { FieldInfo field = typeof(Instance).GetField("Name", BindingFlags.Instance | BindingFlags.NonPublic); if (field != null) { Namesection = field.GetValue(melonLogger) as string; } SetupInstance(); } public static implicit operator Logger(Instance melonLogger) { return new Logger(melonLogger); } public void Trace(params object[] args) { Log(LogLevel.Trace, null, args); } public void Debug(params object[] args) { Log(LogLevel.Debug, null, args); } public void T(params object[] args) { Log(LogLevel.Trace, null, args); } public void D(params object[] args) { Log(LogLevel.Debug, null, args); } public void Info(params object[] args) { Log(LogLevel.Info, null, args); } public void Msg(params object[] args) { Log(LogLevel.Info, null, args); } public void I(params object[] args) { Log(LogLevel.Info, null, args); } public void M(params object[] args) { Log(LogLevel.Info, null, args); } public void Warning(params object[] args) { Log(LogLevel.Warning, null, args); } public void Warn(params object[] args) { Log(LogLevel.Warning, null, args); } public void W(params object[] args) { Log(LogLevel.Warning, null, args); } public void Error(params object[] args) { Log(LogLevel.Error, null, args); } public void Err(params object[] args) { Log(LogLevel.Error, null, args); } public void E(params object[] args) { Log(LogLevel.Error, null, args); } public void Fatal(params object[] args) { Log(LogLevel.BigError, null, args); } public void BigError(params object[] args) { Log(LogLevel.BigError, null, args); } public void F(params object[] args) { Log(LogLevel.BigError, null, args); } public void BE(params object[] args) { Log(LogLevel.BigError, null, args); } public void InfoColored(object txtColor, params object[] args) { Log(LogLevel.Info, txtColor, args); } public void MsgColored(object txtColor, params object[] args) { Log(LogLevel.Info, txtColor, args); } public void IC(object txtColor, params object[] args) { Log(LogLevel.Info, txtColor, args); } public void MC(object txtColor, params object[] args) { Log(LogLevel.Info, txtColor, args); } public void Log(LogLevel level, string message) { Log(level, null, message); } private static void EnsureInitialized() { if (_initialized) { return; } lock (InitLock) { if (_initialized) { return; } Type typeFromHandle = typeof(MelonLogger); FieldInfo field = typeFromHandle.GetField("DefaultTextColor", BindingFlags.Static | BindingFlags.Public); _defaultTextColorType = field?.FieldType; if (_defaultTextColorType == null) { MelonLogger.Error("Could not determine the type of DefaultTextColor"); return; } _defaultTextColor = field?.GetValue(null); Type typeFromHandle2 = typeof(Instance); _melonLoggerCtor = typeFromHandle2.GetConstructor(new Type[2] { typeof(string), _defaultTextColorType }); Type melonBaseType = typeof(MelonBase); Assembly executingAssembly = Assembly.GetExecutingAssembly(); Type[] source; try { source = executingAssembly.GetTypes(); } catch (ReflectionTypeLoadException ex) { source = ex.Types.OfType<Type>().ToArray(); } Type type = source.FirstOrDefault((Type t) => melonBaseType.IsAssignableFrom(t) && !t.IsAbstract); Type type2 = typeof(Melon<>).MakeGenericType(type ?? throw new InvalidOperationException("No MelonBase derived type found in the current assembly.")); PropertyInfo property = type2.GetProperty("Instance", BindingFlags.Static | BindingFlags.Public); if (property != null) { PropertyInfo property2 = property.PropertyType.GetProperty("ConsoleColor", BindingFlags.Instance | BindingFlags.Public); if (property2 != null) { _melonNsColor = property2.GetValue(property.GetValue(null)); } } if (_melonNsColor == null) { FieldInfo field2 = typeFromHandle.GetField("DefaultMelonColor", BindingFlags.Static | BindingFlags.Public); if (field2 != null) { _melonNsColor = field2.GetValue(null); } } _msgMethod = typeFromHandle2.GetMethod("Msg", BindingFlags.Instance | BindingFlags.Public, null, new Type[2] { _defaultTextColorType, typeof(string) }, null); if (_melonLoggerCtor == null) { MelonLogger.Error("Could not find constructor for MelonLogger.Instance(string, <colorType>)"); } if (_defaultTextColor == null) { MelonLogger.Error("Could not find DefaultTextColor"); } if (_melonNsColor == null) { MelonLogger.Error("Could not find Melon<>.Instance.ConsoleColor or MelonLogger.DefaultMelonColor"); } if (_msgMethod == null) { MelonLogger.Error("Could not find MelonLogger.Instance.Msg(<colorType>, string)"); } _initialized = true; } } private static string GetCallerInfo() { StackTrace stackTrace = new StackTrace(); for (int i = 3; i < stackTrace.FrameCount; i++) { StackFrame frame = stackTrace.GetFrame(i); MethodBase method = frame.GetMethod(); if (method?.DeclaringType != null) { return method.DeclaringType.FullName + "." + method.Name; } } return "unknown"; } private void SetupInstance() { //IL_0060: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Expected O, but got Unknown if (string.IsNullOrEmpty(Namesection)) { Namesection = "Logger"; } EnsureInitialized(); object obj = _melonLoggerCtor?.Invoke(new object[2] { Namesection, _melonNsColor }); if (obj == null) { throw new NullReferenceException("Could not create MelonLogger.Instance"); } _melonLogger = (Instance)obj; } private void Log(LogLevel level, object? txtColor, params object[] args) { if (args.Length == 0) { return; } string text; if (args.Length == 1) { text = args[0]?.ToString() ?? ""; } else { string format = args[0]?.ToString() ?? ""; text = string.Format(format, args[1..]); } object obj = txtColor ?? _defaultTextColor; switch (level) { case LogLevel.Trace: { string text2 = "[TRACE " + GetCallerInfo() + "] " + text; if (RaiseTrace) { _msgMethod?.Invoke(_melonLogger, new object[2] { obj, text2 }); } else { MelonDebug.Msg(text2); } break; } case LogLevel.Debug: { string text3 = "[DEBUG " + GetCallerInfo() + "] " + text; if (RaiseDebug) { _msgMethod?.Invoke(_melonLogger, new object[2] { obj, text3 }); } else { MelonDebug.Msg(text3); } break; } default: _msgMethod?.Invoke(_melonLogger, new object[2] { obj, text }); break; case LogLevel.Warning: { Instance? melonLogger3 = _melonLogger; if (melonLogger3 != null) { melonLogger3.Warning(text); } break; } case LogLevel.Error: { Instance? melonLogger2 = _melonLogger; if (melonLogger2 != null) { melonLogger2.Error(text); } break; } case LogLevel.BigError: { Instance? melonLogger = _melonLogger; if (melonLogger != null) { melonLogger.BigError(text); } break; } } } } public enum LogLevel { Trace, Debug, Info, Warning, Error, BigError } public static class BuildInfo { public const string Name = "MultiDelivery"; public const string Description = "Add vehicles, order multiple deliveries from the same place!"; public const string Author = "k073l"; public const string Version = "1.0.5"; } public class MultiDelivery : MelonMod { private static Logger Logger; private IDeliveryNetworkManager? _networkManager; private bool _networkManagerFailed; internal static MelonPreferences_Category Category = MelonPreferences.CreateCategory("MultiDeliverySettings", "MultiDelivery's Settings"); internal static MelonPreferences_Entry<bool> NetworkLogging; internal static MelonPreferences_Entry<bool> PoolDebugLogging; internal static MelonPreferences_Entry<bool> QuestDebugLogging; public override void OnInitializeMelon() { Logger = ((MelonBase)this).LoggerInstance; Logger.Msg("MultiDelivery initialized"); NetworkLogging = Category.CreateEntry<bool>("NetworkDebugLogs", false, "Enable Network Logs", "Display networking-related debug logs in MelonLoader console/log file (may be verbose)", false, false, (ValueValidator)null, (string)null); PoolDebugLogging = Category.CreateEntry<bool>("PoolDebugLogs", false, "Enable Pool Debug Logs", "Display pool-related debug logs in MelonLoader console/log file (may be verbose)", false, false, (ValueValidator)null, (string)null); QuestDebugLogging = Category.CreateEntry<bool>("QuestDebugLogs", false, "Enable Quest Debug Logs", "Display quest-related debug logs in MelonLoader console/log file (may be verbose)", false, false, (ValueValidator)null, (string)null); RegisterLoggerSettings(); CheckDependencies(); MelonCoroutines.Start(InitializeNetworkManager()); } public override void OnSceneWasLoaded(int buildIndex, string sceneName) { QuestSetupManager.OnSceneWasLoaded(buildIndex, sceneName); if (!(sceneName != "Menu")) { PoolManager.Instance.ClearAll(); } } private IEnumerator InitializeNetworkManager() { if (SteamAPI.Init()) { yield return null; _networkManager = NetworkLoader.Create(); if (_networkManager == null) { Logger.Warning("NetworkManager is null, multiplayer features will be unavailable."); } else { NetworkConvenienceMethods.InitializeNetworking(_networkManager); _networkManager.RegisterLoggerSettings(); } } } public override void OnUpdate() { if (_networkManagerFailed || _networkManager == null) { return; } try { _networkManager.Update(); } catch (Exception ex) { MelonLogger.Error("Network manager update failed: " + ex.Message + "\n\nYou can ignore this error if you plan on playing singleplayer only and don't want to install SteamNetworkLib"); _networkManagerFailed = true; } } public override void OnDeinitializeMelon() { _networkManager?.Dispose(); Logger.Msg("MultiDelivery deinitialized"); } private static void CheckDependencies() { DependenciesChecker dependenciesChecker = new DependenciesChecker { ShowMenuBanner = true, UnloadIfMissingRequired = true }; dependenciesChecker.AddDependency(new DependencyInfo { Name = "S1API (Forked)", AssemblyName = "S1API", IsRequired = true, Version = "3.0.1", Urls = new List<DependencyUrl>(3) { new DependencyUrl { SourceName = "Thunderstore", Url = "https://thunderstore.io/c/schedule-i/p/ifBars/S1API_Forked/" }, new DependencyUrl { SourceName = "NexusMods", Url = "https://www.nexusmods.com/schedule1/mods/1194" }, new DependencyUrl { SourceName = "Github", Url = "https://github.com/ifBars/S1API/releases/" } } }); dependenciesChecker.AddDependency(new DependencyInfo { Name = "SteamNetworkLib", AssemblyName = "SteamNetworkLib", IsRequired = false, Version = "1.2.1", Urls = new List<DependencyUrl>(4) { new DependencyUrl { SourceName = "Thunderstore Il2Cpp", Url = "https://thunderstore.io/c/schedule-i/p/ifBars/SteamNetworkLib_Il2Cpp/" }, new DependencyUrl { SourceName = "Thunderstore Mono", Url = "https://thunderstore.io/c/schedule-i/p/ifBars/SteamNetworkLib_Mono/" }, new DependencyUrl { SourceName = "NexusMods", Url = "https://www.nexusmods.com/schedule1/mods/1396" }, new DependencyUrl { SourceName = "Github", Url = "https://github.com/ifBars/SteamNetworkLib/releases" } } }); dependenciesChecker.ProcessAndAlert(); } private static void RegisterLoggerSettings() { SetAndRegister(ActiveDeliveryPatch.Logger, PoolDebugLogging); SetAndRegister(CapacityText.Logger, PoolDebugLogging); SetAndRegister(DeliveryInstancePatches.Logger, PoolDebugLogging); SetAndRegister(LoaderPatch.Logger, PoolDebugLogging); SetAndRegister(PoolManager.Logger, PoolDebugLogging); SetAndRegister(DropoffQuest.Logger, QuestDebugLogging); SetAndRegister(DropoffQuestDialogue.Logger, QuestDebugLogging); SetAndRegister(QuestSetupManager.Logger, QuestDebugLogging); } private static void SetAndRegister(Logger logger, MelonPreferences_Entry<bool>? entry) { logger.RaiseDebug = entry?.Value ?? false; ((MelonEventBase<LemonAction<bool, bool>>)(object)entry?.OnEntryValueChanged).Subscribe((LemonAction<bool, bool>)delegate(bool _, bool newValue) { logger.RaiseDebug = newValue; }, 0, false); } } } namespace MultiDelivery.Quest { public class DropoffQuest : Quest { internal const string Name = "Expanding the Fleet"; private bool _vehicleAdded; private QuestEntry _addVehicleEntry; private int _startingCapacity; private static VehicleDropoffZone dropoffZone; private static Vector3 _dropoffZonePosition; internal static readonly Logger Logger = new Logger("DropoffQuest"); protected override string Title => "Expanding the Fleet"; protected override string Description => "Bring a delivery vehicle to the dropoff zone to expand your delivery capacity"; protected override bool AutoBegin => false; protected override Sprite QuestIcon => IconLoader.QuestIconSprite; internal QuestState State => ((Quest)this).QuestState; protected override void OnCreated() { //IL_0091: Unknown result type (might be due to invalid IL or missing references) //IL_0092: Unknown result type (might be due to invalid IL or missing references) //IL_0093: Unknown result type (might be due to invalid IL or missing references) //IL_009d: Unknown result type (might be due to invalid IL or missing references) //IL_00a2: Unknown result type (might be due to invalid IL or missing references) //IL_00c6: Unknown result type (might be due to invalid IL or missing references) //IL_00c7: Unknown result type (might be due to invalid IL or missing references) //IL_00e1: Unknown result type (might be due to invalid IL or missing references) ((Registerable)this).OnCreated(); _startingCapacity = PoolManager.Instance.PoolCount; Logger.Debug($"Starting Quest with {_startingCapacity} capacity"); Vector3 val = default(Vector3); ((Vector3)(ref val))..ctor(5.1f, 4.2f, 82.58f); Vector3 val2 = default(Vector3); ((Vector3)(ref val2))..ctor(0.55f, 4.2f, 76.56f); _dropoffZonePosition = (val + val2) / 2f; if ((Object)(object)dropoffZone != (Object)null) { Object.Destroy((Object)(object)((Component)dropoffZone).gameObject); } dropoffZone = VehicleDropoffZoneFactory.CreateZone(val, val2, 5f, (Color?)new Color(0f, 1f, 0f, 0.4f), showVisuals: true, this); Logger.Debug("Dropoff zone created. Spawning " + ((Object)dropoffZone).name); ((Quest)this).OnComplete += Completed; Logger.Debug("Wired completion event"); UpdateQuestEntries(); } private void UpdateQuestEntries() { //IL_004f: Unknown result type (might be due to invalid IL or missing references) base.QuestEntries.Clear(); if (!_vehicleAdded) { Logger.Debug("Adding add vehicle entry"); _addVehicleEntry = ((Quest)this).AddEntry("Purchase a " + "veeper".Capitalize() + " and drive it into the green dropoff zone (top floor of the parking garage, next to storage unit)", (Vector3?)_dropoffZonePosition); _addVehicleEntry.Begin(); } Logger.Debug("Entries added!"); } public void MarkAddVehicleEntryComplete() { Logger.Debug("Marked add vehicle entry as completed"); _addVehicleEntry.Complete(); } private void Completed() { Logger.Msg("Delivery vehicle dropoff quest completed!"); MelonCoroutines.Start(NotifyCompletion()); } private IEnumerator NotifyCompletion() { int i = 0; while (i < 3 && PoolManager.Instance.PoolCount <= _startingCapacity) { yield return (object)new WaitForSeconds(3f); int num = i + 1; i = num; } NPC npc = NPC.Get<JeremyWilkinson>(); if (npc != null) { if (PoolManager.Instance.PoolCount <= _startingCapacity) { npc.SendTextMessage("Something went wrong... Vehicle got in a car crash :(", (Response[])null, 1f, true); yield break; } npc.SendTextMessage($"Vehicle added, you now can order {PoolManager.Instance.PoolCount} more deliver{((PoolManager.Instance.PoolCount > 1) ? "ies" : "y")} from stores.", (Response[])null, 1f, true); npc.SendTextMessage("If you want to add more, you know where to find me.", (Response[])null, 1f, true); } } } public class DropoffQuestDialogue { internal static readonly Logger Logger = new Logger("DropoffQuestDialogue"); private const string ContainerName = "DeliveryExpansion"; public static void Register() { //IL_0191: Unknown result type (might be due to invalid IL or missing references) //IL_019b: Expected O, but got Unknown NPC jeremy = NPC.Get<JeremyWilkinson>(); if (jeremy == null) { Logger.Warning("Jeremy Wilkinson not found"); return; } jeremy.Dialogue.BuildAndRegisterContainer("DeliveryExpansion", (Action<DialogueContainerBuilder>)delegate(DialogueContainerBuilder c) { c.AddNode("OFFER_FIRST", "Yeah! I can help with that. If you bring a " + "veeper".Capitalize() + " to the dropoff zone, I'll add it to the fleet. This will let you handle multiple deliveries at once. Want to give it a shot?", (Action<ChoiceList>)delegate(ChoiceList ch) { ch.Add("BUY_NOW", "Sure! Can I buy one from you right now?", "CHECK_FUNDS"); ch.Add("ACCEPT", "I already have one, let's do it!", "ACCEPTED"); ch.Add("DECLINE", "Maybe later.", "DECLINED"); }); c.AddNode("OFFER_REPEAT", "Want to expand your fleet even more? Just bring another vehicle to the dropoff zone and I'll add it for you.", (Action<ChoiceList>)delegate(ChoiceList ch) { ch.Add("BUY_NOW", "Yeah, can I buy another one from you?", "CHECK_FUNDS"); ch.Add("ACCEPT", "I've got one ready!", "ACCEPTED"); ch.Add("DECLINE", "Not right now.", "DECLINED"); }); c.AddNode("CHECK_FUNDS", $"A {"veeper".Capitalize()} runs <color=#19BEF0>({MoneyManager.FormatAmount(GetVehiclePrice(), false, false)})</color>. Want to pick one up?", (Action<ChoiceList>)delegate(ChoiceList ch) { ch.Add("PURCHASE", "I'll take it. <color=#19BEF0>(" + MoneyManager.FormatAmount(GetVehiclePrice(), false, false) + ")</color>", "PURCHASE_COMPLETE"); ch.Add("NEVERMIND", "Let me think about it.", "DECLINED"); }); c.AddNode("PURCHASE_COMPLETE", "All yours. You can customize it if you want. Now just drive it to the dropoff zone - I've marked it on your map.", (Action<ChoiceList>)null); c.AddNode("NOT_ENOUGH", "You don't have enough money to buy it. Come back later.", (Action<ChoiceList>)null); c.AddNode("IN_PROGRESS", "You already have an active delivery expansion going! Just bring the vehicle to the dropoff zone. Top floor of the parking garage, next to the storage units - check your map if you forgot where it is.", (Action<ChoiceList>)null); c.AddNode("ACCEPTED", "Great! I've marked the dropoff zone on your map - top floor of the parking garage, next to the storage units. Just drive the vehicle in there when you're ready.", (Action<ChoiceList>)null); c.AddNode("DECLINED", "No worries, just let me know if you change your mind!", (Action<ChoiceList>)null); }); jeremy.Dialogue.OnChoiceSelected("ACCEPT", (Action)OnAcceptQuest); jeremy.Dialogue.OnChoiceSelected("BUY_NOW", (Action)OnAcceptQuest); jeremy.Dialogue.OnChoiceSelected("DECLINE", (Action)delegate { Logger.Debug("Player declined quest"); }); jeremy.Dialogue.OnChoiceSelected("NEVERMIND", (Action)delegate { Logger.Debug("Player cancelled purchase"); }); jeremy.Dialogue.OnChoiceSelected("PURCHASE", (Action)delegate { //IL_00fb: Unknown result type (might be due to invalid IL or missing references) //IL_0100: Unknown result type (might be due to invalid IL or missing references) float vehiclePrice = GetVehiclePrice(); float onlineBalance = Money.GetOnlineBalance(); if (onlineBalance >= vehiclePrice) { Money.CreateOnlineTransaction("veeper".Capitalize() + " purchase", 0f - vehiclePrice, 1f, "Bought as a part of delivery expansion"); Logger.Msg($"Player purchased {"veeper"} for ${vehiclePrice:F2}"); Jeremy component = jeremy.gameObject.GetComponent<Jeremy>(); if ((Object)(object)component != (Object)null) { Dealership dealership = component.Dealership; if ((Object)(object)dealership != (Object)null) { dealership.SpawnVehicle("veeper"); return; } } NetworkSingleton<VehicleManager>.Instance.SpawnVehicle("veeper", new Vector3(9.92f, 0.54f, -33.55f), Quaternion.identity, true); } else { jeremy.Dialogue.JumpTo("DeliveryExpansion", "NOT_ENOUGH", false); } }); DialogueInjector.Register(new DialogueInjection(jeremy.ID, "Dealership_Salesman_Sell", "cc0d838e-2824-4fd5-907d-798dc0195c16", "OFFER_FIRST", "ASK_EXPANSION", "Can I expand my delivery capacity?", (Action)delegate { //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Invalid comparison between Unknown and I4 DropoffQuest dropoffQuest = QuestManager.GetQuestByName("Expanding the Fleet") as DropoffQuest; bool flag = PoolManager.Instance.PoolCount > 0; bool flag2 = dropoffQuest != null && (int)dropoffQuest.State == 1; Logger.Debug($"Quest state - Active: {flag2}, Has expanded: {flag}"); string text = (flag2 ? "IN_PROGRESS" : (flag ? "OFFER_REPEAT" : "OFFER_FIRST")); Logger.Debug($"Jumping to '{"DeliveryExpansion"}', node '{text}'"); jeremy.Dialogue.JumpTo("DeliveryExpansion", text, false); })); Logger.Msg("Registered delivery expansion dialogue"); } internal static void OnAcceptQuest() { //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Invalid comparison between Unknown and I4 if (!(QuestManager.GetQuestByName("Expanding the Fleet") is DropoffQuest dropoffQuest)) { if (QuestManager.CreateQuest<DropoffQuest>((string)null) is DropoffQuest dropoffQuest2) { ((Quest)dropoffQuest2).Begin(); } Logger.Msg("Started delivery expansion quest"); } else if (dropoffQuest != null && (int)dropoffQuest.State == 2) { ((Quest)dropoffQuest).Begin(); Logger.Msg("Restarted delivery expansion quest"); } } private static float GetVehiclePrice() { LandVehicle vehiclePrefab = NetworkSingleton<VehicleManager>.Instance.GetVehiclePrefab("veeper"); return (vehiclePrefab != null) ? vehiclePrefab.VehiclePrice : 5000f; } } public class QuestSetupManager { internal const string RequestedVehicleCode = "veeper"; internal const string QuestStartMessage = "Your properties are getting busy. Want to handle more than one delivery at a time? I've got an idea. Stop by the dealership."; private static FullRank RequiredRank = new FullRank((Rank)5, 1); internal static readonly Logger Logger = new Logger("QuestSetupManager"); public static void OnSceneWasLoaded(int buildIndex, string sceneName) { if (!(sceneName == "Main")) { if (sceneName == "Menu") { Player.LocalPlayerSpawned -= WirePlayerEvent; LevelManager.OnXPChanged -= SendMessageIfRequiredRank; } } else { Player.LocalPlayerSpawned += WirePlayerEvent; } } private static void WirePlayerEvent(Player _) { Logger.Debug("Player loaded event called"); MelonCoroutines.Start(WireOnXpChangedDelayed()); } private static IEnumerator WireOnXpChangedDelayed() { yield return (object)new WaitUntil(Func<bool>.op_Implicit((Func<bool>)(() => LevelManager.Exists))); if (PersistentDropoffQuestData.Instance.HasMessaged) { DropoffQuestDialogue.Register(); yield break; } Logger.Debug("Wiring on xp changed"); LevelManager.OnXPChanged += SendMessageIfRequiredRank; } private static void SendMessageIfRequiredRank(FullRank _, FullRank current) { //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_0079: Unknown result type (might be due to invalid IL or missing references) Logger.Debug($"Current rank {current}, required: {RequiredRank}"); if (PersistentDropoffQuestData.Instance.HasMessaged) { MelonDebug.Msg("Xp changed wired, but already messaged - registering dialogue now."); DropoffQuestDialogue.Register(); } else { if (current < RequiredRank) { return; } NPC val = NPC.Get<JeremyWilkinson>(); if (val != null) { if (NetworkConvenienceMethods.HostOrSingleplayer) { val.SendTextMessage("Your properties are getting busy. Want to handle more than one delivery at a time? I've got an idea. Stop by the dealership.", (Response[])null, 1f, true); } PersistentDropoffQuestData.Instance.HasMessaged = true; DropoffQuestDialogue.Register(); LevelManager.OnXPChanged -= SendMessageIfRequiredRank; } } } } [RegisterTypeInIl2Cpp] public class VehicleDropoffZone : MonoBehaviour { private VehicleDetector _detector; private Logger _logger; private GameObject _visualPlane; private Material _visualMaterial; private Color _baseColor; private DropoffQuest? _quest; public Vector3 Corner1 { get; private set; } public Vector3 Corner2 { get; private set; } public bool ShowVisuals { get; set; } = true; private void Awake() { _logger = new Logger("VehicleDropoffZone"); _logger.RaiseDebug = MultiDelivery.QuestDebugLogging?.Value ?? false; ((MelonEventBase<LemonAction<bool, bool>>)(object)MultiDelivery.QuestDebugLogging?.OnEntryValueChanged).Subscribe((LemonAction<bool, bool>)delegate(bool _, bool newValue) { _logger.RaiseDebug = newValue; }, 0, false); } private void Start() { _detector = ((Component)this).gameObject.GetComponent<VehicleDetector>(); if ((Object)(object)_detector == (Object)null) { _detector = ((Component)this).gameObject.AddComponent<VehicleDetector>(); } } private void Update() { if (_detector.vehicles.Count <= 0) { return; } foreach (LandVehicle item in _detector.vehicles.AsEnumerable<LandVehicle>()) { OnVehicleEntered(item); } } private void OnVehicleEntered(LandVehicle vehicle) { _logger.Debug("Vehicle entered dropoff zone: " + vehicle.vehicleName); if (!IsCorrectVehicleType(vehicle)) { if (Time.frameCount % 60 == 0) { _logger.Warning("Wrong vehicle type: " + vehicle.vehicleCode); } return; } if (vehicle.IsOccupied) { _logger.Msg("Ejecting player from vehicle"); vehicle.ExitVehicle(); } ProcessAndAddToPool(vehicle); _detector.vehicles.Remove(vehicle); Object.Destroy((Object)(object)((Component)this).gameObject); } private static bool IsCorrectVehicleType(LandVehicle vehicle) { return vehicle.vehicleCode == "veeper"; } private void ProcessAndAddToPool(LandVehicle vehicle) { //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_011a: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Unknown result type (might be due to invalid IL or missing references) //IL_0079: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Unknown result type (might be due to invalid IL or missing references) _logger.Debug($"Adding vehicle to pool: {vehicle.GUID}"); DeliveryVehicle val = ((Component)vehicle).GetComponent<DeliveryVehicle>(); if ((Object)(object)val == (Object)null) { Guid gUID = vehicle.GUID; val = new DeliveryVehicleBuilder().WithLandVehicle(vehicle).WithGuid(gUID).Build(); } vehicle.IsPlayerOwned = false; vehicle.SetIsPlayerOwned((NetworkConnection)null, false); vehicle.SetVisible(false); vehicle.IsPhysicallySimulated = false; IEnumerable<LandVehicle> source = from lv in NetworkSingleton<VehicleManager>.Instance.PlayerOwnedVehicles.AsEnumerable<LandVehicle>() where ((object)lv.GUID/*cast due to .constrained prefix*/).ToString() != ((object)vehicle.GUID/*cast due to .constrained prefix*/).ToString() select lv; NetworkSingleton<VehicleManager>.Instance.PlayerOwnedVehicles = source.ToIl2CppList(); ((Component)vehicle).transform.position = new Vector3(0f, -100f, 0f); PoolManager.Instance.AddToSaveData(val); PoolManager.Instance.AddToPool(val); _quest?.MarkAddVehicleEntryComplete(); _logger.Msg($"Vehicle added to pool. Total vehicles: {PoolManager.Instance.PoolCount}"); } public void SetupZone(Vector3 corner1, Vector3 corner2, float height = 5f, Color? visualColor = null) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0012: 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) //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0022: 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_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_008a: Unknown result type (might be due to invalid IL or missing references) //IL_0092: Unknown result type (might be due to invalid IL or missing references) //IL_0115: Unknown result type (might be due to invalid IL or missing references) //IL_012b: Unknown result type (might be due to invalid IL or missing references) //IL_00d5: Unknown result type (might be due to invalid IL or missing references) //IL_00cc: Unknown result type (might be due to invalid IL or missing references) //IL_00da: Unknown result type (might be due to invalid IL or missing references) //IL_00e0: Unknown result type (might be due to invalid IL or missing references) //IL_00e2: Unknown result type (might be due to invalid IL or missing references) Corner1 = corner1; Corner2 = corner2; Vector3 val = (corner1 + corner2) / 2f; Vector3 val2 = default(Vector3); ((Vector3)(ref val2))..ctor(Mathf.Abs(corner2.x - corner1.x), height, Mathf.Abs(corner2.z - corner1.z)); ((Component)this).transform.position = val; BoxCollider val3 = ((Component)this).gameObject.GetComponent<BoxCollider>(); if ((Object)(object)val3 == (Object)null) { val3 = ((Component)this).gameObject.AddComponent<BoxCollider>(); } ((Collider)val3).isTrigger = true; val3.size = val2; val3.center = Vector3.zero; if (ShowVisuals) { _baseColor = (Color)(((??)visualColor) ?? new Color(0f, 1f, 1f, 0.3f)); CreateVisualPlane(val2, _baseColor); } _logger.Debug($"Dropoff zone created: Center={val}, Size={val2}"); } private void CreateVisualPlane(Vector3 size, Color color) { //IL_0040: 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_0061: 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_0099: Unknown result type (might be due to invalid IL or missing references) _visualPlane = GameObject.CreatePrimitive((PrimitiveType)3); ((Object)_visualPlane).name = "DropoffZoneVisual"; _visualPlane.transform.SetParent(((Component)this).transform); _visualPlane.transform.localPosition = Vector3.zero; _visualPlane.transform.localScale = new Vector3(size.x, 0.1f, size.z); Collider component = _visualPlane.GetComponent<Collider>(); if ((Object)(object)component != (Object)null) { Object.Destroy((Object)(object)component); } ApplyDebugMaterial(_visualPlane, color); } private void ApplyDebugMaterial(GameObject obj, Color color) { //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Expected O, but got Unknown //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_00ae: Unknown result type (might be due to invalid IL or missing references) //IL_00e8: Unknown result type (might be due to invalid IL or missing references) //IL_00ee: Unknown result type (might be due to invalid IL or missing references) //IL_00f4: Unknown result type (might be due to invalid IL or missing references) //IL_00fa: Unknown result type (might be due to invalid IL or missing references) //IL_0104: Unknown result type (might be due to invalid IL or missing references) Renderer component = obj.GetComponent<Renderer>(); if ((Object)(object)component == (Object)null) { return; } Shader val = Shader.Find("Universal Render Pipeline/Lit"); if (!((Object)(object)val == (Object)null)) { _visualMaterial = new Material(val); if (_visualMaterial.HasProperty("_Surface")) { _visualMaterial.SetFloat("_Surface", 1f); } if (color.a <= 0f) { color.a = 0.3f; } if (_visualMaterial.HasProperty("_BaseColor")) { _visualMaterial.SetColor("_BaseColor", color); } if (_visualMaterial.HasProperty("_EmissionColor")) { _visualMaterial.EnableKeyword("_EMISSION"); _visualMaterial.SetColor("_EmissionColor", new Color(color.r, color.g, color.b) * 1.5f); } _visualMaterial.SetInt("_ZWrite", 0); _visualMaterial.renderQueue = 3000; component.material = _visualMaterial; } } public void SetVisualsEnabled(bool enabled) { ShowVisuals = enabled; if ((Object)(object)_visualPlane != (Object)null) { _visualPlane.SetActive(enabled); } } [HideFromIl2Cpp] public void SetQuest(DropoffQuest quest) { _quest = quest; } private void OnDestroy() { if ((Object)(object)_visualPlane != (Object)null) { Object.Destroy((Object)(object)_visualPlane); } } } public static class VehicleDropoffZoneFactory { public static VehicleDropoffZone CreateZone(Vector3 corner1, Vector3 corner2, float height = 5f, Color? visualColor = null, bool showVisuals = true, DropoffQuest? attachedQuest = null) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Expected O, but got Unknown //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) GameObject val = new GameObject("VehicleDropoffZone"); VehicleDropoffZone vehicleDropoffZone = val.AddComponent<VehicleDropoffZone>(); vehicleDropoffZone.ShowVisuals = showVisuals; vehicleDropoffZone.SetupZone(corner1, corner2, height, visualColor); if (attachedQuest != null) { vehicleDropoffZone.SetQuest(attachedQuest); } return vehicleDropoffZone; } public static VehicleDropoffZone CreateZoneNear(Transform reference, Vector3 offset, Vector3 size, Color? visualColor = null) { //IL_0002: 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) //IL_0008: 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_000e: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_001f: 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_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Unknown result type (might be due to invalid IL or missing references) Vector3 val = reference.position + offset; Vector3 corner = val - size / 2f; Vector3 corner2 = val + size / 2f; return CreateZone(corner, corner2, size.y, visualColor); } } } namespace MultiDelivery.Pool { [HarmonyPatch(typeof(DeliveryManager))] internal static class ActiveDeliveryPatch { internal static readonly Logger Logger = new Logger("ActiveDeliveryPatch"); [HarmonyPatch("GetActiveShopDelivery")] [HarmonyPostfix] private static void AllowIfPoolAssigmentAvailable(DeliveryManager __instance, DeliveryShop shop, ref DeliveryInstance __result) { if (__result == null) { return; } lock (PoolManager.Lock) { if (PoolManager.Instance.BaseVehicleAllocationsForShop.TryGetValue(shop.MatchingShopInterfaceName, out var value)) { if (Time.frameCount % 30 == 0) { Logger.Debug($"Base: {value}"); } if (!value) { __result = null; return; } DeliveryVehicle firstFree = PoolManager.Instance.GetFirstFree(); if (!((Object)(object)firstFree == (Object)null)) { Logger.Debug("Free pool vehicle found"); __result = null; } } else { DeliveryVehicle firstFree2 = PoolManager.Instance.GetFirstFree(); if (!((Object)(object)firstFree2 == (Object)null)) { Logger.Debug("Free pool vehicle found"); __result = null; } } } } [HarmonyPatch("SendDelivery")] [HarmonyPostfix] private static void AllocateVehicle(DeliveryManager __instance, DeliveryInstance delivery) { lock (PoolManager.Lock) { if (PoolManager.Instance.Allocations.ContainsKey(delivery.DeliveryID)) { Logger.Debug("Already allocated, skipping"); return; } PoolManager.Instance.BaseVehicleAllocationsForShop.TryAdd(delivery.StoreName, value: false); Logger.Debug($"Base allocation: {PoolManager.Instance.BaseVehicleAllocationsForShop[delivery.StoreName]}"); if (!PoolManager.Instance.BaseVehicleAllocationsForShop[delivery.StoreName]) { Logger.Debug("Base vehicle allocating"); DeliveryVehicle deliveryVehicle = NetworkSingleton<DeliveryManager>.Instance.GetShopInterface(delivery.StoreName).DeliveryVehicle; PoolManager.Instance.SetBaseAllocation(delivery.StoreName, allocated: true); PoolManager.Instance.Allocations.Add(delivery.DeliveryID, deliveryVehicle); VehicleSave.Instance.AddAllocation(delivery.DeliveryID, deliveryVehicle); NetworkConvenienceMethods.NotifyAllocation(delivery.DeliveryID, deliveryVehicle, isAllocated: true); } else { PoolManager.Instance.GetOrAllocateFirstFree(delivery.DeliveryID); Logger.Debug("Allocated for " + delivery.DeliveryID); } } } } [HarmonyPatch(typeof(DeliveryShop))] internal static class CapacityText { internal static readonly Logger Logger = new Logger("CapacityText"); private static ConditionalWeakTable<DeliveryShop, Text> _capacityTexts = new ConditionalWeakTable<DeliveryShop, Text>(); [HarmonyPatch("Initialize")] [HarmonyPostfix] private static void AddCapacityTextPostfix(DeliveryShop __instance) { Logger.Debug("Adding capacity text for " + ((Object)__instance).name); if (_capacityTexts.TryGetValue(__instance, out Text _)) { return; } Text deliveryTimeLabel = __instance.DeliveryTimeLabel; GameObject val = ((deliveryTimeLabel != null) ? ((Component)((Component)deliveryTimeLabel).transform.parent).gameObject : null); Logger.Debug($"Adding capacity text: {deliveryTimeLabel}, templatego: {val}"); if (val == null) { return; } GameObject val2 = Object.Instantiate<GameObject>(val); val2.transform.SetParent(val.transform.parent, false); val2.transform.SetSiblingIndex(val.transform.GetSiblingIndex()); Text component = ((Component)val2.transform.Find("Label")).GetComponent<Text>(); component.text = "Free vehicles"; Text component2 = ((Component)val2.transform.Find("Time")).GetComponent<Text>(); component2.text = "1/1"; _capacityTexts.Add(__instance, component2); List<Transform> list = val.transform.parent.FindAllTransforms("Spacer"); if (list.Count > 0) { Transform val3 = list[list.Count - 1]; if ((Object)(object)val3 != (Object)null) { ((Component)val3).gameObject.SetActive(false); } } } [HarmonyPatch("RefreshCart")] [HarmonyPostfix] private static void UpdateCapacityTextPostfix(DeliveryShop __instance) { if (!((App<DeliveryApp>)(object)PlayerSingleton<DeliveryApp>.Instance).isOpen) { Logger.Debug("Delivery app is not open, skipping capacity text update for " + ((Object)__instance).name); return; } if (!_capacityTexts.TryGetValue(__instance, out Text value)) { Logger.Debug("Capacity text for " + ((Object)__instance).name + " not found!"); return; } var (value2, value3) = GetVehiclesForShop(__instance); value.text = $"{value2}/{value3}"; } private static (int available, int total) GetVehiclesForShop(DeliveryShop shop) { lock (PoolManager.Lock) { string matchingShopInterfaceName = shop.MatchingShopInterfaceName; PoolManager pool = PoolManager.Instance; int item = 1 + pool.Pool.Count; int num = 0; if (!(pool.BaseVehicleAllocationsForShop.TryGetValue(matchingShopInterfaceName, out var value) && value)) { num++; } int num2 = pool.Allocations.Values.Count((DeliveryVehicle v) => pool.Pool.Contains(v)); int num3 = pool.Pool.Count - num2; num += num3; return (available: num, total: item); } } } [HarmonyPatch(typeof(DeliveryInstance))] internal static class DeliveryInstancePatches { internal static readonly Logger Logger = new Logger("DeliveryInstancePatches"); [HarmonyPatch("SetStatus")] [HarmonyPrefix] private static bool UsePoolSetStatus(DeliveryInstance __instance, EDeliveryStatus status) { //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0079: Unknown result type (might be due to invalid IL or missing references) //IL_0091: Unknown result type (might be due to invalid IL or missing references) //IL_0092: Unknown result type (might be due to invalid IL or missing references) //IL_0094: Unknown result type (might be due to invalid IL or missing references) //IL_0096: Unknown result type (might be due to invalid IL or missing references) //IL_0098: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Invalid comparison between Unknown and I4 //IL_009f: Unknown result type (might be due to invalid IL or missing references) //IL_00a2: Invalid comparison between Unknown and I4 lock (PoolManager.Lock) { string text = $"Setting delivery status to {status} for delivery {__instance.DeliveryID}"; Console.Log(Object.op_Implicit(text), (Object)null); Logger.Msg(text); __instance.Status = status; ShopInterface shopInterface = NetworkSingleton<DeliveryManager>.Instance.GetShopInterface(__instance.StoreName); if ((int)status != 2) { if ((int)status == 3) { if ((Object)(object)__instance.ActiveVehicle == (Object)(object)shopInterface.DeliveryVehicle) { PoolManager.Instance.SetBaseAllocation(__instance.StoreName, allocated: false); } PoolManager.Instance.FreeAllocation(__instance.DeliveryID); if ((Object)(object)__instance.ActiveVehicle != (Object)null) { __instance.ActiveVehicle.Deactivate(); } UnityEvent onDeliveryCompleted = __instance.onDeliveryCompleted; if (onDeliveryCompleted != null) { onDeliveryCompleted.Invoke(); } } } else { if (PoolManager.Instance.Allocations.TryGetValue(__instance.DeliveryID, out DeliveryVehicle value)) { __instance.ActiveVehicle = value; } else { __instance.ActiveVehicle = shopInterface.DeliveryVehicle; } __instance.ActiveVehicle.Activate(__instance); } return false; } } [HarmonyPatch("AddItemsToDeliveryVehicle")] [HarmonyPrefix] private static bool UsePoolAddItemsToDeliveryVehicle(DeliveryInstance __instance) { lock (PoolManager.Lock) { ShopInterface shopInterface = NetworkSingleton<DeliveryManager>.Instance.GetShopInterface(__instance.StoreName); if (!PoolManager.Instance.Allocations.TryGetValue(__instance.DeliveryID, out DeliveryVehicle value)) { value = ((shopInterface != null) ? shopInterface.DeliveryVehicle : null); } Il2CppReferenceArray<StringIntPair> items = __instance.Items; foreach (StringIntPair item2 in (Il2CppArrayBase<StringIntPair>)(object)items) { ItemDefinition item = Registry.GetItem(item2.String); int num = item2.Int; while (num > 0) { int num2 = Mathf.Min(num, ((BaseItemDefinition)item).StackLimit); num -= num2; ItemInstance defaultInstance = item.GetDefaultInstance(num2); value.Vehicle.Storage.InsertItem(defaultInstance, Object.op_Implicit((Object)(object)item)); } } return false; } } } [HarmonyPatch(typeof(DeliveryVehicle))] internal class DeliveryVehiclePatch { [HarmonyPatch("Deactivate")] [HarmonyPostfix] private static void NullActiveDelivery(DeliveryVehicle __instance) { DeliveryInstance activeDelivery = __instance.ActiveDelivery; object obj; if (activeDelivery == null) { obj = null; } else { LoadingDock loadingDock = activeDelivery.LoadingDock; obj = ((loadingDock != null) ? loadingDock.VehicleDetector : null); } if ((Object)obj != (Object)null) { __instance.ActiveDelivery.LoadingDock.SetOccupant((LandVehicle)null); __instance.ActiveDelivery.LoadingDock.VehicleDetector.Clear(); } if (__instance.ActiveDelivery != null) { __instance.ActiveDelivery = null; } } } [HarmonyPatch(typeof(DeliveriesLoader))] internal static class LoaderPatch { internal static readonly Logger Logger = new Logger("LoaderPatch"); [HarmonyPatch("Load")] [HarmonyPrefix] private static bool Load(DeliveriesLoader __instance, string mainPath) { bool flag = false; string text = default(string); if (((Loader)__instance).TryLoadFile(Path.Combine(mainPath, "Deliveries"), ref text, true) || ((Loader)__instance).TryLoadFile(mainPath, ref text, true)) { DeliveriesData val = null; try { val = JsonUtility.FromJson<DeliveriesData>(text); } catch (Exception ex) { Debug.LogError(Object.op_Implicit("Error loading data: " + ex.Message)); Logger.Debug("Error loading data: " + ex.Message); } if (val != null && val.ActiveDeliveries != null) { LoadDelivery(val); if (val.DeliveryVehicles != null) { flag = true; VehicleData[] array = Il2CppArrayBase<VehicleData>.op_Implicit((Il2CppArrayBase<VehicleData>)(object)val.DeliveryVehicles); VehicleData[] array2 = array; foreach (VehicleData data in array2) { LoadVehicle(data, mainPath); } } } } if (!flag && Directory.Exists(mainPath)) { Console.Log(Object.op_Implicit("Loading legacy delivery vehicles at: " + mainPath), (Object)null); string text2 = Path.Combine(mainPath, "DeliveryVehicles"); List<DirectoryInfo> directories = ((Loader)__instance).GetDirectories(text2); for (int j = 0; j < directories.Count; j++) { __instance.LoadVehicle(((FileSystemInfo)directories[j]).FullName); } } return false; } private static void LoadVehicle(VehicleData data, string path) { //IL_00c6: Unknown result type (might be due to invalid IL or missing references) Logger.Debug("Processing GUID " + data.GUID); LandVehicle val; lock (PoolManager.Lock) { DeliveryVehicle? obj = ((IEnumerable<DeliveryVehicle>)PoolManager.Instance.Pool).FirstOrDefault((Func<DeliveryVehicle, bool>)((DeliveryVehicle dv) => ((object)dv.Vehicle.GUID/*cast due to .constrained prefix*/).ToString() == data.GUID)); val = ((obj != null) ? obj.Vehicle : null); } if ((Object)(object)val == (Object)null) { Logger.Debug("GUID " + data.GUID + " not found in Pool, lookup via GUIDManager"); val = GUIDManager.GetObject<LandVehicle>(new Guid(data.GUID)); } if ((Object)(object)val == (Object)null) { Logger.Error("Vehicle not found with GUID", data.GUID); Console.LogError(Object.op_Implicit("LoadVehicle: Vehicle not found with GUID " + data.GUID), (Object)null); } else { LoadVehicle(val, data, path); } } private static void LoadVehicle(LandVehicle vehicle, VehicleData data, string containerPath) { //IL_0043: Unknown result type (might be due to invalid IL or missing references) Logger.Debug($"Processing vehicle {((Object)vehicle).name} with GUID {vehicle.GUID}"); if ((Object)(object)vehicle.Storage != (Object)null) { if (data.VehicleContents != null && data.VehicleContents.Items != null) { DeserializedItemSet val = default(DeserializedItemSet); if (ItemSet.TryDeserialize(data.VehicleContents, ref val)) { val.LoadTo(vehicle.Storage.ItemSlots); } } else if (File.Exists(Path.Combine(containerPath, "Contents.json"))) { Console.LogWarning(Object.op_Implicit("Loading legacy vehicle contents."), (Object)null); string text = default(string); DeserializedItemSet val2 = default(DeserializedItemSet); if (vehicle.Loader.TryLoadFile(containerPath, "Contents", ref text) && ItemSet.TryDeserialize(text, ref val2)) { val2.LoadTo(vehicle.Storage.ItemSlots); } } } if (data.SpraySurfaces == null) { return; } try { for (int i = 0; i < data.SpraySurfaces.Count; i++) { if (((Il2CppArrayBase<SpraySurface>)(object)vehicle._spraySurfaces).Length > i) { SpraySurface obj = ((Il2CppArrayBase<SpraySurface>)(object)vehicle._spraySurfaces)[i]; if (obj != null) { obj.Set((NetworkConnection)null, ((Il2CppObjectBase)data.SpraySurfaces[i].Strokes.ToArray()).Cast<Il2CppReferenceArray<SprayStroke>>(), data.SpraySurfaces[i].ContainsCartelGraffiti); } } } } catch (Exception ex) { Logger.Error("Exception thrown while loading saved graffities", ex); } } private static DeliveryVehicle? FindVehicleByGuid(string guid) { lock (PoolManager.Lock) { DeliveryVehicle val = ((IEnumerable<DeliveryVehicle>)PoolManager.Instance.Pool).FirstOrDefault((Func<DeliveryVehicle, bool>)((DeliveryVehicle dv) => dv.GUID.ToString() == guid)); if ((Object)(object)val != (Object)null) { return val; } } Enumerator<ShopInterface> enumerator = ShopInterface.AllShops.GetEnumerator(); while (enumerator.MoveNext()) { ShopInterface current = enumerator.Current; if ((Object)(object)current.DeliveryVehicle != (Object)null && current.DeliveryVehicle.GUID.ToString() == guid) { return current.DeliveryVehicle; } } return null; } private static void LoadDelivery(DeliveriesData data) { if (data == null) { return; } if (data.ActiveDeliveries != null) { foreach (AllocationEntry item in VehicleSave.Instance.AllocationMap.ToList()) { DeliveryVehicle val = FindVehicleByGuid(item.VehicleGUID); if ((Object)(object)val != (Object)null) { PoolManager.Instance.AllocateDirectly(item.DeliveryID, val); Logger.Debug("Restored allocation: " + item.DeliveryID + " -> " + item.VehicleGUID); } else { Logger.Warning("Could not find vehicle for allocation: " + item.VehicleGUID); } } foreach (BaseAllocationEntry item2 in VehicleSave.Instance.BaseAllocationMap.ToList()) { PoolManager.Instance.SetBaseAllocation(item2.ShopName, item2.IsAllocated); Logger.Debug($"Restored base allocation: {item2.ShopName} = {item2.IsAllocated}"); } foreach (DeliveryInstance item3 in (Il2CppArrayBase<DeliveryInstance>)(object)data.ActiveDeliveries) { NetworkSingleton<DeliveryManager>.Instance.SendDelivery(item3); } } if (data.DeliveryHistory != null) { foreach (DeliveryReceipt item4 in (Il2CppArrayBase<DeliveryReceipt>)(object)data.DeliveryHistory) { NetworkSingleton<DeliveryManager>.Instance._deliveryHistory.Add(item4); } } if (data.DisplayedDeliveryHistory == null) { return; } foreach (DeliveryReceipt item5 in (Il2CppArrayBase<DeliveryReceipt>)(object)data.DisplayedDeliveryHistory) { NetworkSingleton<DeliveryManager>.Instance._displayedDeliveryHistory.Add(item5); } } } public class PoolManager { internal static readonly Logger Logger = new Logger("PoolManager"); internal static readonly object Lock = new object(); public static PoolManager Instance { get; } = new PoolManager(); public HashSet<DeliveryVehicle> Pool { get; } = new HashSet<DeliveryVehicle>(); internal Dictionary<string, DeliveryVehicle> Allocations { get; } = new Dictionary<string, DeliveryVehicle>(); internal Dictionary<string, bool> BaseVehicleAllocationsForShop { get; } = new Dictionary<string, bool>(); internal int PoolCount { get { lock (Lock) { return Pool.Count; } } } internal int AllocationCount { get { lock (Lock) { return Allocations.Count; } } } public void AddToPool(DeliveryVehicle deliveryVehicle, bool notify = true) { lock (Lock) { Pool.Add(deliveryVehicle); if (notify) { NetworkConvenienceMethods.NotifyVehicleAdded(deliveryVehicle); } } } public void AddToSaveData(DeliveryVehicle deliveryVehicle) { if ((Object)(object)deliveryVehicle == (Object)null) { throw new ArgumentNullException("deliveryVehicle"); } VehicleSave.Instance.AddVehicle(deliveryVehicle.Vehicle); NetworkConvenienceMethods.NotifyVehicleCreated(deliveryVehicle); } public DeliveryVehicle? GetFirstFree() { lock (Lock) { DeliveryVehicle val = ((IEnumerable<DeliveryVehicle>)Pool).FirstOrDefault((Func<DeliveryVehicle, bool>)((DeliveryVehicle dv) => dv.ActiveDelivery == null && !Allocations.ContainsValue(dv))); if (Time.frameCount % 30 == 0) { Logger.Debug("Pool lookup: " + (((Object)(object)val == (Object)null) ? "null" : "not null")); } return val; } } public DeliveryVehicle GetOrAllocateFirstFree(string deliveryId) { lock (Lock) { Logger.Debug("GetOrAllocateFirstFree called for: " + deliveryId); if (Allocations.TryGetValue(deliveryId, out DeliveryVehicle value)) { Logger.Debug("Already allocated, returning existing"); return value; } DeliveryVehicle firstFree = GetFirstFree(); if ((Object)(object)firstFree == (Object)null) { Logger.Debug($"No free vehicle found! Pool count: {Pool.Count}, Allocations count: {Allocations.Count}"); foreach (KeyValuePair<string, DeliveryVehicle> allocation in Allocations) { Logger logger = Logger; object[] array = new object[1]; string key = allocation.Key; DeliveryInstance activeDelivery = allocation.Value.ActiveDelivery; array[0] = "Allocation: " + key + " -> Vehicle has ActiveDelivery: " + (((activeDelivery != null) ? activeDelivery.DeliveryID : null) ?? "null"); logger.Debug(array); } throw new ArgumentException("Tried to allocate a non-free vehicle"); } Allocations.Add(deliveryId, firstFree); Logger.Debug("Allocated new vehicle for " + deliveryId + ", vehicle: " + firstFree.GUID); VehicleSave.Instance.AddAllocation(deliveryId, firstFree); NetworkConvenienceMethods.NotifyAllocation(deliveryId, firstFree, isAllocated: true); return firstFree; } } internal void AllocateDirectly(string deliveryId, DeliveryVehicle deliveryVehicle) { lock (Lock) { Logger.Debug("AllocateDirectly called for: " + deliveryId); if (Allocations.TryGetValue(deliveryId, out DeliveryVehicle _)) { Logger.Debug("Already allocated, returning"); return; } Allocations.Add(deliveryId, deliveryVehicle); Logger.Debug("Directly allocated vehicle for " + deliveryId + ", vehicle: " + deliveryVehicle.GUID); VehicleSave.Instance.AddAllocation(deliveryId, deliveryVehicle); NetworkConvenienceMethods.NotifyAllocation(deliveryId, deliveryVehicle, isAllocated: true); } } internal void SetBaseAllocation(string shopName, bool allocated) { lock (Lock) { BaseVehicleAllocationsForShop[shopName] = allocated; } VehicleSave.Instance.SyncBaseAllocation(shopName, allocated); NetworkConvenienceMethods.NotifyBaseAllocation(shopName, allocated); } public void FreeAllocation(string deliveryId) { lock (Lock) { Logger.Debug("Free custom allocation: " + deliveryId); if (Allocations.TryGetValue(deliveryId, out DeliveryVehicle value)) { Allocations.Remove(deliveryId); VehicleSave.Instance.RemoveAllocation(deliveryId); NetworkConvenienceMethods.NotifyAllocation(deliveryId, value, isAllocated: false); } } } internal void ClearAll() { lock (Lock) { Pool.Clear(); Allocations.Clear(); BaseVehicleAllocationsForShop.Clear(); } VehicleSave instance = VehicleSave.Instance; instance.ClearAllocations(); instance.ClearBaseAllocations(); } internal bool RemoveFromPool(DeliveryVehicle vehicle) { lock (Lock) { List<string> list = (from x in Allocations where (Object)(object)x.Value == (Object)(object)vehicle select x.Key).ToList(); foreach (string item in list) { Allocations.Remove(item); VehicleSave.Instance.RemoveAllocation(item); } VehicleSave.Instance.RemoveAllocationsForVehicle(vehicle); return Pool.Remove(vehicle); } } internal DeliveryVehicle? LastPoolVehicle() { lock (Lock) { return Pool.LastOrDefault(); } } } [HarmonyPatch(typeof(Wheel))] internal class WheelPatch { [HarmonyPatch("OnWeatherChange")] [HarmonyPrefix] private static bool ExitIfNull(Wheel __instance, WeatherConditions newConditions) { if ((Object)(object)((__instance != null) ? __instance.vehicle : null) == (Object)null) { return false; } if (newConditions != null) { _ = newConditions.Rainy; if (0 == 0) { return true; } } return false; } } [HarmonyPatch(typeof(DeliveryVehicle))] internal static class DeliveryVehicleAwakePatch { [HarmonyPatch("Awake")] [HarmonyPrefix] private static bool ExitIfNull(DeliveryVehicle __instance) { Guid val = default(Guid); if (Guid.TryParse(__instance.GUID, ref val)) { return true; } if ((Object)(object)((Component)__instance).GetComponent<LandVehicle>() == (Object)null) { return false; } __instance.Vehicle = ((Component)__instance).GetComponent<LandVehicle>(); __instance.Deactivate(); return false; } } } namespace MultiDelivery.Persistence { public class PersistentDropoffQuestData : Saveable { [SaveableField("MessageData")] public bool HasMessaged; public static PersistentDropoffQuestData Instance { get; private set; } = new PersistentDropoffQuestData(); public PersistentDropoffQuestData() { Instance = this; } } [Serializable] public class AllocationEntry { public string DeliveryID; public string VehicleGUID; } [Serializable] public class BaseAllocationEntry { public string ShopName; public bool IsAllocated; } public class VehicleSave : Saveable { [SaveableField("VehicleSaveData")] private List<VehicleSaveDto> _dtos = new List<VehicleSaveDto>(); [SaveableField("AllocationMap")] private List<AllocationEntry> _allocationMap = new List<AllocationEntry>(); [SaveableField("BaseAllocationMap")] private List<BaseAllocationEntry> _baseAllocationMap = new List<BaseAllocationEntry>(); private static readonly Logger Logger = new Logger("VehicleSave"); private static Transform _parent; public override SaveableLoadOrder LoadOrder => (SaveableLoadOrder)0; public IReadOnlyList<AllocationEntry> AllocationMap => _allocationMap; public IReadOnlyList<BaseAllocationEntry> BaseAllocationMap => _baseAllocationMap; public static VehicleSave Instance { get; set; } = new VehicleSave(); public VehicleSave() { Instance = this; } public void AddVehicle(LandVehicle vehicle) { //IL_0008: 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_0035: Unknown result type (might be due to invalid IL or missing references) VehicleSaveDto item = new VehicleSaveDto { Guid = ((object)vehicle.GUID/*cast due to .constrained prefix*/).ToString(), VehicleType = vehicle.VehicleCode, Color = vehicle.Color.displayedColor }; if (!_dtos.Contains(item)) { _dtos.Add(item); } } internal void AddAllocation(string deliveryId, DeliveryVehicle vehicle) { string vehicleGUID = vehicle.GUID.ToString(); _allocationMap.RemoveAll((AllocationEntry e) => e.DeliveryID == deliveryId); _allocationMap.Add(new AllocationEntry { DeliveryID = deliveryId, VehicleGUID = vehicleGUID }); } internal void RemoveAllocation(string deliveryId) { _allocationMap.RemoveAll((AllocationEntry e) => e.DeliveryID == deliveryId); } internal void RemoveAllocationsForVehicle(DeliveryVehicle vehicle) { string guid = vehicle.GUID.ToString(); _allocationMap.RemoveAll((AllocationEntry e) => e.VehicleGUID == guid); } internal void ClearAllocations() { _allocationMap.Clear(); } internal void ClearBaseAllocations() { _baseAllocationMap.Clear(); } internal void SyncBaseAllocation(string shopName, bool allocated) { _baseAllocationMap.RemoveAll((BaseAllocationEntry e) => e.ShopName == shopName); _baseAllocationMap.Add(new BaseAllocationEntry { ShopName = shopName, IsAllocated = allocated }); } internal void RemoveVehicle(DeliveryVehicle vehicle) { _dtos.RemoveAll((VehicleSaveDto dto) => dto.Guid.ToString() == vehicle.GUID.ToString()); } protected override void OnLoaded() { //IL_007e: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Expected O, but got Unknown //IL_0160: Unknown result type (might be due to invalid IL or missing references) //IL_0174: Unknown result type (might be due to invalid IL or missing references) //IL_019f: Unknown result type (might be due to invalid IL or missing references) //IL_0218: Unknown result type (might be due to invalid IL or missing references) Logger.Msg($"OnLoaded: Loading {_dtos.Count} vehicles"); if ((Object)(object)_parent == (Object)null) { GameObject val = GameObject.Find("VehiclePool"); if ((Object)(object)val == (Object)null) { val = new GameObject("VehiclePool"); } _parent = val.transform; } Guid guid = default(Guid); for (int i = 0; i < _dtos.Count; i++) { Logger.Debug($"Loading vehicle {i}: {_dtos[i].Guid}"); ((Guid)(ref guid))..ctor(_dtos[i].Guid); LandVehicle val2 = new LandVehicleBuilder().WithVehicleCode(_dtos[i].VehicleType).WithVehicleName($"Additional Delivery Vehicle {i + 1}").WithGuid(guid) .WithColor(_dtos[i].Color) .WithParent(_parent) .Build(); MelonCoroutines.Start(DeferredSetColor(val2, _dtos[i].Color)); Logger.Debug($"Built LandVehicle: {val2.vehicleName}, ObjectId: {((NetworkBehaviour)val2).ObjectId}"); DeliveryVehicle val3 = new DeliveryVehicleBuilder().WithLandVehicle(val2).WithGuid(guid).Build(); Logger.Debug("Built DeliveryVehicle: " + val3.GUID); PoolManager.Instance.AddToPool(val3); Logger.Debug($"Added to pool. Pool count now: {PoolManager.Instance.PoolCount}"); } Logger.Msg($"OnLoaded complete. Final pool count: {PoolManager.Instance.PoolCount}"); } private IEnumerator DeferredSetColor(LandVehicle vehicle, EVehicleColor color) { //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) while ((Object)(object)Player.Local == (Object)null || (Object)(object)((Component)Player.Local).gameObject == (Object)null) { yield return null; } yield return null; vehicle.ApplyColor(color); } } public record VehicleSaveDto { public string Guid { get; set; } public string VehicleType { get; set; } public EVehicleColor Color { get; set; } } } namespace MultiDelivery.Network { public class DeliveryNetworkManager : IDeliveryNetworkManager, IDisposable { private SteamNetworkClient _client; private readonly Logger _logger; private const string ModDataKey = "MultiDelivery_Version"; private const string ModVersion = "1.0.0"; public bool IsInLobby { get { SteamNetworkClient client = _client; return client != null && client.IsInLobby; } } public bool IsHost { get { SteamNetworkClient client = _client; return client != null && client.IsHost; } } public bool IsSingleplayer => !IsInLobby; public bool HostOrSingleplayer => IsHost || IsSingleplayer; public DeliveryNetworkManager() { _logger = new Logger("Network"); } public bool Initialize() { //IL_0002: 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) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Expected O, but got Unknown //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Expected O, but got Unknown try { NetworkRules val = new NetworkRules { EnableRelay = true, AcceptOnlyFriends = false }; _client = new SteamNetworkClient(val); if (!_client.Initialize()) { _logger.Error("Failed to initialize SteamNetworkClient"); return false; } RegisterMessageHandlers(); SubscribeToEvents(); _logger.Msg("Network manager initialized successfully"); return true; } catch (Exception value) { _client = null; _logger.Error($"Failed to initialize network manager: {value}"); return false; } } public void RegisterLoggerSettings() { _logger.RaiseDebug = MultiDelivery.NetworkLogging?.Value ?? false; ((MelonEventBase<LemonAction<bool, bool>>)(object)MultiDelivery.NetworkLogging?.OnEntryValueChanged).Subscribe((LemonAction<bool, bool>)delegate(bool _, bool newValue) { _logger.RaiseDebug = newValue; }, 0, false); } private void RegisterMessageHandlers() { _client.RegisterMessageHandler<VehicleAddedMessage>((Action<VehicleAddedMessage, CSteamID>)OnVehicleAdded); _client.RegisterMessageHandler<VehicleCreatedMessage>((Action<VehicleCreatedMessage, CSteamID>)OnVehicleCreated); _client.RegisterMessageHandler<VehiclePoolSyncRequest>((Action<VehiclePoolSyncRequest, CSteamID>)OnPoolSyncRequest); _client.RegisterMessageHandler<VehiclePoolSyncResponse>((Action<VehiclePoolSyncResponse, CSteamID>)OnPoolSyncResponse); _client.RegisterMessageHandler<VehicleAllocationMessage>((Action<VehicleAllocationMessage, CSteamID>)OnVehicleAllocation); _client.RegisterMessageHandler<BaseVehicleAllocationMessage>((Action<BaseVehicleAllocationMessage, CSteamID>)OnBaseVehicleAllocation); } private void SubscribeToEvents() { _client.OnLobbyCreated += OnLobbyCreated; _client.OnLobbyJoined += OnLobbyJoined; _client.OnMemberJoined += OnMemberJoined; _client.OnLobbyLeft += OnLobbyLeft; } public void Update() { SteamNetworkClient client = _client; if (client != null) { client.ProcessIncomingMessages(); } } public void Dispose() { SteamNetworkClient client = _client; if (client != null) { client.Dispose(); } } public async void BroadcastVehicleAdded(DeliveryVehicle vehicle) { if (!IsSingleplayer) { VehicleAddedMessage message = new VehicleAddedMessage { ObjectId = ((NetworkBehaviour)vehicle.Vehicle).ObjectId, VehicleGuid = vehicle.GUID, VehicleName = vehicle.Vehicle.vehicleName, VehicleCode = vehicle.Vehicle.VehicleCode, VehicleColor = (int)vehicle.Vehicle.Color.displayedColor }; _logger.D($"Broadcasting vehicle added: ObjectId={message.ObjectId}, GUID={message.VehicleGuid}"); await _client.BroadcastMessageAsync((P2PMessage)(object)message); } } public async void BroadcastVehicleCreated(DeliveryVehicle vehicle) { if (!IsSingleplayer && !IsHost) { VehicleCreatedMessage message = new VehicleCreatedMessage { ObjectId = ((NetworkBehaviour)vehicle.Vehicle).ObjectId, VehicleGuid = vehicle.GUID }; _logger.D($"Broadcasting vehicle created: ObjectId={message.ObjectId}"); await _client.BroadcastMessageAsync((P2PMessage)(object)message); } } public async void BroadcastVehicleAllocation(string deliveryId, DeliveryVehicle vehicle, bool isAllocated) { if (!IsSingleplayer) { VehicleAllocationMessage message = new VehicleAllocationMessage { DeliveryId = deliveryId, VehicleGuid = vehicle.GUID, IsAllocated = isAllocated }; _logger.D($"Broadcasting allocation: Delivery={deliveryId}, Vehicle={vehicle.GUID}, Allocated={isAllocated}"); await _client.BroadcastMessageAsync((P2PMessage)(object)message); } } public async void BroadcastBaseVehicleAllocation(string shopName, bool isAllocated) { if (!IsSingleplayer) { BaseVehicleAllocationMessage message = new BaseVehicleAllocationMessage { ShopName = shopName, IsAllocated = isAllocated }; _logger.D($"Broadcasting base allocation: Shop={shopName}, Allocated={isAllocated}"); await _client.BroadcastMessageAsync((P2PMessage)(object)message); } } private void OnVehicleAdded(VehicleAddedMessage message, CSteamID cSteamID) { _logger.D($"Received VehicleAdded: ObjectId={message.ObjectId}, GUID={message.VehicleGuid}"); MelonCoroutines.Start(ProcessMessage()); IEnumerator ProcessMessage() { yield return ExponentialBackoff(delegate { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Unknown result type (might be due to invalid IL or missing references) Scene sceneByName = SceneManager.GetSceneByName("Main"); return ((Scene)(ref sceneByName)).isLoaded; }, 1f, 30f, 60f); yield return ExponentialBackoff(() => (Object)(object)FindLandVehicleByObjectId(message.ObjectId) != (Object)null, 1f, 30f, 60f); try { LandVehicle landVehicle = FindLandVehicleByObjectId(message.ObjectId); if ((Object)(object)landVehicle == (Object)null) { _logger.Warning($"LandVehicle with ObjectId {message.ObjectId} not found - may not have spawned yet"); } else { Guid deliveryGuid = new Guid(message.VehicleGuid); landVehicle.SetGUID(deliveryGuid); landVehicle.ApplyColor((EVehicleColor)message.VehicleColor); DeliveryVehicle deliveryVehicle = ((Component)landVehicle).GetComponent<DeliveryVehicle>() ?? new DeliveryVehicleBuilder().WithLandVehicle(landVehicle).WithGuid(deliveryGuid).Build(); PoolManager.Instance.AddToPool(deliveryVehicle, notify: false); _logger.D($"Added vehicle to pool: ObjectId={message.ObjectId}, DeliveryGUID={message.VehicleGuid}"); } } catch (Exception value) { _logger.Error($"Failed to add vehicle: {value}"); } } } private void OnVehicleCreated(VehicleCreatedMessage message, CSteamID senderId) { //IL_004b: Unknown result type (might be due to invalid IL or missing references) if (IsHost) { _logger.D($"Received vehicle created message from {senderId}"); MelonCoroutines.Start(ProcessMessage()); } IEnumerator ProcessMessage() { yield return ExponentialBackoff(delegate { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Unknown result type (might be due to invalid IL or missing references) Scene sceneByName = SceneManager.GetSceneByName("Main"); return ((Scene)(ref sceneByName)).isLoaded; }, 1f, 30f, 60f); yield return ExponentialBackoff(() => (Object)(object)FindLandVehicleByObjectId(message.ObjectId) != (Object)null, 1f, 30f, 60f); try { LandVehicle landVehicle = FindLandVehicleByObjectId(message.ObjectId); if ((Object)(object)landVehicle == (Object)null) { _logger.Error($"LandVehicle with ObjectId {message.ObjectId} not found"); } else { VehicleSave.Instance.AddVehicle(landVehicle); _logger.D($"Added created vehicle to save data: ObjectId={message.ObjectId}, GUID={message.VehicleGuid}"); } } catch (Exception value) { _logger.Error($"Failed to create vehicle: {value}"); } } } private async void OnPoolSyncRequest(VehiclePoolSyncRequest message, CSteamID senderId) { //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) if (!IsHost) { return; } _logger.D($"Received pool sync request from {senderId}"); try { VehiclePoolSyncResponse response = new VehiclePoolSyncResponse(); lock (PoolManager.Lock) { foreach (DeliveryVehicle vehicle in PoolManager.Instance.Pool) { response.Vehicles.Add(new VehiclePoolSyncResponse.VehicleData { ObjectId = ((NetworkBehaviour)vehicle.Vehicle).ObjectId, Guid = vehicle.GUID, Name = vehicle.Vehicle.vehicleName, Code = vehicle.Vehicle.VehicleCode, Color = (int)vehicle.Vehicle.Color.displayedColor }); } } _logger.D($"Sending pool sync with {response.Vehicles.Count} vehicles"); await _client.SendMessageToPlayerAsync(senderId, (P2PMessage)(object)response); } catch (Exception value) { _logger.Error($"Failed to send pool sync: {value}"); } } private void OnPoolSyncResponse(VehiclePoolSyncResponse message, CSteamID senderId) { if (!IsHost) { _logger.D($"Received pool sync response with {message.Vehicles.Count} vehicles"); MelonCoroutines.Start(ProcessMessage()); } IEnumerator ProcessMessage() { yield return ExponentialBackoff(delegate { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Unknown result type (might be due to invalid IL or missing references) Scene sceneByName = SceneManager.GetSceneByName("Main"); return ((Scene)(ref sceneByName)).isLoaded; }, 1f, 30f, 60f); yield return ExponentialBackoff(() => message.Vehicles.All((VehiclePoolSyncResponse.VehicleData v) => (Object)(object)FindLandVehicleByObjectId(v.ObjectId) != (Object)null), 1f, 30f, 60f); try { foreach (VehiclePoolSyncResponse.VehicleData vehicleData in message.Vehicles) { LandVehicle landVehicle = FindLandVehicleByObjectId(vehicleData.ObjectId); if ((Object)(object)landVehicle == (Object)null) { _logger.Warning($"LandVehicle with ObjectId {vehicleData.ObjectId} not found - skipping"); } else { Guid deliveryGuid = new Guid(vehicleData.Guid); landVehicle.SetGUID(deliveryGuid); landVehicle.ApplyColor((EVehicleColor)vehicleData.Color); DeliveryVehicle deliveryVehicle = ((Component)landVehicle).GetComponent<DeliveryVehicle>() ?? new DeliveryVehicleBuilder().WithLandVehicle(landVehicle).WithGuid(deliveryGuid).Build(); PoolManager.Instance.AddToPool(deliveryVehicle, notify: false); } } _logger.D($"Pool synchronized: {PoolManager.Instance.Pool.Count} vehicles"); } catch (Exception value) { _logger.Error($"Failed to sync pool: {value}"); } } } private void OnVehicleAllocation(VehicleAllocationMessage message, CSteamID senderId) { _logger.D($"Received allocation: Delivery={message.DeliveryId}, Vehicle={message.VehicleGuid}, Allocated={message.IsAllocated}"); MelonCoroutines.Start(ProcessMessage()); IEnumerator ProcessMessage() { yield return ExponentialBackoff(delegate { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Unknown result type (might be due to invalid IL or missing references) Scene sceneByName = SceneManager.GetSceneByName("Main"); return ((Scene)(ref sceneByName)).isLoaded; }, 1f, 30f, 60f); yield return ExponentialBackoff(delegate { lock (PoolManager.Lock) { if ((Object)(object)((IEnumerable<DeliveryVehicle>)PoolManager.Instance.Pool).FirstOrDefault((Func<DeliveryVehicle, bool>)((DeliveryVehicle v) => v.GUID == message.VehicleGuid)) != (Object)null) { return true; } } Enumerator<ShopInterface> enumerator2 = ShopInterface.AllShops.GetEnumerator(); while (enumerator2.MoveNext()) { ShopInterface current = enumerator2.Current; if ((Object)(object)current.DeliveryVehicle != (Object)null && current.DeliveryVehicle.GUID == message.VehicleGuid) { return true; } } return false; }, 1f, 30f, 60f); try { DeliveryVehicle vehicle; lock (PoolManager.Lock) { vehicle = ((IEnumerable<DeliveryVehicle>)PoolManager.Instance.Pool).FirstOrDefault((Func<DeliveryVehicle, bool>)((DeliveryVehicle v) => v.GUID == message.VehicleGuid)); } if ((Object)(object)vehicle == (Object)null) { Enumerator<ShopInterface> enumerator = ShopInterface.AllShops.GetEnumerator(); while (enumerator.MoveNext()) { ShopInterface shop = enumerator.Current; if ((Object)(object)shop.DeliveryVehicle != (Object)null && shop.DeliveryVehicle.GUID == message.VehicleGuid) { vehicle = shop.DeliveryVehicle; break; } } } if ((Object)(object)vehicle == (Object)null) { _logger.Warning("Vehicle not found for allocation: " + message.VehicleGuid); } else { lock (PoolManager.Lock) { if (message.IsAllocated) { _logger.D("Trying to allocate " + message.VehicleGuid + " for delivery " + message.DeliveryId); if (!PoolManager.Instance.Allocations.ContainsKey(message.DeliveryId)) { _logger.D("Allocating " + message.VehicleGuid + " for delivery " + message.DeliveryId); PoolManager.Instance.Allocations.Add(message.DeliveryId, vehicle); } } else { _logger.D("Deallocating " + message.VehicleGuid + " for delivery " + message.DeliveryId); PoolManager.Instance.Allocations.Remove(message.DeliveryId); } } } } catch (Exception value) { _logger.Error($"Failed to process allocation: {value}"); } } } private void OnBaseVehicleAllocation(BaseVehicleAllocationMessage message, CSteamID senderId) { _logger.D($"Received base allocation: Shop={message.ShopName}, Allocated={message.IsAllocated}"); lock (PoolManager.Lock) { try { PoolManager.Instance.BaseVehicleAllocationsForShop[message.ShopName] = message.IsAllocated; } catch (Exception value) { _logger.Error($"Failed to process base allocation: {value}"); } } } private void OnLobbyCreated(object sender, LobbyCreatedEventArgs e) { //IL_002e: Unknown result type (might be due to invalid IL or missing references) _logger.D($"Lobby created: {e.Lobby.LobbyId}"); _client.SetMyData("MultiDelivery_Version", "1.0.0"); } private void OnLobbyJoined(object sender, LobbyJoinedEventArgs e) { //IL_002e: Unknown result type (might be due to invalid IL or missing references) _logger.D($"Joined lobby: {e.Lobby.LobbyId}"); _client.SetMyData("MultiDelivery_Version", "1.0.0"); if (!IsHost) { RequestPoolSync(); } } private async void OnMemberJoined(object sender, MemberJoinedEventArgs e) { if (!IsHost) { return; } _logger.D("Member joined: " + e.Member.DisplayName); await Task.Delay(1000); VehiclePoolSyncResponse response = new VehiclePoolSyncResponse(); lock (PoolManager.Lock) { foreach (DeliveryVehicle vehicle in PoolManager.Instance.Pool) { response.Vehicles.Add(new VehiclePoolSyncResponse.VehicleData { ObjectId = ((NetworkBehaviour)vehicle.Vehicle).ObjectId, Guid = vehicle.GUID, Name = vehicle.Vehicle.vehicleName, Code = vehicle.Vehicle.VehicleCode, Color = (int)vehicle.Vehicle.Color.displayedColor }); } } _logger.D($"Sending pool sync to new member ({response.Vehicles.Count} vehicles)"); await _client.SendMessageToPlayerAsync(e.Member.SteamId, (P2PMessage)(object)response); } private void OnLobbyLeft(object sender, LobbyLeftEventArgs e) { _logger.D("Left lobby: " + e.Reason); } private async void RequestPoolSync() { if (!IsSingleplayer && !IsHost) { List<MemberInfo> members = _client.GetLobbyMembers(); MemberInfo host = ((IEnumerable<MemberInfo>)members).FirstOrDefault((Func<MemberInfo, bool>)((MemberInfo m) => m.IsOwner)); if (host == null) { _logger.Error("Cannot request pool sync - no host found"); return; } VehiclePoolSyncRequest message = new VehiclePoolSyncRequest { RequesterId = ((object)_client.LocalPlayerId/*cast due to .constrained prefix*/).ToString() }; _logger.D("Requesting pool sync from host"); await _client.SendMessageToPlayerAsync(host.SteamId, (P2PMessage)(object)message); } } private LandVehicle? FindLandVehicleByObjectId(int objectId) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) Scene sceneByName = SceneManager.GetSceneByName("Main"); if (!((Scene)(ref sceneByName)).isLoaded) { _logger.Error("Main scene is not loaded"); return null; } Il2CppReferenceArray<GameObject> rootGameObjects = ((Scene)(ref sceneByName)).GetRootGameObjects(); foreach (GameObject item in (Il2CppArrayBase<GameObject>)(object)rootGameObjects) { Il2CppArrayBase<LandVehicle> componentsInChildren = item.GetComponentsInChildren<LandVehicle>(true); foreach (LandVehicle item2 in componentsInChildren) { if (((NetworkBehaviour)item2).ObjectId == objectId) { _logger.D($"Found LandVehicle: ObjectId={objectId}, Name={item2.vehicleName}"); return item2; } } } _logger.Warning($"LandVehicle with ObjectId {objectId} not found in Main scene"); return null; } private static IEnumerator ExponentialBackoff(Func<bool> predicate, float initialDelay, float finalDelay, float timeout) { float delay = initialDelay; float elapsed = 0f; while (!predicate() && elapsed < timeout) { yield return (object)new WaitForSeconds(delay); elapsed += delay; delay = Mathf.Min(delay * 2f, finalDelay); } } } public interface IDeliveryNetworkManager : IDisposable { bool IsInLobby { get; } bool IsHost { get; } bool IsSingleplayer { get; } bool HostOrSingleplayer { get; } void RegisterLoggerSettings(); bool Initialize(); void Update(); void BroadcastVehicleAdded(DeliveryVehicle vehicle); void BroadcastVehicleCreated(DeliveryVehicle vehicle); void BroadcastVehicleAllocation(string deliveryId, DeliveryVehicle vehicle, bool isAllocated); void BroadcastBaseVehicleAllocation(string shopName, bool isAllocated); } public class VehicleAddedMessage : P2PMessage { public override string MessageType => "VehicleAdded"; public int ObjectId { get; set; } public string VehicleGuid { get; set; } public string VehicleName { get; set; } public string VehicleCode { get; set; } public int VehicleColor { get; set; } public override byte[] Serialize() { string s = ((P2PMessage)this).CreateJsonBase($"\"ObjectId\":{ObjectId},\"VehicleGuid\":\"{VehicleGuid}\",\"VehicleName\":\"{VehicleName}\",\"VehicleCode\":\"{VehicleCode}\",\"VehicleColor\":{VehicleColor}"); return Encoding.UTF8.GetBytes(s); } public override void Deserialize(byte[] data) { string text = Encoding.UTF8.GetString(data); ((P2PMessage)this).ParseJsonBase(text); ObjectId = int.Parse(((P2PMessage)this).ExtractJsonValue(text, "ObjectId")); VehicleGuid = ((P2PMessage)this).ExtractJsonValue(text, "VehicleGuid"); VehicleName = ((P2PMessage)this).ExtractJsonValue(text, "VehicleName"); VehicleCode = ((P2PMessage)this).ExtractJsonValue(text, "VehicleCode"); VehicleColor = int.Parse(((P2PMessage)this).ExtractJsonValue(text, "VehicleColor")); } } public class VehicleCreatedMessage : P2PMessage { public override string MessageType => "VehicleCreated"; public int ObjectId { get; set; } public string VehicleGuid { get; set; } public override byte[] Serialize() { string s = ((P2PMessage)this).CreateJsonBase($"\"ObjectId\":{ObjectId},\"VehicleGuid\":\"{VehicleGuid}\""); return Encoding.UTF8.GetBytes(s); } public override void Deserialize(byte[] data) { string text = Encoding.UTF8.GetString(data); ((P2PMessage)this).ParseJsonBase(text); ObjectId = int.Parse(((P2PMessage)this).ExtractJsonValue(text, "ObjectId")); VehicleGuid = ((P2PMessage)this).ExtractJsonValue(text, "VehicleGuid"); } } public class VehiclePoolSyncRequest : P2PMessage { public override string MessageType => "VehiclePoolSyncRequest"; public string RequesterId { get; set; } public override byte[] Serialize() { string s = ((P2PMessage)this).CreateJsonBase("\"RequesterId\":\"" + RequesterId + "\""); return Encoding.UTF8.GetBytes(s); } public override void Deserialize(byte[] data) { string text = Encoding.UTF8.GetString(data); ((P2PMessage)this).ParseJsonBase(text); RequesterId = ((P2PMessage)this).ExtractJsonValue(text, "RequesterId"); } } public class VehiclePoolSyncResponse : P2PMessage { [Serializable] public class VehicleData { public int ObjectId { get; set; } public string Guid { get; set; } public string Name { get; set; } public string Code { get; set; } public int Color { get; set; } } public override string MessageType => "VehiclePoolSyncResponse"; public List<VehicleData> Vehicles { get; set; } = new List<VehicleData>(); public override byte[] Serialize() { string text = string.Join(",", Vehicles.Select((VehicleData v) => $"{{\"ObjectId\":{v.ObjectId},\"Guid\":\"{v.Guid}\",\"Name\":\"{v.Name}\",\"Code\":\"{v.Code}\",\"Color\":{v.Color}}}")); string s = ((P2PMessage)this).CreateJsonBase("\"Vehicles\":[" + text + "]"); return Encoding.UTF8.GetBytes(s); } public override void Deserialize(byte[] data) { string text = Encoding.UTF8.GetString(data); ((P2PMessage)this).ParseJsonBase(text); int num = text.IndexOf("\"Vehicles\":[", StringComparison.Ordinal) + 12; int num2 = text.IndexOf("]", num, StringComparison.Ordinal); string text2 = text.Substring(num, num2 - num); Vehicles.Clear(); if (string.IsNullOrWhiteSpace(text2)) { return; } string[] array = text2.Split(new string[1] { "}," }, StringSplitOptions.None); string[] array2 = array; foreach (string text3 in array2) { string text4 = text3.TrimStart('{').TrimEnd('}') + "}"; if (text4.Contains("ObjectId")) { Vehicles.Add(new VehicleData { ObjectId = int.Parse(((P2PMessage)this).ExtractJsonValue(text4, "ObjectId")), Guid = ((P2PMessage)this).ExtractJsonValue(text4, "Guid"), Name = ((P2PMessage)this).ExtractJsonValue(text4, "Name"), Code = ((P2PMessage)this).ExtractJsonValue(text4, "Code"), Color = int.Parse(((P2PMessage)this).ExtractJsonValue(text4, "Color")) }); } } } } public class VehicleAllocationMessage : P2PMessage { public override string MessageType => "VehicleAllocation"; public string DeliveryId { get; set; } public string VehicleGuid { get; set; } public bool IsAllocated { get; set; } public override byte[] Serialize() { string s = ((P2PMessage)this).CreateJsonBase($"\"DeliveryId\":\"{DeliveryId}\",\"VehicleGuid\":\"{VehicleGuid}\",\"IsAllocated\":{IsAllocated.ToString().ToLower()}"); return Encoding.UTF8.GetBytes(s); } public override void Deserialize(byte[] data) { string text = Encoding.UTF8.GetString(data); ((P2PMessage)this).ParseJsonBase(text); DeliveryId = ((P2PMessage)this).ExtractJsonValue(text, "DeliveryId"); VehicleGuid = ((P2PMessage)this).ExtractJsonValue(text, "VehicleGuid"); IsAllocated = bool.Parse(((P2PMessage)this).ExtractJsonValue(text, "IsAllocated")); } } public class BaseVehicleAllocationMessage : P2PMessage { public override string MessageType => "BaseVehicleAllocation"; public string ShopName { get; set; } public bool IsAllocated { get; set; } public override byte[] Serialize() { string s = ((P2PMessage)this).CreateJsonBase("\"ShopName\":\"" + ShopName + "\",\"IsAllocated\":" + IsAllocated.ToString().ToLower()); return Encoding.UTF8.GetBytes(s); } public override void Deserialize(byte[] data) { string text = Encoding.UTF8.GetString(data); ((P2PMessage)this).ParseJsonBase(text); ShopName = ((P2PMessage)this).ExtractJsonValue(text, "ShopName"); IsAllocated = bool.Parse(((P2PMessage)this).ExtractJsonValue(text, "IsAllocated")); } } public static class NetworkConvenienceMethods { private static IDeliveryNetworkManager? _networkManager; public static bool HostOrSingleplayer => _networkManager?.HostOrSingleplayer ?? true; public static void InitializeNetworking(IDeliveryNetworkManager networkManager) { _networkManager = networkManager; } public static void NotifyVehicleAdded(DeliveryVehicle vehicle) { _networkManager?.BroadcastVehicleAdded(vehicle); } public static void NotifyVehicleCreated(DeliveryVehicle vehicle) { _networkManager?.BroadcastVehicleCreated(vehicle); } public static void NotifyAllocation(string deliveryId, DeliveryVehicle vehicle, bool isAllocated) { _networkManager?.BroadcastVehicleAllocation(deliveryId, vehicle, isAllocated); } public static void NotifyBaseAllocation(string shopName, bool isAllocated) { _networkManager?.BroadcastBaseVehicleAllocation(shopName, isAllocated); } } internal static class NetworkLoader { private static readonly Logger Logger = new Logger("NetworkLoader"); internal static IDeliveryNetworkManager? Create() { if (AppDomain.CurrentDomain.GetAssemblies().All((Assembly a) => a.GetName().Name != "SteamNetworkLib")) { return null; } return CreateInternal(); } [MethodImpl(MethodImplOptions.NoInlining)] private static IDeliveryNetworkManager? CreateInternal() { try { DeliveryNetworkManager deliveryNetworkManager = new DeliveryNetworkManager(); deliveryNetworkManager.Initialize(); return deliveryNetworkManager; } catch (Exception value) { Logger.Warning($"Failed to initialize network manager: {value}. If you're playing singleplayer, you may disregard this message."); return null; } } } } namespace MultiDelivery.Helpers { public class DependenciesChecker { public class MissingDependencyInfo(string name, string version, List<DependencyUrl> urls, bool isRequired, string reason) { public string Name { get; set; } = name; public string Version { get; set; } = version; public List<DependencyUrl> Urls { get; set; } = urls; public bool IsRequired { get; set; } = isRequired; public string Reason { get; set; } = reason; } private static readonly Logger Logger = new Logger("DependenciesChecker"); private readonly List<DependencyInfo> _dependencies = new List<DependencyInfo>(); private List<MissingDependencyInfo> _missingDependencies = new List<MissingDependencyInfo>(); public bool ShowMenuBanner { get; set; } = true; public bool ShowBannerIfOptionalOnly { get; set; } = false; public bool UnloadIfMissingRequired { get; set; } = false; public void AddDependency(DependencyInfo dependency) { _dependencies.Add(dependency); } public void ProcessAndAlert() { CheckDependencies(); if (_missingDependencies.Count != 0) { PrintMissing(); if (ShowMenuBanner) { ShowBanner(); } MelonCoroutines.Start(DelayedUnload()); } } private void CheckDependencies() { _missingDependencies = new List<MissingDependencyInfo>(); foreach (DependencyInfo dependency in _dependencies) { MissingDependencyInfo missingDependencyInfo = IsPresent(dependency); if (missingDependencyInfo != null) { _missingDependencies.Add(missingDependencyInfo); } } } private void PrintMissing() { Logger.Msg("Missing dependencies:"); foreach (MissingDependencyInfo missingDependency in _missingDependencies) { if (missingDependency.IsRequired) { Logger.Error($"- {missingDependency.Name} (min version {missingDependency.Version}): required - {missingDependency.Reason}"); } else { Logger.Warning($"- {missingDependency.Name} (min version {missingDependency.Version}): optional - {missingDependency.Reason}"); } if (missingDependency.Urls.Count == 0) { continue; } Logger.Msg(" Possible sources:"); foreach (DependencyUrl url in missingDependency.Urls) { Logger.Msg(" - " + url.SourceName + ": " + url.Url); } } } private void ShowBanner() { ((MelonEventBase<LemonAction<int, string>>)(object)MelonEvents.OnSceneWasLoaded).Subscribe((LemonAction<int, string>)delegate(int _, string name) { if (!(name != "Menu")) { MissingDepsPanelCreator.Show(this, _missingDependencies); } }, 0, false); ((MelonEventBase<LemonAction<int, string>>)(object)MelonEvents.OnSceneWasUnloaded).Subscribe((LemonAction<int, string>)delegate(int _, string name) { if (!(name != "Menu")) { MissingDepsPanelCreator.Hide(); } }, 0, false); MissingDepsPanelCreator.Show(this, _missingDependencies); } private IEnumerator DelayedUnload() { yield return (object)new WaitForSeconds(5f); if (UnloadIfMissingRequired && _mis
MultiDelivery-Mono.dll
Decompiled 2 days ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Text; using System.Threading.Tasks; using FishNet; using FishNet.Connection; using FishNet.Object; using HarmonyLib; using MelonLoader; using MelonLoader.Preferences; using MelonLoader.Utils; using Microsoft.CodeAnalysis; using MultiDelivery; using MultiDelivery.Builders; using MultiDelivery.GameConsole.Commands; using MultiDelivery.GameConsole.Commands.Help; using MultiDelivery.GameConsole.Commands.Pool; using MultiDelivery.GameConsole.Commands.Quest; using MultiDelivery.GameConsole.Core; using MultiDelivery.Helpers; using MultiDelivery.Network; using MultiDelivery.Persistence; using MultiDelivery.Pool; using MultiDelivery.Quest; using S1API.Console; using S1API.Dialogues; using S1API.Entities; using S1API.Entities.Dialogue; using S1API.Entities.NPCs.Suburbia; using S1API.Internal.Abstraction; using S1API.Leveling; using S1API.Messaging; using S1API.Money; using S1API.Quests; using S1API.Quests.Constants; using S1API.Saveables; using S1API.Utils; using ScheduleOne; using ScheduleOne.Core.Items.Framework; using ScheduleOne.Core.Weather; using ScheduleOne.Delivery; using ScheduleOne.DevUtilities; using ScheduleOne.Graffiti; using ScheduleOne.ItemFramework; using ScheduleOne.Map; using ScheduleOne.Money; using ScheduleOne.NPCs.CharacterClasses; using ScheduleOne.Persistence.Datas; using ScheduleOne.Persistence.Loaders; using ScheduleOne.PlayerScripts; using ScheduleOne.UI; using ScheduleOne.UI.Phone.Delivery; using ScheduleOne.UI.Shop; using ScheduleOne.Vehicles; using ScheduleOne.Vehicles.Modification; using Semver; using SteamNetworkLib; using SteamNetworkLib.Core; using SteamNetworkLib.Events; using SteamNetworkLib.Models; using Steamworks; using UnityEngine; using UnityEngine.Events; using UnityEngine.SceneManagement; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)] [assembly: MelonInfo(typeof(global::MultiDelivery.MultiDelivery), "MultiDelivery", "1.0.5", "k073l", null)] [assembly: MelonColor(1, 0, 255, 0)] [assembly: MelonGame("TVGS", "Schedule I")] [assembly: MelonPlatformDomain(/*Could not decode attribute arguments.*/)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: IgnoresAccessChecksTo("Assembly-CSharp")] [assembly: AssemblyCompany("MultiDelivery-Mono")] [assembly: AssemblyConfiguration("Mono")] [assembly: AssemblyDescription("Add vehicles, order multiple deliveries from the same place!")] [assembly: AssemblyFileVersion("1.0.5.0")] [assembly: AssemblyInformationalVersion("1.0.5+1e3711e3b7b873b5063b983a5dfae1d74542cb89")] [assembly: AssemblyProduct("MultiDelivery-Mono")] [assembly: AssemblyTitle("MultiDelivery-Mono")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.0.5.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace MultiDelivery { public class Logger { private Instance? _melonLogger; private static bool _initialized; private static readonly object InitLock = new object(); private static ConstructorInfo? _melonLoggerCtor; private static object? _defaultTextColor; private static Type? _defaultTextColorType; private static object? _melonNsColor; private static MethodInfo? _msgMethod; public string? Namesection { get; set; } public bool RaiseTrace { get; set; } public bool RaiseDebug { get; set; } private static string? RootNamespace => typeof(Logger).Namespace?.Split('.').FirstOrDefault(); public Logger(string? namesection = null) { if (namesection == null) { Namesection = typeof(Logger).Namespace?.Split('.').FirstOrDefault(); } else if (!string.IsNullOrEmpty(RootNamespace) && !namesection.StartsWith(RootNamespace)) { Namesection = RootNamespace + "." + namesection; } else { Namesection = namesection; } SetupInstance(); } public Logger(Instance melonLogger) { FieldInfo field = typeof(Instance).GetField("Name", BindingFlags.Instance | BindingFlags.NonPublic); if (field != null) { Namesection = field.GetValue(melonLogger) as string; } SetupInstance(); } public static implicit operator Logger(Instance melonLogger) { return new Logger(melonLogger); } public void Trace(params object[] args) { Log(LogLevel.Trace, null, args); } public void Debug(params object[] args) { Log(LogLevel.Debug, null, args); } public void T(params object[] args) { Log(LogLevel.Trace, null, args); } public void D(params object[] args) { Log(LogLevel.Debug, null, args); } public void Info(params object[] args) { Log(LogLevel.Info, null, args); } public void Msg(params object[] args) { Log(LogLevel.Info, null, args); } public void I(params object[] args) { Log(LogLevel.Info, null, args); } public void M(params object[] args) { Log(LogLevel.Info, null, args); } public void Warning(params object[] args) { Log(LogLevel.Warning, null, args); } public void Warn(params object[] args) { Log(LogLevel.Warning, null, args); } public void W(params object[] args) { Log(LogLevel.Warning, null, args); } public void Error(params object[] args) { Log(LogLevel.Error, null, args); } public void Err(params object[] args) { Log(LogLevel.Error, null, args); } public void E(params object[] args) { Log(LogLevel.Error, null, args); } public void Fatal(params object[] args) { Log(LogLevel.BigError, null, args); } public void BigError(params object[] args) { Log(LogLevel.BigError, null, args); } public void F(params object[] args) { Log(LogLevel.BigError, null, args); } public void BE(params object[] args) { Log(LogLevel.BigError, null, args); } public void InfoColored(object txtColor, params object[] args) { Log(LogLevel.Info, txtColor, args); } public void MsgColored(object txtColor, params object[] args) { Log(LogLevel.Info, txtColor, args); } public void IC(object txtColor, params object[] args) { Log(LogLevel.Info, txtColor, args); } public void MC(object txtColor, params object[] args) { Log(LogLevel.Info, txtColor, args); } public void Log(LogLevel level, string message) { Log(level, null, message); } private static void EnsureInitialized() { if (_initialized) { return; } lock (InitLock) { if (_initialized) { return; } Type typeFromHandle = typeof(MelonLogger); FieldInfo field = typeFromHandle.GetField("DefaultTextColor", BindingFlags.Static | BindingFlags.Public); _defaultTextColorType = field?.FieldType; if (_defaultTextColorType == null) { MelonLogger.Error("Could not determine the type of DefaultTextColor"); return; } _defaultTextColor = field?.GetValue(null); Type typeFromHandle2 = typeof(Instance); _melonLoggerCtor = typeFromHandle2.GetConstructor(new Type[2] { typeof(string), _defaultTextColorType }); Type melonBaseType = typeof(MelonBase); Assembly executingAssembly = Assembly.GetExecutingAssembly(); Type[] source; try { source = executingAssembly.GetTypes(); } catch (ReflectionTypeLoadException ex) { source = ex.Types.OfType<Type>().ToArray(); } Type type = source.FirstOrDefault((Type t) => melonBaseType.IsAssignableFrom(t) && !t.IsAbstract); Type type2 = typeof(Melon<>).MakeGenericType(type ?? throw new InvalidOperationException("No MelonBase derived type found in the current assembly.")); PropertyInfo property = type2.GetProperty("Instance", BindingFlags.Static | BindingFlags.Public); if (property != null) { PropertyInfo property2 = property.PropertyType.GetProperty("ConsoleColor", BindingFlags.Instance | BindingFlags.Public); if (property2 != null) { _melonNsColor = property2.GetValue(property.GetValue(null)); } } if (_melonNsColor == null) { FieldInfo field2 = typeFromHandle.GetField("DefaultMelonColor", BindingFlags.Static | BindingFlags.Public); if (field2 != null) { _melonNsColor = field2.GetValue(null); } } _msgMethod = typeFromHandle2.GetMethod("Msg", BindingFlags.Instance | BindingFlags.Public, null, new Type[2] { _defaultTextColorType, typeof(string) }, null); if (_melonLoggerCtor == null) { MelonLogger.Error("Could not find constructor for MelonLogger.Instance(string, <colorType>)"); } if (_defaultTextColor == null) { MelonLogger.Error("Could not find DefaultTextColor"); } if (_melonNsColor == null) { MelonLogger.Error("Could not find Melon<>.Instance.ConsoleColor or MelonLogger.DefaultMelonColor"); } if (_msgMethod == null) { MelonLogger.Error("Could not find MelonLogger.Instance.Msg(<colorType>, string)"); } _initialized = true; } } private static string GetCallerInfo() { StackTrace stackTrace = new StackTrace(); for (int i = 3; i < stackTrace.FrameCount; i++) { StackFrame frame = stackTrace.GetFrame(i); MethodBase method = frame.GetMethod(); if (method?.DeclaringType != null) { return method.DeclaringType.FullName + "." + method.Name; } } return "unknown"; } private void SetupInstance() { //IL_0060: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Expected O, but got Unknown if (string.IsNullOrEmpty(Namesection)) { Namesection = "Logger"; } EnsureInitialized(); object obj = _melonLoggerCtor?.Invoke(new object[2] { Namesection, _melonNsColor }); if (obj == null) { throw new NullReferenceException("Could not create MelonLogger.Instance"); } _melonLogger = (Instance)obj; } private void Log(LogLevel level, object? txtColor, params object[] args) { if (args.Length == 0) { return; } string text; if (args.Length == 1) { text = args[0]?.ToString() ?? ""; } else { string format = args[0]?.ToString() ?? ""; text = string.Format(format, args[1..]); } object obj = txtColor ?? _defaultTextColor; switch (level) { case LogLevel.Trace: { string text2 = "[TRACE " + GetCallerInfo() + "] " + text; if (RaiseTrace) { _msgMethod?.Invoke(_melonLogger, new object[2] { obj, text2 }); } else { MelonDebug.Msg(text2); } break; } case LogLevel.Debug: { string text3 = "[DEBUG " + GetCallerInfo() + "] " + text; if (RaiseDebug) { _msgMethod?.Invoke(_melonLogger, new object[2] { obj, text3 }); } else { MelonDebug.Msg(text3); } break; } default: _msgMethod?.Invoke(_melonLogger, new object[2] { obj, text }); break; case LogLevel.Warning: { Instance? melonLogger3 = _melonLogger; if (melonLogger3 != null) { melonLogger3.Warning(text); } break; } case LogLevel.Error: { Instance? melonLogger2 = _melonLogger; if (melonLogger2 != null) { melonLogger2.Error(text); } break; } case LogLevel.BigError: { Instance? melonLogger = _melonLogger; if (melonLogger != null) { melonLogger.BigError(text); } break; } } } } public enum LogLevel { Trace, Debug, Info, Warning, Error, BigError } public static class BuildInfo { public const string Name = "MultiDelivery"; public const string Description = "Add vehicles, order multiple deliveries from the same place!"; public const string Author = "k073l"; public const string Version = "1.0.5"; } public class MultiDelivery : MelonMod { private static Logger Logger; private IDeliveryNetworkManager? _networkManager; private bool _networkManagerFailed; internal static MelonPreferences_Category Category = MelonPreferences.CreateCategory("MultiDeliverySettings", "MultiDelivery's Settings"); internal static MelonPreferences_Entry<bool> NetworkLogging; internal static MelonPreferences_Entry<bool> PoolDebugLogging; internal static MelonPreferences_Entry<bool> QuestDebugLogging; public override void OnInitializeMelon() { Logger = ((MelonBase)this).LoggerInstance; Logger.Msg("MultiDelivery initialized"); NetworkLogging = Category.CreateEntry<bool>("NetworkDebugLogs", false, "Enable Network Logs", "Display networking-related debug logs in MelonLoader console/log file (may be verbose)", false, false, (ValueValidator)null, (string)null); PoolDebugLogging = Category.CreateEntry<bool>("PoolDebugLogs", false, "Enable Pool Debug Logs", "Display pool-related debug logs in MelonLoader console/log file (may be verbose)", false, false, (ValueValidator)null, (string)null); QuestDebugLogging = Category.CreateEntry<bool>("QuestDebugLogs", false, "Enable Quest Debug Logs", "Display quest-related debug logs in MelonLoader console/log file (may be verbose)", false, false, (ValueValidator)null, (string)null); RegisterLoggerSettings(); CheckDependencies(); MelonCoroutines.Start(InitializeNetworkManager()); } public override void OnSceneWasLoaded(int buildIndex, string sceneName) { QuestSetupManager.OnSceneWasLoaded(buildIndex, sceneName); if (!(sceneName != "Menu")) { PoolManager.Instance.ClearAll(); } } private IEnumerator InitializeNetworkManager() { if (SteamAPI.Init()) { yield return null; _networkManager = NetworkLoader.Create(); if (_networkManager == null) { Logger.Warning("NetworkManager is null, multiplayer features will be unavailable."); } else { NetworkConvenienceMethods.InitializeNetworking(_networkManager); _networkManager.RegisterLoggerSettings(); } } } public override void OnUpdate() { if (_networkManagerFailed || _networkManager == null) { return; } try { _networkManager.Update(); } catch (Exception ex) { MelonLogger.Error("Network manager update failed: " + ex.Message + "\n\nYou can ignore this error if you plan on playing singleplayer only and don't want to install SteamNetworkLib"); _networkManagerFailed = true; } } public override void OnDeinitializeMelon() { _networkManager?.Dispose(); Logger.Msg("MultiDelivery deinitialized"); } private static void CheckDependencies() { DependenciesChecker dependenciesChecker = new DependenciesChecker { ShowMenuBanner = true, UnloadIfMissingRequired = true }; dependenciesChecker.AddDependency(new DependencyInfo { Name = "S1API (Forked)", AssemblyName = "S1API", IsRequired = true, Version = "3.0.1", Urls = new List<DependencyUrl>(3) { new DependencyUrl { SourceName = "Thunderstore", Url = "https://thunderstore.io/c/schedule-i/p/ifBars/S1API_Forked/" }, new DependencyUrl { SourceName = "NexusMods", Url = "https://www.nexusmods.com/schedule1/mods/1194" }, new DependencyUrl { SourceName = "Github", Url = "https://github.com/ifBars/S1API/releases/" } } }); dependenciesChecker.AddDependency(new DependencyInfo { Name = "SteamNetworkLib", AssemblyName = "SteamNetworkLib", IsRequired = false, Version = "1.2.1", Urls = new List<DependencyUrl>(4) { new DependencyUrl { SourceName = "Thunderstore Il2Cpp", Url = "https://thunderstore.io/c/schedule-i/p/ifBars/SteamNetworkLib_Il2Cpp/" }, new DependencyUrl { SourceName = "Thunderstore Mono", Url = "https://thunderstore.io/c/schedule-i/p/ifBars/SteamNetworkLib_Mono/" }, new DependencyUrl { SourceName = "NexusMods", Url = "https://www.nexusmods.com/schedule1/mods/1396" }, new DependencyUrl { SourceName = "Github", Url = "https://github.com/ifBars/SteamNetworkLib/releases" } } }); dependenciesChecker.ProcessAndAlert(); } private static void RegisterLoggerSettings() { SetAndRegister(ActiveDeliveryPatch.Logger, PoolDebugLogging); SetAndRegister(CapacityText.Logger, PoolDebugLogging); SetAndRegister(DeliveryInstancePatches.Logger, PoolDebugLogging); SetAndRegister(LoaderPatch.Logger, PoolDebugLogging); SetAndRegister(PoolManager.Logger, PoolDebugLogging); SetAndRegister(DropoffQuest.Logger, QuestDebugLogging); SetAndRegister(DropoffQuestDialogue.Logger, QuestDebugLogging); SetAndRegister(QuestSetupManager.Logger, QuestDebugLogging); } private static void SetAndRegister(Logger logger, MelonPreferences_Entry<bool>? entry) { logger.RaiseDebug = entry?.Value ?? false; ((MelonEventBase<LemonAction<bool, bool>>)(object)entry?.OnEntryValueChanged).Subscribe((LemonAction<bool, bool>)delegate(bool _, bool newValue) { logger.RaiseDebug = newValue; }, 0, false); } } } namespace MultiDelivery.Quest { public class DropoffQuest : Quest { internal const string Name = "Expanding the Fleet"; private bool _vehicleAdded; private QuestEntry _addVehicleEntry; private int _startingCapacity; private static VehicleDropoffZone dropoffZone; private static Vector3 _dropoffZonePosition; internal static readonly Logger Logger = new Logger("DropoffQuest"); protected override string Title => "Expanding the Fleet"; protected override string Description => "Bring a delivery vehicle to the dropoff zone to expand your delivery capacity"; protected override bool AutoBegin => false; protected override Sprite QuestIcon => IconLoader.QuestIconSprite; internal QuestState State => ((Quest)this).QuestState; protected override void OnCreated() { //IL_006d: 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_006f: Unknown result type (might be due to invalid IL or missing references) //IL_0079: Unknown result type (might be due to invalid IL or missing references) //IL_007e: Unknown result type (might be due to invalid IL or missing references) //IL_00a2: Unknown result type (might be due to invalid IL or missing references) //IL_00a3: Unknown result type (might be due to invalid IL or missing references) //IL_00bd: Unknown result type (might be due to invalid IL or missing references) ((Registerable)this).OnCreated(); _startingCapacity = PoolManager.Instance.PoolCount; Logger.Debug($"Starting Quest with {_startingCapacity} capacity"); Vector3 val = default(Vector3); ((Vector3)(ref val))..ctor(5.1f, 4.2f, 82.58f); Vector3 val2 = default(Vector3); ((Vector3)(ref val2))..ctor(0.55f, 4.2f, 76.56f); _dropoffZonePosition = (val + val2) / 2f; if ((Object)(object)dropoffZone != (Object)null) { Object.Destroy((Object)(object)((Component)dropoffZone).gameObject); } dropoffZone = VehicleDropoffZoneFactory.CreateZone(val, val2, 5f, (Color?)new Color(0f, 1f, 0f, 0.4f), showVisuals: true, this); Logger.Debug("Dropoff zone created. Spawning " + ((Object)dropoffZone).name); ((Quest)this).OnComplete += Completed; Logger.Debug("Wired completion event"); UpdateQuestEntries(); } private void UpdateQuestEntries() { //IL_004f: Unknown result type (might be due to invalid IL or missing references) base.QuestEntries.Clear(); if (!_vehicleAdded) { Logger.Debug("Adding add vehicle entry"); _addVehicleEntry = ((Quest)this).AddEntry("Purchase a " + "veeper".Capitalize() + " and drive it into the green dropoff zone (top floor of the parking garage, next to storage unit)", (Vector3?)_dropoffZonePosition); _addVehicleEntry.Begin(); } Logger.Debug("Entries added!"); } public void MarkAddVehicleEntryComplete() { Logger.Debug("Marked add vehicle entry as completed"); _addVehicleEntry.Complete(); } private void Completed() { Logger.Msg("Delivery vehicle dropoff quest completed!"); MelonCoroutines.Start(NotifyCompletion()); } private IEnumerator NotifyCompletion() { int i = 0; while (i < 3 && PoolManager.Instance.PoolCount <= _startingCapacity) { yield return (object)new WaitForSeconds(3f); int num = i + 1; i = num; } NPC npc = NPC.Get<JeremyWilkinson>(); if (npc != null) { if (PoolManager.Instance.PoolCount <= _startingCapacity) { npc.SendTextMessage("Something went wrong... Vehicle got in a car crash :(", (Response[])null, 1f, true); yield break; } npc.SendTextMessage($"Vehicle added, you now can order {PoolManager.Instance.PoolCount} more " + "deliver" + ((PoolManager.Instance.PoolCount > 1) ? "ies" : "y") + " from stores.", (Response[])null, 1f, true); npc.SendTextMessage("If you want to add more, you know where to find me.", (Response[])null, 1f, true); } } } public class DropoffQuestDialogue { internal static readonly Logger Logger = new Logger("DropoffQuestDialogue"); private const string ContainerName = "DeliveryExpansion"; public static void Register() { //IL_0191: Unknown result type (might be due to invalid IL or missing references) //IL_019b: Expected O, but got Unknown NPC jeremy = NPC.Get<JeremyWilkinson>(); if (jeremy == null) { Logger.Warning("Jeremy Wilkinson not found"); return; } jeremy.Dialogue.BuildAndRegisterContainer("DeliveryExpansion", (Action<DialogueContainerBuilder>)delegate(DialogueContainerBuilder c) { c.AddNode("OFFER_FIRST", "Yeah! I can help with that. If you bring a " + "veeper".Capitalize() + " to the dropoff zone, I'll add it to the fleet. This will let you handle multiple deliveries at once. Want to give it a shot?", (Action<ChoiceList>)delegate(ChoiceList ch) { ch.Add("BUY_NOW", "Sure! Can I buy one from you right now?", "CHECK_FUNDS"); ch.Add("ACCEPT", "I already have one, let's do it!", "ACCEPTED"); ch.Add("DECLINE", "Maybe later.", "DECLINED"); }); c.AddNode("OFFER_REPEAT", "Want to expand your fleet even more? Just bring another vehicle to the dropoff zone and I'll add it for you.", (Action<ChoiceList>)delegate(ChoiceList ch) { ch.Add("BUY_NOW", "Yeah, can I buy another one from you?", "CHECK_FUNDS"); ch.Add("ACCEPT", "I've got one ready!", "ACCEPTED"); ch.Add("DECLINE", "Not right now.", "DECLINED"); }); c.AddNode("CHECK_FUNDS", "A " + "veeper".Capitalize() + " runs <color=#19BEF0>(" + MoneyManager.FormatAmount(GetVehiclePrice(), false, false) + ")</color>. Want to pick one up?", (Action<ChoiceList>)delegate(ChoiceList ch) { ch.Add("PURCHASE", "I'll take it. <color=#19BEF0>(" + MoneyManager.FormatAmount(GetVehiclePrice(), false, false) + ")</color>", "PURCHASE_COMPLETE"); ch.Add("NEVERMIND", "Let me think about it.", "DECLINED"); }); c.AddNode("PURCHASE_COMPLETE", "All yours. You can customize it if you want. Now just drive it to the dropoff zone - I've marked it on your map.", (Action<ChoiceList>)null); c.AddNode("NOT_ENOUGH", "You don't have enough money to buy it. Come back later.", (Action<ChoiceList>)null); c.AddNode("IN_PROGRESS", "You already have an active delivery expansion going! Just bring the vehicle to the dropoff zone. Top floor of the parking garage, next to the storage units - check your map if you forgot where it is.", (Action<ChoiceList>)null); c.AddNode("ACCEPTED", "Great! I've marked the dropoff zone on your map - top floor of the parking garage, next to the storage units. Just drive the vehicle in there when you're ready.", (Action<ChoiceList>)null); c.AddNode("DECLINED", "No worries, just let me know if you change your mind!", (Action<ChoiceList>)null); }); jeremy.Dialogue.OnChoiceSelected("ACCEPT", (Action)OnAcceptQuest); jeremy.Dialogue.OnChoiceSelected("BUY_NOW", (Action)OnAcceptQuest); jeremy.Dialogue.OnChoiceSelected("DECLINE", (Action)delegate { Logger.Debug("Player declined quest"); }); jeremy.Dialogue.OnChoiceSelected("NEVERMIND", (Action)delegate { Logger.Debug("Player cancelled purchase"); }); jeremy.Dialogue.OnChoiceSelected("PURCHASE", (Action)delegate { //IL_00ca: Unknown result type (might be due to invalid IL or missing references) //IL_00cf: Unknown result type (might be due to invalid IL or missing references) float vehiclePrice = GetVehiclePrice(); float onlineBalance = Money.GetOnlineBalance(); if (onlineBalance >= vehiclePrice) { Money.CreateOnlineTransaction("veeper".Capitalize() + " purchase", 0f - vehiclePrice, 1f, "Bought as a part of delivery expansion"); Logger.Msg(string.Format("Player purchased {0} for ${1:F2}", "veeper", vehiclePrice)); Jeremy component = jeremy.gameObject.GetComponent<Jeremy>(); if ((Object)(object)component != (Object)null) { Dealership dealership = component.Dealership; if ((Object)(object)dealership != (Object)null) { dealership.SpawnVehicle("veeper"); return; } } NetworkSingleton<VehicleManager>.Instance.SpawnVehicle("veeper", new Vector3(9.92f, 0.54f, -33.55f), Quaternion.identity, true); } else { jeremy.Dialogue.JumpTo("DeliveryExpansion", "NOT_ENOUGH", false); } }); DialogueInjector.Register(new DialogueInjection(jeremy.ID, "Dealership_Salesman_Sell", "cc0d838e-2824-4fd5-907d-798dc0195c16", "OFFER_FIRST", "ASK_EXPANSION", "Can I expand my delivery capacity?", (Action)delegate { //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Invalid comparison between Unknown and I4 DropoffQuest dropoffQuest = QuestManager.GetQuestByName("Expanding the Fleet") as DropoffQuest; bool flag = PoolManager.Instance.PoolCount > 0; bool flag2 = dropoffQuest != null && (int)dropoffQuest.State == 1; Logger.Debug($"Quest state - Active: {flag2}, Has expanded: {flag}"); string text = (flag2 ? "IN_PROGRESS" : (flag ? "OFFER_REPEAT" : "OFFER_FIRST")); Logger.Debug("Jumping to 'DeliveryExpansion', node '" + text + "'"); jeremy.Dialogue.JumpTo("DeliveryExpansion", text, false); })); Logger.Msg("Registered delivery expansion dialogue"); } internal static void OnAcceptQuest() { //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Invalid comparison between Unknown and I4 if (!(QuestManager.GetQuestByName("Expanding the Fleet") is DropoffQuest dropoffQuest)) { if (QuestManager.CreateQuest<DropoffQuest>((string)null) is DropoffQuest dropoffQuest2) { ((Quest)dropoffQuest2).Begin(); } Logger.Msg("Started delivery expansion quest"); } else if (dropoffQuest != null && (int)dropoffQuest.State == 2) { ((Quest)dropoffQuest).Begin(); Logger.Msg("Restarted delivery expansion quest"); } } private static float GetVehiclePrice() { LandVehicle vehiclePrefab = NetworkSingleton<VehicleManager>.Instance.GetVehiclePrefab("veeper"); return (vehiclePrefab != null) ? vehiclePrefab.VehiclePrice : 5000f; } } public class QuestSetupManager { internal const string RequestedVehicleCode = "veeper"; internal const string QuestStartMessage = "Your properties are getting busy. Want to handle more than one delivery at a time? I've got an idea. Stop by the dealership."; private static FullRank RequiredRank = new FullRank((Rank)5, 1); internal static readonly Logger Logger = new Logger("QuestSetupManager"); public static void OnSceneWasLoaded(int buildIndex, string sceneName) { if (!(sceneName == "Main")) { if (sceneName == "Menu") { Player.LocalPlayerSpawned -= WirePlayerEvent; LevelManager.OnXPChanged -= SendMessageIfRequiredRank; } } else { Player.LocalPlayerSpawned += WirePlayerEvent; } } private static void WirePlayerEvent(Player _) { Logger.Debug("Player loaded event called"); MelonCoroutines.Start(WireOnXpChangedDelayed()); } private static IEnumerator WireOnXpChangedDelayed() { yield return (object)new WaitUntil((Func<bool>)(() => LevelManager.Exists)); if (PersistentDropoffQuestData.Instance.HasMessaged) { DropoffQuestDialogue.Register(); yield break; } Logger.Debug("Wiring on xp changed"); LevelManager.OnXPChanged += SendMessageIfRequiredRank; } private static void SendMessageIfRequiredRank(FullRank _, FullRank current) { //IL_0013: 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) //IL_0051: 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) Logger.Debug($"Current rank {current}, required: {RequiredRank}"); if (PersistentDropoffQuestData.Instance.HasMessaged) { MelonDebug.Msg("Xp changed wired, but already messaged - registering dialogue now."); DropoffQuestDialogue.Register(); } else { if (current < RequiredRank) { return; } NPC val = NPC.Get<JeremyWilkinson>(); if (val != null) { if (NetworkConvenienceMethods.HostOrSingleplayer) { val.SendTextMessage("Your properties are getting busy. Want to handle more than one delivery at a time? I've got an idea. Stop by the dealership.", (Response[])null, 1f, true); } PersistentDropoffQuestData.Instance.HasMessaged = true; DropoffQuestDialogue.Register(); LevelManager.OnXPChanged -= SendMessageIfRequiredRank; } } } } [RegisterTypeInIl2Cpp] public class VehicleDropoffZone : MonoBehaviour { private VehicleDetector _detector; private Logger _logger; private GameObject _visualPlane; private Material _visualMaterial; private Color _baseColor; private DropoffQuest? _quest; public Vector3 Corner1 { get; private set; } public Vector3 Corner2 { get; private set; } public bool ShowVisuals { get; set; } = true; private void Awake() { _logger = new Logger("VehicleDropoffZone"); _logger.RaiseDebug = MultiDelivery.QuestDebugLogging?.Value ?? false; ((MelonEventBase<LemonAction<bool, bool>>)(object)MultiDelivery.QuestDebugLogging?.OnEntryValueChanged).Subscribe((LemonAction<bool, bool>)delegate(bool _, bool newValue) { _logger.RaiseDebug = newValue; }, 0, false); } private void Start() { _detector = ((Component)this).gameObject.GetComponent<VehicleDetector>(); if ((Object)(object)_detector == (Object)null) { _detector = ((Component)this).gameObject.AddComponent<VehicleDetector>(); } } private void Update() { if (_detector.vehicles.Count <= 0) { return; } foreach (LandVehicle item in _detector.vehicles.AsEnumerable()) { OnVehicleEntered(item); } } private void OnVehicleEntered(LandVehicle vehicle) { _logger.Debug("Vehicle entered dropoff zone: " + vehicle.vehicleName); if (!IsCorrectVehicleType(vehicle)) { if (Time.frameCount % 60 == 0) { _logger.Warning("Wrong vehicle type: " + vehicle.vehicleCode); } return; } if (vehicle.IsOccupied) { _logger.Msg("Ejecting player from vehicle"); vehicle.ExitVehicle(); } ProcessAndAddToPool(vehicle); _detector.vehicles.Remove(vehicle); Object.Destroy((Object)(object)((Component)this).gameObject); } private static bool IsCorrectVehicleType(LandVehicle vehicle) { return vehicle.vehicleCode == "veeper"; } private void ProcessAndAddToPool(LandVehicle vehicle) { //IL_0102: Unknown result type (might be due to invalid IL or missing references) _logger.Debug($"Adding vehicle to pool: {vehicle.GUID}"); DeliveryVehicle val = ((Component)vehicle).GetComponent<DeliveryVehicle>(); if ((Object)(object)val == (Object)null) { Guid gUID = vehicle.GUID; val = new DeliveryVehicleBuilder().WithLandVehicle(vehicle).WithGuid(gUID).Build(); } vehicle.IsPlayerOwned = false; vehicle.SetIsPlayerOwned((NetworkConnection)null, false); vehicle.SetVisible(false); vehicle.IsPhysicallySimulated = false; IEnumerable<LandVehicle> source = from lv in NetworkSingleton<VehicleManager>.Instance.PlayerOwnedVehicles.AsEnumerable() where lv.GUID.ToString() != vehicle.GUID.ToString() select lv; NetworkSingleton<VehicleManager>.Instance.PlayerOwnedVehicles = source.ToList(); ((Component)vehicle).transform.position = new Vector3(0f, -100f, 0f); PoolManager.Instance.AddToSaveData(val); PoolManager.Instance.AddToPool(val); _quest?.MarkAddVehicleEntryComplete(); _logger.Msg($"Vehicle added to pool. Total vehicles: {PoolManager.Instance.PoolCount}"); } public void SetupZone(Vector3 corner1, Vector3 corner2, float height = 5f, Color? visualColor = null) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0012: 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) //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0022: 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_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_008a: Unknown result type (might be due to invalid IL or missing references) //IL_0092: Unknown result type (might be due to invalid IL or missing references) //IL_0101: Unknown result type (might be due to invalid IL or missing references) //IL_0107: Unknown result type (might be due to invalid IL or missing references) //IL_00d5: Unknown result type (might be due to invalid IL or missing references) //IL_00cc: Unknown result type (might be due to invalid IL or missing references) //IL_00da: Unknown result type (might be due to invalid IL or missing references) //IL_00e0: Unknown result type (might be due to invalid IL or missing references) //IL_00e2: Unknown result type (might be due to invalid IL or missing references) Corner1 = corner1; Corner2 = corner2; Vector3 val = (corner1 + corner2) / 2f; Vector3 val2 = default(Vector3); ((Vector3)(ref val2))..ctor(Mathf.Abs(corner2.x - corner1.x), height, Mathf.Abs(corner2.z - corner1.z)); ((Component)this).transform.position = val; BoxCollider val3 = ((Component)this).gameObject.GetComponent<BoxCollider>(); if ((Object)(object)val3 == (Object)null) { val3 = ((Component)this).gameObject.AddComponent<BoxCollider>(); } ((Collider)val3).isTrigger = true; val3.size = val2; val3.center = Vector3.zero; if (ShowVisuals) { _baseColor = (Color)(((??)visualColor) ?? new Color(0f, 1f, 1f, 0.3f)); CreateVisualPlane(val2, _baseColor); } _logger.Debug($"Dropoff zone created: Center={val}, Size={val2}"); } private void CreateVisualPlane(Vector3 size, Color color) { //IL_0040: 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_0061: 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_0099: Unknown result type (might be due to invalid IL or missing references) _visualPlane = GameObject.CreatePrimitive((PrimitiveType)3); ((Object)_visualPlane).name = "DropoffZoneVisual"; _visualPlane.transform.SetParent(((Component)this).transform); _visualPlane.transform.localPosition = Vector3.zero; _visualPlane.transform.localScale = new Vector3(size.x, 0.1f, size.z); Collider component = _visualPlane.GetComponent<Collider>(); if ((Object)(object)component != (Object)null) { Object.Destroy((Object)(object)component); } ApplyDebugMaterial(_visualPlane, color); } private void ApplyDebugMaterial(GameObject obj, Color color) { //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Expected O, but got Unknown //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_00ae: Unknown result type (might be due to invalid IL or missing references) //IL_00e8: Unknown result type (might be due to invalid IL or missing references) //IL_00ee: Unknown result type (might be due to invalid IL or missing references) //IL_00f4: Unknown result type (might be due to invalid IL or missing references) //IL_00fa: Unknown result type (might be due to invalid IL or missing references) //IL_0104: Unknown result type (might be due to invalid IL or missing references) Renderer component = obj.GetComponent<Renderer>(); if ((Object)(object)component == (Object)null) { return; } Shader val = Shader.Find("Universal Render Pipeline/Lit"); if (!((Object)(object)val == (Object)null)) { _visualMaterial = new Material(val); if (_visualMaterial.HasProperty("_Surface")) { _visualMaterial.SetFloat("_Surface", 1f); } if (color.a <= 0f) { color.a = 0.3f; } if (_visualMaterial.HasProperty("_BaseColor")) { _visualMaterial.SetColor("_BaseColor", color); } if (_visualMaterial.HasProperty("_EmissionColor")) { _visualMaterial.EnableKeyword("_EMISSION"); _visualMaterial.SetColor("_EmissionColor", new Color(color.r, color.g, color.b) * 1.5f); } _visualMaterial.SetInt("_ZWrite", 0); _visualMaterial.renderQueue = 3000; component.material = _visualMaterial; } } public void SetVisualsEnabled(bool enabled) { ShowVisuals = enabled; if ((Object)(object)_visualPlane != (Object)null) { _visualPlane.SetActive(enabled); } } public void SetQuest(DropoffQuest quest) { _quest = quest; } private void OnDestroy() { if ((Object)(object)_visualPlane != (Object)null) { Object.Destroy((Object)(object)_visualPlane); } } } public static class VehicleDropoffZoneFactory { public static VehicleDropoffZone CreateZone(Vector3 corner1, Vector3 corner2, float height = 5f, Color? visualColor = null, bool showVisuals = true, DropoffQuest? attachedQuest = null) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Expected O, but got Unknown //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) GameObject val = new GameObject("VehicleDropoffZone"); VehicleDropoffZone vehicleDropoffZone = val.AddComponent<VehicleDropoffZone>(); vehicleDropoffZone.ShowVisuals = showVisuals; vehicleDropoffZone.SetupZone(corner1, corner2, height, visualColor); if (attachedQuest != null) { vehicleDropoffZone.SetQuest(attachedQuest); } return vehicleDropoffZone; } public static VehicleDropoffZone CreateZoneNear(Transform reference, Vector3 offset, Vector3 size, Color? visualColor = null) { //IL_0002: 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) //IL_0008: 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_000e: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_001f: 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_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Unknown result type (might be due to invalid IL or missing references) Vector3 val = reference.position + offset; Vector3 corner = val - size / 2f; Vector3 corner2 = val + size / 2f; return CreateZone(corner, corner2, size.y, visualColor); } } } namespace MultiDelivery.Pool { [HarmonyPatch(typeof(DeliveryManager))] internal static class ActiveDeliveryPatch { internal static readonly Logger Logger = new Logger("ActiveDeliveryPatch"); [HarmonyPatch("GetActiveShopDelivery")] [HarmonyPostfix] private static void AllowIfPoolAssigmentAvailable(DeliveryManager __instance, DeliveryShop shop, ref DeliveryInstance __result) { if (__result == null) { return; } lock (PoolManager.Lock) { if (PoolManager.Instance.BaseVehicleAllocationsForShop.TryGetValue(shop.MatchingShopInterfaceName, out var value)) { if (Time.frameCount % 30 == 0) { Logger.Debug($"Base: {value}"); } if (!value) { __result = null; return; } DeliveryVehicle firstFree = PoolManager.Instance.GetFirstFree(); if (!((Object)(object)firstFree == (Object)null)) { Logger.Debug("Free pool vehicle found"); __result = null; } } else { DeliveryVehicle firstFree2 = PoolManager.Instance.GetFirstFree(); if (!((Object)(object)firstFree2 == (Object)null)) { Logger.Debug("Free pool vehicle found"); __result = null; } } } } [HarmonyPatch("SendDelivery")] [HarmonyPostfix] private static void AllocateVehicle(DeliveryManager __instance, DeliveryInstance delivery) { lock (PoolManager.Lock) { if (PoolManager.Instance.Allocations.ContainsKey(delivery.DeliveryID)) { Logger.Debug("Already allocated, skipping"); return; } PoolManager.Instance.BaseVehicleAllocationsForShop.TryAdd(delivery.StoreName, value: false); Logger.Debug($"Base allocation: {PoolManager.Instance.BaseVehicleAllocationsForShop[delivery.StoreName]}"); if (!PoolManager.Instance.BaseVehicleAllocationsForShop[delivery.StoreName]) { Logger.Debug("Base vehicle allocating"); DeliveryVehicle deliveryVehicle = NetworkSingleton<DeliveryManager>.Instance.GetShopInterface(delivery.StoreName).DeliveryVehicle; PoolManager.Instance.SetBaseAllocation(delivery.StoreName, allocated: true); PoolManager.Instance.Allocations.Add(delivery.DeliveryID, deliveryVehicle); VehicleSave.Instance.AddAllocation(delivery.DeliveryID, deliveryVehicle); NetworkConvenienceMethods.NotifyAllocation(delivery.DeliveryID, deliveryVehicle, isAllocated: true); } else { PoolManager.Instance.GetOrAllocateFirstFree(delivery.DeliveryID); Logger.Debug("Allocated for " + delivery.DeliveryID); } } } } [HarmonyPatch(typeof(DeliveryShop))] internal static class CapacityText { internal static readonly Logger Logger = new Logger("CapacityText"); private static ConditionalWeakTable<DeliveryShop, Text> _capacityTexts = new ConditionalWeakTable<DeliveryShop, Text>(); [HarmonyPatch("Initialize")] [HarmonyPostfix] private static void AddCapacityTextPostfix(DeliveryShop __instance) { Logger.Debug("Adding capacity text for " + ((Object)__instance).name); if (_capacityTexts.TryGetValue(__instance, out Text _)) { return; } Text deliveryTimeLabel = __instance.DeliveryTimeLabel; GameObject val = ((deliveryTimeLabel != null) ? ((Component)((Component)deliveryTimeLabel).transform.parent).gameObject : null); Logger.Debug($"Adding capacity text: {deliveryTimeLabel}, templatego: {val}"); if (val == null) { return; } GameObject val2 = Object.Instantiate<GameObject>(val); val2.transform.SetParent(val.transform.parent, false); val2.transform.SetSiblingIndex(val.transform.GetSiblingIndex()); Text component = ((Component)val2.transform.Find("Label")).GetComponent<Text>(); component.text = "Free vehicles"; Text component2 = ((Component)val2.transform.Find("Time")).GetComponent<Text>(); component2.text = "1/1"; _capacityTexts.Add(__instance, component2); List<Transform> list = val.transform.parent.FindAllTransforms("Spacer"); if (list.Count > 0) { Transform val3 = list[list.Count - 1]; if ((Object)(object)val3 != (Object)null) { ((Component)val3).gameObject.SetActive(false); } } } [HarmonyPatch("RefreshCart")] [HarmonyPostfix] private static void UpdateCapacityTextPostfix(DeliveryShop __instance) { Text value; if (!((App<DeliveryApp>)(object)PlayerSingleton<DeliveryApp>.Instance).isOpen) { Logger.Debug("Delivery app is not open, skipping capacity text update for " + ((Object)__instance).name); } else if (!_capacityTexts.TryGetValue(__instance, out value)) { Logger.Debug("Capacity text for " + ((Object)__instance).name + " not found!"); } else { var (num, num2) = GetVehiclesForShop(__instance); value.text = $"{num}/{num2}"; } } private static (int available, int total) GetVehiclesForShop(DeliveryShop shop) { lock (PoolManager.Lock) { string matchingShopInterfaceName = shop.MatchingShopInterfaceName; PoolManager pool = PoolManager.Instance; int item = 1 + pool.Pool.Count; int num = 0; if (!(pool.BaseVehicleAllocationsForShop.TryGetValue(matchingShopInterfaceName, out var value) && value)) { num++; } int num2 = pool.Allocations.Values.Count((DeliveryVehicle v) => pool.Pool.Contains(v)); int num3 = pool.Pool.Count - num2; num += num3; return (available: num, total: item); } } } [HarmonyPatch(typeof(DeliveryInstance))] internal static class DeliveryInstancePatches { internal static readonly Logger Logger = new Logger("DeliveryInstancePatches"); [HarmonyPatch("SetStatus")] [HarmonyPrefix] private static bool UsePoolSetStatus(DeliveryInstance __instance, EDeliveryStatus status) { //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_0069: Invalid comparison between Unknown and I4 //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Invalid comparison between Unknown and I4 lock (PoolManager.Lock) { string text = $"Setting delivery status to {status} for delivery {__instance.DeliveryID}"; Console.Log((object)text, (Object)null); Logger.Msg(text); __instance.Status = status; ShopInterface shopInterface = NetworkSingleton<DeliveryManager>.Instance.GetShopInterface(__instance.StoreName); if ((int)status != 2) { if ((int)status == 3) { if ((Object)(object)__instance.ActiveVehicle == (Object)(object)shopInterface.DeliveryVehicle) { PoolManager.Instance.SetBaseAllocation(__instance.StoreName, allocated: false); } PoolManager.Instance.FreeAllocation(__instance.DeliveryID); if ((Object)(object)__instance.ActiveVehicle != (Object)null) { __instance.ActiveVehicle.Deactivate(); } UnityEvent onDeliveryCompleted = __instance.onDeliveryCompleted; if (onDeliveryCompleted != null) { onDeliveryCompleted.Invoke(); } } } else { if (PoolManager.Instance.Allocations.TryGetValue(__instance.DeliveryID, out DeliveryVehicle value)) { __instance.ActiveVehicle = value; } else { __instance.ActiveVehicle = shopInterface.DeliveryVehicle; } __instance.ActiveVehicle.Activate(__instance); } return false; } } [HarmonyPatch("AddItemsToDeliveryVehicle")] [HarmonyPrefix] private static bool UsePoolAddItemsToDeliveryVehicle(DeliveryInstance __instance) { lock (PoolManager.Lock) { ShopInterface shopInterface = NetworkSingleton<DeliveryManager>.Instance.GetShopInterface(__instance.StoreName); if (!PoolManager.Instance.Allocations.TryGetValue(__instance.DeliveryID, out DeliveryVehicle value)) { value = shopInterface?.DeliveryVehicle; } StringIntPair[] items = __instance.Items; StringIntPair[] array = items; foreach (StringIntPair val in array) { ItemDefinition item = Registry.GetItem(val.String); int num = val.Int; while (num > 0) { int num2 = Mathf.Min(num, ((BaseItemDefinition)item).StackLimit); num -= num2; ItemInstance defaultInstance = item.GetDefaultInstance(num2); value.Vehicle.Storage.InsertItem(defaultInstance, Object.op_Implicit((Object)(object)item)); } } return false; } } } [HarmonyPatch(typeof(DeliveryVehicle))] internal class DeliveryVehiclePatch { [HarmonyPatch("Deactivate")] [HarmonyPostfix] private static void NullActiveDelivery(DeliveryVehicle __instance) { DeliveryInstance activeDelivery = __instance.ActiveDelivery; if ((Object)(object)((activeDelivery == null) ? null : activeDelivery.LoadingDock?.VehicleDetector) != (Object)null) { __instance.ActiveDelivery.LoadingDock.SetOccupant((LandVehicle)null); __instance.ActiveDelivery.LoadingDock.VehicleDetector.Clear(); } if (__instance.ActiveDelivery != null) { __instance.ActiveDelivery = null; } } } [HarmonyPatch(typeof(DeliveriesLoader))] internal static class LoaderPatch { internal static readonly Logger Logger = new Logger("LoaderPatch"); [HarmonyPatch("Load")] [HarmonyPrefix] private static bool Load(DeliveriesLoader __instance, string mainPath) { bool flag = false; string text = default(string); if (((Loader)__instance).TryLoadFile(Path.Combine(mainPath, "Deliveries"), ref text, true) || ((Loader)__instance).TryLoadFile(mainPath, ref text, true)) { DeliveriesData val = null; try { val = JsonUtility.FromJson<DeliveriesData>(text); } catch (Exception ex) { Debug.LogError((object)("Error loading data: " + ex.Message)); Logger.Debug("Error loading data: " + ex.Message); } if (val != null && val.ActiveDeliveries != null) { LoadDelivery(val); if (val.DeliveryVehicles != null) { flag = true; VehicleData[] deliveryVehicles = val.DeliveryVehicles; VehicleData[] array = deliveryVehicles; foreach (VehicleData data in array) { LoadVehicle(data, mainPath); } } } } if (!flag && Directory.Exists(mainPath)) { Console.Log((object)("Loading legacy delivery vehicles at: " + mainPath), (Object)null); string text2 = Path.Combine(mainPath, "DeliveryVehicles"); List<DirectoryInfo> directories = ((Loader)__instance).GetDirectories(text2); for (int j = 0; j < directories.Count; j++) { __instance.LoadVehicle(directories[j].FullName); } } return false; } private static void LoadVehicle(VehicleData data, string path) { Logger.Debug("Processing GUID " + data.GUID); LandVehicle val; lock (PoolManager.Lock) { DeliveryVehicle? obj = ((IEnumerable<DeliveryVehicle>)PoolManager.Instance.Pool).FirstOrDefault((Func<DeliveryVehicle, bool>)((DeliveryVehicle dv) => dv.Vehicle.GUID.ToString() == data.GUID)); val = ((obj != null) ? obj.Vehicle : null); } if ((Object)(object)val == (Object)null) { Logger.Debug("GUID " + data.GUID + " not found in Pool, lookup via GUIDManager"); val = GUIDManager.GetObject<LandVehicle>(new Guid(data.GUID)); } if ((Object)(object)val == (Object)null) { Logger.Error("Vehicle not found with GUID", data.GUID); Console.LogError((object)("LoadVehicle: Vehicle not found with GUID " + data.GUID), (Object)null); } else { LoadVehicle(val, data, path); } } private static void LoadVehicle(LandVehicle vehicle, VehicleData data, string containerPath) { Logger.Debug($"Processing vehicle {((Object)vehicle).name} with GUID {vehicle.GUID}"); if ((Object)(object)vehicle.Storage != (Object)null) { if (data.VehicleContents != null && data.VehicleContents.Items != null) { DeserializedItemSet val = default(DeserializedItemSet); if (ItemSet.TryDeserialize(data.VehicleContents, ref val)) { val.LoadTo(vehicle.Storage.ItemSlots); } } else if (File.Exists(Path.Combine(containerPath, "Contents.json"))) { Console.LogWarning((object)"Loading legacy vehicle contents.", (Object)null); string text = default(string); DeserializedItemSet val2 = default(DeserializedItemSet); if (vehicle.Loader.TryLoadFile(containerPath, "Contents", ref text) && ItemSet.TryDeserialize(text, ref val2)) { val2.LoadTo(vehicle.Storage.ItemSlots); } } } if (data.SpraySurfaces == null) { return; } try { for (int i = 0; i < data.SpraySurfaces.Count; i++) { if (vehicle._spraySurfaces.Length > i) { SpraySurface obj = vehicle._spraySurfaces[i]; if (obj != null) { obj.Set((NetworkConnection)null, data.SpraySurfaces[i].Strokes.ToArray(), data.SpraySurfaces[i].ContainsCartelGraffiti); } } } } catch (Exception ex) { Logger.Error("Exception thrown while loading saved graffities", ex); } } private static DeliveryVehicle? FindVehicleByGuid(string guid) { lock (PoolManager.Lock) { DeliveryVehicle val = ((IEnumerable<DeliveryVehicle>)PoolManager.Instance.Pool).FirstOrDefault((Func<DeliveryVehicle, bool>)((DeliveryVehicle dv) => dv.GUID.ToString() == guid)); if ((Object)(object)val != (Object)null) { return val; } } foreach (ShopInterface allShop in ShopInterface.AllShops) { if ((Object)(object)allShop.DeliveryVehicle != (Object)null && allShop.DeliveryVehicle.GUID.ToString() == guid) { return allShop.DeliveryVehicle; } } return null; } private static void LoadDelivery(DeliveriesData data) { if (data == null) { return; } if (data.ActiveDeliveries != null) { foreach (AllocationEntry item in VehicleSave.Instance.AllocationMap.ToList()) { DeliveryVehicle val = FindVehicleByGuid(item.VehicleGUID); if ((Object)(object)val != (Object)null) { PoolManager.Instance.AllocateDirectly(item.DeliveryID, val); Logger.Debug("Restored allocation: " + item.DeliveryID + " -> " + item.VehicleGUID); } else { Logger.Warning("Could not find vehicle for allocation: " + item.VehicleGUID); } } foreach (BaseAllocationEntry item2 in VehicleSave.Instance.BaseAllocationMap.ToList()) { PoolManager.Instance.SetBaseAllocation(item2.ShopName, item2.IsAllocated); Logger.Debug($"Restored base allocation: {item2.ShopName} = {item2.IsAllocated}"); } DeliveryInstance[] activeDeliveries = data.ActiveDeliveries; foreach (DeliveryInstance val2 in activeDeliveries) { NetworkSingleton<DeliveryManager>.Instance.SendDelivery(val2); } } if (data.DeliveryHistory != null) { DeliveryReceipt[] deliveryHistory = data.DeliveryHistory; foreach (DeliveryReceipt val3 in deliveryHistory) { NetworkSingleton<DeliveryManager>.Instance._deliveryHistory.Add(val3); } } if (data.DisplayedDeliveryHistory != null) { DeliveryReceipt[] displayedDeliveryHistory = data.DisplayedDeliveryHistory; foreach (DeliveryReceipt val4 in displayedDeliveryHistory) { NetworkSingleton<DeliveryManager>.Instance._displayedDeliveryHistory.Add(val4); } } } } public class PoolManager { internal static readonly Logger Logger = new Logger("PoolManager"); internal static readonly object Lock = new object(); public static PoolManager Instance { get; } = new PoolManager(); public HashSet<DeliveryVehicle> Pool { get; } = new HashSet<DeliveryVehicle>(); internal Dictionary<string, DeliveryVehicle> Allocations { get; } = new Dictionary<string, DeliveryVehicle>(); internal Dictionary<string, bool> BaseVehicleAllocationsForShop { get; } = new Dictionary<string, bool>(); internal int PoolCount { get { lock (Lock) { return Pool.Count; } } } internal int AllocationCount { get { lock (Lock) { return Allocations.Count; } } } public void AddToPool(DeliveryVehicle deliveryVehicle, bool notify = true) { lock (Lock) { Pool.Add(deliveryVehicle); if (notify) { NetworkConvenienceMethods.NotifyVehicleAdded(deliveryVehicle); } } } public void AddToSaveData(DeliveryVehicle deliveryVehicle) { if ((Object)(object)deliveryVehicle == (Object)null) { throw new ArgumentNullException("deliveryVehicle"); } VehicleSave.Instance.AddVehicle(deliveryVehicle.Vehicle); NetworkConvenienceMethods.NotifyVehicleCreated(deliveryVehicle); } public DeliveryVehicle? GetFirstFree() { lock (Lock) { DeliveryVehicle val = ((IEnumerable<DeliveryVehicle>)Pool).FirstOrDefault((Func<DeliveryVehicle, bool>)((DeliveryVehicle dv) => dv.ActiveDelivery == null && !Allocations.ContainsValue(dv))); if (Time.frameCount % 30 == 0) { Logger.Debug("Pool lookup: " + (((Object)(object)val == (Object)null) ? "null" : "not null")); } return val; } } public DeliveryVehicle GetOrAllocateFirstFree(string deliveryId) { lock (Lock) { Logger.Debug("GetOrAllocateFirstFree called for: " + deliveryId); if (Allocations.TryGetValue(deliveryId, out DeliveryVehicle value)) { Logger.Debug("Already allocated, returning existing"); return value; } DeliveryVehicle firstFree = GetFirstFree(); if ((Object)(object)firstFree == (Object)null) { Logger.Debug($"No free vehicle found! Pool count: {Pool.Count}, Allocations count: {Allocations.Count}"); foreach (KeyValuePair<string, DeliveryVehicle> allocation in Allocations) { Logger.Debug("Allocation: " + allocation.Key + " -> Vehicle has ActiveDelivery: " + (allocation.Value.ActiveDelivery?.DeliveryID ?? "null")); } throw new ArgumentException("Tried to allocate a non-free vehicle"); } Allocations.Add(deliveryId, firstFree); Logger.Debug("Allocated new vehicle for " + deliveryId + ", vehicle: " + firstFree.GUID); VehicleSave.Instance.AddAllocation(deliveryId, firstFree); NetworkConvenienceMethods.NotifyAllocation(deliveryId, firstFree, isAllocated: true); return firstFree; } } internal void AllocateDirectly(string deliveryId, DeliveryVehicle deliveryVehicle) { lock (Lock) { Logger.Debug("AllocateDirectly called for: " + deliveryId); if (Allocations.TryGetValue(deliveryId, out DeliveryVehicle _)) { Logger.Debug("Already allocated, returning"); return; } Allocations.Add(deliveryId, deliveryVehicle); Logger.Debug("Directly allocated vehicle for " + deliveryId + ", vehicle: " + deliveryVehicle.GUID); VehicleSave.Instance.AddAllocation(deliveryId, deliveryVehicle); NetworkConvenienceMethods.NotifyAllocation(deliveryId, deliveryVehicle, isAllocated: true); } } internal void SetBaseAllocation(string shopName, bool allocated) { lock (Lock) { BaseVehicleAllocationsForShop[shopName] = allocated; } VehicleSave.Instance.SyncBaseAllocation(shopName, allocated); NetworkConvenienceMethods.NotifyBaseAllocation(shopName, allocated); } public void FreeAllocation(string deliveryId) { lock (Lock) { Logger.Debug("Free custom allocation: " + deliveryId); if (Allocations.TryGetValue(deliveryId, out DeliveryVehicle value)) { Allocations.Remove(deliveryId); VehicleSave.Instance.RemoveAllocation(deliveryId); NetworkConvenienceMethods.NotifyAllocation(deliveryId, value, isAllocated: false); } } } internal void ClearAll() { lock (Lock) { Pool.Clear(); Allocations.Clear(); BaseVehicleAllocationsForShop.Clear(); } VehicleSave instance = VehicleSave.Instance; instance.ClearAllocations(); instance.ClearBaseAllocations(); } internal bool RemoveFromPool(DeliveryVehicle vehicle) { lock (Lock) { List<string> list = (from x in Allocations where (Object)(object)x.Value == (Object)(object)vehicle select x.Key).ToList(); foreach (string item in list) { Allocations.Remove(item); VehicleSave.Instance.RemoveAllocation(item); } VehicleSave.Instance.RemoveAllocationsForVehicle(vehicle); return Pool.Remove(vehicle); } } internal DeliveryVehicle? LastPoolVehicle() { lock (Lock) { return Pool.LastOrDefault(); } } } [HarmonyPatch(typeof(Wheel))] internal class WheelPatch { [HarmonyPatch("OnWeatherChange")] [HarmonyPrefix] private static bool ExitIfNull(Wheel __instance, WeatherConditions newConditions) { if ((Object)(object)__instance?.vehicle == (Object)null) { return false; } if (newConditions != null) { _ = newConditions.Rainy; if (0 == 0) { return true; } } return false; } } [HarmonyPatch(typeof(DeliveryVehicle))] internal static class DeliveryVehicleAwakePatch { [HarmonyPatch("Awake")] [HarmonyPrefix] private static bool ExitIfNull(DeliveryVehicle __instance) { if (Guid.TryParse(__instance.GUID, out var _)) { return true; } if ((Object)(object)((Component)__instance).GetComponent<LandVehicle>() == (Object)null) { return false; } __instance.Vehicle = ((Component)__instance).GetComponent<LandVehicle>(); __instance.Deactivate(); return false; } } } namespace MultiDelivery.Persistence { public class PersistentDropoffQuestData : Saveable { [SaveableField("MessageData")] public bool HasMessaged; public static PersistentDropoffQuestData Instance { get; private set; } = new PersistentDropoffQuestData(); public PersistentDropoffQuestData() { Instance = this; } } [Serializable] public class AllocationEntry { public string DeliveryID; public string VehicleGUID; } [Serializable] public class BaseAllocationEntry { public string ShopName; public bool IsAllocated; } public class VehicleSave : Saveable { [SaveableField("VehicleSaveData")] private List<VehicleSaveDto> _dtos = new List<VehicleSaveDto>(); [SaveableField("AllocationMap")] private List<AllocationEntry> _allocationMap = new List<AllocationEntry>(); [SaveableField("BaseAllocationMap")] private List<BaseAllocationEntry> _baseAllocationMap = new List<BaseAllocationEntry>(); private static readonly Logger Logger = new Logger("VehicleSave"); private static Transform _parent; public override SaveableLoadOrder LoadOrder => (SaveableLoadOrder)0; public IReadOnlyList<AllocationEntry> AllocationMap => _allocationMap; public IReadOnlyList<BaseAllocationEntry> BaseAllocationMap => _baseAllocationMap; public static VehicleSave Instance { get; set; } = new VehicleSave(); public VehicleSave() { Instance = this; } public void AddVehicle(LandVehicle vehicle) { //IL_0035: Unknown result type (might be due to invalid IL or missing references) VehicleSaveDto item = new VehicleSaveDto { Guid = vehicle.GUID.ToString(), VehicleType = vehicle.VehicleCode, Color = vehicle.Color.displayedColor }; if (!_dtos.Contains(item)) { _dtos.Add(item); } } internal void AddAllocation(string deliveryId, DeliveryVehicle vehicle) { string vehicleGUID = vehicle.GUID.ToString(); _allocationMap.RemoveAll((AllocationEntry e) => e.DeliveryID == deliveryId); _allocationMap.Add(new AllocationEntry { DeliveryID = deliveryId, VehicleGUID = vehicleGUID }); } internal void RemoveAllocation(string deliveryId) { _allocationMap.RemoveAll((AllocationEntry e) => e.DeliveryID == deliveryId); } internal void RemoveAllocationsForVehicle(DeliveryVehicle vehicle) { string guid = vehicle.GUID.ToString(); _allocationMap.RemoveAll((AllocationEntry e) => e.VehicleGUID == guid); } internal void ClearAllocations() { _allocationMap.Clear(); } internal void ClearBaseAllocations() { _baseAllocationMap.Clear(); } internal void SyncBaseAllocation(string shopName, bool allocated) { _baseAllocationMap.RemoveAll((BaseAllocationEntry e) => e.ShopName == shopName); _baseAllocationMap.Add(new BaseAllocationEntry { ShopName = shopName, IsAllocated = allocated }); } internal void RemoveVehicle(DeliveryVehicle vehicle) { _dtos.RemoveAll((VehicleSaveDto dto) => dto.Guid.ToString() == vehicle.GUID.ToString()); } protected override void OnLoaded() { //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Expected O, but got Unknown //IL_0106: Unknown result type (might be due to invalid IL or missing references) //IL_0130: Unknown result type (might be due to invalid IL or missing references) Logger.Msg($"OnLoaded: Loading {_dtos.Count} vehicles"); if ((Object)(object)_parent == (Object)null) { GameObject val = GameObject.Find("VehiclePool"); if ((Object)(object)val == (Object)null) { val = new GameObject("VehiclePool"); } _parent = val.transform; } for (int i = 0; i < _dtos.Count; i++) { Logger.Debug($"Loading vehicle {i}: {_dtos[i].Guid}"); Guid guid = new Guid(_dtos[i].Guid); LandVehicle val2 = new LandVehicleBuilder().WithVehicleCode(_dtos[i].VehicleType).WithVehicleName($"Additional Delivery Vehicle {i + 1}").WithGuid(guid) .WithColor(_dtos[i].Color) .WithParent(_parent) .Build(); MelonCoroutines.Start(DeferredSetColor(val2, _dtos[i].Color)); Logger.Debug($"Built LandVehicle: {val2.vehicleName}, ObjectId: {((NetworkBehaviour)val2).ObjectId}"); DeliveryVehicle val3 = new DeliveryVehicleBuilder().WithLandVehicle(val2).WithGuid(guid).Build(); Logger.Debug("Built DeliveryVehicle: " + val3.GUID); PoolManager.Instance.AddToPool(val3); Logger.Debug($"Added to pool. Pool count now: {PoolManager.Instance.PoolCount}"); } Logger.Msg($"OnLoaded complete. Final pool count: {PoolManager.Instance.PoolCount}"); } private IEnumerator DeferredSetColor(LandVehicle vehicle, EVehicleColor color) { //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) while ((Object)(object)Player.Local == (Object)null || (Object)(object)((Component)Player.Local).gameObject == (Object)null) { yield return null; } yield return null; vehicle.ApplyColor(color); } } public record VehicleSaveDto { public string Guid { get; set; } public string VehicleType { get; set; } public EVehicleColor Color { get; set; } } } namespace MultiDelivery.Network { public class DeliveryNetworkManager : IDeliveryNetworkManager, IDisposable { private SteamNetworkClient _client; private readonly Logger _logger; private const string ModDataKey = "MultiDelivery_Version"; private const string ModVersion = "1.0.0"; public bool IsInLobby { get { SteamNetworkClient client = _client; return client != null && client.IsInLobby; } } public bool IsHost { get { SteamNetworkClient client = _client; return client != null && client.IsHost; } } public bool IsSingleplayer => !IsInLobby; public bool HostOrSingleplayer => IsHost || IsSingleplayer; public DeliveryNetworkManager() { _logger = new Logger("Network"); } public bool Initialize() { //IL_0002: 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) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Expected O, but got Unknown //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Expected O, but got Unknown try { NetworkRules val = new NetworkRules { EnableRelay = true, AcceptOnlyFriends = false }; _client = new SteamNetworkClient(val); if (!_client.Initialize()) { _logger.Error("Failed to initialize SteamNetworkClient"); return false; } RegisterMessageHandlers(); SubscribeToEvents(); _logger.Msg("Network manager initialized successfully"); return true; } catch (Exception arg) { _client = null; _logger.Error($"Failed to initialize network manager: {arg}"); return false; } } public void RegisterLoggerSettings() { _logger.RaiseDebug = MultiDelivery.NetworkLogging?.Value ?? false; ((MelonEventBase<LemonAction<bool, bool>>)(object)MultiDelivery.NetworkLogging?.OnEntryValueChanged).Subscribe((LemonAction<bool, bool>)delegate(bool _, bool newValue) { _logger.RaiseDebug = newValue; }, 0, false); } private void RegisterMessageHandlers() { _client.RegisterMessageHandler<VehicleAddedMessage>((Action<VehicleAddedMessage, CSteamID>)OnVehicleAdded); _client.RegisterMessageHandler<VehicleCreatedMessage>((Action<VehicleCreatedMessage, CSteamID>)OnVehicleCreated); _client.RegisterMessageHandler<VehiclePoolSyncRequest>((Action<VehiclePoolSyncRequest, CSteamID>)OnPoolSyncRequest); _client.RegisterMessageHandler<VehiclePoolSyncResponse>((Action<VehiclePoolSyncResponse, CSteamID>)OnPoolSyncResponse); _client.RegisterMessageHandler<VehicleAllocationMessage>((Action<VehicleAllocationMessage, CSteamID>)OnVehicleAllocation); _client.RegisterMessageHandler<BaseVehicleAllocationMessage>((Action<BaseVehicleAllocationMessage, CSteamID>)OnBaseVehicleAllocation); } private void SubscribeToEvents() { _client.OnLobbyCreated += OnLobbyCreated; _client.OnLobbyJoined += OnLobbyJoined; _client.OnMemberJoined += OnMemberJoined; _client.OnLobbyLeft += OnLobbyLeft; } public void Update() { SteamNetworkClient client = _client; if (client != null) { client.ProcessIncomingMessages(); } } public void Dispose() { SteamNetworkClient client = _client; if (client != null) { client.Dispose(); } } public async void BroadcastVehicleAdded(DeliveryVehicle vehicle) { if (!IsSingleplayer) { VehicleAddedMessage message = new VehicleAddedMessage { ObjectId = ((NetworkBehaviour)vehicle.Vehicle).ObjectId, VehicleGuid = vehicle.GUID, VehicleName = vehicle.Vehicle.vehicleName, VehicleCode = vehicle.Vehicle.VehicleCode, VehicleColor = (int)vehicle.Vehicle.Color.displayedColor }; _logger.D($"Broadcasting vehicle added: ObjectId={message.ObjectId}, GUID={message.VehicleGuid}"); await _client.BroadcastMessageAsync((P2PMessage)(object)message); } } public async void BroadcastVehicleCreated(DeliveryVehicle vehicle) { if (!IsSingleplayer && !IsHost) { VehicleCreatedMessage message = new VehicleCreatedMessage { ObjectId = ((NetworkBehaviour)vehicle.Vehicle).ObjectId, VehicleGuid = vehicle.GUID }; _logger.D($"Broadcasting vehicle created: ObjectId={message.ObjectId}"); await _client.BroadcastMessageAsync((P2PMessage)(object)message); } } public async void BroadcastVehicleAllocation(string deliveryId, DeliveryVehicle vehicle, bool isAllocated) { if (!IsSingleplayer) { VehicleAllocationMessage message = new VehicleAllocationMessage { DeliveryId = deliveryId, VehicleGuid = vehicle.GUID, IsAllocated = isAllocated }; _logger.D($"Broadcasting allocation: Delivery={deliveryId}, Vehicle={vehicle.GUID}, Allocated={isAllocated}"); await _client.BroadcastMessageAsync((P2PMessage)(object)message); } } public async void BroadcastBaseVehicleAllocation(string shopName, bool isAllocated) { if (!IsSingleplayer) { BaseVehicleAllocationMessage message = new BaseVehicleAllocationMessage { ShopName = shopName, IsAllocated = isAllocated }; _logger.D($"Broadcasting base allocation: Shop={shopName}, Allocated={isAllocated}"); await _client.BroadcastMessageAsync((P2PMessage)(object)message); } } private void OnVehicleAdded(VehicleAddedMessage message, CSteamID cSteamID) { _logger.D($"Received VehicleAdded: ObjectId={message.ObjectId}, GUID={message.VehicleGuid}"); MelonCoroutines.Start(ProcessMessage()); IEnumerator ProcessMessage() { yield return ExponentialBackoff(delegate { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Unknown result type (might be due to invalid IL or missing references) Scene sceneByName = SceneManager.GetSceneByName("Main"); return ((Scene)(ref sceneByName)).isLoaded; }, 1f, 30f, 60f); yield return ExponentialBackoff(() => (Object)(object)FindLandVehicleByObjectId(message.ObjectId) != (Object)null, 1f, 30f, 60f); try { LandVehicle landVehicle = FindLandVehicleByObjectId(message.ObjectId); if ((Object)(object)landVehicle == (Object)null) { _logger.Warning($"LandVehicle with ObjectId {message.ObjectId} not found - may not have spawned yet"); } else { Guid deliveryGuid = new Guid(message.VehicleGuid); landVehicle.SetGUID(deliveryGuid); landVehicle.ApplyColor((EVehicleColor)message.VehicleColor); DeliveryVehicle deliveryVehicle = ((Component)landVehicle).GetComponent<DeliveryVehicle>() ?? new DeliveryVehicleBuilder().WithLandVehicle(landVehicle).WithGuid(deliveryGuid).Build(); PoolManager.Instance.AddToPool(deliveryVehicle, notify: false); _logger.D($"Added vehicle to pool: ObjectId={message.ObjectId}, DeliveryGUID={message.VehicleGuid}"); } } catch (Exception arg) { _logger.Error($"Failed to add vehicle: {arg}"); } } } private void OnVehicleCreated(VehicleCreatedMessage message, CSteamID senderId) { //IL_0037: Unknown result type (might be due to invalid IL or missing references) if (IsHost) { _logger.D($"Received vehicle created message from {senderId}"); MelonCoroutines.Start(ProcessMessage()); } IEnumerator ProcessMessage() { yield return ExponentialBackoff(delegate { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Unknown result type (might be due to invalid IL or missing references) Scene sceneByName = SceneManager.GetSceneByName("Main"); return ((Scene)(ref sceneByName)).isLoaded; }, 1f, 30f, 60f); yield return ExponentialBackoff(() => (Object)(object)FindLandVehicleByObjectId(message.ObjectId) != (Object)null, 1f, 30f, 60f); try { LandVehicle landVehicle = FindLandVehicleByObjectId(message.ObjectId); if ((Object)(object)landVehicle == (Object)null) { _logger.Error($"LandVehicle with ObjectId {message.ObjectId} not found"); } else { VehicleSave.Instance.AddVehicle(landVehicle); _logger.D($"Added created vehicle to save data: ObjectId={message.ObjectId}, GUID={message.VehicleGuid}"); } } catch (Exception arg) { _logger.Error($"Failed to create vehicle: {arg}"); } } } private async void OnPoolSyncRequest(VehiclePoolSyncRequest message, CSteamID senderId) { //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) if (!IsHost) { return; } _logger.D($"Received pool sync request from {senderId}"); try { VehiclePoolSyncResponse response = new VehiclePoolSyncResponse(); lock (PoolManager.Lock) { foreach (DeliveryVehicle vehicle in PoolManager.Instance.Pool) { response.Vehicles.Add(new VehiclePoolSyncResponse.VehicleData { ObjectId = ((NetworkBehaviour)vehicle.Vehicle).ObjectId, Guid = vehicle.GUID, Name = vehicle.Vehicle.vehicleName, Code = vehicle.Vehicle.VehicleCode, Color = (int)vehicle.Vehicle.Color.displayedColor }); } } _logger.D($"Sending pool sync with {response.Vehicles.Count} vehicles"); await _client.SendMessageToPlayerAsync(senderId, (P2PMessage)(object)response); } catch (Exception arg) { _logger.Error($"Failed to send pool sync: {arg}"); } } private void OnPoolSyncResponse(VehiclePoolSyncResponse message, CSteamID senderId) { if (!IsHost) { _logger.D($"Received pool sync response with {message.Vehicles.Count} vehicles"); MelonCoroutines.Start(ProcessMessage()); } IEnumerator ProcessMessage() { yield return ExponentialBackoff(delegate { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Unknown result type (might be due to invalid IL or missing references) Scene sceneByName = SceneManager.GetSceneByName("Main"); return ((Scene)(ref sceneByName)).isLoaded; }, 1f, 30f, 60f); yield return ExponentialBackoff(() => message.Vehicles.All((VehiclePoolSyncResponse.VehicleData v) => (Object)(object)FindLandVehicleByObjectId(v.ObjectId) != (Object)null), 1f, 30f, 60f); try { foreach (VehiclePoolSyncResponse.VehicleData vehicleData in message.Vehicles) { LandVehicle landVehicle = FindLandVehicleByObjectId(vehicleData.ObjectId); if ((Object)(object)landVehicle == (Object)null) { _logger.Warning($"LandVehicle with ObjectId {vehicleData.ObjectId} not found - skipping"); } else { Guid deliveryGuid = new Guid(vehicleData.Guid); landVehicle.SetGUID(deliveryGuid); landVehicle.ApplyColor((EVehicleColor)vehicleData.Color); DeliveryVehicle deliveryVehicle = ((Component)landVehicle).GetComponent<DeliveryVehicle>() ?? new DeliveryVehicleBuilder().WithLandVehicle(landVehicle).WithGuid(deliveryGuid).Build(); PoolManager.Instance.AddToPool(deliveryVehicle, notify: false); } } _logger.D($"Pool synchronized: {PoolManager.Instance.Pool.Count} vehicles"); } catch (Exception arg) { _logger.Error($"Failed to sync pool: {arg}"); } } } private void OnVehicleAllocation(VehicleAllocationMessage message, CSteamID senderId) { _logger.D($"Received allocation: Delivery={message.DeliveryId}, Vehicle={message.VehicleGuid}, Allocated={message.IsAllocated}"); MelonCoroutines.Start(ProcessMessage()); IEnumerator ProcessMessage() { yield return ExponentialBackoff(delegate { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Unknown result type (might be due to invalid IL or missing references) Scene sceneByName = SceneManager.GetSceneByName("Main"); return ((Scene)(ref sceneByName)).isLoaded; }, 1f, 30f, 60f); yield return ExponentialBackoff(delegate { lock (PoolManager.Lock) { if ((Object)(object)((IEnumerable<DeliveryVehicle>)PoolManager.Instance.Pool).FirstOrDefault((Func<DeliveryVehicle, bool>)((DeliveryVehicle v) => v.GUID == message.VehicleGuid)) != (Object)null) { return true; } } foreach (ShopInterface allShop in ShopInterface.AllShops) { if ((Object)(object)allShop.DeliveryVehicle != (Object)null && allShop.DeliveryVehicle.GUID == message.VehicleGuid) { return true; } } return false; }, 1f, 30f, 60f); try { DeliveryVehicle vehicle; lock (PoolManager.Lock) { vehicle = ((IEnumerable<DeliveryVehicle>)PoolManager.Instance.Pool).FirstOrDefault((Func<DeliveryVehicle, bool>)((DeliveryVehicle v) => v.GUID == message.VehicleGuid)); } if ((Object)(object)vehicle == (Object)null) { foreach (ShopInterface shop in ShopInterface.AllShops) { if ((Object)(object)shop.DeliveryVehicle != (Object)null && shop.DeliveryVehicle.GUID == message.VehicleGuid) { vehicle = shop.DeliveryVehicle; break; } } } if ((Object)(object)vehicle == (Object)null) { _logger.Warning("Vehicle not found for allocation: " + message.VehicleGuid); } else { lock (PoolManager.Lock) { if (message.IsAllocated) { _logger.D("Trying to allocate " + message.VehicleGuid + " for delivery " + message.DeliveryId); if (!PoolManager.Instance.Allocations.ContainsKey(message.DeliveryId)) { _logger.D("Allocating " + message.VehicleGuid + " for delivery " + message.DeliveryId); PoolManager.Instance.Allocations.Add(message.DeliveryId, vehicle); } } else { _logger.D("Deallocating " + message.VehicleGuid + " for delivery " + message.DeliveryId); PoolManager.Instance.Allocations.Remove(message.DeliveryId); } } } } catch (Exception arg) { _logger.Error($"Failed to process allocation: {arg}"); } } } private void OnBaseVehicleAllocation(BaseVehicleAllocationMessage message, CSteamID senderId) { _logger.D($"Received base allocation: Shop={message.ShopName}, Allocated={message.IsAllocated}"); lock (PoolManager.Lock) { try { PoolManager.Instance.BaseVehicleAllocationsForShop[message.ShopName] = message.IsAllocated; } catch (Exception arg) { _logger.Error($"Failed to process base allocation: {arg}"); } } } private void OnLobbyCreated(object sender, LobbyCreatedEventArgs e) { //IL_001a: Unknown result type (might be due to invalid IL or missing references) _logger.D($"Lobby created: {e.Lobby.LobbyId}"); _client.SetMyData("MultiDelivery_Version", "1.0.0"); } private void OnLobbyJoined(object sender, LobbyJoinedEventArgs e) { //IL_001a: Unknown result type (might be due to invalid IL or missing references) _logger.D($"Joined lobby: {e.Lobby.LobbyId}"); _client.SetMyData("MultiDelivery_Version", "1.0.0"); if (!IsHost) { RequestPoolSync(); } } private async void OnMemberJoined(object sender, MemberJoinedEventArgs e) { if (!IsHost) { return; } _logger.D("Member joined: " + e.Member.DisplayName); await Task.Delay(1000); VehiclePoolSyncResponse response = new VehiclePoolSyncResponse(); lock (PoolManager.Lock) { foreach (DeliveryVehicle vehicle in PoolManager.Instance.Pool) { response.Vehicles.Add(new VehiclePoolSyncResponse.VehicleData { ObjectId = ((NetworkBehaviour)vehicle.Vehicle).ObjectId, Guid = vehicle.GUID, Name = vehicle.Vehicle.vehicleName, Code = vehicle.Vehicle.VehicleCode, Color = (int)vehicle.Vehicle.Color.displayedColor }); } } _logger.D($"Sending pool sync to new member ({response.Vehicles.Count} vehicles)"); await _client.SendMessageToPlayerAsync(e.Member.SteamId, (P2PMessage)(object)response); } private void OnLobbyLeft(object sender, LobbyLeftEventArgs e) { _logger.D("Left lobby: " + e.Reason); } private async void RequestPoolSync() { if (!IsSingleplayer && !IsHost) { List<MemberInfo> members = _client.GetLobbyMembers(); MemberInfo host = ((IEnumerable<MemberInfo>)members).FirstOrDefault((Func<MemberInfo, bool>)((MemberInfo m) => m.IsOwner)); if (host == null) { _logger.Error("Cannot request pool sync - no host found"); return; } VehiclePoolSyncRequest message = new VehiclePoolSyncRequest { RequesterId = ((object)_client.LocalPlayerId/*cast due to .constrained prefix*/).ToString() }; _logger.D("Requesting pool sync from host"); await _client.SendMessageToPlayerAsync(host.SteamId, (P2PMessage)(object)message); } } private LandVehicle? FindLandVehicleByObjectId(int objectId) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) Scene sceneByName = SceneManager.GetSceneByName("Main"); if (!((Scene)(ref sceneByName)).isLoaded) { _logger.Error("Main scene is not loaded"); return null; } GameObject[] rootGameObjects = ((Scene)(ref sceneByName)).GetRootGameObjects(); GameObject[] array = rootGameObjects; foreach (GameObject val in array) { LandVehicle[] componentsInChildren = val.GetComponentsInChildren<LandVehicle>(true); LandVehicle[] array2 = componentsInChildren; foreach (LandVehicle val2 in array2) { if (((NetworkBehaviour)val2).ObjectId == objectId) { _logger.D($"Found LandVehicle: ObjectId={objectId}, Name={val2.vehicleName}"); return val2; } } } _logger.Warning($"LandVehicle with ObjectId {objectId} not found in Main scene"); return null; } private static IEnumerator ExponentialBackoff(Func<bool> predicate, float initialDelay, float finalDelay, float timeout) { float delay = initialDelay; float elapsed = 0f; while (!predicate() && elapsed < timeout) { yield return (object)new WaitForSeconds(delay); elapsed += delay; delay = Mathf.Min(delay * 2f, finalDelay); } } } public interface IDeliveryNetworkManager : IDisposable { bool IsInLobby { get; } bool IsHost { get; } bool IsSingleplayer { get; } bool HostOrSingleplayer { get; } void RegisterLoggerSettings(); bool Initialize(); void Update(); void BroadcastVehicleAdded(DeliveryVehicle vehicle); void BroadcastVehicleCreated(DeliveryVehicle vehicle); void BroadcastVehicleAllocation(string deliveryId, DeliveryVehicle vehicle, bool isAllocated); void BroadcastBaseVehicleAllocation(string shopName, bool isAllocated); } public class VehicleAddedMessage : P2PMessage { public override string MessageType => "VehicleAdded"; public int ObjectId { get; set; } public string VehicleGuid { get; set; } public string VehicleName { get; set; } public string VehicleCode { get; set; } public int VehicleColor { get; set; } public override byte[] Serialize() { string s = ((P2PMessage)this).CreateJsonBase($"\"ObjectId\":{ObjectId},\"VehicleGuid\":\"{VehicleGuid}\",\"VehicleName\":\"{VehicleName}\",\"VehicleCode\":\"{VehicleCode}\",\"VehicleColor\":{VehicleColor}"); return Encoding.UTF8.GetBytes(s); } public override void Deserialize(byte[] data) { string text = Encoding.UTF8.GetString(data); ((P2PMessage)this).ParseJsonBase(text); ObjectId = int.Parse(((P2PMessage)this).ExtractJsonValue(text, "ObjectId")); VehicleGuid = ((P2PMessage)this).ExtractJsonValue(text, "VehicleGuid"); VehicleName = ((P2PMessage)this).ExtractJsonValue(text, "VehicleName"); VehicleCode = ((P2PMessage)this).ExtractJsonValue(text, "VehicleCode"); VehicleColor = int.Parse(((P2PMessage)this).ExtractJsonValue(text, "VehicleColor")); } } public class VehicleCreatedMessage : P2PMessage { public override string MessageType => "VehicleCreated"; public int ObjectId { get; set; } public string VehicleGuid { get; set; } public override byte[] Serialize() { string s = ((P2PMessage)this).CreateJsonBase($"\"ObjectId\":{ObjectId},\"VehicleGuid\":\"{VehicleGuid}\""); return Encoding.UTF8.GetBytes(s); } public override void Deserialize(byte[] data) { string text = Encoding.UTF8.GetString(data); ((P2PMessage)this).ParseJsonBase(text); ObjectId = int.Parse(((P2PMessage)this).ExtractJsonValue(text, "ObjectId")); VehicleGuid = ((P2PMessage)this).ExtractJsonValue(text, "VehicleGuid"); } } public class VehiclePoolSyncRequest : P2PMessage { public override string MessageType => "VehiclePoolSyncRequest"; public string RequesterId { get; set; } public override byte[] Serialize() { string s = ((P2PMessage)this).CreateJsonBase("\"RequesterId\":\"" + RequesterId + "\""); return Encoding.UTF8.GetBytes(s); } public override void Deserialize(byte[] data) { string text = Encoding.UTF8.GetString(data); ((P2PMessage)this).ParseJsonBase(text); RequesterId = ((P2PMessage)this).ExtractJsonValue(text, "RequesterId"); } } public class VehiclePoolSyncResponse : P2PMessage { [Serializable] public class VehicleData { public int ObjectId { get; set; } public string Guid { get; set; } public string Name { get; set; } public string Code { get; set; } public int Color { get; set; } } public override string MessageType => "VehiclePoolSyncResponse"; public List<VehicleData> Vehicles { get; set; } = new List<VehicleData>(); public override byte[] Serialize() { string text = string.Join(",", Vehicles.Select((VehicleData v) => $"{{\"ObjectId\":{v.ObjectId},\"Guid\":\"{v.Guid}\",\"Name\":\"{v.Name}\",\"Code\":\"{v.Code}\",\"Color\":{v.Color}}}")); string s = ((P2PMessage)this).CreateJsonBase("\"Vehicles\":[" + text + "]"); return Encoding.UTF8.GetBytes(s); } public override void Deserialize(byte[] data) { string text = Encoding.UTF8.GetString(data); ((P2PMessage)this).ParseJsonBase(text); int num = text.IndexOf("\"Vehicles\":[", StringComparison.Ordinal) + 12; int num2 = text.IndexOf("]", num, StringComparison.Ordinal); string text2 = text.Substring(num, num2 - num); Vehicles.Clear(); if (string.IsNullOrWhiteSpace(text2)) { return; } string[] array = text2.Split(new string[1] { "}," }, StringSplitOptions.None); string[] array2 = array; foreach (string text3 in array2) { string text4 = text3.TrimStart('{').TrimEnd('}') + "}"; if (text4.Contains("ObjectId")) { Vehicles.Add(new VehicleData { ObjectId = int.Parse(((P2PMessage)this).ExtractJsonValue(text4, "ObjectId")), Guid = ((P2PMessage)this).ExtractJsonValue(text4, "Guid"), Name = ((P2PMessage)this).ExtractJsonValue(text4, "Name"), Code = ((P2PMessage)this).ExtractJsonValue(text4, "Code"), Color = int.Parse(((P2PMessage)this).ExtractJsonValue(text4, "Color")) }); } } } } public class VehicleAllocationMessage : P2PMessage { public override string MessageType => "VehicleAllocation"; public string DeliveryId { get; set; } public string VehicleGuid { get; set; } public bool IsAllocated { get; set; } public override byte[] Serialize() { string s = ((P2PMessage)this).CreateJsonBase("\"DeliveryId\":\"" + DeliveryId + "\",\"VehicleGuid\":\"" + VehicleGuid + "\",\"IsAllocated\":" + IsAllocated.ToString().ToLower()); return Encoding.UTF8.GetBytes(s); } public override void Deserialize(byte[] data) { string text = Encoding.UTF8.GetString(data); ((P2PMessage)this).ParseJsonBase(text); DeliveryId = ((P2PMessage)this).ExtractJsonValue(text, "DeliveryId"); VehicleGuid = ((P2PMessage)this).ExtractJsonValue(text, "VehicleGuid"); IsAllocated = bool.Parse(((P2PMessage)this).ExtractJsonValue(text, "IsAllocated")); } } public class BaseVehicleAllocationMessage : P2PMessage { public override string MessageType => "BaseVehicleAllocation"; public string ShopName { get; set; } public bool IsAllocated { get; set; } public override byte[] Serialize() { string s = ((P2PMessage)this).CreateJsonBase("\"ShopName\":\"" + ShopName + "\",\"IsAllocated\":" + IsAllocated.ToString().ToLower()); return Encoding.UTF8.GetBytes(s); } public override void Deserialize(byte[] data) { string text = Encoding.UTF8.GetString(data); ((P2PMessage)this).ParseJsonBase(text); ShopName = ((P2PMessage)this).ExtractJsonValue(text, "ShopName"); IsAllocated = bool.Parse(((P2PMessage)this).ExtractJsonValue(text, "IsAllocated")); } } public static class NetworkConvenienceMethods { private static IDeliveryNetworkManager? _networkManager; public static bool HostOrSingleplayer => _networkManager?.HostOrSingleplayer ?? true; public static void InitializeNetworking(IDeliveryNetworkManager networkManager) { _networkManager = networkManager; } public static void NotifyVehicleAdded(DeliveryVehicle vehicle) { _networkManager?.BroadcastVehicleAdded(vehicle); } public static void NotifyVehicleCreated(DeliveryVehicle vehicle) { _networkManager?.BroadcastVehicleCreated(vehicle); } public static void NotifyAllocation(string deliveryId, DeliveryVehicle vehicle, bool isAllocated) { _networkManager?.BroadcastVehicleAllocation(deliveryId, vehicle, isAllocated); } public static void NotifyBaseAllocation(string shopName, bool isAllocated) { _networkManager?.BroadcastBaseVehicleAllocation(shopName, isAllocated); } } internal static class NetworkLoader { private static readonly Logger Logger = new Logger("NetworkLoader"); internal static IDeliveryNetworkManager? Create() { if (AppDomain.CurrentDomain.GetAssemblies().All((Assembly a) => a.GetName().Name != "SteamNetworkLib")) { return null; } return CreateInternal(); } [MethodImpl(MethodImplOptions.NoInlining)] private static IDeliveryNetworkManager? CreateInternal() { try { DeliveryNetworkManager deliveryNetworkManager = new DeliveryNetworkManager(); deliveryNetworkManager.Initialize(); return deliveryNetworkManager; } catch (Exception arg) { Logger.Warning($"Failed to initialize network manager: {arg}. If you're playing singleplayer, you may disregard this message."); return null; } } } } namespace MultiDelivery.Helpers { public class DependenciesChecker { public class MissingDependencyInfo(string name, string version, List<DependencyUrl> urls, bool isRequired, string reason) { public string Name { get; set; } = name; public string Version { get; set; } = version; public List<DependencyUrl> Urls { get; set; } = urls; public bool IsRequired { get; set; } = isRequired; public string Reason { get; set; } = reason; } private static readonly Logger Logger = new Logger("DependenciesChecker"); private readonly List<DependencyInfo> _dependencies = new List<DependencyInfo>(); private List<MissingDependencyInfo> _missingDependencies = new List<MissingDependencyInfo>(); public bool ShowMenuBanner { get; set; } = true; public bool ShowBannerIfOptionalOnly { get; set; } = false; public bool UnloadIfMissingRequired { get; set; } = false; public void AddDependency(DependencyInfo dependency) { _dependencies.Add(dependency); } public void ProcessAndAlert() { CheckDependencies(); if (_missingDependencies.Count != 0) { PrintMissing(); if (ShowMenuBanner) { ShowBanner(); } MelonCoroutines.Start(DelayedUnload()); } } private void CheckDependencies() { _missingDependencies = new List<MissingDependencyInfo>(); foreach (DependencyInfo dependency in _dependencies) { MissingDependencyInfo missingDependencyInfo = IsPresent(dependency); if (missingDependencyInfo != null) { _missingDependencies.Add(missingDependencyInfo); } } } private void PrintMissing() { Logger.Msg("Missing dependencies:"); foreach (MissingDependencyInfo missingDependency in _missingDependencies) { if (missingDependency.IsRequired) { Logger.Error("- " + missingDependency.Name + " (min version " + missingDependency.Version + "): required - " + missingDependency.Reason); } else { Logger.Warning("- " + missingDependency.Name + " (min version " + missingDependency.Version + "): optional - " + missingDependency.Reason); } if (missingDependency.Urls.Count == 0) { continue; } Logger.Msg(" Possible sources:"); foreach (DependencyUrl url in missingDependency.Urls) { Logger.Msg(" - " + url.SourceName + ": " + url.Url); } } } private void ShowBanner() { ((MelonEventBase<LemonAction<int, string>>)(object)MelonEvents.OnSceneWasLoaded).Subscribe((LemonAction<int, string>)delegate(int _, string name) { if (!(name != "Menu")) { MissingDepsPanelCreator.Show(this, _missingDependencies); } }, 0, false); ((MelonEventBase<LemonAction<int, string>>)(object)MelonEvents.OnSceneWasUnloaded).Subscribe((LemonAction<int, string>)delegate(int _, string name) { if (!(name != "Menu")) { MissingDepsPanelCreator.Hide(); } }, 0, false); MissingDepsPanelCreator.Show(this, _missingDependencies); } private IEnumerator DelayedUnload() { yield return (object)new WaitForSeconds(5f); if (UnloadIfMissingRequired && _missingDependencies.Any((MissingDependencyInfo d) => d.IsRequired)) { Logger.Warning("Missing required dependencies detected and UnloadIfMissingRequired is true, unloading mod."); ((MelonBase)Melon<MultiDelivery>.Instance).Unregister("Missing required dependencies", false); } } private static MissingDependencyInfo? IsPresent(DependencyInfo dependency) { Assembly foundInDomain = AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault((Assembly assembly) => assembly.GetName().Name == dependency.AssemblyName); if (foundInDomain == null) { Logger.Debug(dependency.AssemblyName + " not found in AppDomain, marking as missing."); return new MissingDependencyInfo(dependency.Name, dependency.Version, dependency.Urls, dependency.IsRequired, "Assembly not found"); } MelonMod val = ((IEnumerable<MelonMod>)MelonTypeBase<MelonMod>.RegisteredMelons).FirstOrDefault((Func<MelonMod, bool>)((MelonMod m) => ((MelonBase)m).MelonAssembly.Assembly == foundInDomain)); if (val == null) { Logger.Debug(foundInDomain.GetName().Name + " wasn't found in RegisteredMelons."); Version version = foundInDomain.GetName().Version; if (version == null) { Logger.Debug(dependency.AssemblyName + " version is not available."); return new MissingDependencyInfo(dependency.Name, dependency.Version, dependency.Urls, dependency.IsRequired, "Cannot verify version"); } if (!Version.TryParse(dependency.Version, out Version result)) { Logger.Debug(dependency.AssemblyName + " version is not a valid version."); return new MissingDependencyInfo(dependency.Name, dependency.Version, dependency.Urls, dependency.IsRequired, "Invalid version format"); } if (version < result) { Logger.Debug(dependency.AssemblyName + " version is too old."); return new MissingDependencyInfo(dependency.Name, dependency.Version, dependency.Urls, dependency.IsRequired, $"Version {version} found, but {dependency.Version} required"); } return null; } SemVersion semanticVersion = ((MelonBase)val).Info.SemanticVersion; if (semanticVersion == (SemVersion)null) { Logger.Debug($"{foundInDomain.GetName().Name}'s version {semanticVersion} is not SemVer."); if (!Version.TryParse(dependency.Version, out Version result2)) { Logger.Debug(dependency.AssemblyName +