AndrewLin.Echo.dll

Decompiled 6 days ago
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text;
using Alpha;
using Alpha.Core.Command;
using Alpha.Core.Util;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using Echo.Core;
using Echo.Core.Commands;
using Echo.Patches;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using PurrNet;
using TMPro;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("AndrewLin")]
[assembly: AssemblyConfiguration("Publish")]
[assembly: AssemblyDescription("Echo: A mod for On-Together with in-game utility commands including scheduled echoers (/echomein, /echolocalin, /echoglobalin), and more. Use /echohelp")]
[assembly: AssemblyFileVersion("0.2.5.0")]
[assembly: AssemblyInformationalVersion("0.2.5+4e0b7d4a1048390b61c91485bd217c1e7db83993")]
[assembly: AssemblyProduct("AndrewLin.Echo")]
[assembly: AssemblyTitle("AndrewLin.Echo")]
[assembly: AssemblyMetadata("RepositoryUrl", "https://github.com/andrewlimforfun/ot-mods")]
[assembly: InternalsVisibleTo("Echo.Tests")]
[assembly: AssemblyVersion("0.2.5.0")]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.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]
	[Microsoft.CodeAnalysis.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 Echo
{
	public static class BuildInfo
	{
		public const string Version = "0.2.5";
	}
	[BepInPlugin("com.andrewlin.ontogether.echo", "Echo", "0.2.5")]
	public class EchoPlugin : BaseUnityPlugin
	{
		public static readonly TokenValidator Validator = new TokenValidator("c7b7df644ec48a2ce66a51e05d084e177b0a01e6ecb77f9bd91808afe6668148");

		private static ManualLogSource? _logger;

		private static readonly ConcurrentQueue<Action> _mainThreadQueue = new ConcurrentQueue<Action>();

		public const string ModGUID = "com.andrewlin.ontogether.echo";

		public const string ModName = "Echo";

		public const string ModVersion = "0.2.5";

		private const string _locationsFileName = "com.andrewlin.ontogether.echo.locations.cfg";

		public static ConfigEntry<bool>? EnableFeature { get; private set; }

		public static ConfigEntry<string>? AccessToken { get; private set; }

		public static SavedLocationManager? Locations { get; private set; }

		public static void RunOnMainThread(Action action)
		{
			ConfigEntry<bool>? enableFeature = EnableFeature;
			if (enableFeature != null && enableFeature.Value)
			{
				_mainThreadQueue.Enqueue(action);
			}
		}

		private void Awake()
		{
			//IL_0043: Unknown result type (might be due to invalid IL or missing references)
			//IL_0049: Expected O, but got Unknown
			_logger = ((BaseUnityPlugin)this).Logger;
			((BaseUnityPlugin)this).Logger.LogInfo((object)"Echo v0.2.5 is loaded!");
			InitConfig();
			Locations = new SavedLocationManager(Path.Combine(Paths.ConfigPath, "com.andrewlin.ontogether.echo.locations.cfg"));
			Harmony val = new Harmony("com.andrewlin.ontogether.echo");
			val.PatchAll(typeof(PlayerMovementControllerPatch));
			ChatCommandManager commandManager = AlphaPlugin.CommandManager;
			if (commandManager != null)
			{
				commandManager.Register((IChatCommand)(object)new EchoToggleCommand());
			}
			ChatCommandManager commandManager2 = AlphaPlugin.CommandManager;
			if (commandManager2 != null)
			{
				commandManager2.Register((IChatCommand)(object)new EchoTeleportPersonCommand());
			}
			ChatCommandManager commandManager3 = AlphaPlugin.CommandManager;
			if (commandManager3 != null)
			{
				commandManager3.Register((IChatCommand)(object)new EchoTeleportLocationCommand());
			}
			ChatCommandManager commandManager4 = AlphaPlugin.CommandManager;
			if (commandManager4 != null)
			{
				commandManager4.Register((IChatCommand)(object)new EchoLocationListCommand());
			}
			ChatCommandManager commandManager5 = AlphaPlugin.CommandManager;
			if (commandManager5 != null)
			{
				commandManager5.Register((IChatCommand)(object)new EchoLocationRemoveCommand());
			}
			ChatCommandManager commandManager6 = AlphaPlugin.CommandManager;
			if (commandManager6 != null)
			{
				commandManager6.Register((IChatCommand)(object)new EchoFollowCommand());
			}
			ChatCommandManager commandManager7 = AlphaPlugin.CommandManager;
			if (commandManager7 != null)
			{
				commandManager7.Register((IChatCommand)(object)new EchoFollowRelativeCommand());
			}
			ChatCommandManager commandManager8 = AlphaPlugin.CommandManager;
			if (commandManager8 != null)
			{
				commandManager8.Register((IChatCommand)(object)new EchoUnfollowCommand());
			}
			ChatCommandManager commandManager9 = AlphaPlugin.CommandManager;
			if (commandManager9 != null)
			{
				commandManager9.Register((IChatCommand)(object)new EchoSyncRotationCommand());
			}
			ChatCommandManager commandManager10 = AlphaPlugin.CommandManager;
			if (commandManager10 != null)
			{
				commandManager10.Register((IChatCommand)(object)new EchoUnsyncRotationCommand());
			}
			ChatCommandManager commandManager11 = AlphaPlugin.CommandManager;
			if (commandManager11 != null)
			{
				commandManager11.Register((IChatCommand)(object)new EchoRotateCommand());
			}
			ChatCommandManager commandManager12 = AlphaPlugin.CommandManager;
			if (commandManager12 != null)
			{
				commandManager12.Register((IChatCommand)(object)new EchoRotateLockCommand());
			}
			ChatCommandManager commandManager13 = AlphaPlugin.CommandManager;
			if (commandManager13 != null)
			{
				commandManager13.Register((IChatCommand)(object)new EchoRotateUnlockCommand());
			}
			ChatCommandManager commandManager14 = AlphaPlugin.CommandManager;
			if (commandManager14 != null)
			{
				commandManager14.Register((IChatCommand)(object)new EchoLookAtCommand());
			}
			ChatCommandManager commandManager15 = AlphaPlugin.CommandManager;
			if (commandManager15 != null)
			{
				commandManager15.Register((IChatCommand)(object)new EchoCopyNameCommand());
			}
			ChatCommandManager commandManager16 = AlphaPlugin.CommandManager;
			if (commandManager16 != null)
			{
				commandManager16.Register((IChatCommand)(object)new EchoCopyNameStyleCommand());
			}
			ChatCommandManager commandManager17 = AlphaPlugin.CommandManager;
			if (commandManager17 != null)
			{
				commandManager17.Register((IChatCommand)(object)new EchoSetNameCommand());
			}
			ChatCommandManager commandManager18 = AlphaPlugin.CommandManager;
			if (commandManager18 != null)
			{
				commandManager18.Register((IChatCommand)(object)new EchoGetNameCommand());
			}
			ChatCommandManager commandManager19 = AlphaPlugin.CommandManager;
			if (commandManager19 != null)
			{
				commandManager19.Register((IChatCommand)(object)new EchoRevertNameCommand());
			}
			ChatCommandManager commandManager20 = AlphaPlugin.CommandManager;
			if (commandManager20 != null)
			{
				commandManager20.Register((IChatCommand)(object)new EchoCopyOutfitCommand());
			}
			ChatCommandManager commandManager21 = AlphaPlugin.CommandManager;
			if (commandManager21 != null)
			{
				commandManager21.Register((IChatCommand)(object)new EchoRemoveStatusCommand());
			}
		}

		private void InitConfig()
		{
			EnableFeature = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "EnableFeature", true, "Enable or disable the mod feature.");
			AccessToken = ((BaseUnityPlugin)this).Config.Bind<string>("Security", "AccessToken", "put_the_secret_token_here", "Token required to use name/outfit copy commands. Must match the secret embedded in the mod.");
		}

		private void Update()
		{
			ConfigEntry<bool>? enableFeature = EnableFeature;
			if (enableFeature == null || !enableFeature.Value)
			{
				return;
			}
			Action result;
			while (_mainThreadQueue.TryDequeue(out result))
			{
				try
				{
					result();
				}
				catch (Exception ex)
				{
					((BaseUnityPlugin)this).Logger.LogError((object)("Main thread action failed: " + ex.Message));
				}
			}
		}

		private void OnDestroy()
		{
		}
	}
	public static class MyPluginInfo
	{
		public const string PLUGIN_GUID = "AndrewLin.Echo";

		public const string PLUGIN_NAME = "AndrewLin.Echo";

		public const string PLUGIN_VERSION = "0.2.5";
	}
}
namespace Echo.Patches
{
	[HarmonyPatch(typeof(PlayerMovementController))]
	public static class PlayerMovementControllerPatch
	{
		private static readonly ManualLogSource Logger = Logger.CreateLogSource("Echo.PlayerMovementControllerPatch");

