Decompiled source of RoR2Enemies v0.3.0

plugins/MuckRor2Enemies.dll

Decompiled 20 hours ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using UnityEngine;
using UnityEngine.AI;
using UnityEngine.Rendering;
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("MuckRor2Enemies")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyDescription("Risk of Rain 2 enemies as variants of vanilla Muck mobs. Host-authoritative; vanilla clients can join.")]
[assembly: AssemblyFileVersion("0.3.0.0")]
[assembly: AssemblyInformationalVersion("0.3.0+879fc7ac66fec639f2f84b4b7a65d5c61c9ed734")]
[assembly: AssemblyProduct("RoR2 Enemies")]
[assembly: AssemblyTitle("MuckRor2Enemies")]
[assembly: InternalsVisibleTo("MuckRor2Enemies.Tests")]
[assembly: AssemblyVersion("0.3.0.0")]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class IsReadOnlyAttribute : Attribute
	{
	}
}
namespace MuckRor2Enemies
{
	internal static class ConfigFingerprint
	{
		public static ulong Of(RulesSnapshot rules)
		{
			ulong hash = 0uL;
			foreach (KindSpec item in KindRegistry.All)
			{
				KindRules value = default(KindRules);
				if (rules != null && rules.Kinds != null)
				{
					rules.Kinds.TryGetValue(item.Kind, out value);
				}
				hash = Mix(hash, (ulong)(value.Enabled ? 1 : 0));
				hash = Mix(hash, (ulong)value.FirstDay);
				hash = Mix(hash, Bits(value.Chance));
				hash = Mix(hash, (ulong)value.MaxAlive);
				hash = Mix(hash, (ulong)value.Sources);
			}
			float value2 = rules?.TitanShrineChance ?? 0f;
			return Mix(hash, Bits(value2));
		}

		private static ulong Mix(ulong hash, ulong value)
		{
			return Hash01.SplitMix64(hash ^ value);
		}

		private static ulong Bits(float value)
		{
			return BitConverter.ToUInt32(BitConverter.GetBytes(value), 0);
		}
	}
	internal static class DamageTuning
	{
		public static int ProjectileDamage(int attackDamage, float multiplier)
		{
			return (int)((float)attackDamage * multiplier);
		}

		public static int FxDamage(int baseDamage, float multiplier)
		{
			return (int)((float)baseDamage * multiplier);
		}

		public static int HostScaled(int damage, float mobDamageMultiplier)
		{
			return (int)((float)damage * mobDamageMultiplier);
		}

		public static float MeleeRefineMultiplier(float mobMultiplier, float damageScale, float meleeFactor)
		{
			return mobMultiplier / damageScale * meleeFactor;
		}

		public static int MaxHp(int prefabHp, float hpMult, float multiplier, float bossMultiplier)
		{
			return (int)((float)prefabHp * hpMult * multiplier * bossMultiplier);
		}
	}
	internal enum Difficulty
	{
		Easy,
		Normal,
		Gamer
	}
	internal static class DifficultyCurve
	{
		public static float MobHpMultiplier(Difficulty d, int day)
		{
			Coeff(d, hp: true, out var a, out var b, out var p);
			return Curve(a, b, p, day);
		}

		public static float MobDamageMultiplier(Difficulty d, int day)
		{
			Coeff(d, hp: false, out var a, out var b, out var p);
			return Curve(a, b, p, day);
		}

		public static float BossNightMultiplier(int playersAlive)
		{
			return 0.85f + 0.15f * (float)playersAlive;
		}

		public static float ShrineBossMultiplier(int playersAlive)
		{
			return 0.9f + 0.1f * (float)playersAlive;
		}

		public static int BossDay(Difficulty d)
		{
			return d switch
			{
				Difficulty.Easy => 6, 
				Difficulty.Normal => 4, 
				Difficulty.Gamer => 3, 
				_ => 5, 
			};
		}

		public static float BuffChance(int day)
		{
			float num = 0.01f * (float)day;
			if (num < 0.05f)
			{
				num = 0.05f;
			}
			if (num > 0.3f)
			{
				num = 0.3f;
			}
			return 0.01f + num;
		}

		private static float Curve(float a, float b, float p, int day)
		{
			float num = b * (float)day;
			return a + (float)Math.Pow(num, p);
		}

		private static void Coeff(Difficulty d, bool hp, out float a, out float b, out float p)
		{
			switch (d)
			{
			case Difficulty.Easy:
				if (hp)
				{
					a = 0.9f;
					b = 0.2f;
					p = 1.32f;
				}
				else
				{
					a = 0.35f;
					b = 0.2f;
					p = 1.33f;
				}
				break;
			case Difficulty.Gamer:
				if (hp)
				{
					a = 1.3f;
					b = 0.3f;
					p = 1.8f;
				}
				else
				{
					a = 1.75f;
					b = 0.25f;
					p = 2.3f;
				}
				break;
			default:
				if (hp)
				{
					a = 1.05f;
					b = 0.24f;
					p = 1.5f;
				}
				else
				{
					a = 0.9f;
					b = 0.26f;
					p = 1.55f;
				}
				break;
			}
		}
	}
	internal static class EnemyArt
	{
		public const string JellyBell = "Hip__bell";

		public const string TitanEye = "Head__eye";

		private static readonly Dictionary<string, MaterialSpec> Empty = new Dictionary<string, MaterialSpec>();

		private static readonly Dictionary<string, MaterialSpec> Lemurian = Palette(Entry("lem_scale", 0.45f, 0.33f, 0.62f, 0f), Entry("lem_belly", 0.88f, 0.8f, 0.62f, 0f), Entry("lem_dark", 0.14f, 0.11f, 0.16f, 0f), Entry("lem_mouth", 0.7f, 0.16f, 0.16f, 0f), Entry("lem_eye", 1f, 0.55f, 0.1f, 1.5f), Entry("lem_scale_dark", 0.33f, 0.23f, 0.47f, 0f));

		private static readonly Dictionary<string, MaterialSpec> Jellyfish = Palette(Entry("jel_bell", 0.55f, 0.86f, 0.96f, 0.5f), Entry("jel_rim", 0.32f, 0.62f, 0.9f, 0.3f), Entry("jel_core", 0.95f, 0.98f, 1f, 2f), Entry("jel_tentacle", 0.45f, 0.75f, 0.95f, 0.3f), Entry("jel_frill", 0.72f, 0.93f, 1f, 0.9f));

		private static readonly Dictionary<string, MaterialSpec> Wisp = Palette(Entry("wisp_mask", 0.92f, 0.89f, 0.8f, 0f), Entry("wisp_dark", 0.2f, 0.17f, 0.15f, 0f), Entry("wisp_flame", 1f, 0.52f, 0.12f, 2f), Entry("wisp_core", 1f, 0.86f, 0.4f, 3f), Entry("wisp_ember", 0.98f, 0.36f, 0.07f, 2f));

		private static readonly Dictionary<string, MaterialSpec> Titan = Palette(Entry("tit_stone", 0.46f, 0.45f, 0.42f, 0f), Entry("tit_dark", 0.27f, 0.27f, 0.28f, 0f), Entry("tit_moss", 0.34f, 0.5f, 0.22f, 0f), Entry("tit_eye", 1f, 0.18f, 0.1f, 3f), Entry("tit_rune", 0.95f, 0.42f, 0.18f, 1.2f), Entry("tit_stone_hi", 0.56f, 0.55f, 0.51f, 0f));

		public static string Asset(EnemyKind kind)
		{
			return kind switch
			{
				EnemyKind.Lemurian => "lemurian", 
				EnemyKind.Jellyfish => "jellyfish", 
				EnemyKind.LesserWisp => "lesser_wisp", 
				EnemyKind.StoneTitan => "stone_titan", 
				_ => null, 
			};
		}

		public static string ObjResource(string asset)
		{
			return "MuckRor2Enemies.art." + asset + ".obj";
		}

		public static string PartsResource(string asset)
		{
			return "MuckRor2Enemies.art." + asset + ".parts.json";
		}

		public static EnemyModel Load(EnemyKind kind, Func<string, string> read, out string error)
		{
			error = null;
			string text = Asset(kind);
			if (text == null)
			{
				return null;
			}
			try
			{
				EnemyModel enemyModel = EnemyModel.Parse(read(ObjResource(text)), read(PartsResource(text)));
				Validate(kind, enemyModel);
				return enemyModel;
			}
			catch (Exception ex)
			{
				error = ex.Message;
				return null;
			}
		}

		private static void Validate(EnemyKind kind, EnemyModel model)
		{
			if (!KindRegistry.TryGet((int)kind, out var spec))
			{
				throw new FormatException($"{model.Name}: {kind} has no kind row.");
			}
			IReadOnlyList<string> names = MobRigs.Bones(spec.Rig);
			IReadOnlyDictionary<string, MaterialSpec> readOnlyDictionary = Materials(kind);
			foreach (EnemyPart part in model.Parts)
			{
				if (!Contains(names, part.Bone))
				{
					throw new FormatException($"{model.Name}: part {part.Object} rides {part.Bone}, which the {spec.Rig} rig lacks.");
				}
				if (!readOnlyDictionary.ContainsKey(part.Material))
				{
					throw new FormatException(model.Name + ": part " + part.Object + " uses " + part.Material + ", which the palette lacks.");
				}
			}
			int num = TriangleBudget(kind);
			if (model.TriangleCount > num)
			{
				throw new FormatException($"{model.Name}: {model.TriangleCount} triangles, over the budget of {num}.");
			}
		}

		private static bool Contains(IReadOnlyList<string> names, string name)
		{
			for (int i = 0; i < names.Count; i++)
			{
				if (names[i] == name)
				{
					return true;
				}
			}
			return false;
		}

		public static IReadOnlyDictionary<string, MaterialSpec> Materials(EnemyKind kind)
		{
			return kind switch
			{
				EnemyKind.Lemurian => Lemurian, 
				EnemyKind.Jellyfish => Jellyfish, 
				EnemyKind.LesserWisp => Wisp, 
				EnemyKind.StoneTitan => Titan, 
				_ => Empty, 
			};
		}

		public static IReadOnlyList<string> HiddenRenderers(EnemyKind kind)
		{
			if (KindRegistry.TryGet((int)kind, out var spec) && spec.HiddenRenderers != null)
			{
				return spec.HiddenRenderers;
			}
			return Array.Empty<string>();
		}

		public static int TriangleBudget(EnemyKind kind)
		{
			return kind switch
			{
				EnemyKind.Lemurian => 1400, 
				EnemyKind.Jellyfish => 1100, 
				EnemyKind.LesserWisp => 1000, 
				EnemyKind.StoneTitan => 2000, 
				_ => 0, 
			};
		}

		private static Dictionary<string, MaterialSpec> Palette(params KeyValuePair<string, MaterialSpec>[] entries)
		{
			Dictionary<string, MaterialSpec> dictionary = new Dictionary<string, MaterialSpec>();
			for (int i = 0; i < entries.Length; i++)
			{
				dictionary[entries[i].Key] = entries[i].Value;
			}
			return dictionary;
		}

		private static KeyValuePair<string, MaterialSpec> Entry(string name, float r, float g, float b, float emission)
		{
			return new KeyValuePair<string, MaterialSpec>(name, new MaterialSpec
			{
				R = r,
				G = g,
				B = b,
				Emission = emission
			});
		}
	}
	internal sealed class EnemyPart
	{
		public readonly string Object;

		public readonly string Bone;

		public readonly string Material;

		public readonly Vec3 Pivot;

		public readonly MeshData Mesh;

		public int TriangleCount => Mesh.Positions.Length / 3;

		public EnemyPart(string obj, string bone, string material, Vec3 pivot, MeshData mesh)
		{
			Object = obj;
			Bone = bone;
			Material = material;
			Pivot = pivot;
			Mesh = mesh;
		}
	}
	internal sealed class EnemyModel
	{
		private sealed class ObjObject
		{
			public readonly List<Vec3> Positions = new List<Vec3>();

			public readonly List<Vec3> Normals = new List<Vec3>();

			public readonly HashSet<string> Materials = new HashSet<string>();
		}

		public readonly string Name;

		public readonly string Rig;

		public readonly float RootScale;

		public readonly IReadOnlyList<EnemyPart> Parts;

		public readonly IReadOnlyDictionary<string, MaterialSpec> Materials;

		public int TriangleCount
		{
			get
			{
				int num = 0;
				for (int i = 0; i < Parts.Count; i++)
				{
					num += Parts[i].TriangleCount;
				}
				return num;
			}
		}

		private EnemyModel(string name, string rig, float rootScale, IReadOnlyList<EnemyPart> parts, IReadOnlyDictionary<string, MaterialSpec> materials)
		{
			Name = name;
			Rig = rig;
			RootScale = rootScale;
			Parts = parts;
			Materials = materials;
		}

		public EnemyPart Find(string objectName)
		{
			for (int i = 0; i < Parts.Count; i++)
			{
				if (Parts[i].Object == objectName)
				{
					return Parts[i];
				}
			}
			return null;
		}

		public static EnemyModel Parse(string objText, string partsJson)
		{
			if (!(JsonReader.Parse(partsJson) is Dictionary<string, object> obj))
			{
				throw new FormatException("parts.json: top level is not an object.");
			}
			string text = Str(obj, "name");
			Dictionary<string, MaterialSpec> materials = ReadMaterials(Obj(obj, "materials"));
			Dictionary<string, ObjObject> dictionary = ReadObj(objText, text);
			List<EnemyPart> list = new List<EnemyPart>();
			HashSet<string> hashSet = new HashSet<string>();
			foreach (object item in Arr(obj, "parts"))
			{
				if (!(item is Dictionary<string, object> dictionary2))
				{
					throw new FormatException(text + ": a part is not an object.");
				}
				string text2 = Str(dictionary2, "object");
				if (!dictionary.TryGetValue(text2, out var value))
				{
					throw new FormatException(text + ": part " + text2 + " is missing from the OBJ.");
				}
				if (!hashSet.Add(text2))
				{
					throw new FormatException(text + ": part " + text2 + " is listed twice.");
				}
				list.Add(BuildPart(text, text2, dictionary2, value, materials));
			}
			foreach (string key in dictionary.Keys)
			{
				if (!hashSet.Contains(key))
				{
					throw new FormatException(text + ": OBJ object " + key + " is not in parts.json.");
				}
			}
			EnemyModel enemyModel = new EnemyModel(text, Str(obj, "rig"), (float)Num(obj, "root_scale"), list, materials);
			int num = (int)Num(obj, "tris");
			if (enemyModel.TriangleCount != num)
			{
				throw new FormatException($"{text}: OBJ has {enemyModel.TriangleCount} triangles, parts.json says {num}.");
			}
			return enemyModel;
		}

