Decompiled source of ArtifactLobby v0.1.0

plugins/MuckArtifactLobby.dll

Decompiled 12 hours ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using UnityEngine;
using UnityEngine.Events;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.0", FrameworkDisplayName = ".NET Standard 2.0")]
[assembly: AssemblyCompany("MuckArtifactLobby")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyDescription("The lobby host turns artifact mods on and off for every player. Shows nothing without an artifact mod.")]
[assembly: AssemblyFileVersion("0.1.0.0")]
[assembly: AssemblyInformationalVersion("0.1.0+b3815082b9868be7615596cfa6cd60a86ecd2179")]
[assembly: AssemblyProduct("Artifact Lobby")]
[assembly: AssemblyTitle("MuckArtifactLobby")]
[assembly: InternalsVisibleTo("MuckArtifactLobby.Tests")]
[assembly: AssemblyVersion("0.1.0.0")]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class IsReadOnlyAttribute : Attribute
	{
	}
}
namespace MuckArtifactLobby
{
	[BepInPlugin("evan.muck.artifactlobby", "Artifact Lobby", "0.1.0")]
	public class ArtifactLobbyPlugin : BaseUnityPlugin
	{
		public const string Guid = "evan.muck.artifactlobby";

		public const string Name = "Artifact Lobby";

		public const string Version = "0.1.0";

		internal static ManualLogSource Log;

		private static readonly HashSet<string> LoggedKeys = new HashSet<string>();

		private void Awake()
		{
			//IL_0018: 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_002d: Expected O, but got Unknown
			//IL_002d: Unknown result type (might be due to invalid IL or missing references)
			//IL_003d: Expected O, but got Unknown
			//IL_003d: Unknown result type (might be due to invalid IL or missing references)
			//IL_004d: Expected O, but got Unknown
			//IL_004d: Unknown result type (might be due to invalid IL or missing references)
			//IL_005d: Expected O, but got Unknown
			//IL_006c: Expected O, but got Unknown
			Log = ((BaseUnityPlugin)this).Logger;
			if (SteamLobbyData.TryInit())
			{
				Harmony val = new Harmony("evan.muck.artifactlobby");
				TryPatch(val, typeof(LobbyOpenPatch));
				TryPatch(val, typeof(LobbyClosePatch));
				TryPatch(val, typeof(StartGamePatch));
				TryPatch(val, typeof(LobbySettingsStartPatch));
				TryPatch(val, typeof(UpdateSettingPatch));
				LobbySession.Changed += SyncPanel;
			}
		}

		private static void SyncPanel()
		{
			try
			{
				ArtifactPanel.Sync();
			}
			catch (Exception arg)
			{
				LogOnce("sync", $"Artifact Lobby: updating the Artifacts panel failed: {arg}");
			}
		}

		private void Start()
		{
			if (ArtifactRegistry.All.Count == 0)
			{
				Log.LogInfo((object)("Artifact Lobby 0.1.0 loaded. " + ArtifactRegistry.Summary()));
				return;
			}
			Log.LogInfo((object)"Artifact Lobby 0.1.0 loaded.");
			Log.LogInfo((object)("Artifact Lobby: " + ArtifactRegistry.Summary()));
		}

		private void Update()
		{
			try
			{
				LobbySession.Poll(Time.unscaledTime);
			}
			catch (Exception arg)
			{
				LobbySession.Clear();
				LogOnce("poll", $"Artifact Lobby: reading the host's artifacts failed: {arg}");
			}
		}

		internal static void LogOnce(string key, string message)
		{
			if (LoggedKeys.Add(key))
			{
				ManualLogSource log = Log;
				if (log != null)
				{
					log.LogError((object)message);
				}
			}
		}

		private static void TryPatch(Harmony harmony, Type patchClass)
		{
			try
			{
				harmony.CreateClassProcessor(patchClass).Patch();
			}
			catch (Exception arg)
			{
				Log.LogError((object)$"Artifact Lobby failed to patch {patchClass.Name}: {arg}");
			}
		}
	}
	internal static class ArtifactPanel
	{
		private sealed class Row
		{
			public ArtifactEntry Entry;

			public UiSettings Setting;

			public Component Description;
		}

		private static readonly Color DescriptionColor = new Color(0.8f, 0.8f, 0.8f, 1f);

		private static readonly string[] OffOn = new string[2] { "Off", "On" };

		private static GameObject button;

		private static GameObject panel;

		private static Component title;

		private static readonly List<Row> Rows = new List<Row>();

		private static bool open;

		private static bool Built
		{
			get
			{
				if ((Object)(object)button != (Object)null)
				{
					return (Object)(object)panel != (Object)null;
				}
				return false;
			}
		}

