Decompiled source of SoloPossible v1.0.1

SoloPossible.dll

Decompiled 9 hours ago
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.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);
			}
		}
	}
}