Decompiled source of Toss a Tick v1.0.0

tony4twentys-Toss a Tick.dll

Decompiled 3 days ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using PEAK_Ticks;
using Photon.Pun;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: AssemblyTitle("Toss a Tick")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("Toss a Tick")]
[assembly: AssemblyCopyright("Copyright ©  2026")]
[assembly: AssemblyTrademark("")]
[assembly: ComVisible(false)]
[assembly: Guid("361e37c6-51ce-4513-a083-416b8a7fc050")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyVersion("1.0.0.0")]
namespace Toss_a_Tick;

internal static class PeakticksIntegration
{
	public static bool ShouldUsePeakticksRules()
	{
		if (!Chainloader.PluginInfos.TryGetValue("tony4twentys.PEAK_Ticks", out var value) || (Object)(object)value.Instance == (Object)null)
		{
			return false;
		}
		return PeakticksInterop.IsGameplayEnabled;
	}

	public static bool CanAttachAnotherTick(Character character)
	{
		if (ShouldUsePeakticksRules())
		{
			return PeakticksInterop.CanAttachAnotherTick(character);
		}
		return !TickAttachHelper.CharacterHasAttachedTick(character);
	}

	public static bool TryAttachTick(Character target, out int tickNumber)
	{
		tickNumber = 0;
		if (ShouldUsePeakticksRules())
		{
			return PeakticksInterop.TryAttachTick(target, "Toss a Tick", ref tickNumber);
		}
		return TickAttachHelper.SpawnAttachedTickVanilla(target);
	}
}
[BepInPlugin("tony4twentys.Toss_a_Tick", "Toss a Tick", "1.0.0")]
[BepInDependency(/*Could not decode attribute arguments.*/)]
public class TossATickPlugin : BaseUnityPlugin
{
	public const string PluginGuid = "tony4twentys.Toss_a_Tick";

	public const string PluginName = "Toss a Tick";

	public const string PluginVersion = "1.0.0";

	private Harmony _harmony;

	private ConfigEntry<bool> _logThrowDebug;

	private ConfigEntry<float> _throwSearchRadius;

	public static ManualLogSource Log { get; private set; }

	public static bool LogThrowDebug { get; private set; }

	public static float ThrowSearchRadius { get; private set; } = 2.5f;

	private void Awake()
	{
		//IL_00b5: Unknown result type (might be due to invalid IL or missing references)
		//IL_00bf: Expected O, but got Unknown
		Log = ((BaseUnityPlugin)this).Logger;
		_logThrowDebug = ((BaseUnityPlugin)this).Config.Bind<bool>("Debug", "LogThrowDebug", false, "When true, logs throw hit detection and attach attempts.");
		_throwSearchRadius = ((BaseUnityPlugin)this).Config.Bind<float>("Throw", "ThrowSearchRadius", 2.5f, "If a player is hit but the tick collider is unclear, search this radius (meters) for a recently thrown tick to consume.");
		LogThrowDebug = _logThrowDebug.Value;
		ThrowSearchRadius = Mathf.Max(0.5f, _throwSearchRadius.Value);
		_logThrowDebug.SettingChanged += delegate
		{
			LogThrowDebug = _logThrowDebug.Value;
		};
		_throwSearchRadius.SettingChanged += delegate
		{
			ThrowSearchRadius = Mathf.Max(0.5f, _throwSearchRadius.Value);
		};
		_harmony = new Harmony("tony4twentys.Toss_a_Tick");
		_harmony.PatchAll();
		bool flag = Chainloader.PluginInfos.ContainsKey("tony4twentys.PEAK_Ticks");
		((BaseUnityPlugin)this).Logger.LogInfo((object)("Toss a Tick v1.0.0 loaded. Pick off a tick, throw it at a player to attach (host required). PEAK Ticks: " + (flag ? "detected (uses host rules when authorized)" : "not installed") + ". Throw debug logging: " + (LogThrowDebug ? "on" : "off") + ". " + $"Throw search radius: {ThrowSearchRadius:0.#}m."));
	}

	private void OnDestroy()
	{
		Harmony harmony = _harmony;
		if (harmony != null)
		{
			harmony.UnpatchSelf();
		}
		Log = null;
	}
}
internal static class TickAttachHelper
{
	public const string AttachedTickPrefab = "BugfixOnYou";

