Decompiled source of TeleportCreatures v0.3.3

TeleportCreatures.dll

Decompiled a month ago
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.ConfigurationUi;
using Odinplus.Shared.Input;
using Odinplus.Shared.Timing;
using Odinplus.Shared.YamlEditor;
using ServerSync;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
using YamlDotNet.Core;
using YamlDotNet.Core.Events;
using YamlDotNet.Core.ObjectPool;
using YamlDotNet.Core.Tokens;
using YamlDotNet.Helpers;
using YamlDotNet.RepresentationModel;
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.3.3")]
[assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("0.3.3.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 TeleportCreatures
{
	internal static class AllyModeCycle
	{
		internal static readonly AllySelectionMode[] Order = new AllySelectionMode[5]
		{
			AllySelectionMode.None,
			AllySelectionMode.AllTamed,
			AllySelectionMode.OnlyFollow,
			AllySelectionMode.OnlyNamed,
			AllySelectionMode.AllTamedExceptNamed
		};

		public static AllySelectionMode Next(AllySelectionMode mode)
		{
			return Cycle(mode, 1);
		}

		public static AllySelectionMode Previous(AllySelectionMode mode)
		{
			return Cycle(mode, -1);
		}

		public static string LocalizationKey(AllySelectionMode mode)
		{
			return mode switch
			{
				AllySelectionMode.None => "$tc_ally_mode_none", 
				AllySelectionMode.AllTamed => "$tc_ally_mode_all_tamed", 
				AllySelectionMode.OnlyFollow => "$tc_ally_mode_only_follow", 
				AllySelectionMode.OnlyNamed => "$tc_ally_mode_only_named", 
				AllySelectionMode.AllTamedExceptNamed => "$tc_ally_mode_all_tamed_except_named", 
				_ => "$tc_ally_mode_none", 
			};
		}

		private static AllySelectionMode Cycle(AllySelectionMode mode, int direction)
		{
			int num = Array.IndexOf(Order, mode);
			if (num < 0)
			{
				num = 0;
			}
			return Order[(num + direction + Order.Length) % Order.Length];
		}
	}
	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 bool ShouldShowEnemyPreview(bool portalReady, int enemyCount)
		{
			if (portalReady)
			{
				return enemyCount > 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, 
			};
		}
	}
	[BepInPlugin("odinplus.TeleportCreatures", "TeleportCreatures", "0.3.3")]
	[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
		}

		private static readonly object UiGate = new object();

		private static bool _uiCommitting;

		private static bool _uiFaulted;

		private NativeConfigTransport? _uiTransport;

		private ModuleConfigurationService? _uiService;

		private string _uiObservedServer = "";

		private string _uiObservedPlayer = "";

		private string _uiObservedCfg = "";

		internal const string Author = "odinplus";

		internal const string ModGuid = "odinplus.TeleportCreatures";

		internal const string ModName = "TeleportCreatures";

		internal const string ModVersion = "0.3.3";

		private const string ConfigFileName = "odinplus.TeleportCreatures.cfg";

		private static readonly ConfigSync Sync = new ConfigSync("odinplus.TeleportCreatures")
		{
			DisplayName = "TeleportCreatures",
			CurrentVersion = "0.3.3",
			MinimumRequiredVersion = "0.3.3"
		};

		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 static ConfigEntry<KeyboardShortcut>? _previousAlliesModeHotkey;

		private static ConfigEntry<KeyboardShortcut>? _nextAlliesModeHotkey;

		private const float AllyModePersistDelaySeconds = 2f;

		private static AllySelectionMode? _pendingAllyMode;

		private static float _allyModePersistAt;

		private static ConfigEntry<string>? _enemyTransportMessageMode;

		private readonly Harmony _harmony = new Harmony("odinplus.TeleportCreatures");

		private static readonly <cba6ec40-2546-4a46-9577-18d27987fd9e>ISerializer Serializer = new <cba6ec40-2546-4a46-9577-18d27987fd9e>SerializerBuilder().Build();

		private FileSystemWatcher? _serverYamlWatcher;

		private FileSystemWatcher? _playerYamlWatcher;

		private FileSystemWatcher? _configWatcher;

		private string _serverYamlPath;

		private bool _serverYamlMigratedFromV1;

		private static string _playerYamlPath = null;

		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 KeyboardShortcut PreviousAlliesModeHotkey => (KeyboardShortcut)(((??)_previousAlliesModeHotkey?.Value) ?? new KeyboardShortcut((KeyCode)276, (KeyCode[])(object)new KeyCode[1] { (KeyCode)304 }));

		internal static KeyboardShortcut NextAlliesModeHotkey => (KeyboardShortcut)(((??)_nextAlliesModeHotkey?.Value) ?? new KeyboardShortcut((KeyCode)275, (KeyCode[])(object)new KeyCode[1] { (KeyCode)304 }));

		internal static bool IsPreviousAlliesModeHotkeyConfigured
		{
			get
			{
				//IL_000b: Unknown result type (might be due to invalid IL or missing references)
				//IL_0010: Unknown result type (might be due to invalid IL or missing references)
				//IL_0013: Unknown result type (might be due to invalid IL or missing references)
				//IL_0019: Invalid comparison between Unknown and I4
				ConfigEntry<KeyboardShortcut>? previousAlliesModeHotkey = _previousAlliesModeHotkey;
				if (previousAlliesModeHotkey == null)
				{
					return true;
				}
				KeyboardShortcut value = previousAlliesModeHotkey.Value;
				return (int)((KeyboardShortcut)(ref value)).MainKey > 0;
			}
		}

		internal static bool IsNextAlliesModeHotkeyConfigured
		{
			get
			{
				//IL_000b: Unknown result type (might be due to invalid IL or missing references)
				//IL_0010: Unknown result type (might be due to invalid IL or missing references)
				//IL_0013: Unknown result type (might be due to invalid IL or missing references)
				//IL_0019: Invalid comparison between Unknown and I4
				ConfigEntry<KeyboardShortcut>? nextAlliesModeHotkey = _nextAlliesModeHotkey;
				if (nextAlliesModeHotkey == null)
				{
					return true;
				}
				KeyboardShortcut value = nextAlliesModeHotkey.Value;
				return (int)((KeyboardShortcut)(ref value)).MainKey > 0;
			}
		}

		internal static AllyTransportMessageMode EnemyTransportMessageMode => AllyTransportDisplayPolicy.ParseMessageMode(_enemyTransportMessageMode?.Value);

		internal static bool BasementsInstalled { get; private set; }

		internal static bool IsBlocked { get; private set; }

		public string HandleUiConfigRequestV1(string request)
		{
			EnsureUi();
			return _uiTransport.Exchange(request);
		}

		private void EnsureUi()
		{
			if (_uiService != null)
			{
				return;
			}
			try
			{
				ObserveUiFiles(reload: false, "Server");
				ObserveUiFiles(reload: false, "Player");
			}
			catch
			{
			}
			_uiService = new ModuleConfigurationService("odinplus.TeleportCreatures", ReadUi, UiDefaults, ValidateUi, CommitUi, ReadUiCandidate);
			_uiTransport = new NativeConfigTransport("odinplus.TeleportCreatures", delegate(UiConfigRequest r, string caller, bool admin)
			{
				lock (UiGate)
				{
					return _uiService.Handle(r, caller, admin, DateTime.UtcNow);
				}
			}, delegate
			{
				lock (UiGate)
				{
					_uiService.NewSession();
				}
			});
		}

		private void Update()
		{
			EnsureUi();
			_uiTransport.Tick();
		}

		private Dictionary<string, ConfigEntryBase> UiEntries(string scope)
		{
			Dictionary<string, ConfigEntryBase> entries = new Dictionary<string, ConfigEntryBase>();
			if (scope == "Server")
			{
				Add((ConfigEntryBase?)(object)_forceServerConfig);
				Add((ConfigEntryBase?)(object)_enableMod);
			}
			else
			{
				Add((ConfigEntryBase?)(object)_enableDiagnostics);
				Add((ConfigEntryBase?)(object)_logSkippedAnimals);
				Add((ConfigEntryBase?)(object)_profileCreatureTransport);
				Add((ConfigEntryBase?)(object)_allyTransportMessageMode);
				Add((ConfigEntryBase?)(object)_enemyTransportMessageMode);
				Add((ConfigEntryBase?)(object)_previousAlliesModeHotkey);
				Add((ConfigEntryBase?)(object)_nextAlliesModeHotkey);
			}
			return entries;
			void Add(ConfigEntryBase? entry)
			{
				if (entry != null)
				{
					entries[entry.Definition.Section + "/" + entry.Definition.Key] = entry;
				}
			}
		}

		private object UiPolicy(string scope)
		{
			if (!(scope == "Server"))
			{
				return PlayerPreferences;
			}
			return ServerPolicy;
		}

		private object UiDefaultPolicy(string scope)
		{
			if (!(scope == "Server"))
			{
				return PlayerCreaturePreferences.CreateDefault();
			}
			return new CreatureConfig();
		}

		private ModuleConfigurationCandidate UiDefaults(string scope)
		{
			return new ModuleConfigurationCandidate
			{
				Yaml = ModuleConfigProtocolV1.SerializePayload(UiDefaultPolicy(scope)),
				Cfg = UiEntries(scope).ToDictionary<KeyValuePair<string, ConfigEntryBase>, string, string>((KeyValuePair<string, ConfigEntryBase> x) => x.Key, (KeyValuePair<string, ConfigEntryBase> x) => TomlTypeConverter.ConvertToString(x.Value.DefaultValue, x.Value.SettingType))
			};
		}

		private void ObserveUiFiles(bool reload, string scope)
		{
			string path = ((scope == "Server") ? _serverYamlPath : _playerYamlPath);
			string text = (File.Exists(path) ? File.ReadAllText(path) : string.Empty);
			string text2 = (File.Exists(((BaseUnityPlugin)this).Config.ConfigFilePath) ? File.ReadAllText(((BaseUnityPlugin)this).Config.ConfigFilePath) : string.Empty);
			string text3 = ((scope == "Server") ? _uiObservedServer : _uiObservedPlayer);
			if (scope == "Server")
			{
				_uiObservedServer = text;
			}
			else
			{
				_uiObservedPlayer = text;
			}
			if (reload && text != text3)
			{
				if (scope == "Server")
				{
					ReloadServerYaml();
				}
				else
				{
					ReloadPlayerYaml();
				}
			}
			if (reload && text2 != _uiObservedCfg)
			{
				ReloadBepInExConfig(this, new FileSystemEventArgs(WatcherChangeTypes.Changed, Path.GetDirectoryName(((BaseUnityPlugin)this).Config.ConfigFilePath), Path.GetFileName(((BaseUnityPlugin)this).Config.ConfigFilePath)));
			}
			_uiObservedCfg = text2;
		}

		private ModuleConfigurationCandidate ReadUiCandidate(string scope)
		{
			ObserveUiFiles(reload: true, scope);
			return new ModuleConfigurationCandidate
			{
				Yaml = ModuleConfigProtocolV1.SerializePayload(UiPolicy(scope)),
				Cfg = UiEntries(scope).ToDictionary<KeyValuePair<string, ConfigEntryBase>, string, string>((KeyValuePair<string, ConfigEntryBase> x) => x.Key, (KeyValuePair<string, ConfigEntryBase> x) => x.Value.GetSerializedValue())
			};
		}

		private ModuleConfigurationView ReadUi(string scope)
		{
			object obj = UiPolicy(scope);
			ModuleConfigurationView moduleConfigurationView = new ModuleConfigurationView
			{
				Version = "0.3.3",
				Candidate = new ModuleConfigurationCandidate
				{
					Yaml = ModuleConfigProtocolV1.SerializePayload(obj),
					Cfg = UiEntries(scope).ToDictionary<KeyValuePair<string, ConfigEntryBase>, string, string>((KeyValuePair<string, ConfigEntryBase> x) => x.Key, (KeyValuePair<string, ConfigEntryBase> x) => x.Value.GetSerializedValue())
				},
				Descriptors = ConfigurationFields.Describe(obj, UiDefaultPolicy(scope), scope)
			};
			if (scope == "Server" && _serverYamlMigratedFromV1)
			{
				moduleConfigurationView.Warnings.Add("The server's SchemaVersion 1 settings were automatically migrated to SchemaVersion 2. Review the server log and confirm the migrated settings.");
			}
			foreach (KeyValuePair<string, ConfigEntryBase> item in UiEntries(scope))
			{
				ConfigEntryBase value = item.Value;
				ConfigurationDescriptor configurationDescriptor = new ConfigurationDescriptor
				{
					StableId = "cfg:" + item.Key,
					Scope = scope,
					Group = value.Definition.Section,
					Label = value.Definition.Key,
					Help = value.Description.Description,
					ValueYaml = value.GetSerializedValue(),
					DefaultYaml = TomlTypeConverter.ConvertToString(value.DefaultValue, value.SettingType),
					Kind = ((value.SettingType == typeof(bool)) ? "Toggle" : ((value.SettingType == typeof(KeyboardShortcut)) ? "KeyboardShortcut" : (value.SettingType.IsEnum ? "Enum" : "Text")))
				};
				if (value.SettingType.IsEnum)
				{
					configurationDescriptor.AllowedValues = Enum.GetNames(value.SettingType).ToList();
				}
				if (value.Description.AcceptableValues is AcceptableValueList<string> val)
				{
					configurationDescriptor.Kind = "Enum";
					configurationDescriptor.AllowedValues = val.AcceptableValues.ToList();
				}
				moduleConfigurationView.Descriptors.Add(configurationDescriptor);
			}
			if ((Object)(object)ZNetScene.instance != (Object)null)
			{
				moduleConfigurationView.Prefabs = (from x in (from p in ZNetScene.instance.m_prefabs
						where (Object)(object)p != (Object)null && (Object)(object)p.GetComponent<Character>() != (Object)null
						select ((Object)p).name).Distinct()
					orderby x
					select x).Take(2048).ToList();
			}
			string[] array = new string[2] { "com.kpro.TeleportEverything", "zenox.teleporteverything" };
			foreach (string key in array)
			{
				if (Chainloader.PluginInfos.TryGetValue(key, out var value2))
				{
					moduleConfigurationView.CompatibilityWarnings.Add(new CompatibilityWarning
					{
						ModName = value2.Metadata.Name,
						DetectedVersion = value2.Metadata.Version.ToString(),
						Reason = "Overlapping transport rules.",
						SuggestedAction = "Remove it or disable overlapping features, then restart."
					});
				}
			}
			if (!BasementsInstalled)
			{
				moduleConfigurationView.Warnings.Add("Basements is not installed. Basement preferences are retained for future use.");
			}
			if (scope == "Player")
			{
				moduleConfigurationView.Warnings.Add("TE_ENEMY_SERVER_SETTINGS|Enabled=" + ServerPolicy.Enemies.Enabled + "|Mode=" + ServerPolicy.Enemies.Mode.ToString() + "|Portals=" + ServerPolicy.Enemies.Teleport.Portals.Enabled + "|Dungeons=" + ServerPolicy.Enemies.Teleport.Dungeons.Enabled + "|Basements=" + ServerPolicy.Enemies.Teleport.Basements.Enabled);
				foreach (ConfigurationDescriptor item2 in moduleConfigurationView.Descriptors.Where((ConfigurationDescriptor r) => !r.StableId.StartsWith("cfg:")))
				{
					if (item2.StableId == "Allies.SearchRadius")
					{
						item2.EffectiveYaml = ModuleConfigProtocolV1.SerializePayload(Math.Min(PlayerPreferences.Allies.SearchRadius, ServerPolicy.Allies.MaximumSearchRadius));
						item2.Minimum = Math.Max(float.Epsilon, Math.Min(0.01f, ServerPolicy.Allies.MaximumSearchRadius / 1000f));
						item2.Maximum = ServerPolicy.Allies.MaximumSearchRadius;
					}
					if (item2.StableId == "Allies.SearchHeightRadius")
					{
						item2.EffectiveYaml = ModuleConfigProtocolV1.SerializePayload(Math.Min(PlayerPreferences.Allies.SearchHeightRadius, ServerPolicy.Allies.MaximumSearchHeightRadius));
						item2.Minimum = Math.Max(float.Epsilon, Math.Min(0.01f, ServerPolicy.Allies.MaximumSearchHeightRadius / 1000f));
						item2.Maximum = ServerPolicy.Allies.MaximumSearchHeightRadius;
					}
				}
				moduleConfigurationView.Warnings.Add("Player filters only restrict server policy. Placement and enemy transport remain server-owned; Deny wins.");
			}
			foreach (ConfigurationDescriptor item3 in moduleConfigurationView.Descriptors.Where((ConfigurationDescriptor r) => r.Kind == "Number"))
			{
				item3.Minimum = 0.0;
				string text = (item3.StableId.EndsWith("MaximumHeightDifference") ? "Enter zero or greater." : "Enter a number greater than zero. There is no fixed upper limit.");
				item3.Help = (string.IsNullOrWhiteSpace(item3.Help) ? text : (item3.Help + " " + text));
			}
			foreach (ConfigurationDescriptor item4 in moduleConfigurationView.Descriptors.Where((ConfigurationDescriptor r) => r.Kind == "PortalRules"))
			{
				item4.Help += " Add at least one portal tag or type. Set priority from 1 to 100, or leave it blank (100).";
			}
			if (scope == "Player")
			{
				PlayerDescriptorAvailability.Apply(moduleConfigurationView, IsEnabled, ServerPolicy);
			}
			return moduleConfigurationView;
		}

		private object ParseUi(string scope, string yaml)
		{
			if (scope == "Server")
			{
				CreatureConfig creatureConfig = ModuleConfigProtocolV1.DeserializePayload<CreatureConfig>(yaml);
				CreatureConfigValidator.Validate(creatureConfig);
				if (ConfigurationFields.HasNonFinite(creatureConfig))
				{
					throw new ArgumentException("Numbers must be finite.");
				}
				return creatureConfig;
			}
			PlayerCreaturePreferences playerCreaturePreferences = ModuleConfigProtocolV1.DeserializePayload<PlayerCreaturePreferences>(yaml);
			PlayerCreaturePreferencesValidator.Validate(playerCreaturePreferences);
			if (ConfigurationFields.HasNonFinite(playerCreaturePreferences))
			{
				throw new ArgumentException("Numbers must be finite.");
			}
			return playerCreaturePreferences;
		}

		private string? ValidateUi(string scope, ModuleConfigurationCandidate candidate)
		{
			try
			{
				if (_uiFaulted)
				{
					return "Saving is blocked after a rollback failure. Restart after checking the module log and configuration files.";
				}
				ParseUi(scope, candidate.Yaml);
				Dictionary<string, ConfigEntryBase> entries = UiEntries(scope);
				if (candidate.Cfg == null || candidate.Cfg.Count != entries.Count || candidate.Cfg.Keys.Any((string k) => !entries.ContainsKey(k)))
				{
					return "The complete configuration must include only this scope's supported settings.";
				}
				foreach (KeyValuePair<string, string> item in candidate.Cfg)
				{
					ConfigEntryBase val = entries[item.Key];
					object obj = TomlTypeConverter.ConvertToValue(item.Value, val.SettingType);
					if (val.Description.AcceptableValues != null && !val.Description.AcceptableValues.IsValid(obj))
					{
						return "Invalid value for " + val.Definition.Key + ".";
					}
					if (val.SettingType.IsEnum && !Enum.IsDefined(val.SettingType, obj))
					{
						return "Unsupported option.";
					}
				}
				return null;
			}
			catch (ArgumentException ex)
			{
				return ex.Message;
			}
			catch (InvalidDataException ex2)
			{
				return ex2.Message;
			}
			catch
			{
				return "The draft contains malformed or unsupported configuration values.";
			}
		}

		private void CommitUi(string scope, ModuleConfigurationCandidate candidate)
		{
			object parsed = ParseUi(scope, candidate.Yaml);
			CreatureConfig oldServer = ServerPolicy;
			PlayerCreaturePreferences oldPlayer = PlayerPreferences;
			string oldSnapshot = Snapshot.Value;
			Dictionary<string, ConfigEntryBase> entries = UiEntries(scope);
			Dictionary<string, string> oldCfg = entries.ToDictionary<KeyValuePair<string, ConfigEntryBase>, string, string>((KeyValuePair<string, ConfigEntryBase> x) => x.Key, (KeyValuePair<string, ConfigEntryBase> x) => x.Value.GetSerializedValue());
			bool saveOnConfigSet = ((BaseUnityPlugin)this).Config.SaveOnConfigSet;
			_uiCommitting = true;
			((BaseUnityPlugin)this).Config.SaveOnConfigSet = false;
			try
			{
				AtomicModuleCommit.Execute((scope == "Server") ? _serverYamlPath : _playerYamlPath, ModuleConfigProtocolV1.SerializePayload(parsed), ((BaseUnityPlugin)this).Config.ConfigFilePath, delegate
				{
					foreach (KeyValuePair<string, string> item in candidate.Cfg)
					{
						entries[item.Key].SetSerializedValue(item.Value);
					}
					if (scope == "Server")
					{
						ServerPolicy = (CreatureConfig)parsed;
					}
					else
					{
						PlayerPreferences = (PlayerCreaturePreferences)parsed;
					}
					RebuildEffectivePolicy();
				}, delegate
				{
					((BaseUnityPlugin)this).Config.Save();
				}, delegate
				{
					if (scope == "Server")
					{
						Snapshot.AssignLocalValue(ModuleConfigProtocolV1.SerializePayload(ServerPolicy));
					}
				}, delegate
				{
					AtomicModuleCommit.RestoreAll(((IEnumerable<KeyValuePair<string, string>>)oldCfg).Select((Func<KeyValuePair<string, string>, Action>)((KeyValuePair<string, string> pair) => delegate
					{
						entries[pair.Key].SetSerializedValue(pair.Value);
					})).Concat(new Action[2]
					{
						delegate
						{
							ServerPolicy = oldServer;
							PlayerPreferences = oldPlayer;
							RebuildEffectivePolicy();
						},
						delegate
						{
							if (scope == "Server")
							{
								Snapshot.AssignLocalValue(oldSnapshot);
							}
						}
					}));
				}, delegate(Exception error)
				{
					_uiFaulted = true;
					Log.LogError((object)("UI configuration rollback failed: " + error.Message));
				}, (scope == "Server") ? _uiObservedServer : _uiObservedPlayer, _uiObservedCfg);
				if (scope == "Player")
				{
					_pendingAllyMode = null;
				}
				try
				{
					ObserveUiFiles(reload: false, scope);
				}
				catch
				{
				}
				Log.LogInfo((object)("UI configuration saved: scope=" + scope + "."));
			}
			finally
			{
				((BaseUnityPlugin)this).Config.SaveOnConfigSet = saveOnConfigSet;
				_uiCommitting = false;
			}
		}

		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();
			try
			{
				RegisterYamlEditor();
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)("YAML editor unavailable; creature transport will continue. " + ex));
			}
			Localizer.Load();
			_harmony.PatchAll(Assembly.GetExecutingAssembly());
		}

		private void Start()
		{
			EnsureUi();
			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()
		{
			_uiTransport?.Dispose();
			if (!IsBlocked)
			{
				_serverYamlWatcher?.Dispose();
				_playerYamlWatcher?.Dispose();
				_configWatcher?.Dispose();
				if ((Object)(object)_yamlEditorHost != (Object)null)
				{
					Object.Destroy((Object)(object)_yamlEditorHost);
				}
				Snapshot.ValueChanged -= ReadSnapshot;
				if (!_uiFaulted)
				{
					FlushPendingAllyMode();
				}
				AllyTransport.CancelPendingTransports();
				PortalAllyDisplay.Dispose();
				PortalEnemyDisplay.Dispose();
				AllyModeSwitchDisplay.Dispose();
				_harmony.UnpatchSelf();
				if (!_uiFaulted)
				{
					((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_0101: Unknown result type (might be due to invalid IL or missing references)
			//IL_010b: Expected O, but got Unknown
			//IL_012e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0160: Unknown result type (might be due to invalid IL or missing references)
			//IL_01bb: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c5: Expected O, but got Unknown
			//IL_01fb: Unknown result type (might be due to invalid IL or missing references)
			//IL_0205: Expected O, but got Unknown
			//IL_023b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0245: 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>()));
				_enemyTransportMessageMode = ((BaseUnityPlugin)this).Config.Bind<string>("Enemies Display", "Transport Message Mode", "Center", new ConfigDescription("Local-only layout for the live enemy preview and completion message. Top left places the preview below the allies counter; the completion result uses the Valheim queue.", (AcceptableValueBase)(object)new AcceptableValueList<string>(new string[4] { "No messages", "Top left", "Top center", "Center" }), Array.Empty<object>()));
				_previousAlliesModeHotkey = BindLocal<KeyboardShortcut>("Controls", "Previous Allies Mode Hotkey", new KeyboardShortcut((KeyCode)276, (KeyCode[])(object)new KeyCode[1] { (KeyCode)304 }), "Hotkey to cycle to the previous allies mode.");
				_nextAlliesModeHotkey = BindLocal<KeyboardShortcut>("Controls", "Next Allies Mode Hotkey", new KeyboardShortcut((KeyCode)275, (KeyCode[])(object)new KeyCode[1] { (KeyCode)304 }), "Hotkey to cycle to the next allies mode.");
				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_0117: Unknown result type (might be due to invalid IL or missing references)
			//IL_0121: Expected O, but got Unknown
			YamlEditorRegistry.Initialize("odinplus.TeleportCreatures", "TeleportCreatures");
			YamlEditorRegistry.Register(new Odinplus.Shared.YamlEditor.YamlDocument("server", "Server policy (" + Path.GetFileName(_serverYamlPath) + ")", _serverYamlPath, YamlValidation.FromExceptional(delegate(string text)
			{
				CreatureConfigValidator.Validate(CreatureConfigYaml.DeserializeAndMigrate(text));
			}), () => Sync.IsSourceOfTruth && !_uiFaulted, () => "only the server host can edit the server policy"));
			YamlEditorRegistry.Register(new Odinplus.Shared.YamlEditor.YamlDocument("player", "Player preferences (" + Path.GetFileName(_playerYamlPath) + ")", _playerYamlPath, YamlValidation.FromTryPattern((string text) => (!PlayerPreferenceSnapshot.TryReplace(PlayerPreferences, text, out PlayerCreaturePreferences _, out string error)) ? error : null), () => !_uiFaulted));
			_yamlEditorHost = new GameObject("TeleportCreaturesYamlEditor");
			Object.DontDestroyOnLoad((Object)(object)_yamlEditorHost);
			_yamlEditorHost.AddComponent<YamlEditorBehaviour>();
		}

		private void ReloadBepInExConfig(object sender, FileSystemEventArgs eventArgs)
		{
			lock (UiGate)
			{
				if (!_uiCommitting && !_uiFaulted)
				{
					ReloadBepInExConfigCore(sender, eventArgs);
				}
			}
		}

		private void ReloadBepInExConfigCore(object sender, FileSystemEventArgs eventArgs)
		{
			if (!File.Exists(((BaseUnityPlugin)this).Config.ConfigFilePath))
			{
				return;
			}
			try
			{
				((BaseUnityPlugin)this).Config.Reload();
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)this).Logger.LogError((object)("Could not reload odinplus.TeleportCreatures.cfg. Check its entries and format. " + ex.Message));
			}
		}

		private void ReloadServerYaml()
		{
			lock (UiGate)
			{
				if (!_uiCommitting && !_uiFaulted)
				{
					ReloadServerYamlCore();
				}
			}
		}

		private void ReloadServerYamlCore()
		{
			if (!Sync.IsSourceOfTruth)
			{
				return;
			}
			if (!File.Exists(_serverYamlPath))
			{
				File.WriteAllText(_serverYamlPath, DefaultServerYamlTemplate());
			}
			try
			{
				bool migrated;
				CreatureConfig creatureConfig = CreatureConfigYaml.DeserializeAndMigrate(File.ReadAllText(_serverYamlPath), out migrated);
				CreatureConfigValidator.Validate(creatureConfig);
				if (migrated)
				{
					File.WriteAllText(_serverYamlPath, Serializer.Serialize(creatureConfig));
					_serverYamlMigratedFromV1 = true;
					((BaseUnityPlugin)this).Logger.LogInfo((object)"Migrated TeleportCreatures server YAML from SchemaVersion 1 to 2; placement settings were moved under Allies and Enemies.");
				}
				ServerPolicy = creatureConfig;
				RebuildEffectivePolicy();
				Snapshot.AssignLocalValue(Serializer.Serialize(creatureConfig));
				LogDiagnostic($"Accepted server YAML: ally allow={creatureConfig.Allies.Allow.Count}, deny={creatureConfig.Allies.Deny.Count}, radius={creatureConfig.Allies.Placement.Radius:0.##}, water={creatureConfig.Allies.Placement.Water}.");
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)this).Logger.LogError((object)("TeleportCreatures retained the previous valid server YAML: " + ex.Message));
			}
		}

		private void ReloadPlayerYaml()
		{
			lock (UiGate)
			{
				if (!_uiCommitting && !_uiFaulted)
				{
					ReloadPlayerYamlCore();
				}
			}
		}

		private void ReloadPlayerYamlCore()
		{
			if (!File.Exists(_playerYamlPath))
			{
				File.WriteAllText(_playerYamlPath, DefaultPlayerYamlTemplate());
			}
			string yaml = File.ReadAllText(_playerYamlPath);
			if (PlayerPreferenceSnapshot.TryReplace(PlayerPreferences, yaml, out PlayerCreaturePreferences retained, out string error))
			{
				PlayerPreferences = retained;
				RebuildEffectivePolicy();
				LogDiagnostic($"Accepted local player YAML: ally allow={retained.Allies.Allow.Count}, deny={retained.Allies.Deny.Count}.");
			}
			else
			{
				((BaseUnityPlugin)this).Logger.LogError((object)("TeleportCreatures retained the previous valid local player YAML: " + error));
			}
		}

		private static void ReadSnapshot()
		{
			if (Sync.IsSourceOfTruth || string.IsNullOrWhiteSpace(Snapshot.Value))
			{
				return;
			}
			try
			{
				CreatureConfig creatureConfig = CreatureConfigYaml.DeserializeAndMigrate(Snapshot.Value);
				CreatureConfigValidator.Validate(creatureConfig);
				ServerPolicy = creatureConfig;
				RebuildEffectivePolicy();
				LogDiagnostic($"Accepted server YAML snapshot: ally allow={creatureConfig.Allies.Allow.Count}, deny={creatureConfig.Allies.Deny.Count}, radius={creatureConfig.Allies.Placement.Radius:0.##}, water={creatureConfig.Allies.Placement.Water}.");
			}
			catch (Exception ex)
			{
				Log.LogError((object)("Rejected TeleportCreatures server snapshot: " + ex.Message));
			}
		}

		private static void RebuildEffectivePolicy()
		{
			Policy = EffectiveCreaturePolicy.Create(ServerPolicy, PlayerPreferences);
		}

		internal static void CycleAllyMode(int direction)
		{
			if (!_uiCommitting && !_uiFaulted)
			{
				AllySelectionMode allySelectionMode = ((direction > 0) ? AllyModeCycle.Next(PlayerPreferences.Allies.Mode) : AllyModeCycle.Previous(PlayerPreferences.Allies.Mode));
				PlayerPreferences.Allies.Mode = allySelectionMode;
				RebuildEffectivePolicy();
				_pendingAllyMode = allySelectionMode;
				_allyModePersistAt = Time.time + 2f;
				LogDiagnostic(string.Format("Ally mode switched to {0} ({1}).", allySelectionMode, (direction > 0) ? "next" : "previous"));
				AllyModeSwitchDisplay.Show(allySelectionMode, direction);
			}
		}

		internal static void TickAllyModePersistence()
		{
			if (_pendingAllyMode.HasValue && Time.time >= _allyModePersistAt)
			{
				FlushPendingAllyMode();
			}
		}

		private static void FlushPendingAllyMode()
		{
			if (_uiCommitting || _uiFaulted)
			{
				return;
			}
			AllySelectionMode? pendingAllyMode = _pendingAllyMode;
			if (!pendingAllyMode.HasValue)
			{
				return;
			}
			_pendingAllyMode = null;
			try
			{
				string text = File.ReadAllText(_playerYamlPath);
				if (PlayerYamlModeWriter.TryReplaceModeLine(text, pendingAllyMode.Value.ToString(), out string updated))
				{
					File.WriteAllText(_playerYamlPath, updated);
				}
				else
				{
					PlayerCreaturePreferences playerCreaturePreferences = new <cba6ec40-2546-4a46-9577-18d27987fd9e>DeserializerBuilder().Build().Deserialize<PlayerCreaturePreferences>(text);
					playerCreaturePreferences.Allies.Mode = pendingAllyMode.Value;
					File.WriteAllText(_playerYamlPath, Serializer.Serialize(playerCreaturePreferences));
				}
				LogDiagnostic($"Persisted ally mode {pendingAllyMode.Value} to {Path.GetFileName(_playerYamlPath)}.");
			}
			catch (Exception ex)
			{
				Log.LogWarning((object)("Could not persist the ally mode to " + Path.GetFileName(_playerYamlPath) + "; the in-memory mode stays active for this session. " + ex.Message));
			}
		}

		private void FindIncompatibleMods()
		{
			string[] array = new string[2] { "com.kpro.TeleportEverything", "zenox.teleporteverything" };
			foreach (string text in array)
			{
				if (Chainloader.PluginInfos.TryGetValue(text, out var value))
				{
					((BaseUnityPlugin)this).Logger.LogWarning((object)($"Incompatible mod detected: {value.Metadata.Name} ({value.Metadata.Version}), GUID: {text}.\n" + "This mod can conflict with TeleportCreatures.\nTo avoid issues, please either:\n- Uninstall the detected mod, or\n- Disable the conflicting features in one of the mods."));
				}
			}
		}

		internal static void LogDiagnostic(string message)
		{
			if (DiagnosticsEnabled)
			{
				Log.LogInfo((object)("TeleportCreatures: " + message));
			}
		}

		internal static void LogSkipped(string message)
		{
			if (DiagnosticsEnabled && LogSkippedAnimals)
			{
				Log.LogInfo((object)("TeleportCreatures: skipped animal: " + message));
			}
		}

		private string DefaultServerYamlTemplate()
		{
			return "# Server policy. This file is synchronized to clients.\n# Filters accept all/*, none, exact prefab names, and * / ? globs. Deny always wins.\n# Portal rules use the lowest Priority number first (1 is strongest; omitted is 100).\n# Equal priorities keep YAML order. Tags are portal link text; Prefabs are portal prefab names.\n# A rule Allow narrows server Allow. It never expands the server's global creature ceiling.\nSchemaVersion: 2\n\nAllies:\n  # Disable all allied-creature transport for every player.\n  Enabled: true\n  # Hard limits for each player's local SearchRadius and SearchHeightRadius.\n  MaximumSearchRadius: 20\n  MaximumSearchHeightRadius: 8\n  # The server's global creature ceiling. These filters apply before portal rules.\n  Allow: [\"all\"]\n  Deny: []\n\n  Teleport:\n    Portals:\n      # Disable creature transport through every normal portal.\n      Enabled: true\n      Rules:\n        # These neutral defaults allow every server-allowed ally through Wood and Stone portals.\n        # Add a source-specific rule with Priority: 1..99 to override them.\n        - Id: \"wood-portals\"\n          Source:\n            Prefabs: [\"portal_wood\"]\n          Allow: [\"all\"]\n          Deny: []\n\n        - Id: \"stone-portals\"\n          Source:\n            Prefabs: [\"portal_stone\"]\n          Allow: [\"all\"]\n          Deny: []\n\n    Dungeons:\n      # Covers both dungeon entrances and exits.\n      Enabled: true\n    Basements:\n      # Requires the Basements mod on the server.\n      Enabled: true\n  Placement:\n    # Search radius for ally placement in normal portals.\n    Radius: 10\n    MaximumHeightDifference: 3\n    DistanceFromPlayer: 3\n    Spacing: 2.5\n    Water: Allow\n    Dungeons:\n      Radius: 12\n      RequireNaturalGroundOnExit: true\n    Basements:\n      Radius: 8\n      RequireClearance: true\n\nEnemies:\n  # Enemy transport is opt-in and controlled by this server policy.\n  Enabled: false\n  MaximumSearchRadius: 20\n  MaximumSearchHeightRadius: 8\n  Allow: [\"all\"]\n  Deny: []\n  Teleport:\n    Portals:\n      Enabled: true\n      Rules:\n        # Neutral defaults allow all server-allowed enemies through Wood and Stone portals.\n        - Id: \"wood-enemy-portals\"\n          Source:\n            Prefabs: [\"portal_wood\"]\n          Allow: [\"all\"]\n          Deny: []\n        - Id: \"stone-enemy-portals\"\n          Source:\n            Prefabs: [\"portal_stone\"]\n          Allow: [\"all\"]\n          Deny: []\n    Dungeons:\n      Enabled: true\n    Basements:\n      Enabled: false\n\n  Placement:\n    Radius: 12\n    MaximumHeightDifference: 3\n    DistanceFromPlayer: 6\n    Spacing: 2.5\n    Water: Allow\n    Dungeons:\n      Radius: 12\n      RequireNaturalGroundOnExit: true\n    Basements:\n      Radius: 8\n      RequireClearance: true\n";
		}

		private string DefaultPlayerYamlTemplate()
		{
			return "# Local player preference. This file is never synchronized and can only restrict the server policy.\n# SearchRadius values are clamped to the server maximums. Player portal rules cannot override server rules.\n# Filters accept all/*, none, exact prefab names, and * / ? globs. Deny always wins.\nSchemaVersion: 1\n\nAllies:\n  # Disable allied-creature transport only for this player.\n  Enabled: true\n  # None, AllTamed, OnlyFollow, OnlyNamed, or AllTamedExceptNamed.\n  Mode: AllTamed\n  # Personal search limits; the server clamps them to its configured maximums.\n  SearchRadius: 10\n  SearchHeightRadius: 3\n  # Personal restrictions, applied in addition to the server's filters.\n  Allow: [\"all\"]\n  Deny: []\n\n  Teleport:\n    Portals:\n      # Disable creature transport through normal portals only for this player.\n      Enabled: true\n      # Optional personal portal rules. They can narrow, never expand, server policy.\n      Rules: []\n    Dungeons:\n      # Disable dungeon entrance and exit transport only for this player.\n      Enabled: true\n    Basements:\n      # Disable Basements transport only for this player.\n      Enabled: true\n\n";
		}
	}
	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;
		}
	}
	internal readonly struct CreatureClearanceShape
	{
		private const float UniformScaleTolerance = 0.001f;

		public float Radius { get; }

		public float HalfSegment { get; }

		public float UniformScale { get; }

		public float Height => Radius * 2f + HalfSegment * 2f;

		public CreatureClearanceShape(float radius, float halfSegment, float uniformScale = 1f)
		{
			Radius = radius;
			HalfSegment = halfSegment;
			UniformScale = uniformScale;
		}

		public static bool TryCreate(float localRadius, float localHeight, float scaleX, float scaleY, float scaleZ, out CreatureClearanceShape shape)
		{
			shape = default(CreatureClearanceShape);
			if (!IsFinite(localRadius) || !IsFinite(localHeight) || !IsFinite(scaleX) || !IsFinite(scaleY) || !IsFinite(scaleZ) || localRadius <= 0f || localHeight <= 0f || scaleX <= 0f || scaleY <= 0f || scaleZ <= 0f)
			{
				return false;
			}
			float num = Math.Max(scaleX, Math.Max(scaleY, scaleZ));
			float num2 = Math.Min(scaleX, Math.Min(scaleY, scaleZ));
			if (num - num2 > num * 0.001f)
			{
				return false;
			}
			float num3 = localRadius * num;
			float num4 = Math.Max(0f, localHeight * 0.5f - localRadius) * num;
			if (!IsFinite(num3) || !IsFinite(num4))
			{
				return false;
			}
			shape = new CreatureClearanceShape(num3, num4, num);
			return true;
		}

		public float RootYForFloor(float floorHeight, float groundClearance, float endpointAOffsetY, float endpointBOffsetY)
		{
			return floorHeight + groundClearance - (Math.Min(endpointAOffsetY, endpointBOffsetY) - Radius);
		}

		private static bool IsFinite(float value)
		{
			if (!float.IsNaN(value))
			{
				return !float.IsInfinity(value);
			}
			return false;
		}
	}
	public sealed class CreatureConfig
	{
		public int SchemaVersion { get; set; } = 2;

		public CreatureFilterConfig Allies { get; set; } = new CreatureFilterConfig
		{
			Allow = new List<string> { "all" },
			Deny = new List<string>()
		};

		public EnemyFilterConfig Enemies { get; set; } = new EnemyFilterConfig();
	}
	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 CreaturePlacementConfig Placement { get; set; } = new CreaturePlacementConfig();
	}
	public sealed class EnemyFilterConfig
	{
		public bool Enabled { get; set; }

		public EnemyPortalMode Mode { get; set; } = EnemyPortalMode.TakeThemWithYou;

		public EnemyTeleportConfig Teleport { get; set; } = new EnemyTeleportConfig();

		public float MaximumSearchRadius { get; set; } = 20f;

		public float MaximumSearchHeightRadius { get; set; } = 8f;

		public List<string> Allow { get; set; } = new List<string> { "all" };

		public List<string> Deny { get; set; } = new List<string>();

		public CreaturePlacementConfig Placement { get; set; } = new CreaturePlacementConfig
		{
			Radius = 12f,
			DistanceFromPlayer = 6f
		};
	}
	public enum EnemyPortalMode
	{
		VikingsDontRun,
		TakeThemWithYou
	}
	public sealed class EnemyTeleportConfig
	{
		public PortalTransportConfig Portals { get; set; } = new PortalTransportConfig();

		public TransitionToggleConfig Dungeons { get; set; } = new TransitionToggleConfig();

		public TransitionToggleConfig Basements { get; set; } = new TransitionToggleConfig
		{
			Enabled = false
		};
	}
	public enum AllySelectionMode
	{
		AllTamed,
		OnlyFollow,
		OnlyNamed,
		AllTamedExceptNamed,
		None
	}
	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, 
				AllySelectionMode.None => false, 
				_ => 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 CreaturePlacementConfig
	{
		public float Radius { get; set; } = 10f;

		public float MaximumHeightDifference { get; set; } = 3f;

		public float DistanceFromPlayer { get; set; } = 3f;

		public float Spacing { 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();
	}
	internal sealed class LegacyPlacementConfigV1
	{
		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 sealed class EnemyPlacementConfig
	{
		public float MaximumRadius { get; set; } = 12f;

		public float DistanceFromPlayer { get; set; } = 6f;

		public float MaximumHeightDifference { get; set; } = 3f;

		public float Spacing { get; set; } = 2.5f;

		public WaterPolicy Water { get; set; } = WaterPolicy.Allow;
	}
	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 != 2)
			{
				throw new InvalidDataException("Only SchemaVersion 2 is supported. SchemaVersion 1 is migrated when the server YAML is loaded.");
			}
			if (config.Allies == null || config.Enemies == null || config.Enemies.Teleport == null || config.Allies.Teleport == null || config.Allies.Placement == null || config.Enemies.Placement == null || config.Allies.Teleport.Portals == null || config.Enemies.Teleport.Portals == null || config.Enemies.Teleport.Dungeons == null || config.Enemies.Teleport.Basements == null || config.Allies.Placement.Dungeons == null || config.Allies.Placement.Basements == null || config.Enemies.Placement.Dungeons == null || config.Enemies.Placement.Basements == null || config.Allies.Teleport.Dungeons == null || config.Allies.Teleport.Basements == null)
			{
				throw new InvalidDataException("Allies, Enemies, their Placement sections, and their required Teleport sections cannot be null.");
			}
			if (config.Allies.Placement.Dungeons.Radius <= 0f || config.Allies.Placement.Basements.Radius <= 0f || config.Enemies.Placement.Dungeons.Radius <= 0f || config.Enemies.Placement.Basements.Radius <= 0f)
			{
				throw new InvalidDataException("Allies.Placement and Enemies.Placement dungeon and basement radii must be positive.");
			}
			if (config.Allies.MaximumSearchRadius <= 0f || config.Allies.MaximumSearchHeightRadius <= 0f)
			{
				throw new InvalidDataException("Allies.MaximumSearchRadius and Allies.MaximumSearchHeightRadius must be positive.");
			}
			if (!IsPlacementValid(config.Allies.Placement) || !IsPlacementValid(config.Enemies.Placement))
			{
				throw new InvalidDataException("Allies.Placement and Enemies.Placement require positive radius, distance, and spacing, a non-negative height difference, and Water set to Avoid, Allow, or Require.");
			}
			CreatureMatcher.ValidateList(config.Allies.Allow, "Allies.Allow");
			CreatureMatcher.ValidateList(config.Allies.Deny, "Allies.Deny");
			ValidatePortalRules(config.Allies.Teleport.Portals.Rules);
			ValidatePortalRules(config.Enemies.Teleport.Portals.Rules, "Enemies.Teleport.Portals.Rules");
			CreatureMatcher.ValidateList(config.Enemies.Allow, "Enemies.Allow");
			CreatureMatcher.ValidateList(config.Enemies.Deny, "Enemies.Deny");
			if (!Enum.IsDefined(typeof(EnemyPortalMode), config.Enemies.Mode))
			{
				throw new InvalidDataException("Enemies.Mode must be VikingsDontRun or TakeThemWithYou.");
			}
			if (config.Enemies.MaximumSearchRadius <= 0f || config.Enemies.MaximumSearchHeightRadius <= 0f || config.Enemies.Placement.DistanceFromPlayer > config.Enemies.Placement.Radius)
			{
				throw new InvalidDataException("Enemy search limits must be positive and Enemies.Placement.DistanceFromPlayer cannot exceed its Radius.");
			}
		}

		private static bool IsPlacementValid(CreaturePlacementConfig placement)
		{
			if (placement.Radius > 0f && placement.MaximumHeightDifference >= 0f && placement.DistanceFromPlayer > 0f && placement.Spacing > 0f)
			{
				return Enum.IsDefined(typeof(WaterPolicy), placement.Water);
			}
			return false;
		}

		internal static void ValidatePortalRules(IReadOnlyList<PortalRuleConfig> rules, string path = "Allies.Teleport.Portals.Rules")
		{
			if (rules == null)
			{
				throw new InvalidDataException(path + " cannot be null.");
			}
			HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
			for (int i = 0; i < rules.Count; i++)
			{
				string text = $"{path}[{i}]";
				PortalRuleConfig portalRuleConfig = rules[i] ?? throw new InvalidDataException(text + " cannot be null.");
				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 CreatureConfigYaml
	{
		private sealed class CreatureConfigSchemaProbe
		{
			public int SchemaVersion { get; set; }
		}

		private sealed class LegacyEnemyFilterConfigV1
		{
			public bool Enabled { get; set; }

			public LegacyEnemyTeleportConfigV1? Teleport { get; set; }

			public float MaximumSearchRadius { get; set; } = 20f;

			public float MaximumSearchHeightRadius { get; set; } = 8f;

			public List<string>? Allow { get; set; } = new List<string> { "all" };

			public List<string>? Deny { get; set; } = new List<string>();

			public CreaturePlacementConfig? Placement { get; set; }
		}

		private sealed class LegacyEnemyTeleportConfigV1
		{
			public bool PortalsEnabled { get; set; } = true;

			public TransitionToggleConfig? Dungeons { get; set; } = new TransitionToggleConfig();

			public TransitionToggleConfig? Basements { get; set; } = new TransitionToggleConfig
			{
				Enabled = false
			};
		}

		private sealed class LegacyCreatureConfigV1
		{
			public int SchemaVersion { get; set; }

			public CreatureFilterConfig? Allies { get; set; }

			public LegacyEnemyFilterConfigV1? Enemies { get; set; }

			public LegacyPlacementConfigV1? Placement { get; set; }

			public EnemyPlacementConfig? EnemyPlacement { get; set; }
		}

		public static CreatureConfig DeserializeAndMigrate(string yaml)
		{
			bool migrated;
			return DeserializeAndMigrate(yaml, out migrated);
		}

		public static CreatureConfig DeserializeAndMigrate(string yaml, out bool migrated)
		{
			<cba6ec40-2546-4a46-9577-18d27987fd9e>IDeserializer <cba6ec40-2546-4a46-9577-18d27987fd9e>IDeserializer = new <cba6ec40-2546-4a46-9577-18d27987fd9e>DeserializerBuilder().Build();
			CreatureConfigSchemaProbe creatureConfigSchemaProbe = new <cba6ec40-2546-4a46-9577-18d27987fd9e>DeserializerBuilder().IgnoreUnmatchedProperties().Build().Deserialize<CreatureConfigSchemaProbe>(yaml) ?? throw new InvalidDataException("Creature configuration document is empty.");
			if (creatureConfigSchemaProbe.SchemaVersion == 1)
			{
				LegacyCreatureConfigV1 legacyCreatureConfigV = <cba6ec40-2546-4a46-9577-18d27987fd9e>IDeserializer.Deserialize<LegacyCreatureConfigV1>(yaml) ?? throw new InvalidDataException("Creature configuration document is empty.");
				CreatureConfig creatureConfig = new CreatureConfig
				{
					SchemaVersion = 2,
					Allies = (legacyCreatureConfigV.Allies ?? new CreatureFilterConfig()),
					Enemies = MigrateEnemyConfig(legacyCreatureConfigV.Enemies)
				};
				if (legacyCreatureConfigV.Placement != null)
				{
					creatureConfig.Allies.Placement = MigratePlacement(legacyCreatureConfigV.Placement);
				}
				if (legacyCreatureConfigV.EnemyPlacement != null)
				{
					creatureConfig.Enemies.Placement = MigrateEnemyPlacement(legacyCreatureConfigV.EnemyPlacement, legacyCreatureConfigV.Placement);
				}
				migrated = true;
				return creatureConfig;
			}
			if (creatureConfigSchemaProbe.SchemaVersion != 2)
			{
				throw new InvalidDataException($"Unsupported SchemaVersion {creatureConfigSchemaProbe.SchemaVersion}; expected 1 or 2.");
			}
			migrated = false;
			return <cba6ec40-2546-4a46-9577-18d27987fd9e>IDeserializer.Deserialize<CreatureConfig>(yaml) ?? throw new InvalidDataException("Creature configuration document is empty.");
		}

		private static CreaturePlacementConfig MigratePlacement(LegacyPlacementConfigV1 legacy)
		{
			return new CreaturePlacementConfig
			{
				Radius = legacy.Radius,
				MaximumHeightDifference = legacy.MaximumHeightDifference,
				DistanceFromPlayer = legacy.DistanceFromPlayer,
				Spacing = legacy.AnimalSpacing,
				Water = legacy.Water,
				Dungeons = legacy.Dungeons,
				Basements = legacy.Basements
			};
		}

		private static CreaturePlacementConfig MigrateEnemyPlacement(EnemyPlacementConfig legacy, LegacyPlacementConfigV1? previousPlacement)
		{
			return new CreaturePlacementConfig
			{
				Radius = legacy.MaximumRadius,
				MaximumHeightDifference = legacy.MaximumHeightDifference,
				DistanceFromPlayer = legacy.DistanceFromPlayer,
				Spacing = legacy.Spacing,
				Water = legacy.Water,
				Dungeons = (previousPlacement?.Dungeons ?? new DungeonPlacementConfig()),
				Basements = (previousPlacement?.Basements ?? new BasementPlacementConfig())
			};
		}

		private static EnemyFilterConfig MigrateEnemyConfig(LegacyEnemyFilterConfigV1? legacy)
		{
			if (legacy == null)
			{
				return new EnemyFilterConfig();
			}
			return new EnemyFilterConfig
			{
				Enabled = legacy.Enabled,
				Teleport = new EnemyTeleportConfig
				{
					Portals = new PortalTransportConfig
					{
						Enabled = (legacy.Teleport?.PortalsEnabled ?? true)
					},
					Dungeons = (legacy.Teleport?.Dungeons ?? new TransitionToggleConfig()),
					Basements = (legacy.Teleport?.Basements ?? new TransitionToggleConfig
					{
						Enabled = false
					})
				},
				MaximumSearchRadius = legacy.MaximumSearchRadius,
				MaximumSearchHeightRadius = legacy.MaximumSearchHeightRadius,
				Allow = (legacy.Allow ?? new List<string> { "all" }),
				Deny = (legacy.Deny ?? new List<string>()),
				Placement = (legacy.Placement ?? new CreaturePlacementConfig
				{
					Radius = 12f,
					DistanceFromPlayer = 6f
				})
			};
		}
	}
	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 static int FilterClearanceLayerMask(int defaultMask, int excludedLayerMask)
		{
			return defaultMask & ~excludedLayerMask;
		}
	}
	internal enum CreaturePlacementFailure
	{
		SearchRadius,
		TerrainUnavailable,
		DungeonFloorLevel,
		NaturalGround,
		BasementLevel,
		DungeonFallbackLevel,
		SlopeTooSteep,
		BlockedClearance,
		WaterPolicy,
		Spacing
	}
	internal struct CreaturePlacementProbe
	{
		private int _radius;

		private int _terrainUnavailable;

		private int _dungeonFloorLevel;

		private int _naturalGround;

		private int _basementLevel;

		private int _dungeonFallbackLevel;

		private int _slope;

		private int _clearance;

		private int _waterPolicy;

		private int _spacing;

		private bool _hasFailure;

		private CreaturePlacementFailure _firstFailure;

		private float _candidateX;

		private float _candidateY;

		private float _candidateZ;

		private bool _hasFloorHeight;

		private float _floorHeight;

		private bool _hasClearanceCandidate;

		private float _clearanceCandidateX;

		private float _clearanceCandidateY;

		private float _clearanceCandidateZ;

		private float _clearanceFloorHeight;

		private float _clearanceCapsuleRadius;

		private float _clearanceCapsuleHeight;

		private float _blockerCandidateX;

		private float _blockerCandidateY;

		private float _blockerCandidateZ;

		private float _blockerFloorHeight;

		public object? FirstClearanceBlocker { get; private set; }

		public void RecordFailure(CreaturePlacementFailure failure, float candidateX, float candidateY, float candidateZ, float? floorHeight = null)
		{
			switch (failure)
			{
			case CreaturePlacementFailure.SearchRadius:
				_radius++;
				break;
			case CreaturePlacementFailure.TerrainUnavailable:
				_terrainUnavailable++;
				break;
			case CreaturePlacementFailure.DungeonFloorLevel:
				_dungeonFloorLevel++;
				break;
			case CreaturePlacementFailure.NaturalGround:
				_naturalGround++;
				break;
			case CreaturePlacementFailure.BasementLevel:
				_basementLevel++;
				break;
			case CreaturePlacementFailure.DungeonFallbackLevel:
				_dungeonFallbackLevel++;
				break;
			case CreaturePlacementFailure.SlopeTooSteep:
				_slope++;
				break;
			case CreaturePlacementFailure.BlockedClearance:
				_clearance++;
				break;
			case CreaturePlacementFailure.WaterPolicy:
				_waterPolicy++;
				break;
			case CreaturePlacementFailure.Spacing:
				_spacing++;
				break;
			}
			if (!_hasFailure)
			{
				_hasFailure = true;
				_firstFailure = failure;
				_candidateX = candidateX;
				_candidateY = candidateY;
				_candidateZ = candidateZ;
				if (floorHeight.HasValue)
				{
					_hasFloorHeight = true;
					_floorHeight = floorHeight.Value;
				}
			}
		}

		public void RecordClearanceFailure(float candidateX, float candidateY, float candidateZ, float floorHeight, float capsuleRadius, float capsuleHeight, object? blocker)
		{
			RecordFailure(CreaturePlacementFailure.BlockedClearance, candidateX, candidateY, candidateZ, floorHeight);
			if (!_hasClearanceCandidate)
			{
				_hasClearanceCandidate = true;
				_clearanceCandidateX = candidateX;
				_clearanceCandidateY = candidateY;
				_clearanceCandidateZ = candidateZ;
				_clearanceFloorHeight = floorHeight;
				_clearanceCapsuleRadius = capsuleRadius;
				_clearanceCapsuleHeight = capsuleHeight;
			}
			if (blocker != null && FirstClearanceBlocker == null)
			{
				FirstClearanceBlocker = blocker;
				_blockerCandidateX = candidateX;
				_blockerCandidateY = candidateY;
				_blockerCandidateZ = candidateZ;
				_blockerFloorHeight = floorHeight;
			}
		}

		public string FormatSummary()
		{
			StringBuilder stringBuilder = new StringBuilder("placement candidates rejected:");
			AppendCount(stringBuilder, "radius", _radius);
			AppendCount(stringBuilder, "terrainUnavailable", _terrainUnavailable);
			AppendCount(stringBuilder, "dungeonFloorLevel", _dungeonFloorLevel);
			AppendCount(stringBuilder, "naturalGround", _naturalGround);
			AppendCount(stringBuilder, "basementLevel", _basementLevel);
			AppendCount(stringBuilder, "dungeonFallbackLevel", _dungeonFallbackLevel);
			AppendCount(stringBuilder, "slope", _slope);
			AppendCount(stringBuilder, "clearance", _clearance);
			AppendCount(stringBuilder, "waterPolicy", _waterPolicy);
			AppendCount(stringBuilder, "spacing", _spacing);
			if (_hasFailure)
			{
				stringBuilder.Append("; firstFailure=").Append(FailureName(_firstFailure)).Append(" candidate=(")
					.Append(Format(_candidateX))
					.Append(',')
					.Append(Format(_candidateY))
					.Append(',')
					.Append(Format(_candidateZ))
					.Append(')');
			}
			if (_hasFloorHeight)
			{
				stringBuilder.Append(" floorHeight=").Append(Format(_floorHeight));
			}
			if (_hasClearanceCandidate)
			{
				stringBuilder.Append(" firstClearanceCandidate=(").Append(Format(_clearanceCandidateX)).Append(',')
					.Append(Format(_clearanceCandidateY))
					.Append(',')
					.Append(Format(_clearanceCandidateZ))
					.Append(')')
					.Append(" firstClearanceFloorHeight=")
					.Append(Format(_clearanceFloorHeight))
					.Append(" capsuleRadius=")
					.Append(Format(_clearanceCapsuleRadius))
					.Append(" capsuleHeight=")
					.Append(Format(_clearanceCapsuleHeight));
			}
			if (FirstClearanceBlocker != null)
			{
				stringBuilder.Append(" firstBlockerCandidate=(").Append(Format(_blockerCandidateX)).Append(',')
					.Append(Format(_blockerCandidateY))
					.Append(',')
					.Append(Format(_blockerCandidateZ))
					.Append(')')
					.Append(" firstBlockerFloorHeight=")
					.Append(Format(_blockerFloorHeight));
			}
			return stringBuilder.ToString();
		}

		private static void AppendCount(StringBuilder summary, string name, int count)
		{
			if (count > 0)
			{
				summary.Append(' ').Append(name).Append('=')
					.Append(count);
			}
		}

		private static string FailureName(CreaturePlacementFailure failure)
		{
			return failure switch
			{
				CreaturePlacementFailure.SearchRadius => "radius", 
				CreaturePlacementFailure.TerrainUnavailable => "terrainUnavailable", 
				CreaturePlacementFailure.DungeonFloorLevel => "dungeonFloorLevel", 
				CreaturePlacementFailure.NaturalGround => "naturalGround", 
				CreaturePlacementFailure.BasementLevel => "basementLevel", 
				CreaturePlacementFailure.DungeonFallbackLevel => "dungeonFallbackLevel", 
				CreaturePlacementFailure.SlopeTooSteep => "slope", 
				CreaturePlacementFailure.BlockedClearance => "clearance", 
				CreaturePlacementFailure.WaterPolicy => "waterPolicy", 
				CreaturePlacementFailure.Spacing => "spacing", 
				_ => "unknown", 
			};
		}

		private static string Format(float value)
		{
			return value.ToString("0.##", CultureInfo.InvariantCulture);
		}
	}
	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 <cba6ec40-2546-4a46-9577-18d27987fd9e>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; }

		public bool ShouldCountEnemiesForPortalBlock
		{
			get
			{
				if (ServerPolicy.Enemies.Enabled)
				{
					return ServerPolicy.Enemies.Mode == EnemyPortalMode.VikingsDontRun;
				}
				return false;
			}
		}

		public bool ShouldTransportEnemiesThroughPortals
		{
			get
			{
				if (ServerPolicy.Enemies.Enabled)
				{
					return ServerPolicy.Enemies.Mode == EnemyPortalMode.TakeThemWithYou;
				}
				return false;
			}
		}

		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 bool ShouldScanAlliesFor(TransportTransitionKind transitionKind, bool basementsInstalled)
		{
			if (ServerPolicy.Allies.Enabled && PlayerPreferences.Allies.Enabled && PlayerPreferences.Allies.Mode != AllySelectionMode.None)
			{
				return IsTransitionEnabled(transitionKind, basementsInstalled);
			}
			return false;
		}

		public bool ShouldScanEnemiesFor(TransportTransitionKind transitionKind, bool basementsInstalled)
		{
			if (ServerPolicy.Enemies.Enabled && (transitionKind != TransportTransitionKind.Portal || ShouldTransportEnemiesThroughPortals))
			{
				return IsEnemyTransitionEnabled(transitionKind, basementsInstalled);
			}
			return false;
		}

		public bool ShouldCountEnemiesForPortal(bool requirePortalTransition, bool basementsInstalled)
		{
			if (ServerPolicy.Enemies.Enabled)
			{
				if (requirePortalTransition)
				{
					return IsEnemyTransitionEnabled(TransportTransitionKind.Portal, basementsInstalled);
				}
				return true;
			}
			return false;
		}

		public bool ShouldCapturePortalTransport(bool basementsInstalled)
		{
			if (!ShouldScanAlliesFor(TransportTransitionKind.Portal, basementsInstalled))
			{
				return ShouldScanEnemiesFor(TransportTransitionKind.Portal, basementsInstalled);
			}
			return true;
		}

		public bool ShouldEvaluatePortalWork(bool basementsInstalled)
		{
			if (!ShouldCapturePortalTransport(basementsInstalled))
			{
				return ShouldCountEnemiesForPortalBlock;
			}
			return true;
		}

		public float GetAllySearchRadius()
		{
			return Math.Min(ServerPolicy.Allies.MaximumSearchRadius, PlayerPreferences.Allies.SearchRadius);
		}

		public float GetAllySearchHeightRadius()
		{
			return Math.Min(ServerPolicy.Allies.MaximumSearchHeightRadius, PlayerPreferences.Allies.SearchHeightRadius);
		}

		public bool IsEnemyAllowed(string prefabName)
		{
			return IsEnemyAllowed(prefabName, null);
		}

		public bool IsEnemyAllowed(string prefabName, PortalContext? portalContext)
		{
			if (!ServerPolicy.Enemies.Enabled || string.IsNullOrWhiteSpace(prefabName) || !MatchesAllowedList(ServerPolicy.Enemies.Allow, prefabName) || MatchesAny(ServerPolicy.Enemies.Deny, prefabName))
			{
				return false;
			}
			PortalRuleConfig portalRuleConfig = ((portalContext == null) ? null : PortalRuleResolver.Select(ServerPolicy.Enemies.Teleport.Portals, portalContext));
			if (portalRuleConfig?.Allow == null || MatchesAllowedList(portalRuleConfig.Allow, prefabName))
			{
				if (portalRuleConfig != null)
				{
					return !MatchesAny(portalRuleConfig.Deny, prefabName);
				}
				return true;
			}
			return false;
		}

		public bool IsEnemyTransitionEnabled(TransportTransitionKind transitionKind, bool basementsInstalled)
		{
			return CreatureTransitionPolicy.IsEnabled(ServerPolicy.Enemies.Teleport, transitionKind, basementsInstalled);
		}

		public bool ShouldBlockPortalForNearbyEnemies(int eligibleEnemyCount)
		{
			if (ServerPolicy.Enemies.Enabled && ServerPolicy.Enemies.Mode == EnemyPortalMode.VikingsDontRun)
			{
				return eligibleEnemyCount > 0;
			}
			return false;
		}

		public float GetEnemySearchRadius()
		{
			return ServerPolicy.Enemies.MaximumSearchRadius;
		}

		public float GetEnemySearchHeightRadius()
		{
			return ServerPolicy.Enemies.MaximumSearchHeightRadius;
		}

		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 readonly struct CreaturePreviewCounts : IEquatable<CreaturePreviewCounts>
	{
		public int AllyCount { get; }

		public int EnemyCount { get; }

		public CreaturePreviewCounts(int allyCount, int enemyCount)
		{
			AllyCount = allyCount;
			EnemyCount = enemyCount;
		}

		public bool Equals(CreaturePreviewCounts other)
		{
			if (AllyCount == other.AllyCount)
			{
				return EnemyCount == other.EnemyCount;
			}
			return false;
		}

		public override bool Equals(object? obj)
		{
			if (obj is CreaturePreviewCounts other)
			{
				return Equals(other);
			}
			return false;
		}

		public override int GetHashCode()
		{
			return (AllyCount * 397) ^ EnemyCount;
		}
	}
	internal readonly struct CreaturePreviewCacheKey : IEquatable<CreaturePreviewCacheKey>
	{
		private static class NullIdentity
		{
			public static readonly object Value = new object();
		}

		private readonly object? _player;

		private readonly object? _session;

		private readonly object? _portal;

		private readonly object _policy;

		private readonly string _prefabName;

		private readonly string _tag;

		private readonly bool _includeAllies;

		private readonly AllyTransportMessageMode _allyDisplayMode;

		private readonly AllySelectionMode _allyMode;

		private readonly bool _includeEnemies;

		private readonly AllyTransportMessageMode _enemyDisplayMode;

		private readonly EnemyPortalMode _enemyMode;

		public CreaturePreviewCacheKey(object? player, object? session, object? portal, object policy, string prefabName, string tag, bool includeAllies, AllyTransportMessageMode allyDisplayMode, AllySelectionMode allyMode, bool includeEnemies, AllyTransportMessageMode enemyDisplayMode, EnemyPortalMode enemyMode)
		{
			_player = player;
			_session = session;
			_portal = portal;
			_policy = policy ?? throw new ArgumentNullException("policy");
			_prefabName = prefabName ?? string.Empty;
			_tag = tag ?? string.Empty;
			_includeAllies = includeAllies;
			_allyDisplayMode = allyDisplayMode;
			_allyMode = allyMode;
			_includeEnemies = includeEnemies;
			_enemyDisplayMode = enemyDisplayMode;
			_enemyMode = enemyMode;
		}

		public bool IsForPortal(object? portal)
		{
			return _portal == portal;
		}

		public bool Equals(CreaturePreviewCacheKey other)
		{
			if (_player == other._player && _session == other._session && _portal == other._portal && _policy == other._policy && string.Equals(_prefabName, other._prefabName, StringComparison.Ordinal) && string.Equals(_tag, other._tag, StringComparison.Ordinal) && _includeAllies == other._includeAllies && _allyDisplayMode == other._allyDisplayMode && _allyMode == other._allyMode && _includeEnemies == other._includeEnemies && _enemyDisplayMode == other._enemyDisplayMode)
			{
				return _enemyMode == other._enemyMode;
			}
			return false;
		}

		public override bool Equals(object? obj)
		{
			if (obj is CreaturePreviewCacheKey other)
			{
				return Equals(other);
			}
			return false;
		}

		public override int GetHashCode()
		{
			return AddHash(AddHash(AddHash(AddHash(AddHash(AddHash(AddHash(AddHash(AddHash(AddHash(AddHash(RuntimeHelpers.GetHashCode(_player ?? NullIdentity.Value), RuntimeHelpers.GetHashCode(_session ?? NullIdentity.Value)), RuntimeHelpers.GetHashCode(_portal ?? NullIdentity.Value)), RuntimeHelpers.GetHashCode(_policy)), StringComparer.Ordinal.GetHashCode(_prefabName)), StringComparer.Ordinal.GetHashCode(_tag)), _includeAllies ? 1 : 0), (int)_allyDisplayMode), (int)_allyMode), _includeEnemies ? 1 : 0), (int)_enemyDisplayMode), (int)_enemyMode);
		}

		private static int AddHash(int hash, int value)
		{
			return (hash * 397) ^ value;
		}
	}
	internal static class CreaturePreviewCountCalculator
	{
		public static CreaturePreviewCounts Count<T>(IReadOnlyList<T> candidates, bool includeAllies, bool includeEnemies, Func<T, bool> isEligibleAlly, Func<T, bool> isEligibleEnemy)
		{
			int num = 0;
			int num2 = 0;
			for (int i = 0; i < candidates.Count; i++)
			{
				T arg = candidates[i];
				if (includeAllies && isEligibleAlly(arg))
				{
					num++;
				}
				if (includeEnemies && isEligibleEnemy(arg))
				{
					num2++;
				}
			}
			return new CreaturePreviewCounts(num, num2);
		}
	}
	internal static class CreatureTeleportDiagnostics
	{
		public static string FormatCapture(TransportTransitionKind transitionKind, string player, string source, string requestedDestination, int allies, int enemies)
		{
			return $"TeleportCaptured: transition={transitionKind} player={player} source={source} requestedDestination={requestedDestination} allies={allies} enemies={enemies}";
		}

		public static string FormatMovement(bool isEnemy, TransportTransitionKind transitionKind, string prefabName, string customName, string zdoId, string source, string destination, string method)
		{
			return string.Format("{0}: transition={1} prefab={2} name={3} zdo={4} source={5} destination={6} method={7}", isEnemy ? "EnemyMoved" : "AllyMoved", transitionKind, prefabName, customName, zdoId, source, destination, method);
		}

		public static string FormatCompletion(TransportTransitionKind transitionKind, string player, string source, string destination, int movedAllies, int movedEnemies, int skippedAllies, int skippedEnemies)
		{
			return $"TeleportCompleted: transition={transitionKind} player={player} source={source} destination={destination} movedAllies={movedAllies} movedEnemies={movedEnemies} skippedAllies={skippedAllies} skippedEnemies={skippedEnemies}";
		}

		public static string FormatSkip(bool isEnemy, TransportTransitionKind transitionKind, string prefabName, string customName, string zdoId, string source, string reason)
		{
			return string.Format("{0}: transition={1} prefab={2} name={3} zdo={4} source={5} reason={6}", isEnemy ? "EnemySkipped" : "AllySkipped", transitionKind, prefabName, customName, zdoId, source, reason);
		}

		public static string FormatFiltered(TransportTransitionKind transitionKind, string player, int eligibleAllies, int eligibleEnemies, int outOfRangeAllies, int outOfRangeEnemies, int transitionDisabledAllies, int transitionDisabledEnemies, int policyDeniedAllies, int policyDeniedEnemies)
		{
			return $"CreatureFilterSummary: transition={transitionKind} player={player} eligibleAllies={eligibleAllies} eligibleEnemies={eligibleEnemies} outOfRangeAllies={outOfRangeAllies} outOfRangeEnemies={outOfRangeEnemies} transitionDisabledAllies={transitionDisabledAllies} transitionDisabledEnemies={transitionDisabledEnemies} policyDeniedAllies={policyDeniedAllies} policyDeniedEnemies={policyDeniedEnemies}";
		}

		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(EnemyTeleportConfig 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(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 ShouldShowVikingsDontRunAlert(bool isLocalPlayer, bool pluginEnabled, bool isAlive, bool messageHudAvailable, bool localizationAvailable, int eligibleEnemyCount, int frame, int lastAlertFrame)
		{
			if (isLocalPlayer && pluginEnabled && isAlive && messageHudAvailable && localizationAvailable && eligibleEnemyCount > 0)
			{
				return frame != lastAlertFrame;
			}
			return false;
		}

		public static bool ShouldBlockPortal(bool isLocalPlayer, bool isInterior, bool isBasement, bool hasPortalContext, bool pluginEnabled, bool enemiesEnabled, EnemyPortalMode mode, int eligibleEnemyCount)
		{
			if (isLocalPlayer && !isInterior && !isBasement && hasPortalContext && pluginEnabled && enemiesEnabled && mode == EnemyPortalMode.VikingsDontRun)
			{
				return eligibleEnemyCount > 0;
			}
			return false;
		}

		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 PlayerDescriptorAvailability
	{
		internal static void Apply(ModuleConfigurationView view, bool moduleEnabled, CreatureConfig server)
		{
			if (!moduleEnabled)
			{
				Disable(view, (ConfigurationDescriptor _) => true, "The creature module is off. Saved preferences are retained.");
				return;
			}
			if (!server.Allies.Enabled)
			{
				Disable(view, (ConfigurationDescriptor row) => Under(row, "Allies") || row.StableId.StartsWith("cfg:Allies Display/", StringComparison.Ordinal) || row.StableId == "cfg:Controls/Previous Allies Mode Hotkey" || row.StableId == "cfg:Controls/Next Allies Mode Hotkey", "Ally transport is off. Saved preferences are retained.");
			}
			else
			{
				if (!server.Allies.Teleport.Portals.Enabled)
				{
					Disable(view, (ConfigurationDescriptor row) => Under(row, "Allies.Teleport.Portals"), "Portal ally transport is off. Saved preferences are retained.");
				}
				if (!server.Allies.Teleport.Dungeons.Enabled)
				{
					Disable(view, (ConfigurationDescriptor row) => Under(row, "Allies.Teleport.Dungeons"), "Dungeon ally transport is off. Saved preferences are retained.");
				}
				if (!server.Allies.Teleport.Basements.Enabled)
				{
					Disable(view, (ConfigurationDescriptor row) => Under(row, "Allies.Teleport.Basements"), "Basement ally transport is off. Saved preferences are retained.");
				}
			}
			if (!server.Enemies.Enabled)
			{
				Disable(view, (ConfigurationDescriptor row) => row.StableId.StartsWith("cfg:Enemies Display/", StringComparison.Ordinal), "Enemy transport is off. Saved preferences are retained.");
			}
		}

		private static bool Under(ConfigurationDescriptor row, string path)
		{
			if (!row.StableId.Equals(path, StringComparison.Ordinal))
			{
				return row.StableId.StartsWith(path + ".", StringComparison.Ordinal);
			}
			return true;
		}

		private static void Disable(ModuleConfigurationView view, Func<ConfigurationDescriptor, bool> matches, string reason)
		{
			foreach (ConfigurationDescriptor descriptor in view.Descriptors)
			{
				if (matches(descriptor))
				{
					descriptor.Enabled = false;
					descriptor.DisabledReason = "Disabled by server: " + reason;
				}
			}
		}
	}
	internal static class PlayerYamlModeWriter
	{
		private static readonly Regex ModeLine = new Regex("^([ \\t]*)Mode\\s*:.*$", RegexOptions.Multiline | RegexOptions.Compiled);

		public static bool TryReplaceModeLine(string yaml, string modeName, out string updated)
		{
			updated = yaml;
			if (string.IsNullOrEmpty(yaml) || string.IsNullOrWhiteSpace(modeName))
			{
				return false;
			}
			Match match = ModeLine.Match(yaml);
			if (!match.Success)
			{
				return false;
			}
			updated = yaml.Substring(0, match.Index) + match.Groups[1].Value + "Mode: " + modeName + yaml.Substring(match.Index + match.Length);
			return true;
		}
	}
	internal static class AllyModeSwitchDisplay
	{
		private const float FadeInSeconds = 0.2f;

		private const float HoldSeconds = 1.2f;

		private const float FadeOutSeconds = 0.4f;

		private const float SwipeDistancePixels = 36f;

		private const float CursorOffsetPixels = 208f;

		private const string LabelTranslationKey = "$tc_ally_mode_label";

		private static readonly Color ModeColor = new Color(1f, 0.851f, 0.302f);

		private static GameObject? _root;

		private static CanvasGroup? _canvasGroup;

		private static TMP_Text? _labelText;

		private static TMP_Text? _modeText;

		private static RectTransform? _canvasRect;

		private static Vector2 _basePosition;

		private static float _shownAt;

		private static int _direction = 1;

		private static bool _reportedShowError;

		public static void Show(AllySelectionMode mode, int direction)
		{
			try
			{
				ShowSafely(mode, direction);
			}
			catch (Exception ex)
			{
				Hide();
				if (!_reportedShowError)
				{
					_reportedShowError = true;
					TeleportCreaturesPlugin.Log.LogWarning((object)("Could not show the allies mode readout: " + ex.Message));
				}
			}
		}

		public static void Tick()
		{
			//IL_00bb: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c0: 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 referen