Decompiled source of ManorClubbingRevived v1.1.0

plugins/ManorClubbingRevived/ManorClubbingRevived.dll

Decompiled 21 hours ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using ExitGames.Client.Photon;
using HarmonyLib;
using Photon.Pun;
using Photon.Realtime;
using REPOLib.Modules;
using REPOLib.Objects.Sdk;
using UnityEngine;
using UnityEngine.Networking;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("ManorClubbingRevived")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("ManorClubbingRevived")]
[assembly: AssemblyTitle("ManorClubbingRevived")]
[assembly: AssemblyVersion("1.0.0.0")]
namespace ManorClubbingRevived;

internal class ClubAudioFix : MonoBehaviour
{
	private void Awake()
	{
		if (!Plugin.MixClubMusic.Value)
		{
			return;
		}
		AudioManager instance = AudioManager.instance;
		if ((Object)(object)instance == (Object)null || (Object)(object)instance.MusicMasterGroup == (Object)null)
		{
			Plugin.Log.LogWarning((object)(((Object)this).name + ": AudioManager not ready, club music stays unmixed."));
			return;
		}
		AudioSource[] componentsInChildren = ((Component)this).GetComponentsInChildren<AudioSource>(true);
		foreach (AudioSource val in componentsInChildren)
		{
			if ((Object)(object)val.outputAudioMixerGroup == (Object)null)
			{
				val.outputAudioMixerGroup = instance.MusicMasterGroup;
			}
		}
	}
}
internal static class ClubLevel
{
	private static readonly string[] RoomAssets = new string[3] { "manorclub/start_clubbing_1_1", "manorclub/start_clubbing_2_1", "manorclub/start_clubbing_2_2" };

	private static readonly string[] SwappedChildren = new string[2] { "Truck Run", "Extraction Point" };

	public static void Register()
	{
		//IL_0138: Unknown result type (might be due to invalid IL or missing references)
		//IL_013f: Expected O, but got Unknown
		RunManager instance = RunManager.instance;
		Level val = ((IEnumerable<Level>)instance.levels).FirstOrDefault((Func<Level, bool>)((Level l) => (Object)(object)l != (Object)null && ((Object)l).name == "Level - Manor")) ?? ((IEnumerable<Level>)instance.levels).FirstOrDefault((Func<Level, bool>)((Level l) => (Object)(object)l != (Object)null && ((Object)l).name.Contains("Manor")));
		if ((Object)(object)val == (Object)null)
		{
			Plugin.Log.LogError((object)"Vanilla Manor level not found, aborting.");
			return;
		}
		AssetBundle val2 = AssetBundle.LoadFromFile(Plugin.BundlePath);
		if ((Object)(object)val2 == (Object)null)
		{
			Plugin.Log.LogError((object)("Could not load " + Plugin.BundlePath + "."));
			return;
		}
		GameObject val3 = val.StartRooms.Select((PrefabRef r) => ((PrefabRef<GameObject>)(object)r)?.Prefab).FirstOrDefault((Func<GameObject, bool>)((GameObject p) => (Object)(object)p != (Object)null && SwappedChildren.All((string c) => (Object)(object)p.transform.Find(c) != (Object)null)));
		if ((Object)(object)val3 == (Object)null)
		{
			Plugin.Log.LogWarning((object)"No vanilla Manor start room with a truck found, keeping the old truck.");
		}
		else
		{
			Plugin.Log.LogInfo((object)("Using vanilla start room '" + ((Object)val3).name + "' as truck donor."));
		}
		GameObject val4 = new GameObject("ManorClub Templates");
		val4.SetActive(false);
		Object.DontDestroyOnLoad((Object)(object)val4);
		List<GameObject> list = new List<GameObject>();
		string[] roomAssets = RoomAssets;
		foreach (string text in roomAssets)
		{
			GameObject val5 = val2.LoadAsset<GameObject>(text);
			if ((Object)(object)val5 == (Object)null)
			{
				Plugin.Log.LogError((object)("Missing '" + text + "' in bundle."));
			}
			else
			{
				list.Add(StartRoomBuilder.Build(val5, val3, val4.transform, SwappedChildren));
			}
		}
		if (list.Count != 0)
		{
			Level val6 = Object.Instantiate<Level>(val);
			((Object)val6).name = "Level - Manor Club";
			val6.NarrativeName = "Manor Club";
			val6.NarrativeNameLocalized = null;
			Dictionary<string, List<PrefabRef>> saved = ModuleLists.Snapshot(val);
			LevelContent obj = ScriptableObject.CreateInstance<LevelContent>();
			((Object)obj).name = "Manor Club";
			SetField(obj, "_level", val6);
			SetField(obj, "_startRooms", list);
			Levels.RegisterLevel(obj);
			ModuleLists.Restore(val6, saved);
			Plugin.Log.LogInfo((object)$"Registered '{((Object)val6).name}' with {list.Count} club start rooms.");
		}
	}

