Decompiled source of MonsterDanceBreak v0.2.1

BepInEx/plugins/MonsterDanceBreak/MonsterDanceBreak.dll

Decompiled 16 hours ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using EmotesAPI;
using MonoMod.RuntimeDetour;
using On.RoR2;
using R2API.Networking;
using R2API.Networking.Interfaces;
using R2API.Utils;
using RoR2;
using UnityEngine;
using UnityEngine.Networking;
using UnityEngine.SceneManagement;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyVersion("0.0.0.0")]
namespace MonsterDanceBreak;

public sealed class DanceMessage : INetMessage, ISerializableObject
{
	public NetworkInstanceId BodyId;

	public uint Sequence;

	public string Scene;

	public string Emote;

	public int Variant;

	public int AudioEvent;

	public float Duration;

	public bool Stop;

	public void Serialize(NetworkWriter writer)
	{
		//IL_0002: Unknown result type (might be due to invalid IL or missing references)
		writer.Write(BodyId);
		writer.Write(Sequence);
		writer.Write(Scene);
		writer.Write(Emote);
		writer.Write(Variant);
		writer.Write(AudioEvent);
		writer.Write(Duration);
		writer.Write(Stop);
	}

	public void Deserialize(NetworkReader reader)
	{
		//IL_0002: 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)
		BodyId = reader.ReadNetworkId();
		Sequence = reader.ReadUInt32();
		Scene = reader.ReadString();
		Emote = reader.ReadString();
		Variant = reader.ReadInt32();
		AudioEvent = reader.ReadInt32();
		Duration = reader.ReadSingle();
		Stop = reader.ReadBoolean();
	}

	public void OnReceived()
	{
		if (!NetworkServer.active && NetworkClient.active && Object.op_Implicit((Object)(object)Plugin.Instance))
		{
			Plugin.Instance.Receive(this);
		}
	}
}
internal static class DancePolicy
{
	public const float HardLimit = 60f;

	public static float Bound(float value, float min, float max, float fallback)
	{
		if (!float.IsNaN(value) && !float.IsInfinity(value))
		{
			return Math.Max(min, Math.Min(max, value));
		}
		return fallback;
	}

	public static float Duration(float requested, bool looping, bool hasSecondary, float clipLength)
	{
		float num = Bound(requested, 1f, 60f, 25f);
		if (!(looping || hasSecondary))
		{
			return Math.Min(num, Bound(clipLength, 0.1f, 60f, 1f));
		}
		return num;
	}

	public static int Choose(Random random, int count, int previous)
	{
		if (count <= 0)
		{
			return -1;
		}
		if (count == 1)
		{
			return 0;
		}
		if (previous < 0 || previous >= count)
		{
			return random.Next(count);
		}
		int num = random.Next(count - 1);
		if (num < previous)
		{
			return num;
		}
		return num + 1;
	}
}
internal sealed class DanceLifetime
{
	public readonly float Deadline;

	public readonly float ReadyDeadline;

	public bool Started { get; private set; }

	public DanceLifetime(float now, float duration)
	{
		Deadline = now + DancePolicy.Bound(duration, 0.1f, 60f, 25f);
		ReadyDeadline = now + 2f;
	}

	public void Start()
	{
		Started = true;
	}

	public bool Expired(float now)
	{
		if (!(now >= Deadline))
		{
			if (!Started)
			{
				return now >= ReadyDeadline;
			}
			return false;
		}
		return true;
	}
}
internal static class EnemyAnatomy
{
	internal static readonly string[] BlockedFamilies = new string[54]
	{
		"Wisp", "GreaterWisp", "LunarWisp", "FlyingVermin", "Vulture", "Vagrant", "Jellyfish", "BellBody", "RoboBall", "SuperRoboBall",
		"MagmaWorm", "ElectricWorm", "Scorchling", "AcidLarva", "Vermin", "Bison", "Gup", "Geep", "Gip", "BeetleQueen",
		"ClayBoss", "Gravekeeper", "HermitCrab", "MiniMushroom", "LunarExploder", "Nullifier", "VoidInfestor", "VoidBarnacle", "VoidJailer", "VoidMegaCrab",
		"VoidRaidCrab", "MiniVoidRaidCrab", "MinorConstruct", "MegaConstruct", "BackupDrone", "Drone1", "Drone2", "EmergencyDrone", "EquipmentDrone", "FlameDrone",
		"MegaDrone", "MissileDrone", "DroneCommander", "Turret1", "EngiTurret", "EngiWalkerTurret", "DefectiveUnit", "ExtractorUnit", "IronHauler", "SolusAmalgamator",
		"SolusHeart", "SolusWing", "Tanker", "WorkerUnit"
	};

	internal static string Normalize(string name)
	{
		return (name ?? "").Replace("(Clone)", "").Trim();
	}

	internal static bool Blocked(string bodyName, bool flying, string extra)
	{
		string text = Normalize(bodyName);
		if (flying || text.Length == 0)
		{
			return true;
		}
		string[] blockedFamilies = BlockedFamilies;
		foreach (string value in blockedFamilies)
		{
			if (text.StartsWith(value, StringComparison.OrdinalIgnoreCase))
			{
				return true;
			}
		}
		blockedFamilies = (extra ?? "").Split(';');
		for (int i = 0; i < blockedFamilies.Length; i++)
		{
			string text2 = Normalize(blockedFamilies[i]);
			if (text2.EndsWith("*", StringComparison.Ordinal))
			{
				if (text.StartsWith(text2.Substring(0, text2.Length - 1), StringComparison.OrdinalIgnoreCase))
				{
					return true;
				}
			}
			else if (string.Equals(text2, text, StringComparison.OrdinalIgnoreCase))
			{
				return true;
			}
		}
		return false;
	}
}
internal sealed class PingBudget
{
	private float next;

	private readonly Dictionary<uint, float> ready = new Dictionary<uint, float>();

	public bool CanStart(uint id, float now, bool alreadyDancing)
	{
		if (id != 0 && !alreadyDancing && now >= next)
		{
			if (ready.TryGetValue(id, out var value))
			{
				return now >= value;
			}
			return true;
		}
		return false;
	}

	public void Commit(uint id, float now, float deadline)
	{
		next = now + 1f;
		ready[id] = deadline + 12f;
	}

	public void Clear()
	{
		next = 0f;
		ready.Clear();
	}
}
internal sealed class CrowdMemory
{
	private float lastCrowd = float.NegativeInfinity;

	private bool attempted;

