Decompiled source of PraetorisClient v0.1.8

PraetorisClient.dll

Decompiled 2 months ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using System.Text;
using System.Text.RegularExpressions;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Jotunn.Managers;
using Jotunn.Utils;
using Microsoft.CodeAnalysis;
using TMPro;
using UnityEngine;
using UnityEngine.Networking;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyTitle("PraetorisClient")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("PraetorisClient")]
[assembly: AssemblyCopyright("Copyright ©  2023")]
[assembly: AssemblyTrademark("")]
[assembly: ComVisible(false)]
[assembly: Guid("3A303D15-4E60-4110-8EDF-EDF38560FBE2")]
[assembly: AssemblyFileVersion("0.1.8")]
[assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("0.1.8.0")]
[module: UnverifiableCode]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

		public NullableAttribute(byte P_0)
		{
			NullableFlags = new byte[1] { P_0 };
		}

		public NullableAttribute(byte[] P_0)
		{
			NullableFlags = P_0;
		}
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableContextAttribute : Attribute
	{
		public readonly byte Flag;

		public NullableContextAttribute(byte P_0)
		{
			Flag = P_0;
		}
	}
}
namespace PraetorisClient
{
	internal static class BotApiClient
	{
		public static IEnumerator PostLinkRoutine(LinkRequest link, Action<long, string, bool, string> sendResult)
		{
			string linkApiUrl = PraetorisClientPlugin.GetLinkApiUrl();
			string botApiKey = PraetorisClientPlugin.GetBotApiKey();
			if (string.IsNullOrWhiteSpace(linkApiUrl))
			{
				sendResult(link.Sender, link.RequestId, arg3: false, "Link API URL is not configured on the server.");
				yield break;
			}
			if (string.IsNullOrWhiteSpace(botApiKey))
			{
				sendResult(link.Sender, link.RequestId, arg3: false, "Bot API key is not configured on the server.");
				yield break;
			}
			string s = JsonLinkRequest(link);
			byte[] bytes = Encoding.UTF8.GetBytes(s);
			UnityWebRequest request = new UnityWebRequest(linkApiUrl, "POST")
			{
				uploadHandler = (UploadHandler)new UploadHandlerRaw(bytes),
				downloadHandler = (DownloadHandler)new DownloadHandlerBuffer()
			};
			try
			{
				request.SetRequestHeader("Content-Type", "application/json");
				request.SetRequestHeader("X-API-Key", botApiKey);
				request.SetRequestHeader("User-Agent", "PraetorisClient/0.1");
				yield return request.SendWebRequest();
				string text = ((request.downloadHandler != null) ? request.downloadHandler.text : "");
				if ((int)request.result != 1 || request.responseCode < 200 || request.responseCode >= 300)
				{
					string text2 = ((!string.IsNullOrWhiteSpace(text)) ? text : (string.IsNullOrWhiteSpace(request.error) ? ("HTTP " + request.responseCode) : (request.error + " (HTTP " + request.responseCode + ")")));
					PraetorisClientPlugin.Log.LogWarning((object)("Discord link API failed for " + link.PlayerId + ": " + text2));
					sendResult(link.Sender, link.RequestId, arg3: false, text2);
				}
				else
				{
					string arg = (string.IsNullOrWhiteSpace(text) ? "Discord link complete." : text);
					PraetorisClientPlugin.Log.LogInfo((object)("Discord link API accepted " + link.PlayerId + "."));
					sendResult(link.Sender, link.RequestId, arg3: true, arg);
				}
			}
			finally
			{
				((IDisposable)request)?.Dispose();
			}
		}

		private static string JsonLinkRequest(LinkRequest link)
		{
			return "{\"requestId\":\"" + EscapeJson(link.RequestId) + "\",\"code\":\"" + EscapeJson(link.Code) + "\",\"playerId\":\"" + EscapeJson(link.PlayerId) + "\",\"playerName\":\"" + EscapeJson(link.PlayerName) + "\",\"endpoint\":\"" + EscapeJson(link.Endpoint) + "\",\"platformDisplayName\":\"" + EscapeJson(link.PlatformDisplayName) + "\",\"receivedAtUtc\":\"" + EscapeJson(link.ReceivedAtUtc.ToString("O", CultureInfo.InvariantCulture)) + "\"}";
		}

		private static string EscapeJson(string value)
		{
			return (value ?? "").Replace("\\", "\\\\").Replace("\"", "\\\"").Replace("\r", "\\r")
				.Replace("\n", "\\n");
		}
	}
	internal static class CreativeBiomeOverride
	{
		private sealed class OverrideZone
		{
			public Vector3 Center { get; }

			public float Radius { get; }

			public float RadiusSquared { get; }

			public float VisualRadius { get; }

			public float VisualRadiusSquared { get; }

			public float TerrainRadius { get; }

			public float TerrainRadiusSquared { get; }

			public Biome Biome { get; }

			public bool SuppressSpawns { get; }

			public bool UseTerrainSource { get; }

			public Vector3 TerrainSourceCenter { get; }

			public OverrideZone(Vector3 center, float radius, Biome biome, bool suppressSpawns, bool useTerrainSource, Vector3 terrainSourceCenter)
			{
				//IL_0007: Unknown result type (might be due to invalid IL or missing references)
				//IL_0008: Unknown result type (might be due to invalid IL or missing references)
				//IL_0065: Unknown result type (might be due to invalid IL or missing references)
				//IL_0066: Unknown result type (might be due to invalid IL or missing references)
				//IL_007c: Unknown result type (might be due to invalid IL or missing references)
				//IL_007e: Unknown result type (might be due to invalid IL or missing references)
				Center = center;
				Radius = radius;
				RadiusSquared = radius * radius;
				VisualRadius = radius + 16f;
				VisualRadiusSquared = VisualRadius * VisualRadius;
				TerrainRadius = (useTerrainSource ? (radius + 256f) : radius);
				TerrainRadiusSquared = TerrainRadius * TerrainRadius;
				Biome = biome;
				SuppressSpawns = suppressSpawns;
				UseTerrainSource = useTerrainSource;
				TerrainSourceCenter = terrainSourceCenter;
			}

			public bool Contains(float x, float z)
			{
				return ContainsRadius(x, z, RadiusSquared);
			}

			public bool ContainsTerrain(float x, float z)
			{
				return ContainsRadius(x, z, TerrainRadiusSquared);
			}

			private bool ContainsRadius(float x, float z, float radiusSquared)
			{
				//IL_0002: Unknown result type (might be due to invalid IL or missing references)
				//IL_000f: Unknown result type (might be due to invalid IL or missing references)
				float num = x - Center.x;
				float num2 = z - Center.z;
				return num * num + num2 * num2 <= radiusSquared;
			}

			public Vector2 MapToSource(float x, float z)
			{
				//IL_0001: Unknown result type (might be due to invalid IL or missing references)
				//IL_000d: Unknown result type (might be due to invalid IL or missing references)
				//IL_001a: Unknown result type (might be due to invalid IL or missing references)
				//IL_0026: Unknown result type (might be due to invalid IL or missing references)
				//IL_0032: Unknown result type (might be due to invalid IL or missing references)
				return new Vector2(TerrainSourceCenter.x + (x - Center.x), TerrainSourceCenter.z + (z - Center.z));
			}

			public bool ContainsVisual(float x, float z)
			{
				//IL_0002: Unknown result type (might be due to invalid IL or missing references)
				//IL_000f: Unknown result type (might be due to invalid IL or missing references)
				float num = x - Center.x;
				float num2 = z - Center.z;
				return num * num + num2 * num2 <= VisualRadiusSquared;
			}
		}

		[HarmonyPatch(typeof(WorldGenerator), "GetBiome", new Type[]
		{
			typeof(float),
			typeof(float),
			typeof(float),
			typeof(bool)
		})]
		private static class WorldGeneratorGetBiomePatch
		{
			private static bool Prefix(float wx, float wy, ref Biome __result)
			{
				//IL_000e: Unknown result type (might be due to invalid IL or missing references)
				//IL_0010: Expected I4, but got Unknown
				if (!TryGetBiome(wx, wy, out var biome))
				{
					return true;
				}
				__result = (Biome)(int)biome;
				return false;
			}
		}

		[HarmonyPatch(typeof(Heightmap), "GetBiome", new Type[]
		{
			typeof(Vector3),
			typeof(float),
			typeof(bool)
		})]
		private static class HeightmapGetBiomePatch
		{
			private static bool Prefix(Vector3 point, ref Biome __result)
			{
				//IL_0000: Unknown result type (might be due to invalid IL or missing references)
				//IL_0006: Unknown result type (might be due to invalid IL or missing references)
				//IL_0018: Unknown result type (might be due to invalid IL or missing references)
				//IL_001a: Expected I4, but got Unknown
				if (!TryGetBiome(point.x, point.z, out var biome))
				{
					return true;
				}
				__result = (Biome)(int)biome;
				return false;
			}
		}

		[HarmonyPatch(typeof(WorldGenerator), "GetBiomeHeight")]
		private static class WorldGeneratorGetBiomeHeightPatch
		{
			private static void Prefix(ref Biome biome, ref float wx, ref float wy)
			{
				//IL_0021: Unknown result type (might be due to invalid IL or missing references)
				//IL_0027: Unknown result type (might be due to invalid IL or missing references)
				//IL_0033: Unknown result type (might be due to invalid IL or missing references)
				//IL_0039: Expected I4, but got Unknown
				//IL_0043: Unknown result type (might be due to invalid IL or missing references)
				//IL_004b: Unknown result type (might be due to invalid IL or missing references)
				if (TryMapToSource(wx, wy, out var source) && WorldGenerator.instance != null)
				{
					_samplingSourceTerrain = true;
					try
					{
						biome = (Biome)(int)WorldGenerator.instance.GetBiome(source.x, source.y, 0.02f, false);
					}
					finally
					{
						_samplingSourceTerrain = false;
					}
					wx = source.x;
					wy = source.y;
				}
			}
		}

		[HarmonyPatch(typeof(WorldGenerator), "GetHeight", new Type[]
		{
			typeof(float),
			typeof(float)
		})]
		private static class WorldGeneratorGetHeightPatch
		{
			private static bool Prefix(float wx, float wy, ref float __result)
			{
				//IL_0020: Unknown result type (might be due to invalid IL or missing references)
				//IL_0026: Unknown result type (might be due to invalid IL or missing references)
				if (!TryMapToSource(wx, wy, out var source) || WorldGenerator.instance == null)
				{
					return true;
				}
				_samplingSourceTerrain = true;
				try
				{
					__result = WorldGenerator.instance.GetHeight(source.x, source.y);
				}
				finally
				{
					_samplingSourceTerrain = false;
				}
				return false;
			}
		}

		[HarmonyPatch]
		private static class WorldGeneratorGetHeightWithMaskPatch
		{
			private static MethodBase TargetMethod()
			{
				return AccessTools.Method(typeof(WorldGenerator), "GetHeight", new Type[3]
				{
					typeof(float),
					typeof(float),
					typeof(Color).MakeByRefType()
				}, (Type[])null);
			}

			private static bool Prefix(float wx, float wy, ref Color mask, ref float __result)
			{
				//IL_0020: Unknown result type (might be due to invalid IL or missing references)
				//IL_0026: Unknown result type (might be due to invalid IL or missing references)
				if (!TryMapToSource(wx, wy, out var source) || WorldGenerator.instance == null)
				{
					return true;
				}
				_samplingSourceTerrain = true;
				try
				{
					__result = WorldGenerator.instance.GetHeight(source.x, source.y, ref mask);
				}
				finally
				{
					_samplingSourceTerrain = false;
				}
				return false;
			}
		}

		[HarmonyPatch(typeof(Heightmap), "GetBiomeColor", new Type[]
		{
			typeof(float),
			typeof(float)
		})]
		private static class HeightmapGetBiomeColorPatch
		{
			private static bool Prefix(Heightmap __instance, float ix, float iy, ref Color __result)
			{
				//IL_0026: Unknown result type (might be due to invalid IL or missing references)
				//IL_002b: Unknown result type (might be due to invalid IL or missing references)
				//IL_0066: Unknown result type (might be due to invalid IL or missing references)
				//IL_0067: Unknown result type (might be due to invalid IL or missing references)
				//IL_006c: Unknown result type (might be due to invalid IL or missing references)
				//IL_0071: Unknown result type (might be due to invalid IL or missing references)
				if ((Object)(object)__instance == (Object)null)
				{
					return true;
				}
				float num = (float)__instance.m_width * __instance.m_scale * 0.5f;
				Vector3 position = ((Component)__instance).transform.position;
				float x = position.x + (ix - 0.5f) * num * 2f;
				float z = position.z + (iy - 0.5f) * num * 2f;
				if (!TryGetVisualBiome(x, z, out var biome))
				{
					return true;
				}
				__result = Color32.op_Implicit(Heightmap.GetBiomeColor(biome));
				return false;
			}
		}

		[HarmonyPatch(typeof(WorldGenerator), "IsAshlands")]
		private static class WorldGeneratorIsAshlandsPatch
		{
			private static bool Prefix(float x, float y, ref bool __result)
			{
				//IL_000e: Unknown result type (might be due to invalid IL or missing references)
				//IL_0011: Invalid comparison between Unknown and I4
				if (!TryGetBiome(x, y, out var biome))
				{
					return true;
				}
				__result = (int)biome == 32;
				return false;
			}
		}