		public static void Build(LobbySettings settings)
		{
			//IL_014d: Unknown result type (might be due to invalid IL or missing references)
			//IL_015c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0181: Unknown result type (might be due to invalid IL or missing references)
			//IL_018b: Expected O, but got Unknown
			//IL_0294: Unknown result type (might be due to invalid IL or missing references)
			//IL_02f6: Unknown result type (might be due to invalid IL or missing references)
			//IL_030c: Unknown result type (might be due to invalid IL or missing references)
			Rows.Clear();
			button = null;
			panel = null;
			title = null;
			open = false;
			if (ArtifactRegistry.All.Count == 0 || !UiReflection.TryInit())
			{
				return;
			}
			Transform parent = ((Component)settings).transform.parent;
			Transform val = settings.startButton.transform.parent.Find("BackBtn");
			UiSettings friendlyFireSetting = settings.friendlyFireSetting;
			Transform list = ((Component)friendlyFireSetting).transform.parent;
			int siblingIndex = ((Component)friendlyFireSetting).transform.GetSiblingIndex();
			if ((Object)(object)val == (Object)null || siblingIndex < 1 || siblingIndex + 1 >= list.childCount || (Object)(object)UiReflection.FindText(((Component)list.GetChild(siblingIndex - 1)).gameObject) == (Object)null)
			{
				ArtifactLobbyPlugin.LogOnce("build", "Artifact Lobby: the lobby's Back button or Player Damage row is not where expected; no Artifacts panel.");
				return;
			}
			GameObject gameObject = ((Component)list.GetChild(siblingIndex - 1)).gameObject;
			GameObject gameObject2 = ((Component)list.GetChild(siblingIndex + 1)).gameObject;
			GameObject val2 = null;
			GameObject val3 = null;
			try
			{
				val2 = CloneInactive(((Component)val.parent).gameObject, parent, "ArtifactLobbyButton", delegate(GameObject clone)
				{
					DestroyChildren(clone.transform);
				});
				Place(val2, new Vector2(-50f, -642.6f), new Vector2(350f, 75f));
				GameObject obj = Object.Instantiate<GameObject>(((Component)val).gameObject, val2.transform, false);
				UiReflection.ReplaceOnClick(obj, new UnityAction(Toggle));
				UiReflection.SetText(UiReflection.FindText(obj), "Artifacts");
				Transform newList = null;
				val3 = CloneInactive(((Component)settings).gameObject, parent, "ArtifactLobbyPanel", delegate(GameObject clone)
				{
					Object.DestroyImmediate((Object)(object)clone.GetComponent<LobbySettings>());
					newList = clone.transform.Find(((Object)list).name);
					DestroyChildren(newList);
				});
				Component val4 = UiReflection.FindText(val3);
				List<Row> list2 = new List<Row>();
				foreach (ArtifactEntry item in ArtifactRegistry.All)
				{
					if (list2.Count > 0)
					{
						Clone(gameObject2, newList, 5f);
					}
					UiReflection.SetText(UiReflection.FindText(Clone(gameObject, newList, 30f)), item.Name);
					GameObject obj2 = Clone(((Component)friendlyFireSetting).gameObject, newList, 68.6f);
					DestroyChildren(obj2.transform);
					UiSettings component = obj2.GetComponent<UiSettings>();
					component.AddSettings(item.IsOn ? 1 : 0, OffOn);
					Component val5 = UiReflection.FindText(Clone(gameObject, newList, 28f));
					UiReflection.SetAutoSize(val5, 12f, 22f);
					UiReflection.SetColor(val5, DescriptionColor);
					UiReflection.SetText(val5, item.Description);
					list2.Add(new Row
					{
						Entry = item,
						Setting = component,
						Description = val5
					});
				}
				Place(val3, new Vector2(-420f, -50f), new Vector2(350f, PanelLayout.Height(list2.Count)));
				title = val4;
				button = val2;
				panel = val3;
				Rows.AddRange(list2);
			}
			catch
			{
				if ((Object)(object)val2 != (Object)null)
				{
					Object.Destroy((Object)(object)val2);
				}
				if ((Object)(object)val3 != (Object)null)
				{
					Object.Destroy((Object)(object)val3);
				}
				throw;
			}
			LogPositions();
			Sync();
		}

		public static void Sync()
		{
			if (!Built)
			{
				return;
			}
			PanelView panelView = CurrentView();
			if (!panelView.Visible)
			{
				open = false;
			}
			button.SetActive(panelView.Visible);
			panel.SetActive(open);
			if (!panelView.Visible)
			{
				return;
			}
			UiReflection.SetText(title, panelView.Title);
			for (int i = 0; i < Rows.Count; i++)
			{
				int num = (panelView.Rows[i].On ? 1 : 0);
				if (Rows[i].Setting.setting != num)
				{
					UiReflection.UpdateSetting(Rows[i].Setting, num);
				}
				UiReflection.SetText(Rows[i].Description, panelView.Rows[i].Description);
			}
		}

