Decompiled source of CosmeticBoxSpawner v0.3.0

BepInEx\plugins\CosmeticBoxSpawner.dll

Decompiled a day ago
using System;
using System.Collections;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Photon.Pun;
using UnityEngine;

[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("bakhusse")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyCopyright("Copyright (c) bakhusse")]
[assembly: AssemblyDescription("Configurable cosmetic box spawning for R.E.P.O.")]
[assembly: AssemblyFileVersion("0.3.0.0")]
[assembly: AssemblyInformationalVersion("0.3.0")]
[assembly: AssemblyProduct("Cosmetic Box Spawner")]
[assembly: AssemblyTitle("CosmeticBoxSpawner")]
[assembly: AssemblyVersion("0.3.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace CosmeticBoxSpawner
{
	[BepInPlugin("maxim.repo.cosmeticboxspawner", "Cosmetic Box Spawner", "0.3.0")]
	[BepInProcess("REPO.exe")]
	public sealed class CosmeticBoxSpawnerPlugin : BaseUnityPlugin
	{
		private ConfigEntry<KeyCode>[] _keys;

		private ConfigEntry<float> _distance;

		private ConfigEntry<float> _height;

		private ConfigEntry<float> _boxScale;

		private ConfigEntry<bool> _enabled;

		internal static ConfigEntry<int> TokenCap;

		internal static ConfigEntry<bool> RescueTokens;

		internal static ManualLogSource ModLogger;

		private static readonly string[] RarityNames = new string[4] { "Common", "Uncommon", "Rare", "Ultra Rare" };

		private void Awake()
		{
			//IL_00e9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f3: Expected O, but got Unknown
			//IL_0191: Unknown result type (might be due to invalid IL or missing references)
			ModLogger = ((BaseUnityPlugin)this).Logger;
			TokenCap = ((BaseUnityPlugin)this).Config.Bind<int>("Rewards", "MaxBoxesPerLevel", 10000, "Number of cosmetic boxes that can count towards rewards in one level (1-100000). 10000 is effectively unlimited.");
			RescueTokens = ((BaseUnityPlugin)this).Config.Bind<bool>("Rewards", "RescuePendingTokens", true, "At the end of the results screen, credit cosmetic tokens not yet processed by its animations.");
			_enabled = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Enabled", true, "Enable spawning cosmetic boxes with hotkeys.");
			_distance = ((BaseUnityPlugin)this).Config.Bind<float>("Placement", "Distance", 2.25f, "Horizontal distance in metres in front of the player.");
			_height = ((BaseUnityPlugin)this).Config.Bind<float>("Placement", "Height", 1.05f, "Height above the player's feet in metres.");
			_boxScale = ((BaseUnityPlugin)this).Config.Bind<float>("Placement", "BoxScale", 0.4f, new ConfigDescription("Size of newly spawned cosmetic boxes relative to normal (0.10 - 1.00). Also scales inherited colliders locally. In multiplayer, size is NOT guaranteed to sync to players without a compatible scaling mod. 1.00 means original size.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.1f, 1f), Array.Empty<object>()));
			_keys = new ConfigEntry<KeyCode>[4]
			{
				((BaseUnityPlugin)this).Config.Bind<KeyCode>("Hotkeys", "Common", (KeyCode)257, "Spawn a Common cosmetic box."),
				((BaseUnityPlugin)this).Config.Bind<KeyCode>("Hotkeys", "Uncommon", (KeyCode)258, "Spawn an Uncommon cosmetic box."),
				((BaseUnityPlugin)this).Config.Bind<KeyCode>("Hotkeys", "Rare", (KeyCode)259, "Spawn a Rare cosmetic box."),
				((BaseUnityPlugin)this).Config.Bind<KeyCode>("Hotkeys", "UltraRare", (KeyCode)260, "Spawn an Ultra Rare cosmetic box.")
			};
			try
			{
				new Harmony("maxim.repo.cosmeticboxspawner").PatchAll(typeof(CosmeticBoxSpawnerPlugin).Assembly);
				((BaseUnityPlugin)this).Logger.LogInfo((object)("Cosmetic Box Spawner v0.3.0: configurable hotkeys; BoxScale=" + _boxScale.Value + "; extraction limit patch registered (" + Mathf.Clamp(TokenCap.Value, 1, 100000) + " boxes/level)."));
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)this).Logger.LogError((object)("Reward patches failed to initialize. Spawning may still work: " + ex));
			}
		}

		private void Update()
		{
			//IL_0021: Unknown result type (might be due to invalid IL or missing references)
			//IL_0026: Unknown result type (might be due to invalid IL or missing references)
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			//IL_002a: Unknown result type (might be due to invalid IL or missing references)
			if (!_enabled.Value || !Application.isFocused)
			{
				return;
			}
			for (int i = 0; i < _keys.Length; i++)
			{
				KeyCode value = _keys[i].Value;
				if ((int)value != 0 && Input.GetKeyDown(value))
				{
					SpawnBox(i);
				}
			}
		}

		private unsafe void SpawnBox(int rarityIndex)
		{
			//IL_0082: Unknown result type (might be due to invalid IL or missing references)
			//IL_0088: Invalid comparison between Unknown and I4
			//IL_011f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0124: Unknown result type (might be due to invalid IL or missing references)
			//IL_018c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0191: Unknown result type (might be due to invalid IL or missing references)
			//IL_0195: Unknown result type (might be due to invalid IL or missing references)
			//IL_019a: Unknown result type (might be due to invalid IL or missing references)
			//IL_019f: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a6: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ab: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b0: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b2: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b7: Unknown result type (might be due to invalid IL or missing references)
			//IL_0140: Unknown result type (might be due to invalid IL or missing references)
			//IL_0145: Unknown result type (might be due to invalid IL or missing references)
			//IL_01cd: Unknown result type (might be due to invalid IL or missing references)
			//IL_01cf: Unknown result type (might be due to invalid IL or missing references)
			//IL_0209: Unknown result type (might be due to invalid IL or missing references)
			//IL_020b: Unknown result type (might be due to invalid IL or missing references)
			//IL_02e7: Unknown result type (might be due to invalid IL or missing references)
			//IL_02e9: Unknown result type (might be due to invalid IL or missing references)
			//IL_027d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0284: Unknown result type (might be due to invalid IL or missing references)
			//IL_0289: Unknown result type (might be due to invalid IL or missing references)
			//IL_0292: Unknown result type (might be due to invalid IL or missing references)
			//IL_02a0: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				if (!SemiFunc.IsMasterClientOrSingleplayer())
				{
					((BaseUnityPlugin)this).Logger.LogWarning((object)"Only the host (or singleplayer) can spawn cosmetic boxes.");
					return;
				}
				PlayerAvatar instance = PlayerAvatar.instance;
				ValuableDirector instance2 = ValuableDirector.instance;
				GameManager instance3 = GameManager.instance;
				if ((Object)(object)instance == (Object)null || (Object)(object)instance2 == (Object)null || (Object)(object)instance3 == (Object)null)
				{
					((BaseUnityPlugin)this).Logger.LogWarning((object)"Enter an active level before spawning cosmetic boxes.");
					return;
				}
				int num = -1;
				if (instance2.cosmeticWorldObjectSetups != null)
				{
					for (int i = 0; i < instance2.cosmeticWorldObjectSetups.Count; i++)
					{
						CosmeticWorldObjectSetup val = instance2.cosmeticWorldObjectSetups[i];
						if (val != null && (int)val.rarity == rarityIndex)
						{
							num = i;
							break;
						}
					}
				}
				if (num < 0)
				{
					((BaseUnityPlugin)this).Logger.LogWarning((object)("No cosmetic box setup found for " + RarityNames[rarityIndex]));
					return;
				}
				CosmeticWorldObjectSetup val2 = instance2.cosmeticWorldObjectSetups[num];
				if (val2 == null || val2.prefab == null || (Object)(object)((PrefabRef<GameObject>)(object)val2.prefab).Prefab == (Object)null)
				{
					((BaseUnityPlugin)this).Logger.LogWarning((object)("No cosmetic box prefab found for " + RarityNames[rarityIndex]));
					return;
				}
				Vector3 forward = ((Component)instance).transform.forward;
				forward.y = 0f;
				if (((Vector3)(ref forward)).sqrMagnitude < 0.0001f)
				{
					forward = Vector3.forward;
				}
				((Vector3)(ref forward)).Normalize();
				float num2 = Mathf.Clamp(_distance.Value, 0.75f, 10f);
				float num3 = Mathf.Clamp(_height.Value, 0.25f, 4f);
				Vector3 val3 = ((Component)instance).transform.position + forward * num2 + Vector3.up * num3;
				Quaternion identity = Quaternion.identity;
				GameObject val4;
				if (instance3.gameMode == 0)
				{
					val4 = Object.Instantiate<GameObject>(((PrefabRef<GameObject>)(object)val2.prefab).Prefab, val3, identity);
				}
				else
				{
					if (!PhotonNetwork.InRoom || !PhotonNetwork.IsMasterClient)
					{
						((BaseUnityPlugin)this).Logger.LogWarning((object)"Photon room not ready, or you are not the room host.");
						return;
					}
					val4 = PhotonNetwork.InstantiateRoomObject(((PrefabRef<GameObject>)(object)val2.prefab).ResourcePath, val3, identity, (byte)0, (object[])null);
				}
				if ((Object)(object)val4 == (Object)null)
				{
					((BaseUnityPlugin)this).Logger.LogError((object)("Failed to spawn a " + RarityNames[rarityIndex] + " box."));
					return;
				}
				float num4 = Mathf.Clamp(_boxScale.Value, 0.1f, 1f);
				if (Mathf.Abs(num4 - 1f) > 0.0001f)
				{
					Vector3 val5 = val4.transform.localScale * num4;
					val4.transform.localScale = val5;
					val4.AddComponent<SpawnedBoxScaleKeeper>().SetTargetScale(val5);
					if (instance3.gameMode != 0)
					{
						((BaseUnityPlugin)this).Logger.LogWarning((object)"BoxScale applied locally on host. Unmodded clients may see a different size/collision shape.");
					}
				}
				ManualLogSource logger = ((BaseUnityPlugin)this).Logger;
				string[] obj = new string[6]
				{
					"Spawned ",
					RarityNames[rarityIndex],
					" cosmetic box at ",
					null,
					null,
					null
				};
				Vector3 val6 = val3;
				obj[3] = ((object)(*(Vector3*)(&val6))/*cast due to .constrained prefix*/).ToString();
				obj[4] = " with local scale factor ";
				obj[5] = num4.ToString();
				logger.LogInfo((object)string.Concat(obj));
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)this).Logger.LogError((object)("Cosmetic box spawn failed: " + ex));
			}
		}
	}
	public sealed class SpawnedBoxScaleKeeper : MonoBehaviour
	{
		private Vector3 _targetScale;

		private bool _isInitialized;

		internal void SetTargetScale(Vector3 target)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			_targetScale = target;
			_isInitialized = true;
		}

		private void LateUpdate()
		{
			//IL_000e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			if (_isInitialized && ((Component)this).transform.localScale != _targetScale)
			{
				((Component)this).transform.localScale = _targetScale;
			}
		}
	}
	[HarmonyPatch(typeof(CosmeticWorldObject), "ExtractRPC")]
	internal static class CosmeticExtractionWindowPatch
	{
		[ThreadStatic]
		private static int _depth;

		internal static bool IsExtracting => _depth > 0;

		[HarmonyPrefix]
		private static void BeginExtract()
		{
			_depth++;
		}

		[HarmonyFinalizer]
		private static Exception EndExtract(Exception __exception)
		{
			if (_depth > 0)
			{
				_depth--;
			}
			return __exception;
		}
	}
	[HarmonyPatch(typeof(ValuableDirector), "CosmeticWorldObjectLevelLoopsClampedGet")]
	internal static class CosmeticExtractionLimitPatch
	{
		private static bool _loggedFirstUse;

		[HarmonyPostfix]
		private static void RaiseLimit(ref int __result)
		{
			if (CosmeticExtractionWindowPatch.IsExtracting && CosmeticBoxSpawnerPlugin.TokenCap != null)
			{
				int num = Mathf.Clamp(CosmeticBoxSpawnerPlugin.TokenCap.Value, 1, 100000);
				if (!_loggedFirstUse)
				{
					_loggedFirstUse = true;
					CosmeticBoxSpawnerPlugin.ModLogger.LogInfo((object)("Extraction cap override active (" + num + " cosmetic crates/level)."));
				}
				if (__result < num)
				{
					__result = num;
				}
			}
		}
	}
	internal static class PendingTokenRewards
	{
		private static bool _working;

		private static bool _warned;

		private static readonly FieldInfo IndexField = AccessTools.Field(typeof(ResultScreenUI), "cosmeticIndex");

		private static readonly FieldInfo RewardedField = AccessTools.Field(typeof(ResultScreenUI), "cosmeticIndexRewarded");

		private static readonly FieldInfo ExtractedField = AccessTools.Field(typeof(RoundDirector), "cosmeticWorldObjectsExtracted");

		private static readonly MethodInfo TokenAddMethod = typeof(MetaManager).GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).FirstOrDefault((MethodInfo m) => m.Name == "CosmeticTokenAdd" && m.GetParameters().Length == 1);

		internal static void AwardRemaining(string reason)
		{
			if (_working || CosmeticBoxSpawnerPlugin.RescueTokens == null || !CosmeticBoxSpawnerPlugin.RescueTokens.Value)
			{
				return;
			}
			ResultScreenUI instance = ResultScreenUI.instance;
			RoundDirector instance2 = RoundDirector.instance;
			MetaManager instance3 = MetaManager.instance;
			if ((Object)(object)instance == (Object)null || (Object)(object)instance2 == (Object)null || (Object)(object)instance3 == (Object)null)
			{
				return;
			}
			if (IndexField == null || RewardedField == null || ExtractedField == null || TokenAddMethod == null)
			{
				if (!_warned)
				{
					_warned = true;
					CosmeticBoxSpawnerPlugin.ModLogger.LogWarning((object)"Could not find the expected results-screen reward fields in this game version. Automatic payout disabled; extraction limit patch is independent.");
				}
				return;
			}
			_working = true;
			try
			{
				if (ExtractedField.GetValue(instance2) is IList { Count: not 0 } list)
				{
					int num = (int)IndexField.GetValue(instance);
					bool flag = (bool)RewardedField.GetValue(instance);
					if (num > 0 && !flag)
					{
						num--;
					}
					num = Math.Max(0, Math.Min(num, list.Count));
					int num2 = 0;
					for (int i = num; i < list.Count; i++)
					{
						TokenAddMethod.Invoke(instance3, new object[1] { list[i] });
						IndexField.SetValue(instance, i + 1);
						RewardedField.SetValue(instance, true);
						num2++;
					}
					if (num2 > 0)
					{
						CosmeticBoxSpawnerPlugin.ModLogger.LogInfo((object)("Credited " + num2 + " pending cosmetic token(s): " + reason));
					}
				}
			}
			catch (Exception ex)
			{
				CosmeticBoxSpawnerPlugin.ModLogger.LogWarning((object)("Pending token payout failed: " + ex));
			}
			finally
			{
				_working = false;
			}
		}
	}
	[HarmonyPatch(typeof(ResultScreenUI), "Disable")]
	internal static class ResultScreenDisablePatch
	{
		[HarmonyPrefix]
		private static void OnDisable()
		{
			PendingTokenRewards.AwardRemaining("results screen closed");
		}
	}
	[HarmonyPatch(typeof(RunManager), "ChangeLevel")]
	internal static class ResultChangeLevelPatch
	{
		[HarmonyPrefix]
		private static void OnChangeLevel()
		{
			PendingTokenRewards.AwardRemaining("level changed");
		}
	}
	[HarmonyPatch(typeof(ResultScreenUI), "Update")]
	internal static class ResultScreenStatePatch
	{
		private static bool _wasShowingResults;

		[HarmonyPostfix]
		private static void WatchForEnd()
		{
			//IL_0012: Unknown result type (might be due to invalid IL or missing references)
			//IL_0018: Invalid comparison between Unknown and I4
			bool flag = (Object)(object)GameDirector.instance != (Object)null && (int)GameDirector.instance.currentState == 4;
			if (_wasShowingResults && !flag)
			{
				PendingTokenRewards.AwardRemaining("results state ended");
			}
			_wasShowingResults = flag;
		}
	}
}