	private static void SetField(object target, string field, object value)
	{
		target.GetType().GetField(field, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).SetValue(target, value);
	}
}
internal static class ModuleLists
{
	private static readonly string[] Fields = new string[12]
	{
		"ModulesNormal1", "ModulesPassage1", "ModulesDeadEnd1", "ModulesExtraction1", "ModulesNormal2", "ModulesPassage2", "ModulesDeadEnd2", "ModulesExtraction2", "ModulesNormal3", "ModulesPassage3",
		"ModulesDeadEnd3", "ModulesExtraction3"
	};

	public static Dictionary<string, List<PrefabRef>> Snapshot(Level level)
	{
		return Fields.ToDictionary((string f) => f, (string f) => new List<PrefabRef>(Get(level, f) ?? new List<PrefabRef>()));
	}

	public static void Restore(Level level, Dictionary<string, List<PrefabRef>> saved)
	{
		foreach (KeyValuePair<string, List<PrefabRef>> item in saved)
		{
			typeof(Level).GetField(item.Key).SetValue(level, item.Value);
		}
	}

	private static List<PrefabRef> Get(Level level, string field)
	{
		return (List<PrefabRef>)typeof(Level).GetField(field).GetValue(level);
	}
}
internal class ClubSongPicker : MonoBehaviour
{
	private const string PropertyKey = "ManorClub.Song";

	private const float ClientTimeout = 10f;

	public AudioSource music;

	private void Start()
	{
		//IL_003b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0046: Unknown result type (might be due to invalid IL or missing references)
		//IL_0060: Expected O, but got Unknown
		if ((Object)(object)music == (Object)null)
		{
			return;
		}
		if (!SemiFunc.IsMultiplayer())
		{
			Apply(Pick());
			return;
		}
		string text = Token();
		if (PhotonNetwork.IsMasterClient)
		{
			string text2 = Pick();
			PhotonNetwork.CurrentRoom.SetCustomProperties(new Hashtable { [(object)"ManorClub.Song"] = text + "|" + text2 }, (Hashtable)null, (WebFlags)null);
			Apply(text2);
		}
		else
		{
			((MonoBehaviour)this).StartCoroutine(WaitForHost(text));
		}
	}

	private static string Pick()
	{
		IReadOnlyList<string> names = SongLibrary.Names;
		if (names.Count == 0 || Random.value >= Plugin.CustomSongChance.Value)
		{
			return "";
		}
		return names[Random.Range(0, names.Count)];
	}

	private string Token()
	{
		int num = (from v in ((Component)this).GetComponentsInChildren<PhotonView>(true)
			select v.ViewID into id
			where id > 0
			select id).DefaultIfEmpty(0).Min();
		return RunManager.instance.levelsCompleted + ":" + num;
	}

	private IEnumerator WaitForHost(string token)
	{
		float waited = 0f;
		while (waited < 10f)
		{
			if (PhotonNetwork.CurrentRoom != null && ((Dictionary<object, object>)(object)((RoomInfo)PhotonNetwork.CurrentRoom).CustomProperties).TryGetValue((object)"ManorClub.Song", out object value) && value is string text && text.StartsWith(token + "|"))
			{
				Apply(text.Substring(token.Length + 1));
				yield break;
			}
			waited += Time.unscaledDeltaTime;
			yield return null;
		}
		Plugin.Log.LogWarning((object)"No song pick from host, playing the original.");
		Apply("");
	}

