

using 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;
}
}
}
}using 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;
}
}
}
}