Decompiled source of Travs Valheim Server NPCs and Marketplace v1.3.2

plugins/TravsServerNPCs/TravsServerNPCs.dll

Decompiled a day ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
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 System.Text.RegularExpressions;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Logging;
using HarmonyLib;
using Jotunn.Configs;
using Jotunn.Entities;
using Jotunn.Managers;
using Jotunn.Utils;
using Microsoft.CodeAnalysis;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Splatform;
using TravsServerNPCs.Client;
using TravsServerNPCs.Commands;
using TravsServerNPCs.Net;
using TravsServerNPCs.Npc;
using TravsServerNPCs.Server;
using TravsServerNPCs.Server.Services;
using TravsServerNPCs.Shared;
using TravsServerNPCs.UI;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.Networking;
using UnityEngine.UI;
using YamlDotNet.Serialization;
using YamlDotNet.Serialization.NamingConventions;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: IgnoresAccessChecksTo("assembly_guiutils")]
[assembly: IgnoresAccessChecksTo("assembly_utils")]
[assembly: IgnoresAccessChecksTo("assembly_valheim")]
[assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")]
[assembly: AssemblyCompany("TravsServerNPCs")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.3.2.0")]
[assembly: AssemblyInformationalVersion("1.3.2+068d7908c6715039c3d257402de4657d1eaac834")]
[assembly: AssemblyProduct("TravsServerNPCs")]
[assembly: AssemblyTitle("TravsServerNPCs")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.3.2.0")]
[module: UnverifiableCode]
[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 TravsServerNPCs
{
	internal static class Log
	{
		private static ManualLogSource _log;

		public static void Init(ManualLogSource log)
		{
			_log = log;
		}

		public static void Info(string msg)
		{
			ManualLogSource log = _log;
			if (log != null)
			{
				log.LogInfo((object)msg);
			}
		}

		public static void Warn(string msg)
		{
			ManualLogSource log = _log;
			if (log != null)
			{
				log.LogWarning((object)msg);
			}
		}

		public static void Error(string msg)
		{
			ManualLogSource log = _log;
			if (log != null)
			{
				log.LogError((object)msg);
			}
		}

		public static void Debug(string msg)
		{
			ManualLogSource log = _log;
			if (log != null)
			{
				log.LogDebug((object)msg);
			}
		}
	}
	[BepInPlugin("trav.travsservernpcs", "Trav's Valheim Server NPCs and Marketplace Mod", "1.3.2")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[NetworkCompatibility(/*Could not decode attribute arguments.*/)]
	public class Plugin : BaseUnityPlugin
	{
		public const string Guid = "trav.travsservernpcs";

		public const string Name = "Trav's Valheim Server NPCs and Marketplace Mod";

		public const string Version = "1.3.2";

		public const string FolderName = "TravsServerNPCs";

		private Harmony _harmony;

		public static Plugin Instance { get; private set; }

		private void Awake()
		{
			//IL_0030: Unknown result type (might be due to invalid IL or missing references)
			//IL_003a: Expected O, but got Unknown
			Instance = this;
			Log.Init(((BaseUnityPlugin)this).Logger);
			NetChannel.Register();
			NpcPrefabs.Register();
			CommandManager.Instance.AddConsoleCommand((ConsoleCommand)(object)new TsnCommand());
			_harmony = new Harmony("trav.travsservernpcs");
			_harmony.PatchAll(typeof(Plugin).Assembly);
			Log.Info("Trav's Valheim Server NPCs and Marketplace Mod 1.3.2 loaded");
		}

		private void Update()
		{
			ServerHost.Tick();
			ClientState.Tick();
		}

		private void OnDestroy()
		{
			ServerHost.Shutdown();
			Harmony harmony = _harmony;
			if (harmony != null)
			{
				harmony.UnpatchSelf();
			}
		}
	}
}
namespace TravsServerNPCs.UI
{
	internal static class ChroniclerView
	{
		private static WritStateView _state;

		public static void OnOpen()
		{
			_state = null;
			ClientState.Send("writ.state", null, delegate(Response r)
			{
				if (r.State != null)
				{
					_state = ((JToken)r.State).ToObject<WritStateView>();
				}
				if (!string.IsNullOrEmpty(r.Message))
				{
					ClientState.Notify(r.Message);
				}
				NpcPanel.Refresh();
			});
		}

		public static string[] Tabs()
		{
			return new string[2] { "Boss Writs", "Chronicle" };
		}

		public static void Rows(int tab, List<Row> rows, Action<int, Action<int, bool>> pay)
		{
			if (_state == null)
			{
				rows.Add(new Row
				{
					Label = "Consulting the chronicle..."
				});
			}
			else if (tab == 0)
			{
				Writs(rows, pay);
			}
			else
			{
				Chronicle(rows);
			}
		}

		private static void Writs(List<Row> rows, Action<int, Action<int, bool>> pay)
		{
			float num = ClientState.Config?.Settings.Writs.DaysAfterFirstKill ?? 3f;
			rows.Add(new Row
			{
				Header = true,
				Label = $"Missed a boss night? A writ counts the boss as defeated for your character, {num:0.#} days after the world first beat it."
			});
			if (!_state.YnwInstalled)
			{
				rows.Add(new Row
				{
					Label = "   (You Are Not Worthy isn't running, so keys are recorded but don't gate items.)"
				});
			}
			foreach (BossView boss2 in _state.Bosses)
			{
				BossView boss = boss2;
				bool flag = (Object)(object)Player.m_localPlayer != (Object)null && ((Humanoid)Player.m_localPlayer).HaveUniqueKey(boss2.Key);
				bool flag2 = boss2.HaveKey || flag;
				string text = boss2.Price + " coins" + ((boss2.ItemAmount > 0 && !string.IsNullOrEmpty(boss2.Item)) ? $" + {boss2.ItemAmount}x {InventoryHelper.DisplayName(boss2.Item)}" : "");
				bool flag3 = !flag2 && string.IsNullOrEmpty(boss2.Blocked);
				rows.Add(new Row
				{
					Icon = (string.IsNullOrEmpty(boss2.Item) ? null : InventoryHelper.Icon(boss2.Item)),
					Label = (flag2 ? (boss2.Name + "  ✓ defeated") : (boss2.Name + " — " + text + (flag3 ? "" : ("   (" + boss2.Blocked + ")")))),
					ButtonText = (flag3 ? "Buy writ" : null),
					OnClick = (flag3 ? ((Action<int>)delegate
					{
						Buy(boss, pay);
					}) : null)
				});
			}
		}

		private static void Buy(BossView boss, Action<int, Action<int, bool>> pay)
		{
			List<ItemRecord> given = new List<ItemRecord>();
			if (boss.ItemAmount > 0 && !string.IsNullOrEmpty(boss.Item))
			{
				if (InventoryHelper.Count(boss.Item) < boss.ItemAmount)
				{
					ClientState.Notify($"You also need {boss.ItemAmount}x {InventoryHelper.DisplayName(boss.Item)}.");
					return;
				}
				int num = InventoryHelper.Take(boss.Item, boss.ItemAmount);
				if (num > 0)
				{
					given.Add(ItemRecord.Plain(boss.Item, num));
				}
			}
			pay(boss.Price, delegate(int paid, bool bank)
			{
				ClientState.Send("writ.buy", new WritBuyRequest
				{
					Key = boss.Key,
					PaidCoins = paid,
					FromBank = bank,
					Given = given
				}, delegate(Response r)
				{
					ClientState.Notify(r.Message);
					OnOpen();
				});
			});
		}

		private static void Chronicle(List<Row> rows)
		{
			rows.Add(new Row
			{
				Header = true,
				Label = "This world's boss history, and where you stand."
			});
			foreach (BossView boss in _state.Bosses)
			{
				bool flag = (Object)(object)Player.m_localPlayer != (Object)null && ((Humanoid)Player.m_localPlayer).HaveUniqueKey(boss.Key);
				string text = ((boss.HaveKey || flag) ? "✓ you" : "✗ you");
				string text2 = (boss.FirstKillUtc.HasValue ? ($"first defeated {boss.FirstKillUtc.Value.ToLocalTime():d MMM yyyy}" + ((boss.FirstPlayers.Count > 0) ? (" by " + string.Join(", ", boss.FirstPlayers.Take(4))) : "")) : "not yet defeated on this world");
				rows.Add(new Row
				{
					Label = boss.Name + "   " + text + "   ·   " + text2
				});
			}
		}
	}
	internal static class ConfigEditor
	{
		private const float Width = 900f;

		private const float Height = 720f;

		private static GameObject _window;

		private static NpcProfile _profile;

		private static List<TradeEntry> _stock;

		private static List<Destination> _dests;

		private static readonly List<Dictionary<string, InputField>> Rows = new List<Dictionary<string, InputField>>();

		private static int _closedFrame = -1;

		public static bool IsOpen => (Object)(object)_window != (Object)null;

		public static bool ClosedThisFrame => _closedFrame == Time.frameCount;

		public static void Open(NpcProfile profile)
		{
			if (!((Object)(object)GUIManager.CustomGUIFront == (Object)null) && profile != null)
			{
				NpcProfile npcProfile = ClientState.Config?.GetProfile(profile.Id);
				if (npcProfile == null)
				{
					ClientState.Notify("Profile '" + profile.Id + "' isn't a config file on the server, so there's nothing to edit.");
					return;
				}
				NpcPanel.Close();
				NpcEditor.Close();
				Close();
				_profile = npcProfile;
				_stock = npcProfile.Stock.Select(Copy).ToList();
				_dests = npcProfile.Destinations.Select(Copy).ToList();
				GUIManager.BlockInput(true);
				Build();
			}
		}

		public static void Close()
		{
			if (!((Object)(object)_window == (Object)null))
			{
				Object.Destroy((Object)(object)_window);
				_window = null;
				Rows.Clear();
				_closedFrame = Time.frameCount;
				GUIManager.BlockInput(false);
			}
		}

		public static void Tick()
		{
			if (IsOpen && ((Object)(object)Player.m_localPlayer == (Object)null || ZInput.GetKeyDown((KeyCode)27, true)))
			{
				Close();
			}
		}

		private static void Build()
		{
			//IL_00a8: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)_window != (Object)null)
			{
				Object.Destroy((Object)(object)_window);
			}
			Rows.Clear();
			_window = PanelBuilder.Window(900f, 720f);
			((Object)_window).name = "TSN_ConfigEditor";
			Transform transform = _window.transform;
			bool flag = _profile.Role == "trader";
			PanelBuilder.Label(transform, "Edit " + (flag ? "stock" : "destinations") + ": " + _profile.Id, 20f, 18f, 780f, 40f, 26, GUIManager.Instance.ValheimOrange, (TextAnchor)4);
			PanelBuilder.Button(transform, "X", 830f, 18f, 46f, 38f, Close);
			float contentWidth;
			RectTransform content = PanelBuilder.ScrollList(transform, 20f, 66f, 860f, 584f, out contentWidth);
			if (flag)
			{
				PanelBuilder.AddRow(content, contentWidth, new Row
				{
					Header = true,
					Label = "item | amount | sell price | buy price | stock (-1 = unlimited) | restock h | requires key | daily limit"
				});
				for (int i = 0; i < _stock.Count; i++)
				{
					TradeEntry tradeEntry = _stock[i];
					int index = i;
					Dictionary<string, InputField> item = FieldRow(content, contentWidth, new string[8]
					{
						tradeEntry.Item,
						tradeEntry.Amount.ToString(),
						tradeEntry.Sell.ToString(),
						tradeEntry.Buy.ToString(),
						tradeEntry.Stock.ToString(),
						F(tradeEntry.RestockHours),
						tradeEntry.Requires,
						tradeEntry.DailyLimit.ToString()
					}, delegate
					{
						Collect();
						_stock.RemoveAt(index);
						Build();
					});
					Rows.Add(item);
				}
				PanelBuilder.AddRow(content, contentWidth, new Row
				{
					Label = "Add a stock line",
					ButtonText = "Add",
					OnClick = delegate
					{
						Collect();
						_stock.Add(new TradeEntry
						{
							Item = "Wood",
							Amount = 10,
							Sell = 10
						});
						Build();
					}
				});
			}
			else
			{
				PanelBuilder.AddRow(content, contentWidth, new Row
				{
					Header = true,
					Label = "name | x | y | z | cost | requires key | cooldown s | allow all items (true/false)"
				});
				for (int num = 0; num < _dests.Count; num++)
				{
					Destination destination = _dests[num];
					int index2 = num;
					Dictionary<string, InputField> item2 = FieldRow(content, contentWidth, new string[8]
					{
						destination.Name,
						F(destination.X),
						F(destination.Y),
						F(destination.Z),
						destination.Cost.ToString(),
						destination.Requires,
						F(destination.CooldownSeconds),
						destination.AllowAllItems ? "true" : "false"
					}, delegate
					{
						Collect();
						_dests.RemoveAt(index2);
						Build();
					});
					Rows.Add(item2);
				}
				PanelBuilder.AddRow(content, contentWidth, new Row
				{
					Label = "Add a destination at my position",
					ButtonText = "Add here",
					OnClick = delegate
					{
						//IL_000f: 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_002b: Unknown result type (might be due to invalid IL or missing references)
						//IL_003c: Unknown result type (might be due to invalid IL or missing references)
						//IL_004d: Unknown result type (might be due to invalid IL or missing references)
						Collect();
						Vector3 position = ((Component)Player.m_localPlayer).transform.position;
						_dests.Add(new Destination
						{
							Name = "New place",
							X = Mathf.Round(position.x),
							Y = Mathf.Round(position.y),
							Z = Mathf.Round(position.z)
						});
						Build();
					}
				});
			}
			PanelBuilder.Button(transform, "Save to server", 20f, 658f, 200f, 40f, Save);
			PanelBuilder.Button(transform, "Cancel", 230f, 658f, 120f, 40f, Close);
		}

		private static Dictionary<string, InputField> FieldRow(RectTransform content, float width, string[] values, Action remove)
		{
			//IL_003f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0045: Expected O, but got Unknown
			//IL_0064: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a3: Unknown result type (might be due to invalid IL or missing references)
			//IL_01bf: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ce: Unknown result type (might be due to invalid IL or missing references)
			//IL_01dd: Unknown result type (might be due to invalid IL or missing references)
			//IL_0202: Unknown result type (might be due to invalid IL or missing references)
			//IL_020c: Expected O, but got Unknown
			//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_014c: Unknown result type (might be due to invalid IL or missing references)
			GameObject val = new GameObject("Row", new Type[3]
			{
				typeof(RectTransform),
				typeof(LayoutElement),
				typeof(Image)
			});
			val.transform.SetParent((Transform)(object)content, false);
			val.GetComponent<RectTransform>().sizeDelta = new Vector2(width - 8f, 40f);
			LayoutElement component = val.GetComponent<LayoutElement>();
			float minHeight = (component.preferredHeight = 40f);
			component.minHeight = minHeight;
			((Graphic)val.GetComponent<Image>()).color = new Color(0f, 0f, 0f, 0.25f);
			Dictionary<string, InputField> dictionary = new Dictionary<string, InputField>();
			float num2 = (width - 8f - 90f) / (float)values.Length;
			for (int i = 0; i < values.Length; i++)
			{
				float num3 = ((i == 0) ? (num2 * 1.6f) : (num2 * ((float)values.Length - 1.6f) / (float)(values.Length - 1)));
				float num4 = ((i == 0) ? 4f : (4f + num2 * 1.6f + (float)(i - 1) * num3));
				InputField component2 = GUIManager.Instance.CreateInputField(val.transform, new Vector2(0f, 0.5f), new Vector2(0f, 0.5f), new Vector2(num4 + num3 / 2f, 0f), (ContentType)0, "", 13, num3 - 4f, 30f).GetComponent<InputField>();
				component2.text = values[i] ?? "";
				dictionary[i.ToString()] = component2;
			}
			((UnityEvent)GUIManager.Instance.CreateButton("Remove", val.transform, new Vector2(1f, 0.5f), new Vector2(1f, 0.5f), new Vector2(-46f, 0f), 80f, 30f).GetComponent<Button>().onClick).AddListener((UnityAction)delegate
			{
				remove();
			});
			return dictionary;
		}

		private static string V(Dictionary<string, InputField> r, int i)
		{
			if (!r.TryGetValue(i.ToString(), out var value))
			{
				return "";
			}
			return (value.text ?? "").Trim();
		}

		private static float N(Dictionary<string, InputField> r, int i, float d)
		{
			if (!float.TryParse(V(r, i), NumberStyles.Float, CultureInfo.InvariantCulture, out var result))
			{
				return d;
			}
			return result;
		}

		private static string F(float v)
		{
			return v.ToString("0.##", CultureInfo.InvariantCulture);
		}

		private static void Collect()
		{
			if (_profile.Role == "trader")
			{
				for (int i = 0; i < Rows.Count && i < _stock.Count; i++)
				{
					Dictionary<string, InputField> r = Rows[i];
					_stock[i] = new TradeEntry
					{
						Item = V(r, 0),
						Amount = (int)N(r, 1, 1f),
						Sell = (int)N(r, 2, 0f),
						Buy = (int)N(r, 3, 0f),
						Stock = (int)N(r, 4, -1f),
						RestockHours = N(r, 5, 24f),
						Requires = V(r, 6),
						DailyLimit = (int)N(r, 7, 0f)
					};
				}
			}
			else
			{
				for (int j = 0; j < Rows.Count && j < _dests.Count; j++)
				{
					Dictionary<string, InputField> r2 = Rows[j];
					_dests[j] = new Destination
					{
						Name = V(r2, 0),
						X = N(r2, 1, 0f),
						Y = N(r2, 2, 0f),
						Z = N(r2, 3, 0f),
						Cost = (int)N(r2, 4, 0f),
						Requires = V(r2, 5),
						CooldownSeconds = N(r2, 6, 0f),
						AllowAllItems = V(r2, 7).Equals("true", StringComparison.OrdinalIgnoreCase)
					};
				}
			}
		}

		private static void Save()
		{
			Collect();
			if (_stock.Where((TradeEntry e) => string.IsNullOrEmpty(e.Item) || e.Amount < 1).ToList().Count > 0)
			{
				ClientState.Notify("Every stock line needs an item and an amount of 1 or more.");
				return;
			}
			if (_dests.Any((Destination d) => string.IsNullOrEmpty(d.Name)))
			{
				ClientState.Notify("Every destination needs a name.");
				return;
			}
			ClientState.Send("admin.saveProfile", new AdminSaveProfileRequest
			{
				ProfileId = _profile.Id,
				Stock = _stock,
				Destinations = _dests
			}, delegate(Response r)
			{
				ClientState.Notify(r.Message);
				if (r.Ok)
				{
					Close();
				}
			});
		}

		private static TradeEntry Copy(TradeEntry e)
		{
			return new TradeEntry
			{
				Item = e.Item,
				Amount = e.Amount,
				Sell = e.Sell,
				Buy = e.Buy,
				Stock = e.Stock,
				RestockHours = e.RestockHours,
				Requires = e.Requires,
				DailyLimit = e.DailyLimit
			};
		}

		private static Destination Copy(Destination d)
		{
			return new Destination
			{
				Name = d.Name,
				X = d.X,
				Y = d.Y,
				Z = d.Z,
				Cost = d.Cost,
				Requires = d.Requires,
				CooldownSeconds = d.CooldownSeconds,
				AllowAllItems = d.AllowAllItems
			};
		}
	}
	internal static class DialoguePanel
	{
		private const float Width = 720f;

		private const float Height = 520f;

		private const float MaxDistance = 8f;

		private static GameObject _window;

		private static NpcComponent _npc;

		private static NpcProfile _profile;

		private static string _nodeId;

		private static int _closedFrame = -1;

		public static bool IsOpen => (Object)(object)_window != (Object)null;

		public static bool ClosedThisFrame => _closedFrame == Time.frameCount;

		public static void Open(NpcComponent npc, NpcProfile profile)
		{
			if (!((Object)(object)GUIManager.CustomGUIFront == (Object)null) && profile?.Dialogue != null)
			{
				NpcPanel.Close();
				Close();
				_npc = npc;
				_profile = profile;
				GUIManager.BlockInput(true);
				Show(profile.Dialogue.Start);
			}
		}

		public static void Close()
		{
			if (!((Object)(object)_window == (Object)null))
			{
				Object.Destroy((Object)(object)_window);
				_window = null;
				_closedFrame = Time.frameCount;
				GUIManager.BlockInput(false);
			}
		}

		public static void OpenShop()
		{
			NpcComponent npc = _npc;
			NpcProfile npcProfile = (((Object)(object)npc != (Object)null) ? npc.Profile : null);
			Close();
			if (npcProfile != null && npcProfile.Role != "decoration")
			{
				NpcPanel.Open(npc, npcProfile);
			}
		}

		public static void Tick()
		{
			//IL_0032: Unknown result type (might be due to invalid IL or missing references)
			//IL_0041: Unknown result type (might be due to invalid IL or missing references)
			if (IsOpen)
			{
				Player localPlayer = Player.m_localPlayer;
				if ((Object)(object)localPlayer == (Object)null || ((Character)localPlayer).IsDead() || (Object)(object)_npc == (Object)null || Vector3.Distance(((Component)localPlayer).transform.position, ((Component)_npc).transform.position) > 8f || ZInput.GetKeyDown((KeyCode)27, true))
				{
					Close();
				}
			}
		}

		private static void Show(string nodeId)
		{
			//IL_00a3: Unknown result type (might be due to invalid IL or missing references)
			//IL_010c: Unknown result type (might be due to invalid IL or missing references)
			DialogueNode dialogueNode = _profile.Dialogue.Node(nodeId);
			if (dialogueNode == null)
			{
				Close();
				return;
			}
			_nodeId = nodeId;
			if ((Object)(object)_window != (Object)null)
			{
				Object.Destroy((Object)(object)_window);
			}
			_window = PanelBuilder.Window(720f, 520f);
			((Object)_window).name = "TSN_Dialogue";
			Transform transform = _window.transform;
			PanelBuilder.Label(transform, ((Object)(object)_npc != (Object)null) ? _npc.DisplayName : _profile.Name, 20f, 18f, 600f, 40f, 26, GUIManager.Instance.ValheimOrange, (TextAnchor)4);
			PanelBuilder.Button(transform, "X", 650f, 18f, 46f, 38f, Close);
			PanelBuilder.Label(transform, dialogueNode.Text, 30f, 64f, 660f, 150f, 18, GUIManager.Instance.ValheimBeige, (TextAnchor)0);
			float contentWidth;
			RectTransform content = PanelBuilder.ScrollList(transform, 20f, 224f, 680f, 276f, out contentWidth);
			ConditionContext c = QuestClient.Conditions();
			int num = 0;
			for (int i = 0; i < dialogueNode.Choices.Count; i++)
			{
				DialogueChoice choice = dialogueNode.Choices[i];
				if (Conditions.Check(choice.Requires, c))
				{
					int index = i;
					num++;
					PanelBuilder.AddRow(content, contentWidth, new Row
					{
						Label = $"{num}. {choice.Text}",
						ButtonText = "Say",
						OnClick = delegate
						{
							Pick(index, choice);
						}
					});
				}
			}
			if (num == 0)
			{
				PanelBuilder.AddRow(content, contentWidth, new Row
				{
					Label = "...",
					ButtonText = "Leave",
					OnClick = delegate
					{
						Close();
					}
				});
			}
		}

		private static void Pick(int index, DialogueChoice choice)
		{
			if (!choice.Actions.Any((Dictionary<string, object> action) => !ActionRunnerNames.IsClientOnly(Arg.Name(action))))
			{
				foreach (Dictionary<string, object> action in choice.Actions)
				{
					if (Arg.Name(action).Equals("openShop", StringComparison.OrdinalIgnoreCase))
					{
						OpenShop();
						return;
					}
				}
				Advance(choice.Next);
				return;
			}
			List<ItemRecord> list = new List<ItemRecord>();
			foreach (Dictionary<string, object> a in choice.Actions)
			{
				if (Arg.Name(a).Equals("completeQuest", StringComparison.OrdinalIgnoreCase))
				{
					ActiveQuestView activeQuestView = QuestClient.State.Active.FirstOrDefault((ActiveQuestView x) => string.Equals(x.Id, Arg.Scalar(Arg.Value(a)), StringComparison.OrdinalIgnoreCase));
					if (activeQuestView != null)
					{
						list.AddRange(QuestClient.CollectFor(activeQuestView));
					}
				}
			}
			string nodeId = _nodeId;
			ClientState.Send("dialogue.choose", new DialogueChooseRequest
			{
				ProfileId = _profile.Id,
				Node = nodeId,
				Choice = index,
				Given = list
			}, delegate(Response r)
			{
				if (!string.IsNullOrEmpty(r.Message))
				{
					ClientState.Notify(r.Message);
				}
				if (IsOpen)
				{
					if (!r.Ok)
					{
						Show(_nodeId);
					}
					else if (!r.Actions.Any((ClientAction clientAction) => clientAction.Kind == "openShop"))
					{
						Advance(string.IsNullOrEmpty(r.Next) ? choice.Next : r.Next);
					}
				}
			});
		}

		private static void Advance(string next)
		{
			if (string.IsNullOrEmpty(next))
			{
				Close();
			}
			else
			{
				Show(next);
			}
		}
	}
	internal static class ActionRunnerNames
	{
		public static bool IsClientOnly(string name)
		{
			return string.Equals(name, "openShop", StringComparison.OrdinalIgnoreCase);
		}
	}
	internal static class NpcEditor
	{
		private const float Width = 840f;

		private const float Height = 720f;

		private static GameObject _window;

		private static NpcComponent _npc;

		private static readonly Dictionary<string, InputField> Fields = new Dictionary<string, InputField>();

		private static string _role;

		private static string _pin;

		private static int _closedFrame = -1;

		public static bool IsOpen => (Object)(object)_window != (Object)null;

		public static bool ClosedThisFrame => _closedFrame == Time.frameCount;

		public static void Open(NpcComponent npc)
		{
			if (!((Object)(object)GUIManager.CustomGUIFront == (Object)null) && !((Object)(object)npc == (Object)null))
			{
				NpcPanel.Close();
				Close();
				_npc = npc;
				NpcProfile obj = npc.Profile ?? new NpcProfile
				{
					Id = npc.ProfileId
				};
				obj.FillDefaults();
				_role = obj.Role;
				_pin = obj.Behaviour.MapPin;
				Build(obj);
				GUIManager.BlockInput(true);
			}
		}

		public static void Close()
		{
			if (!((Object)(object)_window == (Object)null))
			{
				Object.Destroy((Object)(object)_window);
				_window = null;
				_npc = null;
				Fields.Clear();
				_closedFrame = Time.frameCount;
				GUIManager.BlockInput(false);
			}
		}

		public static void Tick()
		{
			if (IsOpen)
			{
				Player localPlayer = Player.m_localPlayer;
				if ((Object)(object)localPlayer == (Object)null || ((Character)localPlayer).IsDead() || (Object)(object)_npc == (Object)null || ZInput.GetKeyDown((KeyCode)27, true))
				{
					Close();
				}
			}
		}

		private static void Build(NpcProfile eff)
		{
			//IL_004f: Unknown result type (might be due to invalid IL or missing references)
			_window = PanelBuilder.Window(840f, 720f);
			((Object)_window).name = "TSN_Editor";
			Transform transform = _window.transform;
			PanelBuilder.Label(transform, "Edit NPC", 20f, 18f, 720f, 40f, 28, GUIManager.Instance.ValheimOrange, (TextAnchor)4);
			PanelBuilder.Button(transform, "X", 770f, 18f, 46f, 38f, Close);
			float contentWidth;
			RectTransform content = PanelBuilder.ScrollList(transform, 20f, 66f, 800f, 584f, out contentWidth);
			Header(content, contentWidth, "Identity");
			Field(content, contentWidth, "profile", "Profile id (which config file)", eff.Id);
			Field(content, contentWidth, "name", "Name", eff.Name);
			PanelBuilder.AddRow(content, contentWidth, new Row
			{
				Label = "Role: " + _role,
				ButtonText = "Change",
				OnClick = delegate
				{
					_role = Next(NpcRoles.All, _role);
					Reopen();
				}
			});
			Field(content, contentWidth, "model", "Model (Haldor, Hildir, BogWitch, Player, PlayerFemale, Troll, ...)", eff.Model);
			Field(content, contentWidth, "greeting", "Greeting", eff.Greeting);
			Field(content, contentWidth, "goodbye", "Goodbye", eff.Behaviour.Goodbye);
			Header(content, contentWidth, "Look  (gear, hair and colours need the Player or PlayerFemale model)");
			Appearance look = eff.Look;
			Field(content, contentWidth, "helmet", "Helmet", look.Helmet);
			Field(content, contentWidth, "chest", "Chest", look.Chest);
			Field(content, contentWidth, "legs", "Legs", look.Legs);
			Field(content, contentWidth, "cape", "Cape", look.Cape);
			Field(content, contentWidth, "leftHand", "Left hand", look.LeftHand);
			Field(content, contentWidth, "rightHand", "Right hand", look.RightHand);
			Field(content, contentWidth, "leftBack", "Left back", look.LeftBack);
			Field(content, contentWidth, "rightBack", "Right back", look.RightBack);
			Field(content, contentWidth, "hair", "Hair (Hair1 .. Hair23)", look.Hair);
			Field(content, contentWidth, "beard", "Beard (Beard1 .. Beard21)", look.Beard);
			Field(content, contentWidth, "hairColor", "Hair colour (#hex)", look.HairColor);
			Field(content, contentWidth, "skinColor", "Skin colour (#hex)", look.SkinColor);
			Field(content, contentWidth, "scale", "Size (0.5 - 3)", F(look.Scale));
			Header(content, contentWidth, "Behaviour");
			NpcBehaviour behaviour = eff.Behaviour;
			Field(content, contentWidth, "greetRange", "Greet range (m)", F(behaviour.GreetRange));
			Field(content, contentWidth, "greetAnimation", "Greet animation (emote_wave ...)", behaviour.GreetAnimation);
			Field(content, contentWidth, "goodbyeAnimation", "Goodbye animation", behaviour.GoodbyeAnimation);
			Field(content, contentWidth, "interactAnimation", "Interact animation", behaviour.InteractAnimation);
			Field(content, contentWidth, "idleAnimation", "Idle animation", behaviour.IdleAnimation);
			Field(content, contentWidth, "idleEvery", "Idle animation every (s)", F(behaviour.IdleEverySeconds));
			Field(content, contentWidth, "interactSound", "Interact sound (sfx_...)", behaviour.InteractSound);
			Field(content, contentWidth, "idleSound", "Idle sound", behaviour.IdleSound);
			Field(content, contentWidth, "idleSoundEvery", "Idle sound every (s, 0 = off)", F(behaviour.IdleSoundEverySeconds));
			Field(content, contentWidth, "craftingPose", "Crafting pose (-1 none, 0-3)", behaviour.CraftingPose.ToString());
			PanelBuilder.AddRow(content, contentWidth, new Row
			{
				Label = "Map pin: " + _pin,
				ButtonText = "Change",
				OnClick = delegate
				{
					_pin = Next(MapPinModes.All, _pin);
					Reopen();
				}
			});
			Field(content, contentWidth, "patrol", "Patrol points  x,y,z ; x,y,z  (empty = stand still)", string.Join(" ; ", behaviour.Patrol.Points.Select((PatrolPoint p) => F(p.X) + "," + F(p.Y) + "," + F(p.Z))));
			Field(content, contentWidth, "patrolSpeed", "Patrol speed (m/s)", F(behaviour.Patrol.Speed));
			Field(content, contentWidth, "patrolPause", "Pause at each point (s)", F(behaviour.Patrol.PauseSeconds));
			Header(content, contentWidth, "Preset  (saves everything above as a new profile file on the server)");
			Field(content, contentWidth, "preset", "Preset id (letters, numbers, - and _)", "");
			PanelBuilder.AddRow(content, contentWidth, new Row
			{
				Label = "Save this NPC as a preset",
				ButtonText = "Save preset",
				OnClick = delegate
				{
					SavePreset();
				}
			});
			float y = 658f;
			PanelBuilder.Button(transform, "Apply", 20f, y, 150f, 40f, delegate
			{
				Send(move: false);
			});
			PanelBuilder.Button(transform, "Snap to ground + face me", 180f, y, 230f, 40f, delegate
			{
				Send(move: true);
			});
			PanelBuilder.Button(transform, "Use profile defaults", 420f, y, 200f, 40f, UseDefaults);
			PanelBuilder.Button(transform, "Remove NPC", 630f, y, 150f, 40f, Remove);
			if (eff.Role == "trader" || eff.Role == "teleporter")
			{
				PanelBuilder.Button(transform, (eff.Role == "trader") ? "Edit stock" : "Edit destinations", 610f, 18f, 150f, 38f, delegate
				{
					ConfigEditor.Open(_npc?.Profile);
				});
			}
			if (eff.Role == "quest")
			{
				PanelBuilder.Button(transform, "Quest Designer", 610f, 18f, 150f, 38f, delegate
				{
					QuestDesigner.Open();
				});
			}
		}

		private static void Reopen()
		{
			Dictionary<string, string> dictionary = Fields.ToDictionary((KeyValuePair<string, InputField> k) => k.Key, (KeyValuePair<string, InputField> k) => k.Value.text);
			NpcComponent npc = _npc;
			NpcProfile eff = Collect();
			Object.Destroy((Object)(object)_window);
			_window = null;
			Fields.Clear();
			_npc = npc;
			Build(eff);
			foreach (KeyValuePair<string, string> item in dictionary)
			{
				if (Fields.TryGetValue(item.Key, out var value))
				{
					value.text = item.Value;
				}
			}
		}

		private static void Header(RectTransform content, float w, string text)
		{
			PanelBuilder.AddRow(content, w, new Row
			{
				Header = true,
				Label = text
			});
		}

		private static void Field(RectTransform content, float w, string key, string label, string value)
		{
			Fields[key] = PanelBuilder.AddTextRow(content, w, label, value ?? "");
		}

		private static string V(string key)
		{
			if (!Fields.TryGetValue(key, out var value))
			{
				return "";
			}
			return (value.text ?? "").Trim();
		}

		private static float Num(string key, float fallback)
		{
			if (!float.TryParse(V(key), NumberStyles.Float, CultureInfo.InvariantCulture, out var result))
			{
				return fallback;
			}
			return result;
		}

		private static string F(float v)
		{
			return v.ToString("0.##", CultureInfo.InvariantCulture);
		}

		private static string Next(string[] options, string current)
		{
			int num = Array.IndexOf(options, current);
			return options[(num + 1) % options.Length];
		}

		private static NpcProfile Collect()
		{
			NpcProfile npcProfile = new NpcProfile();
			npcProfile.Id = V("profile");
			npcProfile.Name = V("name");
			npcProfile.Role = _role;
			npcProfile.Model = V("model");
			npcProfile.Greeting = V("greeting");
			npcProfile.Look = new Appearance
			{
				Helmet = V("helmet"),
				Chest = V("chest"),
				Legs = V("legs"),
				Cape = V("cape"),
				LeftHand = V("leftHand"),
				RightHand = V("rightHand"),
				LeftBack = V("leftBack"),
				RightBack = V("rightBack"),
				Hair = V("hair"),
				Beard = V("beard"),
				HairColor = V("hairColor"),
				SkinColor = V("skinColor"),
				Scale = Mathf.Clamp(Num("scale", 1f), 0.5f, 3f)
			};
			npcProfile.Behaviour = new NpcBehaviour
			{
				Goodbye = V("goodbye"),
				GreetRange = Num("greetRange", 5f),
				GreetAnimation = V("greetAnimation"),
				GoodbyeAnimation = V("goodbyeAnimation"),
				InteractAnimation = V("interactAnimation"),
				IdleAnimation = V("idleAnimation"),
				IdleEverySeconds = Num("idleEvery", 60f),
				InteractSound = V("interactSound"),
				IdleSound = V("idleSound"),
				IdleSoundEverySeconds = Num("idleSoundEvery", 0f),
				CraftingPose = (int)Num("craftingPose", -1f),
				MapPin = _pin,
				Patrol = new Patrol
				{
					Speed = Num("patrolSpeed", 1.5f),
					PauseSeconds = Num("patrolPause", 3f),
					Points = ParsePoints(V("patrol"))
				}
			};
			npcProfile.FillDefaults();
			return npcProfile;
		}

		private static List<PatrolPoint> ParsePoints(string text)
		{
			List<PatrolPoint> list = new List<PatrolPoint>();
			string[] array = text.Split(new char[1] { ';' });
			for (int i = 0; i < array.Length; i++)
			{
				string[] array2 = (from s in array[i].Split(new char[1] { ',' })
					select s.Trim() into s
					where s.Length > 0
					select s).ToArray();
				if (array2.Length == 3 && float.TryParse(array2[0], NumberStyles.Float, CultureInfo.InvariantCulture, out var result) && float.TryParse(array2[1], NumberStyles.Float, CultureInfo.InvariantCulture, out var result2) && float.TryParse(array2[2], NumberStyles.Float, CultureInfo.InvariantCulture, out var result3))
				{
					list.Add(new PatrolPoint
					{
						X = result,
						Y = result2,
						Z = result3
					});
				}
			}
			return list;
		}

		private static AdminEditRequest Request(NpcProfile over)
		{
			//IL_000a: 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)
			ZDOID uid = _npc.Zdo.m_uid;
			return new AdminEditRequest
			{
				ZdoUser = ((ZDOID)(ref uid)).UserID,
				ZdoId = ((ZDOID)(ref uid)).ID,
				ProfileId = V("profile"),
				Name = V("name"),
				Override = over
			};
		}

		private static void Send(bool move)
		{
			//IL_003f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0044: Unknown result type (might be due to invalid IL or missing references)
			//IL_006e: 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_0074: Unknown result type (might be due to invalid IL or missing references)
			//IL_0079: Unknown result type (might be due to invalid IL or missing references)
			//IL_008e: Unknown result type (might be due to invalid IL or missing references)
			//IL_009a: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a6: Unknown result type (might be due to invalid IL or missing references)
			//IL_0057: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00dc: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ca: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)_npc == (Object)null || _npc.Zdo == null)
			{
				Close();
				return;
			}
			AdminEditRequest adminEditRequest = Request(Collect());
			if (move)
			{
				Player localPlayer = Player.m_localPlayer;
				Vector3 position = ((Component)_npc).transform.position;
				float y = default(float);
				if ((Object)(object)ZoneSystem.instance != (Object)null && ZoneSystem.instance.GetGroundHeight(position, ref y))
				{
					position.y = y;
				}
				Vector3 val = ((Component)localPlayer).transform.position - position;
				val.y = 0f;
				adminEditRequest.Move = true;
				adminEditRequest.X = position.x;
				adminEditRequest.Y = position.y;
				adminEditRequest.Z = position.z;
				float y2;
				if (!(((Vector3)(ref val)).sqrMagnitude > 0.01f))
				{
					y2 = ((Component)_npc).transform.eulerAngles.y;
				}
				else
				{
					Quaternion val2 = Quaternion.LookRotation(val);
					y2 = ((Quaternion)(ref val2)).eulerAngles.y;
				}
				adminEditRequest.RotY = y2;
			}
			ClientState.Send("admin.edit", adminEditRequest, delegate(Response r)
			{
				ClientState.Notify(r.Message);
				if (r.Ok)
				{
					Close();
				}
			});
		}

		private static void UseDefaults()
		{
			if ((Object)(object)_npc == (Object)null || _npc.Zdo == null)
			{
				Close();
				return;
			}
			ClientState.Send("admin.edit", Request(null), delegate(Response r)
			{
				ClientState.Notify(r.Message);
				if (r.Ok)
				{
					Close();
				}
			});
		}

		private static void SavePreset()
		{
			//IL_0030: Unknown result type (might be due to invalid IL or missing references)
			//IL_0035: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)_npc == (Object)null || _npc.Zdo == null)
			{
				Close();
				return;
			}
			ZDOID id = _npc.Zdo.m_uid;
			ClientState.Send("admin.edit", Request(Collect()), delegate(Response r)
			{
				if (!r.Ok)
				{
					ClientState.Notify(r.Message);
				}
				else
				{
					ClientState.Send("admin.savePreset", new AdminSavePresetRequest
					{
						ZdoUser = ((ZDOID)(ref id)).UserID,
						ZdoId = ((ZDOID)(ref id)).ID,
						PresetId = V("preset")
					}, delegate(Response response)
					{
						ClientState.Notify(response.Message);
						if (response.Ok)
						{
							Close();
						}
					});
				}
			});
		}

		private static void Remove()
		{
			//IL_0029: Unknown result type (might be due to invalid IL or missing references)
			//IL_002e: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)_npc == (Object)null || _npc.Zdo == null)
			{
				Close();
				return;
			}
			ZDOID uid = _npc.Zdo.m_uid;
			ClientState.Send("admin.remove", new NpcRef
			{
				ZdoUser = ((ZDOID)(ref uid)).UserID,
				ZdoId = ((ZDOID)(ref uid)).ID
			}, delegate(Response r)
			{
				ClientState.Notify(r.Message);
			});
			Close();
		}
	}
	internal static class NpcPanel
	{
		private const float Width = 760f;

		private const float Height = 620f;

		private const float MaxDistance = 8f;

		private static GameObject _window;

		private static GameObject _body;

		private static int _closedFrame = -1;

		public static NpcComponent Npc { get; private set; }

		public static NpcProfile Profile { get; private set; }

		public static int Tab { get; set; }

		public static bool PayFromBank { get; set; }

		public static bool IsOpen => (Object)(object)_window != (Object)null;

		public static bool ClosedThisFrame => _closedFrame == Time.frameCount;

		public static void Open(NpcComponent npc, NpcProfile profile)
		{
			if (!((Object)(object)GUIManager.CustomGUIFront == (Object)null))
			{
				Close();
				Npc = npc;
				Profile = profile;
				Tab = 0;
				_window = PanelBuilder.Window(760f, 620f);
				((Object)_window).name = "TSN_Panel";
				GUIManager.BlockInput(true);
				Views.OnOpen(profile);
				Refresh();
			}
		}

		public static void Close()
		{
			if (!((Object)(object)_window == (Object)null))
			{
				Object.Destroy((Object)(object)_window);
				_window = null;
				_body = null;
				Npc = null;
				Profile = null;
				_closedFrame = Time.frameCount;
				GUIManager.BlockInput(false);
			}
		}

		public static void Tick()
		{
			//IL_0032: Unknown result type (might be due to invalid IL or missing references)
			//IL_0041: Unknown result type (might be due to invalid IL or missing references)
			if (IsOpen)
			{
				Player localPlayer = Player.m_localPlayer;
				if ((Object)(object)localPlayer == (Object)null || ((Character)localPlayer).IsDead() || (Object)(object)Npc == (Object)null || Vector3.Distance(((Component)localPlayer).transform.position, ((Component)Npc).transform.position) > 8f || ZInput.GetKeyDown((KeyCode)27, true))
				{
					Close();
				}
			}
		}

		public static void OnConfigChanged()
		{
			if (IsOpen)
			{
				NpcProfile npcProfile = (((Object)(object)Npc != (Object)null) ? Npc.Profile : null);
				if (npcProfile == null)
				{
					Close();
					return;
				}
				Profile = npcProfile;
				Refresh();
			}
		}

		public static void Refresh()
		{
			//IL_0037: Unknown result type (might be due to invalid IL or missing references)
			//IL_0041: Expected O, but got Unknown
			//IL_0066: Unknown result type (might be due to invalid IL or missing references)
			//IL_0071: 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_0081: 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_0089: Unknown result type (might be due to invalid IL or missing references)
			//IL_00dc: Unknown result type (might be due to invalid IL or missing references)
			//IL_015a: Unknown result type (might be due to invalid IL or missing references)
			//IL_027b: Unknown result type (might be due to invalid IL or missing references)
			if (!IsOpen)
			{
				return;
			}
			if ((Object)(object)_body != (Object)null)
			{
				Object.Destroy((Object)(object)_body);
			}
			_body = new GameObject("Body", new Type[1] { typeof(RectTransform) });
			_body.transform.SetParent(_window.transform, false);
			RectTransform component = _body.GetComponent<RectTransform>();
			component.anchorMin = Vector2.zero;
			component.anchorMax = Vector2.one;
			Vector2 offsetMin = (component.offsetMax = Vector2.zero);
			component.offsetMin = offsetMin;
			Transform transform = _body.transform;
			string text = (((Object)(object)Npc != (Object)null) ? Npc.DisplayName : Profile.Name);
			PanelBuilder.Label(transform, text, 20f, 18f, 640f, 40f, 28, GUIManager.Instance.ValheimOrange, (TextAnchor)4);
			PanelBuilder.Button(transform, "X", 690f, 18f, 46f, 38f, Close);
			if (!string.IsNullOrEmpty(Profile.Greeting))
			{
				PanelBuilder.Label(transform, Profile.Greeting, 20f, 58f, 720f, 28f, 16, GUIManager.Instance.ValheimBeige, (TextAnchor)4);
			}
			string[] array = Views.Tabs(Profile);
			for (int i = 0; i < array.Length; i++)
			{
				int index = i;
				string text2 = ((i == Tab) ? ("> " + array[i] + " <") : array[i]);
				PanelBuilder.Button(transform, text2, 20f + (float)i * 158f, 94f, 150f, 34f, delegate
				{
					Tab = index;
					Refresh();
				});
			}
			if (Views.UsesPayment(Profile) && ClientState.Config.Settings.Bank.Enabled)
			{
				PanelBuilder.Button(transform, PayFromBank ? "Pay from: Bank" : "Pay from: Inventory", 530f, 94f, 210f, 34f, delegate
				{
					PayFromBank = !PayFromBank;
					Refresh();
				});
			}
			PanelBuilder.Label(transform, Views.Status(Profile), 20f, 134f, 720f, 26f, 16, Color.white, (TextAnchor)3);
			float contentWidth;
			RectTransform content = PanelBuilder.ScrollList(transform, 20f, 166f, 720f, 434f, out contentWidth);
			List<Row> list = new List<Row>();
			Views.Rows(Profile, Tab, list);
			if (list.Count == 0)
			{
				list.Add(new Row
				{
					Label = "Nothing here yet."
				});
			}
			foreach (Row item in list)
			{
				PanelBuilder.AddRow(content, contentWidth, item);
			}
		}
	}
	[HarmonyPatch(typeof(Menu), "Update")]
	internal static class MenuUpdatePatch
	{
		private static bool Prefix()
		{
			if (!NpcPanel.IsOpen && !NpcPanel.ClosedThisFrame && !NpcEditor.IsOpen && !NpcEditor.ClosedThisFrame && !ConfigEditor.IsOpen && !ConfigEditor.ClosedThisFrame && !QuestDesigner.IsOpen && !QuestDesigner.ClosedThisFrame && !DialoguePanel.IsOpen && !DialoguePanel.ClosedThisFrame && !QuestJournal.IsOpen)
			{
				return !QuestJournal.ClosedThisFrame;
			}
			return false;
		}
	}
	internal class Row
	{
		public Sprite Icon;

		public string Label;

		public string ButtonText;

		public Action<int> OnClick;

		public string InputPlaceholder;

		public int InputDefault;

		public bool Header;

		public Action<string> OnText;
	}
	internal static class PanelBuilder
	{
		private static readonly Vector2 Center = new Vector2(0.5f, 0.5f);

		private static readonly Vector2 MidLeft = new Vector2(0f, 0.5f);

		private static readonly Vector2 TopLeft = new Vector2(0f, 1f);

		private const float RowHeight = 40f;

		public static GameObject Window(float width, float height)
		{
			//IL_000f: 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_0019: Unknown result type (might be due to invalid IL or missing references)
			return GUIManager.Instance.CreateWoodpanel(GUIManager.CustomGUIFront.transform, Center, Center, Vector2.zero, width, height, true);
		}

		public static Text Label(Transform parent, string text, float x, float y, float width, float height, int size, Color color, TextAnchor align = (TextAnchor)3)
		{
			//IL_0007: 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_0026: Unknown result type (might be due to invalid IL or missing references)
			//IL_0037: 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)
			//IL_004f: Unknown result type (might be due to invalid IL or missing references)
			Text component = GUIManager.Instance.CreateText(text, parent, TopLeft, TopLeft, new Vector2(x + width / 2f, 0f - (y + height / 2f)), GUIManager.Instance.AveriaSerifBold, size, color, true, Color.black, width, height, false).GetComponent<Text>();
			component.alignment = align;
			component.horizontalOverflow = (HorizontalWrapMode)0;
			component.verticalOverflow = (VerticalWrapMode)0;
			return component;
		}

		public static Button Button(Transform parent, string text, float x, float y, float width, float height, Action onClick)
		{
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			//IL_001a: 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_0054: Unknown result type (might be due to invalid IL or missing references)
			//IL_005e: Expected O, but got Unknown
			Button component = GUIManager.Instance.CreateButton(text, parent, TopLeft, TopLeft, new Vector2(x + width / 2f, 0f - (y + height / 2f)), width, height).GetComponent<Button>();
			((UnityEvent)component.onClick).AddListener((UnityAction)delegate
			{
				onClick();
			});
			return component;
		}

		public static RectTransform ScrollList(Transform parent, float x, float y, float width, float height, out float contentWidth)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0005: 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_003c: Unknown result type (might be due to invalid IL or missing references)
			//IL_005c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0078: Unknown result type (might be due to invalid IL or missing references)
			//IL_008d: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ae: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c8: Unknown result type (might be due to invalid IL or missing references)
			//IL_0101: Unknown result type (might be due to invalid IL or missing references)
			//IL_010b: Expected O, but got Unknown
			ColorBlock defaultColorBlock = ColorBlock.defaultColorBlock;
			((ColorBlock)(ref defaultColorBlock)).normalColor = new Color(0.82f, 0.55f, 0.22f, 1f);
			((ColorBlock)(ref defaultColorBlock)).highlightedColor = new Color(1f, 0.7f, 0.3f, 1f);
			((ColorBlock)(ref defaultColorBlock)).pressedColor = new Color(0.6f, 0.4f, 0.15f, 1f);
			GameObject obj = GUIManager.Instance.CreateScrollView(parent, false, true, 10f, 4f, defaultColorBlock, new Color(0f, 0f, 0f, 0.35f), width, height);
			RectTransform component = obj.GetComponent<RectTransform>();
			Vector2 anchorMin = (component.anchorMax = TopLeft);
			component.anchorMin = anchorMin;
			component.anchoredPosition = new Vector2(x + width / 2f, 0f - (y + height / 2f));
			contentWidth = width - 8f - 10f;
			ScrollRect componentInChildren = obj.GetComponentInChildren<ScrollRect>();
			VerticalLayoutGroup component2 = ((Component)componentInChildren.content).GetComponent<VerticalLayoutGroup>();
			((HorizontalOrVerticalLayoutGroup)component2).spacing = 4f;
			((LayoutGroup)component2).padding = new RectOffset(4, 4, 4, 4);
			return componentInChildren.content;
		}

		private static GameObject NewRow(RectTransform content, float width, bool header)
		{
			//IL_0032: Unknown result type (might be due to invalid IL or missing references)
			//IL_0037: Unknown result type (might be due to invalid IL or missing references)
			//IL_0044: Unknown result type (might be due to invalid IL or missing references)
			//IL_0056: Unknown result type (might be due to invalid IL or missing references)
			//IL_0060: Unknown result type (might be due to invalid IL or missing references)
			//IL_0079: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b1: Unknown result type (might be due to invalid IL or missing references)
			//IL_0096: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bc: Expected O, but got Unknown
			GameObject val = new GameObject("Row", new Type[3]
			{
				typeof(RectTransform),
				typeof(LayoutElement),
				typeof(Image)
			});
			val.transform.SetParent((Transform)(object)content, false);
			val.GetComponent<RectTransform>().sizeDelta = new Vector2(width - 8f, 40f);
			LayoutElement component = val.GetComponent<LayoutElement>();
			float minHeight = (component.preferredHeight = 40f);
			component.minHeight = minHeight;
			((Graphic)val.GetComponent<Image>()).color = (header ? new Color(0.35f, 0.25f, 0.1f, 0.5f) : new Color(0f, 0f, 0f, 0.25f));
			return val;
		}

		private static Text RowText(Transform row, string text, float x, float width, bool header)
		{
			//IL_0010: 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_0028: Unknown result type (might be due to invalid IL or missing references)
			//IL_0051: Unknown result type (might be due to invalid IL or missing references)
			//IL_0045: Unknown result type (might be due to invalid IL or missing references)
			//IL_0057: Unknown result type (might be due to invalid IL or missing references)
			Text component = GUIManager.Instance.CreateText(text ?? "", row, MidLeft, MidLeft, new Vector2(x + width / 2f, 0f), GUIManager.Instance.AveriaSerifBold, header ? 18 : 16, header ? GUIManager.Instance.ValheimOrange : Color.white, true, Color.black, width, 40f, false).GetComponent<Text>();
			component.alignment = (TextAnchor)3;
			component.horizontalOverflow = (HorizontalWrapMode)0;
			component.verticalOverflow = (VerticalWrapMode)0;
			component.resizeTextForBestFit = true;
			component.resizeTextMinSize = 11;
			component.resizeTextMaxSize = (header ? 18 : 16);
			return component;
		}

		public static void AddRow(RectTransform content, float width, Row row)
		{
			//IL_009b: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ba: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bf: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ef: Unknown result type (might be due to invalid IL or missing references)
			//IL_0273: 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_028a: Unknown result type (might be due to invalid IL or missing references)
			//IL_02af: Unknown result type (might be due to invalid IL or missing references)
			//IL_02b9: Expected O, but got Unknown
			//IL_01f1: 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_0208: Unknown result type (might be due to invalid IL or missing references)
			if (row.OnText != null)
			{
				((UnityEvent<string>)(object)AddTextRow(content, width, row.Label, "").onValueChanged).AddListener((UnityAction<string>)delegate(string v)
				{
					row.OnText(v);
				});
				return;
			}
			GameObject val = NewRow(content, width, row.Header);
			float num = 6f;
			if (Object.op_Implicit((Object)(object)row.Icon))
			{
				GameObject val2 = new GameObject("Icon", new Type[2]
				{
					typeof(RectTransform),
					typeof(Image)
				});
				val2.transform.SetParent(val.transform, false);
				RectTransform component = val2.GetComponent<RectTransform>();
				Vector2 anchorMin = (component.anchorMax = MidLeft);
				component.anchorMin = anchorMin;
				component.sizeDelta = new Vector2(32f, 32f);
				component.anchoredPosition = new Vector2(num + 16f, 0f);
				Image component2 = val2.GetComponent<Image>();
				component2.sprite = row.Icon;
				component2.preserveAspect = true;
				num += 40f;
			}
			bool flag = row.OnClick != null && !string.IsNullOrEmpty(row.ButtonText);
			bool flag2 = flag && row.InputPlaceholder != null;
			float num2 = width - 8f - 6f - (flag ? 126f : 0f) - (flag2 ? 86f : 0f);
			RowText(val.transform, row.Label, num, num2 - num, row.Header);
			if (!flag)
			{
				return;
			}
			InputField input = null;
			float num3 = width - 8f - 6f - 120f;
			if (flag2)
			{
				float num4 = num3 - 6f - 80f;
				input = GUIManager.Instance.CreateInputField(val.transform, MidLeft, MidLeft, new Vector2(num4 + 40f, 0f), (ContentType)2, row.InputPlaceholder, 16, 80f, 30f).GetComponent<InputField>();
				if (row.InputDefault > 0)
				{
					input.text = row.InputDefault.ToString();
				}
			}
			((UnityEvent)GUIManager.Instance.CreateButton(row.ButtonText, val.transform, MidLeft, MidLeft, new Vector2(num3 + 60f, 0f), 120f, 32f).GetComponent<Button>().onClick).AddListener((UnityAction)delegate
			{
				int result = 0;
				if ((Object)(object)input != (Object)null)
				{
					int.TryParse(input.text, out result);
				}
				row.OnClick(result);
			});
		}

		public static InputField[] AddFieldsRow(RectTransform content, float width, string[] values, string buttonText = null, Action onButton = null)
		{
			//IL_010c: Unknown result type (might be due to invalid IL or missing references)
			//IL_011b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0133: Unknown result type (might be due to invalid IL or missing references)
			//IL_015a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0164: 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_0097: Unknown result type (might be due to invalid IL or missing references)
			GameObject val = NewRow(content, width, header: false);
			InputField[] array = (InputField[])(object)new InputField[values.Length];
			float num = (string.IsNullOrEmpty(buttonText) ? 0f : 90f);
			float num2 = (width - 8f - num - 8f) / ((float)values.Length + 0.8f);
			float num3 = 4f;
			for (int i = 0; i < values.Length; i++)
			{
				float num4 = ((i == 0) ? (num2 * 1.8f) : num2);
				InputField component = GUIManager.Instance.CreateInputField(val.transform, MidLeft, MidLeft, new Vector2(num3 + num4 / 2f, 0f), (ContentType)0, "", 13, num4 - 4f, 30f).GetComponent<InputField>();
				component.text = values[i] ?? "";
				array[i] = component;
				num3 += num4;
			}
			if (num > 0f)
			{
				((UnityEvent)GUIManager.Instance.CreateButton(buttonText, val.transform, new Vector2(1f, 0.5f), new Vector2(1f, 0.5f), new Vector2(0f - num / 2f - 4f, 0f), num - 6f, 30f).GetComponent<Button>().onClick).AddListener((UnityAction)delegate
				{
					onButton?.Invoke();
				});
			}
			return array;
		}

		public static InputField AddTextRow(RectTransform content, float width, string label, string value)
		{
			//IL_0054: Unknown result type (might be due to invalid IL or missing references)
			//IL_0059: Unknown result type (might be due to invalid IL or missing references)
			//IL_006c: Unknown result type (might be due to invalid IL or missing references)
			GameObject val = NewRow(content, width, header: false);
			float num = (width - 8f) * 0.48f;
			RowText(val.transform, label, 6f, num - 6f, header: false);
			float num2 = num + 4f;
			float num3 = width - 8f - num2 - 6f;
			InputField component = GUIManager.Instance.CreateInputField(val.transform, MidLeft, MidLeft, new Vector2(num2 + num3 / 2f, 0f), (ContentType)0, "", 15, num3, 30f).GetComponent<InputField>();
			component.text = value ?? "";
			return component;
		}
	}
	internal static class QuestDesigner
	{
		private const float Width = 960f;

		private const float Height = 740f;

		private static GameObject _window;

		private static QuestDef _quest;

		private static readonly Dictionary<string, InputField> F = new Dictionary<string, InputField>();

		private static readonly List<InputField[]> StepFields = new List<InputField[]>();

		private static readonly List<InputField[]> ItemFields = new List<InputField[]>();

		private static int _closedFrame = -1;

		public static bool IsOpen => (Object)(object)_window != (Object)null;

		public static bool ClosedThisFrame => _closedFrame == Time.frameCount;

		public static void Open(string editQuestId = null)
		{
			if (!((Object)(object)GUIManager.CustomGUIFront == (Object)null) && ClientState.Config != null)
			{
				if (!ClientState.IsLocalAdmin)
				{
					ClientState.Notify("Only admins can design quests.");
					return;
				}
				NpcPanel.Close();
				NpcEditor.Close();
				Close();
				_quest = ((editQuestId == null) ? null : Clone(ClientState.Config.GetQuest(editQuestId)));
				GUIManager.BlockInput(true);
				Build();
			}
		}

		public static void Close()
		{
			if (!((Object)(object)_window == (Object)null))
			{
				Object.Destroy((Object)(object)_window);
				_window = null;
				_quest = null;
				F.Clear();
				StepFields.Clear();
				ItemFields.Clear();
				_closedFrame = Time.frameCount;
				GUIManager.BlockInput(false);
			}
		}

		public static void Tick()
		{
			if (IsOpen && ((Object)(object)Player.m_localPlayer == (Object)null || ZInput.GetKeyDown((KeyCode)27, true)))
			{
				Close();
			}
		}

		public static void OnConfigChanged()
		{
			if (IsOpen && _quest == null)
			{
				Build();
			}
		}

		private static void Build()
		{
			if ((Object)(object)_window != (Object)null)
			{
				Object.Destroy((Object)(object)_window);
			}
			F.Clear();
			StepFields.Clear();
			ItemFields.Clear();
			_window = PanelBuilder.Window(960f, 740f);
			((Object)_window).name = "TSN_QuestDesigner";
			Transform transform = _window.transform;
			PanelBuilder.Button(transform, "X", 890f, 18f, 46f, 38f, Close);
			if (_quest == null)
			{
				BuildList(transform);
			}
			else
			{
				BuildForm(transform);
			}
		}

		private static void BuildList(Transform t)
		{
			//IL_0021: Unknown result type (might be due to invalid IL or missing references)
			PanelBuilder.Label(t, "Quest Designer", 20f, 18f, 840f, 40f, 28, GUIManager.Instance.ValheimOrange, (TextAnchor)4);
			float contentWidth;
			RectTransform content = PanelBuilder.ScrollList(t, 20f, 66f, 920f, 604f, out contentWidth);
			PanelBuilder.AddRow(content, contentWidth, new Row
			{
				Header = true,
				Label = "Start a new quest",
				ButtonText = "New quest",
				OnClick = delegate
				{
					_quest = NewQuest();
					Build();
				}
			});
			PanelBuilder.AddRow(content, contentWidth, new Row
			{
				Label = "Quests are files in quests/ on the server. Edit here or in any text editor; both reload live."
			});
			foreach (QuestDef item in from q in ClientState.Config.Quests
				orderby q.Giver, q.Name
				select q)
			{
				QuestDef quest = item;
				string text = (string.IsNullOrEmpty(item.Giver) ? "dialogue only" : (ClientState.Config.GetProfile(item.Giver)?.Name ?? item.Giver));
				PanelBuilder.AddRow(content, contentWidth, new Row
				{
					Label = $"{item.Name}   ({item.Id})   giver: {text}   ·   {item.Steps.Count} step(s), {item.Repeat}",
					ButtonText = "Edit",
					OnClick = delegate
					{
						_quest = Clone(quest);
						Build();
					}
				});
			}
			PanelBuilder.Button(t, "Close", 20f, 678f, 120f, 40f, Close);
		}

		private static void BuildForm(Transform t)
		{
			//IL_005b: Unknown result type (might be due to invalid IL or missing references)
			QuestDef q = _quest;
			PanelBuilder.Label(t, string.IsNullOrEmpty(q.Id) ? "New quest" : ("Quest: " + q.Id), 20f, 18f, 840f, 40f, 26, GUIManager.Instance.ValheimOrange, (TextAnchor)4);
			float contentWidth;
			RectTransform val = PanelBuilder.ScrollList(t, 20f, 66f, 920f, 604f, out contentWidth);
			Header(val, contentWidth, "Basics");
			Field(val, contentWidth, "id", "Id (letters, numbers, _ ; becomes the file name)", q.Id);
			Field(val, contentWidth, "name", "Name shown to players", q.Name);
			Field(val, contentWidth, "description", "Description", q.Description);
			List<string> list = (from p in ClientState.Config.Profiles
				where p.Role == "quest"
				select p.Id).ToList();
			Field(val, contentWidth, "giver", "Giver profile id  (quest NPCs: " + ((list.Count == 0) ? "none yet" : string.Join(", ", list)) + "; empty = dialogue only)", q.Giver);
			Field(val, contentWidth, "turnIn", "Hand in at (profile id, empty = the giver)", q.TurnIn);
			PanelBuilder.AddRow(val, contentWidth, new Row
			{
				Label = "Repeat: " + q.Repeat,
				ButtonText = "Change",
				OnClick = delegate
				{
					Collect();
					q.Repeat = Next(QuestRepeat.All, q.Repeat);
					Build();
				}
			});
			Field(val, contentWidth, "cooldownHours", "Cooldown hours (repeat: cooldown)", N(q.CooldownHours));
			Field(val, contentWidth, "timeLimitHours", "Time limit hours (0 = none)", N(q.TimeLimitHours));
			PanelBuilder.AddRow(val, contentWidth, new Row
			{
				Label = "Hidden until unlocked by another quest: " + (q.Hidden ? "yes" : "no"),
				ButtonText = "Toggle",
				OnClick = delegate
				{
					Collect();
					q.Hidden = !q.Hidden;
					Build();
				}
			});
			Field(val, contentWidth, "requiresQuests", "Requires quests done first (ids, comma separated)", string.Join(", ", q.RequiresQuests));
			Field(val, contentWidth, "requiresKey", "Requires global key (e.g. defeated_eikthyr)", q.RequiresKey);
			Header(val, contentWidth, "Steps   —   type | target / npc | amount | min stars+1 | x | z | radius | text shown");
			for (int num = 0; num < q.Steps.Count; num++)
			{
				QuestStep s = q.Steps[num];
				int index = num;
				PanelBuilder.AddRow(val, contentWidth, new Row
				{
					Label = $"Step {num + 1}: {s.Type}   ({Hint(s.Type)})",
					ButtonText = "Change type",
					OnClick = delegate
					{
						Collect();
						s.Type = Next(QuestStepTypes.All, s.Type);
						Build();
					}
				});
				InputField[] item = PanelBuilder.AddFieldsRow(val, contentWidth, new string[7]
				{
					(s.Type == "talk") ? s.Npc : s.Target,
					s.Amount.ToString(),
					s.MinLevel.ToString(),
					N(s.X),
					N(s.Z),
					N(s.Radius),
					s.Text
				}, "Remove", delegate
				{
					Collect();
					q.Steps.RemoveAt(index);
					Build();
				});
				StepFields.Add(item);
				if (s.Type == "explore")
				{
					PanelBuilder.AddRow(val, contentWidth, new Row
					{
						Label = "   Use my current position for this step",
						ButtonText = "Here",
						OnClick = delegate
						{
							//IL_000f: 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_001b: Unknown result type (might be due to invalid IL or missing references)
							//IL_0031: Unknown result type (might be due to invalid IL or missing references)
							Collect();
							Vector3 position = ((Component)Player.m_localPlayer).transform.position;
							s.X = Mathf.Round(position.x);
							s.Z = Mathf.Round(position.z);
							Build();
						}
					});
				}
			}
			PanelBuilder.AddRow(val, contentWidth, new Row
			{
				Label = "Add a step",
				ButtonText = "Add step",
				OnClick = delegate
				{
					Collect();
					q.Steps.Add(new QuestStep
					{
						Type = "kill",
						Target = "Greydwarf",
						Amount = 5
					});
					Build();
				}
			});
			Header(val, contentWidth, "Rewards");
			Field(val, contentWidth, "coins", "Coins", q.Rewards.Coins.ToString());
			PanelBuilder.AddRow(val, contentWidth, new Row
			{
				Label = "Coins go to: " + (q.Rewards.CoinsToBank ? "bank" : "inventory"),
				ButtonText = "Toggle",
				OnClick = delegate
				{
					Collect();
					q.Rewards.CoinsToBank = !q.Rewards.CoinsToBank;
					Build();
				}
			});
			PanelBuilder.AddRow(val, contentWidth, new Row
			{
				Label = "Items   —   prefab | amount | quality"
			});
			for (int num2 = 0; num2 < q.Rewards.Items.Count; num2++)
			{
				int index2 = num2;
				RewardItem rewardItem = q.Rewards.Items[num2];
				ItemFields.Add(PanelBuilder.AddFieldsRow(val, contentWidth, new string[3]
				{
					rewardItem.Item,
					rewardItem.Amount.ToString(),
					rewardItem.Quality.ToString()
				}, "Remove", delegate
				{
					Collect();
					q.Rewards.Items.RemoveAt(index2);
					Build();
				}));
			}
			PanelBuilder.AddRow(val, contentWidth, new Row
			{
				Label = "Add a reward item",
				ButtonText = "Add item",
				OnClick = delegate
				{
					Collect();
					q.Rewards.Items.Add(new RewardItem
					{
						Item = "Coal",
						Amount = 10
					});
					Build();
				}
			});
			Field(val, contentWidth, "skill", "Skill to train (Swords, Bows, Run ...)", q.Rewards.Skill);
			Field(val, contentWidth, "skillXp", "Skill experience", N(q.Rewards.SkillXp));
			Field(val, contentWidth, "buff", "Buff id or status effect name", q.Rewards.Buff);
			Field(val, contentWidth, "buffSeconds", "Buff seconds (0 = its default)", N(q.Rewards.BuffSeconds));
			Field(val, contentWidth, "unlockQuests", "Unlocks hidden quests (ids, comma separated)", string.Join(", ", q.Rewards.UnlockQuests));
			Header(val, contentWidth, "Messages  (more event actions can be added in the YAML file)");
			Field(val, contentWidth, "onAccept", "Said when accepted", EventMessage(q, "onAccept"));
			Field(val, contentWidth, "onComplete", "Said when completed", EventMessage(q, "onComplete"));
			Field(val, contentWidth, "onTimeout", "Said when it times out", EventMessage(q, "onTimeout"));
			float y = 678f;
			PanelBuilder.Button(t, "Save (goes live)", 20f, y, 190f, 40f, Save);
			PanelBuilder.Button(t, "Back to list", 220f, y, 150f, 40f, delegate
			{
				_quest = null;
				Build();
			});
			if (ClientState.Config.GetQuest(q.Id) != null)
			{
				PanelBuilder.Button(t, "Delete quest", 780f, y, 160f, 40f, Delete);
			}
		}

		private static void Header(RectTransform c, float w, string text)
		{
			PanelBuilder.AddRow(c, w, new Row
			{
				Header = true,
				Label = text
			});
		}

		private static void Field(RectTransform c, float w, string key, string label, string value)
		{
			F[key] = PanelBuilder.AddTextRow(c, w, label, value ?? "");
		}

		private static string V(string key)
		{
			if (!F.TryGetValue(key, out var value))
			{
				return "";
			}
			return (value.text ?? "").Trim();
		}

		private static float Num(string s, float d)
		{
			if (!float.TryParse((s ?? "").Trim(), NumberStyles.Float, CultureInfo.InvariantCulture, out var result))
			{
				return d;
			}
			return result;
		}

		private static string N(float v)
		{
			return v.ToString("0.##", CultureInfo.InvariantCulture);
		}

		private static string Next(string[] options, string current)
		{
			return options[(Array.IndexOf(options, current) + 1) % options.Length];
		}

		private static List<string> Csv(string s)
		{
			return (from x in s.Split(new char[1] { ',' })
				select x.Trim() into x
				where x.Length > 0
				select x).ToList();
		}

		private static string Hint(string type)
		{
			return type switch
			{
				"kill" => "creature prefab, e.g. Greydwarf; min stars+1: 1 = any, 2 = one star", 
				"gather" => "item prefab; taken from the player when handed in", 
				"deliver" => "item prefab brought to the hand-in NPC", 
				"craft" => "item prefab crafted", 
				"build" => "piece prefab placed, e.g. fire_pit", 
				"explore" => "reach x, z within radius", 
				"talk" => "profile id of the NPC to speak to", 
				_ => "", 
			};
		}

		private static string EventMessage(QuestDef q, string ev)
		{
			foreach (Dictionary<string, object> item in q.EventActions(ev))
			{
				if (Arg.Name(item).Equals("message", StringComparison.OrdinalIgnoreCase))
				{
					return Arg.Scalar(Arg.Value(item));
				}
			}
			return "";
		}

		private static void SetEventMessage(QuestDef q, string ev, string text)
		{
			List<Dictionary<string, object>> list = (from a in q.EventActions(ev)
				where !Arg.Name(a).Equals("message", StringComparison.OrdinalIgnoreCase)
				select a).ToList();
			if (!string.IsNullOrWhiteSpace(text))
			{
				list.Insert(0, new Dictionary<string, object> { 
				{
					"message",
					text.Trim()
				} });
			}
			string key = q.Events.Keys.FirstOrDefault((string k) => string.Equals(k, ev, StringComparison.OrdinalIgnoreCase)) ?? ev;
			if (list.Count == 0)
			{
				q.Events.Remove(key);
			}
			else
			{
				q.Events[key] = list;
			}
		}

		private static QuestDef NewQuest()
		{
			string giver = ClientState.Config.Profiles.FirstOrDefault((NpcProfile p) => p.Role == "quest")?.Id ?? "";
			QuestDef questDef = new QuestDef();
			questDef.Id = "";
			questDef.Name = "New quest";
			questDef.Description = "";
			questDef.Giver = giver;
			questDef.Repeat = "once";
			questDef.Steps.Add(new QuestStep
			{
				Type = "kill",
				Target = "Greydwarf",
				Amount = 5
			});
			questDef.Rewards.Coins = 50;
			questDef.FillDefaults();
			return questDef;
		}

		private static QuestDef Clone(QuestDef q)
		{
			if (q == null)
			{
				return NewQuest();
			}
			QuestDef questDef = JsonConvert.DeserializeObject<QuestDef>(JsonConvert.SerializeObject((object)q));
			questDef.FillDefaults();
			return questDef;
		}

		private static void Collect()
		{
			QuestDef quest = _quest;
			if (quest == null || F.Count == 0)
			{
				return;
			}
			quest.Id = V("id");
			quest.Name = V("name");
			quest.Description = V("description");
			quest.Giver = V("giver");
			quest.TurnIn = V("turnIn");
			quest.CooldownHours = Num(V("cooldownHours"), 24f);
			quest.TimeLimitHours = Num(V("timeLimitHours"), 0f);
			quest.RequiresQuests = Csv(V("requiresQuests"));
			quest.RequiresKey = V("requiresKey");
			for (int i = 0; i < StepFields.Count && i < quest.Steps.Count; i++)
			{
				InputField[] array = StepFields[i];
				QuestStep questStep = quest.Steps[i];
				string text = array[0].text.Trim();
				if (questStep.Type == "talk")
				{
					questStep.Npc = text;
				}
				else
				{
					questStep.Target = text;
				}
				questStep.Amount = Math.Max(1, (int)Num(array[1].text, 1f));
				questStep.MinLevel = Math.Max(1, (int)Num(array[2].text, 1f));
				questStep.X = Num(array[3].text, 0f);
				questStep.Z = Num(array[4].text, 0f);
				questStep.Radius = Math.Max(2f, Num(array[5].text, 10f));
				questStep.Text = array[6].text.Trim();
			}
			quest.Rewards.Coins = Math.Max(0, (int)Num(V("coins"), 0f));
			for (int j = 0; j < ItemFields.Count && j < quest.Rewards.Items.Count; j++)
			{
				InputField[] array2 = ItemFields[j];
				quest.Rewards.Items[j] = new RewardItem
				{
					Item = array2[0].text.Trim(),
					Amount = Math.Max(1, (int)Num(array2[1].text, 1f)),
					Quality = Math.Max(1, (int)Num(array2[2].text, 1f))
				};
			}
			quest.Rewards.Skill = V("skill");
			quest.Rewards.SkillXp = Num(V("skillXp"), 0f);
			quest.Rewards.Buff = V("buff");
			quest.Rewards.BuffSeconds = Num(V("buffSeconds"), 0f);
			quest.Rewards.UnlockQuests = Csv(V("unlockQuests"));
			SetEventMessage(quest, "onAccept", V("onAccept"));
			SetEventMessage(quest, "onComplete", V("onComplete"));
			SetEventMessage(quest, "onTimeout", V("onTimeout"));
		}

		private static void Save()
		{
			Collect();
			QuestDef quest = _quest;
			if (string.IsNullOrWhiteSpace(quest.Id))
			{
				ClientState.Notify("Give the quest an id first.");
				return;
			}
			if (quest.Steps.Count == 0)
			{
				ClientState.Notify("Add at least one step.");
				return;
			}
			quest.Rewards.Items.RemoveAll((RewardItem i) => string.IsNullOrEmpty(i.Item));
			ClientState.Send("admin.saveQuest", new AdminSaveQuestRequest
			{
				Quest = quest
			}, delegate(Response r)
			{
				ClientState.Notify(r.Message);
				if (r.Ok && IsOpen)
				{
					_quest = null;
					Build();
				}
			});
		}

		private static void Delete()
		{
			string text = _quest?.Id;
			if (string.IsNullOrEmpty(text))
			{
				return;
			}
			ClientState.Send("admin.deleteQuest", new QuestIdRequest
			{
				QuestId = text
			}, delegate(Response r)
			{
				ClientState.Notify(r.Message);
				if (r.Ok && IsOpen)
				{
					_quest = null;
					Build();
				}
			});
		}
	}
	internal static class QuestJournal
	{
		private const float Width = 760f;

		private const float Height = 620f;

		private static GameObject _window;

		private static GameObject _tracker;

		private static Text _trackerText;

		private static int _closedFrame = -1;

		public static bool IsOpen => (Object)(object)_window != (Object)null;

		public static bool ClosedThisFrame => _closedFrame == Time.frameCount;

		public static void Tick()
		{
			//IL_0079: Unknown result type (might be due to invalid IL or missing references)
			Player localPlayer = Player.m_localPlayer;
			if ((Object)(object)localPlayer == (Object)null)
			{
				return;
			}
			if (IsOpen && (((Character)localPlayer).IsDead() || ZInput.GetKeyDown((KeyCode)27, true)))
			{
				Close();
				return;
			}
			if (!TextFocused() && !NpcPanel.IsOpen && !NpcEditor.IsOpen && !DialoguePanel.IsOpen && Enum.TryParse<KeyCode>(ClientState.Config?.Settings.Quests.JournalKey ?? "J", ignoreCase: true, out KeyCode result) && ZInput.GetKeyDown(result, true))
			{
				if (IsOpen)
				{
					Close();
				}
				else
				{
					Open();
				}
			}
			EnsureTracker();
		}

		private static bool TextFocused()
		{
			if (!Console.IsVisible() && !TextInput.IsVisible() && !Menu.IsVisible() && !InventoryGui.IsVisible())
			{
				if ((Object)(object)Chat.instance != (Object)null)
				{
					return Chat.instance.HasFocus();
				}
				return false;
			}
			return true;
		}

		public static void Open()
		{
			if (!((Object)(object)GUIManager.CustomGUIFront == (Object)null))
			{
				Close();
				_window = PanelBuilder.Window(760f, 620f);
				((Object)_window).name = "TSN_Journal";
				GUIManager.BlockInput(true);
				Draw();
			}
		}

		public static void Close()
		{
			if (!((Object)(object)_window == (Object)null))
			{
				Object.Destroy((Object)(object)_window);
				_window = null;
				_closedFrame = Time.frameCount;
				GUIManager.BlockInput(false);
			}
		}

		public static void Refresh()
		{
			if (IsOpen)
			{
				Draw();
			}
			UpdateTracker();
		}

		private static void Draw()
		{
			//IL_0018: Unknown result type (might be due to invalid IL or missing references)
			//IL_001f: Expected O, but got Unknown
			//IL_0074: Unknown result type (might be due to invalid IL or missing references)
			//IL_0079: Unknown result type (might be due to invalid IL or missing references)
			//IL_008f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0096: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ac: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b9: Unknown result type (might be due to invalid IL or missing references)
			//IL_010c: Unknown result type (might be due to invalid IL or missing references)
			foreach (Transform item in _window.transform)
			{
				Transform val = item;
				if (((Object)val).name == "Body")
				{
					Object.Destroy((Object)(object)((Component)val).gameObject);
				}
			}
			GameObject val2 = new GameObject("Body", new Type[1] { typeof(RectTransform) });
			val2.transform.SetParent(_window.transform, false);
			RectTransform component = val2.GetComponent<RectTransform>();
			component.anchorMin = Vector2.zero;
			component.anchorMax = Vector2.one;
			Vector2 offsetMin = (component.offsetMax = Vector2.zero);
			component.offsetMin = offsetMin;
			Transform transform = val2.transform;
			PlayerStateView state = QuestClient.State;
			PanelBuilder.Label(transform, $"Quest Journal  ({state.Active.Count}/{state.MaxActive})", 20f, 18f, 640f, 40f, 28, GUIManager.Instance.ValheimOrange, (TextAnchor)4);
			PanelBuilder.Button(transform, "X", 690f, 18f, 46f, 38f, Close);
			float contentWidth;
			RectTransform content = PanelBuilder.ScrollList(transform, 20f, 70f, 720f, 530f, out contentWidth);
			if (state.Active.Count == 0)
			{
				PanelBuilder.AddRow(content, contentWidth, new Row
				{
					Label = "No active quests. Talk to a quest giver."
				});
			}
			foreach (ActiveQuestView item2 in state.Active)
			{
				ActiveQuestView quest = item2;
				string text = (item2.ExpiresUtc.HasValue ? ("  ·  " + Left(item2.ExpiresUtc.Value)) : "");
				PanelBuilder.AddRow(content, contentWidth, new Row
				{
					Header = true,
					Label = item2.Name + text,
					ButtonText = "Abandon",
					OnClick = delegate
					{
						ClientState.Send("quest.abandon", new QuestIdRequest
						{
							QuestId = quest.Id
						}, delegate(Response r)
						{
							ClientState.Notify(r.Message);
						});
					}
				});
				if (!string.IsNullOrEmpty(item2.Description))
				{
					PanelBuilder.AddRow(content, contentWidth, new Row
					{
						Label = "   " + item2.Description
					});
				}
				foreach (StepView step in item2.Steps)
				{
					int num = (QuestStepTypes.IsLive(step.Type) ? step.Have : Mathf.Min(step.Need, InventoryHelper.Count(step.Target)));
					bool flag = (QuestStepTypes.IsLive(step.Type) ? step.Done : (num >= step.Need));
					PanelBuilder.AddRow(content, contentWidth, new Row
					{
						Label = string.Format("   {0} {1}   {2}/{3}", flag ? "✓" : "•", step.Text, num, step.Need)
					});
				}
				string text2 = ClientState.Config?.GetProfile(item2.TurnIn)?.Name ?? item2.TurnIn;
				if (!string.IsNullOrEmpty(text2))
				{
					PanelBuilder.AddRow(content, contentWidth, new Row
					{
						Label = "   Hand in to " + text2
					});
				}
			}
			if (state.Completed.Count <= 0)
			{
				return;
			}
			PanelBuilder.AddRow(content, contentWidth, new Row
			{
				Header = true,
				Label = $"Completed ({state.Completed.Count})"
			});
			foreach (KeyValuePair<string, DateTime> item3 in state.Completed.OrderByDescending((KeyValuePair<string, DateTime> k) => k.Value))
			{
				string arg = ClientState.Config?.GetQuest(item3.Key)?.Name ?? item3.Key;
				PanelBuilder.AddRow(content, contentWidth, new Row
				{
					Label = $"   {arg}  ·  {item3.Value.ToLocalTime():d MMM}"
				});
			}
		}

		private static string Left(DateTime expiresUtc)
		{
			TimeSpan timeSpan = expiresUtc - DateTime.UtcNow;
			if (timeSpan.TotalSeconds <= 0.0)
			{
				return "expired";
			}
			if (!(timeSpan.TotalHours >= 1.0))
			{
				return $"{timeSpan.Minutes}m left";
			}
			return $"{(int)timeSpan.TotalHours}h {timeSpan.Minutes}m left";
		}

		private static void EnsureTracker()
		{
			//IL_0039: Unknown result type (might be due to invalid IL or missing references)
			//IL_0048: Unknown result type (might be due to invalid IL or missing references)
			//IL_0057: Unknown result type (might be due to invalid IL or missing references)
			//IL_006d: 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)
			if (!((Object)(object)_tracker != (Object)null) && !((Object)(object)GUIManager.CustomGUIBack == (Object)null))
			{
				_tracker = GUIManager.Instance.CreateText("", GUIManager.CustomGUIBack.transform, new Vector2(1f, 1f), new Vector2(1f, 1f), new Vector2(-170f, -120f), GUIManager.Instance.AveriaSerifBold, 15, GUIManager.Instance.ValheimBeige, true, Color.black, 320f, 200f, false);
				((Object)_tracker).name = "TSN_Tracker";
				_trackerText = _tracker.GetComponent<Text>();
				_trackerText.alignment = (TextAnchor)0;
				((Graphic)_trackerText).raycastTarget = false;
				UpdateTracker();
			}
		}

		private static void UpdateTracker()
		{
			if ((Object)(object)_trackerText == (Object)null)
			{
				return;
			}
			StringBuilder stringBuilder = new StringBuilder();
			string text = FactionClient.Names();
			if (text.Length > 0)
			{
				stringBuilder.AppendLine("<color=#9ad0ff>" + text + "</color>");
			}
			foreach (ActiveQuestView item in QuestClient.State.Active.Take(3))
			{
				stringBuilder.AppendLine("<color=#ffb84d>" + item.Name + "</color>");
				foreach (StepView step in item.Steps)
				{
					int num = (QuestStepTypes.IsLive(step.Type) ? step.Have : Mathf.Min(step.Need, InventoryHelper.Count(step.Target)));
					stringBuilder.AppendLine(string.Format("  {0} {1} {2}/{3}", (num >= step.Need) ? "✓" : "•", step.Text, num, step.Need));
				}
			}
			_trackerText.text = stringBuilder.ToString();
		}
	}
	internal static class ServiceViews
	{
		public static string[] Tabs(NpcProfile p)
		{
			switch (p.Role)
			{
			case "gambler":
				return new string[1] { "Roll" };
			case "buffer":
				return new string[1] { "Blessings" };
			case "info":
				if (p.Pages.Count != 0)
				{
					return p.Pages.Select(Title).ToArray();
				}
				return new string[1] { "Info" };
			case "feedback":
				return new string[1] { "Feedback" };
			default:
				return null;
			}
		}

		public static void Rows(NpcProfile p, int tab, List<Row> rows, Action<int, bool, Action<int, bool>> pay)
		{
			switch (p.Role)
			{
			case "gambler":
				Gambler(p, rows, pay);
				break;
			case "buffer":
				Buffer(p, rows, pay);
				break;
			case "info":
				Info(p, tab, rows);
				break;
			case "feedback":
				Feedback(rows);
				break;
			}
		}

		private static void Refresh(Response r)
		{
			if (!string.IsNullOrEmpty(r.Message))
			{
				ClientState.Notify(r.Message);
			}
			NpcPanel.Refresh();
		}

		private static void Gambler(NpcProfile p, List<Row> rows, Action<int, bool, Action<int, bool>> pay)
		{
			GamblerTable t = p.Gambler;
			List<LootEntry> list = t.Loot.Where((LootEntry l) => !string.IsNullOrEmpty(l.Item) && l.Weight > 0f).ToList();
			float num = list.Sum((LootEntry l) => l.Weight);
			rows.Add(new Row
			{
				Header = true,
				Label = $"One roll costs {t.Price} coins" + ((t.DailyLimit > 0) ? $"  ·  {t.DailyLimit} rolls a day" : ""),
				ButtonText = "Roll",
				OnClick = delegate
				{
					pay(t.Price, arg2: false, delegate(int paid, bool bank)
					{
						ClientState.Send("gambler.roll", new GamblerRollRequest
						{
							ProfileId = p.Id,
							PaidCoins = paid,
							FromBank = bank
						}, Refresh);
					});
				}
			});
			foreach (LootEntry item in list.OrderByDescending((LootEntry l) => l.Weight))
			{
				rows.Add(new Row
				{
					Icon = InventoryHelper.Icon(item.Item),
					Label = $"{item.Amount}x {InventoryHelper.DisplayName(item.Item)}" + ((item.Quality > 1) ? $" ★{item.Quality}" : "") + $"   {item.Weight / num * 100f:0.#}%"
				});
			}
			if (list.Count == 0)
			{
				rows.Add(new Row
				{
					Label = "The prize table is empty."
				});
			}
		}

		private static void Buffer(NpcProfile p, List<Row> rows, Action<int, bool, Action<int, bool>> pay)
		{
			ConfigBundle config = ClientState.Config;
			for (int i = 0; i < p.Buffs.Count; i++)
			{
				BuffOffer o = p.Buffs[i];
				int index = i;
				BuffDef buff = config.GetBuff(o.Buff);
				bool flag = !string.IsNullOrWhiteSpace(o.Requires) && (!((Object)(object)ZoneSystem.instance != (Object)null) || !ZoneSystem.instance.GetGlobalKey(o.Requires.Trim()));
				string text = buff?.Name ?? o.Buff;
				string text2 = ((o.Seconds >= 3600f) ? $"{o.Seconds / 3600f:0.#} h" : $"{o.Seconds / 60f:0} min");
				rows.Add(new Row
				{
					Icon = ((buff != null) ? InventoryHelper.Icon(buff.Icon) : null),
					Label = text + " — " + text2 + " — " + ((o.Price > 0) ? (o.Price + " coins") : "free") + (flag ? "  (locked)" : ""),
					ButtonText = (flag ? null : "Bless me"),
					OnClick = (flag ? null : ((Action<int>)delegate
					{
						pay(o.Price, arg2: false, delegate(int paid, bool bank)
						{
							ClientState.Send("buffer.buy", new BufferBuyRequest
							{
								ProfileId = p.Id,
								Index = index,
								PaidCoins = paid,
								FromBank = bank
							}, Refresh);
						});
					}))
				});
				if (buff != null && !string.IsNullOrEmpty(buff.Description))
				{
					rows.Add(new Row
					{
						Label = "   " + buff.Description
					});
				}
			}
			if (p.Buffs.Count == 0)
			{
				rows.Add(new Row
				{
					Label = "No blessings on offer."
				});
			}
		}

		private static void Info(NpcProfile p, int tab, List<Row> rows)
		{
			Dictionary<string, string> source = ClientState.Config?.InfoPages ?? new Dictionary<string, string>();
			if (p.Pages.Count == 0 || tab < 0 || tab >= p.Pages.Count)
			{
				rows.Add(new Row
				{
					Label = "This NPC has no pages set. Add 'pages: [name]' to its profile and info/name.md on the server."
				});
				return;
			}
			string key = p.Pages[tab];
			string value = source.FirstOrDefault((KeyValuePair<string, string> kv) => string.Equals(kv.Key, key, StringComparison.OrdinalIgnoreCase)).Value;
			if (string.IsNullOrEmpty(value))
			{
				rows.Add(new Row
				{
					Label = "Page '" + key + "' wasn't found in the server's info folder."
				});
				return;
			}
			string[] array = value.Replace("\r", "").Split(new char[1] { '\n' });
			for (int num = 0; num < array.Length; num++)
			{
				string text = array[num].TrimEnd(Array.Empty<char>());
				if (text.Length == 0)
				{
					rows.Add(new Row
					{
						Label = ""
					});
					continue;
				}
				bool flag = text.StartsWith("#");
				if (flag)
				{
					text = text.TrimStart(new char[1] { '#' }).Trim();
				}
				foreach (string item in Wrap(text, 95))
				{
					rows.Add(new Row
					{
						Label = item,
						Header = flag
					});
				}
			}
		}

		private static void Feedback(List<Row> rows)
		{
			List<string> obj = ClientState.Config?.Settings.Feedback.Categories ?? new List<string> { "Bug", "Player report", "Idea" };
			rows.Add(new Row
			{
				Header = true,
				Label = "Tell the admins what happened. Type below, then pick a category to send."
			});
			string text = "";
			rows.Add(new Row
			{
				Label = "Your message",
				OnText = delegate(string t)
				{
					text = t;
				}
			});
			foreach (string item in obj)
			{
				string category = item;
				rows.Add(new Row
				{
					Label = "Send as: " + item,
					ButtonText = "Send",
					OnClick = delegate
					{
						//IL_001d: 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_0022: Unknown result type (might be due to invalid IL or missing references)
						//IL_004b: Unknown result type (might be due to invalid IL or missing references)
						//IL_0057: Unknown result type (might be due to invalid IL or missing references)
						Vector3 val = (Object.op_Implicit((Object)(object)Player.m_localPlayer) ? ((Component)Player.m_localPlayer).transform.position : Vector3.zero);
						ClientState.Send("feedback.submit", new FeedbackRequest
						{
							Category = category,
							Text = text,
							X = val.x,
							Z = val.z
						}, delegate(Response r)
						{
							ClientState.Notify(r.Message);
							if (r.Ok)
							{
								NpcPanel.Close();
							}
						});
					}
				});
			}
		}

		private static string Title(string page)
		{
			if (!string.IsNullOrEmpty(page))
			{
				return char.ToUpper(page[0]) + page.Substring(1).Replace('_', ' ');
			}
			return "Info";
		}

		private static IEnumerable<string> Wrap(string line, int width)
		{
			if (line.Length <= width)
			{
				yield return line;
				yield break;
			}
			string[] array = line.Split(new char[1] { ' ' });
			string text = "";
			string[] array2 = array;
			foreach (string w in array2)
			{
				if (text.Length + w.Length + 1 > width && text.Length > 0)
				{
					yield return text;
					text = "";
				}
				text = ((text.Length == 0) ? w : (text + " " + w));
			}
			if (text.Length > 0)
			{
				yield return text;
			}
		}
	}
	internal static class Views
	{
		private static MarketState _market;

		private static BankStateView _bank;

		public static void OnOpen(NpcProfile p)
		{
			_market = null;
			_bank = null;
			if (p.Role == "marketplace")
			{
				ClientState.Send("market.browse", null, OnMarket);
			}
			if (p.Role == "banker")
			{
				ClientState.Send("bank.state", null, OnBank);
			}
			WorldViews.OnOpen(p);
			if (p.Role == "chronicler")
			{
				ChroniclerView.OnOpen();
			}
		}

		public static string[] Tabs(NpcProfile p)
		{
			return p.Role switch
			{
				"marketplace" => new string[3] { "Browse", "Sell", "My listings" }, 
				"trader" => new string[2] { "Buy", "Sell" }, 
				"banker" => new string[2] { "Coins", "Vault" }, 
				"teleporter" => new string[1] { "Destinations" }, 
				"quest" => new string[2] { "Quests", "In progress" }, 
				"chronicler" => ChroniclerView.Tabs(), 
				_ => ServiceViews.Tabs(p) ?? WorldViews.Tabs(p) ?? new string[1] { "Talk" }, 
			};
		}

		public static bool UsesPayment(NpcProfile p)
		{
			if (!(p.Role == "marketplace") && !(p.Role == "trader") && !(p.Role == "teleporter") && !(p.Role == "gambler") && !(p.Role == "buffer") && !(p.Role == "transmog"))
			{
				return p.Role == "chronicler";
			}
			return true;
		}

		public static string Status(NpcProfile p)
		{
			if (p.Role == "quest")
			{
				return $"Journal: {QuestClient.State.Active.Count}/{QuestClient.State.MaxActive} quests";
			}
			string text = $"You carry {InventoryHelper.CountCoins()} coins";
			BankStateView bank = _bank;
			int? num = ((bank != null) ? new int?(bank.Coins) : _market?.BankCoins);
			if (num.HasValue)
			{
				text += $"  ·  Bank: {num.Value} coins";
			}
			if (p.Role == "marketplace" && _market != null)
			{
				text += $"  ·  Sale tax {_market.SaleTaxPercent:0.#}%";
			}
			return text;
		}

		public static void Rows(NpcProfile p, int tab, List<Row> rows)
		{
			switch (p.Role)
			{
			case "marketplace":
				switch (tab)
				{
				case 0:
					MarketBrowse(rows);
					break;
				case 1:
					MarketSell(rows);
					break;
				default:
					MarketMine(rows);
					break;
				}
				break;
			case "trader":
				if (tab == 0)
				{
					TraderBuy(p, rows);
				}
				else
				{
					TraderSell(p, rows);
				}
				break;
			case "banker":
				if (tab == 0)
				{
					BankCoins(rows);
				}
				else
				{
					BankVault(rows);
				}
				break;
			case "teleporter":
				Teleports(p, rows);
				break;
			case "quest":
				if (tab == 0)
				{
					QuestsAvailable(p, rows);
				}
				else
				{
					QuestsActive(p, rows);
				}
				break;
			case "leaderboard":
			case "transmog":
				WorldViews.Rows(p, tab, rows, Pay);
				break;
			case "chronicler":
				ChroniclerView.Rows(tab, rows, Pay);
				break;
			default:
				ServiceViews.Rows(p, tab, rows, delegate(int price, bool _, Action<int, bool> send)
				{
					Pay(price, send);
				});
				break;
			}
		}

		private static void QuestsAvailable(NpcProfile p, List<Row> rows)
		{
			ConfigBundle config = ClientState.Config;
			PlayerStateView state = QuestClient.State;
			ConditionContext conditionContext = QuestClient.Conditions();
			if (ClientState.IsLocalAdmin)
			{
				rows.Add(new Row
				{
					Header = true,
					Label = "Admin: make or edit this NPC's quests",
					ButtonText = "Quest Designer",
					OnClick = delegate
					{
						QuestDesigner.Open();
					}
				});
			}
			foreach (QuestDef item in config.Quests.Where((QuestDef q) => string.Equals(q.Giver, p.Id, StringComparison.OrdinalIgnoreCase)))
			{
				if (state.IsActive(item.Id))
				{
					continue;
				}
				string text = QuestRules.Availability(item, state, conditionContext.HasKey, DateTime.UtcNow);
				if (text == "Already completed." || text == "Not unlocked yet.")
				{
					continue;
				}
				QuestDef quest = item;
				rows.Add(new Row
				{
					Header = true,
					Label = item.Name + ((text != null) ? ("  (" + text + ")") : ""),
					ButtonText = ((text == null) ? "Accept" : null),
					OnClick = ((text == null) ? ((Action<int>)delegate
					{
						ClientState.Send("quest.accept", new QuestAcceptRequest
						{
							QuestId = quest.Id,
							ProfileId = p.Id
						}, OnQuest);
					}) : null)
				});
				if (!string.IsNullOrEmpty(item.Description))
				{
					rows.Add(new Row
					{
						Label = "   " + item.Description
					});
				}
				foreach (QuestStep step in item.Steps)
				{
					rows.Add(new Row
					{
						Label = "   • " + QuestRules.Describe(step)
					});
				}
				string text2 = RewardText(item.Rewards);
				if (text2.Length > 0)
				{
					rows.Add(new Row
					{
						Label = "   Reward: " + text2
					});
				}
			}
			if (!rows.Any((Row r) => r.Header && r.ButtonText != "Quest Designer"))
			{
				rows.Add(new Row
				{
					Label = "Nothing to offer right now."
				});
			}
		}

		private static void QuestsActive(NpcProfile p, List<Row> rows)
		{
			foreach (ActiveQuestView item in QuestClient.State.Active)
			{
				ActiveQuestView quest = item;
				bool flag = string.IsNullOrEmpty(item.TurnIn) || string.Equals(item.TurnIn, p.Id, StringComparison.OrdinalIgnoreCase);
				bool ready = flag && QuestClient.CanTurnIn(item);
				rows.Add(new Row
				{
					Header = true,
					Label = item.Name + (flag ? "" : "  (hand in elsewhere)"),
					ButtonText = (ready ? "Turn in" : "Abandon"),
					OnClick = delegate
					{
						if (ready)
						{
							QuestClient.TurnIn(quest, p.Id, OnQuest);
						}
						else
						{
							ClientState.Send("quest.abandon", new QuestIdRequest
							{
								QuestId = quest.Id
							}, OnQuest);
						}
					}
				});
				foreach (StepView step in item.Steps)
				{
					int num = (QuestStepTypes.IsLive(step.Type) ? step.Have : Mathf.Min(step.Need, InventoryHelper.Count(step.Target)));
					rows.Add(new Row
					{
						Label = string.Format("   {0} {1}   {2}/{3}", (num >= step.Need) ? "✓" : "•", step.Text, num, step.Need)
					});
				}
			}
			if (rows.Count == 0)
			{
				rows.Add(new Row
				{
					Label = "No quests in progress."
				});
			}
		}

		private static void OnQuest(Response r)
		{
			if (!string.IsNullOrEmpty(r.Message))
			{
				ClientState.Notify(r.Message);
			}
			NpcPanel.Refresh();
		}

		private static string RewardText(QuestRewards rw)
		{
			List<string> list = new List<string>();
			foreach (RewardItem item in rw.Items)
			{
				list.Add($"{item.Amount}x {InventoryHelper.DisplayName(item.Item)}");
			}
			if (rw.Coins > 0)
			{
				list.Add($"{rw.Coins} coins" + (rw.CoinsToBank ? " (to bank)" : ""));
			}
			if (!string.IsNullOrEmpty(rw.Skill) && rw.SkillXp > 0f)
			{
				list.Add(rw.Skill + " experience");
			}
			if (!string.IsNullOrEmpty(rw.Buff))
			{
				list.Add(rw.Buff);
			}
			return string.Join(", ", list);
		}

		private static void OnMarket(Response r)
		{
			if (r.State != null)
			{
				_market = ((JToken)r.State).ToObject<MarketState>();
			}
			if (!string.IsNullOrEmpty(r.Message))
			{
				ClientState.Notify(r.Message);
			}
			NpcPanel.Refresh();
		}

		private static void PayoutRow(List<Row> rows)
		{
			if (_market != null && (_market.PayoutCoins != 0 || _market.PayoutItems != 0))
			{
				rows.Add(new Row
				{
					Header = true,
					Label = $"Waiting for you: {_market.PayoutCoins} coins, {_market.PayoutItems} returned item(s)",
					ButtonText = "Collect",
					OnClick = delegate
					{
						ClientState.Send("market.collect", null, OnMarket);
					}
				});
			}
		}

		private static void MarketBrowse(List<Row> rows)
		{
			if (_market == null)
			{
				rows.Add(new Row
				{
					Label = "Loading listings..."
				});
				return;
			}
			PayoutRow(rows);
			foreach (ListingView listing2 in _market.Listings)
			{
				ListingView listing = listing2;
				rows.Add(new Row
				{
					Icon = InventoryHelper.Icon(listing2.Item.Prefab),
					Label = $"{ItemLabel(listing2.Item)} — {listing2.Price} coins · {listing2.SellerName} · {TimeLeft(listing2.HoursLeft)}",
					ButtonText = (listing2.Mine ? "Cancel" : "Buy"),
					OnClick = delegate
					{
						if (listing.Mine)
						{
							ClientState.Send("market.cancel", new MarketIdRequest
							{
								ListingId = listing.Id
							}, OnMarket);
						}
						else
						{
							Pay(listing.Price, delegate(int paid, bool bank)
							{
								ClientState.Send("market.buy", new MarketBuyRequest
								{
									ListingId = listing.Id,
									Price = listing.Price,
									PaidCoins = paid,
									FromBank = bank
								}, OnMarket);
							});
						}
					}
				});
			}
		}

		private static void MarketSell(List<Row> rows)
		{
			float feePct = ClientState.Config.Settings.Marketplace.ListingFeePercent;
			rows.Add(new Row
			{
				Header = true,
				Label = $"Type a price and press List. Fee {feePct:0.#}% now, tax {ClientState.Config.Settings.Marketplace.SaleTaxPercent:0.#}% when it sells."
			});
			Player localPlayer = Player.m_localPlayer;
			Inventory val = ((localPlayer != null) ? ((Humanoid)localPlayer).GetInventory() : null);
			if (val == null)
			{
				return;
			}
			string currency = InventoryHelper.Currency;
			foreach (ItemData item in (from i in val.GetAllItems()
				where !i.m_equipped
				orderby i.m_shared.m_name
				select i).ToList())
			{
				if (string.Equals(InventoryHelper.PrefabName(item), currency, StringComparison.OrdinalIgnoreCase))
				{
					continue;
				}
				ItemData target = item;
				rows.Add(new Row
				{
					Icon = item.GetIcon(),
					Label = ItemLabel(InventoryHelper.ToRecord(item)),
					InputPlaceholder = "price",
					ButtonText = "List",
					OnClick = delegate(int price)
					{
						if (price < 1)
						{
							ClientState.Notify("Type a price first.");
						}
						else
						{
							int num = (int)Math.Ceiling((float)price * feePct / 100f);
							if (!InventoryHelper.TakeCoins(num))
							{
								ClientState.Notify($"Listing costs a {num} coin fee.");
							}
							else
							{
								ItemRecord itemRecord = InventoryHelper.TakeStack(target);
								if (itemRecord == null)
								{
									InventoryHelper.Give(ItemRecord.Plain(currency, num));
									ClientState.Notify("That item is no longer in your inventory.");
									NpcPanel.Refresh();
								}
								else
								{
									ClientState.Send("market.list", new MarketListRequest
									{
										Item = itemRecord,
										Price = price,
										PaidFee = num
									}, OnMarket);
								}
							}
						}
					}
				});
			}
		}

		private static void MarketMine(List<Row> rows)
		{
			if (_market == null)
			{
				rows.Add(new Row
				{
					Label = "Loading listings..."
				});
				return;