	private void Apply(string song)
	{
		if (!SongLibrary.Has(song))
		{
			if (song != "")
			{
				Plugin.Log.LogWarning((object)("Club song '" + song + "' not installed here, playing the original."));
			}
			music.Play();
			return;
		}
		Plugin.Log.LogInfo((object)("Club song: " + song));
		((MonoBehaviour)this).StartCoroutine(SongLibrary.Load(song, delegate(AudioClip clip)
		{
			if ((Object)(object)clip != (Object)null)
			{
				music.clip = clip;
			}
			music.Play();
		}));
	}
}
[BepInPlugin("Slumberdac.ManorClubbingRevived", "ManorClubbingRevived", "1.1.0")]
[BepInDependency(/*Could not decode attribute arguments.*/)]
public class Plugin : BaseUnityPlugin
{
	public const string Guid = "Slumberdac.ManorClubbingRevived";

	public const string Name = "ManorClubbingRevived";

	public const string Version = "1.1.0";

	public const string BundleFile = "manorclub.bundle";

	internal static ManualLogSource Log;

	internal static string BundlePath;

	internal static ConfigEntry<bool> MixClubMusic;

	internal static ConfigEntry<float> CustomSongChance;

	private void Awake()
	{
		//IL_0059: Unknown result type (might be due to invalid IL or missing references)
		//IL_0063: Expected O, but got Unknown
		//IL_0091: Unknown result type (might be due to invalid IL or missing references)
		Log = ((BaseUnityPlugin)this).Logger;
		MixClubMusic = ((BaseUnityPlugin)this).Config.Bind<bool>("Audio", "MixClubMusic", false, "Route the club music through the game's music volume slider. Off keeps the original unmixed audio.");
		CustomSongChance = ((BaseUnityPlugin)this).Config.Bind<float>("Audio", "CustomSongChance", 0.5f, new ConfigDescription("Chance the club plays an extra song instead of its original one. Only the host's value counts.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f), Array.Empty<object>()));
		BundlePath = Path.Combine(Path.GetDirectoryName(((BaseUnityPlugin)this).Info.Location), "manorclub.bundle");
		SongLibrary.Scan();
		new Harmony("Slumberdac.ManorClubbingRevived").PatchAll(typeof(RunManagerAwakePatch));
		Log.LogInfo((object)("ManorClubbingRevived 1.1.0 loaded, bundle: " + BundlePath));
	}
}
[HarmonyPatch(typeof(RunManager), "Awake")]
internal static class RunManagerAwakePatch
{
	private static bool _done;

	[HarmonyPostfix]
	[HarmonyAfter(new string[] { "REPOLib" })]
	[HarmonyPriority(0)]
	private static void Postfix()
	{
		if (_done)
		{
			return;
		}
		_done = true;
		try
		{
			ClubLevel.Register();
		}
		catch (Exception arg)
		{
			Plugin.Log.LogError((object)$"Failed to register Manor Club: {arg}");
		}
	}
}
internal static class SongLibrary
{
	public const string FolderName = "ManorClubSongs";

	private static readonly Dictionary<string, string> Paths = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);

	private static readonly Dictionary<string, AudioClip> Cache = new Dictionary<string, AudioClip>(StringComparer.OrdinalIgnoreCase);

	public static IReadOnlyList<string> Names { get; private set; } = Array.Empty<string>();

	public static void Scan()
	{
		//IL_0036: Unknown result type (might be due to invalid IL or missing references)
		Paths.Clear();
		string[] directories = Directory.GetDirectories(Paths.PluginPath, "ManorClubSongs", SearchOption.AllDirectories);
		for (int i = 0; i < directories.Length; i++)
		{
			string[] files = Directory.GetFiles(directories[i]);
			foreach (string text in files)
			{
				if ((int)TypeOf(text) != 0)
				{
					string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(text);
					if (Paths.ContainsKey(fileNameWithoutExtension))
					{
						Plugin.Log.LogWarning((object)("Duplicate club song '" + fileNameWithoutExtension + "', keeping " + Paths[fileNameWithoutExtension] + "."));
					}
					else
					{
						Paths[fileNameWithoutExtension] = text;
					}
				}
			}
		}
		Names = Paths.Keys.OrderBy<string, string>((string n) => n, StringComparer.OrdinalIgnoreCase).ToList();
		Plugin.Log.LogInfo((object)$"Found {Names.Count} extra club songs.");
	}

	public static bool Has(string name)
	{
		if (!string.IsNullOrEmpty(name))
		{
			return Paths.ContainsKey(name);
		}
		return false;
	}

	public static IEnumerator Load(string name, Action<AudioClip> done)
	{
		if (Cache.TryGetValue(name, out var value) && (Object)(object)value != (Object)null)
		{
			done(value);
			yield break;
		}
		string text = Paths[name];
		UnityWebRequest req = UnityWebRequestMultimedia.GetAudioClip(new Uri(text).AbsoluteUri, TypeOf(text));
		try
		{
			((DownloadHandlerAudioClip)req.downloadHandler).compressed = true;
			yield return req.SendWebRequest();
			if ((int)req.result != 1)
			{
				Plugin.Log.LogError((object)("Could not load club song '" + name + "': " + req.error));
				done(null);
				yield break;
			}
			AudioClip content = DownloadHandlerAudioClip.GetContent(req);
			((Object)content).name = name;
			Cache[name] = content;
			done(content);
		}
		finally
		{
			((IDisposable)req)?.Dispose();
		}
	}

	private static AudioType TypeOf(string file)
	{
		return (AudioType)(Path.GetExtension(file).ToLowerInvariant() switch
		{
			".ogg" => 14, 
			".mp3" => 13, 
			".wav" => 20, 
			_ => 0, 
		});
	}
}
internal static class StartRoomBuilder
{
	public static GameObject Build(GameObject source, GameObject donor, Transform parent, string[] children)
	{
		//IL_013e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0151: Unknown result type (might be due to invalid IL or missing references)
		//IL_0164: Unknown result type (might be due to invalid IL or missing references)
		GameObject val = Object.Instantiate<GameObject>(source, parent);
		((Object)val).name = ((Object)source).name;
		val.AddComponent<ClubAudioFix>();
		Transform val2 = val.transform.Find("Audio Source");
		AudioSource val3 = (((Object)(object)val2 != (Object)null) ? ((Component)val2).GetComponent<AudioSource>() : null);
		if ((Object)(object)val3 != (Object)null)
		{
			val3.playOnAwake = false;
			val.AddComponent<ClubSongPicker>().music = val3;
		}
		else
		{
			Plugin.Log.LogWarning((object)(((Object)val).name + ": no club music source found, custom songs disabled for this room."));
		}
		if ((Object)(object)donor == (Object)null)
		{
			return val;
		}
		List<(Transform, Transform)> list = new List<(Transform, Transform)>();
		foreach (string text in children)
		{
			Transform val4 = val.transform.Find(text);
			Transform val5 = donor.transform.Find(text);
			if ((Object)(object)val4 == (Object)null || (Object)(object)val5 == (Object)null)
			{
				Plugin.Log.LogWarning((object)(((Object)val).name + ": cannot swap '" + text + "'."));
				continue;
			}
			((Object)val4).name = text + " (old)";
			GameObject val6 = Object.Instantiate<GameObject>(((Component)val5).gameObject, val.transform);
			((Object)val6).name = text;
			val6.transform.SetSiblingIndex(val4.GetSiblingIndex());
			val6.transform.localPosition = val4.localPosition;
			val6.transform.localRotation = val4.localRotation;
			val6.transform.localScale = val4.localScale;
			list.Add((val4, val6.transform));
		}
		new ReferenceFixer(val.transform, donor.transform, list).Run();
		foreach (var item in list)
		{
			Object.DestroyImmediate((Object)(object)((Component)item.Item1).gameObject);
		}
		return val;
	}
}
internal class ReferenceFixer
{
	private readonly Transform _room;

