Decompiled source of RecallTames v1.1.0

plugins/RecallTames.dll

Decompiled a day ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("RecallTames")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("RecallTames")]
[assembly: AssemblyTitle("RecallTames")]
[assembly: AssemblyVersion("1.0.0.0")]
namespace RecallTames;

[BepInPlugin("michal.recalltames", "RecallTames", "1.1.0")]
public sealed class RecallTamesPlugin : BaseUnityPlugin
{
	private sealed class TameRecord
	{
		public ZDOID Id { get; private set; }

		public string PrefabName { get; private set; }

		public string LocalizedName { get; private set; }

		public string TamedName { get; private set; }

		public int Level { get; private set; }

		public int Stars => Math.Max(0, Level - 1);

		public Vector3 Position { get; private set; }

		public bool IsFlying { get; private set; }

		public bool IsWaterLike { get; private set; }

		public float Radius { get; private set; }

		public static TameRecord FromUnknown(ZDO zdo)
		{
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_0064: Unknown result type (might be due to invalid IL or missing references)
			return new TameRecord
			{
				Id = zdo.m_uid,
				PrefabName = "UnknownPrefabHash_" + zdo.GetPrefab(),
				LocalizedName = "",
				TamedName = zdo.GetString(ZDOVars.s_tamedName, ""),
				Level = zdo.GetInt(ZDOVars.s_level, 1),
				Position = zdo.GetPosition(),
				IsFlying = false,
				IsWaterLike = false,
				Radius = 1.5f
			};
		}

		public static TameRecord FromZdo(ZDO zdo, GameObject prefab)
		{
			//IL_0042: Unknown result type (might be due to invalid IL or missing references)
			//IL_0084: Unknown result type (might be due to invalid IL or missing references)
			Character component = prefab.GetComponent<Character>();
			string name = ((Object)prefab).name;
			string localizedCharacterName = GetLocalizedCharacterName(component, name);
			bool isFlying = (Object)(object)component != (Object)null && component.m_flying;
			bool isWaterLike = IsWaterLikePrefab(name, prefab, component);
			float creatureRadius = GetCreatureRadius(component);
			return new TameRecord
			{
				Id = zdo.m_uid,
				PrefabName = name,
				LocalizedName = localizedCharacterName,
				TamedName = zdo.GetString(ZDOVars.s_tamedName, ""),
				Level = zdo.GetInt(ZDOVars.s_level, 1),
				Position = zdo.GetPosition(),
				IsFlying = isFlying,
				IsWaterLike = isWaterLike,
				Radius = creatureRadius
			};
		}

		private static string GetLocalizedCharacterName(Character character, string fallback)
		{
			if ((Object)(object)character == (Object)null || string.IsNullOrWhiteSpace(character.m_name))
			{
				return fallback;
			}
			try
			{
				if (Localization.instance != null)
				{
					return Localization.instance.Localize(character.m_name);
				}
			}
			catch
			{
			}
			return character.m_name;
		}

		private static float GetCreatureRadius(Character character)
		{
			if ((Object)(object)character == (Object)null)
			{
				return 1.5f;
			}
			try
			{
				return Mathf.Clamp(character.GetRadius(), 1f, 6f);
			}
			catch
			{
				return 1.5f;
			}
		}

		private static bool IsWaterLikePrefab(string prefabName, GameObject prefab, Character character)
		{
			if ((Object)(object)prefab.GetComponent<Fish>() != (Object)null)
			{
				return true;
			}
			foreach (string waterNameHint in WaterNameHints)
			{
				if (prefabName.IndexOf(waterNameHint, StringComparison.OrdinalIgnoreCase) >= 0)
				{
					return true;
				}
			}
			if ((Object)(object)character != (Object)null && character.m_swimDepth > 3f)
			{
				return prefabName.IndexOf("serpent", StringComparison.OrdinalIgnoreCase) >= 0;
			}
			return false;
		}
	}

	private readonly struct MovePlan
	{
		public TameRecord Record { get; }

		public Vector3 OldPosition { get; }

		public Vector3 NewPosition { get; }

		public MovePlan(TameRecord record, Vector3 oldPosition, Vector3 newPosition)
		{
			//IL_0008: Unknown result type (might be due to invalid IL or missing references)
			//IL_0009: Unknown result type (might be due to invalid IL or missing references)
			//IL_000f: 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)
			Record = record;
			OldPosition = oldPosition;
			NewPosition = newPosition;
		}
	}

