Decompiled source of After Images v1.1.7

plugins/AfterImages.dll

Decompiled a day ago
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Runtime.CompilerServices;
using BepInEx;
using BepInEx.Configuration;
using BombRushMP.Common.Networking;
using BombRushMP.Plugin;
using Reptile;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.SceneManagement;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: AssemblyVersion("0.0.0.0")]
namespace BRCPluginOnlyAfterimages;

[BepInPlugin("com.brc.afterimage3clone", "After Images", "1.1.7")]
public sealed class Plugin : BaseUnityPlugin
{
	private struct RemotePresence
	{
		public bool Enabled;

		public float LastSeen;

		public RemotePresence(bool enabled, float lastSeen)
		{
			Enabled = enabled;
			LastSeen = lastSeen;
		}
	}

	private struct SyncedColors
	{
		public Color c1;

		public Color c2;

		public Color c3;

		public SyncedColors(Color a, Color b, Color c)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			//IL_0008: Unknown result type (might be due to invalid IL or missing references)
			//IL_0009: 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_0010: Unknown result type (might be due to invalid IL or missing references)
			c1 = a;
			c2 = b;
			c3 = c;
		}
	}

	private const string AcnPacketId = "AfterImages.ColorSync.v1";

	private const string PresencePacketId = "AfterImages.Presence.v1";

	private const ushort UnknownOwnerId = ushort.MaxValue;

	private const float PresenceTimeoutSeconds = 8f;

	internal static Plugin Instance;

	internal static ConfigEntry<bool> Enabled;

	internal static ConfigEntry<bool> ShowOtherPlayersAfterimages;

	internal static ConfigEntry<float> Clone1Delay;

	internal static ConfigEntry<float> Clone2Delay;

	internal static ConfigEntry<float> Clone3Delay;

	internal static ConfigEntry<float> MinimumVisibleDistance;

	internal static ConfigEntry<float> MinimumVisibleDistanceHysteresis;

	internal static ConfigEntry<float> SampleInterval;

	internal static ConfigEntry<float> CloneUpdateInterval;

	internal static ConfigEntry<float> PositionSmoothing;

	internal static ConfigEntry<float> RotationSmoothing;

	internal static ConfigEntry<bool> CopyScale;

	internal static ConfigEntry<bool> CopyBlendShapes;

	internal static ConfigEntry<Color> Clone1Color;

	internal static ConfigEntry<Color> Clone2Color;

	internal static ConfigEntry<Color> Clone3Color;

	internal static ConfigEntry<bool> RainbowMode;

	internal static ConfigEntry<float> RainbowSpeed;

	internal static ConfigEntry<float> RainbowSaturation;

	internal static ConfigEntry<bool> MirrorRendererToggles;

	internal static ConfigEntry<bool> MirrorRendererActiveState;

	internal static ConfigEntry<bool> MirrorMeshChanges;

	internal static ConfigEntry<bool> ParticleSystemsVisible;

	internal static ConfigEntry<bool> TrailRenderersVisible;

	internal static ConfigEntry<float> ScanInterval;

	internal static ConfigEntry<bool> DisableCloneShadows;

	internal static ConfigEntry<bool> CloneUpdateWhenOffscreen;

	private readonly Dictionary<int, Driver> drivers = new Dictionary<int, Driver>();

	private readonly Dictionary<ushort, SyncedColors> remoteColors = new Dictionary<ushort, SyncedColors>();

	private readonly Dictionary<ushort, RemotePresence> remotePresence = new Dictionary<ushort, RemotePresence>();

	private float nextScan;

	private float nextColorBroadcast;

	private float nextPresenceBroadcast;

	private Color lastSent1 = new Color(-1f, -1f, -1f, -1f);

	private Color lastSent2 = new Color(-1f, -1f, -1f, -1f);

	private Color lastSent3 = new Color(-1f, -1f, -1f, -1f);

	private void Awake()
	{
		//IL_02ce: Unknown result type (might be due to invalid IL or missing references)
		//IL_0306: Unknown result type (might be due to invalid IL or missing references)
		//IL_033e: Unknown result type (might be due to invalid IL or missing references)
		//IL_03a1: Unknown result type (might be due to invalid IL or missing references)
		//IL_03ab: Expected O, but got Unknown
		//IL_03df: Unknown result type (might be due to invalid IL or missing references)
		//IL_03e9: Expected O, but got Unknown
		Instance = this;
		Enabled = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Enabled", true, "Master toggle for runtime afterimages.");
		ShowOtherPlayersAfterimages = ((BaseUnityPlugin)this).Config.Bind<bool>("Multiplayer", "ShowOtherPlayersAfterimages", true, "Show afterimage clones for other ACN players. Turn this OFF to keep your own afterimages while completely removing remote players' clone rigs for better multiplayer FPS.");
		Clone1Delay = ((BaseUnityPlugin)this).Config.Bind<float>("Clones", "Clone1Delay", 0.05f, "");
		Clone2Delay = ((BaseUnityPlugin)this).Config.Bind<float>("Clones", "Clone2Delay", 0.1f, "");
		Clone3Delay = ((BaseUnityPlugin)this).Config.Bind<float>("Clones", "Clone3Delay", 0.15f, "");
		MinimumVisibleDistance = ((BaseUnityPlugin)this).Config.Bind<float>("Clones", "MinimumVisibleDistance", 0.08f, "Hide an afterimage while its delayed world position is closer than this many meters to the live character. Prevents overlapping/clipping ghosts while standing still or moving very slowly.");
		MinimumVisibleDistanceHysteresis = ((BaseUnityPlugin)this).Config.Bind<float>("Clones", "MinimumVisibleDistanceHysteresis", 0.03f, "Extra distance a hidden clone must travel before becoming visible again. Prevents rapid on/off flicker around MinimumVisibleDistance. Example: with 0.08 minimum and 0.03 hysteresis, a visible clone hides below 0.08m and stays hidden until it reaches 0.11m.");
		SampleInterval = ((BaseUnityPlugin)this).Config.Bind<float>("Pose History", "SampleInterval", 0f, "0 = capture pose every rendered frame (recommended). Higher values cap sampling.");
		CloneUpdateInterval = ((BaseUnityPlugin)this).Config.Bind<float>("Performance", "CloneUpdateInterval", 0f, "0 = update all three afterimages every rendered frame (recommended).");
		PositionSmoothing = ((BaseUnityPlugin)this).Config.Bind<float>("Smoothing", "PositionSmoothing", 16f, "");
		RotationSmoothing = ((BaseUnityPlugin)this).Config.Bind<float>("Smoothing", "RotationSmoothing", 16f, "");
		CopyScale = ((BaseUnityPlugin)this).Config.Bind<bool>("Smoothing", "CopyScale", true, "");
		CopyBlendShapes = ((BaseUnityPlugin)this).Config.Bind<bool>("Performance", "CopyBlendShapes", false, "Copies blendshape weights from the live CBB. Leave OFF for best FPS.");
		DisableCloneShadows = ((BaseUnityPlugin)this).Config.Bind<bool>("Performance", "DisableCloneShadows", true, "");
		CloneUpdateWhenOffscreen = ((BaseUnityPlugin)this).Config.Bind<bool>("Performance", "CloneUpdateWhenOffscreen", false, "");
		MirrorRendererToggles = ((BaseUnityPlugin)this).Config.Bind<bool>("Skin Switch / Toggles", "MirrorRendererToggles", true, "Mirrors source SkinnedMeshRenderer.enabled onto all 3 runtime clones.");
		MirrorRendererActiveState = ((BaseUnityPlugin)this).Config.Bind<bool>("Skin Switch / Toggles", "MirrorRendererActiveState", true, "Hides a clone mesh when the matching source mesh object is inactive.");
		MirrorMeshChanges = ((BaseUnityPlugin)this).Config.Bind<bool>("Skin Switch / Toggles", "MirrorMeshChanges", true, "Updates clone meshes when CharacterDefinition / CBB swaps a SkinnedMeshRenderer mesh.");
		ParticleSystemsVisible = ((BaseUnityPlugin)this).Config.Bind<bool>("Character Effects", "ParticleSystemsVisible", true, "Legacy setting retained for config compatibility. After Images no longer changes ParticleSystems on the original character; all ParticleSystems found under generated clone hierarchies are always suppressed.");
		TrailRenderersVisible = ((BaseUnityPlugin)this).Config.Bind<bool>("Character Effects", "TrailRenderersVisible", true, "Finds TrailRenderer components on the current character and lets you show/hide them.");
		Clone1Color = ((BaseUnityPlugin)this).Config.Bind<Color>("Colors", "Clone1Color", new Color(0.6f, 67f / 75f, 1f, 0.2509804f), "Clone 1 RGBA. Config Manager shows R/G/B/A sliders and Hex.");
		Clone2Color = ((BaseUnityPlugin)this).Config.Bind<Color>("Colors", "Clone2Color", new Color(0.59180313f, 0.8012104f, 93f / 106f, 0.2509804f), "Clone 2 RGBA.");
		Clone3Color = ((BaseUnityPlugin)this).Config.Bind<Color>("Colors", "Clone3Color", new Color(0.6123621f, 99f / 106f, 0.8760166f, 0.2509804f), "Clone 3 RGBA.");
		RainbowMode = ((BaseUnityPlugin)this).Config.Bind<bool>("Rainbow Mode", "Enabled", false, "Continuously cycles all three afterimages through a pastel hue spectrum. Manual clone RGB colors are used again when disabled. Each clone keeps the alpha from its configured color.");
		RainbowSpeed = ((BaseUnityPlugin)this).Config.Bind<float>("Rainbow Mode", "Speed", 0.12f, new ConfigDescription("Rainbow cycles per second. 0.12 is a little over 8 seconds for a full hue loop.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 2f), new object[0]));
		RainbowSaturation = ((BaseUnityPlugin)this).Config.Bind<float>("Rainbow Mode", "Saturation", 0.45f, new ConfigDescription("Pastel strength. Lower values are paler/whiter; higher values are more vivid.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f), new object[0]));
		ScanInterval = ((BaseUnityPlugin)this).Config.Bind<float>("Advanced", "ScanInterval", 0.5f, "");
		SceneManager.sceneLoaded += SceneLoaded;
		try
		{
			ClientController.RegisterCustomPacketHandler("AfterImages.ColorSync.v1", (Action<ushort, byte[]>)OnAcnColorPacket);
			ClientController.RegisterCustomPacketHandler("AfterImages.Presence.v1", (Action<ushort, byte[]>)OnAcnPresencePacket);
		}
		catch (Exception ex)
		{
			((BaseUnityPlugin)this).Logger.LogWarning((object)("ACN custom packet registration failed: " + ex.Message));
		}
		nextScan = Time.unscaledTime + 0.5f;
		nextColorBroadcast = Time.unscaledTime + 1f;
		nextPresenceBroadcast = Time.unscaledTime + 0.25f;
		((BaseUnityPlugin)this).Logger.LogInfo((object)"After Images 1.1.3 loaded. Added multiplayer remote-afterimage visibility toggle.");
	}

	internal static Material CreateExactCloneMaterial(int cloneIndex, Color color)
	{
		//IL_003f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0045: Expected O, but got Unknown
		//IL_0144: Unknown result type (might be due to invalid IL or missing references)
		Shader val = Shader.Find("Particles/Standard Unlit");
		if ((Object)(object)val == (Object)null)
		{
			val = Shader.Find("Particles/Standard Unlit2");
		}
		if ((Object)(object)val == (Object)null)
		{
			val = Shader.Find("Sprites/Default");
		}
		if ((Object)(object)val == (Object)null)
		{
			return null;
		}
		Material val2 = new Material(val);
		((Object)val2).name = cloneIndex switch
		{
			1 => "clone 6", 
			0 => "clone 3", 
			_ => "clone 7", 
		} + " (After Images Runtime)";
		val2.SetOverrideTag("RenderType", "Transparent");
		val2.renderQueue = 3000;
		if (val2.HasProperty("_Mode"))
		{
			val2.SetFloat("_Mode", (cloneIndex < 2) ? 4f : 2f);
		}
		if (val2.HasProperty("_SrcBlend"))
		{
			val2.SetFloat("_SrcBlend", 5f);
		}
		if (val2.HasProperty("_DstBlend"))
		{
			val2.SetFloat("_DstBlend", (cloneIndex < 2) ? 1f : 10f);
		}
		if (val2.HasProperty("_ZWrite"))
		{
			val2.SetFloat("_ZWrite", 0f);
		}
		if (val2.HasProperty("_Cull"))
		{
			val2.SetFloat("_Cull", 2f);
		}
		if (val2.HasProperty("_Color"))
		{
			val2.SetColor("_Color", color);
		}
		val2.DisableKeyword("_ALPHATEST_ON");
		val2.EnableKeyword("_ALPHABLEND_ON");
		val2.DisableKeyword("_ALPHAPREMULTIPLY_ON");
		return val2;
	}

	private void OnDestroy()
	{
		SceneManager.sceneLoaded -= SceneLoaded;
		try
		{
			ClientController.UnregisterCustomPacketHandler("AfterImages.ColorSync.v1");
			ClientController.UnregisterCustomPacketHandler("AfterImages.Presence.v1");
		}
		catch
		{
		}
		CleanupAll();
	}

	private void SceneLoaded(Scene scene, LoadSceneMode mode)
	{
		CleanupAll();
		remotePresence.Clear();
		nextScan = Time.unscaledTime + 0.5f;
		nextPresenceBroadcast = Time.unscaledTime + 0.25f;
	}

	private void CleanupAll()
	{
		foreach (KeyValuePair<int, Driver> driver in drivers)
		{
			if ((Object)(object)driver.Value != (Object)null)
			{
				Object.Destroy((Object)(object)driver.Value);
			}
		}
		drivers.Clear();
	}

	private void Update()
	{
		UpdateAcnColorSync();
		UpdateAcnPresenceSync();
		ushort num = ushort.MaxValue;
		bool flag = false;
		try
		{
			ClientController instance = ClientController.Instance;
			flag = (Object)(object)instance != (Object)null && instance.Connected;
			if (flag)
			{
				num = instance.LocalID;
			}
		}
		catch
		{
		}
		List<int> list = null;
		foreach (KeyValuePair<int, Driver> driver in drivers)
		{
			Driver value = driver.Value;
			if ((Object)(object)value == (Object)null || value.PlayerGone)
			{
				if (list == null)
				{
					list = new List<int>();
				}
				list.Add(driver.Key);
			}
			else if (!((!flag) ? Enabled.Value : ((value.OwnerPlayerId == num) ? Enabled.Value : ((!IsConfirmedRemoteOwner(value.OwnerPlayerId)) ? Enabled.Value : (ShowOtherPlayersAfterimages.Value && IsRemotePresenceEnabled(value.OwnerPlayerId))))))
			{
				if (list == null)
				{
					list = new List<int>();
				}
				list.Add(driver.Key);
			}
			else
			{
				value.SetPluginEnabled(value: true);
			}
		}
		if (list != null)
		{
			for (int i = 0; i < list.Count; i++)
			{
				if (drivers.TryGetValue(list[i], out var value2) && (Object)(object)value2 != (Object)null)
				{
					Object.Destroy((Object)(object)value2);
				}
				drivers.Remove(list[i]);
			}
		}
		if (!(Time.unscaledTime < nextScan))
		{
			ScanPlayers();
			nextScan = Time.unscaledTime + Mathf.Max(0.15f, ScanInterval.Value);
		}
	}

	private void ScanPlayers()
	{
		Player[] array = Object.FindObjectsOfType<Player>();
		ClientController val = null;
		bool flag = false;
		ushort num = ushort.MaxValue;
		try
		{
			val = ClientController.Instance;
			flag = (Object)(object)val != (Object)null && val.Connected;
			if (flag)
			{
				num = val.LocalID;
			}
		}
		catch
		{
		}
		foreach (Player val2 in array)
		{
			if ((Object)(object)val2 == (Object)null || !((Component)val2).gameObject.activeInHierarchy)
			{
				continue;
			}
			int instanceID = ((Object)((Component)val2).gameObject).GetInstanceID();
			if (drivers.TryGetValue(instanceID, out var value) && (Object)(object)value != (Object)null)
			{
				value.RefreshAcnOwner(ResolveAcnOwner(val2));
				value.RefreshVisualRootIfNeeded();
				continue;
			}
			ushort num2 = ResolveAcnOwner(val2);
			if (flag)
			{
				if (num2 == num)
				{
					if (!Enabled.Value)
					{
						continue;
					}
				}
				else if (IsConfirmedRemoteOwner(num2))
				{
					if (!ShowOtherPlayersAfterimages.Value || !IsRemotePresenceEnabled(num2))
					{
						continue;
					}
				}
				else if (!Enabled.Value)
				{
					continue;
				}
			}
			else if (!Enabled.Value)
			{
				continue;
			}
			Transform val3 = FindVisualRoot(((Component)val2).transform);
			if (!((Object)(object)val3 == (Object)null))
			{
				Driver driver = ((Component)val2).gameObject.AddComponent<Driver>();
				drivers[instanceID] = driver;
				try
				{
					driver.Initialize(val2, val3, num2);
					((BaseUnityPlugin)this).Logger.LogInfo((object)("After Images 1.1.4 overlap-safe clones created for '" + ((Object)val3).name + "' (player object " + instanceID + ", ACN owner " + driver.OwnerPlayerId + "). Built-in Particles/Standard Unlit material path."));
				}
				catch (Exception ex)
				{
					((Behaviour)driver).enabled = false;
					((BaseUnityPlugin)this).Logger.LogError((object)("After Images clone build failed once for '" + ((Object)val3).name + "': " + ex.Message));
				}
			}
		}
	}

	private static Transform FindVisualRoot(Transform playerRoot)
	{
		if ((Object)(object)playerRoot == (Object)null)
		{
			return null;
		}
		SkinnedMeshRenderer[] componentsInChildren = ((Component)playerRoot).GetComponentsInChildren<SkinnedMeshRenderer>(true);
		if (componentsInChildren == null || componentsInChildren.Length == 0)
		{
			return null;
		}
		Dictionary<Transform, int> dictionary = new Dictionary<Transform, int>();
		foreach (SkinnedMeshRenderer val in componentsInChildren)
		{
			if (!((Object)(object)val == (Object)null) && ((Component)val).gameObject.activeInHierarchy && !IsOurRuntimeObject(((Component)val).transform) && !IsLegacyAfterimageObject(((Component)val).transform))
			{
				Transform val2 = GetImmediateChildUnder(playerRoot, ((Component)val).transform);
				if ((Object)(object)val2 == (Object)null)
				{
					val2 = playerRoot;
				}
				dictionary.TryGetValue(val2, out var value);
				dictionary[val2] = value + 1;
			}
		}
		Transform result = null;
		int num = 0;
		foreach (KeyValuePair<Transform, int> item in dictionary)
		{
			if ((Object)(object)item.Key != (Object)null && item.Value > num)
			{
				result = item.Key;
				num = item.Value;
			}
		}
		return result;
	}

	private static Transform GetImmediateChildUnder(Transform root, Transform t)
	{
		if ((Object)(object)root == (Object)null || (Object)(object)t == (Object)null)
		{
			return null;
		}
		Transform val = t;
		while ((Object)(object)val.parent != (Object)null && (Object)(object)val.parent != (Object)(object)root)
		{
			val = val.parent;
		}
		if (!((Object)(object)val.parent == (Object)(object)root))
		{
			return root;
		}
		return val;
	}

	internal static bool IsOurRuntimeObject(Transform t)
	{
		Transform val = t;
		while ((Object)(object)val != (Object)null)
		{
			if (((Object)val).name.StartsWith("AFTERIMAGE_RUNTIME_3CLONES"))
			{
				return true;
			}
			val = val.parent;
		}
		return false;
	}

	internal static bool LooksLikeLegacyCloneContainer(Transform t)
	{
		if ((Object)(object)t == (Object)null || ((Object)t).name != "clones")
		{
			return false;
		}
		bool flag = false;
		bool flag2 = false;
		bool result = false;
		for (int i = 0; i < t.childCount; i++)
		{
			switch (((Object)t.GetChild(i)).name)
			{
			case "clone_01":
				flag = true;
				break;
			case "clone_02":
				flag2 = true;
				break;
			case "clone_03":
				result = true;
				break;
			}
		}
		if (flag && flag2)
		{
			return result;
		}
		return false;
	}

	internal static bool IsLegacyAfterimageObject(Transform t)
	{
		Transform val = t;
		while ((Object)(object)val != (Object)null)
		{
			if (LooksLikeLegacyCloneContainer(val))
			{
				return true;
			}
			val = val.parent;
		}
		return false;
	}

	private ushort ResolveAcnOwner(Player player)
	{
		try
		{
			ClientController instance = ClientController.Instance;
			if ((Object)(object)instance == (Object)null)
			{
				return 0;
			}
			foreach (KeyValuePair<ushort, MPPlayer> player2 in instance.Players)
			{
				MPPlayer value = player2.Value;
				if (value != null && !((Object)(object)value.Player == (Object)null))
				{
					if ((Object)(object)value.Player == (Object)(object)player)
					{
						return player2.Key;
					}
					Transform transform = ((Component)value.Player).transform;
					Transform transform2 = ((Component)player).transform;
					if ((Object)(object)transform == (Object)(object)transform2 || transform.IsChildOf(transform2) || transform2.IsChildOf(transform))
					{
						return player2.Key;
					}
				}
			}
			return ushort.MaxValue;
		}
		catch
		{
			return ushort.MaxValue;
		}
	}

	internal bool TryGetSyncedColors(ushort playerId, out Color c1, out Color c2, out Color c3)
	{
		//IL_003b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0040: Unknown result type (might be due to invalid IL or missing references)
		//IL_0046: Unknown result type (might be due to invalid IL or missing references)
		//IL_004b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0052: 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_0013: Unknown result type (might be due to invalid IL or missing references)
		//IL_0018: Unknown result type (might be due to invalid IL or missing references)
		//IL_0020: Unknown result type (might be due to invalid IL or missing references)
		//IL_0025: 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 (remoteColors.TryGetValue(playerId, out var value))
		{
			c1 = value.c1;
			c2 = value.c2;
			c3 = value.c3;
			return true;
		}
		c1 = Color.white;
		c2 = Color.white;
		c3 = Color.white;
		return false;
	}

	private void UpdateAcnPresenceSync()
	{
		try
		{
			ClientController instance = ClientController.Instance;
			if (!((Object)(object)instance == (Object)null) && instance.Connected)
			{
				remotePresence[instance.LocalID] = new RemotePresence(Enabled.Value, Time.unscaledTime);
				if (!(Time.unscaledTime < nextPresenceBroadcast))
				{
					instance.BroadcastCustomPacket(new byte[1] { (byte)(Enabled.Value ? 1 : 0) }, "AfterImages.Presence.v1", (SendModes)2);
					nextPresenceBroadcast = Time.unscaledTime + 2f;
				}
			}
		}
		catch
		{
		}
	}

	private void OnAcnPresencePacket(ushort sender, byte[] data)
	{
		if (data != null && data.Length >= 1)
		{
			remotePresence[sender] = new RemotePresence(data[0] != 0, Time.unscaledTime);
			if (data[0] == 0)
			{
				nextScan = 0f;
			}
		}
	}

	private bool IsConfirmedRemoteOwner(ushort ownerId)
	{
		try
		{
			ClientController instance = ClientController.Instance;
			if ((Object)(object)instance == (Object)null || !instance.Connected)
			{
				return false;
			}
			if (ownerId == ushort.MaxValue || ownerId == instance.LocalID)
			{
				return false;
			}
			return instance.Players != null && instance.Players.ContainsKey(ownerId);
		}
		catch
		{
			return false;
		}
	}

	private bool IsRemotePresenceEnabled(ushort ownerId)
	{
		if (ownerId == ushort.MaxValue)
		{
			return false;
		}
		if (!remotePresence.TryGetValue(ownerId, out var value))
		{
			return false;
		}
		if (!value.Enabled)
		{
			return false;
		}
		return Time.unscaledTime - value.LastSeen <= 8f;
	}

	internal static void GetLocalEffectiveColors(out Color c1, out Color c2, out Color c3)
	{
		//IL_0006: Unknown result type (might be due to invalid IL or missing references)
		//IL_000b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0016: Unknown result type (might be due to invalid IL or missing references)
		//IL_001b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0026: Unknown result type (might be due to invalid IL or missing references)
		//IL_002b: 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_00bb: Unknown result type (might be due to invalid IL or missing references)
		//IL_00c0: Unknown result type (might be due to invalid IL or missing references)
		//IL_00d9: 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_0108: Unknown result type (might be due to invalid IL or missing references)
		//IL_0109: 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_0111: Unknown result type (might be due to invalid IL or missing references)
		//IL_0117: Unknown result type (might be due to invalid IL or missing references)
		//IL_0119: Unknown result type (might be due to invalid IL or missing references)
		c1 = Clone1Color.Value;
		c2 = Clone2Color.Value;
		c3 = Clone3Color.Value;
		if (RainbowMode != null && RainbowMode.Value)
		{
			float num = ((RainbowSpeed != null) ? Mathf.Max(0f, RainbowSpeed.Value) : 0.12f);
			float num2 = ((RainbowSaturation != null) ? Mathf.Clamp01(RainbowSaturation.Value) : 0.45f);
			float num3 = Mathf.Repeat(Time.unscaledTime * num, 1f);
			Color val = Color.HSVToRGB(num3, num2, 1f);
			Color val2 = Color.HSVToRGB(Mathf.Repeat(num3 + 1f / 3f, 1f), num2, 1f);
			Color val3 = Color.HSVToRGB(Mathf.Repeat(num3 + 2f / 3f, 1f), num2, 1f);
			val.a = c1.a;
			val2.a = c2.a;
			val3.a = c3.a;
			c1 = val;
			c2 = val2;
			c3 = val3;
		}
	}

	private void UpdateAcnColorSync()
	{
		//IL_003d: Unknown result type (might be due to invalid IL or missing references)
		//IL_003f: Unknown result type (might be due to invalid IL or missing references)
		//IL_004b: Unknown result type (might be due to invalid IL or missing references)
		//IL_004d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0059: 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_0093: Unknown result type (might be due to invalid IL or missing references)
		//IL_0094: Unknown result type (might be due to invalid IL or missing references)
		//IL_0095: Unknown result type (might be due to invalid IL or missing references)
		//IL_00ac: Unknown result type (might be due to invalid IL or missing references)
		//IL_00ad: Unknown result type (might be due to invalid IL or missing references)
		//IL_00b3: Unknown result type (might be due to invalid IL or missing references)
		//IL_00b4: Unknown result type (might be due to invalid IL or missing references)
		//IL_00ba: Unknown result type (might be due to invalid IL or missing references)
		//IL_00bb: Unknown result type (might be due to invalid IL or missing references)
		//IL_00e8: Unknown result type (might be due to invalid IL or missing references)
		//IL_00e9: Unknown result type (might be due to invalid IL or missing references)
		//IL_00ea: Unknown result type (might be due to invalid IL or missing references)
		try
		{
			ClientController instance = ClientController.Instance;
			if ((Object)(object)instance == (Object)null || !instance.Connected)
			{
				return;
			}
			GetLocalEffectiveColors(out var c, out var c2, out var c3);
			bool flag = RainbowMode != null && RainbowMode.Value;
			bool flag2 = !SameColor(c, lastSent1) || !SameColor(c2, lastSent2) || !SameColor(c3, lastSent3);
			if (flag)
			{
				if (Time.unscaledTime < nextColorBroadcast)
				{
					return;
				}
			}
			else if (!flag2 && Time.unscaledTime < nextColorBroadcast)
			{
				return;
			}
			byte[] array = PackColors(c, c2, c3);
			instance.BroadcastCustomPacket(array, "AfterImages.ColorSync.v1", (SendModes)2);
			lastSent1 = c;
			lastSent2 = c2;
			lastSent3 = c3;
			nextColorBroadcast = Time.unscaledTime + (flag ? 0.1f : 3f);
			remoteColors[instance.LocalID] = new SyncedColors(c, c2, c3);
		}
		catch
		{
		}
	}

	private void OnAcnColorPacket(ushort sender, byte[] data)
	{
		//IL_0014: Unknown result type (might be due to invalid IL or missing references)
		//IL_001b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0022: Unknown result type (might be due to invalid IL or missing references)
		if (data == null || data.Length < 12)
		{
			return;
		}
		remoteColors[sender] = new SyncedColors(ReadColor(data, 0), ReadColor(data, 4), ReadColor(data, 8));
		foreach (KeyValuePair<int, Driver> driver in drivers)
		{
			Driver value = driver.Value;
			if ((Object)(object)value != (Object)null && value.OwnerPlayerId == sender)
			{
				value.ForceColorRefresh();
			}
		}
	}

	private static byte[] PackColors(Color c1, Color c2, Color c3)
	{
		//IL_000a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0012: Unknown result type (might be due to invalid IL or missing references)
		//IL_001a: Unknown result type (might be due to invalid IL or missing references)
		byte[] array = new byte[12];
		WriteColor(array, 0, c1);
		WriteColor(array, 4, c2);
		WriteColor(array, 8, c3);
		return array;
	}

	private static void WriteColor(byte[] d, int o, Color c)
	{
		d[o] = (byte)Mathf.RoundToInt(Mathf.Clamp01(c.r) * 255f);
		d[o + 1] = (byte)Mathf.RoundToInt(Mathf.Clamp01(c.g) * 255f);
		d[o + 2] = (byte)Mathf.RoundToInt(Mathf.Clamp01(c.b) * 255f);
		d[o + 3] = (byte)Mathf.RoundToInt(Mathf.Clamp01(c.a) * 255f);
	}

	private static Color ReadColor(byte[] d, int o)
	{
		//IL_002e: Unknown result type (might be due to invalid IL or missing references)
		return new Color((float)(int)d[o] * 0.003921569f, (float)(int)d[o + 1] * 0.003921569f, (float)(int)d[o + 2] * 0.003921569f, (float)(int)d[o + 3] * 0.003921569f);
	}

	private static bool SameColor(Color a, Color b)
	{
		if (Mathf.Approximately(a.r, b.r) && Mathf.Approximately(a.g, b.g) && Mathf.Approximately(a.b, b.b))
		{
			return Mathf.Approximately(a.a, b.a);
		}
		return false;
	}
}
[DefaultExecutionOrder(32000)]
public sealed class Driver : MonoBehaviour
{
	private sealed class RendererMap
	{
		public SkinnedMeshRenderer source;

		public readonly List<SkinnedMeshRenderer> clones = new List<SkinnedMeshRenderer>();
	}

	private struct EffectRendererState
	{
		public Renderer renderer;

		public bool originalEnabled;

		public EffectRendererState(Renderer r, bool enabled)
		{
			renderer = r;
			originalEnabled = enabled;
		}
	}

	private Player player;

	private Transform visualRoot;

	private GameObject runtimeRoot;

	private Transform[][] cloneBones = new Transform[3][];

	private Transform[] sourceBones = (Transform[])(object)new Transform[0];

	private string[] sourcePaths = new string[0];

	private readonly List<RendererMap> rendererMaps = new List<RendererMap>();

	private readonly List<Material> ownedMaterials = new List<Material>();

	private readonly List<EffectRendererState> particleStates = new List<EffectRendererState>();

	private readonly List<EffectRendererState> trailStates = new List<EffectRendererState>();

	private Vector3[][] posHistory;

	private Quaternion[][] rotHistory;

	private Vector3[][] scaleHistory;

	private float[] timeHistory;

	private int historyCapacity;

	private int historyCount;

	private int historyHead;

	private float nextSample;

	private float nextCloneUpdate;

	private float nextRendererSync;

	private float nextParticleSuppressionScan;

	private float nextStructureCheck;

	private int visualStructureSignature;

	private int pendingStructureSignature;

	private int pendingStructureMatches;

	private readonly bool[] cloneHistoryReady = new bool[3];

	private readonly bool[] cloneDistanceReady = new bool[3] { true, true, true };

	private Color lastColor1 = new Color(-1f, -1f, -1f, -1f);

	private Color lastColor2 = new Color(-1f, -1f, -1f, -1f);

	private Color lastColor3 = new Color(-1f, -1f, -1f, -1f);

	private bool lastParticleVisible = true;

	private bool lastTrailVisible = true;

	public ushort OwnerPlayerId { get; private set; }

	internal bool PlayerGone
	{
		get
		{
			if (!((Object)(object)player == (Object)null))
			{
				return (Object)(object)((Component)player).gameObject == (Object)null;
			}
			return true;
		}
	}

	public void RefreshAcnOwner(ushort ownerId)
	{
		if (ownerId != ushort.MaxValue)
		{
			OwnerPlayerId = ownerId;
		}
	}

	internal void Initialize(Player p, Transform root, ushort owner)
	{
		player = p;
		visualRoot = root;
		OwnerPlayerId = owner;
		CleanupOldAfterimageObjects();
		RebuildRuntimeClones();
	}

	private void CleanupOldAfterimageObjects()
	{
		//IL_0095: Unknown result type (might be due to invalid IL or missing references)
		//IL_009a: Unknown result type (might be due to invalid IL or missing references)
		if ((Object)(object)player == (Object)null)
		{
			return;
		}
		int instanceID = ((Object)player).GetInstanceID();
		string text = "AFTERIMAGE_RUNTIME_3CLONES_" + instanceID;
		Transform[] componentsInChildren = ((Component)player).GetComponentsInChildren<Transform>(true);
		for (int num = componentsInChildren.Length - 1; num >= 0; num--)
		{
			Transform val = componentsInChildren[num];
			if (!((Object)(object)val == (Object)null))
			{
				bool flag = ((Object)val).name.StartsWith("AFTERIMAGE_RUNTIME_3CLONES");
				bool flag2 = Plugin.LooksLikeLegacyCloneContainer(val);
				if (flag || flag2)
				{
					((Component)val).gameObject.SetActive(false);
					Object.Destroy((Object)(object)((Component)val).gameObject);
				}
			}
		}
		Scene activeScene = SceneManager.GetActiveScene();
		GameObject[] rootGameObjects = ((Scene)(ref activeScene)).GetRootGameObjects();
		foreach (GameObject val2 in rootGameObjects)
		{
			if (!((Object)(object)val2 == (Object)null))
			{
				string name = ((Object)val2).name;
				bool flag3 = name == text;
				bool flag4 = name == "AFTERIMAGE_RUNTIME_3CLONES";
				if (flag3 || flag4)
				{
					val2.SetActive(false);
					Object.Destroy((Object)(object)val2);
				}
			}
		}
	}

	internal void SetPluginEnabled(bool value)
	{
		if ((Object)(object)runtimeRoot != (Object)null && runtimeRoot.activeSelf != value)
		{
			runtimeRoot.SetActive(value);
		}
	}

	internal void ForceColorRefresh()
	{
		//IL_0015: Unknown result type (might be due to invalid IL or missing references)
		//IL_001a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0034: 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_0053: Unknown result type (might be due to invalid IL or missing references)
		//IL_0058: Unknown result type (might be due to invalid IL or missing references)
		lastColor1 = new Color(-1f, -1f, -1f, -1f);
		lastColor2 = new Color(-1f, -1f, -1f, -1f);
		lastColor3 = new Color(-1f, -1f, -1f, -1f);
	}

	internal void RefreshVisualRootIfNeeded()
	{
		if (!((Object)(object)player == (Object)null))
		{
			Transform val = FindVisualRootForDriver(((Component)player).transform);
			if (!((Object)(object)val == (Object)null) && ((Object)(object)visualRoot == (Object)null || (Object)(object)visualRoot != (Object)(object)val || !((Component)visualRoot).gameObject.activeInHierarchy))
			{
				DestroyRuntime();
				CleanupOldAfterimageObjects();
				visualRoot = val;
				RebuildRuntimeClones();
			}
		}
	}

	private static Transform FindVisualRootForDriver(Transform playerRoot)
	{
		SkinnedMeshRenderer[] componentsInChildren = ((Component)playerRoot).GetComponentsInChildren<SkinnedMeshRenderer>(true);
		Dictionary<Transform, int> dictionary = new Dictionary<Transform, int>();
		foreach (SkinnedMeshRenderer val in componentsInChildren)
		{
			if (!((Object)(object)val == (Object)null) && ((Component)val).gameObject.activeInHierarchy && !Plugin.IsOurRuntimeObject(((Component)val).transform) && !Plugin.IsLegacyAfterimageObject(((Component)val).transform))
			{
				Transform val2 = ((Component)val).transform;
				while ((Object)(object)val2.parent != (Object)null && (Object)(object)val2.parent != (Object)(object)playerRoot)
				{
					val2 = val2.parent;
				}
				if ((Object)(object)val2.parent != (Object)(object)playerRoot)
				{
					val2 = playerRoot;
				}
				dictionary.TryGetValue(val2, out var value);
				dictionary[val2] = value + 1;
			}
		}
		Transform result = null;
		int num = 0;
		foreach (KeyValuePair<Transform, int> item in dictionary)
		{
			if ((Object)(object)item.Key != (Object)null && item.Value > num)
			{
				result = item.Key;
				num = item.Value;
			}
		}
		return result;
	}

	private void RebuildRuntimeClones()
	{
		//IL_0059: Unknown result type (might be due to invalid IL or missing references)
		//IL_0063: Expected O, but got Unknown
		//IL_0080: Unknown result type (might be due to invalid IL or missing references)
		//IL_0095: Unknown result type (might be due to invalid IL or missing references)
		//IL_00aa: Unknown result type (might be due to invalid IL or missing references)
		//IL_00c7: 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_0103: Unknown result type (might be due to invalid IL or missing references)
		DestroyRuntime();
		if ((Object)(object)visualRoot == (Object)null || !((Component)visualRoot).gameObject.activeInHierarchy)
		{
			return;
		}
		CleanupOldAfterimageObjects();
		BuildSourceBoneList();
		if (sourceBones.Length != 0)
		{
			runtimeRoot = new GameObject("AFTERIMAGE_RUNTIME_3CLONES_" + ((Object)player).GetInstanceID());
			runtimeRoot.transform.SetParent((Transform)null, false);
			runtimeRoot.transform.position = Vector3.zero;
			runtimeRoot.transform.rotation = Quaternion.identity;
			runtimeRoot.transform.localScale = Vector3.one;
			cloneBones[0] = BuildOneClone("clone_01", 0, Plugin.Clone1Color.Value);
			cloneBones[1] = BuildOneClone("clone_02", 1, Plugin.Clone2Color.Value);
			cloneBones[2] = BuildOneClone("clone_03", 2, Plugin.Clone3Color.Value);
			HardDisableCloneParticleSystems();
			BuildEffectToggleLists();
			ClearPoseHistory();
			AllocateHistory();
			if (historyCapacity > 0)
			{
				CapturePose(Time.unscaledTime);
			}
			cloneHistoryReady[0] = false;
			cloneHistoryReady[1] = false;
			cloneHistoryReady[2] = false;
			cloneDistanceReady[0] = false;
			cloneDistanceReady[1] = false;
			cloneDistanceReady[2] = false;
			visualStructureSignature = ComputeVisualStructureSignature();
			pendingStructureSignature = visualStructureSignature;
			pendingStructureMatches = 0;
			nextStructureCheck = 0f;
			nextSample = 0f;
			nextCloneUpdate = 0f;
			nextRendererSync = 0f;
			nextParticleSuppressionScan = 0f;
			ForceColorRefresh();
		}
	}

	private void BuildSourceBoneList()
	{
		Transform[] componentsInChildren = ((Component)visualRoot).GetComponentsInChildren<Transform>(true);
		List<Transform> list = new List<Transform>();
		foreach (Transform val in componentsInChildren)
		{
			if (!((Object)(object)val == (Object)null) && !Plugin.IsOurRuntimeObject(val) && !Plugin.IsLegacyAfterimageObject(val))
			{
				list.Add(val);
			}
		}
		sourceBones = list.ToArray();
		sourcePaths = new string[sourceBones.Length];
		for (int j = 0; j < sourceBones.Length; j++)
		{
			sourcePaths[j] = RelativePath(visualRoot, sourceBones[j]);
		}
	}

	private Transform[] BuildOneClone(string cloneName, int cloneIndex, Color initialColor)
	{
		//IL_0001: Unknown result type (might be due to invalid IL or missing references)
		//IL_0007: Expected O, but got Unknown
		//IL_0071: Unknown result type (might be due to invalid IL or missing references)
		//IL_0078: Expected O, but got Unknown
		//IL_0118: Unknown result type (might be due to invalid IL or missing references)
		//IL_0126: Unknown result type (might be due to invalid IL or missing references)
		//IL_0134: 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_00fa: 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_0222: Unknown result type (might be due to invalid IL or missing references)
		//IL_0229: Expected O, but got Unknown
		//IL_0251: Unknown result type (might be due to invalid IL or missing references)
		//IL_025d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0269: Unknown result type (might be due to invalid IL or missing references)
		//IL_028d: Unknown result type (might be due to invalid IL or missing references)
		GameObject val = new GameObject(cloneName);
		val.transform.SetParent(runtimeRoot.transform, false);
		Transform[] array = (Transform[])(object)new Transform[sourceBones.Length];
		Dictionary<Transform, Transform> dictionary = new Dictionary<Transform, Transform>();
		for (int i = 0; i < sourceBones.Length; i++)
		{
			Transform val2 = sourceBones[i];
			if (!((Object)(object)val2 == (Object)null))
			{
				GameObject val3 = new GameObject((i == 0) ? (((Object)visualRoot).name + "_AFTERIMAGE") : ((Object)val2).name);
				val3.layer = ((Component)val2).gameObject.layer;
				Transform transform = val3.transform;
				Transform val4 = val.transform;
				if ((Object)(object)val2 != (Object)(object)visualRoot && (Object)(object)val2.parent != (Object)null && dictionary.TryGetValue(val2.parent, out var value))
				{
					val4 = value;
				}
				transform.SetParent(val4, false);
				if ((Object)(object)val2 == (Object)(object)visualRoot)
				{
					transform.position = val2.position;
					transform.rotation = val2.rotation;
					transform.localScale = val2.localScale;
				}
				else
				{
					transform.localPosition = val2.localPosition;
					transform.localRotation = val2.localRotation;
					transform.localScale = val2.localScale;
				}
				array[i] = transform;
				dictionary[val2] = transform;
			}
		}
		SkinnedMeshRenderer[] componentsInChildren = ((Component)visualRoot).GetComponentsInChildren<SkinnedMeshRenderer>(true);
		for (int j = 0; j < componentsInChildren.Length; j++)
		{
			SkinnedMeshRenderer val5 = componentsInChildren[j];
			if ((Object)(object)val5 == (Object)null || (Object)(object)val5.sharedMesh == (Object)null || Plugin.IsOurRuntimeObject(((Component)val5).transform) || Plugin.IsLegacyAfterimageObject(((Component)val5).transform))
			{
				continue;
			}
			string name = ((Object)val5.sharedMesh).name;
			if ((string.IsNullOrEmpty(name) || name.IndexOf("sphere", StringComparison.OrdinalIgnoreCase) < 0) && dictionary.TryGetValue(((Component)val5).transform, out var value2))
			{
				GameObject val6 = new GameObject("__AI_RENDERER_" + j.ToString("D2") + "_" + ((Object)((Component)val5).gameObject).name);
				val6.layer = ((Component)val5).gameObject.layer;
				Transform transform2 = val6.transform;
				transform2.SetParent(value2, false);
				transform2.localPosition = Vector3.zero;
				transform2.localRotation = Quaternion.identity;
				transform2.localScale = Vector3.one;
				SkinnedMeshRenderer val7 = val6.AddComponent<SkinnedMeshRenderer>();
				if (!((Object)(object)val7 == (Object)null))
				{
					CopyRenderer(val5, val7, dictionary, cloneIndex, initialColor);
					RendererMap rendererMap = FindOrCreateRendererMap(val5);
					rendererMap.clones.Add(val7);
				}
			}
		}
		return array;
	}

	private RendererMap FindOrCreateRendererMap(SkinnedMeshRenderer src)
	{
		for (int i = 0; i < rendererMaps.Count; i++)
		{
			if ((Object)(object)rendererMaps[i].source == (Object)(object)src)
			{
				return rendererMaps[i];
			}
		}
		RendererMap rendererMap = new RendererMap();
		rendererMap.source = src;
		rendererMaps.Add(rendererMap);
		return rendererMap;
	}

	private void CopyRenderer(SkinnedMeshRenderer src, SkinnedMeshRenderer dst, Dictionary<Transform, Transform> cloneBySource, int cloneIndex, Color color)
	{
		//IL_0042: Unknown result type (might be due to invalid IL or missing references)
		//IL_0082: Unknown result type (might be due to invalid IL or missing references)
		//IL_0140: Unknown result type (might be due to invalid IL or missing references)
		if ((Object)(object)src == (Object)null || (Object)(object)dst == (Object)null || (Object)(object)src.sharedMesh == (Object)null)
		{
			return;
		}
		dst.sharedMesh = src.sharedMesh;
		((Renderer)dst).enabled = ((Renderer)src).enabled;
		dst.updateWhenOffscreen = true;
		((Renderer)dst).localBounds = ((Renderer)src).localBounds;
		((Renderer)dst).sortingLayerID = ((Renderer)src).sortingLayerID;
		((Renderer)dst).sortingOrder = ((Renderer)src).sortingOrder;
		if (Plugin.DisableCloneShadows.Value)
		{
			((Renderer)dst).shadowCastingMode = (ShadowCastingMode)0;
			((Renderer)dst).receiveShadows = false;
		}
		else
		{
			((Renderer)dst).shadowCastingMode = ((Renderer)src).shadowCastingMode;
			((Renderer)dst).receiveShadows = ((Renderer)src).receiveShadows;
		}
		((Renderer)dst).lightProbeUsage = (LightProbeUsage)0;
		((Renderer)dst).reflectionProbeUsage = (ReflectionProbeUsage)0;
		((Renderer)dst).motionVectorGenerationMode = (MotionVectorGenerationMode)2;
		if ((Object)(object)src.rootBone != (Object)null && cloneBySource.TryGetValue(src.rootBone, out var value))
		{
			dst.rootBone = value;
		}
		Transform[] bones = src.bones;
		Transform[] array = (Transform[])(object)new Transform[bones.Length];
		for (int i = 0; i < bones.Length; i++)
		{
			if (!((Object)(object)bones[i] == (Object)null) && cloneBySource.TryGetValue(bones[i], out var value2))
			{
				array[i] = value2;
			}
		}
		dst.bones = array;
		Material[] sharedMaterials = ((Renderer)src).sharedMaterials;
		int num = ((sharedMaterials == null || sharedMaterials.Length <= 0) ? 1 : sharedMaterials.Length);
		Material[] array2 = (Material[])(object)new Material[num];
		for (int j = 0; j < array2.Length; j++)
		{
			Material val = Plugin.CreateExactCloneMaterial(cloneIndex, color);
			if (!((Object)(object)val == (Object)null))
			{
				array2[j] = val;
				ownedMaterials.Add(val);
			}
		}
		((Renderer)dst).sharedMaterials = array2;
	}

	private void HardDisableCloneParticleSystems()
	{
		if ((Object)(object)runtimeRoot == (Object)null)
		{
			return;
		}
		ParticleSystem[] componentsInChildren = runtimeRoot.GetComponentsInChildren<ParticleSystem>(true);
		foreach (ParticleSystem val in componentsInChildren)
		{
			if (!((Object)(object)val == (Object)null))
			{
				val.Stop(true, (ParticleSystemStopBehavior)0);
				val.Clear(true);
				GameObject gameObject = ((Component)val).gameObject;
				if ((Object)(object)gameObject != (Object)null && (Object)(object)gameObject.GetComponent<SkinnedMeshRenderer>() == (Object)null)
				{
					gameObject.SetActive(false);
				}
			}
		}
		ParticleSystemRenderer[] componentsInChildren2 = runtimeRoot.GetComponentsInChildren<ParticleSystemRenderer>(true);
		foreach (ParticleSystemRenderer val2 in componentsInChildren2)
		{
			if ((Object)(object)val2 != (Object)null)
			{
				((Renderer)val2).enabled = false;
			}
		}
	}

	private void BuildEffectToggleLists()
	{
		particleStates.Clear();
		trailStates.Clear();
		TrailRenderer[] componentsInChildren = ((Component)visualRoot).GetComponentsInChildren<TrailRenderer>(true);
		foreach (TrailRenderer val in componentsInChildren)
		{
			if (!((Object)(object)val == (Object)null) && !Plugin.IsOurRuntimeObject(((Component)val).transform) && !Plugin.IsLegacyAfterimageObject(((Component)val).transform))
			{
				trailStates.Add(new EffectRendererState((Renderer)(object)val, ((Renderer)val).enabled));
			}
		}
		lastParticleVisible = !Plugin.ParticleSystemsVisible.Value;
		lastTrailVisible = !Plugin.TrailRenderersVisible.Value;
		ApplyEffectToggles();
	}

	private void ApplyEffectToggles()
	{
		bool value = Plugin.ParticleSystemsVisible.Value;
		bool value2 = Plugin.TrailRenderersVisible.Value;
		if (value != lastParticleVisible)
		{
			lastParticleVisible = value;
		}
		if (value2 == lastTrailVisible)
		{
			return;
		}
		for (int i = 0; i < trailStates.Count; i++)
		{
			Renderer renderer = trailStates[i].renderer;
			if ((Object)(object)renderer != (Object)null)
			{
				renderer.enabled = value2 && trailStates[i].originalEnabled;
			}
		}
		lastTrailVisible = value2;
	}

	private void AllocateHistory()
	{
		if (sourceBones == null || sourceBones.Length == 0)
		{
			historyCapacity = 0;
			historyCount = 0;
			historyHead = -1;
			posHistory = null;
			rotHistory = null;
			scaleHistory = null;
			timeHistory = null;
			return;
		}
		float num = Mathf.Max(Plugin.Clone1Delay.Value, Mathf.Max(Plugin.Clone2Delay.Value, Plugin.Clone3Delay.Value));
		float value = Plugin.SampleInterval.Value;
		float num2 = ((value <= 0f) ? 0.004166667f : Mathf.Max(0.001f, value));
		float num3 = Mathf.Max(0.35f, num + 0.25f);
		historyCapacity = Mathf.Clamp(Mathf.CeilToInt(num3 / num2) + 12, 32, 768);
		posHistory = new Vector3[historyCapacity][];
		rotHistory = new Quaternion[historyCapacity][];
		scaleHistory = new Vector3[historyCapacity][];
		timeHistory = new float[historyCapacity];
		for (int i = 0; i < historyCapacity; i++)
		{
			posHistory[i] = (Vector3[])(object)new Vector3[sourceBones.Length];
			rotHistory[i] = (Quaternion[])(object)new Quaternion[sourceBones.Length];
			scaleHistory[i] = (Vector3[])(object)new Vector3[sourceBones.Length];
		}
		historyCount = 0;
		historyHead = -1;
		nextSample = 0f;
	}

	private void LateUpdate()
	{
		if ((Object)(object)player == (Object)null)
		{
			return;
		}
		float unscaledTime = Time.unscaledTime;
		if ((Object)(object)runtimeRoot != (Object)null && unscaledTime >= nextParticleSuppressionScan)
		{
			nextParticleSuppressionScan = unscaledTime + 0.25f;
			HardDisableCloneParticleSystems();
		}
		if (unscaledTime >= nextStructureCheck)
		{
			nextStructureCheck = unscaledTime + 0.05f;
			int num = ComputeVisualStructureSignature();
			if (num != visualStructureSignature)
			{
				if (num == pendingStructureSignature)
				{
					pendingStructureMatches++;
				}
				else
				{
					pendingStructureSignature = num;
					pendingStructureMatches = 1;
				}
				if (pendingStructureMatches >= 2)
				{
					RebuildRuntimeClones();
					return;
				}
			}
			else
			{
				pendingStructureSignature = visualStructureSignature;
				pendingStructureMatches = 0;
			}
		}
		ApplyEffectToggles();
		ApplyNetworkOrLocalColors();
		if (unscaledTime >= nextRendererSync)
		{
			MirrorRendererStates();
			nextRendererSync = unscaledTime + 0.1f;
		}
		if (!Plugin.Enabled.Value)
		{
			return;
		}
		if (historyCapacity <= 0)
		{
			AllocateHistory();
			if (historyCapacity <= 0)
			{
				return;
			}
		}
		float value = Plugin.SampleInterval.Value;
		if (value <= 0f || unscaledTime >= nextSample)
		{
			CapturePose(unscaledTime);
			nextSample = ((value > 0f) ? (unscaledTime + Mathf.Max(0.001f, value)) : unscaledTime);
		}
		float num2 = Mathf.Max(0f, Plugin.CloneUpdateInterval.Value);
		if (!(num2 > 0f) || !(unscaledTime < nextCloneUpdate))
		{
			nextCloneUpdate = ((num2 > 0f) ? (unscaledTime + num2) : unscaledTime);
			bool flag = ApplyDelayedPose(cloneBones[0], unscaledTime - Mathf.Max(0f, Plugin.Clone1Delay.Value));
			bool flag2 = ApplyDelayedPose(cloneBones[1], unscaledTime - Mathf.Max(0f, Plugin.Clone2Delay.Value));
			bool flag3 = ApplyDelayedPose(cloneBones[2], unscaledTime - Mathf.Max(0f, Plugin.Clone3Delay.Value));
			SetCloneDistanceReady(0, flag);
			SetCloneDistanceReady(1, flag2);
			SetCloneDistanceReady(2, flag3);
			SetCloneHistoryReady(0, flag);
			SetCloneHistoryReady(1, flag2);
			SetCloneHistoryReady(2, flag3);
			if (Plugin.CopyBlendShapes.Value)
			{
				CopyBlendShapes();
			}
		}
	}

	private void CapturePose(float now)
	{
		//IL_0129: Unknown result type (might be due to invalid IL or missing references)
		//IL_012e: Unknown result type (might be due to invalid IL or missing references)
		//IL_013c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0141: Unknown result type (might be due to invalid IL or missing references)
		//IL_014f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0154: Unknown result type (might be due to invalid IL or missing references)
		//IL_00ee: 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_0101: 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_0114: Unknown result type (might be due to invalid IL or missing references)
		//IL_0119: Unknown result type (might be due to invalid IL or missing references)
		if (sourceBones == null || sourceBones.Length == 0)
		{
			return;
		}
		if (historyCapacity <= 0 || posHistory == null || rotHistory == null || scaleHistory == null || timeHistory == null)
		{
			AllocateHistory();
			if (historyCapacity <= 0 || posHistory == null || rotHistory == null || scaleHistory == null || timeHistory == null)
			{
				return;
			}
		}
		historyHead = (historyHead + 1) % historyCapacity;
		if (historyCount < historyCapacity)
		{
			historyCount++;
		}
		Vector3[] array = posHistory[historyHead];
		Quaternion[] array2 = rotHistory[historyHead];
		Vector3[] array3 = scaleHistory[historyHead];
		for (int i = 0; i < sourceBones.Length; i++)
		{
			Transform val = sourceBones[i];
			if (!((Object)(object)val == (Object)null))
			{
				if (i == 0)
				{
					ref Vector3 reference = ref array[i];
					reference = val.position;
					ref Quaternion reference2 = ref array2[i];
					reference2 = val.rotation;
					ref Vector3 reference3 = ref array3[i];
					reference3 = val.localScale;
				}
				else
				{
					ref Vector3 reference4 = ref array[i];
					reference4 = val.localPosition;
					ref Quaternion reference5 = ref array2[i];
					reference5 = val.localRotation;
					ref Vector3 reference6 = ref array3[i];
					reference6 = val.localScale;
				}
			}
		}
		timeHistory[historyHead] = now;
	}

	private bool ApplyDelayedPose(Transform[] clone, float targetTime)
	{
		//IL_00d7: 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_00eb: Unknown result type (might be due to invalid IL or missing references)
		//IL_00f0: Unknown result type (might be due to invalid IL or missing references)
		//IL_00fb: Unknown result type (might be due to invalid IL or missing references)
		//IL_0109: 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_0114: Unknown result type (might be due to invalid IL or missing references)
		//IL_0130: Unknown result type (might be due to invalid IL or missing references)
		//IL_0139: Unknown result type (might be due to invalid IL or missing references)
		//IL_011c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0125: Unknown result type (might be due to invalid IL or missing references)
		//IL_0157: Unknown result type (might be due to invalid IL or missing references)
		//IL_0165: Unknown result type (might be due to invalid IL or missing references)
		//IL_016b: Unknown result type (might be due to invalid IL or missing references)
		if (clone == null || historyCount == 0)
		{
			return false;
		}
		int num = (historyHead - historyCount + 1 + historyCapacity) % historyCapacity;
		if (targetTime < timeHistory[num])
		{
			return false;
		}
		FindFrames(targetTime, out var a, out var b, out var lerp);
		Vector3[] array = posHistory[a];
		Vector3[] array2 = posHistory[b];
		Quaternion[] array3 = rotHistory[a];
		Quaternion[] array4 = rotHistory[b];
		Vector3[] array5 = scaleHistory[a];
		Vector3[] array6 = scaleHistory[b];
		ExpSmoothingFactor(Plugin.PositionSmoothing.Value);
		ExpSmoothingFactor(Plugin.RotationSmoothing.Value);
		int num2 = Mathf.Min(clone.Length, sourceBones.Length);
		for (int i = 0; i < num2; i++)
		{
			Transform val = clone[i];
			if (!((Object)(object)val == (Object)null))
			{
				Vector3 val2 = Vector3.LerpUnclamped(array[i], array2[i], lerp);
				Quaternion val3 = Quaternion.SlerpUnclamped(array3[i], array4[i], lerp);
				if (i == 0)
				{
					val.position = val2;
					val.rotation = val3;
				}
				else
				{
					val.localPosition = val2;
					val.localRotation = val3;
				}
				if (Plugin.CopyScale.Value)
				{
					val.localScale = Vector3.LerpUnclamped(array5[i], array6[i], lerp);
				}
			}
		}
		return true;
	}

	private void FindFrames(float target, out int a, out int b, out float lerp)
	{
		int num = (b = (a = (historyHead - historyCount + 1 + historyCapacity) % historyCapacity));
		lerp = 0f;
		int num2 = num;
		for (int i = 1; i < historyCount; i++)
		{
			int num3 = (num + i) % historyCapacity;
			if (timeHistory[num3] >= target)
			{
				a = num2;
				b = num3;
				float num4 = timeHistory[a];
				float num5 = timeHistory[b];
				lerp = ((num5 > num4) ? Mathf.Clamp01((target - num4) / (num5 - num4)) : 0f);
				return;
			}
			num2 = num3;
		}
		a = historyHead;
		b = historyHead;
	}

	private void ClearPoseHistory()
	{
		historyCount = 0;
		historyHead = -1;
		nextSample = 0f;
		posHistory = null;
		rotHistory = null;
		scaleHistory = null;
		timeHistory = null;
		historyCapacity = 0;
	}

	private void SetCloneDistanceReady(int cloneIndex, bool hasHistory)
	{
		//IL_0084: Unknown result type (might be due to invalid IL or missing references)
		//IL_008f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0094: 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)
		if (cloneIndex < 0 || cloneIndex >= cloneDistanceReady.Length)
		{
			return;
		}
		if (!hasHistory)
		{
			cloneDistanceReady[cloneIndex] = false;
			return;
		}
		float num = Mathf.Max(0f, Plugin.MinimumVisibleDistance.Value);
		if (num <= 0f || (Object)(object)visualRoot == (Object)null || cloneBones[cloneIndex] == null || cloneBones[cloneIndex].Length == 0 || (Object)(object)cloneBones[cloneIndex][0] == (Object)null)
		{
			cloneDistanceReady[cloneIndex] = true;
			return;
		}
		Vector3 val = cloneBones[cloneIndex][0].position - visualRoot.position;
		float sqrMagnitude = ((Vector3)(ref val)).sqrMagnitude;
		float num2 = ((Plugin.MinimumVisibleDistanceHysteresis != null) ? Mathf.Max(0f, Plugin.MinimumVisibleDistanceHysteresis.Value) : 0.03f);
		float num3 = (cloneDistanceReady[cloneIndex] ? num : (num + num2));
		cloneDistanceReady[cloneIndex] = sqrMagnitude >= num3 * num3;
	}

	private void SetCloneHistoryReady(int cloneIndex, bool ready)
	{
		if (cloneIndex < 0 || cloneIndex >= cloneHistoryReady.Length || cloneHistoryReady[cloneIndex] == ready)
		{
			return;
		}
		cloneHistoryReady[cloneIndex] = ready;
		for (int i = 0; i < rendererMaps.Count; i++)
		{
			RendererMap rendererMap = rendererMaps[i];
			if (cloneIndex >= rendererMap.clones.Count)
			{
				continue;
			}
			SkinnedMeshRenderer val = rendererMap.clones[cloneIndex];
			if ((Object)(object)val == (Object)null)
			{
				continue;
			}
			bool flag = ready && cloneIndex < cloneDistanceReady.Length && cloneDistanceReady[cloneIndex];
			if ((Object)(object)rendererMap.source != (Object)null)
			{
				if (Plugin.MirrorRendererToggles.Value)
				{
					flag &= ((Renderer)rendererMap.source).enabled;
				}
				if (Plugin.MirrorRendererActiveState.Value)
				{
					flag &= ((Component)rendererMap.source).gameObject.activeInHierarchy;
				}
			}
			((Renderer)val).enabled = flag;
		}
	}

	private int ComputeVisualStructureSignature()
	{
		if ((Object)(object)visualRoot == (Object)null)
		{
			return 0;
		}
		int num = 17;
		int num2 = 0;
		SkinnedMeshRenderer[] componentsInChildren = ((Component)visualRoot).GetComponentsInChildren<SkinnedMeshRenderer>(true);
		foreach (SkinnedMeshRenderer val in componentsInChildren)
		{
			if (!((Object)(object)val == (Object)null) && !Plugin.IsOurRuntimeObject(((Component)val).transform) && !Plugin.IsLegacyAfterimageObject(((Component)val).transform))
			{
				num2++;
				num = num * 31 + ((Object)val).GetInstanceID();
				num = num * 31 + (((Object)(object)val.sharedMesh != (Object)null) ? ((Object)val.sharedMesh).GetInstanceID() : 0);
			}
		}
		return num * 31 + num2;
	}

	private void MirrorRendererStates()
	{
		for (int i = 0; i < rendererMaps.Count; i++)
		{
			RendererMap rendererMap = rendererMaps[i];
			if ((Object)(object)rendererMap.source == (Object)null)
			{
				continue;
			}
			bool flag = true;
			if (Plugin.MirrorRendererToggles.Value)
			{
				flag &= ((Renderer)rendererMap.source).enabled;
			}
			if (Plugin.MirrorRendererActiveState.Value)
			{
				flag &= ((Component)rendererMap.source).gameObject.activeInHierarchy;
			}
			for (int j = 0; j < rendererMap.clones.Count; j++)
			{
				SkinnedMeshRenderer val = rendererMap.clones[j];
				if (!((Object)(object)val == (Object)null))
				{
					bool flag2 = j < 0 || j >= cloneHistoryReady.Length || cloneHistoryReady[j];
					bool flag3 = j < 0 || j >= cloneDistanceReady.Length || cloneDistanceReady[j];
					((Renderer)val).enabled = flag && flag2 && flag3;
					if (Plugin.MirrorMeshChanges.Value && (Object)(object)val.sharedMesh != (Object)(object)rendererMap.source.sharedMesh)
					{
						val.sharedMesh = rendererMap.source.sharedMesh;
					}
				}
			}
		}
	}

	private void CopyBlendShapes()
	{
		for (int i = 0; i < rendererMaps.Count; i++)
		{
			RendererMap rendererMap = rendererMaps[i];
			if ((Object)(object)rendererMap.source == (Object)null || (Object)(object)rendererMap.source.sharedMesh == (Object)null)
			{
				continue;
			}
			int blendShapeCount = rendererMap.source.sharedMesh.blendShapeCount;
			for (int j = 0; j < rendererMap.clones.Count; j++)
			{
				SkinnedMeshRenderer val = rendererMap.clones[j];
				if (!((Object)(object)val == (Object)null) && !((Object)(object)val.sharedMesh == (Object)null))
				{
					int num = Mathf.Min(blendShapeCount, val.sharedMesh.blendShapeCount);
					for (int k = 0; k < num; k++)
					{
						val.SetBlendShapeWeight(k, rendererMap.source.GetBlendShapeWeight(k));
					}
				}
			}
		}
	}

	private void ApplyNetworkOrLocalColors()
	{
		//IL_006b: Unknown result type (might be due to invalid IL or missing references)
		//IL_006d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0098: Unknown result type (might be due to invalid IL or missing references)
		//IL_00a0: Unknown result type (might be due to invalid IL or missing references)
		//IL_00a8: 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)
		//IL_00b0: Unknown result type (might be due to invalid IL or missing references)
		//IL_00b6: Unknown result type (might be due to invalid IL or missing references)
		//IL_00b7: Unknown result type (might be due to invalid IL or missing references)
		//IL_00bd: Unknown result type (might be due to invalid IL or missing references)
		//IL_00be: Unknown result type (might be due to invalid IL or missing references)
		//IL_0079: 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_0087: Unknown result type (might be due to invalid IL or missing references)
		//IL_0089: 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_005f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0060: Unknown result type (might be due to invalid IL or missing references)
		//IL_0062: Unknown result type (might be due to invalid IL or missing references)
		//IL_0063: 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)
		Plugin.GetLocalEffectiveColors(out var c, out var c2, out var c3);
		try
		{
			ClientController instance = ClientController.Instance;
			if ((Object)(object)instance != (Object)null && instance.Connected && OwnerPlayerId != 0 && OwnerPlayerId != instance.LocalID && (Object)(object)Plugin.Instance != (Object)null && Plugin.Instance.TryGetSyncedColors(OwnerPlayerId, out var c4, out var c5, out var c6))
			{
				c = c4;
				c2 = c5;
				c3 = c6;
			}
		}
		catch
		{
		}
		if (!SameColor(c, lastColor1) || !SameColor(c2, lastColor2) || !SameColor(c3, lastColor3))
		{
			ApplyColorToClone(0, c);
			ApplyColorToClone(1, c2);
			ApplyColorToClone(2, c3);
			lastColor1 = c;
			lastColor2 = c2;
			lastColor3 = c3;
		}
	}

	private void ApplyColorToClone(int cloneIndex, Color color)
	{
		//IL_0051: Unknown result type (might be due to invalid IL or missing references)
		for (int i = 0; i < rendererMaps.Count; i++)
		{
			RendererMap rendererMap = rendererMaps[i];
			if (cloneIndex >= rendererMap.clones.Count)
			{
				continue;
			}
			SkinnedMeshRenderer val = rendererMap.clones[cloneIndex];
			if ((Object)(object)val == (Object)null)
			{
				continue;
			}
			Material[] sharedMaterials = ((Renderer)val).sharedMaterials;
			for (int j = 0; j < sharedMaterials.Length; j++)
			{
				if ((Object)(object)sharedMaterials[j] != (Object)null)
				{
					ApplyColor(sharedMaterials[j], color);
				}
			}
		}
	}

	private static void ApplyColor(Material m, Color c)
	{
		//IL_0013: 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_0045: Unknown result type (might be due to invalid IL or missing references)
		//IL_0058: Unknown result type (might be due to invalid IL or missing references)
		//IL_0059: Unknown result type (might be due to invalid IL or missing references)
		//IL_006c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0085: Unknown result type (might be due to invalid IL or missing references)
		if (m.HasProperty("_Color"))
		{
			m.SetColor("_Color", c);
		}
		if (m.HasProperty("_BaseColor"))
		{
			m.SetColor("_BaseColor", c);
		}
		if (m.HasProperty("_TintColor"))
		{
			m.SetColor("_TintColor", c);
		}
		if (m.HasProperty("_EmissionColor"))
		{
			Color val = c;
			val.a = 1f;
			m.SetColor("_EmissionColor", val);
		}
		if (m.HasProperty("_Tint"))
		{
			m.SetColor("_Tint", c);
		}
	}

	private void DestroyRuntime()
	{
		RestoreEffectStates();
		rendererMaps.Clear();
		particleStates.Clear();
		trailStates.Clear();
		for (int i = 0; i < ownedMaterials.Count; i++)
		{
			if ((Object)(object)ownedMaterials[i] != (Object)null)
			{
				Object.Destroy((Object)(object)ownedMaterials[i]);
			}
		}
		ownedMaterials.Clear();
		if ((Object)(object)runtimeRoot != (Object)null)
		{
			runtimeRoot.SetActive(false);
			Object.Destroy((Object)(object)runtimeRoot);
		}
		runtimeRoot = null;
		sourceBones = (Transform[])(object)new Transform[0];
		sourcePaths = new string[0];
		cloneBones = new Transform[3][];
		cloneHistoryReady[0] = false;
		cloneHistoryReady[1] = false;
		cloneHistoryReady[2] = false;
		cloneDistanceReady[0] = false;
		cloneDistanceReady[1] = false;
		cloneDistanceReady[2] = false;
		ClearPoseHistory();
	}

	private void RestoreEffectStates()
	{
		for (int i = 0; i < particleStates.Count; i++)
		{
			Renderer renderer = particleStates[i].renderer;
			if ((Object)(object)renderer != (Object)null)
			{
				renderer.enabled = particleStates[i].originalEnabled;
			}
		}
		for (int j = 0; j < trailStates.Count; j++)
		{
			Renderer renderer2 = trailStates[j].renderer;
			if ((Object)(object)renderer2 != (Object)null)
			{
				renderer2.enabled = trailStates[j].originalEnabled;
			}
		}
	}

	private void OnDestroy()
	{
		DestroyRuntime();
	}

	private static float ExpSmoothingFactor(float speed)
	{
		if (speed <= 0f)
		{
			return 1f;
		}
		return 1f - Mathf.Exp((0f - speed) * Time.unscaledDeltaTime);
	}

	private static string RelativePath(Transform root, Transform t)
	{
		if ((Object)(object)root == (Object)(object)t)
		{
			return "";
		}
		List<string> list = new List<string>();
		Transform val = t;
		while ((Object)(object)val != (Object)null && (Object)(object)val != (Object)(object)root)
		{
			list.Add(((Object)val).name);
			val = val.parent;
		}
		if ((Object)(object)val != (Object)(object)root)
		{
			return "";
		}
		list.Reverse();
		return string.Join("/", list.ToArray());
	}

	private static string ParentPath(string path)
	{
		int num = path.LastIndexOf('/');
		if (num >= 0)
		{
			return path.Substring(0, num);
		}
		return "";
	}

	private static bool SameColor(Color a, Color b)
	{
		if (Mathf.Approximately(a.r, b.r) && Mathf.Approximately(a.g, b.g) && Mathf.Approximately(a.b, b.b))
		{
			return Mathf.Approximately(a.a, b.a);
		}
		return false;
	}
}