Decompiled source of PlayerTitles v1.2.0

PlayerTitles.dll

Decompiled a week ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
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 BepInEx;
using HarmonyLib;
using Jotunn;
using Jotunn.Entities;
using Jotunn.Managers;
using Jotunn.Utils;
using Microsoft.CodeAnalysis;
using Newtonsoft.Json;
using PlayerTitles.Commands;
using PlayerTitles.Diagnostics;
using PlayerTitles.Networking;
using PlayerTitles.Patches;
using PlayerTitles.Storage;
using Splatform;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")]
[assembly: AssemblyCompany("PlayerTitles")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0+6065a16c54c76ea2fa502c9ec24bb30f7da28c68")]
[assembly: AssemblyProduct("PlayerTitles")]
[assembly: AssemblyTitle("PlayerTitles")]
[assembly: AssemblyVersion("1.0.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace PlayerTitles
{
	[BepInPlugin("com.playertitles.valheim", "PlayerTitles", "1.2.0")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[NetworkCompatibility(/*Could not decode attribute arguments.*/)]
	public class Plugin : BaseUnityPlugin
	{
		public const string ModGuid = "com.playertitles.valheim";

		public const string ModName = "PlayerTitles";

		public const string ModVersion = "1.2.0";

		private static GameObject tickerHost;

		private Harmony harmony;

		public static Plugin Instance { get; private set; }

		public TitleStore TitleStore { get; private set; }

		public NameCache NameCache { get; private set; }

		public RpcManager RpcManager { get; private set; }

		private void Awake()
		{
			//IL_0045: Unknown result type (might be due to invalid IL or missing references)
			//IL_004f: Expected O, but got Unknown
			//IL_0070: Unknown result type (might be due to invalid IL or missing references)
			//IL_007a: Expected O, but got Unknown
			Instance = this;
			string text = Path.Combine(Paths.ConfigPath, "PlayerTitles");
			Directory.CreateDirectory(text);
			TitleStore = new TitleStore(text);
			NameCache = new NameCache(text);
			RpcManager = new RpcManager();
			tickerHost = new GameObject("PlayerTitles_TickerHost");
			Object.DontDestroyOnLoad((Object)(object)tickerHost);
			tickerHost.AddComponent<RegistrationTicker>();
			new PlayerTitlesCommands();
			harmony = new Harmony("com.playertitles.valheim");
			harmony.PatchAll();
			((BaseUnityPlugin)this).Logger.LogInfo((object)"PlayerTitles v1.2.0 loaded.");
		}
	}
}
namespace PlayerTitles.Storage
{
	internal static class AtomicFile
	{
		public static void WriteAllText(string path, string contents)
		{
			string text = path + ".tmp";
			File.WriteAllText(text, contents, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false));
			if (File.Exists(path))
			{
				File.Replace(text, path, null);
			}
			else
			{
				File.Move(text, path);
			}
		}
	}
	public class NameCache
	{
		private readonly string filePath;

		private Dictionary<string, NameCacheEntry> entries = new Dictionary<string, NameCacheEntry>();

		public NameCache(string configDir)
		{
			filePath = Path.Combine(configDir, "namecache.json");
			Load();
		}

		public void Load()
		{
			if (!File.Exists(filePath))
			{
				entries = new Dictionary<string, NameCacheEntry>();
				Save();
			}
			else
			{
				string text = File.ReadAllText(filePath);
				entries = (string.IsNullOrWhiteSpace(text) ? new Dictionary<string, NameCacheEntry>() : (JsonConvert.DeserializeObject<Dictionary<string, NameCacheEntry>>(text) ?? new Dictionary<string, NameCacheEntry>()));
			}
		}

		public void Save()
		{
			string contents = JsonConvert.SerializeObject((object)entries, (Formatting)1);
			AtomicFile.WriteAllText(filePath, contents);
		}

		public string UpdateEntry(string name, string steamId, DateTime lastSeenUtc)
		{
			string result = null;
			bool hadCollision = false;
			if (entries.TryGetValue(name, out var value))
			{
				hadCollision = value.HadCollision;
				if (value.SteamId != steamId)
				{
					result = value.SteamId;
					hadCollision = true;
				}
			}
			entries[name] = new NameCacheEntry(steamId, lastSeenUtc, hadCollision);
			Save();
			return result;
		}

		public bool TryResolve(string name, out NameCacheEntry entry)
		{
			return entries.TryGetValue(name, out entry);
		}

		public bool TryResolveNameBySteamId(string steamId, out string name)
		{
			name = "";
			DateTime dateTime = DateTime.MinValue;
			bool flag = false;
			foreach (KeyValuePair<string, NameCacheEntry> entry in entries)
			{
				if (!(entry.Value.SteamId != steamId) && (!flag || !(entry.Value.LastSeenUtc <= dateTime)))
				{
					flag = true;
					dateTime = entry.Value.LastSeenUtc;
					name = entry.Key;
				}
			}
			return flag;
		}

		public IReadOnlyDictionary<string, NameCacheEntry> GetAll()
		{
			return entries;
		}
	}
	public class NameCacheEntry
	{
		public string SteamId { get; set; }

		public DateTime LastSeenUtc { get; set; }

		public bool HadCollision { get; set; }

		public NameCacheEntry()
		{
		}

		public NameCacheEntry(string steamId, DateTime lastSeenUtc, bool hadCollision = false)
		{
			SteamId = steamId;
			LastSeenUtc = lastSeenUtc;
			HadCollision = hadCollision;
		}
	}
	public class TitleEntry
	{
		public const string PositionPrefix = "above";

		public const string PositionSuffix = "below";

		public const string DefaultPosition = "above";

		public const string DefaultColor = "#FFFFFF";

		public string Title { get; set; }

		public string Position { get; set; }

		public string Color { get; set; }

		public TitleEntry()
		{
		}

		public TitleEntry(string title, string position, string color)
		{
			Title = title;
			Position = position;
			Color = color;
		}

		public static bool TryNormalisePosition(string raw, out string normalised)
		{
			if (string.Equals(raw, "above", StringComparison.OrdinalIgnoreCase) || string.Equals(raw, "prefix", StringComparison.OrdinalIgnoreCase))
			{
				normalised = "above";
				return true;
			}
			if (string.Equals(raw, "below", StringComparison.OrdinalIgnoreCase) || string.Equals(raw, "suffix", StringComparison.OrdinalIgnoreCase))
			{
				normalised = "below";
				return true;
			}
			normalised = "above";
			return false;
		}
	}
	public class TitleStore
	{
		private readonly string filePath;

		private Dictionary<string, TitleEntry> titles = new Dictionary<string, TitleEntry>();

		public TitleStore(string configDir)
		{
			filePath = Path.Combine(configDir, "titles.json");
			Load();
		}

		public void Load()
		{
			if (!File.Exists(filePath))
			{
				titles = new Dictionary<string, TitleEntry>();
				Save();
			}
			else
			{
				string text = File.ReadAllText(filePath);
				titles = (string.IsNullOrWhiteSpace(text) ? new Dictionary<string, TitleEntry>() : (JsonConvert.DeserializeObject<Dictionary<string, TitleEntry>>(text) ?? new Dictionary<string, TitleEntry>()));
			}
		}

		public void Save()
		{
			string contents = JsonConvert.SerializeObject((object)titles, (Formatting)1);
			AtomicFile.WriteAllText(filePath, contents);
		}

		public bool TryGet(string steamId, out TitleEntry entry)
		{
			return titles.TryGetValue(steamId, out entry);
		}

		public void Set(string steamId, TitleEntry entry)
		{
			titles[steamId] = entry;
			Save();
		}

		public bool Remove(string steamId)
		{
			if (!titles.Remove(steamId))
			{
				return false;
			}
			Save();
			return true;
		}

		public IReadOnlyDictionary<string, TitleEntry> GetAll()
		{
			return titles;
		}
	}
}
namespace PlayerTitles.Patches
{
	[HarmonyPatch(typeof(ZNet), "RPC_CharacterID")]
	public static class CharacterIdTagPatch
	{
		public static class PendingTags
		{
			private class PendingTag
			{
				public ZDOID CharacterId;

				public string SteamId;

				public string PlayerName;

				public float ElapsedSeconds;

				public float SinceLastAttempt;

				public int Attempts;
			}

			private const float TimeoutSeconds = 5f;

			private const float RetryIntervalSeconds = 0.2f;

			private static readonly Dictionary<ZDOID, PendingTag> pending = new Dictionary<ZDOID, PendingTag>();

			private static readonly List<ZDOID> completed = new List<ZDOID>();

			public static void Enqueue(ZDOID characterId, string steamId, string playerName)
			{
				//IL_000f: Unknown result type (might be due to invalid IL or missing references)
				//IL_0058: Unknown result type (might be due to invalid IL or missing references)
				//IL_005f: 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_0085: 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)
				if (!((ZDOID)(ref characterId)).IsNone())
				{
					if (pending.TryGetValue(characterId, out var value))
					{
						value.SteamId = steamId;
						value.PlayerName = playerName;
						Logger.LogInfo((object)$"PlayerTitles: PendingTags outcome=already-queued zdoid={characterId} attempts={value.Attempts} elapsed={value.ElapsedSeconds:0.00}s");
						return;
					}
					pending[characterId] = new PendingTag
					{
						CharacterId = characterId,
						SteamId = steamId,
						PlayerName = playerName
					};
					Logger.LogInfo((object)$"PlayerTitles: PendingTags outcome=enqueued zdoid={characterId} playerName={playerName} queueSize={pending.Count} -- ZDO not replicated to the server yet, retrying for {5f:0}s");
				}
			}

			public static void Cancel(ZDOID characterId)
			{
				//IL_0005: Unknown result type (might be due to invalid IL or missing references)
				//IL_0012: Unknown result type (might be due to invalid IL or missing references)
				if (pending.Remove(characterId))
				{
					Logger.LogInfo((object)$"PlayerTitles: PendingTags outcome=cancelled zdoid={characterId} -- superseded");
				}
			}

			public static void Tick(float deltaTime)
			{
				//IL_00d7: Unknown result type (might be due to invalid IL or missing references)
				//IL_0107: Unknown result type (might be due to invalid IL or missing references)
				//IL_0169: Unknown result type (might be due to invalid IL or missing references)
				//IL_0207: 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_0213: Unknown result type (might be due to invalid IL or missing references)
				//IL_0190: Unknown result type (might be due to invalid IL or missing references)
				//IL_01d1: Unknown result type (might be due to invalid IL or missing references)
				if (pending.Count == 0)
				{
					return;
				}
				if ((Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer())
				{
					Logger.LogInfo((object)$"PlayerTitles: PendingTags outcome=discarded count={pending.Count} -- no longer server");
					pending.Clear();
					return;
				}
				completed.Clear();
				foreach (KeyValuePair<ZDOID, PendingTag> item in pending)
				{
					PendingTag value = item.Value;
					value.ElapsedSeconds += deltaTime;
					value.SinceLastAttempt += deltaTime;
					if (!(value.SinceLastAttempt < 0.2f) || value.Attempts <= 0)
					{
						value.SinceLastAttempt = 0f;
						value.Attempts++;
						ZDO val = ((ZDOMan.instance != null) ? ZDOMan.instance.GetZDO(value.CharacterId) : null);
						if (val != null)
						{
							val.Set("PlayerTitles_SteamID", value.SteamId);
							Logger.LogInfo((object)string.Format("PlayerTitles: PendingTags outcome=resolved zdoid={0} playerName={1} attempt={2} elapsed={3:0.00}s readback='{4}' ownerUid={5}", value.CharacterId, value.PlayerName, value.Attempts, value.ElapsedSeconds, val.GetString("PlayerTitles_SteamID", "<empty>"), val.GetOwner()));
							completed.Add(item.Key);
						}
						else if (value.ElapsedSeconds >= 5f)
						{
							Logger.LogWarning((object)$"PlayerTitles: GAVE UP tagging SteamID onto character ZDO {value.CharacterId} (player '{value.PlayerName}') after {value.Attempts} attempts over {value.ElapsedSeconds:0.00}s -- the ZDO never replicated to the server. This character will show no title until it next spawns.");
							completed.Add(item.Key);
						}
					}
				}
				foreach (ZDOID item2 in completed)
				{
					pending.Remove(item2);
				}
				completed.Clear();
			}
		}

		public const string SteamIdZdoKey = "PlayerTitles_SteamID";

		[HarmonyPostfix]
		private static void Postfix(ZNet __instance, ZRpc rpc, ZDOID characterID)
		{
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			//IL_0042: Unknown result type (might be due to invalid IL or missing references)
			//IL_006c: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ac: 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_0121: 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_00d7: Unknown result type (might be due to invalid IL or missing references)
			if (!__instance.IsServer())
			{
				return;
			}
			ZNetPeer peer = PeerLookup.GetPeer(__instance, rpc);
			if (peer == null)
			{
				Logger.LogInfo((object)$"PlayerTitles: RPC_CharacterID branch=peer-null characterID={characterID}");
				return;
			}
			if (peer.m_socket == null)
			{
				Logger.LogWarning((object)$"PlayerTitles: RPC_CharacterID -- peer {peer.m_uid} has no socket, cannot resolve SteamID for {characterID}");
				return;
			}
			string hostName = peer.m_socket.GetHostName();
			if (string.IsNullOrEmpty(hostName))
			{
				Logger.LogInfo((object)$"PlayerTitles: RPC_CharacterID branch=no-steamid characterID={characterID} playerName={peer.m_playerName}");
				return;
			}
			if (((ZDOID)(ref characterID)).IsNone())
			{
				Logger.LogInfo((object)$"PlayerTitles: RPC_CharacterID branch=zdoid-none peer={peer.m_uid} playerName={peer.m_playerName} -- character cleared (respawn/logout), nothing to tag");
				PendingTags.Cancel(characterID);
				return;
			}
			ZDO zDO = ZDOMan.instance.GetZDO(characterID);
			if (zDO == null)
			{
				Logger.LogInfo((object)$"PlayerTitles: RPC_CharacterID postSet zdoid={characterID} outcome=enqueued readback='<no zdo>' -- deferred to PendingTags");
				PendingTags.Enqueue(characterID, hostName, peer.m_playerName);
			}
			else
			{
				zDO.Set("PlayerTitles_SteamID", hostName);
				Logger.LogInfo((object)string.Format("PlayerTitles: RPC_CharacterID postSet zdoid={0} outcome=tagged-direct readback='{1}' ownerUid={2}", characterID, zDO.GetString("PlayerTitles_SteamID", "<empty>"), zDO.GetOwner()));
				PendingTags.Cancel(characterID);
			}
		}
	}
	[HarmonyPatch(typeof(Terminal), "AddString", new Type[]
	{
		typeof(PlatformUserID),
		typeof(string),
		typeof(Type),
		typeof(bool)
	})]
	public static class ChatTitlePatch
	{
		[HarmonyPrefix]
		private static bool Prefix(Terminal __instance, PlatformUserID user, string text, Type type, bool timestamp)
		{
			//IL_0000: 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_0012: Unknown result type (might be due to invalid IL or missing references)
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			//IL_0024: Unknown result type (might be due to invalid IL or missing references)
			//IL_0031: Unknown result type (might be due to invalid IL or missing references)
			PlayerInfo val = default(PlayerInfo);
			if (!ZNet.TryGetPlayerByPlatformUserID(user, ref val))
			{
				return true;
			}
			TitleEntry titleEntry = ResolveTitle(val.m_name, val.m_characterID);
			if (titleEntry == null)
			{
				return true;
			}
			__instance.AddString(DecorateName(val.m_name, titleEntry), text, type, timestamp);
			return false;
		}

		private static string DecorateName(string name, TitleEntry title)
		{
			string text = "<color=" + title.Color + ">" + title.Title + "</color>";
			if (!string.Equals(title.Position, "below", StringComparison.OrdinalIgnoreCase))
			{
				return text + " " + name;
			}
			return name + " " + text;
		}

		private static TitleEntry ResolveTitle(string name, ZDOID characterId)
		{
			//IL_002d: Unknown result type (might be due to invalid IL or missing references)
			if (RpcManager.Instance.ClientTitlesByName.TryGetValue(name, out var value))
			{
				return value;
			}
			if (((ZDOID)(ref characterId)).IsNone() || ZDOMan.instance == null)
			{
				return null;
			}
			ZDO zDO = ZDOMan.instance.GetZDO(characterId);
			if (zDO == null)
			{
				return null;
			}
			string text = zDO.GetString("PlayerTitles_SteamID", "");
			if (string.IsNullOrEmpty(text))
			{
				return null;
			}
			RpcManager.Instance.ClientTitles.TryGetValue(text, out var value2);
			return value2;
		}
	}
	[HarmonyPatch(typeof(Player), "GetHoverName")]
	public static class NameplateTitlePatch
	{
		[HarmonyPostfix]
		private static void Postfix(Player __instance, ref string __result)
		{
			RpcManager instance = RpcManager.Instance;
			if (instance != null && (instance.ClientTitles.Count != 0 || instance.ClientTitlesByName.Count != 0) && !((Object)(object)__instance == (Object)null) && !string.IsNullOrEmpty(__result))
			{
				TitleEntry titleEntry = ResolveTitle(__instance, __result);
				if (titleEntry != null)
				{
					__result = (string.Equals(titleEntry.Position, "below", StringComparison.OrdinalIgnoreCase) ? (__result + " <color=" + titleEntry.Color + ">" + titleEntry.Title + "</color>") : ("<color=" + titleEntry.Color + ">" + titleEntry.Title + "</color> " + __result));
				}
			}
		}

		private static TitleEntry ResolveTitle(Player player, string hoverName)
		{
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			//IL_0038: Unknown result type (might be due to invalid IL or missing references)
			RpcManager instance = RpcManager.Instance;
			if (instance.ClientTitlesByName.TryGetValue(hoverName, out var value))
			{
				return value;
			}
			if (ZDOMan.instance == null)
			{
				return null;
			}
			ZDOID zDOID = ((Character)player).GetZDOID();
			if (((ZDOID)(ref zDOID)).IsNone())
			{
				return null;
			}
			ZDO zDO = ZDOMan.instance.GetZDO(zDOID);
			if (zDO == null)
			{
				return null;
			}
			string text = zDO.GetString("PlayerTitles_SteamID", "");
			if (string.IsNullOrEmpty(text))
			{
				return null;
			}
			instance.ClientTitles.TryGetValue(text, out var value2);
			return value2;
		}
	}
	[HarmonyPatch(typeof(ZNet), "RPC_PeerInfo")]
	public static class PeerConnectedPatch
	{
		[HarmonyPostfix]
		private static void Postfix(ZNet __instance, ZRpc rpc)
		{
			if (!__instance.IsServer())
			{
				return;
			}
			ZNetPeer peer = PeerLookup.GetPeer(__instance, rpc);
			if (peer == null || string.IsNullOrEmpty(peer.m_playerName))
			{
				return;
			}
			if (peer.m_socket == null)
			{
				Logger.LogWarning((object)$"PlayerTitles: RPC_PeerInfo -- peer {peer.m_uid} ('{peer.m_playerName}') has no socket, cannot cache name -> SteamID");
				return;
			}
			string hostName = peer.m_socket.GetHostName();
			if (!string.IsNullOrEmpty(hostName))
			{
				string playerName = peer.m_playerName;
				string text = Plugin.Instance.NameCache.UpdateEntry(playerName, hostName, DateTime.UtcNow);
				if (text != null)
				{
					Logger.LogWarning((object)("PlayerTitles: name cache collision for '" + playerName + "' -- previously SteamID " + text + ", now " + hostName + ". Most-recent-wins applied."));
				}
				Logger.LogInfo((object)("PlayerTitles: name cache updated -- '" + playerName + "' -> " + hostName));
				Plugin.Instance.RpcManager.PushFullStateTo(peer.m_uid);
			}
		}
	}
	public static class PeerLookup
	{
		public static ZNetPeer GetPeer(ZNet znet, ZRpc rpc)
		{
			if ((Object)(object)znet == (Object)null || rpc == null)
			{
				return null;
			}
			List<ZNetPeer> connectedPeers = znet.GetConnectedPeers();
			if (connectedPeers == null)
			{
				return null;
			}
			for (int i = 0; i < connectedPeers.Count; i++)
			{
				ZNetPeer val = connectedPeers[i];
				if (val != null && val.m_rpc == rpc)
				{
					return val;
				}
			}
			return null;
		}
	}
}
namespace PlayerTitles.Networking
{
	public class RegistrationTicker : MonoBehaviour
	{
		private float inWorldSeconds;

		private bool loggedRegistrationTimeout;

		private bool loggedWaitingForWorld;

		private bool loggedTagRetryFailure;

		private bool loggedRegistrationFailure;

		private void Update()
		{
			try
			{
				RetryRegistration();
			}
			catch (Exception arg)
			{
				if (!loggedRegistrationFailure)
				{
					loggedRegistrationFailure = true;
					Logger.LogError((object)$"PlayerTitles: RPC registration retry threw -- suppressing further reports of this. {arg}");
				}
			}
			RetryPendingSteamIdTags();
		}

		private void RetryRegistration()
		{
			RpcManager instance = RpcManager.Instance;
			if (instance == null || instance.Registered)
			{
				return;
			}
			instance.Register();
			if (instance.Registered)
			{
				Logger.LogInfo((object)"PlayerTitles: RegistrationTicker -- RpcManager registered");
				inWorldSeconds = 0f;
				loggedRegistrationTimeout = false;
				loggedWaitingForWorld = false;
			}
			else if ((Object)(object)ZNet.instance == (Object)null)
			{
				inWorldSeconds = 0f;
				loggedRegistrationTimeout = false;
				if (!loggedWaitingForWorld)
				{
					loggedWaitingForWorld = true;
					Logger.LogInfo((object)"PlayerTitles: RegistrationTicker -- not in a world yet, deferring RPC registration (normal at the main menu)");
				}
			}
			else
			{
				loggedWaitingForWorld = false;
				inWorldSeconds += Time.deltaTime;
				if (inWorldSeconds >= 30f && !loggedRegistrationTimeout)
				{
					loggedRegistrationTimeout = true;
					Logger.LogWarning((object)"PlayerTitles: RegistrationTicker -- RpcManager STILL UNREGISTERED 30s after joining a world. Inbound RPCs will not be received on this machine.");
				}
			}
		}

		private void RetryPendingSteamIdTags()
		{
			try
			{
				CharacterIdTagPatch.PendingTags.Tick(Time.deltaTime);
			}
			catch (Exception arg)
			{
				if (!loggedTagRetryFailure)
				{
					loggedTagRetryFailure = true;
					Logger.LogError((object)$"PlayerTitles: SteamID tag retry threw -- suppressing further reports of this. {arg}");
				}
			}
		}
	}
	public class RpcManager
	{
		public const string RpcRequestAssign = "PlayerTitles_RequestAssign";

		public const string RpcRequestRevoke = "PlayerTitles_RequestRevoke";

		public const string RpcRequestList = "PlayerTitles_RequestList";

		public const string RpcCommandResult = "PlayerTitles_CommandResult";

		public const string RpcListResult = "PlayerTitles_ListResult";

		public const string RpcSyncAll = "PlayerTitles_SyncAll";

		public const string RpcTitleChanged = "PlayerTitles_TitleChanged";

		public const string RpcRequestDebugZdo = "PlayerTitles_RequestDebugZdo";

		public readonly Dictionary<string, TitleEntry> ClientTitles = new Dictionary<string, TitleEntry>();

		public readonly Dictionary<string, TitleEntry> ClientTitlesByName = new Dictionary<string, TitleEntry>();

		private readonly Dictionary<string, string> nameBySteamId = new Dictionary<string, string>();

		private bool registered;

		private ZRoutedRpc registeredOn;

		public static RpcManager Instance { get; private set; }

		public bool Registered
		{
			get
			{
				if (registered)
				{
					return registeredOn == ZRoutedRpc.instance;
				}
				return false;
			}
		}

		public RpcManager()
		{
			Instance = this;
			PrefabManager.OnVanillaPrefabsAvailable += Register;
		}

		public void Register()
		{
			if (!Registered && ZRoutedRpc.instance != null)
			{
				ZRoutedRpc.instance.Register<ZPackage>("PlayerTitles_RequestAssign", (Action<long, ZPackage>)RPC_RequestAssign);
				ZRoutedRpc.instance.Register<ZPackage>("PlayerTitles_RequestRevoke", (Action<long, ZPackage>)RPC_RequestRevoke);
				ZRoutedRpc.instance.Register<ZPackage>("PlayerTitles_RequestList", (Action<long, ZPackage>)RPC_RequestList);
				ZRoutedRpc.instance.Register<ZPackage>("PlayerTitles_CommandResult", (Action<long, ZPackage>)RPC_CommandResult);
				ZRoutedRpc.instance.Register<ZPackage>("PlayerTitles_ListResult", (Action<long, ZPackage>)RPC_ListResult);
				ZRoutedRpc.instance.Register<ZPackage>("PlayerTitles_SyncAll", (Action<long, ZPackage>)RPC_SyncAll);
				ZRoutedRpc.instance.Register<ZPackage>("PlayerTitles_TitleChanged", (Action<long, ZPackage>)RPC_TitleChanged);
				ZRoutedRpc.instance.Register<ZPackage>("PlayerTitles_RequestDebugZdo", (Action<long, ZPackage>)RPC_RequestDebugZdo);
				registeredOn = ZRoutedRpc.instance;
				registered = true;
				Logger.LogInfo((object)"PlayerTitles: RPCs registered");
			}
		}

		public void SendRequestAssign(string playerName, string title, string position, string color)
		{
			//IL_000e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0014: Expected O, but got Unknown
			if (ZRoutedRpc.instance != null)
			{
				Register();
				ZPackage val = new ZPackage();
				val.Write(playerName);
				val.Write(title);
				val.Write(position);
				val.Write(color);
				ZRoutedRpc.instance.InvokeRoutedRPC("PlayerTitles_RequestAssign", new object[1] { val });
			}
		}

		public void SendRequestRevoke(string playerName)
		{
			//IL_000e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0014: Expected O, but got Unknown
			if (ZRoutedRpc.instance != null)
			{
				Register();
				ZPackage val = new ZPackage();
				val.Write(playerName);
				ZRoutedRpc.instance.InvokeRoutedRPC("PlayerTitles_RequestRevoke", new object[1] { val });
			}
		}

		public void SendRequestList()
		{
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			//IL_0026: Expected O, but got Unknown
			if (ZRoutedRpc.instance != null)
			{
				Register();
				ZRoutedRpc.instance.InvokeRoutedRPC("PlayerTitles_RequestList", new object[1] { (object)new ZPackage() });
			}
		}

		public void SendRequestDebugZdo(string token)
		{
			//IL_000e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0014: Expected O, but got Unknown
			if (ZRoutedRpc.instance != null)
			{
				Register();
				ZPackage val = new ZPackage();
				val.Write(token);
				ZRoutedRpc.instance.InvokeRoutedRPC("PlayerTitles_RequestDebugZdo", new object[1] { val });
			}
		}

		private bool IsSenderAdmin(long sender)
		{
			if ((Object)(object)ZNet.instance == (Object)null)
			{
				Logger.LogInfo((object)$"PlayerTitles: IsSenderAdmin branch=znet-null sender={sender} playerName=N/A steamId=N/A result=false");
				return false;
			}
			if (ZNet.instance.IsServer() && sender == ZNet.GetUID())
			{
				Logger.LogInfo((object)$"PlayerTitles: IsSenderAdmin branch=self-routed-server sender={sender} playerName=N/A steamId=N/A result=true");
				return true;
			}
			ZNetPeer peer = ZNet.instance.GetPeer(sender);
			if (peer == null)
			{
				Logger.LogInfo((object)$"PlayerTitles: IsSenderAdmin branch=peer-null sender={sender} playerName=N/A steamId=N/A result=false");
				Logger.LogWarning((object)$"PlayerTitles: IsSenderAdmin - no peer for sender {sender} (not server UID {ZNet.GetUID()}) -- rejecting");
				return false;
			}
			if (peer.m_socket == null)
			{
				Logger.LogInfo((object)$"PlayerTitles: IsSenderAdmin branch=peer-null-socket sender={sender} playerName={peer.m_playerName} steamId=N/A result=false");
				Logger.LogWarning((object)$"PlayerTitles: IsSenderAdmin - peer {sender} has no socket -- rejecting");
				return false;
			}
			string hostName = peer.m_socket.GetHostName();
			bool flag = ZNet.instance.IsAdmin(hostName);
			Logger.LogInfo((object)string.Format("PlayerTitles: IsSenderAdmin branch={0} sender={1} playerName={2} steamId={3} result={4}", flag ? "peer-found-admin" : "peer-found-not-admin", sender, peer.m_playerName, hostName, flag));
			return flag;
		}

		private static bool IsFromServer(long sender)
		{
			if ((Object)(object)ZNet.instance == (Object)null)
			{
				return false;
			}
			if (ZNet.instance.IsServer())
			{
				return sender == ZNet.GetUID();
			}
			ZNetPeer serverPeer = ZNet.instance.GetServerPeer();
			if (serverPeer != null)
			{
				return sender == serverPeer.m_uid;
			}
			return false;
		}

		private void SendResult(long targetPeer, string message)
		{
			//IL_000e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0014: Expected O, but got Unknown
			if (ZRoutedRpc.instance != null)
			{
				Register();
				ZPackage val = new ZPackage();
				val.Write(message);
				ZRoutedRpc.instance.InvokeRoutedRPC(targetPeer, "PlayerTitles_CommandResult", new object[1] { val });
			}
		}

		private void RPC_RequestAssign(long sender, ZPackage pkg)
		{
			Logger.LogInfo((object)$"PlayerTitles: RPC_RequestAssign from sender {sender}");
			string text = pkg.ReadString();
			string text2 = pkg.ReadString();
			string text3 = pkg.ReadString();
			string text4 = pkg.ReadString();
			if (!((Object)(object)ZNet.instance == (Object)null) && ZNet.instance.IsServer())
			{
				if (!IsSenderAdmin(sender))
				{
					Logger.LogWarning((object)$"PlayerTitles: RPC_RequestAssign rejected -- sender {sender} is not admin");
					SendResult(sender, "You must be a server admin to use this command.");
					return;
				}
				if (!TryResolveSteamId(text, out var steamId, out var error))
				{
					SendResult(sender, error);
					return;
				}
				TitleEntry entry = new TitleEntry(text2, text3, text4);
				Plugin.Instance.TitleStore.Set(steamId, entry);
				BroadcastTitleChanged(steamId, entry);
				string text5 = "Assigned title '" + text2 + "' to '" + text + "' (position: " + text3 + ", color: " + text4 + ").";
				Logger.LogInfo((object)("PlayerTitles: " + text5));
				SendResult(sender, text5);
			}
		}

		private void RPC_RequestRevoke(long sender, ZPackage pkg)
		{
			Logger.LogInfo((object)$"PlayerTitles: RPC_RequestRevoke from sender {sender}");
			string text = pkg.ReadString();
			if (!((Object)(object)ZNet.instance == (Object)null) && ZNet.instance.IsServer())
			{
				if (!IsSenderAdmin(sender))
				{
					Logger.LogWarning((object)$"PlayerTitles: RPC_RequestRevoke rejected -- sender {sender} is not admin");
					SendResult(sender, "You must be a server admin to use this command.");
					return;
				}
				if (!TryResolveSteamId(text, out var steamId, out var error))
				{
					SendResult(sender, error);
					return;
				}
				if (!Plugin.Instance.TitleStore.TryGet(steamId, out var _))
				{
					SendResult(sender, "'" + text + "' has no title to revoke.");
					return;
				}
				Plugin.Instance.TitleStore.Remove(steamId);
				BroadcastTitleRemoved(steamId);
				string text2 = "Revoked title from '" + text + "'.";
				Logger.LogInfo((object)("PlayerTitles: " + text2));
				SendResult(sender, text2);
			}
		}

		private void RPC_RequestList(long sender, ZPackage pkg)
		{
			//IL_013f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0145: Expected O, but got Unknown
			Logger.LogInfo((object)$"PlayerTitles: RPC_RequestList from sender {sender}");
			if ((Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer())
			{
				return;
			}
			if (!IsSenderAdmin(sender))
			{
				Logger.LogWarning((object)$"PlayerTitles: RPC_RequestList rejected -- sender {sender} is not admin");
				SendResult(sender, "You must be a server admin to use this command.");
				return;
			}
			List<string> list = new List<string>();
			IReadOnlyDictionary<string, TitleEntry> all = Plugin.Instance.TitleStore.GetAll();
			if (all.Count == 0)
			{
				list.Add("No titles are currently awarded.");
			}
			else
			{
				foreach (KeyValuePair<string, TitleEntry> item in all)
				{
					string key = item.Key;
					TitleEntry value = item.Value;
					string name;
					string text = (Plugin.Instance.NameCache.TryResolveNameBySteamId(key, out name) ? name : "<unknown>");
					list.Add(text + " | " + key + " | \"" + value.Title + "\" | " + value.Position + " | " + value.Color);
				}
			}
			ZPackage val = new ZPackage();
			val.Write(list.Count);
			foreach (string item2 in list)
			{
				val.Write(item2);
			}
			if (ZRoutedRpc.instance != null)
			{
				Register();
				ZRoutedRpc.instance.InvokeRoutedRPC(sender, "PlayerTitles_ListResult", new object[1] { val });
			}
		}

		private void RPC_RequestDebugZdo(long sender, ZPackage pkg)
		{
			string text = pkg.ReadString();
			if (!((Object)(object)ZNet.instance == (Object)null) && ZNet.instance.IsServer())
			{
				if (!IsSenderAdmin(sender))
				{
					Logger.LogWarning((object)$"PlayerTitles: RPC_RequestDebugZdo REJECTED -- sender {sender} is not admin. token={text}. NO SERVER DUMP WAS PRODUCED for this token (this is an admin-gate rejection, not an empty result).");
					SendResult(sender, "playertitles_debugzdo: server refused the dump -- you are not a server admin (token " + text + ").");
					return;
				}
				ZdoDump.Begin(text, $"server-relayed(sender={sender})");
				ZdoDump.DumpPeers(text);
				ZdoDump.DumpLocalPlayers(text, isServer: true);
				ZdoDump.End(text);
				SendResult(sender, "playertitles_debugzdo: server-side dump written to the server log (token " + text + ").");
			}
		}

		private bool TryResolveSteamId(string playerName, out string steamId, out string error)
		{
			if (!Plugin.Instance.NameCache.TryResolve(playerName, out var entry))
			{
				steamId = null;
				error = "No player named '" + playerName + "' has connected to this server. Title not assigned.";
				return false;
			}
			if (entry.HadCollision)
			{
				Logger.LogWarning((object)("PlayerTitles: '" + playerName + "' has been used by more than one SteamID -- resolving to the most recently seen SteamID " + entry.SteamId + "."));
			}
			steamId = entry.SteamId;
			error = null;
			return true;
		}

		private static string ResolveNameForSteamId(string steamId)
		{
			if (!Plugin.Instance.NameCache.TryResolveNameBySteamId(steamId, out var name))
			{
				return "";
			}
			return name;
		}

		public void PushFullStateTo(long targetPeer)
		{
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0024: Expected O, but got Unknown
			if (ZRoutedRpc.instance == null)
			{
				return;
			}
			Register();
			IReadOnlyDictionary<string, TitleEntry> all = Plugin.Instance.TitleStore.GetAll();
			ZPackage val = new ZPackage();
			val.Write(all.Count);
			foreach (KeyValuePair<string, TitleEntry> item in all)
			{
				val.Write(item.Key);
				val.Write(ResolveNameForSteamId(item.Key));
				val.Write(item.Value.Title);
				val.Write(item.Value.Position);
				val.Write(item.Value.Color);
			}
			ZRoutedRpc.instance.InvokeRoutedRPC(targetPeer, "PlayerTitles_SyncAll", new object[1] { val });
		}

		private void BroadcastTitleChanged(string steamId, TitleEntry entry)
		{
			//IL_000e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0014: Expected O, but got Unknown
			if (ZRoutedRpc.instance != null)
			{
				Register();
				ZPackage val = new ZPackage();
				val.Write(steamId);
				val.Write(ResolveNameForSteamId(steamId));
				val.Write(false);
				val.Write(entry.Title);
				val.Write(entry.Position);
				val.Write(entry.Color);
				ZRoutedRpc.instance.InvokeRoutedRPC(0L, "PlayerTitles_TitleChanged", new object[1] { val });
			}
		}

		private void BroadcastTitleRemoved(string steamId)
		{
			//IL_000e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0014: Expected O, but got Unknown
			if (ZRoutedRpc.instance != null)
			{
				Register();
				ZPackage val = new ZPackage();
				val.Write(steamId);
				val.Write(ResolveNameForSteamId(steamId));
				val.Write(true);
				ZRoutedRpc.instance.InvokeRoutedRPC(0L, "PlayerTitles_TitleChanged", new object[1] { val });
			}
		}

		private void RPC_CommandResult(long sender, ZPackage pkg)
		{
			if (!IsFromServer(sender))
			{
				Logger.LogWarning((object)$"PlayerTitles: RPC_CommandResult rejected -- sender {sender} is not the server");
				return;
			}
			string text = pkg.ReadString();
			if ((Object)(object)Console.instance != (Object)null)
			{
				Console.instance.Print(text);
			}
			Logger.LogInfo((object)("PlayerTitles: " + text));
		}

		private void RPC_ListResult(long sender, ZPackage pkg)
		{
			if (!IsFromServer(sender))
			{
				Logger.LogWarning((object)$"PlayerTitles: RPC_ListResult rejected -- sender {sender} is not the server");
				return;
			}
			int num = pkg.ReadInt();
			for (int i = 0; i < num; i++)
			{
				string text = pkg.ReadString();
				if ((Object)(object)Console.instance != (Object)null)
				{
					Console.instance.Print(text);
				}
			}
		}

		private void RPC_SyncAll(long sender, ZPackage pkg)
		{
			if (!IsFromServer(sender))
			{
				Logger.LogWarning((object)$"PlayerTitles: RPC_SyncAll rejected -- sender {sender} is not the server");
				return;
			}
			Logger.LogInfo((object)"PlayerTitles: RPC_SyncAll received");
			int num = pkg.ReadInt();
			ClientTitles.Clear();
			ClientTitlesByName.Clear();
			nameBySteamId.Clear();
			for (int i = 0; i < num; i++)
			{
				string key = pkg.ReadString();
				string text = pkg.ReadString();
				string title = pkg.ReadString();
				string position = pkg.ReadString();
				string color = pkg.ReadString();
				TitleEntry value = new TitleEntry(title, position, color);
				ClientTitles[key] = value;
				if (!string.IsNullOrEmpty(text))
				{
					ClientTitlesByName[text] = value;
					nameBySteamId[key] = text;
				}
			}
			Logger.LogInfo((object)$"PlayerTitles: synced {num} title(s) from server");
		}

		private void RPC_TitleChanged(long sender, ZPackage pkg)
		{
			if (!IsFromServer(sender))
			{
				Logger.LogWarning((object)$"PlayerTitles: RPC_TitleChanged rejected -- sender {sender} is not the server");
				return;
			}
			string text = pkg.ReadString();
			string text2 = pkg.ReadString();
			if (pkg.ReadBool())
			{
				ClientTitles.Remove(text);
				string value;
				string text3 = ((!string.IsNullOrEmpty(text2)) ? text2 : (nameBySteamId.TryGetValue(text, out value) ? value : null));
				if (text3 != null)
				{
					ClientTitlesByName.Remove(text3);
				}
				nameBySteamId.Remove(text);
				Logger.LogInfo((object)("PlayerTitles: title removed for " + text));
				return;
			}
			string title = pkg.ReadString();
			string position = pkg.ReadString();
			string color = pkg.ReadString();
			TitleEntry value2 = new TitleEntry(title, position, color);
			ClientTitles[text] = value2;
			if (!string.IsNullOrEmpty(text2))
			{
				ClientTitlesByName[text2] = value2;
				nameBySteamId[text] = text2;
			}
			Logger.LogInfo((object)("PlayerTitles: title updated for " + text));
		}
	}
}
namespace PlayerTitles.Diagnostics
{
	public static class ZdoDump
	{
		public static string NewToken()
		{
			return Guid.NewGuid().ToString("N").Substring(0, 8);
		}

		public static void Begin(string token, string origin)
		{
			Logger.LogInfo((object)("[PlayerTitles][DIAG] ===== debugzdo BEGIN token=" + token + " origin=" + origin + " ====="));
			Logger.LogInfo((object)string.Format("[PlayerTitles][DIAG] token={0} header znetNonNull={1} isServer={2} myUid={3} zdoManNonNull={4} zdoKey={5}", token, (Object)(object)ZNet.instance != (Object)null, (Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsServer(), ((Object)(object)ZNet.instance != (Object)null) ? ZNet.GetUID() : 0, ZDOMan.instance != null, "PlayerTitles_SteamID"));
		}

		public static void End(string token)
		{
			Logger.LogInfo((object)("[PlayerTitles][DIAG] ===== debugzdo END token=" + token + " ====="));
		}

		public static void DumpPeers(string token)
		{
			//IL_00bc: Unknown result type (might be due to invalid IL or missing references)
			//IL_0152: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)ZNet.instance == (Object)null)
			{
				Logger.LogInfo((object)("[PlayerTitles][DIAG] token=" + token + " peers -- ZNet.instance is null, nothing to dump"));
				return;
			}
			long uID = ZNet.GetUID();
			List<ZNetPeer> connectedPeers = ZNet.instance.GetConnectedPeers();
			Logger.LogInfo((object)$"[PlayerTitles][DIAG] token={token} peers count={connectedPeers?.Count ?? 0} myUid={uID}");
			if (connectedPeers == null)
			{
				return;
			}
			foreach (ZNetPeer item in connectedPeers)
			{
				if (item == null)
				{
					Logger.LogInfo((object)("[PlayerTitles][DIAG] token=" + token + " peer <null entry>"));
					continue;
				}
				string text = ((item.m_socket != null) ? item.m_socket.GetHostName() : "<null socket>");
				ZDO val = null;
				if (ZDOMan.instance != null)
				{
					val = ZDOMan.instance.GetZDO(item.m_characterID);
				}
				string text2 = ((val != null) ? val.GetString("PlayerTitles_SteamID", "<empty>") : "<no zdo>");
				string text3 = ((val != null) ? val.GetOwner().ToString() : "<no zdo>");
				string text4 = ((val != null) ? (val.GetOwner() == uID).ToString() : "<no zdo>");
				Logger.LogInfo((object)$"[PlayerTitles][DIAG] token={token} peer uid={item.m_uid} name={item.m_playerName} steamId={text} characterID={item.m_characterID} zdoNonNull={val != null} ownerUid={text3} ownerIsMe={text4} myUid={uID} tag='{text2}'");
			}
		}

		public static void DumpLocalPlayers(string token, bool isServer)
		{
			//IL_00ea: Unknown result type (might be due to invalid IL or missing references)
			List<Player> allPlayers = Player.GetAllPlayers();
			Logger.LogInfo((object)$"[PlayerTitles][DIAG] token={token} GetAllPlayers count={allPlayers?.Count ?? 0} isServer={isServer}");
			if (allPlayers == null)
			{
				return;
			}
			long num = (((Object)(object)ZNet.instance != (Object)null) ? ZNet.GetUID() : 0);
			foreach (Player item in allPlayers)
			{
				if ((Object)(object)item == (Object)null)
				{
					Logger.LogInfo((object)("[PlayerTitles][DIAG] token=" + token + " player <null entry>"));
					continue;
				}
				ZNetView component = ((Component)item).GetComponent<ZNetView>();
				bool flag = (Object)(object)component != (Object)null;
				ZDO val = (flag ? component.GetZDO() : null);
				if (val == null)
				{
					Logger.LogInfo((object)$"[PlayerTitles][DIAG] token={token} player name={item.GetPlayerName()} nviewNonNull={flag} zdoNonNull=false");
					continue;
				}
				Logger.LogInfo((object)string.Format("[PlayerTitles][DIAG] token={0} player name={1} nviewNonNull={2} zdoNonNull=true zdoid={3} ownerUid={4} isOwner={5} myUid={6} tag='{7}'", token, item.GetPlayerName(), flag, val.m_uid, val.GetOwner(), val.IsOwner(), num, val.GetString("PlayerTitles_SteamID", "<empty>")));
			}
		}
	}
}
namespace PlayerTitles.Commands
{
	public class PlayerTitlesCommands
	{
		private class AssignCommand : ConsoleCommand
		{
			public override string Name => "playertitles_assign";

			public override string Help => "Assign a title. Usage: playertitles_assign <playername> [--pos=prefix|suffix] [--color=#RRGGBB] <title text>  (prefix puts the title before the name, suffix after it, in both the nameplate and chat; the legacy spellings above|below still work)";

			public override bool IsCheat => false;

			public override bool IsSecret => false;

			public override bool OnlyServer => false;

			public override void Run(string[] args)
			{
				if (!CheckAdmin())
				{
					return;
				}
				if (!TryParseAssignArgs(args, out var playerName, out var title, out var position, out var color))
				{
					if ((Object)(object)Console.instance != (Object)null)
					{
						Console.instance.Print(((ConsoleCommand)this).Help);
					}
					else
					{
						Logger.LogInfo((object)("PlayerTitles: " + ((ConsoleCommand)this).Help));
					}
					return;
				}
				Logger.LogInfo((object)("PlayerTitles: playertitles_assign '" + playerName + "' pos=" + position + " color=" + color + " title=\"" + title + "\""));
				RpcManager.Instance.SendRequestAssign(playerName, title, position, color);
			}
		}

		private class RevokeCommand : ConsoleCommand
		{
			public override string Name => "playertitles_revoke";

			public override string Help => "Revoke a title. Usage: playertitles_revoke <playername>";

			public override bool IsCheat => false;

			public override bool IsSecret => false;

			public override bool OnlyServer => false;

			public override void Run(string[] args)
			{
				if (!CheckAdmin())
				{
					return;
				}
				if (args.Length < 1)
				{
					if ((Object)(object)Console.instance != (Object)null)
					{
						Console.instance.Print(((ConsoleCommand)this).Help);
					}
					else
					{
						Logger.LogInfo((object)("PlayerTitles: " + ((ConsoleCommand)this).Help));
					}
				}
				else
				{
					string text = args[0];
					Logger.LogInfo((object)("PlayerTitles: playertitles_revoke '" + text + "'"));
					RpcManager.Instance.SendRequestRevoke(text);
				}
			}
		}

		private class ListCommand : ConsoleCommand
		{
			public override string Name => "playertitles_list";

			public override string Help => "List every currently awarded title.";

			public override bool IsCheat => false;

			public override bool IsSecret => false;

			public override bool OnlyServer => false;

			public override void Run(string[] args)
			{
				if (CheckAdmin())
				{
					Logger.LogInfo((object)"PlayerTitles: playertitles_list");
					RpcManager.Instance.SendRequestList();
				}
			}
		}

		private class DebugZdoCommand : ConsoleCommand
		{
			public override string Name => "playertitles_debugzdo";

			public override string Help => "Diagnostic: dump SteamID ZDO tag state locally, and (from a client) ask the server to dump its own view into the server log.";

			public override bool IsCheat => false;

			public override bool IsSecret => false;

			public override bool OnlyServer => false;

			public override void Run(string[] args)
			{
				bool flag = (Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsServer();
				string text = ZdoDump.NewToken();
				ZdoDump.Begin(text, flag ? "server-local" : "client-local");
				if ((Object)(object)ZNet.instance == (Object)null)
				{
					Logger.LogInfo((object)("[PlayerTitles][DIAG] token=" + text + " ZNet.instance is null -- not in a world. Nothing further to dump."));
					ZdoDump.End(text);
					return;
				}
				if (flag)
				{
					ZdoDump.DumpPeers(text);
				}
				ZdoDump.DumpLocalPlayers(text, flag);
				ZdoDump.End(text);
				if (!flag)
				{
					Logger.LogInfo((object)("[PlayerTitles][DIAG] token=" + text + " requesting server-side dump -- look for the same token in the SERVER log"));
					RpcManager.Instance.SendRequestDebugZdo(text);
				}
			}
		}

		private static readonly Regex ColorPattern = new Regex("^#[0-9A-Fa-f]{6}$", RegexOptions.Compiled);

		public PlayerTitlesCommands()
		{
			CommandManager.Instance.AddConsoleCommand((ConsoleCommand)(object)new AssignCommand());
			CommandManager.Instance.AddConsoleCommand((ConsoleCommand)(object)new RevokeCommand());
			CommandManager.Instance.AddConsoleCommand((ConsoleCommand)(object)new ListCommand());
			CommandManager.Instance.AddConsoleCommand((ConsoleCommand)(object)new DebugZdoCommand());
		}

		private static bool CheckAdmin()
		{
			if ((Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsServer())
			{
				return true;
			}
			if (!SynchronizationManager.Instance.PlayerIsAdmin)
			{
				if ((Object)(object)Console.instance != (Object)null)
				{
					Console.instance.Print("You must be a server admin to use this command.");
				}
				else
				{
					Logger.LogInfo((object)"PlayerTitles: You must be a server admin to use this command.");
				}
				return false;
			}
			return true;
		}

		private static bool TryParseAssignArgs(string[] args, out string playerName, out string title, out string position, out string color)
		{
			playerName = null;
			title = null;
			position = "above";
			color = "#FFFFFF";
			if (args.Length < 2)
			{
				return false;
			}
			playerName = args[0];
			string text = null;
			string text2 = null;
			bool flag = false;
			bool flag2 = false;
			int num = 1;
			while (num < args.Length)
			{
				string text3 = args[num];
				if (!flag && text3.StartsWith("--pos=", StringComparison.OrdinalIgnoreCase))
				{
					text = text3.Substring("--pos=".Length);
					flag = true;
					num++;
					continue;
				}
				if (flag2 || !text3.StartsWith("--color=", StringComparison.OrdinalIgnoreCase))
				{
					break;
				}
				text2 = text3.Substring("--color=".Length);
				flag2 = true;
				num++;
			}
			if (num >= args.Length)
			{
				return false;
			}
			title = string.Join(" ", args.Skip(num));
			if (flag && !TitleEntry.TryNormalisePosition(text, out position))
			{
				string text4 = "Invalid --pos value '" + text + "' -- expected prefix|suffix (or the legacy above|below). Defaulting to 'above' (prefix).";
				if ((Object)(object)Console.instance != (Object)null)
				{
					Console.instance.Print(text4);
				}
				Logger.LogWarning((object)("PlayerTitles: " + text4));
			}
			if (flag2)
			{
				if (ColorPattern.IsMatch(text2))
				{
					color = text2;
				}
				else
				{
					string text5 = "Invalid --color value '" + text2 + "' -- defaulting to '#FFFFFF'.";
					if ((Object)(object)Console.instance != (Object)null)
					{
						Console.instance.Print(text5);
					}
					Logger.LogWarning((object)("PlayerTitles: " + text5));
					color = "#FFFFFF";
				}
			}
			return true;
		}
	}
}