		public static Vector3 WarpPosition = Vector3.zero;

		private static Transform? _followTarget;

		private static Vector3 _followOffset;

		private static bool _followRelative;

		private static Transform? _rotationSyncTarget;

		private static Transform? _lookAtTarget;

		public static Quaternion? WarpRotation;

		public static Quaternion? LockedRotation;

		public static bool IsFollowing => (Object)(object)_followTarget != (Object)null;

		public static bool IsSyncingRotation => (Object)(object)_rotationSyncTarget != (Object)null;

		public static bool IsLookingAt => (Object)(object)_lookAtTarget != (Object)null;

		public static void StartFollowing(Transform target, Vector3 offset, bool relative = false)
		{
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_0008: Unknown result type (might be due to invalid IL or missing references)
			_followTarget = target;
			_followOffset = offset;
			_followRelative = relative;
		}

		public static void StopFollowing()
		{
			//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)
			_followTarget = null;
			_followOffset = Vector3.zero;
		}

		public static void StartSyncingRotation(Transform target)
		{
			_rotationSyncTarget = target;
		}

		public static void StopSyncingRotation()
		{
			_rotationSyncTarget = null;
		}

		public static void StartLookingAt(Transform target)
		{
			_lookAtTarget = target;
		}

		public static void StopLookingAt()
		{
			_lookAtTarget = null;
		}

		public static Vector3 GetLocalPlayerPosition()
		{
			//IL_001d: 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_0022: Unknown result type (might be due to invalid IL or missing references)
			//IL_0025: Unknown result type (might be due to invalid IL or missing references)
			TextChannelManager i = NetworkSingleton<TextChannelManager>.I;
			return ((Object)(object)i != (Object)null) ? i.MainPlayer.position : Vector3.zero;
		}

		[HarmonyPrefix]
		[HarmonyPatch("MovePlayer")]
		public static bool MovePlayer_Prefix(ref CharacterController ____characterController, PlayerMovementController __instance)
		{
			//IL_002a: Unknown result type (might be due to invalid IL or missing references)
			//IL_002f: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ba: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00dd: Unknown result type (might be due to invalid IL or missing references)
			//IL_0063: Unknown result type (might be due to invalid IL or missing references)
			//IL_0078: Unknown result type (might be due to invalid IL or missing references)
			//IL_008d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0092: Unknown result type (might be due to invalid IL or missing references)
			TextChannelManager i = NetworkSingleton<TextChannelManager>.I;
			if ((Object)(object)i == (Object)null || (Object)(object)__instance != (Object)(object)i.MainMovementController)
			{
				return true;
			}
			if (WarpPosition != Vector3.zero)
			{
				if (IsFollowing)
				{
					StopFollowing();
					ChatUtils.AddGlobalNotification("Stopped following (teleported).");
				}
				((Component)____characterController).transform.position = WarpPosition;
				Logger.LogInfo((object)$"Teleported to {WarpPosition}");
				WarpPosition = Vector3.zero;
				return false;
			}
			if (WarpRotation.HasValue)
			{
				((Component)__instance).transform.rotation = WarpRotation.Value;
				ManualLogSource logger = Logger;
				Quaternion value = WarpRotation.Value;
				logger.LogInfo((object)$"Rotated to {((Quaternion)(ref value)).eulerAngles}");
				WarpRotation = null;
			}
			if (_followTarget != null && (Object)(object)_followTarget != (Object)null)
			{
				return false;
			}
			return true;
		}

		[HarmonyPostfix]
		[HarmonyPatch("Update")]
		public static void Update_Postfix(PlayerMovementController __instance)
		{
			//IL_0152: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ce: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bd: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00df: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00eb: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f3: Unknown result type (might be due to invalid IL or missing references)
			//IL_0101: Unknown result type (might be due to invalid IL or missing references)
			if (_followTarget == null && _rotationSyncTarget == null && _lookAtTarget == null && !LockedRotation.HasValue)
			{
				return;
			}
			TextChannelManager i = NetworkSingleton<TextChannelManager>.I;
			if ((Object)(object)i == (Object)null || (Object)(object)__instance != (Object)(object)i.MainMovementController)
			{
				return;
			}
			if (_followTarget != null)
			{
				if ((Object)(object)_followTarget == (Object)null || (Object)(object)((Component)_followTarget).gameObject == (Object)null || !((Component)_followTarget).gameObject.activeInHierarchy)
				{
					StopFollowing();
					ChatUtils.AddGlobalNotification("Stopped following (target left).");
				}
				else
				{
					Vector3 val = (_followRelative ? (_followTarget.rotation * _followOffset) : _followOffset);
					Vector3 position = _followTarget.position + val;
					((Component)__instance).transform.position = position;
					i.MainPlayer.position = position;
				}
			}
			ApplyRotationSync(i.MainMovementController, __instance);
			ApplyLookAt(i.MainMovementController, __instance);
			if (LockedRotation.HasValue && (Object)(object)__instance == (Object)(object)i.MainMovementController)
			{
				((Component)__instance).transform.rotation = LockedRotation.Value;
			}
		}

		private static void ApplyRotationSync(PlayerMovementController mainCtrl, PlayerMovementController instance)
		{
			//IL_0070: Unknown result type (might be due to invalid IL or missing references)
			//IL_008c: Unknown result type (might be due to invalid IL or missing references)
			if (_rotationSyncTarget != null)
			{
				if ((Object)(object)_rotationSyncTarget == (Object)null || (Object)(object)((Component)_rotationSyncTarget).gameObject == (Object)null || !((Component)_rotationSyncTarget).gameObject.activeInHierarchy)
				{
					StopSyncingRotation();
					ChatUtils.AddGlobalNotification("Stopped syncing rotation (target left).");
				}
				else if (!((Object)(object)instance != (Object)(object)mainCtrl))
				{
					float y = _rotationSyncTarget.eulerAngles.y;
					((Component)instance).transform.rotation = Quaternion.Euler(0f, y, 0f);
				}
			}
		}

		private static void ApplyLookAt(PlayerMovementController mainCtrl, PlayerMovementController instance)
		{
			//IL_0070: Unknown result type (might be due to invalid IL or missing references)
			//IL_007b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0080: Unknown result type (might be due to invalid IL or missing references)
			//IL_0085: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ae: Unknown result type (might be due to invalid IL or missing references)
			//IL_00af: Unknown result type (might be due to invalid IL or missing references)
			if (_lookAtTarget == null)
			{
				return;
			}
			if ((Object)(object)_lookAtTarget == (Object)null || (Object)(object)((Component)_lookAtTarget).gameObject == (Object)null || !((Component)_lookAtTarget).gameObject.activeInHierarchy)
			{
				StopLookingAt();
				ChatUtils.AddGlobalNotification("Stopped looking at target (target left).");
			}
			else if (!((Object)(object)instance != (Object)(object)mainCtrl))
			{
				Vector3 val = _lookAtTarget.position - ((Component)instance).transform.position;
				val.y = 0f;
				if (!(((Vector3)(ref val)).sqrMagnitude < 0.01f))
				{
					((Component)instance).transform.rotation = Quaternion.LookRotation(val);
				}
			}
		}
	}
}
namespace Echo.Core
{
	public class SavedLocationManager
	{
		private readonly string _filePath;

		private readonly Dictionary<string, Vector3> _locations;

		public SavedLocationManager(string filePath)
		{
			_filePath = filePath;
			_locations = new Dictionary<string, Vector3>(StringComparer.OrdinalIgnoreCase);
			Load();
		}

		public void Save(string name, Vector3 pos)
		{
			//IL_0008: Unknown result type (might be due to invalid IL or missing references)
			_locations[name] = pos;
			Persist();
		}

		public bool TryGet(string name, out Vector3 pos)
		{
			return _locations.TryGetValue(name, out pos);
		}

		public bool Delete(string name)
		{
			if (!_locations.Remove(name))
			{
				return false;
			}
			Persist();
			return true;
		}

		public IReadOnlyDictionary<string, Vector3> GetAll()
		{
			return new ReadOnlyDictionary<string, Vector3>(_locations);
		}

		private void Load()
		{
			//IL_00d1: Unknown result type (might be due to invalid IL or missing references)
			if (!File.Exists(_filePath))
			{
				return;
			}
			string[] array = File.ReadAllLines(_filePath);
			foreach (string text in array)
			{
				int num = text.IndexOf('=');
				if (num >= 1)
				{
					string key = text.Substring(0, num).Trim();
					string[] array2 = text.Substring(num + 1).Split('|');
					if (array2.Length == 3 && float.TryParse(array2[0], NumberStyles.Float, CultureInfo.InvariantCulture, out var result) && float.TryParse(array2[1], NumberStyles.Float, CultureInfo.InvariantCulture, out var result2) && float.TryParse(array2[2], NumberStyles.Float, CultureInfo.InvariantCulture, out var result3))
					{
						_locations[key] = new Vector3(result, result2, result3);
					}
				}
			}
		}

