Decompiled source of Gungeon Register v2.7.5

RegistreDuGungeonMod.dll

Decompiled a day 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.Text;
using System.Threading;
using BepInEx;
using BepInEx.Configuration;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyVersion("0.0.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace RegistreDuGungeonMod
{
	public class DataStore
	{
		public const string ModVersion = "1.0.0";

		public const int GUNS_TOTAL = 237;

		public const int ITEMS_TOTAL = 258;

		public const int BOSSES_TOTAL = 30;

		private readonly string _dir;

		private readonly Action<string> _log;

		private readonly object _lock = new object();

		private Timer _debounceTimer;

		public bool[] GunsFound = new bool[237];

		public bool[] ItemsFound = new bool[258];

		public bool[] BossesFound = new bool[30];

		public List<RunRecord> PastRuns = new List<RunRecord>();

		public RunRecord CurrentRun;

		private volatile bool _saving;

		public string LiveProgressPath => Path.Combine(_dir, "live_progress.json");

		public string RunHistoryPath => Path.Combine(_dir, "run_history.json");

		public DataStore(string dataDir, Action<string> log)
		{
			_dir = dataDir;
			_log = log;
			try
			{
				Directory.CreateDirectory(_dir);
			}
			catch (Exception ex)
			{
				_log?.Invoke("[Registre] Impossible de creer le dossier de donnees : " + ex.Message);
			}
			LoadExisting();
		}

		private static string NowIso()
		{
			return DateTime.UtcNow.ToString("yyyy-MM-ddTHH:mm:ssZ", CultureInfo.InvariantCulture);
		}

		private void LoadExisting()
		{
			try
			{
				if (File.Exists(LiveProgressPath) && MiniJson.Decode(File.ReadAllText(LiveProgressPath)) is Dictionary<string, object> decoded)
				{
					CopyBoolArray(decoded, "guns", GunsFound);
					CopyBoolArray(decoded, "items", ItemsFound);
					CopyBoolArray(decoded, "bosses", BossesFound);
				}
			}
			catch (Exception ex)
			{
				_log?.Invoke("[Registre] Lecture live_progress.json precedent impossible : " + ex.Message);
			}
			try
			{
				if (File.Exists(RunHistoryPath) && MiniJson.Decode(File.ReadAllText(RunHistoryPath)) is Dictionary<string, object> dictionary && dictionary.TryGetValue("runs", out var value) && value is List<object> list)
				{
					foreach (object item in list)
					{
						if (item is Dictionary<string, object> d)
						{
							PastRuns.Add(RunRecord.FromDict(d));
						}
					}
				}
				_log?.Invoke("[Registre] " + PastRuns.Count + " run(s) precedent(s) recharge(s).");
			}
			catch (Exception ex2)
			{
				_log?.Invoke("[Registre] Lecture run_history.json precedent impossible : " + ex2.Message);
			}
			bool flag = false;
			foreach (RunRecord pastRun in PastRuns)
			{
				if (pastRun != null && pastRun.result == "in_progress")
				{
					pastRun.result = "abandoned";
					if (string.IsNullOrEmpty(pastRun.endedAt))
					{
						pastRun.endedAt = NowIso();
					}
					flag = true;
				}
			}
			if (flag)
			{
				_log?.Invoke("[Registre] Run(s) laissee(s) \"en cours\" par une fermeture precedente : marquee(s) comme abandonnee(s).");
				RequestSave();
			}
		}

		private static void CopyBoolArray(Dictionary<string, object> decoded, string key, bool[] target)
		{
			if (decoded.TryGetValue(key, out var value) && value is List<object> list)
			{
				for (int i = 0; i < target.Length && i < list.Count; i++)
				{
					target[i] = ReflectionHelper.As(list[i], fallback: false);
				}
			}
		}

		public bool MarkEncountered(string cat, int idx)
		{
			bool[] array = cat switch
			{
				"bosses" => BossesFound, 
				"items" => ItemsFound, 
				"guns" => GunsFound, 
				_ => null, 
			};
			if (array == null || idx < 0 || idx >= array.Length)
			{
				return false;
			}
			lock (_lock)
			{
				if (array[idx])
				{
					return false;
				}
				array[idx] = true;
			}
			RequestSave();
			return true;
		}

		public void StartRun(string characterKey, string seed)
		{
			lock (_lock)
			{
				CurrentRun = new RunRecord
				{
					id = Guid.NewGuid().ToString("N"),
					startedAt = NowIso(),
					characterKey = characterKey,
					seed = seed,
					floorsReached = 1,
					result = "in_progress"
				};
			}
			RequestSave();
		}

		public void EndRun(string result, string deathCause)
		{
			lock (_lock)
			{
				if (CurrentRun == null)
				{
					return;
				}
				CurrentRun.endedAt = NowIso();
				CurrentRun.result = result;
				CurrentRun.deathCause = deathCause;
				try
				{
					if (DateTime.TryParse(CurrentRun.startedAt, null, DateTimeStyles.RoundtripKind, out var result2))
					{
						CurrentRun.durationSeconds = (DateTime.UtcNow - result2).TotalSeconds;
					}
				}
				catch
				{
				}
				PastRuns.Add(CurrentRun);
				CurrentRun = null;
			}
			RequestSave();
		}

		public void RequestSave()
		{
			lock (_lock)
			{
				if (_debounceTimer == null)
				{
					_debounceTimer = new Timer(delegate
					{
						SaveNow();
					}, null, 500, -1);
				}
				else
				{
					_debounceTimer.Change(500, -1);
				}
			}
		}

		public void SaveNow()
		{
			if (_saving)
			{
				RequestSave();
				return;
			}
			_saving = true;
			try
			{
				bool[] guns;
				bool[] items;
				bool[] bosses;
				List<RunRecord> list;
				RunRecord currentRun;
				lock (_lock)
				{
					guns = (bool[])GunsFound.Clone();
					items = (bool[])ItemsFound.Clone();
					bosses = (bool[])BossesFound.Clone();
					list = new List<RunRecord>(PastRuns);
					currentRun = CurrentRun;
				}
				LiveProgress liveProgress = new LiveProgress
				{
					guns = guns,
					items = items,
					bosses = bosses,
					writtenAt = NowIso(),
					modVersion = "1.0.0"
				};
				WriteAtomic(LiveProgressPath, MiniJson.Encode(liveProgress.ToDict()));
				List<object> list2 = new List<object>();
				foreach (RunRecord item in list)
				{
					list2.Add(item.ToDict());
				}
				if (currentRun != null)
				{
					list2.Add(currentRun.ToDict());
				}
				Dictionary<string, object> obj = new Dictionary<string, object>
				{
					{ "modVersion", "1.0.0" },
					{
						"writtenAt",
						NowIso()
					},
					{ "runs", list2 }
				};
				WriteAtomic(RunHistoryPath, MiniJson.Encode(obj));
			}
			catch (Exception ex)
			{
				_log?.Invoke("[Registre] Erreur d'ecriture des fichiers de progression : " + ex.Message);
			}
			finally
			{
				_saving = false;
			}
		}

		private static void WriteAtomic(string path, string content)
		{
			string text = path + ".tmp";
			File.WriteAllText(text, content);
			if (File.Exists(path))
			{
				File.Replace(text, path, null);
			}
			else
			{
				File.Move(text, path);
			}
		}
	}
	[HarmonyPatch(typeof(EncounterTrackable), "HandleEncounter")]
	public static class EncounterPatcher
	{
		public const int NoQuality = -1000;

		public static Action<string, int> OnGuidTracked;

		[HarmonyPostfix]
		public static void Postfix(EncounterTrackable __instance)
		{
			//IL_0034: Unknown result type (might be due to invalid IL or missing references)
			//IL_003a: Expected I4, but got Unknown
			try
			{
				string text = (((Object)(object)__instance != (Object)null) ? __instance.EncounterGuid : null);
				if (!string.IsNullOrEmpty(text))
				{
					int arg = -1000;
					PickupObject component = ((Component)__instance).GetComponent<PickupObject>();
					if ((Object)(object)component != (Object)null)
					{
						arg = (int)component.quality;
					}
					OnGuidTracked?.Invoke(text, arg);
				}
			}
			catch
			{
			}
		}
	}
	public struct GuidTarget
	{
		public string cat;

		public int idx;
	}
	public static class GuidMap
	{
		public static Dictionary<string, GuidTarget> Load(string path, Action<string> log)
		{
			Dictionary<string, GuidTarget> dictionary = new Dictionary<string, GuidTarget>();
			try
			{
				if (!File.Exists(path))
				{
					log?.Invoke("[Registre] guid_map_data.json introuvable a cote du mod (" + path + ") - le suivi automatique des armes/objets/boss sera desactive.");
					return dictionary;
				}
				if (!(MiniJson.Decode(File.ReadAllText(path)) is Dictionary<string, object> dictionary2))
				{
					log?.Invoke("[Registre] guid_map_data.json n'a pas pu etre lu (format inattendu).");
					return dictionary;
				}
				foreach (KeyValuePair<string, object> item in dictionary2)
				{
					if (item.Value is Dictionary<string, object> dictionary3 && dictionary3.TryGetValue("cat", out var value) && dictionary3.TryGetValue("idx", out var value2))
					{
						dictionary[item.Key] = new GuidTarget
						{
							cat = (value as string),
							idx = ReflectionHelper.As(value2, -1)
						};
					}
				}
				log?.Invoke("[Registre] guid_map_data.json charge : " + dictionary.Count + " entrees.");
			}
			catch (Exception ex)
			{
				log?.Invoke("[Registre] Erreur de lecture de guid_map_data.json : " + ex.Message);
			}
			return dictionary;
		}
	}
	public static class MiniJson
	{
		public static string Encode(object obj, bool pretty = true)
		{
			StringBuilder stringBuilder = new StringBuilder();
			WriteValue(stringBuilder, obj, pretty, 0);
			return stringBuilder.ToString();
		}

		private static void Indent(StringBuilder sb, int level)
		{
			sb.Append(' ', level * 2);
		}

		private static void WriteValue(StringBuilder sb, object value, bool pretty, int level)
		{
			if (value == null)
			{
				sb.Append("null");
			}
			else if (value is bool flag)
			{
				sb.Append(flag ? "true" : "false");
			}
			else if (value is string s)
			{
				WriteString(sb, s);
			}
			else if (value is int || value is long || value is short)
			{
				sb.Append(Convert.ToInt64(value).ToString(CultureInfo.InvariantCulture));
			}
			else if (value is float || value is double || value is decimal)
			{
				double num = Convert.ToDouble(value, CultureInfo.InvariantCulture);
				if (double.IsNaN(num) || double.IsInfinity(num))
				{
					sb.Append('0');
				}
				else if (num == Math.Floor(num))
				{
					sb.Append(((long)num).ToString(CultureInfo.InvariantCulture));
				}
				else
				{
					sb.Append(num.ToString("R", CultureInfo.InvariantCulture));
				}
			}
			else if (value is IDictionary dict)
			{
				WriteObject(sb, dict, pretty, level);
			}
			else if (value is IEnumerable arr)
			{
				WriteArray(sb, arr, pretty, level);
			}
			else
			{
				WriteString(sb, value.ToString());
			}
		}

		private static void WriteObject(StringBuilder sb, IDictionary dict, bool pretty, int level)
		{
			if (dict.Count == 0)
			{
				sb.Append("{}");
				return;
			}
			sb.Append('{');
			if (pretty)
			{
				sb.Append('\n');
			}
			int num = 0;
			foreach (DictionaryEntry item in dict)
			{
				if (pretty)
				{
					Indent(sb, level + 1);
				}
				WriteString(sb, Convert.ToString(item.Key, CultureInfo.InvariantCulture));
				sb.Append(pretty ? ": " : ":");
				WriteValue(sb, item.Value, pretty, level + 1);
				num++;
				if (num < dict.Count)
				{
					sb.Append(',');
				}
				if (pretty)
				{
					sb.Append('\n');
				}
			}
			if (pretty)
			{
				Indent(sb, level);
			}
			sb.Append('}');
		}

		private static void WriteArray(StringBuilder sb, IEnumerable arr, bool pretty, int level)
		{
			List<object> list = new List<object>();
			foreach (object item in arr)
			{
				list.Add(item);
			}
			if (list.Count == 0)
			{
				sb.Append("[]");
				return;
			}
			sb.Append('[');
			if (pretty)
			{
				sb.Append('\n');
			}
			for (int i = 0; i < list.Count; i++)
			{
				if (pretty)
				{
					Indent(sb, level + 1);
				}
				WriteValue(sb, list[i], pretty, level + 1);
				if (i < list.Count - 1)
				{
					sb.Append(',');
				}
				if (pretty)
				{
					sb.Append('\n');
				}
			}
			if (pretty)
			{
				Indent(sb, level);
			}
			sb.Append(']');
		}

		private static void WriteString(StringBuilder sb, string s)
		{
			sb.Append('"');
			foreach (char c in s)
			{
				switch (c)
				{
				case '"':
					sb.Append("\\\"");
					continue;
				case '\\':
					sb.Append("\\\\");
					continue;
				case '\n':
					sb.Append("\\n");
					continue;
				case '\r':
					sb.Append("\\r");
					continue;
				case '\t':
					sb.Append("\\t");
					continue;
				}
				if (c < ' ')
				{
					StringBuilder stringBuilder = sb.Append("\\u");
					int num = c;
					stringBuilder.Append(num.ToString("x4", CultureInfo.InvariantCulture));
				}
				else
				{
					sb.Append(c);
				}
			}
			sb.Append('"');
		}

		public static object Decode(string json)
		{
			if (string.IsNullOrEmpty(json))
			{
				return null;
			}
			int i = 0;
			return ParseValue(json, ref i);
		}

		private static void SkipWhitespace(string s, ref int i)
		{
			while (i < s.Length && char.IsWhiteSpace(s[i]))
			{
				i++;
			}
		}

		private static object ParseValue(string s, ref int i)
		{
			SkipWhitespace(s, ref i);
			if (i >= s.Length)
			{
				return null;
			}
			switch (s[i])
			{
			case '{':
				return ParseObject(s, ref i);
			case '[':
				return ParseArray(s, ref i);
			case '"':
				return ParseString(s, ref i);
			case 't':
				i += 4;
				return true;
			case 'f':
				i += 5;
				return false;
			case 'n':
				i += 4;
				return null;
			default:
				return ParseNumber(s, ref i);
			}
		}

		private static Dictionary<string, object> ParseObject(string s, ref int i)
		{
			Dictionary<string, object> dictionary = new Dictionary<string, object>();
			i++;
			SkipWhitespace(s, ref i);
			if (i < s.Length && s[i] == '}')
			{
				i++;
				return dictionary;
			}
			while (i < s.Length)
			{
				SkipWhitespace(s, ref i);
				string key = ParseString(s, ref i);
				SkipWhitespace(s, ref i);
				i++;
				object value = ParseValue(s, ref i);
				dictionary[key] = value;
				SkipWhitespace(s, ref i);
				if (i < s.Length && s[i] == ',')
				{
					i++;
					continue;
				}
				if (i < s.Length && s[i] == '}')
				{
					i++;
				}
				break;
			}
			return dictionary;
		}

		private static List<object> ParseArray(string s, ref int i)
		{
			List<object> list = new List<object>();
			i++;
			SkipWhitespace(s, ref i);
			if (i < s.Length && s[i] == ']')
			{
				i++;
				return list;
			}
			while (i < s.Length)
			{
				object item = ParseValue(s, ref i);
				list.Add(item);
				SkipWhitespace(s, ref i);
				if (i < s.Length && s[i] == ',')
				{
					i++;
					continue;
				}
				if (i < s.Length && s[i] == ']')
				{
					i++;
				}
				break;
			}
			return list;
		}

		private static string ParseString(string s, ref int i)
		{
			StringBuilder stringBuilder = new StringBuilder();
			i++;
			while (i < s.Length && s[i] != '"')
			{
				char c = s[i];
				if (c == '\\' && i + 1 < s.Length)
				{
					i++;
					char c2 = s[i];
					switch (c2)
					{
					case '"':
						stringBuilder.Append('"');
						break;
					case '\\':
						stringBuilder.Append('\\');
						break;
					case '/':
						stringBuilder.Append('/');
						break;
					case 'n':
						stringBuilder.Append('\n');
						break;
					case 'r':
						stringBuilder.Append('\r');
						break;
					case 't':
						stringBuilder.Append('\t');
						break;
					case 'b':
						stringBuilder.Append('\b');
						break;
					case 'f':
						stringBuilder.Append('\f');
						break;
					case 'u':
					{
						string value = s.Substring(i + 1, 4);
						stringBuilder.Append((char)Convert.ToInt32(value, 16));
						i += 4;
						break;
					}
					default:
						stringBuilder.Append(c2);
						break;
					}
					i++;
				}
				else
				{
					stringBuilder.Append(c);
					i++;
				}
			}
			i++;
			return stringBuilder.ToString();
		}

		private static object ParseNumber(string s, ref int i)
		{
			int num = i;
			while (i < s.Length && (char.IsDigit(s[i]) || s[i] == '-' || s[i] == '+' || s[i] == '.' || s[i] == 'e' || s[i] == 'E'))
			{
				i++;
			}
			string text = s.Substring(num, i - num);
			if (text.IndexOfAny(new char[3] { '.', 'e', 'E' }) >= 0)
			{
				return double.Parse(text, CultureInfo.InvariantCulture);
			}
			return long.Parse(text, CultureInfo.InvariantCulture);
		}
	}
	public static class RunResults
	{
		public const string InProgress = "in_progress";

		public const string Victory = "victory";

		public const string Death = "death";

		public const string Abandoned = "abandoned";

		public const string ReturnedToHub = "returned_to_hub";
	}
	public class ItemPickupEntry
	{
		public string name;

		public string cat;

		public int idx = -1;

		public int floor;

		public string at;

		public int quality = -1000;

		public Dictionary<string, object> ToDict()
		{
			return new Dictionary<string, object>
			{
				{ "name", name },
				{ "cat", cat },
				{ "idx", idx },
				{ "floor", floor },
				{ "at", at },
				{ "quality", quality }
			};
		}

		public static ItemPickupEntry FromDict(Dictionary<string, object> d)
		{
			ItemPickupEntry itemPickupEntry = new ItemPickupEntry();
			if (d == null)
			{
				return itemPickupEntry;
			}
			itemPickupEntry.name = (d.TryGetValue("name", out var value) ? (value as string) : null);
			itemPickupEntry.cat = (d.TryGetValue("cat", out value) ? (value as string) : null);
			itemPickupEntry.idx = (d.TryGetValue("idx", out value) ? ReflectionHelper.As(value, -1) : (-1));
			itemPickupEntry.floor = (d.TryGetValue("floor", out value) ? ReflectionHelper.As(value, 0) : 0);
			itemPickupEntry.at = (d.TryGetValue("at", out value) ? (value as string) : null);
			itemPickupEntry.quality = (d.TryGetValue("quality", out value) ? ReflectionHelper.As(value, -1000) : (-1000));
			return itemPickupEntry;
		}
	}
	public class BossFightEntry
	{
		public string name;

		public int idx = -1;

		public string startedAt;

		public string endedAt;

		public double durationSeconds;

		public double damageDealt;

		public double damageTaken;

		public string result;

		public Dictionary<string, object> ToDict()
		{
			return new Dictionary<string, object>
			{
				{ "name", name },
				{ "idx", idx },
				{ "startedAt", startedAt },
				{ "endedAt", endedAt },
				{ "durationSeconds", durationSeconds },
				{ "damageDealt", damageDealt },
				{ "damageTaken", damageTaken },
				{ "result", result }
			};
		}

		public static BossFightEntry FromDict(Dictionary<string, object> d)
		{
			BossFightEntry bossFightEntry = new BossFightEntry();
			if (d == null)
			{
				return bossFightEntry;
			}
			bossFightEntry.name = (d.TryGetValue("name", out var value) ? (value as string) : null);
			bossFightEntry.idx = (d.TryGetValue("idx", out value) ? ReflectionHelper.As(value, -1) : (-1));
			bossFightEntry.startedAt = (d.TryGetValue("startedAt", out value) ? (value as string) : null);
			bossFightEntry.endedAt = (d.TryGetValue("endedAt", out value) ? (value as string) : null);
			bossFightEntry.durationSeconds = (d.TryGetValue("durationSeconds", out value) ? ReflectionHelper.As(value, 0.0) : 0.0);
			bossFightEntry.damageDealt = (d.TryGetValue("damageDealt", out value) ? ReflectionHelper.As(value, 0.0) : 0.0);
			bossFightEntry.damageTaken = (d.TryGetValue("damageTaken", out value) ? ReflectionHelper.As(value, 0.0) : 0.0);
			bossFightEntry.result = (d.TryGetValue("result", out value) ? (value as string) : null);
			return bossFightEntry;
		}
	}
	public class LiveStats
	{
		public float currentHealth = -1f;

		public float maxHealth;

		public float currentArmor;

		public int currency;

		public int keys;

		public int blanks;

		public int gunsHeld;

		public int itemsHeld;

		public double avgQuality = -1.0;

		public Dictionary<string, object> ToDict()
		{
			return new Dictionary<string, object>
			{
				{ "currentHealth", currentHealth },
				{ "maxHealth", maxHealth },
				{ "currentArmor", currentArmor },
				{ "currency", currency },
				{ "keys", keys },
				{ "blanks", blanks },
				{ "gunsHeld", gunsHeld },
				{ "itemsHeld", itemsHeld },
				{ "avgQuality", avgQuality }
			};
		}

		public static LiveStats FromDict(Dictionary<string, object> d)
		{
			LiveStats liveStats = new LiveStats();
			if (d == null)
			{
				return liveStats;
			}
			liveStats.currentHealth = (d.TryGetValue("currentHealth", out var value) ? ReflectionHelper.As(value, -1f) : (-1f));
			liveStats.maxHealth = (d.TryGetValue("maxHealth", out value) ? ReflectionHelper.As(value, 0f) : 0f);
			liveStats.currentArmor = (d.TryGetValue("currentArmor", out value) ? ReflectionHelper.As(value, 0f) : 0f);
			liveStats.currency = (d.TryGetValue("currency", out value) ? ReflectionHelper.As(value, 0) : 0);
			liveStats.keys = (d.TryGetValue("keys", out value) ? ReflectionHelper.As(value, 0) : 0);
			liveStats.blanks = (d.TryGetValue("blanks", out value) ? ReflectionHelper.As(value, 0) : 0);
			liveStats.gunsHeld = (d.TryGetValue("gunsHeld", out value) ? ReflectionHelper.As(value, 0) : 0);
			liveStats.itemsHeld = (d.TryGetValue("itemsHeld", out value) ? ReflectionHelper.As(value, 0) : 0);
			liveStats.avgQuality = (d.TryGetValue("avgQuality", out value) ? ReflectionHelper.As(value, -1.0) : (-1.0));
			return liveStats;
		}
	}
	public class RunRecord
	{
		public string id;

		public string startedAt;

		public string endedAt;

		public string characterKey;

		public string seed;

		public int floorsReached;

		public string result = "in_progress";

		public string deathCause;

		public double durationSeconds;

		public int enemiesKilled;

		public double damageDealt;

		public double damageTaken;

		public List<ItemPickupEntry> itemsPickedUp = new List<ItemPickupEntry>();

		public List<BossFightEntry> bossFights = new List<BossFightEntry>();

		public LiveStats liveStats = new LiveStats();

		public Dictionary<string, object> ToDict()
		{
			List<object> list = new List<object>();
			foreach (ItemPickupEntry item in itemsPickedUp)
			{
				list.Add(item.ToDict());
			}
			List<object> list2 = new List<object>();
			foreach (BossFightEntry bossFight in bossFights)
			{
				list2.Add(bossFight.ToDict());
			}
			return new Dictionary<string, object>
			{
				{ "id", id },
				{ "startedAt", startedAt },
				{ "endedAt", endedAt },
				{ "characterKey", characterKey },
				{ "seed", seed },
				{ "floorsReached", floorsReached },
				{ "result", result },
				{ "deathCause", deathCause },
				{ "durationSeconds", durationSeconds },
				{ "enemiesKilled", enemiesKilled },
				{ "damageDealt", damageDealt },
				{ "damageTaken", damageTaken },
				{ "itemsPickedUp", list },
				{ "bossFights", list2 },
				{
					"liveStats",
					liveStats.ToDict()
				}
			};
		}

		public static RunRecord FromDict(Dictionary<string, object> d)
		{
			RunRecord runRecord = new RunRecord();
			if (d == null)
			{
				return runRecord;
			}
			runRecord.id = (d.TryGetValue("id", out var value) ? (value as string) : null);
			runRecord.startedAt = (d.TryGetValue("startedAt", out value) ? (value as string) : null);
			runRecord.endedAt = (d.TryGetValue("endedAt", out value) ? (value as string) : null);
			runRecord.characterKey = (d.TryGetValue("characterKey", out value) ? (value as string) : null);
			runRecord.seed = (d.TryGetValue("seed", out value) ? (value as string) : null);
			runRecord.floorsReached = (d.TryGetValue("floorsReached", out value) ? ReflectionHelper.As(value, 0) : 0);
			runRecord.result = (d.TryGetValue("result", out value) ? ((value as string) ?? "in_progress") : "in_progress");
			runRecord.deathCause = (d.TryGetValue("deathCause", out value) ? (value as string) : null);
			runRecord.durationSeconds = (d.TryGetValue("durationSeconds", out value) ? ReflectionHelper.As(value, 0.0) : 0.0);
			runRecord.enemiesKilled = (d.TryGetValue("enemiesKilled", out value) ? ReflectionHelper.As(value, 0) : 0);
			runRecord.damageDealt = (d.TryGetValue("damageDealt", out value) ? ReflectionHelper.As(value, 0.0) : 0.0);
			runRecord.damageTaken = (d.TryGetValue("damageTaken", out value) ? ReflectionHelper.As(value, 0.0) : 0.0);
			if (d.TryGetValue("itemsPickedUp", out value) && value is List<object> list)
			{
				foreach (object item in list)
				{
					if (item is Dictionary<string, object> d2)
					{
						runRecord.itemsPickedUp.Add(ItemPickupEntry.FromDict(d2));
					}
				}
			}
			if (d.TryGetValue("bossFights", out value) && value is List<object> list2)
			{
				foreach (object item2 in list2)
				{
					if (item2 is Dictionary<string, object> d3)
					{
						runRecord.bossFights.Add(BossFightEntry.FromDict(d3));
					}
				}
			}
			runRecord.liveStats = (d.TryGetValue("liveStats", out value) ? LiveStats.FromDict(value as Dictionary<string, object>) : new LiveStats());
			return runRecord;
		}
	}
	public class LiveProgress
	{
		public bool[] guns;

		public bool[] items;

		public bool[] bosses;

		public string source = "mod-live";

		public string writtenAt;

		public string modVersion;

		public Dictionary<string, object> ToDict()
		{
			List<object> list = new List<object>();
			bool[] array = guns;
			foreach (bool flag in array)
			{
				list.Add(flag);
			}
			List<object> list2 = new List<object>();
			array = items;
			foreach (bool flag2 in array)
			{
				list2.Add(flag2);
			}
			List<object> list3 = new List<object>();
			array = bosses;
			foreach (bool flag3 in array)
			{
				list3.Add(flag3);
			}
			int num = 0;
			array = guns;
			for (int i = 0; i < array.Length; i++)
			{
				if (array[i])
				{
					num++;
				}
			}
			int num2 = 0;
			array = items;
			for (int i = 0; i < array.Length; i++)
			{
				if (array[i])
				{
					num2++;
				}
			}
			int num3 = 0;
			array = bosses;
			for (int i = 0; i < array.Length; i++)
			{
				if (array[i])
				{
					num3++;
				}
			}
			return new Dictionary<string, object>
			{
				{ "guns", list },
				{ "items", list2 },
				{ "bosses", list3 },
				{ "gunsFound", num },
				{ "itemsFound", num2 },
				{ "bossesFound", num3 },
				{ "source", source },
				{ "writtenAt", writtenAt },
				{ "modVersion", modVersion }
			};
		}
	}
	public class NotificationUI : MonoBehaviour
	{
		private class Toast
		{
			public string text;

			public float remaining;
		}

		private const float DISPLAY_SECONDS = 3.5f;

		private const float FADE_SECONDS = 0.6f;

		private readonly Queue<Toast> _queue = new Queue<Toast>();

		private Toast _current;

		private GUIStyle _boxStyle;

		private GUIStyle _textStyle;

		public void Push(string text)
		{
			_queue.Enqueue(new Toast
			{
				text = text,
				remaining = 3.5f
			});
		}

		private void Update()
		{
			if (_current == null && _queue.Count > 0)
			{
				_current = _queue.Dequeue();
			}
			if (_current != null)
			{
				_current.remaining -= Time.unscaledDeltaTime;
				if (_current.remaining <= 0f)
				{
					_current = null;
				}
			}
		}

		private void EnsureStyles()
		{
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			//IL_001e: Expected O, but got Unknown
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			//IL_0026: Expected O, but got Unknown
			//IL_003d: 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_0072: Expected O, but got Unknown
			//IL_007d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0082: Unknown result type (might be due to invalid IL or missing references)
			//IL_008a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0091: Unknown result type (might be due to invalid IL or missing references)
			//IL_009d: Expected O, but got Unknown
			//IL_00b7: Unknown result type (might be due to invalid IL or missing references)
			if (_boxStyle == null)
			{
				_boxStyle = new GUIStyle(GUI.skin.box);
				Texture2D val = new Texture2D(1, 1);
				val.SetPixel(0, 0, new Color(0.07f, 0.06f, 0.05f, 0.92f));
				val.Apply();
				_boxStyle.normal.background = val;
				_boxStyle.border = new RectOffset(4, 4, 4, 4);
				_textStyle = new GUIStyle(GUI.skin.label)
				{
					fontSize = 16,
					alignment = (TextAnchor)3,
					wordWrap = true
				};
				_textStyle.normal.textColor = new Color(0.95f, 0.85f, 0.55f);
			}
		}

		private void OnGUI()
		{
			//IL_0065: 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_00a8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d8: Unknown result type (might be due to invalid IL or missing references)
			if (_current != null)
			{
				EnsureStyles();
				float num = 1f;
				if (_current.remaining < 0.6f)
				{
					num = _current.remaining / 0.6f;
				}
				else if (_current.remaining > 2.9f)
				{
					num = (3.5f - _current.remaining) / 0.6f;
				}
				Color color = GUI.color;
				GUI.color = new Color(1f, 1f, 1f, num);
				float num2 = 360f;
				float num3 = 56f;
				float num4 = (float)Screen.width - num2 - 24f;
				float num5 = 24f;
				GUI.Box(new Rect(num4, num5, num2, num3), string.Empty, _boxStyle);
				GUI.Label(new Rect(num4 + 14f, num5 + 8f, num2 - 28f, num3 - 16f), _current.text, _textStyle);
				GUI.color = color;
			}
		}
	}
	[BepInPlugin("user.registredugungeon.livetracker", "GR - Gungeon Register", "2.7.2")]
	public class Plugin : BaseUnityPlugin
	{
		public const string PluginGuid = "user.registredugungeon.livetracker";

		public const string PluginName = "GR - Gungeon Register";

		public const string PluginVersion = "2.7.2";

		private static readonly string[] DesktopAppExeNames = new string[1] { "RegistreDuGungeon.exe" };

		private Harmony _harmony;

		private DataStore _store;

		private Dictionary<string, GuidTarget> _guidMap;

		private NotificationUI _notifications;

		private ConfigEntry<string> _dataDirConfig;

		private ConfigEntry<bool> _notificationsEnabledConfig;

		private ConfigEntry<bool> _autoLaunchAppConfig;

		private void Awake()
		{
			//IL_00dc: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e2: Expected O, but got Unknown
			//IL_00f9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fe: Unknown result type (might be due to invalid IL or missing references)
			//IL_0104: Expected O, but got Unknown
			//IL_012b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0130: Unknown result type (might be due to invalid IL or missing references)
			//IL_0136: Expected O, but got Unknown
			//IL_01b2: Unknown result type (might be due to invalid IL or missing references)
			//IL_01bc: Expected O, but got Unknown
			((BaseUnityPlugin)this).Logger.LogInfo((object)"[Registre] Demarrage du mod GR - Gungeon Register v2.7.2");
			_dataDirConfig = ((BaseUnityPlugin)this).Config.Bind<string>("General", "DataDirectory", DefaultDataDir(), "Dossier ou ecrire live_progress.json et run_history.json. Par defaut, le meme dossier deja utilise par l'application de bureau Le Registre du Gungeon.");
			_notificationsEnabledConfig = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "NotificationsEnabled", true, "Afficher un petit popup a l'ecran lors d'une nouvelle decouverte (arme/objet/boss) ou d'un debut de combat de boss.");
			_autoLaunchAppConfig = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "AutoLaunchApp", true, "Lancer automatiquement RegistreDuGungeon.exe (l'application de bureau) au demarrage du jeu, si cet exe est trouve juste a cote du DLL de ce mod. Mettre a false pour desactiver si tu preferes la lancer toi-meme.");
			try
			{
				string path = Path.Combine(Path.GetDirectoryName(((BaseUnityPlugin)this).Info.Location), "guid_map_data.json");
				_guidMap = GuidMap.Load(path, delegate(string msg)
				{
					((BaseUnityPlugin)this).Logger.LogWarning((object)msg);
				});
				PluginGuidMap.Map = _guidMap;
				_store = new DataStore(_dataDirConfig.Value, delegate(string msg)
				{
					((BaseUnityPlugin)this).Logger.LogInfo((object)msg);
				});
				GameObject val = new GameObject("RegistreDuGungeon_Notifications");
				Object.DontDestroyOnLoad((Object)(object)val);
				_notifications = val.AddComponent<NotificationUI>();
				GameObject val2 = new GameObject("RegistreDuGungeon_RunMonitor");
				Object.DontDestroyOnLoad((Object)val2);
				RunMonitor runMonitor = val2.AddComponent<RunMonitor>();
				runMonitor.Store = _store;
				runMonitor.Log = delegate(string msg)
				{
					((BaseUnityPlugin)this).Logger.LogInfo((object)msg);
				};
				GameObject val3 = new GameObject("RegistreDuGungeon_ProgressPoller");
				Object.DontDestroyOnLoad((Object)val3);
				ProgressPoller progressPoller = val3.AddComponent<ProgressPoller>();
				progressPoller.Store = _store;
				progressPoller.GuidMap = _guidMap;
				progressPoller.Log = delegate(string msg)
				{
					((BaseUnityPlugin)this).Logger.LogInfo((object)msg);
				};
				EncounterPatcher.OnGuidTracked = HandleGuidTracked;
				SessionPatches.Store = _store;
				SessionPatches.Notifications = (_notificationsEnabledConfig.Value ? _notifications : null);
				SessionPatches.Log = delegate(string msg)
				{
					((BaseUnityPlugin)this).Logger.LogInfo((object)msg);
				};
				_harmony = new Harmony("user.registredugungeon.livetracker");
				ApplyPatchesIndividually();
				_store.SaveNow();
				((BaseUnityPlugin)this).Logger.LogInfo((object)("[Registre] Mod initialise. Donnees ecrites dans : " + _dataDirConfig.Value));
				if (_autoLaunchAppConfig.Value)
				{
					try
					{
						LaunchDesktopAppIfNeeded();
						return;
					}
					catch (Exception ex)
					{
						((BaseUnityPlugin)this).Logger.LogWarning((object)("[Registre] Erreur lors du lancement automatique de l'application de bureau (non bloquant) : " + ex.Message));
						return;
					}
				}
			}
			catch (TypeLoadException ex2)
			{
				((BaseUnityPlugin)this).Logger.LogError((object)("[Registre] Erreur au demarrage du mod (TypeLoadException) : " + ex2.Message + " | Type concerne : " + ex2.TypeName));
			}
			catch (Exception ex3)
			{
				((BaseUnityPlugin)this).Logger.LogError((object)("[Registre] Erreur au demarrage du mod : " + ex3));
			}
		}

		private void ApplyPatchesIndividually()
		{
			Type[] array;
			try
			{
				array = Assembly.GetExecutingAssembly().GetTypes();
			}
			catch (ReflectionTypeLoadException ex)
			{
				array = ex.Types.Where((Type t) => (object)t != null).ToArray();
				((BaseUnityPlugin)this).Logger.LogWarning((object)("[Registre] Certains types du mod n'ont pas pu etre charges (ignores) : " + string.Join(", ", ex.LoaderExceptions.Select((Exception le) => le?.Message).ToArray())));
			}
			Type[] array2 = array;
			foreach (Type type in array2)
			{
				bool flag;
				try
				{
					flag = type.GetCustomAttributes(typeof(HarmonyPatch), inherit: true).Any();
				}
				catch (Exception ex2)
				{
					((BaseUnityPlugin)this).Logger.LogWarning((object)("[Registre] Impossible d'inspecter la classe " + type.FullName + " (probablement un point d'accroche perime pour cette version du jeu) : " + ex2.GetType().Name + " - " + ex2.Message));
					continue;
				}
				if (flag)
				{
					try
					{
						_harmony.CreateClassProcessor(type).Patch();
						((BaseUnityPlugin)this).Logger.LogInfo((object)("[Registre] Patch applique : " + type.FullName));
					}
					catch (Exception ex3)
					{
						((BaseUnityPlugin)this).Logger.LogWarning((object)("[Registre] Echec du patch " + type.FullName + " (fonctionnalite desactivee pour cette session) : " + ex3.Message));
					}
				}
			}
		}

		private void HandleGuidTracked(string guid, int quality)
		{
			try
			{
				if (_guidMap != null && _guidMap.TryGetValue(guid, out var target))
				{
					if (_store.MarkEncountered(target.cat, target.idx) && _notificationsEnabledConfig.Value)
					{
						string text = ((target.cat == "guns") ? "Nouvelle arme" : ((target.cat == "items") ? "Nouvel objet" : "Boss rencontre"));
						_notifications?.Push(text + " decouvert(e) !");
					}
					if (_store.CurrentRun != null && (target.cat == "guns" || target.cat == "items") && !_store.CurrentRun.itemsPickedUp.Any((ItemPickupEntry it) => it.cat == target.cat && it.idx == target.idx))
					{
						_store.CurrentRun.itemsPickedUp.Add(new ItemPickupEntry
						{
							name = guid,
							cat = target.cat,
							idx = target.idx,
							floor = _store.CurrentRun.floorsReached,
							at = DateTime.UtcNow.ToString("yyyy-MM-ddTHH:mm:ssZ"),
							quality = quality
						});
					}
					_store.RequestSave();
				}
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)("[Registre] Erreur de traitement d'une decouverte : " + ex.Message));
			}
		}

		private void LaunchDesktopAppIfNeeded()
		{
			string directoryName = Path.GetDirectoryName(((BaseUnityPlugin)this).Info.Location);
			if (string.IsNullOrEmpty(directoryName))
			{
				return;
			}
			string text = null;
			string[] desktopAppExeNames = DesktopAppExeNames;
			foreach (string path in desktopAppExeNames)
			{
				string text2 = Path.Combine(directoryName, path);
				if (File.Exists(text2))
				{
					text = text2;
					break;
				}
			}
			if (text == null)
			{
				((BaseUnityPlugin)this).Logger.LogInfo((object)("[Registre] Application de bureau introuvable a cote du mod (dossier : " + directoryName + "), lancement automatique ignore."));
				return;
			}
			if (Process.GetProcessesByName(Path.GetFileNameWithoutExtension(text)).Length != 0)
			{
				((BaseUnityPlugin)this).Logger.LogInfo((object)"[Registre] L'application de bureau tourne deja, lancement automatique ignore.");
				return;
			}
			((BaseUnityPlugin)this).Logger.LogInfo((object)("[Registre] Lancement automatique de l'application de bureau : " + text));
			Process.Start(new ProcessStartInfo
			{
				FileName = text,
				WorkingDirectory = directoryName,
				UseShellExecute = true
			});
		}

		private void OnApplicationQuit()
		{
			try
			{
				if (_store != null && _store.CurrentRun != null)
				{
					_store.EndRun("abandoned", null);
					((BaseUnityPlugin)this).Logger.LogInfo((object)"[Registre] Run encore en cours a la fermeture du jeu (Quitter vers le bureau) : marquee comme abandonnee.");
				}
				_store?.SaveNow();
			}
			catch
			{
			}
		}

		private static string DefaultDataDir()
		{
			return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "RegistreDuGungeon");
		}
	}
	public class ProgressPoller : MonoBehaviour
	{
		public DataStore Store;

		public Dictionary<string, GuidTarget> GuidMap;

		public Action<string> Log;

		public float PollIntervalSeconds = 1f;

		private FieldInfo _dictField;

		private bool _fieldLookupAttempted;

		private float _timer;

		private void Update()
		{
			_timer += Time.unscaledDeltaTime;
			if (_timer < PollIntervalSeconds)
			{
				return;
			}
			_timer = 0f;
			try
			{
				PollOnce();
			}
			catch (Exception ex)
			{
				Log("[Registre] Erreur lors du sondage de progression (non bloquante) : " + ex.Message);
			}
		}

		private void PollOnce()
		{
			if (!GameStatsManager.HasInstance)
			{
				return;
			}
			if (!_fieldLookupAttempted)
			{
				_fieldLookupAttempted = true;
				_dictField = typeof(GameStatsManager).GetField("m_encounteredTrackables", BindingFlags.Instance | BindingFlags.NonPublic);
				if ((object)_dictField == null)
				{
					Log("[Registre] Champ m_encounteredTrackables introuvable sur GameStatsManager : le rattrapage periodique de progression est desactive (les notifications instantanees continuent de fonctionner si elles marchent).");
					return;
				}
			}
			if ((object)_dictField == null || !(_dictField.GetValue(GameStatsManager.Instance) is IDictionary dictionary))
			{
				return;
			}
			foreach (DictionaryEntry item in dictionary)
			{
				string text = item.Key as string;
				if (!string.IsNullOrEmpty(text))
				{
					object? value = item.Value;
					EncounteredObjectData val = (EncounteredObjectData)((value is EncounteredObjectData) ? value : null);
					if (val != null && val.encounterCount > 0 && GuidMap != null && GuidMap.TryGetValue(text, out var value2))
					{
						Store.MarkEncountered(value2.cat, value2.idx);
					}
				}
			}
		}
	}
	public static class ReflectionHelper
	{
		private const BindingFlags AllInstance = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;

		private const BindingFlags AllStatic = BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic;

		public static bool TryGetMember(object instance, string[] candidateNames, out object value)
		{
			value = null;
			if (instance == null)
			{
				return false;
			}
			Type type = instance.GetType();
			foreach (string name in candidateNames)
			{
				FieldInfo field = type.GetField(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
				if ((object)field != null)
				{
					value = field.GetValue(instance);
					return true;
				}
				PropertyInfo property = type.GetProperty(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
				if ((object)property != null && property.CanRead)
				{
					try
					{
						value = property.GetValue(instance, null);
						return true;
					}
					catch
					{
					}
				}
			}
			return false;
		}

		public static bool TryGetStaticMember(Type type, string[] candidateNames, out object value)
		{
			value = null;
			if ((object)type == null)
			{
				return false;
			}
			foreach (string name in candidateNames)
			{
				FieldInfo field = type.GetField(name, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
				if ((object)field != null)
				{
					value = field.GetValue(null);
					return true;
				}
				PropertyInfo property = type.GetProperty(name, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
				if ((object)property != null && property.CanRead)
				{
					try
					{
						value = property.GetValue(null, null);
						return true;
					}
					catch
					{
					}
				}
			}
			return false;
		}

		public static bool TryCallMethod(object instance, string[] candidateNames, object[] args, out object result)
		{
			result = null;
			if (instance == null)
			{
				return false;
			}
			Type type = instance.GetType();
			foreach (string name in candidateNames)
			{
				MethodInfo method = type.GetMethod(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
				if ((object)method != null)
				{
					try
					{
						result = method.Invoke(instance, args);
						return true;
					}
					catch
					{
					}
				}
			}
			return false;
		}

		public static T As<T>(object value, T fallback)
		{
			if (value == null)
			{
				return fallback;
			}
			try
			{
				return (T)Convert.ChangeType(value, typeof(T));
			}
			catch
			{
				return fallback;
			}
		}

		public static string SafeToString(object value, string fallback = "")
		{
			if (value != null)
			{
				return value.ToString();
			}
			return fallback;
		}
	}
	public class RunMonitor : MonoBehaviour
	{
		public DataStore Store;

		public Action<string> Log;

		public float PollIntervalSeconds = 1f;

		private float _timer;

		private static readonly Dictionary<int, int> QualityScore = new Dictionary<int, int>
		{
			{ 0, 1 },
			{ 1, 2 },
			{ 2, 3 },
			{ 3, 4 },
			{ 4, 5 },
			{ 5, 6 }
		};

		private void Update()
		{
			_timer += Time.unscaledDeltaTime;
			if (_timer < PollIntervalSeconds)
			{
				return;
			}
			_timer = 0f;
			try
			{
				if (Store.CurrentRun != null && GameManager.HasInstance)
				{
					int currentFloor = GameManager.Instance.CurrentFloor;
					if (currentFloor > Store.CurrentRun.floorsReached)
					{
						Store.CurrentRun.floorsReached = currentFloor;
					}
					UpdateLiveStats();
					Store.RequestSave();
				}
			}
			catch (Exception ex)
			{
				Log("[Registre] Erreur de suivi d'etage (non bloquante) : " + ex.Message);
			}
		}

		private void UpdateLiveStats()
		{
			PlayerController value = Traverse.Create((object)GameManager.Instance).Field("m_player").GetValue<PlayerController>();
			if ((Object)(object)value == (Object)null)
			{
				return;
			}
			LiveStats liveStats = Store.CurrentRun.liveStats;
			try
			{
				HealthHaver component = ((Component)value).GetComponent<HealthHaver>();
				if ((Object)(object)component != (Object)null)
				{
					Traverse val = Traverse.Create((object)component);
					liveStats.currentHealth = val.Field("currentHealth").GetValue<float>();
					liveStats.maxHealth = val.Field("maximumHealth").GetValue<float>();
					liveStats.currentArmor = val.Field("currentArmor").GetValue<float>();
				}
			}
			catch (Exception ex)
			{
				Log("[Registre] Erreur de sondage des PV (non bloquante) : " + ex.Message);
			}
			try
			{
				Traverse val2 = Traverse.Create((object)value);
				PlayerConsumables value2 = val2.Field("carriedConsumables").GetValue<PlayerConsumables>();
				if (value2 != null)
				{
					Traverse val3 = Traverse.Create((object)value2);
					liveStats.currency = val3.Field("m_currency").GetValue<int>();
					liveStats.keys = val3.Field("m_keyBullets").GetValue<int>();
				}
				liveStats.blanks = val2.Field("m_blanks").GetValue<int>();
			}
			catch (Exception ex2)
			{
				Log("[Registre] Erreur de sondage des consommables (non bloquante) : " + ex2.Message);
			}
			try
			{
				List<int> list = new List<int>();
				int gunsHeld = 0;
				int num = 0;
				Traverse val4 = Traverse.Create((object)value);
				GunInventory value3 = val4.Field("inventory").GetValue<GunInventory>();
				if (value3 != null)
				{
					List<Gun> value4 = Traverse.Create((object)value3).Field("m_guns").GetValue<List<Gun>>();
					if (value4 != null)
					{
						gunsHeld = value4.Count;
						foreach (Gun item in value4)
						{
							AddQuality(list, (PickupObject)(object)item);
						}
					}
				}
				List<PlayerItem> value5 = val4.Field("activeItems").GetValue<List<PlayerItem>>();
				if (value5 != null)
				{
					num += value5.Count;
					foreach (PlayerItem item2 in value5)
					{
						AddQuality(list, (PickupObject)(object)item2);
					}
				}
				List<PassiveItem> value6 = val4.Field("passiveItems").GetValue<List<PassiveItem>>();
				if (value6 != null)
				{
					num += value6.Count;
					foreach (PassiveItem item3 in value6)
					{
						AddQuality(list, (PickupObject)(object)item3);
					}
				}
				liveStats.gunsHeld = gunsHeld;
				liveStats.itemsHeld = num;
				liveStats.avgQuality = ((list.Count > 0) ? list.Average() : (-1.0));
			}
			catch (Exception ex3)
			{
				Log("[Registre] Erreur de sondage de l'inventaire (non bloquante) : " + ex3.Message);
			}
		}

		private static void AddQuality(List<int> qualities, PickupObject pickup)
		{
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			//IL_001c: Expected I4, but got Unknown
			if (!((Object)(object)pickup == (Object)null) && QualityScore.TryGetValue((int)pickup.quality, out var value))
			{
				qualities.Add(value);
			}
		}
	}
	public static class SessionPatches
	{
		[HarmonyPatch(typeof(GameStatsManager), "BeginNewSession")]
		public static class BeginSessionPatch
		{
			[HarmonyPostfix]
			public static void Postfix(PlayerController player)
			{
				//IL_0011: Unknown result type (might be due to invalid IL or missing references)
				try
				{
					string text = (((Object)(object)player != (Object)null) ? NormalizeCharacterKey(player.characterIdentity) : "unknown");
					string text2 = "unknown";
					if (GameManager.HasInstance)
					{
						text2 = GameManager.Instance.CurrentRunSeed.ToString(CultureInfo.InvariantCulture);
					}
					Store.StartRun(text, text2);
					Log("[Registre] Nouveau run demarre (GameStatsManager.BeginNewSession) : personnage=" + text + ", seed=" + text2);
				}
				catch (Exception ex)
				{
					Log("[Registre] Erreur au demarrage de run : " + ex.Message);
				}
			}
		}

		[HarmonyPatch(typeof(GameStatsManager), "EndSession")]
		public static class EndSessionPatch
		{
			[HarmonyPrefix]
			public static void Prefix(bool recordSessionStats, bool decrementDifferentiator)
			{
				try
				{
					if (Store.CurrentRun != null)
					{
						if (GameManager.HasInstance)
						{
							Store.CurrentRun.floorsReached = Math.Max(Store.CurrentRun.floorsReached, GameManager.Instance.CurrentFloor);
						}
						if (GameStatsManager.HasInstance)
						{
							GameStatsManager instance = GameStatsManager.Instance;
							Store.CurrentRun.enemiesKilled = (int)instance.GetSessionStatValue((TrackedStats)22);
							Store.CurrentRun.durationSeconds = instance.GetSessionStatValue((TrackedStats)23);
						}
						string text = ((Store.CurrentRun.result == "in_progress") ? "returned_to_hub" : Store.CurrentRun.result);
						Store.EndRun(text, Store.CurrentRun.deathCause);
						Log("[Registre] Fin de run (GameStatsManager.EndSession) : resultat=" + text);
					}
				}
				catch (Exception ex)
				{
					Log("[Registre] Erreur a la fin de run : " + ex.Message);
				}
			}
		}

		[HarmonyPatch(typeof(HealthHaver), "Start")]
		public static class HealthHaverStartPatch
		{
			[HarmonyPostfix]
			public static void Postfix(HealthHaver __instance)
			{
				try
				{
					if (Store.CurrentRun != null && !((Object)(object)__instance == (Object)null) && __instance.IsBoss && !ActiveBossFights.ContainsKey(__instance))
					{
						string text = (((Object)(object)((Component)__instance).gameObject != (Object)null) ? ((Object)((Component)__instance).gameObject).name : "Boss");
						int idx = -1;
						EncounterTrackable component = ((Component)__instance).GetComponent<EncounterTrackable>();
						if ((Object)(object)component != (Object)null && !string.IsNullOrEmpty(component.EncounterGuid) && PluginGuidMap.Map != null && PluginGuidMap.Map.TryGetValue(component.EncounterGuid, out var value) && value.cat == "bosses")
						{
							idx = value.idx;
						}
						BossFightEntry bossFightEntry = new BossFightEntry
						{
							name = text,
							idx = idx,
							startedAt = NowIso(),
							result = "unknown"
						};
						Store.CurrentRun.bossFights.Add(bossFightEntry);
						ActiveBossFights[__instance] = bossFightEntry;
						Notifications?.Push("Combat de boss : " + text);
						Log("[Registre] Debut de combat de boss detecte : " + text);
						Store.RequestSave();
					}
				}
				catch (Exception ex)
				{
					Log("[Registre] Erreur au debut de combat de boss : " + ex.Message);
				}
			}
		}

		[HarmonyPatch(typeof(HealthHaver), "ApplyDamage")]
		public static class HealthHaverDamagePatch
		{
			[HarmonyPostfix]
			public static void Postfix(HealthHaver __instance, float damage)
			{
				try
				{
					if (Store.CurrentRun == null || (Object)(object)__instance == (Object)null || damage <= 0f)
					{
						return;
					}
					if (__instance.IsBoss && ActiveBossFights.TryGetValue(__instance, out var value))
					{
						value.damageDealt += damage;
					}
					else
					{
						if (!IsPlayerCharacterSafe(__instance))
						{
							return;
						}
						Store.CurrentRun.damageTaken += damage;
						if (ActiveBossFights.Count <= 0)
						{
							return;
						}
						float num = damage / (float)ActiveBossFights.Count;
						{
							foreach (KeyValuePair<HealthHaver, BossFightEntry> activeBossFight in ActiveBossFights)
							{
								activeBossFight.Value.damageTaken += num;
							}
							return;
						}
					}
				}
				catch
				{
				}
			}
		}

		[HarmonyPatch(typeof(HealthHaver), "Die")]
		public static class HealthHaverDiePatch
		{
			[HarmonyPostfix]
			public static void Postfix(HealthHaver __instance)
			{
				try
				{
					if ((Object)(object)__instance == (Object)null)
					{
						return;
					}
					if (__instance.IsBoss && ActiveBossFights.TryGetValue(__instance, out var value))
					{
						value.endedAt = NowIso();
						value.result = "won";
						try
						{
							if (DateTime.TryParse(value.startedAt, null, DateTimeStyles.RoundtripKind, out var result))
							{
								value.durationSeconds = (DateTime.UtcNow - result).TotalSeconds;
							}
						}
						catch
						{
						}
						ActiveBossFights.Remove(__instance);
						Log("[Registre] Fin de combat de boss : " + value.name + " (" + value.durationSeconds.ToString("F0") + "s).");
						if (value.idx == 17 && Store.CurrentRun != null)
						{
							CloseStaleBossFights("won");
							Store.EndRun("victory", null);
							Log("[Registre] Liche vaincue : run cloturee en victoire.");
						}
						else
						{
							Store.RequestSave();
						}
					}
					else if (IsPlayerCharacterSafe(__instance) && Store.CurrentRun != null)
					{
						CloseStaleBossFights("lost");
						Store.EndRun("death", "Mort en jeu");
						Log("[Registre] Mort du joueur detectee (HealthHaver.Die) : run cloture.");
					}
				}
				catch (Exception ex)
				{
					Log("[Registre] Erreur a la mort/fin de combat : " + ex.Message);
				}
			}
		}

		public static DataStore Store;

		public static NotificationUI Notifications;

		public static Action<string> Log;

		private static readonly Dictionary<HealthHaver, BossFightEntry> ActiveBossFights = new Dictionary<HealthHaver, BossFightEntry>();

		private const int LICH_BOSS_IDX = 17;

		private static FieldInfo _isPlayerCharacterField;

		private static bool _isPlayerCharacterFieldLookupAttempted;

		private static string NowIso()
		{
			return DateTime.UtcNow.ToString("yyyy-MM-ddTHH:mm:ssZ", CultureInfo.InvariantCulture);
		}

		private unsafe static string NormalizeCharacterKey(PlayableCharacters id)
		{
			return ((object)(*(PlayableCharacters*)(&id))/*cast due to .constrained prefix*/).ToString().ToLowerInvariant();
		}

		private static bool IsPlayerCharacterSafe(HealthHaver instance)
		{
			if (instance == null)
			{
				return false;
			}
			if (!_isPlayerCharacterFieldLookupAttempted)
			{
				_isPlayerCharacterFieldLookupAttempted = true;
				_isPlayerCharacterField = typeof(HealthHaver).GetField("isPlayerCharacter", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
			}
			if ((object)_isPlayerCharacterField == null)
			{
				return false;
			}
			try
			{
				return (bool)_isPlayerCharacterField.GetValue(instance);
			}
			catch
			{
				return false;
			}
		}

		private static void CloseStaleBossFights(string fallbackResult)
		{
			foreach (BossFightEntry value in ActiveBossFights.Values)
			{
				if (!string.IsNullOrEmpty(value.endedAt))
				{
					continue;
				}
				value.endedAt = NowIso();
				value.result = fallbackResult;
				try
				{
					if (DateTime.TryParse(value.startedAt, null, DateTimeStyles.RoundtripKind, out var result))
					{
						value.durationSeconds = (DateTime.UtcNow - result).TotalSeconds;
					}
				}
				catch
				{
				}
			}
			ActiveBossFights.Clear();
		}
	}
	public static class PluginGuidMap
	{
		public static Dictionary<string, GuidTarget> Map;
	}
}