	public bool Observe(int livingNearbyIncludingSelf, float now)
	{
		if (livingNearbyIncludingSelf >= 3)
		{
			lastCrowd = now;
		}
		if (attempted || livingNearbyIncludingSelf != 1 || now - lastCrowd > 10f)
		{
			return false;
		}
		attempted = true;
		return true;
	}
}
[BepInPlugin("com.rodanius.monsterdancebreak", "Monster Dance Break", "0.2.1")]
[BepInDependency("com.weliveinasociety.CustomEmotesAPI", "2.11.0")]
[BepInDependency(/*Could not decode attribute arguments.*/)]
[NetworkCompatibility(/*Could not decode attribute arguments.*/)]
public sealed class Plugin : BaseUnityPlugin
{
	private sealed class Dance
	{
		public DanceMessage Message;

		public DanceLifetime Lifetime;

		public CharacterBody Body;

		public BoneMapper Mapper;

		public CustomAnimationClip Clip;
	}

	private sealed class Entrance
	{
		public CharacterBody Body;

		public float Deadline;

		public bool Boss;
	}

	private sealed class Reaction
	{
		public Vector3 Origin;

		public float Radius;

		public float Deadline;

		public string Reason;

		public string Emote;

		public int Maximum;

		public CharacterBody Target;

		public BoneMapper PlayerMapper;
	}

	public const string Guid = "com.rodanius.monsterdancebreak";

	internal static Plugin Instance;

	private readonly Random random = new Random();

	private readonly Dictionary<uint, Dance> dances = new Dictionary<uint, Dance>();

	private readonly Dictionary<uint, uint> received = new Dictionary<uint, uint>();

	private readonly HashSet<uint> danced = new HashSet<uint>();

	private ConfigEntry<bool> modEnabled;

	private ConfigEntry<bool> lineOfSight;

	private ConfigEntry<bool> allowInteractive;

	private ConfigEntry<bool> diagnostics;

	private ConfigEntry<float> intervalMin;

	private ConfigEntry<float> intervalMax;

	private ConfigEntry<float> durationMin;

	private ConfigEntry<float> durationMax;

	private ConfigEntry<float> radius;

	private ConfigEntry<float> groupChance;

	private ConfigEntry<float> groupRadius;

	private ConfigEntry<string> excluded;

	private ConfigEntry<string> excludedBodies;

	private float nextEvent;

	private float nextDiagnostic;

	private uint sequence;

	private string previousEmote;

	private ConfigEntry<bool> pingEnabled;

	private ConfigEntry<float> spawnChance;

	private ConfigEntry<float> bossChance;

	private ConfigEntry<float> shrineChance;

	private ConfigEntry<float> encoreChance;

	private ConfigEntry<float> lastStandChance;

	private ConfigEntry<float> afterpartyChance;

	private ConfigEntry<float> tauntChance;

	private ConfigEntry<float> quietMin;

	private ConfigEntry<float> quietMax;

	private readonly List<Entrance> entrances = new List<Entrance>();

	private readonly Dictionary<CharacterBody, CrowdMemory> crowds = new Dictionary<CharacterBody, CrowdMemory>();

	private readonly Dictionary<uint, float> hitChecks = new Dictionary<uint, float>();

	private readonly PingBudget pingBudget = new PingBudget();

	private float nextAutomatic;

	private float nextEntranceCheck;

	private float nextCrowdCheck;

	private Reaction reaction;

	private bool HostReady
	{
		get
		{
			if (Object.op_Implicit((Object)(object)Run.instance) && NetworkServer.active && NetworkClient.active && modEnabled.Value)
			{
				return !PauseManager.isPaused;
			}
			return false;
		}
	}

	private bool AutomaticReady
	{
		get
		{
			if (HostReady && dances.Count == 0)
			{
				return Time.time >= nextAutomatic;
			}
			return false;
		}
	}

	private void Awake()
	{
		SolusWingGuard.Install(((BaseUnityPlugin)this).Logger);
		Instance = this;
		modEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Enabled", true, "Host controls all dance scenarios. Turning off ends current events.");
		intervalMin = ((BaseUnityPlugin)this).Config.Bind<float>("Timing", "AmbientMinimumQuietSeconds", 90f, "Rare mid-combat fallback delay after an event ends. Spawn entrances are the main trigger.");
		intervalMax = ((BaseUnityPlugin)this).Config.Bind<float>("Timing", "AmbientMaximumQuietSeconds", 150f, "Maximum randomized quiet time after an event ends.");
		durationMin = ((BaseUnityPlugin)this).Config.Bind<float>("Timing", "MinimumDanceSeconds", 20f, "Looping emote minimum; short emotes finish naturally.");
		durationMax = ((BaseUnityPlugin)this).Config.Bind<float>("Timing", "MaximumDanceSeconds", 45f, "Looping emote maximum. Absolute safety limit is 60 seconds.");
		radius = ((BaseUnityPlugin)this).Config.Bind<float>("Selection", "PlayerDistance", 60f, "Maximum distance from a living player.");
		lineOfSight = ((BaseUnityPlugin)this).Config.Bind<bool>("Selection", "RequireLineOfSight", true, "Prefer moments a player could see; checks world geometry, not camera direction.");
		groupChance = ((BaseUnityPlugin)this).Config.Bind<float>("Groups", "Chance", 0.25f, "Chance to include nearby enemies. Everyone uses the same random emote and variant.");
		groupRadius = ((BaseUnityPlugin)this).Config.Bind<float>("Groups", "NeighborDistance", 16f, "Neighbors of the first dancer; maximum three dancers per event.");
		allowInteractive = ((BaseUnityPlugin)this).Config.Bind<bool>("Selection", "IncludeInteractiveEmotes", false, "Include emotes with partner join spots. Experimental: some callbacks move or alter characters.");
		excluded = ((BaseUnityPlugin)this).Config.Bind<string>("Selection", "ExcludedEmotes", "", "Exact registered emote names separated by semicolons.");
		diagnostics = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Diagnostics", true, "Log events and occasional reasons no candidate was available.");
		excludedBodies = ((BaseUnityPlugin)this).Config.Bind<string>("Selection", "ExcludedBodies", "", "Additional body prefab IDs separated by semicolons; a trailing * matches a family. Built-in anatomy and flying exclusions always apply.");
		ConfigureScenarios();
		NetworkingAPI.RegisterMessageType<DanceMessage>();
		SceneManager.activeSceneChanged += SceneChanged;
		Run.onRunDestroyGlobal += RunEnded;
		SubscribeScenarios();
		ResetSchedule();
		((BaseUnityPlugin)this).Logger.LogInfo((object)"MDB_READY 0.2.1: entrances, ping spotlight and contextual moments; anatomy blacklist active.");
	}

