Decompiled source of 4 Player Co oP Mod v0.20.7

BepInEx/plugins/FateDiagnostics/FateDiagnostics.dll

Decompiled 4 days ago
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Net;
using System.Net.NetworkInformation;
using System.Net.Sockets;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Serialization.Json;
using System.Runtime.Versioning;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using BepInEx;
using BepInEx.Configuration;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Steamworks;
using TMPro;
using UnityEngine;
using UnityEngine.AI;
using UnityEngine.EventSystems;
using UnityEngine.Events;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;
using UnityEngine.SceneManagement;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("FateDiagnostics")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("0.20.6.0")]
[assembly: AssemblyInformationalVersion("0.20.6")]
[assembly: AssemblyProduct("FateDiagnostics")]
[assembly: AssemblyTitle("FateDiagnostics")]
[assembly: AssemblyVersion("0.20.6.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace FateDiagnostics
{
	internal static class ActorCodec
	{
		internal static int Activity(ActorState actor)
		{
			int hash = 17;
			float[] position = actor.position;
			for (int i = 0; i < position.Length; i++)
			{
				Number(position[i]);
			}
			position = actor.rotation;
			for (int i = 0; i < position.Length; i++)
			{
				Number(position[i]);
			}
			Number(actor.health);
			Number(actor.shrineSeconds);
			Add(actor.state);
			Add(actor.dead ? 1 : 0);
			Number(actor.animation.speed);
			AnimationLayer[] layers = actor.animation.layers;
			foreach (AnimationLayer animationLayer in layers)
			{
				Add(animationLayer.hash);
				Number(animationLayer.weight);
				if (!animationLayer.loop)
				{
					Number(animationLayer.time);
				}
			}
			AnimationValue[] values = actor.animation.values;
			foreach (AnimationValue obj in values)
			{
				Add(obj.hash);
				Add(obj.type);
				Add(obj.integer);
				Number(obj.value);
				Add(obj.flag ? 1 : 0);
			}
			return hash;
			void Add(int value)
			{
				hash = hash * 31 + value;
			}
			void Number(float value)
			{
				Add((int)Math.Round(value * 100f));
			}
		}

		internal static string Encode(ActorSnapshot packet)
		{
			if (packet?.scope == null || packet.actors == null || packet.actors.Length > 64 || packet.sequence < 0)
			{
				throw new IOException("Invalid actor snapshot");
			}
			using MemoryStream memoryStream = new MemoryStream();
			using BinaryWriter binaryWriter = new BinaryWriter(memoryStream);
			binaryWriter.Write((byte)2);
			MotionCodec.Text(binaryWriter, packet.scope.scene, 128);
			MotionCodec.Text(binaryWriter, packet.scope.expedition, 128);
			binaryWriter.Write(packet.scope.campaign);
			binaryWriter.Write(packet.scope.realm);
			binaryWriter.Write(packet.scope.depth);
			MotionCodec.Text(binaryWriter, packet.generation, 64);
			binaryWriter.Write(packet.sequence);
			binaryWriter.Write(packet.actors.Length);
			ActorState[] actors = packet.actors;
			foreach (ActorState actorState in actors)
			{
				MotionCodec.Text(binaryWriter, actorState.id, 128);
				MotionCodec.Vector(binaryWriter, actorState.position, 3);
				MotionCodec.Vector(binaryWriter, actorState.rotation, 4);
				if (float.IsNaN(actorState.health) || float.IsInfinity(actorState.health) || actorState.health < 0f || actorState.health > 100000000f)
				{
					throw new IOException("Invalid actor health");
				}
				if (float.IsNaN(actorState.shrineSeconds) || float.IsInfinity(actorState.shrineSeconds) || actorState.shrineSeconds < -1f || actorState.shrineSeconds > 3600f)
				{
					throw new IOException("Invalid shrine timer");
				}
				binaryWriter.Write(actorState.health);
				binaryWriter.Write(actorState.shrineSeconds);
				binaryWriter.Write(actorState.state);
				binaryWriter.Write(actorState.dead);
				MotionCodec.WriteAnimation(binaryWriter, actorState.animation);
			}
			byte[] array = memoryStream.ToArray();
			if (array.Length > 98304)
			{
				throw new IOException("Actor snapshot too large");
			}
			return Convert.ToBase64String(array);
		}

		internal static ActorSnapshot Decode(string value)
		{
			if (value == null || value.Length > 131072)
			{
				throw new IOException("Actor snapshot too large");
			}
			using MemoryStream memoryStream = new MemoryStream(Convert.FromBase64String(value));
			using BinaryReader binaryReader = new BinaryReader(memoryStream);
			if (binaryReader.ReadByte() != 2)
			{
				throw new IOException("Actor protocol mismatch");
			}
			ActorSnapshot actorSnapshot = new ActorSnapshot
			{
				scope = new WorldScope
				{
					scene = MotionCodec.Text(binaryReader, 128),
					expedition = MotionCodec.Text(binaryReader, 128),
					campaign = binaryReader.ReadInt32(),
					realm = binaryReader.ReadInt32(),
					depth = binaryReader.ReadInt32()
				},
				generation = MotionCodec.Text(binaryReader, 64),
				sequence = binaryReader.ReadInt64()
			};
			if (!Guid.TryParseExact(actorSnapshot.generation, "N", out var _) || actorSnapshot.sequence < 0)
			{
				throw new IOException("Invalid actor generation");
			}
			actorSnapshot.actors = new ActorState[MotionCodec.Count(binaryReader, 64)];
			HashSet<string> hashSet = new HashSet<string>();
			for (int i = 0; i < actorSnapshot.actors.Length; i++)
			{
				ActorState actorState = new ActorState
				{
					id = MotionCodec.Text(binaryReader, 128),
					position = MotionCodec.Vector(binaryReader, 3),
					rotation = MotionCodec.Vector(binaryReader, 4),
					health = binaryReader.ReadSingle(),
					shrineSeconds = binaryReader.ReadSingle(),
					state = binaryReader.ReadInt32(),
					dead = binaryReader.ReadBoolean(),
					animation = MotionCodec.ReadAnimation(binaryReader)
				};
				if (string.IsNullOrEmpty(actorState.id) || !hashSet.Add(actorState.id) || float.IsNaN(actorState.health) || float.IsInfinity(actorState.health) || actorState.health < 0f || actorState.health > 100000000f || actorState.state < 0 || actorState.state > 33 || float.IsNaN(actorState.shrineSeconds) || float.IsInfinity(actorState.shrineSeconds) || actorState.shrineSeconds < -1f || actorState.shrineSeconds > 3600f)
				{
					throw new IOException("Invalid actor state");
				}
				float[] rotation = actorState.rotation;
				float num = rotation[0] * rotation[0] + rotation[1] * rotation[1] + rotation[2] * rotation[2] + rotation[3] * rotation[3];
				if (num < 0.5f || num > 1.5f)
				{
					throw new IOException("Invalid actor rotation");
				}
				actorSnapshot.actors[i] = actorState;
			}
			if (memoryStream.Position != memoryStream.Length)
			{
				throw new IOException("Trailing actor data");
			}
			return actorSnapshot;
		}
	}
	[Serializable]
	internal sealed class ActorCatalog
	{
		public WorldScope scope;

		public string generation;

		public ActorDefinition[] actors;
	}
	[Serializable]
	internal sealed class ActorDefinition
	{
		public string id;

		public string key;

		public string name;

		public string questOwner;

		public string questToken;

		public int slot;

		public bool npc;

		public bool spawn;

		public bool unique;

		public int level;

		public int rank;

		public int kind;

		public int alignment;

		public float[] position;

		public float[] rotation;

		public float[] scale;
	}
	internal sealed class ActorSnapshot
	{
		internal WorldScope scope;

		internal string generation;

		internal long sequence;

		internal ActorState[] actors;
	}
	internal sealed class ActorState
	{
		internal string id;

		internal float[] position;

		internal float[] rotation;

		internal float health;

		internal float shrineSeconds = -1f;

		internal int state;

		internal bool dead;

		internal AnimationPose animation;
	}
	[Serializable]
	internal sealed class ActorHit
	{
		public WorldScope scope;

		public string generation;

		public string target;

		public string ticket;

		public int amount;

		public int type;

		public bool raw;

		public bool critical;

		public bool respond;
	}
	internal sealed class ActorReceipts
	{
		private readonly HashSet<string> seen = new HashSet<string>();

		internal bool Accept(string ticket)
		{
			if (!Guid.TryParseExact(ticket, "N", out var _))
			{
				throw new IOException("Invalid hit ticket");
			}
			if (seen.Count >= 65536 && !seen.Contains(ticket))
			{
				throw new IOException("Combat receipt limit reached; reconnect");
			}
			return seen.Add(ticket);
		}
	}
	internal static class AggroPreference
	{
		internal static int Pick(int localStrength, int remoteStrength, bool localValid, bool remoteValid, bool localCloser, double roll)
		{
			if (roll >= 0.8 || (!localValid && !remoteValid))
			{
				return -1;
			}
			if (!localValid)
			{
				return 1;
			}
			if (!remoteValid)
			{
				return 0;
			}
			if (localStrength != remoteStrength)
			{
				return (localStrength <= remoteStrength) ? 1 : 0;
			}
			return (!localCloser) ? 1 : 0;
		}
	}
	internal static class AssetIdentity
	{
		internal static string Name(string value)
		{
			string text = value ?? "";
			while (true)
			{
				if (text.EndsWith(" (Instance)", StringComparison.Ordinal))
				{
					text = text.Substring(0, text.Length - 11);
					continue;
				}
				if (!text.EndsWith(" Instance", StringComparison.Ordinal))
				{
					break;
				}
				text = text.Substring(0, text.Length - 9);
			}
			return text;
		}

		internal static string Key(string value)
		{
			if (value == null)
			{
				return "";
			}
			int num = value.IndexOf('|');
			if (num >= 0)
			{
				return Name(value.Substring(0, num)) + value.Substring(num);
			}
			return Name(value);
		}
	}
	internal sealed class ClassMarkerTint
	{
		private sealed class SelectionMaterial
		{
			internal Material[] original;

			internal Material[] selected;

			internal int layer;

			internal MaterialPropertyBlock properties;

			internal readonly List<Material> owned = new List<Material>();
		}

		private static readonly Dictionary<int, Texture2D[]> recoloured = new Dictionary<int, Texture2D[]>();

		private readonly Dictionary<Renderer, MaterialPropertyBlock> originals = new Dictionary<Renderer, MaterialPropertyBlock>();

		private readonly MaterialPropertyBlock paint = new MaterialPropertyBlock();

		private Transform owner;

		private int last = -1;

		private float next;

		private bool wasSelected;

		private bool wasDowned;

		private bool selectionHeld;

		private float selectedAt;

		private readonly Dictionary<Renderer, SelectionMaterial> selectionMaterials = new Dictionary<Renderer, SelectionMaterial>();

		private static readonly Dictionary<Renderer, SelectionMaterial> nativeAppearance = new Dictionary<Renderer, SelectionMaterial>();

		internal static Material[] NativeMaterials(Renderer renderer)
		{
			if (!nativeAppearance.TryGetValue(renderer, out var value))
			{
				return renderer.sharedMaterials;
			}
			return value.original;
		}

		internal static int NativeLayer(GameObject node)
		{
			Renderer component = node.GetComponent<Renderer>();
			if (!Object.op_Implicit((Object)(object)component) || !nativeAppearance.TryGetValue(component, out var value))
			{
				return node.layer;
			}
			return value.layer;
		}

		internal static MaterialPropertyBlock NativeProperties(Renderer renderer)
		{
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			//IL_001c: Expected O, but got Unknown
			if (nativeAppearance.TryGetValue(renderer, out var value))
			{
				return value.properties;
			}
			MaterialPropertyBlock val = new MaterialPropertyBlock();
			renderer.GetPropertyBlock(val);
			return val;
		}

		private static string Key(string name)
		{
			return AssetIdentity.Name(name.StartsWith("FateRemote.", StringComparison.Ordinal) ? name.Substring(11) : name);
		}

		internal void Tick(Transform target, int choice, bool selected = false, bool self = false, bool downed = false)
		{
			//IL_0107: Unknown result type (might be due to invalid IL or missing references)
			//IL_0247: Unknown result type (might be due to invalid IL or missing references)
			//IL_024e: Expected O, but got Unknown
			//IL_04de: Unknown result type (might be due to invalid IL or missing references)
			//IL_0309: Unknown result type (might be due to invalid IL or missing references)
			//IL_030e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0319: Unknown result type (might be due to invalid IL or missing references)
			//IL_0326: Expected O, but got Unknown
			//IL_0352: Unknown result type (might be due to invalid IL or missing references)
			//IL_036d: Unknown result type (might be due to invalid IL or missing references)
			//IL_037e: Unknown result type (might be due to invalid IL or missing references)
			//IL_038f: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)owner != (Object)(object)target)
			{
				Restore();
				owner = target;
				last = -1;
				next = 0f;
			}
			if (selected && !selectionHeld)
			{
				selectedAt = Time.unscaledTime;
			}
			selectionHeld = selected;
			float num = Mathf.SmoothStep(0f, 1f, Mathf.Clamp01((Time.unscaledTime - selectedAt - 0.6f) / 1.4f));
			float num2 = Mathf.Lerp(1f, self ? 0f : 0.4f, num);
			selected = downed || (selected && (!self || num < 1f));
			if (downed)
			{
				num2 = 1f;
			}
			if (selected)
			{
				foreach (KeyValuePair<Renderer, SelectionMaterial> selectionMaterial in selectionMaterials)
				{
					if (Object.op_Implicit((Object)(object)selectionMaterial.Key))
					{
						selectionMaterial.Key.GetPropertyBlock(paint);
						paint.SetColor("_BaseColor", new Color(num2, num2, num2, 1f));
						selectionMaterial.Key.SetPropertyBlock(paint);
					}
				}
			}
			if (!Object.op_Implicit((Object)(object)target) || (Time.unscaledTime < next && choice == last && selected == wasSelected && downed == wasDowned))
			{
				return;
			}
			next = Time.unscaledTime + 1f;
			last = choice;
			wasSelected = selected;
			wasDowned = downed;
			Renderer[] componentsInChildren = ((Component)target).GetComponentsInChildren<Renderer>(true);
			foreach (Renderer val in componentsInChildren)
			{
				selectionMaterials.TryGetValue(val, out var value);
				Material[] array = value?.original ?? val.sharedMaterials;
				Texture val2 = null;
				Material[] array2 = array;
				foreach (Material val3 in array2)
				{
					if (Object.op_Implicit((Object)(object)val3) && Key(((Object)val3).name) == "M_Stencil_Marker_Player")
					{
						val2 = val3.GetTexture("_MainTex");
						break;
					}
				}
				if (!Object.op_Implicit((Object)(object)val2))
				{
					continue;
				}
				if (!originals.TryGetValue(val, out var value2))
				{
					value2 = new MaterialPropertyBlock();
					val.GetPropertyBlock(value2);
					originals[val] = value2;
				}
				if (selected)
				{
					if (value == null)
					{
						Shader val4 = Shader.Find("Tableflip/Basic/Shader_ParticleEmissiveOneTex");
						if (!Object.op_Implicit((Object)(object)val4))
						{
							Debug.LogWarning((object)"FATE selected native marker: game particle shader unavailable");
							continue;
						}
						value = new SelectionMaterial
						{
							original = array,
							selected = (Material[])array.Clone(),
							layer = ((Component)val).gameObject.layer,
							properties = value2
						};
						for (int k = 0; k < array.Length; k++)
						{
							if (Object.op_Implicit((Object)(object)array[k]) && !(Key(((Object)array[k]).name) != "M_Stencil_Marker_Player"))
							{
								Material val5 = new Material(val4)
								{
									name = "Fate.SelectedNativeMarker",
									renderQueue = 4000
								};
								val5.SetTexture("_BaseMap", (Texture)(object)ColourTexture(val2, downed ? 6 : 5));
								val5.SetTextureScale("_BaseMap", array[k].GetTextureScale("_MainTex"));
								val5.SetTextureOffset("_BaseMap", array[k].GetTextureOffset("_MainTex"));
								val5.SetColor("_BaseColor", Color.white);
								val5.SetColor("_EmissionColor", Color.white);
								val5.SetFloat("_Surface", 1f);
								val5.SetFloat("_SrcBlend", 1f);
								val5.SetFloat("_DstBlend", 1f);
								val5.SetFloat("_ZTest", 8f);
								val5.SetFloat("_ZWrite", 0f);
								val5.SetFloat("_Cull", 0f);
								val5.EnableKeyword("_SURFACE_TYPE_TRANSPARENT");
								val5.SetShaderPassEnabled("DepthOnly", false);
								val5.SetShaderPassEnabled("DepthNormalsOnly", false);
								val5.SetShaderPassEnabled("ShadowCaster", false);
								value.selected[k] = val5;
								value.owned.Add(val5);
							}
						}
						selectionMaterials[val] = value;
						nativeAppearance[val] = value;
						Debug.Log((object)"FATE selected marker uses native mesh and game particle shader");
					}
					val.sharedMaterials = value.selected;
					((Component)val).gameObject.layer = 0;
					val.GetPropertyBlock(paint);
					paint.SetTexture("_BaseMap", (Texture)(object)ColourTexture(val2, downed ? 6 : 5));
					paint.SetColor("_BaseColor", new Color(num2, num2, num2, 1f));
					val.SetPropertyBlock(paint);
					continue;
				}
				if (value != null)
				{
					val.sharedMaterials = value.original;
					((Component)val).gameObject.layer = value.layer;
				}
				val.SetPropertyBlock(value2);
				if (choice != 0)
				{
					Texture2D val6 = ColourTexture(val2, choice);
					if (Object.op_Implicit((Object)(object)val6))
					{
						val.GetPropertyBlock(paint);
						paint.SetTexture("_MainTex", (Texture)(object)val6);
						val.SetPropertyBlock(paint);
					}
				}
			}
		}

		private static Texture2D ColourTexture(Texture source, int choice)
		{
			//IL_0087: Unknown result type (might be due to invalid IL or missing references)
			//IL_008c: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a5: 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_00b1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bc: Expected O, but got Unknown
			//IL_00d5: Unknown result type (might be due to invalid IL or missing references)
			//IL_0100: Unknown result type (might be due to invalid IL or missing references)
			//IL_0105: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f2: Unknown result type (might be due to invalid IL or missing references)
			//IL_0113: Unknown result type (might be due to invalid IL or missing references)
			//IL_0118: Unknown result type (might be due to invalid IL or missing references)
			//IL_011a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0121: Unknown result type (might be due to invalid IL or missing references)
			//IL_0128: Unknown result type (might be due to invalid IL or missing references)
			//IL_013f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0146: Unknown result type (might be due to invalid IL or missing references)
			//IL_014d: Unknown result type (might be due to invalid IL or missing references)
			//IL_019d: Unknown result type (might be due to invalid IL or missing references)
			//IL_019f: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a6: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ab: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c6: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d1: Unknown result type (might be due to invalid IL or missing references)
			//IL_01dc: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e5: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ec: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f1: Unknown result type (might be due to invalid IL or missing references)
			//IL_02f6: Unknown result type (might be due to invalid IL or missing references)
			//IL_02fb: Unknown result type (might be due to invalid IL or missing references)
			//IL_02fd: Unknown result type (might be due to invalid IL or missing references)
			//IL_031d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0328: Unknown result type (might be due to invalid IL or missing references)
			//IL_0331: Unknown result type (might be due to invalid IL or missing references)
			//IL_0340: Unknown result type (might be due to invalid IL or missing references)
			//IL_0345: Unknown result type (might be due to invalid IL or missing references)
			//IL_028f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0294: Unknown result type (might be due to invalid IL or missing references)
			//IL_02b9: Unknown result type (might be due to invalid IL or missing references)
			choice = ((choice >= 5 && choice <= 6) ? choice : PartyClass.Clamp(choice));
			if (!recoloured.TryGetValue(((Object)source).GetInstanceID(), out var value))
			{
				value = (Texture2D[])(object)new Texture2D[7];
				recoloured[((Object)source).GetInstanceID()] = value;
			}
			if (Object.op_Implicit((Object)(object)value[choice]))
			{
				return value[choice];
			}
			RenderTexture active = RenderTexture.active;
			RenderTexture val = null;
			Texture2D val2 = null;
			try
			{
				val = RenderTexture.GetTemporary(source.width, source.height, 0, (RenderTextureFormat)0, (RenderTextureReadWrite)2);
				Graphics.Blit(source, val);
				RenderTexture.active = val;
				val2 = new Texture2D(source.width, source.height, (TextureFormat)4, false)
				{
					name = "Fate.ClassMarker." + choice,
					wrapMode = source.wrapMode,
					filterMode = source.filterMode
				};
				val2.ReadPixels(new Rect(0f, 0f, (float)source.width, (float)source.height), 0, 0);
				Color32[] pixels = val2.GetPixels32();
				Color val3 = (Color)(choice switch
				{
					5 => Color.yellow, 
					6 => DownedState.ReviveColour, 
					_ => PartyClass.RingTint(choice), 
				});
				for (int i = 0; i < pixels.Length; i++)
				{
					Color32 val4 = pixels[i];
					byte b = Math.Max(val4.r, Math.Max(val4.g, val4.b));
					float num = ((b == 0) ? 0f : ((float)(int)Math.Min(val4.r, Math.Min(val4.g, val4.b)) / (float)(int)b));
					float num2 = ((choice >= 5) ? 0f : ((choice == 0) ? 1f : (0.72f * Mathf.SmoothStep(0f, 1f, num * num))));
					Color val5 = Color.Lerp(val3, Color.white, num2);
					float num3 = ((choice == 0) ? ((float)(int)b) : ((float)(int)b * 0.94f));
					pixels[i] = new Color32((byte)(num3 * val5.r), (byte)(num3 * val5.g), (byte)(num3 * val5.b), val4.a);
				}
				if (choice == 6)
				{
					Color32[] array = (Color32[])pixels.Clone();
					int width = ((Texture)val2).width;
					int height = ((Texture)val2).height;
					int num4 = Math.Max(3, width / 45);
					for (int j = 0; j < height; j++)
					{
						for (int k = 0; k < width; k++)
						{
							float num5 = 0f;
							for (int l = -num4; l <= num4; l += 2)
							{
								for (int m = -num4; m <= num4; m += 2)
								{
									int num6 = k + m;
									int num7 = j + l;
									if (num6 >= 0 && num7 >= 0 && num6 < width && num7 < height)
									{
										Color32 val6 = array[num7 * width + num6];
										float num8 = Mathf.Exp((float)(-(m * m + l * l)) / ((float)(num4 * num4) * 0.5f));
										num5 = Mathf.Max(num5, (float)(int)val6.r * num8 * 0.55f);
									}
								}
							}
							Color32 val7 = array[j * width + k];
							float num9 = Mathf.Max((float)(int)val7.r, num5);
							pixels[j * width + k] = new Color32((byte)num9, (byte)(num9 * val3.g), (byte)(num9 * val3.b), Math.Max(val7.a, (byte)num5));
						}
					}
				}
				val2.SetPixels32(pixels);
				val2.Apply(false, true);
				value[choice] = val2;
				return val2;
			}
			catch (Exception ex)
			{
				if (Object.op_Implicit((Object)(object)val2))
				{
					Object.Destroy((Object)(object)val2);
				}
				Debug.LogWarning((object)("FATE class marker recolour failed: " + ex.GetType().Name));
				return null;
			}
			finally
			{
				RenderTexture.active = active;
				if (Object.op_Implicit((Object)(object)val))
				{
					RenderTexture.ReleaseTemporary(val);
				}
			}
		}

		internal void Restore()
		{
			foreach (KeyValuePair<Renderer, SelectionMaterial> selectionMaterial in selectionMaterials)
			{
				if (Object.op_Implicit((Object)(object)selectionMaterial.Key))
				{
					selectionMaterial.Key.sharedMaterials = selectionMaterial.Value.original;
					((Component)selectionMaterial.Key).gameObject.layer = selectionMaterial.Value.layer;
				}
				nativeAppearance.Remove(selectionMaterial.Key);
				foreach (Material item in selectionMaterial.Value.owned)
				{
					if (Object.op_Implicit((Object)(object)item))
					{
						Object.Destroy((Object)(object)item);
					}
				}
			}
			selectionMaterials.Clear();
			foreach (KeyValuePair<Renderer, MaterialPropertyBlock> original in originals)
			{
				if (Object.op_Implicit((Object)(object)original.Key))
				{
					original.Key.SetPropertyBlock(original.Value);
				}
			}
			originals.Clear();
			owner = null;
			wasSelected = false;
			selectionHeld = false;
		}
	}
	internal sealed class CombatVisuals
	{
		private sealed class RemoteMotion
		{
			internal VisualMotion packet;

			internal long revision;

			internal long applied;
		}

		private sealed class Tracked
		{
			internal Component source;

			internal string id;

			internal string prefab;

			internal ParticleConfig[] particles;
		}

		private static CombatVisuals current;

		private readonly LoopbackSession network;

		private readonly Func<WorldScope> scope;

		private readonly Func<IReadOnlyList<Component>> actors;

		private readonly Action<string> log;

		private readonly Dictionary<int, Tracked> localEffects = new Dictionary<int, Tracked>();

		private readonly Dictionary<string, GameObject> remoteEffects = new Dictionary<string, GameObject>();

		private readonly Dictionary<string, GameObject> prefabs = new Dictionary<string, GameObject>(StringComparer.OrdinalIgnoreCase);

		private readonly Dictionary<string, Vector3> targets = new Dictionary<string, Vector3>();

		private readonly HashSet<string> missing = new HashSet<string>(StringComparer.OrdinalIgnoreCase);

		private bool prefabsReady;

		private readonly List<GameObject> oneShots = new List<GameObject>();

		private WorldScope lastScope;

		private float nextSend;

		private float lastSent;

		private float nextMetrics;

		private int lastActivity;

		private readonly Dictionary<int, Animator> sourceAnimators = new Dictionary<int, Animator>();

		private readonly Dictionary<int, AnimatorControllerParameter[]> sourceParameters = new Dictionary<int, AnimatorControllerParameter[]>();

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

		private readonly Stopwatch timer = new Stopwatch();

		private double captureMilliseconds;

		private double encodeMilliseconds;

		private double decodeMilliseconds;

		private int captures;

		private int encodes;

		private int decodes;

		private long revision;

		private long appliedRevision;

		private readonly Dictionary<string, RemoteMotion> receivedByMember = new Dictionary<string, RemoteMotion>();

		private VisualMotion received
		{
			get
			{
				return Motion(null).packet;
			}
			set
			{
				Motion(null).packet = value;
			}
		}

		internal string Status { get; private set; } = "Combat visuals: awaiting connection";

		private RemoteMotion Motion(string id)
		{
			id = id ?? network.ContextPeer ?? "host";
			if (!receivedByMember.TryGetValue(id, out var value))
			{
				value = new RemoteMotion();
				receivedByMember[id] = value;
			}
			return value;
		}

		private static object Read(object target, string name)
		{
			return NativeMember.Read(target, name);
		}

		internal CombatVisuals(Harmony harmony, Assembly game, LoopbackSession network, Func<WorldScope> scope, Func<IReadOnlyList<Component>> actors, Action<string> log)
		{
			//IL_0106: Unknown result type (might be due to invalid IL or missing references)
			//IL_0113: Expected O, but got Unknown
			//IL_0147: Unknown result type (might be due to invalid IL or missing references)
			//IL_0154: Expected O, but got Unknown
			//IL_017e: Unknown result type (might be due to invalid IL or missing references)
			//IL_018b: Expected O, but got Unknown
			//IL_01d0: Unknown result type (might be due to invalid IL or missing references)
			//IL_01dd: Expected O, but got Unknown
			//IL_0211: Unknown result type (might be due to invalid IL or missing references)
			//IL_021e: Expected O, but got Unknown
			//IL_02e2: Unknown result type (might be due to invalid IL or missing references)
			//IL_02ef: Expected O, but got Unknown
			current = this;
			this.network = network;
			this.scope = scope;
			this.actors = actors;
			this.log = log;
			string[] array = new string[3] { "Tableflip.SpellProjectile", "Tableflip.SpellArea", "Tableflip.Spell" };
			foreach (string name in array)
			{
				harmony.Patch((MethodBase)AccessTools.Method(game.GetType(name, throwOnError: true), "Setup", (Type[])null, (Type[])null), (HarmonyMethod)null, new HarmonyMethod(typeof(CombatVisuals), "SpellSetup", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			}
			harmony.Patch((MethodBase)AccessTools.Method(game.GetType("Tableflip.Character"), "CastingVFX", (Type[])null, (Type[])null), (HarmonyMethod)null, new HarmonyMethod(typeof(CombatVisuals), "Casting", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			harmony.Patch((MethodBase)AccessTools.Method(game.GetType("Tableflip.SpellVfxAutoDestroy"), "SetLifeTime", (Type[])null, (Type[])null), (HarmonyMethod)null, new HarmonyMethod(typeof(CombatVisuals), "TimedVisual", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			array = new string[2] { "Tableflip.SpellVfxAutoDestroy", "Tableflip.SpellArea" };
			foreach (string name2 in array)
			{
				harmony.Patch((MethodBase)AccessTools.Method(game.GetType(name2), "DestroySelf", (Type[])null, (Type[])null), (HarmonyMethod)null, new HarmonyMethod(typeof(CombatVisuals), "EndVisual", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			}
			harmony.Patch((MethodBase)AccessTools.Method(game.GetType("Tableflip.Character"), "AddPSEffect", (Type[])null, (Type[])null), (HarmonyMethod)null, new HarmonyMethod(typeof(CombatVisuals), "PersistentEffect", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			array = new string[2] { "Tableflip.LevelGenerator.SpawnMonster", "Tableflip.Town.SpawnMonsterInTown" };
			foreach (string entry in array)
			{
				int split = entry.LastIndexOf('.');
				Type type = game.GetType(entry.Substring(0, split), throwOnError: true);
				foreach (MethodInfo item in from m in type.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
					where m.DeclaringType == type && m.Name == entry.Substring(split + 1) && m.ReturnType != typeof(void)
					select m)
				{
					harmony.Patch((MethodBase)item, (HarmonyMethod)null, new HarmonyMethod(typeof(CombatVisuals), "SummonBurst", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				}
			}
			log("COMBAT_VISUAL_HOOKS installed spell projectile/area/buff/casting/impact");
		}

		private static string PrefabName(string name)
		{
			if (name != null)
			{
				if (!name.EndsWith("(Clone)", StringComparison.Ordinal))
				{
					return name;
				}
				return name.Substring(0, name.Length - 7).TrimEnd();
			}
			return "";
		}

		internal bool IsLocal(object caster)
		{
			foreach (Component item in actors())
			{
				if (item == caster)
				{
					return true;
				}
			}
			return false;
		}

		private void Track(Component source, string prefab)
		{
			if (Object.op_Implicit((Object)(object)source) && !localEffects.ContainsKey(((Object)source.gameObject).GetInstanceID()) && localEffects.Count < 64)
			{
				ParticleConfig[] particles = source.GetComponentsInChildren<ParticleSystem>(true).Take(128).Select(delegate(ParticleSystem ps)
				{
					//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)
					ParticleConfig obj = new ParticleConfig
					{
						seed = ps.randomSeed
					};
					MainModule main = ps.main;
					obj.loop = ((MainModule)(ref main)).loop;
					return obj;
				})
					.ToArray();
				localEffects[((Object)source.gameObject).GetInstanceID()] = new Tracked
				{
					source = (Component)(object)source.transform,
					id = Guid.NewGuid().ToString("N"),
					prefab = PrefabName(prefab),
					particles = particles
				};
			}
		}

		private static void SpellSetup(object __instance, object[] __args)
		{
			try
			{
				if (current.scope() == null)
				{
					return;
				}
				Component val = (Component)((__instance is Component) ? __instance : null);
				if (val != null && Object.op_Implicit((Object)(object)val) && current.localEffects.Count < 64)
				{
					object obj = ((__args.Length != 0) ? Read(__args[0], "caster") : null);
					if (obj == null)
					{
						object obj2 = Read(val, "data");
						obj = ((obj2 == null) ? Read(val, "caster") : Read(obj2, "caster"));
					}
					if (current.IsLocal(obj))
					{
						string text = PrefabName(((Object)val.gameObject).name);
						current.Track(val, text);
						current.log("LOCAL_SPELL_VISUAL prefab=" + text);
					}
				}
			}
			catch (Exception ex)
			{
				current.log("SPELL_CAPTURE_FAILED " + ex.GetBaseException().Message);
			}
		}

		private static void TimedVisual(object __instance, object[] __args)
		{
			try
			{
				if (SpellAudio.OwnedContext && current.scope() != null)
				{
					Component val = (Component)((__instance is Component) ? __instance : null);
					if (val != null && Object.op_Implicit((Object)(object)val))
					{
						current.Track(val, ((Object)val.gameObject).name);
						current.log("LOCAL_TIMED_SPELL_VISUAL prefab=" + PrefabName(((Object)val).name) + " requested-life=" + __args[0]?.ToString() + " lifecycle=native");
					}
				}
			}
			catch (Exception ex)
			{
				current.log("TIMED_VISUAL_FAILED " + ex.GetBaseException().Message);
			}
		}

		private static void EndVisual(object __instance)
		{
			Component val = (Component)((__instance is Component) ? __instance : null);
			if (val != null && Object.op_Implicit((Object)(object)val))
			{
				current.localEffects.Remove(((Object)val.gameObject).GetInstanceID());
			}
		}

		private static void Casting(object __instance, object[] __args)
		{
			//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_00c5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d0: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				if (current.scope() == null || !current.IsLocal(__instance))
				{
					return;
				}
				Component val = (Component)((__instance is Component) ? __instance : null);
				if (val == null)
				{
					return;
				}
				string text = Convert.ToString(__args[0]);
				bool flag = false;
				ParticleSystem[] componentsInChildren = val.GetComponentsInChildren<ParticleSystem>(true);
				for (int i = 0; i < componentsInChildren.Length; i++)
				{
					Transform val2 = ((Component)componentsInChildren[i]).transform;
					while (Object.op_Implicit((Object)(object)val2) && (Object)(object)val2 != (Object)(object)val.transform)
					{
						if (PrefabName(((Object)val2).name).Equals(text, StringComparison.OrdinalIgnoreCase))
						{
							current.Track((Component)(object)val2, ((Object)val2).name);
							flag = true;
							break;
						}
						val2 = val2.parent;
					}
				}
				if (!flag)
				{
					current.OneShot(text, val.transform.position + Vector3.up, val.transform.rotation, Mathf.Clamp(Convert.ToSingle(Read(val, "castTimer") ?? ((object)1f)), 0.2f, 10f));
				}
			}
			catch (Exception ex)
			{
				current.log("CAST_VISUAL_FAILED " + ex.GetBaseException().Message);
			}
		}

		private static void PersistentEffect(object __instance, object[] __args)
		{
			try
			{
				if (current.scope() != null && __args.Length != 0 && (current.IsLocal(__instance) || current.IsLocal(Read(__args[0], "source"))))
				{
					current.TrackEffectChildren(__instance);
				}
			}
			catch (Exception ex)
			{
				current.log("PERSISTENT_VISUAL_FAILED " + ex.GetBaseException().Message);
			}
		}

		private void TrackEffectChildren(object actor)
		{
			if (!(Read(actor, "effectChildren") is IEnumerable enumerable))
			{
				return;
			}
			foreach (object item in enumerable)
			{
				GameObject val = (GameObject)((item is GameObject) ? item : null);
				if (val != null && Object.op_Implicit((Object)(object)val))
				{
					Track((Component)(object)val.transform, ((Object)val).name);
				}
			}
		}

		private static void SummonBurst(object __result)
		{
			//IL_003a: 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)
			try
			{
				if (SpellAudio.OwnedContext && current.scope() != null)
				{
					Component val = (Component)((__result is Component) ? __result : null);
					if (val != null && Object.op_Implicit((Object)(object)val))
					{
						current.OneShot("PS_summonMonster", val.transform.position, Quaternion.identity, 2f);
					}
				}
			}
			catch (Exception ex)
			{
				current.log("SUMMON_BURST_FAILED " + ex.GetBaseException().Message);
			}
		}

		private void OneShot(string prefab, Vector3 position, Quaternion rotation, float seconds)
		{
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			//IL_001d: Unknown result type (might be due to invalid IL or missing references)
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			if (!string.IsNullOrEmpty(prefab))
			{
				EffectPose effectPose = Pose(Guid.NewGuid().ToString("N"), prefab, position, rotation, Vector3.one);
				VisualMotion visualMotion = new VisualMotion();
				visualMotion.scope = scope();
				visualMotion.actors = Array.Empty<AnimationPose>();
				visualMotion.effects = new EffectPose[1] { effectPose };
				VisualMotion packet = visualMotion;
				effectPose.duration = seconds;
				network.Send(new WireMessage("visual-once", MotionCodec.Encode(packet)));
			}
		}

		internal static void WorldBurst(string prefab, Vector3 position)
		{
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			if (current?.scope() != null)
			{
				current.OneShot(prefab, position, Quaternion.identity, 3f);
			}
		}

		private static float[] Vec(Vector3 value)
		{
			//IL_0008: 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_001a: Unknown result type (might be due to invalid IL or missing references)
			return new float[3] { value.x, value.y, value.z };
		}

		private static EffectPose Pose(string id, string prefab, Vector3 position, Quaternion rotation, Vector3 scale)
		{
			//IL_0015: 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_0032: Unknown result type (might be due to invalid IL or missing references)
			//IL_003b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0044: Unknown result type (might be due to invalid IL or missing references)
			//IL_0051: Unknown result type (might be due to invalid IL or missing references)
			EffectPose effectPose = new EffectPose();
			effectPose.id = id;
			effectPose.prefab = prefab;
			effectPose.position = Vec(position);
			effectPose.rotation = new float[4] { rotation.x, rotation.y, rotation.z, rotation.w };
			effectPose.scale = Vec(scale);
			return effectPose;
		}

		internal AnimationPose Capture(Component actor)
		{
			return Capture(actor, compact: false);
		}

		internal AnimationPose CaptureWorld(Component actor)
		{
			return Capture(actor, compact: true);
		}

		private AnimationPose Capture(Component actor, bool compact)
		{
			//IL_00c0: 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_00c5: Unknown result type (might be due to invalid IL or missing references)
			//IL_0261: Unknown result type (might be due to invalid IL or missing references)
			//IL_026b: Expected I4, but got Unknown
			//IL_026f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0275: Invalid comparison between Unknown and I4
			//IL_028f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0295: Invalid comparison between Unknown and I4
			//IL_02af: Unknown result type (might be due to invalid IL or missing references)
			//IL_02b5: Invalid comparison between Unknown and I4
			if (!Object.op_Implicit((Object)(object)actor))
			{
				return new AnimationPose
				{
					layers = Array.Empty<AnimationLayer>(),
					values = Array.Empty<AnimationValue>(),
					speed = 1f
				};
			}
			int instanceID = ((Object)actor).GetInstanceID();
			if (!sourceAnimators.TryGetValue(instanceID, out var value) || !Object.op_Implicit((Object)(object)value))
			{
				value = actor.GetComponentInChildren<Animator>();
				sourceAnimators[instanceID] = value;
			}
			if (!Object.op_Implicit((Object)(object)value) || !Object.op_Implicit((Object)(object)value.runtimeAnimatorController))
			{
				return new AnimationPose
				{
					layers = Array.Empty<AnimationLayer>(),
					values = Array.Empty<AnimationValue>(),
					speed = 1f
				};
			}
			List<AnimationLayer> list = new List<AnimationLayer>();
			for (int i = 0; i < Math.Min(value.layerCount, 8); i++)
			{
				AnimatorStateInfo val = (value.IsInTransition(i) ? value.GetNextAnimatorStateInfo(i) : value.GetCurrentAnimatorStateInfo(i));
				list.Add(new AnimationLayer
				{
					hash = ((AnimatorStateInfo)(ref val)).fullPathHash,
					time = ((AnimatorStateInfo)(ref val)).normalizedTime,
					weight = value.GetLayerWeight(i),
					loop = ((AnimatorStateInfo)(ref val)).loop
				});
			}
			List<AnimationValue> list2 = new List<AnimationValue>();
			int instanceID2 = ((Object)value.runtimeAnimatorController).GetInstanceID();
			if (!sourceControllers.TryGetValue(instanceID, out var value2) || value2 != instanceID2)
			{
				sourceControllers[instanceID] = instanceID2;
				sourceParameters[instanceID] = value.parameters.Take(128).ToArray();
			}
			AnimatorControllerParameter[] array = sourceParameters[instanceID];
			foreach (AnimatorControllerParameter val2 in array)
			{
				if (compact && val2.name != "IsWalking" && val2.name != "IsRunning" && val2.name != "IsAttacking" && val2.name != "IsDead" && val2.name != "IsCasting" && val2.name != "SpeedMove" && val2.name != "MoveX" && val2.name != "MoveZ" && val2.name != "Speed")
				{
					continue;
				}
				AnimationValue animationValue = new AnimationValue
				{
					hash = val2.nameHash,
					type = (int)val2.type
				};
				if ((int)val2.type == 1)
				{
					animationValue.value = value.GetFloat(val2.nameHash);
				}
				else if ((int)val2.type == 3)
				{
					animationValue.integer = value.GetInteger(val2.nameHash);
				}
				else
				{
					if ((int)val2.type != 4)
					{
						continue;
					}
					animationValue.flag = value.GetBool(val2.nameHash);
				}
				list2.Add(animationValue);
			}
			return new AnimationPose
			{
				layers = list.ToArray(),
				values = list2.ToArray(),
				speed = value.speed
			};
		}

		internal void Tick()
		{
			//IL_0085: Unknown result type (might be due to invalid IL or missing references)
			//IL_008c: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a7: 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_01b0: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b7: Unknown result type (might be due to invalid IL or missing references)
			WorldScope worldScope = scope();
			if (worldScope == null)
			{
				Clear();
				lastScope = null;
				return;
			}
			if (lastScope == null || !lastScope.Matches(worldScope))
			{
				Clear();
				lastScope = worldScope;
			}
			foreach (KeyValuePair<string, Vector3> target in targets)
			{
				if (remoteEffects.TryGetValue(target.Key, out var value) && Object.op_Implicit((Object)(object)value))
				{
					value.transform.position = Vector3.Lerp(value.transform.position, target.Value, 1f - Mathf.Exp(-20f * Time.unscaledDeltaTime));
				}
			}
			if (Time.realtimeSinceStartup < nextSend)
			{
				return;
			}
			nextSend = Time.realtimeSinceStartup + 0.1f;
			timer.Restart();
			foreach (Component item in actors())
			{
				if (Object.op_Implicit((Object)(object)item))
				{
					TrackEffectChildren(item);
				}
			}
			List<EffectPose> list = new List<EffectPose>();
			KeyValuePair<int, Tracked>[] array = localEffects.ToArray();
			for (int i = 0; i < array.Length; i++)
			{
				KeyValuePair<int, Tracked> keyValuePair2 = array[i];
				Tracked value2 = keyValuePair2.Value;
				if (!Object.op_Implicit((Object)(object)value2.source))
				{
					localEffects.Remove(keyValuePair2.Key);
					continue;
				}
				Transform transform = value2.source.transform;
				EffectPose effectPose = Pose(value2.id, value2.prefab, transform.position, transform.rotation, transform.lossyScale);
				effectPose.particles = value2.particles;
				list.Add(effectPose);
			}
			VisualMotion packet = new VisualMotion
			{
				scope = worldScope,
				actors = actors().Take(5).Select(Capture).ToArray(),
				effects = list.ToArray()
			};
			captureMilliseconds += timer.Elapsed.TotalMilliseconds;
			captures++;
			int num = MotionCodec.Activity(packet);
			if (num != lastActivity || Time.realtimeSinceStartup - lastSent >= 1f)
			{
				timer.Restart();
				string data = MotionCodec.Encode(packet);
				encodeMilliseconds += timer.Elapsed.TotalMilliseconds;
				encodes++;
				network.Send(new WireMessage("visual-motion", data));
				lastActivity = num;
				lastSent = Time.realtimeSinceStartup;
			}
			if (Time.realtimeSinceStartup >= nextMetrics && captures > 0)
			{
				nextMetrics = Time.realtimeSinceStartup + 15f;
				log("VISUAL_TIMING capture-ms=" + (captureMilliseconds / (double)captures).ToString("F3") + " encode-ms=" + (encodeMilliseconds / (double)Math.Max(encodes, 1)).ToString("F3") + " decode-ms=" + (decodeMilliseconds / (double)Math.Max(decodes, 1)).ToString("F3") + " captures=" + captures + " packets=" + encodes);
				captureMilliseconds = (encodeMilliseconds = (decodeMilliseconds = 0.0));
				captures = (encodes = (decodes = 0));
			}
		}

		internal void ApplyAnimations(string member, VisualAvatar avatar, IReadOnlyList<VisualAvatar> pets)
		{
			RemoteMotion remoteMotion = Motion(member);
			VisualMotion packet = remoteMotion.packet;
			if (avatar == null || packet == null || remoteMotion.applied == remoteMotion.revision)
			{
				return;
			}
			WorldScope worldScope = scope();
			if (worldScope != null && worldScope.Matches(packet.scope))
			{
				if (packet.actors.Length != 0)
				{
					avatar.ApplyPose(packet.actors[0]);
				}
				for (int i = 0; i < pets.Count && i + 1 < packet.actors.Length; i++)
				{
					pets[i].ApplyPose(packet.actors[i + 1]);
				}
				remoteMotion.applied = remoteMotion.revision;
			}
		}

		internal bool Receive(WireMessage message)
		{
			//IL_022e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0263: Unknown result type (might be due to invalid IL or missing references)
			//IL_028f: Unknown result type (might be due to invalid IL or missing references)
			if (message.Kind != "visual-motion" && message.Kind != "visual-once")
			{
				return false;
			}
			timer.Restart();
			VisualMotion visualMotion = MotionCodec.Decode(message.Data);
			decodeMilliseconds += timer.Elapsed.TotalMilliseconds;
			decodes++;
			WorldScope worldScope = scope();
			if (worldScope == null || visualMotion?.scope == null || !worldScope.Matches(visualMotion.scope))
			{
				return true;
			}
			Validate(visualMotion);
			EffectPose[] effects;
			if (message.Kind == "visual-once")
			{
				effects = visualMotion.effects;
				foreach (EffectPose effectPose in effects)
				{
					GameObject val = Create(effectPose);
					if (Object.op_Implicit((Object)(object)val))
					{
						oneShots.RemoveAll((GameObject obj) => !Object.op_Implicit((Object)(object)obj));
						if (oneShots.Count >= 64)
						{
							Object.Destroy((Object)(object)val);
							continue;
						}
						oneShots.Add(val);
						Object.Destroy((Object)(object)val, Mathf.Clamp(effectPose.duration, 0.2f, 10f));
					}
				}
				return true;
			}
			received = visualMotion;
			Motion(message.Source).revision++;
			revision++;
			HashSet<string> hashSet = new HashSet<string>();
			effects = visualMotion.effects;
			foreach (EffectPose effectPose2 in effects)
			{
				string text = message.Source + ":" + effectPose2.id;
				hashSet.Add(text);
				if (!remoteEffects.TryGetValue(text, out var value) || !Object.op_Implicit((Object)(object)value))
				{
					value = Create(effectPose2);
					if (!Object.op_Implicit((Object)(object)value))
					{
						continue;
					}
					remoteEffects[text] = value;
				}
				targets[text] = new Vector3(effectPose2.position[0], effectPose2.position[1], effectPose2.position[2]);
				value.transform.rotation = new Quaternion(effectPose2.rotation[0], effectPose2.rotation[1], effectPose2.rotation[2], effectPose2.rotation[3]);
				value.transform.localScale = new Vector3(effectPose2.scale[0], effectPose2.scale[1], effectPose2.scale[2]);
			}
			string[] array = remoteEffects.Keys.ToArray();
			foreach (string text2 in array)
			{
				if (text2.StartsWith(message.Source + ":", StringComparison.Ordinal) && !hashSet.Contains(text2))
				{
					if (Object.op_Implicit((Object)(object)remoteEffects[text2]))
					{
						Object.Destroy((Object)(object)remoteEffects[text2]);
					}
					remoteEffects.Remove(text2);
					targets.Remove(text2);
				}
			}
			Status = "Combat poses synced | spell visuals: " + remoteEffects.Count;
			return true;
		}

		private void RefreshPrefabs()
		{
			object value = AccessTools.Property(NativeTypes.Get("Tableflip.FateDB"), "Instance").GetValue(null, null);
			string[] array = new string[2] { "particlePrefabs", "spellPrefabs" };
			foreach (string name in array)
			{
				if (!(Read(value, name) is IEnumerable enumerable))
				{
					continue;
				}
				foreach (object item in enumerable)
				{
					GameObject val = (GameObject)((item is GameObject) ? item : null);
					if (val != null && Object.op_Implicit((Object)(object)val))
					{
						prefabs[((Object)val).name] = val;
					}
				}
			}
			if (Read(value, "allSpells") is IEnumerable enumerable2)
			{
				foreach (object item2 in enumerable2)
				{
					array = new string[2] { "particleProjectilePrefab", "particleImpactPrefab" };
					foreach (string name2 in array)
					{
						object obj2 = Read(item2, name2);
						GameObject val2 = (GameObject)((obj2 is GameObject) ? obj2 : null);
						if (val2 != null && Object.op_Implicit((Object)(object)val2))
						{
							prefabs[((Object)val2).name] = val2;
						}
					}
				}
			}
			KeyValuePair<string, GameObject>[] array2 = prefabs.ToArray();
			for (int i = 0; i < array2.Length; i++)
			{
				KeyValuePair<string, GameObject> keyValuePair = array2[i];
				if (keyValuePair.Key.StartsWith("PS_", StringComparison.OrdinalIgnoreCase))
				{
					prefabs[keyValuePair.Key.Substring(3)] = keyValuePair.Value;
				}
			}
			prefabsReady = true;
		}

		private GameObject Create(EffectPose pose)
		{
			//IL_0074: Unknown result type (might be due to invalid IL or missing references)
			//IL_007a: Expected O, but got Unknown
			//IL_0095: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b5: Unknown result type (might be due to invalid IL or missing references)
			//IL_0181: Unknown result type (might be due to invalid IL or missing references)
			//IL_0186: Unknown result type (might be due to invalid IL or missing references)
			//IL_0192: Unknown result type (might be due to invalid IL or missing references)
			//IL_0197: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a3: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a8: Unknown result type (might be due to invalid IL or missing references)
			//IL_028b: Unknown result type (might be due to invalid IL or missing references)
			//IL_02bb: Unknown result type (might be due to invalid IL or missing references)
			//IL_02e3: Unknown result type (might be due to invalid IL or missing references)
			if (missing.Contains(pose.prefab))
			{
				return null;
			}
			if (!prefabsReady)
			{
				RefreshPrefabs();
			}
			prefabs.TryGetValue(pose.prefab, out var value);
			if (!Object.op_Implicit((Object)(object)value))
			{
				if (missing.Add(pose.prefab))
				{
					log("REMOTE_SPELL_ASSET_MISSING " + pose.prefab);
				}
				return null;
			}
			GameObject val = new GameObject("Fate.RemoteSpellVisual");
			val.SetActive(false);
			try
			{
				GameObject val2 = Object.Instantiate<GameObject>(value, val.transform, false);
				val2.transform.localPosition = Vector3.zero;
				val2.transform.localRotation = Quaternion.identity;
				val2.transform.localScale = Vector3.one;
				Component[] componentsInChildren = val2.GetComponentsInChildren<Component>(true);
				foreach (Component val3 in componentsInChildren)
				{
					if (Object.op_Implicit((Object)(object)val3) && !(val3 is Transform) && !(val3 is Renderer) && !(val3 is MeshFilter) && !(val3 is ParticleSystem) && !(val3 is Animator) && !(val3 is Light) && !SpellVisualPolicy.AllowsScript(((object)val3).GetType().FullName))
					{
						Object.DestroyImmediate((Object)(object)val3);
					}
				}
				Animator[] componentsInChildren2 = val2.GetComponentsInChildren<Animator>(true);
				foreach (Animator obj in componentsInChildren2)
				{
					obj.fireEvents = false;
					obj.applyRootMotion = false;
				}
				ParticleSystem[] componentsInChildren3 = val2.GetComponentsInChildren<ParticleSystem>(true);
				for (int j = 0; j < componentsInChildren3.Length; j++)
				{
					ParticleSystem val4 = componentsInChildren3[j];
					CollisionModule collision = val4.collision;
					((CollisionModule)(ref collision)).enabled = false;
					TriggerModule trigger = val4.trigger;
					((TriggerModule)(ref trigger)).enabled = false;
					MainModule main = val4.main;
					((MainModule)(ref main)).stopAction = (ParticleSystemStopAction)0;
					if (pose.particles != null && pose.particles.Length == componentsInChildren3.Length)
					{
						((MainModule)(ref main)).loop = pose.particles[j].loop;
						if (pose.particles[j].seed != 0)
						{
							val4.randomSeed = pose.particles[j].seed;
						}
					}
				}
				componentsInChildren = val2.GetComponentsInChildren<Component>(true);
				foreach (Component val5 in componentsInChildren)
				{
					if ((val5 is MonoBehaviour && !SpellVisualPolicy.AllowsScript(((object)val5).GetType().FullName)) || val5 is Collider || val5 is Rigidbody)
					{
						throw new InvalidOperationException("Gameplay component survived spell visual filtering");
					}
				}
				val.transform.position = new Vector3(pose.position[0], pose.position[1], pose.position[2]);
				val.transform.rotation = new Quaternion(pose.rotation[0], pose.rotation[1], pose.rotation[2], pose.rotation[3]);
				val.transform.localScale = new Vector3(pose.scale[0], pose.scale[1], pose.scale[2]);
				val.SetActive(true);
				log("REMOTE_SPELL_VISUAL_CREATED prefab=" + pose.prefab + " gameplay-scripts=0 colliders=0 audited-visual-helpers=allowed");
				return val;
			}
			catch
			{
				Object.Destroy((Object)(object)val);
				throw;
			}
		}

		private static void Numbers(float[] values, int min, int max)
		{
			if (values == null || values.Length < min || values.Length > max || values.Any((float x) => float.IsNaN(x) || float.IsInfinity(x) || Math.Abs(x) > 100000f))
			{
				throw new InvalidOperationException("Invalid visual numeric values");
			}
		}

		private static void Validate(VisualMotion packet)
		{
			if (packet.actors == null || packet.actors.Length > 5 || packet.effects == null || packet.effects.Length > 64)
			{
				throw new InvalidOperationException("Invalid combat visual count");
			}
			AnimationPose[] array = packet.actors;
			foreach (AnimationPose animationPose in array)
			{
				if (animationPose?.layers == null || animationPose.layers.Length > 8 || animationPose.values == null || animationPose.values.Length > 128 || float.IsNaN(animationPose.speed) || float.IsInfinity(animationPose.speed))
				{
					throw new InvalidOperationException("Invalid animator pose");
				}
				AnimationLayer[] layers = animationPose.layers;
				foreach (AnimationLayer animationLayer in layers)
				{
					if (animationLayer == null)
					{
						throw new InvalidOperationException("Invalid animation layer");
					}
					Numbers(new float[2] { animationLayer.time, animationLayer.weight }, 2, 2);
				}
				AnimationValue[] values = animationPose.values;
				foreach (AnimationValue animationValue in values)
				{
					if (animationValue == null)
					{
						throw new InvalidOperationException("Invalid animation parameter");
					}
					Numbers(new float[1] { animationValue.value }, 1, 1);
				}
			}
			EffectPose[] effects = packet.effects;
			foreach (EffectPose effectPose in effects)
			{
				if (effectPose == null || !Guid.TryParseExact(effectPose.id, "N", out var _) || string.IsNullOrEmpty(effectPose.prefab) || effectPose.prefab.Length > 256)
				{
					throw new InvalidOperationException("Invalid spell identity");
				}
				Numbers(effectPose.position, 3, 3);
				Numbers(effectPose.rotation, 4, 4);
				Numbers(effectPose.scale, 3, 3);
				Numbers(new float[1] { effectPose.duration }, 1, 1);
				if (effectPose.particles != null && (effectPose.particles.Length > 128 || effectPose.particles.Any((ParticleConfig p) => p == null)))
				{
					throw new InvalidOperationException("Invalid particle configuration");
				}
			}
		}

		internal void RemoveMember(string member)
		{
			receivedByMember.Remove(member);
			string[] array = remoteEffects.Keys.Where((string text) => text.StartsWith(member + ":", StringComparison.Ordinal)).ToArray();
			foreach (string key in array)
			{
				if (Object.op_Implicit((Object)(object)remoteEffects[key]))
				{
					Object.Destroy((Object)(object)remoteEffects[key]);
				}
				remoteEffects.Remove(key);
				targets.Remove(key);
			}
		}

		private void Clear()
		{
			foreach (GameObject value in remoteEffects.Values)
			{
				if (Object.op_Implicit((Object)(object)value))
				{
					Object.Destroy((Object)(object)value);
				}
			}
			foreach (GameObject oneShot in oneShots)
			{
				if (Object.op_Implicit((Object)(object)oneShot))
				{
					Object.Destroy((Object)(object)oneShot);
				}
			}
			oneShots.Clear();
			remoteEffects.Clear();
			targets.Clear();
			localEffects.Clear();
			receivedByMember.Clear();
			sourceAnimators.Clear();
			sourceParameters.Clear();
			sourceControllers.Clear();
			lastSent = -10f;
			appliedRevision = revision;
			Status = "Combat visuals: waiting for same shared scene";
		}
	}
	internal static class ConnectionHealth
	{
		internal const double StallSeconds = 2.0;

		internal const double DropSeconds = 5.0;

		internal const double SlowSeconds = 10.0;

		internal const double SlowRoundTripMs = 1000.0;

		internal static bool Block(double silence, double roundTrip, double slowFor)
		{
			if (!(silence >= 2.0))
			{
				if (roundTrip >= 1000.0)
				{
					return slowFor >= 2.0;
				}
				return false;
			}
			return true;
		}

		internal static bool Drop(double silence, double roundTrip, double slowFor)
		{
			if (!(silence >= 5.0))
			{
				if (roundTrip >= 1000.0)
				{
					return slowFor >= 10.0;
				}
				return false;
			}
			return true;
		}
	}
	[BepInPlugin("fate.local.diagnostics", "FATE Co-op", "0.20.6")]
	public sealed class Plugin : BaseUnityPlugin
	{
		private sealed class RemoteView
		{
			internal VisualAvatar avatar;

			internal VisualBundle appearance;

			internal readonly List<VisualAvatar> pets = new List<VisualAvatar>();

			internal RemoteMinimap minimap;

			internal string name = "Teammate";

			internal int partyClass;

			internal float nextAttempt;
		}

		private NativeCoopMenu nativeMenu;

		private float nextMenuRefresh;

		public const string Id = "fate.local.diagnostics";

		private static Plugin self;

		private Harmony harmony;

		private static string saveDirectory;

		private StreamWriter events;

		private float nextPoll;

		private string status = "Waiting for player";

		private string previous = "";

		private int snapshotCount;

		private string snapshotFeedback = "No manual snapshots recorded yet.";

		private double feedbackUntil;

		private VisualAvatar visualAvatar;

		private string avatarFeedback = "Visual avatar: not spawned";

		private int eventCount;

		private int frameCount;

		private double nextBudgetReset;

		private bool rateWarning;

		private Component cachedPlayer;

		private bool detailedEvents;

		private string instanceLabel = "SINGLE TEST";

		private SessionPause sessionPause;

		private readonly LoopbackSession localSession = new LoopbackSession();

		private bool appliedConnection;

		private bool appliedHosting;

		private bool gameplayLoading;

		private bool peerEstablished;

		private bool joinedAsHost;

		private bool connectionStalled;

		private bool recoveryPending;

		private bool recoveryStarted;

		private bool recoveryTown;

		private double slowSince = -1.0;

		private readonly Dictionary<string, double> peerSlowSince = new Dictionary<string, double>();

		private readonly HashSet<string> stalledPeers = new HashSet<string>();

		private string lastMemberSet;

		private string recoveryReason;

		private int sessionCampaign;

		private bool steamMode;

		private string joinCode = "";

		private string connectionError = "";

		private string localAddress = "127.0.0.1";

		private string[] hostAddresses = new string[1] { "127.0.0.1" };

		private int hostAddressIndex;

		private double codeCopiedUntil;

		private readonly Dictionary<string, RemoteView> remoteViews = new Dictionary<string, RemoteView>();

		private float nextFrame;

		private Vector3 previousPosition;

		private bool hasPreviousPosition;

		private string remoteStatus = "No remote avatar";

		private bool backgroundApplied;

		private bool previousRunInBackground;

		private SharedItems sharedItems;

		private SharedPortals sharedPortals;

		private PartyQuests partyQuests;

		private TeammateHud teammateHud;

		private SupportSpells supportSpells;

		private ReceiptSaveQueue receiptSaves;

		private string lastSentPlayerName;

		private int selectedClass;

		private int lastSentClass = -1;

		private readonly ClassMarkerTint localClassMarker = new ClassMarkerTint();

		private ConfigEntry<int> classPreference;

		private DungeonDrag dungeonDrag;

		private SharedWorld sharedWorld;

		private CombatVisuals combatVisuals;

		private SharedActors sharedActors;

		private JoinerCombat joinerCombat;

		private RemoteMinimap remoteMinimap;

		private SharedFloors sharedFloors;

		private SharedDeath sharedDeath;

		private DownedState downedState;

		private HostMigrationState migrationState;

		private SpellAudio spellAudio;

		private readonly List<Component> localActors = new List<Component>();

		private readonly Dictionary<int, Vector3> lastPetPositions = new Dictionary<int, Vector3>();

		private string lastAppearanceSignature;

		private float nextAppearance;

		private float nextItemTick;

		private bool legacyInputAvailable = true;

		private float fpsElapsed;

		private int fpsFrames;

		private float fps;

		private float nextPerformanceLog;

		private static readonly Dictionary<string, Type> TypeCache = new Dictionary<string, Type>();

		private static readonly Dictionary<string, Object> ObjectCache = new Dictionary<string, Object>();

		private readonly Stopwatch clock = Stopwatch.StartNew();

		private readonly object logLock = new object();

		private static readonly BindingFlags Flags = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic;

		private static readonly string[] Hooks = new string[29]
		{
			"Tableflip.PlayerManager.RequestPlayerSpawn", "Tableflip.LevelGenerator.Create", "Tableflip.LevelGenerator.OnFinishCreation", "Tableflip.LevelGenerator.FinishLoadSave", "Tableflip.LevelGenerator.UpdateSaveData", "Tableflip.LevelGenerator.Remove", "Tableflip.Player.AttackFrameReached", "Tableflip.CharacterVitals.ApplyDamage", "Tableflip.CharacterVitals.Die", "Tableflip.Monster.Die",
			"Tableflip.Item.Drop", "Tableflip.DroppedItem.Interact", "Tableflip.Character.AddItemToInventory", "Tableflip.Character.RemoveItemFromInventory", "Tableflip.Character.ItemMovedInInventory", "Tableflip.Player.EquipItem", "Tableflip.Player.UnequipItem", "Tableflip.QuestManager.AcceptQuest", "Tableflip.QuestManager.ClaimRewards", "Tableflip.Quest.ClaimRewards",
			"Tableflip.Quest.GenerateObjective", "Tableflip.Quest.OnTargetMonsterKilled", "Tableflip.Quest.OnItemLooted", "Tableflip.LevelGenerator.ValidateQuestsPostLoad", "Tableflip.Character.AwardExperience", "Tableflip.SaveData.RequestSave", "Tableflip.SaveData.ParseSave", "Tivola.Framework.SaveManager.SaveAsync", "Tivola.Framework.SaveManager.LoadAsync"
		};

		private static readonly string[] LightweightHooks = new string[5] { "Tableflip.PlayerManager.RequestPlayerSpawn", "Tableflip.LevelGenerator.OnFinishCreation", "Tableflip.LevelGenerator.FinishLoadSave", "Tableflip.LevelGenerator.Remove", "Tableflip.Quest.ClaimRewards" };

		private static readonly HashSet<string> backedUpNormalSaves = new HashSet<string>(StringComparer.OrdinalIgnoreCase);

		private GUIStyle indicatorStyle;

		private GUIStyle indicatorNameStyle;

		private Font indicatorFont;

		private static readonly Vector2[] haloDirections = (Vector2[])(object)new Vector2[8]
		{
			new Vector2(-1f, 0f),
			new Vector2(1f, 0f),
			new Vector2(0f, -1f),
			new Vector2(0f, 1f),
			new Vector2(-0.707f, -0.707f),
			new Vector2(0.707f, -0.707f),
			new Vector2(-0.707f, 0.707f),
			new Vector2(0.707f, 0.707f)
		};

		internal static bool PanelOwnsInput
		{
			get
			{
				Plugin plugin = self;
				if (plugin == null)
				{
					return false;
				}
				return plugin.nativeMenu?.BlocksInput == true;
			}
		}

		private bool MenuAvailable
		{
			get
			{
				//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_003d: Unknown result type (might be due to invalid IL or missing references)
				//IL_0042: Unknown result type (might be due to invalid IL or missing references)
				SessionPause obj = sessionPause;
				if (obj != null && obj.Ready && Object.op_Implicit((Object)(object)cachedPlayer) && !gameplayLoading)
				{
					Scene activeScene = SceneManager.GetActiveScene();
					if (!SessionRules.Village(((Scene)(ref activeScene)).name))
					{
						activeScene = SceneManager.GetActiveScene();
						return ((Scene)(ref activeScene)).name == "Scene_Dungeon";
					}
					return true;
				}
				return false;
			}
		}

		internal static bool ConnectionBlocked
		{
			get
			{
				if ((Object)(object)self != (Object)null)
				{
					if (!self.connectionStalled && !self.recoveryPending && !self.localSession.Migrating)
					{
						if (self.localSession.ContextPeer != null)
						{
							return self.stalledPeers.Contains(self.localSession.ContextPeer);
						}
						return false;
					}
					return true;
				}
				return false;
			}
		}

		internal static bool RecoveryPending => self?.recoveryPending ?? false;

		private string SelectedHostAddress => hostAddresses[hostAddressIndex % hostAddresses.Length];

		private VisualAvatar remoteAvatar
		{
			get
			{
				return View(null).avatar;
			}
			set
			{
				View(null).avatar = value;
			}
		}

		private VisualBundle remoteAppearance
		{
			get
			{
				return View(null).appearance;
			}
			set
			{
				View(null).appearance = value;
			}
		}

		private List<VisualAvatar> remotePets => View(null).pets;

		private float nextRemoteAttempt
		{
			get
			{
				return View(null).nextAttempt;
			}
			set
			{
				View(null).nextAttempt = value;
			}
		}

		private string remotePlayerName
		{
			get
			{
				return View(null).name;
			}
			set
			{
				View(null).name = value;
			}
		}

		internal static bool FloorEntryPending
		{
			get
			{
				Plugin plugin = self;
				if (plugin == null || plugin.sharedFloors?.Loading != true)
				{
					return self?.localSession.Migrating ?? false;
				}
				return true;
			}
		}

		private bool UpdatePanelInput()
		{
			return nativeMenu?.BlocksInput ?? false;
		}

		private void ToggleCoopMenu()
		{
			if (MenuAvailable)
			{
				EnsureNativeMenu();
				nativeMenu.SetOpen(!nativeMenu.IsOpen);
				nextMenuRefresh = 0f;
			}
		}

		private void EnsureNativeMenu()
		{
			if (nativeMenu != null)
			{
				return;
			}
			nativeMenu = new NativeCoopMenu(new CoopMenuActions
			{
				host = delegate
				{
					StartSession(hosting: true);
				},
				join = delegate
				{
					StartSession(hosting: false);
				},
				close = delegate
				{
					StopSession();
				},
				classChanged = delegate(int value)
				{
					selectedClass = PartyClass.Clamp(value);
					classPreference.Value = selectedClass;
					nextMenuRefresh = 0f;
				},
				codeChanged = delegate(string value)
				{
					if (steamMode)
					{
						joinCode = value;
					}
					else
					{
						localAddress = value;
					}
				},
				paste = delegate
				{
					string text = (GUIUtility.systemCopyBuffer ?? "").Trim();
					if (steamMode)
					{
						joinCode = text;
					}
					else
					{
						localAddress = text;
					}
					nextMenuRefresh = 0f;
				},
				copy = delegate
				{
					CopyLobbyCode(steamMode ? localSession.LobbyCode : (localSession.IsHosting ? SelectedHostAddress : localAddress));
					nextMenuRefresh = 0f;
				},
				nextAddress = delegate
				{
					hostAddressIndex = (hostAddressIndex + 1) % hostAddresses.Length;
					nextMenuRefresh = 0f;
				},
				toggleTransport = delegate
				{
					if (!localSession.Busy)
					{
						steamMode = !steamMode;
					}
					nextMenuRefresh = 0f;
				}
			}, delegate(string message)
			{
				Log(message, essential: true);
			});
		}

		private void TickNativeMenu()
		{
			//IL_01a1: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a6: Unknown result type (might be due to invalid IL or missing references)
			if (!MenuAvailable)
			{
				nativeMenu?.Dispose();
				localClassMarker.Restore();
				return;
			}
			bool flag = localSession.IsConnected && localSession.PeerIds.Length != 0;
			int num = (flag ? selectedClass : 0);
			localClassMarker.Tick(cachedPlayer.transform, num, flag && supportSpells?.Selected == localSession.MemberId, self: true, DownedState.Active);
			EnsureNativeMenu();
			if (!(Time.unscaledTime < nextMenuRefresh))
			{
				nextMenuRefresh = Time.unscaledTime + 0.2f;
				List<int> list = new List<int> { num };
				List<string> list2 = new List<string> { LocalPlayerName() };
				List<string> list3 = new List<string> { localSession.IsHosting ? "YOU / HOST" : "YOU" };
				List<string> list4 = new List<string> { LocalLocation() };
				string[] peerIds = localSession.PeerIds;
				foreach (string text in peerIds)
				{
					list.Add(View(text).partyClass);
					list2.Add(View(text).name);
					list3.Add((text == localSession.HostId) ? "HOST" : "ADVENTURER");
					list4.Add(FrameLocation(localSession.Frame(text)));
				}
				Scene activeScene = SceneManager.GetActiveScene();
				bool flag2 = SessionRules.Village(((Scene)(ref activeScene)).name);
				nativeMenu.Tick(ready: true, new CoopMenuState
				{
					multiplayer = flag,
					inVillage = flag2,
					classes = list.ToArray(),
					selectedClass = selectedClass,
					names = list2.ToArray(),
					roles = list3.ToArray(),
					locations = list4.ToArray(),
					hosting = localSession.IsHosting,
					busy = localSession.Busy,
					steam = steamMode,
					canConnect = (flag2 && !localSession.Busy),
					canJoin = (flag2 && !localSession.Busy && (!steamMode || !string.IsNullOrWhiteSpace(joinCode))),
					status = (localSession.Migrating ? "Choosing the next party leader…" : (localSession.IsConnected ? "Your party is ready for adventure." : (localSession.Busy ? "Your lobby is open. Gather your party." : "An adventure is better with company."))),
					code = ((!steamMode) ? (localSession.IsHosting ? SelectedHostAddress : localAddress) : (localSession.Busy ? localSession.LobbyCode : joinCode)),
					addressCount = hostAddresses.Length,
					addressIndex = hostAddressIndex,
					note = (steamMode ? ((clock.Elapsed.TotalSeconds < codeCopiedUntil) ? "Code copied. Send it to your friends." : ((!flag2) ? "Return to the village to host or join." : (localSession.Busy ? "Share this code. Friends join from their village." : "Up to four players. Everyone joins from the village."))) : ((clock.Elapsed.TotalSeconds < codeCopiedUntil) ? "IP copied." : (localSession.IsHosting ? "127.0.0.1: this PC. Next IP: other networks. TCP 28761." : "Enter the host's IPv4; use 127.0.0.1 on this PC. TCP 28761."))),
					error = connectionError,
					footer = "OPEN ALPHA 0.20.6  /  F8 or Escape to close"
				});
			}
		}

		private RemoteView View(string id)
		{
			id = id ?? localSession.ContextPeer ?? localSession.PeerIds.FirstOrDefault() ?? "pending";
			if (!remoteViews.TryGetValue(id, out var value))
			{
				value = new RemoteView
				{
					minimap = new RemoteMinimap(delegate(string message)
					{
						Log(message, essential: true);
					})
				};
				remoteViews[id] = value;
			}
			return value;
		}

		internal static void SaveReceipt(Action saved)
		{
			self.receiptSaves.Request(saved);
		}

		private void Awake()
		{
			//IL_0648: Unknown result type (might be due to invalid IL or missing references)
			//IL_0656: Expected O, but got Unknown
			//IL_047d: Unknown result type (might be due to invalid IL or missing references)
			//IL_048a: Expected O, but got Unknown
			//IL_0254: Unknown result type (might be due to invalid IL or missing references)
			//IL_025e: Expected O, but got Unknown
			//IL_07e7: Unknown result type (might be due to invalid IL or missing references)
			//IL_07f5: Expected O, but got Unknown
			//IL_02a3: Unknown result type (might be due to invalid IL or missing references)
			//IL_02b0: Expected O, but got Unknown
			//IL_0279: Unknown result type (might be due to invalid IL or missing references)
			//IL_0287: Expected O, but got Unknown
			self = this;
			try
			{
				Assembly assembly = AppDomain.CurrentDomain.GetAssemblies().First((Assembly a) => a.GetName().Name == "Assembly-CSharp");
				string text = Hash(assembly.Location);
				if (text != "4590187091e0e8f064759ce9be534ebac862281744a19bdb030b8293dae5f72b")
				{
					throw new InvalidOperationException("Unsupported game build; diagnostics disabled. Do not use this build for copied-save testing.");
				}
				string value = ((BaseUnityPlugin)this).Config.Bind<string>("Testing", "SaveDirectory", "", "Optional dedicated copied-save directory. Empty uses normal game saves with an automatic backup.").Value;
				bool flag = !string.IsNullOrWhiteSpace(value);
				saveDirectory = (flag ? Path.GetFullPath(value) : Path.Combine(Paths.BepInExRootPath, "save-backups"));
				steamMode = ((BaseUnityPlugin)this).Config.Bind<bool>("Networking", "UseSteam", !flag, "Use Steam lobby networking. Disable for same-PC or local network play.").Value;
				localAddress = ((BaseUnityPlugin)this).Config.Bind<string>("Networking", "LocalHostAddress", "127.0.0.1", "Host IPv4 address for Local play joining. Use the host local network IPv4, or 127.0.0.1 for same-PC tests. TCP port 28761.").Value;
				instanceLabel = ((BaseUnityPlugin)this).Config.Bind<string>("Testing", "InstanceLabel", "SINGLE TEST", "Visible test-copy role; does not establish a network connection.").Value;
				classPreference = ((BaseUnityPlugin)this).Config.Bind<int>("Appearance", "CosmeticClass", 0, "Cosmetic role: 0 Unassigned, 1 Wizard, 2 Fighter, 3 Archer, 4 Assassin. Does not affect gameplay.");
				selectedClass = PartyClass.Clamp(classPreference.Value);
				detailedEvents = ((BaseUnityPlugin)this).Config.Bind<bool>("Diagnostics", "DetailedEvents", false, "Enable frequent combat/inventory hooks for a dedicated trace. Restart required; leave off for normal performance testing.").Value;
				if (flag && (!Directory.Exists(saveDirectory) || !File.Exists(Path.Combine(saveDirectory, "FateMainSave.save"))))
				{
					throw new InvalidOperationException("Copied test save is missing: " + saveDirectory);
				}
				MethodInfo method = assembly.GetType("Tivola.Framework.SaveManagerStandalone", throwOnError: true).GetMethod("GetFilePath", Flags, null, new Type[1] { typeof(string) }, null);
				if (method == null || method.ReturnType != typeof(string))
				{
					throw new MissingMethodException("Save path hook missing");
				}
				if ((bool)assembly.GetType("Tivola.Framework.SaveManager", throwOnError: true).GetProperty("HasEverLoaded", Flags).GetValue(null, null))
				{
					throw new InvalidOperationException("Save backend already loaded; copied-save isolation cannot be established.");
				}
				harmony = new Harmony("fate.local.diagnostics");
				if (flag)
				{
					harmony.Patch((MethodBase)method, new HarmonyMethod(typeof(Plugin), "SavePathPrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				}
				else
				{
					harmony.Patch((MethodBase)method, (HarmonyMethod)null, new HarmonyMethod(typeof(Plugin), "NormalSavePathObserved", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				}
				string text2 = Path.Combine(Paths.BepInExRootPath, "diagnostics");
				Directory.CreateDirectory(text2);
				events = new StreamWriter(Path.Combine(text2, "session-" + DateTime.UtcNow.ToString("yyyyMMdd-HHmmss") + ".log"), append: false);
				Log("START unity=" + Application.unityVersion + " assembly=" + text + " copied-save=" + saveDirectory);
				Log("MODE " + (detailedEvents ? "detailed events" : "lightweight: manual snapshots and lifecycle events"));
				Log("INSTANCE " + instanceLabel + " networking=not connected");
				VisualAppearance.SelfTest();
				Log("APPEARANCE_CODEC_SELFTEST passed", essential: true);
				string[] array = (detailedEvents ? Hooks : LightweightHooks);
				foreach (string text3 in array)
				{
					int num2 = text3.LastIndexOf('.');
					Type type = assembly.GetType(text3.Substring(0, num2));
					string name = text3.Substring(num2 + 1);
					MethodInfo[] array2 = (from m in type?.GetMethods(Flags)
						where m.DeclaringType == type && m.Name == name && !m.IsAbstract && !m.ContainsGenericParameters
						select m).ToArray();
					if (array2 == null || array2.Length == 0)
					{
						Log("MISSING " + text3);
						continue;
					}
					MethodInfo[] array3 = array2;
					foreach (MethodInfo methodInfo in array3)
					{
						try
						{
							harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(typeof(Plugin), methodInfo.IsStatic ? "StaticObserved" : "Observed", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
							Log("HOOK " + text3 + " token=" + methodInfo.MetadataToken.ToString("X8") + " return=" + methodInfo.ReturnType.Name + " args=" + string.Join(",", from p in methodInfo.GetParameters()
								select p.ParameterType.Name));
						}
						catch (Exception ex)
						{
							Log("HOOK_FAILED " + text3 + " " + ex.GetType().Name);
						}
					}
				}
				SceneManager.sceneLoaded += SceneLoaded;
				teammateHud = new TeammateHud(assembly, localSession, () => cachedPlayer, () => MenuAvailable && !FloorEntryPending && !ConnectionBlocked, delegate(string message)
				{
					Log(message, essential: true);
				})
				{
					CosmeticClass = () => selectedClass
				};
				try
				{
					sessionPause = new SessionPause(harmony, assembly, delegate(string message)
					{
						Log(message, essential: true);
					});
				}
				catch (Exception ex2)
				{
					((BaseUnityPlugin)this).Logger.LogError((object)("Menu pause feature disabled: " + ex2));
				}
				MethodInfo methodInfo2 = AccessTools.Method(assembly.GetType("Tableflip.LevelGenerator"), "Create", (Type[])null, (Type[])null);
				if (methodInfo2 != null)
				{
					harmony.Patch((MethodBase)methodInfo2, new HarmonyMethod(typeof(Plugin), "BeginLevelLoading", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				}
				try
				{
					sharedItems = new SharedItems(harmony, assembly, localSession, () => cachedPlayer, CurrentCampaign, delegate
					{
						//IL_0000: Unknown result type (might be due to invalid IL or missing references)
						//IL_0005: Unknown result type (might be due to invalid IL or missing references)
						Scene activeScene = SceneManager.GetActiveScene();
						return ((Scene)(ref activeScene)).name;
					}, () => sessionPause != null && sessionPause.Ready && !gameplayLoading, SharedScope, Path.Combine(text2, "item-transactions.jsonl"), delegate(string message)
					{
						Log(message, essential: true);
					});
				}
				catch (Exception ex3)
				{
					((BaseUnityPlugin)this).Logger.LogError((object)("Shared item feature unavailable: " + ex3));
				}
				try
				{
					sharedPortals = new SharedPortals(harmony, assembly, localSession, SharedScope, delegate(string message)
					{
						Log(message, essential: true);
					});
				}
				catch (Exception ex4)
				{
					((BaseUnityPlugin)this).Logger.LogError((object)("Shared portals unavailable: " + ex4));
				}
				array = new string[3] { "Tableflip.UniqueIdleAnimationState", "Tableflip.AttackAnimationState", "Tableflip.DeathAnimationState" };
				foreach (string name2 in array)
				{
					foreach (MethodInfo item in from m in assembly.GetType(name2, throwOnError: true).GetMethods(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
						where m.Name.StartsWith("OnState", StringComparison.Ordinal) && m.GetParameters().Length == 3 && m.GetParameters()[0].ParameterType == typeof(Animator)
						select m)
					{
						harmony.Patch((MethodBase)item, new HarmonyMethod(typeof(Plugin), "VisualIdleGuard", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
					}
				}
				new VillageEntryGate(harmony, assembly, localSession, delegate(string message)
				{
					Log(message, essential: true);
				});
				try
				{
					dungeonDrag = new DungeonDrag(harmony, assembly, localSession, () => sessionPause != null && sessionPause.Ready && !gameplayLoading, CurrentCampaign, () => saveDirectory, delegate(string message)
					{
						Log(message, essential: true);
					});
				}
				catch (Exception ex5)
				{
					((BaseUnityPlugin)this).Logger.LogError((object)("Dungeon drag unavailable: " + ex5));
				}
				try
				{
					supportSpells = new SupportSpells(harmony, assembly, localSession, () => cachedPlayer, SupportScope, delegate(string message)
					{
						Log(message, essential: true);
					});
					supportSpells.PickModel = (Ray ray) => (from p in remoteViews
						where p.Value.avatar != null
						select new
						{
							id = p.Key,
							distance = p.Value.avatar.PickDistance(ray)
						} into p
						where p.distance.HasValue
						orderby p.distance.Value
						select p.id).FirstOrDefault();
					teammateHud.SelectTarget = delegate(string id)
					{
						supportSpells.Select(id);
					};
					teammateHud.SelectedTarget = () => supportSpells.Selected;
					teammateHud.CastTarget = delegate(string id)
					{
						supportSpells.CastSelected(id);
					};
					teammateHud.SameSpace = (string id) => supportSpells.IsMemberHere(id);
				}
				catch (Exception ex6)
				{
					((BaseUnityPlugin)this).Logger.LogError((object)("Support spell feature unavailable: " + ex6));
				}
				try
				{
					downedState = new DownedState(harmony, assembly, localSession, () => cachedPlayer, SupportScope, delegate(string message)
					{
						Log(message, essential: true);
					});
				}
				catch (Exception ex7)
				{
					((BaseUnityPlugin)this).Logger.LogError((object)("Downed feature unavailable: " + ex7));
				}
				try
				{
					if (dungeonDrag != null)
					{
						sharedFloors = new SharedFloors(harmony, assembly, localSession, dungeonDrag, () => sessionPause != null && sessionPause.Ready && !gameplayLoading, CurrentCampaign, delegate(string message)
						{
							Log(message, essential: true);
						});
					}
				}
				catch (Exception ex8)
				{
					((BaseUnityPlugin)this).Logger.LogError((object)("Individual floor synchronization unavailable: " + ex8));
				}
				try
				{
					sharedWorld = new SharedWorld(harmony, assembly, localSession, SharedScope, () => cachedPlayer, delegate(string message)
					{
						Log(message, essential: true);
					});
				}
				catch (Exception ex9)
				{
					((BaseUnityPlugin)this).Logger.LogError((object)("Shared objects unavailable: " + ex9));
				}
				try
				{
					combatVisuals = new CombatVisuals(harmony, assembly, localSession, SharedScope, () => localActors, delegate(string message)
					{
						Log(message, essential: true);
					});
				}
				catch (Exception ex10)
				{
					((BaseUnityPlugin)this).Logger.LogError((object)("Combat visuals unavailable: " + ex10));
				}
				try
				{
					spellAudio = new SpellAudio(harmony, assembly, localSession, SharedScope, (object actor) => (combatVisuals != null && combatVisuals.IsLocal(actor)) || (sharedActors?.OwnsAudio(actor) ?? false), delegate(string message)
					{
						Log(message, essential: true);
					});
				}
				catch (Exception ex11)
				{
					((BaseUnityPlugin)this).Logger.LogError((object)("Spell audio unavailable: " + ex11));
				}
				try
				{
					sharedActors = new SharedActors(harmony, assembly, localSession, SharedScope, () => cachedPlayer, (object actor) => combatVisuals != null && combatVisuals.IsLocal(actor), (Component actor) => combatVisuals.CaptureWorld(actor), delegate(string message)
					{
						Log(message, essential: true);
					});
				}
				catch (Exception ex12)
				{
					((BaseUnityPlugin)this).Logger.LogError((object)("NPC/enemy synchronization unavailable: " + ex12));
				}
				remoteMinimap = new RemoteMinimap(delegate(string message)
				{
					Log(message, essential: true);
				});
				try
				{
					if (sharedActors != null)
					{
						joinerCombat = new JoinerCombat(harmony, assembly, localSession, SharedScope, () => cachedPlayer, sharedActors, delegate(string message)
						{
							Log(message, essential: true);
						});
					}
				}
				catch (Exception ex13)
				{
					((BaseUnityPlugin)this).Logger.LogError((object)("Joiner combat unavailable: " + ex13));
				}
				try
				{
					partyQuests = new PartyQuests(harmony, assembly, localSession, () => cachedPlayer, CurrentCampaign, text2, delegate(string message)
					{
						Log(message, essential: true);
					});
				}
				catch (Exception ex14)
				{
					((BaseUnityPlugin)this).Logger.LogError((object)("Party quests unavailable: " + ex14));
				}
				VillageEntryGate.QuestReady = () => partyQuests?.Ready ?? false;
				if (sharedFloors != null && partyQuests != null)
				{
					sharedFloors.QuestsReady = () => partyQuests.Ready;
					sharedFloors.NeedsQuestRecovery = partyQuests.NeedsFloorRecovery;
					partyQuests.RecoverFloor = sharedFloors.RequestQuestRecovery;
					partyQuests.HealthyFloor = sharedFloors.ReportHealthyFloor;
				}
				receiptSaves = new ReceiptSaveQueue(() => Convert.ToBoolean(AccessTools.Field(NativeTypes.Get("Tableflip.SaveData"), "isSaving").GetValue(null)), delegate(Action done)
				{
					AccessTools.Method(NativeTypes.Get("Tableflip.SaveData"), "RequestSave", (Type[])null, (Type[])null).Invoke(null, new object[1] { done });
				}, delegate(Exception ex17)
				{
					Log("RECEIPT_SAVE_FAILED " + ex17.GetBaseException(), essential: true);
				});
				if (partyQuests != null)
				{
					partyQuests.MemberColor = (string member) => PartyClass.NameTint(View(member).partyClass);
					partyQuests.MemberName = (string member) => (member != null) ? View(member).name : "Teammate";
					partyQuests.PeerFollower = (string member) => joinerCombat?.TargetFor(member);
					if (sharedItems != null)
					{
						partyQuests.GrantItemReward = delegate(QuestReward reward, Action saved)
						{
							sharedItems.GrantQuestReward(reward, saved);
						};
					}
				}
				if (sharedFloors != null)
				{
					sharedItems.Expedition = () => dungeonDrag?.Expedition;
					sharedItems.QuestItemKey = (object item) => partyQuests?.ItemObjective(item);
					sharedItems.QuestItemOwners = (object item) => partyQuests?.ItemOwners(item);
					sharedItems.QuestOwnerForMember = (string member) => partyQuests?.OwnerForMember(member);
					dungeonDrag.CaptureGold = (WorldScope active) => sharedItems.CaptureDragGold(active);
					dungeonDrag.ApplyGold = delegate(WorldScope active, DropPacket[] drops)
					{
						sharedItems.ApplySavedGold(active, drops);
					};
					sharedFloors.ApplyGold = delegate(WorldScope active, DropPacket[] drops)
					{
						sharedItems.ApplySavedGold(active, drops);
					};
					sharedFloors.FilterGold = (DropPacket[] drops) => sharedItems.FilterSavedGold(drops);
					sharedFloors.FilterItems = (FloorPacket packet) => sharedItems.FilterFloorSnapshot(packet);
					sharedFloors.CaptureArrival = delegate(WorldScope active)
					{
						FloorArrival floorArrival = new FloorArrival();
						sharedActors.CaptureArrival(floorArrival, active);
						floorArrival.objects = sharedWorld.CaptureArrival(active);
						floorArrival.drops = sharedItems.CaptureArrival(active);
						return floorArrival;
					};
					sharedFloors.ApplyArrival = delegate(WorldScope active, FloorArrival packet)
					{
						sharedActors.ApplyArrival(packet);
						sharedWorld.ApplyArrival(active, packet.objects);
						sharedItems.ApplyArrival(active, packet.drops);
					};
				}
				if (sharedFloors != null)
				{
					try
					{
						sharedDeath = new SharedDeath(harmony, assembly, localSession, sharedFloors, delegate(string message)
						{
							Log(message, essential: true);
						});
					}
					catch (Exception ex15)
					{
						((BaseUnityPlugin)this).Logger.LogError((object)("Shared death unavailable: " + ex15));
					}
				}
				migrationState = new HostMigrationState(localSession, Path.Combine(text2, "migration"), delegate
				{
					Dictionary<string, string> dictionary = sharedFloors?.ExportAuthorityParts() ?? new Dictionary<string, string>();
					if (sharedItems != null)
					{
						dictionary["items"] = sharedItems.ExportAuthority();
					}
					if (partyQuests != null)
					{
						dictionary["quests"] = partyQuests.ExportAuthority();
					}
					return dictionary;
				}, delegate(string key, string body)
				{
					if (key == "items")
					{
						sharedItems.ImportAuthority(body);
					}
					else if (key == "quests")
					{
						partyQuests.ImportAuthority(body);
					}
					else
					{
						sharedFloors.ImportAuthority(body);
					}
				}, delegate(string message)
				{
					Log(message, essential: true);
				});
				if (partyQuests != null)
				{
					partyQuests.MigrationFence = delegate(Action action)
					{
						migrationState.Fence(action);
					};
				}
				EnsureDriver();
			}
			catch (Exception ex16)
			{
				((BaseUnityPlugin)this).Logger.LogError((object)ex16);
				((Behaviour)this).enabled = false;
			}
		}

		private static void NormalSavePathObserved(string __result)
		{
			if (string.IsNullOrWhiteSpace(__result))
			{
				throw new IOException("Native save path was empty");
			}
			string fullPath = Path.GetFullPath(__result);
			saveDirectory = Path.GetDirectoryName(fullPath);
			if (File.Exists(fullPath) && !backedUpNormalSaves.Contains(fullPath))
			{
				string text = Path.Combine(Paths.BepInExRootPath, "save-backups", DateTime.UtcNow.ToString("yyyyMMdd-HHmmss-fffffff"));
				Directory.CreateDirectory(text);
				string text2 = Path.Combine(text, Path.GetFileName(fullPath));
				File.Copy(fullPath, text2, overwrite: false);
				if (Hash(fullPath) != Hash(text2))
				{
					throw new IOException("Automatic normal-save backup verification failed");
				}
				backedUpNormalSaves.Add(fullPath);
				self.Log("NORMAL_SAVE_BACKUP source=" + fullPath + " backup=" + text2, essential: true);
			}
		}

		private static bool SavePathPrefix(string __0, ref string __result)
		{
			if (string.IsNullOrEmpty(__0) || Path.GetFileName(__0) != __0 || __0.Contains(".."))
			{
				throw new InvalidOperationException("Unexpected save filename");
			}
			__result = Path.Combine(saveDirectory, __0 + ".save");
			self.Log("SAVE_PATH " + __result);
			return false;
		}

		private static void StaticObserved(MethodBase __originalMethod, object[] __args)
		{
			Observe(__originalMethod, null, __args);
		}

		private static bool VisualIdleGuard(Animator __0)
		{
			if (Object.op_Implicit((Object)(object)__0) && Object.op_Implicit((Object)(object)((Component)__0).GetComponent(NativeTypes.Get("Tableflip.Character"))))
			{
				SharedActors obj = self.sharedActors;
				if (obj == null)
				{
					return true;
				}
				return !obj.IsReplicaAnimator(__0);
			}
			return false;
		}

		private static void Observed(MethodBase __originalMethod, object __instance, object[] __args)
		{
			Observe(__originalMethod, __instance, __args);
		}

		private static void Observe(MethodBase method, object instance, object[] args)
		{
			try
			{
				if (method.DeclaringType.FullName == "Tableflip.PlayerManager" && method.Name == "RequestPlayerSpawn")
				{
					ObjectCache.Clear();
					EnsureDriver((Component)((instance is Component) ? instance : null));
				}
				if (method.DeclaringType.FullName == "Tableflip.LevelGenerator" && (method.Name == "OnFinishCreation" || method.Name == "FinishLoadSave"))
				{
					self.gameplayLoading = false;
				}
				self.Log("CALL " + method.DeclaringType.FullName + "." + method.Name + " object=" + Describe(instance) + " args=[" + string.Join(";", args.Select(Describe)) + "]");
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)self).Logger.LogWarning((object)("Observation failed: " + ex.GetType().Name));
			}
		}

		private static void BeginLevelLoading()
		{
			self.gameplayLoading = true;
			self.localSession.SubmitFrame(PlayerFrame.Unavailable);
			self.RemoveAllRemoteAvatars();
			self.remoteMinimap?.Dispose();
			self.sharedItems?.ReleaseDungeonObjects();
			PartyQuests.ReleaseSceneActors();
			VisualAppearance.ClearAssets();
		}

		private unsafe void SceneLoaded(Scene scene, LoadSceneMode mode)
		{
			if (recoveryStarted && SessionRules.Village(((Scene)(ref scene)).name))
			{
				recoveryPending = (recoveryStarted = false);
				recoveryReason = null;
			}
			if (SessionRules.Village(((Scene)(ref scene)).name))
			{
				sharedItems?.ReleaseDungeonObjects();
			}
			if (((Scene)(ref scene)).name.Contains("MainMenu") || ((Scene)(ref scene)).name == "Scene_GameSelect" || ((Scene)(ref scene)).name == "Scene_Engagement" || ((Scene)(ref scene)).name == "Scene_Startup")
			{
				StopSession();
			}
			sessionPause?.ResetScene();
			gameplayLoading = ((Scene)(ref scene)).name == "Scene_Dungeon";
			localSession.SubmitFrame(PlayerFrame.Unavailable);
			ObjectCache.Clear();
			cachedPlayer = null;
			hasPreviousPosition = false;
			lastSentPlayerName = null;
			lastSentClass = -1;
			lastAppearanceSignature = null;
			nextAppearance = 0f;
			RemoveVisualAvatar();
			RemoveAllRemoteAvatars();
			EnsureDriver();
			Log("SCENE " + ((Scene)(ref scene)).name + " mode=" + ((object)(*(LoadSceneMode*)(&mode))/*cast due to .constrained prefix*/).ToString());
			remoteMinimap?.Dispose();
			VisualAppearance.ClearAssets();
		}

		private static void EnsureDriver(Component anchor = null)
		{
			//IL_0033: Unknown result type (might be due to invalid IL or missing references)
			if (!Object.op_Implicit((Object)(object)Object.FindObjectOfType<DiagnosticsDriver>()))
			{
				if (!Object.op_Implicit((Object)(object)anchor))
				{
					object obj = Find("Tableflip.PlayerManager");
					anchor = (Component)((obj is Component) ? obj : null);
				}
				GameObject val = (GameObject)(Object.op_Implicit((Object)(object)anchor) ? ((object)anchor.gameObject) : ((object)new GameObject("FateDiagnostics.Driver")));
				if (!Object.op_Implicit((Object)(object)anchor))
				{
					Object.DontDestroyOnLoad((Object)(object)val);
				}
				val.AddComponent<DiagnosticsDriver>();
				self.Log("DRIVER attached to " + ((Object)val).name);
			}
		}

		internal static void DriverUpdate()
		{
			self?.Poll();
		}

		internal static void DriverGUI()
		{
			self?.DrawTeammateIndicator();
			self?.DrawConnectionRecovery();
			DownedState.Draw();
			SessionPause.DrawPetReturnNotice();
		}

		internal static void DriverQuit()
		{
			self?.localSession.Dispose();
			self?.FlushLogs();
		}

		private void Poll()
		{
			//IL_116e: Unknown result type (might be due to invalid IL or missing references)
			//IL_1173: Unknown result type (might be due to invalid IL or missing references)
			//IL_0f36: Unknown result type (might be due to invalid IL or missing references)
			//IL_0f3b: Unknown result type (might be due to invalid IL or missing references)
			//IL_11ab: Unknown result type (might be due to invalid IL or missing references)
			//IL_11b0: Unknown result type (might be due to invalid IL or missing references)
			UpdatePanelInput();
			try
			{
				localSession.Tick();
			}
			catch (Exception ex)
			{
				Log("TRANSPORT_TICK_FAILED " + ex.GetBaseException().Message, essential: true);
			}
			string text = string.Join(",", localSession.PeerIds);
			if (text != lastMemberSet)
			{
				lastMemberSet = text;
				lastAppearanceSignature = null;
				lastSentPlayerName = null;
				lastSentClass = -1;
				nextAppearance = 0f;
			}
			try
			{
				migrationState?.Tick(clock.Elapsed.TotalSeconds);
			}
			catch (Exception ex2)
			{
				Log("HOST_MIGRATION_STATE_FAILED " + ex2.GetBaseException(), essential: true);
			}
			CheckConnectionHealth();
			receiptSaves?.Tick();
			string result;
			while (localSession.Events.TryDequeue(out result))
			{
				Log("LOCAL_CONNECTION " + result, essential: true);
				lastAppearanceSignature = null;
				lastSentPlayerName = null;
				lastSentClass = -1;
				nextAppearance = 0f;
			}
			string[] array = remoteViews.Keys.Where((string id) => id != "pending" && !localSession.PeerIds.Contains(id)).ToArray();
			foreach (string text2 in array)
			{
				DisposeView(remoteViews[text2]);
				remoteViews.Remove(text2);
				combatVisuals?.RemoveMember(text2);
			}
			while (true)
			{
				if (!localSession.Messages.TryDequeue(out var message))
				{
					break;
				}
				if (message.Source != localSession.HostId && !localSession.PeerIds.Contains(message.Source))
				{
					Log("STALE_MEMBER_MESSAGE " + message.Kind, essential: true);
					continue;
				}
				localSession.ContextPeer = message.Source;
				try
				{
					if (localSession.IsHosting && (message.Kind == "appearance" || message.Kind == "player-name" || message.Kind == "player-class" || message.Kind.StartsWith("visual-", StringComparison.Ordinal) || message.Kind == "spell-sound"))
					{
						localSession.Relay(message);
					}
					if (message.Kind == "party-hud")
					{
						teammateHud.Receive(message);
						if (localSession.IsHosting)
						{
							localSession.Relay(message);
						}
						continue;
					}
					if (localSession.IsHosting && !string.IsNullOrEmpty(message.Recipient) && message.Recipient != localSession.MemberId && (message.Kind == "actor-hit" || message.Kind.StartsWith("joiner-", StringComparison.Ordinal)))
					{
						WorldScope worldScope = ((message.Kind == "joiner-defense") ? PayloadCodec.Decode<JoinerDefense>(message.Data).scope : PayloadCodec.Decode<ActorHit>(message.Data).scope);
						string text3 = ((worldScope == null) ? null : localSession.Authority(worldScope.scene, worldScope.campaign, worldScope.realm, worldScope.depth));
						PlayerFrame playerFrame = localSession.Frame(message.Source);
						PlayerFrame playerFrame2 = localSession.Frame(message.Recipient);
						if (worldScope == null || !playerFrame.Ready || playerFrame.Scene != worldScope.scene || playerFram