	private readonly Transform _donor;

	private readonly List<(Transform oldT, Transform newT)> _swaps;

	private int _fixed;

	private int _lost;

	public ReferenceFixer(Transform room, Transform donor, List<(Transform, Transform)> swaps)
	{
		_room = room;
		_donor = donor;
		_swaps = swaps;
	}

	public void Run()
	{
		MonoBehaviour[] componentsInChildren = ((Component)_room).GetComponentsInChildren<MonoBehaviour>(true);
		foreach (MonoBehaviour mb in componentsInChildren)
		{
			if ((Object)(object)mb == (Object)null || _swaps.Any(((Transform oldT, Transform newT) s) => IsUnder(((Component)mb).transform, s.oldT)))
			{
				continue;
			}
			foreach (FieldInfo item in SerializedFields(((object)mb).GetType()))
			{
				FixField(mb, item);
			}
		}
		Plugin.Log.LogInfo((object)$"{((Object)_room).name}: fixed {_fixed} references, {_lost} unresolved.");
	}

	private void FixField(MonoBehaviour owner, FieldInfo field)
	{
		//IL_0026: Unknown result type (might be due to invalid IL or missing references)
		//IL_002c: Expected O, but got Unknown
		Type fieldType = field.FieldType;
		object value = field.GetValue(owner);
		if (value == null)
		{
			return;
		}
		if (typeof(Object).IsAssignableFrom(fieldType))
		{
			Object obj = (Object)value;
			if (TryMap(obj, owner, field, out var mapped))
			{
				field.SetValue(owner, mapped);
			}
		}
		else if (fieldType.IsArray && typeof(Object).IsAssignableFrom(fieldType.GetElementType()))
		{
			Array obj2 = (Array)value;
			bool flag = false;
			List<object> list = new List<object>();
			foreach (object item in obj2)
			{
				Object val = (Object)((item is Object) ? item : null);
				if (val != null && val != (Object)null && TryMap(val, owner, field, out var mapped2))
				{
					flag = true;
					if (mapped2 != (Object)null)
					{
						list.Add(mapped2);
					}
				}
				else
				{
					list.Add(item);
				}
			}
			if (flag)
			{
				Array array = Array.CreateInstance(fieldType.GetElementType(), list.Count);
				for (int i = 0; i < list.Count; i++)
				{
					array.SetValue(list[i], i);
				}
				field.SetValue(owner, array);
			}
		}
		else
		{
			if (!fieldType.IsGenericType || !(fieldType.GetGenericTypeDefinition() == typeof(List<>)) || !typeof(Object).IsAssignableFrom(fieldType.GetGenericArguments()[0]))
			{
				return;
			}
			IList list2 = (IList)value;
			for (int num = list2.Count - 1; num >= 0; num--)
			{
				object? obj3 = list2[num];
				Object val2 = (Object)((obj3 is Object) ? obj3 : null);
				if (val2 != null && val2 != (Object)null && TryMap(val2, owner, field, out var mapped3))
				{
					if (mapped3 != (Object)null)
					{
						list2[num] = mapped3;
					}
					else
					{
						list2.RemoveAt(num);
					}
				}
			}
		}
	}