		private void Persist()
		{
			//IL_0024: Unknown result type (might be due to invalid IL or missing references)
			//IL_0029: Unknown result type (might be due to invalid IL or missing references)
			//IL_003f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0044: Unknown result type (might be due to invalid IL or missing references)
			//IL_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)
			List<string> list = new List<string>();
			foreach (KeyValuePair<string, Vector3> location in _locations)
			{
				string text = location.Value.x.ToString(CultureInfo.InvariantCulture);
				string text2 = location.Value.y.ToString(CultureInfo.InvariantCulture);
				string text3 = location.Value.z.ToString(CultureInfo.InvariantCulture);
				list.Add(location.Key + "=" + text + "|" + text2 + "|" + text3);
			}
			File.WriteAllLines(_filePath, list);
		}
	}
	public static class MoveUtil
	{
		public static bool IsRotationLocked => PlayerMovementControllerPatch.LockedRotation.HasValue;

		public static void Teleport(Vector3 targetPosition)
		{
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			//IL_0021: Unknown result type (might be due to invalid IL or missing references)
			//IL_002c: Unknown result type (might be due to invalid IL or missing references)
			TextChannelManager i = NetworkSingleton<TextChannelManager>.I;
			if ((Object)(object)i == (Object)null)
			{
				ChatUtils.AddGlobalNotification("Teleport failed: not in a session.");
				return;
			}
			PlayerMovementControllerPatch.WarpPosition = targetPosition;
			i.MainPlayer.position = targetPosition;
		}

		public static void SetRotation(float pitch, float yaw, float roll)
		{
			//IL_0004: Unknown result type (might be due to invalid IL or missing references)
			PlayerMovementControllerPatch.WarpRotation = Quaternion.Euler(pitch, yaw, roll);
		}

		public static void LockRotation(float pitch, float yaw, float roll)
		{
			//IL_0004: Unknown result type (might be due to invalid IL or missing references)
			PlayerMovementControllerPatch.LockedRotation = Quaternion.Euler(pitch, yaw, roll);
		}

		public static void UnlockRotation()
		{
			PlayerMovementControllerPatch.LockedRotation = null;
		}

		public static void LookAt(Vector3 targetPosition)
		{
			//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_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_000e: Unknown result type (might be due to invalid IL or missing references)
			//IL_002f: 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)
			Vector3 localPlayerPosition = PlayerMovementControllerPatch.GetLocalPlayerPosition();
			Vector3 val = targetPosition - localPlayerPosition;
			val.y = 0f;
			if (!(((Vector3)(ref val)).sqrMagnitude < 0.01f))
			{
				PlayerMovementControllerPatch.WarpRotation = Quaternion.LookRotation(val);
			}
		}
	}
	internal static class NameStyleTransfer
	{
		internal readonly struct StyledSegment
		{
			public readonly string Tags;

			public readonly string Text;

			public StyledSegment(string tags, string text)
			{
				Tags = tags;
				Text = text;
			}
		}

		internal static List<StyledSegment> ParseSegments(string input)
		{
			List<StyledSegment> list = new List<StyledSegment>();
			StringBuilder stringBuilder = new StringBuilder();
			StringBuilder stringBuilder2 = new StringBuilder();
			int num = 0;
			while (num < input.Length)
			{
				if (input[num] == '<')
				{
					if (stringBuilder2.Length > 0)
					{
						list.Add(new StyledSegment(stringBuilder.ToString(), stringBuilder2.ToString()));
						stringBuilder.Clear();
						stringBuilder2.Clear();
					}
					int num2 = input.IndexOf('>', num);
					if (num2 >= 0)
					{
						stringBuilder.Append(input, num, num2 - num + 1);
						num = num2 + 1;
					}
					else
					{
						stringBuilder2.Append(input, num, input.Length - num);
						num = input.Length;
					}
				}
				else
				{
					stringBuilder2.Append(input[num]);
					num++;
				}
			}
			if (stringBuilder.Length > 0 || stringBuilder2.Length > 0)
			{
				list.Add(new StyledSegment(stringBuilder.ToString(), stringBuilder2.ToString()));
			}
			return list;
		}

		internal static string ApplyPositional(List<StyledSegment> segments, string myChars)
		{
			if (segments.Count == 0)
			{
				return myChars;
			}
			StringBuilder stringBuilder = new StringBuilder();
			int num = 0;
			for (int i = 0; i < segments.Count; i++)
			{
				stringBuilder.Append(segments[i].Tags);
				int length = segments[i].Text.Length;
				if (i == segments.Count - 1)
				{
					if (num < myChars.Length)
					{
						stringBuilder.Append(myChars, num, myChars.Length - num);
					}
					continue;
				}
				int num2 = Math.Min(num + length, myChars.Length);
				if (num < num2)
				{
					stringBuilder.Append(myChars, num, num2 - num);
				}
				num = num2;
			}
			return stringBuilder.ToString();
		}

		internal static string ApplyEven(List<StyledSegment> segments, string myChars)
		{
			if (segments.Count == 0)
			{
				return myChars;
			}
			int num = 0;
			foreach (StyledSegment segment in segments)
			{
				if (segment.Text.Length > 0)
				{
					num++;
				}
			}
			if (num == 0)
			{
				StringBuilder stringBuilder = new StringBuilder();
				foreach (StyledSegment segment2 in segments)
				{
					stringBuilder.Append(segment2.Tags);
				}
				stringBuilder.Append(myChars);
				return stringBuilder.ToString();
			}
			int num2 = myChars.Length / num;
			int num3 = myChars.Length % num;
			StringBuilder stringBuilder2 = new StringBuilder();
			int num4 = 0;
			int num5 = 0;
			foreach (StyledSegment segment3 in segments)
			{
				stringBuilder2.Append(segment3.Tags);
				if (segment3.Text.Length > 0)
				{
					int num6 = num2 + ((num5 < num3) ? 1 : 0);
					int num7 = Math.Min(num4 + num6, myChars.Length);
					if (num4 < num7)
					{
						stringBuilder2.Append(myChars, num4, num7 - num4);
					}
					num4 = num7;
					num5++;
				}
			}
			if (num4 < myChars.Length)
			{
				stringBuilder2.Append(myChars, num4, myChars.Length - num4);
			}
			return stringBuilder2.ToString();
		}

		internal static string ApplyEvenKeepNonAscii(List<StyledSegment> segments, string myChars)
		{
			if (segments.Count == 0)
			{
				return myChars;
			}
			int num = 0;
			foreach (StyledSegment segment in segments)
			{
				string text = segment.Text;
				foreach (char c in text)
				{
					if (c <= '\u007f')
					{
						num++;
					}
				}
			}
			if (num == 0)
			{
				StringBuilder stringBuilder = new StringBuilder();
				foreach (StyledSegment segment2 in segments)
				{
					stringBuilder.Append(segment2.Tags);
					stringBuilder.Append(segment2.Text);
				}
				stringBuilder.Append(myChars);
				return stringBuilder.ToString();
			}
			int num2 = 0;
			foreach (StyledSegment segment3 in segments)
			{
				string text2 = segment3.Text;
				foreach (char c2 in text2)
				{
					if (c2 <= '\u007f')
					{
						num2++;
						break;
					}
				}
			}
			int num3 = myChars.Length / num2;
			int num4 = myChars.Length % num2;
			StringBuilder stringBuilder2 = new StringBuilder();
			int num5 = 0;
			int num6 = 0;
			foreach (StyledSegment segment4 in segments)
			{
				stringBuilder2.Append(segment4.Tags);
				bool flag = false;
				string text3 = segment4.Text;
				foreach (char c3 in text3)
				{
					if (c3 <= '\u007f')
					{
						flag = true;
						break;
					}
				}
				if (!flag)
				{
					stringBuilder2.Append(segment4.Text);
					continue;
				}
				int num7 = num3 + ((num6 < num4) ? 1 : 0);
				int l = 0;
				string text4 = segment4.Text;
				foreach (char c4 in text4)
				{
					if (c4 > '\u007f')
					{
						stringBuilder2.Append(c4);
					}
					else if (l < num7 && num5 < myChars.Length)
					{
						stringBuilder2.Append(myChars[num5]);
						num5++;
						l++;
					}
				}
				for (; l < num7; l++)
				{
					if (num5 >= myChars.Length)
					{
						break;
					}
					stringBuilder2.Append(myChars[num5]);
					num5++;
				}
				num6++;
			}
			if (num5 < myChars.Length)
			{
				stringBuilder2.Append(myChars, num5, myChars.Length - num5);
			}
			return stringBuilder2.ToString();
		}
	}
	public static class NameUtil
	{
		private static readonly ManualLogSource _log = Logger.CreateLogSource("Echo.NU");

