Decompiled source of Mimic v0.1.2

plugins/MuckMimic.dll

Decompiled a day ago
using System;
using System.Collections.Generic;
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 UnityEngine;
using UnityEngine.AI;
using UnityEngine.SceneManagement;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.0", FrameworkDisplayName = ".NET Standard 2.0")]
[assembly: AssemblyCompany("MuckMimic")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyDescription("Some paid chests become a buffed mob that drops the chest's loot. Host-only.")]
[assembly: AssemblyFileVersion("0.1.2.0")]
[assembly: AssemblyInformationalVersion("0.1.2+879fc7ac66fec639f2f84b4b7a65d5c61c9ed734")]
[assembly: AssemblyProduct("Mimic")]
[assembly: AssemblyTitle("MuckMimic")]
[assembly: InternalsVisibleTo("MuckMimic.Tests")]
[assembly: AssemblyVersion("0.1.2.0")]
namespace MuckMimic;

[HarmonyPatch(typeof(LootContainerInteract), "ServerExecute", new Type[] { typeof(int) })]
[HarmonyPriority(200)]
internal static class LootContainerServerExecutePatch
{
	private static bool Prefix(LootContainerInteract __instance)
	{
		try
		{
			return !MimicHost.TryMimic(__instance);
		}
		catch (Exception arg)
		{
			MimicPlugin.Log.LogError((object)$"Mimic chest patch failed: {arg}");
			bool mimic;
			return !(MimicPlugin.Claims.TryGet(__instance, out mimic) && mimic);
		}
	}
}
internal sealed class MimicClaims
{
	private sealed class Box
	{
		public bool Mimic;
	}

	private readonly ConditionalWeakTable<object, Box> _claims = new ConditionalWeakTable<object, Box>();

	public bool TryGet(object chest, out bool mimic)
	{
		mimic = false;
		if (chest == null)
		{
			return false;
		}
		if (!_claims.TryGetValue(chest, out var value))
		{
			return false;
		}
		mimic = value.Mimic;
		return true;
	}

	public void Record(object chest, bool mimic)
	{
		if (chest != null)
		{
			_claims.GetValue(chest, (object _) => new Box
			{
				Mimic = mimic
			});
		}
	}
}
internal sealed class MimicConfig
{
	public readonly ConfigEntry<bool> Enabled;

	public readonly ConfigEntry<float> Chance;

	public readonly ConfigEntry<float> MobMultiplier;

	public readonly ConfigEntry<int> LateMobFromDay;

	public readonly ConfigEntry<string> EarlyMobName;

	public readonly ConfigEntry<string> LateMobName;

	public readonly ConfigEntry<bool> ExtraWhite;

	public readonly ConfigEntry<bool> ChatMessages;

	public readonly ConfigEntry<bool> VerboseLogging;