	private readonly struct BackupEntry
	{
		public ZDOID Id { get; }

		public string Prefab { get; }

		public Vector3 OldPosition { get; }

		public BackupEntry(ZDOID id, string prefab, Vector3 oldPosition)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			//IL_000f: 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)
			Id = id;
			Prefab = prefab;
			OldPosition = oldPosition;
		}
	}

	public const string ModGuid = "michal.recalltames";

	public const string ModName = "RecallTames";

	public const string ModVersion = "1.1.0";

	private const string RpcRequest = "RecallTames_Request_v1";

	private const string RpcResponse = "RecallTames_Response_v1";

	private const string Prefix = "[RecallTames]";

	private static readonly string[] BaseOptions = new string[6] { "all", "list", "count", "preview", "undo", "help" };

	private static readonly HashSet<string> WaterNameHints = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "serpent", "fish", "leviathan", "kraken", "shark", "eel", "whale" };

	private static readonly object PendingLock = new object();

	private static readonly Dictionary<long, Terminal> PendingResponses = new Dictionary<long, Terminal>();

	private static long _nextRequestId = DateTime.UtcNow.Ticks;

	private static MethodInfo _increaseDataRevision;

	private static ZRoutedRpc _registeredRpcInstance;

	private static List<string> _autocompleteCache = new List<string>(BaseOptions);

	private static float _autocompleteCacheTime = -999f;

	private static ManualLogSource _log;

	private static ConfigEntry<bool> _enableMod;

	private static ConfigEntry<bool> _requireAdmin;

	private static ConfigEntry<bool> _enableLogging;

	private static ConfigEntry<bool> _createBackupBeforeTeleport;

	private static ConfigEntry<float> _startRadius;

	private static ConfigEntry<float> _ringSpacing;

	private static ConfigEntry<float> _creatureSpacing;

	private static ConfigEntry<float> _maxRadius;

	private static ConfigEntry<float> _flyingHeight;

	private static ConfigEntry<int> _requireConfirmationAbove;

	private static ConfigEntry<bool> _allowUnknownPrefabs;

	private static ConfigEntry<bool> _allowWaterCreaturesOnLand;

	private void Awake()
	{
		_log = ((BaseUnityPlugin)this).Logger;
		BindConfig();
		_increaseDataRevision = typeof(ZDO).GetMethod("IncreaseDataRevision", BindingFlags.Instance | BindingFlags.NonPublic);
		RegisterConsoleCommand();
		LogInfo("Loaded. Waiting for Valheim networking to register RPC handlers.");
	}

	private void Update()
	{
		RegisterRpcIfReady();
	}

	private void BindConfig()
	{
		_enableMod = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "EnableMod", true, "Enable or disable RecallTames.");
		_requireAdmin = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "RequireAdmin", true, "Require Valheim admin rights for commands.");
		_enableLogging = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "EnableLogging", true, "Write RecallTames actions to the BepInEx log.");
		_createBackupBeforeTeleport = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "CreateBackupBeforeTeleport", true, "Create JSON backups before teleport operations.");
		_startRadius = ((BaseUnityPlugin)this).Config.Bind<float>("Placement", "StartRadius", 4f, "First placement ring radius in meters.");
		_ringSpacing = ((BaseUnityPlugin)this).Config.Bind<float>("Placement", "RingSpacing", 4f, "Distance between placement rings.");
		_creatureSpacing = ((BaseUnityPlugin)this).Config.Bind<float>("Placement", "CreatureSpacing", 3f, "Minimum spacing used when distributing creatures.");
		_maxRadius = ((BaseUnityPlugin)this).Config.Bind<float>("Placement", "MaxRadius", 30f, "Maximum placement radius before points wrap around.");
		_flyingHeight = ((BaseUnityPlugin)this).Config.Bind<float>("Placement", "FlyingHeight", 5f, "Extra height above ground for flying creatures.");
		_requireConfirmationAbove = ((BaseUnityPlugin)this).Config.Bind<int>("Safety", "RequireConfirmationAbove", 20, "Require confirm when teleporting more than this number of creatures.");
		_allowUnknownPrefabs = ((BaseUnityPlugin)this).Config.Bind<bool>("Safety", "AllowUnknownPrefabs", false, "Allow ZDOs with unknown prefabs if they are marked tamed.");
		_allowWaterCreaturesOnLand = ((BaseUnityPlugin)this).Config.Bind<bool>("Safety", "AllowWaterCreaturesOnLand", false, "Allow water-like creatures to be placed on land without force.");
	}

	private static void RegisterConsoleCommand()
	{
		//IL_0011: Unknown result type (might be due to invalid IL or missing references)
		//IL_0023: Unknown result type (might be due to invalid IL or missing references)
		//IL_0030: Expected O, but got Unknown
		//IL_0030: Expected O, but got Unknown
		//IL_002b: Unknown result type (might be due to invalid IL or missing references)
		new ConsoleCommand("recalltames", "Admin commands for finding and recalling existing tamed creatures.", new ConsoleEvent(OnConsoleCommand), false, false, false, false, true, false, new ConsoleOptionsFetcher(GetAutocompleteOptions), true, false, false);
	}

	private static void RegisterRpcIfReady()
	{
		ZRoutedRpc instance = ZRoutedRpc.instance;
		if (instance == null || instance == _registeredRpcInstance)
		{
			return;
		}
		try
		{
			instance.Register<long, string, Vector3>("RecallTames_Request_v1", (Action<long, long, string, Vector3>)OnRpcRequest);
			instance.Register<long, string>("RecallTames_Response_v1", (Action<long, long, string>)OnRpcResponse);
			_registeredRpcInstance = instance;
			LogInfo("RPC handlers registered.");
		}
		catch (Exception ex)
		{
			LogWarning("Could not register RPC handlers yet: " + ex.Message);
		}
	}

	private static void OnConsoleCommand(ConsoleEventArgs args)
	{
		//IL_005b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0060: Unknown result type (might be due to invalid IL or missing references)
		//IL_008f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0113: Unknown result type (might be due to invalid IL or missing references)
		Terminal context = args.Context;
		if (!_enableMod.Value)
		{
			Print(context, "RecallTames is disabled in config.");
			return;
		}
		RegisterRpcIfReady();
		if (args.Length <= 1 || IsHelpToken(args.Args[1]))
		{
			Print(context, BuildHelp());
			return;
		}
		if (!IsWorldReady(out var reason))
		{
			Print(context, reason);
			return;
		}
		bool hasOrigin;
		Vector3 localCommandOrigin = GetLocalCommandOrigin(out hasOrigin);
		if (!hasOrigin)
		{
			Print(context, "No local player position found. Join a world before using RecallTames.");
			return;
		}
		if ((Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsServer())
		{
			string message = ExecuteCommand(args.ArgsAll, localCommandOrigin, isServerRpc: false, ZNet.GetUID());
			Print(context, message);
			return;
		}
		if (ZRoutedRpc.instance == null)
		{
			Print(context, "Networking is not ready yet. Try again after the world fully loads.");
			return;
		}
		long num = NextRequestId();
		lock (PendingLock)
		{
			PendingResponses[num] = context;
		}
		ZRoutedRpc.instance.InvokeRoutedRPC("RecallTames_Request_v1", new object[3] { num, args.ArgsAll, localCommandOrigin });
		Print(context, "RecallTames request sent to server. If nothing returns, install RecallTames on the server too.");
	}

	private static void OnRpcRequest(long sender, long requestId, string commandLine, Vector3 requestedOrigin)
	{
		//IL_002a: Unknown result type (might be due to invalid IL or missing references)
		//IL_002b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0030: Unknown result type (might be due to invalid IL or missing references)
		//IL_0032: Unknown result type (might be due to invalid IL or missing references)
		if ((Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer())
		{
			return;
		}
		string text;
		try
		{
			if (!IsSenderAuthorized(sender, out var reason))
			{
				text = reason;
			}
			else
			{
				Vector3 serverOriginForSender = GetServerOriginForSender(sender, requestedOrigin);
				text = ExecuteCommand(commandLine, serverOriginForSender, isServerRpc: true, sender);
			}
		}
		catch (Exception ex)
		{
			text = "RecallTames failed: " + ex.Message;
			LogError(ex);
		}
		ZRoutedRpc.instance.InvokeRoutedRPC(sender, "RecallTames_Response_v1", new object[2] { requestId, text });
	}

	private static void OnRpcResponse(long sender, long requestId, string message)
	{
		Terminal value = null;
		lock (PendingLock)
		{
			if (PendingResponses.TryGetValue(requestId, out value))
			{
				PendingResponses.Remove(requestId);
			}
		}
		Print((Terminal)(((object)value) ?? ((object)Console.instance)), message);
	}

	private static string ExecuteCommand(string commandLine, Vector3 origin, bool isServerRpc, long sender)
	{
		//IL_00a6: Unknown result type (might be due to invalid IL or missing references)
		//IL_00b9: Unknown result type (might be due to invalid IL or missing references)
		if (isServerRpc && !IsSenderAuthorized(sender, out var reason))
		{
			return reason;
		}
		string[] array = Tokenize(commandLine);
		if (array.Length == 0 || IsHelpToken(array[0]))
		{
			return BuildHelp();
		}
		string text = array[0].ToLowerInvariant();
		try
		{
			return text switch
			{
				"help" => BuildHelp(), 
				"list" => BuildList(), 
				"count" => BuildCount(array), 
				"preview" => BuildPreview(array), 
				"all" => TeleportAll(array, origin), 
				"undo" => UndoLast(array), 
				_ => TeleportPrefab(array, origin), 
			};
		}
		catch (Exception ex)
		{
			LogError(ex);
			return "RecallTames failed: " + ex.Message;
		}
	}

	private static string BuildHelp()
	{
		return string.Join(Environment.NewLine, "RecallTames commands:", "recalltames list", "recalltames count", "recalltames count Wolf", "recalltames count Wolf 2", "recalltames preview all", "recalltames preview Wolf", "recalltames preview Wolf 2", "recalltames all", "recalltames all stars 2", "recalltames all confirm", "recalltames Wolf", "recalltames Boar 2", "recalltames Serpent here", "recalltames Serpent force", "recalltames undo", "recalltames undo confirm", "Star number means visible stars: 0 stars = level 1, 1 star = level 2, 2 stars = level 3.", "Use exact prefab names when multiple matches are shown.");
	}

	private static string BuildList()
	{
		string warning;
		List<TameRecord> list = ScanTamedCreatures(out warning);
		StringBuilder stringBuilder = new StringBuilder();
		AppendScanHeader(stringBuilder, list, warning);
		foreach (IGrouping<string, TameRecord> item in from r in list
			group r by r.PrefabName into g
			orderby g.Key
			select g)
		{
			string localizedName = item.First().LocalizedName;
			string text = ((string.IsNullOrWhiteSpace(localizedName) || localizedName == item.Key) ? item.Key : (item.Key + " - " + localizedName));
			stringBuilder.AppendLine(text + ": " + item.Count() + FormatStarBreakdown(item));
		}
		return TrimEnd(stringBuilder);
	}

	private static string BuildCount(string[] tokens)
	{
		string warning;
		List<TameRecord> records = ScanTamedCreatures(out warning);
		if (tokens.Length == 1)
		{
			return HeaderOnly(records, warning);
		}
		if (!TryParseStarFilter(tokens, 1, out var stars, out var message))
		{
			return message;
		}
		if (IsAllOrStarOnly(tokens, 1))
		{
			return HeaderOnly(ApplyStarFilter(records, stars), warning, DescribeSelection("all tamed creatures", stars));
		}
		if (!TryResolveSelection(records, tokens[1], out var selected, out var message2))
		{
			return message2;
		}
		if (!TryParseStarFilter(tokens, 2, out var stars2, out message))
		{
			return message;
		}
		selected = ApplyStarFilter(selected, stars2);
		return HeaderOnly(selected, warning, DescribeSelection(tokens[1], stars2));
	}

	private static string BuildPreview(string[] tokens)
	{
		//IL_0169: Unknown result type (might be due to invalid IL or missing references)
		//IL_0185: Unknown result type (might be due to invalid IL or missing references)
		//IL_01dc: Unknown result type (might be due to invalid IL or missing references)
		//IL_01e1: Unknown result type (might be due to invalid IL or missing references)
		if (tokens.Length < 2)
		{
			return "Use: recalltames preview all OR recalltames preview <prefab> OR recalltames preview <prefab> <stars>";
		}
		string warning;
		List<TameRecord> list = ScanTamedCreatures(out warning);
		int startIndex;
		string label;
		List<TameRecord> selected;
		if (tokens[1].Equals("all", StringComparison.OrdinalIgnoreCase))
		{
			selected = list;
			startIndex = 2;
			label = "all tamed creatures";
		}
		else
		{
			if (!TryResolveSelection(list, tokens[1], out selected, out var message))
			{
				return message;
			}
			startIndex = 2;
			label = tokens[1];
		}
		if (!TryParseStarFilter(tokens, startIndex, out var stars, out var message2))
		{
			return message2;
		}
		selected = ApplyStarFilter(selected, stars);
		StringBuilder stringBuilder = new StringBuilder();
		AppendScanHeader(stringBuilder, selected, warning, DescribeSelection(label, stars));
		int num = 1;
		foreach (TameRecord item in from r in selected
			orderby r.PrefabName, r.Position.x, r.Position.z
			select r)
		{
			string text = (string.IsNullOrWhiteSpace(item.TamedName) ? "" : (" \"" + item.TamedName + "\""));
			stringBuilder.AppendLine(num.ToString(CultureInfo.InvariantCulture) + ". " + item.PrefabName + text + " - X/Z " + Format(item.Position.x) + " / " + Format(item.Position.z) + " - stars " + item.Stars + " (level " + item.Level + ") - " + ((object)item.Id/*cast due to .constrained prefix*/).ToString());
			num++;
		}
		return TrimEnd(stringBuilder);
	}

	private static string TeleportAll(string[] tokens, Vector3 origin)
	{
		//IL_00b6: Unknown result type (might be due to invalid IL or missing references)
		bool flag = HasToken(tokens, "confirm");
		bool force = HasToken(tokens, "force");
		bool here = HasToken(tokens, "here");
		List<TameRecord> records = ScanTamedCreatures(out var warning);
		if (!TryParseStarFilter(tokens, 1, out var stars, out var message))
		{
			return message;
		}
		records = ApplyStarFilter(records, stars);
		string label = DescribeSelection("all tamed creatures", stars);
		if (!flag && records.Count > _requireConfirmationAbove.Value)
		{
			return "Found " + records.Count + " tamed creatures." + Environment.NewLine + "Type: " + BuildConfirmCommand("all", stars);
		}
		return TeleportSelection(records, origin, label, flag, here, force, warning);
	}

	private static string TeleportPrefab(string[] tokens, Vector3 origin)
	{
		//IL_00f4: Unknown result type (might be due to invalid IL or missing references)
		string text = tokens[0];
		bool flag = HasToken(tokens, "confirm");
		bool force = HasToken(tokens, "force");
		bool here = HasToken(tokens, "here");
		string warning;
		List<TameRecord> records = ScanTamedCreatures(out warning);
		if (!TryParseStarFilter(tokens, 1, out var stars, out var message))
		{
			return message;
		}
		if (!TryResolveSelection(records, text, out var selected, out var message2))
		{
			return message2;
		}
		string text2 = ((selected.Count > 0) ? selected[0].PrefabName : text);
		selected = ApplyStarFilter(selected, stars);
		string text3 = DescribeSelection(text2, stars);
		if (!flag && selected.Count > _requireConfirmationAbove.Value)
		{
			return "Found " + selected.Count + " tamed " + text3 + "." + Environment.NewLine + "Type: " + BuildConfirmCommand(text2, stars);
		}
		return TeleportSelection(selected, origin, text3, flag, here, force, warning);
	}

	private static string TeleportSelection(List<TameRecord> records, Vector3 origin, string label, bool confirm, bool here, bool force, string scanWarning)
	{
		//IL_030d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0312: Unknown result type (might be due to invalid IL or missing references)
		//IL_0139: Unknown result type (might be due to invalid IL or missing references)
		//IL_020c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0256: Unknown result type (might be due to invalid IL or missing references)
		//IL_028a: Unknown result type (might be due to invalid IL or missing references)
		//IL_028f: Unknown result type (might be due to invalid IL or missing references)
		//IL_02ab: Unknown result type (might be due to invalid IL or missing references)
		//IL_02c2: Unknown result type (might be due to invalid IL or missing references)
		//IL_022e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0233: Unknown result type (might be due to invalid IL or missing references)
		//IL_00ec: Unknown result type (might be due to invalid IL or missing references)
		if (records.Count == 0)
		{
			return "No matching tamed creatures found.";
		}
		if (!_allowWaterCreaturesOnLand.Value && !force)
		{
			List<TameRecord> list = records.Where((TameRecord r) => r.IsWaterLike).ToList();
			if (list.Count > 0)
			{
				if (!here)
				{
					return "Water-like tamed creatures found (" + string.Join(", ", from s in list.Select((TameRecord r) => r.PrefabName).Distinct()
						orderby s
						select s) + ")." + Environment.NewLine + "Use '<prefab> here' while standing at safe water, or '<prefab> force' if you intentionally accept land placement risk.";
				}
				if (!IsWaterAt(origin))
				{
					return "The target area does not look like water. Move to safe water and use 'here', or use 'force' to override.";
				}
			}
		}
		LogInfo("Teleporting " + records.Count + " " + label + "...");
		List<MovePlan> list2 = BuildMovePlan(records, origin, force, here);
		string path = null;
		if (_createBackupBeforeTeleport.Value && !TryWriteBackup(list2, "teleport", out path, out var error))
		{
			return "Backup failed, teleport was cancelled: " + error;
		}
		int num = 0;
		int num2 = 0;
		StringBuilder stringBuilder = new StringBuilder();
		if (!string.IsNullOrWhiteSpace(scanWarning))
		{
			stringBuilder.AppendLine(scanWarning);
		}
		stringBuilder.AppendLine("Teleporting " + list2.Count + " " + label + "...");
		if (!string.IsNullOrWhiteSpace(path))
		{
			stringBuilder.AppendLine("Backup: " + path);
		}
		foreach (MovePlan item in list2)
		{
			try
			{
				ZDO zDO = ZDOMan.instance.GetZDO(item.Record.Id);
				if (zDO == null)
				{
					num2++;
					stringBuilder.AppendLine("Missing ZDO skipped: " + ((object)item.Record.Id/*cast due to .constrained prefix*/).ToString());
					continue;
				}
				MoveZdo(zDO, item.NewPosition);
				num++;
				LogInfo(item.Record.PrefabName + " " + ((object)item.Record.Id/*cast due to .constrained prefix*/).ToString() + " moved from " + Format(item.OldPosition) + " to " + Format(item.NewPosition));
			}
			catch (Exception ex)
			{
				num2++;
				LogWarning("Failed to move " + item.Record.PrefabName + " " + ((object)item.Record.Id/*cast due to .constrained prefix*/).ToString() + ": " + ex.Message);
			}
		}
		stringBuilder.AppendLine("Completed: " + num + "/" + list2.Count);
		if (num2 > 0)
		{
			stringBuilder.AppendLine("Failed: " + num2);
		}
		return TrimEnd(stringBuilder);
	}

	private static string UndoLast(string[] tokens)
	{
		//IL_010e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0113: Unknown result type (might be due to invalid IL or missing references)
		//IL_00ab: Unknown result type (might be due to invalid IL or missing references)
		//IL_00ef: Unknown result type (might be due to invalid IL or missing references)
		//IL_00ca: Unknown result type (might be due to invalid IL or missing references)
		//IL_00cf: Unknown result type (might be due to invalid IL or missing references)
		if (!TryReadLatestBackup(out var entries, out var path, out var error))
		{
			return error;
		}
		if (!HasToken(tokens, "confirm") && entries.Count > _requireConfirmationAbove.Value)
		{
			return "Latest backup contains " + entries.Count + " creatures." + Environment.NewLine + "Type: recalltames undo confirm";
		}
		int num = 0;
		int num2 = 0;
		int num3 = 0;
		StringBuilder stringBuilder = new StringBuilder();
		stringBuilder.AppendLine("Undo from backup: " + path);
		foreach (BackupEntry item in entries)
		{
			try
			{
				ZDO zDO = ZDOMan.instance.GetZDO(item.Id);
				if (zDO == null)
				{
					num2++;
					stringBuilder.AppendLine("Missing ZDO skipped: " + ((object)item.Id/*cast due to .constrained prefix*/).ToString());
				}
				else
				{
					MoveZdo(zDO, item.OldPosition);
					num++;
				}
			}
			catch (Exception ex)
			{
				num3++;
				LogWarning("Undo failed for " + ((object)item.Id/*cast due to .constrained prefix*/).ToString() + ": " + ex.Message);
			}
		}
		stringBuilder.AppendLine("Undo completed: " + num + "/" + entries.Count);
		if (num2 > 0)
		{
			stringBuilder.AppendLine("Missing: " + num2);
		}
		if (num3 > 0)
		{
			stringBuilder.AppendLine("Failed: " + num3);
		}
		return TrimEnd(stringBuilder);
	}

	private static List<TameRecord> ScanTamedCreatures(out string warning)
	{
		//IL_0050: Unknown result type (might be due to invalid IL or missing references)
		//IL_0055: Unknown result type (might be due to invalid IL or missing references)
		//IL_005c: Unknown result type (might be due to invalid IL or missing references)
		warning = null;
		if (ZDOMan.instance == null || (Object)(object)ZNetScene.instance == (Object)null)
		{
			warning = "World systems are not fully loaded.";
			return new List<TameRecord>();
		}
		LogInfo("Scanning world...");
		List<TameRecord> list = new List<TameRecord>();
		List<ZDOID> allZDOIDsWithHash = ZDOExtraData.GetAllZDOIDsWithHash((Type)3, ZDOVars.s_tamed);
		int num = 0;
		int num2 = 0;
		foreach (ZDOID item in allZDOIDsWithHash)
		{
			ZDO zDO = ZDOMan.instance.GetZDO(item);
			if (zDO == null || !zDO.GetBool(ZDOVars.s_tamed, false))
			{
				continue;
			}
			GameObject prefab = ZNetScene.instance.GetPrefab(zDO.GetPrefab());
			if ((Object)(object)prefab == (Object)null)
			{
				if (!_allowUnknownPrefabs.Value)
				{
					num++;
				}
				else
				{
					list.Add(TameRecord.FromUnknown(zDO));
				}
			}
			else if (!LooksLikeCreature(prefab))
			{
				num2++;
			}
			else
			{
				list.Add(TameRecord.FromZdo(zDO, prefab));
			}
		}
		LogInfo("Found " + list.Count + " tamed creatures.");
		if (num > 0 || num2 > 0)
		{
			warning = "Skipped " + num + " unknown-prefab ZDO and " + num2 + " non-creature ZDO.";
		}
		return list;
	}

	private static bool LooksLikeCreature(GameObject prefab)
	{
		if ((Object)(object)prefab == (Object)null)
		{
			return false;
		}
		if (!((Object)(object)prefab.GetComponent<Character>() != (Object)null) && !((Object)(object)prefab.GetComponent<Tameable>() != (Object)null) && !((Object)(object)prefab.GetComponent<Humanoid>() != (Object)null) && !((Object)(object)prefab.GetComponent<MonsterAI>() != (Object)null) && !((Object)(object)prefab.GetComponent<AnimalAI>() != (Object)null))
		{
			return (Object)(object)prefab.GetComponent<Fish>() != (Object)null;
		}
		return true;
	}

	private static bool TryResolveSelection(List<TameRecord> records, string query, out List<TameRecord> selected, out string message)
	{
		selected = new List<TameRecord>();
		message = null;
		if (string.IsNullOrWhiteSpace(query))
		{
			message = "Missing prefab name.";
			return false;
		}
		List<string> source = (from s in records.Select((TameRecord r) => r.PrefabName).Distinct<string>(StringComparer.OrdinalIgnoreCase)
			orderby s
			select s).ToList();
		string exact = source.FirstOrDefault((string n) => n.Equals(query, StringComparison.OrdinalIgnoreCase));
		if (exact != null)
		{
			selected = records.Where((TameRecord r) => r.PrefabName.Equals(exact, StringComparison.OrdinalIgnoreCase)).ToList();
			return true;
		}
		List<string> contains = source.Where((string n) => n.IndexOf(query, StringComparison.OrdinalIgnoreCase) >= 0).ToList();
		if (contains.Count == 0)
		{
			message = "No tamed prefab matches '" + query + "'. Use 'recalltames list'.";
			return false;
		}
		if (contains.Count > 1)
		{
			message = "Multiple matches:" + Environment.NewLine + string.Join(Environment.NewLine, contains) + Environment.NewLine + "Use exact prefab name.";
			return false;
		}
		selected = records.Where((TameRecord r) => r.PrefabName.Equals(contains[0], StringComparison.OrdinalIgnoreCase)).ToList();
		return true;
	}

	private static bool TryParseStarFilter(string[] tokens, int startIndex, out int? stars, out string message)
	{
		stars = null;
		message = null;
		for (int i = startIndex; i < tokens.Length; i++)
		{
			string text = tokens[i];
			if (IsCommandModifier(text) || text.Equals("all", StringComparison.OrdinalIgnoreCase))
			{
				continue;
			}
			int stars4;
			if (TryParseStarAssignment(text, out var stars2))
			{
				if (!TrySetStars(ref stars, stars2, out message))
				{
					return false;
				}
			}
			else if (IsStarKeyword(text))
			{
				if (i + 1 >= tokens.Length || !TryParseStarNumber(tokens[i + 1], out var stars3))
				{
					message = "Missing star count after '" + text + "'. Example: recalltames Boar stars 2";
					return false;
				}
				if (!TrySetStars(ref stars, stars3, out message))
				{
					return false;
				}
				i++;
			}
			else if (TryParseStarNumber(text, out stars4) && !TrySetStars(ref stars, stars4, out message))
			{
				return false;
			}
		}
		return true;
	}

	private static bool TrySetStars(ref int? stars, int value, out string message)
	{
		message = null;
		if (value < 0 || value > 100)
		{
			message = "Star count must be between 0 and 100.";
			return false;
		}
		if (stars.HasValue && stars.Value != value)
		{
			message = "Conflicting star filters: " + stars.Value + " and " + value + ".";
			return false;
		}
		stars = value;
		return true;
	}

	private static bool IsAllOrStarOnly(string[] tokens, int startIndex)
	{
		for (int i = startIndex; i < tokens.Length; i++)
		{
			string text = tokens[i];
			if (!text.Equals("all", StringComparison.OrdinalIgnoreCase) && !IsCommandModifier(text) && !IsStarKeyword(text) && !TryParseStarNumber(text, out var stars) && !TryParseStarAssignment(text, out stars))
			{
				return false;
			}
		}
		return true;
	}

	private static bool IsCommandModifier(string token)
	{
		if (!token.Equals("confirm", StringComparison.OrdinalIgnoreCase) && !token.Equals("force", StringComparison.OrdinalIgnoreCase))
		{
			return token.Equals("here", StringComparison.OrdinalIgnoreCase);
		}
		return true;
	}

	private static bool IsStarKeyword(string token)
	{
		if (!token.Equals("star", StringComparison.OrdinalIgnoreCase) && !token.Equals("stars", StringComparison.OrdinalIgnoreCase) && !token.Equals("gwiazdka", StringComparison.OrdinalIgnoreCase) && !token.Equals("gwiazdki", StringComparison.OrdinalIgnoreCase))
		{
			return token.Equals("gwiazdek", StringComparison.OrdinalIgnoreCase);
		}
		return true;
	}

	private static bool TryParseStarAssignment(string token, out int stars)
	{
		stars = 0;
		int num = token.IndexOf('=');
		if (num <= 0 || num == token.Length - 1)
		{
			return false;
		}
		string token2 = token.Substring(0, num);
		string token3 = token.Substring(num + 1);
		if (IsStarKeyword(token2))
		{
			return TryParseStarNumber(token3, out stars);
		}
		return false;
	}

	private static bool TryParseStarNumber(string token, out int stars)
	{
		if (int.TryParse(token, NumberStyles.Integer, CultureInfo.InvariantCulture, out stars))
		{
			return stars >= 0;
		}
		return false;
	}

	private static List<TameRecord> ApplyStarFilter(List<TameRecord> records, int? stars)
	{
		if (!stars.HasValue)
		{
			return records;
		}
		return records.Where((TameRecord r) => r.Stars == stars.Value).ToList();
	}

	private static string DescribeSelection(string label, int? stars)
	{
		if (!stars.HasValue)
		{
			return label;
		}
		return label + " with " + FormatStars(stars.Value);
	}

	private static string BuildConfirmCommand(string target, int? stars)
	{
		if (stars.HasValue)
		{
			return "recalltames " + target + " stars " + stars.Value + " confirm";
		}
		return "recalltames " + target + " confirm";
	}

	private static string FormatStarBreakdown(IEnumerable<TameRecord> records)
	{
		List<string> list = (from r in records
			group r by r.Stars into g
			orderby g.Key
			select FormatStars(g.Key) + ": " + g.Count()).ToList();
		if (list.Count != 0)
		{
			return " (" + string.Join(", ", list) + ")";
		}
		return "";
	}

	private static string FormatStars(int stars)
	{
		if (stars != 1)
		{
			return stars + " stars";
		}
		return "1 star";
	}

	private static List<MovePlan> BuildMovePlan(List<TameRecord> records, Vector3 origin, bool force, bool here)
	{
		//IL_011f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0120: Unknown result type (might be due to invalid IL or missing references)
		//IL_0122: Unknown result type (might be due to invalid IL or missing references)
		//IL_0127: Unknown result type (might be due to invalid IL or missing references)
		//IL_012b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0140: Unknown result type (might be due to invalid IL or missing references)
		//IL_0145: Unknown result type (might be due to invalid IL or missing references)
		List<MovePlan> list = new List<MovePlan>(records.Count);
		float num = Mathf.Max(1f, _creatureSpacing.Value);
		float num2 = Mathf.Max(1f, _startRadius.Value);
		float num3 = Mathf.Max(1f, _ringSpacing.Value);
		float num4 = Mathf.Max(num2, _maxRadius.Value);
		int num5 = 0;
		int num6 = 0;
		int ringCapacity = GetRingCapacity(num2, num);
		Vector3 val = default(Vector3);
		for (int i = 0; i < records.Count; i++)
		{
			TameRecord tameRecord = records[i];
			float spacing = Mathf.Max(num, tameRecord.Radius * 2.25f);
			float num7 = Mathf.Min(num4, num2 + (float)num6 * num3);
			ringCapacity = Math.Max(1, GetRingCapacity(num7, spacing));
			if (num5 >= ringCapacity)
			{
				num6++;
				num5 = 0;
				num7 = Mathf.Min(num4, num2 + (float)num6 * num3);
				ringCapacity = Math.Max(1, GetRingCapacity(num7, spacing));
			}
			float num8 = ((ringCapacity <= 1) ? 0f : (MathF.PI * 2f * (float)num5 / (float)ringCapacity));
			((Vector3)(ref val))..ctor(Mathf.Cos(num8) * num7, 0f, Mathf.Sin(num8) * num7);
			Vector3 val2 = origin + val;
			val2.y = ResolveTargetHeight(val2, tameRecord, force, here);
			list.Add(new MovePlan(tameRecord, tameRecord.Position, val2));
			num5++;
		}
		return list;
	}

	private static int GetRingCapacity(float radius, float spacing)
	{
		return Mathf.Max(1, Mathf.FloorToInt(MathF.PI * 2f * Mathf.Max(1f, radius) / Mathf.Max(1f, spacing)));
	}

	private static float ResolveTargetHeight(Vector3 target, TameRecord record, bool force, bool here)
	{
		//IL_0000: Unknown result type (might be due to invalid IL or missing references)
		//IL_0019: Unknown result type (might be due to invalid IL or missing references)
		//IL_0032: Unknown result type (might be due to invalid IL or missing references)
		float num = target.y;
		float num2 = default(float);
		if ((Object)(object)ZoneSystem.instance != (Object)null && ZoneSystem.instance.GetGroundHeight(target, ref num2))
		{
			num = num2;
		}
		if (record.IsWaterLike && here && !force)
		{
			float liquidLevel = Floating.GetLiquidLevel(target, 1f, (LiquidType)10);
			if (liquidLevel > -1000f)
			{
				return liquidLevel - Mathf.Max(0.25f, record.Radius * 0.25f);
			}
		}
		if (record.IsFlying)
		{
			return num + Mathf.Max(1f, _flyingHeight.Value);
		}
		return num + 0.1f;
	}

	private static bool IsWaterAt(Vector3 point)
	{
		//IL_000f: Unknown result type (might be due to invalid IL or missing references)
		//IL_001b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0024: Unknown result type (might be due to invalid IL or missing references)
		if ((Object)(object)ZoneSystem.instance == (Object)null)
		{
			return false;
		}
		float y = point.y;
		ZoneSystem.instance.GetGroundHeight(point, ref y);
		float liquidLevel = Floating.GetLiquidLevel(point, 1f, (LiquidType)10);
		if (liquidLevel > -1000f)
		{
			return liquidLevel > y + 0.25f;
		}
		return false;
	}

	private static void MoveZdo(ZDO zdo, Vector3 position)
	{
		//IL_0001: Unknown result type (might be due to invalid IL or missing references)
		//IL_0006: Unknown result type (might be due to invalid IL or missing references)
		//IL_0007: Unknown result type (might be due to invalid IL or missing references)
		//IL_000c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0023: Unknown result type (might be due to invalid IL or missing references)
		//IL_005c: Unknown result type (might be due to invalid IL or missing references)
		Vector3 val = zdo.GetPosition() - position;
		if (((Vector3)(ref val)).sqrMagnitude < 0.0001f)
		{
			return;
		}
		bool num = zdo.IsOwner();
		zdo.SetPosition(position);
		if (!num)
		{
			if (_increaseDataRevision != null)
			{
				_increaseDataRevision.Invoke(zdo, Array.Empty<object>());
			}
			else
			{
				ZDOMan.instance.SetDirtySector(zdo);
			}
		}
		ZDOMan.instance.ForceSendZDO(zdo.m_uid);
	}

	private static bool TryWriteBackup(List<MovePlan> plan, string operation, out string path, out string error)
	{
		//IL_015f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0164: Unknown result type (might be due to invalid IL or missing references)
		//IL_018f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0194: Unknown result type (might be due to invalid IL or missing references)
		//IL_01e6: Unknown result type (might be due to invalid IL or missing references)
		//IL_020d: Unknown result type (might be due to invalid IL or missing references)
		path = null;
		error = null;
		try
		{
			string text = Path.Combine(Paths.ConfigPath, "RecallTames", "backups");
			Directory.CreateDirectory(text);
			path = Path.Combine(text, "RecallTames_" + DateTime.Now.ToString("yyyy-MM-dd_HHmmss_fff", CultureInfo.InvariantCulture) + ".json");
			int num = 1;
			while (File.Exists(path))
			{
				path = Path.Combine(text, "RecallTames_" + DateTime.Now.ToString("yyyy-MM-dd_HHmmss_fff", CultureInfo.InvariantCulture) + "_" + num + ".json");
				num++;
			}
			StringBuilder stringBuilder = new StringBuilder();
			stringBuilder.AppendLine("{");
			stringBuilder.AppendLine("  \"schemaVersion\": 1,");
			stringBuilder.AppendLine("  \"mod\": \"RecallTames\",");
			stringBuilder.AppendLine("  \"operation\": \"" + JsonEscape(operation) + "\",");
			stringBuilder.AppendLine("  \"createdUtc\": \"" + DateTime.UtcNow.ToString("O", CultureInfo.InvariantCulture) + "\",");
			stringBuilder.AppendLine("  \"entries\": [");
			for (int i = 0; i < plan.Count; i++)
			{
				MovePlan movePlan = plan[i];
				stringBuilder.Append("    {");
				StringBuilder stringBuilder2 = stringBuilder.Append("\"zdoUserId\": ");
				ZDOID id = movePlan.Record.Id;
				stringBuilder2.Append(((ZDOID)(ref id)).UserID).Append(", ");
				StringBuilder stringBuilder3 = stringBuilder.Append("\"zdoId\": ");
				id = movePlan.Record.Id;
				stringBuilder3.Append(((ZDOID)(ref id)).ID).Append(", ");
				stringBuilder.Append("\"prefab\": \"").Append(JsonEscape(movePlan.Record.PrefabName)).Append("\", ");
				stringBuilder.Append("\"oldPosition\": ").Append(JsonVector(movePlan.OldPosition)).Append(", ");
				stringBuilder.Append("\"newPosition\": ").Append(JsonVector(movePlan.NewPosition)).Append(", ");
				stringBuilder.Append("\"tamedName\": \"").Append(JsonEscape(movePlan.Record.TamedName)).Append("\", ");
				stringBuilder.Append("\"level\": ").Append(movePlan.Record.Level);
				stringBuilder.Append((i == plan.Count - 1) ? "}" : "},");
				stringBuilder.AppendLine();
			}
			stringBuilder.AppendLine("  ]");
			stringBuilder.AppendLine("}");
			File.WriteAllText(path, stringBuilder.ToString(), Encoding.UTF8);
			return true;
		}
		catch (Exception ex)
		{
			error = ex.Message;
			LogError(ex);
			return false;
		}
	}

	private static bool TryReadLatestBackup(out List<BackupEntry> entries, out string path, out string error)
	{
		//IL_0186: Unknown result type (might be due to invalid IL or missing references)
		//IL_018d: Unknown result type (might be due to invalid IL or missing references)
		entries = new List<BackupEntry>();
		path = null;
		error = null;
		string text = Path.Combine(Paths.ConfigPath, "RecallTames", "backups");
		if (!Directory.Exists(text))
		{
			error = "No RecallTames backup directory found: " + text;
			return false;
		}
		FileInfo fileInfo = (from f in new DirectoryInfo(text).GetFiles("RecallTames_*.json")
			orderby f.LastWriteTimeUtc descending
			select f).FirstOrDefault();
		if (fileInfo == null)
		{
			error = "No RecallTames backup files found in: " + text;
			return false;
		}
		path = fileInfo.FullName;
		string input = File.ReadAllText(path, Encoding.UTF8);
		Vector3 oldPosition = default(Vector3);
		foreach (Match item in new Regex("\\{\\s*\"zdoUserId\"\\s*:\\s*(?<user>-?\\d+)\\s*,\\s*\"zdoId\"\\s*:\\s*(?<id>\\d+)\\s*,\\s*\"prefab\"\\s*:\\s*\"(?<prefab>(?:\\\\.|[^\"])*)\"\\s*,\\s*\"oldPosition\"\\s*:\\s*\\{\\s*\"x\"\\s*:\\s*(?<x>-?\\d+(?:\\.\\d+)?(?:[Ee][+-]?\\d+)?)\\s*,\\s*\"y\"\\s*:\\s*(?<y>-?\\d+(?:\\.\\d+)?(?:[Ee][+-]?\\d+)?)\\s*,\\s*\"z\"\\s*:\\s*(?<z>-?\\d+(?:\\.\\d+)?(?:[Ee][+-]?\\d+)?)", RegexOptions.Compiled | RegexOptions.CultureInvariant).Matches(input))
		{
			long num = long.Parse(item.Groups["user"].Value, CultureInfo.InvariantCulture);
			uint num2 = uint.Parse(item.Groups["id"].Value, CultureInfo.InvariantCulture);
			((Vector3)(ref oldPosition))..ctor(ParseFloat(item.Groups["x"].Value), ParseFloat(item.Groups["y"].Value), ParseFloat(item.Groups["z"].Value));
			string prefab = JsonUnescape(item.Groups["prefab"].Value);
			entries.Add(new BackupEntry(new ZDOID(num, num2), prefab, oldPosition));
		}
		if (entries.Count == 0)
		{
			error = "Latest backup could not be parsed or contains no entries: " + path;
			return false;
		}
		return true;
	}

	private static Vector3 GetLocalCommandOrigin(out bool hasOrigin)
	{
		//IL_001a: Unknown result type (might be due to invalid IL or missing references)
		//IL_004d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0032: Unknown result type (might be due to invalid IL or missing references)
		//IL_0037: Unknown result type (might be due to invalid IL or missing references)
		//IL_0038: Unknown result type (might be due to invalid IL or missing references)
		//IL_0039: Unknown result type (might be due to invalid IL or missing references)
		//IL_0048: Unknown result type (might be due to invalid IL or missing references)
		if ((Object)(object)Player.m_localPlayer != (Object)null)
		{
			hasOrigin = true;
			return ((Component)Player.m_localPlayer).transform.position;
		}
		if ((Object)(object)ZNet.instance != (Object)null)
		{
			Vector3 referencePosition = ZNet.instance.GetReferencePosition();
			if (referencePosition != Vector3.zero)
			{
				hasOrigin = true;
				return referencePosition;
			}
		}
		hasOrigin = false;
		return Vector3.zero;
	}

	private static Vector3 GetServerOriginForSender(long sender, Vector3 requestedOrigin)
	{
		//IL_0028: 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_0015: Unknown result type (might be due to invalid IL or missing references)
		//IL_0022: Unknown result type (might be due to invalid IL or missing references)
		ZNetPeer peer = ZNet.instance.GetPeer(sender);
		if (peer != null && peer.GetRefPos() != Vector3.zero)
		{
			return peer.GetRefPos();
		}
		return requestedOrigin;
	}

	private static bool IsSenderAuthorized(long sender, out string reason)
	{
		reason = null;
		if (!_requireAdmin.Value)
		{
			return true;
		}
		if ((Object)(object)ZNet.instance == (Object)null)
		{
			reason = "ZNet is not ready.";
			return false;
		}
		if (!ZNet.instance.IsServer())
		{
			if (ZNet.instance.LocalPlayerIsAdminOrHost())
			{
				return true;
			}
			reason = "You are not a Valheim admin.";
			return false;
		}
		if (sender == 0L || sender == ZNet.GetUID())
		{
			return true;
		}
		ZNetPeer peer = ZNet.instance.GetPeer(sender);
		object obj;
		if (peer == null)
		{
			obj = null;
		}
		else
		{
			ZRpc rpc = peer.m_rpc;
			if (rpc == null)
			{
				obj = null;
			}
			else
			{
				ISocket socket = rpc.GetSocket();
				obj = ((socket != null) ? socket.GetHostName() : null);
			}
		}
		string text = (string)obj;
		if (!string.IsNullOrWhiteSpace(text) && ZNet.instance.IsAdmin(text))
		{
			return true;
		}
		reason = "RecallTames denied: sender is not on the Valheim admin list.";
		return false;
	}

	private static bool IsWorldReady(out string reason)
	{
		reason = null;
		if ((Object)(object)ZNet.instance == (Object)null || ZDOMan.instance == null || (Object)(object)ZNetScene.instance == (Object)null)
		{
			reason = "World is not fully loaded yet.";
			return false;
		}
		return true;
	}

	private static List<string> GetAutocompleteOptions()
	{
		try
		{
			if (Time.time - _autocompleteCacheTime < 5f)
			{
				return _autocompleteCache;
			}
			List<string> list = new List<string>(BaseOptions);
			if (ZDOMan.instance != null && (Object)(object)ZNetScene.instance != (Object)null)
			{
				list.AddRange(from s in (from r in ScanTamedCreatures(out var _)
						select r.PrefabName).Distinct<string>(StringComparer.OrdinalIgnoreCase)
					orderby s
					select s);
			}
			_autocompleteCache = list;
			_autocompleteCacheTime = Time.time;
			return _autocompleteCache;
		}
		catch
		{
			return new List<string>(BaseOptions);
		}
	}

	private static void AppendScanHeader(StringBuilder sb, List<TameRecord> records, string warning, string label = null)
	{
		if (!string.IsNullOrWhiteSpace(warning))
		{
			sb.AppendLine(warning);
		}
		if (string.IsNullOrWhiteSpace(label) || label.Equals("all", StringComparison.OrdinalIgnoreCase))
		{
			sb.AppendLine("Tamed creatures found: " + records.Count);
		}
		else
		{
			sb.AppendLine("Tamed creatures found for " + label + ": " + records.Count);
		}
	}

	private static string HeaderOnly(List<TameRecord> records, string warning, string label = null)
	{
		StringBuilder sb = new StringBuilder();
		AppendScanHeader(sb, records, warning, label);
		return TrimEnd(sb);
	}

	private static bool HasToken(string[] tokens, string value)
	{
		return tokens.Any((string t) => t.Equals(value, StringComparison.OrdinalIgnoreCase));
	}

	private static bool IsHelpToken(string token)
	{
		if (!token.Equals("help", StringComparison.OrdinalIgnoreCase) && !token.Equals("-h", StringComparison.OrdinalIgnoreCase))
		{
			return token.Equals("--help", StringComparison.OrdinalIgnoreCase);
		}
		return true;
	}

	private static string[] Tokenize(string commandLine)
	{
		return (commandLine ?? string.Empty).Split(new char[4] { ' ', '\t', '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);
	}

	private static long NextRequestId()
	{
		return Interlocked.Increment(ref _nextRequestId);
	}

	private static void Print(Terminal context, string message)
	{
		if ((Object)(object)context != (Object)null)
		{
			context.AddString(message);
		}
		else
		{
			LogInfo(message);
		}
	}

	private static void LogInfo(string message)
	{
		if (_enableLogging == null || _enableLogging.Value)
		{
			ManualLogSource log = _log;
			if (log != null)
			{
				log.LogInfo((object)("[RecallTames] " + message));
			}
		}
	}

	private static void LogWarning(string message)
	{
		ManualLogSource log = _log;
		if (log != null)
		{
			log.LogWarning((object)("[RecallTames] " + message));
		}
	}

	private static void LogError(Exception ex)
	{
		ManualLogSource log = _log;
		if (log != null)
		{
			log.LogError((object)("[RecallTames] " + ex));
		}
	}

	private static string TrimEnd(StringBuilder sb)
	{
		return sb.ToString().TrimEnd('\r', '\n');
	}

	private static string Format(Vector3 vector)
	{
		//IL_0010: Unknown result type (might be due to invalid IL or missing references)
		//IL_0026: Unknown result type (might be due to invalid IL or missing references)
		//IL_003c: Unknown result type (might be due to invalid IL or missing references)
		return "(" + Format(vector.x) + ", " + Format(vector.y) + ", " + Format(vector.z) + ")";
	}

	private static string Format(float value)
	{
		return value.ToString("0.##", CultureInfo.InvariantCulture);
	}

	private static string JsonVector(Vector3 vector)
	{
		//IL_0010: Unknown result type (might be due to invalid IL or missing references)
		//IL_0026: Unknown result type (might be due to invalid IL or missing references)
		//IL_003c: Unknown result type (might be due to invalid IL or missing references)
		return "{\"x\": " + JsonFloat(vector.x) + ", \"y\": " + JsonFloat(vector.y) + ", \"z\": " + JsonFloat(vector.z) + "}";
	}

	private static string JsonFloat(float value)
	{
		return value.ToString("R", CultureInfo.InvariantCulture);
	}

	private static float ParseFloat(string value)
	{
		return float.Parse(value, NumberStyles.Float, CultureInfo.InvariantCulture);
	}

	private static string JsonEscape(string value)
	{
		if (string.IsNullOrEmpty(value))
		{
			return string.Empty;
		}
		return value.Replace("\\", "\\\\").Replace("\"", "\\\"");
	}

	private static string JsonUnescape(string value)
	{
		if (string.IsNullOrEmpty(value))
		{
			return string.Empty;
		}
		return value.Replace("\\\"", "\"").Replace("\\\\", "\\");
	}
}