		private const string OfficerBallsStatusManagerGUID = "officerballs.StatusManager";

		public static string? OriginalName { get; private set; }

		public static string GetName()
		{
			DataManager i = MonoSingleton<DataManager>.I;
			if ((Object)(object)i == (Object)null)
			{
				_log.LogWarning((object)"GetName called before DataManager is available.");
				return OriginalName ?? "Player";
			}
			return i.PlayerData.Name;
		}

		public static void SetName(string newName, bool saveOriginal = true)
		{
			//IL_0111: Unknown result type (might be due to invalid IL or missing references)
			//IL_0118: Unknown result type (might be due to invalid IL or missing references)
			//IL_011e: Unknown result type (might be due to invalid IL or missing references)
			DataManager i = MonoSingleton<DataManager>.I;
			TextChannelManager i2 = NetworkSingleton<TextChannelManager>.I;
			UIManager i3 = MonoSingleton<UIManager>.I;
			MainSceneManager i4 = MonoSingleton<MainSceneManager>.I;
			if ((Object)(object)i == (Object)null || (Object)(object)i2 == (Object)null)
			{
				_log.LogWarning((object)"SetName called before DataManager/TextChannelManager are available.");
				return;
			}
			PluginInfo value;
			if (saveOriginal && OriginalName == null)
			{
				string text = null;
				if (Chainloader.PluginInfos.TryGetValue("officerballs.StatusManager", out value))
				{
					text = ((Chainloader.PluginInfos["officerballs.StatusManager"].Instance.Config["General", "PlayerName"] is ConfigEntry<string> val) ? val.Value : null);
				}
				if (!string.IsNullOrEmpty(text))
				{
					OriginalName = text;
				}
				else
				{
					OriginalName = i.PlayerData.Name;
				}
			}
			i.PlayerData.Name = newName;
			if ((Object)(object)i4 != (Object)null)
			{
				i2.MainCustomizationController.UpdatePlayerInfo(i.PlayerData.GetPlayerIdInfo(), default(RPCInfo));
				((TMP_Text)i3.PlayerText).text = newName;
				i2.UserName = newName;
				if (Chainloader.PluginInfos.TryGetValue("officerballs.StatusManager", out value))
				{
					ChatUtils.UISendMessage("/setname " + newName);
				}
			}
			_log.LogInfo((object)("Name set to: " + newName));
		}

		public static bool RevertName()
		{
			if (OriginalName == null)
			{
				return false;
			}
			string originalName = OriginalName;
			OriginalName = null;
			SetName(originalName, saveOriginal: false);
			return true;
		}
	}
}
namespace Echo.Core.Commands
{
	public class EchoCopyOutfitSelfCommand : IChatCommand, IComparable<IChatCommand>
	{
		private static ManualLogSource Logger = Logger.CreateLogSource("Echo.ECOSC");

		public string Name => "echocopyoutfitself";

		public string ShortName => "ecos";

		public string Description => "Copy your current outfit/appearance to another slot. Usage: /echocopyoutfit <slot> (1-3)";

		public string Namespace => "echo";

		public void Execute(string[] args)
		{
			if (args.Length != 1 || !int.TryParse(args[0], out var result) || result < 1 || result > 3)
			{
				ChatUtils.AddGlobalNotification("Usage: /echocopyoutfit <slot>  (slot = 1, 2, or 3)");
				return;
			}
			int num = result - 1;
			DataManager i = MonoSingleton<DataManager>.I;
			if ((Object)(object)i == (Object)null)
			{
				ChatUtils.AddGlobalNotification("DataManager not available.");
				return;
			}
			PlayerDataZip playerDataZip = i.PlayerDataZip;
			int selectedStyleIndex = playerDataZip.SelectedStyleIndex;
			if (num == selectedStyleIndex)
			{
				ChatUtils.AddGlobalNotification($"Slot {result} is already your active slot - nothing to copy.");
				return;
			}
			CustomizationDataIDs3 currentCustomizationDataIDs = playerDataZip.CurrentCustomizationDataIDs;
			playerDataZip.SelectedStyleIndex = num;
			playerDataZip.CurrentCustomizationDataIDs = currentCustomizationDataIDs;
			playerDataZip.SelectedStyleIndex = selectedStyleIndex;
			i.SavePlayerZipData();
			string slotName = GetSlotName(playerDataZip, selectedStyleIndex);
			string slotName2 = GetSlotName(playerDataZip, num);
			Logger.LogInfo((object)$"Copied outfit from slot {selectedStyleIndex + 1} ({slotName}) to slot {result} ({slotName2}).");
			ChatUtils.AddGlobalNotification($"Copied outfit from slot {selectedStyleIndex + 1} ({slotName}) → slot {result} ({slotName2}).");
		}

		private static string GetSlotName(PlayerDataZip pdz, int index)
		{
			if (pdz.StyleNames != null && index < pdz.StyleNames.Count)
			{
				return pdz.StyleNames[index];
			}
			return $"Slot {index + 1}";
		}
	}
	public class EchoCopyNameCommand : IChatCommand, IComparable<IChatCommand>
	{
		private static readonly ManualLogSource _log = Logger.CreateLogSource("Echo.ECONC");

		public string Name => "echocopyname";

		public string ShortName => "ecn";

		public string Description => "Copy another player's display name (with TMP tags). Usage: /echocopyname <name|steamid_suffix|persona> [status_brackets]";

		public string Namespace => "echo";

		public void Execute(string[] args)
		{
			if (!PlayerLists.IsAdmin(SteamUtils.GetPlayerSteamID()) && (EchoPlugin.AccessToken == null || !EchoPlugin.Validator.IsValid(EchoPlugin.AccessToken.Value.Trim())))
			{
				ChatUtils.AddGlobalNotification("Access denied: copy name has high abuse potential. Only Certified Users can use.");
				return;
			}
			if (args.Length == 0)
			{
				ChatUtils.AddGlobalNotification("Usage: /echocopyname <name|steamid_suffix|persona> [status_brackets]");
				return;
			}
			string text = string.Join(" ", args).Trim();
			PlayerDetail val = PlayerUtils.FindPlayerByQuery(text);
			if (val == null)
			{
				ChatUtils.AddGlobalNotification("Player \"" + text + "\" not found.");
				return;
			}
			string userName = val.UserName;
			string text2 = ChatUtils.CleanTMPTags(userName).Trim();
			_log.LogInfo((object)("Copying name from " + text2 + ": \"" + userName + "\""));
			NameUtil.SetName(userName);
			ChatUtils.AddGlobalNotification("Name copied from " + userName);
		}
	}
	public class EchoCopyNameStyleCommand : IChatCommand, IComparable<IChatCommand>
	{
		private static readonly ManualLogSource Logger = Logger.CreateLogSource("Echo.ECNSC");

		public string Name => "echocopynamestyle";

		public string ShortName => "ecns";

		public string Description => "Copy another player's name styling onto your name. Usage: /ecns [-s] <player>. -s to also copy non-ASCII chars as style.";

		public string Namespace => "echo";

		public void Execute(string[] args)
		{
			if (!PlayerLists.IsAdmin(SteamUtils.GetPlayerSteamID()) && (EchoPlugin.AccessToken == null || !EchoPlugin.Validator.IsValid(EchoPlugin.AccessToken.Value.Trim())))
			{
				ChatUtils.AddGlobalNotification("Access denied: copy name style has high abuse potential. Only Certified Users can use.");
				return;
			}
			if (args.Length == 0)
			{
				ChatUtils.AddGlobalNotification("Usage: /ecns [-s] <player>. -s to also copy non-ASCII chars as style.");
				return;
			}
			bool flag = false;
			int num = 0;
			if (args[0].Equals("-s", StringComparison.OrdinalIgnoreCase))
			{
				flag = true;
				num = 1;
			}
			if (num >= args.Length)
			{
				ChatUtils.AddGlobalNotification("Usage: /ecns [-s] <player>");
				return;
			}
			string text = string.Join(" ", args, num, args.Length - num).Trim();
			PlayerDetail val = PlayerUtils.FindPlayerByQuery(text);
			if (val == null)
			{
				ChatUtils.AddGlobalNotification("Player \"" + text + "\" not found.");
				return;
			}
			string userName = val.UserName;
			string text2 = ChatUtils.CleanTMPTags(NameUtil.GetName()).Trim();
			if (text2.Length == 0)
			{
				ChatUtils.AddGlobalNotification("Your name is empty after cleaning tags.");
				return;
			}
			List<NameStyleTransfer.StyledSegment> segments = NameStyleTransfer.ParseSegments(userName);
			string text3 = (flag ? NameStyleTransfer.ApplyEvenKeepNonAscii(segments, text2) : NameStyleTransfer.ApplyEven(segments, text2));
			Logger.LogInfo((object)("Style copied from " + val.UserNameClean + ": \"" + text3 + "\""));
			NameUtil.SetName(text3);
			ChatUtils.AddGlobalNotification("Name style copied from " + val.UserNameClean + ".");
		}
	}
	public class EchoCopyOutfitCommand : IChatCommand, IComparable<IChatCommand>
	{
		private static readonly ManualLogSource Logger = Logger.CreateLogSource("Echo.ECOC");