	private bool TryMap(Object obj, MonoBehaviour owner, FieldInfo field, out Object mapped)
	{
		mapped = obj;
		Transform val = TransformOf(obj);
		if ((Object)(object)val == (Object)null)
		{
			return false;
		}
		foreach (var (val2, root) in _swaps)
		{
			if (IsUnder(val, val2))
			{
				mapped = Resolve(obj, val, FindByPath(root, PathFrom(val2, val)));
				Count(mapped, obj, owner, field, val2);
				return true;
			}
		}
		if (IsUnder(val, _room))
		{
			return false;
		}
		if (IsUnder(val, _donor))
		{
			mapped = Resolve(obj, val, FindByPath(_room, PathFrom(_donor, val)));
			Count(mapped, obj, owner, field, _donor);
			return true;
		}
		return false;
	}

	private void Count(Object mapped, Object original, MonoBehaviour owner, FieldInfo field, Transform origin)
	{
		if (mapped != (Object)null)
		{
			_fixed++;
			return;
		}
		_lost++;
		Transform t = TransformOf(original);
		string text = string.Join("/", from s in PathFrom(origin, t)
			select s.name);
		Plugin.Log.LogDebug((object)(((Object)_room).name + ": unresolved " + ((object)owner).GetType().Name + "." + field.Name + " on '" + string.Join("/", from s in PathFrom(_room, ((Component)owner).transform)
			select s.name) + "' -> " + ((object)original).GetType().Name + " '" + ((Object)origin).name + "/" + text + "'"));
	}

