Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of TeleportCreatures v0.2.4
TeleportCreatures.dll
Decompiled 21 hours ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.IO; using System.IO.Compression; using System.Linq; using System.Linq.Expressions; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Serialization; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Text; using System.Text.RegularExpressions; using System.Threading; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using JetBrains.Annotations; using LocalizationManager; using Microsoft.CodeAnalysis; using Odinplus.Shared.YamlEditor; using ServerSync; using TMPro; using UnityEngine; using YamlDotNet.Core; using YamlDotNet.Core.Events; using YamlDotNet.Core.ObjectPool; using YamlDotNet.Core.Tokens; using YamlDotNet.Helpers; using YamlDotNet.Serialization; using YamlDotNet.Serialization.BufferedDeserialization; using YamlDotNet.Serialization.BufferedDeserialization.TypeDiscriminators; using YamlDotNet.Serialization.Callbacks; using YamlDotNet.Serialization.Converters; using YamlDotNet.Serialization.EventEmitters; using YamlDotNet.Serialization.NamingConventions; using YamlDotNet.Serialization.NodeDeserializers; using YamlDotNet.Serialization.NodeTypeResolvers; using YamlDotNet.Serialization.ObjectFactories; using YamlDotNet.Serialization.ObjectGraphTraversalStrategies; using YamlDotNet.Serialization.ObjectGraphVisitors; using YamlDotNet.Serialization.Schemas; using YamlDotNet.Serialization.TypeInspectors; using YamlDotNet.Serialization.TypeResolvers; using YamlDotNet.Serialization.Utilities; using YamlDotNet.Serialization.ValueDeserializers; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: AssemblyTitle("TeleportCreatures")] [assembly: AssemblyDescription("https://valheim.hexium.gg/mods/OdinPlus/TeleportCreatures")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("odinplus")] [assembly: AssemblyProduct("TeleportCreatures")] [assembly: AssemblyCopyright("Copyright © 2026")] [assembly: AssemblyTrademark("")] [assembly: AssemblyFileVersion("0.2.4")] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("0.2.4.0")] [module: UnverifiableCode] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } } namespace LocalizationManager { public class Localizer { private static readonly Dictionary<string, Dictionary<string, Func<string>>> PlaceholderProcessors; private static readonly Dictionary<string, Dictionary<string, string>> loadedTexts; private static readonly ConditionalWeakTable<Localization, string> localizationLanguage; private static readonly List<WeakReference<Localization>> localizationObjects; private static BaseUnityPlugin? _plugin; private static readonly List<string> fileExtensions; private static BaseUnityPlugin plugin { get { //IL_009b: Unknown result type (might be due to invalid IL or missing references) //IL_00a5: Expected O, but got Unknown if (_plugin == null) { IEnumerable<TypeInfo> source; try { source = Assembly.GetExecutingAssembly().DefinedTypes.ToList(); } catch (ReflectionTypeLoadException ex) { source = from t in ex.Types where t != null select t.GetTypeInfo(); } _plugin = (BaseUnityPlugin)Chainloader.ManagerObject.GetComponent((Type)source.First((TypeInfo t) => t.IsClass && typeof(BaseUnityPlugin).IsAssignableFrom(t))); } return _plugin; } } public static event Action? OnLocalizationComplete; private static void UpdatePlaceholderText(Localization localization, string key) { localizationLanguage.TryGetValue(localization, out string value); string text = loadedTexts[value][key]; if (PlaceholderProcessors.TryGetValue(key, out Dictionary<string, Func<string>> value2)) { text = value2.Aggregate(text, (string current, KeyValuePair<string, Func<string>> kv) => current.Replace("{" + kv.Key + "}", kv.Value())); } localization.AddWord(key, text); } public static void AddPlaceholder<T>(string key, string placeholder, ConfigEntry<T> config, Func<T, string>? convertConfigValue = null) where T : notnull { if (convertConfigValue == null) { convertConfigValue = (T val) => val.ToString(); } if (!PlaceholderProcessors.ContainsKey(key)) { PlaceholderProcessors[key] = new Dictionary<string, Func<string>>(); } config.SettingChanged += delegate { UpdatePlaceholder(); }; if (loadedTexts.ContainsKey(Localization.instance.GetSelectedLanguage())) { UpdatePlaceholder(); } void UpdatePlaceholder() { PlaceholderProcessors[key][placeholder] = () => convertConfigValue(config.Value); UpdatePlaceholderText(Localization.instance, key); } } public static void AddText(string key, string text) { List<WeakReference<Localization>> list = new List<WeakReference<Localization>>(); foreach (WeakReference<Localization> localizationObject in localizationObjects) { if (localizationObject.TryGetTarget(out var target)) { Dictionary<string, string> dictionary = loadedTexts[localizationLanguage.GetOrCreateValue(target)]; if (!target.m_translations.ContainsKey(key)) { dictionary[key] = text; target.AddWord(key, text); } } else { list.Add(localizationObject); } } foreach (WeakReference<Localization> item in list) { localizationObjects.Remove(item); } } public static void Load() { _ = plugin; } public static void LoadLocalizationLater(Localization __instance) { LoadLocalization(Localization.instance, __instance.GetSelectedLanguage()); } public static void SafeCallLocalizeComplete() { Localizer.OnLocalizationComplete?.Invoke(); } private static void LoadLocalization(Localization __instance, string language) { if (!localizationLanguage.Remove(__instance)) { localizationObjects.Add(new WeakReference<Localization>(__instance)); } localizationLanguage.Add(__instance, language); Dictionary<string, string> dictionary = new Dictionary<string, string>(); foreach (string item in from f in Directory.GetFiles(Path.GetDirectoryName(Paths.PluginPath), plugin.Info.Metadata.Name + ".*", SearchOption.AllDirectories) where fileExtensions.IndexOf(Path.GetExtension(f)) >= 0 select f) { string[] array = Path.GetFileNameWithoutExtension(item).Split(new char[1] { '.' }); if (array.Length >= 2) { string text = array[1]; if (dictionary.ContainsKey(text)) { Debug.LogWarning((object)("Duplicate key " + text + " found for " + plugin.Info.Metadata.Name + ". The duplicate file found at " + item + " will be skipped.")); } else { dictionary[text] = item; } } } byte[] array2 = LoadTranslationFromAssembly("English"); if (array2 == null) { throw new Exception("Found no English localizations in mod " + plugin.Info.Metadata.Name + ". Expected an embedded resource translations/English.json or translations/English.yml."); } Dictionary<string, string> dictionary2 = new DeserializerBuilder().IgnoreFields().Build().Deserialize<Dictionary<string, string>>(Encoding.UTF8.GetString(array2)); if (dictionary2 == null) { throw new Exception("Localization for mod " + plugin.Info.Metadata.Name + " failed: Localization file was empty."); } string text2 = null; if (language != "English") { if (dictionary.TryGetValue(language, out var value)) { text2 = File.ReadAllText(value); } else { byte[] array3 = LoadTranslationFromAssembly(language); if (array3 != null) { text2 = Encoding.UTF8.GetString(array3); } } } if (text2 == null && dictionary.TryGetValue("English", out var value2)) { text2 = File.ReadAllText(value2); } if (text2 != null) { foreach (KeyValuePair<string, string> item2 in new DeserializerBuilder().IgnoreFields().Build().Deserialize<Dictionary<string, string>>(text2) ?? new Dictionary<string, string>()) { dictionary2[item2.Key] = item2.Value; } } loadedTexts[language] = dictionary2; foreach (KeyValuePair<string, string> item3 in dictionary2) { UpdatePlaceholderText(__instance, item3.Key); } } static Localizer() { //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_0080: Unknown result type (might be due to invalid IL or missing references) //IL_008d: Expected O, but got Unknown //IL_008e: Unknown result type (might be due to invalid IL or missing references) //IL_00bc: Unknown result type (might be due to invalid IL or missing references) //IL_00c9: Expected O, but got Unknown //IL_00f7: Unknown result type (might be due to invalid IL or missing references) //IL_0104: Expected O, but got Unknown PlaceholderProcessors = new Dictionary<string, Dictionary<string, Func<string>>>(); loadedTexts = new Dictionary<string, Dictionary<string, string>>(); localizationLanguage = new ConditionalWeakTable<Localization, string>(); localizationObjects = new List<WeakReference<Localization>>(); fileExtensions = new List<string> { ".json", ".yml" }; Harmony val = new Harmony("org.bepinex.helpers.LocalizationManager"); val.Patch((MethodBase)AccessTools.DeclaredMethod(typeof(Localization), "SetupLanguage", (Type[])null, (Type[])null), (HarmonyMethod)null, new HarmonyMethod(AccessTools.DeclaredMethod(typeof(Localizer), "LoadLocalization", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); val.Patch((MethodBase)AccessTools.DeclaredMethod(typeof(FejdStartup), "SetupGui", (Type[])null, (Type[])null), (HarmonyMethod)null, new HarmonyMethod(AccessTools.DeclaredMethod(typeof(Localizer), "LoadLocalizationLater", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); val.Patch((MethodBase)AccessTools.DeclaredMethod(typeof(FejdStartup), "Start", (Type[])null, (Type[])null), (HarmonyMethod)null, new HarmonyMethod(AccessTools.DeclaredMethod(typeof(Localizer), "SafeCallLocalizeComplete", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } private static byte[]? LoadTranslationFromAssembly(string language) { foreach (string fileExtension in fileExtensions) { byte[] array = ReadEmbeddedFileBytes("translations." + language + fileExtension); if (array != null) { return array; } } return null; } public static byte[]? ReadEmbeddedFileBytes(string resourceFileName, Assembly? containingAssembly = null) { using MemoryStream memoryStream = new MemoryStream(); if ((object)containingAssembly == null) { containingAssembly = Assembly.GetCallingAssembly(); } string text = containingAssembly.GetManifestResourceNames().FirstOrDefault((string str) => str.EndsWith(resourceFileName, StringComparison.Ordinal)); if (text != null) { containingAssembly.GetManifestResourceStream(text)?.CopyTo(memoryStream); } return (memoryStream.Length == 0L) ? null : memoryStream.ToArray(); } } public static class LocalizationManagerVersion { public const string Version = "1.4.1"; } } namespace TeleportCreatures { internal enum AllyTransportMessageMode { NoMessages, TopLeft, TopCenter, Center } internal static class AllyTransportDisplayPolicy { public static bool ShouldShowMessage(AllyTransportMessageMode mode, int allyCount) { if (mode != AllyTransportMessageMode.NoMessages) { return allyCount > 0; } return false; } public static AllyTransportMessageMode ParseMessageMode(string? value) { return value?.Trim().ToLowerInvariant() switch { "no messages" => AllyTransportMessageMode.NoMessages, "top left" => AllyTransportMessageMode.TopLeft, "top center" => AllyTransportMessageMode.TopCenter, _ => AllyTransportMessageMode.Center, }; } } internal enum CreatureArrivalAction { Skip, MoveInstance, MoveOwnedRecord } internal static class CreatureArrivalDecision { public static CreatureArrivalAction Select(bool instanceAvailable, bool recordAvailable, bool recordIsOwnedLocally) { if (instanceAvailable) { return CreatureArrivalAction.MoveInstance; } if (recordAvailable && recordIsOwnedLocally) { return CreatureArrivalAction.MoveOwnedRecord; } return CreatureArrivalAction.Skip; } public static bool ShouldPersistNetworkRecord(CreatureArrivalAction action) { if ((uint)(action - 1) <= 1u) { return true; } return false; } } public sealed class CreatureConfig { public int SchemaVersion { get; set; } = 1; public CreatureFilterConfig Allies { get; set; } = new CreatureFilterConfig { Allow = new List<string> { "all" }, Deny = new List<string>() }; public PlacementConfig Placement { get; set; } = new PlacementConfig(); } public sealed class CreatureFilterConfig { public bool Enabled { get; set; } = true; public AllyTeleportConfig Teleport { get; set; } = new AllyTeleportConfig(); public float MaximumSearchRadius { get; set; } = 20f; public float MaximumSearchHeightRadius { get; set; } = 8f; public List<string> Allow { get; set; } = new List<string>(); public List<string> Deny { get; set; } = new List<string>(); } public enum AllySelectionMode { AllTamed, OnlyFollow, OnlyNamed, AllTamedExceptNamed } public static class AllySelectionPolicy { public static bool IsSelected(AllySelectionMode mode, bool isFollowingPlayer, bool hasCustomName) { return mode switch { AllySelectionMode.AllTamed => true, AllySelectionMode.OnlyFollow => isFollowingPlayer, AllySelectionMode.OnlyNamed => hasCustomName, AllySelectionMode.AllTamedExceptNamed => !hasCustomName, _ => false, }; } } public sealed class AllyTeleportConfig { public PortalTransportConfig Portals { get; set; } = new PortalTransportConfig(); public TransitionToggleConfig Dungeons { get; set; } = new TransitionToggleConfig(); public TransitionToggleConfig Basements { get; set; } = new TransitionToggleConfig(); } public sealed class TransitionToggleConfig { public bool Enabled { get; set; } = true; } public sealed class PortalTransportConfig { public bool Enabled { get; set; } = true; public List<PortalRuleConfig> Rules { get; set; } = new List<PortalRuleConfig>(); } public sealed class PortalRuleConfig { public string Id { get; set; } = string.Empty; public int? Priority { get; set; } public PortalRuleSourceConfig Source { get; set; } = new PortalRuleSourceConfig(); public List<string>? Allow { get; set; } public List<string> Deny { get; set; } = new List<string>(); } public sealed class PortalRuleSourceConfig { public List<string>? Tags { get; set; } public List<string>? Prefabs { get; set; } } public sealed class DungeonPlacementConfig { public float Radius { get; set; } = 12f; public bool RequireNaturalGroundOnExit { get; set; } = true; } public sealed class BasementPlacementConfig { public float Radius { get; set; } = 8f; public bool RequireClearance { get; set; } = true; } public sealed class PlacementConfig { public float Radius { get; set; } = 10f; public float MaximumHeightDifference { get; set; } = 3f; public float DistanceFromPlayer { get; set; } = 3f; public float AnimalSpacing { get; set; } = 2.5f; public WaterPolicy Water { get; set; } = WaterPolicy.Allow; public DungeonPlacementConfig Dungeons { get; set; } = new DungeonPlacementConfig(); public BasementPlacementConfig Basements { get; set; } = new BasementPlacementConfig(); } public enum WaterPolicy { Avoid, Allow, Require } public static class CreatureConfigValidator { public static void Validate(CreatureConfig config) { if (config == null) { throw new InvalidDataException("Creature configuration cannot be null."); } if (config.SchemaVersion != 1) { throw new InvalidDataException("Only SchemaVersion 1 is supported. Move DungeonPlacement and BasementPlacement under Placement."); } if (config.Allies == null || config.Allies.Teleport == null || config.Placement == null || config.Allies.Teleport.Portals == null || config.Placement.Dungeons == null || config.Placement.Basements == null || config.Allies.Teleport.Dungeons == null || config.Allies.Teleport.Basements == null) { throw new InvalidDataException("Allies, Allies.Teleport, Placement, Placement.Dungeons, and Placement.Basements sections are required."); } if (config.Placement.Dungeons.Radius <= 0f || config.Placement.Basements.Radius <= 0f) { throw new InvalidDataException("Placement.Dungeons.Radius and Placement.Basements.Radius must be positive."); } if (config.Allies.MaximumSearchRadius <= 0f || config.Allies.MaximumSearchHeightRadius <= 0f) { throw new InvalidDataException("Allies.MaximumSearchRadius and Allies.MaximumSearchHeightRadius must be positive."); } if (config.Placement.Radius <= 0f || config.Placement.MaximumHeightDifference < 0f || config.Placement.DistanceFromPlayer <= 0f || config.Placement.AnimalSpacing <= 0f) { throw new InvalidDataException("Placement radius, distance, and spacing must be positive; MaximumHeightDifference cannot be negative."); } if (!Enum.IsDefined(typeof(WaterPolicy), config.Placement.Water)) { throw new InvalidDataException("Placement.Water must be Avoid, Allow, or Require."); } CreatureMatcher.ValidateList(config.Allies.Allow, "Allies.Allow"); CreatureMatcher.ValidateList(config.Allies.Deny, "Allies.Deny"); ValidatePortalRules(config.Allies.Teleport.Portals.Rules); } internal static void ValidatePortalRules(IReadOnlyList<PortalRuleConfig> rules) { if (rules == null) { throw new InvalidDataException("Allies.Teleport.Portals.Rules cannot be null."); } HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase); for (int i = 0; i < rules.Count; i++) { PortalRuleConfig portalRuleConfig = rules[i] ?? throw new InvalidDataException($"Allies.Teleport.Portals.Rules[{i}] cannot be null."); string text = $"Allies.Teleport.Portals.Rules[{i}]"; if (string.IsNullOrWhiteSpace(portalRuleConfig.Id)) { throw new InvalidDataException(text + ".Id is required."); } if (!hashSet.Add(portalRuleConfig.Id.Trim())) { throw new InvalidDataException(text + ".Id '" + portalRuleConfig.Id + "' duplicates another portal rule Id."); } bool flag; switch (portalRuleConfig.Priority) { default: flag = true; break; case null: case 1: case 2: case 3: case 4: case 5: case 6: case 7: case 8: case 9: case 10: case 11: case 12: case 13: case 14: case 15: case 16: case 17: case 18: case 19: case 20: case 21: case 22: case 23: case 24: case 25: case 26: case 27: case 28: case 29: case 30: case 31: case 32: case 33: case 34: case 35: case 36: case 37: case 38: case 39: case 40: case 41: case 42: case 43: case 44: case 45: case 46: case 47: case 48: case 49: case 50: case 51: case 52: case 53: case 54: case 55: case 56: case 57: case 58: case 59: case 60: case 61: case 62: case 63: case 64: case 65: case 66: case 67: case 68: case 69: case 70: case 71: case 72: case 73: case 74: case 75: case 76: case 77: case 78: case 79: case 80: case 81: case 82: case 83: case 84: case 85: case 86: case 87: case 88: case 89: case 90: case 91: case 92: case 93: case 94: case 95: case 96: case 97: case 98: case 99: case 100: flag = false; break; } if (flag) { throw new InvalidDataException(text + ".Priority must be between 1 and 100 when specified."); } if (portalRuleConfig.Source == null) { throw new InvalidDataException(text + ".Source is required."); } List<string> tags = portalRuleConfig.Source.Tags; bool num = tags != null && tags.Count > 0; tags = portalRuleConfig.Source.Prefabs; bool flag2 = tags != null && tags.Count > 0; if (!num && !flag2) { throw new InvalidDataException(text + ".Source requires Tags and/or Prefabs."); } if (portalRuleConfig.Source.Tags != null) { CreatureMatcher.ValidateList(portalRuleConfig.Source.Tags, text + ".Source.Tags"); } if (portalRuleConfig.Source.Prefabs != null) { CreatureMatcher.ValidateList(portalRuleConfig.Source.Prefabs, text + ".Source.Prefabs"); } if (portalRuleConfig.Allow != null) { CreatureMatcher.ValidateList(portalRuleConfig.Allow, text + ".Allow"); } CreatureMatcher.ValidateList(portalRuleConfig.Deny, text + ".Deny"); } } } public static class CreatureEligibility { public static bool IsAllowed(CreatureConfig config, string prefabName, bool isTamed) { if (!isTamed || string.IsNullOrWhiteSpace(prefabName)) { return false; } if (config.Allies.Deny.Any((string pattern) => CreatureMatcher.IsMatch(pattern, prefabName))) { return false; } return config.Allies.Allow.Any((string pattern) => CreatureMatcher.IsMatch(pattern, prefabName)); } } internal static class CreaturePlacementPolicy { public static bool IsNaturalGround(float terrainHeight, float solidHeight, float tolerance) { return Math.Abs(terrainHeight - solidHeight) <= tolerance; } public static bool CanUseConstructedFloor(bool isBasement, bool clearanceIsOpen) { return !isBasement || clearanceIsOpen; } public static bool UsesNativePlayerLevel(bool isBasement) { return isBasement; } public static float SelectLandingHeight(bool hasInteriorFloor, float interiorFloorHeight, float solidHeight) { if (!hasInteriorFloor) { return solidHeight; } return interiorFloorHeight; } public static bool IsOnPlayerLevel(float playerHeight, float floorHeight, float maximumDifference) { return Math.Abs(playerHeight - floorHeight) <= maximumDifference; } public static bool CanUseDungeonFallbackHeight(float playerHeight, float solidHeight, float maximumDifference) { return IsOnPlayerLevel(playerHeight, solidHeight, maximumDifference); } public static int GetCandidateSlot(int animalIndex, int attempt, int positionsPerRing) { return animalIndex + attempt; } public static float GetForwardSearchAngle(int slot, int positionsPerRing, float halfAngleDegrees) { int num = slot % positionsPerRing; if (num == 0) { return 0f; } int num2 = (num + 1) / 2; int num3 = (positionsPerRing - 1) / 2; return ((num % 2 == 0) ? 1f : (-1f)) * halfAngleDegrees * (float)num2 / (float)num3; } public static bool IsClearanceFree(int collisionCount, int bufferCapacity) { if (collisionCount == 0) { return collisionCount < bufferCapacity; } return false; } } public sealed class PlayerCreaturePreferences { public int SchemaVersion { get; set; } = 1; public PlayerAllyPreferences Allies { get; set; } = new PlayerAllyPreferences(); public static PlayerCreaturePreferences CreateDefault() { return new PlayerCreaturePreferences(); } } public sealed class PlayerAllyPreferences { public bool Enabled { get; set; } = true; public AllySelectionMode Mode { get; set; } public PlayerAllyTeleportConfig Teleport { get; set; } = new PlayerAllyTeleportConfig(); public float SearchRadius { get; set; } = 10f; public float SearchHeightRadius { get; set; } = 3f; public List<string> Allow { get; set; } = new List<string> { "all" }; public List<string> Deny { get; set; } = new List<string>(); } public sealed class PlayerAllyTeleportConfig { public PortalTransportConfig Portals { get; set; } = new PortalTransportConfig(); public TransitionToggleConfig Dungeons { get; set; } = new TransitionToggleConfig(); public TransitionToggleConfig Basements { get; set; } = new TransitionToggleConfig(); } public static class PlayerCreaturePreferencesValidator { public static void Validate(PlayerCreaturePreferences preferences) { if (preferences == null) { throw new InvalidDataException("Player creature preferences cannot be null."); } if (preferences.SchemaVersion != 1) { throw new InvalidDataException("Only SchemaVersion 1 is supported."); } if (preferences.Allies == null || preferences.Allies.Teleport == null || preferences.Allies.Teleport.Portals == null || preferences.Allies.Teleport.Dungeons == null || preferences.Allies.Teleport.Basements == null) { throw new InvalidDataException("Allies and Allies.Teleport sections are required."); } if (!Enum.IsDefined(typeof(AllySelectionMode), preferences.Allies.Mode)) { throw new InvalidDataException("Allies.Mode must be valid."); } if (preferences.Allies.SearchRadius <= 0f || preferences.Allies.SearchHeightRadius <= 0f) { throw new InvalidDataException("Allies.SearchRadius and Allies.SearchHeightRadius must be positive."); } CreatureMatcher.ValidateList(preferences.Allies.Allow, "Allies.Allow"); CreatureMatcher.ValidateList(preferences.Allies.Deny, "Allies.Deny"); CreatureConfigValidator.ValidatePortalRules(preferences.Allies.Teleport.Portals.Rules); } } public static class PlayerPreferenceSnapshot { public static bool TryReplace(PlayerCreaturePreferences lastValid, string yaml, out PlayerCreaturePreferences retained, out string error) { try { PlayerCreaturePreferences playerCreaturePreferences = new DeserializerBuilder().Build().Deserialize<PlayerCreaturePreferences>(yaml); PlayerCreaturePreferencesValidator.Validate(playerCreaturePreferences); retained = playerCreaturePreferences; error = string.Empty; return true; } catch (Exception ex) { retained = lastValid; error = ex.Message; return false; } } } public static class CreatureMatcher { private static readonly char[] LegacyRegexCharacters = new char[9] { '^', '$', '[', ']', '(', ')', '|', '+', '\\' }; public static bool IsMatch(string pattern, string prefabName) { ValidatePattern(pattern, "filter"); string text = pattern.Trim(); if (text.Equals("all", StringComparison.OrdinalIgnoreCase) || text == "*") { return true; } if (text.Equals("none", StringComparison.OrdinalIgnoreCase)) { return false; } string pattern2 = "^" + GlobToRegex(text) + "$"; return Regex.IsMatch(prefabName ?? string.Empty, pattern2, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); } public static void ValidateList(IEnumerable<string> patterns, string section) { if (patterns == null) { throw new InvalidDataException(section + " cannot be null."); } foreach (string pattern in patterns) { ValidatePattern(pattern, section); } } private static void ValidatePattern(string pattern, string section) { if (string.IsNullOrWhiteSpace(pattern)) { throw new InvalidDataException(section + " cannot contain an empty filter."); } string text = pattern.Trim(); if (text.StartsWith("regex:", StringComparison.OrdinalIgnoreCase) || text.IndexOfAny(LegacyRegexCharacters) >= 0) { throw new InvalidDataException(section + " uses legacy regular-expression syntax. Use an exact prefab name or * / ? glob instead."); } } private static string GlobToRegex(string pattern) { StringBuilder stringBuilder = new StringBuilder(); for (int i = 0; i < pattern.Length; i++) { char c = pattern[i]; StringBuilder stringBuilder2 = stringBuilder; stringBuilder2.Append(c switch { '*' => ".*", '?' => ".", _ => Regex.Escape(c.ToString()), }); } return stringBuilder.ToString(); } } public sealed class PortalContext { public string PrefabName { get; } public string Tag { get; } public bool IsUsable => !string.IsNullOrWhiteSpace(PrefabName); public PortalContext(string prefabName, string tag) { PrefabName = prefabName ?? string.Empty; Tag = tag ?? string.Empty; } } public static class PortalRuleResolver { public static PortalRuleConfig? Select(PortalTransportConfig portals, PortalContext context) { if (portals == null || context == null || !context.IsUsable) { return null; } PortalRuleConfig portalRuleConfig = null; foreach (PortalRuleConfig rule in portals.Rules) { if (Matches(rule, context) && (portalRuleConfig == null || GetEffectivePriority(rule) < GetEffectivePriority(portalRuleConfig))) { portalRuleConfig = rule; } } return portalRuleConfig; } public static int GetEffectivePriority(PortalRuleConfig rule) { return rule.Priority ?? 100; } public static bool Matches(PortalRuleConfig rule, PortalContext context) { if (rule?.Source == null || context == null || !context.IsUsable) { return false; } List<string> tags = rule.Source.Tags; bool flag = tags != null && tags.Count > 0; tags = rule.Source.Prefabs; bool flag2 = tags != null && tags.Count > 0; if (!flag && !flag2) { return false; } if (!flag || MatchesAny(rule.Source.Tags, context.Tag)) { if (flag2) { return MatchesAny(rule.Source.Prefabs, context.PrefabName); } return true; } return false; } private static bool MatchesAny(IEnumerable<string> patterns, string value) { return patterns.Any((string pattern) => CreatureMatcher.IsMatch(pattern, value)); } } public sealed class EffectiveCreaturePolicy { public CreatureConfig ServerPolicy { get; } public PlayerCreaturePreferences PlayerPreferences { get; } private EffectiveCreaturePolicy(CreatureConfig serverPolicy, PlayerCreaturePreferences playerPreferences) { ServerPolicy = serverPolicy; PlayerPreferences = playerPreferences; } public static EffectiveCreaturePolicy Create(CreatureConfig server, PlayerCreaturePreferences player) { CreatureConfigValidator.Validate(server); PlayerCreaturePreferencesValidator.Validate(player); return new EffectiveCreaturePolicy(server, player); } public bool IsAllowed(string prefabName, bool isTamed, bool isFollowingPlayer, bool hasCustomName, PortalContext? portalContext = null) { if (!isTamed || !ServerPolicy.Allies.Enabled || !PlayerPreferences.Allies.Enabled || string.IsNullOrWhiteSpace(prefabName)) { return false; } if (!AllySelectionPolicy.IsSelected(PlayerPreferences.Allies.Mode, isFollowingPlayer, hasCustomName)) { return false; } PortalRuleConfig portalRuleConfig = ((portalContext == null) ? null : PortalRuleResolver.Select(ServerPolicy.Allies.Teleport.Portals, portalContext)); PortalRuleConfig portalRuleConfig2 = ((portalContext == null) ? null : PortalRuleResolver.Select(PlayerPreferences.Allies.Teleport.Portals, portalContext)); IEnumerable<string> patterns = portalRuleConfig2?.Allow ?? PlayerPreferences.Allies.Allow; if (MatchesAllowedList(ServerPolicy.Allies.Allow, prefabName) && (portalRuleConfig?.Allow == null || MatchesAllowedList(portalRuleConfig.Allow, prefabName)) && MatchesAllowedList(patterns, prefabName) && !MatchesAny(ServerPolicy.Allies.Deny, prefabName) && (portalRuleConfig == null || !MatchesAny(portalRuleConfig.Deny, prefabName)) && !MatchesAny(PlayerPreferences.Allies.Deny, prefabName)) { if (portalRuleConfig2 != null) { return !MatchesAny(portalRuleConfig2.Deny, prefabName); } return true; } return false; } public bool IsTransitionEnabled(TransportTransitionKind transitionKind, bool basementsInstalled) { if (CreatureTransitionPolicy.IsEnabled(ServerPolicy.Allies.Teleport, transitionKind, basementsInstalled)) { return CreatureTransitionPolicy.IsEnabled(PlayerPreferences.Allies.Teleport, transitionKind, basementsInstalled); } return false; } public float GetAllySearchRadius() { return Math.Min(ServerPolicy.Allies.MaximumSearchRadius, PlayerPreferences.Allies.SearchRadius); } public float GetAllySearchHeightRadius() { return Math.Min(ServerPolicy.Allies.MaximumSearchHeightRadius, PlayerPreferences.Allies.SearchHeightRadius); } private static bool MatchesAllowedList(IEnumerable<string> patterns, string prefabName) { return patterns.Any((string pattern) => CreatureMatcher.IsMatch(pattern, prefabName)); } private static bool MatchesAny(IEnumerable<string> patterns, string prefabName) { return patterns.Any((string pattern) => CreatureMatcher.IsMatch(pattern, prefabName)); } } internal static class CreatureTeleportDiagnostics { public static string FormatCapture(TransportTransitionKind transitionKind, string player, string source, string requestedDestination, int allies) { return $"TeleportCaptured: transition={transitionKind} player={player} source={source} requestedDestination={requestedDestination} allies={allies}"; } public static string FormatMovement(TransportTransitionKind transitionKind, string prefabName, string customName, string zdoId, string source, string destination, string method) { return $"AllyMoved: transition={transitionKind} prefab={prefabName} name={customName} zdo={zdoId} source={source} destination={destination} method={method}"; } public static string FormatCompletion(TransportTransitionKind transitionKind, string player, string source, string destination, int moved, int skipped) { return $"TeleportCompleted: transition={transitionKind} player={player} source={source} destination={destination} moved={moved} skipped={skipped}"; } public static string FormatSkip(TransportTransitionKind transitionKind, string prefabName, string customName, string zdoId, string source, string reason) { return $"AllySkipped: transition={transitionKind} prefab={prefabName} name={customName} zdo={zdoId} source={source} reason={reason}"; } public static string FormatFiltered(TransportTransitionKind transitionKind, string player, int nonTamed, int outOfRange, int policyDenied) { return $"AllyFilterSummary: transition={transitionKind} player={player} nonTamed={nonTamed} outOfRange={outOfRange} policyDenied={policyDenied}"; } public static string FormatPosition(float x, float y, float z) { return string.Format(CultureInfo.InvariantCulture, "({0:0.0}, {1:0.0}, {2:0.0})", x, y, z); } } public enum TransportTransitionKind { Portal, DungeonEntry, DungeonExit, Basement } internal static class CreatureTransitionPolicy { public static bool IsEnabled(AllyTeleportConfig config, TransportTransitionKind kind, bool basementsInstalled) { switch (kind) { default: return config.Dungeons.Enabled; case TransportTransitionKind.Basement: if (basementsInstalled) { return config.Basements.Enabled; } return false; case TransportTransitionKind.Portal: return config.Portals.Enabled; } } public static bool IsEnabled(PlayerAllyTeleportConfig config, TransportTransitionKind kind, bool basementsInstalled) { switch (kind) { default: return config.Dungeons.Enabled; case TransportTransitionKind.Basement: if (basementsInstalled) { return config.Basements.Enabled; } return false; case TransportTransitionKind.Portal: return config.Portals.Enabled; } } } internal sealed class CreatureTransportMetrics { public int CharactersScanned { get; private set; } public int CandidatesEvaluated { get; private set; } public int ClearanceChecks { get; private set; } public void RecordCharacterScanned() { CharactersScanned++; } public void RecordCandidateEvaluated() { CandidatesEvaluated++; } public void RecordClearanceCheck() { ClearanceChecks++; } } internal static class DirectTeleportDecision { public static bool ShouldCapture(bool teleportSucceeded, bool isLocalPlayer, bool isInterior, bool isBasement, bool nativePortalTransition, bool hasPortalContext, bool transitionEnabled) { return teleportSucceeded && isLocalPlayer && !isInterior && !isBasement && !nativePortalTransition && hasPortalContext && transitionEnabled; } } internal static class AllyTransport { private sealed class PendingTransport { public IReadOnlyList<PendingCreature> Creatures { get; } public TransportTransitionKind TransitionKind { get; } public PortalContext? PortalContext { get; } public Vector3 PlayerSourcePosition { get; } public Vector3 RequestedDestination { get; } public CreatureTransportMetrics? Metrics { get; } public double CaptureElapsedMilliseconds { get; } public PendingTransport(IReadOnlyList<PendingCreature> creatures, TransportTransitionKind transitionKind, PortalContext? portalContext, Vector3 playerSourcePosition, Vector3 requestedDestination, CreatureTransportMetrics? metrics, double captureElapsedMilliseconds) { //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) Creatures = creatures; TransitionKind = transitionKind; PortalContext = portalContext; PlayerSourcePosition = playerSourcePosition; RequestedDestination = requestedDestination; Metrics = metrics; CaptureElapsedMilliseconds = captureElapsedMilliseconds; } } private sealed class PendingCreature { public ZDOID Id { get; } public string PrefabName { get; } public string CustomName { get; } public Vector3 SourcePosition { get; } public Character SourceInstance { get; } public float Radius { get; } public float Height { get; } public Tameable? Tameable { get; } public float OriginalUnsummonDistance { get; } public PendingCreature(ZDOID id, string prefabName, string customName, Vector3 sourcePosition, Character sourceInstance, float radius, float height, Tameable? tameable, float originalUnsummonDistance) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) Id = id; PrefabName = prefabName; CustomName = customName; SourcePosition = sourcePosition; SourceInstance = sourceInstance; Radius = radius; Height = height; Tameable = tameable; OriginalUnsummonDistance = originalUnsummonDistance; } } private const int PlacementAttemptsPerAnimal = 12; private const int PositionsPerRing = 7; private const float ForwardHalfAngleDegrees = 75f; private const float MaximumSlopeDegrees = 35f; private const float GroundClearance = 0.1f; private const float WaterClearance = 0.25f; private const float MaximumDungeonFloorHeightDifference = 3f; private const int ClearanceBufferSize = 32; private static readonly Dictionary<ZDOID, PendingTransport> PendingByPlayer = new Dictionary<ZDOID, PendingTransport>(); private static readonly Collider[] ClearanceBuffer = (Collider[])(object)new Collider[32]; public static int CountEligibleAllies(Player player, PortalContext portalContext) { //IL_00a3: Unknown result type (might be due to invalid IL or missing references) //IL_00ae: Unknown result type (might be due to invalid IL or missing references) //IL_00c5: 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) if (!TeleportCreaturesPlugin.IsEnabled || !((Character)player).IsOwner() || !portalContext.IsUsable || !TeleportCreaturesPlugin.Policy.IsTransitionEnabled(TransportTransitionKind.Portal, TeleportCreaturesPlugin.BasementsInstalled)) { return 0; } EffectiveCreaturePolicy policy = TeleportCreaturesPlugin.Policy; float allySearchRadius = policy.GetAllySearchRadius(); float allySearchHeightRadius = policy.GetAllySearchHeightRadius(); float num = allySearchRadius * allySearchRadius; int num2 = 0; foreach (Character allCharacter in Character.GetAllCharacters()) { if (!((Object)(object)allCharacter == (Object)null) && allCharacter.IsTamed() && !((Object)(object)allCharacter.m_nview == (Object)null) && allCharacter.m_nview.IsValid() && !(HorizontalDistanceSquared(((Component)allCharacter).transform.position, ((Component)player).transform.position) > num) && !(Mathf.Abs(((Component)allCharacter).transform.position.y - ((Component)player).transform.position.y) > allySearchHeightRadius)) { Tameable component = ((Component)allCharacter).GetComponent<Tameable>(); MonsterAI component2 = ((Component)allCharacter).GetComponent<MonsterAI>(); bool isFollowingPlayer = (Object)(object)((component2 != null) ? component2.GetFollowTarget() : null) == (Object)(object)((Component)player).gameObject; bool hasCustomName = (Object)(object)component != (Object)null && !string.IsNullOrWhiteSpace(component.GetText()); if (policy.IsAllowed(allCharacter.m_nview.GetPrefabName(), isTamed: true, isFollowingPlayer, hasCustomName, portalContext)) { num2++; } } } return num2; } public static void CaptureEligibleAllies(Player player, TransportTransitionKind transitionKind, PortalContext? portalContext = null) { //IL_0257: Unknown result type (might be due to invalid IL or missing references) //IL_025c: Unknown result type (might be due to invalid IL or missing references) //IL_0279: Unknown result type (might be due to invalid IL or missing references) //IL_026b: Unknown result type (might be due to invalid IL or missing references) //IL_0290: Unknown result type (might be due to invalid IL or missing references) //IL_029b: Unknown result type (might be due to invalid IL or missing references) //IL_02a1: Unknown result type (might be due to invalid IL or missing references) //IL_02c4: Unknown result type (might be due to invalid IL or missing references) //IL_02cf: Unknown result type (might be due to invalid IL or missing references) //IL_0139: 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) //IL_015d: Unknown result type (might be due to invalid IL or missing references) //IL_016d: Unknown result type (might be due to invalid IL or missing references) //IL_0202: Unknown result type (might be due to invalid IL or missing references) //IL_0217: Unknown result type (might be due to invalid IL or missing references) if (!TeleportCreaturesPlugin.IsEnabled || !((Character)player).IsOwner()) { return; } if (transitionKind == TransportTransitionKind.Portal && (portalContext == null || !portalContext.IsUsable)) { TeleportCreaturesPlugin.LogDiagnostic("Skipped creature portal transport because the source portal context was unavailable."); return; } long timestamp = Stopwatch.GetTimestamp(); CreatureTransportMetrics creatureTransportMetrics = (TeleportCreaturesPlugin.TransportProfilingEnabled ? new CreatureTransportMetrics() : null); EffectiveCreaturePolicy policy = TeleportCreaturesPlugin.Policy; _ = policy.ServerPolicy; List<PendingCreature> list = new List<PendingCreature>(); int num = 0; int num2 = 0; int num3 = 0; foreach (Character allCharacter in Character.GetAllCharacters()) { creatureTransportMetrics?.RecordCharacterScanned(); if ((Object)(object)allCharacter == (Object)null || (Object)(object)allCharacter.m_nview == (Object)null || !allCharacter.m_nview.IsValid()) { continue; } if (!allCharacter.IsTamed()) { num++; continue; } string prefabName = allCharacter.m_nview.GetPrefabName(); Tameable component = ((Component)allCharacter).GetComponent<Tameable>(); MonsterAI component2 = ((Component)allCharacter).GetComponent<MonsterAI>(); bool isFollowingPlayer = (Object)(object)((component2 != null) ? component2.GetFollowTarget() : null) == (Object)(object)((Component)player).gameObject; bool hasCustomName = (Object)(object)component != (Object)null && !string.IsNullOrWhiteSpace(component.GetText()); if (!policy.IsAllowed(prefabName, isTamed: true, isFollowingPlayer, hasCustomName, portalContext)) { num3++; continue; } if (HorizontalDistance(((Component)allCharacter).transform.position, ((Component)player).transform.position) > policy.GetAllySearchRadius() || Mathf.Abs(((Component)allCharacter).transform.position.y - ((Component)player).transform.position.y) > policy.GetAllySearchHeightRadius()) { num2++; continue; } TeleportCreaturesPlugin.LogSkipped(CreatureTeleportDiagnostics.FormatFiltered(transitionKind, player.GetPlayerName(), num, num2, num3)); allCharacter.m_nview.ClaimOwnership(); if (!allCharacter.IsOwner()) { LogSkipped(transitionKind, allCharacter, prefabName, component, "could not claim source network ownership"); continue; } float originalUnsummonDistance = (((Object)(object)component != (Object)null) ? component.m_unsummonDistance : 0f); if ((Object)(object)component != (Object)null) { component.m_unsummonDistance = 0f; } list.Add(new PendingCreature(allCharacter.GetZDOID(), prefabName, GetCustomName(component), ((Component)allCharacter).transform.position, allCharacter, allCharacter.GetRadius(), allCharacter.GetHeight(), component, originalUnsummonDistance)); } ZDOID zDOID = ((Character)player).GetZDOID(); if (list.Count == 0) { PendingByPlayer.Remove(zDOID); return; } if (PendingByPlayer.TryGetValue(zDOID, out PendingTransport value)) { RestoreUnsummonDistances(value); } PendingByPlayer[zDOID] = new PendingTransport(list, transitionKind, portalContext, ((Component)player).transform.position, player.m_teleportTargetPos, creatureTransportMetrics, GetElapsedMilliseconds(timestamp)); TeleportCreaturesPlugin.LogDiagnostic(CreatureTeleportDiagnostics.FormatCapture(transitionKind, player.GetPlayerName(), FormatPosition(((Component)player).transform.position), FormatPosition(player.m_teleportTargetPos), list.Count) + FormatPortalRule(policy, portalContext)); } public static void CompleteAfterNativeArrival(Player player) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //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_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0327: Unknown result type (might be due to invalid IL or missing references) //IL_0337: Unknown result type (might be due to invalid IL or missing references) //IL_009d: Unknown result type (might be due to invalid IL or missing references) //IL_01f7: Unknown result type (might be due to invalid IL or missing references) //IL_0206: Unknown result type (might be due to invalid IL or missing references) //IL_0213: Unknown result type (might be due to invalid IL or missing references) //IL_0221: Unknown result type (might be due to invalid IL or missing references) //IL_027b: Unknown result type (might be due to invalid IL or missing references) //IL_0288: Unknown result type (might be due to invalid IL or missing references) //IL_0299: Unknown result type (might be due to invalid IL or missing references) //IL_02a1: Unknown result type (might be due to invalid IL or missing references) //IL_02b2: Unknown result type (might be due to invalid IL or missing references) //IL_02dd: Unknown result type (might be due to invalid IL or missing references) //IL_02ed: Unknown result type (might be due to invalid IL or missing references) ZDOID zDOID = ((Character)player).GetZDOID(); if (!PendingByPlayer.TryGetValue(zDOID, out PendingTransport value)) { return; } PendingByPlayer.Remove(zDOID); if (!TeleportCreaturesPlugin.IsEnabled || !((Character)player).IsOwner()) { TeleportCreaturesPlugin.LogDiagnostic("Discarded pending creature transport because the local player is no longer the owner."); return; } long timestamp = Stopwatch.GetTimestamp(); EffectiveCreaturePolicy policy = TeleportCreaturesPlugin.Policy; CreatureConfig serverPolicy = policy.ServerPolicy; List<Vector3> list = new List<Vector3>(); int num = 0; int num2 = 0; foreach (PendingCreature creature in value.Creatures) { Character val = ResolveCharacter(creature); object obj; if (!((Object)(object)val == (Object)null)) { obj = null; } else { ZDOMan instance = ZDOMan.instance; obj = ((instance != null) ? instance.GetZDO(creature.Id) : null); } ZDO val2 = (ZDO)obj; CreatureArrivalAction creatureArrivalAction = CreatureArrivalDecision.Select((Object)(object)val != (Object)null, val2 != null, val2 != null && val2.IsOwner()); if (creatureArrivalAction == CreatureArrivalAction.Skip) { num2++; LogSkipped(value, creature, "no locally-owned network state was available after arrival"); RestoreUnsummonDistance(creature); continue; } if ((Object)(object)val != (Object)null) { Tameable component = ((Component)val).GetComponent<Tameable>(); MonsterAI component2 = ((Component)val).GetComponent<MonsterAI>(); bool isFollowingPlayer = (Object)(object)((component2 != null) ? component2.GetFollowTarget() : null) == (Object)(object)((Component)player).gameObject; bool hasCustomName = (Object)(object)component != (Object)null && !string.IsNullOrWhiteSpace(component.GetText()); if (!val.IsTamed() || !policy.IsAllowed(val.m_nview.GetPrefabName(), isTamed: true, isFollowingPlayer, hasCustomName, value.PortalContext)) { num2++; LogSkipped(value, creature, "it is no longer eligible after arrival"); RestoreUnsummonDistance(creature); continue; } } if (!TryFindSafePlacement(player, creature, serverPolicy, value.TransitionKind, list, num + num2, value.Metrics, out Vector3 placement, out string failureReason)) { num2++; LogSkipped(value, creature, failureReason); RestoreUnsummonDistance(creature); continue; } if (!CreatureArrivalDecision.ShouldPersistNetworkRecord(creatureArrivalAction)) { num2++; LogSkipped(value, creature, "no persistable network state was available after arrival"); RestoreUnsummonDistance(creature); continue; } if (creatureArrivalAction == CreatureArrivalAction.MoveOwnedRecord) { val2.SetPosition(placement); val2.SetRotation(((Component)player).transform.rotation); LogMovement(value, creature, placement, "owned-zdo"); list.Add(placement); num++; RestoreUnsummonDistance(creature); continue; } val.m_nview.ClaimOwnership(); if (!val.IsOwner()) { num2++; LogSkipped(value, creature, "could not retain network ownership"); RestoreUnsummonDistance(creature); continue; } ZDO zDO = val.m_nview.GetZDO(); zDO.SetPosition(placement); zDO.SetRotation(((Component)player).transform.rotation); ((Component)val).transform.SetPositionAndRotation(placement, ((Component)player).transform.rotation); val.m_body.linearVelocity = Vector3.zero; val.InvalidateCachedLiquidDepth(); MonsterAI component3 = ((Component)val).GetComponent<MonsterAI>(); if (component3 != null) { component3.SetFollowTarget(((Component)player).gameObject); } list.Add(placement); num++; LogMovement(value, creature, placement, "live-instance"); RestoreUnsummonDistance(creature); } TeleportCreaturesPlugin.LogDiagnostic(CreatureTeleportDiagnostics.FormatCompletion(value.TransitionKind, player.GetPlayerName(), FormatPosition(value.PlayerSourcePosition), FormatPosition(((Component)player).transform.position), num, num2)); if (value.TransitionKind == TransportTransitionKind.Portal) { PortalAllyDisplay.ShowCompletionMessage(num); } if (value.Metrics != null) { TeleportCreaturesPlugin.LogDiagnostic($"TransportProfile: transition={value.TransitionKind} scanned={value.Metrics.CharactersScanned} eligible={value.Creatures.Count} candidates={value.Metrics.CandidatesEvaluated} clearanceChecks={value.Metrics.ClearanceChecks} captureMs={value.CaptureElapsedMilliseconds:0.00} arrivalMs={GetElapsedMilliseconds(timestamp):0.00}"); } } public static void CancelPendingTransports() { foreach (PendingTransport value in PendingByPlayer.Values) { RestoreUnsummonDistances(value); } PendingByPlayer.Clear(); PortalTransitionState.Clear(); } private static Character? ResolveCharacter(PendingCreature pendingCreature) { //IL_0047: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)pendingCreature.SourceInstance != (Object)null && (Object)(object)pendingCreature.SourceInstance.m_nview != (Object)null && pendingCreature.SourceInstance.m_nview.IsValid()) { return pendingCreature.SourceInstance; } ZNetScene instance = ZNetScene.instance; GameObject val = ((instance != null) ? instance.FindInstance(pendingCreature.Id) : null); if (!((Object)(object)val != (Object)null)) { return null; } return val.GetComponent<Character>(); } private static void RestoreUnsummonDistance(PendingCreature pendingCreature) { if ((Object)(object)pendingCreature.Tameable != (Object)null) { pendingCreature.Tameable.m_unsummonDistance = pendingCreature.OriginalUnsummonDistance; } } private static void RestoreUnsummonDistances(PendingTransport pending) { foreach (PendingCreature creature in pending.Creatures) { RestoreUnsummonDistance(creature); } } private static void LogMovement(PendingTransport pending, PendingCreature creature, Vector3 destination, string method) { //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0018: 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_0031: Unknown result type (might be due to invalid IL or missing references) TeleportCreaturesPlugin.LogDiagnostic(CreatureTeleportDiagnostics.FormatMovement(pending.TransitionKind, creature.PrefabName, creature.CustomName, ((object)creature.Id/*cast due to .constrained prefix*/).ToString(), FormatPosition(creature.SourcePosition), FormatPosition(destination), method)); } private static void LogSkipped(TransportTransitionKind transitionKind, Character creature, string prefabName, Tameable? tameable, string reason) { //IL_0009: 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_0022: Unknown result type (might be due to invalid IL or missing references) TeleportCreaturesPlugin.LogSkipped(CreatureTeleportDiagnostics.FormatSkip(transitionKind, prefabName, GetCustomName(tameable), ((object)creature.GetZDOID()/*cast due to .constrained prefix*/).ToString(), FormatPosition(((Component)creature).transform.position), reason)); } private static void LogSkipped(PendingTransport pending, PendingCreature creature, string reason) { //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0018: 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) TeleportCreaturesPlugin.LogSkipped(CreatureTeleportDiagnostics.FormatSkip(pending.TransitionKind, creature.PrefabName, creature.CustomName, ((object)creature.Id/*cast due to .constrained prefix*/).ToString(), FormatPosition(creature.SourcePosition), reason)); } private static string GetCustomName(Tameable? tameable) { if (!((Object)(object)tameable != (Object)null) || string.IsNullOrWhiteSpace(tameable.GetText())) { return "-"; } return tameable.GetText(); } private static string FormatPosition(Vector3 position) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //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) return CreatureTeleportDiagnostics.FormatPosition(position.x, position.y, position.z); } private static bool TryFindSafePlacement(Player player, PendingCreature creature, CreatureConfig config, TransportTransitionKind transitionKind, IReadOnlyCollection<Vector3> reservedPlacements, int animalIndex, CreatureTransportMetrics? metrics, out Vector3 placement, out string failureReason) { //IL_0207: 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_0092: Unknown result type (might be due to invalid IL or missing references) //IL_00a0: Unknown result type (might be due to invalid IL or missing references) //IL_00a5: Unknown result type (might be due to invalid IL or missing references) //IL_00a7: Unknown result type (might be due to invalid IL or missing references) //IL_00af: Unknown result type (might be due to invalid IL or missing references) //IL_00c9: Unknown result type (might be due to invalid IL or missing references) //IL_0109: Unknown result type (might be due to invalid IL or missing references) //IL_015d: Unknown result type (might be due to invalid IL or missing references) //IL_0145: Unknown result type (might be due to invalid IL or missing references) //IL_01a0: Unknown result type (might be due to invalid IL or missing references) //IL_01a5: Unknown result type (might be due to invalid IL or missing references) //IL_01a7: Unknown result type (might be due to invalid IL or missing references) //IL_01a9: Unknown result type (might be due to invalid IL or missing references) //IL_01e9: Unknown result type (might be due to invalid IL or missing references) //IL_01eb: Unknown result type (might be due to invalid IL or missing references) float num = transitionKind switch { TransportTransitionKind.Portal => config.Placement.Radius, TransportTransitionKind.Basement => config.Placement.Basements.Radius, _ => config.Placement.Dungeons.Radius, }; int num2 = Mathf.Max(1, Mathf.FloorToInt((num - config.Placement.DistanceFromPlayer + 0.001f) / config.Placement.AnimalSpacing) + 1); int num3 = Mathf.Max(12, num2 * 7); for (int i = 0; i < num3; i++) { metrics?.RecordCandidateEvaluated(); Vector3 val = CreateCandidate(((Component)player).transform.position, ((Component)player).transform.rotation, config.Placement, animalIndex, i); if (HorizontalDistance(val, ((Component)player).transform.position) > num) { failureReason = "the landing search reached its configured radius"; continue; } if (!TryGetGround(val, transitionKind, config.Placement.MaximumHeightDifference, out Vector3 groundPosition, out float solidHeight, out string failureReason2)) { failureReason = failureReason2; continue; } if (transitionKind == TransportTransitionKind.DungeonExit && config.Placement.Dungeons.RequireNaturalGroundOnExit && !CreaturePlacementPolicy.IsNaturalGround(ZoneSystem.instance.GetGroundHeight(val), solidHeight, 0.5f)) { failureReason = "the landing surface was above natural terrain"; continue; } if ((transitionKind != TransportTransitionKind.Basement || config.Placement.Basements.RequireClearance) && !HasCreatureClearance(creature, groundPosition, metrics)) { failureReason = "the landing space was blocked by a wall, ceiling, furniture, or structure"; continue; } if (!MatchesWaterPolicy(groundPosition, config.Placement.Water)) { failureReason = $"no location matched Placement.Water = {config.Placement.Water}"; continue; } bool flag = false; foreach (Vector3 reservedPlacement in reservedPlacements) { if (HorizontalDistance(groundPosition, reservedPlacement) < config.Placement.AnimalSpacing) { flag = true; break; } } if (flag) { failureReason = "no location met Placement.AnimalSpacing"; continue; } placement = groundPosition; failureReason = string.Empty; return true; } placement = default(Vector3); failureReason = "no safe landing location was found"; return false; } private static Vector3 CreateCandidate(Vector3 playerPosition, Quaternion rotation, PlacementConfig config, int animalIndex, int attempt) { //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) //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_0054: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_0056: 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) int candidateSlot = CreaturePlacementPolicy.GetCandidateSlot(animalIndex, attempt, 7); int num = candidateSlot / 7 + 1; float num2 = config.DistanceFromPlayer + (float)(num - 1) * config.AnimalSpacing; float forwardSearchAngle = CreaturePlacementPolicy.GetForwardSearchAngle(candidateSlot, 7, 75f); Vector3 val = rotation * (Quaternion.Euler(0f, forwardSearchAngle, 0f) * Vector3.forward * num2); return playerPosition + val; } private static bool TryGetGround(Vector3 candidate, TransportTransitionKind transitionKind, float maximumHeightDifference, out Vector3 groundPosition, out float solidHeight, out string failureReason) { //IL_000c: 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_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_0029: 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) //IL_004f: 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_0070: Unknown result type (might be due to invalid IL or missing references) //IL_00f3: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_010f: Unknown result type (might be due to invalid IL or missing references) //IL_00b6: Unknown result type (might be due to invalid IL or missing references) //IL_00c5: Unknown result type (might be due to invalid IL or missing references) //IL_00cb: Unknown result type (might be due to invalid IL or missing references) //IL_00d0: 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_0167: Unknown result type (might be due to invalid IL or missing references) //IL_0168: Unknown result type (might be due to invalid IL or missing references) //IL_013b: Unknown result type (might be due to invalid IL or missing references) //IL_011f: Unknown result type (might be due to invalid IL or missing references) //IL_01a2: Unknown result type (might be due to invalid IL or missing references) //IL_01af: Unknown result type (might be due to invalid IL or missing references) //IL_01b5: Unknown result type (might be due to invalid IL or missing references) //IL_01ba: Unknown result type (might be due to invalid IL or missing references) //IL_017a: Unknown result type (might be due to invalid IL or missing references) //IL_014f: Unknown result type (might be due to invalid IL or missing references) if (CreaturePlacementPolicy.UsesNativePlayerLevel(transitionKind == TransportTransitionKind.Basement)) { groundPosition = new Vector3(candidate.x, candidate.y + 0.1f, candidate.z); solidHeight = candidate.y; failureReason = string.Empty; return true; } if ((Object)(object)ZoneSystem.instance == (Object)null) { groundPosition = default(Vector3); solidHeight = 0f; failureReason = "the destination terrain was not ready"; return false; } float num = default(float); if (transitionKind == TransportTransitionKind.DungeonEntry && ZoneSystem.instance.FindFloor(candidate, ref num)) { if (!CreaturePlacementPolicy.IsOnPlayerLevel(candidate.y, num, 3f)) { groundPosition = default(Vector3); solidHeight = 0f; failureReason = "the dungeon floor was not on the player's current vertical level"; return false; } solidHeight = CreaturePlacementPolicy.SelectLandingHeight(hasInteriorFloor: true, num, 0f); groundPosition = new Vector3(candidate.x, solidHeight + 0.1f, candidate.z); failureReason = string.Empty; return true; } float num2 = default(float); Vector3 val = default(Vector3); GameObject val2 = default(GameObject); if (!ZoneSystem.instance.GetSolidHeight(candidate, ref num2, ref val, ref val2)) { groundPosition = default(Vector3); solidHeight = 0f; failureReason = "the destination terrain was not ready"; return false; } if (transitionKind == TransportTransitionKind.Basement && !CreaturePlacementPolicy.IsOnPlayerLevel(candidate.y, num2, maximumHeightDifference)) { groundPosition = default(Vector3); solidHeight = 0f; failureReason = "the basement surface was on another vertical layer"; return false; } if (transitionKind == TransportTransitionKind.DungeonEntry && !CreaturePlacementPolicy.CanUseDungeonFallbackHeight(candidate.y, num2, 3f)) { groundPosition = default(Vector3); solidHeight = 0f; failureReason = "the dungeon fallback surface was not on the player's current vertical level"; return false; } if (Vector3.Angle(val, Vector3.up) > 35f) { groundPosition = default(Vector3); solidHeight = 0f; failureReason = $"the destination slope exceeded {35f:0} degrees"; return false; } groundPosition = new Vector3(candidate.x, num2 + 0.1f, candidate.z); solidHeight = CreaturePlacementPolicy.SelectLandingHeight(hasInteriorFloor: false, 0f, num2); failureReason = string.Empty; return true; } private static bool HasCreatureClearance(PendingCreature creature, Vector3 groundPosition, CreatureTransportMetrics? metrics) { //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_004b: 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_0058: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Unknown result type (might be due to invalid IL or missing references) metrics?.RecordClearanceCheck(); float num = Mathf.Max(0.2f, creature.Radius * 0.9f); float num2 = Mathf.Max(num * 2f, creature.Height * 0.9f); Vector3 val = groundPosition + Vector3.up * num; Vector3 val2 = groundPosition + Vector3.up * (num2 - num); return CreaturePlacementPolicy.IsClearanceFree(Physics.OverlapCapsuleNonAlloc(val, val2, num, ClearanceBuffer, -5, (QueryTriggerInteraction)1), ClearanceBuffer.Length); } private static bool MatchesWaterPolicy(Vector3 groundPosition, WaterPolicy policy) { //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) bool flag = false; foreach (WaterVolume instance in WaterVolume.Instances) { if ((Object)(object)instance != (Object)null && instance.GetWaterSurface(groundPosition, Time.time) > groundPosition.y + 0.25f) { flag = true; break; } } return policy switch { WaterPolicy.Allow => true, WaterPolicy.Require => flag, _ => !flag, }; } private static float HorizontalDistance(Vector3 first, Vector3 second) { //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) first.y = 0f; second.y = 0f; return Vector3.Distance(first, second); } private static float HorizontalDistanceSquared(Vector3 first, Vector3 second) { //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_0019: 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_001f: Unknown result type (might be due to invalid IL or missing references) first.y = 0f; second.y = 0f; Vector3 val = first - second; return ((Vector3)(ref val)).sqrMagnitude; } private static double GetElapsedMilliseconds(long startTimestamp) { return (double)(Stopwatch.GetTimestamp() - startTimestamp) * 1000.0 / (double)Stopwatch.Frequency; } private static string FormatPortalRule(EffectiveCreaturePolicy policy, PortalContext? context) { if (context == null) { return string.Empty; } PortalRuleConfig portalRuleConfig = PortalRuleResolver.Select(policy.ServerPolicy.Allies.Teleport.Portals, context); PortalRuleConfig portalRuleConfig2 = PortalRuleResolver.Select(policy.PlayerPreferences.Allies.Teleport.Portals, context); return ", portalPrefab=" + context.PrefabName + ", portalTag=" + context.Tag + ", serverPortalRule=" + (portalRuleConfig?.Id ?? "default") + ", playerPortalRule=" + (portalRuleConfig2?.Id ?? "default"); } } internal static class PortalAllyDisplay { private const float RefreshIntervalSeconds = 0.25f; private const float StaleTimeoutSeconds = 1f; private const string TranslationKey = "$tc_transporting_allies_message"; private const string CompletionTranslationKey = "$tc_transported_allies_message"; private static TeleportWorld? _trackedPortal; private static TMP_Text? _counterText; private static AllyTransportMessageMode _counterMode; private static float _nextRefreshTime; private static float _nextCenterMessageTime; private static int _lastCenterMessageCount; private static float _lastShownTime; private static bool _reportedUpdateError; public static void UpdateForPortal(TeleportWorld portal) { try { UpdateForPortalSafely(portal); } catch (Exception ex) { Hide(); if (!_reportedUpdateError) { _reportedUpdateError = true; TeleportCreaturesPlugin.Log.LogWarning((object)("Could not update the local portal ally counter: " + ex.Message)); } } } public static void ShowCompletionMessage(int allyCount) { if (allyCount > 0 && !((Object)(object)Player.m_localPlayer == (Object)null) && !((Character)Player.m_localPlayer).IsDead() && !((Object)(object)MessageHud.instance == (Object)null) && Localization.instance != null) { MessageHud.instance.ShowMessage((MessageType)1, LocalizeCompletionCount(allyCount), 0, (Sprite)null, false, true); } } public static void Dispose() { if ((Object)(object)_counterText != (Object)null) { Object.Destroy((Object)(object)((Component)_counterText).gameObject); } _counterText = null; _trackedPortal = null; _nextRefreshTime = 0f; _nextCenterMessageTime = 0f; _lastCenterMessageCount = 0; _lastShownTime = 0f; _reportedUpdateError = false; } private static void UpdateForPortalSafely(TeleportWorld portal) { if (TeleportCreaturesPlugin.AllyTransportMessageMode == AllyTransportMessageMode.NoMessages) { Hide(); return; } Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null || ((Character)localPlayer).IsDead() || (Object)(object)portal == (Object)null || (Object)(object)portal.m_proximityRoot == (Object)null || !IsPlayerWithinRange(localPlayer, portal)) { if ((Object)(object)portal != (Object)null) { HideIfTracked(portal); } return; } if (!portal.HaveTarget() || !((Humanoid)localPlayer).IsTeleportable(portal.m_allowAllItems)) { HideIfTracked(portal); return; } _trackedPortal = portal; if (Time.time < _nextRefreshTime) { return; } _nextRefreshTime = Time.time + 0.25f; if (!PortalContextReader.TryRead(portal, out PortalContext context)) { Hide(); return; } int allyCount = AllyTransport.CountEligibleAllies(localPlayer, context); if (!AllyTransportDisplayPolicy.ShouldShowMessage(TeleportCreaturesPlugin.AllyTransportMessageMode, allyCount)) { Hide(); return; } if (TeleportCreaturesPlugin.AllyTransportMessageMode == AllyTransportMessageMode.TopCenter) { ShowTopCenterMessage(allyCount); Hide(); return; } TMP_Text orCreateCounter = GetOrCreateCounter(TeleportCreaturesPlugin.AllyTransportMessageMode); if (!((Object)(object)orCreateCounter == (Object)null)) { orCreateCounter.text = LocalizeCount(allyCount); ((Component)orCreateCounter).gameObject.SetActive(true); _lastShownTime = Time.time; } } public static void HideIfStale() { if (!((Object)(object)_counterText == (Object)null) && ((Component)_counterText).gameObject.activeSelf && Time.time - _lastShownTime > 1f) { Hide(); } } public static void Hide() { if ((Object)(object)_counterText != (Object)null) { ((Component)_counterText).gameObject.SetActive(false); } _trackedPortal = null; _nextRefreshTime = 0f; } private static bool IsPlayerWithinRange(Player player, TeleportWorld portal) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) Vector3 val = ((Component)player).transform.position - portal.m_proximityRoot.position; return ((Vector3)(ref val)).sqrMagnitude <= portal.m_activationRange * portal.m_activationRange; } private static TMP_Text? GetOrCreateCounter(AllyTransportMessageMode mode) { //IL_0113: Unknown result type (might be due to invalid IL or missing references) //IL_0131: Unknown result type (might be due to invalid IL or missing references) //IL_014f: Unknown result type (might be due to invalid IL or missing references) //IL_016d: Unknown result type (might be due to invalid IL or missing references) //IL_00cd: Unknown result type (might be due to invalid IL or missing references) //IL_00dc: Unknown result type (might be due to invalid IL or missing references) //IL_00e1: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_counterText != (Object)null && _counterMode == mode) { return _counterText; } if ((Object)(object)_counterText != (Object)null) { Object.Destroy((Object)(object)((Component)_counterText).gameObject); } TMP_Text val = (TMP_Text)((mode != AllyTransportMessageMode.TopLeft) ? ((object)Hud.instance?.m_hoverName) : ((object)MessageHud.instance?.m_messageText)); if ((Object)(object)val == (Object)null) { return null; } _counterText = Object.Instantiate<TMP_Text>(val, val.transform.parent); ((Object)_counterText).name = "TeleportCreaturesAllyCounter"; _counterText.textWrappingMode = (TextWrappingModes)0; _counterText.fontSize = 18f; if (mode == AllyTransportMessageMode.TopLeft) { _counterText.alignment = (TextAlignmentOptions)257; _counterText.rectTransform.anchoredPosition = val.rectTransform.anchoredPosition + new Vector2(0f, -36f); } else { _counterText.alignment = (TextAlignmentOptions)514; _counterText.rectTransform.anchorMin = new Vector2(0.5f, 0.5f); _counterText.rectTransform.anchorMax = new Vector2(0.5f, 0.5f); _counterText.rectTransform.pivot = new Vector2(0.5f, 0.5f); _counterText.rectTransform.anchoredPosition = new Vector2(0f, 72f); } _counterMode = mode; ((Component)_counterText).gameObject.SetActive(false); return _counterText; } private static string LocalizeCount(int allyCount) { return Localization.instance.Localize("$tc_transporting_allies_message", new string[1] { allyCount.ToString() }); } private static void ShowTopCenterMessage(int allyCount) { if (!((Object)(object)MessageHud.instance == (Object)null) && (allyCount != _lastCenterMessageCount || !(Time.time < _nextCenterMessageTime))) { MessageHud.instance.ShowMessage((MessageType)2, LocalizeCount(allyCount), 0, (Sprite)null, false, true); _lastCenterMessageCount = allyCount; _nextCenterMessageTime = Time.time + 1f; } } private static string LocalizeCompletionCount(int allyCount) { return Localization.instance.Localize("$tc_transported_allies_message", new string[1] { allyCount.ToString() }); } private static void HideIfTracked(TeleportWorld portal) { if ((Object)(object)_trackedPortal == (Object)(object)portal) { Hide(); } } } internal static class PortalTransitionState { internal readonly struct DirectTeleportCapture { public bool IsValid { get; } public Vector3 SourcePosition { get; } public PortalContext Context { get; } public DirectTeleportCapture(bool isValid, Vector3 sourcePosition, PortalContext context) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_0009: Unknown result type (might be due to invalid IL or missing references) IsValid = isValid; SourcePosition = sourcePosition; Context = context; } } private const float SourceStateLifetimeSeconds = 30f; private const float MaximumSourceDistance = 10f; private static TeleportWorld? _sourcePortal; private static Vector3 _sourcePosition; private static float _sourceCapturedAt; private static bool _hasSource; private static bool _nativePortalTransition; private static bool _directTeleportInProgress; public static void ObservePortalEntry(TeleportWorld portal, Player player) { //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)portal == (Object)null) && !((Object)(object)player == (Object)null) && !((Object)(object)player != (Object)(object)Player.m_localPlayer)) { _sourcePortal = portal; _sourcePosition = ((Component)player).transform.position; _sourceCapturedAt = Time.time; _hasSource = true; _directTeleportInProgress = false; } } public static void BeginNativePortalTransition() { _nativePortalTransition = true; } public static void EndNativePortalTransition() { _nativePortalTransition = false; _directTeleportInProgress = false; } public static DirectTeleportCapture BeginDirectTeleport(Player player) { //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_0088: Unknown result type (might be due to invalid IL or missing references) if (_nativePortalTransition || _directTeleportInProgress || (Object)(object)player == (Object)null || (Object)(object)player != (Object)(object)Player.m_localPlayer || !_hasSource || Time.time - _sourceCapturedAt > 30f || Vector3.Distance(((Component)player).transform.position, _sourcePosition) > 10f) { return default(DirectTeleportCapture); } if (!TryReadContext(_sourcePortal, out PortalContext context)) { _sourcePortal = null; return default(DirectTeleportCapture); } _directTeleportInProgress = true; return new DirectTeleportCapture(isValid: true, _sourcePosition, context); } public static void EndDirectTeleport() { _directTeleportInProgress = false; ClearSource(); } public static void Clear() { _nativePortalTransition = false; _directTeleportInProgress = false; ClearSource(); } private static void ClearSource() { //IL_000b: Unknown result type (might be due to invalid IL or missing references) _sourcePortal = null; _sourcePosition = default(Vector3); _sourceCapturedAt = 0f; _hasSource = false; } private static bool TryReadContext(TeleportWorld? portal, out PortalContext context) { //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)portal != (Object)null && PortalContextReader.TryRead(portal, out context)) { return true; } TeleportWorld[] array = Object.FindObjectsOfType<TeleportWorld>(); foreach (TeleportWorld val in array) { if (!((Object)(object)val == (Object)null) && !(Vector3.Distance(((Component)val).transform.position, _sourcePosition) > 10f) && PortalContextReader.TryRead(val, out context)) { return true; } } context = null; return false; } } [HarmonyPatch(typeof(Player), "TeleportTo")] internal static class DirectPortalCreatureTeleportPatch { private static void Prefix(Player __instance, out PortalTransitionState.DirectTeleportCapture __state) { __state = PortalTransitionState.BeginDirectTeleport(__instance); } private static void Postfix(Player __instance, bool __result, PortalTransitionState.DirectTeleportCapture __state) { if (!__state.IsValid) { return; } try { bool isBasement = TeleportCreaturesPlugin.BasementsInstalled && (Object)(object)EnvMan.instance != (Object)null && EnvMan.instance.GetCurrentEnvironment().m_name == "Basement"; if (DirectTeleportDecision.ShouldCapture(__result, (Object)(object)__instance == (Object)(object)Player.m_localPlayer, ((Character)__instance).InInterior(), isBasement, nativePortalTransition: false, __state.IsValid, TeleportCreaturesPlugin.IsEnabled && TeleportCreaturesPlugin.Policy.IsTransitionEnabled(TransportTransitionKind.Portal, TeleportCreaturesPlugin.BasementsInstalled))) { AllyTransport.CaptureEligibleAllies(__instance, TransportTransitionKind.Portal, __state.Context); TeleportCreaturesPlugin.LogDiagnostic("Captured creature portal transport through the direct Player.TeleportTo compatibility path."); } } catch (Exception arg) { TeleportCreaturesPlugin.Log.LogError((object)$"Could not capture creatures for a direct portal teleport: {arg}"); } finally { PortalTransitionState.EndDirectTeleport(); } } } [HarmonyPatch(typeof(Teleport), "Interact")] internal static class DungeonCreatureTeleportPatch { private static void Postfix(Humanoid character, bool hold, Teleport __instance, bool __result) { if (!__result || hold || (Object)(object)__instance.m_targetPoint == (Object)null) { return; } Player val = (Player)(object)((character is Player) ? character : null); if (val == null || !TeleportCreaturesPlugin.IsEnabled || !((Character)val).IsTeleporting()) { return; } TransportTransitionKind transportTransitionKind = ((TeleportCreaturesPlugin.BasementsInstalled && (Object)(object)EnvMan.instance != (Object)null && (EnvMan.instance.GetCurrentEnvironment().m_name == "Basement" || EnvMan.instance.GetCurrentEnvironment().m_name == "Undercroft")) ? TransportTransitionKind.Basement : ((!((Character)val).InInterior()) ? TransportTransitionKind.DungeonEntry : TransportTransitionKind.DungeonExit)); if (!TeleportCreaturesPlugin.Policy.IsTransitionEnabled(transportTransitionKind, TeleportCreaturesPlugin.BasementsInstalled)) { return; } try { AllyTransport.CaptureEligibleAllies(val, transportTransitionKind); } catch (Exception arg) { TeleportCreaturesPlugin.Log.LogError((object)$"Could not capture creatures for a {transportTransitionKind} teleport: {arg}"); } } } [HarmonyPatch(typeof(Player), "UpdateTeleport")] [HarmonyPriority(0)] internal static class PlayerCreatureArrivalPatch { private static void Prefix(Player __instance, out bool __state) { __state = ((Character)__instance).IsTeleporting(); } private static void Postfix(Player __instance, bool __state) { if (!__state || ((Character)__instance).IsTeleporting()) { return; } try { AllyTransport.CompleteAfterNativeArrival(__instance); } catch (Exception arg) { TeleportCreaturesPlugin.Log.LogError((object)$"Could not reconcile creatures after portal arrival: {arg}"); } } } [HarmonyPatch(typeof(Player), "Update")] internal static class PlayerAllyDisplayStalePatch { private static void Postfix(Player __instance) { if (!((Object)(object)__instance != (Object)(object)Player.m_localPlayer)) { if (((Character)__instance).IsDead()) { PortalAllyDisplay.Hide(); } else { PortalAllyDisplay.HideIfStale(); } } } } [HarmonyPatch(typeof(Player), "OnDeath")] internal static class PlayerAllyDisplayDeathPatch { private static void Postfix(Player __instance) { if ((Object)(object)__instance == (Object)(object)Player.m_localPlayer) { PortalAllyDisplay.Hide(); } } } [HarmonyPatch(typeof(TeleportWorld), "Teleport")] internal static class TeleportWorldCreaturePatch { private static void Prefix() { PortalTransitionState.BeginNativePortalTransition(); } private static void Postfix(TeleportWorld __instance, Player player) { try { if (TeleportCreaturesPlugin.IsEnabled && ((Character)player).IsTeleporting() && TeleportCreaturesPlugin.Policy.IsTransitionEnabled(TransportTransitionKind.Portal, TeleportCreaturesPlugin.BasementsInstalled)) { if (!PortalContextReader.TryRead(__instance, out PortalContext context)) { TeleportCreaturesPlugin.LogDiagnostic("Skipped creature portal transport because the source portal prefab or tag could not be read safely."); } else { AllyTransport.CaptureEligibleAllies(player, TransportTransitionKind.Portal, context); } } } catch (Exception arg) { TeleportCreaturesPlugin.Log.LogError((object)$"Could not capture creatures for a portal teleport: {arg}"); } finally { PortalTransitionState.EndNativePortalTransition(); } } } [HarmonyPatch(typeof(TeleportWorldTrigger), "OnTriggerEnter")] internal static class TeleportWorldTriggerCreaturePatch { private static void Prefix(TeleportWorldTrigger __instance, Collider colliderIn) { Player component = ((Component)colliderIn).GetComponent<Player>(); if ((Object)(object)component != (Object)null && (Object)(object)__instance.m_teleportWorld != (Object)null) { PortalTransitionState.ObservePortalEntry(__instance.m_teleportWorld, component); } } } [HarmonyPatch(typeof(TeleportWorld), "UpdatePortal")] internal static class TeleportWorldAllyDisplayPatch { private static void Postfix(TeleportWorld __instance) { PortalAllyDisplay.UpdateForPortal(__instance); } } internal static class PortalContextReader { public static bool TryRead(TeleportWorld portal, out PortalContext context) { //IL_0046: Unknown result type (might be due to invalid IL or missing references) context = null; try { if ((Object)(object)portal == (Object)null || (Object)(object)portal.m_nview == (Object)null || !portal.m_nview.IsValid()) { return false; } string prefabName = portal.m_nview.GetPrefabName(); if (string.IsNullOrWhiteSpace(prefabName)) { return false; } context = new PortalContext(prefabName, portal.GetTagInfo().text); return true; } catch (Exception ex) { TeleportCreaturesPlugin.LogDiagnostic("Could not read source portal context: " + ex.Message); return false; } } } [BepInPlugin("odinplus.TeleportCreatures", "TeleportCreatures", "0.2.4")] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] public sealed class TeleportCreaturesPlugin : BaseUnityPlugin { private enum Toggle { On = 1, Off = 0 } internal const string Author = "odinplus"; internal const string ModGuid = "odinplus.TeleportCreatures"; internal const string ModName = "TeleportCreatures"; internal const string ModVersion = "0.2.4"; private const string ConfigFileName = "odinplus.TeleportCreatures.cfg"; private static readonly ConfigSync Sync = new ConfigSync("odinplus.TeleportCreatures") { DisplayName = "TeleportCreatures", CurrentVersion = "0.2.4", MinimumRequiredVersion = "0.2.4" }; private static readonly CustomSyncedValue<string> Snapshot = new CustomSyncedValue<string>(Sync, "YamlPolicy", string.Empty); private static ConfigEntry<Toggle>? _forceServerConfig; private static ConfigEntry<bool>? _enableMod; private static ConfigEntry<bool>? _enableDiagnostics; private static ConfigEntry<bool>? _logSkippedAnimals; private static ConfigEntry<bool>? _profileCreatureTransport; private static ConfigEntry<string>? _allyTransportMessageMode; private readonly Harmony _harmony = new Harmony("odinplus.TeleportCreatures"); private readonly ISerializer _serializer = new SerializerBuilder().Build(); private FileSystemWatcher? _serverYamlWatcher; private FileSystemWatcher? _playerYamlWatcher; private FileSystemWatcher? _configWatcher; private string _serverYamlPath; private string _playerYamlPath; private GameObject? _yamlEditorHost; public static readonly ManualLogSource TeleportCreaturesLogger = Logger.CreateLogSource("TeleportCreatures"); internal static ManualLogSource Log = TeleportCreaturesLogger; internal static CreatureConfig ServerPolicy { get; private set; } = new CreatureConfig(); internal static PlayerCreaturePreferences PlayerPreferences { get; private set; } = PlayerCreaturePreferences.CreateDefault(); internal static EffectiveCreaturePolicy Policy { get; private set; } = EffectiveCreaturePolicy.Create(ServerPolicy, PlayerPreferences); internal static bool IsEnabled => _enableMod?.Value ?? false; internal static bool DiagnosticsEnabled => _enableDiagnostics?.Value ?? false; internal static bool LogSkippedAnimals => _logSkippedAnimals?.Value ?? false; internal static bool TransportProfilingEnabled { get { if (DiagnosticsEnabled) { return _profileCreatureTransport?.Value ?? false; } return false; } } internal static AllyTransportMessageMode AllyTransportMessageMode => AllyTransportDisplayPolicy.ParseMessageMode(_allyTransportMessageMode?.Value); internal static bool BasementsInstalled { get; private set; } internal static bool IsBlocked { get; private set; } private void Awake() { Log = TeleportCreaturesLogger; FindIncompatibleMods(); BindConfiguration(); _serverYamlPath = Path.Combine(Paths.ConfigPath, "odinplus.TeleportCreatures.server.yml"); _playerYamlPath = Path.Combine(Paths.ConfigPath, "odinplus.TeleportCreatures.yml"); Snapshot.ValueChanged += ReadSnapshot; if (Sync.IsSourceOfTruth) { ReloadServerYaml(); SetupServerYamlWatcher(); } ReloadPlayerYaml(); SetupPlayerYamlWatcher(); SetupConfigWatcher(); RegisterYamlEditor(); Localizer.Load(); _harmony.PatchAll(Assembly.GetExecutingAssembly()); } private void Start() { if (!IsBlocked) { BasementsInstalled = Chainloader.PluginInfos.ContainsKey("com.rolopogo.Basement"); ((BaseUnityPlugin)this).Logger.LogInfo((object)string.Format("{0} loaded. enabled={1}, yamlSource={2}.", "TeleportCreatures", IsEnabled, Sync.IsSourceOfTruth)); } } private void OnDestroy() { if (!IsBlocked) { _serverYamlWatcher?.Dispose(); _playerYamlWatcher?.Dispose(); _configWatcher?.Dispose(); if ((Object)(object)_yamlEditorHost != (Object)null) { Object.Destroy((Object)(object)_yamlEditorHost); } Snapshot.ValueChanged -= ReadSnapshot; AllyTransport.CancelPendingTransports(); PortalAllyDisplay.Dispose(); _harmony.UnpatchSelf(); ((BaseUnityPlugin)this).Config.Save(); } } private void BindConfiguration() { //IL_00a8: Unknown result type (might be due to invalid IL or missing references) //IL_00b2: Expected O, but got Unknown //IL_00fe: Unknown result type (might be due to invalid IL or missing references) //IL_0108: Expected O, but got Unknown //IL_013e: Unknown result type (might be due to invalid IL or missing references) //IL_0148: Expected O, but got Unknown //IL_017e: Unknown result type (might be due to invalid IL or missing references) //IL_0188: Expected O, but got Unknown bool saveOnConfigSet = ((BaseUnityPlugin)this).Config.SaveOnConfigSet; ((BaseUnityPlugin)this).Config.SaveOnConfigSet = false; try { _forceServerConfig = BindSynced("General", "Force Server Config", Toggle.On, "When On, the server's synchronized TeleportCreatures settings are enforced for connected clients."); Sync.AddLockingConfigEntry<Toggle>(_forceServerConfig); _enableMod = BindSynced("General", "Enable Mod", defaultValue: true, "Master switch for TeleportCreatures portal transport."); _allyTransportMessageMode = ((BaseUnityPlugin)this).Config.Bind<string>("Allies Display", "Portal Message Mode", "Top left", new ConfigDescription("Shows a local live count of eligible allies while you stand in an active portal. No messages hides it.", (AcceptableValueBase)(object)new AcceptableValueList<string>(new string[4] { "No messages", "Top left", "Top center", "Center" }), Array.Empty<object>())); YamlEditorButton.Bind(((BaseUnityPlugin)this).Config, "Advanced Configuration", "Teleport Rules"); _enableDiagnostics = ((BaseUnityPlugin)this).Config.Bind<bool>("Diagnostics", "Enable Creature Teleport Diagnostics", false, new ConfigDescription("Writes capture, arrival, and per-animal placement details to this machine's log.", (AcceptableValueBase)null, new object[1] { new Odinplus.Shared.YamlEditor.ConfigurationManagerAttributes { IsAdvanced = true } })); _logSkippedAnimals = ((BaseUnityPlugin)this).Config.Bind<bool>("Diagnostics", "Log Skipped Animals", false, new ConfigDescription("When diagnostics are enabled, writes an individual reason for each animal not transported.", (AcceptableValueBase)null, new object[1] { new Odinplus.Shared.YamlEditor.ConfigurationManagerAttributes { IsAdvanced = true } })); _profileCreatureTransport = ((BaseUnityPlugin)this).Config.Bind<bool>("Diagnostics", "Profile Creature Transport", false, new ConfigDescription("When diagnostics are enabled, writes one timing and work-count summary for each creature transport.", (AcceptableValueBase)null, new object[1] { new Odinplus.Shared.YamlEditor.ConfigurationManagerAttributes { IsAdvanced = true } })); } finally { ((BaseUnityPlugin)this).Config.SaveOnConfigSet = saveOnConfigSet; ((BaseUnityPlugin)this).Config.Save(); } } private ConfigEntry<T> BindSynced<T>(string group, string key, T defaultValue, string description) { ConfigEntry<T> val = ((BaseUnityPlugin)this).Config.Bind<T>(group, key, defaultValue, Describe(description, "Server synchronized")); Sync.AddConfigEntry<T>(val).SynchronizedConfig = true; return val; } private ConfigEntry<T> BindLocal<T>(string group, string key, T defaultValue, string description) { return ((BaseUnityPlugin)this).Config.Bind<T>(group, key, defaultValue, Describe(description, "Local only")); } private static ConfigDescription Describe(string description, string scope) { //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Expected O, but got Unknown return new ConfigDescription(description + " [" + scope + "]", (AcceptableValueBase)null, Array.Empty<object>()); } private void SetupServerYamlWatcher() { _serverYamlWatcher = new FileSystemWatcher(Paths.ConfigPath, Path.GetFileName(_serverYamlPath)) { IncludeSubdirectories = false, NotifyFilter = (NotifyFilters.FileName | NotifyFilters.LastWrite | NotifyFilters.CreationTime), SynchronizingObject = ThreadingHelper.SynchronizingObject, EnableRaisingEvents = true }; _serverYamlWatcher.Changed += delegate { ReloadServerYaml(); }; _serverYamlWatcher.Created += delegate { ReloadServerYaml(); }; _serverYamlWatcher.Renamed += delegate { ReloadServerYaml(); }; } private void SetupPlayerYamlWatcher() { _playerYamlWatcher = new FileSystemWatcher(Paths.ConfigPath, Path.GetFileName(_playerYamlPath)) { IncludeSubdirectories = false, NotifyFilter = (NotifyFilters.FileName | NotifyFilters.LastWrite | NotifyFilters.CreationTime), SynchronizingObject = ThreadingHelper.SynchronizingObject, EnableRaisingEvents = true }; _playerYamlWatcher.Changed += delegate { ReloadPlayerYaml(); }; _playerYamlWatcher.Created += delegate { ReloadPlayerYaml(); }; _playerYamlWatcher.Renamed += delegate { ReloadPlayerYaml(); }; } private void SetupConfigWatcher() { _configWatcher = new FileSystemWatcher(Paths.ConfigPath, "odinplus.TeleportCreatures.cfg") { IncludeSubdirectories = false, NotifyFilter = (NotifyFilters.FileName | NotifyFilters.LastWrite | NotifyFilters.CreationTime), SynchronizingObject = ThreadingHelper.SynchronizingObject, EnableRaisingEvents = true }; _configWatcher.Changed += ReloadBepInExConfig; _configWatcher.Created += ReloadBepInExConfig; _configWatcher.Renamed += ReloadBepInExConfig; } private void RegisterYamlEditor() { //IL_0119: Unknown result type (might be due to invalid IL or missing references) //IL_0123: Expected O, but got Unknown YamlEditorRegistry.Initialize("odinplus.TeleportCreatures", "TeleportCreatures"); YamlEditorRegistry.Register(new YamlDocument("server", "Server policy (" + Path.GetFileName(_serverYamlPath) + ")", _serverYamlPath, YamlValidation.FromExceptional(delegate(string text) { CreatureConfigValidator.Validate(new DeserializerBuilder().Build().Deserialize<CreatureConfig>(text) ?? throw new InvalidDataException("The document is empty.")); }), () => Sync.IsSourceOfTruth, () => "only the server host can edit the ser