Decompiled source of ValheimSagaStats v0.3.2

plugins/ValheimSagaStats.dll

Decompiled 2 weeks ago
using System;
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 System.Text.RegularExpressions;
using BepInEx;
using BepInEx.Configuration;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using UnityEngine;

[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("ValheimSagaStats")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("ValheimSagaStats")]
[assembly: AssemblyTitle("ValheimSagaStats")]
[assembly: AssemblyVersion("1.0.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace ValheimSagaStats
{
	internal sealed class PlayerStatsSnapshot
	{
		public string Id = "";

		public string Name = "Jugador";

		public long DamageDealt;

		public long DamageTaken;

		public long PlaySeconds;

		public int Kills;

		public int Deaths;

		public int Parries;

		public double DistanceMeters;

		public float LastSeen;

		public bool Online;
	}
	internal sealed class MultiplayerSync
	{
		private const string RpcReport = "VSS_030_Report";

		private const string RpcRoster = "VSS_030_Roster";

		private const float SendInterval = 2f;

		private const float OnlineTimeout = 15f;

		private float _sendTimer;

		private readonly Dictionary<string, PlayerStatsSnapshot> _players = new Dictionary<string, PlayerStatsSnapshot>();

		private readonly string _savePath;

		private bool _registered;

		internal IEnumerable<PlayerStatsSnapshot> Players => (from p in _players.Values
			orderby p.Online descending, p.DamageDealt descending
			select p).ToArray();

		internal MultiplayerSync(string configDir)
		{
			string text = Path.Combine(configDir, "ValheimSagaStats");
			Directory.CreateDirectory(text);
			_savePath = Path.Combine(text, "multiplayer_players.json");
			Load();
		}

		internal void Tick(StatsData local, string playerName)
		{
			try
			{
				if ((Object)(object)ZNet.instance == (Object)null || ZRoutedRpc.instance == null || local == null)
				{
					return;
				}
				RegisterRpcs();
				_sendTimer += Time.unscaledDeltaTime;
				string text = ZNet.GetUID().ToString();
				if (!_players.ContainsKey(text))
				{
					_players[text] = new PlayerStatsSnapshot
					{
						Id = text,
						Name = playerName
					};
				}
				PlayerStatsSnapshot playerStatsSnapshot = FromLocal(text, playerName, local);
				playerStatsSnapshot.LastSeen = Time.unscaledTime;
				playerStatsSnapshot.Online = true;
				_players[text] = playerStatsSnapshot;
				if (_sendTimer >= 2f)
				{
					_sendTimer = 0f;
					if (ZNet.instance.IsServer())
					{
						Save();
						BroadcastRoster();
					}
					else
					{
						ZPackage val = MakeSnapshotPackage(playerStatsSnapshot);
						MethodInfo methodInfo = AccessTools.Method(typeof(ZRoutedRpc), "GetServerPeerID", (Type[])null, (Type[])null);
						if (methodInfo != null)
						{
							object obj = methodInfo.Invoke(ZRoutedRpc.instance, null);
							if (obj is long)
							{
								ZRoutedRpc.instance.InvokeRoutedRPC((long)obj, "VSS_030_Report", new object[1] { val });
							}
						}
						else
						{
							Plugin.DebugLog("No se pudo resolver ZRoutedRpc.GetServerPeerID en esta versión de Valheim.");
						}
					}
				}
				foreach (PlayerStatsSnapshot value in _players.Values)
				{
					value.Online = value.Id == text || Time.unscaledTime - value.LastSeen < 15f;
				}
			}
			catch (Exception ex)
			{
				Plugin.DebugLog("Multiplayer tick: " + ex.Message);
			}
		}

		private void RegisterRpcs()
		{
			if (!_registered)
			{
				ZRoutedRpc.instance.Register<ZPackage>("VSS_030_Report", (Action<long, ZPackage>)RPC_Report);
				ZRoutedRpc.instance.Register<ZPackage>("VSS_030_Roster", (Action<long, ZPackage>)RPC_Roster);
				_registered = true;
				Plugin.DebugLog("RPC multijugador registrados.");
			}
		}

		private void RPC_Report(long sender, ZPackage pkg)
		{
			try
			{
				if (!((Object)(object)ZNet.instance == (Object)null) && ZNet.instance.IsServer() && pkg != null)
				{
					PlayerStatsSnapshot playerStatsSnapshot = ReadSnapshot(pkg);
					if (playerStatsSnapshot != null)
					{
						playerStatsSnapshot.Id = sender.ToString();
						playerStatsSnapshot.LastSeen = Time.unscaledTime;
						playerStatsSnapshot.Online = true;
						_players[playerStatsSnapshot.Id] = playerStatsSnapshot;
						Save();
					}
				}
			}
			catch (Exception ex)
			{
				Plugin.DebugLog("RPC_Report: " + ex.Message);
			}
		}

		private void RPC_Roster(long sender, ZPackage pkg)
		{
			try
			{
				if (pkg == null)
				{
					return;
				}
				int num = pkg.ReadInt();
				if (num < 0 || num > 128)
				{
					return;
				}
				Dictionary<string, PlayerStatsSnapshot> dictionary = new Dictionary<string, PlayerStatsSnapshot>();
				for (int i = 0; i < num; i++)
				{
					PlayerStatsSnapshot playerStatsSnapshot = ReadSnapshot(pkg);
					if (playerStatsSnapshot == null)
					{
						return;
					}
					playerStatsSnapshot.LastSeen = Time.unscaledTime;
					playerStatsSnapshot.Online = true;
					dictionary[playerStatsSnapshot.Id] = playerStatsSnapshot;
				}
				foreach (KeyValuePair<string, PlayerStatsSnapshot> item in dictionary)
				{
					_players[item.Key] = item.Value;
				}
			}
			catch (Exception ex)
			{
				Plugin.DebugLog("RPC_Roster: " + ex.Message);
			}
		}

		private void BroadcastRoster()
		{
			//IL_0035: Unknown result type (might be due to invalid IL or missing references)
			//IL_003b: Expected O, but got Unknown
			List<PlayerStatsSnapshot> list = _players.Values.Where((PlayerStatsSnapshot p) => p.Online).ToList();
			ZPackage val = new ZPackage();
			val.Write(list.Count);
			foreach (PlayerStatsSnapshot item in list)
			{
				WriteSnapshot(val, item);
			}
			ZRoutedRpc.instance.InvokeRoutedRPC(0L, "VSS_030_Roster", new object[1] { val });
		}

		private static ZPackage MakeSnapshotPackage(PlayerStatsSnapshot s)
		{
			//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_000c: Expected O, but got Unknown
			//IL_000d: Expected O, but got Unknown
			ZPackage val = new ZPackage();
			WriteSnapshot(val, s);
			return val;
		}

		private static void WriteSnapshot(ZPackage pkg, PlayerStatsSnapshot s)
		{
			pkg.Write(s.Id ?? "");
			pkg.Write(s.Name ?? "Jugador");
			pkg.Write(s.DamageDealt);
			pkg.Write(s.DamageTaken);
			pkg.Write(s.Kills);
			pkg.Write(s.Deaths);
			pkg.Write(s.Parries);
			pkg.Write((float)s.DistanceMeters);
			pkg.Write(s.PlaySeconds);
		}

		private static PlayerStatsSnapshot ReadSnapshot(ZPackage pkg)
		{
			return new PlayerStatsSnapshot
			{
				Id = pkg.ReadString(),
				Name = pkg.ReadString(),
				DamageDealt = pkg.ReadLong(),
				DamageTaken = pkg.ReadLong(),
				Kills = pkg.ReadInt(),
				Deaths = pkg.ReadInt(),
				Parries = pkg.ReadInt(),
				DistanceMeters = pkg.ReadSingle(),
				PlaySeconds = pkg.ReadLong()
			};
		}

		private static PlayerStatsSnapshot FromLocal(string id, string name, StatsData d)
		{
			return new PlayerStatsSnapshot
			{
				Id = id,
				Name = (string.IsNullOrWhiteSpace(name) ? "Jugador" : name),
				DamageDealt = d.DamageDealt,
				DamageTaken = d.DamageTaken,
				Kills = d.Kills,
				Deaths = d.Deaths,
				Parries = d.Parries,
				DistanceMeters = d.DistanceMeters,
				PlaySeconds = d.PlaySeconds,
				Online = true
			};
		}

		private void Load()
		{
			try
			{
				if (!File.Exists(_savePath))
				{
					return;
				}
				foreach (Match item in Regex.Matches(File.ReadAllText(_savePath), "\\{\\s*\\\"Id\\\"\\s*:\\s*\\\"([^\\\"]*)\\\"\\s*,\\s*\\\"Name\\\"\\s*:\\s*\\\"([^\\\"]*)\\\"\\s*,\\s*\\\"DamageDealt\\\"\\s*:\\s*(\\d+)\\s*,\\s*\\\"DamageTaken\\\"\\s*:\\s*(\\d+)\\s*,\\s*\\\"Kills\\\"\\s*:\\s*(\\d+)\\s*,\\s*\\\"Deaths\\\"\\s*:\\s*(\\d+)\\s*,\\s*\\\"Parries\\\"\\s*:\\s*(\\d+)\\s*,\\s*\\\"DistanceMeters\\\"\\s*:\\s*([0-9.]+)\\s*,\\s*\\\"PlaySeconds\\\"\\s*:\\s*(\\d+)\\s*\\}", RegexOptions.Singleline))
				{
					double.TryParse(item.Groups[8].Value, NumberStyles.Float, CultureInfo.InvariantCulture, out var result);
					PlayerStatsSnapshot playerStatsSnapshot = new PlayerStatsSnapshot
					{
						Id = item.Groups[1].Value,
						Name = item.Groups[2].Value,
						DamageDealt = long.Parse(item.Groups[3].Value),
						DamageTaken = long.Parse(item.Groups[4].Value),
						Kills = int.Parse(item.Groups[5].Value),
						Deaths = int.Parse(item.Groups[6].Value),
						Parries = int.Parse(item.Groups[7].Value),
						DistanceMeters = result,
						PlaySeconds = long.Parse(item.Groups[9].Value),
						Online = false,
						LastSeen = -10000f
					};
					_players[playerStatsSnapshot.Id] = playerStatsSnapshot;
				}
			}
			catch (Exception ex)
			{
				Plugin.DebugLog("Roster load: " + ex.Message);
			}
		}

		internal void Save()
		{
			try
			{
				if ((Object)(object)ZNet.instance != (Object)null && !ZNet.instance.IsServer())
				{
					return;
				}
				List<string> list = new List<string> { "[" };
				int num = 0;
				foreach (PlayerStatsSnapshot value in _players.Values)
				{
					if (num++ > 0)
					{
						list.Add(",");
					}
					list.Add("{\"Id\":\"" + Esc(value.Id) + "\",\"Name\":\"" + Esc(value.Name) + "\",\"DamageDealt\":" + value.DamageDealt + ",\"DamageTaken\":" + value.DamageTaken + ",\"Kills\":" + value.Kills + ",\"Deaths\":" + value.Deaths + ",\"Parries\":" + value.Parries + ",\"DistanceMeters\":" + value.DistanceMeters.ToString("R", CultureInfo.InvariantCulture) + ",\"PlaySeconds\":" + value.PlaySeconds + "}");
				}
				list.Add("]");
				string text = _savePath + ".tmp";
				File.WriteAllText(text, string.Join("", list));
				if (File.Exists(_savePath))
				{
					File.Delete(_savePath);
				}
				File.Move(text, _savePath);
			}
			catch (Exception ex)
			{
				Plugin.DebugLog("Roster save: " + ex.Message);
			}
		}

		private static string Esc(string s)
		{
			return (s ?? "").Replace("\\", "\\\\").Replace("\"", "\\\"").Replace("\r", " ")
				.Replace("\n", " ");
		}
	}
	[BepInPlugin("com.pedro.valheimsagastats", "Valheim Saga Stats", "0.3.0")]
	public class Plugin : BaseUnityPlugin
	{
		public const string PluginGuid = "com.pedro.valheimsagastats";

		public const string PluginName = "Valheim Saga Stats";

		public const string PluginVersion = "0.3.0";

		internal static Plugin Instance;

		internal static StatsStore Store;

		private Harmony _harmony;

		private bool _showPanel;

		private Rect _windowRect = new Rect(70f, 55f, 650f, 610f);

		private Vector2 _scroll;

		private ConfigEntry<KeyboardShortcut> _toggleKey;

		private Vector3 _lastPosition;

		private bool _hasPosition;

		private float _saveTimer;

		private float _playTimer;

		private MultiplayerSync _multiplayer;

		internal static void DebugLog(string message)
		{
			if ((Object)(object)Instance != (Object)null)
			{
				((BaseUnityPlugin)Instance).Logger.LogDebug((object)message);
			}
		}

		private void Awake()
		{
			//IL_0021: Unknown result type (might be due to invalid IL or missing references)
			//IL_006e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0078: Expected O, but got Unknown
			Instance = this;
			_toggleKey = ((BaseUnityPlugin)this).Config.Bind<KeyboardShortcut>("General", "TogglePanelKey", new KeyboardShortcut((KeyCode)287, Array.Empty<KeyCode>()), "Tecla para abrir/cerrar el panel.");
			Store = new StatsStore(Path.Combine(Paths.ConfigPath, "ValheimSagaStats"));
			Store.Load();
			_multiplayer = new MultiplayerSync(Paths.ConfigPath);
			_harmony = new Harmony("com.pedro.valheimsagastats");
			try
			{
				_harmony.PatchAll(typeof(Plugin).Assembly);
				((BaseUnityPlugin)this).Logger.LogInfo((object)"Valheim Saga Stats 0.3.0 cargado; se instalaron los parches de estadísticas.");
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)this).Logger.LogError((object)("No se pudieron instalar todos los parches: " + ex));
			}
		}

		private void Update()
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			//IL_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_007c: Unknown result type (might be due to invalid IL or missing references)
			//IL_007d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0069: Unknown result type (might be due to invalid IL or missing references)
			//IL_006e: Unknown result type (might be due to invalid IL or missing references)
			KeyboardShortcut value = _toggleKey.Value;
			if (((KeyboardShortcut)(ref value)).IsDown())
			{
				_showPanel = !_showPanel;
			}
			try
			{
				object localPlayer = GameReflection.LocalPlayer;
				if (localPlayer != null)
				{
					Component val = (Component)((localPlayer is Component) ? localPlayer : null);
					if ((Object)(object)val != (Object)null)
					{
						Vector3 position = val.transform.position;
						if (_hasPosition)
						{
							Store.Data.DistanceMeters += Vector3.Distance(_lastPosition, position);
						}
						_lastPosition = position;
						_hasPosition = true;
						_playTimer += Time.unscaledDeltaTime;
						if (_playTimer >= 1f)
						{
							Store.Data.PlaySeconds++;
							Store.Data.SessionSeconds++;
							_playTimer -= 1f;
						}
					}
				}
				else
				{
					_hasPosition = false;
				}
				if (localPlayer != null && _multiplayer != null)
				{
					_multiplayer.Tick(Store.Data, GetLocalPlayerName(localPlayer));
				}
				_saveTimer += Time.unscaledDeltaTime;
				if (_saveTimer >= 30f)
				{
					Store.Save();
					_saveTimer = 0f;
				}
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)this).Logger.LogDebug((object)("Actualización de métricas: " + ex.Message));
			}
		}

		private void OnGUI()
		{
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			//IL_002b: Expected O, but got Unknown
			//IL_0026: Unknown result type (might be due to invalid IL or missing references)
			//IL_002b: Unknown result type (might be due to invalid IL or missing references)
			if (_showPanel)
			{
				_windowRect = GUI.Window(749031, _windowRect, new WindowFunction(DrawWindow), "VALHEIM SAGA STATS  ·  v0.3.0");
			}
		}

		private void DrawWindow(int id)
		{
			//IL_000a: Unknown result type (might be due to invalid IL or missing references)
			//IL_000f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0026: Expected O, but got Unknown
			//IL_0030: Unknown result type (might be due to invalid IL or missing references)
			//IL_0035: Unknown result type (might be due to invalid IL or missing references)
			//IL_003d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0045: Expected O, but got Unknown
			//IL_0085: 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_00a8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ad: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c6: Expected O, but got Unknown
			//IL_03df: Unknown result type (might be due to invalid IL or missing references)
			GUIStyle val = new GUIStyle(GUI.skin.label)
			{
				fontSize = 21,
				fontStyle = (FontStyle)1,
				alignment = (TextAnchor)4
			};
			GUIStyle val2 = new GUIStyle(GUI.skin.label)
			{
				fontSize = 11,
				alignment = (TextAnchor)4
			};
			GUILayout.BeginVertical(Array.Empty<GUILayoutOption>());
			GUILayout.Space(5f);
			GUILayout.Label("CRÓNICAS DEL GUERRERO", val, Array.Empty<GUILayoutOption>());
			GUILayout.Label("Marcador multijugador · sincronización ~2 s · F6 para ocultar", val2, Array.Empty<GUILayoutOption>());
			GUILayout.Space(10f);
			_scroll = GUILayout.BeginScrollView(_scroll, Array.Empty<GUILayoutOption>());
			GUILayout.Label("JUGADORES Y ESTADÍSTICAS ACUMULADAS", new GUIStyle(GUI.skin.label)
			{
				fontSize = 14,
				fontStyle = (FontStyle)1
			}, Array.Empty<GUILayoutOption>());
			GUILayout.BeginHorizontal(GUIStyle.op_Implicit("box"), Array.Empty<GUILayoutOption>());
			GUILayout.Label("Estado / jugador", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(150f) });
			GUILayout.Label("Daño +", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(75f) });
			GUILayout.Label("Daño -", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(75f) });
			GUILayout.Label("Bajas", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(50f) });
			GUILayout.Label("Muertes", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(55f) });
			GUILayout.Label("Distancia", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(80f) });
			GUILayout.EndHorizontal();
			bool flag = false;
			if (_multiplayer != null)
			{
				foreach (PlayerStatsSnapshot player in _multiplayer.Players)
				{
					flag = true;
					GUILayout.BeginHorizontal(GUIStyle.op_Implicit("box"), Array.Empty<GUILayoutOption>());
					GUILayout.Label((player.Online ? "● " : "○ ") + player.Name, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(150f) });
					GUILayout.Label(player.DamageDealt.ToString("N0"), (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(75f) });
					GUILayout.Label(player.DamageTaken.ToString("N0"), (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(75f) });
					GUILayout.Label(player.Kills.ToString("N0"), (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(50f) });
					GUILayout.Label(player.Deaths.ToString("N0"), (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(55f) });
					GUILayout.Label(FormatDistance(player.DistanceMeters), (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(80f) });
					GUILayout.EndHorizontal();
				}
			}
			if (!flag)
			{
				GUILayout.Label("Esperando estadísticas de jugadores con el mod instalado...", Array.Empty<GUILayoutOption>());
			}
			GUILayout.Space(8f);
			GUILayout.Label("Los datos se acumulan entre sesiones. El anfitrión conserva el historial compartido.", val2, Array.Empty<GUILayoutOption>());
			GUILayout.Label("Para sincronizar: todos deben tener la misma versión del mod y entrar al mundo con Start modded.", val2, Array.Empty<GUILayoutOption>());
			GUILayout.EndScrollView();
			GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
			if (GUILayout.Button("Guardar ahora", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(30f) }))
			{
				Store.Save();
				if (_multiplayer != null)
				{
					_multiplayer.Save();
				}
			}
			if (GUILayout.Button("Reiniciar estadísticas", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(30f) }))
			{
				Store.Reset();
			}
			if (GUILayout.Button("Cerrar", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(30f) }))
			{
				_showPanel = false;
			}
			GUILayout.EndHorizontal();
			GUILayout.EndVertical();
			GUI.DragWindow(new Rect(0f, 0f, 10000f, 28f));
		}

		private void Card(string label, string value)
		{
			//IL_0036: Unknown result type (might be due to invalid IL or missing references)
			//IL_003b: 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_004a: Unknown result type (might be due to invalid IL or missing references)
			//IL_005b: Expected O, but got Unknown
			//IL_0066: Unknown result type (might be due to invalid IL or missing references)
			//IL_006b: 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_008b: Expected O, but got Unknown
			GUILayout.BeginVertical(GUIStyle.op_Implicit("box"), (GUILayoutOption[])(object)new GUILayoutOption[2]
			{
				GUILayout.MinHeight(65f),
				GUILayout.ExpandWidth(true)
			});
			GUILayout.Label(label, new GUIStyle(GUI.skin.label)
			{
				fontSize = 10,
				alignment = (TextAnchor)4,
				wordWrap = true
			}, Array.Empty<GUILayoutOption>());
			GUILayout.Label(value, new GUIStyle(GUI.skin.label)
			{
				fontSize = 19,
				fontStyle = (FontStyle)1,
				alignment = (TextAnchor)4
			}, Array.Empty<GUILayoutOption>());
			GUILayout.EndVertical();
		}

		private string GetLocalPlayerName(object player)
		{
			try
			{
				object obj = GameReflection.Call(player, "GetPlayerName");
				if (obj != null && !string.IsNullOrWhiteSpace(obj.ToString()))
				{
					return obj.ToString();
				}
				obj = GameReflection.Read(player, "m_playerName");
				if (obj != null && !string.IsNullOrWhiteSpace(obj.ToString()))
				{
					return obj.ToString();
				}
				obj = GameReflection.Read(player, "m_name");
				if (obj != null && !string.IsNullOrWhiteSpace(obj.ToString()))
				{
					return obj.ToString();
				}
			}
			catch
			{
			}
			return "Jugador local";
		}

		private static string FormatDistance(double m)
		{
			if (!(m >= 1000.0))
			{
				return m.ToString("N0") + " m";
			}
			return (m / 1000.0).ToString("N2") + " km";
		}

		private static string FormatTime(long sec)
		{
			TimeSpan timeSpan = TimeSpan.FromSeconds(Math.Max(0L, sec));
			return ((int)timeSpan.TotalHours).ToString("D2") + ":" + timeSpan.Minutes.ToString("D2") + ":" + timeSpan.Seconds.ToString("D2");
		}

		private void OnDestroy()
		{
			if (Store != null)
			{
				Store.Save();
			}
			if (_multiplayer != null)
			{
				_multiplayer.Save();
			}
			if (_harmony != null)
			{
				_harmony.UnpatchSelf();
			}
			Instance = null;
		}
	}
	internal static class GameReflection
	{
		internal static object LocalPlayer
		{
			get
			{
				Type type = AccessTools.TypeByName("Player");
				if (type == null)
				{
					return null;
				}
				FieldInfo fieldInfo = AccessTools.Field(type, "m_localPlayer");
				if (!(fieldInfo == null))
				{
					return fieldInfo.GetValue(null);
				}
				return null;
			}
		}

		internal static object Read(object obj, string name)
		{
			if (obj == null)
			{
				return null;
			}
			Type type = obj.GetType();
			while (type != null)
			{
				FieldInfo field = type.GetField(name, BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
				if (field != null)
				{
					return field.GetValue(obj);
				}
				PropertyInfo property = type.GetProperty(name, BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
				if (property != null && property.GetIndexParameters().Length == 0)
				{
					try
					{
						return property.GetValue(obj, null);
					}
					catch
					{
					}
				}
				type = type.BaseType;
			}
			return null;
		}

		internal static object Call(object obj, string name)
		{
			if (obj == null)
			{
				return null;
			}
			try
			{
				MethodInfo methodInfo = AccessTools.Method(obj.GetType(), name, Type.EmptyTypes, (Type[])null);
				return (methodInfo == null) ? null : methodInfo.Invoke(obj, null);
			}
			catch
			{
				return null;
			}
		}

		internal static double DamageAmount(object hit)
		{
			object obj = Call(hit, "GetTotalDamage");
			if (obj != null)
			{
				try
				{
					return Math.Max(0.0, Convert.ToDouble(obj));
				}
				catch
				{
				}
			}
			object obj3 = Read(hit, "m_damage");
			if (obj3 == null)
			{
				return 0.0;
			}
			double num = 0.0;
			FieldInfo[] fields = obj3.GetType().GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
			foreach (FieldInfo fieldInfo in fields)
			{
				if (fieldInfo.FieldType == typeof(float) || fieldInfo.FieldType == typeof(double) || fieldInfo.FieldType == typeof(int))
				{
					try
					{
						num += Convert.ToDouble(fieldInfo.GetValue(obj3));
					}
					catch
					{
					}
				}
			}
			return Math.Max(0.0, num);
		}

		internal static bool IsPlayerAttacker(object hit)
		{
			object localPlayer = LocalPlayer;
			if (localPlayer == null || hit == null)
			{
				return false;
			}
			object obj = Call(hit, "GetAttacker");
			if (obj != null)
			{
				if (obj == localPlayer)
				{
					return true;
				}
				object obj2 = Call(obj, "GetZDOID");
				object obj3 = Call(localPlayer, "GetZDOID");
				if (obj2 != null && obj3 != null && (obj2.Equals(obj3) || obj2.ToString() == obj3.ToString()))
				{
					return true;
				}
			}
			object obj4 = Read(hit, "m_attacker");
			object obj5 = Call(localPlayer, "GetZDOID");
			if (obj4 != null && obj5 != null && (obj4.Equals(obj5) || obj4.ToString() == obj5.ToString()))
			{
				return true;
			}
			return false;
		}

		internal static string CreatureName(object character)
		{
			object obj = Read(character, "m_name");
			if (obj != null && !string.IsNullOrWhiteSpace(obj.ToString()))
			{
				return obj.ToString().Trim();
			}
			if (character != null)
			{
				return character.GetType().Name;
			}
			return "Desconocido";
		}
	}
	[HarmonyPatch]
	internal static class DamagePatch
	{
		private static readonly Dictionary<int, bool> LastHitByLocalPlayer = new Dictionary<int, bool>();

		private static IEnumerable<MethodBase> TargetMethods()
		{
			Type type = AccessTools.TypeByName("Character");
			if (type == null)
			{
				yield break;
			}
			foreach (MethodInfo declaredMethod in AccessTools.GetDeclaredMethods(type))
			{
				if (declaredMethod.Name == "Damage" && declaredMethod.GetParameters().Length != 0)
				{
					yield return declaredMethod;
				}
			}
		}

		private static void Prefix(object __instance, object[] __args)
		{
			try
			{
				if (Plugin.Store == null || __instance == null)
				{
					return;
				}
				object obj = null;
				foreach (object obj2 in __args)
				{
					if (obj2 != null && obj2.GetType().Name == "HitData")
					{
						obj = obj2;
						break;
					}
				}
				if (obj == null)
				{
					return;
				}
				double num = GameReflection.DamageAmount(obj);
				if (num <= 0.0)
				{
					return;
				}
				object localPlayer = GameReflection.LocalPlayer;
				if (localPlayer != null)
				{
					if (__instance == localPlayer)
					{
						Plugin.Store.Data.DamageTaken += (long)Math.Round(num);
						return;
					}
					bool value = GameReflection.IsPlayerAttacker(obj);
					int key = (((Object)((__instance is Object) ? __instance : null) != (Object)null) ? ((Object)((__instance is Object) ? __instance : null)).GetInstanceID() : __instance.GetHashCode());
					LastHitByLocalPlayer[key] = value;
				}
			}
			catch (Exception ex)
			{
				if ((Object)(object)Plugin.Instance != (Object)null)
				{
					Plugin.DebugLog("DamagePatch: " + ex.Message);
				}
			}
		}

		internal static void MarkLastHitFromPlayer(int id)
		{
			LastHitByLocalPlayer[id] = true;
		}

		internal static bool WasLastHitFromPlayer(object character)
		{
			if (character == null)
			{
				return false;
			}
			int key = (((Object)((character is Object) ? character : null) != (Object)null) ? ((Object)((character is Object) ? character : null)).GetInstanceID() : character.GetHashCode());
			bool value;
			return LastHitByLocalPlayer.TryGetValue(key, out value) && value;
		}
	}
	[HarmonyPatch]
	internal static class AppliedDamagePatch
	{
		private static IEnumerable<MethodBase> TargetMethods()
		{
			Type type = AccessTools.TypeByName("Character");
			if (type == null)
			{
				yield break;
			}
			foreach (MethodInfo declaredMethod in AccessTools.GetDeclaredMethods(type))
			{
				if (declaredMethod.Name == "ApplyDamage" && declaredMethod.GetParameters().Length != 0)
				{
					yield return declaredMethod;
				}
			}
		}

		private static void Prefix(object __instance, object[] __args)
		{
			try
			{
				if (Plugin.Store == null || __instance == null)
				{
					return;
				}
				object obj = null;
				foreach (object obj2 in __args)
				{
					if (obj2 != null && obj2.GetType().Name == "HitData")
					{
						obj = obj2;
						break;
					}
				}
				if (obj == null)
				{
					return;
				}
				object localPlayer = GameReflection.LocalPlayer;
				if (localPlayer != null && __instance != localPlayer && GameReflection.IsPlayerAttacker(obj))
				{
					double num = GameReflection.DamageAmount(obj);
					if (num > 0.0)
					{
						Plugin.Store.Data.DamageDealt += (long)Math.Round(num);
					}
					DamagePatch.MarkLastHitFromPlayer(((Object)((__instance is Object) ? __instance : null) != (Object)null) ? ((Object)((__instance is Object) ? __instance : null)).GetInstanceID() : __instance.GetHashCode());
				}
			}
			catch (Exception ex)
			{
				if ((Object)(object)Plugin.Instance != (Object)null)
				{
					Plugin.DebugLog("AppliedDamagePatch: " + ex.Message);
				}
			}
		}
	}
	[HarmonyPatch]
	internal static class DeathPatch
	{
		private static IEnumerable<MethodBase> TargetMethods()
		{
			Type type = AccessTools.TypeByName("Character");
			if (type == null)
			{
				yield break;
			}
			foreach (MethodInfo declaredMethod in AccessTools.GetDeclaredMethods(type))
			{
				if (declaredMethod.Name == "OnDeath")
				{
					yield return declaredMethod;
				}
			}
		}

		private static void Postfix(object __instance)
		{
			try
			{
				if (Plugin.Store != null && __instance != null)
				{
					if (__instance == GameReflection.LocalPlayer)
					{
						Plugin.Store.Data.Deaths++;
					}
					else if (DamagePatch.WasLastHitFromPlayer(__instance))
					{
						Plugin.Store.Data.Kills++;
						string key = GameReflection.CreatureName(__instance);
						Plugin.Store.Data.KillsByCreature.TryGetValue(key, out var value);
						Plugin.Store.Data.KillsByCreature[key] = value + 1;
					}
				}
			}
			catch (Exception ex)
			{
				if ((Object)(object)Plugin.Instance != (Object)null)
				{
					Plugin.DebugLog("DeathPatch: " + ex.Message);
				}
			}
		}
	}
	[HarmonyPatch]
	internal static class BlockPatch
	{
		private static IEnumerable<MethodBase> TargetMethods()
		{
			Type type = AccessTools.TypeByName("Humanoid");
			if (type == null)
			{
				yield break;
			}
			foreach (MethodInfo declaredMethod in AccessTools.GetDeclaredMethods(type))
			{
				if ((declaredMethod.Name == "BlockAttack" || declaredMethod.Name == "BlockAttackNoStamina") && declaredMethod.GetParameters().Length != 0 && declaredMethod.ReturnType == typeof(bool))
				{
					yield return declaredMethod;
				}
			}
		}

		private static void Postfix(object __instance, object __result)
		{
			try
			{
				if (Plugin.Store != null && __instance == GameReflection.LocalPlayer && __result is bool && (bool)__result)
				{
					Plugin.Store.Data.Parries++;
				}
			}
			catch (Exception ex)
			{
				if ((Object)(object)Plugin.Instance != (Object)null)
				{
					Plugin.DebugLog("BlockPatch: " + ex.Message);
				}
			}
		}
	}
	[Serializable]
	public class StatsData
	{
		public long DamageDealt;

		public long DamageTaken;

		public int Kills;

		public int Deaths;

		public int Parries;

		public double DistanceMeters;

		public long PlaySeconds;

		public long SessionSeconds;

		public Dictionary<string, int> KillsByCreature = new Dictionary<string, int>();
	}
	public class StatsStore
	{
		private readonly string _directory;

		private readonly string _filePath;

		public StatsData Data { get; private set; } = new StatsData();

		public StatsStore(string directory)
		{
			_directory = directory;
			_filePath = Path.Combine(directory, "stats.json");
		}

		public void Load()
		{
			try
			{
				if (!Directory.Exists(_directory))
				{
					Directory.CreateDirectory(_directory);
				}
				if (!File.Exists(_filePath))
				{
					return;
				}
				string text = File.ReadAllText(_filePath);
				Data = new StatsData
				{
					DamageDealt = ReadLong(text, "DamageDealt"),
					DamageTaken = ReadLong(text, "DamageTaken"),
					Kills = (int)ReadLong(text, "Kills"),
					Deaths = (int)ReadLong(text, "Deaths"),
					Parries = (int)ReadLong(text, "Parries"),
					DistanceMeters = ReadDouble(text, "DistanceMeters"),
					PlaySeconds = ReadLong(text, "PlaySeconds"),
					SessionSeconds = 0L
				};
				Match match = Regex.Match(text, "\\\"KillsByCreature\\\"\\s*:\\s*\\{([^}]*)\\}", RegexOptions.Singleline);
				if (!match.Success)
				{
					return;
				}
				foreach (Match item in Regex.Matches(match.Groups[1].Value, "\\\"((?:\\\\.|[^\\\"])*)\\\"\\s*:\\s*(\\d+)"))
				{
					string key = item.Groups[1].Value.Replace("\\\"", "\"").Replace("\\\\", "\\");
					if (int.TryParse(item.Groups[2].Value, out var result))
					{
						Data.KillsByCreature[key] = result;
					}
				}
			}
			catch (Exception ex)
			{
				Debug.LogWarning((object)("[Valheim Saga Stats] Load failed: " + ex.Message));
			}
		}

		private static long ReadLong(string j, string k)
		{
			Match match = Regex.Match(j, "\\\"" + Regex.Escape(k) + "\\\"\\s*:\\s*(-?\\d+)");
			if (!match.Success || !long.TryParse(match.Groups[1].Value, out var result))
			{
				return 0L;
			}
			return result;
		}

		private static double ReadDouble(string j, string k)
		{
			Match match = Regex.Match(j, "\\\"" + Regex.Escape(k) + "\\\"\\s*:\\s*(-?\\d+(?:\\.\\d+)?)");
			if (!match.Success || !double.TryParse(match.Groups[1].Value, NumberStyles.Float, CultureInfo.InvariantCulture, out var result))
			{
				return 0.0;
			}
			return result;
		}

		public void Save()
		{
			try
			{
				if (!Directory.Exists(_directory))
				{
					Directory.CreateDirectory(_directory);
				}
				StringBuilder stringBuilder = new StringBuilder();
				stringBuilder.AppendLine("{");
				stringBuilder.AppendLine("  \"DamageDealt\": " + Data.DamageDealt + ",");
				stringBuilder.AppendLine("  \"DamageTaken\": " + Data.DamageTaken + ",");
				stringBuilder.AppendLine("  \"Kills\": " + Data.Kills + ",");
				stringBuilder.AppendLine("  \"Deaths\": " + Data.Deaths + ",");
				stringBuilder.AppendLine("  \"Parries\": " + Data.Parries + ",");
				stringBuilder.AppendLine("  \"DistanceMeters\": " + Data.DistanceMeters.ToString("R", CultureInfo.InvariantCulture) + ",");
				stringBuilder.AppendLine("  \"PlaySeconds\": " + Data.PlaySeconds + ",");
				stringBuilder.AppendLine("  \"KillsByCreature\": {");
				int num = 0;
				foreach (KeyValuePair<string, int> item in Data.KillsByCreature)
				{
					stringBuilder.Append("    \"").Append(Escape(item.Key)).Append("\": ")
						.Append(item.Value);
					if (++num < Data.KillsByCreature.Count)
					{
						stringBuilder.Append(',');
					}
					stringBuilder.AppendLine();
				}
				stringBuilder.AppendLine("  }");
				stringBuilder.AppendLine("}");
				string text = _filePath + ".tmp";
				File.WriteAllText(text, stringBuilder.ToString());
				if (File.Exists(_filePath))
				{
					File.Delete(_filePath);
				}
				File.Move(text, _filePath);
			}
			catch (Exception ex)
			{
				Debug.LogWarning((object)("[Valheim Saga Stats] Save failed: " + ex.Message));
			}
		}

		private static string Escape(string s)
		{
			return (s ?? "").Replace("\\", "\\\\").Replace("\"", "\\\"").Replace("\r", " ")
				.Replace("\n", " ");
		}

		public void Reset()
		{
			Data = new StatsData();
			Save();
		}
	}
}