	public const float DefaultThrownWindowSeconds = 8f;

	public const float DefaultSearchRadius = 2.5f;

	private static readonly Dictionary<int, float> LastHitProcessTimeByCharacterId = new Dictionary<int, float>();

	public static bool IsThrowableTickItem(Item item)
	{
		if ((Object)(object)item == (Object)null)
		{
			return false;
		}
		string name = ((Object)((Component)item).gameObject).name;
		if (name.IndexOf("OnYou", StringComparison.OrdinalIgnoreCase) >= 0)
		{
			return false;
		}
		if ((Object)(object)((Component)item).GetComponent<BugPhobia>() != (Object)null)
		{
			return true;
		}
		return name.IndexOf("Bugfix", StringComparison.OrdinalIgnoreCase) >= 0;
	}

	public static bool CharacterHasAttachedTick(Character character)
	{
		if ((Object)(object)character == (Object)null)
		{
			return false;
		}
		foreach (KeyValuePair<Bugfix, Character> allAttachedBug in Bugfix.AllAttachedBugs)
		{
			if ((Object)(object)allAttachedBug.Value == (Object)(object)character)
			{
				return true;
			}
		}
		return false;
	}

	public static Item ResolveItemFromCollision(Collision collision)
	{
		if (collision == null)
		{
			return null;
		}
		Item result = default(Item);
		if (Item.TryGetItemFromCollider(collision.collider, ref result))
		{
			return result;
		}
		if ((Object)(object)collision.rigidbody != (Object)null)
		{
			result = ((Component)collision.rigidbody).GetComponent<Item>();
			if ((Object)(object)result != (Object)null)
			{
				return result;
			}
		}
		return collision.gameObject.GetComponentInParent<Item>();
	}

	public static bool IsRecentlyThrownTick(Item item, float thrownWindowSeconds = 8f)
	{
		if ((Object)(object)item == (Object)null)
		{
			return false;
		}
		TickThrowHandler component = ((Component)item).GetComponent<TickThrowHandler>();
		if ((Object)(object)component != (Object)null && component.IsWithinThrownWindow())
		{
			return true;
		}
		return Time.time - GetLastThrownTime(item) <= thrownWindowSeconds;
	}

	private static float GetLastThrownTime(Item item)
	{
		if ((Object)(object)item == (Object)null)
		{
			return 0f;
		}
		return Traverse.Create((object)item).Field("lastThrownTime").GetValue<float>();
	}

	public static Item FindNearbyThrownTick(Character target, float searchRadius, float thrownWindowSeconds)
	{
		//IL_004c: 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_0051: 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_008a: Unknown result type (might be due to invalid IL or missing references)
		if ((Object)(object)target == (Object)null)
		{
			return null;
		}
		Vector3 val = (((Object)(object)target.refs?.hip != (Object)null) ? ((Component)target.refs.hip).transform.position : ((Component)target).transform.position);
		Item result = null;
		float num = searchRadius;
		for (int i = 0; i < Item.ALL_ACTIVE_ITEMS.Count; i++)
		{
			Item val2 = Item.ALL_ACTIVE_ITEMS[i];
			if (IsThrownTickCandidate(val2, thrownWindowSeconds))
			{
				float num2 = Vector3.Distance(((Component)val2).transform.position, val);
				if (!(num2 > num))
				{
					num = num2;
					result = val2;
				}
			}
		}
		return result;
	}

