using 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.5")] public class SoloPossiblePlugin : BaseUnityPlugin { public const string PluginGuid = "dottore.solopossible"; public const string PluginName = "SoloPossible"; public const string PluginVersion = "1.0.5"; internal static ManualLogSource Log; internal static ConfigEntry EnableMod; internal static ConfigEntry ItemWeightMultiplier; internal static ConfigEntry SprintSpeedMultiplier; internal static ConfigEntry StaminaRegenMultiplier; internal static ConfigEntry MaxHealth; internal static ConfigEntry ShipRegenAmount; internal static ConfigEntry ShipRegenInterval; internal static ConfigEntry InventorySlots; internal static ConfigEntry EnableDarknessVision; internal static ConfigEntry DarknessVisionIntensity; internal static ConfigEntry DarknessVisionRange; private Harmony harmony; private void Awake() { //IL_018e: Unknown result type (might be due to invalid IL or missing references) //IL_0198: Expected O, but got Unknown Log = ((BaseUnityPlugin)this).Logger; EnableMod = ((BaseUnityPlugin)this).Config.Bind("General", "EnableMod", true, "Enables SoloPossible."); ItemWeightMultiplier = ((BaseUnityPlugin)this).Config.Bind("Balance", "ItemWeightMultiplier", 0.75f, "Item weight multiplier. 0.75 = 25% lighter."); SprintSpeedMultiplier = ((BaseUnityPlugin)this).Config.Bind("Balance", "SprintSpeedMultiplier", 1.03f, "Sprint speed multiplier. 1.03 = 3% faster."); StaminaRegenMultiplier = ((BaseUnityPlugin)this).Config.Bind("Balance", "StaminaRegenMultiplier", 1.1f, "Stamina recovery multiplier. 1.10 = 10% faster recovery."); MaxHealth = ((BaseUnityPlugin)this).Config.Bind("Health", "MaxHealth", 150, "Health given at the start of each solo life."); ShipRegenAmount = ((BaseUnityPlugin)this).Config.Bind("Health", "ShipRegenAmount", 5, "HP regenerated per interval while inside the ship."); ShipRegenInterval = ((BaseUnityPlugin)this).Config.Bind("Health", "ShipRegenInterval", 5f, "Seconds between ship-only healing ticks."); InventorySlots = ((BaseUnityPlugin)this).Config.Bind("Inventory", "InventorySlots", 5, "Total inventory slots when playing solo. Vanilla is 4. SoloPossible default is 5."); EnableDarknessVision = ((BaseUnityPlugin)this).Config.Bind("Vision", "EnableDarknessVision", true, "Enables a very small solo-only darkness visibility assist."); DarknessVisionIntensity = ((BaseUnityPlugin)this).Config.Bind("Vision", "DarknessVisionIntensity", 0.05f, "Darkness vision light intensity. 0.05 is a subtle 5% style assist."); DarknessVisionRange = ((BaseUnityPlugin)this).Config.Bind("Vision", "DarknessVisionRange", 18f, "Darkness vision light range."); harmony = new Harmony("dottore.solopossible"); harmony.PatchAll(); Log.LogInfo((object)"SoloPossible 1.0.5 loaded."); } 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 VisionManager { private static Light darknessVisionLight; internal static void Tick(PlayerControllerB player) { //IL_00b4: Unknown result type (might be due to invalid IL or missing references) //IL_00cf: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)player == (Object)null) { Disable(); return; } if (!SoloPossiblePlugin.IsLocalPlayerAllowed(player) || !SoloPossiblePlugin.EnableDarknessVision.Value) { Disable(); return; } if (player.isPlayerDead) { Disable(); return; } Camera gameplayCamera = player.gameplayCamera; if ((Object)(object)gameplayCamera == (Object)null) { Disable(); return; } if ((Object)(object)darknessVisionLight == (Object)null) { CreateLight(gameplayCamera); } if (!((Object)(object)darknessVisionLight == (Object)null)) { ((Component)darknessVisionLight).transform.position = ((Component)gameplayCamera).transform.position; ((Component)darknessVisionLight).transform.rotation = ((Component)gameplayCamera).transform.rotation; darknessVisionLight.intensity = Mathf.Max(0f, SoloPossiblePlugin.DarknessVisionIntensity.Value); darknessVisionLight.range = Mathf.Max(1f, SoloPossiblePlugin.DarknessVisionRange.Value); ((Behaviour)darknessVisionLight).enabled = true; } } private static void CreateLight(Camera camera) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Expected O, but got Unknown //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Unknown result type (might be due to invalid IL or missing references) GameObject val = new GameObject("SoloPossibleDarknessVisionLight"); val.transform.SetParent(((Component)camera).transform); val.transform.localPosition = Vector3.zero; val.transform.localRotation = Quaternion.identity; darknessVisionLight = val.AddComponent(); darknessVisionLight.type = (LightType)2; darknessVisionLight.color = Color.white; darknessVisionLight.intensity = Mathf.Max(0f, SoloPossiblePlugin.DarknessVisionIntensity.Value); darknessVisionLight.range = Mathf.Max(1f, SoloPossiblePlugin.DarknessVisionRange.Value); darknessVisionLight.shadows = (LightShadows)0; ((Behaviour)darknessVisionLight).enabled = true; } internal static void Disable() { if ((Object)(object)darknessVisionLight != (Object)null) { ((Behaviour)darknessVisionLight).enabled = false; } } } internal static class InventoryManager { private static readonly MethodInfo SwitchToItemSlotMethod = AccessTools.Method(typeof(PlayerControllerB), "SwitchToItemSlot", (Type[])null, (Type[])null); internal static void ResizePlayerInventory(PlayerControllerB player) { if ((Object)(object)player == (Object)null) { return; } int num = SoloPossiblePlugin.WantedInventorySlots(); if (player.ItemSlots == null || player.ItemSlots.Length != num) { GrabbableObject[] array = (GrabbableObject[])(((object)player.ItemSlots) ?? ((object)new GrabbableObject[4])); GrabbableObject[] array2 = (GrabbableObject[])(object)new GrabbableObject[num]; int num2 = Mathf.Min(array.Length, array2.Length); for (int i = 0; i < num2; i++) { array2[i] = array[i]; } player.ItemSlots = array2; } } internal static void ResizeAllPlayerInventories() { PlayerControllerB[] array = Object.FindObjectsOfType(); PlayerControllerB[] array2 = array; foreach (PlayerControllerB player in array2) { ResizePlayerInventory(player); } } internal static void ResizeHudInventory(HUDManager hud) { //IL_01e5: Unknown result type (might be due to invalid IL or missing references) //IL_01ea: Unknown result type (might be due to invalid IL or missing references) //IL_01f3: Unknown result type (might be due to invalid IL or missing references) //IL_0200: Unknown result type (might be due to invalid IL or missing references) //IL_0207: Unknown result type (might be due to invalid IL or missing references) //IL_020e: Unknown result type (might be due to invalid IL or missing references) //IL_0215: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)hud == (Object)null) { return; } int num = SoloPossiblePlugin.WantedInventorySlots(); 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, num); if (hud.itemSlotIcons.Length == num && hud.itemSlotIconFrames.Length == num) { CenterHudSlots(val.transform, num); return; } Image[] array = (Image[])(object)new Image[num]; Image[] array2 = (Image[])(object)new Image[num]; int num2 = Mathf.Min(4, Mathf.Min(hud.itemSlotIcons.Length, hud.itemSlotIconFrames.Length)); for (int i = 0; i < num2; i++) { array[i] = hud.itemSlotIcons[i]; array2[i] = hud.itemSlotIconFrames[i]; } 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 < num; j++) { GameObject val3 = GameObject.Find($"Systems/UI/Canvas/IngamePlayerHUD/Inventory/Slot{j}"); GameObject val4; if ((Object)(object)val3 != (Object)null) { val4 = val3; } else { val4 = Object.Instantiate(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(); if (val4.transform.childCount > 0) { array[j] = ((Component)val4.transform.GetChild(0)).GetComponent(); } } hud.itemSlotIcons = array; hud.itemSlotIconFrames = array2; CenterHudSlots(val.transform, num); SoloPossiblePlugin.Log.LogInfo((object)$"Inventory HUD resized to {num} 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) { 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 OriginalWeights = new Dictionary(); 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 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 InitializedPlayers = new HashSet(); 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 PreviousMovementSpeeds = new Dictionary(); 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.ResizePlayerInventory(__instance); InventoryManager.HandleFifthSlotHotkey(__instance); WeightManager.RefreshAllIfNeeded(); HealthManager.Tick(__instance); VisionManager.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); } } } }