Decompiled source of DirectorMode v1.0.0

plugins/ElfDevelop.DirectorMode.dll

Decompiled 9 hours ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using System.Text;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using DefaultNamespace;
using ExitGames.Client.Photon;
using Microsoft.CodeAnalysis;
using Photon.Pun;
using Photon.Realtime;
using TMPro;
using UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.InputSystem.Controls;
using UnityEngine.Localization;
using UnityEngine.Localization.Settings;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
using Zorro.Core;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: IgnoresAccessChecksTo("Assembly-CSharp")]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("0.0.0.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

		public NullableAttribute(byte P_0)
		{
			NullableFlags = new byte[1] { P_0 };
		}

		public NullableAttribute(byte[] P_0)
		{
			NullableFlags = P_0;
		}
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableContextAttribute : Attribute
	{
		public readonly byte Flag;

		public NullableContextAttribute(byte P_0)
		{
			Flag = P_0;
		}
	}
	[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 DirectorMode
{
	internal class DirectorHost : MonoBehaviour
	{
		internal static DirectorHost Instance;

		private readonly Dictionary<int, DirectorBank> banks = new Dictionary<int, DirectorBank>();

		private float nextStatus;

		private static bool InOldWorld
		{
			get
			{
				if (PhotonNetwork.InRoom && !PhotonGameLobbyHandler.IsSurface && (Object)(object)Level.currentLevel != (Object)null)
				{
					return (Object)(object)PlayerHandler.instance != (Object)null;
				}
				return false;
			}
		}

		private void Awake()
		{
			Instance = this;
			SceneManager.sceneLoaded += delegate
			{
				banks.Clear();
			};
		}

		private void Update()
		{
			if (PhotonNetwork.InRoom)
			{
				DirectorPlugin.EnsureSubscribed();
			}
			if (!PhotonNetwork.IsMasterClient || !InOldWorld)
			{
				return;
			}
			bool flag = Time.time >= nextStatus;
			string text = null;
			if (flag)
			{
				nextStatus = Time.time + 2f;
			}
			foreach (Player player in PlayerHandler.instance.players)
			{
				int num = ActorOf(player);
				if (num < 0 || !player.data.dead)
				{
					continue;
				}
				DirectorBank directorBank = Bank(num);
				directorBank.Accrue(Time.deltaTime, DirectorPlugin.PointsPerMinute.Value, DirectorPlugin.MaxPoints.Value);
				if (!flag)
				{
					continue;
				}
				if (text == null)
				{
					text = MonsterList.Serialize(from m in AllowedMonsters()
						select (name: ((Object)m).name, Cost: m.GetComponent<IBudgetCost>().Cost));
				}
				DirectorPlugin.SendStatus(num, Status(directorBank, "", text));
			}
		}

		private static int ActorOf(Player? player)
		{
			if (!((Object)(object)player == (Object)null) && !player.ai)
			{
				PlayerRefs refs = player.refs;
				object obj;
				if (refs == null)
				{
					obj = null;
				}
				else
				{
					PhotonView view = refs.view;
					obj = ((view != null) ? view.Owner : null);
				}
				if (obj != null)
				{
					return player.refs.view.Owner.ActorNumber;
				}
			}
			return -1;
		}

		private DirectorBank Bank(int actor)
		{
			if (!banks.TryGetValue(actor, out DirectorBank value))
			{
				value = (banks[actor] = new DirectorBank(DirectorPlugin.StartPoints.Value));
			}
			return value;
		}

		private static DirectorStatus Status(DirectorBank bank, string message, string monsters)
		{
			return new DirectorStatus
			{
				Points = bank.Points,
				MaxPoints = DirectorPlugin.MaxPoints.Value,
				Recent = bank.RecentReleases(Time.time, DirectorPlugin.WindowSeconds.Value),
				MaxRecent = DirectorPlugin.MaxPerWindow.Value,
				Wait = bank.WaitTime(Time.time, DirectorPlugin.MaxPerWindow.Value, DirectorPlugin.WindowSeconds.Value),
				Message = message,
				Monsters = monsters
			};
		}

		internal void HandleRequest(int actor, string monster, int targetViewId)
		{
			string message;
			try
			{
				message = Release(actor, monster, targetViewId);
			}
			catch (Exception arg)
			{
				DirectorPlugin.Logger.LogError((object)$"Failed to release {monster}: {arg}");
				message = DirectorPlugin.T("Something went wrong, see the host's log", "Что-то пошло не так, смотри лог хоста");
			}
			string monsters = (InOldWorld ? MonsterList.Serialize(from m in AllowedMonsters()
				select (name: ((Object)m).name, Cost: m.GetComponent<IBudgetCost>().Cost)) : "");
			DirectorPlugin.SendStatus(actor, Status(Bank(actor), message, monsters));
		}

		private string Release(int actor, string monster, int targetViewId)
		{
			//IL_01b0: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b5: Unknown result type (might be due to invalid IL or missing references)
			//IL_0236: Unknown result type (might be due to invalid IL or missing references)
			//IL_023b: Unknown result type (might be due to invalid IL or missing references)
			if (!InOldWorld)
			{
				return DirectorPlugin.T("Only in the Old World", "Только в старом мире");
			}
			Player val = ((IEnumerable<Player>)PlayerHandler.instance.players).FirstOrDefault((Func<Player, bool>)((Player p) => ActorOf(p) == actor));
			if ((Object)(object)val == (Object)null || !val.data.dead)
			{
				return DirectorPlugin.T("Only the dead can direct", "Режиссируют только мёртвые");
			}
			GameObject val2 = ((IEnumerable<GameObject>)AllowedMonsters()).FirstOrDefault((Func<GameObject, bool>)((GameObject m) => ((Object)m).name == monster));
			if ((Object)(object)val2 == (Object)null)
			{
				return DirectorPlugin.T(monster + " is not on this level", monster + " нет на этом уровне");
			}
			int cost = val2.GetComponent<IBudgetCost>().Cost;
			Player val3 = ((IEnumerable<Player>)PlayerHandler.instance.players).FirstOrDefault((Func<Player, bool>)((Player p) => (Object)(object)p != (Object)null && !p.ai && p.refs.view.ViewID == targetViewId));
			if ((Object)(object)val3 == (Object)null || val3.data.dead)
			{
				return DirectorPlugin.T("Watch a living player", "Наблюдай за живым игроком");
			}
			DirectorBank directorBank = Bank(actor);
			switch (directorBank.CanSpend(cost, Time.time, DirectorPlugin.MaxPerWindow.Value, DirectorPlugin.WindowSeconds.Value))
			{
			case RejectReason.TooSoon:
			{
				int num2 = Mathf.CeilToInt(directorBank.WaitTime(Time.time, DirectorPlugin.MaxPerWindow.Value, DirectorPlugin.WindowSeconds.Value));
				return DirectorPlugin.T($"Too soon, wait {num2} s", $"Рано, подожди {num2} с");
			}
			case RejectReason.NotEnoughPoints:
				return DirectorPlugin.T("Not enough points", "Не хватает очков");
			default:
			{
				List<PatrolPoint> list = PointsFor(val2);
				Vector3 at = val3.Center();
				float fallback = DirectorPlugin.MaxDistance.Value * 1.5f;
				List<(float, bool)> list2 = list.Select(delegate(PatrolPoint p)
				{
					//IL_0006: 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_0011: Unknown result type (might be due to invalid IL or missing references)
					//IL_0016: Unknown result type (might be due to invalid IL or missing references)
					//IL_0017: Unknown result type (might be due to invalid IL or missing references)
					//IL_001d: Unknown result type (might be due to invalid IL or missing references)
					//IL_0023: Unknown result type (might be due to invalid IL or missing references)
					//IL_0050: Unknown result type (might be due to invalid IL or missing references)
					//IL_0055: Unknown result type (might be due to invalid IL or missing references)
					//IL_005a: Unknown result type (might be due to invalid IL or missing references)
					Vector3 val4 = ((Component)p).transform.position - at;
					float num3 = SpawnPointPicker.WeightedDistance(val4.x, val4.y, val4.z);
					Player val5 = default(Player);
					bool item = num3 >= DirectorPlugin.MinDistance.Value && num3 <= fallback && PlayerHandler.instance.CanAnAlivePlayerSeePoint(((Component)p).transform.position + Vector3.up, ref val5, (Player)null);
					return (distance: num3, visible: item);
				}).ToList();
				int num = SpawnPointPicker.Pick(list2, DirectorPlugin.MinDistance.Value, DirectorPlugin.MaxDistance.Value, fallback);
				if (num < 0)
				{
					return DirectorPlugin.T("No hidden spot nearby, try another player", "Рядом нет укромного места, выбери другого");
				}
				PhotonNetwork.Instantiate(((Object)val2).name, ((Component)list[num]).transform.position, Quaternion.identity, (byte)0, (object[])null);
				directorBank.Spend(cost, Time.time);
				Player owner = val3.refs.view.Owner;
				string text = ((owner != null) ? owner.NickName : null) ?? ((Object)val3).name;
				ManualLogSource logger = DirectorPlugin.Logger;
				object[] array = new object[4];
				Player owner2 = val.refs.view.Owner;
				array[0] = ((owner2 != null) ? owner2.NickName : null);
				array[1] = ((Object)val2).name;
				array[2] = list2[num].Item1;
				array[3] = text;
				logger.LogInfo((object)string.Format("Director {0} released {1} {2:0} m from {3}", array));
				return DirectorPlugin.T(((Object)val2).name + " released near " + text, ((Object)val2).name + " выпущен рядом с " + text);
			}
			}
		}

		private static List<GameObject> AllowedMonsters()
		{
			RoundSpawner val = Object.FindObjectOfType<RoundSpawner>();
			if ((Object)(object)val == (Object)null || val.possibleSpawns == null)
			{
				return new List<GameObject>();
			}
			string[] excluded = (from s in DirectorPlugin.ExcludedMonsters.Value.Split(new char[1] { ',' }, StringSplitOptions.RemoveEmptyEntries)
				select s.Trim() into s
				where s.Length > 0
				select s).ToArray();
			return (from m in val.possibleSpawns
				where (Object)(object)m != (Object)null && m.GetComponent<IBudgetCost>() != null
				where !excluded.Any((string e) => ((Object)m).name.IndexOf(e, StringComparison.OrdinalIgnoreCase) >= 0)
				group m by ((Object)m).name into g
				select g.First() into m
				orderby m.GetComponent<IBudgetCost>().Cost, ((Object)m).name
				select m).ToList();
		}

		private static List<PatrolPoint> PointsFor(GameObject prefab)
		{
			Level currentLevel = Level.currentLevel;
			IHasPatrolGroup componentInChildren = prefab.GetComponentInChildren<IHasPatrolGroup>();
			if (componentInChildren != null)
			{
				List<PatrolPoint> pointsInGroups = currentLevel.GetPointsInGroups(componentInChildren.GetGroup());
				if (pointsInGroups.Count > 0)
				{
					return pointsInGroups;
				}
			}
			return (from p in currentLevel.patrolGroups.Values.SelectMany((List<PatrolPoint> p) => p)
				where (Object)(object)p != (Object)null
				select p).ToList();
		}
	}
	internal class DirectorPanel : MonoBehaviour
	{
		internal static DirectorPanel Instance;

		private const int VisibleRows = 7;

		private GameObject? root;

		private TextMeshProUGUI? text;

		private int selected;

		private DirectorStatus? status;

		private float statusTime;

		private List<(string Name, int Cost)> monsters = new List<(string, int)>();

		private string message = "";

		private float messageUntil;

		private float nextRefresh;

		private float nextRequest;

		private static bool Directing
		{
			get
			{
				if (PhotonNetwork.InRoom && !PhotonGameLobbyHandler.IsSurface && (Object)(object)Player.localPlayer != (Object)null && Player.localPlayer.data.dead && Spectate.spectating && (Object)(object)Player.observedPlayer != (Object)null && (Object)(object)Player.observedPlayer != (Object)(object)Player.localPlayer)
				{
					return !Player.observedPlayer.data.dead;
				}
				return false;
			}
		}

		private void Awake()
		{
			Instance = this;
		}

		internal void SetStatus(DirectorStatus newStatus)
		{
			status = newStatus;
			statusTime = Time.time;
			if (!string.IsNullOrEmpty(newStatus.Monsters))
			{
				monsters = MonsterList.Parse(newStatus.Monsters);
			}
			if (!string.IsNullOrEmpty(newStatus.Message))
			{
				message = newStatus.Message;
				messageUntil = Time.time + 4f;
			}
			nextRefresh = 0f;
		}

		private static bool MenuOpen()
		{
			EscapeMenu instance = Singleton<EscapeMenu>.Instance;
			if ((Object)(object)instance != (Object)null && instance.Open)
			{
				return true;
			}
			Modal val = Object.FindObjectOfType<Modal>();
			if ((Object)(object)val != (Object)null)
			{
				return val.Open;
			}
			return false;
		}

		private void Update()
		{
			if (!Directing)
			{
				if ((Object)(object)root != (Object)null && root.activeSelf)
				{
					root.SetActive(false);
				}
				status = null;
				monsters.Clear();
				return;
			}
			EnsureUi();
			root.SetActive(true);
			if (monsters.Count > 0)
			{
				selected = Mathf.Clamp(selected, 0, monsters.Count - 1);
				Keyboard current = Keyboard.current;
				bool flag = current != null && ((ButtonControl)current.wKey).wasPressedThisFrame;
				bool flag2 = current != null && ((ButtonControl)current.sKey).wasPressedThisFrame;
				bool flag3 = current != null && ((ButtonControl)current.spaceKey).wasPressedThisFrame;
				if ((flag || flag2 || flag3) && !MenuOpen())
				{
					if (flag)
					{
						selected = (selected - 1 + monsters.Count) % monsters.Count;
					}
					if (flag2)
					{
						selected = (selected + 1) % monsters.Count;
					}
					if (flag3 && Time.time >= nextRequest)
					{
						nextRequest = Time.time + 0.75f;
						DirectorPlugin.SendRequest(monsters[selected].Name, Player.observedPlayer.refs.view.ViewID);
					}
					nextRefresh = 0f;
				}
			}
			if (Time.time >= nextRefresh)
			{
				nextRefresh = Time.time + 0.25f;
				((TMP_Text)text).text = Render();
			}
		}

		private string Render()
		{
			StringBuilder stringBuilder = new StringBuilder();
			stringBuilder.AppendLine("<color=#FFD600><b>" + DirectorPlugin.T("DIRECTOR", "РЕЖИССЁР") + "</b></color>");
			if (status == null)
			{
				stringBuilder.AppendLine(DirectorPlugin.T("Waiting for the host…", "Ждём хоста…"));
				stringBuilder.AppendLine(DirectorPlugin.T("(the host needs DirectorMode too)", "(у хоста тоже должен быть DirectorMode)"));
				return stringBuilder.ToString();
			}
			float num = Time.time - statusTime;
			float num2 = Mathf.Min(status.MaxPoints, status.Points + num * DirectorPlugin.PointsPerMinute.Value / 60f);
			float num3 = Mathf.Max(0f, status.Wait - num);
			stringBuilder.AppendLine(DirectorPlugin.T($"Points: {Mathf.Floor(num2):0} / {status.MaxPoints:0}", $"Очки: {Mathf.Floor(num2):0} / {status.MaxPoints:0}"));
			stringBuilder.AppendLine((num3 > 0f) ? DirectorPlugin.T($"Next monster in {Mathf.CeilToInt(num3)} s", $"Следующий монстр через {Mathf.CeilToInt(num3)} с") : DirectorPlugin.T($"Released: {status.Recent}/{status.MaxRecent}", $"Выпущено: {status.Recent}/{status.MaxRecent}"));
			Player owner = Player.observedPlayer.refs.view.Owner;
			string text = ((owner != null) ? owner.NickName : null) ?? ((Object)Player.observedPlayer).name;
			stringBuilder.AppendLine(DirectorPlugin.T("Watching: " + text + " (A/D)", "Смотришь: " + text + " (A/D)"));
			stringBuilder.AppendLine();
			if (monsters.Count == 0)
			{
				stringBuilder.AppendLine(DirectorPlugin.T("No monsters on this level", "На этом уровне нет монстров"));
			}
			int num4 = Mathf.Clamp(selected - 3, 0, Mathf.Max(0, monsters.Count - 7));
			for (int i = num4; i < Mathf.Min(monsters.Count, num4 + 7); i++)
			{
				(string Name, int Cost) tuple = monsters[i];
				string item = tuple.Name;
				int item2 = tuple.Cost;
				bool flag = num2 + 0.0001f >= (float)item2;
				string text2 = string.Format("{0}{1}  {2}", (i == selected) ? "> " : "   ", item, item2);
				stringBuilder.AppendLine(flag ? ((i == selected) ? ("<b>" + text2 + "</b>") : text2) : ("<color=#777777>" + text2 + "</color>"));
			}
			stringBuilder.AppendLine();
			stringBuilder.AppendLine(DirectorPlugin.T("W/S choose, Space release", "W/S выбрать, Пробел выпустить"));
			if (Time.time < messageUntil)
			{
				stringBuilder.AppendLine("<color=#FFD600>" + message + "</color>");
			}
			return stringBuilder.ToString();
		}

		private void EnsureUi()
		{
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			//IL_001f: Expected O, but got Unknown
			//IL_0063: Unknown result type (might be due to invalid IL or missing references)
			//IL_007c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0082: Expected O, but got Unknown
			//IL_00b4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ea: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ff: Unknown result type (might be due to invalid IL or missing references)
			//IL_0114: 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_0137: Unknown result type (might be due to invalid IL or missing references)
			//IL_013d: Expected O, but got Unknown
			//IL_01e0: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f6: Unknown result type (might be due to invalid IL or missing references)
			//IL_0201: Unknown result type (might be due to invalid IL or missing references)
			//IL_0216: Unknown result type (might be due to invalid IL or missing references)
			//IL_022a: Unknown result type (might be due to invalid IL or missing references)
			if (!((Object)(object)root != (Object)null))
			{
				root = new GameObject("DirectorPanel");
				Object.DontDestroyOnLoad((Object)(object)root);
				Canvas obj = root.AddComponent<Canvas>();
				obj.renderMode = (RenderMode)0;
				obj.sortingOrder = 500;
				CanvasScaler obj2 = root.AddComponent<CanvasScaler>();
				obj2.uiScaleMode = (ScaleMode)1;
				obj2.referenceResolution = new Vector2(1920f, 1080f);
				obj2.matchWidthOrHeight = 0.5f;
				GameObject val = new GameObject("Background");
				val.transform.SetParent(root.transform, false);
				Image obj3 = val.AddComponent<Image>();
				((Graphic)obj3).color = new Color(0f, 0f, 0f, 0.55f);
				((Graphic)obj3).raycastTarget = false;
				RectTransform rectTransform = ((Graphic)obj3).rectTransform;
				rectTransform.anchorMin = new Vector2(1f, 0.5f);
				rectTransform.anchorMax = new Vector2(1f, 0.5f);
				rectTransform.pivot = new Vector2(1f, 0.5f);
				rectTransform.anchoredPosition = new Vector2(-30f, 0f);
				rectTransform.sizeDelta = new Vector2(430f, 470f);
				GameObject val2 = new GameObject("Text");
				val2.transform.SetParent(val.transform, false);
				text = val2.AddComponent<TextMeshProUGUI>();
				TMP_FontAsset val3 = (((Object)(object)HelmetText.Instance != (Object)null && (Object)(object)HelmetText.Instance.m_TextObject != (Object)null) ? ((TMP_Text)HelmetText.Instance.m_TextObject).font : null);
				if ((Object)(object)val3 != (Object)null)
				{
					((TMP_Text)text).font = val3;
				}
				((TMP_Text)text).fontSize = 24f;
				((TMP_Text)text).richText = true;
				((Graphic)text).raycastTarget = false;
				((TMP_Text)text).alignment = (TextAlignmentOptions)257;
				((Graphic)text).color = Color.white;
				RectTransform rectTransform2 = ((TMP_Text)text).rectTransform;
				rectTransform2.anchorMin = Vector2.zero;
				rectTransform2.anchorMax = Vector2.one;
				rectTransform2.offsetMin = new Vector2(18f, 14f);
				rectTransform2.offsetMax = new Vector2(-18f, -14f);
			}
		}
	}
	[ContentWarningPlugin("ElfDevelop.DirectorMode", "1.0.0", false)]
	[BepInPlugin("ElfDevelop.DirectorMode", "DirectorMode", "1.0.0")]
	public class DirectorPlugin : BaseUnityPlugin
	{
		internal const byte RequestEvent = 171;

		internal const byte StatusEvent = 172;

		internal static ConfigEntry<float> PointsPerMinute;

		internal static ConfigEntry<float> MaxPoints;

		internal static ConfigEntry<float> StartPoints;

		internal static ConfigEntry<int> MaxPerWindow;

		internal static ConfigEntry<float> WindowSeconds;

		internal static ConfigEntry<float> MinDistance;

		internal static ConfigEntry<float> MaxDistance;

		internal static ConfigEntry<string> ExcludedMonsters;

		private static bool subscribed;

		internal static ManualLogSource Logger { get; private set; }

		internal static bool IsRussian
		{
			get
			{
				//IL_000c: Unknown result type (might be due to invalid IL or missing references)
				//IL_0011: Unknown result type (might be due to invalid IL or missing references)
				try
				{
					Locale selectedLocale = LocalizationSettings.SelectedLocale;
					object obj;
					if (selectedLocale == null)
					{
						obj = null;
					}
					else
					{
						LocaleIdentifier identifier = selectedLocale.Identifier;
						obj = ((LocaleIdentifier)(ref identifier)).Code;
					}
					return ((string)obj)?.StartsWith("ru") ?? false;
				}
				catch
				{
					return false;
				}
			}
		}

		internal static string T(string en, string ru)
		{
			if (!IsRussian)
			{
				return en;
			}
			return ru;
		}

		private void Awake()
		{
			//IL_0039: Unknown result type (might be due to invalid IL or missing references)
			//IL_0043: Expected O, but got Unknown
			Logger = ((BaseUnityPlugin)this).Logger;
			PointsPerMinute = ((BaseUnityPlugin)this).Config.Bind<float>("Host", "PointsPerMinute", 3f, new ConfigDescription("Director points a dead player earns per minute in the Old World. Monster prices are the game's budget costs: Zombe 1, Snatcho 3, Jello 5, Knifo 6, Mime 8, BigSlap 15.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 60f), Array.Empty<object>()));
			MaxPoints = ((BaseUnityPlugin)this).Config.Bind<float>("Host", "MaxPoints", 15f, "Most points a director can save up.");
			StartPoints = ((BaseUnityPlugin)this).Config.Bind<float>("Host", "StartPoints", 2f, "Points a player has after dying for the first time that day.");
			MaxPerWindow = ((BaseUnityPlugin)this).Config.Bind<int>("Host", "MaxReleases", 2, "How many monsters one director can release within ReleaseWindowSeconds. Monsters rarely die, so this limits releases over time.");
			WindowSeconds = ((BaseUnityPlugin)this).Config.Bind<float>("Host", "ReleaseWindowSeconds", 180f, "The time window for MaxReleases.");
			MinDistance = ((BaseUnityPlugin)this).Config.Bind<float>("Host", "MinDistance", 10f, "Closest a released monster may appear to the watched player (metres, height counts 3x).");
			MaxDistance = ((BaseUnityPlugin)this).Config.Bind<float>("Host", "MaxDistance", 45f, "Preferred farthest distance; if no hidden spot is that close, up to 1.5x this is used.");
			ExcludedMonsters = ((BaseUnityPlugin)this).Config.Bind<string>("Host", "ExcludedMonsters", "Weeping", "Monsters directors can't release (a part of the name is enough). Monsters removed from the spawn pool by MonsterCount are never offered either.");
			((Component)this).gameObject.AddComponent<DirectorHost>();
			((Component)this).gameObject.AddComponent<DirectorPanel>();
			Logger.LogInfo((object)"DirectorMode 1.0.0 has loaded!");
		}

		internal static void EnsureSubscribed()
		{
			if (!subscribed && PhotonNetwork.NetworkingClient != null)
			{
				PhotonNetwork.NetworkingClient.EventReceived += OnEvent;
				subscribed = true;
			}
		}

		private static void OnEvent(EventData data)
		{
			try
			{
				if (data.Code == 171 && PhotonNetwork.IsMasterClient && data.CustomData is object[] array && array.Length == 2)
				{
					DirectorHost.Instance.HandleRequest(data.Sender, (string)array[0], (int)array[1]);
				}
				else if (data.Code == 172 && data.CustomData is object[] array2 && array2.Length == 7)
				{
					DirectorPanel.Instance.SetStatus(new DirectorStatus(array2));
				}
			}
			catch (Exception ex)
			{
				Logger.LogError((object)("Bad director event: " + ex));
			}
		}

		internal static void SendRequest(string monster, int targetViewId)
		{
			//IL_0036: Unknown result type (might be due to invalid IL or missing references)
			//IL_003b: Unknown result type (might be due to invalid IL or missing references)
			//IL_003d: 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_004c: Expected O, but got Unknown
			if (PhotonNetwork.IsMasterClient)
			{
				DirectorHost.Instance.HandleRequest(PhotonNetwork.LocalPlayer.ActorNumber, monster, targetViewId);
				return;
			}
			PhotonNetwork.RaiseEvent((byte)171, (object)new object[2] { monster, targetViewId }, new RaiseEventOptions
			{
				Receivers = (ReceiverGroup)2
			}, SendOptions.SendReliable);
		}

		internal static void SendStatus(int actor, DirectorStatus status)
		{
			//IL_0024: Unknown result type (might be due to invalid IL or missing references)
			//IL_002a: Expected O, but got Unknown
			//IL_003b: Unknown result type (might be due to invalid IL or missing references)
			if (actor == PhotonNetwork.LocalPlayer.ActorNumber)
			{
				DirectorPanel.Instance.SetStatus(status);
				return;
			}
			object[] array = status.ToEventData();
			RaiseEventOptions val = new RaiseEventOptions();
			val.TargetActors = new int[1] { actor };
			PhotonNetwork.RaiseEvent((byte)172, (object)array, val, SendOptions.SendReliable);
		}
	}
	internal class DirectorStatus
	{
		public float Points;

		public float MaxPoints;

		public int Recent;

		public int MaxRecent;

		public float Wait;

		public string Message = "";

		public string Monsters = "";

		public DirectorStatus()
		{
		}

		public DirectorStatus(object[] data)
		{
			Points = (float)data[0];
			MaxPoints = (float)data[1];
			Recent = (int)data[2];
			MaxRecent = (int)data[3];
			Wait = (float)data[4];
			Message = (string)data[5];
			Monsters = (string)data[6];
		}

		public object[] ToEventData()
		{
			return new object[7] { Points, MaxPoints, Recent, MaxRecent, Wait, Message, Monsters };
		}
	}
	internal enum RejectReason
	{
		None,
		NotEnoughPoints,
		TooSoon
	}
	internal class DirectorBank
	{
		private readonly List<float> releases = new List<float>();

		public float Points { get; private set; }

		public DirectorBank(float startPoints)
		{
			Points = startPoints;
		}

		public void Accrue(float seconds, float pointsPerMinute, float maxPoints)
		{
			if (!(seconds <= 0f) && !(pointsPerMinute <= 0f))
			{
				Points = Math.Min(maxPoints, Points + seconds * pointsPerMinute / 60f);
			}
		}

		public int RecentReleases(float now, float window)
		{
			releases.RemoveAll((float t) => now - t >= window);
			return releases.Count;
		}

		public float WaitTime(float now, int maxPerWindow, float window)
		{
			if (RecentReleases(now, window) < maxPerWindow)
			{
				return 0f;
			}
			return Math.Max(0f, releases[releases.Count - maxPerWindow] + window - now);
		}

		public RejectReason CanSpend(int cost, float now, int maxPerWindow, float window)
		{
			if (RecentReleases(now, window) >= maxPerWindow)
			{
				return RejectReason.TooSoon;
			}
			if (Points + 0.0001f < (float)cost)
			{
				return RejectReason.NotEnoughPoints;
			}
			return RejectReason.None;
		}

		public void Spend(int cost, float now)
		{
			Points = Math.Max(0f, Points - (float)cost);
			releases.Add(now);
		}
	}
	internal static class SpawnPointPicker
	{
		public static int Pick(IList<(float Distance, bool Visible)> points, float minDistance, float maxDistance, float fallbackDistance)
		{
			int result = -1;
			float num = float.MaxValue;
			for (int i = 0; i < points.Count; i++)
			{
				(float Distance, bool Visible) tuple = points[i];
				var (num2, _) = tuple;
				if (!tuple.Visible && !(num2 < minDistance) && !(num2 > fallbackDistance))
				{
					float num3 = ((num2 <= maxDistance) ? num2 : (fallbackDistance + num2));
					if (num3 < num)
					{
						num = num3;
						result = i;
					}
				}
			}
			return result;
		}

		public static float WeightedDistance(float dx, float dy, float dz)
		{
			return (float)Math.Sqrt(dx * dx + dy * 3f * dy * 3f + dz * dz);
		}
	}
	internal static class MonsterList
	{
		public static string Serialize(IEnumerable<(string Name, int Cost)> monsters)
		{
			List<string> list = new List<string>();
			foreach (var (text, num) in monsters)
			{
				if (!string.IsNullOrEmpty(text) && text.IndexOf('|') < 0 && text.IndexOf(':') < 0)
				{
					list.Add(text + ":" + num);
				}
			}
			return string.Join("|", list);
		}

		public static List<(string Name, int Cost)> Parse(string text)
		{
			List<(string, int)> list = new List<(string, int)>();
			if (string.IsNullOrEmpty(text))
			{
				return list;
			}
			string[] array = text.Split('|');
			foreach (string text2 in array)
			{
				int num = text2.LastIndexOf(':');
				if (num > 0 && int.TryParse(text2.Substring(num + 1), out var result))
				{
					list.Add((text2.Substring(0, num), result));
				}
			}
			return list;
		}
	}
}
namespace System.Runtime.CompilerServices
{
	[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
	internal sealed class IgnoresAccessChecksToAttribute : Attribute
	{
		public IgnoresAccessChecksToAttribute(string assemblyName)
		{
		}
	}
}