		[HarmonyPatch(typeof(WorldGenerator), "IsDeepnorth")]
		private static class WorldGeneratorIsDeepnorthPatch
		{
			private static bool Prefix(float x, float y, ref bool __result)
			{
				//IL_000e: Unknown result type (might be due to invalid IL or missing references)
				//IL_0011: Invalid comparison between Unknown and I4
				if (!TryGetBiome(x, y, out var biome))
				{
					return true;
				}
				__result = (int)biome == 64;
				return false;
			}
		}

		[HarmonyPatch(typeof(SpawnSystem), "IsSpawnPointGood")]
		private static class SpawnSystemIsSpawnPointGoodPatch
		{
			private static bool Prefix(ref Vector3 spawnPoint, ref bool __result)
			{
				//IL_0001: Unknown result type (might be due to invalid IL or missing references)
				if (!ContainsSpawnBlockedZone(spawnPoint))
				{
					return true;
				}
				__result = false;
				return false;
			}
		}

		private const int ProtocolVersion = 2;

		private const float VisualBiomeMargin = 16f;

		private const float TerrainSourcePadding = 256f;

		private static readonly Dictionary<string, OverrideZone> Zones = new Dictionary<string, OverrideZone>();

		private static readonly FieldInfo? HeightmapBuildDataField = AccessTools.Field(typeof(Heightmap), "m_buildData");

		private static bool _samplingSourceTerrain;

		public static void OnOverride(long sender, ZPackage pkg)
		{
			//IL_0079: Unknown result type (might be due to invalid IL or missing references)
			//IL_007e: Unknown result type (might be due to invalid IL or missing references)
			//IL_008e: Unknown result type (might be due to invalid IL or missing references)
			//IL_00be: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ff: Unknown result type (might be due to invalid IL or missing references)
			//IL_0103: Unknown result type (might be due to invalid IL or missing references)
			//IL_0109: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)ZNet.instance == (Object)null || ZNet.instance.IsServer())
			{
				return;
			}
			try
			{
				int num = pkg.ReadInt();
				if (num < 1 || num > 2)
				{
					PraetorisClientPlugin.Log.LogWarning((object)$"Ignoring creative biome override version {num}; expected 1-{2}.");
					return;
				}
				int num2 = pkg.ReadInt();
				if (num2 <= 0)
				{
					ClearAll();
					return;
				}
				for (int i = 0; i < num2; i++)
				{
					string text = pkg.ReadString();
					bool num3 = pkg.ReadBool();
					Vector3 center = pkg.ReadVector3();
					float num4 = pkg.ReadSingle();
					Biome val = (Biome)pkg.ReadInt();
					bool suppressSpawns = ((num2 == 1 && pkg.GetPos() < pkg.Size()) ? pkg.ReadBool() : (!text.StartsWith("siege_", StringComparison.OrdinalIgnoreCase)));
					bool useTerrainSource = false;
					Vector3 terrainSourceCenter = Vector3.zero;
					if (num >= 2 && pkg.GetPos() < pkg.Size())
					{
						useTerrainSource = pkg.ReadBool();
						terrainSourceCenter = pkg.ReadVector3();
					}
					if (!num3 || (int)val == 0 || num4 <= 0f)
					{
						Remove(text);
					}
					else
					{
						Set(text, center, num4, val, suppressSpawns, useTerrainSource, terrainSourceCenter);
					}
				}
			}
			catch (Exception arg)
			{
				PraetorisClientPlugin.Log.LogWarning((object)$"Failed to apply creative biome override: {arg}");
			}
		}

		public static bool TryGetBiome(float x, float z, out Biome biome)
		{
			//IL_0083: Unknown result type (might be due to invalid IL or missing references)
			//IL_0089: Expected I4, but got Unknown
			//IL_0042: Unknown result type (might be due to invalid IL or missing references)
			//IL_0047: Unknown result type (might be due to invalid IL or missing references)
			//IL_0054: Unknown result type (might be due to invalid IL or missing references)
			//IL_005a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0066: Unknown result type (might be due to invalid IL or missing references)
			//IL_006c: Expected I4, but got Unknown
			if (_samplingSourceTerrain)
			{
				biome = (Biome)0;
				return false;
			}
			foreach (OverrideZone value in Zones.Values)
			{
				if (!value.ContainsTerrain(x, z))
				{
					continue;
				}
				if (value.UseTerrainSource && WorldGenerator.instance != null)
				{
					Vector2 val = value.MapToSource(x, z);
					_samplingSourceTerrain = true;
					try
					{
						biome = (Biome)(int)WorldGenerator.instance.GetBiome(val.x, val.y, 0.02f, false);
					}
					finally
					{
						_samplingSourceTerrain = false;
					}
					return true;
				}
				if (!value.UseTerrainSource)
				{
					biome = (Biome)(int)value.Biome;
					return true;
				}
			}
			biome = (Biome)0;
			return false;
		}

		private static bool TryGetVisualBiome(float x, float z, out Biome biome)
		{
			//IL_0026: Unknown result type (might be due to invalid IL or missing references)
			//IL_002c: Expected I4, but got Unknown
			foreach (OverrideZone value in Zones.Values)
			{
				if (value.ContainsVisual(x, z))
				{
					biome = (Biome)(int)value.Biome;
					return true;
				}
			}
			biome = (Biome)0;
			return false;
		}

		private static bool TryMapToSource(float x, float z, out Vector2 source)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: 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_0049: Unknown result type (might be due to invalid IL or missing references)
			source = Vector2.zero;
			if (_samplingSourceTerrain)
			{
				return false;
			}
			foreach (OverrideZone value in Zones.Values)
			{
				if (value.UseTerrainSource && value.ContainsTerrain(x, z))
				{
					source = value.MapToSource(x, z);
					return true;
				}
			}
			return false;
		}

		public static bool ContainsSpawnBlockedZone(Vector3 point)
		{
			//IL_0023: 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)
			foreach (OverrideZone value in Zones.Values)
			{
				if (value.SuppressSpawns && value.Contains(point.x, point.z))
				{
					return true;
				}
			}
			return false;
		}

		private static void Set(string zoneId, Vector3 center, float radius, Biome biome, bool suppressSpawns, bool useTerrainSource, Vector3 terrainSourceCenter)
		{
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			//IL_004e: Unknown result type (might be due to invalid IL or missing references)
			//IL_005a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0082: Unknown result type (might be due to invalid IL or missing references)
			//IL_008b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0099: Unknown result type (might be due to invalid IL or missing references)
			zoneId = NormalizeZoneId(zoneId);
			OverrideZone value;
			bool num = Zones.TryGetValue(zoneId, out value);
			OverrideZone overrideZone = new OverrideZone(center, radius, biome, suppressSpawns, useTerrainSource, terrainSourceCenter);
			Zones[zoneId] = overrideZone;
			if (num)
			{
				RefreshTerrain(value);
			}
			RefreshTerrain(overrideZone);
			string text = (useTerrainSource ? $", terrainSource={terrainSourceCenter.x:0.##},{terrainSourceCenter.z:0.##}" : string.Empty);
			PraetorisClientPlugin.Log.LogInfo((object)$"Creative biome override {zoneId}: {biome} at {center.x:0.##},{center.z:0.##} radius {radius:0.##}, suppressSpawns={suppressSpawns}{text}.");
		}

		private static void Remove(string zoneId)
		{
			zoneId = NormalizeZoneId(zoneId);
			if (Zones.TryGetValue(zoneId, out OverrideZone value))
			{
				Zones.Remove(zoneId);
				RefreshTerrain(value);
				PraetorisClientPlugin.Log.LogInfo((object)("Removed creative biome override " + zoneId + "."));
			}
		}

		private static void ClearAll()
		{
			if (Zones.Count == 0)
			{
				return;
			}
			List<OverrideZone> list = new List<OverrideZone>(Zones.Values);
			Zones.Clear();
			foreach (OverrideZone item in list)
			{
				RefreshTerrain(item);
			}
			PraetorisClientPlugin.Log.LogInfo((object)"Cleared creative biome overrides.");
		}

		private static void RefreshTerrain(OverrideZone zone)
		{
			//IL_006c: Unknown result type (might be due to invalid IL or missing references)
			foreach (Heightmap allHeightmap in Heightmap.GetAllHeightmaps())
			{
				if (!((Object)(object)allHeightmap == (Object)null) && Intersects(allHeightmap, zone))
				{
					HeightmapBuildDataField?.SetValue(allHeightmap, null);
					allHeightmap.Poke(false);
				}
			}
			if ((Object)(object)ClutterSystem.instance != (Object)null)
			{
				ClutterSystem.instance.ResetGrass(zone.Center, zone.TerrainRadius);
			}
		}

		private static bool Intersects(Heightmap heightmap, OverrideZone zone)
		{
			//IL_001b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			//IL_004a: Unknown result type (might be due to invalid IL or missing references)
			float num = (float)heightmap.m_width * heightmap.m_scale * 0.5f;
			Vector3 position = ((Component)heightmap).transform.position;
			float num2 = Math.Max(Math.Abs(position.x - zone.Center.x) - num, 0f);
			float num3 = Math.Max(Math.Abs(position.z - zone.Center.z) - num, 0f);
			return num2 * num2 + num3 * num3 <= zone.TerrainRadiusSquared;
		}