		private static EnemyPart BuildPart(string name, string obj, Dictionary<string, object> entry, ObjObject mesh, Dictionary<string, MaterialSpec> materials)
		{
			List<object> list = Arr(entry, "materials");
			if (list.Count != 1 || !(list[0] is string text))
			{
				throw new FormatException(name + ": part " + obj + " must list exactly one material.");
			}
			if (!materials.ContainsKey(text))
			{
				throw new FormatException(name + ": part " + obj + " uses undefined material '" + text + "'.");
			}
			if (mesh.Materials.Count != 1 || !mesh.Materials.Contains(text))
			{
				throw new FormatException(name + ": part " + obj + " faces use " + string.Join(", ", mesh.Materials) + ", parts.json says " + text + ".");
			}
			int num = mesh.Positions.Count / 3;
			int num2 = (int)Num(entry, "tris");
			if (num != num2)
			{
				throw new FormatException($"{name}: part {obj} has {num} triangles, parts.json says {num2}.");
			}
			Vec3 pivot = ReadVec3(entry, "pivot_unity");
			Vec3[] positions = mesh.Positions.ToArray();
			return new EnemyPart(obj, Str(entry, "bone"), text, pivot, new MeshData(BoundsCentre(positions), positions, mesh.Normals.ToArray()));
		}

		private static Vec3 BoundsCentre(Vec3[] positions)
		{
			if (positions.Length == 0)
			{
				return new Vec3(0f, 0f, 0f);
			}
			Vec3 vec = positions[0];
			Vec3 vec2 = positions[0];
			for (int i = 0; i < positions.Length; i++)
			{
				Vec3 vec3 = positions[i];
				vec = new Vec3(Math.Min(vec.X, vec3.X), Math.Min(vec.Y, vec3.Y), Math.Min(vec.Z, vec3.Z));
				vec2 = new Vec3(Math.Max(vec2.X, vec3.X), Math.Max(vec2.Y, vec3.Y), Math.Max(vec2.Z, vec3.Z));
			}
			return (vec + vec2) * 0.5f;
		}

		private static Dictionary<string, ObjObject> ReadObj(string text, string name)
		{
			if (text == null)
			{
				throw new FormatException(name + ": OBJ text is null.");
			}
			List<Vec3> list = new List<Vec3>();
			List<Vec3> list2 = new List<Vec3>();
			Dictionary<string, ObjObject> dictionary = new Dictionary<string, ObjObject>();
			ObjObject objObject = null;
			string text2 = null;
			string[] array = text.Split(new char[1] { '\n' });
			for (int i = 0; i < array.Length; i++)
			{
				string[] array2 = array[i].Trim().Split((char[]?)null, StringSplitOptions.RemoveEmptyEntries);
				if (array2.Length == 0)
				{
					continue;
				}
				switch (array2[0])
				{
				case "v":
				{
					Vec3 vec2 = ReadVec(array2, name, i);
					list.Add(new Vec3(0f - vec2.X, vec2.Y, vec2.Z));
					break;
				}
				case "vn":
				{
					Vec3 vec = ReadVec(array2, name, i);
					list2.Add(new Vec3(0f - vec.X, vec.Y, vec.Z));
					break;
				}
				case "o":
				{
					string text3 = string.Join(" ", array2, 1, array2.Length - 1);
					if (dictionary.ContainsKey(text3))
					{
						throw new FormatException(name + ": OBJ object " + text3 + " appears twice.");
					}
					objObject = new ObjObject();
					dictionary.Add(text3, objObject);
					text2 = null;
					break;
				}
				case "usemtl":
					text2 = ((array2.Length > 1) ? array2[1] : null);
					break;
				case "f":
				{
					if (objObject == null || text2 == null)
					{
						throw new FormatException($"{name}: OBJ line {i + 1} has a face outside an object or material.");
					}
					if (array2.Length != 4)
					{
						throw new FormatException($"{name}: OBJ line {i + 1} is not a triangle.");
					}
					objObject.Materials.Add(text2);
					int[] array3 = new int[3] { 1, 3, 2 };
					foreach (int num in array3)
					{
						string[] array4 = array2[num].Split(new char[1] { '/' });
						if (array4.Length < 3 || array4[2].Length == 0)
						{
							throw new FormatException($"{name}: OBJ line {i + 1} has a corner without a normal.");
						}
						objObject.Positions.Add(list[Index(array4[0], list.Count, name, i)]);
						objObject.Normals.Add(list2[Index(array4[2], list2.Count, name, i)]);
					}
					break;
				}
				}
			}
			return dictionary;
		}

		private static int Index(string raw, int count, string name, int line)
		{
			if (!int.TryParse(raw, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result) || result == 0)
			{
				throw new FormatException($"{name}: OBJ line {line + 1} has a bad index '{raw}'.");
			}
			int num = ((result > 0) ? (result - 1) : (count + result));
			if (num < 0 || num >= count)
			{
				throw new FormatException($"{name}: OBJ line {line + 1} index {raw} is out of range.");
			}
			return num;
		}

		private static Vec3 ReadVec(string[] tokens, string name, int line)
		{
			if (tokens.Length < 4)
			{
				throw new FormatException($"{name}: OBJ line {line + 1} needs three numbers.");
			}
			return new Vec3(Float(tokens[1], name, line), Float(tokens[2], name, line), Float(tokens[3], name, line));
		}

		private static float Float(string raw, string name, int line)
		{
			if (!float.TryParse(raw, NumberStyles.Float, CultureInfo.InvariantCulture, out var result))
			{
				throw new FormatException($"{name}: OBJ line {line + 1} has a bad number '{raw}'.");
			}
			return result;
		}

		private static Dictionary<string, MaterialSpec> ReadMaterials(Dictionary<string, object> raw)
		{
			Dictionary<string, MaterialSpec> dictionary = new Dictionary<string, MaterialSpec>();
			foreach (KeyValuePair<string, object> item in raw)
			{
				if (!(item.Value is Dictionary<string, object> obj))
				{
					throw new FormatException("parts.json: material " + item.Key + " is not an object.");
				}
				Vec3 vec = ReadVec3(obj, "rgb");
				dictionary.Add(item.Key, new MaterialSpec
				{
					R = vec.X,
					G = vec.Y,
					B = vec.Z,
					Emission = (float)Num(obj, "emission")
				});
			}
			return dictionary;
		}

		private static Vec3 ReadVec3(Dictionary<string, object> obj, string key)
		{
			List<object> list = Arr(obj, key);
			if (list.Count != 3 || !(list[0] is double num) || !(list[1] is double num2) || !(list[2] is double num3))
			{
				throw new FormatException("parts.json: '" + key + "' is not three numbers.");
			}
			return new Vec3((float)num, (float)num2, (float)num3);
		}

		private static object Field(Dictionary<string, object> obj, string key)
		{
			if (!obj.TryGetValue(key, out var value))
			{
				throw new FormatException("parts.json: missing '" + key + "'.");
			}
			return value;
		}

		private static string Str(Dictionary<string, object> obj, string key)
		{
			return (Field(obj, key) as string) ?? throw new FormatException("parts.json: '" + key + "' is not a string.");
		}

		private static double Num(Dictionary<string, object> obj, string key)
		{
			object obj2 = Field(obj, key);
			if (obj2 is double)
			{
				return (double)obj2;
			}
			throw new FormatException("parts.json: '" + key + "' is not a number.");
		}

		private static List<object> Arr(Dictionary<string, object> obj, string key)
		{
			return (Field(obj, key) as List<object>) ?? throw new FormatException("parts.json: '" + key + "' is not an array.");
		}

		private static Dictionary<string, object> Obj(Dictionary<string, object> obj, string key)
		{
			return (Field(obj, key) as Dictionary<string, object>) ?? throw new FormatException("parts.json: '" + key + "' is not an object.");
		}
	}
	internal static class FxLifetime
	{
		public const float Seconds = 5f;

		private static readonly string[] LeakyVanilla = new string[2] { "GolemGroundAttack(Clone)", "TitanGroundAttack(Clone)" };

		public static bool ShouldExpire(string rootName, bool rootHasOwnLifetime, bool fromTaggedMob, bool cleanVanilla)
		{
			if (rootHasOwnLifetime)
			{
				return false;
			}
			if (fromTaggedMob)
			{
				return true;
			}
			if (cleanVanilla)
			{
				return IsLeakyVanilla(rootName);
			}
			return false;
		}

		public static bool IsLeakyVanilla(string rootName)
		{
			for (int i = 0; i < LeakyVanilla.Length; i++)
			{
				if (rootName == LeakyVanilla[i])
				{
					return true;
				}
			}
			return false;
		}
	}
	internal sealed class TaggedFxLedger
	{
		private readonly struct Entry
		{
			public readonly string CloneName;

			public readonly Vec3 At;

			public readonly float Time;

			public Entry(string cloneName, Vec3 at, float time)
			{
				CloneName = cloneName;
				At = at;
				Time = time;
			}
		}

		public const float ClaimSeconds = 1f;

		public const float ClaimDistance = 0.5f;

		private readonly List<Entry> pending = new List<Entry>();

		public int Count => pending.Count;

		public void Note(string cloneName, Vec3 at, float now)
		{
			Prune(now);
			pending.Add(new Entry(cloneName, at, now));
		}

		public bool TryClaim(string rootName, Vec3 at, float now)
		{
			Prune(now);
			for (int i = 0; i < pending.Count; i++)
			{
				Entry entry = pending[i];
				if (!(entry.CloneName != rootName) && !(Vec3.Length(entry.At - at) > 0.5f))
				{
					pending.RemoveAt(i);
					return true;
				}
			}
			return false;
		}

		public void Clear()
		{
			pending.Clear();
		}

		private void Prune(float now)
		{
			pending.RemoveAll((Entry entry) => now - entry.Time > 1f);
		}
	}
	internal static class Hash01
	{
		public static ulong SplitMix64(ulong x)
		{
			x += 11400714819323198485uL;
			x = (x ^ (x >> 30)) * 13787848793156543929uL;
			x = (x ^ (x >> 27)) * 10723151780598845931uL;
			return x ^ (x >> 31);
		}

		public static double Of(int a, int b)
		{
			return ToUnit(SplitMix64(((ulong)(uint)a << 32) | (uint)b));
		}

		private static double ToUnit(ulong h)
		{
			return (double)(h >> 11) * 1.1102230246251565E-16;
		}
	}
	internal struct HoverSample
	{
		public EnemyKind Kind;

		public int Id;

		public float Age;

		public float BaseOffset;

		public float RootY;

		public float GroundY;

		public string LowerBone;

		public float LowerBoneY;

		public string UpperBone;

		public float UpperBoneY;

		public float PartsLowY;

		public float PartsHighY;

		public float ColliderLowY;

		public float ColliderHighY;
	}
	internal static class HoverLog
	{
		public const int Samples = 6;

		public const float IntervalSeconds = 1f;

		public const string UpperBone = "Head";

		public static bool Watches(EnemyKind kind)
		{
			if (kind != EnemyKind.Jellyfish)
			{
				return kind == EnemyKind.LesserWisp;
			}
			return true;
		}

		public static bool Due(float age, int logged)
		{
			if (logged < 6)
			{
				return age >= (float)logged * 1f;
			}
			return false;
		}

		public static bool Done(int logged)
		{
			return logged >= 6;
		}

		public static string LowerBone(RigId rig)
		{
			if (rig != RigId.LilDave)
			{
				return "Hips";
			}
			return "Hip";
		}

		public static float Clearance(float partsLowY, float groundY)
		{
			return partsLowY - groundY;
		}

		public static string Line(HoverSample s)
		{
			StringBuilder stringBuilder = new StringBuilder();
			stringBuilder.Append("RoR2 Enemies hover ").Append(s.Kind).Append(" #")
				.Append(s.Id.ToString(CultureInfo.InvariantCulture))
				.Append(" t=")
				.Append(F(s.Age))
				.Append("s baseOffset=")
				.Append(F(s.BaseOffset))
				.Append(" root.y=")
				.Append(F(s.RootY))
				.Append(" ground.y=")
				.Append(F(s.GroundY))
				.Append(' ')
				.Append(s.LowerBone)
				.Append(".y=")
				.Append(F(s.LowerBoneY))
				.Append(' ')
				.Append(s.UpperBone)
				.Append(".y=")
				.Append(F(s.UpperBoneY))
				.Append(" parts.y=")
				.Append(F(s.PartsLowY))
				.Append("..")
				.Append(F(s.PartsHighY))
				.Append(" collider.y=")
				.Append(F(s.ColliderLowY))
				.Append("..")
				.Append(F(s.ColliderHighY))
				.Append(" clearance=")
				.Append(F(Clearance(s.PartsLowY, s.GroundY)));
			return stringBuilder.ToString();
		}

		private static string F(float value)
		{
			if (!float.IsNaN(value))
			{
				return value.ToString("0.00", CultureInfo.InvariantCulture);
			}
			return "n/a";
		}
	}
	internal enum FuseState
	{
		Idle,
		Glowing,
		Detonated,
		Cancelled
	}
	internal enum FuseEvent
	{
		None,
		Detonate,
		HostDespawn
	}
	internal sealed class JellyFuse
	{
		public const float GlowSeconds = 1f;

		public const float HostDespawnDelay = 0.25f;

		public const int BuildTarget = -1;

		private float start;

		private bool hostDespawned;

		public FuseState State { get; private set; }

		public bool BlocksHostAttacks => State != FuseState.Idle;

		public bool BitingBuild { get; private set; }

		public void Attack(int targetPlayerId, float now)
		{
			BitingBuild = targetPlayerId == -1;
			if (!BitingBuild)
			{
				Start(now);
			}
		}

		public void Start(float now)
		{
			if (State == FuseState.Idle)
			{
				start = now;
				State = FuseState.Glowing;
			}
		}

		public FuseEvent Tick(float now, bool isHost)
		{
			if (State == FuseState.Glowing && now >= start + 1f)
			{
				State = FuseState.Detonated;
				return FuseEvent.Detonate;
			}
			if (State == FuseState.Detonated && isHost && !hostDespawned && now >= start + 1f + 0.25f)
			{
				hostDespawned = true;
				return FuseEvent.HostDespawn;
			}
			return FuseEvent.None;
		}