	private float Range(float low, float high, float floor, float ceiling, float fallback)
	{
		low = DancePolicy.Bound(low, floor, ceiling, fallback);
		high = DancePolicy.Bound(high, low, ceiling, low);
		return low + (float)random.NextDouble() * (high - low);
	}

	private void ResetSchedule()
	{
		nextAutomatic = Time.time;
		nextEvent = Time.time + Range(intervalMin.Value, intervalMax.Value, 60f, 600f, 90f);
	}

	private void ScheduleQuiet()
	{
		nextAutomatic = Time.time + Range(quietMin.Value, quietMax.Value, 10f, 120f, 20f);
		nextEvent = Time.time + Range(intervalMin.Value, intervalMax.Value, 60f, 600f, 90f);
	}

	private void SceneChanged(Scene oldScene, Scene newScene)
	{
		Clear(broadcast: false);
		danced.Clear();
		received.Clear();
		ClearScenarios();
		ResetSchedule();
	}

	private void RunEnded(Run run)
	{
		Clear(broadcast: false);
		danced.Clear();
		received.Clear();
		ClearScenarios();
	}

	private void Update()
	{
		if (!Object.op_Implicit((Object)(object)Run.instance))
		{
			if (dances.Count > 0)
			{
				Clear(broadcast: false);
			}
		}
		else
		{
			if (PauseManager.isPaused)
			{
				return;
			}
			uint[] array = dances.Keys.ToArray();
			foreach (uint num in array)
			{
				Dance dance = dances[num];
				try
				{
					if (dance.Lifetime.Expired(Time.time) || (NetworkServer.active && !modEnabled.Value))
					{
						End(num, broadcast: true);
					}
					else if (!dance.Lifetime.Started)
					{
						TryStart(dance);
					}
					else if (!AliveEnemy(dance.Body) || !Object.op_Implicit((Object)(object)dance.Mapper) || dance.Mapper.ragdolling)
					{
						End(num, broadcast: true);
					}
					else if (dance.Mapper.currentClip != dance.Clip || dance.Mapper.currentClipName != dance.Message.Emote)
					{
						End(num, broadcast: true);
					}
				}
				catch (Exception ex)
				{
					((BaseUnityPlugin)this).Logger.LogWarning((object)("MDB_END_ERROR " + ex));
					End(num, broadcast: true);
				}
			}
			if (!NetworkServer.active || !modEnabled.Value)
			{
				return;
			}
			if (NetworkClient.active)
			{
				try
				{
					UpdateScenarios();
					if (entrances.Count == 0 && AutomaticReady && Time.time >= nextEvent)
					{
						SelectEvent();
					}
					return;
				}
				catch (Exception ex2)
				{
					((BaseUnityPlugin)this).Logger.LogWarning((object)("MDB_SELECTION_ERROR " + ex2));
					ScheduleQuiet();
					return;
				}
			}
			nextEvent = Time.time + 60f;
		}
	}

	private static bool AliveEnemy(CharacterBody body)
	{
		//IL_0060: 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_0066: Unknown result type (might be due to invalid IL or missing references)
		//IL_0068: Invalid comparison between Unknown and I4
		//IL_006a: Unknown result type (might be due to invalid IL or missing references)
		//IL_006c: Invalid comparison between Unknown and I4
		//IL_006e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0070: Invalid comparison between Unknown and I4
		if (!Object.op_Implicit((Object)(object)body) || !Object.op_Implicit((Object)(object)body.healthComponent) || !body.healthComponent.alive || body.isPlayerControlled || !Object.op_Implicit((Object)(object)body.teamComponent) || !Object.op_Implicit((Object)(object)body.master))
		{
			return false;
		}
		if (Object.op_Implicit((Object)(object)body.master.playerCharacterMasterController))
		{
			return false;
		}
		TeamIndex teamIndex = body.teamComponent.teamIndex;
		if ((int)teamIndex != 2 && (int)teamIndex != 3)
		{
			return (int)teamIndex == 4;
		}
		return true;
	}

	private static bool ReadyMapper(CharacterBody body, out BoneMapper mapper)
	{
		mapper = null;
		if (!Object.op_Implicit((Object)(object)body) || !Object.op_Implicit((Object)(object)body.modelLocator) || !Object.op_Implicit((Object)(object)body.modelLocator.modelTransform))
		{
			return false;
		}
		mapper = ((Component)body.modelLocator.modelTransform).GetComponentInChildren<BoneMapper>();
		if (Object.op_Implicit((Object)(object)mapper) && ((Behaviour)mapper).isActiveAndEnabled && (Object)(object)mapper.mapperBody == (Object)(object)body && Object.op_Implicit((Object)(object)mapper.a1) && Object.op_Implicit((Object)(object)mapper.a2) && Object.op_Implicit((Object)(object)mapper.a2.runtimeAnimatorController))
		{
			return !mapper.ragdolling;
		}
		return false;
	}

	private bool NearPlayer(CharacterBody body)
	{
		//IL_0068: Unknown result type (might be due to invalid IL or missing references)
		//IL_006e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0073: Unknown result type (might be due to invalid IL or missing references)
		//IL_0078: Unknown result type (might be due to invalid IL or missing references)
		//IL_0093: Unknown result type (might be due to invalid IL or missing references)
		//IL_0099: Unknown result type (might be due to invalid IL or missing references)
		//IL_009e: Unknown result type (might be due to invalid IL or missing references)
		//IL_00a3: Unknown result type (might be due to invalid IL or missing references)
		//IL_00a7: Unknown result type (might be due to invalid IL or missing references)
		float num = DancePolicy.Bound(radius.Value, 5f, 150f, 60f);
		foreach (TeamComponent teamMember in TeamComponent.GetTeamMembers((TeamIndex)1))
		{
			CharacterBody body2 = teamMember.body;
			if (!Object.op_Implicit((Object)(object)body2) || !body2.isPlayerControlled || !Object.op_Implicit((Object)(object)body2.healthComponent) || !body2.healthComponent.alive)
			{
				continue;
			}
			Vector3 val = body2.corePosition - body.corePosition;
			if (((Vector3)(ref val)).sqrMagnitude > num * num)
			{
				continue;
			}
			if (lineOfSight.Value)
			{
				Vector3 corePosition = body2.corePosition;
				Vector3 corePosition2 = body.corePosition;
				LayerIndex world = LayerIndex.world;
				if (Physics.Linecast(corePosition, corePosition2, LayerMask.op_Implicit(((LayerIndex)(ref world)).mask), (QueryTriggerInteraction)1))
				{
					continue;
				}
			}
			return true;
		}
		return false;
	}

