Decompiled source of CruiserChargingStation v1.0.0

CruiserChargingStation.dll

Decompiled 6 hours ago
using System;
using System.Collections;
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 GameNetcodeStuff;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Unity.Netcode;
using UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.InputSystem.Controls;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyCompany("CruiserChargingStation")]
[assembly: AssemblyConfiguration("Debug")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("CruiserChargingStation")]
[assembly: AssemblyTitle("CruiserChargingStation")]
[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.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace CruiserChargingStation
{
	public class ChargingStationController : MonoBehaviour
	{
		private VehicleController _vc;

		private GameObject _stationObj;

		private Renderer _stationRenderer;

		private Light _stationLight;

		private AudioSource _audioSource;

		private Animator _stationAnimator;

		private float _lastChargeTime = -999f;

		private static readonly Color IdleColor = new Color(0.15f, 0.45f, 0.9f);

		private static readonly Color FlashColor = new Color(0.6f, 0.9f, 1f);

		private bool _showPrompt;

		private string _promptItemName;

		private void Awake()
		{
			_vc = ((Component)this).GetComponent<VehicleController>();
		}

		private void Start()
		{
			if (Plugin.UseVanillaModelConfig.Value)
			{
				SpawnVanillaStation();
			}
			if ((Object)(object)_stationObj == (Object)null)
			{
				SpawnPlaceholderStation();
			}
			if ((Object)(object)_audioSource == (Object)null || (Object)(object)_audioSource.clip == (Object)null)
			{
				TryBorrowShipChargeSound();
			}
		}

		private void OnDestroy()
		{
			if ((Object)(object)_stationObj != (Object)null)
			{
				Object.Destroy((Object)(object)_stationObj);
			}
		}

		private void SpawnVanillaStation()
		{
			//IL_0030: 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_007b: Unknown result type (might be due to invalid IL or missing references)
			ItemCharger val = Object.FindObjectOfType<ItemCharger>();
			if ((Object)(object)val == (Object)null)
			{
				Plugin.Log.LogWarning((object)"[CruiserChargingStation] Could not find the ship's ItemCharger to clone; falling back to the placeholder model.");
				return;
			}
			Vector3 position = ((Component)val).transform.position;
			LogHierarchy("shipCharger.gameObject", ((Component)val).gameObject);
			if ((Object)(object)((Component)val).transform.parent != (Object)null)
			{
				LogHierarchy("shipCharger.transform.parent", ((Component)((Component)val).transform.parent).gameObject);
			}
			GameObject val2 = FindNearestVisibleRenderer(position, 4f);
			if ((Object)(object)val2 == (Object)null)
			{
				Plugin.Log.LogWarning((object)"[CruiserChargingStation] No visible renderer found near the charging station trigger; falling back to the placeholder model.");
				return;
			}
			GameObject val3 = Object.Instantiate<GameObject>(val2);
			((Object)val3).name = "CruiserChargingStation_VanillaClone";
			NetworkBehaviour[] componentsInChildren = val3.GetComponentsInChildren<NetworkBehaviour>(true);
			foreach (NetworkBehaviour val4 in componentsInChildren)
			{
				Object.Destroy((Object)(object)val4);
			}
			NetworkObject[] componentsInChildren2 = val3.GetComponentsInChildren<NetworkObject>(true);
			foreach (NetworkObject val5 in componentsInChildren2)
			{
				Object.Destroy((Object)(object)val5);
			}
			val3.transform.SetParent(((Component)_vc).transform, false);
			_stationObj = val3;
			ApplyPlacement();
			_stationAnimator = val3.GetComponentInChildren<Animator>(true);
			_audioSource = val3.GetComponentInChildren<AudioSource>(true);
			if ((Object)(object)_audioSource == (Object)null)
			{
				_audioSource = val3.AddComponent<AudioSource>();
				_audioSource.spatialBlend = 1f;
				_audioSource.playOnAwake = false;
			}
			Renderer[] componentsInChildren3 = val3.GetComponentsInChildren<Renderer>(true);
			Plugin.Log.LogInfo((object)$"[CruiserChargingStation] Clone spawned from '{((Object)val2).name}': renderers={componentsInChildren3.Length}, animator={(Object)(object)_stationAnimator != (Object)null}, audioSource={(Object)(object)_audioSource != (Object)null}");
			Renderer[] array = componentsInChildren3;
			foreach (Renderer val6 in array)
			{
				Plugin.Log.LogInfo((object)$"[CruiserChargingStation]   renderer '{((Object)((Component)val6).gameObject).name}' enabled={val6.enabled} activeInHierarchy={((Component)val6).gameObject.activeInHierarchy}");
			}
			bool flag = false;
			Renderer[] array2 = componentsInChildren3;
			foreach (Renderer val7 in array2)
			{
				if (val7.enabled && ((Component)val7).gameObject.activeInHierarchy)
				{
					flag = true;
					break;
				}
			}
			if (!flag)
			{
				Plugin.Log.LogWarning((object)"[CruiserChargingStation] Clone has no enabled/visible renderers - falling back to the placeholder model instead.");
				Object.Destroy((Object)(object)val3);
				_stationObj = null;
				_stationAnimator = null;
				_audioSource = null;
			}
			else
			{
				if ((Object)(object)_stationAnimator == (Object)null)
				{
					Plugin.Log.LogWarning((object)"[CruiserChargingStation] Cloned station has no Animator - the zap animation won't play, only the sound/charge.");
				}
				Plugin.Log.LogInfo((object)"[CruiserChargingStation] Using the vanilla charging station model.");
			}
		}

		private static GameObject FindNearestVisibleRenderer(Vector3 worldPos, float radius)
		{
			//IL_003e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0043: Unknown result type (might be due to invalid IL or missing references)
			//IL_0140: Unknown result type (might be due to invalid IL or missing references)
			List<(Renderer, float)> list = new List<(Renderer, float)>();
			Renderer[] array = Object.FindObjectsOfType<Renderer>();
			foreach (Renderer val in array)
			{
				if (val.enabled && ((Component)val).gameObject.activeInHierarchy)
				{
					float num = Vector3.Distance(((Component)val).transform.position, worldPos);
					if (num <= radius)
					{
						list.Add((val, num));
					}
				}
			}
			list.Sort(((Renderer renderer, float dist) a, (Renderer renderer, float dist) b) => a.dist.CompareTo(b.dist));
			Plugin.Log.LogInfo((object)$"[CruiserChargingStation] Found {list.Count} enabled renderer(s) within {radius}m of the charging trigger:");
			foreach (var (val2, num2) in list)
			{
				Plugin.Log.LogInfo((object)string.Format("[CruiserChargingStation]   '{0}' (parent: '{1}') dist={2:0.00}m bounds={3}", ((Object)((Component)val2).gameObject).name, ((Object)(object)((Component)val2).transform.parent != (Object)null) ? ((Object)((Component)val2).transform.parent).name : "-", num2, val2.bounds));
			}
			if (list.Count == 0)
			{
				return null;
			}
			foreach (var item3 in list)
			{
				Renderer item = item3.Item1;
				if (((Object)((Component)item).gameObject).name.IndexOf("ChargeStation", StringComparison.OrdinalIgnoreCase) >= 0 && (Object)(object)((Component)item).gameObject.GetComponent<Animator>() != (Object)null)
				{
					Plugin.Log.LogInfo((object)("[CruiserChargingStation] Selected '" + ((Object)((Component)item).gameObject).name + "' by name+Animator match."));
					return ((Component)item).gameObject;
				}
			}
			foreach (var item4 in list)
			{
				Renderer item2 = item4.Item1;
				Transform val3 = (((Object)(object)((Component)item2).transform.root == (Object)(object)((Component)item2).transform) ? ((Component)item2).transform : FindReasonableRoot(((Component)item2).transform));
				int num3 = ((Component)val3).GetComponentsInChildren<Renderer>(true).Length;
				if (num3 > 1 && num3 <= 15)
				{
					Plugin.Log.LogInfo((object)$"[CruiserChargingStation] Selected '{((Object)val3).name}' as a reasonably-sized parent ({num3} renderers).");
					return ((Component)val3).gameObject;
				}
			}
			Plugin.Log.LogInfo((object)("[CruiserChargingStation] Falling back to closest single renderer: '" + ((Object)((Component)list[0].Item1).gameObject).name + "'."));
			return ((Component)list[0].Item1).gameObject;
		}

		private static Transform FindReasonableRoot(Transform t)
		{
			Transform val = t;
			for (int i = 0; i < 3; i++)
			{
				if ((Object)(object)val.parent == (Object)null)
				{
					break;
				}
				if ((Object)(object)((Component)val.parent).GetComponent<NetworkObject>() != (Object)null)
				{
					break;
				}
				val = val.parent;
			}
			return val;
		}

		private static void LogHierarchy(string label, GameObject root)
		{
			Plugin.Log.LogInfo((object)("[CruiserChargingStation] --- Hierarchy dump: " + label + " = '" + ((Object)root).name + "' ---"));
			DumpRecursive(root.transform, 0);
		}

		private static void DumpRecursive(Transform t, int depth)
		{
			string text = new string(' ', depth * 2);
			Component[] components = ((Component)t).GetComponents<Component>();
			List<string> list = new List<string>();
			Component[] array = components;
			foreach (Component val in array)
			{
				if ((Object)(object)val != (Object)null)
				{
					list.Add(((object)val).GetType().Name);
				}
			}
			Plugin.Log.LogInfo((object)string.Format("[CruiserChargingStation] {0}{1} [{2}] active={3}", text, ((Object)t).name, string.Join(", ", list), ((Component)t).gameObject.activeSelf));
			for (int j = 0; j < t.childCount; j++)
			{
				DumpRecursive(t.GetChild(j), depth + 1);
			}
		}

		private void SpawnPlaceholderStation()
		{
			//IL_0075: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c6: Expected O, but got Unknown
			//IL_00d4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ea: Expected O, but got Unknown
			//IL_0117: Unknown result type (might be due to invalid IL or missing references)
			//IL_0163: Unknown result type (might be due to invalid IL or missing references)
			_stationObj = GameObject.CreatePrimitive((PrimitiveType)3);
			((Object)_stationObj).name = "CruiserChargingStation_Placeholder";
			_stationObj.transform.SetParent(((Component)_vc).transform, false);
			Collider component = _stationObj.GetComponent<Collider>();
			if ((Object)(object)component != (Object)null)
			{
				Object.Destroy((Object)(object)component);
			}
			_stationObj.transform.localScale = new Vector3(0.4f, 0.5f, 0.3f);
			ApplyPlacement();
			_stationRenderer = _stationObj.GetComponent<Renderer>();
			Shader val = Shader.Find("HDRP/Lit") ?? Shader.Find("Universal Render Pipeline/Lit") ?? Shader.Find("Standard");
			Material material = new Material(val);
			_stationRenderer.material = material;
			SetColor(IdleColor);
			GameObject val2 = new GameObject("Light");
			val2.transform.SetParent(_stationObj.transform, false);
			val2.transform.localPosition = new Vector3(0f, 0.3f, 0f);
			_stationLight = val2.AddComponent<Light>();
			_stationLight.type = (LightType)2;
			_stationLight.range = 2f;
			_stationLight.intensity = 0.6f;
			_stationLight.color = IdleColor;
			_audioSource = _stationObj.AddComponent<AudioSource>();
			_audioSource.spatialBlend = 1f;
			_audioSource.playOnAwake = false;
			_audioSource.volume = 0.8f;
		}

		private void ApplyPlacement()
		{
			//IL_003c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0066: Unknown result type (might be due to invalid IL or missing references)
			if (!((Object)(object)_stationObj == (Object)null))
			{
				_stationObj.transform.localPosition = new Vector3(Plugin.OffsetXConfig.Value, Plugin.OffsetYConfig.Value, Plugin.OffsetZConfig.Value);
				_stationObj.transform.localRotation = Quaternion.Euler(0f, Plugin.OffsetYawConfig.Value, 0f);
			}
		}

		private void TryBorrowShipChargeSound()
		{
			if (Plugin.ReuseShipChargeSoundConfig.Value)
			{
				ItemCharger val = Object.FindObjectOfType<ItemCharger>();
				if ((Object)(object)val != (Object)null && (Object)(object)val.zapAudio != (Object)null && (Object)(object)val.zapAudio.clip != (Object)null)
				{
					_audioSource.clip = val.zapAudio.clip;
					Plugin.Log.LogInfo((object)"[CruiserChargingStation] Reusing the ship's charge station sound effect.");
				}
				else
				{
					Plugin.Log.LogWarning((object)"[CruiserChargingStation] Could not find the ship's ItemCharger to borrow its sound; charging will be silent.");
				}
			}
		}

		private void Update()
		{
			//IL_004b: Unknown result type (might be due to invalid IL or missing references)
			//IL_005b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0144: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)_vc == (Object)null || (Object)(object)_stationObj == (Object)null)
			{
				return;
			}
			ApplyPlacement();
			if (!TryGetLocalPlayer(out var player))
			{
				return;
			}
			float num = Vector3.Distance(((Component)player).transform.position, _stationObj.transform.position);
			bool flag = num <= Plugin.ChargeRangeConfig.Value;
			GrabbableObject currentlyHeldObjectServer = player.currentlyHeldObjectServer;
			bool flag2 = (Object)(object)currentlyHeldObjectServer != (Object)null && (Object)(object)currentlyHeldObjectServer.itemProperties != (Object)null && currentlyHeldObjectServer.itemProperties.requiresBattery;
			bool flag3 = Time.time - _lastChargeTime < Plugin.ChargeCooldownConfig.Value;
			bool flag4 = flag && flag2 && !flag3;
			if (Plugin.ShowPromptConfig.Value)
			{
				_showPrompt = flag && flag2 && !flag3;
				_promptItemName = (flag2 ? currentlyHeldObjectServer.itemProperties.itemName : null);
			}
			else
			{
				_showPrompt = false;
			}
			if (flag4 && TryGetKey(Plugin.InteractKeyConfig.Value, out var key))
			{
				Keyboard current = Keyboard.current;
				if (current != null && ((ButtonControl)current[key]).wasPressedThisFrame)
				{
					ChargeHeldItem(currentlyHeldObjectServer);
				}
			}
		}

		private static bool TryGetLocalPlayer(out PlayerControllerB player)
		{
			player = null;
			try
			{
				player = GameNetworkManager.Instance.localPlayerController;
				return (Object)(object)player != (Object)null;
			}
			catch
			{
				return false;
			}
		}

		private static bool TryGetKey(string name, out Key key)
		{
			return Enum.TryParse<Key>(name, ignoreCase: true, out key);
		}

		private void ChargeHeldItem(GrabbableObject item)
		{
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			//IL_001d: Expected O, but got Unknown
			_lastChargeTime = Time.time;
			item.insertedBattery = new Battery(false, 1f);
			item.SyncBatteryServerRpc(100);
			if ((Object)(object)_audioSource != (Object)null && (Object)(object)_audioSource.clip != (Object)null)
			{
				_audioSource.PlayOneShot(_audioSource.clip);
			}
			((MonoBehaviour)this).StopAllCoroutines();
			((MonoBehaviour)this).StartCoroutine(FlashRoutine());
			Plugin.Log.LogInfo((object)("[CruiserChargingStation] Charged '" + item.itemProperties.itemName + "'."));
		}

		private IEnumerator FlashRoutine()
		{
			if ((Object)(object)_stationAnimator != (Object)null)
			{
				_stationAnimator.SetTrigger("zap");
			}
			else
			{
				SetColor(FlashColor);
				if ((Object)(object)_stationLight != (Object)null)
				{
					_stationLight.intensity = 2.5f;
				}
			}
			yield return (object)new WaitForSeconds(0.3f);
			if ((Object)(object)_stationAnimator == (Object)null)
			{
				SetColor(IdleColor);
				if ((Object)(object)_stationLight != (Object)null)
				{
					_stationLight.intensity = 0.6f;
				}
			}
		}

		private void SetColor(Color c)
		{
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			//IL_003c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0042: Unknown result type (might be due to invalid IL or missing references)
			//IL_0062: Unknown result type (might be due to invalid IL or missing references)
			//IL_0068: Unknown result type (might be due to invalid IL or missing references)
			if (!((Object)(object)_stationRenderer == (Object)null))
			{
				Material material = _stationRenderer.material;
				material.color = c;
				if (material.HasProperty("_EmissiveColor"))
				{
					material.SetColor("_EmissiveColor", c * 1.5f);
				}
				if (material.HasProperty("_EmissionColor"))
				{
					material.SetColor("_EmissionColor", c * 1.5f);
				}
			}
		}

		private void OnGUI()
		{
			//IL_0065: Unknown result type (might be due to invalid IL or missing references)
			//IL_006a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0073: 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_0084: Expected O, but got Unknown
			//IL_008a: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bd: Unknown result type (might be due to invalid IL or missing references)
			if (_showPrompt)
			{
				string text = (string.IsNullOrEmpty(_promptItemName) ? ("Press " + Plugin.InteractKeyConfig.Value + " to charge") : ("Press " + Plugin.InteractKeyConfig.Value + " to charge " + _promptItemName));
				GUIStyle val = new GUIStyle(GUI.skin.label)
				{
					fontSize = 20,
					alignment = (TextAnchor)4,
					fontStyle = (FontStyle)1
				};
				val.normal.textColor = Color.white;
				GUI.Label(new Rect((float)Screen.width / 2f - 250f, (float)(Screen.height - 140), 500f, 30f), text, val);
			}
		}
	}
	[HarmonyPatch(typeof(VehicleController))]
	internal static class VehicleControllerPatches
	{
		[HarmonyPatch("Awake")]
		[HarmonyPostfix]
		private static void Awake_Postfix(VehicleController __instance)
		{
			if ((Object)(object)((Component)__instance).GetComponent<ChargingStationController>() == (Object)null)
			{
				((Component)__instance).gameObject.AddComponent<ChargingStationController>();
			}
		}
	}
	[BepInPlugin("justrat.cruiserchargingstation", "Cruiser Charging Station", "1.5.0")]
	public class Plugin : BaseUnityPlugin
	{
		public const string PluginGuid = "justrat.cruiserchargingstation";

		public const string PluginName = "Cruiser Charging Station";

		public const string PluginVersion = "1.5.0";

		internal static ManualLogSource Log;

		internal static ConfigEntry<string> InteractKeyConfig;

		internal static ConfigEntry<float> ChargeRangeConfig;

		internal static ConfigEntry<float> ChargeCooldownConfig;

		internal static ConfigEntry<float> OffsetXConfig;

		internal static ConfigEntry<float> OffsetYConfig;

		internal static ConfigEntry<float> OffsetZConfig;

		internal static ConfigEntry<float> OffsetYawConfig;

		internal static ConfigEntry<bool> ShowPromptConfig;

		internal static ConfigEntry<bool> ReuseShipChargeSoundConfig;

		internal static ConfigEntry<bool> UseVanillaModelConfig;

		private void Awake()
		{
			//IL_016d: Unknown result type (might be due to invalid IL or missing references)
			Log = ((BaseUnityPlugin)this).Logger;
			InteractKeyConfig = ((BaseUnityPlugin)this).Config.Bind<string>("Keybinds", "InteractKey", "E", "Key used to charge the item currently in your hands (key name from UnityEngine.InputSystem.Key).");
			ChargeRangeConfig = ((BaseUnityPlugin)this).Config.Bind<float>("Charging", "ChargeRange", 2.5f, "How close (meters) you need to be to the charging station to use it.");
			ChargeCooldownConfig = ((BaseUnityPlugin)this).Config.Bind<float>("Charging", "ChargeCooldown", 1f, "Minimum seconds between charges, to avoid accidental double-charging.");
			OffsetXConfig = ((BaseUnityPlugin)this).Config.Bind<float>("Placement", "OffsetX", -1.1f, "Local X offset of the charging station relative to the cruiser (confirmed good spot: left side, near the window).");
			OffsetYConfig = ((BaseUnityPlugin)this).Config.Bind<float>("Placement", "OffsetY", 0.8f, "Local Y offset (height) of the charging station relative to the cruiser.");
			OffsetZConfig = ((BaseUnityPlugin)this).Config.Bind<float>("Placement", "OffsetZ", -0.1f, "Local Z offset (forward/back) of the charging station relative to the cruiser.");
			OffsetYawConfig = ((BaseUnityPlugin)this).Config.Bind<float>("Placement", "OffsetYaw", 180f, "Extra rotation (degrees) applied to the charging station around the vertical axis, so it faces into the cabin.");
			ShowPromptConfig = ((BaseUnityPlugin)this).Config.Bind<bool>("Visuals", "ShowPrompt", true, "Show an on-screen 'Press E to charge <item>' prompt when in range holding a chargeable item.");
			ReuseShipChargeSoundConfig = ((BaseUnityPlugin)this).Config.Bind<bool>("Visuals", "ReuseShipChargeSound", true, "Try to reuse the ship's own charging station zap sound instead of playing no sound at all.");
			UseVanillaModelConfig = ((BaseUnityPlugin)this).Config.Bind<bool>("Visuals", "UseVanillaModel", true, "Clone the ship's real charging station model/animation instead of using a simple placeholder cube.");
			new Harmony("justrat.cruiserchargingstation").PatchAll();
			Log.LogInfo((object)"Cruiser Charging Station v1.5.0 loaded.");
		}
	}
}