Decompiled source of StatsCore v1.0.1

StatsCore.dll

Decompiled a week ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Cryptography;
using System.Security.Permissions;
using System.Text;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using MenuLib;
using MenuLib.MonoBehaviors;
using Microsoft.CodeAnalysis;
using Photon.Pun;
using Photon.Realtime;
using TMPro;
using UnityEngine;
using UnityEngine.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: IgnoresAccessChecksTo("Assembly-CSharp")]
[assembly: AssemblyCompany("Vippy")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.1.0")]
[assembly: AssemblyInformationalVersion("1.0.1+76abf6d8aba9a8968e5e7375fe5be3886b8837b9")]
[assembly: AssemblyProduct("StatsCore")]
[assembly: AssemblyTitle("StatsCore")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.0.1.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

		public NullableAttribute(byte P_0)
		{
			NullableFlags = new byte[1] { P_0 };
		}

		public NullableAttribute(byte[] P_0)
		{
			NullableFlags = P_0;
		}
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableContextAttribute : Attribute
	{
		public readonly byte Flag;

		public NullableContextAttribute(byte P_0)
		{
			Flag = P_0;
		}
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace StatsCore
{
	public static class GameStats
	{
		public const string K_Deaths = "statscore.deaths";

		public const string K_DeathsByEnemy = "statscore.deathsByEnemy";

		public const string K_Knockdowns = "statscore.knockdowns";

		public const string K_Revives = "statscore.revives";

		public const string K_SpewerFaces = "statscore.spewerFaces";

		public const string K_CrownWins = "statscore.crownWins";

		public const string K_MapVisits = "statscore.mapVisits";

		public const string K_DamageTaken = "statscore.damageTaken";

		public const string K_DamageTakenByEnemy = "statscore.damageTakenByEnemy";

		public const string K_HitsTaken = "statscore.hitsTaken";

		public const string K_HealthHealed = "statscore.healthHealed";

		public const string K_DistanceTraveled = "statscore.distanceTraveled";

		public const string K_DistanceWalking = "statscore.distanceWalking";

		public const string K_DistanceSprinting = "statscore.distanceSprinting";

		public const string K_ObjectsGrabbed = "statscore.objectsGrabbed";

		public const string K_EnemiesKilled = "statscore.enemiesKilled";

		public const string K_FriendlyFireDealt = "statscore.friendlyFireDealt";

		public const string K_TeammatesKilled = "statscore.teammatesKilled";

		public const string K_DamageFromTeammates = "statscore.damageFromTeammates";

		public const string K_RunLevel = "statscore.runLevel";

		public const string K_RunCurrency = "statscore.runCurrency";

		public const string K_RunTotalHaul = "statscore.runTotalHaul";

		private static bool _registered;

		private static float _distAccum;

		private static float _walkAccum;

		private static float _runAccum;

		public static Stat Deaths { get; private set; }

		public static Stat DeathsByEnemy { get; private set; }

		public static Stat Knockdowns { get; private set; }

		public static Stat Revives { get; private set; }

		public static Stat SpewerFaces { get; private set; }

		public static Stat CrownWins { get; private set; }

		public static Stat MapVisits { get; private set; }

		public static Stat DamageTaken { get; private set; }

		public static Stat DamageTakenByEnemy { get; private set; }

		public static Stat HitsTaken { get; private set; }

		public static Stat HealthHealed { get; private set; }

		public static Stat DistanceTraveled { get; private set; }

		public static Stat DistanceWalking { get; private set; }

		public static Stat DistanceSprinting { get; private set; }

		public static Stat ObjectsGrabbed { get; private set; }

		public static Stat EnemiesKilled { get; private set; }

		public static Stat FriendlyFireDealt { get; private set; }

		public static Stat TeammatesKilled { get; private set; }

		public static Stat DamageFromTeammates { get; private set; }

		public static Stat RunLevel { get; private set; }

		public static Stat RunCurrency { get; private set; }

		public static Stat RunTotalHaul { get; private set; }

		internal static void Register()
		{
			if (!_registered)
			{
				_registered = true;
				DefinePack();
				WireMisfortune();
				WireRecovery();
				WireGlory();
				WireMischief();
				WireRunMirrors();
			}
		}

		private static void DefinePack()
		{
			Deaths = Career("statscore.deaths", "Deaths", "Times you've gone down for good.", "Misfortune").TrackDaily();
			DeathsByEnemy = Career("statscore.deathsByEnemy", "Deaths by Enemy", "Your deaths, broken down by what killed you (keyed by enemy name).", "Misfortune");
			Knockdowns = Career("statscore.knockdowns", "Knockdowns", "Times something put you on the floor.", "Misfortune").TrackDaily();
			SpewerFaces = Career("statscore.spewerFaces", "Spewer Faces", "Times a spewer latched onto your face.", "Misfortune");
			DamageTaken = Career("statscore.damageTaken", "Damage Taken", "Total damage you've soaked.", "Misfortune", StatFormat.Raw).TrackDaily();
			DamageTakenByEnemy = Career("statscore.damageTakenByEnemy", "Damage by Enemy", "Damage dealt to you, broken down by what dealt it (keyed by enemy name). How hard each thing has clapped you.", "Misfortune", StatFormat.Raw);
			HitsTaken = Career("statscore.hitsTaken", "Hits Taken", "Number of separate times you've been hurt.", "Misfortune").TrackDaily();
			Revives = Career("statscore.revives", "Revives", "Times you were brought back.", "Recovery").TrackDaily();
			HealthHealed = Career("statscore.healthHealed", "Health Healed", "Total health restored to you.", "Recovery", StatFormat.Raw).TrackDaily();
			CrownWins = Career("statscore.crownWins", "Crown Wins", "King-of-the-losers victories.", "Glory");
			EnemiesKilled = Career("statscore.enemiesKilled", "Enemies Killed", "Enemies that died to your handiwork (best effort - the game rarely names a killer).", "Glory").TrackDaily();
			MapVisits = Career("statscore.mapVisits", "Map Visits", "How many times you've dropped into each level (keyed by level name).", "Expeditions");
			DistanceTraveled = Career("statscore.distanceTraveled", "Distance Traveled", "Total ground you've covered, in meters.", "Mobility", StatFormat.Distance).TrackDaily();
			DistanceWalking = Career("statscore.distanceWalking", "Distance Walking", "Metres covered at a walk.", "Mobility", StatFormat.Distance).TrackDaily();
			DistanceSprinting = Career("statscore.distanceSprinting", "Distance Sprinting", "Metres covered at a sprint.", "Mobility", StatFormat.Distance).TrackDaily();
			ObjectsGrabbed = Career("statscore.objectsGrabbed", "Objects Grabbed", "Things you've latched onto with the grab beam.", "Mobility").TrackDaily();
			FriendlyFireDealt = Career("statscore.friendlyFireDealt", "Friendly Fire", "Damage you've dealt to your own crew.", "Mischief", StatFormat.Raw).TrackDaily();
			TeammatesKilled = Career("statscore.teammatesKilled", "Teammates Killed", "Crewmates you've personally finished off.", "Mischief");
			DamageFromTeammates = Career("statscore.damageFromTeammates", "Betrayal Taken", "Damage your own crew has dealt to you.", "Mischief", StatFormat.Raw).TrackDaily();
			RunLevel = Stats.Define("statscore.runLevel", StatScope.Team, StatLifetime.Run).Describe("Run Level", "The expedition level the crew is on.", "Expedition", StatFormat.Raw);
			RunCurrency = Stats.Define("statscore.runCurrency", StatScope.Team, StatLifetime.Run).Describe("Run Currency", "Money the crew is holding this run.", "Expedition", StatFormat.Money);
			RunTotalHaul = Stats.Define("statscore.runTotalHaul", StatScope.Team, StatLifetime.Run).Describe("Total Haul", "Total value the crew has hauled this run.", "Expedition", StatFormat.Money);
		}

		private static void WireMisfortune()
		{
			Trackers.Hook(Deaths, typeof(PlayerAvatar), "PlayerDeathRPC", delegate(Stat s, object? inst, object[] args)
			{
				PlayerAvatar val = (PlayerAvatar)((inst is PlayerAvatar) ? inst : null);
				if (val != null && IsLocal(val))
				{
					s.Add(val.steamID, 1);
					int enemyIndex = ((args.Length != 0 && args[0] is int num) ? num : (-1));
					DeathsByEnemy.Add(EnemyName(enemyIndex), 1);
				}
			});
			Trackers.Hook(Knockdowns, typeof(PlayerTumble), "TumbleSetRPC", delegate(Stat s, object? inst, object[] args)
			{
				if (args.Length >= 2 && (bool)args[0] && !(bool)args[1])
				{
					PlayerAvatar val = ((PlayerTumble)(((inst is PlayerTumble) ? inst : null)?)).playerAvatar;
					if ((Object)(object)val != (Object)null && IsLocal(val))
					{
						s.Add(val.steamID, 1);
					}
				}
			});
			Trackers.Hook(SpewerFaces, typeof(EnemySlowMouth), "UpdateStateRPC", delegate(Stat s, object? inst, object[] args)
			{
				//IL_0009: Unknown result type (might be due to invalid IL or missing references)
				//IL_0010: Invalid comparison between Unknown and I4
				if (args.Length >= 1 && (int)(State)args[0] == 9)
				{
					PlayerAvatar val = ((EnemySlowMouth)(((inst is EnemySlowMouth) ? inst : null)?)).playerTarget;
					if ((Object)(object)val != (Object)null && IsLocal(val))
					{
						s.Add(val.steamID, 1);
					}
				}
			});
			Trackers.Hook(DamageTaken, typeof(PlayerHealth), "Hurt", delegate(Stat s, object? inst, object[] args)
			{
				PlayerHealth val = (PlayerHealth)((inst is PlayerHealth) ? inst : null);
				if (val != null)
				{
					PlayerAvatar playerAvatar = val.playerAvatar;
					if (!((Object)(object)playerAvatar == (Object)null) && IsLocal(playerAvatar))
					{
						int num = ((args.Length != 0 && args[0] is int num2) ? num2 : 0);
						bool flag = default(bool);
						int num3;
						if (args.Length > 3)
						{
							object obj = args[3];
							if (obj is bool)
							{
								flag = (bool)obj;
								num3 = 1;
							}
							else
							{
								num3 = 0;
							}
						}
						else
						{
							num3 = 0;
						}
						bool flag2 = (byte)((uint)num3 & (flag ? 1u : 0u)) != 0;
						if (!(num <= 0 || flag2))
						{
							s.Add(playerAvatar.steamID, num);
							HitsTaken.Add(playerAvatar.steamID, 1);
							int enemyIndex = ((args.Length > 2 && args[2] is int num4) ? num4 : (-1));
							DamageTakenByEnemy.Add(EnemyName(enemyIndex), num);
						}
					}
				}
			}, new Type[4]
			{
				typeof(int),
				typeof(bool),
				typeof(int),
				typeof(bool)
			});
		}

		private static void WireRecovery()
		{
			Trackers.Hook(Revives, typeof(PlayerAvatar), "ReviveRPC", delegate(Stat s, object? inst, object[] args)
			{
				PlayerAvatar val = (PlayerAvatar)((inst is PlayerAvatar) ? inst : null);
				if (val != null && IsLocal(val))
				{
					s.Add(val.steamID, 1);
				}
			});
			Trackers.Hook(HealthHealed, typeof(PlayerHealth), "Heal", delegate(Stat s, object? inst, object[] args)
			{
				PlayerHealth val = (PlayerHealth)((inst is PlayerHealth) ? inst : null);
				if (val != null)
				{
					PlayerAvatar playerAvatar = val.playerAvatar;
					if (!((Object)(object)playerAvatar == (Object)null) && IsLocal(playerAvatar))
					{
						int num = ((args.Length != 0 && args[0] is int num2) ? num2 : 0);
						if (num > 0)
						{
							s.Add(playerAvatar.steamID, num);
						}
					}
				}
			}, new Type[2]
			{
				typeof(int),
				typeof(bool)
			});
		}

		private static void WireGlory()
		{
			Trackers.Hook(CrownWins, typeof(Arena), "CrownGrabRPC", delegate(Stat s, object? inst, object[] args)
			{
				PlayerAvatar val = ((Arena)(((inst is Arena) ? inst : null)?)).winnerPlayer;
				if ((Object)(object)val != (Object)null && IsLocal(val))
				{
					s.Add(val.steamID, 1);
				}
			});
			Trackers.Hook(CrownWins, typeof(ArenaRace), "SetWinnerRPC", delegate(Stat s, object? inst, object[] args)
			{
				PlayerAvatar val = ((ArenaRace)(((inst is ArenaRace) ? inst : null)?)).winnerPlayer;
				if ((Object)(object)val != (Object)null && IsLocal(val))
				{
					s.Add(val.steamID, 1);
				}
			});
			Trackers.Hook(EnemiesKilled, typeof(EnemyHealth), "DeathRPC", delegate(Stat s, object? inst, object[] args)
			{
				PlayerAvatar val = ((EnemyHealth)(((inst is EnemyHealth) ? inst : null)?)).onObjectHurtPlayer;
				if ((Object)(object)val != (Object)null && IsLocal(val))
				{
					s.Add(val.steamID, 1);
				}
			});
			Trackers.Hook(ObjectsGrabbed, typeof(PhysGrabObject), "GrabStarted", delegate(Stat s, object? inst, object[] args)
			{
				PhysGrabber val = (PhysGrabber)((args.Length != 0) ? /*isinst with value type is only supported in some contexts*/: null);
				PlayerAvatar val2 = (((Object)(object)val != (Object)null) ? val.playerAvatar : null);
				if ((Object)(object)val2 != (Object)null && IsLocal(val2))
				{
					s.Add(val2.steamID, 1);
				}
			}, new Type[1] { typeof(PhysGrabber) });
		}

		private static void WireMischief()
		{
			Trackers.Hook(FriendlyFireDealt, typeof(PunManager), "PlayerDamagingPlayerRPC", delegate(Stat s, object? inst, object[] args)
			{
				if (args.Length >= 5)
				{
					int num = ((args[0] is int num2) ? num2 : 0);
					int num3 = ((args[1] is int num4) ? num4 : 0);
					int num5 = ((args[2] is int num6) ? num6 : 0);
					object obj = args[4];
					bool flag = default(bool);
					int num7;
					if (obj is bool)
					{
						flag = (bool)obj;
						num7 = 1;
					}
					else
					{
						num7 = 0;
					}
					bool flag2 = (byte)((uint)num7 & (flag ? 1u : 0u)) != 0;
					PlayerAvatar val = SemiFunc.PlayerAvatarGetFromPhotonID(num);
					if ((Object)(object)val != (Object)null && IsLocal(val))
					{
						if (num5 > 0)
						{
							s.Add(val.steamID, num5);
						}
						if (flag2)
						{
							TeammatesKilled.Add(val.steamID, 1);
						}
					}
					PlayerAvatar val2 = SemiFunc.PlayerAvatarGetFromPhotonID(num3);
					if ((Object)(object)val2 != (Object)null && IsLocal(val2) && num5 > 0)
					{
						DamageFromTeammates.Add(val2.steamID, num5);
					}
				}
			});
		}

		private static void WireRunMirrors()
		{
			Trackers.Mirror(RunLevel, () => ((Object)(object)StatsManager.instance != (Object)null) ? StatsManager.instance.GetRunStatLevel() : 0);
			Trackers.Mirror(RunCurrency, () => ((Object)(object)StatsManager.instance != (Object)null) ? StatsManager.instance.GetRunStatCurrency() : 0, SampleWhen.Tick);
			Trackers.Mirror(RunTotalHaul, () => ((Object)(object)StatsManager.instance != (Object)null) ? StatsManager.instance.GetRunStatTotalHaul() : 0, SampleWhen.Tick);
		}

		internal static void SampleLocalMovement(float dt)
		{
			//IL_0047: Unknown result type (might be due to invalid IL or missing references)
			//IL_004c: Unknown result type (might be due to invalid IL or missing references)
			if (!_registered || dt <= 0f || !SemiFunc.RunIsLevel())
			{
				return;
			}
			PlayerController instance = PlayerController.instance;
			PlayerAvatar instance2 = PlayerAvatar.instance;
			if ((Object)(object)instance == (Object)null || (Object)(object)instance2 == (Object)null)
			{
				return;
			}
			string steamID = instance2.steamID;
			if (string.IsNullOrEmpty(steamID))
			{
				return;
			}
			Vector3 velocity = instance.Velocity;
			velocity.y = 0f;
			float num = ((Vector3)(ref velocity)).magnitude * dt;
			if (!(num <= 0.01f) && !(num >= 5f))
			{
				_distAccum += num;
				if (instance.sprinting)
				{
					_runAccum += num;
				}
				else
				{
					_walkAccum += num;
				}
				CommitMetres(DistanceTraveled, steamID, ref _distAccum);
				CommitMetres(DistanceSprinting, steamID, ref _runAccum);
				CommitMetres(DistanceWalking, steamID, ref _walkAccum);
			}
		}

		private static void CommitMetres(Stat stat, string id, ref float accum)
		{
			if (!(accum < 1f))
			{
				int num = (int)accum;
				accum -= num;
				stat.Add(id, num);
			}
		}

		private static Stat Career(string key, string name, string description, string category, StatFormat format = StatFormat.Count)
		{
			return Stats.Define(key, StatScope.PerPlayer, StatLifetime.Career).Describe(name, description, category, format);
		}

		private static bool IsLocal(PlayerAvatar p)
		{
			if ((Object)(object)p != (Object)null && (Object)(object)PlayerAvatar.instance != (Object)null)
			{
				return p.steamID == PlayerAvatar.instance.steamID;
			}
			return false;
		}

		private static string EnemyName(int enemyIndex)
		{
			if (enemyIndex < 0)
			{
				return "Environment";
			}
			Enemy val = SemiFunc.EnemyGetFromIndex(enemyIndex);
			string text = (((Object)(object)val != (Object)null && (Object)(object)val.EnemyParent != (Object)null) ? val.EnemyParent.enemyName : null);
			if (!string.IsNullOrEmpty(text))
			{
				return text;
			}
			return "Unknown";
		}

		internal static void OnLevelChanged()
		{
			if (!_registered || !SemiFunc.RunIsLevel())
			{
				return;
			}
			Level val = (((Object)(object)RunManager.instance != (Object)null) ? RunManager.instance.levelCurrent : null);
			if (!((Object)(object)val == (Object)null))
			{
				string text = ((!string.IsNullOrEmpty(val.NarrativeName)) ? val.NarrativeName : ((Object)val).name);
				if (!string.IsNullOrEmpty(text))
				{
					MapVisits.Add(text, 1);
				}
			}
		}
	}
	public sealed class Stat
	{
		public const string TeamKey = "_team";

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

		private readonly Dictionary<string, long> _firstUtc = new Dictionary<string, long>();

		private readonly Dictionary<string, long> _lastUtc = new Dictionary<string, long>();

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

		public string Key { get; }

		public StatScope Scope { get; }

		public StatLifetime Lifetime { get; }

		public string DisplayName { get; private set; }

		public string Description { get; private set; } = "";

		public string Category { get; private set; } = "";

		public StatFormat Format { get; private set; }

		public bool TracksDaily { get; private set; }

		public bool IsClientAuthoritative { get; private set; }

		public IReadOnlyDictionary<string, int> Entries => _values;

		internal IEnumerable<KeyValuePair<string, int>> Raw => _values;

		internal IEnumerable<KeyValuePair<string, Dictionary<int, int>>> RawDaily => _daily;

		public event Action<string, int>? Changed;

		internal Stat(string key, StatScope scope, StatLifetime lifetime)
		{
			Key = key;
			Scope = scope;
			Lifetime = lifetime;
			DisplayName = key;
		}

		public Stat Describe(string? displayName = null, string? description = null, string? category = null, StatFormat? format = null)
		{
			if (displayName != null)
			{
				DisplayName = displayName;
			}
			if (description != null)
			{
				Description = description;
			}
			if (category != null)
			{
				Category = category;
			}
			if (format.HasValue)
			{
				Format = format.Value;
			}
			return this;
		}

		public Stat TrackDaily()
		{
			if (Lifetime != StatLifetime.Career)
			{
				StatsCorePlugin.Logger.LogWarning((object)("[StatsCore] TrackDaily() on non-career '" + Key + "' ignored - daily history needs persistence (use StatLifetime.Career)"));
				return this;
			}
			if (!TracksDaily)
			{
				TracksDaily = true;
				Persistence.HydrateDaily(this);
			}
			return this;
		}

		public Stat ClientAuthoritative()
		{
			if (Lifetime == StatLifetime.Career)
			{
				StatsCorePlugin.Logger.LogWarning((object)("[StatsCore] ClientAuthoritative() on career '" + Key + "' ignored - career stats are already local; use Stats.RequestFromClients to pull them"));
			}
			else
			{
				IsClientAuthoritative = true;
			}
			return this;
		}

		public int Get(string steamID)
		{
			if (!_values.TryGetValue(Norm(steamID), out var value))
			{
				return 0;
			}
			return value;
		}

		public int GetTeam()
		{
			return Get("_team");
		}

		public int Sum()
		{
			int num = 0;
			foreach (KeyValuePair<string, int> value in _values)
			{
				if (value.Key != "_team")
				{
					num += value.Value;
				}
			}
			return num;
		}

		public List<KeyValuePair<string, int>> Leaderboard()
		{
			return (from kv in _values
				where kv.Key != "_team"
				orderby kv.Value descending
				select kv).ToList();
		}

		public long FirstUtc(string steamID)
		{
			if (!_firstUtc.TryGetValue(Norm(steamID), out var value))
			{
				return 0L;
			}
			return value;
		}

		public long LastUtc(string steamID)
		{
			if (!_lastUtc.TryGetValue(Norm(steamID), out var value))
			{
				return 0L;
			}
			return value;
		}

		public DateTime? FirstRecorded(string steamID)
		{
			long num = FirstUtc(steamID);
			if (num != 0L)
			{
				return DateTimeOffset.FromUnixTimeSeconds(num).UtcDateTime;
			}
			return null;
		}

		public DateTime? LastUpdated(string steamID)
		{
			long num = LastUtc(steamID);
			if (num != 0L)
			{
				return DateTimeOffset.FromUnixTimeSeconds(num).UtcDateTime;
			}
			return null;
		}

		public int GetToday(string steamID)
		{
			return Bucket(Norm(steamID), DayMath.Today());
		}

		public int GetOnDay(string steamID, DateTime day)
		{
			return Bucket(Norm(steamID), DayMath.IndexOf(day));
		}

		public int GetRange(string steamID, DateTime from, DateTime to)
		{
			if (!_daily.TryGetValue(Norm(steamID), out Dictionary<int, int> value))
			{
				return 0;
			}
			int num = DayMath.IndexOf(from);
			int num2 = DayMath.IndexOf(to);
			if (num > num2)
			{
				int num3 = num2;
				num2 = num;
				num = num3;
			}
			int num4 = 0;
			foreach (KeyValuePair<int, int> item in value)
			{
				if (item.Key >= num && item.Key <= num2)
				{
					num4 += item.Value;
				}
			}
			return num4;
		}

		public int GetLastDays(string steamID, int days)
		{
			if (days < 1 || !_daily.TryGetValue(Norm(steamID), out Dictionary<int, int> value))
			{
				return 0;
			}
			int num = DayMath.Today();
			int num2 = num - (days - 1);
			int num3 = 0;
			foreach (KeyValuePair<int, int> item in value)
			{
				if (item.Key >= num2 && item.Key <= num)
				{
					num3 += item.Value;
				}
			}
			return num3;
		}

		public List<KeyValuePair<DateTime, int>> DailyHistory(string steamID)
		{
			List<KeyValuePair<DateTime, int>> list = new List<KeyValuePair<DateTime, int>>();
			if (_daily.TryGetValue(Norm(steamID), out Dictionary<int, int> value))
			{
				foreach (KeyValuePair<int, int> item in value.OrderBy((KeyValuePair<int, int> k) => k.Key))
				{
					list.Add(new KeyValuePair<DateTime, int>(DayMath.DateOf(item.Key), item.Value));
				}
			}
			return list;
		}

		private int Bucket(string id, int dayIndex)
		{
			if (!_daily.TryGetValue(id, out Dictionary<int, int> value) || !value.TryGetValue(dayIndex, out var value2))
			{
				return 0;
			}
			return value2;
		}

		public void Add(string steamID, int delta)
		{
			Write(Norm(steamID), Get(steamID) + delta);
		}

		public void Set(string steamID, int value)
		{
			Write(Norm(steamID), value);
		}

		public void AddTeam(int delta)
		{
			Write("_team", GetTeam() + delta);
		}

		public void SetTeam(int value)
		{
			Write("_team", value);
		}

		private void Write(string steamID, int value)
		{
			if (Lifetime == StatLifetime.Career)
			{
				ApplyLocal(steamID, value);
			}
			else if (IsClientAuthoritative)
			{
				if (!StatsNet.OwnsSlot(steamID))
				{
					StatsCorePlugin.Logger.LogDebug((object)("[StatsCore] client-authoritative write to '" + Key + "' for a slot you don't own ignored (write your own steamID only)"));
					return;
				}
				ApplyLocal(steamID, value);
				StatsNet.OnOwnerWrote(this, steamID, value);
			}
			else if (!StatsNet.CanWriteSynced)
			{
				StatsCorePlugin.Logger.LogDebug((object)("[StatsCore] write to synced '" + Key + "' ignored off-host (gate on Stats.IsHost)"));
			}
			else
			{
				ApplyLocal(steamID, value);
				StatsNet.OnHostWrote(this, steamID, value);
			}
		}

		internal void ApplyFromNetwork(string steamID, int value)
		{
			if (Lifetime == StatLifetime.Career)
			{
				StatsCorePlugin.Logger.LogWarning((object)("[StatsCore] refused a networked write to career stat '" + Key + "'"));
			}
			else
			{
				ApplyLocal(Norm(steamID), value);
			}
		}

		public void OnReach(string steamID, int threshold, Action callback)
		{
			string id = Norm(steamID);
			if (Get(id) >= threshold)
			{
				callback();
			}
			else
			{
				Changed += Handler;
			}
			void Handler(string changedId, int newValue)
			{
				if (!(changedId != id) && newValue >= threshold)
				{
					Changed -= Handler;
					callback();
				}
			}
		}

		internal void ApplyLocal(string steamID, int value)
		{
			int value2;
			int num = (_values.TryGetValue(steamID, out value2) ? value2 : 0);
			_values[steamID] = value;
			long value3 = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
			if (!_firstUtc.ContainsKey(steamID))
			{
				_firstUtc[steamID] = value3;
			}
			_lastUtc[steamID] = value3;
			if (TracksDaily && Lifetime == StatLifetime.Career && value != num)
			{
				int key = DayMath.Today();
				if (!_daily.TryGetValue(steamID, out Dictionary<int, int> value4))
				{
					value4 = new Dictionary<int, int>();
					_daily[steamID] = value4;
				}
				value4.TryGetValue(key, out var value5);
				value4[key] = value5 + (value - num);
			}
			this.Changed?.Invoke(steamID, value);
			Stats.RaiseAnyChanged(this, steamID, value);
			if (Lifetime == StatLifetime.Career)
			{
				Persistence.MarkDirty();
			}
		}

		internal void Clear()
		{
			_values.Clear();
			_firstUtc.Clear();
			_lastUtc.Clear();
			_daily.Clear();
		}

		internal void HydrateCareer(Dictionary<string, CareerEntry> saved)
		{
			foreach (KeyValuePair<string, CareerEntry> item in saved)
			{
				_values[item.Key] = item.Value.Value;
				if (item.Value.FirstUtc != 0L)
				{
					_firstUtc[item.Key] = item.Value.FirstUtc;
				}
				if (item.Value.LastUtc != 0L)
				{
					_lastUtc[item.Key] = item.Value.LastUtc;
				}
			}
		}

		internal void HydrateDailyEntry(string steamID, Dictionary<int, int> byDay)
		{
			_daily[steamID] = byDay;
		}

		private static string Norm(string steamID)
		{
			if (!string.IsNullOrEmpty(steamID))
			{
				return CareerFile.SanitizeSlot(steamID);
			}
			return "_team";
		}
	}
	public static class Stats
	{
		private static readonly Dictionary<string, Stat> _stats = new Dictionary<string, Stat>();

		private static readonly char[] ReservedKeyChars = new char[9] { '|', ',', ':', ';', '=', ' ', '\t', '\n', '\r' };

		public static bool IsHost => StatsNet.CanWriteSynced;

		public static IEnumerable<Stat> All => _stats.Values;

		public static event Action<Stat, string, int>? AnyChanged;

		public static Stat Define(string key, StatScope scope, StatLifetime lifetime)
		{
			if (string.IsNullOrEmpty(key) || key.IndexOfAny(ReservedKeyChars) >= 0)
			{
				throw new ArgumentException("stat key '" + key + "' is empty or contains a reserved character (| , : ; = or whitespace)", "key");
			}
			if (_stats.TryGetValue(key, out Stat value))
			{
				StatsCorePlugin.Logger.LogDebug((object)("[StatsCore] Define('" + key + "') returned the existing stat (scope/lifetime of the first definition win)"));
				return value;
			}
			Stat stat = new Stat(key, scope, lifetime);
			_stats[key] = stat;
			if (lifetime == StatLifetime.Career)
			{
				Persistence.Hydrate(stat);
			}
			return stat;
		}

		public static Stat? Get(string key)
		{
			if (!_stats.TryGetValue(key, out Stat value))
			{
				return null;
			}
			return value;
		}

		public static void RequestFromClients(string key, Action<IReadOnlyDictionary<string, int>> onComplete, float timeoutSeconds = 3f)
		{
			StatsNet.RequestFromClients(key, onComplete, timeoutSeconds);
		}

		internal static void RaiseAnyChanged(Stat stat, string steamID, int value)
		{
			Stats.AnyChanged?.Invoke(stat, steamID, value);
		}

		internal static void ResetLifetime(StatLifetime lifetime)
		{
			foreach (Stat value in _stats.Values)
			{
				if (value.Lifetime == lifetime)
				{
					value.Clear();
				}
			}
		}
	}
	public enum StatScope
	{
		PerPlayer,
		Team
	}
	public enum StatLifetime
	{
		Level,
		Run,
		Session,
		Career
	}
	internal struct CareerEntry
	{
		public int Value;

		public long FirstUtc;

		public long LastUtc;
	}
	public enum StatFormat
	{
		Count,
		Time,
		Distance,
		Money,
		Percent,
		Raw
	}
	public delegate void StatHit(Stat stat, object? instance, object[] args);
	public enum SampleWhen
	{
		LevelChange,
		RunReset,
		Tick
	}
	public static class Trackers
	{
		public sealed class Hooked
		{
			internal readonly StatHit OnHit;

			public Stat Stat { get; }

			public MethodBase? Target { get; }

			public bool Enabled { get; private set; }

			internal Hooked(Stat stat, MethodBase? target, StatHit onHit)
			{
				Stat = stat;
				Target = target;
				OnHit = onHit;
				Enabled = target != null;
			}

			public void Disable()
			{
				Enabled = false;
			}

			internal void Fire(object? instance, object[] args)
			{
				if (!Enabled)
				{
					return;
				}
				try
				{
					OnHit(Stat, instance, args);
				}
				catch (Exception ex)
				{
					StatsCorePlugin.Logger.LogWarning((object)("[StatsCore] hook for '" + Stat.Key + "' threw: " + ex.Message));
				}
			}
		}

		private sealed class MirrorEntry
		{
			public Stat Stat;

			public SampleWhen When;

			public Func<int>? Global;

			public Func<PlayerAvatar, int>? PerPlayer;
		}

		private static readonly Harmony _harmony = new Harmony("Vippy.StatsCore.Trackers");

		private static readonly Dictionary<MethodBase, List<Hooked>> _byMethod = new Dictionary<MethodBase, List<Hooked>>();

		private static readonly List<MirrorEntry> _mirrors = new List<MirrorEntry>();

		public static Hooked Hook(Stat stat, Type gameType, string methodName, StatHit onHit, Type[]? argTypes = null)
		{
			//IL_00a5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b2: Expected O, but got Unknown
			MethodBase methodBase = AccessTools.Method(gameType, methodName, argTypes, (Type[])null);
			Hooked hooked = new Hooked(stat, methodBase, onHit);
			if (methodBase == null)
			{
				StatsCorePlugin.Logger.LogWarning((object)("[StatsCore] " + gameType.Name + "." + methodName + " not found - hook for '" + stat.Key + "' disabled (game update?)."));
				return hooked;
			}
			if (!_byMethod.TryGetValue(methodBase, out List<Hooked> value))
			{
				value = new List<Hooked>();
				_byMethod[methodBase] = value;
				try
				{
					_harmony.Patch(methodBase, (HarmonyMethod)null, new HarmonyMethod(typeof(Trackers), "HookPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				}
				catch (Exception ex)
				{
					StatsCorePlugin.Logger.LogWarning((object)("[StatsCore] couldn't patch " + gameType.Name + "." + methodName + ": " + ex.Message));
					hooked.Disable();
					_byMethod.Remove(methodBase);
					return hooked;
				}
			}
			value.Add(hooked);
			return hooked;
		}

		private static void HookPostfix(object __instance, object[] __args, MethodBase __originalMethod)
		{
			if (_byMethod.TryGetValue(__originalMethod, out List<Hooked> value))
			{
				for (int i = 0; i < value.Count; i++)
				{
					value[i].Fire(__instance, __args);
				}
			}
		}

		public static void Mirror(Stat stat, Func<int> read, SampleWhen when = SampleWhen.LevelChange)
		{
			_mirrors.Add(new MirrorEntry
			{
				Stat = stat,
				Global = read,
				When = when
			});
		}

		public static void MirrorPlayers(Stat stat, Func<PlayerAvatar, int> read, SampleWhen when = SampleWhen.LevelChange)
		{
			_mirrors.Add(new MirrorEntry
			{
				Stat = stat,
				PerPlayer = read,
				When = when
			});
		}

		internal static void Sample(SampleWhen when)
		{
			bool isHost = Stats.IsHost;
			for (int i = 0; i < _mirrors.Count; i++)
			{
				MirrorEntry mirrorEntry = _mirrors[i];
				if (mirrorEntry.When != when || (!isHost && mirrorEntry.Stat.Lifetime != StatLifetime.Career && !mirrorEntry.Stat.IsClientAuthoritative))
				{
					continue;
				}
				try
				{
					if (mirrorEntry.Global != null)
					{
						mirrorEntry.Stat.SetTeam(mirrorEntry.Global());
					}
					else
					{
						if (mirrorEntry.PerPlayer == null)
						{
							continue;
						}
						foreach (PlayerAvatar item in SemiFunc.PlayerGetAll())
						{
							if ((Object)(object)item != (Object)null)
							{
								mirrorEntry.Stat.Set(SemiFunc.PlayerGetSteamID(item), mirrorEntry.PerPlayer(item));
							}
						}
						continue;
					}
				}
				catch (Exception ex)
				{
					StatsCorePlugin.Logger.LogWarning((object)("[StatsCore] mirror for '" + mirrorEntry.Stat.Key + "' threw: " + ex.Message));
				}
			}
		}

		internal static bool HasTickMirrors()
		{
			for (int i = 0; i < _mirrors.Count; i++)
			{
				if (_mirrors[i].When == SampleWhen.Tick)
				{
					return true;
				}
			}
			return false;
		}
	}
	internal static class BuildInfo
	{
		public const string Version = "1.0.1";
	}
	[BepInPlugin("Vippy.StatsCore", "StatsCore", "1.0.1")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	public class StatsCorePlugin : BaseUnityPlugin
	{
		internal static StatsCorePlugin Instance { get; private set; }

		internal static ManualLogSource Logger => Instance.BaseLogger;

		private ManualLogSource BaseLogger => ((BaseUnityPlugin)this).Logger;

		private void Awake()
		{
			//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: Expected O, but got Unknown
			Instance = this;
			((Component)this).gameObject.transform.parent = null;
			((Object)((Component)this).gameObject).hideFlags = (HideFlags)61;
			GameObject val = new GameObject("StatsCore_Driver");
			val.AddComponent<StatsDriver>();
			Object.DontDestroyOnLoad((Object)val);
			GameStats.Register();
			StatsNet.Install();
			if (Chainloader.PluginInfos.ContainsKey("nickklmao.menulib"))
			{
				StatsScreenEntry.Install(((BaseUnityPlugin)this).Config);
			}
			Logger.LogInfo((object)"StatsCore v1.0.1 loaded");
		}
	}
	internal static class CareerFile
	{
		internal const int Version = 1;

		internal const string TotalsHeader = "# statscore career v1";

		internal const string DailyHeader = "# statscore career-daily v1";

		private static readonly char[] Reserved = new char[8] { '|', '=', ';', ',', ':', '\n', '\r', '\t' };

		private const string MergedPrefix = "# merged ";

		internal static string SanitizeSlot(string slot)
		{
			if (string.IsNullOrEmpty(slot) || slot.IndexOfAny(Reserved) < 0)
			{
				return slot;
			}
			StringBuilder stringBuilder = new StringBuilder(slot.Length);
			foreach (char c in slot)
			{
				stringBuilder.Append((Array.IndexOf(Reserved, c) >= 0) ? '_' : c);
			}
			return stringBuilder.ToString();
		}

		internal static Dictionary<string, Dictionary<string, CareerEntry>> ParseTotals(IEnumerable<string> lines)
		{
			Dictionary<string, Dictionary<string, CareerEntry>> dictionary = new Dictionary<string, Dictionary<string, CareerEntry>>();
			foreach (string line in lines)
			{
				if (IsSkippable(line))
				{
					continue;
				}
				int num = line.IndexOf('|');
				int num2 = line.LastIndexOf('=');
				if (num <= 0 || num2 <= num + 1)
				{
					continue;
				}
				string key = line.Substring(0, num);
				string key2 = line.Substring(num + 1, num2 - num - 1);
				string[] array = line.Substring(num2 + 1).Split(';');
				CareerEntry value = default(CareerEntry);
				if (TryInt(array[0], out value.Value))
				{
					if (array.Length > 1)
					{
						TryLong(array[1], out value.FirstUtc);
					}
					if (array.Length > 2)
					{
						TryLong(array[2], out value.LastUtc);
					}
					if (!dictionary.TryGetValue(key, out var value2))
					{
						value2 = (dictionary[key] = new Dictionary<string, CareerEntry>());
					}
					value2[key2] = value;
				}
			}
			return dictionary;
		}

		internal static string SerializeTotals(IReadOnlyDictionary<string, Dictionary<string, CareerEntry>> live, IReadOnlyDictionary<string, Dictionary<string, CareerEntry>> saved, IEnumerable<string>? merged = null)
		{
			StringBuilder stringBuilder = new StringBuilder("# statscore career v1").Append('\n');
			if (merged != null)
			{
				foreach (string item in merged)
				{
					stringBuilder.Append("# merged ").Append(item).Append('\n');
				}
			}
			foreach (KeyValuePair<string, Dictionary<string, CareerEntry>> item2 in live)
			{
				foreach (KeyValuePair<string, CareerEntry> item3 in item2.Value)
				{
					AppendTotal(stringBuilder, item2.Key, item3.Key, item3.Value);
				}
			}
			foreach (KeyValuePair<string, Dictionary<string, CareerEntry>> item4 in saved)
			{
				if (live.ContainsKey(item4.Key))
				{
					continue;
				}
				foreach (KeyValuePair<string, CareerEntry> item5 in item4.Value)
				{
					AppendTotal(stringBuilder, item4.Key, item5.Key, item5.Value);
				}
			}
			return stringBuilder.ToString();
		}

		internal static HashSet<string> ParseMerged(IEnumerable<string> lines)
		{
			HashSet<string> hashSet = new HashSet<string>();
			foreach (string line in lines)
			{
				if (line.StartsWith("# merged ", StringComparison.Ordinal) && line.Length > "# merged ".Length)
				{
					hashSet.Add(line.Substring("# merged ".Length).Trim());
				}
			}
			return hashSet;
		}

		internal static void MergeTotals(Dictionary<string, Dictionary<string, CareerEntry>> target, IReadOnlyDictionary<string, Dictionary<string, CareerEntry>> source)
		{
			foreach (KeyValuePair<string, Dictionary<string, CareerEntry>> item in source)
			{
				if (!target.TryGetValue(item.Key, out Dictionary<string, CareerEntry> value))
				{
					value = new Dictionary<string, CareerEntry>();
					target[item.Key] = value;
				}
				foreach (KeyValuePair<string, CareerEntry> item2 in item.Value)
				{
					if (!value.TryGetValue(item2.Key, out var value2))
					{
						value[item2.Key] = item2.Value;
						continue;
					}
					long val = (long)value2.Value + (long)item2.Value.Value;
					value2.Value = (int)Math.Max(-2147483648L, Math.Min(2147483647L, val));
					value2.FirstUtc = Earlier(value2.FirstUtc, item2.Value.FirstUtc);
					value2.LastUtc = Math.Max(value2.LastUtc, item2.Value.LastUtc);
					value[item2.Key] = value2;
				}
			}
		}

		internal static void MergeDaily(Dictionary<string, Dictionary<string, Dictionary<int, int>>> target, IReadOnlyDictionary<string, Dictionary<string, Dictionary<int, int>>> source)
		{
			foreach (KeyValuePair<string, Dictionary<string, Dictionary<int, int>>> item in source)
			{
				if (!target.TryGetValue(item.Key, out Dictionary<string, Dictionary<int, int>> value))
				{
					value = new Dictionary<string, Dictionary<int, int>>();
					target[item.Key] = value;
				}
				foreach (KeyValuePair<string, Dictionary<int, int>> item2 in item.Value)
				{
					if (!value.TryGetValue(item2.Key, out var value2))
					{
						value[item2.Key] = new Dictionary<int, int>(item2.Value);
						continue;
					}
					foreach (KeyValuePair<int, int> item3 in item2.Value)
					{
						value2[item3.Key] = (value2.TryGetValue(item3.Key, out var value3) ? (value3 + item3.Value) : item3.Value);
					}
				}
			}
		}

		private static long Earlier(long a, long b)
		{
			if (a != 0L)
			{
				if (b != 0L)
				{
					return Math.Min(a, b);
				}
				return a;
			}
			return b;
		}

		private static void AppendTotal(StringBuilder sb, string key, string slot, CareerEntry entry)
		{
			sb.Append(key).Append('|').Append(SanitizeSlot(slot))
				.Append('=')
				.Append(entry.Value.ToString(CultureInfo.InvariantCulture))
				.Append(';')
				.Append(entry.FirstUtc.ToString(CultureInfo.InvariantCulture))
				.Append(';')
				.Append(entry.LastUtc.ToString(CultureInfo.InvariantCulture))
				.Append('\n');
		}

		internal static Dictionary<string, Dictionary<string, Dictionary<int, int>>> ParseDaily(IEnumerable<string> lines)
		{
			Dictionary<string, Dictionary<string, Dictionary<int, int>>> dictionary = new Dictionary<string, Dictionary<string, Dictionary<int, int>>>();
			foreach (string line in lines)
			{
				if (IsSkippable(line))
				{
					continue;
				}
				int num = line.IndexOf('|');
				int num2 = ((num < 0) ? (-1) : line.IndexOf('|', num + 1));
				if (num <= 0 || num2 <= num)
				{
					continue;
				}
				string key = line.Substring(0, num);
				string key2 = line.Substring(num + 1, num2 - num - 1);
				Dictionary<int, int> dictionary2 = new Dictionary<int, int>();
				string[] array = line.Substring(num2 + 1).Split(';');
				foreach (string text in array)
				{
					int num3 = text.IndexOf('=');
					if (num3 > 0 && TryInt(text.Substring(0, num3), out var value) && TryInt(text.Substring(num3 + 1), out var value2))
					{
						dictionary2[value] = value2;
					}
				}
				if (dictionary2.Count != 0)
				{
					if (!dictionary.TryGetValue(key, out var value3))
					{
						value3 = (dictionary[key] = new Dictionary<string, Dictionary<int, int>>());
					}
					value3[key2] = dictionary2;
				}
			}
			return dictionary;
		}

		internal static string SerializeDaily(IReadOnlyDictionary<string, Dictionary<string, Dictionary<int, int>>> live, IReadOnlyDictionary<string, Dictionary<string, Dictionary<int, int>>> saved)
		{
			StringBuilder stringBuilder = new StringBuilder("# statscore career-daily v1").Append('\n');
			foreach (KeyValuePair<string, Dictionary<string, Dictionary<int, int>>> item in live)
			{
				foreach (KeyValuePair<string, Dictionary<int, int>> item2 in item.Value)
				{
					AppendDaily(stringBuilder, item.Key, item2.Key, item2.Value);
				}
			}
			foreach (KeyValuePair<string, Dictionary<string, Dictionary<int, int>>> item3 in saved)
			{
				if (live.ContainsKey(item3.Key))
				{
					continue;
				}
				foreach (KeyValuePair<string, Dictionary<int, int>> item4 in item3.Value)
				{
					AppendDaily(stringBuilder, item3.Key, item4.Key, item4.Value);
				}
			}
			return stringBuilder.ToString();
		}

		private static void AppendDaily(StringBuilder sb, string key, string slot, IEnumerable<KeyValuePair<int, int>> byDay)
		{
			StringBuilder stringBuilder = new StringBuilder();
			bool flag = false;
			foreach (KeyValuePair<int, int> item in byDay)
			{
				if (flag)
				{
					stringBuilder.Append(';');
				}
				stringBuilder.Append(item.Key.ToString(CultureInfo.InvariantCulture)).Append('=').Append(item.Value.ToString(CultureInfo.InvariantCulture));
				flag = true;
			}
			if (flag)
			{
				sb.Append(key).Append('|').Append(SanitizeSlot(slot))
					.Append('|')
					.Append(stringBuilder)
					.Append('\n');
			}
		}

		private static bool IsSkippable(string line)
		{
			if (!string.IsNullOrEmpty(line))
			{
				return line[0] == '#';
			}
			return true;
		}

		private static bool TryInt(string s, out int value)
		{
			return int.TryParse(s, NumberStyles.Integer, CultureInfo.InvariantCulture, out value);
		}

		private static bool TryLong(string s, out long value)
		{
			return long.TryParse(s, NumberStyles.Integer, CultureInfo.InvariantCulture, out value);
		}
	}
	internal static class DayMath
	{
		internal static readonly DateTime Epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Unspecified);

		internal static int IndexOf(DateTime when)
		{
			if (when.Kind == DateTimeKind.Utc)
			{
				when = when.ToLocalTime();
			}
			return (when.Date - Epoch).Days;
		}

		internal static int Today()
		{
			return IndexOf(DateTime.Now);
		}

		internal static DateTime DateOf(int dayIndex)
		{
			return Epoch.AddDays(dayIndex);
		}
	}
	internal static class Persistence
	{
		private static bool _loaded;

		private static readonly Dictionary<string, Dictionary<string, CareerEntry>> _saved = new Dictionary<string, Dictionary<string, CareerEntry>>();

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

		private static readonly HashSet<string> _merged = new HashSet<string>();

		private static readonly List<string> _retire = new List<string>();

		internal static bool Dirty { get; private set; }

		private static string Dir => Path.Combine(Application.persistentDataPath, "StatsCore");

		private static string LegacyDir => Path.Combine(Paths.ConfigPath, "StatsCore");

		private static string FilePath => Path.Combine(Dir, "career.txt");

		private static string DailyFilePath => Path.Combine(Dir, "career-daily.txt");

		internal static void MarkDirty()
		{
			Dirty = true;
		}

		private static void EnsureLoaded()
		{
			if (_loaded)
			{
				return;
			}
			_loaded = true;
			Load(FilePath, delegate(string[] lines)
			{
				foreach (KeyValuePair<string, Dictionary<string, CareerEntry>> item in CareerFile.ParseTotals(lines))
				{
					_saved[item.Key] = item.Value;
				}
				_merged.UnionWith(CareerFile.ParseMerged(lines));
			}, "career file");
			Load(DailyFilePath, delegate(string[] lines)
			{
				foreach (KeyValuePair<string, Dictionary<string, Dictionary<int, int>>> item2 in CareerFile.ParseDaily(lines))
				{
					_savedDaily[item2.Key] = item2.Value;
				}
			}, "career daily file");
			MergeLegacy();
		}

		private static void MergeLegacy()
		{
			string text = Path.Combine(LegacyDir, "career.txt");
			string text2 = Path.Combine(LegacyDir, "career-daily.txt");
			bool flag = File.Exists(text);
			bool flag2 = File.Exists(text2);
			if (!flag && !flag2)
			{
				return;
			}
			string item = Token(text, text2);
			if (!_merged.Contains(item))
			{
				try
				{
					string[] lines = (flag ? File.ReadAllLines(text) : Array.Empty<string>());
					string[] lines2 = (flag2 ? File.ReadAllLines(text2) : Array.Empty<string>());
					CareerFile.MergeTotals(_saved, CareerFile.ParseTotals(lines));
					CareerFile.MergeDaily(_savedDaily, CareerFile.ParseDaily(lines2));
				}
				catch (Exception ex)
				{
					StatsCorePlugin.Logger.LogWarning((object)("[StatsCore] couldn't read this profile's older career file: " + ex.Message));
					return;
				}
				_merged.Add(item);
				MarkDirty();
				StatsCorePlugin.Logger.LogInfo((object)"[StatsCore] folded this profile's career history into the shared one");
			}
			if (flag)
			{
				_retire.Add(text);
			}
			if (flag2)
			{
				_retire.Add(text2);
			}
		}

		private static string Token(string totalsPath, string dailyPath)
		{
			string s = Path.GetFullPath(LegacyDir) + "|" + Describe(totalsPath) + "|" + Describe(dailyPath);
			using SHA256 sHA = SHA256.Create();
			return BitConverter.ToString(sHA.ComputeHash(Encoding.UTF8.GetBytes(s))).Replace("-", "").Substring(0, 16)
				.ToLowerInvariant();
		}

		private static string Describe(string path)
		{
			if (!File.Exists(path))
			{
				return "none";
			}
			FileInfo fileInfo = new FileInfo(path);
			return fileInfo.Length + ":" + fileInfo.LastWriteTimeUtc.Ticks;
		}

		private static void RetireLegacy()
		{
			foreach (string item in _retire)
			{
				try
				{
					string text = item + ".migrated";
					if (File.Exists(text))
					{
						text = item + "." + DateTime.UtcNow.ToString("yyyyMMddHHmmss") + ".migrated";
					}
					File.Move(item, text);
				}
				catch (Exception ex)
				{
					StatsCorePlugin.Logger.LogWarning((object)("[StatsCore] couldn't set aside " + Path.GetFileName(item) + ": " + ex.Message));
				}
			}
			_retire.Clear();
		}

		private static void Load(string path, Action<string[]> parse, string what)
		{
			string[] array = new string[2]
			{
				path,
				path + ".bak"
			};
			foreach (string text in array)
			{
				if (!File.Exists(text))
				{
					continue;
				}
				try
				{
					parse(File.ReadAllLines(text));
					if (text != path)
					{
						StatsCorePlugin.Logger.LogWarning((object)("[StatsCore] " + what + " was missing or unreadable - recovered from " + Path.GetFileName(text)));
					}
					break;
				}
				catch (Exception ex)
				{
					StatsCorePlugin.Logger.LogWarning((object)("[StatsCore] couldn't read " + what + " (" + Path.GetFileName(text) + "): " + ex.Message));
				}
			}
		}

		internal static void Hydrate(Stat stat)
		{
			EnsureLoaded();
			if (_saved.TryGetValue(stat.Key, out Dictionary<string, CareerEntry> value))
			{
				stat.HydrateCareer(value);
			}
		}

		internal static void HydrateDaily(Stat stat)
		{
			EnsureLoaded();
			if (!_savedDaily.TryGetValue(stat.Key, out Dictionary<string, Dictionary<int, int>> value))
			{
				return;
			}
			foreach (KeyValuePair<string, Dictionary<int, int>> item in value)
			{
				stat.HydrateDailyEntry(item.Key, new Dictionary<int, int>(item.Value));
			}
		}

		internal static void Save()
		{
			if (!Dirty)
			{
				return;
			}
			EnsureLoaded();
			try
			{
				Directory.CreateDirectory(Dir);
				Dictionary<string, Dictionary<string, CareerEntry>> dictionary = new Dictionary<string, Dictionary<string, CareerEntry>>();
				Dictionary<string, Dictionary<string, Dictionary<int, int>>> dictionary2 = new Dictionary<string, Dictionary<string, Dictionary<int, int>>>();
				foreach (Stat item in Stats.All)
				{
					if (item.Lifetime != StatLifetime.Career)
					{
						continue;
					}
					Dictionary<string, CareerEntry> dictionary3 = new Dictionary<string, CareerEntry>();
					foreach (KeyValuePair<string, int> item2 in item.Raw)
					{
						dictionary3[item2.Key] = new CareerEntry
						{
							Value = item2.Value,
							FirstUtc = item.FirstUtc(item2.Key),
							LastUtc = item.LastUtc(item2.Key)
						};
					}
					dictionary[item.Key] = dictionary3;
					if (!item.TracksDaily)
					{
						continue;
					}
					Dictionary<string, Dictionary<int, int>> dictionary4 = new Dictionary<string, Dictionary<int, int>>();
					foreach (KeyValuePair<string, Dictionary<int, int>> item3 in item.RawDaily)
					{
						dictionary4[item3.Key] = item3.Value;
					}
					dictionary2[item.Key] = dictionary4;
				}
				WriteAtomic(FilePath, CareerFile.SerializeTotals(dictionary, _saved, _merged));
				WriteAtomic(DailyFilePath, CareerFile.SerializeDaily(dictionary2, _savedDaily));
				Dirty = false;
				RetireLegacy();
			}
			catch (Exception ex)
			{
				StatsCorePlugin.Logger.LogWarning((object)("[StatsCore] couldn't write career file: " + ex.Message));
			}
		}

		private static void WriteAtomic(string path, string content)
		{
			string text = path + ".tmp";
			File.WriteAllText(text, content);
			if (File.Exists(path))
			{
				File.Replace(text, path, path + ".bak");
			}
			else
			{
				File.Move(text, path);
			}
		}
	}
	internal class StatsDriver : MonoBehaviour
	{
		private object? _lastLevel;

		private bool _wasLobbyMenu;

		private float _saveTimer;

		private float _tickTimer;

		private void Update()
		{
			FlushTick();
			RunManager instance = RunManager.instance;
			if ((Object)(object)instance == (Object)null)
			{
				return;
			}
			TickSamples();
			GameStats.SampleLocalMovement(Time.deltaTime);
			object levelCurrent = instance.levelCurrent;
			if (levelCurrent != _lastLevel)
			{
				_lastLevel = levelCurrent;
				Stats.ResetLifetime(StatLifetime.Level);
				bool flag = SemiFunc.RunIsLobbyMenu();
				if (flag && !_wasLobbyMenu)
				{
					Stats.ResetLifetime(StatLifetime.Run);
					Trackers.Sample(SampleWhen.RunReset);
				}
				_wasLobbyMenu = flag;
				Trackers.Sample(SampleWhen.LevelChange);
				GameStats.OnLevelChanged();
			}
		}

		private void TickSamples()
		{
			if (Trackers.HasTickMirrors())
			{
				_tickTimer += Time.unscaledDeltaTime;
				if (!(_tickTimer < 3f))
				{
					_tickTimer = 0f;
					Trackers.Sample(SampleWhen.Tick);
				}
			}
		}

		private void FlushTick()
		{
			if (Persistence.Dirty)
			{
				_saveTimer += Time.unscaledDeltaTime;
				if (!(_saveTimer < 5f))
				{
					_saveTimer = 0f;
					Persistence.Save();
				}
			}
		}

		private void OnApplicationQuit()
		{
			Persistence.Save();
		}
	}
	internal sealed class StatsNet : MonoBehaviourPunCallbacks
	{
		private sealed class PullRequest
		{
			public Dictionary<string, int> Results = new Dictionary<string, int>();

			public HashSet<int> Answered = new HashSet<int>();

			public int Pending;

			public bool Completed;

			public Action<IReadOnlyDictionary<string, int>> Done;
		}

		private static StatsNet? _instance;

		private static readonly Harmony _harmony = new Harmony("Vippy.StatsCore.Net");

		private const int ChunkChars = 32000;

		private static int _nextRequestId;

		private readonly Dictionary<int, PullRequest> _pulls = new Dictionary<int, PullRequest>();

		internal static bool CanWriteSynced
		{
			get
			{
				if (PhotonNetwork.InRoom && !PhotonNetwork.OfflineMode)
				{
					return PhotonNetwork.IsMasterClient;
				}
				return true;
			}
		}

		internal static void Install()
		{
			//IL_0047: Unknown result type (might be due to invalid IL or missing references)
			//IL_0054: Expected O, but got Unknown
			MethodInfo methodInfo = AccessTools.Method(typeof(PunManager), "Awake", (Type[])null, (Type[])null);
			if (methodInfo == null)
			{
				StatsCorePlugin.Logger.LogWarning((object)"[StatsNet] PunManager.Awake not found - sync disabled, stats run local-only");
				return;
			}
			_harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(typeof(StatsNet), "AttachPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			if ((Object)(object)PunManager.instance != (Object)null)
			{
				Attach(PunManager.instance);
			}
		}

		private static void AttachPostfix(PunManager __instance)
		{
			Attach(__instance);
		}

		private static void Attach(PunManager manager)
		{
			if ((Object)(object)((Component)manager).GetComponent<StatsNet>() == (Object)null)
			{
				((Component)manager).gameObject.AddComponent<StatsNet>();
			}
		}

		private void Awake()
		{
			_instance = this;
		}

		internal static void OnHostWrote(Stat stat, string steamID, int value)
		{
			if (!((Object)(object)_instance == (Object)null) && PhotonNetwork.InRoom && !PhotonNetwork.OfflineMode && PhotonNetwork.IsMasterClient)
			{
				((MonoBehaviourPun)_instance).photonView.RPC("StatsCoreSetRPC", (RpcTarget)1, new object[3] { stat.Key, steamID, value });
			}
		}

		[PunRPC]
		private void StatsCoreSetRPC(string key, string steamID, int value, PhotonMessageInfo info)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0009: Unknown result type (might be due to invalid IL or missing references)
			if (info.Sender != null && info.Sender == PhotonNetwork.MasterClient)
			{
				Stats.Get(key)?.ApplyFromNetwork(steamID, value);
			}
		}

		internal static bool OwnsSlot(string steamID)
		{
			if (!PhotonNetwork.InRoom || PhotonNetwork.OfflineMode)
			{
				return true;
			}
			PlayerAvatar instance = PlayerAvatar.instance;
			if ((Object)(object)instance != (Object)null)
			{
				return steamID == instance.steamID;
			}
			return false;
		}

		internal static void OnOwnerWrote(Stat stat, string steamID, int value)
		{
			if (!((Object)(object)_instance == (Object)null) && PhotonNetwork.InRoom && !PhotonNetwork.OfflineMode)
			{
				((MonoBehaviourPun)_instance).photonView.RPC("StatsCoreOwnedRPC", (RpcTarget)1, new object[3] { stat.Key, steamID, value });
			}
		}

		[PunRPC]
		private void StatsCoreOwnedRPC(string key, string steamID, int value, PhotonMessageInfo info)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			if (SenderOwns(info, steamID))
			{
				Stat stat = Stats.Get(key);
				if (stat != null && stat.IsClientAuthoritative)
				{
					stat.ApplyFromNetwork(steamID, value);
				}
			}
		}

		private static bool SenderOwns(PhotonMessageInfo info, string steamID)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0012: Unknown result type (might be due to invalid IL or missing references)
			if (info.Sender == null || string.IsNullOrEmpty(steamID))
			{
				return false;
			}
			PlayerAvatar val = SemiFunc.PlayerAvatarGetFromPhotonPlayer(info.Sender);
			if ((Object)(object)val != (Object)null)
			{
				return val.steamID == steamID;
			}
			return false;
		}

		internal static void RequestFromClients(string key, Action<IReadOnlyDictionary<string, int>> onComplete, float timeoutSeconds)
		{
			if (onComplete != null)
			{
				if ((Object)(object)_instance == (Object)null || !PhotonNetwork.InRoom || PhotonNetwork.OfflineMode || !PhotonNetwork.IsMasterClient)
				{
					onComplete(LocalSlot(key));
				}
				else
				{
					_instance.BeginPull(key, onComplete, timeoutSeconds);
				}
			}
		}

		private void BeginPull(string key, Action<IReadOnlyDictionary<string, int>> done, float timeoutSeconds)
		{
			int num = ++_nextRequestId;
			PullRequest pullRequest = new PullRequest
			{
				Done = done,
				Pending = ((PhotonNetwork.CurrentRoom != null) ? (PhotonNetwork.CurrentRoom.PlayerCount - 1) : 0)
			};
			foreach (KeyValuePair<string, int> item in LocalSlot(key))
			{
				pullRequest.Results[item.Key] = item.Value;
			}
			_pulls[num] = pullRequest;
			if (pullRequest.Pending <= 0)
			{
				CompletePull(num);
				return;
			}
			((MonoBehaviourPun)this).photonView.RPC("StatsCorePullRequestRPC", (RpcTarget)1, new object[2] { key, num });
			((MonoBehaviour)this).StartCoroutine(PullTimeout(num, timeoutSeconds));
		}

		[PunRPC]
		private void StatsCorePullRequestRPC(string key, int requestId, PhotonMessageInfo info)
		{
			//IL_0000: 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_005a: Unknown result type (might be due to invalid IL or missing references)
			if (info.Sender != null && info.Sender == PhotonNetwork.MasterClient)
			{
				PlayerAvatar instance = PlayerAvatar.instance;
				string text = (((Object)(object)instance != (Object)null) ? instance.steamID : "");
				int num = 0;
				Stat stat = Stats.Get(key);
				if (stat != null && !string.IsNullOrEmpty(text))
				{
					num = stat.Get(text);
				}
				((MonoBehaviourPun)this).photonView.RPC("StatsCorePullResponseRPC", info.Sender, new object[4] { key, requestId, text, num });
			}
		}

		[PunRPC]
		private void StatsCorePullResponseRPC(string key, int requestId, string steamID, int value, PhotonMessageInfo info)
		{
			//IL_0008: Unknown result type (might be due to invalid IL or missing references)
			//IL_0032: Unknown result type (might be due to invalid IL or missing references)
			if (PhotonNetwork.IsMasterClient && SenderOwns(info, steamID) && _pulls.TryGetValue(requestId, out PullRequest value2) && !value2.Completed && value2.Answered.Add(info.Sender.ActorNumber))
			{
				value2.Results[steamID] = value;
				value2.Pending--;
				if (value2.Pending <= 0)
				{
					CompletePull(requestId);
				}
			}
		}

		private IEnumerator PullTimeout(int requestId, float seconds)
		{
			yield return (object)new WaitForSecondsRealtime(seconds);
			CompletePull(requestId);
		}

		private void CompletePull(int requestId)
		{
			if (!_pulls.TryGetValue(requestId, out PullRequest value) || value.Completed)
			{
				return;
			}
			value.Completed = true;
			_pulls.Remove(requestId);
			try
			{
				value.Done(value.Results);
			}
			catch (Exception ex)
			{
				StatsCorePlugin.Logger.LogWarning((object)$"[StatsNet] pull callback for '{requestId}' threw: {ex.Message}");
			}
		}

		private static Dictionary<string, int> LocalSlot(string key)
		{
			Dictionary<string, int> dictionary = new Dictionary<string, int>();
			Stat stat = Stats.Get(key);
			PlayerAvatar instance = PlayerAvatar.instance;
			if (stat != null && (Object)(object)instance != (Object)null && !string.IsNullOrEmpty(instance.steamID))
			{
				dictionary[instance.steamID] = stat.Get(instance.steamID);
			}
			return dictionary;
		}

		public override void OnPlayerLeftRoom(Player otherPlayer)
		{
			if (!PhotonNetwork.IsMasterClient)
			{
				return;
			}
			foreach (int item in new List<int>(_pulls.Keys))
			{
				if (_pulls.TryGetValue(item, out PullRequest value) && !value.Completed && !value.Answered.Contains(otherPlayer.ActorNumber))
				{
					value.Pending--;
					if (value.Pending <= 0)
					{
						CompletePull(item);
					}
				}
			}
		}

		public override void OnPlayerEnteredRoom(Player newPlayer)
		{
			if (PhotonNetwork.IsMasterClient)
			{
				SendSnapshot(newPlayer);
			}
		}

		public override void OnMasterClientSwitched(Player newMasterClient)
		{
			if (PhotonNetwork.IsMasterClient)
			{
				SendSnapshot(null);
				_pulls.Clear();
			}
		}

		private void SendSnapshot(Player? target)
		{
			foreach (string item in BuildSnapshotChunks())
			{
				if (target != null)
				{
					((MonoBehaviourPun)this).photonView.RPC("StatsCoreSnapshotRPC", target, new object[1] { item });
				}
				else
				{
					((MonoBehaviourPun)this).photonView.RPC("StatsCoreSnapshotRPC", (RpcTarget)1, new object[1] { item });
				}
			}
		}

		[PunRPC]
		private void StatsCoreSnapshotRPC(string payload, PhotonMessageInfo info)
		{
			//IL_0000: 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)
			if (info.Sender != null && info.Sender == PhotonNetwork.MasterClient)
			{
				ApplySnapshot(payload);
			}
		}

		private static IEnumerable<string> BuildSnapshotChunks()
		{
			StringBuilder sb = new StringBuilder();
			foreach (Stat item in Stats.All)
			{
				if (item.Lifetime == StatLifetime.Career)
				{
					continue;
				}
				StringBuilder entry = new StringBuilder(item.Key).Append('|');
				bool flag = false;
				foreach (KeyValuePair<string, int> item2 in item.Raw)
				{
					if (flag)
					{
						entry.Append(',');
					}
					entry.Append(item2.Key).Append('=').Append(item2.Value);
					flag = true;
				}
				if (flag)
				{
					if (sb.Length > 0 && sb.Length + entry.Length + 1 > 32000)
					{
						yield return sb.ToString();
						sb.Clear();
					}
					if (sb.Length > 0)
					{
						sb.Append(';');
					}
					sb.Append(entry);
				}
			}
			if (sb.Length > 0)
			{
				yield return sb.ToString();
			}
		}

		private static void ApplySnapshot(string payload)
		{
			int num = 0;
			string[] array = payload.Split(';');
			foreach (string text in array)
			{
				int num2 = text.IndexOf('|');
				if (num2 <= 0)
				{
					continue;
				}
				Stat stat = Stats.Get(text.Substring(0, num2));
				if (stat == null)
				{
					continue;
				}
				string[] array2 = text.Substring(num2 + 1).Split(',');
				foreach (string text2 in array2)
				{
					int num3 = text2.IndexOf('=');
					if (num3 > 0 && int.TryParse(text2.Substring(num3 + 1), out var result))
					{
						stat.ApplyFromNetwork(text2.Substring(0, num3), result);
						num++;
					}
				}
			}
			StatsCorePlugin.Logger.LogDebug((object)$"[StatsNet] snapshot applied ({num} entries)");
		}
	}
	internal static class PauseLayout
	{
		private const float DefaultStep = 32f;

		internal static (float QuitY, float ButtonY) Place(IReadOnlyList<float> column, float quitY)
		{
			if (column.Count == 0)
			{
				return (QuitY: quitY, ButtonY: quitY + 32f);
			}
			float[] array = column.OrderByDescending((float y) => y).ToArray();
			float num = 32f;
			for (int num2 = 1; num2 < array.Length; num2++)
			{
				num = ((num2 == 1) ? (array[0] - array[1]) : Math.Min(num, array[num2 - 1] - array[num2]));
			}
			float num3 = array[^1];
			float num4 = Math.Max(0f, 2f * num - (num3 - quitY));
			return (QuitY: quitY - num4, ButtonY: num3 - num);
		}
	}
	internal static class StatFormatter
	{
		private static readonly CultureInfo Inv = CultureInfo.InvariantCulture;

		public static string Value(long value, StatFormat format)
		{
			return format switch
			{
				StatFormat.Time => Duration(value), 
				StatFormat.Distance => Distance(value), 
				StatFormat.Money => ((value < 0) ? "-$" : "$") + Math.Abs(value).ToString("N0", Inv), 
				StatFormat.Percent => value.ToString("N0", Inv) + "%", 
				_ => value.ToString("N0", Inv), 
			};
		}

		public static string Duration(long seconds)
		{
			if (seconds < 0)
			{
				seconds = 0L;
			}
			if (seconds < 60)
			{
				return seconds + "s";
			}
			if (seconds < 3600)
			{
				return seconds / 60 + "m " + (seconds % 60).ToString("00", Inv) + "s";
			}
			if (seconds < 86400)
			{
				return seconds / 3600 + "h " + (seconds % 3600 / 60).ToString("00", Inv) + "m";
			}
			return seconds / 86400 + "d " + seconds % 86400 / 3600 + "h";
		}

		public static string Distance(long meters)
		{
			if (meters < 1000)
			{
				return meters.ToString("N0", Inv) + " m";
			}
			double num = (double)meters / 1000.0;
			return ((num < 100.0) ? num.ToString("0.0", Inv) : num.ToString("N0", Inv)) + " km";
		}

		public static string Fallback(string key)
		{
			string text = key.Substring(key.LastIndexOf('.') + 1);
			StringBuilder stringBuilder = new StringBuilder();
			for (int i = 0; i < text.Length; i++)
			{
				char c = text[i];
				if (c == '_' || c == '-')
				{
					stringBuilder.Append(' ');
					continue;
				}
				if (i > 0 && char.IsUpper(c) && !char.IsUpper(text[i - 1]) && text[i - 1] != '_')
				{
					stringBuilder.Append(' ');
				}
				stringBuilder.Append((i == 0 || stringBuilder[stringBuilder.Length - 1] == ' ') ? char.ToUpperInvariant(c) : c);
			}
			if (stringBuilder.Length != 0)
			{
				return stringBuilder.ToString();
			}
			return key;
		}

		public static bool LooksLikeSteamId(string key)
		{
			if (key.Length < 15)
			{
				return false;
			}
			foreach (char c in key)
			{
				if (c < '0' || c > '9')
				{
					return false;
				}
			}
			return true;
		}
	}
	internal enum StatsView
	{
		Career,
		Today,
		Week,
		Run
	}
	internal enum LineKind
	{
		Header,
		Row,
		Sub,
		Note
	}
	internal readonly struct Line
	{
		public readonly LineKind Kind;

		public readonly string Text;

		public readonly string Value;

		public readonly bool Dim;

		public Line(LineKind kind, string text, string value = "", bool dim = false)
		{
			Kind = kind;
			Text = text;
			Value = value;
			Dim = dim;
		}
	}
	internal static class StatsModel
	{
		internal const string AllCategories = "All";

		internal static readonly string[] ViewNames = new string[4] { "Career", "Today", "Last 7 days", "This run" };

		private static readonly string[] CategoryOrder = new string[7] { "Glory", "Mobility", "Expeditions", "Misfortune", "Recovery", "Mischief", "Expedition" };

		private const int BreakdownRows = 6;

		internal static string LocalId()
		{
			string text = (((Object)(object)PlayerAvatar.instance != (Object)null) ? PlayerAvatar.instance.steamID : null);
			if (!string.IsNullOrEmpty(text))
			{
				return text;
			}
			Dictionary<string, int> dictionary = new Dictionary<string, int>();
			foreach (Stat item in Stats.All)
			{
				if (item.Lifetime != StatLifetime.Career)
				{
					continue;
				}
				foreach (string key in item.Entries.Keys)
				{
					if (StatFormatter.LooksLikeSteamId(key))
					{
						dictionary[key] = ((!dictionary.TryGetValue(key, out var value)) ? 1 : (value + 1));
					}
				}
			}
			if (dictionary.Count != 0)
			{
				return dictionary.OrderByDescending((KeyValuePair<string, int> c) => c.Value).First().Key;
			}
			return "";
		}

		internal static string[] CategoryNames()
		{
			HashSet<string> hashSet = new HashSet<string>(Stats.All.Select(CategoryOf));
			List<string> list = CategoryOrder.Where(hashSet.Contains).ToList();
			list.AddRange(hashSet.Except(list).OrderBy<string, string>((string c) => c, StringComparer.OrdinalIgnoreCase));
			list.Insert(0, "All");
			return list.ToArray();
		}

		internal static List<Line> Build(StatsView view, string category)
		{
			string id = LocalId();
			List<Line> list = new List<Line>();
			if (view == StatsView.Career && category == "All")
			{
				AddHighlights(list, id);
			}
			foreach (IGrouping<string, Stat> item in (from g in Stats.All.Where((Stat s) => Shown(s, view) && (category == "All" || CategoryOf(s) == category)).GroupBy(CategoryOf)
				orderby CategoryRank(g.Key)
				select g).ThenBy<IGrouping<string, Stat>, string>((IGrouping<string, Stat> g) => g.Key, StringComparer.OrdinalIgnoreCase))
			{
				List<Line> list2 = new List<Line>();
				foreach (Stat item2 in item)
				{
					AddStat(list2, item2, view, id);
				}
				if (list2.Count != 0)
				{
					list.Add(new Line(LineKind.Header, item.Key));
					list.AddRange(list2);
				}
			}
			if (list.Count == 0)
			{
				list.Add(new Line(LineKind.Note, EmptyMessage(view)));
			}
			return list;
		}

		private static bool Shown(Stat stat, StatsView view)
		{
			switch (view)
			{
			case StatsView.Career:
				return stat.Lifetime == StatLifetime.Career;
			case StatsView.Today:
			case StatsView.Week:
				return stat.Lifetime == StatLifetime.Career && stat.TracksDaily;
			default:
				return stat.Lifetime != StatLifetime.Career;
			}
		}

		private static string CategoryOf(Stat stat)
		{
			if (!string.IsNullOrWhiteSpace(stat.Category))
			{
				return stat.Category;
			}
			return "Other";
		}

		private static int CategoryRank(string category)
		{
			int num = Array.IndexOf(CategoryOrder, category);
			if (num >= 0)
			{
				return num;
			}
			return CategoryOrder.Length;
		}

		private static string NameOf(Stat stat)
		{
			if (!string.IsNullOrWhiteSpace(stat.DisplayName) && !(stat.DisplayName == stat.Key))
			{
				return stat.DisplayName;
			}
			return StatFormatter.Fallback(stat.Key);
		}

		private static void AddStat(List<Line> rows, Stat stat, StatsView view, string id)
		{
			List<KeyValuePair<string, int>> list = (from e in stat.Entries
				where e.Key != "_team" && !StatFormatter.LooksLikeSteamId(e.Key) && e.Value > 0
				orderby e.Value descending
				select e).ToList();
			if (list.Count > 0)
			{
				if (view == StatsView.Today || view == StatsView.Week)
				{
					return;
				}
				rows.Add(new Line(LineKind.Row, NameOf(stat), StatFormatter.Value(((IEnumerable<KeyValuePair<string, int>>)list).Sum((Func<KeyValuePair<string, int>, long>)((KeyValuePair<string, int> e) => e.Value)), stat.Format)));
				{
					foreach (KeyValuePair<string, int> item in list.Take(6))
					{
						rows.Add(new Line(LineKind.Sub, item.Key, StatFormatter.Value(item.Value, stat.Format)));
					}
					return;
				}
			}
			long num = view switch
			{
				StatsView.Today => stat.GetToday(id), 
				StatsView.Week => stat.GetLastDays(id, 7), 
				_ => (stat.Scope == StatScope.Team) ? stat.GetTeam() : stat.Get(id), 
			};
			if (num != 0L || (view != StatsView.Today && view != StatsView.Week))
			{
				rows.Add(new Line(LineKind.Row, NameOf(stat), StatFormatter.Value(num, stat.Format), num == 0));
			}
		}

		private static void AddHighlights(List<Line> lines, string id)
		{
			long num = 0L;
			foreach (Stat item in Stats.All)
			{
				if (item.Lifetime != StatLifetime.Career)
				{
					continue;
				}
				foreach (string key in item.Entries.Keys)
				{
					long num2 = item.FirstUtc(key);
					if (num2 > 0 && (num == 0L || num2 < num))
					{
						num = num2;
					}
				}
			}
			List<Line> list = new List<Line>();
			if (num > 0)
			{
				list.Add(new Line(LineKind.Row, "Tracked since", DateTimeOffset.FromUnixTimeSeconds(num).LocalDateTime.ToString("d MMM yyyy", CultureInfo.InvariantCulture)));
			}
			AddTopEntry(list, "statscore.deathsByEnemy", "Most deadly");
			AddTopEntry(list, "statscore.mapVisits", "Favorite level");
			if (list.Count != 0)
			{
				lines.Add(new Line(LineKind.Header, "Highlights"));
				lines.AddRange(list);
			}
		}

		private static void AddTopEntry(List<Line> into, string key, string label)
		{
			Stat stat = Stats.Get(key);
			if (stat != null)
			{
				KeyValuePair<string, int> keyValuePair = (from e in stat.Entries
					where e.Key != "_team" && !StatFormatter.LooksLikeSteamId(e.Key) && e.Value > 0
					orderby e.Value descending
					select e).FirstOrDefault();
				if (keyValuePair.Key != null)
				{
					into.Add(new Line(LineKind.Row, label, keyValuePair.Key + " (" + StatFormatter.Value(keyValuePair.Value, StatFormat.Count) + ")"));
				}
			}
		}

		private static string EmptyMessage(StatsView view)
		{
			return view switch
			{
				StatsView.Today => "Nothing recorded today yet.", 
				StatsView.Week => "Nothing recorded this week yet.", 
				StatsView.Run => "Nothing tracked for this run yet.", 
				_ => "Nothing here yet. Play a round and come back.", 
			};
		}
	}
	internal static class StatsPage
	{
		private const string RowPrefix = "StatsRow";

		private static readonly Color Gold = new Color(1f, 0.78f, 0.26f);

		private static readonly Color Orange = new Color(1f, 0.55f, 0f);

		private static readonly Color Gray = new Color(0.72f, 0.72f, 0.72f);

		private static REPOPopupPage? _page;

		private static TMP_Text? _font;

		private static float _rowWidth = 254f;

		private static StatsView _view = StatsView.Career;

		private static string _category = "All";

		internal static void Open()
		{
			//IL_00d3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00dd: Expected O, but got Unknown
			if ((Object)(object)MenuManager.instance == (Object)null)
			{
				return;
			}
			_view = StatsView.Career;
			_category = "All";
			REPOPopupPage page = MenuAPI.CreateREPOPopupPage("Stats", (PresetSide)0, false, true, 2f);
			_page = page;
			AddSelector(page, "View", StatsModel.ViewNames, StatsModel.ViewNames[0], delegate(string choice)
			{
				_view = (StatsView)Math.Max(0, Array.IndexOf(StatsModel.ViewNames, choice));
				Rebuild();
			});
			AddSelector(page, "Category", StatsModel.CategoryNames(), "All", delegate(string choice)
			{
				_category = choice;
				Rebuild();
			});
			Fill(page);
			page.AddElement((BuilderDelegate)delegate(Transform parent)
			{
				//IL_002f: Unknown result type (might be due to invalid IL or missing references)
				MenuAPI.CreateREPOButton("Back", (Action)delegate
				{
					page.ClosePage(true);
				}, parent, new Vector2(66f, 18f));
			});
			page.menuPage.interactionPriority = 0;
			page.OpenPage(false);
			page.menuPage.interactionPriority = MenuManager.instance.HighestOpenPriority() + 1;
			((Behaviour)page.scrollView).enabled = false;
			((MonoBehaviour)page).StartCoroutine(Remeasure(page));
		}

		private static void AddSelector(REPOPopupPage page, string label, string[] options, string current, Action<string> onChange)
		{
			//IL_002b: Unknown result type (might be due to invalid IL or missing references)
			//IL_003f: Expected O, but got Unknown
			page.AddElementToScrollView((ScrollViewBuilderDelegate)delegate(Transform sv)
			{
				//IL_0037: Unknown result type (might be due to invalid IL or missing references)
				//IL_0053: Unknown result type (might be due to invalid IL or missing references)
				//IL_0058: Unknown result type (might be due to invalid IL or missing references)
				REPOSlider val = MenuAPI.CreateREPOSlider(label, "", (Action<string>)delegate(string choice)
				{
					onChange(choice);
				}, sv, options, current, Vector2.zero, "", "", (BarBehavior)0);
				Rect rect = ((REPOElement)val).rectTransform.rect;
				float width = ((Rect)(ref rect)).width;
				if (width > 0f)
				{
					_rowWidth = width;
				}
				if (_font == null)
				{
					_font = (TMP_Text?)(object)val.labelTMP;
				}
				return ((REPOElement)val).rectTransform;
			}, 0f, 0f);
		}

		private static void Rebuild()
		{
			REPOPopupPage page = _page;
			if ((Object)(object)page == (Object)null)
			{
				return;
			}
			REPOScrollViewElement[] componentsInChildren = ((Component)page.scrollView).GetComponentsInChildren<REPOScrollViewElement>(true);
			foreach (REPOScrollViewElement val in componentsInChildren)
			{
				if (((Object)val).name.StartsWith("StatsRow"))
				{
					((Component)val).transform.SetParent((Transform)null);
					Object.Destroy((Object)(object)((Component)val).gameObject);
				}
			}
			Fill(page);
			((MonoBehaviour)page).StartCoroutine(Remeasure(page));
		}

		private static void Fill(REPOPopupPage page)
		{
			//IL_005c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0061: Unknown result type (might be due to invalid IL or missing references)
			//IL_0097: Unknown result type (might be due to invalid IL or missing references)
			//IL_009c: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d6: Unknown result type (might be due to invalid IL or missing references)
			//IL_0101: Unknown result type (might be due to invalid IL or missing references)
			//IL_0106: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fa: Unknown result type (might be due to invalid IL or missing references)
			//IL_010b: Unknown result type (might be due to invalid IL or missing references)
			//IL_011b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0120: Unknown result type (might be due to invalid IL or missing references)
			//IL_0114: Unknown result type (might be due to invalid IL or missing references)
			//IL_0125: Unknown result type (might be due to invalid IL or missing references)
			//IL_013d: Unknown result type (might be due to invalid IL or missing references)
			//IL_013e: Unknown result type (might be due to invalid IL or missing references)
			foreach (Line item in StatsModel.Build(_view, _category))
			{
				switch (item.Kind)
				{
				case LineKind.Header:
					AddRow(page, item.Text, "", 22f, 34f, Orange, Orange, 0f, 10f, 2f, rule: false);
					continue;
				case LineKind.Sub:
					AddRow(page, item.Text, item.Value, 14f, 20f, Gray, Gray, 12f, 0f, 0f, rule: false);
					continue;
				case LineKind.Note:
					AddRow(page, item.Text, "", 15f, 32f, Gray, Gray, 0f, 8f, 0f, rule: false);
					continue;
				}
				Color nameColor = (item.Dim ? Fade(White()) : White());
				Color valueColor = (item.Dim ? Fade(Gold) : Gold);
				AddRow(page, item.Text, item.Value, 17f, 24f, nameColor, valueColor, 0f, 0f, 0f, rule: true);
			}
		}

		private static Color White()
		{
			//IL_0018: Unknown result type (might be due to invalid IL or missing references)
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			if (!((Object)(object)_font != (Object)null))
			{
				return Color.white;
			}
			return ((Graphic)_font).color;
		}

		private static Color Fade(Color c)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_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)
			return new Color(c.r, c.g, c.b, 0.45f);
		}

		private static void AddRow(REPOPopupPage page, string name, string value, float size, float height, Color nameColor, Color valueColor, float indent, float top, float bottom, bool rule)
		{
			//IL_0024: Unknown result type (might be due to invalid IL or missing references)
			//IL_0026: Unknown result type (might be due to invalid IL or missing references)
			//IL_0034: Unknown result type (might be due to invalid IL or missing references)
			//IL_0036: Unknown result type (might be due to invalid IL or missing references)
			//IL_004b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0059: Expected O, but got Unknown
			page.AddElementToScrollView((ScrollViewBuilderDelegate)delegate(Transform sv)
			{
				//IL_002f: Unknown result type (might be due to invalid IL or missing references)
				//IL_0034: Unknown result type (might be due to invalid IL or missing references)
				//IL_0040: Unknown result type (might be due to invalid IL or missing references)
				//IL_0046: Expected O, but got Unknown
				//IL_0050: 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_0056: 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_0063: Unknown result type (might be due to invalid IL or missing references)
				//IL_0079: Unknown result type (might be due to invalid IL or missing references)
				//IL_0084: Unknown result type (might be due to invalid IL or missing references)
				//IL_00bc: Unknown result type (might be due to invalid IL or missing references)
				//IL_00fc: Unknown result type (might be due to invalid IL or missing references)
				//IL_0142: 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)
				//IL_014e: Unknown result type (might be due to invalid IL or missing references)
				//IL_0154: Unknown result type (might be due to invalid IL or missing references)
				//IL_0159: Unknown result type (might be due to invalid IL or missing references)
				//IL_0161: Unknown result type (might be due to invalid IL or missing references)
				//IL_0162: Unknown result type (might be due to invalid IL or missing references)
				//IL_016c: Unknown result type (might be due to invalid IL or missing references)
				//IL_0177: Unknown result type (might be due to invalid IL or missing references)
				//IL_0181: Unknown result type (might be due to invalid IL or missing references)
				//IL_018c: Unknown result type (might be due to invalid IL or missing references)
				//IL_01a0: Unknown result type (might be due to invalid IL or missing references)
				//IL_01c4: Unknown result type (might be due to invalid IL or missing references)
				int layer = ((Component)sv).gameObject.layer;
				RectTransform val = (RectTransform)new GameObject("StatsRow - " + name, new Type[1] { typeof(RectTransform) })
				{
					layer = layer
				}.transform;
				((Transform)val).SetParent(sv, false);
				Vector2 anchorMin = (val.anchorMax = Vector2.zero);
				val.anchorMin = anchorMin;
				val.pivot = Vector2.zero;
				val.sizeDelta = new Vector2(_rowWidth, height);
				((Transform)val).localPosition = Vector3.zero;
				float num = (string.IsNullOrEmpty(value) ? 0f : (_rowWidth * 0.36f));
				AddText(val, name, size, nameColor, (TextAlignmentOptions)4097, indent, _rowWidth - indent - num - 6f, layer);
				if (num > 0f)
				{
					AddText(val, value, size, valueColor, (TextAlignmentOptions)4100, 0f, num, layer);
				}
				if (rule)
				{
					GameObject val2 = new GameObject("Rule", new Type[2]
					{
						typeof(RectTransform),
						typeof(Image)
					})
					{
						layer = layer
					};
					RectTransform val3 = (RectTransform)val2.transform;
					((Transform)val3).SetParent((Transform)(object)val, false);
					val3.anchorMin = Vector2.zero;
					val3.anchorMax = new Vector2(1f, 0f);
					val3.pivot = new Vector2(0.5f, 0f);
					val3.sizeDelta = new Vector2(0f, 1f);
					Image component = val2.GetComponent<Image>();
					((Graphic)component).color = new Color(1f, 1f, 1f, 0.08f);
					((Graphic)component).raycastTarget = false;
				}
				return val;
			}, top, bottom);
		}

		private static void AddText(RectTransform parent, string text, float size, Color color, TextAlignmentOptions align, float indent, float room, int layer)
		{
			//IL_0025: Unknown result type (might be due to invalid IL or missing references)
			//IL_002a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0032: Unknown result type (might be due to invalid IL or missing references)
			//IL_0038: 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: 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_0050: 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_005b: 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_0061: Unknown result type (might be due to invalid IL or missing references)
			//IL_0062: 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)
			//IL_00b2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00dd: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e4: Invalid comparison between Unknown and I4
			//IL_00a7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f7: Unknown result type (might be due to invalid IL or missing references)
			//IL_0111: Unknown result type (might be due to invalid IL or missing references)
			GameObject val = new GameObject("Text", new Type[2]
			{
				typeof(RectTransform),
				typeof(TextMeshProUGUI)
			})
			{
				layer = layer
			};
			RectTransform val2 = (RectTransform)val.transform;
			((Transform)val2).SetParent((Transform)(object)parent, false);
			val2.anchorMin = Vector2.zero;
			val2.anchorMax = Vector2.one;
			Vector2 offsetMin = (val2.offsetMax = Vector2.zero);
			val2.offsetMin = offsetMin;
			TextMeshProUGUI component = val.GetComponent<TextMeshProUGUI>();
			if ((Object)(object)_font != (Object)null)
			{
				((TMP_Text)component).font = _font.font;
				((TMP_Text)component).fontSharedMaterial = _font.fontSharedMaterial;
				((TMP_Text)component).fontStyle = _font.fontStyle;
			}
			((Graphic)component).color = color;
			((TMP_Text)component).alignment = align;
			((Graphic)component).raycastTarget = false;
			((TMP_Text)component).enableWordWrapping = false;
			((TMP_Text)component).overflowMode = (TextOverflowModes)0;
			((TMP_Text)component).margin = new Vector4(indent, 0f, ((int)align == 4100) ? 2f : 0f, 0f);
			((TMP_Text)component).fontSize = size;
			((TMP_Text)component).text = text;
			float x = ((TMP_Text)component).GetPreferredValues(text).x;
			if (room > 0f && x > room)
			{
				((TMP_Text)component).fontSize = Mathf.Max(9f, size * room / x);
			}
		}

		private static IEnumerator Remeasure(REPOPopupPage page)
		{
			yield return (object)new WaitForSecondsRealtime(0.3f);
			if (Object.op_Implicit((Object)(object)page))
			{
				page.menuScrollBox.RecalculateScrollHeight(true);
				page.scrollView.UpdateElements();
			}
		}
	}
	internal static class StatsScreenEntry
	{
		[CompilerGenerated]
		private static class <>O
		{
			public static BuilderDelegate <0>__Build;
		}

		internal static void Install(ConfigFile config)
		{
			//IL_0039: Unknown result type (might be due to invalid IL or missing references)
			//IL_0065: 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_0070: Expected O, but got Unknown
			ConfigEntry<bool> obj = config.Bind<bool>("Stats screen", "Main menu button", true, "Show a Stats button in the main menu.");
			ConfigEntry<bool> val = config.Bind<bool>("Stats screen", "Pause menu button", true, "Show a Stats button in the pause menu.");
			if (obj.Value)
			{
				new Harmony("Vippy.StatsCore.Screen").PatchAll(typeof(MainMenuPatch));
			}
			if (val.Value)
			{
				object obj2 = <>O.<0>__Build;
				if (obj2 == null)
				{
					BuilderDelegate val2 = PauseButton.Build;
					<>O.<0>__Build = val2;
					obj2 = (object)val2;
				}
				MenuAPI.AddElementToEscapeMenu((BuilderDelegate)obj2);
			}
		}
	}
	[HarmonyPatch(typeof(MenuPageMain), "Start")]
	internal static class MainMenuPatch
	{
		[HarmonyPostfix]
		private static void Postfix(MenuPageMain __instance)
		{
			//IL_005e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0080: Unknown result type (might be due to invalid IL or missing references)
			//IL_0087: Expected O, but got Unknown
			//IL_015e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0163: Unknown result type (might be due to invalid IL or missing references)
			//IL_016d: Unknown result type (might be due to invalid IL or missing references)
			Transform val = ((Component)__instance).transform.Find("LeftTransform/Buttons");
			if ((Object)(object)val == (Object)null)
			{
				return;
			}
			VerticalLayoutGroup val2 = default(VerticalLayoutGroup);
			if (!((Component)val).TryGetComponent<VerticalLayoutGroup>(ref val2))
			{
				VerticalLayoutGroup obj = ((Component)val).gameObject.AddComponent<VerticalLayoutGroup>();
				((HorizontalOrVerticalLayoutGroup)obj).childControlWidth = false;
				((HorizontalOrVerticalLayoutGroup)obj).childForceExpandWidth = false;
			}
			REPOButton val3 = MenuAPI.CreateREPOButton("Stats", (Action)StatsPage.Open, val, Vector2.zero);
			List<Transform> list = new List<Transform>();
			foreach (Transform item in val)
			{
				Transform val4 = item;
				if ((Object)(object)val4 != (Object)(object)((Component)val3).transform && (((Object)val4).name.Contains("Menu Button") || (Object)(object)((Component)val4).GetComponent<REPOButton>() != (Object)null))
				{
					list.Add(val4);
				}
			}
			list = list.OrderByDescending((Transform t) => t.localPosition.y).ToList();
			if (list.Count == 0)
			{
				return;
			}
			list.Insert(list.Count - 1, ((Component)val3).transform);
			((Component)val3).transform.SetSiblingIndex(list[list.Count - 1].GetSiblingIndex());
			float num = 230f;
			foreach (Transform item2 in list)
			{
				Vector3 localPosition = item2.localPosition;
				localPosition.y = num;
				item2.localPosition = localPosition;
				num -= 30f;
			}
		}
	}
	internal static class PauseButton
	{
		internal static void Build(Transform page)
		{
			//IL_0030: Unknown result type (might be due to invalid IL or missing references)
			//IL_0037: Expected O, but got Unknown
			//IL_00e1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ff: Unknown result type (might be due to invalid IL or missing references)
			//IL_010b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0115: Unknown result type (might be due to invalid IL or missing references)
			//IL_0141: Unknown result type (might be due to invalid IL or missing references)
			//IL_014c: Unknown result type (might be due to invalid IL or missing references)
			//IL_006f: 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_0093: Unknown result type (might be due to invalid IL or missing references)
			//IL_009e: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ad: Unknown result type (might be due to invalid IL or missing references)
			Transform val = page.Find("Menu Button - Quit game");
			if ((Object)(object)val == (Object)null)
			{
				return;
			}
			List<float> list = new List<float>();
			foreach (Transform item in page)
			{
				Transform val2 = item;
				if (!((Object)(object)val2 == (Object)(object)val) && ((Component)val2).gameObject.activeSelf && !((Object)(object)((Component)val2).GetComponent<MenuButton>() == (Object)null) && !((Object)(object)((Component)val2).GetComponent<REPOButton>() != (Object)null) && !(Mathf.Abs(val2.localPosition.x - val.localPosition.x) > 1f) && val2.localPosition.y > val.localPosition.y)
				{
					list.Add(val2.localPosition.y);
				}
			}
			var (num, num2) = PauseLayout.Place(list, val.localPosition.y);
			val.localPosition = new Vector3(val.localPosition.x, num, val.localPosition.z);
			MenuAPI.CreateREPOButton("Stats", (Action)StatsPage.Open, page, new Vector2(val.localPosition.x, num2));
		}
	}
}