		private static readonly FieldInfo? CustomizationDataField = typeof(PlayerCustomizationController).GetField("_customizationData", BindingFlags.Instance | BindingFlags.NonPublic);

		public string Name => "echocopyoutfit";

		public string ShortName => "eco";

		public string Description => "Copy another player's current outfit. Usage: /echocopyoutfit <name|steamid_suffix|persona>";

		public string Namespace => "echo";

		public void Execute(string[] args)
		{
			//IL_00dc: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e2: Expected O, but got Unknown
			//IL_0117: Unknown result type (might be due to invalid IL or missing references)
			//IL_0121: Expected O, but got Unknown
			//IL_0135: Unknown result type (might be due to invalid IL or missing references)
			//IL_013f: Expected O, but got Unknown
			if (args.Length == 0)
			{
				ChatUtils.AddGlobalNotification("Usage: /echocopyoutfit <name|steamid_suffix|persona>");
				return;
			}
			string text = string.Join(" ", args).Trim();
			PlayerDetail val = PlayerUtils.FindPlayerByQuery(text);
			if (val == null)
			{
				ChatUtils.AddGlobalNotification("Player \"" + text + "\" not found.");
				return;
			}
			if (CustomizationDataField == null)
			{
				Logger.LogError((object)"Could not reflect _customizationData on PlayerCustomizationController.");
				ChatUtils.AddGlobalNotification("Reflection error: outfit field not found.");
				return;
			}
			PlayerCustomizationController component = ((Component)val.PlayerTransform).GetComponent<PlayerCustomizationController>();
			if ((Object)(object)component == (Object)null)
			{
				ChatUtils.AddGlobalNotification("Could not find customization controller for " + ChatUtils.CleanTMPTags(val.UserName).Trim() + ".");
				return;
			}
			CustomizationData val2 = (CustomizationData)CustomizationDataField.GetValue(component);
			PlayerCustomizationController val3 = NetworkSingleton<TextChannelManager>.I?.MainCustomizationController;
			if ((Object)(object)val3 == (Object)null)
			{
				ChatUtils.AddGlobalNotification("Local customization controller not available.");
				return;
			}
			MonoSingleton<DataManager>.I.CustomizationData = new CustomizationData(val2);
			MonoSingleton<DataManager>.I.PlayerDataZip.CurrentCustomizationDataIDs = new CustomizationDataIDs3(MonoSingleton<DataManager>.I.CustomizationData);
			val3.ApplyCustomization(val2);
			string text2 = ChatUtils.CleanTMPTags(val.UserName).Trim();
			Logger.LogInfo((object)("Copied outfit from " + text2 + "."));
			ChatUtils.AddGlobalNotification("Outfit copied from " + text2 + ".");
		}
	}
	public class EchoFollowCommand : IChatCommand, IComparable<IChatCommand>
	{
		private static readonly ManualLogSource Logger = Logger.CreateLogSource("Echo.EchoFollowCommand");

		public string Name => "echofollow";

		public string ShortName => "ef";

		public string Description => "Follow a player at a fixed offset. Usage: /ef <name|steamid> [x y z]";

		public string Namespace => "echo";

		public void Execute(string[] args)
		{
			//IL_0069: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cc: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00df: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f6: Unknown result type (might be due to invalid IL or missing references)
			//IL_0117: Unknown result type (might be due to invalid IL or missing references)
			//IL_012c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0150: Unknown result type (might be due to invalid IL or missing references)
			//IL_015f: Unknown result type (might be due to invalid IL or missing references)
			//IL_016e: Unknown result type (might be due to invalid IL or missing references)
			if (args.Length == 0)
			{
				ChatUtils.AddGlobalNotification("Usage: /ef <name|steamid> [x y z]");
				return;
			}
			Vector3? val = null;
			string text;
			if (args.Length >= 4 && float.TryParse(args[^3], out var result) && float.TryParse(args[^2], out var result2) && float.TryParse(args[^1], out var result3))
			{
				val = new Vector3(result, result2, result3);
				text = string.Join(" ", args, 0, args.Length - 3);
			}
			else
			{
				text = string.Join(" ", args);
			}
			text = text.Trim();
			PlayerDetail val2 = PlayerUtils.FindPlayerByQuery(text);
			if (val2 == null)
			{
				ChatUtils.AddGlobalNotification("Player \"" + text + "\" not found.");
				return;
			}
			Vector3 localPlayerPosition = PlayerMovementControllerPatch.GetLocalPlayerPosition();
			Vector3 val3 = (Vector3)(((??)val) ?? (localPlayerPosition - val2.Position));
			string text2 = ChatUtils.CleanTMPTags(val2.UserName).Trim();
			PlayerMovementControllerPatch.StartFollowing(((Component)val2.PlayerTransform).transform, val3);
			Logger.LogInfo((object)$"Following {text2} at offset {val3}");
			ChatUtils.AddGlobalNotification($"Following {text2} at offset ({val3.x:F1}, {val3.y:F1}, {val3.z:F1}). Use /eu to stop.");
		}
	}
	public class EchoFollowRelativeCommand : IChatCommand, IComparable<IChatCommand>
	{
		private static readonly ManualLogSource Logger = Logger.CreateLogSource("Echo.EchoFollowRelativeCommand");

		private const float DefaultDistance = 1.5f;

		public string Name => "echofollowrelative";

		public string ShortName => "efr";

		public string Description => "Follow a player relative to their facing. Usage: /efr <player> [behind|beside|front|x y z]";

		public string Namespace => "echo";

		public void Execute(string[] args)
		{
			//IL_014c: 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_0183: Unknown result type (might be due to invalid IL or missing references)
			//IL_0191: Unknown result type (might be due to invalid IL or missing references)
			//IL_019f: Unknown result type (might be due to invalid IL or missing references)
			if (args.Length == 0)
			{
				ChatUtils.AddGlobalNotification("Usage: /efr <player> [behind|beside|front|x y z]");
				return;
			}
			Vector3 offset = default(Vector3);
			string text;
			if (args.Length >= 4 && float.TryParse(args[^3], out var result) && float.TryParse(args[^2], out var result2) && float.TryParse(args[^1], out var result3))
			{
				((Vector3)(ref offset))..ctor(result, result2, result3);
				text = string.Join(" ", args, 0, args.Length - 3).Trim();
			}
			else if (args.Length >= 2 && TryParsePreset(args[^1], out offset))
			{
				text = string.Join(" ", args, 0, args.Length - 1).Trim();
			}
			else
			{
				((Vector3)(ref offset))..ctor(0f, 0f, -1.5f);
				text = string.Join(" ", args).Trim();
			}
			if (string.IsNullOrEmpty(text))
			{
				ChatUtils.AddGlobalNotification("Usage: /efr <player> [behind|beside|front|x y z]");
				return;
			}
			PlayerDetail val = PlayerUtils.FindPlayerByQuery(text);
			if (val == null)
			{
				ChatUtils.AddGlobalNotification("Player \"" + text + "\" not found.");
				return;
			}
			string text2 = ChatUtils.CleanTMPTags(val.UserName).Trim();
			PlayerMovementControllerPatch.StartFollowing(((Component)val.PlayerTransform).transform, offset, relative: true);
			Logger.LogInfo((object)$"Following {text2} (relative) at offset {offset}");
			ChatUtils.AddGlobalNotification($"Following {text2} (relative) at ({offset.x:F1}, {offset.y:F1}, {offset.z:F1}). Use /eu to stop.");
		}

		private static bool TryParsePreset(string s, out Vector3 offset)
		{
			//IL_011f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0124: Unknown result type (might be due to invalid IL or missing references)
			//IL_0101: Unknown result type (might be due to invalid IL or missing references)
			//IL_0106: Unknown result type (might be due to invalid IL or missing references)
			//IL_013d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0142: Unknown result type (might be due to invalid IL or missing references)
			//IL_016a: Unknown result type (might be due to invalid IL or missing references)
			//IL_016f: Unknown result type (might be due to invalid IL or missing references)
			//IL_015b: 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)
			switch (s.ToLowerInvariant())
			{
			case "behind":
			case "back":
				offset = new Vector3(0f, 0f, -1.5f);
				return true;
			case "beside":
			case "left":
				offset = new Vector3(-1.5f, 0f, 0f);
				return true;
			case "right":
				offset = new Vector3(1.5f, 0f, 0f);
				return true;
			case "front":
			case "ahead":
				offset = new Vector3(0f, 0f, 1.5f);
				return true;
			default:
				offset = Vector3.zero;
				return false;
			}
		}
	}
	public class EchoGetNameCommand : IChatCommand, IComparable<IChatCommand>
	{
		private static readonly ManualLogSource _log = Logger.CreateLogSource("Echo.ECONC");

