Decompiled source of Muninn v0.7.0

plugins/GuildTelemetry.dll

Decompiled 17 hours ago
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.IO.Compression;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Cryptography;
using System.Security.Permissions;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using GuildTelemetry.Core;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Splatform;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: IgnoresAccessChecksTo("assembly_valheim")]
[assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")]
[assembly: AssemblyCompany("GuildTelemetry")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("0.7.0.0")]
[assembly: AssemblyInformationalVersion("0.7.0+8dc8a4ee5924c88e4590acca90b8fc35bc40353e")]
[assembly: AssemblyProduct("GuildTelemetry")]
[assembly: AssemblyTitle("GuildTelemetry")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("0.7.0.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
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;
		}
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace GuildTelemetry
{
	internal sealed class Announcer
	{
		private readonly ManualLogSource log;

		private readonly Telemetry telemetry;

		private readonly AnnouncementScheduler scheduler = new AnnouncementScheduler();

		public Announcer(ManualLogSource log, Telemetry telemetry)
		{
			this.log = log;
			this.telemetry = telemetry;
		}

		public void Update(TelemetryPipeline pipeline, float now)
		{
			IngestResponse ingestResponse = pipeline.TakeResponse();
			if (ingestResponse != null)
			{
				scheduler.Apply(ingestResponse.Announcements, now);
			}
			foreach (Banner item in scheduler.Due(now))
			{
				Show(item);
			}
		}

		private void Show(Banner banner)
		{
			if (ZRoutedRpc.instance == null)
			{
				log.LogWarning((object)("GuildTelemetry: no routed RPC yet, announcement " + banner.AnnouncementId + " dropped"));
				return;
			}
			try
			{
				ZRoutedRpc.instance.InvokeRoutedRPC(0L, "ShowMessage", new object[2] { 2, banner.Text });
			}
			catch (Exception ex)
			{
				log.LogWarning((object)("GuildTelemetry: ShowMessage failed for announcement " + banner.AnnouncementId + ": " + ex.Message));
				return;
			}
			telemetry.AnnouncementShown(banner);
			log.LogInfo((object)("GuildTelemetry: announced \"" + banner.Text + "\" (announcement " + banner.AnnouncementId + ")"));
		}
	}
	internal static class ComfortCatalogueSender
	{
		private static int started;

		public static void Start(ManualLogSource log, string ingestUrl, string secret)
		{
			if (Interlocked.Exchange(ref started, 1) == 1)
			{
				return;
			}
			try
			{
				string problem;
				ComfortCatalogue comfortCatalogue = ComfortReader.Read(out problem);
				if (comfortCatalogue == null)
				{
					log.LogWarning((object)("GuildTelemetry: comfort catalogue not sent: " + problem));
					return;
				}
				if (comfortCatalogue.Pieces.Count == 0)
				{
					log.LogWarning((object)"GuildTelemetry: comfort catalogue not sent: the game data yielded no comfort pieces");
					return;
				}
				byte[] body = Encoding.UTF8.GetBytes(comfortCatalogue.ToJson());
				string url = ingestUrl.TrimEnd(new char[1] { '/' }) + "/catalogue";
				string summary = comfortCatalogue.Pieces.Count + " comfort pieces for game " + comfortCatalogue.GameVersion;
				Thread thread = new Thread((ThreadStart)delegate
				{
					Send(log, url, secret, body, summary);
				});
				thread.Name = "GuildTelemetry catalogue";
				thread.IsBackground = true;
				thread.Priority = ThreadPriority.BelowNormal;
				thread.Start();
			}
			catch (Exception ex)
			{
				log.LogWarning((object)("GuildTelemetry: comfort catalogue could not be read: " + ex));
			}
		}

		private static void Send(ManualLogSource log, string url, string secret, byte[] body, string summary)
		{
			try
			{
				if (new OneShotUpload(new HttpTransport(), Thread.Sleep, () => DateTime.UtcNow).Send(url, secret, body, "application/json", delegate(string message)
				{
					log.LogWarning((object)("GuildTelemetry: comfort catalogue " + message));
				}) == UploadOutcome.Stored)
				{
					log.LogInfo((object)("GuildTelemetry: sent " + summary));
				}
			}
			catch (Exception ex)
			{
				log.LogWarning((object)("GuildTelemetry: comfort catalogue upload failed: " + ex));
			}
		}
	}
	internal static class ComfortReader
	{
		public static ComfortCatalogue? Read(out string? problem)
		{
			//IL_023b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0240: Unknown result type (might be due to invalid IL or missing references)
			ZNetScene instance = ZNetScene.instance;
			ObjectDB instance2 = ObjectDB.instance;
			if ((Object)(object)instance == (Object)null || (Object)(object)instance2 == (Object)null)
			{
				problem = "the prefab scene or the object database is not ready";
				return null;
			}
			StatusEffect statusEffect = instance2.GetStatusEffect(SEMan.s_statusEffectRested);
			SE_Rested val = (SE_Rested)(object)((statusEffect is SE_Rested) ? statusEffect : null);
			if ((Object)(object)val == (Object)null)
			{
				problem = "the object database has no Rested effect";
				return null;
			}
			HashSet<string> hashSet = BuildablePrefabs(instance);
			List<ComfortSeason> list = new List<ComfortSeason>();
			Dictionary<string, string> dictionary = new Dictionary<string, string>(StringComparer.Ordinal);
			foreach (SeasonalItemGroup item in SeasonalGroups(instance))
			{
				ComfortSeason season = WindowOf(item);
				if (season != null && !list.Exists((ComfortSeason known) => known.Name == season.Name))
				{
					list.Add(season);
				}
				foreach (GameObject piece in item.Pieces)
				{
					if ((Object)(object)piece != (Object)null && !dictionary.ContainsKey(((Object)piece).name))
					{
						dictionary[((Object)piece).name] = ((Object)item).name;
					}
				}
			}
			List<ComfortPiece> list2 = new List<ComfortPiece>();
			foreach (GameObject prefab in instance.m_prefabs)
			{
				if ((Object)(object)prefab == (Object)null || !hashSet.Contains(((Object)prefab).name) || (Object)(object)prefab.GetComponent<ItemDrop>() != (Object)null)
				{
					continue;
				}
				Piece component = prefab.GetComponent<Piece>();
				if (!((Object)(object)component == (Object)null) && component.m_comfort > 0)
				{
					string value = null;
					if (component.m_enabled || dictionary.TryGetValue(((Object)prefab).name, out value))
					{
						list2.Add(new ComfortPiece(((Object)prefab).name, component.m_name ?? string.Empty, NameOf(prefab, component), component.m_comfort, ((object)Unsafe.As<ComfortGroup, ComfortGroup>(ref component.m_comfortGroup)/*cast due to .constrained prefix*/).ToString(), ConditionOf(prefab, component), value));
					}
				}
			}
			problem = null;
			return new ComfortCatalogue(((object)Version.CurrentVersion/*cast due to .constrained prefix*/).ToString(), "0.7.0", DateTime.UtcNow, Radius(), val.m_baseTTL, val.m_TTLPerComfortLevel, Enum.GetNames(typeof(ComfortGroup)), list, list2);
		}

		private static HashSet<string> BuildablePrefabs(ZNetScene scene)
		{
			HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal);
			foreach (GameObject prefab in scene.m_prefabs)
			{
				if ((Object)(object)prefab == (Object)null)
				{
					continue;
				}
				ItemDrop component = prefab.GetComponent<ItemDrop>();
				if ((Object)(object)component == (Object)null || component.m_itemData == null || component.m_itemData.m_shared == null)
				{
					continue;
				}
				PieceTable buildPieces = component.m_itemData.m_shared.m_buildPieces;
				if ((Object)(object)buildPieces == (Object)null || buildPieces.m_pieces == null)
				{
					continue;
				}
				foreach (GameObject piece in buildPieces.m_pieces)
				{
					if ((Object)(object)piece != (Object)null)
					{
						hashSet.Add(((Object)piece).name);
					}
				}
			}
			return hashSet;
		}

		private static List<SeasonalItemGroup> SeasonalGroups(ZNetScene scene)
		{
			List<SeasonalItemGroup> list = new List<SeasonalItemGroup>();
			GameObject prefab = scene.GetPrefab("Player");
			if ((Object)(object)prefab == (Object)null)
			{
				return list;
			}
			Player component = prefab.GetComponent<Player>();
			FieldInfo field = typeof(Player).GetField("m_seasonalItemGroups", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
			if ((Object)(object)component == (Object)null || field == null || !(field.GetValue(component) is IEnumerable enumerable))
			{
				return list;
			}
			foreach (object item in enumerable)
			{
				SeasonalItemGroup val = (SeasonalItemGroup)((item is SeasonalItemGroup) ? item : null);
				if ((Object)(object)val != (Object)null && val.Pieces != null && !string.IsNullOrEmpty(((Object)val).name))
				{
					list.Add(val);
				}
			}
			return list;
		}

		private static ComfortSeason? WindowOf(SeasonalItemGroup group)
		{
			try
			{
				DateTime startDate = group.GetStartDate();
				DateTime endDate = group.GetEndDate();
				return new ComfortSeason(((Object)group).name, startDate.Day, startDate.Month, endDate.Day, endDate.Month);
			}
			catch (ArgumentOutOfRangeException)
			{
				return null;
			}
		}

		private static string NameOf(GameObject prefab, Piece piece)
		{
			string text = piece.m_name ?? string.Empty;
			if (text.Length == 0)
			{
				return ((Object)prefab).name;
			}
			string text2 = Localization.instance.Localize(text);
			if (!string.IsNullOrEmpty(text2) && !(text2 == "[" + text.TrimStart(new char[1] { '$' }) + "]"))
			{
				return text2;
			}
			return ((Object)prefab).name;
		}

		private static string? ConditionOf(GameObject prefab, Piece piece)
		{
			if ((Object)(object)piece.m_comfortObject == (Object)null)
			{
				return null;
			}
			Fireplace component = prefab.GetComponent<Fireplace>();
			if (!((Object)(object)component != (Object)null) || !((Object)(object)component.m_enabledObjectHigh != (Object)null) || !((Object)(object)component.m_enabledObjectLow != (Object)null) || !piece.m_comfortObject.transform.IsChildOf(component.m_enabledObjectHigh.transform))
			{
				return "lit";
			}
			return "lit_dry";
		}

		private static double? Radius()
		{
			FieldInfo field = typeof(SE_Rested).GetField("c_ComfortRadius", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
			if (((field == null) ? null : (field.IsLiteral ? field.GetRawConstantValue() : field.GetValue(null))) is float num && num > 0f)
			{
				return num;
			}
			return null;
		}
	}
	internal static class Creators
	{
		public unsafe static IList<string?> History()
		{
			//IL_0029: Unknown result type (might be due to invalid IL or missing references)
			//IL_002e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0030: 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_0036: 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_0045: 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)
			List<string> list = new List<string>();
			World world = ZNet.World;
			if (world == null || world.m_playerHistory == null)
			{
				return list;
			}
			foreach (CrossNetworkUserInfo item2 in world.m_playerHistory)
			{
				PlatformUserID id = item2.m_id;
				object item;
				if (!((PlatformUserID)(ref id)).IsValid)
				{
					item = null;
				}
				else
				{
					id = item2.m_id;
					item = ((object)(*(PlatformUserID*)(&id))/*cast due to .constrained prefix*/).ToString();
				}
				list.Add((string)item);
			}
			return list;
		}

		public static string? AccountOf(ZDO zdo, IList<string?> history)
		{
			return CreatorAccounts.AccountAt(history, zdo.GetInt(ZDOVars.s_creatorIndex, -1));
		}

		public static CreatorAccounts Scan(out int scanned, out long elapsedMs)
		{
			Stopwatch stopwatch = Stopwatch.StartNew();
			CreatorAccounts creatorAccounts = new CreatorAccounts();
			scanned = 0;
			IList<string> list = History();
			FieldInfo field = typeof(ZDOMan).GetField("m_objectsByID", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
			if (list.Count > 0 && ZDOMan.instance != null && field != null && field.GetValue(ZDOMan.instance) is Dictionary<ZDOID, ZDO> dictionary)
			{
				foreach (ZDO value in dictionary.Values)
				{
					scanned++;
					long num = value.GetLong(ZDOVars.s_creator, 0L);
					if (num != 0L && !creatorAccounts.Knows(num))
					{
						creatorAccounts.Add(num, AccountOf(value, list));
					}
				}
			}
			elapsedMs = stopwatch.ElapsedMilliseconds;
			return creatorAccounts;
		}
	}
	internal static class GameVersions
	{
		public static uint NetworkVersion()
		{
			FieldInfo field = typeof(Version).GetField("c_networkVersion", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
			object obj = ((field == null) ? null : (field.IsLiteral ? field.GetRawConstantValue() : field.GetValue(null)));
			if (obj is uint)
			{
				return (uint)obj;
			}
			if (obj is int)
			{
				return (uint)(int)obj;
			}
			return 40u;
		}
	}
	[BepInPlugin("com.guildsite.telemetry", "GuildTelemetry", "0.7.0")]
	[BepInProcess("valheim_server.exe")]
	public sealed class GuildTelemetryPlugin : BaseUnityPlugin
	{
		private static ManualLogSource? log;

		private Harmony? harmony;

		private PluginConfig? config;

		private TelemetryPipeline? pipeline;

		private Telemetry? telemetry;

		private Announcer? announcer;

		private readonly List<string> missingHooks = new List<string>();

		private bool disabled;

		private float nextBiomeSample;

		private float nextPositionSample;

		private float nextHeartbeat;

		internal static ManualLogSource Log => log ?? throw new InvalidOperationException("GuildTelemetry has not been loaded.");

		private void Awake()
		{
			//IL_00ee: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f8: Expected O, but got Unknown
			log = ((BaseUnityPlugin)this).Logger;
			Log.LogInfo((object)string.Format("{0} {1} loaded on Valheim {2} (network version {3})", "GuildTelemetry", "0.7.0", Version.GetVersionString(false), GameVersions.NetworkVersion()));
			try
			{
				config = new PluginConfig(((BaseUnityPlugin)this).Config);
				string text = config.Validate();
				if (text != null)
				{
					disabled = true;
					Log.LogError((object)("GuildTelemetry: configuration invalid (" + text + "); set Url and Secret in " + ((BaseUnityPlugin)this).Config.ConfigFilePath + " and restart. The plugin stays inactive."));
				}
				else
				{
					telemetry = new Telemetry(Log, config, missingHooks);
					announcer = new Announcer(Log, telemetry);
					harmony = new Harmony("com.guildsite.telemetry");
					missingHooks.AddRange(Hooks.Install(harmony, telemetry));
					Log.LogInfo((object)("GuildTelemetry: hooks installed" + ((missingHooks.Count > 0) ? (" (missing: " + string.Join(", ", missingHooks.ToArray()) + ")") : string.Empty)));
				}
			}
			catch (Exception ex)
			{
				disabled = true;
				Log.LogError((object)("GuildTelemetry: initialisation failed, the plugin stays inactive: " + ex));
			}
		}

		private void Update()
		{
			if (disabled || telemetry == null || config == null)
			{
				return;
			}
			try
			{
				if (!telemetry.Started)
				{
					if ((Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer() || (Object)(object)ZoneSystem.instance == (Object)null || WorldGenerator.instance == null || (Object)(object)ZNetScene.instance == (Object)null)
					{
						return;
					}
					StartPipeline();
					telemetry.ServerStarted();
					if (config.MapEnabled.Value)
					{
						MapRenderer.Start(Log, Path.Combine(Paths.ConfigPath, "GuildTelemetry"), ZNet.instance.GetWorldUID(), config.Url.Value.Trim(), config.Secret.Value.Trim());
					}
					if (config.CatalogEnabled.Value)
					{
						ComfortCatalogueSender.Start(Log, config.Url.Value.Trim(), config.Secret.Value.Trim());
					}
					float realtimeSinceStartup = Time.realtimeSinceStartup;
					nextBiomeSample = realtimeSinceStartup + (float)config.BiomeSampleSeconds.Value;
					nextPositionSample = realtimeSinceStartup + (float)config.PositionSampleSeconds.Value;
					nextHeartbeat = realtimeSinceStartup + (float)config.HeartbeatSeconds.Value;
				}
				Hooks.EnsureDestroyHook();
				float realtimeSinceStartup2 = Time.realtimeSinceStartup;
				if (realtimeSinceStartup2 >= nextBiomeSample)
				{
					nextBiomeSample = realtimeSinceStartup2 + (float)Math.Max(1, config.BiomeSampleSeconds.Value);
					telemetry.SampleBiomes();
				}
				if (realtimeSinceStartup2 >= nextPositionSample)
				{
					nextPositionSample = realtimeSinceStartup2 + (float)Math.Max(1, config.PositionSampleSeconds.Value);
					telemetry.SamplePositions();
				}
				if (realtimeSinceStartup2 >= nextHeartbeat)
				{
					nextHeartbeat = realtimeSinceStartup2 + (float)Math.Max(5, config.HeartbeatSeconds.Value);
					telemetry.Heartbeat();
				}
				if (pipeline != null && announcer != null)
				{
					announcer.Update(pipeline, realtimeSinceStartup2);
				}
			}
			catch (Exception ex)
			{
				Log.LogWarning((object)("GuildTelemetry: update failed: " + ex.Message));
			}
		}

		private void StartPipeline()
		{
			//IL_0049: 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)
			if (telemetry != null && config != null)
			{
				string text = ZNet.m_ServerName ?? string.Empty;
				string text2 = ZNet.instance.GetWorldName() ?? string.Empty;
				long worldUID = ZNet.instance.GetWorldUID();
				BatchMetadata metadata = new BatchMetadata("GuildTelemetry", "0.7.0", ((object)Version.CurrentVersion/*cast due to .constrained prefix*/).ToString(), (int)GameVersions.NetworkVersion(), text, text2, worldUID);
				EventJournal journal = new EventJournal(Path.Combine(Paths.ConfigPath, "GuildTelemetry"), (long)Math.Max(1, config.JournalMaxMB.Value) * 1024L * 1024);
				PipelineOptions pipelineOptions = new PipelineOptions
				{
					Url = config.Url.Value.Trim(),
					Secret = config.Secret.Value.Trim(),
					FlushIntervalMs = Math.Max(500, config.FlushSeconds.Value * 1000)
				};
				pipeline = new TelemetryPipeline(pipelineOptions, metadata, journal, new HttpTransport(), delegate(string message)
				{
					Log.LogInfo((object)("GuildTelemetry: " + message));
				});
				pipeline.Start();
				telemetry.AttachPipeline(pipeline);
				Log.LogInfo((object)("GuildTelemetry: sending to " + pipelineOptions.Url + " for server \"" + text + "\" world \"" + text2 + "\""));
			}
		}

		private void OnDestroy()
		{
			Hooks.Uninstall();
			Harmony? obj = harmony;
			if (obj != null)
			{
				obj.UnpatchSelf();
			}
			pipeline?.Dispose();
		}
	}
	internal static class Hooks
	{
		private static Telemetry? telemetry;

		private static int onDeathHash;

		private static int damageHash;

		private static int showMessageHash;

		private static int spawnBossHash;

		private static int chatMessageHash;

		private static int sayHash;

		private static int registerKillHash;

		private static ZDOID characterBeforeRpc = ZDOID.None;

		private static bool destroyHooked;

		public static List<string> Install(Harmony harmony, Telemetry target)
		{
			telemetry = target;
			onDeathHash = StringExtensionMethods.GetStableHashCode("OnDeath");
			damageHash = StringExtensionMethods.GetStableHashCode("RPC_Damage");
			showMessageHash = StringExtensionMethods.GetStableHashCode("ShowMessage");
			spawnBossHash = StringExtensionMethods.GetStableHashCode("RPC_SpawnBoss");
			chatMessageHash = StringExtensionMethods.GetStableHashCode("ChatMessage");
			sayHash = StringExtensionMethods.GetStableHashCode("Say");
			registerKillHash = StringExtensionMethods.GetStableHashCode("RPC_RegisterKill");
			List<string> list = new List<string>();
			Type typeFromHandle = typeof(Hooks);
			Patch(harmony, list, typeof(ZNet), "RPC_PeerInfo", new Type[2]
			{
				typeof(ZRpc),
				typeof(ZPackage)
			}, null, typeFromHandle.GetMethod("AfterPeerInfo", BindingFlags.Static | BindingFlags.NonPublic));
			Patch(harmony, list, typeof(ZNet), "RPC_CharacterID", new Type[2]
			{
				typeof(ZRpc),
				typeof(ZDOID)
			}, typeFromHandle.GetMethod("BeforeCharacterId", BindingFlags.Static | BindingFlags.NonPublic), typeFromHandle.GetMethod("AfterCharacterId", BindingFlags.Static | BindingFlags.NonPublic));
			Patch(harmony, list, typeof(ZNet), "RPC_Disconnect", new Type[1] { typeof(ZRpc) }, typeFromHandle.GetMethod("BeforeRpcDisconnect", BindingFlags.Static | BindingFlags.NonPublic), null);
			Patch(harmony, list, typeof(ZNet), "InternalKick", new Type[1] { typeof(ZNetPeer) }, typeFromHandle.GetMethod("BeforeKick", BindingFlags.Static | BindingFlags.NonPublic), null);
			Patch(harmony, list, typeof(ZNet), "Disconnect", new Type[1] { typeof(ZNetPeer) }, typeFromHandle.GetMethod("BeforeDisconnect", BindingFlags.Static | BindingFlags.NonPublic), null);
			Patch(harmony, list, typeof(ZNet), "Shutdown", new Type[1] { typeof(bool) }, typeFromHandle.GetMethod("BeforeShutdown", BindingFlags.Static | BindingFlags.NonPublic), null);
			Patch(harmony, list, typeof(ZoneSystem), "RPC_SetGlobalKey", new Type[2]
			{
				typeof(long),
				typeof(string)
			}, typeFromHandle.GetMethod("BeforeSetGlobalKey", BindingFlags.Static | BindingFlags.NonPublic), null);
			Patch(harmony, list, typeof(RandEventSystem), "SetRandomEvent", new Type[2]
			{
				typeof(RandomEvent),
				typeof(Vector3)
			}, typeFromHandle.GetMethod("BeforeSetRandomEvent", BindingFlags.Static | BindingFlags.NonPublic), null);
			Patch(harmony, list, typeof(ZRoutedRpc), "RPC_RoutedRPC", new Type[2]
			{
				typeof(ZRpc),
				typeof(ZPackage)
			}, typeFromHandle.GetMethod("BeforeRoutedRpc", BindingFlags.Static | BindingFlags.NonPublic), null);
			Patch(harmony, list, typeof(ZRoutedRpc), "InvokeRoutedRPC", new Type[4]
			{
				typeof(long),
				typeof(ZDOID),
				typeof(string),
				typeof(object[])
			}, typeFromHandle.GetMethod("BeforeInvokeRoutedRpc", BindingFlags.Static | BindingFlags.NonPublic), null);
			Patch(harmony, list, typeof(ZDOMan), "CreateNewZDO", new Type[3]
			{
				typeof(ZDOID),
				typeof(Vector3),
				typeof(int)
			}, null, typeFromHandle.GetMethod("AfterCreateNewZdo", BindingFlags.Static | BindingFlags.NonPublic));
			Patch(harmony, list, typeof(ZDO), "Deserialize", new Type[1] { typeof(ZPackage) }, null, typeFromHandle.GetMethod("AfterZdoDeserialize", BindingFlags.Static | BindingFlags.NonPublic));
			try
			{
				ZNet.WorldSaveStarted = (Action)Delegate.Combine(ZNet.WorldSaveStarted, new Action(OnWorldSaveStarted));
				ZNet.WorldSaveFinished = (Action)Delegate.Combine(ZNet.WorldSaveFinished, new Action(OnWorldSaveFinished));
			}
			catch (Exception ex)
			{
				list.Add("ZNet.WorldSaveStarted");
				GuildTelemetryPlugin.Log.LogWarning((object)("GuildTelemetry: save events unavailable: " + ex.Message));
			}
			return list;
		}

		public static void Uninstall()
		{
			ZNet.WorldSaveStarted = (Action)Delegate.Remove(ZNet.WorldSaveStarted, new Action(OnWorldSaveStarted));
			ZNet.WorldSaveFinished = (Action)Delegate.Remove(ZNet.WorldSaveFinished, new Action(OnWorldSaveFinished));
		}

		public static void EnsureDestroyHook()
		{
			if (!destroyHooked && ZDOMan.instance != null)
			{
				ZDOMan instance = ZDOMan.instance;
				instance.m_onZDODestroyed = (Action<ZDO>)Delegate.Combine(instance.m_onZDODestroyed, new Action<ZDO>(OnZdoDestroyed));
				destroyHooked = true;
			}
		}

		private static void Patch(Harmony harmony, List<string> missing, Type type, string name, Type[] parameters, MethodInfo? prefix, MethodInfo? postfix)
		{
			//IL_0055: Unknown result type (might be due to invalid IL or missing references)
			//IL_0069: Unknown result type (might be due to invalid IL or missing references)
			string text = type.Name + "." + name;
			try
			{
				MethodInfo methodInfo = AccessTools.Method(type, name, parameters, (Type[])null);
				if (methodInfo == null)
				{
					missing.Add(text);
					GuildTelemetryPlugin.Log.LogWarning((object)("GuildTelemetry: hook target missing: " + text));
				}
				else
				{
					harmony.Patch((MethodBase)methodInfo, (!(prefix != null)) ? ((HarmonyMethod)null) : new HarmonyMethod(prefix), (!(postfix != null)) ? ((HarmonyMethod)null) : new HarmonyMethod(postfix), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				}
			}
			catch (Exception ex)
			{
				missing.Add(text);
				GuildTelemetryPlugin.Log.LogWarning((object)("GuildTelemetry: hook failed for " + text + ": " + ex.Message));
			}
		}

		private static void Guard(string hook, Action action)
		{
			try
			{
				if (telemetry != null)
				{
					action();
				}
			}
			catch (Exception ex)
			{
				GuildTelemetryPlugin.Log.LogWarning((object)("GuildTelemetry: " + hook + " failed: " + ex));
			}
		}

		private static void AfterPeerInfo(ZNet __instance, ZRpc rpc)
		{
			Guard("RPC_PeerInfo", delegate
			{
				if (__instance.IsServer())
				{
					ZNetPeer peer = __instance.GetPeer(rpc);
					if (peer != null && peer.IsReady() && telemetry.Peers.Get(peer.m_uid) == null)
					{
						telemetry.PeerJoined(peer);
					}
				}
			});
		}

		private static void BeforeCharacterId(ZNet __instance, ZRpc rpc)
		{
			Guard("RPC_CharacterID prefix", delegate
			{
				//IL_001d: Unknown result type (might be due to invalid IL or missing references)
				//IL_0015: Unknown result type (might be due to invalid IL or missing references)
				//IL_0022: Unknown result type (might be due to invalid IL or missing references)
				characterBeforeRpc = __instance.GetPeer(rpc)?.m_characterID ?? ZDOID.None;
			});
		}

		private static void AfterCharacterId(ZNet __instance, ZRpc rpc, ZDOID characterID)
		{
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			Guard("RPC_CharacterID", delegate
			{
				//IL_003e: 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_007a: 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)
				ZNetPeer peer = __instance.GetPeer(rpc);
				if (peer != null)
				{
					PeerState peerState = telemetry.Peers.Get(peer.m_uid);
					if (peerState != null)
					{
						if (!((ZDOID)(ref characterID)).IsNone())
						{
							if (peerState.CharacterId != characterID)
							{
								telemetry.PeerSpawned(peerState, characterID);
							}
						}
						else if (!((ZDOID)(ref characterBeforeRpc)).IsNone())
						{
							telemetry.PeerDied(peerState);
							peerState.CharacterId = ZDOID.None;
						}
					}
				}
			});
		}

		private static void BeforeRpcDisconnect(ZNet __instance, ZRpc rpc)
		{
			Guard("RPC_Disconnect", delegate
			{
				ZNetPeer peer = __instance.GetPeer(rpc);
				PeerState peerState = ((peer != null) ? telemetry.Peers.Get(peer.m_uid) : null);
				if (peerState != null)
				{
					peerState.Graceful = true;
				}
			});
		}

		private static void BeforeKick(ZNetPeer peer)
		{
			Guard("InternalKick", delegate
			{
				PeerState peerState = ((peer != null) ? telemetry.Peers.Get(peer.m_uid) : null);
				if (peerState != null)
				{
					peerState.Kicked = true;
				}
			});
		}

		private static void BeforeDisconnect(ZNetPeer peer)
		{
			Guard("Disconnect", delegate
			{
				PeerState peerState = ((peer != null) ? telemetry.Peers.Get(peer.m_uid) : null);
				if (peerState != null)
				{
					string reason = (peerState.Kicked ? "kicked" : (peerState.Graceful ? "disconnect" : "timeout"));
					telemetry.PeerLeft(peerState, reason);
				}
			});
		}

		private static void BeforeShutdown()
		{
			Guard("Shutdown", delegate
			{
				telemetry.Stopping();
			});
		}

		private static void OnWorldSaveStarted()
		{
			Guard("WorldSaveStarted", delegate
			{
				telemetry.SaveStarted();
			});
		}

		private static void OnWorldSaveFinished()
		{
			Guard("WorldSaveFinished", delegate
			{
				telemetry.SaveFinished();
			});
		}

		private static void BeforeSetGlobalKey(long sender, string name)
		{
			Guard("RPC_SetGlobalKey", delegate
			{
				telemetry.GlobalKey(sender, name);
			});
		}

		private static void BeforeSetRandomEvent(RandEventSystem __instance, RandomEvent ev, Vector3 pos)
		{
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			Guard("SetRandomEvent", delegate
			{
				//IL_0031: Unknown result type (might be due to invalid IL or missing references)
				if (!((Object)(object)ZNet.instance == (Object)null) && ZNet.instance.IsServer())
				{
					telemetry.RaidChanged(__instance.m_randomEvent, ev, pos);
				}
			});
		}

		private static void BeforeRoutedRpc(ZPackage pkg)
		{
			Guard("RPC_RoutedRPC", delegate
			{
				//IL_000c: Unknown result type (might be due to invalid IL or missing references)
				//IL_0011: Unknown result type (might be due to invalid IL or missing references)
				//IL_002e: Expected O, but got Unknown
				int pos = pkg.GetPos();
				RoutedRPCData val = new RoutedRPCData();
				val.Deserialize(pkg);
				pkg.SetPos(pos);
				HandleRouted(val);
			});
		}

		private static void BeforeInvokeRoutedRpc(long targetPeerID, ZDOID targetZDO, string methodName, object[] parameters)
		{
			Guard("InvokeRoutedRPC", delegate
			{
				//IL_003c: Unknown result type (might be due to invalid IL or missing references)
				//IL_002d: Unknown result type (might be due to invalid IL or missing references)
				//IL_0041: Unknown result type (might be due to invalid IL or missing references)
				//IL_00bf: Unknown result type (might be due to invalid IL or missing references)
				if (!(methodName != "ChatMessage") && parameters.Length >= 4)
				{
					Vector3 value = (Vector3)((parameters[0] is Vector3) ? ((Vector3)parameters[0]) : Vector3.zero);
					int num = ((parameters[1] is int) ? ((int)parameters[1]) : 0);
					object obj = parameters[2];
					UserInfo val = (UserInfo)((obj is UserInfo) ? obj : null);
					string text = (parameters[3] as string) ?? string.Empty;
					string text2 = num switch
					{
						2 => "shout", 
						3 => "ping", 
						_ => "say", 
					};
					telemetry.Chat(0L, text2, (text2 == "ping") ? null : text, value, (val != null && ((PlatformUserID)(ref val.UserId)).IsValid) ? ((object)Unsafe.As<PlatformUserID, PlatformUserID>(ref val.UserId)/*cast due to .constrained prefix*/).ToString() : null);
				}
			});
		}

		private static void HandleRouted(RoutedRPCData data)
		{
			//IL_0028: 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_0063: Unknown result type (might be due to invalid IL or missing references)
			//IL_006a: Expected O, but got Unknown
			//IL_00b4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cc: 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_00ef: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f6: Expected O, but got Unknown
			//IL_0184: Unknown result type (might be due to invalid IL or missing references)
			//IL_018b: Expected O, but got Unknown
			//IL_0146: Unknown result type (might be due to invalid IL or missing references)
			int methodHash = data.m_methodHash;
			ZPackage parameters = data.m_parameters;
			parameters.SetPos(0);
			if (methodHash == onDeathHash)
			{
				PeerState peerState = telemetry.Peers.ByCharacter(data.m_targetZDO);
				if (peerState != null)
				{
					telemetry.PeerDied(peerState);
				}
			}
			else if (methodHash == damageHash)
			{
				PeerState peerState2 = telemetry.Peers.ByCharacter(data.m_targetZDO);
				if (peerState2 != null)
				{
					HitData val = new HitData();
					val.Deserialize(ref parameters);
					telemetry.RecordHit(peerState2, val);
				}
			}
			else if (methodHash == showMessageHash)
			{
				parameters.ReadInt();
				string message = parameters.ReadString();
				telemetry.BossAlert(data.m_senderPeerID, message);
			}
			else if (methodHash == spawnBossHash)
			{
				Vector3 point = parameters.ReadVector3();
				telemetry.BossSummonRpc(data.m_senderPeerID, data.m_targetZDO, point);
			}
			else if (methodHash == chatMessageHash)
			{
				Vector3 value = parameters.ReadVector3();
				int num = parameters.ReadInt();
				UserInfo val2 = new UserInfo();
				val2.Deserialize(ref parameters);
				string text = parameters.ReadString();
				string text2 = num switch
				{
					2 => "shout", 
					3 => "ping", 
					_ => "say", 
				};
				telemetry.Chat(data.m_senderPeerID, text2, (text2 == "ping") ? null : text, value, ((PlatformUserID)(ref val2.UserId)).IsValid ? ((object)Unsafe.As<PlatformUserID, PlatformUserID>(ref val2.UserId)/*cast due to .constrained prefix*/).ToString() : null);
			}
			else if (methodHash == sayHash)
			{
				int num2 = parameters.ReadInt();
				UserInfo val3 = new UserInfo();
				val3.Deserialize(ref parameters);
				string text3 = parameters.ReadString();
				string kind = ((num2 == 2) ? "shout" : "say");
				telemetry.Chat(data.m_senderPeerID, kind, text3, null, ((PlatformUserID)(ref val3.UserId)).IsValid ? ((object)Unsafe.As<PlatformUserID, PlatformUserID>(ref val3.UserId)/*cast due to .constrained prefix*/).ToString() : null);
			}
			else if (methodHash == registerKillHash)
			{
				string enemyName = parameters.ReadString();
				telemetry.KillCredited(data.m_targetPeerID, enemyName);
			}
		}

		private static void AfterCreateNewZdo(ZDOID uid, int prefabHashIn)
		{
			//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)
			Guard("CreateNewZDO", delegate
			{
				//IL_0006: Unknown result type (might be due to invalid IL or missing references)
				telemetry.ZdoCreated(uid, prefabHashIn);
			});
		}

		private static void AfterZdoDeserialize(ZDO __instance)
		{
			Guard("ZDO.Deserialize", delegate
			{
				telemetry.ZdoDeserialized(__instance);
			});
		}

		private static void OnZdoDestroyed(ZDO zdo)
		{
			Guard("ZDO destroyed", delegate
			{
				telemetry.ZdoDestroyed(zdo);
			});
		}
	}
	internal static class MapRenderer
	{
		public const int Size = 2048;

		public const float RadiusMetres = 10000f;

		public const float WaterLevel = 30f;

		private const int UploadAttempts = 10;

		private const int UploadRetryMs = 30000;

		private static readonly byte[][] Palette = new byte[10][]
		{
			new byte[3] { 39, 75, 138 },
			new byte[3] { 109, 184, 107 },
			new byte[3] { 47, 107, 42 },
			new byte[3] { 110, 90, 85 },
			new byte[3] { 232, 232, 234 },
			new byte[3] { 217, 200, 106 },
			new byte[3] { 76, 74, 90 },
			new byte[3] { 176, 52, 44 },
			new byte[3] { 223, 233, 240 },
			new byte[3]
		};

		private static int started;

		public static string MarkerPath(string directory, long worldUid)
		{
			return Path.Combine(directory, "map-" + worldUid.ToString(CultureInfo.InvariantCulture) + ".uploaded");
		}

		public static void Start(ManualLogSource log, string directory, long worldUid, string ingestUrl, string secret)
		{
			if (Interlocked.Exchange(ref started, 1) == 1)
			{
				return;
			}
			if (File.Exists(MarkerPath(directory, worldUid)))
			{
				log.LogInfo((object)("GuildTelemetry: map for world " + worldUid + " was uploaded earlier, skipping the render"));
				return;
			}
			WorldGenerator generator = WorldGenerator.instance;
			if (generator == null)
			{
				log.LogWarning((object)"GuildTelemetry: no world generator, the map is not rendered");
				return;
			}
			Thread thread = new Thread((ThreadStart)delegate
			{
				Run(log, generator, directory, worldUid, ingestUrl, secret);
			});
			thread.Name = "GuildTelemetry map";
			thread.IsBackground = true;
			thread.Priority = ThreadPriority.BelowNormal;
			thread.Start();
		}

		private static byte PaletteIndex(Biome biome)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0003: Invalid comparison between Unknown and I4
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			//IL_002b: Invalid comparison between Unknown and I4
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_001d: Expected I4, but got Unknown
			//IL_0039: Unknown result type (might be due to invalid IL or missing references)
			//IL_003f: Invalid comparison between Unknown and I4
			//IL_002d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0030: Invalid comparison between Unknown and I4
			//IL_001d: Unknown result type (might be due to invalid IL or missing references)
			//IL_001f: Invalid comparison between Unknown and I4
			//IL_0041: Unknown result type (might be due to invalid IL or missing references)
			//IL_0047: Invalid comparison between Unknown and I4
			//IL_0032: Unknown result type (might be due to invalid IL or missing references)
			//IL_0035: Invalid comparison between Unknown and I4
			//IL_0021: Unknown result type (might be due to invalid IL or missing references)
			//IL_0024: Invalid comparison between Unknown and I4
			if ((int)biome <= 16)
			{
				switch (biome - 1)
				{
				default:
					if ((int)biome != 8)
					{
						if ((int)biome != 16)
						{
							break;
						}
						return 5;
					}
					return 2;
				case 0:
					return 1;
				case 1:
					return 3;
				case 3:
					return 4;
				case 2:
					break;
				}
			}
			else if ((int)biome <= 64)
			{
				if ((int)biome == 32)
				{
					return 7;
				}
				if ((int)biome == 64)
				{
					return 8;
				}
			}
			else
			{
				if ((int)biome == 256)
				{
					return 0;
				}
				if ((int)biome == 512)
				{
					return 6;
				}
			}
			return 9;
		}

		public static byte[] Render(WorldGenerator generator, Action<int>? progress)
		{
			//IL_004b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0050: Unknown result type (might be due to invalid IL or missing references)
			//IL_0052: Unknown result type (might be due to invalid IL or missing references)
			//IL_0059: Invalid comparison between Unknown and I4
			//IL_0061: Unknown result type (might be due to invalid IL or missing references)
			//IL_0076: Unknown result type (might be due to invalid IL or missing references)
			byte[] array = new byte[4194304];
			float num = 9.765625f;
			Color val = default(Color);
			for (int i = 0; i < 2048; i++)
			{
				float num2 = 10000f - ((float)i + 0.5f) * num;
				for (int j = 0; j < 2048; j++)
				{
					float num3 = -10000f + ((float)j + 0.5f) * num;
					Biome biome = generator.GetBiome(num3, num2, 0.02f, false);
					byte b = (byte)(((int)biome != 256) ? ((!(generator.GetBiomeHeight(biome, num3, num2, ref val, false, true) < 30f)) ? PaletteIndex(biome) : 0) : 0);
					array[i * 2048 + j] = b;
				}
				if (progress != null && (i + 1) % 512 == 0)
				{
					progress(i + 1);
				}
			}
			return array;
		}

		private static void Run(ManualLogSource log, WorldGenerator generator, string directory, long worldUid, string ingestUrl, string secret)
		{
			try
			{
				Stopwatch stopwatch = Stopwatch.StartNew();
				byte[] pixels = Render(generator, delegate(int rows)
				{
					log.LogInfo((object)("GuildTelemetry: map render " + rows + "/" + 2048 + " rows"));
				});
				byte[] array = PngEncoder.EncodeIndexed(2048, 2048, pixels, Palette);
				log.LogInfo((object)("GuildTelemetry: map rendered in " + stopwatch.ElapsedMilliseconds / 1000 + " s, " + array.Length + " bytes"));
				Upload(log, array, directory, worldUid, ingestUrl, secret);
			}
			catch (Exception ex)
			{
				log.LogWarning((object)("GuildTelemetry: map rendering failed: " + ex));
			}
		}

		private static void Upload(ManualLogSource log, byte[] png, string directory, long worldUid, string ingestUrl, string secret)
		{
			string text = ingestUrl.TrimEnd(new char[1] { '/' });
			text += "/map";
			RequestSigner requestSigner = new RequestSigner(secret);
			HttpTransport httpTransport = new HttpTransport();
			string body = RequestSigner.Sha256Hex(png);
			for (int i = 1; i <= 10; i++)
			{
				long unixTimestamp = (long)(DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)).TotalSeconds;
				Dictionary<string, string> headers = new Dictionary<string, string>
				{
					{
						"X-Telemetry-Timestamp",
						unixTimestamp.ToString(CultureInfo.InvariantCulture)
					},
					{
						"X-Telemetry-Signature",
						requestSigner.Sign(unixTimestamp, body)
					},
					{
						"X-World-Uid",
						worldUid.ToString(CultureInfo.InvariantCulture)
					},
					{
						"X-Map-Size",
						2048.ToString(CultureInfo.InvariantCulture)
					},
					{
						"X-Map-Radius",
						10000f.ToString(CultureInfo.InvariantCulture)
					}
				};
				SendResult sendResult = httpTransport.Post(text, png, "image/png", headers, 120000);
				if (sendResult.StatusCode >= 200 && sendResult.StatusCode < 300)
				{
					Directory.CreateDirectory(directory);
					File.WriteAllText(MarkerPath(directory, worldUid), JsonWriter.Timestamp(DateTime.UtcNow) + "\n");
					log.LogInfo((object)("GuildTelemetry: map uploaded for world " + worldUid));
					break;
				}
				if (sendResult.StatusCode == 401 || sendResult.StatusCode == 413 || sendResult.StatusCode == 422)
				{
					log.LogWarning((object)("GuildTelemetry: map upload refused (HTTP " + sendResult.StatusCode + "): " + sendResult.Error));
					break;
				}
				log.LogWarning((object)("GuildTelemetry: map upload attempt " + i + " failed (" + (sendResult.Error ?? ("HTTP " + sendResult.StatusCode)) + ")"));
				Thread.Sleep(30000);
			}
		}
	}
	internal sealed class ObservedHit
	{
		public HitType HitType { get; }

		public ZDOID Attacker { get; }

		public DateTime AtUtc { get; }

		public ObservedHit(HitType hitType, ZDOID attacker, DateTime atUtc)
		{
			//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_000e: Unknown result type (might be due to invalid IL or missing references)
			//IL_000f: Unknown result type (might be due to invalid IL or missing references)
			HitType = hitType;
			Attacker = attacker;
			AtUtc = atUtc;
		}
	}
	internal sealed class PeerState
	{
		public ZNetPeer Peer { get; }

		public long Uid { get; }

		public string Name { get; }

		public string? PlayfabId { get; }

		public string HostName { get; }

		public string PlatformUserId { get; }

		public string DisplayId { get; }

		public string Platform { get; }

		public DateTime JoinedAtUtc { get; }

		public ZDOID CharacterId { get; set; }

		public long PlayerId => Peer.m_playerID;

		public long ProfileId
		{
			get
			{
				//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_001d: Unknown result type (might be due to invalid IL or missing references)
				ZDOID characterId = CharacterId;
				ZDO val = ((((ZDOID)(ref characterId)).IsNone() || ZDOMan.instance == null) ? null : ZDOMan.instance.GetZDO(CharacterId));
				if (val == null)
				{
					return 0L;
				}
				return val.GetLong(ZDOVars.s_playerID, 0L);
			}
		}

		public bool Spawned { get; set; }

		public string LastBiome { get; set; } = "None";

		public bool HasLastPosition { get; set; }

		public Vector3 LastPosition { get; set; }

		public double DistanceSinceHeartbeat { get; set; }

		public ObservedHit? LastHit { get; set; }

		public DateTime LastDeathUtc { get; set; } = DateTime.MinValue;

		public bool Graceful { get; set; }

		public bool Kicked { get; set; }

		public Vector3 Position => Peer.m_refPos;

		public double SessionSeconds => (DateTime.UtcNow - JoinedAtUtc).TotalSeconds;

		public PeerState(ZNetPeer peer, string platformUserId, string displayId, string platform)
		{
			//IL_00a2: Unknown result type (might be due to invalid IL or missing references)
			Peer = peer;
			Uid = peer.m_uid;
			Name = peer.m_playerName ?? string.Empty;
			PlayfabId = (string.IsNullOrEmpty(peer.m_playfabId) ? null : peer.m_playfabId);
			HostName = ((peer.m_socket != null) ? peer.m_socket.GetHostName() : string.Empty);
			PlatformUserId = platformUserId;
			DisplayId = displayId;
			Platform = platform;
			JoinedAtUtc = DateTime.UtcNow;
			CharacterId = ZDOID.None;
		}
	}
	internal sealed class PeerTracker
	{
		private readonly Dictionary<long, PeerState> peers = new Dictionary<long, PeerState>();

		public IEnumerable<PeerState> All => peers.Values;

		public int Count => peers.Count;

		public PeerState? Get(long uid)
		{
			if (!peers.TryGetValue(uid, out PeerState value))
			{
				return null;
			}
			return value;
		}

		public unsafe PeerState Register(ZNetPeer peer)
		{
			//IL_0032: Unknown result type (might be due to invalid IL or missing references)
			//IL_0037: Unknown result type (might be due to invalid IL or missing references)
			//IL_0072: Unknown result type (might be due to invalid IL or missing references)
			//IL_0073: Unknown result type (might be due to invalid IL or missing references)
			//IL_0078: Unknown result type (might be due to invalid IL or missing references)
			//IL_0098: Unknown result type (might be due to invalid IL or missing references)
			//IL_0099: Unknown result type (might be due to invalid IL or missing references)
			//IL_009e: Unknown result type (might be due to invalid IL or missing references)
			PeerState peerState = Get(peer.m_uid);
			if (peerState != null)
			{
				return peerState;
			}
			PlatformUserID val = ResolvePlatformId((peer.m_socket != null) ? peer.m_socket.GetHostName() : string.Empty, peer.m_uid);
			string text = (((PlatformUserID)(ref val)).IsValid ? ((object)(*(PlatformUserID*)(&val))/*cast due to .constrained prefix*/).ToString() : ("Unknown_" + peer.m_uid));
			string displayId = (((PlatformUserID)(ref val)).IsValid ? ((object)PlatformUserID.FilterPlatformUserID(val)/*cast due to .constrained prefix*/).ToString() : text);
			object obj;
			if (!((PlatformUserID)(ref val)).IsValid)
			{
				obj = "Steam";
			}
			else
			{
				Platform platform = val.m_platform;
				obj = ((object)(*(Platform*)(&platform))/*cast due to .constrained prefix*/).ToString();
			}
			string platform2 = (string)obj;
			PeerState peerState2 = new PeerState(peer, text, displayId, platform2);
			peers[peer.m_uid] = peerState2;
			return peerState2;
		}

		public void Remove(long uid)
		{
			peers.Remove(uid);
		}

		public PeerState? ByCharacter(ZDOID characterId)
		{
			//IL_0027: 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)
			if (((ZDOID)(ref characterId)).IsNone())
			{
				return null;
			}
			foreach (PeerState value in peers.Values)
			{
				if (value.CharacterId == characterId)
				{
					return value;
				}
			}
			return null;
		}

		public PeerState? ByProfileId(long profileId)
		{
			if (profileId == 0L)
			{
				return null;
			}
			foreach (PeerState value in peers.Values)
			{
				if (value.ProfileId == profileId)
				{
					return value;
				}
			}
			return null;
		}

		public List<string> NearbyIds(Vector3 point, float radius)
		{
			//IL_0022: 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_0032: Unknown result type (might be due to invalid IL or missing references)
			//IL_0037: Unknown result type (might be due to invalid IL or missing references)
			List<string> list = new List<string>();
			foreach (PeerState value in peers.Values)
			{
				ZDOID characterId = value.CharacterId;
				if (!((ZDOID)(ref characterId)).IsNone() && DistanceXZ(value.Position, point) <= radius)
				{
					list.Add(value.PlatformUserId);
				}
			}
			return list;
		}

		public static float DistanceXZ(Vector3 a, Vector3 b)
		{
			//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_000d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			float num = a.x - b.x;
			float num2 = a.z - b.z;
			return Mathf.Sqrt(num * num + num2 * num2);
		}

		private static PlatformUserID ResolvePlatformId(string hostName, long uid)
		{
			//IL_000e: 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_001a: 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_004f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0043: 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)
			//IL_0039: Unknown result type (might be due to invalid IL or missing references)
			if (string.IsNullOrEmpty(hostName))
			{
				return PlatformUserID.None;
			}
			if ((int)ZNet.m_onlineBackend == 0)
			{
				return new PlatformUserID(new Platform("Steam"), hostName);
			}
			PlatformUserID result = default(PlatformUserID);
			if (PlatformUserID.TryParse(hostName, ref result) && ((PlatformUserID)(ref result)).IsValid)
			{
				return result;
			}
			if (uid != 0L)
			{
				return new PlatformUserID(new Platform("Steam"), hostName);
			}
			return PlatformUserID.None;
		}
	}
	internal sealed class PluginConfig
	{
		public ConfigEntry<string> Url { get; }

		public ConfigEntry<string> Secret { get; }

		public ConfigEntry<int> HeartbeatSeconds { get; }

		public ConfigEntry<int> FlushSeconds { get; }

		public ConfigEntry<int> BiomeSampleSeconds { get; }

		public ConfigEntry<int> PositionSampleSeconds { get; }

		public ConfigEntry<int> JournalMaxMB { get; }

		public ConfigEntry<bool> AllowInsecureHttp { get; }

		public ConfigEntry<bool> LogEvents { get; }

		public ConfigEntry<bool> MapEnabled { get; }

		public ConfigEntry<bool> CatalogEnabled { get; }

		public PluginConfig(ConfigFile file)
		{
			Url = file.Bind<string>("General", "Url", "", "Ingest endpoint of the guild site, for example https://guild.example.org/api/ingest");
			Secret = file.Bind<string>("General", "Secret", "", "Shared telemetry secret (TELEMETRY_SECRET on the site)");
			HeartbeatSeconds = file.Bind<int>("General", "HeartbeatSeconds", 60, "Seconds between server.heartbeat events");
			FlushSeconds = file.Bind<int>("General", "FlushSeconds", 2, "Seconds between batch flushes");
			BiomeSampleSeconds = file.Bind<int>("General", "BiomeSampleSeconds", 5, "Seconds between biome and distance samples");
			PositionSampleSeconds = file.Bind<int>("General", "PositionSampleSeconds", 20, "Seconds between player.position events");
			JournalMaxMB = file.Bind<int>("General", "JournalMaxMB", 50, "Hard cap of the on-disk journal in megabytes");
			AllowInsecureHttp = file.Bind<bool>("General", "AllowInsecureHttp", false, "Allow an http:// Url (local rig only)");
			LogEvents = file.Bind<bool>("General", "LogEvents", false, "Log every emitted event to the BepInEx log");
			MapEnabled = file.Bind<bool>("General", "MapEnabled", true, "Render the world biome map once per world and upload it to the site");
			CatalogEnabled = file.Bind<bool>("General", "CatalogEnabled", true, "Send the comfort pieces of the running game to the site at every server start");
		}

		public string? Validate()
		{
			string text = Url.Value.Trim();
			if (text.Length == 0)
			{
				return "Url is empty";
			}
			if (text.StartsWith("http://", StringComparison.OrdinalIgnoreCase) && !AllowInsecureHttp.Value)
			{
				return "Url uses http:// but AllowInsecureHttp is false";
			}
			if (!text.StartsWith("http://", StringComparison.OrdinalIgnoreCase) && !text.StartsWith("https://", StringComparison.OrdinalIgnoreCase))
			{
				return "Url must start with https://";
			}
			if (Secret.Value.Trim().Length == 0)
			{
				return "Secret is empty";
			}
			return null;
		}
	}
	internal sealed class PrefabInfo
	{
		public string Name { get; }

		public bool IsPiece { get; set; }

		public bool IsCharacter { get; set; }

		public bool IsPlayer { get; set; }

		public bool IsBoss { get; set; }

		public bool IsRagdoll { get; set; }

		public bool IsAltar { get; set; }

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

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

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

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

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

		public PrefabInfo(string name)
		{
			Name = name;
		}
	}
	internal sealed class PrefabCatalog
	{
		private readonly Dictionary<int, PrefabInfo> byHash = new Dictionary<int, PrefabInfo>();

		private readonly Dictionary<string, List<int>> hashesByDefeatKey = new Dictionary<string, List<int>>(StringComparer.OrdinalIgnoreCase);

		private readonly Dictionary<string, int> hashByAlertMessage = new Dictionary<string, int>(StringComparer.Ordinal);

		private readonly Dictionary<string, string> prefabByCharacterName = new Dictionary<string, string>(StringComparer.Ordinal);

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

		private bool built;

		public bool Ready => built;

		public int Count => byHash.Count;

		public void EnsureBuilt()
		{
			if (built || (Object)(object)ZNetScene.instance == (Object)null)
			{
				return;
			}
			foreach (GameObject prefab in ZNetScene.instance.m_prefabs)
			{
				if (!((Object)(object)prefab == (Object)null))
				{
					Classify(prefab);
				}
			}
			foreach (KeyValuePair<int, string> item in creatureByRagdollHash)
			{
				PrefabInfo prefabInfo = Get(item.Key);
				if (prefabInfo != null)
				{
					prefabInfo.CreatureForRagdoll = item.Value;
				}
			}
			built = true;
		}

		public PrefabInfo? Get(int hash)
		{
			if (byHash.TryGetValue(hash, out PrefabInfo value))
			{
				return value;
			}
			if ((Object)(object)ZNetScene.instance == (Object)null)
			{
				return null;
			}
			GameObject prefab = ZNetScene.instance.GetPrefab(hash);
			if ((Object)(object)prefab == (Object)null)
			{
				return null;
			}
			return Classify(prefab);
		}

		public string NameOf(int hash)
		{
			PrefabInfo prefabInfo = Get(hash);
			if (prefabInfo == null)
			{
				return hash.ToString();
			}
			return prefabInfo.Name;
		}

		public List<int> HashesForDefeatKey(string key)
		{
			if (!hashesByDefeatKey.TryGetValue(key, out List<int> value))
			{
				return new List<int>();
			}
			return value;
		}

		public bool TryPrefabForAlert(string message, out int hash)
		{
			return hashByAlertMessage.TryGetValue(message, out hash);
		}

		public string PrefabForCharacterName(string characterName)
		{
			if (!prefabByCharacterName.TryGetValue(characterName, out string value))
			{
				return characterName;
			}
			return value;
		}

		private PrefabInfo Classify(GameObject prefab)
		{
			int stableHashCode = StringExtensionMethods.GetStableHashCode(((Object)prefab).name);
			PrefabInfo prefabInfo = new PrefabInfo(((Object)prefab).name);
			prefabInfo.IsPiece = (Object)(object)prefab.GetComponent<Piece>() != (Object)null;
			prefabInfo.IsRagdoll = (Object)(object)prefab.GetComponent<Ragdoll>() != (Object)null;
			Character component = prefab.GetComponent<Character>();
			if ((Object)(object)component != (Object)null)
			{
				prefabInfo.IsCharacter = true;
				prefabInfo.IsPlayer = component is Player;
				prefabInfo.IsBoss = component.m_boss;
				prefabInfo.CharacterName = component.m_name ?? string.Empty;
				prefabInfo.DefeatKey = component.m_defeatSetGlobalKey ?? string.Empty;
				BaseAI component2 = prefab.GetComponent<BaseAI>();
				if ((Object)(object)component2 != (Object)null)
				{
					prefabInfo.AlertMessage = component2.m_alertedMessage ?? string.Empty;
				}
				if (prefabInfo.CharacterName.Length > 0 && !prefabByCharacterName.ContainsKey(prefabInfo.CharacterName))
				{
					prefabByCharacterName[prefabInfo.CharacterName] = ((Object)prefab).name;
				}
				if (prefabInfo.DefeatKey.Length > 0)
				{
					if (!hashesByDefeatKey.TryGetValue(prefabInfo.DefeatKey, out List<int> value))
					{
						value = new List<int>();
						hashesByDefeatKey[prefabInfo.DefeatKey] = value;
					}
					value.Add(stableHashCode);
				}
				if (prefabInfo.IsBoss && prefabInfo.AlertMessage.Length > 0)
				{
					hashByAlertMessage[prefabInfo.AlertMessage] = stableHashCode;
				}
				if (component.m_deathEffects != null && component.m_deathEffects.m_effectPrefabs != null)
				{
					EffectData[] effectPrefabs = component.m_deathEffects.m_effectPrefabs;
					foreach (EffectData val in effectPrefabs)
					{
						if (val != null && (Object)(object)val.m_prefab != (Object)null && (Object)(object)val.m_prefab.GetComponent<Ragdoll>() != (Object)null)
						{
							creatureByRagdollHash[StringExtensionMethods.GetStableHashCode(((Object)val.m_prefab).name)] = ((Object)prefab).name;
						}
					}
				}
			}
			OfferingBowl component3 = prefab.GetComponent<OfferingBowl>();
			if ((Object)(object)component3 != (Object)null && (Object)(object)component3.m_bossPrefab != (Object)null)
			{
				prefabInfo.IsAltar = true;
				prefabInfo.BossPrefabName = ((Object)component3.m_bossPrefab).name;
			}
			if (creatureByRagdollHash.TryGetValue(stableHashCode, out string value2))
			{
				prefabInfo.CreatureForRagdoll = value2;
			}
			byHash[stableHashCode] = prefabInfo;
			return prefabInfo;
		}
	}
	internal sealed class KillCredit
	{
		public string EnemyName { get; }

		public string PlatformUserId { get; }

		public DateTime AtUtc { get; }

		public KillCredit(string enemyName, string platformUserId, DateTime atUtc)
		{
			EnemyName = enemyName;
			PlatformUserId = platformUserId;
			AtUtc = atUtc;
		}
	}
	internal sealed class RecentDeath
	{
		public string Prefab { get; }

		public Vector3 Position { get; }

		public DateTime AtUtc { get; }

		public RecentDeath(string prefab, Vector3 position, DateTime atUtc)
		{
			//IL_000e: Unknown result type (might be due to invalid IL or missing references)
			//IL_000f: Unknown result type (might be due to invalid IL or missing references)
			Prefab = prefab;
			Position = position;
			AtUtc = atUtc;
		}
	}
	internal sealed class Telemetry
	{
		public const float BossNearbyRadius = 200f;

		public const float CreatureNearbyRadius = 100f;

		public const float TeleportThresholdMetres = 500f;

		private readonly ManualLogSource log;

		private readonly PluginConfig config;

		private TelemetryPipeline? pipeline;

		private readonly List<TelemetryEvent> buffered = new List<TelemetryEvent>();

		private readonly List<string> missingHooks;

		private long seq;

		private readonly float startedRealtime = Time.realtimeSinceStartup;

		private Stopwatch? saveWatch;

		private DateTime? lastSaveFinishedUtc;

		private readonly Dictionary<ZDOID, DateTime> engagedBosses = new Dictionary<ZDOID, DateTime>();

		private readonly Dictionary<string, DateTime> summonedByPrefab = new Dictionary<string, DateTime>(StringComparer.Ordinal);

		private readonly List<RecentDeath> recentDeaths = new List<RecentDeath>();

		private readonly List<KillCredit> recentCredits = new List<KillCredit>();

		private readonly Dictionary<string, DateTime> recentChat = new Dictionary<string, DateTime>(StringComparer.Ordinal);

		private readonly HashSet<ZDOID> pendingCreated = new HashSet<ZDOID>();

		private float lastActiveBosses;

		private string? currentRaid;

		private DateTime currentRaidStartedUtc;

		public string RunId { get; } = Guid.NewGuid().ToString();

		public PeerTracker Peers { get; } = new PeerTracker();

		public PrefabCatalog Catalog { get; } = new PrefabCatalog();

		public bool Started { get; private set; }

		public TelemetryPipeline? Pipeline => pipeline;

		public int QueueDepth
		{
			get
			{
				if (pipeline == null)
				{
					return buffered.Count;
				}
				return pipeline.QueueDepth;
			}
		}

		public long DroppedEvents
		{
			get
			{
				if (pipeline == null)
				{
					return 0L;
				}
				return pipeline.DroppedEvents;
			}
		}

		public double UptimeSeconds => Time.realtimeSinceStartup - startedRealtime;

		public Telemetry(ManualLogSource log, PluginConfig config, List<string> missingHooks)
		{
			this.log = log;
			this.config = config;
			this.missingHooks = missingHooks;
		}

		public void AttachPipeline(TelemetryPipeline started)
		{
			pipeline = started;
			foreach (TelemetryEvent item in buffered)
			{
				started.Enqueue(item);
			}
			buffered.Clear();
		}

		public int WorldDay()
		{
			if ((Object)(object)EnvMan.instance != (Object)null)
			{
				return EnvMan.instance.GetDay();
			}
			if (!((Object)(object)ZNet.instance != (Object)null))
			{
				return 0;
			}
			return (int)(ZNet.instance.GetTimeSeconds() / 1800.0);
		}

		public static string BiomeAt(Vector3 position)
		{
			//IL_0012: Unknown result type (might be due to invalid IL or missing references)
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			//IL_0018: Unknown result type (might be due to invalid IL or missing references)
			if (WorldGenerator.instance == null)
			{
				return "None";
			}
			return ((object)WorldGenerator.instance.GetBiome(position)/*cast due to .constrained prefix*/).ToString();
		}

		public void Emit(string type, string dataJson)
		{
			TelemetryEvent telemetryEvent = new TelemetryEvent(Guid.NewGuid().ToString(), Interlocked.Increment(ref seq), RunId, DateTime.UtcNow, type, WorldDay(), dataJson);
			if (pipeline == null)
			{
				if (buffered.Count < 1000)
				{
					buffered.Add(telemetryEvent);
				}
			}
			else if (!pipeline.Enqueue(telemetryEvent))
			{
				log.LogWarning((object)("GuildTelemetry: queue full, dropped " + type));
			}
			if (config.LogEvents.Value)
			{
				log.LogInfo((object)("GuildTelemetry: " + type + " " + dataJson));
			}
		}

		private static JsonWriter Position(JsonWriter writer, Vector3 position)
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_001d: Unknown result type (might be due to invalid IL or missing references)
			return writer.Property("x", Math.Round(position.x, 1)).Property("z", Math.Round(position.z, 1));
		}

		private CreatorAccounts ScanCreators()
		{
			try
			{
				int scanned;
				long elapsedMs;
				CreatorAccounts creatorAccounts = Creators.Scan(out scanned, out elapsedMs);
				log.LogInfo((object)("GuildTelemetry: found " + creatorAccounts.Count + " piece creators among " + scanned + " objects in " + elapsedMs + " ms"));
				return creatorAccounts;
			}
			catch (Exception ex)
			{
				log.LogWarning((object)("GuildTelemetry: the piece creator scan failed: " + ex.Message));
				return new CreatorAccounts();
			}
		}

		private string? BuilderAccount(ZDO zdo, long creator)
		{
			if (creator == 0L)
			{
				return null;
			}
			string text = Creators.AccountOf(zdo, Creators.History());
			if (text != null)
			{
				return text;
			}
			return Peers.ByProfileId(creator)?.PlatformUserId;
		}

		public void ServerStarted()
		{
			//IL_003f: 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)
			if (!Started && !((Object)(object)ZNet.instance == (Object)null))
			{
				Started = true;
				Catalog.EnsureBuilt();
				CreatorAccounts creatorAccounts = ScanCreators();
				JsonWriter jsonWriter = new JsonWriter();
				jsonWriter.BeginObject().Property("game_version", ((object)Version.CurrentVersion/*cast due to .constrained prefix*/).ToString()).Property("network_version", GameVersions.NetworkVersion())
					.Property("plugin_version", "0.7.0")
					.Property("bepinex_version", typeof(Paths).Assembly.GetName().Version.ToString())
					.Property("unity_version", Application.unityVersion)
					.Property("world_name", ZNet.instance.GetWorldName() ?? string.Empty)
					.Property("world_uid", ZNet.instance.GetWorldUID())
					.Property("net_time", Math.Round(ZNet.instance.GetTimeSeconds(), 1))
					.Property("world_day", WorldDay())
					.StringArray("global_keys", ((Object)(object)ZoneSystem.instance != (Object)null) ? ZoneSystem.instance.GetGlobalKeys() : new List<string>())
					.StringArray("missing_hooks", missingHooks);
				creatorAccounts.Write(jsonWriter, "creators").EndObject();
				Emit("server.started", jsonWriter.ToString());
				log.LogInfo((object)("GuildTelemetry: server.started sent for world " + ZNet.instance.GetWorldName() + " with " + Catalog.Count + " classified prefabs"));
			}
		}

		public void Heartbeat()
		{
			//IL_00fc: Unknown result type (might be due to invalid IL or missing references)
			//IL_0101: Unknown result type (might be due to invalid IL or missing references)
			//IL_014e: Unknown result type (might be due to invalid IL or missing references)
			//IL_015c: Unknown result type (might be due to invalid IL or missing references)
			if (!Started || (Object)(object)ZNet.instance == (Object)null)
			{
				return;
			}
			JsonWriter jsonWriter = new JsonWriter();
			jsonWriter.BeginObject().Property("uptime_s", Math.Round(UptimeSeconds, 1)).Property("net_time", Math.Round(ZNet.instance.GetTimeSeconds(), 1))
				.Property("world_day", WorldDay())
				.Name("last_save_age_s");
			if (lastSaveFinishedUtc.HasValue)
			{
				jsonWriter.Value(Math.Round((DateTime.UtcNow - lastSaveFinishedUtc.Value).TotalSeconds, 1));
			}
			else
			{
				jsonWriter.Null();
			}
			jsonWriter.Property("queue_depth", QueueDepth).Property("dropped_events", DroppedEvents).Name("players")
				.BeginArray();
			foreach (PeerState item in Peers.All)
			{
				Vector3 position = item.Position;
				jsonWriter.BeginObject().Property("platform_user_id", item.PlatformUserId).Property("name", item.Name)
					.Property("character_id", item.PlayerId)
					.Property("biome", item.Spawned ? BiomeAt(position) : item.LastBiome);
				Position(jsonWriter, position).Property("distance_since_last_m", Math.Round(item.DistanceSinceHeartbeat, 1)).EndObject();
				item.DistanceSinceHeartbeat = 0.0;
			}
			jsonWriter.EndArray().EndObject();
			Emit("server.heartbeat", jsonWriter.ToString());
		}

		public void SampleBiomes()
		{
			//IL_0029: Unknown result type (might be due to invalid IL or missing references)
			//IL_002e: Unknown result type (might be due to invalid IL or missing references)
			//IL_005f: 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_0037: Unknown result type (might be due to invalid IL or missing references)
			//IL_0039: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c8: Unknown result type (might be due to invalid IL or missing references)
			foreach (PeerState item in Peers.All)
			{
				if (!item.Spawned)
				{
					continue;
				}
				Vector3 position = item.Position;
				if (item.HasLastPosition)
				{
					float num = PeerTracker.DistanceXZ(position, item.LastPosition);
					if (num < 500f)
					{
						item.DistanceSinceHeartbeat += num;
					}
				}
				item.LastPosition = position;
				item.HasLastPosition = true;
				string text = BiomeAt(position);
				if (text != item.LastBiome)
				{
					string lastBiome = item.LastBiome;
					item.LastBiome = text;
					JsonWriter jsonWriter = new JsonWriter();
					jsonWriter.BeginObject().Property("platform_user_id", item.PlatformUserId).Property("from", lastBiome)
						.Property("to", text);
					Position(jsonWriter, position).EndObject();
					Emit("player.biome_changed", jsonWriter.ToString());
				}
			}
		}

		public void SamplePositions()
		{
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			//IL_0047: Unknown result type (might be due to invalid IL or missing references)
			//IL_0052: Unknown result type (might be due to invalid IL or missing references)
			foreach (PeerState item in Peers.All)
			{
				if (item.Spawned)
				{
					Vector3 position = item.Position;
					JsonWriter jsonWriter = new JsonWriter();
					jsonWriter.BeginObject().Property("platform_user_id", item.PlatformUserId);
					Position(jsonWriter, position).Property("biome", BiomeAt(position)).EndObject();
					Emit("player.position", jsonWriter.ToString());
				}
			}
		}

		public void PeerJoined(ZNetPeer peer)
		{
			PeerState peerState = Peers.Register(peer);
			JsonWriter jsonWriter = new JsonWriter();
			jsonWriter.BeginObject().Property("platform_user_id", peerState.PlatformUserId).Property("display_id", peerState.DisplayId)
				.Property("platform", peerState.Platform)
				.Property("name", peerState.Name)
				.Property("peer_uid", peerState.Uid)
				.Property("playfab_id", peerState.PlayfabId)
				.EndObject();
			Emit("player.joined", jsonWriter.ToString());
			log.LogInfo((object)("GuildTelemetry: player joined " + peerState.Name + " (" + peerState.HostName + ") uid " + peerState.Uid));
		}

		public void PeerLeft(PeerState state, string reason)
		{
			JsonWriter jsonWriter = new JsonWriter();
			jsonWriter.BeginObject().Property("platform_user_id", state.PlatformUserId).Property("name", state.Name)
				.Property("session_s", Math.Round(state.SessionSeconds, 1))
				.Property("reason", reason)
				.EndObject();
			Emit("player.left", jsonWriter.ToString());
			Peers.Remove(state.Uid);
			log.LogInfo((object)("GuildTelemetry: player left " + state.Name + " (" + state.HostName + ") uid " + state.Uid + " reason " + reason));
		}

		public void PeerSpawned(PeerState state, ZDOID characterId)
		{
			//IL_0008: Unknown result type (might be due to invalid IL or missing references)
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			//IL_001b: Unknown result type (might be due to invalid IL or missing references)
			//IL_001c: 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_0081: Unknown result type (might be due to invalid IL or missing references)
			bool spawned = state.Spawned;
			state.CharacterId = characterId;
			state.Spawned = true;
			Vector3 position = state.Position;
			string value = (state.LastBiome = BiomeAt(position));
			state.LastPosition = position;
			state.HasLastPosition = true;
			JsonWriter jsonWriter = new JsonWriter();
			jsonWriter.BeginObject().Property("platform_user_id", state.PlatformUserId).Property("name", state.Name)
				.Property("character_id", state.PlayerId)
				.Property("respawn", spawned);
			Position(jsonWriter, position).Property("biome", value).EndObject();
			Emit("player.spawned", jsonWriter.ToString());
		}

		public void PeerDied(PeerState state)
		{
			//IL_002f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0034: Unknown result type (might be due to invalid IL or missing references)
			//IL_0073: 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_00c1: Unknown result type (might be due to invalid IL or missing references)
			//IL_012c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0131: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00da: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f2: Unknown result type (might be due to invalid IL or missing references)
			DateTime utcNow = DateTime.UtcNow;
			if ((utcNow - state.LastDeathUtc).TotalSeconds < 15.0)
			{
				return;
			}
			state.LastDeathUtc = utcNow;
			Vector3 position = state.Position;
			JsonWriter jsonWriter = new JsonWriter();
			jsonWriter.BeginObject().Property("platform_user_id", state.PlatformUserId).Property("name", state.Name)
				.Property("character_id", state.PlayerId);
			Position(jsonWriter, position).Property("biome", BiomeAt(position));
			ObservedHit lastHit = state.LastHit;
			if (lastHit != null && (utcNow - lastHit.AtUtc).TotalSeconds <= 10.0)
			{
				PeerState peerState = Peers.ByCharacter(lastHit.Attacker);
				string value = null;
				if (peerState == null)
				{
					ZDOID attacker = lastHit.Attacker;
					if (!((ZDOID)(ref attacker)).IsNone() && ZDOMan.instance != null)
					{
						ZDO zDO = ZDOMan.instance.GetZDO(lastHit.Attacker);
						if (zDO != null)
						{
							value = Catalog.NameOf(zDO.GetPrefab());
						}
					}
				}
				jsonWriter.Name("observed_cause").BeginObject().Property("hit_type", ((object)lastHit.HitType/*cast due to .constrained prefix*/).ToString())
					.Property("attacker_prefab", value)
					.Property("attacker_platform_user_id", peerState?.PlatformUserId)
					.Property("at", JsonWriter.Timestamp(lastHit.AtUtc))
					.EndObject();
			}
			else
			{
				jsonWriter.PropertyNull("observed_cause");
			}
			jsonWriter.EndObject();
			Emit("player.died", jsonWriter.ToString());
		}

		public void RecordHit(PeerState target, HitData hit)
		{
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			//IL_0008: Unknown result type (might be due to invalid IL or missing references)
			target.LastHit = new ObservedHit(hit.m_hitType, hit.m_attacker, DateTime.UtcNow);
		}

		public void SaveStarted()
		{
			saveWatch = Stopwatch.StartNew();
			Emit("world.save_started", "{}");
		}

		public void SaveFinished()
		{
			long value = ((saveWatch != null) ? saveWatch.ElapsedMilliseconds : 0);
			saveWatch = null;
			lastSaveFinishedUtc = DateTime.UtcNow;
			JsonWriter jsonWriter = new JsonWriter();
			jsonWriter.BeginObject().Property("duration_ms", value).EndObject();
			Emit("world.saved", jsonWriter.ToString());
		}

		public void Stopping()
		{
			List<PeerState> list = new List<PeerState>(Peers.All);
			foreach (PeerState item in list)
			{
				PeerLeft(item, "server_stop");
			}
			JsonWriter jsonWriter = new JsonWriter();
			jsonWriter.BeginObject().Property("uptime_s", Math.Round(UptimeSeconds, 1)).Property("online_count", list.Count)
				.EndObject();
			Emit("server.stopping", jsonWriter.ToString());
			if (pipeline != null)
			{
				pipeline.FlushSync(1500);
			}
		}

		public void GlobalKey(long sender, string line)
		{
			//IL_0144: Unknown result type (might be due to invalid IL or missing references)
			string text = default(string);
			GlobalKeys val = default(GlobalKeys);
			string keyValue = ZoneSystem.GetKeyValue(line.ToLowerInvariant(), ref text, ref val);
			bool flag = (Object)(object)ZoneSystem.instance != (Object)null && ZoneSystem.instance.GetGlobalKey(keyValue);
			PrefabInfo prefabInfo = CreatureForDefeatKey(keyValue);
			if (prefabInfo != null && prefabInfo.IsBoss)
			{
				BossDefeated(sender, keyValue, !flag);
			}
			else if (keyValue == "activebosses")
			{
				float.TryParse(text, NumberStyles.Any, CultureInfo.InvariantCulture, out var result);
				if (result > lastActiveBosses)
				{
					BossEngagedByCount(sender);
				}
				lastActiveBosses = result;
			}
			else
			{
				PeerState peerState = Peers.Get(sender);
				JsonWriter jsonWriter = new JsonWriter();
				jsonWriter.BeginObject().Property("key", keyValue).Property("value", (text.Length > 0) ? text : null)
					.Property("first_time", !flag)
					.Property("prefab", prefabInfo?.Name)
					.Property("name_key", (prefabInfo != null && prefabInfo.CharacterName.Length > 0) ? prefabInfo.CharacterName : null)
					.Property("sender_platform_user_id", peerState?.PlatformUserId)
					.StringArray("nearby", (peerState != null) ? ((IEnumerable<string>)Peers.NearbyIds(peerState.Position, 100f)) : ((IEnumerable<string>)new List<string>()))
					.EndObject();
				Emit("global_key.set", jsonWriter.ToString());
			}
		}

		private PrefabInfo? CreatureForDefeatKey(string key)
		{
			PrefabInfo prefabInfo = null;
			foreach (int item in Catalog.HashesForDefeatKey(key))
			{
				PrefabInfo prefabInfo2 = Catalog.Get(item);
				if (prefabInfo2 != null)
				{
					if (prefabInfo2.IsBoss)
					{
						return prefabInfo2;
					}
					if (prefabInfo == null)
					{
						prefabInfo = prefabInfo2;
					}
				}
			}
			return prefabInfo;
		}

		private ZDO? FindBossZdo(List<int> hashes, Vector3? near, bool requireNotEngaged)
		{
			//IL_0050: 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_0074: Unknown result type (might be due to invalid IL or missing references)
			if (ZDOMan.instance == null || hashes.Count == 0)
			{
				return null;
			}
			ZDO result = null;
			float num = float.MaxValue;
			foreach (ZDO value in ZDOMan.instance.m_objectsByID.Values)
			{
				if (hashes.Contains(value.GetPrefab()) && (!requireNotEngaged || !engagedBosses.ContainsKey(value.m_uid)))
				{
					float num2 = (near.HasValue ? PeerTracker.DistanceXZ(value.GetPosition(), near.Value) : 0f);
					if (num2 < num)
					{
						num = num2;
						result = value;
					}
				}
			}
			return result;
		}

		private void BossDefeated(long sender, string key, bool firstTime)
		{
			//IL_001d: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fe: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e8: Unknown result type (might be due to invalid IL or missing references)
			//IL_0145: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ce: Unknown result type (might be due to invalid IL or missing references)
			PeerState peerState = Peers.Get(sender);
			Vector3? val = peerState?.Position;
			List<int> hashes = Catalog.HashesForDefeatKey(key);
			ZDO val2 = FindBossZdo(hashes, val, requireNotEngaged: false);
			PrefabInfo prefabInfo = ((val2 != null) ? Catalog.Get(val2.GetPrefab()) : null);
			List<string> list = new List<string>();
			if (val2 != null)
			{
				foreach (PeerState item in Peers.All)
				{
					int s_attackers = ZDOVars.s_attackers;
					if (val2.GetBool(s_attackers + item.Name, false))
					{
						list.Add(item.PlatformUserId);
					}
				}
				engagedBosses.Remove(val2.m_uid);
			}
			Vector3 point = (Vector3)((val2 != null) ? val2.GetPosition() : (((??)val) ?? Vector3.zero));
			JsonWriter jsonWriter = new JsonWriter();
			jsonWriter.BeginObject().Property("key", key).Property("first_time", firstTime)
				.Property("sender_platform_user_id", peerState?.PlatformUserId)
				.StringArray("nearby", Peers.NearbyIds(point, 200f))
				.Property("prefab", prefabInfo?.Name)
				.Property("name_key", (prefabInfo != null && prefabInfo.CharacterName.Length > 0) ? prefabInfo.CharacterName : null)
				.StringArray("participants", list)
				.EndObject();
			Emit("boss.defeated", jsonWriter.ToString());
		}

		private void BossEngagedByCount(long sender)
		{
			//IL_0025: 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_00a0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a7: Unknown result type (might be due to invalid IL or missing references)
			if (ZDOMan.instance == null)
			{
				return;
			}
			Vector3? val = Peers.Get(sender)?.Position;
			ZDO val2 = null;
			float num = float.MaxValue;
			foreach (ZDO value in ZDOMan.instance.m_objectsByID.Values)
			{
				PrefabInfo prefabInfo = Catalog.Get(value.GetPrefab());
				if (prefabInfo != null && prefabInfo.IsBoss && !engagedBosses.ContainsKey(value.m_uid))
				{
					float num2 = (val.HasValue ? PeerTracker.DistanceXZ(value.GetPosition(), val.Value) : 0f);
					if (num2 < num)
					{
						num = num2;
						val2 = value;
					}
				}
			}
			if (val2 != null)
			{
				BossEngaged(val2, Catalog.Get(val2.GetPrefab()), null);
			}
		}

		public void BossAlert(long sender, string message)
		{
			//IL_002e: Unknown result type (might be due to invalid IL or missing references)
			//IL_007d: Unknown result type (might be due to invalid IL or missing references)
			if (!Catalog.TryPrefabForAlert(message, out var hash))
			{
				return;
			}
			Vector3? near = Peers.Get(sender)?.Position;
			ZDO val = FindBossZdo(new List<int> { hash }, near, requireNotEngaged: false);
			PrefabInfo prefabInfo = Catalog.Get(hash);
			if (prefabInfo != null)
			{
				if (val != null)
				{
					BossEngaged(val, prefabInfo, message);
				}
				else if (near.HasValue)
				{
					EmitEngaged(prefabInfo, near.Value, message);
				}
			}
		}

		private void BossEngaged(ZDO boss, PrefabInfo info, string? message)
		{
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_001b: Unknown result type (might be due to invalid IL or missing references)
			//IL_002d: Unknown result type (might be due to invalid IL or missing references)
			if (!engagedBosses.ContainsKey(boss.m_uid))
			{
				engagedBosses[boss.m_uid] = DateTime.UtcNow;
				EmitEngaged(info, boss.GetPosition(), message ?? info.AlertMessage);
			}
		}

		private void EmitEngaged(PrefabInfo info, Vector3 position, string message)
		{
			//IL_002e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0039: Unknown result type (might be due to invalid IL or missing references)
			//IL_005a: Unknown result type (might be due to invalid IL or missing references)
			JsonWriter jsonWriter = new JsonWriter();
			jsonWriter.BeginObject().Property("prefab", info.Name).Property("name_key", info.CharacterName);
			Position(jsonWriter, position).Property("biome", BiomeAt(position)).Property("alert_message", message).StringArray("nearby", Peers.NearbyIds(position, 200f))
				.EndObject();
			Emit("boss.engaged", jsonWriter.ToString());
		}

		public void BossSummonRpc(long sender, ZDOID altarId, Vector3 point)
		{
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0075: Unknown result type (might be due to invalid IL or missing references)
			if (ZDOMan.instance == null)
			{
				return;
			}
			ZDO zDO = ZDOMan.instance.GetZDO(altarId);
			PrefabInfo prefabInfo = ((zDO != null) ? Catalog.Get(zDO.GetPrefab()) : null);
			if (prefabInfo != null && prefabInfo.IsAltar)
			{
				PrefabInfo prefabInfo2 = Catalog.Get(StringExtensionMethods.GetStableHashCode(prefabInfo.BossPrefabName));
				if (prefabInfo2 != null && prefabInfo2.IsBoss)
				{
					PeerState peerState = Peers.Get(sender);
					EmitSummoned(prefabInfo.BossPrefabName, prefabInfo2.CharacterName, point, peerState?.PlatformUserId, "spawn_rpc");
				}
			}
		}

		private void EmitSummoned(string prefab, string nameKey, Vector3 position, string? summoner, string method)
		{
			//IL_0035: Unknown result type (might be due to invalid IL or missing references)
			//IL_0040: Unknown result type (might be due to invalid IL or missing references)
			summonedByPrefab[prefab] = DateTime.UtcNow;
			JsonWriter jsonWriter = new JsonWriter();
			jsonWriter.BeginObject().Property("prefab", prefab).Property("name_key", nameKey);
			Position(jsonWriter, position).Property("biome", BiomeAt(position)).Property("summoner_platform_user_id", summoner).Property("method", method)
				.EndObject();
			Emit("boss.summoned", jsonWriter.ToString());
		}

		public void Chat(long sender, string kind, string? text, Vector3? position, string? fallbackUserId)
		{
			//IL_005f: 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_0056: 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_0128: Unknown result type (might be due to invalid IL or missing references)
			//IL_0133: Unknown result type (might be due to invalid IL or missing references)
			PeerState peerState = Peers.Get(sender);
			string text2 = ((peerState != null) ? peerState.PlatformUserId : ((!string.IsNullOrEmpty(fallbackUserId)) ? fallbackUserId : ("Server_" + (ZNet.m_ServerName ?? "server"))));
			Vector3 position2 = (Vector3)(((??)position) ?? peerState?.Position ?? Vector3.zero);
			string key = text2 + "|" + kind + "|" + (text ?? string.Empty);
			DateTime utcNow = DateTime.UtcNow;
			if (!recentChat.TryGetValue(key, out var value) || !((utcNow - value).TotalSeconds < 2.0))
			{
				recentChat[key] = utcNow;
				if (recentChat.Count > 256)
				{
					recentChat.Clear();
				}
				JsonWriter jsonWriter = new JsonWriter();
				jsonWriter.BeginObject().Property("platform_user_id", text2).Property("kind", kind)
					.Property("text", text);
				Position(jsonWriter, position2).Property("biome", BiomeAt(position2)).EndObject();
				Emit("chat.message", jsonWriter.ToString());
			}
		}

		public void AnnouncementShown(Banner banner)
		{
			JsonWriter jsonWriter = new JsonWriter();
			jsonWriter.BeginObject().Property("announcement_id", banner.AnnouncementId).Property("kind", banner.Kind)
				.Property("text", banner.Text);
			if (!banner.RemainingSeconds.HasValue)
			{
				jsonWriter.PropertyNull("remaining_s");
			}
			else
			{
				jsonWriter.Property("remaining_s", banner.RemainingSeconds.Value);
			}
			jsonWriter.Property("final", banner.Final).EndObject();
			Emit("announcement.shown", jsonWriter.ToString());
		}

		public void KillCredited(long targetPeer, string enemyName)
		{
			PeerState peerState = Peers.Get(targetPeer);
			if (peerState != null)
			{
				DateTime now = DateTime.UtcNow;
				recentCredits.RemoveAll((KillCredit credit) => (now - credit.AtUtc).TotalSeconds > 15.0);
				recentCredits.Add(new KillCredit(enemyName, peerState.PlatformUserId, now));
			}
		}

		public void ZdoCreated(ZDOID id, int prefabHash)
		{
			//IL_0009: 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)
			if (prefabHash == 0)
			{
				pendingCreated.Add(id);
				return;
			}
			ZDO val = ((ZDOMan.instance != null) ? ZDOMan.instance.GetZDO(id) : null);
			if (val != null)
			{
				Classify(val, prefabHash);
			}
		}

		public void ZdoDeserialized(ZDO zdo)
		{
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			if (pendingCreated.Remove(zdo.m_uid))
			{
				Classify(zdo, zdo.GetPrefab());
			}
		}

		public void ZdoDestroyed(ZDO zdo)
		{
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			//IL_0054: Unknown result type (might be due to invalid IL or missing references)
			//IL_0059: Unknown result type (might be due to invalid IL or missing references)
			//IL_0078: Unknown result type (might be due to invalid IL or missing references)
			//IL_0083: Unknown result type (might be due to invalid IL or missing references)
			//IL_0115: Unknown result type (might be due to invalid IL or missing references)
			pendingCreated.Remove(zdo.m_uid);
			engagedBosses.Remove(zdo.m_uid);
			PrefabInfo prefabInfo = Catalog.Get(zdo.GetPrefab());
			if (prefabInfo == null)
			{
				return;
			}
			if (prefabInfo.IsPiece)
			{
				long num = zdo.GetLong(ZDOVars.s_creator, 0L);
				Vector3 position = zdo.GetPosition();
				JsonWriter jsonWriter = new JsonWriter();
				jsonWriter.BeginObject().Property("prefab", prefabInfo.Name);
				Position(jsonWriter, position).Property("biome", BiomeAt(position)).Name("creator_character_id");
				if (num != 0L)
				{
					jsonWriter.Value(num);
				}
				else
				{
					jsonWriter.Null();
				}
				jsonWriter.Property("creator_platform_user_id", BuilderAccount(zdo, num)).EndObject();
				Emit("structure.destroyed", jsonWriter.ToString());
			}
			else if (prefabInfo.IsCharacter && !prefabInfo.IsPlayer && !(zdo.GetFloat(ZDOVars.s_health, 1f) > 0f))
			{
				CreatureDied(prefabInfo.Name, prefabInfo, zdo.GetInt(ZDOVars.s_level, 1), zdo.GetPosition());
			}
		}

		private void Classify(ZDO zdo, int prefabHash)
		{
			//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_004c: 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_0155: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f0: Unknown result type (might be due to invalid IL or missing references)
			PrefabInfo prefabInfo = Catalog.Get(prefabHash);
			if (prefabInfo == null)
			{
				return;
			}
			Vector3 position = zdo.GetPosition();
			DateTime value;
			if (prefabInfo.IsPiece)
			{
				long num = zdo.GetLong(ZDOVars.s_creator, 0L);
				JsonWriter jsonWriter = new JsonWriter();
				jsonWriter.BeginObject().Property("prefab", prefabInfo.Name);
				Position(jsonWriter, position).Property("biome", BiomeAt(position)).Property("creator_character_id", num).Property("creator_platform_user_id", BuilderAccount(zdo, num))
					.EndObject();
				Emit("structure.built", jsonWriter.ToString());
			}
			else if (prefabInfo.IsRagdoll)
			{
				string text = ((prefabInfo.CreatureForRagdoll.Length > 0) ? prefabInfo.CreatureForRagdoll : prefabInfo.Name);
				PrefabInfo prefabInfo2 = Catalog.Get(StringExtensionMethods.GetStableHashCode(text));
				if (prefabInfo2 == null || !prefabInfo2.IsPlayer)
				{
					CreatureDied(text, prefabInfo2, zdo.GetInt(ZDOVars.s_level, 1), position);
				}
			}
			else if (prefabInfo.IsBoss && (!summonedByPrefab.TryGetValue(prefabInfo.Name, out value) || !((DateTime.UtcNow - value).TotalSeconds < 120.0)))
			{
				PeerState peerState = Peers.Get(zdo.GetOwner());
				EmitSummoned(prefabInfo.Name, prefabInfo.CharacterName, position, peerState?.PlatformUserId, "zdo");
			}
		}

		private void CreatureDied(string prefab, PrefabInfo? creatureInfo, int level, Vector3 position)
		{
			//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_008a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0161: Unknown result type (might be due to invalid IL or missing references)
			//IL_016d: Unknown result type (might be due to invalid IL or missing references)
			//IL_018f: Unknown result type (might be due to invalid IL or missing references)
			DateTime now = DateTime.UtcNow;
			recentDeaths.RemoveAll((RecentDeath death) => (now - death.AtUtc).TotalSeconds > 10.0);
			foreach (RecentDeath recentDeath in recentDeaths)
			{
				if (recentDeath.Prefab == prefab && PeerTracker.DistanceXZ(recentDeath.Position, position) < 20f)
				{
					return;
				}
			}
			recentDeaths.Add(new RecentDeath(prefab, position, now));
			List<string> list = new List<string>();
			string text = ((creatureInfo != null) ? creatureInfo.CharacterName : string.Empty);
			recentCredits.RemoveAll((KillCredit credit) => (now - credit.AtUtc).TotalSeconds > 15.0);
			for (int num = recentCredits.Count - 1; num >= 0; num--)
			{
				KillCredit killCredit = recentCredits[num];
				if (text.Length > 0 && killCredit.EnemyName == text && !list.Contains(killCredit.PlatformUserId))
				{
					list.Add(killCredit.PlatformUserId);
					recentCredits.RemoveAt(num);
				}
			}
			JsonWriter jsonWriter = new JsonWriter();
			jsonWriter.BeginObject().Property("prefab", prefab).Property("level", Math.Max(1, level));
			Position(jsonWriter, position).Property("biome", BiomeAt(position)).StringArray("credited", list).StringArray("nearby", Peers.NearbyIds(position, 100f))
				.EndObject();
			Emit("creature.died", jsonWriter.ToString());
		}

		public void RaidChanged(RandomEvent? previous, RandomEvent? next, Vector3 position)
		{
			//IL_00c0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cb: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f8: Unknown result type (might be due to invalid IL or missing references)
			if (previous != null && currentRaid != null)
			{
				JsonWriter jsonWriter = new JsonWriter();
				jsonWriter.BeginObject().Property("name", currentRaid).Property("elapsed_s", Math.Round((DateTime.UtcNow - currentRaidStartedUtc).TotalSeconds, 1))
					.Property("active_s", Math.Round(previous.m_time, 1))
					.EndObject();
				Emit("raid.ended", jsonWriter.ToString());
				currentRaid = null;
			}
			if (next != null)
			{
				currentRaid = next.m_name;
				currentRaidStartedUtc = DateTime.UtcNow;
				JsonWriter jsonWriter2 = new JsonWriter();
				jsonWriter2.BeginObject().Property("name", next.m_name);
				Position(jsonWriter2, position).Property("biome", BiomeAt(position)).Property("duration_s", Math.Round(next.m_duration, 1)).StringArray("nearby", Peers.NearbyIds(position, next.m_eventRange))
					.EndObject();
				Emit("raid.started", jsonWriter2.ToString());
			}
		}
	}
	public static class MyPluginInfo
	{
		public const string PLUGIN_GUID = "com.guildsite.telemetry";

		public const string PLUGIN_NAME = "GuildTelemetry";

		public const string PLUGIN_VERSION = "0.7.0";
	}
}
namespace GuildTelemetry.Core
{
	public sealed class Banner
	{
		public long AnnouncementId { get; }

		public string Kind { get; }

		public string Text { get; }

		public int? RemainingSeconds { get; }

		public bool Final { get; }

		public Banner(long announcementId, string kind, string text, int? remainingSeconds, bool final)
		{
			AnnouncementId = announcementId;
			Kind = kind;
			Text = text;
			RemainingSeconds = remainingSeconds;
			Final = final;
		}
	}
	public sealed class AnnouncementScheduler
	{
		private sealed class RestartState
		{
			public string? Note { get; }

			public double DueAt { get; set; }

			public bool Announced { get; set; }

			public HashSet<int> FiredStages { get; } = new HashSet<int>();

			public RestartState(string? note, double dueAt)
			{
				Note = note;
				DueAt = dueAt;
			}
		}

		public static readonly int[] StageMinutes = new int[4] { 15, 10, 5, 1 };

		public const double StageToleranceSeconds = 45.0;

		public const double MaxRememberedMessages = 1000.0;

		private readonly HashSet<long> shownMessages = new HashSet<long>();

		private readonly List<Announcement> pendingMessages = new List<Announcement>();

		private readonly SortedDictionary<long, RestartState> restarts = new SortedDictionary<long, RestartState>();

		public int PendingMessages => pendingMessages.Count;

		public int ActiveRestarts => restarts.Count;

		public void Apply(IList<Announcement> active, double now)
		{
			pendingMessages.Clear();
			HashSet<long> hashSet = new HashSet<long>();
			foreach (Announcement item in active)
			{
				if (item.IsRestart)
				{
					if (item.RestartInSeconds.HasValue)
					{
						double dueAt = now + item.RestartInSeconds.Value;
						if (restarts.TryGetValue(item.Id, out RestartState value))
						{
							value.DueAt = dueAt;
							hashSet.Add(item.Id);
						}
						else if (item.RestartInSeconds.Value >= 0.0)
						{
							restarts[item.Id] = new RestartState(item.Text, dueAt);
							hashSet.Add(item.Id);
						}
					}
				}
				else if (item.Kind == "message" && !shownMessages.Contains(item.Id) && !string.IsNullOrEmpty(item.Text))
				{
					pendingMessages.Add(item);
				}
			}
			List<long> list = new List<long>();
			foreach (long key in restarts.Keys)
			{
				if (!hashSet.Contains(key))
				{
					list.Add(key);
				}
			}
			foreach (long item2 in list)
			{
				restarts.Remove(item2);
			}
		}

		public List<Banner> Due(double now)
		{
			List<Banner> list = new List<Banner>();
			foreach (Announcement pendingMessage in pendingMessages)
			{
				shownMessages.Add(pendingMessage.Id);
				list.Add(new Banner(pendingMessage.Id, "message", pendingMessage.Text, null, final: true));
			}
			pendingMessages.Clear();
			if ((double)shownMessages.Count > 1000.0)
			{
				shownMessages.Clear();
			}
			List<long> list2 = new List<long>();
			foreach (KeyValuePair<long, RestartState> restart in restarts)
			{
				Banner banner = NextRestartBanner(restart.Key, restart.Value, now);
				if (banner != null)
				{
					list.Add(banner);
					if (banner.Final)
					{
						list2.Add(restart.Key);
					}
				}
			}
			foreach (long item in list2)
			{
				restarts.Remove(item);
			}
			return list;
		}

		private static Banner? NextRestartBanner(long id, RestartState state, double now)
		{
			double num = state.DueAt - now;
			int value = (int)Math.Max(0.0, Math.Round(num));
			if (num <= 0.0)
			{
				return new Banner(id, "restart", WithNote("Server restarting now", state.Note), 0, final: true);
			}
			int[] stageMinutes;
			if (!state.Announced)
			{
				state.Announced = true;
				stageMinutes = StageMinutes;
				foreach (int num2 in stageMinutes)
				{
					if ((double)(num2 * 60) >= num - 45.0)
					{
						state.FiredStages.Add(num2);
					}
				}
				return new Banner(id, "restart", WithNote(Describe(num), state.Note), value, final: false);
			}
			stageMinutes = StageMinutes;
			foreach (int num3 in stageMinutes)
			{
				if (state.FiredStages.Contains(num3))
				{
					continue;
				}
				if (num > (double)(num3 * 60))
				{
					break;
				}
				int[] stageMinutes2 = StageMinutes;
				foreach (int num4 in stageMinutes2)
				{
					if (num4 >= num3)
					{
						state.FiredStages.Add(num4);
					}
				}
				return new Banner(id, "restart", WithNote(InMinutes(num3), state.Note), value, final: false);
			}
			return null;
		}

		public static string Describe(double remainingSeconds)
		{
			if (remainingSeconds <= 0.0)
			{
				return "Server restarting now";
			}
			if (remainingSeconds < 60.0)
			{
				return "Server restart in less than a minute";
			}
			return InMinutes(Math.Max(1, (int)Math.Round(remainingSeconds / 60.0, MidpointRounding.AwayFromZero)));
		}

		private static string InMinutes(int minutes)
		{
			return "Server restart in " + minutes.ToString(CultureInfo.InvariantCulture) + " min";
		}

		private static string WithNote(string text, string? note)
		{
			if (!string.IsNullOrEmpty(note))
			{
				return text + ": " + note;
			}
			return text;
		}
	}
	public sealed class Batch
	{
		public List<PendingEvent> Events { get; }

		public byte[] Body { get; }

		public PendingEvent Last => Events[Events.Count - 1];

		public Batch(List<PendingEvent> events, byte[] body)
		{
			Events = events;
			Body = body;
		}
	}
	public static class BatchBuilder
	{
		public const int MaxEvents = 200;

		public const int MaxBodyBytes = 524288;

		public const int FlushEventCount = 50;

		public static Batch Build(BatchMetadata metadata, IList<PendingEvent> pending, int maxEvents = 200, int maxBodyBytes = 524288)
		{
			string text = Prefix(metadata);
			int num = maxBodyBytes - Encoding.UTF8.GetByteCount(text) - 2;
			List<PendingEvent> list = new List<Pe