Decompiled source of RollOfTheScout v1.1.2

plugins/com.peakmodding.RollOfTheScout.dll

Decompiled a week ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using ExitGames.Client.Photon;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Peak;
using Peak.Afflictions;
using PeakNativeUi;
using PeakOverheadCompat;
using Photon.Pun;
using Photon.Realtime;
using RollOfTheScout.Patches;
using RollOfTheScout.Shared;
using TMPro;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
using Zorro.Core;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("com.peakmodding.RollOfTheScout")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.1.2.0")]
[assembly: AssemblyInformationalVersion("1.1.2")]
[assembly: AssemblyProduct("com.peakmodding.RollOfTheScout")]
[assembly: AssemblyTitle("RollOfTheScout")]
[assembly: AssemblyVersion("1.1.2.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 PeakNativeUi
{
	public static class NativeFont
	{
		private static TMP_FontAsset _resolved;

		public static TMP_FontAsset Resolve()
		{
			if ((Object)(object)_resolved != (Object)null)
			{
				return _resolved;
			}
			TMP_FontAsset val = null;
			TMP_FontAsset val2 = null;
			TMP_FontAsset val3 = null;
			try
			{
				FontFallbackSwapper instance = FontFallbackSwapper.instance;
				if ((Object)(object)instance != (Object)null && (Object)(object)instance.mainBaseFont != (Object)null)
				{
					val = instance.mainBaseFont;
				}
			}
			catch
			{
			}
			try
			{
				TMP_FontAsset[] array = Resources.FindObjectsOfTypeAll<TMP_FontAsset>();
				foreach (TMP_FontAsset val4 in array)
				{
					if (!((Object)(object)val4 == (Object)null) && HasCjk(val4))
					{
						if ((Object)(object)val == (Object)null && ((Object)val4).name != null && ((Object)val4).name.IndexOf("Daruma", StringComparison.OrdinalIgnoreCase) >= 0)
						{
							val = val4;
						}
						if ((Object)(object)val2 == (Object)null && HasChar(val4, 'A'))
						{
							val2 = val4;
						}
						if ((Object)(object)val3 == (Object)null && HasChar(val4, 'A') && LooksThin(val4))
						{
							val3 = val4;
						}
					}
				}
			}
			catch
			{
			}
			if ((Object)(object)val3 == (Object)null && (Object)(object)val2 == (Object)null)
			{
				try
				{
					PlayerName[] array2 = Resources.FindObjectsOfTypeAll<PlayerName>();
					foreach (PlayerName val5 in array2)
					{
						if (!((Object)(object)val5 == (Object)null) && !((Object)(object)val5.text == (Object)null) && !((Object)(object)((TMP_Text)val5.text).font == (Object)null) && HasCjk(((TMP_Text)val5.text).font) && HasChar(((TMP_Text)val5.text).font, 'A'))
						{
							val2 = ((TMP_Text)val5.text).font;
							if ((Object)(object)val3 == (Object)null && LooksThin(((TMP_Text)val5.text).font))
							{
								val3 = ((TMP_Text)val5.text).font;
							}
							break;
						}
					}
				}
				catch
				{
				}
			}
			if ((Object)(object)val3 != (Object)null)
			{
				return _resolved = val3;
			}
			if ((Object)(object)val2 != (Object)null)
			{
				return _resolved = val2;
			}
			if ((Object)(object)val != (Object)null && HasCjk(val) && HasChar(val, 'A'))
			{
				return _resolved = val;
			}
			try
			{
				TMP_FontAsset defaultFontAsset = TMP_Settings.defaultFontAsset;
				if ((Object)(object)defaultFontAsset != (Object)null && HasCjk(defaultFontAsset))
				{
					return _resolved = defaultFontAsset;
				}
			}
			catch
			{
			}
			return null;
		}

		private static bool LooksThin(TMP_FontAsset f)
		{
			if ((Object)(object)f == (Object)null || string.IsNullOrEmpty(((Object)f).name))
			{
				return false;
			}
			string name = ((Object)f).name;
			if (name.IndexOf("Daruma", StringComparison.OrdinalIgnoreCase) >= 0)
			{
				return false;
			}
			if (name.IndexOf("Noto", StringComparison.OrdinalIgnoreCase) < 0 && name.IndexOf("Sans", StringComparison.OrdinalIgnoreCase) < 0 && name.IndexOf("SourceHan", StringComparison.OrdinalIgnoreCase) < 0 && name.IndexOf("Source Han", StringComparison.OrdinalIgnoreCase) < 0 && name.IndexOf("CJK", StringComparison.OrdinalIgnoreCase) < 0 && name.IndexOf("Chinese", StringComparison.OrdinalIgnoreCase) < 0 && name.IndexOf("Hei", StringComparison.OrdinalIgnoreCase) < 0)
			{
				return name.IndexOf("Gothic", StringComparison.OrdinalIgnoreCase) >= 0;
			}
			return true;
		}

		private static bool HasChar(TMP_FontAsset font, char c)
		{
			try
			{
				return (Object)(object)font != (Object)null && font.HasCharacter(c, true, false);
			}
			catch
			{
				return false;
			}
		}

		private static bool HasCjk(TMP_FontAsset font)
		{
			try
			{
				return (Object)(object)font != (Object)null && font.HasCharacter('职', true, false);
			}
			catch
			{
				return false;
			}
		}
	}
	public static class NativeOverlay
	{
		private sealed class Block
		{
			public RectTransform Root;

			public Image Bg;

			public TextMeshProUGUI Text;

			public float WidthFraction;

			public bool Bold;

			public string Last;

			public float LastSize;

			public int LastScreenHeight;

			public int LastVersion = -1;
		}

		private static bool _ready;

		private static float _nextTry;

		private static GameObject _root;

		private static Block _toast;

		private static Block _panel;

		private static Block _intro;

		public static bool Ready
		{
			get
			{
				if (_ready && (Object)(object)_root == (Object)null)
				{
					_ready = false;
				}
				return _ready;
			}
		}

		public static bool Ensure()
		{
			if (Ready)
			{
				return true;
			}
			if (Time.time < _nextTry)
			{
				return false;
			}
			_nextTry = Time.time + 1f;
			try
			{
				TMP_FontAsset val = NativeFont.Resolve();
				if ((Object)(object)val == (Object)null)
				{
					return false;
				}
				Build(val);
				_ready = true;
				return true;
			}
			catch (Exception ex)
			{
				Debug.LogWarning((object)("[PeakNativeUi] 创建原生字体叠加层失败(将回退旧绘制):" + ex.Message));
				return false;
			}
		}

		public static void SetToast(IList<string> lines)
		{
			if (Ready)
			{
				SetBlock(_toast, Join(lines, null), _toast.Bold);
				PositionToast();
			}
		}

		public static void SetToast(IList<string> lines, int version)
		{
			if (Ready)
			{
				Block toast = _toast;
				if (toast != null && (version < 0 || version != toast.LastVersion))
				{
					toast.LastVersion = version;
					SetBlock(toast, Join(lines, null), toast.Bold);
					PositionToast();
				}
			}
		}

		public static void SetPanel(string title, IList<string> lines, bool visible)
		{
			SetPanel(title, lines, visible, -1);
		}

		public static void SetPanel(string title, IList<string> lines, bool visible, int version)
		{
			if (Ready)
			{
				Block panel = _panel;
				if (!visible)
				{
					SetBlock(panel, null, panel.Bold);
					panel.LastVersion = int.MinValue;
					SetBlock(_intro, null, _intro.Bold);
					_intro.LastVersion = int.MinValue;
					PositionToast();
				}
				else if (version < 0 || version != panel.LastVersion)
				{
					panel.LastVersion = version;
					SetBlock(panel, Join(lines, title), panel.Bold);
				}
			}
		}

		public static void SetPanelHidden()
		{
			if (Ready)
			{
				SetBlock(_panel, null, _panel.Bold);
				_panel.LastVersion = int.MinValue;
				SetBlock(_intro, null, _intro.Bold);
				_intro.LastVersion = int.MinValue;
				PositionToast();
			}
		}

		public static void SetClassIntro(string title, IList<string> lines, bool visible)
		{
			SetClassIntro(title, lines, visible, -1);
		}

		public static void SetClassIntro(string title, IList<string> lines, bool visible, int version)
		{
			if (!Ready)
			{
				return;
			}
			Block intro = _intro;
			if (!visible)
			{
				SetBlock(intro, null, intro.Bold);
				intro.LastVersion = int.MinValue;
				PositionToast();
				return;
			}
			if (version < 0 || version != intro.LastVersion)
			{
				intro.LastVersion = version;
				SetBlock(intro, Join(lines, title), intro.Bold);
			}
			PositionToast();
		}

		public static void SetClassIntroHidden()
		{
			if (Ready)
			{
				SetBlock(_intro, null, _intro.Bold);
				_intro.LastVersion = int.MinValue;
				PositionToast();
			}
		}

		private static void PositionToast()
		{
			//IL_0077: 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)
			Block toast = _toast;
			if (toast != null && !((Object)(object)toast.Root == (Object)null))
			{
				float num = 12f;
				if (_intro != null && (Object)(object)_intro.Root != (Object)null && ((Component)_intro.Root).gameObject.activeSelf)
				{
					num += _intro.Root.sizeDelta.y + 8f;
				}
				toast.Root.anchoredPosition = new Vector2(12f, 0f - num);
			}
		}

		public static void Dispose()
		{
			try
			{
				if ((Object)(object)_root != (Object)null)
				{
					Object.Destroy((Object)(object)_root);
				}
			}
			catch
			{
			}
			_root = null;
			_toast = null;
			_panel = null;
			_intro = null;
			_ready = false;
			_nextTry = 0f;
		}

		private static void Build(TMP_FontAsset font)
		{
			//IL_0018: Unknown result type (might be due to invalid IL or missing references)
			//IL_0022: Expected O, but got Unknown
			_root = new GameObject("RollOfTheScout.NativeOverlay", new Type[1] { typeof(RectTransform) });
			Object.DontDestroyOnLoad((Object)(object)_root);
			Canvas obj = _root.AddComponent<Canvas>();
			obj.renderMode = (RenderMode)0;
			obj.sortingOrder = 32000;
			_toast = CreateBlock("Toast", font, rightSide: false, 0.45f, bold: false);
			_panel = CreateBlock("ClassPanel", font, rightSide: true, 0.32f, bold: false);
			_intro = CreateBlock("ClassIntro", font, rightSide: false, 0.4f, bold: false);
		}

		private static Block CreateBlock(string name, TMP_FontAsset font, bool rightSide, float widthFraction, bool bold)
		{
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			//IL_001a: Expected O, but got Unknown
			//IL_0056: Unknown result type (might be due to invalid IL or missing references)
			//IL_005d: Unknown result type (might be due to invalid IL or missing references)
			//IL_006b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0094: Unknown result type (might be due to invalid IL or missing references)
			//IL_0083: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cf: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fd: Unknown result type (might be due to invalid IL or missing references)
			//IL_010f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0128: Unknown result type (might be due to invalid IL or missing references)
			//IL_012f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0141: Unknown result type (might be due to invalid IL or missing references)
			//IL_0155: Unknown result type (might be due to invalid IL or missing references)
			//IL_01af: Unknown result type (might be due to invalid IL or missing references)
			GameObject val = new GameObject(name, new Type[1] { typeof(RectTransform) });
			val.transform.SetParent(_root.transform, false);
			RectTransform component = val.GetComponent<RectTransform>();
			float num = (rightSide ? 1f : 0f);
			Vector2 val2 = default(Vector2);
			((Vector2)(ref val2))..ctor(num, 1f);
			component.anchorMax = val2;
			component.anchorMin = val2;
			component.pivot = new Vector2(num, 1f);
			component.anchoredPosition = (rightSide ? new Vector2(-12f, -12f) : new Vector2(12f, -12f));
			component.sizeDelta = new Vector2(400f, 40f);
			Image val3 = val.AddComponent<Image>();
			((Graphic)val3).color = new Color(0f, 0f, 0f, 0.6f);
			((Graphic)val3).raycastTarget = false;
			GameObject val4 = new GameObject("Text", new Type[1] { typeof(RectTransform) });
			val4.transform.SetParent(val.transform, false);
			RectTransform component2 = val4.GetComponent<RectTransform>();
			((Vector2)(ref val2))..ctor(0f, 1f);
			component2.anchorMax = val2;
			component2.anchorMin = val2;
			component2.pivot = new Vector2(0f, 1f);
			component2.anchoredPosition = new Vector2(10f, -6f);
			TextMeshProUGUI val5 = val4.AddComponent<TextMeshProUGUI>();
			((TMP_Text)val5).font = font;
			if ((Object)(object)((TMP_Asset)font).material != (Object)null)
			{
				((TMP_Text)val5).fontSharedMaterial = ((TMP_Asset)font).material;
			}
			((Graphic)val5).raycastTarget = false;
			((TMP_Text)val5).alignment = (TextAlignmentOptions)257;
			((TMP_Text)val5).textWrappingMode = (TextWrappingModes)1;
			((TMP_Text)val5).overflowMode = (TextOverflowModes)0;
			((Graphic)val5).color = Color.white;
			((TMP_Text)val5).fontStyle = (FontStyles)(bold ? 1 : 0);
			return new Block
			{
				Root = component,
				Bg = val3,
				Text = val5,
				WidthFraction = widthFraction,
				Bold = bold
			};
		}

		private static void SetBlock(Block b, string content, bool bold)
		{
			//IL_00f4: Unknown result type (might be due to invalid IL or missing references)
			//IL_0115: Unknown result type (might be due to invalid IL or missing references)
			//IL_012d: Unknown result type (might be due to invalid IL or missing references)
			if (b == null || (Object)(object)b.Root == (Object)null)
			{
				return;
			}
			if (string.IsNullOrEmpty(content))
			{
				if (((Component)b.Root).gameObject.activeSelf)
				{
					((Component)b.Root).gameObject.SetActive(false);
				}
				b.Last = null;
				return;
			}
			if (!((Component)b.Root).gameObject.activeSelf)
			{
				((Component)b.Root).gameObject.SetActive(true);
			}
			float num = CurrentFontSize();
			if (!(content == b.Last) || !Mathf.Approximately(num, b.LastSize) || Screen.height != b.LastScreenHeight)
			{
				b.Last = content;
				b.LastSize = num;
				b.LastScreenHeight = Screen.height;
				float num2 = (float)Screen.width * b.WidthFraction;
				TextMeshProUGUI text = b.Text;
				((TMP_Text)text).fontSize = num;
				((TMP_Text)text).fontStyle = (FontStyles)(bold ? 1 : 0);
				((TMP_Text)text).text = content;
				RectTransform rectTransform = ((TMP_Text)text).rectTransform;
				rectTransform.sizeDelta = new Vector2(num2 - 20f, 4000f);
				((TMP_Text)text).ForceMeshUpdate(false, false);
				float preferredHeight = ((TMP_Text)text).preferredHeight;
				rectTransform.sizeDelta = new Vector2(num2 - 20f, preferredHeight);
				b.Root.sizeDelta = new Vector2(num2, preferredHeight + 12f);
			}
		}

		private static float CurrentFontSize()
		{
			return Mathf.Max(16, Mathf.RoundToInt((float)Screen.height / 48f));
		}

		private static string Join(IList<string> lines, string title)
		{
			if ((lines == null || lines.Count == 0) && string.IsNullOrEmpty(title))
			{
				return null;
			}
			StringBuilder stringBuilder = new StringBuilder();
			if (!string.IsNullOrEmpty(title))
			{
				stringBuilder.Append("<color=#FFE666>").Append(title).Append("</color>");
			}
			if (lines != null)
			{
				for (int i = 0; i < lines.Count; i++)
				{
					if (!string.IsNullOrEmpty(lines[i]))
					{
						if (stringBuilder.Length > 0)
						{
							stringBuilder.Append('\n');
						}
						stringBuilder.Append(lines[i]);
					}
				}
			}
			if (stringBuilder.Length != 0)
			{
				return stringBuilder.ToString();
			}
			return null;
		}
	}
}
namespace PeakOverheadCompat
{
	public static class ThirdPartyOverheadNameCompat
	{
		internal static class BpdExtractNamePatch
		{
			public static void Postfix(string objName, ref string __result)
			{
				try
				{
					if (Active && !string.IsNullOrEmpty(__result) && TryActorFromObjName(objName, out var actor) && !string.IsNullOrEmpty(ClassNameOf(actor)))
					{
						__result = Rebuild(__result, actor);
					}
				}
				catch
				{
				}
			}
		}

		internal static class BpdLabelPatch
		{
			private sealed class LabelState
			{
				public string Original;

				public string Desired;
			}

			private static readonly ConditionalWeakTable<object, LabelState> States = new ConditionalWeakTable<object, LabelState>();

			public static void Postfix(object __instance)
			{
				try
				{
					if (__instance == null || _bpdDisplayNameField == null || _bpdCharacterField == null || _bpdLastContentField == null)
					{
						return;
					}
					object? value = _bpdCharacterField.GetValue(__instance);
					Character val = (Character)((value is Character) ? value : null);
					if (!((Object)(object)val == (Object)null) && !((Object)(object)((MonoBehaviourPun)val).photonView == (Object)null) && ((MonoBehaviourPun)val).photonView.Owner != null)
					{
						int actorNumber = ((MonoBehaviourPun)val).photonView.Owner.ActorNumber;
						LabelState orCreateValue = States.GetOrCreateValue(__instance);
						if (orCreateValue.Original == null)
						{
							orCreateValue.Original = Strip(_bpdDisplayNameField.GetValue(__instance) as string);
						}
						string text = (string.IsNullOrEmpty(orCreateValue.Original) ? orCreateValue.Original : Rebuild(orCreateValue.Original, actorNumber));
						if (!(orCreateValue.Desired == text))
						{
							orCreateValue.Desired = text;
							_bpdDisplayNameField.SetValue(__instance, text);
							_bpdLastContentField.SetValue(__instance, null);
						}
					}
				}
				catch
				{
				}
			}
		}

		internal static class SodRefreshPatch
		{
			public static void Prefix(object __instance, ref string name)
			{
				try
				{
					if (__instance == null || string.IsNullOrEmpty(name) || !Active || _sodEntriesField == null || _sodEntryLabelField == null || (_sodInstanceProp == null && _sodInstanceField == null))
					{
						return;
					}
					object obj = ((_sodInstanceProp != null) ? _sodInstanceProp.GetValue(null) : _sodInstanceField.GetValue(null));
					if (obj == null || !(_sodEntriesField.GetValue(obj) is IDictionary dictionary))
					{
						return;
					}
					foreach (DictionaryEntry item in dictionary)
					{
						if (_sodEntryLabelField.GetValue(item.Value) == __instance)
						{
							object key = item.Key;
							Character val = (Character)((key is Character) ? key : null);
							if (val != null && !((Object)(object)((MonoBehaviourPun)val).photonView == (Object)null) && ((MonoBehaviourPun)val).photonView.Owner != null)
							{
								name = Rebuild(name, ((MonoBehaviourPun)val).photonView.Owner.ActorNumber);
							}
							break;
						}
					}
				}
				catch
				{
				}
			}
		}

		private const string BpdDll = "BetterPlayerDistance.dll";

		private const string SodDll = "SenseOfDirection.dll";

		private static Func<int, string> _classNameByActor;

		private static Action<string> _warn;

		private static bool _initialized;

		private static readonly Dictionary<int, string> _colorByViewId = new Dictionary<int, string>();

		private static readonly Dictionary<int, Character> _characterByActor = new Dictionary<int, Character>();

		private static float _nextTry;

		private static bool _bpdDone;

		private static bool _sodDone;

		private static bool _bpdApplied;

		private static bool _sodApplied;

		private static bool _loggedApplied;

		private static FieldInfo _bpdCharacterField;

		private static FieldInfo _bpdDisplayNameField;

		private static FieldInfo _bpdLastContentField;

		private static FieldInfo _sodEntriesField;

		private static FieldInfo _sodEntryLabelField;

		private static PropertyInfo _sodInstanceProp;

		private static FieldInfo _sodInstanceField;

		private const string ColorFallback = "FFFFFF";

		private const float ColorRetrySeconds = 0.5f;

		private static readonly Dictionary<int, float> _colorRetryAt = new Dictionary<int, float>();

		internal static bool Active
		{
			get
			{
				if (_initialized)
				{
					return _classNameByActor != null;
				}
				return false;
			}
		}

		public static void Initialize(Func<int, string> classNameByActor, Action<string> warn)
		{
			_classNameByActor = classNameByActor;
			_warn = warn;
			_initialized = true;
		}

		public static void Tick(Harmony harmony)
		{
			if (_initialized && harmony != null && (!_bpdDone || !_sodDone) && !(Time.time < _nextTry))
			{
				_nextTry = Time.time + 1f;
				if (!_bpdDone)
				{
					_bpdDone = !Installed("BetterPlayerDistance.dll") || ApplyBpd(harmony);
				}
				if (!_sodDone)
				{
					_sodDone = !Installed("SenseOfDirection.dll") || ApplySod(harmony);
				}
				if (_bpdApplied && _sodApplied && !_loggedApplied)
				{
					_loggedApplied = true;
					Warn("[兼容] 已挂载第三方头顶id显示模组兼容补丁(BetterPlayerDistance / Sense of Direction)。");
				}
			}
		}

		private static bool Installed(string dllFileName)
		{
			try
			{
				string pluginPath = Paths.PluginPath;
				if (!string.IsNullOrEmpty(pluginPath) && Directory.Exists(pluginPath))
				{
					return Directory.GetFiles(pluginPath, dllFileName, SearchOption.AllDirectories).Length != 0;
				}
			}
			catch
			{
			}
			return false;
		}

		private static void Warn(string msg)
		{
			try
			{
				_warn?.Invoke(msg);
			}
			catch
			{
			}
		}

		private static Type FindType(string assemblyName, string simpleName)
		{
			try
			{
				Assembly assembly = FindAssembly(assemblyName);
				if (assembly != null)
				{
					Type[] types = assembly.GetTypes();
					foreach (Type type in types)
					{
						if (type != null && type.Name == simpleName)
						{
							return type;
						}
					}
				}
			}
			catch
			{
			}
			return null;
		}

		private static Assembly FindAssembly(string simpleName)
		{
			try
			{
				Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
				foreach (Assembly assembly in assemblies)
				{
					if (assembly != null && assembly.GetName().Name == simpleName)
					{
						return assembly;
					}
				}
			}
			catch
			{
			}
			return null;
		}

		private static bool ApplyBpd(Harmony harmony)
		{
			//IL_00e3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f0: Expected O, but got Unknown
			//IL_010a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0117: Expected O, but got Unknown
			try
			{
				Type type = FindType("BetterPlayerDistance", "Tracking");
				Type type2 = FindType("BetterPlayerDistance", "PlayerLabel");
				if (type == null || type2 == null)
				{
					return false;
				}
				MethodInfo method = type.GetMethod("ExtractDisplayName", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
				MethodInfo method2 = type2.GetMethod("UpdateState", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
				_bpdCharacterField = type2.GetField("_character", BindingFlags.Instance | BindingFlags.NonPublic);
				_bpdDisplayNameField = type2.GetField("_displayName", BindingFlags.Instance | BindingFlags.NonPublic);
				_bpdLastContentField = type2.GetField("_lastContent", BindingFlags.Instance | BindingFlags.NonPublic);
				if (method == null || method2 == null || _bpdCharacterField == null || _bpdDisplayNameField == null || _bpdLastContentField == null)
				{
					return false;
				}
				harmony.Patch((MethodBase)method, (HarmonyMethod)null, new HarmonyMethod(AccessTools.Method(typeof(BpdExtractNamePatch), "Postfix", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				harmony.Patch((MethodBase)method2, (HarmonyMethod)null, new HarmonyMethod(AccessTools.Method(typeof(BpdLabelPatch), "Postfix", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				_bpdApplied = true;
				return true;
			}
			catch (Exception ex)
			{
				Warn("[兼容] BetterPlayerDistance 补丁失败:" + ex.Message);
				return true;
			}
		}

		private static bool ApplySod(Harmony harmony)
		{
			//IL_0165: Unknown result type (might be due to invalid IL or missing references)
			//IL_0173: Expected O, but got Unknown
			try
			{
				Type type = FindType("SenseOfDirection", "PlayerLabelController");
				Type type2 = FindType("SenseOfDirection", "PlayerLabel");
				if (type == null || type2 == null)
				{
					return false;
				}
				_sodEntriesField = type.GetField("_entries", BindingFlags.Instance | BindingFlags.NonPublic);
				_sodInstanceProp = type.GetProperty("Instance", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
				_sodInstanceField = ((_sodInstanceProp == null) ? type.GetField("_instance", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic) : null);
				_sodEntryLabelField = type.GetNestedType("Entry", BindingFlags.NonPublic)?.GetField("Label", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
				MethodInfo methodInfo = null;
				MethodInfo[] methods = type2.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
				foreach (MethodInfo methodInfo2 in methods)
				{
					if (!(methodInfo2.Name != "Refresh"))
					{
						ParameterInfo[] parameters = methodInfo2.GetParameters();
						if (parameters.Length >= 1 && parameters[0].ParameterType == typeof(string))
						{
							methodInfo = methodInfo2;
							break;
						}
					}
				}
				if (methodInfo == null || _sodEntriesField == null || (_sodInstanceProp == null && _sodInstanceField == null) || _sodEntryLabelField == null)
				{
					return false;
				}
				harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(AccessTools.Method(typeof(SodRefreshPatch), "Prefix", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				_sodApplied = true;
				return true;
			}
			catch (Exception ex)
			{
				Warn("[兼容] Sense of Direction 补丁失败:" + ex.Message);
				return true;
			}
		}

		internal static string ClassNameOf(int actor)
		{
			if (_classNameByActor == null)
			{
				return null;
			}
			try
			{
				return _classNameByActor(actor);
			}
			catch
			{
				return null;
			}
		}

		internal static Character CharacterOf(int actor)
		{
			if (_characterByActor.TryGetValue(actor, out var value) && (Object)(object)value != (Object)null)
			{
				return value;
			}
			List<Character> allCharacters = Character.AllCharacters;
			if (allCharacters != null)
			{
				for (int i = 0; i < allCharacters.Count; i++)
				{
					Character val = allCharacters[i];
					if (!((Object)(object)val == (Object)null) && !((Object)(object)((MonoBehaviourPun)val).photonView == (Object)null) && ((MonoBehaviourPun)val).photonView.Owner != null && ((MonoBehaviourPun)val).photonView.Owner.ActorNumber == actor)
					{
						_characterByActor[actor] = val;
						return val;
					}
				}
			}
			return null;
		}

		internal static string ColorHexOf(int actor)
		{
			Character val = CharacterOf(actor);
			int num = (((Object)(object)val != (Object)null && (Object)(object)((MonoBehaviourPun)val).photonView != (Object)null) ? ((MonoBehaviourPun)val).photonView.ViewID : 0);
			if (num != 0 && _colorByViewId.TryGetValue(num, out var value))
			{
				return value;
			}
			if (_colorRetryAt.TryGetValue(actor, out var value2) && Time.time < value2)
			{
				return "FFFFFF";
			}
			if (TryReadColorOf(val, out var hex))
			{
				if (num != 0)
				{
					_colorByViewId[num] = hex;
				}
				return hex;
			}
			_colorRetryAt[actor] = Time.time + 0.5f;
			return "FFFFFF";
		}

		private static bool TryReadColorOf(Character ch, out string hex)
		{
			//IL_003b: Unknown result type (might be due to invalid IL or missing references)
			hex = "FFFFFF";
			try
			{
				if ((Object)(object)ch == (Object)null || ch.refs == null || (Object)(object)ch.refs.customization == (Object)null)
				{
					return false;
				}
				hex = ColorUtility.ToHtmlStringRGB(ch.refs.customization.PlayerColor);
				return true;
			}
			catch
			{
				return false;
			}
		}

		internal static string BuildPrefix(int actor)
		{
			if (!Active)
			{
				return null;
			}
			string text = ClassNameOf(actor);
			if (string.IsNullOrEmpty(text))
			{
				return null;
			}
			return "<color=#" + ColorHexOf(actor) + ">【" + text + "】</color>";
		}

		internal static string Strip(string text)
		{
			if (string.IsNullOrEmpty(text))
			{
				return text;
			}
			if (!text.StartsWith("<color=#", StringComparison.Ordinal))
			{
				return text;
			}
			int num = text.IndexOf("</color>", StringComparison.Ordinal);
			if (num < 0)
			{
				return text;
			}
			int num2 = text.IndexOf('】');
			if (num2 < 0 || num2 > num)
			{
				return text;
			}
			return text.Substring(num + "</color>".Length);
		}

		internal static string Rebuild(string text, int actor)
		{
			string text2 = Strip(text);
			string text3 = BuildPrefix(actor);
			if (text3 != null)
			{
				return text3 + text2;
			}
			return text2;
		}

		internal static bool TryActorFromObjName(string objName, out int actor)
		{
			actor = 0;
			if (string.IsNullOrEmpty(objName))
			{
				return false;
			}
			int num = objName.IndexOf('[');
			int num2 = objName.LastIndexOf(':');
			if (num == -1 || num2 == -1 || num2 <= num)
			{
				return false;
			}
			return int.TryParse(objName.Substring(num2 + 1).Trim().TrimEnd(']')
				.Trim(), out actor);
		}
	}
}
namespace RollOfTheScout
{
	public static class ClassDisplayNet
	{
		public const string ModId = "com.peakmodding.RollOfTheScout";

		public const byte EventCode = 197;

		public const byte KindClassSnapshot = 1;

		public const byte KindToast = 2;

		public const byte KindClassNames = 3;

		public const byte KindClassKeywords = 4;

		public const byte KindClassIntro = 5;

		public const byte KindTargetedToast = 6;

		public const byte KindModeSnapshot = 11;

		public const byte KindModeOff = 12;

		public const byte KindMilesClientHello = 7;

		public const byte KindMilesUsageReport = 9;

		public const byte KindMilesClientReady = 10;

		public const byte KindMilesGameplayState = 8;

		public const float ClassTextResendSeconds = 10f;

		public const float SnapshotIntervalSeconds = 5f;

		public static void BroadcastClassSnapshot(IList<KeyValuePair<int, int>> actorClassPairs)
		{
			if (!ShouldBroadcast() || actorClassPairs == null || actorClassPairs.Count == 0)
			{
				return;
			}
			StringBuilder stringBuilder = new StringBuilder();
			for (int i = 0; i < actorClassPairs.Count; i++)
			{
				if (i > 0)
				{
					stringBuilder.Append(';');
				}
				stringBuilder.Append(actorClassPairs[i].Key).Append('=').Append(actorClassPairs[i].Value);
			}
			Send(1, stringBuilder.ToString());
		}

		public static void BroadcastClassNames(IList<KeyValuePair<int, string>> codeToName)
		{
			if (!ShouldBroadcast() || codeToName == null || codeToName.Count == 0)
			{
				return;
			}
			StringBuilder stringBuilder = new StringBuilder();
			for (int i = 0; i < codeToName.Count; i++)
			{
				if (i > 0)
				{
					stringBuilder.Append(';');
				}
				stringBuilder.Append(codeToName[i].Key).Append('=').Append(Sanitize(codeToName[i].Value));
			}
			Send(3, stringBuilder.ToString());
		}

		public static void BroadcastClassKeywords(IList<KeyValuePair<int, string>> codeToKeywords)
		{
			SendTextMap(4, codeToKeywords);
		}

		public static void BroadcastClassIntro(IList<KeyValuePair<int, string>> codeToIntro)
		{
			SendTextMap(5, codeToIntro);
		}

		public static void BroadcastTargetedToast(int actorNumber, string text)
		{
			if (ShouldBroadcast() && actorNumber > 0 && !string.IsNullOrEmpty(text))
			{
				Send(6, actorNumber + "\t" + SanitizeToast(text));
			}
		}

		private static void SendTextMap(byte kind, IList<KeyValuePair<int, string>> pairs)
		{
			if (!ShouldBroadcast() || pairs == null || pairs.Count == 0)
			{
				return;
			}
			StringBuilder stringBuilder = new StringBuilder();
			for (int i = 0; i < pairs.Count; i++)
			{
				if (i > 0)
				{
					stringBuilder.Append(';');
				}
				stringBuilder.Append(pairs[i].Key).Append('=').Append(Sanitize(pairs[i].Value));
			}
			Send(kind, stringBuilder.ToString());
		}

		private static string Sanitize(string name)
		{
			if (string.IsNullOrEmpty(name))
			{
				return string.Empty;
			}
			return name.Replace(';', '_').Replace('=', '_').Replace('\n', ' ')
				.Replace('\r', ' ');
		}

		private static string SanitizeToast(string text)
		{
			if (string.IsNullOrEmpty(text))
			{
				return string.Empty;
			}
			return text.Replace('\t', ' ');
		}

		public static void BroadcastToast(string text)
		{
			if (ShouldBroadcast() && !string.IsNullOrEmpty(text))
			{
				Send(2, text);
			}
		}

		public static void BroadcastModeSnapshot(IList<KeyValuePair<int, int>> actorStatusPairs)
		{
			if (!PhotonNetwork.InRoom || !PhotonNetwork.IsMasterClient)
			{
				return;
			}
			if (actorStatusPairs == null || actorStatusPairs.Count == 0)
			{
				BroadcastModeOff();
				return;
			}
			StringBuilder stringBuilder = new StringBuilder(actorStatusPairs.Count * 8);
			for (int i = 0; i < actorStatusPairs.Count; i++)
			{
				if (i > 0)
				{
					stringBuilder.Append(';');
				}
				stringBuilder.Append(actorStatusPairs[i].Key).Append('=').Append(actorStatusPairs[i].Value);
			}
			SendForced(11, stringBuilder.ToString());
		}

		public static void BroadcastModeOff()
		{
			if (PhotonNetwork.InRoom && PhotonNetwork.IsMasterClient)
			{
				SendForced(12, "0");
			}
		}

		public static void BroadcastModeToast(string text)
		{
			if (PhotonNetwork.InRoom && PhotonNetwork.IsMasterClient && !string.IsNullOrEmpty(text))
			{
				SendForced(2, text);
			}
		}

		public static void SendMilesGameplayState(int actorNumber, string data)
		{
			//IL_003d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0043: Expected O, but got Unknown
			//IL_0054: Unknown result type (might be due to invalid IL or missing references)
			if (!PhotonNetwork.InRoom || !PhotonNetwork.IsMasterClient || actorNumber <= 0 || string.IsNullOrEmpty(data))
			{
				return;
			}
			try
			{
				object[] obj = new object[3]
				{
					"com.peakmodding.RollOfTheScout",
					(byte)8,
					data
				};
				RaiseEventOptions val = new RaiseEventOptions();
				val.TargetActors = new int[1] { actorNumber };
				PhotonNetwork.RaiseEvent((byte)197, (object)obj, val, SendOptions.SendReliable);
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("[RollOfTheScout] 残魂本机配置同步失败:" + ex.Message));
			}
		}

		private static bool ShouldBroadcast()
		{
			if (!ModConfig.General.DisplaySync.Value)
			{
				return false;
			}
			if (!PhotonNetwork.InRoom)
			{
				return false;
			}
			return PhotonNetwork.IsMasterClient;
		}

		private static void Send(byte kind, string data)
		{
			//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_0029: Unknown result type (might be due to invalid IL or missing references)
			//IL_002e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0038: Expected O, but got Unknown
			try
			{
				object[] array = new object[3] { "com.peakmodding.RollOfTheScout", kind, data };
				if (!PhotonNetwork.RaiseEvent((byte)197, (object)array, new RaiseEventOptions
				{
					Receivers = (ReceiverGroup)0
				}, SendOptions.SendReliable))
				{
					Plugin.Log.LogWarning((object)$"[RollOfTheScout] 职业显示广播未被 Photon 接受(kind={kind},payload={array.Length} 段)。");
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)$"[RollOfTheScout] 职业显示网络广播失败(kind={kind}):{ex.Message}");
			}
		}

		private static void SendForced(byte kind, string data)
		{
			//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_0029: Unknown result type (might be due to invalid IL or missing references)
			//IL_002e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0038: Expected O, but got Unknown
			try
			{
				object[] array = new object[3] { "com.peakmodding.RollOfTheScout", kind, data };
				if (!PhotonNetwork.RaiseEvent((byte)197, (object)array, new RaiseEventOptions
				{
					Receivers = (ReceiverGroup)0
				}, SendOptions.SendReliable))
				{
					Plugin.Log.LogWarning((object)$"[RollOfTheScout] 玩法模式广播未被 Photon 接受(kind={kind},payload={array.Length} 段)。");
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)$"[RollOfTheScout] 玩法模式网络广播失败(kind={kind}):{ex.Message}");
			}
		}

		public static List<KeyValuePair<int, int>> ParseSnapshot(string data)
		{
			List<KeyValuePair<int, int>> list = new List<KeyValuePair<int, int>>();
			if (string.IsNullOrEmpty(data))
			{
				return list;
			}
			string[] array = data.Split(';');
			foreach (string text in array)
			{
				if (!string.IsNullOrEmpty(text))
				{
					int num = text.IndexOf('=');
					if (num > 0 && int.TryParse(text.Substring(0, num), out var result) && int.TryParse(text.Substring(num + 1), out var result2))
					{
						list.Add(new KeyValuePair<int, int>(result, result2));
					}
				}
			}
			return list;
		}

		public static List<KeyValuePair<int, string>> ParseClassNames(string data)
		{
			List<KeyValuePair<int, string>> list = new List<KeyValuePair<int, string>>();
			if (string.IsNullOrEmpty(data))
			{
				return list;
			}
			string[] array = data.Split(';');
			foreach (string text in array)
			{
				if (!string.IsNullOrEmpty(text))
				{
					int num = text.IndexOf('=');
					if (num > 0 && int.TryParse(text.Substring(0, num), out var result) && num + 1 < text.Length)
					{
						list.Add(new KeyValuePair<int, string>(result, text.Substring(num + 1)));
					}
				}
			}
			return list;
		}
	}
	internal static class ClassIntroText
	{
		public static string TitlePrefix => "[职业介绍 / Class Intro] ";

		public static string Keywords(int code)
		{
			string text = KeywordsEn(code);
			string text2 = KeywordsZh(code);
			if (string.IsNullOrEmpty(text) && string.IsNullOrEmpty(text2))
			{
				return string.Empty;
			}
			if (string.IsNullOrEmpty(text2))
			{
				return "Keywords: " + text;
			}
			if (string.IsNullOrEmpty(text))
			{
				return "关键词:" + text2;
			}
			return "Keywords: " + text + "\n关键词:" + text2;
		}

		public static string Intro(int code)
		{
			string text = IntroEn(code);
			string text2 = IntroZh(code);
			if (string.IsNullOrEmpty(text))
			{
				return text2;
			}
			if (string.IsNullOrEmpty(text2))
			{
				return text;
			}
			return text + "\n" + text2;
		}

		public static string KeywordsLine(int code)
		{
			return Keywords(code);
		}

		public static string IntroLine(int code)
		{
			return Intro(code);
		}

		private static string KeywordsZh(int code)
		{
			return code switch
			{
				1 => "每关随机道具、一次性昏迷复活、高饥饿消耗、无限周期加速", 
				2 => "范围增益、开局负债、绑定全员生死、亡语激励、额外神秘物品", 
				3 => "周期随机补给、开局背包、初始高石化", 
				4 => "每关医疗物资、范围减伤解毒、队友死亡自伤惩罚", 
				5 => "初始武器、匕首吹箭、拾取转化", 
				6 => "范围潘多拉、拾取转化、概率消失、持续自增诅咒", 
				7 => "骸骨之书、死亡增益、骷髅强化、初始高诅咒、诅咒与增益运营", 
				8 => "负面状态转化、无负面周期惩罚、周期负面耐受", 
				9 => "毒孢抗性、自然食物、周期粉蕉、禁用工具", 
				10 => "开局随机物资腰包、独行流血、集体行动、同伴增益", 
				11 => "检查点旗标、插旗范围清负、自身插旗石化、全员随机增益、团队爆发", 
				12 => "跳舞回体、强制共舞、周期后空翻、摔倒风险", 
				13 => "周期产蛋、传染转职、同类大范围回血、周期后空翻", 
				14 => "禁用物品、拾取消失、周期重置、受伤放大、职业免疫", 
				15 => "周期卷轴、双形态、随机效果、负面代价", 
				16 => "初始高负面、自带黄金抓钩、抓人高惩罚、潘多拉免疫", 
				17 => "便携烤炉、烤物解饿、烤多中毒、非食物奖励、持炉炎热", 
				18 => "便携烤炉、生食烹饪、群体增益、熟食增益、生食惩罚、饥饿昏迷", 
				19 => "踢击替代拉人、踢中回体、踢空困倦、初始诅咒", 
				20 => "开局背包、物品囤积、负重奖励、周期加速、额外体力", 
				21 => "昏迷救援、传送无敌、救起加速、背人治疗、死亡惩罚", 
				22 => "防晒喷雾、每关刷新、强制覆盖、防晒回体、用完额外效果", 
				23 => "独行奖励、结伴惩罚、半径检测", 
				24 => "童子军大炮、每关与周期获得、受伤炸药、主手风险", 
				25 => "喇叭乐器、演奏范围增益、拾取转化、库存奖励、初始诅咒、特殊转职", 
				26 => "童军宝石、诅咒锁定、拾取消失、特殊增益、零号守则、天底解放", 
				27 => "早起虫儿、攀爬高频脱手、分解垃圾奖励、感染转职、同类增益、免疫孢子、食虫惩罚", 
				28 => "周期绑气球、拾取转化、只吃包装熟食、冷热抵抗、额外负面", 
				29 => "周期荔莓、食用奖励、非莓惩罚、进食损伤、特殊转职", 
				30 => "周期获得昆虫、额外体力溢出取模、夜间加速、独行清负、职业效果复制", 
				31 => "传送、曲迁菇、负面状态、周期检测", 
				32 => "石化、石头、动态增益、拾取转化、随机效果、范围守护", 
				33 => "工具转化、随机负面、食物炼化、周期强力炼制、背人炼成", 
				34 => "周期范围发糖、满物品栏捣蛋、拾取转化、吃糖惩罚", 
				_ => string.Empty, 
			};
		}

		private static string IntroZh(int code)
		{
			return code switch
			{
				1 => "【开路先锋】每关获得随机道具背包;首次昏迷将完全清除负面复活;*高代谢*迫使你不断管理饥饿与冲刺。", 
				2 => "【团队核心】周期强力范围增益,使玩家获得额外体力、治疗损伤、补充饥饿、减少困倦;自身诅咒石化随全员生死波动;自身死亡触发全员激励;全员存活时,自己开启雕像可获额外物品。", 
				3 => "【移动补给】开局自带随机背包;定时获得随机物资;初始诅咒与较高石化需谨慎管理;与【炊事员】有特殊羁绊。", 
				4 => "【随队医护】每关获得随机医疗物资背包;近身小范围持续减伤解毒;队友阵亡会触发自伤惩罚。", 
				5 => "【生死问题】开局自带仪式匕首与吹箭筒;拾取物品概率变为武器;杀人还是救人,这是一个问题。", 
				6 => "【混沌之子】持续向周围散播潘多拉效果;拾取物品半变魔盒半消失;自身诅咒持续叠加。", 
				7 => "【骷髅之王】致敬本模组最不小团体的职业;通过玩家死亡、与骷髅配合降低自身诅咒;持续为周围的人类玩家施加中毒、骷髅玩家获得额外体力与无敌。", 
				8 => "【负面转化】受到绝大部分负面状态时,等量转化额外体力;身上无负面状态则周期损失额外体力;对特定负面状态有耐受。", 
				9 => "【自然力量】自身对中毒与孢子有较强抗性;只能食用生的自然食物,否则获得损伤;定时获得粉莓蕉;自身无法主动使用攀爬道具,拾取会直接变为木棍。", 
				10 => "【结伴而行】开局自带随机物资腰包;落单独行持续流血与饥饿;需和玩家结伴同行缓解,与同职业【童军】或【领袖】有加强羁绊。", 
				11 => "【团队旗手】每关获得检查点旗标;自身插旗立即范围清除负面,三秒后全员获得随机增益效果;自身每次插旗增加石化。", 
				12 => "【一起跳舞】自身范围内玩家跳舞获得额外体力;自身跳舞强制周围玩家共舞;定时强制后空翻且必定摔倒。", 
				13 => "【模因传播】语音与产蛋周期获得额外体力;传染式转职,可主动通过喂食煎蛋强制同化他人;较大范围内,同类周期回血减伤,但定时强制后空翻且必定摔倒。", 
				14 => "【请输入文本】禁用食物及绝大部分道具,拾取物品直接消失;每关饥饿值清零重置;周期回满额外体力;受伤放大即倒;免疫【潘多拉之子】【死灵法师】【舞王】【区】职业效果。", 
				15 => "【随机施法】定时获得卷轴,打开随机触发效果并增加自身石化/诅咒;人类形态效果偏辅助,骷髅形态效果高收益高代价。", 
				16 => "【立体机动】初始高诅咒高石化需谨慎管理;开局自带黄金抓钩与滑翔翼;使用抓钩勾中玩家会立即触发高损伤惩罚;免疫【潘多拉之子】且拾取的潘多拉餐盒直接消失。", 
				17 => "【烤炉大师】定时获得便携烤炉;烤任意物品都能解饿;烤多会中毒;周期烤非食物(道具)可获额外体力;手持便携烤炉时有高频炎热。", 
				18 => "【饮食保障】定时获得便携烤炉;烤熟生食可为周围回饥饿与体力;食用熟食有额外收益,食用生食有较大惩罚;附近有人饥饿会致困昏迷;与【后勤】有特殊羁绊。", 
				19 => "【并非和平】右键拉人变为踢人;每踢中一名玩家+5额外体力,踢空则-5额外体力并+20困倦;开局自带诅咒。", 
				20 => "【负重前行】开局自带背包;负重过半达标后,周期获得加速与额外体力。", 
				21 => "【火线救援】队友昏迷将自动传送并获得一次无敌,背起再获得一次加速与大量额外体力;背起玩家时治疗双方;有玩家死亡会获得石化惩罚。", 
				22 => "【精致防晒】每关获得三瓶防晒喷雾,强制覆盖物品栏;获得防晒奖励额外体力,每次用完防晒喷雾会额外奖励额外体力并积累石化。", 
				23 => "【单人模式】离人够远时周期奖励额外体力;靠近他人持续困倦。", 
				24 => "【重火力者】每关获得童子军大炮;定时获得童子军大炮;受伤就会获得炸药,且物品栏满时会直接塞入主手。", 
				25 => "【演奏乐队】拾取物品概率变乐器,库存与演奏可范围奖励额外体力;库存集齐三种乐器有额外加速奖励,且给予他人有低概率将其转职。", 
				26 => "【领队残魂】初始诅咒锁定,仅能通过获取童军宝石道具降低;使用童军宝石道具有专属特殊增益;禁用童军宝石道具以外物品,拾取物品直接消失;即时传送救援,不可放弃他人;集齐所有童军宝石道具有额外奖励;抵达天底解放灵魂全员奖励。", 
				27 => "【蠕动之区】攀爬时高频强制脱手;靠拾取分解垃圾物品、获取积攒早起虫儿积累收益;感染式转职,周期对附近施加孢子,过半会使他人强制同化,并获得早起虫儿;同类聚集有加速与无限体力效果奖励;自身免疫孢子;食用早期虫儿有诅咒惩罚。", 
				28 => "【太空漂流】身上定时绑上气球;无法拾取气球/一束气球;拾取物品概率变为火箭背包/喷气背包;只吃包装食品或熟食,否则中毒;对冷热有一定抗性,但部分负面状态会额外加重。", 
				29 => "【大果成瘾】定时获得青/紫荔莓;食用青荔莓获得冲刺效果、紫荔莓则给无限体力;食用其他食物加困倦;每次进食固定损伤;他人多次连吃荔莓会被强制同化。", 
				30 => "【代码之力】定时获得随机虫类,获得时大概率中毒;额外体力溢出会取模重置;夜晚周期获得加速;独行会周期清负面;无负面且无虫时会复制在场他人职业效果,并持续生效一段时间。", 
				31 => "【混乱传送】周期检测自身状态,若检测的负面状态相比上一次增加,则与随机一名其他玩家互换位置,否则获得曲迁菇;拾取物品有较高概率直接消失并触发潘多拉;饮用运动饮料或万灵药、接触水体都会带来额外惩罚;积攒过多曲迁菇会受到惩罚。", 
				32 => "【活体石像】站立不动会持续积蓄石化,移动则缓慢消退;自身石化越高,增益效果越强;若石化偏低,白天会畏光叠加困倦;周期内石化达到峰值时会强制破壳,触发一次性强力增益,且能影响周围;拾取食物会变石头,而石头可能带来随机增益。", 
				33 => "【等价炼成】拾取工具时获得随机负面并转化为随机物品;拾取可食用物品会转化为负面清除与额外体力,每12次会有特殊奖励;每炼制出12石头将触发一次特殊强力炼制;背起玩家会将其变为骷髅,或将其炼化为尸。", 
				34 => "【万圣敲门】定时给范围内的玩家发糖,成功使自身获得额外体力;若对方物品栏已满则捣蛋:随机抢走一件物品或连施潘多拉;拾取食物会变糖,但吃糖有蛀牙的损伤惩罚。", 
				_ => string.Empty, 
			};
		}

		private static string KeywordsEn(int code)
		{
			return code switch
			{
				1 => "Random items each level · One-time faint revive · High hunger drain · Periodic infinite sprint", 
				2 => "Area buffs · Opening debt · Bound to everyone's life · Death-triggered rally · Extra mystery item", 
				3 => "Periodic random supply · Starting backpack · High starting petrify", 
				4 => "Medical supplies each level · Area injury/poison relief · Self-injury on teammate death", 
				5 => "Starting weapons · Dagger and blowgun · Pickup conversion", 
				6 => "Area Pandora · Pickup conversion · Chance to vanish · Steadily rising curse", 
				7 => "Book of Bones · Death buffs · Skeleton empowerment · High starting curse · Curse/gain management", 
				8 => "Debuff conversion · Penalty without debuffs · Periodic debuff tolerance", 
				9 => "Poison/spore resistance · Natural food only · Periodic pink banana · Tools disabled", 
				10 => "Starting random fanny pack · Solo bleeding · Group action · Companion buffs", 
				11 => "Checkpoint flag · Area debuff purge on plant · Self petrify on plant · Random team buffs · Team burst", 
				12 => "Dance for stamina · Forced group dance · Periodic backflip · Fall risk", 
				13 => "Periodic eggs · Contagious reclass · Same-class area healing · Periodic backflip", 
				14 => "Items disabled · Pickups vanish · Periodic reset · Amplified injury · Class immunity", 
				15 => "Periodic scrolls · Two forms · Random effects · Negative cost", 
				16 => "High starting debuffs · Golden grapnel · Heavy grab penalty · Pandora immunity", 
				17 => "Portable stove · Cooking sates hunger · Cooking builds poison · Non-food rewards · Heat while holding", 
				18 => "Portable stove · Cooking raw food · Group buffs · Cooked-food bonus · Raw-food penalty · Hunger-induced drowsiness", 
				19 => "Kick replaces grab · Hits restore stamina · Misses cause drowsiness · Starting curse", 
				20 => "Starting backpack · Item hoarding · Load reward · Periodic sprint · Extra stamina", 
				21 => "Faint rescue · Teleport invincibility · Post-rescue sprint · Carrying heals both · Death penalty", 
				22 => "Sunscreen · Refreshed each level · Forced overwrite · Sunscreen stamina · Per-bottle bonus", 
				23 => "Solo reward · Company penalty · Radius check", 
				24 => "Scout cannon · Each level and periodic · Injury dynamite · Main-hand risk", 
				25 => "Horns and instruments · Performance area buff · Pickup conversion · Inventory bonus · Starting curse · Special reclass", 
				26 => "Scout gems · Locked curse · Pickups vanish · Special bonuses · Rule Zero · Nadir liberation", 
				27 => "Early bug · Frequent climbing slips · Decomposing junk rewards · Contagious reclass · Same-class buffs · Spore immunity · Bug-eating penalty", 
				28 => "Periodic balloons · Pickup conversion · Packaged/cooked food only · Heat/cold resistance · Extra debuffs", 
				29 => "Periodic kingberries · Eating rewards · Non-berry penalty · Injury per meal · Special reclass", 
				30 => "Periodic insects · Extra stamina overflow modulo · Night sprint · Solo debuff cleanse · Class-effect copy", 
				31 => "Teleport · Warp Fungus · Negative statuses · Periodic checks", 
				32 => "Petrify · Stones · Dynamic buffs · Pickup conversion · Random effects · Area guard", 
				33 => "Tool transmutation · Random debuff · Food conversion · Forced periodic refining · Carrying taboo", 
				34 => "Periodic area candy · Trick on full inventories · Pickup conversion · Sugar penalty", 
				_ => string.Empty, 
			};
		}

		private static string IntroEn(int code)
		{
			return code switch
			{
				1 => "【Pioneer】 a random-item backpack every level; your first faint fully clears debuffs as you revive; *Hyper-metabolism* forces you to keep managing hunger and sprints.", 
				2 => "【Team core】 periodic strong area buffs that grant players extra stamina, heal injury, restore hunger and reduce drowsiness; your curse and petrify swing with everyone's life and death; your death rallies everyone; while all are alive, opening an Ancient Idol yourself yields an extra item.", 
				3 => "【Mobile supply】 starts with a random backpack; receives random supplies on a timer; your starting curse and high petrify need careful management; special bond with the Cook.", 
				4 => "【Field medic】 a random medical backpack every level; sustained close-range injury and poison relief; a teammate's death triggers a self-injury penalty.", 
				5 => "【A matter of life and death】 starts with a ritual dagger and a blowgun; picked-up items may become weapons; to kill or to save, that is the question.", 
				6 => "【Child of chaos】 keeps spreading the Pandora effect around you; half of the items you pick up become boxes and half vanish; your curse keeps stacking.", 
				7 => "【King of skeletons】 a tribute to the mod's least small group; lower your curse through player deaths and skeleton allies; keep poisoning nearby humans while skeletons gain extra stamina and invincibility.", 
				8 => "【Negative conversion】 most debuffs you take convert into an equal amount of extra stamina; with no debuffs you periodically lose extra stamina; tolerant against specific debuffs.", 
				9 => "【Power of nature】 strong resistance to poison and spores; you can only eat raw natural food or take injury; gains a pink banana on a timer; climbing tools are unusable — picked-up ones turn into wooden sticks.", 
				10 => "【Better together】 starts with a random-item fanny pack; alone you bleed and starve; you need company to ease it; strengthened bond with fellow Scouts or Leaders.", 
				11 => "【Team standard-bearer】 a checkpoint flag every level; planting it immediately clears debuffs in range and 3 s later grants everyone random buffs; each plant adds petrify to yourself.", 
				12 => "【Dance together】 players dancing in your range grant extra stamina; your own dance forces nearby players to dance; periodic forced backflips that always end in a fall.", 
				13 => "【Meme spread】 voice and egg cycles grant extra stamina; contagious reclassing — you can force-merge others by feeding fried eggs; same-class players heal and take less injury over a larger range, but periodic backflips always end in a fall.", 
				14 => "【Input text here】 food and most items are disabled and picked-up items vanish; hunger resets to zero every level; extra stamina refills periodically; any injury is amplified into a knockdown; immune to the Pandora, Necromancer, Dancer and Qu class effects.", 
				15 => "【Random casting】 scrolls on a timer; opening one triggers a random effect and adds petrify/curse; the human form leans support, the skeleton form trades a high cost for high gain.", 
				16 => "【Aerial mobility】 high starting curse and petrify need careful management; starts with a golden grapnel and a glider; hooking a player with the grapnel immediately triggers a heavy injury penalty; immune to Pandora effects and picked-up Pandora lunchboxes vanish.", 
				17 => "【Grill master】 portable stoves on a timer; cooking anything sates hunger; cooking too much poisons you; periodically cooking non-food (tools) grants extra stamina; holding a portable stove causes frequent heat.", 
				18 => "【Food security】 portable stoves on a timer; cooking raw food restores hunger and stamina around you; cooked food pays extra, raw food punishes hard; nearby hungry players make you drowsy; special bond with the Logistician.", 
				19 => "【Not so peaceful】 the right-click grab becomes a kick; each hit grants +5 extra stamina, while a miss costs 5 extra stamina and adds 20 drowsiness; starts with curse.", 
				20 => "【Carrying the weight】 starts with a backpack; once your load passes the threshold you periodically gain a sprint and extra stamina.", 
				21 => "【Fireline rescue】 a teammate's faint auto-teleports you with invincibility; picking them up grants a sprint and a large amount of extra stamina; carrying a player heals both; a player's death gives you petrify.", 
				22 => "【Exquisite sunscreen】 three sunscreens every level, force-overwriting your inventory; gaining sunscreen grants extra stamina, and finishing each bottle grants more extra stamina plus petrify.", 
				23 => "【Single-player mode】 periodically rewarded with extra stamina when far from others; constant drowsiness when someone is close.", 
				24 => "【Heavy gunner】 a scout cannon every level; scout cannons on a timer; taking damage grants dynamite, which is forced into your main hand when the inventory is full.", 
				25 => "【The playing band】 picked-up items may become instruments; inventory and performance grant area extra stamina; holding all three instruments grants an extra sprint, and giving them to others has a low chance to reclass them.", 
				26 => "【Scoutmaster's soul】 starting curse is locked and only Scout gem items can lower it; Scout gems grant exclusive bonuses; all non-gem items are disabled and picked-up items vanish; instant teleport rescue, never abandon others; collecting every Scout gem grants an extra reward; freeing the soul at the Nadir rewards everyone.", 
				27 => "【Wriggling Qu】 frequent forced let-go while climbing; profit from decomposing junk items and accumulating early bugs; contagious reclassing — periodically applies spores nearby, and past the threshold forces others to merge and grants early bugs; same-class clusters get sprint and infinite stamina rewards; immune to spores; eating early bugs costs curse.", 
				28 => "【Space drift】 balloons are tied on periodically; balloons and bunches of balloons cannot be picked up; picked-up items may become rocket packs or jetpacks; only packaged or cooked food, otherwise poison; some heat/cold resistance but certain debuffs hit harder.", 
				29 => "【Big-berry addiction】 green/purple kingberries on a timer; green grants a sprint, purple grants infinite stamina; other food adds drowsiness; every meal costs injury; others who chain-eat kingberries are forced to merge.", 
				30 => "【Power of code】 random insects on a timer, usually with poison; extra stamina overflow wraps around; periodic sprint at night; solo play periodically cleanses debuffs; with no debuffs and no insects you copy another present class's effect for a while.", 
				31 => "【Chaos Teleport】periodically checks your own status; if the selected negative status increased, swap positions with a random other player; otherwise gain a Warp Fungus; picked-up items may vanish and trigger Pandora; sports drinks, Cure-All and touching water add extra punishment; hoarding too many Warp Fungi is punished.", 
				32 => "【Living Statue】Standing still builds petrify while moving slowly sheds it; the higher your petrify, the stronger your buffs; when petrify is low, daylight adds drowsiness; when petrify peaks within a cycle you are forced to break the shell, triggering a one-time powerful buff that also affects those around you; food you pick up turns to stone, and stones may bring random buffs.", 
				33 => "【Equivalent alchemy】picked-up tools gain a random debuff and are turned into random items; picked-up edible items are converted into debuff relief and extra stamina, with a special reward every 12 triggers; every 12 stones refined triggers one special powerful transmutation; carrying a player turns them into a skeleton, or refines them into a corpse.", 
				34 => "【Halloween knocker】candy to players in range on a timer, granting you extra stamina when it lands; a full inventory gets tricked — one item is snatched or Pandora strikes repeatedly; edible pickups turn into candy, but eating candy costs injury from cavities.", 
				_ => string.Empty, 
			};
		}
	}
	public sealed class ClassLedger
	{
		private readonly Dictionary<string, ClassType> _byId = new Dictionary<string, ClassType>();

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

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

		private readonly Dictionary<PhotonView, ClassType> _classByView = new Dictionary<PhotonView, ClassType>();

		private readonly Dictionary<ClassType, int> _countByType = new Dictionary<ClassType, int>();

		private static readonly string[] _bilingualNames = new string[64];

		public int Count => _byId.Count;

		public void Clear()
		{
			_byId.Clear();
			_idByView.Clear();
			_classByView.Clear();
			_claimById.Clear();
			_countByType.Clear();
		}

		private void SetEntry(string id, ClassType type)
		{
			if (!_byId.TryGetValue(id, out var value) || value != type)
			{
				if (_byId.TryGetValue(id, out var value2) && value2 != ClassType.None)
				{
					_countByType[value2] = Mathf.Max(0, (_countByType.TryGetValue(value2, out var value3) ? value3 : 0) - 1);
				}
				_byId[id] = type;
				if (type != ClassType.None)
				{
					_countByType[type] = (_countByType.TryGetValue(type, out var value4) ? value4 : 0) + 1;
				}
				_classByView.Clear();
			}
		}

		public bool HasClassInRoom(ClassType type)
		{
			if (_countByType.TryGetValue(type, out var value))
			{
				return value > 0;
			}
			return false;
		}

		public bool TryGet(string id, out ClassType type)
		{
			type = ClassType.None;
			if (!string.IsNullOrEmpty(id))
			{
				return _byId.TryGetValue(id, out type);
			}
			return false;
		}

		public string IdOf(Character character)
		{
			if ((Object)(object)character == (Object)null)
			{
				return null;
			}
			PhotonView photonView = ((MonoBehaviourPun)character).photonView;
			if ((Object)(object)photonView == (Object)null)
			{
				return null;
			}
			if (_idByView.TryGetValue(photonView, out var value))
			{
				return value;
			}
			int num = ((photonView.Owner != null) ? photonView.Owner.ActorNumber : (-photonView.ViewID));
			List<string> list = new List<string>(4);
			HostApi.FillStableIdCandidates(character, list);
			if (list.Count == 0)
			{
				return null;
			}
			string text = null;
			for (int i = 0; i < list.Count; i++)
			{
				string text2 = list[i];
				if (!_claimById.TryGetValue(text2, out var value2) || value2 == num || !IsClaimActive(value2))
				{
					text = text2;
					break;
				}
			}
			if (string.IsNullOrEmpty(text))
			{
				text = ((photonView.Owner != null) ? ("a:" + photonView.Owner.ActorNumber) : ("v:" + photonView.ViewID));
			}
			_idByView[photonView] = text;
			_claimById[text] = num;
			if (list.Count > 1)
			{
				Plugin.Log.LogInfo((object)("[ClassLedger] 稳定标识:" + Nick(character) + " → " + text + "(候选:" + string.Join(" | ", list) + ")"));
			}
			else
			{
				Plugin.Log.LogInfo((object)("[ClassLedger] 稳定标识:" + Nick(character) + " → " + text));
			}
			return text;
		}

		private static bool IsClaimActive(int claimKey)
		{
			if (claimKey == 0)
			{
				return true;
			}
			if (claimKey > 0)
			{
				Room currentRoom = PhotonNetwork.CurrentRoom;
				if (currentRoom == null)
				{
					return true;
				}
				return currentRoom.GetPlayer(claimKey, false) != null;
			}
			return (Object)(object)PhotonView.Find(-claimKey) != (Object)null;
		}

		public ClassType GetClass(Character character)
		{
			if ((Object)(object)character == (Object)null)
			{
				return ClassType.None;
			}
			PhotonView photonView = ((MonoBehaviourPun)character).photonView;
			if ((Object)(object)photonView == (Object)null)
			{
				return ClassType.None;
			}
			if (_classByView.TryGetValue(photonView, out var value))
			{
				return value;
			}
			ClassType type;
			ClassType classType = (TryGet(IdOf(character), out type) ? type : ClassType.None);
			_classByView[photonView] = classType;
			return classType;
		}

		public int TotalCapacity()
		{
			int num = 0;
			if (ModConfig.Explorer.Enabled.Value)
			{
				int num2 = MaxOf(ClassType.Explorer);
				if (num2 <= 0)
				{
					return int.MaxValue;
				}
				num += num2;
			}
			if (ModConfig.Leader.Enabled.Value)
			{
				int num3 = MaxOf(ClassType.Leader);
				if (num3 <= 0)
				{
					return int.MaxValue;
				}
				num += num3;
			}
			if (ModConfig.Logistician.Enabled.Value)
			{
				int num4 = MaxOf(ClassType.Logistician);
				if (num4 <= 0)
				{
					return int.MaxValue;
				}
				num += num4;
			}
			if (ModConfig.Doctor.Enabled.Value)
			{
				int num5 = MaxOf(ClassType.Doctor);
				if (num5 <= 0)
				{
					return int.MaxValue;
				}
				num += num5;
			}
			if (ModConfig.Assassin.Enabled.Value)
			{
				int num6 = MaxOf(ClassType.Assassin);
				if (num6 <= 0)
				{
					return int.MaxValue;
				}
				num += num6;
			}
			if (ModConfig.Pandora.Enabled.Value)
			{
				int num7 = MaxOf(ClassType.Pandora);
				if (num7 <= 0)
				{
					return int.MaxValue;
				}
				num += num7;
			}
			if (ModConfig.Necromancer.Enabled.Value)
			{
				int num8 = MaxOf(ClassType.Necromancer);
				if (num8 <= 0)
				{
					return int.MaxValue;
				}
				num += num8;
			}
			if (ModConfig.M.Enabled.Value)
			{
				int num9 = MaxOf(ClassType.M);
				if (num9 <= 0)
				{
					return int.MaxValue;
				}
				num += num9;
			}
			if (ModConfig.Savage.Enabled.Value)
			{
				int num10 = MaxOf(ClassType.Savage);
				if (num10 <= 0)
				{
					return int.MaxValue;
				}
				num += num10;
			}
			if (ModConfig.Scout.Enabled.Value)
			{
				int num11 = MaxOf(ClassType.Scout);
				if (num11 <= 0)
				{
					return int.MaxValue;
				}
				num += num11;
			}
			if (ModConfig.StandardBearer.Enabled.Value)
			{
				int num12 = MaxOf(ClassType.StandardBearer);
				if (num12 <= 0)
				{
					return int.MaxValue;
				}
				num += num12;
			}
			if (ModConfig.Dancer.Enabled.Value)
			{
				int num13 = MaxOf(ClassType.Dancer);
				if (num13 <= 0)
				{
					return int.MaxValue;
				}
				num += num13;
			}
			if (ModConfig.Daihao.Enabled.Value)
			{
				int num14 = MaxOf(ClassType.Daihao);
				if (num14 <= 0)
				{
					return int.MaxValue;
				}
				num += num14;
			}
			if (ModConfig.Saint.Enabled.Value)
			{
				int num15 = MaxOf(ClassType.Saint);
				if (num15 <= 0)
				{
					return int.MaxValue;
				}
				num += num15;
			}
			if (ModConfig.Mage.Enabled.Value)
			{
				int num16 = MaxOf(ClassType.Mage);
				if (num16 <= 0)
				{
					return int.MaxValue;
				}
				num += num16;
			}
			if (ModConfig.Challenger.Enabled.Value)
			{
				int num17 = MaxOf(ClassType.Challenger);
				if (num17 <= 0)
				{
					return int.MaxValue;
				}
				num += num17;
			}
			if (ModConfig.MasterChef.Enabled.Value)
			{
				int num18 = MaxOf(ClassType.MasterChef);
				if (num18 <= 0)
				{
					return int.MaxValue;
				}
				num += num18;
			}
			if (ModConfig.Cook.Enabled.Value)
			{
				int num19 = MaxOf(ClassType.Cook);
				if (num19 <= 0)
				{
					return int.MaxValue;
				}
				num += num19;
			}
			if (ModConfig.Pacifist.Enabled.Value)
			{
				int num20 = MaxOf(ClassType.Pacifist);
				if (num20 <= 0)
				{
					return int.MaxValue;
				}
				num += num20;
			}
			if (ModConfig.Hamster.Enabled.Value)
			{
				int num21 = MaxOf(ClassType.Hamster);
				if (num21 <= 0)
				{
					return int.MaxValue;
				}
				num += num21;
			}
			if (ModConfig.Firefighter.Enabled.Value)
			{
				int num22 = MaxOf(ClassType.Firefighter);
				if (num22 <= 0)
				{
					return int.MaxValue;
				}
				num += num22;
			}
			if (ModConfig.Narcissist.Enabled.Value)
			{
				int num23 = MaxOf(ClassType.Narcissist);
				if (num23 <= 0)
				{
					return int.MaxValue;
				}
				num += num23;
			}
			if (ModConfig.LoneWolf.Enabled.Value)
			{
				int num24 = MaxOf(ClassType.LoneWolf);
				if (num24 <= 0)
				{
					return int.MaxValue;
				}
				num += num24;
			}
			if (ModConfig.Gunner.Enabled.Value)
			{
				int num25 = MaxOf(ClassType.Gunner);
				if (num25 <= 0)
				{
					return int.MaxValue;
				}
				num += num25;
			}
			if (ModConfig.Band.Enabled.Value)
			{
				int num26 = MaxOf(ClassType.Band);
				if (num26 <= 0)
				{
					return int.MaxValue;
				}
				num += num26;
			}
			if (ModConfig.MilesSoul.Enabled.Value)
			{
				int num27 = MaxOf(ClassType.MilesSoul);
				if (num27 <= 0)
				{
					return int.MaxValue;
				}
				num += num27;
			}
			if (ModConfig.Qu.Enabled.Value)
			{
				int num28 = MaxOf(ClassType.Qu);
				if (num28 <= 0)
				{
					return int.MaxValue;
				}
				num += num28;
			}
			if (ModConfig.Astronaut.Enabled.Value)
			{
				int num29 = MaxOf(ClassType.Astronaut);
				if (num29 <= 0)
				{
					return int.MaxValue;
				}
				num += num29;
			}
			if (ModConfig.Langwang.Enabled.Value)
			{
				int num30 = MaxOf(ClassType.Langwang);
				if (num30 <= 0)
				{
					return int.MaxValue;
				}
				num += num30;
			}
			if (ModConfig.Programmer.Enabled.Value)
			{
				int num31 = MaxOf(ClassType.Programmer);
				if (num31 <= 0)
				{
					return int.MaxValue;
				}
				num += num31;
			}
			if (ModConfig.ChaosEnderman.Enabled.Value)
			{
				int num32 = MaxOf(ClassType.ChaosEnderman);
				if (num32 <= 0)
				{
					return int.MaxValue;
				}
				num += num32;
			}
			if (ModConfig.Gargoyle.Enabled.Value)
			{
				int num33 = MaxOf(ClassType.Gargoyle);
				if (num33 <= 0)
				{
					return int.MaxValue;
				}
				num += num33;
			}
			if (ModConfig.Alchemist.Enabled.Value)
			{
				int num34 = MaxOf(ClassType.Alchemist);
				if (num34 <= 0)
				{
					return int.MaxValue;
				}
				num += num34;
			}
			if (ModConfig.Trickster.Enabled.Value)
			{
				int num35 = MaxOf(ClassType.Trickster);
				if (num35 <= 0)
				{
					return int.MaxValue;
				}
				num += num35;
			}
			return num;
		}

		public bool IsFull()
		{
			return Count >= TotalCapacity();
		}

		public ClassType Assign(Character character)
		{
			string text = IdOf(character);
			if (string.IsNullOrEmpty(text))
			{
				return ClassType.None;
			}
			List<ClassType> enabledClasses = GetEnabledClasses();
			if (enabledClasses.Count == 0)
			{
				return ClassType.None;
			}
			ClassType classType = (ModConfig.General.RandomAssign.Value ? PickByWeight(enabledClasses) : PickInOrder(enabledClasses));
			if (classType == ClassType.None)
			{
				Plugin.Log.LogInfo((object)("[ClassLedger] 所有职业名额已满," + Nick(character) + " 本次不分配职业。"));
				return ClassType.None;
			}
			SetEntry(text, classType);
			Plugin.Log.LogInfo((object)("[ClassLedger] 分配职业:" + Nick(character) + " → " + Describe(classType)));
			return classType;
		}

		public bool TryReassign(Character character, out ClassType oldType, out ClassType newType)
		{
			oldType = ClassType.None;
			newType = ClassType.None;
			string text = IdOf(character);
			if (string.IsNullOrEmpty(text))
			{
				return false;
			}
			if (!_byId.TryGetValue(text, out oldType))
			{
				return false;
			}
			List<ClassType> list = new List<ClassType>();
			foreach (ClassType enabledClass in GetEnabledClasses())
			{
				if (enabledClass != oldType && !IsCapped(enabledClass, CountOf(enabledClass)))
				{
					list.Add(enabledClass);
				}
			}
			if (list.Count == 0)
			{
				return false;
			}
			newType = WeightedPick(list);
			if (newType == ClassType.None)
			{
				return false;
			}
			SetEntry(text, newType);
			return true;
		}

		public bool ForceAssign(Character character, ClassType type)
		{
			if (type == ClassType.None)
			{
				return false;
			}
			string text = IdOf(character);
			if (string.IsNullOrEmpty(text))
			{
				return false;
			}
			if (_byId.TryGetValue(text, out var value) && value == type)
			{
				return false;
			}
			SetEntry(text, type);
			return true;
		}

		public bool ForceSet(Character character, ClassType type)
		{
			string text = IdOf(character);
			if (string.IsNullOrEmpty(text))
			{
				return false;
			}
			if (_byId.TryGetValue(text, out var value) && value == type)
			{
				return false;
			}
			SetEntry(text, type);
			return true;
		}

		private static string Nick(Character character)
		{
			if ((Object)(object)character == (Object)null || (Object)(object)((MonoBehaviourPun)character).photonView == (Object)null || ((MonoBehaviourPun)character).photonView.Owner == null)
			{
				return "?";
			}
			return $"{((MonoBehaviourPun)character).photonView.Owner.NickName}(#{((MonoBehaviourPun)character).photonView.Owner.ActorNumber})";
		}

		private static List<ClassType> GetEnabledClasses()
		{
			List<ClassType> list = new List<ClassType>();
			if (ModConfig.Explorer.Enabled.Value)
			{
				list.Add(ClassType.Explorer);
			}
			if (ModConfig.Leader.Enabled.Value)
			{
				list.Add(ClassType.Leader);
			}
			if (ModConfig.Logistician.Enabled.Value)
			{
				list.Add(ClassType.Logistician);
			}
			if (ModConfig.Doctor.Enabled.Value)
			{
				list.Add(ClassType.Doctor);
			}
			if (ModConfig.Assassin.Enabled.Value)
			{
				list.Add(ClassType.Assassin);
			}
			if (ModConfig.Pandora.Enabled.Value)
			{
				list.Add(ClassType.Pandora);
			}
			if (ModConfig.Necromancer.Enabled.Value)
			{
				list.Add(ClassType.Necromancer);
			}
			if (ModConfig.M.Enabled.Value)
			{
				list.Add(ClassType.M);
			}
			if (ModConfig.Savage.Enabled.Value)
			{
				list.Add(ClassType.Savage);
			}
			if (ModConfig.Scout.Enabled.Value)
			{
				list.Add(ClassType.Scout);
			}
			if (ModConfig.StandardBearer.Enabled.Value)
			{
				list.Add(ClassType.StandardBearer);
			}
			if (ModConfig.Dancer.Enabled.Value)
			{
				list.Add(ClassType.Dancer);
			}
			if (ModConfig.Daihao.Enabled.Value)
			{
				list.Add(ClassType.Daihao);
			}
			if (ModConfig.Saint.Enabled.Value)
			{
				list.Add(ClassType.Saint);
			}
			if (ModConfig.Mage.Enabled.Value)
			{
				list.Add(ClassType.Mage);
			}
			if (ModConfig.Challenger.Enabled.Value)
			{
				list.Add(ClassType.Challenger);
			}
			if (ModConfig.MasterChef.Enabled.Value)
			{
				list.Add(ClassType.MasterChef);
			}
			if (ModConfig.Cook.Enabled.Value)
			{
				list.Add(ClassType.Cook);
			}
			if (ModConfig.Pacifist.Enabled.Value)
			{
				list.Add(ClassType.Pacifist);
			}
			if (ModConfig.Hamster.Enabled.Value)
			{
				list.Add(ClassType.Hamster);
			}
			if (ModConfig.Firefighter.Enabled.Value)
			{
				list.Add(ClassType.Firefighter);
			}
			if (ModConfig.Narcissist.Enabled.Value)
			{
				list.Add(ClassType.Narcissist);
			}
			if (ModConfig.LoneWolf.Enabled.Value)
			{
				list.Add(ClassType.LoneWolf);
			}
			if (ModConfig.Gunner.Enabled.Value)
			{
				list.Add(ClassType.Gunner);
			}
			if (ModConfig.Band.Enabled.Value)
			{
				list.Add(ClassType.Band);
			}
			if (ModConfig.MilesSoul.Enabled.Value && MilesSoulSelectableNow())
			{
				list.Add(ClassType.MilesSoul);
			}
			if (ModConfig.Qu.Enabled.Value)
			{
				list.Add(ClassType.Qu);
			}
			if (ModConfig.Astronaut.Enabled.Value)
			{
				list.Add(ClassType.Astronaut);
			}
			if (ModConfig.Langwang.Enabled.Value)
			{
				list.Add(ClassType.Langwang);
			}
			if (ModConfig.Programmer.Enabled.Value)
			{
				list.Add(ClassType.Programmer);
			}
			if (ModConfig.ChaosEnderman.Enabled.Value)
			{
				list.Add(ClassType.ChaosEnderman);
			}
			if (ModConfig.Gargoyle.Enabled.Value)
			{
				list.Add(ClassType.Gargoyle);
			}
			if (ModConfig.Alchemist.Enabled.Value)
			{
				list.Add(ClassType.Alchemist);
			}
			if (ModConfig.Trickster.Enabled.Value)
			{
				list.Add(ClassType.Trickster);
			}
			return list;
		}

		private static bool MilesSoulSelectableNow()
		{
			if (ModConfig.MilesSoul.BeachOnly.Value)
			{
				return !HostClassManager.MilesPastBeach();
			}
			return true;
		}

		private int CountOf(ClassType type)
		{
			if (!_countByType.TryGetValue(type, out var value))
			{
				return 0;
			}
			return value;
		}

		private static bool IsCapped(ClassType type, int current)
		{
			int num = MaxOf(type);
			if (num > 0)
			{
				return current >= num;
			}
			return false;
		}

		private ClassType PickByWeight(List<ClassType> enabled)
		{
			List<ClassType> list = new List<ClassType>();
			foreach (ClassType item in enabled)
			{
				int num = CountOf(item);
				if (num < MinOf(item) && !IsCapped(item, num))
				{
					list.Add(item);
				}
			}
			List<ClassType> obj = ((list.Count > 0) ? list : enabled);
			List<ClassType> list2 = new List<ClassType>();
			foreach (ClassType item2 in obj)
			{
				if (!IsCapped(item2, CountOf(item2)))
				{
					list2.Add(item2);
				}
			}
			if (list2.Count == 0)
			{
				return ClassType.None;
			}
			return WeightedPick(list2);
		}

		private ClassType PickInOrder(List<ClassType> enabled)
		{
			foreach (ClassType item in enabled)
			{
				if (!IsCapped(item, CountOf(item)))
				{
					return item;
				}
			}
			return ClassType.None;
		}

		private ClassType WeightedPick(List<ClassType> pool)
		{
			int num = 0;
			foreach (ClassType item in pool)
			{
				num += WeightOf(item);
			}
			if (num <= 0)
			{
				return pool[Random.Range(0, pool.Count)];
			}
			int num2 = Random.Range(0, num);
			int num3 = 0;
			foreach (ClassType item2 in pool)
			{
				num3 += WeightOf(item2);
				if (num2 < num3)
				{
					return item2;
				}
			}
			return pool[pool.Count - 1];
		}

		private static int WeightOf(ClassType type)
		{
			return type switch
			{
				ClassType.Explorer => Mathf.Max(0, ModConfig.Explorer.Weight.Value), 
				ClassType.Leader => Mathf.Max(0, ModConfig.Leader.Weight.Value), 
				ClassType.Logistician => Mathf.Max(0, ModConfig.Logistician.Weight.Value), 
				ClassType.Doctor => Mathf.Max(0, ModConfig.Doctor.Weight.Value), 
				ClassType.Assassin => Mathf.Max(0, ModConfig.Assassin.Weight.Value), 
				ClassType.Pandora => Mathf.Max(0, ModConfig.Pandora.Weight.Value), 
				ClassType.Necromancer => Mathf.Max(0, ModConfig.Necromancer.Weight.Value), 
				ClassType.M => Mathf.Max(0, ModConfig.M.Weight.Value), 
				ClassType.Savage => Mathf.Max(0, ModConfig.Savage.Weight.Value), 
				ClassType.Scout => Mathf.Max(0, ModConfig.Scout.Weight.Value), 
				ClassType.StandardBearer => Mathf.Max(0, ModConfig.StandardBearer.Weight.Value), 
				ClassType.Dancer => Mathf.Max(0, ModConfig.Dancer.Weight.Value), 
				ClassType.Daihao => Mathf.Max(0, ModConfig.Daihao.Weight.Value), 
				ClassType.Saint => Mathf.Max(0, ModConfig.Saint.Weight.Value), 
				ClassType.Mage => Mathf.Max(0, ModConfig.Mage.Weight.Value), 
				ClassType.Challenger => Mathf.Max(0, ModConfig.Challenger.Weight.Value), 
				ClassType.MasterChef => Mathf.Max(0, ModConfig.MasterChef.Weight.Value), 
				ClassType.Cook => Mathf.Max(0, ModConfig.Cook.Weight.Value), 
				ClassType.Pacifist => Mathf.Max(0, ModConfig.Pacifist.Weight.Value), 
				ClassType.Hamster => Mathf.Max(0, ModConfig.Hamster.Weight.Value), 
				ClassType.Firefighter => Mathf.Max(0, ModConfig.Firefighter.Weight.Value), 
				ClassType.Narcissist => Mathf.Max(0, ModConfig.Narcissist.Weight.Value), 
				ClassType.LoneWolf => Mathf.Max(0, ModConfig.LoneWolf.Weight.Value), 
				ClassType.Gunner => Mathf.Max(0, ModConfig.Gunner.Weight.Value), 
				ClassType.Band => Mathf.Max(0, ModConfig.Band.Weight.Value), 
				ClassType.MilesSoul => Mathf.Max(0, ModConfig.MilesSoul.Weight.Value), 
				ClassType.Qu => Mathf.Max(0, ModConfig.Qu.Weight.Value), 
				ClassType.Astronaut => Mathf.Max(0, ModConfig.Astronaut.Weight.Value), 
				ClassType.Langwang => Mathf.Max(0, ModConfig.Langwang.Weight.Value), 
				ClassType.Programmer => Mathf.Max(0, ModConfig.Programmer.Weight.Value), 
				ClassType.ChaosEnderman => Mathf.Max(0, ModConfig.ChaosEnderman.Weight.Value), 
				ClassType.Gargoyle => Mathf.Max(0, ModConfig.Gargoyle.Weight.Value), 
				ClassType.Alchemist => Mathf.Max(0, ModConfig.Alchemist.Weight.Value), 
				ClassType.Trickster => Mathf.Max(0, ModConfig.Trickster.Weight.Value), 
				_ => 0, 
			};
		}

		private static int MaxOf(ClassType type)
		{
			return type switch
			{
				ClassType.Explorer => Mathf.Max(0, ModConfig.Explorer.MaxPlayers.Value), 
				ClassType.Leader => Mathf.Max(0, ModConfig.Leader.MaxPlayers.Value), 
				ClassType.Logistician => Mathf.Max(0, ModConfig.Logistician.MaxPlayers.Value), 
				ClassType.Doctor => Mathf.Max(0, ModConfig.Doctor.MaxPlayers.Value), 
				ClassType.Assassin => Mathf.Max(0, ModConfig.Assassin.MaxPlayers.Value), 
				ClassType.Pandora => PandoraModeBridge.EffectivePandoraMaxPlayers(Mathf.Max(0, ModConfig.Pandora.MaxPlayers.Value)), 
				ClassType.Necromancer => Mathf.Max(0, ModConfig.Necromancer.MaxPlayers.Value), 
				ClassType.M => Mathf.Max(0, ModConfig.M.MaxPlayers.Value), 
				ClassType.Savage => Mathf.Max(0, ModConfig.Savage.MaxPlayers.Value), 
				ClassType.Scout => Mathf.Max(0, ModConfig.Scout.MaxPlayers.Value), 
				ClassType.StandardBearer => Mathf.Max(0, ModConfig.StandardBearer.MaxPlayers.Value), 
				ClassType.Dancer => Mathf.Max(0, ModConfig.Dancer.MaxPlayers.Value), 
				ClassType.Daihao => Mathf.Max(0, ModConfig.Daihao.MaxPlayers.Value), 
				ClassType.Saint => Mathf.Max(0, ModConfig.Saint.MaxPlayers.Value), 
				ClassType.Mage => Mathf.Max(0, ModConfig.Mage.MaxPlayers.Value), 
				ClassType.Challenger => Mathf.Max(0, ModConfig.Challenger.MaxPlayers.Value), 
				ClassType.MasterChef => Mathf.Max(0, ModConfig.MasterChef.MaxPlayers.Value), 
				ClassType.Cook => Mathf.Max(0, ModConfig.Cook.MaxPlayers.Value), 
				ClassType.Pacifist => Mathf.Max(0, ModConfig.Pacifist.MaxPlayers.Value), 
				ClassType.Hamster => Mathf.Max(0, ModConfig.Hamster.MaxPlayers.Value), 
				ClassType.Firefighter => Mathf.Max(0, ModConfig.Firefighter.MaxPlayers.Value), 
				ClassType.Narcissist => Mathf.Max(0, ModConfig.Narcissist.MaxPlayers.Value), 
				ClassType.LoneWolf => Mathf.Max(0, ModConfig.LoneWolf.MaxPlayers.Value), 
				ClassType.Gunner => Mathf.Max(0, ModConfig.Gunner.MaxPlayers.Value), 
				ClassType.Band => Mathf.Max(0, ModConfig.Band.MaxPlayers.Value), 
				ClassType.MilesSoul => Mathf.Max(0, ModConfig.MilesSoul.MaxPlayers.Value), 
				ClassType.Qu => Mathf.Max(0, ModConfig.Qu.MaxPlayers.Value), 
				ClassType.Astronaut => Mathf.Max(0, ModConfig.Astronaut.MaxPlayers.Value), 
				ClassType.Langwang => Mathf.Max(0, ModConfig.Langwang.MaxPlayers.Value), 
				ClassType.Programmer => Mathf.Max(0, ModConfig.Programmer.MaxPlayers.Value), 
				ClassType.ChaosEnderman => Mathf.Max(0, ModConfig.ChaosEnderman.MaxPlayers.Value), 
				ClassType.Gargoyle => Mathf.Max(0, ModConfig.Gargoyle.MaxPlayers.Value), 
				ClassType.Alchemist => Mathf.Max(0, ModConfig.Alchemist.MaxPlayers.Value), 
				ClassType.Trickster => Mathf.Max(0, ModConfig.Trickster.MaxPlayers.Value), 
				_ => 0, 
			};
		}

		private static int MinOf(ClassType type)
		{
			return type switch
			{
				ClassType.Explorer => Mathf.Max(0, ModConfig.Explorer.MinPlayers.Value), 
				ClassType.Leader => Mathf.Max(0, ModConfig.Leader.MinPlayers.Value), 
				ClassType.Logistician => Mathf.Max(0, ModConfig.Logistician.MinPlayers.Value), 
				ClassType.Doctor => Mathf.Max(0, ModConfig.Doctor.MinPlayers.Value), 
				ClassType.Assassin => Mathf.Max(0, ModConfig.Assassin.MinPlayers.Value), 
				ClassType.Pandora => Mathf.Max(0, ModConfig.Pandora.MinPlayers.Value), 
				ClassType.Necromancer => Mathf.Max(0, ModConfig.Necromancer.MinPlayers.Value), 
				ClassType.M => Mathf.Max(0, ModConfig.M.MinPlayers.Value), 
				ClassType.Savage => Mathf.Max(0, ModConfig.Savage.MinPlayers.Value), 
				ClassType.Scout => Mathf.Max(0, ModConfig.Scout.MinPlayers.Value), 
				ClassType.StandardBearer => Mathf.Max(0, ModConfig.StandardBearer.MinPlayers.Value), 
				ClassType.Dancer => Mathf.Max(0, ModConfig.Dancer.MinPlayers.Value), 
				ClassType.Daihao => Mathf.Max(0, ModConfig.Daihao.MinPlayers.Value), 
				ClassType.Saint => Mathf.Max(0, ModConfig.Saint.MinPlayers.Value), 
				ClassType.Mage => Mathf.Max(0, ModConfig.Mage.MinPlayers.Value), 
				ClassType.Challenger => Mathf.Max(0, ModConfig.Challenger.MinPlayers.Value), 
				ClassType.MasterChef => Mathf.Max(0, ModConfig.MasterChef.MinPlayers.Value), 
				ClassType.Cook => Mathf.Max(0, ModConfig.Cook.MinPlayers.Value), 
				ClassType.Pacifist => Mathf.Max(0, ModConfig.Pacifist.MinPlayers.Value), 
				ClassType.Hamster => Mathf.Max(0, ModConfig.Hamster.MinPlayers.Value), 
				ClassType.Firefighter => Mathf.Max(0, ModConfig.Firefighter.MinPlayers.Value), 
				ClassType.Narcissist => Mathf.Max(0, ModConfig.Narcissist.MinPlayers.Value), 
				ClassType.LoneWolf => Mathf.Max(0, ModConfig.LoneWolf.MinPlayers.Value), 
				ClassType.Gunner => Mathf.Max(0, ModConfig.Gunner.MinPlayers.Value), 
				ClassType.Band => Mathf.Max(0, ModConfig.Band.MinPlayers.Value), 
				ClassType.MilesSoul => Mathf.Max(0, ModConfig.MilesSoul.MinPlayers.Value), 
				ClassType.Qu => Mathf.Max(0, ModConfig.Qu.MinPlayers.Value), 
				ClassType.Astronaut => Mathf.Max(0, ModConfig.Astronaut.MinPlayers.Value), 
				ClassType.Langwang => Mathf.Max(0, ModConfig.Langwang.MinPlayers.Value), 
				ClassType.Programmer => Mathf.Max(0, ModConfig.Programmer.MinPlayers.Value), 
				ClassType.ChaosEnderman => Mathf.Max(0, ModConfig.ChaosEnderman.MinPlayers.Value), 
				ClassType.Gargoyle => Mathf.Max(0, ModConfig.Gargoyle.MinPlayers.Value), 
				ClassType.Alchemist => Mathf.Max(0, ModConfig.Alchemist.MinPlayers.Value), 
				ClassType.Trickster => Mathf.Max(0, ModConfig.Trickster.MinPlayers.Value), 
				_ => 0, 
			};
		}

		public static string Describe(ClassType type)
		{
			return type switch
			{
				ClassType.Explorer => "先驱", 
				ClassType.Leader => "领袖", 
				ClassType.Logistician => "后勤", 
				ClassType.Doctor => "医生", 
				ClassType.Assassin => "刺客", 
				ClassType.Pandora => "潘多拉之子", 
				ClassType.Necromancer => "死灵法师", 
				ClassType.M => "M", 
				ClassType.Savage => "野人", 
				ClassType.Scout => "童军", 
				ClassType.StandardBearer => "旗手", 
				ClassType.Dancer => "舞王", 
				ClassType.Daihao => "待皓", 
				ClassType.Saint => "圣人", 
				ClassType.Mage => "法师", 
				ClassType.Challenger => "挑战者", 
				ClassType.MasterChef => "特级厨师", 
				ClassType.Cook => "炊事员", 
				ClassType.Pacifist => "和平主义者", 
				ClassType.Hamster => "仓鼠", 
				ClassType.Firefighter => "救火队员", 
				ClassType.Narcissist => "自恋狂", 
				ClassType.LoneWolf => "独狼", 
				ClassType.Gunner => "炮手", 
				ClassType.Band => "乐队成员", 
				ClassType.MilesSoul => "迈尔斯残魂", 
				ClassType.Qu => "区", 
				ClassType.Astronaut => "太空人", 
				ClassType.Langwang => "榔王", 
				ClassType.Programmer => "程序员", 
				ClassType.ChaosEnderman => "混乱末影人", 
				ClassType.Gargoyle => "石像鬼", 
				ClassType.Alchemist => "炼金术士", 
				ClassType.Trickster => "捣蛋鬼", 
				_ => "无职业", 
			};
		}

		public static string DescribeEnglish(ClassType type)
		{
			return type switch
			{
				ClassType.Explorer => "Explorer", 
				ClassType.Leader => "Leader", 
				ClassType.Logistician => "Logistician", 
				ClassType.Doctor => "Doctor", 
				ClassType.Assassin => "Assassin", 
				ClassType.Pandora => "Pandora's Child", 
				ClassType.Necromancer => "Necromancer", 
				ClassType.M => "M", 
				ClassType.Savage => "Savage", 
				ClassType.Scout => "Scout", 
				ClassType.StandardBearer => "StandardBearer", 
				ClassType.Dancer => "Dancer", 
				ClassType.Daihao => "Daihao", 
				ClassType.Saint => "Saint", 
				ClassType.Mage => "Mage", 
				ClassType.Challenger => "Challenger", 
				ClassType.MasterChef => "MasterChef", 
				ClassType.Cook => "Cook", 
				ClassType.Pacifist => "Pacifist", 
				ClassType.Hamster => "Hamster", 
				ClassType.Firefighter => "Firefighter", 
				ClassType.Narcissist => "Narcissist", 
				ClassType.LoneWolf => "LoneWolf", 
				ClassType.Gunner => "Gunner", 
				ClassType.Band => "Band", 
				ClassType.MilesSoul => "MilesSoul", 
				ClassType.Qu => "Qu", 
				ClassType.Astronaut => "Astronaut", 
				ClassType.Langwang => "Langwang", 
				ClassType.Programmer => "Programmer", 
				ClassType.ChaosEnderman => "ChaosEnderman", 
				ClassType.Gargoyle => "Gargoyle", 
				ClassType.Alchemist => "Alchemist", 
				ClassType.Trickster => "Trickster", 
				_ => "None", 
			};
		}

		public static string DescribeBilingual(ClassType type)
		{
			if (type > ClassType.None && (int)type < _bilingualNames.Length && _bilingualNames[(int)type] != null)
			{
				return _bilingualNames[(int)type];
			}
			string text = ((type <= ClassType.None) ? "None · 无职业" : (DescribeEnglish(type) + " · " + Describe(type)));
			if (type > ClassType.None && (int)type < _bilingualNames.Length)
			{
				_bilingualNames[(int)type] = text;
			}
			return text;
		}
	}
	public enum ClassType
	{
		None,
		Explorer,
		Leader,
		Logistician,
		Doctor,
		Assassin,
		Pandora,
		Necromancer,
		M,
		Savage,
		Scout,
		StandardBearer,
		Dancer,
		Daihao,
		Saint,
		Mage,
		Challenger,
		MasterChef,
		Cook,
		Pacifist,
		Hamster,
		Firefighter,
		Narcissist,
		LoneWolf,
		Gunner,
		Band,
		MilesSoul,
		Qu,
		Astronaut,
		Langwang,
		Programmer,
		ChaosEnderman,
		Gargoyle,
		Alchemist,
		Trickster
	}
	public static class ModConfig
	{
		public class GeneralConfig
		{
			public ConfigEntry<bool> Enabled { get; private set; }

			public ConfigEntry<bool> RandomAssign { get; private set; }

			public ConfigEntry<float> PollInterval { get; private set; }

			public ConfigEntry<string> ClassListKey { get; private set; }

			public ConfigEntry<string> ClassListMode { get; private set; }

			public ConfigEntry<bool> MageEffectNotify { get; private set; }

			public ConfigEntry<bool> DisplaySync { get; private set; }

			public ConfigEntry<bool> NamePrefix { get; private set; }

			public ConfigEntry<bool> ReclassEnabled { get; private set; }

			public ConfigEntry<string> ReclassItem { get; private set; }

			public ConfigEntry<int> ReclassCount { get; private set; }

			public ConfigEntry<bool> ReclassEmoteEnabled { get; private set; }

			public ConfigEntry<string> ReclassEmoteName { get; private set; }

			public ConfigEntry<bool> NightHeatEnabled { get; private set; }

			public ConfigEntry<float> NightHeatIntervalSeconds { get; private set; }

			public ConfigEntry<float> NightHeatAmount { get; private set; }

			public ConfigEntry<bool> HalloweenPumpkinHat { get; private set; }

			public void Initialize(ConfigFile config)
			{
				Enabled = BindL(config, "通用", "启用模组", defaultValue: true, "总开关。关闭后主机不再分配职业、不再施加任何职业效果。");
				RandomAssign = BindL(config, "通用", "随机分配职业", defaultValue: true, "是否由主机随机分配职业。true=按权重随机分配(无职业选择界面);false=按职业顺序依次分配。");
				PollInterval = BindL(config, "通用", "状态轮询间隔", 0.5f, "主机轮询玩家同步状态的间隔(秒),用于「不屈 / 高代谢 / 共情」的闭环检测。建议 0.25~1。");
				ClassListKey = BindL(config, "通用", "职业显示-按键", "Tab", "按哪个键在主机屏幕上显示「当前职业分配情况」。\n填写 Unity 的 KeyCode 名,例如 Tab、F1、LeftAlt、BackQuote、Mouse2。\n注意:客机没有安装本模组,该面板只能显示在主机(房主)自己的屏幕上。");
				ClassListMode = BindL(config, "通用", "职业显示-模式", "1", "职业分配面板的显示方式(1 / 2 / 3):\n1 = 按住该键时显示,松开立即隐藏;\n2 = 按一下显示 5 秒后自动隐藏;\n3 = 每按一次在「显示 / 隐藏」之间切换。\n默认 1。旧版的文字值(按住显示 / 按下显示 / 按下切换、HoldToShow 等)仍兼容。");
				MageEffectNotify = BindL(config, "通用", "法师效果提示", defaultValue: true, "是否在主机屏幕左上角提示区,显示法师每次触发的是什么效果。\n默认 true。提示形如「法师:XXX 触发【强化】」,显示在左上角、约 8 秒后消失。\n提示只出现在主机(房主)自己的屏幕上;客机未安装本模组,看不到。\n若客机安装了附属模组 com.peakmodding.RollOfTheScoutClient,见下方「职业显示-网络同步」。");
				DisplaySync = BindL(config, "通用", "职业显示-网络同步", defaultValue: true, "是否把「职业分配情况」与「职业效果提示」广播给安装了附属模组\ncom.peakmodding.RollOfTheScoutClient 的客机(供其在客机自己屏幕上显示)。\n原理:主机用 Photon 自定义事件(码 197)周期广播职业快照、并在每次提示时广播该提示文本。\n未安装附属模组的客机完全不受影响(收到即忽略),因此客机仍然零安装可用。\n默认 true。设为 false 可彻底关闭该广播(客机侧将看不到任何职业显示)。");
				NamePrefix = BindL(config, "通用", "头顶职业前缀", defaultValue: false, "是否把「头顶玩家名称」改写成「【职业】昵称」,例如「Rhys」→「【先驱】Rhys」。\n「【职业】」用该玩家进入游戏时的皮肤色着色;「昵称」维持原版颜色不变。\n⚠ **默认关闭**:该功能由「头顶名称刷新补丁」每帧、每个玩家执行;若同时装有其它的头顶名称模组\n(例如 AlwaysDisplayPlayerName),双方会每帧互相改写文本,导致明显掉帧。\n⚠ 头顶名称是**每个客户端本地**的 UI,因此本开关只影响**本机屏幕**上看到的名字:\n   · 主机:显示本局账本里的职业;\n   · 装了本模组或附属模组的客机:显示主机广播过来的职业(见「职业显示-网络同步」);\n   · 未装任何模组的客机:看不到任何前缀(仍显示原版昵称)。\n默认 false(关闭)。");
				ReclassEnabled = BindL(config, "通用", "转职-启用", defaultValue: true, "转职机制总开关。true=玩家食用下方「转职-触发物品」后可更换职业;false=完全关闭。\n转职会在「当前未占满名额的其他职业」中按权重随机重新分配;\n每名玩家每局最多成功转职的次数由下方「转职-次数」决定;\n若没有可转的职业(其余职业名额都已满)则本次不生效、不消耗次数。");
				ReclassItem = BindL(config, "通用", "转职-触发物品", "Mushroom Glow", "食用哪种物品可触发转职(填物品预制体名)。默认 Mushroom Glow = 诡异菇。\n填错或留空则转职机制不会触发。");
				ReclassCount = BindL(config, "通用", "转职-次数", 1, "每名玩家每局最多可成功转职的次数(默认 1)。\n每次成功转职消耗 1 次;没有可转职业时不消耗次数。\n设为 0 等价于关闭转职。每局开始时重置。");
				ReclassEmoteEnabled = BindL(config, "通用", "转职-表情启用", defaultValue: false, "是否允许玩家通过做「转职-表情」来转职(默认**关闭**)。\n开启后:玩家本人做出指定表情即可立即转职一次,效果与食用「转职-触发物品」完全一致,\n并与物品转职**共用**上面的「转职-次数」(每次成功都消耗 1 次)。\n只在玩家**存活、未昏迷、未死亡**时生效;主机为职业效果强制播放的表情不会触发转职。\n注意:「不行不行」是高频沟通表情,默认关闭以避免误触消耗掉本局唯一的转职机会。");
				ReclassEmoteName = BindL(config, "通用", "转职-表情", "A_Scout_Emote_Nono", "触发转职的表情。固定填原版动画名(默认 A_Scout_Emote_Nono,即「不行不行」)。\n大小写不敏感;留空则表情转职不会触发。\n为避免与职业效果冲突,该值**不允许**是跳舞 / 后空翻 / 装死等会被主机强制播放的表情\n(A_Scout_Emote_Dance / Dance1 / Dance2 / BackFlip / PlayDead),命中时表情转职自动失效。");
				NightHeatEnabled = BindL(config, "通用", "夜晚抵消寒冷", defaultValue: true, "是否抵消「天阶 5 及以上难度」夜晚的寒冷(夜晚每 5 秒 +2.5 寒冷)。\n原理:该夜寒由原版在**每个玩家自己的客户端上**结算,主机无法直接关闭,\n因此改为由主机周期性给所有存活玩家 +「炎热」——原版里炎热与寒冷互为抵消。\n仅在「夜晚 + 天阶>=5(或自定义局开启冷夜)+ 尚未走完 3 个检查点」时生效,条件与原版一致。\n默认 true(开启)。设为 false 恢复原版夜寒。");
				NightHeatIntervalSeconds = BindL(config, "通用", "夜晚抵消寒冷-间隔", 5f, "每隔多少秒下发一次「炎热」(秒)。默认 5,与原版夜寒的积累节奏一致。\n设为 0 或负数关闭本效果。");
				NightHeatAmount = BindL(config, "通用", "夜晚抵消寒冷-量", 0.025f, "每次下发的「炎热」量(0.025 = 2.5 点)。\n默认 0.025:满夜时原版正好每 5 秒 +2.5 寒冷(0.005/秒),因此该默认值可完全抵消。\n若同时受其它寒冷来源影响,可适当调大。");
				HalloweenPumpkinHat = BindL(config, "通用", "万圣节-南瓜头", defaultValue: false, "万圣节彩蛋:开启后,房间内所有玩家(含机场大厅、中途加入与退出重进的玩家)的头饰\n会被强制换成南瓜头(帽子编号 27);关闭后恢复为各自原本的头饰。\n开启 / 关闭的切换有 60 秒内置冷却:冷却期间的修改会被**直接忽略**(不会排队到冷却结束后生效),\n如冷却期间改过本项,请在冷却结束后再切换一次。\n玩家做「耸肩」表情(A_Scout_Emote_Shrug)会在「原本帽子 / 南瓜头」之间切换;\n每名玩家该切换有 30 秒内置冷却,冷却期间不做任何检测与判断,也不会延迟到冷却结束后生效。\n仅在主机生效;默认 false(关闭)。");
			}
		}

		public class ExplorerConfig
		{
			public ConfigEntry<bool> Enabled { get; private set; }

			public ConfigEntry<int> MaxPlayers { get; private set; }

			public ConfigEntry<int> MinPlayers { get; private set; }

			public ConfigEntry<int> Weight { get; private set; }

			public ConfigEntry<int> BackpackItemCount { get; private set; }

			public ConfigEntry<int> RopeSpoolWeight { get; private set; }

			public ConfigEntry<int> ClimbingSpikeWeight { get; private set; }

			public ConfigEntry<int> ScoutCookiesWeight { get; private set; }

			public ConfigEntry<int> ChainShooterWeight { get; private set; }

			public ConfigEntry<int> RescueHookWeight { get; private set; }

			public ConfigEntry<int> AntiZookaWeight { get; private set; }

			public ConfigEntry<float> HungerIntervalSeconds { get; private set; }

			public ConfigEntry<float> HungerPerTick { get; private set; }

			public ConfigEntry<float> SprintIntervalSeconds { get; private set; }

			public ConfigEntry<float> DrowsyClearAmount { get; private set; }

			public void Initialize(ConfigFile config)
			{
				Enabled = BindL(config, "先驱", "启用职业", defaultValue: true, "是否启用先驱职业。");
				MaxPlayers = BindL(config, "先驱", "最大人数", 1, "先驱最多分配给几名玩家。设为 0 表示不限制。");
				MinPlayers = BindL(config, "先驱", "最小人数", 0, "先驱最少分配给几名玩家。人数不足时会优先补齐该职业。");
				Weight = BindL(config, "先驱", "抽取权重", 10, "随机分配时先驱被抽中的权重。");
				BackpackItemCount = BindL(config, "先驱", "背包物品数", 4, "开路:每次进入新关卡时直接装备到该玩家身上的背包内装入几件物品(背包容量 4)。\n物品在下方物品池中按权重「独立随机」抽取,允许重复。");
				RopeSpoolWeight = BindL(config, "先驱", "物品权重-绳索", 10, "物品池权重:绳索 RopeSpool。");
				ClimbingSpikeWeight = BindL(config, "先驱", "物品权重-岩钉", 7, "物品池权重:岩钉 ClimbingSpike。");
				ScoutCookiesWeight = BindL(config, "先驱", "物品权重-童军饼干", 2, "物品池权重:童军饼干 ScoutCookies。");
				ChainShooterWeight = BindL(config, "先驱", "物品权重-锁链发射器", 2, "物品池权重:锁链发射器 ChainShooter。");
				RescueHookWeight = BindL(config, "先驱", "物品权重-救援抓钩", 2, "物品池权重:救援抓钩 RescueHook。");
				AntiZookaWeight = BindL(config, "先驱", "物品权重-反重力炮", 1, "物品池权重:反重力炮 AntiZooka。");
				HungerIntervalSeconds = BindL(config, "先驱", "高代