Decompiled source of CityBuilderBetaTracker v1.3.6

CityBuilderBetaTracker.dll

Decompiled 5 days ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text;
using BepInEx;
using BepInEx.Configuration;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.InputSystem.Controls;
using UnityEngine.Networking;
using ValheimAnno;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = "")]
[assembly: AssemblyVersion("0.0.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace CityBuilderBeta
{
	[BepInPlugin("1day2die.citybuilder.betatracker", "CityBuilder Beta Tracker", "1.3.6")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	public class BetaTracker : BaseUnityPlugin
	{
		[Serializable]
		public class Entry
		{
			public string id;

			public string mode;

			public string ver;

			public int uses;

			public int verdict;

			public string note = "";
		}

		[Serializable]
		public class State
		{
			public List<Entry> queue = new List<Entry>();

			public List<string> seen = new List<string>();

			public List<string> rated = new List<string>();
		}

		[Serializable]
		public class ReportBody
		{
			public string p_code;

			public string p_version;

			public string p_valheim;

			public List<Entry> p_entries;
		}

		[Serializable]
		public class RegisterBody
		{
			public string p_name;
		}

		public const string Guid = "1day2die.citybuilder.betatracker";

		public const string Version = "1.3.6";

		private const int MaxNameLength = 32;

		private const float SteamNameRetrySeconds = 10f;

		public const string Site = "https://valheimcitybuilder.com/beta";

		private const string Api = "https://valheimcitybuilder.com/api/rpc/";

		private static readonly string[] ModeNames = new string[4] { "sp", "mp_host", "mp_client", "dedicated" };

		private static bool open;

		private ConfigEntry<string> cfgName;

		private ConfigEntry<string> cfgCode;

		private ConfigEntry<bool> cfgSteamName;

		private float steamNameRetryAt;

		private ConfigEntry<Key> cfgKey;

		private ConfigEntry<bool> cfgPopups;

		private ConfigEntry<float> cfgPopupSeconds;

		private ConfigEntry<Key> cfgWorksKey;

		private ConfigEntry<Key> cfgProblemKey;

		private State state = new State();

		private string statePath;

		private readonly List<string> pending = new List<string>();

		private string mode;

		private float inWorldT = -1f;

		private float sendT = 20f;

		private bool busy;

		private string lastResult = "nothing sent yet";

		private bool placed;

		private Rect win = new Rect(0f, 0f, 900f, 660f);

		private Vector2 scroll;

		private int area;

		private string noteFor;

		private string noteText = "";

		private int noteVerdict;

		private string nameField = "";

		private readonly List<string> popups = new List<string>();

		private float popT;

		private string popAction;

		private string focusId;

		private GUIStyle sWin;

		private GUIStyle sText;

		private GUIStyle sDim;

		private GUIStyle sHead;

		private GUIStyle sRow;

		private GUIStyle sRowAlt;

		private GUIStyle sBox;

		private GUIStyle sTab;

		private GUIStyle sBtn;

		private GUIStyle sField;

		private bool active;

		private static readonly FieldInfo fiOverlayRect = typeof(Features).GetField("OverlayRect");

		private static readonly FieldInfo fiOverlayFrame = typeof(Features).GetField("OverlayFrame");

		private bool Named
		{
			get
			{
				if (!string.IsNullOrEmpty(cfgName.Value))
				{
					return cfgName.Value.Trim().Length >= 2;
				}
				return false;
			}
		}

		private void Awake()
		{
			if (AccessTools.TypeByName("ValheimAnno.Features")?.Assembly.GetName().Name != "WorldEdit")
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)"This Valheim City Builder is older than 1.8.0 (no beta hooks in World Edit), the Beta Tracker stays off. Update the mod to take part.");
				((Behaviour)this).enabled = false;
			}
			else
			{
				active = true;
				Init();
			}
		}

		[MethodImpl(MethodImplOptions.NoInlining)]
		private void Init()
		{
			//IL_0159: Unknown result type (might be due to invalid IL or missing references)
			//IL_015f: Expected O, but got Unknown
			cfgName = ((BaseUnityPlugin)this).Config.Bind<string>("Tester", "Name", "", "Your tester name on the website. Nothing is sent while it is empty.");
			cfgCode = ((BaseUnityPlugin)this).Config.Bind<string>("Tester", "Code", "", "Filled in by the tracker when it first reaches the website. Keep it to stay the same tester.");
			cfgSteamName = ((BaseUnityPlugin)this).Config.Bind<bool>("Tester", "UseSteamName", true, "When Name is empty, takes your Steam name as tester name. False: nothing is sent until you set a name yourself.");
			cfgKey = ((BaseUnityPlugin)this).Config.Bind<Key>("Tester", "PanelKey", (Key)102, "Opens the list of features to rate");
			cfgPopups = ((BaseUnityPlugin)this).Config.Bind<bool>("Popup", "Enabled", true, "Show a popup the first time you use a feature in a game mode, to rate it right away");
			cfgPopupSeconds = ((BaseUnityPlugin)this).Config.Bind<float>("Popup", "Seconds", 20f, "How long each popup stays before the next one (it stays in the F9 list either way)");
			cfgWorksKey = ((BaseUnityPlugin)this).Config.Bind<Key>("Popup", "WorksKey", (Key)67, "Rates the feature in the popup as working (None = off)");
			cfgProblemKey = ((BaseUnityPlugin)this).Config.Bind<Key>("Popup", "ProblemKey", (Key)66, "Opens the panel to describe a problem with the feature in the popup (None = off)");
			nameField = cfgName.Value;
			statePath = Path.Combine(Paths.ConfigPath, "citybuilder_beta_state.json");
			Load();
			Features.Used += OnUsed;
			Harmony h = new Harmony("1day2die.citybuilder.betatracker");
			Patch(h, typeof(Player), "TakeInput", "NoInput", prefix: false);
			Patch(h, typeof(PlayerController), "TakeInput", "NoInput", prefix: false);
			Patch(h, typeof(GameCamera), "UpdateMouseCapture", "SkipCapture", prefix: true);
			((BaseUnityPlugin)this).Logger.LogInfo((object)("Beta Tracker 1.3.6 for Valheim City Builder " + Features.Version + ", " + Features.All.features.Count + " features" + (Named ? (", tester " + cfgName.Value) : " – set a tester name (F9 or config) to take part")));
		}

		private void Patch(Harmony h, Type t, string method, string patch, bool prefix)
		{
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0013: Expected O, but got Unknown
			try
			{
				HarmonyMethod val = new HarmonyMethod(typeof(BetaTracker), patch, (Type[])null);
				h.Patch((MethodBase)AccessTools.Method(t, method, (Type[])null, (Type[])null), prefix ? val : null, prefix ? null : val, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)("Patch " + t.Name + "." + method + ": " + ex.Message));
			}
		}

		private static void NoInput(ref bool __result)
		{
			if (open)
			{
				__result = false;
			}
		}

		private static bool SkipCapture()
		{
			return !open;
		}

		private void OnUsed(string id)
		{
			if (pending.Count < 1000)
			{
				pending.Add(id);
			}
		}

		private static string Mode()
		{
			if (ZNet.IsSinglePlayer)
			{
				return "sp";
			}
			if (ZNet.instance.IsServer())
			{
				return "mp_host";
			}
			if (!ZNet.instance.IsCurrentServerDedicated())
			{
				return "mp_client";
			}
			return "dedicated";
		}

		private void Update()
		{
			//IL_010c: Unknown result type (might be due to invalid IL or missing references)
			TryTakeSteamName();
			if ((Object)(object)Player.m_localPlayer == (Object)null || (Object)(object)ZNet.instance == (Object)null)
			{
				inWorldT = -1f;
				mode = null;
			}
			else
			{
				if (inWorldT < 0f)
				{
					inWorldT = Time.time;
				}
				if (mode == null && Time.time - inWorldT > 8f)
				{
					mode = Mode();
					((BaseUnityPlugin)this).Logger.LogInfo((object)("Game mode: " + ModeText(mode)));
				}
			}
			if (mode != null && pending.Count > 0)
			{
				foreach (string item in pending)
				{
					Add(item, mode, 1, 0, "");
				}
				pending.Clear();
				Save();
			}
			Keyboard current2 = Keyboard.current;
			if (current2 != null && ((ButtonControl)current2[cfgKey.Value]).wasPressedThisFrame && !Typing())
			{
				open = !open;
				noteFor = null;
				focusId = null;
			}
			if (open)
			{
				Cursor.lockState = (CursorLockMode)0;
				Cursor.visible = true;
			}
			TickPopup(current2);
			sendT -= Time.unscaledDeltaTime;
			if (sendT <= 0f && !busy)
			{
				sendT = 60f;
				Send();
			}
		}

		private void TryTakeSteamName()
		{
			if (!Named && cfgSteamName.Value && string.IsNullOrWhiteSpace(cfgName.Value) && !(Time.unscaledTime < steamNameRetryAt))
			{
				steamNameRetryAt = Time.unscaledTime + 10f;
				string text = SteamPersonaName();
				if (text.Length >= 2)
				{
					cfgName.Value = text;
					nameField = text;
					((BaseUnityPlugin)this).Config.Save();
					((BaseUnityPlugin)this).Logger.LogInfo((object)("Tester name taken from Steam: " + text));
				}
			}
		}

		private static string SteamPersonaName()
		{
			try
			{
				string text = (((AccessTools.TypeByName("Steamworks.SteamFriends")?.GetMethod("GetPersonaName", BindingFlags.Static | BindingFlags.Public))?.Invoke(null, null) as string) ?? "").Trim();
				return text.Substring(0, Math.Min(32, text.Length));
			}
			catch (Exception)
			{
				return "";
			}
		}

		private static bool Typing()
		{
			try
			{
				if (((Object)(object)Chat.instance != (Object)null && Chat.instance.HasFocus()) || Console.IsVisible() || TextInput.IsVisible())
				{
					return true;
				}
			}
			catch
			{
			}
			return false;
		}

		private void TickPopup(Keyboard kb)
		{
			//IL_00c4: 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)
			if (popups.Count == 0 || open)
			{
				return;
			}
			if (mode == null || !cfgPopups.Value)
			{
				popups.Clear();
				return;
			}
			string text = popAction;
			popAction = null;
			switch (text)
			{
			case "works":
				RateWorks(popups[0]);
				return;
			case "problem":
				AskProblem(popups[0]);
				return;
			case "later":
				NextPopup();
				return;
			case "skip":
				popups.Clear();
				return;
			}
			if (kb != null && !Typing())
			{
				if (Pressed(kb, cfgWorksKey.Value))
				{
					RateWorks(popups[0]);
					return;
				}
				if (Pressed(kb, cfgProblemKey.Value))
				{
					AskProblem(popups[0]);
					return;
				}
			}
			popT -= Time.unscaledDeltaTime;
			if (popT <= 0f)
			{
				NextPopup();
			}
		}

		private static bool Pressed(Keyboard kb, Key k)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0004: Unknown result type (might be due to invalid IL or missing references)
			if ((int)k != 0)
			{
				return ((ButtonControl)kb[k]).wasPressedThisFrame;
			}
			return false;
		}

		private void NextPopup()
		{
			if (popups.Count > 0)
			{
				popups.RemoveAt(0);
			}
			popT = Mathf.Max(5f, cfgPopupSeconds.Value);
		}

		private void RateWorks(string id)
		{
			Add(id, mode, 0, 1, "");
			Save();
			NextPopup();
		}

		private void AskProblem(string id)
		{
			NextPopup();
			open = true;
			focusId = id;
			noteFor = id;
			noteVerdict = -1;
			noteText = "";
		}

		private bool Rated(string id, string m)
		{
			return RatingOf(id, m) != null;
		}

		private string RatingOf(string id, string m)
		{
			Info val = FeatureInfo(id);
			string since = ((val != null && !string.IsNullOrEmpty(val.changed)) ? val.changed : Features.Version);
			string text = id + "|" + m + "|";
			return (from r in state.rated
				where r.StartsWith(text) && VerAtLeast(r.Substring(text.Length).Split('|')[0], since)
				orderby ParseVer(r.Substring(text.Length).Split('|')[0]) descending
				select r).FirstOrDefault();
		}

		private static Version ParseVer(string v)
		{
			if (!System.Version.TryParse(v, out Version result))
			{
				return new Version(0, 0);
			}
			return result;
		}

		private static bool VerAtLeast(string a, string b)
		{
			return ParseVer(a) >= ParseVer(b);
		}

		private void Add(string id, string m, int uses, int verdict, string note)
		{
			string ver = Features.Version;
			string text = id + "|" + m + "|" + ver;
			if (uses > 0 && !state.seen.Contains(text))
			{
				state.seen.Add(text);
				if (verdict == 0 && cfgPopups.Value && !Rated(id, m) && !popups.Contains(id) && popups.Count < 50)
				{
					if (popups.Count == 0)
					{
						popT = Mathf.Max(5f, cfgPopupSeconds.Value);
					}
					popups.Add(id);
				}
			}
			if (verdict != 0)
			{
				state.rated.RemoveAll((string r) => r.StartsWith(text + "|"));
				state.rated.Add(text + "|" + verdict);
			}
			Entry entry = ((verdict == 0) ? state.queue.FirstOrDefault((Entry e) => e.id == id && e.mode == m && e.ver == ver && e.verdict == 0) : null);
			if (entry != null)
			{
				entry.uses += uses;
			}
			else if (state.queue.Count < 2000)
			{
				state.queue.Add(new Entry
				{
					id = id,
					mode = m,
					ver = ver,
					uses = uses,
					verdict = verdict,
					note = (note ?? "").Trim()
				});
			}
			if (verdict != 0)
			{
				sendT = Mathf.Min(sendT, 2f);
			}
		}

		private void Send()
		{
			if (!Named)
			{
				return;
			}
			if (string.IsNullOrEmpty(cfgCode.Value))
			{
				((MonoBehaviour)this).StartCoroutine(Register());
			}
			else
			{
				if (state.queue.Count == 0)
				{
					return;
				}
				string ver = state.queue[0].ver;
				List<Entry> batch = state.queue.Where((Entry e) => e.ver == ver).Take(200).ToList();
				ReportBody reportBody = new ReportBody
				{
					p_code = cfgCode.Value,
					p_version = ver,
					p_valheim = Version.GetVersionString(false),
					p_entries = batch
				};
				((MonoBehaviour)this).StartCoroutine(Post("report", MiniJson.Write((object)reportBody), delegate(bool ok, string text)
				{
					if (ok)
					{
						foreach (Entry item in batch)
						{
							state.queue.Remove(item);
						}
						lastResult = "sent " + batch.Count + " at " + DateTime.Now.ToString("HH:mm");
						Save();
						if (state.queue.Count > 0)
						{
							sendT = 5f;
						}
					}
					else
					{
						lastResult = "not sent: " + Short(text);
						if (text.Contains("unknown tester code"))
						{
							cfgCode.Value = "";
							sendT = 5f;
						}
						else
						{
							sendT = 120f;
						}
					}
				}));
			}
		}

		private IEnumerator Register()
		{
			string p_name = cfgName.Value.Trim();
			yield return Post("register_tester", MiniJson.Write((object)new RegisterBody
			{
				p_name = p_name
			}), delegate(bool ok, string answer)
			{
				string text = answer.Trim().Trim('"');
				if (ok && text.Length >= 16)
				{
					cfgCode.Value = text;
					((BaseUnityPlugin)this).Config.Save();
					lastResult = "registered as " + cfgName.Value.Trim() + " – on the website once 1Day2Die approves you";
					sendT = 2f;
					((BaseUnityPlugin)this).Logger.LogInfo((object)("Registered as beta tester " + cfgName.Value.Trim()));
				}
				else
				{
					lastResult = "registration failed: " + Short(answer);
					sendT = 300f;
				}
			});
		}

		private IEnumerator Post(string fn, string json, Action<bool, string> done)
		{
			busy = true;
			UnityWebRequest req = new UnityWebRequest("https://valheimcitybuilder.com/api/rpc/" + fn, "POST");
			try
			{
				req.uploadHandler = (UploadHandler)new UploadHandlerRaw(Encoding.UTF8.GetBytes(json));
				req.downloadHandler = (DownloadHandler)new DownloadHandlerBuffer();
				req.SetRequestHeader("Content-Type", "application/json");
				req.timeout = 20;
				yield return req.SendWebRequest();
				bool arg = (int)req.result == 1;
				string arg2 = ((req.downloadHandler != null && !string.IsNullOrEmpty(req.downloadHandler.text)) ? req.downloadHandler.text : (req.error ?? ""));
				busy = false;
				try
				{
					done(arg, arg2);
				}
				catch (Exception ex)
				{
					((BaseUnityPlugin)this).Logger.LogWarning((object)("Beta tracker: " + ex.Message));
				}
			}
			finally
			{
				busy = false;
				req.Dispose();
			}
		}

		private static string Short(string s)
		{
			s = (s ?? "").Replace("\n", " ");
			if (s.Length <= 90)
			{
				return s;
			}
			return s.Substring(0, 90) + "…";
		}

		private void Load()
		{
			try
			{
				if (File.Exists(statePath))
				{
					state = MiniJson.Read<State>(File.ReadAllText(statePath)) ?? new State();
				}
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)("Beta tracker state: " + ex.Message));
				state = new State();
			}
		}

		private void Save()
		{
			try
			{
				File.WriteAllText(statePath, MiniJson.Write((object)state));
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)("Beta tracker state: " + ex.Message));
			}
		}

		private void OnDestroy()
		{
			if (active)
			{
				Save();
			}
		}

		private static string ModeText(string m)
		{
			return m switch
			{
				"sp" => "singleplayer", 
				"mp_host" => "multiplayer (host)", 
				"mp_client" => "multiplayer (client)", 
				"dedicated" => "dedicated server", 
				_ => "not in a world", 
			};
		}

		private static Texture2D Tex(Color c)
		{
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_000f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0012: Unknown result type (might be due to invalid IL or missing references)
			//IL_0018: Unknown result type (might be due to invalid IL or missing references)
			//IL_001f: Expected O, but got Unknown
			Texture2D val = new Texture2D(1, 1)
			{
				hideFlags = (HideFlags)61
			};
			val.SetPixel(0, 0, c);
			val.Apply();
			return val;
		}

		private void Styles()
		{
			//IL_0056: Unknown result type (might be due to invalid IL or missing references)
			//IL_005b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0063: Unknown result type (might be due to invalid IL or missing references)
			//IL_006a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0073: Unknown result type (might be due to invalid IL or missing references)
			//IL_007d: Expected O, but got Unknown
			//IL_007d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0082: Unknown result type (might be due to invalid IL or missing references)
			//IL_008c: Expected O, but got Unknown
			//IL_0091: Expected O, but got Unknown
			//IL_00bb: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e9: 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_00f1: Unknown result type (might be due to invalid IL or missing references)
			//IL_0103: Unknown result type (might be due to invalid IL or missing references)
			//IL_0108: Unknown result type (might be due to invalid IL or missing references)
			//IL_0110: Unknown result type (might be due to invalid IL or missing references)
			//IL_0117: Unknown result type (might be due to invalid IL or missing references)
			//IL_0123: Expected O, but got Unknown
			//IL_012e: Unknown result type (might be due to invalid IL or missing references)
			//IL_013b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0140: Unknown result type (might be due to invalid IL or missing references)
			//IL_0148: Unknown result type (might be due to invalid IL or missing references)
			//IL_0154: Expected O, but got Unknown
			//IL_015f: Unknown result type (might be due to invalid IL or missing references)
			//IL_016c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0171: Unknown result type (might be due to invalid IL or missing references)
			//IL_017e: Expected O, but got Unknown
			//IL_0189: Unknown result type (might be due to invalid IL or missing references)
			//IL_0190: Unknown result type (might be due to invalid IL or missing references)
			//IL_0195: Unknown result type (might be due to invalid IL or missing references)
			//IL_019a: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a4: Expected O, but got Unknown
			//IL_01a4: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a9: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b3: Expected O, but got Unknown
			//IL_01b8: Expected O, but got Unknown
			//IL_01d7: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ed: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f7: Expected O, but got Unknown
			//IL_0209: Unknown result type (might be due to invalid IL or missing references)
			//IL_020e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0217: Unknown result type (might be due to invalid IL or missing references)
			//IL_0221: Expected O, but got Unknown
			//IL_0226: Expected O, but got Unknown
			//IL_0245: Unknown result type (might be due to invalid IL or missing references)
			//IL_025f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0264: Unknown result type (might be due to invalid IL or missing references)
			//IL_026c: Unknown result type (might be due to invalid IL or missing references)
			//IL_027c: Expected O, but got Unknown
			//IL_029d: Unknown result type (might be due to invalid IL or missing references)
			//IL_029e: Unknown result type (might be due to invalid IL or missing references)
			//IL_029f: Unknown result type (might be due to invalid IL or missing references)
			//IL_02a6: Unknown result type (might be due to invalid IL or missing references)
			//IL_02a8: Unknown result type (might be due to invalid IL or missing references)
			//IL_02a9: Unknown result type (might be due to invalid IL or missing references)
			//IL_02b0: Unknown result type (might be due to invalid IL or missing references)
			//IL_02be: Unknown result type (might be due to invalid IL or missing references)
			//IL_02c8: Expected O, but got Unknown
			//IL_02fd: Unknown result type (might be due to invalid IL or missing references)
			//IL_0355: Unknown result type (might be due to invalid IL or missing references)
			//IL_035c: Unknown result type (might be due to invalid IL or missing references)
			//IL_035e: Unknown result type (might be due to invalid IL or missing references)
			//IL_035f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0366: Unknown result type (might be due to invalid IL or missing references)
			//IL_0378: Unknown result type (might be due to invalid IL or missing references)
			//IL_037d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0385: Unknown result type (might be due to invalid IL or missing references)
			//IL_0395: Expected O, but got Unknown
			if (sWin == null)
			{
				Color val = default(Color);
				((Color)(ref val))..ctor(0.94f, 0.91f, 0.85f);
				Color textColor = default(Color);
				((Color)(ref textColor))..ctor(0.76f, 0.73f, 0.67f);
				Color val2 = default(Color);
				((Color)(ref val2))..ctor(0.98f, 0.83f, 0.48f);
				sWin = new GUIStyle(GUI.skin.window)
				{
					fontSize = 16,
					fontStyle = (FontStyle)1,
					padding = new RectOffset(16, 16, 34, 14),
					border = new RectOffset(0, 0, 0, 0)
				};
				GUIStyleState normal = sWin.normal;
				Texture2D background = (sWin.onNormal.background = Tex(new Color(0.09f, 0.085f, 0.075f, 0.98f)));
				normal.background = background;
				GUIStyleState normal2 = sWin.normal;
				Color textColor2 = (sWin.onNormal.textColor = val2);
				normal2.textColor = textColor2;
				sText = new GUIStyle(GUI.skin.label)
				{
					fontSize = 15,
					richText = true,
					alignment = (TextAnchor)3
				};
				sText.normal.textColor = val;
				sDim = new GUIStyle(sText)
				{
					fontSize = 13,
					wordWrap = true
				};
				sDim.normal.textColor = textColor;
				sHead = new GUIStyle(sText)
				{
					fontSize = 16
				};
				sHead.normal.textColor = val2;
				sRow = new GUIStyle
				{
					padding = new RectOffset(8, 8, 2, 2),
					margin = new RectOffset(0, 0, 0, 0)
				};
				sRow.normal.background = Tex(new Color(1f, 1f, 1f, 0.06f));
				sRowAlt = new GUIStyle(sRow);
				sRowAlt.normal.background = null;
				sBox = new GUIStyle
				{
					padding = new RectOffset(14, 14, 10, 10)
				};
				sBox.normal.background = Tex(new Color(0.15f, 0.14f, 0.12f, 0.98f));
				sBtn = new GUIStyle(GUI.skin.button)
				{
					fontSize = 14,
					fixedHeight = 26f
				};
				GUIStyleState normal3 = sBtn.normal;
				GUIStyleState hover = sBtn.hover;
				Color val5 = (sBtn.active.textColor = val);
				textColor2 = (hover.textColor = val5);
				normal3.textColor = textColor2;
				sTab = new GUIStyle(sBtn);
				GUIStyleState onNormal = sTab.onNormal;
				GUIStyleState onHover = sTab.onHover;
				Texture2D val8 = (sTab.onActive.background = Tex(new Color(0.78f, 0.62f, 0.3f, 1f)));
				background = (onHover.background = val8);
				onNormal.background = background;
				GUIStyleState onNormal2 = sTab.onNormal;
				GUIStyleState onHover2 = sTab.onHover;
				GUIStyleState onActive = sTab.onActive;
				((Color)(ref val5))..ctor(0.1f, 0.09f, 0.07f);
				onActive.textColor = val5;
				textColor2 = (onHover2.textColor = val5);
				onNormal2.textColor = textColor2;
				sField = new GUIStyle(GUI.skin.textField)
				{
					fontSize = 14,
					fixedHeight = 26f
				};
			}
		}

		private void OnGUI()
		{
			//IL_00c7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f2: Expected O, but got Unknown
			//IL_00ed: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f2: 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)
			Styles();
			GUI.depth = -1000;
			if (open)
			{
				if (!placed)
				{
					placed = true;
					((Rect)(ref win)).width = Mathf.Min(((Rect)(ref win)).width, (float)Screen.width - 20f);
					((Rect)(ref win)).height = Mathf.Min(((Rect)(ref win)).height, (float)Screen.height - 20f);
					((Rect)(ref win)).x = ((float)Screen.width - ((Rect)(ref win)).width) / 2f;
					((Rect)(ref win)).y = ((float)Screen.height - ((Rect)(ref win)).height) / 2f;
				}
				win = GUI.Window(1128749, win, new WindowFunction(DrawWindow), "CityBuilder Beta Tracker 1.3.6  ·  mod " + Features.Version, sWin);
				Block(win);
			}
			else if (popups.Count > 0 && mode != null && cfgPopups.Value)
			{
				DrawPopup();
			}
		}

		private static void Block(Rect r)
		{
			//IL_0021: Unknown result type (might be due to invalid IL or missing references)
			if (!(fiOverlayRect == null) && !(fiOverlayFrame == null))
			{
				fiOverlayRect.SetValue(null, r);
				fiOverlayFrame.SetValue(null, Time.frameCount);
			}
		}

		private static Info FeatureInfo(string id)
		{
			return ((IEnumerable<Info>)Features.All.features).FirstOrDefault((Func<Info, bool>)((Info f) => f.id == id));
		}

		private unsafe static string KeyHint(Key k)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			if ((int)k != 0)
			{
				return "  (" + ((object)(*(Key*)(&k))/*cast due to .constrained prefix*/).ToString() + ")";
			}
			return "";
		}

		private void DrawPopup()
		{
			//IL_0058: Unknown result type (might be due to invalid IL or missing references)
			//IL_005e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0113: Unknown result type (might be due to invalid IL or missing references)
			//IL_0158: Unknown result type (might be due to invalid IL or missing references)
			//IL_0253: Unknown result type (might be due to invalid IL or missing references)
			//IL_0278: Unknown result type (might be due to invalid IL or missing references)
			//IL_02aa: Unknown result type (might be due to invalid IL or missing references)
			//IL_029e: Unknown result type (might be due to invalid IL or missing references)
			//IL_02a4: Invalid comparison between Unknown and I4
			Info val = FeatureInfo(popups[0]);
			if (val == null)
			{
				popAction = "later";
				return;
			}
			float num = Mathf.Min(640f, (float)Screen.width - 40f);
			Rect val2 = default(Rect);
			((Rect)(ref val2))..ctor(((float)Screen.width - num) / 2f, 90f, num, 122f);
			Block(val2);
			GUILayout.BeginArea(val2, sBox);
			GUILayout.Label("Tested: <b>" + val.title_en + "</b>" + ((popups.Count > 1) ? ("  <color=#b3b0a3>(" + (popups.Count - 1) + " more)</color>") : ""), sHead, Array.Empty<GUILayoutOption>());
			GUILayout.Label(val.desc_en + "  <color=#8d8a80>·  " + ModeText(mode) + "</color>", sDim, Array.Empty<GUILayoutOption>());
			GUILayout.FlexibleSpace();
			GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
			if (GUILayout.Button("Works" + KeyHint(cfgWorksKey.Value), sBtn, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(170f) }))
			{
				popAction = "works";
			}
			if (GUILayout.Button("Problem" + KeyHint(cfgProblemKey.Value), sBtn, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(190f) }))
			{
				popAction = "problem";
			}
			if (GUILayout.Button("Later", sBtn, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(80f) }))
			{
				popAction = "later";
			}
			GUILayout.FlexibleSpace();
			if (popups.Count > 1 && GUILayout.Button("Skip all", sBtn, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(90f) }))
			{
				popAction = "skip";
			}
			GUILayout.EndHorizontal();
			GUILayout.EndArea();
			float num2 = Mathf.Clamp01(popT / Mathf.Max(5f, cfgPopupSeconds.Value));
			GUI.DrawTexture(new Rect(((Rect)(ref val2)).x, ((Rect)(ref val2)).yMax - 3f, ((Rect)(ref val2)).width * num2, 3f), (Texture)(object)Texture2D.whiteTexture, (ScaleMode)0, true, 0f, new Color(0.98f, 0.83f, 0.48f, 0.8f), 0f, 0f);
			Event current = Event.current;
			if ((current.isMouse || (int)current.type == 6) && ((Rect)(ref val2)).Contains(current.mousePosition))
			{
				current.Use();
			}
		}

		private bool Used(string id, params string[] modes)
		{
			string ver = Features.Version;
			return modes.Any((string m) => state.seen.Contains(id + "|" + m + "|" + ver));
		}

		private string Badge(string label, string id, params string[] modes)
		{
			if (!Used(id, modes))
			{
				return "<color=#8d8a80>" + label + "</color>";
			}
			return "<color=#7ddc6a><b>" + label + "</b></color>";
		}

		private void NoteRow(Info feature)
		{
			GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
			GUILayout.Label((noteVerdict > 0) ? "Works – note (optional):" : "What went wrong?", sText, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(200f) });
			noteText = GUILayout.TextField(noteText ?? "", 300, sField, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(470f) });
			if (GUILayout.Button("Send", sBtn, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(70f) }) && mode != null)
			{
				Add(feature.id, mode, 0, noteVerdict, noteText);
				Save();
				sendT = Mathf.Min(sendT, 2f);
				noteFor = null;
				if (focusId == feature.id)
				{
					focusId = null;
					open = false;
				}
			}
			if (GUILayout.Button("×", sBtn, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(30f) }))
			{
				noteFor = null;
				focusId = null;
			}
			GUILayout.EndHorizontal();
		}

		private void DrawWindow(int id)
		{
			//IL_02ea: Unknown result type (might be due to invalid IL or missing references)
			//IL_02f4: Unknown result type (might be due to invalid IL or missing references)
			//IL_02f9: Unknown result type (might be due to invalid IL or missing references)
			//IL_066a: Unknown result type (might be due to invalid IL or missing references)
			//IL_066f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0487: Unknown result type (might be due to invalid IL or missing references)
			//IL_04aa: Expected O, but got Unknown
			GUILayout.Label("Tester: <b>" + (Named ? cfgName.Value.Trim() : "–") + "</b>   ·   Mode: <b>" + ModeText(mode) + "</b>   ·   " + state.queue.Count + " waiting, " + lastResult, sText, Array.Empty<GUILayoutOption>());
			if (!Named || string.IsNullOrEmpty(cfgCode.Value))
			{
				GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
				GUILayout.Label(Named ? "Registering soon – or change the name:" : "Pick a tester name to take part:", sText, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(300f) });
				nameField = GUILayout.TextField(nameField ?? "", 32, sField, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(220f) });
				if (GUILayout.Button("Save name", sBtn, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(110f) }) && nameField.Trim().Length >= 2)
				{
					cfgName.Value = nameField.Trim();
					cfgCode.Value = "";
					((BaseUnityPlugin)this).Config.Save();
					sendT = 1f;
				}
				GUILayout.EndHorizontal();
			}
			Info val = ((focusId != null) ? FeatureInfo(focusId) : null);
			if (val != null)
			{
				GUILayout.BeginVertical(sBox, Array.Empty<GUILayoutOption>());
				GUILayout.Label("Problem with: <b>" + val.title_en + "</b>  <color=#8d8a80>(" + ModeText(mode) + ")</color>", sHead, Array.Empty<GUILayoutOption>());
				GUILayout.Label(val.desc_en, sDim, Array.Empty<GUILayoutOption>());
				NoteRow(val);
				GUILayout.EndVertical();
				GUILayout.Space(6f);
			}
			List<string> list = new List<string> { "All" };
			list.AddRange(Features.All.features.Select((Info f) => f.area).Distinct());
			area = GUILayout.SelectionGrid(Mathf.Clamp(area, 0, list.Count - 1), list.ToArray(), 5, sTab, Array.Empty<GUILayoutOption>());
			GUILayout.Space(4f);
			GUILayout.Label("Green = you used it in this mode with this version. Works / Problem: your verdict for " + ModeText(mode) + " (kept until the feature changes).", sDim, Array.Empty<GUILayoutOption>());
			scroll = GUILayout.BeginScrollView(scroll, Array.Empty<GUILayoutOption>());
			int num = 0;
			foreach (Info feature in Features.All.features)
			{
				if (area <= 0 || !(feature.area != list[area]))
				{
					string text = ((mode != null) ? RatingOf(feature.id, mode) : null);
					GUILayout.BeginHorizontal((num++ % 2 == 0) ? sRow : sRowAlt, Array.Empty<GUILayoutOption>());
					GUILayout.Label(Badge("SP", feature.id, "sp") + "  " + Badge("MP", feature.id, "mp_host", "mp_client") + "  " + Badge("DS", feature.id, "dedicated"), sText, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(100f) });
					GUILayout.Label(new GUIContent(feature.title_en + ((feature.kind == "fix") ? "  <color=#8d8a80>(fix)</color>" : "") + ((text == null) ? "" : (text.EndsWith("|1") ? "  <color=#7ddc6a>works</color>" : "  <color=#ff7a62>problem</color>")), feature.desc_en), sText, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(530f) });
					GUI.enabled = mode != null;
					if (GUILayout.Button("Works", sBtn, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(80f) }))
					{
						noteFor = feature.id;
						noteVerdict = 1;
						noteText = "";
					}
					if (GUILayout.Button("Problem", sBtn, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(90f) }))
					{
						noteFor = feature.id;
						noteVerdict = -1;
						noteText = "";
					}
					GUI.enabled = true;
					GUILayout.EndHorizontal();
					if (noteFor == feature.id && focusId != feature.id)
					{
						NoteRow(feature);
					}
				}
			}
			GUILayout.EndScrollView();
			GUILayout.BeginVertical(sBox, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(44f) });
			GUILayout.Label(string.IsNullOrEmpty(GUI.tooltip) ? "Point at a feature to read what it does." : GUI.tooltip, sDim, Array.Empty<GUILayoutOption>());
			GUILayout.EndVertical();
			GUILayout.Space(4f);
			GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
			if (GUILayout.Button("Open website", sBtn, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(140f) }))
			{
				Application.OpenURL("https://valheimcitybuilder.com/beta");
			}
			if (GUILayout.Button("Send now", sBtn, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(110f) }))
			{
				sendT = 0f;
			}
			GUILayout.FlexibleSpace();
			if (GUILayout.Button("Close (" + ((object)cfgKey.Value/*cast due to .constrained prefix*/).ToString() + ")", sBtn, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(130f) }))
			{
				open = false;
				focusId = null;
			}
			GUILayout.EndHorizontal();
			GUI.DragWindow();
		}
	}
}