		public void Cancel()
		{
			if (State == FuseState.Glowing)
			{
				State = FuseState.Cancelled;
			}
		}

		public float GlowProgress(float now)
		{
			if (State == FuseState.Idle)
			{
				return 0f;
			}
			float num = (now - start) / 1f;
			if (num <= 0f)
			{
				return 0f;
			}
			if (num >= 1f)
			{
				return 1f;
			}
			return num;
		}
	}
	internal static class JellyHover
	{
		public const float VanillaBaseOffset = -0.15f;

		public const float DefaultOffset = 1f;

		public const float RetiredDefaultOffset = 1.5f;

		public const float MinOffset = 0f;

		public const float MaxOffset = 6f;

		public static float Clamp(float hoverOffset)
		{
			if (float.IsNaN(hoverOffset))
			{
				return 1f;
			}
			return Math.Max(0f, Math.Min(6f, hoverOffset));
		}

		public static float BaseOffset(float hoverOffset)
		{
			return -0.15f + Clamp(hoverOffset);
		}

		public static float? CarryRetired(string retiredValue)
		{
			if (retiredValue == null)
			{
				return null;
			}
			if (!float.TryParse(retiredValue.Trim(), NumberStyles.Float, CultureInfo.InvariantCulture, out var result))
			{
				return null;
			}
			if (float.IsNaN(result) || result == 1.5f)
			{
				return null;
			}
			return Clamp(result);
		}
	}
	internal sealed class JsonReader
	{
		private readonly string text;

		private int pos;

		private JsonReader(string text)
		{
			this.text = text;
		}

		public static object Parse(string text)
		{
			if (text == null)
			{
				throw new FormatException("JSON text is null.");
			}
			JsonReader jsonReader = new JsonReader(text);
			object result = jsonReader.ReadValue();
			jsonReader.SkipWhitespace();
			if (jsonReader.pos != text.Length)
			{
				throw jsonReader.Error("trailing characters");
			}
			return result;
		}

		private object ReadValue()
		{
			SkipWhitespace();
			if (pos >= text.Length)
			{
				throw Error("unexpected end");
			}
			char c = text[pos];
			switch (c)
			{
			case '{':
				return ReadObject();
			case '[':
				return ReadArray();
			case '"':
				return ReadString();
			case 't':
				Expect("true");
				return true;
			case 'f':
				Expect("false");
				return false;
			case 'n':
				Expect("null");
				return null;
			default:
				switch (c)
				{
				case '-':
				case '0':
				case '1':
				case '2':
				case '3':
				case '4':
				case '5':
				case '6':
				case '7':
				case '8':
				case '9':
					return ReadNumber();
				default:
					throw Error($"unexpected '{c}'");
				}
			}
		}

		private Dictionary<string, object> ReadObject()
		{
			Dictionary<string, object> dictionary = new Dictionary<string, object>();
			pos++;
			SkipWhitespace();
			if (Peek() == '}')
			{
				pos++;
				return dictionary;
			}
			while (true)
			{
				SkipWhitespace();
				if (Peek() != '"')
				{
					throw Error("expected a key");
				}
				string key = ReadString();
				SkipWhitespace();
				if (Peek() != ':')
				{
					break;
				}
				pos++;
				dictionary[key] = ReadValue();
				SkipWhitespace();
				char c = Peek();
				pos++;
				switch (c)
				{
				case ',':
					break;
				case '}':
					return dictionary;
				default:
					throw Error("expected ',' or '}'");
				}
			}
			throw Error("expected ':'");
		}

		private List<object> ReadArray()
		{
			List<object> list = new List<object>();
			pos++;
			SkipWhitespace();
			if (Peek() == ']')
			{
				pos++;
				return list;
			}
			while (true)
			{
				list.Add(ReadValue());
				SkipWhitespace();
				char c = Peek();
				pos++;
				switch (c)
				{
				case ',':
					break;
				case ']':
					return list;
				default:
					throw Error("expected ',' or ']'");
				}
			}
		}

		private string ReadString()
		{
			pos++;
			StringBuilder stringBuilder = new StringBuilder();
			while (pos < text.Length)
			{
				char c = text[pos++];
				switch (c)
				{
				case '"':
					return stringBuilder.ToString();
				default:
					stringBuilder.Append(c);
					break;
				case '\\':
				{
					if (pos >= text.Length)
					{
						throw Error("unterminated escape");
					}
					char c2 = text[pos++];
					switch (c2)
					{
					case '"':
						stringBuilder.Append('"');
						break;
					case '\\':
						stringBuilder.Append('\\');
						break;
					case '/':
						stringBuilder.Append('/');
						break;
					case 'b':
						stringBuilder.Append('\b');
						break;
					case 'f':
						stringBuilder.Append('\f');
						break;
					case 'n':
						stringBuilder.Append('\n');
						break;
					case 'r':
						stringBuilder.Append('\r');
						break;
					case 't':
						stringBuilder.Append('\t');
						break;
					case 'u':
						if (pos + 4 > text.Length)
						{
							throw Error("short \\u escape");
						}
						stringBuilder.Append((char)int.Parse(text.Substring(pos, 4), NumberStyles.HexNumber, CultureInfo.InvariantCulture));
						pos += 4;
						break;
					default:
						throw Error($"bad escape '\\{c2}'");
					}
					break;
				}
				}
			}
			throw Error("unterminated string");
		}

		private double ReadNumber()
		{
			int num = pos;
			while (pos < this.text.Length && "+-0123456789.eE".IndexOf(this.text[pos]) >= 0)
			{
				pos++;
			}
			string text = this.text.Substring(num, pos - num);
			if (!double.TryParse(text, NumberStyles.Float, CultureInfo.InvariantCulture, out var result))
			{
				throw Error("bad number '" + text + "'");
			}
			return result;
		}

		private void Expect(string word)
		{
			if (string.CompareOrdinal(text, pos, word, 0, word.Length) != 0)
			{
				throw Error("expected '" + word + "'");
			}
			pos += word.Length;
		}

		private char Peek()
		{
			if (pos >= text.Length)
			{
				return '\0';
			}
			return text[pos];
		}

		private void SkipWhitespace()
		{
			while (pos < text.Length && char.IsWhiteSpace(text[pos]))
			{
				pos++;
			}
		}

		private FormatException Error(string what)
		{
			return new FormatException($"JSON: {what} at offset {pos}.");
		}
	}
	internal enum EnemyKind
	{
		None = 0,
		Lemurian = 1,
		Jellyfish = 2,
		LesserWisp = 3,
		StoneTitan = 5,
		Beetle = 6,
		WanderingVagrant = 7
	}
	internal struct AiOverrides
	{
		public float Speed;

		public float RangedCooldown;

		public bool ApplyRangedCooldown;

		public float StartAttackDistance;

		public float MaxAttackDistance;

		public bool ApplyMaxAttackDistance;

		public float MinAttackAngle;

		public float KnockbackThreshold;
	}
	internal sealed class KindSpec
	{
		public EnemyKind Kind;

		public string DisplayName;

		public string SourceMobName;

		public string SpawnedMobName;

		public int PrefabHp;

		public float HpFactor;

		public float DamageScale;

		public float MeleeFactor;

		public bool Implemented;

		public RigId Rig;

		public AiOverrides Ai;

		public LootPlanRow[] Loot;

		public LootMode LootMode;

		public string RequiredItem;

		public string[] HiddenRenderers;
	}
	internal static class KindRegistry
	{
		private sealed class Restorer : IDisposable
		{
			private readonly HashSet<EnemyKind> previous;

			private bool done;

			public Restorer(HashSet<EnemyKind> previous)
			{
				this.previous = previous;
			}

			public void Dispose()
			{
				if (!done)
				{
					done = true;
					implementedOverride = previous;
				}
			}
		}

		public const int GlacialCode = 4;

		private static readonly KindSpec[] Rows = new KindSpec[6]
		{
			Spec(EnemyKind.Lemurian, "Lemurian", "Goblin", "Goblin", 60, 1.0833334f, 0.7f, 1.4f, RigId.Goblin, Ai(7f, applyRanged: true, 3f, 3.3f, applyMax: true, 30f, 40f, 0.25f), LootMode.Inherit, new LootPlanRow[1]
			{
				new LootPlanRow("Fireball", 1, 2, 0.15f)
			}, "Fireball", new string[2] { "Bone", "Trail" }, implemented: true),
			Spec(EnemyKind.Jellyfish, "Jellyfish", "lil Dave", "lil Dave", 40, 1.25f, 1f, 0f, RigId.LilDave, Ai(9f, applyRanged: false, 0f, 6f, applyMax: false, 0f, 180f, 0.2f), LootMode.None, Array.Empty<LootPlanRow>(), null, Array.Empty<string>(), implemented: true),
			Spec(EnemyKind.LesserWisp, "Lesser Wisp", "lil Dave", "Wyvern", 100, 0.4f, 0.25f, 0f, RigId.Wyvern, Ai(8f, applyRanged: false, 0f, 32f, applyMax: false, 0f, 90f, 0.1f), LootMode.Replace, new LootPlanRow[2]
			{
				new LootPlanRow("Coin", 4, 8, 1f),
				new LootPlanRow("Fireball", 1, 1, 0.1f)
			}, "Fireball", Array.Empty<string>(), implemented: true),
			Spec(EnemyKind.StoneTitan, "Stone Titan", "Big Chunk", "Big Chunk", 700, 1.1f, 1f, 0f, RigId.StoneGolem, Ai(11f, applyRanged: false, 0f, 32f, applyMax: true, 150f, 50f, 1f), LootMode.None, Array.Empty<LootPlanRow>(), "Spike Attack", Array.Empty<string>(), implemented: true),
			Spec(EnemyKind.Beetle, "Beetle", "Goblin", "Goblin", 0, 0f, 0f, 0f, RigId.Goblin, default(AiOverrides), LootMode.None, Array.Empty<LootPlanRow>(), null, Array.Empty<string>(), implemented: false),
			Spec(EnemyKind.WanderingVagrant, "Vagrant", "Wyvern", "Wyvern", 0, 0f, 0f, 0f, RigId.Wyvern, default(AiOverrides), LootMode.None, Array.Empty<LootPlanRow>(), null, Array.Empty<string>(), implemented: false)
		};

		private static HashSet<EnemyKind> implementedOverride;

		internal static bool LemurianAvailable = true;

		internal static bool JellyfishAvailable = true;

		internal static bool WispAvailable = true;

		internal static bool TitanAvailable = true;

		public static IReadOnlyList<KindSpec> All => Rows;

		public static IEnumerable<KindSpec> Implemented
		{
			get
			{
				KindSpec[] rows = Rows;
				foreach (KindSpec kindSpec in rows)
				{
					if (IsImplemented(kindSpec))
					{
						yield return kindSpec;
					}
				}
			}
		}

		public static bool TryGet(int code, out KindSpec spec)
		{
			for (int i = 0; i < Rows.Length; i++)
			{
				if (Rows[i].Kind == (EnemyKind)code)
				{
					spec = Rows[i];
					return true;
				}
			}
			spec = null;
			return false;
		}

		public static bool IsImplemented(KindSpec spec)
		{
			if (spec == null)
			{
				return false;
			}
			if (implementedOverride != null)
			{
				return implementedOverride.Contains(spec.Kind);
			}
			return spec.Implemented;
		}

		internal static IDisposable WithImplemented(params EnemyKind[] kinds)
		{
			HashSet<EnemyKind> previous = implementedOverride;
			implementedOverride = new HashSet<EnemyKind>(kinds);
			return new Restorer(previous);
		}

		private static KindSpec Spec(EnemyKind kind, string display, string source, string spawned, int prefabHp, float hpFactor, float damageScale, float meleeFactor, RigId rig, AiOverrides ai, LootMode lootMode, LootPlanRow[] loot, string requiredItem, string[] hidden, bool implemented)
		{
			return new KindSpec
			{
				Kind = kind,
				DisplayName = display,
				SourceMobName = source,
				SpawnedMobName = spawned,
				PrefabHp = prefabHp,
				HpFactor = hpFactor,
				DamageScale = damageScale,
				MeleeFactor = meleeFactor,
				Implemented = implemented,
				Rig = rig,
				Ai = ai,
				Loot = loot,
				LootMode = lootMode,
				RequiredItem = requiredItem,
				HiddenRenderers = hidden
			};
		}

		private static AiOverrides Ai(float speed, bool applyRanged, float ranged, float start, bool applyMax, float max, float angle, float knockback)
		{
			return new AiOverrides
			{
				Speed = speed,
				ApplyRangedCooldown = applyRanged,
				RangedCooldown = ranged,
				StartAttackDistance = start,
				ApplyMaxAttackDistance = applyMax,
				MaxAttackDistance = max,
				MinAttackAngle = angle,
				KnockbackThreshold = knockback
			};
		}
	}
	internal static class LaserAim
	{
		public const float ChargeTurnDegPerSec = 90f;

		public const float FireTurnDegPerSec = 20f;

		public const float TargetHeight = 1f;

		public static Vec3 Step(Vec3 current, Vec3 desired, float maxDegPerSec, float dt)
		{
			Vec3 vec = Vec3.Normalize(current);
			Vec3 vec2 = Vec3.Normalize(desired);
			if (Vec3.Length(vec) < 1E-08f)
			{
				if (!(Vec3.Length(vec2) < 1E-08f))
				{
					return vec2;
				}
				return new Vec3(0f, 0f, 1f);
			}
			if (Vec3.Length(vec2) < 1E-08f)
			{
				return vec;
			}
			float num = maxDegPerSec * dt;
			if (num < 0f)
			{
				num = 0f;
			}
			float num2 = (float)(Math.Acos(ClampUnit(Vec3.Dot(vec, vec2))) * (180.0 / Math.PI));
			if (num2 <= num || num2 < 0.0001f)
			{
				return vec2;
			}
			Vec3 vec3 = Vec3.Cross(vec, vec2);
			if (Vec3.Length(vec3) < 1E-06f)
			{
				vec3 = ((Math.Abs(vec.Y) < 0.9f) ? Vec3.Cross(vec, new Vec3(0f, 1f, 0f)) : Vec3.Cross(vec, new Vec3(1f, 0f, 0f)));
			}
			return Rotate(vec, vec3, num);
		}