	private bool AllowedBody(CharacterBody body)
	{
		if (Object.op_Implicit((Object)(object)body))
		{
			return !EnemyAnatomy.Blocked(((Object)((Component)body).gameObject).name, body.isFlying, excludedBodies.Value);
		}
		return false;
	}

	private bool Candidate(CharacterBody body, bool ping = false)
	{
		//IL_0023: Unknown result type (might be due to invalid IL or missing references)
		//IL_0028: Unknown result type (might be due to invalid IL or missing references)
		//IL_0039: Unknown result type (might be due to invalid IL or missing references)
		//IL_003e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0057: 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)
		if (!AliveEnemy(body) || !AllowedBody(body))
		{
			return false;
		}
		NetworkIdentity component = ((Component)body).GetComponent<NetworkIdentity>();
		if (Object.op_Implicit((Object)(object)component))
		{
			NetworkInstanceId netId = component.netId;
			if (((NetworkInstanceId)(ref netId)).Value != 0)
			{
				Dictionary<uint, Dance> dictionary = dances;
				netId = component.netId;
				if (!dictionary.ContainsKey(((NetworkInstanceId)(ref netId)).Value))
				{
					if (!ping)
					{
						HashSet<uint> hashSet = danced;
						netId = component.netId;
						if (hashSet.Contains(((NetworkInstanceId)(ref netId)).Value))
						{
							goto IL_006b;
						}
					}
					if (!ReadyMapper(body, out var mapper) || mapper.currentClip != null)
					{
						return false;
					}
					if (!ping)
					{
						return NearPlayer(body);
					}
					return true;
				}
			}
		}
		goto IL_006b;
		IL_006b:
		return false;
	}

	private List<string> Emotes()
	{
		HashSet<string> hashSet = new HashSet<string>(from s in (excluded.Value ?? "").Split(';')
			select s.Trim(), StringComparer.OrdinalIgnoreCase);
		List<string> list = new List<string>();
		for (int num = 0; num < CustomEmotesAPI.allClipNames.Count; num++)
		{
			string text = CustomEmotesAPI.allClipNames[num];
			if (!(text == "none") && !hashSet.Contains(text) && !CustomEmotesAPI.blacklistedClips.Contains(num) && BoneMapper.animClips.TryGetValue(text, out var value) && value != null && value.visibility && ValidVariants(value).Count != 0 && (allowInteractive.Value || value.joinSpots == null || value.joinSpots.Length == 0) && !list.Contains(text))
			{
				list.Add(text);
			}
		}
		return list;
	}

	private static List<int> ValidVariants(CustomAnimationClip clip)
	{
		List<int> list = new List<int>();
		if (clip == null || clip.clip == null)
		{
			return list;
		}
		for (int i = 0; i < clip.clip.Length; i++)
		{
			if (Object.op_Implicit((Object)(object)clip.clip[i]) && (clip.secondaryClip == null || clip.secondaryClip.Length == 0 || (i < clip.secondaryClip.Length && Object.op_Implicit((Object)(object)clip.secondaryClip[i]))))
			{
				list.Add(i);
			}
		}
		return list;
	}

	private List<CharacterBody> Candidates()
	{
		return CharacterBody.readOnlyInstancesList.Where((CharacterBody b) => Candidate(b)).ToList();
	}

	private List<CharacterBody> Group(CharacterBody leader, IEnumerable<CharacterBody> pool, float distance, int maximum = 3)
	{
		List<CharacterBody> list = new List<CharacterBody> { leader };
		List<CharacterBody> list2 = pool.Where(delegate(CharacterBody b)
		{
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			//IL_0024: 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_0037: Unknown result type (might be due to invalid IL or missing references)
			//IL_003c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0041: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)b != (Object)(object)leader && b.teamComponent.teamIndex == leader.teamComponent.teamIndex)
			{
				Vector3 val = b.corePosition - leader.corePosition;
				return ((Vector3)(ref val)).sqrMagnitude <= distance * distance;
			}
			return false;
		}).ToList();
		while (list.Count < maximum && list2.Count > 0)
		{
			int index = random.Next(list2.Count);
			list.Add(list2[index]);
			list2.RemoveAt(index);
		}
		return list;
	}

	private void SelectEvent()
	{
		List<CharacterBody> list = Candidates();
		if (list.Count == 0)
		{
			nextEvent = Time.time + 5f;
			if (diagnostics.Value && Time.time >= nextDiagnostic)
			{
				((BaseUnityPlugin)this).Logger.LogInfo((object)"MDB_WAIT no visible eligible ground enemy ready");
				nextDiagnostic = Time.time + 60f;
			}
			return;
		}
		CharacterBody val = list[random.Next(list.Count)];
		List<CharacterBody> bodies = ((random.NextDouble() < (double)DancePolicy.Bound(groupChance.Value, 0f, 1f, 0.25f)) ? Group(val, list, DancePolicy.Bound(groupRadius.Value, 2f, 40f, 16f)) : new List<CharacterBody> { val });
		if (!StartEvent(bodies, "spontaneous"))
		{
			nextEvent = Time.time + 5f;
		}
	}

