Decompiled source of EilifCompanionClient v0.4.6

EilifCompanionClient.dll

Decompiled 4 days ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
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.Text;
using System.Threading;
using System.Threading.Tasks;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.6.2", FrameworkDisplayName = ".NET Framework 4.6.2")]
[assembly: AssemblyCompany("EilifCompanionClient")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("0.4.6.0")]
[assembly: AssemblyInformationalVersion("0.4.6+ef4ea9c22bb085a36b73df4dfb6813557761d77a")]
[assembly: AssemblyProduct("EilifCompanionClient")]
[assembly: AssemblyTitle("EilifCompanionClient")]
[assembly: AssemblyVersion("0.4.6.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace EilifCompanionClient
{
	internal static class EilifDeathReporter
	{
		private static readonly FieldInfo LastHitField = AccessTools.Field(typeof(Character), "m_lastHit");

		internal static void Report(Player who)
		{
			//IL_00d0: 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_018f: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a8: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)who == (Object)null || (Object)(object)who != (Object)(object)Player.m_localPlayer || !EilifMapTrackerPlugin.IsOnServer())
			{
				return;
			}
			if (LastHitField == null)
			{
				ManualLogSource log = EilifMapTrackerPlugin.Log;
				if (log != null)
				{
					log.LogWarning((object)"[EilifDeath] Character.m_lastHit not found — the game's field layout changed; no death reported.");
				}
				return;
			}
			ZNet instance = ZNet.instance;
			if ((Object)(object)instance == (Object)null)
			{
				return;
			}
			object? value = LastHitField.GetValue(who);
			HitData val = (HitData)((value is HitData) ? value : null);
			if (val == null)
			{
				ManualLogSource log2 = EilifMapTrackerPlugin.Log;
				if (log2 != null)
				{
					log2.LogWarning((object)"[EilifDeath] no m_lastHit at death — nothing to report.");
				}
				return;
			}
			string text = ((object)Unsafe.As<HitType, HitType>(ref val.m_hitType)/*cast due to .constrained prefix*/).ToString();
			string text2 = ResolveAttacker(val);
			string text3 = ResolveBiome(who);
			string playerName = who.GetPlayerName();
			string text4 = instance.GetWorldName() ?? "";
			if (!string.IsNullOrEmpty(playerName))
			{
				string playerName2 = Player.m_localPlayer.GetPlayerName();
				Vector3 position = ((Component)who).transform.position;
				string s = DateTime.UtcNow.ToString("yyyy-MM-ddTHH:mm:ss.fffZ", CultureInfo.InvariantCulture);
				string json = "{\"schemaVersion\":1,\"game\":\"valheim\",\"source\":\"eilif-death\",\"world\":" + EilifMapTrackerPlugin.JsonStr(text4) + ",\"player\":" + EilifMapTrackerPlugin.JsonStr(playerName) + ",\"reporter\":" + EilifMapTrackerPlugin.JsonStr(playerName2) + ",\"tsUtc\":" + EilifMapTrackerPlugin.JsonStr(s) + ",\"hitType\":" + EilifMapTrackerPlugin.JsonStr(text) + ",\"attacker\":" + ((text2 == null) ? "null" : EilifMapTrackerPlugin.JsonStr(text2)) + ",\"biome\":" + EilifMapTrackerPlugin.JsonStr(text3) + ",\"pos\":{\"x\":" + Fmt(position.x) + ",\"z\":" + Fmt(position.z) + "}}";
				ManualLogSource log3 = EilifMapTrackerPlugin.Log;
				if (log3 != null)
				{
					log3.LogInfo((object)("[EilifDeath] " + playerName + " died in " + text4 + ": hitType=" + text + ", attacker=" + (text2 ?? "(none)") + ", biome=" + text3));
				}
				EilifMapTrackerPlugin.PostJson(json, "[EilifDeath]", "[EilifDeath] reported " + text + ((text2 == null) ? "" : (" by " + text2)) + " for " + playerName);
			}
		}

		private static string ResolveAttacker(HitData hit)
		{
			try
			{
				Character attacker = hit.GetAttacker();
				if ((Object)(object)attacker == (Object)null)
				{
					return null;
				}
				Player val = (Player)(object)((attacker is Player) ? attacker : null);
				if ((Object)(object)val != (Object)null)
				{
					string playerName = val.GetPlayerName();
					return string.IsNullOrEmpty(playerName) ? null : playerName;
				}
				string name = attacker.m_name;
				if (!string.IsNullOrEmpty(name))
				{
					return name;
				}
				return PrefabName(((Component)attacker).gameObject);
			}
			catch (Exception ex)
			{
				ManualLogSource log = EilifMapTrackerPlugin.Log;
				if (log != null)
				{
					log.LogWarning((object)("[EilifDeath] attacker lookup failed: " + ex.Message));
				}
				return null;
			}
		}

		private unsafe static string ResolveBiome(Player who)
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			//IL_000c: 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_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_0022: 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_0034: Unknown result type (might be due to invalid IL or missing references)
			//IL_0039: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				Vector3 position = ((Component)who).transform.position;
				Biome val = Heightmap.FindBiome(position);
				if ((int)val == 0 && WorldGenerator.instance != null)
				{
					val = WorldGenerator.instance.GetBiome(position.x, position.z, 0.02f, false);
				}
				return ((object)(*(Biome*)(&val))/*cast due to .constrained prefix*/).ToString();
			}
			catch
			{
				return "None";
			}
		}

		private static string PrefabName(GameObject go)
		{
			if ((Object)(object)go == (Object)null)
			{
				return null;
			}
			string name = ((Object)go).name;
			if (string.IsNullOrEmpty(name))
			{
				return null;
			}
			int num = name.IndexOf("(Clone)", StringComparison.OrdinalIgnoreCase);
			if (num <= 0)
			{
				return name;
			}
			return name.Substring(0, num);
		}

		private static string Fmt(float v)
		{
			return ((float)Math.Round(v, 1)).ToString("0.#", CultureInfo.InvariantCulture);
		}
	}
	[HarmonyPatch(typeof(Player), "OnDeath")]
	internal static class Patch_PlayerOnDeath
	{
		private static void Postfix(Player __instance)
		{
			try
			{
				EilifDeathReporter.Report(__instance);
			}
			catch (Exception ex)
			{
				ManualLogSource log = EilifMapTrackerPlugin.Log;
				if (log != null)
				{
					log.LogWarning((object)("[EilifDeath] death hook failed: " + ex.Message));
				}
			}
		}
	}
	[BepInPlugin("net.eilif.companionclient", "Eilif Companion Client", "0.4.6")]
	public class EilifMapTrackerPlugin : BaseUnityPlugin
	{
		public const string PluginGuid = "net.eilif.companionclient";

		public const string PluginName = "Eilif Companion Client";

		public const string PluginVersion = "0.4.6";

		internal static ManualLogSource Log;

		internal static EilifMapTrackerPlugin Instance;

		internal static string IngestUrl = "";

		internal static string IngestToken = "";

		private ConfigEntry<string> _url;

		private ConfigEntry<string> _token;

		private ConfigEntry<int> _intervalSeconds;

		private ConfigEntry<string> _keepItemTypes;

		private ConfigEntry<bool> _statsEnabled;

		private ConfigEntry<string> _pumpkinCreatures;

		private static readonly HttpClient Http = new HttpClient
		{
			Timeout = TimeSpan.FromSeconds(20.0)
		};

		private int _postInFlight;

		private float _postTimer;

		private bool _wasConnected;

		private float _lastPct = -1f;

		private static bool _warnedExploredShape;

		private static readonly FieldInfo ExploredField = AccessTools.Field(typeof(Minimap), "m_explored");

		private static readonly FieldInfo TextureSizeField = AccessTools.Field(typeof(Minimap), "m_textureSize");

		private static readonly string[] ExpectedPatchClasses = new string[4] { "Patch_GameLogout", "Patch_PlayerOnDeath", "Patch_MoveInventoryToGrave", "Patch_PumpkinDropFilter" };

		private const double FaultCooldownSeconds = 60.0;

		private DateTime _faultLastUtc = DateTime.MinValue;

		private int _faultSuppressed;

		private string _lastCachedPlayer = "";

		private string _lastCachedWorld = "";

		private void Awake()
		{
			//IL_0087: Unknown result type (might be due to invalid IL or missing references)
			//IL_0091: Expected O, but got Unknown
			//IL_016b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0171: Expected O, but got Unknown
			Log = ((BaseUnityPlugin)this).Logger;
			Instance = this;
			_url = ((BaseUnityPlugin)this).Config.Bind<string>("Map", "Url", "https://valheim-dashboard.vercel.app/api/gs-ingest", "Dashboard ingest endpoint. The explored-map % is POSTed here as source:'client-map'.");
			_token = ((BaseUnityPlugin)this).Config.Bind<string>("Map", "Token", "", "Optional Bearer token. If set it is sent as 'Authorization: Bearer <token>'. Blank is fine (pilot).");
			_intervalSeconds = ((BaseUnityPlugin)this).Config.Bind<int>("Map", "IntervalSeconds", 300, new ConfigDescription("Seconds between map-% posts while connected to a server.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(60, 3600), Array.Empty<object>()));
			_keepItemTypes = ((BaseUnityPlugin)this).Config.Bind<string>("Death", "KeepItemTypes", "OneHandedWeapon, TwoHandedWeapon, TwoHandedWeaponLeft, Bow, Shield, Torch, Tool, Ammo, AmmoNonEquipable", "Item types that stay with you on death IN ADDITION to equipped gear (comma-separated ItemDrop.ItemData.ItemType names). Only active on a world whose deathkeepequip global key is set (i.e. this server); on other servers deaths are pure vanilla. Empty = off.");
			_statsEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("Stats", "Enabled", true, "Post the local player's raw profile counters (kills, deaths, builds, crafts, distance) to the dashboard ingest on the same cadence as the map-% post. This is what makes the kill/death/build/distance/craft boards and the Great Deeds work on Valheim 1.0, where GsValheimStatsClient no longer fills the stats map. Posts source:'client' to the same Url/Token above.");
			_pumpkinCreatures = ((BaseUnityPlugin)this).Config.Bind<string>("Halloween", "PumpkinDropCreatures", "Draugr,Draugr_Ranged,Draugr_Elite", "Which creatures can drop HalloweenPieces pumpkins (BHP_PumpkinDrop): a comma-separated list of creature PREFAB names (Skeleton, Draugr_Elite, Greydwarf_Shaman), not display names; case and spaces around names are ignored. * = every creature (HalloweenPieces' own behaviour, filter off). Empty = no creature drops pumpkins. Only matters on a server running HalloweenPieces: anywhere else there is no pumpkin drop to remove, so this does nothing.");
			IngestUrl = _url.Value;
			IngestToken = _token.Value;
			EilifStatsReporter.Enabled = _statsEnabled.Value;
			TombstoneKeeper.Configure(_keepItemTypes.Value);
			PumpkinDropFilter.Configure(_pumpkinCreatures.Value);
			try
			{
				ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls12;
			}
			catch
			{
			}
			Harmony val = new Harmony("net.eilif.companionclient");
			HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal);
			Type[] typesFromAssembly = AccessTools.GetTypesFromAssembly(typeof(EilifMapTrackerPlugin).Assembly);
			foreach (Type type in typesFromAssembly)
			{
				string text = "?";
				try
				{
					if (!(type == null))
					{
						text = type.Name;
						if (type.GetCustomAttributes(typeof(HarmonyPatch), inherit: true).Length != 0)
						{
							val.CreateClassProcessor(type).Patch();
							hashSet.Add(text);
						}
					}
				}
				catch (Exception ex)
				{
					Log.LogError((object)("[EilifDeath] could not apply " + text + ": " + ex.Message + " -> " + FeatureOf(text) + "."));
				}
			}
			Log.LogInfo((object)$"[EilifDeath] patch classes applied: {CountApplied(hashSet)}/{ExpectedPatchClasses.Length}");
			ReportMissing(hashSet);
			if (TombstoneKeeper.KeepTypes.Count > 0)
			{
				Log.LogInfo((object)$"[EilifDeath] tombstone keep-list armed ({TombstoneKeeper.KeepTypes.Count} item types; active only where deathkeepequip is set).");
			}
			Log.LogInfo((object)string.Format("[EilifMap] {0} v{1} loaded. Posting explored-map % to {2} every {3}s while on a server.", "Eilif Companion Client", "0.4.6", _url.Value, _intervalSeconds.Value));
			Log.LogInfo((object)("[EilifDeath] death-cause reporter armed (posts source:'eilif-death' to " + _url.Value + " when the local player dies on a server)."));
			Log.LogInfo((object)("[EilifHalloween] pumpkin drop filter: " + PumpkinDropFilter.Describe() + " (inert without HalloweenPieces)."));
			Log.LogInfo((object)string.Format("[EilifStats] raw profile-stats reporter {0} (posts source:'client' builds/crafts/distance/pickups/fish to {1} every {2}s and on logout while on a server).", _statsEnabled.Value ? "armed" : "DISABLED", _url.Value, _intervalSeconds.Value));
		}

		private static string FeatureOf(string patchClass)
		{
			return patchClass switch
			{
				"Patch_GameLogout" => "the final explored-map reading on a clean logout (the interval post and the disconnect fallback still run)", 
				"Patch_PlayerOnDeath" => "the authoritative death cause; the dashboard falls back to the third-party 'enemyhit' catch-all for every death", 
				"Patch_MoveInventoryToGrave" => "the tombstone keep-list; deaths drop everything vanilla would drop", 
				"Patch_PumpkinDropFilter" => "the [Halloween] PumpkinDropCreatures filter; on a HalloweenPieces server every creature can drop pumpkins", 
				_ => "an unnamed feature", 
			};
		}

		private static int CountApplied(HashSet<string> applied)
		{
			int num = 0;
			for (int i = 0; i < ExpectedPatchClasses.Length; i++)
			{
				if (applied.Contains(ExpectedPatchClasses[i]))
				{
					num++;
				}
			}
			return num;
		}

		private static void ReportMissing(HashSet<string> applied)
		{
			for (int i = 0; i < ExpectedPatchClasses.Length; i++)
			{
				if (!applied.Contains(ExpectedPatchClasses[i]))
				{
					ManualLogSource log = Log;
					if (log != null)
					{
						log.LogError((object)("[EilifDeath] MISSING patch class " + ExpectedPatchClasses[i] + " - " + FeatureOf(ExpectedPatchClasses[i]) + "."));
					}
				}
			}
		}

		private void Update()
		{
			try
			{
				UpdateCore();
			}
			catch (Exception ex)
			{
				try
				{
					DateTime utcNow = DateTime.UtcNow;
					if (_faultLastUtc != DateTime.MinValue && (utcNow - _faultLastUtc).TotalSeconds < 60.0)
					{
						if (_faultSuppressed < int.MaxValue)
						{
							_faultSuppressed++;
						}
						return;
					}
					int faultSuppressed = _faultSuppressed;
					_faultSuppressed = 0;
					_faultLastUtc = utcNow;
					ManualLogSource log = Log;
					if (log != null)
					{
						log.LogWarning((object)("[EilifMap] update pump failed: " + ex.Message + ((faultSuppressed > 0) ? $" (+{faultSuppressed} more in the last minute)" : "") + ". Cartography posting will retry on the next tick; nothing else is affected."));
					}
				}
				catch
				{
				}
			}
		}

		private void UpdateCore()
		{
			bool flag = IsOnServer();
			if (flag)
			{
				_postTimer += Time.unscaledDeltaTime;
				if (_postTimer >= (float)_intervalSeconds.Value)
				{
					_postTimer = 0f;
					ComputeAndPost("interval");
					EilifStatsReporter.Post("interval");
				}
			}
			else if (_wasConnected)
			{
				_postTimer = 0f;
				if (_lastPct >= 0f)
				{
					Post(_lastCachedPlayer, _lastCachedWorld, _lastPct, "disconnect(cached)");
				}
				_lastPct = -1f;
			}
			_wasConnected = flag;
		}

		internal static bool IsOnServer()
		{
			ZNet instance = ZNet.instance;
			if ((Object)(object)instance == (Object)null || instance.IsServer())
			{
				return false;
			}
			if (instance.GetServerPeer() == null)
			{
				return false;
			}
			if ((Object)(object)Minimap.instance != (Object)null)
			{
				return (Object)(object)Player.m_localPlayer != (Object)null;
			}
			return false;
		}

		internal void ComputeAndPost(string reason)
		{
			try
			{
				Minimap instance = Minimap.instance;
				ZNet instance2 = ZNet.instance;
				Player localPlayer = Player.m_localPlayer;
				if (!((Object)(object)instance == (Object)null) && !((Object)(object)instance2 == (Object)null) && !((Object)(object)localPlayer == (Object)null))
				{
					float? num = ComputeExploredPct(instance);
					if (num.HasValue)
					{
						string playerName = localPlayer.GetPlayerName();
						string text = instance2.GetWorldName() ?? "";
						_lastCachedPlayer = playerName;
						_lastCachedWorld = text;
						_lastPct = num.Value;
						Post(playerName, text, num.Value, reason);
					}
				}
			}
			catch (Exception ex)
			{
				Log.LogWarning((object)("[EilifMap] compute/post failed: " + ex.Message));
			}
		}

		private static float? ComputeExploredPct(Minimap mm)
		{
			if (ExploredField == null || TextureSizeField == null)
			{
				return null;
			}
			object value = ExploredField.GetValue(mm);
			bool[] arr = value as bool[];
			Func<int, bool> func;
			int num;
			if (arr != null)
			{
				func = (int i) => arr[i];
				num = arr.Length;
			}
			else
			{
				BitArray bits = value as BitArray;
				if (bits == null)
				{
					if (!_warnedExploredShape)
					{
						_warnedExploredShape = true;
						ManualLogSource log = Log;
						if (log != null)
						{
							log.LogWarning((object)("[EilifMap] Minimap.m_explored is " + ((value == null) ? "null" : value.GetType().FullName) + " (expected bool[] or BitArray); explored-map posts are disabled until the plugin is updated."));
						}
					}
					return null;
				}
				func = (int i) => bits[i];
				num = bits.Length;
			}
			if (num == 0)
			{
				return null;
			}
			int num2 = (int)TextureSizeField.GetValue(mm);
			if (num2 <= 0 || num < num2 * num2)
			{
				return null;
			}
			float num3 = (float)num2 / 2f;
			float num4 = num3 * num3;
			long num5 = 0L;
			long num6 = 0L;
			for (int num7 = 0; num7 < num2; num7++)
			{
				float num8 = (float)num7 - num3;
				float num9 = num4 - num8 * num8;
				if (num9 < 0f)
				{
					continue;
				}
				float num10 = (float)Math.Sqrt(num9);
				int num11 = Math.Max(0, (int)Math.Ceiling(num3 - num10));
				int num12 = Math.Min(num2 - 1, (int)Math.Floor(num3 + num10));
				int num13 = num7 * num2;
				for (int num14 = num11; num14 <= num12; num14++)
				{
					num6++;
					if (func(num13 + num14))
					{
						num5++;
					}
				}
			}
			if (num6 == 0L)
			{
				return null;
			}
			float val = (float)((double)num5 / (double)num6 * 100.0);
			return Math.Min(100f, val);
		}

		private void Post(string playerName, string world, float pct, string reason)
		{
			if (!string.IsNullOrEmpty(playerName) && Interlocked.CompareExchange(ref _postInFlight, 1, 0) == 0)
			{
				float num = (float)Math.Round(pct, 2);
				string json = "{\"schemaVersion\":1,\"game\":\"valheim\",\"source\":\"client-map\",\"playerName\":" + JsonStr(playerName) + ",\"reporter\":" + JsonStr(playerName) + ",\"world\":" + JsonStr(world) + ",\"exploredPct\":" + num.ToString("0.##", CultureInfo.InvariantCulture) + "}";
				string url = _url.Value;
				string token = _token.Value;
				string pctText = num.ToString("0.##", CultureInfo.InvariantCulture);
				Task.Run(() => PostAsync(url, token, json, playerName, world, pctText, reason));
			}
		}

		private async Task PostAsync(string url, string token, string json, string playerName, string world, string pctText, string reason)
		{
			try
			{
				HttpRequestMessage req = new HttpRequestMessage(HttpMethod.Post, url);
				try
				{
					req.Content = (HttpContent)new StringContent(json, Encoding.UTF8, "application/json");
					if (!string.IsNullOrEmpty(token))
					{
						((HttpHeaders)req.Headers).TryAddWithoutValidation("Authorization", "Bearer " + token);
					}
					HttpResponseMessage val = await Http.SendAsync(req).ConfigureAwait(continueOnCapturedContext: false);
					try
					{
						if (val.IsSuccessStatusCode)
						{
							Log.LogInfo((object)("[EilifMap] posted " + pctText + "% for " + playerName + " (" + world + ") [" + reason + "]"));
						}
						else
						{
							Log.LogWarning((object)$"[EilifMap] post HTTP {(int)val.StatusCode} {val.ReasonPhrase} for {playerName}");
						}
					}
					finally
					{
						((IDisposable)val)?.Dispose();
					}
				}
				finally
				{
					((IDisposable)req)?.Dispose();
				}
			}
			catch (Exception ex)
			{
				Log.LogWarning((object)("[EilifMap] post failed: " + ex.Message));
			}
			finally
			{
				Interlocked.Exchange(ref _postInFlight, 0);
			}
		}

		internal static void PostJson(string json, string tag, string successLine)
		{
			string url = IngestUrl;
			if (string.IsNullOrEmpty(url) || string.IsNullOrEmpty(json))
			{
				return;
			}
			string token = IngestToken;
			try
			{
				Task.Run(() => SendAsync(url, token, json, tag, successLine));
			}
			catch (Exception ex)
			{
				ManualLogSource log = Log;
				if (log != null)
				{
					log.LogWarning((object)(tag + " could not queue post: " + ex.Message));
				}
			}
		}

		private static async Task SendAsync(string url, string token, string json, string tag, string successLine)
		{
			try
			{
				HttpRequestMessage req = new HttpRequestMessage(HttpMethod.Post, url);
				try
				{
					req.Content = (HttpContent)new StringContent(json, Encoding.UTF8, "application/json");
					if (!string.IsNullOrEmpty(token))
					{
						((HttpHeaders)req.Headers).TryAddWithoutValidation("Authorization", "Bearer " + token);
					}
					HttpResponseMessage val = await Http.SendAsync(req).ConfigureAwait(continueOnCapturedContext: false);
					try
					{
						if (val.IsSuccessStatusCode)
						{
							ManualLogSource log = Log;
							if (log != null)
							{
								log.LogInfo((object)successLine);
							}
						}
						else
						{
							ManualLogSource log2 = Log;
							if (log2 != null)
							{
								log2.LogWarning((object)$"{tag} post HTTP {(int)val.StatusCode} {val.ReasonPhrase}");
							}
						}
					}
					finally
					{
						((IDisposable)val)?.Dispose();
					}
				}
				finally
				{
					((IDisposable)req)?.Dispose();
				}
			}
			catch (Exception ex)
			{
				ManualLogSource log3 = Log;
				if (log3 != null)
				{
					log3.LogWarning((object)(tag + " post failed: " + ex.Message));
				}
			}
		}

		internal static string JsonStr(string s)
		{
			StringBuilder stringBuilder = new StringBuilder(s.Length + 2);
			stringBuilder.Append('"');
			foreach (char c in s)
			{
				switch (c)
				{
				case '"':
					stringBuilder.Append("\\\"");
					continue;
				case '\\':
					stringBuilder.Append("\\\\");
					continue;
				case '\b':
					stringBuilder.Append("\\b");
					continue;
				case '\f':
					stringBuilder.Append("\\f");
					continue;
				case '\n':
					stringBuilder.Append("\\n");
					continue;
				case '\r':
					stringBuilder.Append("\\r");
					continue;
				case '\t':
					stringBuilder.Append("\\t");
					continue;
				}
				if (c < ' ')
				{
					StringBuilder stringBuilder2 = stringBuilder.Append("\\u");
					int num = c;
					stringBuilder2.Append(num.ToString("x4", CultureInfo.InvariantCulture));
				}
				else
				{
					stringBuilder.Append(c);
				}
			}
			stringBuilder.Append('"');
			return stringBuilder.ToString();
		}
	}
	[HarmonyPatch(typeof(Game), "Logout")]
	internal static class Patch_GameLogout
	{
		private static void Prefix()
		{
			try
			{
				EilifMapTrackerPlugin.Instance?.ComputeAndPost("logout");
			}
			catch (Exception ex)
			{
				ManualLogSource log = EilifMapTrackerPlugin.Log;
				if (log != null)
				{
					log.LogWarning((object)("[EilifMap] logout hook failed: " + ex.Message));
				}
			}
			try
			{
				EilifStatsReporter.Post("logout");
			}
			catch (Exception ex2)
			{
				ManualLogSource log2 = EilifMapTrackerPlugin.Log;
				if (log2 != null)
				{
					log2.LogWarning((object)("[EilifStats] logout hook failed: " + ex2.Message));
				}
			}
		}
	}
	internal static class PumpkinDropFilter
	{
		internal const string PumpkinPrefab = "BHP_PumpkinDrop";

		internal const string DefaultCreatures = "Draugr,Draugr_Ranged,Draugr_Elite";

		internal static bool Active;

		internal static readonly HashSet<string> Allowed = new HashSet<string>(StringComparer.OrdinalIgnoreCase);

		private static readonly char[] PrefabCut = new char[2] { '(', ' ' };

		internal static void Configure(string csv)
		{
			Allowed.Clear();
			Active = true;
			if (csv == null)
			{
				return;
			}
			string[] array = csv.Split(new char[1] { ',' });
			for (int i = 0; i < array.Length; i++)
			{
				string text = array[i].Trim();
				if (text.Length != 0)
				{
					if (text == "*")
					{
						Active = false;
						Allowed.Clear();
						break;
					}
					Allowed.Add(text);
				}
			}
		}

		internal static string Describe()
		{
			if (!Active)
			{
				return "off (* = every creature, HalloweenPieces' own behaviour)";
			}
			if (Allowed.Count == 0)
			{
				return "no creature drops pumpkins";
			}
			return Allowed.Count + " creature prefab(s) may drop pumpkins";
		}

		internal static string PrefabName(string name)
		{
			if (string.IsNullOrEmpty(name))
			{
				return "";
			}
			int num = name.IndexOfAny(PrefabCut);
			if (num < 0)
			{
				return name;
			}
			return name.Substring(0, num);
		}
	}
	[HarmonyPatch(typeof(CharacterDrop), "GenerateDropList")]
	internal static class Patch_PumpkinDropFilter
	{
		private static readonly FieldInfo CharacterField = AccessTools.Field(typeof(CharacterDrop), "m_character");

		private static bool _warned;

		private static void Postfix(CharacterDrop __instance, ref List<KeyValuePair<GameObject, int>> __result)
		{
			try
			{
				if (!PumpkinDropFilter.Active || __result == null || __result.Count == 0 || (Object)(object)__instance == (Object)null)
				{
					return;
				}
				bool flag = false;
				for (int i = 0; i < __result.Count; i++)
				{
					if (IsPumpkin(__result[i].Key))
					{
						flag = true;
						break;
					}
				}
				if (!flag)
				{
					return;
				}
				Character val = null;
				if (CharacterField != null)
				{
					object? value = CharacterField.GetValue(__instance);
					val = (Character)((value is Character) ? value : null);
				}
				if ((Object)(object)val == (Object)null)
				{
					val = ((Component)__instance).GetComponent<Character>();
				}
				if ((Object)(object)val == (Object)null)
				{
					return;
				}
				string item = PumpkinDropFilter.PrefabName(((Object)((Component)val).gameObject).name).Trim();
				if (!PumpkinDropFilter.Allowed.Contains(item))
				{
					__result.RemoveAll((KeyValuePair<GameObject, int> kv) => IsPumpkin(kv.Key));
				}
			}
			catch (Exception ex)
			{
				if (_warned)
				{
					return;
				}
				_warned = true;
				try
				{
					ManualLogSource log = EilifMapTrackerPlugin.Log;
					if (log != null)
					{
						log.LogWarning((object)("[EilifHalloween] pumpkin drop filter failed (logged once, drops left as rolled): " + ex.Message));
					}
				}
				catch
				{
				}
			}
		}

		private static bool IsPumpkin(GameObject go)
		{
			if ((Object)(object)go != (Object)null)
			{
				return string.Equals(PumpkinDropFilter.PrefabName(((Object)go).name), "BHP_PumpkinDrop", StringComparison.OrdinalIgnoreCase);
			}
			return false;
		}
	}
	internal static class EilifStatsReporter
	{
		private struct StatKey
		{
			public readonly string VhKey;

			public readonly PlayerStatType Type;

			public StatKey(string vhKey, PlayerStatType type)
			{
				//IL_0008: Unknown result type (might be due to invalid IL or missing references)
				//IL_0009: Unknown result type (might be due to invalid IL or missing references)
				VhKey = vhKey;
				Type = type;
			}
		}

		internal static bool Enabled = true;

		private static readonly StatKey[] StatMap = new StatKey[19]
		{
			new StatKey("vh_EnemyKills", (PlayerStatType)6),
			new StatKey("vh_Builds", (PlayerStatType)2),
			new StatKey("vh_Crafts", (PlayerStatType)13),
			new StatKey("vh_DistanceTraveled", (PlayerStatType)16),
			new StatKey("vh_DistanceWalk", (PlayerStatType)17),
			new StatKey("vh_DistanceRun", (PlayerStatType)18),
			new StatKey("vh_DistanceSail", (PlayerStatType)19),
			new StatKey("vh_DistanceAir", (PlayerStatType)20),
			new StatKey("vh_ItemsPickedUp", (PlayerStatType)12),
			new StatKey("vh_FishCaught", (PlayerStatType)160),
			new StatKey("vh_FishHooked", (PlayerStatType)158),
			new StatKey("vh_FishLost", (PlayerStatType)159),
			new StatKey("vh_FishCaughtTier0", (PlayerStatType)161),
			new StatKey("vh_FishCaughtTier1", (PlayerStatType)162),
			new StatKey("vh_FishCaughtTier2", (PlayerStatType)163),
			new StatKey("vh_FishCaughtTier3", (PlayerStatType)164),
			new StatKey("vh_FishCaughtTier4", (PlayerStatType)165),
			new StatKey("vh_FishCaughtTier5", (PlayerStatType)166),
			new StatKey("vh_FishCaughtTier6", (PlayerStatType)167)
		};

		internal static void Post(string reason)
		{
			//IL_009c: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				if (!Enabled || !EilifMapTrackerPlugin.IsOnServer())
				{
					return;
				}
				Game instance = Game.instance;
				ZNet instance2 = ZNet.instance;
				Player localPlayer = Player.m_localPlayer;
				if ((Object)(object)instance == (Object)null || (Object)(object)instance2 == (Object)null || (Object)(object)localPlayer == (Object)null)
				{
					return;
				}
				PlayerProfile playerProfile = instance.GetPlayerProfile();
				if (playerProfile == null)
				{
					return;
				}
				string playerName = localPlayer.GetPlayerName();
				if (string.IsNullOrEmpty(playerName))
				{
					return;
				}
				string text = instance2.GetWorldName() ?? "";
				int num = 0;
				StringBuilder stringBuilder = new StringBuilder();
				for (int i = 0; i < StatMap.Length; i++)
				{
					if (TryReadStat(playerProfile, StatMap[i].Type, out var value))
					{
						if (stringBuilder.Length > 0)
						{
							stringBuilder.Append(',');
						}
						stringBuilder.Append(EilifMapTrackerPlugin.JsonStr(StatMap[i].VhKey)).Append(':').Append(value.ToString(CultureInfo.InvariantCulture));
						num++;
					}
				}
				StringBuilder stringBuilder2 = new StringBuilder();
				stringBuilder2.Append("{\"name\":").Append(EilifMapTrackerPlugin.JsonStr(playerName));
				stringBuilder2.Append(",\"stats\":{").Append((object?)stringBuilder).Append("}}");
				if (num != 0)
				{
					EilifMapTrackerPlugin.PostJson("{\"schemaVersion\":1,\"game\":\"valheim\",\"source\":\"client\",\"reporter\":" + EilifMapTrackerPlugin.JsonStr(playerName) + ",\"world\":" + EilifMapTrackerPlugin.JsonStr(text) + ",\"players\":[" + stringBuilder2?.ToString() + "]}", "[EilifStats]", $"[EilifStats] posted {num} counters for {playerName} ({text}) [{reason}]");
				}
			}
			catch (Exception ex)
			{
				ManualLogSource log = EilifMapTrackerPlugin.Log;
				if (log != null)
				{
					log.LogWarning((object)("[EilifStats] read/post failed: " + ex.Message));
				}
			}
		}

		private static bool TryReadStat(PlayerProfile profile, PlayerStatType type, out long value)
		{
			//IL_0024: 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)
			value = 0L;
			try
			{
				PlayerStats[] playerStats = profile.m_playerStats;
				float num = ((playerStats == null || playerStats.Length == 0 || playerStats[0] == null) ? profile.GetStat(type) : playerStats[0][type]);
				if (float.IsNaN(num) || float.IsInfinity(num))
				{
					return false;
				}
				value = (long)Math.Round(num, MidpointRounding.AwayFromZero);
				return true;
			}
			catch
			{
				return false;
			}
		}
	}
	internal static class TombstoneKeeper
	{
		internal static readonly HashSet<ItemType> KeepTypes = new HashSet<ItemType>();

		internal const string DefaultKeepTypes = "OneHandedWeapon, TwoHandedWeapon, TwoHandedWeaponLeft, Bow, Shield, Torch, Tool, Ammo, AmmoNonEquipable";

		internal static void Configure(string csv)
		{
			//IL_0051: Unknown result type (might be due to invalid IL or missing references)
			KeepTypes.Clear();
			if (string.IsNullOrEmpty(csv))
			{
				return;
			}
			string[] array = csv.Split(new char[1] { ',' });
			for (int i = 0; i < array.Length; i++)
			{
				string text = array[i].Trim();
				if (text.Length != 0)
				{
					try
					{
						KeepTypes.Add((ItemType)Enum.Parse(typeof(ItemType), text, ignoreCase: true));
					}
					catch
					{
						EilifMapTrackerPlugin.Log.LogWarning((object)("[EilifDeath] unknown item type '" + text + "' in KeepItemTypes - ignored."));
					}
				}
			}
		}
	}
	[HarmonyPatch(typeof(Inventory), "MoveInventoryToGrave")]
	internal static class Patch_MoveInventoryToGrave
	{
		private static readonly List<ItemData> Flagged = new List<ItemData>();

		private static void Prefix(Inventory original)
		{
			//IL_008e: Unknown result type (might be due to invalid IL or missing references)
			Flagged.Clear();
			try
			{
				if (TombstoneKeeper.KeepTypes.Count == 0 || original == null || (Object)(object)ZoneSystem.instance == (Object)null || !ZoneSystem.instance.GetGlobalKey((GlobalKeys)19))
				{
					return;
				}
				List<ItemData> allItems = original.GetAllItems();
				if (allItems == null)
				{
					return;
				}
				foreach (ItemData item in allItems)
				{
					if (item != null && !item.m_equipped && item.m_shared != null && !item.m_shared.m_questItem && TombstoneKeeper.KeepTypes.Contains(item.m_shared.m_itemType))
					{
						item.m_equipped = true;
						Flagged.Add(item);
					}
				}
				if (Flagged.Count > 0)
				{
					EilifMapTrackerPlugin.Log.LogInfo((object)$"[EilifDeath] tombstone keep-list spared {Flagged.Count} item(s).");
				}
			}
			catch (Exception ex)
			{
				ManualLogSource log = EilifMapTrackerPlugin.Log;
				if (log != null)
				{
					log.LogWarning((object)("[EilifDeath] tombstone keep-list prefix failed: " + ex.Message));
				}
			}
		}

		private static void Finalizer()
		{
			try
			{
				for (int i = 0; i < Flagged.Count; i++)
				{
					try
					{
						ItemData val = Flagged[i];
						if (val != null)
						{
							val.m_equipped = false;
						}
					}
					catch
					{
					}
				}
			}
			catch
			{
			}
			finally
			{
				try
				{
					Flagged.Clear();
				}
				catch
				{
				}
			}
		}
	}
}