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 SoloPossible v1.0.1
SoloPossible.dll
Decompiled 9 hours agousing System; using System.Collections.Generic; using System.Diagnostics; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using GameNetcodeStuff; using HarmonyLib; using UnityEngine; using UnityEngine.InputSystem; using UnityEngine.InputSystem.Controls; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("SoloPossible")] [assembly: AssemblyConfiguration("Debug")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0")] [assembly: AssemblyProduct("SoloPossible")] [assembly: AssemblyTitle("SoloPossible")] [assembly: AssemblyVersion("1.0.0.0")] namespace SoloPossible; [BepInPlugin("dottore.solopossible", "SoloPossible", "1.0.1")] public class SoloPossiblePlugin : BaseUnityPlugin { public const string PluginGuid = "dottore.solopossible"; public const string PluginName = "SoloPossible"; public const string PluginVersion = "1.0.1"; internal static ManualLogSource Log; internal static ConfigEntry<bool> EnableMod; internal static ConfigEntry<float> ItemWeightMultiplier; internal static ConfigEntry<float> SprintSpeedMultiplier; internal static ConfigEntry<float> StaminaRegenMultiplier; internal static ConfigEntry<int> MaxHealth; internal static ConfigEntry<int> ShipRegenAmount; internal static ConfigEntry<float> ShipRegenInterval; internal static ConfigEntry<int> InventorySlots; private Harmony harmony; private bool patchesApplied; private void Awake() { //IL_0126: Unknown result type (might be due to invalid IL or missing references) //IL_0130: Expected O, but got Unknown Log = ((BaseUnityPlugin)this).Logger; EnableMod = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "EnableMod", true, "Enables SoloPossible."); ItemWeightMultiplier = ((BaseUnityPlugin)this).Config.Bind<float>("Balance", "ItemWeightMultiplier", 0.85f, "Item weight multiplier. 0.85 = 15% lighter."); SprintSpeedMultiplier = ((BaseUnityPlugin)this).Config.Bind<float>("Balance", "SprintSpeedMultiplier", 1.03f, "Sprint speed multiplier. 1.03 = 3% faster."); StaminaRegenMultiplier = ((BaseUnityPlugin)this).Config.Bind<float>("Balance", "StaminaRegenMultiplier", 1.03f, "Stamina recovery multiplier. 1.03 = 3% faster recovery."); MaxHealth = ((BaseUnityPlugin)this).Config.Bind<int>("Health", "MaxHealth", 150, "Health given at the start of each solo life."); ShipRegenAmount = ((BaseUnityPlugin)this).Config.Bind<int>("Health", "ShipRegenAmount", 5, "HP regenerated per interval while inside the ship."); ShipRegenInterval = ((BaseUnityPlugin)this).Config.Bind<float>("Health", "ShipRegenInterval", 5f, "Seconds between ship-only healing ticks."); InventorySlots = ((BaseUnityPlugin)this).Config.Bind<int>("Inventory", "InventorySlots", 5, "Total inventory slots when playing solo. Vanilla is 4. SoloPossible default is 5."); harmony = new Harmony("dottore.solopossible"); if (!EnableMod.Value) { Log.LogInfo((object)"SoloPossible 1.0.1 loaded but disabled by config. No patches applied."); return; } harmony.PatchAll(); patchesApplied = true; Log.LogInfo((object)"SoloPossible 1.0.1 loaded and patched."); } private void OnDestroy() { try { InventoryManager.ForceVanillaInventoryAndHud(); WeightManager.RestoreAllWeights(); } catch { } if (patchesApplied && harmony != null) { harmony.UnpatchSelf(); } } internal static bool EffectsAllowed() { return EnableMod.Value && IsSoloGame(); } internal static bool IsSoloGame() { StartOfRound instance = StartOfRound.Instance; if ((Object)(object)instance == (Object)null) { return false; } return instance.connectedPlayersAmount <= 0; } internal static bool IsLocalPlayer(PlayerControllerB player) { return (Object)(object)player != (Object)null && (Object)(object)StartOfRound.Instance != (Object)null && (Object)(object)StartOfRound.Instance.localPlayerController == (Object)(object)player; } internal static bool IsLocalPlayerAllowed(PlayerControllerB player) { return IsLocalPlayer(player) && EffectsAllowed(); } internal static int WantedInventorySlots() { if (!EffectsAllowed()) { return 4; } return Mathf.Max(4, InventorySlots.Value); } } internal static class InventoryManager { private static readonly MethodInfo SwitchToItemSlotMethod = AccessTools.Method(typeof(PlayerControllerB), "SwitchToItemSlot", (Type[])null, (Type[])null); private static float nextHudMaintenanceTime; internal static void TickHudMaintenance() { if (!(Time.realtimeSinceStartup < nextHudMaintenanceTime)) { nextHudMaintenanceTime = Time.realtimeSinceStartup + 0.5f; ResizeAllPlayerInventories(); if ((Object)(object)HUDManager.Instance != (Object)null) { ResizeHudInventory(HUDManager.Instance); } } } internal static void ForceVanillaInventoryAndHud() { ResizeAllPlayerInventoriesToSize(4); if ((Object)(object)HUDManager.Instance != (Object)null) { ResizeHudInventoryToSize(HUDManager.Instance, 4); } } internal static void ResizePlayerInventory(PlayerControllerB player) { ResizePlayerInventoryToSize(player, SoloPossiblePlugin.WantedInventorySlots()); } internal static void ResizeAllPlayerInventories() { ResizeAllPlayerInventoriesToSize(SoloPossiblePlugin.WantedInventorySlots()); } private static void ResizeAllPlayerInventoriesToSize(int wantedSlots) { PlayerControllerB[] array = Object.FindObjectsOfType<PlayerControllerB>(); PlayerControllerB[] array2 = array; foreach (PlayerControllerB player in array2) { ResizePlayerInventoryToSize(player, wantedSlots); } } private static void ResizePlayerInventoryToSize(PlayerControllerB player, int wantedSlots) { if ((Object)(object)player == (Object)null) { return; } wantedSlots = Mathf.Max(4, wantedSlots); if (player.ItemSlots == null || player.ItemSlots.Length != wantedSlots) { GrabbableObject[] array = (GrabbableObject[])(((object)player.ItemSlots) ?? ((object)new GrabbableObject[4])); GrabbableObject[] array2 = (GrabbableObject[])(object)new GrabbableObject[wantedSlots]; int num = Mathf.Min(array.Length, array2.Length); for (int i = 0; i < num; i++) { array2[i] = array[i]; } player.ItemSlots = array2; } } internal static void ResizeHudInventory(HUDManager hud) { ResizeHudInventoryToSize(hud, SoloPossiblePlugin.WantedInventorySlots()); } private static void ResizeHudInventoryToSize(HUDManager hud, int wantedSlots) { //IL_01fd: Unknown result type (might be due to invalid IL or missing references) //IL_0202: Unknown result type (might be due to invalid IL or missing references) //IL_020b: Unknown result type (might be due to invalid IL or missing references) //IL_0218: Unknown result type (might be due to invalid IL or missing references) //IL_021f: Unknown result type (might be due to invalid IL or missing references) //IL_0226: Unknown result type (might be due to invalid IL or missing references) //IL_022d: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)hud == (Object)null) { return; } wantedSlots = Mathf.Max(4, wantedSlots); GameObject val = GameObject.Find("Systems/UI/Canvas/IngamePlayerHUD/Inventory"); if ((Object)(object)val == (Object)null) { SoloPossiblePlugin.Log.LogWarning((object)"Could not find inventory HUD root."); return; } if (hud.itemSlotIcons == null || hud.itemSlotIconFrames == null) { SoloPossiblePlugin.Log.LogWarning((object)"HUD inventory icon arrays were null."); return; } if (hud.itemSlotIcons.Length < 4 || hud.itemSlotIconFrames.Length < 4) { SoloPossiblePlugin.Log.LogWarning((object)"HUD inventory icon arrays were smaller than expected."); return; } CleanupExtraHudSlots(val.transform, wantedSlots); if (hud.itemSlotIcons.Length == wantedSlots && hud.itemSlotIconFrames.Length == wantedSlots) { CenterHudSlots(val.transform, wantedSlots); return; } Image[] array = (Image[])(object)new Image[wantedSlots]; Image[] array2 = (Image[])(object)new Image[wantedSlots]; int num = Mathf.Min(4, Mathf.Min(hud.itemSlotIcons.Length, hud.itemSlotIconFrames.Length)); for (int i = 0; i < num; i++) { array[i] = hud.itemSlotIcons[i]; array2[i] = hud.itemSlotIconFrames[i]; } if (wantedSlots > 4) { GameObject val2 = GameObject.Find("Systems/UI/Canvas/IngamePlayerHUD/Inventory/Slot3"); if ((Object)(object)val2 == (Object)null) { SoloPossiblePlugin.Log.LogWarning((object)"Could not find Slot3 HUD template."); return; } Transform transform = val2.transform; for (int j = 4; j < wantedSlots; j++) { GameObject val3 = GameObject.Find($"Systems/UI/Canvas/IngamePlayerHUD/Inventory/Slot{j}"); GameObject val4; if ((Object)(object)val3 != (Object)null) { val4 = val3; val4.SetActive(true); } else { val4 = Object.Instantiate<GameObject>(val2, val.transform); ((Object)val4).name = $"Slot{j}"; } Vector3 localPosition = transform.localPosition; val4.transform.SetLocalPositionAndRotation(new Vector3(localPosition.x + 50f, localPosition.y, localPosition.z), transform.localRotation); transform = val4.transform; array2[j] = val4.GetComponent<Image>(); if (val4.transform.childCount > 0) { array[j] = ((Component)val4.transform.GetChild(0)).GetComponent<Image>(); } } } hud.itemSlotIcons = array; hud.itemSlotIconFrames = array2; CenterHudSlots(val.transform, wantedSlots); SoloPossiblePlugin.Log.LogInfo((object)$"Inventory HUD resized to {wantedSlots} slots."); } private static void CleanupExtraHudSlots(Transform inventoryRoot, int wantedSlots) { if ((Object)(object)inventoryRoot == (Object)null) { return; } for (int num = inventoryRoot.childCount - 1; num >= 0; num--) { Transform child = inventoryRoot.GetChild(num); if (((Object)child).name.StartsWith("Slot")) { string s = ((Object)child).name.Replace("Slot", ""); if (int.TryParse(s, out var result) && result >= wantedSlots && result >= 4) { ((Component)child).gameObject.SetActive(false); Object.Destroy((Object)(object)((Component)child).gameObject); } } } } private static void CenterHudSlots(Transform inventoryRoot, int slotCount) { //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_007b: Unknown result type (might be due to invalid IL or missing references) //IL_0085: Unknown result type (might be due to invalid IL or missing references) //IL_008c: Unknown result type (might be due to invalid IL or missing references) //IL_0093: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)inventoryRoot == (Object)null) { return; } float num = 50f; float num2 = (float)(slotCount - 1) / 2f; for (int i = 0; i < slotCount; i++) { Transform val = inventoryRoot.Find($"Slot{i}"); if ((Object)(object)val == (Object)null && i < inventoryRoot.childCount) { val = inventoryRoot.GetChild(i); } if (!((Object)(object)val == (Object)null)) { Vector3 localPosition = val.localPosition; val.localPosition = new Vector3(num * ((float)i - num2), localPosition.y, localPosition.z); } } } internal static void HandleFifthSlotHotkey(PlayerControllerB player) { if (SoloPossiblePlugin.IsLocalPlayerAllowed(player) && !player.isPlayerDead && player.ItemSlots != null && player.ItemSlots.Length >= 5 && Keyboard.current != null && (((ButtonControl)Keyboard.current.digit5Key).wasPressedThisFrame || ((ButtonControl)Keyboard.current.numpad5Key).wasPressedThisFrame)) { SwitchToSlot(player, 4); } } private static void SwitchToSlot(PlayerControllerB player, int slot) { if (SwitchToItemSlotMethod == null) { SoloPossiblePlugin.Log.LogWarning((object)"Could not find PlayerControllerB.SwitchToItemSlot."); return; } ParameterInfo[] parameters = SwitchToItemSlotMethod.GetParameters(); try { if (parameters.Length == 1) { SwitchToItemSlotMethod.Invoke(player, new object[1] { slot }); } else { SwitchToItemSlotMethod.Invoke(player, new object[2] { slot, null }); } } catch { SoloPossiblePlugin.Log.LogWarning((object)$"Failed to switch to inventory slot {slot}."); } } } internal static class WeightManager { private static readonly Dictionary<Item, float> OriginalWeights = new Dictionary<Item, float>(); private static bool lastAllowedState = false; internal static void RegisterAndApply(GrabbableObject grabbable) { if (!((Object)(object)grabbable == (Object)null) && !((Object)(object)grabbable.itemProperties == (Object)null)) { Item itemProperties = grabbable.itemProperties; if (!OriginalWeights.ContainsKey(itemProperties)) { OriginalWeights[itemProperties] = itemProperties.weight; } ApplyToItem(itemProperties); } } internal static void RestoreAllWeights() { foreach (KeyValuePair<Item, float> originalWeight in OriginalWeights) { if ((Object)(object)originalWeight.Key != (Object)null) { originalWeight.Key.weight = originalWeight.Value; } } lastAllowedState = false; } internal static void RefreshAll() { lastAllowedState = SoloPossiblePlugin.EffectsAllowed(); foreach (Item key in OriginalWeights.Keys) { ApplyToItem(key); } } internal static void RefreshAllIfNeeded() { bool flag = SoloPossiblePlugin.EffectsAllowed(); if (flag == lastAllowedState) { return; } lastAllowedState = flag; foreach (Item key in OriginalWeights.Keys) { ApplyToItem(key); } } private static void ApplyToItem(Item item) { if (!((Object)(object)item == (Object)null) && OriginalWeights.ContainsKey(item)) { float num = OriginalWeights[item]; if (!SoloPossiblePlugin.EffectsAllowed()) { item.weight = num; return; } float num2 = Mathf.Max(0f, num - 1f); item.weight = 1f + num2 * SoloPossiblePlugin.ItemWeightMultiplier.Value; } } } internal static class HealthManager { private static readonly HashSet<ulong> InitializedPlayers = new HashSet<ulong>(); private static float regenTimer; internal static void ResetLifeTracking() { InitializedPlayers.Clear(); regenTimer = 0f; } internal static void Tick(PlayerControllerB player) { if (SoloPossiblePlugin.IsLocalPlayerAllowed(player) && player.isPlayerControlled && !player.isPlayerDead) { EnsureSoloPossibleHealth(player); TickShipRegen(player); } } private static void EnsureSoloPossibleHealth(PlayerControllerB player) { if (!InitializedPlayers.Contains(player.playerClientId) && player.health > 0) { int num = Mathf.Max(1, SoloPossiblePlugin.MaxHealth.Value); if (player.health < num) { player.health = num; } InitializedPlayers.Add(player.playerClientId); UpdateHealthUi(player.health); } } private static void TickShipRegen(PlayerControllerB player) { int num = Mathf.Max(1, SoloPossiblePlugin.MaxHealth.Value); if (player.health >= num) { regenTimer = 0f; return; } if (!player.isInHangarShipRoom) { regenTimer = 0f; return; } regenTimer += Time.deltaTime; if (!(regenTimer < Mathf.Max(0.1f, SoloPossiblePlugin.ShipRegenInterval.Value))) { regenTimer = 0f; int num2 = Mathf.Max(0, SoloPossiblePlugin.ShipRegenAmount.Value); player.health = Mathf.Clamp(player.health + num2, 0, num); UpdateHealthUi(player.health); } } private static void UpdateHealthUi(int health) { if (!((Object)(object)HUDManager.Instance == (Object)null)) { HUDManager.Instance.UpdateHealthUI(health, false); } } } [HarmonyPatch(typeof(HUDManager), "Awake")] internal static class HudManagerAwakePatch { private static void Postfix(HUDManager __instance) { InventoryManager.ResizeHudInventory(__instance); } } [HarmonyPatch(typeof(PlayerControllerB), "Awake")] internal static class PlayerControllerAwakePatch { private static void Postfix(PlayerControllerB __instance) { InventoryManager.ResizePlayerInventory(__instance); } } [HarmonyPatch(typeof(GrabbableObject), "Start")] internal static class GrabbableObjectStartPatch { private static void Postfix(GrabbableObject __instance) { WeightManager.RegisterAndApply(__instance); } } [HarmonyPatch(typeof(StartOfRound), "ReviveDeadPlayers")] internal static class ReviveDeadPlayersPatch { private static void Postfix() { HealthManager.ResetLifeTracking(); InventoryManager.ResizeAllPlayerInventories(); if ((Object)(object)HUDManager.Instance != (Object)null) { InventoryManager.ResizeHudInventory(HUDManager.Instance); } WeightManager.RefreshAll(); } } [HarmonyPatch(typeof(StartOfRound), "Start")] internal static class StartOfRoundStartPatch { private static void Postfix() { HealthManager.ResetLifeTracking(); InventoryManager.ResizeAllPlayerInventories(); if ((Object)(object)HUDManager.Instance != (Object)null) { InventoryManager.ResizeHudInventory(HUDManager.Instance); } WeightManager.RefreshAll(); } } [HarmonyPatch(typeof(PlayerControllerB), "ConnectClientToPlayerObject")] internal static class ConnectClientToPlayerObjectPatch { private static void Postfix(PlayerControllerB __instance) { InventoryManager.ResizeAllPlayerInventories(); if ((Object)(object)HUDManager.Instance != (Object)null) { InventoryManager.ResizeHudInventory(HUDManager.Instance); } if (SoloPossiblePlugin.IsLocalPlayerAllowed(__instance)) { HealthManager.Tick(__instance); } WeightManager.RefreshAll(); } } [HarmonyPatch(typeof(PlayerControllerB), "Update")] internal static class PlayerUpdatePatch { private static readonly Dictionary<PlayerControllerB, float> PreviousMovementSpeeds = new Dictionary<PlayerControllerB, float>(); private static void Prefix(PlayerControllerB __instance, ref bool ___isPlayerControlled, ref bool ___isSprinting, ref float ___movementSpeed) { PreviousMovementSpeeds[__instance] = ___movementSpeed; if (___isPlayerControlled && SoloPossiblePlugin.IsLocalPlayerAllowed(__instance) && ___isSprinting) { ___movementSpeed *= SoloPossiblePlugin.SprintSpeedMultiplier.Value; } } private static void Postfix(PlayerControllerB __instance, ref float ___movementSpeed) { if (PreviousMovementSpeeds.ContainsKey(__instance)) { ___movementSpeed = PreviousMovementSpeeds[__instance]; } if (SoloPossiblePlugin.IsLocalPlayer(__instance)) { InventoryManager.TickHudMaintenance(); InventoryManager.HandleFifthSlotHotkey(__instance); WeightManager.RefreshAllIfNeeded(); HealthManager.Tick(__instance); } } } [HarmonyPatch(typeof(PlayerControllerB), "LateUpdate")] internal static class PlayerStaminaPatch { private static float previousSprintMeter = -1f; private static void Prefix(PlayerControllerB __instance, ref bool ___isPlayerControlled, ref float ___sprintMeter) { if (!___isPlayerControlled || !SoloPossiblePlugin.IsLocalPlayerAllowed(__instance)) { previousSprintMeter = -1f; } else { previousSprintMeter = ___sprintMeter; } } private static void Postfix(PlayerControllerB __instance, ref bool ___isPlayerControlled, ref float ___sprintMeter) { if (!(previousSprintMeter < 0f) && ___isPlayerControlled && SoloPossiblePlugin.IsLocalPlayerAllowed(__instance)) { float num = ___sprintMeter - previousSprintMeter; if (num > 0f) { ___sprintMeter = Mathf.Clamp(previousSprintMeter + num * SoloPossiblePlugin.StaminaRegenMultiplier.Value, 0f, 1f); } } } }