	private bool StartEvent(IEnumerable<CharacterBody> bodies, string reason, string coordinatedEmote = null, bool ping = false)
	{
		//IL_0184: Unknown result type (might be due to invalid IL or missing references)
		//IL_0189: Unknown result type (might be due to invalid IL or missing references)
		//IL_01a9: Unknown result type (might be due to invalid IL or missing references)
		//IL_01ae: Unknown result type (might be due to invalid IL or missing references)
		List<string> list = Emotes();
		if (list.Count == 0 || (coordinatedEmote != null && !list.Contains(coordinatedEmote)))
		{
			return false;
		}
		string text = coordinatedEmote ?? list[DancePolicy.Choose(random, list.Count, list.IndexOf(previousEmote))];
		CustomAnimationClip val = BoneMapper.animClips[text];
		List<int> list2 = ValidVariants(val);
		if (list2.Count == 0)
		{
			return false;
		}
		int num = list2[random.Next(list2.Count)];
		string[] array = ((val.syncPos >= 0 && val.syncPos < BoneMapper.startEvents.Count) ? BoneMapper.startEvents[val.syncPos] : null);
		int audioEvent = ((array != null && array.Length != 0) ? random.Next(array.Length) : 0);
		bool hasSecondary = val.secondaryClip != null && val.secondaryClip.Length > num && Object.op_Implicit((Object)(object)val.secondaryClip[num]);
		float num2 = DancePolicy.Duration(Range(durationMin.Value, durationMax.Value, 1f, 60f, 25f), val.looping, hasSecondary, val.clip[num].length);
		int num3 = 0;
		foreach (CharacterBody item in bodies.Take(3))
		{
			if (!Candidate(item, ping) || dances.Count >= 3)
			{
				continue;
			}
			DanceMessage obj = new DanceMessage
			{
				BodyId = ((Component)item).GetComponent<NetworkIdentity>().netId,
				Sequence = ++sequence
			};
			Scene activeScene = SceneManager.GetActiveScene();
			obj.Scene = ((Scene)(ref activeScene)).name;
			obj.Emote = text;
			obj.Variant = num;
			obj.AudioEvent = audioEvent;
			obj.Duration = num2;
			DanceMessage danceMessage = obj;
			Dance dance = new Dance
			{
				Message = danceMessage,
				Lifetime = new DanceLifetime(Time.time, num2),
				Body = item
			};
			dances.Add(((NetworkInstanceId)(ref danceMessage.BodyId)).Value, dance);
			try
			{
				if (!TryStart(dance))
				{
					End(((NetworkInstanceId)(ref danceMessage.BodyId)).Value, broadcast: false);
					continue;
				}
				danced.Add(((NetworkInstanceId)(ref danceMessage.BodyId)).Value);
				NetMessageExtensions.Send((INetMessage)(object)danceMessage, (NetworkDestination)1);
				if (ping)
				{
					pingBudget.Commit(((NetworkInstanceId)(ref danceMessage.BodyId)).Value, Time.time, dance.Lifetime.Deadline);
				}
				num3++;
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)("MDB_START_ERROR " + ex));
				End(((NetworkInstanceId)(ref danceMessage.BodyId)).Value, broadcast: true);
			}
		}
		if (num3 == 0)
		{
			return false;
		}
		previousEmote = text;
		if (diagnostics.Value)
		{
			((BaseUnityPlugin)this).Logger.LogInfo((object)$"MDB_EVENT scenario={reason} count={num3} emote={text} variant={num} seconds={num2:F1}");
		}
		return true;
	}

	internal void Receive(DanceMessage packet)
	{
		//IL_0012: Unknown result type (might be due to invalid IL or missing references)
		//IL_0017: Unknown result type (might be due to invalid IL or missing references)
		if (!Object.op_Implicit((Object)(object)Run.instance))
		{
			return;
		}
		string scene = packet.Scene;
		Scene activeScene = SceneManager.GetActiveScene();
		if (scene != ((Scene)(ref activeScene)).name || ((NetworkInstanceId)(ref packet.BodyId)).Value == 0)
		{
			return;
		}
		uint value = ((NetworkInstanceId)(ref packet.BodyId)).Value;
		if (!received.TryGetValue(value, out var value2) || packet.Sequence > value2)
		{
			received[value] = packet.Sequence;
			if (packet.Stop)
			{
				End(value, broadcast: false);
			}
			else if (!string.IsNullOrEmpty(packet.Emote) && packet.Emote.Length <= 256 && !(packet.Duration <= 0f) && !float.IsNaN(packet.Duration) && !float.IsInfinity(packet.Duration))
			{
				End(value, broadcast: false);
				dances[value] = new Dance
				{
					Message = packet,
					Lifetime = new DanceLifetime(Time.time, packet.Duration)
				};
			}
		}
	}

	private bool TryStart(Dance dance)
	{
		//IL_0015: Unknown result type (might be due to invalid IL or missing references)
		DanceMessage message = dance.Message;
		if (!Object.op_Implicit((Object)(object)dance.Body))
		{
			GameObject val = Util.FindNetworkObject(message.BodyId);
			if (Object.op_Implicit((Object)(object)val))
			{
				dance.Body = val.GetComponent<CharacterBody>();
			}
		}
		if (!AliveEnemy(dance.Body) || !AllowedBody(dance.Body) || !ReadyMapper(dance.Body, out var mapper))
		{
			return false;
		}
		if (mapper.currentClip != null)
		{
			return false;
		}
		if (!BoneMapper.animClips.TryGetValue(message.Emote, out var value) || value == null || value.clip == null || !ValidVariants(value).Contains(message.Variant))
		{
			return false;
		}
		dance.Mapper = mapper;
		dance.Clip = value;
		dance.Lifetime.Start();
		mapper.PlayAnim(message.Emote, message.Variant, message.AudioEvent);
		return true;
	}

	private void End(uint id, bool broadcast)
	{
		//IL_00bf: Unknown result type (might be due to invalid IL or missing references)
		//IL_00c4: Unknown result type (might be due to invalid IL or missing references)
		if (!dances.TryGetValue(id, out var value))
		{
			return;
		}
		dances.Remove(id);
		try
		{
			if (value.Lifetime.Started && Object.op_Implicit((Object)(object)value.Mapper) && value.Mapper.currentClip == value.Clip && value.Mapper.currentClipName == value.Message.Emote)
			{
				value.Mapper.PlayAnim("none", 0, 0);
			}
		}
		catch (Exception ex)
		{
			((BaseUnityPlugin)this).Logger.LogWarning((object)("MDB_CLEANUP_ERROR " + ex));
		}
		finally
		{
			if (NetworkServer.active && broadcast)
			{
				DanceMessage danceMessage = new DanceMessage
				{
					BodyId = value.Message.BodyId,
					Sequence = ++sequence,
					Scene = value.Message.Scene,
					Emote = "",
					Stop = true
				};
				try
				{
					NetMessageExtensions.Send((INetMessage)(object)danceMessage, (NetworkDestination)1);
				}
				catch (Exception ex2)
				{
					((BaseUnityPlugin)this).Logger.LogWarning((object)("MDB_STOP_SEND_ERROR " + ex2));
				}
			}
			if (NetworkServer.active && dances.Count == 0)
			{
				ScheduleQuiet();
			}
		}
	}

	private void Clear(bool broadcast)
	{
		uint[] array = dances.Keys.ToArray();
		foreach (uint id in array)
		{
			End(id, broadcast);
		}
	}

	private void OnDestroy()
	{
		Clear(broadcast: true);
		UnsubscribeScenarios();
		ClearScenarios();
		SceneManager.activeSceneChanged -= SceneChanged;
		Run.onRunDestroyGlobal -= RunEnded;
		if ((Object)(object)Instance == (Object)(object)this)
		{
			Instance = null;
		}
	}

	private bool Roll(ConfigEntry<float> chance)
	{
		return random.NextDouble() < (double)DancePolicy.Bound(chance.Value, 0f, 1f, 0f);
	}

	private void ConfigureScenarios()
	{
		pingEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("Scenarios", "PingSpotlight", true, "Ping an eligible enemy to make it dance. Does not extend active dances.");
		spawnChance = ((BaseUnityPlugin)this).Config.Bind<float>("Scenarios", "EntranceChance", 0.35f, "One roll per newly spawned enemy, with a 3-second rig readiness window. Main automatic trigger.");
		bossChance = ((BaseUnityPlugin)this).Config.Bind<float>("Scenarios", "BossEntranceChance", 0.65f, "A newly spawned eligible boss may arrive with up to two backing dancers.");
		shrineChance = ((BaseUnityPlugin)this).Config.Bind<float>("Scenarios", "ShrineHecklerChance", 0.6f, "Nearby monster reacts to a failed Shrine of Chance purchase.");
		encoreChance = ((BaseUnityPlugin)this).Config.Bind<float>("Scenarios", "PlayerEncoreChance", 0.4f, "Nearby monsters join a player's compatible emote.");
		lastStandChance = ((BaseUnityPlugin)this).Config.Bind<float>("Scenarios", "LastStandingChance", 0.4f, "A recently outnumbering monster dances when it becomes the sole nearby survivor.");
		afterpartyChance = ((BaseUnityPlugin)this).Config.Bind<float>("Scenarios", "TeleporterAfterpartyChance", 0.75f, "Surviving monsters near a fully charged teleporter may dance together.");
		tauntChance = ((BaseUnityPlugin)this).Config.Bind<float>("Scenarios", "DamageTauntChance", 0.06f, "Rare taunt after hurting a living player. Direct hits only; at most one roll per attacker per 5 seconds.");
		quietMin = ((BaseUnityPlugin)this).Config.Bind<float>("Timing", "AutomaticMinimumQuietSeconds", 20f, "Shared quiet period after all dancers finish; applies to every automatic scenario.");
		quietMax = ((BaseUnityPlugin)this).Config.Bind<float>("Timing", "AutomaticMaximumQuietSeconds", 35f, "Maximum shared automatic quiet period. Pings alone bypass this wait.");
	}

	private void SubscribeScenarios()
	{
		//IL_003a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0044: Expected O, but got Unknown
		//IL_005c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0066: Expected O, but got Unknown
		CharacterBody.onBodyStartGlobal += BodyStarted;
		ShrineChanceBehavior.onShrineChancePurchaseGlobal += ShrinePurchased;
		TeleporterInteraction.onTeleporterChargedGlobal += TeleporterCharged;
		CustomEmotesAPI.animChanged += new AnimationChanged(PlayerEmoted);
		GlobalEventManager.onServerDamageDealt += DamageDealt;
		PingerController.CmdPing += new hook_CmdPing(ObservePing);
	}

	private void UnsubscribeScenarios()
	{
		//IL_003a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0044: Expected O, but got Unknown
		//IL_005c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0066: Expected O, but got Unknown
		CharacterBody.onBodyStartGlobal -= BodyStarted;
		ShrineChanceBehavior.onShrineChancePurchaseGlobal -= ShrinePurchased;
		TeleporterInteraction.onTeleporterChargedGlobal -= TeleporterCharged;
		CustomEmotesAPI.animChanged -= new AnimationChanged(PlayerEmoted);
		GlobalEventManager.onServerDamageDealt -= DamageDealt;
		PingerController.CmdPing -= new hook_CmdPing(ObservePing);
	}

	private void ClearScenarios()
	{
		entrances.Clear();
		crowds.Clear();
		hitChecks.Clear();
		pingBudget.Clear();
		reaction = null;
		nextEntranceCheck = (nextCrowdCheck = 0f);
	}

	private void BodyStarted(CharacterBody body)
	{
		ObserveSafely(delegate
		{
			if (HostReady && Object.op_Implicit((Object)(object)body) && AllowedBody(body) && !body.isPlayerControlled)
			{
				bool flag = body.isBoss || body.isChampion;
				if (Roll(flag ? bossChance : spawnChance) && AutomaticReady)
				{
					entrances.Add(new Entrance
					{
						Body = body,
						Boss = flag,
						Deadline = Time.time + 3f
					});
				}
			}
		});
	}

	private void UpdateScenarios()
	{
		if (Time.time >= nextEntranceCheck)
		{
			nextEntranceCheck = Time.time + 0.1f;
			entrances.RemoveAll((Entrance e) => !Object.op_Implicit((Object)(object)e.Body) || Time.time >= e.Deadline);
			if (AutomaticReady)
			{
				List<Entrance> source = entrances.Where((Entrance e) => Candidate(e.Body)).ToList();
				Entrance entrance = source.FirstOrDefault((Entrance e) => e.Boss) ?? source.FirstOrDefault();
				if (entrance != null)
				{
					List<CharacterBody> bodies = new List<CharacterBody> { entrance.Body };
					if (entrance.Boss || Roll(groupChance))
					{
						CharacterBody body = entrance.Body;
						IEnumerable<CharacterBody> pool;
						if (!entrance.Boss)
						{
							pool = source.Select((Entrance e) => e.Body);
						}
						else
						{
							IEnumerable<CharacterBody> enumerable = Candidates();
							pool = enumerable;
						}
						bodies = Group(body, pool, DancePolicy.Bound(groupRadius.Value, 2f, 40f, 16f));
					}
					StartEvent(bodies, entrance.Boss ? "opening-act" : "entrance");
					entrances.Clear();
				}
			}
			else
			{
				entrances.Clear();
			}
		}
		if (entrances.Count > 0 && AutomaticReady)
		{
			return;
		}
		if (reaction != null)
		{
			Reaction pending = reaction;
			reaction = null;
			if (AutomaticReady && Time.time <= pending.Deadline && (pending.PlayerMapper == null || (Object.op_Implicit((Object)(object)pending.PlayerMapper) && LivingPlayer(pending.PlayerMapper.mapperBody) && pending.PlayerMapper.currentClipName == pending.Emote)))
			{
				List<CharacterBody> list = Candidates().Where(delegate(CharacterBody b)
				{
					//IL_0001: 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_0016: Unknown result type (might be due to invalid IL or missing references)
					Vector3 val2 = b.corePosition - pending.Origin;
					return ((Vector3)(ref val2)).sqrMagnitude <= pending.Radius * pending.Radius;
				}).ToList();
				CharacterBody val = ((!Object.op_Implicit((Object)(object)pending.Target)) ? ((list.Count > 0) ? list[random.Next(list.Count)] : null) : (list.Contains(pending.Target) ? pending.Target : null));
				if (Object.op_Implicit((Object)(object)val))
				{
					StartEvent(Group(val, list, pending.Radius, pending.Maximum), pending.Reason, pending.Emote);
				}
			}
		}
		if (Time.time < nextCrowdCheck)
		{
			return;
		}
		nextCrowdCheck = Time.time + 1f;
		CharacterBody[] array = crowds.Keys.Where((CharacterBody b) => !Object.op_Implicit((Object)(object)b) || !AliveEnemy(b)).ToArray();
		foreach (CharacterBody key in array)
		{
			crowds.Remove(key);
		}
		List<CharacterBody> list2 = CharacterBody.readOnlyInstancesList.Where(AliveEnemy).ToList();
		foreach (CharacterBody body2 in list2)
		{
			if (!AllowedBody(body2) || !NearPlayer(body2))
			{
				continue;
			}
			if (!crowds.TryGetValue(body2, out var value))
			{
				value = (crowds[body2] = new CrowdMemory());
			}
			int livingNearbyIncludingSelf = list2.Count(delegate(CharacterBody b)
			{
				//IL_0006: Unknown result type (might be due to invalid IL or missing references)
				//IL_0016: Unknown result type (might be due to invalid IL or missing references)
				//IL_001e: Unknown result type (might be due to invalid IL or missing references)
				//IL_0029: Unknown result type (might be due to invalid IL or missing references)
				//IL_002e: Unknown result type (might be due to invalid IL or missing references)
				//IL_0033: Unknown result type (might be due to invalid IL or missing references)
				if (b.teamComponent.teamIndex == body2.teamComponent.teamIndex)
				{
					Vector3 val2 = b.corePosition - body2.corePosition;
					return ((Vector3)(ref val2)).sqrMagnitude <= 400f;
				}
				return false;
			});
			if (value.Observe(livingNearbyIncludingSelf, Time.time) && AutomaticReady && Candidate(body2) && Roll(lastStandChance))
			{
				StartEvent((IEnumerable<CharacterBody>)(object)new CharacterBody[1] { body2 }, "last-one-standing");
			}
		}
	}

	private void QueueReaction(Vector3 origin, float distance, string reason, int maximum = 1, string emote = null, CharacterBody target = null, BoneMapper playerMapper = null)
	{
		//IL_0018: Unknown result type (might be due to invalid IL or missing references)
		//IL_0019: Unknown result type (might be due to invalid IL or missing references)
		if (AutomaticReady && reaction == null)
		{
			reaction = new Reaction
			{
				Origin = origin,
				Radius = distance,
				Reason = reason,
				Maximum = maximum,
				Emote = emote,
				Target = target,
				PlayerMapper = playerMapper,
				Deadline = Time.time + 1f
			};
		}
	}

	private void ShrinePurchased(bool failed, Interactor interactor)
	{
		ObserveSafely(delegate
		{
			//IL_0056: Unknown result type (might be due to invalid IL or missing references)
			if (failed && AutomaticReady && Object.op_Implicit((Object)(object)interactor) && Roll(shrineChance))
			{
				CharacterBody component = ((Component)interactor).GetComponent<CharacterBody>();
				if (LivingPlayer(component))
				{
					QueueReaction(component.corePosition, 30f, "shrine-heckler");
				}
			}
		});
	}

	private void TeleporterCharged(TeleporterInteraction teleporter)
	{
		ObserveSafely(delegate
		{
			//IL_0043: Unknown result type (might be due to invalid IL or missing references)
			if (AutomaticReady && Object.op_Implicit((Object)(object)teleporter) && Roll(afterpartyChance))
			{
				QueueReaction(((Component)teleporter).transform.position, 40f, "teleporter-afterparty", 3);
			}
		});
	}

	private void PlayerEmoted(string name, BoneMapper mapper)
	{
		ObserveSafely(delegate
		{
			//IL_0080: Unknown result type (might be due to invalid IL or missing references)
			if (AutomaticReady && Object.op_Implicit((Object)(object)mapper) && LivingPlayer(mapper.mapperBody) && !(name == "none") && Roll(encoreChance) && Emotes().Contains(name))
			{
				QueueReaction(mapper.mapperBody.corePosition, 24f, "follow-my-lead", 3, name, null, mapper);
			}
		});
	}

	private void DamageDealt(DamageReport report)
	{
		ObserveSafely(delegate
		{
			//IL_002d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0033: Invalid comparison between Unknown and I4
			//IL_0071: Unknown result type (might be due to invalid IL or missing references)
			//IL_0076: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e2: Unknown result type (might be due to invalid IL or missing references)
			if (AutomaticReady && report != null && !(report.damageDealt <= 0f) && (int)report.dotType == -1 && LivingPlayer(report.victimBody) && Candidate(report.attackerBody))
			{
				NetworkInstanceId netId = ((Component)report.attackerBody).GetComponent<NetworkIdentity>().netId;
				uint value = ((NetworkInstanceId)(ref netId)).Value;
				if (!hitChecks.TryGetValue(value, out var value2) || !(Time.time < value2))
				{
					hitChecks[value] = Time.time + 5f;
					if (Roll(tauntChance))
					{
						QueueReaction(report.attackerBody.corePosition, 60f, "damage-taunt", 1, null, report.attackerBody);
					}
				}
			}
		});
	}

	private static bool LivingPlayer(CharacterBody body)
	{
		if (Object.op_Implicit((Object)(object)body) && body.isPlayerControlled && Object.op_Implicit((Object)(object)body.healthComponent))
		{
			return body.healthComponent.alive;
		}
		return false;
	}

	private void ObservePing(orig_CmdPing orig, PingerController self, PingInfo info)
	{
		//IL_000e: Unknown result type (might be due to invalid IL or missing references)
		//IL_000f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0023: Unknown result type (might be due to invalid IL or missing references)
		orig.Invoke(self, info);
		ObserveSafely(delegate
		{
			//IL_00aa: Unknown result type (might be due to invalid IL or missing references)
			//IL_00af: Unknown result type (might be due to invalid IL or missing references)
			if (HostReady && pingEnabled.Value && info.active && Object.op_Implicit((Object)(object)self))
			{
				CharacterMaster component = ((Component)self).GetComponent<CharacterMaster>();
				if (Object.op_Implicit((Object)(object)component) && Object.op_Implicit((Object)(object)component.playerCharacterMasterController) && LivingPlayer(component.GetBody()))
				{
					GameObject targetGameObject = ((PingInfo)(ref info)).targetGameObject;
					if (Object.op_Implicit((Object)(object)targetGameObject))
					{
						CharacterBody val = targetGameObject.GetComponent<CharacterBody>();
						if (!Object.op_Implicit((Object)(object)val))
						{
							val = targetGameObject.GetComponentInParent<CharacterBody>();
						}
						if (Candidate(val, ping: true))
						{
							NetworkInstanceId netId = ((Component)val).GetComponent<NetworkIdentity>().netId;
							uint value = ((NetworkInstanceId)(ref netId)).Value;
							if (pingBudget.CanStart(value, Time.time, dances.ContainsKey(value)) && Emotes().Count != 0)
							{
								if (dances.Count >= 3)
								{
									End(dances.OrderBy((KeyValuePair<uint, Dance> kv) => kv.Value.Message.Sequence).First().Key, broadcast: true);
								}
								StartEvent((IEnumerable<CharacterBody>)(object)new CharacterBody[1] { val }, "ping-spotlight", null, ping: true);
							}
						}
					}
				}
			}
		});
	}

	private void ObserveSafely(Action action)
	{
		try
		{
			action();
		}
		catch (Exception ex)
		{
			((BaseUnityPlugin)this).Logger.LogWarning((object)("MDB_SCENARIO_ERROR " + ex));
		}
	}
}
internal static class SolusWingGuard
{
	private delegate void Play2(BoneMapper mapper, string animation, int position);

