Decompiled source of Viking Chronicle v0.1.0

BepInEx/plugins/VikingChronicle/VikingChronicle.Core.dll

Decompiled 5 days ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text;
using Microsoft.CodeAnalysis;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("Rinor")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("0.1.0.0")]
[assembly: AssemblyInformationalVersion("0.1.0")]
[assembly: AssemblyProduct("VikingChronicle.Core")]
[assembly: AssemblyTitle("VikingChronicle.Core")]
[assembly: AssemblyVersion("0.1.0.0")]
[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 VikingChronicle.Core
{
	public static class FeedText
	{
		public static string Format(FeedEntry entry, bool german = true)
		{
			if (entry == null)
			{
				return string.Empty;
			}
			string text = StatSanitizer.DisplayText(entry.PlayerName);
			if (text.Length == 0)
			{
				text = "Viking";
			}
			string text2 = entry.Metric ?? string.Empty;
			string text3 = Subject(entry, german);
			string text4 = Math.Max(0L, entry.Amount).ToString("N0", CultureInfo.GetCultureInfo(german ? "de-DE" : "en-US"));
			if (text2.StartsWith("kill.", StringComparison.Ordinal))
			{
				if (!german)
				{
					return text + " killed " + ((entry.Amount > 1) ? (text4 + " ") : string.Empty) + text3;
				}
				return text + " hat " + ((entry.Amount > 1) ? (text4 + " ") : string.Empty) + text3 + " getötet";
			}
			if (text2.StartsWith("resource.", StringComparison.Ordinal))
			{
				if (!german)
				{
					return text + " gathered " + text4 + " " + text3;
				}
				return text + " hat " + text4 + " " + text3 + " gesammelt";
			}
			switch (text2)
			{
			case "build.placed":
				if (!german)
				{
					return text + " built a piece";
				}
				return text + " hat gebaut";
			case "build.repaired":
				if (!german)
				{
					return text + " repaired " + text4 + " pieces";
				}
				return text + " hat " + text4 + " Bauteile repariert";
			case "craft.items":
				if (!german)
				{
					return text + " crafted " + text4 + " " + text3;
				}
				return text + " hat " + text4 + " " + text3 + " hergestellt";
			case "craft.upgraded":
				if (!german)
				{
					return text + " upgraded " + text3;
				}
				return text + " hat " + text3 + " verbessert";
			case "tree.felled":
				if (!german)
				{
					return text + " felled a tree";
				}
				return text + " hat einen Baum gefällt";
			case "farm.planted":
				if (!german)
				{
					return text + " planted " + text4 + " crops";
				}
				return text + " hat " + text4 + " Pflanzen gesetzt";
			case "death.count":
				if (!german)
				{
					return text + " died";
				}
				return text + " ist gestorben";
			default:
				return string.Empty;
			}
		}

		private static string Subject(FeedEntry entry, bool german)
		{
			string text = StatSanitizer.DisplayText(entry.Subject);
			if (text.Length > 0)
			{
				return text;
			}
			switch (entry.Metric)
			{
			case "kill.boar":
				if (!german)
				{
					return "a Boar";
				}
				return "ein Wildschwein";
			case "kill.deer":
				if (!german)
				{
					return "a Deer";
				}
				return "einen Hirsch";
			case "kill.greydwarf":
				if (!german)
				{
					return "a Greydwarf";
				}
				return "einen Grauzwerg";
			case "kill.troll":
				if (!german)
				{
					return "a Troll";
				}
				return "einen Troll";
			case "craft.items":
				if (!german)
				{
					return "items";
				}
				return "Gegenstände";
			default:
				return MetricCatalog.FriendlyName(entry.Metric ?? string.Empty, german);
			}
		}
	}
	public sealed class StatTab
	{
		public string Id { get; }

		public string Title { get; }

		public IReadOnlyList<StatColumn> Columns { get; }

		public StatTab(string id, string title, IReadOnlyList<StatColumn> columns)
		{
			Id = id;
			Title = title;
			Columns = columns;
		}
	}
	public sealed class StatColumn
	{
		private readonly CultureInfo culture;

		public string Key { get; }

		public string Title { get; }

		public StatColumn(string key, string title, bool german = true)
		{
			Key = key;
			Title = title;
			culture = CultureInfo.GetCultureInfo(german ? "de-DE" : "en-US");
		}

		public string Format(long value)
		{
			value = Math.Max(0L, value);
			if (Key == "time.seconds")
			{
				if (value < 60)
				{
					return value.ToString(culture) + " s";
				}
				if (value < 3600)
				{
					return (value / 60).ToString(culture) + " min";
				}
				return (value / 3600).ToString("N0", culture) + " h " + (value % 3600 / 60).ToString(culture) + " min";
			}
			if (Key.StartsWith("distance.", StringComparison.Ordinal))
			{
				if (value < 1000)
				{
					return value.ToString("N0", culture) + " m";
				}
				return ((double)value / 1000.0).ToString("N1", culture) + " km";
			}
			return value.ToString("N0", culture);
		}
	}
	public static class MetricCatalog
	{
		private static readonly Dictionary<string, string[]> Names = new Dictionary<string, string[]>(StringComparer.Ordinal)
		{
			["kill.total"] = new string[2] { "Kills", "Kills" },
			["kill.boar"] = new string[2] { "Wildschweine", "Boars" },
			["kill.deer"] = new string[2] { "Hirsche", "Deer" },
			["kill.neck"] = new string[2] { "Nixe", "Necks" },
			["kill.greydwarf"] = new string[2] { "Grauzwerge", "Greydwarfs" },
			["kill.troll"] = new string[2] { "Trolle", "Trolls" },
			["kill.draugr"] = new string[2] { "Draugr", "Draugr" },
			["kill.skeleton"] = new string[2] { "Skelette", "Skeletons" },
			["kill.wolf"] = new string[2] { "Wölfe", "Wolves" },
			["resource.total"] = new string[2] { "Gesammelt", "Gathered" },
			["resource.wood"] = new string[2] { "Holz", "Wood" },
			["resource.roundlog"] = new string[2] { "Kernholz", "Core wood" },
			["resource.finewood"] = new string[2] { "Edelholz", "Fine wood" },
			["resource.elderbark"] = new string[2] { "Uralte Rinde", "Ancient bark" },
			["resource.yggdrasilwood"] = new string[2] { "Yggdrasilholz", "Yggdrasil wood" },
			["resource.stone"] = new string[2] { "Stein", "Stone" },
			["resource.copperore"] = new string[2] { "Kupfererz", "Copper ore" },
			["resource.tinore"] = new string[2] { "Zinnerz", "Tin ore" },
			["resource.ironscrap"] = new string[2] { "Eisenschrott", "Scrap iron" },
			["resource.silverore"] = new string[2] { "Silbererz", "Silver ore" },
			["resource.blackmetalscrap"] = new string[2] { "Schwarzmetall", "Black metal" },
			["resource.flametalore"] = new string[2] { "Flametal-Erz", "Flametal ore" },
			["build.placed"] = new string[2] { "Gebaut", "Built" },
			["build.repaired"] = new string[2] { "Repariert", "Repaired" },
			["craft.items"] = new string[2] { "Hergestellt", "Crafted" },
			["craft.upgraded"] = new string[2] { "Verbessert", "Upgraded" },
			["tree.felled"] = new string[2] { "Bäume", "Trees" },
			["farm.planted"] = new string[2] { "Gepflanzt", "Planted" },
			["death.count"] = new string[2] { "Tode", "Deaths" },
			["time.seconds"] = new string[2] { "Spielzeit", "Play time" },
			["distance.walked"] = new string[2] { "Zu Fuß", "On foot" },
			["distance.swam"] = new string[2] { "Geschwommen", "Swimming" },
			["distance.sailed"] = new string[2] { "Gesegelt", "Sailing" }
		};

		public static IReadOnlyList<StatTab> GetTabs(WorldSnapshot snapshot, bool german = true)
		{
			if (snapshot == null)
			{
				throw new ArgumentNullException("snapshot");
			}
			return new StatTab[4]
			{
				new StatTab("hunting", german ? "Jagd" : "Hunting", Columns(snapshot, german, "kill.", "kill.total", "kill.boar", "kill.deer", "kill.greydwarf", "kill.troll")),
				new StatTab("resources", german ? "Ressourcen" : "Resources", Columns(snapshot, german, "resource.", "resource.total", "resource.wood", "resource.stone", "resource.copperore", "resource.tinore", "resource.ironscrap", "tree.felled", "farm.planted")),
				new StatTab("building", german ? "Bauen" : "Building", Columns(snapshot, german, null, "build.placed", "build.repaired", "craft.items", "craft.upgraded")),
				new StatTab("survival", german ? "Überleben" : "Survival", Columns(snapshot, german, null, "time.seconds", "death.count", "distance.walked", "distance.swam", "distance.sailed"))
			};
		}

		public static string FriendlyName(string metric, bool german = true)
		{
			if (Names.TryGetValue(metric, out string[] value))
			{
				return value[(!german) ? 1u : 0u];
			}
			string value2 = metric.Substring(metric.IndexOf('.') + 1).Replace('_', ' ');
			value2 = StatSanitizer.DisplayText(value2);
			if (value2.Length != 0)
			{
				return char.ToUpperInvariant(value2[0]) + value2.Substring(1);
			}
			if (!german)
			{
				return "Unknown";
			}
			return "Unbekannt";
		}

		private static IReadOnlyList<StatColumn> Columns(WorldSnapshot snapshot, bool german, string? prefix, params string[] fixedKeys)
		{
			List<string> list = new List<string>(fixedKeys);
			HashSet<string> hashSet = new HashSet<string>(fixedKeys, StringComparer.Ordinal);
			if (prefix != null)
			{
				HashSet<string> hashSet2 = new HashSet<string>(StringComparer.Ordinal);
				foreach (string key in (snapshot.MetricNames ?? new Dictionary<string, string>()).Keys)
				{
					if (key.StartsWith(prefix, StringComparison.Ordinal) && StatSanitizer.TryMetric(key, out string canonical))
					{
						hashSet2.Add(canonical);
					}
				}
				foreach (PlayerStats item in snapshot.Players ?? new List<PlayerStats>())
				{
					foreach (string key2 in (item.Values ?? new Dictionary<string, long>()).Keys)
					{
						if (key2.StartsWith(prefix, StringComparison.Ordinal) && StatSanitizer.TryMetric(key2, out string canonical2))
						{
							hashSet2.Add(canonical2);
						}
					}
				}
				foreach (string item2 in hashSet2.OrderBy<string, string>((string k) => k, StringComparer.Ordinal))
				{
					if (hashSet.Add(item2))
					{
						list.Add(item2);
					}
				}
			}
			return list.Select((string key) => new StatColumn(key, ColumnTitle(snapshot, key, german), german)).ToArray();
		}

		private static string ColumnTitle(WorldSnapshot snapshot, string key, bool german)
		{
			if (Names.ContainsKey(key))
			{
				return FriendlyName(key, german);
			}
			if (snapshot.MetricNames != null && snapshot.MetricNames.TryGetValue(key, out string value))
			{
				string text = StatSanitizer.DisplayText(value);
				if (!string.IsNullOrEmpty(text))
				{
					return text;
				}
			}
			return FriendlyName(key, german);
		}
	}
	public sealed class StatEvent
	{
		public string EventId { get; set; } = string.Empty;

		public long PlayerId { get; set; }

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

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

		public long Amount { get; set; }

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

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

		public long SubmissionSequence { get; set; }
	}
	public sealed class PlayerStats
	{
		public long PlayerId { get; set; }

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

		public Dictionary<string, long> Values { get; set; } = new Dictionary<string, long>();
	}
	public sealed class WorldSnapshot
	{
		public int SchemaVersion { get; set; } = 1;

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

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

		public long Revision { get; set; }

		public List<PlayerStats> Players { get; set; } = new List<PlayerStats>();

		public List<string> RecentEventIds { get; set; } = new List<string>();

		public Dictionary<string, string> MetricNames { get; set; } = new Dictionary<string, string>();

		public Dictionary<string, long> LastSequences { get; set; } = new Dictionary<string, long>();
	}
	public sealed class FeedEntry
	{
		public string PlayerName { get; set; } = string.Empty;

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

		public long Amount { get; set; }

		public string Subject { get; set; } = string.Empty;
	}
	public static class StatSanitizer
	{
		public const int MaxNameLength = 64;

		public const int MaxPrefabLength = 64;

		public static string DisplayText(string? value, int maxLength = 64)
		{
			if (string.IsNullOrWhiteSpace(value) || maxLength <= 0)
			{
				return string.Empty;
			}
			StringBuilder stringBuilder = new StringBuilder(Math.Min(value.Length, maxLength));
			bool flag = false;
			bool flag2 = false;
			foreach (char c in value)
			{
				switch (c)
				{
				case '<':
					flag = true;
					continue;
				case '>':
					flag = false;
					continue;
				default:
					if (flag)
					{
						continue;
					}
					if (char.IsWhiteSpace(c) || char.IsControl(c))
					{
						flag2 = stringBuilder.Length > 0;
						continue;
					}
					switch (c)
					{
					case '\u200b':
					case '\u200e':
					case '\u200f':
					case '\u202a':
					case '\u202b':
					case '\u202c':
					case '\u202d':
					case '\u202e':
						continue;
					}
					if ((c >= '\u2066' && c <= '\u2069') || c == '\ufeff')
					{
						continue;
					}
					if (flag2 && stringBuilder.Length < maxLength)
					{
						stringBuilder.Append(' ');
					}
					flag2 = false;
					if (stringBuilder.Length < maxLength)
					{
						stringBuilder.Append(c);
						continue;
					}
					break;
				}
				break;
			}
			if (stringBuilder.Length > 0 && char.IsHighSurrogate(stringBuilder[stringBuilder.Length - 1]))
			{
				stringBuilder.Length--;
			}
			return stringBuilder.ToString().Trim();
		}

		public static string NormalizePrefab(string? prefab)
		{
			if (string.IsNullOrWhiteSpace(prefab))
			{
				return string.Empty;
			}
			string text = prefab.Replace("(Clone)", string.Empty).Trim().ToLowerInvariant();
			StringBuilder stringBuilder = new StringBuilder(Math.Min(text.Length, 64));
			string text2 = text;
			foreach (char c in text2)
			{
				if (stringBuilder.Length >= 64)
				{
					break;
				}
				if ((c >= 'a' && c <= 'z') || (c >= '0' && c <= '9'))
				{
					stringBuilder.Append(c);
				}
				else if ((c == '_' || c == '-' || char.IsWhiteSpace(c)) && stringBuilder.Length > 0 && stringBuilder[stringBuilder.Length - 1] != '_')
				{
					stringBuilder.Append('_');
				}
			}
			return stringBuilder.ToString().Trim('_');
		}

		public static bool ValidSubmission(string? source, long sequence)
		{
			if (source == string.Empty)
			{
				return sequence == 0;
			}
			if (source != null && sequence > 0 && Guid.TryParseExact(source, "N", out var result))
			{
				return result.ToString("N") == source;
			}
			return false;
		}

		public static bool TryMetric(string? metric, out string canonical)
		{
			canonical = (metric ?? string.Empty).Trim().ToLowerInvariant();
			switch (canonical)
			{
			case "build.placed":
			case "farm.planted":
			case "time.seconds":
			case "build.repaired":
			case "craft.upgraded":
			case "craft.items":
			case "death.count":
			case "tree.felled":
			case "distance.sailed":
			case "distance.walked":
			case "distance.swam":
				return true;
			default:
			{
				int num = (canonical.StartsWith("kill.", StringComparison.Ordinal) ? 5 : (canonical.StartsWith("resource.", StringComparison.Ordinal) ? 9 : 0));
				if (num == 0)
				{
					return false;
				}
				string text = canonical.Substring(num);
				if (text.Length == 0 || text.Length > 64 || text == "total")
				{
					return false;
				}
				return text == NormalizePrefab(text);
			}
			}
		}
	}
	public sealed class StatsLedger
	{
		public const int MaxRecentEvents = 4096;

		public const int MaxPlayers = 1024;

		public const int MaxDynamicMetrics = 1024;

		public const int MaxMetricsPerPlayer = 1037;

		public const int MaxEventIdLength = 128;

		public const int MaxSubmissionSources = 16384;

		public const long MaxEventAmount = 1000000L;

		private readonly HashSet<string> eventIds;

		private readonly Dictionary<long, PlayerStats> players;

		public WorldSnapshot Snapshot { get; }

		public StatsLedger(WorldSnapshot snapshot)
		{
			if (snapshot == null)
			{
				throw new ArgumentNullException("snapshot");
			}
			if (snapshot.SchemaVersion != 1)
			{
				throw new ArgumentException("Unsupported statistics schema.", "snapshot");
			}
			Snapshot = new WorldSnapshot
			{
				WorldId = StatSanitizer.DisplayText(snapshot.WorldId, 128),
				WorldName = StatSanitizer.DisplayText(snapshot.WorldName),
				Revision = Math.Max(0L, snapshot.Revision)
			};
			eventIds = new HashSet<string>(StringComparer.Ordinal);
			players = new Dictionary<long, PlayerStats>();
			if (snapshot.LastSequences == null || snapshot.LastSequences.Count > 16384)
			{
				throw new ArgumentException("Invalid submission sequence history.", "snapshot");
			}
			foreach (KeyValuePair<string, long> lastSequence in snapshot.LastSequences)
			{
				if (lastSequence.Key.Length == 0 || !StatSanitizer.ValidSubmission(lastSequence.Key, lastSequence.Value))
				{
					throw new ArgumentException("Invalid submission sequence record.", "snapshot");
				}
				Snapshot.LastSequences.Add(lastSequence.Key, lastSequence.Value);
			}
			foreach (string item in (snapshot.RecentEventIds ?? new List<string>()).AsEnumerable().Reverse())
			{
				if (eventIds.Count >= 4096)
				{
					break;
				}
				if (ValidEventId(item) && eventIds.Add(item))
				{
					Snapshot.RecentEventIds.Add(item);
				}
			}
			Snapshot.RecentEventIds.Reverse();
			foreach (PlayerStats item2 in snapshot.Players ?? new List<PlayerStats>())
			{
				if (item2 == null || item2.PlayerId == 0L || players.ContainsKey(item2.PlayerId) || players.Count >= 1024)
				{
					continue;
				}
				PlayerStats playerStats = new PlayerStats
				{
					PlayerId = item2.PlayerId,
					Name = SafePlayerName(item2.Name)
				};
				foreach (KeyValuePair<string, long> item3 in item2.Values ?? new Dictionary<string, long>())
				{
					if (playerStats.Values.Count >= 1035)
					{
						break;
					}
					if (item3.Value >= 0 && StatSanitizer.TryMetric(item3.Key, out string canonical))
					{
						playerStats.Values[canonical] = item3.Value;
					}
				}
				RebuildTotal(playerStats.Values, "kill.", "kill.total");
				RebuildTotal(playerStats.Values, "resource.", "resource.total");
				players.Add(playerStats.PlayerId, playerStats);
				Snapshot.Players.Add(playerStats);
			}
			foreach (KeyValuePair<string, string> item4 in snapshot.MetricNames ?? new Dictionary<string, string>())
			{
				if (Snapshot.MetricNames.Count >= 1024)
				{
					break;
				}
				if (StatSanitizer.TryMetric(item4.Key, out string canonical2))
				{
					Snapshot.MetricNames[canonical2] = StatSanitizer.DisplayText(item4.Value);
				}
			}
		}

		public bool Apply(StatEvent statEvent, out FeedEntry feed)
		{
			feed = null;
			if (statEvent == null || statEvent.PlayerId == 0L || !ValidEventId(statEvent.EventId) || statEvent.Amount <= 0 || statEvent.Amount > 1000000 || Snapshot.Revision == long.MaxValue || !StatSanitizer.TryMetric(statEvent.Metric, out string canonical) || !StatSanitizer.ValidSubmission(statEvent.SubmissionSource, statEvent.SubmissionSequence))
			{
				return false;
			}
			if (statEvent.SubmissionSource.Length > 0)
			{
				long value;
				bool flag = Snapshot.LastSequences.TryGetValue(statEvent.SubmissionSource, out value);
				if ((flag && statEvent.SubmissionSequence <= value) || (!flag && Snapshot.LastSequences.Count >= 16384))
				{
					return false;
				}
			}
			if (eventIds.Contains(statEvent.EventId))
			{
				if (statEvent.SubmissionSource.Length > 0)
				{
					Snapshot.LastSequences[statEvent.SubmissionSource] = statEvent.SubmissionSequence;
					Snapshot.Revision++;
				}
				return false;
			}
			string text = (canonical.StartsWith("kill.", StringComparison.Ordinal) ? "kill.total" : (canonical.StartsWith("resource.", StringComparison.Ordinal) ? "resource.total" : null));
			PlayerStats value2;
			bool flag2 = !players.TryGetValue(statEvent.PlayerId, out value2);
			if (flag2)
			{
				if (players.Count >= 1024)
				{
					return false;
				}
				value2 = new PlayerStats
				{
					PlayerId = statEvent.PlayerId
				};
			}
			if (!value2.Values.ContainsKey(canonical) && value2.Values.Count >= 1037)
			{
				return false;
			}
			if (text != null && !Snapshot.MetricNames.ContainsKey(canonical) && Snapshot.MetricNames.Count >= 1024)
			{
				return false;
			}
			long num = Get(value2.Values, canonical);
			long num2 = ((text == null) ? 0 : Get(value2.Values, text));
			if (num < 0 || num > long.MaxValue - statEvent.Amount || (text != null && (num2 < 0 || num2 > long.MaxValue - statEvent.Amount)))
			{
				return false;
			}
			value2.Name = SafePlayerName(statEvent.PlayerName);
			string text2 = StatSanitizer.DisplayText(statEvent.Subject);
			value2.Values[canonical] = num + statEvent.Amount;
			if (text != null)
			{
				value2.Values[text] = num2 + statEvent.Amount;
				if (!string.IsNullOrEmpty(text2))
				{
					Snapshot.MetricNames[canonical] = text2;
				}
				else if (!Snapshot.MetricNames.ContainsKey(canonical))
				{
					Snapshot.MetricNames[canonical] = MetricCatalog.FriendlyName(canonical);
				}
			}
			if (flag2)
			{
				players.Add(value2.PlayerId, value2);
				Snapshot.Players.Add(value2);
			}
			eventIds.Add(statEvent.EventId);
			Snapshot.RecentEventIds.Add(statEvent.EventId);
			if (Snapshot.RecentEventIds.Count > 4096)
			{
				eventIds.Remove(Snapshot.RecentEventIds[0]);
				Snapshot.RecentEventIds.RemoveAt(0);
			}
			if (statEvent.SubmissionSource.Length > 0)
			{
				Snapshot.LastSequences[statEvent.SubmissionSource] = statEvent.SubmissionSequence;
			}
			Snapshot.Revision++;
			feed = new FeedEntry
			{
				PlayerName = value2.Name,
				Metric = canonical,
				Amount = statEvent.Amount,
				Subject = text2
			};
			return true;
		}

		private static string SafePlayerName(string? name)
		{
			string text = StatSanitizer.DisplayText(name);
			if (text.Length != 0)
			{
				return text;
			}
			return "Viking";
		}

		private static bool ValidEventId(string? value)
		{
			if (string.IsNullOrWhiteSpace(value) || value.Length > 128)
			{
				return false;
			}
			foreach (char c in value)
			{
				if (char.IsControl(c) || char.IsWhiteSpace(c))
				{
					return false;
				}
			}
			return true;
		}

		private static long Get(Dictionary<string, long> values, string key)
		{
			if (!values.TryGetValue(key, out var value))
			{
				return 0L;
			}
			return value;
		}

		private static void RebuildTotal(Dictionary<string, long> values, string prefix, string key)
		{
			long num = 0L;
			foreach (KeyValuePair<string, long> value in values)
			{
				if (value.Key.StartsWith(prefix, StringComparison.Ordinal))
				{
					num = ((value.Value > long.MaxValue - num) ? long.MaxValue : (num + value.Value));
				}
			}
			values[key] = num;
		}
	}
}

BepInEx/plugins/VikingChronicle/VikingChronicle.dll

Decompiled 5 days ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text;
using BepInEx;
using BepInEx.Configuration;
using HarmonyLib;
using Jotunn.Entities;
using Jotunn.Managers;
using Jotunn.Utils;
using Microsoft.CodeAnalysis;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using TMPro;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.UI;
using VikingChronicle.Core;
using VikingChronicle.Persistence;
using VikingChronicle.Tracking;
using VikingChronicle.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("Rinor")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyDescription("World leaderboards and a quiet live activity feed for Valheim friends.")]
[assembly: AssemblyFileVersion("0.1.0.0")]
[assembly: AssemblyInformationalVersion("0.1.0")]
[assembly: AssemblyProduct("VikingChronicle")]
[assembly: AssemblyTitle("VikingChronicle")]
[assembly: AssemblyVersion("0.1.0.0")]
[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 VikingChronicle
{
	[BepInPlugin("rinor.vikingchronicle", "Viking Chronicle", "0.1.0")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[NetworkCompatibility(/*Could not decode attribute arguments.*/)]
	public sealed class Plugin : BaseUnityPlugin
	{
		[HarmonyPatch(typeof(ZNet), "Shutdown")]
		private static class ShutdownPatch
		{
			private static void Prefix()
			{
				Tracker.Flush();
				if (_instance?._outbox != null)
				{
					_instance.SaveOutbox(_instance._pending.Values);
				}
				if ((Object)(object)_instance != (Object)null && Object.op_Implicit((Object)(object)ZNet.instance) && !ZNet.instance.IsServer() && ZRoutedRpc.instance != null && _instance._pending.Count > 0)
				{
					_instance.Send(ServerPeerId, new WireMessage
					{
						Type = "events",
						WorldId = _instance._worldId,
						Events = _instance.PendingBatch()
					});
				}
				_instance?.Save();
			}
		}

		private sealed class PendingFeed
		{
			public FeedEntry Entry;

			public float At;
		}

		private sealed class WireMessage
		{
			public int Protocol { get; set; } = 1;

			public string Type { get; set; } = "";

			public string WorldId { get; set; } = "";

			public List<StatEvent>? Events { get; set; }

			public List<string>? EventIds { get; set; }

			public WorldSnapshot? Snapshot { get; set; }

			public List<FeedEntry>? Feed { get; set; }
		}

		public const string PluginGuid = "rinor.vikingchronicle";

		public const string PluginName = "Viking Chronicle";

		public const string PluginVersion = "0.1.0";

		private const int Protocol = 1;

		private const int BatchLimit = 128;

		private static Plugin? _instance;

		private static readonly JsonSerializerSettings WireJson = new JsonSerializerSettings
		{
			TypeNameHandling = (TypeNameHandling)0,
			MaxDepth = 32
		};

		private readonly Dictionary<string, StatEvent> _pending = new Dictionary<string, StatEvent>();

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

		private readonly Dictionary<string, PendingFeed> _feedGroups = new Dictionary<string, PendingFeed>();

		private readonly Dictionary<long, float> _requestTimes = new Dictionary<long, float>();

		private ConfigEntry<KeyboardShortcut> _openKey;

		private ConfigEntry<KeyboardShortcut> _feedKey;

		private ConfigEntry<string> _language;

		private ConfigEntry<bool> _feedEnabled;

		private ConfigEntry<bool> _feedKills;

		private ConfigEntry<bool> _feedResources;

		private ConfigEntry<bool> _feedBuilding;

		private ConfigEntry<bool> _feedCrafting;

		private ConfigEntry<bool> _feedDeaths;

		private ConfigEntry<int> _feedLines;

		private ConfigEntry<float> _feedDuration;

		private ConfigEntry<float> _uiScale;

		private ConfigEntry<float> _feedX;

		private ConfigEntry<float> _feedY;

		private ConfigEntry<int> _saveInterval;

		private CustomRPC _rpc;

		private Harmony _harmony;

		private ChronicleUi? _ui;

		private WorldStore? _store;

		private ClientOutbox? _outbox;

		private StatsLedger? _ledger;

		private WorldSnapshot _view = new WorldSnapshot();

		private ZNet? _session;

		private string _worldId = "";

		private string _storageError = "";

		private string _submissionSource = "";

		private long _submissionSequence;

		private bool _receivedState;

		private bool _dirty;

		private long _broadcastRevision = -1L;

		private float _nextSend;

		private float _nextHello;

		private float _nextSave;

		private float _nextBroadcast;

		private bool German => _language.Value == "de";

		private static long ServerPeerId
		{
			get
			{
				ZNet instance = ZNet.instance;
				return ((instance == null) ? ((long?)null) : instance.GetServerPeer()?.m_uid).GetValueOrDefault();
			}
		}

		private void Awake()
		{
			//IL_0021: 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_009f: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a9: Expected O, but got Unknown
			//IL_00dd: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e7: Expected O, but got Unknown
			//IL_0130: Unknown result type (might be due to invalid IL or missing references)
			//IL_013a: Expected O, but got Unknown
			//IL_016e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0178: Expected O, but got Unknown
			//IL_0294: Unknown result type (might be due to invalid IL or missing references)
			//IL_029e: Expected O, but got Unknown
			//IL_02b5: Unknown result type (might be due to invalid IL or missing references)
			//IL_02c1: Unknown result type (might be due to invalid IL or missing references)
			//IL_02cb: Expected O, but got Unknown
			//IL_02cb: Expected O, but got Unknown
			//IL_02d6: Unknown result type (might be due to invalid IL or missing references)
			//IL_02e0: Expected O, but got Unknown
			//IL_030b: Unknown result type (might be due to invalid IL or missing references)
			//IL_031f: Expected O, but got Unknown
			//IL_031a: Unknown result type (might be due to invalid IL or missing references)
			_instance = this;
			_openKey = ((BaseUnityPlugin)this).Config.Bind<KeyboardShortcut>("Controls", "LeaderboardKey", new KeyboardShortcut((KeyCode)288, Array.Empty<KeyCode>()), "Open or close the leaderboard.");
			_feedKey = ((BaseUnityPlugin)this).Config.Bind<KeyboardShortcut>("Controls", "FeedToggleKey", new KeyboardShortcut((KeyCode)289, Array.Empty<KeyCode>()), "Show or hide the live feed.");
			_language = ((BaseUnityPlugin)this).Config.Bind<string>("Interface", "Language", "de", new ConfigDescription("Interface language; creature/item names follow the game's language.", (AcceptableValueBase)(object)new AcceptableValueList<string>(new string[2] { "de", "en" }), Array.Empty<object>()));
			_uiScale = ((BaseUnityPlugin)this).Config.Bind<float>("Interface", "Scale", 1f, new ConfigDescription("Scale of the leaderboard and activity feed.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.65f, 1.5f), Array.Empty<object>()));
			_feedEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("Feed", "Enabled", true, "Show activity feed.");
			_feedLines = ((BaseUnityPlugin)this).Config.Bind<int>("Feed", "VisibleLines", 4, new ConfigDescription("Maximum visible messages.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 8), Array.Empty<object>()));
			_feedDuration = ((BaseUnityPlugin)this).Config.Bind<float>("Feed", "MessageSeconds", 7f, new ConfigDescription("How long a message remains visible.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(2f, 30f), Array.Empty<object>()));
			_feedX = ((BaseUnityPlugin)this).Config.Bind<float>("Feed", "OffsetFromRight", 24f, "Horizontal offset from right screen edge in UI units.");
			_feedY = ((BaseUnityPlugin)this).Config.Bind<float>("Feed", "OffsetFromTop", 260f, "Vertical offset below minimap in UI units.");
			_feedKills = ((BaseUnityPlugin)this).Config.Bind<bool>("Feed", "Kills", true, "Show creature kills.");
			_feedResources = ((BaseUnityPlugin)this).Config.Bind<bool>("Feed", "Resources", true, "Show resource harvests, grouped over 3 seconds.");
			_feedDeaths = ((BaseUnityPlugin)this).Config.Bind<bool>("Feed", "Deaths", true, "Show player deaths.");
			_feedBuilding = ((BaseUnityPlugin)this).Config.Bind<bool>("Feed", "Building", false, "Show building, planting, and repairs.");
			_feedCrafting = ((BaseUnityPlugin)this).Config.Bind<bool>("Feed", "Crafting", false, "Show crafting and upgrades.");
			_saveInterval = ((BaseUnityPlugin)this).Config.Bind<int>("Server", "AutosaveSeconds", 30, new ConfigDescription("Server snapshot interval. Restart the server after changing this setting.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(5, 300), Array.Empty<object>()));
			_rpc = NetworkManager.Instance.AddRPC("Chronicle_v1", new CoroutineHandler(ReceiveServer), new CoroutineHandler(ReceiveClient));
			_harmony = new Harmony("rinor.vikingchronicle");
			_harmony.PatchAll(typeof(Plugin).Assembly);
			new ConsoleCommand("chronicle", "Open Viking Chronicle leaderboard", (ConsoleEvent)delegate
			{
				_ui?.Toggle();
			}, false, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false);
			((BaseUnityPlugin)this).Logger.LogInfo((object)"Viking Chronicle 0.1.0 loaded. F7: leaderboard; F8: feed.");
		}

		private UiSettings ReadUiSettings()
		{
			return new UiSettings
			{
				Language = _language.Value,
				FeedEnabled = _feedEnabled.Value,
				FeedDuration = _feedDuration.Value,
				FeedLines = _feedLines.Value,
				Scale = _uiScale.Value,
				FeedOffsetX = _feedX.Value,
				FeedOffsetY = _feedY.Value
			};
		}

		private void Update()
		{
			//IL_005a: 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_008f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0094: Unknown result type (might be due to invalid IL or missing references)
			//IL_00af: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b4: Unknown result type (might be due to invalid IL or missing references)
			if (!EnsureSession())
			{
				return;
			}
			float unscaledTime = Time.unscaledTime;
			if (!ZNet.instance.IsDedicated())
			{
				if (_ui == null)
				{
					_ui = new ChronicleUi(ReadUiSettings);
				}
				Tracker.Update(Time.unscaledDeltaTime);
				KeyboardShortcut value;
				if (_ui.IsOpen)
				{
					value = _openKey.Value;
					if (((KeyboardShortcut)(ref value)).IsDown())
					{
						_ui.Close();
					}
				}
				else if (Object.op_Implicit((Object)(object)Player.m_localPlayer) && !InputBusy())
				{
					value = _openKey.Value;
					if (((KeyboardShortcut)(ref value)).IsDown())
					{
						_ui.Toggle();
					}
					value = _feedKey.Value;
					if (((KeyboardShortcut)(ref value)).IsDown())
					{
						_feedEnabled.Value = !_feedEnabled.Value;
					}
				}
				string status = ((_storageError.Length > 0) ? _storageError : ((!_receivedState && !ZNet.instance.IsServer()) ? (German ? "Warte auf Statistik-Server …" : "Waiting for statistics server …") : (German ? "Mit der Welt synchronisiert" : "Synced with this world")));
				_ui.Tick(_view, status);
			}
			if (ZNet.instance.IsServer())
			{
				if (_ledger == null)
				{
					return;
				}
				FlushFeeds(unscaledTime);
				if (unscaledTime >= _nextBroadcast && _ledger.Snapshot.Revision != _broadcastRevision)
				{
					_nextBroadcast = unscaledTime + 2f;
					_broadcastRevision = _ledger.Snapshot.Revision;
					WireMessage message = StateMessage();
					long[] array = _subscribers.ToArray();
					foreach (long num in array)
					{
						ZNetPeer peer = ZNet.instance.GetPeer(num);
						if (peer == null || !peer.IsReady())
						{
							_subscribers.Remove(num);
							_requestTimes.Remove(num);
						}
						else
						{
							Send(num, message);
						}
					}
					_view = _ledger.Snapshot;
				}
				if (unscaledTime >= _nextSave)
				{
					_nextSave = unscaledTime + (float)_saveInterval.Value;
					Save();
				}
			}
			else
			{
				if (ZRoutedRpc.instance == null || !Object.op_Implicit((Object)(object)Player.m_localPlayer))
				{
					return;
				}
				if (unscaledTime >= _nextHello)
				{
					_nextHello = unscaledTime + (_receivedState ? 30f : 5f);
					Send(ServerPeerId, new WireMessage
					{
						Type = "hello",
						WorldId = _worldId
					});
				}
				if (_receivedState && unscaledTime >= _nextSend && _pending.Count > 0)
				{
					_nextSend = unscaledTime + 1f;
					if (SaveOutbox(_pending.Values))
					{
						Send(ServerPeerId, new WireMessage
						{
							Type = "events",
							WorldId = _worldId,
							Events = PendingBatch()
						});
					}
				}
			}
		}

		private static bool InputBusy()
		{
			if ((!Object.op_Implicit((Object)(object)Chat.instance) || !Chat.instance.HasFocus()) && !Console.IsVisible() && !TextInput.IsVisible() && !InventoryGui.IsVisible() && !Menu.IsVisible() && !Minimap.IsOpen() && !StoreGui.IsVisible() && !Hud.IsPieceSelectionVisible())
			{
				return UnifiedPopup.IsVisible();
			}
			return true;
		}

		private bool EnsureSession()
		{
			//IL_009f: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ab: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c5: Expected O, but got Unknown
			//IL_013c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0146: Expected O, but got Unknown
			ZNet instance = ZNet.instance;
			if (!Object.op_Implicit((Object)(object)instance) || !Object.op_Implicit((Object)(object)Game.instance) || instance.GetWorld() == null)
			{
				if (_worldId.Length > 0)
				{
					EndSession();
				}
				return false;
			}
			string text = instance.GetWorldUID().ToString(CultureInfo.InvariantCulture);
			if ((Object)(object)_session == (Object)(object)instance && _worldId == text)
			{
				return true;
			}
			EndSession();
			_session = instance;
			_worldId = text;
			_submissionSource = Guid.NewGuid().ToString("N");
			_submissionSequence = 0L;
			_view = new WorldSnapshot
			{
				WorldId = text,
				WorldName = (instance.GetWorldName() ?? "Valheim")
			};
			_nextHello = (_nextSend = (_nextBroadcast = 0f));
			_nextSave = Time.unscaledTime + (float)_saveInterval.Value;
			if (instance.IsServer())
			{
				try
				{
					_store = new WorldStore(Path.Combine(Paths.ConfigPath, "VikingChronicle"), text);
					_ledger = new StatsLedger(_store.Load(text, _view.WorldName));
					_ledger.Snapshot.WorldName = _view.WorldName;
					_view = _ledger.Snapshot;
					if (_store.RecoveryNotice != null)
					{
						((BaseUnityPlugin)this).Logger.LogWarning((object)_store.RecoveryNotice);
					}
					((BaseUnityPlugin)this).Logger.LogInfo((object)("Statistics loaded: " + _store.FilePath));
				}
				catch (Exception ex)
				{
					_store = null;
					_storageError = (German ? "Statistikdatei konnte nicht geladen werden; siehe Log." : "Statistics load failed; see log.");
					((BaseUnityPlugin)this).Logger.LogError((object)ex);
				}
			}
			else
			{
				try
				{
					_outbox = new ClientOutbox(Path.Combine(Paths.ConfigPath, "VikingChronicleClient"), text);
					foreach (StatEvent item in _outbox.Load())
					{
						_pending[item.EventId] = item;
					}
					if (_outbox.RecoveryNotice != null)
					{
						((BaseUnityPlugin)this).Logger.LogWarning((object)_outbox.RecoveryNotice);
					}
				}
				catch (Exception ex2)
				{
					_outbox = null;
					_storageError = (German ? "Lokale Ereignisdatei nicht lesbar; siehe Log." : "Local event queue could not be loaded; see log.");
					((BaseUnityPlugin)this).Logger.LogError((object)ex2);
				}
			}
			return true;
		}

		public static void Record(long playerId, string playerName, string metric, long amount, string subject = "", string? eventId = null)
		{
			//IL_0034: Unknown result type (might be due to invalid IL or missing references)
			//IL_0039: Unknown result type (might be due to invalid IL or missing references)
			//IL_0057: Unknown result type (might be due to invalid IL or missing references)
			//IL_005e: Unknown result type (might be due to invalid IL or missing references)
			//IL_006c: 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_007a: Unknown result type (might be due to invalid IL or missing references)
			//IL_008a: Expected O, but got Unknown
			Plugin instance = _instance;
			string metric2 = default(string);
			if ((Object)(object)instance == (Object)null || playerId == 0L || !instance.EnsureSession() || amount <= 0 || amount > 1000000 || !StatSanitizer.TryMetric(metric, ref metric2))
			{
				return;
			}
			StatEvent val = new StatEvent
			{
				EventId = (eventId ?? Guid.NewGuid().ToString("N")),
				PlayerId = playerId,
				PlayerName = StatSanitizer.DisplayText(playerName, 64),
				Metric = metric2,
				Amount = amount,
				Subject = StatSanitizer.DisplayText(subject, 64)
			};
			if (ZNet.instance.IsServer())
			{
				instance.Apply(val);
			}
			else if (instance._outbox != null)
			{
				if (instance._pending.Count < 4096 && !instance._pending.ContainsKey(val.EventId))
				{
					val.SubmissionSource = instance._submissionSource;
					val.SubmissionSequence = ++instance._submissionSequence;
					instance._pending[val.EventId] = val;
				}
				else if (!instance._pending.ContainsKey(val.EventId))
				{
					((BaseUnityPlugin)instance).Logger.LogWarning((object)"Statistics event queue is full; server has not acknowledged events.");
				}
			}
		}

		private void Apply(StatEvent entry)
		{
			if (_ledger == null)
			{
				return;
			}
			long revision = _ledger.Snapshot.Revision;
			FeedEntry val = default(FeedEntry);
			bool num = _ledger.Apply(entry, ref val);
			if (_ledger.Snapshot.Revision != revision)
			{
				_dirty = true;
			}
			if (!num)
			{
				return;
			}
			_view = _ledger.Snapshot;
			if (val == null || entry.Metric.StartsWith("time.", StringComparison.Ordinal) || entry.Metric.StartsWith("distance.", StringComparison.Ordinal))
			{
				return;
			}
			if (entry.Metric.StartsWith("kill.", StringComparison.Ordinal) || entry.Metric == "death.count")
			{
				BroadcastFeed(new List<FeedEntry> { val });
				return;
			}
			string key = entry.PlayerId + ":" + entry.Metric + ":" + val.Subject;
			if (_feedGroups.TryGetValue(key, out PendingFeed value))
			{
				FeedEntry entry2 = value.Entry;
				entry2.Amount += val.Amount;
			}
			else
			{
				_feedGroups[key] = new PendingFeed
				{
					Entry = val,
					At = Time.unscaledTime + 3f
				};
			}
		}

		private IEnumerator ReceiveServer(long sender, ZPackage package)
		{
			if (!EnsureSession() || !ZNet.instance.IsServer() || _ledger == null)
			{
				yield break;
			}
			ZNetPeer peer = ZNet.instance.GetPeer(sender);
			if (peer == null || !peer.IsReady())
			{
				yield break;
			}
			WireMessage wireMessage = Decode(package, 262144);
			if (wireMessage == null || wireMessage.WorldId != _worldId)
			{
				yield break;
			}
			if (wireMessage.Type == "hello")
			{
				if (!_requestTimes.TryGetValue(sender, out var value) || Time.unscaledTime - value >= 2f)
				{
					_requestTimes[sender] = Time.unscaledTime;
					_subscribers.Add(sender);
					Send(sender, StateMessage());
				}
			}
			else
			{
				if (!(wireMessage.Type == "events") || wireMessage.Events == null || wireMessage.Events.Count > 128 || peer.m_playerID == 0L)
				{
					yield break;
				}
				List<string> list = new List<string>();
				foreach (StatEvent @event in wireMessage.Events)
				{
					if (@event != null && !string.IsNullOrEmpty(@event.EventId) && @event.EventId.Length <= 128)
					{
						string text = CanonicalName(@event.PlayerId);
						if (text == null)
						{
							list.Add(@event.EventId);
							((BaseUnityPlugin)this).Logger.LogWarning((object)("Ignored event for an unknown character ID: " + @event.PlayerId));
						}
						else
						{
							@event.PlayerName = text;
							Apply(@event);
							list.Add(@event.EventId);
						}
					}
				}
				if (Save())
				{
					Send(sender, new WireMessage
					{
						Type = "ack",
						WorldId = _worldId,
						EventIds = list
					});
				}
			}
		}

		private string? CanonicalName(long playerId)
		{
			if (Object.op_Implicit((Object)(object)Player.m_localPlayer) && Player.m_localPlayer.GetPlayerID() == playerId)
			{
				return Player.m_localPlayer.GetPlayerName();
			}
			object obj = ((IEnumerable<ZNetPeer>)ZNet.instance.GetPeers()).FirstOrDefault((Func<ZNetPeer, bool>)((ZNetPeer peer) => peer.IsReady() && peer.m_playerID == playerId && playerId != 0))?.m_playerName;
			if (obj == null)
			{
				StatsLedger? ledger = _ledger;
				if (ledger == null)
				{
					return null;
				}
				PlayerStats? obj2 = ((IEnumerable<PlayerStats>)ledger.Snapshot.Players).FirstOrDefault((Func<PlayerStats, bool>)((PlayerStats player) => player.PlayerId == playerId));
				if (obj2 == null)
				{
					return null;
				}
				obj = obj2.Name;
			}
			return (string?)obj;
		}

		private IEnumerator ReceiveClient(long sender, ZPackage package)
		{
			if (!EnsureSession() || ZNet.instance.IsServer() || ZRoutedRpc.instance == null || sender != ServerPeerId)
			{
				yield break;
			}
			WireMessage wireMessage = Decode(package, 4194304);
			if (wireMessage == null || wireMessage.WorldId != _worldId)
			{
				yield break;
			}
			if (wireMessage.Type == "state" && wireMessage.Snapshot != null && wireMessage.Snapshot.WorldId == _worldId && wireMessage.Snapshot.SchemaVersion == 1 && wireMessage.Snapshot.Players != null && wireMessage.Snapshot.MetricNames != null)
			{
				if (!_receivedState || wireMessage.Snapshot.Revision >= _view.Revision)
				{
					_view = wireMessage.Snapshot;
				}
				_receivedState = true;
			}
			else
			{
				if (wireMessage.Type == "ack" && wireMessage.EventIds != null)
				{
					HashSet<string> acknowledged = new HashSet<string>(wireMessage.EventIds.Where((string id) => id != null));
					List<StatEvent> entries = _pending.Values.Where((StatEvent entry) => !acknowledged.Contains(entry.EventId)).ToList();
					if (!SaveOutbox(entries))
					{
						yield break;
					}
					{
						foreach (string item in acknowledged)
						{
							_pending.Remove(item);
						}
						yield break;
					}
				}
				if (!(wireMessage.Type == "feed") || wireMessage.Feed == null)
				{
					yield break;
				}
				foreach (FeedEntry item2 in wireMessage.Feed.Take(32))
				{
					ShowFeed(item2);
				}
			}
		}

		private WireMessage? Decode(ZPackage package, int maxBytes)
		{
			try
			{
				if (package.Size() > maxBytes)
				{
					return null;
				}
				WireMessage wireMessage = JsonConvert.DeserializeObject<WireMessage>(package.ReadString(), WireJson);
				return (wireMessage != null && wireMessage.Protocol == 1) ? wireMessage : null;
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)("Ignored malformed statistics packet: " + ex.Message));
				return null;
			}
		}

		private void Send(long target, WireMessage message)
		{
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			//IL_001d: Expected O, but got Unknown
			if (target != 0L && Object.op_Implicit((Object)(object)ZNet.instance) && ZRoutedRpc.instance != null)
			{
				ZPackage val = new ZPackage();
				val.Write(JsonConvert.SerializeObject((object)message, WireJson));
				_rpc.SendPackage(target, val);
			}
		}

		private WireMessage StateMessage()
		{
			//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_003a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0046: Unknown result type (might be due to invalid IL or missing references)
			//IL_0052: Unknown result type (might be due to invalid IL or missing references)
			//IL_005e: Unknown result type (might be due to invalid IL or missing references)
			//IL_006a: Unknown result type (might be due to invalid IL or missing references)
			//IL_007b: Expected O, but got Unknown
			WorldSnapshot snapshot = _ledger.Snapshot;
			return new WireMessage
			{
				Type = "state",
				WorldId = _worldId,
				Snapshot = new WorldSnapshot
				{
					SchemaVersion = snapshot.SchemaVersion,
					WorldId = snapshot.WorldId,
					WorldName = snapshot.WorldName,
					Revision = snapshot.Revision,
					Players = snapshot.Players,
					MetricNames = snapshot.MetricNames
				}
			};
		}

		private void FlushFeeds(float now)
		{
			KeyValuePair<string, PendingFeed>[] array = _feedGroups.Where<KeyValuePair<string, PendingFeed>>((KeyValuePair<string, PendingFeed> item) => item.Value.At <= now).ToArray();
			KeyValuePair<string, PendingFeed>[] array2 = array;
			foreach (KeyValuePair<string, PendingFeed> keyValuePair in array2)
			{
				_feedGroups.Remove(keyValuePair.Key);
			}
			if (array.Length != 0)
			{
				BroadcastFeed(array.Select((KeyValuePair<string, PendingFeed> item) => item.Value.Entry).ToList());
			}
		}

		private void BroadcastFeed(List<FeedEntry> entries)
		{
			foreach (FeedEntry entry in entries)
			{
				ShowFeed(entry);
			}
			long[] array = _subscribers.ToArray();
			foreach (long num in array)
			{
				ZNetPeer peer = ZNet.instance.GetPeer(num);
				if (peer != null && peer.IsReady())
				{
					Send(num, new WireMessage
					{
						Type = "feed",
						WorldId = _worldId,
						Feed = entries
					});
				}
			}
		}

		private void ShowFeed(FeedEntry entry)
		{
			if (entry != null && !string.IsNullOrEmpty(entry.Metric))
			{
				string metric = entry.Metric;
				if (metric.StartsWith("kill.", StringComparison.Ordinal) ? _feedKills.Value : (metric.StartsWith("resource.", StringComparison.Ordinal) ? _feedResources.Value : (metric.StartsWith("craft.", StringComparison.Ordinal) ? _feedCrafting.Value : ((metric == "death.count") ? _feedDeaths.Value : _feedBuilding.Value))))
				{
					_ui?.AddFeed(entry);
				}
			}
		}

		private List<StatEvent> PendingBatch()
		{
			return _pending.Values.OrderBy<StatEvent, string>((StatEvent entry) => entry.SubmissionSource, StringComparer.Ordinal).ThenBy((StatEvent entry) => entry.SubmissionSequence).Take(128)
				.ToList();
		}

		private bool SaveOutbox(IEnumerable<StatEvent> entries)
		{
			if (_outbox == null)
			{
				return false;
			}
			try
			{
				_outbox.Save(entries);
				_storageError = "";
				return true;
			}
			catch (Exception ex)
			{
				_storageError = (German ? "Ereignisdatei konnte nicht gespeichert werden; siehe Log." : "Event queue could not be saved; see log.");
				((BaseUnityPlugin)this).Logger.LogError((object)("Could not save client event queue: " + ex));
				return false;
			}
		}

		private bool Save()
		{
			if (_store == null || _ledger == null)
			{
				return false;
			}
			if (!_dirty)
			{
				return true;
			}
			try
			{
				_store.Save(_ledger.Snapshot);
				_dirty = false;
				_storageError = "";
				return true;
			}
			catch (Exception ex)
			{
				_storageError = (German ? "Speichern fehlgeschlagen; siehe Log." : "Saving failed; see log.");
				((BaseUnityPlugin)this).Logger.LogError((object)("Could not save statistics. Will retry: " + ex));
				return false;
			}
		}

		private void EndSession()
		{
			//IL_00b5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bf: Expected O, but got Unknown
			Save();
			if (_outbox != null)
			{
				SaveOutbox(_pending.Values);
			}
			Tracker.Reset();
			_ui?.Dispose();
			_ui = null;
			_store = null;
			_outbox = null;
			_ledger = null;
			_session = null;
			_worldId = "";
			_pending.Clear();
			_subscribers.Clear();
			_feedGroups.Clear();
			_requestTimes.Clear();
			_receivedState = (_dirty = false);
			_broadcastRevision = -1L;
			_storageError = "";
			_view = new WorldSnapshot();
		}

		private void OnApplicationQuit()
		{
			Tracker.Flush();
			Save();
			if (_outbox != null)
			{
				SaveOutbox(_pending.Values);
			}
		}

		private void OnDestroy()
		{
			EndSession();
			Harmony harmony = _harmony;
			if (harmony != null)
			{
				harmony.UnpatchSelf();
			}
			_instance = null;
		}
	}
}
namespace VikingChronicle.UI
{
	public sealed class ChronicleUi : IDisposable
	{
		private sealed class LiveLine
		{
			public readonly FeedEntry Entry;

			public readonly float Started;

			public LiveLine(FeedEntry entry, float started)
			{
				Entry = entry;
				Started = started;
			}
		}

		private sealed class FeedVisual
		{
			public readonly GameObject Root;

			public readonly CanvasGroup Fade;

			public readonly TextMeshProUGUI Label;

			public FeedVisual(GameObject root, CanvasGroup fade, TextMeshProUGUI label)
			{
				Root = root;
				Fade = fade;
				Label = label;
			}
		}

		private sealed class TabVisual
		{
			public readonly string Id;

			public readonly Button Button;

			public readonly TextMeshProUGUI Label;

			public TabVisual(string id, Button button, TextMeshProUGUI label)
			{
				Id = id;
				Button = button;
				Label = label;
			}
		}

		private const float BoardWidth = 1000f;

		private const float BoardHeight = 658f;

		private const float TableWidth = 928f;

		private const float NameWidth = 218f;

		private const float ColumnWidth = 128f;

		private const float RowHeight = 39f;

		private const float HeaderHeight = 52f;

		private const float BodyHeight = 335f;

		private const float FeedWidth = 400f;

		private const float FeedRowHeight = 48f;

		private static readonly Color Gold = new Color(1f, 0.72f, 0.27f);

		private static readonly Color Pale = new Color(0.91f, 0.87f, 0.77f);

		private static readonly Color Muted = new Color(0.65f, 0.62f, 0.56f);

		private static readonly Color Inset = new Color(0.07f, 0.055f, 0.06f, 0.82f);

		private static readonly Vector2 TopLeft = new Vector2(0f, 1f);

		private static readonly Vector2 Center = new Vector2(0.5f, 0.5f);

		private readonly Func<UiSettings> _getSettings;

		private readonly List<LiveLine> _feed = new List<LiveLine>();

		private readonly List<FeedVisual> _feedVisuals = new List<FeedVisual>();

		private readonly List<TabVisual> _tabVisuals = new List<TabVisual>();

		private GameObject _board;

		private GameObject _feedRoot;

		private RectTransform _boardRect;

		private RectTransform _feedRect;

		private RectTransform _tabsRoot;

		private RectTransform _headerContent;

		private RectTransform _namesContent;

		private RectTransform _bodyContent;

		private ScrollRect _scroll;

		private TextMeshProUGUI _title;

		private TextMeshProUGUI _subtitle;

		private TextMeshProUGUI _status;

		private TextMeshProUGUI _hint;

		private TextMeshProUGUI _namesHeading;

		private TextMeshProUGUI _empty;

		private TextMeshProUGUI _closeLabel;

		private TextMeshProUGUI _feedTitle;

		private WorldSnapshot? _snapshot;

		private IReadOnlyList<StatTab> _tabs = new List<StatTab>();

		private string _tabId = "hunting";

		private string _sortKey = "kill.total";

		private bool _sortDescending = true;

		private bool _german = true;

		private bool _inputOwned;

		private bool _dirty = true;

		private long _lastRevision = -1L;

		private string _worldId = "";

		private int _releaseAtFrame = -1;

		private float _nextRender;

		private string _connectionStatus = "";

		public bool IsOpen
		{
			get
			{
				if (Object.op_Implicit((Object)(object)_board))
				{
					return _board.activeSelf;
				}
				return false;
			}
		}

		public ChronicleUi(Func<UiSettings> settings)
		{
			_getSettings = settings ?? ((Func<UiSettings>)(() => new UiSettings()));
		}

		public void Tick(WorldSnapshot? snapshot, string status)
		{
			if (_releaseAtFrame >= 0 && Time.frameCount >= _releaseAtFrame)
			{
				ReleaseInput();
			}
			if (!Object.op_Implicit((Object)(object)Player.m_localPlayer) || !Object.op_Implicit((Object)(object)GUIManager.CustomGUIFront))
			{
				Close();
				if (Object.op_Implicit((Object)(object)_feedRoot))
				{
					_feedRoot.SetActive(false);
				}
				_feed.Clear();
				_snapshot = null;
				_lastRevision = -1L;
				_worldId = "";
				return;
			}
			UiSettings uiSettings = _getSettings() ?? new UiSettings();
			bool flag = !string.Equals(uiSettings.Language, "en", StringComparison.OrdinalIgnoreCase);
			if (_german != flag)
			{
				_german = flag;
				_dirty = true;
				_lastRevision = -1L;
			}
			_snapshot = snapshot;
			_connectionStatus = status ?? "";
			if (snapshot != null && !string.Equals(_worldId, Convert.ToString(snapshot.WorldId), StringComparison.Ordinal))
			{
				_worldId = snapshot.WorldId ?? "";
				_lastRevision = -1L;
				_dirty = true;
				_feed.Clear();
			}
			EnsureCreated();
			UpdateScale(uiSettings);
			if (IsOpen)
			{
				if (Input.GetKeyDown((KeyCode)27))
				{
					_board.SetActive(false);
					_releaseAtFrame = Time.frameCount + 1;
				}
				else if (_dirty || (snapshot != null && snapshot.Revision != _lastRevision && Time.unscaledTime >= _nextRender))
				{
					RenderTable();
				}
				((TMP_Text)_status).text = _connectionStatus;
			}
			UpdateFeed(uiSettings);
		}

		public void Toggle()
		{
			if (IsOpen)
			{
				Close();
			}
			else if (Object.op_Implicit((Object)(object)Player.m_localPlayer) && Object.op_Implicit((Object)(object)GUIManager.CustomGUIFront))
			{
				EnsureCreated();
				_board.SetActive(true);
				_board.transform.SetAsLastSibling();
				_releaseAtFrame = -1;
				if (!_inputOwned)
				{
					GUIManager.BlockInput(true);
					_inputOwned = true;
				}
				_dirty = true;
				RenderTable();
			}
		}

		public void Close()
		{
			if (Object.op_Implicit((Object)(object)_board))
			{
				_board.SetActive(false);
			}
			ReleaseInput();
		}

		public void AddFeed(FeedEntry entry)
		{
			if (entry == null || !Object.op_Implicit((Object)(object)Player.m_localPlayer))
			{
				return;
			}
			UiSettings uiSettings = _getSettings() ?? new UiSettings();
			if (uiSettings.FeedEnabled && !string.IsNullOrWhiteSpace(FeedText.Format(entry, _german)))
			{
				_feed.Insert(0, new LiveLine(entry, Time.unscaledTime));
				int num = Mathf.Clamp(uiSettings.FeedLines, 1, 8);
				while (_feed.Count > num)
				{
					_feed.RemoveAt(_feed.Count - 1);
				}
			}
		}

		public void Dispose()
		{
			Close();
			if (Object.op_Implicit((Object)(object)_board))
			{
				Object.Destroy((Object)(object)_board);
			}
			if (Object.op_Implicit((Object)(object)_feedRoot))
			{
				Object.Destroy((Object)(object)_feedRoot);
			}
			_board = null;
			_feedRoot = null;
			_feedVisuals.Clear();
			_feed.Clear();
			_tabVisuals.Clear();
		}

		private void ReleaseInput()
		{
			_releaseAtFrame = -1;
			if (_inputOwned)
			{
				GUIManager.BlockInput(false);
				_inputOwned = false;
			}
		}

		private void EnsureCreated()
		{
			//IL_004a: 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_0054: 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_0094: Expected O, but got Unknown
			//IL_009a: 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)
			//IL_011e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0212: Unknown result type (might be due to invalid IL or missing references)
			//IL_0261: Unknown result type (might be due to invalid IL or missing references)
			//IL_02c6: Unknown result type (might be due to invalid IL or missing references)
			//IL_02e4: Expected O, but got Unknown
			//IL_030a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0314: Expected O, but got Unknown
			//IL_0347: Unknown result type (might be due to invalid IL or missing references)
			//IL_0351: Expected O, but got Unknown
			//IL_036e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0374: Unknown result type (might be due to invalid IL or missing references)
			//IL_038a: Unknown result type (might be due to invalid IL or missing references)
			//IL_03a4: Unknown result type (might be due to invalid IL or missing references)
			//IL_03e1: Unknown result type (might be due to invalid IL or missing references)
			//IL_0481: Unknown result type (might be due to invalid IL or missing references)
			//IL_0501: Unknown result type (might be due to invalid IL or missing references)
			//IL_0521: Unknown result type (might be due to invalid IL or missing references)
			if (!Object.op_Implicit((Object)(object)_board) || !Object.op_Implicit((Object)(object)_feedRoot))
			{
				_tabVisuals.Clear();
				_feedVisuals.Clear();
				_dirty = true;
				GUIManager instance = GUIManager.Instance;
				_board = instance.CreateWoodpanel(GUIManager.CustomGUIFront.transform, Center, Center, Vector2.zero, 1000f, 658f, false);
				((Object)_board).name = "VikingChronicleLeaderboard";
				_boardRect = (RectTransform)_board.transform;
				_boardRect.pivot = Center;
				_title = Label(_board.transform, "ChronicleTitle", "", 38f, Gold, norse: true);
				Position(((TMP_Text)_title).rectTransform, 52f, 28f, 896f, 48f);
				((TMP_Text)_title).alignment = (TextAlignmentOptions)514;
				_subtitle = Label(_board.transform, "WorldSubtitle", "", 17f, Muted);
				Position(((TMP_Text)_subtitle).rectTransform, 40f, 81f, 920f, 25f);
				((TMP_Text)_subtitle).alignment = (TextAlignmentOptions)514;
				Rule(_board.transform, 39f, 116f, 922f);
				_tabsRoot = Rect("CategoryTabs", _board.transform);
				Position(_tabsRoot, 36f, 131f, 928f, 43f);
				HorizontalLayoutGroup obj = ((Component)_tabsRoot).gameObject.AddComponent<HorizontalLayoutGroup>();
				((HorizontalOrVerticalLayoutGroup)obj).spacing = 8f;
				((HorizontalOrVerticalLayoutGroup)obj).childControlWidth = true;
				((HorizontalOrVerticalLayoutGroup)obj).childControlHeight = true;
				((HorizontalOrVerticalLayoutGroup)obj).childForceExpandWidth = true;
				((HorizontalOrVerticalLayoutGroup)obj).childForceExpandHeight = true;
				BuildTable();
				_hint = Label(_board.transform, "SortHint", "", 15f, Muted);
				Position(((TMP_Text)_hint).rectTransform, 39f, 598f, 725f, 23f);
				_status = Label(_board.transform, "ConnectionStatus", "", 14f, Muted);
				Position(((TMP_Text)_status).rectTransform, 39f, 621f, 725f, 20f);
				Position((RectTransform)((Component)NativeButton(_board.transform, "Close", "", Close, out _closeLabel)).transform, 797f, 595f, 165f, 41f);
				_feedRoot = new GameObject("VikingChronicleLiveFeed", new Type[2]
				{
					typeof(RectTransform),
					typeof(CanvasGroup)
				});
				_feedRoot.layer = 5;
				_feedRoot.transform.SetParent(GUIManager.CustomGUIFront.transform, false);
				_feedRect = (RectTransform)_feedRoot.transform;
				RectTransform feedRect = _feedRect;
				RectTransform feedRect2 = _feedRect;
				Vector2 val = default(Vector2);
				((Vector2)(ref val))..ctor(1f, 1f);
				feedRect2.anchorMax = val;
				feedRect.anchorMin = val;
				_feedRect.pivot = new Vector2(1f, 1f);
				_feedRect.sizeDelta = new Vector2(400f, 430f);
				CanvasGroup component = _feedRoot.GetComponent<CanvasGroup>();
				component.blocksRaycasts = false;
				component.interactable = false;
				_feedTitle = Label(_feedRoot.transform, "FeedTitle", "", 19f, Gold, norse: true);
				Position(((TMP_Text)_feedTitle).rectTransform, 13f, 0f, 374f, 27f);
				for (int i = 0; i < 8; i++)
				{
					RectTransform val2 = Rect("FeedLine" + i, _feedRoot.transform);
					Position(val2, 0f, 31f + (float)i * 48f, 400f, 44f);
					Image obj2 = ((Component)val2).gameObject.AddComponent<Image>();
					((Graphic)obj2).color = new Color(0.095f, 0.07f, 0.065f, 0.79f);
					((Graphic)obj2).raycastTarget = false;
					CanvasGroup val3 = ((Component)val2).gameObject.AddComponent<CanvasGroup>();
					val3.blocksRaycasts = false;
					val3.interactable = false;
					RectTransform obj3 = Rect("GoldEdge", (Transform)(object)val2);
					Position(obj3, 0f, 0f, 2f, 44f);
					Image obj4 = ((Component)obj3).gameObject.AddComponent<Image>();
					((Graphic)obj4).color = new Color(Gold.r, Gold.g, Gold.b, 0.68f);
					((Graphic)obj4).raycastTarget = false;
					TextMeshProUGUI val4 = Label((Transform)(object)val2, "Activity", "", 17f, Pale);
					Position(((TMP_Text)val4).rectTransform, 13f, 3f, 374f, 38f);
					((TMP_Text)val4).textWrappingMode = (TextWrappingModes)1;
					((TMP_Text)val4).overflowMode = (TextOverflowModes)1;
					((TMP_Text)val4).enableAutoSizing = true;
					((TMP_Text)val4).fontSizeMin = 13f;
					((TMP_Text)val4).fontSizeMax = 17f;
					_feedVisuals.Add(new FeedVisual(((Component)val2).gameObject, val3, val4));
					((Component)val2).gameObject.SetActive(false);
				}
				_board.SetActive(false);
				_feedRoot.SetActive(false);
			}
		}

		private void BuildTable()
		{
			//IL_003c: Unknown result type (might be due to invalid IL or missing references)
			//IL_005d: Unknown result type (might be due to invalid IL or missing references)
			//IL_026f: Unknown result type (might be due to invalid IL or missing references)
			//IL_028d: Expected O, but got Unknown
			//IL_02af: Unknown result type (might be due to invalid IL or missing references)
			//IL_02cd: Expected O, but got Unknown
			//IL_0327: Unknown result type (might be due to invalid IL or missing references)
			RectTransform val = Rect("LeaderboardTable", _board.transform);
			Position(val, 36f, 189f, 928f, 405f);
			Image obj = ((Component)val).gameObject.AddComponent<Image>();
			((Graphic)obj).color = Inset;
			((Graphic)obj).raycastTarget = true;
			_namesHeading = Label((Transform)(object)val, "PlayersHeading", "", 19f, Gold);
			Position(((TMP_Text)_namesHeading).rectTransform, 15f, 0f, 195f, 52f);
			((TMP_Text)_namesHeading).fontStyle = (FontStyles)1;
			RectTransform val2 = MaskedRect("ColumnHeaderViewport", (Transform)(object)val);
			Position(val2, 218f, 0f, 696f, 52f);
			_headerContent = Rect("ColumnHeaders", (Transform)(object)val2);
			Position(_headerContent, 0f, 0f, 696f, 52f);
			RectTransform val3 = MaskedRect("PlayerNameViewport", (Transform)(object)val);
			Position(val3, 0f, 52f, 218f, 335f);
			_namesContent = Rect("PlayerNames", (Transform)(object)val3);
			Position(_namesContent, 0f, 0f, 218f, 335f);
			AddRowLayout(_namesContent);
			RectTransform val4 = Rect("StatisticsScrollView", (Transform)(object)val);
			Position(val4, 218f, 52f, 710f, 353f);
			_scroll = ((Component)val4).gameObject.AddComponent<ScrollRect>();
			_scroll.horizontal = true;
			_scroll.vertical = true;
			_scroll.movementType = (MovementType)2;
			_scroll.scrollSensitivity = 32f;
			_scroll.inertia = false;
			RectTransform val5 = MaskedRect("Viewport", (Transform)(object)val4);
			Position(val5, 0f, 0f, 696f, 335f);
			_bodyContent = Rect("Content", (Transform)(object)val5);
			Position(_bodyContent, 0f, 0f, 696f, 335f);
			AddRowLayout(_bodyContent);
			_scroll.viewport = val5;
			_scroll.content = _bodyContent;
			_scroll.horizontalScrollbar = Scrollbar((Transform)(object)val4, horizontal: true);
			Position((RectTransform)((Component)_scroll.horizontalScrollbar).transform, 0f, 339f, 696f, 10f);
			_scroll.verticalScrollbar = Scrollbar((Transform)(object)val4, horizontal: false);
			Position((RectTransform)((Component)_scroll.verticalScrollbar).transform, 700f, 0f, 10f, 335f);
			_scroll.horizontalScrollbarVisibility = (ScrollbarVisibility)1;
			_scroll.verticalScrollbarVisibility = (ScrollbarVisibility)1;
			((UnityEvent<Vector2>)(object)_scroll.onValueChanged).AddListener((UnityAction<Vector2>)delegate
			{
				SyncFrozenCells();
			});
			Rule((Transform)(object)val, 0f, 51f, 914f);
			_empty = Label((Transform)(object)val, "EmptyState", "", 21f, Muted);
			Position(((TMP_Text)_empty).rectTransform, 20f, 127f, 888f, 110f);
			((TMP_Text)_empty).textWrappingMode = (TextWrappingModes)1;
			((TMP_Text)_empty).alignment = (TextAlignmentOptions)514;
		}

		private void RenderTable()
		{
			//IL_03f0: Unknown result type (might be due to invalid IL or missing references)
			//IL_03d5: Unknown result type (might be due to invalid IL or missing references)
			//IL_0410: Unknown result type (might be due to invalid IL or missing references)
			//IL_041e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0426: Unknown result type (might be due to invalid IL or missing references)
			//IL_044b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0455: Expected O, but got Unknown
			//IL_048f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0617: Unknown result type (might be due to invalid IL or missing references)
			//IL_061c: Unknown result type (might be due to invalid IL or missing references)
			//IL_063a: Unknown result type (might be due to invalid IL or missing references)
			//IL_05fc: Unknown result type (might be due to invalid IL or missing references)
			//IL_05e1: Unknown result type (might be due to invalid IL or missing references)
			//IL_065f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0658: Unknown result type (might be due to invalid IL or missing references)
			//IL_06af: Unknown result type (might be due to invalid IL or missing references)
			//IL_06a8: Unknown result type (might be due to invalid IL or missing references)
			//IL_0701: Unknown result type (might be due to invalid IL or missing references)
			//IL_0790: Unknown result type (might be due to invalid IL or missing references)
			//IL_0789: Unknown result type (might be due to invalid IL or missing references)
			//IL_0782: Unknown result type (might be due to invalid IL or missing references)
			if (!Object.op_Implicit((Object)(object)_board) || !IsOpen)
			{
				return;
			}
			_dirty = false;
			_nextRender = Time.unscaledTime + 0.35f;
			_lastRevision = ((_snapshot == null) ? (-1) : _snapshot.Revision);
			((TMP_Text)_title).text = (_german ? "WIKINGER-CHRONIK" : "VIKING CHRONICLE");
			((TMP_Text)_closeLabel).text = (_german ? "Schließen" : "Close");
			((TMP_Text)_namesHeading).text = (_german ? "Rang / Wikinger" : "Rank / Viking");
			((TMP_Text)_hint).text = (_german ? "Spalten anklicken zum Sortieren · Weitere Werte mit dem unteren Scrollbalken" : "Click a column to sort · Use the bottom scrollbar for more statistics");
			string text = ((_snapshot == null) ? "" : _snapshot.WorldName);
			WorldSnapshot? snapshot = _snapshot;
			int valueOrDefault = ((snapshot == null) ? ((int?)null) : snapshot.Players?.Count).GetValueOrDefault();
			((TMP_Text)_subtitle).text = (_german ? "Alle Zeiten" : "All time") + "  ·  " + ((!string.IsNullOrWhiteSpace(text)) ? text : (_german ? "Gemeinsame Welt" : "Shared world")) + "  ·  " + valueOrDefault + (_german ? " Wikinger" : " Vikings");
			if (_snapshot == null)
			{
				ClearChildren((Transform)(object)_headerContent);
				ClearChildren((Transform)(object)_namesContent);
				ClearChildren((Transform)(object)_bodyContent);
				((TMP_Text)_empty).text = (_german ? "Die Chronik wird vom Server geladen …" : "Loading the chronicle from the server …");
				((Component)_empty).gameObject.SetActive(true);
				return;
			}
			_tabs = MetricCatalog.GetTabs(_snapshot, _german);
			RenderTabs();
			StatTab val = ((IEnumerable<StatTab>)_tabs).FirstOrDefault((Func<StatTab, bool>)((StatTab item) => item.Id == _tabId)) ?? _tabs.FirstOrDefault();
			if (val == null)
			{
				return;
			}
			_tabId = val.Id;
			if (!val.Columns.Any((StatColumn column) => column.Key == _sortKey))
			{
				StatColumn? obj = val.Columns.FirstOrDefault();
				_sortKey = ((obj != null) ? obj.Key : null) ?? "";
				_sortDescending = true;
			}
			float horizontalNormalizedPosition = _scroll.horizontalNormalizedPosition;
			float verticalNormalizedPosition = _scroll.verticalNormalizedPosition;
			ClearChildren((Transform)(object)_headerContent);
			ClearChildren((Transform)(object)_namesContent);
			ClearChildren((Transform)(object)_bodyContent);
			float num = 696f;
			float num2 = ((val.Columns.Count > 0) ? Mathf.Max(128f, num / (float)val.Columns.Count) : 128f);
			float num3 = Mathf.Max(num, (float)val.Columns.Count * num2);
			_headerContent.SetSizeWithCurrentAnchors((Axis)0, num3);
			_bodyContent.SetSizeWithCurrentAnchors((Axis)0, num3);
			for (int num4 = 0; num4 < val.Columns.Count; num4++)
			{
				StatColumn val2 = val.Columns[num4];
				RectTransform obj2 = Rect("Column_" + val2.Key, (Transform)(object)_headerContent);
				Position(obj2, (float)num4 * num2, 0f, num2 - 2f, 50f);
				Image val3 = ((Component)obj2).gameObject.AddComponent<Image>();
				bool flag = val2.Key == _sortKey;
				((Graphic)val3).color = (flag ? new Color(0.51f, 0.32f, 0.09f, 0.73f) : new Color(0.24f, 0.2f, 0.18f, 0.62f));
				Button obj3 = ((Component)obj2).gameObject.AddComponent<Button>();
				((Selectable)obj3).targetGraphic = (Graphic)(object)val3;
				Navigation navigation = default(Navigation);
				((Navigation)(ref navigation)).mode = (Mode)0;
				((Selectable)obj3).navigation = navigation;
				((Selectable)obj3).colors = HeaderColors();
				string key = val2.Key;
				((UnityEvent)obj3.onClick).AddListener((UnityAction)delegate
				{
					SortBy(key);
				});
				TextMeshProUGUI obj4 = Label((Transform)(object)obj2, "Title", Localize(val2.Title) + (flag ? (_sortDescending ? " ↓" : " ↑") : ""), 17f, Gold);
				Stretch(((TMP_Text)obj4).rectTransform, 6f, 3f, 6f, 3f);
				((TMP_Text)obj4).alignment = (TextAlignmentOptions)514;
				((TMP_Text)obj4).textWrappingMode = (TextWrappingModes)1;
				((TMP_Text)obj4).enableAutoSizing = true;
				((TMP_Text)obj4).fontSizeMin = 12f;
				((TMP_Text)obj4).fontSizeMax = 17f;
			}
			IEnumerable<PlayerStats> source = _snapshot.Players ?? new List<PlayerStats>();
			source = (_sortDescending ? source.OrderByDescending(Value).ThenBy<PlayerStats, string>((PlayerStats player) => player.Name, StringComparer.OrdinalIgnoreCase) : source.OrderBy(Value).ThenBy<PlayerStats, string>((PlayerStats player) => player.Name, StringComparer.OrdinalIgnoreCase));
			int num5 = 0;
			foreach (PlayerStats item in source)
			{
				num5++;
				bool flag2 = IsLocal(item);
				Color color = (flag2 ? new Color(0.59f, 0.39f, 0.12f, 0.24f) : ((num5 % 2 == 1) ? new Color(0.33f, 0.28f, 0.27f, 0.2f) : new Color(0f, 0f, 0f, 0.2f)));
				RectTransform parent = Row("Player_" + num5, (Transform)(object)_namesContent, 218f, color);
				TextMeshProUGUI obj5 = Label((Transform)(object)parent, "Rank", num5.ToString(), 19f, (num5 <= 3) ? Gold : Muted);
				Position(((TMP_Text)obj5).rectTransform, 11f, 0f, 34f, 39f);
				((TMP_Text)obj5).alignment = (TextAlignmentOptions)514;
				TextMeshProUGUI val4 = Label((Transform)(object)parent, "Name", item.Name, 18f, flag2 ? Gold : Pale);
				Position(((TMP_Text)val4).rectTransform, 52f, 0f, 156f, 39f);
				if (flag2)
				{
					((TMP_Text)val4).fontStyle = (FontStyles)1;
				}
				RectTransform parent2 = Row("Statistics_" + num5, (Transform)(object)_bodyContent, num3, color);
				for (int num6 = 0; num6 < val.Columns.Count; num6++)
				{
					StatColumn val5 = val.Columns[num6];
					long value;
					long num7 = ((item.Values != null && item.Values.TryGetValue(val5.Key, out value)) ? value : 0);
					TextMeshProUGUI obj6 = Label((Transform)(object)parent2, "Value_" + val5.Key, val5.Format(num7), 18f, (num7 == 0L) ? Muted : ((val5.Key == _sortKey) ? Gold : Pale));
					Position(((TMP_Text)obj6).rectTransform, (float)num6 * num2 + 6f, 0f, num2 - 14f, 39f);
					((TMP_Text)obj6).alignment = (TextAlignmentOptions)514;
				}
			}
			((Component)_empty).gameObject.SetActive(num5 == 0);
			((TMP_Text)_empty).text = (_german ? "Noch keine Einträge. Eure nächsten Abenteuer schreiben die Chronik." : "No entries yet. Your next adventures will write the chronicle.");
			LayoutRebuilder.ForceRebuildLayoutImmediate(_bodyContent);
			LayoutRebuilder.ForceRebuildLayoutImmediate(_namesContent);
			_scroll.horizontalNormalizedPosition = (float.IsNaN(horizontalNormalizedPosition) ? 0f : horizontalNormalizedPosition);
			_scroll.verticalNormalizedPosition = (float.IsNaN(verticalNormalizedPosition) ? 1f : verticalNormalizedPosition);
			SyncFrozenCells();
		}

		private long Value(PlayerStats player)
		{
			if (player.Values == null || !player.Values.TryGetValue(_sortKey, out var value))
			{
				return 0L;
			}
			return value;
		}

		private static bool IsLocal(PlayerStats player)
		{
			if (Object.op_Implicit((Object)(object)Player.m_localPlayer))
			{
				return string.Equals(Convert.ToString(player.PlayerId), Convert.ToString(Player.m_localPlayer.GetPlayerID()), StringComparison.Ordinal);
			}
			return false;
		}

		private void RenderTabs()
		{
			//IL_0197: Unknown result type (might be due to invalid IL or missing references)
			//IL_0190: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ba: Unknown result type (might be due to invalid IL or missing references)
			//IL_01bf: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e2: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c7: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f1: Unknown result type (might be due to invalid IL or missing references)
			bool flag = _tabVisuals.Count != _tabs.Count;
			if (!flag)
			{
				for (int i = 0; i < _tabs.Count; i++)
				{
					if (_tabVisuals[i].Id != _tabs[i].Id)
					{
						flag = true;
					}
				}
			}
			if (flag)
			{
				ClearChildren((Transform)(object)_tabsRoot);
				_tabVisuals.Clear();
				foreach (StatTab tab in _tabs)
				{
					string id = tab.Id;
					TextMeshProUGUI label;
					Button val = NativeButton((Transform)(object)_tabsRoot, "Tab_" + id, tab.Title, delegate
					{
						SelectTab(id);
					}, out label);
					LayoutElement obj = ((Component)val).gameObject.AddComponent<LayoutElement>();
					obj.flexibleWidth = 1f;
					obj.minWidth = 120f;
					obj.preferredHeight = 43f;
					_tabVisuals.Add(new TabVisual(id, val, label));
				}
			}
			for (int num = 0; num < _tabVisuals.Count; num++)
			{
				TabVisual tabVisual = _tabVisuals[num];
				bool flag2 = tabVisual.Id == _tabId;
				((TMP_Text)tabVisual.Label).text = _tabs[num].Title;
				((Graphic)tabVisual.Label).color = (flag2 ? Gold : Pale);
				((TMP_Text)tabVisual.Label).fontStyle = (FontStyles)(flag2 ? 1 : 0);
				ColorBlock colors = ((Selectable)tabVisual.Button).colors;
				((ColorBlock)(ref colors)).normalColor = (Color)(flag2 ? new Color(1f, 0.78f, 0.41f, 1f) : Color.white);
				((Selectable)tabVisual.Button).colors = colors;
			}
		}

		private void SelectTab(string id)
		{
			if (!(_tabId == id))
			{
				_tabId = id;
				_sortKey = "";
				_sortDescending = true;
				_scroll.horizontalNormalizedPosition = 0f;
				_scroll.verticalNormalizedPosition = 1f;
				_dirty = true;
				RenderTable();
			}
		}

		private void SortBy(string key)
		{
			_sortDescending = !(key == _sortKey) || !_sortDescending;
			_sortKey = key;
			_dirty = true;
			RenderTable();
		}

		private void SyncFrozenCells()
		{
			//IL_0034: 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_005e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0068: Unknown result type (might be due to invalid IL or missing references)
			if (Object.op_Implicit((Object)(object)_bodyContent) && Object.op_Implicit((Object)(object)_headerContent) && Object.op_Implicit((Object)(object)_namesContent))
			{
				_headerContent.anchoredPosition = new Vector2(_bodyContent.anchoredPosition.x, 0f);
				_namesContent.anchoredPosition = new Vector2(0f, _bodyContent.anchoredPosition.y);
			}
		}

		private void UpdateScale(UiSettings settings)
		{
			//IL_0037: Unknown result type (might be due to invalid IL or missing references)
			//IL_003c: 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_005c: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b8: 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)
			//IL_00ce: Unknown result type (might be due to invalid IL or missing references)
			//IL_0147: Unknown result type (might be due to invalid IL or missing references)
			float num = Mathf.Clamp(settings.Scale, 0.65f, 1.6f);
			Transform transform = GUIManager.CustomGUIFront.transform;
			RectTransform val = (RectTransform)(object)((transform is RectTransform) ? transform : null);
			float num2;
			Rect rect;
			if (!Object.op_Implicit((Object)(object)val))
			{
				num2 = Screen.width;
			}
			else
			{
				rect = val.rect;
				num2 = ((Rect)(ref rect)).width;
			}
			float num3 = num2;
			float num4;
			if (!Object.op_Implicit((Object)(object)val))
			{
				num4 = Screen.height;
			}
			else
			{
				rect = val.rect;
				num4 = ((Rect)(ref rect)).height;
			}
			float num5 = num4;
			float num6 = Mathf.Min(new float[3]
			{
				num,
				Mathf.Max(0.35f, (num3 - 32f) / 1000f),
				Mathf.Max(0.35f, (num5 - 32f) / 658f)
			});
			((Transform)_boardRect).localScale = Vector3.one * num6;
			((Transform)_feedRect).localScale = Vector3.one * num;
			float num7 = Mathf.Clamp(settings.FeedOffsetX, 0f, Mathf.Max(0f, num3 - 400f * num));
			float num8 = 31f + (float)Mathf.Clamp(settings.FeedLines, 1, 8) * 48f;
			float num9 = Mathf.Clamp(settings.FeedOffsetY, 0f, Mathf.Max(0f, num5 - num8 * num));
			_feedRect.anchoredPosition = new Vector2(0f - num7, 0f - num9);
		}

		private void UpdateFeed(UiSettings settings)
		{
			if (!Object.op_Implicit((Object)(object)_feedRoot))
			{
				return;
			}
			float lifetime = Mathf.Clamp(settings.FeedDuration, 2f, 30f);
			float now = Time.unscaledTime;
			_feed.RemoveAll((LiveLine line) => now - line.Started >= lifetime);
			int num = Mathf.Clamp(settings.FeedLines, 1, 8);
			while (_feed.Count > num)
			{
				_feed.RemoveAt(_feed.Count - 1);
			}
			_feedRoot.SetActive(settings.FeedEnabled && _feed.Count > 0 && !IsOpen);
			((TMP_Text)_feedTitle).text = (_german ? "NEUES AUS DER WELT" : "WORLD ACTIVITY");
			for (int num2 = 0; num2 < _feedVisuals.Count; num2++)
			{
				FeedVisual feedVisual = _feedVisuals[num2];
				bool flag = num2 < _feed.Count && num2 < num && settings.FeedEnabled;
				feedVisual.Root.SetActive(flag);
				if (flag)
				{
					LiveLine liveLine = _feed[num2];
					float num3 = now - liveLine.Started;
					feedVisual.Fade.alpha = Mathf.Min(Mathf.Clamp01(num3 / 0.18f), Mathf.Clamp01((lifetime - num3) / 1.2f));
					((TMP_Text)feedVisual.Label).text = Localize(FeedText.Format(liveLine.Entry, _german));
				}
			}
		}

		private static string Localize(string value)
		{
			if (string.IsNullOrEmpty(value))
			{
				return "";
			}
			if (Localization.instance == null)
			{
				return value;
			}
			return Localization.instance.Localize(value);
		}

		private static RectTransform Rect(string name, Transform parent)
		{
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			//IL_0033: Unknown result type (might be due to invalid IL or missing references)
			RectTransform component = new GameObject(name, new Type[1] { typeof(RectTransform) }).GetComponent<RectTransform>();
			((Component)component).gameObject.layer = 5;
			((Transform)component).SetParent(parent, false);
			((Transform)component).localScale = Vector3.one;
			return component;
		}

		private static RectTransform MaskedRect(string name, Transform parent)
		{
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			RectTransform obj = Rect(name, parent);
			Image obj2 = ((Component)obj).gameObject.AddComponent<Image>();
			((Graphic)obj2).color = Color.white;
			((Graphic)obj2).raycastTarget = true;
			((Component)obj).gameObject.AddComponent<Mask>().showMaskGraphic = false;
			return obj;
		}

		private static void Position(RectTransform rect, float x, float y, float width, float height)
		{
			//IL_0002: 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_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_0015: Unknown result type (might be due to invalid IL or missing references)
			//IL_0023: 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)
			Vector2 anchorMin = (rect.anchorMax = TopLeft);
			rect.anchorMin = anchorMin;
			rect.pivot = TopLeft;
			rect.anchoredPosition = new Vector2(x, 0f - y);
			rect.sizeDelta = new Vector2(width, height);
		}

		private static void Stretch(RectTransform rect, float left, float top, float right, float bottom)
		{
			//IL_0001: 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_0017: Unknown result type (might be due to invalid IL or missing references)
			//IL_0025: Unknown result type (might be due to invalid IL or missing references)
			//IL_0034: Unknown result type (might be due to invalid IL or missing references)
			rect.anchorMin = Vector2.zero;
			rect.anchorMax = Vector2.one;
			rect.pivot = Center;
			rect.offsetMin = new Vector2(left, bottom);
			rect.offsetMax = new Vector2(0f - right, 0f - top);
		}

		private static TextMeshProUGUI Label(Transform parent, string name, string text, float size, Color color, bool norse = false)
		{
			//IL_0049: 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)
			TextMeshProUGUI obj = ((Component)Rect(name, parent)).gameObject.AddComponent<TextMeshProUGUI>();
			((TMP_Text)obj).font = (norse ? GUIManager.Instance.TMP_Norse : GUIManager.Instance.TMP_AveriaSansLibre);
			((TMP_Text)obj).text = text ?? "";
			((TMP_Text)obj).fontSize = size;
			((Graphic)obj).color = color;
			((TMP_Text)obj).richText = false;
			((Graphic)obj).raycastTarget = false;
			((TMP_Text)obj).textWrappingMode = (TextWrappingModes)0;
			((TMP_Text)obj).overflowMode = (TextOverflowModes)1;
			((TMP_Text)obj).alignment = (TextAlignmentOptions)4097;
			((TMP_Text)obj).margin = Vector4.zero;
			return obj;
		}

		private static Button NativeButton(Transform parent, string name, string text, Action action, out TextMeshProUGUI label)
		{
			//IL_0018: Unknown result type (might be due to invalid IL or missing references)
			//IL_001d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0022: 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)
			//IL_00b8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c6: 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_00e4: Expected O, but got Unknown
			GameObject val = GUIManager.Instance.CreateButton("", parent, TopLeft, TopLeft, Vector2.zero, 180f, 40f);
			((Object)val).name = name;
			Text[] componentsInChildren = val.GetComponentsInChildren<Text>(true);
			for (int i = 0; i < componentsInChildren.Length; i++)
			{
				((Component)componentsInChildren[i]).gameObject.SetActive(false);
			}
			label = Label(val.transform, "Label", text, 21f, Pale);
			Stretch(((TMP_Text)label).rectTransform, 8f, 2f, 8f, 2f);
			((TMP_Text)label).alignment = (TextAlignmentOptions)514;
			Button component = val.GetComponent<Button>();
			Navigation navigation = default(Navigation);
			((Navigation)(ref navigation)).mode = (Mode)0;
			((Selectable)component).navigation = navigation;
			((UnityEvent)component.onClick).AddListener((UnityAction)delegate
			{
				action();
			});
			return component;
		}

		private static ColorBlock HeaderColors()
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_0008: 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_0048: 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_006a: Unknown result type (might be due to invalid IL or missing references)
			ColorBlock defaultColorBlock = ColorBlock.defaultColorBlock;
			((ColorBlock)(ref defaultColorBlock)).normalColor = Color.white;
			((ColorBlock)(ref defaultColorBlock)).highlightedColor = new Color(1f, 0.84f, 0.55f, 1f);
			((ColorBlock)(ref defaultColorBlock)).pressedColor = new Color(0.78f, 0.65f, 0.42f, 1f);
			((ColorBlock)(ref defaultColorBlock)).selectedColor = Color.white;
			((ColorBlock)(ref defaultColorBlock)).fadeDuration = 0.1f;
			return defaultColorBlock;
		}

		private static Scrollbar Scrollbar(Transform parent, bool horizontal)
		{
			//IL_0035: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00df: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ed: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f5: Unknown result type (might be due to invalid IL or missing references)
			RectTransform val = Rect(horizontal ? "HorizontalScrollbar" : "VerticalScrollbar", parent);
			((Graphic)((Component)val).gameObject.AddComponent<Image>()).color = new Color(0.03f, 0.025f, 0.02f, 0.85f);
			Scrollbar obj = ((Component)val).gameObject.AddComponent<Scrollbar>();
			RectTransform val2 = Rect("SlidingArea", (Transform)(object)val);
			Stretch(val2, 1f, 1f, 1f, 1f);
			RectTransform val3 = Rect("Handle", (Transform)(object)val2);
			Stretch(val3, 0f, 0f, 0f, 0f);
			Image val4 = ((Component)val3).gameObject.AddComponent<Image>();
			((Graphic)val4).color = new Color(0.68f, 0.48f, 0.22f, 0.92f);
			((Selectable)obj).targetGraphic = (Graphic)(object)val4;
			obj.handleRect = val3;
			obj.direction = (Direction)((!horizontal) ? 2 : 0);
			Navigation navigation = default(Navigation);
			((Navigation)(ref navigation)).mode = (Mode)0;
			((Selectable)obj).navigation = navigation;
			((Selectable)obj).colors = HeaderColors();
			return obj;
		}

		private static void AddRowLayout(RectTransform content)
		{
			//IL_0036: Unknown result type (might be due to invalid IL or missing references)
			//IL_0040: Expected O, but got Unknown
			VerticalLayoutGroup obj = ((Component)content).gameObject.AddComponent<VerticalLayoutGroup>();
			((HorizontalOrVerticalLayoutGroup)obj).childControlWidth = true;
			((HorizontalOrVerticalLayoutGroup)obj).childControlHeight = true;
			((HorizontalOrVerticalLayoutGroup)obj).childForceExpandWidth = true;
			((HorizontalOrVerticalLayoutGroup)obj).childForceExpandHeight = false;
			((HorizontalOrVerticalLayoutGroup)obj).spacing = 0f;
			((LayoutGroup)obj).padding = new RectOffset(0, 0, 0, 0);
			ContentSizeFitter obj2 = ((Component)content).gameObject.AddComponent<ContentSizeFitter>();
			obj2.horizontalFit = (FitMode)0;
			obj2.verticalFit = (FitMode)2;
		}

		private static RectTransform Row(string name, Transform parent, float width, Color color)
		{
			//IL_000e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0024: Unknown result type (might be due to invalid IL or missing references)
			RectTransform obj = Rect(name, parent);
			obj.sizeDelta = new Vector2(width, 39f);
			Image obj2 = ((Component)obj).gameObject.AddComponent<Image>();
			((Graphic)obj2).color = color;
			((Graphic)obj2).raycastTarget = false;
			LayoutElement obj3 = ((Component)obj).gameObject.AddComponent<LayoutElement>();
			obj3.minHeight = 39f;
			obj3.preferredHeight = 39f;
			return obj;
		}

		private static void Rule(Transform parent, float x, float y, float width)
		{
			//IL_0047: Unknown result type (might be due to invalid IL or missing references)
			RectTransform obj = Rect("GoldRule", parent);
			Position(obj, x, y, width, 1f);
			Image obj2 = ((Component)obj).gameObject.AddComponent<Image>();
			((Graphic)obj2).color = new Color(Gold.r, Gold.g, Gold.b, 0.37f);
			((Graphic)obj2).raycastTarget = false;
		}

		private static void ClearChildren(Transform parent)
		{
			for (int num = parent.childCount - 1; num >= 0; num--)
			{
				GameObject gameObject = ((Component)parent.GetChild(num)).gameObject;
				gameObject.SetActive(false);
				Object.Destroy((Object)(object)gameObject);
			}
		}
	}
	public sealed class UiSettings
	{
		public string Language { get; set; } = "de";

		public bool FeedEnabled { get; set; } = true;

		public float FeedDuration { get; set; } = 7f;

		public int FeedLines { get; set; } = 4;

		public float Scale { get; set; } = 1f;

		public float FeedOffsetX { get; set; } = 24f;

		public float FeedOffsetY { get; set; } = 260f;
	}
}
namespace VikingChronicle.Tracking
{
	[HarmonyPatch(typeof(Player), "PlacePiece")]
	internal static class PlacedPiecePatch
	{
		private static void Postfix(Player __instance, Piece piece)
		{
			if (!((Object)(object)__instance != (Object)(object)Player.m_localPlayer) && Object.op_Implicit((Object)(object)piece))
			{
				if (Object.op_Implicit((Object)(object)((Component)piece).GetComponent<Plant>()))
				{
					TrackingIdentity.From(__instance).Record("farm.planted", 1L, piece.m_name);
				}
				else if (Object.op_Implicit((Object)(object)((Component)piece).GetComponent<ZNetView>()))
				{
					TrackingIdentity.From(__instance).Record("build.placed", 1L, piece.m_name);
				}
			}
		}
	}
	[HarmonyPatch(typeof(WearNTear), "RPC_Repair")]
	internal static class RepairedPiecePatch
	{
		private sealed class State
		{
			internal TrackingIdentity Player;

			internal ZDO Data;

			internal float Health;

			internal string Subject = "";
		}

		private static void Prefix(WearNTear __instance, long sender, out State? __state)
		{
			__state = null;
			ZNetView component = ((Component)__instance).GetComponent<ZNetView>();
			if (!TrackingIdentity.Owned(component))
			{
				return;
			}
			ZDO zDO = component.GetZDO();
			float num = zDO.GetFloat(ZDOVars.s_health, __instance.m_health);
			if (!(num >= __instance.m_health))
			{
				TrackingIdentity player = TrackingIdentity.FromSender(sender);
				if (player.Valid)
				{
					Piece component2 = ((Component)__instance).GetComponent<Piece>();
					__state = new State
					{
						Player = player,
						Data = zDO,
						Health = num,
						Subject = (Object.op_Implicit((Object)(object)component2) ? component2.m_name : "")
					};
				}
			}
		}

		private static void Postfix(State? __state)
		{
			if (__state != null && !(__state.Data.GetFloat(ZDOVars.s_health, __state.Health) <= __state.Health))
			{
				int num = __state.Data.GetInt("vikingchronicle_repairs", 0) + 1;
				__state.Data.Set("vikingchronicle_repairs", num);
				__state.Player.Record("build.repaired", 1L, __state.Subject, TrackingIdentity.Event(__state.Data, "repair") + ":" + num);
			}
		}
	}
	[HarmonyPatch(typeof(InventoryGui), "DoCrafting")]
	internal static class CraftedItemsPatch
	{
		private sealed class State
		{
			internal TrackingIdentity Player;

			internal Inventory Inventory;

			internal string Subject = "";

			internal int Quality;

			internal int Before;

			internal bool Upgrade;
		}

		private static int Count(Inventory inventory, string name, int quality)
		{
			int num = 0;
			foreach (ItemData allItem in inventory.GetAllItems())
			{
				if (allItem.m_shared.m_name == name && allItem.m_quality == quality)
				{
					num += allItem.m_stack;
				}
			}
			return num;
		}

		private static void Prefix(Player player, Recipe ___m_craftRecipe, ItemData ___m_craftUpgradeItem, out State? __state)
		{
			__state = null;
			if (!((Object)(object)player != (Object)(object)Player.m_localPlayer) && Object.op_Implicit((Object)(object)___m_craftRecipe) && Object.op_Implicit((Object)(object)___m_craftRecipe.m_item))
			{
				string name = ___m_craftRecipe.m_item.m_itemData.m_shared.m_name;
				int quality = ((___m_craftUpgradeItem == null) ? 1 : (___m_craftUpgradeItem.m_quality + 1));
				Inventory inventory = ((Humanoid)player).GetInventory();
				__state = new State
				{
					Player = TrackingIdentity.From(player),
					Inventory = inventory,
					Subject = name,
					Quality = quality,
					Before = Count(inventory, name, quality),
					Upgrade = (___m_craftUpgradeItem != null)
				};
			}
		}

		private static void Postfix(State? __state)
		{
			if (__state != null)
			{
				int num = Count(__state.Inventory, __state.Subject, __state.Quality) - __state.Before;
				__state.Player.Record(__state.Upgrade ? "craft.upgraded" : "craft.items", num, __state.Subject);
			}
		}
	}
	internal static class Tracker
	{
		private static TrackingIdentity _player;

		private static Vector3 _position;

		private static bool _positionValid;

		private static float _flushTimer;

		private static float _sampleTimer;

		private static double _seconds;

		private static double _walked;

		private static double _swam;

		private static double _sailed;

		internal static void Update(float deltaTime)
		{
			//IL_00a0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a5: Unknown result type (might be due to invalid IL or missing references)
			//IL_013a: Unknown result type (might be due to invalid IL or missing references)
			//IL_013b: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d4: 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)
			Player localPlayer = Player.m_localPlayer;
			TrackingIdentity player = TrackingIdentity.From(localPlayer);
			if (!Object.op_Implicit((Object)(object)localPlayer) || !player.Valid)
			{
				_positionValid = false;
				return;
			}
			if (_player.Id != player.Id)
			{
				Flush();
				Reset();
				_player = player;
			}
			else
			{
				_player = player;
			}
			if (deltaTime <= 0f || deltaTime > 2f)
			{
				_positionValid = false;
				return;
			}
			_seconds += deltaTime;
			_sampleTimer += deltaTime;
			_flushTimer += deltaTime;
			if (_sampleTimer >= 0.5f)
			{
				Vector3 position = ((Component)localPlayer).transform.position;
				bool flag = !((Character)localPlayer).IsDead() && !((Character)localPlayer).IsTeleporting() && !((Character)localPlayer).InCutscene() && !((Character)localPlayer).IsDebugFlying();
				if (_positionValid && flag)
				{
					double num = Vector3.Distance(position, _position);
					if (num <= (double)(25f * _sampleTimer))
					{
						if (Object.op_Implicit((Object)(object)((Character)localPlayer).GetStandingOnShip()) || ((Character)localPlayer).IsAttachedToShip())
						{
							_sailed += num;
						}
						else if (((Character)localPlayer).IsSwimming())
						{
							_swam += num;
						}
						else
						{
							_walked += num;
						}
					}
				}
				_position = position;
				_positionValid = flag;
				_sampleTimer = 0f;
			}
			if (_flushTimer >= 10f)
			{
				Flush();
				_flushTimer = 0f;
			}
		}

		internal static void Flush()
		{
			Emit("time.seconds", ref _seconds);
			Emit("distance.walked", ref _walked);
			Emit("distance.swam", ref _swam);
			Emit("distance.sailed", ref _sailed);
		}

		private static void Emit(string metric, ref double amount)
		{
			long num = (long)Math.Floor(amount);
			if (num > 0 && _player.Valid)
			{
				_player.Record(metric, num);
				amount -= num;
			}
		}

		internal static void Reset()
		{
			_player = default(TrackingIdentity);
			_positionValid = false;
			_flushTimer = (_sampleTimer = 0f);
			_seconds = (_walked = (_swam = (_sailed = 0.0)));
		}
	}
	internal static class CombatCredit
	{
		private const string PlayerKey = "vikingchronicle_last_attacker";

		private const string NameKey = "vikingchronicle_last_attacker_name";

		private const string TimeKey = "vikingchronicle_last_attack_time";

		internal static void Remember(Character victim, HitData hit)
		{
			ZNetView component = ((Component)victim).GetComponent<ZNetView>();
			if (TrackingIdentity.Owned(component) && !victim.IsPlayer())
			{
				ZDO zDO = component.GetZDO();
				Character attacker = hit.GetAttacker();
				Player val = (Player)(object)((attacker is Player) ? attacker : null);
				if (val != null)
				{
					zDO.Set("vikingchronicle_last_attacker", val.GetPlayerID());
					zDO.Set("vikingchronicle_last_attacker_name", val.GetPlayerName());
					zDO.Set("vikingchronicle_last_attack_time", ZNet.instance.GetTime().Ticks);
				}
				else if (Object.op_Implicit((Object)(object)attacker) || !IsUnattributedDot(hit))
				{
					zDO.Set("vikingchronicle_last_attacker", 0L);
				}
			}
		}

		internal static TrackingIdentity Killer(Character victim, HitData? lastHit, ZDO zdo)
		{
			Character obj = ((lastHit != null) ? lastHit.GetAttacker() : null);
			Player val = (Player)(object)((obj is Player) ? obj : null);
			if (val != null)
			{
				return TrackingIdentity.From(val);
			}
			if (lastHit == null || !IsUnattributedDot(lastHit))
			{
				return default(TrackingIdentity);
			}
			long num = zdo.GetLong("vikingchronicle_last_attacker", 0L);
			long num2 = ZNet.instance.GetTime().Ticks - zdo.GetLong("vikingchronicle_last_attack_time", 0L);
			if (num == 0L || num2 < 0 || num2 > 1200000000)
			{
				return default(TrackingIdentity);
			}
			return new TrackingIdentity(num, zdo.GetString("vikingchronicle_last_attacker_name", ""));
		}

		private static bool IsUnattributedDot(HitData hit)
		{
			//IL_0009: Unknown result type (might be due to invalid IL or missing references)
			//IL_000f: Invalid comparison between Unknown and I4
			//IL_0012: Unknown result type (might be due to invalid IL or missing references)
			//IL_0018: Invalid comparison between Unknown and I4
			if (!hit.HaveAttacker())
			{
				if ((int)hit.m_hitType != 5)
				{
					return (int)hit.m_hitType == 7;
				}
				return true;
			}
			return false;
		}
	}
	[HarmonyPatch(typeof(Character), "ApplyDamage")]
	internal static class DamageCreditPatch
	{
		private static void Prefix(Character __instance, ref float __state)
		{
			__state = __instance.GetHealth();
		}

		private static void Postfix(Character __instance, HitData hit, float __state)
		{
			if (__state > __instance.GetHealth())
			{
				CombatCredit.Remember(__instance, hit);
			}
		}
	}
	[HarmonyPatch(typeof(Character), "OnDeath")]
	internal static class CreatureDeathPatch
	{
		private static void Prefix(Character __instance, HitData ___m_lastHit)
		{
			if (!__instance.IsPlayer())
			{
				ZNetView component = ((Component)__instance).GetComponent<ZNetView>();
				if (TrackingIdentity.Owned(component))
				{
					ZDO zDO = component.GetZDO();
					CombatCredit.Killer(__instance, ___m_lastHit, zDO).Record("kill." + TrackingIdentity.Prefab(((Component)__instance).gameObject), 1L, __instance.m_name, TrackingIdentity.Event(zDO, "kill"));
				}
			}
		}
	}
	[HarmonyPatch(typeof(Player), "OnDeath")]
	internal static class PlayerDeathPatch
	{
		private static void Prefix(Player __instance)
		{
			if (!((Object)(object)__instance != (Object)(object)Player.m_localPlayer) && !((Character)__instance).IsDead())
			{
				ZNetView component = ((Component)__instance).GetComponent<ZNetView>();
				if (TrackingIdentity.Owned(component))
				{
					ZDO zDO = component.GetZDO();
					int num = zDO.GetInt("vikingchronicle_deaths", 0) + 1;
					zDO.Set("vikingchronicle_deaths", num);
					TrackingIdentity.From(__instance).Record("death.count", 1L, "", TrackingIdentity.Event(zDO, "death") + ":" + num);
				}
			}
		}
	}
	internal sealed class HarvestScope : IDisposable
	{
		private readonly struct Drop
		{
			internal readonly ItemDrop Item;

			internal readonly string EventId;

			internal Drop(ItemDrop item, string eventId)
			{
				Item = item;
				EventId = eventId;
			}
		}

		[ThreadStatic]
		private static HarvestScope? _current;

		private readonly HarvestScope? _previous;

		private readonly TrackingIdentity _player;

		private readonly List<Drop> _drops = new List<Drop>();

		private readonly ZDO? _tree;

		private readonly string _treeSubject;

		internal HarvestScope(TrackingIdentity player, ZDO? tree = null, string treeSubject = "")
		{
			_previous = _current;
			_current = this;
			_player = player;
			_tree = tree;
			_treeSubject = treeSubject;
		}

		internal static void Capture(ItemDrop item)
		{
			if (_current != null && _current._player.Valid)
			{
				ZNetView component = ((Component)item).GetComponent<ZNetView>();
				if (TrackingIdentity.Owned(component))
				{
					_current._drops.Add(new Drop(item, TrackingIdentity.Event(component.GetZDO(), "resource")));
				}
			}
		}

		public void Dispose()
		{
			_current = _previous;
			foreach (Drop drop in _drops)
			{
				if (Object.op_Implicit((Object)(object)drop.Item) && drop.Item.m_itemData != null)
				{
					ItemData itemData = drop.Item.m_itemData;
					string text = TrackingIdentity.Prefab(Object.op_Implicit((Object)(object)itemData.m_dropPrefab) ? itemData.m_dropPrefab : ((Component)drop.Item).gameObject);
					_player.Record("resource." + text, itemData.m_stack, itemData.m_shared.m_name, drop.EventId);
				}
			}
			if (_tree != null && _tree.GetFloat(ZDOVars.s_health, 1f) <= 0f)
			{
				_player.Record("tree.felled", 1L, _treeSubject, TrackingIdentity.Event(_tree, "tree"));
			}
		}
	}
	[HarmonyPatch]
	internal static class HarvestDamagePatch
	{
		private static IEnumerable<MethodBase> TargetMethods()
		{
			yield return AccessTools.Method(typeof(TreeBase), "RPC_Damage", (Type[])null, (Type[])null);
			yield return AccessTools.Method(typeof(TreeLog), "RPC_Damage", (Type[])null, (Type[])null);
			yield return AccessTools.Method(typeof(MineRock), "RPC_Hit", (Type[])null, (Type[])null);
			yield return AccessTools.Method(typeof(MineRock5), "RPC_Damage", (Type[])null, (Type[])null);
			yield return AccessTools.Method(typeof(Destructible), "RPC_Damage", (Type[])null, (Type[])null);
		}

		private static void Prefix(Component __instance, HitData hit, out HarvestScope? __state)
		{
			__state = null;
			ZNetView component = __instance.GetComponent<ZNetView>();
			if (!TrackingIdentity.Owned(component))
			{
				return;
			}
			Character attacker = hit.GetAttacker();
			Player val = (Player)(object)((attacker is Player) ? attacker : null);
			if (val == null)
			{
				return;
			}
			Piece component2 = __instance.GetComponent<Piece>();
			if (!Object.op_Implicit((Object)(object)component2) || !component2.IsPlacedByPlayer())
			{
				bool flag = __instance is TreeBase;
				if (!flag || !(component.GetZDO().GetFloat(ZDOVars.s_health, 1f) <= 0f))
				{
					__state = new HarvestScope(TrackingIdentity.From(val), flag ? component.GetZDO() : null, TrackingIdentity.Prefab(__instance.gameObject));
				}
			}
		}

		private static void Finalizer(HarvestScope? __state)
		{
			__state?.Dispose();
		}
	}
	[HarmonyPatch(typeof(Pickable), "RPC_Pick")]
	internal static class PickableHarvestPatch
	{
		private static void Prefix(Pickable __instance, long sender, bool ___m_picked, out HarvestScope? __state)
		{
			__state = null;
			ZNetView component = ((Component)__instance).GetComponent<ZNetView>();
			if (!___m_picked && TrackingIdentity.Owned(component))
			{
				TrackingIdentity player = TrackingIdentity.FromSender(sender);
				if (player.Valid)
				{
					__state = new HarvestScope(player);
				}
			}
		}

		private static void Finalizer(HarvestScope? __state)
		{
			__state?.Dispose();
		}
	}
	[HarmonyPatch(typeof(ItemDrop), "Awake")]
	internal static class FreshResourceSpawnPatch
	{
		private static void Postfix(ItemDrop __instance)
		{
			HarvestScope.Capture(__instance);
		}
	}
	internal readonly struct TrackingIdentity
	{
		internal readonly long Id;

		internal readonly string Name;

		internal bool Valid => Id != 0;

		internal TrackingIdentity(long id, string name)
		{
			Id = id;
			Name = name;
		}

		internal static TrackingIdentity From(Player? player)
		{
			if (!Object.op_Implicit((Object)(object)player))
			{
				return default(TrackingIdentity);
			}
			return new TrackingIdentity(player.GetPlayerID(), player.GetPlayerName());
		}

		internal static TrackingIdentity FromSender(long sender)
		{
			foreach (Player allPlayer in Player.GetAllPlayers())
			{
				ZNetView component = ((Component)allPlayer).GetComponent<ZNetView>();
				if (Object.op_Implicit((Object)(object)component) && component.IsValid() && component.GetZDO().GetOwner() == sender)
				{
					return From(allPlayer);
				}
			}
			return default(TrackingIdentity);
		}

		internal void Record(string metric, long amount, string subject = "", string? eventId = null)
		{
			if (Valid && amount > 0)
			{
				Plugin.Record(Id, Name, metric, amount, subject, eventId);
			}
		}

		internal static string Prefab(GameObject value)
		{
			string name = ((Object)value).name;
			int num = name.IndexOf('(');
			return StatSanitizer.NormalizePrefab((num < 0) ? name : name.Substring(0, num));
		}

		internal static bool Owned(ZNetView? view)
		{
			if (Object.op_Implicit((Object)(object)view) && view.IsValid())
			{
				return view.IsOwner();
			}
			return false;
		}

		internal unsafe static string Event(ZDO zdo, string category)
		{
			//IL_0007: 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)
			ZDOID uid = zdo.m_uid;
			return category + ":" + ((object)(*(ZDOID*)(&uid))/*cast due to .constrained prefix*/).ToString();
		}
	}
}
namespace VikingChronicle.Persistence
{
	public sealed class ClientOutbox
	{
		private sealed class OutboxSnapshot
		{
			public int SchemaVersion { get; set; } = 1;

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

			public List<StatEvent> Events { get; set; } = new List<StatEvent>();
		}

		private sealed class UnsupportedOutboxSchemaException : Exception
		{
			public UnsupportedOutboxSchemaException(string schema)
				: base("Unsupported pending statistics schema " + schema + "; file was preserved.")
			{
			}
		}

		public const int MaxEvents = 4096;

		private const int MaxFileBytes = 8388608;

		private readonly string _worldId;

		private static readonly JsonSerializerSettings JsonSettings = new JsonSerializerSettings
		{
			TypeNameHandling = (TypeNameHandling)0,
			MaxDepth = 16,
			Formatting = (Formatting)1
		};

		public string FilePath { get; }

		public string? RecoveryNotice { get; private set; }

		public ClientOutbox(string directory, string worldId)
		{
			if (!long.TryParse(worldId, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result) || result.ToString(CultureInfo.InvariantCulture) != worldId)
			{
				throw new ArgumentException("World ID must be a canonical signed integer.", "worldId");
			}
			_worldId = worldId;
			FilePath = Path.Combine(directory, worldId + ".outbox.json");
		}

		public List<StatEvent> Load()
		{
			RecoveryNotice = null;
			if (!File.Exists(FilePath) && !File.Exists(FilePath + ".bak"))
			{
				return new List<StatEvent>();
			}
			Exception ex3;
			try
			{
				return Read(FilePath);
			}
			catch (UnsupportedOutboxSchemaException)
			{
				throw;
			}
			catch (Exception ex2) when (ex2 is IOException || ex2 is JsonException || ex2 is InvalidDataException)
			{
				ex3 = ex2;
			}
			try
			{
				List<StatEvent> result = Read(FilePath + ".bak");
				if (File.Exists(FilePath))
				{
					File.Move(FilePath, FilePath + ".corrupt-" + DateTime.UtcNow.ToString("yyyyMMddHHmmssfff", CultureInfo.InvariantCulture) + "-" + Guid.NewGuid().ToString("N"));
				}
				RecoveryNotice = "Recovered pending statistics from backup: " + ex3.Message;
				return result;
			}
			catch (Exception ex4)
			{
				throw new IOException("Pending statistics and backup could not be loaded. Files were preserved; do not replace the outbox.", new AggregateException(ex3, ex4));
			}
		}

		public void Save(IEnumerable<StatEvent> events)
		{
			if (events == null)
			{
				throw new ArgumentNullException("events");
			}
			OutboxSnapshot outboxSnapshot = new OutboxSnapshot
			{
				WorldId = _worldId,
				Events = events.Take(4097).ToList()
			};
			Validate(outboxSnapshot);
			byte[] bytes = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false).GetBytes(JsonConvert.SerializeObject((object)outboxSnapshot, JsonSettings));
			if (bytes.Length > 8388608)
			{
				throw new InvalidDataException("Pending statistics file exceeds 8 MB.");
			}
			Directory.CreateDirectory(Path.GetDirectoryName(FilePath));
			string text = FilePath + ".tmp";
			using (FileStream fileStream = new FileStream(text, FileMode.Create, FileAccess.Write, FileShare.None))
			{
				fileStream.Write(bytes, 0, bytes.Length);
				fileStream.Flush(flushToDisk: true);
			}
			if (File.Exists(FilePath))
			{
				File.Replace(text, FilePath, FilePath + ".bak");
			}
			else
			{
				File.Move(text, FilePath);
			}
		}

		private List<StatEvent> Read(string path)
		{
			//IL_0033: Unknown result type (might be due to invalid IL or missing references)
			//IL_0038: Unknown result type (might be due to invalid IL or missing references)
			//IL_004a: Expected O, but got Unknown
			//IL_004c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0051: Unknown result type (might be due to invalid IL or missing references)
			//IL_005d: Expected O, but got Unknown
			//IL_00b9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bf: Invalid comparison between Unknown and I4
			//IL_01a7: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ae: Invalid comparison between Unknown and I4
			JObject val2;
			using (FileStream fileStream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read))
			{
				if (fileStream.Length > 8388608)
				{
					throw new InvalidDataException("Pending statistics file exceeds 8 MB.");
				}
				using StreamReader streamReader = new StreamReader(fileStream, Encoding.UTF8, detectEncodingFromByteOrderMarks: true);
				JsonTextReader val = new JsonTextReader((TextReader)streamReader)
				{
					MaxDepth = JsonSettings.MaxDepth
				};
				try
				{
					val2 = JObject.Load((JsonReader)(object)val, new JsonLoadSettings
					{
						DuplicatePropertyNameHandling = (DuplicatePropertyNameHandling)2
					});
					if (((JsonReader)val).Read())
					{
						throw new InvalidDataException("Unexpected data after outbox object.");
					}
				}
				finally
				{
					((IDisposable)val)?.Dispose();
				}
			}
			if (val2["SchemaVersion"] == null)
			{
				throw new InvalidDataException("Missing pending statistics schema.");
			}
			if ((int)val2["SchemaVersion"].Type != 6 || ((object)val2["SchemaVersion"]).ToString() != "1")
			{
				throw new UnsupportedOutboxSchemaException(val2["SchemaVersion"].ToString((Formatting)0, Array.Empty<JsonConverter>()));
			}
			if (val2["WorldId"] != null)
			{
				JToken obj = val2["Events"];
				JArray val3 = (JArray)(object)((obj is JArray) ? obj : null);
				if (val3 != null)
				{
					foreach (JToken item in val3)
					{
						JObject val4 = (JObject)(object)((item is JObject) ? item : null);
						if (val4 == null)
						{
							throw new InvalidDataException("Invalid pending event.");
						}
						string[] array = new string[6] { "EventId", "PlayerId", "PlayerName", "Metric", "Amount", "Subject" };
						foreach (string text in array)
						{
							if (val4[text] == null || (int)val4[text].Type == 10)
							{
								throw new InvalidDataException("Missing pending event field: " + text + ".");
							}
						}
					}
					OutboxSnapshot outboxSnapshot = ((JToken)val2).ToObject<OutboxSnapshot>(JsonSerializer.Create(JsonSettings)) ?? throw new InvalidDataException("Empty pending statistics file.");
					Validate(outboxSnapshot);
					return outboxSnapshot.Events;
				}
			}
			throw new InvalidDataException("Incomplete pending statistics file.");
		}

		private void Validate(OutboxSnapshot snapshot)
		{
			if (snapshot.SchemaVersion != 1)
			{
				throw new UnsupportedOutboxSchemaException(snapshot.SchemaVersion.ToString(CultureInfo.InvariantCulture));
			}
			if (snapshot.WorldId != _worldId)
			{
				throw new InvalidDataException("Pending events belong to a different world.");
			}
			if (snapshot.Events == null || snapshot.Events.Count > 4096)
			{
				throw new InvalidDataException("Invalid pending-event count.");
			}
			HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal);
			HashSet<string> hashSet2 = new HashSet<string>(StringComparer.Ordinal);
			string text = default(string);
			foreach (StatEvent @event in snapshot.Events)
			{
				if (@event == null || @event.PlayerId == 0L || @event.PlayerName == null || @event.Subject == null || string.IsNullOrWhiteSpace(@event.EventId) || @event.EventId.Length > 128 || !hashSet.Add(@event.EventId) || @event.Amount <= 0 || @event.Amount > 1000000 || !StatSanitizer.TryMetric(@event.Metric, ref text) || text != @event.Metric || !StatSanitizer.ValidSubmission(@event.SubmissionSource, @event.SubmissionSequence) || (@event.SubmissionSource.Length > 0 && !hashSet2.Add(@event.SubmissionSource + ":" + @event.SubmissionSequence.ToString(CultureInfo.InvariantCulture))))
				{
					throw new InvalidDataException("Invalid or duplicate pending statistics event.");
				}
				string eventId = @event.EventId;
				foreach (char c in eventId)
				{
					if (char.IsWhiteSpace(c) || char.IsControl(c))
					{
						throw new InvalidDataException("Invalid pending event ID.");
					}
				}
			}
		}
	}
	public sealed class WorldStore
	{
		private sealed class UnsupportedSchemaException : Exception
		{
			public UnsupportedSchemaException(string schema)
				: base("Unsupported statistics schema " + schema + "; file was preserved.")
			{
			}
		}

		private const int MaxFileBytes = 33554432;

		private readonly string _worldId;

		private static readonly JsonSerializerSettings JsonSettings = new JsonSerializerSettings
		{
			TypeNameHandling = (TypeNameHandling)0,
			MaxDepth = 32,
			Formatting = (Formatting)1
		};

		public string FilePath { get; }

		public string? RecoveryNotice { get; private set; }

		public WorldStore(string directory, string worldId)
		{
			if (!long.TryParse(worldId, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result) || result.ToString(CultureInfo.InvariantCulture) != worldId)
			{
				throw new ArgumentException("World ID must be a canonical signed integer.", "worldId");
			}
			_worldId = worldId;
			FilePath = Path.Combine(directory, worldId + ".json");
		}

		public WorldSnapshot Load(string worldId, string worldName)
		{
			//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)
			//IL_0055: Unknown result type (might be due to invalid IL or missing references)
			//IL_005d: Expected O, but got Unknown
			if (worldId != _worldId)
			{
				throw new ArgumentException("World ID differs from this store.", "worldId");
			}
			RecoveryNotice = null;
			if (!File.Exists(FilePath) && !File.Exists(FilePath + ".bak"))
			{
				return new WorldSnapshot
				{
					WorldId = worldId,
					WorldName = worldName
				};
			}
			Exception ex = null;
			try
			{
				return Read(FilePath, worldId);
			}
			catch (UnsupportedSchemaException)
			{
				throw;
			}
			catch (Exception ex3) when (ex3 is IOException || ex3 is JsonException || ex3 is InvalidDataException)
			{
				ex = ex3;
			}
			try
			{
				WorldSnapshot result = Read(FilePath + ".bak", worldId);
				if (File.Exists(FilePath))
				{
					File.Move(FilePath, FilePath + ".corrupt-" + DateTime.UtcNow.ToString("yyyyMMddHHmmssfff", CultureInfo.InvariantCulture) + "-" + Guid.NewGuid().ToString("N"));
				}
				RecoveryNotice = "Recovered statistics from backup: " + ex?.Message;
				return result;
			}
			catch (Exception ex4)
			{
				throw new IOException("Statistics and backup could not be loaded. Files were preserved; counting is disabled for this world.", new AggregateException(ex, ex4));
			}
		}

		private static WorldSnapshot Read(string path, string worldId)
		{
			//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_0047: Expected O, but got Unknown
			//IL_0048: Unknown result type (might be due to invalid IL or missing references)
			//IL_004d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0059: Expected O, but got Unknown
			//IL_00b0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b6: Invalid comparison between Unknown and I4
			//IL_0146: Unknown result type (might be due to invalid IL or missing references)
			//IL_014d: Invalid comparison between Unknown and I4
			//IL_01fb: Unknown result type (might be due to invalid IL or missing references)
			//IL_0202: Invalid comparison between Unknown and I4
			JObject val2;
			using (FileStream fileStream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read))
			{
				if (fileStream.Length > 33554432)
				{
					throw new InvalidDataException("Statistics file exceeds 32 MB.");
				}
				using StreamReader streamReader = new StreamReader(fileStream, Encoding.UTF8, detectEncodingFromByteOrderMarks: true);
				JsonTextReader val = new JsonTextReader((TextReader)streamReader)
				{
					MaxDepth = JsonSettings.MaxDepth
				};
				try
				{
					val2 = JObject.Load((JsonReader)(object)val, new JsonLoadSettings
					{
						DuplicatePropertyNameHandling = (DuplicatePropertyNameHandling)2
					});
					if (((JsonReader)val).Read())
					{
						throw new InvalidDataException("Unexpected data after statistics object.");
					}
				}
				finally
				{
					((IDisposable)val)?.Dispose();
				}
			}
			if (val2["SchemaVersion"] == null)
			{
				throw new InvalidDataException("Missing statistics schema.");
			}
			if ((int)val2["SchemaVersion"].Type != 6 || ((object)val2["SchemaVersion"]).ToString() != "1")
			{
				throw new UnsupportedSchemaException(val2["SchemaVersion"].ToString((Formatting)0, Array.Empty<JsonConverter>()));
			}
			string[] array = new string[6] { "WorldId", "WorldName", "Revision", "Players", "RecentEventIds", "MetricNames" };
			foreach (string text in array)
			{
				if (val2[text] == null || (int)val2[text].Type == 10)
				{
					throw new InvalidDataException("Missing statistics field: " + text + ".");
				}
			}
			JToken obj = val2["Players"];
			foreach (JToken item in (JArray)(((obj is JArray) ? obj : null) ?? throw new InvalidDataException("Players must be an array.")))
			{
				JObject val3 = (JObject)(object)((item is JObject) ? item : null);
				if (val3 == null)
				{
					throw new InvalidDataException("Invalid player record.");
				}
				array = new string[3] { "PlayerId", "Name", "Values" };
				foreach (string text2 in array)
				{
					if (val3[text2] == null || (int)val3[text2].Type == 10)
					{
						throw new InvalidDataException("Missing player field: " + text2 + ".");
					}
				}
			}
			WorldSnapshot obj2 = ((JToken)val2).ToObject<WorldSnapshot>(JsonSerializer.Create(JsonSettings)) ?? throw new InvalidDataException("Empty statistics file.");
			Validate(obj2, worldId);
			return obj2;
		}

		private static void Validate(WorldSnapshot snapshot, string worldId)
		{
			if (snapshot == null)
			{