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 QualifiedHorses = new List(); } [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 EnableDebugLogging; internal static ManualLogSource PluginLogger; private static FieldInfo _waypointTeleportingField; private static readonly Dictionary PendingTeleports = new Dictionary(); 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("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() == (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(); 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(); 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(); 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() : 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(); if ((Object)(object)component != (Object)null && !component.IsOwner()) { component.ClaimOwnership(); } ((Component)val2).transform.SetPositionAndRotation(horseDest, rot); Rigidbody component2 = ((Component)val2).GetComponent(); 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() + "..."); } } }