		private static float ClampUnit(float dot)
		{
			if (dot > 1f)
			{
				return 1f;
			}
			if (dot < -1f)
			{
				return -1f;
			}
			return dot;
		}

		private static Vec3 Rotate(Vec3 v, Vec3 axis, float degrees)
		{
			Vec3 vec = Vec3.Normalize(axis);
			if (Vec3.Length(vec) < 1E-08f)
			{
				return v;
			}
			double num = (double)degrees * (Math.PI / 180.0);
			double num2 = Math.Cos(num);
			double num3 = Math.Sin(num);
			Vec3 vec2 = Vec3.Cross(vec, v);
			double num4 = Vec3.Dot(vec, v);
			double num5 = 1.0 - num2;
			return new Vec3((float)((double)v.X * num2 + (double)vec2.X * num3 + (double)vec.X * num4 * num5), (float)((double)v.Y * num2 + (double)vec2.Y * num3 + (double)vec.Y * num4 * num5), (float)((double)v.Z * num2 + (double)vec2.Z * num3 + (double)vec.Z * num4 * num5));
		}
	}
	internal enum LaserPhase
	{
		Charge,
		Fire,
		Done
	}
	internal static class LaserTimeline
	{
		public const float ChargeSeconds = 3.2f;

		public const float FireSeconds = 3f;

		public const int Ticks = 24;

		public const int TickDamage = 3;

		public static LaserPhase PhaseAt(float t)
		{
			if (t < 3.2f)
			{
				return LaserPhase.Charge;
			}
			if (t < 6.2f)
			{
				return LaserPhase.Fire;
			}
			return LaserPhase.Done;
		}

		public static int TicksBetween(float tPrev, float tNow)
		{
			int num = 0;
			for (int i = 0; i < 24; i++)
			{
				float num2 = 3.2f + 0.125f * (float)i;
				if (num2 > tPrev && num2 <= tNow)
				{
					num++;
				}
			}
			return num;
		}

		public static int TickDamageFor(float mobMultiplier)
		{
			int num = DamageTuning.FxDamage(3, mobMultiplier);
			if (num >= 1)
			{
				return num;
			}
			return 1;
		}
	}
	internal enum LootMode
	{
		None,
		Inherit,
		Replace
	}
	internal readonly struct LootPlanRow
	{
		public readonly string ItemName;

		public readonly int Min;

		public readonly int Max;

		public readonly float Chance;

		public LootPlanRow(string itemName, int min, int max, float chance)
		{
			ItemName = itemName;
			Min = min;
			Max = max;
			Chance = chance;
		}
	}
	internal static class LootPlan
	{
		public static bool IsValid(LootPlanRow row)
		{
			if (!string.IsNullOrEmpty(row.ItemName) && row.Min >= 0 && row.Min <= row.Max && row.Chance > 0f)
			{
				return row.Chance <= 1f;
			}
			return false;
		}

		public static IReadOnlyList<LootPlanRow> Merge(IReadOnlyList<LootPlanRow> baseRows, LootMode mode, IReadOnlyList<LootPlanRow> extra)
		{
			if (baseRows == null)
			{
				baseRows = Array.Empty<LootPlanRow>();
			}
			if (mode == LootMode.None)
			{
				return baseRows;
			}
			List<LootPlanRow> list = new List<LootPlanRow>();
			if (extra != null)
			{
				for (int i = 0; i < extra.Count; i++)
				{
					if (IsValid(extra[i]))
					{
						list.Add(extra[i]);
					}
				}
			}
			if (mode == LootMode.Replace)
			{
				return list;
			}
			List<LootPlanRow> list2 = new List<LootPlanRow>(baseRows.Count + list.Count);
			for (int j = 0; j < baseRows.Count; j++)
			{
				list2.Add(baseRows[j]);
			}
			list2.AddRange(list);
			return list2;
		}
	}
	internal sealed class MaterialSpec
	{
		public float R;

		public float G;

		public float B;

		public float Emission;

		public bool Glows => Emission > 0f;
	}
	internal sealed class MeshData
	{
		public readonly Vec3 Origin;

		public readonly Vec3[] Positions;

		public readonly Vec3[] Normals;

		public MeshData(Vec3 origin, Vec3[] positions, Vec3[] normals)
		{
			Origin = origin;
			Positions = positions;
			Normals = normals;
		}
	}
	internal enum RigId
	{
		Goblin,
		LilDave,
		Wyvern,
		StoneGolem
	}
	internal static class MobRigs
	{
		private static readonly string[] GoblinBones = new string[18]
		{
			"Hips", "Torso", "Neck", "Head", "Shoulder.L", "Arm.L", "Hand.L", "Fingers.L", "Shoulder.R", "Arm.R",
			"Hand.R", "Fingers.R", "Foot.L", "Leg.L", "Toe.L", "Foot.R", "Leg.R", "Toe.R"
		};

		private static readonly string[] LilDaveBones = new string[13]
		{
			"Hip", "Foot.L", "Knee.L", "Toe.L", "Foot.R", "Knee.R", "Toe.R", "Neck", "Head", "Peck_bot",
			"Peck_top", "Tail", "Tail1"
		};

		private static readonly string[] WyvernBones = new string[22]
		{
			"Hips", "Foot_L", "Knee.L", "Toes.L", "Foot_R", "Knee.R", "Toes.R", "Tail", "Tail1", "Tail2",
			"Tail3", "Torso", "Neck", "Head", "JAwLower", "JawUpper", "Shoulder.L", "Wing1.L", "Wing2.L", "Shoulder.R",
			"Wing1.R", "Wing2.R"
		};

		private static readonly string[] StoneBones = new string[13]
		{
			"Hips", "Leg.L", "Knee.L", "Toe.L", "Leg.R", "Knee.R", "Toe.R", "Torso", "Head", "Shoulder.L",
			"Arm.L", "Shoulder.R", "Arm.R"
		};

		public static IReadOnlyList<string> Bones(RigId rig)
		{
			return rig switch
			{
				RigId.Goblin => GoblinBones, 
				RigId.LilDave => LilDaveBones, 
				RigId.Wyvern => WyvernBones, 
				_ => StoneBones, 
			};
		}

		public static string RendererName(RigId rig)
		{
			return rig switch
			{
				RigId.Goblin => "GoblinRender", 
				RigId.LilDave => "DinoRenderer", 
				RigId.Wyvern => "WyvernRenderer", 
				_ => "StoneGolem", 
			};
		}
	}
	internal static class NovaFalloff
	{
		public const float Radius = 8f;

		public const float SweetSpot = 0.5f;

		public const float OuterFraction = 0.25f;

		public const int BaseDamage = 40;

		public static float Fraction(float distance)
		{
			if (distance <= 4f)
			{
				return 1f;
			}
			if (distance <= 8f)
			{
				return 0.25f;
			}
			return 0f;
		}

		public static int Damage(float mobMultiplier, float distance)
		{
			float num = Fraction(distance);
			if (num <= 0f)
			{
				return 0;
			}
			int num2 = (int)((float)DamageTuning.FxDamage(40, mobMultiplier) * num);
			if (num2 >= 1)
			{
				return num2;
			}
			return 1;
		}
	}
	internal static class RigMetrics
	{
		public static (Vec3 min, Vec3 max) Aabb(IReadOnlyList<Vec3> corners)
		{
			if (corners == null || corners.Count == 0)
			{
				return (min: new Vec3(0f, 0f, 0f), max: new Vec3(0f, 0f, 0f));
			}
			Vec3 item = corners[0];
			Vec3 item2 = corners[0];
			for (int i = 1; i < corners.Count; i++)
			{
				Vec3 vec = corners[i];
				item = new Vec3(Min(item.X, vec.X), Min(item.Y, vec.Y), Min(item.Z, vec.Z));
				item2 = new Vec3(Max(item2.X, vec.X), Max(item2.Y, vec.Y), Max(item2.Z, vec.Z));
			}
			return (min: item, max: item2);
		}

		public static float BodyHeight(IReadOnlyList<Vec3> corners)
		{
			(Vec3 min, Vec3 max) tuple = Aabb(corners);
			var (vec, _) = tuple;
			return tuple.max.Y - vec.Y;
		}

		private static float Min(float a, float b)
		{
			if (!(a < b))
			{
				return b;
			}
			return a;
		}

		private static float Max(float a, float b)
		{
			if (!(a > b))
			{
				return b;
			}
			return a;
		}
	}
	internal static class RoR2Marker
	{
		public const float GridStep = 0.0625f;

		public const float KindStep = 0.00390625f;

		public const float NudgeStep = 0.00048828125f;

		public const double Tolerance = 0.001;

		public const float MaxH = 16f;

		public static float Encode(float h, EnemyKind kind)
		{
			float num = h;
			if (num < 0f)
			{
				num = 0f;
			}
			if (num > 16f)
			{
				num = 16f;
			}
			double num2 = Math.Floor(16.0 * (double)num) / 16.0;
			if (num2 < 0.0625)
			{
				num2 = 0.0625;
			}
			return (float)(num2 + (double)kind / 256.0);
		}

		public static bool TryDecodeCode(float bossMultiplier, out int code)
		{
			double num = bossMultiplier;
			double num2 = Math.Floor(num * 16.0) / 16.0;
			double num3 = (num - num2) * 256.0;
			int num4 = (int)Math.Round(num3);
			if (Math.Abs(num3 - (double)num4) < 0.001 && num4 >= 1 && num4 <= 15)
			{
				code = num4;
				return true;
			}
			code = 0;
			return false;
		}

		public static bool TryDecode(float bossMultiplier, string spawnedMobName, out EnemyKind kind)
		{
			kind = EnemyKind.None;
			if (!TryDecodeCode(bossMultiplier, out var code))
			{
				return false;
			}
			if (code == 4)
			{
				return false;
			}
			if (!KindRegistry.TryGet(code, out var spec))
			{
				return false;
			}
			if (!KindRegistry.IsImplemented(spec))
			{
				return false;
			}
			if (spec.SpawnedMobName != spawnedMobName)
			{
				return false;
			}
			kind = spec.Kind;
			return true;
		}

		public static bool NeedsNudge(float bossMultiplier)
		{
			KindSpec spec;
			if (TryDecodeCode(bossMultiplier, out var code))
			{
				return KindRegistry.TryGet(code, out spec);
			}
			return false;
		}

		public static float Nudge(float bossMultiplier)
		{
			return bossMultiplier + 0.00048828125f;
		}
	}
	[Flags]
	internal enum SpawnSource
	{
		None = 0,
		Director = 1,
		BossNight = 2,
		BossShrine = 4,
		BattleTotem = 8,
		Debug = 0x10
	}
	internal readonly struct KindRules
	{
		public readonly bool Enabled;

		public readonly int FirstDay;

		public readonly float Chance;

		public readonly int MaxAlive;

		public readonly SpawnSource Sources;

		private KindRules(bool enabled, int firstDay, float chance, int maxAlive, SpawnSource sources)
		{
			Enabled = enabled;
			FirstDay = firstDay;
			Chance = chance;
			MaxAlive = maxAlive;
			Sources = sources;
		}

		public static KindRules Clamped(bool enabled, int firstDay, float chance, int maxAlive, SpawnSource sources)
		{
			if (firstDay < 0)
			{
				firstDay = 0;
			}
			if (float.IsNaN(chance) || chance < 0f)
			{
				chance = 0f;
			}
			if (chance > 1f)
			{
				chance = 1f;
			}
			if (maxAlive < 0)
			{
				maxAlive = 0;
			}
			sources &= ~SpawnSource.Debug;
			return new KindRules(enabled, firstDay, chance, maxAlive, sources);
		}
	}
	internal sealed class RulesSnapshot
	{
		public IReadOnlyDictionary<EnemyKind, KindRules> Kinds;

		public float TitanShrineChance;

		public ulong Fingerprint()
		{
			return ConfigFingerprint.Of(this);
		}

		public bool TryGet(EnemyKind kind, out KindRules rules)
		{
			if (Kinds != null && Kinds.TryGetValue(kind, out rules))
			{
				return true;
			}
			rules = KindRules.Clamped(enabled: false, 0, 0f, 0, SpawnSource.None);
			return false;
		}
	}
	internal enum ShaderKind
	{
		Plain,
		DoubledTint
	}
	internal readonly struct Rgba
	{
		public readonly float R;

		public readonly float G;

		public readonly float B;

		public readonly float A;

		public Rgba(float r, float g, float b, float a)
		{
			R = r;
			G = g;
			B = b;
			A = a;
		}

		public Rgba Scaled(float s)
		{
			return new Rgba(R * s, G * s, B * s, A * s);
		}

		public override string ToString()
		{
			return $"({R}, {G}, {B}, {A})";
		}
	}
	internal readonly struct TintWrite
	{
		public readonly Rgba TintColor;

		public readonly Rgba Color;

		public TintWrite(Rgba tintColor, Rgba color)
		{
			TintColor = tintColor;
			Color = color;
		}
	}
	internal static class ShaderTint
	{
		public static ShaderKind KindOf(string shaderName)
		{
			if (shaderName == null)
			{
				return ShaderKind.Plain;
			}
			bool num = shaderName.IndexOf("Particles/", StringComparison.Ordinal) >= 0;
			bool flag = shaderName.IndexOf("Standard", StringComparison.Ordinal) >= 0;
			if (!num || flag)
			{
				return ShaderKind.Plain;
			}
			return ShaderKind.DoubledTint;
		}

		public static TintWrite For(ShaderKind kind, Rgba intended)
		{
			return new TintWrite((kind == ShaderKind.DoubledTint) ? intended.Scaled(0.5f) : intended, intended);
		}

