Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of Viking Chronicle v0.1.0
BepInEx/plugins/VikingChronicle/VikingChronicle.Core.dll
Decompiled 5 days agousing 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
The result has been truncated due to the large size, download it to view full contents!
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) {