		public static void OnSettingChanged(UiSettings setting)
		{
			if (!Built)
			{
				return;
			}
			int num = Rows.FindIndex((Row row) => row.Setting == setting);
			if (num < 0)
			{
				return;
			}
			bool flag = setting.setting == 1;
			if (LobbySession.IsHost)
			{
				ArtifactEntry entry = Rows[num].Entry;
				if (entry.IsOn != flag)
				{
					entry.Set(flag);
					LobbySession.Publish();
					ArtifactLobbyPlugin.Log.LogInfo((object)("Artifact Lobby: host turned " + entry.Name + " " + (flag ? "on" : "off")));
				}
			}
			else
			{
				PanelView panelView = CurrentView();
				if (panelView.Visible && panelView.Rows[num].On != flag)
				{
					UiReflection.UpdateSetting(setting, panelView.Rows[num].On ? 1 : 0);
				}
			}
		}

		private static void Toggle()
		{
			if (Built)
			{
				open = !open;
				ArtifactLobbyPlugin.Log.LogInfo((object)(open ? "Artifact Lobby: panel opened" : "Artifact Lobby: panel closed"));
				Sync();
				if (open)
				{
					LogPositions();
				}
			}
		}

		private static PanelView CurrentView()
		{
			return PanelView.For(LobbySession.IsHost, LobbySession.HostPicks, Rows.ConvertAll((Row row) => row.Entry));
		}

		private static GameObject CloneInactive(GameObject source, Transform parent, string name, Action<GameObject> strip)
		{
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_000b: Expected O, but got Unknown
			GameObject val = new GameObject("ArtifactLobbyHolder");
			val.SetActive(false);
			try
			{
				GameObject val2 = Object.Instantiate<GameObject>(source, val.transform, false);
				((Object)val2).name = name;
				strip(val2);
				val2.transform.SetParent(parent, false);
				return val2;
			}
			finally
			{
				Object.DestroyImmediate((Object)(object)val);
			}
		}

		private static GameObject Clone(GameObject source, Transform parent, float height)
		{
			//IL_000e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			GameObject obj = Object.Instantiate<GameObject>(source, parent, false);
			RectTransform val = (RectTransform)obj.transform;
			val.sizeDelta = new Vector2(val.sizeDelta.x, height);
			return obj;
		}

		private static void DestroyChildren(Transform parent)
		{
			for (int num = parent.childCount - 1; num >= 0; num--)
			{
				Object.DestroyImmediate((Object)(object)((Component)parent.GetChild(num)).gameObject);
			}
		}

		private static void Place(GameObject target, Vector2 position, Vector2 size)
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_000b: 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_000d: Unknown result type (might be due to invalid IL or missing references)
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0025: Unknown result type (might be due to invalid IL or missing references)
			//IL_0026: 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_002d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0033: Unknown result type (might be due to invalid IL or missing references)
			//IL_0034: Unknown result type (might be due to invalid IL or missing references)
			//IL_003a: Unknown result type (might be due to invalid IL or missing references)
			RectTransform val = (RectTransform)target.transform;
			Vector2 val2 = default(Vector2);
			((Vector2)(ref val2))..ctor(1f, 1f);
			val.pivot = val2;
			Vector2 anchorMin = (val.anchorMax = val2);
			val.anchorMin = anchorMin;
			val.anchoredPosition = position;
			val.sizeDelta = size;
		}

		private static void LogPositions()
		{
			Canvas.ForceUpdateCanvases();
			ArtifactLobbyPlugin.Log.LogInfo((object)("Artifact Lobby: Artifacts button at " + Centre(button.transform)));
			foreach (Row row in Rows)
			{
				Transform transform = ((Component)row.Setting).transform;
				ArtifactLobbyPlugin.Log.LogInfo((object)("Artifact Lobby: " + row.Entry.Name + " Off at " + Centre(transform.GetChild(0)) + ", On at " + Centre(transform.GetChild(1))));
			}
			if (!PanelLayout.FitsOnScreen(Rows.Count))
			{
				ArtifactLobbyPlugin.Log.LogWarning((object)string.Format(CultureInfo.InvariantCulture, "Artifact Lobby: {0} artifacts make the panel {1:0.#} tall; its bottom runs off a 1080-high screen.", Rows.Count, PanelLayout.Height(Rows.Count)));
			}
		}