		private static string NormalizeZoneId(string zoneId)
		{
			if (!string.IsNullOrWhiteSpace(zoneId))
			{
				return zoneId.Trim();
			}
			return "creative";
		}
	}
	internal static class CreativeCommandZoneState
	{
		private const int ProtocolVersion = 1;

		private const string DeniedMessage = "Creative commands can only be used inside your creative zone.";

		private static bool _active;

		private static Vector3 _center;

		private static float _radius;

		private static long _ownerPlayerId;

		private static long _playerId;

		private static string _slotId = string.Empty;

		private static bool _guardEnabled;

		private static string _protectedCommandPrefixes = string.Empty;

		internal static void OnState(long sender, ZPackage package)
		{
			//IL_0052: Unknown result type (might be due to invalid IL or missing references)
			//IL_0057: Unknown result type (might be due to invalid IL or missing references)
			//IL_00dd: Unknown result type (might be due to invalid IL or missing references)
			//IL_00de: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)ZNet.instance == (Object)null || ZNet.instance.IsServer())
			{
				return;
			}
			try
			{
				int num = package.ReadInt();
				if (num != 1)
				{
					PraetorisClientPlugin.Log.LogWarning((object)$"Ignoring creative command zone state protocol {num}; expected {1}.");
					return;
				}
				bool num2 = package.ReadBool();
				Vector3 center = package.ReadVector3();
				float num3 = package.ReadSingle();
				long ownerPlayerId = package.ReadLong();
				long num4 = package.ReadLong();
				string text = package.ReadString();
				bool guardEnabled = package.GetPos() < package.Size() && package.ReadBool();
				object obj = ((package.GetPos() < package.Size()) ? package.ReadString() : string.Empty);
				_guardEnabled = guardEnabled;
				if (obj == null)
				{
					obj = string.Empty;
				}
				_protectedCommandPrefixes = (string)obj;
				if (!num2 || num3 <= 0f || num4 == 0L)
				{
					Clear();
					return;
				}
				_active = true;
				_center = center;
				_radius = num3;
				_ownerPlayerId = ownerPlayerId;
				_playerId = num4;
				_slotId = text ?? string.Empty;
				PraetorisClientPlugin.Log.LogInfo((object)$"Creative command zone active: {_slotId} at {_center.x:0.##},{_center.z:0.##} radius {_radius:0.##}.");
			}
			catch (Exception arg)
			{
				Clear();
				PraetorisClientPlugin.Log.LogWarning((object)$"Failed to apply creative command zone state: {arg}");
			}
		}

		internal static void Clear()
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			_active = false;
			_center = Vector3.zero;
			_radius = 0f;
			_ownerPlayerId = 0L;
			_playerId = 0L;
			_slotId = string.Empty;
		}

		internal static bool CanRunCommand(ConsoleEventArgs args)
		{
			if (args == null || (Object)(object)ZNet.instance == (Object)null || ZNet.instance.IsServer() || !IsProtectedCommand(args.FullLine))
			{
				return true;
			}
			if (IsLocalPlayerInsideActiveZone())
			{
				return true;
			}
			object obj = ((object)args.Context) ?? ((object)Console.instance);
			if (obj != null)
			{
				((Terminal)obj).AddString("Creative commands can only be used inside your creative zone.");
			}
			return false;
		}

		private static bool IsProtectedCommand(string rawCommand)
		{
			if (!_guardEnabled)
			{
				return false;
			}
			string text = NormalizeCommand(rawCommand);
			if (text.Length == 0)
			{
				return false;
			}
			foreach (string protectedCommandPrefix in GetProtectedCommandPrefixes())
			{
				if (text.StartsWith(protectedCommandPrefix, StringComparison.Ordinal))
				{
					return true;
				}
			}
			return false;
		}

		private static bool IsLocalPlayerInsideActiveZone()
		{
			//IL_0040: Unknown result type (might be due to invalid IL or missing references)
			//IL_0045: Unknown result type (might be due to invalid IL or missing references)
			Player localPlayer = Player.m_localPlayer;
			if (!_active || (Object)(object)localPlayer == (Object)null || _radius <= 0f)
			{
				return false;
			}
			if (_playerId != 0L && localPlayer.GetPlayerID() != _playerId)
			{
				return false;
			}
			Vector3 position = ((Component)localPlayer).transform.position;
			float num = position.x - _center.x;
			float num2 = position.z - _center.z;
			return num * num + num2 * num2 <= _radius * _radius;
		}

		private static IEnumerable<string> GetProtectedCommandPrefixes()
		{
			return from value in _protectedCommandPrefixes.Split(new char[1] { ',' }, StringSplitOptions.RemoveEmptyEntries)
				select value.Trim().ToLowerInvariant() into value
				where value.Length > 0
				select value;
		}

		private static string NormalizeCommand(string rawCommand)
		{
			if (string.IsNullOrWhiteSpace(rawCommand))
			{
				return string.Empty;
			}
			string[] array = rawCommand.Trim().Split(new char[1] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
			if (array.Length != 0)
			{
				return array[0].ToLowerInvariant();
			}
			return string.Empty;
		}
	}
	internal static class CreativeInventoryRpc
	{
		private sealed class CreativeInventorySnapshot
		{
			public bool Available { get; set; }

			public string Error { get; set; } = string.Empty;

			public long PlayerId { get; set; }

			public string PlayerName { get; set; } = string.Empty;

			public int PlayerInventoryCount { get; set; }

			public bool ExtraSlotsLoaded { get; set; }

			public bool ExtraSlotsAvailable { get; set; }

			public int ExtraSlotsCount { get; set; }

			public int TotalUniqueCount { get; set; }

			public List<CreativeInventoryItem> Items { get; } = new List<CreativeInventoryItem>();

			public static CreativeInventorySnapshot Unavailable(string error)
			{
				return new CreativeInventorySnapshot
				{
					Available = false,
					Error = error
				};
			}
		}

		private sealed class CreativeInventoryItem
		{
			public string Source { get; set; } = string.Empty;

			public string PrefabName { get; set; } = string.Empty;

			public string SharedName { get; set; } = string.Empty;

			public int Stack { get; set; }

			public int Quality { get; set; }

			public bool Equipped { get; set; }

			public int GridX { get; set; }

			public int GridY { get; set; }

			public static CreativeInventoryItem FromItem(string source, ItemData item)
			{
				return new CreativeInventoryItem
				{
					Source = source,
					PrefabName = (((Object)(object)item.m_dropPrefab != (Object)null) ? ((Object)item.m_dropPrefab).name : string.Empty),
					SharedName = (item.m_shared?.m_name ?? string.Empty),
					Stack = item.m_stack,
					Quality = item.m_quality,
					Equipped = item.m_equipped,
					GridX = item.m_gridPos.x,
					GridY = item.m_gridPos.y
				};
			}
		}

		private sealed class ExtraSlotsReadResult
		{
			public bool ModLoaded { get; private set; }

			public bool Available { get; private set; }

			public string Error { get; private set; } = string.Empty;

			public List<ItemData> Items { get; } = new List<ItemData>();

			public static ExtraSlotsReadResult NotLoaded()
			{
				return new ExtraSlotsReadResult
				{
					ModLoaded = false,
					Available = true
				};
			}

			public static ExtraSlotsReadResult Loaded(IEnumerable<ItemData> items)
			{
				ExtraSlotsReadResult extraSlotsReadResult = new ExtraSlotsReadResult();
				extraSlotsReadResult.ModLoaded = true;
				extraSlotsReadResult.Available = true;
				extraSlotsReadResult.Items.AddRange(items);
				return extraSlotsReadResult;
			}

			public static ExtraSlotsReadResult Failed(string error)
			{
				return new ExtraSlotsReadResult
				{
					ModLoaded = true,
					Available = false,
					Error = error
				};
			}
		}

		private const int ProtocolVersion = 1;

		private const string ExtraSlotsApiTypeName = "ExtraSlots.API";

		public static void OnRequest(long sender, ZPackage pkg)
		{
			//IL_0092: Unknown result type (might be due to invalid IL or missing references)
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			//IL_0025: Unknown result type (might be due to invalid IL or missing references)
			//IL_0055: Unknown result type (might be due to invalid IL or missing references)
			//IL_005a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0062: Unknown result type (might be due to invalid IL or missing references)
			//IL_006d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0035: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)ZNet.instance == (Object)null || ZNet.instance.IsServer())
			{
				return;
			}
			string text = string.Empty;
			ZDOID val = ZDOID.None;
			bool flag = true;
			try
			{
				int num = pkg.ReadInt();
				if (num != 1)
				{
					SendUnavailable(sender, text, val, $"Unsupported creative inventory protocol version {num}.");
					return;
				}
				text = pkg.ReadString();
				val = pkg.ReadZDOID();
				flag = pkg.ReadBool();
				CreativeInventorySnapshot snapshot = BuildSnapshot(val, flag);
				SendResponse(sender, text, val, snapshot);
			}
			catch (Exception arg)
			{
				PraetorisClientPlugin.Log.LogWarning((object)$"Failed to answer creative inventory request {text}: {arg}");
				SendUnavailable(sender, text, val, "Failed to read client inventory.");
			}
		}

		private static CreativeInventorySnapshot BuildSnapshot(ZDOID expectedCharacterId, bool includeItems)
		{
			//IL_0059: Unknown result type (might be due to invalid IL or missing references)
			//IL_005e: Unknown result type (might be due to invalid IL or missing references)
			Player localPlayer = Player.m_localPlayer;
			if ((Object)(object)localPlayer == (Object)null || (Object)(object)((Character)localPlayer).m_nview == (Object)null || !((Character)localPlayer).m_nview.IsValid())
			{
				return CreativeInventorySnapshot.Unavailable("Local player is not available.");
			}
			ZDO zDO = ((Character)localPlayer).m_nview.GetZDO();
			if (zDO == null)
			{
				return CreativeInventorySnapshot.Unavailable("Local player character is not available.");
			}
			if (!((ZDOID)(ref expectedCharacterId)).IsNone() && zDO.m_uid != expectedCharacterId)
			{
				return CreativeInventorySnapshot.Unavailable("Local player character did not match the requested character.");
			}
			Inventory inventory = ((Humanoid)localPlayer).GetInventory();
			if (inventory == null)
			{
				return CreativeInventorySnapshot.Unavailable("Local player inventory is not available.");
			}
			List<ItemData> list = (from item in inventory.GetAllItems()
				where item != null
				select item).ToList();
			ExtraSlotsReadResult extraSlotsReadResult = ReadExtraSlotsItems();
			if (!extraSlotsReadResult.Available && extraSlotsReadResult.ModLoaded)
			{
				return CreativeInventorySnapshot.Unavailable(extraSlotsReadResult.Error);
			}
			List<ItemData> list2 = new List<ItemData>();
			AddUnique(list2, list);
			AddUnique(list2, extraSlotsReadResult.Items);
			CreativeInventorySnapshot creativeInventorySnapshot = new CreativeInventorySnapshot
			{
				Available = true,
				Error = string.Empty,
				PlayerId = localPlayer.GetPlayerID(),
				PlayerName = localPlayer.GetPlayerName(),
				PlayerInventoryCount = list.Count,
				ExtraSlotsAvailable = extraSlotsReadResult.Available,
				ExtraSlotsLoaded = extraSlotsReadResult.ModLoaded,
				ExtraSlotsCount = extraSlotsReadResult.Items.Count,
				TotalUniqueCount = list2.Count
			};
			if (includeItems)
			{
				creativeInventorySnapshot.Items.AddRange(list.Select((ItemData item) => CreativeInventoryItem.FromItem("player", item)));
				creativeInventorySnapshot.Items.AddRange(extraSlotsReadResult.Items.Select((ItemData item) => CreativeInventoryItem.FromItem("extraSlots", item)));
			}
			return creativeInventorySnapshot;
		}

		private static ExtraSlotsReadResult ReadExtraSlotsItems()
		{
			Type type = (from assembly in AppDomain.CurrentDomain.GetAssemblies()
				select assembly.GetType("ExtraSlots.API", throwOnError: false)).FirstOrDefault((Type type2) => type2 != null);
			if (type == null)
			{
				return ExtraSlotsReadResult.NotLoaded();
			}
			MethodInfo method = type.GetMethod("GetAllExtraSlotsItems", BindingFlags.Static | BindingFlags.Public);
			if (method == null)
			{
				return ExtraSlotsReadResult.Failed("ExtraSlots API did not expose GetAllExtraSlotsItems.");
			}
			try
			{
				if (!(method.Invoke(null, Array.Empty<object>()) is IEnumerable enumerable))
				{
					return ExtraSlotsReadResult.Failed("ExtraSlots API returned no item list.");
				}
				List<ItemData> list = new List<ItemData>();
				foreach (object item in enumerable)
				{
					ItemData val = (ItemData)((item is ItemData) ? item : null);
					if (val != null)
					{
						list.Add(val);
					}
				}
				return ExtraSlotsReadResult.Loaded(list);
			}
			catch (Exception ex)
			{
				return ExtraSlotsReadResult.Failed("ExtraSlots API read failed: " + ex.Message);
			}
		}

		private static void AddUnique(List<ItemData> target, IEnumerable<ItemData> items)
		{
			foreach (ItemData item in items)
			{
				if (!target.Contains(item))
				{
					target.Add(item);
				}
			}
		}

		private static void SendUnavailable(long target, string requestId, ZDOID characterId, string error)
		{
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			SendResponse(target, requestId, characterId, CreativeInventorySnapshot.Unavailable(error));
		}

		private static void SendResponse(long target, string requestId, ZDOID characterId, CreativeInventorySnapshot snapshot)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Expected O, but got Unknown
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			ZPackage val = new ZPackage();
			val.Write(1);
			val.Write(requestId);
			val.Write(characterId);
			val.Write(snapshot.Available);
			val.Write(snapshot.Error);
			val.Write(snapshot.PlayerId);
			val.Write(snapshot.PlayerName);
			val.Write(snapshot.PlayerInventoryCount);
			val.Write(snapshot.ExtraSlotsLoaded);
			val.Write(snapshot.ExtraSlotsAvailable);
			val.Write(snapshot.ExtraSlotsCount);
			val.Write(snapshot.TotalUniqueCount);
			val.Write(snapshot.Items.Count);
			foreach (CreativeInventoryItem item in snapshot.Items)
			{
				val.Write(item.Source);
				val.Write(item.PrefabName);
				val.Write(item.SharedName);
				val.Write(item.Stack);
				val.Write(item.Quality);
				val.Write(item.Equipped);
				val.Write(item.GridX);
				val.Write(item.GridY);
			}
			ZRoutedRpc.instance.InvokeRoutedRPC(target, "DiscordTools_CreativeInventoryResponse", new object[1] { val });
		}
	}
	internal static class LinkCommandHandler
	{
		private static readonly Regex CodePattern = new Regex("^[A-Za-z0-9_-]{4,64}$", RegexOptions.Compiled);

		public static bool TryHandle(Chat chat)
		{
			string text = (((Object)(object)((Terminal)chat).m_input != (Object)null) ? ((TMP_InputField)((Terminal)chat).m_input).text : "");
			if (string.IsNullOrWhiteSpace(text))
			{
				return false;
			}
			string text2 = PraetorisClientPlugin.LinkCommand.Value.Trim();
			if (string.IsNullOrWhiteSpace(text2))
			{
				text2 = "!link";
			}
			string text3 = text.Trim();
			if (!text3.Equals(text2, StringComparison.OrdinalIgnoreCase) && !text3.StartsWith(text2 + " ", StringComparison.OrdinalIgnoreCase))
			{
				return false;
			}
			string text4 = ((text3.Length > text2.Length) ? text3.Substring(text2.Length).Trim() : "");
			if (!CodePattern.IsMatch(text4))
			{
				((Terminal)chat).AddString("Usage: " + text2 + " CODE");
				ClearInput(chat);
				return true;
			}
			if (!LinkRpc.TrySendRequest(text4, out string message))
			{
				((Terminal)chat).AddString(message);
				ClearInput(chat);
				return true;
			}
			((Terminal)chat).AddString("Sending Discord link code to the server.");
			ClearInput(chat);
			return true;
		}

		private static void ClearInput(Chat chat)
		{
			if (!((Object)(object)((Terminal)chat).m_input == (Object)null))
			{
				((TMP_InputField)((Terminal)chat).m_input).text = "";
				((Component)((Terminal)chat).m_input).gameObject.SetActive(false);
			}
		}
	}
	internal static class LinkRpc
	{
		public static bool TrySendRequest(string code, out string message)
		{
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			//IL_002d: Invalid comparison between Unknown and I4
			//IL_004b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0051: Expected O, but got Unknown
			message = "";
			if ((Object)(object)ZNet.instance == (Object)null || ZRoutedRpc.instance == null || ZNet.instance.IsServer() || (int)ZNet.GetConnectionStatus() != 2)
			{
				message = "You must be connected to a server before linking Discord.";
				return false;
			}
			string text = Guid.NewGuid().ToString("N");
			ZPackage val = new ZPackage();
			val.Write(text);
			val.Write(code);
			ZRoutedRpc.instance.InvokeRoutedRPC(ZRoutedRpc.instance.GetServerPeerID(), "DiscordTools_LinkRequest", new object[1] { val });
			return true;
		}

		public static void OnRequest(long sender, ZPackage pkg)
		{
			if ((Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer())
			{
				return;
			}
			string requestId = pkg.ReadString();
			string code = pkg.ReadString();
			ZNetPeer val = PlayerResolver.FindPeerBySender(sender);
			if (val == null)
			{
				SendResult(sender, requestId, success: false, "The server could not identify your Valheim connection.");
				return;
			}
			PraetorisClientPlugin instance = PraetorisClientPlugin.Instance;
			if ((Object)(object)instance == (Object)null)
			{
				SendResult(sender, requestId, success: false, "PraetorisClient is not ready on the server.");
				return;
			}
			LinkRequest link = new LinkRequest
			{
				Sender = sender,
				RequestId = requestId,
				Code = code,
				PlayerId = PlayerResolver.StablePlayerId(val),
				PlayerName = (val.m_playerName ?? ""),
				Endpoint = PlayerResolver.SafeEndPoint(val),
				PlatformDisplayName = PlayerResolver.PlatformDisplayName(val),
				ReceivedAtUtc = DateTime.UtcNow
			};
			PraetorisClientPlugin.Log.LogInfo((object)("Received Discord link code from " + PlayerResolver.DescribePeer(val) + "."));
			((MonoBehaviour)instance).StartCoroutine(BotApiClient.PostLinkRoutine(link, SendResult));
		}

		public static void OnResult(long sender, ZPackage pkg)
		{
			if (!((Object)(object)ZNet.instance == (Object)null) && !ZNet.instance.IsServer())
			{
				string text = pkg.ReadString();
				bool flag = pkg.ReadBool();
				string text2 = pkg.ReadString();
				PraetorisClientPlugin.Log.LogInfo((object)("Discord link result " + text + ": " + text2));
				Chat instance = Chat.instance;
				if (instance != null)
				{
					((Terminal)instance).AddString(flag ? text2 : ("Discord link failed: " + text2));
				}
			}
		}

		private static void SendResult(long target, string requestId, bool success, string message)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Expected O, but got Unknown
			ZPackage val = new ZPackage();
			val.Write(requestId);
			val.Write(success);
			val.Write(message);
			ZRoutedRpc.instance.InvokeRoutedRPC(target, "DiscordTools_LinkResult", new object[1] { val });
		}
	}
	internal sealed class LinkRequest
	{
		public long Sender;

		public string RequestId = "";

		public string Code = "";

		public string PlayerId = "";

		public string PlayerName = "";

		public string Endpoint = "";

		public string PlatformDisplayName = "";

		public DateTime ReceivedAtUtc;
	}
	[HarmonyPatch(typeof(ZNet), "Awake")]
	internal static class ZNetAwakePatch
	{
		private static void Postfix()
		{
			CreativeCommandZoneState.Clear();
			PraetorisClientRpc.Register();
		}
	}
	[HarmonyPatch(typeof(Chat), "SendInput")]
	internal static class ChatSendInputPatch
	{
		private static bool Prefix(Chat __instance)
		{
			return !LinkCommandHandler.TryHandle(__instance);
		}
	}
	[HarmonyPatch(typeof(Character), "ApplyDamage")]
	internal static class CharacterApplyDamageTelemetryPatch
	{
		private static void Prefix(Character __instance, HitData hit, ref DamageObservationState __state)
		{
			__state = ValheimEventsTelemetry.CaptureDamageBefore(__instance, hit);
		}

		private static void Postfix(Character __instance, HitData hit, DamageModifier mod, DamageObservationState __state)
		{
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			ValheimEventsTelemetry.LogDamageApplied(__instance, hit, mod, __state);
		}
	}
	[HarmonyPatch(typeof(Player), "OnDeath")]
	internal static class PlayerDeathTelemetryPatch
	{
		private static void Prefix(Player __instance, ref DeathObservationState __state)
		{
			__state = ValheimEventsTelemetry.CaptureDeathBefore(__instance);
		}

		private static void Postfix(Player __instance, DeathObservationState __state)
		{
			ValheimEventsTelemetry.LogPlayerDied(__instance, __state);
		}
	}
	[HarmonyPatch(typeof(Minimap), "Explore", new Type[]
	{
		typeof(int),
		typeof(int)
	})]
	internal static class MinimapExploreTelemetryPatch
	{
		private static void Postfix(Minimap __instance, int x, int y, bool __result)
		{
			if (__result)
			{
				ValheimEventsTelemetry.RecordExploredCell(__instance, x, y);
			}
		}
	}
	[HarmonyPatch(typeof(Game), "Update")]
	internal static class GameUpdateTelemetryPatch
	{
		private static void Postfix()
		{
			ValheimEventsTelemetry.Update();
			RpcTraceTelemetry.Update();
		}
	}
	[HarmonyPatch(typeof(Game), "Logout")]
	internal static class GameLogoutRpcTracePatch
	{
		private static bool Prefix(Game __instance, bool save, bool changeToStartScene)
		{
			return RpcTraceTelemetry.ShouldAllowLogout(__instance, save, changeToStartScene);
		}
	}
	[HarmonyPatch(typeof(Game), "OnApplicationQuit")]
	internal static class GameApplicationQuitRpcTracePatch
	{
		private static void Prefix()
		{
			RpcTraceTelemetry.OnApplicationQuitFallback();
		}
	}
	[HarmonyPatch(typeof(Menu), "QuitGame")]
	internal static class MenuQuitGameRpcTracePatch
	{
		private static bool Prefix()
		{
			return RpcTraceTelemetry.ShouldAllowMenuQuit();
		}
	}
	[HarmonyPatch(typeof(Menu), "OnQuitYes")]
	internal static class MenuQuitYesRpcTracePatch
	{
		private static bool Prefix()
		{
			return RpcTraceTelemetry.ShouldAllowMenuQuit();
		}
	}
	[HarmonyPatch(typeof(ConsoleCommand), "RunAction")]
	internal static class ConsoleCommandRunActionCreativeZoneGuardPatch
	{
		private static bool Prefix(ConsoleEventArgs args)
		{
			return CreativeCommandZoneState.CanRunCommand(args);
		}
	}
	internal static class PlayerResolver
	{
		public static List<ZNetPeer> FindPeers(string query)
		{
			List<ZNetPeer> list = new List<ZNetPeer>();
			if ((Object)(object)ZNet.instance == (Object)null)
			{
				return list;
			}
			string text = Normalize(query);
			foreach (ZNetPeer connectedPeer in ZNet.instance.GetConnectedPeers())
			{
				if (connectedPeer.IsReady())
				{
					string value = SafeHostName(connectedPeer);
					string value2 = StablePlayerId(connectedPeer);
					if (Normalize(connectedPeer.m_playerName) == text || Normalize(value) == text || Normalize(value2) == text || (DigitsOnly(value) == DigitsOnly(query) && DigitsOnly(query).Length > 0))
					{
						list.Add(connectedPeer);
					}
				}
			}
			if (list.Count > 0)
			{
				return list;
			}
			foreach (ZNetPeer connectedPeer2 in ZNet.instance.GetConnectedPeers())
			{
				if (connectedPeer2.IsReady() && Normalize(connectedPeer2.m_playerName).Contains(text))
				{
					list.Add(connectedPeer2);
				}
			}
			return list;
		}

		public static ZNetPeer? FindPeerBySender(long sender)
		{
			if ((Object)(object)ZNet.instance == (Object)null)
			{
				return null;
			}
			return ((IEnumerable<ZNetPeer>)ZNet.instance.GetConnectedPeers()).FirstOrDefault((Func<ZNetPeer, bool>)((ZNetPeer peer) => peer.m_uid == sender));
		}

		public static string DescribePeer(ZNetPeer peer)
		{
			return peer.m_playerName + " (" + StablePlayerId(peer) + ")";
		}

		public static string StablePlayerId(ZNetPeer peer)
		{
			string text = SafeHostName(peer);
			if (!string.IsNullOrWhiteSpace(text))
			{
				return text;
			}
			return peer.m_uid.ToString(CultureInfo.InvariantCulture);
		}

		public static string SafeHostName(ZNetPeer peer)
		{
			try
			{
				ISocket socket = peer.m_socket;
				return ((socket != null) ? socket.GetHostName() : null) ?? "";
			}
			catch
			{
				return "";
			}
		}

		public static string SafeEndPoint(ZNetPeer peer)
		{
			try
			{
				ISocket socket = peer.m_socket;
				return ((socket != null) ? socket.GetEndPointString() : null) ?? "";
			}
			catch
			{
				return "";
			}
		}

		public static string PlatformDisplayName(ZNetPeer peer)
		{
			try
			{
				string value;
				return (peer.m_serverSyncedPlayerData != null && peer.m_serverSyncedPlayerData.TryGetValue("platformDisplayName", out value)) ? value : "";
			}
			catch
			{
				return "";
			}
		}

		private static string Normalize(string value)
		{
			return (value ?? "").Trim().ToLowerInvariant();
		}

		private static string DigitsOnly(string value)
		{
			return new string((value ?? "").Where(char.IsDigit).ToArray());
		}
	}
	[BepInPlugin("warpalicious.PraetorisClient", "PraetorisClient", "0.1.8")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	public class PraetorisClientPlugin : BaseUnityPlugin
	{
		private const string ModName = "PraetorisClient";

		private const string ModVersion = "0.1.8";

		private const string Author = "warpalicious";

		private const string ModGUID = "warpalicious.PraetorisClient";

		private const string LinkApiUrlEnv = "PRAETORISCLIENT_LINK_API_URL";

		private const string BotApiKeyEnv = "PRAETORISCLIENT_BOT_API_KEY";

		private readonly Harmony _harmony = new Harmony("warpalicious.PraetorisClient");

		private DateTime _lastReloadTime;

		private FileSystemWatcher? _configWatcher;

		private const long ReloadDelayTicks = 10000000L;

		public static readonly ManualLogSource Log = Logger.CreateLogSource("PraetorisClient");

		internal static ConfigEntry<string> LinkApiUrl = null;

		internal static ConfigEntry<string> BotApiKey = null;

		internal static ConfigEntry<string> LinkCommand = null;

		internal static ConfigEntry<bool> ValheimEventsTelemetryEnabled = null;

		internal static ConfigEntry<bool> CombatTelemetryEnabled = null;

		internal static ConfigEntry<bool> ExplorationTelemetryEnabled = null;

		internal static ConfigEntry<float> ExplorationFlushSeconds = null;

		internal static ConfigEntry<bool> RpcTraceEnabled = null;

		internal static ConfigEntry<bool> RpcTraceCaptureSendReceive = null;

		internal static ConfigEntry<string> RpcTraceNameDenyList = null;

		internal static ConfigEntry<bool> RpcTraceHttpUploadPreferred = null;

		public static PraetorisClientPlugin? Instance { get; private set; }

		internal static string GetLinkApiUrl()
		{
			string environmentVariable = Environment.GetEnvironmentVariable("PRAETORISCLIENT_LINK_API_URL");
			if (!string.IsNullOrWhiteSpace(environmentVariable))
			{
				return environmentVariable.Trim();
			}
			return LinkApiUrl.Value;
		}

		internal static string GetBotApiKey()
		{
			string environmentVariable = Environment.GetEnvironmentVariable("PRAETORISCLIENT_BOT_API_KEY");
			if (!string.IsNullOrWhiteSpace(environmentVariable))
			{
				return environmentVariable.Trim();
			}
			return BotApiKey.Value;
		}

		public void Awake()
		{
			Instance = this;
			BindConfig();
			SynchronizationManager.OnConfigurationSynchronized += OnConfigurationSynchronized;
			SiegePortalTestCommand.Register();
			RpcTraceTelemetry.Initialize();
			_harmony.PatchAll(Assembly.GetExecutingAssembly());
			SetupWatcher();
		}

		private void OnDestroy()
		{
			SynchronizationManager.OnConfigurationSynchronized -= OnConfigurationSynchronized;
			try
			{
				_configWatcher?.Dispose();
				_configWatcher = null;
			}
			catch (Exception ex)
			{
				Log.LogWarning((object)("Failed to dispose configuration watcher: " + ex.Message));
			}
			try
			{
				RpcTraceTelemetry.Shutdown();
			}
			catch (Exception ex2)
			{
				Log.LogWarning((object)("Failed to shut down RPC trace telemetry: " + ex2.Message));
			}
			try
			{
				((BaseUnityPlugin)this).Config.Save();
			}
			catch (Exception ex3)
			{
				Log.LogWarning((object)("Failed to save configuration during shutdown: " + ex3.Message));
			}
			try
			{
				_harmony.UnpatchSelf();
			}
			catch (Exception ex4)
			{
				Log.LogWarning((object)("Failed to unpatch PraetorisClient during shutdown: " + ex4.Message));
			}
			if ((Object)(object)Instance == (Object)(object)this)
			{
				Instance = null;
			}
		}

		private void BindConfig()
		{
			LinkApiUrl = ((BaseUnityPlugin)this).Config.Bind<string>("BotApi", "LinkApiUrl", "", "Compatible bot Valheim link endpoint. Prefer the PRAETORISCLIENT_LINK_API_URL environment variable on dedicated servers.");
			BotApiKey = ((BaseUnityPlugin)this).Config.Bind<string>("BotApi", "ApiKey", "", "API key sent to the bot in the X-API-Key header. Prefer the PRAETORISCLIENT_BOT_API_KEY environment variable on dedicated servers.");
			LinkCommand = ((BaseUnityPlugin)this).Config.Bind<string>("Linking", "LinkCommand", "!link", "In-game chat command consumed before it is sent as chat.");
			ValheimEventsTelemetryEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("ValheimEvents", "Enabled", true, SyncedDescription("Sends client-observed telemetry to the server-side ValheimEvents mod."));
			CombatTelemetryEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("ValheimEvents", "CombatTelemetry", true, SyncedDescription("Sends client-observed combat and death telemetry."));
			ExplorationTelemetryEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("ValheimEvents", "ExplorationTelemetry", true, SyncedDescription("Sends client-observed minimap exploration telemetry."));
			ExplorationFlushSeconds = ((BaseUnityPlugin)this).Config.Bind<float>("ValheimEvents", "ExplorationFlushSeconds", 2f, SyncedDescription("How long newly explored minimap cells are batched before sending."));
			RpcTraceEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("RpcTrace", "Enabled", true, SyncedDescription("Sends client-observed routed RPC trace rows to the server-side ValheimTracer receiver."));
			RpcTraceCaptureSendReceive = ((BaseUnityPlugin)this).Config.Bind<bool>("RpcTrace", "CaptureSendReceive", true, SyncedDescription("Captures raw routed RPC send and receive points in addition to handled RPC points."));
			RpcTraceNameDenyList = ((BaseUnityPlugin)this).Config.Bind<string>("RpcTrace", "RpcNameDenyList", "", SyncedDescription("Comma-separated routed RPC names to exclude from client trace capture."));
			RpcTraceHttpUploadPreferred = ((BaseUnityPlugin)this).Config.Bind<bool>("RpcTrace", "HttpUploadPreferred", true, SyncedDescription("Uses ValheimTracer-issued HTTP upload tokens for trace batches when the server supports it."));
		}

		private static ConfigDescription SyncedDescription(string description)
		{
			//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)
			//IL_000d: Expected O, but got Unknown
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			//IL_001f: Expected O, but got Unknown
			ConfigurationManagerAttributes val = new ConfigurationManagerAttributes
			{
				IsAdminOnly = true
			};
			return new ConfigDescription(description, (AcceptableValueBase)null, new object[1] { val });
		}

		private static void OnConfigurationSynchronized(object sender, ConfigurationSynchronizationEventArgs args)
		{
			if (args.UpdatedPluginGUIDs != null && args.UpdatedPluginGUIDs.Contains("warpalicious.PraetorisClient"))
			{
				string text = (args.InitialSynchronization ? "initial" : "updated");
				Log.LogInfo((object)("Jotunn synchronized PraetorisClient configuration (" + text + ")."));
			}
		}

		private void SetupWatcher()
		{
			try
			{
				_lastReloadTime = DateTime.Now;
				_configWatcher?.Dispose();
				_configWatcher = new FileSystemWatcher(Paths.ConfigPath, "warpalicious.PraetorisClient.cfg");
				_configWatcher.Changed += ReadConfigValues;
				_configWatcher.Created += ReadConfigValues;
				_configWatcher.Renamed += ReadConfigValues;
				_configWatcher.IncludeSubdirectories = true;
				_configWatcher.EnableRaisingEvents = true;
			}
			catch (Exception ex)
			{
				Log.LogWarning((object)("Failed to start configuration watcher: " + ex.Message));
			}
		}

		private void ReadConfigValues(object sender, FileSystemEventArgs e)
		{
			DateTime now = DateTime.Now;
			long num = now.Ticks - _lastReloadTime.Ticks;
			if (File.Exists(Path.Combine(Paths.ConfigPath, "warpalicious.PraetorisClient.cfg")) && num >= 10000000)
			{
				try
				{
					Log.LogInfo((object)"Reloading configuration.");
					((BaseUnityPlugin)this).Config.Reload();
				}
				catch (Exception ex)
				{
					Log.LogError((object)("Failed to reload configuration: " + ex.Message));
				}
				_lastReloadTime = now;
			}
		}
	}
	internal static class PraetorisClientRpc
	{
		private static ZRoutedRpc? _registeredRpc;

		public static void Register()
		{
			if (ZRoutedRpc.instance != null && _registeredRpc != ZRoutedRpc.instance)
			{
				_registeredRpc = ZRoutedRpc.instance;
				ZRoutedRpc.instance.Register<ZPackage>("DiscordTools_LinkRequest", (Action<long, ZPackage>)LinkRpc.OnRequest);
				ZRoutedRpc.instance.Register<ZPackage>("DiscordTools_LinkResult", (Action<long, ZPackage>)LinkRpc.OnResult);
				ZRoutedRpc.instance.Register<ZPackage>("DiscordTools_CreativeInventoryRequest", (Action<long, ZPackage>)CreativeInventoryRpc.OnRequest);
				ZRoutedRpc.instance.Register<ZPackage>("DiscordTools_CreativeBiomeOverride", (Action<long, ZPackage>)CreativeBiomeOverride.OnOverride);
				ZRoutedRpc.instance.Register<ZPackage>("PraetorisClient_CreativeCommandZoneState", (Action<long, ZPackage>)CreativeCommandZoneState.OnState);
				ZRoutedRpc.instance.Register<ZPackage>("PraetorisClient_RpcTraceClockResponse", (Action<long, ZPackage>)RpcTraceTelemetry.OnClockResponse);
				ZRoutedRpc.instance.Register<ZPackage>("PraetorisClient_RpcTraceUploadTokenResponse", (Action<long, ZPackage>)RpcTraceUploadTokenClient.OnTokenResponse);
				PraetorisClientPlugin.Log.LogInfo((object)"Registered PraetorisClient RPC handlers.");
			}
		}
	}
	internal static class RpcNames
	{
		public const string LinkRequest = "DiscordTools_LinkRequest";

		public const string LinkResult = "DiscordTools_LinkResult";

		public const string CreativeInventoryRequest = "DiscordTools_CreativeInventoryRequest";

		public const string CreativeInventoryResponse = "DiscordTools_CreativeInventoryResponse";

		public const string CreativeBiomeOverride = "DiscordTools_CreativeBiomeOverride";

		public const string CreativeCommandZoneState = "PraetorisClient_CreativeCommandZoneState";

		public const string SiegePortalEnter = "DiscordTools_SiegePortalEnter";

		public const string ValheimEventsTelemetry = "PraetorisClient_ValheimEventsTelemetry";

		public const string RpcTraceClockRequest = "PraetorisClient_RpcTraceClockRequest";

		public const string RpcTraceClockResponse = "PraetorisClient_RpcTraceClockResponse";

		public const string RpcTraceUploadTokenRequest = "PraetorisClient_RpcTraceUploadTokenRequest";

		public const string RpcTraceUploadTokenResponse = "PraetorisClient_RpcTraceUploadTokenResponse";
	}
	internal static class RpcTraceFlushCoordinator
	{
		private const float UploadUnavailableLogIntervalSeconds = 30f;

		private static bool _allowQuit;

		private static float _nextUploadUnavailableLogTime;

		internal static void Initialize()
		{
			Application.wantsToQuit -= OnWantsToQuit;
			Application.wantsToQuit += OnWantsToQuit;
			_allowQuit = false;
			_nextUploadUnavailableLogTime = 0f;
		}

		internal static void Shutdown()
		{
			Application.wantsToQuit -= OnWantsToQuit;
		}

		internal static void RequestFlush(string reason)
		{
			if (RpcTraceTelemetry.IsTracingEnabled())
			{
				if (RpcTraceHttpUploadCoordinator.IsActive())
				{
					RpcTraceHttpUploadCoordinator.RequestFlush(reason);
				}
				else
				{
					LogHttpUnavailable(reason);
				}
			}
		}

		internal static void Update()
		{
			if (RpcTraceTelemetry.IsTracingEnabled() && !RpcTraceHttpUploadCoordinator.IsActive() && RpcTraceLocalStore.HasPendingFiles())
			{
				LogHttpUnavailable("background");
			}
		}

		internal static bool ShouldAllowLogout(Game game, bool save, bool changeToStartScene)
		{
			if (!RpcTraceTelemetry.IsTracingEnabled())
			{
				return true;
			}
			if (RpcTraceHttpUploadCoordinator.IsActive())
			{
				RpcTraceHttpUploadCoordinator.RequestFlush("logout");
			}
			else if (RpcTraceLocalStore.HasPendingFiles())
			{
				LogHttpUnavailable("logout");
			}
			RpcTraceTelemetry.SuppressCaptureUntilDisconnected();
			return true;
		}

		internal static bool ShouldAllowMenuQuit()
		{
			if (!RpcTraceTelemetry.IsTracingEnabled())
			{
				return true;
			}
			if (RpcTraceHttpUploadCoordinator.IsActive())
			{
				RpcTraceHttpUploadCoordinator.RequestFlush("quit");
			}
			else if (RpcTraceLocalStore.HasPendingFiles())
			{
				LogHttpUnavailable("quit");
			}
			RpcTraceTelemetry.DisableCaptureForShutdown();
			return true;
		}

		private static bool OnWantsToQuit()
		{
			if (_allowQuit)
			{
				return true;
			}
			if (!RpcTraceTelemetry.IsTracingEnabled())
			{
				return true;
			}
			if (RpcTraceHttpUploadCoordinator.IsActive())
			{
				RpcTraceHttpUploadCoordinator.RequestFlush("quit");
			}
			else if (RpcTraceLocalStore.HasPendingFiles())
			{
				LogHttpUnavailable("quit");
			}
			RpcTraceTelemetry.DisableCaptureForShutdown();
			_allowQuit = true;
			return true;
		}

		private static void LogHttpUnavailable(string reason)
		{
			if (!(Time.realtimeSinceStartup < _nextUploadUnavailableLogTime))
			{
				_nextUploadUnavailableLogTime = Time.realtimeSinceStartup + 30f;
				PraetorisClientPlugin.Log.LogWarning((object)("RPC trace HTTP upload is unavailable during " + (string.IsNullOrWhiteSpace(reason) ? "flush" : reason) + "; keeping local trace files for later HTTP retry."));
			}
		}
	}
	internal static class RpcTraceHttpUploadCoordinator
	{
		private const int MaxHttpRowsPerBatch = 5000;

		private const float FailureRetrySeconds = 15f;

		private static readonly object Sync = new object();

		private static readonly Queue<string> PendingFiles = new Queue<string>();

		private static bool _flushRequested;

		private static string _flushReason = "background";

		private static bool _uploading;

		private static float _nextUploadTime;

		internal static void Initialize()
		{
			lock (Sync)
			{
				PendingFiles.Clear();
				_flushRequested = false;
				_flushReason = "background";
				_uploading = false;
				_nextUploadTime = 0f;
			}
		}

		internal static void Shutdown()
		{
			lock (Sync)
			{
				PendingFiles.Clear();
				_uploading = false;
				_flushRequested = false;
			}
		}

		internal static bool IsActive()
		{
			if (PraetorisClientPlugin.RpcTraceHttpUploadPreferred.Value && RpcTraceUploadTokenClient.HasUsableToken())
			{
				return CanUpload();
			}
			return false;
		}

		internal static void RequestFlush(string reason)
		{
			lock (Sync)
			{
				_flushRequested = true;
				_flushReason = (string.IsNullOrWhiteSpace(reason) ? "manual" : reason);
				_nextUploadTime = 0f;
			}
		}

		internal static void Update()
		{
			if (!IsActive())
			{
				return;
			}
			lock (Sync)
			{
				if (_uploading || (!_flushRequested && PendingFiles.Count == 0 && Time.realtimeSinceStartup < _nextUploadTime))
				{
					return;
				}
			}
			PrepareFilesIfNeeded();
			StartNextUploadIfReady();
		}

		private static void PrepareFilesIfNeeded()
		{
			lock (Sync)
			{
				if (PendingFiles.Count > 0 || _uploading)
				{
					return;
				}
				foreach (string flushableFile in RpcTraceLocalStore.GetFlushableFiles())
				{
					if (new FileInfo(flushableFile).Length == 0L)
					{
						RpcTraceLocalStore.DeleteFile(flushableFile);
					}
					else
					{
						PendingFiles.Enqueue(flushableFile);
					}
				}
				_flushRequested = false;
				_nextUploadTime = Time.realtimeSinceStartup + RpcTraceUploadTokenClient.FlushIntervalSeconds;
			}
		}

		private static void StartNextUploadIfReady()
		{
			string path;
			string flushReason;
			lock (Sync)
			{
				if (_uploading || PendingFiles.Count == 0)
				{
					return;
				}
				path = PendingFiles.Dequeue();
				flushReason = _flushReason;
				_uploading = true;
			}
			if ((Object)(object)PraetorisClientPlugin.Instance != (Object)null)
			{
				((MonoBehaviour)PraetorisClientPlugin.Instance).StartCoroutine(UploadFile(path, flushReason));
			}
			else
			{
				RequeueUpload(path);
			}
		}

		private static IEnumerator UploadFile(string path, string flushReason)
		{
			string fileId = RpcTraceLocalStore.BuildFileId(path);
			int startLine = 0;
			int batchIndex = 0;
			while (IsActive() && File.Exists(path))
			{
				bool reachedEnd;
				List<string> rows = RpcTraceLocalStore.ReadBatch(path, startLine, 5000, out reachedEnd);
				if (rows.Count == 0 && reachedEnd)
				{
					RpcTraceLocalStore.DeleteFile(path);
					CompleteUpload(success: true);
					yield break;
				}
				bool finalBatch;
				byte[] array = BuildBodyWithinLimit(rows, reachedEnd, out finalBatch);
				if (array.Length == 0 || rows.Count == 0)
				{
					PraetorisClientPlugin.Log.LogWarning((object)("Skipping oversized HTTP RPC trace row in " + fileId + "."));
					startLine++;
					continue;
				}
				string batchId = fileId + "-" + batchIndex.ToString("D6");
				UnityWebRequest request = new UnityWebRequest(RpcTraceUploadTokenClient.EndpointUrl, "POST")
				{
					uploadHandler = (UploadHandler)new UploadHandlerRaw(array),
					downloadHandler = (DownloadHandler)new DownloadHandlerBuffer(),
					timeout = 30
				};
				try
				{
					request.SetRequestHeader("Authorization", "Bearer " + RpcTraceUploadTokenClient.Token);
					request.SetRequestHeader("Content-Type", "application/x-ndjson");
					request.SetRequestHeader("Content-Encoding", "gzip");
					request.SetRequestHeader("X-Trace-Batch-Id", batchId);
					request.SetRequestHeader("X-Trace-File-Id", fileId);
					request.SetRequestHeader("X-Trace-Batch-Index", batchIndex.ToString());
					request.SetRequestHeader("X-Trace-Final-Batch", finalBatch ? "true" : "false");
					request.SetRequestHeader("X-Trace-Flush-Reason", flushReason ?? "");
					request.SetRequestHeader("User-Agent", "PraetorisClient/0.1.8 ValheimTracerHttpUpload");
					yield return request.SendWebRequest();
					if ((int)request.result != 1 || request.responseCode < 200 || request.responseCode >= 300)
					{
						string text = ((request.downloadHandler != null) ? request.downloadHandler.text : "");
						string text2 = (string.IsNullOrWhiteSpace(text) ? request.error : text);
						if (!RpcTraceUploadTokenClient.ShouldRetryUpload(request.responseCode, text2))
						{
							CompleteUpload(success: false);
							yield break;
						}
						PraetorisClientPlugin.Log.LogWarning((object)$"HTTP RPC trace upload failed for {batchId}: HTTP {request.responseCode} {text2}");
						RequeueUpload(path);
						yield break;
					}
					startLine += rows.Count;
					batchIndex++;
					if (finalBatch)
					{
						PraetorisClientPlugin.Log.LogInfo((object)("Uploaded RPC trace file " + fileId + " over HTTP; deleting local copy."));
						RpcTraceLocalStore.DeleteFile(path);
						CompleteUpload(success: true);
						yield break;
					}
				}
				finally
				{
					((IDisposable)request)?.Dispose();
				}
			}
			RequeueUpload(path);
		}

		private static byte[] BuildBodyWithinLimit(List<string> rows, bool reachedEnd, out bool finalBatch)
		{
			finalBatch = reachedEnd;
			int num = Math.Max(4096, RpcTraceUploadTokenClient.MaxBatchBytes);
			while (rows.Count > 0)
			{
				byte[] array = GzipRows(rows);
				if (array.Length <= num)
				{
					return array;
				}
				if (rows.Count == 1)
				{
					rows.Clear();
					finalBatch = false;
					return Array.Empty<byte>();
				}
				rows.RemoveAt(rows.Count - 1);
				finalBatch = false;
			}
			return Array.Empty<byte>();
		}

		private static byte[] GzipRows(IReadOnlyList<string> rows)
		{
			using MemoryStream memoryStream = new MemoryStream();
			using (GZipStream stream = new GZipStream(memoryStream, CompressionLevel.Fastest, leaveOpen: true))
			{
				using StreamWriter streamWriter = new StreamWriter(stream, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false));
				foreach (string row in rows)
				{
					streamWriter.WriteLine(row);
				}
			}
			return memoryStream.ToArray();
		}

		private static void RequeueUpload(string path)
		{
			lock (Sync)
			{
				if (File.Exists(path))
				{
					PendingFiles.Enqueue(path);
				}
				_uploading = false;
				_nextUploadTime = Time.realtimeSinceStartup + 15f;
			}
		}

		private static void CompleteUpload(bool success)
		{
			lock (Sync)
			{
				_uploading = false;
				_nextUploadTime = Time.realtimeSinceStartup + (success ? RpcTraceUploadTokenClient.FlushIntervalSeconds : 15f);
			}
		}

		private static bool CanUpload()
		{
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			//IL_0026: Invalid comparison between Unknown and I4
			if ((Object)(object)ZNet.instance != (Object)null && ZRoutedRpc.instance != null && !ZNet.instance.IsServer())
			{
				return (int)ZNet.GetConnectionStatus() == 2;
			}
			return false;
		}
	}
	internal static class RpcTraceLocalStore
	{
		private static readonly object Sync = new object();

		private static string _pendingDirectory = "";

		private static string? _currentPath;

		private static StreamWriter? _writer;

		internal static void Initialize()
		{
			_pendingDirectory = Path.Combine(Paths.BepInExRootPath, "logs", "PraetorisClient", "RpcTrace", "pending");
			Directory.CreateDirectory(_pendingDirectory);
		}

		internal static void Append(string line, long localPeerId)
		{
			if (string.IsNullOrWhiteSpace(line))
			{
				return;
			}
			lock (Sync)
			{
				EnsureWriterLocked(localPeerId);
				if (_writer != null)
				{
					_writer.WriteLine(line);
					_writer.Flush();
				}
			}
		}

		internal static void CloseCurrentFile()
		{
			lock (Sync)
			{
				_writer?.Flush();
				_writer?.Dispose();
				_writer = null;
				_currentPath = null;
			}
		}

		internal static List<string> GetFlushableFiles()
		{
			lock (Sync)
			{
				CloseCurrentFile();
				List<string> list = new List<string>(Directory.GetFiles(_pendingDirectory, "*.jsonl"));
				list.Sort(StringComparer.Ordinal);
				return list;
			}
		}

		internal static List<string> ReadBatch(string path, int startLine, int maxRows, out bool reachedEnd)
		{
			List<string> list = new List<string>(Math.Max(1, maxRows));
			reachedEnd = true;
			using StreamReader streamReader = new StreamReader(path, Encoding.UTF8);
			int num = 0;
			while (true)
			{
				string text = streamReader.ReadLine();
				if (text == null)
				{
					break;
				}
				if (num++ >= startLine)
				{
					if (list.Count >= maxRows)
					{
						reachedEnd = false;
						break;
					}
					if (text.Length > 0)
					{
						list.Add(text);
					}
				}
			}
			return list;
		}

		internal static void DeleteFile(string path)
		{
			try
			{
				if (File.Exists(path))
				{
					File.Delete(path);
				}
			}
			catch (Exception ex)
			{
				PraetorisClientPlugin.Log.LogWarning((object)("Failed to delete uploaded RPC trace file " + path + ": " + ex.Message));
			}
		}

		internal static bool HasPendingFiles()
		{
			lock (Sync)
			{
				if (_writer != null)
				{
					return true;
				}
				return Directory.Exists(_pendingDirectory) && Directory.GetFiles(_pendingDirectory, "*.jsonl").Length != 0;
			}
		}

		internal static string BuildFileId(string path)
		{
			string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(path);
			if (!string.IsNullOrEmpty(fileNameWithoutExtension))
			{
				return fileNameWithoutExtension;
			}
			return Guid.NewGuid().ToString("N");
		}

		private static void EnsureWriterLocked(long localPeerId)
		{
			if (_writer == null)
			{
				Directory.CreateDirectory(_pendingDirectory);
				long num = ((ZNet.m_world != null) ? ZNet.m_world.m_uid : 0);
				string text = DateTime.UtcNow.ToString("yyyyMMdd_HHmmss_fff", CultureInfo.InvariantCulture);
				string path = "rpc_trace_" + text + "_world_" + num.ToString(CultureInfo.InvariantCulture) + "_peer_" + localPeerId.ToString(CultureInfo.InvariantCulture) + "_" + Guid.NewGuid().ToString("N") + ".jsonl";
				_currentPath = Path.Combine(_pendingDirectory, path);
				_writer = new StreamWriter(_currentPath, append: true, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false))
				{
					AutoFlush = true
				};
			}
		}
	}
	[HarmonyPatch(typeof(ZRoutedRpc), "InvokeRoutedRPC", new Type[]
	{
		typeof(long),
		typeof(ZDOID),
		typeof(string),
		typeof(object[])
	})]
	internal static class RpcTraceInvokeNamePatch
	{
		private static void Prefix(string methodName)
		{
			RpcTraceTelemetry.RegisterRpcName(methodName);
		}
	}
	[HarmonyPatch(typeof(ZRpc), "Invoke")]
	internal static class RpcTraceSendPatch
	{
		private static void Prefix(ZRpc __instance, string method, object[] parameters)
		{
			if (!(method != "RoutedRPC") && parameters != null && parameters.Length != 0)
			{
				object obj = parameters[0];
				ZPackage val = (ZPackage)((obj is ZPackage) ? obj : null);
				if (val != null)
				{
					RoutedRPCData data = RpcTraceTelemetry.TryReadRoutedRpcData(val);
					RpcTraceTelemetry.TraceRoutedRpc("rpc_send", data, RpcTraceTelemetry.GetPeerIdForRpc(__instance));
				}
			}
		}
	}
	[HarmonyPatch(typeof(ZRoutedRpc), "RPC_RoutedRPC")]
	internal static class RpcTraceReceivePatch
	{
		private static void Prefix(ZPackage pkg)
		{
			RoutedRPCData data = RpcTraceTelemetry.TryReadRoutedRpcData(pkg);
			RpcTraceTelemetry.TraceRoutedRpc("rpc_receive", data, (ZDOMan.instance != null) ? ZDOMan.GetSessionID() : 0);
		}
	}
	[HarmonyPatch(typeof(ZRoutedRpc), "HandleRoutedRPC")]
	internal static class RpcTraceHandlePatch
	{
		private static void Prefix(RoutedRPCData data)
		{
			RpcTraceTelemetry.TraceRoutedRpc("rpc_handle", data, (ZDOMan.instance != null) ? ZDOMan.GetSessionID() : 0);
		}
	}
	internal static class RpcTraceTelemetry
	{
		private readonly struct ClockSample
		{
			internal double OffsetMs { get; }

			internal double RoundTripMs { get; }

			internal double Realtime { get; }

			internal ClockSample(double offsetMs, double roundTripMs, double realtime)
			{
				OffsetMs = offsetMs;
				RoundTripMs = roundTripMs;
				Realtime = realtime;
			}
		}

		internal const int ProtocolVersion = 2;

		internal const string Schema = "valheim.trace.rpc.v1";

		private const float ClockSyncIntervalSeconds = 10f;

		private const int ClockSampleWindow = 9;

		private const double MaxClockRoundTripMs = 2000.0;

		private const double MaxClockOffsetJumpMs = 250.0;

		private static readonly object Sync = new object();

		private static readonly Dictionary<int, string> RpcNamesByHash = new Dictionary<int, string>();

		private static readonly List<ClockSample> ClockSamples = new List<ClockSample>();

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

		private static string _lastDenyListConfig = "";

		private static float _nextClockSyncTime;

		private static long _sequence;

		private static int _clockSequence;

		private static double _lastServerMinusClientOffsetMs;

		private static double _selectedClockRoundTripMs;

		private static double _lastGoodClockSyncRealtime;

		private static int _validClockSampleCount;

		private static bool _hasClockOffset;

		private static bool _shutdownCapture;

		private static bool _suppressCaptureUntilDisconnected;

		internal static void Initialize()
		{
			_shutdownCapture = false;
			_suppressCaptureUntilDisconnected = false;
			RpcTraceLocalStore.Initialize();
			RpcTraceUploadTokenClient.Initialize();
			RpcTraceHttpUploadCoordinator.Initialize();
			RpcTraceFlushCoordinator.Initialize();
		}

		internal static void Shutdown()
		{
			_shutdownCapture = true;
			RpcTraceHttpUploadCoordinator.Shutdown();
			RpcTraceFlushCoordinator.Shutdown();
			RpcTraceLocalStore.CloseCurrentFile();
		}

		internal static void DisableCaptureForShutdown()
		{
			_shutdownCapture = true;
			RpcTraceLocalStore.CloseCurrentFile();
		}

		internal static void SuppressCaptureUntilDisconnected()
		{
			_suppressCaptureUntilDisconnected = true;
			RpcTraceLocalStore.CloseCurrentFile();
		}

		internal static void RegisterRpcName(string methodName)
		{
			if (string.IsNullOrEmpty(methodName))
			{
				return;
			}
			lock (Sync)
			{
				RpcNamesByHash[StringExtensionMethods.GetStableHashCode(methodName)] = methodName;
			}
		}

		internal static void TraceRoutedRpc(string eventType, RoutedRPCData? data, long receiverPeerId)
		{
			//IL_00e8: Unknown result type (might be due to invalid IL or missing references)
			if (!CanCapture() || data == null || IsTraceRpc(data) || (!PraetorisClientPlugin.RpcTraceCaptureSendReceive.Value && eventType != "rpc_handle"))
			{
				return;
			}
			try
			{
				RefreshDenyList();
				string rpcName = GetRpcName(data.m_methodHash);
				if (!IsDenied(rpcName))
				{
					long localPeerId = GetLocalPeerId();
					TelemetryJson telemetryJson = ObjectWithEnvelope(eventType, localPeerId);
					telemetryJson.Prop("role", "client");
					telemetryJson.Prop("rpcTraceId", BuildRpcTraceId(data.m_senderPeerID, data.m_msgID));
					telemetryJson.Prop("msgId", data.m_msgID);
					telemetryJson.Prop("senderPeerId", data.m_senderPeerID);
					telemetryJson.Prop("receiverPeerId", receiverPeerId);
					telemetryJson.Prop("targetPeerId", data.m_targetPeerID);
					telemetryJson.Prop("methodHash", data.m_methodHash);
					telemetryJson.Prop("rpcName", rpcName);
					telemetryJson.Prop("targetZdo", ClientZdoSnapshot.FormatId(data.m_targetZDO));
					telemetryJson.Prop("payloadBytes", (data.m_parameters != null) ? data.m_parameters.Size() : 0);
					telemetryJson.Prop("serverMinusClientOffsetMs", _lastServerMinusClientOffsetMs);
					telemetryJson.Prop("clockRoundTripMs", _selectedClockRoundTripMs);
					telemetryJson.Prop("clockOffsetQuality", GetClockOffsetQuality());
					telemetryJson.Prop("clockOffsetAgeMs", GetClockOffsetAgeMs());
					telemetryJson.Prop("clockSampleCount", _validClockSampleCount);
					telemetryJson.End();
					RpcTraceLocalStore.Append(telemetryJson.ToString(), localPeerId);
				}
			}
			catch (Exception ex)
			{
				PraetorisClientPlugin.Log.LogWarning((object)("Failed to capture RPC trace row: " + ex.GetType().Name + ": " + ex.Message));
			}
		}

		internal static RoutedRPCData? TryReadRoutedRpcData(ZPackage? package)
		{
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0012: Expected O, but got Unknown
			//IL_0012: Unknown result type (might be due to invalid IL or missing references)
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			//IL_001f: Expected O, but got Unknown
			if (package == null)
			{
				return null;
			}
			try
			{
				ZPackage val = new ZPackage(package.GetArray());
				RoutedRPCData val2 = new RoutedRPCData();
				val2.Deserialize(val);
				return val2;
			}
			catch (Exception ex)
			{
				PraetorisClientPlugin.Log.LogWarning((object)("Failed to read routed RPC trace package: " + ex.GetType().Name + ": " + ex.Message));
				return null;
			}
		}

		internal static long GetPeerIdForRpc(ZRpc rpc)
		{
			if ((Object)(object)ZNet.instance == (Object)null)
			{
				return 0L;
			}
			foreach (ZNetPeer peer in ZNet.instance.GetPeers())
			{
				if (peer != null && peer.m_rpc == rpc)
				{
					return peer.m_uid;
				}
			}
			return 0L;
		}

		internal static void Update()
		{
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			//IL_001a: Invalid comparison between Unknown and I4
			if (!IsTracingEnabled())
			{
				RpcTraceLocalStore.CloseCurrentFile();
				return;
			}
			if (_suppressCaptureUntilDisconnected && (int)ZNet.GetConnectionStatus() != 2)
			{
				_suppressCaptureUntilDisconnected = false;
			}
			MaybeSendClockSyncRequest();
			RpcTraceUploadTokenClient.Update();
			RpcTraceHttpUploadCoordinator.Update();
			RpcTraceFlushCoordinator.Update();
		}

		internal static void OnClockResponse(long sender, ZPackage package)
		{
			if (!IsTracingEnabled())
			{
				return;
			}
			long ticks = DateTime.UtcNow.Ticks;
			double realtimeSinceStartupAsDouble = Time.realtimeSinceStartupAsDouble;
			try
			{
				int num = package.ReadInt();
				int value = package.ReadInt();
				long value2 = package.ReadLong();
				long value3 = package.ReadLong();
				long num2 = package.ReadLong();
				double num3 = package.ReadDouble();
				long num4 = package.ReadLong();
				double num5 = package.ReadDouble();
				long num6 = package.ReadLong();
				double num7 = package.ReadDouble();
				if (num == 2)
				{
					double num8 = (realtimeSinceStartupAsDouble - num3) * 1000.0;
					double num9 = (num7 - num5) * 1000.0;
					double num10 = num8 - num9;
					double num11 = (TicksToMilliseconds(num4 - num2) + TicksToMilliseconds(num6 - ticks)) / 2.0;
					bool value4 = UpdateClockOffset(num11, num10, realtimeSinceStartupAsDouble);
					long localPeerId = GetLocalPeerId();
					TelemetryJson telemetryJson = ObjectWithEnvelope("clock_sync_sample", localPeerId);
					telemetryJson.Prop("role", "client");
					telemetryJson.Prop("senderPeerId", sender);
					telemetryJson.Prop("clientPeerId", value2);
					telemetryJson.Prop("serverPeerId", value3);
					telemetryJson.Prop("clockSequence", value);
					telemetryJson.Prop("clientSendUtcTicks", num2);
					telemetryJson.Prop("serverReceiveUtcTicks", num4);
					telemetryJson.Prop("serverSendUtcTicks", num6);
					telemetryJson.Prop("clientReceiveUtcTicks", ticks);
					telemetryJson.Prop("clientSendRealtime", num3);
					telemetryJson.Prop("serverReceiveRealtime", num5);
					telemetryJson.Prop("serverSendRealtime", num7);
					telemetryJson.Prop("clientReceiveRealtime", realtimeSinceStartupAsDouble);
					telemetryJson.Prop("roundTripMs", num10);
					telemetryJson.Prop("serverProcessingMs", num9);
					telemetryJson.Prop("sampleServerMinusClientOffsetMs", num11);
					telemetryJson.Prop("serverMinusClientOffsetMs", _lastServerMinusClientOffsetMs);
					telemetryJson.Prop("clockSampleAccepted", value4);
					telemetryJson.Prop("clockOffsetQuality", GetClockOffsetQuality());
					telemetryJson.Prop("clockOffsetAgeMs", GetClockOffsetAgeMs());
					telemetryJson.Prop("clockSampleCount", _validClockSampleCount);
					telemetryJson.End();
					RpcTraceLocalStore.Append(telemetryJson.ToString(), localPeerId);
				}
			}
			catch (Exception ex)
			{
				PraetorisClientPlugin.Log.LogWarning((object)("Failed to process RPC trace clock response: " + ex.GetType().Name + ": " + ex.Message));
			}
		}

		internal static bool ShouldAllowLogout(Game game, bool save, bool changeToStartScene)
		{
			return RpcTraceFlushCoordinator.ShouldAllowLogout(game, save, changeToStartScene);
		}

		internal static bool ShouldAllowMenuQuit()
		{
			return RpcTraceFlushCoordinator.ShouldAllowMenuQuit();
		}

		internal static void OnApplicationQuitFallback()
		{
			RpcTraceFlushCoordinator.RequestFlush("application_quit");
		}

		internal static bool IsTracingEnabled()
		{
			return PraetorisClientPlugin.RpcTraceEnabled.Value;
		}

		private static TelemetryJson ObjectWithEnvelope(string eventType, long localPeerId)
		{
			DateTime utcNow = DateTime.UtcNow;
			long value = ++_sequence;
			TelemetryJson telemetryJson = TelemetryJson.Object();
			telemetryJson.Prop("schema", "valheim.trace.rpc.v1");
			telemetryJson.Prop("eventType", eventType);
			telemetryJson.Prop("traceId", "client:" + localPeerId.ToString(CultureInfo.InvariantCulture) + ":" + value.ToString(CultureInfo.InvariantCulture));
			telemetryJson.Prop("sequence", value);
			telemetryJson.Prop("localPeerId", localPeerId);
			telemetryJson.Prop("timeUtc", utcNow.ToString("o", CultureInfo.InvariantCulture));
			telemetryJson.Prop("timeUtcTicks", utcNow.Ticks);
			telemetryJson.Prop("realtime", Time.realtimeSinceStartupAsDouble);
			telemetryJson.Prop("worldName", (ZNet.m_world != null) ? ZNet.m_world.m_name : "");
			telemetryJson.Prop("worldUid", (ZNet.m_world != null) ? ZNet.m_world.m_uid : 0);
			return telemetryJson;
		}

		private static bool CanCapture()
		{
			//IL_0035: Unknown result type (might be due to invalid IL or missing references)
			//IL_003b: Invalid comparison between Unknown and I4
			if (IsTracingEnabled() && !_shutdownCapture && !_suppressCaptureUntilDisconnected && (Object)(object)ZNet.instance != (Object)null && ZRoutedRpc.instance != null && !ZNet.instance.IsServer())
			{
				return (int)ZNet.GetConnectionStatus() == 2;
			}
			return false;
		}

		private static void MaybeSendClockSyncRequest()
		{
			//IL_0039: Unknown result type (might be due to invalid IL or missing references)
			//IL_003f: Expected O, but got Unknown
			if (CanCapture() && !(Time.realtimeSinceStartup < _nextClockSyncTime) && ZRoutedRpc.instance != null)
			{
				_nextClockSyncTime = Time.realtimeSinceStartup + 10f;
				int num = ++_clockSequence;
				ZPackage val = new ZPackage();
				val.Write(2);
				val.Write(num);
				val.Write(GetLocalPeerId());
				val.Write(DateTime.UtcNow.Ticks);
				val.Write(Time.realtimeSinceStartupAsDouble);
				ZRoutedRpc.instance.InvokeRoutedRPC(ZRoutedRpc.instance.GetServerPeerID(), "PraetorisClient_RpcTraceClockRequest", new object[1] { val });
			}
		}

		private static bool IsTraceRpc(RoutedRPCData data)
		{
			if (data.m_methodHash != StringExtensionMethods.GetStableHashCode("PraetorisClient_RpcTraceClockRequest") && data.m_methodHash != StringExtensionMethods.GetStableHashCode("PraetorisClient_RpcTraceClockResponse") && data.m_methodHash != StringExtensionMethods.GetStableHashCode("PraetorisClient_RpcTraceUploadTokenRequest"))
			{
				return data.m_methodHash == StringExtensionMethods.GetStableHashCode("PraetorisClient_RpcTraceUploadTokenResponse");
			}
			return true;
		}

		private static string GetRpcName(int methodHash)
		{
			lock (Sync)
			{
				string value;
				return RpcNamesByHash.TryGetValue(methodHash, out value) ? value : "";
			}
		}

		private static bool IsDenied(string rpcName)
		{
			if (!string.IsNullOrEmpty(rpcName))
			{
				return _denyList.Contains(rpcName);
			}
			return false;
		}

		private static void RefreshDenyList()
		{
			string text = PraetorisClientPlugin.RpcTraceNameDenyList.Value ?? "";
			if (string.Equals(text, _lastDenyListConfig, StringComparison.Ordinal))
			{
				return;
			}
			HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
			string[] array = text.Split(new char[1] { ',' });
			for (int i = 0; i < array.Length; i++)
			{
				string text2 = array[i].Trim();
				if (text2.Length > 0)
				{
					hashSet.Add(text2);
				}
			}
			_denyList = hashSet;
			_lastDenyListConfig = text;
		}

		internal static long GetLocalPeerId()
		{
			try
			{
				return (ZDOMan.instance != null) ? ZDOMan.GetSessionID() : 0;
			}
			catch
			{
				return 0L;
			}
		}

		private static double TicksToMilliseconds(long ticks)
		{
			return (double)ticks / 10000.0;
		}

		private static string BuildRpcTraceId(long senderPeerId, long msgId)
		{
			long num = ((ZNet.m_world != null) ? ZNet.m_world.m_uid : 0);
			return num.ToString(CultureInfo.InvariantCulture) + ":" + senderPeerId.ToString(CultureInfo.InvariantCulture) + ":" + msgId.ToString(CultureInfo.InvariantCulture);
		}

		private static bool UpdateClockOffset(double offsetMs, double roundTripMs, double realtime)
		{
			if (roundTripMs < 0.0 || roundTripMs > 2000.0 || double.IsNaN(offsetMs) || double.IsInfinity(offsetMs))
			{
				return false;
			}
			if (ClockSamples.Count >= 3)
			{
				double medianOffset = GetMedianOffset();
				if (Math.Abs(offsetMs - medianOffset) > 250.0)
				{
					return false;
				}
			}
			ClockSamples.Add(new ClockSample(offsetMs, roundTripMs, realtime));
			while (ClockSamples.Count > 9)
			{
				ClockSamples.RemoveAt(0);
			}
			List<ClockSample> list = new List<ClockSample>(ClockSamples);
			list.Sort((ClockSample left, ClockSample right) => left.RoundTripMs.CompareTo(right.RoundTripMs));
			int num = Math.Min(5, list.Count);
			double num2 = 0.0;
			for (int num3 = 0; num3 < num; num3++)
			{
				num2 += list[num3].OffsetMs;
			}
			_lastServerMinusClientOffsetMs = num2 / (double)num;
			_selectedClockRoundTripMs = list[0].RoundTripMs;
			_lastGoodClockSyncRealtime = realtime;
			_validClockSampleCount++;
			_hasClockOffset = true;
			return true;
		}

		private static double GetMedianOffset()
		{
			List<double> list = new List<double>(ClockSamples.Count);
			foreach (ClockSample clockSample in ClockSamples)
			{
				list.Add(clockSample.OffsetMs);
			}
			list.Sort();
			int num = list.Count / 2;
			if (list.Count % 2 == 1)
			{
				return list[num];
			}
			return (list[num - 1] + list[num]) / 2.0;
		}

		private static string GetClockOffsetQuality()
		{
			if (!_hasClockOffset)
			{
				return "none";
			}
			if (GetClockOffsetAgeMs() > 30000.0)
			{
				return "stale";
			}
			if (ClockSamples.Count < 3)
			{
				return "warming";
			}
			return "good";
		}

		private static double GetClockOffsetAgeMs()
		{
			if (!_hasClockOffset)
			{
				return 0.0;
			}
			return Math.Max(0.0, (Time.realtimeSinceStartupAsDouble - _lastGoodClockSyncRealtime) * 1000.0);
		}
	}
	internal static class RpcTraceUploadTokenClient
	{
		private const float RequestRetrySeconds = 30f;

		private const float ConfigurationRetrySeconds = 300f;

		private const long RefreshBeforeExpirySeconds = 60L;

		private static string _sessionId = "";

		private static float _nextRequestTime;

		private static float _requestDeadlineTime;

		private static bool _requestPending;

		internal static bool UploadEnabled { get; private set; }

		internal static string EndpointUrl { get; private set; } = "";

		internal static string Token { get; private set; } = "";

		internal static int MaxBatchBytes { get; private set; } = 131072;

		internal static float FlushIntervalSeconds { get; private set; } = 10f;

		internal static long TokenExpiresUnixSeconds { get; private set; }

		internal static string SessionId
		{
			get
			{
				EnsureSessionId();
				return _sessionId;
			}
		}

		internal static void Initialize()
		{
			EnsureSessionId();
			UploadEnabled = false;
			EndpointUrl = "";
			Token = "";
			TokenExpiresUnixSeconds = 0L;
			_requestPending = false;
			_nextRequestTime = 0f;
		}

		internal static void Update()
		{
			if (!PraetorisClientPlugin.RpcTraceHttpUploadPreferred.Value || !RpcTraceTelemetry.IsTracingEnabled())
			{
				return;
			}
			if (!CanRequestToken())
			{
				if (_requestPending && Time.realtimeSinceStartup > _requestDeadlineTime)
				{
					_requestPending = false;
				}
			}
			else if (!HasUsableToken() && !(Time.realtimeSinceStartup < _nextRequestTime))
			{
				RequestToken();
			}
		}

		internal static bool HasUsableToken()
		{
			long num = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
			if (UploadEnabled && !string.IsNullOrWhiteSpace(EndpointUrl) && !string.IsNullOrWhiteSpace(Token))
			{
				return TokenExpiresUnixSeconds - num > 60;
			}
			return false;
		}

		internal static bool ShouldRetryUpload(long responseCode, string responseText)
		{
			if (!IsConfigurationFailure(responseCode, responseText))
			{
				return true;
			}
			UploadEnabled = false;
			EndpointUrl = "";
			Token = "";
			TokenExpiresUnixSeconds = 0L;
			_requestPending = false;
			_nextRequestTime = Time.realtimeSinceStartup + 300f;
			PraetorisClientPlugin.Log.LogWarning((object)("HTTP RPC trace upload rejected by receiver configuration: " + (string.IsNullOrWhiteSpace(responseText) ? ("HTTP " + responseCode) : responseText) + ". Keeping local trace files and pausing token requests."));
			return false;
		}

		internal static void OnTokenResponse(long sender, ZPackage package)
		{
			try
			{
				int num = package.ReadInt();
				bool flag = package.ReadBool();
				string text = package.ReadString();
				string endpointUrl = package.ReadString();
				string token = package.ReadString();
				string text2 = package.ReadString();
				int val = package.ReadInt();
				float val2 = package.ReadSingle();
				long tokenExpiresUnixSeconds = package.ReadLong();
				if (num != 2)
				{
					return;
				}
				_requestPending = false;
				_nextRequestTime = Time.realtimeSinceStartup + 30f;
				if (!flag)
				{
					UploadEnabled = false;
					EndpointUrl = "";
					Token = "";
					TokenExpiresUnixSeconds = 0L;
					if (!string.IsNullOrWhiteSpace(text))
					{
						PraetorisClientPlugin.Log.LogInfo((object)("HTTP RPC trace upload unavailable: " + text));
					}
					return;
				}
				if (!string.IsNullOrWhiteSpace(text2))
				{
					_sessionId = text2;
				}
				UploadEnabled = true;
				EndpointUrl = endpointUrl;
				Token = token;
				MaxBatchBytes = Math.Max(4096, val);
				FlushIntervalSeconds = Math.Max(1f, val2);
				TokenExpiresUnixSeconds = tokenExpiresUnixSeconds;
				PraetorisClientPlugin.Log.LogInfo((object)("Received HTTP RPC trace upload token for session " + _sessionId + " expiring at " + tokenExpiresUnixSeconds.ToString(CultureInfo.InvariantCulture) + "."));
			}
			catch (Exception ex)
			{
				_requestPending = false;
				PraetorisClientPlugin.Log.LogWarning((object)$"Failed to process RPC trace upload token response from peer {sender}: {ex.Message}");
			}
		}

		private static void RequestToken()
		{
			//IL_000e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0014: Expected O, but got Unknown
			if (ZRoutedRpc.instance == null)
			{
				return;
			}
			try
			{
				EnsureSessionId();
				ZPackage val = new ZPackage();
				val.Write(2);
				val.Write(_sessionId);
				ZRoutedRpc.instance.InvokeRoutedRPC(ZRoutedRpc.instance.GetServerPeerID(), "PraetorisClient_RpcTraceUploadTokenRequest", new object[1] { val });
				_requestPending = true;
				_nextRequestTime = Time.realtimeSinceStartup + 30f;
				_requestDeadlineTime = Time.realtimeSinceStartup + 30f;
			}
			catch (Exception ex)
			{
				_requestPending = false;
				_nextRequestTime = Time.realtimeSinceStartup + 30f;
				PraetorisClientPlugin.Log.LogWarning((object)("Failed to request RPC trace upload token: " + ex.Message));
			}
		}

		private static bool CanRequestToken()
		{
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			//IL_002d: Invalid comparison between Unknown and I4
			if (!_requestPending && (Object)(object)ZNet.instance != (Object)null && ZRoutedRpc.instance != null && !ZNet.instance.IsServer())
			{
				return (int)ZNet.GetConnectionStatus() == 2;
			}
			return false;
		}

		private static bool IsConfigurationFailure(long responseCode, string responseText)
		{
			string text = responseText ?? "";
			if (responseCode != 403 && text.IndexOf("missing relay key", StringComparison.OrdinalIgnoreCase) < 0 && text.IndexOf("missing token signing secret", StringComparison.OrdinalIgnoreCase) < 0 && text.IndexOf("unauthorized relay", StringComparison.OrdinalIgnoreCase) < 0 && text.IndexOf("invalid token", StringComparison.OrdinalIgnoreCase) < 0)
			{
				return text.IndexOf("failed to read request body", StringComparison.OrdinalIgnoreCase) >= 0;
			}
			return true;
		}

		private static void EnsureSessionId()
		{
			if (string.IsNullOrWhiteSpace(_sessionId))
			{
				_sessionId = Guid.NewGuid().ToString("N");
			}
		}
	}
	internal static class SiegePortalBridge
	{
		private const int ProtocolVersion = 1;

		internal const string SiegeIdZdoKey = "valheimCreativeSiegeId";

		internal const string SiegeTagPrefix = "siege:";

		internal static bool TryHandle(TeleportWorld portal, Player player)
		{
			//IL_00c2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c9: Expected O, but got Unknown
			//IL_00d4: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)ZNet.instance == (Object)null || ZNet.instance.IsServer() || ZRoutedRpc.instance == null)
			{
				return false;
			}
			if ((Object)(object)player == (Object)null || (Object)(object)Player.m_localPlayer == (Object)null || (Object)(object)player != (Object)(object)Player.m_localPlayer)
			{
				return false;
			}
			ZNetView component = ((Component)portal).GetComponent<ZNetView>();
			ZDO val = (((Object)(object)component != (Object)null && component.IsValid()) ? component.GetZDO() : null);
			if (val == null || !TryGetSiegeId(val, out string siegeId))
			{
				return false;
			}
			if ((Object)(object)((Character)player).m_nview == (Object)null || !((Character)player).m_nview.IsValid())
			{
				((Character)player).Message((MessageType)2, "Siege portal failed: character was not ready.", 0, (Sprite)null);
				return true;
			}
			ZDO zDO = ((Character)player).m_nview.GetZDO();
			if (zDO == null)
			{
				((Character)player).Message((MessageType)2, "Siege portal failed: character was not ready.", 0, (Sprite)null);
				return true;
			}
			ZPackage val2 = new ZPackage();
			val2.Write(1);
			val2.Write(zDO.m_uid);
			val2.Write(siegeId);
			ZRoutedRpc.instance.InvokeRoutedRPC(ZRoutedRpc.instance.GetServerPeerID(), "DiscordTools_SiegePortalEnter", new object[1] { val2 });
			((Character)player).Message((MessageType)2, "Entering siege: " + siegeId, 0, (Sprite)null);
			PraetorisClientPlugin.Log.LogInfo((object)("Requested siege portal enter for " + siegeId + "."));
			return true;
		}

		private static bool TryGetSiegeId(ZDO portalZdo, out string siegeId)
		{
			siegeId = portalZdo.GetString("valheimCreativeSiegeId", "").Trim();
			if (!string.IsNullOrWhiteSpace(siegeId))
			{
				return true;
			}
			string text = portalZdo.GetString(ZDOVars.s_tag, "").Trim();
			if (text.StartsWith("siege:", StringComparison.OrdinalIgnoreCase))
			{
				siegeId = text.Substring("siege:".Length).Trim();
				return !string.IsNullOrWhiteSpace(siegeId);
			}
			return false;
		}
	}
	[HarmonyPatch(typeof(TeleportWorld), "Teleport")]
	internal static class TeleportWorldSiegePortalPatch
	{
		private static bool Prefix(TeleportWorld __instance, Player player)
		{
			return !SiegePortalBridge.TryHandle(__instance, player);
		}
	}
	internal static class SiegePortalTestCommand
	{
		[Serializable]
		[CompilerGenerated]
		private sealed class <>c
		{
			public static readonly <>c <>9 = new <>c();

			public static ConsoleEvent <>9__1_0;

			public static ConsoleEvent <>9__1_1;

			internal void <Register>b__1_0(ConsoleEventArgs args)
			{
				//IL_008e: Unknown result type (might be due to invalid IL or missing references)
				//IL_013d: Unknown result type (might be due to invalid IL or missing references)
				if (args.Length < 2)
				{
					args.Context.AddString("Usage: dt_mark_siege_portal <siegeId> [radius]");
					return;
				}
				string text = args[1].Trim();
				if (string.IsNullOrWhiteSpace(text))
				{
					args.Context.AddString("siegeId is required.");
					return;
				}
				float result = 12f;
				if (args.Length >= 3)
				{
					float.TryParse(args[2], NumberStyles.Float, CultureInfo.InvariantCulture, out result);
				}
				Player localPlayer = Player.m_localPlayer;
				if ((Object)(object)localPlayer == (Object)null)
				{
					args.Context.AddString("No local player found.");
					return;
				}
				float distance;
				TeleportWorld val = FindNearestPortal(((Component)localPlayer).transform.position, Mathf.Clamp(result, 1f, 50f), out distance);
				if ((Object)(object)val == (Object)null)
				{
					args.Context.AddString($"No portal found within {result:0.#}m.");
					return;
				}
				ZNetView component = ((Component)val).GetComponent<ZNetView>();
				ZDO val2 = (((Object)(object)component != (Object)null && component.IsValid()) ? component.GetZDO() : null);
				if (val2 == null)
				{
					args.Context.AddString("Nearest portal has no valid ZDO.");
					return;
				}
				val2.Set("valheimCreativeSiegeId", text);
				val2.Set(ZDOVars.s_tag, "siege:" + text);
				args.Context.AddString($"Marked nearest portal {val2.m_uid} as siege {text} at {distance:0.#}m.");
			}

			internal void <Register>b__1_1(ConsoleEventArgs args)
			{
				//IL_004e: Unknown result type (might be due to invalid IL or missing references)
				float result = 12f;
				if (args.Length >= 2)
				{
					float.TryParse(args[1], NumberStyles.Float, CultureInfo.InvariantCulture, out result);
				}
				Player localPlayer = Player.m_localPlayer;
				if ((Object)(object)localPlayer == (Object)null)
				{
					args.Context.AddString("No local player found.");
					return;
				}
				float distance;
				TeleportWorld val = FindNearestPortal(((Component)localPlayer).transform.position, Mathf.Clamp(result, 1f, 50f), out distance);
				if ((Object)(object)val == (Object)null)
				{
					args.Context.AddString($"No portal found within {result:0.#}m.");
					return;
				}
				bool flag = SiegePortalBridge.TryHandle(val, localPlayer);
				args.Context.AddString($"Nearest siege portal handled={flag} distance={distance:0.#}m.");
			}
		}

		private static bool _registered;

		internal static void Register()
		{
			//IL_0040: Unknown result type (might be due to invalid IL or missing references)
			//IL_002c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0031: Unknown result type (might be due to invalid IL or missing references)
			//IL_0037: Expected O, but got Unknown
			//IL_0078: Unknown result type (might be due to invalid IL or missing references)
			//IL_0064: Unknown result type (might be due to invalid IL or missing references)
			//IL_0069: Unknown result type (might be due to i