		public static Rgba Intended(ShaderKind kind, Rgba tintColor)
		{
			if (kind != ShaderKind.DoubledTint)
			{
				return tintColor;
			}
			return tintColor.Scaled(2f);
		}
	}
	internal readonly struct SpawnRequest
	{
		public readonly SpawnSource Source;

		public readonly int Day;

		public readonly string SourceMobName;

		public readonly float IncomingMultiplier;

		public readonly float IncomingBossMultiplier;

		public readonly int GuardianType;

		public readonly double Roll;

		public readonly bool ShrineIsTitan;

		public readonly EnemyKind ForcedKind;

		public readonly Func<EnemyKind, int> AliveCount;

		public SpawnRequest(SpawnSource source, int day, string sourceMobName, float incomingMultiplier, float incomingBossMultiplier, int guardianType, double roll, bool shrineIsTitan, EnemyKind forcedKind, Func<EnemyKind, int> aliveCount)
		{
			Source = source;
			Day = day;
			SourceMobName = sourceMobName;
			IncomingMultiplier = incomingMultiplier;
			IncomingBossMultiplier = incomingBossMultiplier;
			GuardianType = guardianType;
			Roll = roll;
			ShrineIsTitan = shrineIsTitan;
			ForcedKind = forcedKind;
			AliveCount = aliveCount;
		}
	}
	internal enum SpawnReason
	{
		Converted,
		NoContext,
		Guardian,
		NoCandidate,
		RollMissed,
		Nudged
	}
	internal readonly struct SpawnDecision
	{
		public readonly EnemyKind Kind;

		public readonly string SpawnedMobName;

		public readonly float Multiplier;

		public readonly float BossMultiplier;

		public readonly SpawnReason Reason;

		public SpawnDecision(EnemyKind kind, string spawnedMobName, float multiplier, float bossMultiplier, SpawnReason reason)
		{
			Kind = kind;
			SpawnedMobName = spawnedMobName;
			Multiplier = multiplier;
			BossMultiplier = bossMultiplier;
			Reason = reason;
		}
	}
	internal static class SpawnPlanner
	{
		private readonly struct Candidate
		{
			public readonly KindSpec Spec;

			public readonly float Chance;

			public Candidate(KindSpec spec, float chance)
			{
				Spec = spec;
				Chance = chance;
			}
		}

		public static bool KeepsBuff(float incomingMultiplier, float damageScale)
		{
			if (!(incomingMultiplier > 1f))
			{
				return true;
			}
			return incomingMultiplier * damageScale > 1f;
		}

		public static SpawnDecision Decide(in SpawnRequest req, RulesSnapshot rules)
		{
			if (req.GuardianType != -1)
			{
				return Vanilla(in req, SpawnReason.Guardian);
			}
			if (req.Source == SpawnSource.None)
			{
				return Vanilla(in req, SpawnReason.NoContext);
			}
			List<Candidate> list = new List<Candidate>();
			if (req.Source == SpawnSource.Debug)
			{
				if (rules != null && KindRegistry.TryGet((int)req.ForcedKind, out var spec) && Accept(in req, rules, spec, skipDay: true, skipSources: true) && spec.SourceMobName == req.SourceMobName)
				{
					list.Add(new Candidate(spec, 1f));
				}
			}
			else
			{
				foreach (KindSpec item in KindRegistry.All)
				{
					if (Accept(in req, rules, item, skipDay: false, skipSources: false) && !(item.SourceMobName != req.SourceMobName))
					{
						float num = Chance(in req, rules, item);
						if (!(num <= 0f))
						{
							list.Add(new Candidate(item, num));
						}
					}
				}
			}
			float num2 = 0f;
			for (int i = 0; i < list.Count; i++)
			{
				num2 += list[i].Chance;
			}
			if (num2 > 0f)
			{
				float num3 = ((num2 > 1f) ? (1f / num2) : 1f);
				float num4 = 0f;
				for (int j = 0; j < list.Count; j++)
				{
					num4 += list[j].Chance * num3;
					if (req.Roll < (double)num4)
					{
						return Convert(in req, list[j].Spec);
					}
				}
			}
			SpawnReason reason = ((list.Count == 0) ? SpawnReason.NoCandidate : SpawnReason.RollMissed);
			if (RoR2Marker.NeedsNudge(req.IncomingBossMultiplier))
			{
				return new SpawnDecision(EnemyKind.None, null, req.IncomingMultiplier, RoR2Marker.Nudge(req.IncomingBossMultiplier), SpawnReason.Nudged);
			}
			return Vanilla(in req, reason);
		}

		private static bool Accept(in SpawnRequest req, RulesSnapshot rules, KindSpec spec, bool skipDay, bool skipSources)
		{
			if (spec == null || rules == null)
			{
				return false;
			}
			if (!KindRegistry.IsImplemented(spec))
			{
				return false;
			}
			if (spec.Kind == EnemyKind.Lemurian && !KindRegistry.LemurianAvailable)
			{
				return false;
			}
			if (spec.Kind == EnemyKind.Jellyfish && !KindRegistry.JellyfishAvailable)
			{
				return false;
			}
			if (spec.Kind == EnemyKind.LesserWisp && !KindRegistry.WispAvailable)
			{
				return false;
			}
			if (spec.Kind == EnemyKind.StoneTitan && !KindRegistry.TitanAvailable)
			{
				return false;
			}
			if (!rules.TryGet(spec.Kind, out var rules2) || !rules2.Enabled)
			{
				return false;
			}
			if (!skipSources && (rules2.Sources & req.Source) != req.Source)
			{
				return false;
			}
			if (!skipDay && req.Day < rules2.FirstDay)
			{
				return false;
			}
			int num = ((req.AliveCount != null) ? req.AliveCount(spec.Kind) : 0);
			if (req.Source != SpawnSource.BossShrine && num >= rules2.MaxAlive)
			{
				return false;
			}
			if (!KeepsBuff(req.IncomingMultiplier, spec.DamageScale))
			{
				return false;
			}
			if (req.Source == SpawnSource.BossShrine && !req.ShrineIsTitan)
			{
				return false;
			}
			return spec.DamageScale > 0f;
		}

		private static float Chance(in SpawnRequest req, RulesSnapshot rules, KindSpec spec)
		{
			if (req.Source == SpawnSource.BossShrine)
			{
				return 1f;
			}
			rules.TryGet(spec.Kind, out var rules2);
			return rules2.Chance;
		}

		private static SpawnDecision Convert(in SpawnRequest req, KindSpec spec)
		{
			float multiplier = req.IncomingMultiplier * spec.DamageScale;
			float bossMultiplier = RoR2Marker.Encode(spec.HpFactor / spec.DamageScale * req.IncomingBossMultiplier, spec.Kind);
			return new SpawnDecision(spec.Kind, spec.SpawnedMobName, multiplier, bossMultiplier, SpawnReason.Converted);
		}

		private static SpawnDecision Vanilla(in SpawnRequest req, SpawnReason reason)
		{
			return new SpawnDecision(EnemyKind.None, null, req.IncomingMultiplier, req.IncomingBossMultiplier, reason);
		}
	}
	internal static class SpawnSources
	{
		public const string Accepted = "Director, BossNight, BossShrine, BattleTotem";

		public static SpawnSource Parse(string text, ICollection<string> rejected)
		{
			SpawnSource spawnSource = SpawnSource.None;
			if (string.IsNullOrWhiteSpace(text))
			{
				return spawnSource;
			}
			string[] array = text.Split(new char[1] { ',' });
			for (int i = 0; i < array.Length; i++)
			{
				string text2 = array[i].Trim();
				if (text2.Length != 0)
				{
					if (TryName(text2, out var bit))
					{
						spawnSource |= bit;
					}
					else
					{
						rejected?.Add(text2);
					}
				}
			}
			return spawnSource;
		}

		private static bool TryName(string token, out SpawnSource bit)
		{
			bit = SpawnSource.None;
			if (!char.IsLetter(token[0]))
			{
				return false;
			}
			if (!Enum.TryParse<SpawnSource>(token, ignoreCase: true, out bit) || !Enum.IsDefined(typeof(SpawnSource), bit))
			{
				return false;
			}
			if (bit != SpawnSource.None)
			{
				return bit != SpawnSource.Debug;
			}
			return false;
		}
	}
	internal static class StoneFistAim
	{
		public static Vec3 Predict(Vec3 targetPos, Vec3 targetVelocity, float timeToImpact)
		{
			return new Vec3(targetPos.X + targetVelocity.X * timeToImpact, targetPos.Y, targetPos.Z + targetVelocity.Z * timeToImpact);
		}
	}
	internal enum TitanSkill
	{
		None,
		EyeLaser,
		StoneFist,
		Leap
	}
	internal enum LaserGate
	{
		Ready,
		Cooldown,
		OutOfRange,
		NoLineOfSight
	}
	internal struct TitanCooldowns
	{
		public float LaserReadyAt;

		public float FistReadyAt;

		public float LeapReadyAt;

		public static TitanCooldowns AtSpawn(float now)
		{
			return new TitanCooldowns
			{
				LaserReadyAt = now + 10f,
				FistReadyAt = now + 5f,
				LeapReadyAt = now
			};
		}
	}
	internal readonly struct TitanInputs
	{
		public readonly float Now;

		public readonly float Distance;

		public readonly float AngleToTarget;

		public readonly bool HasLineOfSight;

		public TitanInputs(float now, float distance, float angleToTarget, bool hasLineOfSight)
		{
			Now = now;
			Distance = distance;
			AngleToTarget = angleToTarget;
			HasLineOfSight = hasLineOfSight;
		}
	}
	internal static class TitanScheduler
	{
		public const float LaserCooldown = 20f;

		public const float LaserMinRange = 20f;

		public const float LaserMaxRange = 80f;

		public const float FistCooldown = 10f;

		public const float FistMaxRange = 120f;

		public const float LeapCooldown = 12f;

		public const float LeapMaxRange = 32f;

		public const float LeapMaxAngle = 50f;

		public static TitanSkill Pick(in TitanInputs input, ref TitanCooldowns cd, out LaserGate laser)
		{
			laser = Laser(in input, cd);
			if (laser == LaserGate.Ready)
			{
				cd.LaserReadyAt = input.Now + 20f;
				return TitanSkill.EyeLaser;
			}
			if (FistReady(in input, cd))
			{
				cd.FistReadyAt = input.Now + 10f;
				return TitanSkill.StoneFist;
			}
			if (LeapReady(in input, cd))
			{
				cd.LeapReadyAt = input.Now + 12f;
				return TitanSkill.Leap;
			}
			return TitanSkill.None;
		}

		public static int AttackIndex(TitanSkill skill)
		{
			return skill switch
			{
				TitanSkill.EyeLaser => 0, 
				TitanSkill.StoneFist => 1, 
				TitanSkill.Leap => 2, 
				_ => -1, 
			};
		}

		private static LaserGate Laser(in TitanInputs input, TitanCooldowns cd)
		{
			if (input.Now < cd.LaserReadyAt)
			{
				return LaserGate.Cooldown;
			}
			if (input.Distance < 20f || input.Distance > 80f)
			{
				return LaserGate.OutOfRange;
			}
			if (!input.HasLineOfSight)
			{
				return LaserGate.NoLineOfSight;
			}
			return LaserGate.Ready;
		}

		private static bool FistReady(in TitanInputs input, TitanCooldowns cd)
		{
			if (input.Now >= cd.FistReadyAt)
			{
				return input.Distance <= 120f;
			}
			return false;
		}

		private static bool LeapReady(in TitanInputs input, TitanCooldowns cd)
		{
			if (input.Now >= cd.LeapReadyAt && input.Distance <= 32f)
			{
				return input.AngleToTarget <= 50f;
			}
			return false;
		}
	}
	internal struct LaserSkipLog
	{
		public const float Interval = 5f;

		private float nextAt;

		public bool ShouldLog(LaserGate gate, float now)
		{
			if (gate == LaserGate.Ready || now < nextAt)
			{
				return false;
			}
			nextAt = now + 5f;
			return true;
		}
	}
	internal static class TitanShrineRoll
	{
		public static bool IsTitan(int seed, int shrineId, float chance)
		{
			return Hash01.Of(seed ^ 0x54495441, shrineId) < (double)chance;
		}
	}
	internal readonly struct Vec3
	{
		public readonly float X;

		public readonly float Y;

		public readonly float Z;

		public Vec3(float x, float y, float z)
		{
			X = x;
			Y = y;
			Z = z;
		}

		public static Vec3 operator +(Vec3 a, Vec3 b)
		{
			return new Vec3(a.X + b.X, a.Y + b.Y, a.Z + b.Z);
		}

		public static Vec3 operator -(Vec3 a, Vec3 b)
		{
			return new Vec3(a.X - b.X, a.Y - b.Y, a.Z - b.Z);
		}

		public static Vec3 operator *(Vec3 a, float s)
		{
			return new Vec3(a.X * s, a.Y * s, a.Z * s);
		}

		public static Vec3 operator *(float s, Vec3 a)
		{
			return a * s;
		}

		public static Vec3 Scale(Vec3 a, Vec3 b)
		{
			return new Vec3(a.X * b.X, a.Y * b.Y, a.Z * b.Z);
		}

		public static float Dot(Vec3 a, Vec3 b)
		{
			return a.X * b.X + a.Y * b.Y + a.Z * b.Z;
		}

		public static Vec3 Cross(Vec3 a, Vec3 b)
		{
			return new Vec3(a.Y * b.Z - a.Z * b.Y, a.Z * b.X - a.X * b.Z, a.X * b.Y - a.Y * b.X);
		}

		public static float Length(Vec3 v)
		{
			return (float)Math.Sqrt(Dot(v, v));
		}

		public static Vec3 Normalize(Vec3 v)
		{
			float num = Length(v);
			if (num < 1E-08f)
			{
				return new Vec3(0f, 0f, 0f);
			}
			return v * (1f / num);
		}

		public override string ToString()
		{
			return $"({X}, {Y}, {Z})";
		}
	}
	internal static class WaveRunSpawn
	{
		public const string HostType = "MuckWaveRun.Game.WaveHost";

		public const string Method = "TrySpawn";

		private const int BossNight = 1;

		public static SpawnSource SourceFor(int bossType)
		{
			if (bossType != 1)
			{
				return SpawnSource.Director;
			}
			return SpawnSource.BossNight;
		}
	}
	internal static class WispHover
	{
		public const float DefaultHeight = 5.6f;

		public const float RatePerSecond = 2f;

		public const float MaxNavGap = 2f;

		public static float TargetOffset(float hoverHeight, float currentOffset, float heightAboveGround)
		{
			if (float.IsNaN(heightAboveGround))
			{
				return hoverHeight;
			}
			float num = heightAboveGround - currentOffset;
			if (num > 2f)
			{
				num = 2f;
			}
			if (num < -2f)
			{
				num = -2f;
			}
			return hoverHeight - num;
		}

		public static float Step(float currentOffset, float targetOffset, float deltaTime)
		{
			float num = deltaTime * 2f;
			if (!(num > 0f))
			{
				return currentOffset;
			}
			if (num > 1f)
			{
				num = 1f;
			}
			return currentOffset + (targetOffset - currentOffset) * num;
		}

