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;
}
}