	private delegate void Play3(BoneMapper mapper, string animation, int position, int audio);

	private delegate void Request(string animation, BoneMapper mapper, int position);

	private delegate void Join(BoneMapper mapper);

	private delegate void Prepare(BoneMapper mapper, JoinSpot[] spots);

	private static readonly List<Hook> hooks = new List<Hook>();

	private static ManualLogSource log;

	private static bool installed;

	private static bool reported;

	internal static void Install(ManualLogSource logger)
	{
		if (!installed)
		{
			log = logger;
			Add(typeof(BoneMapper), "PlayAnim", new Type[2]
			{
				typeof(string),
				typeof(int)
			}, new Action<Play2, BoneMapper, string, int>(BlockPlay2));
			Add(typeof(BoneMapper), "PlayAnim", new Type[3]
			{
				typeof(string),
				typeof(int),
				typeof(int)
			}, new Action<Play3, BoneMapper, string, int, int>(BlockPlay3));
			Add(typeof(CustomEmotesAPI), "PlayAnimation", new Type[3]
			{
				typeof(string),
				typeof(BoneMapper),
				typeof(int)
			}, new Action<Request, string, BoneMapper, int>(BlockRequest));
			Add(typeof(BoneMapper), "JoinEmoteSpot", Type.EmptyTypes, new Action<Join, BoneMapper>(BlockJoin));
			Add(typeof(BoneMapper), "NewAnimation", new Type[1] { typeof(JoinSpot[]) }, new Action<Prepare, BoneMapper, JoinSpot[]>(BlockPrepare));
			installed = true;
			log.LogInfo((object)"MDB_SOLUS_WING_GUARD_READY 0.2.1: five API entry points protected on this peer; always active.");
		}
	}