		public static float Clamp(float hoverHeight)
		{
			if (float.IsNaN(hoverHeight))
			{
				return 5.6f;
			}
			return Math.Max(1f, Math.Min(20f, hoverHeight));
		}
	}
	internal readonly struct VolleyBolt
	{
		public readonly Vec3 From;

		public readonly Vec3 Dir;

		public VolleyBolt(Vec3 from, Vec3 dir)
		{
			From = from;
			Dir = dir;
		}
	}
	internal static class WispVolley
	{
		public const int Bolts = 3;

		public const float LateralSpacing = 1f;

		public const float Stagger = 1.5f;

		public const float FullSpacingDistance = 3f;

		public const float JitterMeters = 0.3f;

		private static readonly Vec3 Up = new Vec3(0f, 1f, 0f);

		private static readonly Vec3 FallbackAxis = new Vec3(1f, 0f, 0f);

		public static VolleyBolt[] Plan(Vec3 mouth, Vec3 target, float jitterAcross0, float jitterUp0, float jitterAcross1, float jitterUp1, float jitterAcross2, float jitterUp2)
		{
			Vec3 vec = target - mouth;
			float num = Vec3.Length(vec);
			Vec3 vec2 = ((num < 0.0001f) ? new Vec3(0f, 0f, 1f) : (vec * (1f / num)));
			Vec3 vec3 = Vec3.Normalize(Vec3.Cross(YawAxis(vec2), vec2));
			Vec3 up = Vec3.Cross(vec2, vec3);
			float num2 = ((num < 3f) ? (num / 3f) : 1f);
			Vec3 vec4 = vec3 * (1f * num2);
			Vec3 vec5 = vec2 * (1.5f * num2);
			return new VolleyBolt[3]
			{
				Bolt(mouth - vec4 - vec5, target, vec2, vec3, up, jitterAcross0, jitterUp0),
				Bolt(mouth, target, vec2, vec3, up, jitterAcross1, jitterUp1),
				Bolt(mouth + vec4 + vec5, target, vec2, vec3, up, jitterAcross2, jitterUp2)
			};
		}

		private static Vec3 YawAxis(Vec3 forward)
		{
			if (!(Vec3.Length(Vec3.Cross(Up, forward)) < 0.0001f))
			{
				return Up;
			}
			return FallbackAxis;
		}

		private static VolleyBolt Bolt(Vec3 from, Vec3 target, Vec3 forward, Vec3 across, Vec3 up, float jitterAcross, float jitterUp)
		{
			Vec3 vec = Vec3.Normalize(target + across * (Clamp(jitterAcross) * 0.3f) + up * (Clamp(jitterUp) * 0.3f) - from);
			return new VolleyBolt(from, (Vec3.Length(vec) == 0f) ? forward : vec);
		}

		private static float Clamp(float value)
		{
			if (value > 1f)
			{
				return 1f;
			}
			if (value < -1f)
			{
				return -1f;
			}
			return value;
		}
	}
	[HarmonyPatch]
	internal static class CheckMobSpawnsContextPatch
	{
		private static MethodBase TargetMethod()
		{
			return AccessTools.DeclaredMethod(typeof(GameLoop), "CheckMobSpawns", Type.EmptyTypes, (Type[])null);
		}

		private static void Prefix(out SpawnSource __state)
		{
			__state = SpawnContext.Current;
			try
			{
				SpawnContext.Current = SpawnSource.Director;
			}
			catch (Exception arg)
			{
				Ror2EnemiesPlugin.LogOnce("ctx-director", $"RoR2 Enemies: director context failed. {arg}");
			}
		}

		private static Exception Finalizer(SpawnSource __state, Exception __exception)
		{
			try
			{
				SpawnContext.Current = __state;
			}
			catch (Exception arg)
			{
				Ror2EnemiesPlugin.LogOnce("ctx-director-restore", $"RoR2 Enemies: director context restore failed. {arg}");
			}
			return __exception;
		}
	}
	[HarmonyPatch]
	internal static class StartBossContextPatch
	{
		private static MethodBase TargetMethod()
		{
			return AccessTools.DeclaredMethod(typeof(GameLoop), "StartBoss", new Type[1] { typeof(MobType) }, (Type[])null);
		}

		private static void Prefix(out SpawnSource __state)
		{
			__state = SpawnContext.Current;
			try
			{
				SpawnContext.Current = SpawnSource.BossNight;
			}
			catch (Exception arg)
			{
				Ror2EnemiesPlugin.LogOnce("ctx-boss", $"RoR2 Enemies: boss-night context failed. {arg}");
			}
		}

		private static Exception Finalizer(SpawnSource __state, Exception __exception)
		{
			try
			{
				SpawnContext.Current = __state;
			}
			catch (Exception arg)
			{
				Ror2EnemiesPlugin.LogOnce("ctx-boss-restore", $"RoR2 Enemies: boss-night context restore failed. {arg}");
			}
			return __exception;
		}
	}
	[HarmonyPatch]
	internal static class ShrineBossContextPatch
	{
		private static MethodBase TargetMethod()
		{
			return AccessTools.DeclaredMethod(typeof(ShrineBoss), "SpawnBoss", Type.EmptyTypes, (Type[])null);
		}

		private static void Prefix(ShrineBoss __instance, out SpawnContextSaved __state)
		{
			__state = new SpawnContextSaved
			{
				Source = SpawnContext.Current,
				Shrine = SpawnContext.Shrine
			};
			try
			{
				SpawnContext.Current = SpawnSource.BossShrine;
				SpawnContext.Shrine = __instance;
			}
			catch (Exception arg)
			{
				Ror2EnemiesPlugin.LogOnce("ctx-shrine", $"RoR2 Enemies: shrine context failed. {arg}");
			}
		}

		private static Exception Finalizer(SpawnContextSaved __state, Exception __exception)
		{
			try
			{
				if (__state != null)
				{
					SpawnContext.Current = __state.Source;
					SpawnContext.Shrine = __state.Shrine;
				}
			}
			catch (Exception arg)
			{
				Ror2EnemiesPlugin.LogOnce("ctx-shrine-restore", $"RoR2 Enemies: shrine context restore failed. {arg}");
			}
			return __exception;
		}
	}
	[HarmonyPatch]
	internal static class BattleTotemContextPatch
	{
		private static MethodBase TargetMethod()
		{
			return AccessTools.DeclaredMethod(typeof(ShrineInteractable), "ServerExecute", new Type[1] { typeof(int) }, (Type[])null);
		}

		private static void Prefix(out SpawnSource __state)
		{
			__state = SpawnContext.Current;
			try
			{
				SpawnContext.Current = SpawnSource.BattleTotem;
			}
			catch (Exception arg)
			{
				Ror2EnemiesPlugin.LogOnce("ctx-totem", $"RoR2 Enemies: totem context failed. {arg}");
			}
		}

		private static Exception Finalizer(SpawnSource __state, Exception __exception)
		{
			try
			{
				SpawnContext.Current = __state;
			}
			catch (Exception arg)
			{
				Ror2EnemiesPlugin.LogOnce("ctx-totem-restore", $"RoR2 Enemies: totem context restore failed. {arg}");
			}
			return __exception;
		}
	}
	[HarmonyPatch]
	internal static class FxLifetimePatch
	{
		private static readonly TaggedFxLedger Tagged = new TaggedFxLedger();

		private static MethodBase TargetMethod()
		{
			return AccessTools.DeclaredMethod(typeof(ImpactDamage), "Start", Type.EmptyTypes, (Type[])null);
		}

		public static void NoteTaggedSpawn(SpawnAttackFx fx, int n)
		{
			//IL_004a: Unknown result type (might be due to invalid IL or missing references)
			//IL_004f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0065: 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_0071: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				if (!((Object)(object)fx == (Object)null) && fx.attackFx != null && n >= 0 && n < fx.attackFx.Length)
				{
					GameObject val = fx.attackFx[n];
					if (!((Object)(object)val == (Object)null) && !((Object)(object)fx.spawnPos == (Object)null))
					{
						Vector3 position = fx.spawnPos.position;
						Tagged.Note(((Object)val).name + "(Clone)", new Vec3(position.x, position.y, position.z), Time.time);
					}
				}
			}
			catch (Exception arg)
			{
				Ror2EnemiesPlugin.LogOnce("fx-note", $"RoR2 Enemies: could not track an attack effect. {arg}");
			}
		}

		public static void Reset()
		{
			Tagged.Clear();
		}

