Decompiled source of WaypointTeleportsOdinHorse v1.2.0

WaypointTeleportsOdinHorse.dll

Decompiled 2 weeks ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("RDMods")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyDescription("Teleports your tamed OdinHorse with you when using Waypoints.")]
[assembly: AssemblyFileVersion("1.2.0.0")]
[assembly: AssemblyInformationalVersion("1.2.0")]
[assembly: AssemblyProduct("WaypointTeleportsOdinHorse")]
[assembly: AssemblyTitle("WaypointTeleportsOdinHorse")]
[assembly: AssemblyVersion("1.2.0.0")]
namespace RDMods.WaypointTeleportsOdinHorse;

[BepInPlugin("RDMods.WaypointTeleportsOdinHorse", "WaypointTeleportsOdinHorse", "1.2.0")]
[BepInDependency(/*Could not decode attribute arguments.*/)]
[BepInDependency(/*Could not decode attribute arguments.*/)]
public class WaypointTeleportsOdinHorse : BaseUnityPlugin
{
	private sealed class SnapshottedHorse
	{
		public ZDOID ZdoId;

		public float RegisteredDistance;

		public string Name;
	}

	private sealed class PendingHorseTeleport
	{
		public Vector3 OriginPos;

		public Vector3 TargetPos;

		public float RegisteredAt;

		public readonly List<SnapshottedHorse> QualifiedHorses = new List<SnapshottedHorse>();
	}

	[HarmonyPatch(typeof(Player), "TeleportTo", new Type[]
	{
		typeof(Vector3),
		typeof(Quaternion),
		typeof(bool)
	})]
	private static class PlayerTeleportTo_Patch
	{
		private static void Prefix(Player __instance, ref Vector3 __state)
		{
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			__state = ((Component)__instance).transform.position;
			LogDebug("TELEPORT_TO Prefix: Player=" + __instance.GetPlayerName() + ", " + $"OriginPos=({__state.x:F1}, {__state.y:F1}, {__state.z:F1}), " + $"m_teleporting={IsWaypointTeleporting()}");
		}

		private static void Postfix(Player __instance, Vector3 pos, Quaternion rot, bool __result, Vector3 __state)
		{
			//IL_002c: 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)
			try
			{
				if (!__result || (Object)(object)__instance == (Object)null)
				{
					LogDebug("TELEPORT_TO Postfix: TeleportTo returned false or player null — not queuing");
				}
				else if (!IsWaypointTeleporting())
				{
					LogDebug("TELEPORT_TO Postfix: not a waypoint teleport — not queuing");
				}
				else
				{
					RegisterPendingTeleport(__instance, __state, pos);
				}
			}
			catch (Exception arg)
			{
				LogError($"CRITICAL ERROR in TeleportTo Postfix: {arg}");
			}
		}
	}

	[HarmonyPatch(typeof(Player), "UpdateTeleport")]
	private static class PlayerUpdateTeleport_Patch
	{
		private static void Postfix(Player __instance)
		{
			try
			{
				if (!((Object)(object)__instance == (Object)null) && PendingTeleports.Count != 0)
				{
					long playerID = __instance.GetPlayerID();
					if (PendingTeleports.ContainsKey(playerID))
					{
						TryCompletePendingTeleport(__instance);
					}
				}
			}
			catch (Exception arg)
			{
				LogError($"CRITICAL ERROR in UpdateTeleport Postfix: {arg}");
			}
		}
	}

	public const string PluginGUID = "RDMods.WaypointTeleportsOdinHorse";

	public const string PluginName = "WaypointTeleportsOdinHorse";

	public const string PluginVersion = "1.2.0";

	internal static ConfigEntry<bool> EnableDebugLogging;

	internal static ManualLogSource PluginLogger;

	private static FieldInfo _waypointTeleportingField;

	private static readonly Dictionary<long, PendingHorseTeleport> PendingTeleports = new Dictionary<long, PendingHorseTeleport>();

	private const float PendingTimeoutSeconds = 180f;

	private const float MaxFollowDistance = 20f;

	private const float ScatterRadius = 3f;

