using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Text; using BepInEx; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using Photon.Pun; using RepoSteamNetworking.API; using RepoSteamNetworking.Networking; using RepoSteamNetworking.Networking.Serialization; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("ValuablesCounted")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyDescription("R.E.P.O mod that counts the valuables dropped by monsters.")] [assembly: AssemblyFileVersion("1.1.0.0")] [assembly: AssemblyInformationalVersion("1.1.0+1135855c7ddc26cc7ae1520f154ff015be220bff")] [assembly: AssemblyProduct("ValuablesCounted")] [assembly: AssemblyTitle("ValuablesCounted")] [assembly: AssemblyVersion("1.1.0.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } [CompilerGenerated] [Microsoft.CodeAnalysis.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 ValuablesCounted { [HarmonyPatch(typeof(EnemyParent))] internal static class EnemyParentPatch { [HarmonyPostfix] [HarmonyPatch("SpawnRPC")] private static void SpawnRPCPostfix(EnemyParent __instance) { Plugin.DropTracker.RegisterSpawn(__instance); } [HarmonyPrefix] [HarmonyPatch("Despawn")] private static void DespawnPrefix(EnemyParent __instance, out int __state) { __state = MonsterDropTracker.GetSpawnedValuableCount(__instance); } [HarmonyPostfix] [HarmonyPatch("Despawn")] private static void DespawnPostfix(EnemyParent __instance, int __state) { int spawnedValuableCount = MonsterDropTracker.GetSpawnedValuableCount(__instance); int num = spawnedValuableCount - __state; if (num > 0) { Plugin.DropTracker.RecordDrops(__instance, num); } } } [HarmonyPatch(typeof(LevelGenerator), "GenerateDone")] internal static class LevelGeneratorPatch { private static void Prefix() { Plugin.DropTracker.Reset(); Plugin.Log.LogInfo((object)"New map detected. Valuable drop tracker reset."); } } public sealed class MonsterDropRecord { public int Id { get; } public string Name { get; } public int MaxDrops { get; } public int Drops { get; private set; } public int RemainingDrops { get { if (MaxDrops - Drops >= 0) { return MaxDrops - Drops; } return 0; } } public MonsterDropRecord(int id, string name, int maxDrops) { Id = id; Name = name; MaxDrops = maxDrops; } public void AddDrops(int amount) { Drops += amount; } } internal sealed class MonsterDropSyncPacket : NetworkPacket { public int ViewId { get; set; } public int Amount { get; set; } protected override void WriteData(SocketMessage socketMessage) { socketMessage.Write(ViewId); socketMessage.Write(Amount); } protected override void ReadData(SocketMessage socketMessage) { ViewId = socketMessage.Read(); Amount = socketMessage.Read(); } } public sealed class MonsterDropTracker { private readonly Dictionary records = new Dictionary(); public int TotalDrops => records.Values.Sum((MonsterDropRecord record) => record.Drops); public int TotalPossibleDrops => records.Values.Sum((MonsterDropRecord record) => record.MaxDrops); public int TotalRemainingDrops => records.Values.Sum((MonsterDropRecord record) => record.RemainingDrops); public IReadOnlyCollection Records => (IReadOnlyCollection)(object)records.Values.ToArray(); public void Reset() { records.Clear(); } public void RegisterSpawn(EnemyParent enemyParent) { int id = GetId(enemyParent); if (id != 0 && !records.ContainsKey(id)) { if (!CanSpawnValuables(enemyParent)) { Plugin.Log.LogInfo((object)("Ignoring monster with no valuable drops: " + GetMonsterName(enemyParent) + ".")); return; } MonsterDropRecord monsterDropRecord = new MonsterDropRecord(id, GetMonsterName(enemyParent), GetMaxDrops(enemyParent)); records.Add(id, monsterDropRecord); Plugin.Log.LogInfo((object)$"Tracking monster: {monsterDropRecord.Name}. Monster remaining: {monsterDropRecord.RemainingDrops}/{monsterDropRecord.MaxDrops}. Run remaining: {TotalRemainingDrops}/{TotalPossibleDrops}."); } } public void RecordDrops(EnemyParent enemyParent, int amount, bool broadcast = true) { int id = GetId(enemyParent); if (id != 0 && records.TryGetValue(id, out MonsterDropRecord value)) { value.AddDrops(amount); Plugin.Log.LogInfo((object)$"{value.Name} dropped {amount} valuable(s). Monster remaining: {value.RemainingDrops}/{value.MaxDrops}. Run remaining: {TotalRemainingDrops}/{TotalPossibleDrops}. Dropped total: {TotalDrops}."); if (broadcast) { MultiplayerDropSync.BroadcastDrops(enemyParent, amount); } } } public MonsterDropRecord? GetRecord(EnemyParent enemyParent) { int id = GetId(enemyParent); if (id == 0 || !records.TryGetValue(id, out MonsterDropRecord value)) { return null; } return value; } public static int GetSpawnedValuableCount(EnemyParent enemyParent) { EnemyHealth enemyHealth = GetEnemyHealth(enemyParent); if (!((Object)(object)enemyHealth == (Object)null)) { return Traverse.Create((object)enemyHealth).Field("spawnValuableCurrent").Value; } return 0; } private static int GetMaxDrops(EnemyParent enemyParent) { EnemyHealth enemyHealth = GetEnemyHealth(enemyParent); if (!((Object)(object)enemyHealth == (Object)null)) { return Traverse.Create((object)enemyHealth).Field("spawnValuableMax").Value; } return 0; } private static bool CanSpawnValuables(EnemyParent enemyParent) { EnemyHealth enemyHealth = GetEnemyHealth(enemyParent); if ((Object)(object)enemyHealth != (Object)null) { return Traverse.Create((object)enemyHealth).Field("spawnValuable").Value; } return false; } private static EnemyHealth? GetEnemyHealth(EnemyParent enemyParent) { Enemy value = Traverse.Create((object)enemyParent).Field("Enemy").Value; if (!((Object)(object)value == (Object)null)) { return Traverse.Create((object)value).Field("Health").Value; } return null; } private static int GetId(EnemyParent enemyParent) { if (!((Object)(object)enemyParent == (Object)null)) { return ((Object)enemyParent).GetInstanceID(); } return 0; } public static string GetMonsterName(EnemyParent enemyParent) { string value = Traverse.Create((object)enemyParent).Field("enemyName").Value; if (!string.IsNullOrWhiteSpace(value)) { return value; } if (!((Object)(object)enemyParent == (Object)null)) { return ((Object)enemyParent).name; } return "Unknown Monster"; } } internal static class MultiplayerDropSync { public static void Initialize() { RepoSteamNetwork.RegisterPacket(); RepoSteamNetwork.AddCallback((Action)OnMonsterDropSync); } public static void Dispose() { RepoSteamNetwork.RemoveCallback((Action)OnMonsterDropSync); } public static void BroadcastDrops(EnemyParent enemyParent, int amount) { if (GameManager.Multiplayer() && SemiFunc.IsMasterClientOrSingleplayer()) { PhotonView photonView = GetPhotonView(enemyParent); if ((Object)(object)photonView == (Object)null) { Plugin.Log.LogWarning((object)("Could not sync drops for " + MonsterDropTracker.GetMonsterName(enemyParent) + " because no PhotonView was found.")); return; } RepoSteamNetwork.SendPacket(new MonsterDropSyncPacket { ViewId = photonView.ViewID, Amount = amount }, (NetworkDestination)1); } } private static void OnMonsterDropSync(MonsterDropSyncPacket packet) { if (!SemiFunc.IsMasterClientOrSingleplayer()) { PhotonView val = PhotonView.Find(packet.ViewId); EnemyHealth enemyHealth = (((Object)(object)val == (Object)null) ? null : ((Component)val).GetComponent()); EnemyParent enemyParent = GetEnemyParent(enemyHealth); if ((Object)(object)enemyParent == (Object)null) { Plugin.Log.LogWarning((object)$"Received drop sync for missing PhotonView {packet.ViewId}."); } else { Plugin.DropTracker.RecordDrops(enemyParent, packet.Amount, broadcast: false); } } } private static PhotonView? GetPhotonView(EnemyParent enemyParent) { EnemyHealth enemyHealth = GetEnemyHealth(enemyParent); if (!((Object)(object)enemyHealth == (Object)null)) { return Traverse.Create((object)enemyHealth).Field("photonView").Value; } return null; } private static EnemyHealth? GetEnemyHealth(EnemyParent enemyParent) { Enemy value = Traverse.Create((object)enemyParent).Field("Enemy").Value; if (!((Object)(object)value == (Object)null)) { return Traverse.Create((object)value).Field("Health").Value; } return null; } private static EnemyParent? GetEnemyParent(EnemyHealth? enemyHealth) { if ((Object)(object)enemyHealth == (Object)null) { return null; } Enemy value = Traverse.Create((object)enemyHealth).Field("enemy").Value; if (!((Object)(object)value == (Object)null)) { return Traverse.Create((object)value).Field("EnemyParent").Value; } return null; } } [BepInPlugin("valuablescounted", "ValuablesCounted", "1.1.0")] [BepInProcess("REPO.exe")] public sealed class Plugin : BaseUnityPlugin { public const string PluginGuid = "valuablescounted"; public const string PluginName = "ValuablesCounted"; public const string PluginVersion = "1.1.0"; private readonly Harmony harmony = new Harmony("valuablescounted"); internal static ManualLogSource Log { get; private set; } = null; internal static MonsterDropTracker DropTracker { get; } = new MonsterDropTracker(); private void Awake() { Log = ((BaseUnityPlugin)this).Logger; DropTracker.Reset(); MultiplayerDropSync.Initialize(); harmony.PatchAll(); TimerModUiIntegration.Initialize(harmony); Log.LogInfo((object)"ValuablesCounted 1.1.0 loaded."); } private void OnDestroy() { MultiplayerDropSync.Dispose(); harmony.UnpatchSelf(); } } internal static class TimerModUiIntegration { public static void Initialize(Harmony harmony) { //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Expected O, but got Unknown Type type = AccessTools.TypeByName("TimerPlugin.EnemyListUI"); MethodInfo methodInfo = ((type == null) ? null : AccessTools.Method(type, "Fetch", (Type[])null, (Type[])null)); MethodInfo methodInfo2 = AccessTools.Method(typeof(TimerModUiIntegration), "FetchPostfix", (Type[])null, (Type[])null); if (methodInfo == null || methodInfo2 == null) { Plugin.Log.LogInfo((object)"TimerMod UI not found. ValuablesCounted will log counts only."); return; } harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(methodInfo2), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Plugin.Log.LogInfo((object)"TimerMod UI integration enabled."); } private static void FetchPostfix(object __instance) { try { object value = Traverse.Create(__instance).Field("enemyNamesText").GetValue(); object value2 = Traverse.Create(__instance).Field("respawnTimesText").GetValue(); if (value != null && value2 != null && Plugin.DropTracker.TotalPossibleDrops > 0) { SetText(value2, BuildRespawnTimesWithDropCounts(GetText(value2))); AppendLineToText(value, "Valuables"); AppendLineToText(value2, $"{Plugin.DropTracker.TotalDrops}/{Plugin.DropTracker.TotalPossibleDrops}"); } } catch (Exception ex) { Plugin.Log.LogWarning((object)("TimerMod UI integration failed: " + ex.Message)); } } private static void AppendLineToText(object textComponent, string value) { string text = GetText(textComponent).TrimEnd('\n', '\r'); SetText(textComponent, string.IsNullOrEmpty(text) ? value : (text + "\n" + value)); } private static string GetText(object textComponent) { return (textComponent.GetType().GetProperty("text", BindingFlags.Instance | BindingFlags.Public)?.GetValue(textComponent) as string) ?? string.Empty; } private static void SetText(object textComponent, string value) { textComponent.GetType().GetProperty("text", BindingFlags.Instance | BindingFlags.Public)?.SetValue(textComponent, value); } private static string BuildRespawnTimesWithDropCounts(string respawnTimesText) { if ((Object)(object)EnemyDirector.instance == (Object)null) { return respawnTimesText; } string[] array = respawnTimesText.Split(new char[1] { '\n' }, StringSplitOptions.None); StringBuilder stringBuilder = new StringBuilder(); int i = 0; foreach (EnemyParent item in EnemyDirector.instance.enemiesSpawned) { MonsterDropRecord record = Plugin.DropTracker.GetRecord(item); if (record != null) { for (; i < array.Length && string.IsNullOrWhiteSpace(array[i]); i++) { AppendLine(stringBuilder, array[i]); } if (i >= array.Length) { break; } AppendLine(stringBuilder, $"{record.Drops}/{record.MaxDrops} {array[i]}"); i++; } } for (; i < array.Length; i++) { AppendLine(stringBuilder, array[i]); } return stringBuilder.ToString().TrimEnd('\n'); } private static void AppendLine(StringBuilder builder, string line) { if (builder.Length > 0) { builder.Append('\n'); } builder.Append(line); } } }