		private static void Prefix(ImpactDamage __instance)
		{
			//IL_001b: 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_002c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0032: Unknown result type (might be due to invalid IL or missing references)
			//IL_0038: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				if (!((Object)(object)__instance == (Object)null))
				{
					Transform root = ((Component)__instance).transform.root;
					Vector3 position = root.position;
					bool fromTaggedMob = Tagged.TryClaim(((Object)root).name, new Vec3(position.x, position.y, position.z), Time.time);
					bool rootHasOwnLifetime = (Object)(object)((Component)root).GetComponent<DestroyObject>() != (Object)null;
					bool cleanVanilla = Ror2EnemiesPlugin.Cfg == null || Ror2EnemiesPlugin.Cfg.CleanVanillaGroundFx.Value;
					if (FxLifetime.ShouldExpire(((Object)root).name, rootHasOwnLifetime, fromTaggedMob, cleanVanilla))
					{
						Object.Destroy((Object)(object)((Component)root).gameObject, 5f);
					}
				}
			}
			catch (Exception arg)
			{
				Ror2EnemiesPlugin.LogOnce("fx-lifetime", $"RoR2 Enemies: attack effect cleanup failed. {arg}");
			}
		}
	}
	[HarmonyPatch]
	internal static class JellyAttackGatePatch
	{
		private static MethodBase TargetMethod()
		{
			return AccessTools.DeclaredMethod(typeof(MobServerEnemy), "AttackBehaviour", Type.EmptyTypes, (Type[])null);
		}

		private static bool Prefix(MobServerEnemy __instance)
		{
			try
			{
				if ((Object)(object)__instance == (Object)null)
				{
					return true;
				}
				Ror2Enemy component = ((Component)__instance).GetComponent<Ror2Enemy>();
				if ((Object)(object)component == (Object)null || component.Kind != EnemyKind.Jellyfish || component.Fuse == null)
				{
					return true;
				}
				if (component.Fuse.BlocksHostAttacks)
				{
					return false;
				}
			}
			catch (Exception arg)
			{
				Ror2EnemiesPlugin.LogOnce("jelly-gate", $"RoR2 Enemies: Jellyfish attack gate failed, left the attack. {arg}");
			}
			return true;
		}
	}
	[HarmonyPatch]
	internal static class LootPatch
	{
		private static MethodBase TargetMethod()
		{
			return AccessTools.DeclaredMethod(typeof(LootExtra), "DropMobLoot", new Type[4]
			{
				typeof(Transform),
				typeof(LootDrop),
				typeof(int),
				typeof(float)
			}, (Type[])null);
		}

		private static void Prefix(Transform __0, ref LootDrop __1)
		{
			try
			{
				if (!((Object)(object)__0 == (Object)null))
				{
					Ror2Enemy component = ((Component)__0).GetComponent<Ror2Enemy>();
					if (!((Object)(object)component == (Object)null) && component.Spec != null && component.Spec.LootMode != LootMode.None)
					{
						__1 = LootTables.For(component.Spec, __1);
					}
				}
			}
			catch (Exception arg)
			{
				Ror2EnemiesPlugin.LogOnce("loot", $"RoR2 Enemies: loot plan failed, left the vanilla table. {arg}");
			}
		}
	}
	[HarmonyPatch]
	internal static class MobAttackPatch
	{
		private static MethodBase TargetMethod()
		{
			return AccessTools.DeclaredMethod(typeof(Mob), "Attack", new Type[2]
			{
				typeof(int),
				typeof(int)
			}, (Type[])null);
		}

		private static void Postfix(Mob __instance, int __0, int __1)
		{
			try
			{
				if ((Object)(object)__instance == (Object)null)
				{
					return;
				}
				Ror2Enemy component = ((Component)__instance).GetComponent<Ror2Enemy>();
				if (!((Object)(object)component == (Object)null))
				{
					component.AttackIndex = __1;
					if (component.Kind == EnemyKind.Jellyfish && component.Fuse != null)
					{
						component.Fuse.Attack(__0, Time.time);
					}
					if (component.Kind == EnemyKind.StoneTitan && __1 == 0)
					{
						TitanLaser.Begin(component, __0);
					}
				}
			}
			catch (Exception arg)
			{
				Ror2EnemiesPlugin.LogOnce("mob-attack", $"RoR2 Enemies: attack follow-up failed. {arg}");
			}
		}
	}
	[HarmonyPatch]
	internal static class RagdollPatch
	{
		private static MethodBase TargetMethod()
		{
			return AccessTools.DeclaredMethod(typeof(TestRagdoll), "MakeRagdoll", new Type[1] { typeof(Vector3) }, (Type[])null);
		}

		private static bool Prefix(TestRagdoll __instance)
		{
			try
			{
				if ((Object)(object)__instance == (Object)null)
				{
					return true;
				}
				Ror2Enemy componentInParent = ((Component)__instance).GetComponentInParent<Ror2Enemy>();
				if ((Object)(object)componentInParent != (Object)null && componentInParent.Fuse != null && componentInParent.Fuse.State == FuseState.Detonated)
				{
					return false;
				}
			}
			catch (Exception arg)
			{
				Ror2EnemiesPlugin.LogOnce("jelly-ragdoll", $"RoR2 Enemies: Jellyfish ragdoll skip failed. {arg}");
			}
			return true;
		}
	}
	[HarmonyPatch]
	internal static class ServerSpawnNewMobPatch
	{
		private static MethodBase TargetMethod()
		{
			return AccessTools.DeclaredMethod(typeof(MobSpawner), "ServerSpawnNewMob", new Type[7]
			{
				typeof(int),
				typeof(int),
				typeof(Vector3),
				typeof(float),
				typeof(float),
				typeof(BossType),
				typeof(int)
			}, (Type[])null);
		}

		[HarmonyAfter(new string[] { "evan.muck.artifactofthenight" })]
		[HarmonyPriority(200)]
		private static void Prefix(MobSpawner __instance, ref int __1, ref float __3, ref float __4, int __6)
		{
			try
			{
				if (!LocalClient.serverOwner || (Object)(object)__instance == (Object)null || __instance.allMobs == null || __1 < 0 || __1 >= __instance.allMobs.Length || (Object)(object)__instance.allMobs[__1] == (Object)null || (Object)(object)GameManager.instance == (Object)null || Ror2EnemiesPlugin.Cfg == null)
				{
					return;
				}
				MobCatalog.Ensure();
				Items.Ensure();
				SpawnRequest req = new SpawnRequest(SpawnContext.Current, GameManager.instance.currentDay, __instance.allMobs[__1].name, __3, __4, __6, Random.value, TitanShrines.ShrineConverts(SpawnContext.Shrine), DebugSpawner.Forced, Ror2Enemy.LiveCount);
				SpawnDecision spawnDecision = SpawnPlanner.Decide(in req, Ror2EnemiesPlugin.Cfg.Rules());
				if (spawnDecision.Kind != EnemyKind.None)
				{
					if (!MobCatalog.TryId(spawnDecision.SpawnedMobName, out var id))
					{
						Ror2EnemiesPlugin.LogOnce("spawn-id-" + spawnDecision.SpawnedMobName, "RoR2 Enemies: no id for " + spawnDecision.SpawnedMobName + ". Left vanilla.");
						return;
					}
					__1 = id;
					__3 = spawnDecision.Multiplier;
					__4 = spawnDecision.BossMultiplier;
					if (Ror2EnemiesPlugin.Cfg.VerboseLogging.Value)
					{
						Ror2EnemiesPlugin.Log.LogInfo((object)$"RoR2 Enemies: converted {req.SourceMobName} to {spawnDecision.Kind}.");
					}
				}
				else if (spawnDecision.Reason == SpawnReason.Nudged)
				{
					__4 = spawnDecision.BossMultiplier;
					if (Ror2EnemiesPlugin.Cfg.VerboseLogging.Value)
					{
						Ror2EnemiesPlugin.Log.LogInfo((object)"RoR2 Enemies: nudged a bossMultiplier that already decoded.");
					}
				}
			}
			catch (Exception arg)
			{
				Ror2EnemiesPlugin.LogOnce("spawn", $"RoR2 Enemies: spawn conversion failed, left vanilla. {arg}");
			}
		}
	}
	[HarmonyPatch]
	internal static class ShrineGetNamePatch
	{
		private static MethodBase TargetMethod()
		{
			return AccessTools.DeclaredMethod(typeof(ShrineBoss), "GetName", Type.EmptyTypes, (Type[])null);
		}

		private static void Postfix(ShrineBoss __instance, ref string __result)
		{
			try
			{
				if ((Object)(object)__instance != (Object)null && TitanShrines.IsTitan(__instance))
				{
					__result = "Challenge Stone Titan";
				}
			}
			catch (Exception arg)
			{
				Ror2EnemiesPlugin.LogOnce("titan-shrine-name", $"RoR2 Enemies: shrine name failed. {arg}");
			}
		}
	}
	[HarmonyPatch]
	internal static class ShrineServerExecutePatch
	{
		private static MethodBase TargetMethod()
		{
			return AccessTools.DeclaredMethod(typeof(ShrineBoss), "ServerExecute", new Type[1] { typeof(int) }, (Type[])null);
		}

		private static void Prefix(ShrineBoss __instance)
		{
			try
			{
				if (LocalClient.serverOwner && !((Object)(object)__instance == (Object)null) && TitanShrines.IsTitan(__instance))
				{
					MobType label = TitanShrines.Label;
					if (!((Object)(object)label == (Object)null))
					{
						__instance.boss = label;
					}
				}
			}
			catch (Exception arg)
			{
				Ror2EnemiesPlugin.LogOnce("titan-shrine", $"RoR2 Enemies: shrine label failed. {arg}");
			}
		}
	}
	[HarmonyPatch]
	internal static class SpawnFxPatch
	{
		private static MethodBase TargetMethod()
		{
			return AccessTools.DeclaredMethod(typeof(SpawnAttackFx), "SpawnFx", new Type[1] { typeof(int) }, (Type[])null);
		}

		private static bool Prefix(SpawnAttackFx __instance, int n, ref float __state)
		{
			__state = float.NaN;
			try
			{
				if ((Object)(object)__instance == (Object)null)
				{
					return true;
				}
				Ror2Enemy component = ((Component)__instance).GetComponent<Ror2Enemy>();
				if ((Object)(object)component == (Object)null)
				{
					return true;
				}
				if (component.Kind == EnemyKind.Jellyfish)
				{
					return component.Fuse != null && component.Fuse.BitingBuild;
				}
				if (component.Kind == EnemyKind.StoneTitan && component.AttackIndex == 0)
				{
					return false;
				}
				FxLifetimePatch.NoteTaggedSpawn(__instance, n);
				if (component.Kind != EnemyKind.Lemurian || (Object)(object)component.Mob == (Object)null || component.Spec == null)
				{
					return true;
				}
				__state = component.Mob.multiplier;
				component.Mob.multiplier = DamageTuning.MeleeRefineMultiplier(__state, component.Spec.DamageScale, component.Spec.MeleeFactor);
			}
			catch (Exception arg)
			{
				Ror2EnemiesPlugin.LogOnce("lemurian-bite", $"RoR2 Enemies: Lemurian bite refine failed, left the Goblin bite. {arg}");
			}
			return true;
		}

		private static Exception Finalizer(SpawnAttackFx __instance, float __state, Exception __exception)
		{
			try
			{
				if (float.IsNaN(__state) || (Object)(object)__instance == (Object)null)
				{
					return __exception;
				}
				Ror2Enemy component = ((Component)__instance).GetComponent<Ror2Enemy>();
				if ((Object)(object)component != (Object)null && (Object)(object)component.Mob != (Object)null)
				{
					component.Mob.multiplier = __state;
				}
			}
			catch (Exception arg)
			{
				Ror2EnemiesPlugin.LogOnce("lemurian-bite-restore", $"RoR2 Enemies: Lemurian bite restore failed. {arg}");
			}
			return __exception;
		}
	}
	[HarmonyPatch]
	internal static class SpawnMobAdoptPatch
	{
		private static MethodBase TargetMethod()
		{
			return AccessTools.DeclaredMethod(typeof(MobSpawner), "SpawnMob", new Type[7]
			{
				typeof(Vector3),
				typeof(int),
				typeof(int),
				typeof(float),
				typeof(float),
				typeof(BossType),
				typeof(int)
			}, (Type[])null);
		}

		private static void Prefix(MobSpawner __instance, int __1, float __4, out EnemyKind __state)
		{
			__state = EnemyKind.None;
			try
			{
				if (!((Object)(object)__instance == (Object)null) && __instance.allMobs != null && __1 >= 0 && __1 < __instance.allMobs.Length && !((Object)(object)__instance.allMobs[__1] == (Object)null))
				{
					RoR2Marker.TryDecode(__4, __instance.allMobs[__1].name, out __state);
				}
			}
			catch (Exception arg)
			{
				__state = EnemyKind.None;
				Ror2EnemiesPlugin.LogOnce("decode", $"RoR2 Enemies: decode failed. {arg}");
			}
		}

		private static void Postfix(int __2, EnemyKind __state)
		{
			if (__state == EnemyKind.None)
			{
				return;
			}
			try
			{
				Mob value = null;
				if (!((Object)(object)MobManager.Instance != (Object)null) || MobManager.Instance.mobs == null || !MobManager.Instance.mobs.TryGetValue(__2, out value) || !((Object)(object)value != (Object)null))
				{
					Ror2EnemiesPlugin.LogOnce("adopt-missing", $"RoR2 Enemies: mob {__2} was not in MobManager.mobs.");
				}
				else if (!((Object)(object)((Component)value).GetComponent<Ror2Enemy>() != (Object)null))
				{
					((Component)value).gameObject.AddComponent<Ror2Enemy>().Adopt(__state);
					HostPresence.Confirm();
					if (Ror2EnemiesPlugin.Cfg != null && Ror2EnemiesPlugin.Cfg.VerboseLogging.Value)
					{
						Ror2EnemiesPlugin.Log.LogInfo((object)$"RoR2 Enemies: adopted {__state} mob {__2}.");
					}
				}
			}
			catch (Exception arg)
			{
				Ror2EnemiesPlugin.LogOnce("adopt", $"RoR2 Enemies: adopt failed. {arg}");
			}
		}
	}
	[HarmonyPatch]
	internal static class StoneFistPatch
	{
		private static MethodBase TargetMethod()
		{
			return AccessTools.DeclaredMethod(typeof(ProjectileAttackNoGravity), "SpawnProjectilePredictionTrajectory", Type.EmptyTypes, (Type[])null);
		}

		private static bool Prefix(ProjectileAttackNoGravity __instance)
		{
			//IL_00b7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bc: Unknown result type (might be due to invalid IL or missing references)
			//IL_00de: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ec: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ff: Unknown result type (might be due to invalid IL or missing references)
			//IL_0106: Unknown result type (might be due to invalid IL or missing references)
			//IL_010d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0147: Unknown result type (might be due to invalid IL or missing references)
			//IL_0149: Unknown result type (might be due to invalid IL or missing references)
			//IL_0153: Unknown result type (might be due to invalid IL or missing references)
			//IL_0158: Unknown result type (might be due to invalid IL or missing references)
			//IL_015d: Unknown result type (might be due to invalid IL or missing references)
			//IL_017e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0180: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a0: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a2: Unknown result type (might be due to invalid IL or missing references)
			//IL_0177: Unknown result type (might be due to invalid IL or missing references)
			//IL_017c: Unknown result type (might be due to invalid IL or missing references)
			bool flag = false;
			try
			{
				if ((Object)(object)__instance == (Object)null)
				{
					return true;
				}
				Ror2Enemy component = ((Component)__instance).GetComponent<Ror2Enemy>();
				if ((Object)(object)component == (Object)null || component.Kind != EnemyKind.StoneTitan)
				{
					return true;
				}
				flag = true;
				if (!LocalClient.serverOwner)
				{
					return false;
				}
				Mob mob = component.Mob;
				if ((Object)(object)mob == (Object)null || (Object)(object)mob.target == (Object)null)
				{
					return false;
				}
				Items.Ensure();
				InventoryItem spikeAttack = Items.SpikeAttack;
				if ((Object)(object)spikeAttack == (Object)null || (Object)(object)spikeAttack.bowComponent == (Object)null || (Object)(object)ProjectileController.Instance == (Object)null)
				{
					Ror2EnemiesPlugin.LogOnce("titan-fist", "RoR2 Enemies: Stone Titan could not throw a fist. Left the rock unfired.");
					return false;
				}
				Vector3 position = mob.target.position;
				Rigidbody component2 = ((Component)mob.target).GetComponent<Rigidbody>();
				Vector3 val = (((Object)(object)component2 != (Object)null) ? component2.velocity : Vector3.zero);
				Vec3 vec = StoneFistAim.Predict(new Vec3(position.x, position.y, position.z), new Vec3(val.x, val.y, val.z), spikeAttack.bowComponent.timeToImpact);
				Vector3 point = default(Vector3);
				((Vector3)(ref point))..ctor(vec.X, vec.Y, vec.Z);
				RaycastHit val2 = default(RaycastHit);
				if (Physics.Raycast(point + Vector3.up * 50f, Vector3.down, ref val2, 200f, 512))
				{
					point = ((RaycastHit)(ref val2)).point;
				}
				ServerSend.MobSpawnProjectile(point, Vector3.forward, 0f, spikeAttack.id, mob.id);
				ProjectileController.Instance.SpawnMobProjectile(point, Vector3.forward, 0f, spikeAttack.id, mob.id);
				return false;
			}
			catch (Exception arg)
			{
				Ror2EnemiesPlugin.LogOnce("titan-fist", $"RoR2 Enemies: Stone Fist failed. {arg}");
				return !flag;
			}
		}
	}
	[HarmonyPatch]
	internal static class TitanAttackPatch
	{
		private static FieldRef<MobServerEnemy, bool> serverReady;

		private static MethodBase TargetMethod()
		{
			return AccessTools.DeclaredMethod(typeof(MobServerEnemyMeleeAndRanged), "AttackBehaviour", Type.EmptyTypes, (Type[])null);
		}

		private static bool Prefix(MobServerEnemyMeleeAndRanged __instance)
		{
			//IL_0048: Unknown result type (might be due to invalid IL or missing references)
			//IL_0053: Unknown result type (might be due to invalid IL or missing references)
			bool flag = false;
			try
			{
				if ((Object)(object)__instance == (Object)null)
				{
					return true;
				}
				Ror2Enemy component = ((Component)__instance).GetComponent<Ror2Enemy>();
				if ((Object)(object)component == (Object)null || component.Kind != EnemyKind.StoneTitan)
				{
					return true;
				}
				Mob mob = component.Mob;
				TitanInputs input = new TitanInputs(Time.time, Vector3.Distance(mob.target.position, ((Component)__instance).transform.position), VanillaAngle(((Component)__instance).transform, mob.target), component.HostLineOfSight());
				LaserGate laser;
				TitanSkill titanSkill = TitanScheduler.Pick(in input, ref component.TitanCooldowns, out laser);
				if (component.LaserSkips.ShouldLog(laser, input.Now))
				{
					Ror2EnemiesPlugin.Log.LogInfo((object)($"RoR2 Enemies: Titan {mob.GetId()} held the laser: {laser} at {input.Distance:F1} m, " + $"sight {input.HasLineOfSight}, ready in {Mathf.Max(0f, component.TitanCooldowns.LaserReadyAt - input.Now):F1} s. Picked {titanSkill}."));
				}
				if (titanSkill == TitanSkill.None)
				{
					return false;
				}
				int num = TitanScheduler.AttackIndex(titanSkill);
				float num2 = mob.attackTimes[num] + Random.Range(0f, mob.attackCooldown);
				mob.Attack(mob.targetPlayerId, num);
				flag = true;
				ServerSend.MobAttack(mob.GetId(), mob.targetPlayerId, num);
				HoldFire((MobServerEnemy)(object)__instance);
				((MonoBehaviour)__instance).Invoke("GetReady", num2);
				return false;
			}
			catch (Exception arg)
			{
				Ror2EnemiesPlugin.LogOnce("titan-ai", $"RoR2 Enemies: Titan AI failed, using vanilla. {arg}");
				return !flag;
			}
		}

		private static void HoldFire(MobServerEnemy server)
		{
			if (serverReady == null)
			{
				serverReady = AccessTools.FieldRefAccess<MobServerEnemy, bool>("serverReadyToAttack");
			}
			serverReady.Invoke(server) = false;
		}

		private static float VanillaAngle(Transform body, Transform target)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0007: 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_0011: Unknown result type (might be due to invalid IL or missing references)
			//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_0053: Unknown result type (might be due to invalid IL or missing references)
			//IL_0054: Unknown result type (might be due to invalid IL or missing references)
			Vector3 val = target.position - body.position;
			Vector3 forward = body.forward;
			val.y = 0f;
			forward.y = 0f;
			if (((Vector3)(ref val)).sqrMagnitude < 1E-08f || ((Vector3)(ref forward)).sqrMagnitude < 1E-08f)
			{
				return 0f;
			}
			return Vector3.Angle(forward, val);
		}
	}
	[HarmonyPatch]
	internal static class TitanDeathPatch
	{
		private static MethodBase TargetMethod()
		{
			return AccessTools.DeclaredMethod(typeof(HitableMob), "OnKill", new Type[1] { typeof(Vector3) }, (Type[])null);
		}

		private static Exception Finalizer(HitableMob __instance, Exception __exception)
		{
			try
			{
				if ((Object)(object)__instance == (Object)null)
				{
					return __exception;
				}
				Ror2Enemy component = ((Component)__instance).GetComponent<Ror2Enemy>();
				if ((Object)(object)component == (Object)null || component.Kind != EnemyKind.StoneTitan || (Object)(object)component.Mob == (Object)null)
				{
					return __exception;
				}
				if ((Object)(object)BossUI.Instance != (Object)null && (Object)(object)BossUI.Instance.currentBoss == (Object)(object)component.Mob)
				{
					BossUI.Instance.currentBoss = null;
				}
				if (__exception == null)
				{
					return null;
				}
				Ror2EnemiesPlugin.LogOnce("titan-death", $"RoR2 Enemies: Titan death threw before the despawn. Finished it. {__exception}");
				if ((Object)(object)MobManager.Instance != (Object)null)
				{
					MobManager.Instance.RemoveMob(component.Mob.id);
				}
				Object.Destroy((Object)(object)((Component)__instance).gameObject);
				return null;
			}
			catch (Exception arg)
			{
				Ror2EnemiesPlugin.LogOnce("titan-death-bar", $"RoR2 Enemies: Titan boss bar release failed. {arg}");
				return __exception;
			}
		}
	}
	[HarmonyPatch]
	internal static class WaveRunContextPatch
	{
		private static readonly Type[] Parameters = new Type[6]
		{
			typeof(int),
			typeof(Vector3),
			typeof(float),
			typeof(float),
			typeof(BossType),
			typeof(int).MakeByRefType()
		};

		internal static MethodBase TargetMethod()
		{
			Type type = HostType();
			if (!(type == null))
			{
				return AccessTools.DeclaredMethod(type, "TrySpawn", Parameters, (Type[])null);
			}
			return null;
		}

		internal static Type HostType()
		{
			Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
			foreach (Assembly assembly in assemblies)
			{
				Type type;
				try
				{
					type = assembly.GetType("MuckWaveRun.Game.WaveHost", throwOnError: false);
				}
				catch (Exception)
				{
					continue;
				}
				if (type != null)
				{
					return type;
				}
			}
			return null;
		}

		private static void Prefix(BossType __4, out SpawnSource __state)
		{
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_000d: Expected I4, but got Unknown
			__state = SpawnContext.Current;
			try
			{
				SpawnContext.Current = WaveRunSpawn.SourceFor((int)__4);
			}
			catch (Exception arg)
			{
				Ror2EnemiesPlugin.LogOnce("ctx-waverun", $"RoR2 Enemies: Wave Run context failed. {arg}");
			}
		}

		private static Exception Finalizer(SpawnSource __state, Exception __exception)
		{
			try
			{
				SpawnContext.Current = __state;
			}
			catch (Exception arg)
			{
				Ror2EnemiesPlugin.LogOnce("ctx-waverun-restore", $"RoR2 Enemies: Wave Run context restore failed. {arg}");
			}
			return __exception;
		}
	}
	[HarmonyPatch]
	internal static class WispHoverPatch
	{
		private static MethodBase TargetMethod()
		{
			return AccessTools.DeclaredMethod(typeof(MobFlying), "ExtraUpdate", Type.EmptyTypes, (Type[])null);
		}

		private static bool Prefix(MobFlying __instance)
		{
			//IL_0056: Unknown result type (might be due to invalid IL or missing references)
			//IL_005b: Unknown result type (might be due to invalid IL or missing references)
			//IL_005c: Unknown result type (might be due to invalid IL or missing references)
			//IL_005d: Unknown result type (might be due to invalid IL or missing references)
			//IL_006a: Unknown result type (might be due to invalid IL or missing references)
			//IL_007b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0083: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				if ((Object)(object)__instance == (Object)null)
				{
					return true;
				}
				Ror2Enemy component = ((Component)__instance).GetComponent<Ror2Enemy>();
				if ((Object)(object)component == (Object)null || component.Kind != EnemyKind.LesserWisp)
				{
					return true;
				}
				NavMeshAgent agent = ((Mob)__instance).agent;
				if ((Object)(object)agent == (Object)null)
				{
					return false;
				}
				float heightAboveGround = float.NaN;
				Vector3 position = ((Component)__instance).transform.position;
				RaycastHit val = default(RaycastHit);
				if (Physics.Raycast(position, Vector3.down, ref val, 5000f, LayerMask.op_Implicit(__instance.whatIsGround)))
				{
					heightAboveGround = position.y - ((RaycastHit)(ref val)).point.y;
				}
				float targetOffset = WispHover.TargetOffset(Ror2EnemiesPlugin.WispHoverHeight(), agent.baseOffset, heightAboveGround);
				agent.baseOffset = WispHover.Step(agent.baseOffset, targetOffset, Time.deltaTime);
				return false;
			}
			catch (Exception arg)
			{
				Ror2EnemiesPlugin.LogOnce("wisp-hover", $"RoR2 Enemies: Lesser Wisp hover failed, left the Wyvern hover. {arg}");
				return true;
			}
		}
	}
	[HarmonyPatch]
	internal static class WispVolleyPatch
	{
		private static MethodBase TargetMethod()
		{
			return AccessTools.DeclaredMethod(typeof(ProjectileAttackNoGravity), "SpawnProjectile", Type.EmptyTypes, (Type[])null);
		}

		private static bool Prefix(ProjectileAttackNoGravity __instance)
		{
			//IL_00c5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ca: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00eb: Unknown result type (might be due to invalid IL or missing references)
			//IL_0101: Unknown result type (might be due to invalid IL or missing references)
			//IL_0108: Unknown result type (might be due to invalid IL or missing references)
			//IL_010f: Unknown result type (might be due to invalid IL or missing references)
			//IL_011b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0122: Unknown result type (might be due to invalid IL or missing references)
			//IL_0129: Unknown result type (might be due to invalid IL or missing references)
			//IL_021e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0220: Unknown result type (might be due to invalid IL or missing references)
			//IL_023a: Unknown result type (might be due to invalid IL or missing references)
			//IL_023c: Unknown result type (might be due to invalid IL or missing references)
			bool flag = false;
			try
			{
				if ((Object)(object)__instance == (Object)null)
				{
					return true;
				}
				Ror2Enemy component = ((Component)__instance).GetComponent<Ror2Enemy>();
				if ((Object)(object)component == (Object)null || component.Kind != EnemyKind.LesserWisp)
				{
					return true;
				}
				flag = true;
				if (!LocalClient.serverOwner)
				{
					return false;
				}
				Mob mob = component.Mob;
				if ((Object)(object)mob == (Object)null || (Object)(object)mob.target == (Object)null || (Object)(object)__instance.spawnPos == (Object)null)
				{
					return false;
				}
				Items.Ensure();
				InventoryItem fireball = Items.Fireball;
				if ((Object)(object)fireball == (Object)null || (Object)(object)fireball.bowComponent == (Object)null || (Object)(object)ProjectileController.Instance == (Object)null)
				{
					Ror2EnemiesPlugin.LogOnce("wisp-shot", "RoR2 Enemies: Lesser Wisp could not fire a volley. Left the shot unfired.");
					return false;
				}
				Vector3 position = __instance.spawnPos.position;
				Vector3 val = mob.target.position + Vector3.up * 1f;
				float num = fireball.bowComponent.projectileSpeed * __instance.attackForce;
				VolleyBolt[] array = WispVolley.Plan(new Vec3(position.x, position.y, position.z), new Vec3(val.x, val.y, val.z), Random.Range(-1f, 1f), Random.Range(-1f, 1f), Random.Range(-1f, 1f), Random.Range(-1f, 1f), Random.Range(-1f, 1f), Random.Range(-1f, 1f));
				Vector3 val2 = default(Vector3);
				Vector3 val3 = default(Vector3);
				for (int i = 0; i < array.Length; i++)
				{
					((Vector3)(ref val2))..ctor(array[i].Dir.X, array[i].Dir.Y, array[i].Dir.Z);
					((Vector3)(ref val3))..ctor(array[i].From.X, array[i].From.Y, array[i].From.Z);
					ServerSend.MobSpawnProjectile(val3, val2, num, fireball.id, mob.id);
					ProjectileController.Instance.SpawnMobProjectile(val3, val2, num, fireball.id, mob.id);
				}
				return false;
			}
			catch (Exception arg)
			{
				Ror2EnemiesPlugin.LogOnce("wisp-volley", $"RoR2 Enemies: Lesser Wisp volley failed. {arg}");
				return !flag;
			}
		}
	}
	internal static class PatchGroups
	{
		internal static bool CoreApplied;

		internal static void Apply(Harmony harmony)
		{
			try
			{
				CoreApplied = TryGroup(harmony, "core", typeof(ServerSpawnNewMobPatch), typeof(CheckMobSpawnsContextPatch), typeof(StartBossContextPatch), typeof(ShrineBossContextPatch), typeof(BattleTotemContextPatch), typeof(SpawnMobAdoptPatch), typeof(LootPatch));
				if (!CoreApplied)
				{
					Ror2EnemiesPlugin.Log.LogError((object)"RoR2 Enemies: mod disabled.");
					return;
				}
				KindRegistry.LemurianAvailable = TryGroup(harmony, "Lemurian", typeof(SpawnFxPatch));
				if (!KindRegistry.LemurianAvailable)
				{
					Ror2EnemiesPlugin.Log.LogError((object)"RoR2 Enemies: Lemurian stays a Goblin.");
				}
				bool flag = TryGroup(harmony, "Jellyfish", typeof(MobAttackPatch), typeof(JellyAttackGatePatch), typeof(RagdollPatch));
				KindRegistry.JellyfishAvailable = flag && KindRegistry.LemurianAvailable;
				if (!KindRegistry.JellyfishAvailable)
				{
					Ror2EnemiesPlugin.Log.LogError((object)"RoR2 Enemies: Jellyfish stays a lil Dave.");
				}
				KindRegistry.WispAvailable = TryGroup(harmony, "Wisp", typeof(WispVolleyPatch));
				if (!KindRegistry.WispAvailable)
				{
					Ror2EnemiesPlugin.Log.LogError((object)"RoR2 Enemies: Lesser Wisp stays a lil Dave.");
				}
				if (!TryGroup(harmony, "Wisp hover", typeof(WispHoverPatch)))
				{
					Ror2EnemiesPlugin.Log.LogError((object)"RoR2 Enemies: Lesser Wisps keep the Wyvern hover height.");
				}
				if (!TryGroup(harmony, "FX cleanup", typeof(FxLifetimePatch)))
				{
					Ror2EnemiesPlugin.Log.LogError((object)"RoR2 Enemies: ground attack effects stay in the scene, as in vanilla.");
				}
				KindRegistry.TitanAvailable = TryGroup(harmony, "Titan", typeof(TitanAttackPatch), typeof(StoneFistPatch), typeof(ShrineGetNamePatch), typeof(ShrineServerExecutePatch)) && KindRegistry.LemurianAvailable && flag;
				if (!KindRegistry.TitanAvailable)
				{
					Ror2EnemiesPlugin.Log.LogError((object)"RoR2 Enemies: Stone Titan stays a Big Chunk.");
				}
				if (!TryGroup(harmony, "Titan death", typeof(TitanDeathPatch)))
				{
					Ror2EnemiesPlugin.Log.LogError((object)"RoR2 Enemies: the boss bar closes only through the vanilla path.");
				}
				ApplyWaveRun(harmony);
			}
			catch (Exception arg)
			{
				CoreApplied = false;
				Ror2EnemiesPlugin.Log.LogError((object)$"RoR2 Enemies: hook install failed and the mod is disabled. {arg}");
			}
		}

		private static void ApplyWaveRun(Harmony harmony)
		{
			try
			{
				if (WaveRunContextPatch.HostType() == null)
				{
					Ror2EnemiesPlugin.Log.LogInfo((object)"RoR2 Enemies: Wave Run is not installed. Only vanilla spawns convert.");
				}
				else if (WaveRunContextPatch.TargetMethod() == null)
				{
					Ror2EnemiesPlugin.Log.LogError((object)"RoR2 Enemies: Wave Run's WaveHost.TrySpawn has changed. Wave Run rounds stay vanilla.");
				}
				else if (TryGroup(harmony, "Wave Run", typeof(WaveRunContextPatch)))
				{
					Ror2EnemiesPlugin.Log.LogInfo((object)"RoR2 Enemies: Wave Run rounds and bosses convert too.");
				}
				else
				{
					Ror2EnemiesPlugin.Log.LogError((object)"RoR2 Enemies: Wave Run rounds stay vanilla.");
				}
			}
			catch (Exception arg)
			{
				Ror2EnemiesPlugin.Log.LogError((object)$"RoR2 Enemies: Wave Run hook failed. Wave Run rounds stay vanilla. {arg}");
			}
		}

		private static bool TryGroup(Harmony harmony, string name, params Type[] patchClasses)
		{
			List<MethodBase> list = new List<MethodBase>();
			try
			{
				foreach (Type type in patchClasses)
				{
					MethodInfo methodInfo = AccessTools.Method(type, "TargetMethod", (Type[])null, (Type[])null);
					if (methodInfo == null)
					{
						throw new MissingMethodException(type.Name + ": TargetMethod n