		public string Name => "echogetname";

		public string ShortName => "egn";

		public string Description => "Copy another player's display name (with TMP tags). Usage: /echogetname <name|steamid_suffix|persona> [status_brackets]";

		public string Namespace => "echo";

		public void Execute(string[] args)
		{
			if (!PlayerLists.IsAdmin(SteamUtils.GetPlayerSteamID()) && (EchoPlugin.AccessToken == null || !EchoPlugin.Validator.IsValid(EchoPlugin.AccessToken.Value.Trim())))
			{
				ChatUtils.AddGlobalNotification("Access denied: get name has high abuse potential. Only Certified Users can use.");
				return;
			}
			if (args.Length == 0)
			{
				ChatUtils.AddGlobalNotification("Usage: /echogetname <name|steamid_suffix|persona> [status_brackets]");
				return;
			}
			string text = string.Join(" ", args).Trim();
			PlayerDetail val = PlayerUtils.FindPlayerByQuery(text);
			if (val == null)
			{
				ChatUtils.AddGlobalNotification("Player \"" + text + "\" not found.");
				return;
			}
			string userName = val.UserName;
			string userNameClean = val.UserNameClean;
			_log.LogInfo((object)("Name of " + userNameClean + ": \"" + userName + "\""));
			ChatUtils.AddGlobalNotification("Name of " + userNameClean + ": " + userName);
		}
	}
	public class EchoLocationListCommand : IChatCommand, IComparable<IChatCommand>
	{
		public string Name => "echolocationlist";

		public string ShortName => "ell";

		public string Description => "List all saved locations and their coordinates. Usage: /echolocationlist";

		public string Namespace => "echo";

		public void Execute(string[] args)
		{
			//IL_0097: 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_00bf: Unknown result type (might be due to invalid IL or missing references)
			SavedLocationManager locations = EchoPlugin.Locations;
			if (locations == null)
			{
				ChatUtils.AddGlobalNotification("Location manager not initialized.");
				return;
			}
			IReadOnlyDictionary<string, Vector3> all = locations.GetAll();
			if (all.Count == 0)
			{
				ChatUtils.AddGlobalNotification("No saved locations.");
				return;
			}
			StringBuilder stringBuilder = new StringBuilder();
			stringBuilder.AppendLine($"Saved locations ({all.Count}):");
			foreach (KeyValuePair<string, Vector3> item in all)
			{
				stringBuilder.AppendLine($"  {item.Key}: ({item.Value.x:F1}, {item.Value.y:F1}, {item.Value.z:F1})");
			}
			ChatUtils.AddGlobalNotification(stringBuilder.ToString().TrimEnd());
		}
	}
	public class EchoLocationRemoveCommand : IChatCommand, IComparable<IChatCommand>
	{
		public string Name => "echolocationremove";

		public string ShortName => "elr";

		public string Description => "Remove a saved location. Usage: /echolocationremove <name>";

		public string Namespace => "echo";

		public void Execute(string[] args)
		{
			if (args.Length == 0)
			{
				ChatUtils.AddGlobalNotification("Usage: /echolocationremove <name>");
				return;
			}
			SavedLocationManager locations = EchoPlugin.Locations;
			if (locations == null)
			{
				ChatUtils.AddGlobalNotification("Location manager not initialized.");
				return;
			}
			string text = string.Join(" ", args).Trim();
			if (locations.Delete(text))
			{
				ChatUtils.AddGlobalNotification("Removed location \"" + text + "\".");
			}
			else
			{
				ChatUtils.AddGlobalNotification("No saved location named \"" + text + "\".");
			}
		}
	}
	public class EchoLookAtCommand : IChatCommand, IComparable<IChatCommand>
	{
		private static readonly ManualLogSource Logger = Logger.CreateLogSource("Echo.EchoLookAtCommand");

		public string Name => "echolookat";

		public string ShortName => "ela";

		public string Description => "Continuously face toward a player. Usage: /ela <name|steamid>. Stop with /eru.";

		public string Namespace => "echo";

		public void Execute(string[] args)
		{
			if (args.Length == 0)
			{
				ChatUtils.AddGlobalNotification("Usage: /ela <name|steamid>. Stop with /eru.");
				return;
			}
			string text = string.Join(" ", args).Trim();
			PlayerDetail val = PlayerUtils.FindPlayerByQuery(text);
			if (val == null)
			{
				ChatUtils.AddGlobalNotification("Player \"" + text + "\" not found.");
				return;
			}
			string text2 = ChatUtils.CleanTMPTags(val.UserName).Trim();
			PlayerMovementControllerPatch.StartLookingAt(((Component)val.PlayerTransform).transform);
			Logger.LogInfo((object)("Looking at " + text2 + "."));
			ChatUtils.AddGlobalNotification("Facing toward " + text2 + ". Use /eru to stop.");
		}
	}
	public class EchoRemoveStatusCommand : IChatCommand, IComparable<IChatCommand>
	{
		private static readonly ManualLogSource _log = Logger.CreateLogSource("Echo.ERSC");

		public string Name => "echoremovestatus";

		public string ShortName => "ers";

		public string Description => "Remove status from my player's display name. Usage: /echoremovestatus <status_brackets>";

		public string Namespace => "echo";

		public void Execute(string[] args)
		{
			if (EchoPlugin.AccessToken == null || !EchoPlugin.Validator.IsValid(EchoPlugin.AccessToken.Value.Trim()))
			{
				ChatUtils.AddGlobalNotification("Access denied: remove status has high abuse potential. Only Certified Users can use.");
				return;
			}
			if (args.Length == 0)
			{
				ChatUtils.AddGlobalNotification("Usage: /echoremovestatus <status_brackets>");
				return;
			}
			if (args.Length == 2)
			{
				ChatUtils.AddGlobalNotification("Please provide only one argument with the brackets to remove. Usage: /echoremovestatus <status_brackets>");
				return;
			}
			string text = args[0].Trim();
			string text2 = NameUtil.GetName();
			if (text.Length == 1)
			{
				text2 = text2[..text2.LastIndexOf(text[0])];
			}
			else if (text.Length > 1)
			{
				char bracketStart = text[0];
				char bracketEnd = text[text.Length - 1];
				text2 = RemoveLastBracketedSection(text2, bracketStart, bracketEnd);
			}
			NameUtil.SetName(text2, saveOriginal: false);
			ChatUtils.AddGlobalNotification("Status removed from " + text2);
		}

		public static string RemoveLastBracketedSection(string name, char bracketStart, char bracketEnd)
		{
			int num = name.LastIndexOf(bracketEnd);
			if (num == -1)
			{
				return name;
			}
			int num2 = name.LastIndexOf(bracketStart, num);
			if (num2 == -1)
			{
				return name;
			}
			return name.Remove(num2, num - num2 + 1).TrimEnd();
		}
	}
	public class EchoRevertNameCommand : IChatCommand, IComparable<IChatCommand>
	{
		public string Name => "echorevertname";

		public string ShortName => "ern";

		public string Description => "Revert your display name to the original before any Echo rename.";

		public string Namespace => "echo";

		public void Execute(string[] args)
		{
			if (NameUtil.OriginalName == null)
			{
				ChatUtils.AddGlobalNotification("No saved name to revert to.");
				return;
			}
			string originalName = NameUtil.OriginalName;
			NameUtil.RevertName();
			string text = ChatUtils.CleanTMPTags(originalName).Trim();
			ChatUtils.AddGlobalNotification("Name reverted to: " + text);
		}
	}
	public class EchoRotateCommand : IChatCommand, IComparable<IChatCommand>
	{
		private static readonly ManualLogSource Logger = Logger.CreateLogSource("Echo.EchoRotateCommand");

		public string Name => "echorotate";

		public string ShortName => "er";

		public string Description => "Set facing direction. Usage: /er <degrees|pitch yaw roll|cardinal|player>";

		public string Namespace => "echo";