	private static Object Resolve(Object obj, Transform from, Transform to)
	{
		if ((Object)(object)to == (Object)null)
		{
			return null;
		}
		if (obj is GameObject)
		{
			return (Object)(object)((Component)to).gameObject;
		}
		if (obj is Transform)
		{
			return (Object)(object)to;
		}
		Component val = (Component)(object)((obj is Component) ? obj : null);
		if (val != null)
		{
			Component[] components = ((Component)from).GetComponents(((object)val).GetType());
			Component[] components2 = ((Component)to).GetComponents(((object)val).GetType());
			int num = Array.IndexOf(components, val);
			if (num < 0 || num >= components2.Length)
			{
				return null;
			}
			return (Object)(object)components2[num];
		}
		return null;
	}

	private static Transform TransformOf(Object obj)
	{
		GameObject val = (GameObject)(object)((obj is GameObject) ? obj : null);
		if (val != null)
		{
			return val.transform;
		}
		Component val2 = (Component)(object)((obj is Component) ? obj : null);
		if (val2 != null)
		{
			return val2.transform;
		}
		return null;
	}

	private static bool IsUnder(Transform t, Transform root)
	{
		Transform val = t;
		while ((Object)(object)val != (Object)null)
		{
			if ((Object)(object)val == (Object)(object)root)
			{
				return true;
			}
			val = val.parent;
		}
		return false;
	}

	private static List<(string name, int occurrence)> PathFrom(Transform root, Transform t)
	{
		List<(string, int)> list = new List<(string, int)>();
		Transform val = t;
		while ((Object)(object)val != (Object)(object)root && (Object)(object)val != (Object)null)
		{
			int num = 0;
			Transform parent = val.parent;
			for (int i = 0; i < val.GetSiblingIndex(); i++)
			{
				if (((Object)parent.GetChild(i)).name == ((Object)val).name)
				{
					num++;
				}
			}
			list.Add((((Object)val).name, num));
			val = val.parent;
		}
		list.Reverse();
		return list;
	}

	private static Transform FindByPath(Transform root, List<(string name, int occurrence)> steps)
	{
		Transform val = root;
		foreach (var step in steps)
		{
			string item = step.name;
			int item2 = step.occurrence;
			Transform val2 = null;
			int num = 0;
			for (int i = 0; i < val.childCount; i++)
			{
				Transform child = val.GetChild(i);
				if (!(((Object)child).name != item) && num++ == item2)
				{
					val2 = child;
					break;
				}
			}
			if ((Object)(object)val2 == (Object)null)
			{
				return null;
			}
			val = val2;
		}
		return val;
	}

	private static IEnumerable<FieldInfo> SerializedFields(Type type)
	{
		Type t = type;
		while (t != null && t != typeof(MonoBehaviour))
		{
			FieldInfo[] fields = t.GetFields(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
			foreach (FieldInfo fieldInfo in fields)
			{
				if (!fieldInfo.IsNotSerialized && !fieldInfo.IsInitOnly && (fieldInfo.IsPublic || ((MemberInfo)fieldInfo).GetCustomAttribute<SerializeField>() != null))
				{
					yield return fieldInfo;
				}
			}
			t = t.BaseType;
		}
	}
}