		private static string Centre(Transform target)
		{
			//IL_0008: Unknown result type (might be due to invalid IL or missing references)
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0021: Unknown result type (might be due to invalid IL or missing references)
			//IL_002b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0030: Unknown result type (might be due to invalid IL or missing references)
			//IL_003b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0052: Unknown result type (might be due to invalid IL or missing references)
			Vector3[] array = (Vector3[])(object)new Vector3[4];
			((RectTransform)target).GetWorldCorners(array);
			Vector3 val = (array[0] + array[2]) * 0.5f;
			return string.Format(CultureInfo.InvariantCulture, "{0:0.000},{1:0.000}", val.x / (float)Screen.width, 1f - val.y / (float)Screen.height);
		}
	}
	public sealed class ArtifactEntry
	{
		private readonly Func<bool> isOn;

		private readonly Action<bool> setOn;

		public string Id { get; }

		public string Name { get; }

		public string Description { get; }

		public bool IsOn
		{
			get
			{
				try
				{
					return isOn();
				}
				catch (Exception arg)
				{
					ArtifactLobbyPlugin.LogOnce("get:" + Id, $"Artifact Lobby: reading {Name}'s switch failed: {arg}");
					return false;
				}
			}
		}

		internal ArtifactEntry(string id, string name, string description, Func<bool> isOn, Action<bool> setOn)
		{
			Id = id;
			Name = name;
			Description = description;
			this.isOn = isOn;
			this.setOn = setOn;
		}

		internal void Set(bool on)
		{
			if (IsOn == on)
			{
				return;
			}
			try
			{
				setOn(on);
			}
			catch (Exception arg)
			{
				ArtifactLobbyPlugin.LogOnce("set:" + Id, string.Format("Artifact Lobby: turning {0} {1} failed: {2}", Name, on ? "on" : "off", arg));
			}
		}
	}
	public static class ArtifactRegistry
	{
		private static readonly List<ArtifactEntry> Entries = new List<ArtifactEntry>();

		internal static IReadOnlyList<ArtifactEntry> All => Entries;

		public static bool Register(string id, string name, string description, ConfigEntry<bool> enabled)
		{
			if (enabled == null)
			{
				return Refuse(id, "no Enabled config entry");
			}
			return Register(id, name, description, () => enabled.Value, delegate(bool on)
			{
				enabled.Value = on;
				if (!((ConfigEntryBase)enabled).ConfigFile.SaveOnConfigSet)
				{
					((ConfigEntryBase)enabled).ConfigFile.Save();
				}
			});
		}

		public static bool Register(string id, string name, string description, Func<bool> isOn, Action<bool> setOn)
		{
			string text = Problem(id, isOn, setOn);
			if (text != null)
			{
				return Refuse(id, text);
			}
			Entries.RemoveAll((ArtifactEntry entry) => entry.Id == id);
			Entries.Add(new ArtifactEntry(id, string.IsNullOrEmpty(name) ? id : name, description ?? "", isOn, setOn));
			Entries.Sort(delegate(ArtifactEntry a, ArtifactEntry b)
			{
				int num = string.CompareOrdinal(a.Name, b.Name);
				return (num == 0) ? string.CompareOrdinal(a.Id, b.Id) : num;
			});
			return true;
		}

		internal static string Summary()
		{
			if (Entries.Count == 0)
			{
				return "No artifact mods registered, so no lobby panel. In a lobby you host, players running Artifact Lobby get every artifact turned off.";
			}
			List<string> list = new List<string>();
			foreach (ArtifactEntry entry in Entries)
			{
				list.Add(entry.Name);
			}
			string arg = ((Entries.Count == 1) ? "artifact" : "artifacts");
			return string.Format("{0} {1} registered: {2}", Entries.Count, arg, string.Join(", ", list));
		}

		internal static void Reset()
		{
			Entries.Clear();
		}

		private static bool Refuse(string id, string problem)
		{
			ManualLogSource log = ArtifactLobbyPlugin.Log;
			if (log != null)
			{
				log.LogWarning((object)("Artifact Lobby: refused to register '" + id + "': " + problem + "."));
			}
			return false;
		}

		private static string Problem(string id, Func<bool> isOn, Action<bool> setOn)
		{
			if (isOn == null || setOn == null)
			{
				return "no on/off getter and setter";
			}
			if (string.IsNullOrEmpty(id))
			{
				return "empty id";
			}
			if (id.IndexOfAny(ArtifactWire.Reserved) >= 0)
			{
				return "the id holds one of | , =";
			}
			return null;
		}
	}
	internal static class ArtifactWire
	{
		public const string LobbyKey = "evan.muck.artifactlobby";

		private const string Version = "1";

		internal static readonly char[] Reserved = new char[3] { '|', ',', '=' };

		public static string Encode(IEnumerable<(string Id, bool On)> picks)
		{
			StringBuilder stringBuilder = new StringBuilder("1").Append('|');
			bool flag = true;
			foreach (var (value, flag2) in picks)
			{
				if (!flag)
				{
					stringBuilder.Append(',');
				}
				stringBuilder.Append(value).Append('=').Append(flag2 ? '1' : '0');
				flag = false;
			}
			return stringBuilder.ToString();
		}

		public static bool TryDecode(string text, out Dictionary<string, bool> picks)
		{
			picks = null;
			if (string.IsNullOrEmpty(text))
			{
				return false;
			}
			int num = text.IndexOf('|');
			if (num < 0 || text.Substring(0, num) != "1")
			{
				return false;
			}
			Dictionary<string, bool> dictionary = new Dictionary<string, bool>();
			string text2 = text.Substring(num + 1);
			if (text2.Length > 0)
			{
				string[] array = text2.Split(new char[1] { ',' });
				for (int i = 0; i < array.Length; i++)
				{
					string[] array2 = array[i].Split(new char[1] { '=' });
					if (array2.Length != 2 || array2[0].Length == 0 || array2[0].IndexOf('|') >= 0)
					{
						return false;
					}
					if (array2[1] != "0" && array2[1] != "1")
					{
						return false;
					}
					if (dictionary.ContainsKey(array2[0]))
					{
						return false;
					}
					dictionary[array2[0]] = array2[1] == "1";
				}
			}
			picks = dictionary;
			return true;
		}
	}
	internal enum PickReason
	{
		HostOn,
		HostOff,
		HostLacks
	}
	internal readonly struct ArtifactPick
	{
		public string Id { get; }

		public PickReason Reason { get; }

		public bool On => Reason == PickReason.HostOn;

		public ArtifactPick(string id, PickReason reason)
		{
			Id = id;
			Reason = reason;
		}
	}
	internal sealed class HostPlan
	{
		public IReadOnlyList<ArtifactPick> Picks { get; }

		public IReadOnlyList<string> MissingHere { get; }

		private HostPlan(IReadOnlyList<ArtifactPick> picks, IReadOnlyList<string> missingHere)
		{
			Picks = picks;
			MissingHere = missingHere;
		}

		public static HostPlan For(IEnumerable<string> localIds, IReadOnlyDictionary<string, bool> host)
		{
			List<string> local = localIds.ToList();
			bool value;
			List<ArtifactPick> picks = local.Select((string id) => new ArtifactPick(id, (!host.TryGetValue(id, out value)) ? PickReason.HostLacks : ((!value) ? PickReason.HostOff : PickReason.HostOn))).ToList();
			List<string> missingHere = (from pair in host
				where pair.Value && !local.Contains(pair.Key)
				select pair.Key).OrderBy<string, string>((string id) => id, StringComparer.Ordinal).ToList();
			return new HostPlan(picks, missingHere);
		}

		public void ApplyTo(IEnumerable<ArtifactEntry> entries)
		{
			Dictionary<string, bool> dictionary = Picks.ToDictionary((ArtifactPick pick) => pick.Id, (ArtifactPick pick) => pick.On);
			foreach (ArtifactEntry entry in entries)
			{
				if (dictionary.TryGetValue(entry.Id, out var value))
				{
					entry.Set(value);
				}
			}
		}

		public string Describe(Func<string, string> nameOf)
		{
			if (Picks.Count == 0)
			{
				return "none";
			}
			return string.Join(", ", Picks.Select((ArtifactPick pick) => nameOf(pick.Id) + Word(pick.Reason)));
		}

		private static string Word(PickReason reason)
		{
			return reason switch
			{
				PickReason.HostOn => " on", 
				PickReason.HostOff => " off", 
				_ => " off (host lacks it)", 
			};
		}
	}
	internal enum LobbyRole
	{
		Idle,
		Host,
		Client
	}
	internal static class LobbyRoles
	{
		public static LobbyRole For(bool ownsLobby, int localArtifacts)
		{
			if (!ownsLobby)
			{
				if (localArtifacts <= 0)
				{
					return LobbyRole.Idle;
				}
				return LobbyRole.Client;
			}
			return LobbyRole.Host;
		}
	}
	internal static class PanelLayout
	{
		public const float LabelHeight = 30f;

		public const float RowHeight = 68.6f;

		public const float DescriptionHeight = 28f;

		public const float LineHeight = 5f;

		public const float MaxHeight = 1030f;

		private const float Chrome = 70f;

		private const float ListPadding = 35f;

		private const float Gap = 8f;

		public static float Height(int artifacts)
		{
			int num = 4 * artifacts - 1;
			return 105f + (float)artifacts * 126.6f + (float)(artifacts - 1) * 5f + (float)(num - 1) * 8f;
		}

		public static bool FitsOnScreen(int artifacts)
		{
			return Height(artifacts) <= 1030f;
		}
	}
	internal readonly struct RowView
	{
		public bool On { get; }

		public string Description { get; }

		public RowView(bool on, string description)
		{
			On = on;
			Description = description;
		}
	}
	internal sealed class PanelView
	{
		public const string HostTitle = "Artifacts";

		public const string ClientTitle = "Host's artifacts";

		public const string HostLacksText = "The host does not have this artifact.";

		private static readonly PanelView Hidden = new PanelView(null, new RowView[0]);

		public string Title { get; }

		public bool Visible => Title != null;

		public IReadOnlyList<RowView> Rows { get; }

		private PanelView(string title, IReadOnlyList<RowView> rows)
		{
			Title = title;
			Rows = rows;
		}

		public static PanelView For(bool isHost, IReadOnlyDictionary<string, bool> hostPicks, IReadOnlyList<ArtifactEntry> entries)
		{
			if (isHost)
			{
				return new PanelView("Artifacts", entries.Select((ArtifactEntry entry) => new RowView(entry.IsOn, entry.Description)).ToList());
			}
			if (hostPicks == null)
			{
				return Hidden;
			}
			HostPlan plan = HostPlan.For(entries.Select((ArtifactEntry entry) => entry.Id), hostPicks);
			return new PanelView("Host's artifacts", entries.Select((ArtifactEntry entry, int i) => new RowView(plan.Picks[i].On, (plan.Picks[i].Reason == PickReason.HostLacks) ? "The host does not have this artifact." : entry.Description)).ToList());
		}
	}
	[HarmonyPatch(typeof(LobbyVisuals), "OpenLobby")]
	internal static class LobbyOpenPatch
	{
		private static void Postfix(LobbyVisuals __instance)
		{
			try
			{
				LobbySession.Open(SteamLobbyData.CurrentLobby(__instance));
			}
			catch (Exception arg)
			{
				LobbySession.Clear();
				ArtifactLobbyPlugin.LogOnce("LobbyOpenPatch", $"Artifact Lobby: lobby open failed: {arg}");
			}
		}
	}
	[HarmonyPatch(typeof(LobbyVisuals), "CloseLobby", new Type[] { })]
	internal static class LobbyClosePatch
	{
		private static void Postfix()
		{
			try
			{
				LobbySession.Clear();
			}
			catch (Exception arg)
			{
				ArtifactLobbyPlugin.LogOnce("LobbyClosePatch", $"Artifact Lobby: lobby close failed: {arg}");
			}
		}
	}
	[HarmonyPatch(typeof(ClientHandle), "StartGame", new Type[] { typeof(Packet) })]
	internal static class StartGamePatch
	{
		private static void Prefix()
		{
			try
			{
				LobbySession.OnStartGame();
			}
			catch (Exception arg)
			{
				ArtifactLobbyPlugin.LogOnce("StartGamePatch", $"Artifact Lobby: applying the host's artifacts failed: {arg}");
			}
		}
	}
	[HarmonyPatch(typeof(LobbySettings), "Start", new Type[] { })]
	internal static class LobbySettingsStartPatch
	{
		private static void Postfix(LobbySettings __instance)
		{
			try
			{
				ArtifactPanel.Build(__instance);
			}
			catch (Exception arg)
			{
				ArtifactLobbyPlugin.LogOnce("LobbySettingsStartPatch", $"Artifact Lobby: building the Artifacts panel failed: {arg}");
			}
		}
	}
	[HarmonyPatch(typeof(UiSettings), "UpdateSetting", new Type[] { typeof(int) })]
	internal static class UpdateSettingPatch
	{
		private static void Postfix(UiSettings __instance)
		{
			try
			{
				ArtifactPanel.OnSettingChanged(__instance);
			}
			catch (Exception arg)
			{
				ArtifactLobbyPlugin.LogOnce("UpdateSettingPatch", $"Artifact Lobby: an artifact click failed: {arg}");
			}
		}
	}
	internal static class LobbySession
	{
		private const float PollSeconds = 0.5f;

		private static object lobby;

		private static float nextPoll;

		private static string hostText;

		public static bool IsHost { get; private set; }

		public static Dictionary<string, bool> HostPicks { get; private set; }

		public static event Action Changed;

		public static void Open(object steamLobby)
		{
			Reset();
			LobbyRole lobbyRole = LobbyRoles.For(SteamLobbyData.OwnerId(steamLobby) == SteamLobbyData.LocalSteamId(), ArtifactRegistry.All.Count);
			if (lobbyRole != LobbyRole.Idle)
			{
				lobby = steamLobby;
				IsHost = lobbyRole == LobbyRole.Host;
				if (IsHost)
				{
					Publish();
				}
				else
				{
					Refresh();
				}
			}
			LobbySession.Changed?.Invoke();
		}

		public static void Publish()
		{
			if (lobby != null && IsHost)
			{
				SteamLobbyData.SetData(lobby, "evan.muck.artifactlobby", ArtifactWire.Encode(ArtifactRegistry.All.Select((ArtifactEntry entry) => (Id: entry.Id, IsOn: entry.IsOn))));
			}
		}

		public static void Poll(float now)
		{
			if (lobby != null && !IsHost && !(now < nextPoll))
			{
				nextPoll = now + 0.5f;
				Refresh();
			}
		}

		public static void Clear()
		{
			Reset();
			LobbySession.Changed?.Invoke();
		}

		private static void Reset()
		{
			lobby = null;
			IsHost = false;
			nextPoll = 0f;
			hostText = null;
			HostPicks = null;
		}

		public static void OnStartGame()
		{
			if (lobby == null)
			{
				return;
			}
			IReadOnlyList<ArtifactEntry> all = ArtifactRegistry.All;
			Dictionary<string, string> names = all.ToDictionary((ArtifactEntry entry) => entry.Id, (ArtifactEntry entry) => entry.Name);
			IEnumerable<string> localIds = all.Select((ArtifactEntry entry) => entry.Id);
			if (IsHost)
			{
				Publish();
				Dictionary<string, bool> host = all.ToDictionary((ArtifactEntry entry) => entry.Id, (ArtifactEntry entry) => entry.IsOn);
				ArtifactLobbyPlugin.Log.LogInfo((object)("Artifact Lobby: host artifacts: " + HostPlan.For(localIds, host).Describe((string id) => names[id])));
				return;
			}
			Refresh();
			if (HostPicks == null)
			{
				ArtifactLobbyPlugin.Log.LogInfo((object)(string.IsNullOrEmpty(hostText) ? "Artifact Lobby: the host does not run Artifact Lobby; your own artifact configs apply." : ("Artifact Lobby: could not read the host's artifacts (" + hostText + "); your own artifact configs apply.")));
				return;
			}
			HostPlan hostPlan = HostPlan.For(localIds, HostPicks);
			hostPlan.ApplyTo(all);
			ArtifactLobbyPlugin.Log.LogInfo((object)("Artifact Lobby: applied the host's artifacts: " + hostPlan.Describe((string id) => names[id])));
			if (hostPlan.MissingHere.Count > 0)
			{
				ArtifactLobbyPlugin.Log.LogWarning((object)("Artifact Lobby: the host has artifacts on that you do not have installed: " + string.Join(", ", hostPlan.MissingHere)));
			}
		}

		private static void Refresh()
		{
			string data = SteamLobbyData.GetData(lobby, "evan.muck.artifactlobby");
			if (!(data == hostText))
			{
				hostText = data;
				HostPicks = (ArtifactWire.TryDecode(data, out var picks) ? picks : null);
				LobbySession.Changed?.Invoke();
			}
		}
	}
	internal static class SteamLobbyData
	{
		private const BindingFlags InstanceMembers = BindingFlags.Instance | BindingFlags.Public;

		private static FieldInfo currentLobby;

		private static MethodInfo setData;

		private static MethodInfo getData;

		private static PropertyInfo owner;

		private static PropertyInfo localSteamId;

		private static Func<object, object> friendId;

		private static Func<object, object> steamIdValue;

		public static bool TryInit()
		{
			List<string> list = new List<string>();
			try
			{
				currentLobby = typeof(LobbyVisuals).GetField("currentLobby", BindingFlags.Instance | BindingFlags.NonPublic);
				Type type = currentLobby?.FieldType;
				setData = type?.GetMethod("SetData", BindingFlags.Instance | BindingFlags.Public, null, new Type[2]
				{
					typeof(string),
					typeof(string)
				}, null);
				getData = type?.GetMethod("GetData", BindingFlags.Instance | BindingFlags.Public, null, new Type[1] { typeof(string) }, null);
				owner = type?.GetProperty("Owner", BindingFlags.Instance | BindingFlags.Public);
				friendId = Getter(owner?.PropertyType, "Id");
				localSteamId = type?.Assembly.GetType("Steamworks.SteamClient")?.GetProperty("SteamId", BindingFlags.Static | BindingFlags.Public);
				steamIdValue = Getter(localSteamId?.PropertyType, "Value");
				if (currentLobby == null)
				{
					list.Add("LobbyVisuals.currentLobby");
				}
				if (setData == null || setData.ReturnType != typeof(bool))
				{
					list.Add("Lobby.SetData(string, string)");
				}
				if (getData == null || getData.ReturnType != typeof(string))
				{
					list.Add("Lobby.GetData(string)");
				}
				if (owner == null)
				{
					list.Add("Lobby.Owner");
				}
				if (friendId == null)
				{
					list.Add("Friend.Id");
				}
				if (localSteamId == null)
				{
					list.Add("SteamClient.SteamId");
				}
				if (steamIdValue == null)
				{
					list.Add("SteamId.Value");
				}
			}
			catch (Exception ex)
			{
				list.Add(ex.ToString());
			}
			if (list.Count == 0)
			{
				return true;
			}
			ManualLogSource log = ArtifactLobbyPlugin.Log;
			if (log != null)
			{
				log.LogError((object)("Artifact Lobby: could not find " + string.Join(", ", list) + ". Lobby sync is off; each player's own artifact configs apply."));
			}
			return false;
		}

		public static object CurrentLobby(LobbyVisuals lobbyVisuals)
		{
			return currentLobby.GetValue(lobbyVisuals);
		}

		public static void SetData(object lobby, string key, string value)
		{
			setData.Invoke(lobby, new object[2] { key, value });
		}

		public static string GetData(object lobby, string key)
		{
			return (getData.Invoke(lobby, new object[1] { key }) as string) ?? "";
		}

		public static ulong OwnerId(object lobby)
		{
			return FriendId(owner.GetValue(lobby, null));
		}

		public static ulong LocalSteamId()
		{
			return SteamIdValue(localSteamId.GetValue(null, null));
		}

		internal static ulong FriendId(object friend)
		{
			return SteamIdValue(friendId(friend));
		}

		private static ulong SteamIdValue(object steamId)
		{
			return Convert.ToUInt64(steamIdValue(steamId));
		}

		private static Func<object, object> Getter(Type type, string name)
		{
			PropertyInfo property = type?.GetProperty(name, BindingFlags.Instance | BindingFlags.Public);
			if (property != null)
			{
				return (object target) => property.GetValue(target, null);
			}
			FieldInfo field = type?.GetField(name, BindingFlags.Instance | BindingFlags.Public);
			if (field != null)
			{
				return (object target) => field.GetValue(target);
			}
			return null;
		}
	}
	internal static class UiReflection
	{
		private const BindingFlags Public = BindingFlags.Instance | BindingFlags.Public;

		private static bool? ready;

		private static Type tmpText;

		private static Type button;

		private static PropertyInfo text;

		private static PropertyInfo fontSize;

		private static PropertyInfo enableAutoSizing;

		private static PropertyInfo fontSizeMin;

		private static PropertyInfo fontSizeMax;

		private static PropertyInfo color;

		private static PropertyInfo onClick;

		private static MethodInfo updateSetting;

		public static bool TryInit()
		{
			if (ready.HasValue)
			{
				return ready.Value;
			}
			List<string> list = new List<string>();
			try
			{
				tmpText = Type.GetType("TMPro.TMP_Text, Unity.TextMeshPro");
				button = Type.GetType("UnityEngine.UI.Button, UnityEngine.UI");
				if (tmpText == null)
				{
					list.Add("TMPro.TMP_Text");
				}
				if (button == null)
				{
					list.Add("UnityEngine.UI.Button");
				}
				text = Settable(tmpText, "text", typeof(string), list);
				fontSize = Settable(tmpText, "fontSize", typeof(float), list);
				enableAutoSizing = Settable(tmpText, "enableAutoSizing", typeof(bool), list);
				fontSizeMin = Settable(tmpText, "fontSizeMin", typeof(float), list);
				fontSizeMax = Settable(tmpText, "fontSizeMax", typeof(float), list);
				color = Settable(tmpText, "color", typeof(Color), list);
				onClick = button?.GetProperty("onClick", BindingFlags.Instance | BindingFlags.Public);
				bool flag = onClick != null && onClick.CanWrite && typeof(UnityEvent).IsAssignableFrom(onClick.PropertyType) && onClick.PropertyType.GetConstructor(Type.EmptyTypes) != null;
				if (button != null && !flag)
				{
					list.Add("Button.onClick");
				}
				updateSetting = typeof(UiSettings).GetMethod("UpdateSetting", BindingFlags.Instance | BindingFlags.NonPublic, null, new Type[1] { typeof(int) }, null);
				if (updateSetting == null)
				{
					list.Add("UiSettings.UpdateSetting(int)");
				}
			}
			catch (Exception ex)
			{
				list.Add(ex.ToString());
			}
			ready = list.Count == 0;
			if (!ready.Value)
			{
				ManualLogSource log = ArtifactLobbyPlugin.Log;
				if (log != null)
				{
					log.LogError((object)("Artifact Lobby: could not find " + string.Join(", ", list) + ". The lobby panel is off; lobby sync still runs."));
				}
			}
			return ready.Value;
		}

		public static Component FindText(GameObject root)
		{
			return root.GetComponentInChildren(tmpText, true);
		}

		public static void SetText(Component tmp, string value)
		{
			text.SetValue(tmp, value, null);
		}

		public static void SetColor(Component tmp, Color value)
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			color.SetValue(tmp, value, null);
		}

		public static void SetAutoSize(Component tmp, float min, float max)
		{
			enableAutoSizing.SetValue(tmp, true, null);
			fontSizeMin.SetValue(tmp, min, null);
			fontSizeMax.SetValue(tmp, max, null);
			fontSize.SetValue(tmp, max, null);
		}

		public static void ReplaceOnClick(GameObject buttonObject, UnityAction action)
		{
			//IL_000f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0015: Expected O, but got Unknown
			UnityEvent val = (UnityEvent)Activator.CreateInstance(onClick.PropertyType);
			val.AddListener(action);
			onClick.SetValue(buttonObject.GetComponent(button), val, null);
		}

		public static void UpdateSetting(UiSettings row, int value)
		{
			updateSetting.Invoke(row, new object[1] { value });
		}

		private static PropertyInfo Settable(Type type, string name, Type valueType, List<string> missing)
		{
			PropertyInfo propertyInfo = null;
			try
			{
				propertyInfo = type?.GetProperty(name, BindingFlags.Instance | BindingFlags.Public);
			}
			catch (AmbiguousMatchException)
			{
			}
			if (propertyInfo != null && propertyInfo.CanWrite && propertyInfo.PropertyType == valueType)
			{
				return propertyInfo;
			}
			if (type != null)
			{
				missing.Add("TMP_Text." + name);
			}
			return null;
		}
	}
	public static class MyPluginInfo
	{
		public const string PLUGIN_GUID = "MuckArtifactLobby";

		public const string PLUGIN_NAME = "Artifact Lobby";

		public const string PLUGIN_VERSION = "0.1.0";
	}
}