Decompiled source of Chainsaw BONELAB Code Mod v1.3.1

Mods/ChainsawBONELABCodeMod.dll

Decompiled 2 days ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using BoneLib;
using BoneLib.BoneMenu;
using ChainsawBONELABCodeMod;
using Il2CppInterop.Runtime.InteropTypes.Arrays;
using Il2CppSLZ.Marrow;
using Il2CppSLZ.Marrow.AI;
using MelonLoader;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: MelonInfo(typeof(Main), "Chainsaw BONELAB Code Mod", "1.3.1", "TankFullOfOofs Port", null)]
[assembly: MelonGame("Stress Level Zero", "BONELAB")]
[assembly: TargetFramework(".NETCoreApp,Version=v8.0", FrameworkDisplayName = ".NET 8.0")]
[assembly: AssemblyCompany("ChainsawBONELABCodeMod")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0+8887163c740aae2e742422dd452ade3ea13b7ce5")]
[assembly: AssemblyProduct("ChainsawBONELABCodeMod")]
[assembly: AssemblyTitle("ChainsawBONELABCodeMod")]
[assembly: AssemblyVersion("1.0.0.0")]
[module: RefSafetyRules(11)]
namespace ChainsawBONELABCodeMod;

internal static class ChainsawMenu
{
	private static Page page;

	private static bool initialized;

	public static void Setup()
	{
		//IL_0013: Unknown result type (might be due to invalid IL or missing references)
		//IL_002e: 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_0096: 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)
		//IL_0108: Unknown result type (might be due to invalid IL or missing references)
		//IL_0150: Unknown result type (might be due to invalid IL or missing references)
		//IL_0198: Unknown result type (might be due to invalid IL or missing references)
		//IL_01e0: Unknown result type (might be due to invalid IL or missing references)
		if (initialized)
		{
			return;
		}
		try
		{
			page = Page.Root.CreatePage("Chainsaw", Color.red, 0, true);
			page.CreateFunction("Spawn Chainsaw", Color.green, (Action)delegate
			{
				MelonLogger.Msg("[Chainsaw] Spawn button pressed.");
				try
				{
					ChainsawSpawner.Spawn();
					MelonLogger.Msg("[Chainsaw] Spawn callback finished.");
				}
				catch (Exception ex2)
				{
					MelonLogger.Error("[Chainsaw] Spawn callback crashed: " + ex2);
				}
			});
			page.CreateFunction("Despawn Chainsaws", Color.red, (Action)delegate
			{
				MelonLogger.Msg("[Chainsaw] Despawn button pressed.");
				ChainsawSpawner.DespawnAll();
			});
			page.CreateBool("Enabled", Color.green, Config.Enabled, (Action<bool>)delegate(bool value)
			{
				Config.Enabled = value;
			});
			page.CreateBool("Motor Sound", Color.yellow, Config.MotorSound, (Action<bool>)delegate(bool value)
			{
				Config.MotorSound = value;
			});
			page.CreateFloat("Damage", Color.red, Config.Damage, 1f, 0f, 50f, (Action<float>)delegate(float value)
			{
				Config.Damage = value;
			});
			page.CreateFloat("Blade Speed", Color.cyan, Config.BladeDegreesPerSecond, 100f, 0f, 5000f, (Action<float>)delegate(float value)
			{
				Config.BladeDegreesPerSecond = value;
			});
			page.CreateFloat("Damage Interval", Color.magenta, Config.DamageInterval, 0.025f, 0.025f, 0.5f, (Action<float>)delegate(float value)
			{
				Config.DamageInterval = value;
			});
			page.CreateBool("Debug Mode", Color.white, Config.DebugMode, (Action<bool>)delegate(bool value)
			{
				Config.DebugMode = value;
			});
			initialized = true;
			MelonLogger.Msg("[Chainsaw] BoneMenu page created.");
		}
		catch (Exception ex)
		{
			MelonLogger.Error("[Chainsaw] BoneMenu setup failed: " + ex);
		}
	}
}
internal static class ChainsawSpawner
{
	private static AssetBundle bundle;

	private static GameObject prefab;

	private static readonly List<GameObject> spawnedChainsaws = new List<GameObject>();

	public static void Initialize()
	{
		if ((Object)(object)prefab != (Object)null)
		{
			return;
		}
		try
		{
			string text = FindBundlePath();
			if (string.IsNullOrEmpty(text))
			{
				MelonLogger.Warning("[Chainsaw] chainsaw.bundle was not found in any expected Quest Mods path.");
				MelonLogger.Warning("[Chainsaw] Expected: " + Path.Combine(Application.persistentDataPath, "Mods", "Chainsaw", "chainsaw.bundle"));
				return;
			}
			MelonLogger.Msg("[Chainsaw] Loading bundle from: " + text);
			bundle = AssetBundle.LoadFromFile(text);
			if ((Object)(object)bundle == (Object)null)
			{
				MelonLogger.Error("[Chainsaw] Failed to load chainsaw.bundle. The old bundle may be incompatible with the current BONELAB/Unity runtime.");
				return;
			}
			string[] array = Il2CppArrayBase<string>.op_Implicit((Il2CppArrayBase<string>)(object)bundle.GetAllAssetNames());
			if (array != null)
			{
				MelonLogger.Msg("[Chainsaw] Bundle asset count: " + array.Length);
				string[] array2 = array;
				foreach (string text2 in array2)
				{
					MelonLogger.Msg("[Chainsaw] Bundle asset: " + text2);
				}
			}
			prefab = TryLoadPrefab("Chainsaw.prefab");
			if ((Object)(object)prefab == (Object)null)
			{
				prefab = TryLoadPrefab("Chainsaw");
			}
			if ((Object)(object)prefab == (Object)null && array != null)
			{
				string[] array2 = array;
				foreach (string text3 in array2)
				{
					if (!string.IsNullOrEmpty(text3))
					{
						GameObject val = TryLoadPrefab(text3);
						if ((Object)(object)val != (Object)null)
						{
							prefab = val;
							MelonLogger.Msg("[Chainsaw] Using GameObject asset: " + text3);
							break;
						}
					}
				}
			}
			if ((Object)(object)prefab == (Object)null)
			{
				MelonLogger.Error("[Chainsaw] Bundle loaded, but no GameObject prefab could be loaded from it.");
			}
			else
			{
				MelonLogger.Msg("[Chainsaw] Spawn prefab loaded: " + ((Object)prefab).name);
			}
		}
		catch (Exception ex)
		{
			MelonLogger.Error("[Chainsaw] AssetBundle load failed: " + ex);
		}
	}

	private static string FindBundlePath()
	{
		string text = Path.Combine(Application.persistentDataPath, "Mods", "Chainsaw", "chainsaw.bundle");
		if (File.Exists(text))
		{
			return text;
		}
		string text2 = Path.Combine(Directory.GetCurrentDirectory(), "Mods", "Chainsaw", "chainsaw.bundle");
		if (File.Exists(text2))
		{
			return text2;
		}
		string text3 = "/storage/emulated/0/Android/data/com.StressLevelZero.BONELAB/files/Mods/Chainsaw/chainsaw.bundle";
		if (File.Exists(text3))
		{
			return text3;
		}
		return null;
	}

	private static GameObject TryLoadPrefab(string assetName)
	{
		try
		{
			return bundle.LoadAsset<GameObject>(assetName);
		}
		catch
		{
			return null;
		}
	}

	public unsafe static void Spawn()
	{
		//IL_005c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0067: Unknown result type (might be due to invalid IL or missing references)
		//IL_0071: Unknown result type (might be due to invalid IL or missing references)
		//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_0082: Unknown result type (might be due to invalid IL or missing references)
		//IL_0087: 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_0091: Unknown result type (might be due to invalid IL or missing references)
		//IL_0097: Unknown result type (might be due to invalid IL or missing references)
		//IL_0098: Unknown result type (might be due to invalid IL or missing references)
		//IL_00d5: Unknown result type (might be due to invalid IL or missing references)
		//IL_00d6: Unknown result type (might be due to invalid IL or missing references)
		if (!Config.Enabled)
		{
			MelonLogger.Warning("[Chainsaw] Mod is disabled in BoneMenu.");
			return;
		}
		if ((Object)(object)prefab == (Object)null)
		{
			Initialize();
		}
		if ((Object)(object)prefab == (Object)null)
		{
			MelonLogger.Warning("[Chainsaw] Can't spawn because chainsaw.bundle is missing, incompatible, or contains no loadable GameObject prefab.");
			return;
		}
		Camera main = Camera.main;
		if ((Object)(object)main == (Object)null)
		{
			MelonLogger.Warning("[Chainsaw] Main camera/player view is not ready.");
			return;
		}
		Vector3 val = ((Component)main).transform.position + ((Component)main).transform.forward * 1.5f;
		Quaternion val2 = Quaternion.LookRotation(((Component)main).transform.forward, Vector3.up);
		GameObject val3 = Object.Instantiate<GameObject>(prefab, val, val2);
		if ((Object)(object)val3 == (Object)null)
		{
			MelonLogger.Error("[Chainsaw] Instantiate returned null.");
			return;
		}
		((Object)val3).name = "Chainsaw";
		val3.SetActive(true);
		spawnedChainsaws.Add(val3);
		Vector3 val4 = val;
		MelonLogger.Msg("[Chainsaw] Spawned Chainsaw from BoneMenu at " + ((object)(*(Vector3*)(&val4))/*cast due to .constrained prefix*/).ToString());
	}

	public static void DespawnAll()
	{
		for (int num = spawnedChainsaws.Count - 1; num >= 0; num--)
		{
			GameObject val = spawnedChainsaws[num];
			if ((Object)(object)val != (Object)null)
			{
				Object.Destroy((Object)(object)val);
			}
		}
		spawnedChainsaws.Clear();
		MelonLogger.Msg("[Chainsaw] Despawned all BoneMenu-spawned Chainsaws.");
	}
}
internal static class Config
{
	public static bool Enabled = true;

	public static float BladeDegreesPerSecond = 1800f;

	public static float Damage = 7.5f;

	public static float DamageInterval = 0.075f;

	public static bool MotorSound = true;

	public static bool DebugMode = false;
}
public sealed class Main : MelonMod
{
	private sealed class ChainsawRuntime
	{
		private readonly GameObject root;

		private readonly List<Transform> blades = new List<Transform>();

		private readonly List<Collider> bladeColliders = new List<Collider>();

		private readonly List<AudioSource> motorSounds = new List<AudioSource>();

		private bool running;

		public int BladeCount => blades.Count;

		public int BladeColliderCount => bladeColliders.Count;

		public int MotorSoundCount => motorSounds.Count;

		public ChainsawRuntime(GameObject rootObject)
		{
			root = rootObject;
			Transform[] array = Il2CppArrayBase<Transform>.op_Implicit(root.GetComponentsInChildren<Transform>(true));
			foreach (Transform val in array)
			{
				if (!((Object)(object)val == (Object)null))
				{
					string text = ((Object)val).name ?? string.Empty;
					if (text.IndexOf("BladeTransform", StringComparison.OrdinalIgnoreCase) >= 0 || string.Equals(text, "Blade", StringComparison.OrdinalIgnoreCase) || text.IndexOf("slashTop", StringComparison.OrdinalIgnoreCase) >= 0 || text.IndexOf("slashBottom", StringComparison.OrdinalIgnoreCase) >= 0)
					{
						blades.Add(val);
					}
				}
			}
			Collider[] array2 = Il2CppArrayBase<Collider>.op_Implicit(root.GetComponentsInChildren<Collider>(true));
			foreach (Collider val2 in array2)
			{
				if (!((Object)(object)val2 == (Object)null))
				{
					string text2 = ((Object)val2).name ?? string.Empty;
					if (text2.IndexOf("BladeCollider", StringComparison.OrdinalIgnoreCase) >= 0 || text2.IndexOf("Blade", StringComparison.OrdinalIgnoreCase) >= 0 || text2.IndexOf("slashTop", StringComparison.OrdinalIgnoreCase) >= 0 || text2.IndexOf("slashBottom", StringComparison.OrdinalIgnoreCase) >= 0 || text2.IndexOf("StabPoint", StringComparison.OrdinalIgnoreCase) >= 0)
					{
						bladeColliders.Add(val2);
					}
				}
			}
			AudioSource[] array3 = Il2CppArrayBase<AudioSource>.op_Implicit(root.GetComponentsInChildren<AudioSource>(true));
			foreach (AudioSource val3 in array3)
			{
				if (!((Object)(object)val3 == (Object)null))
				{
					string text3 = ((Object)val3).name ?? string.Empty;
					if (text3.IndexOf("IdleSound", StringComparison.OrdinalIgnoreCase) >= 0 || text3.IndexOf("BladeAudio", StringComparison.OrdinalIgnoreCase) >= 0 || text3.IndexOf("Motor", StringComparison.OrdinalIgnoreCase) >= 0 || text3.IndexOf("Chainsaw", StringComparison.OrdinalIgnoreCase) >= 0 || text3.IndexOf("Idle", StringComparison.OrdinalIgnoreCase) >= 0)
					{
						motorSounds.Add(val3);
					}
				}
			}
		}

		public void Start()
		{
			running = true;
			if (!Config.MotorSound)
			{
				return;
			}
			foreach (AudioSource motorSound in motorSounds)
			{
				try
				{
					motorSound.loop = true;
					if ((Object)(object)motorSound.clip != (Object)null && !motorSound.isPlaying)
					{
						motorSound.Play();
					}
				}
				catch (Exception ex)
				{
					MelonLogger.Warning("Chainsaw audio start failed: " + ex.Message);
				}
			}
		}

		public void Stop()
		{
			running = false;
			foreach (AudioSource motorSound in motorSounds)
			{
				try
				{
					if ((Object)(object)motorSound != (Object)null && motorSound.isPlaying)
					{
						motorSound.Stop();
					}
				}
				catch
				{
				}
			}
		}

		public void Tick(float dt)
		{
			//IL_0037: Unknown result type (might be due to invalid IL or missing references)
			if (!running || (Object)(object)root == (Object)null)
			{
				return;
			}
			foreach (Transform blade in blades)
			{
				if ((Object)(object)blade != (Object)null)
				{
					blade.Rotate(Vector3.forward, Config.BladeDegreesPerSecond * dt, (Space)1);
				}
			}
		}

		public void DamageNearby()
		{
			//IL_004e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0053: Unknown result type (might be due to invalid IL or missing references)
			//IL_0057: 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_0067: Unknown result type (might be due to invalid IL or missing references)
			//IL_006b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0070: Unknown result type (might be due to invalid IL or missing references)
			if (!running || (Object)(object)root == (Object)null)
			{
				return;
			}
			HashSet<AIBrain> hashSet = new HashSet<AIBrain>();
			foreach (Collider bladeCollider in bladeColliders)
			{
				if ((Object)(object)bladeCollider == (Object)null || !bladeCollider.enabled)
				{
					continue;
				}
				Bounds bounds = bladeCollider.bounds;
				Vector3 center = ((Bounds)(ref bounds)).center;
				bounds = bladeCollider.bounds;
				Vector3 extents = ((Bounds)(ref bounds)).extents;
				float num = Mathf.Max(0.035f, ((Vector3)(ref extents)).magnitude * 0.45f);
				Collider[] array = Il2CppArrayBase<Collider>.op_Implicit((Il2CppArrayBase<Collider>)(object)Physics.OverlapSphere(center, num));
				foreach (Collider val in array)
				{
					if ((Object)(object)val == (Object)null || ((Component)val).transform.IsChildOf(root.transform))
					{
						continue;
					}
					AIBrain componentInParent = ((Component)val).GetComponentInParent<AIBrain>();
					if (!((Object)(object)componentInParent == (Object)null) && hashSet.Add(componentInParent))
					{
						try
						{
							Extensions.DealDamage(componentInParent, Config.Damage);
						}
						catch (Exception ex)
						{
							MelonLogger.Warning("Chainsaw damage failed: " + ex.Message);
						}
					}
				}
			}
		}
	}

	private readonly Dictionary<Hand, ChainsawRuntime> held = new Dictionary<Hand, ChainsawRuntime>();

	private float damageTimer;

	public override void OnInitializeMelon()
	{
		MelonLogger.Msg("[Chainsaw] v1.3.1 SPAWN TEST BUILD LOADED");
		Hooking.OnGrabObject += OnGrab;
		Hooking.OnReleaseObject += OnRelease;
		ChainsawSpawner.Initialize();
		ChainsawMenu.Setup();
		MelonLogger.Msg("Chainsaw BONELAB Code Mod 1.3.1 initialized. BoneMenu spawning support enabled.");
	}

	public override void OnDeinitializeMelon()
	{
		Hooking.OnGrabObject -= OnGrab;
		Hooking.OnReleaseObject -= OnRelease;
		foreach (ChainsawRuntime value in held.Values)
		{
			value.Stop();
		}
		held.Clear();
		ChainsawSpawner.DespawnAll();
	}

	public override void OnUpdate()
	{
		if (!Config.Enabled)
		{
			return;
		}
		float deltaTime = Time.deltaTime;
		damageTimer -= deltaTime;
		foreach (ChainsawRuntime value in held.Values)
		{
			value.Tick(deltaTime);
		}
		if (damageTimer > 0f)
		{
			return;
		}
		damageTimer = Mathf.Max(0.025f, Config.DamageInterval);
		foreach (ChainsawRuntime value2 in held.Values)
		{
			value2.DamageNearby();
		}
	}

	private void OnGrab(GameObject objectToAttach, Hand hand)
	{
		if (!Config.Enabled || (Object)(object)objectToAttach == (Object)null || (Object)(object)hand == (Object)null)
		{
			return;
		}
		GameObject val = ResolveChainsawRoot(objectToAttach);
		if (!((Object)(object)val == (Object)null))
		{
			if (held.TryGetValue(hand, out var value))
			{
				value.Stop();
			}
			ChainsawRuntime chainsawRuntime = new ChainsawRuntime(val);
			held[hand] = chainsawRuntime;
			chainsawRuntime.Start();
			MelonLogger.Msg("Chainsaw detected: " + ((Object)val).name + ". Blades=" + chainsawRuntime.BladeCount + ", BladeColliders=" + chainsawRuntime.BladeColliderCount + ", MotorSounds=" + chainsawRuntime.MotorSoundCount);
		}
	}

	private void OnRelease(Hand hand)
	{
		if (!((Object)(object)hand == (Object)null) && held.TryGetValue(hand, out var value))
		{
			value.Stop();
			held.Remove(hand);
			MelonLogger.Msg("Chainsaw released; runtime behavior disabled.");
		}
	}

	private static bool LooksLikeChainsawName(string value)
	{
		if (string.IsNullOrEmpty(value))
		{
			return false;
		}
		if (value.IndexOf("chainsaw", StringComparison.OrdinalIgnoreCase) < 0 && value.IndexOf("DoomHunterChainsaw", StringComparison.OrdinalIgnoreCase) < 0)
		{
			return value.IndexOf("Doom Hunter Chainsaw", StringComparison.OrdinalIgnoreCase) >= 0;
		}
		return true;
	}

	private static bool IsDoomHunterMarker(string value)
	{
		if (string.IsNullOrEmpty(value))
		{
			return false;
		}
		if (value.IndexOf("slashTop", StringComparison.OrdinalIgnoreCase) < 0 && value.IndexOf("slashBottom", StringComparison.OrdinalIgnoreCase) < 0 && value.IndexOf("Pull Cord", StringComparison.OrdinalIgnoreCase) < 0)
		{
			return value.IndexOf("StabPoint", StringComparison.OrdinalIgnoreCase) >= 0;
		}
		return true;
	}

	private static GameObject ResolveChainsawRoot(GameObject grabbed)
	{
		Transform val = grabbed.transform;
		GameObject val2 = null;
		while ((Object)(object)val != (Object)null)
		{
			if (LooksLikeChainsawName(((Object)val).name))
			{
				val2 = ((Component)val).gameObject;
			}
			val = val.parent;
		}
		if ((Object)(object)val2 != (Object)null)
		{
			return val2;
		}
		Transform val3 = grabbed.transform;
		while ((Object)(object)val3.parent != (Object)null && (Object)(object)val3.parent.parent != (Object)null)
		{
			val3 = val3.parent;
		}
		Transform[] array = Il2CppArrayBase<Transform>.op_Implicit(((Component)val3).GetComponentsInChildren<Transform>(true));
		int num = 0;
		bool flag = false;
		bool flag2 = false;
		Transform[] array2 = array;
		foreach (Transform val4 in array2)
		{
			if (!((Object)(object)val4 == (Object)null))
			{
				string text = ((Object)val4).name ?? string.Empty;
				if (IsDoomHunterMarker(text))
				{
					num++;
				}
				if (text.IndexOf("Blade", StringComparison.OrdinalIgnoreCase) >= 0 || text.IndexOf("slashTop", StringComparison.OrdinalIgnoreCase) >= 0 || text.IndexOf("slashBottom", StringComparison.OrdinalIgnoreCase) >= 0)
				{
					flag = true;
				}
				if (text.IndexOf("Grip", StringComparison.OrdinalIgnoreCase) >= 0)
				{
					flag2 = true;
				}
			}
		}
		if (num >= 2 && flag && flag2)
		{
			return ((Component)val3).gameObject;
		}
		array2 = Il2CppArrayBase<Transform>.op_Implicit(grabbed.GetComponentsInChildren<Transform>(true));
		foreach (Transform val5 in array2)
		{
			if (!((Object)(object)val5 == (Object)null))
			{
				string text2 = ((Object)val5).name ?? string.Empty;
				if (text2.IndexOf("BladeTransform", StringComparison.OrdinalIgnoreCase) >= 0 || text2.IndexOf("BladeCollider", StringComparison.OrdinalIgnoreCase) >= 0 || text2.IndexOf("IdleSound", StringComparison.OrdinalIgnoreCase) >= 0)
				{
					return grabbed;
				}
			}
		}
		return null;
	}
}