	public static bool TryProcessTickHit(Character target, Collision collision, Item preferredTickItem, float searchRadius, float thrownWindowSeconds, float minHitVelocity)
	{
		//IL_00d2: Unknown result type (might be due to invalid IL or missing references)
		//IL_00d7: Unknown result type (might be due to invalid IL or missing references)
		if (!PhotonNetwork.IsMasterClient)
		{
			return false;
		}
		if ((Object)(object)target == (Object)null || (Object)(object)target.data == (Object)null || target.data.dead)
		{
			return false;
		}
		if (!PeakticksIntegration.CanAttachAnotherTick(target))
		{
			if (TossATickPlugin.LogThrowDebug)
			{
				ManualLogSource log = TossATickPlugin.Log;
				if (log != null)
				{
					log.LogInfo((object)("[Toss a Tick] Hit " + target.characterName + ", but they cannot receive another tick."));
				}
			}
			return false;
		}
		int instanceID = ((Object)target).GetInstanceID();
		if (LastHitProcessTimeByCharacterId.TryGetValue(instanceID, out var value) && Time.time <= value + 0.25f)
		{
			return false;
		}
		if (collision != null)
		{
			Vector3 relativeVelocity = collision.relativeVelocity;
			if (((Vector3)(ref relativeVelocity)).magnitude < minHitVelocity)
			{
				return false;
			}
		}
		Item val = ResolveTickItemForHit(target, collision, preferredTickItem, searchRadius, thrownWindowSeconds);
		if ((Object)(object)val == (Object)null)
		{
			return false;
		}
		LastHitProcessTimeByCharacterId[instanceID] = Time.time;
		if (!PeakticksIntegration.TryAttachTick(target, out var _))
		{
			ManualLogSource log2 = TossATickPlugin.Log;
			if (log2 != null)
			{
				log2.LogWarning((object)("[Toss a Tick] Tick hit on " + target.characterName + ", but attach failed."));
			}
			return false;
		}
		if ((Object)(object)((MonoBehaviourPun)val).photonView != (Object)null && ((MonoBehaviourPun)val).photonView.IsMine)
		{
			PhotonNetwork.Destroy(((Component)val).gameObject);
		}
		ManualLogSource log3 = TossATickPlugin.Log;
		if (log3 != null)
		{
			log3.LogInfo((object)("[Toss a Tick] Thrown tick attached to " + target.characterName + " and removed from the world."));
		}
		return true;
	}

	public static void ArmThrownTickItem(Item item)
	{
		if ((Object)(object)item == (Object)null || !IsThrowableTickItem(item) || !PhotonNetwork.IsMasterClient || (Object)(object)((MonoBehaviourPun)item).photonView == (Object)null || !((MonoBehaviourPun)item).photonView.IsMine)
		{
			return;
		}
		TickThrowHandler tickThrowHandler = ((Component)item).GetComponent<TickThrowHandler>();
		if ((Object)(object)tickThrowHandler == (Object)null)
		{
			tickThrowHandler = ((Component)item).gameObject.AddComponent<TickThrowHandler>();
		}
		tickThrowHandler.Initialize(item);
		tickThrowHandler.MarkAsThrown();
		if (TossATickPlugin.LogThrowDebug)
		{
			ManualLogSource log = TossATickPlugin.Log;
			if (log != null)
			{
				log.LogInfo((object)("[Toss a Tick] Tracking thrown tick: " + ((Object)item).name + "."));
			}
		}
	}

	private static Item ResolveTickItemForHit(Character target, Collision collision, Item preferredTickItem, float searchRadius, float thrownWindowSeconds)
	{
		Item val = null;
		if (IsThrownTickCandidate(preferredTickItem, thrownWindowSeconds))
		{
			val = preferredTickItem;
		}
		if ((Object)(object)val == (Object)null && collision != null)
		{
			Item val2 = ResolveItemFromCollision(collision);
			if (IsThrownTickCandidate(val2, thrownWindowSeconds))
			{
				val = val2;
			}
		}
		if ((Object)(object)val == (Object)null)
		{
			val = FindNearbyThrownTick(target, searchRadius, thrownWindowSeconds);
			if ((Object)(object)val != (Object)null && TossATickPlugin.LogThrowDebug)
			{
				ManualLogSource log = TossATickPlugin.Log;
				if (log != null)
				{
					log.LogInfo((object)("[Toss a Tick] Resolved tick near " + target.characterName + " via nearby search: " + ((Object)val).name + "."));
				}
			}
		}
		if ((Object)(object)val == (Object)null)
		{
			if (TossATickPlugin.LogThrowDebug && collision != null)
			{
				Character val3 = ResolveCharacterFromCollision(collision);
				if ((Object)(object)val3 != (Object)null)
				{
					ManualLogSource log2 = TossATickPlugin.Log;
					if (log2 != null)
					{
						log2.LogInfo((object)("[Toss a Tick] Player " + val3.characterName + " was hit, but no thrown tick was found."));
					}
				}
			}
			return null;
		}
		if (TossATickPlugin.LogThrowDebug)
		{
			ManualLogSource log3 = TossATickPlugin.Log;
			if (log3 != null)
			{
				log3.LogInfo((object)("[Toss a Tick] Player hit with thrown tick item: " + ((Object)val).name + "."));
			}
		}
		return val;
	}