		public void Execute(string[] args)
		{
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			//IL_002c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0033: Unknown result type (might be due to invalid IL or missing references)
			//IL_003f: Unknown result type (might be due to invalid IL or missing references)
			//IL_004b: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d5: Unknown result type (might be due to invalid IL or missing references)
			if (args.Length == 0)
			{
				PlayerDetail playerDetail = PlayerUtils.GetPlayerDetail();
				Vector3 val = ((playerDetail != null) ? ((Component)playerDetail.PlayerTransform).transform.eulerAngles : Vector3.zero);
				ChatUtils.AddGlobalNotification($"Current rotation: ({val.x:F1}, {val.y:F1}, {val.z:F1})");
				return;
			}
			if (args.Length == 3 && float.TryParse(args[0], out var result) && float.TryParse(args[1], out var result2) && float.TryParse(args[2], out var result3))
			{
				MoveUtil.SetRotation(result, result2, result3);
				ChatUtils.AddGlobalNotification($"Rotation set to ({result}, {result2}, {result3}).");
				Logger.LogInfo((object)$"One-shot rotation to ({result}, {result2}, {result3}).");
				return;
			}
			string text = string.Join(" ", args).Trim();
			if (TryParseCardinal(text, out var angle))
			{
				MoveUtil.SetRotation(0f, angle, 0f);
				ChatUtils.AddGlobalNotification($"Facing {text} ({angle} degrees).");
				Logger.LogInfo((object)$"One-shot rotation to cardinal {text} ({angle}).");
				return;
			}
			if (float.TryParse(text, out var result4))
			{
				MoveUtil.SetRotation(0f, result4, 0f);
				ChatUtils.AddGlobalNotification($"Facing {result4} degrees.");
				Logger.LogInfo((object)$"One-shot rotation to yaw {result4}.");
				return;
			}
			PlayerDetail val2 = PlayerUtils.FindPlayerByQuery(text);
			if (val2 != null)
			{
				MoveUtil.LookAt(((Component)val2.PlayerTransform).transform.position);
				string text2 = ChatUtils.CleanTMPTags(val2.UserName).Trim();
				ChatUtils.AddGlobalNotification("Facing toward " + text2 + ".");
				Logger.LogInfo((object)("One-shot rotation toward player " + text2 + "."));
			}
			else
			{
				ChatUtils.AddGlobalNotification("Unknown target \"" + text + "\". Use degrees, cardinal, or player name.");
			}
		}

		private static bool TryParseCardinal(string s, out float angle)
		{
			string text = s.ToLowerInvariant();
			if (1 == 0)
			{
			}
			float num = text switch
			{
				"north" => 0f, 
				"n" => 0f, 
				"east" => 90f, 
				"e" => 90f, 
				"south" => 180f, 
				"s" => 180f, 
				"west" => 270f, 
				"w" => 270f, 
				"northeast" => 45f, 
				"ne" => 45f, 
				"southeast" => 135f, 
				"se" => 135f, 
				"southwest" => 225f, 
				"sw" => 225f, 
				"northwest" => 315f, 
				"nw" => 315f, 
				_ => -1f, 
			};
			if (1 == 0)
			{
			}
			angle = num;
			return angle >= 0f;
		}
	}
	public class EchoRotateLockCommand : IChatCommand, IComparable<IChatCommand>
	{
		private static readonly ManualLogSource Logger = Logger.CreateLogSource("Echo.EchoRotateLockCommand");

		public string Name => "echorotatelock";

		public string ShortName => "erl";

		public string Description => "Lock rotation to a fixed value. Usage: /erl <degrees|pitch yaw roll|cardinal|player>. Unlock with /eru.";

		public string Namespace => "echo";

		public void Execute(string[] args)
		{
			//IL_0182: Unknown result type (might be due to invalid IL or missing references)
			//IL_0187: Unknown result type (might be due to invalid IL or missing references)
			//IL_0195: Unknown result type (might be due to invalid IL or missing references)
			//IL_019a: Unknown result type (might be due to invalid IL or missing references)
			//IL_019c: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a1: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d4: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d6: Unknown result type (might be due to invalid IL or missing references)
			//IL_01db: Unknown result type (might be due to invalid IL or missing references)
			//IL_01df: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e4: 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_01ed: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f4: Unknown result type (might be due to invalid IL or missing references)
			if (args.Length == 0)
			{
				ChatUtils.AddGlobalNotification("Usage: /erl <degrees|pitch yaw roll|cardinal|player>. Unlock with /eru.");
				return;
			}
			if (args.Length == 3 && float.TryParse(args[0], out var result) && float.TryParse(args[1], out var result2) && float.TryParse(args[2], out var result3))
			{
				MoveUtil.LockRotation(result, result2, result3);
				ChatUtils.AddGlobalNotification($"Rotation locked to ({result}, {result2}, {result3}). Use /eru to unlock.");
				Logger.LogInfo((object)$"Rotation locked to ({result}, {result2}, {result3}).");
				return;
			}
			string text = string.Join(" ", args).Trim();
			if (TryParseCardinal(text, out var angle))
			{
				MoveUtil.LockRotation(0f, angle, 0f);
				ChatUtils.AddGlobalNotification($"Rotation locked to {text} ({angle} degrees). Use /eru to unlock.");
				Logger.LogInfo((object)$"Rotation locked to cardinal {text} ({angle}).");
				return;
			}
			if (float.TryParse(text, out var result4))
			{
				MoveUtil.LockRotation(0f, result4, 0f);
				ChatUtils.AddGlobalNotification($"Rotation locked to {result4} degrees. Use /eru to unlock.");
				Logger.LogInfo((object)$"Rotation locked to yaw {result4}.");
				return;
			}
			PlayerDetail val = PlayerUtils.FindPlayerByQuery(text);
			if (val != null)
			{
				Vector3 localPlayerPosition = PlayerMovementControllerPatch.GetLocalPlayerPosition();
				Vector3 val2 = ((Component)val.PlayerTransform).transform.position - localPlayerPosition;
				val2.y = 0f;
				if (((Vector3)(ref val2)).sqrMagnitude < 0.01f)
				{
					ChatUtils.AddGlobalNotification("Too close to target to determine direction.");
					return;
				}
				Quaternion val3 = Quaternion.LookRotation(val2);
				Vector3 eulerAngles = ((Quaternion)(ref val3)).eulerAngles;
				MoveUtil.LockRotation(eulerAngles.x, eulerAngles.y, eulerAngles.z);
				string text2 = ChatUtils.CleanTMPTags(val.UserName).Trim();
				ChatUtils.AddGlobalNotification("Rotation locked toward " + text2 + ". Use /eru to unlock.");
				Logger.LogInfo((object)("Rotation locked toward player " + text2 + "."));
			}
			else
			{
				ChatUtils.AddGlobalNotification("Unknown target \"" + text + "\". Use degrees, cardinal, or player name.");
			}
		}

		private static bool TryParseCardinal(string s, out float angle)
		{
			string text = s.ToLowerInvariant();
			if (1 == 0)
			{
			}
			float num = text switch
			{
				"north" => 0f, 
				"n" => 0f, 
				"east" => 90f, 
				"e" => 90f, 
				"south" => 180f, 
				"s" => 180f, 
				"west" => 270f, 
				"w" => 270f, 
				"northeast" => 45f, 
				"ne" => 45f, 
				"southeast" => 135f, 
				"se" => 135f, 
				"southwest" => 225f, 
				"sw" => 225f, 
				"northwest" => 315f, 
				"nw" => 315f, 
				_ => -1f, 
			};
			if (1 == 0)
			{
			}
			angle = num;
			return angle >= 0f;
		}
	}
	public class EchoRotateUnlockCommand : IChatCommand, IComparable<IChatCommand>
	{
		public string Name => "echorotateunlock";

		public string ShortName => "eru";

		public string Description => "Stop all rotation overrides (lock, look-at).";

		public string Namespace => "echo";

		public void Execute(string[] args)
		{
			bool isRotationLocked = MoveUtil.IsRotationLocked;
			bool isLookingAt = PlayerMovementControllerPatch.IsLookingAt;
			if (!isRotationLocked && !isLookingAt)
			{
				ChatUtils.AddGlobalNotification("No rotation override active.");
				return;
			}
			MoveUtil.UnlockRotation();
			PlayerMovementControllerPatch.StopLookingAt();
			ChatUtils.AddGlobalNotification("Rotation unlocked.");
		}
	}
	public class EchoSetNameCommand : IChatCommand, IComparable<IChatCommand>
	{
		private static readonly ManualLogSource Logger = Logger.CreateLogSource("Echo.EchoSetNameCommand");

		public string Name => "echosetname";

		public string ShortName => "esn";

		public string Description => "Change name text while keeping current style. Usage: /esn <new name>";

		public string Namespace => "echo";

