Some mods target the Mono version of the game, which is available by opting into the Steam beta branch "alternate"
Decompiled source of BetterSewerKeys v1.0.1
mods/BetterSewerKeys_Il2cpp.dll
Decompiled 2 months agousing System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Reflection; using System.Resources; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using BetterSewerKeys; using BetterSewerKeys.Integrations; using BetterSewerKeys.Utils; using HarmonyLib; using Il2CppFishNet.Connection; using Il2CppInterop.Runtime.InteropTypes.Arrays; using Il2CppScheduleOne.DevUtilities; using Il2CppScheduleOne.Dialogue; using Il2CppScheduleOne.Doors; using Il2CppScheduleOne.Interaction; using Il2CppScheduleOne.ItemFramework; using Il2CppScheduleOne.Map; using Il2CppScheduleOne.Money; using Il2CppScheduleOne.NPCs.Relation; using Il2CppScheduleOne.PlayerScripts; using MelonLoader; using Microsoft.CodeAnalysis; using S1API.GameTime; using S1API.Internal.Abstraction; using S1API.Saveables; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)] [assembly: MelonInfo(typeof(Core), "BetterSewerKeys", "1.0.0", "Bars", null)] [assembly: MelonGame("TVGS", "Schedule I")] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("BetterSewerKeys_Il2cpp")] [assembly: AssemblyConfiguration("Il2cpp")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+34036a6df8f55062ef7da2ec838dbf2321b18c8b")] [assembly: AssemblyProduct("BetterSewerKeys_Il2cpp")] [assembly: AssemblyTitle("BetterSewerKeys_Il2cpp")] [assembly: NeutralResourcesLanguage("en-US")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.0.0.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.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] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace BetterSewerKeys { public class Core : MelonMod { [CompilerGenerated] private sealed class <DelayedDiscovery>d__7 : IEnumerator<object>, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public Core <>4__this; private SewerManager <sewerManager>5__1; private Exception <ex>5__2; object? IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object? IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <DelayedDiscovery>d__7(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <sewerManager>5__1 = null; <ex>5__2 = null; <>1__state = -2; } private bool MoveNext() { //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Expected O, but got Unknown switch (<>1__state) { default: return false; case 0: <>1__state = -1; <>2__current = (object)new WaitForSeconds(1f); <>1__state = 1; return true; case 1: <>1__state = -1; try { if (BetterSewerKeysSave.Instance == null) { ModLogger.Warning("BetterSewerKeys: Save data instance not available after waiting"); return false; } <>4__this._saveData = BetterSewerKeysSave.Instance; BetterSewerKeysManager.Instance.Initialize(<>4__this._saveData); BetterSewerKeysManager.Instance.DiscoverEntrances(); <>4__this._saveData.ApplySaveDataAfterDiscovery(); <sewerManager>5__1 = NetworkSingleton<SewerManager>.Instance; if ((Object)(object)<sewerManager>5__1 != (Object)null) { BetterSewerKeysManager.Instance.AssignKeyDistribution(<sewerManager>5__1); } <sewerManager>5__1 = null; } catch (Exception ex) { <ex>5__2 = ex; ModLogger.Error("Error during entrance discovery", <ex>5__2); } return false; } } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } private BetterSewerKeysSave? _saveData; public static Core? Instance { get; private set; } public override void OnInitializeMelon() { Instance = this; ModLogger.LogInitialization(); try { HarmonyPatches.SetModInstance(this); ModLogger.Info("BetterSewerKeys mod initialized successfully"); } catch (Exception exception) { ModLogger.Error("Failed to initialize BetterSewerKeys mod", exception); } } public override void OnSceneWasInitialized(int buildIndex, string sceneName) { try { if (sceneName.Contains("Main") || sceneName.Contains("Game")) { ModLogger.Info("Scene initialized: " + sceneName + " - Discovering sewer entrances..."); MelonCoroutines.Start(DelayedDiscovery()); } } catch (Exception exception) { ModLogger.Error("Error during scene initialization", exception); } } [IteratorStateMachine(typeof(<DelayedDiscovery>d__7))] private IEnumerator DelayedDiscovery() { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <DelayedDiscovery>d__7(0) { <>4__this = this }; } public override void OnApplicationQuit() { Instance = null; } public BetterSewerKeysSave? GetSaveData() { return _saveData; } } public class BetterSewerKeysData { public Dictionary<int, bool> UnlockedEntrances = new Dictionary<int, bool>(); public Dictionary<int, int> KeyLocationIndices = new Dictionary<int, int>(); public Dictionary<int, int> KeyPossessorIndices = new Dictionary<int, int>(); public Dictionary<int, bool> IsRandomWorldKeyCollected = new Dictionary<int, bool>(); public int LastDayKeyWasCollected = -1; } public class BetterSewerKeysSave : Saveable { [CompilerGenerated] private sealed class <DelayedMigration>d__22 : IEnumerator<object>, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public SewerManager sewerManager; public BetterSewerKeysSave <>4__this; private BetterSewerKeysManager <manager>5__1; private List<int>.Enumerator <>s__2; private int <entranceID>5__3; private Exception <ex>5__4; object IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <DelayedMigration>d__22(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <manager>5__1 = null; <>s__2 = default(List<int>.Enumerator); <ex>5__4 = null; <>1__state = -2; } private bool MoveNext() { //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Expected O, but got Unknown switch (<>1__state) { default: return false; case 0: <>1__state = -1; <>2__current = (object)new WaitForSeconds(2f); <>1__state = 1; return true; case 1: <>1__state = -1; try { <manager>5__1 = BetterSewerKeysManager.Instance; if (<manager>5__1 == null) { ModLogger.Warning("BetterSewerKeys: Manager not initialized during migration"); return false; } <manager>5__1.DiscoverEntrances(); <>s__2 = <manager>5__1.GetAllEntranceIDs().GetEnumerator(); try { while (<>s__2.MoveNext()) { <entranceID>5__3 = <>s__2.Current; <>4__this.SetEntranceUnlocked(<entranceID>5__3, unlocked: true); ModLogger.Info($"BetterSewerKeys: Migrated - unlocked entrance {<entranceID>5__3}"); } } finally { ((IDisposable)<>s__2).Dispose(); } <>s__2 = default(List<int>.Enumerator); ModLogger.Info($"BetterSewerKeys: Migration complete - unlocked {<manager>5__1.GetAllEntranceIDs().Count} entrances"); <manager>5__1 = null; } catch (Exception ex) { <ex>5__4 = ex; ModLogger.Error("Error during delayed migration", <ex>5__4); } return false; } } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } [SaveableField("better_sewer_keysData")] public BetterSewerKeysData Data = new BetterSewerKeysData(); public static BetterSewerKeysSave? Instance { get; private set; } public Dictionary<int, bool> UnlockedEntrances => Data.UnlockedEntrances; public Dictionary<int, int> KeyLocationIndices => Data.KeyLocationIndices; public Dictionary<int, int> KeyPossessorIndices => Data.KeyPossessorIndices; public Dictionary<int, bool> IsRandomWorldKeyCollected => Data.IsRandomWorldKeyCollected; public int LastDayKeyWasCollected => Data.LastDayKeyWasCollected; protected override void OnLoaded() { ModLogger.Info($"BetterSewerKeys: Loaded save data - {Data.UnlockedEntrances.Count} entrances tracked"); Instance = this; if (BetterSewerKeysManager.Instance != null) { BetterSewerKeysManager.Instance.Initialize(this); } CheckAndMigrateOldSave(); } protected override void OnCreated() { ModLogger.Info("BetterSewerKeys: Save data instance created"); Instance = this; if (BetterSewerKeysManager.Instance != null) { BetterSewerKeysManager.Instance.Initialize(this); } } public void ApplySaveDataAfterDiscovery() { TimeManager.OnDayPass = (Action)Delegate.Remove(TimeManager.OnDayPass, new Action(OnDayPass)); TimeManager.OnDayPass = (Action)Delegate.Combine(TimeManager.OnDayPass, new Action(OnDayPass)); if (BetterSewerKeysManager.Instance != null) { BetterSewerKeysManager.Instance.ApplySaveData(this); } CheckAndSpawnNewKeyPickup(); } private void OnDayPass() { ModLogger.Info("BetterSewerKeys: OnDayPass callback fired"); CheckAndSpawnNewKeyPickup(); } private void CheckAndSpawnNewKeyPickup() { try { BetterSewerKeysManager instance = BetterSewerKeysManager.Instance; if (instance == null || instance.AreAllEntrancesUnlocked()) { return; } SewerManager instance2 = NetworkSingleton<SewerManager>.Instance; if ((Object)(object)instance2 == (Object)null || (Object)(object)instance2.RandomWorldSewerKeyPickup == (Object)null || instance2.RandomSewerKeyLocations == null || ((Il2CppArrayBase<Transform>)(object)instance2.RandomSewerKeyLocations).Length == 0) { return; } List<int> list = new List<int>(); foreach (int allEntranceID in instance.GetAllEntranceIDs()) { if (!instance.IsEntranceUnlocked(allEntranceID)) { list.Add(allEntranceID); } } if (list.Count != 0) { int index = Random.Range(0, list.Count); int num = list[index]; List<int> list2 = new List<int>(); for (int i = 0; i < ((Il2CppArrayBase<Transform>)(object)instance2.RandomSewerKeyLocations).Length; i++) { list2.Add(i); } int num2 = list2[Random.Range(0, list2.Count)]; SetKeyLocationIndex(num, num2); instance2.SetSewerKeyLocation((NetworkConnection)null, num2); ((Component)instance2.RandomWorldSewerKeyPickup).gameObject.SetActive(true); ModLogger.Info($"BetterSewerKeys: Moved random key pickup to new location {num2} for entrance {num} on day pass"); } } catch (Exception exception) { ModLogger.Error("Error checking for new key pickup", exception); } } private void CheckAndMigrateOldSave() { try { if (Data.UnlockedEntrances.Count != 0) { return; } ModLogger.Debug("BetterSewerKeys: First run detected, checking for old save migration"); SewerManager instance = NetworkSingleton<SewerManager>.Instance; if ((Object)(object)instance != (Object)null) { PropertyInfo property = typeof(SewerManager).GetProperty("IsSewerUnlocked"); if (property != null && (bool)property.GetValue(instance)) { ModLogger.Info("BetterSewerKeys: Old save detected with global sewer unlock - migrating to per-entrance system"); MelonCoroutines.Start(DelayedMigration(instance)); } } } catch (Exception exception) { ModLogger.Error("Error during save migration check", exception); } } [IteratorStateMachine(typeof(<DelayedMigration>d__22))] private IEnumerator DelayedMigration(SewerManager sewerManager) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <DelayedMigration>d__22(0) { <>4__this = this, sewerManager = sewerManager }; } protected override void OnSaved() { ModLogger.Debug($"BetterSewerKeys: Saved data - {Data.UnlockedEntrances.Count} entrances tracked"); ModLogger.Debug($"BetterSewerKeys: OnSaved - UnlockedEntrances: {Data.UnlockedEntrances.Count}, KeyLocationIndices: {Data.KeyLocationIndices.Count}, KeyPossessorIndices: {Data.KeyPossessorIndices.Count}, IsRandomWorldKeyCollected: {Data.IsRandomWorldKeyCollected.Count}"); } public bool IsEntranceUnlocked(int entranceID) { bool value; return Data.UnlockedEntrances.TryGetValue(entranceID, out value) && value; } public void SetEntranceUnlocked(int entranceID, bool unlocked) { Data.UnlockedEntrances[entranceID] = unlocked; Saveable.RequestGameSave(false); } public int GetKeyLocationIndex(int entranceID) { int value; return Data.KeyLocationIndices.TryGetValue(entranceID, out value) ? value : (-1); } public void SetKeyLocationIndex(int entranceID, int locationIndex) { Data.KeyLocationIndices[entranceID] = locationIndex; Saveable.RequestGameSave(false); } public int GetKeyPossessorIndex(int entranceID) { int value; return Data.KeyPossessorIndices.TryGetValue(entranceID, out value) ? value : (-1); } public void SetKeyPossessorIndex(int entranceID, int possessorIndex) { Data.KeyPossessorIndices[entranceID] = possessorIndex; Saveable.RequestGameSave(false); } public bool IsRandomWorldKeyCollectedForEntrance(int entranceID) { bool value; return Data.IsRandomWorldKeyCollected.TryGetValue(entranceID, out value) && value; } public void SetRandomWorldKeyCollected(int entranceID, bool collected) { Data.IsRandomWorldKeyCollected[entranceID] = collected; if (collected) { Data.LastDayKeyWasCollected = TimeManager.ElapsedDays; } Saveable.RequestGameSave(false); } } public class BetterSewerKeysManager { private static BetterSewerKeysManager? _instance; private Dictionary<int, SewerDoorController> _entranceMap = new Dictionary<int, SewerDoorController>(); private Dictionary<SewerDoorController, int> _doorToEntranceMap = new Dictionary<SewerDoorController, int>(); private BetterSewerKeysSave? _saveData; private bool _isInitialized = false; private int _nextEntranceID = 0; public static BetterSewerKeysManager Instance => _instance ?? (_instance = new BetterSewerKeysManager()); private BetterSewerKeysManager() { } public void Initialize(BetterSewerKeysSave saveData) { if (!_isInitialized) { _saveData = saveData; ModLogger.Info("BetterSewerKeysManager: Initialized"); _isInitialized = true; } } public void DiscoverEntrances() { //IL_00c1: Unknown result type (might be due to invalid IL or missing references) //IL_01fd: Unknown result type (might be due to invalid IL or missing references) if (!_isInitialized || _saveData == null) { ModLogger.Warning("BetterSewerKeysManager: Cannot discover entrances - not initialized"); return; } Il2CppArrayBase<SewerDoorController> val = Object.FindObjectsOfType<SewerDoorController>(true); ModLogger.Info($"BetterSewerKeysManager: Found {val.Length} sewer door controllers"); HashSet<SewerDoorController> hashSet = new HashSet<SewerDoorController>(); _entranceMap.Clear(); _doorToEntranceMap.Clear(); _nextEntranceID = 0; foreach (SewerDoorController item in val) { if ((Object)(object)item == (Object)null) { continue; } if (hashSet.Contains(item)) { ModLogger.Debug($"BetterSewerKeysManager: Skipping duplicate door instance: {((Object)((Component)item).gameObject).name} at {((Component)item).transform.position}"); continue; } hashSet.Add(item); int num = _nextEntranceID++; _entranceMap[num] = item; _doorToEntranceMap[item] = num; if (!_saveData.UnlockedEntrances.ContainsKey(num)) { _saveData.UnlockedEntrances[num] = false; } if (!_saveData.KeyLocationIndices.ContainsKey(num)) { _saveData.KeyLocationIndices[num] = -1; } if (!_saveData.KeyPossessorIndices.ContainsKey(num)) { _saveData.KeyPossessorIndices[num] = -1; } if (!_saveData.IsRandomWorldKeyCollected.ContainsKey(num)) { _saveData.IsRandomWorldKeyCollected[num] = false; } ModLogger.Debug($"BetterSewerKeysManager: Registered entrance {num} for door {((Object)((Component)item).gameObject).name} at position {((Component)item).transform.position}"); } ModLogger.Info($"BetterSewerKeysManager: Discovered {_entranceMap.Count} entrances"); if (_saveData != null && _entranceMap.Count > 0) { ModLogger.Debug($"BetterSewerKeysManager: Triggering save after discovering {_entranceMap.Count} entrances"); Saveable.RequestGameSave(false); } } public int RegisterDoor(SewerDoorController door) { if (_doorToEntranceMap.TryGetValue(door, out var value)) { return value; } int num = _nextEntranceID++; _entranceMap[num] = door; _doorToEntranceMap[door] = num; if (_saveData != null && !_saveData.UnlockedEntrances.ContainsKey(num)) { _saveData.UnlockedEntrances[num] = false; } ModLogger.Debug($"BetterSewerKeysManager: Registered new entrance {num} for door {((Object)((Component)door).gameObject).name}"); return num; } public int GetEntranceID(SewerDoorController door) { int value; return _doorToEntranceMap.TryGetValue(door, out value) ? value : (-1); } public bool IsEntranceUnlocked(int entranceID) { if (_saveData == null) { return false; } return _saveData.IsEntranceUnlocked(entranceID); } public void UnlockEntrance(int entranceID) { if (_saveData == null) { ModLogger.Warning($"BetterSewerKeysManager: Cannot unlock entrance {entranceID} - save data not initialized"); return; } _saveData.SetEntranceUnlocked(entranceID, unlocked: true); ModLogger.Info($"BetterSewerKeysManager: Unlocked entrance {entranceID}"); } public List<int> GetAllEntranceIDs() { return _entranceMap.Keys.ToList(); } public int GetFirstLockedEntranceID() { if (_saveData == null) { return -1; } foreach (int item in _entranceMap.Keys.OrderBy((int id) => id)) { if (!_saveData.IsEntranceUnlocked(item)) { return item; } } return -1; } public bool AreAllEntrancesUnlocked() { if (_saveData == null || _entranceMap.Count == 0) { return false; } return _entranceMap.Keys.All((int id) => _saveData.IsEntranceUnlocked(id)); } public void ApplySaveData(BetterSewerKeysSave saveData) { _saveData = saveData; ModLogger.Info($"BetterSewerKeysManager: Applied save data with {saveData.UnlockedEntrances.Count} entrances"); } public BetterSewerKeysSave? GetSaveData() { return _saveData; } public void AssignKeyDistribution(SewerManager sewerManager) { if (!_isInitialized || _saveData == null || (Object)(object)sewerManager == (Object)null) { ModLogger.Warning("BetterSewerKeysManager: Cannot assign key distribution - not initialized or sewer manager missing"); return; } if (_entranceMap.Count == 0) { ModLogger.Warning("BetterSewerKeysManager: No entrances discovered, cannot assign key distribution"); return; } List<int> list = new List<int>(); List<int> list2 = new List<int>(); if (sewerManager.RandomSewerKeyLocations != null && ((Il2CppArrayBase<Transform>)(object)sewerManager.RandomSewerKeyLocations).Length > 0) { for (int i = 0; i < ((Il2CppArrayBase<Transform>)(object)sewerManager.RandomSewerKeyLocations).Length; i++) { list.Add(i); } } if (sewerManager.SewerKeyPossessors != null && ((Il2CppArrayBase<KeyPossessor>)(object)sewerManager.SewerKeyPossessors).Length > 0) { for (int j = 0; j < ((Il2CppArrayBase<KeyPossessor>)(object)sewerManager.SewerKeyPossessors).Length; j++) { list2.Add(j); } } ShuffleList(list); ShuffleList(list2); int num = 0; int num2 = 0; foreach (int item in _entranceMap.Keys.OrderBy((int id) => id)) { if (num < list.Count && !_saveData.KeyLocationIndices.ContainsKey(item)) { int num3 = list[num++]; _saveData.SetKeyLocationIndex(item, num3); ModLogger.Debug($"Assigned key location {num3} to entrance {item}"); } if (num2 < list2.Count && !_saveData.KeyPossessorIndices.ContainsKey(item)) { int num4 = list2[num2++]; _saveData.SetKeyPossessorIndex(item, num4); ModLogger.Debug($"Assigned key possessor {num4} to entrance {item}"); } } ModLogger.Info($"BetterSewerKeysManager: Assigned key distribution for {_entranceMap.Count} entrances"); if (_saveData != null) { Saveable.RequestGameSave(false); } } private void ShuffleList<T>(List<T> list) { Random random = new Random(); int num = list.Count; while (num > 1) { num--; int index = random.Next(num + 1); T value = list[index]; list[index] = list[num]; list[num] = value; } } } } namespace BetterSewerKeys.Utils { public static class Constants { public static class Defaults { public const bool BOOLEAN_DEFAULT = false; } public static class Constraints { public const float MIN_CONSTRAINT = 0f; public const float MAX_CONSTRAINT = 100f; } public static class Game { public const string GAME_STUDIO = "TVGS"; public const string GAME_NAME = "Schedule I"; } public const string MOD_NAME = "BetterSewerKeys"; public const string MOD_VERSION = "1.0.0"; public const string MOD_AUTHOR = "Bars"; public const string MOD_DESCRIPTION = "Mod description..."; public const string PREFERENCES_CATEGORY = "BetterSewerKeys"; } public static class ModLogger { public static void Info(string message) { MelonLogger.Msg("[BetterSewerKeys] " + message); } public static void Warning(string message) { MelonLogger.Warning("[BetterSewerKeys] " + message); } public static void Error(string message) { MelonLogger.Error("[BetterSewerKeys] " + message); } public static void Error(string message, Exception exception) { MelonLogger.Error("[BetterSewerKeys] " + message + ": " + exception.Message); MelonLogger.Error("Stack trace: " + exception.StackTrace); } public static void Debug(string message) { } public static void LogInitialization() { Info("Initializing BetterSewerKeys v1.0.0 by Bars"); } public static void LogShutdown() { Info("BetterSewerKeys shutting down"); } } } namespace BetterSewerKeys.Integrations { [HarmonyPatch] public static class HarmonyPatches { private static Core? _modInstance; public static void SetModInstance(Core modInstance) { _modInstance = modInstance; } } [HarmonyPatch] public static class SewerDoorControllerPatches { [CompilerGenerated] private sealed class <ClearTrackedDoor>d__11 : IEnumerator<object>, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public SewerDoorController door; object IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <ClearTrackedDoor>d__11(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Expected O, but got Unknown switch (<>1__state) { default: return false; case 0: <>1__state = -1; <>2__current = (object)new WaitForSeconds(0.1f); <>1__state = 1; return true; case 1: <>1__state = -1; if ((Object)(object)_lastInteractedDoor == (Object)(object)door) { _lastInteractedDoor = null; } return false; } } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } private static readonly FieldInfo? EntranceIDField = typeof(SewerDoorController).GetField("_entranceID", BindingFlags.Instance | BindingFlags.NonPublic) ?? typeof(SewerDoorController).GetField("EntranceID", BindingFlags.Instance | BindingFlags.Public); private static readonly Dictionary<SewerDoorController, int> _entranceIDMap = new Dictionary<SewerDoorController, int>(); public static SewerDoorController? _lastInteractedDoor = null; private static readonly FieldInfo? ExteriorIntObjsField = typeof(DoorController).GetField("ExteriorIntObjs", BindingFlags.Instance | BindingFlags.NonPublic); private static void SetEntranceID(SewerDoorController door, int entranceID) { if (EntranceIDField != null) { EntranceIDField.SetValue(door, entranceID); } else { _entranceIDMap[door] = entranceID; } } private static int GetEntranceID(SewerDoorController door) { if (EntranceIDField != null) { return (EntranceIDField.GetValue(door) is int num) ? num : (-1); } int value; return _entranceIDMap.TryGetValue(door, out value) ? value : (-1); } [HarmonyPatch(typeof(SewerDoorController), "Awake")] [HarmonyPostfix] public static void SewerDoorController_Awake_Postfix(SewerDoorController __instance) { try { int num = BetterSewerKeysManager.Instance.RegisterDoor(__instance); SetEntranceID(__instance, num); ModLogger.Debug($"SewerDoorController.Awake: Registered door {((Object)((Component)__instance).gameObject).name} with entrance ID {num}"); } catch (Exception exception) { ModLogger.Error("Error in SewerDoorController.Awake postfix", exception); } } [HarmonyPatch(typeof(SewerDoorController), "CanPlayerAccess")] [HarmonyPrefix] public static bool SewerDoorController_CanPlayerAccess_Prefix(SewerDoorController __instance, EDoorSide side, ref bool __result, ref string reason) { //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Invalid comparison between Unknown and I4 try { reason = string.Empty; if ((int)side == 1 && !((DoorController)__instance).IsOpen) { int entranceID = GetEntranceID(__instance); if (entranceID == -1) { return true; } if (BetterSewerKeysManager.Instance != null && BetterSewerKeysManager.Instance.IsEntranceUnlocked(entranceID)) { __result = true; return false; } SewerManager instance = NetworkSingleton<SewerManager>.Instance; if ((Object)(object)instance != (Object)null) { PlayerInventory instance2 = PlayerSingleton<PlayerInventory>.Instance; if ((Object)(object)instance2 != (Object)null && instance2.GetAmountOfItem(instance.SewerKeyItem.ID) != 0) { __result = true; return false; } reason = instance.SewerKeyItem.Name + " required"; __result = false; return false; } } return true; } catch (Exception exception) { ModLogger.Error("Error in SewerDoorController.CanPlayerAccess prefix", exception); return true; } } private static InteractableObject[]? GetExteriorIntObjs(DoorController door) { if (ExteriorIntObjsField != null) { return ExteriorIntObjsField.GetValue(door) as InteractableObject[]; } return null; } [HarmonyPatch(typeof(SewerDoorController), "ExteriorHandleInteracted")] [HarmonyPrefix] public static bool SewerDoorController_ExteriorHandleInteracted_Prefix(SewerDoorController __instance, out bool __state) { _lastInteractedDoor = __instance; int entranceID = GetEntranceID(__instance); bool flag = entranceID != -1 && BetterSewerKeysManager.Instance != null && BetterSewerKeysManager.Instance.IsEntranceUnlocked(entranceID); __state = flag; return true; } [HarmonyPatch(typeof(SewerDoorController), "ExteriorHandleInteracted")] [HarmonyPostfix] public static void SewerDoorController_ExteriorHandleInteracted_Postfix(SewerDoorController __instance, bool __state) { try { if (__state) { int entranceID = GetEntranceID(__instance); SewerManager instance = NetworkSingleton<SewerManager>.Instance; if ((Object)(object)instance != (Object)null) { ModLogger.Debug($"ExteriorHandleInteracted: Entrance {entranceID} was already unlocked, unlock logic should be prevented"); } } MelonCoroutines.Start(ClearTrackedDoor(__instance)); } catch (Exception exception) { ModLogger.Error("Error in SewerDoorController.ExteriorHandleInteracted postfix", exception); } } [IteratorStateMachine(typeof(<ClearTrackedDoor>d__11))] private static IEnumerator ClearTrackedDoor(SewerDoorController door) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <ClearTrackedDoor>d__11(0) { door = door }; } } [HarmonyPatch] public static class SewerManagerPatches { [HarmonyPatch(typeof(SewerManager), "get_IsSewerUnlocked")] [HarmonyPrefix] public static bool SewerManager_IsSewerUnlocked_Getter_Prefix(ref bool __result) { try { if (BetterSewerKeysManager.Instance != null) { __result = BetterSewerKeysManager.Instance.AreAllEntrancesUnlocked(); return false; } return true; } catch (Exception exception) { ModLogger.Error("Error in SewerManager.IsSewerUnlocked getter prefix", exception); return true; } } [HarmonyPatch(typeof(SewerManager), "SetSewerUnlocked_Server")] [HarmonyPrefix] public static bool SewerManager_SetSewerUnlocked_Server_Prefix(SewerManager __instance) { try { BetterSewerKeysManager instance = BetterSewerKeysManager.Instance; if (instance == null) { return true; } FieldInfo field = typeof(SewerDoorControllerPatches).GetField("_lastInteractedDoor", BindingFlags.Static | BindingFlags.Public); SewerDoorController val = null; if (field != null) { object? value = field.GetValue(null); val = (SewerDoorController)((value is SewerDoorController) ? value : null); } int num = -1; if ((Object)(object)val != (Object)null) { MethodInfo method = typeof(SewerDoorControllerPatches).GetMethod("GetEntranceID", BindingFlags.Static | BindingFlags.NonPublic); if (method != null) { num = (int)method.Invoke(null, new object[1] { val }); if (num != -1 && instance.IsEntranceUnlocked(num)) { ModLogger.Debug($"SetSewerUnlocked_Server: Entrance {num} already unlocked, skipping unlock"); PlayerInventory instance2 = PlayerSingleton<PlayerInventory>.Instance; if ((Object)(object)instance2 != (Object)null) { instance2.AddItemToInventory(__instance.SewerKeyItem.GetDefaultInstance(1)); ModLogger.Debug("SetSewerUnlocked_Server: Restored key to player inventory"); } return false; } } } if (num == -1) { num = instance.GetFirstLockedEntranceID(); if (num == -1) { return true; } ModLogger.Debug($"SetSewerUnlocked_Server: Could not determine entrance ID from door, using first locked entrance: {num}"); } if (num != -1) { instance.UnlockEntrance(num); ModLogger.Info($"SewerManager.SetSewerUnlocked_Server: Unlocked entrance {num}"); return false; } return true; } catch (Exception exception) { ModLogger.Error("Error in SewerManager.SetSewerUnlocked_Server prefix", exception); return true; } } [HarmonyPatch(typeof(SewerManager), "SetSewerUnlocked_Client", new Type[] { typeof(NetworkConnection) })] [HarmonyPrefix] public static bool SewerManager_SetSewerUnlocked_Client_Prefix(SewerManager __instance, NetworkConnection conn) { try { BetterSewerKeysManager instance = BetterSewerKeysManager.Instance; if (instance != null) { int firstLockedEntranceID = instance.GetFirstLockedEntranceID(); if (firstLockedEntranceID != -1) { instance.UnlockEntrance(firstLockedEntranceID); ModLogger.Info($"SewerManager.SetSewerUnlocked_Client: Unlocked entrance {firstLockedEntranceID}"); } } return true; } catch (Exception exception) { ModLogger.Error("Error in SewerManager.SetSewerUnlocked_Client prefix", exception); return true; } } [HarmonyPatch(typeof(SewerManager), "OnSpawnServer")] [HarmonyPostfix] public static void SewerManager_OnSpawnServer_Postfix(SewerManager __instance, NetworkConnection connection) { try { if (connection.IsHost) { return; } BetterSewerKeysManager instance = BetterSewerKeysManager.Instance; if (instance == null) { return; } BetterSewerKeysSave saveData = instance.GetSaveData(); if (saveData == null) { return; } foreach (int allEntranceID in instance.GetAllEntranceIDs()) { if (saveData.IsEntranceUnlocked(allEntranceID)) { ModLogger.Debug($"SewerManager.OnSpawnServer: Entrance {allEntranceID} is unlocked, should sync to client"); } } } catch (Exception exception) { ModLogger.Error("Error in SewerManager.OnSpawnServer postfix", exception); } } [HarmonyPatch(typeof(SewerManager), "SetSewerKeyLocation")] [HarmonyPrefix] public static bool SewerManager_SetSewerKeyLocation_Prefix(SewerManager __instance, NetworkConnection conn, int locationIndex) { try { BetterSewerKeysManager instance = BetterSewerKeysManager.Instance; BetterSewerKeysSave betterSewerKeysSave = instance?.GetSaveData(); if (instance != null && betterSewerKeysSave != null) { int firstLockedEntranceID = instance.GetFirstLockedEntranceID(); if (firstLockedEntranceID != -1 && !betterSewerKeysSave.KeyLocationIndices.ContainsKey(firstLockedEntranceID)) { betterSewerKeysSave.SetKeyLocationIndex(firstLockedEntranceID, locationIndex); ModLogger.Debug($"Assigned key location {locationIndex} to entrance {firstLockedEntranceID}"); } } return true; } catch (Exception exception) { ModLogger.Error("Error in SewerManager.SetSewerKeyLocation prefix", exception); return true; } } [HarmonyPatch(typeof(SewerManager), "SetRandomKeyPossessor")] [HarmonyPrefix] public static bool SewerManager_SetRandomKeyPossessor_Prefix(SewerManager __instance, NetworkConnection conn, int possessorIndex) { try { BetterSewerKeysManager instance = BetterSewerKeysManager.Instance; BetterSewerKeysSave betterSewerKeysSave = instance?.GetSaveData(); if (instance != null && betterSewerKeysSave != null) { int firstLockedEntranceID = instance.GetFirstLockedEntranceID(); if (firstLockedEntranceID != -1 && !betterSewerKeysSave.KeyPossessorIndices.ContainsKey(firstLockedEntranceID)) { betterSewerKeysSave.SetKeyPossessorIndex(firstLockedEntranceID, possessorIndex); ModLogger.Debug($"Assigned key possessor {possessorIndex} to entrance {firstLockedEntranceID}"); } } return true; } catch (Exception exception) { ModLogger.Error("Error in SewerManager.SetRandomKeyPossessor prefix", exception); return true; } } [HarmonyPatch(typeof(SewerManager), "Load")] [HarmonyPrefix] public static void SewerManager_Load_Prefix(SewerManager __instance, ref bool __state) { try { BetterSewerKeysManager instance = BetterSewerKeysManager.Instance; __state = instance != null && !instance.AreAllEntrancesUnlocked(); } catch { __state = false; } } [HarmonyPatch(typeof(SewerManager), "Load")] [HarmonyPostfix] public static void SewerManager_Load_Postfix(SewerManager __instance, bool __state) { try { if (!__state) { return; } BetterSewerKeysManager instance = BetterSewerKeysManager.Instance; if (instance == null) { return; } PropertyInfo property = typeof(SewerManager).GetProperty("IsRandomWorldKeyCollected", BindingFlags.Instance | BindingFlags.Public); if (!(property != null) || !property.CanWrite) { return; } property.SetValue(__instance, false); ModLogger.Debug("SewerManager.Load: Forced IsRandomWorldKeyCollected to false (not all entrances unlocked)"); if (!((Object)(object)__instance.RandomWorldSewerKeyPickup != (Object)null) || ((Component)__instance.RandomWorldSewerKeyPickup).gameObject.activeSelf) { return; } BetterSewerKeysSave saveData = instance.GetSaveData(); if (saveData == null) { return; } int firstLockedEntranceID = instance.GetFirstLockedEntranceID(); if (firstLockedEntranceID != -1) { int keyLocationIndex = saveData.GetKeyLocationIndex(firstLockedEntranceID); if (keyLocationIndex >= 0 && keyLocationIndex < ((Il2CppArrayBase<Transform>)(object)__instance.RandomSewerKeyLocations).Length) { __instance.SetSewerKeyLocation((NetworkConnection)null, keyLocationIndex); ((Component)__instance.RandomWorldSewerKeyPickup).gameObject.SetActive(true); ModLogger.Debug($"SewerManager.Load: Re-enabled key pickup for entrance {firstLockedEntranceID}"); } } } catch (Exception exception) { ModLogger.Error("Error in SewerManager.Load postfix", exception); } } [HarmonyPatch(typeof(SewerManager), "SetRandomKeyCollected_Server")] [HarmonyPrefix] public static bool SewerManager_SetRandomKeyCollected_Server_Prefix(SewerManager __instance) { try { BetterSewerKeysManager instance = BetterSewerKeysManager.Instance; if (instance != null && !instance.AreAllEntrancesUnlocked()) { ModLogger.Debug("SewerManager.SetRandomKeyCollected_Server: Blocked RPC (not all entrances unlocked)"); return false; } return true; } catch (Exception exception) { ModLogger.Error("Error in SewerManager.SetRandomKeyCollected_Server prefix", exception); return true; } } [HarmonyPatch(typeof(SewerManager), "SetRandomWorldKeyCollected")] [HarmonyPrefix] public static bool SewerManager_SetRandomWorldKeyCollected_Prefix(SewerManager __instance) { try { BetterSewerKeysManager instance = BetterSewerKeysManager.Instance; BetterSewerKeysSave betterSewerKeysSave = instance?.GetSaveData(); if (instance != null && betterSewerKeysSave != null) { int randomSewerKeyLocationIndex = __instance.RandomSewerKeyLocationIndex; foreach (int allEntranceID in instance.GetAllEntranceIDs()) { if (betterSewerKeysSave.GetKeyLocationIndex(allEntranceID) == randomSewerKeyLocationIndex) { betterSewerKeysSave.SetRandomWorldKeyCollected(allEntranceID, collected: true); ModLogger.Debug($"Marked world key as collected for entrance {allEntranceID} (location {randomSewerKeyLocationIndex})"); break; } } if (!instance.AreAllEntrancesUnlocked()) { ((Component)__instance.RandomWorldSewerKeyPickup).gameObject.SetActive(false); return false; } } return true; } catch (Exception exception) { ModLogger.Error("Error in SewerManager.SetRandomWorldKeyCollected prefix", exception); return true; } } [HarmonyPatch(typeof(SewerManager), "EnsureKeyPosessorHasKey")] [HarmonyPrefix] public static bool SewerManager_EnsureKeyPosessorHasKey_Prefix(SewerManager __instance) { try { BetterSewerKeysManager instance = BetterSewerKeysManager.Instance; BetterSewerKeysSave betterSewerKeysSave = instance?.GetSaveData(); if (instance == null || betterSewerKeysSave == null || __instance.SewerKeyPossessors == null) { return true; } foreach (int allEntranceID in instance.GetAllEntranceIDs()) { int keyPossessorIndex = betterSewerKeysSave.GetKeyPossessorIndex(allEntranceID); if (keyPossessorIndex >= 0 && keyPossessorIndex < ((Il2CppArrayBase<KeyPossessor>)(object)__instance.SewerKeyPossessors).Length) { KeyPossessor val = ((Il2CppArrayBase<KeyPossessor>)(object)__instance.SewerKeyPossessors)[keyPossessorIndex]; if ((Object)(object)((val != null) ? val.NPC : null) != (Object)null && (Object)(object)val.NPC.Inventory != (Object)null && val.NPC.Inventory._GetItemAmount(__instance.SewerKeyItem.ID) == 0) { val.NPC.Inventory.InsertItem(__instance.SewerKeyItem.GetDefaultInstance(1), true); ModLogger.Debug($"Ensured possessor {keyPossessorIndex} has key for entrance {allEntranceID}"); } } } return false; } catch (Exception exception) { ModLogger.Error("Error in SewerManager.EnsureKeyPosessorHasKey prefix", exception); return true; } } } [HarmonyPatch] public static class DialogueControllerJenPatches { [HarmonyPatch(typeof(DialogueController_Jen), "CanBuyKey")] [HarmonyPrefix] public static bool DialogueController_Jen_CanBuyKey_Prefix(DialogueController_Jen __instance, ref bool __result, ref string invalidReason) { //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_0086: Unknown result type (might be due to invalid IL or missing references) try { BetterSewerKeysManager instance = BetterSewerKeysManager.Instance; if (instance == null) { return true; } if (instance.AreAllEntrancesUnlocked()) { invalidReason = "All sewer entrances are already unlocked"; __result = false; return false; } int firstLockedEntranceID = instance.GetFirstLockedEntranceID(); if (firstLockedEntranceID == -1) { invalidReason = "All sewer entrances are already unlocked"; __result = false; return false; } if (((DialogueController)__instance).npc.RelationData.RelationDelta < __instance.MinRelationToBuyKey) { ERelationshipCategory category = RelationshipCategory.GetCategory(__instance.MinRelationToBuyKey); invalidReason = "'" + ((object)(ERelationshipCategory)(ref category)).ToString() + "' relationship required"; __result = false; return false; } __result = true; return false; } catch (Exception exception) { ModLogger.Error("Error in DialogueController_Jen.CanBuyKey prefix", exception); return true; } } [HarmonyPatch(typeof(DialogueController_Jen), "ChoiceCallback")] [HarmonyPrefix] public static bool DialogueController_Jen_ChoiceCallback_Prefix(DialogueController_Jen __instance, string choiceLabel) { try { if (choiceLabel != "CHOICE_CONFIRM") { return true; } BetterSewerKeysManager instance = BetterSewerKeysManager.Instance; if (instance == null) { return true; } if (instance.AreAllEntrancesUnlocked()) { ModLogger.Warning("DialogueController_Jen.ChoiceCallback: All entrances unlocked, cannot buy key"); return false; } int firstLockedEntranceID = instance.GetFirstLockedEntranceID(); if (firstLockedEntranceID == -1) { ModLogger.Warning("DialogueController_Jen.ChoiceCallback: No locked entrances found"); return false; } if (NetworkSingleton<MoneyManager>.Instance.cashBalance < __instance.KeyItem.BasePurchasePrice) { return true; } NetworkSingleton<MoneyManager>.Instance.ChangeCashBalance(0f - __instance.KeyItem.BasePurchasePrice, true, true); ((DialogueController)__instance).npc.Inventory.InsertItem((ItemInstance)(object)NetworkSingleton<MoneyManager>.Instance.GetCashInstance(__instance.KeyItem.BasePurchasePrice), true); PlayerSingleton<PlayerInventory>.Instance.AddItemToInventory(((ItemDefinition)__instance.KeyItem).GetDefaultInstance(1)); ModLogger.Info($"DialogueController_Jen.ChoiceCallback: Player bought key for entrance {firstLockedEntranceID}"); return false; } catch (Exception exception) { ModLogger.Error("Error in DialogueController_Jen.ChoiceCallback prefix", exception); return true; } } } }
mods/BetterSewerKeys_Mono.dll
Decompiled 2 months agousing System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Reflection; using System.Resources; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using BetterSewerKeys; using BetterSewerKeys.Integrations; using BetterSewerKeys.Utils; using FishNet.Connection; using HarmonyLib; using MelonLoader; using Microsoft.CodeAnalysis; using S1API.GameTime; using S1API.Internal.Abstraction; using S1API.Saveables; using ScheduleOne.DevUtilities; using ScheduleOne.Dialogue; using ScheduleOne.Doors; using ScheduleOne.Interaction; using ScheduleOne.ItemFramework; using ScheduleOne.Map; using ScheduleOne.Money; using ScheduleOne.NPCs.Relation; using ScheduleOne.PlayerScripts; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)] [assembly: MelonInfo(typeof(Core), "BetterSewerKeys", "1.0.0", "Bars", null)] [assembly: MelonGame("TVGS", "Schedule I")] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("BetterSewerKeys_Mono")] [assembly: AssemblyConfiguration("Mono")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+f831208a976f48df7cff82e3a7fd8ceb0780d4e0")] [assembly: AssemblyProduct("BetterSewerKeys_Mono")] [assembly: AssemblyTitle("BetterSewerKeys_Mono")] [assembly: NeutralResourcesLanguage("en-US")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.0.0.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.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] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace BetterSewerKeys { public class Core : MelonMod { [CompilerGenerated] private sealed class <DelayedDiscovery>d__7 : IEnumerator<object>, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public Core <>4__this; private SewerManager <sewerManager>5__1; private Exception <ex>5__2; object? IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object? IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <DelayedDiscovery>d__7(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <sewerManager>5__1 = null; <ex>5__2 = null; <>1__state = -2; } private bool MoveNext() { //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Expected O, but got Unknown switch (<>1__state) { default: return false; case 0: <>1__state = -1; <>2__current = (object)new WaitForSeconds(1f); <>1__state = 1; return true; case 1: <>1__state = -1; try { if (BetterSewerKeysSave.Instance == null) { ModLogger.Warning("BetterSewerKeys: Save data instance not available after waiting"); return false; } <>4__this._saveData = BetterSewerKeysSave.Instance; BetterSewerKeysManager.Instance.Initialize(<>4__this._saveData); BetterSewerKeysManager.Instance.DiscoverEntrances(); <>4__this._saveData.ApplySaveDataAfterDiscovery(); <sewerManager>5__1 = NetworkSingleton<SewerManager>.Instance; if ((Object)(object)<sewerManager>5__1 != (Object)null) { BetterSewerKeysManager.Instance.AssignKeyDistribution(<sewerManager>5__1); } <sewerManager>5__1 = null; } catch (Exception ex) { <ex>5__2 = ex; ModLogger.Error("Error during entrance discovery", <ex>5__2); } return false; } } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } private BetterSewerKeysSave? _saveData; public static Core? Instance { get; private set; } public override void OnInitializeMelon() { Instance = this; ModLogger.LogInitialization(); try { HarmonyPatches.SetModInstance(this); ModLogger.Info("BetterSewerKeys mod initialized successfully"); } catch (Exception exception) { ModLogger.Error("Failed to initialize BetterSewerKeys mod", exception); } } public override void OnSceneWasInitialized(int buildIndex, string sceneName) { try { if (sceneName.Contains("Main") || sceneName.Contains("Game")) { ModLogger.Info("Scene initialized: " + sceneName + " - Discovering sewer entrances..."); MelonCoroutines.Start(DelayedDiscovery()); } } catch (Exception exception) { ModLogger.Error("Error during scene initialization", exception); } } [IteratorStateMachine(typeof(<DelayedDiscovery>d__7))] private IEnumerator DelayedDiscovery() { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <DelayedDiscovery>d__7(0) { <>4__this = this }; } public override void OnApplicationQuit() { Instance = null; } public BetterSewerKeysSave? GetSaveData() { return _saveData; } } public class BetterSewerKeysData { public Dictionary<int, bool> UnlockedEntrances = new Dictionary<int, bool>(); public Dictionary<int, int> KeyLocationIndices = new Dictionary<int, int>(); public Dictionary<int, int> KeyPossessorIndices = new Dictionary<int, int>(); public Dictionary<int, bool> IsRandomWorldKeyCollected = new Dictionary<int, bool>(); public int LastDayKeyWasCollected = -1; } public class BetterSewerKeysSave : Saveable { [CompilerGenerated] private sealed class <DelayedMigration>d__22 : IEnumerator<object>, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public SewerManager sewerManager; public BetterSewerKeysSave <>4__this; private BetterSewerKeysManager <manager>5__1; private List<int>.Enumerator <>s__2; private int <entranceID>5__3; private Exception <ex>5__4; object IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <DelayedMigration>d__22(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <manager>5__1 = null; <>s__2 = default(List<int>.Enumerator); <ex>5__4 = null; <>1__state = -2; } private bool MoveNext() { //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Expected O, but got Unknown switch (<>1__state) { default: return false; case 0: <>1__state = -1; <>2__current = (object)new WaitForSeconds(2f); <>1__state = 1; return true; case 1: <>1__state = -1; try { <manager>5__1 = BetterSewerKeysManager.Instance; if (<manager>5__1 == null) { ModLogger.Warning("BetterSewerKeys: Manager not initialized during migration"); return false; } <manager>5__1.DiscoverEntrances(); <>s__2 = <manager>5__1.GetAllEntranceIDs().GetEnumerator(); try { while (<>s__2.MoveNext()) { <entranceID>5__3 = <>s__2.Current; <>4__this.SetEntranceUnlocked(<entranceID>5__3, unlocked: true); ModLogger.Info($"BetterSewerKeys: Migrated - unlocked entrance {<entranceID>5__3}"); } } finally { ((IDisposable)<>s__2).Dispose(); } <>s__2 = default(List<int>.Enumerator); ModLogger.Info($"BetterSewerKeys: Migration complete - unlocked {<manager>5__1.GetAllEntranceIDs().Count} entrances"); <manager>5__1 = null; } catch (Exception ex) { <ex>5__4 = ex; ModLogger.Error("Error during delayed migration", <ex>5__4); } return false; } } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } [SaveableField("better_sewer_keysData")] public BetterSewerKeysData Data = new BetterSewerKeysData(); public static BetterSewerKeysSave? Instance { get; private set; } public Dictionary<int, bool> UnlockedEntrances => Data.UnlockedEntrances; public Dictionary<int, int> KeyLocationIndices => Data.KeyLocationIndices; public Dictionary<int, int> KeyPossessorIndices => Data.KeyPossessorIndices; public Dictionary<int, bool> IsRandomWorldKeyCollected => Data.IsRandomWorldKeyCollected; public int LastDayKeyWasCollected => Data.LastDayKeyWasCollected; protected override void OnLoaded() { ModLogger.Info($"BetterSewerKeys: Loaded save data - {Data.UnlockedEntrances.Count} entrances tracked"); Instance = this; if (BetterSewerKeysManager.Instance != null) { BetterSewerKeysManager.Instance.Initialize(this); } CheckAndMigrateOldSave(); } protected override void OnCreated() { ModLogger.Info("BetterSewerKeys: Save data instance created"); Instance = this; if (BetterSewerKeysManager.Instance != null) { BetterSewerKeysManager.Instance.Initialize(this); } } public void ApplySaveDataAfterDiscovery() { TimeManager.OnDayPass = (Action)Delegate.Remove(TimeManager.OnDayPass, new Action(OnDayPass)); TimeManager.OnDayPass = (Action)Delegate.Combine(TimeManager.OnDayPass, new Action(OnDayPass)); if (BetterSewerKeysManager.Instance != null) { BetterSewerKeysManager.Instance.ApplySaveData(this); } CheckAndSpawnNewKeyPickup(); } private void OnDayPass() { ModLogger.Info("BetterSewerKeys: OnDayPass callback fired"); CheckAndSpawnNewKeyPickup(); } private void CheckAndSpawnNewKeyPickup() { try { BetterSewerKeysManager instance = BetterSewerKeysManager.Instance; if (instance == null || instance.AreAllEntrancesUnlocked()) { return; } SewerManager instance2 = NetworkSingleton<SewerManager>.Instance; if ((Object)(object)instance2 == (Object)null || (Object)(object)instance2.RandomWorldSewerKeyPickup == (Object)null || instance2.RandomSewerKeyLocations == null || instance2.RandomSewerKeyLocations.Length == 0) { return; } List<int> list = new List<int>(); foreach (int allEntranceID in instance.GetAllEntranceIDs()) { if (!instance.IsEntranceUnlocked(allEntranceID)) { list.Add(allEntranceID); } } if (list.Count != 0) { int index = Random.Range(0, list.Count); int num = list[index]; List<int> list2 = new List<int>(); for (int i = 0; i < instance2.RandomSewerKeyLocations.Length; i++) { list2.Add(i); } int num2 = list2[Random.Range(0, list2.Count)]; SetKeyLocationIndex(num, num2); instance2.SetSewerKeyLocation((NetworkConnection)null, num2); ((Component)instance2.RandomWorldSewerKeyPickup).gameObject.SetActive(true); ModLogger.Info($"BetterSewerKeys: Moved random key pickup to new location {num2} for entrance {num} on day pass"); } } catch (Exception exception) { ModLogger.Error("Error checking for new key pickup", exception); } } private void CheckAndMigrateOldSave() { try { if (Data.UnlockedEntrances.Count != 0) { return; } ModLogger.Debug("BetterSewerKeys: First run detected, checking for old save migration"); SewerManager instance = NetworkSingleton<SewerManager>.Instance; if ((Object)(object)instance != (Object)null) { PropertyInfo property = typeof(SewerManager).GetProperty("IsSewerUnlocked"); if (property != null && (bool)property.GetValue(instance)) { ModLogger.Info("BetterSewerKeys: Old save detected with global sewer unlock - migrating to per-entrance system"); MelonCoroutines.Start(DelayedMigration(instance)); } } } catch (Exception exception) { ModLogger.Error("Error during save migration check", exception); } } [IteratorStateMachine(typeof(<DelayedMigration>d__22))] private IEnumerator DelayedMigration(SewerManager sewerManager) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <DelayedMigration>d__22(0) { <>4__this = this, sewerManager = sewerManager }; } protected override void OnSaved() { ModLogger.Debug($"BetterSewerKeys: Saved data - {Data.UnlockedEntrances.Count} entrances tracked"); ModLogger.Debug($"BetterSewerKeys: OnSaved - UnlockedEntrances: {Data.UnlockedEntrances.Count}, KeyLocationIndices: {Data.KeyLocationIndices.Count}, KeyPossessorIndices: {Data.KeyPossessorIndices.Count}, IsRandomWorldKeyCollected: {Data.IsRandomWorldKeyCollected.Count}"); } public bool IsEntranceUnlocked(int entranceID) { bool value; return Data.UnlockedEntrances.TryGetValue(entranceID, out value) && value; } public void SetEntranceUnlocked(int entranceID, bool unlocked) { Data.UnlockedEntrances[entranceID] = unlocked; Saveable.RequestGameSave(false); } public int GetKeyLocationIndex(int entranceID) { int value; return Data.KeyLocationIndices.TryGetValue(entranceID, out value) ? value : (-1); } public void SetKeyLocationIndex(int entranceID, int locationIndex) { Data.KeyLocationIndices[entranceID] = locationIndex; Saveable.RequestGameSave(false); } public int GetKeyPossessorIndex(int entranceID) { int value; return Data.KeyPossessorIndices.TryGetValue(entranceID, out value) ? value : (-1); } public void SetKeyPossessorIndex(int entranceID, int possessorIndex) { Data.KeyPossessorIndices[entranceID] = possessorIndex; Saveable.RequestGameSave(false); } public bool IsRandomWorldKeyCollectedForEntrance(int entranceID) { bool value; return Data.IsRandomWorldKeyCollected.TryGetValue(entranceID, out value) && value; } public void SetRandomWorldKeyCollected(int entranceID, bool collected) { Data.IsRandomWorldKeyCollected[entranceID] = collected; if (collected) { Data.LastDayKeyWasCollected = TimeManager.ElapsedDays; } Saveable.RequestGameSave(false); } } public class BetterSewerKeysManager { private static BetterSewerKeysManager? _instance; private Dictionary<int, SewerDoorController> _entranceMap = new Dictionary<int, SewerDoorController>(); private Dictionary<SewerDoorController, int> _doorToEntranceMap = new Dictionary<SewerDoorController, int>(); private BetterSewerKeysSave? _saveData; private bool _isInitialized = false; private int _nextEntranceID = 0; public static BetterSewerKeysManager Instance => _instance ?? (_instance = new BetterSewerKeysManager()); private BetterSewerKeysManager() { } public void Initialize(BetterSewerKeysSave saveData) { if (!_isInitialized) { _saveData = saveData; ModLogger.Info("BetterSewerKeysManager: Initialized"); _isInitialized = true; } } public void DiscoverEntrances() { //IL_00ba: Unknown result type (might be due to invalid IL or missing references) //IL_01f6: Unknown result type (might be due to invalid IL or missing references) if (!_isInitialized || _saveData == null) { ModLogger.Warning("BetterSewerKeysManager: Cannot discover entrances - not initialized"); return; } SewerDoorController[] array = Object.FindObjectsOfType<SewerDoorController>(true); ModLogger.Info($"BetterSewerKeysManager: Found {array.Length} sewer door controllers"); HashSet<SewerDoorController> hashSet = new HashSet<SewerDoorController>(); _entranceMap.Clear(); _doorToEntranceMap.Clear(); _nextEntranceID = 0; SewerDoorController[] array2 = array; foreach (SewerDoorController val in array2) { if ((Object)(object)val == (Object)null) { continue; } if (hashSet.Contains(val)) { ModLogger.Debug($"BetterSewerKeysManager: Skipping duplicate door instance: {((Object)((Component)val).gameObject).name} at {((Component)val).transform.position}"); continue; } hashSet.Add(val); int num = _nextEntranceID++; _entranceMap[num] = val; _doorToEntranceMap[val] = num; if (!_saveData.UnlockedEntrances.ContainsKey(num)) { _saveData.UnlockedEntrances[num] = false; } if (!_saveData.KeyLocationIndices.ContainsKey(num)) { _saveData.KeyLocationIndices[num] = -1; } if (!_saveData.KeyPossessorIndices.ContainsKey(num)) { _saveData.KeyPossessorIndices[num] = -1; } if (!_saveData.IsRandomWorldKeyCollected.ContainsKey(num)) { _saveData.IsRandomWorldKeyCollected[num] = false; } ModLogger.Debug($"BetterSewerKeysManager: Registered entrance {num} for door {((Object)((Component)val).gameObject).name} at position {((Component)val).transform.position}"); } ModLogger.Info($"BetterSewerKeysManager: Discovered {_entranceMap.Count} entrances"); if (_saveData != null && _entranceMap.Count > 0) { ModLogger.Debug($"BetterSewerKeysManager: Triggering save after discovering {_entranceMap.Count} entrances"); Saveable.RequestGameSave(false); } } public int RegisterDoor(SewerDoorController door) { if (_doorToEntranceMap.TryGetValue(door, out var value)) { return value; } int num = _nextEntranceID++; _entranceMap[num] = door; _doorToEntranceMap[door] = num; if (_saveData != null && !_saveData.UnlockedEntrances.ContainsKey(num)) { _saveData.UnlockedEntrances[num] = false; } ModLogger.Debug($"BetterSewerKeysManager: Registered new entrance {num} for door {((Object)((Component)door).gameObject).name}"); return num; } public int GetEntranceID(SewerDoorController door) { int value; return _doorToEntranceMap.TryGetValue(door, out value) ? value : (-1); } public bool IsEntranceUnlocked(int entranceID) { if (_saveData == null) { return false; } return _saveData.IsEntranceUnlocked(entranceID); } public void UnlockEntrance(int entranceID) { if (_saveData == null) { ModLogger.Warning($"BetterSewerKeysManager: Cannot unlock entrance {entranceID} - save data not initialized"); return; } _saveData.SetEntranceUnlocked(entranceID, unlocked: true); ModLogger.Info($"BetterSewerKeysManager: Unlocked entrance {entranceID}"); } public List<int> GetAllEntranceIDs() { return _entranceMap.Keys.ToList(); } public int GetFirstLockedEntranceID() { if (_saveData == null) { return -1; } foreach (int item in _entranceMap.Keys.OrderBy((int id) => id)) { if (!_saveData.IsEntranceUnlocked(item)) { return item; } } return -1; } public bool AreAllEntrancesUnlocked() { if (_saveData == null || _entranceMap.Count == 0) { return false; } return _entranceMap.Keys.All((int id) => _saveData.IsEntranceUnlocked(id)); } public void ApplySaveData(BetterSewerKeysSave saveData) { _saveData = saveData; ModLogger.Info($"BetterSewerKeysManager: Applied save data with {saveData.UnlockedEntrances.Count} entrances"); } public BetterSewerKeysSave? GetSaveData() { return _saveData; } public void AssignKeyDistribution(SewerManager sewerManager) { if (!_isInitialized || _saveData == null || (Object)(object)sewerManager == (Object)null) { ModLogger.Warning("BetterSewerKeysManager: Cannot assign key distribution - not initialized or sewer manager missing"); return; } if (_entranceMap.Count == 0) { ModLogger.Warning("BetterSewerKeysManager: No entrances discovered, cannot assign key distribution"); return; } List<int> list = new List<int>(); List<int> list2 = new List<int>(); if (sewerManager.RandomSewerKeyLocations != null && sewerManager.RandomSewerKeyLocations.Length != 0) { for (int i = 0; i < sewerManager.RandomSewerKeyLocations.Length; i++) { list.Add(i); } } if (sewerManager.SewerKeyPossessors != null && sewerManager.SewerKeyPossessors.Length != 0) { for (int j = 0; j < sewerManager.SewerKeyPossessors.Length; j++) { list2.Add(j); } } ShuffleList(list); ShuffleList(list2); int num = 0; int num2 = 0; foreach (int item in _entranceMap.Keys.OrderBy((int id) => id)) { if (num < list.Count && !_saveData.KeyLocationIndices.ContainsKey(item)) { int num3 = list[num++]; _saveData.SetKeyLocationIndex(item, num3); ModLogger.Debug($"Assigned key location {num3} to entrance {item}"); } if (num2 < list2.Count && !_saveData.KeyPossessorIndices.ContainsKey(item)) { int num4 = list2[num2++]; _saveData.SetKeyPossessorIndex(item, num4); ModLogger.Debug($"Assigned key possessor {num4} to entrance {item}"); } } ModLogger.Info($"BetterSewerKeysManager: Assigned key distribution for {_entranceMap.Count} entrances"); if (_saveData != null) { Saveable.RequestGameSave(false); } } private void ShuffleList<T>(List<T> list) { Random random = new Random(); int num = list.Count; while (num > 1) { num--; int index = random.Next(num + 1); T value = list[index]; list[index] = list[num]; list[num] = value; } } } } namespace BetterSewerKeys.Utils { public static class Constants { public static class Defaults { public const bool BOOLEAN_DEFAULT = false; } public static class Constraints { public const float MIN_CONSTRAINT = 0f; public const float MAX_CONSTRAINT = 100f; } public static class Game { public const string GAME_STUDIO = "TVGS"; public const string GAME_NAME = "Schedule I"; } public const string MOD_NAME = "BetterSewerKeys"; public const string MOD_VERSION = "1.0.0"; public const string MOD_AUTHOR = "Bars"; public const string MOD_DESCRIPTION = "Mod description..."; public const string PREFERENCES_CATEGORY = "BetterSewerKeys"; } public static class ModLogger { public static void Info(string message) { MelonLogger.Msg("[BetterSewerKeys] " + message); } public static void Warning(string message) { MelonLogger.Warning("[BetterSewerKeys] " + message); } public static void Error(string message) { MelonLogger.Error("[BetterSewerKeys] " + message); } public static void Error(string message, Exception exception) { MelonLogger.Error("[BetterSewerKeys] " + message + ": " + exception.Message); MelonLogger.Error("Stack trace: " + exception.StackTrace); } public static void Debug(string message) { } public static void LogInitialization() { Info("Initializing BetterSewerKeys v1.0.0 by Bars"); } public static void LogShutdown() { Info("BetterSewerKeys shutting down"); } } } namespace BetterSewerKeys.Integrations { [HarmonyPatch] public static class HarmonyPatches { private static Core? _modInstance; public static void SetModInstance(Core modInstance) { _modInstance = modInstance; } } [HarmonyPatch] public static class SewerDoorControllerPatches { [CompilerGenerated] private sealed class <ClearTrackedDoor>d__11 : IEnumerator<object>, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public SewerDoorController door; object IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <ClearTrackedDoor>d__11(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Expected O, but got Unknown switch (<>1__state) { default: return false; case 0: <>1__state = -1; <>2__current = (object)new WaitForSeconds(0.1f); <>1__state = 1; return true; case 1: <>1__state = -1; if ((Object)(object)_lastInteractedDoor == (Object)(object)door) { _lastInteractedDoor = null; } return false; } } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } private static readonly FieldInfo? EntranceIDField = typeof(SewerDoorController).GetField("_entranceID", BindingFlags.Instance | BindingFlags.NonPublic) ?? typeof(SewerDoorController).GetField("EntranceID", BindingFlags.Instance | BindingFlags.Public); private static readonly Dictionary<SewerDoorController, int> _entranceIDMap = new Dictionary<SewerDoorController, int>(); public static SewerDoorController? _lastInteractedDoor = null; private static readonly FieldInfo? ExteriorIntObjsField = typeof(DoorController).GetField("ExteriorIntObjs", BindingFlags.Instance | BindingFlags.NonPublic); private static void SetEntranceID(SewerDoorController door, int entranceID) { if (EntranceIDField != null) { EntranceIDField.SetValue(door, entranceID); } else { _entranceIDMap[door] = entranceID; } } private static int GetEntranceID(SewerDoorController door) { if (EntranceIDField != null) { return (EntranceIDField.GetValue(door) is int num) ? num : (-1); } int value; return _entranceIDMap.TryGetValue(door, out value) ? value : (-1); } [HarmonyPatch(typeof(SewerDoorController), "Awake")] [HarmonyPostfix] public static void SewerDoorController_Awake_Postfix(SewerDoorController __instance) { try { int num = BetterSewerKeysManager.Instance.RegisterDoor(__instance); SetEntranceID(__instance, num); ModLogger.Debug($"SewerDoorController.Awake: Registered door {((Object)((Component)__instance).gameObject).name} with entrance ID {num}"); } catch (Exception exception) { ModLogger.Error("Error in SewerDoorController.Awake postfix", exception); } } [HarmonyPatch(typeof(SewerDoorController), "CanPlayerAccess")] [HarmonyPrefix] public static bool SewerDoorController_CanPlayerAccess_Prefix(SewerDoorController __instance, EDoorSide side, ref bool __result, ref string reason) { //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Invalid comparison between Unknown and I4 try { reason = string.Empty; if ((int)side == 1 && !((DoorController)__instance).IsOpen) { int entranceID = GetEntranceID(__instance); if (entranceID == -1) { return true; } if (BetterSewerKeysManager.Instance != null && BetterSewerKeysManager.Instance.IsEntranceUnlocked(entranceID)) { __result = true; return false; } SewerManager instance = NetworkSingleton<SewerManager>.Instance; if ((Object)(object)instance != (Object)null) { PlayerInventory instance2 = PlayerSingleton<PlayerInventory>.Instance; if ((Object)(object)instance2 != (Object)null && instance2.GetAmountOfItem(instance.SewerKeyItem.ID) != 0) { __result = true; return false; } reason = instance.SewerKeyItem.Name + " required"; __result = false; return false; } } return true; } catch (Exception exception) { ModLogger.Error("Error in SewerDoorController.CanPlayerAccess prefix", exception); return true; } } private static InteractableObject[]? GetExteriorIntObjs(DoorController door) { if (ExteriorIntObjsField != null) { return ExteriorIntObjsField.GetValue(door) as InteractableObject[]; } return null; } [HarmonyPatch(typeof(SewerDoorController), "ExteriorHandleInteracted")] [HarmonyPrefix] public static bool SewerDoorController_ExteriorHandleInteracted_Prefix(SewerDoorController __instance, out bool __state) { _lastInteractedDoor = __instance; int entranceID = GetEntranceID(__instance); bool flag = entranceID != -1 && BetterSewerKeysManager.Instance != null && BetterSewerKeysManager.Instance.IsEntranceUnlocked(entranceID); __state = flag; return true; } [HarmonyPatch(typeof(SewerDoorController), "ExteriorHandleInteracted")] [HarmonyPostfix] public static void SewerDoorController_ExteriorHandleInteracted_Postfix(SewerDoorController __instance, bool __state) { try { if (__state) { int entranceID = GetEntranceID(__instance); SewerManager instance = NetworkSingleton<SewerManager>.Instance; if ((Object)(object)instance != (Object)null) { ModLogger.Debug($"ExteriorHandleInteracted: Entrance {entranceID} was already unlocked, unlock logic should be prevented"); } } MelonCoroutines.Start(ClearTrackedDoor(__instance)); } catch (Exception exception) { ModLogger.Error("Error in SewerDoorController.ExteriorHandleInteracted postfix", exception); } } [IteratorStateMachine(typeof(<ClearTrackedDoor>d__11))] private static IEnumerator ClearTrackedDoor(SewerDoorController door) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <ClearTrackedDoor>d__11(0) { door = door }; } } [HarmonyPatch] public static class SewerManagerPatches { [HarmonyPatch(typeof(SewerManager), "get_IsSewerUnlocked")] [HarmonyPrefix] public static bool SewerManager_IsSewerUnlocked_Getter_Prefix(ref bool __result) { try { if (BetterSewerKeysManager.Instance != null) { __result = BetterSewerKeysManager.Instance.AreAllEntrancesUnlocked(); return false; } return true; } catch (Exception exception) { ModLogger.Error("Error in SewerManager.IsSewerUnlocked getter prefix", exception); return true; } } [HarmonyPatch(typeof(SewerManager), "SetSewerUnlocked_Server")] [HarmonyPrefix] public static bool SewerManager_SetSewerUnlocked_Server_Prefix(SewerManager __instance) { try { BetterSewerKeysManager instance = BetterSewerKeysManager.Instance; if (instance == null) { return true; } FieldInfo field = typeof(SewerDoorControllerPatches).GetField("_lastInteractedDoor", BindingFlags.Static | BindingFlags.Public); SewerDoorController val = null; if (field != null) { object? value = field.GetValue(null); val = (SewerDoorController)((value is SewerDoorController) ? value : null); } int num = -1; if ((Object)(object)val != (Object)null) { MethodInfo method = typeof(SewerDoorControllerPatches).GetMethod("GetEntranceID", BindingFlags.Static | BindingFlags.NonPublic); if (method != null) { num = (int)method.Invoke(null, new object[1] { val }); if (num != -1 && instance.IsEntranceUnlocked(num)) { ModLogger.Debug($"SetSewerUnlocked_Server: Entrance {num} already unlocked, skipping unlock"); PlayerInventory instance2 = PlayerSingleton<PlayerInventory>.Instance; if ((Object)(object)instance2 != (Object)null) { instance2.AddItemToInventory(__instance.SewerKeyItem.GetDefaultInstance(1)); ModLogger.Debug("SetSewerUnlocked_Server: Restored key to player inventory"); } return false; } } } if (num == -1) { num = instance.GetFirstLockedEntranceID(); if (num == -1) { return true; } ModLogger.Debug($"SetSewerUnlocked_Server: Could not determine entrance ID from door, using first locked entrance: {num}"); } if (num != -1) { instance.UnlockEntrance(num); ModLogger.Info($"SewerManager.SetSewerUnlocked_Server: Unlocked entrance {num}"); return false; } return true; } catch (Exception exception) { ModLogger.Error("Error in SewerManager.SetSewerUnlocked_Server prefix", exception); return true; } } [HarmonyPatch(typeof(SewerManager), "SetSewerUnlocked_Client", new Type[] { typeof(NetworkConnection) })] [HarmonyPrefix] public static bool SewerManager_SetSewerUnlocked_Client_Prefix(SewerManager __instance, NetworkConnection conn) { try { BetterSewerKeysManager instance = BetterSewerKeysManager.Instance; if (instance != null) { int firstLockedEntranceID = instance.GetFirstLockedEntranceID(); if (firstLockedEntranceID != -1) { instance.UnlockEntrance(firstLockedEntranceID); ModLogger.Info($"SewerManager.SetSewerUnlocked_Client: Unlocked entrance {firstLockedEntranceID}"); } } return true; } catch (Exception exception) { ModLogger.Error("Error in SewerManager.SetSewerUnlocked_Client prefix", exception); return true; } } [HarmonyPatch(typeof(SewerManager), "OnSpawnServer")] [HarmonyPostfix] public static void SewerManager_OnSpawnServer_Postfix(SewerManager __instance, NetworkConnection connection) { try { if (connection.IsHost) { return; } BetterSewerKeysManager instance = BetterSewerKeysManager.Instance; if (instance == null) { return; } BetterSewerKeysSave saveData = instance.GetSaveData(); if (saveData == null) { return; } foreach (int allEntranceID in instance.GetAllEntranceIDs()) { if (saveData.IsEntranceUnlocked(allEntranceID)) { ModLogger.Debug($"SewerManager.OnSpawnServer: Entrance {allEntranceID} is unlocked, should sync to client"); } } } catch (Exception exception) { ModLogger.Error("Error in SewerManager.OnSpawnServer postfix", exception); } } [HarmonyPatch(typeof(SewerManager), "SetSewerKeyLocation")] [HarmonyPrefix] public static bool SewerManager_SetSewerKeyLocation_Prefix(SewerManager __instance, NetworkConnection conn, int locationIndex) { try { BetterSewerKeysManager instance = BetterSewerKeysManager.Instance; BetterSewerKeysSave betterSewerKeysSave = instance?.GetSaveData(); if (instance != null && betterSewerKeysSave != null) { int firstLockedEntranceID = instance.GetFirstLockedEntranceID(); if (firstLockedEntranceID != -1 && !betterSewerKeysSave.KeyLocationIndices.ContainsKey(firstLockedEntranceID)) { betterSewerKeysSave.SetKeyLocationIndex(firstLockedEntranceID, locationIndex); ModLogger.Debug($"Assigned key location {locationIndex} to entrance {firstLockedEntranceID}"); } } return true; } catch (Exception exception) { ModLogger.Error("Error in SewerManager.SetSewerKeyLocation prefix", exception); return true; } } [HarmonyPatch(typeof(SewerManager), "SetRandomKeyPossessor")] [HarmonyPrefix] public static bool SewerManager_SetRandomKeyPossessor_Prefix(SewerManager __instance, NetworkConnection conn, int possessorIndex) { try { BetterSewerKeysManager instance = BetterSewerKeysManager.Instance; BetterSewerKeysSave betterSewerKeysSave = instance?.GetSaveData(); if (instance != null && betterSewerKeysSave != null) { int firstLockedEntranceID = instance.GetFirstLockedEntranceID(); if (firstLockedEntranceID != -1 && !betterSewerKeysSave.KeyPossessorIndices.ContainsKey(firstLockedEntranceID)) { betterSewerKeysSave.SetKeyPossessorIndex(firstLockedEntranceID, possessorIndex); ModLogger.Debug($"Assigned key possessor {possessorIndex} to entrance {firstLockedEntranceID}"); } } return true; } catch (Exception exception) { ModLogger.Error("Error in SewerManager.SetRandomKeyPossessor prefix", exception); return true; } } [HarmonyPatch(typeof(SewerManager), "Load")] [HarmonyPrefix] public static void SewerManager_Load_Prefix(SewerManager __instance, ref bool __state) { try { BetterSewerKeysManager instance = BetterSewerKeysManager.Instance; __state = instance != null && !instance.AreAllEntrancesUnlocked(); } catch { __state = false; } } [HarmonyPatch(typeof(SewerManager), "Load")] [HarmonyPostfix] public static void SewerManager_Load_Postfix(SewerManager __instance, bool __state) { try { if (!__state) { return; } BetterSewerKeysManager instance = BetterSewerKeysManager.Instance; if (instance == null) { return; } PropertyInfo property = typeof(SewerManager).GetProperty("IsRandomWorldKeyCollected", BindingFlags.Instance | BindingFlags.Public); if (!(property != null) || !property.CanWrite) { return; } property.SetValue(__instance, false); ModLogger.Debug("SewerManager.Load: Forced IsRandomWorldKeyCollected to false (not all entrances unlocked)"); if (!((Object)(object)__instance.RandomWorldSewerKeyPickup != (Object)null) || ((Component)__instance.RandomWorldSewerKeyPickup).gameObject.activeSelf) { return; } BetterSewerKeysSave saveData = instance.GetSaveData(); if (saveData == null) { return; } int firstLockedEntranceID = instance.GetFirstLockedEntranceID(); if (firstLockedEntranceID != -1) { int keyLocationIndex = saveData.GetKeyLocationIndex(firstLockedEntranceID); if (keyLocationIndex >= 0 && keyLocationIndex < __instance.RandomSewerKeyLocations.Length) { __instance.SetSewerKeyLocation((NetworkConnection)null, keyLocationIndex); ((Component)__instance.RandomWorldSewerKeyPickup).gameObject.SetActive(true); ModLogger.Debug($"SewerManager.Load: Re-enabled key pickup for entrance {firstLockedEntranceID}"); } } } catch (Exception exception) { ModLogger.Error("Error in SewerManager.Load postfix", exception); } } [HarmonyPatch(typeof(SewerManager), "SetRandomKeyCollected_Server")] [HarmonyPrefix] public static bool SewerManager_SetRandomKeyCollected_Server_Prefix(SewerManager __instance) { try { BetterSewerKeysManager instance = BetterSewerKeysManager.Instance; if (instance != null && !instance.AreAllEntrancesUnlocked()) { ModLogger.Debug("SewerManager.SetRandomKeyCollected_Server: Blocked RPC (not all entrances unlocked)"); return false; } return true; } catch (Exception exception) { ModLogger.Error("Error in SewerManager.SetRandomKeyCollected_Server prefix", exception); return true; } } [HarmonyPatch(typeof(SewerManager), "SetRandomWorldKeyCollected")] [HarmonyPrefix] public static bool SewerManager_SetRandomWorldKeyCollected_Prefix(SewerManager __instance) { try { BetterSewerKeysManager instance = BetterSewerKeysManager.Instance; BetterSewerKeysSave betterSewerKeysSave = instance?.GetSaveData(); if (instance != null && betterSewerKeysSave != null) { int randomSewerKeyLocationIndex = __instance.RandomSewerKeyLocationIndex; foreach (int allEntranceID in instance.GetAllEntranceIDs()) { if (betterSewerKeysSave.GetKeyLocationIndex(allEntranceID) == randomSewerKeyLocationIndex) { betterSewerKeysSave.SetRandomWorldKeyCollected(allEntranceID, collected: true); ModLogger.Debug($"Marked world key as collected for entrance {allEntranceID} (location {randomSewerKeyLocationIndex})"); break; } } if (!instance.AreAllEntrancesUnlocked()) { ((Component)__instance.RandomWorldSewerKeyPickup).gameObject.SetActive(false); return false; } } return true; } catch (Exception exception) { ModLogger.Error("Error in SewerManager.SetRandomWorldKeyCollected prefix", exception); return true; } } [HarmonyPatch(typeof(SewerManager), "EnsureKeyPosessorHasKey")] [HarmonyPrefix] public static bool SewerManager_EnsureKeyPosessorHasKey_Prefix(SewerManager __instance) { try { BetterSewerKeysManager instance = BetterSewerKeysManager.Instance; BetterSewerKeysSave betterSewerKeysSave = instance?.GetSaveData(); if (instance == null || betterSewerKeysSave == null || __instance.SewerKeyPossessors == null) { return true; } foreach (int allEntranceID in instance.GetAllEntranceIDs()) { int keyPossessorIndex = betterSewerKeysSave.GetKeyPossessorIndex(allEntranceID); if (keyPossessorIndex >= 0 && keyPossessorIndex < __instance.SewerKeyPossessors.Length) { KeyPossessor val = __instance.SewerKeyPossessors[keyPossessorIndex]; if ((Object)(object)val?.NPC != (Object)null && (Object)(object)val.NPC.Inventory != (Object)null && val.NPC.Inventory._GetItemAmount(__instance.SewerKeyItem.ID) == 0) { val.NPC.Inventory.InsertItem(__instance.SewerKeyItem.GetDefaultInstance(1), true); ModLogger.Debug($"Ensured possessor {keyPossessorIndex} has key for entrance {allEntranceID}"); } } } return false; } catch (Exception exception) { ModLogger.Error("Error in SewerManager.EnsureKeyPosessorHasKey prefix", exception); return true; } } } [HarmonyPatch] public static class DialogueControllerJenPatches { [HarmonyPatch(typeof(DialogueController_Jen), "CanBuyKey")] [HarmonyPrefix] public static bool DialogueController_Jen_CanBuyKey_Prefix(DialogueController_Jen __instance, ref bool __result, ref string invalidReason) { //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_0086: Unknown result type (might be due to invalid IL or missing references) try { BetterSewerKeysManager instance = BetterSewerKeysManager.Instance; if (instance == null) { return true; } if (instance.AreAllEntrancesUnlocked()) { invalidReason = "All sewer entrances are already unlocked"; __result = false; return false; } int firstLockedEntranceID = instance.GetFirstLockedEntranceID(); if (firstLockedEntranceID == -1) { invalidReason = "All sewer entrances are already unlocked"; __result = false; return false; } if (((DialogueController)__instance).npc.RelationData.RelationDelta < __instance.MinRelationToBuyKey) { ERelationshipCategory category = RelationshipCategory.GetCategory(__instance.MinRelationToBuyKey); invalidReason = "'" + ((object)(ERelationshipCategory)(ref category)).ToString() + "' relationship required"; __result = false; return false; } __result = true; return false; } catch (Exception exception) { ModLogger.Error("Error in DialogueController_Jen.CanBuyKey prefix", exception); return true; } } [HarmonyPatch(typeof(DialogueController_Jen), "ChoiceCallback")] [HarmonyPrefix] public static bool DialogueController_Jen_ChoiceCallback_Prefix(DialogueController_Jen __instance, string choiceLabel) { try { if (choiceLabel != "CHOICE_CONFIRM") { return true; } BetterSewerKeysManager instance = BetterSewerKeysManager.Instance; if (instance == null) { return true; } if (instance.AreAllEntrancesUnlocked()) { ModLogger.Warning("DialogueController_Jen.ChoiceCallback: All entrances unlocked, cannot buy key"); return false; } int firstLockedEntranceID = instance.GetFirstLockedEntranceID(); if (firstLockedEntranceID == -1) { ModLogger.Warning("DialogueController_Jen.ChoiceCallback: No locked entrances found"); return false; } if (NetworkSingleton<MoneyManager>.Instance.cashBalance < __instance.KeyItem.BasePurchasePrice) { return true; } NetworkSingleton<MoneyManager>.Instance.ChangeCashBalance(0f - __instance.KeyItem.BasePurchasePrice, true, true); ((DialogueController)__instance).npc.Inventory.InsertItem((ItemInstance)(object)NetworkSingleton<MoneyManager>.Instance.GetCashInstance(__instance.KeyItem.BasePurchasePrice), true); PlayerSingleton<PlayerInventory>.Instance.AddItemToInventory(((ItemDefinition)__instance.KeyItem).GetDefaultInstance(1)); ModLogger.Info($"DialogueController_Jen.ChoiceCallback: Player bought key for entrance {firstLockedEntranceID}"); return false; } catch (Exception exception) { ModLogger.Error("Error in DialogueController_Jen.ChoiceCallback prefix", exception); return true; } } } }