	public MimicConfig(ConfigFile cfg)
	{
		//IL_004c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0056: Expected O, but got Unknown
		//IL_0085: Unknown result type (might be due to invalid IL or missing references)
		//IL_008f: Expected O, but got Unknown
		//IL_00b6: Unknown result type (might be due to invalid IL or missing references)
		//IL_00c0: Expected O, but got Unknown
		Enabled = cfg.Bind<bool>("Mimic", "Enabled", true, "When false, chests drop loot as usual. Only the host's value is used.");
		Chance = cfg.Bind<float>("Mimic", "Chance", 0.06f, new ConfigDescription("Chance from 0 to 1 that a paid chest spawns a mimic instead of loot. The 0.06 default is not from the game. Only the host's value is used.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f), Array.Empty<object>()));
		MobMultiplier = cfg.Bind<float>("Mimic", "MobMultiplier", 1.75f, new ConfigDescription("Multiplier passed to the vanilla mob spawn. The Battle Totem uses 1.75, which draws the mob at 1.4 times size and drops 1.25 times mob loot. 1 is allowed and spawns a normal mob, because the game only buffs a multiplier above 1. Only the host's value is used.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 10f), Array.Empty<object>()));
		LateMobFromDay = cfg.Bind<int>("Mimic", "LateMobFromDay", 5, new ConfigDescription("Use LateMobName when GameLoop.currentDay is at least this. In Update 4, currentDay starts at -1 and the first day writes 0, so 5 is the sixth day if you count the opening day as day 1. Only the host's value is used.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 1000), Array.Empty<object>()));
		EarlyMobName = cfg.Bind<string>("Mimic", "EarlyMobName", "Goblin", "MobType.name spawned before LateMobFromDay. Update 4 compares kills to the literal Goblin. Only the host's value is used.");
		LateMobName = cfg.Bind<string>("Mimic", "LateMobName", "Stone Golem", "MobType.name spawned from LateMobFromDay on. Default Stone Golem, a real Update 4 mob, so every late mimic is a buffed Stone Golem. A name that matches no mob, a boss, or a neutral animal falls back to the Battle Totem pick, and the verbose log lists the real names. Only the host's value is used.");
		ExtraWhite = cfg.Bind<bool>("Mimic", "ExtraWhite", true, "When the mimic dies, also drop one White powerup. Only the host's value is used.");
		ChatMessages = cfg.Bind<bool>("Mimic", "ChatMessages", true, "Tell the whole lobby, vanilla players included, when a chest turns out to be a mimic and when it drops its loot. Only the host's value is used.");
		VerboseLogging = cfg.Bind<bool>("Debug", "VerboseLogging", false, "Log each roll, the mob name table on the first lookup, each spawn, each death, each loot drop, and each stale entry.");
	}

	public MimicTuning Tuning()
	{
		return new MimicTuning(Chance.Value, MobMultiplier.Value, LateMobFromDay.Value, EarlyMobName.Value, LateMobName.Value, ExtraWhite.Value);
	}
}
internal static class MimicDefaults
{
	public const float Chance = 0.06f;

	public const float MobMultiplier = 1.75f;

	public const int LateMobFromDay = 5;

	public const string EarlyMobName = "Goblin";

	public const string LateMobName = "Stone Golem";

	public const bool ExtraWhite = true;
}
internal static class MimicHost
{
	private static readonly Random Rng = new Random();

	private static readonly HashSet<string> WarnedNames = new HashSet<string>();

	private static bool _loggedMobTable;

	public static bool TryMimic(LootContainerInteract chest)
	{
		if ((Object)(object)chest == (Object)null || !LocalClient.serverOwner)
		{
			return false;
		}
		if (!MimicPlugin.Cfg.Enabled.Value)
		{
			return false;
		}
		if (MimicPlugin.Claims.TryGet(chest, out var mimic))
		{
			return mimic;
		}
		MimicTuning mimicTuning = MimicPlugin.Cfg.Tuning();
		bool flag = MimicRoll.IsEligible(chest.price, chest.testPowerup);
		float num = 0f;
		bool flag2 = false;
		if (flag)
		{
			num = (float)Rng.NextDouble();
			flag2 = MimicRoll.Rolls(num, mimicTuning.Chance);
		}
		if (MimicPlugin.Cfg.VerboseLogging.Value)
		{
			MimicPlugin.Log.LogInfo((object)(flag ? string.Format("Mimic roll {0} against {1} on a chest priced {2}. Result: {3}.", num, mimicTuning.Chance, chest.price, flag2 ? "mimic" : "normal chest") : $"Mimic skipped a chest priced {chest.price}. Test chest: {chest.testPowerup}."));
		}
		if (!flag2)
		{
			MimicPlugin.Claims.Record(chest, mimic: false);
			return false;
		}
		bool flag3 = SpawnMimic(chest, mimicTuning);
		MimicPlugin.Claims.Record(chest, flag3);
		return flag3;
	}

	private static bool SpawnMimic(LootContainerInteract chest, MimicTuning tuning)
	{
		//IL_0006: Unknown result type (might be due to invalid IL or missing references)
		//IL_000b: Unknown result type (might be due to invalid IL or missing references)
		//IL_000c: Unknown result type (might be due to invalid IL or missing references)
		//IL_000d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0012: Unknown result type (might be due to invalid IL or missing references)
		//IL_020e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0161: Unknown result type (might be due to invalid IL or missing references)
		//IL_0167: Unknown result type (might be due to invalid IL or missing references)
		//IL_016d: Unknown result type (might be due to invalid IL or missing references)
		//IL_00f2: Unknown result type (might be due to invalid IL or missing references)
		//IL_01a5: Unknown result type (might be due to invalid IL or missing references)
		//IL_01b3: Unknown result type (might be due to invalid IL or missing references)
		//IL_01c1: Unknown result type (might be due to invalid IL or missing references)
		Vector3 position = ((Component)chest).transform.position;
		Vector3 val = Grounded(position);
		bool flag = false;
		int num = -1;
		int[] array = null;
		MobManager instance = MobManager.Instance;
		try
		{
			if ((Object)(object)instance == (Object)null || (Object)(object)MobSpawner.Instance == (Object)null)
			{
				return false;
			}
			int num2 = ResolveMobType(tuning);
			if (num2 < 0)
			{
				return false;
			}
			Powerup randomPowerup = ItemManager.Instance.GetRandomPowerup(chest.white, chest.blue, chest.gold);
			if ((Object)(object)randomPowerup == (Object)null)
			{
				return false;
			}
			int extraWhiteId = -1;
			if (tuning.ExtraWhite)
			{
				Powerup randomPowerup2 = ItemManager.Instance.GetRandomPowerup(1f, 0f, 0f);
				if ((Object)(object)randomPowerup2 != (Object)null)
				{
					extraWhiteId = randomPowerup2.id;
				}
			}
			array = MimicLoot.Ids(randomPowerup.id, tuning.ExtraWhite, extraWhiteId);
			num = instance.GetNextId();
			MimicPlugin.Claims.Record(chest, mimic: true);
			flag = true;
			MobSpawner.Instance.ServerSpawnNewMob(num, num2, val, tuning.MobMultiplier, 1f, (BossType)0, -1);
		}
		catch (Exception arg)
		{
			MimicPlugin.Log.LogError((object)$"Mimic failed while spawning: {arg}");
			if (!flag)
			{
				return false;
			}
		}
		if ((Object)(object)instance != (Object)null && instance.mobs != null && instance.mobs.ContainsKey(num))
		{
			MimicPlugin.Ledger.Add(num, array, instance, val.x, val.y, val.z);
			if (MimicPlugin.Cfg.VerboseLogging.Value)
			{
				MimicPlugin.Log.LogInfo((object)string.Format("Mimic spawned mob {0} at ({1}, {2}, {3}) with powerups [{4}].", num, val.x, val.y, val.z, string.Join(", ", array)));
			}
			Announce("<color=#e8b04a>The chest was a Mimic! Kill it to claim the loot.</color>");
			return true;
		}
		MimicPlugin.Log.LogWarning((object)$"Mimic mob {num} did not register; dropping its loot at the chest.");
		DropLoot(array, position);
		return true;
	}

	private static int ResolveMobType(MimicTuning tuning)
	{
		MobType[] array = (((Object)(object)MobSpawner.Instance != (Object)null) ? MobSpawner.Instance.allMobs : null);
		if (array == null)
		{
			return -1;
		}
		List<string> list = array.Select((MobType mob) => (!((Object)(object)mob == (Object)null)) ? mob.name : null).ToList();
		if (!_loggedMobTable && MimicPlugin.Cfg.VerboseLogging.Value)
		{
			_loggedMobTable = true;
			MimicPlugin.Log.LogInfo((object)("Mimic mob table: " + string.Join(", ", list)));
		}
		int day = 0;
		GameLoop instance = GameLoop.Instance;
		if ((Object)(object)instance != (Object)null)
		{
			day = instance.currentDay;
		}
		string text = MimicMobPick.PreferredName(day, tuning.LateMobFromDay, tuning.EarlyMobName, tuning.LateMobName);
		int num = MimicMobPick.IndexOf(list, text);
		if (num >= 0 && Suitable(array[num]))
		{
			return num;
		}
		if (num >= 0)
		{
			if (WarnedNames.Add("bad:" + text))
			{
				MimicPlugin.Log.LogWarning((object)("Mimic: '" + text + "' is a boss or a neutral animal. Using the Battle Totem pick."));
			}
		}
		else if (WarnedNames.Add(text ?? ""))
		{
			MimicPlugin.Log.LogWarning((object)("Mimic: no mob named '" + text + "'. Mobs: " + string.Join(", ", list) + ". Using the Battle Totem pick."));
		}
		try
		{
			if ((Object)(object)instance == (Object)null)
			{
				return -1;
			}
			MobType val = instance.SelectMobToSpawn(true);
			return ((Object)(object)val != (Object)null) ? val.id : (-1);
		}
		catch (Exception arg)
		{
			MimicPlugin.Log.LogError((object)$"Mimic Battle Totem pick failed: {arg}");
			return -1;
		}
	}

	public static void PayOnRemove(MobManager mgr, int mobId)
	{
		//IL_00e1: Unknown result type (might be due to invalid IL or missing references)
		//IL_00a9: Unknown result type (might be due to invalid IL or missing references)
		//IL_00b7: Unknown result type (might be due to invalid IL or missing references)
		//IL_00c5: Unknown result type (might be due to invalid IL or missing references)
		//IL_006b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0070: Unknown result type (might be due to invalid IL or missing references)
		if (LocalClient.serverOwner && !((Object)(object)mgr == (Object)null) && MimicPlugin.Ledger.TryGet(mobId, out var pending) && pending.Owner == mgr)
		{
			Vector3 position = default(Vector3);
			((Vector3)(ref position))..ctor(pending.X, pending.Y, pending.Z);
			if (mgr.mobs != null && mgr.mobs.TryGetValue(mobId, out var value) && (Object)(object)value != (Object)null)
			{
				position = ((Component)value).transform.position;
			}
			MimicPlugin.Ledger.Remove(mobId);
			if (MimicPlugin.Cfg.VerboseLogging.Value)
			{
				MimicPlugin.Log.LogInfo((object)$"Mimic mob {mobId} died. Dropping loot at ({position.x}, {position.y}, {position.z}).");
			}
			DropLoot(pending.PowerupIds, position);
			Announce("<color=#e8b04a>The Mimic dropped its loot.</color>");
		}
	}

	private static void Announce(string line)
	{
		if (!MimicPlugin.Cfg.ChatMessages.Value)
		{
			return;
		}
		try
		{
			ServerSend.SendChatMessage(-1, "", line);
		}
		catch (Exception arg)
		{
			MimicPlugin.Log.LogError((object)$"Mimic chat line failed: {arg}");
		}
	}

	private static bool Suitable(MobType mob)
	{
		//IL_0016: Unknown result type (might be due to invalid IL or missing references)
		//IL_001c: Invalid comparison between Unknown and I4
		if ((Object)(object)mob == (Object)null)
		{
			return false;
		}
		if (mob.boss)
		{
			return false;
		}
		return (int)mob.behaviour > 0;
	}

	private static Vector3 Grounded(Vector3 at)
	{
		//IL_0000: Unknown result type (might be due to invalid IL or missing references)
		//IL_0018: Unknown result type (might be due to invalid IL or missing references)
		//IL_0012: Unknown result type (might be due to invalid IL or missing references)
		NavMeshHit val = default(NavMeshHit);
		if (NavMesh.SamplePosition(at, ref val, 5f, -1))
		{
			return ((NavMeshHit)(ref val)).position;
		}
		return at;
	}

	public static void DropLoot(int[] ids, Vector3 at)
	{
		//IL_0007: Unknown result type (might be due to invalid IL or missing references)
		//IL_0018: Unknown result type (might be due to invalid IL or missing references)
		//IL_001d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0022: Unknown result type (might be due to invalid IL or missing references)
		//IL_0036: Unknown result type (might be due to invalid IL or missing references)
		//IL_0041: Unknown result type (might be due to invalid IL or missing references)
		//IL_007e: Unknown result type (might be due to invalid IL or missing references)
		//IL_008c: Unknown result type (might be due to invalid IL or missing references)
		//IL_009a: Unknown result type (might be due to invalid IL or missing references)
		for (int i = 0; i < ids.Length; i++)
		{
			Vector3 val = at + new Vector3(MimicLoot.OffsetX(i), 1f, 0f);
			int nextId = ItemManager.Instance.GetNextId();
			ItemManager.Instance.DropPowerupAtPosition(ids[i], val, nextId);
			ServerSend.DropPowerupAtPosition(ids[i], nextId, val);
			if (MimicPlugin.Cfg.VerboseLogging.Value)
			{
				MimicPlugin.Log.LogInfo((object)$"Mimic dropped powerup {ids[i]} as object {nextId} at ({val.x}, {val.y}, {val.z}).");
			}
		}
	}
}
internal sealed class MimicPending
{
	public int MobId { get; }

	public int[] PowerupIds { get; }

	public object Owner { get; }

	public float X { get; private set; }

	public float Y { get; private set; }

	public float Z { get; private set; }

	public MimicPending(int mobId, int[] powerupIds, object owner, float x, float y, float z)
	{
		MobId = mobId;
		PowerupIds = powerupIds;
		Owner = owner;
		X = x;
		Y = y;
		Z = z;
	}

	internal void MoveTo(float x, float y, float z)
	{
		X = x;
		Y = y;
		Z = z;
	}
}
internal sealed class MimicLedger
{
	private readonly Dictionary<int, MimicPending> _entries = new Dictionary<int, MimicPending>();

	public int Count => _entries.Count;

	public bool Add(int mobId, int[] powerupIds, object owner, float x, float y, float z)
	{
		if (_entries.ContainsKey(mobId) || powerupIds == null || powerupIds.Length == 0)
		{
			return false;
		}
		int[] array = new int[powerupIds.Length];
		Array.Copy(powerupIds, array, powerupIds.Length);
		_entries[mobId] = new MimicPending(mobId, array, owner, x, y, z);
		return true;
	}

	public List<int> SnapshotIds()
	{
		List<int> list = new List<int>(_entries.Keys);
		list.Sort();
		return list;
	}

	public bool TryGet(int mobId, out MimicPending pending)
	{
		return _entries.TryGetValue(mobId, out pending);
	}

	public void Move(int mobId, float x, float y, float z)
	{
		if (_entries.TryGetValue(mobId, out var value))
		{
			value.MoveTo(x, y, z);
		}
	}

	public bool Remove(int mobId)
	{
		return _entries.Remove(mobId);
	}

	public void Clear()
	{
		_entries.Clear();
	}
}
internal static class MimicLoot
{
	public const float DropHeight = 1f;

	public const float Spread = 0.75f;

	public static int[] Ids(int chestPowerupId, bool extraWhite, int extraWhiteId)
	{
		if (extraWhite && extraWhiteId >= 0)
		{
			return new int[2] { chestPowerupId, extraWhiteId };
		}
		return new int[1] { chestPowerupId };
	}

	public static float OffsetX(int index)
	{
		if (index <= 0)
		{
			return 0f;
		}
		if (index % 2 == 1)
		{
			return 0.75f * (float)((index + 1) / 2);
		}
		return -0.75f * (float)(index / 2);
	}
}
internal static class MimicMobPick
{
	public static string Normalize(string s)
	{
		if (s == null)
		{
			return "";
		}
		return s.ToLowerInvariant().Replace(" ", "").Replace("_", "")
			.Replace("-", "");
	}

	public static string PreferredName(int day, int lateFromDay, string earlyName, string lateName)
	{
		if (day < lateFromDay)
		{
			return earlyName;
		}
		return lateName;
	}

	public static int IndexOf(IReadOnlyList<string> names, string wanted)
	{
		string text = Normalize(wanted);
		if (names == null || text.Length == 0)
		{
			return -1;
		}
		for (int i = 0; i < names.Count; i++)
		{
			if (names[i] != null && Normalize(names[i]) == text)
			{
				return i;
			}
		}
		return -1;
	}
}
[BepInPlugin("evan.muck.mimic", "Mimic", "0.1.2")]
public class MimicPlugin : BaseUnityPlugin
{
	public const string Guid = "evan.muck.mimic";

	public const string Name = "Mimic";

	public const string Version = "0.1.2";

	internal static ManualLogSource Log;

	internal static MimicConfig Cfg;

	internal static readonly MimicClaims Claims = new MimicClaims();

	internal static readonly MimicLedger Ledger = new MimicLedger();

	private void Awake()
	{
		//IL_0020: Unknown result type (might be due to invalid IL or missing references)
		//IL_0026: Expected O, but got Unknown
		Log = ((BaseUnityPlugin)this).Logger;
		Cfg = new MimicConfig(((BaseUnityPlugin)this).Config);
		Harmony harmony = new Harmony("evan.muck.mimic");
		if (!TryPatch(harmony, typeof(LootContainerServerExecutePatch)))
		{
			Log.LogError((object)"Mimic disabled: the chest patch failed.");
			return;
		}
		if (!TryPatch(harmony, typeof(MobManagerRemoveMobPatch)))
		{
			Log.LogWarning((object)"Mimic will drop chest loot from the 0.25 s poll. The RemoveMob patch failed.");
		}
		MimicRunner.Ensure();
		Log.LogInfo((object)"Mimic 0.1.2 loaded. Only the host needs this mod; only the host's [Mimic] settings are used.");
	}

	private static bool TryPatch(Harmony harmony, Type patchClass)
	{
		try
		{
			harmony.CreateClassProcessor(patchClass).Patch();
			return true;
		}
		catch (Exception arg)
		{
			Log.LogError((object)string.Format("{0} failed to patch {1}: {2}", "Mimic", patchClass.Name, arg));
			return false;
		}
	}
}
internal static class MimicRoll
{
	public static bool IsEligible(int price, bool testChest)
	{
		if (price > 0)
		{
			return !testChest;
		}
		return false;
	}

	public static bool Rolls(float roll, float chance)
	{
		if (!(chance > 0f))
		{
			return false;
		}
		if (chance >= 1f)
		{
			return true;
		}
		return roll < chance;
	}
}
internal sealed class MimicRunner : MonoBehaviour
{
	private static MimicRunner _instance;

	private float _timer;

	private bool _loggedUpdateFailure;

	public static void Ensure()
	{
		//IL_0013: Unknown result type (might be due to invalid IL or missing references)
		//IL_0018: Unknown result type (might be due to invalid IL or missing references)
		//IL_0020: Unknown result type (might be due to invalid IL or missing references)
		//IL_0026: Expected O, but got Unknown
		if (!((Object)(object)_instance != (Object)null))
		{
			GameObject val = new GameObject("MuckMimic")
			{
				hideFlags = (HideFlags)61
			};
			Object.DontDestroyOnLoad((Object)val);
			_instance = val.AddComponent<MimicRunner>();
		}
	}

	private void OnEnable()
	{
		SceneManager.activeSceneChanged += OnSceneChanged;
	}

	private void OnDisable()
	{
		SceneManager.activeSceneChanged -= OnSceneChanged;
	}

	private static void OnSceneChanged(Scene previous, Scene next)
	{
		MimicPlugin.Ledger.Clear();
	}

	private void Update()
	{
		try
		{
			_timer += Time.unscaledDeltaTime;
			if (!(_timer < 0.25f))
			{
				_timer = 0f;
				if (LocalClient.serverOwner && MimicPlugin.Ledger.Count != 0)
				{
					Poll();
				}
			}
		}
		catch (Exception arg)
		{
			_timer = 0f;
			if (!_loggedUpdateFailure)
			{
				_loggedUpdateFailure = true;
				MimicPlugin.Log.LogError((object)$"Mimic watch failed: {arg}");
			}
		}
	}

	private static void Poll()
	{
		//IL_009f: Unknown result type (might be due to invalid IL or missing references)
		//IL_00a4: Unknown result type (might be due to invalid IL or missing references)
		//IL_00ac: Unknown result type (might be due to invalid IL or missing references)
		//IL_00b3: Unknown result type (might be due to invalid IL or missing references)
		//IL_00ba: 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)
		MobManager instance = MobManager.Instance;
		foreach (int item in MimicPlugin.Ledger.SnapshotIds())
		{
			try
			{
				if (!MimicPlugin.Ledger.TryGet(item, out var pending))
				{
					continue;
				}
				bool num = (Object)(object)instance != (Object)null && pending.Owner == instance;
				Mob value = null;
				bool mobPresent = num && instance.mobs != null && instance.mobs.TryGetValue(item, out value) && (Object)(object)value != (Object)null;
				switch (MimicWatch.Classify(num, mobPresent))
				{
				case MimicState.Alive:
				{
					Vector3 position = ((Component)value).transform.position;
					MimicPlugin.Ledger.Move(item, position.x, position.y, position.z);
					break;
				}
				case MimicState.Stale:
					MimicPlugin.Ledger.Remove(item);
					if (MimicPlugin.Cfg.VerboseLogging.Value)
					{
						MimicPlugin.Log.LogInfo((object)$"Mimic forgot mob {item}. Its mob manager is gone, so no loot drops.");
					}
					break;
				case MimicState.Dead:
					MimicPlugin.Ledger.Remove(item);
					if (MimicPlugin.Cfg.VerboseLogging.Value)
					{
						MimicPlugin.Log.LogInfo((object)$"Mimic mob {item} died. Dropping loot at ({pending.X}, {pending.Y}, {pending.Z}).");
					}
					MimicHost.DropLoot(pending.PowerupIds, new Vector3(pending.X, pending.Y, pending.Z));
					break;
				default:
					throw new InvalidOperationException("Unknown mimic watch state.");
				}
			}
			catch (Exception arg)
			{
				MimicPlugin.Log.LogError((object)$"Mimic watch failed for mob {item}: {arg}");
			}
		}
	}
}
internal static class MimicText
{
	public const int ChatMaxLength = 120;

	public const string Spawned = "<color=#e8b04a>The chest was a Mimic! Kill it to claim the loot.</color>";

	public const string Dropped = "<color=#e8b04a>The Mimic dropped its loot.</color>";
}
internal sealed class MimicTuning
{
	public float Chance { get; }

	public float MobMultiplier { get; }

	public int LateMobFromDay { get; }

	public string EarlyMobName { get; }

	public string LateMobName { get; }

	public bool ExtraWhite { get; }

	public MimicTuning(float chance, float mobMultiplier, int lateMobFromDay, string earlyMobName, string lateMobName, bool extraWhite)
	{
		if (float.IsNaN(chance))
		{
			Chance = 0f;
		}
		else if (chance < 0f)
		{
			Chance = 0f;
		}
		else if (chance > 1f)
		{
			Chance = 1f;
		}
		else
		{
			Chance = chance;
		}
		if (float.IsNaN(mobMultiplier))
		{
			MobMultiplier = 1f;
		}
		else if (mobMultiplier < 1f)
		{
			MobMultiplier = 1f;
		}
		else if (mobMultiplier > 10f)
		{
			MobMultiplier = 10f;
		}
		else
		{
			MobMultiplier = mobMultiplier;
		}
		LateMobFromDay = ((lateMobFromDay >= 0) ? lateMobFromDay : 0);
		EarlyMobName = ((earlyMobName == null) ? "" : earlyMobName.Trim());
		LateMobName = ((lateMobName == null) ? "" : lateMobName.Trim());
		ExtraWhite = extraWhite;
	}
}
internal enum MimicState
{
	Alive,
	Dead,
	Stale
}
internal static class MimicWatch
{
	public static MimicState Classify(bool ownerMatches, bool mobPresent)
	{
		if (ownerMatches)
		{
			if (!mobPresent)
			{
				return MimicState.Dead;
			}
			return MimicState.Alive;
		}
		return MimicState.Stale;
	}
}
[HarmonyPatch(typeof(MobManager), "RemoveMob", new Type[] { typeof(int) })]
internal static class MobManagerRemoveMobPatch
{
	private static void Prefix(MobManager __instance, int __0)
	{
		try
		{
			MimicHost.PayOnRemove(__instance, __0);
		}
		catch (Exception arg)
		{
			MimicPlugin.Log.LogError((object)$"Mimic death drop failed for mob {__0}: {arg}");
		}
	}
}
public static class MyPluginInfo
{
	public const string PLUGIN_GUID = "MuckMimic";

	public const string PLUGIN_NAME = "Mimic";

	public const string PLUGIN_VERSION = "0.1.2";
}