Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of SharedLives v1.0.0
plugins/SharedLives.dll
Decompiled a day agousing System; using System.Collections.Generic; using System.Diagnostics; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using SharedLives.Config; using SharedLives.Core; using SharedLives.UI; 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: IgnoresAccessChecksTo("")] [assembly: AssemblyCompany("Kai")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+d66481a8bc647715b69d50a271b1afc2f9ecfa3b")] [assembly: AssemblyProduct("SharedLives")] [assembly: AssemblyTitle("SharedLives")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.0.0.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace SharedLives { [BepInPlugin("Kai.SharedLives", "SharedLives", "1.0")] public class SharedLives : BaseUnityPlugin { internal static SharedLives Instance { get; private set; } internal static ManualLogSource Logger => Instance._logger; private ManualLogSource _logger => ((BaseUnityPlugin)this).Logger; internal Harmony? Harmony { get; set; } private void Awake() { Instance = this; ((Component)this).gameObject.transform.parent = null; ((Object)((Component)this).gameObject).hideFlags = (HideFlags)61; SharedLivesConfig.Bind(((BaseUnityPlugin)this).Config); Patch(); Logger.LogInfo((object)$"{((BaseUnityPlugin)this).Info.Metadata.GUID} v{((BaseUnityPlugin)this).Info.Metadata.Version} has loaded!"); } internal void Patch() { //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Expected O, but got Unknown //IL_0025: Expected O, but got Unknown if (Harmony == null) { Harmony val = new Harmony(((BaseUnityPlugin)this).Info.Metadata.GUID); Harmony val2 = val; Harmony = val; } Harmony.PatchAll(); } private void OnGUI() { SharedLivesHUD.Draw(); } } } namespace SharedLives.UI { internal static class SharedLivesHUD { private static GUIStyle? _style; internal static void Draw() { //IL_00b5: Unknown result type (might be due to invalid IL or missing references) if (SharedLivesConfig.Enabled.Value && !((Object)(object)StatsManager.instance == (Object)null)) { bool flag = (Object)(object)MapToolController.instance != (Object)null && SharedLivesHUDState.MapOpen; bool holdingDeathHead = SharedLivesHUDState.HoldingDeathHead; bool flag2 = (Object)(object)PlayerController.instance != (Object)null && (Object)(object)PlayerController.instance.playerAvatarScript != (Object)null && PlayerController.instance.playerAvatarScript.isDisabled; if (flag || holdingDeathHead || flag2) { EnsureStyle(); string text = $"Shared Lives: {SharedLivesState.CurrentLives}"; Rect val = default(Rect); ((Rect)(ref val))..ctor((float)Screen.width - 280f - 20f, 20f, 280f, 32f); GUI.Label(val, text, _style); } } } private static void EnsureStyle() { //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Expected O, but got Unknown //IL_003c: Unknown result type (might be due to invalid IL or missing references) if (_style == null) { _style = new GUIStyle(GUI.skin.label) { fontSize = 20, fontStyle = (FontStyle)0, alignment = (TextAnchor)2 }; _style.normal.textColor = Color.white; } } } internal static class SharedLivesHUDState { private static PhysGrabObject? _heldDeathHead; internal static bool MapOpen { get; set; } internal static bool HoldingDeathHead { get { if ((Object)(object)_heldDeathHead == (Object)null) { return false; } return _heldDeathHead.grabbedLocal; } } internal static void DeathHeadGrabStarted(PhysGrabObject physGrabObject) { _heldDeathHead = physGrabObject; } internal static void DeathHeadGrabEnded(PhysGrabObject physGrabObject) { if ((Object)(object)_heldDeathHead == (Object)(object)physGrabObject) { _heldDeathHead = null; } } } } namespace SharedLives.Patches { [HarmonyPatch] internal static class DeathHeadGrabPatch { [HarmonyPatch(typeof(PhysGrabObject), "GrabStarted")] [HarmonyPostfix] private static void GrabStartedPostfix(PhysGrabObject __instance) { if (!((Object)(object)((Component)__instance).GetComponent<PlayerDeathHead>() == (Object)null)) { SharedLivesHUDState.DeathHeadGrabStarted(__instance); } } [HarmonyPatch(typeof(PhysGrabObject), "GrabEnded")] [HarmonyPostfix] private static void GrabEndedPostfix(PhysGrabObject __instance) { if (!((Object)(object)((Component)__instance).GetComponent<PlayerDeathHead>() == (Object)null)) { SharedLivesHUDState.DeathHeadGrabEnded(__instance); } } } [HarmonyPatch(typeof(EnemyHealth), "DeathImpulseRPC")] internal static class EnemyHealthDeathImpulseRPCPatch { private static void Prefix(bool ___dead, out bool __state) { __state = ___dead; } private static void Postfix(EnemyHealth __instance, bool ___dead, bool __state) { if (!SharedLivesConfig.Enabled.Value || !SharedLivesConfig.LifeGainEnabled.Value || !SharedLivesState.IsAuthority || __state || !___dead) { return; } EnemyParent componentInParent = ((Component)__instance).GetComponentInParent<EnemyParent>(); if ((Object)(object)componentInParent != (Object)null) { string enemyName = componentInParent.enemyName; if (SharedLivesConfig.IsEnemyKillBlacklisted(enemyName)) { SharedLives.Logger.LogDebug((object)("Enemy kill ignored by blacklist: " + enemyName)); return; } } int value = SharedLivesConfig.EnemyKiller.Value; if (SharedLivesState.TryAddLives(value, out var previousLives, out var currentLives)) { SharedLives.Logger.LogDebug((object)($"Life gained from enemy kill (+{value}): " + $"{previousLives} -> {currentLives}")); } } } [HarmonyPatch(typeof(ExtractionPoint), "DestroyAllPhysObjectsInHaulList")] internal static class ExtractionPointDestroyAllHaulPatch { private static void Prefix() { if (SharedLivesConfig.Enabled.Value && SharedLivesConfig.LifeGainEnabled.Value && SharedLivesState.IsAuthority && SemiFunc.RunIsLevel()) { int value = SharedLivesConfig.CompleteExtractionPoint.Value; if (SharedLivesState.TryAddLives(value, out var previousLives, out var currentLives)) { SharedLives.Logger.LogDebug((object)($"Extraction point bonus (+{value}): " + $"{previousLives} -> {currentLives}")); } } } } [HarmonyPatch(typeof(LevelGenerator), "GenerateDone")] internal static class LevelGeneratorGenerateDonePatch { private static void Postfix() { if (SharedLivesConfig.Enabled.Value && SharedLivesConfig.Scope.Value == LivesScope.Level && SharedLivesState.IsAuthority && SemiFunc.RunIsLevel() && SharedLivesState.ResetLives()) { SharedLives.Logger.LogDebug((object)$"Shared lives reset for level: {SharedLivesState.CurrentLives}"); } } } [HarmonyPatch(typeof(MapToolController), "Update")] internal static class MapToolControllerUpdatePatch { private static void Postfix(MapToolController __instance, bool ___Active) { if (!((Object)(object)__instance != (Object)(object)MapToolController.instance)) { SharedLivesHUDState.MapOpen = ___Active; } } } [HarmonyPatch(typeof(PlayerAvatar), "PlayerDeathRPC")] internal static class PlayerAvatarPlayerDeathRPCPatch { private static void Prefix(bool ___deadSet, out bool __state) { __state = ___deadSet; } private static void Postfix(bool ___deadSet, bool __state) { if (SharedLivesConfig.Enabled.Value && SharedLivesConfig.ConsumeMode.Value == LifeConsumeMode.OnDeath && SharedLivesState.IsAuthority && !__state && ___deadSet && SharedLivesState.TryConsumeLife(out var previousLives, out var currentLives)) { SharedLives.Logger.LogDebug((object)$"Life consumed on death: {previousLives} -> {currentLives}"); } } } [HarmonyPatch(typeof(PlayerAvatar), "Revive")] internal static class PlayerAvatarRevivePatch { private static bool Prefix(bool ___deadSet, PlayerDeathHead ___playerDeathHead) { if (!SharedLivesConfig.Enabled.Value) { return true; } if (!SharedLivesState.IsLivesActiveScene) { return true; } if (!___deadSet) { return true; } if (!SharedLivesState.IsAuthority) { return true; } if ((Object)(object)___playerDeathHead == (Object)null) { return true; } if (!SharedLivesState.CanRevive()) { SharedLives.Logger.LogDebug((object)"Revive blocked: no shared lives remaining."); return false; } return true; } } [HarmonyPatch(typeof(PlayerAvatar), "ReviveRPC")] internal static class PlayerAvatarReviveRPCPatch { private static bool Prefix(bool ___deadSet, PlayerDeathHead ___playerDeathHead, out bool __state) { __state = false; if (!SharedLivesConfig.Enabled.Value) { return true; } if (!SharedLivesState.IsLivesActiveScene) { return true; } if (!___deadSet) { return true; } if ((Object)(object)___playerDeathHead == (Object)null) { return true; } if (!SharedLivesState.CanRevive()) { if (SharedLivesState.IsAuthority) { SharedLives.Logger.LogDebug((object)"ReviveRPC blocked: no shared lives remaining."); } return false; } __state = true; return true; } private static void Postfix(bool ___deadSet, bool __state) { if (__state && SharedLivesConfig.Enabled.Value && SharedLivesState.IsLivesActiveScene && SharedLivesConfig.ConsumeMode.Value == LifeConsumeMode.OnRevive && SharedLivesState.IsAuthority && !___deadSet) { if (!SharedLivesState.TryConsumeLife(out var previousLives, out var currentLives)) { SharedLives.Logger.LogWarning((object)"Revive succeeded, but SharedLives failed to consume a life."); } else { SharedLives.Logger.LogDebug((object)("Life consumed on successful revive: " + $"{previousLives} -> {currentLives}")); } } } } [HarmonyPatch(typeof(SemiFunc), "LevelSuccessful")] internal static class SemiFuncLevelSuccessfulPatch { private static void Postfix() { if (SharedLivesConfig.Enabled.Value && SharedLivesConfig.LifeGainEnabled.Value && SharedLivesState.IsAuthority) { int value = SharedLivesConfig.CompleteLevelBonus.Value; if (SharedLivesState.TryAddLives(value, out var previousLives, out var currentLives)) { SharedLives.Logger.LogDebug((object)($"Complete level bonus (+{value}): " + $"{previousLives} -> {currentLives}")); } } } } [HarmonyPatch(typeof(StatsManager), "RunStartStats")] internal static class StatsManagerRunStartStatsPatch { private static void Postfix() { if (SharedLivesConfig.Enabled.Value) { SharedLivesState.Initialize(StatsManager.instance); SharedLives.Logger.LogDebug((object)$"Shared lives initialized: {SharedLivesState.StartingLives}"); } } } } namespace SharedLives.Patches.Compat { [HarmonyPatch] internal static class MedicHasRequiredHealthPatch { private const string TypeName = "Medic_.MedicHeadReviver"; private static bool Prepare() { return AccessTools.TypeByName("Medic_.MedicHeadReviver") != null; } private static MethodBase TargetMethod() { return AccessTools.Method(AccessTools.TypeByName("Medic_.MedicHeadReviver"), "HasRequiredHealth", (Type[])null, (Type[])null); } private static void Postfix(ref bool __result) { if (SharedLivesConfig.Enabled.Value && SharedLivesState.IsLivesActiveScene && !SharedLivesState.CanRevive()) { __result = false; } } } [HarmonyPatch] internal static class MedicFailedReviveMessagePatch { private const string TypeName = "Medic_.MedicPlugin"; private static bool Prepare() { return AccessTools.TypeByName("Medic_.MedicPlugin") != null; } private static MethodBase TargetMethod() { return AccessTools.Method(AccessTools.TypeByName("Medic_.MedicPlugin"), "GetRandomFailedReviveMessage", (Type[])null, (Type[])null); } private static void Postfix(ref string __result) { if (SharedLivesConfig.Enabled.Value && SharedLivesState.IsLivesActiveScene && !SharedLivesState.CanRevive()) { __result = SharedLivesConfig.GetRandomMedicNoLivesMessage(); } } } [HarmonyPatch] internal static class MedicReviveHeadPatch { private const string MedicHeadReviverTypeName = "Medic_.MedicHeadReviver"; private static bool Prepare() { return AccessTools.TypeByName("Medic_.MedicHeadReviver") != null; } private static MethodBase TargetMethod() { Type type = AccessTools.TypeByName("Medic_.MedicHeadReviver"); return AccessTools.Method(type, "ReviveHead", (Type[])null, (Type[])null); } private static bool Prefix(PlayerDeathHead __0, PlayerAvatar __1) { if (!SharedLivesConfig.Enabled.Value) { return true; } if (!SharedLivesState.IsLivesActiveScene) { return true; } if (!SharedLivesState.IsAuthority) { return true; } if ((Object)(object)__0 == (Object)null || (Object)(object)__1 == (Object)null) { return true; } if (SharedLivesState.CanRevive()) { return true; } SharedLives.Logger.LogDebug((object)"Medic revive blocked: no shared lives remaining."); return false; } } } namespace SharedLives.Core { internal static class SharedLivesState { internal const string StatsKey = "SharedLives.CurrentLives"; internal static int CurrentLives { get { if ((Object)(object)StatsManager.instance == (Object)null) { return StartingLives; } if (StatsManager.instance.runStats.TryGetValue("SharedLives.CurrentLives", out var value)) { return Math.Max(0, value); } return StartingLives; } } internal static int MaxLives => Math.Max(0, SharedLivesConfig.MaxLives.Value); internal static int StartingLives => ClampLives(SharedLivesConfig.StartingLives.Value); internal static bool IsAuthority => SemiFunc.IsMasterClientOrSingleplayer(); internal static bool IsLivesActiveScene { get { if (!SemiFunc.RunIsLevel()) { return SemiFunc.RunIsLobby(); } return true; } } internal static int ClampLives(int value) { return Math.Clamp(value, 0, MaxLives); } internal static void Initialize(StatsManager statsManager) { statsManager.runStats["SharedLives.CurrentLives"] = StartingLives; } internal static bool SetLives(int value) { if (!IsAuthority) { return false; } if ((Object)(object)StatsManager.instance == (Object)null) { return false; } value = ClampLives(value); if ((Object)(object)PunManager.instance != (Object)null) { PunManager.instance.SetRunStatSet("SharedLives.CurrentLives", value); } else { StatsManager.instance.runStats["SharedLives.CurrentLives"] = value; } return true; } internal static bool ResetLives() { return SetLives(StartingLives); } internal static bool TryConsumeLife(out int previousLives, out int currentLives) { previousLives = CurrentLives; currentLives = previousLives; if (!IsAuthority) { return false; } if (!IsLivesActiveScene) { return false; } if (previousLives <= 0) { return false; } currentLives = previousLives - 1; return SetLives(currentLives); } internal static bool TryAddLives(int amount, out int previousLives, out int currentLives) { previousLives = CurrentLives; currentLives = previousLives; if (!IsAuthority) { return false; } if (!IsLivesActiveScene) { return false; } if (amount <= 0) { return false; } if (previousLives >= MaxLives) { return false; } currentLives = ClampLives(previousLives + amount); if (currentLives == previousLives) { return false; } return SetLives(currentLives); } internal static bool CanRevive() { return CurrentLives > 0; } } } namespace SharedLives.Config { internal enum LifeConsumeMode { OnDeath, OnRevive } internal enum LivesScope { Run, Level } internal static class SharedLivesConfig { internal static ConfigEntry<bool> Enabled { get; private set; } internal static ConfigEntry<int> StartingLives { get; private set; } internal static ConfigEntry<int> MaxLives { get; private set; } internal static ConfigEntry<LifeConsumeMode> ConsumeMode { get; private set; } internal static ConfigEntry<LivesScope> Scope { get; private set; } internal static ConfigEntry<bool> LifeGainEnabled { get; private set; } internal static ConfigEntry<int> EnemyKiller { get; private set; } internal static ConfigEntry<string> EnemyKillBlacklist { get; private set; } internal static ConfigEntry<int> CompleteLevelBonus { get; private set; } internal static ConfigEntry<int> CompleteExtractionPoint { get; private set; } internal static ConfigEntry<string> MedicNoLivesMessages { get; private set; } internal static void Bind(ConfigFile config) { //IL_00c0: Unknown result type (might be due to invalid IL or missing references) //IL_00ca: Expected O, but got Unknown //IL_010c: Unknown result type (might be due to invalid IL or missing references) //IL_0116: Expected O, but got Unknown //IL_0139: Unknown result type (might be due to invalid IL or missing references) //IL_0143: Expected O, but got Unknown Enabled = config.Bind<bool>("General", "Enabled", true, "Enable or disable SharedLives."); StartingLives = config.Bind<int>("General", "StartingLives", 5, "Number of shared lives at the start of a run or level. Clamped between 0 and MaxLives."); MaxLives = config.Bind<int>("General", "MaxLives", 5, "Maximum number of shared lives. Values below 0 are treated as 0."); ConsumeMode = config.Bind<LifeConsumeMode>("General", "ConsumeMode", LifeConsumeMode.OnDeath, "OnDeath = consume one life when a player dies. OnRevive = consume one life when a player is revived."); Scope = config.Bind<LivesScope>("General", "LivesScope", LivesScope.Run, "Run = lives persist across the whole run. Level = lives reset at the start of every normal level."); LifeGainEnabled = config.Bind<bool>("LifeGain", "Enabled", true, "Enable or disable SharedLives life gain rules."); EnemyKiller = config.Bind<int>("LifeGain", "EnemyKiller", 1, new ConfigDescription("Number of shared lives gained when an enemy death is confirmed.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 100), Array.Empty<object>())); EnemyKillBlacklist = config.Bind<string>("LifeGain", "EnemyKillBlacklist", "Gnome,Banger", "Comma-separated enemy names that do not grant lives when killed. Example: Gnome,Banger"); CompleteLevelBonus = config.Bind<int>("LifeGain", "Complete_LevelBonus", 5, new ConfigDescription("Number of shared lives gained when a level is successfully completed.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 100), Array.Empty<object>())); CompleteExtractionPoint = config.Bind<int>("LifeGain", "Complete_ExtractionPoint", 5, new ConfigDescription("Number of shared lives gained when an extraction point is completed.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 100), Array.Empty<object>())); MedicNoLivesMessages = config.Bind<string>("Compatibility.Medic", "NoLivesMessages", "There are no shared lives left!|We're out of shared lives!|I can't revive you - we're out of lives!", "Messages used by Medic when a revive is attempted with no shared lives remaining. Separate multiple messages with |."); } internal static bool IsEnemyKillBlacklisted(string enemyName) { if (string.IsNullOrWhiteSpace(enemyName)) { return false; } string[] array = EnemyKillBlacklist.Value.Split(','); foreach (string text in array) { if (string.Equals(text.Trim(), enemyName, StringComparison.OrdinalIgnoreCase)) { return true; } } return false; } internal static string GetRandomMedicNoLivesMessage() { string[] array = MedicNoLivesMessages.Value.Split('|'); List<string> list = new List<string>(); string[] array2 = array; foreach (string text in array2) { string text2 = text.Trim(); if (text2.Length > 0) { list.Add(text2); } } if (list.Count == 0) { return "There are no shared lives left!"; } int index = Random.Range(0, list.Count); return list[index]; } } }