		public void Execute(string[] args)
		{
			if (args.Length == 0)
			{
				ChatUtils.AddGlobalNotification("Usage: /esn <new name>");
				return;
			}
			string text = string.Join(" ", args).Trim();
			if (text.Length == 0)
			{
				ChatUtils.AddGlobalNotification("Name cannot be empty.");
				return;
			}
			string name = NameUtil.GetName();
			List<NameStyleTransfer.StyledSegment> list = NameStyleTransfer.ParseSegments(name);
			bool flag = false;
			foreach (NameStyleTransfer.StyledSegment item in list)
			{
				if (item.Tags.Length > 0)
				{
					flag = true;
					break;
				}
			}
			string text2 = (flag ? NameStyleTransfer.ApplyEven(list, text) : text);
			Logger.LogInfo((object)("Set name to \"" + text2 + "\" (preserving style)."));
			NameUtil.SetName(text2);
			ChatUtils.AddGlobalNotification("Name set to \"" + ChatUtils.CleanTMPTags(text2) + "\" with existing style.");
		}
	}
	public class EchoSyncRotationCommand : IChatCommand, IComparable<IChatCommand>
	{
		private static readonly ManualLogSource Logger = Logger.CreateLogSource("Echo.EchoSyncRotationCommand");

		public string Name => "echosyncrotation";

		public string ShortName => "esr";

		public string Description => "Mirror another player's facing direction. Usage: /esr <name|steamid>. Stop with /eur.";

		public string Namespace => "echo";

		public void Execute(string[] args)
		{
			if (args.Length == 0)
			{
				ChatUtils.AddGlobalNotification("Usage: /esr <name|steamid>. Stop with /eur.");
				return;
			}
			string text = string.Join(" ", args).Trim();
			PlayerDetail val = PlayerUtils.FindPlayerByQuery(text);
			if (val == null)
			{
				ChatUtils.AddGlobalNotification("Player \"" + text + "\" not found.");
				return;
			}
			string text2 = ChatUtils.CleanTMPTags(val.UserName).Trim();
			PlayerMovementControllerPatch.StartSyncingRotation(((Component)val.PlayerTransform).transform);
			Logger.LogInfo((object)("Syncing rotation to " + text2 + "."));
			ChatUtils.AddGlobalNotification("Syncing rotation to " + text2 + ". Use /eur to stop.");
		}
	}
	public class EchoTeleportLocationCommand : IChatCommand, IComparable<IChatCommand>
	{
		private static readonly ManualLogSource _log = Logger.CreateLogSource("Echo.ETL");

		public string Name => "echoteleportlocation";

		public string ShortName => "etl";

		public string Description => "Teleport to a saved location or save current spot. Usage: /etl <name> | /etl <x> <y> <z>";

		public string Namespace => "echo";

		public void Execute(string[] args)
		{
			//IL_0059: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f4: Unknown result type (might be due to invalid IL or missing references)
			//IL_011d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0143: Unknown result type (might be due to invalid IL or missing references)
			//IL_013c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0148: Unknown result type (might be due to invalid IL or missing references)
			//IL_014d: Unknown result type (might be due to invalid IL or missing references)
			//IL_015b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0178: Unknown result type (might be due to invalid IL or missing references)
			if (args.Length == 0)
			{
				ChatUtils.AddGlobalNotification("Usage: /etl <name>  - teleport to or save a location\n       /etl <x> <y> <z>  - teleport to coordinates");
				return;
			}
			if (args.Length == 3 && float.TryParse(args[0], out var result) && float.TryParse(args[1], out var result2) && float.TryParse(args[2], out var result3))
			{
				Vector3 targetPosition = default(Vector3);
				((Vector3)(ref targetPosition))..ctor(result, result2, result3);
				MoveUtil.Teleport(targetPosition);
				ChatUtils.AddGlobalNotification($"Teleported to ({result}, {result2}, {result3}).");
				_log.LogInfo((object)$"Teleported to coordinates ({result}, {result2}, {result3}).");
				return;
			}
			string text = string.Join(" ", args).Trim();
			SavedLocationManager locations = EchoPlugin.Locations;
			Vector3 pos;
			if (locations == null)
			{
				ChatUtils.AddGlobalNotification("Location manager not initialized.");
			}
			else if (locations.TryGet(text, out pos))
			{
				MoveUtil.Teleport(pos);
				ChatUtils.AddGlobalNotification("Teleported to \"" + text + "\".");
				_log.LogInfo((object)$"Teleported to saved location \"{text}\" at {pos}.");
			}
			else
			{
				PlayerDetail playerDetail = PlayerUtils.GetPlayerDetail();
				Vector3 val = ((playerDetail != null) ? playerDetail.Position : Vector3.zero);
				locations.Save(text, val);
				ChatUtils.AddGlobalNotification($"Saved location \"{text}\" at {val}.");
				_log.LogInfo((object)$"Saved location \"{text}\" at {val}.");
			}
		}
	}
	public class EchoTeleportPersonCommand : IChatCommand, IComparable<IChatCommand>
	{
		private static readonly ManualLogSource Logger = Logger.CreateLogSource("Echo.EchoTpToCommand");

		public string Name => "echoteleportperson";

		public string ShortName => "etp";

		public string Description => "Teleport to a player by name or Steam ID suffix. Usage: /echotpto <name|steamid_suffix|persona> [x y z]";

		public string Namespace => "echo";

		public void Execute(string[] args)
		{
			//IL_001d: 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)
			//IL_00c7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cc: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cd: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d2: Unknown result type (might be due to invalid IL or missing references)
			//IL_0100: Unknown result type (might be due to invalid IL or missing references)
			//IL_010d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0116: Unknown result type (might be due to invalid IL or missing references)
			//IL_0129: Unknown result type (might be due to invalid IL or missing references)
			//IL_0131: Unknown result type (might be due to invalid IL or missing references)
			//IL_0132: Unknown result type (might be due to invalid IL or missing references)
			//IL_0143: Unknown result type (might be due to invalid IL or missing references)
			//IL_014e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0159: Unknown result type (might be due to invalid IL or missing references)
			if (args.Length == 0)
			{
				ChatUtils.AddGlobalNotification("Usage: /echotpto <name|steamid_suffix|persona> [x y z]");
				return;
			}
			Vector3 zero = Vector3.zero;
			string text;
			if (args.Length >= 4 && float.TryParse(args[^3], out var result) && float.TryParse(args[^2], out var result2) && float.TryParse(args[^1], out var result3))
			{
				((Vector3)(ref zero))..ctor(result, result2, result3);
				text = string.Join(" ", args, 0, args.Length - 3);
			}
			else
			{
				text = string.Join(" ", args);
			}
			text = text.Trim();
			PlayerDetail val = PlayerUtils.FindPlayerByQuery(text);
			if (val == null)
			{
				ChatUtils.AddGlobalNotification("Player \"" + text + "\" not found.");
				return;
			}
			Vector3 val2 = val.Position + zero;
			string text2 = ChatUtils.CleanTMPTags(val.UserName).Trim();
			Logger.LogInfo((object)$"Teleporting to {text2} at {val.Position} + offset {zero} = {val2}");
			MoveUtil.Teleport(val2);
			string text3 = ((zero == Vector3.zero) ? "" : $" (+{zero.x}, +{zero.y}, +{zero.z})");
			ChatUtils.AddGlobalNotification("Teleporting to " + text2 + text3 + ".");
		}
	}
	public class EchoToggleCommand : IChatCommand, IComparable<IChatCommand>
	{
		public const string CMD = "echotoggle";

		public string Name => "echotoggle";

		public string ShortName => "et";

		public string Description => "Toggle Echo feature on/off. ";

		public string Namespace => "echo";

		public void Execute(string[] args)
		{
			if (EchoPlugin.EnableFeature != null)
			{
				EchoPlugin.EnableFeature.Value = !EchoPlugin.EnableFeature.Value;
				ChatUtils.AddGlobalNotification("Echo feature is now " + (EchoPlugin.EnableFeature.Value ? "enabled" : "disabled") + ".");
			}
		}
	}
	public class EchoUnfollowCommand : IChatCommand, IComparable<IChatCommand>
	{
		public string Name => "echounfollow";

		public string ShortName => "eu";

		public string Description => "Stop following the current target.";

		public string Namespace => "echo";

		public void Execute(string[] args)
		{
			if (!PlayerMovementControllerPatch.IsFollowing)
			{
				ChatUtils.AddGlobalNotification("Not currently following anyone.");
				return;
			}
			PlayerMovementControllerPatch.StopFollowing();
			ChatUtils.AddGlobalNotification("Stopped following.");
		}
	}
	public class EchoUnsyncRotationCommand : IChatCommand, IComparable<IChatCommand>
	{
		public string Name => "echounsyncrotation";

		public string ShortName => "eur";

		public string Description => "Stop syncing rotation to the current target.";

		public string Namespace => "echo";

		public void Execute(string[] args)
		{
			if (!PlayerMovementControllerPatch.IsSyncingRotation)
			{
				ChatUtils.AddGlobalNotification("Not currently syncing rotation.");
				return;
			}
			PlayerMovementControllerPatch.StopSyncingRotation();
			ChatUtils.AddGlobalNotification("Stopped syncing rotation.");
		}
	}
}