	private void Awake()
	{
		//IL_0074: Unknown result type (might be due to invalid IL or missing references)
		PluginLogger = ((BaseUnityPlugin)this).Logger;
		EnableDebugLogging = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "EnableDebugLogging", true, "Enables detailed debug logging for troubleshooting. Set to false to reduce log spam.");
		LogInfo("WaypointTeleportsOdinHorse v1.2.0 initializing...");
		LogInfo("Debug logging: " + (EnableDebugLogging.Value ? "ENABLED" : "DISABLED"));
		if (!CacheReflectionMembers())
		{
			LogError("CRITICAL: Failed to cache Waypoints reflection members. Plugin will be disabled.");
			return;
		}
		new Harmony("RDMods.WaypointTeleportsOdinHorse").PatchAll();
		LogInfo("Harmony patches applied (Player.TeleportTo, Player.UpdateTeleport).");
		LogInfo("WaypointTeleportsOdinHorse initialized successfully.");
	}

	private bool CacheReflectionMembers()
	{
		try
		{
			Assembly assembly = AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault((Assembly a) => a.GetName().Name == "Waypoints");
			if (assembly == null)
			{
				LogError("Waypoints assembly not found in loaded assemblies. Is Waypoints mod installed?");
				return false;
			}
			LogDebug("Found Waypoints assembly: " + assembly.FullName);
			Type type = assembly.GetType("Waypoints.Behaviors.Waypoint");
			if (type == null)
			{
				LogError("Waypoints.Behaviors.Waypoint type not found in Waypoints assembly.");
				return false;
			}
			_waypointTeleportingField = AccessTools.Field(type, "m_teleporting");
			if (_waypointTeleportingField == null)
			{
				LogError("Waypoint.m_teleporting field not found.");
				return false;
			}
			LogDebug("Cached m_teleporting field: " + _waypointTeleportingField.Name);
			return true;
		}
		catch (Exception arg)
		{
			LogError($"Exception during reflection caching: {arg}");
			return false;
		}
	}

	internal static void LogDebug(string message)
	{
		if (EnableDebugLogging != null && EnableDebugLogging.Value)
		{
			PluginLogger.LogInfo((object)message);
		}
	}

	internal static void LogInfo(string message)
	{
		PluginLogger.LogInfo((object)message);
	}

	internal static void LogError(string message)
	{
		PluginLogger.LogError((object)message);
	}

	private static bool IsWaypointTeleporting()
	{
		try
		{
			if (_waypointTeleportingField == null)
			{
				return false;
			}
			object value = _waypointTeleportingField.GetValue(null);
			bool flag = default(bool);
			int num;
			if (value is bool)
			{
				flag = (bool)value;
				num = 1;
			}
			else
			{
				num = 0;
			}
			return (byte)((uint)num & (flag ? 1u : 0u)) != 0;
		}
		catch (Exception ex)
		{
			LogDebug("IsWaypointTeleporting check failed: " + ex.Message);
			return false;
		}
	}

	private static void SnapshotQualifyingHorses(Player player, Vector3 originPos, PendingHorseTeleport pending)
	{
		//IL_004a: Unknown result type (might be due to invalid IL or missing references)
		//IL_004f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0065: 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)
		//IL_007b: Unknown result type (might be due to invalid IL or missing references)
		//IL_00b2: Unknown result type (might be due to invalid IL or missing references)
		//IL_00b3: Unknown result type (might be due to invalid IL or missing references)
		//IL_017d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0182: Unknown result type (might be due to invalid IL or missing references)
		//IL_0190: Unknown result type (might be due to invalid IL or missing references)
		//IL_0192: Unknown result type (might be due to invalid IL or missing references)
		//IL_01b4: Unknown result type (might be due to invalid IL or missing references)
		pending.QualifiedHorses.Clear();
		foreach (Character allCharacter in Character.GetAllCharacters())
		{
			if ((Object)(object)allCharacter == (Object)null || !((Object)allCharacter).name.Contains("rae_OdinHorse"))
			{
				continue;
			}
			Vector3 position = ((Component)allCharacter).transform.position;
			LogDebug("  SNAPSHOT candidate: name=" + ((Object)allCharacter).name + ", " + $"pos=({position.x:F1}, {position.y:F1}, {position.z:F1})");
			if ((Object)(object)((Component)allCharacter).GetComponent<Tameable>() == (Object)null)
			{
				LogDebug("    SKIP: No Tameable component (wild/untamed)");
				continue;
			}
			float num = Vector3.Distance(originPos, position);
			if (num > 20f)
			{
				LogDebug($"    SKIP: Too far ({num:F1}m > {20f}m)");
				continue;
			}
			MonsterAI component = ((Component)allCharacter).GetComponent<MonsterAI>();
			if ((Object)(object)component == (Object)null)
			{
				LogDebug("    SKIP: No MonsterAI component");
				continue;
			}
			GameObject followTarget = component.GetFollowTarget();
			if ((Object)(object)followTarget != (Object)(object)((Component)player).gameObject)
			{
				LogDebug("    SKIP: Not actively following player (followTarget=" + (((followTarget != null) ? ((Object)followTarget).name : null) ?? "null") + ")");
				continue;
			}
			ZNetView component2 = ((Component)allCharacter).GetComponent<ZNetView>();
			if ((Object)(object)component2 == (Object)null || !component2.IsValid())
			{
				LogDebug("    SKIP: No valid ZNetView");
				continue;
			}
			ZDOID uid = component2.GetZDO().m_uid;
			pending.QualifiedHorses.Add(new SnapshottedHorse
			{
				ZdoId = uid,
				RegisteredDistance = num,
				Name = ((Object)allCharacter).name
			});
			LogDebug($"    QUALIFIED: ZDOID={uid}, distance={num:F1}m " + "(ownership implied by follow target)");
		}
		LogDebug($"SNAPSHOT complete: {pending.QualifiedHorses.Count} horse(s) qualified");
	}

	private static void RegisterPendingTeleport(Player player, Vector3 originPos, Vector3 targetPos)
	{
		//IL_000d: Unknown result type (might be due to invalid IL or missing references)
		//IL_000e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0014: Unknown result type (might be due to invalid IL or missing references)
		//IL_0015: Unknown result type (might be due to invalid IL or missing references)
		//IL_0027: Unknown result type (might be due to invalid IL or missing references)
		//IL_0060: 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_0076: Unknown result type (might be due to invalid IL or missing references)
		//IL_008e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0099: Unknown result type (might be due to invalid IL or missing references)
		//IL_00a4: Unknown result type (might be due to invalid IL or missing references)
		long playerID = player.GetPlayerID();
		PendingHorseTeleport pendingHorseTeleport = new PendingHorseTeleport
		{
			OriginPos = originPos,
			TargetPos = targetPos,
			RegisteredAt = Time.time
		};
		SnapshotQualifyingHorses(player, originPos, pendingHorseTeleport);
		PendingTeleports[playerID] = pendingHorseTeleport;
		LogDebug("PENDING: Registered for " + player.GetPlayerName() + ", " + $"Origin=({originPos.x:F1}, {originPos.y:F1}, {originPos.z:F1}), " + $"Target=({targetPos.x:F1}, {targetPos.y:F1}, {targetPos.z:F1}), " + $"Qualified={pendingHorseTeleport.QualifiedHorses.Count}");
	}

	private static void ClearPending(long playerId, string reason)
	{
		if (PendingTeleports.Remove(playerId))
		{
			LogDebug($"PENDING: Cleared for playerId={playerId} ({reason})");
		}
	}

	private static bool PlayerLandedAtDestination(Player player, PendingHorseTeleport pending)
	{
		//IL_0006: Unknown result type (might be due to invalid IL or missing references)
		//IL_000c: Unknown result type (might be due to invalid IL or missing references)
		//IL_001d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0023: Unknown result type (might be due to invalid IL or missing references)
		float num = Vector3.Distance(((Component)player).transform.position, pending.OriginPos);
		float num2 = Vector3.Distance(((Component)player).transform.position, pending.TargetPos);
		bool num3 = num2 < num;
		if (!num3)
		{
			LogDebug("PENDING: Player did not land at destination " + $"(distToOrigin={num:F1}m, distToTarget={num2:F1}m) — horse stays put");
		}
		return num3;
	}

	private static bool IsDestinationAreaReady(Player player, PendingHorseTeleport pending)
	{
		//IL_0015: Unknown result type (might be due to invalid IL or missing references)
		//IL_001a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0020: Unknown result type (might be due to invalid IL or missing references)
		//IL_0030: Unknown result type (might be due to invalid IL or missing references)
		if ((Object)(object)ZNetScene.instance == (Object)null)
		{
			return false;
		}
		Vector3 position = ((Component)player).transform.position;
		if (!ZNetScene.instance.IsAreaReady(position))
		{
			return false;
		}
		return ZNetScene.instance.IsAreaReady(pending.TargetPos);
	}

	private static bool IsStillFollowingPlayer(Character character, Player player)
	{
		MonsterAI component = ((Component)character).GetComponent<MonsterAI>();
		if ((Object)(object)component == (Object)null)
		{
			return false;
		}
		return (Object)(object)component.GetFollowTarget() == (Object)(object)((Component)player).gameObject;
	}

	private static bool TeleportSnapshottedHorse(SnapshottedHorse snapshot, Vector3 horseDest, Quaternion rot, Player player)
	{
		//IL_000f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0023: Unknown result type (might be due to invalid IL or missing references)
		//IL_004f: 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_00e7: Unknown result type (might be due to invalid IL or missing references)
		//IL_00ee: Unknown result type (might be due to invalid IL or missing references)
		//IL_01db: Unknown result type (might be due to invalid IL or missing references)
		//IL_020a: 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)
		//IL_0220: Unknown result type (might be due to invalid IL or missing references)
		//IL_0128: Unknown result type (might be due to invalid IL or missing references)
		//IL_0129: Unknown result type (might be due to invalid IL or missing references)
		//IL_016e: Unknown result type (might be due to invalid IL or missing references)
		//IL_019d: Unknown result type (might be due to invalid IL or missing references)
		//IL_01a8: Unknown result type (might be due to invalid IL or missing references)
		//IL_01b3: Unknown result type (might be due to invalid IL or missing references)
		//IL_0143: Unknown result type (might be due to invalid IL or missing references)
		//IL_014b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0153: Unknown result type (might be due to invalid IL or missing references)
		if (ZDOMan.instance == null)
		{
			return false;
		}
		ZDO zDO = ZDOMan.instance.GetZDO(snapshot.ZdoId);
		if (zDO == null)
		{
			LogDebug($"    SKIP: ZDO {snapshot.ZdoId} no longer exists");
			return false;
		}
		GameObject val = (((Object)(object)ZNetScene.instance != (Object)null) ? ZNetScene.instance.FindInstance(snapshot.ZdoId) : null);
		Character val2 = (((Object)(object)val != (Object)null) ? val.GetComponent<Character>() : null);
		if ((Object)(object)val2 != (Object)null)
		{
			if (!((Object)val2).name.Contains("rae_OdinHorse"))
			{
				LogDebug("    SKIP: Instance is no longer an OdinHorse (" + ((Object)val2).name + ")");
				return false;
			}
			if (!IsStillFollowingPlayer(val2, player))
			{
				LogDebug("    SKIP at execute: no longer following player");
				return false;
			}
		}
		bool flag = zDO.IsOwner();
		if (!flag)
		{
			zDO.SetOwner(ZDOMan.GetSessionID());
		}
		horseDest.y = ZoneSystem.instance.GetSolidHeight(horseDest) + 0.5f;
		zDO.SetPosition(horseDest);
		zDO.SetRotation(rot);
		if ((Object)(object)val2 != (Object)null)
		{
			ZNetView component = ((Component)val2).GetComponent<ZNetView>();
			if ((Object)(object)component != (Object)null && !component.IsOwner())
			{
				component.ClaimOwnership();
			}
			((Component)val2).transform.SetPositionAndRotation(horseDest, rot);
			Rigidbody component2 = ((Component)val2).GetComponent<Rigidbody>();
			if ((Object)(object)component2 != (Object)null)
			{
				component2.position = horseDest;
				component2.rotation = rot;
				component2.linearVelocity = Vector3.zero;
			}
			Physics.SyncTransforms();
			LogDebug($"    SUCCESS via ZDO+instance: {snapshot.Name}, ZDOID={snapshot.ZdoId}, " + $"registeredDistance={snapshot.RegisteredDistance:F1}m, wasOwner={flag} " + $"-> ({horseDest.x:F1}, {horseDest.y:F1}, {horseDest.z:F1})");
		}
		else
		{
			LogDebug($"    SUCCESS via ZDO only: {snapshot.Name}, ZDOID={snapshot.ZdoId}, " + $"registeredDistance={snapshot.RegisteredDistance:F1}m, wasOwner={flag} " + $"-> ({horseDest.x:F1}, {horseDest.y:F1}, {horseDest.z:F1})");
		}
		return true;
	}

	private static void ExecuteHorseTeleports(Player player, PendingHorseTeleport pending)
	{
		//IL_0006: Unknown result type (might be due to invalid IL or missing references)
		//IL_000b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0012: Unknown result type (might be due to invalid IL or missing references)
		//IL_0017: Unknown result type (might be due to invalid IL or missing references)
		//IL_003e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0049: Unknown result type (might be due to invalid IL or missing references)
		//IL_0054: Unknown result type (might be due to invalid IL or missing references)
		//IL_00f0: Unknown result type (might be due to invalid IL or missing references)
		//IL_0104: Unknown result type (might be due to invalid IL or missing references)
		//IL_0128: Unknown result type (might be due to invalid IL or missing references)
		//IL_012d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0132: Unknown result type (might be due to invalid IL or missing references)
		//IL_0136: Unknown result type (might be due to invalid IL or missing references)
		//IL_0138: Unknown result type (might be due to invalid IL or missing references)
		Vector3 position = ((Component)player).transform.position;
		Quaternion rotation = ((Component)player).transform.rotation;
		LogDebug("EXECUTE: Player=" + player.GetPlayerName() + ", " + $"Landed=({position.x:F1}, {position.y:F1}, {position.z:F1}), " + $"Origin=({pending.OriginPos.x:F1}, {pending.OriginPos.y:F1}, {pending.OriginPos.z:F1}), " + $"Snapshotted={pending.QualifiedHorses.Count}");
		int num = 0;
		foreach (SnapshottedHorse qualifiedHorse in pending.QualifiedHorses)
		{
			LogDebug($"  TELEPORTING snapshotted horse: {qualifiedHorse.Name}, ZDOID={qualifiedHorse.ZdoId}");
			Vector3 horseDest = position + new Vector3(Random.Range(-3f, 3f), 0f, Random.Range(-3f, 3f));
			if (TeleportSnapshottedHorse(qualifiedHorse, horseDest, rotation, player))
			{
				num++;
			}
		}
		LogDebug($"EXECUTE complete: {pending.QualifiedHorses.Count} snapshotted, {num} teleported");
	}

	private static void TryCompletePendingTeleport(Player player)
	{
		if ((Object)(object)player == (Object)null)
		{
			return;
		}
		long playerID = player.GetPlayerID();
		if (!PendingTeleports.TryGetValue(playerID, out var value))
		{
			return;
		}
		if (Time.time - value.RegisteredAt > 180f)
		{
			ClearPending(playerID, "timeout");
		}
		else
		{
			if (((Character)player).IsTeleporting())
			{
				return;
			}
			if (!PlayerLandedAtDestination(player, value))
			{
				ClearPending(playerID, "teleport cancelled or failed");
				return;
			}
			if (IsDestinationAreaReady(player, value))
			{
				try
				{
					if (value.QualifiedHorses.Count == 0)
					{
						LogDebug("EXECUTE: No horses were snapshotted at registration — nothing to teleport");
					}
					else
					{
						ExecuteHorseTeleports(player, value);
					}
					return;
				}
				catch (Exception arg)
				{
					LogError($"CRITICAL ERROR executing horse teleport: {arg}");
					return;
				}
				finally
				{
					ClearPending(playerID, "completed");
				}
			}
			LogDebug("PENDING: Waiting for destination area to load for " + player.GetPlayerName() + "...");
		}
	}
}