	private static bool IsThrownTickCandidate(Item item, float thrownWindowSeconds)
	{
		//IL_0045: Unknown result type (might be due to invalid IL or missing references)
		//IL_004b: Invalid comparison between Unknown and I4
		if ((Object)(object)item == (Object)null || (Object)(object)((MonoBehaviourPun)item).photonView == (Object)null || !((MonoBehaviourPun)item).photonView.IsMine)
		{
			return false;
		}
		if (!IsThrowableTickItem(item))
		{
			return false;
		}
		if ((int)item.itemState > 0)
		{
			return false;
		}
		return IsRecentlyThrownTick(item, thrownWindowSeconds);
	}

	internal static bool SpawnAttachedTickVanilla(Character target)
	{
		//IL_007e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0083: Unknown result type (might be due to invalid IL or missing references)
		if (!PhotonNetwork.IsMasterClient)
		{
			return false;
		}
		if ((Object)(object)target == (Object)null || (Object)(object)target.refs?.view == (Object)null || (Object)(object)target.data == (Object)null)
		{
			return false;
		}
		if (target.data.dead || CharacterHasAttachedTick(target))
		{
			return false;
		}
		GameObject val = PhotonNetwork.Instantiate("BugfixOnYou", Vector3.zero, Quaternion.identity, (byte)0, (object[])null);
		if ((Object)(object)val == (Object)null)
		{
			ManualLogSource log = TossATickPlugin.Log;
			if (log != null)
			{
				log.LogWarning((object)"[Toss a Tick] Failed to instantiate BugfixOnYou.");
			}
			return false;
		}
		PhotonView component = val.GetComponent<PhotonView>();
		if ((Object)(object)component == (Object)null)
		{
			ManualLogSource log2 = TossATickPlugin.Log;
			if (log2 != null)
			{
				log2.LogWarning((object)"[Toss a Tick] BugfixOnYou has no PhotonView.");
			}
			return false;
		}
		component.RPC("AttachBug", (RpcTarget)0, new object[1] { target.refs.view.ViewID });
		return true;
	}

	private static Character ResolveCharacterFromCollision(Collision collision)
	{
		Character result = default(Character);
		if (CharacterRagdoll.TryGetCharacterFromCollider(collision.collider, ref result))
		{
			return result;
		}
		return collision.gameObject.GetComponentInParent<Character>();
	}
}
[HarmonyPatch(typeof(GlobalEvents), "TriggerItemThrown")]
internal static class TickThrownGlobalPatch
{
	private static void Postfix(Item item)
	{
		if (PhotonNetwork.IsMasterClient)
		{
			TickAttachHelper.ArmThrownTickItem(item);
		}
	}
}
internal class TickThrowHandler : MonoBehaviour
{
	private const float MinHitVelocity = 0.5f;

	private Item _item;

	private float _armedAtTime = -999f;

	public void Initialize(Item item)
	{
		_item = item;
	}

	public void MarkAsThrown()
	{
		_armedAtTime = Time.time;
	}

	public bool IsWithinThrownWindow()
	{
		return (Object)(object)_item != (Object)null && Time.time - _armedAtTime <= 8f;
	}

	private void OnCollisionEnter(Collision collision)
	{
		//IL_0068: Unknown result type (might be due to invalid IL or missing references)
		if (!((Object)(object)_item == (Object)null) && !((Object)(object)((MonoBehaviourPun)_item).photonView == (Object)null) && ((MonoBehaviourPun)_item).photonView.IsMine && !((Object)(object)_item.rig == (Object)null) && IsWithinThrownWindow() && (LayerMask.op_Implicit(HelperFunctions.terrainMapMask) & (1 << ((Component)collision.transform).gameObject.layer)) == 0)
		{
			Character val = ResolveCharacterFromCollision(collision);
			if (!((Object)(object)val == (Object)null))
			{
				TickAttachHelper.TryProcessTickHit(val, collision, _item, TossATickPlugin.ThrowSearchRadius, 8f, 0.5f);
			}
		}
	}

	private static Character ResolveCharacterFromCollision(Collision collision)
	{
		Character result = default(Character);
		if (CharacterRagdoll.TryGetCharacterFromCollider(collision.collider, ref result))
		{
			return result;
		}
		return collision.gameObject.GetComponentInParent<Character>();
	}
}