	private static void Add(Type owner, string name, Type[] arguments, Delegate guard)
	{
		//IL_0025: Unknown result type (might be due to invalid IL or missing references)
		//IL_002b: Expected O, but got Unknown
		MethodInfo method = owner.GetMethod(name, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic, null, arguments, null);
		if (method == null)
		{
			throw new MissingMethodException(owner.FullName, name);
		}
		Hook val = new Hook((MethodBase)method, guard);
		hooks.Add(val);
		if (!val.IsApplied)
		{
			throw new InvalidOperationException("Solus Wing safety hook did not apply: " + method);
		}
	}

	internal static bool IsWingIdentity(string identity)
	{
		string text = (identity ?? "").Trim();
		if (!text.StartsWith("SolusWing", StringComparison.OrdinalIgnoreCase))
		{
			return text.StartsWith("mdlSolusWing", StringComparison.OrdinalIgnoreCase);
		}
		return true;
	}

	private static bool NamedWing(Object target)
	{
		if (Object.op_Implicit(target))
		{
			return IsWingIdentity(target.name);
		}
		return false;
	}

	private static bool Blocked(BoneMapper mapper)
	{
		if (!Object.op_Implicit((Object)(object)mapper))
		{
			return true;
		}
		bool flag = NamedWing((Object)(object)mapper.mapperBody) || NamedWing((Object)(object)mapper.bodyPrefab) || NamedWing((Object)(object)mapper.model);
		if (!flag)
		{
			Transform val = ((Component)mapper).transform;
			while (Object.op_Implicit((Object)(object)val))
			{
				if (NamedWing((Object)(object)val))
				{
					flag = true;
					break;
				}
				val = val.parent;
			}
		}
		if (flag && !reported)
		{
			reported = true;
			ManualLogSource obj = log;
			if (obj != null)
			{
				obj.LogWarning((object)"MDB_SOLUS_WING_BLOCKED: rejected an upstream emote request before animation, audio, props or callbacks.");
			}
		}
		return flag;
	}

	private static void BlockPlay2(Play2 orig, BoneMapper mapper, string animation, int position)
	{
		if (!Blocked(mapper))
		{
			orig(mapper, animation, position);
		}
	}

	private static void BlockPlay3(Play3 orig, BoneMapper mapper, string animation, int position, int audio)
	{
		if (!Blocked(mapper))
		{
			orig(mapper, animation, position, audio);
		}
	}

	private static void BlockRequest(Request orig, string animation, BoneMapper mapper, int position)
	{
		if (!Blocked(mapper))
		{
			orig(animation, mapper, position);
		}
	}

	private static void BlockJoin(Join orig, BoneMapper mapper)
	{
		if (!Blocked(mapper))
		{
			orig(mapper);
		}
	}

	private static void BlockPrepare(Prepare orig, BoneMapper mapper, JoinSpot[] spots)
	{
		if (!Blocked(mapper))
		{
			orig(mapper, spots);
		}
	}
}